与 pancake 商品链接 currency=USD 对齐。 Co-authored-by: Cursor <cursoragent@cursor.com>
309 lines
8.1 KiB
Go
309 lines
8.1 KiB
Go
package database
|
||
|
||
import (
|
||
"backend/internal/model"
|
||
"backend/utils"
|
||
"log"
|
||
"time"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
const (
|
||
defaultAdminUsername = "admin"
|
||
defaultAdminPassword = "admin"
|
||
)
|
||
|
||
// 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
|
||
}
|
||
|
||
admin := model.User{
|
||
Username: defaultAdminUsername,
|
||
Password: utils.HashPassword(defaultAdminPassword),
|
||
Email: "admin@opsx-licence.local",
|
||
Role: "admin",
|
||
IsActive: true,
|
||
}
|
||
if err := db.Create(&admin).Error; err != nil {
|
||
return err
|
||
}
|
||
log.Printf("[初始化] 已创建默认管理员账号: %s / %s(请尽快修改密码)", defaultAdminUsername, defaultAdminPassword)
|
||
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: "Yearly",
|
||
PlanType: "yearly",
|
||
Price: 99,
|
||
Currency: "CNY",
|
||
Duration: 365,
|
||
MaxDevices: 2,
|
||
Features: `["专业功能","包年","2 设备"]`,
|
||
SortOrder: 1,
|
||
Status: 1,
|
||
},
|
||
{
|
||
ProductID: product.ID,
|
||
Name: "Perpetual",
|
||
PlanType: "perpetual",
|
||
Price: 199,
|
||
Currency: "CNY",
|
||
Duration: 0,
|
||
MaxDevices: 5,
|
||
Features: `["专业功能","永久授权","5 设备"]`,
|
||
SortOrder: 2,
|
||
Status: 1,
|
||
},
|
||
{
|
||
ProductID: product.ID,
|
||
Name: "Trial 180d",
|
||
PlanType: "trial",
|
||
Price: 0,
|
||
Currency: "USD",
|
||
Duration: 180,
|
||
MaxDevices: 1,
|
||
Features: `["专业功能","半年体验","1 设备"]`,
|
||
SortOrder: 0,
|
||
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{
|
||
{"yearly", "Yearly", 99, "CNY", 365, 2, "[\"专业功能\",\"包年\",\"2 设备\"]", 1},
|
||
{"perpetual", "Perpetual", 199, "CNY", 0, 5, "[\"专业功能\",\"永久授权\",\"5 设备\"]", 2},
|
||
{"trial", "Trial 180d", 0, "CNY", 180, 1, "[\"专业功能\",\"半年体验\",\"1 设备\"]", 0},
|
||
}
|
||
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 {
|
||
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
|
||
}
|
||
continue
|
||
}
|
||
plan.Name = u.name
|
||
plan.Price = u.price
|
||
plan.Currency = u.currency
|
||
plan.Duration = u.duration
|
||
plan.MaxDevices = u.maxDevices
|
||
plan.Features = u.features
|
||
plan.SortOrder = u.sortOrder
|
||
plan.Status = 1
|
||
if err := db.Save(&plan).Error; err != nil {
|
||
return err
|
||
}
|
||
}
|
||
log.Printf("[初始化] OpsXShell 套餐已同步: 包年¥99/2台, 永久¥199/5台, 体验180天/1台")
|
||
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: "USD",
|
||
Priority: 0,
|
||
}
|
||
if err := db.Create(&gw).Error; err != nil {
|
||
return err
|
||
}
|
||
} else {
|
||
existing.Enabled = true
|
||
existing.SandboxMode = true
|
||
existing.Currency = "USD"
|
||
existing.Priority = 0
|
||
if err := db.Save(&existing).Error; err != nil {
|
||
return err
|
||
}
|
||
}
|
||
// Lower priority for other enabled gateways (ASC pick prefers Waffo).
|
||
if err := db.Model(&model.PaymentGatewayConfig{}).
|
||
Where("gateway <> ? AND enabled = ?", "waffo", true).
|
||
Update("priority", 100).Error; err != nil {
|
||
return err
|
||
}
|
||
log.Println("[初始化] 支付网关已优先 Waffo(包年 subscription / 永久 onetime)")
|
||
return nil
|
||
}
|