package role import ( "context" "fmt" "time" "perms-system-server/internal/consts" authHelper "perms-system-server/internal/logic/auth" "perms-system-server/internal/model/roleperm" "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 BindRolePermsLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewBindRolePermsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindRolePermsLogic { return &BindRolePermsLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } // BindRolePerms 绑定角色权限。对指定角色做权限全量覆盖(diff 后批量新增/删除),变更后自动清理该角色下所有用户的权限缓存。 func (l *BindRolePermsLogic) BindRolePerms(req *types.BindPermsReq) error { role, err := l.svcCtx.SysRoleModel.FindOne(l.ctx, req.RoleId) if err != nil { return response.ErrNotFound("角色不存在") } if err := authHelper.RequireProductAdminFor(l.ctx, role.ProductCode); err != nil { return err } permIds := req.PermIds if len(permIds) > 0 { seen := make(map[int64]bool, len(permIds)) uniqueIds := make([]int64, 0, len(permIds)) for _, id := range permIds { if !seen[id] { seen[id] = true uniqueIds = append(uniqueIds, id) } } permIds = uniqueIds } if len(permIds) > 0 { perms, err := l.svcCtx.SysPermModel.FindByIds(l.ctx, permIds) if err != nil { return err } if len(perms) != len(permIds) { return response.ErrBadRequest("包含无效的权限ID") } for _, p := range perms { if p.ProductCode != role.ProductCode { return response.ErrBadRequest("不能绑定其他产品的权限") } if p.Status != consts.StatusEnabled { return response.ErrBadRequest(fmt.Sprintf("权限 %s 已被禁用,无法绑定", p.Code)) } } } existingPermIds, err := l.svcCtx.SysRolePermModel.FindPermIdsByRoleId(l.ctx, req.RoleId) if err != nil { return err } existingSet := make(map[int64]bool, len(existingPermIds)) for _, id := range existingPermIds { existingSet[id] = true } newSet := make(map[int64]bool, len(permIds)) for _, id := range permIds { newSet[id] = true } var toAdd []int64 for _, id := range permIds { if !existingSet[id] { toAdd = append(toAdd, id) } } var toRemove []int64 for _, id := range existingPermIds { if !newSet[id] { toRemove = append(toRemove, id) } } if len(toAdd) == 0 && len(toRemove) == 0 { return nil } if err := l.svcCtx.SysRolePermModel.TransactCtx(l.ctx, func(ctx context.Context, session sqlx.Session) error { if err := l.svcCtx.SysRolePermModel.DeleteByRoleIdAndPermIdsTx(ctx, session, req.RoleId, toRemove); err != nil { return err } if len(toAdd) > 0 { now := time.Now().Unix() data := make([]*roleperm.SysRolePerm, 0, len(toAdd)) for _, permId := range toAdd { data = append(data, &roleperm.SysRolePerm{ RoleId: req.RoleId, PermId: permId, CreateTime: now, UpdateTime: now, }) } return l.svcCtx.SysRolePermModel.BatchInsertWithTx(ctx, session, data) } return nil }); err != nil { return err } // 事务已提交成功,缓存清理属于尽力而为:FindUserIdsByRoleId 失败仅记录 Errorf, // 不映射为 500——否则客户端会把"数据已改但缓存未刷"的 degraded 成功状态误判为完全失败 // 而发起重试,重试时 diff 出的 toAdd/toRemove 均为空将静默 200,业务语义反而更怪 // (见审计 M-4)。旧权限缓存最多在 TTL (5 分钟) 后自然过期,不影响正确性。 if affectedUserIds, err := l.svcCtx.SysUserRoleModel.FindUserIdsByRoleId(l.ctx, req.RoleId); err == nil { l.svcCtx.UserDetailsLoader.BatchDel(l.ctx, affectedUserIds, role.ProductCode) } else { logx.WithContext(l.ctx).Errorf("BindRolePerms roleId=%d 角色权限已更新但 FindUserIdsByRoleId 失败,用户权限缓存将等待 TTL 自然过期: %v", req.RoleId, err) } return nil }