From a16e518b6c7490e49b082625e9681ec853d733a2 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 30 Jul 2026 21:07:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=BC=80=E6=94=BE=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=97=A5=E5=BF=97=E5=88=86=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/handler/open.go | 2 + backend/internal/handler/open_v1.go | 5 + backend/internal/middleware/open_auth.go | 2 + .../internal/middleware/source_open_auth.go | 2 + backend/internal/pkg/openlog/openlog.go | 125 ++++++++++++++++-- backend/internal/pkg/openlog/openlog_test.go | 83 ++++++++++++ 6 files changed, 210 insertions(+), 9 deletions(-) create mode 100644 backend/internal/pkg/openlog/openlog_test.go diff --git a/backend/internal/handler/open.go b/backend/internal/handler/open.go index fc27dfb..9a9d8cc 100644 --- a/backend/internal/handler/open.go +++ b/backend/internal/handler/open.go @@ -23,6 +23,7 @@ func NewOpenHandler(fulfillmentSvc *service.FulfillmentService) *OpenHandler { // QueryOrder GET /api/open/v1/orders/:order_no // 上游输入店铺订单号,查询商品与是否可发货 func (h *OpenHandler) QueryOrder(c *gin.Context) { + openlog.SetAction(c, openlog.ActionQuery) orderNo := c.Param("order_no") if orderNo == "" { orderNo = c.Query("order_no") @@ -67,6 +68,7 @@ type shipNotifyReq struct { // ShipNotify POST /api/open/v1/orders/ship-notify // 上游发货后推送结果,同步订单状态 func (h *OpenHandler) ShipNotify(c *gin.Context) { + openlog.SetAction(c, openlog.ActionPush) var req shipNotifyReq if err := c.ShouldBindJSON(&req); err != nil { openlog.Warn(c, "ship_notify bind_fail err=%v", err) diff --git a/backend/internal/handler/open_v1.go b/backend/internal/handler/open_v1.go index c993cf1..f803ab1 100644 --- a/backend/internal/handler/open_v1.go +++ b/backend/internal/handler/open_v1.go @@ -25,6 +25,7 @@ func NewOpenV1Handler(merchantSvc *service.MerchantService, fulfillmentSvc *serv } func (h *OpenV1Handler) ListProducts(c *gin.Context) { + openlog.SetAction(c, openlog.ActionList) page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) size, _ := strconv.Atoi(c.DefaultQuery("size", "20")) openlog.Info(c, "list_products start page=%d size=%d", page, size) @@ -47,6 +48,7 @@ type openCreateOrderReq struct { } func (h *OpenV1Handler) CreateOrder(c *gin.Context) { + openlog.SetAction(c, openlog.ActionCreate) var req openCreateOrderReq if err := c.ShouldBindJSON(&req); err != nil { openlog.Warn(c, "create_order bind_fail err=%v", err) @@ -103,6 +105,7 @@ func (h *OpenV1Handler) CreateOrder(c *gin.Context) { } func (h *OpenV1Handler) QueryOrder(c *gin.Context) { + openlog.SetAction(c, openlog.ActionQuery) orderNo := c.Param("order_no") openlog.Info(c, "query_order start order_no=%s", orderNo) order, err := h.fulfillmentSvc.GetOrder(middleware.GetMerchantID(c), orderNo) @@ -125,6 +128,7 @@ type openCancelOrderReq struct { } func (h *OpenV1Handler) CancelOrder(c *gin.Context) { + openlog.SetAction(c, openlog.ActionCancel) var req openCancelOrderReq if err := c.ShouldBindJSON(&req); err != nil { openlog.Warn(c, "cancel_order bind_fail err=%v", err) @@ -145,6 +149,7 @@ func (h *OpenV1Handler) CancelOrder(c *gin.Context) { } func (h *OpenV1Handler) GetWallet(c *gin.Context) { + openlog.SetAction(c, openlog.ActionWallet) openlog.Info(c, "get_wallet start") wallet, err := h.fulfillmentSvc.GetWallet(middleware.GetMerchantID(c)) if err != nil { diff --git a/backend/internal/middleware/open_auth.go b/backend/internal/middleware/open_auth.go index 2e18017..84ec777 100644 --- a/backend/internal/middleware/open_auth.go +++ b/backend/internal/middleware/open_auth.go @@ -43,6 +43,8 @@ func OpenAuth(cfg OpenAuthConfig) gin.HandlerFunc { } return func(c *gin.Context) { reqID := openlog.EnsureReqID(c) + side, action := openlog.ScopeFromPath(openlog.SideClient, c.Request.Method, c.Request.URL.Path) + openlog.SetScope(c, side, action) c.Set(openlog.CtxDebug, cfg.Debug) c.Set(openlog.CtxStart, time.Now()) c.Header("X-Request-Id", reqID) diff --git a/backend/internal/middleware/source_open_auth.go b/backend/internal/middleware/source_open_auth.go index aa08845..48ac5dc 100644 --- a/backend/internal/middleware/source_open_auth.go +++ b/backend/internal/middleware/source_open_auth.go @@ -55,6 +55,8 @@ func SourceOpenAuth(cfg SourceOpenAuthConfig) gin.HandlerFunc { store := newSourceNonceStore() return func(c *gin.Context) { reqID := openlog.EnsureReqID(c) + side, action := openlog.ScopeFromPath(openlog.SideSource, c.Request.Method, c.Request.URL.Path) + openlog.SetScope(c, side, action) c.Set(openlog.CtxDebug, cfg.Debug) c.Set(openlog.CtxStart, time.Now()) c.Header("X-Request-Id", reqID) diff --git a/backend/internal/pkg/openlog/openlog.go b/backend/internal/pkg/openlog/openlog.go index e0229de..7a7dcaf 100644 --- a/backend/internal/pkg/openlog/openlog.go +++ b/backend/internal/pkg/openlog/openlog.go @@ -11,11 +11,32 @@ import ( ) const ( - CtxReqID = "open_req_id" - CtxDebug = "open_debug" - CtxBody = "open_body" - CtxStart = "open_start" - CtxAPIKey = "open_api_key" + CtxReqID = "open_req_id" + CtxDebug = "open_debug" + CtxBody = "open_body" + CtxStart = "open_start" + CtxAPIKey = "open_api_key" + CtxSide = "open_side" + CtxAPIType = "open_api_type" + CtxAction = "open_action" +) + +const ( + SideClient = "client" + SideSource = "source" + + APITypeClientV1 = "client_v1" + APITypeSourceOpenV1 = "source_open_v1" + APITypeUnknown = "unknown" + + ActionAuth = "auth" + ActionList = "list" + ActionCreate = "create" + ActionQuery = "query" + ActionCancel = "cancel" + ActionWallet = "wallet" + ActionPush = "push" + ActionUnknown = "unknown" ) // Enabled 是否开启开放接口详细日志 @@ -50,6 +71,38 @@ func EnsureReqID(c *gin.Context) string { return id } +func SetScope(c *gin.Context, side, action string) { + c.Set(CtxSide, side) + c.Set(CtxAPIType, APITypeFromSide(side)) + c.Set(CtxAction, action) +} + +func SetAction(c *gin.Context, action string) { + c.Set(CtxAction, action) +} + +func ScopeFromPath(side, method, path string) (string, string) { + action := ActionUnknown + switch side { + case SideClient: + action = clientAction(method, path) + case SideSource: + action = sourceAction(method, path) + } + return side, action +} + +func APITypeFromSide(side string) string { + switch side { + case SideClient: + return APITypeClientV1 + case SideSource: + return APITypeSourceOpenV1 + default: + return APITypeUnknown + } +} + func IsDebug(c *gin.Context) bool { if !Enabled { return false @@ -92,12 +145,37 @@ func Truncate(s string, max int) string { return s[:max] + fmt.Sprintf("...(%d bytes)", len(s)) } +func Prefix(c *gin.Context) string { + side, _ := c.Get(CtxSide) + apiType, _ := c.Get(CtxAPIType) + action, _ := c.Get(CtxAction) + sideText, _ := side.(string) + apiTypeText, _ := apiType.(string) + actionText, _ := action.(string) + tag := "open" + if apiTypeText != "" && apiTypeText != APITypeUnknown { + tag += "." + apiTypeText + } else if sideText != "" { + tag += "." + sideText + } + if actionText != "" { + tag += "." + actionText + } + fields := "" + if apiTypeText != "" { + fields += fmt.Sprintf(" api_type=%s", apiTypeText) + } + if actionText != "" { + fields += fmt.Sprintf(" action=%s", actionText) + } + return fmt.Sprintf("[%s] req_id=%s%s ", tag, GetReqID(c), fields) +} + func Info(c *gin.Context, format string, args ...interface{}) { if !IsDebug(c) { return } - prefix := fmt.Sprintf("[open] req_id=%s ", GetReqID(c)) - log.Printf(prefix+format, args...) + log.Printf(Prefix(c)+format, args...) } func Warn(c *gin.Context, format string, args ...interface{}) { @@ -105,8 +183,7 @@ func Warn(c *gin.Context, format string, args ...interface{}) { if !Enabled { return } - prefix := fmt.Sprintf("[open] req_id=%s ", GetReqID(c)) - log.Printf(prefix+format, args...) + log.Printf(Prefix(c)+format, args...) } func Elapsed(c *gin.Context) time.Duration { @@ -117,3 +194,33 @@ func Elapsed(c *gin.Context) time.Duration { } return 0 } + +func clientAction(method, path string) string { + method = strings.ToUpper(method) + switch { + case method == "GET" && path == "/api/client/v1/products": + return ActionList + case method == "POST" && path == "/api/client/v1/orders": + return ActionCreate + case method == "GET" && strings.HasPrefix(path, "/api/client/v1/orders/"): + return ActionQuery + case method == "POST" && strings.HasPrefix(path, "/api/client/v1/orders/") && strings.HasSuffix(path, "/cancel"): + return ActionCancel + case method == "GET" && path == "/api/client/v1/wallet": + return ActionWallet + default: + return ActionUnknown + } +} + +func sourceAction(method, path string) string { + method = strings.ToUpper(method) + switch { + case method == "GET" && strings.HasPrefix(path, "/api/open/v1/orders/"): + return ActionQuery + case method == "POST" && path == "/api/open/v1/orders/ship-notify": + return ActionPush + default: + return ActionUnknown + } +} diff --git a/backend/internal/pkg/openlog/openlog_test.go b/backend/internal/pkg/openlog/openlog_test.go new file mode 100644 index 0000000..7f71035 --- /dev/null +++ b/backend/internal/pkg/openlog/openlog_test.go @@ -0,0 +1,83 @@ +package openlog + +import ( + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" +) + +func TestPrefixIncludesSideAndAction(t *testing.T) { + gin.SetMode(gin.TestMode) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set(CtxReqID, "req123") + SetScope(c, SideSource, ActionPush) + + got := Prefix(c) + want := "[open.source_open_v1.push] req_id=req123 api_type=source_open_v1 action=push " + if got != want { + t.Fatalf("prefix mismatch: got %q want %q", got, want) + } +} + +func TestScopeFromPath(t *testing.T) { + cases := []struct { + name string + side string + method string + path string + wantSide string + wantAction string + wantType string + }{ + { + name: "客户侧查询订单", + side: SideClient, + method: "GET", + path: "/api/client/v1/orders/FO123", + wantSide: SideClient, + wantAction: ActionQuery, + wantType: APITypeClientV1, + }, + { + name: "客户侧创建订单", + side: SideClient, + method: "POST", + path: "/api/client/v1/orders", + wantSide: SideClient, + wantAction: ActionCreate, + wantType: APITypeClientV1, + }, + { + name: "源头侧发货推送", + side: SideSource, + method: "POST", + path: "/api/open/v1/orders/ship-notify", + wantSide: SideSource, + wantAction: ActionPush, + wantType: APITypeSourceOpenV1, + }, + { + name: "源头侧查询订单", + side: SideSource, + method: "GET", + path: "/api/open/v1/orders/FO123", + wantSide: SideSource, + wantAction: ActionQuery, + wantType: APITypeSourceOpenV1, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + gotSide, gotAction := ScopeFromPath(tc.side, tc.method, tc.path) + if gotSide != tc.wantSide || gotAction != tc.wantAction { + t.Fatalf("scope mismatch: got %s/%s want %s/%s", gotSide, gotAction, tc.wantSide, tc.wantAction) + } + if gotType := APITypeFromSide(gotSide); gotType != tc.wantType { + t.Fatalf("api type mismatch: got %s want %s", gotType, tc.wantType) + } + }) + } +}