|
@@ -6,6 +6,7 @@ from enum import Enum
|
|
|
from fastapi import APIRouter, File, UploadFile, Depends, HTTPException, Form, Path
|
|
from fastapi import APIRouter, File, UploadFile, Depends, HTTPException, Form, Path
|
|
|
from fastapi.responses import JSONResponse, FileResponse
|
|
from fastapi.responses import JSONResponse, FileResponse
|
|
|
from mysql.connector.pooling import PooledMySQLConnection
|
|
from mysql.connector.pooling import PooledMySQLConnection
|
|
|
|
|
+from mysql.connector import IntegrityError
|
|
|
|
|
|
|
|
from app.core.config import settings
|
|
from app.core.config import settings
|
|
|
from app.core.logger import get_logger
|
|
from app.core.logger import get_logger
|
|
@@ -35,15 +36,14 @@ async def upload_image_for_card(
|
|
|
db_conn: PooledMySQLConnection = db_dependency
|
|
db_conn: PooledMySQLConnection = db_dependency
|
|
|
):
|
|
):
|
|
|
"""
|
|
"""
|
|
|
- 上传一张图片,将其存入 card_images 表,并更新 cards 表中对应的外键字段。
|
|
|
|
|
- 这是一个事务性操作。
|
|
|
|
|
|
|
+ 上传一张图片,并将其作为一条新记录存入 card_images 表。
|
|
|
|
|
+ 这是一个事务性操作,并会检查是否存在重复的 (card_id, image_type) 组合。
|
|
|
"""
|
|
"""
|
|
|
try:
|
|
try:
|
|
|
detection_json = json.loads(json_data_str)
|
|
detection_json = json.loads(json_data_str)
|
|
|
except json.JSONDecodeError:
|
|
except json.JSONDecodeError:
|
|
|
raise HTTPException(status_code=400, detail="JSON格式无效。")
|
|
raise HTTPException(status_code=400, detail="JSON格式无效。")
|
|
|
|
|
|
|
|
- # 保存图片文件
|
|
|
|
|
file_extension = os.path.splitext(image.filename)[1]
|
|
file_extension = os.path.splitext(image.filename)[1]
|
|
|
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
|
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
|
|
image_path = settings.DATA_DIR / unique_filename
|
|
image_path = settings.DATA_DIR / unique_filename
|
|
@@ -58,40 +58,45 @@ async def upload_image_for_card(
|
|
|
try:
|
|
try:
|
|
|
cursor = db_conn.cursor()
|
|
cursor = db_conn.cursor()
|
|
|
|
|
|
|
|
- # 1. 插入图片记录到 card_images
|
|
|
|
|
|
|
+ # 检查 card_id 是否存在
|
|
|
|
|
+ cursor.execute(f"SELECT id FROM {settings.DB_CARD_TABLE_NAME} WHERE id = %s", (card_id,))
|
|
|
|
|
+ if not cursor.fetchone():
|
|
|
|
|
+ raise HTTPException(status_code=404, detail=f"ID为 {card_id} 的卡牌不存在。")
|
|
|
|
|
+
|
|
|
query_insert_image = (
|
|
query_insert_image = (
|
|
|
- f"INSERT INTO {settings.DB_IMAGE_TABLE_NAME} (image_name, image_path, detection_json) "
|
|
|
|
|
- "VALUES (%s, %s, %s)"
|
|
|
|
|
- )
|
|
|
|
|
- params = (image_name, str(image_path), json.dumps(detection_json, ensure_ascii=False))
|
|
|
|
|
- cursor.execute(query_insert_image, params)
|
|
|
|
|
- new_image_id = cursor.lastrowid
|
|
|
|
|
-
|
|
|
|
|
- # 2. 更新 cards 表,将新图片ID设置到对应字段
|
|
|
|
|
- # image_type.value 的值是 "front_face", "back_face" 等
|
|
|
|
|
- column_to_update = f"{image_type.value}_id"
|
|
|
|
|
- query_update_card = (
|
|
|
|
|
- f"UPDATE {settings.DB_CARD_TABLE_NAME} SET {column_to_update} = %s "
|
|
|
|
|
- f"WHERE card_id = %s"
|
|
|
|
|
|
|
+ f"INSERT INTO {settings.DB_IMAGE_TABLE_NAME} "
|
|
|
|
|
+ "(card_id, image_type, image_name, image_path, detection_json) "
|
|
|
|
|
+ "VALUES (%s, %s, %s, %s, %s)"
|
|
|
)
|
|
)
|
|
|
- cursor.execute(query_update_card, (new_image_id, card_id))
|
|
|
|
|
|
|
+ params = (
|
|
|
|
|
+ card_id, image_type.value, image_name, str(image_path), json.dumps(detection_json, ensure_ascii=False))
|
|
|
|
|
|
|
|
- if cursor.rowcount == 0:
|
|
|
|
|
- raise HTTPException(status_code=404, detail=f"ID为 {card_id} 的卡牌未找到,操作已回滚。")
|
|
|
|
|
|
|
+ cursor.execute(query_insert_image, params)
|
|
|
|
|
+ new_id = cursor.lastrowid
|
|
|
|
|
|
|
|
db_conn.commit()
|
|
db_conn.commit()
|
|
|
- logger.info(f"图片 {new_image_id} 已成功关联到卡牌 {card_id} 的 {image_type.value}。")
|
|
|
|
|
|
|
+ logger.info(f"图片 {new_id} 已成功关联到卡牌 {card_id},类型为 {image_type.value}。")
|
|
|
|
|
|
|
|
- # 查询并返回完整的图片记录
|
|
|
|
|
- cursor.execute(f"SELECT * FROM {settings.DB_IMAGE_TABLE_NAME} WHERE image_id = %s", (new_image_id,))
|
|
|
|
|
|
|
+ cursor.execute(f"SELECT * FROM {settings.DB_IMAGE_TABLE_NAME} WHERE id = %s", (new_id,))
|
|
|
new_image_data = cursor.fetchone()
|
|
new_image_data = cursor.fetchone()
|
|
|
columns = [desc[0] for desc in cursor.description]
|
|
columns = [desc[0] for desc in cursor.description]
|
|
|
return CardImageResponse.model_validate(dict(zip(columns, new_image_data)))
|
|
return CardImageResponse.model_validate(dict(zip(columns, new_image_data)))
|
|
|
|
|
|
|
|
|
|
+ except IntegrityError as e:
|
|
|
|
|
+ db_conn.rollback()
|
|
|
|
|
+ if os.path.exists(image_path): os.remove(image_path)
|
|
|
|
|
+ if e.errno == 1062:
|
|
|
|
|
+ raise HTTPException(
|
|
|
|
|
+ status_code=409,
|
|
|
|
|
+ detail=f"卡牌ID {card_id} 已存在类型为 '{image_type.value}' 的图片,请勿重复添加。"
|
|
|
|
|
+ )
|
|
|
|
|
+ logger.error(f"数据库完整性错误: {e}")
|
|
|
|
|
+ raise HTTPException(status_code=500, detail="数据库操作失败。")
|
|
|
|
|
+
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
db_conn.rollback()
|
|
db_conn.rollback()
|
|
|
if os.path.exists(image_path):
|
|
if os.path.exists(image_path):
|
|
|
- os.remove(image_path) # 如果数据库操作失败,删除已上传的文件
|
|
|
|
|
|
|
+ os.remove(image_path)
|
|
|
logger.error(f"关联图片到卡牌失败: {e}")
|
|
logger.error(f"关联图片到卡牌失败: {e}")
|
|
|
if isinstance(e, HTTPException): raise e
|
|
if isinstance(e, HTTPException): raise e
|
|
|
raise HTTPException(status_code=500, detail="数据库操作失败,所有更改已回滚。")
|
|
raise HTTPException(status_code=500, detail="数据库操作失败,所有更改已回滚。")
|
|
@@ -100,21 +105,21 @@ async def upload_image_for_card(
|
|
|
cursor.close()
|
|
cursor.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
-@router.get("/jsons/{image_id}", response_model=ImageJsonPairResponse, summary="获取图片的原始和修改后JSON")
|
|
|
|
|
-def get_image_jsons(image_id: int, db_conn: PooledMySQLConnection = db_dependency):
|
|
|
|
|
|
|
+@router.get("/jsons/{id}", response_model=ImageJsonPairResponse, summary="获取图片的原始和修改后JSON")
|
|
|
|
|
+def get_image_jsons(id: int, db_conn: PooledMySQLConnection = db_dependency):
|
|
|
"""获取指定图片ID的 detection_json 和 modified_json。"""
|
|
"""获取指定图片ID的 detection_json 和 modified_json。"""
|
|
|
cursor = None
|
|
cursor = None
|
|
|
try:
|
|
try:
|
|
|
cursor = db_conn.cursor(dictionary=True)
|
|
cursor = db_conn.cursor(dictionary=True)
|
|
|
- query = f"SELECT image_id, detection_json, modified_json FROM {settings.DB_IMAGE_TABLE_NAME} WHERE image_id = %s"
|
|
|
|
|
- cursor.execute(query, (image_id,))
|
|
|
|
|
|
|
+ query = f"SELECT id, detection_json, modified_json FROM {settings.DB_IMAGE_TABLE_NAME} WHERE id = %s"
|
|
|
|
|
+ cursor.execute(query, (id,))
|
|
|
result = cursor.fetchone()
|
|
result = cursor.fetchone()
|
|
|
if not result:
|
|
if not result:
|
|
|
- raise HTTPException(status_code=404, detail=f"ID为 {image_id} 的图片未找到。")
|
|
|
|
|
|
|
+ raise HTTPException(status_code=404, detail=f"ID为 {id} 的图片未找到。")
|
|
|
|
|
|
|
|
return ImageJsonPairResponse.model_validate(result)
|
|
return ImageJsonPairResponse.model_validate(result)
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logger.error(f"获取JSON对失败 ({image_id}): {e}")
|
|
|
|
|
|
|
+ logger.error(f"获取JSON对失败 ({id}): {e}")
|
|
|
if isinstance(e, HTTPException): raise e
|
|
if isinstance(e, HTTPException): raise e
|
|
|
raise HTTPException(status_code=500, detail="数据库查询失败。")
|
|
raise HTTPException(status_code=500, detail="数据库查询失败。")
|
|
|
finally:
|
|
finally:
|
|
@@ -122,30 +127,29 @@ def get_image_jsons(image_id: int, db_conn: PooledMySQLConnection = db_dependenc
|
|
|
cursor.close()
|
|
cursor.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
-@router.put("/update/json/{image_id}", status_code=200, summary="7. 修改图片的 modified_json")
|
|
|
|
|
|
|
+@router.put("/update/json/{id}", status_code=200, summary="修改图片的 modified_json")
|
|
|
def update_image_modified_json(
|
|
def update_image_modified_json(
|
|
|
- image_id: int,
|
|
|
|
|
|
|
+ id: int,
|
|
|
new_json_data: Dict[str, Any],
|
|
new_json_data: Dict[str, Any],
|
|
|
db_conn: PooledMySQLConnection = db_dependency
|
|
db_conn: PooledMySQLConnection = db_dependency
|
|
|
):
|
|
):
|
|
|
- """根据 image_id 更新 modified_json 字段。updated_at 会自动更新。"""
|
|
|
|
|
|
|
+ """根据 id 更新 modified_json 字段。updated_at 会自动更新。"""
|
|
|
cursor = None
|
|
cursor = None
|
|
|
try:
|
|
try:
|
|
|
cursor = db_conn.cursor()
|
|
cursor = db_conn.cursor()
|
|
|
new_json_str = json.dumps(new_json_data, ensure_ascii=False)
|
|
new_json_str = json.dumps(new_json_data, ensure_ascii=False)
|
|
|
- # updated_at 会由数据库自动更新
|
|
|
|
|
- query = f"UPDATE {settings.DB_IMAGE_TABLE_NAME} SET modified_json = %s WHERE image_id = %s"
|
|
|
|
|
- cursor.execute(query, (new_json_str, image_id))
|
|
|
|
|
|
|
+ query = f"UPDATE {settings.DB_IMAGE_TABLE_NAME} SET modified_json = %s WHERE id = %s"
|
|
|
|
|
+ cursor.execute(query, (new_json_str, id))
|
|
|
|
|
|
|
|
if cursor.rowcount == 0:
|
|
if cursor.rowcount == 0:
|
|
|
- raise HTTPException(status_code=404, detail=f"ID为 {image_id} 的图片未找到。")
|
|
|
|
|
|
|
+ raise HTTPException(status_code=404, detail=f"ID为 {id} 的图片未找到。")
|
|
|
|
|
|
|
|
db_conn.commit()
|
|
db_conn.commit()
|
|
|
- logger.info(f"图片ID {image_id} 的 modified_json 已更新。")
|
|
|
|
|
- return {"message": f"成功更新图片ID {image_id} 的JSON数据"}
|
|
|
|
|
|
|
+ logger.info(f"图片ID {id} 的 modified_json 已更新。")
|
|
|
|
|
+ return {"message": f"成功更新图片ID {id} 的JSON数据"}
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
db_conn.rollback()
|
|
db_conn.rollback()
|
|
|
- logger.error(f"更新JSON失败 ({image_id}): {e}")
|
|
|
|
|
|
|
+ logger.error(f"更新JSON失败 ({id}): {e}")
|
|
|
if isinstance(e, HTTPException): raise e
|
|
if isinstance(e, HTTPException): raise e
|
|
|
raise HTTPException(status_code=500, detail="更新JSON数据失败。")
|
|
raise HTTPException(status_code=500, detail="更新JSON数据失败。")
|
|
|
finally:
|
|
finally:
|
|
@@ -153,29 +157,29 @@ def update_image_modified_json(
|
|
|
cursor.close()
|
|
cursor.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
-@router.get("/image_file/{image_id}", summary="获取指定ID的图片文件")
|
|
|
|
|
-def get_image_file(image_id: int, db_conn: PooledMySQLConnection = db_dependency):
|
|
|
|
|
- """根据 image_id 查找记录,并返回对应的图片文件。"""
|
|
|
|
|
|
|
+@router.get("/image_file/{id}", summary="获取指定ID的图片文件")
|
|
|
|
|
+def get_image_file(id: int, db_conn: PooledMySQLConnection = db_dependency):
|
|
|
|
|
+ """根据 id 查找记录,并返回对应的图片文件。"""
|
|
|
cursor = None
|
|
cursor = None
|
|
|
try:
|
|
try:
|
|
|
cursor = db_conn.cursor()
|
|
cursor = db_conn.cursor()
|
|
|
- query = f"SELECT image_path FROM {settings.DB_IMAGE_TABLE_NAME} WHERE image_id = %s"
|
|
|
|
|
- cursor.execute(query, (image_id,))
|
|
|
|
|
|
|
+ query = f"SELECT image_path FROM {settings.DB_IMAGE_TABLE_NAME} WHERE id = %s"
|
|
|
|
|
+ cursor.execute(query, (id,))
|
|
|
result = cursor.fetchone()
|
|
result = cursor.fetchone()
|
|
|
|
|
|
|
|
if not result:
|
|
if not result:
|
|
|
- raise HTTPException(status_code=404, detail=f"ID为 {image_id} 的记录未找到。")
|
|
|
|
|
|
|
+ raise HTTPException(status_code=404, detail=f"ID为 {id} 的记录未找到。")
|
|
|
|
|
|
|
|
image_path = result[0]
|
|
image_path = result[0]
|
|
|
if not os.path.exists(image_path):
|
|
if not os.path.exists(image_path):
|
|
|
- logger.error(f"文件在服务器上未找到: {image_path} (数据库ID: {image_id})")
|
|
|
|
|
|
|
+ logger.error(f"文件在服务器上未找到: {image_path} (数据库ID: {id})")
|
|
|
raise HTTPException(status_code=404, detail="图片文件在服务器上不存在。")
|
|
raise HTTPException(status_code=404, detail="图片文件在服务器上不存在。")
|
|
|
|
|
|
|
|
return FileResponse(image_path)
|
|
return FileResponse(image_path)
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logger.error(f"获取图片失败 ({image_id}): {e}")
|
|
|
|
|
|
|
+ logger.error(f"获取图片失败 ({id}): {e}")
|
|
|
if isinstance(e, HTTPException): raise e
|
|
if isinstance(e, HTTPException): raise e
|
|
|
raise HTTPException(status_code=500, detail="获取图片文件失败。")
|
|
raise HTTPException(status_code=500, detail="获取图片文件失败。")
|
|
|
finally:
|
|
finally:
|
|
|
if cursor:
|
|
if cursor:
|
|
|
- cursor.close()
|
|
|
|
|
|
|
+ cursor.close()
|