Files
opsx-licence/backend/database/seed.go
T
Logan.LiandCursor 95405de119 feat: 对齐 Waffo 包年/永久结账与客户端席位 API
同步 OpsXShell 档位定价与 Waffo 商品映射,补齐 checkout/webhook/设备列表契约,便于桌面端购买与满席释放。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 11:42:56 +08:00

309 lines
8.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: "CNY",
Priority: 0,
}
if err := db.Create(&gw).Error; err != nil {
return err
}
} else {
existing.Enabled = true
existing.SandboxMode = true
existing.Currency = "CNY"
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
}