// Package httpapi 路由装配。各业务模块在各自包内实现 handler,这里只负责挂载, // 并按配置在真实外部依赖(微信/支付/OSS/ASR)与 mock 之间切换。 package httpapi import ( "context" "net/http" "github.com/gin-gonic/gin" "github.com/redis/go-redis/v9" "gorm.io/gorm" "dudu/server/internal/asr" "dudu/server/internal/auth" "dudu/server/internal/billing" "dudu/server/internal/config" "dudu/server/internal/feedback" "dudu/server/internal/gateway" "dudu/server/internal/quota" "dudu/server/internal/telemetry" "dudu/server/internal/user" ) type Deps struct { Cfg config.Config DB *gorm.DB RDB *redis.Client JWT *auth.JWT Quota *quota.Manager } // pickProvider 按配置选择 ASR Provider(gummy 在 6B 实现 + DASHSCOPE_API_KEY 就绪后接入)。 func pickProvider(cfg config.Config) asr.Provider { switch cfg.ASRProvider { default: return asr.NewMock() } } // pickWechat 微信凭证未配置(#2A 申请中)时使用 mock。 func pickWechat(cfg config.Config) auth.WechatClient { if cfg.WechatWebAppID == "" { return auth.MockWechat{} } return auth.MockWechat{} // TODO(5C-真实): 开放平台实现,凭证就绪后替换 } // pickPay 商户号未配置(#2B 申请中)时使用 mock。 func pickPay(cfg config.Config) billing.PayClient { if cfg.WxPayMchID == "" { return billing.MockPay{} } return billing.MockPay{} // TODO(7D-真实): wechatpay-go 实现,商户号就绪后替换 } // pickStorage OSS 未配置时图片落本地盘。 func pickStorage(cfg config.Config) feedback.Storage { if cfg.OSSBucket == "" { return feedback.LocalStorage{Dir: "var/uploads"} } return feedback.LocalStorage{Dir: "var/uploads"} // TODO(8C-真实): OSS 实现 } func Register(r *gin.Engine, d *Deps) { r.GET("/healthz", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) }) authH := &auth.Handlers{DB: d.DB, RDB: d.RDB, JWT: d.JWT, Wechat: pickWechat(d.Cfg)} billH := &billing.Handlers{DB: d.DB, Pay: pickPay(d.Cfg), Quota: d.Quota} userH := &user.Handlers{DB: d.DB, Quota: d.Quota} fbH := &feedback.Handlers{DB: d.DB, RDB: d.RDB, Storage: pickStorage(d.Cfg)} telH := telemetry.New(d.DB, d.RDB) gwH := &gateway.Handler{Provider: pickProvider(d.Cfg), Quota: d.Quota, RDB: d.RDB} // 后台任务 bg := context.Background() billH.StartWatchdog(bg) d.Quota.StartDailyReconcile(bg) telH.StartDailyAggregate(bg) v1 := r.Group("/v1") // 🔓 无需登录 v1.POST("/auth/qr", authH.CreateQr) v1.GET("/auth/qr/:state", authH.PollQr) v1.GET("/auth/wechat/callback", authH.QrCallback) v1.POST("/auth/wechat", authH.MobileLogin) v1.GET("/packs", billH.Packs) v1.POST("/pay/notify", billH.Notify) v1.POST("/metrics/batch", telH.Batch) v1.GET("/app/latest", userH.AppLatest) // 需登录 authed := v1.Group("", d.JWT.Middleware()) authed.POST("/auth/logout", authH.Logout) authed.GET("/me", userH.Me) authed.POST("/orders", billH.CreateOrder) authed.GET("/orders/:id", billH.OrderStatus) authed.POST("/feedback", fbH.Submit) authed.GET("/asr/stream", gwH.HandleWS) }