feat: support upload image api
- support upload and delete image of people - support uploads any image and get link after login
This commit is contained in:
@@ -7,6 +7,9 @@ class ErrorCode(Enum):
|
||||
SUCCESS = 0
|
||||
MODEL_ERROR = 1000
|
||||
RLDB_ERROR = 2100
|
||||
OBS_ERROR = 3100
|
||||
OBS_INPUT_ERROR = 3102
|
||||
OBS_SERVICE_ERROR = 3103
|
||||
|
||||
class error(Protocol):
|
||||
_error_code: int = 0
|
||||
|
||||
@@ -4,11 +4,13 @@ import logging
|
||||
from typing import Protocol
|
||||
import qiniu
|
||||
import requests
|
||||
|
||||
from .error import ErrorCode, error
|
||||
from .config import get_instance as get_config
|
||||
|
||||
|
||||
class OBS(Protocol):
|
||||
def Put(self, obs_path: str, content: bytes) -> str:
|
||||
def put(self, obs_path: str, content: bytes) -> str:
|
||||
"""
|
||||
上传文件到OBS
|
||||
|
||||
@@ -21,7 +23,7 @@ class OBS(Protocol):
|
||||
"""
|
||||
...
|
||||
|
||||
def Get(self, obs_path: str) -> bytes:
|
||||
def get(self, obs_path: str) -> bytes:
|
||||
"""
|
||||
从OBS下载文件
|
||||
|
||||
@@ -33,7 +35,7 @@ class OBS(Protocol):
|
||||
"""
|
||||
...
|
||||
|
||||
def List(self, obs_path: str) -> list:
|
||||
def list(self, obs_path: str) -> list:
|
||||
"""
|
||||
列出OBS目录下的所有文件
|
||||
|
||||
@@ -45,7 +47,7 @@ class OBS(Protocol):
|
||||
"""
|
||||
...
|
||||
|
||||
def Del(self, obs_path: str) -> bool:
|
||||
def delete(self, obs_path: str) -> error:
|
||||
"""
|
||||
删除OBS文件
|
||||
|
||||
@@ -57,7 +59,7 @@ class OBS(Protocol):
|
||||
"""
|
||||
...
|
||||
|
||||
def Link(self, obs_path: str) -> str:
|
||||
def get_link(self, obs_path: str) -> str:
|
||||
"""
|
||||
获取OBS文件链接
|
||||
|
||||
@@ -68,6 +70,31 @@ class OBS(Protocol):
|
||||
str: OBS文件链接
|
||||
"""
|
||||
...
|
||||
|
||||
def delete_by_link(self, obs_link: str) -> error:
|
||||
"""
|
||||
根据OBS文件链接删除文件
|
||||
|
||||
Args:
|
||||
obs_link (str): OBS文件链接
|
||||
|
||||
Returns:
|
||||
bool: 是否删除成功
|
||||
"""
|
||||
...
|
||||
|
||||
def get_obs_path_by_link(self, obs_link: str) -> (str, error):
|
||||
"""
|
||||
从OBS文件链接获取OBS路径
|
||||
|
||||
Args:
|
||||
obs_link (str): OBS文件链接
|
||||
|
||||
Returns:
|
||||
str: OBS文件路径
|
||||
error: 错误信息
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
class Koodo:
|
||||
@@ -82,7 +109,7 @@ class Koodo:
|
||||
self.bucket = qiniu.BucketManager(self.auth)
|
||||
pass
|
||||
|
||||
def Put(self, obs_path: str, content: bytes) -> str:
|
||||
def put(self, obs_path: str, content: bytes) -> str:
|
||||
"""
|
||||
上传文件到OBS
|
||||
|
||||
@@ -103,7 +130,7 @@ class Koodo:
|
||||
logging.info(f"文件 {obs_path} 上传成功, OBS路径: {full_path}")
|
||||
return f"{self.outer_domain}/{full_path}"
|
||||
|
||||
def Get(self, obs_path: str) -> bytes:
|
||||
def get(self, obs_path: str) -> bytes:
|
||||
"""
|
||||
从OBS下载文件
|
||||
|
||||
@@ -121,7 +148,7 @@ class Koodo:
|
||||
return None
|
||||
return resp.content
|
||||
|
||||
def List(self, prefix: str = "") -> list[str]:
|
||||
def list(self, prefix: str = "") -> list[str]:
|
||||
"""
|
||||
列出OBS目录下的所有文件
|
||||
|
||||
@@ -143,7 +170,7 @@ class Koodo:
|
||||
# logging.debug(f"info: {info}")
|
||||
return keys
|
||||
|
||||
def Del(self, obs_path: str) -> bool:
|
||||
def delete(self, obs_path: str) -> error:
|
||||
"""
|
||||
删除OBS文件
|
||||
|
||||
@@ -151,17 +178,17 @@ class Koodo:
|
||||
obs_path (str): OBS文件路径
|
||||
|
||||
Returns:
|
||||
bool: 是否删除成功
|
||||
error: 删除结果
|
||||
"""
|
||||
ret, info = self.bucket.delete(self.bucket_name, f"{self.prefix_path}{obs_path}")
|
||||
logging.debug(f"文件 {obs_path} 删除 OBS, 结果: {ret}, 状态码: {info.status_code}, 错误信息: {info.text_body}")
|
||||
logging.debug(f"文件 {self.prefix_path}{obs_path} 删除 OBS, 结果: {ret}, 状态码: {info.status_code}, 错误信息: {info.text_body}")
|
||||
if ret is None or info.status_code != 200:
|
||||
logging.error(f"文件 {obs_path} 删除 OBS 失败, 错误信息: {info.text_body}")
|
||||
return False
|
||||
return error(error_code=ErrorCode.OBS_INPUT_ERROR, error_info=f"文件 {self.prefix_path}{obs_path} 删除 OBS 失败, 错误信息: {info.text_body}")
|
||||
logging.info(f"文件 {obs_path} 删除 OBS 成功")
|
||||
return True
|
||||
return error(error_code=ErrorCode.SUCCESS, error_info="success")
|
||||
|
||||
def Link(self, obs_path: str) -> str:
|
||||
def get_link(self, obs_path: str) -> str:
|
||||
"""
|
||||
获取OBS文件链接
|
||||
|
||||
@@ -173,6 +200,38 @@ class Koodo:
|
||||
"""
|
||||
return f"{self.outer_domain}/{self.prefix_path}{obs_path}"
|
||||
|
||||
def delete_by_link(self, obs_link: str) -> error:
|
||||
"""
|
||||
根据OBS文件链接删除文件
|
||||
|
||||
Args:
|
||||
obs_link (str): OBS文件链接
|
||||
|
||||
Returns:
|
||||
error: 删除结果
|
||||
"""
|
||||
obs_path, err = self.get_obs_path_by_link(obs_link)
|
||||
if not err.success:
|
||||
return err
|
||||
return self.delete(obs_path)
|
||||
|
||||
def get_obs_path_by_link(self, obs_link: str) -> (str, error):
|
||||
"""
|
||||
从OBS文件链接获取OBS路径
|
||||
|
||||
Args:
|
||||
obs_link (str): OBS文件链接
|
||||
|
||||
Returns:
|
||||
str: OBS文件路径
|
||||
error: 错误信息
|
||||
"""
|
||||
if not obs_link.startswith(f"{self.outer_domain}/{self.prefix_path}"):
|
||||
logging.error(f"文件 {obs_link} 不是 OBS 文件链接")
|
||||
return "", error(error_code=ErrorCode.OBS_INPUT_ERROR, error_info=f"文件 {obs_link} 不是 OBS 文件链接")
|
||||
obs_path = obs_link[len(self.outer_domain) + len(self.prefix_path) + 1:]
|
||||
return obs_path, error(error_code=ErrorCode.SUCCESS, error_info="success")
|
||||
|
||||
|
||||
_obs_instance: OBS = None
|
||||
|
||||
@@ -213,8 +272,8 @@ if __name__ == "__main__":
|
||||
# print(f"文件 {obs_path} 链接: {link}")
|
||||
|
||||
# 列出OBS目录下的所有文件
|
||||
keys = obs.List("")
|
||||
keys = obs.list("")
|
||||
print(f"OBS 目录下的所有文件: {keys}")
|
||||
for key in keys:
|
||||
link = obs.Del(key)
|
||||
link = obs.delete(key)
|
||||
print(f"文件 {key} 删除 OBS 成功: {link}")
|
||||
|
||||
119
src/web/api.py
119
src/web/api.py
@@ -38,38 +38,52 @@ async def ping():
|
||||
class PostInputRequest(BaseModel):
|
||||
text: str
|
||||
|
||||
@api.post("/api/recognition/input")
|
||||
async def post_input(request: PostInputRequest):
|
||||
people = extract_people(request.text)
|
||||
resp = BaseResponse(error_code=0, error_info="success")
|
||||
resp.data = people.to_dict()
|
||||
return resp
|
||||
|
||||
@api.post("/api/recognition/image")
|
||||
async def post_input_image(image: UploadFile = File(...)):
|
||||
@authorized_router.post("/api/upload/image")
|
||||
async def post_upload_image(image: UploadFile = File(...)):
|
||||
# 实现上传图片的处理
|
||||
# 保存上传的图片文件
|
||||
# 生成唯一的文件名
|
||||
file_extension = os.path.splitext(image.filename)[1]
|
||||
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
||||
|
||||
# 确保uploads目录存在
|
||||
os.makedirs("uploads", exist_ok=True)
|
||||
|
||||
|
||||
# 保存文件到对象存储
|
||||
file_path = f"uploads/{unique_filename}"
|
||||
obs_util = obs.get_instance()
|
||||
obs_util.Put(file_path, await image.read())
|
||||
|
||||
obs_util.put(file_path, await image.read())
|
||||
|
||||
# 获取对象存储外链
|
||||
obs_url = obs_util.Link(file_path)
|
||||
obs_url = obs_util.get_link(file_path)
|
||||
return BaseResponse(error_code=0, error_info="success", data=obs_url)
|
||||
|
||||
@authorized_router.post("/api/recognition/input")
|
||||
async def post_recognition_input(request: PostInputRequest):
|
||||
people = extract_people(request.text)
|
||||
resp = BaseResponse(error_code=0, error_info="success")
|
||||
resp.data = people.to_dict()
|
||||
return resp
|
||||
|
||||
@authorized_router.post("/api/recognition/image")
|
||||
async def post_recognition_image(image: UploadFile = File(...)):
|
||||
# 实现上传图片的处理
|
||||
# 保存上传的图片文件
|
||||
# 生成唯一的文件名
|
||||
file_extension = os.path.splitext(image.filename)[1]
|
||||
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
||||
|
||||
# 保存文件到对象存储
|
||||
file_path = f"uploads/{unique_filename}"
|
||||
obs_util = obs.get_instance()
|
||||
obs_util.put(file_path, await image.read())
|
||||
|
||||
# 获取对象存储外链
|
||||
obs_url = obs_util.get_link(file_path)
|
||||
logging.info(f"obs_url: {obs_url}")
|
||||
|
||||
|
||||
# 调用OCR处理图片
|
||||
ocr_util = ocr.get_instance()
|
||||
ocr_result = ocr_util.recognize_image_text(obs_url)
|
||||
logging.info(f"ocr_result: {ocr_result}")
|
||||
|
||||
|
||||
people = extract_people(ocr_result, obs_url)
|
||||
resp = BaseResponse(error_code=0, error_info="success")
|
||||
resp.data = people.to_dict()
|
||||
@@ -130,7 +144,7 @@ class GetPeopleRequest(BaseModel):
|
||||
query: Optional[str] = None
|
||||
conds: Optional[dict] = None
|
||||
top_k: int = 5
|
||||
|
||||
|
||||
@authorized_router.get("/api/peoples")
|
||||
async def get_peoples(
|
||||
request: Request,
|
||||
@@ -142,7 +156,7 @@ async def get_peoples(
|
||||
limit: int = Query(10, description="分页大小"),
|
||||
offset: int = Query(0, description="分页偏移量"),
|
||||
):
|
||||
|
||||
|
||||
# 解析查询参数为字典
|
||||
conds = {}
|
||||
conds["user_id"] = getattr(request.state, 'user_id', '')
|
||||
@@ -156,7 +170,7 @@ async def get_peoples(
|
||||
conds["height"] = height
|
||||
if marital_status:
|
||||
conds["marital_status"] = marital_status
|
||||
|
||||
|
||||
logging.info(f"conds: , limit: {limit}, offset: {offset}")
|
||||
|
||||
results = []
|
||||
@@ -174,7 +188,7 @@ class RemarkRequest(BaseModel):
|
||||
|
||||
|
||||
@authorized_router.post("/api/people/{people_id}/remark")
|
||||
async def post_remark(request: Request, people_id: str, body: RemarkRequest):
|
||||
async def post_people_remark(request: Request, people_id: str, body: RemarkRequest):
|
||||
service = get_people_service()
|
||||
res, err = service.get(people_id)
|
||||
if not err.success or not res:
|
||||
@@ -188,7 +202,7 @@ async def post_remark(request: Request, people_id: str, body: RemarkRequest):
|
||||
|
||||
|
||||
@authorized_router.delete("/api/people/{people_id}/remark")
|
||||
async def delete_remark(request: Request, people_id: str):
|
||||
async def delete_people_remark(request: Request, people_id: str):
|
||||
service = get_people_service()
|
||||
res, err = service.get(people_id)
|
||||
if not err.success or not res:
|
||||
@@ -201,6 +215,64 @@ async def delete_remark(request: Request, people_id: str):
|
||||
return BaseResponse(error_code=0, error_info="success")
|
||||
|
||||
|
||||
@authorized_router.post("/api/people/{people_id}/image")
|
||||
async def post_people_image(request: Request, people_id: str, image: UploadFile = File(...)):
|
||||
|
||||
# 检查 people id 是否存在
|
||||
service = get_people_service()
|
||||
people, err = service.get(people_id)
|
||||
if not err.success:
|
||||
return BaseResponse(error_code=err.code, error_info=err.info)
|
||||
|
||||
if people.user_id != getattr(request.state, 'user_id', ''):
|
||||
return BaseResponse(error_code=ErrorCode.MODEL_ERROR.value, error_info="permission denied")
|
||||
|
||||
# 实现上传图片的处理
|
||||
# 保存上传的图片文件
|
||||
# 生成唯一的文件名
|
||||
file_extension = os.path.splitext(image.filename)[1]
|
||||
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
||||
|
||||
# 保存文件到对象存储
|
||||
file_path = f"peoples/{people_id}/images/{unique_filename}"
|
||||
obs_util = obs.get_instance()
|
||||
obs_util.put(file_path, await image.read())
|
||||
|
||||
# 获取对象存储外链
|
||||
obs_url = obs_util.get_link(file_path)
|
||||
logging.info(f"obs_url: {obs_url}")
|
||||
|
||||
return BaseResponse(error_code=0, error_info="success", data=obs_url)
|
||||
|
||||
|
||||
@authorized_router.delete("/api/people/{people_id}/image")
|
||||
async def delete_people_image(request: Request, people_id: str, image_url: str):
|
||||
# 检查 people id 是否存在
|
||||
service = get_people_service()
|
||||
people, err = service.get(people_id)
|
||||
if not err.success:
|
||||
return BaseResponse(error_code=err.code, error_info=err.info)
|
||||
|
||||
if people.user_id != getattr(request.state, 'user_id', ''):
|
||||
return BaseResponse(error_code=ErrorCode.MODEL_ERROR.value, error_info="permission denied")
|
||||
|
||||
# 检查 image_url 是否是该 people 名下的图片链接
|
||||
obs_util = obs.get_instance()
|
||||
obs_path, err = obs_util.get_obs_path_by_link(image_url)
|
||||
if not err.success:
|
||||
return BaseResponse(error_code=err.code, error_info=err.info)
|
||||
if not obs_path.startswith(f"peoples/{people_id}/images/"):
|
||||
return BaseResponse(error_code=ErrorCode.OBS_INPUT_ERROR, error_info=f"文件 {image_url} 不是 {people_id} 名下的图片链接")
|
||||
|
||||
# 实现删除图片的处理
|
||||
# 删除对象存储中的文件
|
||||
err = obs_util.delete_by_link(image_url)
|
||||
if not err.success:
|
||||
return BaseResponse(error_code=err.code, error_info=err.info)
|
||||
|
||||
return BaseResponse(error_code=0, error_info="success")
|
||||
|
||||
|
||||
class SendCodeRequest(BaseModel):
|
||||
target_type: str
|
||||
target: str
|
||||
@@ -411,4 +483,5 @@ async def update_user_email(request: Request, body: UpdateEmailRequest):
|
||||
}
|
||||
return BaseResponse(error_code=0, error_info="success", data=data)
|
||||
|
||||
|
||||
api.include_router(authorized_router)
|
||||
|
||||
Reference in New Issue
Block a user