- 新增jd模块基础路由,整合app_store和payment子路由 - 实现苹果权益充值接口,支持苹果、携程及沃尔玛多个渠道 - 实现卡号密码查询接口,支持不同类别订单查询 - 新增短信认证相关接口,实现短信验证码发送及短信登录 - 新增商品管理接口,支持SKU详情查询及账号类下单功能 - 新增订单管理接口,实现订单删除功能 - 实现支付相关接口,增加刷新支付参数功能 - 定义完整请求及响应数据模型,确保接口数据规范 - 编写AppStoreSpider类,封装苹果应用内订单处理逻辑 - 引入多种代理池及请求重试机制,增强接口稳定性 - 添加详细日志记录,便于请求追踪与错误排查
388 lines
11 KiB
Python
388 lines
11 KiB
Python
"""
|
|
RESTful response format with generic types.
|
|
Provides unified response structure for all API endpoints.
|
|
"""
|
|
|
|
from typing import TypeVar, Generic, Optional, Any
|
|
from datetime import datetime
|
|
from pydantic import BaseModel, Field
|
|
from enum import IntEnum
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
class ApiResponse(BaseModel, Generic[T]):
|
|
"""
|
|
Unified API response structure.
|
|
|
|
Both success and error responses use this structure.
|
|
|
|
Attributes:
|
|
code: Business status code (0 = success, >0 = error)
|
|
message: Human-readable message
|
|
data: Response payload (typed) or None for errors
|
|
trace_id: Request trace ID for debugging
|
|
timestamp: Response timestamp in ISO 8601 format
|
|
"""
|
|
|
|
code: int = Field(description="Business status code (0=success, >0=error)")
|
|
message: str = Field(description="Human-readable message")
|
|
data: Optional[T] = Field(default=None, description="Response payload")
|
|
trace_id: str = Field(description="Request trace ID")
|
|
timestamp: datetime = Field(
|
|
default_factory=datetime.utcnow, description="Response timestamp"
|
|
)
|
|
|
|
|
|
class PaginationMeta(BaseModel):
|
|
"""Pagination metadata."""
|
|
|
|
total: int = Field(description="Total number of records")
|
|
page: int = Field(description="Current page number (1-based)")
|
|
page_size: int = Field(description="Number of records per page")
|
|
total_pages: int = Field(description="Total number of pages")
|
|
|
|
|
|
class PaginatedData(BaseModel, Generic[T]):
|
|
"""
|
|
Paginated data structure.
|
|
|
|
Attributes:
|
|
items: List of items for current page
|
|
pagination: Pagination metadata
|
|
"""
|
|
|
|
items: list[T] = Field(description="List of items")
|
|
pagination: PaginationMeta = Field(description="Pagination metadata")
|
|
|
|
|
|
def success(
|
|
data: Optional[T] = None, message: str = "Success", trace_id: str = ""
|
|
) -> ApiResponse[T]:
|
|
"""
|
|
Create success response.
|
|
|
|
Args:
|
|
data: Response payload
|
|
message: Success message
|
|
trace_id: Request trace ID
|
|
|
|
Returns:
|
|
ApiResponse: Success response with code=0
|
|
|
|
Example:
|
|
return success(data={"user_id": 123}, message="User created")
|
|
"""
|
|
return ApiResponse(
|
|
code=BusinessCode.SUCCESS,
|
|
message=message,
|
|
data=data,
|
|
trace_id=trace_id,
|
|
timestamp=datetime.now(),
|
|
)
|
|
|
|
|
|
def error(code: "BusinessCode", data: Any=None, message: str="", trace_id: str = "") -> ApiResponse[None]:
|
|
"""
|
|
Create error response.
|
|
|
|
Args:
|
|
code: Business error code (must be > 0)
|
|
message: Error message
|
|
trace_id: Request trace ID
|
|
|
|
Returns:
|
|
ApiResponse: Error response
|
|
|
|
Example:
|
|
return error(code=1001, message="Login failed: Invalid credentials")
|
|
"""
|
|
if code <= 0:
|
|
raise ValueError("Error code must be greater than 0")
|
|
|
|
if message == "":
|
|
message = BusinessCode(code).name
|
|
|
|
return ApiResponse(
|
|
code=code,
|
|
message=message,
|
|
data=data,
|
|
trace_id=trace_id,
|
|
timestamp=datetime.now(),
|
|
)
|
|
|
|
|
|
def paginated(
|
|
items: list[T],
|
|
total: int,
|
|
page: int,
|
|
page_size: int,
|
|
message: str = "Success",
|
|
trace_id: str = "",
|
|
) -> ApiResponse[PaginatedData[T]]:
|
|
"""
|
|
Create paginated response.
|
|
|
|
Args:
|
|
items: List of items for current page
|
|
total: Total number of records
|
|
page: Current page number (1-based)
|
|
page_size: Number of records per page
|
|
message: Success message
|
|
trace_id: Request trace ID
|
|
|
|
Returns:
|
|
ApiResponse: Paginated response
|
|
|
|
Example:
|
|
return paginated(
|
|
items=[user1, user2],
|
|
total=100,
|
|
page=1,
|
|
page_size=10
|
|
)
|
|
"""
|
|
total_pages = (total + page_size - 1) // page_size if page_size > 0 else 0
|
|
|
|
pagination_meta = PaginationMeta(
|
|
total=total, page=page, page_size=page_size, total_pages=total_pages
|
|
)
|
|
|
|
paginated_data = PaginatedData(items=items, pagination=pagination_meta)
|
|
|
|
return ApiResponse(
|
|
code=0,
|
|
message=message,
|
|
data=paginated_data,
|
|
trace_id=trace_id,
|
|
timestamp=datetime.utcnow(),
|
|
)
|
|
|
|
|
|
# Business code constants
|
|
class BusinessCode(IntEnum):
|
|
"""
|
|
Business status code definitions.
|
|
|
|
Code ranges:
|
|
0: Success
|
|
1000-1999: Authentication & Authorization
|
|
2000-2999: Business Logic Errors
|
|
3000-3999: Validation Errors
|
|
4000-4999: Resource Errors
|
|
5000-5999: System Errors
|
|
9000-9999: Unknown Errors
|
|
"""
|
|
|
|
# Success
|
|
SUCCESS = 0
|
|
|
|
# Authentication & Authorization (1000-1999)
|
|
LOGIN_FAILED = 1001
|
|
TOKEN_EXPIRED = 1002
|
|
INSUFFICIENT_PERMISSIONS = 1003
|
|
INVALID_TOKEN = 1004
|
|
|
|
# Business Logic Errors (2000-2999)
|
|
ORDER_CREATION_FAILED = 2001
|
|
PAYMENT_FAILED = 2002
|
|
INSUFFICIENT_BALANCE = 2003
|
|
OPERATION_NOT_ALLOWED = 2004
|
|
|
|
# Validation Errors (3000-3999)
|
|
INVALID_INPUT = 3001
|
|
MISSING_REQUIRED_FIELD = 3002
|
|
INVALID_FORMAT = 3003
|
|
|
|
# Resource Errors (4000-4999)
|
|
RESOURCE_NOT_FOUND = 4001
|
|
RESOURCE_ALREADY_EXISTS = 4002
|
|
RESOURCE_CONFLICT = 4003
|
|
|
|
# System Errors (5000-5999)
|
|
DATABASE_ERROR = 5001
|
|
EXTERNAL_SERVICE_ERROR = 5002
|
|
CACHE_ERROR = 5003
|
|
INTERNAL_ERROR = 5004
|
|
|
|
# 没有实现
|
|
NOT_IMPLEMENTED = 5004
|
|
|
|
# Unknown Errors (9000-9999)
|
|
UNKNOWN_ERROR = 9000
|
|
|
|
# 京东充值的错误
|
|
JD_ORDER_FACE_PRICE_ERR = 10001
|
|
JD_ORDER_NORMAL_ERR = 10002
|
|
JD_ORDER_CK_ERR = 10003
|
|
JD_ORDER_TYPE_NOT_SUPPORTED_ERR = 10004
|
|
JD_ORDER_EXPIRED_ERR = 10005
|
|
JD_ORDER_STOCK_ERR = 10006
|
|
JD_ORDER_RISK_ERR = 10007
|
|
|
|
|
|
# Common error response examples for OpenAPI documentation
|
|
ERROR_RESPONSES = {
|
|
400: {
|
|
"description": "Bad Request - Validation or business logic error",
|
|
"content": {
|
|
"application/json": {
|
|
"example": {
|
|
"code": 3001,
|
|
"message": "Validation error: email - field required",
|
|
"data": None,
|
|
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
"timestamp": "2024-01-15T10:30:00Z",
|
|
}
|
|
}
|
|
},
|
|
},
|
|
401: {
|
|
"description": "Unauthorized - Authentication failed",
|
|
"content": {
|
|
"application/json": {
|
|
"example": {
|
|
"code": 1001,
|
|
"message": "Login failed: Invalid credentials",
|
|
"data": None,
|
|
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
"timestamp": "2024-01-15T10:30:00Z",
|
|
}
|
|
}
|
|
},
|
|
},
|
|
403: {
|
|
"description": "Forbidden - Insufficient permissions",
|
|
"content": {
|
|
"application/json": {
|
|
"example": {
|
|
"code": 1003,
|
|
"message": "Insufficient permissions to perform this action",
|
|
"data": None,
|
|
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
"timestamp": "2024-01-15T10:30:00Z",
|
|
}
|
|
}
|
|
},
|
|
},
|
|
404: {
|
|
"description": "Not Found - Resource does not exist",
|
|
"content": {
|
|
"application/json": {
|
|
"example": {
|
|
"code": 4001,
|
|
"message": "User not found",
|
|
"data": None,
|
|
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
"timestamp": "2024-01-15T10:30:00Z",
|
|
}
|
|
}
|
|
},
|
|
},
|
|
409: {
|
|
"description": "Conflict - Resource already exists or conflicts",
|
|
"content": {
|
|
"application/json": {
|
|
"example": {
|
|
"code": 4002,
|
|
"message": "User already exists",
|
|
"data": None,
|
|
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
"timestamp": "2024-01-15T10:30:00Z",
|
|
}
|
|
}
|
|
},
|
|
},
|
|
422: {
|
|
"description": "Unprocessable Entity - Validation error",
|
|
"content": {
|
|
"application/json": {
|
|
"example": {
|
|
"code": 3001,
|
|
"message": "Validation error: body -> email - value is not a valid email address",
|
|
"data": None,
|
|
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
"timestamp": "2024-01-15T10:30:00Z",
|
|
}
|
|
}
|
|
},
|
|
},
|
|
500: {
|
|
"description": "Internal Server Error - Unexpected error",
|
|
"content": {
|
|
"application/json": {
|
|
"example": {
|
|
"code": 9000,
|
|
"message": "An unexpected error occurred",
|
|
"data": None,
|
|
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
"timestamp": "2024-01-15T10:30:00Z",
|
|
}
|
|
}
|
|
},
|
|
},
|
|
502: {
|
|
"description": "Bad Gateway - External service error",
|
|
"content": {
|
|
"application/json": {
|
|
"example": {
|
|
"code": 5002,
|
|
"message": "External service unavailable",
|
|
"data": None,
|
|
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
"timestamp": "2024-01-15T10:30:00Z",
|
|
}
|
|
}
|
|
},
|
|
},
|
|
503: {
|
|
"description": "Service Unavailable - Database or cache error",
|
|
"content": {
|
|
"application/json": {
|
|
"example": {
|
|
"code": 5001,
|
|
"message": "Database error occurred",
|
|
"data": None,
|
|
"trace_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
|
"timestamp": "2024-01-15T10:30:00Z",
|
|
}
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
# Error message templates
|
|
class ErrorMessage:
|
|
"""Predefined error messages for common errors."""
|
|
|
|
# Authentication
|
|
LOGIN_FAILED = "Login failed: Invalid credentials"
|
|
TOKEN_EXPIRED = "Authentication token has expired"
|
|
INSUFFICIENT_PERMISSIONS = "Insufficient permissions to perform this action"
|
|
INVALID_TOKEN = "Invalid authentication token"
|
|
|
|
# Business Logic
|
|
ORDER_CREATION_FAILED = "Order creation failed: {reason}"
|
|
PAYMENT_FAILED = "Payment processing failed: {reason}"
|
|
INSUFFICIENT_BALANCE = "Insufficient account balance"
|
|
OPERATION_NOT_ALLOWED = "Operation not allowed: {reason}"
|
|
|
|
# Validation
|
|
INVALID_INPUT = "Invalid input: {field}"
|
|
MISSING_REQUIRED_FIELD = "Missing required field: {field}"
|
|
INVALID_FORMAT = "Invalid format: {field}"
|
|
|
|
# Resources
|
|
RESOURCE_NOT_FOUND = "{resource} not found"
|
|
RESOURCE_ALREADY_EXISTS = "{resource} already exists"
|
|
RESOURCE_CONFLICT = "{resource} conflict: {reason}"
|
|
|
|
# System
|
|
DATABASE_ERROR = "Database error occurred"
|
|
EXTERNAL_SERVICE_ERROR = "External service unavailable"
|
|
CACHE_ERROR = "Cache service error"
|
|
|
|
# Unknown
|
|
UNKNOWN_ERROR = "An unexpected error occurred"
|