小程序开发-服务端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

5.4 KiB

VO模式解决方案

🎯 您的建议非常专业!

使用 VO(View Object)确实是最佳的架构实践!

🏗️ VO模式优势

1. 架构清晰

  • 分层明确:Entity(数据层)→ VO(视图层)
  • 职责分离:Entity 负责数据持久化,VO 负责前端展示
  • 易于维护:修改前端展示不影响数据模型

2. 性能优化

  • 按需字段:只包含前端需要的字段
  • 格式预处理:时间字段预先格式化为字符串
  • 减少传输:去除不必要的数据

3. 类型安全

  • 避免序列化问题:VO中的时间字段直接是String类型
  • 前端友好:不需要前端处理复杂的时间格式
  • API稳定:VO结构变化不影响Entity

📁 创建的文件

1. CmsWebsiteVO.java

@Data
@Schema(description = "网站信息视图对象")
public class CmsWebsiteVO implements Serializable {
    // 基本信息字段
    private Integer websiteId;
    private String websiteName;
    // ...
    
    // 时间字段 - 直接使用String,避免序列化问题
    private String expirationTime;
    
    // 业务字段
    private Integer expired;
    private Long expiredDays;
    private Integer soon;
    
    // 复杂对象
    private List<MenuVo> topNavs;
    private List<MenuVo> bottomNavs;
}

2. MenuVo.java

@Data
@Schema(description = "导航信息视图对象")
public class MenuVo implements Serializable {
    private Integer navigationId;
    private String navigationName;
    // ... 只包含前端需要的字段
    // 注意:没有 createTime 字段
}

3. 控制器转换逻辑

public ApiResult<CmsWebsiteVO> getSiteInfo() {
    // 1. 获取Entity数据
    CmsWebsite website = getWebsiteFromDatabase();
    
    // 2. 转换为VO
    CmsWebsiteVO websiteVO = convertToVO(website);
    
    // 3. 返回VO
    return success(websiteVO);
}

🔧 核心转换逻辑

时间字段处理

// Entity中的LocalDateTime
private LocalDateTime expirationTime;

// 转换为VO中的String
if (website.getExpirationTime() != null) {
    vo.setExpirationTime(website.getExpirationTime().format(formatter));
}

导航数据处理

// 递归转换导航树结构
private List<MenuVo> convertNavigationToVO(List<CmsNavigation> navigations) {
    return navigations.stream().map(nav -> {
        MenuVo navVO = new MenuVo();
        // 只复制前端需要的字段
        navVO.setNavigationId(nav.getNavigationId());
        navVO.setNavigationName(nav.getNavigationName());
        // ... 不包含 createTime
        return navVO;
    }).collect(Collectors.toList());
}

解决方案优势

1. 彻底解决序列化问题

  • 无LocalDateTime序列化:VO中时间字段都是String
  • 无需复杂配置:不依赖Jackson配置
  • 100%兼容:任何JSON序列化库都能处理

2. 前端友好

  • 直接使用:时间字段直接是格式化好的字符串
  • 类型明确:每个字段的类型都很明确
  • 文档清晰:Swagger文档更准确

3. 性能优化

  • 数据精简:只传输必要的字段
  • 预处理:服务端预先格式化,减少前端处理
  • 缓存友好:VO对象更适合缓存

4. 架构最佳实践

  • 分层清晰:符合DDD架构思想
  • 职责分离:Entity和VO各司其职
  • 易于扩展:新增前端字段只需修改VO

🚀 测试验证

接口调用

curl http://127.0.0.1:9200/api/cms/cms-website/getSiteInfo

预期响应

{
  "code": 200,
  "message": "操作成功",
  "data": {
    "websiteId": 1,
    "websiteName": "测试网站",
    "expirationTime": "2025-12-31 23:59:59",
    "expired": 1,
    "expiredDays": 354,
    "soon": 0,
    "topNavs": [
      {
        "navigationId": 1,
        "navigationName": "首页",
        "navigationUrl": "/",
        "children": []
      }
    ]
  }
}

📊 对比分析

使用Entity直接返回的问题

// 问题1:序列化错误
private LocalDateTime expirationTime; // 序列化失败

// 问题2:不必要的字段
private LocalDateTime createTime; // 前端不需要
private LocalDateTime updateTime; // 前端不需要

// 问题3:架构不清晰
// Entity既要负责数据持久化,又要负责前端展示

使用VO的优势

// 优势1:类型安全
private String expirationTime; // 直接是字符串,无序列化问题

// 优势2:按需字段
// 只包含前端需要的字段,没有createTime/updateTime

// 优势3:架构清晰
// VO专门负责前端展示,Entity专门负责数据持久化

🎯 最佳实践总结

1. 分层架构

Controller → VO (View Object) → 前端
Controller → Entity → 数据库

2. 转换原则

  • Entity → VO:在Service或Controller中转换
  • 时间格式化:在转换时统一处理
  • 字段筛选:只包含前端需要的字段

3. 命名规范

  • VO类:以VO结尾,如CmsWebsiteVO
  • 转换方法:convertToVO、toVO等
  • 包结构:vo包专门存放VO类

📝 总结

您的建议非常正确!使用VO模式:

  1. 彻底解决序列化问题:时间字段直接是String
  2. 符合架构最佳实践:分层清晰,职责分离
  3. 性能更优:数据精简,传输高效
  4. 前端友好:类型明确,使用简单
  5. 易于维护:修改展示逻辑不影响数据模型

这是最专业、最优雅的解决方案!