sysProductModel.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. package product
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "fmt"
  7. "github.com/zeromicro/go-zero/core/logx"
  8. "github.com/zeromicro/go-zero/core/stores/cache"
  9. "github.com/zeromicro/go-zero/core/stores/sqlx"
  10. )
  11. var ErrUpdateConflict = errors.New("update conflict: data has been modified by another operation")
  12. var _ SysProductModel = (*customSysProductModel)(nil)
  13. type (
  14. SysProductModel interface {
  15. sysProductModel
  16. FindList(ctx context.Context, page, pageSize int64) ([]*SysProduct, int64, error)
  17. UpdateWithOptLock(ctx context.Context, data *SysProduct, expectedUpdateTime int64) error
  18. // UpdateWithOptLockTx 与 UpdateWithOptLock 的 SQL 语义完全一致(WHERE id=? AND updateTime=?),
  19. // 区别仅在于 UPDATE 执行在调用方传入的事务里。用于 UpdateProduct 把"产品行 CAS 更新 +
  20. // 成员 userId 读取 + 批量 tokenVersion 递增"串成原子事务(审计 L-R15-3)。
  21. //
  22. // 审计 L-R12-1:本方法**不做**缓存失效——事务尚未 commit 时失效会把未落盘的新值灌入缓存;
  23. // 调用方在事务 commit 成功后负责走相应的 post-commit 链路(通常由 UserDetailsLoader.CleanByProduct
  24. // + 底层 sysProduct cache 失效配合覆盖)。session==nil 时直接拒绝。
  25. UpdateWithOptLockTx(ctx context.Context, session sqlx.Session, data *SysProduct, expectedUpdateTime int64) error
  26. // InvalidateProductCache 失效 sysProduct 的 id / appKey / code 三把低层缓存键。对齐
  27. // sysUserModel.InvalidateProfileCache 的 L-R12-1 契约,仅应在事务 commit 成功后由调用方
  28. // 显式调用;best-effort,失败只留日志。
  29. InvalidateProductCache(ctx context.Context, id int64, appKey, code string)
  30. // LockByCodeTx 在当前事务里锁定 product 行(SELECT ... FOR UPDATE),用于把跨表写入(如权限同步)
  31. // 按 product 串行化,避免两次并发 SyncPermissions 在 sys_perm UNIQUE(productCode, code) 上撞 1062。
  32. LockByCodeTx(ctx context.Context, session sqlx.Session, code string) (*SysProduct, error)
  33. }
  34. customSysProductModel struct {
  35. *defaultSysProductModel
  36. }
  37. )
  38. func NewSysProductModel(conn sqlx.SqlConn, c cache.CacheConf, cachePrefix string, opts ...cache.Option) SysProductModel {
  39. return &customSysProductModel{
  40. defaultSysProductModel: newSysProductModel(conn, c, cachePrefix, opts...),
  41. }
  42. }
  43. func (m *customSysProductModel) UpdateWithOptLock(ctx context.Context, data *SysProduct, expectedUpdateTime int64) error {
  44. sysProductIdKey := fmt.Sprintf("%s%v", cacheSysProductIdPrefix, data.Id)
  45. sysProductAppKeyKey := fmt.Sprintf("%s%v", cacheSysProductAppKeyPrefix, data.AppKey)
  46. sysProductCodeKey := fmt.Sprintf("%s%v", cacheSysProductCodePrefix, data.Code)
  47. res, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (sql.Result, error) {
  48. query := fmt.Sprintf("UPDATE %s SET `name`=?, `remark`=?, `status`=?, `updateTime`=? WHERE `id`=? AND `updateTime`=?", m.table)
  49. return conn.ExecCtx(ctx, query, data.Name, data.Remark, data.Status, data.UpdateTime, data.Id, expectedUpdateTime)
  50. }, sysProductIdKey, sysProductAppKeyKey, sysProductCodeKey)
  51. if err != nil {
  52. return err
  53. }
  54. affected, _ := res.RowsAffected()
  55. if affected == 0 {
  56. return ErrUpdateConflict
  57. }
  58. return nil
  59. }
  60. // InvalidateProductCache 见接口注释(审计 L-R12-1 / L-R15-3)。
  61. func (m *customSysProductModel) InvalidateProductCache(ctx context.Context, id int64, appKey, code string) {
  62. sysProductIdKey := fmt.Sprintf("%s%v", cacheSysProductIdPrefix, id)
  63. sysProductAppKeyKey := fmt.Sprintf("%s%v", cacheSysProductAppKeyPrefix, appKey)
  64. sysProductCodeKey := fmt.Sprintf("%s%v", cacheSysProductCodePrefix, code)
  65. if err := m.DelCacheCtx(ctx, sysProductIdKey, sysProductAppKeyKey, sysProductCodeKey); err != nil {
  66. if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
  67. logx.WithContext(ctx).Errorw("cache invalidation skipped: ctx canceled",
  68. logx.Field("audit", "cache_invalidation_skipped_due_to_ctx_cancel"),
  69. logx.Field("scope", "sysProductModel.InvalidateProductCache"),
  70. logx.Field("id", id),
  71. logx.Field("err", err.Error()),
  72. )
  73. } else {
  74. logx.WithContext(ctx).Errorf("sysProductModel.InvalidateProductCache failed: id=%d err=%v", id, err)
  75. }
  76. }
  77. }
  78. // UpdateWithOptLockTx 见接口注释(审计 L-R15-3 / L-R12-1)。
  79. func (m *customSysProductModel) UpdateWithOptLockTx(ctx context.Context, session sqlx.Session, data *SysProduct, expectedUpdateTime int64) error {
  80. if session == nil {
  81. return errors.New("UpdateWithOptLockTx requires a non-nil session")
  82. }
  83. query := fmt.Sprintf("UPDATE %s SET `name`=?, `remark`=?, `status`=?, `updateTime`=? WHERE `id`=? AND `updateTime`=?", m.table)
  84. res, err := session.ExecCtx(ctx, query, data.Name, data.Remark, data.Status, data.UpdateTime, data.Id, expectedUpdateTime)
  85. if err != nil {
  86. return err
  87. }
  88. affected, _ := res.RowsAffected()
  89. if affected == 0 {
  90. return ErrUpdateConflict
  91. }
  92. return nil
  93. }
  94. func (m *customSysProductModel) LockByCodeTx(ctx context.Context, session sqlx.Session, code string) (*SysProduct, error) {
  95. var resp SysProduct
  96. query := fmt.Sprintf("SELECT %s FROM %s WHERE `code` = ? LIMIT 1 FOR UPDATE", sysProductRows, m.table)
  97. if err := session.QueryRowCtx(ctx, &resp, query, code); err != nil {
  98. return nil, err
  99. }
  100. return &resp, nil
  101. }
  102. func (m *customSysProductModel) FindList(ctx context.Context, page, pageSize int64) ([]*SysProduct, int64, error) {
  103. var total int64
  104. countQuery := fmt.Sprintf("SELECT COUNT(*) FROM %s", m.table)
  105. if err := m.QueryRowNoCacheCtx(ctx, &total, countQuery); err != nil {
  106. return nil, 0, err
  107. }
  108. var list []*SysProduct
  109. query := fmt.Sprintf("SELECT %s FROM %s ORDER BY id DESC LIMIT ?,?", sysProductRows, m.table)
  110. if err := m.QueryRowsNoCacheCtx(ctx, &list, query, (page-1)*pageSize, pageSize); err != nil {
  111. return nil, 0, err
  112. }
  113. return list, total, nil
  114. }