wangdongxu

jira:NYJ-1460 desc:接口代码

# 应用镜像 - 基于基础镜像部署代码
FROM aigc-embedding-service-base:latest
ENV PROJECT_ROOT=/app ENV=docker
# 设置工作目录
WORKDIR /app
# 复制应用代码(包含配置文件)
COPY app/ ./app/
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
CMD ["python", "main.py"]
... ...
from typing import List, Optional, Literal, Union, Dict, Any, Annotated
from pydantic import BaseModel, Field
from fastapi import APIRouter
from api.resp_bean import RespBean, success
router = APIRouter()
# ============== 请求/响应模型定义 ==============
class FaceKwargs(BaseModel):
"""人脸类型扩展字段"""
person_id: Optional[str] = Field(None, description="人物id")
person_name: Optional[str] = Field(None, description="人物姓名")
class SportShotKwargs(BaseModel):
"""体育镜头类型扩展字段"""
match_name: Optional[str] = Field(None, description="比赛名称")
person_name: Optional[str] = Field(None, description="人名")
video_desc: Optional[str] = Field(None, description="视频描述")
shot_cls: Optional[str] = Field(None, description="镜头分类")
match_id: Optional[str] = Field(None, description="比赛id")
program_id: Optional[str] = Field(None, description="节目id")
# 定义类型映射,用于根据type自动转换kwargs
KwargsType = Union[FaceKwargs, SportShotKwargs]
class PutDataRequest(BaseModel):
"""写入数据请求体"""
id: str = Field(..., description="唯一键id")
embedding: List[float] = Field(..., description="向量")
embedding_version: str = Field(..., description="向量版本 小写数字下划线组成")
kwargs: Optional[Dict[str, Any]] = Field(None, description="扩展字段,根据type不同字段不同")
def get_typed_kwargs(self, type: str) -> Optional[KwargsType]:
"""根据type自动转换kwargs为具体类型"""
if self.kwargs is None:
return None
type_map = {
'face': FaceKwargs,
'sport_shot': SportShotKwargs
}
kwargs_class = type_map.get(type)
if kwargs_class:
return kwargs_class(**self.kwargs)
return None
class DelByIdRequest(BaseModel):
"""删除数据请求体"""
ids: List[str] = Field(..., description="id 集合")
class FilterItem(BaseModel):
"""过滤条件项"""
name: str = Field(..., description="字段名")
value: List = Field(..., description="字段值")
opt: Optional[Literal["eq", "neq", "lt", "gt", "lte", "gte", "like", "in"]] = Field(
None, description="操作类型: eq相等, neq不等, lt小于, gt大于, lte小于等于, gte大于等于, like模糊匹配, in内"
)
class SearchRequest(BaseModel):
"""检索数据请求体"""
embedding: List[float] = Field(..., description="向量")
embedding_version: str = Field(..., description="向量版本 小写数字下划线组成")
topk: int = Field(..., description="top k")
filters: Optional[List[FilterItem]] = Field(None, description="过滤字段")
class SearchResultItem(BaseModel):
"""检索结果单项"""
id: str = Field(..., description="id")
kwargs: dict = Field(..., description="扩展字段")
score: float = Field(..., description="分值")
class SearchResponseData(BaseModel):
"""检索响应数据"""
type: str = Field(..., description="type")
data_list: List[SearchResultItem] = Field(..., description="topk结果列表")
# ============== 接口定义 ==============
@router.put("/{type}/put", response_model=RespBean, tags=["数据服务"], summary="写入数据")
@router.post("/{type}/put", response_model=RespBean, tags=["数据服务"], summary="写入数据")
async def put_data(type: Literal["face", "sport_shot"], request: PutDataRequest):
"""
写入数据
"""
pass
return success()
@router.delete("/{type}/del_by_id", response_model=RespBean, tags=["数据服务"], summary="删除数据")
@router.post("/{type}/del_by_id", response_model=RespBean, tags=["数据服务"], summary="删除数据")
async def del_by_id(type: Literal["face", "sport_shot"], request: DelByIdRequest):
"""
删除数据
- **type**: 数据类型 (face-人脸, sport_shot-体育镜头)
- **ids**: id集合
"""
# TODO: 实现数据删除逻辑
pass
return success()
@router.post("/{type}/search", response_model=RespBean[SearchResponseData], tags=["数据服务"], summary="检索数据")
async def search_data(type: Literal["face", "sport_shot"], request: SearchRequest):
"""
检索数据
- **type**: 数据类型 (face-人脸, sport_shot-体育镜头)
- **embedding**: 查询向量
- **embedding_version**: 向量版本
- **topk**: 返回top k条结果
- **filters**: 过滤条件列表
"""
# TODO: 实现数据检索逻辑
pass
return success()
... ...
... ... @@ -11,7 +11,6 @@ from config import settings
class LoggingMiddleware(BaseHTTPMiddleware):
# 日志记录路径前缀
async def dispatch(self, request: Request, call_next):
# 获取请求信息
method = request.method
... ... @@ -40,11 +39,6 @@ class LoggingMiddleware(BaseHTTPMiddleware):
except Exception:
pass
# 记录请求日志
logger.info(f"→ HTTP Request | {method} {url} | Client: {client_host}")
if request_body:
logger.info(f" Request Body: {request_body}")
# 处理请求
response = await call_next(request)
... ... @@ -77,11 +71,22 @@ class LoggingMiddleware(BaseHTTPMiddleware):
except Exception:
pass
# 记录响应日志
# 构建单条日志记录
status_code = response.status_code
logger.info(f"← HTTP Response | {method} {url} | Status: {status_code} | Time: {process_time:.3f}s")
log_data = {
"method": method,
"url": url,
"client": client_host,
"status": status_code,
"time": f"{process_time:.3f}s"
}
if request_body:
log_data["request_body"] = request_body
if response_body:
logger.info(f" Response Body: {response_body}")
log_data["response_body"] = response_body
logger.info(json.dumps(log_data, ensure_ascii=False))
return response
else:
... ...
from fastapi import APIRouter
from config import settings
from api.data_router import router as data_router
api_router = APIRouter(prefix=settings.url_prefix)
def register_router(router: APIRouter, prefix: str):
api_router.include_router(router, prefix=prefix)
# 注册数据路由
register_router(data_router, prefix="/data")
... ...
from aabd.base.cfg_loader import load_yaml_by_file_with_env
from aabd.base.enhance_dict import EnhanceDict, read_prefixed_env_vars
from pathlib import Path
from logging import getLogger
logger = getLogger(__name__)
cfg_dir = Path(__file__).parent.absolute() / 'cfg'
settings = EnhanceDict(load_yaml_by_file_with_env((cfg_dir / 'config.yaml').as_posix()))
settings.update(read_prefixed_env_vars('APP_'))
settings.update(read_prefixed_env_vars('EMB_'))
logger.info(f'Settings: {settings}')
\ No newline at end of file
... ...
... ... @@ -50,8 +50,9 @@ config_fastapi(app)
if __name__ == "__main__":
import uvicorn
logger.info(f'docs_url: http://127.0.0.1:{settings.port}/docs')
uvicorn.run(
"main:app",
app,
host='0.0.0.0',
port=settings.port,
reload=settings.debug,
... ...