// Package repository 负责数据库访问。 // // 设计原则: // - 每个 repo 都是接口 + GORM 实现,Service 持有接口便于单测替换 // - 不在 repo 内做业务逻辑(比如不会去算油耗) // - 错误统一包成 apperror.NotFound / apperror.Internal package repository import ( "context" "errors" "gorm.io/gorm" "github.com/kennethcheng/fuellog-api/internal/model" "github.com/kennethcheng/fuellog-api/pkg/apperror" ) // VehicleRepository 车辆表的数据访问接口。 type VehicleRepository interface { Create(ctx context.Context, v *model.Vehicle) error Get(ctx context.Context, id uint64) (*model.Vehicle, error) List(ctx context.Context) ([]model.Vehicle, error) Update(ctx context.Context, v *model.Vehicle) error Delete(ctx context.Context, id uint64) error ClearDefault(ctx context.Context, tx *gorm.DB) error SetDefault(ctx context.Context, tx *gorm.DB, id uint64) error } // NewVehicleRepository 返回 GORM 实现。 func NewVehicleRepository(g *gorm.DB) VehicleRepository { return &vehicleRepo{g: g} } type vehicleRepo struct{ g *gorm.DB } func (r *vehicleRepo) Create(ctx context.Context, v *model.Vehicle) error { if err := r.g.WithContext(ctx).Create(v).Error; err != nil { return apperror.Wrap(apperror.CodeInternal, "创建车辆失败", err) } return nil } func (r *vehicleRepo) Get(ctx context.Context, id uint64) (*model.Vehicle, error) { var v model.Vehicle if err := r.g.WithContext(ctx).First(&v, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, apperror.New(apperror.CodeNotFound, "车辆不存在") } return nil, apperror.Wrap(apperror.CodeInternal, "查询车辆失败", err) } return &v, nil } func (r *vehicleRepo) List(ctx context.Context) ([]model.Vehicle, error) { var vs []model.Vehicle if err := r.g.WithContext(ctx).Order("default_vehicle DESC, id ASC").Find(&vs).Error; err != nil { return nil, apperror.Wrap(apperror.CodeInternal, "查询车辆列表失败", err) } return vs, nil } func (r *vehicleRepo) Update(ctx context.Context, v *model.Vehicle) error { res := r.g.WithContext(ctx).Save(v) if res.Error != nil { return apperror.Wrap(apperror.CodeInternal, "更新车辆失败", res.Error) } if res.RowsAffected == 0 { return apperror.New(apperror.CodeNotFound, "车辆不存在") } return nil } func (r *vehicleRepo) Delete(ctx context.Context, id uint64) error { res := r.g.WithContext(ctx).Delete(&model.Vehicle{}, id) if res.Error != nil { return apperror.Wrap(apperror.CodeInternal, "删除车辆失败", res.Error) } if res.RowsAffected == 0 { return apperror.New(apperror.CodeNotFound, "车辆不存在") } return nil } // ClearDefault 在事务内把当前所有 default_vehicle 置为 false。 // 由 service 在切换默认车辆时调用,避免出现两个默认车辆。 func (r *vehicleRepo) ClearDefault(ctx context.Context, tx *gorm.DB) error { if err := tx.WithContext(ctx).Model(&model.Vehicle{}). Where("default_vehicle = ?", true). Update("default_vehicle", false).Error; err != nil { return apperror.Wrap(apperror.CodeInternal, "清除默认车辆标记失败", err) } return nil } // SetDefault 在事务内把指定车辆置为默认。 func (r *vehicleRepo) SetDefault(ctx context.Context, tx *gorm.DB, id uint64) error { res := tx.WithContext(ctx).Model(&model.Vehicle{}). Where("id = ?", id). Update("default_vehicle", true) if res.Error != nil { return apperror.Wrap(apperror.CodeInternal, "设置默认车辆失败", res.Error) } if res.RowsAffected == 0 { return apperror.New(apperror.CodeNotFound, "车辆不存在") } return nil }