优化发货接口对接流程
This commit is contained in:
@@ -6,53 +6,31 @@ import (
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"affiliate_dash/internal/model"
|
||||
"affiliate_dash/internal/pkg/openlog"
|
||||
"affiliate_dash/internal/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// SourceOpenAuthConfig 是原上游发货接口鉴权配置,保持 X-Api-Key 兼容。
|
||||
type SourceOpenAuthConfig struct {
|
||||
DB *gorm.DB
|
||||
APIKey string
|
||||
APISecret string
|
||||
SkewSeconds int64
|
||||
Debug bool
|
||||
}
|
||||
|
||||
type sourceNonceStore struct {
|
||||
mu sync.Mutex
|
||||
data map[string]int64
|
||||
}
|
||||
|
||||
func newSourceNonceStore() *sourceNonceStore {
|
||||
return &sourceNonceStore{data: make(map[string]int64)}
|
||||
}
|
||||
|
||||
func (s *sourceNonceStore) seen(nonce string, now, ttl int64) bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for key, expiresAt := range s.data {
|
||||
if expiresAt < now {
|
||||
delete(s.data, key)
|
||||
}
|
||||
}
|
||||
if expiresAt, ok := s.data[nonce]; ok && expiresAt >= now {
|
||||
return true
|
||||
}
|
||||
s.data[nonce] = now + ttl
|
||||
return false
|
||||
}
|
||||
|
||||
// SourceOpenAuth 保持现有上游对接签名算法不变:X-Api-Key + 字典序 HMAC。
|
||||
func SourceOpenAuth(cfg SourceOpenAuthConfig) gin.HandlerFunc {
|
||||
if cfg.SkewSeconds <= 0 {
|
||||
cfg.SkewSeconds = 300
|
||||
}
|
||||
store := newSourceNonceStore()
|
||||
return func(c *gin.Context) {
|
||||
reqID := openlog.EnsureReqID(c)
|
||||
side, action := openlog.ScopeFromPath(openlog.SideSource, c.Request.Method, c.Request.URL.Path)
|
||||
@@ -61,6 +39,12 @@ func SourceOpenAuth(cfg SourceOpenAuthConfig) gin.HandlerFunc {
|
||||
c.Set(openlog.CtxStart, time.Now())
|
||||
c.Header("X-Request-Id", reqID)
|
||||
|
||||
if cfg.DB == nil {
|
||||
openlog.Warn(c, "source_open_auth uninitialized_db")
|
||||
response.ServerError(c, "源头开放接口认证服务未初始化")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if cfg.APIKey == "" || cfg.APISecret == "" {
|
||||
openlog.Warn(c, "source_open_auth uninitialized")
|
||||
response.ServerError(c, "服务端未配置 OPEN_API_KEY / OPEN_API_SECRET")
|
||||
@@ -105,13 +89,8 @@ func SourceOpenAuth(cfg SourceOpenAuthConfig) gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
c.Set(openlog.CtxBody, string(bodyBytes))
|
||||
|
||||
if store.seen(apiKey+":"+nonce, time.Now().Unix(), cfg.SkewSeconds) {
|
||||
openlog.Warn(c, "source_open_auth nonce_replay nonce=%s", nonce)
|
||||
response.Unauthorized(c, "重复的 X-Nonce(请勿重放请求)")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
expected := BuildOpenSign(apiKey, cfg.APISecret, timestamp, nonce, c.Request.Method, c.Request.URL.Path, string(bodyBytes))
|
||||
if !hmac.Equal([]byte(strings.ToLower(sign)), []byte(expected)) {
|
||||
openlog.Warn(c, "source_open_auth sign_mismatch method=%s path=%s body=%s sign=%s expected=%s",
|
||||
@@ -122,6 +101,26 @@ func SourceOpenAuth(cfg SourceOpenAuthConfig) gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
_ = cfg.DB.Where("expires_at < ?", now).Delete(&model.SourceAPINonce{}).Error
|
||||
created := cfg.DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&model.SourceAPINonce{
|
||||
APIKey: apiKey,
|
||||
Nonce: nonce,
|
||||
ExpiresAt: now.Add(time.Duration(cfg.SkewSeconds) * time.Second),
|
||||
})
|
||||
if created.Error != nil {
|
||||
openlog.Warn(c, "source_open_auth nonce_db_fail err=%v", created.Error)
|
||||
response.ServerError(c, "记录请求 nonce 失败")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if created.RowsAffected == 0 {
|
||||
openlog.Warn(c, "source_open_auth nonce_replay nonce=%s", nonce)
|
||||
response.Unauthorized(c, "重复的 X-Nonce(请勿重放请求)")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
openlog.Info(c, "source_open_auth ok method=%s path=%s body_size=%d",
|
||||
c.Request.Method, c.Request.URL.Path, len(bodyBytes))
|
||||
c.Next()
|
||||
|
||||
Reference in New Issue
Block a user