✨ feat(vscode): 代码阅读加异常卡片,Python、TypeScript 的异常类画成同一张表(属性、构造参数),方法照常是子块
This commit is contained in:
1 parent
d5a092dd6f
commit
0afd012c94
21 files changed
+1118
-66
No files matched your search
@@ -0,0 +1,145 @@
|
||||
"""自定义异常体系.
|
||||
|
||||
所有业务异常继承自 AppException,携带 HTTP 状态码和结构化错误信息。
|
||||
在 main.py 中注册全局异常处理器统一返回格式。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
class AppException(Exception):
|
||||
"""应用异常基类.
|
||||
|
||||
Attributes:
|
||||
status_code: HTTP 状态码。
|
||||
error_code: 业务错误码(机器可读)。
|
||||
message: 错误描述(人类可读)。
|
||||
detail: 额外错误详情。
|
||||
"""
|
||||
|
||||
status_code: int = 500
|
||||
error_code: str = "INTERNAL_ERROR"
|
||||
message: str = "服务器内部错误"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str | None = None,
|
||||
*,
|
||||
detail: Any = None,
|
||||
error_code: str | None = None,
|
||||
) -> None:
|
||||
if message is not None:
|
||||
self.message = message
|
||||
if error_code is not None:
|
||||
self.error_code = error_code
|
||||
self.detail = detail
|
||||
super().__init__(self.message)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""序列化为 JSON 响应体."""
|
||||
body: dict[str, Any] = {
|
||||
"error_code": self.error_code,
|
||||
"message": self.message,
|
||||
}
|
||||
if self.detail is not None:
|
||||
body["detail"] = self.detail
|
||||
return body
|
||||
|
||||
|
||||
class NotFound(AppException):
|
||||
"""资源不存在 (404)."""
|
||||
|
||||
status_code = 404
|
||||
error_code = "NOT_FOUND"
|
||||
message = "资源不存在"
|
||||
|
||||
|
||||
class Unauthorized(AppException):
|
||||
"""未认证 (401)."""
|
||||
|
||||
status_code = 401
|
||||
error_code = "UNAUTHORIZED"
|
||||
message = "未认证或凭据无效"
|
||||
|
||||
|
||||
class Forbidden(AppException):
|
||||
"""无权限 (403)."""
|
||||
|
||||
status_code = 403
|
||||
error_code = "FORBIDDEN"
|
||||
message = "无操作权限"
|
||||
|
||||
|
||||
class ValidationError(AppException):
|
||||
"""数据验证失败 (422)."""
|
||||
|
||||
status_code = 422
|
||||
error_code = "VALIDATION_ERROR"
|
||||
message = "数据验证失败"
|
||||
|
||||
|
||||
class ExternalServiceError(AppException):
|
||||
"""外部服务调用失败 (502)."""
|
||||
|
||||
status_code = 502
|
||||
error_code = "EXTERNAL_SERVICE_ERROR"
|
||||
message = "外部服务调用失败"
|
||||
|
||||
|
||||
class BusinessError(AppException):
|
||||
"""业务错误 (400)."""
|
||||
|
||||
status_code = 400
|
||||
error_code = "BUSINESS_ERROR"
|
||||
message = "业务处理失败"
|
||||
|
||||
|
||||
class Conflict(AppException):
|
||||
"""资源冲突 (409)."""
|
||||
|
||||
status_code = 409
|
||||
error_code = "CONFLICT"
|
||||
message = "资源已存在"
|
||||
|
||||
|
||||
class GatewayTimeout(AppException):
|
||||
"""上游服务响应超时 (504).
|
||||
|
||||
用于表达 "请求已发往上游但在时限内未拿到响应":区别于 502
|
||||
(上游明确报错) 和 503 (上游不可达/未就绪)。
|
||||
"""
|
||||
|
||||
status_code = 504
|
||||
error_code = "UPSTREAM_TIMEOUT"
|
||||
message = "上游服务响应超时"
|
||||
|
||||
|
||||
class ServiceUnavailable(AppException):
|
||||
"""服务暂不可用 (503).
|
||||
|
||||
用于表达 "功能依赖的配置/外部资源未就绪" 这类状态:
|
||||
资源本身合法存在但当前不可达 / 未配置;区别于 404 (资源不存在)
|
||||
和 502 (上游报错)。
|
||||
"""
|
||||
|
||||
status_code = 503
|
||||
error_code = "SERVICE_UNAVAILABLE"
|
||||
message = "服务暂不可用"
|
||||
|
||||
|
||||
class PayloadTooLarge(AppException):
|
||||
"""请求体超限 (413)."""
|
||||
|
||||
status_code = 413
|
||||
error_code = "PAYLOAD_TOO_LARGE"
|
||||
message = "压缩包超过 20MB 上限"
|
||||
|
||||
|
||||
class UploadReadTimeout(AppException):
|
||||
"""上传收流超时 (408)."""
|
||||
|
||||
status_code = 408
|
||||
error_code = "UPLOAD_READ_TIMEOUT"
|
||||
message = "上传超时, 请检查网络后重试"
|
||||
@@ -1,10 +1,10 @@
|
||||
"""领域异常。"""
|
||||
"""接口响应。"""
|
||||
|
||||
from abc import ABCMeta
|
||||
|
||||
|
||||
class AppException(Exception):
|
||||
"""应用异常的基类。
|
||||
class AppResponse(BaseModel):
|
||||
"""应用响应的基类。
|
||||
|
||||
带上 HTTP 状态码。
|
||||
"""
|
||||
@@ -15,7 +15,7 @@ class AppException(Exception):
|
||||
self.message = message
|
||||
|
||||
|
||||
class NotFound(AppException): # 行尾
|
||||
class NotFound(AppResponse): # 行尾
|
||||
# 说明前面的注释
|
||||
"""找不到资源。"""
|
||||
status_code = 404
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"""commerce 域异常体系."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class CommerceError(Exception):
|
||||
"""commerce 域基础异常."""
|
||||
|
||||
|
||||
class ProductNotFoundError(CommerceError):
|
||||
"""商品不存在或已下架."""
|
||||
|
||||
|
||||
class OrderNotFoundError(CommerceError):
|
||||
"""订单不存在."""
|
||||
|
||||
|
||||
class InvalidOrderStateError(CommerceError):
|
||||
"""订单状态非法转换."""
|
||||
|
||||
|
||||
class PaymentVerifyError(CommerceError):
|
||||
"""支付通道验签失败."""
|
||||
|
||||
|
||||
class AmountMismatchError(CommerceError):
|
||||
"""支付金额与订单金额不匹配."""
|
||||
|
||||
|
||||
class FulfillmentError(CommerceError):
|
||||
"""fulfillment 调 billing 失败."""
|
||||
|
||||
|
||||
class RefundError(CommerceError):
|
||||
"""退款失败."""
|
||||
|
||||
|
||||
class UnsupportedPaymentMethodError(CommerceError):
|
||||
"""未配置当前 payment_method 对应的 driver. 通常意味着环境变量缺失."""
|
||||
|
||||
def __init__(self, method: str) -> None:
|
||||
super().__init__(f"unsupported payment method: {method}")
|
||||
self.method = method
|
||||
|
||||
|
||||
class OutOfStockError(CommerceError):
|
||||
"""商品库存不足, 无法下单。"""
|
||||
|
||||
|
||||
class PurchaseLimitExceededError(CommerceError):
|
||||
"""超出商品 purchase_limit_per_user 限购次数。"""
|
||||
|
||||
|
||||
class PendingOrderExistsError(CommerceError):
|
||||
"""同一用户已有相同商品的待支付订单。"""
|
||||
|
||||
|
||||
class ProductPlanMissingError(CommerceError):
|
||||
"""商品未绑定任何 billing.Plan, 无法下单/履约。"""
|
||||
|
||||
|
||||
class InstanceTypeNotFoundError(CommerceError):
|
||||
"""instance_type_id 指向的档位不存在; admin 应回 400 而不是撞 FK 500。"""
|
||||
|
||||
def __init__(self, instance_type_id: int) -> None:
|
||||
super().__init__(f"instance_type {instance_type_id} not found")
|
||||
self.instance_type_id = instance_type_id
|
||||
|
||||
|
||||
class ProductPlanMismatchError(CommerceError):
|
||||
"""商品展示档位与 Billing Plan 实际发放档位不一致."""
|
||||
|
||||
|
||||
class WechatPayUnknownError(CommerceError):
|
||||
"""微信预下单结果未知: 网络中断/超时, 不能判定远端是否已受理."""
|
||||
@@ -0,0 +1,30 @@
|
||||
/** 调接口出错。 */
|
||||
export class ApiError extends Error {
|
||||
status = 500
|
||||
// 机器可读的错误码
|
||||
static readonly kind: string = 'API_ERROR'
|
||||
|
||||
constructor(message: string, public code?: string) {
|
||||
super(message)
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return { status: this.status, code: this.code }
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundError extends ApiError {
|
||||
status = 404 // 找不到
|
||||
}
|
||||
|
||||
class Slow extends Timeout {}
|
||||
|
||||
class Timeout extends NotFoundError {}
|
||||
|
||||
const Cancelled = class extends RangeError {}
|
||||
|
||||
export class Forbidden extends errors.HttpException<string> {}
|
||||
|
||||
export class Repository extends Base {
|
||||
status = 1
|
||||
}
|
||||
Reference in new issue
Block a user