errors.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. """DMS业务异常。"""
  2. from __future__ import annotations
  3. from typing import Any
  4. class DmsError(Exception):
  5. code = "INTERNAL_ERROR"
  6. status_code = 500
  7. default_message = "服务器内部错误"
  8. def __init__(
  9. self,
  10. message: str | None = None,
  11. *,
  12. details: Any = None,
  13. code: str | None = None,
  14. ) -> None:
  15. super().__init__(message or self.default_message)
  16. self.message = message or self.default_message
  17. self.details = details
  18. self.code = code or self.code
  19. class InvalidArgumentError(DmsError):
  20. code = "INVALID_ARGUMENT"
  21. status_code = 400
  22. default_message = "请求参数错误"
  23. class AuthenticationError(DmsError):
  24. code = "TOKEN_INVALID"
  25. status_code = 401
  26. default_message = "未认证或登录凭证无效"
  27. class InvalidCredentialsError(AuthenticationError):
  28. code = "INVALID_CREDENTIALS"
  29. default_message = "用户名或密码错误"
  30. class TokenExpiredError(AuthenticationError):
  31. code = "TOKEN_EXPIRED"
  32. default_message = "登录凭证已过期"
  33. class UserDisabledError(AuthenticationError):
  34. code = "USER_DISABLED"
  35. default_message = "用户已被禁用"
  36. class AuthVersionMismatchError(AuthenticationError):
  37. code = "AUTH_VERSION_MISMATCH"
  38. default_message = "登录状态已失效,请重新登录"
  39. class AccountLockedError(AuthenticationError):
  40. """连续密码错误触发临时锁定(策略见 services/login_lockout.py)。"""
  41. code = "ACCOUNT_LOCKED"
  42. default_message = "失败次数过多,账号已临时锁定,请15分钟后重试"
  43. class PortalTokenInvalidError(AuthenticationError):
  44. code = "PORTAL_TOKEN_INVALID"
  45. default_message = "门户跳转凭证无效或已过期"
  46. class PortalTicketInvalidError(AuthenticationError):
  47. code = "PORTAL_TICKET_INVALID"
  48. default_message = "设置密码凭证无效或已过期,请从门户重新跳转"
  49. class ForbiddenError(DmsError):
  50. code = "FORBIDDEN"
  51. status_code = 403
  52. default_message = "无权限执行此操作"
  53. class SecurityLevelForbiddenError(ForbiddenError):
  54. code = "SECURITY_LEVEL_FORBIDDEN"
  55. default_message = "用户密级不足,无法查看该文档"
  56. class DocumentViewForbiddenError(ForbiddenError):
  57. code = "DOCUMENT_VIEW_FORBIDDEN"
  58. default_message = "无权查看该文档"
  59. class DocumentDownloadForbiddenError(ForbiddenError):
  60. code = "DOCUMENT_DOWNLOAD_FORBIDDEN"
  61. default_message = "无权下载该文档"
  62. class DocumentActionForbiddenError(ForbiddenError):
  63. """写路径按文档权限(矩阵/ACL)拒绝:区别于密级不足与查看/下载拒绝。"""
  64. code = "DOCUMENT_ACTION_FORBIDDEN"
  65. default_message = "无权对该文档执行此操作"
  66. class ResourceNotFoundError(DmsError):
  67. code = "RESOURCE_NOT_FOUND"
  68. status_code = 404
  69. default_message = "请求的资源不存在"
  70. class ConflictError(DmsError):
  71. code = "DATA_VERSION_CONFLICT"
  72. status_code = 409
  73. default_message = "数据冲突"
  74. class CategoryInUseError(ConflictError):
  75. code = "CATEGORY_IN_USE"
  76. default_message = "分类仍被子分类或方案文档占用"
  77. class CategoryNotLeafError(ConflictError):
  78. code = "CATEGORY_NOT_LEAF"
  79. default_message = "只有有效叶子分类可以直接存放主案"
  80. class CategoryFlatModeConflictError(ConflictError):
  81. code = "CATEGORY_FLAT_MODE_CONFLICT"
  82. default_message = "该一级分类下已存在子方案或配套资料挂载,请先解绑后再切换为扁平模式"
  83. class MainPlanNotFoundError(ResourceNotFoundError):
  84. code = "MAIN_PLAN_NOT_FOUND"
  85. default_message = "主案不存在"
  86. class FileNotFoundError(ResourceNotFoundError):
  87. code = "FILE_NOT_FOUND"
  88. default_message = "文档文件不存在"
  89. class PreviewUnavailableError(DmsError):
  90. code = "PREVIEW_UNAVAILABLE"
  91. status_code = 415
  92. default_message = "当前文件类型不支持在线预览"
  93. class PreviewConverterUnavailableError(DmsError):
  94. code = "PREVIEW_CONVERTER_UNAVAILABLE"
  95. status_code = 503
  96. default_message = "预览转换器未配置或不可用"
  97. class PreviewConversionTimeoutError(DmsError):
  98. code = "PREVIEW_CONVERSION_TIMEOUT"
  99. status_code = 504
  100. default_message = "文档转换超时,请稍后重试"
  101. class PreviewConversionFailedError(DmsError):
  102. code = "PREVIEW_CONVERSION_FAILED"
  103. status_code = 502
  104. default_message = "文档转换为PDF失败"
  105. class BatchManifestMismatchError(InvalidArgumentError):
  106. code = "BATCH_MANIFEST_MISMATCH"
  107. default_message = "批量文件数量与元数据数量不一致"
  108. class MainPlanHasChildrenError(ConflictError):
  109. code = "MAIN_PLAN_HAS_CHILDREN"
  110. default_message = "主案仍存在有效子方案"
  111. class DocumentNameConflictError(ConflictError):
  112. code = "DOCUMENT_NAME_CONFLICT"
  113. default_message = "同一范围内已存在同名方案"
  114. class AttachmentInUseError(ConflictError):
  115. code = "ATTACHMENT_IN_USE"
  116. default_message = "共享附件仍被主案挂载"
  117. class AttachmentHasNoAclError(InvalidArgumentError):
  118. code = "ATTACHMENT_HAS_NO_ACL"
  119. default_message = "共享附件不配置访问权限"
  120. class SubPlanPermissionInheritedError(ConflictError):
  121. code = "SUB_PLAN_PERMISSION_INHERITED"
  122. default_message = "子方案动态继承主案权限,不能独立配置"
  123. class DuplicatePermissionSubjectError(InvalidArgumentError):
  124. code = "DUPLICATE_PERMISSION_SUBJECT"
  125. default_message = "权限主体不能重复"
  126. class UnsupportedFileTypeError(DmsError):
  127. code = "UNSUPPORTED_FILE_TYPE"
  128. status_code = 415
  129. default_message = "不支持的文件类型"
  130. class PayloadTooLargeError(DmsError):
  131. code = "PAYLOAD_TOO_LARGE"
  132. status_code = 413
  133. default_message = "请求内容过大"
  134. class InternalError(DmsError):
  135. code = "INTERNAL_ERROR"
  136. status_code = 500
  137. default_message = "服务器内部错误"
  138. class DocumentNotDeletedError(ConflictError):
  139. code = "DOCUMENT_NOT_DELETED"
  140. default_message = "文档当前不在回收站中"
  141. class FileIntegrityMismatchError(ConflictError):
  142. code = "FILE_INTEGRITY_MISMATCH"
  143. default_message = "文档文件完整性校验失败"
  144. class FilePathInvalidError(InternalError):
  145. code = "FILE_PATH_INVALID"
  146. default_message = "文档存储路径异常"
  147. class RestoreFileChangedError(ConflictError):
  148. code = "RESTORE_FILE_CHANGED"
  149. default_message = "校验期间文件发生变化"
  150. class RestoreCategoryInvalidError(ConflictError):
  151. code = "RESTORE_CATEGORY_INVALID"
  152. default_message = "原方案分类无效,无法恢复"
  153. class RestoreParentInvalidError(ConflictError):
  154. code = "RESTORE_PARENT_INVALID"
  155. default_message = "原所属主案无效,无法恢复"
  156. class RestoreRelationConflictError(ConflictError):
  157. code = "RESTORE_RELATION_CONFLICT"
  158. default_message = "历史权限或挂载关系与当前有效关系冲突"
  159. class DocumentNotInMoveScopeError(ConflictError):
  160. """跨页移动时文档不在请求指定的可见范围内(分类或附件类型不符)。"""
  161. code = "DOCUMENT_NOT_IN_MOVE_SCOPE"
  162. default_message = "方案不在当前调整范围内"
  163. class OrganizationInUseError(ConflictError):
  164. code = "ORGANIZATION_IN_USE"
  165. default_message = "机构仍存在下级机构或挂载用户"
  166. class UserSelfProtectionError(ForbiddenError):
  167. code = "USER_SELF_PROTECTION"
  168. default_message = "不能对本账号执行该操作"
  169. class RoleInUseError(ConflictError):
  170. code = "ROLE_IN_USE"
  171. default_message = "角色仍有关联用户,无法删除"
  172. class DocumentPasswordRequiredError(ForbiddenError):
  173. code = "DOCUMENT_PASSWORD_REQUIRED"
  174. default_message = "该方案已设置密码保护,请输入密码后重试"
  175. class DocumentPasswordInvalidError(ForbiddenError):
  176. code = "DOCUMENT_PASSWORD_INVALID"
  177. default_message = "方案密码错误"
  178. class LoginIpForbiddenError(ForbiddenError):
  179. code = "LOGIN_IP_FORBIDDEN"
  180. default_message = "当前IP不在该部门允许的登录IP范围内"