217 lines
7.9 KiB
Markdown
217 lines
7.9 KiB
Markdown
# 3.3 系统实现
|
||
|
||
## 3.3.1 开发环境与技术栈
|
||
|
||
在本项目的开发过程中,我采用了现代化、高效且广泛应用于企业级开发的技术栈组合,确保系统的稳定性、可扩展性和维护性。
|
||
|
||
### 后端技术栈
|
||
|
||
- **编程语言**: Java 17
|
||
- **框架**: Spring Boot 3.1.5
|
||
- **API文档**: Springdoc OpenAPI (Swagger)
|
||
- **安全框架**: Spring Security + JWT
|
||
- **构建工具**: Maven 3.9
|
||
- **数据存储**: 自定义JSON文件存储(模拟数据库)
|
||
- **异步处理**: Spring Events
|
||
- **日志框架**: SLF4J + Logback
|
||
- **单元测试**: JUnit 5 + Mockito
|
||
|
||
### 前端技术栈
|
||
|
||
- **框架**: Vue 3 (Composition API)
|
||
- **构建工具**: Vite
|
||
- **UI组件库**: Element Plus
|
||
- **状态管理**: Pinia
|
||
- **路由**: Vue Router
|
||
- **HTTP客户端**: Axios
|
||
- **CSS预处理器**: SCSS
|
||
- **图表库**: ECharts
|
||
|
||
### 开发工具
|
||
|
||
- **IDE**: IntelliJ IDEA / VS Code
|
||
- **版本控制**: Git
|
||
- **API测试**: Postman
|
||
- **代码质量**: SonarLint
|
||
- **调试工具**: Chrome DevTools
|
||
|
||
## 3.3.2 核心功能模块实现
|
||
|
||
在系统设计阶段,我们已经详细规划了各个功能模块。在实现阶段,我负责了多个核心模块的编码工作,下面将详细介绍这些模块的实现细节。
|
||
|
||
### 1. 泛型JSON存储服务
|
||
|
||
在本项目中,我设计并实现了一个创新的数据持久化解决方案,使用JSON文件代替传统数据库进行数据存储。这种方案简化了系统部署,同时提供了类似于JPA的操作接口,使得业务层代码无需关心底层存储细节。
|
||
|
||
**关键代码展示**:
|
||
|
||
```java
|
||
@Service
|
||
public class JsonStorageService {
|
||
|
||
private static final Path STORAGE_DIRECTORY = Paths.get("json-db");
|
||
private final ObjectMapper objectMapper;
|
||
|
||
public JsonStorageService(ObjectMapper objectMapper) {
|
||
this.objectMapper = objectMapper;
|
||
try {
|
||
if (!Files.exists(STORAGE_DIRECTORY)) {
|
||
Files.createDirectories(STORAGE_DIRECTORY);
|
||
}
|
||
} catch (IOException e) {
|
||
throw new UncheckedIOException("无法创建存储目录", e);
|
||
}
|
||
}
|
||
|
||
// 同步写数据方法,保证线程安全
|
||
public synchronized <T> void writeData(String fileName, List<T> data) {
|
||
try {
|
||
Path filePath = STORAGE_DIRECTORY.resolve(fileName);
|
||
// 使用writeValueAsString先转为格式化的JSON字符串,再写入文件,提高可读性
|
||
String jsonContent = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(data);
|
||
Files.write(filePath, jsonContent.getBytes(StandardCharsets.UTF_8));
|
||
} catch (IOException e) {
|
||
throw new UncheckedIOException("写入数据到 " + fileName + " 时出错", e);
|
||
}
|
||
}
|
||
|
||
// 读数据方法
|
||
public <T> List<T> readData(String fileName, TypeReference<List<T>> typeReference) {
|
||
Path filePath = STORAGE_DIRECTORY.resolve(fileName);
|
||
if (!Files.exists(filePath)) {
|
||
return new ArrayList<>(); // 文件不存在是正常情况,返回空列表
|
||
}
|
||
try {
|
||
// 使用TypeReference来处理泛型擦除问题,确保JSON能被正确反序列化为List<T>
|
||
return objectMapper.readValue(filePath.toFile(), typeReference);
|
||
} catch (IOException e) {
|
||
log.warn("无法读取或解析文件: {}. 返回空列表。", fileName, e);
|
||
return new ArrayList<>();
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**实现要点分析**:
|
||
|
||
1. **泛型设计**: 通过Java泛型和TypeReference,实现了类型安全的数据读写,支持任意模型类。
|
||
2. **线程安全**: 使用synchronized关键字确保写操作的线程安全,防止并发写入导致的数据损坏。
|
||
3. **容错处理**: 对文件不存在、IO异常等情况进行了妥善处理,增强了系统的健壮性。
|
||
4. **格式化输出**: 使用Jackson的prettyPrinter功能,使生成的JSON文件具有良好的可读性,便于调试。
|
||
|
||
**程序流程图**:
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[开始] --> B{文件存在?}
|
||
B -->|是| C[读取文件内容]
|
||
B -->|否| D[返回空列表]
|
||
C --> E{解析JSON成功?}
|
||
E -->|是| F[返回对象列表]
|
||
E -->|否| G[记录警告日志]
|
||
G --> D
|
||
```
|
||
|
||
### 2. A*寻路算法实现
|
||
|
||
在网格与地图模块中,我实现了A*寻路算法,为网格员提供从当前位置到任务地点的最优路径规划。该算法考虑了地图障碍物,能够高效地找到最短路径。
|
||
|
||
**关键代码展示**:
|
||
|
||
```java
|
||
@Service
|
||
@RequiredArgsConstructor
|
||
public class AStarService {
|
||
|
||
private final MapGridRepository mapGridRepository;
|
||
|
||
// 内部Node类,用于A*算法的节点表示
|
||
private static class Node {
|
||
Point point;
|
||
int g; // 从起点到当前节点的实际代价
|
||
int h; // 启发式:从当前节点到终点的估计代价
|
||
int f; // g + h
|
||
Node parent;
|
||
|
||
public Node(Point point, Node parent, int g, int h) {
|
||
this.point = point;
|
||
this.parent = parent;
|
||
this.g = g;
|
||
this.h = h;
|
||
this.f = g + h;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 寻找两点间的最短路径,避开障碍物
|
||
* 地图数据(包括尺寸和障碍物)从数据库动态加载
|
||
*/
|
||
public List<Point> findPath(Point start, Point end) {
|
||
// 从数据库加载地图数据
|
||
List<MapGrid> mapGrids = mapGridRepository.findAll();
|
||
if (mapGrids.isEmpty()) {
|
||
return Collections.emptyList(); // 无地图数据
|
||
}
|
||
|
||
// 动态确定地图尺寸和障碍物
|
||
int maxX = 0, maxY = 0;
|
||
Set<Point> obstacles = new HashSet<>();
|
||
for (MapGrid gridCell : mapGrids) {
|
||
if (gridCell.isObstacle() || gridCell.getCityName() == null || gridCell.getCityName().trim().isEmpty()) {
|
||
obstacles.add(new Point(gridCell.getX(), gridCell.getY()));
|
||
}
|
||
if (gridCell.getX() > maxX) maxX = gridCell.getX();
|
||
if (gridCell.getY() > maxY) maxY = gridCell.getY();
|
||
}
|
||
final int mapWidth = maxX + 1;
|
||
final int mapHeight = maxY + 1;
|
||
|
||
// A*算法核心实现
|
||
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);
|
||
|
||
while (!openSet.isEmpty()) {
|
||
Node currentNode = openSet.poll();
|
||
|
||
if (currentNode.point.equals(end)) {
|
||
return reconstructPath(currentNode);
|
||
}
|
||
|
||
for (Point neighborPoint : getNeighbors(currentNode.point, mapWidth, mapHeight)) {
|
||
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());
|
||
}
|
||
} |