VO字段翻译实现方案示例
VO字段翻译实现方案示例
背景介绍
当我们在查看一些公司中小系统产品的源码时,我们会发现系统实体VO总会有一些固定出现的字段,例如:code、createBy、updateBy......,里面存放的是一些唯一标识,例如:id或者unCode的值。其中createBy、updateBy这两个字段我们在接口返回业务数据时,需要将这些唯一标识通过“翻译”的手段将其变成用户能够看得懂的数据,以便前端展示在页面,例如:username、name等,本案例就是通过介绍一个VO字段翻译抽象类的实现方案来为新手读者扩宽设计思路。
方案前提
本方案最佳实践中需要要求有以下前提,如果是改造系统,也需要根据以下前提来对系统进行完善和改造:
每个实体都应该有固定的业务基础字段,本例中的示例业务基础字段有:业务主键-unCode、创建人-createBy、修改人-updateBy,数据库创建数据表时也要有这些字段,示例建表语句如下:
CREATE TABLE `user` ( `un_code` char(32) NOT NULL COMMENT '业务主键', -- 其他业务字段 `create_by` varchar(255) NOT NULL COMMENT '记录创建人,保存account', `create_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '记录创建时间', `update_by` varchar(255) NOT NULL COMMENT '记录更新人,保存account', `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '记录最近更新时间' );提示
此处数据库建表语句中,关于字段和表名采用的是
蛇形命名法则,和下方的基类字段需要一一对应,部分数据库工具允许蛇形和驼峰命名的字段形成映射关系,仅此数据库字段使用蛇形命名,VO类字段采用驼峰命名,可以根据实际情况进行修改。在编码时,以Java代码为例,需要将这些基础业务字段封装在一个“基类”
QueryBaseVO中,如下所示:import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.Date; /** * @author think * @date 2025/6/10 22:19 * @description: 数据查询基础模型 */ @Data @Schema(description = "数据查询基础模型") public class QueryBaseVO { @Schema(description = "业务主键") private String unCode; @Schema(description = "记录创建人") private String createBy; @Schema(description = "记录创建时间") private Date createTime; @Schema(description = "记录更新人") private String updateBy; @Schema(description = "记录最近更新时间") private Date updateTime; }相关信息
上方的示例代码中的注解作用解释如下:
- @Data:帮助自动生成该类的
Getter和Setter,如果不使用该注解,可手动实现Getter和Setter; - @Schema:用于将
description属性中的值渲染在swagger文档上,形成接口文档,可以选择不使用该注解。
- @Data:帮助自动生成该类的
所有的业务查询实体VO类都继承上方的基类
QueryBaseVO,例如:import cool.webstudy.tmc.constant.enums.EnabledStatusFlagEnum; import cool.webstudy.tmc.constant.enums.user.RoleEnum; import cool.webstudy.tmc.model.vo.QueryBaseVO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * @author think * @date 2025/6/15 23:02 * @description: 查询用户信息返回值 */ @Data public class QueryUserInfoVO extends QueryBaseVO { @Schema(description = "用户账号") private String account; @Schema(description = "用户名称") private String username; @Schema(description = "用户角色") private RoleEnum role; @Schema(description = "用户状态") private EnabledStatusFlagEnum statusFlag; }
方案实现
将翻译逻辑封装成一个抽象类,以便在需要的地方进行使用,示例的方案中,由于笔者项目设计有以下原则:
- 字段翻译逻辑需要放在Controller层实现;
- 如果是微服务的项目,每个服务接口业务基础字段的翻译逻辑,都放在各自的服务中实现;
因此抽象类的设计和具体使用方法如下。
抽象类
import cool.webstudy.tmc.model.vo.user.QueryUserInfoVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author think
* @date 2025/8/4 23:16
* @description: 抽象翻译字段服务
*/
@Component
@Slf4j
public abstract class AbstractTranslateFieldService<QueryBaseVO> {
/**
* 模板方法:执行单个实体字段翻译
*/
protected final void translateEntity(QueryBaseVO entity, Map<String, QueryUserInfoVO> userRefInfoMap) {
if (entity == null) return;
// 复用列表处理逻辑,将单个实体包装成列表
List<QueryBaseVO> entityList = new ArrayList<>();
entityList.add(entity);
translateEntityList(entityList,userRefInfoMap);
}
/**
* 模板方法:执行列表字段翻译
*/
protected final void translateEntityList(List<? extends QueryBaseVO> entities,Map<String, QueryUserInfoVO> userRefInfoMap){
if (entities == null || entities.isEmpty() || userRefInfoMap == null || userRefInfoMap.isEmpty()) return;
Map<QueryBaseVO, Map<String, String>> entityFieldMap = new HashMap<>();
for (QueryBaseVO entity : entities) {
if (entity != null) {
Map<String, String> fieldMap = getTranslateFieldMap(entity);
entityFieldMap.put(entity, fieldMap);
}
}
// 批量设置翻译后的值
for (Map.Entry<QueryBaseVO, Map<String, String>> entry : entityFieldMap.entrySet()) {
QueryBaseVO entity = entry.getKey();
Map<String, String> translateFields = entry.getValue();
translateFields.forEach((fieldName, userUnCode) -> {
try {
//通过反射获取字段
Field field = findField(entity.getClass(), fieldName);
field.setAccessible(true);
QueryUserInfoVO userInfoVO = userRefInfoMap.get(userUnCode);
if (userInfoVO != null) {
String translatedName = getTranslatedName(userInfoVO);
field.set(entity, translatedName);
}
} catch (Exception e) {
log.error("字段翻译异常:" + e.getMessage(), e);
}
});
}
}
/**
* 获取需要翻译的字段映射 (字段名 -> 用户编码)
*/
private Map<String, String> getTranslateFieldMap(QueryBaseVO entity) {
Map<String, String> fieldMap = new HashMap<>();
// 自动检测 createBy/updateBy 字段(支持多种命名方式)
try {
// 尝试多种可能的字段名
String[] createByNames = {"createBy", "create_by", "createdBy"};
String[] updateByNames = {"updateBy", "update_by", "updatedBy"};
for (String fieldName : createByNames) {
try {
Field field = findField(entity.getClass(), fieldName);
if (field != null && field.getType() == String.class) {
field.setAccessible(true);
Object value = field.get(entity);
if (value != null) {
fieldMap.put(fieldName, (String) value);
break; // 找到就退出
}
}
} catch (Exception ignored) {}
}
for (String fieldName : updateByNames) {
try {
Field field = findField(entity.getClass(), fieldName);
if (field != null && field.getType() == String.class) {
field.setAccessible(true);
Object value = field.get(entity);
if (value != null) {
fieldMap.put(fieldName, (String) value);
break; // 找到就退出
}
}
} catch (Exception ignored) {}
}
} catch (Exception ignored) {}
// 子类可添加额外字段
addExtraTranslateFields(entity, fieldMap);
return fieldMap;
}
// 递归查找字段(包括父类)
private Field findField(Class<?> clazz, String fieldName) {
while (clazz != null) {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
return null;
}
//---------------- 需要子类实现的抽象方法 ----------------
/**
* 获取翻译后的名称 (如用户姓名)
*/
protected abstract String getTranslatedName(QueryUserInfoVO userInfoVO);
/**
* 添加额外的翻译字段
*/
protected void addExtraTranslateFields(QueryBaseVO entity, Map<String, String> fieldMap) {
// 默认空实现,子类按需覆盖
}
}具体使用
//导入的包太多,移除掉了导包语句
/**
* @author think
* @date 2025/7/24 23:14
* @description: 字典值管理接口
*/
@RestController
@RequestMapping("/api/dict/data")
@Tag(name = "字典值管理")
public class DictDataController extends AbstractTranslateFieldService<QueryBaseVO> {
@Autowired
private IDictDataService dictDataService;
@Autowired
private UserFeignService userFeignService;
@Operation(summary = "查询字典值信息详情")
@PostMapping("/detail-info")
public Result<QueryDictDataInfoVO> detailInfo(@RequestBody BaseDTO dto) {
QueryDictDataInfoVO queryDictDataInfoVO = dictDataService.queryDetailInfo(dto);
//通过远程调用获取用户信息,其中Map的key为用户业务主键,value为用户信息VO
Result<Map<String, QueryUserInfoVO>> userInfoVOMap = userFeignService.queryUserRefTransInfoMap(dto);
//翻译一个VO的示例
translateEntity(queryDictDataInfoVO,userInfoVOMap.getData());
return Result.success(queryDictDataInfoVO);
}
@Operation(summary = "分页查询字典值信息列表")
@PostMapping("/page-query/list")
public Result<Page<PaginationQueryDictDataListVO>> pageQueryList(@RequestBody PaginationQueryDictDataListDTO dto) {
Page<PaginationQueryDictDataListVO> page = dictDataService.paginationQueryList(dto);
//通过远程调用获取用户信息,其中Map的key为用户业务主键,value为用户信息VO
Result<Map<String, QueryUserInfoVO>> userInfoVOMap = userFeignService.queryUserRefTransInfoMap(dto);
//翻译VO列表的示例
translateEntityList(page.getRecords(),userInfoVOMap.getData());
return Result.success(page);
}
@Override
protected String getTranslatedName(QueryUserInfoVO userInfoVO) {
//此处可以设置将用户的什么信息翻译至createBy和updateBy字段中,这里是翻译成用户名username的值
return userInfoVO.getUsername();
}
}本案例由于是微服务的项目,因此获取用户信息是通过Feign调用接口来实现,如果是单体应用,可以将以下代码更换成实际项目的用户信息管理接口:
private UserFeignService userFeignService;以上的具体使用示例中,存在其他的业务代码,读者可以根据注释来理解每一行代码的作用,没有注解解释的代码行一般不作为本次案例的核心代码。
后语
示例中只示范了翻译createBy和updateBy两个字段,但是细心的读者已经发现翻译服务抽象类中包含了扩展的逻辑,因此我们可以举一反三扩展我们的翻译服务类,实现VO类其他字段的逻辑,这些就留给以后的我们共同探索了。
方案中示例是在笔者自己定义的原则和规范中实现,因此各位读者可以根据自己的项目需求和习惯来进行调整。
各位读者如果觉得读后觉得有收获,可以帮忙多多分享,如果有更好的建议,可以在主页寻找笔者联系方式,我们可以共同讨论,感谢各位!