This commit is contained in:
小蚂蚁云
2026-10-26 08:46:20 +08:00
parent 276740924a
commit 69f5c1767d
26 changed files with 259 additions and 121 deletions
+40 -68
View File
@@ -55,7 +55,6 @@ import (
"easygoadmin/internal/dao"
"easygoadmin/internal/service"
"easygoadmin/utility/cache"
"easygoadmin/utility/crypto"
)
// ============================== 租户业务实现 ==============================
@@ -307,80 +306,53 @@ func (s *sTenant) Add(ctx context.Context, req *v1.TenantAddReq) (res *v1.IdRes,
return &v1.IdRes{Id: idv}, nil
}
// Account 创建租户账号
// Account 添加租户账号
//
// 同时创建租户及其初始管理员账号;
// bcrypt 属 CPU 密集操作,放在事务外预计算,保持短事务
// 在指定租户(req.TenantId)下创建用户账号,返回目标租户ID、租户编码与新用户ID。
// 平台级专属:拦截租户侧身份——本操作按请求显式指定的目标租户写入,
// 且唯一性校验/用户数上限的查询链依赖平台视角(无租户收敛)。
// 目标租户不存在返回 CodeTenantNotFound;账号唯一性、用户数上限、编码生成、
// 密码加盐、头像转正、角色关联复用用户新增链路(sUser.addWithTenant)。
func (s *sTenant) Account(ctx context.Context, req *v1.TenantAccountReq) (res *v1.TenantAccountRes, err error) {
// 租户:自增回填(临时编码占位 → T%04d 修正)
tempCode := fmt.Sprintf("T%010d", time.Now().UnixNano()%10000000000)
tData := map[string]any{
"name": req.Name,
"code": tempCode,
"contact": req.Contact,
"mobile": req.Mobile,
"email": req.Email,
"address": req.Address,
"status": 1,
"max_user_num": req.MaxUserNum,
"note": req.Note,
if !consts.IsPlatform(ctx) {
return nil, gerror.NewCode(consts.CodeForbidden)
}
if req.ExpireTime != "" {
if t := gtime.New(req.ExpireTime); t != nil {
tData["expire_time"] = t
}
// 目标租户存在性校验(同时取编码供响应回传)
one, e := dao.Tenant.Ctx(ctx).Where("id", req.TenantId).Fields("code").One()
if e != nil {
return nil, e
}
// bcrypt 属 CPU 密集操作,放在事务外预计算(短事务纪律)
salt := crypto.GenerateSalt()
hashed, err := crypto.HashPassword(req.Password, salt)
if len(one) == 0 {
return nil, gerror.NewCode(consts.CodeTenantNotFound)
}
// 用户表单字段转用户新增入参,按目标租户落库
idRes, err := (&sUser{}).addWithTenant(ctx, req.TenantId, &v1.UserAddReq{
Username: req.Username,
Password: req.Password,
Realname: req.Realname,
Gender: req.Gender,
Mobile: req.Mobile,
Email: req.Email,
Roles: req.Roles,
DeptId: req.DeptId,
LevelId: req.LevelId,
PositionId: req.PositionId,
City: req.City,
Address: req.Address,
Avatar: req.Avatar,
Intro: req.Intro,
Status: req.Status,
Sort: req.Sort,
Note: req.Note,
})
if err != nil {
return nil, err
}
realname := req.Realname
if realname == "" {
realname = req.Username
}
// 租户 + 初始管理员三段写入保持原子性(全部经 txCtx)
var tenantId, uid int64
var code string
if err = WithTrans(ctx, func(txCtx context.Context) error {
tResult, e := dao.Tenant.Ctx(txCtx).Data(tData).Insert()
if e != nil {
return e
}
tenantId, _ = tResult.LastInsertId()
code = fmt.Sprintf("T%04d", tenantId)
if _, e = dao.Tenant.Ctx(txCtx).Where("id", tenantId).Data(map[string]any{"code": code}).Update(); e != nil {
return e
}
// 初始管理员(tenant_id 由 hooks 自动填充)
tenantCtx := consts.WithTenantID(txCtx, int(tenantId))
uResult, e := dao.User.Ctx(tenantCtx).Data(map[string]any{
"username": req.Username,
"code": fmt.Sprintf("%s-000001", code), // 首个用户序号 000001
"password": hashed,
"salt": salt,
"realname": realname,
"mobile": req.Mobile,
"email": req.Email,
"province_code": "",
"city_code": "",
"district_code": "",
"street_code": "",
"address": "",
"status": 1,
"tenant_id": int(tenantId),
}).Insert()
if e != nil {
return e
}
uid, _ = uResult.LastInsertId()
return nil
}); err != nil {
return nil, err
}
return &v1.TenantAccountRes{TenantId: tenantId, Code: code, UserId: uid}, nil
return &v1.TenantAccountRes{
TenantId: int64(req.TenantId),
Code: one["code"].String(),
UserId: idRes.Id,
}, nil
}
// Users 查询租户用户列表