有关seata的安装,请参看博客:https://blog.csdn.net/lianghecai52171314/article/details/127330916

业务需求:下单—减库存—扣钱—改订单状态

当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存;再通过远程调用账户服务来扣减用户账户里面的余额;最后订单服务中修改订单状态。

需要涉及到三个模块:

模块名 数据库名 port Context-path Application-name
fafu-account seata_account 3001 fafu Fafu-account-3001
fafu-storage seata_storage 4001 fafu Fafu-storage-4001
fafu-order seata_order 5001 fafu Fafu-order-5001

可以看出:该操作需要跨域三个数据库,有两次远程调用,需要用到分布式事务技术。

数据库准备

创建seata_order数据库,然后在其中创建表tb_order:

CREATE TABLE `tb_order`  (`id` bigint UNSIGNED NOT NULL COMMENT '订单编号',`user_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '用户编号',`sku_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '商品编号',`count` int UNSIGNED NULL DEFAULT NULL COMMENT '数量',`money` decimal(10, 3) UNSIGNED NULL DEFAULT NULL COMMENT '金额',`state` int UNSIGNED NULL DEFAULT NULL COMMENT '状态',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
上面表的设计只是模拟性质的,大概对应的是拼多多的业务,一个订单中只能购买1种商品,但这种商品可以购买多个。

创建seata_storage数据库,然后在其中创建表tb_storage

CREATE TABLE `tb_storage`  (`id` bigint UNSIGNED NOT NULL COMMENT '编号',`sku_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '商品编号',`total` int UNSIGNED NULL DEFAULT NULL COMMENT '总库存',`used` int UNSIGNED NULL DEFAULT NULL COMMENT '已用库存',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

创建seata_account数据库,然后在其中创建表tb_account:

CREATE TABLE `tb_account`  (`id` bigint UNSIGNED NOT NULL COMMENT '编号',`total` decimal(11, 3) UNSIGNED NULL DEFAULT NULL COMMENT '总额度',`used` decimal(11, 3) UNSIGNED NULL DEFAULT NULL COMMENT '已用额度',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

依次在三个数据库创建undo_log 表,用于保存需要回滚的数据:

CREATE TABLE `undo_log`  (`id` bigint NOT NULL AUTO_INCREMENT,`branch_id` bigint NOT NULL,`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,`ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE,UNIQUE INDEX `ux_undo_log`(`xid` ASC, `branch_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

:每个服务对应的业务数据库都需要创建这个日志表。

项目共同部分

Nacos中

在Nacos添加给客户端加载使用的配置seata-client-demo.yml:

  • Data Id:seata-client-demo.yml
  • Group:SEATA_GROUP

注意:上面配置中的group和nampespace都是在Nacos中创建命名空间时创建的。
上面配置的具体内容如下:

# seata配置
seata:enabled: trueapplication-id: seata-cilent-deme# Seata 事务组编号,此处需于 seata 相同tx-service-group: default-tx-groupconfig:type: nacosnacos:# nacos ip地址server-addr: 127.0.0.1:8848group: SEATA_GROUPnamespace: 99970600-6f67-43fd-a2a2-4e9795947bf4data-id: seata-server.propertiesregistry:type: nacosnacos:application: seata-server# nacos ip地址server-addr: 127.0.0.1:8848group: SEATA_GROUPnamespace: 99970600-6f67-43fd-a2a2-4e9795947bf4

依赖

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency><!-- 持久层相关 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--  nacos自从2020版本之后不再整合的是Netflix,也就没有ribbon了 -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- sentinel配置持久化到Nacos -->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId>
</dependency><dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.5.2</version>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><exclusions><exclusion><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId></exclusion></exclusions>
</dependency>

Result

@Getter
@ToString
public class Result<T> {/*** 请求响应状态码*/private int code;/*** 请求结果描述信息*/private String msg;/*** 请求结果数据*/private T data;public Result<T> setCode(int code) {this.code = code;return this;}public Result<T> setMsg(String msg) {this.msg = msg;return this;}public Result<T> setData(T data) {this.data = data;return this;}public static void main(String[] args) {Result<Object> result = new Result<>().addData("a", "aaa", "b", 123, "c", new Date());System.out.println(result);}/*** 将key-value形式的成对出现的参数转换为JSON** @param objs* @return*/public Result<T> addData(Object... objs) {if (objs.length % 2 != 0) {throw new RuntimeException("参数个数不对");}for (int i = 0; i < objs.length; i += 2) {if (!(objs[i] instanceof String)) {throw new RuntimeException("奇数参数必须为字符串");}}Map<String, Object> map = new HashMap<>();for (int i = 0; i < objs.length; i += 2) {map.put((String) objs[i], objs[i + 1]);}this.data = (T) map;return this;}
}

ResultUtil

public class ResultUtil {public static <T> Result<T> success() {Result<T> result = new Result<>();result.setCode(200);result.setMsg("成功");return result;}public static <T> Result<T> success(Integer code, String msg) {Result<T> result = new Result<>();result.setCode(code);result.setMsg(msg);return result;}public static <T> Result<T> success(T data) {Result<T> result = new Result<>();result.setCode(200);result.setMsg("成功");result.setData(data);return result;}public static <T> Result<T> error() {Result<T> result = new Result<>();result.setCode(500);result.setMsg("失败");return result;}public static <T> Result<T> error(Integer code, String msg) {Result<T> result = new Result<>();result.setCode(code);result.setMsg(msg);return result;}public static void main(String[] args) {Result error = ResultUtil.error().addData("a", "aaaa", "b", LocalDateTime.now(), "c", true);System.out.println(error);//ResultBean res = ResultBeanUtil.success(DataUtil.build(UserVO.class));//System.out.println(res);}
}

druid数据源代理配置

  • MyBatis版本
@Configuration
public class DataSourceProxyConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource")public DataSource druidDataSource() {DruidDataSource druidDataSource = new DruidDataSource();return druidDataSource;}@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(dataSource);factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));return factoryBean.getObject();}
}
  • MyBatisPlus版本
@Configuration
public class DataSourceProxyConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource.druid")public DataSource druidDataSource() {DruidDataSource druidDataSource = new DruidDataSource();return druidDataSource;}
}

在2.2.0.RELEASE及以后,数据源代理自动实现了,不需要再手动去配置一个代理类。

seata-account-3001

第一步;创建项目,添加依赖,修改application.yml文件:

server:port: 3001servlet:context-path: /seataspring:application:name: seata-account-3001#配置数据源datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/seata_account?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&failOverReadOnly=falseusername: rootpassword: root# 连接池的配置信息:初始化大小,最小,最大initial-size: 8min-idle: 1max-active: 20# 配置获取连接等待超时的时间max-wait: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒time-between-eviction-runs-millis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒min-evictable-idle-time-millis: 300000#验证库是否正常sqlvalidation-query: select 'x' from dual#空闲时验证,防止连接断开test-while-idle: truetest-on-borrow: falsetest-on-return: false# 打开PSCache,并且指定每个连接上PSCache的大小pool-prepared-statements: truemax-open-prepared-statements: 20max-pool-prepared-statement-per-connection-size: 20# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙filters: stat,wall,slf4j# 通过connectProperties属性来打开mergeSql功能;慢SQL记录connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000# 合并多个DruidDataSource的监控数据use-global-data-source-stat: truecloud:nacos:discovery:server-addr: 127.0.0.1:8848 # 配置nacos地址sentinel:transport:# 配置sentinel dashboard地址dashboard: 127.0.0.1:8080# 默认端口8719,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口port: 8719config:import:- optional:nacos:seata-client-demo.ymlseata:service:vgroup-mapping:default_tx_group: defaultgrouplist:default: 127.0.0.1:8091management:endpoints:web:exposure:include: '*' #暴露出所有的端点#MyBatisPlus相关配置
mybatis-plus:#mapper映射文件位置,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)mapper-locations: classpath:mapper/*.xml#实体扫描,多个package用逗号或者分号分隔typeAliasesPackage: com.seata.account.domain#  以下配置均有默认值,可以不设置global-config:db-config:#主键类型id-type: autotable-underline: true#数据库大写下划线转换capital-mode: trueconfiguration:# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射map-underscore-to-camel-case: truecache-enabled: false#配置JdbcTypeForNulljdbc-type-for-null: 'null'# 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段call-setters-on-nulls: true# 将执行的sql打印出来,在开发或测试的时候可以用log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

第二步:项目主启动类:

@EnableDiscoveryClient
@SpringBootApplication
public class SeataAccountApplication {public static void main(String[] args) {SpringApplication.run(SeataAccountApplication.class, args);}
}

第三步:利用MyBatisCodeHelperPro生成Account表对应的实体类、Mapper、Service代码。

第四步:控制器AccountController

@RestController
@RequestMapping("/account")
public class AccountController {@Resourceprivate AccountService accountService;@PostMapping("/decrease")Result decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) {//模拟异常,全局事务回滚// System.out.println(3/0);      //--------------------①accountService.update(Wrappers.<Account>lambdaUpdate().eq(Account::getId, userId).setSql("used = used + " + money));return ResultUtil.success().setMsg("扣款成功");}
}

启动项目,在Nacos中会看到:

post方式请求:http://localhost:3001/seata/account/decrease?userId=1&money=15
能够正常访问。

seata-storage-4001

第一步;创建项目,添加依赖,修改application.yml文件:

server:port: 4001servlet:context-path: /seataspring:application:name: seata-storage-4001#配置数据源datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/seata_storage?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&failOverReadOnly=falseusername: rootpassword: root# 连接池的配置信息:初始化大小,最小,最大initial-size: 8min-idle: 1max-active: 20# 配置获取连接等待超时的时间max-wait: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒time-between-eviction-runs-millis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒min-evictable-idle-time-millis: 300000#验证库是否正常sqlvalidation-query: select 'x' from dual#空闲时验证,防止连接断开test-while-idle: truetest-on-borrow: falsetest-on-return: false# 打开PSCache,并且指定每个连接上PSCache的大小pool-prepared-statements: truemax-open-prepared-statements: 20max-pool-prepared-statement-per-connection-size: 20# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙filters: stat,wall,slf4j# 通过connectProperties属性来打开mergeSql功能;慢SQL记录connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000# 合并多个DruidDataSource的监控数据use-global-data-source-stat: truecloud:nacos:discovery:server-addr: 127.0.0.1:8848 # 配置nacos地址sentinel:transport:# 配置sentinel dashboard地址dashboard: 127.0.0.1:8080# 默认端口8719,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口port: 8719config:import:- optional:nacos:seata-client-demo.ymlseata:service:vgroup-mapping:default_tx_group: defaultgrouplist:default: 127.0.0.1:8091management:endpoints:web:exposure:include: '*' #暴露出所有的端点#MyBatisPlus相关配置
mybatis-plus:#mapper映射文件位置,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)mapper-locations: classpath:mapper/*.xml#实体扫描,多个package用逗号或者分号分隔typeAliasesPackage: com.seata.storage.domain#  以下配置均有默认值,可以不设置global-config:db-config:#主键类型id-type: autotable-underline: true#数据库大写下划线转换capital-mode: trueconfiguration:# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射map-underscore-to-camel-case: truecache-enabled: false#配置JdbcTypeForNulljdbc-type-for-null: 'null'# 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段call-setters-on-nulls: true# 将执行的sql打印出来,在开发或测试的时候可以用log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

第二步:项目主启动类添加注解

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动创建
public class SeataStorageApplication {public static void main(String[] args) {SpringApplication.run(SeataStorageApplication.class, args);}
}

第三步:利用MyBatisCodeHelperPro生成Storage表对应的实体类、Mapper、Service代码。

第四步:控制器StorageController

@RestController
@RequestMapping("/storage")
public class StorageController {@Resourceprivate StorageService storageService;@PostMapping("/decrease")Result decrease(@RequestParam("skuId") Long skuId, @RequestParam("count") Integer count) {storageService.update(Wrappers.<Storage>lambdaUpdate().eq(Storage::getSkuId, skuId).setSql("used= used+" + count));return ResultUtil.success().setMsg("库存扣减成功");}
}

启动项目,在Nacos中会看到:


post方式请求:http://localhost:4001/seata/storage/decrease?skuId=1&money=3
能够正常访问。

seata-order-5001

第一步;创建项目,添加依赖,修改application.yml文件:

server:port: 5001servlet:context-path: /seataspring:application:name: seata-order-5001#配置数据源datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/seata_order?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&failOverReadOnly=falseusername: rootpassword: root# 连接池的配置信息:初始化大小,最小,最大initial-size: 8min-idle: 1max-active: 20# 配置获取连接等待超时的时间max-wait: 60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒time-between-eviction-runs-millis: 60000# 配置一个连接在池中最小生存的时间,单位是毫秒min-evictable-idle-time-millis: 300000#验证库是否正常sqlvalidation-query: select 'x' from dual#空闲时验证,防止连接断开test-while-idle: truetest-on-borrow: falsetest-on-return: false# 打开PSCache,并且指定每个连接上PSCache的大小pool-prepared-statements: truemax-open-prepared-statements: 20max-pool-prepared-statement-per-connection-size: 20# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙filters: stat,wall,slf4j# 通过connectProperties属性来打开mergeSql功能;慢SQL记录connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000# 合并多个DruidDataSource的监控数据use-global-data-source-stat: truecloud:nacos:discovery:server-addr: 127.0.0.1:8848 # 配置nacos地址sentinel:transport:# 配置sentinel dashboard地址dashboard: 127.0.0.1:8080# 默认端口8719,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口port: 8719config:import:- optional:nacos:seata-client-demo.ymlseata:service:vgroup-mapping:default_tx_group: defaultgrouplist:default: 127.0.0.1:8091management:endpoints:web:exposure:include: '*' #暴露出所有的端点#MyBatisPlus相关配置
mybatis-plus:#mapper映射文件位置,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)mapper-locations: classpath:mapper/*.xml#实体扫描,多个package用逗号或者分号分隔typeAliasesPackage: com.seata.order.domain#  以下配置均有默认值,可以不设置global-config:db-config:#主键类型id-type: autotable-underline: true#数据库大写下划线转换capital-mode: trueconfiguration:# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射map-underscore-to-camel-case: truecache-enabled: false#配置JdbcTypeForNulljdbc-type-for-null: 'null'# 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段call-setters-on-nulls: true# 将执行的sql打印出来,在开发或测试的时候可以用log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

第二步:项目主启动类上添加注解:

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动创建
public class SeataOrderApplication {public static void main(String[] args) {SpringApplication.run(SeataOrderApplication.class, args);}
}

第三步:利用MyBatisCodeHelperPro生成Order表对应的实体类、Mapper、Service代码。

第四步:在feign包下创建:

  • AccountFeignService.java
@FeignClient(name = "seata-account-3001", path = "/seata")
@RequestMapping("/account")
public interface AccountFeignService {/*** 将skuId对应的商品的库存减少count* @param userId* @param money* @return*/@PostMapping("/decrease")Result decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
  • StorageFeignService.java
@FeignClient(name = "seata-storage-4001", path = "/seata")
@RequestMapping("/storage")
public interface StorageFeignService {/*** 将skuId对应的商品的库存减少count* @param skuId* @param count* @return*/@PostMapping("/decrease")Result decrease(@RequestParam("skuId") Long skuId, @RequestParam("count") Integer count);
}

第五步:修改OrderServiceImpl的代码如下所示:

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {@Resourceprivate OrderMapper orderMapper;@Resourceprivate StorageFeignService storageFeignService;@Resourceprivate AccountFeignService accountFeignService;@Override//  @GlobalTransactional(rollbackFor = Exception.class)  //全局异常   //----------------②public void create(Order order) {System.out.println("user建订单");orderMapper.insert(order);System.out.println("扣减库存");storageFeignService.decrease(order.getSkuId(),order.getCount());System.out.println("账户余额扣减");accountFeignService.decrease(order.getUserId(),order.getMoney());System.out.println("修改订单的状态");//1表示完成状态orderMapper.updateStateByUserId(order.getUserId(),1);System.out.println("下单结束");}
}

第六步:控制器OrderController

@RestController
@RequestMapping("/order")
public class OrderController {@Resourceprivate OrderService orderService;@GetMapping("/create")public Result create(Order order){orderService.create(order);return ResultUtil.success().setMsg("下单成功!");}
}

启动项目:

测试

url:http://localhost:5001/seata/order/create?userId=1&skuId=1&count=10&money=30

  • 保持编号①和编号②处于注释状态:请求url,tb_order表增加了一条记录,用户账户用掉30,库存用掉10。
  • 保持编号②处于注释状态,去掉编号①前的注释:请求url,tb_order表增加了一条记录但状态值为null,用户账户没有发生变化,库存用掉10。没有能够保证数据的完整性
  • 去掉编号①和编号②前的注释:请求url,三个表的数据都没有发生变化。

结论:
微服务中使用alibaba的seata技术实现全局事务控制只需要在相应的Service实现类中添加@GlobalTransactional注解就可以了。

【精品】seata综合示例:订单-库存-扣款相关推荐

  1. SAP MM 销售订单库存与普通库存之间相互转换过账后对于EBEWH以及MBEWH表的更新

    SAP MM 销售订单库存与普通库存之间相互转换过账后对于EBEWH以及MBEWH表的更新 1,DEMO数据 物料号:1300009995 工厂:2160 销售订单号/item号:0010097627 ...

  2. make--变量与函数的综合示例 自动生成依赖关系

    一.变量与函数的示例 示例的要求 1.自动生成target文件夹存放可执行文件 2.自动生成objs文件夹存放编译生成的目标文件 3.支持调试版本的编译选项 4.考虑代码的扩展性 完成该示例所需的 1 ...

  3. SAP MM 销售订单库存与普通库存之间相互转换过账后对于EBEWH以及MBEWH表的更新...

    SAP MM 销售订单库存与普通库存之间相互转换过账后对于EBEWH以及MBEWH表的更新 1,DEMO数据 物料号:1300009995 工厂:2160 销售订单号/item号:0010097627 ...

  4. C结构体工具DirectStruct(综合示例二)

    2019独角兽企业重金招聘Python工程师标准>>> C结构体工具DirectStruct(综合示例二) 1.编写定义文件,用工具dsc处理之,自动生成XML转换代码和ESQL代码 ...

  5. 【Demo】修改销售订单库存地点

    当我们使用BAPI函数"BAPI_SALESORDER_CHANGE"修改销售订单库存地点时,需要注意的是,若不加上工厂,虽不会报错,但会将库存地点置空. [样例代码] *& ...

  6. 订单库存是否与库存地有关

    问题需求 现在碰到了一个单子,关联销售订单的库存一共100 EA,其中10EA在分公司异地库(在MRP之外的),90EA在公司成品库(库存地参与MRP),但是在MD04中看到,系统是考虑该物料有100 ...

  7. QT综合示例:QT串口通信

    QT综合示例:QT串口通信 0.界面: 1.代码: 如果用qt写程序作为上位机,然后通过和usb和下位机通信的时候,就需要用到qt中的串口通信了. 0.界面: 1.代码: 1).pro 添加: QT ...

  8. QT+Halcon综合示例:clip回形针2D位姿检测

    QT+Halcon综合示例(一):clip回形针2D位姿检测 0.halcon源码: 1.Qt代码: 2.运行结果: 下载:clip回形针2D位姿检测 0.halcon源码: * clip.hdev: ...

  9. QT+OpenCV综合示例:图像混合(滑动条)

    QT+OpenCV综合示例:图像混合(滑动条) 1.代码: 2.运行结果: 0)Widget_op.ui (界面文件)设计: 1.代码: 1)opencv_imwrite_Q.pro 添加: INCL ...

最新文章

  1. 百度地图应用开发(一)
  2. html 禁止缩放 ios10,完美解决ios10及以上Safari无法禁止缩放的问题
  3. 分布式系统原理 之5 日志技术
  4. SQL Server 2008安装配置说明书+简单使用 亲测可用
  5. iOS 开发学习之 User Interface(4)UIView 与 UIViewController【二】
  6. 在springboot中,如何读取配置文件中的属性
  7. 一大波干货学习资源分享
  8. 安装j2sdk以后,配置环境变量
  9. 苹果硅:Wintel的过去
  10. bgp (二)改变下一跳本地,
  11. etc 文件夹下放什么内容
  12. 小红书用户画像分析_用户画像,该怎么分析?
  13. MSI(Message Signaled Interrupt)/MSI-X
  14. 在 Microsoft Windows 平台上安装 JDK 17
  15. Base64方式上传文件
  16. GEE用户贡献的数据列表
  17. 映射和反射的概念及应用方法
  18. QVGA、WVGA、VGA、WQVGA、SQVGA等几种手机分辨率扫盲
  19. 项目管理最佳实践:项目管理案例
  20. 计算机组装与维修最大的收获,计算机维修与维护论文3000字_计算机组装与维修心得体会1000字_计算机组装与维护心得...

热门文章

  1. unity3d 凹凸贴图、法线贴图、置换贴图 (一)
  2. vue - vue表单中v-model和:value的区别
  3. yolov5 代码解读 损失函数 loss.py
  4. 【Scratch考级99图】图14-等级考试scratch绘制复杂图形花朵图案
  5. css里面li标签怎么加图片_css 点击ul切换其中li标签内的图片
  6. wallpaper破解解决方法
  7. 程序员的十个层次,你属于哪一层?
  8. 【latex】\pdfendlink ended up in different nesting level than \pdfstartlink
  9. rabbitmq使用
  10. 24个节气之美,每一个都如诗如画!