diff --git a/app/api/v1/api.py b/app/api/v1/api.py index 0d2f31e..26f1a25 100644 --- a/app/api/v1/api.py +++ b/app/api/v1/api.py @@ -2,12 +2,12 @@ from fastapi import APIRouter from app.api.v1.endpoints import router as endpoints_router from app.api.v1.users import router as users_router from app.api.v1.auth import router as auth_router -from app.api.v1.user_images import router as user_images_router +from app.api.v1.person_images import router as person_images_router from app.api.v1.clothing import router as clothing_router api_router = APIRouter() api_router.include_router(endpoints_router, prefix="") api_router.include_router(users_router, prefix="/users") api_router.include_router(auth_router, prefix="/auth") -api_router.include_router(user_images_router, prefix="/user-images") +api_router.include_router(person_images_router, prefix="/person-images") api_router.include_router(clothing_router, prefix="/clothing") diff --git a/app/api/v1/person_images.py b/app/api/v1/person_images.py new file mode 100644 index 0000000..20a1b5a --- /dev/null +++ b/app/api/v1/person_images.py @@ -0,0 +1,86 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from app.api import deps +from app.schemas.person_image import ( + PersonImage, + PersonImageCreate, + PersonImageUpdate +) +from app.services import person_image as user_image_service + +router = APIRouter() + +@router.get("", response_model=List[PersonImage]) +async def get_person_images( + db: AsyncSession = Depends(deps.get_db), + current_user = Depends(deps.get_current_user), + skip: int = 0, + limit: int = 100 +): + """获取当前用户的所有人物形象""" + return await user_image_service.get_person_images_by_user( + db=db, + user_id=current_user.id, + skip=skip, + limit=limit + ) + +@router.post("", response_model=PersonImage, status_code=status.HTTP_201_CREATED) +async def create_person_image( + *, + db: AsyncSession = Depends(deps.get_db), + current_user = Depends(deps.get_current_user), + image_in: PersonImageCreate +): + """创建新的人物形象""" + image_in.user_id = current_user.id + return await user_image_service.create_person_image(db=db, image=image_in) + +@router.put("/{image_id}", response_model=PersonImage) +async def update_person_image( + *, + db: AsyncSession = Depends(deps.get_db), + current_user = Depends(deps.get_current_user), + image_id: int, + image_in: PersonImageUpdate +): + """更新人物形象""" + image = await user_image_service.get_person_image(db=db, image_id=image_id) + if not image: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="人物形象不存在" + ) + if image.user_id != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="没有权限修改此人物形象" + ) + return await user_image_service.update_person_image( + db=db, + image_id=image_id, + image=image_in + ) + +@router.delete("/{image_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_person_image( + *, + db: AsyncSession = Depends(deps.get_db), + current_user = Depends(deps.get_current_user), + image_id: int +): + """删除人物形象""" + image = await user_image_service.get_person_image(db=db, image_id=image_id) + if not image: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="人物形象不存在" + ) + if image.user_id != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="没有权限删除此人物形象" + ) + await user_image_service.delete_person_image(db=db, image_id=image_id) + return None \ No newline at end of file diff --git a/app/api/v1/user_images.py b/app/api/v1/user_images.py deleted file mode 100644 index 15357b4..0000000 --- a/app/api/v1/user_images.py +++ /dev/null @@ -1,119 +0,0 @@ -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.ext.asyncio import AsyncSession -from typing import List - -from app.schemas.user_image import UserImage, UserImageCreate, UserImageUpdate, UserImageWithUser -from app.services import user_image as user_image_service -from app.db.database import get_db -from app.api.deps import get_current_user -from app.models.users import User as UserModel -from app.core.exceptions import BusinessError -from app.schemas.response import StandardResponse -import logging - -# 创建日志记录器 -logger = logging.getLogger(__name__) - -router = APIRouter() - -@router.post("/", tags=["user-images"]) -async def create_user_image( - image: UserImageCreate, - current_user: UserModel = Depends(get_current_user), - db: AsyncSession = Depends(get_db) -): - """ - 创建用户形象图片 - - 需要JWT令牌认证 - """ - # 设置用户ID - image.user_id = current_user.id - - result = await user_image_service.create_user_image(db=db, image=image) - return StandardResponse(code=200, data=result) - -@router.get("/", tags=["user-images"]) -async def read_user_images( - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=100), - current_user: UserModel = Depends(get_current_user), - db: AsyncSession = Depends(get_db) -): - """ - 获取当前用户的所有形象图片 - - 需要JWT令牌认证 - """ - images = await user_image_service.get_user_images_by_user( - db, - user_id=current_user.id, - skip=skip, - limit=limit - ) - return StandardResponse(code=200, data=images) - -@router.get("/{image_id}", tags=["user-images"]) -async def read_image( - image_id: int, - current_user: UserModel = Depends(get_current_user), - db: AsyncSession = Depends(get_db) -): - """ - 获取指定形象图片 - - 需要JWT令牌认证 - """ - image = await user_image_service.get_user_image(db, image_id=image_id) - if image is None: - raise BusinessError("图片不存在", code=404) - if image.user_id != current_user.id: - raise BusinessError("没有权限访问此图片", code=403) - return StandardResponse(code=200, data=image) - -@router.put("/{image_id}", tags=["user-images"]) -async def update_image( - image_id: int, - image: UserImageUpdate, - current_user: UserModel = Depends(get_current_user), - db: AsyncSession = Depends(get_db) -): - """ - 更新形象图片 - - 需要JWT令牌认证 - """ - # 检查图片是否存在且属于当前用户 - db_image = await user_image_service.get_user_image(db, image_id=image_id) - if db_image is None: - raise BusinessError("图片不存在", code=404) - if db_image.user_id != current_user.id: - raise BusinessError("没有权限更新此图片", code=403) - - updated_image = await user_image_service.update_user_image( - db=db, - image_id=image_id, - image=image - ) - return StandardResponse(code=200, data=updated_image) - -@router.delete("/{image_id}", tags=["user-images"]) -async def delete_image( - image_id: int, - current_user: UserModel = Depends(get_current_user), - db: AsyncSession = Depends(get_db) -): - """ - 删除形象图片 - - 需要JWT令牌认证 - """ - # 检查图片是否存在且属于当前用户 - db_image = await user_image_service.get_user_image(db, image_id=image_id) - if db_image is None: - raise BusinessError("图片不存在", code=404) - if db_image.user_id != current_user.id: - raise BusinessError("没有权限删除此图片", code=403) - - deleted_image = await user_image_service.delete_user_image(db=db, image_id=image_id) - return StandardResponse(code=200, data=deleted_image) \ No newline at end of file diff --git a/app/db/init_db.py b/app/db/init_db.py index 4cc7cfa..611d9c8 100644 --- a/app/db/init_db.py +++ b/app/db/init_db.py @@ -1,7 +1,7 @@ import asyncio from app.db.database import Base, engine from app.models.users import User -from app.models.user_images import UserImage +from app.models.person_images import PersonImage from app.models.clothing import ClothingCategory, Clothing # 创建所有表格 diff --git a/app/models/person_images.py b/app/models/person_images.py new file mode 100644 index 0000000..616c0db --- /dev/null +++ b/app/models/person_images.py @@ -0,0 +1,13 @@ +from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey +from sqlalchemy.sql import func +from app.db.database import Base + +class PersonImage(Base): + """人物形象数据模型""" + __tablename__ = "person_images" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + image_url = Column(String(255), nullable=False) + is_default = Column(Boolean, default=False) + create_time = Column(DateTime(timezone=True), server_default=func.now()) \ No newline at end of file diff --git a/app/models/user_images.py b/app/models/user_images.py deleted file mode 100644 index 98e140e..0000000 --- a/app/models/user_images.py +++ /dev/null @@ -1,20 +0,0 @@ -from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean -from sqlalchemy.sql import func -from sqlalchemy.orm import relationship -from app.db.database import Base - -class UserImage(Base): - """用户个人形象库模型""" - __tablename__ = "user_images" - - id = Column(Integer, primary_key=True, autoincrement=True, index=True) - user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True, comment="用户ID") - image_url = Column(String(500), nullable=False, comment="图片URL") - is_default = Column(Boolean, default=False, nullable=False, comment="是否为默认形象") - create_time = Column(DateTime, default=func.now(), comment="创建时间") - - # 关系 - user = relationship("User", back_populates="images") - - def __repr__(self): - return f"" \ No newline at end of file diff --git a/app/schemas/person_image.py b/app/schemas/person_image.py new file mode 100644 index 0000000..5880e14 --- /dev/null +++ b/app/schemas/person_image.py @@ -0,0 +1,26 @@ +from pydantic import BaseModel +from typing import Optional +from datetime import datetime + +class PersonImageBase(BaseModel): + """人物形象基础模型""" + image_url: str + is_default: bool = False + +class PersonImageCreate(PersonImageBase): + """创建人物形象请求模型""" + pass + +class PersonImageUpdate(PersonImageBase): + """更新人物形象请求模型""" + image_url: Optional[str] = None + is_default: Optional[bool] = None + +class PersonImage(PersonImageBase): + """人物形象响应模型""" + id: int + user_id: int + create_time: datetime + + class Config: + from_attributes = True \ No newline at end of file diff --git a/app/schemas/user_image.py b/app/schemas/user_image.py deleted file mode 100644 index 2c634c0..0000000 --- a/app/schemas/user_image.py +++ /dev/null @@ -1,38 +0,0 @@ -from pydantic import BaseModel, Field -from typing import Optional -from datetime import datetime -from app.schemas.user import User - -class UserImageBase(BaseModel): - """用户形象基础模式""" - image_url: str = Field(..., description="图片URL") - is_default: bool = Field(False, description="是否为默认形象") - -class UserImageCreate(UserImageBase): - """创建用户形象请求""" - user_id: Optional[int] = Field(None, description="用户ID,可选,通常由系统设置") - -class UserImageInDB(UserImageBase): - """数据库中的用户形象数据""" - id: int - user_id: int - create_time: datetime - - class Config: - from_attributes = True - -class UserImage(UserImageInDB): - """用户形象响应模式""" - pass - -class UserImageUpdate(BaseModel): - """更新用户形象请求""" - image_url: Optional[str] = Field(None, description="图片URL") - is_default: Optional[bool] = Field(None, description="是否为默认形象") - -class UserImageWithUser(UserImage): - """包含用户信息的用户形象响应""" - user: Optional[User] = Field(None, description="用户信息") - - class Config: - from_attributes = True \ No newline at end of file diff --git a/app/services/user_image.py b/app/services/person_image.py similarity index 55% rename from app/services/user_image.py rename to app/services/person_image.py index a1485b6..a35c4f0 100644 --- a/app/services/user_image.py +++ b/app/services/person_image.py @@ -1,36 +1,36 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select from sqlalchemy import delete, update -from app.models.user_images import UserImage -from app.schemas.user_image import UserImageCreate, UserImageUpdate +from app.models.person_images import PersonImage +from app.schemas.person_image import PersonImageCreate, PersonImageUpdate from typing import List, Optional -async def get_user_image(db: AsyncSession, image_id: int): - """获取单个用户形象""" - result = await db.execute(select(UserImage).filter(UserImage.id == image_id)) +async def get_person_image(db: AsyncSession, image_id: int): + """获取单个人物形象""" + result = await db.execute(select(PersonImage).filter(PersonImage.id == image_id)) return result.scalars().first() -async def get_user_images_by_user(db: AsyncSession, user_id: int, skip: int = 0, limit: int = 100): - """获取用户的所有形象图片""" +async def get_person_images_by_user(db: AsyncSession, user_id: int, skip: int = 0, limit: int = 100): + """获取用户的所有人物形象图片""" result = await db.execute( - select(UserImage) - .filter(UserImage.user_id == user_id) - .order_by(UserImage.create_time.desc()) + select(PersonImage) + .filter(PersonImage.user_id == user_id) + .order_by(PersonImage.create_time.desc()) .offset(skip) .limit(limit) ) return result.scalars().all() -async def create_user_image(db: AsyncSession, image: UserImageCreate): - """创建用户形象 +async def create_person_image(db: AsyncSession, image: PersonImageCreate): + """创建人物形象 - image: 包含user_id的UserImageCreate对象 + image: 包含user_id的PersonImageCreate对象 """ # 如果设置为默认形象,先重置用户的所有形象为非默认 if image.is_default and image.user_id: await reset_default_images(db, image.user_id) - db_image = UserImage( + db_image = PersonImage( user_id=image.user_id, image_url=image.image_url, is_default=image.is_default @@ -40,9 +40,9 @@ async def create_user_image(db: AsyncSession, image: UserImageCreate): await db.refresh(db_image) return db_image -async def update_user_image(db: AsyncSession, image_id: int, image: UserImageUpdate): - """更新用户形象""" - db_image = await get_user_image(db, image_id) +async def update_person_image(db: AsyncSession, image_id: int, image: PersonImageUpdate): + """更新人物形象""" + db_image = await get_person_image(db, image_id) if not db_image: return None @@ -62,33 +62,33 @@ async def update_user_image(db: AsyncSession, image_id: int, image: UserImageUpd await db.refresh(db_image) return db_image -async def delete_user_image(db: AsyncSession, image_id: int): - """删除用户形象""" - db_image = await get_user_image(db, image_id) +async def delete_person_image(db: AsyncSession, image_id: int): + """删除人物形象""" + db_image = await get_person_image(db, image_id) if db_image: await db.delete(db_image) await db.commit() return db_image -async def delete_user_images(db: AsyncSession, user_id: int): - """删除用户所有形象图片""" - stmt = delete(UserImage).where(UserImage.user_id == user_id) +async def delete_person_images(db: AsyncSession, user_id: int): + """删除用户所有人物形象图片""" + stmt = delete(PersonImage).where(PersonImage.user_id == user_id) await db.execute(stmt) await db.commit() return True async def reset_default_images(db: AsyncSession, user_id: int): """重置用户所有形象为非默认""" - stmt = update(UserImage).where( - UserImage.user_id == user_id, - UserImage.is_default == True + stmt = update(PersonImage).where( + PersonImage.user_id == user_id, + PersonImage.is_default == True ).values(is_default=False) await db.execute(stmt) async def get_default_image(db: AsyncSession, user_id: int): """获取用户的默认形象""" result = await db.execute( - select(UserImage) - .filter(UserImage.user_id == user_id, UserImage.is_default == True) + select(PersonImage) + .filter(PersonImage.user_id == user_id, PersonImage.is_default == True) ) return result.scalars().first() \ No newline at end of file diff --git a/main.py b/main.py deleted file mode 100644 index 1592e77..0000000 --- a/main.py +++ /dev/null @@ -1,53 +0,0 @@ -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware -from app.core.config import settings -from app.api.v1.api import api_router -from app.core.middleware import add_response_middleware -from app.core.exceptions import add_exception_handlers -from contextlib import asynccontextmanager - -@asynccontextmanager -async def lifespan(app: FastAPI): - # 在应用启动时执行 - from app.db.init_db import init_db - await init_db() - yield - # 在应用关闭时执行 - # 清理代码可以放在这里 - -app = FastAPI( - title=settings.PROJECT_NAME, - description=settings.PROJECT_DESCRIPTION, - version=settings.PROJECT_VERSION, - lifespan=lifespan -) - -# 配置CORS -app.add_middleware( - CORSMiddleware, - allow_origins=settings.BACKEND_CORS_ORIGINS, - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -# 添加响应中间件 -add_response_middleware(app) - -# 添加异常处理器 -add_exception_handlers(app) - -# 包含API路由 -app.include_router(api_router, prefix=settings.API_V1_STR) - -@app.get("/") -async def root(): - return {"message": "欢迎使用美搭Meida API服务"} - -@app.get("/health") -async def health_check(): - return {"status": "healthy"} - -if __name__ == "__main__": - import uvicorn - uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) \ No newline at end of file