Rest通用Base工程构建

前言
  1. 创建服务提供者(支付服务 8001):作为微服务架构中的支付模块,对外提供支付相关的接口与业务逻辑。

  2. 创建服务调用者(订单服务 80):作为订单模块,通过 RestTemplate 调用支付服务,实现下单与支付流程的联动。

支付模块V1版本

创建支付模块

微服务提供者—支付模块:cloud-provider-payment8001

创建支付模块cloud-provider-payment8001
img
修改 pom.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
  <groupId>com.sun.cloud</groupId>
  <artifactId>cloud2025</artifactId>
  <version>1.0-SNAPSHOT</version>
</parent>
 
<artifactId>cloud-provider-payment8001</artifactId>
 
<properties>
  <maven.compiler.source>21</maven.compiler.source>
  <maven.compiler.target>21</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
 
<dependencies>
  <!--SpringBoot通用依赖模块-->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
  <!--SpringBoot集成druid连接池-->
  <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
  </dependency>
  <!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
  <dependency>
      <groupId>org.springdoc</groupId>
      <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
  </dependency>
  <!--mybatis和springboot整合-->
  <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
  </dependency>
  <!--Mysql数据库驱动8 -->
  <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
  </dependency>
  <!--persistence-->
  <dependency>
      <groupId>jakarta.persistence</groupId>
      <artifactId>jakarta.persistence-api</artifactId>
  </dependency>
  <!--通用Mapper4-->
  <dependency>
      <groupId>tk.mybatis</groupId>
      <artifactId>mapper</artifactId>
  </dependency>
  <!--hutool-->
  <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
  </dependency>
  <!-- fastjson2 -->
  <dependency>
      <groupId>com.alibaba.fastjson2</groupId>
      <artifactId>fastjson2</artifactId>
  </dependency>
  <!--lombok-->
  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <scope>provided</scope>
  </dependency>
  <!--test-->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
  </dependency>
</dependencies>
 
<build>
  <plugins>
      <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
  </plugins>
</build>
 
</project>
编写 application.yaml 文件
server:
  port: 8001
 
# ==========applicationName + druid-mysql8 driver===================
spring:
  application:
    name: cloud-payment-service
 
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db2025?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
    username: root
    password: 12345678
 
# ========================mybatis===================
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.sun.cloud.entities
  configuration:
    map-underscore-to-camel-case: true
创建启动类
package com.sun.cloud;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
 
 
@SpringBootApplication
@MapperScan("com.sun.cloud.mapper")
public class Main8001 {
    public static void main(String[] args) {
        SpringApplication.run(Main8001.class, args);
    }
}
注意

上面引入的是tk.mybatis.spring.annotation.MapperScan,而不是org.mybatis.spring.annotation.MapperScan。这样就不用在每个Mapper接口上添加@Mapper注解了。

业务类

实体类

之前使用自动生成,生成了Pay实体类,这个实体类包含了全部的字段。通常我们都会创建一个PayDTO作为数据传输对象,将一些不必要的字段进行隐藏。

  1. 将生成的实体类,复制到支付模块
  2. 根据业务需求,创建PayDTO
package com.sun.cloud.entities;
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 
import java.io.Serializable;
import java.math.BigDecimal;
 
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayDTO implements Serializable {
    // 主键ID
    private Integer id;
    // 支付流水号
    private String payNo;
    // 订单流水号
    private String orderNo;
    // 用户账号ID
    private Integer userId;
    // 交易金额
    private BigDecimal amount;
}
img
Mapper
  1. 将生成的Mapper接口xml文件,复制到支付模块
img
Service
  1. 创建Service接口
package com.sun.cloud.service;
 
import com.sun.cloud.entities.Pay;
 
import java.util.List;
 
public interface PayService {
    public int add(Pay pay);
 
    public int delete(Integer id);
 
    public int update(Pay pay);
 
    public Pay getById(Integer id);
 
    public List<Pay> getAll();
}
  1. 创建Service实现类
package com.sun.cloud.service.impl;
 
import com.sun.cloud.entities.Pay;
import com.sun.cloud.mapper.PayMapper;
import com.sun.cloud.service.PayService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
 
import java.util.List;
 
@Service
public class PayServiceImpl implements PayService{
    @Resource
    PayMapper payMapper;
    @Override
    public int add(Pay pay){
        return payMapper.insertSelective(pay);
    }
    @Override
    public int delete(Integer id){
        return payMapper.deleteByPrimaryKey(id);
    }
    @Override
    public int update(Pay pay){
        return payMapper.updateByPrimaryKeySelective(pay);
    }
    @Override
    public Pay getById(Integer id){
        return payMapper.selectByPrimaryKey(id);
    }
    @Override
    public List<Pay> getAll(){
        return payMapper.selectAll();
    }
}
Controller
package com.sun.cloud.controller;
 
import com.sun.cloud.entities.Pay;
import com.sun.cloud.entities.PayDTO;
import com.sun.cloud.service.PayService;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
 
import java.util.List;
 
@RestController
public class PayController {
    @Resource
    PayService payService;
 
    @PostMapping(value = "/pay/add")
    public String addPay(@RequestBody Pay pay) {
        System.out.println(pay.toString());
        int i = payService.add(pay);
        return "成功插入记录,返回值:" + i;
    }
 
    @DeleteMapping(value = "/pay/del/{id}")
    public Integer deletePay(@PathVariable("id") Integer id) {
        return payService.delete(id);
    }
 
    @PutMapping(value = "/pay/update")
    public String updatePay(@RequestBody PayDTO payDTO) {
        Pay pay = new Pay();
        BeanUtils.copyProperties(payDTO, pay);
 
        int i = payService.update(pay);
        return "成功修改记录,返回值:" + i;
    }
 
    @GetMapping(value = "/pay/get/{id}")
    public Pay getById(@PathVariable("id") Integer id) {
        return payService.getById(id);
    }
 
    @GetMapping(value = "/pay/get")
    public List<Pay> getAll() {
        return payService.getAll();
    }
}

接口测试

为了方便,这里直接使用:https://apifox.com/

测试添加
  • 请求路径:http://localhost:8001/pay/add

  • 请求参数:

{
    "payNo": "17204076",
    "orderNo": "6544de1c424a",
    "userId": "2",
    "amount": "19.90"
}
img
测试更新
  • 请求路径:http://localhost:8001/pay/update

  • 请求参数:

{
    "id" : "2",
    "payNo": "17204076",
    "orderNo": "6544de1c424a",
    "userId": "2",
    "amount": "16.90"
}
img
测试查询
  • 请求路径:http://localhost:8001/pay/get/1

  • 请求参数:无

img
测试查询全部
  • 请求路径:http://localhost:8001/pay/get

  • 请求参数:无

img
测试删除
  • 请求路径:http://localhost:8001/pay/del/2
  • 请求参数:无

Swagger

  1. Swagger 是一套基于 OpenAPI 规范(OpenAPI Specification,OAS)构建的开源工具,后来成为了 Open API 标准的主要定义者。对于 Rest API 来说很重要的一部分内容就是文档,Swagger 为我们提供了一套通过代码和注解自动生成文档的方法,这一点对于保证 API 文档的及时性将有很大的帮助。
  2. Swagger官网:https://swagger.io/
  3. Spring Boot集成Swagger3的实践文档:https://springdoc.org/ ,对于Spring Boot 3.x版本,需使用springdoc-openapi替代旧版springfox
引入依赖
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.8.6</version>
</dependency>

这会自动将 swagger-ui 部署到 spring-boot 应用程序。

⚠️ 注意:不要重复引入依赖,否则会出现LiteWebJarsResourceResolver找不到的问题,导致项目启动失败。

常用注解
注解作用作用对象示例
@Tag描述API接口分组的元信息(替代Swagger2的@Api类或接口@Tag(name = "用户模块", description = "用户相关接口")
@Operation描述单个接口的详细信息(如请求方法、说明等)方法@Operation(summary = "创建用户", description = "通过表单提交用户信息")
@Parameter描述接口参数的属性(如类型、是否必填等)方法参数@Parameter(name = "id", description = "用户ID", required = true, in = ParameterIn.PATH)
@Schema定义数据模型或属性的元信息(替代Swagger2的@ApiModelProperty模型类或属性@Schema(name = "User", description = "用户实体") @Schema(description = "用户名", required = true)
@Content定义请求或响应的媒体类型及数据示例方法或响应类@Content(mediaType = "application/json", schema = @Schema(implementation = User.class))
@ApiResponse定义接口的响应状态码及说明方法@ApiResponse(responseCode = "200", description = "请求成功")
@Hidden隐藏某个接口或模型类,不生成文档类、方法、属性或参数@Hidden
@SecurityScheme定义接口的安全认证方式(如OAuth2、API Key等)配置类@SecurityScheme(name = "BearerAuth", type = SecuritySchemeType.HTTP, scheme = "bearer")
修改controller
package com.sun.cloud.controller;
 
import com.sun.cloud.entities.Pay;
import com.sun.cloud.entities.PayDTO;
import com.sun.cloud.service.PayService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
 
import java.util.List;
 
@RestController
@Tag(name = "支付微服务模块", description = "支付CRUD")
public class PayController {
    @Resource
    PayService payService;
 
    @PostMapping(value = "/pay/add")
    @Operation(summary = "新增", description = "添加支付记录")
    public String addPay(@RequestBody Pay pay) {
        System.out.println(pay.toString());
        int i = payService.add(pay);
        return "成功插入记录,返回值:" + i;
    }
 
    @DeleteMapping(value = "/pay/del/{id}")
    @Operation(summary = "删除", description = "删除支付记录")
    public Integer deletePay(@PathVariable("id") Integer id) {
        return payService.delete(id);
    }
 
    @PutMapping(value = "/pay/update")
    @Operation(summary = "修改", description = "修改支付记录")
    public String updatePay(@RequestBody PayDTO payDTO) {
        Pay pay = new Pay();
        BeanUtils.copyProperties(payDTO, pay);
 
        int i = payService.update(pay);
        return "成功修改记录,返回值:" + i;
    }
 
    @GetMapping(value = "/pay/get/{id}")
    @Operation(summary = "根据ID查询", description = "查询单条支付记录")
    public Pay getById(@PathVariable("id") Integer id) {
        return payService.getById(id);
    }
 
    @GetMapping(value = "/pay/get")
    @Operation(summary = "查询", description = "查询全部支付记录")
    public List<Pay> getAll() {
        return payService.getAll();
    }
}
修改实体类
package com.sun.cloud.entities;
 
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;
 
import java.math.BigDecimal;
import java.util.Date;
 
/**
 * 表名:t_pay
 * 表注释:支付交易表
 */
@Data
@Table(name = "t_pay")
@Schema(title = "支付交易表Entity")
public class Pay {
    @Id
    @GeneratedValue(generator = "JDBC")
    @Schema(title = "主键")
    private Integer id;
 
    /**
     * 支付流水号
     */
    @Column(name = "pay_no")
    @Schema(title = "支付流水号")
    private String payNo;
 
    /**
     * 订单流水号
     */
    @Column(name = "order_no")
    @Schema(title = "订单流水号")
    private String orderNo;
 
    /**
     * 用户账号ID
     */
    @Column(name = "user_id")
    @Schema(title = "用户账号ID")
    private Integer userId;
 
    /**
     * 交易金额
     */
    @Schema(title = "交易金额")
    private BigDecimal amount;
 
    /**
     * 删除标志,默认0不删除,1删除
     */
    private Byte deleted;
 
    /**
     * 创建时间
     */
    @Column(name = "create_time")
    @Schema(title = "创建时间")
    private Date createTime;
 
    /**
     * 更新时间
     */
    @Column(name = "update_time")
    @Schema(title = "更新时间")
    private Date updateTime;
}
创建配置类
package com.sun.cloud.config;
 
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class Swagger3Config {
    @Bean
    public GroupedOpenApi PayApi() {
        return GroupedOpenApi.builder().group("支付微服务模块").pathsToMatch("/pay/**").build();
    }
 
    @Bean
    public GroupedOpenApi OtherApi() {
        return GroupedOpenApi.builder().group("其它微服务模块").pathsToMatch("/other/**", "/others").build();
    }
    /**
     * @Bean
    public GroupedOpenApi CustomerApi() {
        return GroupedOpenApi.builder().group("客户微服务模块").pathsToMatch("/customer/**", "/customers").build();
    }*/
 
    @Bean
    public OpenAPI docsOpenApi() {
        return new OpenAPI()
                .info(new Info().title("cloud2025")
                        .description("通用设计rest")
                        .version("v1.0"))
                .externalDocs(new ExternalDocumentation()
                        .description("www.sun.com")
                        .url("https://yiyan.baidu.com/"));
    }
}
测试

启动项目后,通过 http://localhost:8001/swagger-ui/index.html 访问接口文档

img

V1版本的不足

  1. 时间格式问题
img
  1. 响应数据的格式问题:目前的几个接口,返回数据的格式没有统一。
  2. 全局异常处理:目前没有全局的异常处理器。

支付模块V2版本

统一时间格式

方法一:配置文件全局设置
  1. application.ymlapplication.properties 中配置 Jackson 的日期格式和时区,适用于 JSON 序列化场景(如接口返回数据)。
  2. ⚠️ 注意:这里需要修改时区time-zone:数据库的默认时区是格林尼治的时间,如果不设置,会比实际时间少8个小时(北京时间)。
spring:
  # 配置日期格式化
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss  #时间戳统一转换为指定格式
    time-zone: GMT+8  # 时区修改为东8区
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
方法二:注解方式局部调整

在实体类字段上使用 @JsonFormat@DateTimeFormat,适用于特定字段覆盖全局配置。

  1. @JsonFormat注解

    • @JsonFormat注解用于属性或方法上,将Date类型转换为我们需要的类型显示。
    //在pattern上设置自己需要的格式
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
  2. @DateTimeFormat注解

    • 在需要进行日期格式转换的Date属性上添加注解@DateTimeFormat(pattern = "需要转换的格式")
    //注解将yyyy-MM-dd的形式转换为Date数据
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date birthday;
  3. @Temporal注解

    • 在需要进行日期格式转换的Date属性上添加注解@Temporal
    @Entity
    public class MyEntity {
        @Id
        private Long id;
       
        // 得到YYYY-MM-DD日期格式
        @Temporal(TemporalType.DATE)
        private Date myDate;
       
        // 得到HH:MM:SS时间格式
        @Temporal(TemporalType.TIME)
        private Date myTime;
       
        // 得到YYYY-MM-DD HH:MM:SS时间格式
        @Temporal(TemporalType.TIMESTAMP)
        private Date myTimestamp;
        // other fields and methods
    }
方法三:自定义全局转换器

通过实现 WebMvcConfigurer 接口配置全局格式化规则,兼容多种时间类型(如 LocalDateTimeDate

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
        // 处理 LocalDateTime 类型
        registry.addFormatterForFieldType(LocalDateTime.class, 
        new TemporalAccessorPrinter(formatter), 
        new TemporalAccessorParser(LocalDateTime.class, formatter));
 
        // 处理 Date 类型(兼容旧 API)
        registry.addFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
    }
}
测试
img

统一响应格式

  1. 定义响应的标准格式:
    • code状态值:由后端统一定义各种返回结果的状态码
    • message描述:本次接口调用的结果描述
    • data数据:本次返回的数据
  2. 扩展:接口调用时间等。
    • timestamp: 接口调用时间
img
新建枚举类ReturnCodeEnum
package com.sun.cloud.resp;
 
import lombok.Getter;
 
import java.util.Arrays;
 
@Getter
public enum ReturnCodeEnum {
    /**
     * 操作失败
     **/
    RC999("999", "操作XXX失败"),
    /**
     * 操作成功
     **/
    RC200("200", "success"),
    /**
     * 服务降级
     **/
    RC201("201", "服务开启降级保护,请稍后再试!"),
    /**
     * 热点参数限流
     **/
    RC202("202", "热点参数限流,请稍后再试!"),
    /**
     * 系统规则不满足
     **/
    RC203("203", "系统规则不满足要求,请稍后再试!"),
    /**
     * 授权规则不通过
     **/
    RC204("204", "授权规则不通过,请稍后再试!"),
    /**
     * access_denied
     **/
    RC403("403", "无访问权限,请联系管理员授予权限"),
    /**
     * access_denied
     **/
    RC401("401", "匿名用户访问无权限资源时的异常"),
    RC404("404", "404页面找不到的异常"),
    /**
     * 服务异常
     **/
    RC500("500", "系统异常,请稍后重试"),
    RC375("375", "数学运算异常,请稍后重试"),
 
    INVALID_TOKEN("2001", "访问令牌不合法"),
    ACCESS_DENIED("2003", "没有权限访问该资源"),
    CLIENT_AUTHENTICATION_FAILED("1001", "客户端认证失败"),
    USERNAME_OR_PASSWORD_ERROR("1002", "用户名或密码错误"),
    BUSINESS_ERROR("1004", "业务逻辑异常"),
    UNSUPPORTED_GRANT_TYPE("1003", "不支持的认证模式");
 
    /**
     * 自定义状态码
     **/
    private final String code;
    /**
     * 自定义描述
     **/
    private final String message;
 
    ReturnCodeEnum(String code, String message) {
        this.code = code;
        this.message = message;
    }
 
    // 遍历枚举V1
    public static ReturnCodeEnum getReturnCodeEnum(String code) {
        for (ReturnCodeEnum element : ReturnCodeEnum.values()) {
            if (element.getCode().equalsIgnoreCase(code)) {
                return element;
            }
        }
        return null;
    }
 
    // 遍历枚举V2
    public static ReturnCodeEnum getReturnCodeEnumV2(String code) {
        return Arrays.stream(ReturnCodeEnum.values()).filter(x -> x.getCode().equalsIgnoreCase(code)).findFirst().orElse(null);
    }
 
 
    /*public static void main(String[] args)
    {
        System.out.println(getReturnCodeEnumV2("200"));
        System.out.println(getReturnCodeEnumV2("200").getCode());
        System.out.println(getReturnCodeEnumV2("200").getMessage());
    }*/
}
新建统一定义返回对象ResultData
package com.sun.cloud.resp;
 
import lombok.Data;
import lombok.experimental.Accessors;
 
@Data
@Accessors(chain = true)
public class ResultData<T> {
 
    /** 结果状态 ,具体状态码参见枚举类ReturnCodeEnum.java*/
    private String code;
    private String message;
    private T data;
    private long timestamp ;
 
 
    public ResultData (){
        this.timestamp = System.currentTimeMillis();
    }
 
    public static <T> ResultData<T> success(T data) {
        ResultData<T> resultData = new ResultData<>();
        resultData.setCode(ReturnCodeEnum.RC200.getCode());
        resultData.setMessage(ReturnCodeEnum.RC200.getMessage());
        resultData.setData(data);
        return resultData;
    }
 
    public static <T> ResultData<T> fail(String code, String message) {
        ResultData<T> resultData = new ResultData<>();
        resultData.setCode(code);
        resultData.setMessage(message);
 
        return resultData;
    }
 
}
修改controller
package com.sun.cloud.controller;
 
import com.sun.cloud.entities.Pay;
import com.sun.cloud.entities.PayDTO;
import com.sun.cloud.resp.ResultData;
import com.sun.cloud.service.PayService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
 
import java.util.List;
 
@RestController
@Tag(name = "支付微服务模块", description = "支付CRUD")
public class PayController {
    @Resource
    PayService payService;
 
    @PostMapping(value = "/pay/add")
    @Operation(summary = "新增", description = "添加支付记录")
    public ResultData<String> addPay(@RequestBody Pay pay) {
        System.out.println(pay.toString());
        int i = payService.add(pay);
        return ResultData.success("成功添加记录,返回值:" + i);
    }
 
    @DeleteMapping(value = "/pay/del/{id}")
    @Operation(summary = "删除", description = "删除支付记录")
    public ResultData<Integer> deletePay(@PathVariable("id") Integer id) {
        int i = payService.delete(id);
        return ResultData.success(i);
    }
 
    @PutMapping(value = "/pay/update")
    @Operation(summary = "修改", description = "修改支付记录")
    public ResultData<String> updatePay(@RequestBody PayDTO payDTO) {
        Pay pay = new Pay();
        BeanUtils.copyProperties(payDTO, pay);
 
        int i = payService.update(pay);
        return ResultData.success("成功修改记录,返回值:" + i);
    }
 
    @GetMapping(value = "/pay/get/{id}")
    @Operation(summary = "根据ID查询", description = "查询单条支付记录")
    public ResultData<Pay> getById(@PathVariable("id") Integer id) {
        Pay pay = payService.getById(id);
        return ResultData.success(pay);
    }
 
    @GetMapping(value = "/pay/get")
    @Operation(summary = "查询", description = "查询全部支付记录")
    public ResultData<List<Pay>> getAll() {
        List<Pay> pays = payService.getAll();
        return ResultData.success(pays);
    }
}
测试
img

全局异常处理器

在项目中统一处理系统异常,返回标准化的接口响应格式。

  1. 统一响应格式
    • 避免不同接口返回的错误信息格式不一致,保证前端能够统一解析和处理异常数据,提高接口的规范性和一致性
  2. 减少重复代码
    • 不需要在每个 Controller 或 Service 中手动写 try ... catch,降低代码冗余
  3. 集中管理异常
    • 可以对不同类型的异常(业务异常、系统异常、验证异常等)进行统一处理和日志记录,便于维护和监控。
  4. 提高可维护性与可扩展性
    • 当需要修改异常返回规则或添加统一日志/告警时,只需修改全局异常处理器即可,无需修改每个接口。
新建全局异常类GlobalExceptionHandler
package com.sun.cloud.exp;
 
import com.sun.cloud.resp.ResultData;
import com.sun.cloud.resp.ReturnCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
 
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 默认全局异常处理。
     *
     * @param e the e
     * @return ResultData
     */
    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResultData<String> exception(Exception e) {
        System.out.println("---- come in GlobalExceptionHandler");
        log.error("全局异常信息exception:{}", e.getMessage(), e);
        return ResultData.fail(ReturnCodeEnum.RC500.getCode(), e.getMessage());
    }
}
测试

修改controller,抛出异常

@GetMapping(value = "/pay/get/{id}")
@Operation(summary = "根据ID查询", description = "查询单条支付记录")
public ResultData<Pay> getById(@PathVariable("id") Integer id) {
    if (id < 0) throw new RuntimeException("id不能为负数");
    Pay pay = payService.getById(id);
    return ResultData.success(pay);
}
img

订单模块

创建订单模块

微服务调用者—订单模块:cloud-consumer-order80

新建订单模块
img
修改pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
  <groupId>com.sun.cloud</groupId>
  <artifactId>cloud2025</artifactId>
  <version>1.0-SNAPSHOT</version>
</parent>
 
<artifactId>cloud-consumer-order80</artifactId>
 
<properties>
  <maven.compiler.source>21</maven.compiler.source>
  <maven.compiler.target>21</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
 
<dependencies>
  <!--web + actuator-->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
  <!--lombok-->
  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
  </dependency>
  <!--hutool-all-->
  <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
  </dependency>
  <!--fastjson2-->
  <dependency>
      <groupId>com.alibaba.fastjson2</groupId>
      <artifactId>fastjson2</artifactId>
  </dependency>
  <!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
  <dependency>
      <groupId>org.springdoc</groupId>
      <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
  </dependency>
</dependencies>
 
<build>
  <plugins>
      <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
  </plugins>
</build>
 
</project>
修改application.yaml
server:
  port: 80
创建主启动类
package com.sun.cloud;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class Main80 {
    public static void main(String[] args) {
        SpringApplication.run(Main80.class, args);
    }
}

业务类

实体类entities

一般情况下,服务调用方不应直接接触服务提供方的实体类(Entity)或了解数据库表结构。服务提供方对外暴露的接口应使用 DTO(Data Transfer Object) 进行数据传输,从而实现数据隔离、保证服务内部实现的封装性,同时便于接口版本管理和系统演进。

package com.sun.cloud.entities;
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 
import java.io.Serializable;
import java.math.BigDecimal;
 
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayDTO implements Serializable {
    private Integer id;
    // 支付流水号
    private String payNo;
    // 订单流水号
    private String orderNo;
    // 用户账号ID
    private Integer userId;
    // 交易金额
    private BigDecimal amount;
}
统一返回值ResultData

直接从支付模块复制过来

image-20251018151053245

RestTemplate

  1. RestTemplate 是 Spring 提供的用于访问 RESTful 服务的客户端工具类,封装了 HTTP 请求和响应处理,提供多种便捷方法调用远程服务。
  2. 官网文档:RestTemplate
常用API说明
img
  1. 方法参数说明

    • url:REST 请求地址
    • requestMap:请求参数,可以是查询参数或请求体
    • ResponseBean.class:指定将 HTTP 响应体转换成的目标对象类型
  2. getForObject / postForObject

    • 用于发送 GET 或 POST 请求
    • 返回值为直接转换后的响应对象(通常为 JSON 对象映射的 Java 对象)
    ResponseBean response = restTemplate.getForObject("http://localhost:8001/payment/1", null, ResponseBean.class);
  3. getForEntity / postForEntity

    • 返回值为 ResponseEntity<T> 对象
    • 包含响应体(body)、HTTP 状态码(status code)以及响应头(headers)
    • 适合需要处理 HTTP 状态或获取响应头信息的场景
    ResponseEntity<ResponseBean> responseEntity = restTemplate.getForEntity("http://localhost:8001/payment/1", ResponseBean.class);
    HttpStatus status = responseEntity.getStatusCode();
    ResponseBean body = responseEntity.getBody();
    HttpHeaders headers = responseEntity.getHeaders();
配置类
package com.sun.cloud.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
 
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Controller

package com.sun.cloud.controller;
 
import com.sun.cloud.entities.PayDTO;
import com.sun.cloud.resp.ResultData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
 
@RestController
public class OrderController {
    public static final String PaymentSrv_URL = "http://localhost:8001";// 先写死,硬编码
 
    @Autowired
    private RestTemplate restTemplate;
 
    /**
     * 一般情况下,通过浏览器的地址栏输入url,发送的只能是get请求
     * 我们底层调用的是post方法,模拟消费者发送get请求,客户端消费者参数可以不添加@RequestBody
     *
     * @param payDTO 支付流水记录的数据传输对象
     * @return ResultData
     */
    @GetMapping("/consumer/pay/add")
    public ResultData addOrder(PayDTO payDTO) {
        return restTemplate.postForObject(PaymentSrv_URL + "/pay/add", payDTO, ResultData.class);
    }
    
    @GetMapping("/consumer/pay/get/{id}")
    public ResultData getPayInfo(@PathVariable("id") Integer id) {
        return restTemplate.getForObject(PaymentSrv_URL + "/pay/get/" + id, ResultData.class, id);
    }
 
    @GetMapping("/consumer/pay/update")
    public ResultData updatePay(PayDTO payDTO) {
        return restTemplate.postForObject(PaymentSrv_URL + "/pay/update", payDTO, ResultData.class);
    }
 
    @GetMapping("/consumer/pay/delete")
    public ResultData deletePay(Integer id) {
        return restTemplate.getForObject(PaymentSrv_URL + "/pay/del/" + id, ResultData.class, id);
    }
}

测试

imgimg

工程重构

在当前项目中,支付模块和订单模块存在重复的类,例如: PayDTOResultDataReturnCodeEnum 等。

为提高代码复用性和维护性,建议将这些公共类提取到一个 独立的公共模块 中,供各业务模块共享使用,从而减少重复代码、统一数据传输对象和返回结构。

创建公共模块

新建模块

创建公共模块cloud-api-commons,对外暴露通用的组件、API、接口、工具类等。

img
修改pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.sun.cloud</groupId>
        <artifactId>cloud2025</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
 
    <artifactId>cloud-api-commons</artifactId>
 
    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
 
    <dependencies>
        <!--SpringBoot通用依赖模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
    </dependencies>
</project>
创建公共类

直接从支付模块、订单模块复制

img

公共模块打包

将公共模块打成 jar 包,并安装到本地 maven 仓库中。

img

在maven本地仓库中能找到该目录

img

订单、支付模块改造

  1. 删除支付模块和订单模块多余的文件,将公共模块已经包含的类,删除即可。
  2. 引入公共模块作为依赖
<dependency>
    <groupId>com.sun.cloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

测试

img

相关文章

评论区