Files
smartmate/backend/services/tokenstore/dao/connect.go

180 lines
4.8 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 dao
import (
"fmt"
tokenmodel "github.com/LoveLosita/smartflow/backend/services/tokenstore/model"
"github.com/spf13/viper"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// OpenDBFromConfig 创建 token-store 服务自己的数据库句柄,并迁移本服务私有表。
//
// 职责边界:
// 1. 只迁移 token_* 表,不迁移 users避免和 user/auth 服务边界冲突;
// 2. 自动迁移后执行 P0 seed确保前端商品页有可展示商品
// 3. 返回 *gorm.DB 供本服务 DAO 复用,调用方负责进程生命周期。
func OpenDBFromConfig() (*gorm.DB, error) {
host := viper.GetString("database.host")
port := viper.GetString("database.port")
user := viper.GetString("database.user")
password := viper.GetString("database.password")
dbname := viper.GetString("database.dbname")
dsn := fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
user, password, host, port, dbname,
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
return nil, err
}
if err = AutoMigrate(db); err != nil {
return nil, err
}
if err = SeedDefaults(db); err != nil {
return nil, err
}
return db, nil
}
// AutoMigrate 只迁移 token-store 服务拥有的表。
//
// 步骤说明:
// 1. 先创建商品、订单、获取账本和奖励规则表;
// 2. 通过唯一约束保证 order_no、event_id 和幂等键不会重复写入;
// 3. 失败时直接返回错误,避免服务在 schema 不完整时继续启动。
func AutoMigrate(db *gorm.DB) error {
if db == nil {
return fmt.Errorf("tokenstore auto migrate failed: db is nil")
}
if err := db.AutoMigrate(
&tokenmodel.TokenProduct{},
&tokenmodel.TokenOrder{},
&tokenmodel.TokenGrant{},
&tokenmodel.TokenRewardRule{},
); err != nil {
return fmt.Errorf("auto migrate tokenstore tables failed: %w", err)
}
return nil
}
// SeedDefaults 写入 P0 默认商品和奖励规则。
//
// 步骤说明:
// 1. 商品和奖励规则都用稳定业务键做 upsert允许重复启动服务
// 2. seed 只提供 P0 默认数据,不代表有管理后台能力;
// 3. 后续若商品或规则由运营后台维护,可替换本函数或仅保留初始化兜底。
func SeedDefaults(db *gorm.DB) error {
if db == nil {
return fmt.Errorf("tokenstore seed failed: db is nil")
}
if err := seedDefaultProducts(db); err != nil {
return err
}
if err := seedDefaultRewardRules(db); err != nil {
return err
}
return nil
}
func seedDefaultProducts(db *gorm.DB) error {
products := defaultTokenProducts()
for _, product := range products {
if err := db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "sku"}},
DoUpdates: clause.AssignmentColumns([]string{
"name",
"description",
"token_amount",
"price_cent",
"currency",
"badge",
"status",
"sort_order",
"updated_at",
}),
}).Create(&product).Error; err != nil {
return fmt.Errorf("seed token product %s failed: %w", product.SKU, err)
}
}
return nil
}
func defaultTokenProducts() []tokenmodel.TokenProduct {
return []tokenmodel.TokenProduct{
{
SKU: "token_basic_100",
Name: "基础 Token 包",
Description: "适合轻量使用 Agent。",
TokenAmount: 100,
PriceCent: 990,
Currency: "CNY",
Badge: "入门",
Status: tokenmodel.TokenProductStatusActive,
SortOrder: 10,
},
{
SKU: "token_plus_300",
Name: "进阶 Token 包",
Description: "适合高频规划和复盘。",
TokenAmount: 300,
PriceCent: 1990,
Currency: "CNY",
Badge: "推荐",
Status: tokenmodel.TokenProductStatusActive,
SortOrder: 20,
},
{
SKU: "token_pro_800",
Name: "专业 Token 包",
Description: "适合长周期学习计划和高频 Agent 使用。",
TokenAmount: 800,
PriceCent: 3990,
Currency: "CNY",
Badge: "高频",
Status: tokenmodel.TokenProductStatusActive,
SortOrder: 30,
},
}
}
func seedDefaultRewardRules(db *gorm.DB) error {
rules := defaultTokenRewardRules()
for _, rule := range rules {
if err := db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "source"}},
DoUpdates: clause.AssignmentColumns([]string{
"name",
"amount",
"status",
"config_json",
"updated_at",
}),
}).Create(&rule).Error; err != nil {
return fmt.Errorf("seed token reward rule %s failed: %w", rule.Source, err)
}
}
return nil
}
func defaultTokenRewardRules() []tokenmodel.TokenRewardRule {
return []tokenmodel.TokenRewardRule{
{
Source: tokenmodel.TokenGrantSourceForumLike,
Name: "计划被点赞奖励",
Amount: 1,
Status: tokenmodel.TokenRewardRuleStatusActive,
},
{
Source: tokenmodel.TokenGrantSourceForumImport,
Name: "计划被导入奖励",
Amount: 2,
Status: tokenmodel.TokenRewardRuleStatusActive,
},
}
}