This commit is contained in:
2026-03-13 15:51:59 +08:00
parent 4db2386bbf
commit 4e91f4cede
133 changed files with 19502 additions and 37 deletions

View File

@@ -0,0 +1,140 @@
package service
import (
"context"
"fmt"
"sync"
"time"
"pay-bridge/internal/channel"
"pay-bridge/internal/model"
"pay-bridge/internal/repository"
"pay-bridge/pkg/config"
"pay-bridge/pkg/crypto"
)
const channelCacheTTL = 5 * time.Minute
type cachedChannel struct {
ch channel.PaymentChannel
expiresAt time.Time
}
// ChannelService 渠道服务(负责加载渠道配置并获取渠道实例)
type ChannelService struct {
repo *repository.ChannelConfigRepository
encKey string
urlsCfg config.ChannelsConfig
mu sync.Mutex
cache map[string]*cachedChannel
}
func NewChannelService(repo *repository.ChannelConfigRepository, encKey string, urlsCfg config.ChannelsConfig) *ChannelService {
return &ChannelService{
repo: repo,
encKey: encKey,
urlsCfg: urlsCfg,
cache: make(map[string]*cachedChannel),
}
}
// GetChannel 根据 appID 和渠道码获取渠道适配器实例5 分钟内存缓存)
func (s *ChannelService) GetChannel(ctx context.Context, appID, channelCode string) (channel.PaymentChannel, error) {
cacheKey := appID + ":" + channelCode
s.mu.Lock()
if entry, ok := s.cache[cacheKey]; ok && time.Now().Before(entry.expiresAt) {
ch := entry.ch
s.mu.Unlock()
return ch, nil
}
s.mu.Unlock()
cfg, err := s.repo.GetByAppChannel(ctx, appID, channelCode)
if err != nil {
return nil, err
}
if cfg == nil {
return nil, fmt.Errorf("channel config not found: app=%s channel=%s", appID, channelCode)
}
decCfg, err := s.decryptConfig(cfg)
if err != nil {
return nil, err
}
ch, err := channel.Get(channelCode, decCfg, s.urlsFor(channelCode))
if err != nil {
return nil, err
}
s.mu.Lock()
s.cache[cacheKey] = &cachedChannel{ch: ch, expiresAt: time.Now().Add(channelCacheTTL)}
s.mu.Unlock()
return ch, nil
}
// InvalidateCache 使指定渠道的缓存失效(配置变更时调用)
func (s *ChannelService) InvalidateCache(appID, channelCode string) {
s.mu.Lock()
delete(s.cache, appID+":"+channelCode)
s.mu.Unlock()
}
// ListChannelCodes 获取应用下所有渠道码
func (s *ChannelService) ListChannelCodes(ctx context.Context, appID string) ([]string, error) {
cfgs, err := s.repo.ListByApp(ctx, appID)
if err != nil {
return nil, err
}
codes := make([]string, 0, len(cfgs))
for _, c := range cfgs {
codes = append(codes, c.ChannelCode)
}
return codes, nil
}
// GetChannelConfig 获取渠道配置(已解密)
func (s *ChannelService) GetChannelConfig(ctx context.Context, appID, channelCode string) (*model.ChannelConfig, error) {
cfg, err := s.repo.GetByAppChannel(ctx, appID, channelCode)
if err != nil || cfg == nil {
return cfg, err
}
return s.decryptConfig(cfg)
}
// urlsFor 根据渠道码返回对应的网关地址配置
func (s *ChannelService) urlsFor(channelCode string) channel.URLs {
switch channelCode {
case "HEEPAY":
return channel.URLs{
PayURL: s.urlsCfg.Heepay.PayURL,
MerchantURL: s.urlsCfg.Heepay.MerchantURL,
}
default:
return channel.URLs{}
}
}
func (s *ChannelService) decryptConfig(cfg *model.ChannelConfig) (*model.ChannelConfig, error) {
copied := *cfg
if cfg.APIKey != "" {
dec, err := crypto.Decrypt(cfg.APIKey, s.encKey)
if err != nil {
return nil, fmt.Errorf("decrypt api_key: %w", err)
}
copied.APIKey = dec
}
if cfg.PrivateKey != "" {
dec, err := crypto.Decrypt(cfg.PrivateKey, s.encKey)
if err != nil {
return nil, fmt.Errorf("decrypt private_key: %w", err)
}
copied.PrivateKey = dec
}
return &copied, nil
}