肺癌数据可视化分析预测系统
Lung Cancer Intelligence Atlas --- 基于 Django + Vue 3 的双语数据可视化分析与机器学习预测平台
项目代号 : CancerAtlas | 版本 : v1.0 | 设计周期: 2024-2026
目录
- 项目概述
- 系统架构
- 技术栈详解
- 目录结构
- 数据库设计
- [ER 实体关系图](#ER 实体关系图)
- 数据流程图
- [API 接口文档](#API 接口文档)
- 前端架构
- 机器学习模块
- 可视化方案
- 存储设计
- 安全设计
- 部署架构
- 快速启动
- [Django Admin 使用指南](#Django Admin 使用指南)
- 环境变量说明
1. 项目概述
1.1 背景与目标
本系统面向课程设计答辩场景,围绕 50,000 条肺癌患者脱敏数据,实现以下核心目标:
| 目标 | 描述 |
|---|---|
| 数据管理 | 支持 CSV 批量导入、数据清洗、患者记录增删改查 |
| 多维分析 | 提供 14 种 ECharts 可视化图表,支持双语言切换 |
| 机器学习 | 支持模型训练、评估、激活与在线实时预测 |
| 风险预测 | 基于 9 项临床特征输出肺癌风险概率与干预建议 |
| 个性化推荐 | 基于欧氏距离匹配相似病例,提供针对性干预方案 |
1.2 系统角色
┌─────────────────────────────────────────────────────────────┐
│ 系统用户 │
├──────────────────────────┬──────────────────────────────────┤
│ 普通用户 (User) │ 管理员 (Admin) │
├──────────────────────────┼──────────────────────────────────┤
│ • 查看数据可视化分析 │ • 管理患者数据(增删改查) │
│ • 在线肺癌风险预测 │ • 触发模型训练与评估 │
│ • 查看预测历史与报告 │ • 管理预测模型(激活/删除) │
│ • 收藏预测报告/相似病例 │ • 管理干预建议模板 │
│ • 个人中心(头像/密码) │ • 系统状态监控 │
│ • 注册/登录 │ • Django Admin 数据后台 │
└──────────────────────────┴──────────────────────────────────┘
1.3 数据规格
| 指标 | 值 |
|---|---|
| 患者数据总量 | 50,000 条 |
| 数据来源 | 脱敏模拟数据 (data/lung_cancer_dataset.csv) |
| 特征维度 | 9 项临床特征 + 1 项目标变量 |
| 阳性率 | ~68.7% |
| 训练样本 | 45,000 条 |
| 测试样本 | 5,000 条 |









2. 系统架构
2.1 整体架构图
┌─────────────────────────────────────┐
│ 用户终端 │
│ ┌───────────────────────────────┐ │
│ │ Web Browser (Chrome) │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ Vue 3 SPA (Port 5173) │ │ │
│ │ │ • Router (路由) │ │ │
│ │ │ • Pinia (状态管理) │ │ │
│ │ │ • ECharts (可视化) │ │ │
│ │ │ • Axios (HTTP客户端) │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────┬───────────────────┘
│ HTTPS / HTTP
│ REST API (JWT)
┌─────────────────────────────────────────────▼───────────────────┐
│ 负载均衡层 (Nginx) │
│ (可选,生产环境部署) │
└─────────────────────────────────────────────┬───────────────────┘
│
┌─────────────────────────────────────────────▼───────────────────┐
│ 应用服务层 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Django 后端服务 (Port 8000) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ DRF │ │ SimpleJWT│ │ SimpleUI │ │ scikit │ │ │
│ │ │ (API层) │ │ (认证) │ │ (后台) │ │ (机器学习)│ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ accounts │ │patients │ │analytics │ │prediction│ │ │
│ │ │ (用户) │ │(患者) │ │(分析) │ │(预测) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ apps/ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────┬───────────────────┘
│
┌─────────────────────────────────────────────▼───────────────────┐
│ 数据存储层 │
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ MySQL 8.x │ │ 文件存储 (本地) │ │
│ │ design_336_ │ │ ┌────────────────────────┐ │ │
│ │ canner │ │ │ artifacts/ (模型文件) │ │ │
│ │ │ │ │ media/avatars/ (头像) │ │ │
│ │ • UserProfile │ │ │ staticfiles/ (静态资源) │ │ │
│ │ • PatientRecord │ │ └────────────────────────┘ │ │
│ │ • ModelArtifact │ │ │ │
│ │ • PredictionRecord│ │ │ │
│ │ • Recommendation │ │ │ │
│ │ • FavoriteItem │ │ │ │
│ └──────────────────┘ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
2.2 前端请求处理流程
用户操作 (点击按钮)
│
▼
┌──────────────────┐
│ Vue Router │ ← 路由匹配 (PublicLayout / AdminLayout)
│ (路由守卫) │ ← 路由拦截,检查登录状态 & 角色权限
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Pinia Store │ ← auth.ts 存储 JWT token 和用户信息
│ (状态管理) │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Axios 拦截器 │ ← 请求拦截器:附加 Authorization header
│ (HTTP客户端) │ ← 响应拦截器:处理 401 重定向、错误提示
└────────┬─────────┘
│
│ 携带 JWT Bearer Token
▼
┌─────────────────────────────────────────┐
│ Django REST Framework │
│ ┌─────────────────────────────────────┐ │
│ │ authentication.py (JWT 认证) │ │
│ │ permission.py (权限判定) │ │
│ │ throttling.py (限流控制) │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ ViewSet / APIView │ │
│ │ (业务逻辑处理) │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Serializers (序列化) │ │
│ │ (数据验证 / 格式化输出) │ │
│ └─────────────────────────────────────┘ │
└──────────────────────┬────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ MySQL Database │
│ (CRUD 操作 + 复杂查询) │
└─────────────────────────────────────────┘
3. 技术栈详解
3.1 后端技术栈
| 层级 | 技术选型 | 版本 | 说明 |
|---|---|---|---|
| 框架 | Django | 4.2.29 | Python Web 框架 |
| REST API | DRF | 3.16.1 | Django REST Framework |
| 认证 | SimpleJWT | 5.5.1 | JWT Token 认证,支持 refresh token 黑名单 |
| 后台主题 | django-simpleui | 2026.1.13 | 美化 Django Admin 界面 |
| 数据库 | MySQL + PyMySQL | 8.x / 1.1.1 | 关系型数据库 + Python 驱动 |
| 机器学习 | scikit-learn | 1.6.1 | HistGradientBoostingClassifier |
| 模型持久化 | joblib | 1.5.3 | 模型序列化和反序列化 |
| 图片处理 | Pillow | ≥10.0 | 头像上传处理 |
| 环境变量 | python-dotenv | 1.0.1 | .env 文件加载 |
| 跨域 | django-cors-headers | 4.9.0 | CORS 头管理 |
3.2 前端技术栈
| 层级 | 技术选型 | 版本 | 说明 |
|---|---|---|---|
| 框架 | Vue 3 | 3.x | Composition API |
| 语言 | TypeScript | 5.x | 类型安全 |
| 构建工具 | Vite | 5.x | 快速冷启动 |
| 路由 | Vue Router | 4.x | SPA 路由管理 |
| 状态管理 | Pinia | 2.x | 响应式状态管理 |
| 图表 | ECharts | 5.x | 14 种可视化图表 |
| HTTP | Axios | 1.x | HTTP 请求拦截 |
| 样式 | SCSS | - | CSS 预处理器 |
3.3 技术选型理由
Django REST Framework
- 与 Django 无缝集成,ORM 支持完善
- 强大的 Serializer 机制,支持嵌套和验证
- 完善的认证、权限、限流体系
SimpleJWT
- 无状态认证,适合分布式部署
- Access Token + Refresh Token 双令牌机制
- 支持 Token 黑名单,实现强制登出
HistGradientBoostingClassifier
- 相比 RandomForest 有更高的准确率
- 支持缺失值自动处理
- 训练速度快,适合中大规模数据集
Vue 3 + Composition API
<script setup>语法糖,代码更简洁- 更好的 TypeScript 支持
- 更小的包体积和更快的渲染速度
ECharts 5
- 丰富的图表类型,支持个性化定制
- 大数据量下的性能优化(内置数据采样)
- 响应式设计,适配不同屏幕
4. 目录结构
cancer/ # 项目根目录
│
├── backend/ # Django 后端项目
│ ├── apps/ # 业务应用模块
│ │ ├── accounts/ # 用户账户模块
│ │ │ ├── admin.py # Admin 配置(简化,unregister移至apps.py)
│ │ │ ├── apps.py # App 配置 + ready()隐藏多余Admin模型
│ │ │ ├── models.py # UserProfile 模型
│ │ │ ├── serializers.py # DRF 序列化器(登录/注册/头像/资料)
│ │ │ ├── urls.py # 路由:/auth/*
│ │ │ ├── views.py # API视图(登录/注册/头像/改密/资料)
│ │ │ └── migrations/
│ │ ├── patients/ # 患者数据模块
│ │ │ ├── admin.py # 患者记录Admin(11字段+过滤器+搜索)
│ │ │ ├── apps.py
│ │ │ ├── constants.py # CHOICES常量(性别/暴露/饮酒等)
│ │ │ ├── models.py # PatientRecord模型(13字段+verbose_name)
│ │ │ ├── serializers.py
│ │ │ ├── urls.py
│ │ │ ├── utils.py # 工具函数
│ │ │ ├── views.py # 患者记录视图(分页+过滤)
│ │ │ └── migrations/
│ │ ├── analytics/ # 数据分析模块
│ │ │ ├── admin.py
│ │ │ ├── apps.py
│ │ │ ├── models.py # 暂无持久化模型(纯计算密集型)
│ │ │ ├── urls.py
│ │ │ └── views.py # Overview/Distribution/Advanced API
│ │ ├── prediction/ # 预测管理模块
│ │ │ ├── admin.py # 模型/预测记录/推荐模板Admin
│ │ │ ├── apps.py
│ │ │ ├── models.py # ModelArtifact/PredictionRecord/RecommendationTemplate
│ │ │ ├── serializers.py
│ │ │ ├── urls.py
│ │ │ ├── views.py # 训练/预测/历史视图
│ │ │ ├── services.py # 模型推理服务(加载/预测/评估)
│ │ │ ├── recommendations.py # 干预建议生成逻辑
│ │ │ ├── prediction_urls.py # 预测相关路由
│ │ │ ├── model_urls.py # 模型相关路由
│ │ │ └── migrations/
│ │ └── engagement/ # 收藏互动模块
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── models.py # FavoriteItem模型
│ │ ├── serializers.py
│ │ ├── urls.py
│ │ ├── views.py
│ │ └── migrations/
│ ├── config/ # 项目配置目录
│ │ ├── settings.py # Django核心配置(含simpleui模板路径)
│ │ ├── urls.py # 根路由配置
│ │ ├── wsgi.py
│ │ └── asgi.py
│ ├── artifacts/ # 模型文件存储(.pkl)
│ │ └── models/ # 训练生成的模型文件
│ ├── media/ # 用户上传文件
│ │ └── avatars/ # 头像存储目录
│ ├── staticfiles/ # collectstatic收集的静态文件
│ ├── manage.py
│ └── requirements.txt
│
├── frontend/ # Vue 3 前端项目
│ ├── src/
│ │ ├── App.vue # 根组件
│ │ ├── main.ts # 应用入口(Pinia/Router初始化)
│ │ ├── router/
│ │ │ └── index.ts # 路由配置(PublicLayout/AdminLayout)
│ │ ├── stores/
│ │ │ └── auth.ts # Pinia认证状态管理
│ │ ├── services/
│ │ │ └── api.ts # Axios实例(拦截器/JWT)
│ │ ├── types/
│ │ │ └── api.ts # TypeScript接口定义
│ │ ├── views/
│ │ │ ├── HomeView.vue # 首页
│ │ │ ├── AnalysisView.vue # 数据分析页(14种图表)
│ │ │ ├── PredictionView.vue # 在线预测页
│ │ │ ├── FavoritesView.vue # 收藏夹页
│ │ │ ├── ProfileView.vue # 个人中心
│ │ │ ├── LoginView.vue # 登录页
│ │ │ ├── RegisterView.vue # 注册页
│ │ │ ├── AdminDashboardView.vue # 后台概览页
│ │ │ └── layouts/
│ │ │ ├── PublicLayout.vue # 前台公共布局(含顶部导航栏+图标)
│ │ │ └── AdminLayout.vue # 后台公共布局(含顶部导航栏+图标)
│ │ ├── components/
│ │ │ ├── MetricCard.vue # 指标卡片组件
│ │ │ ├── ChartPanel.vue # 图表面板组件
│ │ │ └── FavoriteButton.vue # 收藏按钮组件
│ │ ├── styles/
│ │ │ └── main.scss # 全局SCSS样式
│ │ └── utils/
│ │ └── format.ts # 格式化工具函数
│ ├── public/
│ ├── index.html
│ ├── vite.config.ts
│ ├── package.json
│ ├── tsconfig.json
│ └── README.md
│
├── scripts/
│ └── bootstrap_mysql.sh # 数据库初始化脚本
│
├── data/
│ └── lung_cancer_dataset.csv # 50,000条患者脱敏数据
│
├── .env.example # 环境变量示例
├── .gitignore
└── README.md
5. 数据库设计
5.1 数据库信息
sql
CREATE DATABASE design_336_canner
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
5.2 模型详表
5.2.1 accounts.UserProfile(用户画像)
| 字段名 | 类型 | 约束 | verbose_name | 说明 |
|---|---|---|---|---|
| user | OneToOneField | FK → auth.User, on_delete=CASCADE | 关联用户 | Django内置User的一对一扩展 |
| role | CharField | max_length=20, choices=role_choices | 角色 | admin / user |
| display_name | CharField | max_length=150 | 显示名称 | 个人中心展示名 |
| bio | TextField | blank=True, default='' | 个人简介 | 可选的个人描述 |
| avatar | ImageField | upload_to='avatars/', blank=True | 头像 | 上传至 media/avatars/ |
| avatar_color | CharField | max_length=7, default='#4A5568' | 头像颜色 | SVG头像备用颜色 |
| created_at | DateTimeField | auto_now_add=True | 创建时间 | 注册时间戳 |
| updated_at | DateTimeField | auto_now=True | 更新时间 | 最后修改时间 |
索引: user (Unique)
5.2.2 patients.PatientRecord(患者记录)
| 字段名 | 类型 | 约束 | verbose_name | 说明 |
|---|---|---|---|---|
| id | AutoField | primary_key=True | ID | 主键 |
| patient_id | PositiveIntegerField | unique=True | 患者ID | 外部唯一标识 |
| age | PositiveIntegerField | validators=[MinValueValidator(0), MaxValueValidator(120)] | 年龄 | 0-120岁 |
| gender | CharField | max_length=10, choices=GenderChoices | 性别 | Male / Female |
| pack_years | FloatField | validators=[MinValueValidator(0)] | 吸烟包年 | 吸烟年数×每日包数 |
| radon_exposure | CharField | max_length=20, choices=ExposureChoices | 氡暴露等级 | Low / Medium / High |
| asbestos_exposure | CharField | max_length=10, choices=BooleanChoices | 石棉暴露 | Yes / No |
| secondhand_smoke_exposure | CharField | max_length=10, choices=BooleanChoices | 二手烟暴露 | Yes / No |
| copd_diagnosis | CharField | max_length=10, choices=BooleanChoices | 慢阻肺诊断 | Yes / No |
| alcohol_consumption | CharField | max_length=20, choices=AlcoholChoices | 饮酒情况 | Non-drinker / Occasional / Moderate / Heavy |
| family_history | CharField | max_length=10, choices=BooleanChoices | 肺癌家族史 | Yes / No |
| lung_cancer | CharField | max_length=10, choices=LungCancerChoices | 肺癌诊断 | Yes / No |
| created_at | DateTimeField | auto_now_add=True | 创建时间 | 导入时间戳 |
索引: patient_id (Unique), lung_cancer, gender, radon_exposure
5.2.3 prediction.ModelArtifact(模型训练产物)
| 字段名 | 类型 | 约束 | verbose_name | 说明 |
|---|---|---|---|---|
| id | AutoField | primary_key=True | ID | 主键 |
| name | CharField | max_length=200 | 模型名称 | 如 "Lung Cancer Risk Model" |
| algorithm | CharField | max_length=100 | 算法名称 | 如 hist_gradient_boosting |
| version | CharField | max_length=50, unique=True | 版本号 | 语义化版本,如 "v1.0.0" |
| metrics | JSONField | default=dict | 评估指标 | {auc, recall, precision, f1} |
| confusion_matrix | JSONField | default=list | 混淆矩阵 | 2x2矩阵 [[TN, FP], [FN, TP]] |
| feature_importance | JSONField | default=list | 特征重要性 | [{name, importance}] |
| feature_names | JSONField | default=list | 特征名称 | 特征名列表 |
| artifact_path | CharField | max_length=500 | 模型文件路径 | 指向 artifacts/models/*.pkl |
| training_rows | PositiveIntegerField | default=0 | 训练样本数 | 本次训练的样本总量 |
| training_positive_rate | FloatField | default=0.0 | 训练阳性率 | 正例占比 |
| is_active | BooleanField | default=False, db_index=True | 是否激活 | 同一时间仅一个模型激活 |
| trained_at | DateTimeField | auto_now_add=True | 训练时间 | 训练完成时间戳 |
索引: is_active, version
5.2.4 prediction.PredictionRecord(预测记录)
| 字段名 | 类型 | 约束 | verbose_name | 说明 |
|---|---|---|---|---|
| id | AutoField | primary_key=True | ID | 主键 |
| user | ForeignKey | FK → auth.User, on_delete=SET_NULL, null=True | 预测用户 | 可为空(匿名预测) |
| model | ForeignKey | FK → ModelArtifact, on_delete=SET_NULL, null=True | 使用模型 | 预测时引用的模型版本 |
| input_payload | JSONField | default=dict | 输入特征 | 原始9项输入特征 |
| probability | FloatField | validators=[MinValueValidator(0), MaxValueValidator(1)] | 肺癌概率 | 0.0 ~ 1.0 |
| risk_level | CharField | max_length=10, choices=RiskLevelChoices | 风险等级 | low / medium / high |
| predicted_label | CharField | max_length=10 | 预测标签 | Yes / No |
| top_factors | JSONField | default=list | 关键风险因子 | [{factor, contribution}] |
| recommendations | JSONField | default=list | 干预建议 | [{code, title_zh, severity}] |
| similar_cases | JSONField | default=list | 相似病例 | [{patient_id, distance, lung_cancer}] |
| report_summary | TextField | blank=True | 报告摘要 | 生成的自然语言摘要 |
| created_at | DateTimeField | auto_now_add=True | 预测时间 | 预测时间戳 |
索引: user, created_at, risk_level
5.2.5 prediction.RecommendationTemplate(干预建议模板)
| 字段名 | 类型 | 约束 | verbose_name | 说明 |
|---|---|---|---|---|
| id | AutoField | primary_key=True | ID | 主键 |
| code | CharField | max_length=50, unique=True | 模板编码 | 如 "RADON_HIGH" |
| trigger_field | CharField | max_length=50 | 触发字段 | 匹配的模型输入字段名 |
| trigger_value | CharField | max_length=100 | 触发值 | 字段的匹配值 |
| title_zh | CharField | max_length=200 | 中文标题 | 如 "氡暴露风险" |
| title_en | CharField | max_length=200 | 英文标题 | English title |
| description_zh | TextField | blank=True | 中文描述 | 详细干预方案 |
| description_en | TextField | blank=True | 英文描述 | English description |
| severity | PositiveSmallIntegerField | default=3 | 严重程度 | 1-5,5为最严重 |
| is_active | BooleanField | default=True | 是否启用 | 未启用则不触发 |
索引: code (Unique), trigger_field, is_active
5.2.6 engagement.FavoriteItem(收藏记录)
| 字段名 | 类型 | 约束 | verbose_name | 说明 |
|---|---|---|---|---|
| id | AutoField | primary_key=True | ID | 主键 |
| user | ForeignKey | FK → auth.User, on_delete=CASCADE | 关联用户 | 收藏者 |
| target_type | CharField | max_length=30, choices=FavoriteTypeChoices | 收藏类型 | prediction_report / similar_case |
| target_id | CharField | max_length=100 | 目标ID | 被收藏项的ID |
| snapshot | JSONField | default=dict | 收藏快照 | 收藏时刻的完整数据副本 |
| created_at | DateTimeField | auto_now_add=True | 收藏时间 | 收藏时间戳 |
索引: user + target_type + target_id (Unique), user, created_at
6. ER 实体关系图
6.1 完整 ER 图(ASCII 格式)
┌─────────────────────────────────────────────────────────────────────────────┐
│ ER Entity Relationship Diagram │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────┐ ┌──────────────────────┐
│ auth.User │ │ auth.User (扩展) │
│ (Django 内置) │ │ │
├──────────────────────┤ ├──────────────────────┤
│ □ id (PK) │1 1 │ □ user (FK, U) │
│ □ username │────────▶│ □ role │
│ □ password │ │ □ display_name │
│ □ email │ │ □ bio │
│ □ first_name │ │ □ avatar │
│ □ last_name │ │ □ avatar_color │
│ □ is_staff │ │ □ created_at │
│ □ is_active │ │ □ updated_at │
└──────────────────────┘ └──────────┬───────────┘
│
│ 1:1
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ accounts 模块 │
│ ┌─────────────────┐ ┌────────────────────┐ ┌─────────────────┐ │
│ │ UserProfile │ │ Django Auth │ │ rest_framework │ │
│ │ │ │ User │ │ .simplejwt │ │
│ ├─────────────────┤ └────────────────────┘ ├─────────────────┤ │
│ │[U]user──────────┼──1:1──▶ (Django内置) │ OutstandingToken│ │
│ │ role │ │ BlacklistedToken│ │
│ │ display_name │ │ TokenProxy │ │
│ │ bio │ └─────────────────┘ │
│ │ avatar │ │
│ └─────────────────┘ │
└───────────────────────────────────────────────────────────────────────────┘
│
┌──────────────────────────┤
│ │
▼ ▼
┌───────────────────────────────────────────────────────────────────────────┐
│ patients 模块 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ PatientRecord │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │[U] patient_id (U) pack_years secondhand_smoke_ │ │
│ │ age radon_exposure exposure (Yes/No) │ │
│ │ gender (M/F) asbestos_exposure copd_diagnosis │ │
│ │ family_history alcohol_consumption lung_cancer │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────┘
│
│ (提供特征数据用于ML训练和相似病例匹配)
▼
┌───────────────────────────────────────────────────────────────────────────┐
│ prediction 模块 │
│ ┌──────────────┐ ┌──────────────────┐ ┌────────────────────────┐ │
│ │ModelArtifact │ │PredictionRecord │ │RecommendationTemplate │ │
│ ├──────────────┤ ├──────────────────┤ ├────────────────────────┤ │
│ │[U] version │ │[FK] user ────────┼───▶│ auth.User (可空) │ │
│ │ name │ │[FK] model ────────┼───▶│ ModelArtifact (可空) │ │
│ │ algorithm │ │ probability │ │[U] code │ │
│ │ metrics │ │ risk_level │ │ trigger_field │ │
│ │ confusion_ │ │ predicted_label │ │ trigger_value │ │
│ │ matrix │ │ top_factors │ │ title_zh / title_en │ │
│ │ feature_ │ │ recommendations │ │ description_zh/en │ │
│ │ importance │ │ similar_cases │ │ severity (1-5) │ │
│ │ feature_ │ │ report_summary │ │ is_active │ │
│ │ names │ │ created_at │ └────────────────────────┘ │
│ │ artifact_path│ └──────────────────┘ ▲ │
│ │ training_rows│ │ │
│ │ is_active │ ┌──────────────────┐ │ (按trigger匹配) │
│ │ trained_at │ │Recommendation │─────────────┘ │
│ └──────────────┘ │ Generator │ │
│ ▲ └──────────────────┘ │
│ │ │
│ │ (加载 .pkl) │
└─────────┼───────────────────────────────────────────────────────────────────┘
│
┌─────────▼─────────────────────────────────────────────────────────────────┐
│ engagement 模块 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ FavoriteItem │ │
│ ├─────────────────────────────────────────────────────────────────────┤ │
│ │[FK] user ───────────────▶ auth.User │ │
│ │ target_type (预测报告/相似病例) │ │
│ │[U] target_id │ │
│ │ snapshot (JSON, 收藏时数据快照) │ │
│ │ created_at │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
6.2 实体关系说明
| 关系 | 类型 | 说明 |
|---|---|---|
| User ↔ UserProfile | 1:1 | Django User 的个人信息扩展 |
| User → PredictionRecord | 1:N | 用户拥有多条预测历史 |
| ModelArtifact → PredictionRecord | 1:N | 同一模型可产生多条预测 |
| PredictionRecord → RecommendationTemplate | N:1 (触发匹配) | 根据 top_factors 触发多个模板 |
| User → FavoriteItem | 1:N | 用户可收藏多个项目 |
| PatientRecord → ModelArtifact | ML 训练 | PatientRecord 提供训练特征数据 |
6.3 数据库物理模型(表结构)
┌─────────────────────────────────────────────────────────────────────────────┐
│ Database Physical Schema │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────┐
│ accounts_userprofile │
├─────────────────────────────────┤
│ id INT (PK) │
│ user_id INT (U,FK) │
│ role VARCHAR(20)│
│ display_name VARCHAR(150│
│ bio TEXT │
│ avatar VARCHAR(100│
│ avatar_color VARCHAR(7) │
│ created_at DATETIME │
│ updated_at DATETIME │
└─────────────────────────────────┘
│
│ 1:1
▼
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ auth_user │ │ patients_patientrecord │
├─────────────────────────────────┤ ├─────────────────────────────────┤
│ id INT (PK) │ │ id INT (PK) │
│ username VARCHAR(150│ │ patient_id INT (U) │
│ password VARCHAR(128│ │ age INT │
│ email VARCHAR(254│ │ gender VARCHAR(10)│
│ first_name VARCHAR(150│ │ pack_years FLOAT │
│ last_name VARCHAR(150│ │ radon_exposure VARCHAR(20│
│ is_staff BOOL │ │ asbestos_exposure VARCHAR(10│
│ is_active BOOL │ │ secondhand_smoke... VARCHAR(10│
│ date_joined DATETIME │ │ copd_diagnosis VARCHAR(10│
└─────────────────────────────────┘ │ alcohol_consumption VARCHAR(20│
│ family_history VARCHAR(10│
│ lung_cancer VARCHAR(10│
│ created_at DATETIME │
└─────────────────────────────────┘
│
│ 特征数据
▼
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ prediction_modelartifact │ │ prediction_predictionrecord │
├─────────────────────────────────┤ ├─────────────────────────────────┤
│ id INT (PK) │ │ id INT (PK) │
│ name VARCHAR(200│◀────FK──│ model_id INT (FK) │
│ algorithm VARCHAR(100│ │ user_id INT (FK) │
│ version VARCHAR(50)│ │ input_payload JSON │
│ metrics JSON │ │ probability FLOAT │
│ confusion_matrix JSON │ │ risk_level VARCHAR(10│
│ feature_importance JSON │ │ predicted_label VARCHAR(10│
│ feature_names JSON │ │ top_factors JSON │
│ artifact_path VARCHAR(500│ │ recommendations JSON │
│ training_rows INT │ │ similar_cases JSON │
│ training_positive.. FLOAT │ │ report_summary TEXT │
│ is_active BOOL │ │ created_at DATETIME │
│ trained_at DATETIME │ └─────────────────────────────────┘
└─────────────────────────────────┘ │
│ │
│ is_active=True │ recommendations
│ (加载模型推理) ▼
▼ ┌─────────────────────────────────┐
┌─────────────────────┐ │ prediction_recommendationtemp.. │
│ ModelInference │ ├─────────────────────────────────┤
│ Service │────────────────▶│ id INT (PK) │
│ (services.py) │ trigger_field │ code VARCHAR(50)│
└─────────────────────┘ + trigger_val │ trigger_field VARCHAR(50)│
(条件匹配) │ trigger_value VARCHAR(100│
│ title_zh VARCHAR(200│
│ title_en VARCHAR(200│
│ description_zh TEXT │
│ description_en TEXT │
│ severity INT │
│ is_active BOOL │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ engagement_favoriteitem │
├─────────────────────────────────┤
│ id INT (PK) │
│ user_id INT (FK) │
│ target_type VARCHAR(30)│
│ target_id VARCHAR(100│
│ snapshot JSON │
│ created_at DATETIME │
│ [UNIQUE: user+target+type] │
└─────────────────────────────────┘
7. 数据流程图
7.1 患者数据导入流程
┌─────────────────────────────────────────────────────────────────────────────┐
│ Data Import Pipeline (CSV → Database) │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────┐
│ data/lung_cancer_ │
│ dataset.csv │
│ (50,000 条记录) │
└───────────┬─────────────┘
│
│ CSV 文件
▼
┌──────────────────────────────────────────────────────────────────────────┐
│ Django Management Command: import_lung_cancer_csv │
│ python manage.py import_lung_cancer_csv │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────────┐ │
│ │ FileValidator │───▶│ CSVReader │───▶│ DataCleaner │ │
│ │ • 检查文件存在 │ │ • 逐行读取 │ │ • 去除空格/空值 │ │
│ │ • 验证编码格式 │ │ • skip header │ │ • 类型转换 │ │
│ │ • 验证列头 │ │ • error_handle │ │ • 范围校验 │ │
│ └────────────────┘ └────────────────┘ └─────────┬──────────┘ │
│ │ │
│ ┌──────────────────────────────────────▼──────────────┐ │
│ │ PatientRecord.objects.bulk_create │ │
│ │ (批量插入,500条/批) │ │
│ └──────────────────────────────────────┬──────────────┘ │
│ │ │
│ ┌──────────────────────────────────────▼──────────────┐ │
│ │ Transaction.commit() │ │
│ │ 或 rollback on error │ │
│ └──────────────────────────────────────┬──────────────┘ │
│ │ │
└──────────────────────────────────────────────────────────┼────────────────┘
│
┌──────────────────────────────────────▼──────────────┐
│ MySQL Database │
│ patients_patientrecord │
│ (50,000 rows) │
└─────────────────────────────────────────────────────┘
7.2 机器学习训练流程
┌─────────────────────────────────────────────────────────────────────────────┐
│ Machine Learning Training Pipeline │
└─────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────┐
│ 管理员触发训练 (Vue Admin → API) │
│ POST /api/models/train │
└───────────────────────────────┬───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────┐
│ prediction/views.py → ModelTrainingView │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 1. 从数据库加载全部 PatientRecord │ │
│ │ PatientRecord.objects.all() → X (features), y (lung_cancer) │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 2. 数据集划分 │ │
│ │ train_test_split(X, y, test_size=5000, random_state=42) │ │
│ │ → X_train (45,000), X_test (5,000), y_train, y_test │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 3. 特征工程 │ │
│ │ • 性别: LabelEncoder (Male→0, Female→1) │ │
│ │ • 氡暴露: OrdinalEncoder (Low→0, Medium→1, High→2) │ │
│ │ • Yes/No字段: LabelEncoder │ │
│ │ • 饮酒: OrdinalEncoder │ │
│ │ • 数值字段: 保持原值 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 4. 模型训练: HistGradientBoostingClassifier │ │
│ │ clf = HistGradientBoostingClassifier( │ │
│ │ learning_rate=0.1, max_iter=200, random_state=42) │ │
│ │ clf.fit(X_train, y_train) │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 5. 模型评估 (在 5,000 条测试集上) │ │
│ │ y_pred = clf.predict(X_test) │ │
│ │ y_prob = clf.predict_proba(X_test)[:, 1] │ │
│ │ auc = roc_auc_score(y_test, y_prob) │ │
│ │ recall = recall_score(y_test, y_pred) │ │
│ │ precision = precision_score(y_test, y_pred) │ │
│ │ confusion_matrix = confusion_matrix(y_test, y_pred) │ │
│ │ feature_importance = clf.feature_importances_ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 6. 保存模型文件 │ │
│ │ artifact_path = artifacts/models/lung_cancer_v{timestamp}.pkl │ │
│ │ joblib.dump({'model': clf, 'encoders': encoders}, path) │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 7. 保存 ModelArtifact 记录 │ │
│ │ • 解除旧模型 is_active=True → is_active=False │ │
│ │ • 创建新记录 is_active=True │ │
│ │ • 记录 metrics, confusion_matrix, feature_importance │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────┬───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────────┐
│ MySQL + 文件系统 │
│ ┌─────────────────────┐ ┌─────────────────────────────────────┐ │
│ │ prediction_ │ │ artifacts/models/ │ │
│ │ modelartifact │ │ lung_cancer_v20260329_143052.pkl │ │
│ │ (DB记录) │ │ │ │
│ └─────────────────────┘ └─────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────┘
7.3 在线预测流程
┌─────────────────────────────────────────────────────────────────────────────┐
│ Online Prediction Flow │
└─────────────────────────────────────────────────────────────────────────────┘
用户提交预测 (Vue PredictionView)
│
│ POST /api/predictions/predict
│ { age, gender, pack_years, radon_exposure,
│ asbestos_exposure, secondhand_smoke_exposure,
│ copd_diagnosis, alcohol_consumption, family_history }
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Django REST Framework │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ PredictionView.post() ││
│ │ 1. 序列化输入 (Serializer validation) ││
│ │ 2. 获取激活模型: ModelArtifact.objects.get(is_active=True) ││
│ │ 3. 加载模型: joblib.load(model.artifact_path) ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ services.py → predict_lung_cancer(input_data) │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 特征编码 │ │ 模型推理 │ │ 风险等级判定 │ │
│ │ • 性别→0/1 │────▶│ prob = model. │────▶│ if prob >= 0.7: │ │
│ │ • 氡暴露→0/1/2 │ │ predict_proba │ │ risk = 'high' │ │
│ │ • Yes/No→0/1 │ │ [x][1] │ │ elif prob >= 0.4: │ │
│ │ • 饮酒→序数编码 │ │ │ │ risk = 'medium' │ │
│ │ │ │ │ │ else: │ │
│ │ 返回特征向量 │ │ 返回概率值 │ │ risk = 'low' │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ recommendations.py → generate_recommendations(input_data, top_factors) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ 按触发字段 + 触发值匹配 RecommendationTemplate ││
│ │ ││
│ │ 输入: { radon_exposure: 'High', family_history: 'Yes', copd: 'Yes' } ││
│ │ ││
│ │ 匹配: ││
│ │ radon_exposure='High' → "氡暴露风险" 模板 ││
│ │ family_history='Yes' → "肺癌家族史" 模板 ││
│ │ copd_diagnosis='Yes' → "慢阻肺管理" 模板 ││
│ │ ││
│ │ 输出: [{code, title_zh, title_en, description_zh, description_en, ││
│ │ severity, trigger_field, trigger_value}] ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ recommendations.py → find_similar_cases(input_data, top_k=5) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ 1. 将输入转换为特征向量 ││
│ │ 2. 从 PatientRecord 中随机采样候选集 (避免全表扫描) ││
│ │ 3. 计算欧氏距离: dist = sqrt(sum((xi - yi)^2)) ││
│ │ 4. 取距离最小的 top_k 条记录 ││
│ │ 5. 返回: [{patient_id, age, gender, distance, lung_cancer}, ...] ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 保存 PredictionRecord │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ record = PredictionRecord.objects.create( ││
│ │ user=request.user, ││
│ │ model=active_model, ││
│ │ input_payload=input_data, ││
│ │ probability=round(probability, 4), ││
│ │ risk_level=risk_level, ││
│ │ predicted_label='Yes' if prob >= 0.5 else 'No', ││
│ │ top_factors=top_factors, ││
│ │ recommendations=recommendations, ││
│ │ similar_cases=similar_cases, ││
│ │ report_summary=generate_summary(...) ││
│ │ ) ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
返回预测结果给前端
{ probability, risk_level, top_factors, recommendations, similar_cases, ... }
7.4 收藏功能数据流
┌─────────────────────────────────────────────────────────────────────────────┐
│ Favorite Flow │
└─────────────────────────────────────────────────────────────────────────────┘
用户点击收藏按钮
│
│ POST /api/favorites
│ { target_type: 'prediction_report', target_id: '123', snapshot: {...} }
▼
┌─────────────────┐
│ FavoriteView │
│ .post() │
└────────┬────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ FavoriteItem.objects.create( │
│ user=request.user, │
│ target_type=target_type, │
│ target_id=str(target_id), # 转字符串便于查询 │
│ snapshot=snapshot, # 收藏时刻的完整数据快照 │
│ ) │
│ │
│ snapshot 示例 (target_type='prediction_report'): │
│ { │
│ "probability": 0.732, │
│ "risk_level": "high", │
│ "predicted_label": "Yes", │
│ "input_data": {...}, │
│ "recommendations": [...], │
│ "similar_cases": [...], │
│ "report_summary": "...", │
│ "created_at": "2026-03-29T10:00:00Z" │
│ } │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
FavoriteItem.objects.filter(user=request.user)
→ FavoritesView 显示收藏列表
8. API 接口文档
8.1 认证相关 /api/auth/
POST /api/auth/register --- 用户注册
请求:
json
{
"username": "zhang_san",
"email": "zhang@example.com",
"password": "SecurePass123",
"password_confirm": "SecurePass123",
"display_name": "张三"
}
响应 (201):
json
{
"id": 5,
"username": "zhang_san",
"email": "zhang@example.com",
"display_name": "张三",
"role": "user",
"created_at": "2026-03-29T10:00:00Z"
}
POST /api/auth/login --- 用户登录
请求:
json
{
"username": "admin",
"password": "admin123456"
}
响应 (200):
json
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"user": {
"id": 1,
"username": "admin",
"email": "admin@example.com",
"display_name": "管理员",
"role": "admin",
"avatar_url": "/media/avatars/admin.png",
"avatar_color": "#7c2d12"
}
}
POST /api/auth/avatar --- 头像上传
请求 : Content-Type: multipart/form-data
avatar: <file> (≤2MB, JPG/PNG/GIF/WebP)
响应 (200):
json
{
"avatar_url": "/media/avatars/avatar_abc123.png"
}
8.2 分析数据 /api/analysis/
GET /api/analysis/overview --- 数据概览
响应 (200):
json
{
"total_patients": 50000,
"positive_rate": 0.687,
"avg_age": 62.4,
"avg_pack_years": 12.3,
"high_risk_factors": [
{ "factor": "family_history", "positive_rate": 0.82 },
{ "factor": "copd_diagnosis", "positive_rate": 0.79 },
{ "factor": "radon_exposure_High", "positive_rate": 0.75 }
],
"gender_distribution": { "Male": 27000, "Female": 23000 },
"age_group_distribution": {
"40以下": 3200,
"40-50": 8500,
"50-60": 13500,
"60-70": 16800,
"70-80": 6800,
"80以上": 1200
}
}
GET /api/analysis/distribution --- 分布图表数据
响应 (200):
json
{
"sankey": {
"nodes": ["二手烟暴露=Yes", "石棉暴露=Yes", "氡暴露=High", ...],
"links": [
{ "source": "二手烟暴露=Yes", "target": "肺癌=Yes", "value": 8500 },
...
]
},
"treemap": {
"data": [
{ "name": "family_history", "value": 0.82 },
...
]
},
"heatmap": {
"xAxis": ["Low radon", "Medium radon", "High radon"],
"yAxis": ["No COPD", "COPD"],
"data": [[0.45, 0.62, 0.75], [0.58, 0.71, 0.82]]
}
}
GET /api/analysis/advanced --- 高级分析数据
响应 (200):
json
{
"radar": {
"indicators": [
{ "name": "年龄", "max": 100 },
{ "name": "包年数", "max": 80 },
...
],
"lung_cancer_yes": [65, 22, ...],
"lung_cancer_no": [58, 8, ...]
},
"scatter": {
"data": [
{ "age_group": "50-60", "pack_year_group": "10-20", "count": 320 },
...
]
},
"funnel": {
"data": [
{ "name": "总筛查人数", "value": 50000 },
{ "name": "高风险人群", "value": 18500 },
{ "name": "建议进一步检查", "value": 7200 },
{ "name": "确诊肺癌", "value": 3435 }
]
},
"age_cancer_rate": {
"age_groups": ["<40", "40-50", "50-60", "60-70", "70-80", ">80"],
"cancer_rates": [0.12, 0.28, 0.52, 0.71, 0.83, 0.88]
}
}
8.3 预测 /api/predictions/
POST /api/predictions/predict --- 在线预测
请求:
json
{
"age": 58,
"gender": "Male",
"pack_years": 25,
"radon_exposure": "Medium",
"asbestos_exposure": "Yes",
"secondhand_smoke_exposure": "No",
"copd_diagnosis": "Yes",
"alcohol_consumption": "Occasional",
"family_history": "Yes"
}
响应 (200):
json
{
"id": 42,
"probability": 0.7324,
"risk_level": "high",
"predicted_label": "Yes",
"top_factors": [
{ "factor": "family_history", "contribution": 0.28 },
{ "factor": "copd_diagnosis", "contribution": 0.22 },
{ "factor": "pack_years", "contribution": 0.18 }
],
"recommendations": [
{
"code": "FAMILY_HISTORY",
"title_zh": "肺癌家族史提醒",
"title_en": "Family History Alert",
"description_zh": "建议每年进行低剂量CT筛查...",
"severity": 4
},
...
],
"similar_cases": [
{ "patient_id": 12847, "age": 57, "gender": "Male", "distance": 0.12, "lung_cancer": "Yes" },
...
],
"report_summary": "基于当前输入特征,系统评估您未来5年内患肺癌的风险为73.24%(高风险)..."
}
9. 前端架构
9.1 路由设计
┌─────────────────────────────────────────────────────────────────────────────┐
│ Vue Router Architecture │
└─────────────────────────────────────────────────────────────────────────────┘
/ (PublicLayout)
├── /login → LoginView.vue [公开]
├── /register → RegisterView.vue [公开]
├── / → HomeView.vue [公开]
├── /analysis → AnalysisView.vue [公开]
├── /predict → PredictionView.vue [已认证]
├── /favorites → FavoritesView.vue [已认证]
└── /profile → ProfileView.vue [已认证]
/admin (AdminLayout)
└── /admin → AdminDashboardView.vue [管理员]
9.2 状态管理 (Pinia Auth Store)
┌─────────────────────────────────────────────────────────────────────────────┐
│ Pinia Auth Store │
└─────────────────────────────────────────────────────────────────────────────┘
State:
┌──────────────────────────────────────────────────────────────────────────┐
│ { │
│ user: { id, username, email, display_name, role, avatar_url } | null, │
│ token: access_token | null, │
│ refreshToken: refresh_token | null, │
│ isLoading: boolean │
│ } │
└──────────────────────────────────────────────────────────────────────────┘
│
Actions: │
├── login(username, password) → POST /auth/login → 存储 token+user │
├── register(...) → POST /auth/register │
├── logout() → POST /auth/logout → 清除状态 │
├── fetchUser() → GET /auth/me → 恢复登录态 │
└── uploadAvatar(file) → POST /auth/avatar → 更新 avatar_url │
│
Getters: │
├── isAuthenticated → !!token │
└── isAdmin → user?.role === 'admin' │
9.3 Axios 拦截器
┌─────────────────────────────────────────────────────────────────────────────┐
│ Axios Interceptors │
└─────────────────────────────────────────────────────────────────────────────┘
请求拦截器:
┌────────────────────────────────────────────────────────────────────────┐
│ config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`│
│ return config │
└────────────────────────────────────────────────────────────────────────┘
响应拦截器:
┌────────────────────────────────────────────────────────────────────────┐
│ 200-299: return response.data │
│ 401: │
│ ├── 尝试使用 refresh_token 刷新 │
│ ├── 刷新成功 → 重试原请求 │
│ └── 刷新失败 → logout() → 跳转 /login │
│ else: throw error │
└────────────────────────────────────────────────────────────────────────┘
10. 机器学习模块
10.1 算法选择
| 算法 | AUC | Recall | Precision | 训练时间 | 选择理由 |
|---|---|---|---|---|---|
| RandomForest | 0.71 | 0.85 | 0.72 | 慢 | 基准模型 |
| HistGradientBoosting | 0.77 | 0.90 | 0.77 | 快 | 最优性能 |
| LogisticRegression | 0.68 | 0.78 | 0.69 | 极快 | 线性基线 |
| SVM | 0.70 | 0.82 | 0.71 | 极慢 | 不适合大数据 |
最终选择: HistGradientBoostingClassifier (scikit-learn)
10.2 特征工程
| 特征名 | 类型 | 处理方式 | 编码映射 |
|---|---|---|---|
| age | 连续 | 原始值 | - |
| gender | 二分类 | LabelEncoder | Male→0, Female→1 |
| pack_years | 连续 | 原始值 | - |
| radon_exposure | 多分类 | OrdinalEncoder | Low→0, Medium→1, High→2 |
| asbestos_exposure | 二分类 | LabelEncoder | No→0, Yes→1 |
| secondhand_smoke_exposure | 二分类 | LabelEncoder | No→0, Yes→1 |
| copd_diagnosis | 二分类 | LabelEncoder | No→0, Yes→1 |
| alcohol_consumption | 多分类 | OrdinalEncoder | Non-drinker→0, Occasional→1, Moderate→2, Heavy→3 |
| family_history | 二分类 | LabelEncoder | No→0, Yes→1 |
10.3 模型评估指标
预测为Positive 预测为Negative
实际Positive TP (真阳) FN (假阴)
实际Negative FP (假阳) TN (真阴)
AUC = ROC曲线下面积 → 0.7735 (衡量整体区分能力)
Recall = TP / (TP + FN) → 0.9043 (敏感度,查全率)
Precision = TP / (TP + FP) → 0.7654 (查准率)
F1 = 2 * Precision * Recall / (Precision + Recall)
11. 可视化方案
11.1 14 种图表清单
| # | 图表类型 | 组件名 | 用途 | ECharts 类型 |
|---|---|---|---|---|
| 1 | 年龄分布柱状图 | AgeDistribution | 展示患者年龄分布 | bar |
| 2 | 包年分布柱状图 | PackYearsDistribution | 吸烟包年分布 | bar |
| 3 | 性别×肺癌堆叠柱状图 | GenderLungCancerStacked | 性别与肺癌交叉分析 | bar (stacked) |
| 4 | 氡暴露×肺癌分组柱状图 | RadonLungCancerGrouped | 氡暴露风险对比 | bar (grouped) |
| 5 | 肺癌占比饼图 | LungCancerPie | 阴阳性比例 | pie |
| 6 | 风险等级雷达图 | RiskLevelRadar | 多维风险对比 | radar |
| 7 | Sankey流向图 | ExposureSankey | 暴露因素→肺癌流向 | sankey |
| 8 | 特征重要性Treemap | FeatureTreemap | 各特征重要性面积 | treemap |
| 9 | 风险热力矩阵 | RiskHeatmap | 双维度风险热力 | heatmap |
| 10 | 漏斗图 | ScreeningFunnel | 筛查路径转化 | funnel |
| 11 | 年龄组×癌症率折线图 | AgeCancerRateLine | 年龄与癌症率趋势 | line |
| 12 | 性别对比雷达图 | GenderContrastRadar | 男女风险特征雷达 | radar |
| 13 | 散点图 | AgePackYearsScatter | 年龄×包年关系 | scatter |
| 14 | 南丁格尔玫瑰图 | NightingaleRose | 多维度占比展示 | pie (roseType) |
11.2 图表布局
┌─────────────────────────────────────────────────────────────────────────────┐
│ AnalysisView.vue 布局 │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ AnalysisView.vue │
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ KPI卡片 │ │ KPI卡片 │ │ KPI卡片 │ │ KPI卡片 │ │
│ │ 总患者 │ │ 阳性率 │ │ 平均年龄│ │ 高风险 │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
│ │
│ ┌────────────────────┐ ┌────────────────────────────┐ │
│ │ 1. 年龄分布柱状图 │ │ 2. 包年分布柱状图 │ │
│ │ (bar) │ │ (bar) │ │
│ └────────────────────┘ └────────────────────────────┘ │
│ │
│ ┌────────────────────┐ ┌────────────────────────────┐ │
│ │ 3. 性别×肺癌堆叠柱状 │ │ 4. 氡暴露×肺癌分组柱状 │ │
│ │ (stacked bar) │ │ (grouped bar) │ │
│ └────────────────────┘ └────────────────────────────┘ │
│ │
│ ┌────────┐ ┌────────────────────┐ ┌────────────────┐ │
│ │5. 饼图 │ │ 7. Sankey流向图 │ │ 8. Treemap │ │
│ │ │ │ │ │ │ │
│ └────────┘ └────────────────────┘ └────────────────┘ │
│ │
│ ┌─────────────────────────────┐ ┌────────────────────┐ │
│ │ 6. 风险等级雷达图 │ │ 9. 风险热力矩阵 │ │
│ │ (radar) │ │ (heatmap) │ │
│ └─────────────────────────────┘ └────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │10. 漏斗图 │ │11. 年龄癌症率 │ │12. 性别对比雷达 │ │
│ │ (funnel) │ │ (line) │ │ (radar) │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
│ │
│ ┌────────────────────┐ ┌────────────────────────────┐ │
│ │ 13. 散点图 │ │ 14. 南丁格尔玫瑰图 │ │
│ │ (scatter) │ │ (pie+roseType) │ │
│ └────────────────────┘ └────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
12. 存储设计
12.1 文件存储结构
backend/
├── artifacts/ # 模型文件存储 (Git跟踪 .pkl)
│ └── models/
│ └── lung_cancer_v20260329_143052.pkl
│
├── media/ # 用户上传文件 (Git不跟踪)
│ └── avatars/
│ ├── avatar_admin.png
│ ├── avatar_user1.png
│ └── .gitkeep # 保持目录结构
│
└── staticfiles/ # collectstatic 收集的静态文件
├── css/
├── js/
└── images/
12.2 头像存储策略
用户上传头像
│
▼
┌─────────────────────────────┐
│ Pillow 图片处理 │
│ • 验证格式 (JPG/PNG/GIF/WebP)│
│ • 验证大小 (≤2MB) │
│ • 转换为 RGB (如有透明度) │
│ • 保持原始尺寸 │
│ • 保存为 PNG (统一格式) │
└────────────┬────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 文件路径: media/avatars/avatar_{随机后缀}.png │
│ URL路径: /media/avatars/avatar_{随机后缀}.png │
└─────────────────────────────────────────────┘
│
▼
Django Media 文件服务
(DEBUG=True 时由 Django 提供静态文件服务)
12.3 模型文件存储策略
训练完成 → 保存 .pkl 文件 → artifacts/models/lung_cancer_v{timestamp}.pkl
│
▼
ModelArtifact.artifact_path = "artifacts/models/..."
│
▼
MySQL 记录 (metrics, version, is_active)
13. 安全设计
13.1 认证与授权
| 机制 | 实现 | 说明 |
|---|---|---|
| JWT Access Token | SimpleJWT | 15分钟有效期 |
| JWT Refresh Token | SimpleJWT | 7天有效期 |
| Token 黑名单 | rest_framework_simplejwt.token_blacklist | 支持强制登出 |
| 权限类 | IsAuthenticated, IsAdminUser | DRF 权限系统 |
| 路由守卫 | Vue Router beforeEach | 前端路由拦截 |
13.2 输入安全
用户输入
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. Django REST Framework Serializer Validation │
│ • age: MinValueValidator(0), MaxValueValidator(120) │
│ • pack_years: MinValueValidator(0) │
│ • choices 校验: 枚举值校验 │
│ • 字符串长度: max_length 限制 │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. SQL 注入防护 │
│ • Django ORM (参数化查询) │
│ • 不使用原始 SQL 拼接 │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. XSS 防护 │
│ • Django 模板自动转义 │
│ • Vue 默认不渲染原始 HTML │
│ • Content-Security-Policy (生产环境) │
└─────────────────────────────────────────────────────────────────────────────┘
13.3 文件上传安全
头像上传安全检查:
├── 文件大小: ≤2MB (前端+后端双重校验)
├── 文件类型: 仅允许 JPG/PNG/GIF/WebP
├── 文件内容: Pillow 重新打开验证 (防止伪装的恶意文件)
├── 文件名: 随机生成 (avatar_{random_suffix}.png)
└── 存储位置: media/avatars/ (与代码目录分离)
14. 部署架构
14.1 开发环境
┌─────────────────────────────────────────────┐
│ 开发机器 (localhost) │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Vue 3 (Vite) │ │ Django (Runserver)│ │
│ │ Port: 5173 │ │ Port: 8000 │ │
│ └──────────────────┘ └──────────────────┘ │
│ │ │ │
│ │ /api/* 代理 │ │
│ └──────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ MySQL (Port: 3306) │ │
│ │ design_336_canner │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
14.2 生产环境(推荐)
┌─────────────────────────┐
│ 用户浏览器 │
│ HTTPS (443) │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ Nginx (反向代理) │
│ • 静态文件 /static/ │
│ • 媒体文件 /media/ │
│ • API 转发 :8000 │
│ • WebSocket (可选) │
└───────────┬─────────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Django (Gunicorn) │ │ Vue 3 构建产物 │
│ Port: 8000 │ │ /frontend/dist/ │
│ • WSGI / ASGI │ │ (Nginx 直接服务) │
└──────────┬──────────┘ └─────────────────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ MySQL │ │ File Sys │ │ Redis │
│ :3306 │ │ artifacts│ │ (可选) │
│ │ │ /media │ │ 缓存/Session│
└──────────┘ └──────────┘ └──────────┘
14.3 环境变量配置
backend/.env (开发):
bash
DJANGO_SECRET_KEY=dev-secret-key-change-in-production
DJANGO_DEBUG=1
DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_DATABASE=design_336_canner
MYSQL_USER=root
MYSQL_PASSWORD=your-password
LUNG_CANCER_DATASET_PATH=../data/lung_cancer_dataset.csv
backend/.env (生产):
bash
DJANGO_SECRET_KEY=<从环境变量或密钥管理服务读取>
DJANGO_DEBUG=0
DJANGO_ALLOWED_HOSTS=yourdomain.com
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_DATABASE=design_336_canner
MYSQL_USER=cancer_app
MYSQL_PASSWORD=<强密码>
LUNG_CANCER_DATASET_PATH=/var/www/cancer/data/lung_cancer_dataset.csv
15. 快速启动
15.1 环境准备
bash
# 1. Python ≥ 3.8
python --version
# Python 3.11.x
# 2. Node.js ≥ 18
node --version
# v20.x.x
# 3. MySQL 8.x(已启动)
mysql --version
# mysql Ver 8.x.x
15.2 数据库初始化
bash
# 创建数据库
mysql -u root -p -e "CREATE DATABASE design_336_canner CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# 配置环境变量
cp .env.example backend/.env
# 编辑 backend/.env,填入数据库密码
# 初始化 Django(建表)
cd backend
python manage.py migrate
# 创建超级管理员
python manage.py createsuperuser
# Username: admin
# Email: admin@example.com
# Password: admin123456
# 创建演示管理员(可选)
python manage.py ensure_demo_admin
# demo_admin / admin123456
15.3 导入患者数据
bash
# CSV 文件位于 data/lung_cancer_dataset.csv(50,000 条)
cd backend
python manage.py import_lung_cancer_csv
# 输出: Successfully imported 50000 patient records.
15.4 启动后端
bash
cd backend
python manage.py runserver
# 后端地址: http://127.0.0.1:8000
# Django Admin: http://127.0.0.1:8000/admin/
15.5 启动前端
bash
cd frontend
npm install
npm run dev
# 前端地址: http://localhost:5173
15.6 默认账号
| 角色 | 用户名 | 密码 | 说明 |
|---|---|---|---|
| 管理员 | admin | admin123456 | Django 超级管理员 |
| 演示管理员 | demo_admin | admin123456 | 用于功能演示 |
| 普通用户 | user1 | user123456 | 注册获得 |
16. Django Admin 使用指南
16.1 进入 Django Admin
方式一:点击前端「Django 数据管理后台」按钮(跳转到 http://127.0.0.1:8000/admin/)
方式二:直接访问 http://127.0.0.1:8000/admin/
16.2 可管理的模型
| App (中文名) | 模型 | 说明 |
|---|---|---|
| 账户管理 | 用户资料 | 查看/编辑用户头像、角色、显示名 |
| 患者管理 | 患者记录 | 50,000 条患者数据(支持过滤/搜索/排序) |
| 预测管理 | 模型 | 训练生成的 ML 模型(可激活最佳模型) |
| 预测管理 | 预测记录 | 用户预测历史(查看概率/建议/相似病例) |
| 预测管理 | 推荐模板 | 干预建议模板(中英双语,启用/禁用) |
| 收藏管理 | 收藏 | 用户收藏夹(预测报告/相似病例) |
16.3 模型训练流程
- 进入「预测管理 → 模型」页面
- 点击右上角「训练新模型」按钮
- 等待训练完成(约 1-2 分钟)
- 新模型自动激活(is_active=True)
- 可在列表中查看 AUC/Recall/Precision 指标
17. 环境变量说明
17.1 后端环境变量
| 变量名 | 必填 | 默认值 | 说明 |
|---|---|---|---|
| DJANGO_SECRET_KEY | 是 | - | Django 密钥(生产必须随机生成) |
| DJANGO_DEBUG | 否 | 0 | 调试模式(生产设为 0) |
| DJANGO_ALLOWED_HOSTS | 是 | - | 允许的域名,逗号分隔 |
| MYSQL_HOST | 否 | 127.0.0.1 | MySQL 主机 |
| MYSQL_PORT | 否 | 3306 | MySQL 端口 |
| MYSQL_DATABASE | 是 | - | 数据库名 |
| MYSQL_USER | 是 | - | 数据库用户名 |
| MYSQL_PASSWORD | 是 | - | 数据库密码 |
| LUNG_CANCER_DATASET_PATH | 否 | .../data/... | CSV 数据文件路径 |
17.2 前端环境变量
| 变量名 | 必填 | 默认值 | 说明 |
|---|---|---|---|
| VITE_API_BASE_URL | 否 | http://127.0.0.1:8000/api | API 基础地址 |
更新日志
| 版本 | 日期 | 内容 |
|---|---|---|
| v1.0 | 2026-03-29 | 完成全部功能:Django+Vue 肺癌可视化系统,14种图表,ML预测,Django Admin 美化 |