拆分 service 与前端大文件,修复 CORS 配置与格式问题
- 后端 internal/service 按职责拆分: fulfillment.go(1397→527)拆出 wallet/timeout/data/order/query/dashboard/shipnotify delivery.go(1124→801)拆出 upstream/link/state/helpers merchant.go(855→251)拆出 member/product/api_client/catalog/helpers - 前端 MerchantCenter.tsx(1327→606)拆出 merchantCenterTabs/merchantCenterUtils - docker-compose backend 透传 CORS_ALLOWED_ORIGINS - CORS 白名单实现(config/router/README/.env.example 配套) - 修复 gofmt 与文件尾部多余空行
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
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})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user