Files
opsx-licence/backend/router/router.go
T
Logan.Li 7d53c6ed3c feat(payment): 后台「支付配置」支持微信支付与支付宝凭据管理
后端:
- 新增 payment_configs 单行配置表;APIv3 密钥、应用私钥/公钥以 AES-256-GCM 密文入库(enc:v1 前缀,主密钥取 PAYMENT_SECRET_KEY,缺省回退 JWT_SECRET 派生)
- 证书/私钥落 PAYMENT_CERT_DIR(默认 /data/payment-certs),目录 0700 / 文件 0600,不经任何静态路由暴露
- 接口:GET/PUT /api/admin/payment/config、POST /upload(PEM 校验 + 微信证书序列号自动回填)、POST /test(真打官方接口:微信 /v3/certificates 签名校验、支付宝 alipay.trade.query 签名校验)、POST /wechat/platform-certs(APIv3 密钥解密平台证书并落盘)
- 未配置凭据时测试返回 skipped 而不是 500;敏感字段在返回体里一律脱敏

前端:
- /payments 改为「通用设置 + 微信支付 / 支付宝 / Waffo 海外支付」页签(原 Waffo 页整页迁到 views/payments/Waffo.vue)
- 微信:mchid、AppID、APIv3 密钥(密码框 + 掩码回显 + 更换/清除)、证书序列号、支付方式多选、三项证书上传、自动获取平台证书
- 支付宝:app_id、应用私钥(粘贴/上传)、支付宝公钥、网关、RSA2、回调地址
- 菜单归入「审计与设置 → 支付配置」
2026-11-01 05:59:51 +08:00

308 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package router
import (
"backend/config"
"backend/internal/controller/analytics"
"backend/internal/controller/auth"
"backend/internal/controller/customer"
"backend/internal/controller/entitlement"
"backend/internal/controller/license"
"backend/internal/controller/order"
paymentctl "backend/internal/controller/payment"
"backend/internal/controller/plan"
"backend/internal/controller/product"
"backend/internal/controller/setting"
synccontroller "backend/internal/controller/sync"
"backend/middleware"
"os"
"strings"
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// register 同时注册带和不带 trailing slash 的路由 // Logan.Li
func register(r gin.IRoutes, method, relativePath string, handlers ...gin.HandlerFunc) {
isParamRoute := strings.Contains(relativePath, ":")
switch method {
case "GET":
r.GET(relativePath, handlers...)
if !isParamRoute {
r.GET(relativePath+"/", handlers...)
}
case "POST":
r.POST(relativePath, handlers...)
if !isParamRoute {
r.POST(relativePath+"/", handlers...)
}
case "PUT":
r.PUT(relativePath, handlers...)
if !isParamRoute {
r.PUT(relativePath+"/", handlers...)
}
case "DELETE":
r.DELETE(relativePath, handlers...)
if !isParamRoute {
r.DELETE(relativePath+"/", handlers...)
}
case "PATCH":
r.PATCH(relativePath, handlers...)
if !isParamRoute {
r.PATCH(relativePath+"/", handlers...)
}
}
}
// SetupRouter 初始化路由 // Logan.Li
func SetupRouter(db *gorm.DB, cfg *config.Config) *gin.Engine {
gin.SetMode(cfg.Mode)
r := gin.Default()
r.RedirectTrailingSlash = false
r.RedirectFixedPath = false
// 信任代理:默认不信任任何代理头(X-Forwarded-For 可伪造,会绕过按 IP 限流);
// 经 nginx 反代的节点用 TRUSTED_PROXIES 指定其内网 CIDR,多个用逗号分隔 // Logan.Li
if proxies := strings.TrimSpace(os.Getenv("TRUSTED_PROXIES")); proxies != "" {
if err := r.SetTrustedProxies(strings.Split(proxies, ",")); err != nil {
panic("TRUSTED_PROXIES 配置非法: " + err.Error())
}
} else if err := r.SetTrustedProxies(nil); err != nil {
panic("SetTrustedProxies 失败: " + err.Error())
}
// Gzip 压缩中间件 // Logan.Li
r.Use(gzip.Gzip(gzip.DefaultCompression))
// CORS 中间件 // Logan.Li
r.Use(middleware.CORSMiddleware(cfg))
// 存活探针(不查库) // Logan.Li
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"code": 0, "message": "ok", "data": nil})
})
r.GET("/healthz", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "alive"})
})
// 就绪探针:检查数据库可达 // Logan.Li
r.GET("/readyz", func(c *gin.Context) {
sqlDB, err := db.DB()
if err != nil || sqlDB.Ping() != nil {
c.JSON(503, gin.H{"status": "db unreachable"})
return
}
c.JSON(200, gin.H{"status": "ready"})
})
// 初始化控制器 // Logan.Li
authController := auth.NewAuthController(db, cfg)
productController := product.NewProductController(db)
customerController := customer.NewCustomerController(db)
licenseController := license.NewLicenseController(db, cfg)
planController := plan.NewPlanController(db)
orderController := order.NewOrderController(db)
entitlementController := entitlement.NewEntitlementController(db)
analyticsController := analytics.NewAnalyticsController(db)
settingController := setting.NewSettingController(db)
syncController := synccontroller.NewController(db, cfg)
paymentConfigController := paymentctl.NewPaymentConfigController(db, cfg)
api := r.Group("/api")
{
// ============ 节点互联(对端调用,HMAC 签名认证,非 JWT) ============
syncGroup := api.Group("/v1/sync")
{
syncGroup.POST("/handshake", middleware.RateLimit(1, 30), syncController.HandshakeIn)
syncGroup.POST("/exchange", middleware.RateLimit(2, 60), syncController.ExchangeIn)
}
// ============ 认证路由(无需 JWT) ============
authGroup := api.Group("/auth")
{
register(authGroup, "POST", "/login", middleware.RateLimit(0.2, 5), authController.Login)
}
// ============ 客户端授权契约(无需认证,供外部工具调用) ============
clientAPI := api.Group("/v1")
{
licLimit := middleware.RateLimit(2, 30)
checkoutLimit := middleware.RateLimit(0.5, 10)
webhookLimit := middleware.RateLimit(1, 20)
// License 验证 / 激活 / 停用 / 心跳
register(clientAPI, "POST", "/license/verify", licLimit, licenseController.Verify)
register(clientAPI, "POST", "/license/activate", licLimit, licenseController.ActivateDevice)
register(clientAPI, "POST", "/license/deactivate", licLimit, licenseController.Deactivate)
register(clientAPI, "POST", "/license/heartbeat", licLimit, licenseController.Heartbeat)
register(clientAPI, "POST", "/license/devices", licLimit, licenseController.ListDevices)
// 产品定价查询(展示用)
register(clientAPI, "GET", "/products/code/:code", productController.GetByCode)
clientAPI.GET("/products/:product_id/plans", planController.GetByProduct)
// 订单与支付(客户端购买入口 + 支付回调)
register(clientAPI, "POST", "/orders", checkoutLimit, orderController.CreateOrder)
register(clientAPI, "POST", "/checkout", checkoutLimit, orderController.Checkout)
register(clientAPI, "POST", "/purchase/claim", checkoutLimit, orderController.ClaimPurchase)
register(clientAPI, "POST", "/webhooks/payment", webhookLimit, orderController.PaymentWebhook)
register(clientAPI, "GET", "/webhooks/waffo", orderController.WaffoWebhookReady)
register(clientAPI, "POST", "/webhooks/waffo", webhookLimit, orderController.WaffoWebhook)
register(clientAPI, "GET", "/purchase/success", orderController.PurchaseSuccess)
register(clientAPI, "POST", "/purchase/check", checkoutLimit, orderController.CheckDuplicatePurchase)
register(clientAPI, "POST", "/purchase/status", checkoutLimit, orderController.CheckPurchaseStatus)
// 客户权益查询(通过邮箱)
register(clientAPI, "GET", "/entitlements/my", checkoutLimit, entitlementController.GetByEmail)
}
// ============ 需要认证的管理路由 ============
protected := api.Group("", middleware.AuthMiddleware(cfg, db))
{
// 认证相关
register(protected, "GET", "/auth/me", authController.Me)
register(protected, "PUT", "/auth/password", authController.ChangePassword)
// 产品管理
products := protected.Group("/products")
{
register(products, "GET", "", productController.List)
register(products, "GET", "/all", productController.GetAll)
register(products, "POST", "", productController.Create)
register(products, "GET", "/:id", productController.Get)
register(products, "PUT", "/:id", productController.Update)
register(products, "DELETE", "/:id", productController.Delete)
register(products, "POST", "/batch-delete", productController.BatchDelete)
register(products, "PATCH", "/:id/status", productController.UpdateStatus)
}
// 客户管理
customers := protected.Group("/customers")
{
register(customers, "GET", "", customerController.List)
register(customers, "GET", "/all", customerController.GetAll)
register(customers, "POST", "", customerController.Create)
register(customers, "GET", "/:id", customerController.Get)
register(customers, "PUT", "/:id", customerController.Update)
register(customers, "DELETE", "/:id", customerController.Delete)
register(customers, "POST", "/batch-delete", customerController.BatchDelete)
register(customers, "PATCH", "/:id/status", customerController.UpdateStatus)
}
// License 管理
licenses := protected.Group("/licenses")
{
register(licenses, "GET", "", licenseController.List)
register(licenses, "POST", "", licenseController.Create)
register(licenses, "POST", "/offline", licenseController.IssueOffline)
register(licenses, "GET", "/offline/public-key", licenseController.OfflinePublicKey)
register(licenses, "GET", "/export", licenseController.Export)
register(licenses, "POST", "/batch-delete", licenseController.BatchDelete)
register(licenses, "GET", "/:id", licenseController.Get)
register(licenses, "PUT", "/:id", licenseController.Update)
register(licenses, "DELETE", "/:id", licenseController.Delete)
register(licenses, "POST", "/:id/renew", licenseController.Renew)
register(licenses, "POST", "/:id/revoke", licenseController.Revoke)
register(licenses, "POST", "/:id/suspend", licenseController.Suspend)
register(licenses, "POST", "/:id/activate", licenseController.Activate)
register(licenses, "GET", "/:id/activations", licenseController.GetActivations)
register(licenses, "POST", "/:id/activations/:activationId/offline", licenseController.ForceOffline)
}
// 定价计划管理
plans := protected.Group("/plans")
{
register(plans, "GET", "", planController.List)
register(plans, "POST", "", planController.Create)
register(plans, "PUT", "/:id", planController.Update)
register(plans, "DELETE", "/:id", planController.Delete)
register(plans, "PATCH", "/:id/status", planController.ToggleStatus)
}
// 订单管理
orders := protected.Group("/orders")
{
register(orders, "GET", "", orderController.List)
register(orders, "GET", "/:id", orderController.GetDetail)
register(orders, "POST", "/manual-grant", orderController.ManualGrant)
register(orders, "POST", "/batch-delete", orderController.BatchDelete)
}
// 客户权益管理
entitlements := protected.Group("/entitlements")
{
register(entitlements, "GET", "", entitlementController.List)
register(entitlements, "GET", "/:id", entitlementController.Get)
register(entitlements, "POST", "/:id/revoke", entitlementController.Revoke)
register(entitlements, "POST", "/:id/renew", entitlementController.Renew)
}
// 仪表板与数据分析
dashboard := protected.Group("/dashboard")
{
register(dashboard, "GET", "/stats", analyticsController.GetDashboardStats)
register(dashboard, "GET", "/expiring", analyticsController.GetExpiringLicenses)
register(dashboard, "GET", "/activities", analyticsController.GetRecentActivities)
register(dashboard, "GET", "/online-stats", analyticsController.GetOnlineStats)
register(dashboard, "GET", "/revenue", analyticsController.GetRevenueStats)
}
analyticsGroup := protected.Group("/analytics")
{
register(analyticsGroup, "GET", "/license-growth", analyticsController.GetLicenseGrowth)
register(analyticsGroup, "GET", "/license-growth-detail", analyticsController.GetLicenseGrowthDetail)
register(analyticsGroup, "GET", "/product-distribution", analyticsController.GetProductDistribution)
register(analyticsGroup, "GET", "/revenue", analyticsController.GetRevenueStats)
register(analyticsGroup, "GET", "/geo-distribution", analyticsController.GetGeoDistribution)
register(analyticsGroup, "GET", "/platform-distribution", analyticsController.GetPlatformDistribution)
}
// 日志审计与系统设置
logs := protected.Group("/logs")
{
register(logs, "GET", "", settingController.ListAuditLogs)
register(logs, "DELETE", "/clear", settingController.ClearAuditLogs)
}
system := protected.Group("/settings")
{
register(system, "GET", "", settingController.Get)
register(system, "PUT", "", settingController.Update)
register(system, "GET", "/waffo", settingController.GetWaffo)
register(system, "PUT", "/waffo", settingController.UpdateWaffo)
register(system, "POST", "/waffo/test", settingController.DebugWaffo)
}
// 节点互联管理(双向授权同步) // Logan.Li
syncAdmin := protected.Group("/sync")
{
register(syncAdmin, "GET", "/peers", syncController.ListPeers)
register(syncAdmin, "POST", "/peers", syncController.SavePeer)
register(syncAdmin, "DELETE", "/peers/:id", syncController.DeletePeer)
register(syncAdmin, "POST", "/peers/:id/handshake", syncController.TestHandshake)
register(syncAdmin, "POST", "/peers/:id/sync", syncController.SyncNow)
register(syncAdmin, "GET", "/generate-key", syncController.GenerateKey)
}
// 支付调试(与 settings/waffo/test 同一 handler) // Logan.Li
paymentGroup := protected.Group("/payment")
{
register(paymentGroup, "POST", "/waffo/debug", settingController.DebugWaffo)
}
// 支付配置(微信支付 / 支付宝,仅管理员) // Logan.Li
paymentConfig := protected.Group("/admin/payment")
{
register(paymentConfig, "GET", "/config", paymentConfigController.GetConfig)
register(paymentConfig, "PUT", "/config", paymentConfigController.UpdateConfig)
register(paymentConfig, "POST", "/upload", paymentConfigController.Upload)
register(paymentConfig, "POST", "/test", paymentConfigController.Test)
register(paymentConfig, "POST", "/wechat/platform-certs", paymentConfigController.FetchWechatPlatformCerts)
}
}
}
return r
}