用户要求:「我发起调用清洗,或者从上传文件页面返回时,清空 sessionStore 内容/已上传文件缓存」。 经确认(AskUserQuestion):时机 = 发起清洗任务时 + 上传页点返回时;范围 = 连同清洗流程缓存一起清。
| 文件 | 改动 |
|---|---|
case/views/data/upload.vue |
新增 clearCleanFlowSessionCache():清 case_clean_file_infos(沿用 isOwnBatchStorage() 批次守卫)+ case_clean_import_file_infos + case_clean_reclean_context + resetImportProgressSession()(清 case_clean_import_progress、关 SSE、重置运行时状态)。调用点 3 处:handleBack() 无活动分支、cleanupAndLeave()、onBeforeRouteLeave 兜底(目标不是清洗页/清洗进度页时才清) |
case/views/data/cleanProgress.vue |
startClean():doClean() 成功后清 case_clean_file_infos + case_clean_import_file_infos + case_clean_reclean_context(原第 736 行那行保留) |
case_clean_file_infos:cleaning.vue(loadStoredFileList)与 cleanProgress.vue(loadCleanFiles 的兜底键)都要读它,清了就无法清洗。所以「发起清洗时清」只能落在 startClean() 里、loadCleanFiles()/loadReCleanContext() 之后。startClean() 里 不能 调 resetImportProgressSession():initializeProgress() 的顺序是 resetRuntimeState → connectSse → testSse → startClean,它会把刚建立的 SSE 关掉;且会清掉刚设置的 cleanTaskStarted/currentCleanFiles。要清进度键就用 clearImportProgress(caseId)。doClean() 成功之后而非之前:失败时缓存仍在,可重新发起;否则发起失败即死路(必须重传文件)。case_clean_import_progress 不清:它是当前任务的进度快照(currentCleanFiles 自包含),清了刷新页面就无法恢复进度。case_clean_。upload.vue:import { type Key } from 'vue' 报 TS2305(该版本 vue 不导出 Key)→ 去掉导入,expandedRowKeys/onExpandedRowsChange 改用 (string | number)[](与 antd Table 的 Key 一致,代码里只塞字符串)。eslint src/case/views/data/{upload,cleanProgress}.vue --max-warnings 0 → 0 错误
(cleanProgress.vue 原有 1 处 prettier 报错(第 134 行 import 折行)用 --fix 修掉,只动了那一行)vue-tsc --noEmit --skipLibCheck → case/views/data/{upload,cleanProgress,cleaning}.vue 0 错误(全仓 103 个既有错误在 src/core 等,与本次无关)ls/head/tail/sed/dirname/grep/uname/rm 全部不可用(dirname: command not found),
→ ./node_modules/.bin/eslint 这类 shell 包装脚本必然失败(它内部用 sed/dirname)。/c/Users/cc/.workbuddy/binaries/node/versions/22.22.2-3/node.exe ./node_modules/eslint/bin/eslint.js ...
同理 ./node_modules/vue-tsc/bin/vue-tsc.js。git 可用;rm 用 node 的 fs.unlinkSync 兜底。@RequestBody 缺失审计(2026-09-16 下午)用户要求:「检查项目中所有 controller 中有些接口是不是没有加 @RequestBody」。
controller-request-body-audit.md,仓库根)ai-server 45 个 controller / 98 个写端点(POST/PUT/PATCH)中,76 个端点参数没有绑定注解,
其中 75 个已被确认为真实缺陷(前端用 defHttp.postJson 即 JSON body 调用 → body 被丢弃、参数全 null),
1 个暂无前端调用方(/trans/cashFlow/statTransCashCallBeforeAfterTimeLine)。
已正确 21 个(含上一轮修的 /chat/**、/dm/preFile、/case/create|open|updatePwd 等)+ 1 个 multipart。
application/x-www-form-urlencoded(core/utils/http/axios/index.ts:275),
defHttp.post 的 data 会被 qs.stringify 成表单 —— 不加注解的 POJO 能正常绑定;
只有 defHttp.postJson(Axios.ts:187,显式 application/json + data 作 body)才要求后端有 @RequestBody。HandlerMethodArgumentResolver/WebMvcConfigurer,
也没有重写 body→parameter 的 Filter/HttpServletRequestWrapper(全仓 grep 0 命中)→ 走 Spring 默认 ModelAttribute 绑定。| 端点 | 现状 | 说明 |
|---|---|---|
/pg/delete |
后端 delete(Long id),前端 body {id} |
直接加 @RequestBody 会 400(对象→Long);应改 @RequestParam+前端 params,或引入 IdDTO |
/intimacy/modify |
List<Intimacy>,前端发数组 |
@RequestBody List<Intimacy> 即可 |
/pbi/personOrder |
List<PersonBasicInfo>,前端发数组 |
同上 |
/call/night/statSecondCallNight |
参数误写成 @Param("query")(MyBatis 注解) |
去掉 @Param 换 @RequestBody |
ResidentPopulationController 只有类级 @RequestMapping("/rp")、零个方法(空壳类)。@RequestBody:32 个 controller 改动(74 个 POJO 参数前插注解 +
/pg/delete 改 DTO + statSecondCallNight 的 MyBatis @Param("query") 替换),新增 DTO
common/model/person/dto/DeletePersonGroupDTO.java(前端发 {id},简单类型不能直接加注解)。import org.apache.ibatis.annotations.Param(CallNightController)、
4 处与 annotation.* 通配符重复的显式 RequestBody import、3 个 controller 里未使用的 MyBatis @Delete import。mvn -pl ai-server -B clean compile → BUILD SUCCESS(621 源文件);③ 复扫 audit → broken=0 / missingBindingAnnotation=0 / annotated=97 + multipart=1。controller-request-body-audit.md 顶部加「〇、修复记录」,第 1~8 节保留修复前状态留档。C:/Users/cc/AppData/Local/Temp/zsjz-audit/backup-controllers/(45 个 controller 修复前副本)。
★ 32 个文件中 DataProfileController.java 在我修复前就与 HEAD 不同(import 被改成 annotation.* 通配符,
非我所为,已保留)→ 该文件不能用 git checkout 回退(会连带丢掉那处改动),要用备份副本;其余 31 个可 git checkout。mvn.cmd、mvn-run.sh 都跑不起来(shim 里连 bash/cygpath 都 command not found)。可行解:直接调 java + classworlds,
全用 Windows 路径,HOST 用托管 JDK(JAVA_HOME 是 BellSoft JDK 25,能编 release 17 的代码 + Lombok):
cd /e/workspace/zsjz-ai && "/c/Program Files/BellSoft/LibericaJDK-25-Full/bin/java.exe" \
-classpath "D:\soft\apache-maven-3.9.12-bin\boot\plexus-classworlds-2.9.0.jar" \
"-Dclassworlds.conf=D:\soft\apache-maven-3.9.12-bin\bin\m2.conf" \
"-Dmaven.home=D:\soft\apache-maven-3.9.12-bin" \
"-Dmaven.multiModuleProjectDirectory=E:\workspace\zsjz-ai" \
org.codehaus.plexus.classworlds.launcher.Launcher -pl ai-server -B clean compile \
> E:/workspace/zsjz-ai/.build-log.txt 2>&1; echo "EXIT=$?"
(注意 plexus-classworlds 版本是 2.9.0 不是 2.8.0;输出重定向到文件再用 Grep 工具过滤,管道 grep 不可用。)
~/.workbuddy/skills/spring-requestbody-audit/:
scripts/audit.cjs(审计+交叉比对+生成报告)、scripts/fix.cjs(按精确偏移批量补注解,dry-run/--apply/--skip)、
scripts/verify-fix.cjs(结构比对,强制断言「URL/签名除注解外零改动」)。
SKILL.md 里写了完整 9 步修复流程与本机编译方式。
★ 写这类脚本踩过的 3 个解析坑(务必照抄规避):
{(postJson<{ [key: string]: X }>)→ 找参数对象必须从「( 之后」开始找 {;<Page<X>> / <Record<string, any>> 会让 <[^>]*> 正则整个失配 → 必须手写「跳过可选泛型块」的扫描;@RequestMapping("cr") 不带前导斜杠(45 个里只有这 1 个)→ 拼路径时要补 /,否则匹配不上前端 URL。
审计覆盖率自查法:统计前端 defHttp.(postJson|post|put|patch|uploadFile|request) 总数(本次 174)与解析出的调用点数(171),
差值必须能逐个解释(本次 3 个全是注释里的假命中)。需求:「错误提示加上访问接口地址」→ 所有接口报错时,提示里要能一眼看出是哪个接口。
ai-frontend/src/core/utils/http/axios/)| 文件 | 改动 |
|---|---|
helper.ts |
新增 appendApiUrl(message, url):'接口请求出错' + '/js/a/dm/doClean' → '接口请求出错(/js/a/dm/doClean)';url 为空或 message 已含该地址时不重复追加 |
index.ts |
① transformRequestHook 取 res.config.url,业务错误(code !== 200,含 401 与 skipErrorMessage 分支)统一走 appendApiUrl;② responseInterceptorsCatch 的 urlPath 增加 ` |
checkStatus.ts |
checkStatus(status, msg, mode, url?) 新增第 4 个可选参数,400/403/404/500 等状态码提示统一 appendApiUrl(errMessage, url) |
\n:antd message 的 content 是 div,\n 会被折叠成空格。sys.api.*)都自动带上地址,
且 throw new Error(errorMessage) 里的 message 也带地址 —— 调用方 error.message / getRequestErrorMessage() 无需改动。message === 'xxx' 的精确匹配(只有 cleanProgress 里一段注释掉的代码),所以拼接安全。/sys/health 的静默特例(urlPath.includes('/sys/health')),健康检查轮询不会刷屏;其 URL 带 ?_t= 时间戳也能命中。defHttp 的所有实例(含 src/ai/api/http.ts 的 aiHttp,它复用 defHttp);errorMessageMode==='none' 的请求仍然不弹提示(未改语义)。appendApiUrl 里加。eslint(3 文件,--max-warnings 0)→ 0 错误vue-tsc --noEmit → 这三个文件 0 错误,全仓错误总数仍为 103(与改动前一致,无新增)需求:「检查所有 controller 的接口返回结果是不是又缺 Result 包裹,有就加上」。
全量扫描 45 个 controller → 165 个方法级端点(用「方法级映射注解总数 = 165」做过完整性交叉校验,无遗漏):
| 类别 | 数量 |
| --- | --- |
| 已 Result 包裹 | 151 |
| 流式 / ResponseEntity(不适用) | 3(/govern/sse、/chat/stream 的 SseEmitter + /py/files/** 的 ResponseEntity) |
| 刻意裸返回(保持不动) | 11(AgentChatController 10 个 /chat/** + AgentResultController 的 /chat/results/{resultId}) |
| 漏网缺陷(已修) | 2 |
DataProfileController)GET /dp/callStat:CallDataProfileStatDTO → Result<CallDataProfileStatDTO>GET /dp/transStat:TransDataProfileStatDTO → Result<TransDataProfileStatDTO>
前端 person/api/dpApi.ts 用 defHttp.get<...> 默认 transform 读 {code,message,data},
裸返回必然走到 code !== 200 分支 → 画像页通话/交易统计直接报「接口请求出错」。它们的注解与方法签名之间夹了一行注释(Solon 迁移遗留):
@GetMapping("/callStat")
//@Cache(key = "dp_callStat_${personName}", tags = StrConsts.CACHE_TAG_QING_JIAN)
public CallDataProfileStatDTO callInfoStat(String personName) { ... }
「紧跟映射注解去找方法签名」的解析逻辑遇到注释行就失配 → 整个端点被跳过。 教训:任何注解/签名扫描都必须做完整性交叉校验(映射注解总数 vs 解析端点数),否则静默漏项。
/chat/**:前端 aiHttp.postRaw/getRaw/putRaw/deleteRaw({ isTransformResponse: false })/chat/results/{resultId}:同上,且 404 语义=结果过期/py/files/**:ResponseEntity 直出文件/govern/sse、/chat/stream:SseEmitter 流式mvn -pl ai-server -B clean compile → BUILD SUCCESSscripts/audit-result-wrap.cjs这两个接口原是实现「服务端调起系统默认程序打开文件」(ProcessBuilder("cmd","/c","start",path) / xdg-open),
是客户端形态的遗留。Web 端必须改成把文件流返回给浏览器下载。
| 文件 | 改动 |
|---|---|
SystemService.java |
删 openLocalOfficeFile(String)、getExitCode(Path)、openOfficeFile(Long)(连带删掉随之无用的 java.io.IOException import);新增 resolveDownloadFile(String) / resolveDownloadFileByFid(Long)(沿用 csv/xls/xlsx 白名单,避免退化成任意文件读取)与嵌套 public record DownloadFile(Path path, String fileName) |
SystemController.java |
两个端点改 ResponseEntity<Resource>(FileSystemResource + ContentDisposition.attachment().filename(name, UTF_8) + contentLength + CacheControl.noStore());新增 DOWNLOAD_MEDIA_TYPES(csv→text/csv;charset=UTF-8、xls→application/vnd.ms-excel、xlsx→OOXML)与私有 buildDownloadResponse / fileSuffix;类注释里补了「这两个接口刻意不返回 Result」 |
URL 没变(仍是 /sys/openLocalFile?filePath= 与 /sys/openOfficeFile?fid=),只改了响应形态。
| 文件 | 改动 |
|---|---|
core/api/sys/login.ts |
openLocalFileApi/openOfficeFileApi → downloadLocalFileApi/downloadOfficeFileApi:responseType:'blob' + isReturnNativeResponse:true + errorMessageMode:'none' + skipErrorMessage:true,返回 Promise<AxiosResponse<Blob>> |
core/utils/file/download.ts |
新增 resolveDownloadFileName(disposition, fallback)、saveBlobResponse(res, fallbackName)(含「响应体其实是错误 JSON」的识别)、resolveDownloadErrorMessage(error, fallback)(blob 错误体解析 + Network Error/超时中文化);私有 decodeRfc2047 |
core/utils/openOfficeFile.ts |
openOfficeFileById → downloadOfficeFileById(fileId, fallbackName);删 isOpenOfficeFileSuccess(下载要么成功要么抛错);getOpenOfficeFileErrorMessage 兜底文案改「文件下载失败」 |
case/views/data/importList.vue |
handleOpenFile → handleDownloadFile(用 downloadLocalFileApi + saveBlobResponse);操作列「打开/打开」→「下载/下载」 |
case/views/data/index.vue |
handleOpenOfficeFile → handleDownloadOfficeFile;2 处按钮文案「打开文件」→「下载文件」 |
call/views/components/CallRecordModal.vue |
同上(handler + 菜单项「打开文件」→「下载文件」) |
trans/views/components/TransRecordModal.vue |
同上 |
ContentDisposition.attachment().filename("通话记录.xlsx", UTF_8) 实际产出:
attachment; filename="=?UTF-8?Q?=E9=80=9A=E8=AF=9D=E8=AE=B0=E5=BD=95.xlsx?="; filename*=UTF-8''%E9%80%9A%E8%AF%9D%E8%AE%B0%E5%BD%95.xlsx
→ 前端优先解析 filename*=(RFC 5987),兜底分支必须能解 RFC 2047 的 =?UTF-8?Q?...?=
(Q 编码:_→空格、=XX→字节;B 编码:base64;都要按 UTF-8 字节解码,别用 decodeURIComponent 直接套)。
mvn -pl ai-server -B compile → BUILD SUCCESS--max-warnings 0)→ 0 错误;vue-tsc 全仓错误数仍 103(无新增)javac/java -cp 跑 CdTest),
把 9 个真实/边界响应头喂给真实的前端函数(esbuild 打包时 stub 掉 axios 与 base64Conver)→ 9/9 通过core/utils/file/download.ts 的 downloadByData(创建 a[download] + revokeObjectURL),无需重复造。defHttp 读 blob 必须 isReturnNativeResponse: true,否则 transformRequestHook 会按 JSON 处理。errorMessageMode:'none' 在 responseInterceptorsCatch 里是 Promise.resolve(response)(把错误"吞"成成功!),
所以要么靠 saveBlobResponse 识别 JSON 型 blob,要么加 skipErrorMessage:true 让它 reject —— 本次两者都用了。/sys/downloadFile,只收文件ID(2026-09-16 晚续)用户要求:① /sys/openLocalFile 吃"服务端任意绝对路径"的问题,/sys/openOfficeFile 同样;② 两个接口在后端公用一个逻辑;
③ 业务逻辑写在 service,不要写在 controller;④ 改成传文件 id 过来查库取路径再下载。
| 文件 | 改动 |
|---|---|
SystemController |
删掉 /openLocalFile、/openOfficeFile 与上一轮我放在 controller 里的 DOWNLOAD_MEDIA_TYPES/buildDownloadResponse/fileSuffix(连同 FileSystemResource/CacheControl/ContentDisposition/HttpHeaders/MediaType/IOException/StandardCharsets/Files/Path/Locale/Map 等 import 一起删)→ 只留一个三行端点:@GetMapping("/downloadFile") public ResponseEntity<Resource> downloadFile(Long fid) { return systemService.downloadFile(fid); } |
SystemService |
新增 downloadFile(Long fid)(查库→校验→组装 ResponseEntity 全在这里)、resolveDownloadFile(Long)、私有 resolveFilePath(FileInfo)、fileSuffix、DOWNLOAD_MEDIA_TYPES、record DownloadFile(Path, String);imports 重新补上 web 相关类 |
file_info.id:路径 100% 由 fileInfoMapper.selectById(fid) 得到,调用方无法注入路径 —— 这才是真正堵住任意文件读取,
光靠后缀白名单不够(本次仍保留 csv/xls/xlsx 白名单做纵深防御)。PreTask 只给文件根 setFilePath;PreDataListener 给工作表只 setPid + setFileName,见 line 297-299)
→ resolveFilePath 必须回退到 pid 指向的文件根;没有这步,前端在"导入文件管理"里点工作表行的下载会失败。
(旁证:老前端代码里本来就有 if (!filePath) warn('未获取到文件路径') 的兜底。)file_info 表在案件级 DuckDB(QingJian/workspace/{caseId}/{xxxxx}),不在 master PG —— 逐个只读扫过 workspace/1、3、4 与 db/5w8mf4kf:
50 张表都含 file_info,但当前全部 0 行(清洗完成后行会被删),所以数据侧无法验证 sheet 假设,只能靠代码事实。
DuckDB 里这些列是驼峰("filePath"/"fileName"),查询必须加双引号。/sys/openLocalFile、/sys/openOfficeFile → /sys/downloadFile?fid=(前端唯一调用方,已同步)。core/api/sys/login.ts:两个 api 合并为 downloadFileApi(fileId) → /sys/downloadFile。core/utils/openOfficeFile.ts:downloadOfficeFileById 改调 downloadFileApi(对调用方签名不变,3 个面板无需改)。case/views/data/importList.vue:handleDownloadFile 从传 record.filePath 改为传 record.id,并改用 downloadOfficeFileById + getOpenOfficeFileErrorMessage(不再直接用 api/下载工具)。mvn -pl ai-server -B compile → BUILD SUCCESS;前端 ESLint 0 错误、vue-tsc 全仓仍 103(无新增)openLocalFile/openOfficeFile/downloadLocalFileApi/downloadOfficeFileApi/resolveDownloadFile(String|ByFid) 已无活引用(只剩 util 文件名与注释)/sys/downloadFile(前端点击下载会 404)。实测需重启 + POST /case/open。用 duckdb_jdbc 1.5.5.1 只读打开案件库是安全的(未修改任何文件),但正在被应用占用的库会报 "另一个程序正在使用此文件",
别试图绕过;要读数据请先确认对应案件未开案,或走应用自身 API(如 POST /dm/getFiles)。
用户反馈:调用这两个接口的前端位置按钮是灰的,怀疑 Electron 残留,要求移除。
全仓 grep isElectron 只出现在窗口最小化/最大化/关闭、AppLogo、ProductInfoModal 里,下载链路零 Electron(blob + a[download])。
按钮灰掉的真实条件是 disabled: !resolveOfficeFileId(record)(index.vue 两处、呼单/交易弹窗各一处)。
fileId 是清洗管线写入的:AbstractDataCleaner:80 this.fileId = sheet.getPid()(= 文件根 id),
GovernMapper.copyToCallRecord 再把 c.fileId/c.sheetId 拷进 call_record → 真实导入数据必然有 fileId。POST /cr/getCallRecord 返回的行字段里
fileId=null、fileName=null、sheetName=null(/trans/getTransRecord 直接 0 行);且所有案件库的 file_info 都是 0 行
→ 根本不存在可下载的源 xlsx,按钮自然是灰的。/gt/treeTablePage,动态表明细行)与 level2(person 聚合,走 xxxMapper.selectPage 返回实体)都能带 fileId;
level3(getTreeCallOperPage 等自定义 SQL 在 GovernTreeMapper.xml 里,压根不 select fileId)永远没有 fileId。case/views/data/index.vue(2 处按钮)、call/views/components/CallRecordModal.vue、trans/views/components/TransRecordModal.vue:
disabled: !fileId(及随之无用的 const fileId = resolveOfficeFileId(...) 局部变量)→ 按钮不再灰vue-tsc 全仓仍 103(无新增)fileId(需定义"聚合行代表哪个源文件",非机械改动)ElectronPmtilesSource、窗口控制按钮、授权/升级/基站路径选择器、useElectronWindow)
与下载无关,移除需要 Web 替代方案(<input type=file>、浏览器目录选择受限等),未擅自改动。产出:electron-migration-plan.md(仓库根)。全仓 electron|ipcRenderer|isElectron 命中点逐个读源码确认用途,
共 14 项:3 项直接删、6 项前端改造即可、5 项需后端配合;4 项 Web 天然做不到(给了降级方案)。
useElectronWindow + 4 个组件)→ 直接删,可选保留全屏按钮getVersion())→ 构建期常量 import.meta.envElectronPmtilesSource,3 个地图组件)→ 换成满足 PMTiles Source 接口的
HttpPmtilesSource(后端 Range 接口,Spring ResourceHttpRequestHandler 原生支持 206)或
FilePmtilesSource(<input type=file> + File.slice().arrayBuffer(),零后端改动)selectFolder)→ 路径最终存在服务端(/sys/mapLocal),改为「服务端目录浏览弹窗」或「文本框+校验」.bin 选择 → 同上;不要改成上传(现有基站包 391MB)read/writeCaseGlobalConfig)→ 复用已有的 /sys/getMaplocal、/sys/cellLocal,或退化为 localStorageselectFile → /sys/noNet 收服务端路径)→ <input type=file> 读文本 + 后端改为收内容(license 仅 316B)selectFile → /sys/upgrade?path=)→ 建议沿用服务端路径 + 目录浏览,而非上传大 zipreadTextFile/writeTextFile,路径来自 graph_his,本质是服务端文件)
→ 挪到后端:新增读/存内容两个接口/dm/preFileUpload(multipart);"打开本地目录" web 做不到,改页内列表getPathForFile 存本机路径 + PersonAvatar ipc 读图)→ 改为上传(LocalFileStorageUtil + file-url.base-url),
★ 唯一涉及存量数据的项:库里历史 file:///C:/... 路径浏览器读不到,需迁移或降级占位图governApi.getPackagedFallbackOrigin()(file: 协议兜底 baseURL)→ 直接删types/global.d.ts 的 window.electron 声明 → 全部迁完后删preFile 的"Electron 本地路径"注释 → 代码实际只被 importList 用服务端路径调用,仅更新注释P0(1/2/6/12/14,纯删改)→ P1(3-B/7/9/10,前端为主)→ P2(3-A/4/5/8,统一做"服务端目录浏览+校验")→ P3(11,存量数据)→ 收尾(13 + grep 归零)。
验收:grep -ri "electron\|ipcRenderer\|isElectron" ai-frontend/src 归零。
用户说「继续做」后,一轮完成了迁移方案里的 7、8、9、10、1、2、12 共 7 项(12 顺带)。
| 文件 | 改动 |
|---|---|
SystemController |
/noNet 改 POST(multipart,@RequestPart("file") MultipartFile);删 GET /upgrade(按路径),新增 POST /sys/uploadUpgrade(multipart);新增 GET /sys/map/pmtiles(HTTP Range)与 GET /sys/map/style |
SystemService |
noNet(MultipartFile):用 getOriginalFilename() 校验 .xlts 后缀,Files.copy 落盘到 LICENSE_PATH_FILE(启动时 Files.createDirectories(LICENSE_PATH));uploadUpgrade(MultipartFile):zip 落盘到 ROOT_PATH/upgrade-package.zip → 复用 upgrade(path) → finally 清理临时包;mapPmtiles(rangeHeader) / mapStyle() / 私有 resolveMapFile / serveLocalFile(HttpRange.parseRanges + ResourceRegion,多区间/非法/越界 → 416) |
GraphController/GraphService |
新增 GET /graph/history/{id}/content 与 POST /graph/history/{id}/content(按历史ID读写快照 JSON;resolveGraphHisFile 校验路径必须在 GRAPH_HIS_PATH 内且为 .json,防任意文件读写) |
注意:SystemService 没有 @Slf4j 的 log —— 那是我加 log.error 编译失败后发现的(GraphService 也没有);这两个类里新代码要么别用 log,要么加注解。ResourceRegion 在 org.springframework.core.io.support 下。
| 项 | 改动 |
|---|---|
| 7 授权 | authorization/index.vue:ipc 选择器 → 隐藏 <input type=file accept=".xlts"> + licenseFile/licenseFileName;login.ts 的 offlineAuthorizationApi(file) 改 defHttp.uploadFile(ctxPath + adminPath + '/sys/noNet')并手动解包 Result(uploadFile 直接走 axiosInstance.request,不经过 transformRequestHook) |
| 8 升级包 | UpgradeModal.vue:<input type=file accept=".zip"> + 上传进度;setting/api.ts 新增 uploadUpgradePackage(file, onProgress)(uploadFile + 30min 超时),删 upgradeSystem(path) |
| 9 图谱历史 | graph/index.vue:写 → saveGraphHistoryContent(String(historyId), payload)(historyId 来自 createGraphHistory 返回的 id);读 → fetchGraphHistoryContent(remoteId);删 ipc 的 getElectronIpcRenderer/writeGraphHistoryPayloadToFile/readGraphHistoryPayloadFromFile(保留 extractGraphHistoryFilePath 用于 normalizeGraphHistoryItem 的元数据、looksLike/normalizeGraphHistoryFilePath 用于 payload 判别) |
| 10 基站 | baseStationQueryApi.ts:删路径版 convertBaseStationData/fetchBaseStationTemplatePath,新增 uploadBaseStationConvert(file, onProgress)(blob + 原生响应)与 downloadBaseStationTemplate();aiBaseStation/index.vue:隐藏 <input type=file> + handleConvertFileChange(上传→blob→downloadByData 下载,JSON 错误体解析)+ handleFetchTemplate 改下载模板;删 openDirectoryByPath/resolveTemplatePath/doConvertFile |
| 1/2 窗口与版本 | 删 WindowControls/Minimize/Maximize/Close.vue + useElectronWindow.ts + header/components/index.ts 导出 + 三个页头的挂载(header/index.vue、authorization、case.vue);AppLogo/ProductInfoModal 版本号改 __APP_INFO__.pkg.version(vite define 已注入 appPkg.version) |
| 12 file: 兜底 | governApi.ts 删 DEFAULT_ELECTRON_API_URL + getPackagedFallbackOrigin(),host 兜底链只剩同源 |
\n 全部失配 → 脚本必须先探测 EOL(\r\n vs \n 计数),统一转 LF 处理再按原 EOL 写回。(cond ? a : '') 的括号去掉、把 import 折行),别把 --fix 前的预期当真,以 --fix 后的 grep 为准。aiBaseStation/index.vue 的行过滤把 getCarrierLabel 连带删了 → 发现后立即恢复(用唯一锚点行重插)。mvn -pl ai-server -B compile → BUILD SUCCESSvue-tsc 全仓 103(无新增)HttpPmtilesSource 10 项运行时断言全通过(本地 Range 服务)用户决定:离线地图目录选择 删除、基站库(.bin)选择 删除、离线地图瓦片用方案A。
GET /sys/map/pmtiles:从系统配置 mapLocal 目录读 china.pmtiles,支持 HTTP Range
(无 Range → 200 全量 + Accept-Ranges: bytes;单区间 → 206 ResourceRegion;多区间/非法/越界 → 416)GET /sys/map/style:返回配置目录里的 behavior-spacetime-style.json(无则 404,前端回退内置样式)mapPmtiles / mapStyle / resolveMapFile / serveLocalFile / rangeNotSatisfiable;controller 仅转发ResourceRegion 的包是 org.springframework.core.io.support.ResourceRegion(不是 core.io)core/utils/map/offlineMap.ts:删 ElectronPmtilesSource,新增 HttpPmtilesSource(fetch + Range 头)、
getOfflineMapPmtilesUrl() / getOfflineMapStyleUrl() / getBuiltinPmtilesUrl() / resolveOfflinePmtilesUrls();
删掉三个已无用的本地路径 URL 工具(getOfflineMapPmtilesUrl 旧的本地版 / getOfflineMapFileUrl / getOfflineMapFilePath)BaseStationQueryMap.vue/StationMapModal.vue/behaviorSpacetime/index.vue):
瓦片源换 HttpPmtilesSource(服务端目录未配置则回退前端静态 resource/map/china.pmtiles,并把它也注册成源);
自定义样式读取从 ipc readTextFile 换成 fetch /sys/map/styleOfflineMapModal.vue、CellLocalModal.vue、core/utils/caseGlobalConfig.ts(ipc 持久化)、
SettingFooter.vue 两个入口按钮/弹窗/引导监听、components/index.ts 两个导出、caseConfigGuide.ts 的 hydrate 调用与两个弹窗事件常量两条路径本来就存在服务端(SystemConfManager → QingJian/conf/c5e503ad.json 的 mapLocal/cellLocal):
GET /js/a/sys/mapLocal?path=...、GET /js/a/sys/cellLocal?path=...QingJian/conf/c5e503ad.json(改完要重启后端,配置是启动时读入内存的)mvn -pl ai-server -B compile → BUILD SUCCESSserveLocalFile 逻辑跑 9 例(0-99/100-/-100/999-999 → 206;
0-1,5-6/abc/2000-3000/-0 → 416;无 Range → 200)全通过
(注:bytes=2000-3000 的 416 是靠 ResourceRegion 的 count<0 断言抛错兜住的,HttpRange 本身不报错)offlineMap.ts + 起本地 Range 服务,10 项断言(URL 拼装 / Range 头 / 返回字节 / 空参数不发请求 / 404 返回空)全通过eslint --max-warnings 0 0 错误;vue-tsc 全仓仍 103(无新增)cellLocal.ts 里 getCellLocalPath/getManualCellLocalPath/getServerCellLocalPath/setCellLocalPath/isValidCellLocalPath/
CELL_LOCAL_PATH_CHANGED_EVENT 与 offlineMap.ts 里 getManualOfflineMapDir/getServerOfflineMapDir/setOfflineMapDir/clearOfflineMapDir
在删掉两个弹窗后已无引用(纯 localStorage 工具,无 Electron 代码),未删以控制改动面。
getOfflineMapDir() 仍保留读旧的 OFFLINE_MAP_DIR_KEY 作为兜底(兼容 Electron 时代留下的 localStorage)。