386 lines
21 KiB
Markdown
386 lines
21 KiB
Markdown
### 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` | 非空 | 记录最后更新时间 | |