Skip to content

Commit

Permalink
重构!v0.2.0版本!理论可以快速写其他框架的适配
Browse files Browse the repository at this point in the history
  • Loading branch information
Shua-github committed Jan 26, 2025
1 parent 6f8dbb3 commit d08df3b
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 160 deletions.
44 changes: 37 additions & 7 deletions JMComicAPICore/app.py → JMComicAPICore/FastAPI_APP.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
from fastapi.responses import FileResponse, RedirectResponse, JSONResponse
from fastapi import FastAPI, Request, Query
from typing import Literal
import inspect
import uvicorn
from . import download, file_service
from typing import Union,List
from .utils import CustomError,CustomHTTPException,_Dict
def FastAPI_response_treat(data:_Dict):
"""
返回 JSON 响应。
class FastAPIApp:
参数:
- data (dict): JSON 数据
返回:
- 响应对象
"""
match data['type']:
case 'json':
return JSONResponse(status_code=data['code'],content=data)
case 'file':
return FileResponse(data['data']['path'])
case 'redirect':
return RedirectResponse(url=data['data']['url'])
case _:
raise CustomError.unknown_error(log="没有匹配到类型")

class FastAPI_App:
def __init__(self, config_path: str):
"""
初始化 FastAPI 应用实例,并绑定路由。
Expand All @@ -27,22 +47,32 @@ async def jm_file(self, file_name: str):
返回:
- FileResponse: 文件响应
"""
return await file_service.jm_file(file_name=file_name, config_path=self.config_path)
try:
return FastAPI_response_treat(await file_service.jm_file(file_name=file_name, config_path=self.config_path))
except CustomError as e:
raise CustomHTTPException(e)


async def jm_api(self, file_type: Literal["pdf", "zip"], request: Request, direct: Literal["true", "false"] = "false", jm_id: Union[int]=Query(...)):
async def jm_api(self, file_type: Literal["pdf", "zip"], request: Request, direct: Literal["true", "false"] = "false", jm_id: int = Query(...)):
"""
下载 API,判断是否需要直接返回下载链接或通过下载器下载。
参数:
- jm_id (int,list): 漫画 ID 或 漫画 ID 列表
- jm_id (int, list): 漫画 ID 或 漫画 ID 列表
- file_type (Literal["pdf", "zip"]): 文件类型
- request (Request): FastAPI 请求对象
- direct (Literal["true", "false"]): 是否直接返回下载链接
返回:
- Response: 下载链接或下载响应
"""
return await download.jm_download(jm_id, file_type, request=request, config_path=self.config_path, direct=direct)
try:
# 使用 await 等待协程结果
result = await download.jm_download(jm_id, file_type, request=request, config_path=self.config_path, direct=direct)
# 将结果传递给 FastAPI_response_treat
return FastAPI_response_treat(result)
except CustomError as e:
raise CustomHTTPException(e)

def _bind_routes(self):
"""
Expand Down Expand Up @@ -85,4 +115,4 @@ def _run(self, host: str, port: int):
- host (str): 服务器地址
- port (int): 端口号
"""
uvicorn.run(self.app, host=host, port=port)
uvicorn.run(self.app, host=host, port=port)
6 changes: 3 additions & 3 deletions JMComicAPICore/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .app import FastAPIApp
from .FastAPI_APP import FastAPI_App
def run(config_path,host,port):
FastAPIApp(config_path=config_path)._run(host=host,port=port)
FastAPI_App(config_path=config_path)._run(host=host,port=port)
def return_app(config_path):
app = FastAPIApp(config_path=config_path).app
app = FastAPI_App(config_path=config_path).app
return app
85 changes: 28 additions & 57 deletions JMComicAPICore/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
)
from jmcomic.jm_entity import JmAlbumDetail
from jmcomic.jm_downloader import JmDownloader
from .utils import error_json, ok_json, temp_if, Config
from .utils import ok_json, temp_if, Config, CustomError

async def direct_if(
request: Request,
Expand All @@ -21,65 +21,36 @@ async def direct_if(
file_name: Optional[str] = None,
file_type: Optional[str] = None
):
"""
根据 direct 参数判断是否直接返回文件下载链接或者 JSON 响应。
参数:
- jm_id (int): 文件的 JM ID。
- file_type (str): 文件类型(如 'mp4', 'pdf' 等)。
- request (Request): FastAPI 的请求对象,用于生成文件 URL。
- direct (Literal["true", "false"]): 控制返回行为,如果是 "false" 返回 JSON 响应,"true" 返回重定向(默认值为 "false")。
- album (JmAlbumDetail): 如果提供了 album,则使用 album 的 jm_id。
- dler (JmDownloader): 下载器实例,用于调试或处理下载。
- file_name (str): 文件名。
- file_type (str): 文件类型。
返回:
- JSON 响应或重定向响应:根据 direct 参数返回不同的结果。
异常:
- 如果 direct 参数值无效,返回 404 错误。
- 如果发生未知错误,返回 500 错误。
"""

# 回调支持,暂时没写好
# 检查 jm_id 和 file_name
if not jm_id:
if album:
jm_id = album.album_id
else:
return error_json.not_found(msg="没有提供jm_id", log="Missing jm_id.")
raise CustomError.not_found(msg="没有提供 jm_id", log="用户没有提供 jm_id")

if not file_name:
if jm_id and file_type:
file_name = f"{jm_id}.{file_type}"
else:
return error_json.not_found(msg="没有提供file_type", log="用户没有提供 file_type")

raise CustomError.not_found(msg="没有提供 file_type", log="用户没有提供 file_type")
# 回调调试
if dler:
print(f"JmDownloader instance: {dler}")

# 获取文件 URL
try:
# 使用 FastAPI 的 url_for 动态生成文件的 URL
file_url = request.url_for("jm_file", file_name=file_name)._url # _url 在这里是获取最终的 URL

# 根据 direct 参数选择响应
if direct == "false":
# 返回带有文件 URL 的 JSON 响应
return ok_json.jm_json(jm_id=jm_id, file_type=file_type, file_url=file_url)

elif direct == "true":
# 执行重定向
return ok_json.redirect(file_url)

else:
# 返回 404 错误
return error_json.not_found(msg="无效的 direct 参数值", log=f"Invalid 'direct' parameter value: {direct}")

file_url = str(request.url_for("jm_file", file_name=file_name)._url)
except Exception as e:
# 捕获其他异常并返回 500 错误
return error_json.unknown_error(log=f"Unexpected error: {str(e)}")
raise CustomError.unknown_error(log=f"生成文件 URL 失败: {str(e)}")

# 根据 direct 参数选择响应
if direct == "false":
return ok_json.jm_json(jm_id=jm_id, file_type=file_type, file_url=file_url)
elif direct == "true":
return ok_json.redirect(file_url)
else:
raise CustomError.not_found(msg="无效的 direct 参数值", log=f"Invalid 'direct' parameter value: {direct}")

async def jm_download(
jm_id: int,
Expand All @@ -88,53 +59,53 @@ async def jm_download(
config_path: str,
direct: Literal["true", "false"] = "false"
):
"""处理下载逻辑"""

# 加载配置
config = Config(config_path=config_path)
file_name = f"{jm_id}.{file_type}"
if temp_if([file_name],config_path)[file_name]:
return await direct_if(jm_id=jm_id,file_type=file_type, request=request, direct=direct)

# 检查文件是否已存在
if temp_if([file_name], config_path)[file_name]:
return await direct_if(jm_id=jm_id, file_type=file_type, request=request, direct=direct)

# 检查文件格式是否受支持
if file_type not in config.supported_formats:
return error_json.not_found(
raise CustomError.not_found(
msg="不支持的格式或输入错误",
log="请求的格式不在支持的格式列表中"
)

try:
download(jm_id, config.jm_config)

return await direct_if(jm_id=jm_id,file_type=file_type, request=request, direct=direct)
return await direct_if(jm_id=jm_id, file_type=file_type, request=request, direct=direct)

except MissingAlbumPhotoException as e:
return error_json.not_found(
raise CustomError.not_found(
msg=f"本子不存在: {e.error_jmid}",
log=f"ID:{e.error_jmid}, Msg:{e.msg}"
)

except JsonResolveFailException as e:
return error_json(
raise CustomError._raise_error(
code=500,
msg="解析 JSON 失败",
log=f"Msg:{e.resp.text}, URL: {e.resp.url}"
)

except RequestRetryAllFailException as e:
return error_json(
raise CustomError._raise_error(
code=500,
msg="请求失败,重试耗尽",
log=f"Msg:{e}, URL: {e.url}"
)

except JmcomicException as e:
return error_json(
raise CustomError._raise_error(
code=500,
msg="Jmcomic 库遇到异常",
log=f"Msg:{e}, Detail: {str(e)}"
)

except Exception as e:
return error_json.unknown_error(
raise CustomError.unknown_error(
log=f"Jmcomic 库遇到未知异常, Msg:{e}"
)
15 changes: 6 additions & 9 deletions JMComicAPICore/file_service.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
from .utils import ok_json, error_json, Config, file_path_join,file_if

from .utils import ok_json, Config, file_path_join,CustomError
async def jm_file(file_name: str, config_path: str):
"""提供文件"""
config = Config(config_path=config_path)



try:
# 拼接路径,构造JSON
path = file_path_join([config.temp_output, file_name])
return ok_json.file(path=path)

except FileNotFoundError as e:
# 文件未找到,返回 404 错误
return error_json.not_found(
msg="文件不存在",
log=f"文件 {file_name} 不存在,Msg: {str(e)}"
raise CustomError.not_found(
msg="文件未找到",
log=f"未找到文件: {file_name}, Msg: {str(e)}"
)

except Exception as e:
# 其他未知错误
return error_json.unknown_error(
log=f"文件服务 遇到未知异常,文件名: {file_name}, Msg: {str(e)}"
raise CustomError.unknown_error(
log=f"未知错误: {str(e)}"
)
5 changes: 3 additions & 2 deletions JMComicAPICore/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .json import Error as error_json
from .json import Ok as ok_json
from .json import _Dict
from .config import Config
from .file import file_if,file_path_join
from .temp import temp_if
from .temp import temp_if
from .exceptions import CustomError,CustomHTTPException
81 changes: 81 additions & 0 deletions JMComicAPICore/utils/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from inspect import stack

class CustomError(Exception):
"""自定义异常类,用于处理错误响应并返回详细的异常信息"""

def __init__(self, msg: str, code: int, log: str, status: str):
# 直接在 __init__ 中获取调用模块的名称
module_name = stack()[2].function
super().__init__(msg) # 调用父类构造函数
self.code = code
self.msg = msg
self.status = status
self.module_name = module_name
self.log = log

@staticmethod
def json(code: int, status: str, msg: str, log: str):
"""返回自定义格式的错误响应对象"""
return CustomError(msg=msg, code=code, log=log, status=status)

@staticmethod
def _raise_error(msg: str, log: str, code: int):
"""内部错误,返回错误对象"""
return CustomError(
msg=msg,
code=code,
log=log,
status='Internal Server Error'
)

@staticmethod
def not_found(msg: str, log: str):
"""返回404错误响应对象"""
return CustomError(
code=404,
msg=msg,
log=log,
status="Not Found"
)

@staticmethod
def unknown_error(log: str):
"""返回500未知错误的响应对象"""
return CustomError(
code=500,
msg="Unknown Error",
log=log,
status="Unknown Error"
)

def to_dict(self):
"""将异常信息转换为字典格式,方便其他框架使用"""
return {
"code": self.code,
"msg": self.msg,
"log": self.log,
"status": self.status,
"module_name": self.module_name
}


# FastAPI 专用的异常类
from fastapi import HTTPException

class CustomHTTPException(HTTPException):
"""FastAPI 专用的异常类,继承自 HTTPException"""

def __init__(self, custom_error: CustomError):
self.status_code = custom_error.code
self.code = custom_error.code
self.msg = custom_error.msg
self.log = custom_error.log
super().__init__(status_code=self.status_code, detail=self._format_detail())

def _format_detail(self):
return {
"data": {
"msg": self.msg,
"log": self.log,
}
}
Loading

0 comments on commit d08df3b

Please sign in to comment.