fix header
This commit is contained in:
19
.dockerignore
Normal file
19
.dockerignore
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
__pycache__
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
env
|
||||||
|
venv
|
||||||
|
.venv
|
||||||
|
.env
|
||||||
|
.git
|
||||||
|
.cache
|
||||||
|
.pytest_cache
|
||||||
|
.coverage
|
||||||
|
htmlcov
|
||||||
|
*.log
|
||||||
|
notebooks
|
||||||
|
.DS_Store
|
||||||
|
README.md
|
||||||
|
.dockerignore
|
||||||
|
Dockerfile
|
||||||
51
Dockerfile
51
Dockerfile
@@ -1,29 +1,60 @@
|
|||||||
|
# 使用多阶段构建来分离构建环境和运行环境
|
||||||
|
FROM python:3.10-slim as builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 1. 先只复制依赖文件
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# 换源和安装构建依赖
|
||||||
|
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources \
|
||||||
|
&& sed -i 's|security.debian.org/debian-security|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list.d/debian.sources
|
||||||
|
|
||||||
|
# 安装构建依赖
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends gcc python3-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 安装Python依赖
|
||||||
|
RUN pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple \
|
||||||
|
&& pip install --user -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple
|
||||||
|
|
||||||
|
# 第二阶段:运行环境
|
||||||
FROM python:3.10-slim
|
FROM python:3.10-slim
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
# 换源
|
||||||
|
|
||||||
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources \
|
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources \
|
||||||
&& sed -i 's|security.debian.org/debian-security|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list.d/debian.sources
|
&& sed -i 's|security.debian.org/debian-security|mirrors.ustc.edu.cn/debian-security|g' /etc/apt/sources.list.d/debian.sources
|
||||||
|
|
||||||
|
# 安装运行时的系统依赖(只安装必要的)
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends gcc python3-dev supervisor \
|
&& apt-get install -y --no-install-recommends supervisor \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
&& pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple \
|
|
||||||
&& pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple \
|
|
||||||
&& pip install gunicorn wait-for-it -i https://mirrors.aliyun.com/pypi/simple
|
|
||||||
|
|
||||||
|
# 从构建阶段复制已安装的Python包
|
||||||
|
COPY --from=builder /root/.local /root/.local
|
||||||
|
|
||||||
|
# 设置PATH让Python可以找到用户安装的包
|
||||||
|
ENV PATH=/root/.local/bin:$PATH
|
||||||
|
ENV PYTHONPATH=/app
|
||||||
|
|
||||||
|
# 设置时区
|
||||||
ENV TZ="Asia/Shanghai"
|
ENV TZ="Asia/Shanghai"
|
||||||
|
|
||||||
RUN mkdir -p /var/log/fastapi_server \
|
# 创建目录结构
|
||||||
|
RUN mkdir -p /var/log/app_server \
|
||||||
&& mkdir -p /var/log/supervisor \
|
&& mkdir -p /var/log/supervisor \
|
||||||
&& mkdir -p /etc/supervisor/conf.d
|
&& mkdir -p /etc/supervisor/conf.d
|
||||||
|
|
||||||
|
# 复制配置文件
|
||||||
COPY deploy/supervisor.conf /etc/supervisor/supervisord.conf
|
COPY deploy/supervisor.conf /etc/supervisor/supervisord.conf
|
||||||
|
COPY deploy/app_server.conf /etc/supervisor/conf.d/
|
||||||
|
|
||||||
COPY deploy/fastapi_server.conf /etc/supervisor/conf.d/
|
# 最后复制应用代码(利用Docker缓存层)
|
||||||
|
COPY backend/ ./backend
|
||||||
|
|
||||||
EXPOSE 8001
|
EXPOSE 8001
|
||||||
|
|
||||||
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8080"]
|
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8080"]
|
||||||
@@ -4,8 +4,6 @@ import logging
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
|
|
||||||
from soupsieve.util import lower
|
|
||||||
|
|
||||||
from backend.app.ai.crud import recording_dao
|
from backend.app.ai.crud import recording_dao
|
||||||
from backend.app.ai.crud.image_text_crud import image_text_dao
|
from backend.app.ai.crud.image_text_crud import image_text_dao
|
||||||
from backend.app.ai.model.image_text import ImageText
|
from backend.app.ai.model.image_text import ImageText
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ from datetime import datetime
|
|||||||
from backend.app.ai.crud.image_task_crud import image_task_dao
|
from backend.app.ai.crud.image_task_crud import image_task_dao
|
||||||
from backend.app.ai.model.image_task import ImageTaskStatus
|
from backend.app.ai.model.image_task import ImageTaskStatus
|
||||||
from backend.app.ai.service.image_service import image_service
|
from backend.app.ai.service.image_service import image_service
|
||||||
from backend.database.db import async_db_session, background_db_session
|
from backend.database.db import background_db_session
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError, OperationalError
|
||||||
from asyncpg import SerializationError
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -85,8 +84,8 @@ async def update_task_status_with_retry(db, task_id, status, result=None, error_
|
|||||||
try:
|
try:
|
||||||
result = await image_task_dao.update_task_status(db, task_id, status, result, error_message)
|
result = await image_task_dao.update_task_status(db, task_id, status, result, error_message)
|
||||||
return result
|
return result
|
||||||
except SerializationError as e:
|
except OperationalError as e:
|
||||||
logger.warning(f"Serialization error updating task {task_id} status to {status} (attempt {attempt + 1}): {str(e)}")
|
logger.warning(f"Operational error updating task {task_id} status to {status} (attempt {attempt + 1}): {str(e)}")
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
# Exponential backoff with jitter
|
# Exponential backoff with jitter
|
||||||
import random
|
import random
|
||||||
@@ -116,8 +115,8 @@ async def increment_retry_count_with_retry(db, task_id, max_retries=3):
|
|||||||
try:
|
try:
|
||||||
result = await image_task_dao.increment_retry_count(db, task_id)
|
result = await image_task_dao.increment_retry_count(db, task_id)
|
||||||
return result
|
return result
|
||||||
except SerializationError as e:
|
except OperationalError as e:
|
||||||
logger.warning(f"Serialization error incrementing retry count for task {task_id} (attempt {attempt + 1}): {str(e)}")
|
logger.warning(f"Operational error incrementing retry count for task {task_id} (attempt {attempt + 1}): {str(e)}")
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
# Exponential backoff with jitter
|
# Exponential backoff with jitter
|
||||||
import random
|
import random
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ from pydantic_core import from_json
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from backend.app.admin.model import WxUser
|
from backend.app.admin.model import WxUser
|
||||||
from backend.app.admin.schema.wx import GetWxUserInfoWithRelationDetail
|
from backend.app.admin.model.dict import DictCategory
|
||||||
|
from backend.app.admin.schema.wx import GetWxUserInfoWithRelationDetail, DictLevel
|
||||||
|
from backend.app.admin.service.points_service import points_service
|
||||||
from backend.common.dataclasses import AccessToken, NewToken, RefreshToken, TokenPayload
|
from backend.common.dataclasses import AccessToken, NewToken, RefreshToken, TokenPayload
|
||||||
from backend.common.exception import errors
|
from backend.common.exception import errors
|
||||||
from backend.common.exception.errors import TokenError
|
from backend.common.exception.errors import TokenError
|
||||||
@@ -25,6 +27,7 @@ from backend.core.conf import settings
|
|||||||
from backend.database.db import async_db_session
|
from backend.database.db import async_db_session
|
||||||
from backend.database.redis import redis_client
|
from backend.database.redis import redis_client
|
||||||
from backend.utils.serializers import select_as_dict
|
from backend.utils.serializers import select_as_dict
|
||||||
|
from backend.utils.snowflake import snowflake
|
||||||
from backend.utils.timezone import timezone
|
from backend.utils.timezone import timezone
|
||||||
|
|
||||||
|
|
||||||
@@ -337,3 +340,31 @@ async def jwt_authentication(token: str) -> GetWxUserInfoWithRelationDetail:
|
|||||||
# https://docs.pydantic.dev/latest/concepts/json/#partial-json-parsing
|
# https://docs.pydantic.dev/latest/concepts/json/#partial-json-parsing
|
||||||
user = GetWxUserInfoWithRelationDetail.model_validate(from_json(cache_user, allow_partial=True))
|
user = GetWxUserInfoWithRelationDetail.model_validate(from_json(cache_user, allow_partial=True))
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
async def wx_openid_authentication(openid: str, unionid: str) -> GetWxUserInfoWithRelationDetail:
|
||||||
|
from backend.app.admin.crud.wx_user_crud import wx_user_dao
|
||||||
|
async with async_db_session() as db:
|
||||||
|
user = None
|
||||||
|
try:
|
||||||
|
# 查找或创建用户
|
||||||
|
user = await wx_user_dao.get_by_openid(db, openid)
|
||||||
|
if not user:
|
||||||
|
session_key = snowflake.generate()
|
||||||
|
user = WxUser(
|
||||||
|
openid=openid,
|
||||||
|
unionid=unionid,
|
||||||
|
session_key=session_key,
|
||||||
|
profile={
|
||||||
|
'dict_level': DictLevel.LEVEL1.value,
|
||||||
|
'dict_category': DictCategory.GENERAL.value
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await wx_user_dao.add(db, user)
|
||||||
|
await db.flush()
|
||||||
|
await db.refresh(user)
|
||||||
|
await points_service.initialize_user_points(user_id=user.id, db=db)
|
||||||
|
|
||||||
|
return GetWxUserInfoWithRelationDetail(**select_as_dict(user))
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
raise
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from starlette.requests import HTTPConnection
|
|||||||
from backend.app.admin.schema.wx import GetWxUserInfoWithRelationDetail
|
from backend.app.admin.schema.wx import GetWxUserInfoWithRelationDetail
|
||||||
from backend.common.exception.errors import TokenError
|
from backend.common.exception.errors import TokenError
|
||||||
from backend.common.log import log
|
from backend.common.log import log
|
||||||
from backend.common.security.jwt import jwt_authentication
|
from backend.common.security.jwt import jwt_authentication, wx_openid_authentication
|
||||||
from backend.core.conf import settings
|
from backend.core.conf import settings
|
||||||
from backend.utils.serializers import MsgSpecJSONResponse
|
from backend.utils.serializers import MsgSpecJSONResponse
|
||||||
|
|
||||||
@@ -55,6 +55,13 @@ class JwtAuthMiddleware(AuthenticationBackend):
|
|||||||
:param request: FastAPI 请求对象
|
:param request: FastAPI 请求对象
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
wx_openid = request.headers.get('X-WX-OPENID')
|
||||||
|
wx_unionid = request.headers.get('X-WX-UNIONID')
|
||||||
|
if wx_openid:
|
||||||
|
user = await wx_openid_authentication(wx_openid, wx_unionid)
|
||||||
|
if user:
|
||||||
|
return AuthCredentials(['authenticated']), user
|
||||||
|
|
||||||
token = request.headers.get('Authorization')
|
token = request.headers.get('Authorization')
|
||||||
if not token:
|
if not token:
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user