package console import ( "yunyan/comm" "yunyan/lego/sys/postgres" "yunyan/pb" "golang.org/x/crypto/bcrypt" ) // Account 后台账号,存 console 主库(postgres,console_account 表)。 // 角色 Identity:1 超管 / 2 管理员 / 3 代理商 / 4 运营(语义见 pb.Identity)。 // 超管引导账号(yaml AdminAccount/AdminPassword)不入表,account_id=0;表里存的是后台新建的账号。 type Account struct { Id uint32 `gorm:"primaryKey;autoIncrement;column:id" json:"id"` Username string `gorm:"column:username;size:64;uniqueIndex" json:"username"` Password string `gorm:"column:password;size:128" json:"-"` // bcrypt 哈希,绝不回传前端 Identity pb.Identity `gorm:"column:identity" json:"identity"` // 角色 Enabled bool `gorm:"column:enabled" json:"enabled"` // 停用的不能登录 Remark string `gorm:"column:remark;size:255" json:"remark"` // 作用域绑定(仅代理/运营有意义;超管/管理员忽略,拥有全部权限)。均为 CSV,登录时回传前端做限制。 Access string `gorm:"column:access;type:text" json:"access"` // 可访问页面 id 列表,如 "dashboard,product" Apps string `gorm:"column:apps;type:text" json:"apps"` // 绑定应用名称列表(与 stats_global_day.app_id 一致),如 "Voitrans,Echomeet" Regions string `gorm:"column:regions;type:text" json:"regions"` // 绑定区域代码列表,如 "cn,us" Products string `gorm:"column:products;type:text" json:"products"` // 绑定产品 id 列表,如 "45058,45059" // 代理资源点余额(仅代理 identity=3 有意义):管理员充值入账,代理给终端用户赠送时按类原子扣减。 // 单位与赠送表单/公码奖励一致:VIP=天、翻译/会议=分钟。 BalanceVipday int64 `gorm:"column:balance_vipday" json:"balance_vipday"` // VIP天数余额 BalanceTrademin int64 `gorm:"column:balance_trademin" json:"balance_trademin"` // 翻译分钟余额 BalanceMeetmin int64 `gorm:"column:balance_meetmin" json:"balance_meetmin"` // 会议分钟余额 Createtime int64 `gorm:"column:createtime" json:"createtime"` Updatetime int64 `gorm:"column:updatetime" json:"updatetime"` } func (Account) TableName() string { return comm.TableConsoleAccount } // hashPassword 生成 bcrypt 哈希。 func hashPassword(plain string) (string, error) { b, err := bcrypt.GenerateFromPassword([]byte(plain), bcrypt.DefaultCost) return string(b), err } // checkPassword 校验明文与 bcrypt 哈希是否匹配。 func checkPassword(hash, plain string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(plain)) == nil } // ---- 账号表 CRUD(走 console 主库 postgres)---- func (this *modelComp) ensureAccountTable() error { if err := postgres.CreateTable(comm.TableConsoleAccount, &Account{}); err != nil { return err } // postgres CreateTable 对已存在的表跳过 AutoMigrate,故新增的绑定列需显式补(幂等)。 for _, col := range []string{"access", "apps", "regions", "products"} { sql := "ALTER TABLE " + comm.TableConsoleAccount + " ADD COLUMN IF NOT EXISTS " + col + " text DEFAULT ''" if res := postgres.Exec(sql); res.Error != nil { return res.Error } } // 代理资源点余额列(bigint),同样幂等补列。 for _, col := range []string{"balance_vipday", "balance_trademin", "balance_meetmin"} { sql := "ALTER TABLE " + comm.TableConsoleAccount + " ADD COLUMN IF NOT EXISTS " + col + " bigint DEFAULT 0" if res := postgres.Exec(sql); res.Error != nil { return res.Error } } return nil } // agentDeductBalance 代理赠送时按类原子扣减资源点余额(CAS):任一类不足则一律不扣、返回 ok=false。 func (this *modelComp) agentDeductBalance(id uint32, vipday, trademin, meetmin, ts int64) (bool, error) { res := postgres.Exec( "UPDATE "+comm.TableConsoleAccount+" SET balance_vipday=balance_vipday-?, balance_trademin=balance_trademin-?, balance_meetmin=balance_meetmin-?, updatetime=? "+ "WHERE id=? AND balance_vipday>=? AND balance_trademin>=? AND balance_meetmin>=?", vipday, trademin, meetmin, ts, id, vipday, trademin, meetmin) if res.Error != nil { return false, res.Error } return res.RowsAffected == 1, nil } // agentAddBalance 给代理账号余额按类加 delta(充值正数 / 回滚或下调负数);结果任一类为负则一律不改、返回 ok=false。 // WHERE 限定 identity=代理,避免误给非代理账号建立余额语义。 func (this *modelComp) agentAddBalance(id uint32, vipday, trademin, meetmin, ts int64) (bool, error) { res := postgres.Exec( "UPDATE "+comm.TableConsoleAccount+" SET balance_vipday=balance_vipday+?, balance_trademin=balance_trademin+?, balance_meetmin=balance_meetmin+?, updatetime=? "+ "WHERE id=? AND identity=? AND balance_vipday+?>=0 AND balance_trademin+?>=0 AND balance_meetmin+?>=0", vipday, trademin, meetmin, ts, id, int32(pb.Identity_Agent), vipday, trademin, meetmin) if res.Error != nil { return false, res.Error } return res.RowsAffected == 1, nil } // listAccounts 返回全部账号(按 id 升序)。Password 字段 json:"-" 不会外泄。 func (this *modelComp) listAccounts() (models []*Account, err error) { models = make([]*Account, 0) err = postgres.Find(comm.TableConsoleAccount, &models, "") return } // getAccountByName 按用户名取账号(登录用)。 func (this *modelComp) getAccountByName(username string) (model *Account, err error) { model = &Account{} err = postgres.FindOne(comm.TableConsoleAccount, model, "username=?", username) return } // getAccount 按 id 取账号。 func (this *modelComp) getAccount(id uint32) (model *Account, err error) { model = &Account{} err = postgres.FindOne(comm.TableConsoleAccount, model, "id=?", id) return } // addAccount 新增账号(Insert 后 model.Id 被回填)。 func (this *modelComp) addAccount(model *Account) (err error) { err = postgres.Insert(comm.TableConsoleAccount, model) return } // saveAccount 更新账号(整行保存)。 func (this *modelComp) saveAccount(model *Account) (err error) { err = postgres.Save(comm.TableConsoleAccount, model) return } // delAccount 删除账号。 func (this *modelComp) delAccount(id uint32) (err error) { err = postgres.Delete(comm.TableConsoleAccount, "id=?", id) return }