Files
Environment-Monitoring-System/Report/系统设计_第四部分.md
ChuXun 02a830145e 1
2025-10-25 19:18:43 +08:00

386 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### 2.4 算法设计
本节详细描述了系统中的核心算法设计,这些算法是系统智能化和高效运行的关键。
#### 2.4.1 A* 寻路算法
A*寻路算法是系统中的核心算法之一,用于为网格员规划从当前位置到任务目标点的最优路径。该算法在网格与地图模块中发挥着重要作用。
**算法目的**:
为网格员提供从当前位置到任务地点的最优(最短或最快)路径,避开障碍物。
**算法输入**:
- `startNode`: 起始点坐标 (x, y)。
- `endNode`: 目标点坐标 (x, y)。
- `grid`: 包含障碍物信息的地图网格数据。
**核心逻辑**:
1. 维护一个开放列表(`openList`)和一个关闭列表(`closedList`)。
- `openList`: 存储待探索的节点使用优先队列实现按F值排序。
- `closedList`: 存储已探索过的节点。
2.`openList` 中选取F值G值+H值最小的节点作为当前节点。
- G值: 从起点到当前节点的实际代价。
- H值: 从当前节点到终点的预估代价(启发函数)。
- F值: G值 + H值表示经过当前节点到达终点的总代价估计。
3. 遍历当前节点的相邻节点(上、下、左、右四个方向)。
4. 对于每个相邻节点:
- 如果是障碍物或已在`closedList`中,则跳过。
- 如果不在`openList`计算其G值、H值和F值将其加入`openList`,并记录其父节点为当前节点。
- 如果已在`openList`检查经由当前节点到达该相邻节点的路径是否更优G值更小。如果更优则更新其G值、F值和父节点。
5. 将当前节点从`openList`移除,加入`closedList`
6. 重复步骤2-5直到
- 找到目标节点(当前节点为终点)。
-`openList`为空(无法找到路径)。
7. 如果找到目标节点,通过回溯父节点构建从起点到终点的路径。
**启发函数选择**:
系统采用曼哈顿距离Manhattan Distance作为启发函数计算公式为
```
h(n) = |n.x - goal.x| + |n.y - goal.y|
```
这种启发函数适合网格化的移动模式,只允许上、下、左、右四个方向的移动。
**算法实现**:
```java
public List<Point> findPath(Point start, Point end) {
// 加载地图数据和障碍物信息
List<MapGrid> mapGrids = mapGridRepository.findAll();
Set<Point> obstacles = new HashSet<>();
for (MapGrid gridCell : mapGrids) {
if (gridCell.isObstacle()) {
obstacles.add(new Point(gridCell.getX(), gridCell.getY()));
}
}
// 初始化开放列表和所有节点映射
PriorityQueue<Node> openSet = new PriorityQueue<>(Comparator.comparingInt(n -> n.f));
Map<Point, Node> allNodes = new HashMap<>();
// 创建起始节点
Node startNode = new Node(start, null, 0, calculateHeuristic(start, end));
openSet.add(startNode);
allNodes.put(start, startNode);
// 开始A*算法主循环
while (!openSet.isEmpty()) {
Node currentNode = openSet.poll();
// 找到目标
if (currentNode.point.equals(end)) {
return reconstructPath(currentNode);
}
// 探索相邻节点
for (Point neighborPoint : getNeighbors(currentNode.point)) {
if (obstacles.contains(neighborPoint)) {
continue; // 跳过障碍物
}
int tentativeG = currentNode.g + 1; // 相邻节点间距离为1
Node neighborNode = allNodes.get(neighborPoint);
if (neighborNode == null) {
// 新节点
int h = calculateHeuristic(neighborPoint, end);
neighborNode = new Node(neighborPoint, currentNode, tentativeG, h);
allNodes.put(neighborPoint, neighborNode);
openSet.add(neighborNode);
} else if (tentativeG < neighborNode.g) {
// 找到更好的路径
neighborNode.parent = currentNode;
neighborNode.g = tentativeG;
neighborNode.f = tentativeG + neighborNode.h;
// 更新优先队列
openSet.remove(neighborNode);
openSet.add(neighborNode);
}
}
}
return Collections.emptyList(); // 没有找到路径
}
// 计算启发式函数值(曼哈顿距离)
private int calculateHeuristic(Point a, Point b) {
return Math.abs(a.x() - b.x()) + Math.abs(a.y() - b.y());
}
// 获取相邻节点(上、下、左、右)
private List<Point> getNeighbors(Point point) {
List<Point> neighbors = new ArrayList<>(4);
int[] dx = {0, 0, 1, -1}; // 右、左、下、上
int[] dy = {1, -1, 0, 0};
for (int i = 0; i < 4; i++) {
int newX = point.x() + dx[i];
int newY = point.y() + dy[i];
// 检查边界
if (newX >= 0 && newX < mapWidth && newY >= 0 && newY < mapHeight) {
neighbors.add(new Point(newX, newY));
}
}
return neighbors;
}
// 重建路径
private List<Point> reconstructPath(Node node) {
LinkedList<Point> path = new LinkedList<>();
while (node != null) {
path.addFirst(node.point);
node = node.parent;
}
return path;
}
```
**算法输出**:
从起点到终点的一系列坐标点列表,表示规划好的路径。如果无法找到路径,则返回空列表。
**算法应用**:
`PathfindingController`中通过`/api/pathfinding/find`接口暴露,网格员可以通过该接口获取从当前位置到任务地点的最优路径。
#### 2.4.2 任务智能分配算法
任务智能分配算法是系统的另一个核心算法,用于在多个可用网格员中,为新任务选择最合适的执行者。
**算法目的**:
在多个可用网格员中,为新任务选择最合适的执行者,优化任务分配效率和完成质量。
**算法输入**:
- `task`: 需要分配的任务对象。
- `availableWorkers`: 可用的网格员列表。
**核心逻辑**:
这是一个复合策略算法,综合考虑多个因素,通过加权评分机制选择最合适的网格员:
1. **地理距离评分**:
- 计算每个网格员当前位置到任务位置的距离。
- 距离越近,评分越高。
- 使用公式: `distanceScore = maxDistance - distance`,其中`maxDistance`是一个预设的最大考虑距离。
2. **当前负载评分**:
- 评估每个网格员当前正在处理的任务数量。
- 任务数量越少,评分越高。
- 使用公式: `loadScore = maxTasks - currentTasks`,其中`maxTasks`是一个预设的最大任务数。
3. **专业匹配度评分**:
- 根据任务类型和网格员的专业技能进行匹配。
- 匹配度越高,评分越高。
- 使用公式: `skillScore = matchedSkills / requiredSkills.size()`
4. **历史表现评分**:
- 考虑网格员的历史完成率和质量评分。
- 表现越好,评分越高。
- 使用公式: `performanceScore = (completionRate * 0.7) + (qualityRating * 0.3)`
5. **综合评分计算**:
- 对上述各项评分进行加权求和。
- 使用公式: `totalScore = (distanceScore * w1) + (loadScore * w2) + (skillScore * w3) + (performanceScore * w4)`
- 其中w1、w2、w3、w4是各项评分的权重且满足`w1 + w2 + w3 + w4 = 1`
6. **选择最高评分的网格员**:
- 根据综合评分对网格员进行排序。
- 选择评分最高的网格员作为推荐人选。
**算法实现**:
```java
public UserAccount recommendWorkerForTask(Task task, List<UserAccount> availableWorkers) {
if (availableWorkers.isEmpty()) {
return null;
}
// 权重配置
final double DISTANCE_WEIGHT = 0.4;
final double LOAD_WEIGHT = 0.3;
final double SKILL_WEIGHT = 0.2;
final double PERFORMANCE_WEIGHT = 0.1;
// 任务位置
Point taskLocation = new Point(task.getGridX(), task.getGridY());
// 计算每个网格员的评分
Map<UserAccount, Double> workerScores = new HashMap<>();
for (UserAccount worker : availableWorkers) {
// 1. 地理距离评分
Point workerLocation = new Point(worker.getGridX(), worker.getGridY());
double distance = calculateDistance(workerLocation, taskLocation);
double maxDistance = 10.0; // 最大考虑距离
double distanceScore = Math.max(0, maxDistance - distance) / maxDistance;
// 2. 当前负载评分
int currentTasks = taskRepository.countByAssigneeIdAndStatusIn(
worker.getId(), Arrays.asList(TaskStatus.ASSIGNED, TaskStatus.IN_PROGRESS));
int maxTasks = 5; // 最大任务数
double loadScore = (double)(maxTasks - currentTasks) / maxTasks;
// 3. 专业匹配度评分
double skillScore = calculateSkillMatch(worker, task);
// 4. 历史表现评分
double completionRate = workerStatsService.getCompletionRate(worker.getId());
double qualityRating = workerStatsService.getAverageQualityRating(worker.getId());
double performanceScore = (completionRate * 0.7) + (qualityRating * 0.3);
// 5. 综合评分
double totalScore = (distanceScore * DISTANCE_WEIGHT) +
(loadScore * LOAD_WEIGHT) +
(skillScore * SKILL_WEIGHT) +
(performanceScore * PERFORMANCE_WEIGHT);
workerScores.put(worker, totalScore);
}
// 选择评分最高的网格员
return workerScores.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse(null);
}
```
**算法输出**:
推荐的最佳网格员对象。如果没有合适的网格员则返回null。
**算法应用**:
`TaskAssignmentService`中实现,当主管触发自动分配或系统基于反馈自动创建任务时调用。
### 2.5 数据持久化设计
系统采用了一种独特的数据持久化方案不使用传统的关系型数据库而是以JSON文件的形式存储所有数据。这种设计简化了部署和配置特别适合快速迭代和中小型应用场景。
#### 2.5.1 JSON文件存储架构
系统的数据持久化层基于以下架构:
1. **核心存储服务**:
- `JsonStorageService`: 提供对JSON文件的读写操作包括序列化和反序列化。
- `FileSystemService`: 处理文件系统操作,如创建、读取、更新和删除文件。
2. **Repository层**:
- 为每种核心模型提供专门的Repository类`UserRepository``FeedbackRepository`等。
- 这些Repository类模拟了类似JPA的接口提供CRUD操作和查询功能。
- 内部使用`JsonStorageService`进行实际的文件操作。
3. **并发控制**:
- 使用文件锁机制确保在并发环境下的数据一致性。
- 实现了简单的乐观锁定策略,通过版本号检测冲突。
4. **索引和查询优化**:
- 在内存中维护索引结构,加速常见查询操作。
- 支持基于字段值的过滤和排序。
#### 2.5.2 核心JSON文件结构
系统中的每个核心模型对应一个JSON文件下面详细描述了这些文件的结构。
##### `users.json` - 用户账户数据
存储系统中所有用户的账户信息,包括认证信息和角色权限。
| 字段名 | JSON数据类型 | 约束/说明 | 描述 |
| --------------------- | ----------------- | ------------------------ | ---------------------------------------- |
| `id` | `Number` | **唯一标识**, 自增 | 用户的唯一标识符 |
| `name` | `String` | 非空 | 用户姓名 |
| `phone` | `String` | 非空, **唯一** | 手机号码,可用于登录 |
| `email` | `String` | 非空, **唯一** | 电子邮箱,可用于登录 |
| `password` | `String` | 非空, 长度>=8 | 加密后的用户密码 |
| `gender` | `String` | (ENUM) | 性别 (MALE, FEMALE, OTHER) |
| `role` | `String` | (ENUM) | 用户角色 (ADMIN, SUPERVISOR, GRID_WORKER等) |
| `status` | `String` | 非空, (ENUM) | 账户状态 (ACTIVE, INACTIVE, SUSPENDED) |
| `grid_x` | `Number` | | 关联的网格X坐标 (主要用于网格员) |
| `grid_y` | `Number` | | 关联的网格Y坐标 (主要用于网格员) |
| `region` | `String` | | 所属区域或地区 |
| `level` | `String` | (ENUM) | 用户等级 (JUNIOR, SENIOR, EXPERT) |
| `skills` | `Array` | | 技能列表 (JSON数组格式的字符串) |
| `enabled` | `Boolean` | 非空, 默认 `true` | 账户是否启用 |
| `current_latitude` | `Number` | | 当前纬度坐标 (用于实时定位) |
| `current_longitude` | `Number` | | 当前经度坐标 (用于实时定位) |
| `failed_login_attempts` | `Number` | 默认 `0` | 连续失败登录次数 |
| `lockout_end_time` | `String` | | 账户锁定截止时间 |
| `created_at` | `String` | 非空 | 记录创建时间 |
| `updated_at` | `String` | 非空 | 记录最后更新时间 |
##### `feedback.json` - 环境问题反馈数据
存储用户提交的所有环境问题反馈信息。
| 字段名 | JSON数据类型 | 约束/说明 | 描述 |
| ---------------- | --------------- | ------------------------ | ---------------------------------------- |
| `id` | `Number` | **唯一标识**, 自增 | 反馈的唯一标识符 |
| `event_id` | `String` | 非空, **唯一** | 人类可读的事件ID |
| `title` | `String` | 非空 | 反馈标题 |
| `description` | `String` | | 问题详细描述 |
| `pollution_type` | `String` | 非空, (ENUM) | 污染类型 (AIR, WATER, SOIL, NOISE) |
| `severity_level` | `String` | 非空, (ENUM) | 严重程度 (LOW, MEDIUM, HIGH, CRITICAL) |
| `status` | `String` | 非空, (ENUM) | 反馈状态 (PENDING_REVIEW, PROCESSED等) |
| `text_address` | `String` | | 文字描述的地址 |
| `grid_x` | `Number` | | 事发地网格X坐标 |
| `grid_y` | `Number` | | 事发地网格Y坐标 |
| `latitude` | `Number` | | 事发地纬度 |
| `longitude` | `Number` | | 事发地经度 |
| `submitter_id` | `Number` | 外键 (FK) -> users.id (可为空) | 提交者ID (公众提交时可为空) |
| `attachments` | `Array` | | 附件列表 (包含文件路径等信息) |
| `created_at` | `String` | 非空 | 记录创建时间 |
| `updated_at` | `String` | 非空 | 记录最后更新时间 |
##### `tasks.json` - 任务数据
存储系统中所有工作任务的信息。
| 字段名 | JSON数据类型 | 约束/说明 | 描述 |
| ---------------- | --------------- | ------------------------ | ---------------------------------------- |
| `id` | `Number` | **唯一标识**, 自增 | 任务的唯一标识符 |
| `feedback_id` | `Number` | 外键 (FK) -> feedback.id (可为空) | 关联的原始反馈ID |
| `assignee_id` | `Number` | 外键 (FK) -> users.id (可为空) | 任务执行人网格员ID |
| `created_by` | `Number` | 外键 (FK) -> users.id | 任务创建人主管ID |
| `status` | `String` | 非空, (ENUM) | 任务状态 (CREATED, ASSIGNED, IN_PROGRESS等) |
| `title` | `String` | | 任务标题 |
| `description` | `String` | | 任务详细描述 |
| `pollution_type` | `String` | (ENUM) | 污染类型 |
| `severity_level` | `String` | (ENUM) | 严重程度 |
| `text_address` | `String` | | 任务地点文字描述 |
| `grid_x` | `Number` | | 任务地点网格X坐标 |
| `grid_y` | `Number` | | 任务地点网格Y坐标 |
| `latitude` | `Number` | | 任务地点纬度 |
| `longitude` | `Number` | | 任务地点经度 |
| `assigned_at` | `String` | | 任务分配时间 |
| `completed_at` | `String` | | 任务完成时间 |
| `created_at` | `String` | 非空 | 记录创建时间 |
| `updated_at` | `String` | 非空 | 记录最后更新时间 |
| `deadline` | `String` | | 任务截止日期 |
| `history` | `Array` | | 任务状态历史记录 |
| `submissions` | `Array` | | 任务提交记录 |
##### `grids.json` - 业务网格数据
存储系统中定义的地理网格信息。
| 字段名 | JSON数据类型 | 约束/说明 | 描述 |
| --------------- | --------------- | ------------------- | ---------------------------------- |
| `id` | `Number` | **唯一标识**, 自增 | 网格的唯一标识符 |
| `grid_x` | `Number` | | 网格X坐标 |
| `grid_y` | `Number` | | 网格Y坐标 |
| `city_name` | `String` | | 所属城市 |
| `district_name` | `String` | | 所属区县 |
| `description` | `String` | | 网格描述信息 |
| `is_obstacle` | `Boolean` | 默认 `false` | 是否为障碍物(如禁区) |
| `created_at` | `String` | 非空 | 记录创建时间 |
| `updated_at` | `String` | 非空 | 记录最后更新时间 |
##### `assignments.json` - 任务分配记录数据
存储任务分配的详细记录。
| 字段名 | JSON数据类型 | 约束/说明 | 描述 |
| ---------------- | --------------- | ------------------------ | -------------------------- |
| `id` | `Number` | **唯一标识**, 自增 | 分配记录的唯一标识符 |
| `task_id` | `Number` | 非空, 外键 (FK) -> tasks.id | 关联的任务ID |
| `assigner_id` | `Number` | 非空, 外键 (FK) -> users.id | 分配者主管ID |
| `status` | `String` | 非空, (ENUM) | 分配状态 |
| `remarks` | `String` | | 分配备注 |
| `assignment_time`| `String` | 非空 | 分配时间 |
| `deadline` | `String` | | 任务截止日期 |
| `created_at` | `String` | 非空 | 记录创建时间 |
| `updated_at` | `String` | 非空 | 记录最后更新时间 |