完善开放接口日志并修复调试页刷新
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"affiliate_dash/internal/middleware"
|
"affiliate_dash/internal/middleware"
|
||||||
"affiliate_dash/internal/model"
|
"affiliate_dash/internal/model"
|
||||||
|
"affiliate_dash/internal/pkg/openlog"
|
||||||
"affiliate_dash/internal/pkg/response"
|
"affiliate_dash/internal/pkg/response"
|
||||||
"affiliate_dash/internal/service"
|
"affiliate_dash/internal/service"
|
||||||
|
|
||||||
@@ -26,11 +27,14 @@ func NewOpenV1Handler(merchantSvc *service.MerchantService, fulfillmentSvc *serv
|
|||||||
func (h *OpenV1Handler) ListProducts(c *gin.Context) {
|
func (h *OpenV1Handler) ListProducts(c *gin.Context) {
|
||||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||||||
|
openlog.Info(c, "list_products start page=%d size=%d", page, size)
|
||||||
list, total, err := h.merchantSvc.ListMerchantProducts(middleware.GetMerchantID(c), page, size, true)
|
list, total, err := h.merchantSvc.ListMerchantProducts(middleware.GetMerchantID(c), page, size, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
openlog.Warn(c, "list_products fail err=%v", err)
|
||||||
response.ServerError(c, err.Error())
|
response.ServerError(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
openlog.Info(c, "list_products ok total=%d returned=%d", total, len(list))
|
||||||
response.Page(c, list, total, page, size)
|
response.Page(c, list, total, page, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,10 +49,12 @@ type openCreateOrderReq struct {
|
|||||||
func (h *OpenV1Handler) CreateOrder(c *gin.Context) {
|
func (h *OpenV1Handler) CreateOrder(c *gin.Context) {
|
||||||
var req openCreateOrderReq
|
var req openCreateOrderReq
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
openlog.Warn(c, "create_order bind_fail err=%v", err)
|
||||||
response.BadRequest(c, "参数错误:client_order_no 与 sku 必填")
|
response.BadRequest(c, "参数错误:client_order_no 与 sku 必填")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if key := c.GetHeader("Idempotency-Key"); key != "" && key != req.ClientOrderNo {
|
if key := c.GetHeader("Idempotency-Key"); key != "" && key != req.ClientOrderNo {
|
||||||
|
openlog.Warn(c, "create_order idempotency_key_mismatch header=%s body=%s", key, req.ClientOrderNo)
|
||||||
response.BadRequest(c, "Idempotency-Key 必须与 client_order_no 一致")
|
response.BadRequest(c, "Idempotency-Key 必须与 client_order_no 一致")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -56,12 +62,15 @@ func (h *OpenV1Handler) CreateOrder(c *gin.Context) {
|
|||||||
if len(req.Data) > 0 {
|
if len(req.Data) > 0 {
|
||||||
var decoded interface{}
|
var decoded interface{}
|
||||||
if err := json.Unmarshal(req.Data, &decoded); err != nil {
|
if err := json.Unmarshal(req.Data, &decoded); err != nil {
|
||||||
|
openlog.Warn(c, "create_order bad_data err=%v", err)
|
||||||
response.BadRequest(c, "data 必须是有效 JSON")
|
response.BadRequest(c, "data 必须是有效 JSON")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data = decoded
|
data = decoded
|
||||||
}
|
}
|
||||||
client := middleware.GetAPIClient(c)
|
client := middleware.GetAPIClient(c)
|
||||||
|
openlog.Info(c, "create_order start client_order_no=%s sku=%s quantity=%d buyer=%s",
|
||||||
|
req.ClientOrderNo, req.SKU, req.Quantity, req.BuyerReference)
|
||||||
result, err := h.fulfillmentSvc.CreateOrder(service.CreateFulfillmentOrderInput{
|
result, err := h.fulfillmentSvc.CreateOrder(service.CreateFulfillmentOrderInput{
|
||||||
MerchantID: middleware.GetMerchantID(c),
|
MerchantID: middleware.GetMerchantID(c),
|
||||||
APIClientID: client.ID,
|
APIClientID: client.ID,
|
||||||
@@ -72,6 +81,8 @@ func (h *OpenV1Handler) CreateOrder(c *gin.Context) {
|
|||||||
RequestData: data,
|
RequestData: data,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
openlog.Warn(c, "create_order fail client_order_no=%s sku=%s err=%v",
|
||||||
|
req.ClientOrderNo, req.SKU, err)
|
||||||
response.BadRequest(c, err.Error())
|
response.BadRequest(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -79,6 +90,8 @@ func (h *OpenV1Handler) CreateOrder(c *gin.Context) {
|
|||||||
if !result.Idempotent {
|
if !result.Idempotent {
|
||||||
status = http.StatusCreated
|
status = http.StatusCreated
|
||||||
}
|
}
|
||||||
|
openlog.Info(c, "create_order ok order_no=%s idempotent=%v amount=%d",
|
||||||
|
result.Order.OrderNo, result.Idempotent, result.Order.Amount)
|
||||||
c.JSON(status, response.Body{
|
c.JSON(status, response.Body{
|
||||||
Code: 0,
|
Code: 0,
|
||||||
Message: "ok",
|
Message: "ok",
|
||||||
@@ -90,8 +103,11 @@ func (h *OpenV1Handler) CreateOrder(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *OpenV1Handler) QueryOrder(c *gin.Context) {
|
func (h *OpenV1Handler) QueryOrder(c *gin.Context) {
|
||||||
order, err := h.fulfillmentSvc.GetOrder(middleware.GetMerchantID(c), c.Param("order_no"))
|
orderNo := c.Param("order_no")
|
||||||
|
openlog.Info(c, "query_order start order_no=%s", orderNo)
|
||||||
|
order, err := h.fulfillmentSvc.GetOrder(middleware.GetMerchantID(c), orderNo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
openlog.Warn(c, "query_order fail order_no=%s err=%v", orderNo, err)
|
||||||
if err.Error() == "订单不存在" {
|
if err.Error() == "订单不存在" {
|
||||||
response.NotFound(c, err.Error())
|
response.NotFound(c, err.Error())
|
||||||
return
|
return
|
||||||
@@ -99,6 +115,8 @@ func (h *OpenV1Handler) QueryOrder(c *gin.Context) {
|
|||||||
response.ServerError(c, err.Error())
|
response.ServerError(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
openlog.Info(c, "query_order ok order_no=%s status=%s payment=%s",
|
||||||
|
order.OrderNo, order.FulfillmentStatus, order.PaymentStatus)
|
||||||
response.OK(c, buildOpenOrderResponse(order))
|
response.OK(c, buildOpenOrderResponse(order))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,15 +127,20 @@ type openCancelOrderReq struct {
|
|||||||
func (h *OpenV1Handler) CancelOrder(c *gin.Context) {
|
func (h *OpenV1Handler) CancelOrder(c *gin.Context) {
|
||||||
var req openCancelOrderReq
|
var req openCancelOrderReq
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
openlog.Warn(c, "cancel_order bind_fail err=%v", err)
|
||||||
response.BadRequest(c, "参数错误")
|
response.BadRequest(c, "参数错误")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
orderNo := c.Param("order_no")
|
||||||
|
openlog.Info(c, "cancel_order start order_no=%s reason=%s", orderNo, req.Reason)
|
||||||
client := middleware.GetAPIClient(c)
|
client := middleware.GetAPIClient(c)
|
||||||
order, err := h.fulfillmentSvc.CancelOrder(middleware.GetMerchantID(c), client.ID, c.Param("order_no"), req.Reason)
|
order, err := h.fulfillmentSvc.CancelOrder(middleware.GetMerchantID(c), client.ID, orderNo, req.Reason)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
openlog.Warn(c, "cancel_order fail order_no=%s err=%v", orderNo, err)
|
||||||
response.BadRequest(c, err.Error())
|
response.BadRequest(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
openlog.Info(c, "cancel_order ok order_no=%s", order.OrderNo)
|
||||||
response.OK(c, buildOpenOrderResponse(order))
|
response.OK(c, buildOpenOrderResponse(order))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +155,7 @@ type openShipNotifyReq struct {
|
|||||||
func (h *OpenV1Handler) ShipNotify(c *gin.Context) {
|
func (h *OpenV1Handler) ShipNotify(c *gin.Context) {
|
||||||
var req openShipNotifyReq
|
var req openShipNotifyReq
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
openlog.Warn(c, "client_ship_notify bind_fail err=%v", err)
|
||||||
response.BadRequest(c, "参数错误:ship_status 必填")
|
response.BadRequest(c, "参数错误:ship_status 必填")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -144,12 +168,14 @@ func (h *OpenV1Handler) ShipNotify(c *gin.Context) {
|
|||||||
case "failed":
|
case "failed":
|
||||||
status = model.FulfillmentStatusFailed
|
status = model.FulfillmentStatusFailed
|
||||||
default:
|
default:
|
||||||
|
openlog.Warn(c, "client_ship_notify bad_status=%s", req.ShipStatus)
|
||||||
response.BadRequest(c, "ship_status 仅支持 processing、success、failed")
|
response.BadRequest(c, "ship_status 仅支持 processing、success、failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var result interface{}
|
var result interface{}
|
||||||
if len(req.Result) > 0 {
|
if len(req.Result) > 0 {
|
||||||
if err := json.Unmarshal(req.Result, &result); err != nil {
|
if err := json.Unmarshal(req.Result, &result); err != nil {
|
||||||
|
openlog.Warn(c, "client_ship_notify bad_result err=%v", err)
|
||||||
response.BadRequest(c, "result 必须是有效 JSON")
|
response.BadRequest(c, "result 必须是有效 JSON")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -160,9 +186,12 @@ func (h *OpenV1Handler) ShipNotify(c *gin.Context) {
|
|||||||
orderNo = req.OrderNo
|
orderNo = req.OrderNo
|
||||||
}
|
}
|
||||||
if orderNo == "" {
|
if orderNo == "" {
|
||||||
|
openlog.Warn(c, "client_ship_notify missing_order_no")
|
||||||
response.BadRequest(c, "order_no 必填")
|
response.BadRequest(c, "order_no 必填")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
openlog.Info(c, "client_ship_notify start order_no=%s ship_status=%s provider_no=%s",
|
||||||
|
orderNo, req.ShipStatus, req.ProviderOrderNo)
|
||||||
order, err := h.fulfillmentSvc.UpdateFulfillment(service.FulfillmentUpdateInput{
|
order, err := h.fulfillmentSvc.UpdateFulfillment(service.FulfillmentUpdateInput{
|
||||||
MerchantID: middleware.GetMerchantID(c),
|
MerchantID: middleware.GetMerchantID(c),
|
||||||
APIClientID: client.ID,
|
APIClientID: client.ID,
|
||||||
@@ -173,18 +202,26 @@ func (h *OpenV1Handler) ShipNotify(c *gin.Context) {
|
|||||||
ResultData: result,
|
ResultData: result,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
openlog.Warn(c, "client_ship_notify fail order_no=%s ship_status=%s err=%v",
|
||||||
|
orderNo, req.ShipStatus, err)
|
||||||
response.BadRequest(c, err.Error())
|
response.BadRequest(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
openlog.Info(c, "client_ship_notify ok order_no=%s status=%s",
|
||||||
|
order.OrderNo, order.FulfillmentStatus)
|
||||||
response.OK(c, buildOpenOrderResponse(order))
|
response.OK(c, buildOpenOrderResponse(order))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *OpenV1Handler) GetWallet(c *gin.Context) {
|
func (h *OpenV1Handler) GetWallet(c *gin.Context) {
|
||||||
|
openlog.Info(c, "get_wallet start")
|
||||||
wallet, err := h.fulfillmentSvc.GetWallet(middleware.GetMerchantID(c))
|
wallet, err := h.fulfillmentSvc.GetWallet(middleware.GetMerchantID(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
openlog.Warn(c, "get_wallet fail err=%v", err)
|
||||||
response.ServerError(c, err.Error())
|
response.ServerError(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
openlog.Info(c, "get_wallet ok balance=%d frozen=%d",
|
||||||
|
wallet.AvailableBalance, wallet.FrozenBalance)
|
||||||
response.OK(c, wallet)
|
response.OK(c, wallet)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,18 +238,18 @@ func buildOpenOrderResponse(order *model.FulfillmentOrder) gin.H {
|
|||||||
"sku": order.ProductSKU,
|
"sku": order.ProductSKU,
|
||||||
"name": order.ProductName,
|
"name": order.ProductName,
|
||||||
},
|
},
|
||||||
"quantity": order.Quantity,
|
"quantity": order.Quantity,
|
||||||
"base_amount": order.BaseAmount,
|
"base_amount": order.BaseAmount,
|
||||||
"fee_type": order.FeeType,
|
"fee_type": order.FeeType,
|
||||||
"service_fee_amount": order.ServiceFeeAmount,
|
"service_fee_amount": order.ServiceFeeAmount,
|
||||||
"amount": order.Amount,
|
"amount": order.Amount,
|
||||||
"currency": order.Currency,
|
"currency": order.Currency,
|
||||||
"buyer_reference": order.BuyerReference,
|
"buyer_reference": order.BuyerReference,
|
||||||
"provider_order_no": order.ProviderOrderNo,
|
"provider_order_no": order.ProviderOrderNo,
|
||||||
"failure_reason": order.FailureReason,
|
"failure_reason": order.FailureReason,
|
||||||
"created_at": order.CreatedAt,
|
"created_at": order.CreatedAt,
|
||||||
"delivered_at": order.DeliveredAt,
|
"delivered_at": order.DeliveredAt,
|
||||||
"cancelled_at": order.CancelledAt,
|
"cancelled_at": order.CancelledAt,
|
||||||
}
|
}
|
||||||
if json.Valid([]byte(order.RequestData)) {
|
if json.Valid([]byte(order.RequestData)) {
|
||||||
data["data"] = json.RawMessage(order.RequestData)
|
data["data"] = json.RawMessage(order.RequestData)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ func OpenAuth(cfg OpenAuthConfig) gin.HandlerFunc {
|
|||||||
c.Header("X-Request-Id", reqID)
|
c.Header("X-Request-Id", reqID)
|
||||||
|
|
||||||
if cfg.DB == nil || cfg.Codec == nil {
|
if cfg.DB == nil || cfg.Codec == nil {
|
||||||
|
openlog.Warn(c, "open_auth uninitialized")
|
||||||
response.ServerError(c, "开放接口认证服务未初始化")
|
response.ServerError(c, "开放接口认证服务未初始化")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -57,17 +58,20 @@ func OpenAuth(cfg OpenAuthConfig) gin.HandlerFunc {
|
|||||||
nonce := c.GetHeader("X-Nonce")
|
nonce := c.GetHeader("X-Nonce")
|
||||||
sign := c.GetHeader("X-Sign")
|
sign := c.GetHeader("X-Sign")
|
||||||
if appKey == "" || timestamp == "" || nonce == "" || sign == "" {
|
if appKey == "" || timestamp == "" || nonce == "" || sign == "" {
|
||||||
|
openlog.Warn(c, "open_auth missing_headers app_key=%s", openlog.MaskKey(appKey))
|
||||||
response.Unauthorized(c, "缺少鉴权头:X-App-Key、X-Timestamp、X-Nonce、X-Sign")
|
response.Unauthorized(c, "缺少鉴权头:X-App-Key、X-Timestamp、X-Nonce、X-Sign")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(nonce) < 8 || len(nonce) > 96 {
|
if len(nonce) < 8 || len(nonce) > 96 {
|
||||||
|
openlog.Warn(c, "open_auth bad_nonce_len len=%d app_key=%s", len(nonce), openlog.MaskKey(appKey))
|
||||||
response.Unauthorized(c, "X-Nonce 长度需在 8~96 之间")
|
response.Unauthorized(c, "X-Nonce 长度需在 8~96 之间")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ts, err := strconv.ParseInt(timestamp, 10, 64)
|
ts, err := strconv.ParseInt(timestamp, 10, 64)
|
||||||
if err != nil || abs64(time.Now().Unix()-ts) > cfg.SkewSeconds {
|
if err != nil || abs64(time.Now().Unix()-ts) > cfg.SkewSeconds {
|
||||||
|
openlog.Warn(c, "open_auth expired app_key=%s ts=%s skew=%d", openlog.MaskKey(appKey), timestamp, cfg.SkewSeconds)
|
||||||
response.Unauthorized(c, "请求已过期或 X-Timestamp 格式错误")
|
response.Unauthorized(c, "请求已过期或 X-Timestamp 格式错误")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -75,6 +79,7 @@ func OpenAuth(cfg OpenAuthConfig) gin.HandlerFunc {
|
|||||||
|
|
||||||
bodyBytes, err := io.ReadAll(c.Request.Body)
|
bodyBytes, err := io.ReadAll(c.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
openlog.Warn(c, "open_auth read_body_fail err=%v", err)
|
||||||
response.BadRequest(c, "读取请求体失败")
|
response.BadRequest(c, "读取请求体失败")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -83,17 +88,20 @@ func OpenAuth(cfg OpenAuthConfig) gin.HandlerFunc {
|
|||||||
|
|
||||||
var client model.APIClient
|
var client model.APIClient
|
||||||
if err := cfg.DB.Where("app_key = ? AND status = ?", appKey, model.APIClientStatusActive).First(&client).Error; err != nil {
|
if err := cfg.DB.Where("app_key = ? AND status = ?", appKey, model.APIClientStatusActive).First(&client).Error; err != nil {
|
||||||
|
openlog.Warn(c, "open_auth invalid_key app_key=%s", openlog.MaskKey(appKey))
|
||||||
response.Unauthorized(c, "无效的 API Key")
|
response.Unauthorized(c, "无效的 API Key")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if client.ExpiresAt != nil && client.ExpiresAt.Before(time.Now()) {
|
if client.ExpiresAt != nil && client.ExpiresAt.Before(time.Now()) {
|
||||||
|
openlog.Warn(c, "open_auth key_expired app_key=%s client_id=%d", openlog.MaskKey(appKey), client.ID)
|
||||||
response.Unauthorized(c, "API Key 已过期")
|
response.Unauthorized(c, "API Key 已过期")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
secret, err := cfg.Codec.Decrypt(client.SecretCiphertext)
|
secret, err := cfg.Codec.Decrypt(client.SecretCiphertext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
openlog.Warn(c, "open_auth decrypt_fail app_key=%s client_id=%d err=%v", openlog.MaskKey(appKey), client.ID, err)
|
||||||
response.ServerError(c, "API 客户端密钥不可用")
|
response.ServerError(c, "API 客户端密钥不可用")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -103,6 +111,10 @@ func OpenAuth(cfg OpenAuthConfig) gin.HandlerFunc {
|
|||||||
path := c.Request.URL.Path
|
path := c.Request.URL.Path
|
||||||
expected := BuildOpenV1Sign(secret, appKey, timestamp, nonce, method, path, bodyBytes)
|
expected := BuildOpenV1Sign(secret, appKey, timestamp, nonce, method, path, bodyBytes)
|
||||||
if !hmac.Equal([]byte(strings.ToLower(sign)), []byte(expected)) {
|
if !hmac.Equal([]byte(strings.ToLower(sign)), []byte(expected)) {
|
||||||
|
bodyHash := sha256.Sum256(bodyBytes)
|
||||||
|
openlog.Warn(c, "open_auth sign_mismatch app_key=%s method=%s path=%s body_sha256=%s sign=%s expected=%s",
|
||||||
|
openlog.MaskKey(appKey), method, path, hex.EncodeToString(bodyHash[:]),
|
||||||
|
openlog.MaskSign(sign), openlog.MaskSign(expected))
|
||||||
response.Unauthorized(c, "签名校验失败")
|
response.Unauthorized(c, "签名校验失败")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -118,11 +130,13 @@ func OpenAuth(cfg OpenAuthConfig) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
created := cfg.DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&nonceRow)
|
created := cfg.DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&nonceRow)
|
||||||
if created.Error != nil {
|
if created.Error != nil {
|
||||||
|
openlog.Warn(c, "open_auth nonce_db_fail app_key=%s err=%v", openlog.MaskKey(appKey), created.Error)
|
||||||
response.ServerError(c, "记录请求 nonce 失败")
|
response.ServerError(c, "记录请求 nonce 失败")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if created.RowsAffected == 0 {
|
if created.RowsAffected == 0 {
|
||||||
|
openlog.Warn(c, "open_auth nonce_replay app_key=%s nonce=%s", openlog.MaskKey(appKey), nonce)
|
||||||
response.Unauthorized(c, "重复的 X-Nonce(请勿重放请求)")
|
response.Unauthorized(c, "重复的 X-Nonce(请勿重放请求)")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -133,6 +147,9 @@ func OpenAuth(cfg OpenAuthConfig) gin.HandlerFunc {
|
|||||||
c.Set(CtxMerchantID, client.MerchantID)
|
c.Set(CtxMerchantID, client.MerchantID)
|
||||||
c.Set(openlog.CtxAPIKey, appKey)
|
c.Set(openlog.CtxAPIKey, appKey)
|
||||||
_ = cfg.DB.Model(&model.APIClient{}).Where("id = ?", client.ID).Update("last_used_at", now).Error
|
_ = cfg.DB.Model(&model.APIClient{}).Where("id = ?", client.ID).Update("last_used_at", now).Error
|
||||||
|
|
||||||
|
openlog.Info(c, "open_auth ok app_key=%s merchant_id=%d method=%s path=%s body_size=%d",
|
||||||
|
openlog.MaskKey(appKey), client.MerchantID, method, path, len(bodyBytes))
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ func SourceOpenAuth(cfg SourceOpenAuthConfig) gin.HandlerFunc {
|
|||||||
c.Header("X-Request-Id", reqID)
|
c.Header("X-Request-Id", reqID)
|
||||||
|
|
||||||
if cfg.APIKey == "" || cfg.APISecret == "" {
|
if cfg.APIKey == "" || cfg.APISecret == "" {
|
||||||
|
openlog.Warn(c, "source_open_auth uninitialized")
|
||||||
response.ServerError(c, "服务端未配置 OPEN_API_KEY / OPEN_API_SECRET")
|
response.ServerError(c, "服务端未配置 OPEN_API_KEY / OPEN_API_SECRET")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -69,22 +70,26 @@ func SourceOpenAuth(cfg SourceOpenAuthConfig) gin.HandlerFunc {
|
|||||||
nonce := c.GetHeader("X-Nonce")
|
nonce := c.GetHeader("X-Nonce")
|
||||||
sign := c.GetHeader("X-Sign")
|
sign := c.GetHeader("X-Sign")
|
||||||
if apiKey == "" || timestamp == "" || nonce == "" || sign == "" {
|
if apiKey == "" || timestamp == "" || nonce == "" || sign == "" {
|
||||||
|
openlog.Warn(c, "source_open_auth missing_headers")
|
||||||
response.Unauthorized(c, "缺少鉴权头:需要 X-Api-Key、X-Timestamp、X-Nonce、X-Sign")
|
response.Unauthorized(c, "缺少鉴权头:需要 X-Api-Key、X-Timestamp、X-Nonce、X-Sign")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if apiKey != cfg.APIKey {
|
if apiKey != cfg.APIKey {
|
||||||
|
openlog.Warn(c, "source_open_auth invalid_key api_key=%s", openlog.MaskKey(apiKey))
|
||||||
response.Unauthorized(c, "无效的 API Key")
|
response.Unauthorized(c, "无效的 API Key")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(nonce) < 8 || len(nonce) > 64 {
|
if len(nonce) < 8 || len(nonce) > 64 {
|
||||||
|
openlog.Warn(c, "source_open_auth bad_nonce_len len=%d", len(nonce))
|
||||||
response.Unauthorized(c, "X-Nonce 长度需在 8~64 之间")
|
response.Unauthorized(c, "X-Nonce 长度需在 8~64 之间")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ts, err := strconv.ParseInt(timestamp, 10, 64)
|
ts, err := strconv.ParseInt(timestamp, 10, 64)
|
||||||
if err != nil || abs64(time.Now().Unix()-ts) > cfg.SkewSeconds {
|
if err != nil || abs64(time.Now().Unix()-ts) > cfg.SkewSeconds {
|
||||||
|
openlog.Warn(c, "source_open_auth expired ts=%s skew=%d", timestamp, cfg.SkewSeconds)
|
||||||
response.Unauthorized(c, "请求已过期或 X-Timestamp 格式错误")
|
response.Unauthorized(c, "请求已过期或 X-Timestamp 格式错误")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -92,6 +97,7 @@ func SourceOpenAuth(cfg SourceOpenAuthConfig) gin.HandlerFunc {
|
|||||||
|
|
||||||
bodyBytes, err := io.ReadAll(c.Request.Body)
|
bodyBytes, err := io.ReadAll(c.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
openlog.Warn(c, "source_open_auth read_body_fail err=%v", err)
|
||||||
response.BadRequest(c, "读取请求体失败")
|
response.BadRequest(c, "读取请求体失败")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -99,16 +105,23 @@ func SourceOpenAuth(cfg SourceOpenAuthConfig) gin.HandlerFunc {
|
|||||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||||
|
|
||||||
if store.seen(apiKey+":"+nonce, time.Now().Unix(), cfg.SkewSeconds) {
|
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(请勿重放请求)")
|
response.Unauthorized(c, "重复的 X-Nonce(请勿重放请求)")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
expected := BuildOpenSign(apiKey, cfg.APISecret, timestamp, nonce, c.Request.Method, c.Request.URL.Path, string(bodyBytes))
|
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)) {
|
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",
|
||||||
|
c.Request.Method, c.Request.URL.Path, openlog.Truncate(string(bodyBytes), 200),
|
||||||
|
openlog.MaskSign(sign), openlog.MaskSign(expected))
|
||||||
response.Unauthorized(c, "签名校验失败")
|
response.Unauthorized(c, "签名校验失败")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
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()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export default defineConfig(({ mode }) => {
|
|||||||
server: {
|
server: {
|
||||||
port,
|
port,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
// 只代理真实后端 API,避免 /api-debug 这类前端路由刷新时被误转发到后端。
|
||||||
|
'^/api(/|$)': {
|
||||||
target: apiTarget,
|
target: apiTarget,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user