userDetailsLoader.go 13 KB

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