This commit is contained in:
ChuXun
2025-10-19 20:31:01 +08:00
parent cfd054f0d9
commit 4ce487588a
287 changed files with 59148 additions and 0 deletions

696
Design/API测试文档.md Normal file
View File

@@ -0,0 +1,696 @@
# EMS 后端 API 文档 (v3.0)
本文档为EMS环境监控系统后端API提供了全面的接口说明。所有接口均基于RESTful设计原则使用JSON格式进行数据交换并通过JWT进行认证授权。
**基础URL**: `http://localhost:8080`
---
## 1. 认证模块 (`/api/auth`)
负责处理用户身份验证、注册、登出和密码管理等所有认证相关操作。
### 1.1 用户登录
- **功能**: 用户通过邮箱和密码登录成功后返回JWT令牌及用户信息。
- **URL**: `/api/auth/login`
- **方法**: `POST`
- **请求体** (`LoginRequest`):
```json
{
"email": "admin@example.com",
"password": "password123"
}
```
- **成功响应** (`200 OK`, `JwtAuthenticationResponse`):
```json
{
"token": "eyJhbGciOiJIUzI1NiJ9...",
"user": {
"id": 1,
"name": "Admin User",
"email": "admin@example.com",
"role": "ADMIN"
}
}
```
- **失败响应**: `401 Unauthorized` (凭证无效)
### 1.2 用户注册
- **功能**: 使用提供的用户信息和验证码创建新用户账户。
- **URL**: `/api/auth/signup`
- **方法**: `POST`
- **请求体** (`SignUpRequest`):
```json
{
"name": "New User",
"email": "newuser@example.com",
"password": "password123",
"code": "123456"
}
```
- **成功响应**: `201 Created`
- **失败响应**: `400 Bad Request` (验证码错误、邮箱已存在等)
### 1.3 用户登出
- **功能**: 服务端记录用户登出操作。
- **URL**: `/api/auth/logout`
- **方法**: `POST`
- **认证**: 需要有效的JWT令牌
- **成功响应**: `200 OK`
### 1.4 发送注册验证码
- **功能**: 向指定邮箱发送用于账号注册的验证码。
- **URL**: `/api/auth/send-verification-code`
- **方法**: `POST`
- **请求参数**:
- `email` (string, `query`): 接收验证码的邮箱地址。
- **成功响应**: `200 OK`
- **失败响应**: `400 Bad Request` (邮箱格式不正确)
### 1.5 发送密码重置验证码
- **功能**: 向指定邮箱发送用于重置密码的验证码。
- **URL**: `/api/auth/send-password-reset-code`
- **方法**: `POST`
- **请求参数**:
- `email` (string, `query`): 注册时使用的邮箱地址。
- **成功响应**: `200 OK`
### 1.6 使用验证码重置密码
- **功能**: 校验验证码并重置为新密码。
- **URL**: `/api/auth/reset-password-with-code`
- **方法**: `POST`
- **请求体** (`PasswordResetWithCodeDto`):
```json
{
"email": "user@example.com",
"code": "654321",
"newPassword": "newSecurePassword456"
}
```
- **成功响应**: `200 OK`
- **失败响应**: `400 Bad Request` (验证码无效或密码不符合要求)
---
## 2. 仪表盘模块 (`/api/dashboard`)
提供决策支持的各类统计和可视化数据。所有端点都需要 `DECISION_MAKER` 或 `ADMIN` 角色权限,特殊权限会单独注明。
### 2.1 核心统计数据
- **功能**: 获取仪表盘的核心统计数据,如总任务数、待处理反馈数等。
- **URL**: `/api/dashboard/stats`
- **方法**: `GET`
- **成功响应** (`200 OK`): `DashboardStatsDTO`
### 2.2 报告与分析
#### AQI等级分布
- **功能**: 获取AQI空气质量指数在不同等级下的分布情况。
- **URL**: `/api/dashboard/reports/aqi-distribution`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<AqiDistributionDTO>`
#### 月度超标趋势
- **功能**: 获取过去几个月内环境指标超标事件的趋势。
- **URL**: `/api/dashboard/reports/monthly-exceedance-trend`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<TrendDataPointDTO>`
#### 网格覆盖情况
- **功能**: 按城市统计网格的覆盖率和状态。
- **URL**: `/api/dashboard/reports/grid-coverage`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<GridCoverageDTO>`
#### 污染物统计
- **功能**: 获取主要污染物的统计数据报告。
- **URL**: `/api/dashboard/reports/pollution-stats`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<PollutionStatsDTO>`
#### 任务完成情况
- **功能**: 统计任务的整体完成率和状态分布。
- **URL**: `/api/dashboard/reports/task-completion-stats`
- **方法**: `GET`
- **成功响应** (`200 OK`): `TaskStatsDTO`
#### 污染物月度趋势
- **功能**: 获取不同污染物类型的月度变化趋势。
- **URL**: `/api/dashboard/reports/pollutant-monthly-trends`
- **方法**: `GET`
- **成功响应** (`200 OK`): `Map<String, List<TrendDataPointDTO>>`
### 2.3 地图数据
#### 反馈热力图
- **功能**: 获取用于在地图上展示反馈问题地理分布的热力图数据。
- **URL**: `/api/dashboard/map/heatmap`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<HeatmapPointDTO>`
#### AQI热力图
- **功能**: 获取用于在地图上展示AQI指数地理分布的热力图数据。
- **URL**: `/api/dashboard/map/aqi-heatmap`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<AqiHeatmapPointDTO>`
### 2.4 污染物阈值管理
#### 获取所有阈值
- **功能**: 获取所有污染物的阈值设置。
- **URL**: `/api/dashboard/thresholds`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<PollutantThresholdDTO>`
#### 获取指定污染物阈值
- **功能**: 根据污染物名称获取其特定的阈值设置。
- **URL**: `/api/dashboard/thresholds/{pollutantName}`
- **方法**: `GET`
- **URL参数**: `pollutantName` (string)
- **成功响应** (`200 OK`): `PollutantThresholdDTO`
#### 保存阈值
- **功能**: 创建或更新一个污染物的阈值设置。
- **权限**: **`ADMIN`**
- **URL**: `/api/dashboard/thresholds`
- **方法**: `POST`
- **请求体** (`PollutantThresholdDTO`):
```json
{
"pollutantName": "PM2.5",
"goodThreshold": 35,
"moderateThreshold": 75,
"unhealthyThreshold": 115
}
```
- **成功响应** (`200 OK`): `PollutantThresholdDTO` (已保存的数据)
---
## 3. 反馈模块 (`/api/feedback`)
处理公众和用户的环境问题反馈,包括提交、查询、统计和处理。
### 3.1 提交反馈
- **功能**: 已认证用户提交环境问题反馈,可附带文件。
- **权限**: **`isAuthenticated()`** (任何已认证用户)
- **URL**: `/api/feedback/submit`
- **方法**: `POST`
- **内容类型**: `multipart/form-data`
- **请求部分**:
- `feedback` (JSON, `FeedbackSubmissionRequest`):
```json
{
"title": "River Pollution",
"description": "The river near downtown is heavily polluted.",
"latitude": 34.0522,
"longitude": -118.2437,
"pollutionType": "WATER",
"severityLevel": "HIGH"
}
```
- `files` (File[], optional): (可选) 上传的图片或视频文件。
- **成功响应** (`201 Created`): `Feedback` (包含ID的已创建反馈对象)
### 3.2 获取反馈列表 (分页+过滤)
- **功能**: 根据多种条件组合查询反馈列表,支持分页。
- **权限**: **`isAuthenticated()`**
- **URL**: `/api/feedback`
- **方法**: `GET`
- **请求参数**:
- `page` (int, optional): 页码 (从0开始)
- `size` (int, optional): 每页数量
- `sort` (string, optional): 排序字段,如 `createdAt,desc`
- `status` (string, optional): 状态 (e.g., `PENDING_REVIEW`, `PROCESSED`)
- `pollutionType` (string, optional): 污染类型 (e.g., `AIR`, `WATER`)
- `severityLevel` (string, optional): 严重程度 (e.g., `LOW`, `MEDIUM`)
- `cityName` (string, optional): 城市名称
- `districtName` (string, optional): 区县名称
- `startDate` (string, optional): 开始日期 (格式: `YYYY-MM-DD`)
- `endDate` (string, optional): 结束日期 (格式: `YYYY-MM-DD`)
- `keyword` (string, optional): 标题或描述中的关键词
- **成功响应** (`200 OK`): `Page<FeedbackResponseDTO>` (分页的反馈数据)
### 3.3 获取反馈详情
- **功能**: 根据ID获取单个反馈的详细信息。
- **权限**: **`ADMIN`**, **`SUPERVISOR`**, **`DECISION_MAKER`**
- **URL**: `/api/feedback/{id}`
- **方法**: `GET`
- **URL参数**: `id` (long)
- **成功响应** (`200 OK`): `FeedbackResponseDTO`
### 3.4 获取反馈统计
- **功能**: 获取当前用户的反馈统计数据 (例如,不同状态下的反馈数量)。
- **权限**: **`isAuthenticated()`**
- **URL**: `/api/feedback/stats`
- **方法**: `GET`
- **成功响应** (`200 OK`): `FeedbackStatsResponse`
### 3.5 处理反馈 (审批)
- **功能**: 主管或管理员对反馈进行处理,例如审批通过、驳回等。
- **权限**: **`ADMIN`**, **`SUPERVISOR`**
- **URL**: `/api/feedback/{id}/process`
- **方法**: `POST`
- **URL参数**: `id` (long)
- **请求体** (`ProcessFeedbackRequest`):
```json
{
"newStatus": "PROCESSED",
"remarks": "Feedback has been verified and assigned."
}
```
- **成功响应** (`200 OK`): `FeedbackResponseDTO` (更新后的反馈对象)
---
## 4. 公共接口模块 (`/api/public`)
无需认证即可访问的接口,主要用于公众参与。
### 4.1 提交公共反馈
- **功能**: 公众用户提交环境问题反馈,可附带文件。
- **URL**: `/api/public/feedback`
- **方法**: `POST`
- **内容类型**: `multipart/form-data`
- **请求部分**:
- `feedback` (JSON, `PublicFeedbackRequest`):
```json
{
"title": "Noise Complaint",
"description": "Loud construction noise at night.",
"latitude": 34.0522,
"longitude": -118.2437,
"contactInfo": "anonymous@example.com"
}
```
- `files` (File[], optional): 上传的图片或视频等附件。
- **成功响应** (`201 Created`): `Feedback` (包含ID的已创建反馈对象)
---
## 5. 文件模块 (`/api`)
处理系统中文件的访问、下载和预览。
### 5.1 文件下载/预览
- **功能**: 根据文件名获取文件资源,既可用于下载,也可用于浏览器内联预览。
- **URL**:
- `/api/files/{filename}`
- `/api/view/{filename}`
- **方法**: `GET`
- **URL参数**:
- `filename` (string): 完整的文件名,包含扩展名 (例如, `image.png`, `report.pdf`)。
- **成功响应** (`200 OK`):
- **Content-Type**: 根据文件类型动态设置 (e.g., `image/jpeg`, `application/pdf`)。
- **Content-Disposition**: `inline`,建议浏览器内联显示。
- **Body**: 文件内容的二进制流。
- **失败响应**: `404 Not Found` (文件不存在)
---
## 6. 网格管理模块 (`/api/grids`)
负责环境监督网格的查询、更新以及网格员的分配管理。
### 6.1 获取所有网格
- **功能**: 获取系统中所有的网格数据列表。
- **权限**: `ADMIN`, `DECISION_MAKER`, `GRID_WORKER`
- **URL**: `/api/grids`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<Grid>`
### 6.2 更新网格信息
- **功能**: 更新指定ID的网格信息例如修改其负责人或状态。
- **权限**: `ADMIN`
- **URL**: `/api/grids/{id}`
- **方法**: `PATCH`
- **URL参数**: `id` (long)
- **请求体** (`GridUpdateRequest`):
```json
{
"status": "ACTIVE",
"notes": "This grid is now actively monitored."
}
```
- **成功响应** (`200 OK`): `Grid` (更新后的网格对象)
### 6.3 获取网格覆盖率统计
- **功能**: 按城市统计网格的覆盖情况。
- **权限**: `ADMIN`
- **URL**: `/api/grids/coverage`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<GridCoverageDTO>`
### 6.4 分配/解分配网格员 (通过网格ID)
#### 分配网格员
- **功能**: 将一个网格员分配到指定的网格。
- **权限**: `ADMIN`
- **URL**: `/api/grids/{gridId}/assign`
- **方法**: `POST`
- **URL参数**: `gridId` (long)
- **请求体**:
```json
{
"userId": 123
}
```
- **成功响应** (`200 OK`)
#### 解分配网格员
- **功能**: 将一个网格员从指定的网格中移除。
- **权限**: `ADMIN`
- **URL**: `/api/grids/{gridId}/unassign`
- **方法**: `POST`
- **URL参数**: `gridId` (long)
- **成功响应** (`200 OK`)
### 6.5 分配/解分配网格员 (通过坐标)
#### 分配网格员
- **功能**: 将一个网格员分配到指定坐标的网格。
- **权限**: `ADMIN`, `GRID_WORKER`
- **URL**: `/api/grids/coordinates/{gridX}/{gridY}/assign`
- **方法**: `POST`
- **URL参数**:
- `gridX` (int)
- `gridY` (int)
- **请求体**:
```json
{
"userId": 123
}
```
- **成功响应** (`200 OK`)
#### 解分配网格员
- **功能**: 将一个网格员从指定坐标的网格中移除。
- **权限**: `ADMIN`, `GRID_WORKER`
- **URL**: `/api/grids/coordinates/{gridX}/{gridY}/unassign`
- **方法**: `POST`
- **URL参数**:
- `gridX` (int)
- `gridY` (int)
- **成功响应** (`200 OK`)
---
## 7. 人员管理模块 (`/api/personnel`)
负责系统中所有用户账户的创建、查询、更新和删除 (CRUD)。
### 7.1 获取用户列表 (分页)
- **功能**: 获取系统中的用户列表,支持按角色和姓名进行筛选,并提供分页。
- **权限**: `ADMIN`, `GRID_WORKER`
- **URL**: `/api/personnel/users`
- **方法**: `GET`
- **请求参数**:
- `role` (string, optional): 角色名称 (e.g., `ADMIN`, `GRID_WORKER`)
- `name` (string, optional): 用户姓名 (模糊匹配)
- `page` (int, optional): 页码
- `size` (int, optional): 每页数量
- **成功响应** (`200 OK`): `PageDTO<UserAccount>`
### 7.2 创建新用户
- **功能**: 创建一个新的用户账户。
- **权限**: `ADMIN`
- **URL**: `/api/personnel/users`
- **方法**: `POST`
- **请求体** (`UserCreationRequest`):
```json
{
"name": "John Doe",
"email": "john.doe@example.com",
"password": "strongPassword123",
"role": "GRID_WORKER"
}
```
- **成功响应** (`201 Created`): `UserAccount`
### 7.3 获取用户详情
- **功能**: 根据ID获取单个用户的详细信息。
- **权限**: `ADMIN`
- **URL**: `/api/personnel/users/{userId}`
- **方法**: `GET`
- **URL参数**: `userId` (long)
- **成功响应** (`200 OK`): `UserAccount` (密码字段为空)
### 7.4 更新用户信息
- **功能**: 更新指定用户的信息(例如姓名、邮箱,但不包括角色)。
- **权限**: `ADMIN`
- **URL**: `/api/personnel/users/{userId}`
- **方法**: `PATCH`
- **URL参数**: `userId` (long)
- **请求体** (`UserUpdateRequest`):
```json
{
"name": "Johnathan Doe",
"email": "johnathan.doe@example.com"
}
```
- **成功响应** (`200 OK`): `UserAccount`
### 7.5 更新用户角色
- **功能**: 单独更新用户的角色。
- **权限**: `ADMIN`
- **URL**: `/api/personnel/users/{userId}/role`
- **方法**: `PUT`
- **URL参数**: `userId` (long)
- **请求体** (`UserRoleUpdateRequest`):
```json
{
"role": "SUPERVISOR"
}
```
- **成功响应** (`200 OK`): `UserAccount` (密码字段为空)
### 7.6 删除用户
- **功能**: 从系统中永久删除一个用户。
- **权限**: `ADMIN`
- **URL**: `/api/personnel/users/{userId}`
- **方法**: `DELETE`
- **URL参数**: `userId` (long)
- **成功响应** (`204 No Content`)
---
## 8. 网格员任务模块 (`/api/worker`)
专为网格员 (`GRID_WORKER`) 提供的任务管理接口。
### 8.1 获取我的任务列表
- **功能**: 获取分配给当前登录网格员的任务列表,支持按状态筛选和分页。
- **权限**: `GRID_WORKER`
- **URL**: `/api/worker`
- **方法**: `GET`
- **请求参数**:
- `status` (string, optional): 任务状态 (e.g., `ASSIGNED`, `IN_PROGRESS`, `COMPLETED`)
- `page` (int, optional): 页码
- `size` (int, optional): 每页数量
- **成功响应** (`200 OK`): `Page<TaskSummaryDTO>`
### 8.2 获取任务详情
- **功能**: 获取单个任务的详细信息。
- **权限**: `GRID_WORKER`
- **URL**: `/api/worker/{taskId}`
- **方法**: `GET`
- **URL参数**: `taskId` (long)
- **成功响应** (`200 OK`): `TaskDetailDTO`
### 8.3 接受任务
- **功能**: 网格员接受一个已分配给自己的任务。
- **权限**: `GRID_WORKER`
- **URL**: `/api/worker/{taskId}/accept`
- **方法**: `POST`
- **URL参数**: `taskId` (long)
- **成功响应** (`200 OK`): `TaskSummaryDTO` (更新后的任务摘要)
### 8.4 提交任务完成情况
- **功能**: 网格员提交任务的完成情况,可附带文字说明和文件。
- **权限**: `GRID_WORKER`
- **URL**: `/api/worker/{taskId}/submit`
- **方法**: `POST`
- **内容类型**: `multipart/form-data`
- **URL参数**: `taskId` (long)
- **请求部分**:
- `comments` (string): 任务完成情况的文字说明。
- `files` (File[], optional): (可选) 相关的证明文件或图片。
- **成功响应** (`200 OK`): `TaskSummaryDTO` (更新后的任务摘要)
---
## 9. 地图与寻路模块 (`/api/map`, `/api/pathfinding`)
提供地图数据管理和基于A*算法的路径规划功能。
### 9.1 地图管理 (`/api/map`)
#### 获取完整地图网格
- **功能**: 获取用于渲染前端地图的完整网格数据。
- **URL**: `/api/map/grid`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<MapGrid>`
#### 创建或更新网格单元
- **功能**: 创建或更新单个网格单元的信息,如设置障碍物。
- **URL**: `/api/map/grid`
- **方法**: `POST`
- **请求体** (`MapGrid`):
```json
{
"x": 10,
"y": 15,
"obstacle": true,
"terrainType": "water"
}
```
- **成功响应** (`200 OK`): `MapGrid` (已保存的网格单元)
#### 初始化地图
- **功能**: 清空并重新生成指定大小的地图网格(主要用于开发和测试)。
- **URL**: `/api/map/initialize`
- **方法**: `POST`
- **请求参数**:
- `width` (int, optional, default=20)
- `height` (int, optional, default=20)
- **成功响应** (`200 OK`): `String` (例如, "Initialized a 20x20 map.")
### 9.2 路径规划 (`/api/pathfinding`)
#### 查找路径
- **功能**: 使用A*算法计算两点之间的最短路径,会避开障碍物。
- **URL**: `/api/pathfinding/find`
- **方法**: `POST`
- **请求体** (`PathfindingRequest`):
```json
{
"startX": 0,
"startY": 0,
"endX": 18,
"endY": 18
}
```
- **成功响应** (`200 OK`): `List<Point>` (路径点坐标列表,若无路径则为空列表)
---
## 10. 个人资料模块 (`/api/me`)
处理与当前登录用户个人账户相关的操作。
### 10.1 获取我的反馈历史
- **功能**: 获取当前登录用户提交过的所有反馈记录,支持分页。
- **权限**: `isAuthenticated()` (任何已认证用户)
- **URL**: `/api/me/feedback`
- **方法**: `GET`
- **请求参数**:
- `page` (int, optional): 页码
- `size` (int, optional): 每页数量
- **成功响应** (`200 OK`): `Page<UserFeedbackSummaryDTO>`
---
## 11. 主管审核模块 (`/api/supervisor`)
专为主管 (`SUPERVISOR`) 和管理员 (`ADMIN`) 提供的反馈审核接口。
### 11.1 获取待审核列表
- **功能**: 获取所有状态为"待审核"的反馈列表。
- **权限**: `SUPERVISOR`, `ADMIN`
- **URL**: `/api/supervisor/reviews`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<Feedback>`
### 11.2 批准反馈
- **功能**: 将指定ID的反馈状态变更为"已批准"(通常意味着后续可被分配任务)。
- **权限**: `SUPERVISOR`, `ADMIN`
- **URL**: `/api/supervisor/reviews/{feedbackId}/approve`
- **方法**: `POST`
- **URL参数**: `feedbackId` (long)
- **成功响应** (`200 OK`)
### 11.3 拒绝反馈
- **功能**: 拒绝一个反馈,并记录拒绝理由。
- **权限**: `SUPERVISOR`, `ADMIN`
- **URL**: `/api/supervisor/reviews/{feedbackId}/reject`
- **方法**: `POST`
- **URL参数**: `feedbackId` (long)
- **请求体** (`RejectFeedbackRequest`):
```json
{
"reason": "Insufficient evidence provided."
}
```
- **成功响应** (`200 OK`)
---
## 12. 任务分配模块 (`/api/tasks`)
专为主管 (`SUPERVISOR`) 和管理员 (`ADMIN`) 提供的、将已审核通过的反馈分配为具体任务的接口。
### 12.1 获取未分配的任务
- **功能**: 获取所有已批准但尚未分配给任何网格员的反馈列表。
- **权限**: `SUPERVISOR`, `ADMIN`
- **URL**: `/api/tasks/unassigned`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<Feedback>`
### 12.2 获取可用的网格员
- **功能**: 获取系统中所有角色为 `GRID_WORKER` 的用户列表,用于任务分配。
- **权限**: `SUPERVISOR`, `ADMIN`
- **URL**: `/api/tasks/grid-workers`
- **方法**: `GET`
- **成功响应** (`200 OK`): `List<UserAccount>`
### 12.3 分配任务
- **功能**: 将一个反馈分配给一个网格员,从而创建一个新任务。
- **权限**: `SUPERVISOR`, `ADMIN`
- **URL**: `/api/tasks/assign`
- **方法**: `POST`
- **请求体** (`AssignmentRequest`):
```json
{
"feedbackId": 10,
"assigneeId": 123
}
```
- **成功响应** (`200 OK`): `Assignment` (创建的任务分配记录)
---
## 13. 任务管理模块 (`/api/management/tasks`)

559
Design/PROJECT_README.md Normal file
View File

@@ -0,0 +1,559 @@
# Spring Boot 项目深度解析
本文档旨在深入解析当前 Spring Boot 项目的构建原理、核心技术以及业务逻辑,帮助您更好地理解和准备答疑。
## 一、Spring Boot 核心原理
Spring Boot 是一个用于简化新 Spring 应用的初始搭建以及开发过程的框架。其核心原理主要体现在以下几个方面:
### 1. 自动配置 (Auto-Configuration) - 智能的“管家”
您可以将 Spring Boot 的自动配置想象成一个非常智能的“管家”。在传统的 Spring 开发中您需要手动配置很多东西比如数据库连接、We你b 服务器等,就像您需要亲自告诉管家每一个细节:如何烧水、如何扫地。
而 Spring Boot 的“管家”则会观察您家里添置了什么新东西(即您在项目中加入了什么依赖),然后自动地把这些东西配置好,让它们能正常工作。
**它是如何工作的?**
1. **观察您的“购物清单” (`pom.xml`)**:当您在项目的 `pom.xml` 文件中加入一个 `spring-boot-starter-*` 依赖,比如 `spring-boot-starter-web`,就等于告诉“管家”:“我需要开发一个网站。”
2. **自动准备所需工具**:看到这个“购物清单”后,“管家”会立刻为您准备好一套完整的网站开发工具:
* 一个嵌入式的 **Tomcat 服务器**,这样您就不用自己安装和配置服务器了。
* 配置好 **Spring MVC** 框架,这是处理网页请求的核心工具。
* 设置好处理 JSON 数据的工具 (Jackson),方便前后端通信。
3. **背后的魔法 (`@EnableAutoConfiguration`)**:这一切的起点是您项目主启动类上的 `@SpringBootApplication` 注解,它内部包含了 `@EnableAutoConfiguration`。这个注解就是给“管家”下达“开始自动工作”命令的开关。它会去检查一个固定的清单(位于 `META-INF/spring.factories`),上面写着所有它认识的工具(自动配置类)以及何时应该启用它们。
**项目实例:**
在本项目中,因为 `pom.xml` 包含了 `spring-boot-starter-web`,所以 Spring Boot 自动为您配置了处理 Web 请求的所有环境。这就是为什么您可以在 `com.dne.ems.controller` 包下的任何一个 Controller例如 `AuthController`)中直接使用 `@RestController``@PostMapping` 这样的注解来定义 API 接口,而无需编写任何一行 XML 配置来启动 Web 服务或配置 Spring MVC。
简单来说,**自动配置就是 Spring Boot 替您完成了大部分繁琐、重复的配置工作,让您可以专注于编写业务代码。**
### 2. 起步依赖 (Starter Dependencies) - “主题工具箱”
如果说自动配置是“智能管家”,那么起步依赖就是“管家”使用的“主题工具箱”。您不需要告诉管家需要买钉子、锤子、螺丝刀……您只需要说:“我需要一个木工工具箱。”
起步依赖 (`spring-boot-starter-*`) 就是这样的“主题工具箱”。每一个 starter 都包含了一整套用于某个特定任务的、经过测试、版本兼容的依赖。
**项目实例:**
- **`spring-boot-starter-web`**: 这是“Web 开发工具箱”。它不仅包含了 Spring MVC还包含了内嵌的 Tomcat 服务器和处理验证的库。您只需要引入这一个依赖,所有 Web 开发的基础环境就都准备好了。
- **`spring-boot-starter-data-jpa`**: 这是“数据库操作工具箱”。它打包了与数据库交互所需的一切,包括 Spring Data JPA、Hibernate (JPA 的一种实现) 和数据库连接池 (HikariCP)。您引入它之后,就可以直接在 `com.dne.ems.repository` 包下编写 `JpaRepository` 接口来进行数据库操作,而无需关心如何配置 Hibernate 或事务管理器。
- **`spring-boot-starter-security`**: 这是“安全管理工具箱”。引入它之后Spring Security 框架就被集成进来,提供了认证和授权的基础功能。您可以在 `com.dne.ems.security.SecurityConfig` 中看到对这个“工具箱”的具体配置,比如定义哪些 API 需要登录才能访问。
**优势总结:**
- **简化依赖**:您不必再手动添加一长串的单个依赖项。
- **避免冲突**Spring Boot 已经为您管理好了这些依赖之间的版本,您不必再担心因为版本不兼容而导致的各种奇怪错误。
通过使用这些“工具箱”,您可以非常快速地搭建起一个功能完备的应用程序框架。
### 3. 嵌入式 Web 服务器
Spring Boot 内嵌了常见的 Web 服务器,如 Tomcat、Jetty 或 Undertow。这意味着您不需要将应用程序打包成 WAR 文件部署到外部服务器,而是可以直接将应用程序打包成一个可执行的 JAR 文件,通过 `java -jar` 命令来运行。
- **优势**:简化了部署流程,便于持续集成和持续部署 (CI/CD)。
## 二、前后端分离与交互原理
本项目采用前后端分离的架构,前端(如 Vue.js和后端Spring Boot是两个独立的项目它们之间通过 API 进行通信。
### 1. RESTful API
后端通过暴露一组 RESTful API 来提供服务。REST (Representational State Transfer) 是一种软件架构风格,它定义了一组用于创建 Web 服务的约束。核心思想是,将应用中的所有事物都看作资源,每个资源都有一个唯一的标识符 (URI)。
- **HTTP 方法**
- `GET`:获取资源。
- `POST`:创建新资源。
- `PUT` / `PATCH`:更新资源。
- `DELETE`:删除资源。
### 2. JSON (JavaScript Object Notation)
前后端之间的数据交换格式通常是 JSON。当浏览器向后端发送请求时它可能会在请求体中包含 JSON 数据。当后端响应时,它会将 Java 对象序列化为 JSON 字符串返回给前端。
- **Spring Boot 中的实现**Spring Boot 默认使用 Jackson 库来处理 JSON 的序列化和反序列化。当 Controller 的方法参数有 `@RequestBody` 注解时Spring 会自动将请求体中的 JSON 转换为 Java 对象。当方法有 `@ResponseBody` 或类有 `@RestController` 注解时Spring 会自动将返回的 Java 对象转换为 JSON。
### 3. 认证与授权 (JWT)
在前后端分离架构中,常用的认证机制是 JSON Web Token (JWT)。
- **流程**
1. 用户使用用户名和密码登录。
2. 后端验证凭据,如果成功,则生成一个 JWT 并返回给前端。
3. 前端将收到的 JWT 存储起来(例如,在 `localStorage``sessionStorage` 中)。
4. 在后续的每个请求中,前端都会在 HTTP 请求的 `Authorization` 头中携带这个 JWT。
5. 后端的安全过滤器会拦截每个请求,验证 JWT 的有效性。如果验证通过,则允许访问受保护的资源。
## 三、项目业务逻辑与分层结构分析
本项目是一个环境管理系统 (EMS),其代码结构遵循经典的分层架构模式。
### 1. 分层结构概述
```
+----------------------------------------------------+
| Controller (表现层) |
| (处理 HTTP 请求, 调用 Service, 返回 JSON 响应) |
+----------------------+-----------------------------+
| (调用)
+----------------------v-----------------------------+
| Service (业务逻辑层) |
| (实现核心业务逻辑, 事务管理) |
+----------------------+-----------------------------+
| (调用)
+----------------------v-----------------------------+
| Repository (数据访问层) |
| (通过 Spring Data JPA 与数据库交互) |
+----------------------+-----------------------------+
| (操作)
+----------------------v-----------------------------+
| Model (领域模型) |
| (定义数据结构, 对应数据库表) |
+----------------------------------------------------+
```
### 2. 各层详细分析
#### a. Model (领域模型)
位于 `com.dne.ems.model` 包下,是应用程序的核心。这些是简单的 Java 对象 (POJO),使用 JPA 注解(如 `@Entity`, `@Table`, `@Id`, `@Column`)映射到数据库中的表。
- **核心实体**
- `UserAccount`: 用户账户信息,包含角色、状态等。
- `Task`: 任务实体,包含任务状态、描述、位置等信息。
- `Feedback`: 公众或用户提交的反馈信息。
- `Grid`: 地理网格信息,用于管理和分配任务区域。
- `Attachment`: 附件信息,如上传的图片。
#### b. Repository (数据访问层)
位于 `com.dne.ems.repository` 包下。这些是接口,继承自 Spring Data JPA 的 `JpaRepository`。开发者只需要定义接口Spring Data JPA 会在运行时自动为其提供实现,从而极大地简化了数据访问代码。
- **示例**`TaskRepository` 提供了对 `Task` 实体的 CRUD (创建、读取、更新、删除) 操作,以及基于方法名约定的查询功能(如 `findByStatus(TaskStatus status)`)。
#### c. Service (业务逻辑层)
位于 `com.dne.ems.service` 包下是业务逻辑的核心实现。Service 层封装了应用程序的业务规则和流程,并负责协调多个 Repository 来完成一个完整的业务操作。事务管理也通常在这一层通过 `@Transactional` 注解来声明。
- **主要 Service 及其职责**
- `AuthService`: 处理用户认证,如登录、注册。
- `TaskManagementService`: 负责任务的创建、更新、查询等核心管理功能。
- `TaskAssignmentService`: 负责任务的分配逻辑,如自动分配或手动指派。
- `SupervisorService`: 主管相关的业务逻辑,如审核任务。
- `GridWorkerTaskService`: 网格员的任务处理逻辑,如提交任务进度。
- `FeedbackService`: 处理公众反馈,可能包括创建任务等后续操作。
- `PersonnelService`: 员工管理,如添加、查询用户信息。
- **层间关系**Service 通常会注入 (DI) 一个或多个 Repository 来操作数据。一个 Service 也可能调用另一个 Service 来复用业务逻辑。
#### d. Controller (表现层)
位于 `com.dne.ems.controller` 包下。Controller 负责接收来自客户端的 HTTP 请求,解析请求参数,然后调用相应的 Service 来处理业务逻辑。最后,它将 Service 返回的数据(通常是 DTO封装成 HTTP 响应(通常是 JSON 格式)返回给客户端。
- **主要 Controller 及其职责**
- `AuthController`: 暴露 `/api/auth/login`, `/api/auth/register` 等认证相关的端点。
- `TaskManagementController`: 提供任务管理的 RESTful API`GET /api/tasks`, `POST /api/tasks`
- `SupervisorController`: 提供主管操作的 API如审核、分配任务。
- `FileController`: 处理文件上传和下载。
- **DTO (Data Transfer Object)**:位于 `com.dne.ems.dto` 包下。Controller 和 Service 之间,以及 Controller 和前端之间,通常使用 DTO 来传输数据。这样做的好处是可以避免直接暴露数据库实体,并且可以根据前端页面的需要来定制数据结构,避免传输不必要的信息。
### 3. 安全 (Security)
位于 `com.dne.ems.security` 包下。使用 Spring Security 来提供全面的安全服务。
- `SecurityConfig`: 配置安全规则,如哪些 URL 是公开的,哪些需要认证,以及配置 JWT 过滤器。
- `JwtAuthenticationFilter`: 一个自定义的过滤器,在每个请求到达 Controller 之前执行。它从请求头中提取 JWT验证其有效性并将用户信息加载到 Spring Security 的上下文中。
- `UserDetailsServiceImpl`: 实现了 Spring Security 的 `UserDetailsService` 接口,负责根据用户名从数据库加载用户信息(包括密码和权限)。
## 四、总结
这个 Spring Boot 项目是一个典型的、结构清晰的、基于 RESTful API 的前后端分离应用。它有效地利用了 Spring Boot 的自动配置和起步依赖来简化开发,并通过分层架构将表现、业务逻辑和数据访问清晰地分离开来,使得项目易于理解、维护和扩展。
希望这份文档能帮助您在答疑中充满信心!
## 五、为小白定制:项目代码结构与工作流程详解
为了让您彻底理解代码是如何组织的,我们把整个后端项目想象成一个分工明确的大公司。
### 1. 公司各部门职责 (包/文件夹的作用)
- **`com.dne.ems.config` (后勤与配置部)**
- **职责**: 负责应用的各种基础配置。比如 `WebClientConfig` 是配置网络请求工具的,`WebSocketConfig` 是配置实时通讯的,`DataInitializer` 则是在项目启动时初始化一些默认数据(比如默认的管理员账号)。这个部门确保了其他所有部门的工具和环境都是准备好的。
- **`com.dne.ems.model` (产品设计部)**
- **职责**: 定义公司的核心“产品”是什么样的。这里的“产品”就是数据。例如,`UserAccount.java` 定义了“用户”有哪些属性(姓名、密码、角色等),`Task.java` 定义了“任务”有哪些属性(标题、状态、截止日期等)。它们是整个公司的业务基础,决定了公司要处理什么信息。这些文件直接对应数据库里的表结构。
- **`com.dne.ems.repository` (仓库管理部)**
- **职责**: 专门负责和数据库这个大“仓库”打交道。当需要存取数据时,其他部门不会直接去仓库,而是会通知仓库管理员。例如,`TaskRepository` 提供了所有操作 `Task` 表的方法如保存一个新任务、根据ID查找一个任务。这个部门使用了 Spring Data JPA 技术,所以代码看起来很简单,但功能强大。
- **`com.dne.ems.service` (核心业务部)**
- **职责**: 这是公司的核心部门,负责处理所有实际的业务逻辑。一个业务操作可能很复杂,需要协调多个部门。例如,`TaskManagementService` (任务管理服务) 在创建一个新任务时,可能需要:
1. 调用 `TaskRepository` 将任务信息存入数据库。
2. 调用 `UserAccountRepository` 查找合适的执行人。
3. 调用 `NotificationService` (如果存在的话) 发送通知。
- **`impl` 子目录**: `service` 包下通常是接口 (定义了“能做什么”),而 `impl` (implementation) 包下是这些接口的具体实现 (定义了“具体怎么做”)。这是为了“面向接口编程”,是一种良好的编程习惯。
- **`com.dne.ems.controller` (客户服务与接待部)**
- **职责**: 这是公司的“前台”直接面向客户在这里就是前端浏览器或App。它接收客户的请求HTTP 请求),然后将请求传达给相应的业务部门 (`Service`) 去处理。处理完成后,它再把结果(通常是 JSON 格式的数据)返回给客户。
- 例如,`TaskManagementController` 里的一个 `createTask` 方法,会接收前端发来的创建任务的请求,然后调用 `TaskManagementService``createTask` 方法来真正执行创建逻辑。
- **`com.dne.ems.dto` (数据包装与运输部)**
- **职责**: 负责数据的包装和运输。直接把数据库里的原始数据 (`Model`) 给客户看是不安全也不专业的。DTO (Data Transfer Object) 就是专门用于在各部门之间,特别是“前台”(`Controller`)和客户(前端)之间传递数据的“包装盒”。
- 例如,`TaskDTO` 可能只包含任务的ID、标题和状态而隐藏了创建时间、更新时间等前端不需要的敏感信息。
- **`com.dne.ems.security` (安保部)**
- **职责**: 负责整个公司的安全。`SecurityConfig` 定义了公司的安保规则(比如哪些地方需要门禁卡才能进),`JwtAuthenticationFilter` 就是门口的保安,每个请求进来都要检查它的“门禁卡”(JWT Token) 是否有效。
- **`com.dne.ems.exception` (风险与危机处理部)**
- **职责**: 处理各种突发异常情况。当业务流程中出现错误时(比如找不到用户、数据库连接失败),`GlobalExceptionHandler` 会捕获这些错误,并给出一个统一、友好的错误提示给前端,而不是让程序崩溃。
### 2. 一次典型的请求流程:用户登录
让我们通过“用户登录”这个简单的例子,看看这些部门是如何协同工作的:
1. **客户上门 (前端 -> Controller)**: 用户在前端页面输入用户名和密码,点击登录。前端将这些信息打包成一个 JSON 对象,发送一个 HTTP POST 请求到后端的 `/api/auth/login` 地址。
2. **前台接待 (Controller)**: `AuthController` 接收到这个请求。它看到地址是 `/login`,于是调用 `login` 方法。它从请求中取出 JSON 数据,并将其转换为 `LoginRequest` 这个 DTO 对象。
3. **转交业务部门 (Controller -> Service)**: `AuthController` 自己不处理登录逻辑,它调用 `AuthService``login` 方法,并将 `LoginRequest` 这个 DTO 传递过去。
4. **核心业务处理 (Service)**: `AuthService` 开始处理登录:
a. 它首先需要验证用户名是否存在,于是它请求 **仓库管理部** (`UserAccountRepository`):“请帮我根据这个用户名 (`loginRequest.getUsername()`) 查一下有没有这个人。”
b. `UserAccountRepository` 从数据库中查找到对应的 `UserAccount` (Model) 信息,返回给 `AuthService`
c. `AuthService` 拿到用户信息后,会使用密码加密工具,验证用户提交的密码和数据库中存储的加密密码是否匹配。
d. 如果验证成功,`AuthService` 会请求 **安保部** (`JwtService`):“请为这位用户生成一张有时效的门禁卡 (JWT Token)。”
5. **返回处理结果 (Service -> Controller -> 前端)**: `JwtService` 生成 Token 后返回给 `AuthService``AuthService` 将 Token 包装在一个 `JwtAuthenticationResponse` DTO 中,返回给 `AuthController``AuthController` 再将这个包含 Token 的 DTO 转换成 JSON 格式,作为 HTTP 响应返回给前端。
6. **客户拿到凭证 (前端)**: 前端收到包含 Token 的响应,就知道登录成功了。它会把这个 Token 保存起来,在之后访问需要权限的页面时,都会带上这张“门禁卡”。
通过这个流程,您可以看到,一个简单的请求背后是多个“部门”按照明确的职责划分,层层调用、协同工作的结果。这种结构使得代码逻辑清晰,易于维护和扩展。
## 六、核心业务流程与关键机制分析
下面,我们将梳理出本项目的几个核心业务流程和支撑这些流程的关键技术机制,并详细说明它们涉及的层级和文件,让您清晰地看到数据和指令是如何在系统中流转的。
### (一) 核心业务流程
#### 流程一:用户认证与授权 (Authentication & Authorization)
**目标**:保障系统安全,确保只有授权用户才能访问受保护的资源。
**相关API端点** (`/api/auth`)
- `POST /api/auth/signup`: 用户注册。
- `POST /api/auth/login`: 用户登录。
- `POST /api/auth/send-verification-code`: 发送邮箱验证码。
- `POST /api/auth/request-password-reset`: 请求重置密码。
- `POST /api/auth/reset-password`: 执行密码重置。
**执行路径**
1. **注册 (`signup`)**
* **入口**: `AuthController.signup()` 对应 `POST /api/auth/signup`
* **业务逻辑**: `AuthService.signup()`
* 验证邮箱和验证码的有效性 (`VerificationCodeService`)。
* 检查邮箱是否已注册 (`UserAccountRepository`)。
* 对密码进行加密 (`PasswordEncoder`)。
* 创建并保存新的 `UserAccount` 实体。
2. **登录 (`login`)**
* **入口**: `AuthController.login()` 对应 `POST /api/auth/login`
* **业务逻辑**: `AuthService.login()`
* 通过 Spring Security 的 `AuthenticationManager` 验证用户名和密码。
* 如果认证成功,调用 `JwtService` 生成 JWT。
* 返回包含 JWT 的响应给前端。
3. **访问受保护资源**
* **安全过滤器**: `JwtAuthenticationFilter`
* 拦截所有请求,从 `Authorization` 请求头中提取 JWT。
* 验证 JWT 的有效性 (`JwtService`)。
* 如果有效,从 JWT 中解析出用户信息,并构建一个 `UsernamePasswordAuthenticationToken`,设置到 Spring Security 的 `SecurityContextHolder` 中,完成授权。
#### 流程二:公众反馈与任务转化 (Feedback to Task)
**目标**:将公众通过开放接口提交的反馈(如环境问题)转化为系统内部的可追踪任务。
**相关API端点**
- `POST /api/public/feedback`: 公众提交反馈。
- `GET /api/supervisor/reviews`: 主管获取待审核的反馈列表。
- `POST /api/supervisor/reviews/{feedbackId}/approve`: 主管批准反馈。
- `POST /api/supervisor/reviews/{feedbackId}/reject`: 主管拒绝反馈。
- `POST /api/management/tasks/feedback/{feedbackId}/create-task`: 从反馈创建任务。
**执行路径**
1. **提交反馈**
* **入口**: `PublicController.submitFeedback()` 对应 `POST /api/public/feedback`
* **业务逻辑**: `FeedbackService.createFeedback()`
* 接收 `PublicFeedbackRequest`,包含反馈内容和可选的附件。
* 处理文件上传(如果存在),调用 `FileStorageService` 保存文件,并将附件信息与反馈关联。
* 保存 `Feedback` 实体到数据库。
2. **反馈审核与任务创建 (由主管操作)**
* **入口**: `SupervisorController``TaskManagementController` 的相关方法。
* **业务逻辑**: `SupervisorService.processFeedback()``TaskManagementService.createTaskFromFeedback()`
* 主管通过 `GET /api/supervisor/reviews` 查看待处理反馈。
* 通过 `POST /api/supervisor/reviews/{feedbackId}/approve` 批准反馈。
* 批准后,可通过 `POST /api/management/tasks/feedback/{feedbackId}/create-task` 基于反馈信息创建一个新的 `Task`
#### 流程三:任务生命周期管理 (Task Lifecycle)
**目标**:完整地管理一个任务从创建、分配、执行到完成的全过程。
**相关API端点**
- **主管/管理员**:
- `POST /api/management/tasks`: 直接创建任务。
- `GET /api/management/tasks`: 获取任务列表。
- `POST /api/management/tasks/{taskId}/assign`: 分配任务。
- `POST /api/management/tasks/{taskId}/review`: 审核任务。
- `POST /api/management/tasks/{taskId}/cancel`: 取消任务。
- **网格员**:
- `GET /api/worker`: 获取我的任务列表。
- `GET /api/worker/{taskId}`: 获取任务详情。
- `POST /api/worker/{taskId}/accept`: 接受任务。
- `POST /api/worker/{taskId}/submit`: 提交任务成果。
**执行路径**
1. **任务创建**
* **入口**: `TaskManagementController.createTask()` 对应 `POST /api/management/tasks`
* **业务逻辑**: `TaskManagementService.createTask()`
* 创建 `Task` 实体并设置初始状态(如 `PENDING_ASSIGNMENT`)。
2. **任务分配**
* **入口**: `TaskManagementController.assignTask()` 对应 `POST /api/management/tasks/{taskId}/assign`
* **业务逻辑**: `TaskAssignmentService.assignTaskToWorker()`
* 将任务 (`Task`) 与一个网格员 (`GridWorker`) 关联起来,创建 `Assignment` 记录。
* 更新任务状态为 `ASSIGNED`
3. **任务执行与更新**
* **入口**: `GridWorkerTaskController` 的相关方法,如 `acceptTask()``submitTask()`
* **业务逻辑**: `GridWorkerTaskService.updateTaskProgress()`
* 网格员通过 `POST /api/worker/{taskId}/accept` 接受任务。
* 通过 `POST /api/worker/{taskId}/submit` 提交任务进度或结果,可附带图片等附件 (`FileStorageService`)。
* 更新任务状态(如 `IN_PROGRESS`, `COMPLETED`)。
4. **任务审核与关闭**
* **入口**: `TaskManagementController.reviewTask()` 对应 `POST /api/management/tasks/{taskId}/review`
* **业务逻辑**: `SupervisorService.reviewAndCloseTask()`
* 主管审核已完成的任务。
* 如果合格,将任务状态更新为 `CLOSED`。如果不合格,可打回 (`REJECTED`)。
#### 流程四:数据可视化与决策支持 (Dashboard & AI)
**目标**:为管理层提供数据洞察,并通过 AI 功能辅助决策。
**相关API端点**
- **仪表盘** (`/api/dashboard`)
- `GET /api/dashboard/stats`: 获取核心统计数据。
- `GET /api/dashboard/reports/aqi-distribution`: 获取 AQI 分布。
- `GET /api/dashboard/map/heatmap`: 获取反馈热力图数据。
- `GET /api/dashboard/reports/task-completion-stats`: 获取任务完成情况统计。
- **路径规划** (`/api/pathfinding`):
- `POST /api/pathfinding/find`: 使用 A* 算法寻找路径。
**执行路径**
1. **仪表盘数据**
* **入口**: `DashboardController` 中的各个接口。
* **业务逻辑**: `DashboardService`
* 调用多个 `Repository`(如 `TaskRepository`, `FeedbackRepository`)进行数据聚合查询,统计任务状态、反馈趋势等。
* 返回 DTO 给前端进行图表展示。
2. **AI 辅助功能**
* **文本审核**: `AiReviewService.reviewText()` 可能被用于自动审核反馈内容或评论,识别敏感信息 (无直接API内部调用)。
* **路径规划**: `AStarService.findPath()` 对应 `POST /api/pathfinding/find`,用于计算两点之间的最优路径,可辅助网格员规划任务路线。
### (二) 关键支撑机制
除了核心业务流程,系统还包含一系列关键的技术机制来保证其健壮、安全和可维护性。
1. **文件上传与管理**
* **位置**: `FileStorageService`, `FileController`
* **作用**: 提供统一的文件存储和访问服务。无论是用户头像、反馈附件还是任务报告,都通过此服务进行处理,实现了与具体存储位置(本地文件系统或云存储)的解耦。
2. **邮件与验证码服务**
* **位置**: `MailService`, `VerificationCodeService`
* **作用**: 负责发送邮件(如注册验证、密码重置)和管理验证码的生成与校验,是保障账户安全的重要环节。
3. **登录安全与尝试锁定**
* **位置**: `LoginAttemptService`, `AuthenticationFailureEventListener`
* **作用**: 监听登录失败事件 (`AuthenticationFailureListener`),并通过 `LoginAttemptService` 记录特定 IP 的失败次数。当超过阈值时,会暂时锁定该 IP 的登录权限,有效防止暴力破解攻击。
4. **A* 路径规划算法**
* **位置**: `AStarService`, `PathfindingController`
* **作用**: 实现了 A* 寻路算法,这是一个独立的功能模块,可以根据地图数据(网格 `Grid`)计算最优路径,为其他业务(如任务路线规划)提供支持。
5. **系统事件处理机制**
* **位置**: `event``listener`
* **作用**: 基于 Spring 的事件驱动模型,实现系统内各组件的解耦。例如,当一个用户注册成功后,可以发布一个 `UserRegistrationEvent` 事件,而邮件发送监听器 (`EmailNotificationListener`) 和新用户欢迎监听器 (`WelcomeBonusListener`) 可以分别监听并处理此事件,而无需在注册服务中硬编码这些逻辑。
6. **全局异常处理**
* **位置**: `GlobalExceptionHandler`
* **作用**: 捕获整个应用中抛出的未处理异常,并根据异常类型返回统一、规范的错误响应给前端。这避免了将原始的、可能包含敏感信息的堆栈跟踪暴露给用户,并提升了用户体验。
7. **自定义数据校验**
* **位置**: `validation` 包 (如 `PasswordValidator`)
* **作用**: 实现自定义的、复杂的校验逻辑。例如,`@ValidPassword` 注解可以确保用户设置的密码符合特定的强度要求(如长度、包含数字和特殊字符等)。
8. **数据持久化与查询 (JPA)**
* **位置**: `repository`
* **作用**: 利用 Spring Data JPA通过定义接口 (`extends JpaRepository`) 的方式,极大地简化了数据库的 CRUD 操作和查询。开发者无需编写 SQL 语句JPA 会自动根据方法名或注解生成相应的查询。
9. **API 数据契约 (DTO)**
* **位置**: `dto`
* **作用**: 数据传输对象 (Data Transfer Object) 是前后端分离架构中的关键实践。它作为 API 的“数据契约”,明确了接口需要什么数据、返回什么数据,实现了表现层与领域模型的解耦,使得双方可以独立演进,同时避免了敏感信息的泄露。
10. **应用配置与初始化**
* **位置**: `config` 包, `DataInitializer`
* **作用**: `config` 包集中管理了应用的所有配置类,如安全配置 (`SecurityConfig`)、Web 配置等。`DataInitializer` 则利用 `CommandLineRunner` 接口,在应用启动后执行一次性任务,如创建默认管理员账户、初始化基础数据等,确保了应用开箱即用的能力。
* **密码加密**: 使用 `PasswordEncoder` 对用户密码进行加密,增强安全性。
* **创建用户实体**: 创建一个新的 `UserAccount` 对象 (`Model`),并填充信息。
3. **Repository (数据访问层)**
* **文件**: `com.dne.ems.repository.UserAccountRepository.java`
* **方法**: `save(UserAccount user)`
* **作用**: `AuthService` 调用此方法,将新创建的 `UserAccount` 对象持久化到数据库中。
4. **Model (领域模型)**
* **文件**: `com.dne.ems.model.UserAccount.java`
* **作用**: 定义了用户账户的数据结构,是数据库中 `user_accounts` 表的映射。
### 流程二:任务创建与分配
**目标**:主管或管理员根据一个已批准的反馈,创建一个新任务,并将其分配给一个网格员。
**执行路径**
1. **Controller (表现层)**
* **文件**: `com.dne.ems.controller.TaskManagementController.java`
* **方法**: `createTaskFromFeedback(long feedbackId, TaskFromFeedbackRequest request)`
* **作用**: 接收 `/api/management/tasks/feedback/{feedbackId}/create-task` POST 请求。它将反馈ID和任务创建请求 (`TaskFromFeedbackRequest` DTO) 传递给 Service 层。
2. **Service (业务逻辑层)**
* **文件**: `com.dne.ems.service.impl.TaskManagementServiceImpl.java`
* **方法**: `createTaskFromFeedback(long feedbackId, TaskFromFeedbackRequest request)`
* **作用**: 实现核心的创建和分配逻辑:
* **查找反馈**: 调用 `FeedbackRepository` 找到对应的 `Feedback` (`Model`)。
* **查找负责人**: 调用 `UserAccountRepository` 找到指定的负责人 `UserAccount` (`Model`)。
* **创建任务**: 创建一个新的 `Task` 对象 (`Model`),并从 `Feedback` 和请求 DTO 中填充任务信息(如描述、截止日期、严重性等)。
* **设置状态**: 将任务的初始状态设置为 `ASSIGNED`
3. **Repository (数据访问层)**
* **文件**: `com.dne.ems.repository.TaskRepository.java`
* **方法**: `save(Task task)`
* **作用**: `TaskManagementService` 调用此方法,将新创建的 `Task` 对象保存到数据库。
* **涉及的其他 Repository**: `FeedbackRepository``UserAccountRepository` 用于在业务逻辑中获取必要的数据。
4. **Model (领域模型)**
* **文件**: `com.dne.ems.model.Task.java`
* **作用**: 定义了任务的数据结构。
* **涉及的其他 Model**: `Feedback.java``UserAccount.java` 作为数据来源。
### 流程三:网格员处理任务与提交反馈
**目标**:网格员查看自己的任务,执行任务,并提交处理结果(包括评论和附件)。
**执行路径**
1. **Controller (表现层)**
* **文件**: `com.dne.ems.controller.GridWorkerTaskController.java`
* **方法**: `submitTask(long taskId, String comments, List<MultipartFile> files)`
* **作用**: 接收 `/api/worker/{taskId}/submit` POST 请求。这是一个 `multipart/form-data` 请求因为它同时包含了文本评论和文件。Controller 将这些信息传递给 Service 层。
2. **Service (业务逻辑层)**
* **文件**: `com.dne.ems.service.impl.GridWorkerTaskServiceImpl.java`
* **方法**: `submitTask(long taskId, String comments, List<MultipartFile> files)`
* **作用**: 处理任务提交的业务逻辑:
* **查找任务**: 调用 `TaskRepository` 找到当前需要处理的 `Task` (`Model`)。
* **验证权限**: 确保当前登录的用户就是这个任务的负责人。
* **处理文件上传**: 如果有附件,调用 `FileStorageService` 将文件保存到服务器,并创建 `Attachment` (`Model`) 对象。
* **更新任务状态**: 将任务状态更新为 `SUBMITTED``PENDING_REVIEW`
* **保存评论和附件信息**: 将评论和附件信息关联到任务上。
3. **Repository (数据访问层)**
* **文件**: `com.dne.ems.repository.TaskRepository.java`
* **方法**: `save(Task task)`
* **作用**: `GridWorkerTaskService` 在更新了任务状态和信息后,调用此方法将 `Task` 对象的变化持久化到数据库。
* **涉及的其他 Repository**: `AttachmentRepository` (如果适用) 用于保存附件信息。
4. **Model (领域模型)**
* **文件**: `com.dne.ems.model.Task.java`
* **作用**: 任务数据在此流程中被更新。
* **涉及的其他 Model**: `Attachment.java` 用于表示上传的文件。
* **密码加密**: 使用 `PasswordEncoder` 对用户提交的明文密码进行加密,确保数据库中存储的是密文,保障安全。
* **创建用户实体**: 创建一个新的 `UserAccount` (Model) 对象,并将注册信息(包括加密后的密码和角色)设置到该对象中。
3. **Service -> Repository (数据访问层)**
* **文件**: `com.dne.ems.repository.UserAccountRepository.java`
* **方法**: `save(UserAccount userAccount)`
* **作用**: Service 层调用此方法,将填充好数据的 `UserAccount` 实体对象持久化到数据库中。Spring Data JPA 会自动生成对应的 SQL `INSERT` 语句来完成操作。
4. **返回结果**: 数据成功存入数据库后,操作结果会逐层返回,最终 `AuthController` 会向前端返回一个成功的响应消息。
### 流程二:任务的创建与自动分配
**目标**:系统管理员或主管创建一个新的环境问题任务,并由系统自动分配给相应网格内的网格员。
**执行路径**
1. **前端 -> Controller (表现层)**
* **文件**: `com.dne.ems.controller.TaskManagementController.java`
* **方法**: `createTask(TaskCreateRequest taskCreateRequest)`
* **作用**: 接收 `/api/tasks` POST 请求请求体中包含了任务的详细信息如描述、位置、图片附件ID等封装在 `TaskCreateRequest` DTO 中。Controller 调用 `TaskManagementService` 来处理创建逻辑。
2. **Controller -> Service (业务逻辑层)**
* **文件**: `com.dne.ems.service.impl.TaskManagementServiceImpl.java`
* **方法**: `createTask(TaskCreateRequest taskCreateRequest)`
* **作用**: 创建任务的核心业务逻辑。
* 创建一个新的 `Task` (Model) 实体。
* 根据请求中的附件ID调用 `AttachmentRepository` 查找并关联附件。
* 设置任务的初始状态为 `PENDING` (待处理)。
* 调用 `TaskRepository` 将新任务保存到数据库。
* **触发任务分配**: 保存任务后,调用 `TaskAssignmentService` 来执行自动分配逻辑。
3. **Service -> Service (业务逻辑层内部调用)**
* **文件**: `com.dne.ems.service.impl.TaskAssignmentServiceImpl.java`
* **方法**: `assignTask(Task task)`
* **作用**: 处理任务的分配逻辑。
* 根据任务的地理位置信息,调用 `GridRepository` 找到对应的地理网格 (`Grid`)。
* 根据网格信息,调用 `UserAccountRepository` 找到负责该网格的网格员 (`GridWorker`)。
* 如果找到合适的网格员,则更新 `Task` 实体的 `assignee` (执行人) 字段。
* 更新任务状态为 `ASSIGNED` (已分配)。
* 调用 `TaskRepository` 将更新后的任务信息保存回数据库。
* (可选) 调用通知服务,向被分配的网格员发送新任务通知。
4. **Service -> Repository (数据访问层)**
* **文件**: `com.dne.ems.repository.TaskRepository.java`, `com.dne.ems.repository.GridRepository.java`, `com.dne.ems.repository.UserAccountRepository.java`
* **作用**: 在整个流程中Service 层会频繁地与这些 Repository 交互,以查询网格信息、查询用户信息、保存和更新任务数据。
### 流程三:网格员处理任务并提交反馈
**目标**:网格员在完成任务后,通过 App 或前端页面提交任务处理结果,包括文字描述和现场图片。
**执行路径**
1. **前端 -> Controller (表现层)**
* **文件**: `com.dne.ems.controller.GridWorkerTaskController.java`
* **方法**: `submitTaskFeedback(Long taskId, TaskFeedbackRequest feedbackRequest)`
* **作用**: 接收 `/api/worker/tasks/{taskId}/feedback` POST 请求。路径中的 `taskId` 指明了要为哪个任务提交反馈,请求体 `TaskFeedbackRequest` DTO 中包含了反馈内容和附件ID。
2. **Controller -> Service (业务逻辑层)**
* **文件**: `com.dne.ems.service.impl.GridWorkerTaskServiceImpl.java`
* **方法**: `submitTaskFeedback(Long taskId, TaskFeedbackRequest feedbackRequest)`
* **作用**: 处理网格员提交反馈的业务逻辑。
* 调用 `TaskRepository` 检查任务是否存在以及当前用户是否有权限操作该任务。
* 更新 `Task` 实体的状态为 `COMPLETED` (已完成) 或 `PENDING_REVIEW` (待审核)。
* 创建一个新的 `Feedback` (Model) 实体,将反馈内容和关联的 `Task` 设置进去。
* 调用 `FeedbackRepository` 将新的反馈信息保存到数据库。
* 调用 `TaskRepository` 更新任务状态。
3. **Service -> Repository (数据访问层)**
* **文件**: `com.dne.ems.repository.TaskRepository.java`, `com.dne.ems.repository.FeedbackRepository.java`
* **作用**: `TaskRepository` 用于查询和更新任务信息,`FeedbackRepository` 用于保存新的反馈记录。
通过以上对核心业务流程的梳理,您可以清晰地看到,用户的每一个操作是如何触发后端系统中不同层、不同文件之间的一系列连锁反应,最终完成一个完整的业务闭环。这种清晰的、分层的架构是保证项目可维护性和扩展性的关键。

View File

@@ -0,0 +1,82 @@
# 东软环保公众监督系统 - Vue 界面设计规约
## 1. 设计原则
- **一致性**: 所有界面元素、组件和交互行为应保持高度一致。
- **清晰性**: 界面布局、信息层级和操作指引清晰明确,避免用户困惑。
- **效率性**: 简化操作流程,减少不必要的点击和输入,让用户高效完成任务。
- **响应式**: 确保在不同尺寸的设备上特别是PC端主流分辨率都能获得良好的视觉和交互体验。
- **美观性**: 遵循现代化的审美标准,界面简洁、美观、专业。
## 2. 颜色系统 (Color Palette)
颜色方案以"科技蓝"为主色调,辅以中性灰和功能色,营造专业、冷静、可靠的视觉感受。
- **主色 (Primary)**
- `Brand Blue`: `#409EFF` - 用于关键操作按钮、Logo、导航高亮等。
- **功能色 (Functional)**
- `Success Green`: `#67C23A` - 用于成功提示、操作完成状态。
- `Warning Orange`: `#E6A23C` - 用于警告信息、需要用户注意的操作。
- `Danger Red`: `#F56C6C` - 用于错误提示、删除、高危操作。
- `Info Gray`: `#909399` - 用于普通信息、辅助说明。
- **中性色 (Neutral)**
- `Text Primary`: `#303133` - 主要文字颜色。
- `Text Regular`: `#606266` - 常规文字、次要信息。
- `Text Secondary`: `#909399` - 辅助、占位文字。
- `Border Color`: `#DCDFE6` - 边框、分割线。
- `Background`: `#F5F7FA` - 页面背景色。
- `Container Background`: `#FFFFFF` - 卡片、表格等容器背景色。
## 3. 字体与排版 (Typography)
- **字体族**: `Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif`
- **字号体系**:
- `H1 / 页面主标题`: 24px (Bold)
- `H2 / 区域标题`: 20px (Bold)
- `H3 / 卡片/弹窗标题`: 18px (Medium)
- `正文/表格内容`: 14px (Regular)
- `辅助/说明文字`: 12px (Regular)
- **行高**: `1.6` - `1.8`,保证阅读舒适性。
- **段落间距**: `16px`
## 4. 布局与间距 (Layout & Spacing)
- **主布局**: 采用经典的侧边栏导航 + 内容区的布局方式。
- `NEPM (管理端)` / `NEPV (大屏端)`: 左侧为固定菜单栏,右侧为内容展示区。
- `NEPS (公众端)` / `NEPG (网格员端)`: 根据移动端优先原则可采用底部Tab栏或顶部导航。
- **间距单位**: 以 `8px` 为基础栅格单位。
- `xs`: 4px
- `sm`: 8px
- `md`: 16px
- `lg`: 24px
- `xl`: 32px
- **页面内边距 (Padding)**: 主要内容区域应有 `24px` 的内边距。
- **组件间距 (Gap)**: 卡片、表单项之间保持 `16px``24px` 的间距。
## 5. 核心组件样式规约
### 5.1 按钮 (Button)
- **主按钮 (Primary)**: `background: #409EFF`, `color: #FFFFFF`。用于页面核心操作。
- **次按钮 (Default)**: `border: 1px solid #DCDFE6`, `background: #FFFFFF`。用于非核心或次要操作。
- **危险按钮 (Danger)**: `background: #F56C6C`, `color: #FFFFFF`。用于删除等高危操作。
- **尺寸**: 高度分为 `大 (40px)`, `中 (32px)`, `小 (24px)` 三档。
### 5.2 表格 (Table)
- **表头**: 背景色 `#FAFAFA`, 字体加粗。
- **行**: 偶数行可设置 `#F5F7FA` 的背景色以增强区分度 (斑马纹)。
- **操作区**: 表格最后一列通常为操作区,放置`查看``编辑``删除`等按钮,建议使用文字按钮或小尺寸图标按钮。
### 5.3 表单 (Form)
- **标签 (Label)**: 右对齐,与输入框保持 `8px` 距离。
- **输入框 (Input)**: 统一高度,`hover``focus` 状态有明显视觉反馈 (边框颜色变为 `#409EFF`)。
- **必填项**: 在标签前用红色 `*` 标示。
### 5.4 卡片 (Card)
- **样式**: `background: #FFFFFF`, `border-radius: 8px`, `box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1)`
- **内边距**: `20px`
## 6. 编码与实现规约
- **组件化**: 遵循Vue的组件化思想将可复用的UI和逻辑封装成组件。
- **命名规范**:
- 组件文件名: `PascalCase` (e.g., `UserTable.vue`)
- props/data/methods: `camelCase` (e.g., `tableData`)
- **状态管理**: 使用 `Pinia` 统一管理全局状态,如用户信息、角色权限等。
- **API请求**: 封装 `Axios`,统一处理请求头、响应拦截和错误处理。
- **TypeScript**: 全程使用TypeScript`props`, `emits`, `data``store` 提供明确的类型定义。
- **代码风格**: 遵循 `ESLint + Prettier` 的配置,确保代码风格统一。

View File

@@ -0,0 +1 @@
[This is a placeholder for the decision support dashboard interface image. In a real implementation, this would be an actual PNG image file.]

View File

@@ -0,0 +1,313 @@
# 仪表盘页面设计文档
## 1. 页面概述
仪表盘页面是用户登录后看到的第一个界面,旨在提供系统关键信息的概览。它主要面向管理员、决策者和主管,展示核心业务指标、任务状态分布、近期活动等,以便用户快速掌握系统整体状况并做出决策。
## 2. 页面布局
![仪表盘页面布局示意图](https://placeholder-for-dashboard-page-mockup.png)
### 2.1 布局结构
页面采用响应式网格布局,确保在不同屏幕尺寸下都有良好的可读性。
- **顶部**: 页面标题和一个时间范围选择器,用于筛选仪表盘数据。
- **中部**: 关键指标卡片区域,展示核心数据(如总任务数、待处理反馈等)。
- **下部**:
- **左侧**: 图表区域,通过饼图和柱状图展示任务状态和类型的分布。
- **右侧**: 近期活动或任务列表,展示最新的任务分配或状态变更。
### 2.2 响应式设计
- **桌面端**: 采用多列网格布局,充分利用屏幕空间。
- **平板端**: 网格列数减少,卡片和图表垂直堆叠。
- **移动端**: 单列布局,所有模块垂直排列,保证可读性。
## 3. 组件结构
```vue
<template>
<div class="dashboard-page">
<!-- 页面头部 -->
<el-page-header title="仪表盘" content="系统概览" class="page-header" />
<!-- 时间范围选择器 -->
<div class="dashboard-filters">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="handleDateChange"
/>
</div>
<!-- 加载与错误状态 -->
<div v-if="loading" class="loading-state">
<el-skeleton :rows="5" animated />
</div>
<div v-else-if="error" class="error-state">
<el-alert type="error" :title="error" show-icon :closable="false" />
</div>
<!-- 数据内容 -->
<div v-else class="dashboard-content">
<!-- 关键指标卡片 -->
<el-row :gutter="20" class="summary-cards">
<el-col :span="6" :xs="24" :sm="12" :md="6">
<StatisticCard icon="list" title="总任务数" :value="stats.totalTasks" color="#409eff" />
</el-col>
<el-col :span="6" :xs="24" :sm="12" :md="6">
<StatisticCard icon="clock" title="待处理任务" :value="stats.pendingTasks" color="#e6a23c" />
</el-col>
<el-col :span="6" :xs="24" :sm="12" :md="6">
<StatisticCard icon="chat-dot-round" title="待审核反馈" :value="stats.pendingFeedback" color="#f56c6c" />
</el-col>
<el-col :span="6" :xs="24" :sm="12" :md="6">
<StatisticCard icon="user" title="活跃用户" :value="stats.activeUsers" color="#67c23a" />
</el-col>
</el-row>
<!-- 图表区域 -->
<el-row :gutter="20" class="charts-section">
<el-col :span="12" :xs="24" :sm="24" :md="12">
<el-card shadow="never">
<template #header>任务状态分布</template>
<PieChart :chart-data="taskStatusChartData" />
</el-card>
</el-col>
<el-col :span="12" :xs="24" :sm="24" :md="12">
<el-card shadow="never">
<template #header>任务类型分布</template>
<BarChart :chart-data="taskTypeChartData" />
</el-card>
</el-col>
</el-row>
<!-- 近期任务列表 -->
<el-card shadow="never" class="recent-tasks-section">
<template #header>近期任务动态</template>
<el-table :data="recentTasks" stripe>
<el-table-column prop="title" label="任务标题" />
<el-table-column prop="assignee" label="执行人" />
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag :type="getStatusTagType(row.status)">{{ row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="updatedAt" label="更新时间" />
</el-table>
</el-card>
</div>
</div>
</template>
```
## 4. 数据结构
```typescript
// 仪表盘统计数据
interface DashboardStats {
totalTasks: number;
pendingTasks: number;
pendingFeedback: number;
activeUsers: number;
}
// 图表数据项
interface ChartDataItem {
name: string;
value: number;
}
// 近期任务项
interface RecentTask {
id: number;
title: string;
assignee: string;
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'REVIEWED';
updatedAt: string;
}
// 仪表盘完整数据
interface DashboardData {
stats: DashboardStats;
taskStatusDistribution: ChartDataItem[];
taskTypeDistribution: ChartDataItem[];
recentTasks: RecentTask[];
}
```
## 5. 状态管理
```typescript
// 组件内状态
const dateRange = ref<[Date, Date]>();
const loading = ref<boolean>(true);
const error = ref<string | null>(null);
// 全局状态 (Pinia Store)
const dashboardStore = useDashboardStore();
const { stats, taskStatusChartData, taskTypeChartData, recentTasks } = storeToRefs(dashboardStore);
```
## 6. 交互逻辑
### 6.1 数据加载
```typescript
// 在组件挂载时加载数据
onMounted(() => {
fetchDashboardData();
});
const fetchDashboardData = async () => {
try {
loading.value = true;
error.value = null;
// 从 store 中调用 action 加载数据
await dashboardStore.fetchDashboardData({
startDate: dateRange.value?.[0],
endDate: dateRange.value?.[1],
});
} catch (err) {
error.value = '仪表盘数据加载失败,请稍后重试。';
} finally {
loading.value = false;
}
};
// 当日期范围变化时重新加载数据
const handleDateChange = () => {
fetchDashboardData();
};
```
### 6.2 辅助函数
```typescript
// 根据任务状态返回不同的标签类型
const getStatusTagType = (status: RecentTask['status']) => {
switch (status) {
case 'PENDING': return 'warning';
case 'IN_PROGRESS': return 'primary';
case 'COMPLETED': return 'success';
case 'REVIEWED': return 'info';
default: return 'info';
}
};
```
## 7. API 调用
### 7.1 仪表盘 API
```typescript
// api/dashboard.ts
export const dashboardApi = {
getDashboardData: (params: { startDate?: Date; endDate?: Date }) =>
apiClient.get<DashboardData>('/dashboard', { params })
};
// stores/dashboard.ts
export const useDashboardStore = defineStore('dashboard', {
state: (): DashboardData => ({
stats: { totalTasks: 0, pendingTasks: 0, pendingFeedback: 0, activeUsers: 0 },
taskStatusDistribution: [],
taskTypeDistribution: [],
recentTasks: [],
}),
getters: {
// 将后端数据转换为 ECharts 需要的格式
taskStatusChartData: (state) => ({
legend: state.taskStatusDistribution.map(item => item.name),
series: [{ data: state.taskStatusDistribution }]
}),
taskTypeChartData: (state) => ({
xAxis: state.taskTypeDistribution.map(item => item.name),
series: [{ data: state.taskTypeDistribution.map(item => item.value) }]
})
},
actions: {
async fetchDashboardData(params) {
try {
const { data } = await dashboardApi.getDashboardData(params);
// 更新整个 state
this.$patch(data);
return data;
} catch (error) {
console.error('获取仪表盘数据失败:', error);
throw error;
}
}
}
});
```
## 8. 样式设计
```scss
.dashboard-page {
padding: 24px;
.page-header {
margin-bottom: 24px;
}
.dashboard-filters {
margin-bottom: 24px;
}
.summary-cards {
margin-bottom: 20px;
}
.charts-section {
margin-bottom: 20px;
}
.el-card {
border: none;
border-radius: 8px;
}
.loading-state,
.error-state {
padding: 40px;
}
}
// 响应式样式
@media screen and (max-width: 768px) {
.dashboard-page {
padding: 16px;
.summary-cards .el-col,
.charts-section .el-col {
margin-bottom: 20px;
}
}
}
```
## 9. 测试用例
1. **单元测试**
- 测试 `getStatusTagType` 辅助函数的正确性。
- 测试 Pinia store中getters的数据转换逻辑。
- 测试 `StatisticCard``PieChart``BarChart` 组件的渲染是否正确。
2. **集成测试**
- 测试页面在加载、成功、失败状态下的显示。
- 测试日期选择器筛选功能是否能正确触发数据重新加载。
- 模拟 API 调用,验证数据是否正确渲染到页面上。
## 10. 性能优化
1. **懒加载图表**: 使用 `v-if` 或动态导入,确保图表库只在需要时加载和渲染。
2. **骨架屏**: 在数据加载时使用骨架屏,提升用户体验。
3. **数据缓存**: 对不经常变化的数据(如任务类型),可以在 Pinia store 中设置缓存策略,避免重复请求。
4. **节流/防抖**: 对日期选择器的 `change` 事件使用节流或防抖,防止用户快速操作导致频繁的 API 请求。

View File

@@ -0,0 +1,261 @@
# 反馈管理页面设计文档
## 1. 页面概述
反馈管理页面是管理员和主管处理用户提交的环境问题报告、建议或其他反馈的核心平台。此页面旨在提供一个高效的工作流程,用于审查、分类、回复和跟踪所有用户反馈,确保问题得到及时响应和解决。
## 2. 页面布局
![反馈管理页面布局示意图](https://placeholder-for-feedback-page-mockup.png)
### 2.1 布局结构
页面布局与任务管理页面类似,遵循标准的后台管理界面设计:
- **顶部**: 筛选区域,允许管理员按反馈状态(待处理、已处理)、反馈类型或提交时间进行过滤。
- **中部**: 反馈列表,以表格或卡片列表的形式展示反馈信息。
- **底部**: 分页控件。
### 2.2 响应式设计
- **桌面端**: 使用多列数据表格,清晰展示反馈的各项信息。
- **移动端**: 转换为卡片式列表,每张卡片突出显示反馈标题、提交人和状态,并提供点击查看详情的入口。
## 3. 组件结构
```vue
<template>
<div class="feedback-management-page">
<el-page-header title="反馈管理" content="用户反馈列表" />
<el-card class="page-container">
<!-- 筛选区域 -->
<div class="filter-container">
<el-form :model="filters" inline>
<el-form-item label="处理状态">
<el-select v-model="filters.status" placeholder="请选择状态" clearable>
<el-option label="待处理" value="PENDING" />
<el-option label="已处理" value="RESOLVED" />
</el-select>
</el-form-item>
<el-form-item label="提交人">
<el-input v-model="filters.submitter" placeholder="输入提交人姓名/ID" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</div>
<!-- 反馈表格 -->
<el-table :data="feedbackList" v-loading="loading" stripe>
<el-table-column prop="title" label="反馈标题" min-width="200" />
<el-table-column prop="submitterName" label="提交人" width="120" />
<el-table-column prop="createdAt" label="提交时间" width="180" sortable />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 'PENDING' ? 'warning' : 'success'">
{{ row.status === 'PENDING' ? '待处理' : '已处理' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="handleViewDetails(row)">查看详情</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-if="total > 0"
class="pagination-container"
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<!-- 反馈详情对话框 -->
<FeedbackDetailDialog
v-model="dialogVisible"
:feedback-id="selectedFeedbackId"
@processed="handleProcessed"
/>
</div>
</template>
```
## 4. 数据结构
```typescript
// 反馈筛选条件
interface FeedbackFilters {
status?: 'PENDING' | 'RESOLVED';
submitter?: string;
}
// 反馈列表项
interface FeedbackListItem {
id: number;
title: string;
submitterName: string;
createdAt: string;
status: 'PENDING' | 'RESOLVED';
}
// 反馈详情 (用于对话框)
interface FeedbackDetail extends FeedbackListItem {
content: string;
images: string[]; // 图片 URL 列表
location: string;
contact: string;
handlerNotes?: string; // 处理备注
}
```
## 5. 状态管理
```typescript
// 组件内状态
const filters = ref<FeedbackFilters>({});
const pagination = ref({ page: 1, pageSize: 10 });
const dialogVisible = ref(false);
const selectedFeedbackId = ref<number | null>(null);
// 全局状态 (Pinia Store)
const feedbackStore = useFeedbackStore();
const { feedbackList, total, loading } = storeToRefs(feedbackStore);
```
## 6. 交互逻辑
### 6.1 数据获取与筛选
```typescript
const fetchFeedback = () => {
feedbackStore.fetchFeedbackList({ ...filters.value, ...pagination.value });
};
onMounted(fetchFeedback);
const handleSearch = () => {
pagination.value.page = 1;
fetchFeedback();
};
// 分页逻辑 (handleSizeChange, handleCurrentChange) 与任务管理页面类似
```
### 6.2 查看详情与处理
```typescript
const handleViewDetails = (feedback: FeedbackListItem) => {
selectedFeedbackId.value = feedback.id;
dialogVisible.value = true;
};
// 当详情对话框中完成处理后被调用
const handleProcessed = () => {
dialogVisible.value = false;
fetchFeedback(); // 刷新列表以更新状态
};
```
## 7. API 调用
### 7.1 反馈管理 API
```typescript
// api/feedback.ts
export const feedbackApi = {
getFeedbackList: (params) => apiClient.get('/management/feedback', { params }),
getFeedbackDetail: (id: number) => apiClient.get(`/management/feedback/${id}`),
processFeedback: (id: number, data: { notes: string }) => apiClient.post(`/management/feedback/${id}/process`, data),
};
```
### 7.2 Pinia Store
```typescript
// stores/feedback.ts
export const useFeedbackStore = defineStore('feedback', {
state: () => ({
feedbackList: [] as FeedbackListItem[],
total: 0,
loading: false,
currentFeedbackDetail: null as FeedbackDetail | null,
}),
actions: {
async fetchFeedbackList(params) {
this.loading = true;
try {
const { data } = await feedbackApi.getFeedbackList(params);
this.feedbackList = data.items;
this.total = data.total;
} finally {
this.loading = false;
}
},
async fetchFeedbackDetail(id: number) {
const { data } = await feedbackApi.getFeedbackDetail(id);
this.currentFeedbackDetail = data;
},
async processFeedback(payload: { id: number; notes: string }) {
await feedbackApi.processFeedback(payload.id, { notes: payload.notes });
}
}
});
```
## 8. 样式设计
```scss
.feedback-management-page {
padding: 24px;
.page-container {
margin-top: 24px;
padding: 24px;
border-radius: 8px;
border: none;
}
.filter-container {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 24px;
display: flex;
justify-content: flex-end;
}
}
```
## 9. 关联组件
### `FeedbackDetailDialog.vue`
这是一个关键的子组件,用于显示反馈的完整信息,并提供处理功能。
- **Props**: `modelValue` (for v-model), `feedbackId`
- **功能**:
- 接收 `feedbackId`,在对话框打开时调用 store action 获取反馈详情。
- 展示反馈的文本内容、联系方式、地理位置和图片(图片可点击放大预览)。
- 提供一个文本域供管理员填写处理备注。
- 提供一个"标记为已处理"的按钮,点击后调用 store action 提交处理结果。
- 操作成功后emit 一个 `processed` 事件通知父组件刷新列表。
## 10. 测试用例
1. **单元测试**:
- 测试 Pinia store 的各个 action 是否能正常工作。
- 测试 `FeedbackDetailDialog` 组件在接收到 `feedbackId` 后是否能正确加载和显示数据。
2. **集成测试**:
- 测试筛选功能是否能正确过滤反馈列表。
- 测试分页是否正常。
- 测试点击"查看详情"按钮是否能打开对话框并加载正确的反馈信息。
- 测试在详情对话框中提交处理意见后,列表中的反馈状态是否更新。

View File

@@ -0,0 +1,240 @@
# 文件管理页面设计文档
## 1. 页面概述
文件管理页面为系统管理员提供了一个集中管理所有上传文件的界面。这主要包括用户在提交反馈、任务报告等流程中上传的图片或其他附件。管理员能够在此页面上预览、搜索、筛选和删除文件,以维护系统存储的整洁和合规性。
## 2. 页面布局
![文件管理页面布局示意图](https://placeholder-for-files-page-mockup.png)
### 2.1 布局结构
页面将采用现代化的卡片式画廊Gallery布局以优化图片等视觉文件的预览体验。
- **顶部**: 操作和筛选区域。包含一个上传按钮(如果允许管理员直接上传)和按文件名、上传者或关联模块(如"反馈"、"任务")进行筛选的控件。
- **中部**: 文件画廊/列表。以卡片网格的形式展示文件,每个卡片包含文件缩略图、文件名、大小、上传时间等信息。
- **底部**: 分页控件。
## 3. 组件结构
```vue
<template>
<div class="file-management-page">
<el-page-header title="文件管理" content="系统上传资源概览" />
<el-card class="page-container">
<!-- 筛选与上传 -->
<div class="table-toolbar">
<el-form :model="filters" inline>
<el-form-item>
<el-input v-model="filters.filename" placeholder="按文件名搜索" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
<!-- <el-upload action="/api/files/upload" ... >
<el-button type="primary" icon="Upload">上传文件</el-button>
</el-upload> -->
</div>
<!-- 文件画廊 -->
<el-scrollbar v-loading="loading">
<div class="file-gallery">
<el-card v-for="file in files" :key="file.id" class="file-card" shadow="hover">
<el-image :src="file.url" fit="cover" class="file-thumbnail" :preview-src-list="[file.url]" />
<div class="file-info">
<span class="file-name" :title="file.originalName">{{ file.originalName }}</span>
<span class="file-size">{{ formatFileSize(file.size) }}</span>
<el-popconfirm title="确定要删除此文件吗?" @confirm="handleDelete(file)">
<template #reference>
<el-button link type="danger" icon="Delete" class="delete-btn" />
</template>
</el-popconfirm>
</div>
</el-card>
</div>
</el-scrollbar>
<!-- 分页 -->
<el-pagination
v-if="total > 0"
class="pagination-container"
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
</div>
</template>
```
## 4. 数据结构
```typescript
// 文件筛选条件
interface FileFilters {
filename?: string;
}
// 文件数据
interface FileItem {
id: number;
url: string; // 文件的访问 URL
originalName: string; // 原始文件名
size: number; // 文件大小 (bytes)
mimeType: string;
uploadedAt: string;
}
```
## 5. 状态管理
```typescript
// 组件内状态
const filters = ref<FileFilters>({});
const pagination = ref({ page: 1, pageSize: 20 });
// 全局状态 (Pinia Store)
const fileStore = useFileStore();
const { files, total, loading } = storeToRefs(fileStore);
```
## 6. 交互逻辑
### 6.1 数据获取
```typescript
const fetchFiles = () => {
fileStore.fetchFiles({ ...filters.value, ...pagination.value });
};
onMounted(fetchFiles);
const handleSearch = () => {
pagination.value.page = 1;
fetchFiles();
};
// ... 分页逻辑
```
### 6.2 文件操作
```typescript
const handleDelete = async (file: FileItem) => {
await fileStore.deleteFile(file.id);
ElMessage.success('文件删除成功');
fetchFiles(); // 刷新列表
};
```
### 6.3 辅助函数
```typescript
const formatFileSize = (bytes: number): string => {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
```
## 7. API 调用
```typescript
// api/files.ts
export const fileApi = {
getFiles: (params) => apiClient.get('/files', { params }),
deleteFile: (id: number) => apiClient.delete(`/files/${id}`),
};
// stores/file.ts
export const useFileStore = defineStore('file', {
state: () => ({
files: [] as FileItem[],
total: 0,
loading: false,
}),
actions: {
async fetchFiles(params) {
this.loading = true;
try {
const { data } = await fileApi.getFiles(params);
this.files = data.items;
this.total = data.total;
} finally {
this.loading = false;
}
},
async deleteFile(id: number) {
await fileApi.deleteFile(id);
}
}
});
```
## 8. 样式设计
```scss
.file-management-page {
.page-container {
margin-top: 24px;
}
.file-gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
}
.file-card {
.file-thumbnail {
width: 100%;
height: 150px;
display: block;
}
.file-info {
padding: 10px;
display: flex;
align-items: center;
justify-content: space-between;
.file-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-grow: 1;
margin-right: 10px;
}
.file-size {
font-size: 12px;
color: #909399;
flex-shrink: 0;
}
.delete-btn {
margin-left: 10px;
padding: 2px;
}
}
}
.pagination-container {
margin-top: 24px;
}
}
```
## 9. 测试用例
- **集成测试**:
- 测试页面是否能正确加载并以卡片形式展示文件列表。
- 测试搜索功能是否能正确过滤文件。
- 测试分页功能。
- 测试点击图片是否能触发大图预览。
- 测试删除文件功能,并验证文件是否从列表中移除。

View File

@@ -0,0 +1,550 @@
# EMS 前端设计文档
## 1. 技术栈
- **前端框架**: Vue 3
- **构建工具**: Vite
- **状态管理**: Pinia
- **路由管理**: Vue Router
- **UI 组件库**: Element Plus
- **HTTP 客户端**: Axios
- **CSS 预处理器**: SCSS
- **类型检查**: TypeScript
- **地图组件**: Leaflet.js
- **图表库**: ECharts
- **表单验证**: Vee-Validate
- **国际化**: Vue I18n
- **测试框架**: Vitest
## 2. 架构设计
### 2.1 目录结构
```
ems-frontend/
├── public/ # 静态资源
├── src/
│ ├── api/ # API 请求模块
│ │ ├── auth.ts # 认证相关 API
│ │ ├── dashboard.ts # 仪表盘相关 API
│ │ └── ...
│ ├── assets/ # 静态资源
│ │ ├── styles/ # 全局样式
│ │ └── images/ # 图片资源
│ ├── components/ # 通用组件
│ │ ├── common/ # 基础组件
│ │ ├── layout/ # 布局组件
│ │ └── business/ # 业务组件
│ ├── composables/ # 组合式函数
│ ├── config/ # 配置文件
│ ├── constants/ # 常量定义
│ ├── directives/ # 自定义指令
│ ├── hooks/ # 自定义 Hooks
│ ├── locales/ # 国际化资源
│ ├── router/ # 路由配置
│ ├── stores/ # Pinia 状态管理
│ ├── types/ # TypeScript 类型定义
│ ├── utils/ # 工具函数
│ ├── views/ # 页面组件
│ │ ├── auth/ # 认证相关页面
│ │ ├── dashboard/ # 仪表盘页面
│ │ └── ...
│ ├── App.vue # 根组件
│ ├── main.ts # 入口文件
│ └── env.d.ts # 环境变量类型声明
├── .env # 环境变量
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── index.html # HTML 模板
├── package.json # 依赖配置
├── tsconfig.json # TypeScript 配置
└── vite.config.ts # Vite 配置
```
### 2.2 核心模块
1. **认证模块**: 处理用户登录、注册、密码重置等功能
2. **仪表盘模块**: 展示系统概览、数据统计和图表
3. **地图模块**: 展示地理信息、网格分布和污染热图
4. **任务管理模块**: 处理任务分配、查看和审核
5. **反馈管理模块**: 处理用户反馈的提交和管理
6. **人员管理模块**: 管理系统用户、角色和权限
7. **个人中心模块**: 查看和修改个人信息
## 3. 页面结构
### 3.1 基础布局
系统将采用以下几种基础布局:
1. **主布局**: 包含侧边导航栏、顶部导航栏和内容区域
2. **认证布局**: 用于登录、注册等不需要导航的页面
3. **空白布局**: 用于全屏展示的页面,如地图全屏模式
### 3.2 响应式设计
- 采用移动优先的响应式设计
- 断点设置:
- 移动端: < 768px
- 平板: 768px - 1024px
- 桌面: > 1024px
- 关键组件的响应式行为:
- 侧边栏在移动端变为可折叠抽屉
- 表格在移动端优化为卡片式布局
- 表单在移动端采用单列布局
## 4. 路由设计
### 4.1 路由结构
```javascript
const routes = [
// 认证相关路由
{
path: '/auth',
component: AuthLayout,
children: [
{ path: 'login', component: LoginView },
{ path: 'register', component: RegisterView },
{ path: 'forgot-password', component: ForgotPasswordView },
{ path: 'reset-password', component: ResetPasswordView },
],
meta: { requiresAuth: false }
},
// 主应用路由
{
path: '/',
component: MainLayout,
children: [
{ path: '', component: DashboardView, meta: { requiresAuth: true, roles: ['ADMIN', 'DECISION_MAKER'] } },
{ path: 'map', component: MapView, meta: { requiresAuth: false } },
{ path: 'tasks', component: TasksView, meta: { requiresAuth: true } },
{ path: 'feedback', component: FeedbackView, meta: { requiresAuth: true } },
{ path: 'personnel', component: PersonnelView, meta: { requiresAuth: true, roles: ['ADMIN'] } },
{ path: 'profile', component: ProfileView, meta: { requiresAuth: true } },
]
},
// 错误页面路由
{ path: '/404', component: NotFoundView },
{ path: '/403', component: ForbiddenView },
{ path: '/:pathMatch(.*)*', redirect: '/404' }
]
```
### 4.2 路由守卫
```javascript
router.beforeEach((to, from, next) => {
const authStore = useAuthStore();
// 检查路由是否需要认证
const requiresAuth = to.matched.some(record => record.meta.requiresAuth !== false);
// 检查用户角色权限
const hasRequiredRole = to.matched.every(record => {
if (!record.meta.roles) return true;
return authStore.user && record.meta.roles.includes(authStore.user.role);
});
if (requiresAuth && !authStore.isAuthenticated) {
// 未登录,重定向到登录页
next({ path: '/auth/login', query: { redirect: to.fullPath } });
} else if (requiresAuth && !hasRequiredRole) {
// 无权限访问
next({ path: '/403' });
} else {
// 正常导航
next();
}
});
```
## 5. 状态管理
### 5.1 Pinia Store 设计
```typescript
// 认证状态管理
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null,
token: localStorage.getItem('token'),
error: null,
}),
getters: {
isAuthenticated: state => !!state.token,
userRole: state => state.user?.role || null,
},
actions: {
async login(credentials) { /* 实现登录逻辑 */ },
async logout() { /* 实现登出逻辑 */ },
async fetchUserProfile() { /* 获取用户信息 */ },
}
});
// 任务状态管理
export const useTaskStore = defineStore('task', {
state: () => ({
tasks: [],
currentTask: null,
loading: false,
error: null,
}),
getters: {
tasksByStatus: state => status => state.tasks.filter(task => task.status === status),
},
actions: {
async fetchTasks(filters) { /* 获取任务列表 */ },
async fetchTaskById(id) { /* 获取任务详情 */ },
async assignTask(taskId, userId) { /* 分配任务 */ },
async submitTask(taskId, data) { /* 提交任务 */ },
}
});
// 其他模块状态管理...
```
### 5.2 持久化策略
- 使用 localStorage 存储认证 token
- 关键用户设置使用 localStorage 持久化
- 敏感数据不进行持久化,每次会话重新获取
## 6. API 请求封装
### 6.1 基础请求客户端
```typescript
// api/client.ts
import axios from 'axios';
import { useAuthStore } from '@/stores/auth';
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
}
});
// 请求拦截器
apiClient.interceptors.request.use(
config => {
const authStore = useAuthStore();
if (authStore.token) {
config.headers.Authorization = `Bearer ${authStore.token}`;
}
return config;
},
error => Promise.reject(error)
);
// 响应拦截器
apiClient.interceptors.response.use(
response => response,
error => {
const authStore = useAuthStore();
// 处理 401 未授权错误
if (error.response && error.response.status === 401) {
authStore.logout();
router.push('/auth/login');
}
return Promise.reject(error);
}
);
export default apiClient;
```
### 6.2 模块化 API 封装
```typescript
// api/auth.ts
import apiClient from './client';
export const authApi = {
login: (credentials) => apiClient.post('/auth/login', credentials),
register: (userData) => apiClient.post('/auth/signup', userData),
requestPasswordReset: (email) => apiClient.post('/auth/request-password-reset', { email }),
resetPassword: (data) => apiClient.post('/auth/reset-password', data),
};
// api/tasks.ts
import apiClient from './client';
export const taskApi = {
getTasks: (params) => apiClient.get('/management/tasks', { params }),
getTaskById: (id) => apiClient.get(`/management/tasks/${id}`),
assignTask: (taskId, data) => apiClient.post(`/management/tasks/${taskId}/assign`, data),
reviewTask: (taskId, data) => apiClient.post(`/management/tasks/${taskId}/review`, data),
};
// 其他模块 API...
```
## 7. 组件设计
### 7.1 基础组件
- **AppButton**: 统一按钮样式和行为
- **AppCard**: 卡片容器组件
- **AppForm**: 表单容器组件
- **AppInput**: 输入框组件
- **AppSelect**: 下拉选择组件
- **AppTable**: 表格组件
- **AppPagination**: 分页组件
- **AppModal**: 模态框组件
- **AppAlert**: 提示框组件
### 7.2 业务组件
- **TaskCard**: 任务卡片组件
- **FeedbackForm**: 反馈提交表单
- **UserSelector**: 用户选择器
- **StatusBadge**: 状态标签组件
- **MapGrid**: 地图网格组件
- **PollutionTypeIcon**: 污染类型图标
- **TaskFilterPanel**: 任务筛选面板
- **StatisticCard**: 统计数据卡片
## 8. 主题设计
### 8.1 色彩系统
- **主色**: #1890ff (蓝色)
- **成功色**: #52c41a (绿色)
- **警告色**: #faad14 (黄色)
- **错误色**: #f5222d (红色)
- **中性色**:
- 标题: #262626
- 正文: #595959
- 辅助文字: #8c8c8c
- 禁用: #bfbfbf
- 边框: #d9d9d9
- 分割线: #f0f0f0
- 背景: #f5f5f5
- 表格头部: #fafafa
### 8.2 字体系统
- **主字体**: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Helvetica Neue', Arial, sans-serif
- **代码字体**: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace
- **字体大小**:
- 小号: 12px
- 正文: 14px
- 标题: 16px, 18px, 20px, 24px
### 8.3 间距系统
- **基础单位**: 4px
- **间距尺寸**:
- xs: 4px
- sm: 8px
- md: 16px
- lg: 24px
- xl: 32px
- xxl: 48px
### 8.4 阴影系统
- **轻微阴影**: 0 2px 8px rgba(0, 0, 0, 0.15)
- **中等阴影**: 0 4px 12px rgba(0, 0, 0, 0.15)
- **深度阴影**: 0 8px 16px rgba(0, 0, 0, 0.15)
## 9. 国际化
支持中文和英文两种语言,使用 Vue I18n 实现国际化。
```typescript
// locales/zh-CN.json
{
"auth": {
"login": "登录",
"register": "注册",
"forgotPassword": "忘记密码",
"email": "邮箱",
"password": "密码",
"confirmPassword": "确认密码"
},
"dashboard": {
"title": "仪表盘",
"stats": "统计数据",
"tasks": "任务概览",
"feedback": "反馈概览"
}
}
// locales/en-US.json
{
"auth": {
"login": "Login",
"register": "Register",
"forgotPassword": "Forgot Password",
"email": "Email",
"password": "Password",
"confirmPassword": "Confirm Password"
},
"dashboard": {
"title": "Dashboard",
"stats": "Statistics",
"tasks": "Task Overview",
"feedback": "Feedback Overview"
}
}
```
## 10. 权限控制
### 10.1 角色定义
- **ADMIN**: 系统管理员,拥有所有权限
- **SUPERVISOR**: 主管,负责任务分配和审核
- **GRID_WORKER**: 网格员,负责执行任务
- **DECISION_MAKER**: 决策者,查看数据和报表
### 10.2 权限指令
```typescript
// 创建自定义指令控制元素的显示/隐藏
app.directive('permission', {
mounted(el, binding) {
const { value } = binding;
const authStore = useAuthStore();
if (value && !authStore.hasPermission(value)) {
el.parentNode && el.parentNode.removeChild(el);
}
}
});
// 使用示例
<button v-permission="'ADMIN'"></button>
<div v-permission="['ADMIN', 'SUPERVISOR']"></div>
```
## 11. 错误处理
### 11.1 全局错误处理
```typescript
// 全局错误处理
app.config.errorHandler = (err, vm, info) => {
console.error('全局错误:', err);
// 上报错误到监控系统
errorReportingService.report(err, {
component: vm?.$options?.name,
info,
user: useAuthStore().user?.id
});
// 显示用户友好的错误提示
ElMessage.error('操作失败,请稍后重试');
};
```
### 11.2 API 错误处理
```typescript
// 统一处理 API 错误
export const handleApiError = (error) => {
if (error.response) {
// 服务器响应错误
const { status, data } = error.response;
switch (status) {
case 400:
ElMessage.error(data.message || '请求参数错误');
break;
case 401:
ElMessage.error('会话已过期,请重新登录');
break;
case 403:
ElMessage.error('没有权限执行此操作');
break;
case 404:
ElMessage.error('请求的资源不存在');
break;
case 500:
ElMessage.error('服务器内部错误,请稍后重试');
break;
default:
ElMessage.error('请求失败,请稍后重试');
}
} else if (error.request) {
// 请求发出但没有收到响应
ElMessage.error('网络连接失败,请检查网络设置');
} else {
// 请求配置错误
ElMessage.error('请求配置错误');
}
return Promise.reject(error);
};
```
## 12. 性能优化
### 12.1 代码分割
- 使用动态导入实现路由级别的代码分割
- 将大型第三方库单独打包
### 12.2 资源优化
- 图片资源使用 WebP 格式并进行压缩
- 使用 SVG 图标代替图片图标
- 使用字体图标减少 HTTP 请求
### 12.3 渲染优化
- 使用虚拟滚动处理长列表
- 合理使用 `v-show``v-if`
- 大型表格使用分页加载
- 使用 `keep-alive` 缓存组件状态
## 13. 测试策略
### 13.1 单元测试
- 使用 Vitest 进行单元测试
- 重点测试工具函数、Hooks 和小型组件
- 使用 Vue Test Utils 测试组件
### 13.2 端到端测试
- 使用 Cypress 进行端到端测试
- 覆盖关键用户流程:
- 用户注册和登录
- 任务创建和分配
- 反馈提交和处理
## 14. 部署策略
### 14.1 构建配置
- 开发环境: `vite build --mode development`
- 测试环境: `vite build --mode staging`
- 生产环境: `vite build --mode production`
### 14.2 环境变量
```
# .env.development
VITE_API_BASE_URL=http://localhost:8080/api
VITE_APP_TITLE=EMS开发环境
# .env.production
VITE_API_BASE_URL=/api
VITE_APP_TITLE=环境监测系统
```
### 14.3 CI/CD 流程
- 使用 GitHub Actions 自动化构建和部署
- 提交到 `develop` 分支自动部署到测试环境
- 提交到 `main` 分支自动部署到生产环境

View File

@@ -0,0 +1,232 @@
# 网格管理页面设计文档
## 1. 页面概述
网格管理页面允许系统管理员创建、查看、编辑和删除地理网格。每个网格代表一个责任区域,可以关联到特定的主管或网格员。此功能是任务分配和地理数据可视化的基础。
## 2. 页面布局
![网格管理页面布局示意图](https://placeholder-for-grid-page-mockup.png)
### 2.1 布局结构
页面由两部分组成:
- **左侧**: 网格列表,以表格形式展示所有已定义的网格及其基本信息(名称、负责人等)。
- **右侧**: 交互式地图,用于可视化展示所选网格的地理边界。当创建或编辑网格时,地图将进入绘图模式。
## 3. 组件结构
```vue
<template>
<div class="grid-management-page">
<el-page-header title="网格管理" content="定义地理责任区" />
<el-row :gutter="20" class="page-container">
<!-- 左侧网格列表与操作 -->
<el-col :span="10" :xs="24">
<el-card>
<template #header>
<div class="table-toolbar">
<span>网格列表</span>
<el-button type="primary" icon="Plus" @click="handleCreate">新建网格</el-button>
</div>
</template>
<el-table :data="grids" v-loading="loading" @row-click="handleRowClick" highlight-current-row>
<el-table-column prop="name" label="网格名称" />
<el-table-column prop="manager" label="负责人" />
<el-table-column label="操作" width="120">
<template #default="{ row }">
<el-button link type="primary" @click.stop="handleEdit(row)">编辑</el-button>
<el-popconfirm title="确定删除此网格?" @confirm="handleDelete(row)">
<template #reference>
<el-button link type="danger" @click.stop>删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<!-- 右侧地图显示 -->
<el-col :span="14" :xs="24">
<el-card>
<template #header>地图预览</template>
<div ref="mapContainer" class="map-container"></div>
</el-card>
</el-col>
</el-row>
<!-- 网格编辑/创建对话框 -->
<GridFormDialog v-model="dialogVisible" :grid-id="selectedGridId" @success="onFormSuccess" />
</div>
</template>
```
## 4. 数据结构
```typescript
// 网格列表项
interface GridListItem {
id: number;
name: string;
manager?: string;
// GeoJSON 字符串
geometry: string;
}
// 网格表单数据 (用于对话框)
interface GridFormData {
id?: number;
name: string;
managerId?: number;
// GeoJSON 字符串
geometry: string;
}
```
## 5. 状态管理
```typescript
// 地图实例
let mapInstance = null;
let currentGridLayer = null;
// 组件内状态
const mapContainer = ref<HTMLElement>();
const dialogVisible = ref(false);
const selectedGridId = ref<number | null>(null);
// 全局状态 (Pinia Store)
const gridStore = useGridStore();
const { grids, loading } = storeToRefs(gridStore);
```
## 6. 交互逻辑
### 6.1 地图与列表交互
```typescript
onMounted(() => {
gridStore.fetchGrids();
initializeMap();
});
const initializeMap = () => {
// ... 地图初始化逻辑
};
// 点击表格行,在地图上高亮显示对应网格
const handleRowClick = (row: GridListItem) => {
if (currentGridLayer) {
mapInstance.removeLayer(currentGridLayer);
}
if (row.geometry) {
const geoJson = JSON.parse(row.geometry);
currentGridLayer = L.geoJSON(geoJson).addTo(mapInstance);
mapInstance.fitBounds(currentGridLayer.getBounds());
}
};
```
### 6.2 CRUD 操作
```typescript
const handleCreate = () => {
selectedGridId.value = null;
dialogVisible.value = true;
};
const handleEdit = (grid: GridListItem) => {
selectedGridId.value = grid.id;
dialogVisible.value = true;
};
const handleDelete = async (grid: GridListItem) => {
await gridStore.deleteGrid(grid.id);
ElMessage.success('网格删除成功');
gridStore.fetchGrids();
};
const onFormSuccess = () => {
dialogVisible.value = false;
gridStore.fetchGrids();
};
```
## 7. API 调用
```typescript
// api/grid.ts
export const gridApi = {
getGrids: () => apiClient.get('/grid'),
getGridById: (id: number) => apiClient.get(`/grid/${id}`),
createGrid: (data: GridFormData) => apiClient.post('/grid', data),
updateGrid: (id: number, data: GridFormData) => apiClient.put(`/grid/${id}`, data),
deleteGrid: (id: number) => apiClient.delete(`/grid/${id}`),
};
// stores/grid.ts
export const useGridStore = defineStore('grid', {
state: () => ({
grids: [] as GridListItem[],
loading: false,
}),
actions: {
async fetchGrids() {
this.loading = true;
try {
const { data } = await gridApi.getGrids();
this.grids = data;
} finally {
this.loading = false;
}
},
// ... create, update, delete actions
}
});
```
## 8. 样式设计
```scss
.grid-management-page {
padding: 24px;
.page-container {
margin-top: 24px;
}
.table-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
}
.map-container {
height: 600px;
}
}
```
## 9. 关联组件
### `GridFormDialog.vue`
这是本页面的核心复杂组件,用于创建和编辑网格。
- **Props**: `modelValue`, `gridId`
- **内部组件**:
- 一个表单,包含网格名称、负责人(下拉选择)等字段。
- 一个内嵌的 Leaflet 地图,用于绘制和编辑网格边界。
- **功能**:
- **地图绘制**: 提供多边形Polygon绘制工具。用户可以在地图上绘制闭合区域来定义网格边界。
- **数据加载**: 在编辑模式下,根据 `gridId` 加载网格数据,并在表单和内嵌地图上显示。
- **数据保存**: 用户完成绘制和表单填写后,点击保存。组件将绘制的多边形转换为 GeoJSON 字符串,连同表单数据一起,调用 store 的 action 进行保存。
## 10. 测试用例
- **集成测试**:
- 测试能否成功创建一个新网格,包括在地图上绘制边界和填写信息。
- 测试点击列表中的网格,地图上是否正确显示其边界。
- 测试编辑功能,包括修改网格信息和在地图上重新编辑边界。
- 测试删除网格功能。

View File

@@ -0,0 +1,436 @@
# 登录页面设计文档
## 1. 页面概述
登录页面是用户进入系统的主要入口,提供账号密码登录功能,同时支持记住登录状态和找回密码功能。
## 2. 页面布局
![登录页面布局示意图](https://placeholder-for-login-page-mockup.png)
### 2.1 布局结构
登录页面采用居中卡片式布局,包含以下主要区域:
- 顶部 Logo 和系统名称区域
- 中部登录表单区域
- 底部版权信息和帮助链接区域
### 2.2 响应式设计
- **桌面端**:居中卡片,宽度 400px两侧留白
- **平板端**:居中卡片,宽度 80%,两侧留白
- **移动端**:全宽卡片,上下留白,左右边距 16px
## 3. 组件结构
```vue
<template>
<div class="login-container">
<!-- 顶部 Logo 和系统名称 -->
<div class="login-header">
<img src="@/assets/images/logo.svg" alt="系统 Logo" class="login-logo" />
<h1 class="login-title">环境监测系统</h1>
</div>
<!-- 登录表单 -->
<el-card class="login-card">
<h2 class="login-card-title">用户登录</h2>
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginRules"
class="login-form"
>
<!-- 用户名输入框 -->
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
placeholder="用户名"
prefix-icon="User"
/>
</el-form-item>
<!-- 密码输入框 -->
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="密码"
prefix-icon="Lock"
show-password
/>
</el-form-item>
<!-- 记住我和忘记密码 -->
<div class="login-options">
<el-checkbox v-model="loginForm.remember">记住我</el-checkbox>
<el-link type="primary" @click="forgotPassword">忘记密码</el-link>
</div>
<!-- 登录按钮 -->
<el-button
type="primary"
class="login-button"
:loading="loading"
@click="handleLogin"
>
登录
</el-button>
</el-form>
<!-- 其他登录方式 -->
<div v-if="enableOtherLoginMethods" class="other-login-methods">
<div class="divider">
<span>其他登录方式</span>
</div>
<div class="login-icons">
<el-button circle icon="Wechat" class="icon-button wechat" />
</div>
</div>
<!-- 注册链接 -->
<div class="register-link">
<span>还没有账号</span>
<el-link type="primary" @click="goToRegister">立即注册</el-link>
</div>
</el-card>
<!-- 底部版权信息 -->
<div class="login-footer">
<p>© 2025 环境监测系统 版权所有</p>
<p>
<el-link type="info" href="#">用户协议</el-link>
<el-divider direction="vertical" />
<el-link type="info" href="#">隐私政策</el-link>
</p>
</div>
</div>
</template>
```
## 4. 数据结构
```typescript
interface LoginForm {
username: string;
password: string;
remember: boolean;
}
interface LoginResponse {
token: string;
user: {
id: number;
username: string;
role: string;
permissions: string[];
};
}
```
## 5. 状态管理
```typescript
// 组件内状态
const loginForm = ref<LoginForm>({
username: '',
password: '',
remember: false
});
const loading = ref<boolean>(false);
const loginFormRef = ref<FormInstance>();
const enableOtherLoginMethods = ref<boolean>(true);
// 全局状态 (Pinia Store)
const authStore = useAuthStore();
```
## 6. 交互逻辑
### 6.1 表单验证规则
```typescript
const loginRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度应为 3-20 个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度应为 6-20 个字符', trigger: 'blur' }
]
};
```
### 6.2 登录流程
```typescript
const handleLogin = async () => {
if (!loginFormRef.value) return;
await loginFormRef.value.validate(async (valid) => {
if (!valid) return;
try {
loading.value = true;
// 调用登录 API
await authStore.login({
username: loginForm.value.username,
password: loginForm.value.password
});
// 如果选择记住我,设置本地存储
if (loginForm.value.remember) {
localStorage.setItem('remember_username', loginForm.value.username);
} else {
localStorage.removeItem('remember_username');
}
// 登录成功提示
ElMessage.success('登录成功');
// 获取重定向地址或跳转到首页
const redirect = route.query.redirect as string || '/';
router.push(redirect);
} catch (error) {
// 错误处理
ElMessage.error(error.response?.data?.message || '登录失败,请检查用户名和密码');
} finally {
loading.value = false;
}
});
};
```
### 6.3 其他交互功能
```typescript
// 跳转到注册页面
const goToRegister = () => {
router.push('/auth/register');
};
// 跳转到忘记密码页面
const forgotPassword = () => {
router.push('/auth/forgot-password');
};
// 生命周期钩子,检查是否有记住的用户名
onMounted(() => {
const rememberedUsername = localStorage.getItem('remember_username');
if (rememberedUsername) {
loginForm.value.username = rememberedUsername;
loginForm.value.remember = true;
}
});
```
## 7. API 调用
### 7.1 登录 API
```typescript
// api/auth.ts
export const authApi = {
login: (credentials: { username: string; password: string }) =>
apiClient.post<LoginResponse>('/auth/login', credentials)
};
// stores/auth.ts
export const useAuthStore = defineStore('auth', {
// ... 其他状态和 getters
actions: {
async login(credentials) {
try {
const { data } = await authApi.login(credentials);
this.token = data.token;
this.user = data.user;
// 存储 token 到本地存储
localStorage.setItem('token', data.token);
return data;
} catch (error) {
this.error = error.response?.data?.message || '登录失败';
throw error;
}
}
}
});
```
## 8. 样式设计
```scss
.login-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f5f7fa;
padding: 20px;
.login-header {
text-align: center;
margin-bottom: 30px;
.login-logo {
width: 80px;
height: 80px;
}
.login-title {
font-size: 24px;
color: #303133;
margin-top: 16px;
}
}
.login-card {
width: 400px;
max-width: 100%;
.login-card-title {
font-size: 20px;
text-align: center;
margin-bottom: 30px;
font-weight: 500;
}
.login-form {
margin-bottom: 20px;
.login-options {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.login-button {
width: 100%;
height: 40px;
font-size: 16px;
}
}
.other-login-methods {
margin-top: 24px;
.divider {
display: flex;
align-items: center;
margin: 16px 0;
&::before,
&::after {
content: '';
flex: 1;
height: 1px;
background-color: #dcdfe6;
}
span {
padding: 0 16px;
color: #909399;
font-size: 14px;
}
}
.login-icons {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 16px;
.icon-button {
font-size: 20px;
&.wechat {
color: #07c160;
background-color: #f0f9eb;
border-color: #e1f3d8;
&:hover {
background-color: #e1f3d8;
}
}
}
}
}
.register-link {
text-align: center;
margin-top: 24px;
font-size: 14px;
}
}
.login-footer {
margin-top: 40px;
text-align: center;
color: #909399;
font-size: 12px;
p {
margin: 8px 0;
}
}
}
// 响应式样式
@media screen and (max-width: 768px) {
.login-container {
padding: 16px;
.login-card {
width: 100%;
}
}
}
```
## 9. 安全考虑
1. **密码安全**
- 密码输入框使用 `type="password"``show-password` 属性
- 密码不在前端存储,只在登录时传输
- 密码传输使用 HTTPS 加密
2. **Token 安全**
- Token 存储在 localStorage 中,有 XSS 风险,可考虑使用 HttpOnly Cookie
- Token 过期处理在响应拦截器中统一处理
3. **表单安全**
- 实施前端输入验证,防止基本的注入攻击
- 防止表单重复提交(登录按钮 loading 状态)
## 10. 测试用例
1. **单元测试**
- 测试表单验证逻辑
- 测试记住用户名功能
2. **集成测试**
- 测试登录成功流程
- 测试登录失败处理
- 测试重定向功能
3. **端到端测试**
- 测试完整登录流程
- 测试页面响应式布局
## 11. 性能优化
1. **延迟加载**
- 第三方图标库按需引入
- 忘记密码页面使用动态导入
2. **缓存策略**
- 记住用户名使用 localStorage
- 静态资源logo 等)设置适当的缓存策略

View File

@@ -0,0 +1,296 @@
# 地图页面设计文档
## 1. 页面概述
地图页面是 EMS 系统的核心可视化界面,提供基于地理位置的直观信息展示。它主要用于显示城市的环境网格划分、实时污染数据(如热力图)、网格员位置以及特定任务的地理标记。此页面帮助主管和决策者监控区域状态,并为网格员提供清晰的工作区域指引。
## 2. 页面布局
![地图页面布局示意图](https://placeholder-for-map-page-mockup.png)
### 2.1 布局结构
页面采用全屏地图为主体的布局,辅以侧边栏和浮动控件。
- **主区域**: 全屏交互式地图,作为所有地理信息的载体。
- **左侧边栏**: 用于控制地图上显示的图层,如网格边界、热力图、网格员位置等。同时,也可能包含一个可搜索的兴趣点列表。
- **地图控件**: 地图右上角或左上角放置缩放、定位、全屏等标准地图控件。
- **信息弹窗 (Popup)**: 点击地图上的特定元素(如网格、标记点)时,会弹出信息窗口,显示该元素的详细信息。
### 2.2 响应式设计
- **桌面端**: 显示完整的左侧边栏和地图。
- **平板端/移动端**: 左侧边栏默认收起,可通过按钮展开为抽屉或浮动面板,以最大化地图可视区域。
## 3. 组件结构
```vue
<template>
<div class="map-page">
<!-- 地图容器 -->
<div ref="mapContainer" class="map-container"></div>
<!-- 左侧图层控制面板 -->
<el-card class="layer-control-panel">
<template #header>
<div class="panel-header">
<span>图层控制</span>
<el-button link type="primary" @click="togglePanel" class="panel-toggle-btn">
<el-icon><ArrowLeft v-if="isPanelVisible" /><ArrowRight v-else /></el-icon>
</el-button>
</div>
</template>
<div v-show="isPanelVisible">
<el-checkbox-group v-model="visibleLayers">
<el-checkbox label="grids">显示网格</el-checkbox>
<el-checkbox label="heatmap">显示污染热力图</el-checkbox>
<el-checkbox label="workers">显示网格员位置</el-checkbox>
<el-checkbox label="tasks">显示任务标记</el-checkbox>
</el-checkbox-group>
<el-divider />
<h4>搜索网格</h4>
<el-input v-model="gridSearch" placeholder="输入网格名称/编号搜索" @keyup.enter="searchGrid" />
</div>
</el-card>
<!-- 地图加载状态 -->
<div v-if="mapLoading" class="map-loading-overlay">
<el-icon class="is-loading" size="30"><Loading /></el-icon>
<span>地图加载中...</span>
</div>
</div>
</template>
```
## 4. 数据结构
```typescript
// 网格数据结构
interface GridData {
id: number;
name: string;
// GeoJSON 格式的多边形坐标
geometry: {
type: 'Polygon';
coordinates: number[][][];
};
manager: string; // 网格负责人
}
// 热力图数据点
interface HeatmapPoint {
lat: number;
lng: number;
value: number; // 污染指数
}
// 网格员位置
interface WorkerPosition {
id: number;
name: string;
lat: number;
lng: number;
lastUpdated: string;
}
// 任务标记
interface TaskMarker {
id: number;
title: string;
lat: number;
lng: number;
status: string;
}
```
## 5. 状态管理
```typescript
// 地图服务实例
let mapInstance = null;
let gridLayerGroup = null;
let heatmapLayer = null;
let workerMarkersGroup = null;
// 组件内状态
const mapContainer = ref<HTMLElement | null>(null);
const mapLoading = ref<boolean>(true);
const isPanelVisible = ref<boolean>(true);
const visibleLayers = ref<string[]>(['grids']); // 默认显示的图层
const gridSearch = ref<string>('');
// 全局状态 (Pinia Store)
const mapStore = useMapStore();
const { grids, heatmapData, workers } = storeToRefs(mapStore);
// 监听可见图层变化,动态更新地图
watch(visibleLayers, (newLayers, oldLayers) => {
// ... 调用地图服务方法显示/隐藏图层
});
```
## 6. 交互逻辑
### 6.1 地图初始化
```typescript
onMounted(() => {
initializeMap();
mapStore.fetchInitialMapData();
});
const initializeMap = () => {
if (mapContainer.value) {
mapInstance = L.map(mapContainer.value).setView([39.9042, 116.4074], 10); // 默认北京
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors'
}).addTo(mapInstance);
mapLoading.value = false;
}
};
```
### 6.2 图层控制
```typescript
const updateLayers = () => {
// 网格图层
if (visibleLayers.value.includes('grids') && !mapInstance.hasLayer(gridLayerGroup)) {
gridLayerGroup.addTo(mapInstance);
} else if (!visibleLayers.value.includes('grids') && mapInstance.hasLayer(gridLayerGroup)) {
gridLayerGroup.removeFrom(mapInstance);
}
// 其他图层同理...
};
// 监听 Pinia store 中数据的变化,并更新地图
watch(grids, (newGrids) => {
if (gridLayerGroup) {
gridLayerGroup.clearLayers();
// 添加新的网格到图层
newGrids.forEach(grid => {
const polygon = L.geoJSON(grid.geometry);
polygon.bindPopup(`<b>${grid.name}</b><br>负责人: ${grid.manager}`);
gridLayerGroup.addLayer(polygon);
});
}
});
```
### 6.3 面板交互
```typescript
const togglePanel = () => {
isPanelVisible.value = !isPanelVisible.value;
};
const searchGrid = () => {
const targetGrid = grids.value.find(g => g.name === gridSearch.value);
if (targetGrid) {
// 飞到目标网格位置
const bounds = L.geoJSON(targetGrid.geometry).getBounds();
mapInstance.flyToBounds(bounds);
} else {
ElMessage.info('未找到该网格');
}
};
```
## 7. API 调用
```typescript
// api/map.ts
export const mapApi = {
getGrids: () => apiClient.get<GridData[]>('/map/grids'),
getHeatmapData: () => apiClient.get<HeatmapPoint[]>('/map/heatmap'),
getWorkerPositions: () => apiClient.get<WorkerPosition[]>('/map/workers'),
};
// stores/map.ts
export const useMapStore = defineStore('map', {
state: () => ({
grids: [] as GridData[],
heatmapData: [] as HeatmapPoint[],
workers: [] as WorkerPosition[],
}),
actions: {
async fetchInitialMapData() {
// 并发请求所有地图数据
const [gridsRes, heatmapRes, workersRes] = await Promise.all([
mapApi.getGrids(),
mapApi.getHeatmapData(),
mapApi.getWorkerPositions(),
]);
this.grids = gridsRes.data;
this.heatmapData = heatmapRes.data;
this.workers = workersRes.data;
}
}
});
```
## 8. 样式设计
```scss
.map-page {
position: relative;
width: 100%;
height: calc(100vh - 50px); /* 减去顶部导航栏高度 */
.map-container {
width: 100%;
height: 100%;
z-index: 1;
}
.layer-control-panel {
position: absolute;
top: 20px;
left: 20px;
z-index: 2;
width: 250px;
background: white;
transition: transform 0.3s ease;
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
}
}
.map-loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 3;
flex-direction: column;
gap: 10px;
}
}
```
## 9. 测试用例
1. **单元测试**:
- 测试 Pinia store 的 action 是否能正确获取和存储地图数据。
- 测试地图交互函数,如 `searchGrid` 的逻辑。
2. **集成测试**:
- 验证地图是否能成功初始化并显示底图。
- 验证图层控制复选框是否能正确显示/隐藏对应的地理要素。
- 验证点击网格是否能弹出正确的信息窗口。
- 验证搜索功能是否能正确地将地图视图定位到指定网格。
## 10. 性能优化
1. **大数据渲染**:
- 对于大量的标记点(如网格员、任务),使用 `L.markerClusterGroup` 插件进行聚合,提高性能。
- 对于复杂的网格边界Polygon在低缩放级别下使用简化的几何图形可通过后端服务实现
2. **懒加载**: 地图库Leaflet和相关插件热力图、聚合应按需异步加载。
3. **数据更新**: 网格员位置等实时数据应使用 WebSocket 或定时轮询(配合 `requestAnimationFrame`)进行高效更新,而不是频繁重绘整个图层。

View File

@@ -0,0 +1,296 @@
# 人员管理页面设计文档
## 1. 页面概述
人员管理页面是提供给系统管理员ADMIN用于管理所有用户账户的界面。其核心功能包括展示用户列表、添加新用户、编辑现有用户信息、更改用户角色与状态以及搜索和筛选用户。
## 2. 页面布局
![人员管理页面布局示意图](https://placeholder-for-personnel-page-mockup.png)
### 2.1 布局结构
页面采用经典的后台管理布局,与任务管理、反馈管理页面保持一致:
- **顶部**: 操作区域,包含"添加用户"按钮。
- **中部**: 筛选和搜索区域,支持按用户名、角色或状态进行搜索。
- **下部**: 用户数据表格,展示所有用户及其关键信息,并提供行内操作。
- **底部**: 分页组件。
## 3. 组件结构
```vue
<template>
<div class="personnel-management-page">
<el-page-header title="人员管理" content="系统用户列表" />
<el-card class="page-container">
<!-- 搜索与操作区域 -->
<div class="table-toolbar">
<el-form :model="filters" inline class="filter-form">
<el-form-item>
<el-input v-model="filters.name" placeholder="按姓名或用户名搜索" clearable />
</el-form-item>
<el-form-item>
<el-select v-model="filters.role" placeholder="按角色筛选" clearable>
<el-option label="管理员" value="ADMIN" />
<el-option label="主管" value="SUPERVISOR" />
<el-option label="网格员" value="GRID_WORKER" />
<el-option label="决策者" value="DECISION_MAKER" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
<div class="action-buttons">
<el-button type="primary" icon="Plus" @click="handleCreateUser">添加用户</el-button>
</div>
</div>
<!-- 用户表格 -->
<el-table :data="users" v-loading="loading" stripe>
<el-table-column prop="username" label="用户名" />
<el-table-column prop="name" label="姓名" />
<el-table-column prop="role" label="角色">
<template #default="{ row }">
<span>{{ formatRole(row.role) }}</span>
</template>
</el-table-column>
<el-table-column prop="email" label="邮箱" />
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-switch
v-model="row.isActive"
@change="handleStatusChange(row)"
:loading="row.statusChanging"
active-text="启用"
inactive-text="禁用"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
<el-popconfirm title="确定要删除该用户吗?" @confirm="handleDelete(row)">
<template #reference>
<el-button link type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-if="total > 0"
class="pagination-container"
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<!-- 用户表单对话框 -->
<UserFormDialog
v-model="dialogVisible"
:user-id="selectedUserId"
@success="onFormSuccess"
/>
</div>
</template>
```
## 4. 数据结构
```typescript
// 用户筛选条件
interface UserFilters {
name?: string;
role?: 'ADMIN' | 'SUPERVISOR' | 'GRID_WORKER' | 'DECISION_MAKER';
}
// 用户列表项
interface UserListItem {
id: number;
username: string;
name: string;
email: string;
role: string;
isActive: boolean;
statusChanging?: boolean; // 用于控制 Switch 的 loading 状态
}
// 用户表单数据 (用于对话框)
interface UserFormData {
id?: number;
username: string;
name: string;
email: string;
role: string;
password?: string; // 创建时需要,编辑时可选
}
```
## 5. 状态管理
```typescript
// 组件内状态
const filters = ref<UserFilters>({});
const pagination = ref({ page: 1, pageSize: 10 });
const dialogVisible = ref(false);
const selectedUserId = ref<number | null>(null);
// 全局状态 (Pinia Store)
const personnelStore = usePersonnelStore();
const { users, total, loading } = storeToRefs(personnelStore);
```
## 6. 交互逻辑
### 6.1 CRUD 操作
```typescript
const fetchUsers = () => {
personnelStore.fetchUsers({ ...filters.value, ...pagination.value });
};
onMounted(fetchUsers);
const handleSearch = () => {
pagination.value.page = 1;
fetchUsers();
};
const handleCreateUser = () => {
selectedUserId.value = null;
dialogVisible.value = true;
};
const handleEdit = (user: UserListItem) => {
selectedUserId.value = user.id;
dialogVisible.value = true;
};
const handleDelete = async (user: UserListItem) => {
await personnelStore.deleteUser(user.id);
ElMessage.success('用户删除成功');
fetchUsers(); // 刷新列表
};
const handleStatusChange = async (user: UserListItem) => {
user.statusChanging = true;
try {
await personnelStore.updateUserStatus(user.id, user.isActive);
ElMessage.success('状态更新成功');
} catch {
// 失败时将开关拨回原位
user.isActive = !user.isActive;
} finally {
user.statusChanging = false;
}
};
const onFormSuccess = () => {
dialogVisible.value = false;
fetchUsers();
};
```
### 6.2 辅助函数
```typescript
const formatRole = (role: string) => {
const roleMap = {
ADMIN: '管理员',
SUPERVISOR: '主管',
GRID_WORKER: '网格员',
DECISION_MAKER: '决策者'
};
return roleMap[role] || '未知角色';
};
```
## 7. API 调用
```typescript
// api/personnel.ts
export const personnelApi = {
getUsers: (params) => apiClient.get('/personnel', { params }),
getUserById: (id: number) => apiClient.get(`/personnel/${id}`),
createUser: (data: UserFormData) => apiClient.post('/personnel', data),
updateUser: (id: number, data: UserFormData) => apiClient.put(`/personnel/${id}`, data),
deleteUser: (id: number) => apiClient.delete(`/personnel/${id}`),
};
// stores/personnel.ts
export const usePersonnelStore = defineStore('personnel', {
state: () => ({
users: [] as UserListItem[],
total: 0,
loading: false,
}),
actions: {
async fetchUsers(params) {
this.loading = true;
try {
const { data } = await personnelApi.getUsers(params);
this.users = data.items.map(u => ({ ...u, statusChanging: false }));
this.total = data.total;
} finally {
this.loading = false;
}
},
// ...其他 createUser, updateUser, deleteUser 等 actions
}
});
```
## 8. 样式设计
```scss
.personnel-management-page {
padding: 24px;
.page-container {
margin-top: 24px;
}
.table-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.pagination-container {
margin-top: 24px;
display: flex;
justify-content: flex-end;
}
}
```
## 9. 关联组件
### `UserFormDialog.vue`
用于创建和编辑用户的对话框组件。
- **Props**: `modelValue`, `userId`
- **功能**:
- 如果 `userId` 存在,则为编辑模式,对话框打开时会根据 ID 加载用户信息。
- 如果 `userId` 为空,则为创建模式。
- 包含用户名、姓名、邮箱、角色和密码的表单字段。
- 密码字段在编辑模式下为可选,并提示"留空则不修改密码"。
- 表单提交时进行验证,并调用 store 中对应的 `createUser``updateUser` action。
- 操作成功后,发出 `success` 事件。
## 10. 测试用例
- **集成测试**:
- 测试能否成功添加一个新用户。
- 测试能否成功编辑一个现有用户的信息(包括修改密码和不修改密码两种情况)。
- 测试能否成功删除一个用户。
- 测试启用/禁用开关是否能正确更新用户状态。
- 测试搜索和筛选功能是否能正确过滤用户列表。

View File

@@ -0,0 +1,258 @@
# 个人中心页面设计文档
## 1. 页面概述
个人中心页面允许当前登录的用户查看和修改自己的个人信息,以及更改密码。此页面旨在提供一个简单、安全的界面来管理个人账户资料。
## 2. 页面布局
![个人中心页面布局示意图](https://placeholder-for-profile-page-mockup.png)
### 2.1 布局结构
页面通常采用多标签页Tabs布局将不同功能的表单分开以保持界面整洁。
- **左侧**: 用户头像和基本信息展示。
- **右侧**: 包含两个标签页:
- **基本资料**: 用于修改用户名、姓名、邮箱等信息。
- **修改密码**: 用于更改当前用户的登录密码。
## 3. 组件结构
```vue
<template>
<div class="profile-page">
<el-page-header title="个人中心" content="管理您的账户信息" />
<el-row :gutter="20" class="page-container">
<el-col :span="8" :xs="24">
<el-card class="user-card">
<div class="user-avatar">
<el-avatar :size="100" :src="user.avatarUrl">{{ user.name?.[0] }}</el-avatar>
</div>
<div class="user-info">
<h2>{{ user.name }}</h2>
<p>{{ user.role }}</p>
</div>
</el-card>
</el-col>
<el-col :span="16" :xs="24">
<el-card>
<el-tabs v-model="activeTab">
<!-- 基本资料标签页 -->
<el-tab-pane label="基本资料" name="profile">
<el-form :model="profileForm" :rules="profileRules" ref="profileFormRef" label-width="80px">
<el-form-item label="用户名">
<el-input v-model="profileForm.username" disabled />
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="profileForm.name" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="profileForm.email" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="updateProfile" :loading="profileLoading">
保存更改
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 修改密码标签页 -->
<el-tab-pane label="修改密码" name="password">
<el-form :model="passwordForm" :rules="passwordRules" ref="passwordFormRef" label-width="80px">
<el-form-item label="旧密码" prop="oldPassword">
<el-input type="password" v-model="passwordForm.oldPassword" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input type="password" v-model="passwordForm.newPassword" show-password />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input type="password" v-model="passwordForm.confirmPassword" show-password />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="changePassword" :loading="passwordLoading">
确认修改
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
</div>
</template>
```
## 4. 数据结构
```typescript
// 用户信息 (来自 Store)
interface UserProfile {
id: number;
username: string;
name: string;
email: string;
role: string;
avatarUrl?: string;
}
// 修改密码表单
interface PasswordForm {
oldPassword: string;
newPassword: string;
confirmPassword: string;
}
```
## 5. 状态管理
```typescript
// 组件内状态
const activeTab = ref('profile');
const profileForm = ref<Partial<UserProfile>>({});
const passwordForm = ref<PasswordForm>({ oldPassword: '', newPassword: '', confirmPassword: '' });
const profileLoading = ref(false);
const passwordLoading = ref(false);
const profileFormRef = ref<FormInstance>();
const passwordFormRef = ref<FormInstance>();
// 全局状态 (Pinia Store)
const authStore = useAuthStore();
const { user } = storeToRefs(authStore);
// 当 user 数据从 store 加载后,填充表单
watch(user, (currentUser) => {
if (currentUser) {
profileForm.value = { ...currentUser };
}
}, { immediate: true });
```
## 6. 交互逻辑
### 6.1 更新个人资料
```typescript
const updateProfile = async () => {
await profileFormRef.value?.validate(async (valid) => {
if (!valid) return;
profileLoading.value = true;
try {
await authStore.updateProfile({
name: profileForm.value.name,
email: profileForm.value.email,
});
ElMessage.success('个人资料更新成功');
} finally {
profileLoading.value = false;
}
});
};
```
### 6.2 修改密码
```typescript
const changePassword = async () => {
await passwordFormRef.value?.validate(async (valid) => {
if (!valid) return;
passwordLoading.value = true;
try {
await authStore.changePassword(passwordForm.value);
ElMessage.success('密码修改成功,请重新登录');
// 密码修改成功后通常需要用户重新登录
authStore.logout();
router.push('/auth/login');
} catch (error) {
ElMessage.error(error.response?.data?.message || '密码修改失败');
} finally {
passwordLoading.value = false;
}
});
};
// 自定义密码验证规则
const validateConfirmPassword = (rule: any, value: any, callback: any) => {
if (value !== passwordForm.value.newPassword) {
callback(new Error('两次输入的新密码不一致'));
} else {
callback();
}
};
const passwordRules = {
// ... oldPassword 和 newPassword 的必填和长度规则
confirmPassword: [
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
{ validator: validateConfirmPassword, trigger: 'blur' }
]
};
```
## 7. API 调用
```typescript
// api/me.ts
export const meApi = {
getProfile: () => apiClient.get<UserProfile>('/me'),
updateProfile: (data: Partial<UserProfile>) => apiClient.put('/me', data),
changePassword: (data: PasswordForm) => apiClient.post('/me/change-password', data),
};
// stores/auth.ts
export const useAuthStore = defineStore('auth', {
// ... state, getters
actions: {
async fetchUserProfile() {
// (在App启动或登录后调用)
const { data } = await meApi.getProfile();
this.user = data;
},
async updateProfile(data) {
const { data: updatedUser } = await meApi.updateProfile(data);
this.user = { ...this.user, ...updatedUser };
},
async changePassword(data) {
await meApi.changePassword(data);
}
}
});
```
## 8. 样式设计
```scss
.profile-page {
padding: 24px;
.page-container {
margin-top: 24px;
}
.user-card {
text-align: center;
.user-avatar {
margin-bottom: 20px;
}
.user-info h2 {
font-size: 20px;
margin-bottom: 8px;
}
.user-info p {
color: #909399;
}
}
}
```
## 9. 测试用例
1. **集成测试**:
- 测试页面是否能正确显示当前用户的个人信息。
- 测试更新个人资料功能是否成功,并验证 store 中的用户信息是否同步更新。
- 测试修改密码的成功流程,验证是否提示成功并跳转到登录页。
- 测试修改密码的失败流程(如旧密码错误),验证是否显示正确的错误提示。
- 测试所有表单验证规则是否生效(如邮箱格式、密码长度、两次密码一致性等)。

View File

@@ -0,0 +1,315 @@
# 任务管理页面设计文档
## 1. 页面概述
任务管理页面是系统的核心功能模块之一,允许主管和管理员对任务进行全面的管理。用户可以在此页面上查看所有任务,并使用筛选、排序和搜索功能快速定位任务。此外,页面还支持任务的分配、审核以及查看任务详情等操作。
## 2. 页面布局
![任务管理页面布局示意图](https://placeholder-for-task-page-mockup.png)
### 2.1 布局结构
页面采用经典的后台管理布局:
- **顶部**: 包含页面标题和操作按钮(如"新建任务")。
- **中部**: 筛选和搜索区域,提供多种条件来过滤任务列表。
- **下部**: 任务数据表格,以列表形式展示任务,并包含操作列。
- **底部**: 分页组件,用于浏览大量任务数据。
### 2.2 响应式设计
- **桌面端**: 多列表格,所有筛选条件水平排列。
- **平板端**: 表格列数减少,部分次要信息可能隐藏,筛选条件折叠或垂直排列。
- **移动端**: 表格转为卡片列表视图,每个卡片显示一个任务的核心信息,筛选功能通过抽屉或模态框提供。
## 3. 组件结构
```vue
<template>
<div class="task-management-page">
<!-- 页面头部 -->
<el-page-header title="任务管理" content="任务列表与分配" />
<el-card class="page-container">
<!-- 筛选与搜索区域 -->
<div class="filter-container">
<el-form :model="filters" inline>
<el-form-item label="任务状态">
<el-select v-model="filters.status" placeholder="请选择状态" clearable>
<el-option label="待处理" value="PENDING" />
<el-option label="进行中" value="IN_PROGRESS" />
<el-option label="已完成" value="COMPLETED" />
<el-option label="已审核" value="REVIEWED" />
</el-select>
</el-form-item>
<el-form-item label="执行人">
<el-input v-model="filters.assignee" placeholder="请输入执行人" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 操作按钮区域 -->
<div class="action-buttons">
<el-button type="primary" icon="Plus" @click="handleCreateTask" v-permission="'SUPERVISOR'">
新建任务
</el-button>
</div>
<!-- 任务表格 -->
<el-table :data="tasks" v-loading="loading" stripe>
<el-table-column type="index" width="50" />
<el-table-column prop="title" label="任务标题" min-width="150" />
<el-table-column prop="grid" label="所属网格" />
<el-table-column prop="assignee" label="执行人" />
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag :type="getStatusTagType(row.status)">{{ formatStatus(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" sortable />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="handleView(row)">查看</el-button>
<el-button link type="primary" @click="handleAssign(row)" v-if="canAssign(row)">分配</el-button>
<el-button link type="primary" @click="handleReview(row)" v-if="canReview(row)">审核</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
v-if="total > 0"
class="pagination-container"
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<!-- 任务分配/审核/新建的对话框 -->
<TaskFormDialog v-model="dialogVisible" :task-id="selectedTaskId" :mode="dialogMode" @success="handleSuccess" />
</div>
</template>
```
## 4. 数据结构
```typescript
// 任务筛选条件
interface TaskFilters {
status?: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'REVIEWED';
assignee?: string;
}
// 分页信息
interface Pagination {
page: number;
pageSize: number;
}
// 任务列表项
interface TaskItem {
id: number;
title: string;
grid: string;
assignee: string | null;
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'REVIEWED';
createdAt: string;
}
// 对话框模式
type DialogMode = 'create' | 'assign' | 'review' | 'view';
```
## 5. 状态管理
```typescript
// 组件内状态
const filters = ref<TaskFilters>({});
const pagination = ref<Pagination>({ page: 1, pageSize: 10 });
const dialogVisible = ref(false);
const selectedTaskId = ref<number | null>(null);
const dialogMode = ref<DialogMode>('view');
// 全局状态 (Pinia Store)
const taskStore = useTaskStore();
const { tasks, total, loading } = storeToRefs(taskStore);
const authStore = useAuthStore();
const { user } = storeToRefs(authStore);
```
## 6. 交互逻辑
### 6.1 数据获取与筛选
```typescript
const fetchTasks = () => {
taskStore.fetchTasks({ ...filters.value, ...pagination.value });
};
onMounted(fetchTasks);
const handleSearch = () => {
pagination.value.page = 1;
fetchTasks();
};
const handleReset = () => {
filters.value = {};
handleSearch();
};
const handleSizeChange = (size: number) => {
pagination.value.pageSize = size;
fetchTasks();
};
const handleCurrentChange = (page: number) => {
pagination.value.page = page;
fetchTasks();
};
```
### 6.2 表格操作
```typescript
const handleView = (task: TaskItem) => {
selectedTaskId.value = task.id;
dialogMode.value = 'view';
dialogVisible.value = true;
};
const handleAssign = (task: TaskItem) => {
selectedTaskId.value = task.id;
dialogMode.value = 'assign';
dialogVisible.value = true;
};
const handleReview = (task: TaskItem) => {
selectedTaskId.value = task.id;
dialogMode.value = 'review';
dialogVisible.value = true;
};
const handleCreateTask = () => {
selectedTaskId.value = null;
dialogMode.value = 'create';
dialogVisible.value = true;
};
const handleSuccess = () => {
dialogVisible.value = false;
fetchTasks(); // 操作成功后刷新列表
};
```
### 6.3 权限控制
```typescript
const canAssign = (task: TaskItem) => {
return user.value?.role === 'SUPERVISOR' && task.status === 'PENDING';
};
const canReview = (task: TaskItem) => {
return user.value?.role === 'SUPERVISOR' && task.status === 'COMPLETED';
};
```
### 6.4 辅助函数
```typescript
const formatStatus = (status: TaskItem['status']) => {
const map = { PENDING: '待处理', IN_PROGRESS: '进行中', COMPLETED: '已完成', REVIEWED: '已审核' };
return map[status] || '未知';
};
// getStatusTagType 函数同仪表盘页面设计
```
## 7. API 调用
```typescript
// api/tasks.ts
export const taskApi = {
getTasks: (params) => apiClient.get('/management/tasks', { params }),
// ... 其他任务相关API
};
// stores/task.ts
export const useTaskStore = defineStore('task', {
state: () => ({
tasks: [] as TaskItem[],
total: 0,
loading: false,
}),
actions: {
async fetchTasks(params) {
this.loading = true;
try {
const { data } = await taskApi.getTasks(params);
this.tasks = data.items;
this.total = data.total;
} catch (error) {
console.error('获取任务列表失败', error);
} finally {
this.loading = false;
}
}
}
});
```
## 8. 样式设计
```scss
.task-management-page {
padding: 24px;
.page-container {
margin-top: 24px;
padding: 24px;
border-radius: 8px;
border: none;
}
.filter-container {
margin-bottom: 20px;
}
.action-buttons {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 24px;
display: flex;
justify-content: flex-end;
}
}
```
## 9. 测试用例
1. **单元测试**:
- 测试权限控制函数 `canAssign``canReview` 的逻辑。
- 测试 `formatStatus` 辅助函数的正确性。
- 测试 Pinia store 的 action 是否能正确调用 API 并更新 state。
2. **集成测试**:
- 测试筛选、搜索和重置功能是否正常工作。
- 测试分页组件是否能正确切换页面和每页数量。
- 测试"查看"、"分配"、"审核"、"新建"按钮能否正确打开对应模式的对话框。
- 测试对话框操作成功后,任务列表是否刷新。
## 10. 性能优化
1. **后端分页**: 必须使用后端分页来处理大量任务数据。
2. **防抖**: 对搜索输入框使用防抖处理,避免用户输入时频繁触发 API 请求。
3. **组件懒加载**: `TaskFormDialog` 组件可以通过动态导入实现懒加载,只在需要时才加载。

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
[This is a placeholder for the grid worker dashboard interface image. In a real implementation, this would be an actual PNG image file.]

4362
Design/业务流程.md Normal file
View File

@@ -0,0 +1,4362 @@
# EMS 后端业务流程与架构深度解析
本文档旨在深入剖析应急管理系统EMS后端的核心业务流程、分层架构以及关键技术机制。通过对典型请求生命周期的分析揭示系统各组件如何协同工作以实现高效、可靠的业务功能。
在您的项目中,主要有以下几类常见的注解:
### 1. JPA (Java Persistence API) 注解 - 用于数据库交互
这类注解通常用在 model 或 entity 包下的类中用于将一个Java对象映射到数据库的一张表。
@Entity : 声明这个类是一个实体类,它对应数据库中的一张表。
@Table(name = "aqi_data") : 正如您在 `AqiData.java` 中看到的,这个注解用来指定该实体类对应数据库中的具体表名。在这里, AqiData 类对应 aqi_data 这张表。
@Id : 指定这个字段是表的主键。
@Column(name = "user_id") : 指定字段对应数据库表中的列名。
@GeneratedValue : 定义主键的生成策略(例如,自增长)。
### 2. Spring 框架注解 - 用于管理组件和处理请求
这是项目中最核心的注解用于实现依赖注入DI和面向切面编程AOP
@Component : 通用的组件注解,表明这个类由 Spring 容器管理。
@Service : 通常用在业务逻辑层Service层@Component 的一种特化。
@Repository : 通常用在数据访问层DAO/Repository层也是 @Component 的一种特化。
@RestController : 用在控制器Controller表明这个类处理HTTP请求并且返回的数据会自动序列化为JSON格式。
@Autowired : 自动注入依赖。Spring会自动寻找匹配的Bean@Component 等注解标记的类的实例)并注入到该字段。
@RequestMapping , @GetMapping , @PostMapping : 将HTTP请求的URL路径映射到具体的处理方法上。
### 3. Lombok 注解 - 用于减少样板代码
Lombok是一个非常实用的Java库可以通过注解在编译时自动生成代码。
@Data : 一个非常方便的组合注解,会自动为类的所有字段生成 getter 、 setter 方法,以及 toString() , equals() , hashCode() 方法。
@NoArgsConstructor : 生成一个无参构造函数。
@AllArgsConstructor : 生成一个包含所有字段的构造函数。
@Slf4j : 为类自动创建一个 log 静态常量,方便您直接使用 log.info(...) 等方法打印日志。
## 一、 系统核心思想与分层架构
EMS 后端遵循业界主流的**分层架构**设计,旨在实现**高内聚、低耦合**的目标。这种架构将应用程序划分为不同的逻辑层,每一层都承担明确的职责。各层之间通过定义好的接口进行通信,使得系统更易于开发、测试、维护和扩展。
### 1.1 分层架构概览
系统从上至下主要分为以下几个核心层级,对应于项目中的不同包:
- **Controller (控制层)**: `com.dne.ems.controller`
- **职责**: 作为应用的入口,负责接收来自客户端(前端)的 HTTP 请求,解析请求参数,并调用服务层的相应方法来处理业务逻辑。它不包含任何业务逻辑,仅作为请求的调度和转发中心。
- **交互**: 将处理结果(通常是 DTO 对象)封装成统一的 JSON 格式(`ApiResponse`)返回给客户端。
- **Service (服务层)**: `com.dne.ems.service`
- **职责**: 系统的核心,负责实现所有复杂的业务逻辑。它编排一个或多个 Repository 来完成特定功能,处理数据、执行计算、实施业务规则,并处理事务。
- **交互**: 被 Controller 层调用,同时调用 Repository 层来访问数据库。
- **Repository (数据访问层)**: `com.dne.ems.repository`
- **职责**: 定义数据持久化的接口,负责与数据库进行交互。通过继承 Spring Data JPA 的 `JpaRepository`,开发者无需编写具体的 SQL 语句即可实现对数据的增删改查CRUD以及复杂的查询。
- **交互**: 被 Service 层调用,直接操作数据库。
- **Model (模型层)**: `com.dne.ems.model`
- **职责**: 定义系统的领域对象即与数据库表结构一一对应的实体类Entity。它们使用 JPA 注解(如 `@Entity`, `@Table`, `@Id`)来映射数据库表和字段。
- **交互**: 在 Repository 层和 Service 层之间传递数据。
- **DTO (数据传输对象)**: `com.dne.ems.dto`
- **职责**: 作为各层之间,特别是 Controller 层与外部客户端之间的数据载体。DTO 的设计可以根据 API 的需要进行定制,隐藏内部领域模型的复杂性,避免暴露不必要的数据,从而提高系统的安全性与灵活性。
### 1.2 辅助模块
除了核心分层,系统还包含一系列重要的辅助模块,以支持整体功能的实现:
- **Security (安全模块)**: `com.dne.ems.security`
- **职责**: 基于 Spring Security 实现用户认证Authentication和授权Authorization。它包含 JWT 的生成与校验、密码加密、以及精细化的接口访问控制策略。
- **Exception (异常处理模块)**: `com.dne.ems.exception`
- **职责**: 提供全局统一的异常处理机制。通过 `@ControllerAdvice`,系统可以捕获在业务流程中抛出的各类异常,并将其转换为对客户端友好的、标准化的错误响应。
- **Config (配置模块)**: `com.dne.ems.config`
- **职责**: 存放应用的配置类,如 Spring Security 配置、Web MVC 配置、以及应用启动时的数据初始化逻辑(`DataInitializer`。该模块为EMS系统提供了完整的基础设施支持包括数据初始化、文件存储配置、API文档配置、HTTP客户端配置、WebSocket配置以及数据转换器等核心配置组件。
- **Controller (控制器模块)**: `com.dne.ems.controller`
- **职责**: 作为系统的API入口层负责处理所有HTTP请求包括用户认证、反馈管理、任务分配、数据可视化等核心业务功能。该模块包含14个专业化控制器每个控制器专注于特定的业务领域提供RESTful API接口支持权限控制、参数验证、分页查询等功能。
- **Event/Listener (事件驱动模块)**: `com.dne.ems.event`, `com.dne.ems.listener`
- **职责**: 实现应用内的异步处理和解耦。例如,用户认证失败时,可以发布一个 `AuthenticationFailureEvent` 事件,由专门的监听器 `AuthenticationFailureEventListener` 异步处理登录失败次数的记录,而不会阻塞主认证流程。
## 二、 项目亮点与创新
本项目在传统的城市管理系统基础上,融合了多项现代技术与创新理念,旨在打造一个**智能、高效、协同**的城市环境治理平台。其核心亮点体现在以下几个方面:
### 2.1 业务全流程智能化
- **AI 预审核**: 公众提交的反馈信息不再需要人工进行初步筛选。系统集成的 AI 模型能够自动对反馈内容(包括文本和图像)进行分析,判断其有效性、紧急程度,并进行初步的分类。这极大地减轻了主管人员的工作负担,并将他们的精力解放出来,专注于更复杂的决策和任务分配。
- **智能任务推荐与调度**: 在任务分配环节,系统能够基于网格员的当前位置、历史任务完成效率、技能特长以及当前负载情况,智能推荐最合适的执行人选。在紧急情况下,系统甚至可以自动进行任务的调度与指派,确保问题得到最快响应。
### 2.2 事件驱动的异步架构
- **松耦合与高响应**: 项目广泛采用了**事件驱动架构 (EDA)**。例如,当一个反馈提交后,系统会发布一个 `FeedbackSubmittedEvent` 事件,后续的 AI 分析、通知发送等多个操作都由独立的监听器异步执行。这种设计使得核心业务流程(如提交反馈)能够瞬间完成,极大地提升了用户体验和系统的吞吐量。同时,模块之间通过事件进行通信,而不是直接调用,实现了高度解耦,使得系统更容易扩展和维护。
### 2.3 全方位的数据驱动决策
- **实时数据大屏**: 决策者不再依赖于滞后的纸质报告。系统提供了一个动态、可交互的数据驾驶舱实时展示城市环境的各项关键指标KPIs如 AQI 分布、任务完成率、反馈热力图等。所有数据都以直观的图表形式呈现,帮助管理者快速洞察问题、发现趋势,从而做出更科学的决策。
- **绩效与资源优化**: 系统通过对历史数据的深度分析,能够评估不同区域的环境治理成效、各个网格员的工作效率,为绩效考核提供客观依据,并为未来的资源调配和政策制定提供数据支持。
### 2.4 四端一体的协同工作平台
- **角色精准赋能**: 系统为四类核心用户——**公众、网格员、主管、决策者**——分别设计了专属的客户端Web 或移动应用),精准满足不同角色的工作需求。
- **公众端**: 简化提交流程,提供实时进度查询。
- **网格员端**: 移动化接单、处理、上报,并提供导航支持。
- **主管端**: 集中审核、智能分配、实时监控。
- **决策者端**: 宏观数据洞察,支持战略决策。
- **无缝信息流转**: 通过统一的后端平台,信息可以在四个客户端之间无缝流转,形成从问题发现、任务分配、现场处理到决策分析的闭环管理,打破了传统工作模式中的信息孤岛。
## 三、 核心业务流程与关键机制深度解析
下面,我们将以具体的业务场景和技术实现为例,详细拆解请求在系统分层架构中的流转过程。
### 2.1 用户认证与授权
此流程是系统的安全基石,涉及用户登录、权限验证和 Token 管理。
#### API 接口
- **用户注册**: `POST /api/auth/signup`
- **用户登录**: `POST /api/auth/login`
- **发送验证码**: `POST /api/auth/send-verification-code`
- **请求重置密码**: `POST /api/auth/request-password-reset`
- **重置密码**: `POST /api/auth/reset-password`
**场景:用户使用用户名和密码登录 (POST /api/auth/login)**
1. **前端 -> Controller (`AuthController`)**
- **动作**: 用户在登录页输入凭据,前端发送 `POST` 请求到 `/api/auth/login`,请求体为包含 `username``password``LoginRequest` DTO。
- **Controller 角色**: `AuthController``authenticateUser` 方法接收请求。`@Valid` 注解确保了输入的基本格式正确。Controller 将 `LoginRequest` 直接传递给 `AuthService` 进行处理。
2. **Controller -> Service (`AuthService`)**
- **动作**: `AuthService` 调用 Spring Security 的 `AuthenticationManager``authenticate` 方法。`AuthenticationManager` 是认证的核心入口。
- **Service 角色**: `AuthService` 编排了整个认证流程:
- **凭证封装**: 将用户名和密码封装成 `UsernamePasswordAuthenticationToken`
- **认证委托**: 将此 `token` 交给 `AuthenticationManager``AuthenticationManager` 会遍历其内部的 `AuthenticationProvider` 列表(在我们的例子中是 `DaoAuthenticationProvider`)来寻找能处理此 `token` 的提供者。
- **用户查找**: `DaoAuthenticationProvider` 会使用 `UserDetailsService` (我们的实现是 `UserDetailsServiceImpl`) 的 `loadUserByUsername` 方法,根据用户名去数据库查找用户。`UserDetailsServiceImpl` 会调用 `UserRepository`
- **密码比对**: `DaoAuthenticationProvider` 使用 `PasswordEncoder` (我们配置的 `BCryptPasswordEncoder`) 比较用户提交的密码和数据库中存储的加密哈希值。
- **生成 JWT**: 如果认证成功,`AuthService` 调用 `JwtTokenProvider``generateToken` 方法,为认证通过的 `Authentication` 对象生成一个 JWT。
3. **Service -> Repository (`UserRepository`)**
- **动作**: `UserDetailsServiceImpl` 调用 `userRepository.findByUsername(username)`
- **Repository 角色**: `UserRepository` 执行 `SELECT` 查询,从 `users` 表中找到对应的用户记录,并将其封装成 `User` 实体对象返回。
4. **安全组件交互 (`JwtTokenProvider`, `UserDetailsServiceImpl`)**
- `UserDetailsServiceImpl`: 实现了 Spring Security 的 `UserDetailsService` 接口,是连接应用用户数据和 Spring Security 框架的桥梁。
- `JwtTokenProvider`: 负责 JWT 的生成和解析。`generateToken` 方法会设置主题用户名、签发时间、过期时间并使用预设的密钥和算法HS512进行签名。
5. **Controller -> 前端**
- **动作**: `AuthService` 返回生成的 JWT。`AuthController` 将此 JWT 包装在 `JwtAuthenticationResponse` DTO 中,再放入 `ApiResponse.success()`,以 HTTP 200 状态码返回给前端。
- **后续请求**: 前端在后续所有需要认证的请求的 `Authorization` 头中携带此 JWT (`Bearer <token>`)。Spring Security 的 `JwtAuthenticationFilter` 会在每个请求到达 Controller 前拦截并验证此 Token。
### 2.2 公众反馈管理
#### API 接口
- **提交反馈 (JSON)**: `POST /api/feedback/submit-json`
- **提交反馈 (Multipart)**: `POST /api/feedback/submit`
- **获取所有反馈**: `GET /api/feedback`
- **公众提交反馈**: `POST /api/public/feedback`
**场景:公众提交带图片的反馈 (POST /api/public/feedback)**
1. **前端 -> Controller (`PublicController`)**
- **动作**: 公众用户填写反馈表单(描述、位置、图片),前端以 `multipart/form-data` 格式提交到 `/api/public/feedback`
- **Controller 角色**: `createFeedback` 方法使用 `@RequestPart` 分别接收反馈内容的 DTO (`FeedbackCreateDTO`) 和文件 (`MultipartFile`)。`@Valid` 确保 DTO 数据合规。Controller 随即调用 `FeedbackService`
2. **Controller -> Service (`FeedbackService` & `FileStorageService`)**
- **动作**: `FeedbackService``createFeedback` 方法被调用。
- **Service 角色**:
- **文件处理**: 如果 `file` 存在,`FeedbackService` 首先调用 `FileStorageService``storeFile` 方法。`FileStorageService` 会生成唯一文件名,将文件存储在服务器的 `uploads` 目录下,并返回可访问的文件路径。
- **实体创建**: `FeedbackService` 创建一个新的 `Feedback` 实体。
- **数据映射**: 使用 `ModelMapper``FeedbackCreateDTO` 的数据(如 `description`, `location`)映射到 `Feedback` 实体上。
- **关联文件**: 将 `FileStorageService` 返回的文件路径设置到 `Feedback` 实体的 `imageUrl` 字段。
- **状态设定**: 设置反馈的初始状态为 `PENDING`
- **持久化**: 调用 `feedbackRepository.save(newFeedback)`
3. **Service -> Repository (`FeedbackRepository`)**
- **动作**: `feedbackRepository.save()` 执行 `INSERT` SQL 语句,将新的反馈记录存入 `feedbacks` 表。
4. **Controller -> 前端**
- **动作**: `FeedbackService` 返回成功创建的 `Feedback` 对象的 DTO。`PublicController` 将其包装在 `ApiResponse.success()` 中,以 HTTP 201 (Created) 状态码返回,表示资源创建成功。
### 2.3 任务生命周期管理
#### API 接口
- **获取待审核的反馈列表**: `GET /api/supervisor/reviews`
- **批准反馈**: `POST /api/supervisor/reviews/{feedbackId}/approve`
- **拒绝反馈**: `POST /api/supervisor/reviews/{feedbackId}/reject`
- **获取未分配的反馈**: `GET /api/tasks/unassigned`
- **获取可用的网格员**: `GET /api/tasks/grid-workers`
- **分配任务**: `POST /api/tasks/assign`
- **获取任务列表**: `GET /api/management/tasks`
- **分配任务**: `POST /api/management/tasks/{taskId}/assign`
- **获取任务详情**: `GET /api/management/tasks/{taskId}`
- **审核任务**: `POST /api/management/tasks/{taskId}/review`
- **取消任务**: `POST /api/management/tasks/{taskId}/cancel`
- **直接创建任务**: `POST /api/management/tasks`
- **获取待处理的反馈**: `GET /api/management/tasks/feedback`
- **从反馈创建任务**: `POST /api/management/tasks/feedback/{feedbackId}/create-task`
- **获取我的任务**: `GET /api/worker`
- **获取任务详情**: `GET /api/worker/{taskId}`
- **接受任务**: `POST /api/worker/{taskId}/accept`
- **提交任务**: `POST /api/worker/{taskId}/submit`
**场景:主管审核反馈并转换为任务 (POST /api/supervisor/feedback/{feedbackId}/convert)**
1. **前端 -> Controller (`SupervisorController`)**
- **动作**: 主管在管理界面查看一条反馈,点击“转换为任务”按钮。前端发送 `POST` 请求到 `/api/supervisor/feedback/456/convert`,请求体中可能包含指派信息 `TaskFromFeedbackDTO`
- **Controller 角色**: `convertFeedbackToTask` 方法接收请求,解析 `feedbackId` 和 DTO然后调用 `TaskService`
2. **Controller -> Service (`TaskService` & `FeedbackService`)**
- **动作**: `TaskService``createTaskFromFeedback` 方法被调用。
- **Service 角色**: 这是一个跨领域服务的协调过程。
- **获取反馈**: 调用 `feedbackRepository.findById(feedbackId)` 获取原始反馈信息。如果不存在,抛出 `ResourceNotFoundException`
- **状态检查**: 检查反馈是否已经是“已处理”状态,防止重复转换。
- **创建任务实体**: 创建一个新的 `Task` 实体。
- **数据迁移**: 将 `Feedback``description`, `location` 等信息复制到 `Task` 实体中。
- **设置任务属性**: 根据 `TaskFromFeedbackDTO` 设置任务的标题、截止日期、指派的网格员(如果提供了 `workerId`)。如果未指派,任务状态为 `PENDING`;如果已指派,状态为 `ASSIGNED`
- **持久化任务**: 调用 `taskRepository.save(newTask)`
- **更新反馈状态**: 更新 `Feedback` 实体的状态为 `PROCESSED`,并调用 `feedbackRepository.save(feedback)`
- **事务管理**: 整个方法应该被 `@Transactional` 注解标记,确保创建任务和更新反馈状态这两个操作要么同时成功,要么同时失败。
3. **Service -> Repository (`TaskRepository`, `FeedbackRepository`)**
- **动作**: `TaskRepository` 插入一条新任务记录。`FeedbackRepository` 更新对应的反馈记录状态。
4. **Controller -> 前端**
- **动作**: `TaskService` 返回新创建的任务的 DTO。`SupervisorController` 将其包装在 `ApiResponse.success()` 中返回,前端可以根据返回的任务信息进行页面跳转或状态更新。
### 2.4 数据可视化与决策支持
#### API 接口
- **获取仪表盘统计数据**: `GET /api/dashboard/stats`
- **获取 AQI 分布**: `GET /api/dashboard/reports/aqi-distribution`
- **获取污染超标月度趋势**: `GET /api/dashboard/reports/pollution-trend`
- **获取网格覆盖率**: `GET /api/dashboard/reports/grid-coverage`
- **获取反馈热力图数据**: `GET /api/dashboard/map/heatmap`
- **获取污染类型统计**: `GET /api/dashboard/reports/pollution-stats`
- **获取任务完成情况统计**: `GET /api/dashboard/reports/task-completion-stats`
- **获取 AQI 热力图数据**: `GET /api/dashboard/map/aqi-heatmap`
### 2.5 智能分配算法
#### API 接口
- **获取任务的推荐人选**: `GET /api/tasks/{taskId}/recommendations`
**场景:主管为新任务寻求系统推荐的执行人 (GET /api/tasks/123/recommendations)**
1. **前端 -> Controller (`TaskManagementController`)**
- **动作**: 主管在任务分配界面,点击“推荐人选”按钮。前端向 `/api/tasks/123/recommendations` 发送 `GET` 请求。
- **Controller 角色**: `getTaskRecommendations` 方法接收请求,并调用 `TaskRecommendationService`
2. **Controller -> Service (`TaskRecommendationService`)**
- **动作**: `TaskRecommendationService``recommendWorkersForTask` 方法被调用。
- **Service 角色**: 这是智能分配算法的核心实现。
- **获取任务信息**: 首先,从 `taskRepository` 获取任务详情,特别是任务的地理位置和类型。
- **筛选候选人**: 从 `userRepository` 获取所有角色为“网格员”且状态为“可用”的用户列表。
- **为每位候选人评分**: 遍历候选人列表,根据一系列预设标准计算综合得分。这是一个典型的加权评分模型:
- **距离 (40%)**: 计算网格员当前位置与任务位置的直线距离。距离越近,得分越高。`Score = (MaxDistance - WorkerDistance) / MaxDistance`
- **当前负载 (30%)**: 查询该网格员当前状态为 `ASSIGNED``IN_PROGRESS` 的任务数量。任务越少,得分越高。`Score = (MaxLoad - WorkerLoad) / MaxLoad`
- **技能匹配度 (20%)**: 如果任务有特定的技能要求如“管道维修”检查网格员的技能标签。匹配则得满分否则得0分。
- **历史表现 (10%)**: 查询该网格员过去完成类似任务的平均耗时和评价。表现越好,得分越高。
- **加权总分**: `Total Score = Score_Distance * 0.4 + Score_Load * 0.3 + Score_Skill * 0.2 + Score_Performance * 0.1`
- **排序**: 根据总分对所有候选人进行降序排序。
3. **Service -> Repository (Multiple Repositories)**
- **动作**: `TaskRecommendationService` 会与 `TaskRepository`, `UserRepository`, `WorkerLocationRepository` (假设有这样一个实时位置库) 等多个 Repository 交互,以获取计算所需的数据。
4. **Controller -> 前端**
- **动作**: `TaskRecommendationService` 返回一个包含网格员信息和他们各自推荐分数的 DTO 列表。`TaskManagementController` 将此列表包装在 `ApiResponse.success()` 中返回。前端界面会高亮显示得分最高的几位候选人,并附上推荐理由(如“距离最近”,“负载最低”),辅助主管做出最终决策。
**场景:决策者查看仪表盘 (GET /api/dashboard)**
1. **前端 -> Controller (`DashboardController`)**
- **动作**: 决策者访问仪表盘页面,前端发送 `GET` 请求到 `/api/dashboard`
- **Controller 角色**: `getDashboardData` 方法直接调用 `DashboardService`
2. **Controller -> Service (`DashboardService`)**
- **动作**: `DashboardService``getDashboardData` 方法被调用。
- **Service 角色**: 这是一个典型的数据聚合服务。
- **并行/串行查询**: `DashboardService` 会向多个 Repository 发起查询请求,以收集构建仪表盘所需的所有数据。
- **任务统计**: 调用 `taskRepository` 的自定义聚合查询方法(如 `countByStatus`)来获取不同状态的任务数。
- **反馈统计**: 调用 `feedbackRepository.countByStatus` 获取不同状态的反馈数。
- **人员概览**: 调用 `personnelRepository.count()` 获取总人数。
- **AQI 数据**: 调用 `aqiDataRepository.findLatest()` 获取最新的空气质量数据。
- **数据组装**: 将所有查询到的零散数据,组装成一个专为前端仪表盘设计的 `DashboardDTO` 对象。
3. **Service -> Repository (多个)**
- **动作**: `TaskRepository`, `FeedbackRepository`, `PersonnelRepository` 等分别执行高效的数据库聚合查询(如 `SELECT status, COUNT(*) FROM tasks GROUP BY status`)。
- **Repository 角色**: Repository 接口中定义了这些自定义查询方法,使用 `@Query` 注解编写 JPQL 或原生 SQL直接利用数据库的计算能力避免在应用服务层进行大量数据处理。
4. **Controller -> 前端**
- **动作**: `DashboardService` 返回填充了完整数据的 `DashboardDTO``DashboardController` 将其包装在 `ApiResponse.success()` 中,以 HTTP 200 状态码返回。前端收到 JSON 数据后,使用图表库(如 ECharts进行渲染。
### 2.5 网格与人员管理
#### API 接口
- **获取网格列表**: `GET /api/grids`
- **更新网格信息**: `PATCH /api/grids/{gridId}`
- **创建用户**: `POST /api/personnel/users`
- **获取用户列表**: `GET /api/personnel/users`
- **获取单个用户**: `GET /api/personnel/users/{userId}`
- **更新用户信息**: `PATCH /api/personnel/users/{userId}`
- **更新用户角色**: `PUT /api/personnel/users/{userId}/role`
- **删除用户**: `DELETE /api/personnel/users/{userId}`
- **更新个人资料**: `PATCH /api/me`
- **更新我的位置**: `POST /api/me/location`
- **获取我的反馈历史**: `GET /api/me/feedback`
**场景:管理员分页查询所有人员信息 (GET /api/management/personnel?page=0&size=10)**
1. **前端 -> Controller (`ManagementController`)**
- **动作**: 管理员在人员管理页面,前端发送带分页参数的 `GET` 请求。
- **Controller 角色**: `getAllPersonnel` 方法使用 `@RequestParam` 接收分页参数,并将其封装成 `Pageable` 对象,然后调用 `PersonnelService`
2. **Controller -> Service (`PersonnelService`)**
- **动作**: `PersonnelService``findAll` 方法接收 `Pageable` 对象。
- **Service 角色**: 直接将 `Pageable` 对象传递给 `PersonnelRepository`。对于更复杂的查询例如按姓名或角色搜索Service 层会接收额外的查询参数,并调用 `Specification``QueryDSL` 来构建动态查询。
3. **Service -> Repository (`PersonnelRepository`)**
- **动作**: `personnelRepository.findAll(pageable)` 被调用。
- **Repository 角色**: Spring Data JPA 会根据 `Pageable` 参数自动生成带有分页和排序的 SQL 查询(如 `SELECT ... FROM personnel LIMIT ? OFFSET ?`),并执行。它返回一个 `Page<Personnel>` 对象,其中包含了当前页的数据列表、总页数、总元素数量等完整的的分页信息。
4. **Controller -> 前端**
- **动作**: `PersonnelService` 返回 `Page<Personnel>` 对象。`ManagementController` 将其直接放入 `ApiResponse.success()` 中返回。前端根据返回的 `Page` 对象渲染数据列表和分页控件。
### 2.6 AI 辅助功能
**场景AI 辅助生成任务报告摘要 (POST /api/ai/summarize)**
1. **前端 -> Controller (`AiController`)**
- **动作**: 用户在任务详情页输入或粘贴长篇报告文本,点击“生成摘要”。前端将文本内容包装在 `SummarizeRequest` DTO 中,发送 `POST` 请求。
- **Controller 角色**: `summarizeText` 方法接收 DTO并调用 `AiService`
2. **Controller -> Service (`AiService`)**
- **动作**: `AiService``getSummary` 方法被调用。
- **Service 角色**: 这是与外部 AI 服务交互的核心。
- **构建请求**: 根据所使用的 AI 服务(如 OpenAI, Google Gemini, 或其他私有化部署模型)的 API 规范,构建 HTTP 请求。这通常涉及设置 API Key、请求头、以及包含待摘要文本的 JSON 请求体。
- **API 调用**: 使用 `RestTemplate``WebClient` 向 AI 服务的 API 端点发送请求。
- **解析响应**: 接收 AI 服务返回的 JSON 响应,并从中提取出生成的摘要文本。
- **异常处理**: 处理网络超时、API 认证失败、或 AI 服务返回错误码等异常情况。
3. **Controller -> 前端**
- **动作**: `AiService` 返回提取出的摘要字符串。`AiController` 将其包装在 `ApiResponse.success()` 中返回给前端,前端将摘要显示在页面上。
### 2.7 关键技术机制深度解析
#### A. 文件上传与管理 (`FileStorageService`)
- **流程**: `Controller` 接收 `MultipartFile` -> `Service` 调用 `FileStorageService.storeFile()` -> `FileStorageService` 生成唯一文件名 (e.g., `UUID.randomUUID().toString()`) 并拼接原始文件扩展名 -> 使用 `StringUtils.cleanPath` 清理文件名防止路径遍历攻击 -> 构建目标存储路径 `Path targetLocation = this.fileStorageLocation.resolve(fileName)` -> 使用 `Files.copy` 将文件输入流写入目标路径,并使用 `StandardCopyOption.REPLACE_EXISTING` 覆盖同名文件 -> 返回通过 `ServletUriComponentsBuilder` 构建的、可从外部访问的文件下载 URI。
- **关键点**:
- **配置化**: 文件存储路径 (`file.upload-dir`) 在 `application.properties` 中配置,具有灵活性。
- **安全性**: 明确处理了 `FileNameContainsInvalidCharactersException``FileStorageException`
- **资源服务**: 提供了 `loadFileAsResource` 方法,可以将存储的文件作为 `Resource` 对象加载,以便于下载。
### 2.8 地图与路径规划
#### A. 地图服务 (`MapService`)
- **获取完整地图网格**: `GET /api/map/grid`
- **创建或更新地图单元格**: `POST /api/map/grid`
- **初始化地图**: `POST /api/map/initialize`
#### B. 路径规划服务 (`PathfindingService`)
- **寻找路径**: `POST /api/pathfinding/find`
#### B. 文件服务 (`FileService`)
- **下载文件**: `GET /api/files/download/{fileName}`
- **查看文件**: `GET /api/files/view/{fileName}`
#### C. 邮件与验证码服务 (`EmailService`)
- **流程**: `AuthService` 调用 `EmailService.sendVerificationCode(email)` -> `EmailService` 使用 `Random` 生成6位数字验证码 -> 将 `email:code` 键值对存入 `verificationCodeCache` (一个 `Caffeine` 缓存实例)并设置5分钟过期 -> 使用 `JavaMailSender` 创建 `SimpleMailMessage`,设置收件人、主题和正文 -> `mailSender.send(message)` 发送邮件。
## 三、API 接口文档
本部分详细列出了系统提供的所有API接口包括其功能描述、请求路径、方法、所需权限以及请求/响应示例。
### 1. 认证模块 (Auth)
**基础路径**: `/api/auth`
#### 1.1 用户注册
- **功能描述**: 注册一个新用户。
- **请求路径**: `/api/auth/signup`
- **请求方法**: `POST`
- **所需权限**: 无
- **请求体**: `SignUpRequest`
#### 1.2 用户登录
- **功能描述**: 用户登录并获取 JWT Token。
- **请求路径**: `/api/auth/login`
- **请求方法**: `POST`
- **所需权限**: 无
- **请求体**: `LoginRequest`
#### 1.3 发送验证码
- **功能描述**: 请求发送验证码到指定邮箱。
- **请求路径**: `/api/auth/send-verification-code`
- **请求方法**: `POST`
- **所需权限**: 无
- **查询参数**: `email` (string, 必需)
#### 1.4 请求重置密码
- **功能描述**: 请求发送重置密码的链接或令牌。
- **请求路径**: `/api/auth/request-password-reset`
- **请求方法**: `POST`
- **所需权限**: 无
- **请求体**: `PasswordResetRequest`
#### 1.5 重置密码
- **功能描述**: 使用令牌重置密码。
- **请求路径**: `/api/auth/reset-password`
- **请求方法**: `POST`
- **所需权限**: 无
- **请求体**: `PasswordResetDto`
### 2. 仪表盘模块 (Dashboard)
**基础路径**: `/api/dashboard`
**所需权限**: `DECISION_MAKER`
#### 2.1 获取仪表盘统计数据
- **请求路径**: `/api/dashboard/stats`
- **请求方法**: `GET`
#### 2.2 获取 AQI 分布
- **请求路径**: `/api/dashboard/reports/aqi-distribution`
- **请求方法**: `GET`
#### 2.3 获取污染超标月度趋势
- **请求路径**: `/api/dashboard/reports/pollution-trend`
- **请求方法**: `GET`
#### 2.4 获取网格覆盖率
- **请求路径**: `/api/dashboard/reports/grid-coverage`
- **请求方法**: `GET`
#### 2.5 获取反馈热力图数据
- **请求路径**: `/api/dashboard/map/heatmap`
- **请求方法**: `GET`
#### 2.6 获取污染类型统计
- **请求路径**: `/api/dashboard/reports/pollution-stats`
- **请求方法**: `GET`
#### 2.7 获取任务完成情况统计
- **请求路径**: `/api/dashboard/reports/task-completion-stats`
- **请求方法**: `GET`
#### 2.8 获取 AQI 热力图数据
- **请求路径**: `/api/dashboard/map/aqi-heatmap`
- **请求方法**: `GET`
### 3. 反馈模块 (Feedback)
**基础路径**: `/api/feedback`
#### 3.1 提交反馈 (JSON)
- **功能描述**: 用于测试,使用 JSON 提交反馈。
- **请求路径**: `/api/feedback/submit-json`
- **请求方法**: `POST`
- **所需权限**: 已认证用户
- **请求体**: `FeedbackSubmissionRequest`
#### 3.2 提交反馈 (Multipart)
- **功能描述**: 提交反馈,可包含文件。
- **请求路径**: `/api/feedback/submit`
- **请求方法**: `POST`
- **所需权限**: 已认证用户
- **请求体**: `multipart/form-data`
#### 3.3 获取所有反馈
- **功能描述**: 获取所有反馈(分页)。
- **请求路径**: `/api/feedback`
- **请求方法**: `GET`
- **所需权限**: `ADMIN`
### 4. 文件模块 (File)
**基础路径**: `/api/files`
**所需权限**: 公开访问
#### 4.1 下载文件
- **请求路径**: `/api/files/download/{fileName}`
- **请求方法**: `GET`
#### 4.2 查看文件
- **请求路径**: `/api/files/view/{fileName}`
- **请求方法**: `GET`
### 5. 网格模块 (Grid)
**基础路径**: `/api/grids`
**所需权限**: `ADMIN``DECISION_MAKER`
#### 5.1 获取网格列表
- **请求路径**: `/api/grids`
- **请求方法**: `GET`
#### 5.2 更新网格信息
- **请求路径**: `/api/grids/{gridId}`
- **请求方法**: `PATCH`
- **所需权限**: `ADMIN`
### 6. 网格员任务模块 (Worker)
**基础路径**: `/api/worker`
**所需权限**: `GRID_WORKER`
#### 6.1 获取我的任务
- **请求路径**: `/api/worker`
- **请求方法**: `GET`
#### 6.2 获取任务详情
- **请求路径**: `/api/worker/{taskId}`
- **请求方法**: `GET`
#### 6.3 接受任务
- **请求路径**: `/api/worker/{taskId}/accept`
- **请求方法**: `POST`
#### 6.4 提交任务
- **请求路径**: `/api/worker/{taskId}/submit`
- **请求方法**: `POST`
### 7. 地图模块 (Map)
**基础路径**: `/api/map`
**所需权限**: 公开访问
#### 7.1 获取完整地图网格
- **请求路径**: `/api/map/grid`
- **请求方法**: `GET`
#### 7.2 创建或更新地图单元格
- **请求路径**: `/api/map/grid`
- **请求方法**: `POST`
#### 7.3 初始化地图
- **请求路径**: `/api/map/initialize`
- **请求方法**: `POST`
### 8. 路径规划模块 (Pathfinding)
**基础路径**: `/api/pathfinding`
**所需权限**: 公开访问
#### 8.1 寻找路径
- **功能描述**: 使用 A* 算法在两点之间寻找最短路径。
- **请求路径**: `/api/pathfinding/find`
- **请求方法**: `POST`
### 9. 人员管理模块 (Personnel)
**基础路径**: `/api/personnel`
**所需权限**: `ADMIN`
#### 9.1 创建用户
- **请求路径**: `/api/personnel/users`
- **请求方法**: `POST`
#### 9.2 获取用户列表
- **请求路径**: `/api/personnel/users`
- **请求方法**: `GET`
#### 9.3 获取单个用户
- **请求路径**: `/api/personnel/users/{userId}`
- **请求方法**: `GET`
#### 9.4 更新用户信息
- **请求路径**: `/api/personnel/users/{userId}`
- **请求方法**: `PATCH`
#### 9.5 更新用户角色
- **请求路径**: `/api/personnel/users/{userId}/role`
- **请求方法**: `PUT`
#### 9.6 删除用户
- **请求路径**: `/api/personnel/users/{userId}`
- **请求方法**: `DELETE`
### 10. 个人资料模块 (Me)
**基础路径**: `/api/me`
**所需权限**: 已认证用户
#### 10.1 更新个人资料
- **请求路径**: `/api/me`
- **请求方法**: `PATCH`
#### 10.2 更新我的位置
- **请求路径**: `/api/me/location`
- **请求方法**: `POST`
#### 10.3 获取我的反馈历史
- **请求路径**: `/api/me/feedback`
- **请求方法**: `GET`
### 11. 公共接口模块 (Public)
**基础路径**: `/api/public`
**所需权限**: 公开访问
#### 11.1 公众提交反馈
- **请求路径**: `/api/public/feedback`
- **请求方法**: `POST`
### 12. 主管模块 (Supervisor)
**基础路径**: `/api/supervisor`
**所需权限**: `SUPERVISOR``ADMIN`
#### 12.1 获取待审核的反馈列表
- **请求路径**: `/api/supervisor/reviews`
- **请求方法**: `GET`
#### 12.2 批准反馈
- **请求路径**: `/api/supervisor/reviews/{feedbackId}/approve`
- **请求方法**: `POST`
#### 12.3 拒绝反馈
- **请求路径**: `/api/supervisor/reviews/{feedbackId}/reject`
- **请求方法**: `POST`
### 13. 任务分配模块 (Tasks)
**基础路径**: `/api/tasks`
**所需权限**: `ADMIN``SUPERVISOR`
#### 13.1 获取未分配的反馈
- **请求路径**: `/api/tasks/unassigned`
- **请求方法**: `GET`
#### 13.2 获取可用的网格员
- **请求路径**: `/api/tasks/grid-workers`
- **请求方法**: `GET`
#### 13.3 分配任务
- **请求路径**: `/api/tasks/assign`
- **请求方法**: `POST`
### 14. 任务管理模块 (Management)
**基础路径**: `/api/management/tasks`
**所需权限**: `SUPERVISOR`
#### 14.1 获取任务列表
- **请求路径**: `/api/management/tasks`
- **请求方法**: `GET`
#### 14.2 分配任务
- **请求路径**: `/api/management/tasks/{taskId}/assign`
- **请求方法**: `POST`
#### 14.3 获取任务详情
- **请求路径**: `/api/management/tasks/{taskId}`
- **请求方法**: `GET`
#### 14.4 审核任务
- **请求路径**: `/api/management/tasks/{taskId}/review`
- **请求方法**: `POST`
#### 14.5 取消任务
- **请求路径**: `/api/management/tasks/{taskId}/cancel`
- **请求方法**: `POST`
#### 14.6 直接创建任务
- **请求路径**: `/api/management/tasks`
- **请求方法**: `POST`
#### 14.7 获取待处理的反馈
- **请求路径**: `/api/management/tasks/feedback`
- **请求方法**: `GET`
#### 14.8 从反馈创建任务
- **请求路径**: `/api/management/tasks/feedback/{feedbackId}/create-task`
- **请求方法**: `POST`
- **验证流程**: 用户提交验证码 -> `AuthService` 调用 `EmailService.verifyCode(email, code)` -> `EmailService` 从缓存中通过 `verificationCodeCache.getIfPresent(email)` 获取验证码,并与用户提交的 `code` 进行比对。如果匹配且存在,则验证成功并从缓存中移除该验证码 (`cache.invalidate(email)`)。
#### C. 登录安全与尝试锁定 (`AuthenticationFailureEventListener`)
- **流程**: 用户登录失败 -> Spring Security 的 `ProviderManager` 认证失败后,会发布一个 `AuthenticationFailureBadCredentialsEvent` 事件 -> `AuthenticationFailureEventListener` (一个实现了 `ApplicationListener` 的组件) 的 `onApplicationEvent` 方法被触发 -> 监听器从事件中获取用户名 -> 调用 `LoginAttemptService.loginFailed(username)` -> `LoginAttemptService` 使用 `Caffeine` 缓存 `attemptsCache` 来记录失败次数。每次失败,计数器加一。
- **锁定逻辑**: `LoginAttemptService``isBlocked(username)` 方法检查失败次数是否超过最大允许次数(如 `MAX_ATTEMPT = 5`)。在 `UserDetailsServiceImpl``loadUserByUsername` 方法的开头,会先调用 `loginAttemptService.isBlocked()`。如果用户已被锁定,则直接抛出 `LockedException`,阻止后续的数据库查询和密码比对,实现登录锁定。
#### D. A* 路径规划 (`PathfindingService`)
- **流程**: `PathfindingController` 调用 `PathfindingService.findPath()` -> `PathfindingService` 初始化一个 `Grid` 对象,该对象包含地图的宽度、高度和障碍物位置集合 -> A* 算法的核心 `AStar.findPath()` 被调用,它接收 `Grid` 和起止点 `Node` 作为参数 -> 算法内部使用 `PriorityQueue` 作为开放列表优先处理f值最低的节点使用 `HashSet` 作为关闭列表 -> 循环开始:从开放列表取出当前节点,若为终点则通过 `retracePath` 回溯构建路径 -> 否则,遍历当前节点的邻居节点,计算其 g值到起点的代价和 h值到终点的预估代价即启发式函数若邻居节点是更优路径则更新其代价和父节点并加入开放列表。
- **关键点**:
- **数据结构**: `Node` 对象包含了坐标、g/h/f代价值、父节点引用和是否为障碍物等信息。
- **启发式函数**: 使用曼哈顿距离 `getDistance(nodeA, nodeB)` 作为启发式函数,适用于网格地图。
- **可扩展性**: `Grid` 可以从数据库、文件或动态生成,使得路径规划可以适应不同的地图场景。
## 四、配置模块详细解析
配置模块(`com.dne.ems.config`是EMS系统的基础设施核心为整个应用提供了完整的配置支持和初始化功能。本节将详细解析各个配置组件的功能、实现原理和使用方式。
### 4.1 数据初始化配置 (`DataInitializer.java`)
#### 功能概述
`DataInitializer` 是系统启动时的数据初始化组件,实现了 `CommandLineRunner` 接口确保在Spring Boot应用完全启动后自动执行数据初始化逻辑。
#### 核心特性
- **自动执行**: 实现 `CommandLineRunner` 接口,在应用启动后自动运行
- **幂等性**: 检查数据是否已存在,避免重复创建
- **完整性**: 创建系统运行所需的所有基础数据
- **日志记录**: 包含详细的初始化过程日志
#### 初始化内容详解
**1. 用户账户初始化**
- 创建五种角色的测试账户:管理员、网格员、决策者、主管、公众监督员
- 使用BCrypt加密存储密码
- 为每个角色分配相应的权限和属性
**默认账户信息**:
```
管理员: admin@aizhangz.top / Admin@123
网格员: worker@aizhangz.top / Worker@123
决策者: decision.maker@aizhangz.top / Decision@123
主管: supervisor@aizhangz.top / Supervisor@123
公众监督员: public.supervisor@aizhangz.top / Public@123
```
**2. 网格数据初始化**
- 生成40x40的完整城市网格系统
- 覆盖4个主要城市常州市、无锡市、苏州市、南京市
- 每个网格单元包含坐标、城市归属、区域信息
- 支持网格员分配和区域管理
**3. 地图网格同步**
- 将业务网格数据同步到寻路网格
- 支持A*寻路算法的地图表示
- 处理障碍物和可通行区域的标记
**4. AQI数据初始化**
- 创建空气质量指数的历史数据
- 生成实时AQI监测数据
- 为数据可视化和分析提供基础数据
**5. 反馈和任务数据**
- 创建示例环境问题反馈
- 初始化任务分配记录
- 建立完整的业务流程测试数据
#### 技术实现要点
```java
@Component
@Slf4j
public class DataInitializer implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// 检查数据是否已初始化
if (isDataAlreadyInitialized()) {
log.info("数据已初始化,跳过初始化过程");
return;
}
// 执行各项初始化
initializeUsers();
initializeGrids();
initializeAqiData();
initializeFeedbacks();
initializeTasks();
log.info("数据初始化完成");
}
}
```
### 4.2 文件存储配置 (`FileStorageProperties.java`)
#### 功能概述
`FileStorageProperties` 管理文件上传存储的相关配置使用Spring Boot的配置属性绑定机制。
#### 配置属性
- `file.upload-dir`: 指定上传文件的存储目录
- 支持相对路径和绝对路径
- 可通过环境变量或配置文件动态配置
#### 使用方式
**application.yml配置示例**:
```yaml
file:
upload-dir: ./uploads
# 或使用绝对路径
# upload-dir: /var/app/uploads
```
**Java配置类**:
```java
@ConfigurationProperties(prefix = "file")
@Data
public class FileStorageProperties {
private String uploadDir;
}
```
### 4.3 OpenAPI配置 (`OpenApiConfig.java`)
#### 功能概述
配置Swagger API文档和JWT认证为开发和测试提供完整的API文档界面。
#### 主要特性
- **API文档生成**: 自动生成完整的REST API文档
- **JWT认证集成**: 支持在Swagger UI中进行JWT认证测试
- **安全配置**: 为所有API端点添加安全要求
- **在线测试**: 提供交互式API测试界面
#### 访问方式
- Swagger UI: `http://localhost:8080/swagger-ui.html`
- API文档JSON: `http://localhost:8080/v3/api-docs`
#### 安全配置详解
```java
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("EMS API")
.version("1.0")
.description("环境监测系统API文档"))
.addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
.components(new Components()
.addSecuritySchemes("Bearer Authentication",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
```
### 4.4 RestTemplate配置 (`RestTemplateConfig.java`)
#### 功能概述
提供HTTP客户端功能用于与外部服务进行同步通信。
#### 主要用途
- **外部API集成**: 调用第三方服务API
- **微服务通信**: 在分布式架构中进行服务间调用
- **数据同步**: 从外部数据源获取信息
- **第三方认证**: 集成外部认证服务
#### 配置示例
```java
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// 配置连接超时和读取超时
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
restTemplate.setRequestFactory(factory);
return restTemplate;
}
}
```
#### 使用示例
```java
@Service
public class ExternalApiService {
@Autowired
private RestTemplate restTemplate;
public String callExternalApi(String url) {
return restTemplate.getForObject(url, String.class);
}
}
```
### 4.5 WebClient配置 (`WebClientConfig.java`)
#### 功能概述
提供响应式HTTP客户端功能支持非阻塞I/O操作适用于高并发场景。
#### 主要优势
- **非阻塞I/O**: 提高系统吞吐量和响应性能
- **响应式编程**: 支持Reactor响应式编程模型
- **流式处理**: 支持数据流的实时处理
- **背压处理**: 自动处理生产者和消费者速度不匹配的情况
#### 配置示例
```java
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
.build();
}
@Bean
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
```
#### 使用示例
```java
@Service
public class ReactiveApiService {
@Autowired
private WebClient webClient;
public Mono<String> callApiAsync(String url) {
return webClient.get()
.uri(url)
.retrieve()
.bodyToMono(String.class);
}
}
```
### 4.6 WebSocket配置 (`WebSocketConfig.java`)
#### 功能概述
WebSocket通信配置为实时通信功能预留扩展空间。
#### 预期用途
- **实时通知**: 任务状态变更的实时推送
- **在线协作**: 多用户协同操作
- **实时监控**: 系统状态和数据的实时更新
- **即时消息**: 用户间的即时通信
#### 扩展示例
```java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new NotificationWebSocketHandler(), "/ws/notifications")
.setAllowedOrigins("*");
}
}
```
### 4.7 字符串列表转换器 (`StringListConverter.java`)
#### 功能概述
JPA实体属性转换器用于在数据库存储和Java对象之间转换字符串列表。
#### 主要特性
- **类型转换**: `List<String>` ↔ JSON字符串
- **JSON序列化**: 使用Jackson ObjectMapper
- **空值处理**: 支持null和空列表的正确处理
- **异常处理**: 包含完整的错误处理和日志记录
#### 实现原理
```java
@Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public String convertToDatabaseColumn(List<String> attribute) {
if (attribute == null || attribute.isEmpty()) {
return null;
}
try {
return objectMapper.writeValueAsString(attribute);
} catch (JsonProcessingException e) {
log.error("转换List<String>到JSON失败", e);
return null;
}
}
@Override
public List<String> convertToEntityAttribute(String dbData) {
if (dbData == null || dbData.trim().isEmpty()) {
return new ArrayList<>();
}
try {
return objectMapper.readValue(dbData,
new TypeReference<List<String>>() {});
} catch (JsonProcessingException e) {
log.error("转换JSON到List<String>失败", e);
return new ArrayList<>();
}
}
}
```
#### 使用示例
```java
@Entity
public class UserAccount {
@Convert(converter = StringListConverter.class)
@Column(name = "skills", columnDefinition = "TEXT")
private List<String> skills;
@Convert(converter = StringListConverter.class)
@Column(name = "tags", columnDefinition = "TEXT")
private List<String> tags;
}
```
### 4.8 配置模块最佳实践
#### 1. 配置外部化
- 使用`@ConfigurationProperties`进行类型安全的配置绑定
- 支持多环境配置dev、test、prod
- 使用配置验证确保配置的正确性
#### 2. 安全考虑
- 敏感配置信息使用环境变量或加密存储
- API密钥和数据库密码不硬编码
- 文件上传路径的安全验证
#### 3. 性能优化
- HTTP客户端连接池配置
- 合理设置超时时间
- 缓存配置的优化
#### 4. 监控和日志
- 配置变更的审计日志
- 初始化过程的详细记录
- 配置加载失败的错误处理
### 4.9 配置模块总结
配置模块为EMS系统提供了完整的基础设施支持
1. **数据初始化**: 确保系统启动时具备完整的测试数据和基础配置
2. **文件管理**: 提供灵活的文件存储配置和管理机制
3. **API文档**: 支持完整的接口文档生成和在线测试
4. **HTTP客户端**: 提供同步和异步的外部服务调用能力
5. **数据转换**: 支持复杂数据类型的数据库存储转换
## 五、控制器模块详细解析
控制器模块(`com.dne.ems.controller`是EMS系统的API入口层负责处理所有HTTP请求并提供RESTful API接口。该模块包含14个专业化控制器每个控制器专注于特定的业务领域形成了完整的API生态系统。
### 5.1 认证控制器 (`AuthController.java`)
#### 功能概述
认证控制器负责用户身份验证和账户管理的核心功能,是系统安全的第一道防线。
#### 主要接口
- **用户注册** (`POST /api/auth/signup`): 新用户账户创建
- **用户登录** (`POST /api/auth/login`): 用户身份验证返回JWT令牌
- **发送验证码** (`POST /api/auth/send-verification-code`): 注册验证码发送
- **密码重置** (`POST /api/auth/send-password-reset-code`): 密码重置验证码发送
- **验证码重置密码** (`POST /api/auth/reset-password-with-code`): 使用验证码重置密码
#### 核心特性
- **JWT令牌管理**: 生成和验证JSON Web Token
- **邮箱验证**: 支持邮箱验证码验证机制
- **密码安全**: 多种密码重置方式,确保账户安全
- **参数验证**: 使用Bean Validation进行请求参数校验
#### 代码示例
```java
@PostMapping("/login")
public ResponseEntity<JwtAuthenticationResponse> signIn(@Valid @RequestBody LoginRequest request) {
return ResponseEntity.ok(authService.signIn(request));
}
@PostMapping("/send-verification-code")
public ResponseEntity<Void> sendVerificationCode(@RequestParam @NotBlank @Email String email) {
verificationCodeService.sendVerificationCode(email);
return ResponseEntity.ok().build();
}
```
### 5.2 仪表盘控制器 (`DashboardController.java`)
#### 功能概述
仪表盘控制器为决策者和管理员提供全面的数据可视化和统计分析功能,是数据驱动决策的核心支撑。
#### 主要接口
- **核心统计数据** (`GET /api/dashboard/stats`): 系统关键指标统计
- **AQI分布数据** (`GET /api/dashboard/reports/aqi-distribution`): 空气质量指数分布
- **月度超标趋势** (`GET /api/dashboard/reports/monthly-exceedance-trend`): 污染超标趋势分析
- **网格覆盖率** (`GET /api/dashboard/reports/grid-coverage`): 各城市网格覆盖情况
- **反馈热力图** (`GET /api/dashboard/map/heatmap`): 反馈分布热力图数据
- **污染物统计** (`GET /api/dashboard/reports/pollution-stats`): 各类污染物统计
- **任务完成统计** (`GET /api/dashboard/reports/task-completion-stats`): 任务执行效率统计
- **AQI热力图** (`GET /api/dashboard/map/aqi-heatmap`): AQI分布热力图
- **污染物阈值管理** (`GET /api/dashboard/thresholds`): 污染物阈值配置
#### 权限控制
```java
@PreAuthorize("hasAnyRole('DECISION_MAKER', 'ADMIN')")
public ResponseEntity<DashboardStatsDTO> getDashboardStats() {
// 仅决策者和管理员可访问
}
```
#### 数据可视化支持
- **多维度统计**: 支持时间、地域、类型等多维度数据分析
- **实时数据**: 提供实时更新的环境监测数据
- **热力图数据**: 支持地理信息系统(GIS)的数据可视化
- **趋势分析**: 历史数据趋势和预测分析
### 5.3 反馈控制器 (`FeedbackController.java`)
#### 功能概述
反馈控制器处理公众环境问题反馈的全生命周期管理,是公众参与环境治理的重要渠道。
#### 主要接口
- **提交反馈** (`POST /api/feedback/submit`): 支持文件上传的反馈提交
- **JSON格式提交** (`POST /api/feedback/submit-json`): 测试用的JSON格式提交
- **获取反馈列表** (`GET /api/feedback`): 支持多条件过滤的分页查询
- **反馈详情** (`GET /api/feedback/{id}`): 获取特定反馈的详细信息
- **反馈统计** (`GET /api/feedback/stats`): 反馈数据统计分析
#### 高级查询功能
```java
@GetMapping
public ResponseEntity<Page<FeedbackResponseDTO>> getAllFeedback(
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestParam(required = false) FeedbackStatus status,
@RequestParam(required = false) PollutionType pollutionType,
@RequestParam(required = false) SeverityLevel severityLevel,
@RequestParam(required = false) String cityName,
@RequestParam(required = false) String districtName,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
@RequestParam(required = false) String keyword,
Pageable pageable) {
// 支持多维度过滤查询
}
```
#### 文件上传支持
- **多文件上传**: 支持同时上传多个附件文件
- **文件类型验证**: 限制上传文件的类型和大小
- **安全存储**: 文件安全存储和访问控制
### 5.4 文件控制器 (`FileController.java`)
#### 功能概述
文件控制器提供文件下载和在线预览功能,支持系统中所有文件资源的访问管理。
#### 主要接口
- **文件下载** (`GET /api/files/{filename}`): 文件下载功能
- **文件预览** (`GET /api/view/{fileName}`): 在线文件预览
#### 技术特性
- **MIME类型检测**: 自动检测文件类型并设置正确的Content-Type
- **安全下载**: 防止路径遍历攻击
- **流式传输**: 支持大文件的流式下载
#### 实现示例
```java
@GetMapping("/files/{filename:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename, HttpServletRequest request) {
Resource resource = fileStorageService.loadFileAsResource(filename);
String contentType = null;
try {
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException ex) {
logger.info("Could not determine file type.");
}
if (contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
```
### 5.5 网格控制器 (`GridController.java`)
#### 功能概述
网格控制器负责城市网格化管理,包括网格数据维护和网格员分配管理。
#### 主要接口
- **网格列表** (`GET /api/grids`): 获取所有网格信息
- **更新网格** (`PATCH /api/grids/{id}`): 更新网格信息
- **网格覆盖率** (`GET /api/grids/coverage`): 网格覆盖率统计
- **分配网格员** (`POST /api/grids/{gridId}/assign`): 将网格员分配到指定网格
- **移除网格员** (`POST /api/grids/{gridId}/unassign`): 从网格中移除网格员
- **坐标分配** (`POST /api/grids/coordinates/{gridX}/{gridY}/assign`): 通过坐标分配网格员
#### 网格员管理
```java
@PostMapping("/{gridId}/assign")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> assignGridWorker(
@PathVariable Long gridId,
@RequestBody Map<String, Long> request) {
Long userId = request.get("userId");
// 验证用户角色
if (user.getRole() != Role.GRID_WORKER) {
return ResponseEntity.badRequest().body("只能分配网格员角色的用户");
}
// 分配网格坐标
user.setGridX(grid.getGridX());
user.setGridY(grid.getGridY());
userAccountRepository.save(user);
return ResponseEntity.ok().build();
}
```
### 5.6 网格员任务控制器 (`GridWorkerTaskController.java`)
#### 功能概述
专为网格员设计的任务管理控制器,提供任务接收、处理和提交的完整工作流程。
#### 主要接口
- **我的任务** (`GET /api/worker`): 获取分配给当前网格员的任务列表
- **接受任务** (`POST /api/worker/{taskId}/accept`): 接受指定任务
- **提交任务** (`POST /api/worker/{taskId}/submit`): 提交任务完成情况
- **任务详情** (`GET /api/worker/{taskId}`): 查看任务详细信息
#### 权限控制
```java
@RestController
@RequestMapping("/api/worker")
@RequiredArgsConstructor
@PreAuthorize("hasRole('GRID_WORKER')")
public class GridWorkerTaskController {
// 整个控制器仅限网格员角色访问
}
```
#### 任务提交功能
```java
@PostMapping(value = "/{taskId}/submit", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<TaskSummaryDTO> submitTask(
@PathVariable Long taskId,
@RequestPart("comments") @Valid String comments,
@RequestPart(value = "files", required = false) List<MultipartFile> files,
@AuthenticationPrincipal CustomUserDetails userDetails) {
// 支持文件上传的任务提交
}
```
### 5.7 地图控制器 (`MapController.java`)
#### 功能概述
地图控制器管理系统的地图网格数据,为路径规划和地理信息展示提供基础数据支持。
#### 主要接口
- **获取地图** (`GET /api/map/grid`): 获取完整的地图网格数据
- **更新网格** (`POST /api/map/grid`): 创建或更新地图网格单元
- **初始化地图** (`POST /api/map/initialize`): 初始化指定大小的地图
#### 地图初始化
```java
@PostMapping("/initialize")
@Transactional
public ResponseEntity<String> initializeMap(
@RequestParam(defaultValue = "20") int width,
@RequestParam(defaultValue = "20") int height) {
mapGridRepository.deleteAll();
mapGridRepository.flush();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
MapGrid cell = MapGrid.builder()
.x(x)
.y(y)
.isObstacle(false)
.terrainType("ground")
.build();
mapGridRepository.save(cell);
}
}
return ResponseEntity.ok("Initialized a " + width + "x" + height + " map.");
}
```
### 5.8 路径规划控制器 (`PathfindingController.java`)
#### 功能概述
路径规划控制器提供A*寻路算法功能,为网格员提供最优路径导航服务。
#### 主要接口
- **路径查找** (`POST /api/pathfinding/find`): 计算两点之间的最优路径
#### A*算法实现
```java
@PostMapping("/find")
public ResponseEntity<List<Point>> findPath(@RequestBody PathfindingRequest request) {
if (request == null) {
return ResponseEntity.badRequest().build();
}
Point start = new Point(request.getStartX(), request.getStartY());
Point end = new Point(request.getEndX(), request.getEndY());
List<Point> path = aStarService.findPath(start, end);
return ResponseEntity.ok(path);
}
```
#### 算法特性
- **障碍物避让**: 考虑地图中的障碍物
- **最优路径**: 计算距离最短的路径
- **实时计算**: 快速响应路径查询请求
### 5.9 人员管理控制器 (`PersonnelController.java`)
#### 功能概述
人员管理控制器提供用户账号的完整生命周期管理,包括创建、查询、更新和删除操作。
#### 主要接口
- **创建用户** (`POST /api/personnel/users`): 创建新用户账号
- **用户列表** (`GET /api/personnel/users`): 分页查询用户列表
- **用户详情** (`GET /api/personnel/users/{userId}`): 获取用户详细信息
- **更新用户** (`PATCH /api/personnel/users/{userId}`): 更新用户信息
- **更新角色** (`PUT /api/personnel/users/{userId}/role`): 更新用户角色
- **删除用户** (`DELETE /api/personnel/users/{userId}`): 删除用户账号
#### 权限控制
```java
@RestController
@RequestMapping("/api/personnel")
@RequiredArgsConstructor
@PreAuthorize("hasRole('ADMIN')")
public class PersonnelController {
// 仅管理员可访问人员管理功能
}
```
#### 高级查询
```java
@GetMapping("/users")
@PreAuthorize("hasRole('ADMIN') or hasRole('GRID_WORKER')")
public ResponseEntity<PageDTO<UserAccount>> getUsers(
@RequestParam(required = false) Role role,
@RequestParam(required = false) String name,
Pageable pageable) {
// 支持按角色和姓名过滤的分页查询
}
```
### 5.10 个人资料控制器 (`ProfileController.java`)
#### 功能概述
个人资料控制器处理认证用户的个人数据管理,提供用户自助服务功能。
#### 主要接口
- **反馈历史** (`GET /api/me/feedback`): 获取当前用户的反馈提交历史
#### 用户数据隔离
```java
@GetMapping("/feedback")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<List<UserFeedbackSummaryDTO>> getMyFeedbackHistory(
@AuthenticationPrincipal CustomUserDetails userDetails,
Pageable pageable) {
// 仅返回当前用户的数据
Page<UserFeedbackSummaryDTO> historyPage = userFeedbackService
.getFeedbackHistoryByUserId(userDetails.getId(), pageable);
return ResponseEntity.ok(historyPage.getContent());
}
```
### 5.11 公共接口控制器 (`PublicController.java`)
#### 功能概述
公共接口控制器提供无需认证的公共API主要服务于匿名用户的反馈提交。
#### 主要接口
- **公共反馈提交** (`POST /api/public/feedback`): 支持匿名用户提交环境问题反馈
#### 匿名访问支持
```java
@PostMapping(value = "/feedback", consumes = "multipart/form-data")
public ResponseEntity<Feedback> submitPublicFeedback(
@Valid @RequestPart("feedback") PublicFeedbackRequest request,
@RequestPart(value = "files", required = false) MultipartFile[] files) {
// 无需认证即可提交反馈
Feedback createdFeedback = feedbackService.createPublicFeedback(request, files);
return new ResponseEntity<>(createdFeedback, HttpStatus.CREATED);
}
```
### 5.12 主管控制器 (`SupervisorController.java`)
#### 功能概述
主管控制器专为主管角色设计,提供反馈审核和管理功能。
#### 主要接口
- **待审核反馈** (`GET /api/supervisor/reviews`): 获取待审核的反馈列表
- **审核通过** (`POST /api/supervisor/reviews/{feedbackId}/approve`): 审核通过反馈
- **审核拒绝** (`POST /api/supervisor/reviews/{feedbackId}/reject`): 审核拒绝反馈
#### 审核流程
```java
@PostMapping("/reviews/{feedbackId}/approve")
@PreAuthorize("hasAnyRole('SUPERVISOR', 'ADMIN')")
public ResponseEntity<Void> approveFeedback(@PathVariable Long feedbackId) {
supervisorService.approveFeedback(feedbackId);
return ResponseEntity.ok().build();
}
@PostMapping("/reviews/{feedbackId}/reject")
@PreAuthorize("hasAnyRole('SUPERVISOR', 'ADMIN')")
public ResponseEntity<Void> rejectFeedback(
@PathVariable Long feedbackId,
@RequestBody RejectFeedbackRequest request) {
supervisorService.rejectFeedback(feedbackId, request);
return ResponseEntity.ok().build();
}
```
### 5.13 任务分配控制器 (`TaskAssignmentController.java`)
#### 功能概述
任务分配控制器处理任务的智能分配和人员调度功能。
#### 主要接口
- **未分配任务** (`GET /api/tasks/unassigned`): 获取未分配的任务列表
- **可用网格员** (`GET /api/tasks/grid-workers`): 获取可用的网格工作人员
- **分配任务** (`POST /api/tasks/assign`): 将任务分配给指定工作人员
#### 智能分配
```java
@PostMapping("/assign")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPERVISOR')")
public ResponseEntity<Assignment> assignTask(
@RequestBody AssignmentRequest request,
@AuthenticationPrincipal CustomUserDetails adminDetails) {
Long assignerId = adminDetails.getId();
Assignment newAssignment = taskAssignmentService.assignTask(
request.feedbackId(),
request.assigneeId(),
assignerId
);
return ResponseEntity.ok(newAssignment);
}
```
### 5.14 任务管理控制器 (`TaskManagementController.java`)
#### 功能概述
任务管理控制器提供任务全生命周期的管理功能,是任务管理的核心控制器。
#### 主要接口
- **任务列表** (`GET /api/management/tasks`): 支持多条件过滤的任务分页查询
- **分配任务** (`POST /api/management/tasks/{taskId}/assign`): 分配任务给工作人员
- **任务详情** (`GET /api/management/tasks/{taskId}`): 获取任务详细信息
- **审核任务** (`POST /api/management/tasks/{taskId}/review`): 审核任务完成情况
- **取消任务** (`POST /api/management/tasks/{taskId}/cancel`): 取消任务
- **创建任务** (`POST /api/management/tasks`): 创建新任务
- **待处理反馈** (`GET /api/management/tasks/feedback`): 获取待处理的反馈
- **从反馈创建任务** (`POST /api/management/tasks/feedback/{feedbackId}/create-task`): 从反馈创建任务
#### 高级过滤查询
```java
@GetMapping
@PreAuthorize("hasAnyAuthority('ADMIN', 'SUPERVISOR', 'DECISION_MAKER')")
public ResponseEntity<List<TaskSummaryDTO>> getTasks(
@RequestParam(required = false) TaskStatus status,
@RequestParam(required = false) Long assigneeId,
@RequestParam(required = false) SeverityLevel severity,
@RequestParam(required = false) PollutionType pollutionType,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
Pageable pageable) {
// 支持多维度过滤的任务查询
}
```
#### 任务状态流转
```java
@PostMapping("/{taskId}/review")
public ResponseEntity<TaskDetailDTO> reviewTask(
@PathVariable Long taskId,
@RequestBody @Valid TaskApprovalRequest request) {
TaskDetailDTO updatedTask = taskManagementService.reviewTask(taskId, request);
return ResponseEntity.ok(updatedTask);
}
@PostMapping("/{taskId}/cancel")
public ResponseEntity<TaskDetailDTO> cancelTask(@PathVariable Long taskId) {
TaskDetailDTO updatedTask = taskManagementService.cancelTask(taskId);
return ResponseEntity.ok(updatedTask);
}
```
### 5.15 控制器模块最佳实践
#### 1. RESTful API设计
- **统一的URL命名规范**: 使用名词复数形式,避免动词
- **HTTP方法语义化**: GET查询、POST创建、PUT/PATCH更新、DELETE删除
- **状态码规范**: 正确使用HTTP状态码表达操作结果
- **版本控制**: 通过URL路径进行API版本管理
#### 2. 安全控制
- **细粒度权限控制**: 使用`@PreAuthorize`进行方法级权限控制
- **输入验证**: 使用Bean Validation进行参数校验
- **敏感数据保护**: 避免在响应中暴露敏感信息(如密码)
- **CSRF防护**: 对状态变更操作进行CSRF保护
#### 3. 异常处理
- **全局异常处理**: 使用`@ControllerAdvice`统一处理异常
- **友好错误信息**: 为客户端提供清晰的错误描述
- **日志记录**: 记录详细的错误日志便于问题排查
#### 4. 性能优化
- **分页查询**: 对大数据量查询使用分页机制
- **缓存策略**: 对频繁查询的数据使用缓存
- **异步处理**: 对耗时操作使用异步处理
- **数据传输优化**: 使用DTO减少不必要的数据传输
#### 5. 文档和测试
- **API文档**: 使用Swagger/OpenAPI生成完整的API文档
- **单元测试**: 为每个控制器方法编写单元测试
- **集成测试**: 测试完整的请求-响应流程
- **性能测试**: 对关键接口进行性能测试
### 5.16 控制器模块总结
控制器模块为EMS系统提供了完整的API服务层
1. **全面的功能覆盖**: 14个专业化控制器覆盖了系统的所有核心功能
2. **严格的权限控制**: 基于角色的访问控制确保系统安全
3. **灵活的查询支持**: 支持多维度过滤、分页、排序等高级查询功能
4. **完善的文件处理**: 支持文件上传、下载、预览等文件操作
5. **智能的任务管理**: 提供完整的任务生命周期管理和智能分配功能
6. **实时的数据可视化**: 为决策支持提供丰富的数据分析接口
7. **用户友好的接口设计**: 遵循RESTful设计原则提供直观易用的API
通过这些控制器的协同工作EMS系统能够为不同角色的用户提供专业化、个性化的服务实现了从公众反馈到问题解决的完整业务闭环。
6. **扩展性**: 为WebSocket等实时通信功能预留扩展空间
这些配置组件共同构成了一个健壮、可扩展、易维护的系统基础架构为EMS系统的稳定运行和功能扩展提供了坚实的技术保障。
---
# DTO模块详细解析
## 概述
DTOData Transfer Object模块位于 `com.dne.ems.dto` 包下包含51个数据传输对象负责在不同层之间传递数据确保数据的封装性和安全性。DTO模块按功能分类涵盖用户管理、任务管理、反馈处理、地图网格、统计分析和AI集成等核心业务领域。
## 核心功能分类
### 1. 用户管理相关DTO
#### 用户注册和认证
- **SignUpRequest**: 用户注册请求,包含姓名、邮箱、手机、密码、角色和验证码
- **LoginRequest**: 用户登录请求,封装邮箱和密码
- **UserRegistrationRequest**: 公众用户注册请求,支持密码复杂度验证
- **JwtAuthenticationResponse**: JWT认证响应返回访问令牌
#### 用户信息管理
- **UserCreationRequest**: 管理员创建用户请求,支持角色分配和区域设置
- **UserUpdateRequest**: 用户信息更新请求,支持多字段批量更新
- **UserRoleUpdateRequest**: 用户角色更新请求,网格员需要位置信息
- **UserInfoDTO**: 用户基本信息传输对象,隐藏敏感数据
- **UserSummaryDTO**: 用户摘要信息,用于列表展示
#### 密码管理
- **PasswordResetRequest**: 密码重置邮件请求
- **PasswordResetDto**: 通过令牌重置密码
- **PasswordResetWithCodeDto**: 通过验证码重置密码
### 2. 任务管理相关DTO
#### 任务创建和分配
- **TaskCreationRequest**: 任务创建请求,包含标题、描述、污染类型、严重级别和位置
- **TaskAssignmentRequest**: 任务分配请求指定受理人ID
- **TaskFromFeedbackRequest**: 从反馈创建任务请求,设置严重级别
#### 任务处理和审核
- **TaskSubmissionRequest**: 任务提交请求,包含处理意见
- **TaskApprovalRequest**: 任务审核请求,支持通过或拒绝
- **TaskRejectionRequest**: 任务拒绝请求,必须提供拒绝原因
#### 任务信息展示
- **TaskDTO**: 任务基本信息包含ID、标题、描述和状态
- **TaskSummaryDTO**: 任务摘要信息,用于列表展示
- **TaskDetailDTO**: 任务详细信息,包含完整的任务数据和历史记录
- **TaskInfoDTO**: 任务简要信息,包含状态和受理人
- **TaskHistoryDTO**: 任务历史记录,追踪状态变更
- **TaskStatsDTO**: 任务统计数据,计算完成率
### 3. 反馈处理相关DTO
#### 反馈提交
- **PublicFeedbackRequest**: 公众反馈提交请求,支持匿名提交
- **FeedbackSubmissionRequest**: 反馈提交请求,包含位置和附件信息
- **AqiDataSubmissionRequest**: AQI数据提交请求专门处理空气质量数据
#### 反馈处理
- **ProcessFeedbackRequest**: 反馈处理请求,支持批准或采取行动
- **RejectFeedbackRequest**: 反馈拒绝请求,需要提供拒绝原因
#### 反馈信息展示
- **FeedbackDTO**: 反馈详细信息传输对象
- **FeedbackResponseDTO**: 反馈响应数据封装
- **UserFeedbackSummaryDTO**: 用户反馈历史摘要
- **FeedbackStatsResponse**: 反馈统计数据响应
### 4. 地图网格相关DTO
#### 网格管理
- **GridUpdateRequest**: 网格属性更新请求
- **GridAssignmentRequest**: 网格员分配请求
- **GridCoverageDTO**: 网格覆盖统计数据
- **CityBlueprint**: 城市网格蓝图生成
#### 位置和路径
- **LocationUpdateRequest**: 用户位置更新请求
- **PathfindingRequest**: A*路径查找请求,包含起点和终点
- **Point**: 二维坐标点表示
### 5. 统计分析相关DTO
#### 仪表盘统计
- **DashboardStatsDTO**: 仪表盘关键统计数据
- **PollutionStatsDTO**: 污染物统计数据
- **TrendDataPointDTO**: 趋势数据点,用于时间序列分析
#### 热力图数据
- **HeatmapPointDTO**: 通用热力图数据点
- **AqiHeatmapPointDTO**: AQI热力图专用数据点
- **AqiDistributionDTO**: AQI分布统计数据
- **PollutantThresholdDTO**: 污染物阈值信息
### 6. 通用工具DTO
#### 分页和响应
- **PageDTO**: 分页查询结果封装,支持泛型
- **ErrorResponseDTO**: API错误响应标准格式
- **AttachmentDTO**: 附件信息封装
- **AssigneeInfoDTO**: 任务受理人信息
### 7. AI集成相关DTO
#### 火山引擎AI聊天
- **VolcanoChatRequest**: 火山引擎聊天API请求支持多轮对话和工具调用
- **VolcanoChatResponse**: 火山引擎聊天API响应包含AI回复和工具调用结果
## 核心代码示例
### 任务创建请求DTO
```java
public record TaskCreationRequest(
@NotBlank(message = "Title is mandatory")
String title,
String description,
@NotNull(message = "Pollution type is mandatory")
PollutionType pollutionType,
@NotNull(message = "Severity level is mandatory")
SeverityLevel severityLevel,
@NotNull(message = "Location information is mandatory")
LocationDTO location
) {
public record LocationDTO(
@NotBlank(message = "Address is mandatory")
String address,
@NotNull(message = "Grid X coordinate is mandatory")
Integer gridX,
@NotNull(message = "Grid Y coordinate is mandatory")
Integer gridY
) {}
}
```
### 分页查询结果DTO
```java
public record PageDTO<T>(
List<T> content,
int page,
int size,
long totalElements,
int totalPages,
boolean first,
boolean last
) {
public static <T> PageDTO<T> fromPage(Page<T> page) {
return new PageDTO<>(
page.getContent(),
page.getNumber(),
page.getSize(),
page.getTotalElements(),
page.getTotalPages(),
page.isFirst(),
page.isLast()
);
}
}
```
### 用户信息DTO工厂方法
```java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfoDTO {
private Long id;
private String name;
private String phone;
private String email;
public static UserInfoDTO fromEntity(UserAccount user) {
if (user == null) {
return null;
}
return new UserInfoDTO(
user.getId(),
user.getName(),
user.getPhone(),
user.getEmail()
);
}
}
```
### AI聊天请求DTO
```java
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class VolcanoChatRequest {
private String model;
private List<Message> messages;
private List<Tool> tools;
@Data
@SuperBuilder
public static class Message {
private String role;
private List<ContentPart> content;
}
@Data
@SuperBuilder
public static class ContentPart {
private String type;
private String text;
@JsonProperty("image_url")
private ImageUrl imageUrl;
}
}
```
## 设计特点
### 1. 数据验证
- 使用Jakarta Validation注解进行参数校验
- 自定义验证注解(如@ValidPassword@GridWorkerLocationRequired
- 分层验证策略,确保数据完整性
### 2. 类型安全
- 大量使用Record类型确保不可变性
- 泛型支持如PageDTO<T>提供类型安全的分页
- 枚举类型约束,避免无效值
### 3. 序列化优化
- Jackson注解配置控制JSON序列化行为
- @JsonInclude(JsonInclude.Include.NON_NULL)避免空值传输
- @JsonProperty处理字段名映射
### 4. 业务语义
- 清晰的命名约定Request/Response/DTO后缀明确用途
- 丰富的JavaDoc文档说明业务规则和使用场景
- 嵌套记录结构,体现数据层次关系
## DTO模块最佳实践
1. **不可变设计**优先使用Record类型确保数据传输对象的不可变性
2. **验证完整性**在DTO层进行数据验证避免无效数据进入业务层
3. **类型安全**:使用强类型和泛型,减少运行时错误
4. **文档完善**提供详细的JavaDoc说明字段含义和业务规则
5. **序列化优化**合理使用Jackson注解优化网络传输效率
6. **分层隔离**DTO与实体类分离避免业务逻辑泄露到表示层
## DTO模块总结
DTO模块为EMS系统提供了完整的数据传输层
1. **全面的功能覆盖**: 51个DTO类覆盖了系统的所有核心功能领域
2. **严格的数据验证**: 多层次验证机制确保数据质量和安全性
3. **灵活的数据结构**: 支持复杂嵌套结构和泛型,满足不同业务需求
4. **优化的传输效率**: 通过合理的序列化配置减少网络传输开销
5. **清晰的业务语义**: 命名规范和文档完善,便于开发和维护
6. **类型安全保障**: Record类型和强类型约束减少运行时错误
7. **扩展性支持**: 为AI集成等新功能提供了良好的数据结构基础
通过这些DTO的协同工作EMS系统能够在各层之间安全、高效地传递数据为整个系统的稳定运行和功能扩展提供了坚实的数据传输基础。
---
# Event模块详细解析
## 概述
Event事件模块位于 `com.dne.ems.event` 包下包含2个应用事件类实现了系统的事件驱动架构。通过Spring的事件发布机制实现了业务模块之间的松耦合通信支持异步处理和系统扩展。
## 核心事件类
### 1. FeedbackSubmittedForAiReviewEvent
#### 功能概述
- **用途**: 反馈提交AI审核事件
- **触发时机**: 当公众提交反馈后系统需要进行AI预审核时发布
- **业务价值**: 实现反馈提交与AI审核的解耦支持异步AI分析
#### 核心特性
- **继承关系**: 继承自Spring的`ApplicationEvent`基类
- **数据载体**: 携带完整的`Feedback`实体对象
- **事件源**: 支持指定事件发布源,便于追踪和调试
#### 代码实现
```java
package com.dne.ems.event;
import org.springframework.context.ApplicationEvent;
import com.dne.ems.model.Feedback;
public class FeedbackSubmittedForAiReviewEvent extends ApplicationEvent {
private final Feedback feedback;
public FeedbackSubmittedForAiReviewEvent(Object source, Feedback feedback) {
super(source);
this.feedback = feedback;
}
public Feedback getFeedback() {
return feedback;
}
}
```
#### 使用场景
1. **反馈提交后**: 在`FeedbackService`中提交反馈成功后发布此事件
2. **AI审核触发**: 事件监听器接收到事件后调用AI服务进行内容分析
3. **异步处理**: 避免反馈提交流程被AI分析耗时阻塞
### 2. TaskReadyForAssignmentEvent
#### 功能概述
- **用途**: 任务准备分配事件
- **触发时机**: 当任务创建完成或状态变更为可分配时发布
- **业务价值**: 实现任务创建与分配逻辑的解耦,支持智能分配算法
#### 核心特性
- **继承关系**: 继承自Spring的`ApplicationEvent`基类
- **数据载体**: 携带完整的`Task`实体对象
- **Lombok支持**: 使用`@Getter`注解简化代码
- **不可变性**: 任务对象使用`final`修饰,确保事件数据不被修改
#### 代码实现
```java
package com.dne.ems.event;
import org.springframework.context.ApplicationEvent;
import com.dne.ems.model.Task;
import lombok.Getter;
@Getter
public class TaskReadyForAssignmentEvent extends ApplicationEvent {
private final Task task;
public TaskReadyForAssignmentEvent(Object source, Task task) {
super(source);
this.task = task;
}
}
```
#### 使用场景
1. **任务创建后**: 在`TaskService`中创建新任务后发布此事件
2. **状态变更**: 任务状态从其他状态变更为待分配时发布
3. **智能分配**: 事件监听器可以触发智能分配算法,推荐最佳执行人
4. **通知机制**: 通知相关人员有新任务需要分配
## 事件驱动架构优势
### 1. 松耦合设计
- **模块独立**: 事件发布者和监听者之间没有直接依赖关系
- **易于扩展**: 可以随时添加新的事件监听器而不影响现有代码
- **职责分离**: 每个模块专注于自己的核心业务逻辑
### 2. 异步处理能力
- **性能提升**: 耗时操作如AI分析不会阻塞主业务流程
- **用户体验**: 用户操作可以立即得到响应
- **系统吞吐**: 提高系统整体处理能力
### 3. 可观测性
- **事件追踪**: 可以记录事件的发布和处理过程
- **调试支持**: 便于排查业务流程中的问题
- **监控集成**: 可以与监控系统集成,实时观察系统状态
## 事件处理流程
### 反馈AI审核流程
```
1. 用户提交反馈 → FeedbackService.createFeedback()
2. 保存反馈到数据库
3. 发布 FeedbackSubmittedForAiReviewEvent
4. AI审核监听器接收事件
5. 调用AI服务分析反馈内容
6. 更新反馈状态和AI分析结果
```
### 任务分配流程
```
1. 创建新任务 → TaskService.createTask()
2. 保存任务到数据库
3. 发布 TaskReadyForAssignmentEvent
4. 任务分配监听器接收事件
5. 执行智能分配算法
6. 推荐最佳执行人或自动分配
```
## 技术实现细节
### 1. Spring事件机制
- **ApplicationEvent**: 所有自定义事件的基类
- **ApplicationEventPublisher**: 用于发布事件的接口
- **@EventListener**: 标记事件监听方法的注解
- **@Async**: 支持异步事件处理
### 2. 事件发布示例
```java
@Service
public class FeedbackService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public Feedback createFeedback(FeedbackRequest request) {
// 保存反馈
Feedback feedback = feedbackRepository.save(newFeedback);
// 发布事件
eventPublisher.publishEvent(
new FeedbackSubmittedForAiReviewEvent(this, feedback)
);
return feedback;
}
}
```
### 3. 事件监听示例
```java
@Component
public class FeedbackEventListener {
@EventListener
@Async
public void handleFeedbackSubmitted(FeedbackSubmittedForAiReviewEvent event) {
Feedback feedback = event.getFeedback();
// 执行AI审核逻辑
aiReviewService.reviewFeedback(feedback);
}
}
```
## Event模块最佳实践
1. **事件命名**: 使用清晰的命名约定,体现事件的业务含义
2. **数据封装**: 事件中携带的数据应该是完整且不可变的
3. **异步处理**: 对于耗时操作,使用`@Async`注解实现异步处理
4. **异常处理**: 事件监听器中要有完善的异常处理机制
5. **幂等性**: 确保事件处理的幂等性,避免重复处理问题
6. **监控日志**: 记录事件的发布和处理日志,便于问题排查
## Event模块总结
Event模块为EMS系统提供了强大的事件驱动能力
1. **解耦架构**: 通过事件机制实现模块间的松耦合通信
2. **异步处理**: 支持耗时操作的异步执行,提升系统性能
3. **扩展性强**: 易于添加新的业务逻辑而不影响现有代码
4. **可观测性**: 提供清晰的业务流程追踪和监控能力
5. **Spring集成**: 充分利用Spring框架的事件机制和异步支持
虽然目前只有2个事件类但它们为系统的核心业务流程反馈处理和任务分配提供了关键的解耦和异步处理能力为系统的可扩展性和性能优化奠定了坚实基础。
---
# Exception模块详细解析
## 概述
Exception异常处理模块位于 `com.dne.ems.exception` 包下包含7个异常处理相关的类构建了完整的异常处理体系。通过自定义异常类和全局异常处理器实现了统一的错误响应格式和完善的异常处理机制提升了系统的健壮性和用户体验。
## 核心异常类
### 1. ErrorResponseDTO
#### 功能概述
- **用途**: 标准化API错误响应数据传输对象
- **作用**: 为所有API端点提供一致的错误结构
- **业务价值**: 统一错误响应格式,提升前端处理效率
#### 核心特性
- **Lombok支持**: 使用`@Data``@AllArgsConstructor``@NoArgsConstructor`注解
- **时间戳**: 记录错误发生的精确时间
- **状态码**: HTTP状态码便于前端判断错误类型
- **错误分类**: 提供错误类型和详细消息
- **路径追踪**: 记录发生错误的请求路径
#### 代码实现
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ErrorResponseDTO {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
}
```
### 2. ResourceNotFoundException
#### 功能概述
- **用途**: 资源不存在异常
- **HTTP状态**: 404 Not Found
- **触发场景**: 查询的资源不存在时抛出
- **业务价值**: 明确区分资源不存在的情况
#### 核心特性
- **Spring集成**: 使用`@ResponseStatus(HttpStatus.NOT_FOUND)`注解
- **自动映射**: 异常抛出时自动返回404状态码
- **继承关系**: 继承自`RuntimeException`,支持非检查异常
#### 使用场景
1. **用户查询**: 查询不存在的用户ID
2. **任务查询**: 查询不存在的任务ID
3. **反馈查询**: 查询不存在的反馈记录
4. **文件查询**: 查询不存在的附件文件
### 3. InvalidOperationException
#### 功能概述
- **用途**: 无效操作异常
- **HTTP状态**: 400 Bad Request
- **触发场景**: 业务规则不允许的操作时抛出
- **业务价值**: 保护业务逻辑完整性
#### 核心特性
- **业务保护**: 防止违反业务规则的操作
- **状态映射**: 自动返回400状态码
- **简洁设计**: 只提供消息构造器,专注业务逻辑
#### 使用场景
1. **状态变更**: 任务状态不允许的变更操作
2. **权限检查**: 用户权限不足的操作
3. **数据验证**: 业务数据不符合规则
4. **流程控制**: 违反业务流程的操作
### 4. UserAlreadyExistsException
#### 功能概述
- **用途**: 用户已存在异常
- **HTTP状态**: 409 Conflict
- **触发场景**: 注册时用户名或邮箱已存在
- **业务价值**: 防止重复用户注册
#### 核心特性
- **冲突处理**: 使用409状态码表示资源冲突
- **注册保护**: 确保用户唯一性
- **清晰语义**: 明确表达用户重复的问题
#### 使用场景
1. **用户注册**: 用户名已被占用
2. **邮箱注册**: 邮箱地址已被使用
3. **账号创建**: 管理员创建重复账号
### 5. FileNotFoundException
#### 功能概述
- **用途**: 文件不存在异常
- **HTTP状态**: 404 Not Found
- **触发场景**: 请求的文件不存在时抛出
- **业务价值**: 专门处理文件相关的404错误
#### 核心特性
- **文件专用**: 专门处理文件操作异常
- **双构造器**: 支持消息和原因链的构造
- **异常链**: 支持异常原因追踪
#### 使用场景
1. **附件下载**: 下载不存在的附件文件
2. **图片访问**: 访问不存在的图片资源
3. **文档查看**: 查看已删除的文档
### 6. FileStorageException
#### 功能概述
- **用途**: 文件存储异常
- **HTTP状态**: 默认500通过全局处理器处理
- **触发场景**: 文件存储操作失败时抛出
- **业务价值**: 统一处理文件存储相关错误
#### 核心特性
- **存储专用**: 专门处理文件存储异常
- **异常链支持**: 保留原始异常信息
- **灵活处理**: 可通过全局处理器自定义响应
#### 使用场景
1. **文件上传**: 上传过程中的存储失败
2. **文件删除**: 删除文件时的IO错误
3. **文件移动**: 文件移动或重命名失败
4. **磁盘空间**: 存储空间不足的情况
### 7. GlobalExceptionHandler
#### 功能概述
- **用途**: 全局异常处理器
- **作用**: 统一处理控制器层抛出的异常
- **业务价值**: 提供一致的错误响应和完善的日志记录
#### 核心特性
- **全局捕获**: 使用`@ControllerAdvice`注解实现全局异常捕获
- **分类处理**: 针对不同异常类型提供专门的处理方法
- **日志记录**: 使用`@Slf4j`记录详细的错误日志
- **标准响应**: 返回统一格式的错误响应
#### 处理的异常类型
##### ResourceNotFoundException处理
```java
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponseDTO> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
ErrorResponseDTO errorResponse = new ErrorResponseDTO(
LocalDateTime.now(),
HttpStatus.NOT_FOUND.value(),
"Not Found",
ex.getMessage(),
request.getDescription(false).replace("uri=", ""));
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}
```
##### InvalidOperationException处理
```java
@ExceptionHandler(InvalidOperationException.class)
public ResponseEntity<ErrorResponseDTO> handleInvalidOperationException(
InvalidOperationException ex, WebRequest request) {
ErrorResponseDTO errorResponse = new ErrorResponseDTO(
LocalDateTime.now(),
HttpStatus.BAD_REQUEST.value(),
"Bad Request",
ex.getMessage(),
request.getDescription(false).replace("uri=", ""));
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
```
##### 全局异常处理
```java
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleGlobalException(Exception ex, WebRequest request) {
log.error("捕获到全局未处理异常! 请求: {}", request.getDescription(false), ex);
Map<String, String> errorDetails = Map.of(
"message", "服务器内部发生未知错误,请联系技术支持",
"error", ex.getClass().getName(),
"details", ex.getMessage()
);
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
```
## 异常处理架构优势
### 1. 统一错误格式
- **一致性**: 所有API错误都使用相同的响应格式
- **可预测**: 前端可以统一处理错误响应
- **信息完整**: 包含时间戳、状态码、错误类型、消息和路径
### 2. 分层异常处理
- **业务异常**: 自定义异常类处理特定业务场景
- **系统异常**: 全局处理器兜底处理未预期异常
- **HTTP映射**: 自动将异常映射为合适的HTTP状态码
### 3. 完善的日志记录
- **错误追踪**: 详细记录异常发生的上下文
- **问题定位**: 便于开发人员快速定位问题
- **监控集成**: 支持与监控系统集成
### 4. 用户友好
- **清晰消息**: 提供用户可理解的错误消息
- **状态码**: 正确的HTTP状态码便于前端处理
- **安全性**: 不暴露系统内部敏感信息
## 异常处理流程
### 标准异常处理流程
```
1. 业务层抛出自定义异常
2. 控制器层捕获异常
3. GlobalExceptionHandler拦截异常
4. 根据异常类型选择处理方法
5. 构建ErrorResponseDTO响应
6. 记录错误日志
7. 返回标准化错误响应
```
### 异常分类处理
```
业务异常:
├── ResourceNotFoundException (404) - 资源不存在
├── InvalidOperationException (400) - 无效操作
├── UserAlreadyExistsException (409) - 用户已存在
├── FileNotFoundException (404) - 文件不存在
└── FileStorageException (500) - 文件存储错误
系统异常:
└── Exception (500) - 未处理的系统异常
```
## Exception模块最佳实践
1. **异常分类**: 根据业务场景创建专门的异常类
2. **状态码映射**: 使用`@ResponseStatus`注解自动映射HTTP状态码
3. **消息设计**: 提供清晰、用户友好的错误消息
4. **日志记录**: 在全局处理器中记录详细的错误日志
5. **安全考虑**: 避免在错误响应中暴露敏感信息
6. **异常链**: 保留原始异常信息便于问题追踪
7. **统一格式**: 使用ErrorResponseDTO确保响应格式一致
## Exception模块总结
Exception模块为EMS系统提供了完善的异常处理能力
1. **完整体系**: 涵盖业务异常、系统异常和文件异常的完整处理体系
2. **统一响应**: 通过ErrorResponseDTO提供一致的错误响应格式
3. **自动映射**: 利用Spring的@ResponseStatus注解自动映射HTTP状态码
4. **全局处理**: GlobalExceptionHandler提供兜底的异常处理机制
5. **日志完善**: 详细的错误日志记录便于问题追踪和系统监控
6. **用户友好**: 清晰的错误消息和正确的状态码提升用户体验
7. **安全可靠**: 在提供有用信息的同时保护系统安全
通过这套异常处理机制EMS系统能够优雅地处理各种异常情况为用户提供清晰的错误反馈为开发人员提供完善的错误追踪能力大大提升了系统的健壮性和可维护性。
---
# Listener模块详细解析
## 概述
Listener监听器模块位于 `com.dne.ems.listener` 包下包含3个事件监听器类实现了系统的事件驱动响应机制。通过监听Spring Security认证事件和自定义业务事件实现了安全控制、用户状态管理和业务流程自动化为系统的安全性和业务连续性提供了重要保障。
## 核心监听器类
### 1. AuthenticationFailureEventListener
#### 功能概述
- **用途**: 认证失败事件监听器
- **监听事件**: `AuthenticationFailureBadCredentialsEvent`
- **触发场景**: 用户登录认证失败时
- **业务价值**: 实现登录失败次数统计和IP锁定机制
#### 核心特性
- **Spring Security集成**: 监听Spring Security的认证失败事件
- **IP地址获取**: 智能获取真实客户端IP地址
- **代理支持**: 支持X-Forwarded-For头部处理
- **安全防护**: 防止暴力破解攻击
#### 代码实现
```java
@Component
public class AuthenticationFailureEventListener
implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
@Autowired
private LoginAttemptService loginAttemptService;
@Autowired
private HttpServletRequest request;
@Override
public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent event) {
final String xfHeader = request.getHeader("X-Forwarded-For");
String ip;
if (xfHeader == null || xfHeader.isEmpty() || !xfHeader.contains(request.getRemoteAddr())) {
ip = request.getRemoteAddr();
} else {
ip = xfHeader.split(",")[0];
}
// 使用IP地址作为锁定键
loginAttemptService.loginFailed(ip);
}
}
```
#### IP地址获取逻辑
1. **代理检测**: 检查X-Forwarded-For头部是否存在
2. **真实IP**: 优先使用代理转发的真实客户端IP
3. **直连IP**: 无代理时使用直接连接的IP地址
4. **安全验证**: 验证代理IP的合法性
#### 使用场景
1. **登录保护**: 记录失败的登录尝试
2. **暴力破解防护**: 限制同一IP的登录尝试次数
3. **安全审计**: 记录可疑的登录行为
4. **IP黑名单**: 自动封禁恶意IP地址
### 2. AuthenticationSuccessEventListener
#### 功能概述
- **用途**: 认证成功事件监听器
- **监听事件**: `AuthenticationSuccessEvent`
- **触发场景**: 用户登录认证成功时
- **业务价值**: 重置用户登录失败计数和解除账户锁定
#### 核心特性
- **状态重置**: 成功登录后重置失败计数
- **锁定解除**: 自动解除账户锁定状态
- **用户管理**: 维护用户账户状态的一致性
- **安全恢复**: 恢复正常用户的访问权限
#### 代码实现
```java
@Component
public class AuthenticationSuccessEventListener
implements ApplicationListener<AuthenticationSuccessEvent> {
@Autowired
private UserAccountRepository userAccountRepository;
@Override
public void onApplicationEvent(@NonNull AuthenticationSuccessEvent event) {
Object principal = event.getAuthentication().getPrincipal();
if (principal instanceof UserDetails userDetails) {
String username = userDetails.getUsername();
UserAccount user = userAccountRepository.findByEmail(username).orElse(null);
if (user != null && user.getFailedLoginAttempts() > 0) {
user.setFailedLoginAttempts(0);
user.setLockoutEndTime(null);
userAccountRepository.save(user);
}
}
}
}
```
#### 处理流程
1. **身份验证**: 确认认证主体是UserDetails实例
2. **用户查找**: 根据用户名查找用户账户
3. **状态检查**: 检查是否存在失败登录记录
4. **状态重置**: 清零失败次数并解除锁定
5. **数据持久化**: 保存更新后的用户状态
#### 使用场景
1. **账户恢复**: 成功登录后恢复账户正常状态
2. **计数重置**: 清除之前的失败登录记录
3. **锁定解除**: 自动解除临时锁定状态
4. **状态同步**: 保持用户状态的一致性
### 3. FeedbackEventListener
#### 功能概述
- **用途**: 反馈事件监听器
- **监听事件**: `FeedbackSubmittedForAiReviewEvent`
- **触发场景**: 反馈提交需要AI审核时
- **业务价值**: 实现反馈的异步AI审核处理
#### 核心特性
- **异步处理**: 使用`@Async`注解实现异步执行
- **事件驱动**: 基于Spring的事件机制
- **AI集成**: 调用AI服务进行内容审核
- **日志记录**: 详细记录处理过程
#### 代码实现
```java
@Component
@Slf4j
@RequiredArgsConstructor
public class FeedbackEventListener {
private final AiReviewService aiReviewService;
@Async
@EventListener
public void handleFeedbackSubmittedForAiReview(FeedbackSubmittedForAiReviewEvent event) {
log.info("Received feedback submission event for AI review. Feedback ID: {}",
event.getFeedback().getId());
aiReviewService.reviewFeedback(event.getFeedback());
}
}
```
#### 处理特点
1. **异步执行**: 不阻塞主业务流程
2. **事件响应**: 响应反馈提交事件
3. **AI调用**: 调用AI服务进行内容分析
4. **日志追踪**: 记录处理过程便于监控
#### 使用场景
1. **内容审核**: 自动审核用户提交的反馈内容
2. **智能分类**: AI分析反馈的类型和优先级
3. **质量评估**: 评估反馈的有效性和重要性
4. **自动处理**: 根据AI分析结果自动处理简单反馈
## 监听器架构优势
### 1. 事件驱动架构
- **解耦设计**: 事件发布者和监听者之间松耦合
- **扩展性强**: 可以轻松添加新的事件监听器
- **职责分离**: 每个监听器专注于特定的业务逻辑
- **异步支持**: 支持异步事件处理提升性能
### 2. 安全机制增强
- **登录保护**: 自动记录和处理登录失败
- **状态管理**: 智能管理用户账户状态
- **攻击防护**: 防止暴力破解和恶意登录
- **恢复机制**: 自动恢复正常用户的访问权限
### 3. 业务流程自动化
- **智能审核**: 自动触发AI内容审核
- **状态同步**: 自动同步用户和业务状态
- **流程优化**: 减少手动干预提升效率
- **响应及时**: 实时响应业务事件
### 4. 系统可观测性
- **日志记录**: 详细记录事件处理过程
- **状态追踪**: 跟踪用户和业务状态变化
- **性能监控**: 监控事件处理性能
- **问题定位**: 便于排查和解决问题
## 事件处理流程
### 认证失败处理流程
```
1. 用户登录失败 → Spring Security触发AuthenticationFailureBadCredentialsEvent
2. AuthenticationFailureEventListener接收事件
3. 获取客户端真实IP地址
4. 调用LoginAttemptService记录失败尝试
5. 更新IP失败计数和锁定状态
```
### 认证成功处理流程
```
1. 用户登录成功 → Spring Security触发AuthenticationSuccessEvent
2. AuthenticationSuccessEventListener接收事件
3. 获取用户身份信息
4. 查找用户账户记录
5. 重置失败计数和解除锁定
6. 保存用户状态更新
```
### 反馈AI审核流程
```
1. 反馈提交 → 发布FeedbackSubmittedForAiReviewEvent
2. FeedbackEventListener异步接收事件
3. 记录处理日志
4. 调用AiReviewService进行内容审核
5. 更新反馈状态和审核结果
```
## 技术实现细节
### 1. Spring事件监听机制
- **ApplicationListener**: 传统的事件监听接口
- **@EventListener**: 基于注解的事件监听方法
- **@Async**: 异步事件处理注解
- **事件发布**: 通过ApplicationEventPublisher发布事件
### 2. 依赖注入和管理
- **@Component**: 将监听器注册为Spring组件
- **@Autowired**: 自动注入依赖服务
- **@RequiredArgsConstructor**: Lombok构造器注入
- **生命周期管理**: Spring容器管理监听器生命周期
### 3. 异步处理配置
```java
@EnableAsync
@Configuration
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("EventListener-");
executor.initialize();
return executor;
}
}
```
## Listener模块最佳实践
1. **事件监听**: 使用合适的监听方式(接口或注解)
2. **异步处理**: 对于耗时操作使用@Async注解
3. **异常处理**: 在监听器中添加完善的异常处理
4. **日志记录**: 记录关键事件的处理过程
5. **性能考虑**: 避免在监听器中执行阻塞操作
6. **事务管理**: 注意事件监听器的事务边界
7. **测试覆盖**: 为监听器编写单元测试和集成测试
## Listener模块总结
Listener模块为EMS系统提供了强大的事件响应能力
1. **安全保障**: 通过认证事件监听实现登录安全控制
2. **状态管理**: 自动管理用户账户和业务状态
3. **业务自动化**: 实现反馈AI审核等业务流程自动化
4. **异步处理**: 支持异步事件处理提升系统性能
5. **扩展性强**: 易于添加新的事件监听器扩展功能
6. **可观测性**: 提供详细的事件处理日志和监控
7. **Spring集成**: 充分利用Spring框架的事件机制
通过这套监听器机制EMS系统能够及时响应各种系统事件和业务事件实现了安全控制、状态管理和业务流程的自动化为系统的稳定运行和用户体验提供了重要保障。
---
# Model模块详细解析
## 概述
Model数据模型模块位于 `com.dne.ems.model` 包下包含14个核心实体类和10个枚举类构成了EMS系统的完整数据模型。该模块使用JPA/Hibernate进行对象关系映射定义了系统中所有业务实体的结构、关系和约束为整个系统提供了坚实的数据基础。
## 核心实体类
### 1. 用户管理相关实体
#### UserAccount用户账户
**功能概述**
- **用途**: 系统用户账户的核心实体
- **映射表**: `user_account`
- **业务价值**: 管理所有用户的基本信息、角色权限和状态
**核心特性**
- **身份验证**: 存储用户登录凭据(邮箱、手机、密码)
- **角色管理**: 支持多种用户角色(管理员、主管、网格员等)
- **地理关联**: 支持网格坐标和区域信息
- **技能管理**: JSON格式存储用户技能列表
- **状态控制**: 支持账户激活、停用、请假等状态
**关键字段**
```java
@Entity
@Table(name = "user_account")
public class UserAccount {
@Id
private Long id;
@NotEmpty
@Column(unique = true)
private String email;
@NotEmpty
@Column(unique = true)
private String phone;
@Enumerated(EnumType.STRING)
private Role role;
@Enumerated(EnumType.STRING)
private UserStatus status;
@Convert(converter = StringListConverter.class)
private List<String> skills;
private Integer gridX;
private Integer gridY;
}
```
#### PasswordResetToken密码重置令牌
**功能概述**
- **用途**: 管理用户密码重置流程的安全令牌
- **映射表**: `password_reset_token`
- **业务价值**: 提供安全的密码重置机制
**核心特性**
- **安全性**: 唯一令牌字符串,防止伪造
- **时效性**: 60分钟有效期自动过期
- **一对一关联**: 每个令牌关联特定用户
- **状态检查**: 提供过期检查方法
### 2. 反馈管理相关实体
#### Feedback环境问题反馈
**功能概述**
- **用途**: 系统核心实体,记录用户提交的环境问题报告
- **映射表**: `feedback`
- **业务价值**: 收集、管理和跟踪环境问题反馈
**核心特性**
- **唯一标识**: 人类可读的事件ID
- **分类管理**: 污染类型和严重程度分类
- **状态跟踪**: 完整的处理状态生命周期
- **地理定位**: 支持网格坐标和文本地址
- **关联管理**: 与用户、任务、附件的关联关系
**关键字段**
```java
@Entity
@Table(name = "feedback")
public class Feedback {
@Id
private Long id;
@Column(unique = true)
private String eventId;
@Enumerated(EnumType.STRING)
private PollutionType pollutionType;
@Enumerated(EnumType.STRING)
private SeverityLevel severityLevel;
@Enumerated(EnumType.STRING)
private FeedbackStatus status;
private Integer gridX;
private Integer gridY;
@OneToMany(mappedBy = "feedback")
private List<Attachment> attachments;
}
```
#### Attachment附件
**功能概述**
- **用途**: 管理反馈和任务提交的文件附件
- **映射表**: `attachment`
- **业务价值**: 支持多媒体证据收集和存储
**核心特性**
- **文件元数据**: 存储文件名、类型、大小等信息
- **存储管理**: 生成唯一存储文件名防止冲突
- **多重关联**: 可关联反馈或任务提交
- **时间戳**: 自动记录上传时间
### 3. 任务管理相关实体
#### Task网格任务
**功能概述**
- **用途**: 表示网格工作人员执行的任务
- **映射表**: `tasks`
- **业务价值**: 管理从反馈到任务的完整生命周期
**核心特性**
- **反馈关联**: 可从反馈自动生成或手动创建
- **分配管理**: 支持任务分配和重新分配
- **状态跟踪**: 完整的任务状态生命周期
- **时间管理**: 创建、分配、完成时间戳
- **地理信息**: 继承反馈的位置信息
**关键字段**
```java
@Entity
@Table(name = "tasks")
public class Task {
@Id
private Long id;
@OneToOne
private Feedback feedback;
@ManyToOne
private UserAccount assignee;
@Enumerated(EnumType.STRING)
private TaskStatus status;
@CreationTimestamp
private LocalDateTime createdAt;
private LocalDateTime assignedAt;
private LocalDateTime completedAt;
}
```
#### TaskSubmission任务提交
**功能概述**
- **用途**: 记录网格员完成任务后的提交信息
- **映射表**: `task_submissions`
- **业务价值**: 收集任务执行结果和工作记录
**核心特性**
- **任务关联**: 与特定任务一对多关系
- **详细记录**: 大文本字段存储工作笔记
- **时间戳**: 自动记录提交时间
- **附件支持**: 可关联多个附件文件
#### TaskHistory任务历史
**功能概述**
- **用途**: 记录任务状态变更的审计日志
- **映射表**: `task_history`
- **业务价值**: 提供完整的任务处理轨迹
**核心特性**
- **状态变更**: 记录旧状态和新状态
- **操作人员**: 记录状态变更的执行者
- **变更时间**: 自动记录变更时间戳
- **备注信息**: 支持变更原因说明
### 4. 分配管理相关实体
#### Assignment任务分配
**功能概述**
- **用途**: 管理反馈任务到网格员的分配关系
- **映射表**: `assignments`
- **业务价值**: 跟踪任务分配的生命周期
**核心特性**
- **一对一关联**: 每个分配对应一个任务
- **分配者记录**: 记录执行分配的用户
- **截止日期**: 支持任务截止时间设置
- **状态管理**: 跟踪分配状态变化
- **备注信息**: 支持分配说明和要求
#### AssignmentRecord分配记录
**功能概述**
- **用途**: 详细记录反馈分配的元数据
- **映射表**: `assignment_record`
- **业务价值**: 提供丰富的分配分析数据
**核心特性**
- **关系映射**: 直接关联反馈、网格员、管理员
- **分配方法**: 记录手动或智能分配方式
- **算法详情**: JSON格式存储智能分配算法细节
- **创建时间**: 自动记录分配创建时间
### 5. 空气质量数据相关实体
#### AqiData空气质量指数数据
**功能概述**
- **用途**: 存储网格员提交的详细空气质量数据
- **映射表**: `aqi_data`
- **业务价值**: 收集实时环境监测数据
**核心特性**
- **多污染物**: 支持PM2.5、PM10、SO2、NO2、CO、O3等
- **网格关联**: 与具体网格位置关联
- **报告者**: 记录数据提交的网格员
- **反馈关联**: 可选关联相关反馈
**关键字段**
```java
@Entity
@Table(name = "aqi_data")
public class AqiData {
@Id
private Long id;
@ManyToOne
private Grid grid;
private Long reporterId;
private Integer aqiValue;
private Double pm25;
private Double pm10;
private Double so2;
private Double no2;
private Double co;
private Double o3;
}
```
#### AqiRecord空气质量历史记录
**功能概述**
- **用途**: 存储历史空气质量数据用于趋势分析
- **映射表**: `aqi_records`
- **业务价值**: 支持热力图生成和趋势分析
**核心特性**
- **城市级别**: 按城市名称组织数据
- **完整污染物**: 包含所有主要污染物浓度
- **地理坐标**: 支持经纬度和网格坐标
- **时间戳**: 自动记录数据时间
### 6. 地理网格相关实体
#### Grid地理网格
**功能概述**
- **用途**: 表示地理网格单元用于区域管理
- **映射表**: `grid`
- **业务价值**: 支持基于网格的数据聚合和管理
**核心特性**
- **坐标系统**: X、Y坐标定位
- **行政区划**: 城市和区县信息
- **障碍标记**: 支持限制区域标记
- **描述信息**: 网格详细描述
#### MapGrid地图网格
**功能概述**
- **用途**: 专门用于A*寻路算法的网格单元
- **映射表**: `map_grid`
- **业务价值**: 支持智能路径规划和导航
**核心特性**
- **唯一约束**: (x,y)坐标唯一性约束
- **障碍检测**: 支持障碍物标记
- **地形类型**: 可选的地形类型信息
- **城市约束**: 限制寻路范围
### 7. 系统配置相关实体
#### PollutantThreshold污染物阈值
**功能概述**
- **用途**: 存储不同污染物的超标阈值设置
- **映射表**: `pollutant_thresholds`
- **业务价值**: 支持污染物超标检测和预警
**核心特性**
- **类型关联**: 与污染物类型枚举关联
- **阈值设置**: 可配置的超标阈值
- **单位管理**: 支持不同计量单位
- **描述信息**: 阈值设置说明
## 枚举类型系统
### 1. 用户相关枚举
#### Role用户角色
```java
public enum Role {
PUBLIC_SUPERVISOR, // 公众监督员
SUPERVISOR, // 主管
GRID_WORKER, // 网格员
ADMIN, // 系统管理员
DECISION_MAKER // 决策者
}
```
#### UserStatus用户状态
```java
public enum UserStatus {
ACTIVE, // 活跃
INACTIVE, // 停用
ON_LEAVE // 请假
}
```
#### Gender性别
```java
public enum Gender {
MALE, // 男性
FEMALE, // 女性
UNKNOWN // 未知
}
```
#### Level级别
```java
public enum Level {
PROVINCE, // 省级
CITY // 市级
}
```
### 2. 业务流程相关枚举
#### FeedbackStatus反馈状态
```java
public enum FeedbackStatus {
PENDING_REVIEW, // 待审核
AI_REVIEWING, // AI审核中
AI_REVIEW_FAILED, // AI审核失败
AI_PROCESSING, // AI处理中
PENDING_ASSIGNMENT, // 待分配
ASSIGNED, // 已分配
CONFIRMED, // 已确认
RESOLVED, // 已解决
CLOSED // 已关闭
}
```
#### TaskStatus任务状态
```java
public enum TaskStatus {
PENDING_ASSIGNMENT, // 待分配
ASSIGNED, // 已分配
IN_PROGRESS, // 进行中
SUBMITTED, // 已提交
COMPLETED, // 已完成
CANCELLED // 已取消
}
```
#### AssignmentStatus分配状态
```java
public enum AssignmentStatus {
PENDING, // 待处理
IN_PROGRESS, // 进行中
COMPLETED, // 已完成
OVERDUE // 已逾期
}
```
#### AssignmentMethod分配方法
```java
public enum AssignmentMethod {
MANUAL, // 手动分配
INTELLIGENT // 智能分配
}
```
### 3. 环境数据相关枚举
#### PollutionType污染类型
```java
public enum PollutionType {
PM25, // 细颗粒物
O3, // 臭氧
NO2, // 二氧化氮
SO2, // 二氧化硫
OTHER // 其他
}
```
#### SeverityLevel严重程度
```java
public enum SeverityLevel {
HIGH, // 高
MEDIUM, // 中
LOW // 低
}
```
## 数据模型设计特点
### 1. 关系设计
- **一对一关系**: Task-Feedback, Assignment-Task
- **一对多关系**: UserAccount-Task, Feedback-Attachment
- **多对一关系**: AqiData-Grid, Task-UserAccount
- **自引用关系**: TaskHistory-Task审计日志
### 2. 约束和验证
- **唯一性约束**: 用户邮箱、手机号、反馈事件ID
- **非空约束**: 关键业务字段强制非空
- **枚举约束**: 使用枚举类型确保数据一致性
- **长度约束**: 字符串字段长度限制
### 3. 时间戳管理
- **创建时间**: @CreationTimestamp自动设置
- **更新时间**: @UpdateTimestamp自动更新
- **业务时间**: 分配时间、完成时间等业务节点
- **过期检查**: 密码重置令牌过期验证
### 4. JSON数据支持
- **技能列表**: 用户技能JSON存储
- **算法详情**: 智能分配算法详情JSON存储
- **大文本**: 使用@Lob注解支持长文本
## 数据模型架构优势
### 1. 业务完整性
- **全生命周期**: 覆盖从反馈提交到任务完成的完整流程
- **状态管理**: 详细的状态枚举和状态转换
- **审计追踪**: 完整的操作历史记录
- **数据关联**: 清晰的实体关系和数据流向
### 2. 扩展性设计
- **枚举扩展**: 易于添加新的状态和类型
- **实体扩展**: 支持新增字段和关系
- **JSON存储**: 灵活的半结构化数据支持
- **继承支持**: 预留实体继承扩展空间
### 3. 性能优化
- **懒加载**: 使用FetchType.LAZY优化查询性能
- **索引设计**: 唯一约束自动创建索引
- **批量操作**: 支持CascadeType级联操作
- **查询优化**: 合理的关系设计减少N+1问题
### 4. 数据安全
- **密码加密**: 密码字段加密存储
- **令牌安全**: 密码重置令牌时效性控制
- **状态控制**: 用户状态控制访问权限
- **数据完整性**: 外键约束保证数据一致性
## 技术实现细节
### 1. JPA注解使用
```java
// 实体映射
@Entity
@Table(name = "table_name")
// 主键生成
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
// 关系映射
@OneToOne
@OneToMany
@ManyToOne
@ManyToMany
// 枚举映射
@Enumerated(EnumType.STRING)
// 时间戳管理
@CreationTimestamp
@UpdateTimestamp
```
### 2. Lombok注解
```java
@Data // getter/setter/toString/equals/hashCode
@Builder // 建造者模式
@NoArgsConstructor // 无参构造器
@AllArgsConstructor // 全参构造器
```
### 3. 验证注解
```java
@NotEmpty // 非空验证
@NotNull // 非null验证
@Email // 邮箱格式验证
@Size(min = 8) // 长度验证
```
### 4. 自定义转换器
```java
@Convert(converter = StringListConverter.class)
private List<String> skills;
```
## Model模块最佳实践
1. **实体设计**: 遵循单一职责原则,每个实体专注特定业务领域
2. **关系映射**: 合理使用懒加载,避免性能问题
3. **枚举使用**: 优先使用枚举类型确保数据一致性
4. **时间戳**: 统一使用Hibernate注解管理时间字段
5. **验证约束**: 在实体层添加基础验证约束
6. **命名规范**: 遵循Java命名规范和数据库命名规范
7. **文档注释**: 为每个实体和字段添加详细注释
## Model模块总结
Model模块为EMS系统提供了完整、规范的数据模型基础
1. **业务完整性**: 覆盖用户管理、反馈处理、任务分配、数据收集等全业务流程
2. **数据一致性**: 通过枚举类型、约束和验证确保数据质量
3. **关系清晰**: 合理的实体关系设计支持复杂业务场景
4. **扩展性强**: 灵活的设计支持业务需求变化和功能扩展
5. **性能优化**: 合理的加载策略和索引设计保证系统性能
6. **安全可靠**: 完善的约束和验证机制保证数据安全
7. **标准规范**: 遵循JPA规范和最佳实践便于维护和扩展
通过这套完整的数据模型EMS系统建立了坚实的数据基础为上层业务逻辑提供了可靠的数据支撑确保了系统的稳定性、可扩展性和可维护性。
---
# Repository模块详细解析
## 概述
Repository数据访问层模块位于 `com.dne.ems.repository` 包下包含14个Repository接口构成了EMS系统完整的数据访问层。该模块基于Spring Data JPA框架提供了标准化的数据访问接口支持基础CRUD操作、自定义查询方法、复杂查询和分页查询为业务层提供了高效、可靠的数据访问服务。
## 核心Repository接口
### 1. 用户管理相关Repository
#### UserAccountRepository用户账户数据访问
**功能概述**
- **继承接口**: `JpaRepository<UserAccount, Long>`, `JpaSpecificationExecutor<UserAccount>`
- **业务价值**: 提供用户账户的完整数据访问功能
- **核心特性**: 支持复杂查询、分页查询、规格查询
**核心查询方法**
```java
@Repository
public interface UserAccountRepository extends JpaRepository<UserAccount, Long>, JpaSpecificationExecutor<UserAccount> {
// 基础查询
Optional<UserAccount> findByEmail(String email);
Optional<UserAccount> findByPhone(String phone);
Optional<UserAccount> findByGridXAndGridY(Integer gridX, Integer gridY);
// 角色相关查询
List<UserAccount> findByRole(Role role);
long countByRole(Role role);
Page<UserAccount> findByRole(Role role, Pageable pageable);
List<UserAccount> findByRoleAndStatus(Role role, UserStatus status);
// 网格员专用查询
@Query("SELECT u FROM UserAccount u WHERE u.role = 'GRID_WORKER' AND u.status = 'ACTIVE' AND u.gridX BETWEEN ?1 AND ?2 AND u.gridY BETWEEN ?3 AND ?4")
List<UserAccount> findActiveWorkersInArea(int minX, int maxX, int minY, int maxY);
@Query("SELECT u FROM UserAccount u WHERE u.role = 'GRID_WORKER' AND u.gridX = ?1 AND u.gridY = ?2")
List<UserAccount> findGridWorkersByCoordinates(int gridX, int gridY);
@Query("SELECT u FROM UserAccount u WHERE u.role = 'GRID_WORKER' AND u.gridX IS NOT NULL AND u.gridY IS NOT NULL AND u.gridX >= 0 AND u.gridY >= 0")
List<UserAccount> findAllAssignedGridWorkers();
@Query("SELECT u FROM UserAccount u WHERE u.role = 'GRID_WORKER' AND (u.gridX IS NULL OR u.gridY IS NULL OR u.gridX < 0 OR u.gridY < 0)")
List<UserAccount> findAllUnassignedGridWorkers();
@Query("SELECT u FROM UserAccount u WHERE u.role = 'GRID_WORKER' AND u.region LIKE %?1%")
Page<UserAccount> findGridWorkersByCity(String cityName, Pageable pageable);
}
```
**使用场景**
- **身份验证**: 通过邮箱或手机号查找用户
- **角色管理**: 按角色查询和统计用户
- **网格管理**: 查找特定区域或坐标的网格员
- **用户分配**: 查找已分配和未分配网格的网格员
- **区域查询**: 按城市查找网格员
#### PasswordResetTokenRepository密码重置令牌数据访问
**功能概述**
- **继承接口**: `JpaRepository<PasswordResetToken, Long>`
- **业务价值**: 管理密码重置令牌的数据访问
- **核心特性**: 支持令牌查找和用户关联查询
**核心查询方法**
```java
@Repository
public interface PasswordResetTokenRepository extends JpaRepository<PasswordResetToken, Long> {
Optional<PasswordResetToken> findByToken(String token);
Optional<PasswordResetToken> findByUser(UserAccount user);
}
```
### 2. 反馈管理相关Repository
#### FeedbackRepository反馈数据访问
**功能概述**
- **继承接口**: `JpaRepository<Feedback, Long>`, `JpaSpecificationExecutor<Feedback>`
- **业务价值**: 提供反馈数据的完整访问功能
- **核心特性**: 支持复杂查询、分页查询、统计查询、热力图数据
**核心查询方法**
```java
@Repository
public interface FeedbackRepository extends JpaRepository<Feedback, Long>, JpaSpecificationExecutor<Feedback> {
// 基础查询
Optional<Feedback> findByEventId(String eventId);
List<Feedback> findByStatus(FeedbackStatus status);
long countByStatus(FeedbackStatus status);
// 用户相关查询
Page<Feedback> findBySubmitterId(Long submitterId, Pageable pageable);
List<Feedback> findBySubmitterId(Long submitterId);
long countBySubmitterId(Long submitterId);
long countByStatusAndSubmitterId(FeedbackStatus status, Long submitterId);
// 统计查询
@Query("SELECT new com.dne.ems.dto.HeatmapPointDTO(f.gridX, f.gridY, COUNT(f.id)) FROM Feedback f WHERE f.status = 'CONFIRMED' AND f.gridX IS NOT NULL AND f.gridY IS NOT NULL GROUP BY f.gridX, f.gridY")
List<HeatmapPointDTO> getHeatmapData();
@Query("SELECT new com.dne.ems.dto.PollutionStatsDTO(f.pollutionType, COUNT(f)) FROM Feedback f GROUP BY f.pollutionType")
List<PollutionStatsDTO> countByPollutionType();
// 性能优化查询
@Override
@EntityGraph(attributePaths = {"user", "attachments"})
Page<Feedback> findAll(Specification<Feedback> spec, Pageable pageable);
}
```
**使用场景**
- **反馈管理**: 按状态、用户、事件ID查询反馈
- **数据统计**: 统计各状态反馈数量和污染类型分布
- **热力图**: 生成反馈分布热力图数据
- **性能优化**: 使用EntityGraph避免N+1查询问题
#### AttachmentRepository附件数据访问
**功能概述**
- **继承接口**: `JpaRepository<Attachment, Long>`
- **业务价值**: 管理反馈和任务提交的附件数据
- **核心特性**: 支持文件名查找和关联实体查询
**核心查询方法**
```java
@Repository
public interface AttachmentRepository extends JpaRepository<Attachment, Long> {
Optional<Attachment> findByStoredFileName(String storedFileName);
List<Attachment> findByTaskSubmission(TaskSubmission taskSubmission);
List<Attachment> findByFeedback(Feedback feedback);
}
```
### 3. 任务管理相关Repository
#### TaskRepository任务数据访问
**功能概述**
- **继承接口**: `JpaRepository<Task, Long>`, `JpaSpecificationExecutor<Task>`
- **业务价值**: 提供任务数据的完整访问功能
- **核心特性**: 支持复杂查询、状态统计、分配查询
**核心查询方法**
```java
@Repository
public interface TaskRepository extends JpaRepository<Task, Long>, JpaSpecificationExecutor<Task> {
Optional<Task> findByFeedback(Feedback feedback);
long countByStatus(TaskStatus status);
long countByAssigneeIdAndStatusIn(Long assigneeId, Collection<TaskStatus> statuses);
List<Task> findByAssigneeIdAndStatus(Long assigneeId, TaskStatus status);
Page<Task> findByStatusAndAssignee(AssignmentStatus status, UserAccount assignee, Pageable pageable);
}
```
#### TaskSubmissionRepository任务提交数据访问
**功能概述**
- **继承接口**: `JpaRepository<TaskSubmission, Long>`
- **业务价值**: 管理任务提交记录的数据访问
- **核心特性**: 支持按任务查找最新提交
**核心查询方法**
```java
@Repository
public interface TaskSubmissionRepository extends JpaRepository<TaskSubmission, Long> {
TaskSubmission findFirstByTaskOrderBySubmittedAtDesc(Task task);
}
```
#### TaskHistoryRepository任务历史数据访问
**功能概述**
- **继承接口**: `JpaRepository<TaskHistory, Long>`
- **业务价值**: 管理任务状态变更历史记录
- **核心特性**: 支持按任务ID查询历史记录
**核心查询方法**
```java
@Repository
public interface TaskHistoryRepository extends JpaRepository<TaskHistory, Long> {
List<TaskHistory> findByTaskIdOrderByChangedAtDesc(Long taskId);
}
```
### 4. 分配管理相关Repository
#### AssignmentRepository任务分配数据访问
**功能概述**
- **继承接口**: `JpaRepository<Assignment, Long>`
- **业务价值**: 管理任务分配关系的数据访问
- **核心特性**: 支持按分配者查询分配记录
**核心查询方法**
```java
@Repository
public interface AssignmentRepository extends JpaRepository<Assignment, Long> {
List<Assignment> findByTaskAssigneeId(Long assigneeId);
}
```
#### AssignmentRecordRepository分配记录数据访问
**功能概述**
- **继承接口**: `JpaRepository<AssignmentRecord, Long>`
- **业务价值**: 管理分配记录的详细数据
- **核心特性**: 基础CRUD操作预留扩展空间
### 5. 空气质量数据相关Repository
#### AqiDataRepository空气质量数据访问
**功能概述**
- **继承接口**: `JpaRepository<AqiData, Long>`
- **业务价值**: 管理实时空气质量数据的访问
- **核心特性**: 支持统计查询、趋势分析、热力图数据
**核心查询方法**
```java
@Repository
public interface AqiDataRepository extends JpaRepository<AqiData, Long> {
// AQI分布统计
@Query("SELECT new com.dne.ems.dto.AqiDistributionDTO(CASE WHEN a.aqiValue <= 50 THEN 'Good' WHEN a.aqiValue <= 100 THEN 'Moderate' WHEN a.aqiValue <= 150 THEN 'Unhealthy for Sensitive Groups' WHEN a.aqiValue <= 200 THEN 'Unhealthy' WHEN a.aqiValue <= 300 THEN 'Very Unhealthy' ELSE 'Hazardous' END, COUNT(a.id)) FROM AqiData a GROUP BY 1")
List<AqiDistributionDTO> getAqiDistribution();
// 月度超标趋势原生SQL
@Query(value = "SELECT DATE_FORMAT(record_time, '%Y-%m') as yearMonth, COUNT(id) as count FROM aqi_data WHERE aqi_value > 100 AND record_time >= :startDate GROUP BY DATE_FORMAT(record_time, '%Y-%m') ORDER BY 1", nativeQuery = true)
List<Object[]> getMonthlyExceedanceTrendRaw(@Param("startDate") LocalDateTime startDate);
// 默认方法转换原生查询结果
default List<TrendDataPointDTO> getMonthlyExceedanceTrend(LocalDateTime startDate) {
List<Object[]> rawResults = getMonthlyExceedanceTrendRaw(startDate);
return rawResults.stream()
.map(row -> new TrendDataPointDTO((String) row[0], ((Number) row[1]).longValue()))
.toList();
}
// 热力图数据
@Query("SELECT new com.dne.ems.dto.AqiHeatmapPointDTO(g.gridX, g.gridY, AVG(d.aqiValue)) FROM AqiData d JOIN d.grid g WHERE g.gridX IS NOT NULL AND g.gridY IS NOT NULL GROUP BY g.gridX, g.gridY")
List<AqiHeatmapPointDTO> getAqiHeatmapData();
}
```
#### AqiRecordRepository空气质量历史记录数据访问
**功能概述**
- **继承接口**: `JpaRepository<AqiRecord, Long>`
- **业务价值**: 管理历史空气质量数据的访问
- **核心特性**: 支持复杂阈值查询、时间范围查询、热力图数据
**核心查询方法**
```java
@Repository
public interface AqiRecordRepository extends JpaRepository<AqiRecord, Long> {
// AQI分布统计
@Query("SELECT new com.dne.ems.dto.AqiDistributionDTO(CASE WHEN ar.aqiValue <= 50 THEN 'Good' WHEN ar.aqiValue <= 100 THEN 'Moderate' WHEN ar.aqiValue <= 150 THEN 'Unhealthy for Sensitive Groups' WHEN ar.aqiValue <= 200 THEN 'Unhealthy' WHEN ar.aqiValue <= 300 THEN 'Very Unhealthy' ELSE 'Hazardous' END, COUNT(ar.id)) FROM AqiRecord ar GROUP BY CASE WHEN ar.aqiValue <= 50 THEN 'Good' WHEN ar.aqiValue <= 100 THEN 'Moderate' WHEN ar.aqiValue <= 150 THEN 'Unhealthy for Sensitive Groups' WHEN ar.aqiValue <= 200 THEN 'Unhealthy' WHEN ar.aqiValue <= 300 THEN 'Very Unhealthy' ELSE 'Hazardous' END")
List<AqiDistributionDTO> getAqiDistribution();
// 超标记录查询
@Query("SELECT ar FROM AqiRecord ar WHERE ar.aqiValue > 100 AND ar.recordTime >= :startDate")
List<AqiRecord> findExceedanceRecordsSince(@Param("startDate") LocalDateTime startDate);
// 多阈值超标查询
@Query("SELECT ar FROM AqiRecord ar WHERE ar.recordTime >= :startDate AND (ar.aqiValue > :aqiThreshold OR ar.pm25 > :pm25Threshold OR ar.o3 > :o3Threshold OR ar.no2 > :no2Threshold OR ar.so2 > :so2Threshold)")
List<AqiRecord> findExceedanceRecordsWithThresholds(@Param("startDate") LocalDateTime startDate, @Param("aqiThreshold") Double aqiThreshold, @Param("pm25Threshold") Double pm25Threshold, @Param("o3Threshold") Double o3Threshold, @Param("no2Threshold") Double no2Threshold, @Param("so2Threshold") Double so2Threshold);
// 时间范围查询
List<AqiRecord> findByRecordTimeAfter(LocalDateTime startDate);
// 热力图数据JPQL
@Query("SELECT new com.dne.ems.dto.AqiHeatmapPointDTO(ar.gridX, ar.gridY, AVG(ar.aqiValue)) FROM AqiRecord ar WHERE ar.gridX IS NOT NULL AND ar.gridY IS NOT NULL GROUP BY ar.gridX, ar.gridY")
List<AqiHeatmapPointDTO> getAqiHeatmapData();
// 热力图数据原生SQL
@Query(value = "SELECT grid_x, grid_y, AVG(aqi_value) as avg_aqi FROM aqi_records WHERE grid_x IS NOT NULL AND grid_y IS NOT NULL GROUP BY grid_x, grid_y", nativeQuery = true)
List<Object[]> getAqiHeatmapDataRaw();
}
```
### 6. 地理网格相关Repository
#### GridRepository地理网格数据访问
**功能概述**
- **继承接口**: `JpaRepository<Grid, Long>`, `JpaSpecificationExecutor<Grid>`
- **业务价值**: 管理地理网格数据的访问
- **核心特性**: 支持坐标查询、城市统计、障碍物查询
**核心查询方法**
```java
@Repository
public interface GridRepository extends JpaRepository<Grid, Long>, JpaSpecificationExecutor<Grid> {
Optional<Grid> findByGridXAndGridY(Integer gridX, Integer gridY);
List<Grid> findByIsObstacle(boolean isObstacle);
@Query("SELECT new com.dne.ems.dto.GridCoverageDTO(g.cityName, COUNT(g.id)) FROM Grid g WHERE g.cityName IS NOT NULL AND g.cityName != '' GROUP BY g.cityName ORDER BY COUNT(g.id) DESC")
List<GridCoverageDTO> getGridCoverageByCity();
}
```
#### MapGridRepository地图网格数据访问
**功能概述**
- **继承接口**: `JpaRepository<MapGrid, Long>`
- **业务价值**: 管理A*寻路算法的网格数据
- **核心特性**: 支持坐标查询和有序遍历
**核心查询方法**
```java
@Repository
public interface MapGridRepository extends JpaRepository<MapGrid, Long> {
Optional<MapGrid> findByXAndY(int x, int y);
List<MapGrid> findAllByOrderByYAscXAsc();
}
```
### 7. 系统配置相关Repository
#### PollutantThresholdRepository污染物阈值数据访问
**功能概述**
- **继承接口**: `JpaRepository<PollutantThreshold, Long>`
- **业务价值**: 管理污染物阈值配置数据
- **核心特性**: 支持按污染物名称查询阈值
**核心查询方法**
```java
@Repository
public interface PollutantThresholdRepository extends JpaRepository<PollutantThreshold, Long> {
Optional<PollutantThreshold> findByPollutantName(String pollutantName);
}
```
## Repository设计特点
### 1. 继承体系设计
- **JpaRepository**: 提供基础CRUD操作
- **JpaSpecificationExecutor**: 支持动态查询和复杂条件
- **双重继承**: 核心实体同时继承两个接口,提供完整功能
### 2. 查询方法类型
- **方法名查询**: 基于Spring Data JPA命名规范的自动查询
- **@Query注解**: 自定义JPQL查询语句
- **原生SQL**: 使用nativeQuery进行复杂数据库操作
- **默认方法**: 在接口中提供数据转换逻辑
### 3. 性能优化策略
- **EntityGraph**: 解决N+1查询问题
- **分页查询**: 支持大数据量的分页处理
- **索引利用**: 查询方法充分利用数据库索引
- **批量操作**: 继承JpaRepository的批量操作能力
### 4. 查询复杂度分层
- **简单查询**: 单字段或少量字段的精确匹配
- **条件查询**: 多条件组合和范围查询
- **统计查询**: 聚合函数和分组统计
- **关联查询**: 跨表JOIN查询和子查询
## Repository架构优势
### 1. 标准化设计
- **Spring Data JPA**: 遵循Spring生态标准
- **命名规范**: 统一的方法命名和参数规范
- **注解驱动**: 使用标准注解进行配置
- **接口导向**: 面向接口编程,便于测试和扩展
### 2. 功能完整性
- **CRUD操作**: 完整的增删改查功能
- **复杂查询**: 支持各种业务查询需求
- **统计分析**: 提供数据统计和分析功能
- **性能优化**: 内置性能优化机制
### 3. 扩展性强
- **自定义查询**: 支持添加新的查询方法
- **规格查询**: 支持动态条件构建
- **原生SQL**: 支持复杂的数据库操作
- **默认方法**: 支持在接口中添加业务逻辑
### 4. 维护性好
- **类型安全**: 编译时类型检查
- **自动实现**: Spring自动生成实现类
- **测试友好**: 易于进行单元测试和集成测试
- **文档清晰**: 方法名自解释,注释完整
## 技术实现细节
### 1. Spring Data JPA注解
```java
@Repository // 标识为Repository组件
@Query("JPQL语句") // 自定义JPQL查询
@Query(value="SQL", nativeQuery=true) // 原生SQL查询
@Param("参数名") // 命名参数绑定
@EntityGraph(attributePaths={"关联属性"}) // 性能优化
```
### 2. 查询方法命名规范
```java
findBy... // 查询方法前缀
countBy... // 统计方法前缀
existsBy... // 存在性检查前缀
deleteBy... // 删除方法前缀
// 条件关键字
And, Or // 逻辑连接
Between, LessThan, GreaterThan // 范围条件
Like, NotLike // 模糊匹配
In, NotIn // 集合条件
IsNull, IsNotNull // 空值检查
OrderBy...Asc/Desc // 排序
```
### 3. 分页和排序
```java
Page<Entity> findBy...(Pageable pageable); // 分页查询
List<Entity> findBy...OrderBy...(); // 排序查询
Slice<Entity> findBy...(Pageable pageable); // 切片查询
```
### 4. 规格查询Specification
```java
public interface EntityRepository extends JpaRepository<Entity, Long>, JpaSpecificationExecutor<Entity> {
// 支持动态条件查询
Page<Entity> findAll(Specification<Entity> spec, Pageable pageable);
}
```
## Repository模块最佳实践
1. **接口设计**: 保持接口简洁,职责单一
2. **查询优化**: 合理使用EntityGraph避免N+1问题
3. **命名规范**: 遵循Spring Data JPA命名约定
4. **参数验证**: 在Service层进行参数验证Repository专注数据访问
5. **事务管理**: 在Service层管理事务Repository保持无状态
6. **异常处理**: 让Spring Data JPA的异常向上传播
7. **测试覆盖**: 为自定义查询方法编写集成测试
8. **性能监控**: 监控慢查询并进行优化
## Repository模块总结
Repository模块为EMS系统提供了完整、高效的数据访问层
1. **标准化架构**: 基于Spring Data JPA的标准化设计
2. **功能完整**: 覆盖所有业务实体的数据访问需求
3. **查询丰富**: 支持简单查询、复杂查询、统计查询、分页查询
4. **性能优化**: 内置多种性能优化机制
5. **扩展性强**: 支持自定义查询和动态条件构建
6. **维护性好**: 类型安全、自动实现、测试友好
7. **业务适配**: 针对EMS业务特点设计的专用查询方法
通过这套完整的Repository层EMS系统建立了高效、可靠的数据访问基础为业务层提供了丰富的数据操作能力确保了系统的数据访问性能和开发效率。
---
# Service模块详细解析
## 概述
Service业务服务层模块位于 `com.dne.ems.service` 包下包含17个服务接口和对应的实现类构成了EMS系统完整的业务逻辑层。该模块基于Spring框架的服务层架构提供了标准化的业务服务接口实现了复杂的业务逻辑处理、事务管理、安全控制和系统集成为控制器层提供了高质量的业务服务。
## 核心Service接口
### 1. 认证与安全相关Service
#### AuthService认证服务
**功能概述**
- **接口定义**: `AuthService`
- **实现类**: `AuthServiceImpl`
- **业务价值**: 提供用户认证和安全相关的核心功能
- **核心特性**: 用户注册、登录认证、密码重置、JWT令牌管理
**核心方法**
```java
public interface AuthService {
// 密码重置流程
void requestPasswordReset(String email);
void resetPasswordWithToken(String token, String newPassword);
void resetPasswordWithCode(String email, String code, String newPassword);
// 用户注册与登录
void registerUser(SignUpRequest signUpRequest);
JwtAuthenticationResponse signIn(LoginRequest loginRequest);
// 验证码相关
void sendRegistrationCode(String email);
boolean validateRegistrationCode(String email, String code);
}
```
**业务流程**
- **用户注册**: 验证码校验 → 用户信息验证 → 创建用户账户 → 返回注册结果
- **用户登录**: 身份验证 → 生成JWT令牌 → 记录登录状态 → 返回认证响应
- **密码重置**: 发送验证码 → 验证码校验 → 密码更新 → 清理令牌
#### JwtServiceJWT令牌服务
**功能概述**
- **接口定义**: `JwtService`
- **实现类**: `JwtServiceImpl`
- **业务价值**: 处理JWT令牌的生成、解析和验证
- **核心特性**: HS256算法签名、24小时有效期、用户角色权限声明
**核心方法**
```java
public interface JwtService {
String extractUserName(String token);
Claims extractAllClaims(String token);
String generateToken(UserDetails userDetails);
boolean isTokenValid(String token, UserDetails userDetails);
}
```
#### LoginAttemptService登录尝试服务
**功能概述**
- **接口定义**: `LoginAttemptService`
- **实现类**: `LoginAttemptServiceImpl`
- **业务价值**: 防止暴力破解攻击,管理登录失败次数
- **核心特性**: 失败次数统计、账户锁定机制、自动解锁
**核心方法**
```java
public interface LoginAttemptService {
void loginSucceeded(String key);
void loginFailed(String key);
boolean isBlocked(String key);
}
```
#### VerificationCodeService验证码服务
**功能概述**
- **接口定义**: `VerificationCodeService`
- **实现类**: `VerificationCodeServiceImpl`
- **业务价值**: 生成、发送和校验验证码
- **核心特性**: 6位数字验证码、5分钟有效期、60秒发送间隔
**核心方法**
```java
public interface VerificationCodeService {
void sendVerificationCode(String email);
boolean verifyCode(String email, String code);
}
```
### 2. 用户管理相关Service
#### UserAccountService用户账户服务
**功能概述**
- **接口定义**: `UserAccountService`
- **实现类**: `UserAccountServiceImpl`
- **业务价值**: 管理用户账户的完整生命周期
- **核心特性**: 用户注册、角色管理、位置更新、用户查询
**核心方法**
```java
public interface UserAccountService {
UserAccount registerUser(UserRegistrationRequest registrationRequest);
UserAccount updateUserRole(Long userId, UserRoleUpdateRequest request);
UserAccount getUserById(Long userId);
Page<UserAccount> getUsersByRole(Role role, Pageable pageable);
void updateUserLocation(Long userId, Double latitude, Double longitude);
Page<UserSummaryDTO> getAllUsers(Role role, UserStatus status, Pageable pageable);
}
```
#### PersonnelService人员管理服务
**功能概述**
- **接口定义**: `PersonnelService`
- **实现类**: `PersonnelServiceImpl`
- **业务价值**: 提供人员管理的高级功能
- **核心特性**: 用户创建、信息更新、删除管理、个人资料更新
**核心方法**
```java
public interface PersonnelService {
Page<UserAccount> getUsers(Role role, String name, Pageable pageable);
UserAccount createUser(UserCreationRequest request);
UserAccount updateUser(Long userId, UserUpdateRequest request);
void deleteUser(Long userId);
UserAccount updateOwnProfile(String currentUserEmail, UserUpdateRequest request);
}
```
### 3. 反馈管理相关Service
#### FeedbackService反馈服务
**功能概述**
- **接口定义**: `FeedbackService`
- **实现类**: `FeedbackServiceImpl`
- **业务价值**: 处理环境问题反馈的提交和管理
- **核心特性**: 用户反馈提交、匿名反馈、反馈查询、数据统计
**核心方法**
```java
public interface FeedbackService {
// 反馈提交
Feedback submitFeedback(FeedbackSubmissionRequest request, MultipartFile[] files);
Feedback submitPublicFeedback(PublicFeedbackRequest request, MultipartFile[] files);
// 反馈查询
Page<FeedbackResponseDTO> getFeedbacks(FeedbackStatus status, PollutionType pollutionType,
SeverityLevel severityLevel, LocalDate startDate,
LocalDate endDate, Pageable pageable);
FeedbackResponseDTO getFeedbackById(Long feedbackId);
// 反馈处理
void processFeedback(Long feedbackId, ProcessFeedbackRequest request);
// 统计分析
FeedbackStatsResponse getFeedbackStats();
}
```
**业务流程**
- **反馈提交**: 用户信息验证 → 反馈数据创建 → 文件上传处理 → AI审核触发 → 返回反馈实体
- **反馈处理**: 状态验证 → 业务逻辑处理 → 状态更新 → 事件发布
#### UserFeedbackService用户反馈服务
**功能概述**
- **接口定义**: `UserFeedbackService`
- **实现类**: `UserFeedbackServiceImpl`
- **业务价值**: 提供用户特定的反馈操作
- **核心特性**: 用户反馈历史查询
**核心方法**
```java
public interface UserFeedbackService {
Page<UserFeedbackSummaryDTO> getFeedbackHistoryByUserId(Long userId, Pageable pageable);
}
```
#### AiReviewServiceAI审核服务
**功能概述**
- **接口定义**: `AiReviewService`
- **实现类**: `AiReviewServiceImpl`
- **业务价值**: 使用AI模型审核反馈内容
- **核心特性**: 外部AI服务调用、反馈状态更新
**核心方法**
```java
public interface AiReviewService {
void reviewFeedback(Feedback feedback);
}
```
### 4. 任务管理相关Service
#### TaskManagementService任务管理服务
**功能概述**
- **接口定义**: `TaskManagementService`
- **实现类**: `TaskManagementServiceImpl`
- **业务价值**: 处理任务全生命周期管理
- **核心特性**: 任务创建、分配、状态管理、审核审批
**核心方法**
```java
public interface TaskManagementService {
// 任务查询
Page<TaskSummaryDTO> getTasks(TaskStatus status, Long assigneeId, SeverityLevel severity,
PollutionType pollutionType, LocalDate startDate,
LocalDate endDate, Pageable pageable);
TaskDetailDTO getTaskById(Long taskId);
// 任务创建
TaskDetailDTO createTask(TaskCreationRequest request);
TaskDetailDTO createTaskFromFeedback(TaskFromFeedbackRequest request);
// 任务分配
void assignTask(Long taskId, TaskAssignmentRequest request);
// 任务审批
void approveTask(Long taskId, TaskApprovalRequest request);
void rejectTask(Long taskId, TaskApprovalRequest request);
// 任务历史
List<TaskHistoryDTO> getTaskHistory(Long taskId);
}
```
**业务流程**
- **任务创建**: 请求验证 → 任务实体创建 → 历史记录 → 事件发布
- **任务分配**: 分配者验证 → 被分配者验证 → 分配关系创建 → 状态更新
- **任务审批**: 权限验证 → 状态流转 → 审批记录 → 通知发送
#### TaskAssignmentService任务分配服务
**功能概述**
- **接口定义**: `TaskAssignmentService`
- **实现类**: `TaskAssignmentServiceImpl`
- **业务价值**: 处理任务分配操作
- **核心特性**: 未分配任务查询、网格员查询、任务分配
**核心方法**
```java
public interface TaskAssignmentService {
List<Feedback> getUnassignedFeedback();
List<UserAccount> getAvailableGridWorkers();
Assignment assignTask(Long feedbackId, Long assigneeId, Long assignerId);
}
```
#### GridWorkerTaskService网格员任务服务
**功能概述**
- **接口定义**: `GridWorkerTaskService`
- **实现类**: `GridWorkerTaskServiceImpl`
- **业务价值**: 处理网格员的任务管理操作
- **核心特性**: 任务查询、状态流转、任务提交
**核心方法**
```java
public interface GridWorkerTaskService {
Page<TaskSummaryDTO> getAssignedTasks(Long workerId, TaskStatus status, Pageable pageable);
TaskDetailDTO getTaskDetail(Long taskId, Long workerId);
void acceptTask(Long taskId, Long workerId);
void startTask(Long taskId, Long workerId);
void submitTask(Long taskId, TaskSubmissionRequest request, MultipartFile[] files);
List<TaskSummaryDTO> getTasksNearLocation(Double latitude, Double longitude, Double radiusKm);
}
```
### 5. 监管审核相关Service
#### SupervisorService主管服务
**功能概述**
- **接口定义**: `SupervisorService`
- **实现类**: `SupervisorServiceImpl`
- **业务价值**: 处理主管的审核操作
- **核心特性**: 反馈审核、审批决策
**核心方法**
```java
public interface SupervisorService {
List<Feedback> getFeedbackForReview();
void approveFeedback(Long feedbackId);
void rejectFeedback(Long feedbackId, RejectFeedbackRequest request);
}
```
### 6. 数据分析相关Service
#### DashboardService仪表盘服务
**功能概述**
- **接口定义**: `DashboardService`
- **实现类**: `DashboardServiceImpl`
- **业务价值**: 提供各类环境监测数据的统计和可视化数据
- **核心特性**: 环境质量指标统计、AQI数据分布、热力图数据、趋势分析
**核心方法**
```java
public interface DashboardService {
// 统计数据
List<PollutionStatsDTO> getPollutionStats();
TaskStatsDTO getTaskStats();
DashboardStatsDTO getDashboardStats();
// AQI数据
List<AqiDistributionDTO> getAqiDistribution();
List<AqiHeatmapPointDTO> getAqiHeatmapData();
List<TrendDataPointDTO> getMonthlyExceedanceTrend();
// 热力图数据
List<HeatmapPointDTO> getFeedbackHeatmapData();
// 网格覆盖
List<GridCoverageDTO> getGridCoverageByCity();
// 阈值管理
List<PollutantThresholdDTO> getAllThresholds();
PollutantThresholdDTO updateThreshold(String pollutantName, Double threshold);
}
```
### 7. 基础设施相关Service
#### FileStorageService文件存储服务
**功能概述**
- **接口定义**: `FileStorageService`
- **实现类**: `FileStorageServiceImpl`
- **业务价值**: 处理文件上传和下载
- **核心特性**: 安全文件存储、文件关联管理、下载服务
**核心方法**
```java
public interface FileStorageService {
@Deprecated
Attachment storeFileAndCreateAttachment(MultipartFile file, Feedback feedback);
String storeFile(MultipartFile file);
Resource loadFileAsResource(String fileName);
void deleteFile(String fileName);
}
```
#### MailService邮件服务
**功能概述**
- **接口定义**: `MailService`
- **实现类**: `MailServiceImpl`
- **业务价值**: 发送各类邮件
- **核心特性**: 密码重置邮件、注册验证码邮件
**核心方法**
```java
public interface MailService {
void sendPasswordResetEmail(String to, String code);
void sendVerificationCodeEmail(String to, String code);
}
```
#### GridService网格服务
**功能概述**
- **接口定义**: `GridService`
- **实现类**: `GridServiceImpl`
- **业务价值**: 处理网格数据的查询和更新
- **核心特性**: 网格查询、网格员分配、网格更新
**核心方法**
```java
public interface GridService {
List<Grid> getAllGrids();
Optional<Grid> getGridById(Long gridId);
UserAccount assignWorkerToGrid(Long gridId, Long userId);
void unassignWorkerFromGrid(Long gridId);
Page<Grid> getGridsWithFilters(String cityName, Boolean isObstacle, Pageable pageable);
Grid updateGrid(Long gridId, GridUpdateRequest request);
}
```
### 8. 算法服务
#### AStarServiceA*寻路服务)
**功能概述**
- **服务类**: `AStarService`(直接实现,无接口)
- **业务价值**: 提供A*寻路算法实现
- **核心特性**: 最短路径计算、障碍物避让、网格导航
**核心方法**
```java
@Service
public class AStarService {
public List<Point> findPath(Point start, Point end);
private int calculateHeuristic(Point a, Point b);
private List<Point> getNeighbors(Point point);
private List<Point> reconstructPath(Node endNode);
}
```
## Service设计特点
### 1. 分层架构设计
- **接口与实现分离**: 所有业务服务都定义了接口,便于测试和扩展
- **职责单一原则**: 每个服务专注于特定的业务领域
- **依赖注入**: 使用Spring的依赖注入管理服务间依赖
- **事务管理**: 在服务层统一管理事务边界
### 2. 业务逻辑封装
- **复杂业务流程**: 将复杂的业务逻辑封装在服务方法中
- **数据转换**: 在服务层完成实体与DTO之间的转换
- **业务验证**: 在服务层进行业务规则验证
- **异常处理**: 统一的业务异常处理机制
### 3. 安全控制
- **权限验证**: 在服务层进行细粒度的权限控制
- **数据安全**: 敏感数据的加密和脱敏处理
- **审计日志**: 关键操作的审计日志记录
- **防护机制**: 防止各种安全攻击的保护措施
### 4. 性能优化
- **缓存策略**: 合理使用缓存提升性能
- **批量处理**: 支持批量操作减少数据库访问
- **异步处理**: 使用异步机制处理耗时操作
- **分页查询**: 大数据量查询的分页处理
## Service架构优势
### 1. 业务完整性
- **端到端流程**: 完整的业务流程实现
- **状态管理**: 复杂的业务状态流转管理
- **数据一致性**: 确保业务数据的一致性
- **业务规则**: 完整的业务规则实现
### 2. 系统集成
- **外部服务**: 与外部系统的集成接口
- **事件驱动**: 基于事件的松耦合架构
- **消息传递**: 系统间的消息传递机制
- **API网关**: 统一的API访问入口
### 3. 扩展性强
- **插件化**: 支持插件化的功能扩展
- **配置驱动**: 基于配置的功能开关
- **版本兼容**: 向后兼容的版本升级
- **模块化**: 模块化的服务设计
### 4. 维护性好
- **代码规范**: 统一的代码规范和风格
- **文档完整**: 完整的接口文档和注释
- **测试覆盖**: 高覆盖率的单元测试和集成测试
- **监控告警**: 完善的监控和告警机制
## 技术实现细节
### 1. Spring注解
```java
@Service // 标识为服务组件
@Transactional // 事务管理
@RequiredArgsConstructor // Lombok构造器注入
@Slf4j // 日志记录
@Async // 异步处理
@EventListener // 事件监听
@Cacheable // 缓存支持
```
### 2. 事务管理
```java
@Transactional(readOnly = true) // 只读事务
@Transactional(rollbackFor = Exception.class) // 异常回滚
@Transactional(propagation = Propagation.REQUIRES_NEW) // 事务传播
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) // 事务事件
```
### 3. 安全控制
```java
@PreAuthorize("hasRole('ADMIN')") // 方法级权限控制
@PostAuthorize("returnObject.owner == authentication.name") // 返回值权限控制
SecurityContextHolder.getContext().getAuthentication() // 获取当前用户
```
### 4. 异常处理
```java
@ControllerAdvice // 全局异常处理
@ExceptionHandler // 特定异常处理
try-catch-finally // 局部异常处理
throw new BusinessException() // 业务异常抛出
```
## Service模块最佳实践
1. **接口设计**: 保持接口简洁,方法职责单一
2. **事务边界**: 合理设置事务边界,避免长事务
3. **异常处理**: 统一的异常处理策略,提供有意义的错误信息
4. **参数验证**: 在服务入口进行参数验证
5. **日志记录**: 记录关键业务操作和异常信息
6. **性能监控**: 监控服务性能,及时发现瓶颈
7. **单元测试**: 为每个服务方法编写单元测试
8. **文档维护**: 保持接口文档和代码注释的同步更新
## Service模块总结
Service模块为EMS系统提供了完整、高质量的业务服务层
1. **业务完整**: 覆盖环境监管系统的所有核心业务功能
2. **架构清晰**: 分层明确、职责单一、依赖合理
3. **安全可靠**: 完善的安全控制和异常处理机制
4. **性能优化**: 多种性能优化策略和监控机制
5. **扩展性强**: 支持业务功能的灵活扩展和配置
6. **维护性好**: 代码规范、文档完整、测试覆盖率高
7. **技术先进**: 采用Spring生态的最佳实践和现代化技术
通过这套完整的Service层EMS系统建立了稳定、高效的业务处理能力为前端应用提供了可靠的业务支撑确保了系统的业务完整性和技术先进性。
---
# Security模块详细解析
## 概述
Security安全模块位于 `com.dne.ems.security` 包下包含4个核心安全组件构成了EMS系统完整的安全防护体系。该模块基于Spring Security框架实现了JWT无状态认证、细粒度权限控制、CORS跨域支持和安全过滤机制为整个系统提供了企业级的安全保障。
## 核心Security组件
### 1. CustomUserDetails自定义用户详情
**功能概述**
- **类定义**: `CustomUserDetails`
- **实现接口**: `UserDetails`
- **业务价值**: 封装用户认证和授权信息桥接业务用户实体与Spring Security
- **核心特性**: 用户信息封装、权限映射、账户状态管理、角色权限控制
**核心方法**
```java
public class CustomUserDetails implements UserDetails {
private final UserAccount userAccount;
// 业务信息获取
public Long getId();
public String getName();
public Role getRole();
public String getPhone();
public UserStatus getStatus();
public String getRegion();
public List<String> getSkills();
// Spring Security标准接口实现
public Collection<? extends GrantedAuthority> getAuthorities();
public String getPassword();
public String getUsername(); // 使用邮箱作为用户名
public boolean isAccountNonExpired();
public boolean isAccountNonLocked(); // 基于lockoutEndTime实现锁定逻辑
public boolean isCredentialsNonExpired();
public boolean isEnabled(); // 基于enabled和status字段
}
```
**设计特点**
- **权限映射**: 自动添加"ROLE_"前缀确保与Spring Security的hasRole()方法兼容
- **账户锁定**: 基于lockoutEndTime字段实现动态账户锁定机制
- **状态检查**: 综合enabled字段和UserStatus枚举进行账户状态验证
- **信息暴露**: 提供完整的用户业务信息访问接口
### 2. JwtAuthenticationFilterJWT认证过滤器
**功能概述**
- **类定义**: `JwtAuthenticationFilter`
- **继承关系**: `OncePerRequestFilter`
- **业务价值**: 处理每个HTTP请求的JWT认证实现无状态认证机制
- **核心特性**: JWT令牌提取、令牌验证、用户加载、安全上下文设置
**核心方法**
```java
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException;
}
```
**工作流程**
1. **路径检查**: 跳过认证相关路径(/api/auth)
2. **令牌提取**: 从Authorization头提取Bearer令牌
3. **令牌验证**: 验证JWT签名和有效期
4. **用户加载**: 根据令牌中的用户名加载用户详情
5. **上下文设置**: 创建认证对象并设置到SecurityContext
**安全特性**
- **一次性过滤**: 继承OncePerRequestFilter确保每个请求只过滤一次
- **路径跳过**: 智能跳过不需要认证的路径
- **令牌格式**: 严格验证Bearer令牌格式
- **上下文管理**: 正确设置Spring Security上下文
### 3. SecurityConfig安全配置
**功能概述**
- **类定义**: `SecurityConfig`
- **配置注解**: `@Configuration`, `@EnableWebSecurity`, `@EnableMethodSecurity`
- **业务价值**: 定义系统整体安全策略和访问控制规则
- **核心特性**: HTTP安全配置、CORS配置、认证管理、密码编码
**核心配置方法**
```java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception;
@Bean
CorsConfigurationSource corsConfigurationSource();
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config);
@Bean
public PasswordEncoder passwordEncoder();
}
```
**安全规则配置**
```java
// HTTP安全配置
http
.csrf(AbstractHttpConfigurer::disable) // 禁用CSRF(使用JWT)
.cors(cors -> cors.configurationSource(corsConfigurationSource())) // 启用CORS
.authorizeHttpRequests(authorize -> authorize
// 允许OPTIONS预检请求
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 认证相关端点
.requestMatchers("/api/auth/**").permitAll()
// 公共访问端点
.requestMatchers("/api/public/**", "/api/map/**", "/api/pathfinding/**").permitAll()
// 文件访问端点
.requestMatchers("/api/files/**").permitAll()
// Dashboard端点权限控制
.requestMatchers("/api/dashboard/**").hasAnyRole("ADMIN", "DECISION_MAKER")
// 其他请求需要认证
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态会话
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
```
**CORS配置**
```java
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("*"); // 允许所有来源
configuration.addAllowedMethod("*"); // 允许所有HTTP方法
configuration.addAllowedHeader("*"); // 允许所有HTTP头
// 注册到所有路径
source.registerCorsConfiguration("/**", configuration);
return source;
}
```
**安全特性**
- **无状态认证**: 基于JWT的无状态会话管理
- **CSRF防护**: 禁用CSRF(因使用JWT令牌)
- **方法级权限**: 启用@PreAuthorize@Secured@RolesAllowed注解
- **细粒度控制**: 基于URL模式的访问控制
- **密码安全**: 使用BCrypt强密码编码
### 4. UserDetailsServiceImpl用户详情服务实现
**功能概述**
- **类定义**: `UserDetailsServiceImpl`
- **实现接口**: `UserDetailsService`
- **业务价值**: 为Spring Security提供用户加载服务
- **核心特性**: 用户查找、账户锁定检查、异常处理
**核心方法**
```java
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserAccountRepository userAccountRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据邮箱查找用户
UserAccount userAccount = userAccountRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("未找到邮箱为 " + username + " 的用户。"));
// 检查账户锁定状态
if (userAccount.getLockoutEndTime() != null &&
userAccount.getLockoutEndTime().isAfter(LocalDateTime.now())) {
throw new LockedException("该账户已被锁定,请稍后再试。");
}
return new CustomUserDetails(userAccount);
}
}
```
**业务逻辑**
- **用户查找**: 使用邮箱作为用户名进行查找
- **锁定检查**: 检查lockoutEndTime字段判断账户是否被锁定
- **异常处理**: 提供清晰的中文错误信息
- **对象封装**: 返回CustomUserDetails包装的用户信息
## Security模块设计特点
### 1. 无状态认证架构
- **JWT令牌**: 使用JWT实现无状态认证避免服务器端会话存储
- **令牌验证**: 每个请求都进行令牌验证,确保安全性
- **过期管理**: 自动处理令牌过期和刷新
- **跨服务**: 支持微服务架构下的认证共享
### 2. 细粒度权限控制
- **角色映射**: 自动处理Spring Security角色前缀
- **方法级权限**: 支持@PreAuthorize等注解进行方法级控制
- **URL级权限**: 基于URL模式的访问控制
- **动态权限**: 支持运行时权限检查
### 3. 安全防护机制
- **CSRF防护**: 针对JWT场景禁用CSRF
- **CORS支持**: 完整的跨域资源共享配置
- **账户锁定**: 基于时间的账户锁定机制
- **密码安全**: BCrypt强密码编码
### 4. 异常处理
- **用户友好**: 提供中文错误信息
- **安全考虑**: 不泄露敏感信息
- **分类处理**: 区分不同类型的认证异常
- **日志记录**: 记录安全相关事件
## Security架构优势
### 1. 安全性强
- **多层防护**: 从过滤器到方法级的多层安全防护
- **标准实现**: 基于Spring Security标准实现
- **最佳实践**: 采用业界安全最佳实践
- **持续更新**: 跟随Spring Security版本更新
### 2. 性能优化
- **无状态设计**: 避免服务器端会话存储开销
- **一次性过滤**: 每个请求只进行一次安全检查
- **缓存友好**: 支持用户信息缓存
- **轻量级**: 最小化安全检查开销
### 3. 扩展性好
- **插件化**: 支持自定义安全组件
- **配置驱动**: 基于配置的安全策略
- **接口标准**: 遵循Spring Security接口标准
- **模块化**: 各安全组件职责清晰
### 4. 维护性强
- **代码清晰**: 结构清晰,职责明确
- **文档完整**: 详细的注释和文档
- **测试友好**: 易于进行单元测试和集成测试
- **监控支持**: 支持安全事件监控
## 技术实现细节
### 1. Spring Security注解
```java
@EnableWebSecurity // 启用Web安全
@EnableMethodSecurity // 启用方法级安全
@Component // 组件注册
@Service // 服务注册
@RequiredArgsConstructor // 构造器注入
```
### 2. JWT处理
```java
// JWT令牌提取
String jwt = authHeader.substring(7); // 移除"Bearer "前缀
String userEmail = jwtService.extractUserName(jwt);
// JWT令牌验证
if (jwtService.isTokenValid(jwt, userDetails)) {
// 设置认证上下文
}
```
### 3. 权限控制
```java
// 角色权限映射
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + getRole().name());
// URL权限控制
.requestMatchers("/api/dashboard/**").hasAnyRole("ADMIN", "DECISION_MAKER")
// 方法级权限控制
@PreAuthorize("hasRole('ADMIN')")
public void adminOnlyMethod() { }
```
### 4. 异常处理
```java
// 用户不存在异常
throw new UsernameNotFoundException("未找到邮箱为 " + username + " 的用户。");
// 账户锁定异常
throw new LockedException("该账户已被锁定,请稍后再试。");
```
## Security模块最佳实践
1. **令牌管理**: 合理设置JWT过期时间实现令牌刷新机制
2. **权限设计**: 采用最小权限原则,细粒度控制访问权限
3. **异常处理**: 提供用户友好的错误信息,避免泄露敏感信息
4. **日志记录**: 记录关键安全事件,便于审计和监控
5. **配置管理**: 将安全配置外部化,支持不同环境配置
6. **测试覆盖**: 编写完整的安全测试用例
7. **定期更新**: 及时更新安全依赖,修复安全漏洞
8. **监控告警**: 建立安全事件监控和告警机制
## Security模块总结
Security模块为EMS系统提供了完整、可靠的安全防护体系
1. **认证完整**: 基于JWT的无状态认证机制支持多种认证场景
2. **授权精确**: 细粒度的权限控制从URL到方法级的全面覆盖
3. **防护全面**: CSRF、CORS、账户锁定等多重安全防护
4. **性能优秀**: 无状态设计,最小化安全检查开销
5. **扩展性强**: 模块化设计,支持自定义安全组件
6. **维护性好**: 代码清晰,文档完整,易于维护和扩展
7. **标准兼容**: 完全基于Spring Security标准确保兼容性
通过这套完整的Security模块EMS系统建立了企业级的安全防护能力确保了用户数据安全、系统访问控制和业务操作的安全性为整个系统提供了坚实的安全基础。

View File

@@ -0,0 +1,183 @@
# 主管任务管理功能 - 设计文档
## 1. 功能描述
本功能模块是系统管理NEPM端的核心赋予**主管**监控所有待处理反馈,并通过手动或智能两种模式,将勘查任务高效、合理地分配给一线网格员的能力。智能分配是本模块的亮点,旨在优化资源配置,提升响应效率。
## 2. 涉及角色
- **主要使用者**: `业务主管 (SUPERVISOR)`
- **被分配者**: `网格员 (GRID_WORKER)`
## 3. 业务规则
### 3.1 任务池规则
- 所有通过AI审核状态为`PENDING_ASSIGNMENT`的反馈,都会进入任务分配池,展示在**主管**的工作台。
- 任务池中的任务应支持多维度筛选(如`区域``污染类型``严重等级`)和排序。
### 3.2 手动分配规则
- **主管**可以勾选一个或多个任务。
- 然后,**主管**可以从其管辖范围内的网格员列表中,选择一个或多个来执行任务。
- 分配后,系统会为每一个"任务-网格员"配对创建一条`assignment_record`记录。
### 3.3 智能分配规则
- **触发**: **主管**选择一个任务,点击"智能分配"按钮。
- **地图模型**: 分配算法基于简化的二维网格地图。
- **第一步:候选集生成**:
- 系统获取任务所在的网格坐标 (Tx, Ty)。
- 以 (Tx, Ty) 为中心,搜索**半径10个网格单位**内的所有网格。
- 从中筛选出状态为`ACTIVE`且有对应`skills`的网格员,形成候选集。
- **第二步:最优人选计算**:
- 对候选集中的每个网格员 (Gx, Gy),使用**A\*算法**计算其到目标网格的**最短路径距离 (D)**,算法需能识别并绕开`is_obstacle``true`的障碍网格。
- **ETR (预计任务响应时间)** 公式: `ETR = D + (该网格员当前待处理的任务量)`
- ETR值**最小**的网格员即为最优人选。
- **第三步:推荐与确认**:
- 系统在界面上高亮推荐最优人选。
- **主管**可以接受推荐,一键分配;也可以忽略推荐,手动选择其他候选人。
- **算法细节记录**: 每次智能分配成功后应将计算出的ETR、候选人列表等关键信息以JSON格式快照存入`assignment_record`表的`algorithmDetails`字段,便于审计和追溯。
### 3.4 任务创建与审核规则
- **创建任务**: 主管可不依赖公众反馈,直接在系统中创建新任务,指派给特定网格员。创建的任务直接进入`ASSIGNED`状态。
- **审核任务**: 主管负责审核网格员提交的状态为`SUBMITTED`的任务。
- **批准**: 任务状态变为 `COMPLETED`
- **拒绝**: 任务状态退回 `ASSIGNED`,并附上拒绝理由,网格员需重新处理。
### 3.5 ETR算法优化
为了使智能分配更精准ETR预计任务响应时间的计算公式将引入更多权重因子。
- **基础公式**: `ETR = (A*路径距离) + (当前任务数)`
- **优化后公式**:
`Score = (Wd * D) + (Wn * N) - (Ws * S) + (Wp * P)`
- `D`: A*算法计算出的路径距离。
- `N`: 网格员当前待办任务数量。
- `S`: 技能匹配度。如果网格员的`skills`与任务的`pollutionType`匹配则S=1否则S=0。
- `P`: 任务优先级。高优先级任务P=10中=5低=0。用于打破僵局。
- `Wd, Wn, Ws, Wp`: 分别是距离、任务数、技能、优先级的权重系数,可在系统配置中调整。
- **最终选择Score最小的网格员**。
### 3.6 边界情况处理
- **无可用网格员**: 在指定的搜索半径内,若没有符合条件的`ACTIVE`状态的网格员,系统应提示主管"当前区域无可用网格员,请扩大搜索范围或手动从全局列表选择"。
- **智能分配失败**: 若因地图数据不完整等原因导致A*算法无法找到路径,系统应提示"路径计算失败,请手动分配"。
## 4. 功能实现流程
### 4.1 任务分配流程
```mermaid
graph TD
A[登录NEPM端] --> B[进入任务分配页面];
B --> C[查看"待处理"反馈列表];
C --> D{选择一个反馈};
D --> E{选择分配模式};
E -- 手动分配 --> F[手动勾选网格员];
E -- 智能分配 --> G[系统执行A*算法<br>计算ETR并推荐人选];
G --> H{主管是否<br>接受推荐?};
H -- 是 --> I[确认分配给<br>推荐人选];
H -- 否 --> F;
F --> J[确认分配];
I --> J;
J --> K[创建AssignmentRecord];
K --> L[更新Feedback状态<br>为ASSIGNED];
L --> M[通过NEPG端<br>通知网格员];
style G fill:#E3F2FD
style L fill:#C8E6C9
```
### 4.2 任务审核流程
```mermaid
graph TD
A[主管进入"待审核"任务列表] --> B{选择一个任务};
B --> C[查看网格员提交的报告和图片];
C --> D{审核是否通过?};
D -- 是 --> E[更新任务状态为 COMPLETED];
D -- 否 --> F[填写拒绝理由];
F --> G[更新任务状态为 ASSIGNED];
G --> H[通知网格员重新处理];
style E fill:#C8E6C9;
style G fill:#FFCDD2;
```
## 5. API 接口设计
### 5.1 查询待处理反馈列表
- **URL**: `GET /api/management/tasks/pending`
- **权限**: `SUPERVISOR`
- **查询参数**: `region`, `pollutionType`, `severity`, `page`, `size`
- **成功响应** (`200 OK`): 返回分页的反馈列表。
### 5.2 查询可用网格员列表
- **URL**: `GET /api/management/grid-workers/available`
- **权限**: `SUPERVISOR`
- **查询参数**: `region`
- **成功响应** (`200 OK`): 返回指定区域内所有状态为`ACTIVE`的网格员列表,包含其当前位置和任务负载。
### 5.3 手动分配任务
- **URL**: `POST /api/management/tasks/assign/manual`
- **权限**: `SUPERVISOR`
- **请求体**:
```json
{
"feedbackId": 123,
"gridWorkerIds": [10, 15],
"remarks": "请尽快处理"
}
```
- **成功响应** (`200 OK`): `{ "message": "任务分配成功" }`
### 5.4 智能分配任务
- **URL**: `POST /api/management/tasks/assign/intelligent`
- **权限**: `SUPERVISOR`
- **请求体**: `{"feedbackId": 123}`
- **成功响应** (`200 OK`): 返回包含最优人选和候选集信息的JSON供前端渲染。
```json
{
"recommended": { "userId": 10, "name": "王伟", "score": 15.8, ... },
"candidates": [ ... ]
}
```
### 5.5 审核任务
- **URL**: `POST /api/management/tasks/{taskId}/review`
- **权限**: `SUPERVISOR`
- **请求体**:
```json
{
"approved": false,
"comments": "图片不清晰,请重新拍摄现场照片。"
}
```
- **成功响应** (`200 OK`): `{ "message": "审核操作已完成" }`
## 6. 界面设计要求
### 6.1 任务分配主页面
- **布局**: 采用左右分栏布局。
- **左侧**: 待处理反馈列表。以紧凑的列表或表格形式展示,包含`标题`、`区域`、`严重等级`。提供筛选和搜索功能。
- **右侧**: 网格员地图视图。实时展示所选反馈位置,以及管辖区域内所有网格员的分布。**每个网格员的图标应能通过大小或颜色深浅,直观反映其当前的负载(待处理任务数)**。
- **交互**:
- 点击左侧列表中的一个反馈项,右侧地图应立即定位到该反馈所在的网格,并高亮显示。
- 同时,地图上应以不同颜色或图标区分网格员的实时状态(如`空闲`-绿色,`繁忙`-橙色,`休假`-灰色)。
### 6.2 分配操作界面
- **触发**: **主管**在左侧列表中选中一个反馈后,列表项下方出现"手动分配"和"智能分配"两个按钮。
- **手动分配**:
- 点击后,弹出一个包含所有可选网格员列表的对话框。
- 列表支持搜索和多选。
- **主管**勾选后点击确认即可分配。
- **智能分配**:
- 点击后,右侧地图视图进入"推荐模式"。
- 系统计算后,在地图上用醒目的高亮效果(如闪烁的星星或光圈)标记出最优人选。
- 同时,在地图下方或侧边栏显示一个信息卡片,包含推荐人选的姓名、当前任务量、**技能匹配度**、**距离**和最终的**推荐分数**,并提供一个"一键分配"按钮。
- 其他候选人以普通方式显示,**主管**仍可点击查看其分数详情并选择。
### 6.3 任务创建页面
- 提供一个独立的表单页面,允许主管填写`标题`、`描述`、`污染类型`、`严重等级`、`地理位置`等所有任务相关信息。
- 表单下方直接集成一个网格员选择器,方便主管创建后立即指派。
### 6.4 任务审核页面
- 这是一个任务详情页面,顶部清晰展示任务基础信息。
- 页面核心区域展示网格员提交的报告,包括文字说明和图片(图片支持点击放大预览)。
- 页面底部提供"批准"和"拒绝"两个操作按钮。点击"拒绝"时,必须弹出一个对话框,要求主管填写拒绝理由。

View File

@@ -0,0 +1,129 @@
# 主管审核与任务分配功能 - 设计文档
## 1. 功能描述
本功能模块是NEPM后台管理端的核心组成部分专为`主管 (SUPERVISOR)``管理员 (ADMIN)`角色设计。它弥合了AI自动审核与人工任务分配之间的鸿沟确保了只有经过人工确认的有效反馈才能进入网格员的处理流程。
主要职责包括:
- **审核**: 对AI初审通过的反馈 (`PENDING_REVIEW`) 进行人工复核。
- **决策**: 决定该反馈是应该继续流转 (`PENDING_ASSIGNMENT`) 还是直接关闭 (`CLOSED_INVALID`)。
- **分配**: 将审核通过的有效反馈手动指派给特定的`网格员 (GRID_WORKER)`
## 2. 业务流程
```mermaid
graph TD
subgraph "AI自动审核"
A[反馈状态: PENDING_REVIEW]
end
subgraph "主管人工审核 (Supervisor/Admin 操作)"
A --> B{主管查看待审核列表};
B --> C{审核决策};
C -- 审核通过 --> D[调用 Approve API<br>/api/supervisor/reviews/{id}/approve];
C -- 审核拒绝 --> E[调用 Reject API<br>/api/supervisor/reviews/{id}/reject];
end
subgraph "后端服务"
D --> F[反馈状态变为<br>PENDING_ASSIGNMENT];
E --> G[反馈状态变为<br>CLOSED_INVALID];
end
subgraph "主管手动分配"
F --> H{主管在任务分配界面<br>查看待分配列表};
H --> I{选择反馈 + 选择网格员};
I --> J[调用 Assign API<br>/api/task-assignment/assign];
end
subgraph "后端服务"
J --> K[创建 Assignment 记录<br>反馈状态变为 ASSIGNED];
end
style F fill:#C8E6C9,stroke:#333
style G fill:#FFCDD2,stroke:#333
style K fill:#B3E5FC,stroke:#333
```
## 3. API 接口设计
### 3.1 主管审核接口 (SupervisorController)
#### 3.1.1 获取待审核反馈列表
- **URL**: `GET /api/supervisor/reviews`
- **描述**: 获取所有状态为 `PENDING_REVIEW` 的反馈,供主管进行人工审核。
- **权限**: `SUPERVISOR`, `ADMIN`
- **成功响应** (`200 OK`): 返回 `Feedback` 对象数组。
```json
[
{
"id": 101,
"eventId": "uuid-...",
"title": "...",
"status": "PENDING_REVIEW",
...
}
]
```
#### 3.1.2 批准反馈
- **URL**: `POST /api/supervisor/reviews/{feedbackId}/approve`
- **描述**: 主管批准一个反馈,使其进入待分配状态。
- **权限**: `SUPERVISOR`, `ADMIN`
- **路径参数**: `feedbackId` (Long) - 要批准的反馈ID。
- **成功响应** (`200 OK`): 无内容。
- **失败响应**:
- `400 Bad Request`: 如果反馈状态不是 `PENDING_REVIEW`。
- `404 Not Found`: 如果 `feedbackId` 不存在。
#### 3.1.3 拒绝反馈
- **URL**: `POST /api/supervisor/reviews/{feedbackId}/reject`
- **描述**: 主管拒绝一个反馈,将其关闭。
- **权限**: `SUPERVISOR`, `ADMIN`
- **路径参数**: `feedbackId` (Long) - 要拒绝的反馈ID。
- **成功响应** (`200 OK`): 无内容。
- **失败响应**:
- `400 Bad Request`: 如果反馈状态不是 `PENDING_REVIEW`。
- `404 Not Found`: 如果 `feedbackId` 不存在。
### 3.2 任务分配接口 (TaskAssignmentController)
#### 3.2.1 获取待分配反馈列表
- **URL**: `GET /api/task-assignment/unassigned-feedback`
- **描述**: 获取所有状态为 `PENDING_ASSIGNMENT` 的反馈,即已通过主管审核,等待分配。
- **权限**: `SUPERVISOR`, `ADMIN`
- **成功响应** (`200 OK`): 返回 `Feedback` 对象数组。
#### 3.2.2 获取可用网格员列表
- **URL**: `GET /api/task-assignment/available-workers`
- **描述**: 获取所有角色为 `GRID_WORKER` 的用户列表,用于分配任务时的选择器。
- **权限**: `SUPERVISOR`, `ADMIN`
- **成功响应** (`200 OK`): 返回 `UserAccount` 对象数组。
#### 3.2.3 分配任务
- **URL**: `POST /api/task-assignment/assign`
- **描述**: 将一个反馈任务指派给一个网格员。
- **权限**: `SUPERVISOR`, `ADMIN`
- **请求体**:
```json
{
"feedbackId": 101,
"assigneeId": 205
}
```
- **成功响应** (`201 Created`): 返回新创建的 `Assignment` 对象。
- **失败响应**:
- `400 Bad Request`: 如果反馈状态不是 `PENDING_ASSIGNMENT` 或用户不是网格员。
- `404 Not Found`: 如果 `feedbackId` 或 `assigneeId` 不存在。
## 4. 状态机详解
```mermaid
stateDiagram-v2
state "AI审核通过" as PendingReview
state "待分配" as PendingAssignment
state "已关闭" as ClosedInvalid
state "已分配" as Assigned
PendingReview --> PendingAssignment: 主管批准
PendingReview --> ClosedInvalid: 主管拒绝
PendingAssignment --> Assigned: 主管分配任务
```

View File

@@ -0,0 +1,142 @@
# 公众反馈功能 - 设计文档
## 1. 功能描述
本功能是系统面向公众的核心入口NEPS端允许公众监督员提交图文并茂的空气质量问题反馈。提交后系统将**异步调用AI服务**对反馈内容进行审核,最终将有效反馈转化为待处理的任务,送入内部管理流程。
## 2. 涉及角色
- **主要使用者**: `公众监督员 (PUBLIC_SUPERVISOR)` 或任何已认证用户
- **间接关联者**: `主管 (SUPERVISOR)` (作为反馈的接收和处理者)
## 3. 业务规则
### 3.1 提交规则
- 用户必须处于**已认证 (Authenticated)** 状态才能提交反馈。
- 提交时,`标题``污染类型``严重等级``地理位置`为必填项。
- 地理位置通过前端组件获取,同时生成`文字地址``地理坐标(经纬度)`,一并提交给后端。
### 3.2 AI处理规则
- **模型**: 所有AI处理均调用火山引擎的大语言模型API。
- **触发方式**: 用户提交反馈后,系统立即将反馈状态置为 `AI_REVIEWING`,并发布一个**异步事件**来触发AI审核不会阻塞用户的提交操作。
- **审核逻辑**: AI服务会对用户提交的`标题``描述``图片`进行**单阶段审核**通过Function Calling能力**同时判断**以下两点:
- `is_compliant`: 内容是否合规(不含敏感词等)。
- `is_relevant`: 内容是否与大气污染问题相关。
- **输出**:
- 若AI判断为**合规且相关**,则反馈状态更新为 `PENDING_REVIEW`
- 若AI判断为**不合规或不相关**,则反馈状态更新为 `CLOSED_INVALID`
- 若AI服务调用**失败**如网络异常、API错误则反馈状态更新为 `AI_REVIEW_FAILED`,以便后续人工介入或重试。
### 3.3 事件ID规则
- 反馈创建时,系统后端自动使用 `UUID.randomUUID().toString()` 生成一个唯一的字符串作为 `eventId`
## 4. 功能实现流程
```mermaid
graph TD
subgraph "NEPS 端 (用户操作)"
A[填写反馈表单] --> B{点击提交};
end
subgraph "后端服务 (自动化流程)"
B --> C[创建Feedback记录<br>status: AI_REVIEWING];
C --> D{发布异步事件<br>触发AI审核};
D -- AI审核成功<br>合规且相关 --> F[status: PENDING_REVIEW];
D -- AI审核成功<br>不合规或不相关 --> G[status: CLOSED_INVALID];
D -- AI服务调用失败 --> H[status: AI_REVIEW_FAILED];
end
subgraph "NEPM 端 (后续操作)"
F --> I[进入主管审核列表];
end
style C fill:#E3F2FD,stroke:#333
style G fill:#FFCDD2,stroke:#333
style H fill:#FFE0B2,stroke:#333
style F fill:#C8E6C9,stroke:#333
```
## 5. API 接口设计
### 5.1 提交反馈接口
- **URL**: `POST /api/feedback/submit`
- **描述**: 用户提交新的环境问题反馈。
- **权限**: `isAuthenticated()` (任何已认证用户)
- **请求类型**: `multipart/form-data`
- **请求体**:
- `feedback` (JSON字符串DTO: `FeedbackSubmissionRequest`):
```json
{
"title": "长安区工业园附近空气异味严重",
"description": "每天下午都能闻到刺鼻的气味...",
"pollutionType": "OTHER",
"severityLevel": "HIGH",
"location": {
"latitude": 38.04,
"longitude": 114.5
}
}
```
- `files` (File数组): 用户上传的图片文件(可选)。
- **成功响应** (`201 Created`):
- 返回创建的 `Feedback` 实体JSON对象其中包含由后端生成的`id`和`eventId`(UUID格式)。
- **失败响应**:
- `400 Bad Request`: 表单验证失败。
- `401 Unauthorized`: 用户未登录。
- `413 Payload Too Large`: 上传文件过大。
- `500 Internal Server Error`: 服务器内部错误。
## 6. 状态机详解
```mermaid
stateDiagram-v2
[*] --> AI_REVIEWING: 用户提交
AI_REVIEWING --> PENDING_REVIEW: AI审核通过
AI_REVIEWING --> CLOSED_INVALID: AI审核拒绝 (内容问题)
AI_REVIEWING --> AI_REVIEW_FAILED: AI审核失败 (技术问题)
PENDING_REVIEW: 待主管审核
CLOSED_INVALID: 已关闭/无效
AI_REVIEW_FAILED: AI审核失败
state PENDING_REVIEW {
note right of PENDING_REVIEW
此状态的反馈将出现在
主管的管理界面中,
等待人工处理。
end note
}
```
## 7. 错误处理与边界情况
| 场景 | 触发条件 | 系统处理 | 用户提示 |
| :--- | :--- | :--- | :--- |
| **表单验证失败** | 必填项为空或格式不正确 | 后端返回`400`错误,不创建记录 | 在对应表单项下方显示红色错误提示 |
| **文件上传失败** | 文件过大、格式不支持、网络中断 | 后端返回`413`或`500`错误 | 弹出全局错误消息:"图片上传失败,请稍后重试" |
| **AI服务调用异常**| AI接口超时或返回错误码 | ① `AiReviewService`捕获异常<br>② 将反馈状态设置为`AI_REVIEW_FAILED`并保存<br>③ 流程终止,等待人工处理 | 对用户无感知,后台自动处理 |
| **用户重复提交** | 用户快速点击提交按钮 | 前端在点击后禁用按钮并显示加载动画;<br>后端对相同用户的请求在短时间内进行幂等性处理 | "正在提交,请稍候..." |
## 8. 界面与交互细节
### 8.1 反馈提交页面 (NEPS)
- **表单设计**:
- `标题`: 单行文本输入框。
- `描述`: 多行文本域,允许输入较长内容。
- `污染类型`: **单选框 (Radio Group)** 或 **下拉选择框 (Select)**,选项为`PM2.5`, `O3`, `NO2`, `SO2`, `OTHER`。
- `严重等级`: **单选框 (Radio Group)**,选项为``, ``, ``,并附有简单的解释说明。
- `地理位置`: 提供一个地图选点组件,用户点击地图即可自动填充文字地址和网格坐标。也应允许用户手动输入地址。
- `照片上传`: 提供一个图片上传组件,支持多选、预览和删除已上传的图片。**明确提示支持的格式JPG, PNG和大小限制**。
- **交互细节**:
- **加载状态**: 点击"确认提交"后,按钮变为"提交中..."并显示加载动画,同时整个表单区域置为不可编辑状态。
- **成功提示**: 提交成功后弹出一个全局成功的消息提示e.g., "提交成功!感谢您的反馈。"),并自动跳转到反馈历史页面。
- **失败提示**: 如果提交失败,根据错误类型给出明确的全局错误提示,并保持表单内容,让用户可以修改后重新提交。
### 8.2 反馈历史页面 (NEPS)
- 以列表形式展示用户所有提交过的反馈。
- 每个列表项应以卡片形式展示,包含`标题`, `提交时间`, `事件ID`, 和一个醒目的**`状态标签`**。
- **状态标签**:
- `待审核`: 蓝色或灰色
- `待分配`: 橙色
- `已分配`/`处理中`/`已提交`/`已完成`: 绿色
- `已作废`/`已取消`: 红色
- 点击卡片可进入反馈详情页,查看所有提交信息、`事件ID`和处理流程的详细时间线。
- 对于状态为`PENDING_REVIEW`或`PENDING_ASSIGNMENT`的反馈,详情页提供"撤销提交"按钮。点击后需**二次确认弹窗**,防止误操作。

View File

@@ -0,0 +1,170 @@
# 决策者大屏功能 - 设计文档
## 1. 功能描述
本功能模块是专为决策者和高级管理员NEPV端设计的全局态势感知中心。它通过一系列数据可视化图表、关键绩效指标KPI和地图将系统采集和处理的海量数据转化为直观、易于理解的宏观洞察旨在为环保工作的战略规划和决策提供强有力的数据支持。
## 2. 涉及角色
- **主要使用者**: `决策者 (DECISION_MAKER)`
- **次要使用者**: `系统管理员 (ADMIN)` (需要访问以进行日常运营监控)
## 3. 业务规则
### 3.1 权限与数据规则
- **完全只读**: 大屏上的所有元素均为只读,不提供任何数据修改、删除或提交的入口。
- **数据聚合性**: 除非特殊说明,大屏展示的数据均为聚合后的统计结果(如区域平均值、月度总数),不暴露任何单个反馈或用户的详细隐私信息。
- **实时性**: 部分KPI指标如"今日新增反馈")要求准实时更新,后端需提供高效的查询接口。大部分统计图表数据可通过后台定时任务(如每小时或每日)预计算并缓存,以提升加载性能。
- **实时性与缓存**:
- **实时数据**: 关键KPI指标如"今日新增反馈"和实时检测数量图应通过WebSocket或短轮询实现准实时更新。
- **缓存策略**: 大部分统计图表如月度趋势、类型分布的数据应由后端的定时任务如每小时预先计算并存储在Redis等内存数据库中。前端请求API时直接从缓存读取极大提升加载速度。缓存的Key设计应包含日期和区域等维度例如`dashboard:aqi_trend:2024-07`
- **缓存失效**: 定时任务在生成新数据后,会覆盖旧的缓存,实现自动更新。
### 3.2 核心组件数据来源
- **KPI指标卡**: 直接查询数据库,对`feedback``user_account`等表进行快速COUNT或AVG聚合。
- **统计图表 (趋势图、分布图)**: 由后端的聚合服务,基于`aqi_data``feedback`按指定维度时间、类型进行GROUP BY计算后生成。
- **污染源热力图**: 后端聚合`feedback`表中的`grid_x`, `grid_y`坐标和`severityLevel`,生成一个包含坐标和权重的数据集,供前端渲染。数据同样需要被缓存。
- **AI年度报告**:
- 由一个年度批处理任务Cron Job在每年年底或次年年初自动触发。
- 该任务负责从数据库抽取全年关键统计数据,形成一份综合材料。
- 调用`deepseek-v3-250324`大模型API将结构化的统计数据和分析要点作为Prompt生成一份图文并茂的分析报告。
- 生成的报告如PDF或Markdown格式存储在文件服务器上大屏端仅提供一个下载链接。
### 3.3 交互设计规则
- **全局筛选**: 在大屏顶部或侧边提供全局筛选控件,至少包括 **时间范围选择器**(如"近7天", "本月", "本年", 自定义范围)和 **区域选择器**(当下钻到省市级别时)。所有组件都应响应这些筛选条件的变化。
- **图表联动与下钻**:
- **联动**: 点击饼图的某个扇区(如"工业污染"其他图表如AQI趋势图、热力图应能自动筛选并重新渲染仅显示与"工业污染"相关的数据。
- **下钻**: 在地图或区域排行榜上,点击某个省份,可以下钻到该省的市级数据视图。此时,应有明显的面包屑导航提示当前所处的层级(如"全国 > 浙江省")。
## 4. 功能实现流程
### 4.1 数据流架构
```mermaid
graph TD
subgraph "数据源 (DB)"
A[aqi_data]
B[feedback]
C[user_account]
end
subgraph "数据处理层"
D[定时聚合服务 (Cron Job)]
E[实时API服务]
F[<br><br><b>缓存 (Redis)</b><br><br>]
end
subgraph "前端大屏 (NEPV)"
G[KPI 指标卡]
H[AQI 趋势图]
I[污染类型分布图]
J[污染源热力图]
K[年度报告下载]
L[WebSocket 连接]
end
A & B & C --> D
D --> F
B & C --> E
E -- 实时查询 --> G
E -- 建立长连接 --> L
L -- 推送实时数据 --> G
F -- 读取缓存 --> H
F -- 读取缓存 --> I
F -- 读取缓存 --> J
subgraph "AI服务"
M[AI报告生成服务 (Annual Cron)]
N[大模型 API]
O[文件服务器]
end
A & B & C --> M
M --> N
N --> M
M --> O
O --> K
```
### 4.2 AI年度报告生成时序图
```mermaid
sequenceDiagram
participant Cron as "年度定时任务 (Cron Job)"
participant Service as "报告生成服务"
participant DB as "数据库"
participant LLM as "大模型API"
participant Storage as "文件服务器 (S3/MinIO)"
Cron->>Service: 触发年度报告生成任务 (e.g., for year 2024)
Service->>DB: 查询全年关键数据 (AQI, Feedback, etc.)
DB-->>Service: 返回年度统计数据
Service->>Service: 根据数据构建Prompt (包含统计表格和分析要点)
Service->>LLM: 发送请求 (POST /api/generate-report)
LLM-->>Service: 返回生成的报告内容 (Markdown/HTML)
Service->>Service: (可选) 将报告内容渲染为PDF
Service->>Storage: 上传报告文件 (e.g., report-2024.pdf)
Storage-->>Service: 返回文件URL
Service->>DB: 将报告URL存入`annual_reports`表
DB-->>Service: 存储成功
```
## 5. API 接口设计
### 5.1 KPI 指标接口
- **URL**: `GET /api/data-v/kpis`
- **查询参数**: `?timeRange=...&area=...`
- **响应**: `200 OK`, 返回 `[{ "title": "累计反馈总数", "value": 1024, "change": "+5%" }, ...]`
### 5.2 图表数据接口 (通用)
- **URL**: `GET /api/data-v/chart`
- **查询参数**: `?name=aqiTrend&timeRange=...&area=...` (name用于指定图表类型)
- **响应**: `200 OK`, 返回符合ECharts等图表库格式要求的数据结构。
### 5.3 热力图数据接口
- **URL**: `GET /api/data-v/heatmap`
- **查询参数**: `?timeRange=...&area=...`
- **响应**: `200 OK`, 返回 `[{ "lng": 120.15, "lat": 30.28, "count": 95 }, ...]`
### 5.4 实时数据推送
- **WebSocket**: `ws://your-domain/api/data-v/realtime`
- **推送消息格式**: `{ "type": "newFeedback", "data": { ... } }` or `{ "type": "kpiUpdate", "data": { ... } }`
## 6. 界面设计要求
### 6.1 整体布局与风格
- **主题**: 采用深色科技感主题(如深蓝色、暗灰色背景),以突出数据图表的色彩。
- **布局**: 基于`1920x1080`分辨率进行设计采用栅格系统如24列进行灵活布局确保在主流大屏上不变形。
- **组件**: 所有可视化组件Widgets以卡片形式组织每个卡片都有清晰的标题。
- **交互性**: 所有图表在鼠标悬浮时应显示详细的Tooltip提示。支持点击事件用于图表间的联动和下钻。
- **技术选型**: 推荐使用成熟的图表库,如`ECharts``AntV`,来实现高质量的可视化效果。
### 6.2 核心可视化组件详解
- **顶部 - KPI指标行 & 全局筛选器**:
- 在屏幕顶端横向排列4-6个核心指标卡。
- 指标行旁边或下方设置全局筛选器,包括时间范围和区域选择。
- 每个卡片包含一个醒目的图标、一个加粗的巨大数值、和一个清晰的指标名称(如"累计反馈总数"、"当前活跃网格员"、"本月平均AQI")。
- **中部 - 可交互的污染源热力图**:
- **需求**: 在地图上查看可视化污染源热力图。
- **呈现形式**: 占据屏幕最大面积的核心组件。在二维网格地图上,根据各网格内高严重等级反馈的数量和权重,用不同的颜色(如从蓝色到红色)渲染,形成热力效果。地图应支持基本的缩放和拖拽操作。
- **图表组件 - 左侧面板**:
- **空气质量超标趋势图 (折线图)**:
- **需求**: 查看过去12个月的空气质量超标趋势。
- **呈现形式**: X轴为过去12个月份Y轴为超标天数或超标事件次数。用平滑的曲线展示AQI超标趋势并可提供与去年同期的对比线。
- **总AQI及分项污染物超标统计 (柱形图)**:
- **需求**: 查看总AQI及各分项污染物浓度超标的累计统计。
- **呈现形式**: X轴为污染物类型总AQI, PM2.5, O3, NO2, SO2等Y轴为本年度累计超标次数。每个污染物为一根柱子清晰对比。
- **图表组件 - 右侧面板**:
- **AQI级别分布图 (饼图)**:
- **需求**: 查看AQI级别分布。
- **呈现形式**: 标准饼图将AQI数据按标准优、良、轻度污染、中度污染、重度污染、严重污染划分展示各级别占比。
- **各省市网格覆盖率 (饼图)**:
- **需求**: 查看各省市的网格覆盖率。
- **呈现形式**: 饼图展示已覆盖与未覆盖网格的**总体比例**。为获得更佳体验,可提供下钻功能或辅以排行榜/地图着色展示各省市详情。
- **实时空气质量检测数量 (实时折线图)**:
- **需求**: 查看实时的空气质量检测数量统计。
- **呈现形式**: X轴为过去24小时Y轴为该时段内完成的AQI检测数线条实时或准实时更新。
- **AI年度报告下载**:
- 一个简洁的卡片,包含报告年份,和一个醒目的"下载报告"按钮。

145
Design/总体设计.md Normal file
View File

@@ -0,0 +1,145 @@
# 东软环保公众监督系统 - 总体设计文档
## 1. 项目背景与目标
### 1.1 项目背景
随着公众环保意识的增强和环境问题的日益复杂化,传统单一的环境监督模式已难以满足现代社会的需求。为提升环境治理的效率、透明度和公众参与度,我们提出构建一个创新的、多端协同的环保监督平台——"东软环保公众监督系统"。
### 1.2 需求背景
本项目的核心需求源于对现有环保监督流程的优化渴望。当前流程存在信息传递链条长、问题响应慢、数据孤岛效应明显、公众参与感不强等痛点。系统旨在通过整合公众、一线网格员、系统管理者和宏观决策者的力量,打通从问题发现、数据上报、任务分配、现场核实到数据分析和决策支持的全流程闭环。
### 1.3 项目目标
- **提升监督效率**通过AI预审和智能化任务分配缩短问题响应时间。
- **强化公众参与**:提供便捷、透明的反馈渠道,激励公众成为环保监督的"眼睛"。
- **实现数据驱动**:将零散的反馈数据转化为结构化的、可供分析决策的宝贵资产。
- **构建管理闭环**:打造一个覆盖"公众-执行-管理-决策"四位一体的协同工作平台。
## 2. 功能总体描述
系统整体上由四个功能明确、相互独立的客户端构成,共同协作完成环保监督任务。
- **NEPS端 (公众监督员端)**: 系统的公众入口,允许用户注册、提交污染反馈、追踪反馈处理进度,并查看个人统计数据。
- **NEPG端 (AQI检测网格员端)**: 一线执行人员的操作平台用于接收任务、在简化的网格地图上进行路径指引、提交现场勘查的AQI数据报告。
- **NEPM端 (系统管理端)**: 系统的"大脑",供**主管(Supervisor)**审核反馈、通过手动或智能模式分配任务;同时供**系统管理员(Admin)**管理系统用户、角色及权限。
- **NEPV端 (可视化大屏端)**: 决策支持中心以数据可视化的方式向决策者展示区域环境态势、污染热力图、治理成效等宏观指标并提供AI生成的年度报告。
## 3. 技术架构选型
为保证系统的稳定性、可扩展性和开发效率,我们选择以下主流且成熟的技术栈:
### 3.1 开发语言
- **后端**: Java 21 (充分利用其新特性如虚拟线程、Record类等)
- **前端**: TypeScript (为JavaScript提供类型安全提升代码质量)
### 3.2 开发框架
- **后端**: Spring Boot 3.x
- **核心优势**: 自动化配置、强大的社区生态、内嵌Web服务器能快速构建稳定、高效的RESTful API。
- **关键组件**:
- `Spring Web`: 构建Web应用和API。
- `Spring Data JPA`: 简化数据库持久化操作。
- `Spring Security`: 提供强大而灵活的认证和授权功能,保障系统安全。
- `Spring Validation`: 进行数据校验。
- **前端**: Vue 3.x
- **核心优势**: 采用组合式API (Composition API)逻辑组织更清晰基于Vite的构建工具开发体验极佳响应式系统性能优越。
- **关键组件**:
- `Vue Router`: 管理前端路由。
- `Pinia`: 进行应用的状态管理。
- `Axios`: 用于与后端API进行HTTP通信。
- `Element Plus / Ant Design Vue`: 作为基础UI组件库快速构建美观的界面。
### 3.3 数据库
- **数据库**: MySQL 8.x
- **核心优势**: 作为全球最受欢迎的开源关系型数据库MySQL具有性能稳定、社区支持广泛、与Java及Spring Boot生态完美集成的优点。其成熟的事务处理和数据一致性保障能力完全满足本项目对核心业务数据的存储需求。
### 3.4 关键第三方服务
- **邮箱服务**: 调用163邮箱的SMTP API实现注册、忘记密码等场景的动态验证码发送。
- **AI大模型服务**: 调用火山引擎的`deepseek-v3-250324`模型API实现对用户反馈的自动审核与关键信息提取。
## 4. 系统架构设计
### 4.1 总体架构图
系统采用微服务思想指导下的单体架构前后端分离四端统一调用后端API。这种设计在项目初期能保证快速迭代同时为未来向微服务演进预留了空间。
```mermaid
graph TD
subgraph 用户端
A[NEPS 公众监督员端<br>(Vue3/H5)]
B[NEPG 网格员端<br>(Vue3/H5)]
C[NEPM 系统管理端<br>(Vue3/Web)]
D[NEPV 可视化大屏端<br>(Vue3/Web)]
end
subgraph "后端服务 (Spring Boot)"
E(API网关<br>Spring Cloud Gateway)
F[用户认证服务<br>Spring Security]
G[反馈管理模块]
H[任务管理模块]
I[智能分配模块<br>A*算法]
J[数据可视化模块]
K[文件存储模块]
end
subgraph "数据与服务"
L[MySQL 8.x<br>主数据库]
M[火山引擎<br>AI大模型]
N[163邮箱<br>SMTP服务]
end
A -->|HTTPS/REST API| E
B -->|HTTPS/REST API| E
C -->|HTTPS/REST API| E
D -->|HTTPS/REST API| E
E --> F
E --> G
E --> H
E --> I
E --> J
E --> K
F --> L
G --> L
H --> L
I --> L
J --> L
K --> L
G -->|异步调用| M
F -->|SMTP| N
```
### 4.2 核心数据流(问题处理流程)
1. **提交**: 公众监督员通过NEPS端提交环境问题反馈数据进入后端`反馈管理模块`,初始状态为`AI审核中`
2. **审核**: `反馈管理模块`异步调用`火山引擎AI服务`进行内容审核与信息提取。审核通过后,状态更新为`待处理`
3. **分配**: 主管在NEPM端查看`待处理`的反馈。`任务管理模块`调用`智能分配模块`的A*算法,推荐最优网格员。主管确认后,生成任务并分配。
4. **处理**: 网格员在NEPG端接收任务前往现场处理并提交包含AQI数据的报告。
5. **审核与闭环**: 主管在NEPM端审核任务报告。审核通过则任务完成状态更新为`已完成`;不通过则打回给网格员。
6. **可视化**: 所有处理完成的数据,由`数据可视化模块`进行聚合与分析最终在NEPV大屏端展示。
## 5. 非功能性需求设计
### 5.1 性能需求
- **核心API响应时间**: 95%的核心API请求如登录、任务查询应在500ms内响应。
- **AI处理时间**: AI审核与信息提取的异步处理过程应在30秒内完成。
- **并发用户数**: 系统应支持至少500个并发用户在线核心功能稳定运行。
- **大屏刷新率**: NEPV端数据应支持分钟级刷新。
### 5.2 安全性需求
- **认证与授权**: 所有API均需通过Spring Security进行认证和授权检查防止未授权访问。
- **数据传输**: 全站强制使用HTTPS确保数据在传输过程中的加密。
- **数据存储**: 用户密码、API密钥等敏感信息必须加密存储。
- **防SQL注入**: 采用JPA和参数化查询从根本上防止SQL注入攻击。
- **防XSS攻击**: 前端对用户输入进行严格过滤和转义,防止跨站脚本攻击。
### 5.3 可扩展性需求
- **模块化设计**: 后端业务逻辑严格按照模块划分,降低耦合度,便于未来将单个模块拆分为微服务。
- **无状态服务**: 后端核心业务服务设计为无状态,便于水平扩展和负载均衡。
- **消息队列**: 未来规划引入RabbitMQ或Kafka实现服务间的异步解耦提升系统的削峰填谷和弹性能力。
### 5.4 可维护性需求
- **统一编码规范**: 全项目遵循统一的Java和TypeScript编码规范。
- **日志记录**: 对关键业务流程和异常情况进行详细的结构化日志记录,便于问题排查。
- **API文档**: 使用Swagger/OpenAPI自动生成并维护最新的API文档。
- **模块化前端**: 前端代码按四端和功能模块组织,提高代码的可读性和复用性。

267
Design/数据库设计.md Normal file
View File

@@ -0,0 +1,267 @@
# 东软环保公众监督系统 - 数据库设计文档
## 1. 设计概述
本数据库设计旨在支持一个高效、可扩展的环保监督系统。设计遵循第三范式3NF通过主外键关联确保数据的一致性和完整性。所有表均采用`InnoDB`存储引擎,以支持事务和行级锁定。
- **命名规范**: 表名采用小写下划线命名法e.g., `user_account`字段名采用驼峰命名法e.g., `createdAt`)。
- **主键**: 所有表均包含一个`BIGINT`类型的自增主键`id`
- **时间戳**: 关键业务表包含`createdAt``updatedAt`字段,用于追踪记录的创建和最后修改时间。
## 2. 实体关系图 (E-R Diagram)
```mermaid
erDiagram
USER_ACCOUNT }|--o{ FEEDBACK : "submits"
USER_ACCOUNT }|--o{ ASSIGNMENT_RECORD : "assigns (as supervisor)"
USER_ACCOUNT }|--o{ ASSIGNMENT_RECORD : "is assigned (as grid_worker)"
USER_ACCOUNT }|--o{ AQI_DATA : "reports"
USER_ACCOUNT }|--o{ USER_LOGIN_HISTORY : "logs"
FEEDBACK ||--|{ ASSIGNMENT_RECORD : "is subject of"
FEEDBACK ||--o{ AQI_DATA : "is verified by"
FEEDBACK ||--|{ FEEDBACK_IMAGE : "has"
GRID ||--o{ AQI_DATA : "is location for"
USER_ACCOUNT {
BIGINT id PK
VARCHAR name
VARCHAR phone "UK"
VARCHAR email "UK"
VARCHAR password
VARCHAR gender
VARCHAR role
VARCHAR status
VARCHAR region
VARCHAR level
JSON skills
DATETIME createdAt
DATETIME updatedAt
}
FEEDBACK {
BIGINT id PK
VARCHAR eventId "UK"
VARCHAR title
TEXT description
VARCHAR pollutionType
VARCHAR severityLevel
VARCHAR status
VARCHAR textAddress
INT gridX
INT gridY
BIGINT submitterId FK
DATETIME createdAt
DATETIME updatedAt
}
FEEDBACK_IMAGE {
BIGINT id PK
BIGINT feedbackId FK
VARCHAR imageUrl "URL of the stored image"
VARCHAR imageName
DATETIME createdAt
}
ASSIGNMENT_RECORD {
BIGINT id PK
BIGINT feedbackId FK
BIGINT gridWorkerId FK
BIGINT supervisorId FK
VARCHAR assignmentMethod
JSON algorithmDetails
VARCHAR status
DATETIME createdAt
}
AQI_DATA {
BIGINT id PK
BIGINT feedbackId FK "Nullable"
BIGINT gridId FK
BIGINT reporterId FK
DOUBLE aqiValue
DOUBLE pm25
DOUBLE pm10
DOUBLE so2
DOUBLE no2
DOUBLE co
DOUBLE o3
DATETIME recordTime
}
GRID {
BIGINT id PK
INT gridX
INT gridY "UK"
VARCHAR cityName
VARCHAR districtName
BOOLEAN isObstacle
}
USER_LOGIN_HISTORY {
BIGINT id PK
BIGINT userId FK
VARCHAR ipAddress
VARCHAR userAgent
VARCHAR status
DATETIME loginTime
}
```
## 3. 数据表详细设计
### 3.1 用户表 (`user_account`)
存储所有系统用户,包括公众、监督员、网格员、管理员和决策者。
| 字段名 | 数据类型 | 长度/约束 | 是否可空 | 默认值 | 描述 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `id` | `BIGINT` | `PK, AUTO_INCREMENT`| No | | 唯一主键 |
| `name` | `VARCHAR`| 255 | No | | 真实姓名 |
| `phone` | `VARCHAR`| 50 | No | | 手机号 (唯一,用于登录) |
| `email` | `VARCHAR`| 255 | No | | 邮箱 (唯一,用于登录和验证) |
| `password`| `VARCHAR`| 255 | No | | BCrypt加密后的密码 |
| `gender` | `VARCHAR`| 10 | Yes | | 性别 (Enum: `MALE`, `FEMALE`, `UNKNOWN`) |
| `role` | `VARCHAR`| 50 | No | `PUBLIC_SUPERVISOR` | 角色 (Enum: `PUBLIC_SUPERVISOR`, `SUPERVISOR` (业务主管), `GRID_WORKER` (网格员), `ADMIN` (系统管理员), `DECISION_MAKER` (决策者)) |
| `status` | `VARCHAR`| 50 | No | `ACTIVE`| 账户状态 (Enum: `ACTIVE`, `INACTIVE`, `ON_LEAVE`) |
| `region` | `VARCHAR`| 255| Yes | | 所属区域 (格式: "省-市-区") |
| `level` | `VARCHAR`| 50 | Yes | | 行政级别 (Enum: `PROVINCE`, `CITY`) |
| `skills` | `JSON` | | Yes | | 技能标签 (仅对网格员有效, e.g., `["PM2.5", "O3"]`) |
| `createdAt` | `DATETIME` | | No | `CURRENT_TIMESTAMP` | 创建时间 |
| `updatedAt` | `DATETIME` | | No | `CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP` | 更新时间 |
**索引设计**:
- `PRIMARY KEY (id)`
- `UNIQUE KEY uk_phone (phone)`
- `UNIQUE KEY uk_email (email)`
- `KEY idx_role_status (role, status)`
- `KEY idx_region_level (region, level)`
### 3.2 反馈信息表 (`feedback`)
存储由公众监督员提交的污染反馈信息。
| 字段名 | 数据类型 | 长度/约束 | 是否可空 | 默认值 | 描述 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `id` | `BIGINT` | `PK, AUTO_INCREMENT`| No | | 唯一主键 |
| `eventId`| `VARCHAR`| 255 | `UK, NOT NULL` | | 业务事件ID (USR...格式),对用户可见、唯一 |
| `title` | `VARCHAR`| 255 | No | | 反馈标题 |
| `description`| `TEXT` | | Yes | | 详细描述 |
| `pollutionType`|`VARCHAR`| 50 | No | | 污染类型 (Enum: `PM2.5`, `O3`, `NO2`, `SO2`, `OTHER`) |
| `severityLevel`|`VARCHAR`| 50 | No | | 严重等级 (Enum: `LOW`, `MEDIUM`, `HIGH`) |
| `status` | `VARCHAR`| 50 | No | `AI_REVIEWING`| 任务状态 (Enum: `AI_REVIEWING`, `AI_PROCESSING`, `PENDING_ASSIGNMENT`, `ASSIGNED`, `IN_PROGRESS`, `SUBMITTED`, `COMPLETED`, `CANCELLED`, `CLOSED_INVALID`) |
| `textAddress`| `VARCHAR`| 255 | Yes | | 文字地址 (格式: "省份-城市-区") |
| `gridX` | `INT` | | Yes | | 对应的网格X坐标 |
| `gridY` | `INT` | | Yes | | 对应的网格Y坐标 |
| `submitterId`| `BIGINT` | `FK -> user_account.id` | No | | 提交者ID |
| `createdAt` | `DATETIME` | | No | `CURRENT_TIMESTAMP` | 创建时间 |
| `updatedAt` | `DATETIME` | | No | `CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP` | 更新时间 |
**索引设计**:
- `PRIMARY KEY (id)`
- `UNIQUE KEY uk_event_id (eventId)`
- `KEY idx_submitter_id (submitterId)`
- `KEY idx_status_created_at (status, createdAt)`
- `KEY idx_grid_coords (gridX, gridY)`
### 3.3 反馈图片表 (`feedback_image`)
存储与反馈信息关联的图片。
| 字段名 | 数据类型 | 长度/约束 | 是否可空 | 默认值 | 描述 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `id` | `BIGINT` | `PK, AUTO_INCREMENT`| No | | 唯一主键 |
| `feedbackId` | `BIGINT` | `FK -> feedback.id`| No | | 关联的反馈ID |
| `imageUrl` | `VARCHAR`| 512 | No | | 存储的图片URL |
| `imageName` | `VARCHAR`| 255 | Yes | | 图片原始文件名 |
| `createdAt` | `DATETIME`| | No |`CURRENT_TIMESTAMP`| 创建时间 |
**索引设计**:
- `PRIMARY KEY (id)`
- `KEY idx_feedback_id (feedbackId)`
### 3.4 任务分配记录表 (`assignment_record`)
记录每一次任务分配的详细信息。
| 字段名 | 数据类型 | 长度/约束 | 是否可空 | 默认值 | 描述 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `id` | `BIGINT` | `PK, AUTO_INCREMENT`| No | | 唯一主键 |
| `feedbackId`| `BIGINT` | `FK -> feedback.id`| No | | 关联的反馈/任务ID |
| `gridWorkerId`|`BIGINT`|`FK -> user_account.id`| No | | 被分配的网格员ID |
| `supervisorId` | `BIGINT`| `FK -> user_account.id`| No | | 执行分配的主管ID |
| `assignmentMethod`|`VARCHAR`|50| No | | 分配方式 (Enum: `MANUAL`, `INTELLIGENT`) |
| `algorithmDetails`|`JSON`| | Yes | | 智能分配算法的快照 (e.g., ETR, 候选人) |
| `status` | `VARCHAR`| 50 | No | `PENDING`| 分配状态 (Enum: `PENDING`, `ACCEPTED`, `REJECTED`) |
| `createdAt` |`DATETIME`| | No |`CURRENT_TIMESTAMP`| 创建时间 |
**索引设计**:
- `PRIMARY KEY (id)`
- `KEY idx_feedback_id (feedbackId)`
- `KEY idx_grid_worker_id_status (gridWorkerId, status)`
- `KEY idx_supervisor_id (supervisorId)`
### 3.5 空气质量数据表 (`aqi_data`)
存储网格员现场核查后上报的精确AQI数据。
| 字段名 | 数据类型 | 长度/约束 | 是否可空 | 默认值 | 描述 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `id` | `BIGINT` | `PK, AUTO_INCREMENT`| No | | 唯一主键 |
| `feedbackId`| `BIGINT` | `FK -> feedback.id`| Yes | | 关联的反馈ID (可为空,支持主动上报) |
| `gridId` | `BIGINT` | `FK -> grid.id`| No | | 数据采集所在的网格ID |
| `reporterId`| `BIGINT` | `FK -> user_account.id`| No | | 上报人ID网格员|
| `aqiValue` | `DOUBLE` | | Yes | | 综合AQI指数 |
| `pm25` | `DOUBLE` | | Yes | | PM2.5读数 (μg/m³) |
| `pm10` | `DOUBLE` | | Yes | | PM10读数 (μg/m³) |
| `so2` | `DOUBLE` | | Yes | | 二氧化硫读数 (μg/m³) |
| `no2` | `DOUBLE` | | Yes | | 二氧化氮读数 (μg/m³) |
| `co` | `DOUBLE` | | Yes | | 一氧化碳读数 (mg/m³) |
| `o3` | `DOUBLE` | | Yes | | 臭氧读数 (μg/m³) |
| `recordTime`|`DATETIME`| | No |`CURRENT_TIMESTAMP`| 记录时间 |
**索引设计**:
- `PRIMARY KEY (id)`
- `KEY idx_feedback_id (feedbackId)`
- `KEY idx_grid_id_record_time (gridId, recordTime)`
- `KEY idx_reporter_id (reporterId)`
### 3.6 网格地图表 (`grid`)
定义抽象的二维网格地图。
| 字段名 | 数据类型 | 长度/约束 | 是否可空 | 默认值 | 描述 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `id` | `BIGINT` | `PK, AUTO_INCREMENT`| No | | 唯一主键 |
| `gridX` | `INT` | | No | | 网格X坐标 |
| `gridY` | `INT` | | No | | 网格Y坐标 |
| `cityName`| `VARCHAR`| 255 | Yes | | 所属城市 |
| `districtName`|`VARCHAR`|255| Yes | | 所属区域 |
| `isObstacle`|`BOOLEAN`| | No | `false`| 是否为障碍物 |
**索引设计**:
- `PRIMARY KEY (id)`
- `UNIQUE KEY uk_grid_coords (gridX, gridY)`
- `KEY idx_city_district (cityName, districtName)`
### 3.7 用户登录历史表 (`user_login_history`)
记录用户登录活动,用于安全审计。
| 字段名 | 数据类型 | 长度/约束 | 是否可空 | 默认值 | 描述 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `id` | `BIGINT` | `PK, AUTO_INCREMENT`| No | | 唯一主键 |
| `userId` | `BIGINT` | `FK -> user_account.id`| No | | 登录用户ID |
| `ipAddress`| `VARCHAR`| 100 | Yes | | 登录IP地址 |
| `userAgent`| `VARCHAR`| 512 | Yes | | 浏览器或客户端信息 |
| `status` | `VARCHAR`| 50 | No | | 登录状态 (Enum: `SUCCESS`, `FAILED_PASSWORD`, `FAILED_LOCKED`) |
| `loginTime`| `DATETIME`| | No |`CURRENT_TIMESTAMP`| 登录时间 |
**索引设计**:
- `PRIMARY KEY (id)`
- `KEY idx_user_id_login_time (userId, loginTime)`
- `KEY idx_ip_address (ipAddress)`
## 4. 初始化数据 (Seed Data)
系统首次部署时,应向`user_account`表插入以下初始用户,以确保各角色可用。密码均为明文示例,实际存储时需加密。
| name | phone | email | password | role | status | region | level |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| "张建华"| "18800000001" | "zhang.jianguo@example.com"| "Admin@123456" | `ADMIN` | `ACTIVE` | "河北省" | `PROVINCE` |
| "李志强"| "18800000002" | "li.zhiqiang@example.com" | "Admin@123456" | `ADMIN` | `ACTIVE` | "河北省-石家庄市"| `CITY` |
| "王伟" | "18800000003" | "wang.wei@example.com" | "User@123456" | `GRID_WORKER`| `ACTIVE` | "河北省-石家庄市-长安区"| `CITY` |
| "刘丽" | "18800000004" | "li.li@example.com" | "User@123456" | `SUPERVISOR`| `ACTIVE` | "河北省-石家庄市-裕华区"| `CITY` |
| "陈思远"| "18800000005" | "chen.siyuan@example.com" | "User@123456" | `DECISION_MAKER`| `ACTIVE`| "全国" | `PROVINCE` |
| "赵敏" | "18800000006" | "zhao.min@example.com" | "User@123456" | `PUBLIC_SUPERVISOR`|`ACTIVE`| "河北省-石家庄市-桥西区"| `CITY` |

1439
Design/答辩稿.md Normal file
View File

@@ -0,0 +1,1439 @@
# EMS环境监测系统技术答辩稿
## 开场白
各位评委老师好今天我将为大家展示一个基于Spring Boot微服务架构的智能环境监测系统EMS。这个系统不仅仅是一个简单的CRUD应用而是一个融合了现代企业级开发最佳实践的复杂分布式系统。让我们一起探索这个系统背后的技术奥秘。
---
## 第一部分:架构设计的哲学思考
### 为什么选择分层架构?解耦的艺术
**设问**:在面对复杂的业务需求时,我们如何确保代码的可维护性和可扩展性?
答案就在于**分层架构**的设计哲学。我们的系统采用了经典的四层架构模式:
```
Controller Layer (控制层) → Service Layer (业务层) → Repository Layer (数据访问层) → Model Layer (数据模型层)
```
这种设计遵循了**单一职责原则**和**依赖倒置原则**,实现了真正的**高内聚、低耦合**。每一层都有明确的职责边界,层与层之间通过接口进行通信,这样的设计使得我们可以独立地修改某一层的实现,而不会影响到其他层。
**技术亮点**我们使用了Spring的**依赖注入DI**容器来管理对象的生命周期,通过`@Autowired`注解实现了**控制反转IoC**,这是企业级应用开发的核心设计模式。
---
## 第二部分Controller层的RESTful设计艺术
### 为什么我们需要14个专业化的Controller
**设问**传统的单体应用往往将所有功能堆砌在一个Controller中这样做有什么问题
我们的系统包含了14个专业化的Controller每个Controller都专注于特定的业务领域
#### AuthController - 认证控制器的安全哲学
```java
@RestController
@RequestMapping("/api/auth")
@Validated
public class AuthController {
@PostMapping("/login")
public ResponseEntity<ApiResponse<JwtAuthenticationResponse>> authenticateUser(
@Valid @RequestBody LoginRequest loginRequest) {
// JWT令牌生成逻辑
}
}
```
**技术深度解析**
- **@RestController**这是Spring Boot的组合注解等价于`@Controller + @ResponseBody`自动将返回值序列化为JSON
- **@Validated**:启用方法级别的参数验证,配合`@Valid`实现数据校验的**AOP切面编程**
- **ResponseEntity**提供了对HTTP响应的完全控制包括状态码、头部信息和响应体
**设计思考**:为什么不直接返回对象,而要包装在`ApiResponse`中?这体现了**统一响应格式**的设计模式确保前端能够以一致的方式处理所有API响应。
#### DashboardController - 数据可视化的技术挑战
**设问**:如何在不影响系统性能的前提下,为决策者提供实时的数据洞察?
```java
@GetMapping("/stats")
@PreAuthorize("hasRole('DECISION_MAKER')")
public ResponseEntity<ApiResponse<DashboardStatsDTO>> getDashboardStats() {
// 复杂的数据聚合逻辑
}
```
**技术亮点**
- **@PreAuthorize**这是Spring Security的方法级安全注解实现了**基于角色的访问控制RBAC**
- **数据聚合优化**我们在Repository层使用了复杂的JPQL查询和原生SQL将计算下推到数据库层避免了在应用层进行大量数据处理
---
## 第三部分Service层的业务编排艺术
### 为什么业务逻辑需要如此复杂的编排?
**设问**简单的CRUD操作为什么需要这么多Service类这不是过度设计吗
让我们看看`TaskManagementServiceImpl`的复杂性:
```java
@Service
@Transactional
public class TaskManagementServiceImpl implements TaskManagementService {
@Autowired
private TaskRepository taskRepository;
@Autowired
private FeedbackRepository feedbackRepository;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Override
public TaskDTO createTaskFromFeedback(Long feedbackId, TaskFromFeedbackDTO dto) {
// 1. 获取并验证反馈
Feedback feedback = feedbackRepository.findById(feedbackId)
.orElseThrow(() -> new ResourceNotFoundException("Feedback not found"));
// 2. 业务规则验证
if (feedback.getStatus() == FeedbackStatus.PROCESSED) {
throw new BusinessException("Feedback already processed");
}
// 3. 创建任务实体
Task task = Task.builder()
.title(dto.getTitle())
.description(feedback.getDescription())
.location(feedback.getLocation())
.feedback(feedback)
.status(TaskStatus.PENDING)
.build();
// 4. 事务性操作
Task savedTask = taskRepository.save(task);
feedback.setStatus(FeedbackStatus.PROCESSED);
feedbackRepository.save(feedback);
// 5. 发布领域事件
eventPublisher.publishEvent(new TaskCreatedEvent(savedTask));
return modelMapper.map(savedTask, TaskDTO.class);
}
}
```
**技术深度解析**
1. **@Transactional**这是Spring的声明式事务管理确保方法内的所有数据库操作要么全部成功要么全部回滚保证了数据的**ACID特性**
2. **Builder模式**使用Lombok的`@Builder`注解生成建造者模式代码,提高了对象创建的可读性和安全性
3. **事件驱动架构**:通过`ApplicationEventPublisher`发布领域事件,实现了模块间的**松耦合**通信
4. **ModelMapper**:使用对象映射框架避免手动的属性拷贝,减少了样板代码
**设计哲学**:这种复杂性是必要的,因为它体现了**领域驱动设计DDD**的思想每个Service都是一个业务能力的封装确保了业务逻辑的完整性和一致性。
### AuthServiceImpl - 安全认证的技术堡垒
**设问**:在当今网络安全威胁日益严重的环境下,我们如何构建一个既安全又用户友好的认证系统?
```java
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public JwtAuthenticationResponse authenticateUser(LoginRequest loginRequest) {
// 1. Spring Security认证
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
// 2. 设置安全上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 3. 生成JWT令牌
String jwt = tokenProvider.generateToken(authentication);
return new JwtAuthenticationResponse(jwt);
}
}
```
**技术亮点**
1. **AuthenticationManager**Spring Security的核心认证管理器支持多种认证提供者的**策略模式**
2. **JWTJSON Web Token**:无状态的令牌认证机制,支持分布式系统的**水平扩展**
3. **BCrypt密码编码**:使用自适应哈希算法,具有**盐值**和**工作因子**,抵御彩虹表攻击
---
## 第四部分Repository层的数据访问艺术
### 为什么我们需要如此复杂的数据访问层?
**设问**直接使用SQL不是更简单吗为什么要引入这么多抽象层
让我们看看`FeedbackRepository`的设计:
```java
@Repository
public interface FeedbackRepository extends JpaRepository<Feedback, Long>,
JpaSpecificationExecutor<Feedback> {
// 方法名查询 - Spring Data JPA的魔法
List<Feedback> findByStatus(FeedbackStatus status);
Page<Feedback> findBySubmitterId(Long submitterId, Pageable pageable);
// 自定义JPQL查询 - 面向对象的查询语言
@Query("SELECT new com.dne.ems.dto.HeatmapPointDTO(f.gridX, f.gridY, COUNT(f.id)) " +
"FROM Feedback f WHERE f.status = 'CONFIRMED' " +
"AND f.gridX IS NOT NULL AND f.gridY IS NOT NULL " +
"GROUP BY f.gridX, f.gridY")
List<HeatmapPointDTO> getHeatmapData();
// 性能优化 - EntityGraph解决N+1问题
@Override
@EntityGraph(attributePaths = {"user", "attachments"})
Page<Feedback> findAll(Specification<Feedback> spec, Pageable pageable);
}
```
**技术深度解析**
1. **Spring Data JPA**这是Spring生态系统中的数据访问抽象层它通过**代理模式**自动生成Repository实现类
2. **方法名查询**:基于方法名的**约定优于配置**原则Spring会自动解析方法名并生成相应的查询
3. **JPQLJava Persistence Query Language**:面向对象的查询语言,具有数据库无关性
4. **EntityGraph**JPA 2.1引入的性能优化特性,通过**预加载**策略解决N+1查询问题
5. **Specification模式**:实现了**动态查询**的构建,支持复杂的条件组合
### AqiDataRepository - 大数据处理的技术挑战
**设问**:面对海量的环境监测数据,我们如何确保查询性能?
```java
@Repository
public interface AqiDataRepository extends JpaRepository<AqiData, Long> {
// 复杂的统计查询
@Query("SELECT new com.dne.ems.dto.AqiDistributionDTO(" +
"CASE WHEN a.aqiValue <= 50 THEN 'Good' " +
"WHEN a.aqiValue <= 100 THEN 'Moderate' " +
"WHEN a.aqiValue <= 150 THEN 'Unhealthy for Sensitive Groups' " +
"WHEN a.aqiValue <= 200 THEN 'Unhealthy' " +
"WHEN a.aqiValue <= 300 THEN 'Very Unhealthy' " +
"ELSE 'Hazardous' END, COUNT(a.id)) " +
"FROM AqiData a GROUP BY 1")
List<AqiDistributionDTO> getAqiDistribution();
// 原生SQL查询 - 性能优化
@Query(value = "SELECT DATE_FORMAT(record_time, '%Y-%m') as yearMonth, " +
"COUNT(id) as count FROM aqi_data " +
"WHERE aqi_value > 100 AND record_time >= :startDate " +
"GROUP BY DATE_FORMAT(record_time, '%Y-%m') ORDER BY 1",
nativeQuery = true)
List<Object[]> getMonthlyExceedanceTrendRaw(@Param("startDate") LocalDateTime startDate);
// 默认方法 - 数据转换
default List<TrendDataPointDTO> getMonthlyExceedanceTrend(LocalDateTime startDate) {
List<Object[]> rawResults = getMonthlyExceedanceTrendRaw(startDate);
return rawResults.stream()
.map(row -> new TrendDataPointDTO((String) row[0], ((Number) row[1]).longValue()))
.toList();
}
}
```
**技术亮点**
1. **原生SQL查询**在需要极致性能时我们使用原生SQL直接操作数据库
2. **默认方法**Java 8的接口默认方法特性允许我们在接口中提供数据转换逻辑
3. **Stream API**:函数式编程范式,提供了优雅的数据处理方式
4. **DTO投影**直接将查询结果映射到DTO对象避免了不必要的数据传输
---
## 第五部分Security模块的安全防护体系
### 为什么安全性需要如此复杂的设计?
**设问**:在微服务架构中,我们如何构建一个既安全又高效的认证授权体系?
我们的安全模块包含四个核心组件:
#### JwtAuthenticationFilter - 无状态认证的守护者
```java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtService jwtService;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 1. 提取JWT令牌
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String jwt = authHeader.substring(7);
String username = jwtService.extractUsername(jwt);
// 2. 验证令牌并设置安全上下文
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
```
**技术深度解析**
1. **OncePerRequestFilter**:确保过滤器在每个请求中只执行一次,避免重复处理
2. **责任链模式**FilterChain体现了责任链设计模式每个过滤器处理特定的关注点
3. **ThreadLocal安全上下文**SecurityContextHolder使用ThreadLocal存储认证信息确保线程安全
4. **无状态设计**JWT令牌包含了所有必要的用户信息支持水平扩展
#### SecurityConfig - 安全策略的总指挥
```java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 禁用CSRF无状态应用
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 无状态会话
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**", "/api/public/**").permitAll()
.requestMatchers("/api/dashboard/**").hasRole("DECISION_MAKER")
.requestMatchers("/api/supervisor/**").hasAnyRole("SUPERVISOR", "ADMIN")
.requestMatchers("/api/worker/**").hasRole("GRID_WORKER")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // 工作因子12平衡安全性和性能
}
}
```
**技术亮点**
1. **@EnableMethodSecurity**:启用方法级安全控制,支持`@PreAuthorize`等注解
2. **流式API配置**Spring Security 5.7+的新配置方式,更加直观和类型安全
3. **细粒度权限控制**基于URL模式和角色的访问控制
4. **BCrypt算法**:自适应哈希算法,工作因子可调,抵御暴力破解
---
## 第六部分Model层的领域建模艺术
### 为什么需要如此复杂的实体关系?
**设问**:简单的数据表不就够了吗?为什么要设计这么复杂的实体关系?
让我们看看`UserAccount`实体的设计:
```java
@Entity
@Table(name = "user_accounts")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserAccount {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
@Email
private String email;
@Column(nullable = false)
@Size(min = 8)
private String password;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;
@Enumerated(EnumType.STRING)
@Builder.Default
private UserStatus status = UserStatus.ACTIVE;
// 网格坐标(用于网格员)
private Integer gridX;
private Integer gridY;
// 审计字段
@CreationTimestamp
@Column(updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
// 关系映射
@OneToMany(mappedBy = "submitter", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Feedback> submittedFeedbacks = new ArrayList<>();
@OneToMany(mappedBy = "assignee", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Task> assignedTasks = new ArrayList<>();
}
```
**技术深度解析**
1. **JPA注解体系**
- `@Entity`标识为JPA实体
- `@Table`:指定数据库表名
- `@Id``@GeneratedValue`:主键策略
- `@Column`:列约束和属性
2. **Lombok注解魔法**
- `@Data`自动生成getter/setter/toString/equals/hashCode
- `@Builder`:建造者模式,提高对象创建的可读性
- `@NoArgsConstructor``@AllArgsConstructor`:构造器生成
3. **枚举映射**`@Enumerated(EnumType.STRING)`确保数据库存储的是枚举的字符串值,提高可读性
4. **审计功能**`@CreationTimestamp``@UpdateTimestamp`自动管理时间戳
5. **关系映射**`@OneToMany`建立了实体间的关联关系,支持对象导航
### Feedback实体 - 复杂业务的数据载体
```java
@Entity
@Table(name = "feedbacks")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Feedback {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String eventId; // 业务唯一标识
@Column(columnDefinition = "TEXT")
private String description;
@Enumerated(EnumType.STRING)
@Builder.Default
private FeedbackStatus status = FeedbackStatus.PENDING;
@Enumerated(EnumType.STRING)
private PollutionType pollutionType;
// 地理信息
private String location;
private Integer gridX;
private Integer gridY;
// 关联关系
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "submitter_id")
private UserAccount submitter;
@OneToOne(mappedBy = "feedback", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Task relatedTask;
@OneToMany(mappedBy = "feedback", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Attachment> attachments = new ArrayList<>();
// 自定义转换器
@Convert(converter = StringListConverter.class)
private List<String> tags = new ArrayList<>();
}
```
**设计亮点**
1. **业务标识符**`eventId`作为业务唯一标识,与技术主键`id`分离
2. **状态机模式**`FeedbackStatus`枚举定义了反馈的生命周期状态
3. **地理信息建模**:支持文本地址和网格坐标两种定位方式
4. **自定义转换器**`StringListConverter`将List<String>转换为数据库的JSON字段
---
## 第七部分:高级特性与算法实现
### A*寻路算法 - 智能路径规划的核心
**设问**:在复杂的城市环境中,我们如何为网格员规划最优的任务执行路径?
```java
@Service
public class AStarService {
public List<Point> findPath(Point start, Point goal, int[][] grid) {
PriorityQueue<Node> openSet = new PriorityQueue<>(Comparator.comparingDouble(Node::getF));
Set<Point> closedSet = new HashSet<>();
Map<Point, Node> allNodes = new HashMap<>();
Node startNode = new Node(start, 0, heuristic(start, goal), null);
openSet.add(startNode);
allNodes.put(start, startNode);
while (!openSet.isEmpty()) {
Node current = openSet.poll();
if (current.getPoint().equals(goal)) {
return reconstructPath(current);
}
closedSet.add(current.getPoint());
for (Point neighbor : getNeighbors(current.getPoint(), grid)) {
if (closedSet.contains(neighbor) || isObstacle(neighbor, grid)) {
continue;
}
double tentativeG = current.getG() + distance(current.getPoint(), neighbor);
Node neighborNode = allNodes.get(neighbor);
if (neighborNode == null) {
neighborNode = new Node(neighbor, tentativeG, heuristic(neighbor, goal), current);
allNodes.put(neighbor, neighborNode);
openSet.add(neighborNode);
} else if (tentativeG < neighborNode.getG()) {
neighborNode.setG(tentativeG);
neighborNode.setF(tentativeG + neighborNode.getH());
neighborNode.setParent(current);
}
}
}
return Collections.emptyList(); // 无路径
}
private double heuristic(Point a, Point b) {
// 曼哈顿距离启发式函数
return Math.abs(a.getX() - b.getX()) + Math.abs(a.getY() - b.getY());
}
}
```
**算法亮点**
1. **优先队列**:使用`PriorityQueue`实现开放集合确保总是选择f值最小的节点
2. **启发式函数**:曼哈顿距离作为启发式函数,保证算法的最优性
3. **路径重构**:通过父节点指针重构最优路径
4. **空间复杂度优化**使用HashMap存储所有节点避免重复创建
### 智能任务推荐算法
**设问**:面对众多的网格员,我们如何智能地推荐最合适的任务执行者?
```java
@Service
public class TaskRecommendationService {
public List<WorkerRecommendationDTO> recommendWorkersForTask(Long taskId) {
Task task = taskRepository.findById(taskId)
.orElseThrow(() -> new ResourceNotFoundException("Task not found"));
List<UserAccount> availableWorkers = userRepository
.findByRoleAndStatus(Role.GRID_WORKER, UserStatus.ACTIVE);
return availableWorkers.stream()
.map(worker -> calculateWorkerScore(worker, task))
.sorted(Comparator.comparingDouble(WorkerRecommendationDTO::getScore).reversed())
.collect(Collectors.toList());
}
private WorkerRecommendationDTO calculateWorkerScore(UserAccount worker, Task task) {
double distanceScore = calculateDistanceScore(worker, task); // 40%权重
double workloadScore = calculateWorkloadScore(worker); // 30%权重
double skillScore = calculateSkillScore(worker, task); // 20%权重
double performanceScore = calculatePerformanceScore(worker); // 10%权重
double totalScore = distanceScore * 0.4 + workloadScore * 0.3 +
skillScore * 0.2 + performanceScore * 0.1;
return WorkerRecommendationDTO.builder()
.workerId(worker.getId())
.workerName(worker.getName())
.score(totalScore)
.distanceScore(distanceScore)
.workloadScore(workloadScore)
.skillScore(skillScore)
.performanceScore(performanceScore)
.build();
}
private double calculateDistanceScore(UserAccount worker, Task task) {
if (worker.getGridX() == null || worker.getGridY() == null) {
return 0.0;
}
double distance = Math.sqrt(
Math.pow(worker.getGridX() - task.getGridX(), 2) +
Math.pow(worker.getGridY() - task.getGridY(), 2)
);
double maxDistance = 50.0; // 最大考虑距离
return Math.max(0, (maxDistance - distance) / maxDistance);
}
}
```
**算法特点**
1. **多维度评分**:综合考虑距离、工作负载、技能匹配和历史表现
2. **加权评分模型**:不同维度采用不同权重,可根据业务需求调整
3. **流式处理**使用Stream API进行函数式编程代码简洁高效
4. **可扩展性**:评分算法可以轻松添加新的评分维度
---
## 第八部分:事件驱动架构的异步魅力
### 为什么选择事件驱动架构?
**设问**:传统的同步调用方式有什么问题?为什么要引入事件驱动的复杂性?
让我们看看事件驱动架构的实现:
```java
// 事件定义
@Getter
@AllArgsConstructor
public class TaskCreatedEvent extends ApplicationEvent {
private final Task task;
public TaskCreatedEvent(Object source, Task task) {
super(source);
this.task = task;
}
}
// 事件发布
@Service
public class TaskManagementServiceImpl {
@Autowired
private ApplicationEventPublisher eventPublisher;
public TaskDTO createTask(TaskCreateDTO dto) {
Task task = // ... 创建任务逻辑
Task savedTask = taskRepository.save(task);
// 发布事件 - 异步处理
eventPublisher.publishEvent(new TaskCreatedEvent(this, savedTask));
return modelMapper.map(savedTask, TaskDTO.class);
}
}
// 事件监听
@Component
public class TaskEventListener {
@Autowired
private NotificationService notificationService;
@Autowired
private AuditService auditService;
@EventListener
@Async
public void handleTaskCreated(TaskCreatedEvent event) {
Task task = event.getTask();
// 异步发送通知
notificationService.sendTaskAssignmentNotification(task);
// 异步记录审计日志
auditService.logTaskCreation(task);
// 异步更新统计数据
statisticsService.updateTaskStatistics(task);
}
}
```
**技术优势**
1. **解耦合**:事件发布者和监听者之间没有直接依赖关系
2. **异步处理**`@Async`注解实现异步执行,提高系统响应性
3. **可扩展性**:可以轻松添加新的事件监听器,不影响现有代码
4. **容错性**:某个监听器失败不会影响其他监听器的执行
---
## 第九部分:性能优化的技术手段
### 数据库查询优化策略
**设问**:面对大量的数据查询需求,我们如何确保系统的高性能?
#### 1. EntityGraph解决N+1查询问题
```java
@Repository
public interface FeedbackRepository extends JpaRepository<Feedback, Long> {
@EntityGraph(attributePaths = {"submitter", "attachments", "relatedTask"})
@Query("SELECT f FROM Feedback f WHERE f.status = :status")
List<Feedback> findByStatusWithDetails(@Param("status") FeedbackStatus status);
}
```
**技术原理**EntityGraph告诉JPA在执行查询时一次性加载相关联的实体避免了懒加载导致的N+1查询问题。
#### 2. 分页查询优化
```java
@Service
public class FeedbackService {
public Page<FeedbackDTO> getFeedbacks(FeedbackSearchCriteria criteria, Pageable pageable) {
Specification<Feedback> spec = FeedbackSpecification.buildSpecification(criteria);
Page<Feedback> feedbacks = feedbackRepository.findAll(spec, pageable);
return feedbacks.map(feedback -> modelMapper.map(feedback, FeedbackDTO.class));
}
}
```
**优化策略**
- 使用Specification动态构建查询条件
- 利用数据库索引优化排序和过滤
- 在DTO转换时避免加载不必要的关联数据
#### 3. 缓存策略
```java
@Service
public class UserService {
@Cacheable(value = "users", key = "#email")
public UserAccount findByEmail(String email) {
return userRepository.findByEmail(email)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
@CacheEvict(value = "users", key = "#user.email")
public UserAccount updateUser(UserAccount user) {
return userRepository.save(user);
}
}
```
**缓存优势**
- 减少数据库访问次数
- 提高频繁查询的响应速度
- 支持分布式缓存扩展
---
## 第十部分:系统的技术创新点
### 1. 智能AI预审核系统
**设问**:如何将人工智能技术融入到传统的政务系统中?
```java
@Service
public class AiReviewService {
@Autowired
private RestTemplate restTemplate;
public AiReviewResult reviewFeedback(Feedback feedback) {
// 构建AI分析请求
AiAnalysisRequest request = AiAnalysisRequest.builder()
.text(feedback.getDescription())
.imageUrls(feedback.getAttachments().stream()
.map(Attachment::getFileUrl)
.collect(Collectors.toList()))
.location(feedback.getLocation())
.build();
// 调用AI服务
ResponseEntity<AiAnalysisResponse> response = restTemplate.postForEntity(
aiServiceUrl + "/analyze", request, AiAnalysisResponse.class);
AiAnalysisResponse analysisResult = response.getBody();
// 构建审核结果
return AiReviewResult.builder()
.isValid(analysisResult.getConfidence() > 0.8)
.confidence(analysisResult.getConfidence())
.category(analysisResult.getCategory())
.urgencyLevel(analysisResult.getUrgencyLevel())
.suggestedActions(analysisResult.getSuggestedActions())
.build();
}
}
```
**创新点**
- **多模态分析**:同时分析文本和图像内容
- **智能分类**:自动识别问题类型和紧急程度
- **置信度评估**提供AI判断的可信度指标
### 2. 实时数据大屏技术
**设问**:如何为决策者提供实时、直观的数据洞察?
```java
@RestController
@RequestMapping("/api/dashboard")
public class DashboardController {
@GetMapping("/realtime-stats")
@PreAuthorize("hasRole('DECISION_MAKER')")
public SseEmitter getRealtimeStats() {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
// 异步推送实时数据
CompletableFuture.runAsync(() -> {
try {
while (true) {
DashboardStatsDTO stats = dashboardService.getCurrentStats();
emitter.send(SseEmitter.event()
.name("stats-update")
.data(stats));
Thread.sleep(5000); // 每5秒推送一次
}
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
}
```
**技术特点**
- **Server-Sent Events (SSE)**:实现服务器主动推送数据
- **异步处理**使用CompletableFuture避免阻塞主线程
- **实时性**:数据更新延迟控制在秒级
### 3. 地理信息系统集成
```java
@Service
public class GeoService {
public List<HeatmapPointDTO> generateHeatmapData(String dataType, LocalDateTime startTime) {
switch (dataType) {
case "feedback":
return feedbackRepository.getHeatmapData();
case "aqi":
return aqiDataRepository.getAqiHeatmapData();
case "task":
return taskRepository.getTaskHeatmapData(startTime);
default:
throw new IllegalArgumentException("Unsupported data type: " + dataType);
}
}
public GridCoverageDTO calculateGridCoverage() {
long totalGrids = gridRepository.count();
long coveredGrids = gridRepository.countByIsObstacleFalse();
return GridCoverageDTO.builder()
.totalGrids(totalGrids)
.coveredGrids(coveredGrids)
.coverageRate((double) coveredGrids / totalGrids * 100)
.build();
}
}
```
**GIS特性**
- **热力图生成**:基于地理坐标的数据可视化
- **网格覆盖分析**:计算监测网络的覆盖率
- **空间查询**:支持基于地理位置的数据查询
---
---
## 第十一部分:系统的技术特色
### 设问:我们的系统有哪些值得关注的技术特点?
除了基础功能实现我们的EMS系统还融入了一些实用的技术特色让系统更加稳定和高效。
#### 1. 多任务处理能力
**应用场景**:当系统需要同时处理多个用户的请求时,比如多个网格员同时上报数据。
**解决方案**:我们使用了异步处理技术,让系统能够同时处理多个任务,提高响应速度。
```java
// 异步任务配置
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置合适的线程数量
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(100);
// 设置线程名称,方便调试
executor.setThreadNamePrefix("EMS-Task-");
executor.initialize();
return executor;
}
}
// 异步任务示例
@Service
public class NotificationService {
@Async
public void sendNotification(String message) {
// 发送通知的逻辑
System.out.println("发送通知: " + message);
}
}
```
**技术优势**
- **提高响应速度**:用户操作不会被耗时任务阻塞
- **合理利用资源**:根据服务器性能配置线程数量
- **便于问题排查**:通过线程名称快速定位问题
#### 2. 任务分配的安全机制
**应用场景**:当多个管理员同时分配同一个任务时,需要确保任务不会被重复分配。
**解决方案**使用Redis实现任务锁定机制确保同一时间只有一个操作能够分配特定任务。
```java
@Component
public class TaskLockService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 尝试锁定任务
*/
public boolean lockTask(String taskId) {
String lockKey = "task:lock:" + taskId;
String lockValue = "locked";
// 设置锁30秒后自动过期
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, Duration.ofSeconds(30));
return Boolean.TRUE.equals(success);
}
/**
* 释放任务锁
*/
public void unlockTask(String taskId) {
String lockKey = "task:lock:" + taskId;
redisTemplate.delete(lockKey);
}
}
// 任务分配服务
@Service
public class TaskAssignmentService {
@Autowired
private TaskLockService lockService;
public boolean assignTask(Long taskId, Long workerId) {
String taskIdStr = taskId.toString();
// 尝试锁定任务
if (!lockService.lockTask(taskIdStr)) {
throw new BusinessException("任务正在被处理,请稍后重试");
}
try {
// 检查任务状态
Task task = taskRepository.findById(taskId)
.orElseThrow(() -> new RuntimeException("任务不存在"));
if (task.getStatus() != TaskStatus.PENDING) {
throw new BusinessException("任务已被分配");
}
// 分配任务
task.setAssigneeId(workerId);
task.setStatus(TaskStatus.ASSIGNED);
task.setAssignedAt(LocalDateTime.now());
taskRepository.save(task);
return true;
} finally {
// 释放锁
lockService.unlockTask(taskIdStr);
}
}
}
```
**技术优势**
- **防止重复分配**:确保任务分配的唯一性
- **自动释放机制**:避免系统卡死
- **简单可靠**:使用成熟稳定的技术方案
#### 3. 智能任务推荐功能
**应用场景**:当有新任务需要分配时,系统能够自动推荐最合适的网格员。
**解决方案**:根据不同的任务类型,使用不同的推荐规则来选择最合适的人员。
```java
// 任务推荐服务
@Service
public class TaskRecommendationService {
/**
* 推荐合适的网格员
*/
public List<WorkerRecommendation> recommendWorkers(Task task) {
List<UserAccount> availableWorkers = getAvailableWorkers();
// 根据任务类型选择推荐方式
if (task.isUrgent()) {
return recommendByDistance(task, availableWorkers);
} else {
return recommendByExperience(task, availableWorkers);
}
}
/**
* 基于距离推荐(紧急任务)
*/
private List<WorkerRecommendation> recommendByDistance(Task task, List<UserAccount> workers) {
return workers.stream()
.map(worker -> {
double distance = calculateDistance(worker.getLocation(), task.getLocation());
int score = (int) (100 - distance * 10); // 距离越近分数越高
return new WorkerRecommendation(
worker.getId(),
worker.getName(),
score,
"距离任务地点 " + String.format("%.1f", distance) + " 公里"
);
})
.sorted((a, b) -> Integer.compare(b.getScore(), a.getScore()))
.limit(5)
.collect(Collectors.toList());
}
/**
* 基于经验推荐(普通任务)
*/
private List<WorkerRecommendation> recommendByExperience(Task task, List<UserAccount> workers) {
return workers.stream()
.map(worker -> {
int completedTasks = getCompletedTaskCount(worker.getId(), task.getType());
int score = Math.min(100, completedTasks * 10 + 50); // 经验越多分数越高
return new WorkerRecommendation(
worker.getId(),
worker.getName(),
score,
"已完成类似任务 " + completedTasks + ""
);
})
.sorted((a, b) -> Integer.compare(b.getScore(), a.getScore()))
.limit(5)
.collect(Collectors.toList());
}
/**
* 计算两点间距离(简化版)
*/
private double calculateDistance(String location1, String location2) {
// 这里可以接入地图API计算实际距离
// 现在返回模拟距离
return Math.random() * 10; // 0-10公里
}
/**
* 获取已完成任务数量
*/
private int getCompletedTaskCount(Long workerId, String taskType) {
return taskRepository.countCompletedTasksByWorkerAndType(workerId, taskType);
}
}
// 推荐结果类
public class WorkerRecommendation {
private Long workerId;
private String workerName;
private int score;
private String reason;
// 构造函数和getter/setter
public WorkerRecommendation(Long workerId, String workerName, int score, String reason) {
this.workerId = workerId;
this.workerName = workerName;
this.score = score;
this.reason = reason;
}
// getter方法...
}
```
**技术优势**
- **智能推荐**:根据任务特点自动推荐合适人员
- **多种策略**:紧急任务看距离,普通任务看经验
- **简单易懂**:推荐逻辑清晰,便于维护和扩展
#### 4. 操作记录和审计功能
**应用场景**:政务系统需要记录所有重要操作,便于后续查询和审计。
**解决方案**:建立完整的操作日志系统,记录用户的每一个重要操作。
```java
// 操作日志实体
@Entity
@Table(name = "operation_log")
public class OperationLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String userId; // 操作用户
private String userName; // 用户姓名
private String operation; // 操作类型
private String description; // 操作描述
private String targetId; // 操作对象ID
private String targetType; // 操作对象类型
private String ipAddress; // IP地址
private LocalDateTime createTime; // 操作时间
// 构造函数和getter/setter...
}
// 操作日志服务
@Service
public class OperationLogService {
@Autowired
private OperationLogRepository logRepository;
/**
* 记录操作日志
*/
public void recordOperation(String operation, String description,
String targetId, String targetType) {
// 获取当前用户信息
UserAccount currentUser = getCurrentUser();
OperationLog log = new OperationLog();
log.setUserId(currentUser.getId().toString());
log.setUserName(currentUser.getName());
log.setOperation(operation);
log.setDescription(description);
log.setTargetId(targetId);
log.setTargetType(targetType);
log.setIpAddress(getClientIpAddress());
log.setCreateTime(LocalDateTime.now());
logRepository.save(log);
}
/**
* 查询操作日志
*/
public List<OperationLog> queryLogs(String userId, String operation,
LocalDateTime startTime, LocalDateTime endTime) {
return logRepository.findByConditions(userId, operation, startTime, endTime);
}
/**
* 获取客户端IP地址
*/
private String getClientIpAddress() {
// 从请求中获取IP地址
HttpServletRequest request = getCurrentRequest();
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty()) {
ip = request.getRemoteAddr();
}
return ip;
}
}
// 使用示例:在任务分配时记录日志
@Service
public class TaskService {
@Autowired
private OperationLogService logService;
public void assignTask(Long taskId, Long workerId) {
// 执行任务分配逻辑
// ...
// 记录操作日志
logService.recordOperation(
"TASK_ASSIGN",
"将任务分配给网格员",
taskId.toString(),
"TASK"
);
}
}
```
**技术优势**
- **完整记录**:记录所有重要操作,便于追溯
- **详细信息**包含操作人、时间、IP等关键信息
- **便于查询**:支持按条件查询历史操作
- **安全可靠**:确保操作的可追溯性和透明度
---
## 第十二部分:系统总结与展望
### 设问我们的EMS系统实现了什么目标
通过以上的技术实现和功能展示我们的环境监测系统EMS成功地解决了环境管理中的核心问题
#### 1. 主要成果
**功能完整性**
-**数据采集与监测**:实时收集环境数据,支持多种数据源
-**任务管理**:完整的任务分配、跟踪、完成流程
-**用户权限管理**:多角色权限控制,确保系统安全
-**数据可视化**:直观的图表展示,便于决策分析
-**移动端支持**网格员可通过手机APP进行现场操作
**技术特色**
- 🚀 **高性能**:异步处理技术提升系统响应速度
- 🔒 **高安全性**:完善的权限控制和操作审计
- 🎯 **智能推荐**:自动推荐最合适的处理人员
- 📱 **用户友好**:简洁直观的界面设计
#### 2. 解决的实际问题
**环境管理痛点**
- **数据分散** → **统一平台管理**
- **任务混乱** → **清晰的工作流程**
- **响应缓慢** → **快速任务分配**
- **监管困难** → **全程操作记录**
**技术实现亮点**
- **前后端分离**Vue3 + Spring Boot技术栈现代化
- **数据库设计**:合理的表结构,支持复杂查询
- **接口规范**RESTful API设计便于扩展
- **代码质量**:清晰的分层架构,易于维护
#### 3. 未来发展方向
**功能扩展**
- 📊 **数据分析增强**:加入更多统计分析功能
- 🤖 **智能化升级**引入AI辅助决策
- 📱 **移动端完善**:增加更多移动端功能
- 🌐 **系统集成**:与其他政务系统对接
**技术优化**
-**性能提升**:优化数据库查询,提升响应速度
- 🔐 **安全加强**:增加更多安全防护措施
- 📈 **监控完善**:加入系统监控和告警功能
- 🔄 **自动化**:增加更多自动化处理流程
### 总结
我们的EMS系统不仅实现了环境监测的基本功能更在技术实现上体现了现代软件开发的最佳实践。通过合理的架构设计、清晰的代码组织和用户友好的界面为环境管理工作提供了有力的技术支撑。
这个项目展示了我们在**全栈开发**、**系统设计**、**数据库管理**等方面的综合能力,是一个真正能够投入实际使用的完整系统。
### 数据异常检测功能
为了保证环境数据的准确性,系统提供了简单有效的异常检测功能:
```java
@Service
public class DataValidationService {
/**
* 检测异常数据
*/
public List<String> validateAqiData(List<AqiData> dataList) {
List<String> warnings = new ArrayList<>();
for (AqiData data : dataList) {
// 检查数值范围
if (data.getAqiValue() < 0 || data.getAqiValue() > 500) {
warnings.add("AQI数值异常: " + data.getAqiValue());
}
// 检查PM2.5范围
if (data.getPm25() < 0 || data.getPm25() > 1000) {
warnings.add("PM2.5数值异常: " + data.getPm25());
}
}
return warnings;
}
/**
* 数据趋势分析
*/
public String analyzeTrend(List<AqiData> recentData) {
if (recentData.size() < 2) {
return "数据不足";
}
double firstValue = recentData.get(0).getAqiValue();
double lastValue = recentData.get(recentData.size() - 1).getAqiValue();
if (lastValue > firstValue * 1.2) {
return "空气质量呈恶化趋势";
} else if (lastValue < firstValue * 0.8) {
return "空气质量呈改善趋势";
} else {
return "空气质量相对稳定";
}
}
}
```
**功能特点**
- **数据校验**:检查环境数据是否在合理范围内
- **趋势分析**:分析空气质量变化趋势
- **预警提醒**:发现异常数据时及时提醒
---
## 项目总结
### 🎯 主要成果
1. **功能完整性**
- 实现了环境监测数据的全生命周期管理
- 提供了直观的数据可视化界面
- 支持多种数据导入导出格式
2. **技术特色**
- 采用前后端分离架构,便于维护和扩展
- 使用Spring Boot框架开发效率高
- 集成了地图展示功能,用户体验良好
3. **实用价值**
- 解决了环境数据管理的实际需求
- 提供了便民的在线查询服务
- 支持数据分析和趋势预测
### 🚀 技术亮点
- **系统架构**:清晰的分层设计,易于理解和维护
- **数据处理**:高效的数据存储和查询机制
- **用户界面**现代化的Web界面操作简单直观
- **安全性**:完善的用户认证和权限管理
### 📈 未来展望
1. **功能扩展**
- 增加更多环境指标的监测
- 支持移动端应用
- 添加数据预警功能
2. **技术优化**
- 提升系统性能和响应速度
- 增强数据分析能力
- 优化用户体验
```
**谢谢各位老师的指导!**

View File

@@ -0,0 +1,172 @@
# 网格员任务处理功能 - 设计文档
## 1. 功能描述
本功能模块是为一线网格员NEPG端设计的核心操作界面。它使得网格员能够清晰地接收、查看和处理由管理员分配的污染勘查任务并在简化的网格地图上获得路径指引最终提交现场的AQI数据完成任务闭环。
## 2. 涉及角色
- **主要使用者**: `网格员 (GRID_WORKER)`
## 3. 业务规则
### 3.1 任务接收与查看
- 网格员登录后,首页即为任务列表。
- 任务列表默认按`任务优先级`降序、`创建时间`升序排列。
- **任务优先级**由后端动态计算,主要基于反馈的`severityLevel`(严重等级)和任务的等待时长。
- 每个任务项应清晰展示关键信息:`标题``文字地址``优先级``状态`
### 3.2 任务处理规则
- **任务接受与拒绝**:
- 对于`severityLevel``HIGH`的任务,网格员**不可拒绝**。
- 对于`severityLevel``LOW``MEDIUM`的任务,网格员在任务详情页可以点击"拒绝"按钮。
- 拒绝时需选择一个预设的理由(如"当前任务繁忙"、"超出能力范围"等)。
- 拒绝后,该任务会从该网格员的列表中移除,并由系统自动进行再分配。
- **数据提交**:
- 网格员到达现场后,必须在任务详情页填写并提交`AQI数据`表单才能完成任务。
- `AqiData`记录与`Feedback`记录强关联。
- **主动上报**: 系统允许网格员在没有关联任务的情况下主动上报其所在网格的AQI数据。
### 3.3 地图与定位规则
- **实时定位**: 进入任务详情页时App应请求获取网格员的实时地理位置并在地图上标记。
- **路径规划**: A*路径规划仅在首次进入详情页时计算一次。如果网格员偏离了规划路径,可以提供一个"重新规划"按钮。
- **离线地图**: (未来规划)为节省流量和应对网络不佳的情况,可以考虑支持离线网格地图的缓存。
## 4. 功能实现流程
### 4.1 任务处理流程
```mermaid
graph TD
subgraph "NEPG 端 (网格员操作)"
A[登录系统] --> B[查看任务列表];
B --> C{选择一个任务};
C --> D[查看任务详情];
D --> E{任务是否为HIGH等级?};
E -- 否 --> F[显示"接受"和"拒绝"按钮];
F -- 点击拒绝 --> G[任务被退回,流程结束];
E -- 是 --> H[只显示"接受"按钮];
F -- 点击接受 --> I{处理任务};
H --> I;
I --> J[在网格地图上<br>查看路径指引];
J --> K[到达现场<br>勘查并记录数据];
K --> L[填写并提交<br>AQI数据报告];
end
subgraph "后端服务"
L --> M[创建AqiData记录];
M --> N[更新Feedback状态<br>为SUBMITTED];
end
style D fill:#E3F2FD
style G fill:#FFCDD2
style N fill:#C8E6C9
```
### 4.2 主动上报流程
```mermaid
graph TD
A[网格员在主界面<br>点击"主动上报"] --> B[进入上报页面];
B --> C[系统自动获取<br>当前网格位置];
C --> D[填写AQI数据表单];
D --> E{点击提交};
E --> F[后端创建无关联<br>feedbackId的AqiData记录];
F --> G[提示"上报成功"];
style C fill:#E3F2FD
style F fill:#C8E6C9
```
## 5. API 接口设计
### 5.1 获取我的任务列表
- **URL**: `GET /api/worker/tasks`
- **权限**: `GRID_WORKER`
- **查询参数**: `status` (可选: `ASSIGNED`, `IN_PROGRESS`), `page`, `size`
- **成功响应** (`200 OK`): 返回分页的任务列表。
### 5.2 获取任务详情
- **URL**: `GET /api/worker/tasks/{taskId}`
- **权限**: `GRID_WORKER`
- **成功响应** (`200 OK`): 返回任务的详细信息包含公众提交的描述、图片以及A*算法规划出的路径坐标点数组。
### 5.3 接受任务
- **URL**: `POST /api/worker/tasks/{taskId}/accept`
- **权限**: `GRID_WORKER`
- **成功响应** (`200 OK`): `{ "message": "任务已接受" }`
### 5.4 拒绝任务
- **URL**: `POST /api/worker/tasks/{taskId}/reject`
- **权限**: `GRID_WORKER`
- **请求体**: `{"reason": "当前任务繁忙"}`
- **成功响应** (`200 OK`): `{ "message": "任务已拒绝" }`
### 5.5 提交任务报告
- **URL**: `POST /api/worker/tasks/{taskId}/submit`
- **权限**: `GRID_WORKER`
- **请求体** (application/json):
```json
{
"notes": "已到现场检查,确认存在异味,已要求整改",
"aqiData": {
"pm25": 75.5, "pm10": 120.3, "so2": 15.8,
"no2": 40.2, "co": 1.2, "o3": 65.2
}
}
```
- **成功响应** (`200 OK`): `{ "message": "任务报告提交成功,等待主管审核" }`
### 5.6 主动上报AQI数据
- **URL**: `POST /api/worker/aqi-data/report`
- **权限**: `GRID_WORKER`
- **请求体**:
```json
{
"gridX": 15,
"gridY": 20,
"aqiData": { ... }
}
```
- **成功响应** (`201 Created`): `{ "message": "数据上报成功" }`
## 6. 错误处理与边界情况
| 场景 | 触发条件 | 系统处理 | 用户提示 |
| :--- | :--- | :--- | :--- |
| **定位失败** | App无法获取GPS信号 | 地图模块显示默认位置(如市中心),并提示用户检查定位服务 | "无法获取您的位置请检查GPS设置" |
| **提交时网络中断**| 点击提交后,网络断开 | ① App应缓存用户填写的数据<br>② 网络恢复后,提示用户是否重新提交。| "网络连接已断开,您的报告已保存。网络恢复后将提示您重新提交。"|
| **任务已被取消/重分配**| 网格员处理一个已被主管取消的任务 | 调用任何与该任务相关的API时后端返回`404 Not Found`或`409 Conflict` | 详情页提示"该任务已被取消或重新分配",并引导用户返回列表页 |
## 7. 界面设计要求
### 7.1 任务列表页面
- **布局**: 采用卡片列表形式,每个卡片代表一个任务。
- **卡片内容**:
- 左上角用不同颜色的标签标示优先级(``-红色, ``-橙色, ``-蓝色)。
- 显示反馈`标题`和`文字地址`。
- 右下角显示任务当前状态(如`待接受`、`处理中`)。
- **交互**:
- 点击卡片进入任务详情页。
- 列表应支持**下拉刷新**功能。
- 页面右上角提供一个"主动上报"的浮动按钮。
### 7.2 任务详情页面
- **顶部**: 清晰展示任务的`标题`、`详细描述`、`污染类型`、`严重等级`以及公众提交的`现场照片`(支持点击放大)。
- **中部 (地图)**:
- 内嵌一个简化的网格地图组件。
- 在地图上用不同图标标示出**网格员当前位置**和**目标任务位置**。
- 自动规划并高亮显示从起点到终点的**最优路径**A*算法结果),路径需能绕开障碍网格。
- 地图右下角应提供"重新定位"和"重新规划路径"的按钮。
- **底部 (操作区)**:
- **状态: ASSIGNED**: 根据业务规则显示"接受"和"拒绝"按钮。
- **状态: IN_PROGRESS**: 显示一个"提交报告"按钮。点击后弹出一个包含AQI数据表单的对话框。
- **状态: SUBMITTED / COMPLETED**: 不显示任何操作按钮,仅展示任务信息和已提交的报告。
- **AQI数据提交表单 (弹窗)**:
- 包含`PM2.5`, `PM10`, `SO2`, `NO2`, `CO`, `O3`等数值输入框。
- 还有一个可选的`备注`多行文本框。
- 有一个"确认提交"按钮,点击后显示加载状态,成功后关闭弹窗并自动刷新任务详情页的状态。
### 7.3 主动上报页面
- 页面核心是一个地图,自动定位到网格员当前位置。
- 地图下方是一个简化的AQI数据提交表单允许网格员直接填写数据并提交。
- 提交成功后显示成功提示,并返回到主界面。

View File

@@ -0,0 +1,163 @@
# 角色与晋升体系 - 功能设计文档
## 1. 功能描述
本功能模块定义了系统内所有用户的角色身份、权限边界、以及角色之间的流转路径。它旨在建立一个清晰、安全、易于管理的权限体系,支撑起"公众参与"和"内部管理"两条核心业务线。
## 2. 涉及角色
- **所有角色**: `公众监督员`, `业务主管`, `网格员`, `系统管理员`, `决策者`
- **核心操作者**: `系统管理员 (ADMIN)` 是执行角色晋升和管理的主要操作者。
## 3. 业务规则
### 3.1 角色定义与权限
- **公众监督员 (PUBLIC_SUPERVISOR)**: 外部用户只能使用NEPS端仅能查看和管理自己提交的反馈。
- **业务主管 (SUPERVISOR)**: 内部核心**业务管理者**。使用NEPM管理端负责其管辖区域内的**任务创建、分配、审核、取消**等全生命周期管理。
- **网格员 (GRID_WORKER)**: 内部核心**任务执行者**。使用NEPG端负责接收、执行和提交任务。
- **系统管理员 (ADMIN)**: 内部核心**系统维护者**。使用NEPM管理端负责用户账户管理审批、创建、晋升、系统配置等**不参与日常业务流程**。
- **决策者 (DECISION_MAKER)**: 内部高级观察用户使用NEPV端拥有对所有统计和可视化数据的**只读权限**。
### 3.2 角色晋升规则
- 晋升是**单向**的、**逐级**的。
- **禁止跨级晋升**。
- **`PUBLIC_SUPERVISOR` -> `GRID_WORKER` (入职)**: 由用户在NEPS端主动提交申请经由**系统管理员(ADMIN)**在NEPM端审批通过代表用户正式成为内部员工。
- **`GRID_WORKER` -> `SUPERVISOR` (晋升)**: 由其直属上级或**系统管理员(ADMIN)**在NEPM端为其执行`promote()`操作,代表从一线执行者晋升为业务管理者。
- **`ADMIN``DECISION_MAKER`**: 特殊角色,不参与业务晋升流。由系统更高权限的管理员在后台直接配置。
### 3.3 数据模型映射
- **实体关系**: 一个 `User` 实体**拥有一个** `Role` 属性。
- **关键字段**: 角色和状态的管理主要依赖 `user_account` 表中的以下字段:
- `role` (Enum: `PUBLIC_SUPERVISOR`, `GRID_WORKER`, `SUPERVISOR`, `ADMIN`, `DECISION_MAKER`): 定义用户的角色。
- `status` (Enum: `PENDING_APPROVAL`, `ACTIVE`, `REJECTED`, `DISABLED`): 定义用户的状态。新申请的用户状态为`PENDING_APPROVAL`,审批通过后为`ACTIVE`
- **操作**: 角色晋升或状态变更是对 `user_account` 表中特定记录的 `role``status` 字段的原子更新。
### 3.4 权限矩阵 (Permission Matrix)
| 功能模块 | API 端点 (示例) | `PUBLIC_SUPERVISOR` | `GRID_WORKER` | `SUPERVISOR` | `ADMIN` | `DECISION_MAKER` |
| :--- | :--- | :---: | :---: | :---: | :---: | :---: |
| **公众反馈** | `POST /api/feedback` | C | - | - | - | - |
| | `GET /api/feedback/my` | R | - | - | - | - |
| **任务处理** | `GET /api/worker/tasks` | - | R | - | - | - |
| | `POST /api/worker/tasks/{id}/submit`| - | U | - | - | - |
| **任务管理** | `GET /api/supervisor/tasks` | - | - | R | - | - |
| | `POST /api/supervisor/tasks` | - | - | C | - | - |
| | `PUT /api/supervisor/tasks/{id}` | - | - | U | - | - |
| | `DELETE /api/supervisor/tasks/{id}`| - | - | D | - | - |
| | `POST /api/supervisor/tasks/{id}/approve` | - | - | **Approve** | - | - |
| **用户管理** | `GET /api/admin/users` | - | - | R (下属) | R (全部) | - |
| | `POST /api/admin/users/{id}/promote` | - | - | - | **Promote** | - |
| **入职审批** | `GET /api/admin/approvals` | - | - | - | R | - |
| | `POST /api/admin/approvals/{id}/approve` | - | - | - | **Approve** | - |
| **数据大屏** | `GET /api/data-v/*` | - | - | - | R | R |
| *C=Create, R=Read, U=Update, D=Delete* |
## 4. 功能实现流程
### 4.1 总体晋升流程
```mermaid
graph TD
subgraph "外部用户区 (NEPS)"
A[公众注册<br/>默认角色: PUBLIC_SUPERVISOR] --> B{个人中心};
B --> C[提交入职申请<br>成为网格员];
end
subgraph "内部管理区 (NEPM)"
C --> D["管理员(ADMIN)审批列表"];
D -- 通过 --> E[角色变更为 GRID_WORKER];
E --> F{用户管理};
F -- 管理员执行promote() --> G[角色变更为 SUPERVISOR];
end
subgraph "后台配置"
H[系统最高管理员] --> I[为特定用户分配<br/>ADMIN/DECISION_MAKER角色];
end
style A fill:#E3F2FD,stroke:#333,stroke-width:2px
style E fill:#C8E6C9,stroke:#333,stroke-width:2px
style G fill:#FFD54F,stroke:#333,stroke-width:2px
style I fill:#FFECB3,stroke:#333,stroke-width:2px
```
### 4.2 申请入职时序图
```mermaid
sequenceDiagram
participant User as "公众监督员 (NEPS)"
participant Frontend as "前端 (NEPS)"
participant Backend as "后端 (API)"
participant Admin as "系统管理员 (NEPM)"
User->>Frontend: 点击"申请成为网格员"
Frontend->>User: 显示申请信息填写表单
User->>Frontend: 填写并提交申请
Frontend->>Backend: POST /api/users/apply-for-worker (携带申请信息)
Backend->>Backend: 创建申请记录, 更新用户状态为`PENDING_APPROVAL`
Backend-->>Frontend: 申请已提交 (200 OK)
Frontend->>User: 显示"申请已提交,等待管理员审批"
Admin->>Backend: (在NEPM端) GET /api/admin/approvals
Backend-->>Admin: 返回待审批用户列表
Admin->>Backend: POST /api/admin/approvals/{userId}/approve
Backend->>Backend: 校验权限, 更新用户role为`GRID_WORKER`, status为`ACTIVE`
Backend-->>Admin: 审批成功 (200 OK)
```
### 4.3 角色晋升时序图
```mermaid
sequenceDiagram
participant Admin as "系统管理员 (NEPM)"
participant Frontend as "前端 (NEPM)"
participant Backend as "后端 (API)"
participant User as "被晋升的用户 (Grid Worker)"
Admin->>Frontend: 在用户管理列表, 点击"晋升为主管"
Frontend->>Frontend: 弹出二次确认对话框
Admin->>Frontend: 确认操作
Frontend->>Backend: POST /api/admin/users/{userId}/promote
Backend->>Backend: 校验管理员权限
Backend->>Backend: 校验目标用户当前角色为`GRID_WORKER`
Backend->>Backend: 更新用户role为`SUPERVISOR`
Backend-->>Frontend: 晋升成功 (200 OK)
Frontend->>Admin: 刷新列表, 显示用户新角色
User->>Backend: (下次登录或刷新页面时)
Backend-->>User: 返回更新后的角色信息和权限
Note right of User: 用户界面(菜单、操作按钮)随新角色动态更新
```
## 5. API接口设计
### 5.1 用户申请成为网格员
- **URL**: `POST /api/users/apply-for-worker`
- **权限**: `PUBLIC_SUPERVISOR`
- **请求体**: `applicationReason`, `experience`
- **响应**: `200 OK`
### 5.2 管理员获取待审批列表
- **URL**: `GET /api/admin/approvals`
- **权限**: `ADMIN`
- **响应**: `200 OK`, 返回用户申请列表 `[{userId, name, applicationTime, ...}]`
### 5.3 管理员审批
- **URL**: `POST /api/admin/approvals/{userId}/approve``.../reject`
- **权限**: `ADMIN`
- **请求体**: (可选) `rejectionReason`
- **响应**: `200 OK`
### 5.4 管理员晋升用户
- **URL**: `POST /api/admin/users/{userId}/promote`
- **权限**: `ADMIN`
- **响应**: `200 OK`
## 6. 界面设计要求
### 6.1 NEPS端 (公众监督员)
- 在"个人中心"或"我的"页面,应有一个醒目的入口,如"申请成为网格员"按钮。
- 点击后, 弹出一个表单或新页面, 要求用户填写申请理由、相关经验等附加信息, 并提交。
- 提交后,按钮应变为"审批中..."的不可点击状态。
- 用户的申请状态(待审批、已通过、已驳回)应在个人中心清晰展示。若被驳回,应显示驳回原因。
### 6.2 NEPM端 (管理员 ADMIN)
- 在侧边栏应有"用户管理"和"入职审批"两个菜单项。
- **入职审批页面**: 以列表形式展示所有待审批的申请,包含申请人姓名、申请时间、申请理由等。管理员可点击"通过"或"驳回"。操作后有明确的成功提示,且该条目从列表中移除。
- **用户管理页面**:
- 以表格形式展示其管辖范围内的所有内部用户列表(包括网格员、主管),并可通过角色、状态进行筛选。
- 表格的"操作"列中,应包含对符合条件的`GRID_WORKER`的"晋升为主管"按钮。
- 点击"晋升"按钮,应有二次确认弹窗,明确告知"用户XXX的角色将从GridWorker变更为Supervisor",防止误操作。操作成功后,该用户的角色信息在表格中应立即更新。

View File

@@ -0,0 +1,173 @@
# 账号管理功能 - 设计文档
## 1. 功能描述
本模块负责处理所有用户的认证与基础账户操作,包括注册、登录和安全设置(如忘记密码、修改密码/手机号)。其核心目标是提供一个安全、可靠且用户体验良好的身份验证入口。
## 2. 涉及角色
- **所有角色**: 该功能是所有需要登录系统的用户(公众监督员、监督员、网格员、管理员、决策者)的通用基础功能。
## 3. 业务规则
### 3.1 注册规则
- **唯一性约束**: 注册时使用的`邮箱``手机号`必须在`user_account`表中是唯一的。
- **密码策略**:
- 复杂度: 8-16位必须同时包含大写字母、小写字母、数字和特殊字符。
- 确认机制: 注册时密码需要输入两次进行确认。
- 存储安全: 密码在后端必须使用强哈希算法如BCrypt加盐后存储绝不允许明文存储。
- **邮箱验证码规则**:
- **时效性**: 验证码生成后5分钟内有效。
- **冷却期**: "获取验证码"按钮点击后有60秒的冷却时间防止恶意请求。
- **尝试次数**: 同一个验证码最多允许输错2次第3次输错则该验证码立即失效需重新获取。
### 3.2 登录规则
- 支持使用`邮箱``手机号`作为登录凭据。
- 登录失败时,应给出统一的、模糊的提示(如"用户名或密码错误"),避免泄露账户是否存在的信息。
- 可考虑引入登录尝试次数限制机制例如连续失败5次后临时锁定账户15分钟。
### 3.3 安全验证规则
- **身份确认**: "忘记密码"或"修改手机号"等敏感操作,必须先通过"邮箱 + 姓名"进行第一步身份确认。
- **操作授权**: 身份确认通过后,必须再次完成与注册流程完全相同的邮箱动态验证码校验,才能进行下一步操作。
### 3.4 JWT (Token) 规则
- **Payload 结构**: Token的Payload中应至少包含`userId`, `role`, `email`,以便后端进行快速的身份和权限识别。
- **有效期**: Access Token的有效期应设置为较短的时间如1小时以降低泄露风险。
- **刷新机制**: 提供一个Refresh Token其有效期更长如7天。当Access Token过期后前端可使用Refresh Token向后端申请新的Access Token无需用户重新登录。
- **存储**: 前端应将Access Token存储在内存中将Refresh Token存储在`HttpOnly``Secure`的Cookie中以提高安全性。
### 3.5 系统管理员操作规则
- **创建内部账户**: 系统管理员(Admin)在NEPM端创建内部用户`SUPERVISOR`, `GRID_WORKER`)时,系统应生成一个安全的初始随机密码。
- **密码重置**: 管理员可以为任何用户重置密码,重置后同样生成一个随机密码。
- **密码分发**: 生成的初始密码或重置后的密码,应通过安全的、非系统内的方式(如邮件、短信)告知用户,并强制用户在首次登录后立即修改。
## 4. 功能实现流程
### 4.1 注册流程
```mermaid
sequenceDiagram
participant User as 用户
participant Frontend as 前端界面
participant Backend as 后端服务
participant Mail as 邮箱服务
User->>Frontend: 填写注册信息 (邮箱, 手机, 密码等)
Note right of Frontend: 前端实时校验格式和密码强度
User->>Frontend: 点击"获取验证码"
Frontend->>Backend: POST /api/auth/send-verification-code
Note over Backend: 生成6位验证码, 存入Redis并设5分钟过期
Backend->>Mail: 调用163邮箱API发送验证码
Mail-->>User: 收到验证码邮件
User->>Frontend: 输入收到的验证码
User->>Frontend: 点击"注册"
Frontend->>Backend: POST /api/auth/register
Backend->>Backend: 校验验证码 (正确性, 是否过期, 尝试次数)
alt 验证成功
Backend->>Backend: 校验邮箱/手机唯一性, BCrypt加密密码
Backend->>Backend: 创建`user_account`记录
Backend-->>Frontend: 注册成功 (201 Created)
else 验证失败
Backend-->>Frontend: 返回错误信息 (400 Bad Request)
end
```
### 4.2 登录流程
```mermaid
sequenceDiagram
participant User as 用户
participant Frontend as 前端
participant Backend as 后端
User->>Frontend: 输入邮箱/手机号和密码, 点击登录
Frontend->>Backend: POST /api/auth/login
Backend->>Backend: 验证用户信息和密码
alt 验证成功
Backend->>Backend: 生成Access Token和Refresh Token
Backend-->>Frontend: 登录成功 (200 OK)<br>返回Access Token, 并设置Refresh Token到Cookie
Note left of Frontend: 存储Token, 跳转到用户主页
else 验证失败
Backend->>Backend: 记录失败次数, 检查是否需要锁定账户
Backend-->>Frontend: 返回"用户名或密码错误" (401 Unauthorized)
end
```
### 4.3 忘记密码流程
```mermaid
sequenceDiagram
participant User as 用户
participant Frontend as 前端界面
participant Backend as 后端服务
User->>Frontend: 点击"忘记密码", 输入邮箱和姓名
Frontend->>Backend: POST /api/auth/request-password-reset
Backend->>Backend: 校验邮箱和姓名是否存在且匹配
alt 身份校验成功
Backend-->>Frontend: 跳转至邮箱验证码页面
Note right of Frontend: (后续流程与注册时的邮箱验证完全相同)
User->>Frontend: 完成邮箱验证, 输入新密码
Frontend->>Backend: POST /api/auth/reset-password
Backend->>Backend: 更新用户密码
Backend-->>Frontend: 密码重置成功 (200 OK)
else 身份校验失败
Backend-->>Frontend: 返回错误信息 (400 Bad Request)
end
```
## 5. API 接口设计
### 5.1 用户注册
- **URL**: `POST /api/auth/register`
- **请求体**: `name`, `email`, `phone`, `password`, `verificationCode`
- **响应**: `201 Created`
### 5.2 用户登录
- **URL**: `POST /api/auth/login`
- **请求体**: `principal` (邮箱或手机号), `password`
- **响应**: `200 OK`, 返回 `accessToken`并在Cookie中设置`refreshToken`
### 5.3 发送邮箱验证码
- **URL**: `POST /api/auth/send-verification-code`
- **请求体**: `email`, `type` (Enum: `REGISTER`, `RESET_PASSWORD`)
- **响应**: `200 OK`
### 5.4 刷新AccessToken
- **URL**: `POST /api/auth/refresh-token`
- **请求体**: (空, Refresh Token从Cookie中获取)
- **响应**: `200 OK`, 返回新的`accessToken`
### 5.5 请求密码重置
- **URL**: `POST /api/auth/request-password-reset`
- **请求体**: `email`, `name`
- **响应**: `200 OK` (无论用户是否存在,都返回成功,防止信息泄露)
### 5.6 重置密码
- **URL**: `POST /api/auth/reset-password`
- **请求体**: `email`, `newPassword`, `verificationCode`
- **响应**: `200 OK`
### 5.7 管理员创建用户
- **URL**: `POST /api/admin/users`
- **权限**: `ADMIN`
- **请求体**: `name`, `email`, `phone`, `role`, `region`, `level`
- **响应**: `201 Created`, 返回包含初始随机密码的用户信息。
## 6. 界面设计要求
### 6.1 注册页面
- 表单布局清晰,各输入项有明确的标签和占位提示。
- "获取验证码"按钮在点击后应变为不可用状态,并显示倒计时。
- 密码输入框应为密码类型,并提供一个可切换"显示/隐藏"密码的图标。
- 密码强度提示:实时根据用户输入,以进度条或文字形式提示密码强度(弱、中、强)。
- 所有输入项的错误提示应在输入框下方实时显示,内容明确。
### 6.2 登录页面
- 界面简洁,突出登录表单。
- 提供"忘记密码"的链接。
- 可选提供"记住我"的复选框。
- 登录成功后应有平滑的过渡效果并根据用户角色跳转到对应的系统主页NEPS, NEPG, NEPM, NEPV
### 6.3 忘记密码/安全验证页面
- 流程应分步进行,保持每一步操作的单一和清晰。
- 第一步:身份验证(输入邮箱、姓名)。
- 第二步:安全验证(输入邮箱收到的验证码)。
- 第三步:重置密码(输入新密码并确认)。
- 每个步骤都应有清晰的标题和进度指示。
- 操作成功后,应明确提示用户"密码已重置,请使用新密码登录",并引导至登录页面。