2026-04-09 20:28:23 +08:00
|
|
|
|
package adapter
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
|
|
"lijiaoqiao/supply-api/internal/audit"
|
|
|
|
|
|
"lijiaoqiao/supply-api/internal/domain"
|
|
|
|
|
|
"lijiaoqiao/supply-api/internal/middleware"
|
|
|
|
|
|
"lijiaoqiao/supply-api/internal/repository"
|
|
|
|
|
|
"lijiaoqiao/supply-api/internal/storage"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 内存存储适配器(开发模式)====================
|
|
|
|
|
|
|
|
|
|
|
|
// InMemoryAccountStoreAdapter 内存账号存储适配器
|
|
|
|
|
|
type InMemoryAccountStoreAdapter struct {
|
|
|
|
|
|
store *storage.InMemoryAccountStore
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewInMemoryAccountStoreAdapter 创建内存账号存储适配器
|
|
|
|
|
|
func NewInMemoryAccountStoreAdapter() *InMemoryAccountStoreAdapter {
|
|
|
|
|
|
return &InMemoryAccountStoreAdapter{store: storage.NewInMemoryAccountStore()}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemoryAccountStoreAdapter) Create(ctx context.Context, account *domain.Account) error {
|
|
|
|
|
|
return a.store.Create(ctx, account)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemoryAccountStoreAdapter) GetByID(ctx context.Context, supplierID, id int64) (*domain.Account, error) {
|
|
|
|
|
|
return a.store.GetByID(ctx, supplierID, id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemoryAccountStoreAdapter) Update(ctx context.Context, account *domain.Account) error {
|
|
|
|
|
|
return a.store.Update(ctx, account)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemoryAccountStoreAdapter) List(ctx context.Context, supplierID int64) ([]*domain.Account, error) {
|
|
|
|
|
|
return a.store.List(ctx, supplierID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// InMemoryPackageStoreAdapter 内存套餐存储适配器
|
|
|
|
|
|
type InMemoryPackageStoreAdapter struct {
|
|
|
|
|
|
store *storage.InMemoryPackageStore
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewInMemoryPackageStoreAdapter 创建内存套餐存储适配器
|
|
|
|
|
|
func NewInMemoryPackageStoreAdapter() *InMemoryPackageStoreAdapter {
|
|
|
|
|
|
return &InMemoryPackageStoreAdapter{store: storage.NewInMemoryPackageStore()}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemoryPackageStoreAdapter) Create(ctx context.Context, pkg *domain.Package) error {
|
|
|
|
|
|
return a.store.Create(ctx, pkg)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemoryPackageStoreAdapter) GetByID(ctx context.Context, supplierID, id int64) (*domain.Package, error) {
|
|
|
|
|
|
return a.store.GetByID(ctx, supplierID, id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemoryPackageStoreAdapter) Update(ctx context.Context, pkg *domain.Package) error {
|
|
|
|
|
|
return a.store.Update(ctx, pkg)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemoryPackageStoreAdapter) List(ctx context.Context, supplierID int64) ([]*domain.Package, error) {
|
|
|
|
|
|
return a.store.List(ctx, supplierID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// InMemorySettlementStoreAdapter 内存结算存储适配器
|
|
|
|
|
|
type InMemorySettlementStoreAdapter struct {
|
|
|
|
|
|
store *storage.InMemorySettlementStore
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewInMemorySettlementStoreAdapter 创建内存结算存储适配器
|
|
|
|
|
|
func NewInMemorySettlementStoreAdapter() *InMemorySettlementStoreAdapter {
|
|
|
|
|
|
return &InMemorySettlementStoreAdapter{store: storage.NewInMemorySettlementStore()}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemorySettlementStoreAdapter) Create(ctx context.Context, s *domain.Settlement) error {
|
|
|
|
|
|
return a.store.Create(ctx, s)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-09 22:16:08 +08:00
|
|
|
|
// CreateWithdrawTx 内存存储的原子提现创建(简化实现,假设无并发)
|
|
|
|
|
|
func (a *InMemorySettlementStoreAdapter) CreateWithdrawTx(ctx context.Context, s *domain.Settlement) error {
|
|
|
|
|
|
return a.store.CreateInTx(ctx, s)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-09 20:28:23 +08:00
|
|
|
|
func (a *InMemorySettlementStoreAdapter) CreateInTx(ctx context.Context, s *domain.Settlement) error {
|
|
|
|
|
|
return a.store.CreateInTx(ctx, s)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemorySettlementStoreAdapter) GetByID(ctx context.Context, supplierID, id int64) (*domain.Settlement, error) {
|
|
|
|
|
|
return a.store.GetByID(ctx, supplierID, id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemorySettlementStoreAdapter) Update(ctx context.Context, s *domain.Settlement, expectedVersion int) error {
|
|
|
|
|
|
return a.store.Update(ctx, s, expectedVersion)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemorySettlementStoreAdapter) List(ctx context.Context, supplierID int64) ([]*domain.Settlement, error) {
|
|
|
|
|
|
return a.store.List(ctx, supplierID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemorySettlementStoreAdapter) GetWithdrawableBalance(ctx context.Context, supplierID int64) (float64, error) {
|
|
|
|
|
|
return a.store.GetWithdrawableBalance(ctx, supplierID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemorySettlementStoreAdapter) HasPendingOrProcessingWithdraw(ctx context.Context, supplierID int64) (bool, error) {
|
|
|
|
|
|
return a.store.HasPendingOrProcessingWithdraw(ctx, supplierID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// InMemoryEarningStoreAdapter 内存收益存储适配器
|
|
|
|
|
|
type InMemoryEarningStoreAdapter struct {
|
|
|
|
|
|
store *storage.InMemoryEarningStore
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewInMemoryEarningStoreAdapter 创建内存收益存储适配器
|
|
|
|
|
|
func NewInMemoryEarningStoreAdapter() *InMemoryEarningStoreAdapter {
|
|
|
|
|
|
return &InMemoryEarningStoreAdapter{store: storage.NewInMemoryEarningStore()}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemoryEarningStoreAdapter) ListRecords(ctx context.Context, supplierID int64, startDate, endDate string, page, pageSize int) ([]*domain.EarningRecord, int, error) {
|
|
|
|
|
|
return a.store.ListRecords(ctx, supplierID, startDate, endDate, page, pageSize)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (a *InMemoryEarningStoreAdapter) GetBillingSummary(ctx context.Context, supplierID int64, startDate, endDate string) (*domain.BillingSummary, error) {
|
|
|
|
|
|
return a.store.GetBillingSummary(ctx, supplierID, startDate, endDate)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== DB-backed存储适配器 ====================
|
|
|
|
|
|
|
|
|
|
|
|
// DBAccountStore DB-backed账号存储
|
|
|
|
|
|
type DBAccountStore struct {
|
|
|
|
|
|
repo *repository.AccountRepository
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewDBAccountStore 创建DB-backed账号存储
|
|
|
|
|
|
func NewDBAccountStore(repo *repository.AccountRepository) *DBAccountStore {
|
|
|
|
|
|
return &DBAccountStore{repo: repo}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBAccountStore) Create(ctx context.Context, account *domain.Account) error {
|
|
|
|
|
|
return s.repo.Create(ctx, account, "", "", "")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBAccountStore) GetByID(ctx context.Context, supplierID, id int64) (*domain.Account, error) {
|
|
|
|
|
|
return s.repo.GetByID(ctx, supplierID, id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBAccountStore) Update(ctx context.Context, account *domain.Account) error {
|
|
|
|
|
|
return s.repo.Update(ctx, account, account.Version)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBAccountStore) List(ctx context.Context, supplierID int64) ([]*domain.Account, error) {
|
|
|
|
|
|
return s.repo.List(ctx, supplierID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DBPackageStore DB-backed套餐存储
|
|
|
|
|
|
type DBPackageStore struct {
|
|
|
|
|
|
repo *repository.PackageRepository
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewDBPackageStore 创建DB-backed套餐存储
|
|
|
|
|
|
func NewDBPackageStore(repo *repository.PackageRepository) *DBPackageStore {
|
|
|
|
|
|
return &DBPackageStore{repo: repo}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBPackageStore) Create(ctx context.Context, pkg *domain.Package) error {
|
|
|
|
|
|
return s.repo.Create(ctx, pkg, "", "")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBPackageStore) GetByID(ctx context.Context, supplierID, id int64) (*domain.Package, error) {
|
|
|
|
|
|
return s.repo.GetByID(ctx, supplierID, id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBPackageStore) Update(ctx context.Context, pkg *domain.Package) error {
|
|
|
|
|
|
return s.repo.Update(ctx, pkg, pkg.Version)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBPackageStore) List(ctx context.Context, supplierID int64) ([]*domain.Package, error) {
|
|
|
|
|
|
return s.repo.List(ctx, supplierID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DBSettlementStore DB-backed结算存储
|
|
|
|
|
|
type DBSettlementStore struct {
|
|
|
|
|
|
repo *repository.SettlementRepository
|
|
|
|
|
|
accountRepo *repository.AccountRepository // 用于GetWithdrawableBalance查询账户余额
|
|
|
|
|
|
pool *pgxpool.Pool // 用于创建事务
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewDBSettlementStore 创建DB-backed结算存储
|
|
|
|
|
|
func NewDBSettlementStore(repo *repository.SettlementRepository, accountRepo *repository.AccountRepository, pool *pgxpool.Pool) *DBSettlementStore {
|
|
|
|
|
|
return &DBSettlementStore{
|
|
|
|
|
|
repo: repo,
|
|
|
|
|
|
accountRepo: accountRepo,
|
|
|
|
|
|
pool: pool,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBSettlementStore) Create(ctx context.Context, settlement *domain.Settlement) error {
|
|
|
|
|
|
return s.repo.Create(ctx, settlement, "", "", "")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-09 22:16:08 +08:00
|
|
|
|
// CreateWithdrawTx 原子化创建提现(带锁)
|
|
|
|
|
|
// 使用 SELECT ... FOR UPDATE SKIP LOCKED 防止并发提现
|
|
|
|
|
|
func (s *DBSettlementStore) CreateWithdrawTx(ctx context.Context, settlement *domain.Settlement) error {
|
|
|
|
|
|
tx, err := s.pool.Begin(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer tx.Rollback(ctx)
|
|
|
|
|
|
|
|
|
|
|
|
if err := s.repo.CreateWithdrawTx(ctx, tx, settlement, "", "", ""); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := tx.Commit(ctx); err != nil {
|
|
|
|
|
|
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CreateInTx 在事务中创建结算单(非提现)
|
2026-04-09 20:28:23 +08:00
|
|
|
|
func (s *DBSettlementStore) CreateInTx(ctx context.Context, settlement *domain.Settlement) error {
|
|
|
|
|
|
tx, err := s.pool.Begin(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer tx.Rollback(ctx)
|
|
|
|
|
|
|
|
|
|
|
|
if err := s.repo.CreateTx(ctx, tx, settlement, "", "", ""); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := tx.Commit(ctx); err != nil {
|
|
|
|
|
|
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBSettlementStore) GetByID(ctx context.Context, supplierID, id int64) (*domain.Settlement, error) {
|
|
|
|
|
|
return s.repo.GetByID(ctx, supplierID, id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBSettlementStore) Update(ctx context.Context, settlement *domain.Settlement, expectedVersion int) error {
|
|
|
|
|
|
return s.repo.Update(ctx, settlement, expectedVersion)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBSettlementStore) List(ctx context.Context, supplierID int64) ([]*domain.Settlement, error) {
|
|
|
|
|
|
return s.repo.List(ctx, supplierID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBSettlementStore) GetWithdrawableBalance(ctx context.Context, supplierID int64) (float64, error) {
|
|
|
|
|
|
if s.accountRepo == nil {
|
|
|
|
|
|
return 0.0, fmt.Errorf("account repository not initialized")
|
|
|
|
|
|
}
|
|
|
|
|
|
return s.accountRepo.GetWithdrawableBalance(ctx, supplierID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBSettlementStore) HasPendingOrProcessingWithdraw(ctx context.Context, supplierID int64) (bool, error) {
|
|
|
|
|
|
return s.repo.HasPendingOrProcessingWithdraw(ctx, supplierID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DBEarningStore DB-backed收益存储
|
|
|
|
|
|
type DBEarningStore struct {
|
|
|
|
|
|
usageRepo *repository.UsageRepository
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewDBEarningStore 创建DB-backed收益存储
|
|
|
|
|
|
func NewDBEarningStore(usageRepo *repository.UsageRepository) *DBEarningStore {
|
|
|
|
|
|
return &DBEarningStore{usageRepo: usageRepo}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBEarningStore) ListRecords(ctx context.Context, supplierID int64, startDate, endDate string, page, pageSize int) ([]*domain.EarningRecord, int, error) {
|
|
|
|
|
|
if s.usageRepo == nil {
|
|
|
|
|
|
return nil, 0, fmt.Errorf("usage repository not initialized")
|
|
|
|
|
|
}
|
|
|
|
|
|
return s.usageRepo.ListRecords(ctx, supplierID, startDate, endDate, page, pageSize)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *DBEarningStore) GetBillingSummary(ctx context.Context, supplierID int64, startDate, endDate string) (*domain.BillingSummary, error) {
|
|
|
|
|
|
if s.usageRepo == nil {
|
|
|
|
|
|
return nil, fmt.Errorf("usage repository not initialized")
|
|
|
|
|
|
}
|
|
|
|
|
|
return s.usageRepo.GetBillingSummary(ctx, supplierID, startDate, endDate)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 内存Backend适配器 ====================
|
|
|
|
|
|
|
|
|
|
|
|
// MemoryTokenBackend 内存token状态后端(临时实现,生产应使用DB-backed)
|
|
|
|
|
|
type MemoryTokenBackend struct {
|
|
|
|
|
|
revokedTokens map[string]string // tokenID -> status
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewMemoryTokenBackend 创建内存token后端
|
|
|
|
|
|
func NewMemoryTokenBackend() *MemoryTokenBackend {
|
|
|
|
|
|
return &MemoryTokenBackend{
|
|
|
|
|
|
revokedTokens: make(map[string]string),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CheckTokenStatus 检查token状态
|
|
|
|
|
|
func (b *MemoryTokenBackend) CheckTokenStatus(ctx context.Context, tokenID string) (string, error) {
|
|
|
|
|
|
// 默认所有token都是active的
|
|
|
|
|
|
if status, found := b.revokedTokens[tokenID]; found {
|
|
|
|
|
|
return status, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return "active", nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RevokeToken 吊销token
|
|
|
|
|
|
func (b *MemoryTokenBackend) RevokeToken(tokenID string) {
|
|
|
|
|
|
b.revokedTokens[tokenID] = "revoked"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 审计事件适配器 ====================
|
|
|
|
|
|
|
|
|
|
|
|
// AuditEmitterAdapter 将auditStore适配为middleware.AuditEmitter
|
|
|
|
|
|
type AuditEmitterAdapter struct {
|
|
|
|
|
|
store audit.AuditStore
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewAuditEmitterAdapter 创建审计事件适配器
|
|
|
|
|
|
func NewAuditEmitterAdapter(store audit.AuditStore) *AuditEmitterAdapter {
|
|
|
|
|
|
return &AuditEmitterAdapter{store: store}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Emit 发送审计事件
|
|
|
|
|
|
func (a *AuditEmitterAdapter) Emit(ctx context.Context, event middleware.AuditEvent) error {
|
|
|
|
|
|
if a.store == nil {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
// 转换middleware.AuditEvent为audit.Event
|
|
|
|
|
|
auditEvent := audit.Event{
|
|
|
|
|
|
EventID: event.RequestID,
|
|
|
|
|
|
ObjectType: "auth",
|
|
|
|
|
|
Action: event.EventName,
|
|
|
|
|
|
RequestID: event.RequestID,
|
|
|
|
|
|
ResultCode: event.ResultCode,
|
|
|
|
|
|
SourceIP: event.ClientIP,
|
|
|
|
|
|
}
|
|
|
|
|
|
a.store.Emit(ctx, auditEvent)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|