Skip to content
返回

后端脚手架

脚手架

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

概述
后端脚手架

digitalhainan-tools ​

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

执行顺序:

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

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


13.1.1 模块简介 ​

digitalhainan-tools 是数字海南框架的通用工具集组件,提供日常开发中常用的工具类,包括:

  • 加密解密 — AES / SM4(国密)/ MD5 / SHA256 / RSA / DES3 / SM2
  • 数据脱敏 — 身份证、手机号、姓名、邮箱脱敏
  • JSON 序列化 — JacksonMapper(多实例、Java 8 时间、加密/脱敏序列化)
  • 日期处理 — DateUtils / NewDateUtils(格式化、解析、年龄计算、时间差)
  • HTTP 请求 — HttpUtils(GET/POST、连接池、HTTPS)
  • 钉钉机器人 — DingTalkRobotUtils(text/link/markdown/actionCard/feedCard)
  • 图形验证码 — ImageValidateCodeUtils / captch 模块(自定义验证码)
  • 身份证工具 — IdNoUtils(校验、性别提取、生日提取)
  • Spring 上下文 — ApplicationContextUtils(获取 Bean、发布事件)
  • 线程变量 — ThreadLocalUtils(线程内数据传递)
  • IP 工具 — IPUtils(客户端 IP 获取、阿里云 GeoIP 解析)
  • UUID / 随机数 / 文件处理 / XML / 反射 等通用工具
  • 地图工具 — 高德地图地址转坐标
  • 操作日志 — @OperationLog 注解 + AOP 拦截

Maven 坐标:

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

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

依赖说明
digitalhainan-pom父 POM,统一管理版本号
digitalhainan-common通用基础组件(EncryptEnum、EncryptedModuleType 等)

当前版本: 3.1.0-SNAPSHOT


13.1.2 快速集成 ​

目标: 完成以下 4 个步骤后,项目即具备全部工具类能力。

13.1.2.1 确认父 POM 已引入 digitalhainan-pom ​

xml
<parent>
    <groupId>cn.com.digitalhainan</groupId>
    <artifactId>digitalhainan-pom</artifactId>
    <version>3.1.0-SNAPSHOT</version>
</parent>

13.1.2.2 添加依赖 ​

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

13.1.2.3 验证集成 ​

创建验证类:

java
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 集成成功 ===");
    }
}

13.1.2.4 验证清单 ​

  • [ ] UUIDUtils.getUUId() 正常返回 32 位字符串
  • [ ] JacksonMapper.alwaysMapper.toJson() 正常序列化
  • [ ] AESUtils.encrypt() / decrypt() 加解密正常
  • [ ] Spring 容器启动后 ApplicationContextUtils.getContext() 返回非 null

13.1.3 配置属性参考 ​

digitalhainan-tools 大部分工具类为纯静态方法,无需配置。以下为需要配置的场景:

13.1.3.1 自动配置 ​

模块通过 ToolsAutoConfiguration 自动注册 ApplicationContextUtils 到 Spring 容器,无需手动配置。

13.1.3.2 AES 默认密钥 ​

常量值说明
AESUtils.KEY8192553d3db81630AES 默认密钥(16 位)
AESUtils.ivd22b0a851e014f7bAES CBC 模式 IV 向量

安全提示: 生产环境建议使用自定义密钥,通过 AESUtils.encrypt(data, customKey) 传入。

13.1.3.3 IP 解析(阿里云 GeoIP) ​

IPUtils.parseIp() 内置了阿里云 GeoIP AK/SK,如需替换请修改 IPUtils.java 中的 accessKeyId 和 secret 常量。


13.1.4 核心功能使用 ​

功能集成决策索引 ​

需求场景参考章节
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

13.1.4.1 JacksonMapper — JSON 序列化 ​

java
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;
        }
    }
}

13.1.4.2 AESUtils — AES 加解密 ​

java
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);
    }
}

13.1.4.3 SM4Utils — 国密 SM4 加解密 ​

java
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);
    }
}

13.1.4.4 Md5Utils — MD5 哈希 ​

java
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);
    }
}

13.1.4.5 RSAUtils — RSA 公私钥加解密 ​

java
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);
    }
}

13.1.4.6 DesensitizeUtils — 数据脱敏 ​

java
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));
    }
}

13.1.4.7 IdNoUtils — 身份证号工具 ​

java
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);
    }
}

13.1.4.8 DateUtils — 日期工具 ​

java
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天前"
    }
}

13.1.4.9 HttpUtils — HTTP 请求工具 ​

java
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);
    }
}

13.1.4.10 DingTalkRobotUtils — 钉钉机器人消息 ​

java
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, "加签消息");
    }
}

13.1.4.11 ApplicationContextUtils — Spring 容器工具 ​

java
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 {}
}

13.1.4.12 ThreadLocalUtils — 线程变量管理 ​

java
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();
    }
}

13.1.4.13 IPUtils — IP 地址工具 ​

java
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();
        }
    }
}

13.1.4.14 UUIDUtils — UUID 生成 ​

java
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
    }
}

13.1.4.15 ImageValidateCodeUtils — 图形验证码 ​

java
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);
    }
}

13.1.5 版本变更记录 ​

v3.1.0(当前版本) ​

  • SM4 工具更新,支持 ECB/CBC 模式、十六进制密钥
  • 新增依赖管理

v3.0.1 ​

  • SM4 工具类重构

v3.0.0 ​

  • 初始化 3.x 版本
  • Jackson 新增方法
  • 修复 JSON Serializer
  • 加解密工具更新

v2.1.0 ​

  • 新增加解密工具
  • Groovy 依赖更新

v2.0.0 ​

  • 初始版本

13.1.6 常见问题 ​

Q1:AES 加解密返回 null? ​

原因: 密钥长度不是 16 位,或数据编码问题。 解决: 确保密钥为 16 位字符串,使用 AESUtils.KEY 或自定义 16 位密钥。

Q2:SM4 加解密抛出 RuntimeException? ​

原因: 密钥长度不是 16 位(SM4 要求 128 位密钥)。 解决: 确保 secretKey 参数为 16 位字符串。

Q3:JacksonMapper 反序列化 LocalDateTime 失败? ​

原因: JSON 中的日期格式与默认格式不匹配。 解决: 默认格式为 yyyy-MM-dd HH:mm:ss.SSS,确保 JSON 中日期格式一致,或使用 createCustomMapper() 自定义格式。

Q4:HttpUtils 请求 HTTPS 报证书错误? ​

解决: HttpUtils.buildHttpClient() 已内置信任所有证书的 SSL 配置,正常情况下不会报证书错误。如仍报错,检查 JDK 安全策略配置。

Q5:ApplicationContextUtils.getContext() 返回 null? ​

原因: Spring 容器尚未初始化完成,或 ToolsAutoConfiguration 未被扫描到。 解决: 确保 cn.com.digitalhainan.tools.conf 包在 Spring Boot 组件扫描范围内。

Q6:ThreadLocalUtils 导致内存泄漏? ​

原因: 线程池复用线程时未清理 ThreadLocal。 解决: 在请求结束时(如 Filter/Interceptor 的 afterCompletion)调用 ThreadLocalUtils.remove()。


13.1.7 版本兼容性矩阵 ​

digitalhainan-toolsSpring BootJDK说明
3.1.0-SNAPSHOT3.x21当前版本,Jakarta EE 命名空间
3.0.0 ~ 3.0.13.x21Spring Boot 3.x 适配
2.0.0 ~ 2.1.02.x8/11javax 命名空间

Power By 数字海南