package router import ( "strings" "hfb_sys/backend/internal/config" "hfb_sys/backend/internal/handler" smsintegration "hfb_sys/backend/internal/integrations/sms" "hfb_sys/backend/internal/middleware" "hfb_sys/backend/internal/modules/adminaudit" "hfb_sys/backend/internal/modules/adminauth" "hfb_sys/backend/internal/modules/admindashboard" "hfb_sys/backend/internal/modules/adminmgr" "hfb_sys/backend/internal/modules/adminrole" "hfb_sys/backend/internal/modules/adminuser" "hfb_sys/backend/internal/modules/auth" "hfb_sys/backend/internal/modules/chat" "hfb_sys/backend/internal/modules/chathub" "hfb_sys/backend/internal/modules/dispute" filemodule "hfb_sys/backend/internal/modules/file" "hfb_sys/backend/internal/modules/listing" "hfb_sys/backend/internal/modules/notification" "hfb_sys/backend/internal/modules/order" "hfb_sys/backend/internal/modules/payment" "hfb_sys/backend/internal/modules/realname" "hfb_sys/backend/internal/modules/systemconfig" "hfb_sys/backend/internal/modules/user" "hfb_sys/backend/internal/modules/wallet" "github.com/gin-gonic/gin" "go.uber.org/zap" ) func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { if cfg.AppEnv == "production" { gin.SetMode(gin.ReleaseMode) } engine := gin.New() engine.Use(middleware.RequestID()) engine.Use(middleware.RequestLogger(logger)) engine.Use(middleware.Recovery(logger)) health := handler.NewHealthHandler() engine.GET("/health", health.Check) jwtManager := auth.NewJWTManager(cfg.JWTSecret) var userRepo *auth.UserRepository if deps.DB != nil { userRepo = auth.NewUserRepository(deps.DB) } smsProvider := newSMSProvider(cfg, logger) authService := auth.NewService(userRepo, deps.Redis, jwtManager, smsProvider, logger) authHandler := auth.NewHandler(authService) var adminAuthRepo *adminauth.Repository if deps.DB != nil { adminAuthRepo = adminauth.NewRepository(deps.DB, deps.Redis, jwtManager) } adminAuthService := adminauth.NewService(adminAuthRepo, jwtManager) adminAuthHandler := adminauth.NewHandler(adminAuthService) var adminDashboardRepo *admindashboard.Repository if deps.DB != nil { adminDashboardRepo = admindashboard.NewRepository(deps.DB) } adminDashboardService := admindashboard.NewService(adminDashboardRepo) adminDashboardHandler := admindashboard.NewHandler(adminDashboardService) var adminUserRepo *adminuser.Repository if deps.DB != nil { adminUserRepo = adminuser.NewRepository(deps.DB) } adminUserService := adminuser.NewService(adminUserRepo) adminUserHandler := adminuser.NewHandler(adminUserService) var adminAuditRepo *adminaudit.Repository if deps.DB != nil { adminAuditRepo = adminaudit.NewRepository(deps.DB) } adminAuditService := adminaudit.NewService(adminAuditRepo) adminAuditHandler := adminaudit.NewHandler(adminAuditService) userHandler := user.NewHandler(userRepo) var realnameRepo *realname.Repository if deps.DB != nil { realnameRepo = realname.NewRepository(deps.DB) } realnameService := realname.NewService(realnameRepo, newRealnameProvider(cfg, logger), logger) realnameHandler := realname.NewHandler(realnameService) var listingRepo *listing.Repository if deps.DB != nil { listingRepo = listing.NewRepository(deps.DB) } var orderRepo *order.Repository if deps.DB != nil { orderRepo = order.NewRepository(deps.DB) } orderService := order.NewService(orderRepo) orderHandler := order.NewHandler(orderService) var walletRepo *wallet.Repository if deps.DB != nil { walletRepo = wallet.NewRepository(deps.DB) } walletService := wallet.NewService(walletRepo) walletHandler := wallet.NewHandler(walletService) var paymentRepo *payment.Repository if deps.DB != nil { paymentRepo = payment.NewRepository(deps.DB, cfg.Payment, orderRepo, walletRepo) } paymentService := payment.NewService(paymentRepo) paymentHandler := payment.NewHandler(paymentService) // Inject refund function into order repo to avoid circular dependency if orderRepo != nil && paymentRepo != nil { orderRepo.SetRefundFunc(func(orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { dto, err := paymentRepo.StartRefund(orderID, refundAmountCent, bizType, remark) if err != nil { return "", err } return dto.Status, nil }) } var notificationRepo *notification.Repository if deps.DB != nil { notificationRepo = notification.NewRepository(deps.DB) } notificationService := notification.NewService(notificationRepo) notificationHandler := notification.NewHandler(notificationService) var chatHub *chathub.Hub if deps.DB != nil { chatHub = chathub.NewHub(deps.DB) } var chatRepo *chat.Repository if deps.DB != nil { chatRepo = chat.NewRepository(deps.DB, chatHub) } chatService := chat.NewService(chatRepo) chatHandler := chat.NewHandler(chatService) var chatHubHandler *chathub.Handler if chatHub != nil { chatHubHandler = chathub.NewHandler(chatHub) } if orderRepo != nil && chatRepo != nil { orderRepo.SetChatRepo(chatRepo) } var disputeRepo *dispute.Repository if deps.DB != nil { disputeRepo = dispute.NewRepository(deps.DB) } disputeService := dispute.NewService(disputeRepo) disputeHandler := dispute.NewHandler(disputeService) var systemConfigRepo *systemconfig.Repository if deps.DB != nil { systemConfigRepo = systemconfig.NewRepository(deps.DB) } systemConfigService := systemconfig.NewService(systemConfigRepo) systemConfigHandler := systemconfig.NewHandler(systemConfigService) var adminRoleRepo *adminrole.Repository if deps.DB != nil { adminRoleRepo = adminrole.NewRepository(deps.DB) } adminRoleService := adminrole.NewService(adminRoleRepo) adminRoleHandler := adminrole.NewHandler(adminRoleService) var adminMgrRepo *adminmgr.Repository if deps.DB != nil { adminMgrRepo = adminmgr.NewRepository(deps.DB, deps.Redis) } adminMgrService := adminmgr.NewService(adminMgrRepo) adminMgrHandler := adminmgr.NewHandler(adminMgrService) var fileStorage *filemodule.Storage if cfg.Storage.Endpoint != "" && cfg.Storage.Bucket != "" { var err error fileStorage, err = filemodule.NewStorage(cfg.Storage) if err != nil { logger.Warn("file storage unavailable; file APIs will return 503", zap.Error(err)) } } fileService := filemodule.NewService(fileStorage) fileHandler := filemodule.NewHandler(fileService, fileStorage) listingService := listing.NewService(listingRepo, systemConfigRepo) listingHandler := listing.NewHandler(listingService, fileStorage) requireAuth := middleware.Auth(jwtManager) requireAdmin := middleware.AdminAuth(jwtManager) requireRealname := middleware.RequireRealname(userRepo) requirePerm := func(code string) gin.HandlerFunc { return middleware.RequirePermission(code, deps.Redis) } api := engine.Group("/api") { api.GET("/health", health.Check) api.GET("/listing-publish-options", systemConfigHandler.PublishOptions) api.GET("/listing-sale-price-config", systemConfigHandler.SalePriceConfig) api.GET("/home-announcements", systemConfigHandler.HomeAnnouncements) api.GET("/mobile-home-config", systemConfigHandler.HomeConfig) api.GET("/public/files/object", fileHandler.PublicObject) api.POST("/payments/leshua/notify", paymentHandler.LeshuaNotify) openRoutes := api.Group("/open") { openRoutes.POST("/listing-uploads", listingHandler.ImportExternalUpload) } authRoutes := api.Group("/auth") { authRoutes.POST("/sms/send", authHandler.SendSMS) authRoutes.POST("/sms/login", authHandler.Login) authRoutes.POST("/refresh", authHandler.Refresh) authRoutes.POST("/logout", authHandler.Logout) } api.GET("/me", requireAuth, userHandler.Me) api.PUT("/me", requireAuth, userHandler.UpdateMe) listingRoutes := api.Group("/listings") { listingRoutes.GET("", listingHandler.ListPublic) listingRoutes.GET("/default-upload-screenshot", listingHandler.DefaultUploadScreenshot) listingRoutes.GET("/:id/cover", listingHandler.Cover) listingRoutes.GET("/:id/screenshots/:index", listingHandler.Screenshot) listingRoutes.GET("/:id", listingHandler.FindPublic) listingRoutes.POST("", requireAuth, requireRealname, listingHandler.Create) listingRoutes.PUT("/:id", requireAuth, requireRealname, listingHandler.Update) listingRoutes.POST("/:id/submit-review", requireAuth, requireRealname, listingHandler.SubmitReview) listingRoutes.DELETE("/:id", requireAuth, requireRealname, listingHandler.Offline) } sellerRoutes := api.Group("/seller", requireAuth, requireRealname) { sellerRoutes.GET("/listings", listingHandler.ListMine) sellerRoutes.GET("/listings/:id", listingHandler.FindMine) } orderRoutes := api.Group("/orders", requireAuth) { orderRoutes.POST("", orderHandler.Create) orderRoutes.GET("", orderHandler.List) orderRoutes.GET("/:id", orderHandler.Detail) orderRoutes.GET("/:id/chat", chatHandler.OrderConversation) orderRoutes.POST("/:id/pay", orderHandler.Pay) orderRoutes.POST("/:id/start-payment", paymentHandler.Start) orderRoutes.GET("/:id/query-payment", paymentHandler.Query) orderRoutes.POST("/:id/cancel", orderHandler.Cancel) orderRoutes.POST("/:id/handoff", orderHandler.SubmitHandoff) orderRoutes.GET("/:id/handoff-records", orderHandler.HandoffRecords) orderRoutes.POST("/:id/confirm-receive", orderHandler.ConfirmReceive) orderRoutes.POST("/:id/return", orderHandler.SubmitReturn) orderRoutes.POST("/:id/confirm-return", orderHandler.ConfirmReturn) orderRoutes.POST("/:id/checkout", orderHandler.SubmitCheckout) orderRoutes.POST("/:id/checkout/confirm", orderHandler.ConfirmCheckout) orderRoutes.POST("/:id/checkout/counter", orderHandler.CounterCheckout) orderRoutes.POST("/:id/checkout/accept", orderHandler.AcceptCheckout) orderRoutes.POST("/:id/dispute", disputeHandler.Create) orderRoutes.GET("/:id/refund-status", paymentHandler.QueryRefundStatus) } disputeRoutes := api.Group("/disputes", requireAuth) { disputeRoutes.GET("", disputeHandler.List) disputeRoutes.GET("/:id", disputeHandler.Detail) } walletRoutes := api.Group("/wallet", requireAuth) { walletRoutes.GET("/balance", walletHandler.Balance) walletRoutes.GET("/ledger", walletHandler.Ledger) walletRoutes.POST("/recharge", walletHandler.Recharge) walletRoutes.POST("/recharge/pay", paymentHandler.WalletRecharge) walletRoutes.POST("/recharge/pay/:id/query", paymentHandler.WalletRechargeQuery) walletRoutes.POST("/withdraw", walletHandler.Withdraw) } fileRoutes := api.Group("/files", requireAuth) { fileRoutes.POST("/upload", fileHandler.Upload) fileRoutes.GET("/object", fileHandler.Object) } notificationRoutes := api.Group("/notifications", requireAuth) { notificationRoutes.GET("", notificationHandler.List) notificationRoutes.POST("/:id/read", notificationHandler.MarkRead) } chatRoutes := api.Group("/chats", requireAuth) { if chatHubHandler != nil { chatRoutes.GET("/events", chatHubHandler.UserEvents) } chatRoutes.POST("/support", chatHandler.EnsureSupportConversation) chatRoutes.GET("", chatHandler.List) chatRoutes.GET("/:id", chatHandler.Detail) chatRoutes.GET("/:id/messages", chatHandler.Messages) chatRoutes.POST("/:id/messages", chatHandler.Send) chatRoutes.POST("/:id/read", chatHandler.MarkRead) } realnameRoutes := api.Group("/realname", requireAuth) { realnameRoutes.POST("/start", realnameHandler.Start) realnameRoutes.GET("/status", realnameHandler.Status) } adminAuthRoutes := api.Group("/admin/auth") { adminAuthRoutes.GET("/captcha", adminAuthHandler.Captcha) adminAuthRoutes.POST("/login", adminAuthHandler.Login) adminAuthRoutes.POST("/refresh", adminAuthHandler.Refresh) } adminRoutes := api.Group("/admin", requireAdmin) { adminRoutes.GET("/me", adminAuthHandler.Me) adminRoutes.POST("/auth/logout", adminAuthHandler.Logout) adminRoutes.POST("/files/upload", fileHandler.Upload) adminRoutes.GET("/dashboard", requirePerm("dashboard:view"), adminDashboardHandler.Summary) adminRoutes.GET("/files/object", fileHandler.Object) adminRoutes.GET("/users", requirePerm("user:view"), adminUserHandler.List) adminRoutes.POST("/users/:id/freeze", requirePerm("user:freeze"), adminUserHandler.Freeze) adminRoutes.POST("/users/:id/unfreeze", requirePerm("user:unfreeze"), adminUserHandler.Unfreeze) adminRoutes.GET("/orders", requirePerm("order:view"), orderHandler.AdminList) adminRoutes.GET("/orders/:id", requirePerm("order:view"), orderHandler.AdminDetail) adminRoutes.GET("/orders/:id/handoff-records", requirePerm("order:view"), orderHandler.AdminHandoffRecords) adminRoutes.POST("/orders/:id/close", requirePerm("order:close"), orderHandler.AdminClose) adminRoutes.POST("/orders/:id/mark-abnormal", requirePerm("order:mark_abnormal"), orderHandler.AdminMarkAbnormal) adminRoutes.POST("/orders/:id/refund", requirePerm("order:close"), orderHandler.AdminRefund) adminRoutes.GET("/orders/:id/refund-status", requirePerm("order:view"), orderHandler.AdminRefundStatus) adminRoutes.GET("/listings", requirePerm("listing:view"), listingHandler.ListAdmin) adminRoutes.GET("/listings/pending", requirePerm("listing:approve"), listingHandler.ListPendingReview) adminRoutes.GET("/listings/:id", requirePerm("listing:view"), listingHandler.FindAdmin) adminRoutes.POST("/listings/:id/approve", requirePerm("listing:approve"), listingHandler.Approve) adminRoutes.POST("/listings/:id/reject", requirePerm("listing:reject"), listingHandler.Reject) adminRoutes.POST("/listings/:id/offline", requirePerm("listing:offline"), listingHandler.AdminOffline) adminRoutes.POST("/listings/:id/mark-abnormal", requirePerm("listing:offline"), listingHandler.AdminMarkAbnormal) adminRoutes.GET("/disputes", requirePerm("dispute:view"), disputeHandler.AdminList) adminRoutes.POST("/disputes/:id/arbitrate", requirePerm("dispute:arbitrate"), disputeHandler.AdminArbitrate) adminRoutes.GET("/wallet/ledger", requirePerm("wallet:view"), walletHandler.AdminLedger) adminRoutes.GET("/system-configs", requirePerm("system_config:view"), systemConfigHandler.List) adminRoutes.PUT("/system-configs/:key", requirePerm("system_config:update"), systemConfigHandler.Update) adminRoutes.GET("/audit-logs", requirePerm("audit_log:view"), adminAuditHandler.List) if chatHubHandler != nil { adminRoutes.GET("/chats/events", requirePerm("chat:view"), chatHubHandler.AdminEvents) } adminRoutes.GET("/chats", requirePerm("chat:view"), chatHandler.AdminList) adminRoutes.GET("/chats/:id", requirePerm("chat:view"), chatHandler.AdminDetail) adminRoutes.GET("/chats/:id/messages", requirePerm("chat:view"), chatHandler.AdminMessages) adminRoutes.POST("/chats/:id/messages", requirePerm("chat:send"), chatHandler.AdminSend) adminRoutes.POST("/chats/:id/read", requirePerm("chat:view"), chatHandler.AdminMarkRead) adminRoutes.POST("/chats/:id/transfer", requirePerm("chat:send"), chatHandler.AdminTransfer) adminRoutes.GET("/chats/support-admins", requirePerm("chat:view"), chatHandler.AdminSupportAdmins) adminRoutes.PUT("/chats/:id/remark", requirePerm("chat:send"), chatHandler.AdminUpdateRemark) adminRoutes.GET("/chats/quick-replies", requirePerm("chat:view"), chatHandler.AdminListQuickReplies) adminRoutes.POST("/chats/quick-replies", requirePerm("chat:send"), chatHandler.AdminCreateQuickReply) adminRoutes.PUT("/chats/quick-replies/:id", requirePerm("chat:send"), chatHandler.AdminUpdateQuickReply) adminRoutes.DELETE("/chats/quick-replies/:id", requirePerm("chat:send"), chatHandler.AdminDeleteQuickReply) adminRoutes.GET("/chats/auto-welcome", requirePerm("system_config:view"), chatHandler.AdminGetAutoWelcome) adminRoutes.PUT("/chats/auto-welcome", requirePerm("system_config:update"), chatHandler.AdminUpdateAutoWelcome) // 角色管理 adminRoutes.GET("/roles", requirePerm("role:manage"), adminRoleHandler.List) adminRoutes.GET("/roles/:id", requirePerm("role:manage"), adminRoleHandler.FindByID) adminRoutes.POST("/roles", requirePerm("role:manage"), adminRoleHandler.Create) adminRoutes.PUT("/roles/:id", requirePerm("role:manage"), adminRoleHandler.Update) adminRoutes.DELETE("/roles/:id", requirePerm("role:manage"), adminRoleHandler.Delete) adminRoutes.PUT("/roles/:id/permissions", requirePerm("role:manage"), adminRoleHandler.AssignPermissions) adminRoutes.GET("/permissions", requirePerm("role:manage"), adminRoleHandler.ListPermissions) // 管理员管理 adminRoutes.GET("/admin-users", requirePerm("admin_user:manage"), adminMgrHandler.List) adminRoutes.GET("/admin-users/:id", requirePerm("admin_user:manage"), adminMgrHandler.FindByID) adminRoutes.POST("/admin-users", requirePerm("admin_user:manage"), adminMgrHandler.Create) adminRoutes.PUT("/admin-users/:id", requirePerm("admin_user:manage"), adminMgrHandler.Update) adminRoutes.DELETE("/admin-users/:id", requirePerm("admin_user:manage"), adminMgrHandler.Delete) adminRoutes.PUT("/admin-users/:id/roles", requirePerm("admin_user:manage"), adminMgrHandler.AssignRoles) adminRoutes.PUT("/admin-users/:id/password", adminMgrHandler.ChangePassword) } } return engine } func newSMSProvider(cfg config.Config, logger *zap.Logger) smsintegration.Provider { switch strings.ToLower(strings.TrimSpace(cfg.SMS.Provider)) { case "aliyun": provider, err := smsintegration.NewAliyunProvider(smsintegration.AliyunConfig{ AccessKeyID: cfg.SMS.AliyunAccessKeyID, AccessKeySecret: cfg.SMS.AliyunAccessKeySecret, Endpoint: cfg.SMS.AliyunEndpoint, SignName: cfg.SMS.AliyunSignName, LoginTemplateCode: cfg.SMS.AliyunLoginTemplateCode, }, logger) if err != nil { logger.Warn("aliyun sms provider unavailable; sms send will fail", zap.Error(err)) return smsintegration.NewUnavailableProvider(err) } return provider default: return smsintegration.NewMockProvider(logger) } } func newRealnameProvider(cfg config.Config, logger *zap.Logger) realname.Provider { switch strings.ToLower(strings.TrimSpace(cfg.Realname.Provider)) { case "cloudmarket", "aliyun_cloudmarket": provider, err := realname.NewCloudMarketProvider(realname.CloudMarketConfig{ URL: cfg.Realname.CloudMarketURL, AppCode: cfg.Realname.CloudMarketAppCode, }) if err != nil { logger.Warn("cloud market realname provider unavailable; realname verify will fail", zap.Error(err)) return realname.NewUnavailableProvider(err) } return provider default: return realname.NewMockProvider() } }