error_translate.go 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. package repository
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "strings"
  7. dbent "github.com/Wei-Shaw/sub2api/ent"
  8. infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
  9. "github.com/lib/pq"
  10. )
  11. // clientFromContext 从 context 中获取事务 client,如果不存在则返回默认 client。
  12. //
  13. // 这个辅助函数支持 repository 方法在事务上下文中工作:
  14. // - 如果 context 中存在事务(通过 ent.NewTxContext 设置),返回事务的 client
  15. // - 否则返回传入的默认 client
  16. //
  17. // 使用示例:
  18. //
  19. // func (r *someRepo) SomeMethod(ctx context.Context) error {
  20. // client := clientFromContext(ctx, r.client)
  21. // return client.SomeEntity.Create().Save(ctx)
  22. // }
  23. func clientFromContext(ctx context.Context, defaultClient *dbent.Client) *dbent.Client {
  24. if tx := dbent.TxFromContext(ctx); tx != nil {
  25. return tx.Client()
  26. }
  27. return defaultClient
  28. }
  29. // translatePersistenceError 将数据库层错误翻译为业务层错误。
  30. //
  31. // 这是 Repository 层的核心错误处理函数,确保数据库细节不会泄露到业务层。
  32. // 通过统一的错误翻译,业务层可以使用语义明确的错误类型(如 ErrUserNotFound)
  33. // 而不是依赖于特定数据库的错误(如 sql.ErrNoRows)。
  34. //
  35. // 参数:
  36. // - err: 原始数据库错误
  37. // - notFound: 当记录不存在时返回的业务错误(可为 nil 表示不处理)
  38. // - conflict: 当违反唯一约束时返回的业务错误(可为 nil 表示不处理)
  39. //
  40. // 返回:
  41. // - 翻译后的业务错误,或原始错误(如果不匹配任何规则)
  42. //
  43. // 示例:
  44. //
  45. // err := translatePersistenceError(dbErr, service.ErrUserNotFound, service.ErrEmailExists)
  46. func translatePersistenceError(err error, notFound, conflict *infraerrors.ApplicationError) error {
  47. if err == nil {
  48. return nil
  49. }
  50. // 兼容 Ent ORM 和标准 database/sql 的 NotFound 行为。
  51. // Ent 使用自定义的 NotFoundError,而标准库使用 sql.ErrNoRows。
  52. // 这里同时处理两种情况,保持业务错误映射一致。
  53. if notFound != nil && (errors.Is(err, sql.ErrNoRows) || dbent.IsNotFound(err)) {
  54. return notFound.WithCause(err)
  55. }
  56. // 处理唯一约束冲突(如邮箱已存在、名称重复等)
  57. if conflict != nil && isUniqueConstraintViolation(err) {
  58. return conflict.WithCause(err)
  59. }
  60. // 未匹配任何规则,返回原始错误
  61. return err
  62. }
  63. // isUniqueConstraintViolation 判断错误是否为唯一约束冲突。
  64. //
  65. // 支持多种检测方式:
  66. // 1. PostgreSQL 特定错误码 23505(唯一约束冲突)
  67. // 2. 错误消息中包含的通用关键词
  68. //
  69. // 这种多层次的检测确保了对不同数据库驱动和 ORM 的兼容性。
  70. func isUniqueConstraintViolation(err error) bool {
  71. if err == nil {
  72. return false
  73. }
  74. // 优先检测 PostgreSQL 特定错误码(最精确)。
  75. // 错误码 23505 对应 unique_violation。
  76. // 参考:https://www.postgresql.org/docs/current/errcodes-appendix.html
  77. var pgErr *pq.Error
  78. if errors.As(err, &pgErr) {
  79. return pgErr.Code == "23505"
  80. }
  81. // 回退到错误消息检测(兼容其他场景)。
  82. // 这些关键词覆盖了 PostgreSQL、MySQL 等主流数据库的错误消息。
  83. msg := strings.ToLower(err.Error())
  84. return strings.Contains(msg, "duplicate key") ||
  85. strings.Contains(msg, "unique constraint") ||
  86. strings.Contains(msg, "duplicate entry")
  87. }