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

本文档用于指导在目标项目中完成
digitalhainan-tools模块集成。执行顺序:
- 先完成「13.1.2 快速集成」的全部步骤(必须)
- 根据业务需求,查阅「功能集成决策索引」选择对应路径,逐项实现
- 遇到配置问题时参考「13.1.3 配置属性参考」
- 出现异常时查阅「13.1.6 常见问题」
代码约定: 所有代码示例均包含完整
import路径和类声明,可直接用于代码生成。
digitalhainan-tools 是数字海南框架的通用工具集组件,提供日常开发中常用的工具类,包括:
Maven 坐标:
<dependency>
<groupId>cn.com.digitalhainan</groupId>
<artifactId>digitalhainan-tools</artifactId>
</dependency>前置依赖(项目 pom.xml 中必须已有):
| 依赖 | 说明 |
|---|---|
digitalhainan-pom | 父 POM,统一管理版本号 |
digitalhainan-common | 通用基础组件(EncryptEnum、EncryptedModuleType 等) |
当前版本: 3.1.0-SNAPSHOT
目标: 完成以下 4 个步骤后,项目即具备全部工具类能力。
<parent>
<groupId>cn.com.digitalhainan</groupId>
<artifactId>digitalhainan-pom</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent><dependencies>
<dependency>
<groupId>cn.com.digitalhainan</groupId>
<artifactId>digitalhainan-tools</artifactId>
</dependency>
</dependencies>创建验证类:
package com.example;
import cn.com.digitalhainan.tools.UUIDUtils;
import cn.com.digitalhainan.tools.jackson.JacksonMapper;
import cn.com.digitalhainan.tools.secret.AESUtils;
public class ToolsIntegrationVerify {
public static void main(String[] args) {
// 1. UUID 工具
String uuid = UUIDUtils.getUUId();
System.out.println("UUID: " + uuid);
// 2. JSON 序列化
String json = JacksonMapper.nonNullMapper.toJson(new java.util.HashMap<>() {{
put("key", "value");
}});
System.out.println("JSON: " + json);
// 3. AES 加解密
String encrypted = AESUtils.encrypt("hello", AESUtils.KEY);
String decrypted = AESUtils.decrypt(encrypted, AESUtils.KEY);
System.out.println("AES: " + decrypted);
System.out.println("=== digitalhainan-tools 集成成功 ===");
}
}UUIDUtils.getUUId() 正常返回 32 位字符串JacksonMapper.alwaysMapper.toJson() 正常序列化AESUtils.encrypt() / decrypt() 加解密正常ApplicationContextUtils.getContext() 返回非 nulldigitalhainan-tools 大部分工具类为纯静态方法,无需配置。以下为需要配置的场景:
模块通过 ToolsAutoConfiguration 自动注册 ApplicationContextUtils 到 Spring 容器,无需手动配置。
| 常量 | 值 | 说明 |
|---|---|---|
AESUtils.KEY | 8192553d3db81630 | AES 默认密钥(16 位) |
AESUtils.iv | d22b0a851e014f7b | AES CBC 模式 IV 向量 |
安全提示: 生产环境建议使用自定义密钥,通过
AESUtils.encrypt(data, customKey)传入。
IPUtils.parseIp() 内置了阿里云 GeoIP AK/SK,如需替换请修改 IPUtils.java 中的 accessKeyId 和 secret 常量。
| 需求场景 | 参考章节 |
|---|---|
| JSON 序列化/反序列化 | 4.1 JacksonMapper |
| AES 加解密 | 4.2 AESUtils |
| SM4 国密加解密 | 4.3 SM4Utils |
| MD5 哈希 | 4.4 Md5Utils |
| RSA 公私钥加解密 | 4.5 RSAUtils |
| 数据脱敏(身份证/手机/姓名/邮箱) | 4.6 DesensitizeUtils |
| 身份证号校验与信息提取 | 4.7 IdNoUtils |
| 日期格式化/解析/计算 | 4.8 DateUtils |
| HTTP GET/POST 请求 | 4.9 HttpUtils |
| 钉钉机器人消息 | 4.10 DingTalkRobotUtils |
| Spring 容器工具 | 4.11 ApplicationContextUtils |
| 线程变量管理 | 4.12 ThreadLocalUtils |
| 客户端 IP 获取 | 4.13 IPUtils |
| UUID 生成 | 4.14 UUIDUtils |
| 图形验证码生成 | 4.15 ImageValidateCodeUtils |
package com.example.tools;
import cn.com.digitalhainan.tools.jackson.JacksonMapper;
import com.fasterxml.jackson.databind.JavaType;
import java.util.List;
import java.util.Map;
/**
* JacksonMapper 使用示例
* 验证方式:直接运行 main 方法,观察控制台输出
*/
public class JacksonMapperDemo {
public static void main(String[] args) {
// 1. 序列化所有字段
String json = JacksonMapper.alwaysMapper.toJson(Map.of("name", "test", "age", 18));
// 2. 仅序列化非 null 字段
String nonNullJson = JacksonMapper.nonNullMapper.toJson(new UserDemo("name", null));
// 3. 反序列化为对象
UserDemo user = JacksonMapper.alwaysMapper.fromJson(
"{\"name\":\"test\",\"age\":18}", UserDemo.class);
// 4. 反序列化为 List
JavaType listType = JacksonMapper.alwaysMapper
.createCollectionType(List.class, UserDemo.class);
List<UserDemo> users = JacksonMapper.alwaysMapper.fromJson("[]", listType);
// 5. 美化输出
String pretty = JacksonMapper.alwaysMapper.beautifyJson(Map.of("key", "value"));
// 6. 下划线命名策略(驼峰 → 下划线)
String snakeJson = JacksonMapper.snakeDefaultMapper.toJson(new UserDemo("test", 18));
// 输出: {"user_name":"test","user_age":18}
// 7. 自定义 ObjectMapper
com.fasterxml.jackson.databind.ObjectMapper customMapper = JacksonMapper.createCustomMapper();
}
static class UserDemo {
public String name;
public Integer age;
public UserDemo(String name, Integer age) {
this.name = name;
this.age = age;
}
}
}package com.example.tools;
import cn.com.digitalhainan.tools.secret.AESUtils;
/**
* AES 加解密示例
* 验证方式:直接运行 main 方法,确认加解密结果一致
*/
public class AESDemo {
public static void main(String[] args) {
String data = "敏感数据";
// 使用默认密钥
String encrypted = AESUtils.encrypt(data, AESUtils.KEY);
String decrypted = AESUtils.decrypt(encrypted, AESUtils.KEY);
System.out.println("加密: " + encrypted);
System.out.println("解密: " + decrypted);
// 验证: decrypted 应等于 data
// 使用自定义密钥(必须 16 位)
String customKey = "1234567890abcdef";
String enc2 = AESUtils.encrypt(data, customKey);
String dec2 = AESUtils.decrypt(enc2, customKey);
}
}package com.example.tools;
import cn.com.digitalhainan.tools.secret.SM4Utils;
/**
* SM4 国密加解密示例
* 验证方式:直接运行 main 方法,确认加解密结果一致
*/
public class SM4Demo {
public static void main(String[] args) {
SM4Utils sm4 = new SM4Utils();
// ECB 模式(密钥 16 位)
String plainText = "国密测试数据";
String secretKey = "4370780c9a8c43e5";
String cipherText = sm4.encryptData_ECB(plainText, secretKey);
System.out.println("ECB 加密: " + cipherText);
String decrypted = sm4.decryptData_ECB(cipherText, secretKey);
System.out.println("ECB 解密: " + decrypted);
// 验证: decrypted 应等于 plainText
// CBC 模式(需要额外设置 iv)
sm4.iv = "UISwD9fW6cFh9SNS";
String cbcCipher = sm4.encryptData_CBC(plainText, secretKey);
String cbcDecrypted = sm4.decryptData_CBC(cbcCipher, secretKey);
System.out.println("CBC 解密: " + cbcDecrypted);
}
}package com.example.tools;
import cn.com.digitalhainan.tools.secret.Md5Utils;
import java.security.NoSuchAlgorithmException;
/**
* MD5 哈希示例
* 验证方式:直接运行 main 方法
*/
public class Md5Demo {
public static void main(String[] args) throws NoSuchAlgorithmException {
// 32 位 MD5
String md5_32 = Md5Utils.md5To32Bit("hello");
System.out.println("MD5(32): " + md5_32);
// 16 位 MD5
String md5_16 = Md5Utils.md5To16Bit("hello");
System.out.println("MD5(16): " + md5_16);
}
}package com.example.tools;
import cn.com.digitalhainan.tools.secret.RSAUtils;
/**
* RSA 公私钥加解密示例
* 验证方式:直接运行 main 方法
*/
public class RSADemo {
public static void main(String[] args) {
String publicKey = "你的公钥Base64字符串";
String privateKey = "你的私钥Base64字符串";
// 公钥加密
String encrypted = RSAUtils.encryptPubkey("敏感数据", publicKey);
// 私钥解密
String decrypted = RSAUtils.decryptPrikey(encrypted, privateKey);
System.out.println("解密: " + decrypted);
// 带盐值加解密
String encWithSalt = RSAUtils.encryptSaltPubkey("数据", publicKey);
String decWithSalt = RSAUtils.decryptSaltPrikey(encWithSalt, privateKey);
}
}package com.example.tools;
import cn.com.digitalhainan.tools.DesensitizeUtils;
/**
* 数据脱敏示例
* 验证方式:直接运行 main 方法,观察脱敏结果
*/
public class DesensitizeDemo {
public static void main(String[] args) {
// 身份证脱敏:保留前三后四
System.out.println(DesensitizeUtils.desensitizeIdNoFirstThreeLastFour("460100199001011234"));
// 输出: 460***********1234
// 手机号脱敏:保留前三后四
System.out.println(DesensitizeUtils.desensitizePhoneFirstThreeLastFour("13800138000"));
// 输出: 138****8000
// 姓名脱敏
System.out.println(DesensitizeUtils.desensitizeNameFirstOneLastOne("张三"));
// 输出: *三
System.out.println(DesensitizeUtils.desensitizeNameFirstOneLastOne("王小明"));
// 输出: 王*明
// 邮箱脱敏
System.out.println(DesensitizeUtils.desensitizeEmailFirstOneLaseAddress("guanyu@163.com"));
// 输出: g*****@163.com
// 通用脱敏:保留前3后5
System.out.println(DesensitizeUtils.commonDesensitize("海南省数字政府", 3, 5));
}
}package com.example.tools;
import cn.com.digitalhainan.tools.IdNoUtils;
import java.time.LocalDate;
/**
* 身份证号工具示例
* 验证方式:直接运行 main 方法
*/
public class IdNoDemo {
public static void main(String[] args) {
String idNo = "460100199001011234";
// 校验身份证号是否合法
boolean valid = IdNoUtils.isIDNumber(idNo);
// 提取性别:M=男,F=女
String gender = IdNoUtils.getGenderFromIdNo(idNo);
// 提取生日
LocalDate birthday = IdNoUtils.getBirthdayFromIdNo(idNo);
}
}package com.example.tools;
import cn.com.digitalhainan.tools.DateUtils;
import cn.com.digitalhainan.tools.DateUtils.TimeFormat;
import java.util.Date;
/**
* 日期工具示例
* 验证方式:直接运行 main 方法
*/
public class DateDemo {
public static void main(String[] args) {
// 获取当前日期时间
String now = DateUtils.getCurrentDatetime(); // yyyy-MM-dd HH:mm:ss
String today = DateUtils.getCurrentDate(); // yyyy-MM-dd
// 格式化
Date date = DateUtils.currentDate();
String formatted = DateUtils.formatDate(date); // yyyy-MM-dd
String formattedTime = DateUtils.formatDateTime(date); // yyyy-MM-dd HH:mm:ss
String custom = DateUtils.formatDate(date, TimeFormat.SHORT_DATE_PATTERN_SLASH); // yyyy/MM/dd
// 解析
Date parsed = DateUtils.parseDateTime("2024-01-15 10:30:00");
Date parsedDate = DateUtils.parseDate("2024-01-15");
// 日期加减
Date tomorrow = DateUtils.addDayWithTime(date, 1);
Date nextMonth = DateUtils.addMonthWithTime(date, 1);
Date nextYear = DateUtils.addYearWithTime(date, 1);
// 天数差
String days = DateUtils.daysBetween(parsed, date);
// 根据身份证计算年龄
int age = DateUtils.getAgeByIDNumber("460100199001011234");
// 友好时间间隔
String interval = DateUtils.getInterval(parsed); // 如 "3天前"
}
}package com.example.tools;
import cn.com.digitalhainan.tools.HttpUtils;
import java.util.HashMap;
import java.util.Map;
/**
* HTTP 请求工具示例
* 验证方式:需要网络环境
*/
public class HttpDemo {
public static void main(String[] args) {
// GET 请求
String result = HttpUtils.getRequest("https://httpbin.org/get");
// GET 带参数
Map<String, Object> params = new HashMap<>();
params.put("key", "value");
String getResult = HttpUtils.getRequest("https://httpbin.org/get", params);
// POST 请求
Map<String, Object> postParams = new HashMap<>();
postParams.put("name", "test");
String postResult = HttpUtils.postRequest("https://httpbin.org/post", postParams);
// POST 带自定义 Header
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer token");
String postResult2 = HttpUtils.postRequest("https://httpbin.org/post", headers, postParams);
}
}package com.example.tools;
import cn.com.digitalhainan.tools.dingtalk.DingTalkRobotUtils;
/**
* 钉钉机器人消息示例
* 验证方式:替换 webhook 后运行
*/
public class DingTalkDemo {
// 替换为实际的钉钉机器人 Webhook 地址
private static final String WEBHOOK = "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN";
public static void main(String[] args) {
// 发送文本消息
DingTalkRobotUtils.sendText(WEBHOOK, "测试消息:系统启动成功");
// 发送 Markdown 消息
DingTalkRobotUtils.sendMarkdown(WEBHOOK, "部署通知",
"### 部署成功\n- 应用:my-app\n- 环境:production");
// 发送 Link 消息
DingTalkRobotUtils.sendLink(WEBHOOK, "标题", "描述内容",
"https://example.com", "https://example.com/logo.png");
// 加签模式(安全设置选择了加签时使用)
String secret = "SECxxxxxx";
String signedUrl = DingTalkRobotUtils.buildSignUrl(WEBHOOK, secret);
DingTalkRobotUtils.sendText(signedUrl, "加签消息");
}
}package com.example.tools;
import cn.com.digitalhainan.tools.spring.ApplicationContextUtils;
import org.springframework.context.ApplicationEvent;
import org.springframework.stereotype.Service;
/**
* Spring 容器工具示例
* 验证方式:在 Spring 容器启动后调用
*/
@Service
public class ApplicationContextDemo {
public void demo() {
// 获取 Spring 上下文
var context = ApplicationContextUtils.getContext();
// 获取 Bean
var bean = ApplicationContextUtils.getContext().getBean(SomeService.class);
// 发布事件
ApplicationContextUtils.publishEvent(new ApplicationEvent(this) {});
}
static class SomeService {}
}package com.example.tools;
import cn.com.digitalhainan.tools.thread.ThreadLocalUtils;
/**
* 线程变量工具示例
* 验证方式:直接运行 main 方法
*/
public class ThreadLocalDemo {
public static void main(String[] args) {
// 设置值
ThreadLocalUtils.set("userId", "12345");
ThreadLocalUtils.set("tenantId", "T001");
// 获取值
String userId = ThreadLocalUtils.get("userId");
System.out.println("userId: " + userId);
// 移除单个值
ThreadLocalUtils.remove("userId");
// 清除所有(请求结束时必须调用,防止内存泄漏)
ThreadLocalUtils.remove();
}
}package com.example.tools;
import cn.com.digitalhainan.tools.IPUtils;
import com.aliyuncs.geoip.model.v20200101.DescribeIpv4LocationResponse;
import jakarta.servlet.http.HttpServletRequest;
/**
* IP 地址工具示例
* 验证方式:在 Spring MVC Controller 中调用
*/
public class IPDemo {
public void demo(HttpServletRequest request) {
// 获取客户端真实 IP(支持代理穿透)
String clientIp = IPUtils.getIpAddr(request);
// 获取本机 IP
String localIp = IPUtils.getLocalIp();
// 阿里云 GeoIP 解析(需要网络)
try {
DescribeIpv4LocationResponse response = IPUtils.parseIp("8.8.8.8");
System.out.println("国家: " + response.getCountry());
System.out.println("省份: " + response.getProvince());
System.out.println("城市: " + response.getCity());
} catch (Exception e) {
e.printStackTrace();
}
}
}package com.example.tools;
import cn.com.digitalhainan.tools.UUIDUtils;
/**
* UUID 工具示例
* 验证方式:直接运行 main 方法
*/
public class UUIDDemo {
public static void main(String[] args) {
// 生成 32 位无横线的 UUID
String uuid = UUIDUtils.getUUId();
System.out.println("UUID: " + uuid);
// 输出示例: a1b2c3d4e5f6789012345678901234ab
}
}package com.example.tools;
import cn.com.digitalhainan.tools.ImageValidateCodeUtils;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 图形验证码示例
* 验证方式:在 Spring MVC Controller 中调用
*/
public class CaptchaDemo {
public void generateCaptcha(HttpServletResponse response) throws IOException {
// 生成 4 位验证码
String code = ImageValidateCodeUtils.generateVerifyCode(4);
// 输出到响应流(宽 200,高 80)
response.setContentType("image/jpeg");
ImageValidateCodeUtils.outputImage(200, 80, response.getOutputStream(), code);
// 将验证码存入 Session 或 Redis 用于后续校验
// session.setAttribute("captcha", code);
}
}原因: 密钥长度不是 16 位,或数据编码问题。 解决: 确保密钥为 16 位字符串,使用 AESUtils.KEY 或自定义 16 位密钥。
原因: 密钥长度不是 16 位(SM4 要求 128 位密钥)。 解决: 确保 secretKey 参数为 16 位字符串。
原因: JSON 中的日期格式与默认格式不匹配。 解决: 默认格式为 yyyy-MM-dd HH:mm:ss.SSS,确保 JSON 中日期格式一致,或使用 createCustomMapper() 自定义格式。
解决: HttpUtils.buildHttpClient() 已内置信任所有证书的 SSL 配置,正常情况下不会报证书错误。如仍报错,检查 JDK 安全策略配置。
原因: Spring 容器尚未初始化完成,或 ToolsAutoConfiguration 未被扫描到。 解决: 确保 cn.com.digitalhainan.tools.conf 包在 Spring Boot 组件扫描范围内。
原因: 线程池复用线程时未清理 ThreadLocal。 解决: 在请求结束时(如 Filter/Interceptor 的 afterCompletion)调用 ThreadLocalUtils.remove()。
| digitalhainan-tools | Spring Boot | JDK | 说明 |
|---|---|---|---|
| 3.1.0-SNAPSHOT | 3.x | 21 | 当前版本,Jakarta EE 命名空间 |
| 3.0.0 ~ 3.0.1 | 3.x | 21 | Spring Boot 3.x 适配 |
| 2.0.0 ~ 2.1.0 | 2.x | 8/11 | javax 命名空间 |