diff --git a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/OrderServiceImpl.java b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/OrderServiceImpl.java
index 40e67811..1d0e633b 100644
--- a/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/OrderServiceImpl.java
+++ b/lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/OrderServiceImpl.java
@@ -27,9 +27,9 @@ public class OrderServiceImpl implements OrderService {
@Autowired
private ProductService productService;
- @GlobalTransactional
@Override
@DS(value = "order-ds")
+ @GlobalTransactional
public Integer createOrder(Long userId, Long productId, Integer price) throws Exception {
Integer amount = 1; // 购买数量,暂时设置为 1。
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/pom.xml b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/pom.xml
new file mode 100644
index 00000000..b803d0e2
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.2.RELEASE
+
+
+ 4.0.0
+
+ lab-52-seata-at-httpclient-demo-account-service
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ mysql
+ mysql-connector-java
+ 5.1.48
+
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 2.1.2
+
+
+
+
+ io.seata
+ seata-spring-boot-starter
+ 1.1.0
+
+
+
+ io.seata
+ seata-http
+ 1.1.0
+
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.8
+
+
+
+
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/AccountServiceApplication.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/AccountServiceApplication.java
new file mode 100644
index 00000000..713a4c23
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/AccountServiceApplication.java
@@ -0,0 +1,13 @@
+package cn.iocoder.springboot.lab52.accountservice;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class AccountServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(AccountServiceApplication.class, args);
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/controller/AccountController.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/controller/AccountController.java
new file mode 100644
index 00000000..dd0eed2b
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/controller/AccountController.java
@@ -0,0 +1,36 @@
+package cn.iocoder.springboot.lab52.accountservice.controller;
+
+import cn.iocoder.springboot.lab52.accountservice.dto.AccountReduceBalanceDTO;
+import cn.iocoder.springboot.lab52.accountservice.service.AccountService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+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.RestController;
+
+@RestController
+@RequestMapping("/account")
+public class AccountController {
+
+ private Logger logger = LoggerFactory.getLogger(AccountController.class);
+
+ @Autowired
+ private AccountService accountService;
+
+ @PostMapping("/reduce-balance")
+ public Boolean reduceBalance(@RequestBody AccountReduceBalanceDTO accountReduceBalanceDTO) {
+ logger.info("[reduceBalance] 收到减少余额请求, 用户:{}, 金额:{}", accountReduceBalanceDTO.getUserId(),
+ accountReduceBalanceDTO.getPrice());
+ try {
+ accountService.reduceBalance(accountReduceBalanceDTO.getUserId(), accountReduceBalanceDTO.getPrice());
+ // 正常扣除余额,返回 true
+ return true;
+ } catch (Exception e) {
+ // 失败扣除余额,返回 false
+ return false;
+ }
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/dao/AccountDao.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/dao/AccountDao.java
new file mode 100644
index 00000000..17ac5957
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/dao/AccountDao.java
@@ -0,0 +1,32 @@
+package cn.iocoder.springboot.lab52.accountservice.dao;
+
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AccountDao {
+
+ /**
+ * 获取账户余额
+ *
+ * @param userId 用户 ID
+ * @return 账户余额
+ */
+ @Select("SELECT balance FROM account WHERE id = #{userId}")
+ Integer getBalance(@Param("userId") Long userId);
+
+ /**
+ * 扣减余额
+ *
+ * @param price 需要扣减的数目
+ * @return 影响记录行数
+ */
+ @Update("UPDATE account SET balance = balance - #{price} WHERE id = 1 AND balance >= ${price}")
+ int reduceBalance(@Param("price") Integer price);
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/dto/AccountReduceBalanceDTO.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/dto/AccountReduceBalanceDTO.java
new file mode 100644
index 00000000..be3ecdf5
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/dto/AccountReduceBalanceDTO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.springboot.lab52.accountservice.dto;
+
+/**
+ * 账户减少余额 DTO
+ */
+public class AccountReduceBalanceDTO {
+
+ /**
+ * 用户编号
+ */
+ private Long userId;
+
+ /**
+ * 扣减金额
+ */
+ private Integer price;
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public AccountReduceBalanceDTO setUserId(Long userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ public Integer getPrice() {
+ return price;
+ }
+
+ public AccountReduceBalanceDTO setPrice(Integer price) {
+ this.price = price;
+ return this;
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/service/AccountService.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/service/AccountService.java
new file mode 100644
index 00000000..c5eb6668
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/service/AccountService.java
@@ -0,0 +1,17 @@
+package cn.iocoder.springboot.lab52.accountservice.service;
+
+/**
+ * 账户 Service
+ */
+public interface AccountService {
+
+ /**
+ * 扣除余额
+ *
+ * @param userId 用户编号
+ * @param price 扣减金额
+ * @throws Exception 失败时抛出异常
+ */
+ void reduceBalance(Long userId, Integer price) throws Exception;
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/service/AccountServiceImpl.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/service/AccountServiceImpl.java
new file mode 100644
index 00000000..d63abb38
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/java/cn/iocoder/springboot/lab52/accountservice/service/AccountServiceImpl.java
@@ -0,0 +1,47 @@
+package cn.iocoder.springboot.lab52.accountservice.service;
+
+import cn.iocoder.springboot.lab52.accountservice.dao.AccountDao;
+import io.seata.core.context.RootContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class AccountServiceImpl implements AccountService {
+
+ private Logger logger = LoggerFactory.getLogger(getClass());
+
+ @Autowired
+ private AccountDao accountDao;
+
+ @Override
+ @Transactional // 开启新事物
+ public void reduceBalance(Long userId, Integer price) throws Exception {
+ logger.info("[reduceBalance] 当前 XID: {}", RootContext.getXID());
+
+ // 检查余额
+ checkBalance(userId, price);
+
+ logger.info("[reduceBalance] 开始扣减用户 {} 余额", userId);
+ // 扣除余额
+ int updateCount = accountDao.reduceBalance(price);
+ // 扣除成功
+ if (updateCount == 0) {
+ logger.warn("[reduceBalance] 扣除用户 {} 余额失败", userId);
+ throw new Exception("余额不足");
+ }
+ logger.info("[reduceBalance] 扣除用户 {} 余额成功", userId);
+ }
+
+ private void checkBalance(Long userId, Integer price) throws Exception {
+ logger.info("[checkBalance] 检查用户 {} 余额", userId);
+ Integer balance = accountDao.getBalance(userId);
+ if (balance < price) {
+ logger.warn("[checkBalance] 用户 {} 余额不足,当前余额:{}", userId, balance);
+ throw new Exception("余额不足");
+ }
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/resources/application.yaml b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/resources/application.yaml
new file mode 100644
index 00000000..ea7faeae
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-account-service/src/main/resources/application.yaml
@@ -0,0 +1,25 @@
+server:
+ port: 8083
+
+spring:
+ application:
+ name: account-service
+
+ datasource:
+ url: jdbc:mysql://127.0.0.1:3306/seata_account?useSSL=false&useUnicode=true&characterEncoding=UTF-8
+ driver-class-name: com.mysql.jdbc.Driver
+ username: root
+ password:
+
+# Seata 配置项,对应 SeataProperties 类
+seata:
+ application-id: ${spring.application.name} # Seata 应用编号,默认为 ${spring.application.name}
+ tx-service-group: ${spring.application.name}-group # Seata 事务组编号,用于 TC 集群名
+ # 服务配置项,对应 ServiceProperties 类
+ service:
+ # 虚拟组和分组的映射
+ vgroup-mapping:
+ account-service-group: default
+ # 分组和 Seata 服务的映射
+ grouplist:
+ default: 127.0.0.1:8091
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/pom.xml b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/pom.xml
new file mode 100644
index 00000000..43a785c6
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.2.RELEASE
+
+
+ 4.0.0
+
+ lab-52-seata-at-httpclient-demo-order-service
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ mysql
+ mysql-connector-java
+ 5.1.48
+
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 2.1.2
+
+
+
+
+ io.seata
+ seata-spring-boot-starter
+ 1.1.0
+
+
+
+ io.seata
+ seata-http
+ 1.1.0
+
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.8
+
+
+
+
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/OrderServiceApplication.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/OrderServiceApplication.java
new file mode 100644
index 00000000..7f9ae1b5
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/OrderServiceApplication.java
@@ -0,0 +1,13 @@
+package cn.iocoder.springboot.lab52.orderservice;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class OrderServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(OrderServiceApplication.class, args);
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/controller/OrderController.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/controller/OrderController.java
new file mode 100644
index 00000000..51f4154e
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/controller/OrderController.java
@@ -0,0 +1,29 @@
+package cn.iocoder.springboot.lab52.orderservice.controller;
+
+import cn.iocoder.springboot.lab52.orderservice.service.OrderService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/order")
+public class OrderController {
+
+ private Logger logger = LoggerFactory.getLogger(OrderController.class);
+
+ @Autowired
+ private OrderService orderService;
+
+ @PostMapping("/create")
+ public Integer createOrder(@RequestParam("userId") Long userId,
+ @RequestParam("productId") Long productId,
+ @RequestParam("price") Integer price) throws Exception {
+ logger.info("[createOrder] 收到下单请求,用户:{}, 商品:{}, 价格:{}", userId, productId, price);
+ return orderService.createOrder(userId, productId, price);
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/dao/OrderDao.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/dao/OrderDao.java
new file mode 100644
index 00000000..6001ee4f
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/dao/OrderDao.java
@@ -0,0 +1,23 @@
+package cn.iocoder.springboot.lab52.orderservice.dao;
+
+import cn.iocoder.springboot.lab53.orderservice.entity.OrderDO;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Options;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface OrderDao {
+
+ /**
+ * 插入订单记录
+ *
+ * @param order 订单
+ * @return 影响记录数量
+ */
+ @Insert("INSERT INTO orders (user_id, product_id, pay_amount) VALUES (#{userId}, #{productId}, #{payAmount})")
+ @Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
+ int saveOrder(OrderDO order);
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/entity/OrderDO.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/entity/OrderDO.java
new file mode 100644
index 00000000..4ff509e4
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/entity/OrderDO.java
@@ -0,0 +1,56 @@
+package cn.iocoder.springboot.lab53.orderservice.entity;
+
+/**
+ * 订单实体
+ */
+public class OrderDO {
+
+ /** 订单编号 **/
+ private Integer id;
+
+ /** 用户编号 **/
+ private Long userId;
+
+ /** 产品编号 **/
+ private Long productId;
+
+ /** 支付金额 **/
+ private Integer payAmount;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public OrderDO setId(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public OrderDO setUserId(Long userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ public Long getProductId() {
+ return productId;
+ }
+
+ public OrderDO setProductId(Long productId) {
+ this.productId = productId;
+ return this;
+ }
+
+ public Integer getPayAmount() {
+ return payAmount;
+ }
+
+ public OrderDO setPayAmount(Integer payAmount) {
+ this.payAmount = payAmount;
+ return this;
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/service/OrderService.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/service/OrderService.java
new file mode 100644
index 00000000..1ca8d010
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/service/OrderService.java
@@ -0,0 +1,19 @@
+package cn.iocoder.springboot.lab52.orderservice.service;
+
+/**
+ * 订单 Service
+ */
+public interface OrderService {
+
+ /**
+ * 创建订单
+ *
+ * @param userId 用户编号
+ * @param productId 产品编号
+ * @param price 价格
+ * @return 订单编号
+ * @throws Exception 创建订单失败,抛出异常
+ */
+ Integer createOrder(Long userId, Long productId, Integer price) throws Exception;
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/service/OrderServiceImpl.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/service/OrderServiceImpl.java
new file mode 100644
index 00000000..dd1d8c13
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/java/cn/iocoder/springboot/lab52/orderservice/service/OrderServiceImpl.java
@@ -0,0 +1,76 @@
+package cn.iocoder.springboot.lab52.orderservice.service;
+
+import cn.iocoder.springboot.lab52.orderservice.dao.OrderDao;
+import cn.iocoder.springboot.lab53.orderservice.entity.OrderDO;
+import com.alibaba.fastjson.JSONObject;
+import io.seata.core.context.RootContext;
+import io.seata.integration.http.DefaultHttpExecutor;
+import io.seata.spring.annotation.GlobalTransactional;
+import org.apache.http.HttpResponse;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+
+@Service
+public class OrderServiceImpl implements OrderService {
+
+ private Logger logger = LoggerFactory.getLogger(getClass());
+
+ @Autowired
+ private OrderDao orderDao;
+
+ @Override
+ @GlobalTransactional
+ public Integer createOrder(Long userId, Long productId, Integer price) throws Exception {
+ Integer amount = 1; // 购买数量,暂时设置为 1。
+
+ logger.info("[createOrder] 当前 XID: {}", RootContext.getXID());
+
+ // 扣减库存
+ this.reduceStock(productId, amount);
+
+ // 扣减余额
+ this.reduceBalance(userId, price);
+
+ // 保存订单
+ OrderDO order = new OrderDO().setUserId(userId).setProductId(productId).setPayAmount(amount * price);
+ orderDao.saveOrder(order);
+ logger.info("[createOrder] 保存订单: {}", order.getId());
+
+ // 返回订单编号
+ return order.getId();
+ }
+
+ private void reduceStock(Long productId, Integer amount) throws IOException {
+ // 参数拼接
+ JSONObject params = new JSONObject().fluentPut("productId", String.valueOf(productId))
+ .fluentPut("amount", String.valueOf(amount));
+ // 执行调用
+ HttpResponse response = DefaultHttpExecutor.getInstance().executePost("http://127.0.0.1:8082", "/product/reduce-stock",
+ params, HttpResponse.class);
+ // 解析结果
+ Boolean success = Boolean.valueOf(EntityUtils.toString(response.getEntity()));
+ if (!success) {
+ throw new RuntimeException("扣除库存失败");
+ }
+ }
+
+ private void reduceBalance(Long userId, Integer price) throws IOException {
+ // 参数拼接
+ JSONObject params = new JSONObject().fluentPut("userId", String.valueOf(userId))
+ .fluentPut("price", String.valueOf(price));
+ // 执行调用
+ HttpResponse response = DefaultHttpExecutor.getInstance().executePost("http://127.0.0.1:8083", "/account/reduce-balance",
+ params, HttpResponse.class);
+ // 解析结果
+ Boolean success = Boolean.valueOf(EntityUtils.toString(response.getEntity()));
+ if (!success) {
+ throw new RuntimeException("扣除余额失败");
+ }
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/resources/application.yaml b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/resources/application.yaml
new file mode 100644
index 00000000..204bf93c
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-order-service/src/main/resources/application.yaml
@@ -0,0 +1,25 @@
+server:
+ port: 8081 # 端口
+
+spring:
+ application:
+ name: order-service
+
+ datasource:
+ url: jdbc:mysql://127.0.0.1:3306/seata_order?useSSL=false&useUnicode=true&characterEncoding=UTF-8
+ driver-class-name: com.mysql.jdbc.Driver
+ username: root
+ password:
+
+# Seata 配置项,对应 SeataProperties 类
+seata:
+ application-id: ${spring.application.name} # Seata 应用编号,默认为 ${spring.application.name}
+ tx-service-group: ${spring.application.name}-group # Seata 事务组编号,用于 TC 集群名
+ # 服务配置项,对应 ServiceProperties 类
+ service:
+ # 虚拟组和分组的映射
+ vgroup-mapping:
+ order-service-group: default
+ # 分组和 Seata 服务的映射
+ grouplist:
+ default: 127.0.0.1:8091
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/pom.xml b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/pom.xml
new file mode 100644
index 00000000..e9260443
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.2.RELEASE
+
+
+ 4.0.0
+
+ lab-52-seata-at-httpclient-demo-product-service
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ mysql
+ mysql-connector-java
+ 5.1.48
+
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 2.1.2
+
+
+
+
+ io.seata
+ seata-spring-boot-starter
+ 1.1.0
+
+
+
+ io.seata
+ seata-http
+ 1.1.0
+
+
+
+
+
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/ProductServiceApplication.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/ProductServiceApplication.java
new file mode 100644
index 00000000..4666d4bf
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/ProductServiceApplication.java
@@ -0,0 +1,13 @@
+package cn.iocoder.springboot.lab52.productservice;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ProductServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ProductServiceApplication.class, args);
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/controller/ProductController.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/controller/ProductController.java
new file mode 100644
index 00000000..9935006a
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/controller/ProductController.java
@@ -0,0 +1,33 @@
+package cn.iocoder.springboot.lab52.productservice.controller;
+
+import cn.iocoder.springboot.lab52.productservice.dto.ProductReduceStockDTO;
+import cn.iocoder.springboot.lab52.productservice.service.ProductService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/product")
+public class ProductController {
+
+ private Logger logger = LoggerFactory.getLogger(ProductController.class);
+
+ @Autowired
+ private ProductService productService;
+
+ @PostMapping("/reduce-stock")
+ public Boolean reduceStock(@RequestBody ProductReduceStockDTO productReduceStockDTO) {
+ logger.info("[reduceStock] 收到减少库存请求, 商品:{}, 价格:{}", productReduceStockDTO.getProductId(),
+ productReduceStockDTO.getAmount());
+ try {
+ productService.reduceStock(productReduceStockDTO.getProductId(), productReduceStockDTO.getAmount());
+ // 正常扣除库存,返回 true
+ return true;
+ } catch (Exception e) {
+ // 失败扣除库存,返回 false
+ return false;
+ }
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/dao/ProductDao.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/dao/ProductDao.java
new file mode 100644
index 00000000..2b8ad712
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/dao/ProductDao.java
@@ -0,0 +1,33 @@
+package cn.iocoder.springboot.lab52.productservice.dao;
+
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface ProductDao {
+
+ /**
+ * 获取库存
+ *
+ * @param productId 商品编号
+ * @return 库存
+ */
+ @Select("SELECT stock FROM product WHERE id = #{productId}")
+ Integer getStock(@Param("productId") Long productId);
+
+ /**
+ * 扣减库存
+ *
+ * @param productId 商品编号
+ * @param amount 扣减数量
+ * @return 影响记录行数
+ */
+ @Update("UPDATE product SET stock = stock - #{amount} WHERE id = #{productId} AND stock >= #{amount}")
+ int reduceStock(@Param("productId") Long productId, @Param("amount") Integer amount);
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/dto/ProductReduceStockDTO.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/dto/ProductReduceStockDTO.java
new file mode 100644
index 00000000..4d195de1
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/dto/ProductReduceStockDTO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.springboot.lab52.productservice.dto;
+
+/**
+ * 商品减少库存 DTO
+ */
+public class ProductReduceStockDTO {
+
+ /**
+ * 商品编号
+ */
+ private Long productId;
+ /**
+ * 数量
+ */
+ private Integer amount;
+
+ public Long getProductId() {
+ return productId;
+ }
+
+ public ProductReduceStockDTO setProductId(Long productId) {
+ this.productId = productId;
+ return this;
+ }
+
+ public Integer getAmount() {
+ return amount;
+ }
+
+ public ProductReduceStockDTO setAmount(Integer amount) {
+ this.amount = amount;
+ return this;
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/service/ProductService.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/service/ProductService.java
new file mode 100644
index 00000000..388d408a
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/service/ProductService.java
@@ -0,0 +1,17 @@
+package cn.iocoder.springboot.lab52.productservice.service;
+
+/**
+ * 商品 Service
+ */
+public interface ProductService {
+
+ /**
+ * 扣减库存
+ *
+ * @param productId 商品 ID
+ * @param amount 扣减数量
+ * @throws Exception 扣减失败时抛出异常
+ */
+ void reduceStock(Long productId, Integer amount) throws Exception;
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/service/ProductServiceImpl.java b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/service/ProductServiceImpl.java
new file mode 100644
index 00000000..16899ed1
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/java/cn/iocoder/springboot/lab52/productservice/service/ProductServiceImpl.java
@@ -0,0 +1,48 @@
+package cn.iocoder.springboot.lab52.productservice.service;
+
+import cn.iocoder.springboot.lab52.productservice.dao.ProductDao;
+import io.seata.core.context.RootContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class ProductServiceImpl implements ProductService {
+
+ private Logger logger = LoggerFactory.getLogger(getClass());
+
+ @Autowired
+ private ProductDao productDao;
+
+ @Override
+ @Transactional // 开启事物
+ public void reduceStock(Long productId, Integer amount) throws Exception {
+ logger.info("[reduceStock] 当前 XID: {}", RootContext.getXID());
+
+ // 检查库存
+ checkStock(productId, amount);
+
+ logger.info("[reduceStock] 开始扣减 {} 库存", productId);
+ // 扣减库存
+ int updateCount = productDao.reduceStock(productId, amount);
+ // 扣除成功
+ if (updateCount == 0) {
+ logger.warn("[reduceStock] 扣除 {} 库存失败", productId);
+ throw new Exception("库存不足");
+ }
+ // 扣除失败
+ logger.info("[reduceStock] 扣除 {} 库存成功", productId);
+ }
+
+ private void checkStock(Long productId, Integer requiredAmount) throws Exception {
+ logger.info("[checkStock] 检查 {} 库存", productId);
+ Integer stock = productDao.getStock(productId);
+ if (stock < requiredAmount) {
+ logger.warn("[checkStock] {} 库存不足,当前库存: {}", productId, stock);
+ throw new Exception("库存不足");
+ }
+ }
+
+}
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/resources/application.yaml b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/resources/application.yaml
new file mode 100644
index 00000000..ac63d302
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/lab-52-seata-at-httpclient-demo-product-service/src/main/resources/application.yaml
@@ -0,0 +1,25 @@
+server:
+ port: 8082 # 端口
+
+spring:
+ application:
+ name: product-service
+
+ datasource:
+ url: jdbc:mysql://127.0.0.1:3306/seata_product?useSSL=false&useUnicode=true&characterEncoding=UTF-8
+ driver-class-name: com.mysql.jdbc.Driver
+ username: root
+ password:
+
+# Seata 配置项,对应 SeataProperties 类
+seata:
+ application-id: ${spring.application.name} # Seata 应用编号,默认为 ${spring.application.name}
+ tx-service-group: ${spring.application.name}-group # Seata 事务组编号,用于 TC 集群名
+ # 服务配置项,对应 ServiceProperties 类
+ service:
+ # 虚拟组和分组的映射
+ vgroup-mapping:
+ product-service-group: default
+ # 分组和 Seata 服务的映射
+ grouplist:
+ default: 127.0.0.1:8091
diff --git a/lab-52/lab-52-seata-at-httpclient-demo/pom.xml b/lab-52/lab-52-seata-at-httpclient-demo/pom.xml
new file mode 100644
index 00000000..01b2c5d8
--- /dev/null
+++ b/lab-52/lab-52-seata-at-httpclient-demo/pom.xml
@@ -0,0 +1,20 @@
+
+
+
+ lab-52
+ cn.iocoder.springboot.labs
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ lab-52-seata-at-httpclient-demo
+
+
+ lab-52-seata-at-httpclient-demo-order-service
+ lab-52-seata-at-httpclient-demo-product-service
+ lab-52-seata-at-httpclient-demo-account-service
+
+
+
diff --git a/lab-52/pom.xml b/lab-52/pom.xml
index 65e4992d..e3525458 100644
--- a/lab-52/pom.xml
+++ b/lab-52/pom.xml
@@ -11,9 +11,10 @@
lab-52
pom
+
lab-52-multiple-datasource
+ lab-52-seata-at-httpclient-demo
-
diff --git a/lab-53/lab-53-seata-at-dubbo-demo/lab-53-seata-at-dubbo-demo-order-service/src/main/resources/application-file.yaml b/lab-53/lab-53-seata-at-dubbo-demo/lab-53-seata-at-dubbo-demo-order-service/src/main/resources/application-file.yaml
index 81773e6c..b8616381 100644
--- a/lab-53/lab-53-seata-at-dubbo-demo/lab-53-seata-at-dubbo-demo-order-service/src/main/resources/application-file.yaml
+++ b/lab-53/lab-53-seata-at-dubbo-demo/lab-53-seata-at-dubbo-demo-order-service/src/main/resources/application-file.yaml
@@ -3,7 +3,7 @@ server:
spring:
application:
- name: user-service
+ name: order-service
datasource:
url: jdbc:mysql://127.0.0.1:3306/seata_order?useSSL=false&useUnicode=true&characterEncoding=UTF-8
@@ -35,7 +35,7 @@ seata:
service:
# 虚拟组和分组的映射
vgroup-mapping:
- user-service-group: default
+ order-service-group: default
# 分组和 Seata 服务的映射
grouplist:
default: 127.0.0.1:8091