实现多商户履约平台基础
This commit is contained in:
@@ -5,9 +5,11 @@ import (
|
||||
"affiliate_dash/internal/middleware"
|
||||
"affiliate_dash/internal/model"
|
||||
"affiliate_dash/internal/pkg/jwt"
|
||||
"affiliate_dash/internal/service"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Handlers struct {
|
||||
@@ -15,8 +17,13 @@ type Handlers struct {
|
||||
Skin *handler.SkinHandler
|
||||
Order *handler.OrderHandler
|
||||
User *handler.UserHandler
|
||||
Open *handler.OpenHandler
|
||||
Open *handler.OpenV1Handler
|
||||
SourceOpen *handler.OpenHandler
|
||||
Merchant *handler.MerchantHandler
|
||||
JWT *jwt.Manager
|
||||
Tenant *service.TenantService
|
||||
OpenDB *gorm.DB
|
||||
SecretCodec *service.SecretCodec
|
||||
OpenAPIKey string
|
||||
OpenAPISecret string
|
||||
OpenSignSkew int64
|
||||
@@ -29,8 +36,8 @@ func Setup(h *Handlers) *gin.Engine {
|
||||
r.Use(cors.New(cors.Config{
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "X-Api-Key", "X-Timestamp", "X-Nonce", "X-Sign"},
|
||||
ExposeHeaders: []string{"Content-Length"},
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "X-Merchant-ID", "X-App-Key", "X-Api-Key", "X-Timestamp", "X-Nonce", "X-Sign", "Idempotency-Key", "X-Request-ID"},
|
||||
ExposeHeaders: []string{"Content-Length", "X-Request-ID"},
|
||||
AllowCredentials: true,
|
||||
}))
|
||||
|
||||
@@ -43,21 +50,39 @@ func Setup(h *Handlers) *gin.Engine {
|
||||
api.POST("/auth/login", h.Auth.Login)
|
||||
api.POST("/auth/register", h.Auth.Register)
|
||||
|
||||
// 皮肤源头开放接口(ApiKey + HMAC 签名)
|
||||
open := api.Group("/open/v1")
|
||||
open.Use(middleware.OpenAuth(middleware.OpenAuthConfig{
|
||||
// 原上游发货接口:路径、鉴权和字段保持不变。
|
||||
sourceOpen := api.Group("/open/v1")
|
||||
sourceOpen.Use(middleware.SourceOpenAuth(middleware.SourceOpenAuthConfig{
|
||||
APIKey: h.OpenAPIKey,
|
||||
APISecret: h.OpenAPISecret,
|
||||
SkewSeconds: h.OpenSignSkew,
|
||||
Debug: h.OpenAPIDebug,
|
||||
}))
|
||||
{
|
||||
open.GET("/orders/:order_no", h.Open.QueryOrder)
|
||||
open.POST("/orders/ship-notify", h.Open.ShipNotify)
|
||||
sourceOpen.GET("/orders/:order_no", h.SourceOpen.QueryOrder)
|
||||
sourceOpen.POST("/orders/ship-notify", h.SourceOpen.ShipNotify)
|
||||
}
|
||||
|
||||
// 客户侧通用开放接口:独立 API 客户端 + HMAC 签名。
|
||||
clientOpen := api.Group("/client/v1")
|
||||
clientOpen.Use(middleware.OpenAuth(middleware.OpenAuthConfig{
|
||||
DB: h.OpenDB,
|
||||
Codec: h.SecretCodec,
|
||||
SkewSeconds: h.OpenSignSkew,
|
||||
Debug: h.OpenAPIDebug,
|
||||
}))
|
||||
{
|
||||
clientOpen.GET("/products", middleware.RequireAPIScope("products:read"), h.Open.ListProducts)
|
||||
clientOpen.POST("/orders", middleware.RequireAPIScope("orders:write"), h.Open.CreateOrder)
|
||||
clientOpen.GET("/orders/:order_no", middleware.RequireAPIScope("orders:read", "fulfillment:read"), h.Open.QueryOrder)
|
||||
clientOpen.POST("/orders/:order_no/cancel", middleware.RequireAPIScope("orders:write"), h.Open.CancelOrder)
|
||||
clientOpen.POST("/orders/:order_no/ship-notify", middleware.RequireAPIScope("fulfillment:write"), h.Open.ShipNotify)
|
||||
clientOpen.GET("/wallet", middleware.RequireAPIScope("wallet:read"), h.Open.GetWallet)
|
||||
}
|
||||
|
||||
auth := api.Group("")
|
||||
auth.Use(middleware.Auth(h.JWT))
|
||||
auth.Use(middleware.Tenant(h.Tenant))
|
||||
{
|
||||
auth.GET("/auth/profile", h.Auth.Profile)
|
||||
auth.GET("/dashboard", h.Order.Dashboard)
|
||||
@@ -74,6 +99,27 @@ func Setup(h *Handlers) *gin.Engine {
|
||||
auth.POST("/orders", h.Order.Create)
|
||||
auth.PATCH("/orders/:id/status", middleware.RequireRole(model.RoleAdmin), h.Order.UpdateStatus)
|
||||
|
||||
// 新商户后台:不依赖旧皮肤/分销商模型。
|
||||
merchant := auth.Group("/merchant")
|
||||
{
|
||||
merchant.GET("", h.Merchant.Current)
|
||||
merchant.GET("/products", h.Merchant.ListProducts)
|
||||
merchant.POST("/products", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateProduct)
|
||||
merchant.PATCH("/products/:id", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateProduct)
|
||||
merchant.GET("/orders", h.Merchant.ListOrders)
|
||||
merchant.GET("/wallet", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator, model.MemberRoleFinance), h.Merchant.GetWallet)
|
||||
merchant.GET("/wallet/ledger", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Merchant.ListWalletLedger)
|
||||
merchant.POST("/wallet/adjust", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Merchant.AdjustWallet)
|
||||
merchant.GET("/api-clients", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.ListAPIClients)
|
||||
merchant.POST("/api-clients", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateAPIClient)
|
||||
merchant.PATCH("/api-clients/:id/status", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateAPIClientStatus)
|
||||
merchant.GET("/callbacks", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.ListCallbacks)
|
||||
merchant.POST("/callbacks", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateCallback)
|
||||
merchant.PATCH("/callbacks/:id/status", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateCallbackStatus)
|
||||
merchant.GET("/members", middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.ListMembers)
|
||||
merchant.POST("/members", middleware.RequireMerchantRole(model.MemberRoleOwner), h.Merchant.AddCurrentMerchantMember)
|
||||
}
|
||||
|
||||
// 用户 / 分销商 / 发货记录(仅管理员)
|
||||
admin := auth.Group("")
|
||||
admin.Use(middleware.RequireRole(model.RoleAdmin))
|
||||
@@ -82,6 +128,9 @@ func Setup(h *Handlers) *gin.Engine {
|
||||
admin.POST("/users", h.User.Create)
|
||||
admin.PATCH("/users/:id/status", h.User.UpdateStatus)
|
||||
admin.GET("/ship-logs", h.Order.ListShipLogs)
|
||||
admin.GET("/platform/merchants", h.Merchant.ListPlatformMerchants)
|
||||
admin.POST("/platform/merchants", h.Merchant.CreateMerchant)
|
||||
admin.POST("/platform/merchants/:id/members", h.Merchant.AddPlatformMerchantMember)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"affiliate_dash/internal/handler"
|
||||
"affiliate_dash/internal/pkg/jwt"
|
||||
"affiliate_dash/internal/service"
|
||||
"affiliate_dash/internal/testdb"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestSetupDoesNotPanic(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := testdb.New(t)
|
||||
codec, err := service.NewSecretCodec("test-master-key")
|
||||
if err != nil {
|
||||
t.Fatalf("codec: %v", err)
|
||||
}
|
||||
tenantSvc := service.NewTenantService(db)
|
||||
authSvc := service.NewAuthService(db, jwt.NewManager("test-jwt"), tenantSvc)
|
||||
skinSvc := service.NewSkinService(db)
|
||||
orderSvc := service.NewOrderService(db)
|
||||
userSvc := service.NewUserService(db, tenantSvc)
|
||||
callbackSvc := service.NewCallbackService(db, codec)
|
||||
fulfillmentSvc := service.NewFulfillmentService(db, callbackSvc)
|
||||
merchantSvc := service.NewMerchantService(db, codec, tenantSvc)
|
||||
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
t.Fatalf("setup should not panic: %v", recovered)
|
||||
}
|
||||
}()
|
||||
_ = Setup(&Handlers{
|
||||
Auth: handler.NewAuthHandler(authSvc),
|
||||
Skin: handler.NewSkinHandler(skinSvc),
|
||||
Order: handler.NewOrderHandler(orderSvc),
|
||||
User: handler.NewUserHandler(userSvc),
|
||||
Open: handler.NewOpenV1Handler(merchantSvc, fulfillmentSvc),
|
||||
SourceOpen: handler.NewOpenHandler(orderSvc),
|
||||
Merchant: handler.NewMerchantHandler(merchantSvc, fulfillmentSvc, callbackSvc),
|
||||
JWT: jwt.NewManager("test-jwt"),
|
||||
Tenant: tenantSvc,
|
||||
OpenDB: db,
|
||||
SecretCodec: codec,
|
||||
OpenAPIKey: "source-key",
|
||||
OpenAPISecret: "source-secret",
|
||||
OpenSignSkew: 300,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user