当前位置:   article > 正文

spring cloud alibaba开发笔记十三(订单微服务,openFeign,seate的实践应用)_seate pom依赖

seate pom依赖

首先创建订单微服务e-commerce-order-service

pom

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>e-commerce-service</artifactId>
  7. <groupId>com.taluohui.ecommerce</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>e-commerce-order-service</artifactId>
  12. <version>1.0-SNAPSHOT</version>
  13. <packaging>jar</packaging>
  14. <!-- 模块名及描述信息 -->
  15. <name>e-commerce-order-service</name>
  16. <description>订单服务</description>
  17. <properties>
  18. <maven.compiler.source>8</maven.compiler.source>
  19. <maven.compiler.target>8</maven.compiler.target>
  20. </properties>
  21. <dependencies>
  22. <!-- spring cloud alibaba nacos discovery 依赖 -->
  23. <dependency>
  24. <groupId>com.alibaba.cloud</groupId>
  25. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  26. </dependency>
  27. <!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin-->
  28. <dependency>
  29. <groupId>org.springframework.cloud</groupId>
  30. <artifactId>spring-cloud-starter-zipkin</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.kafka</groupId>
  34. <artifactId>spring-kafka</artifactId>
  35. <version>2.5.0.RELEASE</version>
  36. </dependency>
  37. <!-- Java Persistence API, ORM 规范 -->
  38. <dependency>
  39. <groupId>org.springframework.boot</groupId>
  40. <artifactId>spring-boot-starter-data-jpa</artifactId>
  41. </dependency>
  42. <!-- MySQL 驱动, 注意, 这个需要与 MySQL 版本对应 -->
  43. <dependency>
  44. <groupId>mysql</groupId>
  45. <artifactId>mysql-connector-java</artifactId>
  46. <version>8.0.12</version>
  47. <scope>runtime</scope>
  48. </dependency>
  49. <!-- SpringCloud Stream + Kafka -->
  50. <dependency>
  51. <groupId>org.springframework.cloud</groupId>
  52. <artifactId>spring-cloud-stream</artifactId>
  53. </dependency>
  54. <dependency>
  55. <groupId>org.springframework.cloud</groupId>
  56. <artifactId>spring-cloud-stream-binder-kafka</artifactId>
  57. </dependency>
  58. <dependency>
  59. <groupId>com.taluohui.ecommerce</groupId>
  60. <artifactId>e-commerce-service-config</artifactId>
  61. <version>1.0-SNAPSHOT</version>
  62. </dependency>
  63. <dependency>
  64. <groupId>com.taluohui.ecommerce</groupId>
  65. <artifactId>e-commerce-service-sdk</artifactId>
  66. <version>1.0-SNAPSHOT</version>
  67. </dependency>
  68. <!-- 集成 hystrix -->
  69. <dependency>
  70. <groupId>org.springframework.cloud</groupId>
  71. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  72. </dependency>
  73. </dependencies>
  74. <!--
  75. SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
  76. SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
  77. -->
  78. <build>
  79. <finalName>${artifactId}</finalName>
  80. <plugins>
  81. <plugin>
  82. <groupId>org.springframework.boot</groupId>
  83. <artifactId>spring-boot-maven-plugin</artifactId>
  84. <executions>
  85. <execution>
  86. <goals>
  87. <goal>repackage</goal>
  88. </goals>
  89. </execution>
  90. </executions>
  91. </plugin>
  92. </plugins>
  93. </build>
  94. </project>

配置文件

  1. server:
  2. port: 8002
  3. servlet:
  4. context-path: /ecommerce-order-service
  5. spring:
  6. main:
  7. allow-bean-definition-overriding: true #允许同名的Bean
  8. application:
  9. name: e-commerce-order-service # 应用名称也是构成 Nacos 配置管理 dataId 字段的一部分 (当 config.prefix 为空时)
  10. cloud:
  11. stream:
  12. kafka:
  13. binder:
  14. brokers: 1.15.247.9:9092
  15. auto-create-topics: true
  16. bindings:
  17. logisticsOutput:
  18. destination: e-commerce-topic # kafka topic
  19. content-type: text/plain
  20. alibaba:
  21. seata:
  22. tx-service-group: imooc-ecommerce # seata 全局事务分组
  23. nacos:
  24. # 服务注册发现
  25. discovery:
  26. enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
  27. server-addr: 1.15.247.9:8848
  28. # server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址
  29. namespace: 22d40198-8462-499d-a7fe-dbb2da958648
  30. metadata:
  31. management:
  32. context-path: ${server.servlet.context-path}/actuator
  33. # 开启 ribbon 重试机制, 即获取服务失败是否从另外一个节点重试
  34. loadbalancer:
  35. retry:
  36. enabled: true
  37. kafka:
  38. bootstrap-servers: 1.15.247.9:9092
  39. producer:
  40. retries: 3
  41. consumer:
  42. auto-offset-reset: latest
  43. sleuth:
  44. sampler:
  45. # ProbabilityBasedSampler 抽样策略
  46. probability: 1.0 # 采样比例, 1.0 表示 100%, 默认是 0.1
  47. # RateLimitingSampler 抽样策略, 设置了限速采集, spring.sleuth.sampler.probability 属性值无效
  48. rate: 100 # 每秒间隔接受的 trace 量
  49. zipkin:
  50. sender:
  51. type: kafka # 默认是 web
  52. base-url: http://1.15.247.9:9411/
  53. jpa:
  54. show-sql: true
  55. hibernate:
  56. ddl-auto: none
  57. properties:
  58. hibernate.show_sql: true
  59. hibernate.format_sql: true
  60. open-in-view: false
  61. datasource:
  62. # 数据源
  63. url: jdbc:mysql://1.15.247.9:3306/ecommerce?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
  64. username: root
  65. password: Cjw970404
  66. type: com.zaxxer.hikari.HikariDataSource
  67. driver-class-name: com.mysql.cj.jdbc.Driver
  68. # 连接池
  69. hikari:
  70. maximum-pool-size: 8
  71. minimum-idle: 4
  72. idle-timeout: 30000
  73. connection-timeout: 30000
  74. max-lifetime: 45000
  75. auto-commit: true
  76. pool-name: ImoocEcommerceHikariCP
  77. # feign 相关的配置
  78. feign:
  79. hystrix:
  80. enabled: true # Hystrix 默认是关闭的
  81. client:
  82. config:
  83. default: # 全局的
  84. connectTimeout: 2000 # 默认的连接超时时间是 10s
  85. readTimeout: 5000
  86. # 暴露端点
  87. management:
  88. endpoints:
  89. web:
  90. exposure:
  91. include: '*'
  92. endpoint:
  93. health:
  94. show-details: always

入口启动类

  1. /**
  2. * <h1>订单微服务启动入口</h1>
  3. * */
  4. @EnableJpaAuditing
  5. @SpringBootApplication
  6. @EnableCircuitBreaker
  7. @EnableFeignClients
  8. @EnableDiscoveryClient
  9. public class OrderApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(OrderApplication.class, args);
  12. }
  13. }

订单的sql,这边只是简单的演示,还有很多字段,可以自行添加

  1. -- 创建 t_ecommerce_order 数据表
  2. CREATE TABLE IF NOT EXISTS `ecommerce`.`t_ecommerce_order` (
  3. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  4. `user_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '用户 id',
  5. `address_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '用户地址记录 id',
  6. `order_detail` text NOT NULL COMMENT '订单详情(json 存储, goodsId, count)',
  7. `create_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '创建时间',
  8. `update_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '更新时间',
  9. PRIMARY KEY (`id`)
  10. ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='用户订单表';

entity

  1. /**
  2. * <h1>用户订单表实体类定义</h1>
  3. * */
  4. @Data
  5. @NoArgsConstructor
  6. @AllArgsConstructor
  7. @Entity
  8. @EntityListeners(AuditingEntityListener.class)
  9. @Table(name = "t_ecommerce_order")
  10. public class EcommerceOrder {
  11. /** 自增主键 */
  12. @Id
  13. @GeneratedValue(strategy = GenerationType.IDENTITY)
  14. @Column(name = "id", nullable = false)
  15. private Long id;
  16. /** 用户 id */
  17. @Column(name = "user_id", nullable = false)
  18. private Long userId;
  19. /** 用户地址 id */
  20. @Column(name = "address_id", nullable = false)
  21. private Long addressId;
  22. /** 订单详情(json 存储) */
  23. @Column(name = "order_detail", nullable = false)
  24. private String orderDetail;
  25. /** 创建时间 */
  26. @CreatedDate
  27. @Column(name = "create_time", nullable = false)
  28. private Date createTime;
  29. /** 更新时间 */
  30. @LastModifiedDate
  31. @Column(name = "update_time", nullable = false)
  32. private Date updateTime;
  33. public EcommerceOrder(Long userId, Long addressId, String orderDetail) {
  34. this.userId = userId;
  35. this.addressId = addressId;
  36. this.orderDetail = orderDetail;
  37. }
  38. }

vo

  1. /**
  2. * <h1>订单详情</h1>
  3. * */
  4. @ApiModel(description = "分页订单详情对象")
  5. @Data
  6. @NoArgsConstructor
  7. @AllArgsConstructor
  8. public class PageSimpleOrderDetail {
  9. @ApiModelProperty(value = "订单详情")
  10. private List<SingleOrderItem> orderItems;
  11. @ApiModelProperty(value = "是否有更多的订单(分页)")
  12. private Boolean hasMore;
  13. /**
  14. * <h2>单个订单信息</h2>
  15. * */
  16. @ApiModel(description = "单个订单信息对象")
  17. @Data
  18. @NoArgsConstructor
  19. @AllArgsConstructor
  20. public static class SingleOrderItem {
  21. @ApiModelProperty(value = "订单表主键 id")
  22. private Long id;
  23. @ApiModelProperty(value = "用户地址信息")
  24. private UserAddress userAddress;
  25. @ApiModelProperty(value = "订单商品信息")
  26. private List<SingleOrderGoodsItem> goodsItems;
  27. }
  28. @ApiModel(description = "单个订单中的单项商品信息")
  29. @Data
  30. @NoArgsConstructor
  31. @AllArgsConstructor
  32. public static class SingleOrderGoodsItem {
  33. @ApiModelProperty(value = "简单商品信息")
  34. private SimpleGoodsInfo simpleGoodsInfo;
  35. @ApiModelProperty(value = "商品个数")
  36. private Integer count;
  37. }
  38. }

以及用于微服务之间传递的类

  1. /**
  2. * <h1>订单信息</h1>
  3. */
  4. @ApiModel(description = "用户发起购买订单")
  5. @Data
  6. @NoArgsConstructor
  7. @AllArgsConstructor
  8. public class OrderInfo {
  9. @ApiModelProperty(value = "用户地址表主键 id")
  10. private Long userAddress;
  11. @ApiModelProperty(value = "订单中的商品信息")
  12. private List<OrderItem> orderItems;
  13. /**
  14. * <h2>订单中的商品信息</h2>
  15. * */
  16. @ApiModel(description = "订单中的单项商品信息")
  17. @Data
  18. @NoArgsConstructor
  19. @AllArgsConstructor
  20. public static class OrderItem {
  21. @ApiModelProperty(value = "商品表主键 id")
  22. private Long goodsId;
  23. @ApiModelProperty(value = "购买商品个数")
  24. private Integer count;
  25. public DeductGoodsInventory toDeductGoodsInventory() {
  26. return new DeductGoodsInventory(this.goodsId, this.count);
  27. }
  28. }
  29. }

dao

  1. /**
  2. * <h1>EcommerceOrder Dao 接口定义</h1>
  3. * */
  4. public interface EcommerceOrderDao extends PagingAndSortingRepository<EcommerceOrder, Long> {
  5. /**
  6. * <h2>根据 userId 查询分页订单</h2>
  7. * select * from t_ecommerce_order where user_id = ?
  8. * order by ... desc/asc limit x offset y
  9. * */
  10. Page<EcommerceOrder> findAllByUserId(Long userId, Pageable pageable);
  11. }

service接口

  1. /**
  2. * <h1>订单相关服务接口定义</h1>
  3. * */
  4. public interface IOrderService {
  5. /**
  6. * <h2>下单(分布式事务): 创建订单 -> 扣减库存 -> 扣减余额 -> 创建物流信息(Stream + Kafka)</h2>
  7. * */
  8. TableId createOrder(OrderInfo orderInfo);
  9. /**
  10. * <h2>获取当前用户的订单信息: 带有分页</h2>
  11. * */
  12. PageSimpleOrderDetail getSimpleOrderDetailByPage(int page);
  13. }

openFeign的使用

由于订单微服务需要依赖其他微服务,所以需要使用到openFeign,进行微服务之间的通信。这里的openFeign只是简化使用,需要更多的设置,可以看前面专门写的openfeign。

由于所有的微服务都会做权限的校验,所以需要对openFeign进行配置,让他的请求头带上校验信息。配置文件如下。(在order微服务下创建feign文件夹,用于openFeign的使用)

  1. /**
  2. * <h1>Feign 调用时, 把 Header 也传递到服务提供方</h1>
  3. * */
  4. @Slf4j
  5. @Configuration
  6. public class FeignConfig {
  7. /**
  8. * <h2>给 Feign 配置请求拦截器</h2>
  9. * RequestInterceptor 是我们提供给 open-feign 的请求拦截器, 把 Header 信息传递
  10. * */
  11. @Bean
  12. public RequestInterceptor headerInterceptor() {
  13. return template -> {
  14. ServletRequestAttributes attributes =
  15. (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  16. if (null != attributes) {
  17. HttpServletRequest request = attributes.getRequest();
  18. Enumeration<String> headerNames = request.getHeaderNames();
  19. if (null != headerNames) {
  20. while (headerNames.hasMoreElements()) {
  21. String name = headerNames.nextElement();
  22. String values = request.getHeader(name);
  23. // 不能把当前请求的 content-length 传递到下游的服务提供方, 这明显是不对的
  24. // 请求可能一直返回不了, 或者是请求响应数据被截断
  25. if (!name.equalsIgnoreCase("content-length")) {
  26. // 这里的 template 就是 RestTemplate
  27. template.header(name, values);
  28. }
  29. }
  30. }
  31. }
  32. };
  33. }
  34. }

不需要兜底策略的服务间通信

下面创建不需要兜底策略的服务间通信,并不是所有服务间通信都需要兜底策略,有一些没有完成的需要即使的报错,比如扣减用户余额,如果使用兜底策略,明显是不对的。

  1. /**
  2. * <h1>用户账户服务 Feign 接口</h1>
  3. * */
  4. @FeignClient(
  5. contextId = "NotSecuredBalanceClient",
  6. value = "e-commerce-account-service"
  7. )
  8. public interface NotSecuredBalanceClient {
  9. @RequestMapping(
  10. value = "/ecommerce-account-service/balance/deduct-balance",
  11. method = RequestMethod.PUT
  12. )
  13. CommonResponse<BalanceInfo> deductBalance(@RequestBody BalanceInfo balanceInfo);
  14. }

商品

  1. public interface NotSecuredGoodsClient {
  2. /**
  3. * <h2>根据 ids 扣减商品库存</h2>
  4. * */
  5. @RequestMapping(
  6. value = "/ecommerce-goods-service/goods/deduct-goods-inventory",
  7. method = RequestMethod.PUT
  8. )
  9. CommonResponse<Boolean> deductGoodsInventory(
  10. @RequestBody List<DeductGoodsInventory> deductGoodsInventories);
  11. /**
  12. * <h2>根据 ids 查询简单的商品信息</h2>
  13. * */
  14. @RequestMapping(
  15. value = "/ecommerce-goods-service/goods/simple-goods-info",
  16. method = RequestMethod.POST
  17. )
  18. CommonResponse<List<SimpleGoodsInfo>> getSimpleGoodsInfoByTableId(
  19. @RequestBody TableId tableId);
  20. }

需要兜底策略的服务间通信

在feign文件夹下创建hystrix文件夹,用于存放兜底策略

地址微服务

  1. /**
  2. * <h1>用户账户服务 Feign 接口(安全的)</h1>
  3. * */
  4. @FeignClient(
  5. contextId = "AddressClient",
  6. value = "e-commerce-account-service",
  7. fallback = AddressClientHystrix.class
  8. )
  9. public interface AddressClient {
  10. /**
  11. * <h2>根据 id 查询地址信息</h2>
  12. * */
  13. @RequestMapping(
  14. value = "/ecommerce-account-service/address/address-info-by-table-id",
  15. method = RequestMethod.POST
  16. )
  17. CommonResponse<AddressInfo> getAddressInfoByTablesId(@RequestBody TableId tableId);
  18. }

兜底策略

  1. /**
  2. * <h1>账户服务熔断降级兜底策略</h1>
  3. * */
  4. @Slf4j
  5. @Component
  6. public class AddressClientHystrix implements AddressClient {
  7. @Override
  8. public CommonResponse<AddressInfo> getAddressInfoByTablesId(TableId tableId) {
  9. log.error("[account client feign request error in order service] get address info" +
  10. "error: [{}]", JSON.toJSONString(tableId));
  11. return new CommonResponse<>(
  12. -1,
  13. "[account client feign request error in order service]",
  14. new AddressInfo(-1L, Collections.emptyList())
  15. );
  16. }
  17. }

商品服务

  1. /**
  2. * <h1>安全的商品服务 Feign 接口</h1>
  3. * */
  4. @FeignClient(
  5. contextId = "SecuredGoodsClient",
  6. value = "e-commerce-goods-service",
  7. fallback = GoodsClientHystrix.class
  8. )
  9. public interface SecuredGoodsClient {
  10. /**
  11. * <h2>根据 ids 查询简单的商品信息</h2>
  12. * */
  13. @RequestMapping(
  14. value = "/ecommerce-goods-service/goods/simple-goods-info",
  15. method = RequestMethod.POST
  16. )
  17. CommonResponse<List<SimpleGoodsInfo>> getSimpleGoodsInfoByTableId(
  18. @RequestBody TableId tableId);
  19. }

兜底策略

  1. /**
  2. * <h1>商品服务熔断降级兜底</h1>
  3. * */
  4. @Slf4j
  5. @Component
  6. public class GoodsClientHystrix implements SecuredGoodsClient {
  7. @Override
  8. public CommonResponse<List<SimpleGoodsInfo>> getSimpleGoodsInfoByTableId(TableId tableId) {
  9. log.error("[goods client feign request error in order service] get simple goods" +
  10. "error: [{}]", JSON.toJSONString(tableId));
  11. return new CommonResponse<>(
  12. -1,
  13. "[goods client feign request error in order service]",
  14. Collections.emptyList()
  15. );
  16. }
  17. }

Client(微服务)使用Seata(AT模式)的步骤

pom.xml 中引入依赖: spring-cloud-starter alibaba-seata, HikariCP

  1. <!-- seata-->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  5. </dependency>
  6. <!-- 注册Seata数据源需要的连接池-->
  7. <dependency>
  8. <groupId>com.zaxxer</groupId>
  9. <artifactId>HikariCP</artifactId>
  10. <optional>true</optional>
  11. </dependency>

创建undo_ log 表(如果业务使用了多个数据库,每一个数据库都要有这张表)

  1. CREATE TABLE IF NOT EXISTS `ecommerce`.`undo_log` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `branch_id` bigint(20) NOT NULL,
  4. `xid` varchar(100) NOT NULL,
  5. `context` varchar(128) NOT NULL,
  6. `rollback_info` longblob NOT NULL,
  7. `log_status` int(11) NOT NULL,
  8. `log_created` datetime NOT NULL,
  9. `log_modified` datetime NOT NULL,
  10. `ext` varchar(100) DEFAULT NULL,
  11. PRIMARY KEY (`id`),
  12. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

配置事务分组(配置文件file.conf,registry.conf)

file.conf

  1. ## transaction log store, only used in seata-server
  2. store {
  3. ## store mode: file、db、redis
  4. mode = "db"
  5. ## file store property
  6. file {
  7. ## store location dir
  8. dir = "sessionStore"
  9. # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
  10. maxBranchSessionSize = 16384
  11. # globe session size , if exceeded throws exceptions
  12. maxGlobalSessionSize = 512
  13. # file buffer size , if exceeded allocate new buffer
  14. fileWriteBufferCacheSize = 16384
  15. # when recover batch read size
  16. sessionReloadReadSize = 100
  17. # async, sync
  18. flushDiskMode = async
  19. }
  20. ## database store property
  21. db {
  22. ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
  23. datasource = "druid"
  24. ## mysql/oracle/postgresql/h2/oceanbase etc.
  25. dbType = "mysql"
  26. driverClassName = "com.mysql.jdbc.Driver"
  27. url = "jdbc:mysql://127.0.0.1:3306/seata?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false"
  28. user = "root"
  29. password = "root"
  30. minConn = 5
  31. maxConn = 100
  32. globalTable = "global_table"
  33. branchTable = "branch_table"
  34. lockTable = "lock_table"
  35. queryLimit = 100
  36. maxWait = 5000
  37. }
  38. ## redis store property
  39. redis {
  40. host = "127.0.0.1"
  41. port = "6379"
  42. password = ""
  43. database = "0"
  44. minConn = 1
  45. maxConn = 10
  46. maxTotal = 100
  47. queryLimit = 100
  48. }
  49. }
  50. ##事务分组
  51. service {
  52. vgroupMapping.imooc-ecommerce = "default"
  53. default.grouplist = "127.0.0.1:8091"
  54. }
  55. client {
  56. async.commit.buffer.limit = 10000
  57. lock {
  58. retry.internal = 10
  59. retry.times = 30
  60. }
  61. }

 注意:需要在MySQL中创建库和表

  1. CREATE DATABASE `seata`;
  2. CREATE TABLE IF NOT EXISTS `seata`.`global_table`
  3. (
  4. `xid` VARCHAR(128) NOT NULL,
  5. `transaction_id` BIGINT,
  6. `status` TINYINT NOT NULL,
  7. `application_id` VARCHAR(32),
  8. `transaction_service_group` VARCHAR(32),
  9. `transaction_name` VARCHAR(128),
  10. `timeout` INT,
  11. `begin_time` BIGINT,
  12. `application_data` VARCHAR(2000),
  13. `gmt_create` DATETIME,
  14. `gmt_modified` DATETIME,
  15. PRIMARY KEY (`xid`),
  16. KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
  17. KEY `idx_transaction_id` (`transaction_id`)
  18. ) ENGINE = InnoDB
  19. DEFAULT CHARSET = utf8;
  20. -- the table to store BranchSession data
  21. CREATE TABLE IF NOT EXISTS `seata`.`branch_table`
  22. (
  23. `branch_id` BIGINT NOT NULL,
  24. `xid` VARCHAR(128) NOT NULL,
  25. `transaction_id` BIGINT,
  26. `resource_group_id` VARCHAR(32),
  27. `resource_id` VARCHAR(256),
  28. `branch_type` VARCHAR(8),
  29. `status` TINYINT,
  30. `client_id` VARCHAR(64),
  31. `application_data` VARCHAR(2000),
  32. `gmt_create` DATETIME,
  33. `gmt_modified` DATETIME,
  34. PRIMARY KEY (`branch_id`),
  35. KEY `idx_xid` (`xid`)
  36. ) ENGINE = InnoDB
  37. DEFAULT CHARSET = utf8;
  38. -- the table to store lock data
  39. CREATE TABLE IF NOT EXISTS `seata`.`lock_table`
  40. (
  41. `row_key` VARCHAR(128) NOT NULL,
  42. `xid` VARCHAR(96),
  43. `transaction_id` BIGINT,
  44. `branch_id` BIGINT NOT NULL,
  45. `resource_id` VARCHAR(256),
  46. `table_name` VARCHAR(32),
  47. `pk` VARCHAR(36),
  48. `gmt_create` DATETIME,
  49. `gmt_modified` DATETIME,
  50. PRIMARY KEY (`row_key`),
  51. KEY `idx_branch_id` (`branch_id`)
  52. ) ENGINE = InnoDB
  53. DEFAULT CHARSET = utf8;

registry.conf

  1. registry {
  2. # file、nacos、eureka、redis、zk、consul
  3. type = "file"
  4. file {
  5. name = "file.conf"
  6. }
  7. }
  8. config {
  9. type = "file"
  10. file {
  11. name = "file.conf"
  12. }
  13. }

bootstrap.yml

  1. spring:
  2. cloud:
  3. alibaba:
  4. seata:
  5. tx-service-group: imooc-ecommerce # seata 全局事务分组

配置Seata数据源代理(思考下,为什么需要这个? )

  1. import com.zaxxer.hikari.HikariDataSource;
  2. import io.seata.rm.datasource.DataSourceProxy;
  3. import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.context.annotation.Primary;
  7. import javax.sql.DataSource;
  8. /**
  9. * <h1>Seata 所需要的数据源代理配置类</h1>
  10. * */
  11. @Configuration
  12. public class DataSourceProxyAutoConfiguration {
  13. private final DataSourceProperties dataSourceProperties;
  14. public DataSourceProxyAutoConfiguration(DataSourceProperties dataSourceProperties) {
  15. this.dataSourceProperties = dataSourceProperties;
  16. }
  17. /**
  18. * <h2>配置数据源代理, 用于 Seata 全局事务回滚</h2>
  19. * before image + after image -> undo_log
  20. * */
  21. @Primary
  22. @Bean("dataSource")
  23. public DataSource dataSource() {
  24. HikariDataSource dataSource = new HikariDataSource();
  25. dataSource.setJdbcUrl(dataSourceProperties.getUrl());
  26. dataSource.setUsername(dataSourceProperties.getUsername());
  27. dataSource.setPassword(dataSourceProperties.getPassword());
  28. dataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
  29. return new DataSourceProxy(dataSource);
  30. }
  31. }

加载拦截器SeataHandlerInterceptor,实现微服务之间xid的传递

  1. /**
  2. * <h2>添加拦截器配置</h2>
  3. * */
  4. @Override
  5. protected void addInterceptors(InterceptorRegistry registry) {
  6. // 添加用户身份统一登录拦截的拦截器
  7. registry.addInterceptor(new LoginUserInfoInterceptor())
  8. .addPathPatterns("/**").order(0);
  9. // Seata 传递 xid 事务 id 给其他的微服务
  10. // 只有这样, 其他的服务才会写 undo_log, 才能够实现回滚
  11. registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns("/**");
  12. }

将@GlobalTransactional注解标注在需要分布式事务的方法上

使用SpringCloud Stream完成消息通信

首先引入依赖,添加配置文件(在创建微服务时已经引入,看上面)

这里使用的是自定义通道,所以要创建自定义通道接口

  1. /**
  2. * <h1>自定义物流消息通信信道(Source)</h1>
  3. * */
  4. public interface LogisticsSource {
  5. /** 输出信道名称 */
  6. String OUTPUT = "logisticsOutput";
  7. /**
  8. * <h2>物流 Source -> logisticsOutput</h2>
  9. * 通信信道的名称是 logisticsOutput, 对应到 yml 文件里的配置
  10. * */
  11. @Output(LogisticsSource.OUTPUT)
  12. MessageChannel logisticsOutput();
  13. }

在公共服务内创建微服务之间传递的对象

  1. /**
  2. * <h1>创建订单时发送的物流消息</h1>
  3. * */
  4. @ApiModel(description = "Stream 物流消息对象")
  5. @Data
  6. @NoArgsConstructor
  7. @AllArgsConstructor
  8. public class LogisticsMessage {
  9. @ApiModelProperty(value = "用户表主键 id")
  10. private Long userId;
  11. @ApiModelProperty(value = "订单表主键 id")
  12. private Long orderId;
  13. @ApiModelProperty(value = "用户地址表主键 id")
  14. private Long addressId;
  15. @ApiModelProperty(value = "备注信息(json 存储)")
  16. private String extraInfo;
  17. }

结合上面所有的知识点,完成商品的下单和查询操作

  1. import com.alibaba.fastjson.JSON;
  2. import com.imooc.ecommerce.account.AddressInfo;
  3. import com.imooc.ecommerce.account.BalanceInfo;
  4. import com.imooc.ecommerce.common.TableId;
  5. import com.imooc.ecommerce.dao.EcommerceOrderDao;
  6. import com.imooc.ecommerce.entity.EcommerceOrder;
  7. import com.imooc.ecommerce.feign.AddressClient;
  8. import com.imooc.ecommerce.feign.NotSecuredBalanceClient;
  9. import com.imooc.ecommerce.feign.NotSecuredGoodsClient;
  10. import com.imooc.ecommerce.feign.SecuredGoodsClient;
  11. import com.imooc.ecommerce.filter.AccessContext;
  12. import com.imooc.ecommerce.goods.DeductGoodsInventory;
  13. import com.imooc.ecommerce.goods.SimpleGoodsInfo;
  14. import com.imooc.ecommerce.order.LogisticsMessage;
  15. import com.imooc.ecommerce.order.OrderInfo;
  16. import com.imooc.ecommerce.service.IOrderService;
  17. import com.imooc.ecommerce.source.LogisticsSource;
  18. import com.imooc.ecommerce.vo.PageSimpleOrderDetail;
  19. import io.seata.spring.annotation.GlobalTransactional;
  20. import lombok.extern.slf4j.Slf4j;
  21. import org.apache.commons.collections4.CollectionUtils;
  22. import org.springframework.beans.factory.annotation.Autowired;
  23. import org.springframework.cloud.stream.annotation.EnableBinding;
  24. import org.springframework.data.domain.Page;
  25. import org.springframework.data.domain.PageRequest;
  26. import org.springframework.data.domain.Pageable;
  27. import org.springframework.data.domain.Sort;
  28. import org.springframework.messaging.support.MessageBuilder;
  29. import org.springframework.stereotype.Service;
  30. import java.util.ArrayList;
  31. import java.util.Collections;
  32. import java.util.HashSet;
  33. import java.util.List;
  34. import java.util.Map;
  35. import java.util.Set;
  36. import java.util.function.Function;
  37. import java.util.stream.Collectors;
  38. /**
  39. * <h1>订单相关服务接口实现</h1>
  40. * */
  41. @Slf4j
  42. @Service
  43. @EnableBinding(LogisticsSource.class)
  44. public class OrderServiceImpl implements IOrderService {
  45. /** 表的 dao 接口 */
  46. private final EcommerceOrderDao orderDao;
  47. /** Feign 客户端 */
  48. private final AddressClient addressClient;
  49. private final SecuredGoodsClient securedGoodsClient;
  50. private final NotSecuredGoodsClient notSecuredGoodsClient;
  51. private final NotSecuredBalanceClient notSecuredBalanceClient;
  52. /** SpringCloud Stream 的发射器 */
  53. private final LogisticsSource logisticsSource;
  54. public OrderServiceImpl(EcommerceOrderDao orderDao,
  55. AddressClient addressClient,
  56. SecuredGoodsClient securedGoodsClient,
  57. NotSecuredGoodsClient notSecuredGoodsClient,
  58. NotSecuredBalanceClient notSecuredBalanceClient,
  59. LogisticsSource logisticsSource) {
  60. this.orderDao = orderDao;
  61. this.addressClient = addressClient;
  62. this.securedGoodsClient = securedGoodsClient;
  63. this.notSecuredGoodsClient = notSecuredGoodsClient;
  64. this.notSecuredBalanceClient = notSecuredBalanceClient;
  65. this.logisticsSource = logisticsSource;
  66. }
  67. /**
  68. * <h2>创建订单: 这里会涉及到分布式事务</h2>
  69. * 创建订单会涉及到多个步骤和校验, 当不满足情况时直接抛出异常;
  70. * 1. 校验请求对象是否合法
  71. * 2. 创建订单
  72. * 3. 扣减商品库存
  73. * 4. 扣减用户余额
  74. * 5. 发送订单物流消息 SpringCloud Stream + Kafka
  75. * */
  76. @Override
  77. @GlobalTransactional(rollbackFor = Exception.class)
  78. public TableId createOrder(OrderInfo orderInfo) {
  79. // 获取地址信息
  80. AddressInfo addressInfo = addressClient.getAddressInfoByTablesId(
  81. new TableId(Collections.singletonList(
  82. new TableId.Id(orderInfo.getUserAddress())))).getData();
  83. // 1. 校验请求对象是否合法(商品信息不需要校验, 扣减库存会做校验)
  84. if (CollectionUtils.isEmpty(addressInfo.getAddressItems())) {
  85. throw new RuntimeException("user address is not exist: "
  86. + orderInfo.getUserAddress());
  87. }
  88. // 2. 创建订单
  89. EcommerceOrder newOrder = orderDao.save(
  90. new EcommerceOrder(
  91. AccessContext.getLoginUserInfo().getId(),
  92. orderInfo.getUserAddress(),
  93. JSON.toJSONString(orderInfo.getOrderItems())
  94. )
  95. );
  96. log.info("create order success: [{}], [{}]",
  97. AccessContext.getLoginUserInfo().getId(), newOrder.getId());
  98. // 3. 扣减商品库存
  99. if (
  100. !notSecuredGoodsClient.deductGoodsInventory(
  101. orderInfo.getOrderItems()
  102. .stream()
  103. .map(OrderInfo.OrderItem::toDeductGoodsInventory)
  104. .collect(Collectors.toList())
  105. ).getData()
  106. ) {
  107. throw new RuntimeException("deduct goods inventory failure");
  108. }
  109. // 4. 扣减用户账户余额
  110. // 4.1 获取商品信息, 计算总价格
  111. List<SimpleGoodsInfo> goodsInfos = notSecuredGoodsClient.getSimpleGoodsInfoByTableId(
  112. new TableId(
  113. orderInfo.getOrderItems()
  114. .stream()
  115. .map(o -> new TableId.Id(o.getGoodsId()))
  116. .collect(Collectors.toList())
  117. )
  118. ).getData();
  119. Map<Long, SimpleGoodsInfo> goodsId2GoodsInfo = goodsInfos.stream()
  120. .collect(Collectors.toMap(SimpleGoodsInfo::getId, Function.identity()));
  121. long balance = 0;
  122. for (OrderInfo.OrderItem orderItem : orderInfo.getOrderItems()) {
  123. balance += goodsId2GoodsInfo.get(orderItem.getGoodsId()).getPrice()
  124. * orderItem.getCount();
  125. }
  126. assert balance > 0;
  127. // 4.2 填写总价格, 扣减账户余额
  128. BalanceInfo balanceInfo = notSecuredBalanceClient.deductBalance(
  129. new BalanceInfo(AccessContext.getLoginUserInfo().getId(), balance)
  130. ).getData();
  131. if (null == balanceInfo) {
  132. throw new RuntimeException("deduct user balance failure");
  133. }
  134. log.info("deduct user balance: [{}], [{}]", newOrder.getId(),
  135. JSON.toJSONString(balanceInfo));
  136. // 5. 发送订单物流消息 SpringCloud Stream + Kafka
  137. LogisticsMessage logisticsMessage = new LogisticsMessage(
  138. AccessContext.getLoginUserInfo().getId(),
  139. newOrder.getId(),
  140. orderInfo.getUserAddress(),
  141. null // 没有备注信息
  142. );
  143. if (!logisticsSource.logisticsOutput().send(
  144. MessageBuilder.withPayload(JSON.toJSONString(logisticsMessage)).build()
  145. )) {
  146. throw new RuntimeException("send logistics message failure");
  147. }
  148. log.info("send create order message to kafka with stream: [{}]",
  149. JSON.toJSONString(logisticsMessage));
  150. // 返回订单 id
  151. return new TableId(Collections.singletonList(new TableId.Id(newOrder.getId())));
  152. }
  153. @Override
  154. public PageSimpleOrderDetail getSimpleOrderDetailByPage(int page) {
  155. if (page <= 0) {
  156. page = 1; // 默认是第一页
  157. }
  158. // 这里分页的规则是: 1页10条数据, 按照 id 倒序排列
  159. Pageable pageable = PageRequest.of(page - 1, 10,
  160. Sort.by("id").descending());
  161. Page<EcommerceOrder> orderPage = orderDao.findAllByUserId(
  162. AccessContext.getLoginUserInfo().getId(), pageable
  163. );
  164. List<EcommerceOrder> orders = orderPage.getContent();
  165. // 如果是空, 直接返回空数组
  166. if (CollectionUtils.isEmpty(orders)) {
  167. return new PageSimpleOrderDetail(Collections.emptyList(), false);
  168. }
  169. // 获取当前订单中所有的 goodsId, 这个 set 不可能为空或者是 null, 否则, 代码一定有 bug
  170. Set<Long> goodsIdsInOrders = new HashSet<>();
  171. orders.forEach(o -> {
  172. List<DeductGoodsInventory> goodsAndCount = JSON.parseArray(
  173. o.getOrderDetail(), DeductGoodsInventory.class
  174. );
  175. goodsIdsInOrders.addAll(goodsAndCount.stream()
  176. .map(DeductGoodsInventory::getGoodsId)
  177. .collect(Collectors.toSet()));
  178. });
  179. assert CollectionUtils.isNotEmpty(goodsIdsInOrders);
  180. // 是否还有更多页: 总页数是否大于当前给定的页
  181. boolean hasMore = orderPage.getTotalPages() > page;
  182. // 获取商品信息
  183. List<SimpleGoodsInfo> goodsInfos = securedGoodsClient.getSimpleGoodsInfoByTableId(
  184. new TableId(goodsIdsInOrders.stream()
  185. .map(TableId.Id::new).collect(Collectors.toList()))
  186. ).getData();
  187. // 获取地址信息
  188. AddressInfo addressInfo = addressClient.getAddressInfoByTablesId(
  189. new TableId(orders.stream()
  190. .map(o -> new TableId.Id(o.getAddressId()))
  191. .distinct().collect(Collectors.toList()))
  192. ).getData();
  193. // 组装订单中的商品, 地址信息 -> 订单信息
  194. return new PageSimpleOrderDetail(
  195. assembleSimpleOrderDetail(orders, goodsInfos, addressInfo),
  196. hasMore
  197. );
  198. }
  199. /**
  200. * <h2>组装订单详情</h2>
  201. * */
  202. private List<PageSimpleOrderDetail.SingleOrderItem> assembleSimpleOrderDetail(
  203. List<EcommerceOrder> orders, List<SimpleGoodsInfo> goodsInfos,
  204. AddressInfo addressInfo
  205. ) {
  206. // goodsId -> SimpleGoodsInfo
  207. Map<Long, SimpleGoodsInfo> id2GoodsInfo = goodsInfos.stream()
  208. .collect(Collectors.toMap(SimpleGoodsInfo::getId, Function.identity()));
  209. // addressId -> AddressInfo.AddressItem
  210. Map<Long, AddressInfo.AddressItem> id2AddressItem = addressInfo.getAddressItems()
  211. .stream().collect(
  212. Collectors.toMap(AddressInfo.AddressItem::getId, Function.identity())
  213. );
  214. List<PageSimpleOrderDetail.SingleOrderItem> result = new ArrayList<>(orders.size());
  215. orders.forEach(o -> {
  216. PageSimpleOrderDetail.SingleOrderItem orderItem =
  217. new PageSimpleOrderDetail.SingleOrderItem();
  218. orderItem.setId(o.getId());
  219. orderItem.setUserAddress(id2AddressItem.getOrDefault(o.getAddressId(),
  220. new AddressInfo.AddressItem(-1L)).toUserAddress());
  221. orderItem.setGoodsItems(buildOrderGoodsItem(o, id2GoodsInfo));
  222. result.add(orderItem);
  223. });
  224. return result;
  225. }
  226. /**
  227. * <h2>构造订单中的商品信息</h2>
  228. * */
  229. private List<PageSimpleOrderDetail.SingleOrderGoodsItem> buildOrderGoodsItem(
  230. EcommerceOrder order, Map<Long, SimpleGoodsInfo> id2GoodsInfo
  231. ) {
  232. List<PageSimpleOrderDetail.SingleOrderGoodsItem> goodsItems = new ArrayList<>();
  233. List<DeductGoodsInventory> goodsAndCount = JSON.parseArray(
  234. order.getOrderDetail(), DeductGoodsInventory.class
  235. );
  236. goodsAndCount.forEach(gc -> {
  237. PageSimpleOrderDetail.SingleOrderGoodsItem goodsItem =
  238. new PageSimpleOrderDetail.SingleOrderGoodsItem();
  239. goodsItem.setCount(gc.getCount());
  240. goodsItem.setSimpleGoodsInfo(id2GoodsInfo.getOrDefault(gc.getGoodsId(),
  241. new SimpleGoodsInfo(-1L)));
  242. goodsItems.add(goodsItem);
  243. });
  244. return goodsItems;
  245. }
  246. }

物流微服务

创建物流微服务

引入依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>e-commerce-service</artifactId>
  7. <groupId>com.taluohui.ecommerce</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>e-commerce-logistics-service</artifactId>
  12. <version>1.0-SNAPSHOT</version>
  13. <packaging>jar</packaging>
  14. <!-- 模块名及描述信息 -->
  15. <name>e-commerce-logistics-service</name>
  16. <description>物流服务</description>
  17. <properties>
  18. <maven.compiler.source>8</maven.compiler.source>
  19. <maven.compiler.target>8</maven.compiler.target>
  20. </properties>
  21. <dependencies>
  22. <!-- spring cloud alibaba nacos discovery 依赖 -->
  23. <dependency>
  24. <groupId>com.alibaba.cloud</groupId>
  25. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  26. </dependency>
  27. <!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin-->
  28. <dependency>
  29. <groupId>org.springframework.cloud</groupId>
  30. <artifactId>spring-cloud-starter-zipkin</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.kafka</groupId>
  34. <artifactId>spring-kafka</artifactId>
  35. <version>2.5.0.RELEASE</version>
  36. </dependency>
  37. <!-- Java Persistence API, ORM 规范 -->
  38. <dependency>
  39. <groupId>org.springframework.boot</groupId>
  40. <artifactId>spring-boot-starter-data-jpa</artifactId>
  41. </dependency>
  42. <!-- SpringCloud Stream + Kafka -->
  43. <dependency>
  44. <groupId>org.springframework.cloud</groupId>
  45. <artifactId>spring-cloud-stream</artifactId>
  46. </dependency>
  47. <dependency>
  48. <groupId>org.springframework.cloud</groupId>
  49. <artifactId>spring-cloud-stream-binder-kafka</artifactId>
  50. </dependency>
  51. <!-- MySQL 驱动, 注意, 这个需要与 MySQL 版本对应 -->
  52. <dependency>
  53. <groupId>mysql</groupId>
  54. <artifactId>mysql-connector-java</artifactId>
  55. <version>8.0.12</version>
  56. <scope>runtime</scope>
  57. </dependency>
  58. <dependency>
  59. <groupId>com.taluohui.ecommerce</groupId>
  60. <artifactId>e-commerce-service-config</artifactId>
  61. <version>1.0-SNAPSHOT</version>
  62. </dependency>
  63. <dependency>
  64. <groupId>com.taluohui.ecommerce</groupId>
  65. <artifactId>e-commerce-service-sdk</artifactId>
  66. <version>1.0-SNAPSHOT</version>
  67. </dependency>
  68. </dependencies>
  69. <!--
  70. SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
  71. SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
  72. -->
  73. <build>
  74. <finalName>${artifactId}</finalName>
  75. <plugins>
  76. <plugin>
  77. <groupId>org.springframework.boot</groupId>
  78. <artifactId>spring-boot-maven-plugin</artifactId>
  79. <executions>
  80. <execution>
  81. <goals>
  82. <goal>repackage</goal>
  83. </goals>
  84. </execution>
  85. </executions>
  86. </plugin>
  87. </plugins>
  88. </build>
  89. </project>

添加配置

  1. server:
  2. port: 8004
  3. spring:
  4. main:
  5. allow-bean-definition-overriding: true #允许同名的Bean
  6. application:
  7. name: e-commerce-order-service # 应用名称也是构成 Nacos 配置管理 dataId 字段的一部分 (当 config.prefix 为空时)
  8. cloud:
  9. stream:
  10. kafka:
  11. binder:
  12. brokers: 1.15.247.9:9092
  13. auto-create-topics: true
  14. bindings:
  15. logisticsInput:
  16. destination: e-commerce-topic # kafka topic
  17. content-type: text/plain
  18. alibaba:
  19. seata:
  20. tx-service-group: imooc-ecommerce # seata 全局事务分组
  21. nacos:
  22. # 服务注册发现
  23. discovery:
  24. enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
  25. server-addr: 1.15.247.9:8848
  26. # server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址
  27. namespace: 22d40198-8462-499d-a7fe-dbb2da958648
  28. # 引入 sleuth + zipkin + kafka
  29. kafka:
  30. bootstrap-servers: 1.15.247.9:9092
  31. producer:
  32. retries: 3
  33. consumer:
  34. auto-offset-reset: latest
  35. sleuth:
  36. sampler:
  37. # ProbabilityBasedSampler 抽样策略
  38. probability: 1.0 # 采样比例, 1.0 表示 100%, 默认是 0.1
  39. # RateLimitingSampler 抽样策略, 设置了限速采集, spring.sleuth.sampler.probability 属性值无效
  40. zipkin:
  41. sender:
  42. type: kafka # 默认是 web
  43. base-url: http://1.15.247.9:9411/
  44. jpa:
  45. show-sql: true
  46. hibernate:
  47. ddl-auto: none
  48. properties:
  49. hibernate.show_sql: true
  50. hibernate.format_sql: true
  51. open-in-view: false
  52. datasource:
  53. # 数据源
  54. url: jdbc:mysql://1.15.247.9:3306/ecommerce?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
  55. username: root
  56. password: Cjw970404
  57. type: com.zaxxer.hikari.HikariDataSource
  58. driver-class-name: com.mysql.cj.jdbc.Driver
  59. # 连接池
  60. hikari:
  61. maximum-pool-size: 8
  62. minimum-idle: 4
  63. idle-timeout: 30000
  64. connection-timeout: 30000
  65. max-lifetime: 45000
  66. auto-commit: true
  67. pool-name: ImoocEcommerceHikariCP
  68. # 暴露端点
  69. management:
  70. endpoints:
  71. web:
  72. exposure:
  73. include: '*'
  74. endpoint:
  75. health:
  76. show-details: always

入口程序

  1. /**
  2. * <h1>物流微服务启动入口</h1>
  3. * */
  4. @Import(DataSourceProxyAutoConfiguration.class)
  5. @EnableJpaAuditing
  6. @EnableDiscoveryClient
  7. @SpringBootApplication
  8. public class LogisticsApplication {
  9. public static void main(String[] args) {
  10. SpringApplication.run(LogisticsApplication.class, args);
  11. }
  12. }

SQL

  1. -- 创建 t_ecommerce_logistics 数据表
  2. CREATE TABLE IF NOT EXISTS `ecommerce`.`t_ecommerce_logistics` (
  3. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  4. `user_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '用户 id',
  5. `order_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '订单 id',
  6. `address_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '用户地址记录 id',
  7. `extra_info` varchar(512) NOT NULL COMMENT '备注信息(json 存储)',
  8. `create_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '创建时间',
  9. `update_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '更新时间',
  10. PRIMARY KEY (`id`)
  11. ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='物流表';

引入seata的两个配置文件,同上

实体类

  1. /**
  2. * <h1>物流表实体类定义</h1>
  3. * */
  4. @Data
  5. @NoArgsConstructor
  6. @AllArgsConstructor
  7. @Entity
  8. @EntityListeners(AuditingEntityListener.class)
  9. @Table(name = "t_ecommerce_logistics")
  10. public class EcommerceLogistics {
  11. /** 自增主键 */
  12. @Id
  13. @GeneratedValue(strategy = GenerationType.IDENTITY)
  14. @Column(name = "id", nullable = false)
  15. private Long id;
  16. /** 用户 id */
  17. @Column(name = "user_id", nullable = false)
  18. private Long userId;
  19. /** 订单 id */
  20. @Column(name = "order_id", nullable = false)
  21. private Long orderId;
  22. /** 用户地址 id */
  23. @Column(name = "address_id", nullable = false)
  24. private Long addressId;
  25. /** 备注信息(json 存储) */
  26. @Column(name = "extra_info", nullable = false)
  27. private String extraInfo;
  28. /** 创建时间 */
  29. @CreatedDate
  30. @Column(name = "create_time", nullable = false)
  31. private Date createTime;
  32. /** 更新时间 */
  33. @LastModifiedDate
  34. @Column(name = "update_time", nullable = false)
  35. private Date updateTime;
  36. public EcommerceLogistics(Long userId, Long orderId, Long addressId, String extraInfo) {
  37. this.userId = userId;
  38. this.orderId = orderId;
  39. this.addressId = addressId;
  40. this.extraInfo = StringUtils.isNotBlank(extraInfo) ? extraInfo : "{}";
  41. }
  42. }

Dao

  1. /**
  2. * <h1>EcommerceLogistics Dao 接口定义</h1>
  3. * */
  4. public interface EcommerceLogisticsDao extends JpaRepository<EcommerceLogistics, Long> {
  5. }

service

  1. /**
  2. * <h1>物流服务实现</h1>
  3. * */
  4. @Slf4j
  5. @EnableBinding(LogisticsSink.class)
  6. public class LogisticsServiceImpl {
  7. private final EcommerceLogisticsDao logisticsDao;
  8. public LogisticsServiceImpl(EcommerceLogisticsDao logisticsDao) {
  9. this.logisticsDao = logisticsDao;
  10. }
  11. /**
  12. * <h2>订阅监听订单微服务发送的物流消息</h2>
  13. * */
  14. @StreamListener("logisticsInput")
  15. public void consumeLogisticsMessage(@Payload Object payload) {
  16. log.info("receive and consume logistics message: [{}]", payload.toString());
  17. LogisticsMessage logisticsMessage = JSON.parseObject(
  18. payload.toString(), LogisticsMessage.class
  19. );
  20. EcommerceLogistics ecommerceLogistics = logisticsDao.save(
  21. new EcommerceLogistics(
  22. logisticsMessage.getUserId(),
  23. logisticsMessage.getOrderId(),
  24. logisticsMessage.getAddressId(),
  25. logisticsMessage.getExtraInfo()
  26. )
  27. );
  28. log.info("consume logistics message success: [{}]", ecommerceLogistics.getId());
  29. }
  30. }

自定义消息通道

  1. /**
  2. * <h1>物流微服务启动入口</h1>
  3. * */
  4. @Import(DataSourceProxyAutoConfiguration.class)
  5. @EnableJpaAuditing
  6. @EnableDiscoveryClient
  7. @SpringBootApplication
  8. public class LogisticsApplication {
  9. public static void main(String[] args) {
  10. SpringApplication.run(LogisticsApplication.class, args);
  11. }
  12. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/165667
推荐阅读
相关标签