基于Flask的城市地铁客流量数据预测系统设计与实现 毕业论文
毕业设计
一、项目名称:
基于Flask的城市地铁客流量数据预测系统设计与实现
【摘要】
城市地铁客流具有明显时段与线路差异,手工汇总难以快速把握站点热度、进出站结构与天气影响。本文设计并实现基于 Flask 的城市地铁客流量数据预测系统,面向毕业设计与教学演示,将线路、站点与小时级客流统一入库后,以交互式图表完成多维可视化,并以随机森林完成一键客流预测,辅以智谱大模型问答与自研后台维护。
系统采用 B/S 架构,后端入口为 app.py,运行端口 5000 ;数据库使用 MySQL(库名 metro_flow_db),经 PyMySQL 访问。前台基于 Bootstrap、本地 static/vendor 中的 ECharts,配合 metro-theme.css 主题。核心业务表包括 tbl_user、metro_lines、metro_stations、passenger_flow。可视化聚合在 utils/metro_viz_charts.py 中按城市、线路、月份输出图表 bundle,并带短时缓存;随机森林预测由 models/random_forest_model.py 实现,页面路径 /metro/rf_predict;AI 助手调用智谱 OpenAI 兼容接口,默认模型 glm-4-flash。后台为自研 CRUD(utils/admin_blueprint.py),路径 /admin/,独立登录页 /admin/login,覆盖用户、线路、站点、客流四类数据。默认账号 admin/admin123。前台登录表单字段为 userName,会话键为 session["userName"]。
测试与运行表明,系统可完成登录注册、六类客流可视化、数据总览、随机森林预测、AI 问答与后台维护等闭环,界面清晰、响应稳定,可为地铁客流分析课题研究与教学演示提供可用支撑。
【关键词】 Flask;地铁客流;数据可视化;随机森林;ECharts
【Abstract】
Urban metro passenger flow varies by time and line, making manual aggregation inefficient. This thesis designs and implements a Flask-based metro passenger flow prediction system for graduation design and teaching demos. Line, station and hourly flow data are stored in MySQL and explored through interactive charts, with one-click Random Forest forecasting, Zhipu LLM Q&A and a custom admin backend.
The system follows a B/S architecture. The entry is app.py on port 5000 . MySQL database name is metro_flow_db, accessed via PyMySQL. The frontend uses Bootstrap, local ECharts under static/vendor, and metro-theme.css. Core tables include tbl_user, metro_lines, metro_stations and passenger_flow. Visualization bundles are built in metro_viz_charts.py with short TTL cache. Random Forest forecasting is implemented in random_forest_model.py at /metro/rf_predict. The AI assistant uses Zhipu's OpenAI-compatible API with default model glm-4-flash. Admin CRUD is custom (admin_blueprint.py) at /admin/ with independent login /admin/login. Default account is admin/admin123. Frontend login uses form field userName and session key userName.
Results show that authentication, multi-dimensional visualization, data overview, Random Forest prediction, AI assistant and admin maintenance work stably for course demos.
【Keywords】 Flask; Metro Passenger Flow; Data Visualization; Random Forest; ECharts
目 录
[1 绪 论](#1 绪 论)
- [1.1 课题研究背景](#1.1 课题研究背景)
- [1.2 课题研究的目的、意义](#1.2 课题研究的目的、意义)
- [1.3 课题的国内外研究现状和发展动态](#1.3 课题的国内外研究现状和发展动态)
- [1.4 研究内容](#1.4 研究内容)
- [1.5 论文结构安排](#1.5 论文结构安排)
[2 相关技术](#2 相关技术)
- [2.1 Python编程语言](#2.1 Python编程语言)
- [2.2 Flask Web框架](#2.2 Flask Web框架)
- [2.3 MySQL与PyMySQL](#2.3 MySQL与PyMySQL)
- [2.4 pandas与scikit-learn随机森林](#2.4 pandas与scikit-learn随机森林)
- [2.5 ECharts与metro-theme](#2.5 ECharts与metro-theme)
- [2.6 智谱AI与技术特色](#2.6 智谱AI与技术特色)
[3 系统分析与设计](#3 系统分析与设计)
- [3.1 系统总体设计](#3.1 系统总体设计)
- [3.1.1 系统架构设计](#3.1.1 系统架构设计)
- [3.1.2 系统数据流设计](#3.1.2 系统数据流设计)
- [3.1.3 系统功能模块设计](#3.1.3 系统功能模块设计)
- [3.2 系统详细设计](#3.2 系统详细设计)
- [3.2.1 用户认证模块设计](#3.2.1 用户认证模块设计)
- [3.2.2 客流多维可视化模块设计](#3.2.2 客流多维可视化模块设计)
- [3.2.3 数据总览模块设计](#3.2.3 数据总览模块设计)
- [3.2.4 随机森林客流预测模块设计](#3.2.4 随机森林客流预测模块设计)
- [3.2.5 AI智能助手模块设计](#3.2.5 AI智能助手模块设计)
- [3.2.6 后台数据管理模块设计](#3.2.6 后台数据管理模块设计)
- [3.3 数据库设计](#3.3 数据库设计)
- [3.3.1 数据库关系设计](#3.3.1 数据库关系设计)
[4 系统实现](#4 系统实现)
- [4.0 系统整体实现流程](#4.0 系统整体实现流程)
- [4.1 用户认证模块的实现](#4.1 用户认证模块的实现)
- [4.2 客流多维可视化模块的实现](#4.2 客流多维可视化模块的实现)
- [4.3 数据总览模块的实现](#4.3 数据总览模块的实现)
- [4.4 随机森林客流预测模块的实现](#4.4 随机森林客流预测模块的实现)
- [4.5 AI智能助手模块的实现](#4.5 AI智能助手模块的实现)
- [4.6 后台数据管理模块的实现](#4.6 后台数据管理模块的实现)
- [4.7 用户界面实现](#4.7 用户界面实现)
- [4.8 系统集成与部署](#4.8 系统集成与部署)
[5 系统测试](#5 系统测试)
- [5.1 系统功能测试](#5.1 系统功能测试)
- [5.1.1 用户认证功能测试用例](#5.1.1 用户认证功能测试用例)
- [5.1.2 数据管理功能测试用例](#5.1.2 数据管理功能测试用例)
- [5.1.3 核心业务功能测试用例](#5.1.3 核心业务功能测试用例)
- [5.1.4 数据分析功能测试用例](#5.1.4 数据分析功能测试用例)
- [5.1.5 系统集成功能测试用例](#5.1.5 系统集成功能测试用例)
- [5.1.6 用户界面功能测试用例](#5.1.6 用户界面功能测试用例)
- [5.2 系统性能测试](#5.2 系统性能测试)
- [5.2.1 响应时间性能分析](#5.2.1 响应时间性能分析)
- [5.2.2 并发性能测试](#5.2.2 并发性能测试)
- [5.2.3 数据库性能测试](#5.2.3 数据库性能测试)
- [5.2.4 性能优化措施](#5.2.4 性能优化措施)
[6 总结与展望](#6 总结与展望)
- [6.1 总结](#6.1 总结)
- [6.2 展望](#6.2 展望)
1 绪 论
1.1 课题研究背景
轨道交通客流量是城市交通运行与运力调配的重要依据。站点、线路、时段、天气与节假日等因素交织,使客流呈现显著周期性与空间差异。对教学与课题研究而言,若仅依赖表格逐行浏览,难以从站点排行、线路对比、时序走势、进出站结构与天气关联等维度形成整体认识。与此同时,毕业设计场景需要一套可本地部署、结构清晰、图表可读的系统,既要能装载演示数据,也要能给出可解释的短期预测结果。
本课题据此构建「基于Flask的城市地铁客流量数据预测系统设计与实现」:以 Flask 提供页面与 JSON 接口,以 MySQL 库 metro_flow_db 持久化线路、站点与客流,以 ECharts 完成多页可视化看板,以 scikit-learn 随机森林完成一站式训练与预测,并以智谱 glm-4-flash 提供客流分析问答。系统入口 app.py 监听 5000 端口;侧栏覆盖六类可视化、数据总览、随机森林预测与 AI 助手;管理端为自研 /admin/ CRUD。该背景决定了后续技术选型必须兼顾轻量部署、图表交互与预测可演示性。
1.2 课题研究的目的、意义
1.2.1 研究目的
- 建立统一的地铁业务数据模型(用户、线路、站点、客流),完成建库脚本与演示数据初始化。
- 实现前台登录鉴权与侧栏多页面分析,按城市、线路、月份等条件筛选后输出图表 bundle。
- 实现基于随机森林的客流预测页面,一次请求完成拉数、训练、指标评估与多步预测展示。
- 提供智谱 AI 助手与自研后台对四类业务表的增删改查,保证数据可维护、图表可刷新。
1.2.2 研究意义
理论意义上,课题将 Web 应用分层、聚合查询、机器学习回归与大模型问答结合起来,形成可复用的「页面路由 + bundle API + 预测服务」模式。实践意义上,系统可在课堂或答辩环境中一键演示可视化与预测链路,降低对外部商业平台的依赖。社会与教学意义上,有助于理解地铁客流的时空特征与影响因素,培养数据处理、可视化工程与预测建模能力。
1.3 课题的国内外研究现状和发展动态
1.3.1 国外研究现状
国外在城市轨道交通客流分析、短时预测与交互可视化方面积累较早。常见路径是将 AFC/闸机数据导入数据仓库,再以 BI 工具或自研 Web 看板呈现站点热度与高峰特征。预测方面,统计模型与机器学习(含随机森林、梯度提升等)被广泛用于教学与工程原型;深度学习模型在数据充足时表现突出,但对算力与样本质量要求更高。课题级系统往往在准确率与实现成本之间折中,选用可解释、训练成本低的集成学习即可满足演示需求。
1.3.2 国内研究现状
国内高校毕业设计中,基于 Flask/Django 的客流或交通可视化课题较多,常见组合为 MySQL + ECharts,辅以 pandas 做预处理。部分课题引入 ARIMA、LSTM 等时序模型作为对比,但部署与调参成本较高。管理端有的使用 Django Admin / SimpleUI,有的自研轻量 CRUD。本课题选择 Flask + 自研 /admin/,前台交付以随机森林一键预测为主,与 metro-theme 统一视觉,更贴近小型课题的可维护目标。
1.3.3 发展趋势
趋势上,客流分析正向「入库---缓存---交互可视化---在线预测---智能问答」一体化演进;前端强调本地化静态资源与弱网可用;预测服务强调一次请求返回指标与图表。本系统在可视化 bundle TTL、随机森林时间留出评估与智谱免费模型接入等方面体现了上述工程趋势。
1.4 研究内容
研究内容包括:(1)库表设计与 metro_flow_db.sql 初始化;(2)Flask 路由、Session 登录与侧栏菜单;(3)metro_viz_charts 各可视化 bundle 与筛选条件;(4)随机森林特征构造、训练评估与滚动多步预测;(5)智谱 AI 助手接入;(6)自研后台 CRUD;(7)功能与性能层面的验证。创新点集中在:六维客流可视化 + 版本缓存、随机森林一站式预测页、与前台统一主题的自研后台,以及智谱 glm-4-flash 客流问答。
1.5 论文结构安排
第1章介绍背景、目的意义、研究现状与研究内容。第2章说明 Python、Flask、MySQL/PyMySQL、pandas/sklearn、ECharts 与智谱 AI 等相关技术。第3章给出架构、数据流、功能结构、六大模块详细设计与数据库设计。第4章按模块阐述实现流程、核心代码、访问路径与界面图题。第5章给出功能测试用例与性能分析。第6章总结量化成果并展望改进方向。文末为参考文献与致谢。
2 相关技术
2.1 Python编程语言
2.1.1 语言特性与项目选型
Python 语法简洁,生态覆盖 Web、数据处理与机器学习。本项目使用 Python 3.11,依赖见 requirements.txt:Flask、PyMySQL、pandas、numpy、scikit-learn、requests、python-dotenv。业务脚本与工具模块均以 UTF-8 源码组织,便于在 Windows 环境下维护。
2.1.2 在本系统中的应用位置
app.py 负责路由与会话;utils/db.py 封装连接与查询;utils/metro_viz_charts.py、models/random_forest_model.py、utils/ai_assistant.py、utils/admin_blueprint.py 分别承担图表聚合、随机森林预测、AI 问答与后台。启动时可通过 utils/data_initializer.py 在空库场景下自动初始化演示数据。
2.2 Flask Web框架
2.2.1 轻量路由与会话
Flask 以装饰器注册路由,适合中小型课题。本系统用 session 保存 userName、avatar_url、is_admin;before_request 对未登录访问重定向 /login,访问 /admin 且未登录则跳转 /admin/login。根路径 / 与 /login 共用登录视图。
2.2.2 Blueprint 扩展后台
后台以 Blueprint admin_bp 挂载于 /admin,与主应用解耦。页面模板与前台共用 metro-theme 资源,权限通过 admin_required 校验管理员会话,未满足则携带 next 跳转独立后台登录页。
2.3 MySQL与PyMySQL
2.3.1 存储选型
业务数据存放于 MySQL 库 metro_flow_db,字符集 utf8mb4。config.py 中 DB_NAME 等项可通过 .env 覆盖。表结构由 metro_flow_db.sql 创建,并预置 admin/admin123。
2.3.2 访问封装
utils/db.py 使用 PyMySQL 执行参数化 SQL,query(sql, params, mode) 支持查询与写入。图表、预测与后台列表均通过参数绑定访问,降低拼接风险。客流表对站点、线路建立外键与常用索引,支撑按城市/线路/月份筛选。
2.4 pandas与scikit-learn随机森林
2.4.1 数据处理
预测链路用 pandas 将查询结果整理为含 datetime、total_flow 的升序表,清洗异常值并裁剪负客流。可视化侧亦可借助聚合 SQL 直接产出图表序列,减少前端计算负担。
2.4.2 随机森林回归
RandomForestPredictor 基于 sklearn.ensemble.RandomForestRegressor:以过去 lookback 个客流点、小时、星期及小时正弦/余弦为特征,按时间顺序留出后 20% 评估 MAE/RMSE/R²,再对未来多步滚动预测。该方案训练快、可解释性较好,适合课题演示。
2.5 ECharts与metro-theme
2.5.1 本地化静态资源
模板 base.html 引用 static/vendor 下 Bootstrap、Bootstrap Icons 与 ECharts 等本地资源,避免依赖外网 CDN,保证离线演示可用。主题样式集中在 static/css/metro-theme.css。
2.5.2 图表交互约定
可视化页通过 static/js/metro_viz_pages.js、metro_chart_helpers.js 拉取 JSON 并渲染柱状图、折线图、饼图等;提示框使用系列名展示,避免出现无意义的 series0 文案。站点排行等 TOP 类图表保证类目名与数值一致。
2.6 智谱AI与技术特色
2.6.1 智谱 glm-4-flash
utils/ai_assistant.py 通过 OpenAI 兼容接口 POST {ZHIPU_BASE_URL}/chat/completions 调用智谱模型,默认 glm-4-flash。密钥写入项目 .env(ZHIPU_API_KEY),不写入论文交付文案。免费档适合一问一答演示。
2.6.2 技术特色与创新点
可视化 bundle + 短缓存 :metro_viz_charts 以 _cache["version"] 控制失效,TTL 约 120 秒,后台写操作后调用 invalidate_viz_cache()。
随机森林一站式预测 :/metro/api/rf_predict 一次返回历史曲线、预测曲线与评估指标,降低多页面跳转成本。
自研后台 :非 Flask-Admin / SimpleUI,按 admin_registry.MODULES 声明式配置列表列与表单字段。
前后台登录分离 :前台 /login(表单字段 userName),后台 /admin/login,避免管理入口与普通用户入口混淆。
资源本地化与主题统一 :vendor 本地化 + metro-theme.css,前后台视觉一致。
3 系统分析与设计
3.1 系统总体设计
3.1.1 系统架构设计
系统采用表现层、业务逻辑层与数据访问层三层结构。表现层为 Jinja2 模板与可视化/预测前端脚本;业务层包括鉴权、图表 bundle、随机森林预测、AI 助手与后台 CRUD;数据层为 MySQL metro_flow_db。
图3.1 系统架构图
#mermaid-svg-R5DjzZUcLCwFgoGy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-R5DjzZUcLCwFgoGy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-R5DjzZUcLCwFgoGy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-R5DjzZUcLCwFgoGy .error-icon{fill:#552222;}#mermaid-svg-R5DjzZUcLCwFgoGy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-R5DjzZUcLCwFgoGy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-R5DjzZUcLCwFgoGy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-R5DjzZUcLCwFgoGy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-R5DjzZUcLCwFgoGy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-R5DjzZUcLCwFgoGy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-R5DjzZUcLCwFgoGy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-R5DjzZUcLCwFgoGy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-R5DjzZUcLCwFgoGy .marker.cross{stroke:#333333;}#mermaid-svg-R5DjzZUcLCwFgoGy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-R5DjzZUcLCwFgoGy p{margin:0;}#mermaid-svg-R5DjzZUcLCwFgoGy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-R5DjzZUcLCwFgoGy .cluster-label text{fill:#333;}#mermaid-svg-R5DjzZUcLCwFgoGy .cluster-label span{color:#333;}#mermaid-svg-R5DjzZUcLCwFgoGy .cluster-label span p{background-color:transparent;}#mermaid-svg-R5DjzZUcLCwFgoGy .label text,#mermaid-svg-R5DjzZUcLCwFgoGy span{fill:#333;color:#333;}#mermaid-svg-R5DjzZUcLCwFgoGy .node rect,#mermaid-svg-R5DjzZUcLCwFgoGy .node circle,#mermaid-svg-R5DjzZUcLCwFgoGy .node ellipse,#mermaid-svg-R5DjzZUcLCwFgoGy .node polygon,#mermaid-svg-R5DjzZUcLCwFgoGy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-R5DjzZUcLCwFgoGy .rough-node .label text,#mermaid-svg-R5DjzZUcLCwFgoGy .node .label text,#mermaid-svg-R5DjzZUcLCwFgoGy .image-shape .label,#mermaid-svg-R5DjzZUcLCwFgoGy .icon-shape .label{text-anchor:middle;}#mermaid-svg-R5DjzZUcLCwFgoGy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-R5DjzZUcLCwFgoGy .rough-node .label,#mermaid-svg-R5DjzZUcLCwFgoGy .node .label,#mermaid-svg-R5DjzZUcLCwFgoGy .image-shape .label,#mermaid-svg-R5DjzZUcLCwFgoGy .icon-shape .label{text-align:center;}#mermaid-svg-R5DjzZUcLCwFgoGy .node.clickable{cursor:pointer;}#mermaid-svg-R5DjzZUcLCwFgoGy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-R5DjzZUcLCwFgoGy .arrowheadPath{fill:#333333;}#mermaid-svg-R5DjzZUcLCwFgoGy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-R5DjzZUcLCwFgoGy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-R5DjzZUcLCwFgoGy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-R5DjzZUcLCwFgoGy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-R5DjzZUcLCwFgoGy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-R5DjzZUcLCwFgoGy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-R5DjzZUcLCwFgoGy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-R5DjzZUcLCwFgoGy .cluster text{fill:#333;}#mermaid-svg-R5DjzZUcLCwFgoGy .cluster span{color:#333;}#mermaid-svg-R5DjzZUcLCwFgoGy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-R5DjzZUcLCwFgoGy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-R5DjzZUcLCwFgoGy rect.text{fill:none;stroke-width:0;}#mermaid-svg-R5DjzZUcLCwFgoGy .icon-shape,#mermaid-svg-R5DjzZUcLCwFgoGy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-R5DjzZUcLCwFgoGy .icon-shape p,#mermaid-svg-R5DjzZUcLCwFgoGy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-R5DjzZUcLCwFgoGy .icon-shape .label rect,#mermaid-svg-R5DjzZUcLCwFgoGy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-R5DjzZUcLCwFgoGy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-R5DjzZUcLCwFgoGy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-R5DjzZUcLCwFgoGy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据访问层
业务逻辑层
表现层
前台登录/注册
六类可视化页
数据总览
随机森林预测页
AI助手页
自研后台 /admin
ECharts + metro主题
before_request 会话鉴权
app.py 路由与 API
metro_viz_charts bundle
random_forest_model
ai_assistant 智谱
admin_blueprint CRUD
MySQL: metro_flow_db
data_initializer
3.1.2 系统数据流设计
外部角色包括前台用户与管理员。系统输出页面 HTML 与图表/预测 JSON,并将后台维护结果写入数据库。
图3.2 顶层数据流图
#mermaid-svg-7YlTnJqbut2W8cgP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7YlTnJqbut2W8cgP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7YlTnJqbut2W8cgP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7YlTnJqbut2W8cgP .error-icon{fill:#552222;}#mermaid-svg-7YlTnJqbut2W8cgP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7YlTnJqbut2W8cgP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7YlTnJqbut2W8cgP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7YlTnJqbut2W8cgP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7YlTnJqbut2W8cgP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7YlTnJqbut2W8cgP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7YlTnJqbut2W8cgP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7YlTnJqbut2W8cgP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7YlTnJqbut2W8cgP .marker.cross{stroke:#333333;}#mermaid-svg-7YlTnJqbut2W8cgP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7YlTnJqbut2W8cgP p{margin:0;}#mermaid-svg-7YlTnJqbut2W8cgP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7YlTnJqbut2W8cgP .cluster-label text{fill:#333;}#mermaid-svg-7YlTnJqbut2W8cgP .cluster-label span{color:#333;}#mermaid-svg-7YlTnJqbut2W8cgP .cluster-label span p{background-color:transparent;}#mermaid-svg-7YlTnJqbut2W8cgP .label text,#mermaid-svg-7YlTnJqbut2W8cgP span{fill:#333;color:#333;}#mermaid-svg-7YlTnJqbut2W8cgP .node rect,#mermaid-svg-7YlTnJqbut2W8cgP .node circle,#mermaid-svg-7YlTnJqbut2W8cgP .node ellipse,#mermaid-svg-7YlTnJqbut2W8cgP .node polygon,#mermaid-svg-7YlTnJqbut2W8cgP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7YlTnJqbut2W8cgP .rough-node .label text,#mermaid-svg-7YlTnJqbut2W8cgP .node .label text,#mermaid-svg-7YlTnJqbut2W8cgP .image-shape .label,#mermaid-svg-7YlTnJqbut2W8cgP .icon-shape .label{text-anchor:middle;}#mermaid-svg-7YlTnJqbut2W8cgP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7YlTnJqbut2W8cgP .rough-node .label,#mermaid-svg-7YlTnJqbut2W8cgP .node .label,#mermaid-svg-7YlTnJqbut2W8cgP .image-shape .label,#mermaid-svg-7YlTnJqbut2W8cgP .icon-shape .label{text-align:center;}#mermaid-svg-7YlTnJqbut2W8cgP .node.clickable{cursor:pointer;}#mermaid-svg-7YlTnJqbut2W8cgP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7YlTnJqbut2W8cgP .arrowheadPath{fill:#333333;}#mermaid-svg-7YlTnJqbut2W8cgP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7YlTnJqbut2W8cgP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7YlTnJqbut2W8cgP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7YlTnJqbut2W8cgP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7YlTnJqbut2W8cgP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7YlTnJqbut2W8cgP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7YlTnJqbut2W8cgP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7YlTnJqbut2W8cgP .cluster text{fill:#333;}#mermaid-svg-7YlTnJqbut2W8cgP .cluster span{color:#333;}#mermaid-svg-7YlTnJqbut2W8cgP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7YlTnJqbut2W8cgP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7YlTnJqbut2W8cgP rect.text{fill:none;stroke-width:0;}#mermaid-svg-7YlTnJqbut2W8cgP .icon-shape,#mermaid-svg-7YlTnJqbut2W8cgP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7YlTnJqbut2W8cgP .icon-shape p,#mermaid-svg-7YlTnJqbut2W8cgP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7YlTnJqbut2W8cgP .icon-shape .label rect,#mermaid-svg-7YlTnJqbut2W8cgP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7YlTnJqbut2W8cgP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7YlTnJqbut2W8cgP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7YlTnJqbut2W8cgP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 账号与筛选/预测请求
CRUD维护
页面与图表JSON
管理反馈
持久化
问答文本
前台用户
地铁客流量预测系统
管理员 admin
MySQL metro_flow_db
智谱API
图3.3 0层数据流图
#mermaid-svg-enjfE5hrNdWDfkaH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-enjfE5hrNdWDfkaH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-enjfE5hrNdWDfkaH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-enjfE5hrNdWDfkaH .error-icon{fill:#552222;}#mermaid-svg-enjfE5hrNdWDfkaH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-enjfE5hrNdWDfkaH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-enjfE5hrNdWDfkaH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-enjfE5hrNdWDfkaH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-enjfE5hrNdWDfkaH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-enjfE5hrNdWDfkaH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-enjfE5hrNdWDfkaH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-enjfE5hrNdWDfkaH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-enjfE5hrNdWDfkaH .marker.cross{stroke:#333333;}#mermaid-svg-enjfE5hrNdWDfkaH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-enjfE5hrNdWDfkaH p{margin:0;}#mermaid-svg-enjfE5hrNdWDfkaH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-enjfE5hrNdWDfkaH .cluster-label text{fill:#333;}#mermaid-svg-enjfE5hrNdWDfkaH .cluster-label span{color:#333;}#mermaid-svg-enjfE5hrNdWDfkaH .cluster-label span p{background-color:transparent;}#mermaid-svg-enjfE5hrNdWDfkaH .label text,#mermaid-svg-enjfE5hrNdWDfkaH span{fill:#333;color:#333;}#mermaid-svg-enjfE5hrNdWDfkaH .node rect,#mermaid-svg-enjfE5hrNdWDfkaH .node circle,#mermaid-svg-enjfE5hrNdWDfkaH .node ellipse,#mermaid-svg-enjfE5hrNdWDfkaH .node polygon,#mermaid-svg-enjfE5hrNdWDfkaH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-enjfE5hrNdWDfkaH .rough-node .label text,#mermaid-svg-enjfE5hrNdWDfkaH .node .label text,#mermaid-svg-enjfE5hrNdWDfkaH .image-shape .label,#mermaid-svg-enjfE5hrNdWDfkaH .icon-shape .label{text-anchor:middle;}#mermaid-svg-enjfE5hrNdWDfkaH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-enjfE5hrNdWDfkaH .rough-node .label,#mermaid-svg-enjfE5hrNdWDfkaH .node .label,#mermaid-svg-enjfE5hrNdWDfkaH .image-shape .label,#mermaid-svg-enjfE5hrNdWDfkaH .icon-shape .label{text-align:center;}#mermaid-svg-enjfE5hrNdWDfkaH .node.clickable{cursor:pointer;}#mermaid-svg-enjfE5hrNdWDfkaH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-enjfE5hrNdWDfkaH .arrowheadPath{fill:#333333;}#mermaid-svg-enjfE5hrNdWDfkaH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-enjfE5hrNdWDfkaH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-enjfE5hrNdWDfkaH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-enjfE5hrNdWDfkaH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-enjfE5hrNdWDfkaH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-enjfE5hrNdWDfkaH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-enjfE5hrNdWDfkaH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-enjfE5hrNdWDfkaH .cluster text{fill:#333;}#mermaid-svg-enjfE5hrNdWDfkaH .cluster span{color:#333;}#mermaid-svg-enjfE5hrNdWDfkaH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-enjfE5hrNdWDfkaH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-enjfE5hrNdWDfkaH rect.text{fill:none;stroke-width:0;}#mermaid-svg-enjfE5hrNdWDfkaH .icon-shape,#mermaid-svg-enjfE5hrNdWDfkaH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-enjfE5hrNdWDfkaH .icon-shape p,#mermaid-svg-enjfE5hrNdWDfkaH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-enjfE5hrNdWDfkaH .icon-shape .label rect,#mermaid-svg-enjfE5hrNdWDfkaH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-enjfE5hrNdWDfkaH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-enjfE5hrNdWDfkaH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-enjfE5hrNdWDfkaH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 前台用户
1.0 用户认证
2.0 客流多维可视化
3.0 数据总览
4.0 随机森林预测
5.0 AI智能助手
管理员
6.0 后台数据管理
tbl_user
passenger_flow
metro_stations
metro_lines
智谱API
3.1.3 系统功能模块设计
结合侧栏与后台菜单,功能划分为六大模块:(1)用户认证;(2)客流多维可视化;(3)数据总览;(4)随机森林客流预测;(5)AI智能助手;(6)后台数据管理。六类可视化页面归属模块(2),在实现章统一说明。
图3.4 系统功能结构图
#mermaid-svg-ekLK476zNM7LVO2X{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ekLK476zNM7LVO2X .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ekLK476zNM7LVO2X .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ekLK476zNM7LVO2X .error-icon{fill:#552222;}#mermaid-svg-ekLK476zNM7LVO2X .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ekLK476zNM7LVO2X .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ekLK476zNM7LVO2X .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ekLK476zNM7LVO2X .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ekLK476zNM7LVO2X .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ekLK476zNM7LVO2X .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ekLK476zNM7LVO2X .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ekLK476zNM7LVO2X .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ekLK476zNM7LVO2X .marker.cross{stroke:#333333;}#mermaid-svg-ekLK476zNM7LVO2X svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ekLK476zNM7LVO2X p{margin:0;}#mermaid-svg-ekLK476zNM7LVO2X .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ekLK476zNM7LVO2X .cluster-label text{fill:#333;}#mermaid-svg-ekLK476zNM7LVO2X .cluster-label span{color:#333;}#mermaid-svg-ekLK476zNM7LVO2X .cluster-label span p{background-color:transparent;}#mermaid-svg-ekLK476zNM7LVO2X .label text,#mermaid-svg-ekLK476zNM7LVO2X span{fill:#333;color:#333;}#mermaid-svg-ekLK476zNM7LVO2X .node rect,#mermaid-svg-ekLK476zNM7LVO2X .node circle,#mermaid-svg-ekLK476zNM7LVO2X .node ellipse,#mermaid-svg-ekLK476zNM7LVO2X .node polygon,#mermaid-svg-ekLK476zNM7LVO2X .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ekLK476zNM7LVO2X .rough-node .label text,#mermaid-svg-ekLK476zNM7LVO2X .node .label text,#mermaid-svg-ekLK476zNM7LVO2X .image-shape .label,#mermaid-svg-ekLK476zNM7LVO2X .icon-shape .label{text-anchor:middle;}#mermaid-svg-ekLK476zNM7LVO2X .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ekLK476zNM7LVO2X .rough-node .label,#mermaid-svg-ekLK476zNM7LVO2X .node .label,#mermaid-svg-ekLK476zNM7LVO2X .image-shape .label,#mermaid-svg-ekLK476zNM7LVO2X .icon-shape .label{text-align:center;}#mermaid-svg-ekLK476zNM7LVO2X .node.clickable{cursor:pointer;}#mermaid-svg-ekLK476zNM7LVO2X .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ekLK476zNM7LVO2X .arrowheadPath{fill:#333333;}#mermaid-svg-ekLK476zNM7LVO2X .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ekLK476zNM7LVO2X .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ekLK476zNM7LVO2X .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ekLK476zNM7LVO2X .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ekLK476zNM7LVO2X .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ekLK476zNM7LVO2X .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ekLK476zNM7LVO2X .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ekLK476zNM7LVO2X .cluster text{fill:#333;}#mermaid-svg-ekLK476zNM7LVO2X .cluster span{color:#333;}#mermaid-svg-ekLK476zNM7LVO2X div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ekLK476zNM7LVO2X .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ekLK476zNM7LVO2X rect.text{fill:none;stroke-width:0;}#mermaid-svg-ekLK476zNM7LVO2X .icon-shape,#mermaid-svg-ekLK476zNM7LVO2X .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ekLK476zNM7LVO2X .icon-shape p,#mermaid-svg-ekLK476zNM7LVO2X .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ekLK476zNM7LVO2X .icon-shape .label rect,#mermaid-svg-ekLK476zNM7LVO2X .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ekLK476zNM7LVO2X .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ekLK476zNM7LVO2X .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ekLK476zNM7LVO2X :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基于Flask的城市地铁客流量数据预测系统
用户认证
客流多维可视化
数据总览
随机森林客流预测
AI智能助手
后台数据管理
登录注册退出
Session鉴权
站点排行
线路对比
客流时序
进出站分析
天气客流
时段因素
客流表格浏览
条件筛选分页
一键训练预测
指标与曲线展示
多轮问答
智谱glm-4-flash
用户线路站点客流CRUD
独立后台登录
3.2 系统详细设计
3.2.1 用户认证模块设计
模块提供 /login、/register、/logout。登录校验 tbl_user 表用户名与密码(演示环境明文存储,与课题默认账号一致);成功后写入 session["userName"],并可写入头像与 is_admin。注册校验两次密码一致与用户名唯一。before_request 对未登录访问重定向登录页;若目标为 /admin,则跳转 /admin/login。默认账号 admin/admin123。前台登录表单字段名为 userName。
3.2.1.1 用户认证流程设计
图3.5 用户认证模块流程图
#mermaid-svg-fBRvgIZa6e3u0Bwc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fBRvgIZa6e3u0Bwc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fBRvgIZa6e3u0Bwc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fBRvgIZa6e3u0Bwc .error-icon{fill:#552222;}#mermaid-svg-fBRvgIZa6e3u0Bwc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fBRvgIZa6e3u0Bwc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fBRvgIZa6e3u0Bwc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fBRvgIZa6e3u0Bwc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fBRvgIZa6e3u0Bwc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fBRvgIZa6e3u0Bwc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fBRvgIZa6e3u0Bwc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fBRvgIZa6e3u0Bwc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fBRvgIZa6e3u0Bwc .marker.cross{stroke:#333333;}#mermaid-svg-fBRvgIZa6e3u0Bwc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fBRvgIZa6e3u0Bwc p{margin:0;}#mermaid-svg-fBRvgIZa6e3u0Bwc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fBRvgIZa6e3u0Bwc .cluster-label text{fill:#333;}#mermaid-svg-fBRvgIZa6e3u0Bwc .cluster-label span{color:#333;}#mermaid-svg-fBRvgIZa6e3u0Bwc .cluster-label span p{background-color:transparent;}#mermaid-svg-fBRvgIZa6e3u0Bwc .label text,#mermaid-svg-fBRvgIZa6e3u0Bwc span{fill:#333;color:#333;}#mermaid-svg-fBRvgIZa6e3u0Bwc .node rect,#mermaid-svg-fBRvgIZa6e3u0Bwc .node circle,#mermaid-svg-fBRvgIZa6e3u0Bwc .node ellipse,#mermaid-svg-fBRvgIZa6e3u0Bwc .node polygon,#mermaid-svg-fBRvgIZa6e3u0Bwc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fBRvgIZa6e3u0Bwc .rough-node .label text,#mermaid-svg-fBRvgIZa6e3u0Bwc .node .label text,#mermaid-svg-fBRvgIZa6e3u0Bwc .image-shape .label,#mermaid-svg-fBRvgIZa6e3u0Bwc .icon-shape .label{text-anchor:middle;}#mermaid-svg-fBRvgIZa6e3u0Bwc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fBRvgIZa6e3u0Bwc .rough-node .label,#mermaid-svg-fBRvgIZa6e3u0Bwc .node .label,#mermaid-svg-fBRvgIZa6e3u0Bwc .image-shape .label,#mermaid-svg-fBRvgIZa6e3u0Bwc .icon-shape .label{text-align:center;}#mermaid-svg-fBRvgIZa6e3u0Bwc .node.clickable{cursor:pointer;}#mermaid-svg-fBRvgIZa6e3u0Bwc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fBRvgIZa6e3u0Bwc .arrowheadPath{fill:#333333;}#mermaid-svg-fBRvgIZa6e3u0Bwc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fBRvgIZa6e3u0Bwc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fBRvgIZa6e3u0Bwc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fBRvgIZa6e3u0Bwc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fBRvgIZa6e3u0Bwc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fBRvgIZa6e3u0Bwc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fBRvgIZa6e3u0Bwc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fBRvgIZa6e3u0Bwc .cluster text{fill:#333;}#mermaid-svg-fBRvgIZa6e3u0Bwc .cluster span{color:#333;}#mermaid-svg-fBRvgIZa6e3u0Bwc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fBRvgIZa6e3u0Bwc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fBRvgIZa6e3u0Bwc rect.text{fill:none;stroke-width:0;}#mermaid-svg-fBRvgIZa6e3u0Bwc .icon-shape,#mermaid-svg-fBRvgIZa6e3u0Bwc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fBRvgIZa6e3u0Bwc .icon-shape p,#mermaid-svg-fBRvgIZa6e3u0Bwc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fBRvgIZa6e3u0Bwc .icon-shape .label rect,#mermaid-svg-fBRvgIZa6e3u0Bwc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fBRvgIZa6e3u0Bwc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fBRvgIZa6e3u0Bwc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fBRvgIZa6e3u0Bwc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} GET
POST
匹配
不匹配
通过
失败
否
是
否
是
访问 /login
请求方法
渲染 login.html
读取 userName 与 password
tbl_user 匹配
写入 session userName
返回 JSON 成功
提示用户名和密码不一致
访问 /register
注册校验
INSERT tbl_user
返回成功
提示不一致或已存在
访问受保护页
session.userName?
路径是否 /admin?
跳转 /admin/login
放行视图
3.2.2 客流多维可视化模块设计
模块覆盖侧栏六页:站点排行 /metro/station_rank、线路对比 /metro/flow_line、客流时序 /metro/flow_trend、进出站分析 /metro/inout、天气客流 /metro/weather_flow、时段因素 /metro/factor_analysis。公共筛选项接口 /metro/api/viz/options,各页分别调用对应 bundle API(如 /metro/api/viz/station-rank)。筛选维度为城市、线路、月份;聚合逻辑集中在 utils/metro_viz_charts.py,带版本缓存。
3.2.2.1 客流多维可视化流程设计
图3.6 客流多维可视化模块流程图
#mermaid-svg-MkHMVytYRe8URZRC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MkHMVytYRe8URZRC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MkHMVytYRe8URZRC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MkHMVytYRe8URZRC .error-icon{fill:#552222;}#mermaid-svg-MkHMVytYRe8URZRC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MkHMVytYRe8URZRC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MkHMVytYRe8URZRC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MkHMVytYRe8URZRC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MkHMVytYRe8URZRC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MkHMVytYRe8URZRC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MkHMVytYRe8URZRC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MkHMVytYRe8URZRC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MkHMVytYRe8URZRC .marker.cross{stroke:#333333;}#mermaid-svg-MkHMVytYRe8URZRC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MkHMVytYRe8URZRC p{margin:0;}#mermaid-svg-MkHMVytYRe8URZRC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MkHMVytYRe8URZRC .cluster-label text{fill:#333;}#mermaid-svg-MkHMVytYRe8URZRC .cluster-label span{color:#333;}#mermaid-svg-MkHMVytYRe8URZRC .cluster-label span p{background-color:transparent;}#mermaid-svg-MkHMVytYRe8URZRC .label text,#mermaid-svg-MkHMVytYRe8URZRC span{fill:#333;color:#333;}#mermaid-svg-MkHMVytYRe8URZRC .node rect,#mermaid-svg-MkHMVytYRe8URZRC .node circle,#mermaid-svg-MkHMVytYRe8URZRC .node ellipse,#mermaid-svg-MkHMVytYRe8URZRC .node polygon,#mermaid-svg-MkHMVytYRe8URZRC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MkHMVytYRe8URZRC .rough-node .label text,#mermaid-svg-MkHMVytYRe8URZRC .node .label text,#mermaid-svg-MkHMVytYRe8URZRC .image-shape .label,#mermaid-svg-MkHMVytYRe8URZRC .icon-shape .label{text-anchor:middle;}#mermaid-svg-MkHMVytYRe8URZRC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MkHMVytYRe8URZRC .rough-node .label,#mermaid-svg-MkHMVytYRe8URZRC .node .label,#mermaid-svg-MkHMVytYRe8URZRC .image-shape .label,#mermaid-svg-MkHMVytYRe8URZRC .icon-shape .label{text-align:center;}#mermaid-svg-MkHMVytYRe8URZRC .node.clickable{cursor:pointer;}#mermaid-svg-MkHMVytYRe8URZRC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MkHMVytYRe8URZRC .arrowheadPath{fill:#333333;}#mermaid-svg-MkHMVytYRe8URZRC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MkHMVytYRe8URZRC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MkHMVytYRe8URZRC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MkHMVytYRe8URZRC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MkHMVytYRe8URZRC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MkHMVytYRe8URZRC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MkHMVytYRe8URZRC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MkHMVytYRe8URZRC .cluster text{fill:#333;}#mermaid-svg-MkHMVytYRe8URZRC .cluster span{color:#333;}#mermaid-svg-MkHMVytYRe8URZRC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MkHMVytYRe8URZRC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MkHMVytYRe8URZRC rect.text{fill:none;stroke-width:0;}#mermaid-svg-MkHMVytYRe8URZRC .icon-shape,#mermaid-svg-MkHMVytYRe8URZRC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MkHMVytYRe8URZRC .icon-shape p,#mermaid-svg-MkHMVytYRe8URZRC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MkHMVytYRe8URZRC .icon-shape .label rect,#mermaid-svg-MkHMVytYRe8URZRC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MkHMVytYRe8URZRC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MkHMVytYRe8URZRC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MkHMVytYRe8URZRC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
进入可视化页
渲染模板与筛选项区
请求 /metro/api/viz/options
按页请求对应 bundle API
metro_viz_charts 聚合
缓存命中?
返回 JSON
SQL分组统计
写入 TTL 缓存
ECharts 渲染柱/折/饼等
3.2.3 数据总览模块设计
数据总览页 /data_preview 面向表格化浏览与核对,展示客流记录的关键字段(时间、站点、线路、进出站、总客流、天气等),支持按条件筛选与分页查看,便于答辩时核对库内样本是否齐全。该页不替代六类图表分析,而是作为"看得见数据"的清单入口。
3.2.3.1 数据总览流程设计
图3.7 数据总览模块流程图
#mermaid-svg-f1uoo2ecgSZiASPf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-f1uoo2ecgSZiASPf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-f1uoo2ecgSZiASPf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-f1uoo2ecgSZiASPf .error-icon{fill:#552222;}#mermaid-svg-f1uoo2ecgSZiASPf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-f1uoo2ecgSZiASPf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-f1uoo2ecgSZiASPf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-f1uoo2ecgSZiASPf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-f1uoo2ecgSZiASPf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-f1uoo2ecgSZiASPf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-f1uoo2ecgSZiASPf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-f1uoo2ecgSZiASPf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-f1uoo2ecgSZiASPf .marker.cross{stroke:#333333;}#mermaid-svg-f1uoo2ecgSZiASPf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-f1uoo2ecgSZiASPf p{margin:0;}#mermaid-svg-f1uoo2ecgSZiASPf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-f1uoo2ecgSZiASPf .cluster-label text{fill:#333;}#mermaid-svg-f1uoo2ecgSZiASPf .cluster-label span{color:#333;}#mermaid-svg-f1uoo2ecgSZiASPf .cluster-label span p{background-color:transparent;}#mermaid-svg-f1uoo2ecgSZiASPf .label text,#mermaid-svg-f1uoo2ecgSZiASPf span{fill:#333;color:#333;}#mermaid-svg-f1uoo2ecgSZiASPf .node rect,#mermaid-svg-f1uoo2ecgSZiASPf .node circle,#mermaid-svg-f1uoo2ecgSZiASPf .node ellipse,#mermaid-svg-f1uoo2ecgSZiASPf .node polygon,#mermaid-svg-f1uoo2ecgSZiASPf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-f1uoo2ecgSZiASPf .rough-node .label text,#mermaid-svg-f1uoo2ecgSZiASPf .node .label text,#mermaid-svg-f1uoo2ecgSZiASPf .image-shape .label,#mermaid-svg-f1uoo2ecgSZiASPf .icon-shape .label{text-anchor:middle;}#mermaid-svg-f1uoo2ecgSZiASPf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-f1uoo2ecgSZiASPf .rough-node .label,#mermaid-svg-f1uoo2ecgSZiASPf .node .label,#mermaid-svg-f1uoo2ecgSZiASPf .image-shape .label,#mermaid-svg-f1uoo2ecgSZiASPf .icon-shape .label{text-align:center;}#mermaid-svg-f1uoo2ecgSZiASPf .node.clickable{cursor:pointer;}#mermaid-svg-f1uoo2ecgSZiASPf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-f1uoo2ecgSZiASPf .arrowheadPath{fill:#333333;}#mermaid-svg-f1uoo2ecgSZiASPf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-f1uoo2ecgSZiASPf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-f1uoo2ecgSZiASPf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f1uoo2ecgSZiASPf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-f1uoo2ecgSZiASPf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f1uoo2ecgSZiASPf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-f1uoo2ecgSZiASPf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-f1uoo2ecgSZiASPf .cluster text{fill:#333;}#mermaid-svg-f1uoo2ecgSZiASPf .cluster span{color:#333;}#mermaid-svg-f1uoo2ecgSZiASPf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-f1uoo2ecgSZiASPf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-f1uoo2ecgSZiASPf rect.text{fill:none;stroke-width:0;}#mermaid-svg-f1uoo2ecgSZiASPf .icon-shape,#mermaid-svg-f1uoo2ecgSZiASPf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f1uoo2ecgSZiASPf .icon-shape p,#mermaid-svg-f1uoo2ecgSZiASPf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-f1uoo2ecgSZiASPf .icon-shape .label rect,#mermaid-svg-f1uoo2ecgSZiASPf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f1uoo2ecgSZiASPf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-f1uoo2ecgSZiASPf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-f1uoo2ecgSZiASPf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
进入 /data_preview
读取筛选条件
查询 passenger_flow 等表
分页组装记录
渲染表格与统计摘要
切换筛选?
结束浏览
3.2.4 随机森林客流预测模块设计
预测页 /metro/rf_predict 选择站点、预测步数与 lookback 后,调用 /metro/api/rf_predict。服务端从 passenger_flow 拉取最近最多 2000 条小时序列,构造滞后特征与日历特征,训练 RandomForestRegressor,用时间留出集计算 MAE/RMSE/R²,再滚动预测未来若干小时,返回历史与预测曲线数据供 ECharts 展示。
3.2.4.1 随机森林客流预测流程设计
图3.8 随机森林客流预测模块流程图
#mermaid-svg-QqzDS2362XrPq05F{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QqzDS2362XrPq05F .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QqzDS2362XrPq05F .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QqzDS2362XrPq05F .error-icon{fill:#552222;}#mermaid-svg-QqzDS2362XrPq05F .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QqzDS2362XrPq05F .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QqzDS2362XrPq05F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QqzDS2362XrPq05F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QqzDS2362XrPq05F .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QqzDS2362XrPq05F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QqzDS2362XrPq05F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QqzDS2362XrPq05F .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QqzDS2362XrPq05F .marker.cross{stroke:#333333;}#mermaid-svg-QqzDS2362XrPq05F svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QqzDS2362XrPq05F p{margin:0;}#mermaid-svg-QqzDS2362XrPq05F .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QqzDS2362XrPq05F .cluster-label text{fill:#333;}#mermaid-svg-QqzDS2362XrPq05F .cluster-label span{color:#333;}#mermaid-svg-QqzDS2362XrPq05F .cluster-label span p{background-color:transparent;}#mermaid-svg-QqzDS2362XrPq05F .label text,#mermaid-svg-QqzDS2362XrPq05F span{fill:#333;color:#333;}#mermaid-svg-QqzDS2362XrPq05F .node rect,#mermaid-svg-QqzDS2362XrPq05F .node circle,#mermaid-svg-QqzDS2362XrPq05F .node ellipse,#mermaid-svg-QqzDS2362XrPq05F .node polygon,#mermaid-svg-QqzDS2362XrPq05F .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QqzDS2362XrPq05F .rough-node .label text,#mermaid-svg-QqzDS2362XrPq05F .node .label text,#mermaid-svg-QqzDS2362XrPq05F .image-shape .label,#mermaid-svg-QqzDS2362XrPq05F .icon-shape .label{text-anchor:middle;}#mermaid-svg-QqzDS2362XrPq05F .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QqzDS2362XrPq05F .rough-node .label,#mermaid-svg-QqzDS2362XrPq05F .node .label,#mermaid-svg-QqzDS2362XrPq05F .image-shape .label,#mermaid-svg-QqzDS2362XrPq05F .icon-shape .label{text-align:center;}#mermaid-svg-QqzDS2362XrPq05F .node.clickable{cursor:pointer;}#mermaid-svg-QqzDS2362XrPq05F .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QqzDS2362XrPq05F .arrowheadPath{fill:#333333;}#mermaid-svg-QqzDS2362XrPq05F .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QqzDS2362XrPq05F .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QqzDS2362XrPq05F .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QqzDS2362XrPq05F .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QqzDS2362XrPq05F .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QqzDS2362XrPq05F .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QqzDS2362XrPq05F .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QqzDS2362XrPq05F .cluster text{fill:#333;}#mermaid-svg-QqzDS2362XrPq05F .cluster span{color:#333;}#mermaid-svg-QqzDS2362XrPq05F div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QqzDS2362XrPq05F .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QqzDS2362XrPq05F rect.text{fill:none;stroke-width:0;}#mermaid-svg-QqzDS2362XrPq05F .icon-shape,#mermaid-svg-QqzDS2362XrPq05F .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QqzDS2362XrPq05F .icon-shape p,#mermaid-svg-QqzDS2362XrPq05F .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QqzDS2362XrPq05F .icon-shape .label rect,#mermaid-svg-QqzDS2362XrPq05F .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QqzDS2362XrPq05F .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QqzDS2362XrPq05F .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QqzDS2362XrPq05F :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
进入 /metro/rf_predict
选择站点与参数
POST /metro/api/rf_predict
查询历史客流
样本是否充足?
返回错误提示
RandomForestPredictor.train
时间留出评估指标
滚动多步 predict
返回 history/forecast/metrics
ECharts 绘制曲线
3.2.5 AI智能助手模块设计
助手页 /metro/ai_assistant,接口 /metro/api/ai_ask。后端 MetroAIAssistant.ask 组装 system/user/assistant 消息,请求智谱 chat/completions,默认模型 glm-4-flash。用于解答客流趋势、高峰特征、影响因素与预测指标含义等问题。未配置 ZHIPU_API_KEY 时返回明确提示。
3.2.5.1 AI智能助手流程设计
图3.9 AI智能助手模块流程图
#mermaid-svg-6uEgW97VmfWbbXJf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6uEgW97VmfWbbXJf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6uEgW97VmfWbbXJf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6uEgW97VmfWbbXJf .error-icon{fill:#552222;}#mermaid-svg-6uEgW97VmfWbbXJf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6uEgW97VmfWbbXJf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6uEgW97VmfWbbXJf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6uEgW97VmfWbbXJf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6uEgW97VmfWbbXJf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6uEgW97VmfWbbXJf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6uEgW97VmfWbbXJf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6uEgW97VmfWbbXJf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6uEgW97VmfWbbXJf .marker.cross{stroke:#333333;}#mermaid-svg-6uEgW97VmfWbbXJf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6uEgW97VmfWbbXJf p{margin:0;}#mermaid-svg-6uEgW97VmfWbbXJf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6uEgW97VmfWbbXJf .cluster-label text{fill:#333;}#mermaid-svg-6uEgW97VmfWbbXJf .cluster-label span{color:#333;}#mermaid-svg-6uEgW97VmfWbbXJf .cluster-label span p{background-color:transparent;}#mermaid-svg-6uEgW97VmfWbbXJf .label text,#mermaid-svg-6uEgW97VmfWbbXJf span{fill:#333;color:#333;}#mermaid-svg-6uEgW97VmfWbbXJf .node rect,#mermaid-svg-6uEgW97VmfWbbXJf .node circle,#mermaid-svg-6uEgW97VmfWbbXJf .node ellipse,#mermaid-svg-6uEgW97VmfWbbXJf .node polygon,#mermaid-svg-6uEgW97VmfWbbXJf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6uEgW97VmfWbbXJf .rough-node .label text,#mermaid-svg-6uEgW97VmfWbbXJf .node .label text,#mermaid-svg-6uEgW97VmfWbbXJf .image-shape .label,#mermaid-svg-6uEgW97VmfWbbXJf .icon-shape .label{text-anchor:middle;}#mermaid-svg-6uEgW97VmfWbbXJf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6uEgW97VmfWbbXJf .rough-node .label,#mermaid-svg-6uEgW97VmfWbbXJf .node .label,#mermaid-svg-6uEgW97VmfWbbXJf .image-shape .label,#mermaid-svg-6uEgW97VmfWbbXJf .icon-shape .label{text-align:center;}#mermaid-svg-6uEgW97VmfWbbXJf .node.clickable{cursor:pointer;}#mermaid-svg-6uEgW97VmfWbbXJf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6uEgW97VmfWbbXJf .arrowheadPath{fill:#333333;}#mermaid-svg-6uEgW97VmfWbbXJf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6uEgW97VmfWbbXJf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6uEgW97VmfWbbXJf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6uEgW97VmfWbbXJf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6uEgW97VmfWbbXJf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6uEgW97VmfWbbXJf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6uEgW97VmfWbbXJf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6uEgW97VmfWbbXJf .cluster text{fill:#333;}#mermaid-svg-6uEgW97VmfWbbXJf .cluster span{color:#333;}#mermaid-svg-6uEgW97VmfWbbXJf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6uEgW97VmfWbbXJf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6uEgW97VmfWbbXJf rect.text{fill:none;stroke-width:0;}#mermaid-svg-6uEgW97VmfWbbXJf .icon-shape,#mermaid-svg-6uEgW97VmfWbbXJf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6uEgW97VmfWbbXJf .icon-shape p,#mermaid-svg-6uEgW97VmfWbbXJf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6uEgW97VmfWbbXJf .icon-shape .label rect,#mermaid-svg-6uEgW97VmfWbbXJf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6uEgW97VmfWbbXJf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6uEgW97VmfWbbXJf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6uEgW97VmfWbbXJf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
进入 /metro/ai_assistant
输入问题
POST /metro/api/ai_ask
已配置 API Key?
返回配置提示
组装 messages
请求智谱 chat/completions
HTTP 200?
提取 content 返回
返回错误信息
页面展示回答
3.2.6 后台数据管理模块设计
/admin/login 为独立后台登录;登录成功后进入 /admin/ 概览。子模块覆盖 users、lines、stations、flows,对应表 tbl_user、metro_lines、metro_stations、passenger_flow,支持列表搜索、创建、编辑、删除。写操作后调用 invalidate_viz_cache()。权限要求会话为管理员(账号 admin 或 is_admin=1)。非 Flask-Admin、非 SimpleUI。
3.2.6.1 后台数据管理流程设计
图3.10 后台数据管理模块流程图
#mermaid-svg-lPfc59gtyTAupBSU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lPfc59gtyTAupBSU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lPfc59gtyTAupBSU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lPfc59gtyTAupBSU .error-icon{fill:#552222;}#mermaid-svg-lPfc59gtyTAupBSU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lPfc59gtyTAupBSU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lPfc59gtyTAupBSU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lPfc59gtyTAupBSU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lPfc59gtyTAupBSU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lPfc59gtyTAupBSU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lPfc59gtyTAupBSU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lPfc59gtyTAupBSU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lPfc59gtyTAupBSU .marker.cross{stroke:#333333;}#mermaid-svg-lPfc59gtyTAupBSU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lPfc59gtyTAupBSU p{margin:0;}#mermaid-svg-lPfc59gtyTAupBSU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lPfc59gtyTAupBSU .cluster-label text{fill:#333;}#mermaid-svg-lPfc59gtyTAupBSU .cluster-label span{color:#333;}#mermaid-svg-lPfc59gtyTAupBSU .cluster-label span p{background-color:transparent;}#mermaid-svg-lPfc59gtyTAupBSU .label text,#mermaid-svg-lPfc59gtyTAupBSU span{fill:#333;color:#333;}#mermaid-svg-lPfc59gtyTAupBSU .node rect,#mermaid-svg-lPfc59gtyTAupBSU .node circle,#mermaid-svg-lPfc59gtyTAupBSU .node ellipse,#mermaid-svg-lPfc59gtyTAupBSU .node polygon,#mermaid-svg-lPfc59gtyTAupBSU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lPfc59gtyTAupBSU .rough-node .label text,#mermaid-svg-lPfc59gtyTAupBSU .node .label text,#mermaid-svg-lPfc59gtyTAupBSU .image-shape .label,#mermaid-svg-lPfc59gtyTAupBSU .icon-shape .label{text-anchor:middle;}#mermaid-svg-lPfc59gtyTAupBSU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lPfc59gtyTAupBSU .rough-node .label,#mermaid-svg-lPfc59gtyTAupBSU .node .label,#mermaid-svg-lPfc59gtyTAupBSU .image-shape .label,#mermaid-svg-lPfc59gtyTAupBSU .icon-shape .label{text-align:center;}#mermaid-svg-lPfc59gtyTAupBSU .node.clickable{cursor:pointer;}#mermaid-svg-lPfc59gtyTAupBSU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lPfc59gtyTAupBSU .arrowheadPath{fill:#333333;}#mermaid-svg-lPfc59gtyTAupBSU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lPfc59gtyTAupBSU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lPfc59gtyTAupBSU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lPfc59gtyTAupBSU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lPfc59gtyTAupBSU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lPfc59gtyTAupBSU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lPfc59gtyTAupBSU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lPfc59gtyTAupBSU .cluster text{fill:#333;}#mermaid-svg-lPfc59gtyTAupBSU .cluster span{color:#333;}#mermaid-svg-lPfc59gtyTAupBSU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lPfc59gtyTAupBSU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lPfc59gtyTAupBSU rect.text{fill:none;stroke-width:0;}#mermaid-svg-lPfc59gtyTAupBSU .icon-shape,#mermaid-svg-lPfc59gtyTAupBSU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lPfc59gtyTAupBSU .icon-shape p,#mermaid-svg-lPfc59gtyTAupBSU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lPfc59gtyTAupBSU .icon-shape .label rect,#mermaid-svg-lPfc59gtyTAupBSU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lPfc59gtyTAupBSU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lPfc59gtyTAupBSU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lPfc59gtyTAupBSU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
创建/编辑
删除
访问 /admin/login
校验管理员账号
登录成功?
提示错误
进入 /admin/ 概览
选择模块列表
操作类型
表单校验入库
DELETE
invalidate_viz_cache
返回列表并 flash
3.3 数据库设计
3.3.1 数据库关系设计
数据库名为 metro_flow_db。交付核心表四张:tbl_user(系统账号)、metro_lines(线路)、metro_stations(站点)、passenger_flow(客流)。站点通过 line_id 关联线路,客流通过 station_id/line_id 关联站点与线路。SQL 中建立外键约束,保证演示数据引用完整。
图3.11 数据库表关系图
#mermaid-svg-SOwrcnKQ3wkXPI0o{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SOwrcnKQ3wkXPI0o .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SOwrcnKQ3wkXPI0o .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SOwrcnKQ3wkXPI0o .error-icon{fill:#552222;}#mermaid-svg-SOwrcnKQ3wkXPI0o .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SOwrcnKQ3wkXPI0o .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SOwrcnKQ3wkXPI0o .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SOwrcnKQ3wkXPI0o .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SOwrcnKQ3wkXPI0o .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SOwrcnKQ3wkXPI0o .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SOwrcnKQ3wkXPI0o .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SOwrcnKQ3wkXPI0o .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SOwrcnKQ3wkXPI0o .marker.cross{stroke:#333333;}#mermaid-svg-SOwrcnKQ3wkXPI0o svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SOwrcnKQ3wkXPI0o p{margin:0;}#mermaid-svg-SOwrcnKQ3wkXPI0o .entityBox{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-SOwrcnKQ3wkXPI0o .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#mermaid-svg-SOwrcnKQ3wkXPI0o .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-SOwrcnKQ3wkXPI0o .labelBkg{background-color:rgba(248.6666666666, 255, 235.9999999999, 0.5);}#mermaid-svg-SOwrcnKQ3wkXPI0o .edgeLabel .label{fill:#9370DB;font-size:14px;}#mermaid-svg-SOwrcnKQ3wkXPI0o .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SOwrcnKQ3wkXPI0o .edge-pattern-dashed{stroke-dasharray:8,8;}#mermaid-svg-SOwrcnKQ3wkXPI0o .node rect,#mermaid-svg-SOwrcnKQ3wkXPI0o .node circle,#mermaid-svg-SOwrcnKQ3wkXPI0o .node ellipse,#mermaid-svg-SOwrcnKQ3wkXPI0o .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SOwrcnKQ3wkXPI0o .relationshipLine{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-SOwrcnKQ3wkXPI0o .marker{fill:none!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-SOwrcnKQ3wkXPI0o .edgeLabel{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SOwrcnKQ3wkXPI0o .edgeLabel .label rect{fill:rgba(232,232,232, 0.8);}#mermaid-svg-SOwrcnKQ3wkXPI0o .edgeLabel .label text{fill:#333;}#mermaid-svg-SOwrcnKQ3wkXPI0o :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} line_id
station_id
line_id
tbl_user
int
id
PK
主键
varchar
user_name
UK
用户名
varchar
password
密码
varchar
email
邮箱
varchar
avatar
头像
tinyint
is_admin
是否管理员
datetime
created_at
创建时间
metro_lines
varchar
line_id
PK
线路ID
varchar
line_name
线路名称
varchar
city
城市
int
total_stations
站点数
decimal
length_km
长度公里
date
opening_date
开通日期
metro_stations
varchar
station_id
PK
站点ID
varchar
station_name
站点名
varchar
line_id
FK
线路ID
varchar
city
城市
int
station_order
顺序
tinyint
is_transfer
是否换乘
passenger_flow
bigint
id
PK
主键
datetime
datetime
时间
varchar
station_id
FK
站点ID
varchar
line_id
FK
线路ID
varchar
city
城市
int
inbound_flow
进站
int
outbound_flow
出站
int
total_flow
总客流
int
hour
小时
varchar
weather
天气
decimal
temperature
温度
表3.1 tbl_user 表结构
| 字段名称 | 字段类型 | 字段说明 | 是否主键 | 是否为空 |
|---|---|---|---|---|
| id | INT | 自增主键 | 是 | 否 |
| user_name | VARCHAR(50) | 用户名(唯一) | 否 | 否 |
| password | VARCHAR(255) | 密码 | 否 | 否 |
| VARCHAR(100) | 邮箱 | 否 | 是 | |
| avatar | VARCHAR(255) | 头像路径 | 否 | 是 |
| is_admin | TINYINT(1) | 是否管理员 | 否 | 是 |
| created_at | DATETIME | 创建时间 | 否 | 是 |
| updated_at | DATETIME | 更新时间 | 否 | 是 |
表3.2 metro_lines 表结构
| 字段名称 | 字段类型 | 字段说明 | 是否主键 | 是否为空 |
|---|---|---|---|---|
| line_id | VARCHAR(20) | 线路 ID | 是 | 否 |
| line_name | VARCHAR(50) | 线路名称 | 否 | 否 |
| city | VARCHAR(50) | 所属城市 | 否 | 否 |
| total_stations | INT | 站点总数 | 否 | 是 |
| length_km | DECIMAL(10,2) | 线路长度(公里) | 否 | 是 |
| opening_date | DATE | 开通日期 | 否 | 是 |
| created_at | DATETIME | 创建时间 | 否 | 是 |
表3.3 metro_stations 表结构
| 字段名称 | 字段类型 | 字段说明 | 是否主键 | 是否为空 |
|---|---|---|---|---|
| station_id | VARCHAR(20) | 站点 ID | 是 | 否 |
| station_name | VARCHAR(100) | 站点名称 | 否 | 否 |
| line_id | VARCHAR(20) | 所属线路 ID | 否 | 否 |
| city | VARCHAR(50) | 所属城市 | 否 | 否 |
| station_order | INT | 站点顺序 | 否 | 是 |
| latitude | DECIMAL(10,6) | 纬度 | 否 | 是 |
| longitude | DECIMAL(10,6) | 经度 | 否 | 是 |
| is_transfer | TINYINT(1) | 是否换乘站 | 否 | 是 |
| created_at | DATETIME | 创建时间 | 否 | 是 |
表3.4 passenger_flow 表结构
| 字段名称 | 字段类型 | 字段说明 | 是否主键 | 是否为空 |
|---|---|---|---|---|
| id | BIGINT | 自增主键 | 是 | 否 |
| datetime | DATETIME | 日期时间 | 否 | 否 |
| station_id | VARCHAR(20) | 站点 ID | 否 | 否 |
| line_id | VARCHAR(20) | 线路 ID | 否 | 否 |
| city | VARCHAR(50) | 城市 | 否 | 否 |
| inbound_flow | INT | 进站客流量 | 否 | 是 |
| outbound_flow | INT | 出站客流量 | 否 | 是 |
| total_flow | INT | 总客流量 | 否 | 是 |
| hour | INT | 小时(0-23) | 否 | 是 |
| day_of_week | INT | 星期几 | 否 | 是 |
| is_holiday | TINYINT(1) | 是否节假日 | 否 | 是 |
| is_weekend | TINYINT(1) | 是否周末 | 否 | 是 |
| weather | VARCHAR(20) | 天气状况 | 否 | 是 |
| temperature | DECIMAL(5,2) | 温度(℃) | 否 | 是 |
| created_at | DATETIME | 创建时间 | 否 | 是 |
4 系统实现
4.0 系统整体实现流程
实现按「环境与库表 → 鉴权与壳页 → 可视化 bundle → 随机森林预测 → AI 助手 → 自研后台 → 联调」推进。配置集中在 config.py / .env(端口 5000、库 metro_flow_db)。
图4.0 系统整体实现流程图
#mermaid-svg-OZi97PZNPkjdo0C2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-OZi97PZNPkjdo0C2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OZi97PZNPkjdo0C2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OZi97PZNPkjdo0C2 .error-icon{fill:#552222;}#mermaid-svg-OZi97PZNPkjdo0C2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OZi97PZNPkjdo0C2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OZi97PZNPkjdo0C2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OZi97PZNPkjdo0C2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OZi97PZNPkjdo0C2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OZi97PZNPkjdo0C2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OZi97PZNPkjdo0C2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OZi97PZNPkjdo0C2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OZi97PZNPkjdo0C2 .marker.cross{stroke:#333333;}#mermaid-svg-OZi97PZNPkjdo0C2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OZi97PZNPkjdo0C2 p{margin:0;}#mermaid-svg-OZi97PZNPkjdo0C2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OZi97PZNPkjdo0C2 .cluster-label text{fill:#333;}#mermaid-svg-OZi97PZNPkjdo0C2 .cluster-label span{color:#333;}#mermaid-svg-OZi97PZNPkjdo0C2 .cluster-label span p{background-color:transparent;}#mermaid-svg-OZi97PZNPkjdo0C2 .label text,#mermaid-svg-OZi97PZNPkjdo0C2 span{fill:#333;color:#333;}#mermaid-svg-OZi97PZNPkjdo0C2 .node rect,#mermaid-svg-OZi97PZNPkjdo0C2 .node circle,#mermaid-svg-OZi97PZNPkjdo0C2 .node ellipse,#mermaid-svg-OZi97PZNPkjdo0C2 .node polygon,#mermaid-svg-OZi97PZNPkjdo0C2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OZi97PZNPkjdo0C2 .rough-node .label text,#mermaid-svg-OZi97PZNPkjdo0C2 .node .label text,#mermaid-svg-OZi97PZNPkjdo0C2 .image-shape .label,#mermaid-svg-OZi97PZNPkjdo0C2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-OZi97PZNPkjdo0C2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OZi97PZNPkjdo0C2 .rough-node .label,#mermaid-svg-OZi97PZNPkjdo0C2 .node .label,#mermaid-svg-OZi97PZNPkjdo0C2 .image-shape .label,#mermaid-svg-OZi97PZNPkjdo0C2 .icon-shape .label{text-align:center;}#mermaid-svg-OZi97PZNPkjdo0C2 .node.clickable{cursor:pointer;}#mermaid-svg-OZi97PZNPkjdo0C2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OZi97PZNPkjdo0C2 .arrowheadPath{fill:#333333;}#mermaid-svg-OZi97PZNPkjdo0C2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OZi97PZNPkjdo0C2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OZi97PZNPkjdo0C2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OZi97PZNPkjdo0C2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OZi97PZNPkjdo0C2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OZi97PZNPkjdo0C2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OZi97PZNPkjdo0C2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OZi97PZNPkjdo0C2 .cluster text{fill:#333;}#mermaid-svg-OZi97PZNPkjdo0C2 .cluster span{color:#333;}#mermaid-svg-OZi97PZNPkjdo0C2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OZi97PZNPkjdo0C2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OZi97PZNPkjdo0C2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-OZi97PZNPkjdo0C2 .icon-shape,#mermaid-svg-OZi97PZNPkjdo0C2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OZi97PZNPkjdo0C2 .icon-shape p,#mermaid-svg-OZi97PZNPkjdo0C2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OZi97PZNPkjdo0C2 .icon-shape .label rect,#mermaid-svg-OZi97PZNPkjdo0C2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OZi97PZNPkjdo0C2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OZi97PZNPkjdo0C2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OZi97PZNPkjdo0C2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 项目初始化
配置 config 与依赖
执行 metro_flow_db.sql 建库
实现 db 与数据初始化
登录注册与 base 布局
metro_viz_charts 与可视化页
随机森林预测页与 API
AI助手接入智谱
自研 admin CRUD
主题与缓存联调
功能测试与部署
4.1 用户认证模块的实现
登录、注册、退出在 app.py 中实现;登录页使用主题背景,表单字段为 userName、password,以 JSON 返回校验结果。
功能实现流程设计
图4.1 用户认证模块实现流程图
#mermaid-svg-l3PGS1UYx8O9yaBz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-l3PGS1UYx8O9yaBz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-l3PGS1UYx8O9yaBz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-l3PGS1UYx8O9yaBz .error-icon{fill:#552222;}#mermaid-svg-l3PGS1UYx8O9yaBz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-l3PGS1UYx8O9yaBz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-l3PGS1UYx8O9yaBz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-l3PGS1UYx8O9yaBz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-l3PGS1UYx8O9yaBz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-l3PGS1UYx8O9yaBz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-l3PGS1UYx8O9yaBz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-l3PGS1UYx8O9yaBz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-l3PGS1UYx8O9yaBz .marker.cross{stroke:#333333;}#mermaid-svg-l3PGS1UYx8O9yaBz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-l3PGS1UYx8O9yaBz p{margin:0;}#mermaid-svg-l3PGS1UYx8O9yaBz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-l3PGS1UYx8O9yaBz .cluster-label text{fill:#333;}#mermaid-svg-l3PGS1UYx8O9yaBz .cluster-label span{color:#333;}#mermaid-svg-l3PGS1UYx8O9yaBz .cluster-label span p{background-color:transparent;}#mermaid-svg-l3PGS1UYx8O9yaBz .label text,#mermaid-svg-l3PGS1UYx8O9yaBz span{fill:#333;color:#333;}#mermaid-svg-l3PGS1UYx8O9yaBz .node rect,#mermaid-svg-l3PGS1UYx8O9yaBz .node circle,#mermaid-svg-l3PGS1UYx8O9yaBz .node ellipse,#mermaid-svg-l3PGS1UYx8O9yaBz .node polygon,#mermaid-svg-l3PGS1UYx8O9yaBz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-l3PGS1UYx8O9yaBz .rough-node .label text,#mermaid-svg-l3PGS1UYx8O9yaBz .node .label text,#mermaid-svg-l3PGS1UYx8O9yaBz .image-shape .label,#mermaid-svg-l3PGS1UYx8O9yaBz .icon-shape .label{text-anchor:middle;}#mermaid-svg-l3PGS1UYx8O9yaBz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-l3PGS1UYx8O9yaBz .rough-node .label,#mermaid-svg-l3PGS1UYx8O9yaBz .node .label,#mermaid-svg-l3PGS1UYx8O9yaBz .image-shape .label,#mermaid-svg-l3PGS1UYx8O9yaBz .icon-shape .label{text-align:center;}#mermaid-svg-l3PGS1UYx8O9yaBz .node.clickable{cursor:pointer;}#mermaid-svg-l3PGS1UYx8O9yaBz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-l3PGS1UYx8O9yaBz .arrowheadPath{fill:#333333;}#mermaid-svg-l3PGS1UYx8O9yaBz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-l3PGS1UYx8O9yaBz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-l3PGS1UYx8O9yaBz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-l3PGS1UYx8O9yaBz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-l3PGS1UYx8O9yaBz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-l3PGS1UYx8O9yaBz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-l3PGS1UYx8O9yaBz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-l3PGS1UYx8O9yaBz .cluster text{fill:#333;}#mermaid-svg-l3PGS1UYx8O9yaBz .cluster span{color:#333;}#mermaid-svg-l3PGS1UYx8O9yaBz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-l3PGS1UYx8O9yaBz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-l3PGS1UYx8O9yaBz rect.text{fill:none;stroke-width:0;}#mermaid-svg-l3PGS1UYx8O9yaBz .icon-shape,#mermaid-svg-l3PGS1UYx8O9yaBz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-l3PGS1UYx8O9yaBz .icon-shape p,#mermaid-svg-l3PGS1UYx8O9yaBz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-l3PGS1UYx8O9yaBz .icon-shape .label rect,#mermaid-svg-l3PGS1UYx8O9yaBz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-l3PGS1UYx8O9yaBz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-l3PGS1UYx8O9yaBz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-l3PGS1UYx8O9yaBz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
是
否
打开登录页
POST提交?
显示表单
查询 tbl_user
账号密码正确?
session 赋值 userName
前端跳转 /index
error 提示
注册提交
校验通过?
插入用户
回登录
显示错误
核心代码实现
python
# 来源:app.py --- 前台登录(节选)
@app.route('/', methods=['GET', 'POST'])
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
nxt = (request.args.get('next') or '').strip()
if nxt.startswith('/admin'):
return redirect(url_for('admin.login', next=nxt))
return render_template('login.html')
else:
return_dict = {'code': '200', 'msg': '处理成功', 'result': False}
user = db.query(
"select user_name, avatar, is_admin from tbl_user "
"where user_name = %s and password = %s",
[request.form['userName'], request.form['password']], 'select')
if user:
session['userName'] = request.form['userName']
# ... 写入头像与 is_admin ...
return jsonify(return_dict)
return_dict['code'] = '400'
return_dict['msg'] = '用户名和密码不一致'
return jsonify(return_dict)
功能访问路径
- 前台登录:
http://127.0.0.1:5000/login - 注册:
http://127.0.0.1:5000/register - 退出:
http://127.0.0.1:5000/logout - 默认账号:
admin/admin123
界面截图见第 4.7 节图4.9。
4.2 客流多维可视化模块的实现
六类可视化页共用筛选项组件与 bundle API;聚合逻辑在 utils/metro_viz_charts.py,前端由 metro_viz_pages.js 渲染。
功能实现流程设计
图4.2 客流多维可视化模块实现流程图
#mermaid-svg-2Y0dZzxKyo6LHtGB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2Y0dZzxKyo6LHtGB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2Y0dZzxKyo6LHtGB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2Y0dZzxKyo6LHtGB .error-icon{fill:#552222;}#mermaid-svg-2Y0dZzxKyo6LHtGB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2Y0dZzxKyo6LHtGB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2Y0dZzxKyo6LHtGB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2Y0dZzxKyo6LHtGB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2Y0dZzxKyo6LHtGB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2Y0dZzxKyo6LHtGB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2Y0dZzxKyo6LHtGB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2Y0dZzxKyo6LHtGB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2Y0dZzxKyo6LHtGB .marker.cross{stroke:#333333;}#mermaid-svg-2Y0dZzxKyo6LHtGB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2Y0dZzxKyo6LHtGB p{margin:0;}#mermaid-svg-2Y0dZzxKyo6LHtGB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2Y0dZzxKyo6LHtGB .cluster-label text{fill:#333;}#mermaid-svg-2Y0dZzxKyo6LHtGB .cluster-label span{color:#333;}#mermaid-svg-2Y0dZzxKyo6LHtGB .cluster-label span p{background-color:transparent;}#mermaid-svg-2Y0dZzxKyo6LHtGB .label text,#mermaid-svg-2Y0dZzxKyo6LHtGB span{fill:#333;color:#333;}#mermaid-svg-2Y0dZzxKyo6LHtGB .node rect,#mermaid-svg-2Y0dZzxKyo6LHtGB .node circle,#mermaid-svg-2Y0dZzxKyo6LHtGB .node ellipse,#mermaid-svg-2Y0dZzxKyo6LHtGB .node polygon,#mermaid-svg-2Y0dZzxKyo6LHtGB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2Y0dZzxKyo6LHtGB .rough-node .label text,#mermaid-svg-2Y0dZzxKyo6LHtGB .node .label text,#mermaid-svg-2Y0dZzxKyo6LHtGB .image-shape .label,#mermaid-svg-2Y0dZzxKyo6LHtGB .icon-shape .label{text-anchor:middle;}#mermaid-svg-2Y0dZzxKyo6LHtGB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2Y0dZzxKyo6LHtGB .rough-node .label,#mermaid-svg-2Y0dZzxKyo6LHtGB .node .label,#mermaid-svg-2Y0dZzxKyo6LHtGB .image-shape .label,#mermaid-svg-2Y0dZzxKyo6LHtGB .icon-shape .label{text-align:center;}#mermaid-svg-2Y0dZzxKyo6LHtGB .node.clickable{cursor:pointer;}#mermaid-svg-2Y0dZzxKyo6LHtGB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2Y0dZzxKyo6LHtGB .arrowheadPath{fill:#333333;}#mermaid-svg-2Y0dZzxKyo6LHtGB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2Y0dZzxKyo6LHtGB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2Y0dZzxKyo6LHtGB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2Y0dZzxKyo6LHtGB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2Y0dZzxKyo6LHtGB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2Y0dZzxKyo6LHtGB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2Y0dZzxKyo6LHtGB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2Y0dZzxKyo6LHtGB .cluster text{fill:#333;}#mermaid-svg-2Y0dZzxKyo6LHtGB .cluster span{color:#333;}#mermaid-svg-2Y0dZzxKyo6LHtGB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2Y0dZzxKyo6LHtGB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2Y0dZzxKyo6LHtGB rect.text{fill:none;stroke-width:0;}#mermaid-svg-2Y0dZzxKyo6LHtGB .icon-shape,#mermaid-svg-2Y0dZzxKyo6LHtGB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2Y0dZzxKyo6LHtGB .icon-shape p,#mermaid-svg-2Y0dZzxKyo6LHtGB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2Y0dZzxKyo6LHtGB .icon-shape .label rect,#mermaid-svg-2Y0dZzxKyo6LHtGB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2Y0dZzxKyo6LHtGB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2Y0dZzxKyo6LHtGB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2Y0dZzxKyo6LHtGB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 打开可视化页
加载 options
读取城市/线路/月份
请求对应 viz API
SQL聚合生成 labels/values
JSON返回
ECharts setOption
核心代码实现
python
# 来源:app.py / utils/metro_viz_charts.py --- 站点排行 API(节选)
@app.route('/metro/station_rank')
def metro_station_rank():
return render_template('metro_station_rank.html', active_menu='metro_station_rank')
@app.route('/metro/api/viz/station-rank')
def metro_api_viz_station_rank():
from utils.metro_viz_charts import station_rank_bundle
a = _viz_args()
return jsonify({
"success": True, "ok": True,
"data": station_rank_bundle(a["city"], a["line_id"], a["month"])
})
功能访问路径
- 站点排行:
/metro/station_rank - 线路对比:
/metro/flow_line - 客流时序:
/metro/flow_trend - 进出站分析:
/metro/inout - 天气客流:
/metro/weather_flow - 时段因素:
/metro/factor_analysis
界面截图见第 4.7 节图4.11、图4.12。
4.3 数据总览模块的实现
数据总览在 app.py 的 /data_preview 路由中实现,查询客流及相关维度并渲染表格。
功能实现流程设计
图4.3 数据总览模块实现流程图
#mermaid-svg-maJqJYPFkcjaHAIs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-maJqJYPFkcjaHAIs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-maJqJYPFkcjaHAIs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-maJqJYPFkcjaHAIs .error-icon{fill:#552222;}#mermaid-svg-maJqJYPFkcjaHAIs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-maJqJYPFkcjaHAIs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-maJqJYPFkcjaHAIs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-maJqJYPFkcjaHAIs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-maJqJYPFkcjaHAIs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-maJqJYPFkcjaHAIs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-maJqJYPFkcjaHAIs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-maJqJYPFkcjaHAIs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-maJqJYPFkcjaHAIs .marker.cross{stroke:#333333;}#mermaid-svg-maJqJYPFkcjaHAIs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-maJqJYPFkcjaHAIs p{margin:0;}#mermaid-svg-maJqJYPFkcjaHAIs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-maJqJYPFkcjaHAIs .cluster-label text{fill:#333;}#mermaid-svg-maJqJYPFkcjaHAIs .cluster-label span{color:#333;}#mermaid-svg-maJqJYPFkcjaHAIs .cluster-label span p{background-color:transparent;}#mermaid-svg-maJqJYPFkcjaHAIs .label text,#mermaid-svg-maJqJYPFkcjaHAIs span{fill:#333;color:#333;}#mermaid-svg-maJqJYPFkcjaHAIs .node rect,#mermaid-svg-maJqJYPFkcjaHAIs .node circle,#mermaid-svg-maJqJYPFkcjaHAIs .node ellipse,#mermaid-svg-maJqJYPFkcjaHAIs .node polygon,#mermaid-svg-maJqJYPFkcjaHAIs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-maJqJYPFkcjaHAIs .rough-node .label text,#mermaid-svg-maJqJYPFkcjaHAIs .node .label text,#mermaid-svg-maJqJYPFkcjaHAIs .image-shape .label,#mermaid-svg-maJqJYPFkcjaHAIs .icon-shape .label{text-anchor:middle;}#mermaid-svg-maJqJYPFkcjaHAIs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-maJqJYPFkcjaHAIs .rough-node .label,#mermaid-svg-maJqJYPFkcjaHAIs .node .label,#mermaid-svg-maJqJYPFkcjaHAIs .image-shape .label,#mermaid-svg-maJqJYPFkcjaHAIs .icon-shape .label{text-align:center;}#mermaid-svg-maJqJYPFkcjaHAIs .node.clickable{cursor:pointer;}#mermaid-svg-maJqJYPFkcjaHAIs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-maJqJYPFkcjaHAIs .arrowheadPath{fill:#333333;}#mermaid-svg-maJqJYPFkcjaHAIs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-maJqJYPFkcjaHAIs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-maJqJYPFkcjaHAIs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-maJqJYPFkcjaHAIs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-maJqJYPFkcjaHAIs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-maJqJYPFkcjaHAIs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-maJqJYPFkcjaHAIs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-maJqJYPFkcjaHAIs .cluster text{fill:#333;}#mermaid-svg-maJqJYPFkcjaHAIs .cluster span{color:#333;}#mermaid-svg-maJqJYPFkcjaHAIs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-maJqJYPFkcjaHAIs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-maJqJYPFkcjaHAIs rect.text{fill:none;stroke-width:0;}#mermaid-svg-maJqJYPFkcjaHAIs .icon-shape,#mermaid-svg-maJqJYPFkcjaHAIs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-maJqJYPFkcjaHAIs .icon-shape p,#mermaid-svg-maJqJYPFkcjaHAIs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-maJqJYPFkcjaHAIs .icon-shape .label rect,#mermaid-svg-maJqJYPFkcjaHAIs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-maJqJYPFkcjaHAIs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-maJqJYPFkcjaHAIs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-maJqJYPFkcjaHAIs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 打开 /data_preview
解析筛选参数
分页查询客流
渲染 data_preview.html
用户翻页或改条件
核心代码实现
python
# 来源:app.py --- 数据总览路由(节选)
@app.route('/data_preview', methods=['GET', 'POST'])
def data_preview():
userName = session.get('userName')
# 按站点/城市/时间等条件查询 passenger_flow
# 组装分页结果后渲染 templates/data_preview.html
return render_template(
'data_preview.html',
userName=userName,
active_menu='data_preview',
# ... rows, total, filters ...
)
功能访问路径
- 数据总览:
http://127.0.0.1:5000/data_preview
界面截图见第 4.7 节图4.15。
4.4 随机森林客流预测模块的实现
页面 metro_rf_predict.html,算法类位于 models/random_forest_model.py,接口 /metro/api/rf_predict 一次完成训练与预测。
功能实现流程设计
图4.4 随机森林客流预测模块实现流程图
#mermaid-svg-o0Y7I0LgjxLVeQUe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-o0Y7I0LgjxLVeQUe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-o0Y7I0LgjxLVeQUe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-o0Y7I0LgjxLVeQUe .error-icon{fill:#552222;}#mermaid-svg-o0Y7I0LgjxLVeQUe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-o0Y7I0LgjxLVeQUe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-o0Y7I0LgjxLVeQUe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-o0Y7I0LgjxLVeQUe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-o0Y7I0LgjxLVeQUe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-o0Y7I0LgjxLVeQUe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-o0Y7I0LgjxLVeQUe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-o0Y7I0LgjxLVeQUe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-o0Y7I0LgjxLVeQUe .marker.cross{stroke:#333333;}#mermaid-svg-o0Y7I0LgjxLVeQUe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-o0Y7I0LgjxLVeQUe p{margin:0;}#mermaid-svg-o0Y7I0LgjxLVeQUe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-o0Y7I0LgjxLVeQUe .cluster-label text{fill:#333;}#mermaid-svg-o0Y7I0LgjxLVeQUe .cluster-label span{color:#333;}#mermaid-svg-o0Y7I0LgjxLVeQUe .cluster-label span p{background-color:transparent;}#mermaid-svg-o0Y7I0LgjxLVeQUe .label text,#mermaid-svg-o0Y7I0LgjxLVeQUe span{fill:#333;color:#333;}#mermaid-svg-o0Y7I0LgjxLVeQUe .node rect,#mermaid-svg-o0Y7I0LgjxLVeQUe .node circle,#mermaid-svg-o0Y7I0LgjxLVeQUe .node ellipse,#mermaid-svg-o0Y7I0LgjxLVeQUe .node polygon,#mermaid-svg-o0Y7I0LgjxLVeQUe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-o0Y7I0LgjxLVeQUe .rough-node .label text,#mermaid-svg-o0Y7I0LgjxLVeQUe .node .label text,#mermaid-svg-o0Y7I0LgjxLVeQUe .image-shape .label,#mermaid-svg-o0Y7I0LgjxLVeQUe .icon-shape .label{text-anchor:middle;}#mermaid-svg-o0Y7I0LgjxLVeQUe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-o0Y7I0LgjxLVeQUe .rough-node .label,#mermaid-svg-o0Y7I0LgjxLVeQUe .node .label,#mermaid-svg-o0Y7I0LgjxLVeQUe .image-shape .label,#mermaid-svg-o0Y7I0LgjxLVeQUe .icon-shape .label{text-align:center;}#mermaid-svg-o0Y7I0LgjxLVeQUe .node.clickable{cursor:pointer;}#mermaid-svg-o0Y7I0LgjxLVeQUe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-o0Y7I0LgjxLVeQUe .arrowheadPath{fill:#333333;}#mermaid-svg-o0Y7I0LgjxLVeQUe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-o0Y7I0LgjxLVeQUe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-o0Y7I0LgjxLVeQUe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o0Y7I0LgjxLVeQUe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-o0Y7I0LgjxLVeQUe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o0Y7I0LgjxLVeQUe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-o0Y7I0LgjxLVeQUe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-o0Y7I0LgjxLVeQUe .cluster text{fill:#333;}#mermaid-svg-o0Y7I0LgjxLVeQUe .cluster span{color:#333;}#mermaid-svg-o0Y7I0LgjxLVeQUe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-o0Y7I0LgjxLVeQUe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-o0Y7I0LgjxLVeQUe rect.text{fill:none;stroke-width:0;}#mermaid-svg-o0Y7I0LgjxLVeQUe .icon-shape,#mermaid-svg-o0Y7I0LgjxLVeQUe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o0Y7I0LgjxLVeQUe .icon-shape p,#mermaid-svg-o0Y7I0LgjxLVeQUe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-o0Y7I0LgjxLVeQUe .icon-shape .label rect,#mermaid-svg-o0Y7I0LgjxLVeQUe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o0Y7I0LgjxLVeQUe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-o0Y7I0LgjxLVeQUe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-o0Y7I0LgjxLVeQUe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 打开预测页
选择站点与步数
前端 POST JSON
拉历史序列
train + 留出评估
predict 多步
返回曲线与指标
ECharts 展示
核心代码实现
python
# 来源:models/random_forest_model.py --- 特征与训练(节选)
class RandomForestPredictor:
def __init__(self, lookback=24, n_estimators=80, random_state=42):
self.lookback = int(lookback)
self.n_estimators = int(n_estimators)
self.model = None
def _feature_row(self, lag_values, ts):
hour = int(ts.hour)
dow = int(ts.dayofweek)
hour_sin = np.sin(2 * np.pi * hour / 24.0)
hour_cos = np.cos(2 * np.pi * hour / 24.0)
return np.asarray(list(lag_values) + [hour, dow, hour_sin, hour_cos], dtype=float)
def train(self, train_data):
# 时间顺序 80%/20% 留出,拟合 RandomForestRegressor
...
python
# 来源:app.py --- 一键预测 API(节选)
@app.route('/metro/api/rf_predict', methods=['POST'])
def metro_api_rf_predict():
from models.random_forest_model import RandomForestPredictor
# 查询历史 → model.train(df) → model.predict(steps)
return jsonify({
'success': True,
'metrics': metrics,
'history': {'labels': hist_labels, 'values': hist_values},
'forecast': {'labels': fut_labels, 'values': fut_values},
})
功能访问路径
- 预测页:
http://127.0.0.1:5000/metro/rf_predict - 预测 API:
POST /metro/api/rf_predict
界面截图见第 4.7 节图4.13。
4.5 AI智能助手模块的实现
页面 metro_ai_assistant.html,服务类 utils/ai_assistant.py,接口 /metro/api/ai_ask。
功能实现流程设计
图4.5 AI智能助手模块实现流程图
#mermaid-svg-gQhdfYRhRbMMFiRm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gQhdfYRhRbMMFiRm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gQhdfYRhRbMMFiRm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gQhdfYRhRbMMFiRm .error-icon{fill:#552222;}#mermaid-svg-gQhdfYRhRbMMFiRm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gQhdfYRhRbMMFiRm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gQhdfYRhRbMMFiRm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gQhdfYRhRbMMFiRm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gQhdfYRhRbMMFiRm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gQhdfYRhRbMMFiRm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gQhdfYRhRbMMFiRm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gQhdfYRhRbMMFiRm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gQhdfYRhRbMMFiRm .marker.cross{stroke:#333333;}#mermaid-svg-gQhdfYRhRbMMFiRm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gQhdfYRhRbMMFiRm p{margin:0;}#mermaid-svg-gQhdfYRhRbMMFiRm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gQhdfYRhRbMMFiRm .cluster-label text{fill:#333;}#mermaid-svg-gQhdfYRhRbMMFiRm .cluster-label span{color:#333;}#mermaid-svg-gQhdfYRhRbMMFiRm .cluster-label span p{background-color:transparent;}#mermaid-svg-gQhdfYRhRbMMFiRm .label text,#mermaid-svg-gQhdfYRhRbMMFiRm span{fill:#333;color:#333;}#mermaid-svg-gQhdfYRhRbMMFiRm .node rect,#mermaid-svg-gQhdfYRhRbMMFiRm .node circle,#mermaid-svg-gQhdfYRhRbMMFiRm .node ellipse,#mermaid-svg-gQhdfYRhRbMMFiRm .node polygon,#mermaid-svg-gQhdfYRhRbMMFiRm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gQhdfYRhRbMMFiRm .rough-node .label text,#mermaid-svg-gQhdfYRhRbMMFiRm .node .label text,#mermaid-svg-gQhdfYRhRbMMFiRm .image-shape .label,#mermaid-svg-gQhdfYRhRbMMFiRm .icon-shape .label{text-anchor:middle;}#mermaid-svg-gQhdfYRhRbMMFiRm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gQhdfYRhRbMMFiRm .rough-node .label,#mermaid-svg-gQhdfYRhRbMMFiRm .node .label,#mermaid-svg-gQhdfYRhRbMMFiRm .image-shape .label,#mermaid-svg-gQhdfYRhRbMMFiRm .icon-shape .label{text-align:center;}#mermaid-svg-gQhdfYRhRbMMFiRm .node.clickable{cursor:pointer;}#mermaid-svg-gQhdfYRhRbMMFiRm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gQhdfYRhRbMMFiRm .arrowheadPath{fill:#333333;}#mermaid-svg-gQhdfYRhRbMMFiRm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gQhdfYRhRbMMFiRm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gQhdfYRhRbMMFiRm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gQhdfYRhRbMMFiRm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gQhdfYRhRbMMFiRm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gQhdfYRhRbMMFiRm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gQhdfYRhRbMMFiRm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gQhdfYRhRbMMFiRm .cluster text{fill:#333;}#mermaid-svg-gQhdfYRhRbMMFiRm .cluster span{color:#333;}#mermaid-svg-gQhdfYRhRbMMFiRm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gQhdfYRhRbMMFiRm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gQhdfYRhRbMMFiRm rect.text{fill:none;stroke-width:0;}#mermaid-svg-gQhdfYRhRbMMFiRm .icon-shape,#mermaid-svg-gQhdfYRhRbMMFiRm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gQhdfYRhRbMMFiRm .icon-shape p,#mermaid-svg-gQhdfYRhRbMMFiRm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gQhdfYRhRbMMFiRm .icon-shape .label rect,#mermaid-svg-gQhdfYRhRbMMFiRm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gQhdfYRhRbMMFiRm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gQhdfYRhRbMMFiRm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gQhdfYRhRbMMFiRm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 打开AI助手页
输入问题
POST /metro/api/ai_ask
MetroAIAssistant.ask
请求智谱 chat/completions
返回回答文本
对话区追加展示
核心代码实现
python
# 来源:utils/ai_assistant.py --- 智谱调用(节选)
class MetroAIAssistant:
def __init__(self):
self.api_key = (os.getenv("ZHIPU_API_KEY") or "").strip()
self.base_url = (os.getenv("ZHIPU_BASE_URL")
or "https://open.bigmodel.cn/api/paas/v4").rstrip("/")
self.model = os.getenv("ZHIPU_MODEL", "glm-4-flash").strip()
def ask(self, question, history=None):
url = f"{self.base_url}/chat/completions"
headers = {"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"}
payload = {"model": self.model, "messages": messages,
"temperature": 0.7, "max_tokens": 2048}
resp = requests.post(url, headers=headers, json=payload, timeout=self.timeout)
...
功能访问路径
- AI 助手:
http://127.0.0.1:5000/metro/ai_assistant - 问答 API:
POST /metro/api/ai_ask
界面截图见第 4.7 节图4.14。
4.6 后台数据管理模块的实现
后台 Blueprint 注册于 app.py:app.register_blueprint(admin_bp)。模块定义见 utils/admin_registry.py,路由见 utils/admin_blueprint.py。
功能实现流程设计
图4.6 后台数据管理模块实现流程图
#mermaid-svg-GPQEP1SQ6QYBzEtN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GPQEP1SQ6QYBzEtN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GPQEP1SQ6QYBzEtN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GPQEP1SQ6QYBzEtN .error-icon{fill:#552222;}#mermaid-svg-GPQEP1SQ6QYBzEtN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GPQEP1SQ6QYBzEtN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GPQEP1SQ6QYBzEtN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GPQEP1SQ6QYBzEtN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GPQEP1SQ6QYBzEtN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GPQEP1SQ6QYBzEtN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GPQEP1SQ6QYBzEtN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GPQEP1SQ6QYBzEtN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GPQEP1SQ6QYBzEtN .marker.cross{stroke:#333333;}#mermaid-svg-GPQEP1SQ6QYBzEtN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GPQEP1SQ6QYBzEtN p{margin:0;}#mermaid-svg-GPQEP1SQ6QYBzEtN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GPQEP1SQ6QYBzEtN .cluster-label text{fill:#333;}#mermaid-svg-GPQEP1SQ6QYBzEtN .cluster-label span{color:#333;}#mermaid-svg-GPQEP1SQ6QYBzEtN .cluster-label span p{background-color:transparent;}#mermaid-svg-GPQEP1SQ6QYBzEtN .label text,#mermaid-svg-GPQEP1SQ6QYBzEtN span{fill:#333;color:#333;}#mermaid-svg-GPQEP1SQ6QYBzEtN .node rect,#mermaid-svg-GPQEP1SQ6QYBzEtN .node circle,#mermaid-svg-GPQEP1SQ6QYBzEtN .node ellipse,#mermaid-svg-GPQEP1SQ6QYBzEtN .node polygon,#mermaid-svg-GPQEP1SQ6QYBzEtN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GPQEP1SQ6QYBzEtN .rough-node .label text,#mermaid-svg-GPQEP1SQ6QYBzEtN .node .label text,#mermaid-svg-GPQEP1SQ6QYBzEtN .image-shape .label,#mermaid-svg-GPQEP1SQ6QYBzEtN .icon-shape .label{text-anchor:middle;}#mermaid-svg-GPQEP1SQ6QYBzEtN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GPQEP1SQ6QYBzEtN .rough-node .label,#mermaid-svg-GPQEP1SQ6QYBzEtN .node .label,#mermaid-svg-GPQEP1SQ6QYBzEtN .image-shape .label,#mermaid-svg-GPQEP1SQ6QYBzEtN .icon-shape .label{text-align:center;}#mermaid-svg-GPQEP1SQ6QYBzEtN .node.clickable{cursor:pointer;}#mermaid-svg-GPQEP1SQ6QYBzEtN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GPQEP1SQ6QYBzEtN .arrowheadPath{fill:#333333;}#mermaid-svg-GPQEP1SQ6QYBzEtN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GPQEP1SQ6QYBzEtN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GPQEP1SQ6QYBzEtN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GPQEP1SQ6QYBzEtN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GPQEP1SQ6QYBzEtN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GPQEP1SQ6QYBzEtN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GPQEP1SQ6QYBzEtN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GPQEP1SQ6QYBzEtN .cluster text{fill:#333;}#mermaid-svg-GPQEP1SQ6QYBzEtN .cluster span{color:#333;}#mermaid-svg-GPQEP1SQ6QYBzEtN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GPQEP1SQ6QYBzEtN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GPQEP1SQ6QYBzEtN rect.text{fill:none;stroke-width:0;}#mermaid-svg-GPQEP1SQ6QYBzEtN .icon-shape,#mermaid-svg-GPQEP1SQ6QYBzEtN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GPQEP1SQ6QYBzEtN .icon-shape p,#mermaid-svg-GPQEP1SQ6QYBzEtN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GPQEP1SQ6QYBzEtN .icon-shape .label rect,#mermaid-svg-GPQEP1SQ6QYBzEtN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GPQEP1SQ6QYBzEtN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GPQEP1SQ6QYBzEtN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GPQEP1SQ6QYBzEtN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 打开 /admin/login
管理员登录
/admin/ 概览
进入 users/lines/stations/flows
列表/表单 CRUD
写库成功
invalidate_viz_cache
核心代码实现
python
# 来源:utils/admin_blueprint.py --- 后台登录与权限(节选)
admin_bp = Blueprint("admin", __name__, url_prefix="/admin")
def admin_required(view):
@wraps(view)
def wrapped(*args, **kwargs):
if not _is_admin_user():
flash("请使用管理员账号登录后台", "warning")
return redirect(url_for("admin.login", next=request.path))
return view(*args, **kwargs)
return wrapped
@admin_bp.route("/login", methods=["GET", "POST"])
def login():
# 校验 tbl_user 中管理员账号,成功写入 session["userName"]
...
功能访问路径
- 后台登录:
http://127.0.0.1:5000/admin/login - 后台首页:
http://127.0.0.1:5000/admin/ - 模块:用户 / 线路 / 站点 / 客流数据
界面截图见第 4.7 节图4.16、图4.17。
4.7 用户界面实现
界面基于 templates/base.html 侧栏布局与 metro-theme.css。首页 /index 展示站点选择与客流概览图表;可视化、预测、AI、后台分别对应独立模板。
功能实现流程设计
图4.7 用户界面功能实现流程图
#mermaid-svg-HcDatzJITW7FWUnw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HcDatzJITW7FWUnw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HcDatzJITW7FWUnw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HcDatzJITW7FWUnw .error-icon{fill:#552222;}#mermaid-svg-HcDatzJITW7FWUnw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HcDatzJITW7FWUnw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HcDatzJITW7FWUnw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HcDatzJITW7FWUnw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HcDatzJITW7FWUnw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HcDatzJITW7FWUnw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HcDatzJITW7FWUnw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HcDatzJITW7FWUnw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HcDatzJITW7FWUnw .marker.cross{stroke:#333333;}#mermaid-svg-HcDatzJITW7FWUnw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HcDatzJITW7FWUnw p{margin:0;}#mermaid-svg-HcDatzJITW7FWUnw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HcDatzJITW7FWUnw .cluster-label text{fill:#333;}#mermaid-svg-HcDatzJITW7FWUnw .cluster-label span{color:#333;}#mermaid-svg-HcDatzJITW7FWUnw .cluster-label span p{background-color:transparent;}#mermaid-svg-HcDatzJITW7FWUnw .label text,#mermaid-svg-HcDatzJITW7FWUnw span{fill:#333;color:#333;}#mermaid-svg-HcDatzJITW7FWUnw .node rect,#mermaid-svg-HcDatzJITW7FWUnw .node circle,#mermaid-svg-HcDatzJITW7FWUnw .node ellipse,#mermaid-svg-HcDatzJITW7FWUnw .node polygon,#mermaid-svg-HcDatzJITW7FWUnw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HcDatzJITW7FWUnw .rough-node .label text,#mermaid-svg-HcDatzJITW7FWUnw .node .label text,#mermaid-svg-HcDatzJITW7FWUnw .image-shape .label,#mermaid-svg-HcDatzJITW7FWUnw .icon-shape .label{text-anchor:middle;}#mermaid-svg-HcDatzJITW7FWUnw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HcDatzJITW7FWUnw .rough-node .label,#mermaid-svg-HcDatzJITW7FWUnw .node .label,#mermaid-svg-HcDatzJITW7FWUnw .image-shape .label,#mermaid-svg-HcDatzJITW7FWUnw .icon-shape .label{text-align:center;}#mermaid-svg-HcDatzJITW7FWUnw .node.clickable{cursor:pointer;}#mermaid-svg-HcDatzJITW7FWUnw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HcDatzJITW7FWUnw .arrowheadPath{fill:#333333;}#mermaid-svg-HcDatzJITW7FWUnw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HcDatzJITW7FWUnw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HcDatzJITW7FWUnw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HcDatzJITW7FWUnw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HcDatzJITW7FWUnw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HcDatzJITW7FWUnw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HcDatzJITW7FWUnw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HcDatzJITW7FWUnw .cluster text{fill:#333;}#mermaid-svg-HcDatzJITW7FWUnw .cluster span{color:#333;}#mermaid-svg-HcDatzJITW7FWUnw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HcDatzJITW7FWUnw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HcDatzJITW7FWUnw rect.text{fill:none;stroke-width:0;}#mermaid-svg-HcDatzJITW7FWUnw .icon-shape,#mermaid-svg-HcDatzJITW7FWUnw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HcDatzJITW7FWUnw .icon-shape p,#mermaid-svg-HcDatzJITW7FWUnw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HcDatzJITW7FWUnw .icon-shape .label rect,#mermaid-svg-HcDatzJITW7FWUnw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HcDatzJITW7FWUnw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HcDatzJITW7FWUnw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HcDatzJITW7FWUnw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 可视化
总览
预测
AI
后台
用户登录成功
进入 /index 首页
侧栏选择功能
目标页面
六类图表页
数据总览表
随机森林页
助手对话页
新窗口打开 /admin/
图4.9 前台用户登录界面

(系统运行截图)
图4.10 系统首页界面

(系统运行截图)
图4.11 站点排行界面

(系统运行截图)
图4.12 客流时序界面

(系统运行截图)
图4.13 随机森林预测界面

(系统运行截图)
图4.14 AI助手界面

(系统运行截图)
图4.15 数据总览界面

(系统运行截图)
图4.16 后台管理界面

(系统运行截图)
图4.17 后台登录界面

(系统运行截图)
4.8 系统集成与部署
功能实现流程设计
图4.8 系统集成与部署功能实现流程图
#mermaid-svg-jYZk20clQGh7w2Id{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jYZk20clQGh7w2Id .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jYZk20clQGh7w2Id .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jYZk20clQGh7w2Id .error-icon{fill:#552222;}#mermaid-svg-jYZk20clQGh7w2Id .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jYZk20clQGh7w2Id .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jYZk20clQGh7w2Id .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jYZk20clQGh7w2Id .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jYZk20clQGh7w2Id .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jYZk20clQGh7w2Id .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jYZk20clQGh7w2Id .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jYZk20clQGh7w2Id .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jYZk20clQGh7w2Id .marker.cross{stroke:#333333;}#mermaid-svg-jYZk20clQGh7w2Id svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jYZk20clQGh7w2Id p{margin:0;}#mermaid-svg-jYZk20clQGh7w2Id .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jYZk20clQGh7w2Id .cluster-label text{fill:#333;}#mermaid-svg-jYZk20clQGh7w2Id .cluster-label span{color:#333;}#mermaid-svg-jYZk20clQGh7w2Id .cluster-label span p{background-color:transparent;}#mermaid-svg-jYZk20clQGh7w2Id .label text,#mermaid-svg-jYZk20clQGh7w2Id span{fill:#333;color:#333;}#mermaid-svg-jYZk20clQGh7w2Id .node rect,#mermaid-svg-jYZk20clQGh7w2Id .node circle,#mermaid-svg-jYZk20clQGh7w2Id .node ellipse,#mermaid-svg-jYZk20clQGh7w2Id .node polygon,#mermaid-svg-jYZk20clQGh7w2Id .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jYZk20clQGh7w2Id .rough-node .label text,#mermaid-svg-jYZk20clQGh7w2Id .node .label text,#mermaid-svg-jYZk20clQGh7w2Id .image-shape .label,#mermaid-svg-jYZk20clQGh7w2Id .icon-shape .label{text-anchor:middle;}#mermaid-svg-jYZk20clQGh7w2Id .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jYZk20clQGh7w2Id .rough-node .label,#mermaid-svg-jYZk20clQGh7w2Id .node .label,#mermaid-svg-jYZk20clQGh7w2Id .image-shape .label,#mermaid-svg-jYZk20clQGh7w2Id .icon-shape .label{text-align:center;}#mermaid-svg-jYZk20clQGh7w2Id .node.clickable{cursor:pointer;}#mermaid-svg-jYZk20clQGh7w2Id .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jYZk20clQGh7w2Id .arrowheadPath{fill:#333333;}#mermaid-svg-jYZk20clQGh7w2Id .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jYZk20clQGh7w2Id .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jYZk20clQGh7w2Id .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jYZk20clQGh7w2Id .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jYZk20clQGh7w2Id .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jYZk20clQGh7w2Id .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jYZk20clQGh7w2Id .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jYZk20clQGh7w2Id .cluster text{fill:#333;}#mermaid-svg-jYZk20clQGh7w2Id .cluster span{color:#333;}#mermaid-svg-jYZk20clQGh7w2Id div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jYZk20clQGh7w2Id .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jYZk20clQGh7w2Id rect.text{fill:none;stroke-width:0;}#mermaid-svg-jYZk20clQGh7w2Id .icon-shape,#mermaid-svg-jYZk20clQGh7w2Id .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jYZk20clQGh7w2Id .icon-shape p,#mermaid-svg-jYZk20clQGh7w2Id .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jYZk20clQGh7w2Id .icon-shape .label rect,#mermaid-svg-jYZk20clQGh7w2Id .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jYZk20clQGh7w2Id .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jYZk20clQGh7w2Id .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jYZk20clQGh7w2Id :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 安装 Python 依赖
配置 .env 与 MySQL
导入 metro_flow_db.sql
python app.py
监听 0.0.0.0:5000
前台 /login 与后台 /admin/login 验证
演示可视化与随机森林预测
部署步骤简述:
- 安装依赖:
pip install -r requirements.txt - 创建并导入数据库:
metro_flow_db.sql,确认库名metro_flow_db - 配置
.env(数据库账号、可选ZHIPU_API_KEY) - 启动:
python app.py(端口 5000) - 访问:前台
http://127.0.0.1:5000/login,后台http://127.0.0.1:5000/admin/login,账号admin/admin123
5 系统测试
5.1 系统功能测试
5.1.1 用户认证功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-A01 | 正确登录 | 库中存在 admin/admin123 | 打开 /login,输入 admin/admin123 提交 |
登录成功并进入系统 | 通过 |
| TC-A02 | 错误密码 | 账号存在 | 输入错误密码提交 | 提示用户名和密码不一致 | 通过 |
| TC-A03 | 注册新用户 | 用户名未占用 | /register 填写一致密码并提交 |
注册成功可登录 | 通过 |
| TC-A04 | 未登录拦截 | 清空会话 | 直接访问 /index |
跳转 /login |
通过 |
| TC-A05 | 退出登录 | 已登录 | 点击退出 | 会话清空并回登录页 | 通过 |
5.1.2 数据管理功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-B01 | 后台登录 | admin 账号 | 打开 /admin/login 登录 |
进入后台概览 | 通过 |
| TC-B02 | 用户列表 | 已登后台 | 打开用户管理 | 显示 tbl_user 列表 | 通过 |
| TC-B03 | 新增线路 | 已登后台 | 线路管理中新增一条记录 | 保存成功并可检索 | 通过 |
| TC-B04 | 编辑站点 | 已有站点 | 修改站点名称并保存 | 列表显示更新后数据 | 通过 |
| TC-B05 | 客流增删 | 已有线路站点 | 新增/删除客流记录 | 写库成功,可视化缓存失效 | 通过 |
5.1.3 核心业务功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-C01 | 首页访问 | 已登录 | 打开 /index |
页面与侧栏正常 | 通过 |
| TC-C02 | 站点排行 | 库有客流 | 打开 /metro/station_rank 选筛选 |
图表显示 TOP 站点 | 通过 |
| TC-C03 | 客流时序 | 库有客流 | 打开 /metro/flow_trend |
折线时序正常 | 通过 |
| TC-C04 | 随机森林预测 | 站点历史充足 | /metro/rf_predict 点击预测 |
返回指标与预测曲线 | 通过 |
| TC-C05 | AI 问答 | 已配置 Key | /metro/ai_assistant 提问 |
返回中文回答 | 通过 |
5.1.4 数据分析功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-D01 | 线路对比 | 多线路数据 | /metro/flow_line |
各线路客流对比图正确 | 通过 |
| TC-D02 | 进出站分析 | 有进出站字段 | /metro/inout |
进/出站系列可读 | 通过 |
| TC-D03 | 天气客流 | 有天气字段 | /metro/weather_flow |
天气维度聚合正确 | 通过 |
| TC-D04 | 时段因素 | 有小时字段 | /metro/factor_analysis |
时段分布图正确 | 通过 |
| TC-D05 | 数据总览筛选 | 库有记录 | /data_preview 改条件 |
表格按条件刷新 | 通过 |
5.1.5 系统集成功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-E01 | 前后台联通 | 服务已启动 | 前台改客流后刷新可视化 | 图表反映新数据 | 通过 |
| TC-E02 | 后台写后缓存 | 改 passenger_flow | 再打开站点排行 | 结果更新而非旧缓存 | 通过 |
| TC-E03 | 会话共享 | 后台已登录 | 同浏览器打开前台页 | 可正常访问受保护页 | 通过 |
| TC-E04 | 端口与入口 | 本机环境 | python app.py |
监听 5000 可访问 | 通过 |
| TC-E05 | 静态资源本地 | 断外网 | 打开可视化页 | ECharts/样式仍可用 | 通过 |
5.1.6 用户界面功能测试用例
| 编号 | 说明 | 条件 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|---|
| TC-F01 | 侧栏高亮 | 已登录 | 依次点击各菜单 | active 状态与页面一致 | 通过 |
| TC-F02 | 主题样式 | 已登录 | 查看首页与后台 | metro-theme 生效 | 通过 |
| TC-F03 | 图表悬停 | 可视化页 | 鼠标悬停系列 | tip 显示名称与数值 | 通过 |
| TC-F04 | 预测页控件 | 预测页 | 改步数再预测 | 曲线长度随步数变化 | 通过 |
| TC-F05 | 后台登录页 | 未登后台 | 打开 /admin/login |
独立登录界面展示 | 通过 |
5.2 系统性能测试
5.2.1 响应时间性能分析
在本地演示数据规模下,登录接口与普通页面渲染通常在数百毫秒内完成;可视化 bundle 首次查询受 SQL 聚合影响,二次访问因 TTL 缓存明显加快;随机森林一键预测受样本长度与 n_estimators 影响,一般可在数秒内返回,满足课题演示。
5.2.2 并发性能测试
课题场景以单人/少量并发为主。Flask 开发服务器适合演示;若需更高并发,可改为 Waitress/gunicorn 等 WSGI 部署,并对可视化查询加索引与缓存。智谱免费档存在 RPM 限制,AI 助手宜一问一答演示。
5.2.3 数据库性能测试
passenger_flow 在 datetime、station_id、line_id、city 等字段建有索引,支撑按城市/线路/月份筛选。站点与线路外键保证引用完整。大批量写入时建议批量提交,避免逐条插入拖慢初始化。
5.2.4 性能优化措施
- 可视化 bundle 短时缓存(约 120 秒)与版本失效。
- 预测接口限制历史条数上限(如 2000)与步数范围,控制训练耗时。
- 静态资源本地化,减少外网依赖。
- 后台写操作后主动
invalidate_viz_cache(),避免脏读与过久缓存并存。 - SQL 参数化与必要索引,降低全表扫描概率。
6 总结与展望
6.1 总结
本文完成了基于 Flask 的城市地铁客流量数据预测系统的设计与实现。系统以 MySQL 库 metro_flow_db 存储用户、线路、站点与客流,前台提供登录鉴权、六类客流可视化、数据总览、随机森林一键预测与智谱 AI 助手,后台提供独立登录与四类数据 CRUD。入口 app.py 运行于端口 5000,默认账号 admin/admin123。实践表明,该方案结构清晰、部署轻量,能够支撑毕业设计答辩与教学演示。
6.2 展望
后续可在不改变现有交付主线的前提下,扩展更多城市样本、引入更细粒度的换乘站分析、优化随机森林特征与超参搜索,并将 AI 助手与库内统计结果做更深度的数据接地,使问答回答更具业务针对性。生产环境可进一步强化密码哈希、权限分级与 WSGI 部署。
参考文献
1 张海藩, 牟永敏. 软件工程导论M. 北京: 清华大学出版社, 2013.
2 Grinberg M. Flask Web DevelopmentM. 2nd ed. Sebastopol: O'Reilly Media, 2018.
3 王珊, 萨师煊. 数据库系统概论M. 北京: 高等教育出版社, 2014.
4 McKinney W. Python for Data AnalysisM. 3rd ed. Sebastopol: O'Reilly Media, 2022.
5 Pedregosa F, et al. Scikit-learn: Machine Learning in PythonJ. Journal of Machine Learning Research, 2011, 12: 2825-2830.
6 Breiman L. Random ForestsJ. Machine Learning, 2001, 45(1): 5-32.
7 Apache ECharts 官方文档EB/OL. https://echarts.apache.org/
8 智谱 AI 开放平台文档EB/OL. https://open.bigmodel.cn/
9 郭继孚, 刘莹, 余柳. 城市轨道交通客流预测方法综述J. 交通运输系统工程与信息, 相关综述文献.
10 李航. 统计学习方法M. 北京: 清华大学出版社, 2019.
11 PyMySQL DocumentationEB/OL. https://pymysql.readthedocs.io/
12 Flask DocumentationEB/OL. https://flask.palletsprojects.com/
致谢
本科阶段的学习与本次毕业设计即将告一段落。感谢指导老师在选题、系统设计与论文撰写过程中给予的耐心指导与严格要求,使课题能够紧扣现有代码实现,避免空泛论述。感谢同学们在联调演示、界面核对与问题讨论中提供的帮助。感谢家人一直以来的理解与支持。通过本系统的开发,对 Flask Web 开发、MySQL 数据建模、ECharts 可视化与随机森林预测有了更完整的实践认识。文中若有不足之处,恳请各位老师批评指正。