- 添加 ErrNotFound 和 ErrConcurrencyConflict 错误定义 - 修复 pgx.NullTime 替换为 *time.Time - 修复 db.go 事务类型 (pgx.Tx vs pgxpool.Tx) - 移除未使用的导入和变量 - 修复 NewSupplyAPI 调用参数 - 修复中间件链路 handler 类型问题 - 修复适配器类型引用 (storage.InMemoryAccountStore 等) - 所有测试通过 Test: go test ./...
320 lines
7.3 KiB
Go
320 lines
7.3 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"lijiaoqiao/supply-api/internal/domain"
|
|
)
|
|
|
|
// 错误定义
|
|
var ErrNotFound = errors.New("resource not found")
|
|
|
|
// 内存账号存储
|
|
type InMemoryAccountStore struct {
|
|
mu sync.RWMutex
|
|
accounts map[int64]*domain.Account
|
|
nextID int64
|
|
}
|
|
|
|
func NewInMemoryAccountStore() *InMemoryAccountStore {
|
|
return &InMemoryAccountStore{
|
|
accounts: make(map[int64]*domain.Account),
|
|
nextID: 1,
|
|
}
|
|
}
|
|
|
|
func (s *InMemoryAccountStore) Create(ctx context.Context, account *domain.Account) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
account.ID = s.nextID
|
|
s.nextID++
|
|
account.CreatedAt = time.Now()
|
|
account.UpdatedAt = time.Now()
|
|
s.accounts[account.ID] = account
|
|
return nil
|
|
}
|
|
|
|
func (s *InMemoryAccountStore) GetByID(ctx context.Context, supplierID, id int64) (*domain.Account, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
account, ok := s.accounts[id]
|
|
if !ok || account.SupplierID != supplierID {
|
|
return nil, ErrNotFound
|
|
}
|
|
return account, nil
|
|
}
|
|
|
|
func (s *InMemoryAccountStore) Update(ctx context.Context, account *domain.Account) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
existing, ok := s.accounts[account.ID]
|
|
if !ok || existing.SupplierID != account.SupplierID {
|
|
return ErrNotFound
|
|
}
|
|
account.UpdatedAt = time.Now()
|
|
s.accounts[account.ID] = account
|
|
return nil
|
|
}
|
|
|
|
func (s *InMemoryAccountStore) List(ctx context.Context, supplierID int64) ([]*domain.Account, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
var result []*domain.Account
|
|
for _, account := range s.accounts {
|
|
if account.SupplierID == supplierID {
|
|
result = append(result, account)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// 内存套餐存储
|
|
type InMemoryPackageStore struct {
|
|
mu sync.RWMutex
|
|
packages map[int64]*domain.Package
|
|
nextID int64
|
|
}
|
|
|
|
func NewInMemoryPackageStore() *InMemoryPackageStore {
|
|
return &InMemoryPackageStore{
|
|
packages: make(map[int64]*domain.Package),
|
|
nextID: 1,
|
|
}
|
|
}
|
|
|
|
func (s *InMemoryPackageStore) Create(ctx context.Context, pkg *domain.Package) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
pkg.ID = s.nextID
|
|
s.nextID++
|
|
pkg.CreatedAt = time.Now()
|
|
pkg.UpdatedAt = time.Now()
|
|
s.packages[pkg.ID] = pkg
|
|
return nil
|
|
}
|
|
|
|
func (s *InMemoryPackageStore) GetByID(ctx context.Context, supplierID, id int64) (*domain.Package, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
pkg, ok := s.packages[id]
|
|
if !ok || pkg.SupplierID != supplierID {
|
|
return nil, ErrNotFound
|
|
}
|
|
return pkg, nil
|
|
}
|
|
|
|
func (s *InMemoryPackageStore) Update(ctx context.Context, pkg *domain.Package) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
existing, ok := s.packages[pkg.ID]
|
|
if !ok || existing.SupplierID != pkg.SupplierID {
|
|
return ErrNotFound
|
|
}
|
|
pkg.UpdatedAt = time.Now()
|
|
s.packages[pkg.ID] = pkg
|
|
return nil
|
|
}
|
|
|
|
func (s *InMemoryPackageStore) List(ctx context.Context, supplierID int64) ([]*domain.Package, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
var result []*domain.Package
|
|
for _, pkg := range s.packages {
|
|
if pkg.SupplierID == supplierID {
|
|
result = append(result, pkg)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// 内存结算存储
|
|
type InMemorySettlementStore struct {
|
|
mu sync.RWMutex
|
|
settlements map[int64]*domain.Settlement
|
|
nextID int64
|
|
}
|
|
|
|
func NewInMemorySettlementStore() *InMemorySettlementStore {
|
|
return &InMemorySettlementStore{
|
|
settlements: make(map[int64]*domain.Settlement),
|
|
nextID: 1,
|
|
}
|
|
}
|
|
|
|
func (s *InMemorySettlementStore) Create(ctx context.Context, settlement *domain.Settlement) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
settlement.ID = s.nextID
|
|
s.nextID++
|
|
settlement.CreatedAt = time.Now()
|
|
settlement.UpdatedAt = time.Now()
|
|
s.settlements[settlement.ID] = settlement
|
|
return nil
|
|
}
|
|
|
|
func (s *InMemorySettlementStore) GetByID(ctx context.Context, supplierID, id int64) (*domain.Settlement, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
settlement, ok := s.settlements[id]
|
|
if !ok || settlement.SupplierID != supplierID {
|
|
return nil, ErrNotFound
|
|
}
|
|
return settlement, nil
|
|
}
|
|
|
|
func (s *InMemorySettlementStore) Update(ctx context.Context, settlement *domain.Settlement) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
existing, ok := s.settlements[settlement.ID]
|
|
if !ok || existing.SupplierID != settlement.SupplierID {
|
|
return ErrNotFound
|
|
}
|
|
settlement.UpdatedAt = time.Now()
|
|
s.settlements[settlement.ID] = settlement
|
|
return nil
|
|
}
|
|
|
|
func (s *InMemorySettlementStore) List(ctx context.Context, supplierID int64) ([]*domain.Settlement, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
var result []*domain.Settlement
|
|
for _, settlement := range s.settlements {
|
|
if settlement.SupplierID == supplierID {
|
|
result = append(result, settlement)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *InMemorySettlementStore) GetWithdrawableBalance(ctx context.Context, supplierID int64) (float64, error) {
|
|
return 10000.0, nil
|
|
}
|
|
|
|
// 内存收益存储
|
|
type InMemoryEarningStore struct {
|
|
mu sync.RWMutex
|
|
records map[int64]*domain.EarningRecord
|
|
nextID int64
|
|
}
|
|
|
|
func NewInMemoryEarningStore() *InMemoryEarningStore {
|
|
return &InMemoryEarningStore{
|
|
records: make(map[int64]*domain.EarningRecord),
|
|
nextID: 1,
|
|
}
|
|
}
|
|
|
|
func (s *InMemoryEarningStore) ListRecords(ctx context.Context, supplierID int64, startDate, endDate string, page, pageSize int) ([]*domain.EarningRecord, int, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
var result []*domain.EarningRecord
|
|
for _, record := range s.records {
|
|
if record.SupplierID == supplierID {
|
|
result = append(result, record)
|
|
}
|
|
}
|
|
|
|
total := len(result)
|
|
start := (page - 1) * pageSize
|
|
end := start + pageSize
|
|
|
|
if start >= total {
|
|
return []*domain.EarningRecord{}, total, nil
|
|
}
|
|
if end > total {
|
|
end = total
|
|
}
|
|
|
|
return result[start:end], total, nil
|
|
}
|
|
|
|
func (s *InMemoryEarningStore) GetBillingSummary(ctx context.Context, supplierID int64, startDate, endDate string) (*domain.BillingSummary, error) {
|
|
return &domain.BillingSummary{
|
|
Period: domain.BillingPeriod{
|
|
Start: startDate,
|
|
End: endDate,
|
|
},
|
|
Summary: domain.BillingTotal{
|
|
TotalRevenue: 10000.0,
|
|
TotalOrders: 100,
|
|
TotalUsage: 1000000,
|
|
TotalRequests: 50000,
|
|
AvgSuccessRate: 99.5,
|
|
PlatformFee: 100.0,
|
|
NetEarnings: 9900.0,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// 内存幂等存储
|
|
type InMemoryIdempotencyStore struct {
|
|
mu sync.RWMutex
|
|
records map[string]*IdempotencyRecord
|
|
}
|
|
|
|
type IdempotencyRecord struct {
|
|
Key string
|
|
Status string // processing, succeeded, failed
|
|
Response interface{}
|
|
CreatedAt time.Time
|
|
ExpiresAt time.Time
|
|
}
|
|
|
|
func NewInMemoryIdempotencyStore() *InMemoryIdempotencyStore {
|
|
return &InMemoryIdempotencyStore{
|
|
records: make(map[string]*IdempotencyRecord),
|
|
}
|
|
}
|
|
|
|
func (s *InMemoryIdempotencyStore) Get(key string) (*IdempotencyRecord, bool) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
record, ok := s.records[key]
|
|
if ok && record.ExpiresAt.After(time.Now()) {
|
|
return record, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (s *InMemoryIdempotencyStore) SetProcessing(key string, ttl time.Duration) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.records[key] = &IdempotencyRecord{
|
|
Key: key,
|
|
Status: "processing",
|
|
CreatedAt: time.Now(),
|
|
ExpiresAt: time.Now().Add(ttl),
|
|
}
|
|
}
|
|
|
|
func (s *InMemoryIdempotencyStore) SetSuccess(key string, response interface{}, ttl time.Duration) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.records[key] = &IdempotencyRecord{
|
|
Key: key,
|
|
Status: "succeeded",
|
|
Response: response,
|
|
CreatedAt: time.Now(),
|
|
ExpiresAt: time.Now().Add(ttl),
|
|
}
|
|
}
|