21 KiB
2.4 算法设计
本节详细描述了系统中的核心算法设计,这些算法是系统智能化和高效运行的关键。
2.4.1 A* 寻路算法
A*寻路算法是系统中的核心算法之一,用于为网格员规划从当前位置到任务目标点的最优路径。该算法在网格与地图模块中发挥着重要作用。
算法目的: 为网格员提供从当前位置到任务地点的最优(最短或最快)路径,避开障碍物。
算法输入:
startNode: 起始点坐标 (x, y)。endNode: 目标点坐标 (x, y)。grid: 包含障碍物信息的地图网格数据。
核心逻辑:
- 维护一个开放列表(
openList)和一个关闭列表(closedList)。openList: 存储待探索的节点,使用优先队列实现,按F值排序。closedList: 存储已探索过的节点。
- 从
openList中选取F值(G值+H值)最小的节点作为当前节点。- G值: 从起点到当前节点的实际代价。
- H值: 从当前节点到终点的预估代价(启发函数)。
- F值: G值 + H值,表示经过当前节点到达终点的总代价估计。
- 遍历当前节点的相邻节点(上、下、左、右四个方向)。
- 对于每个相邻节点:
- 如果是障碍物或已在
closedList中,则跳过。 - 如果不在
openList中,计算其G值、H值和F值,将其加入openList,并记录其父节点为当前节点。 - 如果已在
openList中,检查经由当前节点到达该相邻节点的路径是否更优(G值更小)。如果更优,则更新其G值、F值和父节点。
- 如果是障碍物或已在
- 将当前节点从
openList移除,加入closedList。 - 重复步骤2-5,直到:
- 找到目标节点(当前节点为终点)。
- 或
openList为空(无法找到路径)。
- 如果找到目标节点,通过回溯父节点构建从起点到终点的路径。
启发函数选择: 系统采用曼哈顿距离(Manhattan Distance)作为启发函数,计算公式为:
h(n) = |n.x - goal.x| + |n.y - goal.y|
这种启发函数适合网格化的移动模式,只允许上、下、左、右四个方向的移动。
算法实现:
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: 可用的网格员列表。
核心逻辑: 这是一个复合策略算法,综合考虑多个因素,通过加权评分机制选择最合适的网格员:
-
地理距离评分:
- 计算每个网格员当前位置到任务位置的距离。
- 距离越近,评分越高。
- 使用公式:
distanceScore = maxDistance - distance,其中maxDistance是一个预设的最大考虑距离。
-
当前负载评分:
- 评估每个网格员当前正在处理的任务数量。
- 任务数量越少,评分越高。
- 使用公式:
loadScore = maxTasks - currentTasks,其中maxTasks是一个预设的最大任务数。
-
专业匹配度评分:
- 根据任务类型和网格员的专业技能进行匹配。
- 匹配度越高,评分越高。
- 使用公式:
skillScore = matchedSkills / requiredSkills.size()。
-
历史表现评分:
- 考虑网格员的历史完成率和质量评分。
- 表现越好,评分越高。
- 使用公式:
performanceScore = (completionRate * 0.7) + (qualityRating * 0.3)。
-
综合评分计算:
- 对上述各项评分进行加权求和。
- 使用公式:
totalScore = (distanceScore * w1) + (loadScore * w2) + (skillScore * w3) + (performanceScore * w4)。 - 其中w1、w2、w3、w4是各项评分的权重,且满足
w1 + w2 + w3 + w4 = 1。
-
选择最高评分的网格员:
- 根据综合评分对网格员进行排序。
- 选择评分最高的网格员作为推荐人选。
算法实现:
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文件存储架构
系统的数据持久化层基于以下架构:
-
核心存储服务:
JsonStorageService: 提供对JSON文件的读写操作,包括序列化和反序列化。FileSystemService: 处理文件系统操作,如创建、读取、更新和删除文件。
-
Repository层:
- 为每种核心模型提供专门的Repository类,如
UserRepository、FeedbackRepository等。 - 这些Repository类模拟了类似JPA的接口,提供CRUD操作和查询功能。
- 内部使用
JsonStorageService进行实际的文件操作。
- 为每种核心模型提供专门的Repository类,如
-
并发控制:
- 使用文件锁机制确保在并发环境下的数据一致性。
- 实现了简单的乐观锁定策略,通过版本号检测冲突。
-
索引和查询优化:
- 在内存中维护索引结构,加速常见查询操作。
- 支持基于字段值的过滤和排序。
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 |
非空 | 记录最后更新时间 |