ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Django与MySQL的污染源可视分析系统实战解析

基于Django与MySQL的污染源可视分析系统实战解析 1. 项目缘起为什么需要一个污染源可视分析系统去年在做一个环保方向的数字化项目时客户手里有一批污染源数据——包括固定排放源的基础台账、自动监测设备的历史分钟级数据、以及网格化监测站点的气象与浓度记录。数据散落在几张Excel和一套旧系统里领导要的却是一张图看懂区域污染从哪来、往哪去、浓度怎么变。这个需求最终落地成了一个基于 Python 的 Django 框架做后端、MySQL 做数据存储、前端使用 ECharts 和 Leaflet 做可视化的分析系统。开发周期三周部署后维护成本很低这篇文章就把整个项目的拆解思路、技术选型逻辑、建模过程和实际踩坑记录完整写出来给正在做类似数据汇聚展示项目的开发者和产品一个可复用的参考框架。适用的人群主要有三类一是正在做环境监测、能耗监管、园区安全等数据可视化项目的开发者二是刚接触 Django MySQL 全栈开发想找一个完整业务场景练手的同学三是需要向客户或领导说明系统为什么这么设计的产品负责人和项目经理。无论你是后端主导还是前端主导这个项目文中的每一段都会尽量落到具体代码和配置上不是为了讲概念而是为了让你能直接拿去用。1.1 核心需求拆解在写任何代码之前我习惯先把需求画成一张可以落地的功能地图。污染源可视分析系统表面上是一个看数据的页面但拆开来背后至少包含五个相互关联的模块模块功能描述优先级污染源管理维护排放源基础信息包括名称、类型、坐标、行业分类高监测数据管理导入和存储监测站点上报的污染物浓度时间序列高地图可视化展示污染源空间分布按浓度进行分级着色高趋势分析对指定污染物和时间范围做趋势分析支持周/月/季聚合中报表导出将统计结果导出为 Excel 或 CSV便于汇报低这五个模块之间的数据流是数据采集程序定时把数据写入 MySQLDjango 的 ORM 层把数据读出来后端通过 JSON 接口把聚合结果交给前端前端再渲染成地图和图表。整个过程听起来路径很长但每一层都承担了明确的职责出了问题也容易定位数据不对先查导入脚本接口不对先查视图函数展示不对直接看浏览器控制台。在数据层面我给它设计了一条真实性优先的规则入库之前的清洗比入库之后的修正成本低得多。后面我会具体讲清洗脚本的处理逻辑这里先记住这句话——可视分析系统的质量上限取决于原始数据的质量下限。1.2 一个系统要装下三种用户视角设计时我特意区分了三种使用视角因为不同角色对可视分析的理解完全不同。管理人员关心的是整体态势。他们早上打开系统第一眼要知道今天哪个片区浓度超标、本周排放源运行情况怎样、和上周相比是变好还是变差。所以系统的默认首页必须是一张全域总览图不能是一堆空表格。业务分析人员要的是钻取能力。从总览图点进某个站点能看到该站点各项污染物指数的逐小时曲线、同比环比甚至能筛选特定排放源类型做交叉对比。运维人员则更关心数据维护上传数据、调整站点坐标、修改排放因子这些操作最好在后台直接完成不依赖程序员写 SQL。技术上的结论也变得清晰用 Django 自带的 admin 后台做数据维护用独立的前端页面做分析和展示用 JSON API 把两者串起来。这个架构足够简单也足够稳定不会一开始就把系统做得过于笨重。2. 为什么是 Django MySQL Python而不是另起炉灶技术选型阶段我其实纠结了很长时间。当时可选的方案很多后端可以换 Flask 或 FastAPI数据库可以换 PostgreSQL前端可视化也可以换 Pyecharts 或 Plotly。最终坚持这套组合原因有四个值得展开说说。2.1 Django 的 ORM 和 Admin 太适合内部数据系统污染源数据本质上是结构化业务数据每天的操作大量集中在增删改查上。Django 的 ORM 让我不需要为简单查询写一堆重复的 SQLQuerySet 链式查询写起来非常自然。同时 Django 自带了一套功能完整的 Admin 后台数据录入、核对、修改的页面开箱即用业务人员培训一下就能操作省了我单独开发后台管理界面的大量精力。有一个细节很多人会忽略Django 自带的 ORM 对查询-删除对象这类常见数据管理操作提供了非常直观的封装。比如清理某个站点三个月前的无效记录只需要MonitorRecord.objects.filter(station_id1001, time__ltdatetime_now).delete()后台管理界面里的删除操作还默认带有事务保护不会因为误操作就把关联数据一把清空。这一点在业务系统里非常重要。2.2 MySQL 的稳定性和生态通用性环保系统的政企客户环境里MySQL 往往是最容易申请到的数据库资源也最容易找到有经验的运维人员。虽然 PostgreSQL 在某些空间查询上更强但本项目只需要存储经纬度并做边界检索MySQL 5.7 之后对基础地理位置函数的支持已经足够。Django 在不同数据库之间切换也比较容易如果以后客户要求换成 PostgreSQL大部分模型代码不用改动。MySQL 5.7 和 8.0 的安装配置是热搜里经常出现的问题我在后面环境部署部分会专门讲。这里先说结论只要把字符集统一为 utf8mb4、时区设置清楚、连接账号权限给对基本上不会遇到太离奇的坑。2.3 Python 生态帮助我打通了数据链路第三个原因来自 Python 生态。数据处理阶段我用 pandas 做 Excel 清洗和格式转换Django 后端负责业务逻辑和 API前端图表虽然是用 JavaScript 的 ECharts 画的但数据组装逻辑控制在 Python 侧。整套链路如果有异常可以集中在 Python 侧排查比多语言混编的链路要省心得多。2.4 框架选型的对比清单我当时的评估结果是这样的对比项DjangoFlaskFastAPI自带 Admin 后台有开箱即用无需自己写无需自己写ORM 成熟度高QuerySet 链式查询灵活低常配 SQLAlchemy低常配 SQLAlchemy适合业务系统非常合适适合轻量 API适合高并发 API学习曲线稍陡但规则清晰平缓平缓本项目的契合度高中中如果你的项目需求是以数据管理界面 展示看板为主而不是纯高并发 APIDjango 在这三者里其实是综合成本最低的选择。它把很多系统开发的公共问题——认证、后台、迁移、表单校验——都提前解决了你需要专注的只有业务本身。2.5 别忘了数据库排序和锁的基础问题热搜词里常能看到django mysql排序mysql锁的分类这类问题实际项目中也是绕不开的。排序方面别在 Python 层干这件事把排序下沉到数据库层性能差异很大。比如查某个站点污染物浓度从高到低排序直接用order_by(-value)配合索引能很快返回。而数据库锁的问题则集中体现在数据导入环节。我的系统里有一段定时任务会从采集服务器同步监测数据。同一时刻可能有两个 worker 进程在写同一批站点记录如果不用数据库锁数据很容易出现重复或互相覆盖。这个场景需要用select_for_update()配合事务先锁住相关记录行再执行检查或更新写完之后提交事务释放锁。Django 里写法是from django.db import transaction with transaction.atomic(): station Station.objects.select_for_update().get(pkstation_id) records parse_new_records(station) MonitorRecord.objects.bulk_create(records, ignore_conflictsTrue)这段代码避免了两条并发进程同时修改同一批记录的问题。MySQL 默认的 InnoDB 引擎在行锁之外还有间隙锁、意向锁等机制虽然 Django 封装了一部分但当你需要做批量导入时理解锁是保护数据一致性的必要条件依然很重要。3. 数据库建模与 ORM 设计从 Excel 到可分析这一部分是整个系统最需要细抠的地方。污染源数据的核心实体无非三类污染源、监测站点、监测记录。但如果表格设计得不好后面的查询和可视化都会非常痛苦。我在这个项目里把数据模型分成了四张核心业务表下面逐一讲清楚。3.1 数据模型的设计思路models.py 里主要包含这四个模型PollutionSource污染源、MonitorStation监测站点、Pollutant污染物字典、MonitorRecord监测记录。from django.db import models class PollutionSource(models.Model): INDUSTRY_CHOICES [ (power, 电力), (steel, 钢铁), (cement, 水泥), (chemical, 化工), (other, 其他), ] name models.CharField(max_length128, verbose_name排放源名称) industry models.CharField(max_length32, choicesINDUSTRY_CHOICES, verbose_name所属行业) longitude models.FloatField(verbose_name经度) latitude models.FloatField(verbose_name纬度) emission_factor models.FloatField(nullTrue, blankTrue, verbose_name排放系数) created_at models.DateTimeField(auto_now_addTrue) class Meta: ordering [-created_at] class MonitorStation(models.Model): name models.CharField(max_length128, verbose_name站点名称) station_code models.CharField(max_length32, uniqueTrue, verbose_name站点编号) longitude models.FloatField(verbose_name经度) latitude models.FloatField(verbose_name纬度) region models.CharField(max_length64, verbose_name所属区域) class Pollutant(models.Model): code models.CharField(max_length16, uniqueTrue, verbose_name污染物编码) name models.CharField(max_length64, verbose_name污染物名称) unit models.CharField(max_length16, verbose_name计量单位) default_limit models.FloatField(nullTrue, blankTrue, verbose_name默认限值) class MonitorRecord(models.Model): station models.ForeignKey(MonitorStation, on_deletemodels.CASCADE, related_namerecords) pollutant models.ForeignKey(Pollutant, on_deletemodels.CASCADE, related_namerecords) time models.DateTimeField(db_indexTrue, verbose_name监测时间) value models.FloatField(verbose_name监测值) class Meta: constraints [ models.UniqueConstraint(fields[station, pollutant, time], nameuniq_station_pollutant_time) ]这个设计里有几个细节值得单独拎出来说。污染物单独建表而不是直接存一个PM2.5字符串。因为不同污染物的单位、限值、国家执行标准都不一样单独建表便于后续扩展也方便做动态筛选下拉框。监测记录加了唯一约束(station, pollutant, time)这样同一站点同一时刻同一污染物不会重复入库导入脚本天然具备幂等性——重复执行不会产生脏数据。time字段加了db_indexTrue因为绝大部分查询都围绕时间范围进行没有索引的时候数据量一旦超过几十万行查询速度会肉眼可见地变慢。3.2 用 Django 迁移别手工建表很多新手喜欢先在 MySQL 里CREATE TABLE再反向生成 Django 模型。我个人的习惯是反过来先用 Django 定义模型然后通过makemigrations和migrate生成表结构。项目的模型代码本身就成了数据库结构文档迁移历史也可以随时回滚比手工导 SQL 更贴近团队协作的习惯。实际操作命令很简单python manage.py makemigrations monitor python manage.py migrate每次执行完迁移我都建议顺手打开数据库客户端看一眼表结构确认 Django 生成的字段类型、索引、约束都符合预期。有些 Django 版本差异会导致索引命名不同及时发现比上线后再处理省事得多。3.3 数据入库的清洗与校验原始数据通常是 Excel 或者 CSV字段命名五花八门时间格式不统一偶尔还有空值和异常大数。导入之前必须做清洗。我用 pandas 做中间处理核心逻辑分三步第一步统一时间格式。比如把2024/6/1 8:00转成2024-06-01 08:00:00这一步能避免大量后续查询错误。第二步把字符型数值转成 float转换失败的行单独记录下来不直接中断整个导入任务。第三步对明显的异常值设置阈值比如 PM2.5 大于 1000 直接判为无效数据不进入库。此外对于同一时刻重复的监测值保留最后一条即可。先清洗、后入库对可视化的准确性影响极大。很多时候看到折线图里一根异常高的尖峰并不是真实污染而是原始数据里小数点错位或者漏填导致的。如果不清洗前端所有图表都会失真分析结论也站不住脚。4. 从后端接口到前端图表可视分析的核心链路数据模型确定之后系统的主要工作量就落在查询 → 聚合 → 渲染这条链路上。这条链路听起来简单但它决定了系统到底是一个数据表格系统还是一个真正有分析能力的可视化系统。4.1 后端接口的设计与实现为了让前端开发尽量简单后端直接返回已经聚合好的 JSON。比如某个站点某个污染物最近24小时的逐时均值视图函数大致是from django.db.models.functions import TruncHour from django.db.models import Avg from django.http import JsonResponse from django.utils import timezone from datetime import timedelta def station_hourly_avg(request, station_id, pollutant_code): end timezone.now() start end - timedelta(hours24) qs (MonitorRecord.objects .filter(station_idstation_id, pollutant__codepollutant_code, time__range(start, end)) .annotate(hourTruncHour(time)) .values(hour) .annotate(avg_valueAvg(value)) .order_by(hour)) data [ {time: item[hour].strftime(%Y-%m-%d %H:00:00), avg_value: round(item[avg_value], 2)} for item in qs ] return JsonResponse({data: data})这里用 Django 原生的TruncHour函数做按小时分组可移植性比 MySQL 的DATE_FORMAT好换成 PostgreSQL 也不需要改代码。前端拿到这个 JSON 之后直接塞进 ECharts 的 series 就能画图。更有意思的是分区域、分源类型的污染对比这种多维分析。此时会用到values()加annotate()的组合把数据按多个维度分组。比如统计某个时间段内不同行业排放源的平均浓度qs (MonitorRecord.objects .filter(time__range(start, end)) .values(station__pollution_source__industry) .annotate(avg_valueAvg(value)) .order_by(-avg_value))这种写法把原来需要写多条 SQL 的分析逻辑收拢在 ORM 里代码可读性高也方便测试。前端下拉框选中行业对比时后端换一个参数返回的 JSON 结构不变图表组件可以完全复用。4.2 前端可视化地图和趋势图的实现可视化的核心是地图总览和趋势分析两类页面。地图部分我用了 Leaflet 加在线底图把污染源或者站点渲染成 Marker并根据污染物浓度动态改变颜色代码思路大致是这样的fetch(/api/station-concentration/?pollutantPM2.5time selectedTime) .then(res res.json()) .then(data { data.stations.forEach(function (item) { var color item.level high ? #d73027 : item.level mid ? #fdae61 : #66bd63; L.circleMarker([item.lat, item.lng], { radius: 10, color: color, fillOpacity: 0.7 }).addTo(stationLayer); }); });趋势图表部分用的是 ECharts它支持按时间轴缩放、legend 切换、多数据列对比很适合污染物趋势这类场景。我给数据拼装做成了一个统一的buildTrendOption(labels, seriesGroup)函数不同的污染物选择只切换 series 数据图表配置大体通用。ECharts 折线图有一个值得注意的细节时间轴的type要用time而不是category。用category时ECharts 会默认把数据点按等间隔排列当原始数据缺失某些小时时横轴会出现数据变形的错觉。改成type: time之后缺失时间段会按真实时间间距展示图形更接近事实也更容易让业务人员看懂。4.3 可视分析要回答业务问题而不只是画图做这个系统给我的最大教训是可视分析不是把数据画成图关键是把分析逻辑做成产品功能。系统默认打开时不应该只是一张普通的散点地图而应该自动把当前时段超标点位和高排放源突出出来。要让使用者一眼看到问题而不是给他一张需要自己慢慢琢磨的图。我后来在系统里加了一个简单的超标预警接口查询当日各站点污染物浓度超过标准限值的自动标记为红色并按超标倍数排序。管理者打开系统第一眼就能确认今天哪几个点位有事而不是先去翻统计数据。这种功能虽然技术难度不高但对实际使用价值的提升非常明显。报表导出模块也从这个逻辑延伸出来既然查询结果已经聚合好导出只是把同一份 JSON 转成 Excel调用方直接用openpyxl写文件流返回。这个模块开发成本很低但对客户日常汇报非常重要属于小功能、大价值的典型例子。5. 环境搭建、部署和常见问题排查很多同学卡在项目第一步环境装不起来。尤其是 Windows 上装 MySQL、装 Python 扩展的过程坑特别多。我把这个项目从零搭建过程中的关键步骤和踩坑记录整理出来如果你准备复现这个系统可以直接按顺序操作。5.1 本地环境准备Windows / LinuxPython 环境我强烈建议用虚拟环境。Python 3.10 之后直接用python -m venv .venv创建即可激活之后再安装依赖。安装 Django 和 MySQL 驱动pip install django pip install mysqlclient在 Windows 上mysqlclient经常安装失败原因是需要本机的 MySQL C 库头文件。几个可行的替代办法使用pip install pymysql然后在项目的__init__.py中执行pymysql.install_as_MySQLdb()这是最省事的方式如果坚持用 mysqlclient需要先安装 Visual C Build Tools 和 MySQL 开发库过程比较折腾Docker 环境下直接选择带 mysqlclient 的 Python 基础镜像会省去很多编译麻烦。MySQL 的安装配置也是一个高频热搜话题。Windows 上直接下载安装包并勾选Developer Default即可装完记得端口默认 3306、字符集选择utf8mb4。如果想用 Docker 方式启动docker run -d --name mysql8 -e MYSQL_ROOT_PASSWORDyourpassword -p 3306:3306 mysql:8.0“Docker 安装 MySQL 失败”这个坑我在项目初期也踩过。容器启动后连不上报Access denied for user排查顺序一般是确认容器内SHOW VARIABLES LIKE port、确认MYSQL_ROOT_PASSWORD环境变量是否生效、确认连接命令的host不要写成localhost导致走了 socket 而不是 TCP。很多人报错的原因就是官方镜像首次启动时初始化比较慢连接太早导致密码还没设置完成多等几十秒再连就正常了。5.2 Django 项目初始化和 app 创建创建项目和应用是 Django 开发的基本操作django-admin startproject air_quality_system cd air_quality_system python manage.py startapp monitor然后把monitor注册到settings.py的INSTALLED_APPS再在DATABASES里配置 MySQL 连接DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: air_quality, USER: root, PASSWORD: yourpassword, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }编码问题请务必重视。utf8mb4是很多中文项目出现乱码或存储失败的根源。MySQL 默认字符集如果是utf8mb3旧版叫 utf8一些特殊字符和扩展表情会存不进去大型中文环境统一用utf8mb4是稳妥的选择。5.3 常见报错与排查链路用 Django MySQL 做项目下面这些报错几乎躲不掉。我把报错、原因和解决办法整理成一张表报错信息原因解决办法OperationalError: (2003, Cant connect to MySQL server)MySQL 服务没启动或端口被占用检查服务状态和端口监听ProgrammingError: (1146, Table xxx doesnt exist)忘记执行 migrate执行python manage.py migrateYou have an error in your SQL syntax手写 SQL 用了 MySQL 关键字没加反引号尽量用 ORM必须手写时注意转义BrokenPipeError或Connection reset by peer数据库连接池被断开调大 MySQLwait_timeoutDjango 配置合理的CONN_MAX_AGE排查系统性报错我总结的固定姿势是先看 Django 日志再看 MySQL 慢查询日志最后才怀疑代码逻辑。很多时候系统很慢并不是 Python 的问题而是某条查询没有命中索引或者 ORM 生成了巨大的IN子句。打开django.db.backends的日志能看到每一条实际执行的 SQL这是定位性能问题最直接的手段# settings.py 临时启用 SQL 日志 LOGGING { version: 1, handlers: {console: {class: logging.StreamHandler}}, loggers: {django.db.backends: {handlers: [console], level: DEBUG}}, }5.4 数据量变大之后的优化思路当监测记录表增长到百万行以上时单靠索引不一定够用。我会优先做两件事第一按时间进行分区或者定期把历史数据归档到单独表让热数据查询始终在一个可控范围内。MySQL 8.0 支持 RANGE 分区可以把一张大表按月份拆成多个物理分区查询时只扫描目标分区性能提升非常明显。第二把频繁使用的聚合查询做成统计结果表。比如每天凌晨定时任务跑一遍站点-污染物-日均值的汇总存入单独一张daily_summary表前端查询日均值趋势时直接读统计表。这种设计在数据量大之后非常实用避免每次点击页面都对原始明细表做全量AVG聚合。这些优化不一定在项目第一版就做但数据量上来之后越早设计越好。我在第一版就预留了daily_summary的模型结构后续只是写一个定时脚本的事不用动前端。6. 源码组织与文档交付让项目可持续维护环境和技术架构都确认之后整个项目交付给客户或同事时源码和文档的组织同样重要。一个能跑的项目如果交到别人手里跑不起来价值会大打折扣。这里详细说一下我整理源码和文档的习惯。6.1 项目目录结构我建议这样组织 Django 项目air_quality_system/ ├── manage.py ├── requirements.txt ├── .env.example ├── README.md ├── config/ │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── monitor/ │ ├── __init__.py │ ├── models.py │ ├── views.py │ ├── admin.py │ ├── urls.py │ ├── migrations/ │ └── api.py ├── static/ │ ├── css/ │ ├── js/ │ └── maps/ ├── templates/ │ ├── base.html │ ├── map_view.html │ └── trend_view.html └── scripts/ ├── import_excel.py └── sync_task.pyrequirements.txt最好固定版本号不要用django4.0这样的写法。过几个月重新部署时第三方库升级可能导致不可预知的行为变化。固定的方式非常简单pip freeze requirements.txt6.2 README 该怎么写README 不是流水账而是整个项目的门面。我通常会包含这几部分项目简介功能和适用场景、技术栈Django 版本、MySQL 版本、Python 版本、快速启动步骤从创建虚拟环境到最终运行、数据初始化说明如何导入样例数据、常见问题环境问题和端口问题。写快速启动步骤时一定要亲自走一遍不要凭记忆写。我早年在一个项目里给过一个看似正确的步骤执行 migrate 之后直接 runserver但实际遗漏了数据样例导入新环境起来之后页面上什么都没有。后来我在 README 里加了一步提供一个最小的sample_data.csv和一个导入命令保证任何新人拿到项目之后都能立刻看到可视化效果。6.3 接口文档和数据说明的沉淀如果你的系统有对外 API建议在文档里单独列一个 API 表格写清楚每个接口的路径、请求参数、返回结构。这样前端同事不需要每次翻代码也能快速对接。数据说明部分也很重要。我在文档里专门维护了一张字段字典表标明每个字段的含义、单位、来源、精度。这个习惯在数据系统里价值极大能避免这个字段到底存的是什么的反复沟通。另外在scripts/目录里放几个独立可运行的脚本比如数据导入脚本和数据同步脚本定时任务、命令行手动补数、测试环境造数据都可以直接复用。脚本入口统一收在main()函数里方便在命令行里调用。7. 实际运行中的踩坑记录与优化方向项目上线三个月后回看这段开发经历有几个比较典型的踩坑记录值得单独写出来。第一个坑是时区不一致。MySQL 默认时区可能是 UTC而 Django 的USE_TZ True又会让 ORM 在读写时自动做时区转换。如果数据库连接没显式设置时区查询结果会出现 8 小时偏差。解决办法是在 MySQL 连接参数里加上init_command: SET time_zone 08:00或者统一把 Django 的TIME_ZONE设置为业务所在地时区保证所有时间字段展示一致。第二个坑是导入脚本没有做事务控制。第一版脚本处理大批量数据时如果中间某一行报错前面已经写入的数据就留下了半截记录。后来把整个导入流程包进transaction.atomic()出错时全部回滚才彻底解决数据完整性问题。第三个坑是图表组件滥用真实数据量。可视分析不等于把百万行数据全部渲染到前端。地图 Marker 数量超过几百个时页面就会开始卡顿这时候应该做聚类或者按网格聚合而不是折磨浏览器。趋势图超过上千个点也应该做降采样只展示时间间隔内的均值或最大值。从后续扩展方向看我给自己列了几个继续往下走的计划接入空气质量预报模型把未来 24 小时的预测趋势画到图表里加入浓度时序的异常检测用统计方法识别突然升高的点位并推送告警把可视化页面做成大屏订阅模式配合单位的大屏投放需求把数据导入流程改成实时接口对接不再依赖人工上传 Excel。从技术架构角度看当前这套 Django MySQL 的方案并没有给扩展设置障碍。Django 的 app 结构可以继续拆分数据量大了可以接入 Redis 做缓存地图渲染可以换更精细的矢量瓦片服务前端图表也可以逐步替换成更轻量的渲染引擎。这些都是后续的事但设计之初不要把路走窄后面的每一步都会轻松很多。做这套系统给我最大的感受是技术栈永远只是工具真正难的是把零散、脏乱、多源的数据变成决策者一眼能看懂的结论。Django 帮我把数据底座搭得稳MySQL 帮我把数据存得可靠Python 生态帮我把数据清洗和分析的链路串了起来。如果你也在做类似的污染源分析、环保可视化或者任何数据量不大但场景复杂的信息化项目希望这篇实战记录能让你少走几步弯路。
返回列表