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 }