Repository files navigation

SentimentPlatform

中文评论情感分析与洞察平台(云析智研)

模型推理只是其中一环。评论进来、分析落库、低置信度交人工复核、有效标注沉淀成数据集、
攒够阈值触发重训、新模型注册激活——这条闭环才是本项目要解决的问题。

CILicense: MITDjangoVueTests

关键决策 · 业务闭环 · 系统架构 · 能力边界 · 快速开始

SentimentPlatform 产品入口页

公开版首页。不含真实业务数据、账号、模型权重或训练资产。

落地场景

面向产品反馈分析、服务评价监测、电商评论归因、客服工单洞察这类中文文本分析场景。这类场景的共同特征决定了系统形态:

场景约束对系统的要求
模型对真实语料一定会判错必须有人工复核通道,且修正结果要能回流训练
标注成本高,但线上样本源源不断高置信度样本自动采信,低置信度才占用人力
训练是长任务,报告生成是短任务两类任务不能挤同一个队列
模型会迭代多个版本注册、激活、回退要能在后台完成,不靠改配置重启
三类使用者职责不同权限要在前端路由与后端接口两侧同时约束

要评估设计的人看关键决策能力边界;要跑起来的人直接跳快速开始

关键决策

决策选择代价
有效标注的判定置信度 ≥ 0.70 自动采信;低于阈值必须分析师复核过才入池阈值是经验值,调高会拖慢数据沉淀、调低会引入噪声标注
训练任务队列独立 training 队列 + 独立 worker,与默认队列物理隔离要多起一个 worker 进程;本地开发也得跑两个
重训触发攒满阈值(默认 5000 条)自动建训练任务,另有 signal 模式只记提醒自动模式下管理员是事后知情,不是事前批准
refresh tokenHttpOnly 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
Loading

运行时模型探测:激活的模型路径按文件系统特征判定产物类型 —— .joblib 文件识别为传统模型,.pt 且同目录有 vocab.jsonconfig_snapshot.json 识别为神经基线,目录内同时有 config.jsonmodel.safetensorstokenizer_config.json 识别为 Transformer。三者都不匹配则返回空类型,接口据此向前端报告能力缺失,而不是在推理时抛异常。

模型路径另有一道校验:必须落在 MODEL_WORKSPACE_DIR 之内,否则拒绝加载并记日志。

技术栈

组件
前端Vue 3.5 · Vite 8 · Pinia · Vue Router 5 · Element Plus · Tailwind CSS 4 · ECharts
前端安全DOMPurify —— 验证码以 SVG 下发,SVG 能携带脚本,渲染前必须净化
APIDjango 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 + 图形验证码)角色注册与申领(RBAC + 邮箱验证码)
登录页面注册页面

核心能力

能力域说明
账号与权限图形验证码、邮箱验证码、注册登录、JWT access token、HttpOnly refresh cookie、三角色 RBAC
评论分析单条分析、TXT/XLSX 批量分析、批量模板下载、运行时模型能力检测、分析历史与详情
分析师复核全局评论检索、情感修正、审核备注、重点关注、趋势统计、报表导出
报告中心PDF / Excel / CSV 报告生成,异步入队,状态追踪,安全下载
管理后台用户管理、数据集沉淀与导出、模型注册与激活、训练中心、日志审计、数据库备份
模型训练Transformer 微调、Transformer 超参搜索、传统模型对比、TextCNN / BiLSTM 基线训练
自动重训按高置信度与人工审核样本构建批次,达阈值触发训练或记录提醒
运维治理Celery Beat 定时任务、日志保留策略、异常训练任务清理、路径安全校验
数据模型 · 8 张核心表
表名Django 模型说明
usersusers.User自定义用户表,支持 user / analyst / admin 三类角色
email_verification_codesusers.EmailVerificationCode邮箱验证码、用途、失败次数与过期控制
commentsanalysis.Comment评论正文、项目名、评分、类别、来源、评论时间
analysis_resultsanalysis.AnalysisResult情感类别、置信度、关键词、分析渠道、人工修正、审核信息、自动训练数据集引用
modelsanalysis.Model模型注册、版本、指标、路径、激活状态、运行时兼容性
training_runsadmin_panel.TrainingRun训练任务、数据集引用、配置快照、指标、产物与日志路径
reportsreports.Report报告类型、格式、状态、文件路径、摘要与入队信息
operation_logsadmin_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 与灰度。激活是全量切换,回退靠重新激活旧模型

工程验证

规模
后端 Python304 个文件 / 27,877 行
后端测试15 个文件 / 122 个测试
前端40 个 .vue:23 个页面 + 13 个组件 + 3 个布局,三角色路由
数据表8 张核心业务表
Celery2 个队列 + 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 checkruff checkpytest、前端 checkbuild

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_KEYJWT_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:8000

前端

cd sentiment_webapp
npm install
npm run check
npm run build
npm run dev

Vite 开发服务默认 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、版本与文档。

文档

公开发布说明 · 模型与数据资产 · 后端说明 · 前端说明 · 前端设计规范 · 手工测试素材

许可证

MIT License

About

One of the projects I keep around while learning from text, signals, and product feedback.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

SentimentPlatform

中文评论情感分析与洞察平台(云析智研)

模型推理只是其中一环。评论进来、分析落库、低置信度交人工复核、有效标注沉淀成数据集、
攒够阈值触发重训、新模型注册激活——这条闭环才是本项目要解决的问题。

CILicense: MITDjangoVueTests

关键决策 · 业务闭环 · 系统架构 · 能力边界 · 快速开始

SentimentPlatform 产品入口页

公开版首页。不含真实业务数据、账号、模型权重或训练资产。

落地场景

面向产品反馈分析、服务评价监测、电商评论归因、客服工单洞察这类中文文本分析场景。这类场景的共同特征决定了系统形态:

场景约束对系统的要求
模型对真实语料一定会判错必须有人工复核通道,且修正结果要能回流训练
标注成本高,但线上样本源源不断高置信度样本自动采信,低置信度才占用人力
训练是长任务,报告生成是短任务两类任务不能挤同一个队列
模型会迭代多个版本注册、激活、回退要能在后台完成,不靠改配置重启
三类使用者职责不同权限要在前端路由与后端接口两侧同时约束

要评估设计的人看关键决策能力边界;要跑起来的人直接跳快速开始

关键决策

决策选择代价
有效标注的判定置信度 ≥ 0.70 自动采信;低于阈值必须分析师复核过才入池阈值是经验值,调高会拖慢数据沉淀、调低会引入噪声标注
训练任务队列独立 training 队列 + 独立 worker,与默认队列物理隔离要多起一个 worker 进程;本地开发也得跑两个
重训触发攒满阈值(默认 5000 条)自动建训练任务,另有 signal 模式只记提醒自动模式下管理员是事后知情,不是事前批准
refresh tokenHttpOnly 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
Loading

运行时模型探测:激活的模型路径按文件系统特征判定产物类型 —— .joblib 文件识别为传统模型,.pt 且同目录有 vocab.jsonconfig_snapshot.json 识别为神经基线,目录内同时有 config.jsonmodel.safetensorstokenizer_config.json 识别为 Transformer。三者都不匹配则返回空类型,接口据此向前端报告能力缺失,而不是在推理时抛异常。

模型路径另有一道校验:必须落在 MODEL_WORKSPACE_DIR 之内,否则拒绝加载并记日志。

技术栈

组件
前端Vue 3.5 · Vite 8 · Pinia · Vue Router 5 · Element Plus · Tailwind CSS 4 · ECharts
前端安全DOMPurify —— 验证码以 SVG 下发,SVG 能携带脚本,渲染前必须净化
APIDjango 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 + 图形验证码)角色注册与申领(RBAC + 邮箱验证码)
登录页面注册页面

核心能力

能力域说明
账号与权限图形验证码、邮箱验证码、注册登录、JWT access token、HttpOnly refresh cookie、三角色 RBAC
评论分析单条分析、TXT/XLSX 批量分析、批量模板下载、运行时模型能力检测、分析历史与详情
分析师复核全局评论检索、情感修正、审核备注、重点关注、趋势统计、报表导出
报告中心PDF / Excel / CSV 报告生成,异步入队,状态追踪,安全下载
管理后台用户管理、数据集沉淀与导出、模型注册与激活、训练中心、日志审计、数据库备份
模型训练Transformer 微调、Transformer 超参搜索、传统模型对比、TextCNN / BiLSTM 基线训练
自动重训按高置信度与人工审核样本构建批次,达阈值触发训练或记录提醒
运维治理Celery Beat 定时任务、日志保留策略、异常训练任务清理、路径安全校验
数据模型 · 8 张核心表
表名Django 模型说明
usersusers.User自定义用户表,支持 user / analyst / admin 三类角色
email_verification_codesusers.EmailVerificationCode邮箱验证码、用途、失败次数与过期控制
commentsanalysis.Comment评论正文、项目名、评分、类别、来源、评论时间
analysis_resultsanalysis.AnalysisResult情感类别、置信度、关键词、分析渠道、人工修正、审核信息、自动训练数据集引用
modelsanalysis.Model模型注册、版本、指标、路径、激活状态、运行时兼容性
training_runsadmin_panel.TrainingRun训练任务、数据集引用、配置快照、指标、产物与日志路径
reportsreports.Report报告类型、格式、状态、文件路径、摘要与入队信息
operation_logsadmin_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 与灰度。激活是全量切换,回退靠重新激活旧模型

工程验证

规模
后端 Python304 个文件 / 27,877 行
后端测试15 个文件 / 122 个测试
前端40 个 .vue:23 个页面 + 13 个组件 + 3 个布局,三角色路由
数据表8 张核心业务表
Celery2 个队列 + 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 checkruff checkpytest、前端 checkbuild

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_KEYJWT_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:8000

前端

cd sentiment_webapp
npm install
npm run check
npm run build
npm run dev

Vite 开发服务默认 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、版本与文档。

文档

公开发布说明 · 模型与数据资产 · 后端说明 · 前端说明 · 前端设计规范 · 手工测试素材

许可证

MIT License

About

One of the projects I keep around while learning from text, signals, and product feedback.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

SentimentPlatform

中文评论情感分析与洞察平台(云析智研)

模型推理只是其中一环。评论进来、分析落库、低置信度交人工复核、有效标注沉淀成数据集、
攒够阈值触发重训、新模型注册激活——这条闭环才是本项目要解决的问题。

CILicense: MITDjangoVueTests

关键决策 · 业务闭环 · 系统架构 · 能力边界 · 快速开始

SentimentPlatform 产品入口页

公开版首页。不含真实业务数据、账号、模型权重或训练资产。

落地场景

面向产品反馈分析、服务评价监测、电商评论归因、客服工单洞察这类中文文本分析场景。这类场景的共同特征决定了系统形态:

场景约束对系统的要求
模型对真实语料一定会判错必须有人工复核通道,且修正结果要能回流训练
标注成本高,但线上样本源源不断高置信度样本自动采信,低置信度才占用人力
训练是长任务,报告生成是短任务两类任务不能挤同一个队列
模型会迭代多个版本注册、激活、回退要能在后台完成,不靠改配置重启
三类使用者职责不同权限要在前端路由与后端接口两侧同时约束

要评估设计的人看关键决策能力边界;要跑起来的人直接跳快速开始

关键决策

决策选择代价
有效标注的判定置信度 ≥ 0.70 自动采信;低于阈值必须分析师复核过才入池阈值是经验值,调高会拖慢数据沉淀、调低会引入噪声标注
训练任务队列独立 training 队列 + 独立 worker,与默认队列物理隔离要多起一个 worker 进程;本地开发也得跑两个
重训触发攒满阈值(默认 5000 条)自动建训练任务,另有 signal 模式只记提醒自动模式下管理员是事后知情,不是事前批准
refresh tokenHttpOnly 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
Loading

运行时模型探测:激活的模型路径按文件系统特征判定产物类型 —— .joblib 文件识别为传统模型,.pt 且同目录有 vocab.jsonconfig_snapshot.json 识别为神经基线,目录内同时有 config.jsonmodel.safetensorstokenizer_config.json 识别为 Transformer。三者都不匹配则返回空类型,接口据此向前端报告能力缺失,而不是在推理时抛异常。

模型路径另有一道校验:必须落在 MODEL_WORKSPACE_DIR 之内,否则拒绝加载并记日志。

技术栈

组件
前端Vue 3.5 · Vite 8 · Pinia · Vue Router 5 · Element Plus · Tailwind CSS 4 · ECharts
前端安全DOMPurify —— 验证码以 SVG 下发,SVG 能携带脚本,渲染前必须净化
APIDjango 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 + 图形验证码)角色注册与申领(RBAC + 邮箱验证码)
登录页面注册页面

核心能力

能力域说明
账号与权限图形验证码、邮箱验证码、注册登录、JWT access token、HttpOnly refresh cookie、三角色 RBAC
评论分析单条分析、TXT/XLSX 批量分析、批量模板下载、运行时模型能力检测、分析历史与详情
分析师复核全局评论检索、情感修正、审核备注、重点关注、趋势统计、报表导出
报告中心PDF / Excel / CSV 报告生成,异步入队,状态追踪,安全下载
管理后台用户管理、数据集沉淀与导出、模型注册与激活、训练中心、日志审计、数据库备份
模型训练Transformer 微调、Transformer 超参搜索、传统模型对比、TextCNN / BiLSTM 基线训练
自动重训按高置信度与人工审核样本构建批次,达阈值触发训练或记录提醒
运维治理Celery Beat 定时任务、日志保留策略、异常训练任务清理、路径安全校验
数据模型 · 8 张核心表
表名Django 模型说明
usersusers.User自定义用户表,支持 user / analyst / admin 三类角色
email_verification_codesusers.EmailVerificationCode邮箱验证码、用途、失败次数与过期控制
commentsanalysis.Comment评论正文、项目名、评分、类别、来源、评论时间
analysis_resultsanalysis.AnalysisResult情感类别、置信度、关键词、分析渠道、人工修正、审核信息、自动训练数据集引用
modelsanalysis.Model模型注册、版本、指标、路径、激活状态、运行时兼容性
training_runsadmin_panel.TrainingRun训练任务、数据集引用、配置快照、指标、产物与日志路径
reportsreports.Report报告类型、格式、状态、文件路径、摘要与入队信息
operation_logsadmin_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 与灰度。激活是全量切换,回退靠重新激活旧模型

工程验证

规模
后端 Python304 个文件 / 27,877 行
后端测试15 个文件 / 122 个测试
前端40 个 .vue:23 个页面 + 13 个组件 + 3 个布局,三角色路由
数据表8 张核心业务表
Celery2 个队列 + 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 checkruff checkpytest、前端 checkbuild

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_KEYJWT_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:8000

前端

cd sentiment_webapp
npm install
npm run check
npm run build
npm run dev

Vite 开发服务默认 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、版本与文档。

文档

公开发布说明 · 模型与数据资产 · 后端说明 · 前端说明 · 前端设计规范 · 手工测试素材

许可证

MIT License

About

One of the projects I keep around while learning from text, signals, and product feedback.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

SentimentPlatform

中文评论情感分析与洞察平台(云析智研)

模型推理只是其中一环。评论进来、分析落库、低置信度交人工复核、有效标注沉淀成数据集、
攒够阈值触发重训、新模型注册激活——这条闭环才是本项目要解决的问题。

CILicense: MITDjangoVueTests

关键决策 · 业务闭环 · 系统架构 · 能力边界 · 快速开始

SentimentPlatform 产品入口页

公开版首页。不含真实业务数据、账号、模型权重或训练资产。

落地场景

面向产品反馈分析、服务评价监测、电商评论归因、客服工单洞察这类中文文本分析场景。这类场景的共同特征决定了系统形态:

场景约束对系统的要求
模型对真实语料一定会判错必须有人工复核通道,且修正结果要能回流训练
标注成本高,但线上样本源源不断高置信度样本自动采信,低置信度才占用人力
训练是长任务,报告生成是短任务两类任务不能挤同一个队列
模型会迭代多个版本注册、激活、回退要能在后台完成,不靠改配置重启
三类使用者职责不同权限要在前端路由与后端接口两侧同时约束

要评估设计的人看关键决策能力边界;要跑起来的人直接跳快速开始

关键决策

决策选择代价
有效标注的判定置信度 ≥ 0.70 自动采信;低于阈值必须分析师复核过才入池阈值是经验值,调高会拖慢数据沉淀、调低会引入噪声标注
训练任务队列独立 training 队列 + 独立 worker,与默认队列物理隔离要多起一个 worker 进程;本地开发也得跑两个
重训触发攒满阈值(默认 5000 条)自动建训练任务,另有 signal 模式只记提醒自动模式下管理员是事后知情,不是事前批准
refresh tokenHttpOnly 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
Loading

运行时模型探测:激活的模型路径按文件系统特征判定产物类型 —— .joblib 文件识别为传统模型,.pt 且同目录有 vocab.jsonconfig_snapshot.json 识别为神经基线,目录内同时有 config.jsonmodel.safetensorstokenizer_config.json 识别为 Transformer。三者都不匹配则返回空类型,接口据此向前端报告能力缺失,而不是在推理时抛异常。

模型路径另有一道校验:必须落在 MODEL_WORKSPACE_DIR 之内,否则拒绝加载并记日志。

技术栈

组件
前端Vue 3.5 · Vite 8 · Pinia · Vue Router 5 · Element Plus · Tailwind CSS 4 · ECharts
前端安全DOMPurify —— 验证码以 SVG 下发,SVG 能携带脚本,渲染前必须净化
APIDjango 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 + 图形验证码)角色注册与申领(RBAC + 邮箱验证码)
登录页面注册页面

核心能力

能力域说明
账号与权限图形验证码、邮箱验证码、注册登录、JWT access token、HttpOnly refresh cookie、三角色 RBAC
评论分析单条分析、TXT/XLSX 批量分析、批量模板下载、运行时模型能力检测、分析历史与详情
分析师复核全局评论检索、情感修正、审核备注、重点关注、趋势统计、报表导出
报告中心PDF / Excel / CSV 报告生成,异步入队,状态追踪,安全下载
管理后台用户管理、数据集沉淀与导出、模型注册与激活、训练中心、日志审计、数据库备份
模型训练Transformer 微调、Transformer 超参搜索、传统模型对比、TextCNN / BiLSTM 基线训练
自动重训按高置信度与人工审核样本构建批次,达阈值触发训练或记录提醒
运维治理Celery Beat 定时任务、日志保留策略、异常训练任务清理、路径安全校验
数据模型 · 8 张核心表
表名Django 模型说明
usersusers.User自定义用户表,支持 user / analyst / admin 三类角色
email_verification_codesusers.EmailVerificationCode邮箱验证码、用途、失败次数与过期控制
commentsanalysis.Comment评论正文、项目名、评分、类别、来源、评论时间
analysis_resultsanalysis.AnalysisResult情感类别、置信度、关键词、分析渠道、人工修正、审核信息、自动训练数据集引用
modelsanalysis.Model模型注册、版本、指标、路径、激活状态、运行时兼容性
training_runsadmin_panel.TrainingRun训练任务、数据集引用、配置快照、指标、产物与日志路径
reportsreports.Report报告类型、格式、状态、文件路径、摘要与入队信息
operation_logsadmin_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 与灰度。激活是全量切换,回退靠重新激活旧模型

工程验证

规模
后端 Python304 个文件 / 27,877 行
后端测试15 个文件 / 122 个测试
前端40 个 .vue:23 个页面 + 13 个组件 + 3 个布局,三角色路由
数据表8 张核心业务表
Celery2 个队列 + 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 checkruff checkpytest、前端 checkbuild

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_KEYJWT_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:8000

前端

cd sentiment_webapp
npm install
npm run check
npm run build
npm run dev

Vite 开发服务默认 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、版本与文档。

文档

公开发布说明 · 模型与数据资产 · 后端说明 · 前端说明 · 前端设计规范 · 手工测试素材

许可证

MIT License

About

One of the projects I keep around while learning from text, signals, and product feedback.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

SentimentPlatform

中文评论情感分析与洞察平台(云析智研)

模型推理只是其中一环。评论进来、分析落库、低置信度交人工复核、有效标注沉淀成数据集、
攒够阈值触发重训、新模型注册激活——这条闭环才是本项目要解决的问题。

CILicense: MITDjangoVueTests

关键决策 · 业务闭环 · 系统架构 · 能力边界 · 快速开始

SentimentPlatform 产品入口页

公开版首页。不含真实业务数据、账号、模型权重或训练资产。

落地场景

面向产品反馈分析、服务评价监测、电商评论归因、客服工单洞察这类中文文本分析场景。这类场景的共同特征决定了系统形态:

场景约束对系统的要求
模型对真实语料一定会判错必须有人工复核通道,且修正结果要能回流训练
标注成本高,但线上样本源源不断高置信度样本自动采信,低置信度才占用人力
训练是长任务,报告生成是短任务两类任务不能挤同一个队列
模型会迭代多个版本注册、激活、回退要能在后台完成,不靠改配置重启
三类使用者职责不同权限要在前端路由与后端接口两侧同时约束

要评估设计的人看关键决策能力边界;要跑起来的人直接跳快速开始

关键决策

决策选择代价
有效标注的判定置信度 ≥ 0.70 自动采信;低于阈值必须分析师复核过才入池阈值是经验值,调高会拖慢数据沉淀、调低会引入噪声标注
训练任务队列独立 training 队列 + 独立 worker,与默认队列物理隔离要多起一个 worker 进程;本地开发也得跑两个
重训触发攒满阈值(默认 5000 条)自动建训练任务,另有 signal 模式只记提醒自动模式下管理员是事后知情,不是事前批准
refresh tokenHttpOnly 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
Loading

运行时模型探测:激活的模型路径按文件系统特征判定产物类型 —— .joblib 文件识别为传统模型,.pt 且同目录有 vocab.jsonconfig_snapshot.json 识别为神经基线,目录内同时有 config.jsonmodel.safetensorstokenizer_config.json 识别为 Transformer。三者都不匹配则返回空类型,接口据此向前端报告能力缺失,而不是在推理时抛异常。

模型路径另有一道校验:必须落在 MODEL_WORKSPACE_DIR 之内,否则拒绝加载并记日志。

技术栈

组件
前端Vue 3.5 · Vite 8 · Pinia · Vue Router 5 · Element Plus · Tailwind CSS 4 · ECharts
前端安全DOMPurify —— 验证码以 SVG 下发,SVG 能携带脚本,渲染前必须净化
APIDjango 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 + 图形验证码)角色注册与申领(RBAC + 邮箱验证码)
登录页面注册页面

核心能力

能力域说明
账号与权限图形验证码、邮箱验证码、注册登录、JWT access token、HttpOnly refresh cookie、三角色 RBAC
评论分析单条分析、TXT/XLSX 批量分析、批量模板下载、运行时模型能力检测、分析历史与详情
分析师复核全局评论检索、情感修正、审核备注、重点关注、趋势统计、报表导出
报告中心PDF / Excel / CSV 报告生成,异步入队,状态追踪,安全下载
管理后台用户管理、数据集沉淀与导出、模型注册与激活、训练中心、日志审计、数据库备份
模型训练Transformer 微调、Transformer 超参搜索、传统模型对比、TextCNN / BiLSTM 基线训练
自动重训按高置信度与人工审核样本构建批次,达阈值触发训练或记录提醒
运维治理Celery Beat 定时任务、日志保留策略、异常训练任务清理、路径安全校验
数据模型 · 8 张核心表
表名Django 模型说明
usersusers.User自定义用户表,支持 user / analyst / admin 三类角色
email_verification_codesusers.EmailVerificationCode邮箱验证码、用途、失败次数与过期控制
commentsanalysis.Comment评论正文、项目名、评分、类别、来源、评论时间
analysis_resultsanalysis.AnalysisResult情感类别、置信度、关键词、分析渠道、人工修正、审核信息、自动训练数据集引用
modelsanalysis.Model模型注册、版本、指标、路径、激活状态、运行时兼容性
training_runsadmin_panel.TrainingRun训练任务、数据集引用、配置快照、指标、产物与日志路径
reportsreports.Report报告类型、格式、状态、文件路径、摘要与入队信息
operation_logsadmin_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 与灰度。激活是全量切换,回退靠重新激活旧模型

工程验证

规模
后端 Python304 个文件 / 27,877 行
后端测试15 个文件 / 122 个测试
前端40 个 .vue:23 个页面 + 13 个组件 + 3 个布局,三角色路由
数据表8 张核心业务表
Celery2 个队列 + 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 checkruff checkpytest、前端 checkbuild

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_KEYJWT_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:8000

前端

cd sentiment_webapp
npm install
npm run check
npm run build
npm run dev

Vite 开发服务默认 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、版本与文档。

文档

公开发布说明 · 模型与数据资产 · 后端说明 · 前端说明 · 前端设计规范 · 手工测试素材

许可证

MIT License

About

One of the projects I keep around while learning from text, signals, and product feedback.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

SentimentPlatform

中文评论情感分析与洞察平台(云析智研)

模型推理只是其中一环。评论进来、分析落库、低置信度交人工复核、有效标注沉淀成数据集、
攒够阈值触发重训、新模型注册激活——这条闭环才是本项目要解决的问题。

CILicense: MITDjangoVueTests

关键决策 · 业务闭环 · 系统架构 · 能力边界 · 快速开始

SentimentPlatform 产品入口页

公开版首页。不含真实业务数据、账号、模型权重或训练资产。

落地场景

面向产品反馈分析、服务评价监测、电商评论归因、客服工单洞察这类中文文本分析场景。这类场景的共同特征决定了系统形态:

场景约束对系统的要求
模型对真实语料一定会判错必须有人工复核通道,且修正结果要能回流训练
标注成本高,但线上样本源源不断高置信度样本自动采信,低置信度才占用人力
训练是长任务,报告生成是短任务两类任务不能挤同一个队列
模型会迭代多个版本注册、激活、回退要能在后台完成,不靠改配置重启
三类使用者职责不同权限要在前端路由与后端接口两侧同时约束

要评估设计的人看关键决策能力边界;要跑起来的人直接跳快速开始

关键决策

决策选择代价
有效标注的判定置信度 ≥ 0.70 自动采信;低于阈值必须分析师复核过才入池阈值是经验值,调高会拖慢数据沉淀、调低会引入噪声标注
训练任务队列独立 training 队列 + 独立 worker,与默认队列物理隔离要多起一个 worker 进程;本地开发也得跑两个
重训触发攒满阈值(默认 5000 条)自动建训练任务,另有 signal 模式只记提醒自动模式下管理员是事后知情,不是事前批准
refresh tokenHttpOnly 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
Loading

运行时模型探测:激活的模型路径按文件系统特征判定产物类型 —— .joblib 文件识别为传统模型,.pt 且同目录有 vocab.jsonconfig_snapshot.json 识别为神经基线,目录内同时有 config.jsonmodel.safetensorstokenizer_config.json 识别为 Transformer。三者都不匹配则返回空类型,接口据此向前端报告能力缺失,而不是在推理时抛异常。

模型路径另有一道校验:必须落在 MODEL_WORKSPACE_DIR 之内,否则拒绝加载并记日志。

技术栈

组件
前端Vue 3.5 · Vite 8 · Pinia · Vue Router 5 · Element Plus · Tailwind CSS 4 · ECharts
前端安全DOMPurify —— 验证码以 SVG 下发,SVG 能携带脚本,渲染前必须净化
APIDjango 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 + 图形验证码)角色注册与申领(RBAC + 邮箱验证码)
登录页面注册页面

核心能力

能力域说明
账号与权限图形验证码、邮箱验证码、注册登录、JWT access token、HttpOnly refresh cookie、三角色 RBAC
评论分析单条分析、TXT/XLSX 批量分析、批量模板下载、运行时模型能力检测、分析历史与详情
分析师复核全局评论检索、情感修正、审核备注、重点关注、趋势统计、报表导出
报告中心PDF / Excel / CSV 报告生成,异步入队,状态追踪,安全下载
管理后台用户管理、数据集沉淀与导出、模型注册与激活、训练中心、日志审计、数据库备份
模型训练Transformer 微调、Transformer 超参搜索、传统模型对比、TextCNN / BiLSTM 基线训练
自动重训按高置信度与人工审核样本构建批次,达阈值触发训练或记录提醒
运维治理Celery Beat 定时任务、日志保留策略、异常训练任务清理、路径安全校验
数据模型 · 8 张核心表
表名Django 模型说明
usersusers.User自定义用户表,支持 user / analyst / admin 三类角色
email_verification_codesusers.EmailVerificationCode邮箱验证码、用途、失败次数与过期控制
commentsanalysis.Comment评论正文、项目名、评分、类别、来源、评论时间
analysis_resultsanalysis.AnalysisResult情感类别、置信度、关键词、分析渠道、人工修正、审核信息、自动训练数据集引用
modelsanalysis.Model模型注册、版本、指标、路径、激活状态、运行时兼容性
training_runsadmin_panel.TrainingRun训练任务、数据集引用、配置快照、指标、产物与日志路径
reportsreports.Report报告类型、格式、状态、文件路径、摘要与入队信息
operation_logsadmin_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 与灰度。激活是全量切换,回退靠重新激活旧模型

工程验证

规模
后端 Python304 个文件 / 27,877 行
后端测试15 个文件 / 122 个测试
前端40 个 .vue:23 个页面 + 13 个组件 + 3 个布局,三角色路由
数据表8 张核心业务表
Celery2 个队列 + 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 checkruff checkpytest、前端 checkbuild

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_KEYJWT_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:8000

前端

cd sentiment_webapp
npm install
npm run check
npm run build
npm run dev

Vite 开发服务默认 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、版本与文档。

文档

公开发布说明 · 模型与数据资产 · 后端说明 · 前端说明 · 前端设计规范 · 手工测试素材

许可证

MIT License

About

One of the projects I keep around while learning from text, signals, and product feedback.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

SentimentPlatform

中文评论情感分析与洞察平台(云析智研)

模型推理只是其中一环。评论进来、分析落库、低置信度交人工复核、有效标注沉淀成数据集、
攒够阈值触发重训、新模型注册激活——这条闭环才是本项目要解决的问题。

CILicense: MITDjangoVueTests

关键决策 · 业务闭环 · 系统架构 · 能力边界 · 快速开始

SentimentPlatform 产品入口页

公开版首页。不含真实业务数据、账号、模型权重或训练资产。

落地场景

面向产品反馈分析、服务评价监测、电商评论归因、客服工单洞察这类中文文本分析场景。这类场景的共同特征决定了系统形态:

场景约束对系统的要求
模型对真实语料一定会判错必须有人工复核通道,且修正结果要能回流训练
标注成本高,但线上样本源源不断高置信度样本自动采信,低置信度才占用人力
训练是长任务,报告生成是短任务两类任务不能挤同一个队列
模型会迭代多个版本注册、激活、回退要能在后台完成,不靠改配置重启
三类使用者职责不同权限要在前端路由与后端接口两侧同时约束

要评估设计的人看关键决策能力边界;要跑起来的人直接跳快速开始

关键决策

决策选择代价
有效标注的判定置信度 ≥ 0.70 自动采信;低于阈值必须分析师复核过才入池阈值是经验值,调高会拖慢数据沉淀、调低会引入噪声标注
训练任务队列独立 training 队列 + 独立 worker,与默认队列物理隔离要多起一个 worker 进程;本地开发也得跑两个
重训触发攒满阈值(默认 5000 条)自动建训练任务,另有 signal 模式只记提醒自动模式下管理员是事后知情,不是事前批准
refresh tokenHttpOnly 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
Loading

运行时模型探测:激活的模型路径按文件系统特征判定产物类型 —— .joblib 文件识别为传统模型,.pt 且同目录有 vocab.jsonconfig_snapshot.json 识别为神经基线,目录内同时有 config.jsonmodel.safetensorstokenizer_config.json 识别为 Transformer。三者都不匹配则返回空类型,接口据此向前端报告能力缺失,而不是在推理时抛异常。

模型路径另有一道校验:必须落在 MODEL_WORKSPACE_DIR 之内,否则拒绝加载并记日志。

技术栈

组件
前端Vue 3.5 · Vite 8 · Pinia · Vue Router 5 · Element Plus · Tailwind CSS 4 · ECharts
前端安全DOMPurify —— 验证码以 SVG 下发,SVG 能携带脚本,渲染前必须净化
APIDjango 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 + 图形验证码)角色注册与申领(RBAC + 邮箱验证码)
登录页面注册页面

核心能力

能力域说明
账号与权限图形验证码、邮箱验证码、注册登录、JWT access token、HttpOnly refresh cookie、三角色 RBAC
评论分析单条分析、TXT/XLSX 批量分析、批量模板下载、运行时模型能力检测、分析历史与详情
分析师复核全局评论检索、情感修正、审核备注、重点关注、趋势统计、报表导出
报告中心PDF / Excel / CSV 报告生成,异步入队,状态追踪,安全下载
管理后台用户管理、数据集沉淀与导出、模型注册与激活、训练中心、日志审计、数据库备份
模型训练Transformer 微调、Transformer 超参搜索、传统模型对比、TextCNN / BiLSTM 基线训练
自动重训按高置信度与人工审核样本构建批次,达阈值触发训练或记录提醒
运维治理Celery Beat 定时任务、日志保留策略、异常训练任务清理、路径安全校验
数据模型 · 8 张核心表
表名Django 模型说明
usersusers.User自定义用户表,支持 user / analyst / admin 三类角色
email_verification_codesusers.EmailVerificationCode邮箱验证码、用途、失败次数与过期控制
commentsanalysis.Comment评论正文、项目名、评分、类别、来源、评论时间
analysis_resultsanalysis.AnalysisResult情感类别、置信度、关键词、分析渠道、人工修正、审核信息、自动训练数据集引用
modelsanalysis.Model模型注册、版本、指标、路径、激活状态、运行时兼容性
training_runsadmin_panel.TrainingRun训练任务、数据集引用、配置快照、指标、产物与日志路径
reportsreports.Report报告类型、格式、状态、文件路径、摘要与入队信息
operation_logsadmin_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 与灰度。激活是全量切换,回退靠重新激活旧模型

工程验证

规模
后端 Python304 个文件 / 27,877 行
后端测试15 个文件 / 122 个测试
前端40 个 .vue:23 个页面 + 13 个组件 + 3 个布局,三角色路由
数据表8 张核心业务表
Celery2 个队列 + 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 checkruff checkpytest、前端 checkbuild

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_KEYJWT_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:8000

前端

cd sentiment_webapp
npm install
npm run check
npm run build
npm run dev

Vite 开发服务默认 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、版本与文档。

文档

公开发布说明 · 模型与数据资产 · 后端说明 · 前端说明 · 前端设计规范 · 手工测试素材

许可证

MIT License

About

One of the projects I keep around while learning from text, signals, and product feedback.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

SentimentPlatform

中文评论情感分析与洞察平台(云析智研)

模型推理只是其中一环。评论进来、分析落库、低置信度交人工复核、有效标注沉淀成数据集、
攒够阈值触发重训、新模型注册激活——这条闭环才是本项目要解决的问题。

CILicense: MITDjangoVueTests

关键决策 · 业务闭环 · 系统架构 · 能力边界 · 快速开始

SentimentPlatform 产品入口页

公开版首页。不含真实业务数据、账号、模型权重或训练资产。

落地场景

面向产品反馈分析、服务评价监测、电商评论归因、客服工单洞察这类中文文本分析场景。这类场景的共同特征决定了系统形态:

场景约束对系统的要求
模型对真实语料一定会判错必须有人工复核通道,且修正结果要能回流训练
标注成本高,但线上样本源源不断高置信度样本自动采信,低置信度才占用人力
训练是长任务,报告生成是短任务两类任务不能挤同一个队列
模型会迭代多个版本注册、激活、回退要能在后台完成,不靠改配置重启
三类使用者职责不同权限要在前端路由与后端接口两侧同时约束

要评估设计的人看关键决策能力边界;要跑起来的人直接跳快速开始

关键决策

决策选择代价
有效标注的判定置信度 ≥ 0.70 自动采信;低于阈值必须分析师复核过才入池阈值是经验值,调高会拖慢数据沉淀、调低会引入噪声标注
训练任务队列独立 training 队列 + 独立 worker,与默认队列物理隔离要多起一个 worker 进程;本地开发也得跑两个
重训触发攒满阈值(默认 5000 条)自动建训练任务,另有 signal 模式只记提醒自动模式下管理员是事后知情,不是事前批准
refresh tokenHttpOnly 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
Loading

运行时模型探测:激活的模型路径按文件系统特征判定产物类型 —— .joblib 文件识别为传统模型,.pt 且同目录有 vocab.jsonconfig_snapshot.json 识别为神经基线,目录内同时有 config.jsonmodel.safetensorstokenizer_config.json 识别为 Transformer。三者都不匹配则返回空类型,接口据此向前端报告能力缺失,而不是在推理时抛异常。

模型路径另有一道校验:必须落在 MODEL_WORKSPACE_DIR 之内,否则拒绝加载并记日志。

技术栈

组件
前端Vue 3.5 · Vite 8 · Pinia · Vue Router 5 · Element Plus · Tailwind CSS 4 · ECharts
前端安全DOMPurify —— 验证码以 SVG 下发,SVG 能携带脚本,渲染前必须净化
APIDjango 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 + 图形验证码)角色注册与申领(RBAC + 邮箱验证码)
登录页面注册页面

核心能力

能力域说明
账号与权限图形验证码、邮箱验证码、注册登录、JWT access token、HttpOnly refresh cookie、三角色 RBAC
评论分析单条分析、TXT/XLSX 批量分析、批量模板下载、运行时模型能力检测、分析历史与详情
分析师复核全局评论检索、情感修正、审核备注、重点关注、趋势统计、报表导出
报告中心PDF / Excel / CSV 报告生成,异步入队,状态追踪,安全下载
管理后台用户管理、数据集沉淀与导出、模型注册与激活、训练中心、日志审计、数据库备份
模型训练Transformer 微调、Transformer 超参搜索、传统模型对比、TextCNN / BiLSTM 基线训练
自动重训按高置信度与人工审核样本构建批次,达阈值触发训练或记录提醒
运维治理Celery Beat 定时任务、日志保留策略、异常训练任务清理、路径安全校验
数据模型 · 8 张核心表
表名Django 模型说明
usersusers.User自定义用户表,支持 user / analyst / admin 三类角色
email_verification_codesusers.EmailVerificationCode邮箱验证码、用途、失败次数与过期控制
commentsanalysis.Comment评论正文、项目名、评分、类别、来源、评论时间
analysis_resultsanalysis.AnalysisResult情感类别、置信度、关键词、分析渠道、人工修正、审核信息、自动训练数据集引用
modelsanalysis.Model模型注册、版本、指标、路径、激活状态、运行时兼容性
training_runsadmin_panel.TrainingRun训练任务、数据集引用、配置快照、指标、产物与日志路径
reportsreports.Report报告类型、格式、状态、文件路径、摘要与入队信息
operation_logsadmin_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 与灰度。激活是全量切换,回退靠重新激活旧模型

工程验证

规模
后端 Python304 个文件 / 27,877 行
后端测试15 个文件 / 122 个测试
前端40 个 .vue:23 个页面 + 13 个组件 + 3 个布局,三角色路由
数据表8 张核心业务表
Celery2 个队列 + 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 checkruff checkpytest、前端 checkbuild

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_KEYJWT_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:8000

前端

cd sentiment_webapp
npm install
npm run check
npm run build
npm run dev

Vite 开发服务默认 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、版本与文档。

文档

公开发布说明 · 模型与数据资产 · 后端说明 · 前端说明 · 前端设计规范 · 手工测试素材

许可证

MIT License

About

One of the projects I keep around while learning from text, signals, and product feedback.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages