package router import ( "context" "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/adminfinance" "hfb_sys/backend/internal/modules/adminmgr" "hfb_sys/backend/internal/modules/adminrole" "hfb_sys/backend/internal/modules/adminuser" "hfb_sys/backend/internal/modules/announcement" "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/paymentaccount" "hfb_sys/backend/internal/modules/paymentconfig" "hfb_sys/backend/internal/modules/realname" "hfb_sys/backend/internal/modules/supportgroup" "hfb_sys/backend/internal/modules/systemconfig" "hfb_sys/backend/internal/modules/user" "hfb_sys/backend/internal/modules/wallet" "hfb_sys/backend/internal/modules/withdrawal" _ "hfb_sys/backend/docs" // Swagger 文档 "hfb_sys/backend/pkg/crypto" "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" "go.uber.org/zap" ) func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { if config.IsProductionEnv(cfg.AppEnv) { gin.SetMode(gin.ReleaseMode) } engine := gin.New() // 生产环境前置 Caddy,已用实测对端地址覆盖 X-Real-IP, // 直接据此取真实客户端 IP,避免客户端伪造 X-Forwarded-For。 engine.TrustedPlatform = "X-Real-IP" engine.Use(middleware.RequestID()) engine.Use(middleware.RequestLogger(logger)) engine.Use(middleware.Recovery(logger)) health := handler.NewHealthHandler() engine.GET("/health", health.Check) // Swagger 文档路由(仅在非生产环境) if !config.IsProductionEnv(cfg.AppEnv) { engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) } if cfg.RateLimit.Enabled { engine.Use(middleware.RateLimitPerMinute(cfg.RateLimit.RequestsPerMinute, deps.Redis)) } // 业务字段加密器(实名/收款账号):主密钥 + legacy 回退,用于密钥轮换期间透明解出旧密文。 // 生产环境必须配置 FIELD_ENCRYPTION_KEY(ValidateProductionSecurity 已校验)。 // 开发/测试缺 FIELD_ENCRYPTION_KEY 时用 legacy 密钥构造单密钥加密器, // 保证零配置兼容历史硬编码密钥加密的存量密文(非生产 legacy 默认填历史硬编码值)。 var fieldEncryptor crypto.Encryptor primary := cfg.FieldEncryptionKey legacy := cfg.FieldEncryptionLegacyKey switch { case primary != "": encryptor, err := crypto.NewFieldEncryptor(primary, legacy) if err != nil { if config.IsProductionEnv(cfg.AppEnv) { logger.Fatal("FIELD_ENCRYPTION_KEY invalid", zap.Error(err)) } logger.Warn("FIELD_ENCRYPTION_KEY invalid, fallback to legacy-only", zap.Error(err)) // primary 非法时退回 legacy 单密钥(若有),否则 MockEncryptor if legacy != "" { if e, eErr := crypto.NewFieldEncryptor(legacy); eErr == nil { fieldEncryptor = e } } } else { fieldEncryptor = encryptor } case legacy != "": // 开发态零配置:用 legacy 单密钥,能解历史存量密文。 encryptor, err := crypto.NewFieldEncryptor(legacy) if err == nil { fieldEncryptor = encryptor logger.Warn("FIELD_ENCRYPTION_KEY not set, using legacy-only encryptor for dev compatibility") } } if fieldEncryptor == nil { if config.IsProductionEnv(cfg.AppEnv) { logger.Fatal("FIELD_ENCRYPTION_KEY not set") } fieldEncryptor = &crypto.MockEncryptor{} logger.Warn("FIELD_ENCRYPTION_KEY and legacy both unset, using MockEncryptor") } 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 adminFinanceRepo *adminfinance.Repository if deps.DB != nil { adminFinanceRepo = adminfinance.NewRepository(deps.DB) } adminFinanceService := adminfinance.NewService(adminFinanceRepo) adminFinanceHandler := adminfinance.NewHandler(adminFinanceService) 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, fieldEncryptor, logger), logger) realnameHandler := realname.NewHandler(realnameService) 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) } // 创建 chat 适配器用于 listing chatCreator := chat.NewListingChatCreatorAdapter() var listingRepo *listing.Repository if deps.DB != nil { listingRepo = listing.NewRepository(deps.DB, chatCreator) } var paymentRepo *payment.Repository var orderRepo *order.Repository if deps.DB != nil { orderRepo = order.NewRepository(deps.DB, order.Dependencies{ ChatNotifier: chatRepo, RefundStarter: order.RefundStarterFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { if paymentRepo == nil { return "", order.ErrDependencyUnavailable } dto, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) if err != nil { return "", err } return dto.Status, nil }), }) } 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 paymentAccountRepo *paymentaccount.Repository if deps.DB != nil { paymentAccountRepo = paymentaccount.NewRepository(deps.DB, fieldEncryptor) } paymentAccountService := paymentaccount.NewService(paymentAccountRepo) paymentAccountHandler := paymentaccount.NewHandler(paymentAccountService) var withdrawalRepo *withdrawal.Repository if deps.DB != nil { withdrawalRepo = withdrawal.NewRepository(deps.DB, walletRepo, fieldEncryptor) } withdrawalService := withdrawal.NewService(withdrawalRepo) withdrawalHandler := withdrawal.NewHandler(withdrawalService) // 支付配置管理 var paymentConfigRepo *paymentconfig.Repository var paymentConfigService *paymentconfig.Service var paymentConfigHandler *paymentconfig.Handler if deps.DB != nil { // 生产环境必须配置有效加密密钥;开发/测试缺失时才降级 MockEncryptor。 encryptionKey := cfg.PaymentConfigEncryptionKey var encryptor paymentconfig.Encryptor if encryptionKey != "" { if aesEncryptor, err := paymentconfig.NewAESEncryptor(encryptionKey); err == nil { encryptor = aesEncryptor } } if encryptor == nil { if config.IsProductionEnv(cfg.AppEnv) { logger.Fatal("PAYMENT_CONFIG_ENCRYPTION_KEY not set or invalid") } encryptor = &paymentconfig.MockEncryptor{} logger.Warn("PAYMENT_CONFIG_ENCRYPTION_KEY not set or invalid, using MockEncryptor") } paymentConfigRepo = paymentconfig.NewRepository(deps.DB, encryptor) paymentConfigService = paymentconfig.NewService(paymentConfigRepo) paymentConfigHandler = paymentconfig.NewHandler(paymentConfigService) } if deps.DB != nil { paymentRepo = payment.NewRepository(deps.DB, paymentConfigRepo, orderRepo, payment.WithLogger(logger)) } paymentService := payment.NewService(paymentRepo) paymentHandler := payment.NewHandler(paymentService, payment.WithHandlerLogger(logger)) var notificationRepo *notification.Repository if deps.DB != nil { notificationRepo = notification.NewRepository(deps.DB) } notificationService := notification.NewService(notificationRepo) notificationHandler := notification.NewHandler(notificationService) chatService := chat.NewService(chatRepo) chatHandler := chat.NewHandler(chatService) var chatHubHandler *chathub.Handler if chatHub != nil { chatHubHandler = chathub.NewHandler(chatHub) } var disputeRepo *dispute.Repository if deps.DB != nil { disputeRepo = dispute.NewRepository(deps.DB, dispute.Dependencies{ RefundStarter: dispute.RefundStarterFunc(func(ctx context.Context, orderID uint64, refundAmountCent int64, bizType string, remark string) (string, error) { if paymentRepo == nil { return "", dispute.ErrDependencyUnavailable } dto, err := paymentRepo.StartRefund(ctx, orderID, refundAmountCent, bizType, remark) if err != nil { return "", err } return dto.Status, nil }), }) } 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, deps.Redis) } 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 supportGroupRepo *supportgroup.Repository if deps.DB != nil { supportGroupRepo = supportgroup.NewRepository(deps.DB) } supportGroupService := supportgroup.NewService(supportGroupRepo) supportGroupHandler := supportgroup.NewHandler(supportGroupService) 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 bucket is not ready; file APIs will retry on request", zap.Error(err)) } } fileService := filemodule.NewService(fileStorage) fileHandler := filemodule.NewHandler(fileService, fileStorage) listingService := listing.NewService(listingRepo, systemConfigRepo) listingHandler := listing.NewHandler(listingService, fileStorage, listing.HandlerOptions{ ExternalUploadSecret: cfg.ExternalUploadSecret, ExternalUploadAllowedIPs: cfg.ExternalUploadAllowedIPs, }) var announcementRepo *announcement.Repository if deps.DB != nil { announcementRepo = announcement.NewRepository(deps.DB) } announcementService := announcement.NewService(announcementRepo) announcementHandler := announcement.NewHandler(announcementService) requireAuth := middleware.Auth(jwtManager) var validateAdminToken middleware.AdminTokenValidatorFunc if adminAuthRepo != nil { validateAdminToken = func(ctx context.Context, adminID uint64, tokenVersion int64) (middleware.AdminTokenContext, error) { admin, err := adminAuthRepo.FindActiveForPasswordGate(ctx, adminID, tokenVersion) if err != nil { return middleware.AdminTokenContext{}, err } return middleware.AdminTokenContext{ Username: admin.Username, PasswordMustChange: admin.PasswordMustChange, }, nil } } requireAdmin := middleware.AdminAuth(jwtManager, validateAdminToken) 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-publish-agreements", systemConfigHandler.ListingPublishAgreements) api.GET("/listing-sale-price-config", systemConfigHandler.SalePriceConfig) api.GET("/order-agreements", systemConfigHandler.OrderAgreements) api.GET("/post-rental-notice", systemConfigHandler.PostRentalNotice) 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) api.POST("/payments/lakala/notify", paymentHandler.LakalaNotify) api.POST("/payments/shuncheng/notify", paymentHandler.ShunchengNotify) openRoutes := api.Group("/open") { openRoutes.POST("/listing-uploads", listingHandler.ImportExternalUpload) } authRoutes := api.Group("/auth") { authRoutes.GET("/captcha", authHandler.Captcha) 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("", requireRealname, 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.POST("/:id/dispute/cancel", disputeHandler.CancelByOrder) orderRoutes.GET("/:id/refund-status", paymentHandler.QueryRefundStatus) } disputeRoutes := api.Group("/disputes", requireAuth) { disputeRoutes.GET("", disputeHandler.List) disputeRoutes.GET("/:id", disputeHandler.Detail) disputeRoutes.POST("/:id/cancel", disputeHandler.Cancel) } walletRoutes := api.Group("/wallet", requireAuth) { walletRoutes.GET("/balance", walletHandler.Balance) walletRoutes.GET("/ledger", walletHandler.Ledger) walletRoutes.POST("/withdraw", walletHandler.Withdraw) } paymentAccountRoutes := api.Group("/payment-accounts", requireAuth) { paymentAccountRoutes.GET("", paymentAccountHandler.List) paymentAccountRoutes.GET("/:id", paymentAccountHandler.FindByID) paymentAccountRoutes.POST("", paymentAccountHandler.Create) paymentAccountRoutes.PUT("/:id", paymentAccountHandler.Update) paymentAccountRoutes.DELETE("/:id", paymentAccountHandler.Delete) paymentAccountRoutes.POST("/:id/set-default", paymentAccountHandler.SetDefault) } withdrawalRoutes := api.Group("/withdrawals", requireAuth) { withdrawalRoutes.POST("", withdrawalHandler.Create) withdrawalRoutes.GET("", withdrawalHandler.List) withdrawalRoutes.GET("/:id", withdrawalHandler.FindByID) withdrawalRoutes.POST("/:id/cancel", withdrawalHandler.Cancel) } 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("/unread-count", chatHandler.UnreadCount) 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) } announcementRoutes := api.Group("/announcements") { announcementRoutes.GET("", announcementHandler.List) announcementRoutes.GET("/:id", announcementHandler.GetByID) } 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, middleware.RequireAdminPasswordChanged()) { adminRoutes.GET("/me", adminAuthHandler.Me) adminRoutes.PUT("/me/support-status", adminAuthHandler.UpdateSupportStatus) 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.POST("/users/:id/deposit-free-quota", requirePerm("user:deposit_free"), adminUserHandler.SetDepositFreeQuota) adminRoutes.POST("/users/:id/revoke-realname", requirePerm("user:revoke_realname"), adminUserHandler.RevokeRealname) adminRoutes.GET("/orders", requirePerm("order:view"), orderHandler.AdminList) adminRoutes.GET("/listing-orders/:id/latest", requirePerm("order:view"), orderHandler.AdminLatestByListing) 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/reset-handoff", requirePerm("order:mark_abnormal"), orderHandler.AdminResetHandoff) 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/adjust-price", requirePerm("listing:approve"), listingHandler.AdjustReviewPrice) adminRoutes.POST("/listings/:id/transfer-owner", requirePerm("listing:approve"), listingHandler.TransferOwner) 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("/finance/dashboard", requirePerm("wallet:view"), adminFinanceHandler.Dashboard) adminRoutes.GET("/finance/details", requirePerm("wallet:view"), adminFinanceHandler.Details) adminRoutes.GET("/wallet/ledger", requirePerm("wallet:view"), walletHandler.AdminLedger) adminRoutes.GET("/payments", requirePerm("wallet:view"), paymentHandler.AdminList) // 提现管理 adminRoutes.GET("/withdrawals", requirePerm("withdrawal:list"), withdrawalHandler.AdminList) adminRoutes.GET("/withdrawals/:id", requirePerm("withdrawal:detail"), withdrawalHandler.AdminFindByID) adminRoutes.POST("/withdrawals/:id/review", requirePerm("withdrawal:review"), withdrawalHandler.Review) adminRoutes.POST("/withdrawals/:id/confirm-payment", requirePerm("withdrawal:pay"), withdrawalHandler.ConfirmPayment) // 支付配置管理 if paymentConfigHandler != nil { adminRoutes.GET("/payment-configs", requirePerm("payment_config:list"), paymentConfigHandler.List) adminRoutes.GET("/payment-configs/export", requirePerm("payment_config:view_secret"), paymentConfigHandler.ExportBackup) adminRoutes.POST("/payment-configs/import", requirePerm("payment_config:view_secret"), paymentConfigHandler.ImportBackup) adminRoutes.GET("/payment-configs/:id", requirePerm("payment_config:list"), middleware.RequirePermissionIf("payment_config:view_secret", deps.Redis, func(c *gin.Context) bool { return c.Query("include_secret") == "true" }), paymentConfigHandler.Get) adminRoutes.POST("/payment-configs", requirePerm("payment_config:create"), paymentConfigHandler.Create) adminRoutes.PUT("/payment-configs/:id", requirePerm("payment_config:update"), paymentConfigHandler.Update) adminRoutes.DELETE("/payment-configs/:id", requirePerm("payment_config:delete"), paymentConfigHandler.Delete) } 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("/chats/listing-group-welcome", requirePerm("system_config:view"), chatHandler.AdminGetListingGroupWelcome) adminRoutes.PUT("/chats/listing-group-welcome", requirePerm("system_config:update"), chatHandler.AdminUpdateListingGroupWelcome) // 二维码池管理 adminRoutes.POST("/chats/qrcodes", requirePerm("chat:manage"), chatHandler.CreateQrCodeHandler) adminRoutes.POST("/chats/qrcodes/batch", requirePerm("chat:manage"), chatHandler.BatchCreateQrCodeHandler) adminRoutes.GET("/chats/qrcodes", requirePerm("chat:view"), chatHandler.ListQrCodesHandler) adminRoutes.GET("/chats/qrcodes/stats", requirePerm("chat:view"), chatHandler.GetQrCodeStatsHandler) adminRoutes.POST("/chats/qrcodes/ocr-group-name", requirePerm("chat:manage"), chatHandler.RecognizeQrCodeGroupNameHandler) adminRoutes.PATCH("/chats/qrcodes/:id", requirePerm("chat:manage"), chatHandler.UpdateQrCodeHandler) adminRoutes.DELETE("/chats/qrcodes/:id", requirePerm("chat:manage"), chatHandler.DeleteQrCodeHandler) // 客服分组 adminRoutes.GET("/chat-support-groups", requirePerm("chat:manage"), supportGroupHandler.List) adminRoutes.GET("/chat-support-groups/support-admins", requirePerm("chat:manage"), supportGroupHandler.ListSupportAdmins) adminRoutes.GET("/chat-support-groups/:id", requirePerm("chat:manage"), supportGroupHandler.FindByID) adminRoutes.POST("/chat-support-groups", requirePerm("chat:manage"), supportGroupHandler.Create) adminRoutes.PUT("/chat-support-groups/:id", requirePerm("chat:manage"), supportGroupHandler.Update) adminRoutes.DELETE("/chat-support-groups/:id", requirePerm("chat:manage"), supportGroupHandler.Delete) adminRoutes.PUT("/chat-support-groups/:id/members", requirePerm("chat:manage"), supportGroupHandler.AssignMembers) // 角色管理 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/me/password", adminMgrHandler.ChangeOwnPassword) adminRoutes.PUT("/admin-users/:id/password", requirePerm("admin_user:manage"), adminMgrHandler.ResetPassword) // 公告管理 adminRoutes.GET("/announcements", requirePerm("announcement:view"), announcementHandler.AdminList) adminRoutes.GET("/announcements/:id", requirePerm("announcement:view"), announcementHandler.AdminGetByID) adminRoutes.POST("/announcements", requirePerm("announcement:manage"), announcementHandler.Create) adminRoutes.PUT("/announcements/:id", requirePerm("announcement:manage"), announcementHandler.Update) adminRoutes.POST("/announcements/:id/publish", requirePerm("announcement:manage"), announcementHandler.Publish) adminRoutes.POST("/announcements/:id/archive", requirePerm("announcement:manage"), announcementHandler.Archive) adminRoutes.POST("/announcements/:id/unarchive", requirePerm("announcement:manage"), announcementHandler.Unarchive) adminRoutes.DELETE("/announcements/:id", requirePerm("announcement:manage"), announcementHandler.Delete) } } 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, encryptor crypto.Encryptor, 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, Encryptor: encryptor, }) 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(encryptor) } }