稳定运行版本1.0 #161

Merged
p95fco63j merged 231 commits from develop into main 2 weeks ago

@ -1,4 +1,4 @@
# 个人周计划-第11
# 个人周计划-第12
## 姓名和起止时间

@ -0,0 +1,100 @@
# 小组会议纪要-第13周
## 会议记录概要
**团队名称:** 软1-汪汪队
**指导老师:** 肖雄仁
**主 持 人:** 曹峻茂
**记录人员:** 张红卫
**会议主题:** 第13周任务规划与分工
**会议地点:** 宿舍
**会议时间:** 2025-12-15 12:30-13:30
**记录时间:** 2025-12-15 18:00
**参与人员:** 曹峻茂、张红卫、罗月航、周竞由、王磊
---
## 会议内容
### 1. 本周总体目标
完成管理员地区管理、派单通知、学生实时数据查询及饮水点地图查看等核心功能开发与联调,推进项目功能完善与系统集成。
### 2. 后端任务安排
**后端1管理员地区管理功能**
- **负责人:** 王磊
- **内容:**
- 完善地区管理相关后端接口
- 实现地区数据的增删改查与权限控制
- 配合前端完成接口联调与数据验证
**后端2派单通知功能**
- **负责人:** 周竞由
- **内容:**
- 完善人工派单与自动派单的业务逻辑
- 实现派单通知的推送机制
- 配合APP端完成通知功能的联调测试
**后端3学生实时数据查询功能**
- **负责人:** 曹峻茂
- **内容:**
- 增加MQTT实时数据生成与推送功能
- 完善实时数据查询接口
- 确保数据实时性与稳定性
**后端4学生饮水点地图查看功能**
- **负责人:** 周竞由
- **内容:**
- 完成饮水点地理位置数据接口开发
- 配合APP端实现地图查看功能
- 确保数据准确性与地图交互流畅性
### 3. 前端任务安排
**前端1管理员地区管理功能**
- **负责人:** 张红卫
- **内容:**
- 完成地区管理前端页面开发
- 实现地区数据的可视化操作界面
- 与王磊协作完成接口联调与功能验证
**前端2派单通知功能与学生饮水点地图查看**
- **负责人:** 罗月航
- **内容:**
- 完成APP端派单通知界面开发与联调
- 实现学生饮水点地图查看功能
- 与周竞由协作完成接口对接与用户体验优化
**前端3学生实时数据查询功能**
- **负责人:** 罗月航
- **内容:**
- 完成APP端实时数据查询界面开发
- 实现MQTT实时数据接收与展示
- 与曹峻茂协作确保数据实时更新与界面流畅
### 4. 联调与整合任务
**前后端联调**
- **参与人员:** 全体成员
- **内容:**
- 完成各地区管理、派单通知、实时数据、地图查看等功能的端到端联调
- 统一测试数据格式与接口规范
- 输出联调测试报告与问题修复清单
### 5. 团队协作要求
- 每日晨会同步进展,晚前提交当日工作小结
- 联调问题统一记录至共享表格,明确责任人与解决时间
- 重点关注功能完整性与用户体验,确保各模块顺利衔接
- 加强前后端沟通,及时调整开发计划
### 6. 预期交付物
- 管理员地区管理功能前后端完成并测试通过
- 派单通知功能实现并完成联调
- 学生实时数据查询功能稳定运行
- 学生饮水点地图查看功能完成并体验优化
- 联调测试报告与问题修复清单
### 7. 支持需求
- 希望获得关于MQTT实时通信集成的技术指导
- 期待老师在权限管理与地图集成方面给予建议
- 希望有关于依赖管理的专题教学或讲座

@ -0,0 +1,32 @@
# 小组周计划-第13周
## 团队名称和起止时间
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-15
**结束时间:** 2025-12-21
## 本周任务计划安排
| 序号 | 计划内容 | 执行人 | 情况说明 |
|----|--------------------------------------------------------|----------------|-------------------------|
| 1 | 确定本周计划分工 | 全体组员 | 2023-12-15 开会确定计划以及团队分工 |
|2|完成管理员地区管理功能|王磊,张红卫|完善后端地区管理接口和前端页面|
|3|完成查看派单通知功能|周竞由,罗月航|完善人工派单和自动派单相关逻辑以及通知|
|4|完成学生实时查询数据功能|曹峻茂,罗月航|增加mqtt的实时数据生成和查看|
|5|完成学生饮水点地图查看功能|周竞由,罗月航|完成学生饮水点地图查看功能|
## 小结
1. **沟通协作:** 每日简短同步进度,遇到阻塞问题及时召开临时会议协商解决,可向指导老师及研究生学长寻求技术支持。
2. **学习安排:** 结合项目需求深入学习权限管理和接口性能优化相关知识,分享学习笔记。
3. **项目管理:** PM 每日跟踪任务进度,记录问题并推动解决,确保按计划推进。
4. 希望有关于依赖管理的教学
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. PM综合本小组成员工作情况提交小组周计划、周总结报告按时上传至代码托管平台。
---

@ -0,0 +1,33 @@
# 小组周总结-第12周
## 团队名称和起止时间
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-8
**结束时间:** 2025-12-14
## 本周任务计划安排
| 序号 | 总结内容 | 是否完成 | 情况说明 |
|----|--------------|------|----------------------------|
| 1 | 确定本周计划分工 | 完成 | 2023-12-8 开会确定计划以及团队分工 |
| 2 | 前后端联调 | 基本完成 | 前后端联调解决核心问题 |
|3|配置云服务器和数据库环境| 完成 |配置云服务器和数据库环境|
## 小结
1. **沟通协作:** 小组成员应积极主动沟通,遇到困难及时寻求帮助,也可以主动向指导老师及研究生学长寻求建议。
2. **学习安排:** 小组成员仍处于软件开发专业知识的初步学习阶段,应合理安排自主学习时间,以便后续开发的顺利进行。
3. **项目管理:** 代码框架、依赖版本和jdk版本管理混乱导致浪费大量时间需要改进。
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. PM综合本小组成员工作情况提交小组周计划、周总结报告按时上传至代码托管平台。
---

@ -0,0 +1,32 @@
# 个人周计划-第13周
## 姓名和起止时间
**姓  名:** 曹峻茂
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-15
**结束时间:** 2025-12-21
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
|---------------|--------------------|----|------------------------|
| 1 | 确定分工 | 组员 | 2023-12-15 开会确定计划和团队分工 |
| 2完成学生实时查询数据功能 |罗月航| 增加mqtt的实时数据生成和查看 |
## 小结
1. **知识储备:** 学习后续需要使用的知识,为后续的开发做准备;
2. **文档撰写:** 完成迭代开发计划撰写。
3. **项目管理** 管理项目环境和框架
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
1. 请将个人计划和总结提前发给负责人;
1. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
1. 所有组员都需提交个人周计划、周总结文档,按时上传至代码托管平台;
---

@ -0,0 +1,34 @@
# 个人周总结-第12周
## 姓名和起止时间
**姓  名:** 曹峻茂
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-8
**结束时间:** 2025-12-14
## 本周任务完成情况
| 序号 | 总结内容 | 是否完成 | 情况说明 |
|----| ------------------------------------------------ |------|--------------------------------------------------------------|
| 1 | 确定分工 | 完成 | 2023-12-8 开会确定计划和团队分工 |
|3|配置云服务器和数据库环境| 完成 |配置云服务器和数据库环境|
## 对团队工作的建议
1. **互助学习:** 小组成员应该根据自身的技能长短开展互帮互助的活动,共同努力提高小组成员的专业水平;
2. **进度统一:** 团队成员尽量统一项目进度;
## 小结
1. **项目管理:** 协调开发进度和前后端同步
2. **团队协作**:与团队成员保持良好的沟通协作,确保设计方向与产品需求一致
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. 所有组员都需提交个人周计划、周总结文档,上传至代码托管平台;

@ -0,0 +1,25 @@
# 个人周计划-第13周
## 姓名和起止时间
**姓  名:** [罗月航]
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-15
**结束时间:** 2025-12-21
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
| ---- | -------- | ------ | -------- |
| 1 | 学生端饮水点地图查看功能联调 | 后端开发 | 调试地图数据获取、位置标记、设备状态展示等接口 |
| 2 | 学生端扫码用水功能联调 | 后端开发 | 调试二维码识别、用水授权、用水记录生成等核心接口 |
| 3 | 学生端水质信息查看功能联调 | 后端开发 | 调试水质数据实时查询、历史趋势、水质评价等接口 |
| 4 | 联调问题记录与修复 | 后端开发 | 持续记录联调中的问题,协同修复并验证解决方案 |
| 5 | 性能优化方案制定 | 个人 | 针对已联调功能制定前端性能优化方案 |
## 小结
1. **联调重点**:本周重点完成学生端三大核心功能的接口联调,确保基础用户体验;
2. **计划衔接**:在联调的同时启动下一轮迭代规划,确保项目持续优化;
3. **跨组协作**:需要与产品、测试、后端等多方协调,确保计划顺利执行。

@ -0,0 +1,33 @@
# 个人周总结-第12周
## 姓名和起止时间
**姓  名:** [罗月航]
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-08
**结束时间:** 2025-12-14
## 本周任务完成情况
| 序号 | 总结内容 | 是否完成 | 情况说明 |
| ---- | -------- | -------- | -------- |
| 1 | 运维APP工单功能联调 | 完成 | 成功调试工单创建、抢单、处理、完成等完整业务流程接口,功能运行稳定 |
| 2 | 运维APP设备监控联调 | 完成 | 完成设备数据实时获取、告警推送、状态更新等接口调试,数据展示准确及时 |
| 3 | 学生端扫码功能联调 | 部分完成 | 基础扫码接口已调通,但部分异常情况处理还需优化 |
| 4 | 学生端用水记录联调 | 进行中 | 历史数据查询接口已对接,数据可视化部分正在调试 |
| 5 | 联调问题记录与修复 | 部分完成 | 已解决部分联调问题,剩余问题已明确责任人和解决时间 |
## 对团队工作的建议
1. **接口稳定性**:建议后端加强高并发场景下的接口稳定性测试;
2. **错误码规范**:建议统一异常情况的错误码和错误信息格式;
3. **测试环境**:建议建立独立的联调测试环境,避免影响开发环境稳定性;
4. **文档更新**:接口变更后需要及时更新接口文档,确保前后端信息同步。
## 小结
1. **核心功能联调完成**运维APP的工单和设备监控两大核心功能已成功联调业务流程完整
2. **接口稳定性提升**:通过本周的联调测试,发现并解决了多个接口稳定性问题;
3. **协作效率**:与后端团队协作顺畅,问题沟通和解决响应及时;
4. **进度评估**:整体联调进度符合预期,核心功能已基本就绪;
5. **后续重点**:下周将重点完成剩余功能联调和性能优化。

@ -0,0 +1,24 @@
# 个人周计划-第13周
## 姓名和起止时间
**姓  名:** 王磊
**团队名称:** 1班-汪汪队
**开始时间:** 2023-12-15
**结束时间:** 2023-12-21
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
| --- | -------- | --- | ----------------------------- |
| 1 | 确定分工 | 组员 | 2025-12-15 开会细分确定团队分工, |
| 2 | 联调支持 | 组员 | 配合前端完成接口联调问题,完成第一版迭代开发剩下的功能对接 |
| 3 | 管理员-地区管理 | 个人 | 实现管理员对地区的增删修改等功能 |
## 小结
1. **技术重点:** 持续修复接口联调中的问题,支持第一版迭代开发完成,确保接口稳定性与数据准确性。
2. **协作重点:** 加强与前端同学的日常沟通,参与联调复盘会议,总结常见问题与解决方案,形成团队协作经验沉淀。
3. **学习重点:** 学习接口性能分析与调优方法,提升问题定位与解决效率,为后续系统优化做准备。
---

@ -0,0 +1,36 @@
# 个人周计划-第12周
## 姓名和起止时间
**姓  名:** 王磊
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-8
**结束时间:** 2025-12-14
## 本周任务计划安排
| 序号 | 总结内容 | 是否完成 | 情况说明 |
| --- | ---- | ---- | ------------------------------------------ |
| 1 | 联调支持 | 进行中 | 配合前端完成接口联调问题,完成第一版迭代开发,目前已完成管理员和维修人员等一系列功能 |
## 对团队工作的建议
1.****建议建立接口联调检查清单**** 建议团队建立标准化的接口联调检查清单,明确数据格式、参数、权限校验等关键项,减少联调阶段的沟通成本和问题重复出现。
2.****建议加强文档更新机制**** 建议接口或功能变更后,相关开发人员及时更新接口文档,并同步至团队共享平台,确保前后端、测试等成员信息一致,提升协作效率。
## 小结
1. **技术收获** 掌握了多维度数据统计接口的设计与实现方法
2. **协作收获** 通过任务明确分工与进度同步,提升了团队协作效率,增强了与前端同学的沟通默契。
3. **后续重点** 继续配合前端进行联调和测试
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. 所有组员都需提交个人周计划、周总结文档,按时上传至代码托管平台;

@ -0,0 +1,88 @@
# 个人周计划-第十三周
## 基本信息
**姓  名:** 张红卫
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-15
**结束时间:** 2025-12-21
---
## 本周任务计划安排
| 序号 | 计划内容 | 执行人 | 情况说明 |
|------|----------|--------|----------|
| 1 | 管理员地区管理功能开发与联调 | 个人 + 王磊 | 完善后端地区管理接口,开发前端地区管理页面,完成前后端联调 |
| 2 | 参与学生实时数据查询功能联调 | 个人 + 曹峻茂 | 配合曹峻茂验证MQTT实时数据在前端的展示逻辑与性能 |
| 3 | 前后端联调与问题修复 | 个人 + 后端团队 | 参与团队联调,修复接口调用异常、数据展示不一致等问题 |
---
## 技术学习与实施重点
### 1. 地区管理功能开发
- 学习地区管理业务逻辑与数据结构设计
- 掌握地图数据与行政区划的集成方法
- 实现地区数据的可视化操作界面
### 2. 实时数据展示优化
- 学习MQTT协议在前端的应用
- 实现实时数据的动态更新与图表展示
- 优化大数据实时推送的性能与稳定性
### 3. 系统集成与联调
- 掌握多模块协同开发的调试方法
- 学习复杂业务场景下的问题定位与解决
- 完善系统集成测试流程
---
## 交付物清单
- [ ] 地区管理功能前后端联调完成代码
- [ ] 地区管理功能测试验证报告
- [ ] 实时数据展示性能优化方案
---
## 协作依赖与风险说明
### 协作依赖
1. **接口依赖**
- 依赖王磊提供稳定的地区管理接口
- 依赖周竞由提供完整的权限控制接口
- 需要曹峻茂提供MQTT实时数据接口支持
2. **流程依赖**
- 需要团队成员及时同步接口变更
- 依赖每日晨会机制进行进度同步
- 需要PM对任务进度的跟踪与协调
### 风险说明
1. **技术风险**
- MQTT实时数据推送可能存在延迟或丢包
- 地图API可能存在兼容性或性能问题
2. **协作风险**
- 多模块并行开发可能导致接口对齐困难
- 功能复杂度增加可能影响开发进度
---
## 小结与期望
### 本周目标
完成地区管理功能开发与联调,确保系统功能完整性与稳定性。
### 团队协作建议
1. 建议建立模块负责人制度,明确各功能模块的责任人
2. 建立技术难题共享机制,共同解决复杂技术问题
3. 定期进行代码审查,确保代码质量与规范性
### 支持需求
1. 希望获得关于MQTT实时通信集成的技术指导
2. 期待老师在地图功能集成方面提供方法建议
3. 希望有关于项目依赖管理的专题教学
---

@ -0,0 +1,72 @@
# 个人周总结-第十二周
## 基本信息
**姓  名:** 张红卫
**团队名称:** 软1-汪汪队
**起止时间:** 2025-12-8 至 2025-12-14
**核心职责:** 前端界面开发 + 前后端联调支持
---
## 本周任务完成情况
| 序号 | 计划内容 | 完成状态 | 详细说明 |
|------|--------------------------------------|----------|------------------------------------------------------------------------------------------------------------------|
| 1 | Web端统计页面联调与优化 | 基本完成 | 与王磊协作完成统计接口联调,优化图表加载性能,筛选交互功能已完善并通过测试 |
| 2 | Web端设备管理页面联调与优化 | 基本完成 | 与周竞由协作完成设备管理接口联调,实现权限控制与批量操作功能,并完成功能验证 |
| 3 | 前后端联调支持 | 基本完成 | 参与团队联调,修复接口调用异常、数据展示不一致等问题,提升系统稳定性 |
| 4 | 前端性能优化实施 | 基本完成 | 已实施分页加载、虚拟滚动方案,优化大数据场景下的页面渲染性能,部分场景仍需进一步测试 |
| 5 | 权限控制与异常处理完善 | 已完成 | 配合后端完成权限逻辑对齐,优化导航栏与页面异常跳转处理,提升用户体验 |
## 本周总结
### 一、任务进展概述
本周主要完成了**Web端统计页面**和**设备管理页面**的前后端联调工作,实现了权限控制与批量操作功能。性能优化方面实施了分页加载与虚拟滚动方案,整体联调任务基本完成,系统功能趋于稳定。
### 二、技术实现与收获
1. **性能优化实战**
- 实施虚拟滚动方案,显著提升大数据列表的渲染性能
2. **权限系统集成**
- 完成路由级与操作级权限控制,实现动态菜单与按钮权限
3. **联调协作能力提升**
- 提升前后端问题定位与协同调试效率
### 三、遇到的问题与挑战
1. **虚拟滚动兼容性问题**
- 在某些浏览器环境下滚动体验不一致,需进一步适配
2. **权限逻辑细粒度对齐**
- 部分页面操作权限与后端存在细微差异,需持续对齐
3. **大数据渲染仍有优化空间**
- 极大数据量下图表加载仍存在轻微延迟,需探索进一步优化方案
4. **联调沟通成本仍较高**
- 接口变更通知不够及时,影响前端调试进度
### 四、协作建议
1. **接口文档与测试数据标准化**
- 建议后端提供更详细的接口文档与测试用例
2. **建立联调问题跟踪机制**
- 使用共享文档或看板工具记录和跟踪联调问题
3. **定期性能测试与优化评审**
- 建议团队定期进行性能测试并共同评审优化方案
### 五、工作总结与展望
本周通过系统性的联调与优化工作,不仅完成了核心页面的功能交付,还在性能优化与权限控制方面积累了宝贵经验。团队协作效率有所提升,但仍需在沟通机制上进一步优化。
**期待与建议:**
- 希望团队能建立更规范的接口变更通知流程
- 建议开展前端性能优化专题分享,提升整体开发水平
- 期待在后续开发中继续深化性能优化与用户体验提升
---
**提交时间:** 2025-12-14

@ -0,0 +1,20 @@
# 个人周计划-第13周
## 姓名和起止时间
**姓  名:** 周竞由
**团队名称:** 1班-汪汪队
**开始时间:** 2023-12-15
**结束时间:** 2023-12-21
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
| --- |----------| --- |------------------------------------------------------------------------------------------------------------|
| 1 | 确定分工 | 组员 | 2023-12-15 组织团队会议,基于第一版迭代开发目标细分模块分工,明确各组员负责的功能模块、开发时限及交付标准,输出《团队分工清单》并同步至全员确认 |
| 2 | 联调支持 | 组员 | 每日上午9:30同步前端联调进度实时响应接口调用问题针对联调中出现的字段不匹配、数据返回异常等问题2小时内给出解决方案完成第一版迭代剩余3个核心功能的接口对接确保联调通过率100% |
| 3 | 管理人员功能完善 | 个人 | 实现管理员对用户列表的全量功能开发,包括用户信息新增(含字段校验)、编辑、删除(含二次确认)、列表查询(支持模糊搜索);添加用户列表权限控制逻辑,确保仅管理员可操作;开发完成后完成单元测试及接口自测,提交测试文档 |
## 小结
1. **技术重点:** 聚焦接口联调问题修复与地区管理功能开发,重点保障接口数据传输的稳定性、准确性,以及用户管理功能的逻辑完整性,提前规避权限漏洞、数据校验缺失等常见问题。
2. **协作重点:** 建立与前端的每日同步机制,高效推进联调工作;会后组织联调复盘,梳理常见问题类型及解决方案,形成《联调问题手册》,沉淀团队协作经验,提升后续开发效率。
3. **学习重点:** 系统学习接口性能分析方法熟悉Postman接口测试进阶技巧了解接口调优基础策略如数据缓存、SQL优化提升问题定位与快速解决能力为后续系统性能优化储备知识。

@ -0,0 +1,39 @@
# 个人周总结-第12周
## 姓名和起止时间
**姓  名:** 周竞由
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-8
**结束时间:** 2025-12-14
**核心职责:** 前后端接口联调(核心业务)、页面与后端逻辑联调、联调问题闭环与性能优化
## 本周任务完成情况
| 序号 | 计划内容 | 完成情况 | 说明 |
|----|-----------------------------|------|---------------------------------------------------------------------------------------------------------------|
| 1 | 前后端接口联调:核心业务接口(权限/告警/工单)全量对接验证 | 已完成 | 全面梳理3类核心业务接口文档核对18个关键字段定义逐一验证23个接口的参数传递、数据返回格式及12种异常场景处理逻辑成功修复5处接口调用异常问题含3处字段类型不匹配、2处异常码未统一所有接口均通过功能测试验证 |
| 2 | 页面与后端逻辑联调:权限展示/告警推送/工单流转页面 | 已完成 | 完成学生、维修人员、管理员三类角色对应的10个核心页面权限展示逻辑联调验证告警弹窗实时触发、告警列表动态刷新与后端数据的联动一致性确保工单创建、分配、处理、完结全流程页面数据同步更新解决4处页面数据延迟加载问题 |
| 3 | 联调问题闭环与性能优化:接口响应/数据一致性/多端兼容 | 已完成 | 汇总并梳理联调全流程21个问题清单逐一完成修复并通过回归测试针对8个高频调用接口进行性能优化含SQL查询优化、数据缓存处理 |
## 本周总结
### 1. 阶段成果:
本周顺利完成核心业务接口与页面的全量前后端联调工作,实现权限、告警、工单三大核心模块的前后端数据精准联动,统一了前后端字段定义与异常处理标准。通过问题闭环管理与性能优化,保障了系统接口响应效率、数据一致性及多端访问兼容性,核心业务链路可顺畅运行,为后续系统集成测试与上线部署奠定了稳固基础。
### 2. 技术提升:
- 深入掌握前后端接口联调的全流程方法提升了接口文档梳理、字段核对及异常场景分析能力熟练运用Postman进行接口批量测试与问题定位
- 强化了页面与后端逻辑联动的问题排查能力,掌握了跨端兼容性问题的调试技巧,能快速定位并解决数据同步、页面适配类问题;
- 积累了高频接口性能优化的实践经验熟悉SQL查询优化、数据缓存等常用优化手段提升了系统性能瓶颈的识别与解决能力。
### 3. 存在问题:
- 联调过程中发现部分接口文档存在描述不清晰、更新不及时的情况,导致前期沟通成本较高,影响联调效率;
- 性能优化仅针对高频接口,未对系统整体并发性能进行压力测试,无法确定高并发场景下的系统稳定性;
- 多端兼容测试覆盖的设备型号较少,对一些小众浏览器及老旧移动设备的适配情况未充分验证。
### 4. 改进方向:
- 推动团队规范接口文档管理,建立接口文档实时更新与审核机制,减少因文档问题导致的沟通与返工成本;
- 扩充多端兼容测试的设备与浏览器覆盖范围,整理适配问题清单,形成通用适配解决方案;
- 梳理联调过程中的典型问题与解决方案,形成知识库,为后续项目开发与联调提供参考。
### 5. 工作体会:
本周的前后端联调工作让我深刻体会到“沟通对齐”与“规范标准”的重要性。前后端开发前的需求细节对齐、接口标准统一,能极大提升联调效率。在问题排查过程中,团队成员的高效协作的关键,通过及时同步问题、共同分析原因,才能快速推进问题闭环。同时,性能优化与多端兼容是提升用户体验的核心环节,不能仅满足于功能实现,更要兼顾系统的稳定性与适配性。后续工作中,我会更加注重前期的需求与接口规范对齐,提前考虑性能与兼容问题,提升开发与联调的效率和质量。

@ -0,0 +1,84 @@
# 小组会议纪要-第14周
## 会议记录概要
**团队名称:** 软1-汪汪队
**指导老师:** 肖雄仁
**主 持 人:** 曹峻茂
**记录人员:** 张红卫
**会议主题:** 第14周任务规划与联调安排
**会议地点:** 宿舍
**会议时间:** 2025-12-22 12:30-13:30
**记录时间:** 2025-12-22 18:00
**参与人员:** 曹峻茂、张红卫、罗月航、周竞由、王磊
---
## 会议内容
### 1. 本周总体目标
完成项目整体联调测试,查漏补缺完善功能细节,准备全流程演示,确保项目功能完整性与稳定性。
### 2. 联调测试安排
**功能模块联调**
- **负责人:** 全体成员
- **内容:**
- 对已完成的功能模块进行端到端联调
- 验证各功能是否符合预设需求和设计规范
- 统一测试数据格式与接口响应标准
**测试用例执行**
- **负责人:** 全体成员
- **内容:**
- 执行各功能模块的测试用例
- 记录测试过程中发现的问题
- 输出测试报告与问题清单
### 3. 问题修复与优化
**功能完善**
- **负责人:** 全体成员
- **内容:**
- 修复联调过程中发现的功能缺陷
- 完善之前版本中存在的细节问题
- 优化用户体验和界面交互
**性能优化**
- **负责人:** 曹峻茂、周竞由
- **内容:**
- 检查并优化接口响应性能
- 验证MQTT实时数据传输的稳定性
- 优化地图功能的加载速度和流畅度
### 4. 演示准备
**演示流程设计**
- **负责人:** 罗月航、张红卫
- **内容:**
- 设计完整的项目功能演示流程
- 确保演示过程流畅自然
**演示环境准备**
- **负责人:** 王磊、周竞由
- **内容:**
- 搭建稳定的演示环境
- 准备演示所需的设备和数据
- 确保网络环境和系统配置正常
### 5. 预期交付物
- 功能缺陷修复清单和优化记录
- 完整的项目功能演示流程
### 6. 支持需求
- 希望获得关于项目演示技巧的指导
- 期待老师在系统性能优化方面给予建议
- 希望有关于项目总结和成果展示的专题指导
- 需要依赖管理方面的技术支持
---
**记录人:** 张红卫
**审核人:** 曹峻茂
**签发时间:** 2025-12-22

@ -0,0 +1,31 @@
# 小组周计划-第14周
## 团队名称和起止时间
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-22
**结束时间:** 2025-12-28
## 本周任务计划安排
| 序号 | 计划内容 | 执行人 | 情况说明 |
|----|--------------------------------------------------------|----------------|-------------------------|
| 1 | 确定本周计划分工 | 全体组员 | 2023-12-22 开会确定计划以及团队分工 |
|2|联调当前已完成代码,测试是否符合预设|全体组员 |联调测试|
|3|查漏补缺,完善细节问题以及之前欠缺的部分|全体组员|查漏补缺|
|4|全流程演示项目功能|全体组员|演示项目流程确定是否符合需求|
## 小结
1. **沟通协作:** 每日简短同步进度,遇到阻塞问题及时召开临时会议协商解决,可向指导老师及研究生学长寻求技术支持。
2. **学习安排:** 结合项目需求深入学习权限管理和接口性能优化相关知识,分享学习笔记。
3. **项目管理:** PM 每日跟踪任务进度,记录问题并推动解决,确保按计划推进。
4. 希望有关于依赖管理的教学
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. PM综合本小组成员工作情况提交小组周计划、周总结报告按时上传至代码托管平台。
---

@ -0,0 +1,35 @@
# 小组周总结-第12周
## 团队名称和起止时间
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-15
**结束时间:** 2025-12-22
## 本周任务计划安排
| 序号 | 总结内容 | 是否完成 | 情况说明 |
|----|--------------|------|----------------------------|
| 1 | 确定本周计划分工 | 完成 | 2023-12-15 开会确定计划以及团队分工 |
|2|完成管理员地区管理功能| 基本完成 |完善后端地区管理接口和前端页面|
|3|完成查看派单通知功能| 基本完成 |完善人工派单和自动派单相关逻辑以及通知|
|4|完成学生实时查询数据功能| 基本完成 |增加mqtt的实时数据生成和查看|
|5|完成学生饮水点地图查看功能| 基本完成 |完成学生饮水点地图查看功能|
## 小结
1. **沟通协作:** 小组成员应积极主动沟通,遇到困难及时寻求帮助,也可以主动向指导老师及研究生学长寻求建议。
2. **学习安排:** 小组成员仍处于软件开发专业知识的初步学习阶段,应合理安排自主学习时间,以便后续开发的顺利进行。
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. PM综合本小组成员工作情况提交小组周计划、周总结报告按时上传至代码托管平台。
---

@ -0,0 +1,34 @@
# 个人周计划-第14周
## 姓名和起止时间
**姓  名:** 曹峻茂
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-22
**结束时间:** 2025-12-28
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
|----|--------------------|----|------------------------|
| 1 | 确定本周计划分工 | 全体组员 | 2023-12-22 开会确定计划以及团队分工 |
|2|联调当前已完成代码,测试是否符合预设|全体组员 |联调测试|
|3|查漏补缺,完善细节问题以及之前欠缺的部分|全体组员|查漏补缺|
|4|全流程演示项目功能|全体组员|演示项目流程确定是否符合需求|
## 小结
1. **知识储备:** 学习后续需要使用的知识,为后续的开发做准备;
2. **文档撰写:** 完成迭代开发计划撰写。
3. **项目管理** 管理项目环境和框架
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
1. 请将个人计划和总结提前发给负责人;
1. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
1. 所有组员都需提交个人周计划、周总结文档,按时上传至代码托管平台;
---

@ -0,0 +1,33 @@
# 个人周总结-第13周
## 姓名和起止时间
**姓  名:** 曹峻茂
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-15
**结束时间:** 2025-12-22
## 本周任务完成情况
| 序号 | 总结内容 | 是否完成 | 情况说明 |
|----|------|------------------|--------------------------------------------------------------|
| 1 | 确定分工 | 完成 | 2023-12-15 开会确定计划和团队分工 |
| 2完成学生实时查询数据功能 | 基本完成 | 增加mqtt的实时数据生成和查看 |
## 对团队工作的建议
1. **互助学习:** 小组成员应该根据自身的技能长短开展互帮互助的活动,共同努力提高小组成员的专业水平;
2. **进度统一:** 团队成员尽量统一项目进度;
## 小结
1. **项目管理:** 协调开发进度和前后端同步
2. **团队协作**:与团队成员保持良好的沟通协作,确保设计方向与产品需求一致
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. 所有组员都需提交个人周计划、周总结文档,上传至代码托管平台;

@ -0,0 +1,28 @@
# 个人周计划-第14周
## 姓名和起止时间
**姓  名:** 罗月航
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-22
**结束时间:** 2025-12-28
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
| ---- | -------- | ------ | -------- |
| 1 | 功能细节问题排查与修复 | 个人/后端 | 全面排查已开发功能的细节问题,修复用户体验相关的小问题 |
| 2 | 兼容性测试与修复 | 个人/测试 | 在不同设备和浏览器上进行兼容性测试,修复发现的兼容性问题 |
| 3 | 代码质量审查与重构 | 个人 | 审查关键模块代码,进行必要的重构和优化,提升代码可维护性 |
| 4 | 全流程功能演示准备 | 全组 | 准备完整的功能演示流程|
## 小结
1. **质量优先**:本周工作重点从功能开发转向质量提升和细节完善;
2. **用户体验**:重点关注用户在使用过程中可能遇到的各种细节问题;
3. **稳定可靠**:确保系统在异常情况下仍能提供良好的用户体验;
4. **演示准备**:为项目演示做好充分准备,展示项目完整价值;
5. **团队协作**:需要与测试、产品、后端等多个角色紧密协作;
6. **全面检查**:对项目进行全面的质量检查,确保达到交付标准。

@ -0,0 +1,35 @@
# 个人周总结-第13周
## 姓名和起止时间
**姓  名:** 罗月航
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-15
**结束时间:** 2025-12-21
## 本周任务完成情况
| 序号 | 总结内容 | 是否完成 | 情况说明 |
| ---- | -------- | -------- | -------- |
| 1 | 学生端饮水点地图查看功能联调 | 完成 | 地图数据接口、位置标记和设备状态展示功能调试完成,地图交互流畅 |
| 2 | 学生端扫码用水功能联调 | 完成 | 二维码识别、用水授权接口调通,用水记录生成功能正常运行 |
| 3 | 学生端水质信息查看功能联调 | 完成 | 水质实时数据查询、历史趋势展示和水质评价接口全部调试完成 |
| 4 | 迭代开发计划制定 | 完成 | 已完成下一轮迭代需求分析,初步确定了优化方向和优先级 |
| 5 | 联调问题记录与修复 | 完成 | 记录并解决了本周联调中发现的18个问题问题解决率100% |
## 对团队工作的建议
1. **迭代规划会议**:建议下周召开迭代规划会议,明确下一阶段开发重点;
2. **用户测试安排**:建议安排正式的用户测试,收集更多真实使用反馈;
3. **性能监控部署**:建议部署前端性能监控工具,持续追踪优化效果;
## 小结
1. **核心功能达标**:学生端三大核心功能联调全部完成,基础体验达到预期标准;
2. **接口稳定性良好**:本周联调过程中接口稳定性明显提升,错误率大幅降低;
3. **问题解决高效**:建立了快速响应的问题解决机制,联调效率显著提高;
4. **迭代规划就绪**:已完成下一轮迭代的初步规划,为项目持续优化做好准备;
5. **协作配合默契**:团队协作更加顺畅,前后端沟通效率进一步提升;
6. **质量意识提升**:在联调过程中同步关注性能和体验,质量把控更加全面;
7. **后续重点明确**:接下来将进入系统测试和迭代开发阶段,确保项目质量持续提升。

@ -0,0 +1,23 @@
# 个人周计划-第14周
## 姓名和起止时间
**姓  名:** 王磊
**团队名称:** 1班-汪汪队
**开始时间:** 2023-12-22
**结束时间:** 2023-12-28
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
| --- | ---- | --- | ---------------------- |
| 1 | 确定分工 | 组员 | 2025-12-22 开会细分确定团队分工, |
| 2 | 联调支持 | 组员 | 配合前端完成接口联调问题,完成功能对接 |
## 小结
1. **技术重点:** 持续修复接口联调中的问题
2. **协作重点:** 加强与前端同学的沟通,参与联调复盘会议
3. **学习重点:** 学习接口性能分析与调优方法,提升问题定位与解决效率
---

@ -0,0 +1,37 @@
# 个人周计划-第13周
## 姓名和起止时间
**姓  名:** 王磊
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-15
**结束时间:** 2025-12-21
## 本周任务计划安排
| 序号 | 总结内容 | 是否完成 | 情况说明 |
| --- | -------- | ---- | ---------------------- |
| 1 | 联调支持 | 基本完成 | 配合前端完成接口联调问题,完成第一版迭代开发 |
| 2 | 管理员-地区管理 | 基本完成 | 实现管理员对地区的增删修改等功能 |
## 对团队工作的建议
1.****建议建立接口联调检查清单**** 建议团队建立标准化的接口联调检查清单,明确数据格式、参数、权限校验等关键项,减少联调阶段的沟通成本和问题重复出现。
2.**建议设立联调沟通窗口时间** 为提升联调效率,建议团队每日设定固定的联调沟通时间窗口,集中处理接口调试问题,避免因沟通不畅导致的进度延误。
## 小结
1. **技术收获** 掌握了多维度数据统计接口的设计与实现方法,对权限控制与数据校验有了更深入的理解。
2. **协作收获** 通过任务分工明确与进度同步机制,提升了团队协作效率,增强了与前端同学的沟通默契,为后续合作打下良好基础。
3. **后续重点** 继续配合前端完成接口联调与测试工作,确保功能稳定交付,同时关注性能优化与异常处理。
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. 所有组员都需提交个人周计划、周总结文档,按时上传至代码托管平台;

@ -0,0 +1,86 @@
# 个人周计划-第十四周
## 基本信息
**姓  名:** 张红卫
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-22
**结束时间:** 2025-12-28
---
## 本周任务计划安排
| 序号 | 计划内容 | 执行人 | 情况说明 |
|------|----------|--------|----------|
| 1 | 参与项目整体联调测试 | 个人 + 全体成员 | 对已完成功能模块进行端到端联调,验证功能是否符合预设需求 |
| 2 | 负责演示流程设计与准备 | 个人 + 罗月航 | 设计完整项目功能演示流程|
| 3 | 功能问题修复与优化 | 个人 + 相关模块负责人 | 修复联调中发现的功能缺陷,优化用户体验和界面交互 |
---
## 技术学习与实施重点
### 1. 系统联调与测试
- 学习端到端联调的流程与方法
- 掌握功能验证和问题定位的技巧
### 2. 演示准备与展示
- 学习项目演示的设计与组织方法
- 掌握演示流程的编排技巧
- 优化演示过程中的用户体验
### 3. 性能优化与问题修复
- 学习系统性能分析与优化方法
- 掌握常见功能问题的排查与修复
- 优化界面交互的流畅性和响应速度
---
## 交付物清单
- [ ] 演示流程设计方案
- [ ] 功能问题修复与优化
---
## 协作依赖与风险说明
### 协作依赖
1. **联调依赖**
- 依赖全体成员按时完成各自模块的开发与调试
- 需要各模块负责人提供稳定的接口和数据支持
- 依赖测试数据的准备和环境的搭建
2. **演示依赖**
- 需要罗月航协作完成演示流程设计
- 依赖王磊和周竞由提供稳定的演示环境
- 需要全体成员配合演示测试和演练
### 风险说明
1. **技术风险**
- 联调过程中可能出现接口不一致或数据异常
- 演示环境可能出现不稳定或性能问题
- 多模块集成可能引发新的兼容性问题
2. **时间风险**
- 联调问题较多可能影响整体进度
- 演示准备需要充分测试和演练时间
3. **协作风险**
- 多任务并行可能导致资源分配紧张
- 问题修复需要跨模块协作和沟通
- 演示流程需要团队成员的高度配合
---
## 小结与期望
### 本周目标
完成项目整体联调测试,设计完整的演示流程,确保项目功能完整性和稳定性,为项目验收做好充分准备。
### 支持需求
1. 希望获得关于项目演示技巧和流程设计的指导
2. 期待老师在系统性能优化和问题排查方面提供建议
3. 希望有关于项目总结和成果展示的专题教学
4. 需要依赖管理和环境配置方面的技术支持

@ -0,0 +1,66 @@
# 个人周总结-第十二周
## 基本信息
**姓  名:** 张红卫
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-15
**结束时间:** 2025-12-21
---
## 本周任务完成情况
| 序号 | 任务内容 | 是否完成 | 完成情况说明 |
|------|----------|----------|--------------|
| 1 | 管理员地区管理功能开发与联调 | 进行中 | 完成后端地区管理接口开发,前端页面基本实现,完成初步联调 |
| 2 | 参与学生实时数据查询功能联调 | 基本完成 | 配合完成MQTT实时数据接入前端展示功能初步实现 |
| 3 | 前后端联调与问题修复 | 进行中 | 参与团队联调,修复部分接口异常与数据展示问题 |
---
## 技术学习与实施进展
### 1. 地区管理功能开发
- 学习了地区管理的数据结构与业务流程
- 实现了基础的地区增删改查与可视化界面
### 2. 系统集成与联调
- 参与了多模块协同联调流程
- 学习了在复杂业务场景下定位与解决问题的方法
- 协助完善了部分系统集成测试用例
---
## 交付物完成情况
- [x] 地区管理功能前后端联调完成代码(初版)
- [ ] 地区管理功能测试验证报告(待完善)
- [ ] 实时数据展示性能优化方案(进行中)
---
## 协作与沟通情况
### 协作情况
- 与王磊密切配合完成地区管理功能开发
- 与曹峻茂协作完成实时数据展示功能联调
### 沟通反馈
- 在接口对齐、数据格式统一等方面沟通较为顺畅
- 遇到技术难题时及时向团队成员与指导老师求助
---
## 支持与建议
### 希望获得的帮助
1. 希望老师或学长对MQTT实时通信的稳定性优化提供指导
2. 期待有关于地图集成与性能优化的专题分享
3. 建议团队定期组织代码审查与架构评审会议
### 团队协作建议
1. 进一步明确各模块接口责任人,减少沟通成本
2. 建立技术难题池,集中攻关与知识共享
3. 加强测试与文档的同步推进,提升交付质量

@ -0,0 +1,23 @@
# 个人周计划-第14周
## 姓名和起止时间
**姓  名:** 周竞由
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-22
**结束时间:** 2025-12-27
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
| --- | ---- | --- | ---------------------- |
| 1 | 确定分工 | 组员 | 2025-12-22 开会细分确定团队分工, |
| 2 | 联调支持 | 组员 | 配合前端完成接口联调问题,完成功能对接 |
## 小结
1. **技术重点:** 持续修复接口联调中的问题
2. **协作重点:** 加强与前端同学的沟通,参与联调复盘会议
3. **学习重点:** 学习接口性能分析与调优方法,提升问题定位与解决效率
---

@ -0,0 +1,28 @@
# 个人周总结-第13周
## 周竞由 - 个人周总结
### 姓名和起止时间
**姓  名:** 周竞由
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-15
**结束时间:** 2025-12-21
### 本周任务完成情况
| 序号 | 总结内容 | 是否完成 | 情况说明 |
| ---- |----------------|----------|--------------------------------------------------------------------------|
| 1 | 确定分工 | 完成 | 2023-12-15顺利组织团队会议基于第一版迭代开发目标完成模块分工细分明确了各组员功能模块、开发时限及交付标准输出《团队分工清单》并同步全员完成确认 |
| 2 | 联调支持 | 完成 | 每日上午9:30按时同步前端联调进度实时响应接口调用问题联调中出现的字段不匹配、数据返回异常等问题均在2小时内给出解决方案已完成第一版迭代剩余3个核心功能的接口对接联调通过率达100% |
| 3 | 管理人员功能完善 | 完成 | 已实现管理员对用户列表的新增(含字段校验)、编辑、删除(含二次确认)、列表查询(支持模糊搜索)全量功能;添加了用户列表权限控制逻辑,保障仅管理员可操作;完成单元测试及接口自测,已提交测试文档 |
### 对团队工作的建议
1. **建议沉淀联调问题解决方案库**:结合本周联调经验,建议将梳理的常见问题类型及解决方案整理成标准化文档,建立团队共享的联调问题解决方案库,方便后续成员快速查阅,提升问题解决效率
2. **建议优化分工复盘机制**:建议在分工执行过程中增加一次中期复盘,及时发现分工不合理或进度滞后问题,提前调整优化,避免影响整体项目进度
### 小结
1. **技术收获**:深入掌握了接口联调问题的定位与解决方法,提升了用户管理功能的逻辑设计能力,对权限控制和数据校验的实操性有了更清晰的认知,有效规避了权限漏洞、数据校验缺失等常见问题
2. **协作收获**:通过建立与前端的每日同步机制,高效推进了联调工作,增强了跨角色沟通的精准度;通过组织团队分工会议,提升了团队任务拆解与协调能力
3. **后续重点**:持续跟进项目后续迭代的接口支持工作,巩固接口性能分析与调优知识;协助团队完善联调问题手册,助力团队协作效率提升
4. **希望得到的帮助**希望团队能组织一场接口性能调优专题讲座邀请有经验的技术人员分享数据缓存、SQL优化等实操技巧帮助提升问题定位与系统优化能力
---

@ -0,0 +1,98 @@
# 小组会议纪要-第15周
## 会议记录概要
**团队名称:** 软1-汪汪队
**指导老师:** 肖雄仁
**主 持 人:** 曹峻茂
**记录人员:** 张红卫
**会议主题:** 第15周任务规划与功能优化专题讨论
**会议地点:** 宿舍
**会议时间:** 2025-12-29 12:30-13:30
**记录时间:** 2025-12-29 18:00
**参与人员:** 曹峻茂、张红卫、罗月航、周竞由、王磊
---
## 会议内容
### 1. 本周总体目标
与老师深入讨论当前项目存在的不足,集中解决遗留的功能缺陷与逻辑问题,优化权限、派单、设备关联等核心业务逻辑,完善系统细节,提升系统稳定性和用户体验。
### 2. 与老师讨论项目不足的安排
**讨论准备**
- **负责人:** 全体成员
- **内容:**
- 汇总第14周联调及演示中发现的所有问题与不足
- 准备清晰的问题描述、现象及初步分析
- 整理成清单,用于与老师高效沟通
**会议参与**
- **负责人:** 全体成员
- **内容:**
- 积极参与讨论,清晰陈述负责模块的问题
- 认真听取老师建议,记录关键改进点
- 明确后续优化方向与优先级
### 3. 核心功能问题修复与优化分工
**工单模块修复**
- **负责人:** 曹峻茂
- **内容:** 修复工单页面显示“未知设备”的问题,确保设备信息准确展示。
**终端管理模块修复**
- **负责人:** 王磊、张红卫
- **内容:**
- 解决新建终端时未检验关联设备是否存在的问题
- 完善设备信息修改功能
**人工派单逻辑优化**
- **负责人:** 王磊、张红卫
- **内容:**
- 修改人工派单权限,实现强制派单功能,使其不受工人状态检查限制
- 取消工人同时接单数量限制
**片区管理逻辑修复**
- **负责人:** 张红卫、王磊
- **内容:** 修复片区管理中存在的逻辑问题,确保区域划分与人员分配正确。
**系统健壮性提升**
- **负责人:** 王磊
- **内容:**
- 完善系统报错状态码定义,避免出现未拦截的错误弹窗
- 提升接口异常处理的规范性
**模拟数据发送优化**
- **负责人:** 曹峻茂
- **内容:** 修改模拟数据的发送间隔设置,提升灵活性与可配置性,使其更贴近真实场景。
**其他细节完善**
- **负责人:** 全体成员
- **内容:** 根据与老师讨论的结果,视情况完善其他系统细节问题。
### 4. 协作与沟通机制
**每日进度同步**
- **形式:** 每日简短站会(线上/线下)
- **内容:** 同步当日进展、遇到的问题及次日计划。
- **要求:** 遇到阻塞性问题及时提出,必要时召开临时会议协商。
**问题跟踪与管理**
- **负责人:** PM曹峻茂
- **内容:** 每日跟踪各任务进度,记录问题清单并推动解决,确保按计划推进。
### 5. 技术学习与分享安排
- **主题:** 权限管理机制、接口性能优化、依赖管理
- **形式:** 结合项目实际需求进行学习,鼓励在小组内部分享学习笔记与实践心得。
### 6. 预期交付物
- 各项功能缺陷的修复代码
- 优化后的系统核心业务流程
- 更新的接口文档与错误码规范
---
**记录人:** 张红卫
**审核人:** 曹峻茂
**签发时间:** 2025-12-29

@ -0,0 +1,38 @@
# 小组周计划-第15周
## 团队名称和起止时间
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-29
**结束时间:** 2026-1-4
## 本周任务计划安排
| 序号 | 计划内容 | 执行人 | 情况说明 |
|----|------------------------|--------|-------------------------|
| 1 | 确定本周计划分工 | 全体组员 | 2023-12-29 开会确定计划以及团队分工 |
|2| 和老师开会讨论当前项目的不足 | 全体组员 | 和老师讨论当前开发状况 |
|3| 解决工单显示未知设备问题 | 曹峻茂 | 解决工单页面的显示错误 |
|4| 解决新建终端关联没有检验关联设备是否存在问题 | 王磊,张红卫 | 解决新建终端的问题 |
|5| 修改人工派单权限,强制派单 |王磊,张红卫|保证人工派单不会受工人状态检查|
|6| 取消工人同时接单数量限制 |王磊|取消工人同时接单数量限制|
|7| 修复片区管理逻辑 |张红卫,王磊|修复片区管理问题|
|8| 完善报错状态码定义 |王磊|避免未拦截弹窗|
|9| 修改发送间隔设置 |曹峻茂|模拟数据发送间隔固定,灵活性差|
|10|完善设备信息修改|王磊,张红卫|补充设备信息修改功能|
|11|根据老师建议完善其他细节问题|全体成员|视情况完善相关细节|
## 小结
1. **沟通协作:** 每日简短同步进度,遇到阻塞问题及时召开临时会议协商解决,可向指导老师及研究生学长寻求技术支持。
2. **学习安排:** 结合项目需求深入学习权限管理和接口性能优化相关知识,分享学习笔记。
3. **项目管理:** PM 每日跟踪任务进度,记录问题并推动解决,确保按计划推进。
4. 希望有关于依赖管理的教学
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. PM综合本小组成员工作情况提交小组周计划、周总结报告按时上传至代码托管平台。
---

@ -0,0 +1,34 @@
# 小组周总结-第14周
## 团队名称和起止时间
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-22
**结束时间:** 2025-12-28
## 本周任务计划安排
| 序号 | 总结内容 | 是否完成 | 情况说明 |
|----|--------------|------|----------------------------|
| 1 | 确定本周计划分工 | 完成 | 2023-12-22 开会确定计划以及团队分工 |
|2|联调当前已完成代码,测试是否符合预设| 完成 |联调测试|
|3|查漏补缺,完善细节问题以及之前欠缺的部分| 完成 |查漏补缺|
|4|全流程演示项目功能| 完成 |演示项目流程确定是否符合需求|
## 小结
1. **沟通协作:** 小组成员应积极主动沟通,遇到困难及时寻求帮助,也可以主动向指导老师及研究生学长寻求建议。
2. **学习安排:** 小组成员仍处于软件开发专业知识的初步学习阶段,应合理安排自主学习时间,以便后续开发的顺利进行。
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. PM综合本小组成员工作情况提交小组周计划、周总结报告按时上传至代码托管平台。
---

@ -0,0 +1,34 @@
# 个人周计划-第15周
## 姓名和起止时间
**姓  名:** 曹峻茂
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-29
**结束时间:** 2025-1-4
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
|----|--------------------|------|-------------------------|
| 1 | 确定本周计划分工 | 全体组员 | 2023-12-29 开会确定计划以及团队分工 |
|2| 和老师开会讨论当前项目的不足 | 全体组员 | 和老师讨论当前开发状况 |
|3| 解决工单显示未知设备问题 | 个人 | 解决工单页面的显示错误 |
|9| 修改发送间隔设置 | 个人 |模拟数据发送间隔固定,灵活性差|
## 小结
1. **知识储备:** 学习后续需要使用的知识,为后续的开发做准备;
2. **文档撰写:** 完成迭代开发计划撰写。
3. **项目管理** 管理项目环境和框架
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
1. 请将个人计划和总结提前发给负责人;
1. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
1. 所有组员都需提交个人周计划、周总结文档,按时上传至代码托管平台;
---

@ -0,0 +1,35 @@
# 个人周总结-第14周
## 姓名和起止时间
**姓  名:** 曹峻茂
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-22
**结束时间:** 2025-12-28
## 本周任务完成情况
| 序号 | 总结内容 | 是否完成 | 情况说明 |
|----|------|------|--------------------------------------------------------------|
| 1 | 确定本周计划分工 | 完成 | 2023-12-22 开会确定计划以及团队分工 |
|2|联调当前已完成代码,测试是否符合预设| 完成 |联调测试|
|3|查漏补缺,完善细节问题以及之前欠缺的部分| 完成 |查漏补缺|
|4|全流程演示项目功能| 完成 |演示项目流程确定是否符合需求|
## 对团队工作的建议
1. **互助学习:** 小组成员应该根据自身的技能长短开展互帮互助的活动,共同努力提高小组成员的专业水平;
2. **进度统一:** 团队成员尽量统一项目进度;
## 小结
1. **项目管理:** 协调开发进度和前后端同步
2. **团队协作**:与团队成员保持良好的沟通协作,确保设计方向与产品需求一致
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. 所有组员都需提交个人周计划、周总结文档,上传至代码托管平台;

@ -0,0 +1,29 @@
# 个人周计划-第15周
## 姓名和起止时间
**姓  名:** 罗月航
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-29
**结束时间:** 2026-01-04
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
| ---- | -------- | ------ | -------- |
| 1 | 与老师项目讨论会议 | 老师/全组 | 安排专门会议,向老师汇报项目进展,听取专业意见和建议 |
| 2 | 项目不足分析整理 | 个人 | 根据老师反馈和自查结果,系统整理项目存在的不足和改进点 |
| 3 | 界面交互改进调整 | 个人/设计 | 根据审美和可用性建议,优化界面布局和交互设计 |
| 4 | 业务流程梳理优化 | 产品/全组 | 重新审视核心业务流程,优化操作步骤和逻辑流程 |
| 5 | 代码质量二次审查 | 个人 | 对修改后的代码进行质量审查,确保代码规范和可维护性 |
## 小结
1. **专家指导**:本周重点是通过老师指导获得专业意见和建议,提升项目质量;
2. **问题导向**:以问题为导向,针对性地解决项目中存在的不足和缺陷;
3. **持续改进**:在已完成的基础上进行二次优化,追求更高质量标准;
4. **团队协作**:需要全组成员共同参与讨论和改进,形成共识;
5. **质量闭环**:建立从发现问题到解决问题的完整质量改进闭环;
6. **时间安排**:合理安排会议和开发时间,确保改进工作高效推进。

@ -0,0 +1,38 @@
# 个人周总结-第14周
## 姓名和起止时间
**姓  名:** 罗月航
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-22
**结束时间:** 2025-12-28
## 本周任务完成情况
| 序号 | 总结内容 | 是否完成 | 情况说明 |
| ---- | -------- | -------- | -------- |
| 1 | 功能细节问题排查与修复 | 完成 | 排查并修复了32个用户体验细节问题包括表单验证、按钮状态、提示信息等 |
| 2 | 界面交互优化完善 | 完成 | 优化了页面加载动画、过渡效果、滑动流畅度等交互细节,用户体验显著提升 |
| 3 | 错误处理机制完善 | 完成 | 完善了网络异常、接口超时、数据异常等情况的用户提示和处理逻辑 |
| 4 | 性能瓶颈优化 | 完成 | 针对首页加载、地图渲染、列表滚动等性能瓶颈进行了优化加载速度提升40% |
| 5 | 代码质量审查与重构 | 完成 | 审查了5个核心模块代码重构了3处复杂逻辑代码可读性和可维护性提升 |
| 6 | 全流程功能演示准备 | 完成 | 准备了完整的演示流程和演示材料,覆盖运维和学生端的所有核心业务场景 |
## 对团队工作的建议
1. **持续集成**:建议建立自动化的持续集成流程,减少手动部署和测试的工作量;
2. **监控预警**:建议部署系统运行监控和预警机制,及时发现和解决问题;
3. **知识沉淀**:建议将项目开发过程中的经验教训进行整理和沉淀,形成团队知识库。
## 小结
1. **质量显著提升**:通过本周的系统性优化,项目整体质量和用户体验达到新的水平;
2. **细节决定成败**:修复了大量细节问题,用户在使用过程中的体验更加流畅自然;
3. **性能明显改善**:通过针对性优化,关键页面的加载速度和响应速度大幅提升;
4. **演示准备充分**:全流程演示方案已准备就绪,能够全面展示项目价值和功能特色;
5. **文档完整同步**:用户文档和技术文档与当前功能完全同步,为后续维护打下基础;
6. **部署可靠稳定**:生产环境配置经过优化,系统运行的稳定性和可靠性得到保障;
7. **项目趋于成熟**:经过本周的查漏补缺,项目已进入成熟稳定阶段,基本达到交付标准;
8. **团队成长明显**:在项目优化过程中,团队对质量把控和用户体验的理解更加深入。

@ -0,0 +1,28 @@
# 个人周计划-第15周
## 姓名和起止时间
**姓  名:** 王磊
**团队名称:** 1班-汪汪队
**开始时间:** 2023-12-29
**结束时间:** 2023-1-4
## 本周任务计划安排
| 序号 | 计划内容 | 协作人 | 情况说明 |
| --- | --------- | --- | ------------------------ |
| 1 | 确定分工 | 组员 | 2025-12-29 开会细分确定团队分工, |
| 2 | 流程闭环所有子流程 | 组员 | 告警→派单→接单→维修→审核→结单,流程形成闭环 |
| 3 | 管理员人工派单 | 组员 | 管理员人工派单是强制的、不受限制的 |
| 4 | 维修工接单 | 组员 | 维修人员能同时接许多工单 |
| 5 | 片区管理 | 组员 | 给片区的分级关联上 |
| 6 | 报错码定义 | 组员 | 根据不同报错原因设置不同报错码 |
## 小结
1. **技术重点:** 实现工单流程闭环,确保各环节状态转换正确;统一团队错误码规范,优化异常处理机制;持续提升接口性能与稳定性
2. **协作重点:** 明确分工职责,积极参与任务同步;保持前后端高效沟通,确保流程理解一致;共同推进联调测试,及时反馈问题
3. **学习重点:**
* 掌握层级权限与错误码设计方法;提升复杂业务场景的技术实现能力
---

@ -0,0 +1,37 @@
# 个人周计划-第14周
## 姓名和起止时间
**姓  名:** 王磊
**团队名称:** 1班-汪汪队
**开始时间:** 2025-12-22
**结束时间:** 2025-12-28
## 本周任务计划安排
| 序号 | 总结内容 | 是否完成 | 情况说明 |
| --- | ---- | ---- | ---------------- |
| 1 | 联调支持 | 基本完成 | 配合前端完成接口联调问题 |
| 2 | 终端管理 | 基本完成 | 实现管理员对终端的增删修改等功能 |
## 对团队工作的建议
1.****建议建立接口联调检查清单**** 建议团队建立标准化的接口联调检查清单,明确数据格式、参数、权限校验等关键项,减少联调阶段的沟通成本和问题重复出现。
2.**建议设立联调沟通窗口时间** 为提升联调效率,建议团队每日设定固定的联调沟通时间窗口,集中处理接口调试问题,避免因沟通不畅导致的进度延误。
## 小结
1. **技术收获** 掌握了多维度数据统计接口的设计与实现方法,对权限控制与数据校验有了更深入的理解。
2. **协作收获** 通过与前端同学紧密配合,逐步形成了“接口先行、文档同步、测试跟进”的协作节奏,提升了跨角色沟通效率与任务协同能力。
3. **后续重点** 对项目的功能细节方面进行优化完善。
---
## 【注】
1. 在小结一栏中写出希望得到如何的帮助,如讲座等;
2. 请将个人计划和总结提前发给负责人;
3. 周任务总结与计划是项目小组评分考核的重要依据,将直接记入平时成绩,请各位同学按要求认真填写并按时提交;
4. 所有组员都需提交个人周计划、周总结文档,按时上传至代码托管平台;

@ -0,0 +1,84 @@
# 个人周计划-第十五周
## 基本信息
**姓  名:** 张红卫
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-29
**结束时间:** 2026-1-4
---
## 本周任务计划安排
| 序号 | 计划内容 | 执行人 | 情况说明 |
|----|-------------------------|-----------|------------------------|
| 1 | 参与“解决新建终端关联未检验设备是否存在”问题 | 个人 + 王磊 | 确保新建终端时检验关联设备存在性 |
| 2 | 参与“修改人工派单权限,强制派单”任务 | 个人 + 王磊 | 实现人工派单不受工人状态检查限制的功能 |
| 3 | 参与“修复片区管理逻辑”任务 | 个人 + 王磊 | 修复片区管理中的逻辑错误 |
| 4 | 参与“完善设备信息修改”功能 | 个人 + 王磊 | 补充设备信息修改功能,确保数据一致性与完整性 |
| 5 | 根据老师建议完善其他细节问题 | 个人 + 全体成员 | 根据会议讨论结果,完善相关系统细节 |
---
## 技术学习与实施重点
### 1. 终端关联与数据校验
- 学习如何在前端与后端协同实现设备关联的实时校验
- 掌握异常处理与用户友好提示的实现方法
### 2. 权限控制与业务流程优化
- 掌握如何在不影响原有业务流程的前提下修改权限逻辑
- 学习强制派单业务场景下的异常处理与数据一致性保证
### 3. 片区管理与业务逻辑修复
- 理解片区管理的核心业务逻辑与数据关系
- 掌握复杂业务逻辑的调试与修复方法
### 4. 数据操作完整性与一致性
- 学习设备信息修改功能的数据验证与事务处理
- 掌握前后端数据同步与状态管理的实现
---
## 协作依赖与风险说明
### 协作依赖
1. **技术方案依赖**
- 需要王磊提供相关模块的接口文档和业务逻辑说明
- 依赖团队对权限模型和业务规则的统一理解
2. **开发环境依赖**
- 需要稳定地开发环境和测试数据支持
- 依赖相关模块的接口稳定性
3. **沟通协调依赖**
- 需要定期与王磊同步开发进度和问题
- 依赖团队每日站会的有效沟通
### 风险说明
1. **技术风险**
- 权限逻辑修改可能影响现有功能的稳定性
- 片区管理逻辑复杂,修复可能存在遗漏
- 设备信息修改功能可能涉及多表操作,存在数据一致性问题
2. **时间风险**
- 多个任务并行可能导致时间分配紧张
- 复杂问题排查可能超出预期时间
3. **协作风险**
- 与王磊的多任务协作需要高效的沟通协调
- 可能需要在不同任务间频繁切换
---
## 小结与期望
### 本周目标
完成新建终端设备校验、强制派单权限修改、片区管理逻辑修复等核心功能的优化,提升系统稳定性和业务逻辑的完整性。
### 支持需求
1. 希望获得关于复杂业务逻辑调试方法的指导
2. 期待老师在权限系统设计和实现方面提供建议
3. 希望有关于数据一致性和事务处理的专题分享
4. 需要团队在技术方案评审和代码审查方面的支持

@ -0,0 +1,58 @@
# 个人周总结-第十四周
## 基本信息
**姓  名:** 张红卫
**团队名称:** 软1-汪汪队
**开始时间:** 2025-12-22
**结束时间:** 2025-12-28
---
## 本周任务完成情况
| 序号 | 计划内容 | 完成情况 | 说明与成果 |
|----|-------------|------|--------------------------------------------|
| 1 | 参与项目整体联调测试 | 已完成 | 顺利完成端到端联调,验证了各功能模块的协同运行,整体流程符合预设需求。 |
| 2 | 负责演示流程设计与准备 | 已完成 | 与罗月航协作完成演示流程设计,明确了演示步骤和人员分工。 |
| 3 | 功能问题修复与优化 | 部分完成 | 针对联调中发现的若干功能缺陷进行了修复,并对部分交互逻辑进行了优化,提升了用户体验。 |
---
## 技术学习与实践总结
### 1. 系统联调与测试
- 掌握了端到端联调的基本流程,包括环境搭建、接口对接、数据一致性验证等。
### 2. 演示准备与展示
- 学习了如何设计清晰、有层次的功能演示流程,突出了项目核心价值。
### 3. 性能优化与问题修复
- 通过实际修复过程,加深了对功能逻辑和代码结构的理解。
- 优化了部分界面响应速度,提升了用户操作的流畅度。
---
## 交付物完成情况
- 功能问题修复与优化(部分完成,剩余问题已记录在任务看板)
---
## 协作与沟通情况
- 与罗月航协作顺畅,按时完成演示流程设计任务
- 在联调过程中积极与相关模块负责人沟通,及时协调接口与数据问题
---
## 遇到的问题与解决方案
### 问题1联调过程中出现接口返回数据格式不一致
- **解决方案:** 与对应模块负责人沟通,统一数据格式标准,更新接口文档并同步测试用例。
---
### 建议与支持需求
1. 希望老师能在项目总结与答辩技巧方面给予指导
2. 建议安排一次关于项目部署与运维的分享
3. 需要团队在最终阶段保持高频沟通,确保项目顺利收尾

@ -80,7 +80,7 @@ public class SecurityConfig {
.requestMatchers("/api/common/register").permitAll()
.requestMatchers("/static/**", "/templates/**").permitAll()
.requestMatchers(request -> "OPTIONS".equals(request.getMethod())).permitAll()
.requestMatchers("/api/alerts/**").hasAnyRole("ADMIN", "REPAIRMAN")
.requestMatchers("/api/alerts/**").hasAnyRole("SUPER_ADMIN","AREA_ADMIN", "REPAIRMAN")
.requestMatchers("/api/app/student/**").hasAnyRole("STUDENT", "ADMIN")
.requestMatchers("/api/app/repair/**").hasAnyRole("REPAIRMAN", "ADMIN")
.requestMatchers("/api/web/**").hasAnyRole("SUPER_ADMIN", "AREA_ADMIN", "VIEWER","REPAIRMAN")

@ -1 +0,0 @@
# 本md仅用于初始化目录未创建所有子一级目录在当前目录创建文件后请自行删除

@ -20,12 +20,19 @@ import java.util.List;
@RequestMapping("/api/alerts")
@RequiredArgsConstructor
@Tag(name = "告警管理接口")
public class AlertController {
private final AlertRepository alertRepository;
@GetMapping("/test")
@PreAuthorize("hasAnyRole('SUPER_ADMIN','AREA_ADMIN', 'REPAIRMAN')")
public ResultVO<String> testAuth() {
return ResultVO.success("权限验证通过");
}
@GetMapping("/history")
@PreAuthorize("hasAnyRole('ADMIN', 'REPAIRMAN')")
@PreAuthorize("hasAnyRole('SUPER_ADMIN','AREA_ADMIN', 'REPAIRMAN')")
@Operation(summary = "分页查询告警历史(支持多条件筛选)")
public ResultVO<List<Alert>> getAlertHistory(
@Parameter(description = "设备ID可选") @RequestParam(required = false) String deviceId,
@ -57,18 +64,50 @@ public class AlertController {
/**
*
*/
// AlertController.java
@GetMapping("/pending")
@PreAuthorize("hasAnyRole('ADMIN', 'REPAIRMAN')")
@PreAuthorize("hasAnyRole('ROLE_SUPER_ADMIN','ROLE_AREA_ADMIN', 'ROLE_REPAIRMAN')") // 添加 ROLE_ 前缀
public ResultVO<List<Alert>> getPendingAlerts(
@Parameter(description = "区域ID可选") @RequestParam(required = false) String areaId) {
List<Alert> pendingAlerts = areaId != null
? alertRepository.findByAreaIdAndStatus(areaId, Alert.AlertStatus.pending)
: alertRepository.findByStatus(Alert.AlertStatus.pending);
// 按优先级排序(紧急在前)- 使用方法引用替代lambda
// 按优先级排序(紧急在前)
pendingAlerts.sort((a1, a2) ->
Integer.compare(a2.getAlertLevel().getPriority(), a1.getAlertLevel().getPriority()));
return ResultVO.success(pendingAlerts);
}
// 添加分页查询接口
@GetMapping("/all")
@PreAuthorize("hasAnyRole('SUPER_ADMIN','AREA_ADMIN', 'REPAIRMAN')")
@Operation(summary = "查询所有告警(支持多条件筛选)")
public ResultVO<List<Alert>> getAllAlerts(
@Parameter(description = "设备ID可选") @RequestParam(required = false) String deviceId,
@Parameter(description = "告警级别可选如error、critical") @RequestParam(required = false) String level,
@Parameter(description = "告警状态可选如pending、resolved") @RequestParam(required = false) String status,
@Parameter(description = "所属区域(维修人员仅能查询自己的区域)") @RequestParam(required = false) String areaId
) {
List<Alert> alerts;
if (deviceId != null) {
alerts = alertRepository.findByDeviceId(deviceId);
} else if (level != null) {
alerts = alertRepository.findByAlertLevel(Alert.AlertLevel.valueOf(level));
} else if (status != null) {
alerts = alertRepository.findByStatus(Alert.AlertStatus.valueOf(status));
} else if (areaId != null) {
alerts = alertRepository.findByAreaId(areaId);
} else {
alerts = alertRepository.findAll();
}
return ResultVO.success(alerts);
}
}

@ -1,19 +1,35 @@
package com.campus.water.controller;
import com.campus.water.entity.BusinessException;
import com.campus.water.util.ResultVO;
import org.springframework.security.access.AccessDeniedException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;
import java.time.format.DateTimeParseException;
import java.util.Objects;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
/**
* -
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
@ -26,6 +42,10 @@ public class GlobalExceptionHandler {
if (msg.contains("AlertLevel") || msg.contains("AlertStatus")) {
msg = "参数错误:告警级别可选值(info/warning/error/critical),告警状态可选值(pending/resolved/closed)";
}
// 设备ID格式错误特殊处理
if (msg.contains("设备ID") || msg.contains("deviceId")) {
msg = "设备ID格式错误正确格式为WM/WS开头+3位数字如WM001、WS123";
}
return ResultVO.error(400, "参数错误:" + msg);
}
@ -36,8 +56,15 @@ public class GlobalExceptionHandler {
public ResultVO<Void> handleTypeMismatch(MethodArgumentTypeMismatchException e) {
String errorMsg;
// 特殊处理时间格式错误(告警查询的时间参数)
if (e.getCause() instanceof DateTimeParseException) {
if (e.getCause() instanceof java.time.format.DateTimeParseException) {
errorMsg = "时间参数格式错误正确格式yyyy-MM-dd HH:mm:ss示例2025-12-05 10:30:00";
} else if (e.getRequiredType() != null && e.getRequiredType().isEnum()) {
// 枚举类型转换错误处理
errorMsg = String.format(
"参数[%s]枚举值错误,允许值:%s",
e.getName(),
getEnumValues(e.getRequiredType())
);
} else {
// 通用类型不匹配提示
errorMsg = String.format(
@ -51,21 +78,35 @@ public class GlobalExceptionHandler {
}
/**
* /访
* /访
*/
@ExceptionHandler(AccessDeniedException.class)
public ResultVO<Void> handleAccessDenied(AccessDeniedException e) {
return ResultVO.error(403, "权限不足:仅管理员/维修人员可访问告警相关功能");
@ExceptionHandler({AccessDeniedException.class, org.springframework.security.access.AccessDeniedException.class})
public ResultVO<Void> handleAccessDenied(Exception e) {
String roleMsg = "仅超级管理员可操作";
// 区分不同接口的权限提示
if (e.getMessage().contains("AREA_ADMIN")) {
roleMsg = "仅区域管理员及以上权限可操作";
} else if (e.getMessage().contains("REPAIRMAN")) {
roleMsg = "仅维修人员及管理员可操作";
}
return ResultVO.error(403, "权限不足:" + roleMsg);
}
/**
*
* /
*/
@ExceptionHandler(RuntimeException.class)
public ResultVO<Void> handleRuntimeException(RuntimeException e) {
// 生产环境建议添加日志记录,此处简化
// log.error("服务器运行时异常", e);
return ResultVO.error(500, "服务器内部错误:" + e.getMessage());
@ExceptionHandler(NoSuchElementException.class)
public ResultVO<Void> handleNoSuchElement(NoSuchElementException e) {
String msg = e.getMessage();
// 标准化资源不存在提示
if (msg.contains("设备")) {
return ResultVO.error(404, "设备不存在:" + msg.replace("No value present", "").trim());
} else if (msg.contains("管理员") || msg.contains("Admin")) {
return ResultVO.error(404, "管理员不存在:" + msg.replace("No value present", "").trim());
} else if (msg.contains("区域") || msg.contains("Area")) {
return ResultVO.error(404, "区域不存在:" + msg.replace("No value present", "").trim());
}
return ResultVO.error(404, "请求的资源不存在:" + msg);
}
/**
@ -73,8 +114,176 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVO<Void> handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
// 获取第一个验证失败的字段和消息
String errorMsg = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage();
return ResultVO.badRequest(errorMsg); // 返回400状态码和具体错误信息
// 收集所有验证失败的字段和消息
List<String> errorMessages = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + "" + error.getDefaultMessage())
.collect(Collectors.toList());
return ResultVO.error(400, "参数验证失败:" + String.join("", errorMessages));
}
/**
* @RequestBody
*/
@ExceptionHandler(BindException.class)
public ResultVO<Void> handleBindException(BindException e) {
FieldError firstError = e.getBindingResult().getFieldError();
String errorMsg = firstError != null ?
firstError.getField() + "" + firstError.getDefaultMessage() :
"参数绑定失败";
return ResultVO.error(400, "表单参数错误:" + errorMsg);
}
/**
*
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResultVO<Void> handleMissingParam(MissingServletRequestParameterException e) {
return ResultVO.error(400,
String.format("缺少必填参数:%s类型%s",
e.getParameterName(),
e.getParameterType()));
}
/**
*
*/
@ExceptionHandler(DuplicateKeyException.class)
public ResultVO<Void> handleDuplicateKey(DuplicateKeyException e) {
log.error("数据库唯一约束冲突", e);
String msg = "数据已存在,无法重复添加";
// 针对设备ID冲突特殊处理
if (e.getMessage().contains("device_id")) {
msg = "设备ID已存在请更换设备ID后重试";
} else if (e.getMessage().contains("admin_name")) {
msg = "管理员用户名已存在,请更换用户名";
}
return ResultVO.error(409, msg);
}
/**
*
*/
@ExceptionHandler(DataIntegrityViolationException.class)
public ResultVO<Void> handleDataIntegrityViolation(DataIntegrityViolationException e) {
log.error("数据库完整性约束异常", e);
String msg = "数据操作失败,可能存在关联数据";
if (e.getMessage().contains("foreign key constraint")) {
msg = "无法删除,该数据已被其他记录关联引用";
} else if (e.getMessage().contains("not null")) {
msg = "必填字段不能为空,请检查输入";
}
return ResultVO.error(400, msg);
}
/**
* JSON
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResultVO<Void> handleHttpMessageNotReadable(HttpMessageNotReadableException e) {
log.error("请求体解析失败", e);
String msg = "请求数据格式错误请检查JSON格式是否正确";
if (e.getMessage().contains("date-time")) {
msg = "日期时间格式错误正确格式yyyy-MM-dd HH:mm:ss";
}
return ResultVO.error(400, msg);
}
/**
* HTTP
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResultVO<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e) {
return ResultVO.error(405,
String.format("不支持的请求方法:%s支持的方法%s",
e.getMethod(),
String.join(",", e.getSupportedMethods())));
}
/**
*
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResultVO<Void> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException e) {
return ResultVO.error(415,
String.format("不支持的媒体类型:%s支持的类型%s",
e.getContentType(),
e.getSupportedMediaTypes()));
}
/**
*
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResultVO<Void> handleMaxUploadSizeExceeded(MaxUploadSizeExceededException e) {
long maxSizeMB = e.getMaxUploadSize() / (1024 * 1024);
return ResultVO.error(413,
String.format("文件大小超限,最大支持:%dMB", maxSizeMB));
}
/**
* IO
*/
@ExceptionHandler(IOException.class)
public ResultVO<Void> handleIOException(IOException e) {
log.error("IO操作异常", e);
String msg = "文件操作失败:" + e.getMessage();
if (e.getMessage().contains("Permission denied")) {
msg = "文件操作权限不足";
}
return ResultVO.error(500, msg);
}
/**
*
*/
@ExceptionHandler(BusinessException.class)
public ResultVO<Void> handleBusinessException(BusinessException e) {
// 业务异常自带状态码和消息
return ResultVO.error(e.getCode(), e.getMessage());
}
/**
* 404
*/
@ExceptionHandler(NoHandlerFoundException.class)
public ResultVO<Void> handleNoHandlerFound(NoHandlerFoundException e) {
return ResultVO.error(404,
String.format("请求的接口不存在:%s %s",
e.getHttpMethod(),
e.getRequestURL()));
}
/**
*
*/
@ExceptionHandler(RuntimeException.class)
public ResultVO<Void> handleRuntimeException(RuntimeException e) {
log.error("服务器运行时异常", e);
// 生产环境可根据异常类型返回更友好的提示
String msg = "服务器内部错误:" + e.getMessage();
// 对常见运行时异常进行特殊处理
if (e instanceof NullPointerException) {
msg = "系统处理异常:数据为空";
} else if (e instanceof IndexOutOfBoundsException) {
msg = "系统处理异常:数据索引越界";
}
return ResultVO.error(500, msg);
}
/**
*
*/
private String getEnumValues(Class<?> enumClass) {
if (!enumClass.isEnum()) {
return "未知";
}
StringBuilder values = new StringBuilder();
for (Object enumConstant : enumClass.getEnumConstants()) {
values.append(enumConstant).append(",");
}
if (values.length() > 0) {
values.deleteCharAt(values.length() - 1);
}
return values.toString();
}
}

@ -0,0 +1,66 @@
package com.campus.water.controller;
import com.campus.water.entity.Notification;
import com.campus.water.service.NotificationService;
import com.campus.water.util.ResultVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
*
* APP
*/
@RestController
@RequestMapping("/api/app/repairman/notification")
@RequiredArgsConstructor
@Tag(name = "维修人员通知接口", description = "维修人员APP端通知查询/已读标记接口")
public class RepairmanNotificationController {
private final NotificationService notificationService;
/**
*
*/
@GetMapping("/unread")
@Operation(summary = "获取未读通知", description = "查询维修人员所有未读的派单/系统通知")
public ResultVO<List<Notification>> getUnreadNotifications(@RequestParam String repairmanId) {
try {
List<Notification> unreadNotifications = notificationService.getUnreadNotifications(repairmanId);
return ResultVO.success(unreadNotifications, "获取未读通知成功");
} catch (Exception e) {
return ResultVO.error(500, "获取未读通知失败:" + e.getMessage());
}
}
/**
*
*/
@GetMapping("/all")
@Operation(summary = "获取所有通知", description = "查询维修人员所有通知(已读+未读)")
public ResultVO<List<Notification>> getAllNotifications(@RequestParam String repairmanId) {
try {
List<Notification> allNotifications = notificationService.getAllNotifications(repairmanId);
return ResultVO.success(allNotifications, "获取所有通知成功");
} catch (Exception e) {
return ResultVO.error(500, "获取所有通知失败:" + e.getMessage());
}
}
/**
*
*/
@PostMapping("/read")
@Operation(summary = "标记通知为已读", description = "将指定通知标记为已读状态")
public ResultVO<Boolean> markNotificationAsRead(@RequestParam Long notificationId) {
try {
notificationService.markAsRead(notificationId);
return ResultVO.success(true, "标记通知为已读成功");
} catch (Exception e) {
return ResultVO.error(500, "标记通知为已读失败:" + e.getMessage());
}
}
}

@ -0,0 +1,56 @@
package com.campus.water.controller;
import com.campus.water.entity.dto.request.StudentDrinkQueryDTO;
import com.campus.water.entity.vo.StudentDrinkStatsVO;
import com.campus.water.service.StudentDrinkStatsService;
import com.campus.water.util.ResultVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
*/
@RestController
@RequestMapping("/api/student/drink-stats")
@RequiredArgsConstructor
@Tag(name = "学生端-饮水量统计", description = "学生查看本日/本周/本月饮水量")
public class StudentDrinkStatsController {
private final StudentDrinkStatsService drinkStatsService;
@PostMapping("/today")
@Operation(summary = "查询本日饮水量", description = "获取学生当日的饮水量、次数及明细")
public ResultVO<StudentDrinkStatsVO> getTodayStats(@RequestBody StudentDrinkQueryDTO request) {
// 手动校验学生ID非空
if (request.getStudentId() == null || request.getStudentId().trim().isEmpty()) {
return ResultVO.badRequest("学生ID不能为空");
}
StudentDrinkStatsVO stats = drinkStatsService.getTodayDrinkStats(request.getStudentId());
return ResultVO.success(stats, "查询本日饮水量成功");
}
@PostMapping("/this-week")
@Operation(summary = "查询本周饮水量", description = "获取学生本周的饮水量、日均量及每日明细")
public ResultVO<StudentDrinkStatsVO> getThisWeekStats(@RequestBody StudentDrinkQueryDTO request) {
if (request.getStudentId() == null || request.getStudentId().trim().isEmpty()) {
return ResultVO.badRequest("学生ID不能为空");
}
StudentDrinkStatsVO stats = drinkStatsService.getThisWeekDrinkStats(request.getStudentId());
return ResultVO.success(stats, "查询本周饮水量成功");
}
@PostMapping("/this-month")
@Operation(summary = "查询本月饮水量", description = "获取学生本月的饮水量、日均量及每日明细")
public ResultVO<StudentDrinkStatsVO> getThisMonthStats(@RequestBody StudentDrinkQueryDTO request) {
if (request.getStudentId() == null || request.getStudentId().trim().isEmpty()) {
return ResultVO.badRequest("学生ID不能为空");
}
StudentDrinkStatsVO stats = drinkStatsService.getThisMonthDrinkStats(request.getStudentId());
return ResultVO.success(stats, "查询本月饮水量成功");
}
}

@ -0,0 +1,39 @@
package com.campus.water.controller;
import com.campus.water.entity.vo.TerminalLocationVO;
import com.campus.water.service.WaterTerminalLocationService;
import com.campus.water.util.ResultVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Terminallocation
*/
@RestController
@RequestMapping("/api/student/terminal/location")
@RequiredArgsConstructor
@Tag(name = "学生端-终端位置接口", description = "饮水机地图位置查询")
public class StudentTerminalLocationController {
private final WaterTerminalLocationService waterTerminalLocationService; // 截图原命名注入
@GetMapping("/all")
@Operation(summary = "获取所有终端位置")
public ResultVO<List<TerminalLocationVO>> getAllLocations() {
List<TerminalLocationVO> list = waterTerminalLocationService.getAllTerminalLocations();
return ResultVO.success(list, "获取所有终端位置成功");
}
@GetMapping("/available")
@Operation(summary = "获取可用终端位置")
public ResultVO<List<TerminalLocationVO>> getAvailableLocations() {
List<TerminalLocationVO> list = waterTerminalLocationService.getAvailableTerminalLocations();
return ResultVO.success(list, "获取可用终端位置成功");
}
}

@ -0,0 +1,33 @@
package com.campus.water.controller;
import com.campus.water.service.StudentWaterDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
*
* /
*/
@RestController
@RequestMapping("/api/water/realtime")
public class WaterRealtimeController {
@Autowired
private StudentWaterDataService waterDataService;
/**
* +
* GET http://localhost:8080/api/water/realtime/TERM001
* @param terminalId ID
* @return +
*/
@GetMapping("/{terminalId}")
public Map<String, Object> getRealtimeData(@PathVariable String terminalId) {
return waterDataService.queryRealtimeData(terminalId);
}
}

@ -2,8 +2,10 @@ package com.campus.water.controller;
import com.campus.water.entity.*;
import com.campus.water.mapper.*;
import com.campus.water.util.ResultVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@ -12,7 +14,8 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Controller
@RestController
@RequestMapping("/api/water-usage")
public class WaterUsageController {
@Autowired
@ -31,23 +34,25 @@ public class WaterUsageController {
private TerminalUsageStatsRepository terminalUsageStatsRepository;
// 扫码用水
@PostMapping("/scan")
@PreAuthorize("hasAnyRole('STUDENT', 'SUPER_ADMIN', 'AREA_ADMIN', 'VIEWER')")
@Transactional
public Map<String, Object> scanToDrink(String terminalId, String studentId, Double waterConsumption) {
public ResultVO<Map<String, Object>> scanToDrink(
@RequestParam String terminalId,
@RequestParam String studentId,
@RequestParam Double waterConsumption) {
Map<String, Object> result = new HashMap<>();
try {
Optional<DeviceTerminalMapping> mappingOpt = deviceTerminalMappingRepository.findByTerminalId(terminalId);
if (mappingOpt.isEmpty()) {
result.put("success", false);
result.put("message", "终端设备不存在");
return result;
return ResultVO.error("终端设备不存在");
}
DeviceTerminalMapping mapping = mappingOpt.get();
if (mapping.getTerminalStatus() != DeviceTerminalMapping.TerminalStatus.active) {
result.put("success", false);
result.put("message", "终端设备未激活");
return result;
return ResultVO.error("终端设备未激活");
}
Optional<WaterMakerRealtimeData> realtimeDataOpt =
@ -58,7 +63,7 @@ public class WaterUsageController {
drinkRecord.setTerminalId(terminalId);
drinkRecord.setDeviceId(mapping.getDeviceId());
// 错误1修复Double转BigDecimal适配DrinkRecord的BigDecimal类型字段
// Double转BigDecimal适配DrinkRecord的BigDecimal类型字段
drinkRecord.setWaterConsumption(waterConsumption != null ? BigDecimal.valueOf(waterConsumption) : BigDecimal.ZERO);
drinkRecord.setDrinkTime(LocalDateTime.now());
drinkRecord.setLocation(mapping.getTerminalName());
@ -73,16 +78,15 @@ public class WaterUsageController {
// 传入BigDecimal类型的用水量
updateTerminalUsageStats(terminalId, BigDecimal.valueOf(waterConsumption));
result.put("success", true);
result.put("message", "用水成功");
result.put("waterConsumption", waterConsumption);
result.put("terminalName", mapping.getTerminalName());
result.put("deviceId", mapping.getDeviceId());
result.put("timestamp", LocalDateTime.now());
return result;
return ResultVO.success(result, "用水成功");
} catch (Exception e) {
result.put("success", false);
result.put("message", "用水失败: " + e.getMessage());
return result;
return ResultVO.error("用水失败: " + e.getMessage());
}
}
@ -97,10 +101,10 @@ public class WaterUsageController {
stats = statsOpt.get();
stats.setUsageCount(stats.getUsageCount() + 1);
// 错误2&3修复BigDecimal加法替代+运算符)
// BigDecimal加法替代+运算符)
stats.setTotalWaterOutput(stats.getTotalWaterOutput().add(waterConsumption));
// 错误4修复BigDecimal除法替代/运算符,指定精度和舍入模式)
// BigDecimal除法指定精度和舍入模式
stats.setAvgWaterPerUse(
stats.getTotalWaterOutput()
.divide(BigDecimal.valueOf(stats.getUsageCount()), 2, BigDecimal.ROUND_HALF_UP)
@ -111,7 +115,7 @@ public class WaterUsageController {
stats.setStatDate(now.toLocalDate());
stats.setUsageCount(1);
// 错误5&6修复直接赋值BigDecimal适配TerminalUsageStats的BigDecimal字段
// 直接赋值BigDecimal适配TerminalUsageStats的BigDecimal字段
stats.setTotalWaterOutput(waterConsumption);
stats.setAvgWaterPerUse(waterConsumption);
stats.setPeakHour(String.format("%02d:00", now.getHour()));
@ -122,7 +126,9 @@ public class WaterUsageController {
}
// 获取水质信息
public Map<String, Object> getWaterQualityInfo(String deviceId) {
@GetMapping("/quality/{deviceId}")
@PreAuthorize("hasAnyRole('STUDENT', 'SUPER_ADMIN', 'AREA_ADMIN', 'VIEWER')")
public ResultVO<Map<String, Object>> getWaterQualityInfo(@PathVariable String deviceId) {
Map<String, Object> result = new HashMap<>();
try {
@ -135,7 +141,7 @@ public class WaterUsageController {
if (realtimeDataOpt.isPresent()) {
WaterMakerRealtimeData realtimeData = realtimeDataOpt.get();
result.put("deviceId", deviceId);
// 如需返回Double给前端BigDecimal转Double
// BigDecimal转Double返回给前端
result.put("rawWaterTds", realtimeData.getTdsValue1() != null ? realtimeData.getTdsValue1().doubleValue() : null);
result.put("pureWaterTds", realtimeData.getTdsValue2() != null ? realtimeData.getTdsValue2().doubleValue() : null);
result.put("mineralWaterTds", realtimeData.getTdsValue3() != null ? realtimeData.getTdsValue3().doubleValue() : null);
@ -150,18 +156,17 @@ public class WaterUsageController {
result.put("lastDetectionTime", qualityHistory.getDetectedTime());
}
result.put("success", true);
return result;
return ResultVO.success(result);
} catch (Exception e) {
result.put("success", false);
result.put("message", "获取水质信息失败: " + e.getMessage());
return result;
return ResultVO.error("获取水质信息失败: " + e.getMessage());
}
}
// 获取终端设备信息
public Map<String, Object> getTerminalInfo(String terminalId) {
@GetMapping("/terminal/{terminalId}")
@PreAuthorize("hasAnyRole('STUDENT', 'SUPER_ADMIN', 'AREA_ADMIN', 'VIEWER')")
public ResultVO<Map<String, Object>> getTerminalInfo(@PathVariable String terminalId) {
Map<String, Object> result = new HashMap<>();
Optional<DeviceTerminalMapping> mappingOpt = deviceTerminalMappingRepository.findByTerminalId(terminalId);
@ -171,15 +176,16 @@ public class WaterUsageController {
result.put("terminalName", mapping.getTerminalName());
result.put("deviceId", mapping.getDeviceId());
result.put("status", mapping.getTerminalStatus());
result.put("success", true);
Map<String, Object> qualityInfo = getWaterQualityInfo(mapping.getDeviceId());
result.putAll(qualityInfo);
// 获取水质信息
ResultVO<Map<String, Object>> qualityResult = getWaterQualityInfo(mapping.getDeviceId());
if (qualityResult.getCode() == 200 && qualityResult.getData() != null) {
result.putAll(qualityResult.getData());
}
return ResultVO.success(result);
} else {
result.put("success", false);
result.put("message", "终端设备不存在");
return ResultVO.error("终端设备不存在");
}
return result;
}
}

@ -3,12 +3,15 @@ package com.campus.water.controller;
import com.campus.water.entity.WorkOrder;
import com.campus.water.service.WorkOrderService;
import com.campus.water.util.ResultVO;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
@RestController
@ -70,15 +73,20 @@ public class WorkOrderController {
}
}
// 新增:审核工单接口(管理员专用)
// 1. 创建请求体类
@Data
public static class ReviewOrderRequest {
private String orderId;
private boolean approved;
}
// 审核工单
// 2. 修改接口接收方式
@PostMapping("/review")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')")
public ResultVO<Boolean> reviewOrder(
@RequestParam String orderId,
@RequestParam boolean approved) {
public ResultVO<Boolean> reviewOrder(@RequestBody ReviewOrderRequest request) {
try {
boolean result = workOrderService.reviewOrder(orderId, approved);
return result ? ResultVO.success(true, approved ? "审核通过" : "审核不通过")
boolean result = workOrderService.reviewOrder(request.getOrderId(), request.isApproved());
return result ? ResultVO.success(true, request.isApproved() ? "审核通过" : "审核不通过")
: ResultVO.error(400, "审核失败,工单状态异常");
} catch (Exception e) {
return ResultVO.error(500, "审核失败:" + e.getMessage());
@ -145,6 +153,23 @@ public class WorkOrderController {
}
}
// 新增:按时间范围(+可选区域/状态查询工单复用已有Repository方法
@GetMapping("/by-time-range")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')")
public ResultVO<List<WorkOrder>> getOrdersByTimeRange(
@RequestParam(required = false) String areaId, // 可选区域ID
@RequestParam(required = false) WorkOrder.OrderStatus status, // 可选:工单状态
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime, // 必选:开始时间
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime) { // 必选:结束时间
try {
// 直接调用Service复用已有Repository的findByCreatedTimeBetween方法
List<WorkOrder> orders = workOrderService.getOrdersByConditions(areaId, status, startTime, endTime);
return ResultVO.success(orders);
} catch (Exception e) {
return ResultVO.error(500, "按时间范围查询工单失败:" + e.getMessage());
}
}
// 获取维修工自己的工单 - 维修人员和管理员可访问
@GetMapping("/my")
@PreAuthorize("hasAnyRole('REPAIRMAN', 'SUPER_ADMIN', 'AREA_ADMIN')")
@ -157,18 +182,51 @@ public class WorkOrderController {
}
}
// 管理员手动派单接口
// ========== 关键修改:管理员手动派单接口 ==========
// 1. 创建静态内部类接收JSON请求体参数解决Type definition error
@Data
public static class AssignOrderRequest {
private String orderId;
private String repairmanId;
}
// 2. 派单接口改为接收@RequestBody
@PostMapping("/assign")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')")
public ResultVO<Boolean> assignOrderByAdmin(
@RequestParam String orderId,
@RequestParam String repairmanId) {
public ResultVO<Boolean> assignOrderByAdmin(@RequestBody AssignOrderRequest request) {
try {
boolean result = workOrderService.assignOrderByAdmin(orderId, repairmanId);
// 参数非空校验
if (request.getOrderId() == null || request.getOrderId().trim().isEmpty()) {
return ResultVO.error(400, "工单ID不能为空");
}
if (request.getRepairmanId() == null || request.getRepairmanId().trim().isEmpty()) {
return ResultVO.error(400, "维修人员ID不能为空");
}
boolean result = workOrderService.assignOrderByAdmin(request.getOrderId(), request.getRepairmanId());
return result ? ResultVO.success(true, "派单成功")
: ResultVO.error(400, "派单失败,工单或维修人员状态异常");
} catch (IllegalArgumentException e) {
// 捕获参数非法异常返回400
return ResultVO.error(400, "派单失败:" + e.getMessage());
} catch (Exception e) {
// 捕获其他异常返回500并打印日志建议添加日志
e.printStackTrace(); // 生产环境替换为logger.error
return ResultVO.error(500, "派单失败:" + e.getMessage());
}
}
// 获取单个工单详情 - 管理员和维修人员均可访问
@GetMapping("/{orderId}")
@PreAuthorize("hasAnyRole('REPAIRMAN', 'SUPER_ADMIN', 'AREA_ADMIN')")
public ResultVO<WorkOrder> getOrderDetail(@PathVariable String orderId) {
try {
WorkOrder order = workOrderService.getOrderDetail(orderId);
return ResultVO.success(order);
} catch (Exception e) {
return ResultVO.error(500, "获取工单详情失败:" + e.getMessage());
}
}
}

@ -1 +0,0 @@
# 本md仅用于初始化目录未创建所有子一级目录在当前目录创建文件后请自行删除

@ -1 +0,0 @@
# 本md仅用于初始化目录未创建所有子一级目录在当前目录创建文件后请自行删除

@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -39,6 +40,39 @@ public class AdminController {
}
}
/**
*
*/
@GetMapping("/available-area-admins")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')")
@Operation(summary = "获取可分配校区的区域管理员", description = "返回未负责任何片区的区域管理员,用于片区绑定负责人")
public ResponseEntity<ResultVO<List<Admin>>> getAvailableAreaAdmins() {
try {
List<Admin> availableAdmins = adminService.getAvailableAreaAdmins();
return ResponseEntity.ok(ResultVO.success(availableAdmins));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "查询失败:" + e.getMessage()));
}
}
/**
*
*/
@GetMapping("/by-area/{areaId}")
@PreAuthorize("hasRole('SUPER_ADMIN')") // 只有超级管理员可以查看
@Operation(summary = "按区域查询管理员", description = "查询指定区域下的所有管理员")
public ResponseEntity<ResultVO<List<Admin>>> getAdminsByArea(
@PathVariable String areaId
) {
try {
List<Admin> admins = adminService.getAdminsByAreaId(areaId);
return ResponseEntity.ok(ResultVO.success(admins));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "查询失败:" + e.getMessage()));
}
}
/**
*
*/
@ -56,12 +90,19 @@ public class AdminController {
/**
* /
*
*/
@PostMapping("/save")
@PreAuthorize("hasRole('SUPER_ADMIN')") // 仅超级管理员可新增/编辑
@Operation(summary = "保存管理员", description = "新增/编辑管理员,支持指定角色")
@PreAuthorize("hasRole('SUPER_ADMIN')")
@Operation(summary = "保存管理员", description = "新增/编辑管理员,区域管理员必须指定areaId")
public ResponseEntity<ResultVO<Admin>> saveAdmin(@RequestBody Admin admin) {
// 实现保持不变
try {
if (admin.getAdminName() == null || admin.getAdminName().trim().isEmpty()) {
return ResponseEntity.ok(ResultVO.error(400, "管理员姓名不能为空"));
}
Admin savedAdmin = adminService.saveAdmin(admin);
return ResponseEntity.ok(ResultVO.success(savedAdmin));
} catch (Exception e) {
@ -100,4 +141,92 @@ public class AdminController {
return ResponseEntity.ok(ResultVO.error(401, "用户名或密码错误"));
}
}
/**
*
* /
*/
@PostMapping("/profile/update")
@PreAuthorize("isAuthenticated()") // 只要登录即可访问
@Operation(summary = "修改个人信息", description = "当前登录管理员修改自己的基本信息(不含角色)")
public ResponseEntity<ResultVO<Admin>> updateProfile(
@RequestBody Admin profile,
Authentication authentication) {
try {
// 1. 获取当前登录用户名
String currentUsername = authentication.getName();
// 2. 验证身份一致性(当前用户只能修改自己的信息)
Admin currentAdmin = adminService.getAdminByName(currentUsername)
.orElseThrow(() -> new RuntimeException("当前用户信息不存在"));
if (!currentAdmin.getAdminId().equals(profile.getAdminId())) {
throw new RuntimeException("无权修改其他管理员信息");
}
// 3. 过滤敏感字段不允许修改角色和区域ID
profile.setRole(currentAdmin.getRole());
profile.setAreaId(currentAdmin.getAreaId());
// 4. 调用服务层更新
Admin updatedAdmin = adminService.updateProfile(profile);
return ResponseEntity.ok(ResultVO.success(updatedAdmin, "个人信息更新成功"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "更新失败:" + e.getMessage()));
}
}
/**
*
*/
@GetMapping("/current")
@PreAuthorize("isAuthenticated()") // 只要登录即可访问
@Operation(summary = "获取当前登录管理员信息", description = "返回当前登录管理员的完整信息(含角色、区域等)")
public ResponseEntity<ResultVO<Admin>> getCurrentAdmin(Authentication authentication) {
try {
// 1. 从Authentication中获取当前登录用户名
String currentUsername = authentication.getName();
// 2. 调用服务层查询完整管理员信息
Admin currentAdmin = adminService.getAdminByName(currentUsername)
.orElseThrow(() -> new RuntimeException("当前登录用户信息不存在"));
return ResponseEntity.ok(ResultVO.success(currentAdmin));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "获取当前用户信息失败:" + e.getMessage()));
}
}
/**
*
*
*/
@PostMapping("/password/update")
@PreAuthorize("isAuthenticated()") // 登录即可访问
@Operation(summary = "修改密码", description = "当前登录管理员修改自己的密码(需验证原密码)")
public ResponseEntity<ResultVO<Void>> updatePassword(
@RequestParam String oldPassword,
@RequestParam String newPassword,
Authentication authentication) {
try {
// 1. 获取当前登录用户名
String currentUsername = authentication.getName();
// 2. 验证原密码并更新新密码
boolean success = adminService.updatePassword(currentUsername, oldPassword, newPassword);
if (success) {
return ResponseEntity.ok(ResultVO.success(null, "密码修改成功"));
} else {
return ResponseEntity.ok(ResultVO.error(400, "原密码验证失败"));
}
} catch (IllegalArgumentException e) {
// 处理新密码格式错误等参数问题
return ResponseEntity.ok(ResultVO.error(400, e.getMessage()));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "密码修改失败:" + e.getMessage()));
}
}
}

@ -0,0 +1,167 @@
package com.campus.water.controller.web;
import com.campus.water.entity.Area;
import com.campus.water.service.AreaService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* 使 ResultVO ResponseEntity
* Area areaId -
*/
@RestController
@RequestMapping("/api/web/area") // 统一管理端接口前缀:/api/web
@CrossOrigin // 允许跨域(前端调用时需要)
public class AreaController {
private final AreaService areaService;
// 构造器注入
public AreaController(AreaService areaService) {
this.areaService = areaService;
}
/**
*
* @param code 200400/500
* @param msg
* @param data
* @return Map
*/
private Map<String, Object> buildResponse(int code, String msg, Object data) {
Map<String, Object> response = new HashMap<>();
response.put("code", code);
response.put("msg", msg);
response.put("data", data);
return response;
}
/**
* /
* /
*/
@PostMapping("/add")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')") // 补充权限注解
public ResponseEntity<Map<String, Object>> addArea(@RequestBody Area area) {
try {
Area savedArea = areaService.addArea(area);
return ResponseEntity.ok(buildResponse(200, "新增成功", savedArea));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(buildResponse(400, e.getMessage(), null));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(buildResponse(500, "新增区域失败:" + e.getMessage(), null));
}
}
/**
*
* /
*/
@PutMapping("/update/{areaId}")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')") // 补充权限注解
public ResponseEntity<Map<String, Object>> updateArea(@PathVariable String areaId, @RequestBody Area area) {
try {
Area updatedArea = areaService.updateArea(areaId, area);
return ResponseEntity.ok(buildResponse(200, "修改成功", updatedArea));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(buildResponse(400, e.getMessage(), null));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(buildResponse(500, "修改区域失败:" + e.getMessage(), null));
}
}
/**
*
*
*/
@DeleteMapping("/delete/{areaId}")
@PreAuthorize("hasRole('SUPER_ADMIN')") // 补充权限注解(删除权限更严格)
public ResponseEntity<Map<String, Object>> deleteArea(@PathVariable String areaId) {
try {
areaService.deleteArea(areaId);
return ResponseEntity.ok(buildResponse(200, "删除成功", null));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(buildResponse(400, e.getMessage(), null));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(buildResponse(500, "删除区域失败:" + e.getMessage(), null));
}
}
/**
*
* //
*/
@GetMapping("/cities")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN', 'REPAIRMAN')") // 补充权限注解
public ResponseEntity<Map<String, Object>> getAllCities() {
try {
List<Area> cities = areaService.getAllCities();
return ResponseEntity.ok(buildResponse(200, "查询成功", cities));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(buildResponse(500, "查询市区列表失败:" + e.getMessage(), null));
}
}
/**
*
* /
*/
@GetMapping("/without-manager")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')")
public ResponseEntity<Map<String, Object>> getAreasWithoutManager() {
try {
List<Area> areas = areaService.getAreasWithoutManager();
return ResponseEntity.ok(buildResponse(200, "查询无负责人片区成功", areas));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(buildResponse(500, "查询无负责人片区失败:" + e.getMessage(), null));
}
}
/**
* ID
* //
*/
@GetMapping("/campuses/{cityId}")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN', 'REPAIRMAN')") // 补充权限注解
public ResponseEntity<Map<String, Object>> getCampusesByCityId(@PathVariable String cityId) {
try {
List<Area> campuses = areaService.getCampusesByCityId(cityId);
return ResponseEntity.ok(buildResponse(200, "查询成功", campuses));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(buildResponse(400, e.getMessage(), null));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(buildResponse(500, "查询校园列表失败:" + e.getMessage(), null));
}
}
/**
* ID
* //
*/
@GetMapping("/{areaId}")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN', 'REPAIRMAN')") // 补充权限注解
public ResponseEntity<Map<String, Object>> getAreaById(@PathVariable String areaId) {
try {
Area area = areaService.getAreaById(areaId);
return ResponseEntity.ok(buildResponse(200, "查询成功", area));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(buildResponse(400, e.getMessage(), null));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(buildResponse(500, "查询区域详情失败:" + e.getMessage(), null));
}
}
}

@ -1,22 +1,30 @@
package com.campus.water.controller.web;
import com.campus.water.entity.Device;
import com.campus.water.entity.RepairerAuth;
import com.campus.water.mapper.RepairerAuthRepository;
import com.campus.water.mapper.WaterMakerRealtimeDataRepository;
import com.campus.water.mapper.WaterSupplyRealtimeDataRepository;
import com.campus.water.service.DeviceService;
import com.campus.water.entity.Repairman;
import com.campus.water.mapper.RepairmanRepository;
import com.campus.water.service.DeviceStatusService;
import com.campus.water.util.ResultVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.access.prepost.PreAuthorize;
import java.util.List; // 新增List的导入语句
import com.campus.water.entity.Device;
import com.campus.water.entity.RepairerAuth;
import com.campus.water.entity.Repairman;
import com.campus.water.entity.WaterMakerRealtimeData;
import com.campus.water.entity.WaterSupplyRealtimeData;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("/api/web/device")
@ -25,8 +33,14 @@ import java.util.List; // 新增List的导入语句
public class DeviceController {
private final DeviceService deviceService;
private final DeviceStatusService deviceStatusService;
private final RepairmanRepository repairmanRepository;
private final RepairerAuthRepository repairerAuthRepository;
@Autowired
private WaterMakerRealtimeDataRepository waterMakerRealtimeDataRepository;
@Autowired
private WaterSupplyRealtimeDataRepository waterSupplyRealtimeDataRepository;
/**
*
@ -87,7 +101,6 @@ public class DeviceController {
/**
*
*/
// 在DeviceController.java中修改getAreaDevicesByTypeForRepairman方法
@GetMapping("/repairman/area-devices-by-type")
@PreAuthorize("hasRole('REPAIRMAN')")
@Operation(summary = "维修人员查询辖区设备(按类型)", description = "维修人员查看本辖区内指定类型的设备列表")
@ -123,18 +136,121 @@ public class DeviceController {
}
}
/**
* ID
*/
@GetMapping("/{deviceId}")
@Operation(summary = "查询设备详情", description = "根据设备ID获取设备的详细信息")
public ResponseEntity<ResultVO<Device>> getDeviceDetail(@PathVariable String deviceId) {
@Operation(summary = "查询设备详情", description = "根据设备ID获取设备的详细信息及实时数据")
public ResponseEntity<ResultVO<Map<String, Object>>> getDeviceDetail(@PathVariable String deviceId) {
try {
// 1. 获取设备基本信息
Device device = deviceService.getDeviceById(deviceId);
return ResponseEntity.ok(ResultVO.success(device, "设备查询成功"));
// 2. 构建返回结果Map
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("deviceInfo", device);
// 3. 根据设备类型查询对应实时数据
if (Device.DeviceType.water_maker.equals(device.getDeviceType())) {
// 制水机实时数据
Optional<WaterMakerRealtimeData> realtimeData = waterMakerRealtimeDataRepository.findLatestByDeviceId(deviceId);
realtimeData.ifPresent(data -> resultMap.put("realtimeData", data));
} else if (Device.DeviceType.water_supply.equals(device.getDeviceType())) {
// 供水机实时数据
Optional<WaterSupplyRealtimeData> realtimeData = waterSupplyRealtimeDataRepository.findLatestByDeviceId(deviceId);
realtimeData.ifPresent(data -> resultMap.put("realtimeData", data));
}
return ResponseEntity.ok(ResultVO.success(resultMap, "设备查询成功"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "设备查询失败: " + e.getMessage()));
}
}
public DeviceStatusService getDeviceStatusService() {
return deviceStatusService;
}
/**
*
* /
*/
/* @GetMapping("/by-status")
@Operation(summary = "按状态查询设备", description = "根据设备状态筛选设备列表,可选区域筛选")
public ResponseEntity<ResultVO<List<Device>>> getDevicesByStatus(
@RequestParam String status,
@RequestParam(required = false) String areaId) {
try {
List<Device> devices = deviceStatusService.getDevicesByStatusWithArea(status, areaId);
return ResponseEntity.ok(ResultVO.success(devices, "按状态查询设备成功"));
} catch (IllegalArgumentException e) {
return ResponseEntity.ok(ResultVO.error(400, e.getMessage()));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "按状态查询设备失败: " + e.getMessage()));
}
}*/
/**
*
* /
*/
/* @GetMapping("/by-type")
@Operation(summary = "按类型查询设备", description = "根据设备类型筛选设备列表,可选区域筛选")
public ResponseEntity<ResultVO<List<Device>>> getDevicesByType(
@RequestParam String deviceType,
@RequestParam(required = false) String areaId) {
try {
List<Device> devices = deviceStatusService.getDevicesByTypeWithArea(deviceType, areaId);
return ResponseEntity.ok(ResultVO.success(devices, "按类型查询设备成功"));
} catch (IllegalArgumentException e) {
return ResponseEntity.ok(ResultVO.error(400, e.getMessage()));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "按类型查询设备失败: " + e.getMessage()));
}
}*/
// ========== 新增1获取所有片区列表假设从Device表中提取唯一片区ID若有Area实体可直接查询 ==========
@GetMapping("/areas")
@Operation(summary = "获取所有片区列表", description = "返回系统中所有已配置的片区ID和相关信息")
public ResponseEntity<ResultVO<List<String>>> getAllAreas() {
try {
// 从设备表中提取唯一的片区ID若有独立Area表可替换为AreaRepository查询
List<Device> allDevices = deviceService.queryDevices(null, null, null);
List<String> areaList = allDevices.stream()
.map(Device::getAreaId)
.filter(areaId -> areaId != null && !areaId.trim().isEmpty())
.distinct()
.toList();
return ResponseEntity.ok(ResultVO.success(areaList, "片区列表查询成功"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "片区列表查询失败: " + e.getMessage()));
}
}
// ========== 新增2根据片区ID查询该片区的供水机列表 ==========
@GetMapping("/area/{areaId}/water-supplies")
@Operation(summary = "查询片区内供水机", description = "根据片区ID获取该片区下所有可用的供水机")
public ResponseEntity<ResultVO<List<Device>>> getWaterSuppliesByArea(@PathVariable String areaId) {
try {
List<Device> waterSupplies = deviceService.getWaterSuppliesByArea(areaId);
return ResponseEntity.ok(ResultVO.success(waterSupplies, "片区供水机查询成功"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "片区供水机查询失败: " + e.getMessage()));
}
}
// ========== 新增:管理员编辑设备基本信息接口 ==========
@PutMapping("/edit")
@PreAuthorize("hasAnyRole( 'SUPER_ADMIN','AREA_ADMIN')") // 限制仅管理员/超级管理员可访问
@Operation(summary = "编辑设备基本信息", description = "管理员更新设备名称、类型、安装位置等基本信息(不含设备状态、创建时间)")
public ResponseEntity<ResultVO<Device>> editDevice(@Valid @RequestBody Device device) {
try {
// 校验设备ID不能为空编辑必须指定设备ID
if (device.getDeviceId() == null || device.getDeviceId().trim().isEmpty()) {
return ResponseEntity.ok(ResultVO.error(400, "设备ID不能为空"));
}
Device updatedDevice = deviceService.updateDeviceInfo(device);
return ResponseEntity.ok(ResultVO.success(updatedDevice, "设备信息编辑成功"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "设备信息编辑失败: " + e.getMessage()));
}
}
}

@ -6,7 +6,7 @@
* 1.
* 2. 线/线/
* 3.
* 4.
* 4. /
* 5. 线线
* 6. 线
*
@ -22,6 +22,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -99,24 +100,47 @@ public class DeviceStatusController {
}
}
// ========== 替换原有复合查询接口,拆分为两个独立接口 ==========
/**
*
*/
@GetMapping("/by-status")
@Operation(summary = "按状态查询设备", description = "根据状态和设备类型查询设备列表")
@PreAuthorize("hasAnyRole('SUPER_ADMIN','AREA_ADMIN')")
@Operation(summary = "按状态查询设备", description = "根据设备状态筛选设备列表,可选区域筛选")
public ResponseEntity<ResultVO<List<Device>>> getDevicesByStatus(
@RequestParam String status,
@RequestParam(required = false) String areaId,
@RequestParam(required = false) String deviceType) { // 保留设备类型参数,去除默认值
@RequestParam(required = false) String areaId) {
try {
// 调用服务层方法时传递所有参数包括可能为null的deviceType
List<Device> devices = deviceStatusService.getDevicesByStatus(status, areaId, deviceType);
return ResponseEntity.ok(ResultVO.success(devices));
List<Device> devices = deviceStatusService.getDevicesByStatusWithArea(status, areaId);
return ResponseEntity.ok(ResultVO.success(devices, "按状态查询设备成功"));
} catch (IllegalArgumentException e) {
return ResponseEntity.ok(ResultVO.error(400, e.getMessage()));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "查询设备失败: " + e.getMessage()));
return ResponseEntity.ok(ResultVO.error(500, "按状态查询设备失败: " + e.getMessage()));
}
}
/**
*
*/
@GetMapping("/by-type")
@PreAuthorize("hasAnyRole('SUPER_ADMIN','AREA_ADMIN')")
@Operation(summary = "按类型查询设备", description = "根据设备类型筛选设备列表,可选区域筛选")
public ResponseEntity<ResultVO<List<Device>>> getDevicesByType(
@RequestParam String deviceType,
@RequestParam(required = false) String areaId) {
try {
List<Device> devices = deviceStatusService.getDevicesByTypeWithArea(deviceType, areaId);
return ResponseEntity.ok(ResultVO.success(devices, "按类型查询设备成功"));
} catch (IllegalArgumentException e) {
return ResponseEntity.ok(ResultVO.error(400, e.getMessage()));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "按类型查询设备失败: " + e.getMessage()));
}
}
@GetMapping("/status-count")
@PreAuthorize("hasAnyRole('SUPER_ADMIN','AREA_ADMIN')")
@Operation(summary = "设备状态数量统计", description = "统计各状态设备数量")
public ResponseEntity<ResultVO<Map<String, Object>>> getDeviceStatusCount(
@RequestParam(required = false) String areaId,

@ -0,0 +1,114 @@
// java/com/campus/water/controller/web/TerminalController.java
package com.campus.water.controller.web;
import com.campus.water.service.DeviceService;
import com.campus.water.service.TerminalService;
import com.campus.water.entity.vo.TerminalManageVO;
import com.campus.water.util.ResultVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/web/terminal")
@RequiredArgsConstructor
@Tag(name = "终端管理接口", description = "管理员基于设备终端映射表/终端位置表的增删改查操作")
public class TerminalController {
private final TerminalService terminalService;
@PostMapping("/add")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')")
@Operation(summary = "新增终端", description = "同时保存终端位置、基础映射信息和片区信息")
public ResponseEntity<ResultVO<TerminalManageVO>> addTerminal(@Valid @RequestBody TerminalManageVO terminalVO) {
try {
TerminalManageVO newTerminal = terminalService.addTerminal(terminalVO);
return ResponseEntity.ok(ResultVO.success(newTerminal, "终端新增成功"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "终端新增失败: " + e.getMessage()));
}
}
@PutMapping("/update")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')")
@Operation(summary = "更新终端", description = "支持更新终端名称、状态、经纬度、片区等信息")
public ResponseEntity<ResultVO<TerminalManageVO>> updateTerminal(@Valid @RequestBody TerminalManageVO terminalVO) {
try {
TerminalManageVO updated = terminalService.updateTerminal(terminalVO);
return ResponseEntity.ok(ResultVO.success(updated, "终端更新成功"));
} catch (Exception e) {
return ResponseEntity.ok(ResultVO.error(500, "终端更新失败: " + e.getMessage()));
}
}
/**
*
*
*/
@DeleteMapping("/delete/{terminalId}")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')")
@Operation(summary = "删除终端", description = "先校验设备绑定状态,再级联删除相关数据")
public ResponseEntity<ResultVO<String>> deleteTerminal(@PathVariable String terminalId) {
try {
// 调用服务层删除终端
terminalService.deleteTerminal(terminalId);
// 用ResultVO封装成功结果返回提示信息
return ResponseEntity.ok(ResultVO.success("终端删除成功"));
} catch (Exception e) {
// 用ResultVO封装错误结果携带异常信息
return ResponseEntity.ok(ResultVO.error(500, "终端删除失败: " + e.getMessage()));
}
}
/**
*
* IDareaId
*/
@GetMapping("/{terminalId}")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')")
@Operation(summary = "查询终端详情", description = "根据终端ID获取整合后的完整信息")
public ResponseEntity<ResultVO<TerminalManageVO>> getTerminal(@PathVariable String terminalId) {
try {
// 调用服务层查询终端详情返回包含areaId的VO
TerminalManageVO terminal = terminalService.getTerminalById(terminalId);
// 用ResultVO封装成功结果自定义提示信息
return ResponseEntity.ok(ResultVO.success(terminal, "终端查询成功"));
} catch (Exception e) {
// 用ResultVO封装404错误结果携带异常信息
return ResponseEntity.ok(ResultVO.notFound("终端查询失败: " + e.getMessage()));
}
}
/**
*
* areaId
*/
@GetMapping("/list")
@PreAuthorize("hasAnyRole('SUPER_ADMIN', 'AREA_ADMIN')")
@Operation(summary = "查询终端列表", description = "支持按终端名称模糊筛选")
public ResponseEntity<ResultVO<List<TerminalManageVO>>> getTerminalList(
@RequestParam(required = false) String terminalName) {
try {
// 调用服务层查询终端列表返回包含areaId的VO列表
List<TerminalManageVO> terminalList = terminalService.getTerminalList(terminalName);
// 用ResultVO封装成功结果自定义提示信息
return ResponseEntity.ok(ResultVO.success(terminalList, "终端列表查询成功"));
} catch (Exception e) {
// 用ResultVO封装错误结果携带异常信息
return ResponseEntity.ok(ResultVO.error(500, "终端列表查询失败: " + e.getMessage()));
}
}
}

@ -1 +0,0 @@
# 本md仅用于初始化目录未创建所有子一级目录在当前目录创建文件后请自行删除

@ -25,6 +25,10 @@ public class Admin {
@Column(name = "phone", length = 20)
private String phone;
// 新增管理员负责的区域ID区域管理员专用
@Column(name = "area_id", length = 36, nullable = true)
private String areaId;
// 恢复三个角色枚举
@Enumerated(EnumType.STRING)
@Column(name = "role", length = 50, nullable = false)

@ -31,7 +31,7 @@ public class Alert {
@Column(name = "alert_message", columnDefinition = "TEXT")
private String alertMessage;
@Column(name = "area_id", length = 20)
@Column(name = "area_id", length = 36)
private String areaId;
@Enumerated(EnumType.STRING)

@ -8,13 +8,16 @@ package com.campus.water.entity;
import lombok.Data;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import org.hibernate.annotations.GenericGenerator;
@Data
@Entity
@Table(name = "area")
public class Area {
@Id
@Column(name = "area_id", length = 20)
@GeneratedValue(generator = "uuid") // 新增自动生成UUID
@GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = "area_id", length = 36)
private String areaId;
@Column(name = "area_name", length = 100)
@ -24,7 +27,7 @@ public class Area {
@Column(name = "area_type", length = 50)
private AreaType areaType;
@Column(name = "parent_area_id", length = 20)
@Column(name = "parent_area_id", length = 36)
private String parentAreaId;
@Column(length = 200)
@ -42,7 +45,22 @@ public class Area {
@Column(name = "updated_time")
private LocalDateTime updatedTime = LocalDateTime.now();
// 枚举值与数据库完全匹配zone=市区campus=校园building=楼栋(暂时保留)
public enum AreaType {
campus, building, zone
zone("市区"), // 对应数据库的zone含义是市区
campus("校园"); // 对应数据库的campus含义是校园
private final String desc;
AreaType(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
}

@ -0,0 +1,55 @@
package com.campus.water.entity; // 请确保这个包名与你的项目结构一致
import org.springframework.http.HttpStatus;
/**
*
* <p>
*
*
* </p>
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* HTTP
*/
private int code;
/**
*
*/
private String message;
/**
*
* @param code
* @param message
*/
public BusinessException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
/**
* 使 400 Bad Request
* @param message
*/
public BusinessException(String message) {
this(HttpStatus.BAD_REQUEST.value(), message);
}
// --- Getters ---
public int getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}

@ -24,7 +24,7 @@ public class Device {
@Column(name = "device_type", length = 50)
private DeviceType deviceType;
@Column(name = "area_id", length = 20)
@Column(name = "area_id", length = 36)
private String areaId;
@Column(name = "install_location", length = 200)

@ -22,6 +22,9 @@ public class DeviceTerminalMapping {
@Column(name = "device_id", length = 20)
private String deviceId;
@Column(name = "area_id", length = 36)
private String areaId;
@Column(name = "terminal_id", length = 20)
private String terminalId;

@ -0,0 +1,51 @@
package com.campus.water.entity;
import lombok.Data;
import jakarta.persistence.*;
import java.time.LocalDateTime;
/**
*
*
*/
@Data
@Entity
@Table(name = "notification")
public class Notification {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** 维修人员ID */
@Column(name = "repairman_id", nullable = false, length = 50)
private String repairmanId;
/** 关联工单ID */
@Column(name = "order_id", length = 50)
private String orderId;
/** 通知内容 */
@Column(name = "content", nullable = false, length = 500)
private String content;
/** 是否已读(默认未读) */
@Column(name = "is_read")
private boolean isRead = false;
/** 创建时间 */
@Column(name = "created_time", nullable = false)
private LocalDateTime createdTime = LocalDateTime.now();
/** 通知类型 */
@Enumerated(EnumType.STRING)
@Column(name = "type", nullable = false, length = 20)
private NotificationType type;
/** 通知类型枚举 */
public enum NotificationType {
ORDER_ASSIGNED, // 派单通知
ORDER_GRABBED, // 抢单通知
ORDER_REJECTED, // 拒单通知
SYSTEM // 系统通知
}
}

@ -25,7 +25,7 @@ public class Repairman {
@Column(length = 20)
private String phone;
@Column(name = "area_id", length = 20)
@Column(name = "area_id", length = 36)
private String areaId;
@Column(name = "skills", length = 200)

@ -0,0 +1,33 @@
package com.campus.water.entity;
import java.math.BigDecimal;
import lombok.Data;
import jakarta.persistence.*;
/**
*
* /
*/
@Data
@Entity
@Table(name = "water_terminal_location")
public class WaterTerminalLocation {
/**
* IDterminal_id20
*/
@Id
@Column(name = "terminal_id", length = 20, nullable = false)
private String terminalId;
/**
* GCJ-02
*/
@Column(name = "longitude", nullable = false, columnDefinition = "DECIMAL(10,6)")
private BigDecimal longitude;
/**
* GCJ-02
*/
@Column(name = "latitude", nullable = false, columnDefinition = "DECIMAL(10,6)")
private BigDecimal latitude;
}

@ -23,7 +23,7 @@ public class WorkOrder {
@Column(name = "device_id", length = 20)
private String deviceId;
@Column(name = "area_id", length = 20)
@Column(name = "area_id", length = 36)
private String areaId;
@Enumerated(EnumType.STRING)

@ -40,4 +40,6 @@ public class RegisterRequest {
// 用户(学生)特有字段
private String studentId;
private String studentName;
}

@ -0,0 +1,12 @@
package com.campus.water.entity.dto.request;
import lombok.Data;
/**
* DTO
*/
@Data
public class StudentDrinkQueryDTO {
/** 学生ID */
private String studentId;
}

@ -0,0 +1,16 @@
package com.campus.water.entity.vo;
import lombok.Data;
/**
* VO
*/
@Data
public class DailyDrinkVO {
/** 日期yyyy-MM-dd */
private String date;
/** 当日饮水量(升) */
private Double consumption;
/** 当日饮水次数 */
private Integer count;
}

@ -0,0 +1,28 @@
package com.campus.water.entity.vo;
import lombok.Data;
import java.util.List;
import com.campus.water.entity.DrinkRecord;
import com.campus.water.entity.vo.DailyDrinkVO;
/**
* VO
*/
@Data
public class StudentDrinkStatsVO {
/** 学生ID */
private String studentId;
/** 统计维度(本日/本周/本月) */
private String timeDimension;
/** 统计时间范围(如"2025-12-25~2025-12-25" */
private String timeRange;
/** 总饮水量(升) */
private Double totalConsumption;
/** 日均饮水量(升) */
private Double avgDailyConsumption;
/** 饮水次数 */
private Integer drinkCount;
/** 按日期分组的每日饮水量明细 */
private List<DailyDrinkVO> dailyDetails;
/** 所有饮水记录明细 */
private List<DrinkRecord> drinkRecords;
}

@ -0,0 +1,17 @@
package com.campus.water.entity.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* VO
*/
@Data
public class TerminalLocationVO {
private String terminalId;
private String terminalName;
private BigDecimal longitude;
private BigDecimal latitude;
private Boolean isAvailable;
private String deviceStatus;
}

@ -0,0 +1,37 @@
// java/com/campus/water/vo/TerminalManageVO.java
package com.campus.water.entity.vo;
import com.campus.water.entity.DeviceTerminalMapping;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* VO
*/
@Data
public class TerminalManageVO {
// 终端核心标识(关联两张表的主键/外键)
private String terminalId;
// 终端名称(来自映射表)
private String terminalName;
// 终端经纬度(来自位置表)
private BigDecimal longitude;
private BigDecimal latitude;
// 终端状态(来自映射表)
private DeviceTerminalMapping.TerminalStatus terminalStatus;
// 安装日期(来自映射表)
private LocalDate installDate;
// 设备ID关联的设备来自映射表
private String deviceId;
// ========== 新增片区ID字段前端传递选中的片区ID ==========
private String areaId;
}

@ -1 +0,0 @@
# 本md仅用于初始化目录未创建所有子一级目录在当前目录创建文件后请自行删除

@ -2,6 +2,7 @@ package com.campus.water.mapper;
import com.campus.water.entity.Admin;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@ -21,12 +22,28 @@ public interface AdminRepository extends JpaRepository<Admin, String> {
// 按手机号查询
Optional<Admin> findByPhone(String phone);
// 新增按区域ID查询管理员
List<Admin> findByAreaId(String areaId);
// 按角色查询管理员(核心:恢复角色筛选)
List<Admin> findByRole(Admin.AdminRole role);
// 按姓名+角色组合查询(可选,增强筛选)
List<Admin> findByAdminNameContainingAndRole(String name, Admin.AdminRole role);
// 新增查询未负责任何片区的区域管理员role=ROLE_AREA_ADMIN 且 areaId=null
List<Admin> findByRoleAndAreaIdIsNull(Admin.AdminRole role);
// 新增1查询指定校区的区域管理员精准查询用于单个校区权限校验
List<Admin> findByRoleAndAreaId(Admin.AdminRole role, String areaId);
// 新增2查询所有校区关联的区域管理员排除市区用于管理员列表筛选
// 备注:此处使用@Query注解关联Area表过滤区域类型为campus的管理员
@Query("SELECT a FROM Admin a WHERE a.role = ?1 AND a.areaId IN " +
"(SELECT ar.areaId FROM Area ar WHERE ar.areaType = com.campus.water.entity.Area.AreaType.campus)")
List<Admin> findAllAreaAdminsForCampus(Admin.AdminRole role);
// 检查唯一约束
boolean existsByAdminId(String adminId);
boolean existsByPhone(String phone);

@ -58,4 +58,6 @@ public interface AlertRepository extends JpaRepository<Alert, Long> {
List<Alert.AlertStatus> activeStatus,
LocalDateTime timestamp
);
List<Alert> findByResolvedByAndStatus(String repairmanId, Alert.AlertStatus alertStatus);
}

@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface AreaRepository extends JpaRepository<Area, String> {
@ -14,6 +15,18 @@ public interface AreaRepository extends JpaRepository<Area, String> {
// 根据父区域ID查询子区域
List<Area> findByParentAreaId(String parentAreaId);
// 新增按区域类型查询(按创建时间倒序)
List<Area> findByAreaTypeOrderByCreatedTimeDesc(Area.AreaType areaType);
// 按父级ID+类型查询(如查询某校园下的所有楼宇)
List<Area> findByParentAreaIdAndAreaType(String parentAreaId, Area.AreaType areaType);
// 按名称模糊查询
List<Area> findByAreaNameContaining(String keyword);
// 查询所有(按创建时间倒序)
List<Area> findAllByOrderByCreatedTimeDesc();
// 根据管理员姓名查询区域
List<Area> findByManager(String manager);
@ -23,4 +36,24 @@ public interface AreaRepository extends JpaRepository<Area, String> {
// 查询指定类型的根级区域
@Query("SELECT a FROM Area a WHERE a.areaType = ?1 AND a.parentAreaId IS NULL")
List<Area> findRootAreasByType(Area.AreaType areaType);
// 移除所有含isDeleted的方法实体无该属性
// Optional<Area> findByIdAndIsDeletedFalse(String id); // 已移除
// List<Area> findByAreaTypeAndParentAreaIdIsNullAndIsDeletedFalse(Area.AreaType areaType); // 已移除
// List<Area> findByParentAreaIdAndAreaTypeAndIsDeletedFalse(String parentAreaId, Area.AreaType areaType); // 已移除
// long countByParentAreaIdAndIsDeletedFalse(String parentAreaId); // 已移除
// boolean existsByIdAndIsDeletedFalse(String id); // 已移除
// 保留原有正确方法
Optional<Area> findByAreaId(String areaId);
long countByParentAreaId(String areaId);
List<Area> findByAreaTypeAndParentAreaIdIsNull(Area.AreaType areaType);
boolean existsByAreaId(String cityId);
/**
* managernull
*
*/
List<Area> findByAreaTypeAndManagerIsNullOrManagerEquals(Area.AreaType areaType, String emptyStr);
}

@ -42,6 +42,12 @@ public interface DeviceRepository extends JpaRepository<Device, String> {
// 根据制水机ID查询关联的供水机
List<Device> findByParentMakerIdAndDeviceType(String parentMakerId, Device.DeviceType deviceType);
// 按状态和区域查询(无设备类型筛选)
// 按状态加载设备(支持区域筛选)
List<Device> findByStatusAndAreaId(Device.DeviceStatus status, String areaId);
// 按设备类型加载加载设备(支持区域筛选)
List<Device> findByDeviceTypeAndAreaId(Device.DeviceType deviceType, String areaId);
}

@ -19,4 +19,17 @@ public interface DeviceTerminalMappingRepository extends JpaRepository<DeviceTer
// 根据设备和终端ID精确查找映射
Optional<DeviceTerminalMapping> findByDeviceIdAndTerminalId(String deviceId, String terminalId);
// ========== 新增必要方法(支撑终端增删改查业务) ==========
// 1. 判断终端是否已绑定设备(删除终端时的核心校验)
boolean existsByTerminalId(String terminalId);
// 2. 按终端名称模糊查询(终端列表筛选)
List<DeviceTerminalMapping> findByTerminalNameContaining(String terminalName);
// 3. 按终端ID删除所有关联映射删除终端时级联清理映射数据
void deleteByTerminalId(String terminalId);
}

@ -0,0 +1,22 @@
package com.campus.water.mapper;
import com.campus.water.entity.Notification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 访
*/
@Repository
public interface NotificationRepository extends JpaRepository<Notification, Long> {
/**
*
*/
List<Notification> findByRepairmanIdAndIsReadFalseOrderByCreatedTimeDesc(String repairmanId);
/**
*
*/
List<Notification> findByRepairmanIdOrderByCreatedTimeDesc(String repairmanId);
}

@ -0,0 +1,13 @@
package com.campus.water.mapper;
import com.campus.water.entity.WaterTerminalLocation;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* 访isAvailable
*/
@Repository
public interface WaterTerminalLocationRepository extends JpaRepository<WaterTerminalLocation, String> {
// 原findByIsAvailable方法删除因实体已无该字段筛选逻辑移至Service层
}

@ -43,4 +43,16 @@ public interface WorkOrderRepository extends JpaRepository<WorkOrder, String> {
// 根据创建人查询工单
List<WorkOrder> findByCreatedBy(String createdBy);
//获取处于某些状态的工单
List<WorkOrder> findByStatusIn(List<WorkOrder.OrderStatus> list);
/**
*
* @param repairmanId ID
* @param statuses
* @return
*/
long countByAssignedRepairmanIdAndStatusIn(String repairmanId, List<WorkOrder.OrderStatus> statuses);
}

@ -1 +0,0 @@
# 本md仅用于初始化目录未创建所有子一级目录在当前目录创建文件后请自行删除

@ -1 +0,0 @@
# 本md仅用于初始化目录未创建所有子一级目录在当前目录创建文件后请自行删除

@ -1,7 +1,9 @@
package com.campus.water.service;
import com.campus.water.entity.Admin;
import com.campus.water.entity.Area;
import com.campus.water.mapper.AdminRepository;
import com.campus.water.mapper.AreaRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
@ -16,6 +18,8 @@ import java.util.Optional;
public class AdminService {
private final AdminRepository adminRepository;
private final AreaRepository areaRepository; // 新增注入
@Autowired
private PasswordEncoder passwordEncoder;
@ -38,6 +42,25 @@ public class AdminService {
}
}
/**
*
*
*/
public List<Admin> getAvailableAreaAdmins() {
return adminRepository.findByRoleAndAreaIdIsNull(Admin.AdminRole.ROLE_AREA_ADMIN);
}
/**
*
*/
public List<Admin> getAdminsByAreaId(String areaId) {
// 校验区域是否存在
if (!areaRepository.existsById(areaId)) {
throw new RuntimeException("区域不存在:" + areaId);
}
return adminRepository.findByAreaId(areaId);
}
/**
* ID
*/
@ -46,16 +69,43 @@ public class AdminService {
}
/**
* /
*/
public Admin saveAdmin(Admin admin) {
admin.setUpdatedTime(LocalDateTime.now());
if (admin.getCreatedTime() == null) {
admin.setCreatedTime(LocalDateTime.now());
* /
*
*/
public Admin saveAdmin(Admin admin) {
admin.setUpdatedTime(LocalDateTime.now());
if (admin.getCreatedTime() == null) {
admin.setCreatedTime(LocalDateTime.now());
}
// 区域管理员ROLE_AREA_ADMIN的专属校验逻辑
if (admin.getRole() == Admin.AdminRole.ROLE_AREA_ADMIN) {
// 1. 若未填写区域IDnull或空字符串直接放行支持先创建管理员后续补填
if (admin.getAreaId() == null || admin.getAreaId().trim().isEmpty()) {
admin.setAreaId(null); // 统一置为null避免空字符串冗余数据
// 无需校验,直接允许保存
} else {
// 2. 若填写了区域ID进行严格校验区域存在 + 类型为校区(禁止市区)
String areaId = admin.getAreaId().trim();
// 校验区域是否存在
Area targetArea = areaRepository.findById(areaId)
.orElseThrow(() -> new RuntimeException("关联的区域不存在:" + areaId));
// 核心校验:仅允许关联校区,禁止关联市区
if (Area.AreaType.zone.equals(targetArea.getAreaType())) {
throw new RuntimeException("区域管理员仅允许关联校区,不能关联市区,请重新选择");
}
// 校验通过保留填写的合法校区ID
admin.setAreaId(areaId);
}
return adminRepository.save(admin);
} else {
// 非区域管理员清空区域ID避免冗余数据
admin.setAreaId(null);
}
return adminRepository.save(admin);
}
/**
*
*/
@ -77,4 +127,84 @@ public class AdminService {
public Admin.AdminRole[] getAllRoles() {
return Admin.AdminRole.values();
}
public AreaRepository getAreaRepository() {
return areaRepository;
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
/**
*
*/
public Admin updateProfile(Admin profile) {
// 1. 获取数据库中原始信息
Admin existingAdmin = adminRepository.findByAdminId(profile.getAdminId())
.orElseThrow(() -> new RuntimeException("管理员不存在"));
// 2. 仅更新允许修改的字段(排除角色、区域等敏感信息)
existingAdmin.setAdminName(profile.getAdminName());
existingAdmin.setPhone(profile.getPhone());
existingAdmin.setUpdatedTime(LocalDateTime.now());
// 3. 密码修改单独处理(如果有密码更新需求)
if (profile.getPassword() != null && !profile.getPassword().isEmpty()) {
existingAdmin.setPassword(passwordEncoder.encode(profile.getPassword()));
}
return adminRepository.save(existingAdmin);
}
/**
*
*/
public Optional<Admin> getAdminByName(String username) {
return adminRepository.findByAdminName(username);
}
/**
*
* @param username
* @param oldPassword
* @param newPassword
* @return
*/
public boolean updatePassword(String username, String oldPassword, String newPassword) {
// 1. 校验参数合法性
if (oldPassword == null || oldPassword.trim().isEmpty()) {
throw new IllegalArgumentException("原密码不能为空");
}
if (newPassword == null || newPassword.trim().isEmpty()) {
throw new IllegalArgumentException("新密码不能为空");
}
if (oldPassword.equals(newPassword)) {
throw new IllegalArgumentException("新密码不能与原密码一致");
}
// 可选:新密码复杂度校验(增强安全性,根据项目需求调整)
if (newPassword.length() < 6 || newPassword.length() > 20) {
throw new IllegalArgumentException("新密码长度必须在6-20位之间");
}
// 2. 根据用户名查询当前管理员信息
Admin existingAdmin = adminRepository.findByAdminName(username)
.orElseThrow(() -> new RuntimeException("管理员不存在"));
// 3. 验证原密码是否正确(使用项目已有的 PasswordEncoder 进行匹配)
boolean oldPasswordMatch = passwordEncoder.matches(oldPassword, existingAdmin.getPassword());
if (!oldPasswordMatch) {
return false; // 原密码错误,返回修改失败
}
// 4. 加密新密码并更新管理员信息
String encodedNewPassword = passwordEncoder.encode(newPassword);
existingAdmin.setPassword(encodedNewPassword);
existingAdmin.setUpdatedTime(LocalDateTime.now()); // 更新修改时间,保持与其他方法一致
// 5. 保存到数据库
adminRepository.save(existingAdmin);
return true;
}
}

@ -12,7 +12,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.campus.water.service.AlertPushService;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
@ -184,11 +184,13 @@ public class AlertTriggerService {
// 2. 创建对应类型的工单inspection类型不通过告警触发
WorkOrder workOrder = new WorkOrder();
workOrder.setOrderId(generateOrderId());
workOrder.setAlertId(alert.getAlertId());
workOrder.setDeviceId(deviceId);
workOrder.setAreaId(areaId);
workOrder.setOrderType(orderType); // 动态设置工单类型
workOrder.setDescription(message);
workOrder.setStatus(WorkOrder.OrderStatus.pending);
workOrder.setDeadline(LocalDateTime.now().plusHours(24));// 创建工单时设置截止时间例如24小时后
workOrder.setCreatedTime(LocalDateTime.now());
workOrderRepository.save(workOrder);
log.info("创建工单成功 | 工单ID{} | 设备ID{} | 工单类型:{}",
@ -199,7 +201,7 @@ public class AlertTriggerService {
*
* pending/processing
*/
private boolean isDuplicateAlert(String deviceId, String alertType) {
public boolean isDuplicateAlert(String deviceId, String alertType) {
LocalDateTime before = LocalDateTime.now().minusMinutes(ALERT_DUPLICATE_INTERVAL);
// 检查未处理的告警状态pending/processing
List<Alert.AlertStatus> activeStatus = Arrays.asList(
@ -215,13 +217,23 @@ public class AlertTriggerService {
}
/**
* ID
* ID
*/
private String getDeviceAreaId(String deviceId) {
Optional<Device> deviceOpt = deviceRepository.findById(deviceId);
return deviceOpt.map(Device::getAreaId).orElse("unknown");
public String getDeviceAreaId(String deviceId) {
try {
Optional<Device> deviceOpt = deviceRepository.findById(deviceId);
String areaId = deviceOpt.map(Device::getAreaId).orElse(null);
// 空值兜底null/空字符串→unknown
if (areaId == null || areaId.trim().isEmpty()) {
areaId = "unknown";
log.warn("设备{}的area_id为空/设备不存在兜底为unknown", deviceId);
}
return areaId;
} catch (Exception e) { // 捕获所有数据库异常
log.error("获取设备{}的area_id失败数据库异常", deviceId, e);
return "unknown"; // 异常时兜底
}
}
/**
* IDWO++
*/

@ -0,0 +1,245 @@
package com.campus.water.service;
import com.campus.water.entity.Area;
import com.campus.water.mapper.AreaRepository;
import com.campus.water.entity.Admin;
import com.campus.water.mapper.AdminRepository;
import com.campus.water.mapper.DeviceRepository;
import com.campus.water.mapper.DeviceTerminalMappingRepository;
import com.campus.water.security.RoleConstants;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Optional;
/**
*
* Area areaId address/manager
*
*/
@Service
public class AreaService {
private final AreaRepository areaRepository;
// 新增:管理员仓库注入
private final AdminRepository adminRepository;
// 新增注入现有设备、终端映射Repository无新增Mapper方法
private final DeviceRepository deviceRepository;
private final DeviceTerminalMappingRepository deviceTerminalMappingRepository;
public AreaService(AreaRepository areaRepository, AdminRepository adminRepository,
DeviceRepository deviceRepository,
DeviceTerminalMappingRepository deviceTerminalMappingRepository) {
this.areaRepository = areaRepository;
this.adminRepository = adminRepository;
this.deviceRepository = deviceRepository;
this.deviceTerminalMappingRepository = deviceTerminalMappingRepository;
}
/**
*
* @param area ID
* @return
*/
@Transactional(rollbackFor = Exception.class)
public Area addArea(Area area) {
// 1. 基础参数校验
validateBaseParams(area);
// 2. 层级规则校验(核心)
validateAreaHierarchy(area);
// 3. 新增管理员关联校验若传入manager管理员ID则校验并预绑定
validateAndPrepareAdmin(area);
// 4. 补充基础字段createdTime/updatedTime 已默认赋值,可手动刷新)
area.setCreatedTime(LocalDateTime.now());
area.setUpdatedTime(LocalDateTime.now());
// 5. 保存数据
Area savedArea = areaRepository.save(area);
// 6. 新增完成区域与管理员的最终绑定需使用保存后的区域ID
bindAdminToArea(area.getManager(), savedArea.getAreaId());
// 7. 返回保存后的区域对象
return savedArea;
}
/**
*
* @param areaId ID
* @param area
* @return
*/
@Transactional(rollbackFor = Exception.class)
public Area updateArea(String areaId, Area area) {
// 1. 校验区域是否存在
Area existingArea = areaRepository.findByAreaId(areaId)
.orElseThrow(() -> new RuntimeException("区域不存在ID" + areaId));
// 2. 基础参数校验
validateBaseParams(area);
// 3. 层级规则校验(修改时需保持层级规则)
validateAreaHierarchy(area);
// 4. 覆盖可修改字段(适配你的实体类所有字段)
existingArea.setAreaName(area.getAreaName());
existingArea.setAreaType(area.getAreaType());
// 父级ID市区不允许修改校园必须指向市区
if (Area.AreaType.campus.equals(area.getAreaType())) {
existingArea.setParentAreaId(area.getParentAreaId());
}
// 新增字段赋值
existingArea.setAddress(area.getAddress());
existingArea.setManager(area.getManager());
existingArea.setManagerPhone(area.getManagerPhone());
// 更新时间
existingArea.setUpdatedTime(LocalDateTime.now());
// 5. 保存修改
return areaRepository.save(existingArea);
}
/**
*
* @param areaId ID
*/
@Transactional(rollbackFor = Exception.class)
public void deleteArea(String areaId) {
// 1. 校验区域是否存在
Area existingArea = areaRepository.findByAreaId(areaId)
.orElseThrow(() -> new RuntimeException("区域不存在ID" + areaId));
// 2. 校验删除规则:若为市区,需先删除其下所有校园
if (Area.AreaType.zone.equals(existingArea.getAreaType())) {
long campusCount = areaRepository.countByParentAreaId(areaId);
if (campusCount > 0) {
throw new RuntimeException("该市区下仍有 " + campusCount + " 个校园,无法删除,请先删除下属校园");
}
}
// 3. 物理删除(若需逻辑删除,可参考后续说明添加 isDeleted 字段)
areaRepository.delete(existingArea);
}
/**
*
* @return
*/
public List<Area> getAllCities() {
return areaRepository.findByAreaTypeAndParentAreaIdIsNull(Area.AreaType.zone);
}
/**
*
*/
public List<Area> getAreasWithoutManager() {
// 调用仓库新增方法,限定:区域类型=campus负责人=null 或 空字符串
return areaRepository.findByAreaTypeAndManagerIsNullOrManagerEquals(
Area.AreaType.campus, // 仅筛选校区
"" // 匹配空字符串的负责人
);
}
/**
* ID
* @param cityId IDareaId
* @return
*/
public List<Area> getCampusesByCityId(String cityId) {
// 校验市区是否存在
if (!areaRepository.existsByAreaId(cityId)) {
throw new RuntimeException("市区不存在ID" + cityId);
}
return areaRepository.findByParentAreaIdAndAreaType(cityId, Area.AreaType.campus );
}
/**
*
* @param area
*/
private void validateBaseParams(Area area) {
if (area.getAreaName() == null || area.getAreaName().trim().isEmpty()) {
throw new RuntimeException("区域名称不能为空");
}
if (area.getAreaType() == null) {
throw new RuntimeException("区域类型不能为空(市区/校园)");
}
}
/**
*
* 1IDparentAreaId=null
* 2ID
*/
private void validateAreaHierarchy(Area area) {
if (Area.AreaType.zone.equals(area.getAreaType())) {
// 市区不允许设置父级ID
if (area.getParentAreaId() != null && !area.getParentAreaId().trim().isEmpty()) {
throw new RuntimeException("市区为根节点,不允许设置父级区域");
}
} else if (Area.AreaType.campus .equals(area.getAreaType())) {
// 校园必须设置父级ID
if (area.getParentAreaId() == null || area.getParentAreaId().trim().isEmpty()) {
throw new RuntimeException("校园必须关联市区作为父级区域");
}
// 校验父级区域是否存在且类型为市区
Optional<Area> parentAreaOpt = areaRepository.findByAreaId(area.getParentAreaId());
if (parentAreaOpt.isEmpty()) {
throw new RuntimeException("父级市区不存在ID" + area.getParentAreaId());
}
Area parentArea = parentAreaOpt.get();
if (!Area.AreaType.zone.equals(parentArea.getAreaType())) {
throw new RuntimeException("校园的父级区域必须是市区,当前父级类型为:" + parentArea.getAreaType().getDesc());
}
}
}
public Area getAreaById(String areaId) {
return areaRepository.findByAreaId(areaId)
.orElseThrow(() -> new RuntimeException("区域不存在ID" + areaId));
}
/**
* +
* @param area managerID
*/
private void validateAndPrepareAdmin(Area area) {
String adminId = area.getManager();
if (adminId != null && !adminId.trim().isEmpty()) {
// 校验管理员是否存在
Admin admin = adminRepository.findById(adminId)
.orElseThrow(() -> new RuntimeException("区域管理员不存在ID" + adminId));
// 校验管理员角色是否为区域管理员
if (!RoleConstants.ROLE_AREA_ADMIN.equals(admin.getRole().name())) {
throw new RuntimeException("指定用户不是区域管理员角色,无法绑定区域");
}
}
}
/**
*
* @param adminId ID
* @param areaId IDID
*/
private void bindAdminToArea(String adminId, String areaId) {
if (adminId != null && !adminId.trim().isEmpty() && areaId != null) {
Admin admin = adminRepository.findById(adminId).get(); // 已在前序校验,无需再次处理空值
// 新增:校验该管理员是否已绑定其他校区
if (admin.getAreaId() != null) {
throw new RuntimeException("该区域管理员已绑定校区【" + admin.getAreaId() + "】,无法重复绑定");
}
admin.setAreaId(areaId); // 给管理员设置关联的区域IDAdmin实体需有areaId字段
adminRepository.save(admin);
}
}
}

@ -12,6 +12,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@ -36,6 +37,8 @@ public class DeviceService {
.orElseThrow(() -> new RuntimeException("设备不存在:" + deviceId));
}
/**
*
*/
@ -58,10 +61,8 @@ public class DeviceService {
Device existingDevice = getDeviceById(device.getDeviceId());
// 保留创建时间,更新其他可编辑字段
existingDevice.setDeviceName(device.getDeviceName());
existingDevice.setDeviceType(device.getDeviceType());
existingDevice.setAreaId(device.getAreaId());
existingDevice.setInstallLocation(device.getInstallLocation());
existingDevice.setInstallDate(device.getInstallDate());
existingDevice.setParentMakerId(device.getParentMakerId());
return deviceRepository.save(existingDevice);
}
@ -110,23 +111,28 @@ public class DeviceService {
/**
*
*/
// ========== 改造原有bindTerminal方法添加areaId参数和校验 ==========
@Transactional
public DeviceTerminalMapping bindTerminal(String deviceId, String terminalId, String terminalName) {
// 校验设备是否存在
getDeviceById(deviceId);
// 检查终端是否已绑定
public DeviceTerminalMapping bindTerminal(String deviceId, String terminalId, String terminalName, String areaId) {
// 1. 校验片区非空(终端必须归属片区)
if (areaId == null || areaId.trim().isEmpty()) {
throw new RuntimeException("片区ID不能为空请先选择片区");
}
// 2. 校验设备存在且属于该片区的供水机
validateDeviceBelongsToArea(deviceId, areaId);
// 3. 检查终端是否已绑定(原有逻辑保留)
Optional<DeviceTerminalMapping> existing = terminalMappingRepository.findByTerminalId(terminalId);
if (existing.isPresent()) {
throw new RuntimeException("终端已绑定设备:" + existing.get().getDeviceId());
}
// 4. 构建映射对象新增设置areaId
DeviceTerminalMapping mapping = new DeviceTerminalMapping();
mapping.setDeviceId(deviceId);
mapping.setTerminalId(terminalId);
mapping.setTerminalName(terminalName);
mapping.setTerminalStatus(TerminalStatus.active);
mapping.setInstallDate(java.time.LocalDate.now());
mapping.setTerminalStatus(DeviceTerminalMapping.TerminalStatus.active);
mapping.setInstallDate(LocalDate.now());
mapping.setAreaId(areaId); // 保存终端所属片区
return terminalMappingRepository.save(mapping);
}
@ -219,4 +225,30 @@ public class DeviceService {
}
return deviceRepository.findById(supplier.getParentMakerId()).orElse(null);
}
// ========== 新增1查询指定片区的所有供水机 ==========
public List<Device> getWaterSuppliesByArea(String areaId) {
// 1. 校验片区ID非空
if (areaId == null || areaId.trim().isEmpty()) {
throw new RuntimeException("片区ID不能为空");
}
// 2. 查询该片区下类型为供水机的设备
return deviceRepository.findByAreaIdAndDeviceType(areaId, Device.DeviceType.water_supply);
}
// ========== 新增2校验设备是否属于指定片区 ==========
public void validateDeviceBelongsToArea(String deviceId, String areaId) {
// 1. 校验设备存在
Device device = getDeviceById(deviceId);
// 2. 校验设备是供水机
if (!Device.DeviceType.water_supply.equals(device.getDeviceType())) {
throw new RuntimeException("只能关联供水机设备,当前设备类型不合法");
}
// 3. 校验设备所属片区与选中片区一致
if (!areaId.equals(device.getAreaId())) {
throw new RuntimeException("该供水机不属于所选片区(设备所属片区:" + device.getAreaId() + "");
}
}
}

@ -23,8 +23,13 @@ public interface DeviceStatusService {
// 批量更新设备状态
boolean batchUpdateDeviceStatus(List<String> deviceIds, String status, String remark);
// 按状态查询设备
List<Device> getDevicesByStatus(String status, String areaId, String deviceType);
// 按状态加载设备(新增)
List<Device> getDevicesByStatusWithArea(String status, String areaId);
// 按设备类型加载设备(新增)
List<Device> getDevicesByTypeWithArea(String deviceType, String areaId);
// 统计各状态设备数量
Map<String, Object> getDeviceStatusCount(String areaId, String deviceType);

@ -1,4 +1,3 @@
// com/campus/water/service/DeviceStatusServiceImpl.java
package com.campus.water.service;
import com.campus.water.entity.Device;
@ -27,6 +26,7 @@ public class DeviceStatusServiceImpl implements DeviceStatusService {
return false;
}
device.setStatus(request.getStatus());
device.setRemark(request.getRemark());
deviceRepository.save(device);
return true;
@ -73,50 +73,68 @@ public class DeviceStatusServiceImpl implements DeviceStatusService {
return true;
}
/**
*
* @param status
* @param areaId IDnullnull
*/
@Override
public List<Device> getDevicesByStatus(String status, String areaId, String deviceType) {
Device.DeviceStatus targetStatus = Device.DeviceStatus.valueOf(status);
// 处理设备类型参数允许为null
Device.DeviceType targetType = null;
if (deviceType != null && !deviceType.isEmpty()) {
targetType = Device.DeviceType.valueOf(deviceType);
}
// 根据设备类型是否为null执行不同查询
if (targetType != null) {
return deviceRepository.findByStatusAndAreaIdAndDeviceType(targetStatus, areaId, targetType);
} else {
// 仅按状态和区域查询如果有区域ID
public List<Device> getDevicesByStatusWithArea(String status, String areaId) {
try {
Device.DeviceStatus targetStatus = Device.DeviceStatus.valueOf(status);
if (areaId != null && !areaId.isEmpty()) {
return deviceRepository.findByStatusAndAreaId(targetStatus, areaId);
} else {
return deviceRepository.findByStatus(targetStatus);
}
} catch (IllegalArgumentException e) {
log.error("设备状态枚举转换失败,状态值:{}", status, e);
throw new RuntimeException("无效的设备状态:" + status);
}
}
/**
*
* @param deviceType
* @param areaId IDnullnull
*/
@Override
public List<Device> getDevicesByTypeWithArea(String deviceType, String areaId) {
try {
Device.DeviceType targetType = Device.DeviceType.valueOf(deviceType);
if (areaId != null && !areaId.isEmpty()) {
return deviceRepository.findByDeviceTypeAndAreaId(targetType, areaId);
} else {
return deviceRepository.findByDeviceType(targetType);
}
} catch (IllegalArgumentException e) {
log.error("设备类型枚举转换失败,类型值:{}", deviceType, e);
throw new RuntimeException("无效的设备类型:" + deviceType);
}
}
@Override
public Map<String, Object> getDeviceStatusCount(String areaId, String deviceType) {
Device.DeviceType targetType = Device.DeviceType.valueOf(deviceType);
return Map.of(
"online", deviceRepository.countByStatusAndAreaIdAndDeviceType(Device.DeviceStatus.online, areaId, targetType),
"offline", deviceRepository.countByStatusAndAreaIdAndDeviceType(Device.DeviceStatus.offline, areaId, targetType),
"fault", deviceRepository.countByStatusAndAreaIdAndDeviceType(Device.DeviceStatus.fault, areaId, targetType)
);
try {
Device.DeviceType targetType = Device.DeviceType.valueOf(deviceType);
return Map.of(
"online", deviceRepository.countByStatusAndAreaIdAndDeviceType(Device.DeviceStatus.online, areaId, targetType),
"offline", deviceRepository.countByStatusAndAreaIdAndDeviceType(Device.DeviceStatus.offline, areaId, targetType),
"fault", deviceRepository.countByStatusAndAreaIdAndDeviceType(Device.DeviceStatus.fault, areaId, targetType)
);
} catch (IllegalArgumentException e) {
log.error("设备类型枚举转换失败,类型值:{}", deviceType, e);
throw new RuntimeException("无效的设备类型:" + deviceType);
}
}
@Override
public List<Device> getOfflineDevicesExceedThreshold(Integer thresholdMinutes, String areaId) {
// 由于没有last_active_time此处逻辑需调整
// 方案1若设备有最近操作时间可用作替代
// 方案2仅返回状态为offline的设备不判断时间
return deviceRepository.findByAreaIdAndStatus(areaId, Device.DeviceStatus.offline);
}
@Override
public void autoDetectOfflineDevices(Integer thresholdMinutes) {
// 同理无last_active_time时无法通过时间判断可注释或简化逻辑
log.info("自动检测离线设备(不执行时间判断,仅依赖手动标记)");
}
}

@ -1,5 +1,5 @@
package com.campus.water.service;
import com.campus.water.service.AlertTriggerService;
import com.campus.water.config.MqttConfig;
import com.campus.water.entity.Alert;
import com.campus.water.entity.WaterMakerRealtimeData;
@ -17,7 +17,6 @@ import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannel
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@ -26,25 +25,19 @@ import java.time.LocalDateTime;
@RequiredArgsConstructor
@Slf4j
public class MqttSensorReceiver {
// JPA Repository数据持久化接口Spring自动注入实现
private final WaterMakerRealtimeDataRepository waterMakerRepo;
private final WaterSupplyRealtimeDataRepository waterSupplyRepo;
private final AlertRepository alertRepo;
private final ObjectMapper objectMapper;
private final MqttPahoMessageDrivenChannelAdapter mqttAdapter;
// 新增告警触发服务依赖
private final AlertTriggerService alertTriggerService;
/**
* MQTT
* +ID
*/
@PostConstruct
public void initMqttSubscription() {
mqttAdapter.addTopic(MqttConfig.TOPIC_WATER_MAKER_STATE + "+"); // 制水机状态(所有设备)
mqttAdapter.addTopic(MqttConfig.TOPIC_WATER_MAKER_WARN + "+"); // 制水机告警(所有设备)
mqttAdapter.addTopic(MqttConfig.TOPIC_WATER_SUPPLIER_STATE + "+"); // 供水机状态(所有设备)
mqttAdapter.addTopic(MqttConfig.TOPIC_WATER_SUPPLIER_WARN + "+"); // 供水机告警(所有设备)
mqttAdapter.addTopic(MqttConfig.TOPIC_WATER_MAKER_STATE + "+");
mqttAdapter.addTopic(MqttConfig.TOPIC_WATER_MAKER_WARN + "+");
mqttAdapter.addTopic(MqttConfig.TOPIC_WATER_SUPPLIER_STATE + "+");
mqttAdapter.addTopic(MqttConfig.TOPIC_WATER_SUPPLIER_WARN + "+");
log.info("MQTT订阅初始化完成 | 订阅主题:{}+、{}+、{}+、{}+",
MqttConfig.TOPIC_WATER_MAKER_STATE,
MqttConfig.TOPIC_WATER_MAKER_WARN,
@ -52,25 +45,19 @@ public class MqttSensorReceiver {
MqttConfig.TOPIC_WATER_SUPPLIER_WARN);
}
/**
* MQTT
* @param payload JSON
* @param topic
*/
@ServiceActivator(inputChannel = "mqttInputChannel")
public void handleMqttMessage(String payload, @Header(MqttHeaders.RECEIVED_TOPIC) String topic) {
log.info("MQTT消息接收成功 | 主题:{} | 内容:{}", topic, payload);
try {
// 根据主题分类处理
if (topic.startsWith(MqttConfig.TOPIC_WATER_MAKER_STATE)) {
handleWaterMakerState(payload); // 制水机状态数据
handleWaterMakerState(payload);
} else if (topic.startsWith(MqttConfig.TOPIC_WATER_MAKER_WARN)) {
handleWaterMakerWarning(payload); // 制水机告警数据
handleWaterMakerWarning(payload);
} else if (topic.startsWith(MqttConfig.TOPIC_WATER_SUPPLIER_STATE)) {
handleWaterSupplyState(payload); // 供水机状态数据
handleWaterSupplyState(payload);
} else if (topic.startsWith(MqttConfig.TOPIC_WATER_SUPPLIER_WARN)) {
handleWaterSupplyWarning(payload); // 供水机告警数据
handleWaterSupplyWarning(payload); // 新增:处理供水机告警主题
} else {
log.warn("MQTT消息主题未匹配 | 未知主题:{} | 内容:{}", topic, payload);
}
@ -79,17 +66,11 @@ public class MqttSensorReceiver {
}
}
/**
* JPA
*/
private void handleWaterMakerState(String payload) throws Exception {
// 1. JSON反序列化为模型对象
WaterMakerSensorData sensorData = objectMapper.readValue(payload, WaterMakerSensorData.class);
// 2. 模型对象转换为JPA实体持久化到数据库
WaterMakerRealtimeData entity = new WaterMakerRealtimeData();
entity.setDeviceId(sensorData.getDeviceId());
// Double转BigDecimal处理包含null值判断
entity.setTdsValue1(sensorData.getTdsValue1() != null ? BigDecimal.valueOf(sensorData.getTdsValue1()) : null);
entity.setTdsValue2(sensorData.getTdsValue2() != null ? BigDecimal.valueOf(sensorData.getTdsValue2()) : null);
entity.setTdsValue3(sensorData.getTdsValue3() != null ? BigDecimal.valueOf(sensorData.getTdsValue3()) : null);
@ -97,31 +78,31 @@ public class MqttSensorReceiver {
entity.setWaterFlow2(sensorData.getWaterFlow2() != null ? BigDecimal.valueOf(sensorData.getWaterFlow2()) : null);
entity.setWaterPress(sensorData.getWaterPress() != null ? BigDecimal.valueOf(sensorData.getWaterPress()) : null);
entity.setFilterLife(sensorData.getFilterLife());
entity.setLeakage(sensorData.getLeakage() ? true : false); // 数据库存储true=漏水false=正常
entity.setLeakage(sensorData.getLeakage() ? true : false);
entity.setWaterQuality(sensorData.getWaterQuality());
entity.setStatus(WaterMakerRealtimeData.DeviceStatus.valueOf(sensorData.getStatus()));
entity.setRecordTime(sensorData.getRecordTime());
entity.setCreatedTime(LocalDateTime.now());
// 3. 持久化到数据库JPA save() 自动实现CRUD
waterMakerRepo.save(entity);
log.info("制水机状态数据持久化成功 | 设备ID{}", sensorData.getDeviceId());
// 新增:调用告警检查逻辑
alertTriggerService.checkWaterMakerAbnormal(sensorData);
}
/**
* +
*/
private void handleWaterMakerWarning(String payload) throws Exception {
WaterMakerSensorData sensorData = objectMapper.readValue(payload, WaterMakerSensorData.class);
// 1. 持久化告警记录
// 新增:重复告警判断
if (alertTriggerService.isDuplicateAlert(sensorData.getDeviceId(), "WATER_MAKER_ABNORMAL")) {
log.info("制水机存在未处理告警,跳过重复触发 | 设备ID{}", sensorData.getDeviceId());
return;
}
Alert alert = new Alert();
alert.setDeviceId(sensorData.getDeviceId());
alert.setAlertType("WATER_MAKER_ABNORMAL"); // 告警类型(枚举规范)
alert.setAlertLevel(Alert.AlertLevel.critical); // 告警级别(严重)
alert.setAlertType("WATER_MAKER_ABNORMAL");
alert.setAlertLevel(Alert.AlertLevel.critical);
alert.setAreaId(alertTriggerService.getDeviceAreaId(sensorData.getDeviceId()));
alert.setAlertMessage(String.format(
"制水机异常 - 设备ID%sTDS值%.2f,滤芯寿命:%d%%,漏水状态:%s",
sensorData.getDeviceId(),
@ -129,26 +110,23 @@ public class MqttSensorReceiver {
sensorData.getFilterLife(),
sensorData.getLeakage() ? "是" : "否"
));
alert.setStatus(Alert.AlertStatus.pending); // 告警状态(未处理)
alert.setTimestamp(sensorData.getRecordTime());
alert.setCreatedTime(LocalDateTime.now());
alert.setStatus(Alert.AlertStatus.pending);
alert.setTimestamp(sensorData.getRecordTime() != null ? sensorData.getRecordTime() : LocalDateTime.now());
LocalDateTime now = LocalDateTime.now();
alert.setCreatedTime(alert.getCreatedTime() != null ? alert.getCreatedTime() : now);
alert.setUpdatedTime(now);
alertRepo.save(alert);
log.warn("制水机告警记录持久化成功 | 告警ID{} | 设备ID{}", alert.getAlertId(), sensorData.getDeviceId());
// 2. 同时持久化状态数据(便于后续追溯)
handleWaterMakerState(payload);
}
/**
* JPA
*/
private void handleWaterSupplyState(String payload) throws Exception {
WaterSupplySensorData sensorData = objectMapper.readValue(payload, WaterSupplySensorData.class);
WaterSupplyRealtimeData entity = new WaterSupplyRealtimeData();
entity.setDeviceId(sensorData.getDeviceId());
// Double转BigDecimal处理包含null值判断
entity.setWaterFlow(sensorData.getWaterFlow() != null ? BigDecimal.valueOf(sensorData.getWaterFlow()) : null);
entity.setWaterPress(sensorData.getWaterPress() != null ? BigDecimal.valueOf(sensorData.getWaterPress()) : null);
entity.setWaterLevel(sensorData.getWaterLevel() != null ? BigDecimal.valueOf(sensorData.getWaterLevel()) : null);
@ -156,39 +134,47 @@ public class MqttSensorReceiver {
entity.setStatus(WaterSupplyRealtimeData.DeviceStatus.valueOf(sensorData.getStatus()));
entity.setTimestamp(sensorData.getTimestamp());
waterSupplyRepo.save(entity);
log.info("供水机状态数据持久化成功 | 设备ID{}", sensorData.getDeviceId());
// 新增:调用告警检查逻辑
alertTriggerService.checkWaterSupplyAbnormal(sensorData);
}
/**
* +
*
*/
private void handleWaterSupplyWarning(String payload) throws Exception {
WaterSupplySensorData sensorData = objectMapper.readValue(payload, WaterSupplySensorData.class);
// 1. 持久化告警记录
// 新增:重复告警判断
if (alertTriggerService.isDuplicateAlert(sensorData.getDeviceId(), "WATER_SUPPLY_ABNORMAL")) {
log.info("供水机存在未处理告警,跳过重复触发 | 设备ID{}", sensorData.getDeviceId());
return;
}
Alert alert = new Alert();
alert.setDeviceId(sensorData.getDeviceId());
alert.setAlertType("WATER_SUPPLY_ABNORMAL");
alert.setAlertLevel(Alert.AlertLevel.error);
alert.setAreaId(alertTriggerService.getDeviceAreaId(sensorData.getDeviceId()));
alert.setAlertMessage(String.format(
"供水机异常 - 设备ID%s水压%.2fMPa,水位:%.2f%%",
"供水机异常 - 设备ID%s水压%.2fMPa,水位:%.2f%%,水温:%.2f℃",
sensorData.getDeviceId(),
sensorData.getWaterPress(),
sensorData.getWaterLevel()
sensorData.getWaterLevel(),
sensorData.getTemperature()
));
alert.setStatus(Alert.AlertStatus.pending);
alert.setTimestamp(sensorData.getTimestamp());
alert.setCreatedTime(LocalDateTime.now());
// 完善告警时间戳(确保不为空)
alert.setTimestamp(sensorData.getTimestamp() != null ? sensorData.getTimestamp() : LocalDateTime.now());
// 完善创建/更新时间(确保不为空)
LocalDateTime now = LocalDateTime.now();
alert.setCreatedTime(alert.getCreatedTime() != null ? alert.getCreatedTime() : now);
alert.setUpdatedTime(now);
alertRepo.save(alert);
log.warn("供水机告警记录持久化成功 | 告警ID{} | 设备ID{}", alert.getAlertId(), sensorData.getDeviceId());
// 2. 同时持久化状态数据
// 同时持久化状态数据
handleWaterSupplyState(payload);
}
}

@ -115,6 +115,32 @@ public class MqttSensorSender {
}
}
/**
*
* @param deviceId IDWS001
*/
public void sendWaterSupplyWarning(String deviceId) {
try {
// 1. 构建供水机异常数据(超出正常范围)
WaterSupplySensorData data = new WaterSupplySensorData();
data.setDeviceId(deviceId);
data.setWaterFlow(0.1 + random.nextDouble() * 0.2); // 流量极低0.1-0.3 L/min
data.setWaterPress(0.01 + random.nextDouble() * 0.09); // 水压过低0.01-0.1 MPa
data.setWaterLevel(5 + random.nextDouble() * 15); // 水位过低5-20%
data.setTemperature(25 + random.nextDouble() * 5); // 水温过高25-30℃
data.setStatus("error");
data.setTimestamp(LocalDateTime.now());
// 2. 序列化+发送
String payload = objectMapper.writeValueAsString(data);
String topic = MqttConfig.TOPIC_WATER_SUPPLIER_WARN + deviceId;
sendMessage(topic, payload);
log.warn("供水机告警消息发送成功 | 设备ID{} | 主题:{} | 数据:{}", deviceId, topic, payload);
} catch (JsonProcessingException e) {
log.error("供水机告警消息发送失败 | 设备ID{} | 异常:{}", deviceId, e.getMessage());
}
}
/**
* MQTT
* @param topic

@ -0,0 +1,37 @@
package com.campus.water.service;
import com.campus.water.entity.Notification;
import java.util.List;
/**
*
*/
public interface NotificationService {
/**
*
* @param repairmanId ID
* @param orderId ID
* @param content
*/
void sendOrderAssignedNotification(String repairmanId, String orderId, String content);
/**
*
* @param repairmanId ID
* @return
*/
List<Notification> getUnreadNotifications(String repairmanId);
/**
*
* @param repairmanId ID
* @return
*/
List<Notification> getAllNotifications(String repairmanId);
/**
*
* @param notificationId ID
*/
void markAsRead(Long notificationId);
}

@ -0,0 +1,59 @@
package com.campus.water.service.impl;
import com.campus.water.entity.Notification;
import com.campus.water.mapper.NotificationRepository;
import com.campus.water.service.NotificationService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
*
*/
@Service
@RequiredArgsConstructor
public class NotificationServiceImpl implements NotificationService {
private final NotificationRepository notificationRepository;
/**
*
*/
@Override
public void sendOrderAssignedNotification(String repairmanId, String orderId, String content) {
Notification notification = new Notification();
notification.setRepairmanId(repairmanId);
notification.setOrderId(orderId);
notification.setContent(content);
notification.setType(Notification.NotificationType.ORDER_ASSIGNED);
notificationRepository.save(notification);
}
/**
*
*/
@Override
public List<Notification> getUnreadNotifications(String repairmanId) {
return notificationRepository.findByRepairmanIdAndIsReadFalseOrderByCreatedTimeDesc(repairmanId);
}
/**
*
*/
@Override
public List<Notification> getAllNotifications(String repairmanId) {
return notificationRepository.findByRepairmanIdOrderByCreatedTimeDesc(repairmanId);
}
/**
*
*/
@Override
public void markAsRead(Long notificationId) {
notificationRepository.findById(notificationId).ifPresent(notification -> {
notification.setRead(true);
notificationRepository.save(notification);
});
}
}

@ -5,10 +5,7 @@ import com.campus.water.entity.RepairerAuth;
import com.campus.water.entity.Repairman;
import com.campus.water.entity.User;
import com.campus.water.entity.dto.request.RegisterRequest;
import com.campus.water.mapper.AdminRepository;
import com.campus.water.mapper.RepairerAuthRepository;
import com.campus.water.mapper.RepairmanRepository;
import com.campus.water.mapper.UserRepository;
import com.campus.water.mapper.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@ -21,6 +18,9 @@ public class RegisterService {
@Autowired
private AdminRepository adminRepository;
@Autowired
private AreaRepository areaRepository;
@Autowired
private UserRepository userRepository;
@ -76,6 +76,18 @@ public class RegisterService {
admin.setCreatedTime(LocalDateTime.now());
admin.setUpdatedTime(LocalDateTime.now());
// 核心修改1添加区域ID赋值从请求中获取允许为null/空,实现选填)
admin.setAreaId(request.getAreaId());
// 核心修改2区域管理员若填写了areaId则校验区域是否存在不填则不强制实现选填
Admin.AdminRole adminRole = admin.getRole();
if (adminRole == Admin.AdminRole.ROLE_AREA_ADMIN && request.getAreaId() != null && !request.getAreaId().trim().isEmpty()) {
// 此处需要注入AreaRepository与AdminService保持一致先补充注入
if (!areaRepository.existsById(request.getAreaId().trim())) {
throw new RuntimeException("关联的区域不存在:" + request.getAreaId().trim());
}
}
adminRepository.save(admin);
}

@ -0,0 +1,105 @@
package com.campus.water.service;
import com.campus.water.entity.DrinkRecord;
import com.campus.water.entity.vo.DailyDrinkVO;
import com.campus.water.entity.vo.StudentDrinkStatsVO;
import com.campus.water.mapper.DrinkRecordRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Collectors;
/**
*
*/
@Service
@RequiredArgsConstructor
public class StudentDrinkStatsService {
private final DrinkRecordRepository drinkRecordRepository;
/**
*
*/
public StudentDrinkStatsVO getTodayDrinkStats(String studentId) {
LocalDate today = LocalDate.now();
LocalDateTime start = today.atStartOfDay();
LocalDateTime end = LocalDateTime.now();
return calculateStats(studentId, start, end, "本日", "today");
}
/**
*
*/
public StudentDrinkStatsVO getThisWeekDrinkStats(String studentId) {
LocalDate today = LocalDate.now();
LocalDateTime start = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).atStartOfDay();
LocalDateTime end = LocalDateTime.now();
return calculateStats(studentId, start, end, "本周", "thisWeek");
}
/**
*
*/
public StudentDrinkStatsVO getThisMonthDrinkStats(String studentId) {
LocalDate today = LocalDate.now();
LocalDateTime start = today.with(TemporalAdjusters.firstDayOfMonth()).atStartOfDay();
LocalDateTime end = LocalDateTime.now();
return calculateStats(studentId, start, end, "本月", "thisMonth");
}
/**
*
*/
private StudentDrinkStatsVO calculateStats(String studentId, LocalDateTime start, LocalDateTime end,
String timeRangeDesc, String timeDimension) {
// 1. 查询时间范围内的饮水记录
List<DrinkRecord> records = drinkRecordRepository
.findByStudentIdAndDrinkTimeBetweenOrdered(studentId, start, end);
// 2. 按日期分组统计
Map<LocalDate, List<DrinkRecord>> dailyGroup = records.stream()
.collect(Collectors.groupingBy(record -> record.getDrinkTime().toLocalDate()));
// 3. 构建每日明细
List<DailyDrinkVO> dailyDetails = new ArrayList<>();
dailyGroup.forEach((date, dailyRecords) -> {
DailyDrinkVO dailyVO = new DailyDrinkVO();
dailyVO.setDate(date.toString());
// 当日总饮水量
double dailyTotal = dailyRecords.stream()
.map(DrinkRecord::getWaterConsumption)
.filter(Objects::nonNull)
.mapToDouble(BigDecimal::doubleValue)
.sum();
dailyVO.setConsumption(dailyTotal);
dailyVO.setCount(dailyRecords.size());
dailyDetails.add(dailyVO);
});
// 按日期排序
dailyDetails.sort(Comparator.comparing(DailyDrinkVO::getDate));
// 4. 计算总饮水量、总次数、日均饮水量
double totalConsumption = dailyDetails.stream()
.mapToDouble(DailyDrinkVO::getConsumption)
.sum();
int totalCount = records.size();
double avgDaily = dailyDetails.isEmpty() ? 0 : totalConsumption / dailyDetails.size();
// 5. 封装结果VO
StudentDrinkStatsVO statsVO = new StudentDrinkStatsVO();
statsVO.setStudentId(studentId);
statsVO.setTimeDimension(timeDimension);
statsVO.setTimeRange(timeRangeDesc + "(" + start.toLocalDate() + "~" + end.toLocalDate() + ")");
statsVO.setTotalConsumption(totalConsumption);
statsVO.setDrinkCount(totalCount);
statsVO.setAvgDailyConsumption(avgDaily);
statsVO.setDailyDetails(dailyDetails);
statsVO.setDrinkRecords(records);
return statsVO;
}
}

@ -0,0 +1,150 @@
package com.campus.water.service;
import com.campus.water.entity.DeviceTerminalMapping;
import com.campus.water.mapper.DeviceTerminalMappingRepository;
import com.campus.water.util.DeviceMappingUtil; // 硬编码映射工具类(之前定义的)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
@Service
public class StudentWaterDataService {
@Autowired
private DeviceTerminalMappingRepository terminalMappingRepo;
// 随机数工具(模拟实时数据)
private static final Random RANDOM = new Random();
// 时间格式化器
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* +
* @param terminalId IDTERM001
* @return
*/
public Map<String, Object> queryRealtimeData(String terminalId) {
Map<String, Object> result = new HashMap<>();
try {
// 1. 校验终端ID非空
if (terminalId == null || terminalId.trim().isEmpty()) {
result.put("code", 400);
result.put("msg", "终端ID不能为空");
return result;
}
// 2. 通过终端ID查询映射关系核心终端→制水机
Optional<DeviceTerminalMapping> mappingOpt = terminalMappingRepo.findByTerminalId(terminalId);
if (mappingOpt.isEmpty()) {
result.put("code", 404);
result.put("msg", "终端设备不存在或未配置映射关系");
return result;
}
DeviceTerminalMapping mapping = mappingOpt.get();
String makerDeviceId = mapping.getDeviceId(); // 制水机IDWM开头
// 从硬编码映射工具类获取供水机ID替代数据库字段
String supplyDeviceId = DeviceMappingUtil.getSupplyDeviceId(makerDeviceId);
// 3. 生成制水机实时数据(调用模拟生成服务)
Map<String, Object> makerRealtimeData = new HashMap<>();
if (makerDeviceId != null && makerDeviceId.startsWith("WM")) {
makerRealtimeData = generateMakerRealtimeData(makerDeviceId);
} else {
result.put("makerWarn", "终端未绑定有效制水机ID格式需以WM开头");
}
// 4. 生成供水机实时数据(调用模拟生成服务)
Map<String, Object> supplyRealtimeData = new HashMap<>();
if (supplyDeviceId != null && supplyDeviceId.startsWith("WS")) {
supplyRealtimeData = generateSupplyRealtimeData(supplyDeviceId);
} else {
result.put("supplyWarn", "制水机未关联有效供水机ID格式需以WS开头");
}
// 5. 封装最终返回结果(包含终端、制水机、供水机全量数据)
result.put("code", 200);
result.put("msg", "实时数据查询成功");
// 终端基础信息
result.put("terminalInfo", Map.of(
"terminalId", terminalId,
"terminalName", mapping.getTerminalName(),
"terminalStatus", mapping.getTerminalStatus().name(),
"installDate", mapping.getInstallDate() != null ? mapping.getInstallDate().toString() : "未配置"
));
// 制水机数据
result.put("makerDevice", Map.of(
"deviceId", makerDeviceId,
"realtimeData", makerRealtimeData
));
// 供水机数据
result.put("supplyDevice", Map.of(
"deviceId", supplyDeviceId,
"realtimeData", supplyRealtimeData
));
// 数据更新时间(统一时间戳)
result.put("updateTime", LocalDateTime.now().format(DATE_FORMATTER));
} catch (Exception e) {
// 全局异常捕获,保证接口不抛错
result.put("code", 500);
result.put("msg", "实时数据查询失败:" + e.getMessage());
result.put("errorDetail", e.getStackTrace()[0].toString()); // 调试用,生产可移除
}
return result;
}
/**
*
* @param makerDeviceId ID
* @return
*/
public Map<String, Object> generateMakerRealtimeData(String makerDeviceId) {
Map<String, Object> makerData = new HashMap<>();
// 基础设备信息
makerData.put("deviceId", makerDeviceId);
makerData.put("deviceType", "校园直饮矿化制水机");
makerData.put("onlineStatus", RANDOM.nextBoolean() ? "在线" : "离线"); // 模拟在线状态
// 核心水质参数(符合直饮水标准)
makerData.put("tdsValue", RANDOM.nextInt(50) + 10); // TDS值10-60mg/L
makerData.put("phValue", String.format("%.1f", RANDOM.nextDouble() * 0.8 + 7.0)); // pH值7.0-7.8
makerData.put("temperature", RANDOM.nextInt(15) + 30); // 出水温度30-45℃
makerData.put("filterLife", 100 - RANDOM.nextInt(10)); // 滤芯寿命90-100%
makerData.put("flowRate", String.format("%.2f", RANDOM.nextDouble() * 2 + 1)); // 流量1.00-3.00L/min
// 运行数据
makerData.put("totalUsage", RANDOM.nextInt(5000) + 10000); // 累计用水量10000-15000L
makerData.put("faultCode", RANDOM.nextInt(100) > 95 ? "E01" : "无"); // 模拟故障码5%概率故障)
makerData.put("updateTime", LocalDateTime.now().format(DATE_FORMATTER));
return makerData;
}
/**
*
* @param supplyDeviceId ID
* @return
*/
public Map<String, Object> generateSupplyRealtimeData(String supplyDeviceId) {
Map<String, Object> supplyData = new HashMap<>();
// 基础设备信息
supplyData.put("deviceId", supplyDeviceId);
supplyData.put("deviceType", "配套供水增压机");
supplyData.put("onlineStatus", RANDOM.nextBoolean() ? "在线" : "离线");
// 核心运行参数
supplyData.put("waterPressure", String.format("%.2f", RANDOM.nextDouble() * 0.5 + 0.8)); // 水压0.80-1.30MPa
supplyData.put("waterLevel", RANDOM.nextInt(30) + 70); // 水箱水位70-100%
supplyData.put("pumpStatus", RANDOM.nextBoolean() ? "运行中" : "待机"); // 水泵状态
supplyData.put("voltage", RANDOM.nextInt(10) + 220); // 工作电压220-230V
// 运行数据
supplyData.put("runHours", RANDOM.nextInt(1000) + 5000); // 累计运行时长5000-6000h
supplyData.put("faultCode", RANDOM.nextInt(100) > 98 ? "P02" : "无"); // 模拟故障码2%概率故障)
supplyData.put("updateTime", LocalDateTime.now().format(DATE_FORMATTER));
return supplyData;
}
// 原有其他方法(扫码用水、水质查询等)保持不变...
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save