fix column
This commit is contained in:
@@ -10,7 +10,6 @@ RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debi
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends gcc python3-dev supervisor \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
# 某些包可能存在同步不及时导致安装失败的情况,可更改为官方源:https://pypi.org/simple
|
||||
&& 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
|
||||
@@ -27,4 +26,4 @@ COPY deploy/fastapi_server.conf /etc/supervisor/conf.d/
|
||||
|
||||
EXPOSE 8001
|
||||
|
||||
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8001"]
|
||||
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8080"]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from asyncpg.pgproto.pgproto import timedelta
|
||||
from sqlalchemy import select, update, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy_crud_plus import CRUDPlus
|
||||
|
||||
@@ -1,342 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Request, Query
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
from backend.app.ai.schema.article import ArticleSchema, ArticleWithParagraphsSchema, CreateArticleParam, UpdateArticleParam, ArticleParagraphSchema, CreateArticleParagraphParam, UpdateArticleParagraphParam, ArticleSentenceSchema, CreateArticleSentenceParam, UpdateArticleSentenceParam
|
||||
from backend.app.ai.service.article_service import article_service
|
||||
from backend.common.response.response_schema import response_base, ResponseSchemaModel
|
||||
from backend.common.security.jwt import DependsJwtAuth
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("", summary="创建文章", dependencies=[DependsJwtAuth])
|
||||
async def create_article(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
params: CreateArticleParam
|
||||
) -> ResponseSchemaModel[ArticleSchema]:
|
||||
"""
|
||||
创建文章记录
|
||||
|
||||
请求体参数:
|
||||
- title: 文章标题
|
||||
- content: 文章完整内容
|
||||
- author: 作者(可选)
|
||||
- category: 分类(可选)
|
||||
- level: 难度等级(可选)
|
||||
- info: 附加信息(可选)
|
||||
|
||||
返回:
|
||||
- 创建的文章记录
|
||||
"""
|
||||
article_id = await article_service.create_article(obj=params)
|
||||
article = await article_service.get_article_by_id(article_id)
|
||||
|
||||
return response_base.success(data=article)
|
||||
|
||||
|
||||
@router.get("/{article_id}", summary="获取文章详情", dependencies=[DependsJwtAuth])
|
||||
async def get_article(
|
||||
article_id: int
|
||||
) -> ResponseSchemaModel[ArticleWithParagraphsSchema]:
|
||||
"""
|
||||
获取文章详情,包括所有段落和句子
|
||||
|
||||
参数:
|
||||
- article_id: 文章ID
|
||||
|
||||
返回:
|
||||
- 文章记录及其所有段落和句子
|
||||
"""
|
||||
article = await article_service.get_article_with_content(article_id)
|
||||
if not article:
|
||||
return response_base.fail(code=404, msg="文章不存在")
|
||||
|
||||
return response_base.success(data=article)
|
||||
|
||||
|
||||
@router.put("/{article_id}", summary="更新文章", dependencies=[DependsJwtAuth])
|
||||
async def update_article(
|
||||
article_id: int,
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
params: UpdateArticleParam
|
||||
) -> ResponseSchemaModel[ArticleSchema]:
|
||||
"""
|
||||
更新文章记录
|
||||
|
||||
参数:
|
||||
- article_id: 文章ID
|
||||
|
||||
请求体参数:
|
||||
- title: 文章标题
|
||||
- content: 文章完整内容
|
||||
- author: 作者(可选)
|
||||
- category: 分类(可选)
|
||||
- level: 难度等级(可选)
|
||||
- info: 附加信息(可选)
|
||||
|
||||
返回:
|
||||
- 更新后的文章记录
|
||||
"""
|
||||
success = await article_service.update_article(article_id, params)
|
||||
if not success:
|
||||
return response_base.fail(code=404, msg="文章不存在")
|
||||
|
||||
article = await article_service.get_article_by_id(article_id)
|
||||
return response_base.success(data=article)
|
||||
|
||||
|
||||
@router.delete("/{article_id}", summary="删除文章", dependencies=[DependsJwtAuth])
|
||||
async def delete_article(
|
||||
article_id: int,
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks
|
||||
) -> ResponseSchemaModel[None]:
|
||||
"""
|
||||
删除文章记录
|
||||
|
||||
参数:
|
||||
- article_id: 文章ID
|
||||
|
||||
返回:
|
||||
- 无
|
||||
"""
|
||||
success = await article_service.delete_article(article_id)
|
||||
if not success:
|
||||
return response_base.fail(code=404, msg="文章不存在")
|
||||
|
||||
return response_base.success()
|
||||
|
||||
|
||||
@router.post("/paragraph", summary="创建文章段落", dependencies=[DependsJwtAuth])
|
||||
async def create_article_paragraph(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
params: CreateArticleParagraphParam
|
||||
) -> ResponseSchemaModel[ArticleParagraphSchema]:
|
||||
"""
|
||||
创建文章段落记录
|
||||
|
||||
请求体参数:
|
||||
- article_id: 关联的文章ID
|
||||
- paragraph_index: 段落序号
|
||||
- content: 段落内容
|
||||
- standard_audio_id: 标准朗读音频文件ID(可选)
|
||||
- info: 附加信息(可选)
|
||||
|
||||
返回:
|
||||
- 创建的段落记录
|
||||
"""
|
||||
paragraph_id = await article_service.create_article_paragraph(obj=params)
|
||||
paragraph = await article_service.get_article_paragraph_by_id(paragraph_id)
|
||||
|
||||
return response_base.success(data=paragraph)
|
||||
|
||||
|
||||
@router.put("/paragraph/{paragraph_id}", summary="更新文章段落", dependencies=[DependsJwtAuth])
|
||||
async def update_article_paragraph(
|
||||
paragraph_id: int,
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
params: UpdateArticleParagraphParam
|
||||
) -> ResponseSchemaModel[ArticleParagraphSchema]:
|
||||
"""
|
||||
更新文章段落记录
|
||||
|
||||
参数:
|
||||
- paragraph_id: 段落ID
|
||||
|
||||
请求体参数:
|
||||
- article_id: 关联的文章ID
|
||||
- paragraph_index: 段落序号
|
||||
- content: 段落内容
|
||||
- standard_audio_id: 标准朗读音频文件ID(可选)
|
||||
- info: 附加信息(可选)
|
||||
|
||||
返回:
|
||||
- 更新后的段落记录
|
||||
"""
|
||||
success = await article_service.update_article_paragraph(paragraph_id, params)
|
||||
if not success:
|
||||
return response_base.fail(code=404, msg="段落不存在")
|
||||
|
||||
paragraph = await article_service.get_article_paragraph_by_id(paragraph_id)
|
||||
return response_base.success(data=paragraph)
|
||||
|
||||
|
||||
@router.delete("/paragraph/{paragraph_id}", summary="删除文章段落", dependencies=[DependsJwtAuth])
|
||||
async def delete_article_paragraph(
|
||||
paragraph_id: int,
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks
|
||||
) -> ResponseSchemaModel[None]:
|
||||
"""
|
||||
删除文章段落记录
|
||||
|
||||
参数:
|
||||
- paragraph_id: 段落ID
|
||||
|
||||
返回:
|
||||
- 无
|
||||
"""
|
||||
success = await article_service.delete_article_paragraph(paragraph_id)
|
||||
if not success:
|
||||
return response_base.fail(code=404, msg="段落不存在")
|
||||
|
||||
return response_base.success()
|
||||
|
||||
|
||||
@router.get("/paragraph/{paragraph_id}", summary="获取段落详情", dependencies=[DependsJwtAuth])
|
||||
async def get_article_paragraph(
|
||||
paragraph_id: int
|
||||
) -> ResponseSchemaModel[ArticleParagraphSchema]:
|
||||
"""
|
||||
获取文章段落详情
|
||||
|
||||
参数:
|
||||
- paragraph_id: 段落ID
|
||||
|
||||
返回:
|
||||
- 段落记录
|
||||
"""
|
||||
paragraph = await article_service.get_article_paragraph_by_id(paragraph_id)
|
||||
if not paragraph:
|
||||
return response_base.fail(code=404, msg="段落不存在")
|
||||
|
||||
return response_base.success(data=paragraph)
|
||||
|
||||
|
||||
@router.get("/paragraph", summary="获取文章的所有段落", dependencies=[DependsJwtAuth])
|
||||
async def get_article_paragraphs(
|
||||
article_id: int = Query(..., description="文章ID")
|
||||
) -> ResponseSchemaModel[List[ArticleParagraphSchema]]:
|
||||
"""
|
||||
获取指定文章的所有段落记录
|
||||
|
||||
参数:
|
||||
- article_id: 文章ID
|
||||
|
||||
返回:
|
||||
- 文章的所有段落记录列表
|
||||
"""
|
||||
paragraphs = await article_service.get_article_paragraphs_by_article_id(article_id)
|
||||
return response_base.success(data=paragraphs)
|
||||
|
||||
|
||||
@router.post("/sentence", summary="创建文章句子", dependencies=[DependsJwtAuth])
|
||||
async def create_article_sentence(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
params: CreateArticleSentenceParam
|
||||
) -> ResponseSchemaModel[ArticleSentenceSchema]:
|
||||
"""
|
||||
创建文章句子记录
|
||||
|
||||
请求体参数:
|
||||
- paragraph_id: 关联的段落ID
|
||||
- sentence_index: 句子序号
|
||||
- content: 句子内容
|
||||
- standard_audio_id: 标准朗读音频文件ID(可选)
|
||||
- info: 附加信息(可选)
|
||||
|
||||
返回:
|
||||
- 创建的句子记录
|
||||
"""
|
||||
sentence_id = await article_service.create_article_sentence(obj=params)
|
||||
sentence = await article_service.get_article_sentence_by_id(sentence_id)
|
||||
|
||||
return response_base.success(data=sentence)
|
||||
|
||||
|
||||
@router.put("/sentence/{sentence_id}", summary="更新文章句子", dependencies=[DependsJwtAuth])
|
||||
async def update_article_sentence(
|
||||
sentence_id: int,
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
params: UpdateArticleSentenceParam
|
||||
) -> ResponseSchemaModel[ArticleSentenceSchema]:
|
||||
"""
|
||||
更新文章句子记录
|
||||
|
||||
参数:
|
||||
- sentence_id: 句子ID
|
||||
|
||||
请求体参数:
|
||||
- paragraph_id: 关联的段落ID
|
||||
- sentence_index: 句子序号
|
||||
- content: 句子内容
|
||||
- standard_audio_id: 标准朗读音频文件ID(可选)
|
||||
- info: 附加信息(可选)
|
||||
|
||||
返回:
|
||||
- 更新后的句子记录
|
||||
"""
|
||||
success = await article_service.update_article_sentence(sentence_id, params)
|
||||
if not success:
|
||||
return response_base.fail(code=404, msg="句子不存在")
|
||||
|
||||
sentence = await article_service.get_article_sentence_by_id(sentence_id)
|
||||
return response_base.success(data=sentence)
|
||||
|
||||
|
||||
@router.delete("/sentence/{sentence_id}", summary="删除文章句子", dependencies=[DependsJwtAuth])
|
||||
async def delete_article_sentence(
|
||||
sentence_id: int,
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks
|
||||
) -> ResponseSchemaModel[None]:
|
||||
"""
|
||||
删除文章句子记录
|
||||
|
||||
参数:
|
||||
- sentence_id: 句子ID
|
||||
|
||||
返回:
|
||||
- 无
|
||||
"""
|
||||
success = await article_service.delete_article_sentence(sentence_id)
|
||||
if not success:
|
||||
return response_base.fail(code=404, msg="句子不存在")
|
||||
|
||||
return response_base.success()
|
||||
|
||||
|
||||
@router.get("/sentence/{sentence_id}", summary="获取句子详情", dependencies=[DependsJwtAuth])
|
||||
async def get_article_sentence(
|
||||
sentence_id: int
|
||||
) -> ResponseSchemaModel[ArticleSentenceSchema]:
|
||||
"""
|
||||
获取文章句子详情
|
||||
|
||||
参数:
|
||||
- sentence_id: 句子ID
|
||||
|
||||
返回:
|
||||
- 句子记录
|
||||
"""
|
||||
sentence = await article_service.get_article_sentence_by_id(sentence_id)
|
||||
if not sentence:
|
||||
return response_base.fail(code=404, msg="句子不存在")
|
||||
|
||||
return response_base.success(data=sentence)
|
||||
|
||||
|
||||
@router.get("/sentence", summary="获取段落的所有句子", dependencies=[DependsJwtAuth])
|
||||
async def get_article_sentences(
|
||||
paragraph_id: int = Query(..., description="段落ID")
|
||||
) -> ResponseSchemaModel[List[ArticleSentenceSchema]]:
|
||||
"""
|
||||
获取指定段落的所有句子记录
|
||||
|
||||
参数:
|
||||
- paragraph_id: 段落ID
|
||||
|
||||
返回:
|
||||
- 段落的所有句子记录列表
|
||||
"""
|
||||
sentences = await article_service.get_article_sentences_by_paragraph_id(paragraph_id)
|
||||
return response_base.success(data=sentences)
|
||||
@@ -5,12 +5,10 @@ from fastapi import APIRouter
|
||||
from backend.app.ai.api.image import router as image_router
|
||||
from backend.app.ai.api.recording import router as recording_router
|
||||
from backend.app.ai.api.image_text import router as image_text_router
|
||||
from backend.app.ai.api.article import router as article_router
|
||||
from backend.core.conf import settings
|
||||
|
||||
v1 = APIRouter(prefix=settings.FASTAPI_API_V1_PATH)
|
||||
|
||||
v1.include_router(image_router, prefix='/image', tags=['AI图片服务'])
|
||||
v1.include_router(recording_router, prefix='/recording', tags=['AI录音服务'])
|
||||
v1.include_router(image_text_router, prefix='/image_text', tags=['AI图片文本服务'])
|
||||
# v1.include_router(article_router, prefix='/article', tags=['AI文章服务'])
|
||||
v1.include_router(image_text_router, prefix='/image_text', tags=['AI图片文本服务'])
|
||||
@@ -1,65 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import Optional, List
|
||||
from sqlalchemy import select, and_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy_crud_plus import CRUDPlus
|
||||
|
||||
from backend.app.ai.model.article import Article, ArticleParagraph, ArticleSentence
|
||||
|
||||
|
||||
class CRUDArticle(CRUDPlus[Article]):
|
||||
async def get_by_title(self, db: AsyncSession, title: str) -> Optional[Article]:
|
||||
"""根据标题获取文章"""
|
||||
stmt = select(self.model).where(self.model.title == title)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_articles_by_category(self, db: AsyncSession, category: str) -> List[Article]:
|
||||
"""根据分类获取文章列表"""
|
||||
stmt = select(self.model).where(self.model.category == category)
|
||||
result = await db.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
class CRUDArticleParagraph(CRUDPlus[ArticleParagraph]):
|
||||
async def get_by_article_id(self, db: AsyncSession, article_id: int) -> List[ArticleParagraph]:
|
||||
"""根据文章ID获取所有段落,按序号排序"""
|
||||
stmt = select(self.model).where(self.model.article_id == article_id).order_by(self.model.paragraph_index)
|
||||
result = await db.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def get_by_article_id_and_index(self, db: AsyncSession, article_id: int, paragraph_index: int) -> Optional[ArticleParagraph]:
|
||||
"""根据文章ID和段落序号获取段落"""
|
||||
stmt = select(self.model).where(
|
||||
and_(
|
||||
self.model.article_id == article_id,
|
||||
self.model.paragraph_index == paragraph_index
|
||||
)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
class CRUDArticleSentence(CRUDPlus[ArticleSentence]):
|
||||
async def get_by_paragraph_id(self, db: AsyncSession, paragraph_id: int) -> List[ArticleSentence]:
|
||||
"""根据段落ID获取所有句子,按序号排序"""
|
||||
stmt = select(self.model).where(self.model.paragraph_id == paragraph_id).order_by(self.model.sentence_index)
|
||||
result = await db.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def get_by_paragraph_id_and_index(self, db: AsyncSession, paragraph_id: int, sentence_index: int) -> Optional[ArticleSentence]:
|
||||
"""根据段落ID和句子序号获取句子"""
|
||||
stmt = select(self.model).where(
|
||||
and_(
|
||||
self.model.paragraph_id == paragraph_id,
|
||||
self.model.sentence_index == sentence_index
|
||||
)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
article_dao: CRUDArticle = CRUDArticle(Article)
|
||||
article_paragraph_dao: CRUDArticleParagraph = CRUDArticleParagraph(ArticleParagraph)
|
||||
article_sentence_dao: CRUDArticleSentence = CRUDArticleSentence(ArticleSentence)
|
||||
@@ -1,68 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import BigInteger, Text, String, DateTime, ForeignKey
|
||||
from sqlalchemy.dialects.mysql import JSON as MySQLJSON
|
||||
from sqlalchemy.orm import mapped_column, Mapped
|
||||
|
||||
from backend.common.model import snowflake_id_key, Base
|
||||
|
||||
|
||||
class Article(Base):
|
||||
"""
|
||||
文章内容
|
||||
用于存储朗读文章的完整内容,支持多段落结构
|
||||
"""
|
||||
__tablename__ = 'article'
|
||||
|
||||
id: Mapped[snowflake_id_key] = mapped_column(BigInteger, init=False, primary_key=True)
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False, comment="文章标题")
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False, comment="文章完整内容")
|
||||
author: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment="作者")
|
||||
category: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, comment="分类")
|
||||
level: Mapped[Optional[str]] = mapped_column(String(20), nullable=True, comment="难度等级")
|
||||
info: Mapped[Optional[dict]] = mapped_column(MySQLJSON, default=None, comment="附加信息")
|
||||
|
||||
# 表参数 - 包含所有必要的约束
|
||||
__table_args__ = (
|
||||
)
|
||||
|
||||
|
||||
class ArticleParagraph(Base):
|
||||
"""
|
||||
文章段落
|
||||
用于存储文章中的各个段落,支持段落级别的朗读和评估
|
||||
"""
|
||||
__tablename__ = 'article_paragraph'
|
||||
|
||||
id: Mapped[snowflake_id_key] = mapped_column(BigInteger, init=False, primary_key=True)
|
||||
article_id: Mapped[int] = mapped_column(BigInteger, ForeignKey('article.id'), nullable=False, comment="关联的文章ID")
|
||||
paragraph_index: Mapped[int] = mapped_column(BigInteger, nullable=False, comment="段落序号")
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False, comment="段落内容")
|
||||
standard_audio_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey('file.id'), nullable=True, comment="标准朗读音频文件ID")
|
||||
info: Mapped[Optional[dict]] = mapped_column(MySQLJSON, default=None, comment="附加信息")
|
||||
|
||||
# 表参数 - 包含所有必要的约束
|
||||
__table_args__ = (
|
||||
)
|
||||
|
||||
|
||||
class ArticleSentence(Base):
|
||||
"""
|
||||
文章句子
|
||||
用于存储段落中的各个句子,支持句子级别的朗读和评估
|
||||
"""
|
||||
__tablename__ = 'article_sentence'
|
||||
|
||||
id: Mapped[snowflake_id_key] = mapped_column(BigInteger, init=False, primary_key=True)
|
||||
paragraph_id: Mapped[int] = mapped_column(BigInteger, ForeignKey('article_paragraph.id'), nullable=False, comment="关联的段落ID")
|
||||
sentence_index: Mapped[int] = mapped_column(BigInteger, nullable=False, comment="句子序号")
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False, comment="句子内容")
|
||||
standard_audio_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey('file.id'), nullable=True, comment="标准朗读音频文件ID")
|
||||
info: Mapped[Optional[dict]] = mapped_column(MySQLJSON, default=None, comment="附加信息")
|
||||
|
||||
# 表参数 - 包含所有必要的约束
|
||||
__table_args__ = (
|
||||
)
|
||||
@@ -19,7 +19,6 @@ class ImageText(Base):
|
||||
|
||||
id: Mapped[snowflake_id_key] = mapped_column(BigInteger, init=False, primary_key=True)
|
||||
image_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey('image.id'), nullable=True, comment="关联的图片ID")
|
||||
article_sentence_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey('article_sentence.id'), nullable=True, comment="关联的文章句子ID")
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False, comment="文本内容")
|
||||
standard_audio_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey('file.id'), nullable=True, comment="标准朗读音频文件ID")
|
||||
ipa: Mapped[Optional[str]] = mapped_column(String(100), default=None, comment="ipa")
|
||||
|
||||
@@ -20,7 +20,6 @@ class Recording(Base):
|
||||
user_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey('wx_user.id'), nullable=True, comment="关联的用户ID")
|
||||
image_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey('image.id'), nullable=True, comment="关联的图片ID")
|
||||
image_text_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey('image_text.id'), nullable=True, comment="关联的图片文本ID")
|
||||
article_sentence_id: Mapped[Optional[int]] = mapped_column(BigInteger, ForeignKey('article_sentence.id'), nullable=True, comment="关联的文章句子ID")
|
||||
text: Mapped[Optional[str]] = mapped_column(String(255), nullable=True, comment='朗读文本')
|
||||
eval_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True, comment='评测模式')
|
||||
info: Mapped[Optional[RecordingMetadata]] = mapped_column(PydanticType(pydantic_type=RecordingMetadata), default=None, comment="附加元数据") # 其他可能的字段(根据实际需求添加)
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
from typing import Optional, Dict, Any, List
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.common.schema import SchemaBase
|
||||
|
||||
|
||||
class ArticleSchemaBase(SchemaBase):
|
||||
"""文章基础结构"""
|
||||
title: str
|
||||
content: str
|
||||
author: Optional[str] = None
|
||||
category: Optional[str] = None
|
||||
level: Optional[str] = None
|
||||
info: Optional[dict] = None
|
||||
|
||||
|
||||
class CreateArticleParam(ArticleSchemaBase):
|
||||
"""创建文章参数"""
|
||||
|
||||
|
||||
class UpdateArticleParam(ArticleSchemaBase):
|
||||
"""更新文章参数"""
|
||||
|
||||
|
||||
class ArticleSchema(ArticleSchemaBase):
|
||||
"""文章信息"""
|
||||
id: int
|
||||
created_time: Optional[str] = None
|
||||
updated_time: Optional[str] = None
|
||||
|
||||
|
||||
class ArticleParagraphSchemaBase(SchemaBase):
|
||||
"""文章段落基础结构"""
|
||||
article_id: int
|
||||
paragraph_index: int
|
||||
content: str
|
||||
standard_audio_id: Optional[int] = None
|
||||
info: Optional[dict] = None
|
||||
|
||||
|
||||
class CreateArticleParagraphParam(ArticleParagraphSchemaBase):
|
||||
"""创建文章段落参数"""
|
||||
|
||||
|
||||
class UpdateArticleParagraphParam(ArticleParagraphSchemaBase):
|
||||
"""更新文章段落参数"""
|
||||
|
||||
|
||||
class ArticleParagraphSchema(ArticleParagraphSchemaBase):
|
||||
"""文章段落信息"""
|
||||
id: int
|
||||
created_time: Optional[str] = None
|
||||
|
||||
|
||||
class ArticleSentenceSchemaBase(SchemaBase):
|
||||
"""文章句子基础结构"""
|
||||
paragraph_id: int
|
||||
sentence_index: int
|
||||
content: str
|
||||
standard_audio_id: Optional[int] = None
|
||||
info: Optional[dict] = None
|
||||
|
||||
|
||||
class CreateArticleSentenceParam(ArticleSentenceSchemaBase):
|
||||
"""创建文章句子参数"""
|
||||
|
||||
|
||||
class UpdateArticleSentenceParam(ArticleSentenceSchemaBase):
|
||||
"""更新文章句子参数"""
|
||||
|
||||
|
||||
class ArticleSentenceSchema(ArticleSentenceSchemaBase):
|
||||
"""文章句子信息"""
|
||||
id: int
|
||||
created_time: Optional[str] = None
|
||||
|
||||
|
||||
class ArticleWithParagraphsSchema(ArticleSchema):
|
||||
"""包含段落的文章信息"""
|
||||
paragraphs: Optional[List['ArticleParagraphWithSentencesSchema']] = None
|
||||
|
||||
|
||||
class ArticleParagraphWithSentencesSchema(ArticleParagraphSchema):
|
||||
"""包含句子的段落信息"""
|
||||
sentences: Optional[List[ArticleSentenceSchema]] = None
|
||||
|
||||
|
||||
# Update forward references
|
||||
ArticleWithParagraphsSchema.model_rebuild()
|
||||
ArticleParagraphWithSentencesSchema.model_rebuild()
|
||||
@@ -1,215 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from typing import Optional, List
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from backend.app.ai.crud.article_crud import article_dao, article_paragraph_dao, article_sentence_dao
|
||||
from backend.app.ai.model.article import Article, ArticleParagraph, ArticleSentence
|
||||
from backend.app.ai.schema.article import CreateArticleParam, UpdateArticleParam, CreateArticleParagraphParam, UpdateArticleParagraphParam, CreateArticleSentenceParam, UpdateArticleSentenceParam
|
||||
from backend.database.db import async_db_session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ArticleService:
|
||||
@staticmethod
|
||||
async def create_article(*, obj: CreateArticleParam) -> int:
|
||||
"""
|
||||
创建文章
|
||||
|
||||
:param obj: 文章创建参数
|
||||
:return: 文章ID
|
||||
"""
|
||||
async with async_db_session.begin() as db:
|
||||
article = await article_dao.create(db, obj)
|
||||
return article.id
|
||||
|
||||
@staticmethod
|
||||
async def get_article_by_id(article_id: int) -> Optional[Article]:
|
||||
"""
|
||||
根据ID获取文章
|
||||
|
||||
:param article_id: 文章ID
|
||||
:return: 文章记录
|
||||
"""
|
||||
async with async_db_session() as db:
|
||||
return await article_dao.get(db, article_id)
|
||||
|
||||
@staticmethod
|
||||
async def get_article_with_content(article_id: int) -> Optional[dict]:
|
||||
"""
|
||||
获取文章及其所有段落和句子
|
||||
|
||||
:param article_id: 文章ID
|
||||
:return: 包含段落和句子的文章信息
|
||||
"""
|
||||
async with async_db_session() as db:
|
||||
article = await article_dao.get(db, article_id)
|
||||
if not article:
|
||||
return None
|
||||
|
||||
# 获取所有段落
|
||||
paragraphs = await article_paragraph_dao.get_by_article_id(db, article_id)
|
||||
|
||||
# 获取所有句子
|
||||
paragraph_ids = [p.id for p in paragraphs]
|
||||
if paragraph_ids:
|
||||
stmt = select(ArticleSentence).where(ArticleSentence.paragraph_id.in_(paragraph_ids)).order_by(ArticleSentence.paragraph_id, ArticleSentence.sentence_index)
|
||||
result = await db.execute(stmt)
|
||||
sentences = list(result.scalars().all())
|
||||
|
||||
# 将句子按段落分组
|
||||
sentences_by_paragraph = {}
|
||||
for sentence in sentences:
|
||||
if sentence.paragraph_id not in sentences_by_paragraph:
|
||||
sentences_by_paragraph[sentence.paragraph_id] = []
|
||||
sentences_by_paragraph[sentence.paragraph_id].append(sentence)
|
||||
|
||||
# 将句子添加到对应的段落中
|
||||
for paragraph in paragraphs:
|
||||
paragraph.sentences = sentences_by_paragraph.get(paragraph.id, [])
|
||||
|
||||
article.paragraphs = paragraphs
|
||||
return article
|
||||
|
||||
@staticmethod
|
||||
async def update_article(article_id: int, obj: UpdateArticleParam) -> bool:
|
||||
"""
|
||||
更新文章
|
||||
|
||||
:param article_id: 文章ID
|
||||
:param obj: 文章更新参数
|
||||
:return: 是否更新成功
|
||||
"""
|
||||
async with async_db_session.begin() as db:
|
||||
return await article_dao.update(db, article_id, obj)
|
||||
|
||||
@staticmethod
|
||||
async def delete_article(article_id: int) -> bool:
|
||||
"""
|
||||
删除文章
|
||||
|
||||
:param article_id: 文章ID
|
||||
:return: 是否删除成功
|
||||
"""
|
||||
async with async_db_session.begin() as db:
|
||||
return await article_dao.delete(db, article_id)
|
||||
|
||||
@staticmethod
|
||||
async def create_article_paragraph(*, obj: CreateArticleParagraphParam) -> int:
|
||||
"""
|
||||
创建文章段落
|
||||
|
||||
:param obj: 段落创建参数
|
||||
:return: 段落ID
|
||||
"""
|
||||
async with async_db_session.begin() as db:
|
||||
paragraph = await article_paragraph_dao.create(db, obj)
|
||||
return paragraph.id
|
||||
|
||||
@staticmethod
|
||||
async def get_article_paragraph_by_id(paragraph_id: int) -> Optional[ArticleParagraph]:
|
||||
"""
|
||||
根据ID获取文章段落
|
||||
|
||||
:param paragraph_id: 段落ID
|
||||
:return: 段落记录
|
||||
"""
|
||||
async with async_db_session() as db:
|
||||
return await article_paragraph_dao.get(db, paragraph_id)
|
||||
|
||||
@staticmethod
|
||||
async def get_article_paragraphs_by_article_id(article_id: int) -> List[ArticleParagraph]:
|
||||
"""
|
||||
根据文章ID获取所有段落
|
||||
|
||||
:param article_id: 文章ID
|
||||
:return: 段落记录列表
|
||||
"""
|
||||
async with async_db_session() as db:
|
||||
return await article_paragraph_dao.get_by_article_id(db, article_id)
|
||||
|
||||
@staticmethod
|
||||
async def update_article_paragraph(paragraph_id: int, obj: UpdateArticleParagraphParam) -> bool:
|
||||
"""
|
||||
更新文章段落
|
||||
|
||||
:param paragraph_id: 段落ID
|
||||
:param obj: 段落更新参数
|
||||
:return: 是否更新成功
|
||||
"""
|
||||
async with async_db_session.begin() as db:
|
||||
return await article_paragraph_dao.update(db, paragraph_id, obj)
|
||||
|
||||
@staticmethod
|
||||
async def delete_article_paragraph(paragraph_id: int) -> bool:
|
||||
"""
|
||||
删除文章段落
|
||||
|
||||
:param paragraph_id: 段落ID
|
||||
:return: 是否删除成功
|
||||
"""
|
||||
async with async_db_session.begin() as db:
|
||||
return await article_paragraph_dao.delete(db, paragraph_id)
|
||||
|
||||
@staticmethod
|
||||
async def create_article_sentence(*, obj: CreateArticleSentenceParam) -> int:
|
||||
"""
|
||||
创建文章句子
|
||||
|
||||
:param obj: 句子创建参数
|
||||
:return: 句子ID
|
||||
"""
|
||||
async with async_db_session.begin() as db:
|
||||
sentence = await article_sentence_dao.create(db, obj)
|
||||
return sentence.id
|
||||
|
||||
@staticmethod
|
||||
async def get_article_sentence_by_id(sentence_id: int) -> Optional[ArticleSentence]:
|
||||
"""
|
||||
根据ID获取文章句子
|
||||
|
||||
:param sentence_id: 句子ID
|
||||
:return: 句子记录
|
||||
"""
|
||||
async with async_db_session() as db:
|
||||
return await article_sentence_dao.get(db, sentence_id)
|
||||
|
||||
@staticmethod
|
||||
async def get_article_sentences_by_paragraph_id(paragraph_id: int) -> List[ArticleSentence]:
|
||||
"""
|
||||
根据段落ID获取所有句子
|
||||
|
||||
:param paragraph_id: 段落ID
|
||||
:return: 句子记录列表
|
||||
"""
|
||||
async with async_db_session() as db:
|
||||
return await article_sentence_dao.get_by_paragraph_id(db, paragraph_id)
|
||||
|
||||
@staticmethod
|
||||
async def update_article_sentence(sentence_id: int, obj: UpdateArticleSentenceParam) -> bool:
|
||||
"""
|
||||
更新文章句子
|
||||
|
||||
:param sentence_id: 句子ID
|
||||
:param obj: 句子更新参数
|
||||
:return: 是否更新成功
|
||||
"""
|
||||
async with async_db_session.begin() as db:
|
||||
return await article_sentence_dao.update(db, sentence_id, obj)
|
||||
|
||||
@staticmethod
|
||||
async def delete_article_sentence(sentence_id: int) -> bool:
|
||||
"""
|
||||
删除文章句子
|
||||
|
||||
:param sentence_id: 句子ID
|
||||
:return: 是否删除成功
|
||||
"""
|
||||
async with async_db_session.begin() as db:
|
||||
return await article_sentence_dao.delete(db, sentence_id)
|
||||
|
||||
|
||||
article_service = ArticleService()
|
||||
@@ -195,7 +195,6 @@ class ImageTextService:
|
||||
# 创建新的文本记录
|
||||
new_text = ImageText(
|
||||
image_id=image_id,
|
||||
article_sentence_id=None,
|
||||
content=text_content,
|
||||
standard_audio_id=None,
|
||||
source=source,
|
||||
|
||||
@@ -256,7 +256,6 @@ class RecordingService:
|
||||
file_id=file_id,
|
||||
image_id=image_id,
|
||||
image_text_id=image_text_id,
|
||||
article_sentence_id=None,
|
||||
eval_mode=eval_mode,
|
||||
info=metadata,
|
||||
text=ref_text,
|
||||
@@ -288,7 +287,6 @@ class RecordingService:
|
||||
file_id=file_id,
|
||||
image_id=image_id,
|
||||
image_text_id=image_text_id,
|
||||
article_sentence_id=None,
|
||||
eval_mode=eval_mode,
|
||||
info=metadata,
|
||||
text=ref_text,
|
||||
|
||||
9
deploy/app_server.conf
Executable file
9
deploy/app_server.conf
Executable file
@@ -0,0 +1,9 @@
|
||||
[program:app_server]
|
||||
directory=/app
|
||||
command=/usr/local/bin/gunicorn -c /app/deploy/gunicorn.conf.py main:app
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=5
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/app_server/app_server.log
|
||||
@@ -1,14 +1,28 @@
|
||||
# Env: dev、pro
|
||||
ENVIRONMENT='dev'
|
||||
# MySQL
|
||||
DATABASE_HOST='fsm_mysql'
|
||||
ENVIRONMENT='prod'
|
||||
# Database
|
||||
DATABASE_HOST='127.0.0.1'
|
||||
DATABASE_PORT=3306
|
||||
DATABASE_USER='root'
|
||||
DATABASE_PASSWORD='123456'
|
||||
DATABASE_USER='app'
|
||||
DATABASE_PASSWORD='Im_614000'
|
||||
DATABASE_DB_NAME='app'
|
||||
# Redis
|
||||
REDIS_HOST='fsm_redis'
|
||||
REDIS_HOST='127.0.0.1'
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=''
|
||||
REDIS_DATABASE=0
|
||||
# Token
|
||||
TOKEN_SECRET_KEY='1VkVF75nsNABBjK_7-qz7GtzNy3AMvktc9TCPwKczCk'
|
||||
# model
|
||||
QWEN_API_KEY='sk-901901c68d6e44359ed31e9c59f6c8f9'
|
||||
#QWEN_VISION_MODEL='qwen-vl-max-latest'
|
||||
QWEN_VISION_MODEL='qwen-vl-plus'
|
||||
QWEN_VISION_EMBEDDING_MODEL='multimodal-embedding-v1'
|
||||
TENCENT_CLOUD_APP_ID='1251798270'
|
||||
TENCENT_CLOUD_SECRET_ID='AKIDZ3CIFMVcadaRCrQr9HfLxRdlhYG0b2MX'
|
||||
TENCENT_CLOUD_SECRET_KEY='51oZRMWirvsgLqCORK6kFdOPMAylakqw'
|
||||
TENCENT_CLOUD_VOICE_MODEL_TYPE='501009'
|
||||
YOUDAO_APP_ID='638c41df17d15cd6'
|
||||
YOUDAO_APP_SECRET='VFicuogEnlQWaPPf4maGPnO1IztVzxVh'
|
||||
WX_APPID='wxe739c0e6fb02eda8'
|
||||
WX_SECRET='f0f56b636adf6baa9832cfcd4cb3276b'
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
fsm_server:
|
||||
app_server:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8000:8000"
|
||||
container_name: fsm_server
|
||||
- "8080:8000"
|
||||
container_name: app_server
|
||||
restart: always
|
||||
depends_on:
|
||||
- fsm_mysql
|
||||
- fsm_redis
|
||||
- app_mysql
|
||||
- app_redis
|
||||
volumes:
|
||||
- fsm_static:/www/fsm_server/backend/static
|
||||
- app_static:/www/app_server/backend/static
|
||||
environment:
|
||||
- SERVER_HOST=0.0.0.0
|
||||
- SERVER_PORT=8000
|
||||
- DATABASE_HOST=fsm_mysql
|
||||
- DATABASE_HOST=app_mysql
|
||||
- DATABASE_PORT=3306
|
||||
- DATABASE_USER=root
|
||||
- DATABASE_PASSWORD=123456
|
||||
- DATABASE_DB_NAME=fsm
|
||||
- REDIS_HOST=fsm_redis
|
||||
- DATABASE_PASSWORD=Im_614000
|
||||
- DATABASE_DB_NAME=app
|
||||
- REDIS_HOST=app_redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=
|
||||
- REDIS_DATABASE=0
|
||||
networks:
|
||||
- fsm_network
|
||||
- app_network
|
||||
command: |
|
||||
sh -c "
|
||||
wait-for-it -s fsm_mysql:3306 -s fsm_redis:6379 -t 300
|
||||
wait-for-it -s app_mysql:3306 -s app_redis:6379 -t 300
|
||||
supervisord -c /etc/supervisor/supervisord.conf
|
||||
supervisorctl restart
|
||||
"
|
||||
|
||||
fsm_mysql:
|
||||
image: mysql:8.0.29
|
||||
app_mysql:
|
||||
image: mysql:8.0.44
|
||||
ports:
|
||||
- "3306:3306"
|
||||
container_name: fsm_mysql
|
||||
container_name: app_mysql
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_DATABASE: fsm
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
MYSQL_DATABASE: app
|
||||
MYSQL_ROOT_PASSWORD: Im_614000
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- fsm_mysql:/var/lib/mysql
|
||||
- app_mysql:/var/lib/mysql
|
||||
networks:
|
||||
- fsm_network
|
||||
- app_network
|
||||
command:
|
||||
--default-authentication-plugin=mysql_native_password
|
||||
--character-set-server=utf8mb4
|
||||
--collation-server=utf8mb4_general_ci
|
||||
--lower_case_table_names=1
|
||||
|
||||
fsm_redis:
|
||||
image: redis:7.0.4
|
||||
app_redis:
|
||||
image: redis:8.4.0-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
container_name: fsm_redis
|
||||
container_name: app_redis
|
||||
restart: always
|
||||
volumes:
|
||||
- fsm_redis:/data
|
||||
- app_redis:/data
|
||||
networks:
|
||||
- fsm_network
|
||||
- app_network
|
||||
command: |
|
||||
--requirepass ""
|
||||
--appendonly yes
|
||||
@@ -75,23 +75,23 @@ services:
|
||||
--maxmemory 256mb
|
||||
--maxmemory-policy allkeys-lru
|
||||
|
||||
fsm_nginx:
|
||||
app_nginx:
|
||||
image: nginx:stable
|
||||
ports:
|
||||
- "8000:80"
|
||||
container_name: fsm_nginx
|
||||
container_name: app_nginx
|
||||
restart: always
|
||||
depends_on:
|
||||
- fsm_server
|
||||
- app_server
|
||||
volumes:
|
||||
- ../nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- fsm_static:/www/fsm_server/backend/static
|
||||
- app_static:/www/app_server/backend/static
|
||||
networks:
|
||||
- fsm_network
|
||||
- app_network
|
||||
|
||||
networks:
|
||||
fsm_network:
|
||||
name: fsm_network
|
||||
app_network:
|
||||
name: app_network
|
||||
driver: bridge
|
||||
ipam:
|
||||
driver: default
|
||||
@@ -99,9 +99,9 @@ networks:
|
||||
- subnet: 172.10.10.0/24
|
||||
|
||||
volumes:
|
||||
fsm_mysql:
|
||||
name: fsm_mysql
|
||||
fsm_redis:
|
||||
name: fsm_redis
|
||||
fsm_static:
|
||||
name: fsm_static
|
||||
app_mysql:
|
||||
name: app_mysql
|
||||
app_redis:
|
||||
name: app_redis
|
||||
app_static:
|
||||
name: app_static
|
||||
@@ -1,9 +0,0 @@
|
||||
[program:fastapi_server]
|
||||
directory=/fsm
|
||||
command=/usr/local/bin/gunicorn -c /fsm/deploy/gunicorn.conf.py main:app
|
||||
user=root
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=5
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/fastapi_server/fsm_server.log
|
||||
@@ -3,7 +3,7 @@
|
||||
bind = '0.0.0.0:8001'
|
||||
|
||||
# 工作目录
|
||||
chdir = '/fsm/backend/'
|
||||
chdir = '/app/backend/'
|
||||
|
||||
# 并行工作进程数
|
||||
workers = 1
|
||||
@@ -26,11 +26,11 @@ worker_class = 'uvicorn.workers.UvicornWorker'
|
||||
worker_connections = 2000
|
||||
|
||||
# 设置进程文件目录
|
||||
pidfile = '/fsm/gunicorn.pid'
|
||||
pidfile = '/app/gunicorn.pid'
|
||||
|
||||
# 设置访问日志和错误信息日志路径
|
||||
accesslog = '/var/log/fastapi_server/gunicorn_access.log'
|
||||
errorlog = '/var/log/fastapi_server/gunicorn_error.log'
|
||||
accesslog = '/var/log/app_server/gunicorn_access.log'
|
||||
errorlog = '/var/log/app_server/gunicorn_error.log'
|
||||
|
||||
# 设置这个值为true 才会把打印信息记录到错误日志里
|
||||
capture_output = True
|
||||
|
||||
@@ -3,7 +3,7 @@ server {
|
||||
listen [::]:80 default_server;
|
||||
server_name 127.0.0.1;
|
||||
|
||||
root /fsm;
|
||||
root /app;
|
||||
|
||||
client_max_body_size 5M;
|
||||
client_body_buffer_size 5M;
|
||||
@@ -16,7 +16,7 @@ server {
|
||||
keepalive_timeout 300;
|
||||
|
||||
location / {
|
||||
proxy_pass http://fsm_server:8001;
|
||||
proxy_pass http://app_server:8080;
|
||||
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
@echo off
|
||||
REM 首次部署时预加载字典链接缓存的脚本 (Windows版本)
|
||||
REM 该脚本应在应用启动前运行
|
||||
|
||||
echo 开始预加载字典链接缓存...
|
||||
|
||||
REM 检查是否在正确的目录
|
||||
if not exist "backend\utils\preload_dict_links.py" (
|
||||
echo 错误: 请在项目根目录运行此脚本
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 激活虚拟环境(如果存在)
|
||||
if exist "venv\Scripts\activate.bat" (
|
||||
call venv\Scripts\activate.bat
|
||||
echo 已激活虚拟环境
|
||||
) else if exist ".venv\Scripts\activate.bat" (
|
||||
call .venv\Scripts\activate.bat
|
||||
echo 已激活虚拟环境
|
||||
)
|
||||
|
||||
REM 运行预加载脚本
|
||||
echo 正在运行预加载脚本...
|
||||
python backend/utils/preload_dict_links.py preload --batch-size 2000
|
||||
|
||||
if %errorlevel% neq 0 (
|
||||
echo 预加载脚本执行失败
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
echo 字典链接缓存预加载完成!
|
||||
|
||||
echo 显示缓存统计信息:
|
||||
python backend/utils/preload_dict_links.py stats
|
||||
|
||||
echo 部署预加载完成!
|
||||
Reference in New Issue
Block a user