308 lines
8.3 KiB
Go
308 lines
8.3 KiB
Go
package database
|
|
|
|
import (
|
|
"backend/internal/model"
|
|
"backend/utils"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"log"
|
|
"os"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const (
|
|
defaultAdminUsername = "admin"
|
|
// 仅 debug 模式使用固定默认密码;release 模式首次启动生成随机密码并打印到日志 // Logan.Li
|
|
defaultAdminPassword = "admin"
|
|
)
|
|
|
|
// initialAdminPassword 按运行模式决定初始管理员密码:release 随机生成,debug 用固定值 // Logan.Li
|
|
func initialAdminPassword() string {
|
|
if os.Getenv("GIN_MODE") == "release" {
|
|
buf := make([]byte, 15)
|
|
if _, err := rand.Read(buf); err != nil {
|
|
log.Fatal("[安全] 生成初始管理员密码失败:", err)
|
|
}
|
|
return base64.RawURLEncoding.EncodeToString(buf)
|
|
}
|
|
return defaultAdminPassword
|
|
}
|
|
|
|
// SeedDefaults 初始化默认数据:管理员账号 + 系统设置 + 示例业务数据 // Logan.Li
|
|
func SeedDefaults(db *gorm.DB) error {
|
|
if err := seedAdmin(db); err != nil {
|
|
return err
|
|
}
|
|
if err := seedSetting(db); err != nil {
|
|
return err
|
|
}
|
|
if err := seedSampleData(db); err != nil {
|
|
return err
|
|
}
|
|
if err := ensureOpsXShellPlanPricing(db); err != nil {
|
|
return err
|
|
}
|
|
if err := ensureWaffoPaymentGateway(db); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// seedAdmin 初始化默认管理员账号(仅在不存在时创建) // Logan.Li
|
|
func seedAdmin(db *gorm.DB) error {
|
|
var count int64
|
|
db.Model(&model.User{}).Where("username = ?", defaultAdminUsername).Count(&count)
|
|
if count > 0 {
|
|
return nil
|
|
}
|
|
|
|
initialPassword := initialAdminPassword()
|
|
admin := model.User{
|
|
Username: defaultAdminUsername,
|
|
Password: utils.HashPassword(initialPassword),
|
|
Email: "admin@opsx-licence.local",
|
|
Role: "admin",
|
|
IsActive: true,
|
|
}
|
|
if err := db.Create(&admin).Error; err != nil {
|
|
return err
|
|
}
|
|
log.Printf("[初始化] 已创建管理员账号: %s / %s(仅打印一次,release 部署后请立即修改密码)",
|
|
defaultAdminUsername, initialPassword)
|
|
return nil
|
|
}
|
|
|
|
// seedSetting 初始化默认系统设置(仅创建一次) // Logan.Li
|
|
func seedSetting(db *gorm.DB) error {
|
|
var count int64
|
|
db.Model(&model.Setting{}).Count(&count)
|
|
if count > 0 {
|
|
return nil
|
|
}
|
|
|
|
setting := model.Setting{
|
|
SystemName: "opsx-licence",
|
|
DefaultDuration: 365,
|
|
DefaultMaxActivations: 1,
|
|
ExpireReminderDays: 7,
|
|
APIRateLimit: 1000,
|
|
LogRetentionDays: 365,
|
|
}
|
|
if err := db.Create(&setting).Error; err != nil {
|
|
return err
|
|
}
|
|
log.Println("[初始化] 已创建默认系统设置")
|
|
return nil
|
|
}
|
|
|
|
// seedSampleData 初始化 OpsXShell 产品 / 计划 / Waffo 网关,便于桌面端联调 // Logan.Li
|
|
func seedSampleData(db *gorm.DB) error {
|
|
var productCount int64
|
|
db.Model(&model.Product{}).Count(&productCount)
|
|
if productCount > 0 {
|
|
return nil
|
|
}
|
|
|
|
product := model.Product{
|
|
Name: "OpsXShell",
|
|
Code: "opsxshell",
|
|
Version: "2.0.0",
|
|
Description: "OpsXShell Pro — SSH / SFTP / RDP / VNC / AI",
|
|
Features: `["ssh","sftp","rdp","vnc","ai","portForward","proxy","serial","batchExec"]`,
|
|
Price: 199.00,
|
|
PriceModel: "perpetual",
|
|
Status: 1,
|
|
}
|
|
if err := db.Create(&product).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
customer := model.Customer{
|
|
Name: "Demo",
|
|
Company: "OpsX",
|
|
Email: "demo@opsxshell.local",
|
|
Status: 1,
|
|
}
|
|
if err := db.Create(&customer).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
plans := []model.Plan{
|
|
{
|
|
ProductID: product.ID,
|
|
Name: "体验",
|
|
PlanType: "trial",
|
|
Price: 0,
|
|
Currency: "CNY",
|
|
Duration: 180,
|
|
MaxDevices: 1,
|
|
Features: `["专业功能","体验","1 设备"]`,
|
|
SortOrder: 0,
|
|
Status: 1,
|
|
},
|
|
{
|
|
ProductID: product.ID,
|
|
Name: "包年",
|
|
PlanType: "yearly",
|
|
Price: 99,
|
|
Currency: "CNY",
|
|
Duration: 365,
|
|
MaxDevices: 2,
|
|
Features: `["专业功能","包年","2 设备"]`,
|
|
SortOrder: 1,
|
|
Status: 1,
|
|
},
|
|
{
|
|
ProductID: product.ID,
|
|
Name: "永久",
|
|
PlanType: "perpetual",
|
|
Price: 199,
|
|
Currency: "CNY",
|
|
Duration: 0,
|
|
MaxDevices: 5,
|
|
Features: `["专业功能","永久授权","5 设备"]`,
|
|
SortOrder: 2,
|
|
Status: 1,
|
|
},
|
|
}
|
|
for i := range plans {
|
|
if err := db.Create(&plans[i]).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
now := time.Now()
|
|
license := model.License{
|
|
LicenseKey: utils.GenerateLicenseKey(),
|
|
CustomerID: customer.ID,
|
|
ProductID: product.ID,
|
|
Status: model.LicenseStatusActive,
|
|
StartDate: now,
|
|
EndDate: model.PermanentEndDate,
|
|
Duration: 0,
|
|
MaxActivations: 5,
|
|
Notes: "开发示例授权",
|
|
}
|
|
if err := db.Create(&license).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
gateway := model.PaymentGatewayConfig{
|
|
Gateway: "waffo",
|
|
Enabled: true,
|
|
SandboxMode: true,
|
|
Currency: "USD",
|
|
Priority: 1,
|
|
}
|
|
if err := db.Create(&gateway).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("[初始化] OpsXShell 产品已就绪 (code=opsxshell),示例 License: %s", license.LicenseKey)
|
|
return nil
|
|
}
|
|
|
|
// ensureOpsXShellPlanPricing ensures opsxshell product exists and syncs plan price/seats. // Logan.Li
|
|
func ensureOpsXShellPlanPricing(db *gorm.DB) error {
|
|
var product model.Product
|
|
err := db.Where("code = ?", "opsxshell").First(&product).Error
|
|
if err != nil {
|
|
// Prefer renaming legacy demo product so existing plans/licenses stay attached.
|
|
if renameErr := db.Where("code = ?", "opsx-tools").First(&product).Error; renameErr == nil {
|
|
product.Code = "opsxshell"
|
|
product.Name = "OpsXShell"
|
|
product.Description = "OpsXShell Pro — SSH / SFTP / RDP / VNC / AI"
|
|
product.Features = `["ssh","sftp","rdp","vnc","ai","portForward","proxy","serial","batchExec"]`
|
|
product.Price = 199
|
|
product.PriceModel = "perpetual"
|
|
product.Status = 1
|
|
if err := db.Save(&product).Error; err != nil {
|
|
return err
|
|
}
|
|
log.Println("[初始化] 已将 opsx-tools 迁移为 opsxshell")
|
|
} else {
|
|
product = model.Product{
|
|
Name: "OpsXShell",
|
|
Code: "opsxshell",
|
|
Version: "2.0.0",
|
|
Description: "OpsXShell Pro — SSH / SFTP / RDP / VNC / AI",
|
|
Features: `["ssh","sftp","rdp","vnc","ai","portForward","proxy","serial","batchExec"]`,
|
|
Price: 199,
|
|
PriceModel: "perpetual",
|
|
Status: 1,
|
|
}
|
|
if err := db.Create(&product).Error; err != nil {
|
|
return err
|
|
}
|
|
log.Println("[初始化] 已创建 opsxshell 产品")
|
|
}
|
|
}
|
|
type planSpec struct {
|
|
planType string
|
|
name string
|
|
price float64
|
|
currency string
|
|
duration int
|
|
maxDevices int
|
|
features string
|
|
sortOrder int
|
|
}
|
|
updates := []planSpec{
|
|
{"trial", "体验", 0, "CNY", 180, 1, "[\"专业功能\",\"体验\",\"1 设备\"]", 0},
|
|
{"yearly", "包年", 99, "CNY", 365, 2, "[\"专业功能\",\"包年\",\"2 设备\"]", 1},
|
|
{"perpetual", "永久", 199, "CNY", 0, 5, "[\"专业功能\",\"永久授权\",\"5 设备\"]", 2},
|
|
}
|
|
for _, u := range updates {
|
|
var plan model.Plan
|
|
err := db.Where("product_id = ? AND plan_type = ?", product.ID, u.planType).First(&plan).Error
|
|
if err == nil {
|
|
continue // 已存在:尊重管理员运行时修改,不再覆盖 // Logan.Li
|
|
}
|
|
plan = model.Plan{
|
|
ProductID: product.ID,
|
|
Name: u.name,
|
|
PlanType: u.planType,
|
|
Price: u.price,
|
|
Currency: u.currency,
|
|
Duration: u.duration,
|
|
MaxDevices: u.maxDevices,
|
|
Features: u.features,
|
|
SortOrder: u.sortOrder,
|
|
Status: 1,
|
|
}
|
|
if err := db.Create(&plan).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
log.Printf("[初始化] OpsXShell 套餐已确保存在(仅新建,不覆盖已有价格)")
|
|
return nil
|
|
}
|
|
|
|
// ensureWaffoPaymentGateway makes pancake.waffo.ai the preferred checkout gateway. // Logan.Li
|
|
func ensureWaffoPaymentGateway(db *gorm.DB) error {
|
|
var existing model.PaymentGatewayConfig
|
|
err := db.Where("gateway = ?", "waffo").First(&existing).Error
|
|
if err != nil {
|
|
gw := model.PaymentGatewayConfig{
|
|
Gateway: "waffo",
|
|
Enabled: true,
|
|
SandboxMode: true,
|
|
Currency: "CNY",
|
|
Priority: 0,
|
|
}
|
|
if err := db.Create(&gw).Error; err != nil {
|
|
return err
|
|
}
|
|
log.Println("[初始化] 已创建 Waffo 支付网关(默认沙箱,可在后台改 live)")
|
|
}
|
|
// 已存在则不覆盖 Enabled/SandboxMode,尊重管理员配置;其它网关一律关闭 // Logan.Li
|
|
if err := db.Model(&model.PaymentGatewayConfig{}).
|
|
Where("gateway <> ?", "waffo").
|
|
Updates(map[string]interface{}{"enabled": false, "priority": 100}).Error; err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|