diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/AccountApplication.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/AccountApplication.java index d3a87fe..6fbc0f8 100644 --- a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/AccountApplication.java +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/java/org/example/AccountApplication.java @@ -1,8 +1,10 @@ package org.example; +import org.example.conf.DataSourceProxyAutoConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; /** @@ -13,6 +15,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @EnableJpaAuditing @SpringBootApplication @EnableDiscoveryClient +@Import(DataSourceProxyAutoConfiguration.class) // 显示的指定定义的数据源 public class AccountApplication { public static void main(String[] args) { SpringApplication.run(AccountApplication.class, args); diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/bootstrap.yml b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/bootstrap.yml index 1a3b3da..9488952 100644 --- a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/bootstrap.yml +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/bootstrap.yml @@ -16,6 +16,9 @@ spring: metadata: management: context-path: ${server.servlet.context-path}/actuator + alibaba: + seata: + tx-service-group: dev-protocol # seata 全局事务分组, 对应 file.conf 里面的配置名一样 kafka: bootstrap-servers: 127.0.0.1:9092 producer: diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/file.conf b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/file.conf new file mode 100644 index 0000000..909a828 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/file.conf @@ -0,0 +1,65 @@ +## transaction log store, only used in seata-server +store { + ## store mode: file、db、redis + mode = "db" + + ## file store property + file { + ## store location dir + dir = "sessionStore" + # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions + maxBranchSessionSize = 16384 + # globe session size , if exceeded throws exceptions + maxGlobalSessionSize = 512 + # file buffer size , if exceeded allocate new buffer + fileWriteBufferCacheSize = 16384 + # when recover batch read size + sessionReloadReadSize = 100 + # async, sync + flushDiskMode = async + } + + ## database store property + db { + ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc. + datasource = "druid" + ## mysql/oracle/postgresql/h2/oceanbase etc. + dbType = "mysql" + driverClassName = "com.mysql.jdbc.Driver" + url = "jdbc:mysql://127.0.0.1:3306/seata?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false" + user = "root" + password = "root" + minConn = 5 + maxConn = 100 + globalTable = "global_table" + branchTable = "branch_table" + lockTable = "lock_table" + queryLimit = 100 + maxWait = 5000 + } + + ## redis store property + redis { + host = "127.0.0.1" + port = "6379" + password = "" + database = "0" + minConn = 1 + maxConn = 10 + maxTotal = 100 + queryLimit = 100 + } + +} + +service { + vgroupMapping.dev-protocol = "default" + default.grouplist = "127.0.0.1:8091" +} +client { + async.commit.buffer.limit = 10000 + lock { + retry.internal = 10 + retry.times = 30 + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/registry.conf b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/registry.conf new file mode 100644 index 0000000..f2a5df2 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/registry.conf @@ -0,0 +1,17 @@ +registry { + # file、nacos、eureka、redis、zk、consul + type = "file" + + file { + name = "file.conf" + } + +} + +config { + type = "file" + + file { + name = "file.conf" + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/GoodsApplication.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/GoodsApplication.java index 9d32063..21fe92a 100644 --- a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/GoodsApplication.java +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/GoodsApplication.java @@ -1,8 +1,10 @@ package org.example; +import org.example.conf.DataSourceProxyAutoConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; /** @@ -13,6 +15,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @EnableJpaAuditing @EnableDiscoveryClient @SpringBootApplication +@Import(DataSourceProxyAutoConfiguration.class) // 显示的指定定义的数据源 public class GoodsApplication { public static void main(String[] args) { diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/bootstrap.yml b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/bootstrap.yml index 37b83cc..9aaa3c7 100644 --- a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/bootstrap.yml +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/bootstrap.yml @@ -16,6 +16,9 @@ spring: metadata: management: context-path: ${server.servlet.context-path}/actuator + alibaba: + seata: + tx-service-group: dev-protocol # seata 全局事务分组, 对应 file.conf 里面的配置名一样 kafka: bootstrap-servers: 127.0.0.1:9092 producer: diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/file.conf b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/file.conf new file mode 100644 index 0000000..909a828 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/file.conf @@ -0,0 +1,65 @@ +## transaction log store, only used in seata-server +store { + ## store mode: file、db、redis + mode = "db" + + ## file store property + file { + ## store location dir + dir = "sessionStore" + # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions + maxBranchSessionSize = 16384 + # globe session size , if exceeded throws exceptions + maxGlobalSessionSize = 512 + # file buffer size , if exceeded allocate new buffer + fileWriteBufferCacheSize = 16384 + # when recover batch read size + sessionReloadReadSize = 100 + # async, sync + flushDiskMode = async + } + + ## database store property + db { + ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc. + datasource = "druid" + ## mysql/oracle/postgresql/h2/oceanbase etc. + dbType = "mysql" + driverClassName = "com.mysql.jdbc.Driver" + url = "jdbc:mysql://127.0.0.1:3306/seata?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false" + user = "root" + password = "root" + minConn = 5 + maxConn = 100 + globalTable = "global_table" + branchTable = "branch_table" + lockTable = "lock_table" + queryLimit = 100 + maxWait = 5000 + } + + ## redis store property + redis { + host = "127.0.0.1" + port = "6379" + password = "" + database = "0" + minConn = 1 + maxConn = 10 + maxTotal = 100 + queryLimit = 100 + } + +} + +service { + vgroupMapping.dev-protocol = "default" + default.grouplist = "127.0.0.1:8091" +} +client { + async.commit.buffer.limit = 10000 + lock { + retry.internal = 10 + retry.times = 30 + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/registry.conf b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/registry.conf new file mode 100644 index 0000000..f2a5df2 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/registry.conf @@ -0,0 +1,17 @@ +registry { + # file、nacos、eureka、redis、zk、consul + type = "file" + + file { + name = "file.conf" + } + +} + +config { + type = "file" + + file { + name = "file.conf" + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/README.md b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/README.md new file mode 100644 index 0000000..4275898 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/README.md @@ -0,0 +1,4 @@ +# 订单微服务 + + +## \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/OrderApplication.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/OrderApplication.java index d049e97..4080d22 100644 --- a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/OrderApplication.java +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/OrderApplication.java @@ -1,10 +1,12 @@ package org.example; +import org.example.conf.DataSourceProxyAutoConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; /** @@ -12,9 +14,10 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing; * */ @EnableJpaAuditing @SpringBootApplication -@EnableCircuitBreaker +@EnableCircuitBreaker // 保证 OpenFeign 开启 Hystrix @EnableFeignClients @EnableDiscoveryClient +@Import(DataSourceProxyAutoConfiguration.class) // 显示的指定定义的数据源 public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/controller/OrderController.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/controller/OrderController.java new file mode 100644 index 0000000..1145a85 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/controller/OrderController.java @@ -0,0 +1,52 @@ +package org.example.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.example.common.TableId; +import org.example.order.OrderInfo; +import org.example.service.IOrderService; +import org.example.vo.PageSimpleOrderDetail; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + *

订单服务对外 HTTP 接口

+ * */ +@Api(tags = "订单服务") +@Slf4j +@RestController +@RequestMapping("/order") +public class OrderController { + + private final IOrderService orderService; + + public OrderController(IOrderService orderService) { + this.orderService = orderService; + } + + @ApiOperation( + value = "创建", + notes = "购买(分布式事务): 创建订单 -> 扣减库存 -> 扣减余额 -> 发送物流消息", + httpMethod = "POST" + ) + @PostMapping("/create-order") + public TableId createOrder(@RequestBody OrderInfo orderInfo) { + return orderService.createOrder(orderInfo); + } + + @ApiOperation( + value = "订单信息", + notes = "获取当前用户的订单信息: 带有分页", + httpMethod = "GET" + ) + @GetMapping("/order-detail") + public PageSimpleOrderDetail getSimpleOrderDetailByPage( + @RequestParam(required = false, defaultValue = "1") int page) { + return orderService.getSimpleOrderDetailByPage(page); + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/dao/EcommerceOrderDao.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/dao/EcommerceOrderDao.java new file mode 100644 index 0000000..48bb41b --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/dao/EcommerceOrderDao.java @@ -0,0 +1,20 @@ +package org.example.dao; + +import org.example.entity.EcommerceOrder; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.PagingAndSortingRepository; + +/** + *

EcommerceOrder Dao 接口定义

+ * 支持分页 + * */ +public interface EcommerceOrderDao extends PagingAndSortingRepository { + + /** + *

根据 userId 查询分页订单

+ * select * from t_dev_protocol_cloud_order where user_id = ? + * order by ... desc/asc limit x offset y + * */ + Page findAllByUserId(Long userId, Pageable pageable); +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/entity/EcommerceOrder.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/entity/EcommerceOrder.java new file mode 100644 index 0000000..26ce2b3 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/entity/EcommerceOrder.java @@ -0,0 +1,64 @@ +package org.example.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; + +/** + *

用户订单表实体类定义

+ * */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@EntityListeners(AuditingEntityListener.class) +@Table(name = "t_dev_protocol_cloud_order") +public class EcommerceOrder { + + /** 自增主键 */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + /** 用户 id */ + @Column(name = "user_id", nullable = false) + private Long userId; + + /** 用户地址 id */ + @Column(name = "address_id", nullable = false) + private Long addressId; + + /** 订单详情(json 存储) */ + @Column(name = "order_detail", nullable = false) + private String orderDetail; + + /** 创建时间 */ + @CreatedDate + @Column(name = "create_time", nullable = false) + private Date createTime; + + /** 更新时间 */ + @LastModifiedDate + @Column(name = "update_time", nullable = false) + private Date updateTime; + + public EcommerceOrder(Long userId, Long addressId, String orderDetail) { + + this.userId = userId; + this.addressId = addressId; + this.orderDetail = orderDetail; + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/AddressClient.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/AddressClient.java new file mode 100644 index 0000000..04df8c5 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/AddressClient.java @@ -0,0 +1,30 @@ +package org.example.feign; + +import org.example.account.AddressInfo; +import org.example.common.TableId; +import org.example.feign.hystrix.AddressClientHystrix; +import org.example.vo.CommonResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + *

用户账户服务 Feign 接口(安全的)

+ * */ +@FeignClient( + contextId = "AddressClient", + value = "dev-protocol-springcloud-project-account-service", // 调用账号微服务 + fallback = AddressClientHystrix.class // 兜底策略 +) +public interface AddressClient { + + /** + *

根据 id 查询地址信息

+ * */ + @RequestMapping( + value = "/dev-protocol-springcloud-project-account-service/address/address-info-by-table-id", + method = RequestMethod.POST + ) + CommonResponse getAddressInfoByTablesId(@RequestBody TableId tableId); +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/FeignConfig.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/FeignConfig.java new file mode 100644 index 0000000..c676922 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/FeignConfig.java @@ -0,0 +1,49 @@ +package org.example.feign; + +import feign.RequestInterceptor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; + +/** + *

Feign 调用时, 把 Header 也传递到服务提供方

+ * */ +@Slf4j +@Configuration +public class FeignConfig { + + /** + *

给 Feign 配置请求拦截器

+ * RequestInterceptor 是我们提供给 open-feign 的请求拦截器, 把 Header 信息传递 + * */ + @Bean + public RequestInterceptor headerInterceptor() { + + return template -> { + // 获取请求的信息 + ServletRequestAttributes attributes = + (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (null != attributes) { + HttpServletRequest request = attributes.getRequest(); + Enumeration headerNames = request.getHeaderNames(); + if (null != headerNames) { + while (headerNames.hasMoreElements()) { + String name = headerNames.nextElement(); + String values = request.getHeader(name); + // 不能把当前请求的 content-length 传递到下游的服务提供方, 这明显是不对的 + // 请求可能一直返回不了, 或者是请求响应数据被截断 + if (!name.equalsIgnoreCase("content-length")) { + // 这里的 template 就是 RestTemplate + template.header(name, values); + } + } + } + } + }; + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/NotSecuredBalanceClient.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/NotSecuredBalanceClient.java new file mode 100644 index 0000000..a8a0f7d --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/NotSecuredBalanceClient.java @@ -0,0 +1,25 @@ +package org.example.feign; + +import org.example.account.BalanceInfo; +import org.example.vo.CommonResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + *

用户账户服务 Feign 接口

+ * 无兜底策略 + * */ +@FeignClient( + contextId = "NotSecuredBalanceClient", + value = "dev-protocol-springcloud-project-account-service" // 调用账号微服务 +) +public interface NotSecuredBalanceClient { + + @RequestMapping( + value = "/dev-protocol-springcloud-project-account-service/balance/deduct-balance", + method = RequestMethod.PUT + ) + CommonResponse deductBalance(@RequestBody BalanceInfo balanceInfo); +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/NotSecuredGoodsClient.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/NotSecuredGoodsClient.java new file mode 100644 index 0000000..637e0de --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/NotSecuredGoodsClient.java @@ -0,0 +1,42 @@ +package org.example.feign; + +import org.example.common.TableId; +import org.example.goods.DeductGoodsInventory; +import org.example.goods.SimpleGoodsInfo; +import org.example.vo.CommonResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.util.List; + +/** + *

不安全的商品服务 Feign 接口

+ * */ +@FeignClient( + contextId = "NotSecuredGoodsClient", + value = "dev-protocol-springcloud-project-goods-service" // 调用商品微服务接口 +) +public interface NotSecuredGoodsClient { + + /** + *

根据 ids 查询简单的商品信息

+ * */ + @RequestMapping( + value = "/dev-protocol-springcloud-project-goods-service/goods/deduct-goods-inventory", + method = RequestMethod.PUT + ) + CommonResponse deductGoodsInventory( + @RequestBody List deductGoodsInventories); + + /** + *

根据 ids 查询简单的商品信息

+ * */ + @RequestMapping( + value = "/dev-protocol-springcloud-project-goods-service/goods/simple-goods-info", + method = RequestMethod.POST + ) + CommonResponse> getSimpleGoodsInfoByTableId( + @RequestBody TableId tableId); +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/SecuredGoodsClient.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/SecuredGoodsClient.java new file mode 100644 index 0000000..018851a --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/SecuredGoodsClient.java @@ -0,0 +1,33 @@ +package org.example.feign; + +import org.example.common.TableId; +import org.example.feign.hystrix.GoodsClientHystrix; +import org.example.goods.SimpleGoodsInfo; +import org.example.vo.CommonResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.util.List; + +/** + *

商品服务 Feign 接口(安全的)

+ * */ +@FeignClient( + contextId = "SecuredGoodsClient", + value = "dev-protocol-springcloud-project-goods-service", + fallback = GoodsClientHystrix.class +) +public interface SecuredGoodsClient { + + /** + *

根据 ids 查询简单的商品信息

+ * */ + @RequestMapping( + value = "/dev-protocol-springcloud-project-goods-service/goods/simple-goods-info", + method = RequestMethod.POST + ) + CommonResponse> getSimpleGoodsInfoByTableId( + @RequestBody TableId tableId); +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/hystrix/AddressClientHystrix.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/hystrix/AddressClientHystrix.java new file mode 100644 index 0000000..7ae8278 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/hystrix/AddressClientHystrix.java @@ -0,0 +1,31 @@ +package org.example.feign.hystrix; + +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.example.account.AddressInfo; +import org.example.common.TableId; +import org.example.feign.AddressClient; +import org.example.vo.CommonResponse; +import org.springframework.stereotype.Component; + +import java.util.Collections; + +/** + *

账户服务熔断降级兜底策略

+ * */ +@Slf4j +@Component +public class AddressClientHystrix implements AddressClient { + + @Override + public CommonResponse getAddressInfoByTablesId(TableId tableId) { + + log.error("[account client feign request error in order service] get address info" + + "error: [{}]", JSON.toJSONString(tableId)); + return new CommonResponse<>( + -1, + "[account client feign request error in order service]", + new AddressInfo(-1L, Collections.emptyList()) // 返回用户Id为-1, 兜底返回一个空的List + ); + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/hystrix/GoodsClientHystrix.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/hystrix/GoodsClientHystrix.java new file mode 100644 index 0000000..4c5b1fe --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/hystrix/GoodsClientHystrix.java @@ -0,0 +1,32 @@ +package org.example.feign.hystrix; + +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.example.common.TableId; +import org.example.feign.SecuredGoodsClient; +import org.example.goods.SimpleGoodsInfo; +import org.example.vo.CommonResponse; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +/** + *

商品服务熔断降级兜底

+ * */ +@Slf4j +@Component +public class GoodsClientHystrix implements SecuredGoodsClient { + + @Override + public CommonResponse> getSimpleGoodsInfoByTableId(TableId tableId) { + + log.error("[goods client feign request error in order service] get simple goods" + + "error: [{}]", JSON.toJSONString(tableId)); + return new CommonResponse<>( + -1, + "[goods client feign request error in order service]", + Collections.emptyList() // 兜底返回一个空的List + ); + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/service/IOrderService.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/service/IOrderService.java new file mode 100644 index 0000000..5ce647e --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/service/IOrderService.java @@ -0,0 +1,22 @@ +package org.example.service; + + +import org.example.common.TableId; +import org.example.order.OrderInfo; +import org.example.vo.PageSimpleOrderDetail; + +/** + *

订单相关服务接口定义

+ * */ +public interface IOrderService { + + /** + *

下单(分布式事务): 创建订单 -> 扣减库存 -> 扣减余额 -> 创建物流信息(Stream + Kafka)

+ * */ + TableId createOrder(OrderInfo orderInfo); + + /** + *

获取当前用户的订单信息: 带有分页

+ * */ + PageSimpleOrderDetail getSimpleOrderDetailByPage(int page); +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/service/impl/OrderServiceImpl.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/service/impl/OrderServiceImpl.java new file mode 100644 index 0000000..7a3f350 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/service/impl/OrderServiceImpl.java @@ -0,0 +1,286 @@ +package org.example.service.impl; + +import com.alibaba.fastjson.JSON; +import io.seata.spring.annotation.GlobalTransactional; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.example.account.AddressInfo; +import org.example.account.BalanceInfo; +import org.example.common.TableId; +import org.example.dao.EcommerceOrderDao; +import org.example.entity.EcommerceOrder; +import org.example.feign.AddressClient; +import org.example.feign.NotSecuredBalanceClient; +import org.example.feign.NotSecuredGoodsClient; +import org.example.feign.SecuredGoodsClient; +import org.example.filter.AccessContext; +import org.example.goods.DeductGoodsInventory; +import org.example.goods.SimpleGoodsInfo; +import org.example.order.LogisticsMessage; +import org.example.order.OrderInfo; +import org.example.service.IOrderService; +import org.example.source.LogisticsSource; +import org.example.vo.PageSimpleOrderDetail; +import org.springframework.cloud.stream.annotation.EnableBinding; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + *

订单相关服务接口实现

+ * */ +@Slf4j +@Service +@EnableBinding(LogisticsSource.class) +public class OrderServiceImpl implements IOrderService { + + /** 表的 dao 接口 */ + private final EcommerceOrderDao orderDao; + + /** Feign 客户端 */ + private final AddressClient addressClient; + private final SecuredGoodsClient securedGoodsClient; + private final NotSecuredGoodsClient notSecuredGoodsClient; + private final NotSecuredBalanceClient notSecuredBalanceClient; + + /** SpringCloud Stream 的发射器 */ + private final LogisticsSource logisticsSource; + + public OrderServiceImpl(EcommerceOrderDao orderDao, + // fixme 这里如果报红的话, 可以不用理会, 去 Idea 工具中进行配置忽略 设置-> 编辑器(Editor) -> 检查(Inspections) -> + // Spring | Spring Core | 代码 | Spring Bean 组件中不正确的自动装配(Autowiring for bean class) + AddressClient addressClient, + SecuredGoodsClient securedGoodsClient, + NotSecuredGoodsClient notSecuredGoodsClient, + NotSecuredBalanceClient notSecuredBalanceClient, + LogisticsSource logisticsSource) { + this.orderDao = orderDao; + this.addressClient = addressClient; + this.securedGoodsClient = securedGoodsClient; + this.notSecuredGoodsClient = notSecuredGoodsClient; + this.notSecuredBalanceClient = notSecuredBalanceClient; + this.logisticsSource = logisticsSource; + } + + /** + *

创建订单: 这里会涉及到分布式事务

+ * 创建订单会涉及到多个步骤和校验, 当不满足情况时直接抛出异常; + * 1. 校验请求对象是否合法 + * 2. 创建订单 - 本地事务 + * 3. 扣减商品库存 - 本地事务 + * 4. 扣减用户余额 - 本地事务 + * 5. 发送订单物流消息 SpringCloud Stream + Kafka + * */ + @Override + @GlobalTransactional(rollbackFor = Exception.class) // 分布式事务注解 + public TableId createOrder(OrderInfo orderInfo) { + + // 获取地址信息 + AddressInfo addressInfo = addressClient.getAddressInfoByTablesId( + new TableId(Collections.singletonList( + new TableId.Id(orderInfo.getUserAddress())))).getData(); + + // 1. 校验请求对象是否合法(商品信息不需要校验, 扣减库存会做校验) + if (CollectionUtils.isEmpty(addressInfo.getAddressItems())) { + throw new RuntimeException("user address is not exist: " + + orderInfo.getUserAddress()); + } + + // 2. 创建订单 + EcommerceOrder newOrder = orderDao.save( + new EcommerceOrder( + AccessContext.getLoginUserInfo().getId(), + orderInfo.getUserAddress(), + JSON.toJSONString(orderInfo.getOrderItems()) + ) + ); + log.info("create order success: [{}], [{}]", + AccessContext.getLoginUserInfo().getId(), newOrder.getId()); + + // 3. 扣减商品库存 + if ( + !notSecuredGoodsClient.deductGoodsInventory( + orderInfo.getOrderItems() + .stream() + .map(OrderInfo.OrderItem::toDeductGoodsInventory) + .collect(Collectors.toList()) + ).getData() + ) { + throw new RuntimeException("deduct goods inventory failure"); + } + + // 4. 扣减用户账户余额 + // 4.1 获取商品信息, 计算总价格 + List goodsInfos = notSecuredGoodsClient.getSimpleGoodsInfoByTableId( + new TableId( + orderInfo.getOrderItems() + .stream() + .map(o -> new TableId.Id(o.getGoodsId())) + .collect(Collectors.toList()) + ) + ).getData(); + + Map goodsId2GoodsInfo = goodsInfos.stream() + .collect(Collectors.toMap(SimpleGoodsInfo::getId, Function.identity())); + long balance = 0; + for (OrderInfo.OrderItem orderItem : orderInfo.getOrderItems()) { + balance += goodsId2GoodsInfo.get(orderItem.getGoodsId()).getPrice() + * orderItem.getCount(); + } + assert balance > 0; + + // 4.2 填写总价格, 扣减账户余额 + BalanceInfo balanceInfo = notSecuredBalanceClient.deductBalance( + new BalanceInfo(AccessContext.getLoginUserInfo().getId(), balance) + ).getData(); + if (null == balanceInfo) { + throw new RuntimeException("deduct user balance failure"); + } + log.info("deduct user balance: [{}], [{}]", newOrder.getId(), + JSON.toJSONString(balanceInfo)); + + // 5. 发送订单物流消息 SpringCloud Stream + Kafka + LogisticsMessage logisticsMessage = new LogisticsMessage( + AccessContext.getLoginUserInfo().getId(), + newOrder.getId(), + orderInfo.getUserAddress(), + null // 没有备注信息 + ); + if (!logisticsSource.logisticsOutput().send( + MessageBuilder.withPayload(JSON.toJSONString(logisticsMessage)).build() + )) { + throw new RuntimeException("send logistics message failure"); + } + log.info("send create order message to kafka with stream: [{}]", + JSON.toJSONString(logisticsMessage)); + + // 返回订单 id + return new TableId(Collections.singletonList(new TableId.Id(newOrder.getId()))); + } + + @Override + public PageSimpleOrderDetail getSimpleOrderDetailByPage(int page) { + + if (page <= 0) { + page = 1; // 默认是第一页 + } + + // 这里分页的规则是: 1页10条数据, 按照 id 倒序排列 + Pageable pageable = PageRequest.of(page - 1, 10, + Sort.by("id").descending()); + Page orderPage = orderDao.findAllByUserId( + AccessContext.getLoginUserInfo().getId(), pageable + ); + List orders = orderPage.getContent(); + + // 如果是空, 直接返回空数组 + if (CollectionUtils.isEmpty(orders)) { + return new PageSimpleOrderDetail(Collections.emptyList(), false); + } + + // 获取当前订单中所有的 goodsId, 这个 set 不可能为空或者是 null, 否则, 代码一定有 bug + Set goodsIdsInOrders = new HashSet<>(); + orders.forEach(o -> { + List goodsAndCount = JSON.parseArray( + o.getOrderDetail(), DeductGoodsInventory.class + ); + goodsIdsInOrders.addAll(goodsAndCount.stream() + .map(DeductGoodsInventory::getGoodsId) + .collect(Collectors.toSet())); + }); + + assert CollectionUtils.isNotEmpty(goodsIdsInOrders); + + // 是否还有更多页: 总页数是否大于当前给定的页 + boolean hasMore = orderPage.getTotalPages() > page; + + // 获取商品信息 + List goodsInfos = securedGoodsClient.getSimpleGoodsInfoByTableId( + new TableId(goodsIdsInOrders.stream() + .map(TableId.Id::new).collect(Collectors.toList())) + ).getData(); + + // 获取地址信息 + AddressInfo addressInfo = addressClient.getAddressInfoByTablesId( + new TableId(orders.stream() + .map(o -> new TableId.Id(o.getAddressId())) + .distinct().collect(Collectors.toList())) + ).getData(); + + // 组装订单中的商品, 地址信息 -> 订单信息 + return new PageSimpleOrderDetail( + assembleSimpleOrderDetail(orders, goodsInfos, addressInfo), + hasMore + ); + } + + /** + *

组装订单详情

+ * */ + private List assembleSimpleOrderDetail( + List orders, List goodsInfos, + AddressInfo addressInfo + ) { + // goodsId -> SimpleGoodsInfo + Map id2GoodsInfo = goodsInfos.stream() + .collect(Collectors.toMap(SimpleGoodsInfo::getId, Function.identity())); + // addressId -> AddressInfo.AddressItem + Map id2AddressItem = addressInfo.getAddressItems() + .stream().collect( + Collectors.toMap(AddressInfo.AddressItem::getId, Function.identity()) + ); + + List result = new ArrayList<>(orders.size()); + orders.forEach(o -> { + + PageSimpleOrderDetail.SingleOrderItem orderItem = + new PageSimpleOrderDetail.SingleOrderItem(); + orderItem.setId(o.getId()); + orderItem.setUserAddress(id2AddressItem.getOrDefault(o.getAddressId(), + new AddressInfo.AddressItem(-1L)).toUserAddress()); + orderItem.setGoodsItems(buildOrderGoodsItem(o, id2GoodsInfo)); + + result.add(orderItem); + }); + + return result; + } + + /** + *

构造订单中的商品信息

+ * */ + private List buildOrderGoodsItem( + EcommerceOrder order, Map id2GoodsInfo + ) { + + List goodsItems = new ArrayList<>(); + List goodsAndCount = JSON.parseArray( + order.getOrderDetail(), DeductGoodsInventory.class + ); + + goodsAndCount.forEach(gc -> { + + PageSimpleOrderDetail.SingleOrderGoodsItem goodsItem = + new PageSimpleOrderDetail.SingleOrderGoodsItem(); + goodsItem.setCount(gc.getCount()); + goodsItem.setSimpleGoodsInfo(id2GoodsInfo.getOrDefault(gc.getGoodsId(), + new SimpleGoodsInfo(-1L))); + + goodsItems.add(goodsItem); + }); + + return goodsItems; + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/source/LogisticsSource.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/source/LogisticsSource.java new file mode 100644 index 0000000..dd96925 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/source/LogisticsSource.java @@ -0,0 +1,20 @@ +package org.example.source; + +import org.springframework.cloud.stream.annotation.Output; +import org.springframework.messaging.MessageChannel; + +/** + *

自定义物流消息通信信道(Source)

+ * */ +public interface LogisticsSource { + + /** 输出信道名称 */ + String OUTPUT = "logisticsOutput"; + + /** + *

物流 Source -> logisticsOutput

+ * 通信信道的名称是 logisticsOutput, 对应到 yml 文件里的配置 + * */ + @Output(LogisticsSource.OUTPUT) + MessageChannel logisticsOutput(); +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/vo/PageSimpleOrderDetail.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/vo/PageSimpleOrderDetail.java new file mode 100644 index 0000000..5fdcbde --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/vo/PageSimpleOrderDetail.java @@ -0,0 +1,59 @@ +package org.example.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.example.account.UserAddress; +import org.example.goods.SimpleGoodsInfo; + +import java.util.List; + +/** + *

订单详情

- 分页, 仅用于订单服务 + * */ +@ApiModel(description = "分页订单详情对象") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PageSimpleOrderDetail { + + @ApiModelProperty(value = "订单详情") + private List orderItems; + + @ApiModelProperty(value = "是否有更多的订单(分页)") + private Boolean hasMore; + + /** + *

单个订单信息

+ * */ + @ApiModel(description = "单个订单信息对象") + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class SingleOrderItem { + + @ApiModelProperty(value = "订单表主键 id") + private Long id; + + @ApiModelProperty(value = "用户地址信息") + private UserAddress userAddress; + + @ApiModelProperty(value = "订单商品信息") + private List goodsItems; + } + + @ApiModel(description = "单个订单中的单项商品信息") + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class SingleOrderGoodsItem { + + @ApiModelProperty(value = "简单商品信息") + private SimpleGoodsInfo simpleGoodsInfo; + + @ApiModelProperty(value = "商品个数") + private Integer count; + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/bootstrap.yml b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/bootstrap.yml index 687b265..7a10dcb 100644 --- a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/bootstrap.yml +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/bootstrap.yml @@ -20,7 +20,7 @@ spring: content-type: text/plain alibaba: seata: - tx-service-group: dev-protocol # seata 全局事务分组 + tx-service-group: dev-protocol # seata 全局事务分组, 对应 file.conf 里面的配置名一样 nacos: discovery: enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可 diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/file.conf b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/file.conf index 4e03188..909a828 100644 --- a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/file.conf +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/file.conf @@ -53,7 +53,7 @@ store { } service { - vgroupMapping.imooc-ecommerce = "default" + vgroupMapping.dev-protocol = "default" default.grouplist = "127.0.0.1:8091" } client { diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/sql/undo_log.sql b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/sql/undo_log.sql index 71fb4a1..49629e7 100644 --- a/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/sql/undo_log.sql +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/resources/sql/undo_log.sql @@ -10,4 +10,4 @@ CREATE TABLE `undo_log` ( `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/pom.xml index 1a20987..ce317a7 100644 --- a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/pom.xml +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/pom.xml @@ -52,7 +52,7 @@ org.springframework.cloud spring-cloud-starter-openfeign - + com.alibaba.cloud spring-cloud-starter-alibaba-seata diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/DataSourceProxyAutoConfiguration.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/DataSourceProxyAutoConfiguration.java new file mode 100644 index 0000000..e09ae04 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/DataSourceProxyAutoConfiguration.java @@ -0,0 +1,106 @@ +package org.example.conf; + +//import com.alibaba.druid.support.http.StatViewServlet; +//import com.alibaba.druid.support.http.WebStatFilter; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.fastjson2.JSON; +//import com.zaxxer.hikari.HikariDataSource; +import io.seata.rm.datasource.DataSourceProxy; +import com.zaxxer.hikari.HikariDataSource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import javax.annotation.Resource; +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + +/** + * @author q + * @createTime 2022/12/28/ 16:13:00 + * @Description Seata 所需要的数据源代理配置类 + */ +@Slf4j +@Configuration +public class DataSourceProxyAutoConfiguration { + + @Resource + private DataSourceProperties dataSourceProperties; + + /** + * 配置数据源代理, 用于 Seata 全局事务回滚 + * before image + after image -> undo_log + */ + @Primary + @Bean("dataSource") + public DataSource dataSource() { + + HikariDataSource dataSource = new HikariDataSource(); + log.info("dataSource properties:[{}]", JSON.toJSONString( + dataSourceProperties + )); + dataSource.setJdbcUrl(dataSourceProperties.getUrl()); + dataSource.setUsername(dataSourceProperties.getUsername()); + dataSource.setPassword(dataSourceProperties.getPassword()); + dataSource.setDriverClassName(dataSourceProperties.getDriverClassName()); + return new DataSourceProxy(dataSource); + } + + +// @ConfigurationProperties(prefix = "spring.datasource") +// @Bean +// public DataSource dataSource(){ +// return new DruidDataSource(); +// } + +// @Bean +// @ConfigurationProperties(prefix = "spring.datasource") +// public DataSource druidDataSource(){ +// return new DruidDataSource(); +// } +// +// @Bean +// public DataSourceProxy dataSourceProxy(DataSource dataSource) { +// return new DataSourceProxy(dataSource); +// } + + + /** + * @Description: 后台监控 + * @Author: J.Flying + * @Date: 2020/10/20 + */ +// @Bean +// public ServletRegistrationBean registrationBean(){ +// ServletRegistrationBean bean = new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*"); +// Map initParameters=new HashMap<>(); +// // 登录名 +// initParameters.put("loginUsername","admin"); +// initParameters.put("loginPassword","1234"); +// +// //准许访问 +// initParameters.put("allow",""); +// +// bean.setInitParameters(initParameters); +// return bean; +// } + +// @Bean +// public FilterRegistrationBean druidStatFilter() { +// FilterRegistrationBean filterRegistrationBean = +// new FilterRegistrationBean(new WebStatFilter()); +// //添加过滤规则 +// filterRegistrationBean.addUrlPatterns("/*"); +// //添加需要忽略的格式信息 +// filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif," + +// "*.jpg,*.png, *.css,*.ico,/druid/*"); +// return filterRegistrationBean; +// } + +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/DevProtocolWebMvcConfig.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/DevProtocolWebMvcConfig.java index b78b70d..dde2add 100644 --- a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/DevProtocolWebMvcConfig.java +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/DevProtocolWebMvcConfig.java @@ -1,11 +1,16 @@ package org.example.conf; +import com.alibaba.cloud.seata.web.SeataHandlerInterceptor; import org.example.filter.LoginUserInfoInterceptor; import org.springframework.context.annotation.Configuration; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.config.annotation.InterceptorRegistration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; +import java.util.List; + /** *

Web Mvc 配置

* */ @@ -22,6 +27,27 @@ public class DevProtocolWebMvcConfig extends WebMvcConfigurationSupport { // 添加用户身份统一登录拦截的拦截器 registry.addInterceptor(new LoginUserInfoInterceptor()) .addPathPatterns("/**").order(0); + + // Seata 传递 xid 事务 id 给其他的微服务 + // 只有这样, 其他的服务才会写 undo_log, 才能够实现回滚 + registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns("/**"); + + //通用拦截器排除swagger设置,所有拦截器都会自动加swagger相关的资源排除信息 +// try { +// Field registrationsField = FieldUtils.getField(InterceptorRegistry.class, "registrations", true); +// List registrations = (List) ReflectionUtils.getField(registrationsField, registry); +// if (registrations != null) { +// for (InterceptorRegistration interceptorRegistration : registrations) { +// interceptorRegistration +// .excludePathPatterns("/swagger**/**") +// .excludePathPatterns("/webjars/**") +// .excludePathPatterns("/v3/**") +// .excludePathPatterns("/doc.html"); +// } +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } } /** diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/order/LogisticsMessage.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/order/LogisticsMessage.java new file mode 100644 index 0000000..2f25d46 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/order/LogisticsMessage.java @@ -0,0 +1,33 @@ +package org.example.order; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * @author q + * @createTime 2022/12/29/ 17:23:00 + * @Description 创建订单时发送的物流消息 + */ +@ApiModel(description = "Stream 物流消息对象") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class LogisticsMessage { + + @ApiModelProperty(value = "用户表主键 id") + private Long userId; + + @ApiModelProperty(value = "订单表主键 id") + private Long orderId; + + @ApiModelProperty(value = "用户地址表主键 id") + private Long addressId; + + @ApiModelProperty(value = "备注信息(json 存储)") + private String extraInfo; +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/order/OrderInfo.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/order/OrderInfo.java new file mode 100644 index 0000000..7dcada4 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/order/OrderInfo.java @@ -0,0 +1,54 @@ +package org.example.order; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.example.goods.DeductGoodsInventory; + +import java.util.List; + +/** + * @author danny + * @createTime 2022/12/29/ 17:21:00 + * @Description 订单信息 + */ +@ApiModel(description = "用户发起购买订单") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class OrderInfo { + + @ApiModelProperty(value = "用户地址表主键 id") + private Long userAddress; + + @ApiModelProperty(value = "订单中的商品信息") + private List orderItems; + + /** + * 订单中的商品信息 + */ + @ApiModel(description = "订单中的单项商品信息") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class OrderItem { + + @ApiModelProperty(value = "商品表主键 id") + private Long goodsId; + + @ApiModelProperty(value = "购买商品个数") + private Integer count; + + /** + * 扣减库存 + */ + public DeductGoodsInventory toDeductGoodsInventory() { + return new DeductGoodsInventory(this.goodsId, this.count); + } + } +}