- 后端 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 与文件尾部多余空行
132 lines
4.3 KiB
Go
132 lines
4.3 KiB
Go
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})
|
|
})
|
|
}
|