中文评论情感分析与洞察平台(云析智研)
模型推理只是其中一环。评论进来、分析落库、低置信度交人工复核、有效标注沉淀成数据集、
攒够阈值触发重训、新模型注册激活——这条闭环才是本项目要解决的问题。
关键决策 · 业务闭环 · 系统架构 · 能力边界 · 快速开始
公开版首页。不含真实业务数据、账号、模型权重或训练资产。
面向产品反馈分析、服务评价监测、电商评论归因、客服工单洞察这类中文文本分析场景。这类场景的共同特征决定了系统形态:
| 场景约束 | 对系统的要求 |
|---|---|
| 模型对真实语料一定会判错 | 必须有人工复核通道,且修正结果要能回流训练 |
| 标注成本高,但线上样本源源不断 | 高置信度样本自动采信,低置信度才占用人力 |
| 训练是长任务,报告生成是短任务 | 两类任务不能挤同一个队列 |
| 模型会迭代多个版本 | 注册、激活、回退要能在后台完成,不靠改配置重启 |
| 三类使用者职责不同 | 权限要在前端路由与后端接口两侧同时约束 |
要评估设计的人看关键决策到能力边界;要跑起来的人直接跳快速开始。
| 决策 | 选择 | 代价 |
|---|---|---|
| 有效标注的判定 | 置信度 ≥ 0.70 自动采信;低于阈值必须分析师复核过才入池 | 阈值是经验值,调高会拖慢数据沉淀、调低会引入噪声标注 |
| 训练任务队列 | 独立 training 队列 + 独立 worker,与默认队列物理隔离 |
要多起一个 worker 进程;本地开发也得跑两个 |
| 重训触发 | 攒满阈值(默认 5000 条)自动建训练任务,另有 signal 模式只记提醒 |
自动模式下管理员是事后知情,不是事前批准 |
| refresh token | 放 HttpOnly cookie,不进响应体 | 前端拿不到 refresh token,跨域部署要额外配 cookie 域 |
| 模型激活 | 后台注册 + 激活,运行时按文件系统特征探测产物类型 | 产物目录结构成了隐式契约,缺一个文件就识别不出来 |
| 训练产物浏览 | 强制落在 TRAINING_WORKSPACE_ROOT 白名单内 |
想看工作区外的产物得改配置,不能临时传路径 |
| 任务可靠性 | acks_late + reject_on_worker_lost |
任务可能重复执行,幂等性得由任务自己保证 |
几条值得展开的:
为什么置信度阈值配人工复核,而不是全量复核或全量自动采信。 全量复核的话,标注人力会成为吞吐上限,线上样本再多也沉淀不下来;全量自动采信的话,模型判错的样本会作为「正确标注」回流,下一轮训练把错误固化。0.70 这条线把人力集中在模型自己也不确定的样本上 —— 这批样本的标注收益最高。
为什么训练必须独占队列。 训练是分钟到小时级的任务,报告生成是秒级。共用队列时,一个训练任务会把后面排队的报告请求全部堵住,而用户侧看到的现象是「报告一直在生成中」,排查方向会指向报告模块,和真正的原因隔了一层。
为什么 refresh token 不进响应体。 进了响应体,前端就得自己存 —— 存 localStorage 会被 XSS 直接读走,存内存则刷新页面即丢失登录态。放 HttpOnly cookie 后 JS 读不到,代价是跨域部署时要额外配 cookie 域和 SameSite,这个代价比 XSS 泄漏长效凭证小。
用户提交评论 / 上传文件
↓ 文本校验 · 文件解析 · 批量行数限制(默认 1000)
模型推理 + 关键词归一化
↓
评论与分析结果入库
↓
┌────────────────┬─────────────────┐
置信度 ≥ 0.70 置信度 < 0.70
自动视为有效标注 分析师复核修正后才算
└────────┬───────┴─────────────────┘
↓ 攒满阈值(默认 5000 条)
保存为 HuggingFace Dataset 批次
↓ auto 模式建训练任务 / signal 模式只记提醒
训练队列异步执行 → 评估 → 注册 → 激活
↓
新模型接管推理
分析结果会记录分析渠道、分析会话、来源名称、模型原始情感、人工修正情感、审核人、审核时间和自动训练数据集引用 —— 最后一个字段是防重复入池的关键:已进批次的结果不会被下一轮再统计一次。
%%{init: {'theme': 'base', 'themeVariables': { 'edgeLabelBackground': '#ffffff', 'mainBkg': '#ffffff', 'lineColor': '#64748b' }}}%%
flowchart TB
classDef client fill:#ffffff,stroke:#3b82f6,stroke-width:1.5px,color:#1e40af,rx:5px,ry:5px;
classDef edge fill:#ffffff,stroke:#2563eb,stroke-width:1.5px,color:#1d4ed8,rx:5px,ry:5px;
classDef app fill:#ffffff,stroke:#334155,stroke-width:1.5px,color:#0f172a,rx:5px,ry:5px;
classDef training fill:#ffffff,stroke:#ef4444,stroke-width:1.5px,color:#b91c1c,rx:5px,ry:5px;
classDef ml fill:#ffffff,stroke:#8b5cf6,stroke-width:1.5px,color:#6d28d9,rx:5px,ry:5px;
classDef mq fill:#ffffff,stroke:#f59e0b,stroke-width:1.5px,color:#b45309,rx:5px,ry:5px;
classDef db fill:#ffffff,stroke:#64748b,stroke-width:1.5px,color:#334155,rx:5px,ry:5px;
%% 统一入口
U["多角色用户 (普通用户 · 分析师 · 管理员)"]:::client
FE["Vue 3.5 前端单页应用 (三角色路由守卫 · DOMPurify)"]:::edge
API["Django 6 REST API (JWT 鉴权 · HttpOnly Refresh)"]:::app
%% 线上推理链路 (左路)
ANA["文本分析与人工复核<br/>(apps/analysis · 置信度分流)"]:::app
ML_INF["模型工作区 (运行时特征探测)<br/>Transformer · 传统 ML · 神经基线"]:::ml
Q_DEF["Celery 默认队列 (短任务)<br/>PDF/Excel 报告生成 · 状态审计"]:::mq
%% 自动化重训闭环 (右路)
ADM["模型管理与审计中心<br/>(apps/admin_panel · 自动重训判定)"]:::app
Q_TRN["Celery training 队列 (长任务)<br/>独占 Worker 进程 · 微调/超参搜索"]:::training
ML_REG["新模型产物交付<br/>自动评估 · 注册登记 · 一键激活"]:::ml
%% 数据基础设施
DB[("MySQL 8.0 持久库<br/>(8 张核心业务表 · RBAC · 审计)")]:::db
RD[("Redis 7.0 缓存中间件<br/>(Celery Broker · Backend · 缓存)")]:::db
%% 顶层流转
U --> FE --> API
%% 核心业务与计算分流 (左路与右路)
API -->|"单条 / 批量分析"| ANA
ANA -->|"加载产物推理"| ML_INF
ANA -->|"异步生成报告"| Q_DEF
ANA --> DB
API -->|"阈值触发 / 管理"| ADM
ADM -->|"下发重训任务"| Q_TRN
Q_TRN -->|"训练产物产出"| ML_REG
ML_REG -.->|"激活新模型生效"| ML_INF
ADM --> DB
%% 异步到 Redis
Q_DEF & Q_TRN --> RD
运行时模型探测:激活的模型路径按文件系统特征判定产物类型 —— .joblib 文件识别为传统模型,.pt 且同目录有 vocab.json 与 config_snapshot.json 识别为神经基线,目录内同时有 config.json、model.safetensors、tokenizer_config.json 识别为 Transformer。三者都不匹配则返回空类型,接口据此向前端报告能力缺失,而不是在推理时抛异常。
模型路径另有一道校验:必须落在 MODEL_WORKSPACE_DIR 之内,否则拒绝加载并记日志。
| 层 | 组件 |
|---|---|
| 前端 | Vue 3.5 · Vite 8 · Pinia · Vue Router 5 · Element Plus · Tailwind CSS 4 · ECharts |
| 前端安全 | DOMPurify —— 验证码以 SVG 下发,SVG 能携带脚本,渲染前必须净化 |
| API | Django 6 · Django REST Framework · Simple JWT · drf-spectacular |
| 业务 | apps/users · apps/analysis · apps/reports · apps/admin_panel |
| 存储 | MySQL 8 · Redis |
| 异步 | Celery 5.6 · Celery Beat |
| 模型 | PyTorch · Transformers · scikit-learn · jieba · datasets · safetensors |
| 报告 | ReportLab · openpyxl · CSV / TXT / XLSX |
| 质量 | pytest · ruff · ESLint · Prettier · vue-tsc · GitHub Actions |
| 角色 | 前端路径 | 权限范围 | 典型操作 |
|---|---|---|---|
| 普通用户 | /user/* |
仅本人分析历史与报告 | 单条分析、批量上传、查看历史、生成报告、维护资料 |
| 分析师 | /analyst/* |
全局分析结果与统计报表 | 评论复核、情感修正、重点标注、报表查看与导出 |
| 管理员 | /admin/* |
系统级资源 | 用户管理、模型切换、训练编排、数据集导出、日志与备份 |
登录成功后按角色跳转对应首页。权限在两侧同时约束:前端路由守卫决定看得见什么,后端接口权限决定拿得到什么 —— 只做前端守卫的话,直接调接口就能越权。
| 能力域 | 说明 |
|---|---|
| 账号与权限 | 图形验证码、邮箱验证码、注册登录、JWT access token、HttpOnly refresh cookie、三角色 RBAC |
| 评论分析 | 单条分析、TXT/XLSX 批量分析、批量模板下载、运行时模型能力检测、分析历史与详情 |
| 分析师复核 | 全局评论检索、情感修正、审核备注、重点关注、趋势统计、报表导出 |
| 报告中心 | PDF / Excel / CSV 报告生成,异步入队,状态追踪,安全下载 |
| 管理后台 | 用户管理、数据集沉淀与导出、模型注册与激活、训练中心、日志审计、数据库备份 |
| 模型训练 | Transformer 微调、Transformer 超参搜索、传统模型对比、TextCNN / BiLSTM 基线训练 |
| 自动重训 | 按高置信度与人工审核样本构建批次,达阈值触发训练或记录提醒 |
| 运维治理 | Celery Beat 定时任务、日志保留策略、异常训练任务清理、路径安全校验 |
数据模型 · 8 张核心表
| 表名 | Django 模型 | 说明 |
|---|---|---|
users |
users.User |
自定义用户表,支持 user / analyst / admin 三类角色 |
email_verification_codes |
users.EmailVerificationCode |
邮箱验证码、用途、失败次数与过期控制 |
comments |
analysis.Comment |
评论正文、项目名、评分、类别、来源、评论时间 |
analysis_results |
analysis.AnalysisResult |
情感类别、置信度、关键词、分析渠道、人工修正、审核信息、自动训练数据集引用 |
models |
analysis.Model |
模型注册、版本、指标、路径、激活状态、运行时兼容性 |
training_runs |
admin_panel.TrainingRun |
训练任务、数据集引用、配置快照、指标、产物与日志路径 |
reports |
reports.Report |
报告类型、格式、状态、文件路径、摘要与入队信息 |
operation_logs |
admin_panel.OperationLog |
登录、分析、导入导出、训练、模型切换等审计日志 |
API 概览与异步任务
| 前缀 | 说明 |
|---|---|
/api/healthz/ |
服务健康检查 |
/api/auth/ |
验证码、注册、登录、刷新、退出、资料、密码 |
/api/analyze/ |
单条 / 批量分析、模板、历史、详情、分析师视图、报表导出 |
/api/report/ |
报告生成、报告列表、报告下载 |
/api/admin/ |
用户、日志、仪表盘、备份、数据集、自动重训状态、模型、训练中心 |
/swagger/、/redoc/ |
OpenAPI 文档,需 SWAGGER_ENABLED=True 且管理员访问 |
| 队列 / 定时任务 | 用途 |
|---|---|
celery 默认队列 |
报告生成、验证码清理、日志清理等通用任务 |
training 队列 |
模型训练、训练后处理、训练产物登记 |
Beat */5 * * * * |
清理异常停留在 running 的训练任务 |
| Beat 每小时第 15 分 | 自动重训阈值检查 |
| Beat 每 6 小时第 10 分 | 操作日志清理(保留期默认 180 天) |
| Beat 每天 03:30 | 过期验证码清理 |
本仓库是 SentimentPlatform 的公开源码版本,只发布系统源码、自动化测试、文档、前端品牌资源和开发脚本。以下资产不在公开范围:
| 类型 | 公开策略 |
|---|---|
.env、真实密钥、本地账号 |
不发布 |
| 训练数据集、Arrow 文件、数据切分结果 | 不发布 |
模型权重、.joblib、.pt、.safetensors 训练产物 |
不发布 |
| 本地数据库、日志、上传文件、报告导出、备份文件 | 不发布 |
因此克隆后无法直接跑通真实分析、训练或模型激活 —— 这三条路径都依赖上面的资产。要跑通需按 模型与数据资产说明 在本地准备模型、数据集与配置。
界面截图同理只提供不含业务数据的首页与认证页面;分析、复核、训练中心等页面的截图需要真实数据与模型权重才能呈现,不随公开版发布。
已实现并验证:
- 三角色 RBAC 在前端路由与后端接口两侧同时约束
- 高低置信度分流的复核机制,修正结果回流训练数据池
- 训练与常规任务队列物理隔离,长任务不阻塞报告生成
- 三类模型产物(Transformer / 传统 ML / 神经基线)运行时按文件特征探测
- 训练产物与数据集路径强制落在白名单根目录内
- 122 个后端测试覆盖认证、分析、报告、训练与权限路径
明确的限制:
- 单机部署。Celery Beat 未做选主,多实例会重复触发定时任务
- 自动重训是「事后知情」。
auto模式下达阈值直接建任务,管理员事后在训练中心看到;需要事前批准应切signal模式 - 置信度阈值 0.70 是经验值,没有做过阈值扫描实验来定这条线
acks_late意味着任务可能重复执行,幂等性由各任务自行保证,没有统一的去重中间层- 批量分析上限 1000 行(
MAX_BATCH_RECORDS),更大的文件需要分批 - 无模型 A/B 与灰度。激活是全量切换,回退靠重新激活旧模型
| 项 | 规模 |
|---|---|
| 后端 Python | 304 个文件 / 27,877 行 |
| 后端测试 | 15 个文件 / 122 个测试 |
| 前端 | 40 个 .vue:23 个页面 + 13 个组件 + 3 个布局,三角色路由 |
| 数据表 | 8 张核心业务表 |
| Celery | 2 个队列 + 4 类 Beat 定时任务 |
最近一次本地全量检查:
cd sentiment_server
$env:DJANGO_SETTINGS_MODULE = "sentiment_server.settings.local"
python manage.py check
python manage.py makemigrations --check --dry-run
python -m ruff check .
python -m pytest -q
cd ..\sentiment_webapp
npm run check
npm run build结果:系统检查通过、迁移无遗漏、ruff 通过、后端 122 passed;前端 lint、Prettier、vue-tsc 与生产构建均通过。
CI(.github/workflows/ci.yml)跑 manage.py check、ruff check、pytest、前端 check 与 build。
lint 规则集显式声明在 [tool.ruff.lint] 的 select 里,不吃 ruff 的默认集 —— 默认集从 0.15 的 59 条涨到 0.16 的 413 条,曾导致不改一行代码 CI 自己变红。选定的是 E4/E7/E9/F(ruff 自身默认,代码库本来就对着它干净)加三样:I 导入排序(纯机械、可自动修)、DTZ 时区(它抓出过一个真缺陷,见下)、UP 语法跟上 requires-python。再放宽是独立决定:全开 E 会引入 522 条行超长,全开 RUF 会引入 205 条全角标点告警 —— 后者是本项目在面向用户的文案里刻意用的。
已知缺口:makemigrations --check 只在本地清单里,未进 CI —— 它拦的是「模型改了但没生成迁移」,这类问题在已有库的开发机上不报错,换台机器从零建库才暴露。
| 缺陷 | 后果 | 修法 |
|---|---|---|
| 训练记录时间戳一半 aware、一半 naive | 按 --start-time 筛训练记录时抛 can't compare offset-naive and offset-aware;排序路径不崩,但改用 .timestamp() 后 naive 被按本地时区解释,结果随机器漂移 |
两条来源统一为 aware,无偏移输入按 UTC 解释;DTZ 规则守着 |
| lint 规则集吃 ruff 默认值 | 默认集 0.15→0.16 从 59 条涨到 413 条,不改一行代码 CI 自己变红,284 处告警无一是缺陷 | select 显式声明,门禁与工具版本解耦 |
时间戳那条的具体位置:_record_timestamp 在 payload 里找到 ISO 字符串时返回 aware,找不到就回落到 datetime.fromtimestamp(mtime) 返回 naive。trends.filter_experiment_records 拿它和 CLI 传入的 aware 时间直接比较,于是「payload 里没有时间字段的记录」+「指定了时间范围」这个组合必崩。三个文件里同一个函数各有一份副本,都改了。
环境要求:Python 3.12+ · Node.js 22.18+ 或 24.11+ · MySQL 8+(127.0.0.1:3306)· Redis(127.0.0.1:6379/0)· PowerShell 7.0+(一键脚本用)
cd sentiment_server
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -e ".[training,testing]"
Copy-Item .env.example .env编辑 .env,至少填 SECRET_KEY、JWT_SIGNING_KEY 与数据库连接。然后:
$env:DJANGO_SETTINGS_MODULE = "sentiment_server.settings.local"
python manage.py migrate
python manage.py check
python -m pytest -q
python manage.py runserver 127.0.0.1:8000cd sentiment_webapp
npm install
npm run check
npm run build
npm run devVite 开发服务默认 http://127.0.0.1:5173/,通过代理访问后端 /api。
.\start-dev.ps1 # 全部服务
.\start-dev.ps1 -Services backend,frontend # 只起部分
.\stop-dev.ps1一键启动会先检查后端 .env 必填密钥、MySQL 与 Redis 连接,再依次拉起 Django、Celery 默认 worker、Celery training worker、Celery Beat 和 Vite。健康检查地址 http://127.0.0.1:8000/api/healthz/。
注意这里会起两个 Celery worker —— 默认队列和
training队列各一个。只起一个的话,训练任务会一直排队不执行,而界面上看起来只是「训练中」。
SentimentPlatform/
├── sentiment_server/ Django REST API、Celery、模型推理与训练工作区
│ ├── apps/ users / analysis / reports / admin_panel
│ ├── core/ 跨应用的公共设施
│ ├── ml_assets/ 模型工作区(权重与数据集不公开)
│ └── tests/ 后端测试
├── sentiment_webapp/ Vue 3 + Vite 单页应用
│ ├── src/pages/ 23 个页面,按 auth / user / analyst / admin 分组
│ ├── src/components/ 13 个复用组件
│ ├── src/layouts/ 3 个布局(按角色)
│ ├── src/stores/ Pinia 状态
│ └── src/api/ 接口封装
├── docs/ 公开发布、模型与数据资产说明
├── start-dev.ps1 一键启动后端、两个 Celery worker、Beat、前端
├── stop-dev.ps1 一键停止
└── dev-services.ps1 本地服务定义与复用函数
公开版采用 monorepo 组织,后端与前端在同一仓库,便于统一管理 issue、CI、版本与文档。

