v0.1.0: CRM/ERP 系统内测版本 - 安全加固完成
- Docker bridge 网络隔离(8000 端口封死) - Gunicorn 4 Worker 多进程 - Alembic 数据库迁移基线 - 日志轮转 20m×3 - JWT 密钥 + DB 密码 + CORS 收紧 - 3-2-1 备份链路(NAS + R740-B 冷备) - 连接池 pool_pre_ping + pool_recycle=3600
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
认证端点
|
||||
处理用户登录,签发 JWT 令牌。
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import create_access_token
|
||||
from app.crud.user import authenticate_user
|
||||
from app.schemas.user import Token, UserLogin
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/login", response_model=Token, summary="用户登录", tags=["认证"])
|
||||
async def login(body: UserLogin, db: AsyncSession = Depends(get_db)):
|
||||
"""
|
||||
校验用户名密码,成功后签发 JWT access_token。
|
||||
前端后续请求需在 Authorization 头携带 Bearer <token>。
|
||||
"""
|
||||
user = await authenticate_user(db, body.username, body.password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="用户名或密码错误",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
token = create_access_token(subject=user.username, role=user.role)
|
||||
return Token(
|
||||
access_token=token,
|
||||
role=user.role,
|
||||
username=user.username,
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
健康检查端点
|
||||
用于 Nginx/LB 探活和数据库连接状态探测。
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/health", summary="健康检查", tags=["系统"])
|
||||
async def health_check(db: AsyncSession = Depends(get_db)):
|
||||
"""
|
||||
探测服务与数据库连接是否存活。
|
||||
- 数据库可达 → {"status": "healthy", "database": "connected"}
|
||||
- 数据库不可达 → {"status": "degraded", "database": "disconnected", "detail": "..."}
|
||||
"""
|
||||
try:
|
||||
await db.execute(text("SELECT 1"))
|
||||
return {"status": "healthy", "database": "connected"}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "degraded",
|
||||
"database": "disconnected",
|
||||
"detail": str(e),
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
客户沟通日志 API
|
||||
POST /api/v1/logs - 提交日志并触发后台 AI 标签提取
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_user
|
||||
from app.core.database import get_db
|
||||
from app.models.crm_business import CustomerLog
|
||||
from app.services.ai_workflow import process_log_with_ai
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ---- 请求/响应模型 ----
|
||||
|
||||
class LogCreate(BaseModel):
|
||||
"""提交沟通日志请求"""
|
||||
customer_id: uuid.UUID = Field(..., description="关联客户 ID")
|
||||
content: str = Field(..., min_length=5, max_length=5000, description="沟通日志内容")
|
||||
|
||||
|
||||
class LogResponse(BaseModel):
|
||||
"""提交成功响应"""
|
||||
id: uuid.UUID
|
||||
message: str = "日志已提交,AI 正在后台分析标签和待办"
|
||||
|
||||
|
||||
# ---- 路由 ----
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=LogResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="提交客户沟通日志",
|
||||
tags=["客户日志"],
|
||||
)
|
||||
async def create_customer_log(
|
||||
body: LogCreate,
|
||||
background_tasks: BackgroundTasks,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
接收前端提交的客户沟通日志:
|
||||
1. 立即存入 customer_logs 表
|
||||
2. 将 AI 标签提取任务加入 BackgroundTasks 后台队列
|
||||
3. 立即返回 200 OK(不等待 AI 处理完成)
|
||||
|
||||
AI 后台任务会:
|
||||
- 调用 qwen3:14b 分析日志内容
|
||||
- 自动提取最多 3 个客户标签 → customer_tags
|
||||
- 自动生成 1 个跟进待办 → follow_up_todos
|
||||
"""
|
||||
# Step 1: 立即写入日志记录
|
||||
log = CustomerLog(
|
||||
customer_id=body.customer_id,
|
||||
content=body.content,
|
||||
)
|
||||
db.add(log)
|
||||
await db.flush()
|
||||
await db.refresh(log)
|
||||
|
||||
# Step 2: 将 AI 处理加入后台队列
|
||||
# *** 关键:传入 log.id / body.content / body.customer_id 三个值 ***
|
||||
# process_log_with_ai 会创建独立的 DB Session,不与当前请求的 db 共享
|
||||
background_tasks.add_task(
|
||||
process_log_with_ai,
|
||||
log_id=log.id,
|
||||
content=body.content,
|
||||
customer_id=body.customer_id,
|
||||
)
|
||||
|
||||
# Step 3: 立即返回(不等待 AI)
|
||||
return LogResponse(id=log.id)
|
||||
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
销售复盘报告 API
|
||||
GET /api/v1/reports/monthly - 获取当月销售复盘报告 (AI 生成)
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import get_current_user
|
||||
from app.core.database import get_db
|
||||
from app.services.analytics import generate_monthly_report
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ---- 响应模型 ----
|
||||
|
||||
class StageMetric(BaseModel):
|
||||
"""单个阶段的统计指标"""
|
||||
stage: str
|
||||
count: int
|
||||
total_amount: float
|
||||
|
||||
|
||||
class MonthlyReportResponse(BaseModel):
|
||||
"""月度复盘报告响应"""
|
||||
metrics: list[StageMetric]
|
||||
report: str
|
||||
|
||||
|
||||
# ---- 路由 ----
|
||||
|
||||
@router.get(
|
||||
"/monthly",
|
||||
response_model=MonthlyReportResponse,
|
||||
summary="获取当月销售复盘报告",
|
||||
tags=["数据报告"],
|
||||
)
|
||||
async def get_monthly_report(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
生成当月销售复盘报告:
|
||||
1. SQL 预聚合统计各阶段的机会数量和金额
|
||||
2. 将真实数据注入 Prompt,调用 qwen3:14b 生成分析报告
|
||||
3. 同步返回结构化数据 + AI 报告文本
|
||||
|
||||
注意:此接口为同步等待模式(用户主动触发),
|
||||
AI 生成可能需要 10-30 秒,前端应显示加载状态。
|
||||
"""
|
||||
result = await generate_monthly_report(db)
|
||||
return MonthlyReportResponse(**result)
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
API v1 路由汇总
|
||||
所有 v1 版本的子路由在此注册,由 main.py 统一挂载到 /api/v1 前缀。
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.v1.endpoints import auth, health, logs, reports
|
||||
|
||||
api_v1_router = APIRouter()
|
||||
|
||||
# 挂载各业务模块路由
|
||||
api_v1_router.include_router(health.router, prefix="", tags=["系统"])
|
||||
api_v1_router.include_router(auth.router, prefix="/auth", tags=["认证"])
|
||||
api_v1_router.include_router(logs.router, prefix="/logs", tags=["客户日志"])
|
||||
api_v1_router.include_router(reports.router, prefix="/reports", tags=["数据报告"])
|
||||
|
||||
# 后续新增模块在此追加,例如:
|
||||
# from app.api.v1.endpoints import clients, expenses
|
||||
# api_v1_router.include_router(clients.router, prefix="/clients", tags=["客户管理"])
|
||||
# api_v1_router.include_router(expenses.router, prefix="/expenses", tags=["报销管理"])
|
||||
Reference in New Issue
Block a user