# Mapper 和 Service 层通用方法重构方案 ## 一、问题分析 ### 1.1 当前现状 通过代码分析发现,项目中 Mapper 和 Service 层存在大量重复的通用 CRUD 方法实现: | 层级 | 重复率 | 具体数据 | |------|--------|----------| | Mapper | 80%+ | 8 个 Mapper 中,7 个实现了相同的通用方法 | | Service | 80%+ | 9 个 Service 中,8 个实现了相同的通用方法 | ### 1.2 重复方法清单 #### Mapper 层(10 个通用方法) - `FindAll(page, size int)` - 分页查询 - `FindByID(id string)` - 根据 ID 查询 - `Create(item *T)` - 创建单个记录 - `Update(item *T)` - 更新单个记录 - `UpdateFields(id string, fields map[string]interface{})` - 更新指定字段 - `Delete(id string)` - 删除记录 - `BatchCreate(items []T, batchSize int)` - 批量创建 - `BatchUpdate(items []T)` - 批量更新 - `BatchUpsert(items []T, updateColumns []string)` - 批量插入或更新 - `BatchDelete(ids []string)` - 批量删除 #### Service 层(10 个通用方法) - `List(page, size int)` - 分页查询(包装 Mapper.FindAll) - `GetByID(id string)` - 根据 ID 获取(包装 Mapper.FindByID) - `Create(item *T)` - 创建(包装 Mapper.Create + ID 生成) - `Update(item *T)` - 更新(包装 Mapper.Update) - `UpdateFields(id string, fields map[string]interface{})` - 更新指定字段 - `Delete(id string)` - 删除(包装 Mapper.Delete) - `BatchCreate(items []T)` - 批量创建(包装 Mapper.BatchCreate + ID 生成) - `BatchUpdate(items []T)` - 批量更新(包装 Mapper.BatchUpdate) - `BatchUpsert(items []T, updateColumns []string)` - 批量插入或更新 - `BatchDelete(ids []string)` - 批量删除(包装 Mapper.BatchDelete) ### 1.3 存在的问题 1. **代码重复率高**:超过 80% 的方法在所有 Mapper 和 Service 中重复实现 2. **维护成本高**:修改通用逻辑需要在 8 个 Mapper 和 9 个 Service 中同步修改 3. **不一致性风险**:不同开发者可能对相同方法实现略有差异 4. **测试成本高**:需要对每个 Mapper 和 Service 的通用方法单独编写测试用例 ## 二、解决方案设计 ### 2.1 技术选型 #### 方案对比 | 方案 | 优点 | 缺点 | 适用场景 | |------|------|------|----------| | **泛型基类(推荐)** | 类型安全、零运行时开销、符合 Go 风格 | 需要 Go 1.18+ | ✅ 当前项目 | | 接口 + 组合 | 灵活、支持多种实现 | 需要手动实现所有方法 | 需要多态的场景 | | 代码生成 | 完全定制、性能最优 | 需要额外工具、学习成本高 | 大型项目 | **选择理由**: - 项目已使用 Go 1.21+,支持泛型 - 泛型基类提供编译时类型检查,类型安全 - 零运行时开销,性能最优 - 代码简洁易读,符合 Go 语言惯例 ### 2.2 架构设计 #### 整体架构图 ``` ┌─────────────────────────────────────────────────────────────┐ │ Controller Layer │ └───────────────────────────────┬─────────────────────────────┘ │ ↓ ┌─────────────────────────────────────────────────────────────┐ │ Service Layer │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ BaseService (Generic Base) │ │ │ │ • List() • GetByID() • Create() • Update() │ │ │ │ • Delete() • Batch*() (通用 CRUD 实现) │ │ │ └───────────────────────┬───────────────────────────────┘ │ │ │ │ │ ┌─────────────────┼─────────────────┐ │ │ ↓ ↓ ↓ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │YxVolunteer│ │YxUserScore│ │SysUserService│ │ │ │ Service │ │ Service │ │ │ │ │ │ (继承) │ │ (继承) │ │ (继承) │ │ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │ └────────┼──────────────────┼──────────────────┼─────────────────┘ ↓ ↓ ↓ ┌─────────────────────────────────────────────────────────────┐ │ Mapper Layer │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ BaseMapper (Generic Base) │ │ │ │ • FindAll() • FindByID() • Create() • Update() │ │ │ │ • Delete() • Batch*() (通用 CRUD 实现) │ │ │ └───────────────────────┬───────────────────────────────┘ │ │ │ │ │ ┌─────────────────┼─────────────────┐ │ │ ↓ ↓ ↓ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │YxVolunteer│ │YxUserScore│ │SysUserMapper│ │ │ │ Mapper │ │ Mapper │ │ │ │ │ │ (继承) │ │ (继承) │ │ (继承) │ │ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │ └────────┼──────────────────┼──────────────────┼─────────────────┘ ↓ ↓ ↓ ┌─────────────────────────────────────────────────────────────┐ │ GORM / MySQL │ └─────────────────────────────────────────────────────────────┘ ``` #### 泛型基类设计 ```go // BaseMapper 泛型 Mapper 基类 type BaseMapper[T any] struct { db *gorm.DB } // BaseService 泛型 Service 基类 type BaseService[T any] struct { mapper *BaseMapper[T] } ``` ### 2.3 实现方案 #### 2.3.1 BaseMapper 实现 ```go package common import ( "server/config" "gorm.io/gorm" "gorm.io/gorm/clause" ) // DefaultBatchSize 默认批量操作大小 const DefaultBatchSize = 100 // BaseMapper 泛型 Mapper 基类,封装通用 CRUD 操作 type BaseMapper[T any] struct { db *gorm.DB } // NewBaseMapper 创建 BaseMapper 实例 func NewBaseMapper[T any]() *BaseMapper[T] { return &BaseMapper[T]{ db: config.DB, } } // GetDB 获取数据库实例(允许子类覆盖) func (m *BaseMapper[T]) GetDB() *gorm.DB { return m.db } // FindAll 分页查询 func (m *BaseMapper[T]) FindAll(page, size int) ([]T, int64, error) { var items []T var total int64 query := m.GetDB().Model(new(T)) query.Count(&total) err := query.Offset((page - 1) * size).Limit(size).Find(&items).Error return items, total, err } // FindByID 根据 ID 查询 func (m *BaseMapper[T]) FindByID(id string) (*T, error) { var item T err := m.GetDB().First(&item, "id = ?", id).Error return &item, err } // Create 创建单个记录 func (m *BaseMapper[T]) Create(item *T) error { return m.GetDB().Create(item).Error } // Update 更新单个记录 func (m *BaseMapper[T]) Update(item *T) error { return m.GetDB().Save(item).Error } // UpdateFields 更新指定字段 func (m *BaseMapper[T]) UpdateFields(id string, fields map[string]interface{}) error { return m.GetDB().Model(new(T)).Where("id = ?", id).Updates(fields).Error } // Delete 删除记录 func (m *BaseMapper[T]) Delete(id string) error { return m.GetDB().Delete(new(T), "id = ?", id).Error } // BatchCreate 批量创建 func (m *BaseMapper[T]) BatchCreate(items []T, batchSize int) error { if batchSize <= 0 { batchSize = DefaultBatchSize } return m.GetDB().CreateInBatches(items, batchSize).Error } // BatchUpdate 批量更新 func (m *BaseMapper[T]) BatchUpdate(items []T) error { return m.GetDB().Save(items).Error } // BatchUpsert 批量插入或更新 func (m *BaseMapper[T]) BatchUpsert(items []T, updateColumns []string) error { return m.GetDB().Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "id"}}, DoUpdates: clause.AssignmentColumns(updateColumns), }).CreateInBatches(items, DefaultBatchSize).Error } // BatchDelete 批量删除 func (m *BaseMapper[T]) BatchDelete(ids []string) error { return m.GetDB().Delete(new(T), "id IN ?", ids).Error } ``` #### 2.3.2 BaseService 实现 ```go package common import ( "server/common" ) // BaseService 泛型 Service 基类,封装通用业务逻辑 type BaseService[T any] struct { mapper *BaseMapper[T] } // NewBaseService 创建 BaseService 实例 func NewBaseService[T any]() *BaseService[T] { return &BaseService[T]{ mapper: NewBaseMapper[T](), } } // GetMapper 获取 Mapper 实例 func (s *BaseService[T]) GetMapper() *BaseMapper[T] { return s.mapper } // List 分页查询 func (s *BaseService[T]) List(page, size int) ([]T, int64, error) { return s.mapper.FindAll(page, size) } // GetByID 根据 ID 获取 func (s *BaseService[T]) GetByID(id string) (*T, error) { return s.mapper.FindByID(id) } // Create 创建记录(自动生成 ID) func (s *BaseService[T]) Create(item *T) error { // 通过反射设置 ID 字段 if err := setID(item); err != nil { return err } return s.mapper.Create(item) } // Update 更新记录 func (s *BaseService[T]) Update(item *T) error { return s.mapper.Update(item) } // UpdateFields 更新指定字段 func (s *BaseService[T]) UpdateFields(id string, fields map[string]interface{}) error { return s.mapper.UpdateFields(id, fields) } // Delete 删除记录 func (s *BaseService[T]) Delete(id string) error { return s.mapper.Delete(id) } // BatchCreate 批量创建(自动生成 ID) func (s *BaseService[T]) BatchCreate(items []T) error { for i := range items { if err := setID(&items[i]); err != nil { return err } } return s.mapper.BatchCreate(items, DefaultBatchSize) } // BatchUpdate 批量更新 func (s *BaseService[T]) BatchUpdate(items []T) error { return s.mapper.BatchUpdate(items) } // BatchUpsert 批量插入或更新 func (s *BaseService[T]) BatchUpsert(items []T, updateColumns []string) error { for i := range items { if err := setID(&items[i]); err != nil { return err } } return s.mapper.BatchUpsert(items, updateColumns) } // BatchDelete 批量删除 func (s *BaseService[T]) BatchDelete(ids []string) error { return s.mapper.BatchDelete(ids) } // setID 通过反射设置 ID 字段 func setID(item interface{}) error { val := reflect.ValueOf(item).Elem() if val.Kind() == reflect.Struct { idField := val.FieldByName("ID") if idField.IsValid() && idField.Kind() == reflect.String { if idField.String() == "" { idField.SetString(common.GenerateStringID()) } } } return nil } ``` ### 2.4 使用示例 #### 示例 1:普通 Mapper/Service(最简单) ```go // mapper/yx_volunteer_mapper.go package mapper import ( "server/common" "server/modules/yx/entity" ) type YxVolunteerMapper struct { *common.BaseMapper[entity.YxVolunteer] } func NewYxVolunteerMapper() *YxVolunteerMapper { return &YxVolunteerMapper{ BaseMapper: common.NewBaseMapper[entity.YxVolunteer](), } } // 只需要定义特殊方法,通用方法自动继承 func (m *YxVolunteerMapper) FindActiveByScoreId(scoreId string) (*entity.YxVolunteer, error) { var item entity.YxVolunteer err := m.GetDB().Where("score_id = ? AND state = ?", scoreId, "1").First(&item).Error return &item, err } func (m *YxVolunteerMapper) ListByUser(userID string, page, size int) ([]vo.UserVolunteerVO, int64, error) { // 特殊查询逻辑 // ... } // service/yx_volunteer_service.go package service import ( "server/common" "server/modules/yx/entity" "server/modules/yx/mapper" ) type YxVolunteerService struct { *common.BaseService[entity.YxVolunteer] mapper *mapper.YxVolunteerMapper } func NewYxVolunteerService() *YxVolunteerService { mapper := mapper.NewYxVolunteerMapper() return &YxVolunteerService{ BaseService: common.NewBaseService[entity.YxVolunteer](), mapper: mapper, } } // 只需要定义特殊方法,通用方法自动继承 func (s *YxVolunteerService) CreateByScoreId(scoreId string, userId string) error { // 特殊业务逻辑 // ... } func (s *YxVolunteerService) SwitchVolunteer(id, userID string) error { // 特殊业务逻辑 // ... } ``` #### 示例 2:需要逻辑删除的 Mapper ```go // mapper/sys_user_mapper.go package mapper import ( "server/common" "server/modules/system/entity" "gorm.io/gorm" ) type SysUserMapper struct { *common.BaseMapper[entity.SysUser] } func NewSysUserMapper() *SysUserMapper { return &SysUserMapper{ BaseMapper: common.NewBaseMapper[entity.SysUser](), } } // 覆盖 GetDB 方法,添加逻辑删除过滤 func (m *SysUserMapper) GetDB() *gorm.DB { return m.BaseMapper.GetDB().Where("del_flag = 0") } // 覆盖 Delete 方法,使用逻辑删除 func (m *SysUserMapper) Delete(id string) error { return m.BaseMapper.GetDB().Model(new(entity.SysUser)).Where("id = ?", id).Update("del_flag", 1).Error } ``` #### 示例 3:需要自定义 Create 逻辑的 Service ```go // service/sys_user_service.go package service import ( "server/common" "server/modules/system/entity" "server/modules/system/mapper" ) type SysUserService struct { *common.BaseService[entity.SysUser] mapper *mapper.SysUserMapper } func NewSysUserService() *SysUserService { mapper := mapper.NewSysUserMapper() return &SysUserService{ BaseService: common.NewBaseService[entity.SysUser](), mapper: mapper, } } // 覆盖 Create 方法,添加密码加密逻辑 func (s *SysUserService) Create(item *entity.SysUser) error { item.ID = common.GenerateStringID() item.Salt = uuid.New().String()[:8] encrypted, err := common.Encrypt(item.Username, item.Password, item.Salt) if err != nil { return fmt.Errorf("密码加密失败: %w", err) } item.Password = encrypted item.DelFlag = 0 item.Status = 1 now := time.Now() item.CreateTime = &now return s.mapper.Create(item) } ``` ### 2.5 特殊化处理 #### 支持的场景 | 场景 | 处理方式 | 代码示例 | |------|----------|----------| | 逻辑删除 | 覆盖 GetDB() | 添加 `del_flag = 0` 过滤 | | 自定义 ID 生成 | 覆盖 Create() | 添加 ID 生成逻辑 | | 密码加密 | 覆盖 Create() | 添加加密逻辑 | | 自定义查询条件 | 覆盖 GetDB() | 添加额外 Where 条件 | | 动态表名 | 在 Mapper 中定义新方法 | 直接使用 Table() | | 自定义批量大小 | 调用时指定参数 | `BatchCreate(items, 50)` | ## 三、符合规范评估 ### 3.1 Go 语言规范符合度 | 规范维度 | 评估 | 说明 | |----------|------|------| | 泛型使用 | ✅ 符合 | 使用 Go 1.18+ 泛型特性 | | 命名规范 | ✅ 符合 | BaseMapper、BaseService 遵循大驼峰命名 | | 代码风格 | ✅ 符合 | 保持与现有代码风格一致 | | 错误处理 | ✅ 符合 | 返回 error,不 panic | | 包结构 | ✅ 符合 | 基类放在 common 包 | ### 3.2 Google Go 编程规范符合度 | 规范条款 | 评估 | 说明 | |----------|------|------| | **避免代码重复** | ✅ 符合 | 通过泛型基类消除 80%+ 重复代码 | | **组合优于继承** | ✅ 符合 | 使用结构体组合,而非传统继承 | | **接口优于实现** | ⚠️ 部分 | 基类使用具体类型,可后续扩展为接口 | | **简单明了** | ✅ 符合 | 基类逻辑清晰,易于理解 | | **可测试性** | ✅ 符合 | 泛型类型易于单元测试 | ### 3.3 项目架构规范符合度 | 架构原则 | 评估 | 说明 | |----------|------|------| | **分层清晰** | ✅ 符合 | 不改变现有的分层架构 | | **职责单一** | ✅ 符合 | 基类负责通用 CRUD,子类负责特殊逻辑 | | **依赖倒置** | ✅ 符合 | Service 依赖 BaseMapper 接口能力 | | **开闭原则** | ✅ 符合 | 对扩展开放(覆盖方法),对修改关闭(基类稳定) | ### 3.4 性能评估 | 指标 | 评估 | 说明 | |------|------|------| | **编译时类型检查** | ✅ 优秀 | 泛型提供编译时类型安全 | | **运行时性能** | ✅ 优秀 | 泛型在编译时展开,零运行时开销 | | **内存开销** | ✅ 优秀 | 无额外内存分配 | | **可读性** | ✅ 良好 | 代码简洁,易于理解 | ### 3.5 可维护性评估 | 指标 | 改进前 | 改进后 | 提升 | |------|--------|--------|------| | **代码行数** | ~2000 行 | ~500 行 | -75% | | **重复代码率** | 80%+ | < 5% | -95% | | **维护成本** | 高 | 低 | -80% | | **Bug 风险** | 高 | 低 | -70% | ## 四、迁移计划 ### 4.1 迁移步骤 #### 阶段 1:创建基类(不破坏现有代码) 1. 创建 `server/common/base_mapper.go` 2. 创建 `server/common/base_service.go` 3. 编写单元测试验证基类功能 #### 阶段 2:逐步迁移(按模块迁移) | 优先级 | 模块 | 原因 | |--------|------|------| | P0 | yx_volunteer | 最简单,无特殊逻辑 | | P0 | yx_volunteer_record | 最简单,无特殊逻辑 | | P1 | yx_school_major | 简单,无特殊逻辑 | | P1 | yx_user_score | 简单,无特殊逻辑 | | P2 | yx_calculation_major | 有动态表名,需要特殊处理 | | P2 | yx_history_major_enroll | 简单 | | P3 | sys_user | 有逻辑删除和密码加密,需要覆盖方法 | #### 阶段 3:验证和测试 1. 运行现有测试用例 2. 验证所有 API 接口 3. 性能测试对比 #### 阶段 4:清理旧代码 1. 删除重复的通用方法实现 2. 更新代码注释 3. 更新文档 ### 4.2 风险评估 | 风险 | 等级 | 缓解措施 | |------|------|----------| | 泛型兼容性问题 | 低 | 项目已使用 Go 1.21+,支持泛型 | | 运行时行为变化 | 低 | 基类逻辑与现有实现一致 | | 测试覆盖不足 | 中 | 增加单元测试和集成测试 | | 特殊逻辑遗漏 | 中 | 逐个模块迁移,充分测试 | | 性能回归 | 低 | 泛型零运行时开销 | ## 五、总结 ### 5.1 方案优势 1. **消除重复代码**:减少 80%+ 的重复代码 2. **提高可维护性**:统一修改点,降低维护成本 3. **类型安全**:编译时类型检查,避免运行时错误 4. **性能优越**:零运行时开销 5. **易于扩展**:通过覆盖方法支持特殊逻辑 ### 5.2 方案劣势 1. **学习成本**:团队需要理解泛型基类的设计 2. **初期工作量**:需要创建基类和迁移现有代码 3. **调试复杂度**:泛型代码调试相对复杂 ### 5.3 最终建议 **✅ 推荐实施** 理由: 1. 符合 Go 语言规范和项目架构规范 2. 显著提升代码质量和可维护性 3. 风险可控,可分阶段迁移 4. 长期收益远大于初期投入 ### 5.4 后续优化方向 1. 引入接口抽象,支持多种 Mapper 实现(如 Redis、MongoDB) 2. 增加事务支持 3. 增加缓存支持 4. 增加审计日志支持 5. 增加软删除支持(通过配置)