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.
226 lines
7.3 KiB
226 lines
7.3 KiB
package console
|
|
|
|
import (
|
|
"time"
|
|
|
|
"yunyan/pb"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// 后台账号管理 handlers(仅超管可访问,路由已用 requireIdentity(Admin) 守卫)。
|
|
// 角色 Identity:1 超管 / 2 管理员 / 3 代理商 / 4 运营。账号存 console 主库 console_account 表,
|
|
// 密码 bcrypt 哈希。引导超管(yaml AdminAccount)不入表,故列表里看不到,也不能被改/删。
|
|
|
|
// validIdentity 限定可分配的角色取值(1~5)。
|
|
func validIdentity(idt pb.Identity) bool {
|
|
return idt >= pb.Identity_Admin && idt <= pb.Identity_Dealer
|
|
}
|
|
|
|
// identityHint 角色取值说明,供参数校验的错误信息复用。
|
|
const identityHint = "角色非法(1超管/2管理员/3品牌商(代理)/4运营/5渠道商)"
|
|
|
|
// validChannelBinding 校验品牌商/渠道商账号的归属绑定是否自洽:
|
|
// - 品牌商账号(3):必须绑定品牌商,且不绑定渠道商(它管的是名下全部渠道商);
|
|
// - 渠道商账号(5):必须同时绑定品牌商与渠道商,且该渠道商确实归属该品牌商;
|
|
// - 其余角色:两者都必须留空,避免误配出「超管绑了某品牌商」这种矛盾状态。
|
|
//
|
|
// 返回错误说明(空串=通过)。
|
|
func validChannelBinding(idt pb.Identity, brandid uint32, channelid string) string {
|
|
switch idt {
|
|
case pb.Identity_Agent:
|
|
if brandid == 0 {
|
|
return "品牌商账号必须绑定品牌商"
|
|
}
|
|
if channelid != "" {
|
|
return "品牌商账号不能绑定渠道商(它可见名下全部渠道商)"
|
|
}
|
|
case pb.Identity_Dealer:
|
|
if brandid == 0 || channelid == "" {
|
|
return "渠道商账号必须同时绑定品牌商与渠道商"
|
|
}
|
|
ch, err := dvChannel(consoleDeviceConn(), channelid)
|
|
if err != nil {
|
|
return "渠道商不存在: " + channelid
|
|
}
|
|
if ch.Brandid != brandid {
|
|
return "渠道商不属于所选品牌商: " + channelid
|
|
}
|
|
default:
|
|
if brandid != 0 || channelid != "" {
|
|
return "只有品牌商账号/渠道商账号才能绑定品牌商与渠道商"
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// accountsList 账号列表(Password 字段 json:"-",不外泄)。
|
|
func (this *serverComp) accountsList(c *gin.Context) {
|
|
list, err := this.module.model.listAccounts()
|
|
if err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
writeOK(c, list)
|
|
}
|
|
|
|
// accountsAdd 新建账号。校验用户名唯一(且不撞引导超管)、角色合法、密码非空。
|
|
func (this *serverComp) accountsAdd(c *gin.Context) {
|
|
var req struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
Identity pb.Identity `json:"identity"`
|
|
Remark string `json:"remark"`
|
|
Enabled bool `json:"enabled"`
|
|
Access string `json:"access"` // 可访问页面 CSV
|
|
Apps string `json:"apps"` // 绑定应用名称 CSV
|
|
Regions string `json:"regions"` // 绑定区域代码 CSV
|
|
Products string `json:"products"` // 绑定产品 id CSV
|
|
// 渠道分成体系的归属绑定:品牌商账号只填 brandid;渠道商账号两者都填。
|
|
Brandid uint32 `json:"brandid"`
|
|
Channelid string `json:"channelid"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, err.Error())
|
|
return
|
|
}
|
|
if req.Username == "" || req.Password == "" {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "用户名和密码必填")
|
|
return
|
|
}
|
|
if !validIdentity(req.Identity) {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, identityHint)
|
|
return
|
|
}
|
|
if msg := validChannelBinding(req.Identity, req.Brandid, req.Channelid); msg != "" {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, msg)
|
|
return
|
|
}
|
|
if req.Username == this.options.AdminAccount {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "用户名与引导超管冲突,请换一个")
|
|
return
|
|
}
|
|
if exist, _ := this.module.model.getAccountByName(req.Username); exist != nil && exist.Id != 0 {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "用户名已存在")
|
|
return
|
|
}
|
|
hash, err := hashPassword(req.Password)
|
|
if err != nil {
|
|
writeErr(c, pb.ErrorCode_SystemError, err.Error())
|
|
return
|
|
}
|
|
now := time.Now().Unix()
|
|
model := &Account{
|
|
Username: req.Username,
|
|
Password: hash,
|
|
Identity: req.Identity,
|
|
Enabled: req.Enabled,
|
|
Remark: req.Remark,
|
|
Access: req.Access,
|
|
Apps: req.Apps,
|
|
Regions: req.Regions,
|
|
Products: req.Products,
|
|
Brandid: req.Brandid,
|
|
Channelid: req.Channelid,
|
|
Createtime: now,
|
|
Updatetime: now,
|
|
}
|
|
if err := this.module.model.addAccount(model); err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
writeOK(c, model)
|
|
}
|
|
|
|
// accountsUpdate 改账号(角色/启用/备注;不在此改密码,改密走 resetpwd)。
|
|
func (this *serverComp) accountsUpdate(c *gin.Context) {
|
|
var req struct {
|
|
Id uint32 `json:"id"`
|
|
Identity pb.Identity `json:"identity"`
|
|
Remark string `json:"remark"`
|
|
Enabled bool `json:"enabled"`
|
|
Access string `json:"access"`
|
|
Apps string `json:"apps"`
|
|
Regions string `json:"regions"`
|
|
Products string `json:"products"`
|
|
Brandid uint32 `json:"brandid"`
|
|
Channelid string `json:"channelid"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil || req.Id == 0 {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "id 必填")
|
|
return
|
|
}
|
|
if !validIdentity(req.Identity) {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, identityHint)
|
|
return
|
|
}
|
|
if msg := validChannelBinding(req.Identity, req.Brandid, req.Channelid); msg != "" {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, msg)
|
|
return
|
|
}
|
|
model, err := this.module.model.getAccount(req.Id)
|
|
if err != nil || model.Id == 0 {
|
|
writeErr(c, pb.ErrorCode_DBError, "账号不存在")
|
|
return
|
|
}
|
|
model.Identity = req.Identity
|
|
model.Remark = req.Remark
|
|
model.Enabled = req.Enabled
|
|
model.Access = req.Access
|
|
model.Apps = req.Apps
|
|
model.Regions = req.Regions
|
|
model.Products = req.Products
|
|
model.Brandid = req.Brandid
|
|
model.Channelid = req.Channelid
|
|
model.Updatetime = time.Now().Unix()
|
|
if err := this.module.model.saveAccount(model); err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
writeOK(c, model)
|
|
}
|
|
|
|
// accountsResetPwd 重置某账号密码(bcrypt 重新哈希)。
|
|
func (this *serverComp) accountsResetPwd(c *gin.Context) {
|
|
var req struct {
|
|
Id uint32 `json:"id"`
|
|
Password string `json:"password"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil || req.Id == 0 || req.Password == "" {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "id 和新密码必填")
|
|
return
|
|
}
|
|
model, err := this.module.model.getAccount(req.Id)
|
|
if err != nil || model.Id == 0 {
|
|
writeErr(c, pb.ErrorCode_DBError, "账号不存在")
|
|
return
|
|
}
|
|
hash, err := hashPassword(req.Password)
|
|
if err != nil {
|
|
writeErr(c, pb.ErrorCode_SystemError, err.Error())
|
|
return
|
|
}
|
|
model.Password = hash
|
|
model.Updatetime = time.Now().Unix()
|
|
if err := this.module.model.saveAccount(model); err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
writeOK(c, gin.H{"id": req.Id})
|
|
}
|
|
|
|
// accountsDel 删除账号。
|
|
func (this *serverComp) accountsDel(c *gin.Context) {
|
|
var req struct {
|
|
Id uint32 `json:"id"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil || req.Id == 0 {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "id 必填")
|
|
return
|
|
}
|
|
if err := this.module.model.delAccount(req.Id); err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
writeOK(c, gin.H{"id": req.Id})
|
|
}
|
|
|