golang-yitisheng-server/docs/generic-base-class-solution.md

21 KiB
Raw Blame History

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<T> (Generic Base)             │  │
│  │  • List()     • GetByID()  • Create()   • Update()     │  │
│  │  • Delete()   • Batch*()    (通用 CRUD 实现)            │  │
│  └───────────────────────┬───────────────────────────────┘  │
│                          │                                   │
│        ┌─────────────────┼─────────────────┐                 │
│        ↓                 ↓                 ↓                 │
│  ┌───────────┐     ┌───────────┐     ┌───────────┐          │
│  │YxVolunteer│     │YxUserScore│     │SysUserService│        │
│  │  Service  │     │  Service  │     │            │          │
│  │ (继承)    │     │ (继承)    │     │ (继承)     │          │
│  └─────┬─────┘     └─────┬─────┘     └─────┬─────┘          │
└────────┼──────────────────┼──────────────────┼─────────────────┘
         ↓                  ↓                  ↓
┌─────────────────────────────────────────────────────────────┐
│                       Mapper Layer                           │
│  ┌───────────────────────────────────────────────────────┐  │
│  │               BaseMapper<T> (Generic Base)             │  │
│  │  • FindAll()  • FindByID() • Create()   • Update()     │  │
│  │  • Delete()   • Batch*()    (通用 CRUD 实现)            │  │
│  └───────────────────────┬───────────────────────────────┘  │
│                          │                                   │
│        ┌─────────────────┼─────────────────┐                 │
│        ↓                 ↓                 ↓                 │
│  ┌───────────┐     ┌───────────┐     ┌───────────┐          │
│  │YxVolunteer│     │YxUserScore│     │SysUserMapper│        │
│  │  Mapper   │     │  Mapper   │     │            │          │
│  │ (继承)    │     │ (继承)    │     │ (继承)     │          │
│  └─────┬─────┘     └─────┬─────┘     └─────┬─────┘          │
└────────┼──────────────────┼──────────────────┼─────────────────┘
         ↓                  ↓                  ↓
┌─────────────────────────────────────────────────────────────┐
│                      GORM / MySQL                            │
└─────────────────────────────────────────────────────────────┘

泛型基类设计

// 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 实现

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 实现

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最简单

// 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

// 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

// 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. 增加软删除支持(通过配置)