Python打造水位监测与预警系统:从数据采集到可视化
最近有一条新闻值得关注罗马尼亚因多瑙河水位创下历史新低不得不暂停一座核电站的运行。虽然这条新闻看起来更偏向能源和环境领域但站在工业自动化、IoT 监控和数字化运维的角度它揭示了一个非常经典的技术课题——关键基础设施如何用实时监测系统提前发现水位异常并作出响应。核电站、火电厂、水电站、水利枢纽这些场景往往对冷却水水位、上游来水量、循环水系统运行状态有极高要求。水位过低会导致冷却系统无法正常工作过高则可能引发防汛风险。无论是工业现场还是智慧城市项目水位监测都是一项刚需能力。本文将围绕“水位监测与预警系统”展开从背景概念讲起带大家搭建一套完整的轻量级水位监测系统。我们会使用 Python 读取水位传感器数据或模拟数据完成阈值判断、数据存储、Web 可视化展示并实现告警提醒。整个过程不需要昂贵硬件普通电脑 Python 环境即可跑通适合新手学习也适合作为工业项目原型参考。读完本文你将掌握水位监测系统的整体架构与核心模块。使用 Python 采集与解析水位数据的常用方式。设计高水位、低水位两级阈值并实现告警逻辑。将历史水位数据存储到 SQLite并进行可视化展示。水位数据抖动、串口异常、告警风暴等常见问题的排查思路。1. 水位监测系统它到底是什么能解决什么问题1.1 为什么水位监测如此重要回到开头的新闻。核电站运行需要大量冷却水通常从河流、水库或冷却塔取水。当河道水位下降到取水口以下或者流量不足以支撑冷却循环时机组必须降低负荷甚至停机。这不仅是能源损失问题更关系到设备安全和运行合规。类似场景在工业领域非常普遍电厂循环水系统冷却塔水池水位过低会导致循环水泵气蚀损坏设备。水电站上游水位决定发电水头下游水位影响尾水排放。城市排水防涝泵站前池水位需要实时监测避免城市内涝。水厂取水原水水位过低时取水口可能吸入泥沙或空气。这些场景中水位数据就是“眼睛”。如果没有可靠的监测手段只能靠人工巡检效率低、滞后性高还容易出现误判。水位监测系统的价值就是把水位变化过程变成连续、可追踪、可分析的数据流让运维人员可以提前采取行动。1.2 水位监测系统包含哪些核心模块一个完整的水位监测系统通常由四个部分组成模块作用常见实现方式数据采集层读取水位传感器信号转换为数字量超声波液位计、压力式液位计、雷达液位计 PLC/串口/网关数据传输层将采集到的数据送到服务器或边缘节点RS485、Modbus RTU、MQTT、HTTP数据处理与存储层清洗数据、判断阈值、存储历史数据边缘网关、MySQL、SQLite、时序数据库展示与告警层查看实时水位、历史曲线触发告警Web Dashboard、大屏、短信/邮件/钉钉通知本文的实战项目会把这四层全部走通但用轻量级方案替代工业级硬件传感器数据用模拟数据或串口读取代替存储使用 SQLite展示层使用 Flask ECharts告警通过日志和 Python 内置告警函数实现。1.3 阈值预警低水位和高水位同样关键在电厂场景中低水位会导致水泵气蚀、冷却失效高水位可能导致溢流、设备淹没。因此预警系统不能只盯着一个方向。通常我们会设置两级阈值警戒低水位低于该值提醒巡检人员注意水位下降趋势。停机低水位低于该值系统必须触发严重告警甚至联动停机。警戒高水位高于该值需要检查排水系统是否正常。溢流高水位高于该值需要采取紧急泄洪或排水措施。阈值不是随意写的需要结合设备安装高度、取水口位置、传感器量程等因素。项目原型中我们先用可配置的阈值做演示后续可以替换为实际业务参数。2. 系统架构与方案选型2.1 整体架构设计为了让新手容易理解我们把系统拆成三个进程采集进程Collector负责周期性读取水位数据进行简单校验写入数据库。业务进程API Server提供 Web 接口用于查询实时水位和历史数据。前端页面Dashboard展示实时水位仪表盘和历史曲线附带告警提示。架构图用文字可以这样描述[水位传感器] - [串口/模拟数据源] - [Python 采集程序] | | 写入 SQLite v [水位数据库] ^ | 查询 [Flask Web API] ^ | [浏览器 Dashboard]采集进程独立运行的好处是即使 Web 服务重启数据采集也不会中断Web 服务只负责读库和展示降低耦合度。2.2 技术选型说明组件选择理由语言Python 3.9生态丰富串口、Web、数据处理都很方便数据采集pyserial可选 模拟数据没有硬件也能跑通流程数据存储SQLite单文件、零配置适合原型和小规模场景Web 框架Flask轻量、简单容易上手前端图表ECharts功能强大支持实时曲线和仪表盘任务调度schedule 或 threading轻量级定时采集即可不引入重量级框架版本方面需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果你使用的是 Python 3.12 或更新版本注意 pyserial 等库需要安装兼容版本。2.3 项目文件结构我们先规划一下项目结构避免代码写到后面混乱water-level-monitor/ ├── app.py # Flask Web 服务入口 ├── collector.py # 数据采集进程 ├── database.py # 数据库初始化与操作函数 ├── config.py # 全局配置阈值、串口、采集间隔 ├── alert.py # 告警逻辑 ├── requirements.txt # Python 依赖清单 ├── templates/ │ └── index.html # Dashboard 页面 └── data/ └── water_level.db # SQLite 数据库运行后生成下面的实战部分会按照这个结构逐个文件实现。3. 环境准备与依赖安装3.1 基础环境要求本文示例运行环境如下你可以根据实际情况调整操作系统Windows 10/11、Ubuntu 20.04 均可Python 版本3.9 及以上浏览器Chrome、Edge 等现代浏览器3.2 初始化虚拟环境为了不污染系统 Python 环境建议先创建虚拟环境mkdir water-level-monitor cd water-level-monitor python -m venv venvWindows 系统激活虚拟环境venv\Scripts\activateLinux/macOS 系统激活虚拟环境source venv/bin/activate激活后命令行前面会出现(venv)前缀。3.3 安装依赖创建requirements.txt文件写入以下依赖flask2.3.3 pyserial3.5 schedule1.2.1然后执行安装pip install -r requirements.txt如果你使用更新的 Python 版本pyserial和schedule都是比较稳定的库问题不大Flask 2.x 和 3.x 差异主要在内部接口本文用到的代码两个版本均兼容。3.4 可选连接真实传感器如果手头有超声波液位计或压力式液位计可以通过 RS485 转 USB 模块接入电脑利用pyserial读取数据。不同传感器的通讯协议不同本文会在代码中预留串口读取的接口。没有硬件的同学直接使用模拟数据生成器即可。4. 数据库设计与配置管理4.1 SQLite 表结构设计水位监测数据的特点是写多读少、按时间排序因此表设计不需要太复杂。我们需要两张表water_level_records存储水位历史数据。alert_logs存储告警触发记录。water_level_records表结构如下CREATE TABLE IF NOT EXISTS water_level_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, level REAL NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime(now, localtime)) );alert_logs表结构如下CREATE TABLE IF NOT EXISTS alert_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, level REAL NOT NULL, alert_type TEXT NOT NULL, message TEXT, created_at TEXT NOT NULL DEFAULT (datetime(now, localtime)) );其中alert_type用于区分是“低水位告警”还是“高水位告警”。4.2 编写数据库操作模块创建database.py封装数据库初始化和插入、查询操作# 文件路径database.py import sqlite3 import os DB_PATH os.path.join(os.path.dirname(__file__), data, water_level.db) def get_connection(): 获取数据库连接开启外键支持并设置行工厂 conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn def init_db(): 初始化数据库表 os.makedirs(os.path.dirname(DB_PATH), exist_okTrue) conn get_connection() cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS water_level_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, level REAL NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime(now, localtime)) ) ) cursor.execute( CREATE TABLE IF NOT EXISTS alert_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, level REAL NOT NULL, alert_type TEXT NOT NULL, message TEXT, created_at TEXT NOT NULL DEFAULT (datetime(now, localtime)) ) ) conn.commit() conn.close() def insert_water_level(level): 插入一条水位记录 conn get_connection() cursor conn.cursor() cursor.execute( INSERT INTO water_level_records (level) VALUES (?), (level,) ) conn.commit() conn.close() def insert_alert_log(level, alert_type, message): 插入一条告警日志 conn get_connection() cursor conn.cursor() cursor.execute( INSERT INTO alert_logs (level, alert_type, message) VALUES (?, ?, ?), (level, alert_type, message) ) conn.commit() conn.close() def query_recent_records(limit100): 查询最近 N 条水位记录 conn get_connection() cursor conn.cursor() cursor.execute( SELECT level, created_at FROM water_level_records ORDER BY id DESC LIMIT ?, (limit,) ) rows cursor.fetchall() conn.close() return [dict(row) for row in rows] def query_alert_logs(limit50): 查询最近 N 条告警记录 conn get_connection() cursor conn.cursor() cursor.execute( SELECT level, alert_type, message, created_at FROM alert_logs ORDER BY id DESC LIMIT ?, (limit,) ) rows cursor.fetchall() conn.close() return [dict(row) for row in rows]这里需要注意的是datetime(now, localtime)是 SQLite 内置函数表示使用本地时间。如果部署时容器或主机的时区不是中国标准时间需要确认系统时区无误否则时间显示会偏差数小时。5. 配置管理与告警逻辑5.1 全局配置模块创建config.py把阈值、采集间隔、串口参数等集中管理。生产环境中这些配置通常应该放在环境变量或配置中心里而不是硬编码但作为原型可以先统一放一个文件# 文件路径config.py import os # 水位阈值单位米根据实际场景调整 ALERT_LOW_LEVEL 2.0 ALERT_HIGH_LEVEL 8.0 DANGER_LOW_LEVEL 1.0 DANGER_HIGH_LEVEL 9.0 # 采集间隔秒 COLLECT_INTERVAL 5 # 模拟数据参数 SIMULATE_MODE True SIMULATE_BASE_LEVEL 5.0 SIMULATE_AMPLITUDE 1.5 SIMULATE_TREND_STEP 0.05 # 串口参数真实硬件时使用 SERIAL_PORT os.getenv(SERIAL_PORT, /dev/ttyUSB0) SERIAL_BAUDRATE int(os.getenv(SERIAL_BAUDRATE, 9600))5.2 告警逻辑模块告警逻辑的核心有两点判断当前水位属于哪个区间。避免重复告警防止“告警风暴”。如果每次采集到水位低于阈值就发一条告警采集间隔是 5 秒那么一分钟会触发 12 条告警一小时内就是 720 条运维人员根本看不过来。因此我们需要引入“持续确认 状态变更才告警”的机制。创建alert.py# 文件路径alert.py import logging from datetime import datetime import config import database # 配置日志输出 logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s ) logger logging.getLogger(__name__) # 当前告警状态防止重复触发 _current_alert_status None def check_alert(level): 根据当前水位值判断是否需要告警。 返回值为告警类型字符串如果没有告警则返回 None。 global _current_alert_status if level config.DANGER_LOW_LEVEL: alert_type DANGER_LOW message f水位严重过低: {level:.2f}m低于危险值 {config.DANGER_LOW_LEVEL}m elif level config.ALERT_LOW_LEVEL: alert_type ALERT_LOW message f水位偏低: {level:.2f}m低于警戒值 {config.ALERT_LOW_LEVEL}m elif level config.DANGER_HIGH_LEVEL: alert_type DANGER_HIGH message f水位严重过高: {level:.2f}m超过危险值 {config.DANGER_HIGH_LEVEL}m elif level config.ALERT_HIGH_LEVEL: alert_type ALERT_HIGH message f水位偏高: {level:.2f}m超过警戒值 {config.ALERT_HIGH_LEVEL}m else: # 正常范围 if _current_alert_status is not None: logger.info(水位已恢复正常解除告警。当前水位: %.2f m, level) _current_alert_status None return None # 状态未变化时只记录日志不重复写告警记录 if _current_alert_status alert_type: logger.debug(告警状态未变化: %s, alert_type) return alert_type # 状态变化时记录告警 logger.warning(message) database.insert_alert_log(level, alert_type, message) _current_alert_status alert_type return alert_type这种设计把告警从“每次触发”改成“状态跳变触发”能大幅降低重复告警。真实项目中还可以加入告警恢复、告警升级、通知去重等机制这里保留核心逻辑。6. 数据采集模块实现6.1 模拟数据源在没有传感器的环境下我们可以构造一个带有周期性波动和趋势变化的水位数据源。这样能够模拟水位正常波动、缓慢下降、缓慢上升等场景。创建collector.py# 文件路径collector.py import time import random import math import schedule import config import database from alert import check_alert def read_from_simulator(): 模拟读取水位数据。 使用正弦波 随机噪声模拟真实水位波动。 base config.SIMULATE_BASE_LEVEL amplitude config.SIMULATE_AMPLITUDE step config.SIMULATE_TREND_STEP # 使用时间戳生成缓慢变化的正弦波 phase time.time() / 30 wave math.sin(phase) * amplitude # 添加随机扰动模拟传感器噪声 noise random.uniform(-0.1, 0.1) level base wave noise return round(level, 2) def read_from_serial(): 从真实串口设备读取水位数据。 这里以 pyserial 为例具体协议需要根据传感器手册调整。 import serial try: ser serial.Serial( portconfig.SERIAL_PORT, baudrateconfig.SERIAL_BAUDRATE, timeout3 ) line ser.readline().decode(utf-8).strip() ser.close() # 假设设备返回的是纯数字例如 5.23 return float(line) except Exception as e: # 读取失败时返回 None由调用方处理 print(f串口读取失败: {e}) return None def collect_once(): 执行一次采集读取数据、判空、告警判断、写入数据库 if config.SIMULATE_MODE: level read_from_simulator() else: level read_from_serial() if level is None: print(采集失败跳过本次采样) return # 阈值判断 check_alert(level) # 写入数据库 database.insert_water_level(level) print(f[{time.strftime(%Y-%m-%d %H:%M:%S)}] 当前水位: {level:.2f} m) def main(): print(水位采集服务启动按 CtrlC 停止...) database.init_db() # 立即执行一次避免等待第一个调度周期 collect_once() # 每隔 COLLECT_INTERVAL 秒执行一次 schedule.every(config.COLLECT_INTERVAL).seconds.do(collect_once) while True: schedule.run_pending() time.sleep(1) if __name__ __main__: main()代码中有几个细节值得说明schedule库的定时任务适合轻量级场景采集间隔 5 秒时没有问题如果采集频率要求毫秒级或者需要高并发建议使用APScheduler或消息队列。read_from_serial()中的解析逻辑是示例不同传感器返回的格式差异很大有的带帧头有的用 CRC 校验需要参考具体协议。打印时间使用time.strftime和数据库中 SQLite 的时间格式保持一致。6.2 模拟数据的行为说明运行采集程序后输出效果类似[2025-06-08 10:23:01] 当前水位: 5.12 m [2025-06-08 10:23:06] 当前水位: 4.95 m [2025-06-08 10:23:11] 当前水位: 5.23 m由于加入了正弦波和随机噪声水位会在 3.5 米到 6.5 米之间波动。当前的阈值配置中ALERT_LOW_LEVEL 2.0所以正常情况下不会触发告警。如果你想让告警快速出现可以临时把阈值调高比如ALERT_HIGH_LEVEL 5.0观察水位超过阈值时程序的行为。7. Web 可视化与 API 服务7.1 Flask 服务入口采集程序负责写数据Web 服务负责读数据并展示。创建app.py# 文件路径app.py from flask import Flask, render_template, jsonify import database app Flask(__name__) app.route(/) def index(): 渲染 Dashboard 页面 return render_template(index.html) app.route(/api/water-level/recent) def api_recent(): 返回最近 100 条水位记录用于绘制图表 records database.query_recent_records(limit100) # 按时间正序返回方便前端绘图 records.reverse() data { times: [r[created_at] for r in records], levels: [r[level] for r in records] } return jsonify(data) app.route(/api/alerts/recent) def api_alerts(): 返回最近 50 条告警记录 logs database.query_alert_logs(limit50) return jsonify(logs) if __name__ __main__: database.init_db() app.run(host0.0.0.0, port5000, debugTrue)7.2 Dashboard 前端页面在templates目录下创建index.html使用 ECharts 绘制水位曲线。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title水位监测 Dashboard/title script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script style body { font-family: Microsoft YaHei, sans-serif; background: #f5f7fb; margin: 0; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } h1 { font-size: 24px; color: #333; } .status-bar { display: flex; gap: 20px; margin: 20px 0; } .status-card { flex: 1; background: #fafbfe; border-radius: 8px; padding: 16px; text-align: center; } .status-card .label { color: #888; font-size: 14px; } .status-card .value { font-size: 32px; font-weight: bold; color: #333; } #chart { width: 100%; height: 400px; } #alertList { margin-top: 20px; border-top: 1px solid #eee; padding-top: 16px; } .alert-item { padding: 8px; border-left: 4px solid #e6a23c; background: #fef9f0; margin-bottom: 8px; } /style /head body div classcontainer h1实时水位监测系统/h1 div classstatus-bar div classstatus-card div classlabel当前水位/div div classvalue idcurrentLevel--/div /div div classstatus-card div classlabel低水位警戒值/div div classvalue idlowThreshold--/div /div div classstatus-card div classlabel高水位警戒值/div div classvalue idhighThreshold--/div /div /div div idchart/div div idalertList h3最近告警/h3 /div /div script // 从后端获取阈值配置 const LOW_THRESHOLD 2.0; const HIGH_THRESHOLD 8.0; document.getElementById(lowThreshold).textContent LOW_THRESHOLD m; document.getElementById(highThreshold).textContent HIGH_THRESHOLD m; // 初始化图表 const chart echarts.init(document.getElementById(chart)); function loadData() { fetch(/api/water-level/recent) .then(res res.json()) .then(data { if (data.levels.length 0) { const latest data.levels[data.levels.length - 1]; document.getElementById(currentLevel).textContent latest.toFixed(2) m; } chart.setOption({ tooltip: { trigger: axis }, grid: { left: 3%, right: 4%, bottom: 3%, containLabel: true }, xAxis: { type: category, data: data.times, name: 时间 }, yAxis: { type: value, name: 水位(m), min: 0, max: 10 }, series: [{ name: 水位, type: line, data: data.levels, smooth: true, areaStyle: { opacity: 0.2 }, markLine: { data: [ { yAxis: LOW_THRESHOLD, name: 低水位警戒, lineStyle: { color: #e6a23c } }, { yAxis: HIGH_THRESHOLD, name: 高水位警戒, lineStyle: { color: #f56c6c } } ] } }] }); }); } function loadAlerts() { fetch(/api/alerts/recent) .then(res res.json()) .then(logs { const container document.getElementById(alertList); container.innerHTML h3最近告警/h3; if (logs.length 0) { container.innerHTML p暂无告警记录/p; return; } logs.forEach(log { const div document.createElement(div); div.className alert-item; div.innerHTML strong${log.created_at}/strong ${log.message}; container.appendChild(div); }); }); } // 每 3 秒刷新一次数据 loadData(); loadAlerts(); setInterval(loadData, 3000); setInterval(loadAlerts, 5000); /script /body /html这个页面中的阈值是写死在 JavaScript 里的实际项目中应该由后端接口动态下发。这样调整阈值时不用重新部署前端大家可以在进阶阶段自行改造。8. 运行与验证8.1 启动采集服务在一个终端窗口中运行python collector.py预期输出类似水位采集服务启动按 CtrlC 停止... [2025-06-08 10:30:01] 当前水位: 4.92 m [2025-06-08 10:30:06] 当前水位: 5.13 m [2025-06-08 10:30:11] 当前水位: 4.78 m8.2 启动 Web 服务打开另一个终端窗口运行python app.py浏览器访问http://127.0.0.1:5000应该能看到水位曲线页面每隔 3 秒自动刷新一次。8.3 验证告警功能为了让告警效果更快被看到可以临时修改config.py中的阈值例如将ALERT_HIGH_LEVEL改为5.0然后重启采集服务。当模拟水位超过 5.0 米时采集服务终端会打印告警日志。数据库alert_logs表中会多出一条记录。Web 页面“最近告警”区域会显示告警时间与内容。验证完毕后把阈值恢复原值防止影响后续实验。8.4 查看 SQLite 数据可以通过 Python 快速查看数据库中的数据import database database.init_db() records database.query_recent_records(limit5) for r in records: print(r)输出示例{level: 4.92, created_at: 2025-06-08 10:30:01} {level: 5.13, created_at: 2025-06-08 10:30:06} {level: 4.78, created_at: 2025-06-08 10:30:11}9. 常见问题与排查思路在实际运行中以下几个问题出现的概率很高。问题现象常见原因解决思路串口读取数据为空传感器波特率错误、串口被占用、USB 转串口驱动未安装检查设备管理器确认串口号关闭占用串口程序的进程使用串口调试工具验证传感器返回乱码波特率不匹配、数据位/校验位配置错误、协议解析不正确对照传感器手册确认通讯参数打印原始字节用 hex 查看结果采集一段时间后无数据串口断开、传感器故障、采集线程崩溃在collect_once()外层增加异常捕获使用try-except保证单次异常不影响后续采样告警记录刷屏每次采集都满足告警条件且没有去重机制引入状态变化判断只有状态跳变时才写入告警记录Web 页面图表不更新Flask 服务与采集服务没有使用同一个数据库路径检查两个进程是否读取同一个water_level.db文件确认相对路径问题时间显示与本地时间不一致SQLitedatetime(now)默认使用 UTC 时间使用datetime(now, localtime)或系统层设置时区10. 工程化与最佳实践前面几节完成了可运行的原型但在真实项目中还有很多需要补强的点。10.1 数据可靠性本地缓存当网络或数据库临时不可用时采集数据应写入本地缓冲文件恢复后回补。数据校验水位值超过量程范围的比如负数或大于 100 米应直接丢弃并记录异常而不是入库。水位突变滤波真实水位不会在 1 秒内跳变 2 米可以使用滑动平均或中值滤波消除传感器尖峰。10.2 告警与通知本文只实现了日志和页面告警生产环境应接入短信、邮件、钉钉、企业微信等通知渠道。告警最好支持“告警升级”低水位持续 5 分钟通知值班人员持续 30 分钟通知主管持续 1 小时启动应急预案。每次发布阈值变更前应走配置变更审批流程避免误配置导致漏报。10.3 安全与权限Web 服务当前没有鉴权任何人访问都能看到数据。真实环境需要加入登录认证。API 接口应增加访问频率限制防止被外部频繁调用。数据库文件要有备份策略尤其是历史数据需要长期留存时。10.4 部署与运维采集服务和 Web 服务建议使用进程守护工具管理例如 systemd、supervisor保证进程退出后自动重启。日志建议按天滚动保存方便后期排查历史问题。水位数据和告警记录定期归档避免数据库无限膨胀。11. 总结与下一步学习方向本文从一个真实的工业事件出发介绍了水位监测系统的概念、架构和实现方式并完成了一个包含数据采集、阈值判断、SQLite 存储、Web 可视化的可运行原型。这套代码稍作改造就可以用于电厂循环水、泵站前池、水厂取水、防汛预警等场景的快速验证。下一步如果你希望继续深入可以围绕这几个方向将模拟数据源替换为真实传感器学习 Modbus RTU 协议与串口解析。研究水位趋势预测用历史数据训练简单的时间序列模型提前预判水位是否接近阈值。把 SQLite 升级为时序数据库例如 InfluxDB应对高频采集和大数据量场景。增加告警通知渠道把告警信息推送到钉钉或企业微信机器人。水位监测只是工业物联网的一个缩影背后涉及的硬件接入、数据采集、实时告警、可视化分析都是值得系统学习的方向。你可以先把本文的代码跑起来再根据实际业务需求逐步扩展。如果遇到什么问题欢迎在评论区留言交流。