Skip to content
返回

后端脚手架

脚手架

DigitalHainan Spring 微服务框架脚手架与集成指南,覆盖 POM、缓存、MyBatis、认证、OSS 等核心模块。

概述
后端脚手架

digitalhainan-common ​

本文档用于指导在目标项目中完成 digitalhainan-common 模块集成。

执行顺序:

  1. 先完成「2.1.2 快速集成」的全部步骤(必须)
  2. 根据业务需求,查阅「功能集成决策索引」选择对应路径,逐项实现
  3. 遇到配置问题时参考「2.1.3 配置属性参考」
  4. 出现异常时查阅「2.1.6 常见问题」

代码约定: 所有代码示例均包含完整 import 路径和类声明,可直接用于代码生成。


2.1.1 模块简介 ​

digitalhainan-common 是数字海南框架的通用基础组件,提供所有模块共享的基础设施,包括:

  • 统一响应体(BaseResponse<T, V>)
  • 异常体系(BusinessException / BusinessHintException / SystemException / AuthException)
  • 错误码接口(IBusinessExceptionCodeConfiguration)
  • 分页 DTO/VO(PaginationInfoResDTO<T> / BasePageQueryVO)
  • 对象转换基接口(BaseConverter<SOURCE, TARGET>,基于 MapStruct)
  • 字段加密/脱敏注解(@Encrypt)
  • 参数校验注解(@ValidBean)
  • 国际化注解(@AppI18n)
  • Git 构建信息(GitProperties)

Maven 坐标:

xml
<dependency>
    <groupId>cn.com.digitalhainan</groupId>
    <artifactId>digitalhainan-common</artifactId>
</dependency>

前置依赖(项目 pom.xml 中必须已有):

依赖说明
digitalhainan-pom父 POM,统一管理版本号

当前版本: 3.1.0-SNAPSHOT


2.1.2 快速集成 ​

目标: 完成以下 4 个步骤后,项目即具备统一响应、异常、分页等基础能力。

2.1.2.1 添加 Maven 依赖 ​

操作: 在项目 pom.xml 的 <dependencies> 中添加:

xml
<dependency>
    <groupId>cn.com.digitalhainan</groupId>
    <artifactId>digitalhainan-common</artifactId>
</dependency>

版本号由 digitalhainan-pom 统一管理,无需指定 <version>。

2.1.2.2 确认自动配置加载 ​

digitalhainan-common 提供 BasisAutoConfiguration 自动配置类,通过 spring.factories 或 AutoConfiguration.imports 自动加载,无需额外配置。

2.1.2.3 创建验证代码 ​

操作: 在项目中创建使用 BaseResponse 的 Controller 验证集成:

java
package com.example.controller;

import cn.com.digitalhainan.common.core.response.BaseResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CommonVerifier {

    @GetMapping("/verify")
    public BaseResponse<String, Void> verify() {
        return BaseResponse.success("integration:ok");
    }
}

2.1.2.4 验证集成成功 ​

操作: 启动项目,调用 GET /verify,检查返回:

检查项预期结果
HTTP 状态码200
响应体 successtrue
响应体 code"0"
响应体 data"integration:ok"

2.1.3 配置属性参考 ​

2.1.3.1 内置依赖 ​

digitalhainan-common 自动引入以下依赖(无需项目重复声明):

依赖说明
spring-webSpring Web 基础
spring-boot-starter-aopAOP 支持
hibernate-validator参数校验
commons-lang3Apache 工具集
httpclientHTTP 客户端
mapstruct对象映射
jackson-core/databindJSON 序列化
lombok代码简化
apm-toolkit-traceSkyWalking 链路追踪
aliyun-java-sdk-geoip阿里云 IP 定位

2.1.3.2 错误码枚举接口 ​

实现 IBusinessExceptionCodeConfiguration 接口的枚举类需提供:

方法返回类型说明
code()String业务异常状态码(必须实现)
defaultMsg()String默认提示信息(默认返回 code)
belongHttpStatus()HttpStatus对应的 HTTP 状态码(默认 400 BAD_REQUEST)

2.1.4 核心功能使用 ​

功能集成决策索引 ​

AI 执行指引: 先完成「2.1.2 快速集成」,然后根据业务需求在下表中找到对应路径,跳转到对应小节实现。

业务需求集成路径前置条件
Controller 返回统一 JSON 格式4.1完成第二章
定义业务错误码枚举4.2完成第二章
抛出业务异常(可恢复)4.3完成第二章 + 4.2
抛出业务提示异常(用户提示)4.4完成第二章 + 4.2
抛出系统异常(不可恢复)4.5完成第二章 + 4.2
分页查询请求/响应4.6完成第二章
DO/VO/DTO 对象转换4.7完成第二章
响应字段加密/脱敏4.8完成第二章

2.1.4.1 统一响应体 ​

适用场景: Controller 接口返回统一格式的 JSON 响应。

java
package com.example.controller;

import cn.com.digitalhainan.common.core.response.BaseResponse;
import cn.com.digitalhainan.common.common.dto.PaginationInfoResDTO;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

    // 成功 - 返回单个对象
    @GetMapping("/{id}")
    public BaseResponse<UserVO, Void> getUser(@PathVariable String id) {
        UserVO user = userService.findById(id);
        return BaseResponse.success(user);
    }

    // 成功 - 返回带扩展信息
    @GetMapping("/list")
    public BaseResponse<List<UserVO>, PaginationInfoResDTO<UserVO>> listUsers(
            @RequestParam Integer pageNum, @RequestParam Integer pageSize) {
        List<UserVO> users = userService.findAll(pageNum, pageSize);
        PaginationInfoResDTO<UserVO> page = new PaginationInfoResDTO<>(
            (long) pageNum, (long) pageSize, 100L, users);
        return BaseResponse.success("0", null, users, page);
    }

    // 失败 - 返回错误码和消息
    @PostMapping
    public BaseResponse<UserVO, Void> createUser(@RequestBody UserCreateDTO dto) {
        // 校验失败
        return BaseResponse.fail("USER_001", "用户名已存在");
    }
}

BaseResponse 字段说明:

字段类型说明
successBoolean是否成功
codeString状态码,成功为 "0"
messageString提示信息(支持 i18n)
dataT业务数据(支持 i18n)
extendV扩展数据(如分页信息,支持 i18n)
debugMessageString调试信息(仅开发环境)
debugTraceString调试追踪
requestIdString请求唯一标识

验证: 调用接口返回 JSON 包含 success、code、message、data 字段。


2.1.4.2 定义业务错误码枚举 ​

适用场景: 项目需要统一的错误码体系。

java
package com.example.constants;

import cn.com.digitalhainan.common.core.exception.IBusinessExceptionCodeConfiguration;
import org.springframework.http.HttpStatus;

public enum UserErrorCode implements IBusinessExceptionCodeConfiguration {

    USER_NOT_FOUND("USER_001", "用户不存在"),
    USER_NAME_EXISTS("USER_002", "用户名已存在"),
    USER_DISABLED("USER_003", "用户已禁用"),
    USER_PASSWORD_WRONG("USER_004", "密码错误");

    private final String code;
    private final String defaultMsg;

    UserErrorCode(String code, String defaultMsg) {
        this.code = code;
        this.defaultMsg = defaultMsg;
    }

    @Override
    public String code() {
        return this.code;
    }

    @Override
    public String defaultMsg() {
        return this.defaultMsg;
    }

    @Override
    public HttpStatus belongHttpStatus() {
        return HttpStatus.BAD_REQUEST;
    }
}

验证: 枚举类实现 IBusinessExceptionCodeConfiguration 接口,编译通过。


2.1.4.3 抛出业务异常(BusinessException) ​

适用场景: 业务逻辑校验失败,需要中断流程并返回错误码给前端。

java
package com.example.service;

import cn.com.digitalhainan.common.core.exception.BusinessException;
import com.example.constants.UserErrorCode;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    public void deleteUser(String userId) {
        UserVO user = findById(userId);
        if (user == null) {
            // 方式一:使用错误码枚举 + 自定义消息
            throw BusinessException.error(UserErrorCode.USER_NOT_FOUND, "用户ID=" + userId + "不存在");
        }
        if (user.isDisabled()) {
            // 方式二:使用错误码枚举的默认消息
            throw BusinessException.error(UserErrorCode.USER_DISABLED);
        }
        // 方式三:带 i18n 参数占位符
        throw BusinessException.error(UserErrorCode.USER_NOT_FOUND,
            java.util.Arrays.asList(userId));
    }
}

BusinessException 特点:

  • 继承 AbstractRuntimeException → RuntimeException
  • 标注 @IgnoredException(SkyWalking 不记录为错误)
  • 对应 HTTP 状态码由 IBusinessExceptionCodeConfiguration.belongHttpStatus() 决定(默认 400)

验证: 调用 deleteUser 方法,接口返回 success: false,code: "USER_001"。


2.1.4.4 抛出业务提示异常(BusinessHintException) ​

适用场景: 需要向用户展示友好提示信息(如"库存不足"),SkyWalking 不记录为错误。

java
package com.example.service;

import cn.com.digitalhainan.common.core.exception.BusinessHintException;
import com.example.constants.OrderErrorCode;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    public void createOrder(String productId, int quantity) {
        int stock = getStock(productId);
        if (stock < quantity) {
            // 提示型异常:不记录日志堆栈,仅返回提示信息给用户
            throw BusinessHintException.error(OrderErrorCode.STOCK_NOT_ENOUGH,
                "库存不足,当前库存: " + stock);
        }
    }
}

BusinessHintException vs BusinessException:

特性BusinessExceptionBusinessHintException
用途业务逻辑错误用户友好提示
SkyWalking 记录不记录不记录
日志级别ERRORWARN(仅消息)

验证: 调用 createOrder,前端收到提示信息,日志中无堆栈。


2.1.4.5 抛出系统异常(SystemException) ​

适用场景: 系统级不可恢复错误(数据库连接失败、第三方服务超时等)。

java
package com.example.service;

import cn.com.digitalhainan.common.core.exception.SystemException;
import com.example.constants.SystemErrorCode;
import org.springframework.stereotype.Service;

@Service
public class PaymentService {

    public void processPayment(String orderId) {
        try {
            paymentGateway.pay(orderId);
        } catch (Exception e) {
            // 系统异常:记录堆栈,SkyWalking 标记为错误
            throw SystemException.error(SystemErrorCode.PAYMENT_GATEWAY_ERROR,
                "支付网关调用失败", e);
        }
    }
}

SystemException 特点:

  • 未标注 @IgnoredException,SkyWalking 会记录为错误
  • 日志级别为 ERROR,包含完整堆栈

验证: 调用 processPayment 触发异常,SkyWalking 链路中显示错误标记。


2.1.4.6 分页查询 ​

适用场景: 列表查询接口需要统一分页参数和响应格式。

java
package com.example.controller;

import cn.com.digitalhainan.common.common.vo.BasePageQueryVO;
import cn.com.digitalhainan.common.common.dto.PaginationInfoResDTO;
import cn.com.digitalhainan.common.core.response.BaseResponse;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping
    public BaseResponse<List<UserVO>, PaginationInfoResDTO<UserVO>> list(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {

        // 查询数据
        List<UserVO> records = userService.findPage(pageNum, pageSize);
        long total = userService.count();

        // 构建分页响应
        PaginationInfoResDTO<UserVO> pageInfo = new PaginationInfoResDTO<>(
            (long) pageNum, (long) pageSize, total, records);

        return BaseResponse.success("0", null, records, pageInfo);
    }
}

BasePageQueryVO 字段:

字段类型说明
pageNumInteger当前页码
pageSizeInteger每页大小

PaginationInfoResDTO<T> 字段:

字段类型说明
pageNumLong当前页码
pageSizeLong每页大小
pagesLong总页数(自动计算)
totalLong总记录数
recordsList<T>当前页数据

验证: 调用分页接口,返回 extend 中包含 pages、total、records。


2.1.4.7 对象转换(MapStruct BaseConverter) ​

适用场景: DO/DTO/VO 之间的对象映射。

java
package com.example.converter;

import cn.com.digitalhainan.common.core.BaseConverter;
import com.example.model.UserDO;
import com.example.dto.UserCreateDTO;
import com.example.vo.UserVO;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface UserConverter extends BaseConverter<UserDO, UserVO> {

    // 继承自 BaseConverter 的方法(自动实现):
    // UserVO sourceToTarget(UserDO var1);
    // UserDO targetToSource(UserVO var1);
    // List<UserVO> sourceToTarget(List<UserDO> var1);
    // List<UserDO> targetToSource(List<UserVO> var1);

    // 自定义转换
    UserVO fromCreateDTO(UserCreateDTO dto);
}

使用转换:

java
package com.example.service;

import com.example.converter.UserConverter;
import com.example.model.UserDO;
import com.example.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserConverter userConverter;

    public UserVO getUserVO(String userId) {
        UserDO userDO = userMapper.selectById(userId);
        // DO -> VO
        return userConverter.sourceToTarget(userDO);
    }

    public java.util.List<UserVO> listUserVOs() {
        java.util.List<UserDO> doList = userMapper.selectList(null);
        // List<DO> -> List<VO>
        return userConverter.sourceToTarget(doList);
    }
}

BaseConverter<SOURCE, TARGET> 提供的方法:

方法说明
sourceToTarget(SOURCE)单个对象正向转换
targetToSource(TARGET)单个对象反向转换
sourceToTarget(List<SOURCE>)集合正向转换
targetToSource(List<TARGET>)集合反向转换
sourceToTarget(Stream<SOURCE>)流正向转换
targetToSource(Stream<TARGET>)流反向转换

验证: 调用 sourceToTarget 返回非 null 的转换结果,同名字段已映射。


2.1.4.8 字段加密/脱敏 ​

适用场景: 响应数据中敏感字段需要自动加密或脱敏(如日志中隐藏手机号)。

java
package com.example.vo;

import cn.com.digitalhainan.common.core.annotation.Encrypt;
import cn.com.digitalhainan.common.constants.EncryptEnum;
import cn.com.digitalhainan.common.constants.EncryptedModuleType;
import lombok.Data;

@Data
public class UserVO {

    private String userId;
    private String userName;

    // 手机号脱敏:138****1234
    @Encrypt(type = EncryptEnum.PHONE_DESENSITIZE,
             moduleType = {EncryptedModuleType.LOG, EncryptedModuleType.WEB})
    private String phone;

    // 身份证脱敏:110***********1234
    @Encrypt(type = EncryptEnum.IDNO_DESENSITIZE)
    private String idNo;

    // 邮箱脱敏:a***@example.com
    @Encrypt(type = EncryptEnum.EMAIL_DESENSITIZE)
    private String email;

    // 姓名脱敏:张*
    @Encrypt(type = EncryptEnum.NAME_DESENSITIZE)
    private String realName;

    // MD5 加密(不可逆)
    @Encrypt(type = EncryptEnum.MD5)
    private String password;
}

EncryptEnum 可选值:

枚举值说明示例
MD5MD5 哈希e10adc3949ba59abbe56e057f20f883e
AESAES 加密(可逆)加密后密文
BASE64Base64 编码MTIzNDU2
RSA2RSA2 加密加密后密文
PHONE_DESENSITIZE手机号脱敏138****1234
IDNO_DESENSITIZE身份证脱敏110***********1234
EMAIL_DESENSITIZE邮箱脱敏a***@example.com
NAME_DESENSITIZE姓名脱敏张*

EncryptedModuleType 可选值:

枚举值说明
LOG仅在日志输出时加密/脱敏
WEB在 HTTP 响应中也加密/脱敏

验证: 调用返回 UserVO 的接口,检查 phone 字段显示为 138****1234 格式。


2.1.5 版本变更记录 ​

v3.1.0(当前版本) ​

  • [变更] 版本升级
  • [修复] 代码格式化

v3.0.0 ​

  • [破坏性变更] 适配 Spring Boot 3.x + JDK 21
  • [变更] javax.* 迁移至 jakarta.*

v2.1.0 ​

  • [新增] 支持 HTTP 状态码(belongHttpStatus())
  • [新增] Git 构建信息集成

v2.0 ​

  • [新增] 字段加密/脱敏注解(@Encrypt)
  • [新增] 对象转换基接口(BaseConverter)
  • [新增] 异常体系完善(BusinessHintException)

v1.0.0(master) ​

  • 初始版本
  • BaseResponse 统一响应体
  • 异常体系(BusinessException / SystemException)
  • 分页 DTO/VO
  • IBusinessExceptionCodeConfiguration 错误码接口

2.1.6 常见问题 ​

Q1: BaseResponse 返回的 message 为 null ​

排查步骤:

  1. 使用 BaseResponse.success(data) 时 message 默认为 null,这是正常行为
  2. 如需自定义消息,使用 BaseResponse.success("0", "操作成功", data)

Q2: 异常抛出后前端收到 500 而非 400 ​

排查步骤:

  1. 确认错误码枚举的 belongHttpStatus() 返回了正确的状态码
  2. 确认全局异常处理器(通常在 ms-common 模块)已正确配置
  3. SystemException 默认不会被 @IgnoredException 标记,可能被框架记录为 500

Q3: MapStruct 生成的实现类为空 ​

排查步骤:

  1. 确认 @Mapper(componentModel = "spring") 注解已添加
  2. 确认 mapstruct-processor 在编译依赖中(已由 digitalhainan-common 引入)
  3. 执行 mvn clean compile 重新生成

Q4: @Encrypt 脱敏注解不生效 ​

排查步骤:

  1. 确认 moduleType 包含 EncryptedModuleType.WEB(仅 LOG 则只在日志中生效)
  2. 确认字段类型为 String
  3. 确认 AOP 配置已启用(BasisAutoConfiguration 自动加载)

Q5: 如何自定义错误码枚举 ​

解决方案:

实现 IBusinessExceptionCodeConfiguration 接口即可:

java
public enum MyErrorCode implements IBusinessExceptionCodeConfiguration {
    MY_ERROR("E001", "自定义错误");

    private final String code;
    private final String msg;

    MyErrorCode(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    @Override
    public String code() { return this.code; }

    @Override
    public String defaultMsg() { return this.msg; }
}

2.1.7 版本兼容性矩阵 ​

digitalhainan-commonSpring BootJDK备注
3.1.0-SNAPSHOT3.2.4+21当前最新
3.0.0 ~ 3.0.13.2.421适配 Spring Boot 3.x
2.0 ~ 2.1.02.2.4.RELEASE1.8新增加密注解、HTTP 状态码支持
1.0.0(master)2.2.4.RELEASE1.8初始版本

升级提示: 从 2.x 升级到 3.x 需注意:

  1. JDK 需升级至 21
  2. javax.validation 迁移至 jakarta.validation
  3. javax.servlet 迁移至 jakarta.servlet

Power By 数字海南