feat: complete production readiness improvements

- Fix DIP violations in service layer (device, stats, auth middleware)
- Add ReplaceUserRoles interface method for transaction safety
- Implement Magic Bytes validation for avatar uploads
- Standardize OAuth error handling with ErrOAuthProviderNotSupported
- Use crypto/rand for JWT secret generation instead of weak fixed key
- Apply code formatting with gofumpt and goimports
- Fix staticcheck issues (S1024, S1008, ST1005)
- Add comprehensive quality and functional test reports
- Achieve 36.3% test coverage (up from 16.3%)
- All E2E, integration, and business logic tests passing
This commit is contained in:
2026-04-12 16:15:32 +08:00
parent 861736cf4d
commit 09beb173cc
22 changed files with 3122 additions and 414 deletions

View File

@@ -171,7 +171,7 @@ func (r *DeviceRepository) GetActiveDevices(ctx context.Context, userID int64) (
// TrustDevice 设置设备为信任状态
func (r *DeviceRepository) TrustDevice(ctx context.Context, deviceID int64, expiresAt *time.Time) error {
updates := map[string]interface{}{
"is_trusted": true,
"is_trusted": true,
"trust_expires_at": expiresAt,
}
return r.db.WithContext(ctx).Model(&domain.Device{}).Where("id = ?", deviceID).Updates(updates).Error
@@ -180,7 +180,7 @@ func (r *DeviceRepository) TrustDevice(ctx context.Context, deviceID int64, expi
// UntrustDevice 取消设备信任状态
func (r *DeviceRepository) UntrustDevice(ctx context.Context, deviceID int64) error {
updates := map[string]interface{}{
"is_trusted": false,
"is_trusted": false,
"trust_expires_at": nil,
}
return r.db.WithContext(ctx).Model(&domain.Device{}).Where("id = ?", deviceID).Updates(updates).Error

View File

@@ -136,7 +136,6 @@ func (r *PermissionRepository) GetByRoleIDs(ctx context.Context, roleIDs []int64
Where("role_permissions.role_id IN ?", roleIDs).
Where("permissions.status = ?", domain.PermissionStatusEnabled).
Find(&permissions).Error
if err != nil {
return nil, err
}

View File

@@ -86,11 +86,11 @@ func (r *UserRoleRepository) GetRoleIDsByUserID(ctx context.Context, userID int6
// GetUserRolesAndPermissions 获取用户角色和权限PERF-01 优化:合并为单次 JOIN 查询)
func (r *UserRoleRepository) GetUserRolesAndPermissions(ctx context.Context, userID int64) ([]*domain.Role, []*domain.Permission, error) {
var results []struct {
RoleID int64
RoleName string
RoleCode string
RoleStatus int
PermissionID int64
RoleID int64
RoleName string
RoleCode string
RoleStatus int
PermissionID int64
PermissionCode string
PermissionName string
}
@@ -118,9 +118,9 @@ func (r *UserRoleRepository) GetUserRolesAndPermissions(ctx context.Context, use
for _, row := range results {
if _, ok := roleMap[row.RoleID]; !ok {
roleMap[row.RoleID] = &domain.Role{
ID: row.RoleID,
Name: row.RoleName,
Code: row.RoleCode,
ID: row.RoleID,
Name: row.RoleName,
Code: row.RoleCode,
Status: domain.RoleStatus(row.RoleStatus),
}
}
@@ -180,11 +180,38 @@ func (r *UserRoleRepository) BatchDelete(ctx context.Context, userRoles []*domai
if len(userRoles) == 0 {
return nil
}
var ids []int64
for _, ur := range userRoles {
ids = append(ids, ur.ID)
}
return r.db.WithContext(ctx).Delete(&domain.UserRole{}, ids).Error
}
// ReplaceUserRoles replaces all roles for a user in a single transaction
// This encapsulates the delete-then-create pattern to ensure atomicity
func (r *UserRoleRepository) ReplaceUserRoles(ctx context.Context, userID int64, roleIDs []int64) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// Delete all existing roles for the user
if err := tx.Where("user_id = ?", userID).Delete(&domain.UserRole{}).Error; err != nil {
return err
}
// Create new role associations if any
if len(roleIDs) > 0 {
userRoles := make([]*domain.UserRole, len(roleIDs))
for i, roleID := range roleIDs {
userRoles[i] = &domain.UserRole{
UserID: userID,
RoleID: roleID,
}
}
if err := tx.Create(&userRoles).Error; err != nil {
return err
}
}
return nil
})
}