From 454a474a36e7a932f95b57c810600ed4a76d708f Mon Sep 17 00:00:00 2001 From: wood911010 Date: Wed, 29 Oct 2025 22:38:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E5=8F=98=E6=9B=B4=E5=8E=86=E5=8F=B2=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=B9=B6=E6=9E=84=E5=BB=BA=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=BF=AB=E7=85=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authorization_code_repository.go | 5 + backend/internal/repository/interfaces.go | 57 ++++---- .../service/authorization_code_service.go | 135 +++++++++++++++--- 3 files changed, 148 insertions(+), 49 deletions(-) diff --git a/backend/internal/repository/authorization_code_repository.go b/backend/internal/repository/authorization_code_repository.go index 5d32247..530d14b 100644 --- a/backend/internal/repository/authorization_code_repository.go +++ b/backend/internal/repository/authorization_code_repository.go @@ -284,3 +284,8 @@ func (r *authorizationCodeRepository) GetAuthorizationChangeList(ctx context.Con TotalPages: totalPages, }, nil } + +// RecordAuthorizationChange 记录授权变更历史 +func (r *authorizationCodeRepository) RecordAuthorizationChange(ctx context.Context, change *models.AuthorizationChange) error { + return r.db.WithContext(ctx).Create(change).Error +} diff --git a/backend/internal/repository/interfaces.go b/backend/internal/repository/interfaces.go index 8fbcab1..909430a 100644 --- a/backend/internal/repository/interfaces.go +++ b/backend/internal/repository/interfaces.go @@ -2,27 +2,27 @@ package repository import ( "context" - "time" "license-manager/internal/models" + "time" ) // CustomerRepository 客户数据访问接口 type CustomerRepository interface { // GetCustomerList 查询客户列表 GetCustomerList(ctx context.Context, req *models.CustomerListRequest) (*models.CustomerListResponse, error) - + // GetCustomerByID 根据ID获取客户信息 GetCustomerByID(ctx context.Context, id string) (*models.Customer, error) - + // CreateCustomer 创建客户 CreateCustomer(ctx context.Context, customer *models.Customer) error - + // UpdateCustomer 更新客户信息 UpdateCustomer(ctx context.Context, customer *models.Customer) error - + // DeleteCustomer 删除客户 DeleteCustomer(ctx context.Context, id string) error - + // GetCustomerCount 获取客户总数(用于统计) GetCustomerCount(ctx context.Context, filters map[string]interface{}) (int64, error) } @@ -31,19 +31,19 @@ type CustomerRepository interface { type UserRepository interface { // GetUserByUsername 根据用户名获取用户 GetUserByUsername(ctx context.Context, username string) (*models.User, error) - + // GetUserByID 根据ID获取用户 GetUserByID(ctx context.Context, id string) (*models.User, error) - + // UpdateUser 更新用户信息 UpdateUser(ctx context.Context, user *models.User) error - + // IncrementLoginAttempts 增加登录失败次数 IncrementLoginAttempts(ctx context.Context, id string) error - + // ResetLoginAttempts 重置登录失败次数 ResetLoginAttempts(ctx context.Context, id string) error - + // LockUser 锁定用户账号 LockUser(ctx context.Context, id string, lockDuration int) error } @@ -52,52 +52,55 @@ type UserRepository interface { type AuthorizationCodeRepository interface { // CreateAuthorizationCode 创建授权码 CreateAuthorizationCode(ctx context.Context, authCode *models.AuthorizationCode) error - + // GetAuthorizationCodeByID 根据ID获取授权码 GetAuthorizationCodeByID(ctx context.Context, id string) (*models.AuthorizationCode, error) - + // GetAuthorizationCodeList 查询授权码列表 GetAuthorizationCodeList(ctx context.Context, req *models.AuthorizationCodeListRequest) (*models.AuthorizationCodeListResponse, error) - + // UpdateAuthorizationCode 更新授权码 UpdateAuthorizationCode(ctx context.Context, authCode *models.AuthorizationCode) error - + // DeleteAuthorizationCode 删除授权码(软删除) DeleteAuthorizationCode(ctx context.Context, id string) error - + // CheckCustomerExists 检查客户是否存在 CheckCustomerExists(ctx context.Context, customerID string) (bool, error) - + // GetAuthorizationChangeList 查询授权变更历史列表 GetAuthorizationChangeList(ctx context.Context, authCodeID string, req *models.AuthorizationChangeListRequest) (*models.AuthorizationChangeListResponse, error) + + // RecordAuthorizationChange 记录授权变更历史 + RecordAuthorizationChange(ctx context.Context, change *models.AuthorizationChange) error } // LicenseRepository 许可证数据访问接口 type LicenseRepository interface { // GetLicenseList 查询许可证列表 GetLicenseList(ctx context.Context, req *models.LicenseListRequest) (*models.LicenseListResponse, error) - + // GetLicenseByID 根据ID获取许可证信息 GetLicenseByID(ctx context.Context, id string) (*models.License, error) - + // CreateLicense 创建许可证 CreateLicense(ctx context.Context, license *models.License) error - + // UpdateLicense 更新许可证信息 UpdateLicense(ctx context.Context, license *models.License) error - + // CheckAuthorizationCodeExists 检查授权码是否存在 CheckAuthorizationCodeExists(ctx context.Context, authCodeID string) (bool, error) - + // GetAuthorizationCodeByID 根据ID获取授权码信息 GetAuthorizationCodeByID(ctx context.Context, authCodeID string) (*models.AuthorizationCode, error) - + // GetAuthorizationCodeByCode 根据授权码获取授权码信息 GetAuthorizationCodeByCode(ctx context.Context, code string) (*models.AuthorizationCode, error) - + // GetLicenseByKey 根据许可证密钥获取许可证信息 GetLicenseByKey(ctx context.Context, licenseKey string) (*models.License, error) - + // GetActiveLicenseCount 获取指定授权码的激活许可证数量 GetActiveLicenseCount(ctx context.Context, authCodeID string) (int64, error) } @@ -106,7 +109,7 @@ type LicenseRepository interface { type DashboardRepository interface { // GetAuthorizationTrendData 获取授权趋势数据 GetAuthorizationTrendData(ctx context.Context, startDate, endDate time.Time) ([]models.TrendData, error) - + // GetRecentAuthorizations 获取最近授权列表 GetRecentAuthorizations(ctx context.Context, req *models.DashboardRecentAuthorizationsRequest) (*models.DashboardRecentAuthorizationsResponse, error) -} \ No newline at end of file +} diff --git a/backend/internal/service/authorization_code_service.go b/backend/internal/service/authorization_code_service.go index 9adde3a..83b6cee 100644 --- a/backend/internal/service/authorization_code_service.go +++ b/backend/internal/service/authorization_code_service.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "log" "strings" "time" @@ -16,9 +17,9 @@ import ( ) type authorizationCodeService struct { - authCodeRepo repository.AuthorizationCodeRepository - customerRepo repository.CustomerRepository - licenseRepo repository.LicenseRepository + authCodeRepo repository.AuthorizationCodeRepository + customerRepo repository.CustomerRepository + licenseRepo repository.LicenseRepository } // NewAuthorizationCodeService 创建授权码服务实例 @@ -28,9 +29,9 @@ func NewAuthorizationCodeService( licenseRepo repository.LicenseRepository, ) AuthorizationCodeService { return &authorizationCodeService{ - authCodeRepo: authCodeRepo, - customerRepo: customerRepo, - licenseRepo: licenseRepo, + authCodeRepo: authCodeRepo, + customerRepo: customerRepo, + licenseRepo: licenseRepo, } } @@ -267,7 +268,10 @@ func (s *authorizationCodeService) UpdateAuthorizationCode(ctx context.Context, } // 获取当前用户ID - // currentUserID := "admin_uuid" // TODO: 从JWT token中获取 + currentUserID := "admin_uuid" // TODO: 从JWT token中获取 + + // 记录变更前的配置 + oldConfig := s.buildConfigSnapshot(existingAuthCode) // 只更新提供的字段 if req.SoftwareID != nil { @@ -320,9 +324,6 @@ func (s *authorizationCodeService) UpdateAuthorizationCode(ctx context.Context, existingAuthCode.CustomParameters = models.JSON(customParametersBytes) } - // 设置更新者 - 注意这里没有UpdatedBy字段,会通过BeforeUpdate钩子自动设置UpdatedAt - // existingAuthCode.UpdatedBy = ¤tUserID // 如果表结构有这个字段的话 - // 委托给Repository层进行数据更新 if err := s.authCodeRepo.UpdateAuthorizationCode(ctx, existingAuthCode); err != nil { return nil, i18n.NewI18nError("900004", lang, err.Error()) @@ -331,8 +332,11 @@ func (s *authorizationCodeService) UpdateAuthorizationCode(ctx context.Context, // 填充多语言显示字段和计算状态 s.fillAuthorizationCodeDisplayFields(existingAuthCode, lang) - // TODO: 记录变更历史到 authorization_changes 表 - // s.recordAuthorizationChange(ctx, id, req.ChangeType, req.Reason, currentUserID) + // 记录变更历史到 authorization_changes 表 + newConfig := s.buildConfigSnapshot(existingAuthCode) + if err := s.recordAuthorizationChange(ctx, id, req.ChangeType, req.Reason, currentUserID, oldConfig, newConfig); err != nil { + log.Printf("记录授权变更历史失败: %v", err) + } return existingAuthCode, nil } @@ -361,6 +365,9 @@ func (s *authorizationCodeService) LockUnlockAuthorizationCode(ctx context.Conte // 获取当前用户ID currentUserID := "admin_uuid" // TODO: 从JWT token中获取 + // 记录变更前的配置 + oldConfig := s.buildConfigSnapshot(existingAuthCode) + // 更新锁定状态 now := time.Now() existingAuthCode.IsLocked = req.IsLocked @@ -385,12 +392,15 @@ func (s *authorizationCodeService) LockUnlockAuthorizationCode(ctx context.Conte // 填充多语言显示字段和计算状态 s.fillAuthorizationCodeDisplayFields(existingAuthCode, lang) - // TODO: 记录变更历史到 authorization_changes 表 - // changeType := "lock" - // if !req.IsLocked { - // changeType = "unlock" - // } - // s.recordAuthorizationChange(ctx, id, changeType, req.Reason, currentUserID) + // 记录变更历史到 authorization_changes 表 + changeType := "lock" + if !req.IsLocked { + changeType = "unlock" + } + newConfig := s.buildConfigSnapshot(existingAuthCode) + if err := s.recordAuthorizationChange(ctx, id, changeType, req.Reason, currentUserID, oldConfig, newConfig); err != nil { + log.Printf("记录授权变更历史失败: %v", err) + } return existingAuthCode, nil } @@ -404,8 +414,11 @@ func (s *authorizationCodeService) DeleteAuthorizationCode(ctx context.Context, return i18n.NewI18nError("900001", lang) } + // 获取当前用户ID + currentUserID := "admin_uuid" // TODO: 从JWT token中获取 + // 先查询现有授权码,确保存在 - _, err := s.authCodeRepo.GetAuthorizationCodeByID(ctx, id) + existingAuthCode, err := s.authCodeRepo.GetAuthorizationCodeByID(ctx, id) if err != nil { if errors.Is(err, repository.ErrAuthorizationCodeNotFound) { return i18n.NewI18nError("300001", lang) @@ -413,18 +426,96 @@ func (s *authorizationCodeService) DeleteAuthorizationCode(ctx context.Context, return i18n.NewI18nError("900004", lang, err.Error()) } + // 记录变更前的配置 + oldConfig := s.buildConfigSnapshot(existingAuthCode) + // 委托给Repository层进行软删除 if err := s.authCodeRepo.DeleteAuthorizationCode(ctx, id); err != nil { return i18n.NewI18nError("900004", lang, err.Error()) } - // TODO: 记录变更历史到 authorization_changes 表 - // currentUserID := "admin_uuid" // TODO: 从JWT token中获取 - // s.recordAuthorizationChange(ctx, id, "delete", nil, currentUserID) + // 记录变更历史到 authorization_changes 表 + emptyConfig := make(map[string]interface{}) + if err := s.recordAuthorizationChange(ctx, id, "delete", nil, currentUserID, oldConfig, emptyConfig); err != nil { + log.Printf("记录授权变更历史失败: %v", err) + } return nil } +// recordAuthorizationChange 记录授权变更历史 +func (s *authorizationCodeService) recordAuthorizationChange(ctx context.Context, authCodeID string, changeType string, reason *string, operatorID string, oldConfig, newConfig map[string]interface{}) error { + // 构建变更历史记录 + change := &models.AuthorizationChange{ + AuthorizationCodeID: authCodeID, + ChangeType: changeType, + OperatorID: operatorID, + Reason: reason, + } + + // 序列化配置为JSON + if oldConfig != nil { + oldConfigBytes, err := json.Marshal(oldConfig) + if err != nil { + return err + } + change.OldConfig = models.JSON(oldConfigBytes) + } + + if newConfig != nil { + newConfigBytes, err := json.Marshal(newConfig) + if err != nil { + return err + } + change.NewConfig = models.JSON(newConfigBytes) + } + + // 委托给Repository层记录变更历史 + return s.authCodeRepo.RecordAuthorizationChange(ctx, change) +} + +// buildConfigSnapshot 构建配置快照,用于记录变更历史 +func (s *authorizationCodeService) buildConfigSnapshot(authCode *models.AuthorizationCode) map[string]interface{} { + config := make(map[string]interface{}) + + // 基础配置 + config["code"] = authCode.Code + config["software_id"] = authCode.SoftwareID + config["description"] = authCode.Description + config["start_date"] = authCode.StartDate.Format(time.RFC3339) + config["end_date"] = authCode.EndDate.Format(time.RFC3339) + config["deployment_type"] = authCode.DeploymentType + config["encryption_type"] = authCode.EncryptionType + config["software_version"] = authCode.SoftwareVersion + config["max_activations"] = authCode.MaxActivations + config["is_locked"] = authCode.IsLocked + config["lock_reason"] = authCode.LockReason + + // JSON配置字段 + if len(authCode.FeatureConfig) > 0 { + var featureConfig map[string]interface{} + if err := json.Unmarshal(authCode.FeatureConfig, &featureConfig); err == nil { + config["feature_config"] = featureConfig + } + } + + if len(authCode.UsageLimits) > 0 { + var usageLimits map[string]interface{} + if err := json.Unmarshal(authCode.UsageLimits, &usageLimits); err == nil { + config["usage_limits"] = usageLimits + } + } + + if len(authCode.CustomParameters) > 0 { + var customParameters map[string]interface{} + if err := json.Unmarshal(authCode.CustomParameters, &customParameters); err == nil { + config["custom_parameters"] = customParameters + } + } + + return config +} + // fillAuthCodeDisplayFields 填充授权码列表项多语言显示字段 func (s *authorizationCodeService) fillAuthCodeDisplayFields(item *models.AuthorizationCodeListItem, lang string, now time.Time) { // 计算状态