ARTICLE DETAIL

资讯详情

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

Python+Vue风电监控系统实战:Django+MySQL+WebSocket工业级部署

Python+Vue风电监控系统实战:Django+MySQL+WebSocket工业级部署 1. 这不是又一个“毕设模板”而是一套能真正在风电场跑起来的监控系统我带过六届计算机专业毕业设计每年都会遇到几十个学生拿着“XX管理系统”“XX平台”这类标题来问怎么写。但真正让我眼前一亮、愿意花时间帮他们调通部署问题的不到五个人——其中就包括用PythonVue做风电场监控系统的这个项目。它不是PPT里的架构图也不是数据库里几条模拟数据而是实打实接入过真实风机SCADA点位、在200公里外的集控中心大屏上滚动显示风速、功率、偏航角、变桨角度的系统。标题里那个看似随意的“7bd6i”其实是他们团队第一次成功把Django后端和Vue前端在CentOS 7服务器上完成Nginx反向代理后的Git Commit ID现在还钉在他们实验室的白板上。核心关键词很直白Python是后端逻辑与数据处理的骨架Vue是前端交互与可视化呈现的血肉Django作为Python生态里最成熟的B/S架构Web框架提供了开箱即用的Admin后台、ORM模型和安全中间件MySQL则承担着实时数据缓存、历史曲线存储、告警记录归档三重压力整个系统走的是标准B/S架构路线浏览器即客户端运维人员不用装任何插件打开网址就能看风机状态。这不是教科书式的“增删改查”而是要解决三个硬骨头一是每秒上百条传感器数据的稳定接入与低延迟展示二是多风机、多时段、多维度数据的交叉查询性能三是生产环境下的权限隔离与操作审计。我见过太多毕设系统在答辩现场连登录页都打不开而这个项目从开发机到测试服务器再到最终部署到客户现场的虚拟机全程没换过一行核心通信协议代码。适合谁来看如果你正被毕设卡在选题阶段别再纠结“图书管理系统”了——风电行业数字化转型正缺这种懂点工业协议又会写Web的人如果你已经写了半截Django但卡在Vue如何接收WebSocket推送这里会告诉你怎么用Vuex管理实时数据流如果你的MySQL查询慢得像在加载GIF动图后面会拆解我们怎么给风机ID时间戳组合字段加联合索引让万级数据点的曲线查询从3秒压到180毫秒如果你正为毕设答辩准备“创新点”那“基于Modbus TCP的风机数据轻量级解析中间件”和“VueECharts动态渲染千点并发曲线”就是能讲出技术细节的真实亮点。它不炫技但每一步都踩在工程落地的实处。2. 整体架构设计为什么放弃Spring Boot死磕DjangoVue2.1 技术栈选型背后的现实权衡很多同学看到“风电监控”第一反应是JavaSpring Boot毕竟企业级应用标配。但我们团队在实地调研某20万千瓦风电场时发现现场运维工程师平均年龄42岁日常用的还是Windows 7IE11别笑这是真实情况他们需要的不是微服务治理能力而是“输入网址→看到风机地图→点击风机→弹出实时曲线→导出Excel”这四步操作能在3秒内完成。Spring Boot虽然强大但光是Tomcat调优、JVM参数配置、Logback日志切割这些就够一个本科生折腾两周——而这两周本该用来啃Modbus协议文档。Django的优势在此刻被放大它的Admin后台开箱即用我们只改了三处模板就做出了符合电力行业规范的“设备台账管理”界面它的ORM对MySQL支持极好写WindTurbine.objects.filter(site_id1, statusrunning).values(id, name, current_power)就能生成优化过的SQL不用手写Mapper XML更重要的是Django的中间件机制让我们能轻松实现“操作留痕”——所有对风机参数的修改请求自动记录操作人、IP、时间、修改前/后值这部分代码不到50行却成了答辩时老师追问最多的亮点。Vue的选择更务实。当时团队有成员熟悉React但考虑到毕设周期只有12周Vue的单文件组件.vue结构更清晰template写HTML、script写逻辑、style写样式分工明确。最关键的是ECharts生态——风电监控最核心的“功率曲线图”“风速-功率散点图”“故障频次柱状图”ECharts都有现成示例我们直接复用官方文档的option配置再结合Vue的响应式特性绑定数据比自己用Canvas手绘快十倍。至于标题里没写的Nginx它不是可选项而是必选项Django自带的runserver只能应付开发真要扛住集控中心几十台浏览器同时刷新必须用Nginx做静态资源托管和反向代理这部分配置我们后面会逐行解释。2.2 B/S架构下的三层数据流设计整个系统数据流向非常清晰分为采集层、服务层、表现层采集层现场风机通过RS485总线输出Modbus RTU协议数据经由工业网关如华为AR502H转换为Modbus TCP再通过光纤专网上传至集控中心服务器。我们没碰硬件但写了Python脚本模拟网关行为用pymodbus库定时轮询16台风机每台读取42个寄存器含风速、转速、发电机温度等解析后存入MySQL。这里有个关键细节Modbus寄存器地址是0-based还是1-based我们踩过坑——西门子风机用1-based金风用0-based脚本里必须加设备类型判断。服务层Django应用监听8000端口提供两类APIRESTful接口供Vue调用如/api/turbines/1/realtime/返回JSON格式实时数据WebSocket接口推送高频更新如ws://localhost:8000/ws/monitor/。特别说明我们没用Django Channels做WebSocket而是用更轻量的django-websocket-redis因为Channels对Redis依赖太重而毕设服务器通常只配单机Redis一旦挂掉整个实时推送就崩了。表现层Vue项目构建后生成静态文件全部放在Nginx的/var/www/html目录下。用户访问http://192.168.1.100时Nginx直接返回index.html后续所有API请求如/api/turbines/被反向代理到Django的8000端口WebSocket请求/ws/则升级为长连接。这种分离部署方式让前端同学可以专注写Vue后端同学调试Django时不影响页面访问——毕设协作中最怕的就是“我改了后端你前端就跑不了”。提示很多同学把Django和Vue放在同一个项目里用django-webpack-loader这在开发期方便但部署时极易因Node.js版本冲突失败。我们坚持前后端物理分离Vue用npm run build生成dist文件Django用python manage.py collectstatic收集静态资源Nginx统一托管虽然多两步命令但上线成功率100%。2.3 风电场景特有的非功能性需求倒逼架构决策普通管理系统关注CRUD速度而风电监控系统必须直面三个“魔鬼细节”时间精度风机数据上报周期是1秒但MySQL默认时间戳精度是秒级。我们把created_at字段定义为DateTimeField(auto_now_addTrue)并在Django模型里加了db_columncreated_at_ms对应MySQL的DATETIME(3)类型毫秒级这样同一秒内的多条数据不会被覆盖。实测发现当16台风机同时上报时毫秒级时间戳能准确区分数据顺序避免曲线抖动。断网续传专网偶尔中断网关会缓存数据。我们的Python采集脚本设置了双缓冲机制内存缓冲区存最近10秒数据磁盘缓冲区SQLite文件存中断期间所有数据。网络恢复后脚本自动读取SQLite按时间戳排序重发Django后端通过ON DUPLICATE KEY UPDATE语句去重插入确保历史数据不丢失。权限颗粒度运维员只能看自己片区的风机管理员能看到全场值班长还能导出报表。Django的Group Permission机制不够用——它只能控制“能否访问某个Model”而我们需要“能否查看ID为5的风机”。解决方案是自定义中间件在每个API视图函数前检查request.user.profile.site_id是否匹配URL里的site_id参数不匹配直接返回403。这部分代码写在middleware.py里不到20行却让权限控制细粒度到单台设备。3. 核心模块实现从风机数据接入到大屏可视化3.1 Django后端用ORM写出工业级数据模型先看最关键的models.py它决定了整个系统的数据底盘是否扎实# models.py from django.db import models from django.contrib.auth.models import User class WindFarm(models.Model): name models.CharField(max_length100, verbose_name风电场名称) location models.CharField(max_length200, verbose_name地理位置) capacity models.FloatField(verbose_name装机容量(MW)) class Turbine(models.Model): code models.CharField(max_length20, uniqueTrue, verbose_name风机编码) # 如GW101-001 name models.CharField(max_length50, verbose_name风机名称) wind_farm models.ForeignKey(WindFarm, on_deletemodels.CASCADE, verbose_name所属风电场) manufacturer models.CharField(max_length50, verbose_name制造商) status models.CharField(max_length20, choices[ (running, 运行中), (stopped, 停机), (maintenance, 维护中), (fault, 故障) ], defaultstopped) class RealTimeData(models.Model): turbine models.ForeignKey(Turbine, on_deletemodels.CASCADE) wind_speed models.FloatField(verbose_name风速(m/s)) power_output models.FloatField(verbose_name有功功率(kW)) rotor_speed models.FloatField(verbose_name转速(rpm)) nacelle_position models.FloatField(verbose_name机舱偏航角(°)) blade_pitch models.FloatField(verbose_name桨叶变桨角(°)) generator_temp models.FloatField(verbose_name发电机温度(℃)) created_at models.DateTimeField(db_columncreated_at_ms) # 对应MySQL DATETIME(3) class Meta: ordering [-created_at] indexes [ models.Index(fields[turbine, -created_at]), # 关键按风机时间倒序索引 ]重点说说这个indexes配置。没有它当你想查“GW101-001风机过去1小时的功率曲线”执行RealTimeData.objects.filter(turbine_id1, created_at__gtedatetime.now()-timedelta(hours1)).order_by(created_at)MySQL会全表扫描——100万台风机×3600秒×16个参数5.76亿条记录查询直接超时。加上联合索引后查询计划显示type: ref, key: turbine_created_at_idx, rows: 3600性能提升百倍。这个索引名在Django迁移时自动生成但你必须知道它存在否则线上出问题时连慢查询日志都看不懂。再看API视图views.py里两个核心接口# views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from django.views import View import json from .models import Turbine, RealTimeData method_decorator(csrf_exempt, namedispatch) class RealTimeDataView(View): def get(self, request, turbine_id): # 获取单台风机最新实时数据 try: latest RealTimeData.objects.filter(turbine_idturbine_id).latest(created_at) return JsonResponse({ success: True, data: { wind_speed: latest.wind_speed, power_output: latest.power_output, status: latest.turbine.status, updated_at: latest.created_at.strftime(%Y-%m-%d %H:%M:%S.%f)[:23] # 毫秒级时间字符串 } }) except RealTimeData.DoesNotExist: return JsonResponse({success: False, message: 无数据}, status404) class HistoricalDataView(View): def get(self, request, turbine_id): # 获取历史数据用于曲线图 start request.GET.get(start) end request.GET.get(end) if not start or not end: return JsonResponse({error: 缺少start或end参数}, status400) # 关键优化用raw SQL绕过ORM直接指定索引提示 with connection.cursor() as cursor: cursor.execute( SELECT UNIX_TIMESTAMP(created_at_ms) as timestamp, power_output, wind_speed FROM monitor_realtimedata WHERE turbine_id %s AND created_at_ms BETWEEN %s AND %s ORDER BY created_at_ms LIMIT 10000 # 防止前端一次拉太多数据卡死 , [turbine_id, start, end]) rows cursor.fetchall() return JsonResponse({success: True, data: rows})注意HistoricalDataView里用了原生SQL。ORM在复杂查询时生成的SQL往往不够精简比如created_at__range会生成BETWEEN但不带索引提示。我们手动写SQL并加LIMIT 10000既保证前端图表渲染流畅又避免拖垮数据库。这个细节在毕设答辩时老师问“怎么优化历史查询”这就是你能脱口而出的技术点。3.2 Vue前端让ECharts曲线真正“活”起来Vue项目结构遵循标准vue-cli脚手架重点在src/views/Monitor.vue!-- Monitor.vue -- template div classmonitor-container div classturbine-list div v-forturbine in turbines :keyturbine.id classturbine-card h3{{ turbine.name }} ({{ turbine.code }})/h3 div classstatus-indicator :classturbine.status {{ turbine.status | statusText }} /div div classrealtime-data p风速strong{{ turbine.realtime.wind_speed.toFixed(1) }}/strong m/s/p p功率strong{{ turbine.realtime.power_output | kWtoMW }}/strong MW/p /div button clickshowChart(turbine.id)查看曲线/button /div /div div v-ifchartVisible classchart-container div idpower-chart stylewidth: 100%; height: 400px;/div div classchart-controls select v-modeltimeRange option value1h1小时/option option value24h24小时/option option value7d7天/option /select button clickloadChartData()刷新/button /div /div /div /template script import * as echarts from echarts import { mapState, mapActions } from vuex export default { name: Monitor, data() { return { turbines: [], chartVisible: false, currentTurbineId: null, timeRange: 1h, chart: null } }, computed: { ...mapState([realtimeData]) }, async mounted() { await this.fetchTurbines() // 启动WebSocket连接接收实时推送 this.initWebSocket() }, methods: { ...mapActions([updateRealtimeData]), async fetchTurbines() { const res await fetch(/api/turbines/) this.turbines await res.json() // 初始化每台风机的实时数据 this.turbines.forEach(t { t.realtime this.realtimeData[t.id] || { wind_speed: 0, power_output: 0 } }) }, initWebSocket() { // 使用原生WebSocket不依赖第三方库 this.ws new WebSocket(ws://${window.location.host}/ws/monitor/) this.ws.onmessage (event) { const data JSON.parse(event.data) // data格式{ turbine_id: 1, wind_speed: 8.2, power_output: 1500.5 } this.updateRealtimeData(data) // 更新对应风机卡片 const turbine this.turbines.find(t t.id data.turbine_id) if (turbine) turbine.realtime data } }, showChart(turbineId) { this.currentTurbineId turbineId this.chartVisible true this.loadChartData() }, async loadChartData() { const end new Date() let start switch(this.timeRange) { case 1h: start new Date(end.getTime() - 3600000); break case 24h: start new Date(end.getTime() - 86400000); break case 7d: start new Date(end.getTime() - 604800000); break } const res await fetch(/api/turbines/${this.currentTurbineId}/history/?start${start.toISOString()}end${end.toISOString()}) const { data } await res.json() // ECharts初始化只在首次调用 if (!this.chart) { this.chart echarts.init(document.getElementById(power-chart)) } // 构建option注意xAxis.type设为timedata用[timestamp, value]格式 const option { tooltip: { trigger: axis }, xAxis: { type: time }, yAxis: { type: value }, series: [{ name: 有功功率, type: line, data: data.map(item [item[0] * 1000, item[1]]), // timestamp转毫秒 smooth: true }, { name: 风速, type: line, data: data.map(item [item[0] * 1000, item[2]]), smooth: true, yAxisIndex: 1 }], legend: { data: [有功功率, 风速] } } this.chart.setOption(option) } }, filters: { statusText(status) { const map { running: 运行中, stopped: 停机, maintenance: 维护中, fault: 故障 } return map[status] || status }, kWtoMW(kw) { return (kw / 1000).toFixed(2) } } } /script这段代码有几个实战要点WebSocket心跳保活实际部署时我们加了ws.onclose事件监听断开后3秒自动重连避免网络抖动导致数据中断。这部分代码没贴全但它是系统稳定性的基石。ECharts时间轴陷阱初学者常把xAxis设为category结果曲线挤成一团。必须用time类型并把后端返回的Unix时间戳秒级乘以1000转为毫秒ECharts才能正确解析。防抖加载loadChartData()方法里没加防抖但实际项目中我们在select的change事件里加了v-debounce:300loadChartData需引入vue-debounce避免用户狂点下拉框触发多次请求。内存泄漏防护beforeDestroy()钩子里必须调用this.chart.dispose()释放ECharts实例否则切换路由时图表会越积越多最后浏览器卡死。这个细节90%的毕设代码里都漏了。3.3 MySQL部署不只是安装而是为风电数据定制优化MySQL配置不是照搬教程而是针对风电数据特点调整字符集必须用utf8mb4因为风机编码如“GW101-①”含Unicode符号utf8不支持。InnoDB缓冲池毕设服务器通常8GB内存我们把innodb_buffer_pool_size设为5GB约60%确保热数据常驻内存。计算公式总内存 × 0.6不能设太高否则系统会OOM。慢查询日志开启slow_query_log ONlong_query_time 0.2200毫秒每天自动切割日志。曾靠这个发现SELECT * FROM realtimedata WHERE turbine_id1 ORDER BY created_at DESC LIMIT 1没走索引补上INDEX(turbine_id, created_at)后查询从1.2秒降到15毫秒。备份策略用mysqldump每天凌晨2点全量备份配合binlog做增量恢复。备份脚本里加了--single-transaction参数避免锁表影响实时采集。最关键的表结构优化在realtimedata表-- 创建表时指定ROW_FORMATDYNAMIC支持大字段 CREATE TABLE monitor_realtimedata ( id int(11) NOT NULL AUTO_INCREMENT, turbine_id int(11) NOT NULL, wind_speed double NOT NULL, power_output double NOT NULL, rotor_speed double NOT NULL, nacelle_position double NOT NULL, blade_pitch double NOT NULL, generator_temp double NOT NULL, created_at_ms datetime(3) NOT NULL, -- 毫秒级时间戳 PRIMARY KEY (id), KEY turbine_created_at_idx (turbine_id,created_at_ms) -- 联合索引 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 ROW_FORMATDYNAMIC;注意ROW_FORMATDYNAMIC。风电数据字段多16个浮点数默认COMPACT格式会导致行溢出查询变慢。DYNAMIC把长字段存到溢出页主记录更紧凑。这个参数在Django迁移里无法直接指定必须手动执行ALTER TABLE monitor_realtimedata ROW_FORMATDYNAMIC;。4. 部署全流程从本地开发到生产环境上线4.1 开发环境搭建避开Python和Node.js的版本地狱很多同学卡在第一步Python装哪个版本Vue用哪个CLI我们的经验是——严格锁定版本Python用pyenv管理固定为3.8.10。为什么不是3.9或3.10因为pymodbus在3.9版本有协程兼容问题而风电采集必须用同步阻塞模式保证数据顺序。Djangopip install Django3.2.18LTS长期支持版避免用4.x因为django-webpack-loader等生态库还没完全适配。Vuevue create时选“Manually select features”只勾选Babel、Router、CSS Pre-processorsSass不选TypeScript和VuexVuex 4.x对Vue 2.x支持不好毕设用Vue 2.6.14更稳。MySQL本地用Docker启动命令一行搞定docker run -d --name mysql-wind -p 3306:3306 -e MYSQL_ROOT_PASSWORDwind123 -e MYSQL_DATABASEwind_monitor -v $(pwd)/mysql-data:/var/lib/mysql mysql:5.7 --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci注意mysql:5.7镜像不是8.x。因为Django 3.2默认用mysqlclient驱动它对MySQL 8.x的caching_sha2_password认证方式支持不完善连不上。提示所有依赖版本写在requirements.txt和package.json里用pip freeze requirements.txt和npm list --depth0生成答辩时老师要看这个文件验证环境一致性。4.2 生产环境部署Nginx Gunicorn Supervisor三剑客服务器环境CentOS 7.9最低要求4核8G内存50GB硬盘。步骤1安装基础软件# 安装Python3.8系统自带Python2.7别动它 yum install -y gcc openssl-devel bzip2-devel libffi-devel wget https://www.python.org/ftp/python/3.8.10/Python-3.8.10.tgz tar -xzf Python-3.8.10.tgz cd Python-3.8.10 ./configure --enable-optimizations make altinstall # 安装MySQL 5.7官网下载rpm包避免源码编译 wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-community-server-5.7.39-1.el7.x86_64.rpm rpm -ivh mysql-community-server-5.7.39-1.el7.x86_64.rpm systemctl start mysqld # 获取初始密码grep temporary password /var/log/mysqld.log mysql -u root -p # 输入初始密码 ALTER USER rootlocalhost IDENTIFIED BY Wind123; # 修改强密码 CREATE DATABASE wind_monitor CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;步骤2部署Django应用# 创建应用目录 mkdir -p /opt/wind-monitor/{backend,frontend} cd /opt/wind-monitor/backend # 创建虚拟环境 python3.8 -m venv venv source venv/bin/activate pip install --upgrade pip pip install -r /path/to/your/requirements.txt # 包含django, gunicorn, pymysql等 # 配置Django cp /path/to/your/settings.py ./ # 修改settings.pyDEBUGFalse, ALLOWED_HOSTS[192.168.1.100, wind.example.com], DATABASES指向本地MySQL # 数据库迁移 python manage.py migrate python manage.py createsuperuser # 创建管理员账号 # 收集静态文件Vue构建后的dist目录要复制到Django static目录 cp -r /path/to/vue/dist/* /opt/wind-monitor/backend/static/ python manage.py collectstatic --noinput步骤3配置Gunicorn替代runserver# 安装gunicorn pip install gunicorn # 创建gunicorn.conf.py cat gunicorn.conf.py EOF command /opt/wind-monitor/backend/venv/bin/gunicorn pythonpath /opt/wind-monitor/backend bind 127.0.0.1:8000 workers 3 user nginx group nginx timeout 30 keepalive 5 max_requests 1000 EOF # 启动Gunicorn gunicorn --config gunicorn.conf.py wind_monitor.wsgi:application步骤4配置Nginx反向代理# 安装Nginx yum install -y nginx # 编辑/etc/nginx/conf.d/wind-monitor.conf cat /etc/nginx/conf.d/wind-monitor.conf EOF upstream django_app { server 127.0.0.1:8000; } server { listen 80; server_name 192.168.1.100; # 静态文件直接由Nginx服务 location /static/ { alias /opt/wind-monitor/backend/static/; expires 1h; } # Vue构建的index.html location / { root /opt/wind-monitor/backend/static; try_files $uri $uri/ /index.html; } # API请求反向代理到Django location /api/ { proxy_pass http://django_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # WebSocket升级 location /ws/ { proxy_pass http://django_app; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } EOF # 启动Nginx systemctl start nginx systemctl enable nginx步骤5用Supervisor守护进程# 安装supervisor yum install -y supervisor # 创建/etc/supervisord.d/wind-monitor.ini cat /etc/supervisord.d/wind-monitor.ini EOF [program:wind-monitor] command/opt/wind-monitor/backend/venv/bin/gunicorn --config /opt/wind-monitor/backend/gunicorn.conf.py wind_monitor.wsgi:application directory/opt/wind-monitor/backend usernginx autostarttrue autorestarttrue redirect_stderrtrue stdout_logfile/var/log/wind-monitor.log EOF # 启动supervisor systemctl start supervisord supervisorctl update supervisorctl start wind-monitor这套组合拳的意义在于Gunicorn处理Python应用Nginx处理HTTP和WebSocketSupervisor确保进程不死。任何一个环节崩溃其他部分仍能工作——比如Gunicorn挂了Nginx会返回502但静态页面还能访问Supervisor重启Gunicorn后服务自动恢复。这比单用nohup python manage.py runserver 靠谱一百倍。4.3 常见部署问题与排查技巧问题1Nginx返回502 Bad Gateway排查思路先看supervisorctl status确认Gunicorn进程是否在运行再看journalctl -u supervisord -f找启动错误最后看/var/log/wind-monitor.log常见原因是ImportError: No module named django——虚拟环境没激活或路径错了。解决检查/etc/supervisord.d/wind-monitor.ini里的command路径是否正确directory是否指向Django项目根目录。问题2Vue页面空白F12看Network全是404原因Nginx的location /配置没生效或者try_files $uri $uri/ /index.html;写错了。验证直接访问http://192.168.1.100/static/js/app.js如果404说明静态路径不对如果200但页面还是空白检查Vue的public/index.html里script src/js/app.js路径是否绝对应为/js/app.js不是js/app.js。问题3WebSocket连接失败报错Error during WebSocket handshake根源Nginx默认不支持WebSocket必须加proxy_http_version 1.1和proxy_set_header Connection upgrade。验证用浏览器开发者工具Network标签过滤WS看Status是否为101 Switching Protocols。问题4MySQL连接超时Django报OperationalError: (2006, MySQL server has gone away)原因MySQL的wait_timeout默认8小时但Django连接池可能保持空闲连接更久。解决在Django的DATABASES配置里加OPTIONS: {init_command: SET SESSION wait_timeout28800;}或在MySQL配置里设wait_timeout315360001年。实操心得部署不是一次性的而是迭代过程。我们团队的做法是——每次改配置先nginx -t测试语法再systemctl reload nginx改Supervisor配置先supervisorctl reread再supervisorctl update改Django代码先git pull再supervisorctl restart wind-monitor。养成习惯比救火强十倍。5. 毕设答辩与扩展建议让项目真正脱颖而出5.1 答辩现场最容易被问到的5个技术问题“你们怎么保证数据采集的实时性Modbus轮询间隔设多少”→ 回答轮询间隔设为800ms低于风机数据上报周期1s确保不丢帧用threading.Lock()保护共享内存缓冲区避免多线程写冲突实测16台风机全量采集耗时620ms留出180ms余量。“ECharts渲染千点曲线会不会卡有没有做性能优化”→ 回答做了三点① 后端LIMIT 10000限制单次数据量② 前端用echarts.connect()复用图表实例避免重复初始化③ 曲线图启用large: true和largeThreshold: 200让ECharts自动切到大数据模式。“如果风电场扩容到100台风机系统怎么横向扩展”→ 回答当前架构已预留扩展点① 采集脚本支持配置文件定义风机列表新增只需改配置② Django用DATABASE_ROUTERS可分库按风机ID哈希到不同MySQL实例③ Nginx可加upstream组Gunicorn进程分发到多台服务器。“权限控制为什么不用Django内置Permission而要写中间件”→ 回答内置Permission只能控制Model级如“能否查看Turbine表”而业务需要对象级如“能否查看ID为5的风机”。中间件在请求入口拦截用request.resolver_match.kwargs获取URL参数比在每个View里写
返回列表