| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- package dept
- import (
- "context"
- "errors"
- "fmt"
- "time"
- "perms-system-server/internal/consts"
- authHelper "perms-system-server/internal/logic/auth"
- deptModel "perms-system-server/internal/model/dept"
- "perms-system-server/internal/response"
- "perms-system-server/internal/svc"
- "perms-system-server/internal/types"
- "github.com/zeromicro/go-zero/core/logx"
- "github.com/zeromicro/go-zero/core/stores/sqlx"
- )
- type CreateDeptLogic struct {
- logx.Logger
- ctx context.Context
- svcCtx *svc.ServiceContext
- }
- func NewCreateDeptLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateDeptLogic {
- return &CreateDeptLogic{
- Logger: logx.WithContext(ctx),
- ctx: ctx,
- svcCtx: svcCtx,
- }
- }
- // CreateDept 创建部门。在指定父部门下新建子部门,自动继承路径层级。仅超管可调用。
- func (l *CreateDeptLogic) CreateDept(req *types.CreateDeptReq) (resp *types.IdResp, err error) {
- if err := authHelper.RequireSuperAdmin(l.ctx); err != nil {
- return nil, err
- }
- if len(req.Name) > 64 {
- return nil, response.ErrBadRequest("部门名称长度不能超过64个字符")
- }
- if len(req.Remark) > 255 {
- return nil, response.ErrBadRequest("备注长度不能超过255个字符")
- }
- // 审计 L-R18-5:Sort 只在同级部门间相对有效,用不到 int64 的极端值;把合法区间固定为
- // [-100000, 100000],与 permsLevel 1-999 的"业务侧人类可读范围"思路一致,避免前端偶发
- // 把 math.MaxInt64 之类的值透传到 DB 触发"排序溢出"的 edge case。
- if req.Sort < -100000 || req.Sort > 100000 {
- return nil, response.ErrBadRequest("排序值必须在 -100000 到 100000 之间")
- }
- parentPath := "/"
- if req.ParentId > 0 {
- parent, err := l.svcCtx.SysDeptModel.FindOne(l.ctx, req.ParentId)
- if err != nil {
- return nil, response.ErrNotFound("父部门不存在")
- }
- parentPath = parent.Path
- }
- now := time.Now().Unix()
- var deptId int64
- deptType := req.DeptType
- if deptType == "" {
- deptType = consts.DeptTypeNormal
- } else if deptType != consts.DeptTypeNormal && deptType != consts.DeptTypeDev {
- return nil, response.ErrBadRequest("无效的部门类型")
- }
- err = l.svcCtx.SysDeptModel.TransactCtx(l.ctx, func(ctx context.Context, session sqlx.Session) error {
- if req.ParentId > 0 {
- // 审计 L-R12-2:事务内用 FindOneForShareTx 重取父部门快照——同时拿到 status 与
- // path。此前只 SELECT `id` 无法感知"父部门在事务外 FindOne 之后被 UpdateDept 禁用"
- // 这条交错:读到 id 仍存在就放行,结果子部门会以 Enabled 挂在已 Disabled 的父部门下,
- // 让运营侧的"禁用整个子树"意图被静默绕过。改用事务内带 S 锁的完整行读取,同时把
- // parentPath 覆盖为事务内视图——与事务外 FindOne 理论上一致(UpdateDept 不改 path),
- // 但如果未来 UpdateDept 扩展支持 path 重写,这里已经内建 snapshot。
- parent, err := l.svcCtx.SysDeptModel.FindOneForShareTx(ctx, session, req.ParentId)
- if err != nil {
- if errors.Is(err, sqlx.ErrNotFound) {
- return response.ErrNotFound("父部门已被删除")
- }
- return err
- }
- if parent.Status != consts.StatusEnabled {
- return response.ErrBadRequest("父部门已被禁用,无法创建子部门")
- }
- parentPath = parent.Path
- }
- result, err := l.svcCtx.SysDeptModel.InsertWithTx(ctx, session, &deptModel.SysDept{
- ParentId: req.ParentId,
- Name: req.Name,
- Path: parentPath,
- Sort: req.Sort,
- DeptType: deptType,
- Remark: req.Remark,
- Status: consts.StatusEnabled,
- CreateTime: now,
- UpdateTime: now,
- })
- if err != nil {
- return err
- }
- deptId, _ = result.LastInsertId()
- // 审计 L-R17-5:以前用 FindOneWithTx 把刚 INSERT 的整行读回来,仅为了把 path 覆写成
- // `parentPath + deptId + "/"` 后再 UpdateWithTx 走整行回写——3 次 DB 往返。现在改调
- // UpdatePathWithTx 只改 path/updateTime 两列,减一次 SELECT,sys_dept X 锁持有时长
- // 同步缩短,降低并发 DeleteDept / UpdateDept 的尾部延迟。
- finalPath := fmt.Sprintf("%s%d/", parentPath, deptId)
- return l.svcCtx.SysDeptModel.UpdatePathWithTx(ctx, session, deptId, finalPath, now)
- })
- if err != nil {
- return nil, err
- }
- return &types.IdResp{Id: deptId}, nil
- }
|