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

本文档用于指导在目标项目中完成
digitalhainan-common模块集成。执行顺序:
- 先完成「2.1.2 快速集成」的全部步骤(必须)
- 根据业务需求,查阅「功能集成决策索引」选择对应路径,逐项实现
- 遇到配置问题时参考「2.1.3 配置属性参考」
- 出现异常时查阅「2.1.6 常见问题」
代码约定: 所有代码示例均包含完整
import路径和类声明,可直接用于代码生成。
digitalhainan-common 是数字海南框架的通用基础组件,提供所有模块共享的基础设施,包括:
BaseResponse<T, V>)BusinessException / BusinessHintException / SystemException / AuthException)IBusinessExceptionCodeConfiguration)PaginationInfoResDTO<T> / BasePageQueryVO)BaseConverter<SOURCE, TARGET>,基于 MapStruct)@Encrypt)@ValidBean)@AppI18n)GitProperties)Maven 坐标:
<dependency>
<groupId>cn.com.digitalhainan</groupId>
<artifactId>digitalhainan-common</artifactId>
</dependency>前置依赖(项目 pom.xml 中必须已有):
| 依赖 | 说明 |
|---|---|
digitalhainan-pom | 父 POM,统一管理版本号 |
当前版本: 3.1.0-SNAPSHOT
目标: 完成以下 4 个步骤后,项目即具备统一响应、异常、分页等基础能力。
操作: 在项目 pom.xml 的 <dependencies> 中添加:
<dependency>
<groupId>cn.com.digitalhainan</groupId>
<artifactId>digitalhainan-common</artifactId>
</dependency>版本号由
digitalhainan-pom统一管理,无需指定<version>。
digitalhainan-common 提供 BasisAutoConfiguration 自动配置类,通过 spring.factories 或 AutoConfiguration.imports 自动加载,无需额外配置。
操作: 在项目中创建使用 BaseResponse 的 Controller 验证集成:
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");
}
}操作: 启动项目,调用 GET /verify,检查返回:
| 检查项 | 预期结果 |
|---|---|
| HTTP 状态码 | 200 |
响应体 success | true |
响应体 code | "0" |
响应体 data | "integration:ok" |
digitalhainan-common 自动引入以下依赖(无需项目重复声明):
| 依赖 | 说明 |
|---|---|
spring-web | Spring Web 基础 |
spring-boot-starter-aop | AOP 支持 |
hibernate-validator | 参数校验 |
commons-lang3 | Apache 工具集 |
httpclient | HTTP 客户端 |
mapstruct | 对象映射 |
jackson-core/databind | JSON 序列化 |
lombok | 代码简化 |
apm-toolkit-trace | SkyWalking 链路追踪 |
aliyun-java-sdk-geoip | 阿里云 IP 定位 |
实现 IBusinessExceptionCodeConfiguration 接口的枚举类需提供:
| 方法 | 返回类型 | 说明 |
|---|---|---|
code() | String | 业务异常状态码(必须实现) |
defaultMsg() | String | 默认提示信息(默认返回 code) |
belongHttpStatus() | HttpStatus | 对应的 HTTP 状态码(默认 400 BAD_REQUEST) |
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 | 完成第二章 |
适用场景: Controller 接口返回统一格式的 JSON 响应。
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 字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
success | Boolean | 是否成功 |
code | String | 状态码,成功为 "0" |
message | String | 提示信息(支持 i18n) |
data | T | 业务数据(支持 i18n) |
extend | V | 扩展数据(如分页信息,支持 i18n) |
debugMessage | String | 调试信息(仅开发环境) |
debugTrace | String | 调试追踪 |
requestId | String | 请求唯一标识 |
验证: 调用接口返回 JSON 包含 success、code、message、data 字段。
适用场景: 项目需要统一的错误码体系。
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 接口,编译通过。
适用场景: 业务逻辑校验失败,需要中断流程并返回错误码给前端。
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 不记录为错误)IBusinessExceptionCodeConfiguration.belongHttpStatus() 决定(默认 400)验证: 调用 deleteUser 方法,接口返回 success: false,code: "USER_001"。
适用场景: 需要向用户展示友好提示信息(如"库存不足"),SkyWalking 不记录为错误。
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:
| 特性 | BusinessException | BusinessHintException |
|---|---|---|
| 用途 | 业务逻辑错误 | 用户友好提示 |
| SkyWalking 记录 | 不记录 | 不记录 |
| 日志级别 | ERROR | WARN(仅消息) |
验证: 调用 createOrder,前端收到提示信息,日志中无堆栈。
适用场景: 系统级不可恢复错误(数据库连接失败、第三方服务超时等)。
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 会记录为错误验证: 调用 processPayment 触发异常,SkyWalking 链路中显示错误标记。
适用场景: 列表查询接口需要统一分页参数和响应格式。
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 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
pageNum | Integer | 当前页码 |
pageSize | Integer | 每页大小 |
PaginationInfoResDTO<T> 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
pageNum | Long | 当前页码 |
pageSize | Long | 每页大小 |
pages | Long | 总页数(自动计算) |
total | Long | 总记录数 |
records | List<T> | 当前页数据 |
验证: 调用分页接口,返回 extend 中包含 pages、total、records。
适用场景: DO/DTO/VO 之间的对象映射。
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);
}使用转换:
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 的转换结果,同名字段已映射。
适用场景: 响应数据中敏感字段需要自动加密或脱敏(如日志中隐藏手机号)。
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 可选值:
| 枚举值 | 说明 | 示例 |
|---|---|---|
MD5 | MD5 哈希 | e10adc3949ba59abbe56e057f20f883e |
AES | AES 加密(可逆) | 加密后密文 |
BASE64 | Base64 编码 | MTIzNDU2 |
RSA2 | RSA2 加密 | 加密后密文 |
PHONE_DESENSITIZE | 手机号脱敏 | 138****1234 |
IDNO_DESENSITIZE | 身份证脱敏 | 110***********1234 |
EMAIL_DESENSITIZE | 邮箱脱敏 | a***@example.com |
NAME_DESENSITIZE | 姓名脱敏 | 张* |
EncryptedModuleType 可选值:
| 枚举值 | 说明 |
|---|---|
LOG | 仅在日志输出时加密/脱敏 |
WEB | 在 HTTP 响应中也加密/脱敏 |
验证: 调用返回 UserVO 的接口,检查 phone 字段显示为 138****1234 格式。
javax.* 迁移至 jakarta.*belongHttpStatus())@Encrypt)BaseConverter)BusinessHintException)BaseResponse 统一响应体BusinessException / SystemException)IBusinessExceptionCodeConfiguration 错误码接口BaseResponse 返回的 message 为 null 排查步骤:
BaseResponse.success(data) 时 message 默认为 null,这是正常行为BaseResponse.success("0", "操作成功", data)排查步骤:
belongHttpStatus() 返回了正确的状态码ms-common 模块)已正确配置SystemException 默认不会被 @IgnoredException 标记,可能被框架记录为 500排查步骤:
@Mapper(componentModel = "spring") 注解已添加mapstruct-processor 在编译依赖中(已由 digitalhainan-common 引入)mvn clean compile 重新生成@Encrypt 脱敏注解不生效 排查步骤:
moduleType 包含 EncryptedModuleType.WEB(仅 LOG 则只在日志中生效)StringBasisAutoConfiguration 自动加载)解决方案:
实现 IBusinessExceptionCodeConfiguration 接口即可:
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; }
}| digitalhainan-common | Spring Boot | JDK | 备注 |
|---|---|---|---|
| 3.1.0-SNAPSHOT | 3.2.4+ | 21 | 当前最新 |
| 3.0.0 ~ 3.0.1 | 3.2.4 | 21 | 适配 Spring Boot 3.x |
| 2.0 ~ 2.1.0 | 2.2.4.RELEASE | 1.8 | 新增加密注解、HTTP 状态码支持 |
| 1.0.0(master) | 2.2.4.RELEASE | 1.8 | 初始版本 |
升级提示: 从 2.x 升级到 3.x 需注意:
- JDK 需升级至 21
javax.validation迁移至jakarta.validationjavax.servlet迁移至jakarta.servlet