package router import ( "context" "soul-api/internal/config" "soul-api/internal/database" "soul-api/internal/handler" "soul-api/internal/middleware" "soul-api/internal/redis" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) // Setup 创建并配置 Gin 引擎,路径与 app/api 一致 func Setup(cfg *config.Config) *gin.Engine { gin.SetMode(cfg.Mode) r := gin.New() r.Use(gin.Recovery()) r.Use(gin.Logger()) _ = r.SetTrustedProxies(cfg.TrustedProxies) r.Use(middleware.Secure()) r.Use(cors.New(cors.Config{ AllowOrigins: cfg.CORSOrigins, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, AllowCredentials: true, MaxAge: 86400, })) rateLimiter := middleware.NewRateLimiter(100, 200) r.Use(rateLimiter.Middleware()) uploadDir := cfg.UploadDir if uploadDir == "" { uploadDir = "./uploads" } r.Static("/uploads", uploadDir) api := r.Group("/api") { // ----- 管理端 ----- api.GET("/admin", handler.AdminCheck) api.POST("/admin", handler.AdminLogin) api.POST("/admin/logout", handler.AdminLogout) admin := api.Group("/admin") admin.Use(middleware.AdminAuth()) { admin.GET("/chapters", handler.AdminChaptersList) admin.POST("/chapters", handler.AdminChaptersAction) admin.PUT("/chapters", handler.AdminChaptersAction) admin.DELETE("/chapters", handler.AdminChaptersAction) admin.GET("/content", handler.AdminContent) admin.POST("/content", handler.AdminContent) admin.PUT("/content", handler.AdminContent) admin.DELETE("/content", handler.AdminContent) admin.GET("/dashboard/stats", handler.AdminDashboardStats) admin.GET("/dashboard/recent-orders", handler.AdminDashboardRecentOrders) admin.GET("/dashboard/new-users", handler.AdminDashboardNewUsers) admin.GET("/dashboard/overview", handler.AdminDashboardOverview) admin.GET("/dashboard/merchant-balance", handler.AdminDashboardMerchantBalance) admin.GET("/distribution/overview", handler.AdminDistributionOverview) admin.GET("/payment", handler.AdminPayment) admin.POST("/payment", handler.AdminPayment) admin.PUT("/payment", handler.AdminPayment) admin.DELETE("/payment", handler.AdminPayment) admin.GET("/referral", handler.AdminReferral) admin.POST("/referral", handler.AdminReferral) admin.PUT("/referral", handler.AdminReferral) admin.DELETE("/referral", handler.AdminReferral) admin.GET("/withdrawals", handler.AdminWithdrawalsList) admin.PUT("/withdrawals", handler.AdminWithdrawalsAction) admin.POST("/withdrawals/sync", handler.AdminWithdrawalsSync) admin.GET("/withdraw-test", handler.AdminWithdrawTest) admin.POST("/withdraw-test", handler.AdminWithdrawTest) admin.GET("/settings", handler.AdminSettingsGet) admin.POST("/settings", handler.AdminSettingsPost) admin.GET("/linked-miniprograms", handler.AdminLinkedMpList) admin.POST("/linked-miniprograms", handler.AdminLinkedMpCreate) admin.PUT("/linked-miniprograms", handler.AdminLinkedMpUpdate) admin.DELETE("/linked-miniprograms/:id", handler.AdminLinkedMpDelete) admin.GET("/referral-settings", handler.AdminReferralSettingsGet) admin.POST("/referral-settings", handler.AdminReferralSettingsPost) // 存客宝开放 API 辅助接口:设备列表(供链接人与事选择设备) admin.GET("/ckb/devices", handler.AdminCKBDevices) admin.GET("/author-settings", handler.AdminAuthorSettingsGet) admin.POST("/author-settings", handler.AdminAuthorSettingsPost) admin.GET("/shensheshou/query", handler.AdminShensheShouQuery) admin.POST("/shensheshou/enrich", handler.AdminShensheShouEnrich) admin.POST("/shensheshou/ingest", handler.AdminShensheShouIngest) admin.PUT("/orders/refund", handler.AdminOrderRefund) admin.GET("/users/:id/balance", handler.AdminUserBalanceGet) admin.POST("/users/:id/balance/adjust", handler.AdminUserBalanceAdjust) admin.GET("/users", handler.AdminUsersList) admin.POST("/users", handler.AdminUsersAction) admin.PUT("/users", handler.AdminUsersAction) admin.DELETE("/users", handler.AdminUsersAction) admin.GET("/orders", handler.OrdersList) admin.GET("/gift-pay-requests", handler.AdminGiftPayRequestsList) admin.GET("/user/track", handler.UserTrackGet) admin.GET("/track/stats", handler.AdminTrackStats) } // ----- 鉴权 ----- api.POST("/auth/login", handler.AuthLogin) api.POST("/auth/reset-password", handler.AuthResetPassword) // ----- 书籍/章节(只读,写操作由 /api/db/book 管理端路由承担) ----- api.GET("/book/all-chapters", handler.BookAllChapters) api.GET("/book/parts", handler.BookParts) api.GET("/book/chapters-by-part", handler.BookChaptersByPart) api.GET("/book/chapter/:id", handler.BookChapterByID) api.GET("/book/chapter/by-mid/:mid", handler.BookChapterByMID) api.GET("/book/chapters", handler.BookChapters) // POST/PUT/DELETE /api/book/chapters 已移除:写操作须由管理端 /api/db/book(AdminAuth)完成 api.GET("/book/hot", handler.BookHot) api.GET("/book/recommended", handler.BookRecommended) api.GET("/book/latest-chapters", handler.BookLatestChapters) api.GET("/book/search", handler.BookSearch) api.GET("/book/stats", handler.BookStats) api.GET("/book/sync", handler.BookSync) api.POST("/book/sync", handler.BookSync) // ----- CKB ----- api.POST("/ckb/join", handler.CKBJoin) api.POST("/ckb/match", handler.CKBMatch) api.GET("/ckb/sync", handler.CKBSync) api.POST("/ckb/sync", handler.CKBSync) // ----- 配置 ----- api.GET("/config", handler.GetConfig) // 小程序用:GET /api/db/config 返回 freeChapters、prices(不鉴权,先于 db 组匹配) api.GET("/db/config", handler.GetPublicDBConfig) // ----- 内容 ----- api.GET("/content", handler.ContentGet) // ----- 定时任务(须携带 X-Cron-Secret 请求头,与 .env CRON_SECRET 一致) ----- cron := api.Group("/cron") cron.Use(middleware.CronAuth()) { cron.GET("/sync-orders", handler.CronSyncOrders) cron.POST("/sync-orders", handler.CronSyncOrders) cron.GET("/unbind-expired", handler.CronUnbindExpired) cron.POST("/unbind-expired", handler.CronUnbindExpired) } // ----- 数据库(管理端) ----- db := api.Group("/db") db.Use(middleware.AdminAuth()) { db.GET("/book", handler.DBBookAction) db.POST("/book", handler.DBBookAction) db.PUT("/book", handler.DBBookAction) db.DELETE("/book", handler.DBBookDelete) db.GET("/chapters", handler.DBChapters) db.POST("/chapters", handler.DBChapters) db.GET("/config/full", handler.DBConfigGet) // 管理端拉全量配置;GET /api/db/config 已用于公开接口 GetPublicDBConfig db.POST("/config", handler.DBConfigPost) db.DELETE("/config", handler.DBConfigDelete) db.GET("/distribution", handler.DBDistribution) db.GET("/init", handler.DBInitGet) db.POST("/init", handler.DBInit) db.GET("/migrate", handler.DBMigrateGet) db.POST("/migrate", handler.DBMigratePost) db.GET("/users", handler.DBUsersList) db.POST("/users", handler.DBUsersAction) db.PUT("/users", handler.DBUsersAction) db.DELETE("/users", handler.DBUsersDelete) db.GET("/users/referrals", handler.DBUsersReferrals) db.GET("/users/rfm", handler.DBUsersRFM) db.GET("/users/journey-stats", handler.DBUsersJourneyStats) db.GET("/vip-roles", handler.DBVipRolesList) db.POST("/vip-roles", handler.DBVipRolesAction) db.PUT("/vip-roles", handler.DBVipRolesAction) db.DELETE("/vip-roles", handler.DBVipRolesAction) db.GET("/vip-members", handler.DBVipMembersList) db.GET("/match-records", handler.DBMatchRecordsList) db.GET("/match-pool-counts", handler.DBMatchPoolCounts) db.GET("/mentors", handler.DBMentorsList) db.POST("/mentors", handler.DBMentorsAction) db.PUT("/mentors", handler.DBMentorsAction) db.DELETE("/mentors", handler.DBMentorsAction) db.GET("/mentor-consultations", handler.DBMentorConsultationsList) db.GET("/persons", handler.DBPersonList) db.GET("/person", handler.DBPersonDetail) db.POST("/persons", handler.DBPersonSave) db.DELETE("/persons", handler.DBPersonDelete) db.GET("/link-tags", handler.DBLinkTagList) db.POST("/link-tags", handler.DBLinkTagSave) db.DELETE("/link-tags", handler.DBLinkTagDelete) db.GET("/ckb-leads", handler.DBCKBLeadList) db.GET("/ckb-plan-stats", handler.CKBPlanStats) db.GET("/user-rules", handler.DBUserRulesList) db.POST("/user-rules", handler.DBUserRulesAction) db.PUT("/user-rules", handler.DBUserRulesAction) db.DELETE("/user-rules", handler.DBUserRulesAction) } // ----- 分销 ----- api.GET("/distribution", handler.DistributionGet) api.POST("/distribution", handler.DistributionGet) api.PUT("/distribution", handler.DistributionGet) api.GET("/distribution/auto-withdraw-config", handler.DistributionAutoWithdrawConfig) api.POST("/distribution/auto-withdraw-config", handler.DistributionAutoWithdrawConfig) api.DELETE("/distribution/auto-withdraw-config", handler.DistributionAutoWithdrawConfig) api.GET("/distribution/messages", handler.DistributionMessages) api.POST("/distribution/messages", handler.DistributionMessages) // ----- 文档生成 ----- api.POST("/documentation/generate", handler.DocGenerate) // ----- 找伙伴 ----- api.GET("/match/config", handler.MatchConfigGet) api.POST("/match/config", handler.MatchConfigPost) api.POST("/match/users", handler.MatchUsers) // ----- 菜单 ----- api.GET("/menu", handler.MenuGet) // /api/orders 已移入 admin 组(需鉴权),见下方 // ----- 支付 ----- api.POST("/payment/alipay/notify", handler.PaymentAlipayNotify) api.POST("/payment/callback", handler.PaymentCallback) api.POST("/payment/create-order", handler.PaymentCreateOrder) api.GET("/payment/methods", handler.PaymentMethods) api.GET("/payment/query", handler.PaymentQuery) api.GET("/payment/status/:orderSn", handler.PaymentStatusOrderSn) api.POST("/payment/verify", handler.PaymentVerify) api.POST("/payment/wechat/notify", handler.PaymentWechatNotify) api.GET("/payment/wechat/transfer/notify", handler.PaymentWechatTransferNotify) api.POST("/payment/wechat/transfer/notify", handler.PaymentWechatTransferNotify) // ----- 推荐 ----- api.POST("/referral/bind", handler.ReferralBind) api.GET("/referral/data", handler.ReferralData) api.POST("/referral/visit", handler.ReferralVisit) // ----- 搜索 ----- api.GET("/search", handler.SearchGet) // ----- 同步 ----- api.GET("/sync", handler.SyncGet) api.POST("/sync", handler.SyncPost) api.PUT("/sync", handler.SyncPut) // ----- 上传 ----- api.POST("/upload", handler.UploadPost) api.DELETE("/upload", handler.UploadDelete) // ----- 用户 ----- api.GET("/user/addresses", handler.UserAddressesGet) api.POST("/user/addresses", handler.UserAddressesPost) api.GET("/user/addresses/:id", handler.UserAddressesByID) api.PUT("/user/addresses/:id", handler.UserAddressesByID) api.DELETE("/user/addresses/:id", handler.UserAddressesByID) api.GET("/user/check-purchased", handler.UserCheckPurchased) api.GET("/user/profile", handler.UserProfileGet) api.POST("/user/profile", handler.UserProfilePost) api.GET("/user/purchase-status", handler.UserPurchaseStatus) api.GET("/user/reading-progress", handler.UserReadingProgressGet) api.POST("/user/reading-progress", handler.UserReadingProgressPost) api.GET("/user/track", handler.UserTrackGet) api.POST("/user/track", handler.UserTrackPost) api.POST("/user/update", handler.UserUpdate) // ----- 微信登录 ----- api.POST("/wechat/login", handler.WechatLogin) api.POST("/wechat/phone-login", handler.WechatPhoneLogin) // ----- 小程序组(所有小程序端接口统一在 /api/miniprogram 下) ----- miniprogram := api.Group("/miniprogram") { miniprogram.GET("/config", handler.GetPublicDBConfig) miniprogram.POST("/login", handler.MiniprogramLogin) miniprogram.POST("/phone-login", handler.WechatPhoneLogin) miniprogram.POST("/dev/login-as", handler.MiniprogramDevLoginAs) // 开发专用:按 userId 切换账号 miniprogram.POST("/phone", handler.MiniprogramPhone) miniprogram.GET("/pay", handler.MiniprogramPay) miniprogram.POST("/pay", handler.MiniprogramPay) miniprogram.POST("/pay/notify", handler.MiniprogramPayNotify) // 微信支付回调,URL 需在商户平台配置 miniprogram.POST("/qrcode", handler.MiniprogramQrcode) miniprogram.GET("/qrcode/image", handler.MiniprogramQrcodeImage) miniprogram.GET("/book/all-chapters", handler.BookAllChapters) miniprogram.GET("/book/parts", handler.BookParts) miniprogram.GET("/book/chapters-by-part", handler.BookChaptersByPart) miniprogram.GET("/book/chapter/:id", handler.BookChapterByID) miniprogram.GET("/book/chapter/by-mid/:mid", handler.BookChapterByMID) miniprogram.GET("/book/hot", handler.BookHot) miniprogram.GET("/book/recommended", handler.BookRecommended) miniprogram.GET("/book/latest-chapters", handler.BookLatestChapters) miniprogram.GET("/book/search", handler.BookSearch) miniprogram.GET("/book/stats", handler.BookStats) miniprogram.POST("/referral/visit", handler.ReferralVisit) miniprogram.POST("/referral/bind", handler.ReferralBind) miniprogram.GET("/referral/data", handler.ReferralData) miniprogram.GET("/earnings", handler.MyEarnings) miniprogram.GET("/match/config", handler.MatchConfigGet) miniprogram.POST("/match/users", handler.MatchUsers) miniprogram.POST("/ckb/join", handler.CKBJoin) miniprogram.POST("/ckb/match", handler.CKBMatch) miniprogram.POST("/ckb/lead", handler.CKBLead) miniprogram.POST("/ckb/index-lead", handler.CKBIndexLead) miniprogram.POST("/upload", handler.UploadPost) miniprogram.DELETE("/upload", handler.UploadDelete) miniprogram.GET("/user/addresses", handler.UserAddressesGet) miniprogram.POST("/user/addresses", handler.UserAddressesPost) miniprogram.GET("/user/addresses/:id", handler.UserAddressesByID) miniprogram.PUT("/user/addresses/:id", handler.UserAddressesByID) miniprogram.DELETE("/user/addresses/:id", handler.UserAddressesByID) miniprogram.GET("/user/check-purchased", handler.UserCheckPurchased) miniprogram.GET("/user/dashboard-stats", handler.UserDashboardStats) miniprogram.GET("/user/profile", handler.UserProfileGet) miniprogram.POST("/user/profile", handler.UserProfilePost) miniprogram.GET("/user/purchase-status", handler.UserPurchaseStatus) miniprogram.GET("/user/reading-progress", handler.UserReadingProgressGet) miniprogram.POST("/user/reading-progress", handler.UserReadingProgressPost) miniprogram.POST("/user/update", handler.UserUpdate) miniprogram.POST("/withdraw", handler.WithdrawPost) miniprogram.GET("/withdraw/records", handler.WithdrawRecords) miniprogram.GET("/withdraw/pending-confirm", handler.WithdrawPendingConfirm) miniprogram.POST("/withdraw/confirm-received", handler.WithdrawConfirmReceived) miniprogram.GET("/withdraw/confirm-info", handler.WithdrawConfirmInfo) // VIP 接口(小程序专用,按使用方区分路径) miniprogram.GET("/vip/status", handler.VipStatus) miniprogram.GET("/vip/profile", handler.VipProfileGet) miniprogram.POST("/vip/profile", handler.VipProfilePost) miniprogram.GET("/vip/members", handler.VipMembers) // 用户列表/单个(首页超级个体、会员详情回退) miniprogram.GET("/users", handler.MiniprogramUsers) miniprogram.GET("/orders", handler.MiniprogramOrders) // 导师(stitch_soul) miniprogram.GET("/mentors", handler.MiniprogramMentorsList) miniprogram.GET("/mentors/:id", handler.MiniprogramMentorsDetail) miniprogram.POST("/mentors/:id/book", handler.MiniprogramMentorsBook) miniprogram.GET("/about/author", handler.MiniprogramAboutAuthor) // 埋点 miniprogram.POST("/track", handler.MiniprogramTrackPost) // 规则引擎(用户旅程引导) miniprogram.GET("/user-rules", handler.MiniprogramUserRulesGet) // 余额 miniprogram.GET("/balance", handler.BalanceGet) miniprogram.GET("/balance/transactions", handler.BalanceTransactionsGet) miniprogram.POST("/balance/recharge", handler.BalanceRechargePost) miniprogram.POST("/balance/recharge/confirm", handler.BalanceRechargeConfirmPost) miniprogram.POST("/balance/refund", handler.BalanceRefundPost) miniprogram.POST("/balance/consume", handler.BalanceConsumePost) miniprogram.GET("/gift/link", handler.GiftLinkGet) // 代付(美团式:代付页面) miniprogram.POST("/gift-pay/create", handler.GiftPayCreate) miniprogram.GET("/gift-pay/detail", handler.GiftPayDetail) miniprogram.POST("/gift-pay/pay", handler.GiftPayPay) miniprogram.POST("/gift-pay/cancel", handler.GiftPayCancel) miniprogram.GET("/gift-pay/my-requests", handler.GiftPayMyRequests) miniprogram.GET("/gift-pay/my-payments", handler.GiftPayMyPayments) } // ----- 提现 ----- api.POST("/withdraw", handler.WithdrawPost) api.GET("/withdraw/records", handler.WithdrawRecords) api.GET("/withdraw/pending-confirm", handler.WithdrawPendingConfirm) // 提现测试(固定用户 1 元,无需 admin 鉴权,仅用于脚本/本地调试) api.GET("/withdraw-test", handler.AdminWithdrawTest) api.POST("/withdraw-test", handler.AdminWithdrawTest) // ----- 提现 V3(独立实现,依文档 提现功能完整技术文档.md) ----- api.POST("/v3/withdraw/initiate", handler.WithdrawV3Initiate) api.POST("/v3/withdraw/notify", handler.WithdrawV3Notify) api.POST("/v3/withdraw/query", handler.WithdrawV3Query) } // 根路径不返回任何页面(仅 204) r.GET("/", func(c *gin.Context) { c.Status(204) }) // 健康检查:返回状态、版本号、数据库与 Redis 连接状态 r.GET("/health", func(c *gin.Context) { dbStatus := "ok" if sqlDB, err := database.DB().DB(); err != nil { dbStatus = "error" } else if err := sqlDB.Ping(); err != nil { dbStatus = "disconnected" } redisStatus := "disabled" if redis.Client() != nil { if err := redis.Client().Ping(context.Background()).Err(); err != nil { redisStatus = "disconnected" } else { redisStatus = "ok" } } c.JSON(200, gin.H{ "status": "ok", "version": cfg.Version, "database": dbStatus, "redis": redisStatus, }) }) return r }