CI / backend (push) Canceled after 0s
- 新增 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、哪些已收敛到页面
230 lines
9.0 KiB
Go
230 lines
9.0 KiB
Go
package appconfig
|
||
|
||
// 运行配置注册表:项目里所有「运维/业务参数」的唯一清单。
|
||
//
|
||
// 约定(重要):
|
||
// - Editable=true 的项可在后台「系统设置 → 运行配置」页维护,落库 app_configs,
|
||
// 读取优先级 DB → 环境变量 → Default;
|
||
// - Editable=false 的项只做展示(值来自环境变量),用于回答「哪些配置必须留在 .env」,
|
||
// 尤其是启动引导密钥:它们决定能否解开库里的密文 / 能否签发令牌,
|
||
// 放进数据库等于把钥匙锁进自己锁的箱子,因此永不入库; // Logan.Li
|
||
// - 新增配置时先在这里登记,再在消费点用 String/Bool/List 读取,避免散落的 os.Getenv。 // Logan.Li
|
||
|
||
// Kind 配置值类型 // Logan.Li
|
||
const (
|
||
KindString = "string" // 普通字符串
|
||
KindBool = "bool" // 开关,存 "1" / "0"
|
||
KindList = "list" // 逗号分隔列表
|
||
KindSecret = "secret" // 机密字符串(落库 AES-GCM 加密,页面只回显掩码)
|
||
)
|
||
|
||
// 配置分组顺序(页面按此顺序渲染) // Logan.Li
|
||
var GroupOrder = []string{"支付与回调", "安全与访问", "引导密钥(仅环境变量)", "部署参数(仅环境变量)"}
|
||
|
||
// Item 单个配置项的元数据 // Logan.Li
|
||
type Item struct {
|
||
Key string // 键名 = 环境变量名
|
||
Label string // 中文名
|
||
Group string // 分组
|
||
Description string // 说明(页面直接展示)
|
||
Kind string // 见 Kind 常量
|
||
Default string // 环境变量与数据库都缺省时的值
|
||
Editable bool // 是否允许页面编辑
|
||
RequiresRestart bool // 改动是否需要重启容器才生效
|
||
Danger bool // 高风险开关,页面二次确认
|
||
ReadOnlyReason string // Editable=false 时的原因说明
|
||
Placeholder string // 输入提示
|
||
}
|
||
|
||
// items 全量注册表(顺序即页面顺序) // Logan.Li
|
||
var items = []Item{
|
||
// ============ 支付与回调 ============
|
||
{
|
||
Key: "PAYMENT_PUBLIC_BASE_URL",
|
||
Label: "支付回调对外基址",
|
||
Group: "支付与回调",
|
||
Description: "生成微信/支付宝回调地址(notify_url)用的对外域名,需与浏览器访问域名一致且公网可达。",
|
||
Kind: KindString,
|
||
Default: "https://licence.rlwj.cn",
|
||
Editable: true,
|
||
Placeholder: "https://licence.rlwj.cn",
|
||
},
|
||
{
|
||
Key: "SUCCESS_URL",
|
||
Label: "支付成功回跳地址",
|
||
Group: "支付与回调",
|
||
Description: "买家付款完成后跳回的页面。Waffo 页签填写了同名字段时以页签为准,此项作为兜底。",
|
||
Kind: KindString,
|
||
Default: "",
|
||
Editable: true,
|
||
Placeholder: "https://licence.rlwj.cn/pay/success",
|
||
},
|
||
{
|
||
Key: "PAYMENT_CERT_DIR",
|
||
Label: "支付证书目录",
|
||
Group: "支付与回调",
|
||
Description: "容器内证书/私钥落地目录(0700),必须在数据卷内,否则重建容器即丢证书。",
|
||
Kind: KindString,
|
||
Default: "/data/payment-certs",
|
||
Editable: false,
|
||
ReadOnlyReason: "属于容器部署参数(数据卷路径),随镜像/编排交付,改错会导致证书丢失",
|
||
},
|
||
|
||
// ============ 安全与访问 ============
|
||
{
|
||
Key: "INTERNAL_WEBHOOK_SECRET",
|
||
Label: "内部 Webhook 共享密钥",
|
||
Group: "安全与访问",
|
||
Description: "通用支付回调 /api/v1/webhooks/payment 的验签密钥;未配置时回落到已启用网关的 WebhookSecret。留空表示不单独配置。",
|
||
Kind: KindSecret,
|
||
Default: "",
|
||
Editable: true,
|
||
Placeholder: "至少 16 位随机串",
|
||
},
|
||
{
|
||
Key: "ALLOW_UNVERIFIED_WEBHOOK",
|
||
Label: "允许未验签回调",
|
||
Group: "安全与访问",
|
||
Description: "仅本地调试使用。开启后未配置密钥也放行回调,任何人都能伪造「已付款」订单;生产(GIN_MODE=release)下禁止开启。",
|
||
Kind: KindBool,
|
||
Default: "0",
|
||
Editable: true,
|
||
Danger: true,
|
||
},
|
||
{
|
||
Key: "CORS_ORIGINS",
|
||
Label: "跨域白名单",
|
||
Group: "安全与访问",
|
||
Description: "允许跨域访问 API 的 Origin 列表(逗号分隔,含协议与端口)。留空 = 不限制来源。",
|
||
Kind: KindList,
|
||
Default: "",
|
||
Editable: true,
|
||
RequiresRestart: true,
|
||
Placeholder: "https://app.example.com,tauri://localhost",
|
||
},
|
||
{
|
||
Key: "TRUSTED_PROXIES",
|
||
Label: "可信反代网段",
|
||
Group: "安全与访问",
|
||
Description: "反向代理的内网 CIDR(逗号分隔),用于取真实客户端 IP;配置错误会导致限流与审计 IP 失真。",
|
||
Kind: KindList,
|
||
Default: "",
|
||
Editable: true,
|
||
RequiresRestart: true,
|
||
Placeholder: "127.0.0.1,172.16.0.0/12",
|
||
},
|
||
{
|
||
Key: "GEOIP_DB_PATH",
|
||
Label: "GeoIP 库路径",
|
||
Group: "安全与访问",
|
||
Description: "GeoLite2-City.mmdb 在容器内的路径,用于订单/激活的地理分布统计;文件不存在时静默降级。",
|
||
Kind: KindString,
|
||
Default: "/data/geoip/GeoLite2-City.mmdb",
|
||
Editable: true,
|
||
RequiresRestart: true,
|
||
Placeholder: "/data/geoip/GeoLite2-City.mmdb",
|
||
},
|
||
|
||
// ============ 引导密钥(仅环境变量) ============
|
||
{
|
||
Key: "JWT_SECRET",
|
||
Label: "登录令牌签名密钥",
|
||
Group: "引导密钥(仅环境变量)",
|
||
Description: "签发/校验管理员登录令牌。入库即与历史令牌脱钩,且数据库泄露等于直接交出后台登录能力,故只允许环境变量。",
|
||
Kind: KindSecret,
|
||
Editable: false,
|
||
ReadOnlyReason: "启动引导密钥:登录鉴权在数据库可用之前就要用它,且必须与历史令牌保持一致",
|
||
},
|
||
{
|
||
Key: "OPSLIC_MASTER_KEY",
|
||
Label: "机密字段信封加密主密钥",
|
||
Group: "引导密钥(仅环境变量)",
|
||
Description: "解开库里全部密文(支付私钥、证书口令、本页机密项)的根密钥,64 位 hex。丢失=所有密文不可解,泄露=所有密文失效。",
|
||
Kind: KindSecret,
|
||
Editable: false,
|
||
ReadOnlyReason: "根密钥必须独立于数据库存放,否则加密形同虚设;更换需先离线重加密全部密文",
|
||
},
|
||
{
|
||
Key: "OFFLINE_LICENSE_SEED",
|
||
Label: "离线授权签名种子",
|
||
Group: "引导密钥(仅环境变量)",
|
||
Description: "Ed25519 种子(64 位 hex),签发离线授权证。更换后已发出的离线证将无法验证,需同步桌面端公钥,故不提供页面修改。",
|
||
Kind: KindSecret,
|
||
Editable: false,
|
||
ReadOnlyReason: "更换会使既有离线授权证全部失效(桌面端内置公钥需同步升级),须人工走轮换流程",
|
||
},
|
||
|
||
// ============ 部署参数(仅环境变量) ============
|
||
{
|
||
Key: "PORT",
|
||
Label: "服务监听端口",
|
||
Group: "部署参数(仅环境变量)",
|
||
Kind: KindString,
|
||
Default: "9001",
|
||
Editable: false,
|
||
ReadOnlyReason: "进程启动参数:端口在读取数据库之前就已绑定",
|
||
},
|
||
{
|
||
Key: "GIN_MODE",
|
||
Label: "运行模式",
|
||
Group: "部署参数(仅环境变量)",
|
||
Kind: KindString,
|
||
Default: "debug",
|
||
Editable: false,
|
||
ReadOnlyReason: "进程启动参数:决定日志级别与安全开关的 fail-closed 行为",
|
||
},
|
||
{
|
||
Key: "DB_DRIVER",
|
||
Label: "数据库驱动",
|
||
Group: "部署参数(仅环境变量)",
|
||
Kind: KindString,
|
||
Default: "sqlite",
|
||
Editable: false,
|
||
ReadOnlyReason: "启动引导参数:数据库连接建立前无法读取本表",
|
||
},
|
||
{
|
||
Key: "DB_NAME",
|
||
Label: "数据库文件 / 库名",
|
||
Group: "部署参数(仅环境变量)",
|
||
Kind: KindString,
|
||
Default: "db.sqlite3",
|
||
Editable: false,
|
||
ReadOnlyReason: "启动引导参数:数据库连接建立前无法读取本表",
|
||
},
|
||
{
|
||
Key: "HOST_PORT",
|
||
Label: "宿主机映射端口",
|
||
Group: "部署参数(仅环境变量)",
|
||
Kind: KindString,
|
||
Default: "9001",
|
||
Editable: false,
|
||
ReadOnlyReason: "docker compose 编排参数,由宿主机侧读取,进程内不可见",
|
||
},
|
||
{
|
||
Key: "VERSION",
|
||
Label: "版本标记",
|
||
Group: "部署参数(仅环境变量)",
|
||
Kind: KindString,
|
||
Default: "",
|
||
Editable: false,
|
||
ReadOnlyReason: "构建期注入的镜像版本标记,运行时只读",
|
||
},
|
||
}
|
||
|
||
// Items 返回注册表副本(调用方可自由修改,不污染全局) // Logan.Li
|
||
func Items() []Item {
|
||
out := make([]Item, len(items))
|
||
copy(out, items)
|
||
return out
|
||
}
|
||
|
||
// Lookup 按键查注册项 // Logan.Li
|
||
func Lookup(key string) (Item, bool) {
|
||
for _, it := range items {
|
||
if it.Key == key {
|
||
return it, true
|
||
}
|
||
}
|
||
return Item{}, false
|
||
}
|