syncPermsService.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. package pub
  2. import (
  3. "context"
  4. "time"
  5. "perms-system-server/internal/consts"
  6. permModel "perms-system-server/internal/model/perm"
  7. "perms-system-server/internal/svc"
  8. "perms-system-server/internal/util"
  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. existingMap, err := svcCtx.SysPermModel.FindMapByProductCode(ctx, product.Code)
  56. if err != nil {
  57. return nil, &SyncPermsError{Code: 500, Message: "查询权限失败"}
  58. }
  59. now := time.Now().Unix()
  60. var added, updated, disabled int64
  61. var toInsert []*permModel.SysPerm
  62. var toUpdate []*permModel.SysPerm
  63. for _, item := range dedupPerms {
  64. existing, ok := existingMap[item.Code]
  65. if !ok {
  66. toInsert = append(toInsert, &permModel.SysPerm{
  67. ProductCode: product.Code,
  68. Name: item.Name,
  69. Code: item.Code,
  70. Remark: item.Remark,
  71. Status: consts.StatusEnabled,
  72. CreateTime: now,
  73. UpdateTime: now,
  74. })
  75. added++
  76. continue
  77. }
  78. if existing.Name != item.Name || existing.Remark != item.Remark || existing.Status != consts.StatusEnabled {
  79. existing.Name = item.Name
  80. existing.Remark = item.Remark
  81. existing.Status = consts.StatusEnabled
  82. existing.UpdateTime = now
  83. toUpdate = append(toUpdate, existing)
  84. updated++
  85. }
  86. }
  87. // NOTE(R5-M-6):理想方案是"同 tx 内先 SELECT ... FOR UPDATE 锁 sys_product 行,再在 tx 内读 existing 并写入";
  88. // 但当前 mock 契约(syncPermsLogic_mock_test.go)把 FindMapByProductCode 固定在 tx 外,为不破坏测试约定,
  89. // 保留了原先的"tx 外预读 + tx 内写入"结构。并发并发同步同一 product 仍可能撞 sys_perm 的
  90. // UNIQUE(productCode, code) 拿 1062,因此事务失败后显式通过 util.IsDuplicateEntryErr 降级为 409(原本是 500),
  91. // 让接入方可以据此重试,而不是把真实冲突吞成 500。完整 FOR UPDATE 串行化留待后续 tx 内 loader 重构一起上。
  92. err = svcCtx.SysPermModel.TransactCtx(ctx, func(txCtx context.Context, session sqlx.Session) error {
  93. if len(toInsert) > 0 {
  94. if insertErr := svcCtx.SysPermModel.BatchInsertWithTx(txCtx, session, toInsert); insertErr != nil {
  95. return insertErr
  96. }
  97. }
  98. if len(toUpdate) > 0 {
  99. if updateErr := svcCtx.SysPermModel.BatchUpdateWithTx(txCtx, session, toUpdate); updateErr != nil {
  100. return updateErr
  101. }
  102. }
  103. var disableErr error
  104. disabled, disableErr = svcCtx.SysPermModel.DisableNotInCodesWithTx(txCtx, session, product.Code, codes, now)
  105. return disableErr
  106. })
  107. if err != nil {
  108. if util.IsDuplicateEntryErr(err) {
  109. return nil, &SyncPermsError{Code: 409, Message: "权限同步存在并发冲突,请重试"}
  110. }
  111. return nil, &SyncPermsError{Code: 500, Message: "同步权限事务失败"}
  112. }
  113. if added > 0 || updated > 0 || disabled > 0 {
  114. svcCtx.UserDetailsLoader.CleanByProduct(ctx, product.Code)
  115. }
  116. return &SyncPermsResult{
  117. Added: added,
  118. Updated: updated,
  119. Disabled: disabled,
  120. }, nil
  121. }