golang-yitisheng-server/entity_dto_vo_usage_improve...

15 KiB
Raw Blame History

Entity、DTO、VO 使用问题分析与改进建议

概述

本文档分析了项目中 Entity、DTO、VO 的使用情况,指出了存在的不规范使用问题,并提供了改进建议。

一、标准规范

1.1 定义与职责

类型 全称 职责 位置 示例
Entity Entity 数据库表映射实体,与数据库表一一对应 modules/xxx/entity/ YxUserScore
DTO Data Transfer Object 数据传输对象,用于接口请求/响应的数据传递 modules/xxx/dto/ SaveScoreRequest
VO View Object 视图对象用于前端展示可能聚合多个Entity数据 modules/xxx/vo/ UserScoreVO

1.2 分层调用规范

┌─────────────┐
│ Controller  │ ← 接收DTO返回VO
└──────┬──────┘
       │ DTO入参VO出参
┌──────▼──────┐
│   Service   │ ← 处理业务逻辑使用Entity进行数据操作
└──────┬──────┘
       │ Entity操作
┌──────▼──────┐
│   Mapper    │ ← 直接操作Entity与数据库
└─────────────┘

二、当前使用问题分析

2.1 System 模块问题

问题1Controller 直接接收和返回 Entity

文件: server/modules/system/controller/sys_user_controller.go

接口 问题 问题描述
POST /sys-users 接收Entity Create方法直接使用 entity.SysUser 绑定JSON请求
PUT /sys-users/:id 接收Entity Update方法直接使用 entity.SysUser 绑定JSON请求
GET /sys-users 返回Entity List方法直接返回 []entity.SysUser
GET /sys-users/:id 返回Entity GetByID方法直接返回 entity.SysUser

代码示例:

// ❌ 错误做法
func (ctrl *SysUserController) Create(c *gin.Context) {
    var item entity.SysUser  // 直接使用Entity绑定请求
    if err := c.ShouldBindJSON(&item); err != nil {
        common.Error(c, 400, "参数错误")
        return
    }
    // ...
}

// ✅ 正确做法(建议)
type CreateUserRequest struct {
    Username string `json:"username" binding:"required"`
    Realname string `json:"realname" binding:"required"`
    Password string `json:"password" binding:"required"`
    Email    string `json:"email"`
    Phone    string `json:"phone"`
}

func (ctrl *SysUserController) Create(c *gin.Context) {
    var req CreateUserRequest  // 使用DTO绑定请求
    if err := c.ShouldBindJSON(&req); err != nil {
        common.Error(c, 400, "参数错误")
        return
    }
    // Service层处理DTO到Entity的转换
}

问题2LoginUser 应该是 VO 而不是 Entity

文件: server/modules/system/entity/sys_user.go

// ❌ 当前LoginUser定义在entity包中
type LoginUser struct {
    ID       string `json:"id"`
    Username string `json:"username"`
    Realname string `json:"realname"`
    // ...
}

改进建议: LoginUser 应该移至 server/modules/system/vo/login_user_vo.go


2.2 User 模块问题

问题3Controller 返回数据不一致 ⚠️

文件: server/modules/user/controller/user_score_controller.go

接口 返回类型 评价
GET /user/score vo.UserScoreVO 正确返回VO
GET /user/score/:id interface{} ⚠️ 模糊Service返回 vo.UserScoreVO 但Controller使用 interface{} 接收
GET /user/score/list []vo.UserScoreVO 正确返回VO

代码示例:

// ⚠️ 当前做法
func (ctrl *UserScoreController) GetByID(c *gin.Context) {
    id := c.Param("id")
    item, err := ctrl.userScoreService.GetByID(id)  // 返回 interface{}
    if err != nil {
        common.Error(c, 404, "记录不存在")
        return
    }
    common.Success(c, item)
}

// ✅ Service层应该明确返回类型
func (s *UserScoreService) GetByID(id string) (vo.UserScoreVO, error) {
    var entity entity.YxUserScore
    // 查询逻辑...
    return s.convertEntityToVo(entity), nil
}

问题4UserVolunteerController 的 GetVolunteerDetail 使用匿名结构体 ⚠️

文件: server/modules/user/controller/user_volunteer_controller.go:127

// ⚠️ 当前做法在Controller中定义匿名结构体
type VolunteerDetailItem struct {
    entity.YxVolunteerRecord
    SchoolName      string `json:"schoolName"`
    MajorName       string `json:"majorName"`
    // ...
}

改进建议: 应该定义在 server/modules/user/vo/volunteer_detail_vo.go


2.3 Yx 模块问题

问题5YxVolunteerController 和 YxCalculationMajorController 大量使用 Entity

文件:

  • server/modules/yx/controller/yx_volunteer_controller.go
  • server/modules/yx/controller/yx_calculation_major_controller.go
接口 问题 严重程度
POST /yx-volunteers 接收Entity 🔴
PUT /yx-volunteers/:id 接收Entity 🔴
GET /yx-volunteers 返回Entity 🟡
POST /yx-calculation-majors 接收Entity 🔴
PUT /yx-calculation-majors/:id 接收Entity 🔴

代码示例:

// ❌ 错误做法
func (ctrl *YxVolunteerController) Create(c *gin.Context) {
    var item entity.YxVolunteer  // 直接使用Entity
    if err := c.ShouldBindJSON(&item); err != nil {
        common.Error(c, 400, "参数错误")
        return
    }
    // ...
}

问题6SchoolMajorDTO 混合了Entity和计算字段 ⚠️

文件: server/modules/yx/dto/yx_school_major_dto.go:23

type SchoolMajorDTO struct {
    // ... 基础字段
    HistoryMajorEnrollList []entity.YxHistoryMajorEnroll `json:"historyMajorEnrollList"`  // ❌ DTO中嵌套Entity
    // ...
}

改进建议: 创建 YxHistoryMajorEnrollVO 替代


三、改进方案

3.1 System 模块改进

需要创建的文件

server/modules/system/
├── dto/
│   ├── sys_user_dto.go          (新建)
│   └── auth_dto.go              (已存在,需检查)
└── vo/
    ├── sys_user_vo.go           (新建)
    └── login_user_vo.go         (新建从entity迁移)

需要修改的接口

Controller方法 当前 改进后
Create 接收Entity 接收CreateUserRequest DTO
Update 接收Entity 接收UpdateUserRequest DTO
List 返回Entity 返回[]SysUserVO
GetByID 返回Entity 返回SysUserVO

3.2 User 模块改进

需要创建的文件

server/modules/user/
├── dto/
│   └── user_volunteer_dto.go    (新建)
└── vo/
    ├── volunteer_detail_vo.go   (新建)
    └── volunteer_record_vo.go   (新建)

需要修改的接口

Controller方法 当前 改进后
UserScoreController.GetByID 返回interface{} 返回UserScoreVO
UserVolunteerController.GetVolunteerDetail 使用匿名结构体 使用VolunteerDetailVO
UserVolunteerController.SaveVolunteer 接收[]string 创建SaveVolunteerRequest DTO

3.3 Yx 模块改进

需要创建的文件

server/modules/yx/
├── dto/
│   ├── yx_volunteer_dto.go      (新建)
│   ├── yx_calculation_major_dto.go  (新建)
│   └── yx_history_major_enroll_vo.go  (新建)
└── vo/
    ├── yx_volunteer_vo.go       (新建)
    ├── yx_calculation_major_vo.go   (新建)
    └── yx_school_major_vo.go    (新建)

需要修改的接口

Controller方法 当前 改进后
YxVolunteerController.Create 接收Entity 接收CreateVolunteerRequest DTO
YxVolunteerController.Update 接收Entity 接收UpdateVolunteerRequest DTO
YxVolunteerController.List 返回Entity 返回[]YxVolunteerVO
YxCalculationMajorController.Create 接收Entity 接收CreateCalculationMajorRequest DTO
YxCalculationMajorController.Update 接收Entity 接收UpdateCalculationMajorRequest DTO
YxCalculationMajorController.List 返回Entity 返回[]YxCalculationMajorVO

四、代码示例

4.1 创建请求DTO

// server/modules/system/dto/sys_user_dto.go
package dto

// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
    Username string `json:"username" binding:"required,min=3,max=20"`
    Realname string `json:"realname" binding:"required"`
    Password string `json:"password" binding:"required,min=6"`
    Email    string `json:"email" binding:"omitempty,email"`
    Phone    string `json:"phone" binding:"omitempty,len=11"`
    Avatar   string `json:"avatar"`
}

// UpdateUserRequest 更新用户请求
type UpdateUserRequest struct {
    Realname string `json:"realname"`
    Email    string `json:"email" binding:"omitempty,email"`
    Phone    string `json:"phone" binding:"omitempty,len=11"`
    Avatar   string `json:"avatar"`
    Sex      *int   `json:"sex"`
}

4.2 创建响应VO

// server/modules/system/vo/sys_user_vo.go
package vo

import "time"

// SysUserVO 用户视图对象
type SysUserVO struct {
    ID       string     `json:"id"`
    Username string     `json:"username"`
    Realname string     `json:"realname"`
    Avatar   string     `json:"avatar"`
    Email    string     `json:"email"`
    Phone    string     `json:"phone"`
    Sex      int        `json:"sex"`
    Status   int        `json:"status"`
    CreateTime *time.Time `json:"createTime"`
    // 注意:不包含 Password、Salt 等敏感字段
}

4.3 Controller 修改示例

// server/modules/system/controller/sys_user_controller.go
package controller

import (
    "server/common"
    "server/modules/system/dto"
    "server/modules/system/vo"
    "server/modules/system/service"
    "github.com/gin-gonic/gin"
)

// Create 创建用户
// @Summary      创建用户
// @Tags         用户管理
// @Param        request  body  dto.CreateUserRequest  true  "用户信息"
// @Success      200  {object}  common.Response{data=vo.SysUserVO}
// @Router       /sys-users [post]
func (ctrl *SysUserController) Create(c *gin.Context) {
    var req dto.CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        common.Error(c, 400, "参数错误: "+err.Error())
        return
    }

    result, err := ctrl.service.CreateUser(&req)
    if err != nil {
        common.Error(c, 500, err.Error())
        return
    }

    common.Success(c, result)  // 返回VO
}

// List 获取用户列表
// @Summary      获取用户列表
// @Tags         用户管理
// @Param        page  query  int  false  "页码"
// @Param        size  query  int  false  "每页数量"
// @Success      200  {object}  common.Response{data=[]vo.SysUserVO}
// @Router       /sys-users [get]
func (ctrl *SysUserController) List(c *gin.Context) {
    page := common.GetPage(c)
    size := common.GetSize(c)

    items, total, err := ctrl.service.ListUsers(page, size)
    if err != nil {
        common.Error(c, 500, err.Error())
        return
    }

    common.SuccessPage(c, items, total, page, size)  // 返回[]VO
}

4.4 Service 修改示例

// server/modules/system/service/sys_user_service.go
package service

import (
    "server/modules/system/dto"
    "server/modules/system/entity"
    "server/modules/system/vo"
    "time"
)

// CreateUser 创建用户并返回VO
func (s *SysUserService) CreateUser(req *dto.CreateUserRequest) (*vo.SysUserVO, error) {
    // DTO 转 Entity
    entityItem := &entity.SysUser{
        Username: req.Username,
        Realname: req.Realname,
        Password: req.Password,  // 会在Create方法中加密
        Email:    req.Email,
        Phone:    req.Phone,
        Avatar:   req.Avatar,
    }

    // 保存到数据库
    if err := s.Create(entityItem); err != nil {
        return nil, err
    }

    // Entity 转 VO
    return s.convertToVO(entityItem), nil
}

// ListUsers 获取用户列表
func (s *SysUserService) ListUsers(page, size int) ([]vo.SysUserVO, int64, error) {
    entities, total, err := s.List(page, size)
    if err != nil {
        return nil, 0, err
    }

    // 批量转换 Entity 到 VO
    vos := make([]vo.SysUserVO, len(entities))
    for i, item := range entities {
        vos[i] = s.convertToVO(&item)
    }

    return vos, total, nil
}

// convertToVO Entity 转 VO私有方法
func (s *SysUserService) convertToVO(entity *entity.SysUser) *vo.SysUserVO {
    return &vo.SysUserVO{
        ID:         entity.ID,
        Username:   entity.Username,
        Realname:   entity.Realname,
        Avatar:     entity.Avatar,
        Email:      entity.Email,
        Phone:      entity.Phone,
        Sex:        entity.Sex,
        Status:     entity.Status,
        CreateTime: entity.CreateTime,
        // 不包含 Password、Salt 等敏感字段
    }
}

五、优先级排序

优先级 模块 改进内容 原因
P0 User UserScoreController.GetByID 返回类型明确化 影响API契约稳定性
P0 Yx YxVolunteerController 接口DTO/VO改造 安全性风险直接暴露Entity
P1 System SysUserController 接口DTO/VO改造 规范性改进
P1 User UserVolunteerController GetVolunteerDetail VO封装 代码维护性
P2 Yx YxCalculationMajorController 接口DTO/VO改造 规范性改进
P2 Yx SchoolMajorDTO 去除Entity嵌套 架构清晰度

六、注意事项

6.1 敏感字段处理

  • Entity 中的 PasswordSalt 等敏感字段必须在 VO 中排除
  • 使用 json:"-" 标签确保不序列化

6.2 转换工具函数

建议在 Service 层实现以下方法:

// DTO -> Entity
func (s *Service) convertDtoToEntity(dto *DTO) *Entity

// Entity -> VO
func (s *Service) convertEntityToVo(entity *Entity) *VO

// []Entity -> []VO
func (s *Service) convertEntitiesToVos(entities []Entity) []VO

6.3 渐进式改进

为避免一次性改动过大,建议:

  1. 先为新增接口使用DTO/VO规范
  2. 再逐步改造现有接口
  3. 保持向后兼容,避免破坏现有客户端调用

七、总结

7.1 核心问题

  1. Controller 层直接使用 Entity:违反了分层架构原则
  2. 返回类型不明确:使用 interface{} 导致API契约不清晰
  3. VO 定义缺失大量接口直接返回Entity
  4. DTO 定义不足请求参数直接使用Entity

7.2 改进收益

  1. 安全性提升:敏感字段不会泄露到前端
  2. API 契约清晰:请求/响应类型明确
  3. 代码可维护性:解耦前后端数据结构
  4. 符合DDD规范:清晰的领域模型分层

7.3 后续行动

  1. 按优先级逐步改进
  2. 补充单元测试
  3. 更新API文档Swagger
  4. 团队代码规范培训