syncPermsService.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package pub
  2. import (
  3. "context"
  4. "errors"
  5. "time"
  6. "perms-system-server/internal/consts"
  7. permModel "perms-system-server/internal/model/perm"
  8. "perms-system-server/internal/svc"
  9. "github.com/zeromicro/go-zero/core/stores/sqlx"
  10. "golang.org/x/crypto/bcrypt"
  11. )
  12. type SyncPermsResult struct {
  13. Added int64
  14. Updated int64
  15. Disabled int64
  16. }
  17. type SyncPermItem struct {
  18. Code string
  19. Name string
  20. Remark string
  21. }
  22. type SyncPermsError struct {
  23. Code int
  24. Message string
  25. }
  26. func (e *SyncPermsError) Error() string {
  27. return e.Message
  28. }
  29. func ExecuteSyncPerms(ctx context.Context, svcCtx *svc.ServiceContext, appKey, appSecret string, perms []SyncPermItem) (*SyncPermsResult, error) {
  30. product, err := svcCtx.SysProductModel.FindOneByAppKey(ctx, appKey)
  31. if err != nil {
  32. return nil, &SyncPermsError{Code: 401, Message: "无效的appKey"}
  33. }
  34. if err := bcrypt.CompareHashAndPassword([]byte(product.AppSecret), []byte(appSecret)); err != nil {
  35. return nil, &SyncPermsError{Code: 401, Message: "appSecret验证失败"}
  36. }
  37. if product.Status != consts.StatusEnabled {
  38. return nil, &SyncPermsError{Code: 403, Message: "产品已被禁用"}
  39. }
  40. if len(perms) == 0 {
  41. return nil, &SyncPermsError{Code: 400, Message: "权限列表不能为空"}
  42. }
  43. // 去重请求列表,避免同一笔同步里 codes 互相冲突。
  44. codes := make([]string, 0, len(perms))
  45. seen := make(map[string]bool, len(perms))
  46. dedupPerms := make([]SyncPermItem, 0, len(perms))
  47. for _, item := range perms {
  48. if seen[item.Code] {
  49. continue
  50. }
  51. seen[item.Code] = true
  52. codes = append(codes, item.Code)
  53. dedupPerms = append(dedupPerms, item)
  54. }
  55. now := time.Now().Unix()
  56. var added, updated, disabled int64
  57. // 同 tx 内先 SELECT ... FOR UPDATE 锁 sys_product 行,再在 tx 内读取 existing 并写入:
  58. // 把同一 product 的并发同步串行化,避免两次同步都认为 code X 不存在并并发 INSERT 撞
  59. // sys_perm UNIQUE(productCode, code) 拿 1062(见审计 H-3)。
  60. err = svcCtx.SysPermModel.TransactCtx(ctx, func(txCtx context.Context, session sqlx.Session) error {
  61. if _, err := svcCtx.SysProductModel.LockByCodeTx(txCtx, session, product.Code); err != nil {
  62. if errors.Is(err, sqlx.ErrNotFound) {
  63. return &SyncPermsError{Code: 404, Message: "产品不存在"}
  64. }
  65. return err
  66. }
  67. existingMap, err := svcCtx.SysPermModel.FindMapByProductCodeWithTx(txCtx, session, product.Code)
  68. if err != nil {
  69. return err
  70. }
  71. var toInsert []*permModel.SysPerm
  72. var toUpdate []*permModel.SysPerm
  73. for _, item := range dedupPerms {
  74. existing, ok := existingMap[item.Code]
  75. if !ok {
  76. toInsert = append(toInsert, &permModel.SysPerm{
  77. ProductCode: product.Code,
  78. Name: item.Name,
  79. Code: item.Code,
  80. Remark: item.Remark,
  81. Status: consts.StatusEnabled,
  82. CreateTime: now,
  83. UpdateTime: now,
  84. })
  85. added++
  86. continue
  87. }
  88. if existing.Name != item.Name || existing.Remark != item.Remark || existing.Status != consts.StatusEnabled {
  89. existing.Name = item.Name
  90. existing.Remark = item.Remark
  91. existing.Status = consts.StatusEnabled
  92. existing.UpdateTime = now
  93. toUpdate = append(toUpdate, existing)
  94. updated++
  95. }
  96. }
  97. if len(toInsert) > 0 {
  98. if insertErr := svcCtx.SysPermModel.BatchInsertWithTx(txCtx, session, toInsert); insertErr != nil {
  99. return insertErr
  100. }
  101. }
  102. if len(toUpdate) > 0 {
  103. if updateErr := svcCtx.SysPermModel.BatchUpdateWithTx(txCtx, session, toUpdate); updateErr != nil {
  104. return updateErr
  105. }
  106. }
  107. var disableErr error
  108. disabled, disableErr = svcCtx.SysPermModel.DisableNotInCodesWithTx(txCtx, session, product.Code, codes, now)
  109. return disableErr
  110. })
  111. if err != nil {
  112. var se *SyncPermsError
  113. if errors.As(err, &se) {
  114. return nil, se
  115. }
  116. return nil, &SyncPermsError{Code: 500, Message: "同步权限事务失败"}
  117. }
  118. if added > 0 || updated > 0 || disabled > 0 {
  119. svcCtx.UserDetailsLoader.CleanByProduct(ctx, product.Code)
  120. }
  121. return &SyncPermsResult{
  122. Added: added,
  123. Updated: updated,
  124. Disabled: disabled,
  125. }, nil
  126. }