Files
opsx-licence/backend/database/database.go
T
Logan.Li 83a3630404
CI / backend (push) Canceled after 0s
feat(settings): 运行配置中心——.env 业务参数统一到后台页面维护
- 新增 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、哪些已收敛到页面
2026-11-01 06:17:58 +08:00

136 lines
4.1 KiB
Go

package database
import (
"backend/config"
"backend/internal/model"
"fmt"
"strings"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// Init 初始化数据库连接并执行迁移(当前交付形态以 SQLite 为准) // Logan.Li
func Init(cfg *config.Config) (*gorm.DB, error) {
logLevel := getLogLevel(cfg.Mode)
driver := strings.ToLower(strings.TrimSpace(cfg.DBDriver))
if driver != "" && driver != "sqlite" && driver != "sqlite3" {
return nil, fmt.Errorf("当前仅支持 SQLite(收到 DB_DRIVER=%s);MySQL/PostgreSQL 尚未接入", cfg.DBDriver)
}
dsn := cfg.DBName
if !strings.Contains(dsn, "?") {
dsn += "?_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL&_foreign_keys=on"
}
fmt.Printf("[数据库] 使用 SQLite(WAL): %s\n", cfg.DBName)
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logLevel),
})
if err != nil {
return nil, fmt.Errorf("SQLite 连接失败: %v", err)
}
sqlDB, err := db.DB()
if err != nil {
return nil, err
}
sqlDB.SetMaxOpenConns(1) // SQLite 写串行化,WAL 下读不阻塞 // Logan.Li
sqlDB.SetMaxIdleConns(1)
sqlDB.SetConnMaxLifetime(time.Hour)
return migrateDatabase(db)
}
// getLogLevel 根据运行模式返回日志级别 // Logan.Li
func getLogLevel(mode string) logger.LogLevel {
if mode == "release" {
return logger.Warn
}
return logger.Info
}
// migrateDatabase 执行数据库迁移 // Logan.Li
func migrateDatabase(db *gorm.DB) (*gorm.DB, error) {
err := db.AutoMigrate(
// 认证与系统
&model.User{}, // 管理员用户 // Logan.Li
&model.Setting{}, // 系统设置 // Logan.Li
&model.AuditLog{}, // 操作日志 // Logan.Li
&model.LicenseTemplate{}, // License 模板 // Logan.Li
&model.PaymentGatewayConfig{}, // 支付网关配置 // Logan.Li
&model.PaymentConfig{}, // 支付配置(微信/支付宝,敏感字段 AES-GCM 加密) // Logan.Li
&model.AppConfig{}, // 运行配置中心(页面可配参数,机密项加密) // Logan.Li
// 核心授权域
&model.Product{}, // 产品 // Logan.Li
&model.Customer{}, // 客户 // Logan.Li
&model.CustomerProduct{}, // 客户-产品关联 // Logan.Li
&model.License{}, // License // Logan.Li
&model.Activation{}, // 激活记录 // Logan.Li
// 定价与订单
&model.Plan{}, // 定价计划 // Logan.Li
&model.Order{}, // 订单 // Logan.Li
// 防重复购买
&model.CustomerEntitlement{}, // 客户权益 // Logan.Limodel.CustomerEntitlement{}, // 客户权益 // Logan.Li
// 节点互联
&model.PeerNode{}, // 双向同步对端节点 // Logan.Li
)
if err != nil {
return nil, fmt.Errorf("数据库迁移失败: %v", err)
}
// 初始化种子数据(默认管理员、默认设置、示例数据) // Logan.Li
if err := SeedDefaults(db); err != nil {
return nil, fmt.Errorf("种子数据初始化失败: %v", err)
}
if err := backfillCustomerProducts(db); err != nil {
return nil, fmt.Errorf("客户产品关联回填失败: %v", err)
}
return db, nil
}
// backfillCustomerProducts 用历史 License/权益回填客户-产品关联 // Logan.Li
func backfillCustomerProducts(db *gorm.DB) error {
type pair struct {
CustomerID uint
ProductID uint
}
var pairs []pair
if err := db.Model(&model.License{}).
Select("DISTINCT customer_id, product_id").
Where("customer_id > 0 AND product_id > 0").
Scan(&pairs).Error; err != nil {
return err
}
var entPairs []pair
_ = db.Model(&model.CustomerEntitlement{}).
Select("DISTINCT customer_id, product_id").
Where("customer_id > 0 AND product_id > 0").
Scan(&entPairs)
pairs = append(pairs, entPairs...)
seen := map[[2]uint]struct{}{}
for _, p := range pairs {
k := [2]uint{p.CustomerID, p.ProductID}
if _, ok := seen[k]; ok {
continue
}
seen[k] = struct{}{}
var n int64
db.Model(&model.CustomerProduct{}).
Where("customer_id = ? AND product_id = ?", p.CustomerID, p.ProductID).
Count(&n)
if n > 0 {
continue
}
if err := db.Create(&model.CustomerProduct{CustomerID: p.CustomerID, ProductID: p.ProductID}).Error; err != nil {
return err
}
}
return nil
}