package service import ( "errors" "fmt" "strings" "time" "affiliate_dash/internal/model" "gorm.io/gorm" ) type APICredential struct { Client *model.APIClient `json:"client"` Secret string `json:"secret"` } type CreateAPIClientInput struct { Name string Scopes string SignatureVersion string ExpiresAt *time.Time } // MaxAPIClientsPerMerchant 每个商户最多可创建的 API 密钥数量,防止密钥滥用。 const MaxAPIClientsPerMerchant = 5 func (s *MerchantService) CreateAPIClient(merchantID uint, in CreateAPIClientInput, actorUserID uint) (*APICredential, error) { in.Name = strings.TrimSpace(in.Name) if in.Name == "" { return nil, errors.New("API 客户端名称不能为空") } if len(ParseScopes(in.Scopes)) == 0 { return nil, errors.New("至少配置一个 API 权限") } if in.SignatureVersion == "" { in.SignatureVersion = "v1" } if in.SignatureVersion != "v1" { return nil, errors.New("无效的签名版本") } appKey, err := randomToken("ak_", 24) if err != nil { return nil, err } secret, err := randomToken("sk_", 32) if err != nil { return nil, err } ciphertext, err := s.codec.Encrypt(secret) if err != nil { return nil, err } client := &model.APIClient{ MerchantID: merchantID, Name: in.Name, AppKey: appKey, SecretCiphertext: ciphertext, SignatureVersion: in.SignatureVersion, Scopes: strings.Join(scopeList(in.Scopes), ","), Status: model.APIClientStatusActive, ExpiresAt: in.ExpiresAt, } err = s.db.Transaction(func(tx *gorm.DB) error { var merchant model.Merchant if err := tx.Where("id = ? AND status = ?", merchantID, model.MerchantStatusActive).First(&merchant).Error; err != nil { return errors.New("商户不存在或已禁用") } var clientCount int64 if err := tx.Model(&model.APIClient{}). Where("merchant_id = ? AND status = ?", merchantID, model.APIClientStatusActive). Count(&clientCount).Error; err != nil { return err } if clientCount >= MaxAPIClientsPerMerchant { return fmt.Errorf("每个商户最多可创建 %d 个 API 密钥", MaxAPIClientsPerMerchant) } if err := tx.Create(client).Error; err != nil { return err } return writeAudit(tx, &merchantID, &actorUserID, nil, "api_client.create", "api_client", fmt.Sprint(client.ID), map[string]string{"name": in.Name}) }) if err != nil { return nil, err } return &APICredential{Client: client, Secret: secret}, nil } func (s *MerchantService) ListAPIClients(merchantID uint) ([]model.APIClient, error) { var clients []model.APIClient err := s.db.Where("merchant_id = ?", merchantID).Order("id DESC").Find(&clients).Error return clients, err } func (s *MerchantService) UpdateAPIClientStatus(merchantID, id uint, status string, actorUserID uint) error { if status != model.APIClientStatusActive && status != model.APIClientStatusDisabled { return errors.New("无效的 API 客户端状态") } return s.db.Transaction(func(tx *gorm.DB) error { result := tx.Model(&model.APIClient{}).Where("id = ? AND merchant_id = ?", id, merchantID).Update("status", status) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return errors.New("API 客户端不存在") } return writeAudit(tx, &merchantID, &actorUserID, nil, "api_client.status.update", "api_client", fmt.Sprint(id), map[string]string{"status": status}) }) } // DeleteAPIClient 删除商户的 API 密钥(物理删除,不可恢复)。 // 删除前要求先停用,避免在用的密钥被误删。 func (s *MerchantService) DeleteAPIClient(merchantID, id uint, actorUserID uint) error { return s.db.Transaction(func(tx *gorm.DB) error { var client model.APIClient if err := tx.Where("id = ? AND merchant_id = ?", id, merchantID).First(&client).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("API 客户端不存在") } return err } if client.Status == model.APIClientStatusActive { return errors.New("请先停用该密钥再删除") } if err := tx.Delete(&client).Error; err != nil { return err } return writeAudit(tx, &merchantID, &actorUserID, nil, "api_client.delete", "api_client", fmt.Sprint(id), map[string]string{"name": client.Name, "app_key": client.AppKey}) }) }