微服务拆分

[复制链接]
发表于 2025-11-4 14:12:49 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
用户服务

创建项目

在hmall下新建一个module,定名为user-service:

依靠

user-service的pom.xml文件内容如下:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <parent>
  6.         <artifactId>hmall</artifactId>
  7.         <groupId>com.heima</groupId>
  8.         <version>1.0.0</version>
  9.     </parent>
  10.     <modelVersion>4.0.0</modelVersion>
  11.     <artifactId>user-service</artifactId>
  12.     <properties>
  13.         <maven.compiler.source>11</maven.compiler.source>
  14.         <maven.compiler.target>11</maven.compiler.target>
  15.     </properties>
  16.     <dependencies>
  17.         <!--common-->
  18.         <dependency>
  19.             <groupId>com.heima</groupId>
  20.             <artifactId>hm-common</artifactId>
  21.             <version>1.0.0</version>
  22.         </dependency>
  23.         <!--api-->
  24.         <dependency>
  25.             <groupId>com.heima</groupId>
  26.             <artifactId>hm-api</artifactId>
  27.             <version>1.0.0</version>
  28.         </dependency>
  29.         <!--web-->
  30.         <dependency>
  31.             <groupId>org.springframework.boot</groupId>
  32.             <artifactId>spring-boot-starter-web</artifactId>
  33.         </dependency>
  34.         <!--数据库-->
  35.         <dependency>
  36.             <groupId>mysql</groupId>
  37.             <artifactId>mysql-connector-java</artifactId>
  38.         </dependency>
  39.         <!--mybatis-->
  40.         <dependency>
  41.             <groupId>com.baomidou</groupId>
  42.             <artifactId>mybatis-plus-boot-starter</artifactId>
  43.         </dependency>
  44.         <!--nacos 服务注册发现-->
  45.         <dependency>
  46.             <groupId>com.alibaba.cloud</groupId>
  47.             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  48.         </dependency>
  49.     </dependencies>
  50.     <build>
  51.         <finalName>${project.artifactId}</finalName>
  52.         <plugins>
  53.             <plugin>
  54.                 <groupId>org.springframework.boot</groupId>
  55.                 <artifactId>spring-boot-maven-plugin</artifactId>
  56.             </plugin>
  57.         </plugins>
  58.     </build>
  59. </project>
复制代码
启动类

在user-service中的com.hmall.user包下创建启动类:
  1. package com.hmall.user;
  2. import org.mybatis.spring.annotation.MapperScan;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. @MapperScan("com.hmall.user.mapper")
  6. @SpringBootApplication
  7. public class UserApplication {
  8.     public static void main(String[] args) {
  9.         SpringApplication.run(UserApplication.class, args);
  10.     }
  11. }
复制代码
设置文件

从hm-service项目中复制3个yaml设置文件到user-service的resource目次。
此中application-dev.yaml和application-local.yaml保持稳固。application.yaml如下:
  1. server:
  2.   port: 8084
  3. spring:
  4.   application:
  5.     name: user-service # 服务名称
  6.   profiles:
  7.     active: dev
  8.   datasource:
  9.     url: jdbc:mysql://${hm.db.host}:3306/hm-user?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
  10.     driver-class-name: com.mysql.cj.jdbc.Driver
  11.     username: root
  12.     password: ${hm.db.pw}
  13.   cloud:
  14.     nacos:
  15.       server-addr: 192.168.150.101 # nacos地址
  16. mybatis-plus:
  17.   configuration:
  18.     default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
  19.   global-config:
  20.     db-config:
  21.       update-strategy: not_null
  22.       id-type: auto
  23. logging:
  24.   level:
  25.     com.hmall: debug
  26.   pattern:
  27.     dateformat: HH:mm:ss:SSS
  28.   file:
  29.     path: "logs/${spring.application.name}"
  30. knife4j:
  31.   enable: true
  32.   openapi:
  33.     title: 用户服务接口文档
  34.     description: "信息"
  35.     email: [email protected]
  36.     concat: Charlie
  37.     url: https://www.itcast.cn
  38.     version: v1.0.0
  39.     group:
  40.       default:
  41.         group-name: default
  42.         api-rule: package
  43.         api-rule-resources:
  44.           - com.hmall.user.controller
  45. hm:
  46.   jwt:
  47.     location: classpath:hmall.jks
  48.     alias: hmall
  49.     password: hmall123
  50.     tokenTTL: 30m
复制代码
将hm-service下的hmall.jks文件拷贝到user-service下的resources目次,这是JWT加密的秘钥文件:

代码

复制hm-service中全部与user、address、jwt有关的代码,终极项目结构如下:

数据库

user-service也须要本身的独立的database,向MySQL中导入课前资料提供的SQL:
通过网盘分享的文件:hm-user.sql
链接:  提取码: ii6b
–来自百度网盘超等会员v5的分享

导入结果如下:

设置启动项

给user-service设置启动项,设置profile为local:

测试

启动UserApplication,访问http://localhost:8084/doc.html#/default/用户相干接口/loginUsingPOST,测试登录接口:

用户服务测试通过。
交易业务服务

创建项目

在hmall下新建一个module,定名为trade-service:

依靠

trade-service的pom.xml文件内容如下:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <parent>
  6.         <artifactId>hmall</artifactId>
  7.         <groupId>com.heima</groupId>
  8.         <version>1.0.0</version>
  9.     </parent>
  10.     <modelVersion>4.0.0</modelVersion>
  11.     <artifactId>trade-service</artifactId>
  12.     <properties>
  13.         <maven.compiler.source>11</maven.compiler.source>
  14.         <maven.compiler.target>11</maven.compiler.target>
  15.     </properties>
  16.     <dependencies>
  17.         <!--common-->
  18.         <dependency>
  19.             <groupId>com.heima</groupId>
  20.             <artifactId>hm-common</artifactId>
  21.             <version>1.0.0</version>
  22.         </dependency>
  23.         <!--api-->
  24.         <dependency>
  25.             <groupId>com.heima</groupId>
  26.             <artifactId>hm-api</artifactId>
  27.             <version>1.0.0</version>
  28.         </dependency>
  29.         <!--web-->
  30.         <dependency>
  31.             <groupId>org.springframework.boot</groupId>
  32.             <artifactId>spring-boot-starter-web</artifactId>
  33.         </dependency>
  34.         <!--数据库-->
  35.         <dependency>
  36.             <groupId>mysql</groupId>
  37.             <artifactId>mysql-connector-java</artifactId>
  38.         </dependency>
  39.         <!--mybatis-->
  40.         <dependency>
  41.             <groupId>com.baomidou</groupId>
  42.             <artifactId>mybatis-plus-boot-starter</artifactId>
  43.         </dependency>
  44.         <!--nacos 服务注册发现-->
  45.         <dependency>
  46.             <groupId>com.alibaba.cloud</groupId>
  47.             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  48.         </dependency>
  49.     </dependencies>
  50.     <build>
  51.         <finalName>${project.artifactId}</finalName>
  52.         <plugins>
  53.             <plugin>
  54.                 <groupId>org.springframework.boot</groupId>
  55.                 <artifactId>spring-boot-maven-plugin</artifactId>
  56.             </plugin>
  57.         </plugins>
  58.     </build>
  59. </project>
复制代码
启动类

在trade-service中的com.hmall.trade包下创建启动类:
  1. package com.hmall.trade;
  2. import org.mybatis.spring.annotation.MapperScan;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.cloud.openfeign.EnableFeignClients;
  6. @EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
  7. @MapperScan("com.hmall.trade.mapper")
  8. @SpringBootApplication
  9. public class TradeApplication {
  10.     public static void main(String[] args) {
  11.         SpringApplication.run(TradeApplication.class, args);
  12.     }
  13. }
复制代码
设置文件

从hm-service项目中复制3个yaml设置文件到trade-service的resource目次。
此中application-dev.yaml和application-local.yaml保持稳固。application.yaml如下:
  1. server:
  2.   port: 8085
  3. spring:
  4.   application:
  5.     name: trade-service # 服务名称
  6.   profiles:
  7.     active: dev
  8.   datasource:
  9.     url: jdbc:mysql://${hm.db.host}:3306/hm-trade?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
  10.     driver-class-name: com.mysql.cj.jdbc.Driver
  11.     username: root
  12.     password: ${hm.db.pw}
  13.   cloud:
  14.     nacos:
  15.       server-addr: 192.168.150.101 # nacos地址
  16. mybatis-plus:
  17.   configuration:
  18.     default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
  19.   global-config:
  20.     db-config:
  21.       update-strategy: not_null
  22.       id-type: auto
  23. logging:
  24.   level:
  25.     com.hmall: debug
  26.   pattern:
  27.     dateformat: HH:mm:ss:SSS
  28.   file:
  29.     path: "logs/${spring.application.name}"
  30. knife4j:
  31.   enable: true
  32.   openapi:
  33.     title: 交易服务接口文档
  34.     description: "信息"
  35.     email: [email protected]
  36.     concat: 虎哥
  37.     url: https://www.itcast.cn
  38.     version: v1.0.0
  39.     group:
  40.       default:
  41.         group-name: default
  42.         api-rule: package
  43.         api-rule-resources:
  44.           - com.hmall.trade.controller
复制代码
代码

根本代码

复制hm-service中全部与trade有关的代码,终极项目结构如下:

在交易业务服务中,用户下单时须要做下列变乱:

  • 根据id查询商品列表
  • 盘算商品总价
  • 生存订单
  • 扣减库存
  • 清算购物车商品
此中,查询商品、扣减库存都是与商品有关的业务,在item-service中有相干功能;清算购物车商品是购物车业务,在cart-service中有相干功能。
因此交易业务服务要调用他们,必须通过OpenFeign长途调用。我们须要将上述功能抽取为FeignClient.
抽取ItemClient接口

起首是扣减库存,在item-service中的对应业务接口如下:

我们将这个接口抽取到hm-api模块的com.hmall.api.client.ItemClient中:

将接口参数的OrderDetailDTO抽取到hm-api模块的com.hmall.api.dto包下:

抽取CartClient接口

接下来是清算购物车商品,在cart-service中的对应业务接口如下:

我们在hm-api模块的com.hmall.api.client包下界说一个CartClient接口:

代码如下:
  1. package com.hmall.api.client;
  2. import org.springframework.cloud.openfeign.FeignClient;
  3. import org.springframework.web.bind.annotation.DeleteMapping;
  4. import org.springframework.web.bind.annotation.RequestParam;
  5. import java.util.Collection;
  6. @FeignClient("cart-service")
  7. public interface CartClient {
  8.     @DeleteMapping("/carts")
  9.     void deleteCartItemByIds(@RequestParam("ids") Collection<Long> ids);
  10. }
复制代码
改造OrderServiceImpl

接下来,就可以改造OrderServiceImpl中的逻辑,将本地方法调用改造为基于FeignClient的调用,完备代码如下:
  1. package com.hmall.trade.service.impl;
  2. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  3. import com.hmall.api.client.CartClient;
  4. import com.hmall.api.client.ItemClient;
  5. import com.hmall.api.dto.ItemDTO;
  6. import com.hmall.api.dto.OrderDetailDTO;
  7. import com.hmall.common.exception.BadRequestException;
  8. import com.hmall.common.utils.UserContext;
  9. import com.hmall.trade.domain.dto.OrderFormDTO;
  10. import com.hmall.trade.domain.po.Order;
  11. import com.hmall.trade.domain.po.OrderDetail;
  12. import com.hmall.trade.mapper.OrderMapper;
  13. import com.hmall.trade.service.IOrderDetailService;
  14. import com.hmall.trade.service.IOrderService;
  15. import lombok.RequiredArgsConstructor;
  16. import org.springframework.stereotype.Service;
  17. import org.springframework.transaction.annotation.Transactional;
  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Set;
  22. import java.util.stream.Collectors;
  23. /**
  24. * <p>
  25. * 服务实现类
  26. * </p>
  27. */
  28. @Service
  29. @RequiredArgsConstructor
  30. public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
  31.     private final ItemClient itemClient;
  32.     private final IOrderDetailService detailService;
  33.     private final CartClient cartClient;
  34.     @Override
  35.     @Transactional
  36.     public Long createOrder(OrderFormDTO orderFormDTO) {
  37.         // 1.订单数据
  38.         Order order = new Order();
  39.         // 1.1.查询商品
  40.         List<OrderDetailDTO> detailDTOS = orderFormDTO.getDetails();
  41.         // 1.2.获取商品id和数量的Map
  42.         Map<Long, Integer> itemNumMap = detailDTOS.stream()
  43.                 .collect(Collectors.toMap(OrderDetailDTO::getItemId, OrderDetailDTO::getNum));
  44.         Set<Long> itemIds = itemNumMap.keySet();
  45.         // 1.3.查询商品
  46.         List<ItemDTO> items = itemClient.queryItemByIds(itemIds);
  47.         if (items == null || items.size() < itemIds.size()) {
  48.             throw new BadRequestException("商品不存在");
  49.         }
  50.         // 1.4.基于商品价格、购买数量计算商品总价:totalFee
  51.         int total = 0;
  52.         for (ItemDTO item : items) {
  53.             total += item.getPrice()  itemNumMap.get(item.getId());
  54.         }
  55.         order.setTotalFee(total);
  56.         // 1.5.其它属性
  57.         order.setPaymentType(orderFormDTO.getPaymentType());
  58.         order.setUserId(UserContext.getUser());
  59.         order.setStatus(1);
  60.         // 1.6.将Order写入数据库order表中
  61.         save(order);
  62.         // 2.保存订单详情
  63.         List<OrderDetail> details = buildDetails(order.getId(), items, itemNumMap);
  64.         detailService.saveBatch(details);
  65.         // 3.扣减库存
  66.         try {
  67.             itemClient.deductStock(detailDTOS);
  68.         } catch (Exception e) {
  69.             throw new RuntimeException("库存不足!");
  70.         }
  71.         // 4.清理购物车商品
  72.         cartClient.deleteCartItemByIds(itemIds);
  73.         return order.getId();
  74.     }
  75.     private List<OrderDetail> buildDetails(Long orderId, List<ItemDTO> items, Map<Long, Integer> numMap) {
  76.         List<OrderDetail> details = new ArrayList<>(items.size());
  77.         for (ItemDTO item : items) {
  78.             OrderDetail detail = new OrderDetail();
  79.             detail.setName(item.getName());
  80.             detail.setSpec(item.getSpec());
  81.             detail.setPrice(item.getPrice());
  82.             detail.setNum(numMap.get(item.getId()));
  83.             detail.setItemId(item.getId());
  84.             detail.setImage(item.getImage());
  85.             detail.setOrderId(orderId);
  86.             details.add(detail);
  87.         }
  88.         return details;
  89.     }
  90. }
复制代码
数据库

trade-service也须要本身的独立的database,向MySQL中导入课前资料提供的SQL:

导入结果如下:

设置启动项

给trade-service设置启动项,设置profile为local:

测试

启动TradeApplication,访问http://localhost:8085/doc.html,测试查询订单接口:

哀求参数:1654779387523936258,交易业务服务测试通过。注意,创建订单接口无法测试,由于无法获取登任命户信息。
付出服务

创建项目

在hmall下新建一个module,定名为pay-service:

依靠

pay-service的pom.xml文件内容如下:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5.     <parent>
  6.         <artifactId>hmall</artifactId>
  7.         <groupId>com.heima</groupId>
  8.         <version>1.0.0</version>
  9.     </parent>
  10.     <modelVersion>4.0.0</modelVersion>
  11.     <artifactId>pay-service</artifactId>
  12.     <properties>
  13.         <maven.compiler.source>11</maven.compiler.source>
  14.         <maven.compiler.target>11</maven.compiler.target>
  15.     </properties>
  16.     <dependencies>
  17.         <!--common-->
  18.         <dependency>
  19.             <groupId>com.heima</groupId>
  20.             <artifactId>hm-common</artifactId>
  21.             <version>1.0.0</version>
  22.         </dependency>
  23.         <!--api-->
  24.         <dependency>
  25.             <groupId>com.heima</groupId>
  26.             <artifactId>hm-api</artifactId>
  27.             <version>1.0.0</version>
  28.         </dependency>
  29.         <!--web-->
  30.         <dependency>
  31.             <groupId>org.springframework.boot</groupId>
  32.             <artifactId>spring-boot-starter-web</artifactId>
  33.         </dependency>
  34.         <!--数据库-->
  35.         <dependency>
  36.             <groupId>mysql</groupId>
  37.             <artifactId>mysql-connector-java</artifactId>
  38.         </dependency>
  39.         <!--mybatis-->
  40.         <dependency>
  41.             <groupId>com.baomidou</groupId>
  42.             <artifactId>mybatis-plus-boot-starter</artifactId>
  43.         </dependency>
  44.         <!--nacos 服务注册发现-->
  45.         <dependency>
  46.             <groupId>com.alibaba.cloud</groupId>
  47.             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  48.         </dependency>
  49.     </dependencies>
  50.     <build>
  51.         <finalName>${project.artifactId}</finalName>
  52.         <plugins>
  53.             <plugin>
  54.                 <groupId>org.springframework.boot</groupId>
  55.                 <artifactId>spring-boot-maven-plugin</artifactId>
  56.             </plugin>
  57.         </plugins>
  58.     </build>
  59. </project>
复制代码
启动类

在pay-service中的com.hmall.pay包下创建启动类:
  1. package com.hmall.pay;
  2. import org.mybatis.spring.annotation.MapperScan;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.cloud.openfeign.EnableFeignClients;
  6. @EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
  7. @MapperScan("com.hmall.pay.mapper")
  8. @SpringBootApplication
  9. public class PayApplication {
  10.     public static void main(String[] args) {
  11.         SpringApplication.run(PayApplication.class, args);
  12.     }
  13. }
复制代码
设置文件

从hm-service项目中复制3个yaml设置文件到trade-service的resource目次。
此中application-dev.yaml和application-local.yaml保持稳固。application.yaml如下:
  1. server:
  2.   port: 8086
  3. spring:
  4.   application:
  5.     name: pay-service
  6.   profiles:
  7.     active: dev
  8.   datasource:
  9.     url: jdbc:mysql://${hm.db.host}:3306/hm-pay?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
  10.     driver-class-name: com.mysql.cj.jdbc.Driver
  11.     username: root
  12.     password: ${hm.db.pw}
  13.   cloud:
  14.     nacos:
  15.       server-addr: 192.168.150.101
  16. mybatis-plus:
  17.   configuration:
  18.     default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
  19.   global-config:
  20.     db-config:
  21.       update-strategy: not_null
  22.       id-type: auto
  23. logging:
  24.   level:
  25.     com.hmall: debug
  26.   pattern:
  27.     dateformat: HH:mm:ss:SSS
  28.   file:
  29.     path: "logs/${spring.application.name}"
  30. knife4j:
  31.   enable: true
  32.   openapi:
  33.     title: 支付服务接口文档
  34.     description: "支付服务接口文档"
  35.     email: [email protected]
  36.     concat: 虎哥
  37.     url: https://www.itcast.cn
  38.     version: v1.0.0
  39.     group:
  40.       default:
  41.         group-name: default
  42.         api-rule: package
  43.         api-rule-resources:
  44.           - com.hmall.pay.controller
复制代码
代码

根本代码

复制hm-service中全部与付出有关的代码,终极项目结构如下:

在付出服务中,基于用户余额付出时须要做下列变乱:

  • 扣减用户余额
  • 标志付出单状态为已付出
  • 标志订单状态为已付出
此中,扣减用户余额是在user-service中有相干功能;标志订单状态则是在trade-service中有相干功能。因此交易业务服务要调用他们,必须通过OpenFeign长途调用。我们须要将上述功能抽取为FeignClient.
抽取UserClient接口

起首是扣减用户余额,在user-service中的对应业务接口如下:

我们将这个接口抽取到hm-api模块的com.hmall.api.client.UserClient中:

详细代码如下:
  1. package com.hmall.api.client;
  2. import org.springframework.cloud.openfeign.FeignClient;
  3. import org.springframework.web.bind.annotation.PutMapping;
  4. import org.springframework.web.bind.annotation.RequestParam;
  5. @FeignClient("user-service")
  6. public interface UserClient {
  7.     @PutMapping("/users/money/deduct")
  8.     void deductMoney(@RequestParam("pw") String pw,@RequestParam("amount") Integer amount);
  9. }
复制代码
抽取TradeClient接口

接下来是标志订单状态,在trade-service中的对应业务接口如下:

我们将这个接口抽取到hm-api模块的com.hmall.api.client.TradeClient中:

代码如下:
  1. package com.hmall.api.client;
  2. import org.springframework.cloud.openfeign.FeignClient;
  3. import org.springframework.web.bind.annotation.PathVariable;
  4. import org.springframework.web.bind.annotation.PutMapping;
  5. @FeignClient("trade-service")
  6. public interface TradeClient {
  7.     @PutMapping("/orders/{orderId}")
  8.     void markOrderPaySuccess(@PathVariable("orderId") Long orderId);
  9. }
复制代码
改造PayOrderServiceImpl

接下来,就可以改造PayOrderServiceImpl中的逻辑,将本地方法调用改造为基于FeignClient的调用,完备代码如下:
  1. package com.hmall.pay.service.impl;
  2. import com.baomidou.mybatisplus.core.toolkit.IdWorker;
  3. import com.baomidou.mybatisplus.core.toolkit.StringUtils;
  4. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  5. import com.hmall.api.client.TradeClient;
  6. import com.hmall.api.client.UserClient;
  7. import com.hmall.common.exception.BizIllegalException;
  8. import com.hmall.common.utils.BeanUtils;
  9. import com.hmall.common.utils.UserContext;
  10. import com.hmall.pay.domain.dto.PayApplyDTO;
  11. import com.hmall.pay.domain.dto.PayOrderFormDTO;
  12. import com.hmall.pay.domain.po.PayOrder;
  13. import com.hmall.pay.enums.PayStatus;
  14. import com.hmall.pay.mapper.PayOrderMapper;
  15. import com.hmall.pay.service.IPayOrderService;
  16. import lombok.RequiredArgsConstructor;
  17. import org.springframework.stereotype.Service;
  18. import org.springframework.transaction.annotation.Transactional;
  19. import java.time.LocalDateTime;
  20. /**
  21. * <p>
  22. * 支付订单 服务实现类
  23. * </p>
  24. *
  25. */
  26. @Service
  27. @RequiredArgsConstructor
  28. public class PayOrderServiceImpl extends ServiceImpl<PayOrderMapper, PayOrder> implements IPayOrderService {
  29.     private final UserClient userClient;
  30.     private final TradeClient tradeClient;
  31.     @Override
  32.     public String applyPayOrder(PayApplyDTO applyDTO) {
  33.         // 1.幂等性校验
  34.         PayOrder payOrder = checkIdempotent(applyDTO);
  35.         // 2.返回结果
  36.         return payOrder.getId().toString();
  37.     }
  38.     @Override
  39.     @Transactional
  40.     public void tryPayOrderByBalance(PayOrderFormDTO payOrderDTO) {
  41.         // 1.查询支付单
  42.         PayOrder po = getById(payOrderDTO.getId());
  43.         // 2.判断状态
  44.         if(!PayStatus.WAIT_BUYER_PAY.equalsValue(po.getStatus())){
  45.             // 订单不是未支付,状态异常
  46.             throw new BizIllegalException("交易已支付或关闭!");
  47.         }
  48.         // 3.尝试扣减余额
  49.         userClient.deductMoney(payOrderDTO.getPw(), po.getAmount());
  50.         // 4.修改支付单状态
  51.         boolean success = markPayOrderSuccess(payOrderDTO.getId(), LocalDateTime.now());
  52.         if (!success) {
  53.             throw new BizIllegalException("交易已支付或关闭!");
  54.         }
  55.         // 5.修改订单状态
  56.         tradeClient.markOrderPaySuccess(po.getBizOrderNo());
  57.     }
  58.     public boolean markPayOrderSuccess(Long id, LocalDateTime successTime) {
  59.         return lambdaUpdate()
  60.                 .set(PayOrder::getStatus, PayStatus.TRADE_SUCCESS.getValue())
  61.                 .set(PayOrder::getPaySuccessTime, successTime)
  62.                 .eq(PayOrder::getId, id)
  63.                 // 支付状态的乐观锁判断
  64.                 .in(PayOrder::getStatus, PayStatus.NOT_COMMIT.getValue(), PayStatus.WAIT_BUYER_PAY.getValue())
  65.                 .update();
  66.     }
  67.     private PayOrder checkIdempotent(PayApplyDTO applyDTO) {
  68.         // 1.首先查询支付单
  69.         PayOrder oldOrder = queryByBizOrderNo(applyDTO.getBizOrderNo());
  70.         // 2.判断是否存在
  71.         if (oldOrder == null) {
  72.             // 不存在支付单,说明是第一次,写入新的支付单并返回
  73.             PayOrder payOrder = buildPayOrder(applyDTO);
  74.             payOrder.setPayOrderNo(IdWorker.getId());
  75.             save(payOrder);
  76.             return payOrder;
  77.         }
  78.         // 3.旧单已经存在,判断是否支付成功
  79.         if (PayStatus.TRADE_SUCCESS.equalsValue(oldOrder.getStatus())) {
  80.             // 已经支付成功,抛出异常
  81.             throw new BizIllegalException("订单已经支付!");
  82.         }
  83.         // 4.旧单已经存在,判断是否已经关闭
  84.         if (PayStatus.TRADE_CLOSED.equalsValue(oldOrder.getStatus())) {
  85.             // 已经关闭,抛出异常
  86.             throw new BizIllegalException("订单已关闭");
  87.         }
  88.         // 5.旧单已经存在,判断支付渠道是否一致
  89.         if (!StringUtils.equals(oldOrder.getPayChannelCode(), applyDTO.getPayChannelCode())) {
  90.             // 支付渠道不一致,需要重置数据,然后重新申请支付单
  91.             PayOrder payOrder = buildPayOrder(applyDTO);
  92.             payOrder.setId(oldOrder.getId());
  93.             payOrder.setQrCodeUrl("");
  94.             updateById(payOrder);
  95.             payOrder.setPayOrderNo(oldOrder.getPayOrderNo());
  96.             return payOrder;
  97.         }
  98.         // 6.旧单已经存在,且可能是未支付或未提交,且支付渠道一致,直接返回旧数据
  99.         return oldOrder;
  100.     }
  101.     private PayOrder buildPayOrder(PayApplyDTO payApplyDTO) {
  102.         // 1.数据转换
  103.         PayOrder payOrder = BeanUtils.toBean(payApplyDTO, PayOrder.class);
  104.         // 2.初始化数据
  105.         payOrder.setPayOverTime(LocalDateTime.now().plusMinutes(120L));
  106.         payOrder.setStatus(PayStatus.WAIT_BUYER_PAY.getValue());
  107.         payOrder.setBizUserId(UserContext.getUser());
  108.         return payOrder;
  109.     }
  110.     public PayOrder queryByBizOrderNo(Long bizOrderNo) {
  111.         return lambdaQuery()
  112.                 .eq(PayOrder::getBizOrderNo, bizOrderNo)
  113.                 .one();
  114.     }
  115. }
复制代码
数据库

pay-service也须要本身的独立的database,向MySQL中导入课前资料提供的SQL:

导入结果如下:

设置启动项

启动类开启FeignClient:
  1. package com.hamll.pay;
  2. import com.hmall.api.client.TradeClient;
  3. import com.hmall.api.client.UserClient;
  4. import com.hmall.api.config.DefaultFeignConfig;
  5. import org.mybatis.spring.annotation.MapperScan;
  6. import org.springframework.boot.autoconfigure.SpringBootApplication;
  7. import org.springframework.cloud.openfeign.EnableFeignClients;
  8. @SpringBootApplication
  9. @EnableFeignClients(clients = {UserClient.class, TradeClient.class},defaultConfiguration = DefaultFeignConfig.class)
  10. @MapperScan("com.hamll.pay.mapper")
  11. public class PayApplication {
  12.     public static void main(String[] args) {
  13.         org.springframework.boot.SpringApplication.run(PayApplication.class, args);
  14.     }
  15. }
复制代码
给pay-service设置启动项,设置profile为local:

测试

在付出服务的PayController中添加一个接口方便测试:
  1. @ApiOperation("查询支付单")
  2. @GetMapping
  3. public List<PayOrderVO> queryPayOrders(){
  4.     return BeanUtils.copyList(payOrderService.list(), PayOrderVO.class);
  5. }
复制代码
启动PayApplication,访问http://localhost:8086/doc.html,测试查询订单接口:

付出服务测试通过。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表