- 新增 app_configs 表与 appconfig 注册表:读取优先级 页面 → .env → 默认,删除即回退 - 页面可配 7 项:支付回调基址、成功回跳、Webhook 密钥(加密入库)、未验签放行开关、CORS、可信代理、GeoIP 路径 - 引导密钥(JWT_SECRET / OPSLIC_MASTER_KEY / OFFLINE_LICENSE_SEED)与部署参数只读展示掩码+指纹,永不入库 - 校验:URL 格式、密钥长度≥16、CIDR、release 下禁止开启未验签回调;变更写审计日志 - 消费点改造:支付回调地址、Waffo 回跳、webhook 验签、可信代理、CORS、GeoIP 统一走配置中心 - 前端新增「审计与设置 → 运行配置」页(分组表格 + 来源标记 + 编辑弹窗 + 恢复默认) - .env.example 补注释:哪些必须留在 .env、哪些已收敛到页面
This commit is contained in:
1 parent
b0b611eee2
commit
83a3630404
17 files changed
+1204
-33
No files matched your search
@@ -0,0 +1,56 @@
|
||||
package appconfig
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"backend/internal/dto"
|
||||
appconfigservice "backend/internal/service/appconfig"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Controller 运行配置中心控制器(仅管理员,挂在 JWT 保护组下) // Logan.Li
|
||||
type Controller struct {
|
||||
svc *appconfigservice.Service
|
||||
}
|
||||
|
||||
// NewController 创建控制器(复用全局已装载的服务实例,保证读写同一份缓存) // Logan.Li
|
||||
func NewController(db *gorm.DB) *Controller {
|
||||
return &Controller{svc: appconfigservice.Standard(db)}
|
||||
}
|
||||
|
||||
// Overview 读取全部配置(机密项掩码) // Logan.Li
|
||||
func (ctrl *Controller) Overview(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "data": ctrl.svc.Overview()})
|
||||
}
|
||||
|
||||
// Update 批量保存配置 // Logan.Li
|
||||
func (ctrl *Controller) Update(c *gin.Context) {
|
||||
var req dto.RuntimeConfigUpdate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"})
|
||||
return
|
||||
}
|
||||
data, err := ctrl.svc.Update(req.Values, c.GetString("username"), c.ClientIP())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "运行配置已保存", "data": data})
|
||||
}
|
||||
|
||||
// Reset 恢复默认(删除页面值) // Logan.Li
|
||||
func (ctrl *Controller) Reset(c *gin.Context) {
|
||||
var req dto.RuntimeResetRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"})
|
||||
return
|
||||
}
|
||||
data, err := ctrl.svc.Reset(req.Keys, c.GetString("username"), c.ClientIP())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"code": 1, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "已恢复环境变量/默认值", "data": data})
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package order
|
||||
import (
|
||||
"backend/internal/dto"
|
||||
"backend/internal/model"
|
||||
"backend/internal/service/appconfig"
|
||||
orderservice "backend/internal/service/order"
|
||||
"backend/internal/service/payment"
|
||||
"backend/utils"
|
||||
@@ -107,7 +108,7 @@ func (ctrl *OrderController) ClaimPurchase(c *gin.Context) {
|
||||
|
||||
// PaymentWebhook 通用支付回调:必须携带与网关配置一致的共享密钥,禁止匿名结算 // Logan.Li
|
||||
func (ctrl *OrderController) PaymentWebhook(c *gin.Context) {
|
||||
secret := strings.TrimSpace(os.Getenv("INTERNAL_WEBHOOK_SECRET"))
|
||||
secret := appconfig.String("INTERNAL_WEBHOOK_SECRET")
|
||||
if secret == "" {
|
||||
var gw model.PaymentGatewayConfig
|
||||
if err := ctrl.db.Where("enabled = ?", true).Order("priority ASC").First(&gw).Error; err == nil {
|
||||
@@ -123,7 +124,7 @@ func (ctrl *OrderController) PaymentWebhook(c *gin.Context) {
|
||||
if secret == "" {
|
||||
// fail-closed:未配置密钥一律拒绝;仅当显式声明 ALLOW_UNVERIFIED_WEBHOOK=1
|
||||
// (本地调试)才放行,防止调试配置带进生产 // Logan.Li
|
||||
if release || os.Getenv("ALLOW_UNVERIFIED_WEBHOOK") != "1" {
|
||||
if release || !appconfig.Bool("ALLOW_UNVERIFIED_WEBHOOK") {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "未配置 webhook 密钥,已拒绝"})
|
||||
return
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user