draft
This commit is contained in:
140
backend/internal/service/channel.go
Normal file
140
backend/internal/service/channel.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user