userDetailsLoader.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. package loaders
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "math"
  7. "perms-system-server/internal/consts"
  8. "perms-system-server/internal/model"
  9. "perms-system-server/internal/model/productmember"
  10. "github.com/zeromicro/go-zero/core/logx"
  11. "github.com/zeromicro/go-zero/core/stores/redis"
  12. )
  13. const defaultCacheTTL = 300 // 5 分钟
  14. // -------- UserDetails 及子结构 --------
  15. // UserDetails 用户完整信息,包含用户、部门、产品、成员、角色、权限等所有有效字段。
  16. // 由 UserDetailsLoader 一次性加载,可用于中间件 context 注入、login/userInfo 响应、Claims 构造等。
  17. type UserDetails struct {
  18. // 用户基本信息 (sys_user)
  19. UserId int64 `json:"userId"`
  20. Username string `json:"username"`
  21. Nickname string `json:"nickname"`
  22. Avatar string `json:"avatar"`
  23. Email string `json:"email"`
  24. Phone string `json:"phone"`
  25. Remark string `json:"remark"`
  26. IsSuperAdmin bool `json:"isSuperAdmin"`
  27. IsSuperAdminRaw int64 `json:"isSuperAdminRaw"`
  28. MustChangePassword bool `json:"mustChangePassword"`
  29. MustChangePwdRaw int64 `json:"mustChangePwdRaw"`
  30. Status int64 `json:"status"`
  31. // 部门信息 (sys_dept)
  32. DeptId int64 `json:"deptId"`
  33. DeptName string `json:"deptName"`
  34. DeptPath string `json:"deptPath"`
  35. DeptType string `json:"deptType"`
  36. // 产品上下文 (sys_product)
  37. ProductCode string `json:"productCode"`
  38. ProductName string `json:"productName"`
  39. // 成员信息 (sys_product_member)
  40. MemberType string `json:"memberType"`
  41. // 角色列表 (sys_role,当前产品下已启用的角色)
  42. Roles []RoleInfo `json:"roles"`
  43. // 权限列表 (计算后的权限 code 集合)
  44. Perms []string `json:"perms"`
  45. // 当前产品下最小 permsLevel(无角色时为 math.MaxInt64)
  46. MinPermsLevel int64 `json:"minPermsLevel"`
  47. }
  48. // RoleInfo 角色摘要信息。
  49. type RoleInfo struct {
  50. Id int64 `json:"id"`
  51. Name string `json:"name"`
  52. Remark string `json:"remark"`
  53. PermsLevel int64 `json:"permsLevel"`
  54. }
  55. // -------- UserDetailsLoader --------
  56. // UserDetailsLoader 负责加载、缓存、失效用户详细信息。
  57. // 优先从 Redis 读取完整 UserDetails,miss 时查 DB 并回填。
  58. type UserDetailsLoader struct {
  59. rds *redis.Redis
  60. keyPrefix string
  61. ttl int
  62. models *model.Models
  63. }
  64. func NewUserDetailsLoader(rds *redis.Redis, keyPrefix string, models *model.Models) *UserDetailsLoader {
  65. return &UserDetailsLoader{
  66. rds: rds,
  67. keyPrefix: keyPrefix,
  68. ttl: defaultCacheTTL,
  69. models: models,
  70. }
  71. }
  72. func (l *UserDetailsLoader) cacheKey(userId int64, productCode string) string {
  73. return fmt.Sprintf("%s:ud:%d:%s", l.keyPrefix, userId, productCode)
  74. }
  75. // Load 根据 userId 和 productCode 加载完整的 UserDetails。
  76. func (l *UserDetailsLoader) Load(ctx context.Context, userId int64, productCode string) *UserDetails {
  77. key := l.cacheKey(userId, productCode)
  78. if val, err := l.rds.GetCtx(ctx, key); err == nil && val != "" {
  79. var ud UserDetails
  80. if err := json.Unmarshal([]byte(val), &ud); err == nil {
  81. return &ud
  82. }
  83. }
  84. ud := l.loadFromDB(ctx, userId, productCode)
  85. if val, err := json.Marshal(ud); err == nil {
  86. if err := l.rds.SetexCtx(ctx, key, string(val), l.ttl); err != nil {
  87. logx.WithContext(ctx).Errorf("set user details cache failed: %v", err)
  88. }
  89. }
  90. return ud
  91. }
  92. // Del 删除指定用户在指定产品下的缓存。
  93. func (l *UserDetailsLoader) Del(ctx context.Context, userId int64, productCode string) {
  94. key := l.cacheKey(userId, productCode)
  95. if _, err := l.rds.DelCtx(ctx, key); err != nil {
  96. logx.WithContext(ctx).Errorf("del user details cache [%s] failed: %v", key, err)
  97. }
  98. }
  99. // Clean 清除指定用户所有产品下的缓存。
  100. func (l *UserDetailsLoader) Clean(ctx context.Context, userId int64) {
  101. pattern := fmt.Sprintf("%s:ud:%d:*", l.keyPrefix, userId)
  102. l.cleanByPattern(ctx, pattern)
  103. }
  104. // CleanByProduct 清除指定产品下所有用户的缓存。
  105. func (l *UserDetailsLoader) CleanByProduct(ctx context.Context, productCode string) {
  106. pattern := fmt.Sprintf("%s:ud:*:%s", l.keyPrefix, productCode)
  107. l.cleanByPattern(ctx, pattern)
  108. }
  109. // BatchDel 批量删除多个用户在指定产品下的缓存。
  110. func (l *UserDetailsLoader) BatchDel(ctx context.Context, userIds []int64, productCode string) {
  111. if len(userIds) == 0 {
  112. return
  113. }
  114. keys := make([]string, 0, len(userIds))
  115. for _, uid := range userIds {
  116. keys = append(keys, l.cacheKey(uid, productCode))
  117. }
  118. if _, err := l.rds.DelCtx(ctx, keys...); err != nil {
  119. logx.WithContext(ctx).Errorf("batch del user details cache failed: %v", err)
  120. }
  121. }
  122. func (l *UserDetailsLoader) cleanByPattern(ctx context.Context, pattern string) {
  123. var cursor uint64
  124. for {
  125. script := `local cursor = tonumber(ARGV[2])
  126. local result = redis.call('SCAN', cursor, 'MATCH', ARGV[1], 'COUNT', 100)
  127. local nextCursor = tonumber(result[1])
  128. local keys = result[2]
  129. for i = 1, #keys do redis.call('DEL', keys[i]) end
  130. return nextCursor`
  131. val, err := l.rds.EvalCtx(ctx, script, []string{}, pattern, cursor)
  132. if err != nil {
  133. logx.WithContext(ctx).Errorf("clean user details cache [%s] failed: %v", pattern, err)
  134. return
  135. }
  136. next, ok := val.(int64)
  137. if !ok || next == 0 {
  138. return
  139. }
  140. cursor = uint64(next)
  141. }
  142. }
  143. // -------- 内部加载逻辑 --------
  144. func (l *UserDetailsLoader) loadFromDB(ctx context.Context, userId int64, productCode string) *UserDetails {
  145. ud := &UserDetails{
  146. UserId: userId,
  147. ProductCode: productCode,
  148. MinPermsLevel: math.MaxInt64,
  149. }
  150. l.loadUser(ctx, ud)
  151. l.loadDept(ctx, ud)
  152. l.loadProduct(ctx, ud)
  153. l.loadMembership(ctx, ud)
  154. l.loadRoles(ctx, ud)
  155. l.loadPerms(ctx, ud)
  156. return ud
  157. }
  158. func (l *UserDetailsLoader) loadUser(ctx context.Context, ud *UserDetails) {
  159. u, err := l.models.SysUserModel.FindOne(ctx, ud.UserId)
  160. if err != nil {
  161. logx.WithContext(ctx).Errorf("userDetailsLoader: query user %d failed: %v", ud.UserId, err)
  162. return
  163. }
  164. ud.Username = u.Username
  165. ud.Nickname = u.Nickname
  166. ud.Avatar = u.Avatar.String
  167. ud.Email = u.Email
  168. ud.Phone = u.Phone
  169. ud.Remark = u.Remark
  170. ud.DeptId = u.DeptId
  171. ud.IsSuperAdminRaw = u.IsSuperAdmin
  172. ud.IsSuperAdmin = u.IsSuperAdmin == consts.IsSuperAdminYes
  173. ud.MustChangePwdRaw = u.MustChangePassword
  174. ud.MustChangePassword = u.MustChangePassword == consts.MustChangePasswordYes
  175. ud.Status = u.Status
  176. }
  177. func (l *UserDetailsLoader) loadDept(ctx context.Context, ud *UserDetails) {
  178. if ud.DeptId == 0 {
  179. return
  180. }
  181. d, err := l.models.SysDeptModel.FindOne(ctx, ud.DeptId)
  182. if err != nil {
  183. logx.WithContext(ctx).Errorf("userDetailsLoader: query dept %d failed: %v", ud.DeptId, err)
  184. return
  185. }
  186. ud.DeptName = d.Name
  187. ud.DeptPath = d.Path
  188. ud.DeptType = d.DeptType
  189. }
  190. func (l *UserDetailsLoader) loadProduct(ctx context.Context, ud *UserDetails) {
  191. if ud.ProductCode == "" {
  192. return
  193. }
  194. p, err := l.models.SysProductModel.FindOneByCode(ctx, ud.ProductCode)
  195. if err != nil {
  196. logx.WithContext(ctx).Errorf("userDetailsLoader: query product %s failed: %v", ud.ProductCode, err)
  197. return
  198. }
  199. ud.ProductName = p.Name
  200. }
  201. func (l *UserDetailsLoader) loadMembership(ctx context.Context, ud *UserDetails) {
  202. if ud.IsSuperAdmin {
  203. ud.MemberType = consts.MemberTypeSuperAdmin
  204. }
  205. if ud.ProductCode == "" {
  206. return
  207. }
  208. if ud.IsSuperAdmin {
  209. return
  210. }
  211. member, err := l.models.SysProductMemberModel.FindOneByProductCodeUserId(ctx, ud.ProductCode, ud.UserId)
  212. if err != nil {
  213. if err != productmember.ErrNotFound {
  214. logx.WithContext(ctx).Errorf("userDetailsLoader: query member failed: %v", err)
  215. }
  216. return
  217. }
  218. ud.MemberType = member.MemberType
  219. }
  220. func (l *UserDetailsLoader) loadRoles(ctx context.Context, ud *UserDetails) {
  221. if ud.ProductCode == "" {
  222. return
  223. }
  224. roleIds, err := l.models.SysUserRoleModel.FindRoleIdsByUserId(ctx, ud.UserId)
  225. if err != nil || len(roleIds) == 0 {
  226. return
  227. }
  228. roles, err := l.models.SysRoleModel.FindByIds(ctx, roleIds)
  229. if err != nil {
  230. logx.WithContext(ctx).Errorf("userDetailsLoader: query roles failed: %v", err)
  231. return
  232. }
  233. ud.Roles = make([]RoleInfo, 0)
  234. minLevel := int64(math.MaxInt64)
  235. for _, r := range roles {
  236. if r.ProductCode == ud.ProductCode && r.Status == consts.StatusEnabled {
  237. ud.Roles = append(ud.Roles, RoleInfo{
  238. Id: r.Id,
  239. Name: r.Name,
  240. Remark: r.Remark,
  241. PermsLevel: r.PermsLevel,
  242. })
  243. if r.PermsLevel < minLevel {
  244. minLevel = r.PermsLevel
  245. }
  246. }
  247. }
  248. if minLevel < math.MaxInt64 {
  249. ud.MinPermsLevel = minLevel
  250. }
  251. }
  252. func (l *UserDetailsLoader) loadPerms(ctx context.Context, ud *UserDetails) {
  253. if ud.ProductCode == "" {
  254. return
  255. }
  256. // 超管 / ADMIN / DEVELOPER / 研发部门成员 → 全量权限
  257. if ud.IsSuperAdmin ||
  258. ud.MemberType == consts.MemberTypeAdmin ||
  259. ud.MemberType == consts.MemberTypeDeveloper ||
  260. ud.DeptType == consts.DeptTypeDev {
  261. codes, err := l.models.SysPermModel.FindAllCodesByProductCode(ctx, ud.ProductCode)
  262. if err != nil {
  263. logx.WithContext(ctx).Errorf("userDetailsLoader: query all perms failed: %v", err)
  264. }
  265. ud.Perms = codes
  266. return
  267. }
  268. // 普通成员:角色权限 + 用户附加权限 - 用户拒绝权限
  269. rolePermIds := make([]int64, 0)
  270. if len(ud.Roles) > 0 {
  271. roleIds := make([]int64, 0, len(ud.Roles))
  272. for _, r := range ud.Roles {
  273. roleIds = append(roleIds, r.Id)
  274. }
  275. ids, err := l.models.SysRolePermModel.FindPermIdsByRoleIds(ctx, roleIds)
  276. if err == nil {
  277. rolePermIds = ids
  278. }
  279. }
  280. allowIds, _ := l.models.SysUserPermModel.FindPermIdsByUserIdAndEffect(ctx, ud.UserId, consts.PermEffectAllow)
  281. denyIds, _ := l.models.SysUserPermModel.FindPermIdsByUserIdAndEffect(ctx, ud.UserId, consts.PermEffectDeny)
  282. denySet := make(map[int64]bool, len(denyIds))
  283. for _, id := range denyIds {
  284. denySet[id] = true
  285. }
  286. permIdSet := make(map[int64]bool)
  287. for _, id := range rolePermIds {
  288. if !denySet[id] {
  289. permIdSet[id] = true
  290. }
  291. }
  292. for _, id := range allowIds {
  293. if !denySet[id] {
  294. permIdSet[id] = true
  295. }
  296. }
  297. finalIds := make([]int64, 0, len(permIdSet))
  298. for id := range permIdSet {
  299. finalIds = append(finalIds, id)
  300. }
  301. if len(finalIds) > 0 {
  302. perms, err := l.models.SysPermModel.FindByIds(ctx, finalIds)
  303. if err == nil {
  304. codes := make([]string, 0, len(perms))
  305. for _, p := range perms {
  306. if p.Status == consts.StatusEnabled {
  307. codes = append(codes, p.Code)
  308. }
  309. }
  310. ud.Perms = codes
  311. }
  312. }
  313. }