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 存在的问题
- 代码重复率高:超过 80% 的方法在所有 Mapper 和 Service 中重复实现
- 维护成本高:修改通用逻辑需要在 8 个 Mapper 和 9 个 Service 中同步修改
- 不一致性风险:不同开发者可能对相同方法实现略有差异
- 测试成本高:需要对每个 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:创建基类(不破坏现有代码)
- 创建
server/common/base_mapper.go
- 创建
server/common/base_service.go
- 编写单元测试验证基类功能
阶段 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:验证和测试
- 运行现有测试用例
- 验证所有 API 接口
- 性能测试对比
阶段 4:清理旧代码
- 删除重复的通用方法实现
- 更新代码注释
- 更新文档
4.2 风险评估
| 风险 |
等级 |
缓解措施 |
| 泛型兼容性问题 |
低 |
项目已使用 Go 1.21+,支持泛型 |
| 运行时行为变化 |
低 |
基类逻辑与现有实现一致 |
| 测试覆盖不足 |
中 |
增加单元测试和集成测试 |
| 特殊逻辑遗漏 |
中 |
逐个模块迁移,充分测试 |
| 性能回归 |
低 |
泛型零运行时开销 |
五、总结
5.1 方案优势
- 消除重复代码:减少 80%+ 的重复代码
- 提高可维护性:统一修改点,降低维护成本
- 类型安全:编译时类型检查,避免运行时错误
- 性能优越:零运行时开销
- 易于扩展:通过覆盖方法支持特殊逻辑
5.2 方案劣势
- 学习成本:团队需要理解泛型基类的设计
- 初期工作量:需要创建基类和迁移现有代码
- 调试复杂度:泛型代码调试相对复杂
5.3 最终建议
✅ 推荐实施
理由:
- 符合 Go 语言规范和项目架构规范
- 显著提升代码质量和可维护性
- 风险可控,可分阶段迁移
- 长期收益远大于初期投入
5.4 后续优化方向
- 引入接口抽象,支持多种 Mapper 实现(如 Redis、MongoDB)
- 增加事务支持
- 增加缓存支持
- 增加审计日志支持
- 增加软删除支持(通过配置)