E:\workspace\zsjz-ai\AI_AGENT.md(仓库根,12 节)ai-server/src/main/resources/prompts/AGENTS.md 与 QingJian/agent/AGENTS.md
是产品运行时 AI 人格提示词,不是编码助手约定,勿混改。common/config/Result.java 有未使用且无依赖的 import io.milvus.param.R;Result 的 SUCCEED_CODE/FAILURE_CODE 非 finalapp.yml 为 Solon 遗留配置,已失效;context-path: "!/js/a/" 语义可疑dirname: command not found,PATH 损坏),改用 Glob/Read/Grep/PowerShell。src/cms/(11 文件);App.vue 重写(去掉 AI 悬浮面板全部状态/函数/样式);
menu.json 删「AI 助手」IFRAME 节点(10261);package.json 删 @jeesite/cms-lib。templates/chat.html、static/js/chat.js、marked.min.js、static/css/chat.css、
theme.css、static/layui/;static/ 目录整体移除。AI_AGENT.md §6 前端消费方改为 ai-frontend/src/ai/api/。context-path: "!/js/a/" 导致 ai-server 无法启动实测(直接 new TomcatServletWebServerFactory().setContextPath(v),Spring Boot 3.5.16):
!/js/a/ → IllegalArgumentException: ContextPath must start with '/' and not end with '/'/js/a/ → 同样被拒(尾部斜杠不合法)/js/a → 通过已改为 context-path: /js/a。结论:接口实际前缀就是 /js/a,
前端 = urlPrefix(/js) + adminPath(/a),与 defHttp 约定一致。
(此前文档把它标为"语义可疑",现已实证。)
mvn -pl ai-server compile 通过)sql/agent_chat_session_add_user_id.sql:加 user_id + 索引,不回填(回填会把历史会话判成他人会话)AgentChatSession 加 userId;assertOwner 改比 userId(NULL 历史行宽放+告警);
listSessions 加 (user_id = ? OR user_id IS NULL);createSession 写入 userIdAgentResultController(/chat/results/{resultId},裸返回,404=已过期)AgentPythonFileController(/py/files/{wsId}/{execId}/{file},白名单+normalize 防穿越)PythonExecutor.IMAGE_URL_PREFIX:/api/v1/py/files/ → /js/a/py/files/SqlResultStore 既有缺陷:sql 未存入 CachedResult,导致 PageData.sql 恒 nullsrc/ai/(27 文件)api/(types/http/chatApi/modelApi)、utils/(constants/scanner/parseMessageContent/reportExport/echartsSetup/useAiChart)、
store/chatStream.ts、styles/ai.less、components/(6 个 blocks + 9 个业务组件)、views/(3 页)menu.json 加一级分组 10263 + 子节点 10264/10265/10266;
routeHelper.ts 的 explicitDynamicViewMap 加 3 条显式映射scanner.ts),不能只按围栏切分。流式未配平时用头部特征 {"resultId" 提前出 pending 占位块。
增量策略:已完成块冻结(尾部 8KB 窗口外)+ 只重扫尾部 + 尾部块按 key 复用对象
(保持 data 引用稳定,否则图表重复 setOption、表格分页被重置)。menu.json(content.pipeline.include 只含 **/*.{vue,tsx,ts}),
而 Icon 组件是运行时拼 i-<collection>:<name> 类名 → 菜单图标不会生成 CSS(项目既有隐患)。
已在 uno.config.ts 加 safelist 显式列出。streamdown-vue/style.css 全项目零引入,不引 markdown 无样式(已在 MarkdownBlock 引入)streamdown-vue 默认只放行 http/https 图片,Python 图走同源 /js/a/py/files/ → 必须放行 / 前缀@relation-graph/vue 的 RGOptions 键名是 defaultLineTextOffset_y(下划线),
defaultJunctionPoint 是枚举不是字符串;数据靠 graph.setJsonData() 注入mvn shell 脚本会把 /d/... 传给 Windows java 导致 classworlds 找不到;
本地仓库是 D:\soft\repository;ai-server/pom.xml 的 <relativePath/> 为空,
需先 mvn -N install 安装父 POM 才能 -pl ai-server 编译package.json 声明(只在 pnpm 虚拟存储),顶层无法解析其 CSS → 数学公式无样式mvn -pl ai-server compile:BUILD SUCCESSpnpm type:check 中 src/ai/**:0 错误(项目其他模块有大量既有类型错误)eslint ./src/ai/** + 改动文件:0 问题pnpm build:成功,三页各自独立懒加载 chunki-mdi:history 等含真实内联 SVGSqlResultStore 加 ownerKey(登录 userId 字符串),get(..., ownerKey) 校验归属,
不匹配/未登录一律 null → 404(刻意不区分"不存在"与"无权限")。
链路:doStream(userId) → getOrCreateAgent → buildAgent(..., ownerKey) → SqlAnalysisTool(..., ownerKey)。
agent 实例本就按 u{userId}-m{modelId} 池化,所以把 userId 绑进工具是安全的。AI_AGENT.md 同步:目录树 cms/→ai/;§6 控制器清单(4 个)+ 工具名纠正
(实际是 execute_python / get_current_workspace / get_user_selected_workspace,
原文写的 python_analysis / workspace_info 是错的)+ 新增"前端渲染管线"映射表;
§8 新增"新增页面/菜单的四个坑";§11 context-path 项标记为已实证修复。curl --noproxy '*' 返回 000;不加 --noproxy 会走沙箱代理得到 502 假象)。electric-beacon-darwin-pwxCEck4.md(我的计划/完成记录快照,已被 git add),
~/.workbuddy-ai/plans/ 下的同名文件被系统工具重新生成过 —— 两者内容不同,注意别搞混。PG 192.168.0.109:5432 与 Redis 都可达,于是把 ai-server 真跑起来了。过程中发现:
com.qingjian 包名src/main/resources/mappers/ 下 85 个 XML 全是 com.qingjian.* 命名空间,
而 Java 侧 0 处引用 com.qingjian,有 85 个 com.zsjz.* Mapper 接口(一一对应)
→ 包名迁移时漏改了 XML。Failed to parse mapping resource: mappers/ai/SkillMapper.xml
→ Could not resolve type alias 'com.qingjian.common.model.ai.dto.AiStatCallFrequencyTop10'。
即 ai-server 在本仓库根本无法启动。com.qingjian.X → com.zsjz.ai.X(目标类均存在);
14 处无法映射的全部落在 mappers/ai/(6 个不存在的 DTO + 8 个不存在的 Mapper),
该目录是 QingJian 旧 AI 模块,现 AI 能力在 module/agent(MyBatis-Plus BaseMapper,无需 XML)。mappers/ai/(8 文件);其余 77 个文件、160 处引用 com.qingjian → com.zsjz.ai。
文件均在 git 跟踪且改动前无本地修改 → 可 git checkout 完全回退。mvn -pl ai-server clean compile BUILD SUCCESS,应用成功启动。Tomcat started on port 8980 (http) with context path '/js/a'
→ context-path 修复正确,此前实证的 /js/a 得到运行时验证。AppLoadEndEventListener(CommandLineRunner)随后失败:
PSQLException: 关系 "table_info" 不存在。etl-2,agent 结构在 zsjz-ai。GlobalCache.initCache() 需 table_info+table_field、
CaseDataCache 需 person_lib_no、initCaseRocksDbData() 需 RocksDB 目录、
TowerUtils.init() 需基站库文件 —— PathConst.ROOT_PATH = user.dir,
说明 ai-server 是按"客户端应用"设计的,裸跑仓库目录跑不通。sql/agent_chat_session_add_user_id.sql 应用到 zsjz-ai(additive,见下)。zsjz-ai 执行了 ADD COLUMN user_id BIGINT + COMMENT + CREATE INDEX idx_agent_chat_session_user_case。
均为 additive/幂等;执行前该列不存在、索引不存在。user_id 全 NULL、case_id 全有值
→ 印证了"NULL 宽放"的设计(回填 case_id 会把这些会话判成他人会话而全部 403)。agent(row_id=1 "数刃")、agent_model(3 条:embedding 1 + llm 2,默认是 Ollama qwen)、
agent_model_provider(4 条:Ollama/OpenAI/通义千问/DeepSeek)。agent_model.type 是用途类型 llm/embedding,
不是厂商协议(协议在 agent_model_provider.type)。我原先把表单选项写成
openai/dashscope/ollama/… → 已改为 llm/embedding。StatusEnum 的 ACTIVE/SUSPENDED/ARCHIVED/DISABLED/DELETED,
我原先按 '0' 判断停用 → 已改为枚举映射 + 配色。type === 'llm'(默认模型选取也同步过滤)。
另加一道保护:把 embedding 设为默认模型时前端拦截并提示
(后端 getDefaultModelId() 只按 default_model=true AND status=ACTIVE 过滤,不区分 type,
这是个后端待改进点)。项目没有测试框架(无 vitest/jest),但 esbuild 是 devDependency,够用。
做法:esbuild --alias(或 JS API 的 alias + onResolve 插件)把依赖
import.meta.env / axios 的模块换成桩,打包成 CJS 后跑 Node 断言。
(constants.ts 在模块作用域调 useGlobSetting();http.ts 会拉进 axios+antdv+pinia 整条链。)
Windows 路径必须 cygpath -w,否则 node/esbuild 把 /c/... 解析到当前盘符。
utils/scanner.ts 56 项:围栏(``/~~~/四个反引号/未闭合→pending)、
**裸 JSON 表格识别**(字符串内花括号、多表格、未配平→pending)、HTML/SVG 块、
isTablePayload/findJsonEnd、增量扫描(冻结块**同一对象引用**、key 稳定、data` 引用稳定、
短内容回退全量、流式表格组装)、导出还原。api/chatApi.ts 的 SSE 解析 25 项:mock globalThis.fetch + ReadableStream,
覆盖跨 chunk 分帧、中文/emoji 多字节被切断、\r\n、多行 data、8 种事件、
error 帧、非 SSE 错误响应、末帧无结尾空行、请求体/凭据、Abort 语义。store/chatStream.ts 流式状态机 68 项:pinia 在 Node 下用
setActivePinia(createPinia()) 跑,chatApi 用可编程替身驱动回调。覆盖
init 幂等、自动建会话+乐观插入+流式中状态、token 累积、节流 100ms 中途解析、
工具步骤按 toolCallId 打补丁、intent/suggestions、会话元信息本地更新、
错误帧/传输异常/已有内容不被覆盖、stop() 触发 abort、会话 CRUD、
selectSession 反序列化 toolEvents、本地消息不调接口、reset、无工作空间不发送。utils/reportExport.ts 19 项:单表 sheet 结构与表头、多表分 sheet、
sheet 名唯一/带序号/≤31 字符/不含 Excel 非法字符、无表格返回 null、
缺列定义按行键推断、空行集、撞名去重、空值与缺键容错。SqlResultStore 28 项(后端,直接 java -cp target/classes 跑 main,无需 Spring/DB):
ownerKey 归属隔离(本人可读/他人与未登录不可读/不泄露存在性/ownerKey=null 历史结果宽放)、
sql 回传、分页 clamp(超界/0/负数)、自定义 pageSize、空结果、LRU 淘汰、
并发(小规模正确性 + 高竞争下隔离不变量)。--node-paths(裸包名从项目 node_modules 解析)只有 JS API 支持,CLI 没有;
所以需要它时写 .cjs 脚本 require('绝对路径/esbuild')(脚本在仓库外,ESM 无法用 NODE_PATH)。'../api/chatApi')CLI --alias 匹配不了,
要用 JS API 的 onResolve 插件。output: [null,null,null]),
直接调 .pnpm/@esbuild+win32-x64@*/.../esbuild.exe 看真实报错。scanner.ts:围栏正文多带一个结尾换行 → 代码块多一个空行、导出/复制带多余换行。chatApi.ts:末帧没有结尾空行时整帧被丢弃(SSE 规范允许末帧无空行;
createParser 只按 \n\n 分帧,EOF 时没有冲刷缓冲区)→ 会丢 token 文本或 done 事件。
已改为返回 {push, flush},读取循环结束后 parse.push(decoder.decode()) + parse.flush()。await sleep(10) 时 send() 已收尾,观察不到"流式中"状态
(mock 必须中途挂起);api.calls[0] 未必是目标调用(前面还有 listSessions);
分页把第 2 页当第 1 页断言;并发用例 1600 次 put 必然触发 LRU(50) 淘汰(与隔离无关);
多行 data: 切在了非法 JSON 位置;等等。用户明确:"项目已移除用户体系。移除 sa-token 相关代码。"
import cn.dev33.satoken.stp.StpUtil 与 13 处 StpUtil.getLoginIdAsLong()AgentChatService 11 个方法签名去掉 Long userId;AgentChatServiceImpl 同步,
assertOwner → requireSession(只保留 404,去掉归属判断),listSessions 去掉 user_id 过滤,
createSession 去掉 setUserIdAgentChatSession 删 userId 字段;ChatSessionVO 删 userId;AgentChatResult 去掉 ownerKeyAgentService.getOrCreateAgent 去掉 userId,池化 key 改为 w{ws}-a{agent}-m{model}
(原 key u{userId}-m{modelId} 漏了 workspace/agent → 切换案件会复用旧 agent 实例,属真实缺陷)SqlResultStore 去掉 ownerKey 归属隔离(保留 sql 回传修复)sql/agent_chat_session_add_user_id.sqlsa-token 不在任何 pom.xml 中声明(靠传递依赖),所以删完 import 无需改 pomRUNTIME_USER_ID = "default"
(用于 RuntimeContext.userId() / AgentStateStore 记忆槽位 / usage 统计)LicenseFilter 吞掉所有请求common/config/LicenseFilter.java 的 doFilter() 方法体是空的,从未调用 chain.doFilter,
而它是 @Component implements Filter → Spring Boot 注册到 /*。启动日志实证:
Mapping filters: ..., licenseFilter urls=[/*] order=2147483647。
后果:所有 HTTP 请求(含不存在的路径)都返回 200 + 空体,后端"能启动但完全不服务"。
该类无任何其他引用 → 直接删除。修复后不存在的路径正确返回 404。
com/zsjz/ai/common/ 目录被删除事件(已恢复)会话中途发现 ai-server/src/main/java/com/zsjz/ai/common/ 下 243 个文件从工作区消失
(HEAD 中有、工作区 0 个)。非我的 git rm 所致(那次只删了 LicenseFilter 一个文件)。
排查:无 git stash、Code/User/History 无该项目快照、JetBrains LocalHistory 为二进制格式。
处理:git checkout -- <common 目录> 从索引恢复 243 文件,并重新应用两个已知未提交改动:
PathConst.RUN_QINGJIAN_PATH = ROOT_PATH.resolve(APP_NAME)(HEAD 里是 = ROOT_PATH)AppLoadEndEventListener 里注释掉 CaseDataCache.initCache()
恢复后 common/ 相对 HEAD 仅剩这 3 处预期差异(含 LicenseFilter 删除),编译 BUILD SUCCESS。
若用户侧有其它未提交改动在该目录内,需其自行核对。PathConst.ROOT_PATH = user.dir,而 spring-boot:run 默认以 ai-server
为工作目录(会命中残缺的 ai-server/QingJian/)。需显式:
-Dspring-boot.run.workingDirectory=E:/workspace/zsjz-aiTomcat started on port 8980 with context path '/js/a' + Started App
initCache success(table_info/table_field 已就位)+ 请先设置基站数据库路径!(TowerUtils 未配,不致命)case_info 表的三处 schema 不匹配(SQLite DDL 套到 PG 造成,非我引入)| 问题 | 现象 | 修复 |
|---|---|---|
id NOT NULL 但无自增/默认 |
IdType.AUTO 插入留空 → 违反非空约束 |
ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY |
列名 db_Path 为驼峰(带引号创建) |
未加引号查询折叠成 db_path → 字段不存在 |
RENAME COLUMN "db_Path" TO db_path |
db 是 varchar(8) |
代码写 randomString(8)+id(9+ 字符)→ 值太长 |
ALTER COLUMN db TYPE varchar(64) |
case_info 当时 0 行,改动零数据风险。
全库同类问题扫描结果(未修,仅报告):
meta_raw_sheet.lineNoagent_chat_session/agent_message/agent_model/agent_model_provider/
lunar/meta_raw_file/meta_raw_sheet/table_field/table_info
(其中 agent_* 由 Java 侧生成 ID,无影响;lunar/meta_raw_* 若走 AUTO 插入会失败)POST /case/create → id=2,QingJian/workspace/2/pYRn5q3L2 生成,case_info 落库POST /case/open?id=2&pwd=123456 → 200(注册 DuckDB slave + CaseDataCache)GET /case/exit 释放文件锁):
person_basic_info 8 / call_record 280 / trans_record 199 / rel_node 24 / rel_edge 16POST /chat/sessions {workspaceId:2, modelId:DeepSeek} → 200intent×1、token×1071、tool_call×13、tool_input×13、
tool_result×13、suggestions×1、done×1 —— 无 errorintent 帧含意图分类 + 增强问题 + entities;正文按契约输出裸 JSON 表格载荷
(resultId + sql + columns + rows),数据与灌入数据一致page=1/pageSize=3 → totalRows=5/totalPages=2;page=2 返回余量 2 行;
page=999 clamp 到末页 2;不存在的 resultId → HTTP 404 + 友好提示;sql 正确回传10.66.66.66:8080)不可达;api.deepseek.com:443 可达且已配 Key
→ 验证时用 modelId=3742141657084284816 指定 DeepSeekecharts `/`graph 围栏渲染、execute_python 出图、模型 CRUD、中断落库QingJian/workspace/1/(首次建案失败留下的空目录)ChatSession.id 等 Long 字段后端序列化为字符串(防 JS 精度丢失),
而 TS 类型声明为 number → aiAnalysis 的 openFromQuery 用 Number(raw) 与 s.id 比较会失配
(从聊天历史"继续对话"跳转可能定位不到会话),待修上一轮的后端在 2026-09-15 23:29 随会话结束被终止(日志无关闭信息)。重启后需重新 /case/open
(slave DuckDB 数据源是进程内状态,不持久化)。
| # | 测试 | 结果 |
|---|---|---|
| 1 | 图表渲染 render_chart |
✅ ```echarts 围栏 817 字符,数据口径正确 |
| 2 | 关系图谱 render_graph |
✅ ```graph 围栏 4591 字符 + 28 行表格 |
| 3 | 会话 CRUD(重命名/置顶/收藏/详情/消息列表) | ✅ 全 200 且状态生效 |
| 4 | 模型 CRUD(增/改/设默认/删) | ✅ 全 200 |
| 5 | 中断落库 | ✅ 12s kill → messageType=interrupted |
| 6 | 用户消息落库(修复后回归) | ✅ 1 user + 1 assistant |
| 7 | SSE 错误路径(无效/非法 sessionKey、空消息、无效 modelId) | ✅ 全部返回规范 error 帧 |
| 8 | 路径穿越防护(..%2f、..%5c、--path-as-is) |
✅ 400/404,无文件泄露 |
| 9 | 模型污染修复回归 | ✅ 无效 modelId 后会话 model_id 未被写坏 |
| 10 | 多轮记忆延续 | ✅ 追问正确引用上一轮结论(280/199) |
| 11 | 案件隔离(新建空案件问同一问题) | ✅ 所有表返回 0 行,未串案件2数据 |
| 12 | 结果集翻页 + 404 | ✅(上一轮已验) |
| 13 | 真实载荷扫描器验证(4 个真实 SSE 输出) | ✅ 全部正确识别 |
| 14 | 前端 type:check + ESLint |
✅ 0 错误 |
execute_python 未注册:日志 Python 执行环境不可用(command=python,需安装依赖:
pip install pandas duckdb matplotlib) → 该 Python 环境缺依赖。故 Python 出图 + /py/files/** 无法验证。
启用方式:给 etlProperties.python.command 指向的 Python 装 pandas/duckdb/matplotlib。doStream 只落 assistant,messageMapper.insert(userMessage)
仅在非流式的 /chat/messages 里 → 前端走流式,刷新后用户看不到自己的提问、会话转录缺一半。
修复:新增 persistUserMessage(),在 doStream 解析会话后落库一次(含 messageCount+1)。doStream 先回写 session.model_id 再校验模型是否存在,
一次带无效 modelId 的请求即把会话钉死在坏模型上,后续不带 modelId 的每轮都失败。
修复:把回写移到 getOrCreateAgent()(内含模型存在性校验)之后。
已修数据:UPDATE agent_chat_session SET model_id=3742141657084284816 WHERE model_id=999999(1 行)。json 或sql 块」,
但实测模型约 1/4 概率违反(包进 或json)。前端 scanner 原先把围栏内容一律判为 code 块
→ 退化成不可翻页的代码块。
修复:scanner.ts 围栏分支加容错——若围栏体是合法表格载荷(isTablePayload)且 lang 为
`/json/sql,则按table` 渲染。已用 4 个真实 SSE 输出验证(含那个被围栏包住的用例)。
扫描器断言从 56 项增至 64 项。execute(shell,11 次)、write_file、glob_files、list_files。对处理案情数据的产品,
LLM 能执行任意 shell 命令是显著的安全/范围问题,建议限制 HarnessAgent 的内置工具集。/sys/** 全部 404:SystemController 是 @Controller 而所有方法都没有 @ResponseBody
→ 返回 void/String/实体时被当成视图名去转发(日志:View name [sys/health] →
Forwarding to [sys/health] → 404)。且前端 main.ts 期望 Result 包装(res?.code === 200)。
影响:waitForBackendReady() 重试 60×2s=卡 2 分钟;resolveStartupRoute() 拿不到
code===200 → 总是跳转 /authorization,前端无法进入应用。
(注:LicenseFilter 修复前该接口返回空 200,同样不满足 code===200,故此问题一直存在。)/chat/sessions 缺 workspaceId 参数返回 500(MissingServletRequestParameterException
未被 GlobalExceptionHandler 处理),宜为 400。agent_test.py:发起 SSE 提问 + 解析事件统计/围栏/裸 JSON 表格/图片引用real-payload.cjs:把真实 SSE 的 token 正文喂给 scanner,验证块识别(防契约漂移)PgMsg*.java / PgSess*.java:核对消息落库与会话状态agent-browser 的守护进程在本机起不来:open/eval/snapshot/doctor 全部挂死
(~/.agent-browser/default.pid 写了进程号但进程已死、端口未监听)。
Node 派生能力已排除(托管 node22 与系统 node24 都能正常 spawn detached 子进程)。playwright-core + agent-browser 已下载的 Chrome:
chromium.launch({executablePath: 'C:\\Users\\cc\\.agent-browser\\browsers\\chrome-153.0.8010.47\\chrome.exe'})
(playwright-core 装在 temp 目录,不下载浏览器,秒级可用)。ui-stage1.cjs(开案+进 AI 页)、ui-stage2.cjs(提问+渲染校验)、ui-probe-case.cjs(DOM 探测)C:\Users\cc\AppData\Local\Temp\ai-verify\shots\前端启动强校验 /sys/checkAuth 必须返回 code:200(main.ts 的 shouldRunStartupAuthCheck
只豁免授权页本身,skipAuthCheck 查询参数并未被采纳)。
A9DEBA7C48078E4B...(GET /sys/code)lib/windows/license_刘备_A9DEBA7C.xlts 文件名后缀与机器码前缀完全匹配
→ 复制到 QingJian/license/license.xlts 后 /sys/checkAuth 返回 {"code":200,"data":"试用"}system_info 表为空 → /sys/info 的 data 为 null;但前端 isAuthorizationExpired(undefined)
返回 false(不拦截),所以不影响进入应用。/sys/** 全部 404(前文已记)→ SystemController 改 @RestController + 全部返回 Result。
修复后 /sys/health、/sys/info、/sys/checkAuth 均 code:200。/case/* 返回裸对象/数组 → 前端 defHttp 默认 transform 期望 {code,message,data},
实际抛「接口请求出错,请稍后重试!」且案件列表渲染为空。
修复:CaseInfoController 全部返回 Result;/case/open 额外返回已开案信息。/case/create|open|updatePwd 参数绑定失败 → 前端以 JSON body 提交,
后端却是简单参数(form/query)→ 日志 parameters={} → 500。
修复:改为 @RequestBody(新建 OpenCaseDTO;create 用 @RequestBody CaseInfo;
updatePwd 用 @RequestBody UpdateCasePwdDTO)。
注意:/case/open 现在只认 JSON body,curl -d "id=2&pwd=..." 不再可用。/chat/sessions 缺 workspaceId 返回 500 → GlobalExceptionHandler 补
MissingServletRequestParameterException → 400。案件列表不是表格,是 antd 卡片:
.ant-card(标题 .ant-card-head-title 内含案件名)
└ ul.ant-card-actions > li > span > div.flex > button "打开案件"
密码弹窗:input[type="password"] + .ant-modal-footer button.ant-btn-primary。
/case,标题「清鉴线索调查工具」,左侧菜单完整
(案件数据/数据画像/通信分析/交易分析/活动轨迹分析/行为时空分析/对象关系分析/基站查询/AI 数据分析/模型管理/聊天历史)| 项 | 证据 |
|---|---|
| 流式逐字输出 | 21s 完成,中间态截图有「正在理解问题」→ 逐段文本 |
| 交互表格 + 分页 | 表头[号码/机主姓名/通话次数/作为本方/作为对方] + 5 行数据;分页器「共 5 行 · 50 条/页」 |
| ECharts 图表 | canvas 850×340 painted=true;堆叠柱状图 + 图例 + 坐标轴 + 保存图片/配置 |
| 关系图谱 | 852×494;meta「节点 8 · 关系 20」;8 个节点 DOM 带姓名与分类;边标签「11笔/¥3260…」 |
| 工具调用面板 | 「3 次工具调用 · 检索表结构、校验图表配置、执行 SQL 查询」+ 每步可展开「详情」 |
| 导出下载 | 菜单 Markdown/Excel/PDF;实下 .xlsx(16938B,合法 OOXML) 与 .md(3646B,含表格 JSON+echarts 围栏,往返无损) |
| 模型选择器 | 下拉仅 ["本地模型(默认)","ds"] —— 证实只列 llm 模型,embedding 未出现 |
| 会话列表 | 显示案件 2 的全部会话 + 新建会话即时出现,条数含 user 消息(落库修复生效) |
| 错误渲染 | 模型不可达时气泡显示 ⚠️ HTTP transport error... + 复制/导出/删除 |
| 控制台 | 修复后错误数 1(仅 antd rowKey 弃用警告) |
graphOptions 里写了 lineUseTextPath: true,当前
@relation-graph/vue 版本不支持该属性并直接抛错
(Graph options do not support setting "lineUseTextPath" ... Please use "defaultLineTextOnPath"),
导致图谱不出图 + 连带 60+ 个 Vue Cannot set properties of null (setting '__vnode') 错误。
修复:改名为库要求的 defaultLineTextOnPath。修复后控制台错误 67 → 1。.ai-node { color: var(--node-color) } 是文字颜色,
而模板把节点填充色 node.color(= #ffffff)绑给了它 → 姓名渲染了但看不见。
修复:在 jsonData 的 data 里显式写 accentColor: color,模板改绑
node.data?.accentColor(不依赖图谱库内部字段名)。修复后节点显示姓名 + 分类。AppDarkModeToggle.vue 已导出但未在任何模板挂载(showDarkModeToggle: true 形同虚设)build/plugins/index.ts:45 被注释掉data-theme=dark 无运行时暗色 CSS 可加载。手动加 dark 类后 body 变深色
(rgb(44,52,74))但 AI 容器仍为白底 —— 因为 --ai-* 是 Less 编译期变量。
结论:暗色未启用,无法验证;若将来启用(恢复插件 + 挂载开关),需用生产构建复验 AI 页配色。| 文件 | 问题 | 修复 |
|---|---|---|
SystemController |
@Controller 无 @ResponseBody → 全部 404 |
改 @RestController + 返回 Result |
CaseInfoController |
返回裸对象/数组 + 入参用简单参数 | 返回 Result;create/open/updatePwd 改 @RequestBody(新增 OpenCaseDTO) |
GovernTreeController |
返回裸 List/Map | 全部 Result 包装 |
GlobalExceptionHandler |
缺 MissingServletRequestParameterException |
补 → 400 |
注意:/case/open 现在只认 JSON body。
| 项 | 结果 |
|---|---|
| 模型管理页 3 行 | ✅ 类型列「对话模型/对话模型/向量模型」、状态列「活跃」(枚举映射生效) |
| 向量模型「设为默认」拦截 | ✅ 弹窗「无法设为默认模型…请选择「对话模型」作为默认。」 |
| 通过 UI 表单建案 | ✅ 新案件出现在列表;0 接口错误、0 控制台错误(验证 /case/create 的 @RequestBody 改造) |
| 案件页/模型页控制台 | ✅ 0 错误;AI 页仅 1 条 antd rowKey 弃用警告 |
.ant-table-header / .ant-table-body),
不可滚动表只有一张(行在 .ant-table-tbody tr)。统计行数要兼容两者,否则误判 0 行。page.locator('button:has-text("x"), text=y') 这种混用 CSS 与文本引擎的写法会报错,
要用 page.getByText('y')。停止生成 按钮消失」,别用"文本长度稳定"。写脚本扫描全部 45 个 controller:147 个端点中 122 个未用 Result 包装(涉及 40 个文件)。
再扫前端 250 个调用点:248 个期望 Result(defHttp 默认 transform),
只有 2 个显式 isTransformResponse:false(/sys/info、/sys/openLocalFile)。
⇒ 这些未包装端点在前端几乎全是坏的(抛「接口请求出错,请稍后重试!」)。
写脚本 wrap-result.cjs 批量转换,规则:
@XxxMapping 的 public 方法」void → Result<Void> + 末尾补 return Result.succeed();X → Result<X>,所有 return expr; → return Result.succeed(expr);SseEmitter / Flux<...> / ResponseEntity<...> / 已是 Result<...>/chat/**、/chat/results/**、/py/files/**
前端 aiHttp 显式按裸响应读)结果:未包装端点 122 → 14,剩下的 14 个正是刻意保持裸响应的
(/chat/** 11 个 + /chat/results/** 1 + /py/files/** 1 + /govern/sse 1)。
编译 BUILD SUCCESS。
boolean → Result<boolean> 编译失败,必须装箱
(boolean→Boolean/int→Integer/long→Long/double→Double/char→Character…)。indexOf 定位返回类型会命中 URL 里的同名子串:
@GetMapping("/createGraphHis") public GraphHis createGraphHis(...) →
URL 被改成 /createResult<GraphHis>。必须用精确偏移,改完逐文件比对映射注解。/(^import .*;\n)/m 匹配不到(行尾是 \r\n)→ 必须写 \r?\n。return 语句多消耗了一个 ; → 出现 return Result.succeed(x);;。
处理完表达式后要 i = j + 1 跳过原分号。backup-controllers/)audit-diff.cjs)→ 找出 URL 被污染那 1 处/sdc/list、/dm/getGovernConfList、/graph/graphHis、/gt/tree、
/case/list、/models、/models/providers、/cleanLog/list 全部返回 {code,message,data};
/chat/sessions 仍是裸数组 ✓;SSE /chat/stream 正常出帧 ✓DeepSeek API Key 已失效:Authentication Fails, Your api key: ****dfsf is invalid(HTTP 401)。
08:55 时还能用,之后失效 —— 需要用户更新 Key 才能继续跑 LLM 对话验证。
默认模型 Ollama(10.66.66.66:8080)本就不可达。
mvn -pl ai-server spring-boot:run -Dspring-boot.run.workingDirectory=E:/workspace/zsjz-ai,
启动后必须重新 POST /js/a/case/open(JSON body)注册 slave 数据源。用户要求去掉独立的「聊天历史」菜单。共改 4 处:
core/store/modules/menu.json:删掉 AI 分组(/10263)下的 /aiChatHistory/index 子项
(分组 redirect 仍是 /aiAnalysis/index,未受影响)core/router/helper/routeHelper.ts:删掉 '/aiChatHistory/index' 的组件映射
(AI 模块的视图只走这张映射表,不走通用 glob,删了就不会再有引用)ai/views/aiChatHistory/index.vue(该文件已被 git 跟踪,已 git rm)ai/views/aiAnalysis/index.vue:删掉因此失效的 openFromQuery()(读 ?sessionId= 定位会话)
以及随之无用的 useRoute 导入与 route 变量验证:menu.json JSON 合法且 AI 菜单变为 ['AI 数据分析','模型管理'];
全仓 grep aiChatHistory|聊天历史 0 命中;i-mdi:history 无残留引用;
src/ai/** 类型检查与 ESLint 0 错误;改动的 3 个文件均不在 src/core 既有类型报错列表中。
注意:pnpm type:check 全量跑会报出 src/core/** 的既有错误(getToken/getUserInfo/
getSessionTimeout 等在用户体系移除后失配,约 10+ 处),这是历史遗留、非本次引入;
检查时用 grep "^src/ai/" 过滤即可。
用户要求:移除 Electron 相关内容,改纯 Web 上传(多选文件 + 文件夹,xls/xlsx/csv,带进度), 后端改单文件上传并解析,解析完查结果复用原结果页,后续流程与参数保持不变。
POST /dm/preFile({filePath: string[], isDir}),
后端拿路径读服务端/客户端本机文件 → Web 化必须真正上传字节。filePath 在清洗阶段会被再次读取(CleanTask:87 打开文件、DmService.hasSourceFile 过滤)
→ 上传文件必须落盘持久化,且 filePath 要回填为服务端可读的绝对 Path。QingJian/tmp → 上传文件绝不能放 tmp 下,
否则「重新清洗」会失效。落盘目录:QingJian/workspace/{caseId}/upload/{batchId}/。PreTask/PreDataListener 本来就接收 Path 入参 → 落盘成 Path 即可整套复用解析逻辑。DmController 所有 POST 端点都缺 @RequestBody(Solon→Spring 迁移遗留)
→ 现有 preFile 本来就是坏的,必须先补。| 文件 | 改动 |
|---|---|
DmController.java |
给 preFile/doClean/getFiles/deletedFiles/saveGovernCon 补 @RequestBody;新增 POST /dm/preFileUpload(单文件 MultipartFile) |
common/model/dm/dto/DeletedFilesDTO.java |
新增:deletedFiles 前端发 {ids:[]},原签名是简单参数绑定不上 |
DmService.java |
新增上传落盘 + 调 PreTask 解析的方法;后缀白名单 xls/xlsx/csv;filePath 回填服务端绝对路径、fileSize 回填真实大小 |
StateManager.java |
新增 isCaseOpened()(避免在业务代码里硬编码哨兵值 getCaseId()==888888) |
注意:getCaseId() 返回 Integer,未开案时是哨兵值 888888 而非 null。
| 文件 | 改动 |
|---|---|
case/api/govern/governApi.ts |
新增 preFileUpload(file, onProgress, data),走 defHttp.uploadFile(底层 axios,可传 onUploadProgress) |
case/views/data/index.vue |
上传弹窗整体重写(见下) |
index.vue 具体改法:
extractUploadFilePath / extractUploadFolderPath(读 window.electron.getPathForFile)、
handleSelectFile(ipcRenderer.invoke 选文件夹)、beforeUpload / beforeUploadFolder、
processUploadFilePaths / queueUploadFilePaths / normalizeUploadPaths / pendingUploadPaths / uploadBatchTimer /
selecting / uploadProcessing。Upload.Dragger 换成原生拖拽区(@dragover/@dragleave/@drop)+ 两个隐藏 input
([multiple][accept=.xls,.xlsx,.csv] 与 [webkitdirectory])。webkitdirectory;拖拽走 webkitGetAsEntry() 递归遍历 createReader().readEntries()
(注意 readEntries 每次只返回最多 100 条,要循环读到空为止)。isValidUploadFile,不合规的过滤掉并提示条数。uploadProgressMap(单文件 %)+ overallProgress(按 (各文件进度和)/总数 计算)+
uploadStatuses(pending/uploading/done/error)。preFileUpload,累加返回的 FileInfo[],全部结束后
sessionStorage.setItem('case_clean_file_infos', ...) → emitter.emit(CASE_IMPORT_FILE_REFRESH_EVENT)
→ go(PageEnum.BASE_MATCH) —— 下游(结果页/匹配页)完全没动。mvn compile + mvn package -DskipTests 均 BUILD SUCCESSpnpm type:check:index.vue / governApi.ts 无新增错误
(残留 3 处是既有:CheckedType×2、Type instantiation×1)--max-warnings 0)0 错误(prettier/vue 格式问题已用 --fix 修掉)spring-boot:run 会随 shell 退出被杀:日志显示已到
Tomcat started on port 8980 + Started App + ACCEPTING_TRAFFIC,但 netstat 查不到、
进程也没了。改用 mvn package -DskipTests 打 jar 后 java -jar 启动即可正常监听
(但同样要避免后台被杀,测试完及时清理)。mvn 在本机 Git Bash 下直接用会报
找不到或无法加载主类 org.codehaus.plexus.classworlds.launcher.Launcher
→ 必须用 bash /c/Users/cc/AppData/Local/Temp/cptest/mvn-run.sh(它显式传 classworlds jar 与 -Dmaven.home)。pip install openpyxl 装到了系统 Python,但 python 命令走的是另一个解释器 →
用 python -m pip install 才装到当前 python 上。