From 79b41707059a5b5fbcbeddaa0fdca67f68c84831 Mon Sep 17 00:00:00 2001
From: qyx <565485304@qq.com>
Date: Fri, 20 Sep 2024 14:42:32 +0800
Subject: [PATCH] =?UTF-8?q?feat(master):=E8=AE=A2=E5=8D=95=E5=BE=AE?=
=?UTF-8?q?=E6=9C=8D=E5=8A=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
代码完成
---
.../java/org/example/AccountApplication.java | 3 +
.../src/main/resources/bootstrap.yml | 3 +
.../src/main/resources/file.conf | 65 ++++
.../src/main/resources/registry.conf | 17 ++
.../java/org/example/GoodsApplication.java | 3 +
.../src/main/resources/bootstrap.yml | 3 +
.../src/main/resources/file.conf | 65 ++++
.../src/main/resources/registry.conf | 17 ++
.../README.md | 4 +
.../java/org/example/OrderApplication.java | 5 +-
.../example/controller/OrderController.java | 52 ++++
.../org/example/dao/EcommerceOrderDao.java | 20 ++
.../org/example/entity/EcommerceOrder.java | 64 ++++
.../java/org/example/feign/AddressClient.java | 30 ++
.../java/org/example/feign/FeignConfig.java | 49 +++
.../feign/NotSecuredBalanceClient.java | 25 ++
.../example/feign/NotSecuredGoodsClient.java | 42 +++
.../org/example/feign/SecuredGoodsClient.java | 33 ++
.../feign/hystrix/AddressClientHystrix.java | 31 ++
.../feign/hystrix/GoodsClientHystrix.java | 32 ++
.../org/example/service/IOrderService.java | 22 ++
.../service/impl/OrderServiceImpl.java | 286 ++++++++++++++++++
.../org/example/source/LogisticsSource.java | 20 ++
.../org/example/vo/PageSimpleOrderDetail.java | 59 ++++
.../src/main/resources/bootstrap.yml | 2 +-
.../src/main/resources/file.conf | 2 +-
.../src/main/resources/sql/undo_log.sql | 2 +-
.../pom.xml | 2 +-
.../DataSourceProxyAutoConfiguration.java | 106 +++++++
.../example/conf/DevProtocolWebMvcConfig.java | 26 ++
.../org/example/order/LogisticsMessage.java | 33 ++
.../java/org/example/order/OrderInfo.java | 54 ++++
32 files changed, 1172 insertions(+), 5 deletions(-)
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/file.conf
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-account-service/src/main/resources/registry.conf
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/file.conf
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/resources/registry.conf
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/README.md
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/controller/OrderController.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/dao/EcommerceOrderDao.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/entity/EcommerceOrder.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/AddressClient.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/FeignConfig.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/NotSecuredBalanceClient.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/NotSecuredGoodsClient.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/SecuredGoodsClient.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/hystrix/AddressClientHystrix.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/feign/hystrix/GoodsClientHystrix.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/service/IOrderService.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/service/impl/OrderServiceImpl.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/source/LogisticsSource.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-order-service/src/main/java/org/example/vo/PageSimpleOrderDetail.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-service-config/src/main/java/org/example/conf/DataSourceProxyAutoConfiguration.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/order/LogisticsMessage.java
create mode 100644 dev-protocol-springcloud/dev-protocol-springcloud-project-service-sdk/src/main/java/org/example/order/OrderInfo.java
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);
+ }
+ }
+}