You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

154 lines
7.6 KiB

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 运营 / 5 渠道商(语义见 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"
// 渠道分成体系的归属绑定:
// 代理(3) = 品牌商账号:只填 Brandid,可见该品牌商及其名下全部渠道商的数据;
// 渠道商(5) :Brandid + Channelid 都填,只可见自己这一个渠道商的数据。
// 与 Apps/Products/Regions 一样随 JWT 下发,请求级由 scopeOf 强制收敛,前端传参不可越权。
Brandid uint32 `gorm:"column:brandid;index" json:"brandid"` // 绑定品牌商 id,0=不绑定
Channelid string `gorm:"column:channelid;size:16;index" json:"channelid"` // 绑定渠道商 id(短码),空=不绑定
// 代理资源点余额(仅代理 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
}
}
// 渠道分成体系的归属绑定列(品牌商账号/渠道商账号),幂等补列。
if res := postgres.Exec("ALTER TABLE " + comm.TableConsoleAccount + " ADD COLUMN IF NOT EXISTS brandid bigint DEFAULT 0"); res.Error != nil {
return res.Error
}
if res := postgres.Exec("ALTER TABLE " + comm.TableConsoleAccount + " ADD COLUMN IF NOT EXISTS channelid varchar(16) DEFAULT ''"); res.Error != nil {
return res.Error
}
return nil
}
// countAccountsByChannel 统计绑定到某渠道商的账号数(渠道商列表展示「是否已开通账号」用)。
func (this *modelComp) countAccountsByChannel(channelid string) (n int64, err error) {
err = postgres.Table(comm.TableConsoleAccount).Where("channelid=?", channelid).Count(&n).Error
return
}
// 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
}