跳到主要内容

dict-trans-spring-boot-starter

自动收集自 lib/tool-starter/dict-trans-spring-boot-starter/README.md

基于 Spring Boot AOP 的字典翻译自动翻译组件。在 Controller 方法返回值时,自动扫描对象上标注的 @Dict 注解,将字典编码(code)翻译为可读的文本(label/name),支持内置字典表和任意数据库表两种翻译数据源。

特性

  • 零侵入:基于 AOP 拦截,只需在 VO 字段上加 @Dict 注解,Controller 方法上加 @Dict 即可自动翻译
  • 内置字典翻译:通过 dictCode 查询系统内置字典表,code → label
  • 任意表翻译:指定表名、code 列、name 列,直接从任意数据库表翻译
  • 多值翻译:字段值支持逗号分隔的多 code 翻译(如 "1,2,3""启用,禁用,删除"
  • 嵌套对象递归翻译:自动递归遍历嵌套对象和集合中的 @Dict 字段
  • ByteBuddy 动态子类:普通 VO 翻译后的字段通过 ByteBuddy 动态生成子类注入,不修改原始 VO 结构
  • Jimmer immutable 实体支持:Jimmer 实体不会被动态继承或原地修改,会输出已加载属性和翻译字段组成的 Map
  • 策略模式:根据返回值类型(单个对象 / Collection / String)自动选择翻译策略
  • Spring Boot AutoConfiguration:引入依赖即自动生效

快速开始

1. 引入依赖

// build.gradle.kts
dependencies {
implementation("site.addzero:dict-trans-spring-boot-starter:<version>")
}

2. 实现 TransApi 接口

提供字典数据源查询能力,组件通过此接口获取翻译数据:

@Component
class MyTransApi : TransApi {

@Autowired
private lateinit var dictService: DictService

@Autowired
private lateinit var jdbcTemplate: JdbcTemplate

/**
* 内置字典批量翻译
* @param dictCodes 逗号分隔的字典编码,如 "user_status,sex"
* @param keys 逗号分隔的字典项 value,如 "1,2,0"
*/
override fun translateDictBatchCode2name(dictCodes: String, keys: String?): List<DictModel> {
// 从你的字典表中查询,返回 DictModel(dictCode, value, label)
return dictService.findDictItems(dictCodes, keys)
}

/**
* 任意表批量翻译
* @param table 表名
* @param text 显示列名(name 列)
* @param code 编码列名
* @param keys 逗号分隔的编码值
*/
override fun translateTableBatchCode2name(
table: String, text: String, code: String, keys: String
): List<Map<String, Any?>> {
val keyList = keys.split(",").map { "'$it'" }.joinToString(",")
val sql = "SELECT $code, $text FROM $table WHERE $code IN ($keyList)"
return jdbcTemplate.queryForList(sql)
}
}

3. 配置 Controller 扫描包(可选)

默认扫描 site.addzero 包下的 Controller,可通过配置修改:

# application.yml
expression:
scan:
controller:
pkg: com.example # 你的 Controller 所在包
# 也可以自定义完整表达式,支持 ${pkg} 占位符
# expression: "execution(* ${pkg}..*Controller*+.*(..))"

4. 在 VO 上使用 @Dict 注解

@Dict 注解支持 @Repeatable,可以在同一字段上标注多次翻译。

注解参数说明

参数说明默认值
value内置字典编码(等同于 dicCode""
dicCode内置字典编码(与 value 二选一,优先使用 dicCode""
tab任意表翻译 - 表名""
codeColumn任意表翻译 - 编码列名(默认取字段名)""
nameColumn任意表翻译 - 显示列名""
whereCondition附加 WHERE 条件(预留)""
serializationAlias翻译后字段的序列化别名(优先作为输出字段名)""

翻译模式判定规则

  • dicCode/value 非空,且 tabcodeColumnnameColumn 全为空 → 内置字典翻译
  • 当指定了 tab 等参数 → 任意表翻译

使用示例

示例 1:内置字典翻译

data class UserVO(
val id: Long,
val name: String,

// 使用内置字典 "user_status" 翻译,字段值 "1" → "启用"
@Dict(dicCode = "user_status")
val status: String,

// 多值翻译:字段值 "1,2" → "男,女"
@Dict(value = "sex")
val gender: String,
)

示例 2:任意表翻译

data class OrderVO(
val id: Long,

// 从 department 表翻译,department_id 列匹配,显示 department_name 列
@Dict(tab = "department", codeColumn = "department_id", nameColumn = "department_name")
val deptId: String,
)

示例 3:同一字段多次翻译

data class ReportVO(
val id: Long,

// 同一个字段可以同时标注内置字典和任意表翻译
@Dict(dicCode = "region_code")
@Dict(tab = "sys_region", codeColumn = "region_id", nameColumn = "region_name")
val regionId: String,
)

示例 4:Jimmer 实体字段翻译

Jimmer Kotlin 实体是 interface,实体属性运行时对应 immutable 属性和 getter 方法,不适合走普通 VO 的字段注入路径。 在 Jimmer 实体属性上可以直接使用 @Dict(...)

@Entity
interface SysUser {
@Id
val id: Long

@Dict(dicCode = "user_status", serializationAlias = "statusName")
val status: String
}

Controller 返回 Jimmer 实体或实体集合并在方法上标注 @Dict 时,starter 会读取已加载属性上的 @Dict,输出原实体字段,并追加翻译字段,例如 statusName 或默认的 status_dictText。 对于 Page<R>Result<List<R>> 这类普通包装对象,只要内部 R 是已加载的 Jimmer 实体,也会递归翻译为带字典文本字段的 Map

示例 5:Controller 方法标注 @Dict

在 Controller 方法上加 @Dict 注解即可触发 AOP 翻译:

@RestController
@RequestMapping("/api/user")
class UserController {

@GetMapping("/list")
@Dict // 触发返回值的字典翻译
fun list(): List<UserVO> {
return userService.findUsers()
}

@GetMapping("/{id}")
@Dict // 单个对象也会被翻译
fun getById(@PathVariable id: Long): UserVO {
return userService.findById(id)
}
}

示例 6:手动调用翻译工具函数

不依赖 AOP 时,也可以在 Service 层手动翻译:

import site.addzero.aop.dicttrans.util.code2name
import site.addzero.aop.dicttrans.util.code2nameT
import site.addzero.aop.dicttrans.util.name2code
import site.addzero.aop.dicttrans.util.name2codeT

// 批量翻译 List
val users: MutableList<UserVO> = userService.findUsers()
code2name(users) // 原地翻译

// 单个对象翻译
val user: UserVO = userService.findById(1)
val translated = code2nameT(user)

// 反向翻译:name → code
name2code(users)
name2codeT(user)

自定义翻译策略

组件内置三种策略,按优先级匹配:

策略匹配类型说明
StringStrategyString直接对字符串值做字典翻译
CollectionStrategyCollection<*>遍历集合内每个元素,批量翻译
TStrategy任意 POJO 对象将单个对象包装为 List 后委托集合策略

如需扩展,可以实现 TransStrategy<T> 接口并注册为 Spring Bean:

@Component
class MyCustomStrategy : TransStrategy<MySpecialType> {
override fun trans(t: MySpecialType): MySpecialType {
// 自定义翻译逻辑
return t
}
}

自定义类型黑名单

TPredicate 接口用于定义哪些类型不应被当作 POJO 递归翻译。默认实现 DefaultTPredicate 已排除常见基本类型。如需自定义:

@Component
class MyTPredicate : TPredicate {
override fun tBlackList(): List<Class<out Any>> {
return listOf(
String::class.java,
Number::class.java,
// ... 添加你不需要翻译的类型
MyImmutableValueObject::class.java,
)
}
}

使用 @ConditionalOnMissingBean,自定义实现会自动替换默认实现。

翻译后字段命名规则

翻译后的字段写入到动态生成的子类中,字段名按以下优先级确定:

  1. serializationAlias(注解参数,最高优先级)
  2. nameColumn 的驼峰形式
  3. {原始字段名}_dictText(默认后缀)

架构概览

DictAopConfiguration (AutoConfiguration)
├── DictAdvisor (AOP 拦截器)
│ └── MethodInterceptor → 拦截 @Dict 标注的 Controller 方法
├── TransStrategySelector (策略选择器)
│ ├── StringStrategy → String 类型翻译
│ ├── CollectionStrategy → Collection 类型翻译
│ └── TStrategy → POJO 对象翻译
├── TransApi (SPI 接口,用户实现)
│ ├── translateDictBatchCode2name() → 内置字典查询
│ └── translateTableBatchCode2name() → 任意表查询
└── TPredicate (类型黑名单,可自定义)
└── DefaultTPredicate (默认实现)

注意事项

  • @Dict 注解需要保持 RUNTIME,普通 VO 翻译字段由 ByteBuddy 运行时动态生成,Jimmer 实体翻译结果输出为 Map
  • 集合超过 1000 条时会跳过翻译并打印警告日志
  • AOP 切面的切点表达式由 ScanControllerProperties 配置,确保 Controller 包路径正确
  • 翻译过程为原地修改(通过 ByteBuddy 子类注入新字段),原始 VO 类不会被修改

Maven / Gradle

已发布至 Maven Central。

implementation("site.addzero:dict-trans-spring-boot-starter:latest version")
<dependency>
<groupId>site.addzero</groupId>
<artifactId>dict-trans-spring-boot-starter</artifactId>
<version>latest version</version>
</dependency>