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

@@ -20,6 +20,18 @@ const (
ExportFormatXLSX = "xlsx"
)
// Interfaces for dependency inversion (DIP) — service layer depends on these abstractions, not concrete types.
type exportUserRepository interface {
List(ctx context.Context, offset, limit int) ([]*domain.User, int64, error)
AdvancedSearch(ctx context.Context, filter *repository.AdvancedFilter) ([]*domain.User, int64, error)
ExistsByUsername(ctx context.Context, username string) (bool, error)
Create(ctx context.Context, user *domain.User) error
}
type exportRoleRepository interface {
// Reserved for future use (role assignment during import)
}
// ExportUsersRequest defines the supported export filters and output options.
type ExportUsersRequest struct {
Format string
@@ -53,14 +65,14 @@ var defaultExportColumns = []exportColumn{
// ExportService 用户数据导入导出服务
type ExportService struct {
userRepo *repository.UserRepository
roleRepo *repository.RoleRepository
userRepo exportUserRepository
roleRepo exportRoleRepository
}
// NewExportService 创建导入导出服务
func NewExportService(
userRepo *repository.UserRepository,
roleRepo *repository.RoleRepository,
userRepo exportUserRepository,
roleRepo exportRoleRepository,
) *ExportService {
return &ExportService{
userRepo: userRepo,
@@ -461,13 +473,13 @@ func parseCSVRecords(data []byte) ([][]string, error) {
func parseXLSXRecords(data []byte) ([][]string, error) {
file, err := excelize.OpenReader(bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("Excel 解析失败: %w", err)
return nil, fmt.Errorf("excel parse failed: %w", err)
}
defer file.Close()
sheets := file.GetSheetList()
if len(sheets) == 0 {
return nil, fmt.Errorf("Excel 文件没有可用工作表")
return nil, fmt.Errorf("excel file has no available sheets")
}
rows, err := file.GetRows(sheets[0])