feat: 初始化考培练系统项目

- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
111
2026-01-24 19:33:28 +08:00
commit 998211c483
1197 changed files with 228429 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
"""
知识点分析 API
使用 Python 原生 AI 服务实现
"""
import logging
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_db, get_current_user
from app.schemas.base import ResponseModel
from app.models.user import User
from app.services.course_service import course_service
from app.services.ai.knowledge_analysis_v2 import knowledge_analysis_service_v2
logger = logging.getLogger(__name__)
router = APIRouter()
@router.post("/courses/{course_id}/materials/{material_id}/analyze", response_model=ResponseModel[Dict[str, Any]])
async def analyze_material_knowledge_points(
course_id: int,
material_id: int,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""
分析单个资料的知识点
- **course_id**: 课程ID
- **material_id**: 资料ID
使用 Python 原生 AI 服务:
- 本地 AI 服务调用4sapi.com 首选OpenRouter 备选)
- 多层 JSON 解析兜底
- 无外部平台依赖,更稳定
"""
try:
# 验证课程是否存在
course = await course_service.get_by_id(db, course_id)
if not course:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"课程 {course_id} 不存在"
)
# 获取资料信息
materials = await course_service.get_course_materials(db, course_id=course_id)
material = next((m for m in materials if m.id == material_id), None)
if not material:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"资料 {material_id} 不存在"
)
logger.info(
f"准备启动知识点分析 - course_id: {course_id}, material_id: {material_id}, "
f"file_url: {material.file_url}, user_id: {current_user.id}"
)
# 调用 Python 原生知识点分析服务
result = await knowledge_analysis_service_v2.analyze_course_material(
db=db,
course_id=course_id,
material_id=material_id,
file_url=material.file_url,
course_title=course.name,
user_id=current_user.id
)
logger.info(
f"知识点分析完成 - course_id: {course_id}, material_id: {material_id}, "
f"knowledge_points: {result.get('knowledge_points_count', 0)}, "
f"provider: {result.get('ai_provider')}"
)
# 构建响应
response_data = {
"message": "知识点分析完成",
"course_id": course_id,
"material_id": material_id,
"status": result.get("status", "completed"),
"knowledge_points_count": result.get("knowledge_points_count", 0),
"ai_provider": result.get("ai_provider"),
"ai_model": result.get("ai_model"),
"ai_tokens": result.get("ai_tokens"),
"ai_latency_ms": result.get("ai_latency_ms"),
}
return ResponseModel(
data=response_data,
message="知识点分析完成"
)
except HTTPException:
raise
except Exception as e:
logger.error(
f"知识点分析失败 - course_id: {course_id}, material_id: {material_id}, error: {e}",
exc_info=True
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"知识点分析失败: {str(e)}"
)
@router.post("/courses/{course_id}/reanalyze", response_model=ResponseModel[Dict[str, Any]])
async def reanalyze_course_materials(
course_id: int,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""
重新分析课程的所有资料
- **course_id**: 课程ID
该接口会重新分析课程下的所有资料,提取知识点
"""
try:
# 验证课程是否存在
course = await course_service.get_by_id(db, course_id)
if not course:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"课程 {course_id} 不存在"
)
# 获取课程资料信息
materials = await course_service.get_course_materials(db, course_id=course_id)
if not materials:
return ResponseModel(
data={
"message": "该课程暂无资料需要分析",
"course_id": course_id,
"status": "stopped",
"materials_count": 0
},
message="无资料需要分析"
)
# 调用 Python 原生知识点分析服务
result = await knowledge_analysis_service_v2.reanalyze_course_materials(
db=db,
course_id=course_id,
course_title=course.name,
user_id=current_user.id
)
return ResponseModel(
data={
"message": "课程资料重新分析完成",
"course_id": course_id,
"status": "completed",
"materials_count": result.get("materials_count", 0),
"success_count": result.get("success_count", 0),
"knowledge_points_count": result.get("knowledge_points_count", 0),
"analysis_results": result.get("analysis_results", [])
},
message="重新分析完成"
)
except HTTPException:
raise
except Exception as e:
logger.error(
f"启动课程资料重新分析失败 - course_id: {course_id}, error: {e}",
exc_info=True
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="启动重新分析失败"
)
@router.get("/engines", response_model=ResponseModel[Dict[str, Any]])
async def list_analysis_engines():
"""
获取可用的分析引擎列表
"""
return ResponseModel(
data={
"engines": [
{
"id": "native",
"name": "Python 原生实现",
"description": "使用本地 AI 服务4sapi.com + OpenRouter稳定可靠",
"default": True
}
],
"default_engine": "native"
},
message="获取分析引擎列表成功"
)