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

7.9 KiB
Raw Blame History

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的操作接口使得业务层代码无需关心底层存储细节。

关键代码展示:

@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文件具有良好的可读性便于调试。

程序流程图:

flowchart TD
    A[开始] --> B{文件存在?}
    B -->|是| C[读取文件内容]
    B -->|否| D[返回空列表]
    C --> E{解析JSON成功?}
    E -->|是| F[返回对象列表]
    E -->|否| G[记录警告日志]
    G --> D

2. A*寻路算法实现

在网格与地图模块中我实现了A*寻路算法,为网格员提供从当前位置到任务地点的最优路径规划。该算法考虑了地图障碍物,能够高效地找到最短路径。

关键代码展示:

@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());
    }
}