From 3c0aa5969186af0412cef9531f4f5a8270ce126b Mon Sep 17 00:00:00 2001 From: sansenhoshi Date: Tue, 30 Jun 2026 18:21:30 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=BC=98=E5=8C=96=E5=9C=B0=E5=8C=BAcsv?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E6=9C=BA=E5=88=B6=202.=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E7=9B=91=E6=8E=A7=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E9=97=AE=E9=A2=98=203.=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E7=99=BB=E5=BD=95=E7=8A=B6=E6=80=81=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=204.=E9=92=88=E5=AF=B9session=E5=AD=98=E5=82=A8=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=E5=81=9A=E9=80=82=E9=85=8D=205.=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E6=9C=BA=E5=88=B6=E4=BC=98=E5=8C=96=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E8=A1=8C=E6=89=AB=E6=8F=8F=E9=80=9F=E5=BA=A6=E6=9B=B4?= =?UTF-8?q?=E5=BF=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filescan/FileWatchServiceManager.java | 29 +++++++- .../controller/WeatherStationController.java | 2 +- .../src/main/resources/application-dev.yml | 14 +--- .../src/main/resources/application-prod.yml | 1 + .../src/main/resources/application-test.yml | 5 +- weather-data-ui/.env.development | 2 +- .../src/composables/useRegionCache.ts | 59 ++++++++++++++++ .../src/composables/useWeatherChart.ts | 8 +++ weather-data-ui/src/store/index.ts | 2 +- weather-data-ui/src/utils/cache.ts | 2 +- weather-data-ui/src/views/login.vue | 2 +- weather-data-ui/src/views/region/region.vue | 56 ++++------------ .../station/weatherstation-add-or-update.vue | 67 +++++++++---------- .../src/views/station/weatherstation.vue | 40 ++++------- .../src/views/weather/prediction.vue | 5 +- .../src/views/weather/realtime-monitoring.vue | 6 +- weather-data-ui/vite.config.ts | 8 ++- 17 files changed, 175 insertions(+), 133 deletions(-) create mode 100644 weather-data-ui/src/composables/useRegionCache.ts diff --git a/system-admin/src/main/java/com/weather/modules/weather/filescan/FileWatchServiceManager.java b/system-admin/src/main/java/com/weather/modules/weather/filescan/FileWatchServiceManager.java index 48248e4..a210d99 100644 --- a/system-admin/src/main/java/com/weather/modules/weather/filescan/FileWatchServiceManager.java +++ b/system-admin/src/main/java/com/weather/modules/weather/filescan/FileWatchServiceManager.java @@ -22,6 +22,8 @@ import java.nio.channels.FileChannel; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import static java.nio.file.StandardWatchEventKinds.*; @@ -50,6 +52,9 @@ public class FileWatchServiceManager { private String rootReceiveDir; private volatile boolean running = false; + /** 文件处理线程池,用于并发 MD5 计算 + 入库 */ + private final ExecutorService fileProcessExecutor = Executors.newFixedThreadPool(4); + @PostConstruct public void init() { try { @@ -72,6 +77,7 @@ public class FileWatchServiceManager { log.warn("WatchService 关闭异常", e); } } + fileProcessExecutor.shutdown(); } /** 确保文件扫描目录结构存在(receive/display/archive 及各部门子目录) */ @@ -188,7 +194,8 @@ public class FileWatchServiceManager { /** 扫描部门目录下的所有文件 */ private void scanDirectory(Path deptDir, Long deptId) { try (var fileStream = Files.list(deptDir)) { - fileStream.filter(Files::isRegularFile).forEach(file -> processFile(file, deptId)); + fileStream.filter(Files::isRegularFile).forEach(file -> + fileProcessExecutor.submit(() -> processFile(file, deptId))); } catch (IOException e) { log.error("扫描目录失败: {}", deptDir, e); } @@ -212,7 +219,7 @@ public class FileWatchServiceManager { Long deptId = deptDirMap.get(watchDir.toString()); // deptDirMap 中没有的是根目录,deptId 为 null - processFile(fullPath, deptId); + fileProcessExecutor.submit(() -> processFile(fullPath, deptId)); } if (!key.reset()) { @@ -227,8 +234,24 @@ public class FileWatchServiceManager { } } - /** 等待文件写入完成并可读 */ + /** + * 等待文件写入完成并可读。 + * 先快速路径尝试获取共享锁(已有文件通常立即可用), + * 若被占用再进入大小稳定 + 等待循环。 + */ private boolean waitForFileReady(Path filePath) { + if (!Files.exists(filePath)) return false; + + // 快速路径:已写完的文件可立即获得共享锁,直接返回 + try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "rw"); + FileChannel ch = raf.getChannel()) { + java.nio.channels.FileLock lock = ch.lock(0, Long.MAX_VALUE, true); + lock.release(); + return true; + } catch (Exception ignored) { + // 文件正在被写入,进入等待逻辑 + } + long lastSize = -1; int stableCount = 0; int maxRetries = 20; diff --git a/system-admin/src/main/java/com/weather/modules/weather/station/controller/WeatherStationController.java b/system-admin/src/main/java/com/weather/modules/weather/station/controller/WeatherStationController.java index 8f80485..452983f 100644 --- a/system-admin/src/main/java/com/weather/modules/weather/station/controller/WeatherStationController.java +++ b/system-admin/src/main/java/com/weather/modules/weather/station/controller/WeatherStationController.java @@ -39,7 +39,7 @@ import java.util.Map; */ @RestController @RequestMapping("station/weatherstation") -@Tag(name = "站点") +@Tag(name = "气象站点") public class WeatherStationController { @Resource diff --git a/system-admin/src/main/resources/application-dev.yml b/system-admin/src/main/resources/application-dev.yml index 0cfad29..517b080 100644 --- a/system-admin/src/main/resources/application-dev.yml +++ b/system-admin/src/main/resources/application-dev.yml @@ -33,19 +33,11 @@ spring: test-on-borrow: false test-on-return: false stat-view-servlet: + allow: localhost enabled: true url-pattern: /druid/* - #login-username: admin - #login-password: admin - #达梦数据库,需要注释掉,其他数据库可以打开 -# filter: -# stat: -# log-slow-sql: true -# slow-sql-millis: 1000 -# merge-sql: false -# wall: -# config: -# multi-statement-allow: true +# login-username: admin +# login-password: admin # 是否开启redis缓存 true开启 false关闭 project-options: diff --git a/system-admin/src/main/resources/application-prod.yml b/system-admin/src/main/resources/application-prod.yml index 05fffe2..5471965 100644 --- a/system-admin/src/main/resources/application-prod.yml +++ b/system-admin/src/main/resources/application-prod.yml @@ -33,6 +33,7 @@ spring: test-on-borrow: false test-on-return: false stat-view-servlet: + allow: localhost enabled: true url-pattern: /druid/* #login-username: admin diff --git a/system-admin/src/main/resources/application-test.yml b/system-admin/src/main/resources/application-test.yml index 0f233d0..171e50f 100644 --- a/system-admin/src/main/resources/application-test.yml +++ b/system-admin/src/main/resources/application-test.yml @@ -33,10 +33,11 @@ spring: test-on-borrow: false test-on-return: false stat-view-servlet: + allow: 0.0.0.0 enabled: true url-pattern: /druid/* - #login-username: admin - #login-password: admin +# login-username: admin +# login-password: admin filter: stat: log-slow-sql: true diff --git a/weather-data-ui/.env.development b/weather-data-ui/.env.development index 3a4d50e..d073d66 100644 --- a/weather-data-ui/.env.development +++ b/weather-data-ui/.env.development @@ -1,3 +1,3 @@ NODE_ENV=development -VITE_APP_API=http://192.168.2.151:48080/system-admin +VITE_APP_API=/system-admin diff --git a/weather-data-ui/src/composables/useRegionCache.ts b/weather-data-ui/src/composables/useRegionCache.ts new file mode 100644 index 0000000..e25f8dd --- /dev/null +++ b/weather-data-ui/src/composables/useRegionCache.ts @@ -0,0 +1,59 @@ +/** + * 地区数据模块级单例缓存。 + * 首次调用时 fetch /region.csv 并构建 idMap 和 parentMap, + * 后续调用直接返回缓存,SPA 生命周期内只加载一次。 + */ + +interface RegionItem { + id: number; + parentId: number; + level: number; + name: string; +} + +interface RegionCache { + /** id → RegionItem,用于表格列的 id→name 转换 */ + idMap: Map; + /** parentId → children[],用于省/市/区下拉联动 O(1) 查找 */ + parentMap: Map; + /** 所有地区平铺数组 */ + list: RegionItem[]; +} + +let _cache: RegionCache | null = null; + +function parseCSV(text: string): RegionItem[] { + const lines = text.split("\n").filter((l) => l.trim()); + const headers = lines[0].split(",").map((h) => h.trim()); + return lines.slice(1).map((line) => { + const cols = line.split(",").map((c) => c.trim()); + const obj: any = {}; + headers.forEach((h, i) => { + obj[h] = h === "name" ? cols[i] : Number(cols[i]); + }); + return obj as RegionItem; + }); +} + +export async function useRegionCache(): Promise { + if (_cache) return _cache; + + const res = await fetch("/region.csv"); + if (!res.ok) throw new Error("地区数据加载失败"); + const text = await res.text(); + const list = parseCSV(text); + + const idMap = new Map(); + const parentMap = new Map(); + + for (const item of list) { + idMap.set(item.id, item); + if (!parentMap.has(item.parentId)) { + parentMap.set(item.parentId, []); + } + parentMap.get(item.parentId)!.push(item); + } + + _cache = { idMap, parentMap, list }; + return _cache; +} diff --git a/weather-data-ui/src/composables/useWeatherChart.ts b/weather-data-ui/src/composables/useWeatherChart.ts index ab6766e..7c2c546 100644 --- a/weather-data-ui/src/composables/useWeatherChart.ts +++ b/weather-data-ui/src/composables/useWeatherChart.ts @@ -91,6 +91,14 @@ export function useWeatherChart( async function renderChart() { if (!chartRef.value) return; + // 确保 DOM 已渲染且有尺寸,避免 ECharts "invalid dom" 报错 + const { width, height } = chartRef.value.getBoundingClientRect(); + if (width === 0 || height === 0) { + // DOM 未就绪,延迟重试 + scheduleRender(); + return; + } + // 动态按需加载 ECharts const echarts = await import("echarts"); const mod = (echarts as any).default || echarts; diff --git a/weather-data-ui/src/store/index.ts b/weather-data-ui/src/store/index.ts index 8f9e50f..4381951 100644 --- a/weather-data-ui/src/store/index.ts +++ b/weather-data-ui/src/store/index.ts @@ -67,7 +67,7 @@ export const useAppStore = defineStore("useAppStore", { }, //退出 logout() { - removeCache(CacheToken, true); + removeCache(CacheToken); this.updateState({ appIsLogin: false, permissions: [], diff --git a/weather-data-ui/src/utils/cache.ts b/weather-data-ui/src/utils/cache.ts index 3a51355..103939f 100644 --- a/weather-data-ui/src/utils/cache.ts +++ b/weather-data-ui/src/utils/cache.ts @@ -67,5 +67,5 @@ export const removeCache = (key: string, isSessionStorage?: boolean): void => { }; export const getToken = (): string => { - return getCache(CacheToken, { isSessionStorage: true }, {})["token"]; + return getCache(CacheToken, {}, {})["token"]; }; diff --git a/weather-data-ui/src/views/login.vue b/weather-data-ui/src/views/login.vue index 5b8189b..0e281fd 100644 --- a/weather-data-ui/src/views/login.vue +++ b/weather-data-ui/src/views/login.vue @@ -98,7 +98,7 @@ const onLogin = () => { .then((res) => { state.loading = false; if (res.code === 0) { - setCache(CacheToken, res.data, true); + setCache(CacheToken, res.data); ElMessage.success("登录成功"); router.push("/"); } else { diff --git a/weather-data-ui/src/views/region/region.vue b/weather-data-ui/src/views/region/region.vue index b9a323b..2ab62aa 100644 --- a/weather-data-ui/src/views/region/region.vue +++ b/weather-data-ui/src/views/region/region.vue @@ -28,6 +28,7 @@ diff --git a/weather-data-ui/src/views/weather/prediction.vue b/weather-data-ui/src/views/weather/prediction.vue index 44c015a..167c89d 100644 --- a/weather-data-ui/src/views/weather/prediction.vue +++ b/weather-data-ui/src/views/weather/prediction.vue @@ -74,9 +74,8 @@