 VO 对象转换、数据翻译
VO 对象转换、数据翻译
  本小节,我们来讲解 VO 的对象转换、数据翻译的功能。注意,这里的 VO 是泛指 Java POJO 对象,也可以是 DTO、BO 等等。
# 1. 对象转换
对象转换,指的是 A 类型对象,转换成 B 类型对象。例如说,我们有一个 UserDO 类型对象,需要转换成 UserVO 或者 UserDTO 类型对象。
市面上有很多的对象转换工具,例如说 MapStruct、Dozer、各种 BeanUtils、BeanCopier 等等。目前我们提供了 MapStruct、BeanUtils 两种解决方案。
相比来说,MapStruct 性能会略好于 BeanUtils,但是相比数据库操作带来的耗时来说,基本可以忽略不计。因此,一般情况下,建议使用 BeanUtils 即可。
# 1.1 MapStruct
项目使用 MapStruct (opens new window) 实现 VO、DO、DTO 等对象之间的转换。
如果你没有学习过 MapStruct,需要阅读下 《芋道 Spring Boot 对象转换 MapStruct 入门》 (opens new window) 文章。
在每个 yudao-module-xxx 模块的 convert 包下,可以看到各个业务的 Convert 接口,如下图所示:

# 1.2 BeanUtils
项目提供了 BeanUtils (opens new window) 类,它是基于 Hutool 的 BeanUtil 封装一层。如下图所示:

疑问:为什么不直接使用 Hutool BeanUtil,而是额外封装一层呢?
① 方便替换实现。例如说,你想把 Hutool BeanUtil 换成 Spring BeanUtil、BeanCopier 等时,只需要修改它。
② 特性增强。额外支持 List、Page 对象的转换,也支持 Consumer 进一步转化。
1、在简单场景,直接使用 BeanUtils 的 #toBean(...) 方法,如下图所示:

2、在复杂场景,可以通过 Consumer 进一步拼接,如下图所示:
图片纠错:最新版本不区分 yudao-module-erp-api 和 yudao-module-erp-biz 子模块,代码直接合并到 yudao-module-erp 模块的 src 目录下,更适合单体项目

当然,如果 Consumer 的逻辑比较复杂,又希望 Controller 代码精简一点,可以放到对应的 Convert 类里,如下图所示:

# 2. 数据翻译
数据翻译,指的是将 A 类型对象的某个字段,“翻译”成 B 类型对象的某个字段。例如说,我们有一个 UserVO 的 deptId 字段,读取对应 DeptDO  的 name 字段,最终设置到 UserVO 的 deptName 字段。
一般来说,目前有两种方案:
- 方案一:数据库 SQL 联表查询,可见 《MyBatis 联表&分页查询》 文档
- 方案二:数据库多次单表查询,然后在 Java 代码中进行数据拼接(翻译)。其实就是「1.2 BeanUtils」的“复杂场景”。如下图所示:
图片纠错:最新版本不区分 yudao-module-erp-api 和 yudao-module-erp-biz 子模块,代码直接合并到 yudao-module-erp 模块的 src 目录下,更适合单体项目

项目里,大多数采用“方案二”,因为这样可以减少数据库的压力,避免 SQL 过于复杂,也方便后续维护。
不过如果你觉得“方案二”比较麻烦,我们也集成了 easy-trans (opens new window) 框架,一个注解,搞定数据翻译。
下面,我们来分场景,看看具体如何使用!
# 2.1 场景一:模块内翻译
模块内翻译,指的是在同一个模块内,进行数据翻译。例如说,OperateLogRespVO 属于 yudao-module-system 模块,需要读取模块内的 AdminUserDO 数据。
① 第一步,给 OperateLogRespVO 实现 com.fhs.core.trans.vo.VO 接口。
② 第二步,给 OperateLogRespVO 的 deptId 字段,添加 @Trans 注解,如下图所示:

- type属性:使用- TransType.SIMPLE简单翻译,使用 MyBatis Plus
- target属性:目标 DO 实体的类,例如说- AdminUserDO.class
- fields属性:读取 DO 实体的字段,例如说- nickname。如果是多个字段,它也是个数组
- ref属性:设置 VO 类的字段,例如说- userNickname。如果是多个字段,可以使用- refs
更多关于 @Trans 注解的讲解,可见 《Trans 注解详解(必读)》 (opens new window) 文档。
③ 第三步,给需要翻译的 Controller 接口,添加 @@TransMethodResult 注解,代码如下所示:
@Tag(name = "管理后台 - 操作日志")
@RestController
@RequestMapping("/system/operate-log")
@Validated
public class OperateLogController {
    @GetMapping("/page")
    @Operation(summary = "查看操作日志分页列表")
    @PreAuthorize("@ss.hasPermission('system:operate-log:query')")
    @TransMethodResult // 【重要】这里是关键!!!
    public CommonResult<PageResult<OperateLogRespVO>> pageOperateLog(@Valid OperateLogPageReqVO pageReqVO) {
        PageResult<OperateLogDO> pageResult = operateLogService.getOperateLogPage(pageReqVO);
        return success(BeanUtils.toBean(pageResult, OperateLogRespVO.class));
    }
    
}
# 2.2 场景二:跨模块翻译
友情提示:
由于最新版本 biz 和 api 进行合并了,所以「跨模块翻译」,也可以使用「模块内翻译」的方式!!!
为什么呢?可以思考下,嘿嘿~
跨模块翻译,指的是在不同模块,进行数据翻译。例如说,CrmProductRespVO 属于 yudao-module-crm 模块,需要读取 yudao-module-system 模块的 AdminUserDO 数据。
① 第一步,给 CrmProductRespVO 实现 com.fhs.core.trans.vo.VO 接口。
② 第二步,给 CrmProductRespVO 的 ownerUserId 字段,添加 @Trans 注解,如下图所示:
图片纠错:最新版本不区分 yudao-module-crm-api 和 yudao-module-crm-biz 子模块,代码直接合并到 yudao-module-erp 模块的 src 目录下,更适合单体项目

- type属性:使用- TransType.SIMPLE跨模块翻译。不过实际上,因为多模块是在单个 Java 进程中,所以它底层还是走的 MyBatis Plus
- targetClassName属性:目标 DO 实体的类全路径,例如说- cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO
- fields和- ref属性:同上,不重复解释
友情提示:
后续这个场景下,easy-trans 的作者,也会改成 TransType.SIMPLE 简单翻译。
因此,“跨模块翻译”使用 targetClassName 属性的原因,是因为拿不到跨模块的 DO  实体类 = =
③ 第三步,给需要翻译的 Controller 接口,添加 @TransMethodResult 注解,和上面是一样的。
# 2.3 场景三:Excel 导出翻译
在 Excel 导出时,如果也有数据翻译的需求,需要调用 TranslateUtils (opens new window) 的 #translate(...) 方法,如下图所示:
图片纠错:最新版本不区分 yudao-module-crm-api 和 yudao-module-crm-biz 子模块,代码直接合并到 yudao-module-erp 模块的 src 目录下,更适合单体项目

本质上,它就是 easy-trans 的手动翻译,可见 《Trans 基础使用(必读)》的“3、自动翻译和手动翻译” (opens new window)
