# 清鉴(zsjz-ai)开发快速开始 > 面向新加入的开发者:从零把后端 `ai-server` 和前端 `ai-frontend` 跑起来。 > 编码约定、分层落位、Agent 子系统等**改代码前必读**的内容见 [AI_AGENT.md](AI_AGENT.md),本文只讲「怎么跑起来」。 --- ## 1. 项目构成 ``` zsjz-ai/ ├─ pom.xml # 父 POM(packaging=pom,Spring Boot 3.5.16),只聚合 ai-server ├─ ai-server/ # 后端:Spring Boot 3 + MyBatis-Plus + AgentScope ├─ ai-frontend/ # 前端:Vue3 + Vite7 + TS + Ant Design Vue(JeeSite 血统) ├─ sql/ # 建表 / 初始化脚本(手工执行,项目无 Flyway / Liquibase) ├─ QingJian/ # 运行期工作空间资产(conf / db / license / rocksdb / 基站库) └─ data/files/ # 本地文件存储根目录(storage.local.root-path) ``` 业务闭环:多源数据导入 → 清洗治理(`dm`)→ 建模分析(话单 / 资金 / 轨迹 / 画像 / 图谱)→ 可视化 → AI 对话式研判。 --- ## 2. 环境要求 | 项 | 版本 | 校验命令 | |---|---|---| | JDK | **25**(`ai-server/pom.xml` 的 `java.version`) | `java -version` | | Maven | 3.9.x+ | `mvn -v` | | Node | `>=20.19 \|\| >=22.12`(`package.json` engines) | `node -v` | | pnpm | 10.x(**只能用 pnpm**,`preinstall: only-allow pnpm`) | `pnpm -v` | | PostgreSQL | 14+,**必须装 pgvector 扩展** | — | | Redis | 任意近期版本 | — | | Elasticsearch | **8.18.x**(须与 Spring Boot 3.5 托管的客户端版本匹配) | — | | DuckDB | 无需安装,JDBC 驱动内嵌(案件分析库) | — | | Neo4j | 可选,仅在配置图谱能力时需要 | — | > 依赖服务的硬/软关系: > - **Redis 是登录的硬依赖** —— `sa-token-redis-jackson` 已装配,Redis 不可用则所有 `StpUtil.*` 抛异常,无降级实现。 > - **PostgreSQL(master)** 存平台元数据 / Agent 会话 / 模型配置 / 向量,不可用则起不来。 > - **Elasticsearch** 供 `module/search` 全文检索(索引 `zsjz_search`)。 > - **DuckDB(slave)** 是按案件/工作空间切换的文件库,不是常驻服务。 --- ## 3. 准备依赖服务 ### 3.1 PostgreSQL ```sql CREATE DATABASE "zsjz-ai"; \c zsjz-ai CREATE EXTENSION IF NOT EXISTS vector; -- pgvector,RAG 向量检索必需 ``` 连接信息写在 `ai-server/src/main/resources/application-dev.yaml`: ```yaml spring.datasource.dynamic.datasource.master: url: jdbc:postgresql://192.168.0.109:5432/zsjz-ai username: postgres password: postgres spring.data.redis: host 192.168.0.109 / port 6379 spring.elasticsearch: uris http://192.168.0.109:9200 ``` **换成自己的本机地址即可**(改这一个文件,`application.yaml` 是公共配置,不要动它的 `context-path`)。 > ⚠️ 该文件里存的是**内网 IP + 明文口令**,只为开发便利。不要把真实凭证提交到公开仓库,也不要在生产 profile 复用 dev 配置。 ### 3.2 Elasticsearch 单机开发用默认 9200 即可。索引 settings 见 `ai-server/src/main/resources/es/search-settings.json`(单分片 0 副本 + `\x1F` 分隔符自定义分析器),文档映射见 `module/search/domain/SearchDoc.java`(索引名 `zsjz_search`)。 ### 3.3 QingJian 运行期资产 `com.zsjz.ai.common.constants.PathConst` 以 **JVM 工作目录**(`user.dir`)为根,拼出 `/QingJian/...`,用到: | 路径 | 用途 | |---|---| | `QingJian/conf/*.json` | 系统配置 / AI 配置(`SystemConfManager` 读写) | | `QingJian/d5e03ad1/` | 内置银行卡 BIN、手机号运营商 RocksDB | | `QingJian/uy76tkp8/data.db` | RogueMap 数据 | | `QingJian/db/5w8mf4kf` | 基础案件库模板;`db/temp` 临时案件库 | | `QingJian/w6df89sk/` | 基站库 `cellinfo.bin` + `libCell.dll/.so`(JNI) | | `QingJian/license/` | 授权文件 `license.xlts` 与 `regInfo` 工具 | | `QingJian/agent/skills/` | Agent 技能文件 | **`.gitignore` 里 `/QingJian/` 被忽略**,这些资产不在版本库中,新环境需找同事单独拷贝;缺了它们启动后置任务(`AppLoadEndEventListener` 初始化 RocksDB / 基站库)会失败。 --- ## 4. 初始化数据库 项目**没有**自动建表机制,`sql/` 下只有部分脚本,需手工执行: | 脚本 | 目标库 | 内容 | |---|---|---| | `sql/user.sql` | PostgreSQL(master) | `sys_user` + 案件归属迁移,**幂等可重复执行** | | `sql/insight_agent_tables.sql` | PostgreSQL(master) | `agent_insight_session` / `agent_insight_message` | | `sql/insight_agent_tables_align.sql` | PostgreSQL(master) | 上表的约束/索引补丁 | | `sql/plat.sql` | 平台库 | 平台元数据表(`table_info` / `table_field` / `case_info` / `bank_card_issuer_info` …),**含 Solon 时代的 `chat_*` 旧表,与当前 `agent_*` 实体不完全对应** | | `sql/external_tables.sql` / `external_table_field.sql` | 案件库(DuckDB) + 元数据 | 13 张外部数据表建表 + `table_info`/`table_field` 登记 | | `sql/case_table*.sql`、`create_table_2.sql`、`table_data_1.sql` | 案件库(DuckDB) | 案件业务表与初始配置 | ```bash # 平台库(PostgreSQL) psql -h -U postgres -d zsjz-ai -f sql/user.sql psql -h -U postgres -d zsjz-ai -f sql/insight_agent_tables.sql psql -h -U postgres -d zsjz-ai -f sql/insight_agent_tables_align.sql ``` 初始账号(`sql/user.sql` 内置,BCrypt 哈希):**`admin` / `admin123`**,首次登录后立刻改密。 > ⚠️ **已知缺口(搭建新环境前必读)**:`agent_chat_session`、`agent_message`、`agent_model`、`agent_model_provider`、`rag_index_status` 等当前 Agent 实体对应的平台表,**在 `sql/` 下没有完整建表脚本**。`AI_AGENT.md` §11.8 记录:开发库 `zsjz-ai` 缺平台库结构(平台表在另一套 `etl-2` 库),裸跑会在启动后置任务阶段报 `PSQLException: 关系 "table_info" 不存在`。 > 新环境请**从已有可用环境导出平台库结构**(`pg_dump --schema-only`)后再启动,别指望 `sql/` 目录自带全套。 > 改表结构时务必补 `sql/` 脚本,并同步 `table_info` / `table_field` 元数据三表链路。 --- ## 5. 启动后端 ```bash cd E:/workspace/zsjz-ai # ★ 工作目录必须是仓库根,PathConst 才命中 QingJian/ mvn -pl ai-server -am clean compile # 编译 mvn -pl ai-server spring-boot:run # 运行 mvn -pl ai-server test # 跑测试 mvn -pl ai-server -am clean package -DskipTests # 打包 ``` IDEA 里直接跑 `com.zsjz.ai.App`,把 **Working directory 设为仓库根目录**(默认是模块目录,会导致 QingJian 资产与 `./data/files` 落点不对)。 > 上面四条命令都在**仓库根**执行;`cd ai-server` 后单模块构建也能解析到父 POM,但打包/运行时的相对路径(`QingJian/`、`./data/files`)会跟着工作目录走,所以统一在根目录执行更省心。 启动后: ```bash curl http://localhost:8980/js/a/sys/health # 免登录健康检查 ``` 关键地址(`server.port=8980`,`server.servlet.context-path=/js/a`): | 端点 | 说明 | |---|---| | `POST /js/a/auth/login` | 登录,返回 token;`body: {username, password}` 明文 JSON | | `GET /js/a/sys/health` | 就绪探测(白名单,免登录) | | `GET /js/a/mcpsse` | MCP SSE 端点(**白名单、无鉴权**,见 §7) | | `POST /js/a/chat/stream` | Agent 对话 SSE | > 生效配置只有 `application.yaml` + `application-dev.yaml`(`resources` 下没有别的 yaml)。 > `context-path` 必须写成 `/js/a`(以 `/` 开头、**不能**以 `/` 结尾),原 Solon 写法 `!/js/a/` 会让 Tomcat 直接抛 `IllegalArgumentException` 起不来。 ### 模型 / API Key 配置在哪 Agent 用的模型与厂商信息存平台库 `agent_model` / `agent_model_provider`,通过前端「模型管理」界面(后端 `module/agent` 的 `/models/**`)维护,**不是**写在 yaml 里。想跑通 AI 研判,先在这个界面上配好一个可用的 Provider(DashScope / OpenAI 兼容 / Ollama,三套 AgentScope 扩展都已在 pom 引入)。 --- ## 6. 启动前端 ```bash cd ai-frontend pnpm install # = pnpm bootstrap pnpm dev # http://localhost:3100 ``` `.env` 与 `.env.development` 关键项(默认值已可用): ```ini VITE_PORT = 3100 # dev server 端口 VITE_PROXY = [["/js","http://127.0.0.1:8980/js",false]] # 代理到本地后端 VITE_GLOB_API_URL_PREFIX = /js VITE_GLOB_ADMIN_PATH = /a ``` 后端换地址时只改 `VITE_PROXY` 一行(不能换行)。浏览器打开 `http://localhost:3100`,用 `admin / admin123` 登录。 日常命令: ```bash pnpm type:check # vue-tsc --noEmit pnpm lint:eslint # 自动修复 pnpm lint:all # 全量(含 install) pnpm build # 生产构建(--mode production) pnpm build:tomcat # tomcat 模式,输出到 .env.tomcat 的 VITE_OUTPUT_DIR pnpm preview:dist # 预览构建产物 :3100 ``` > `.npmrc` 里配了 `@jeesite` 私有源;装不动私有包时先确认网络/镜像可达。 --- ## 7. 联调要点(改接口前必看) 1. **路径三段式**:请求最终落到 `/js/a/...` = `urlPrefix(/js)` + `adminPath(/a)` + 后端 `context-path`。前端 `defHttp` 自动补 `/js`,代码里写 `url: adminPath + '/xxx'`;不走 `defHttp`(`fetch` / ``)时手动拼 `glob.apiUrl + glob.urlPrefix + glob.adminPath + path`。**这三处必须同步改**。 2. **鉴权只认请求头**:Sa-Token 配的是 `token-name: x-token`、`is-read-cookie: false`(仅 Header)。`defHttp` 自动注入;`fetch` 场景要手动带 `'x-token': getToken()`。原生 `EventSource` **无法自定义请求头**,SSE 一律用 `fetch` + `body.getReader()` 读增量。 3. **未登录返回 HTTP 200 + `body.code = 401`**(不是 HTTP 401),前端据此跳登录页。 4. **两套响应形态**:`module/agent` 的 `/chat/**`(含 `/chat/results/**`)是裸返回,调它们必须传 `{ isTransformResponse: false }`;`/models/**` 是 `Result` 包装,用默认 transform。 5. **同账号不允许多端登录**(`is-concurrent: false`),后登录顶掉前登录 —— 两个浏览器窗口同时登录,前一个会 401,这是设计行为不是 bug。 6. **MCP 端点无鉴权**:`/mcpsse`、`/mcp/message`、`/mcpstreamable` 在 sa-token 白名单里(外部 MCP 客户端不带 `x-token`)。代价是**能访问 8980 端口的人即可读取全部案件数据**,非可信网络部署必须在外层(反代/防火墙)限来源。 7. **`LicenseFilter` 当前是透传实现**,授权校验下沉在 `SystemService` 的 `/sys/info`、`/sys/checkAuth`。它被 Spring 自动注册为 `/*` 全局过滤器,**`doFilter` 里的 `chain.doFilter` 绝不能删** —— 删了表现为所有接口返回 `200 + Content-Length: 0` 空白,连 404 都没有,日志无异常。 --- ## 8. 新页面 / 新接口常见坑 - **菜单图标不显示**:`uno.config.ts` 的 `content.pipeline.include` 只扫 `**/*.{vue,tsx,ts}`,`menu.json` 不在扫描范围,而 `Icon` 组件是运行时拼 `i-:` 类名的 → 新图标要把完整 `i-` 类名加进 `uno.config.ts` 的 `safelist`。 - **路由不生效**:优先在 `src/core/router/helper/routeHelper.ts` 的 `explicitDynamicViewMap` 加显式映射,默认的 `import.meta.glob('**/views/**')` 会命中 `node_modules` 里的同名 views。 - **Mapper XML 命名空间**:必须是 `com.zsjz.ai.*`。历史上包名迁移漏改 XML(`com.qingjian.*`)会让 MyBatis 启动即炸。 - **改后端 DTO 字段 → 同步前端** `src//api/types/{dto,query}.ts`。 - **新增 Agent 工具** → 写清 `@Tool(description=...)` / `@ToolParam`,并同步更新 `AgentToolRegistryTest#TOTAL_BUSINESS_TOOLS` 的数量断言。 --- ## 9. 提交前检查清单 ```bash # 后端 mvn -pl ai-server -am clean compile # 前端 cd ai-frontend && pnpm type:check && pnpm lint:eslint ``` - [ ] 接口字段变更 → 后端 DTO/VO 与前端 TS 类型同步 - [ ] 表结构变更 → `sql/` 补脚本 + `table_info` / `table_field` 元数据同步 - [ ] 用户级资源接口 → 带 `StpUtil.getLoginIdAsLong()` 归属校验 - [ ] 无密钥 / 真实库密码 / 内网地址入库 - [ ] 中文注释齐全,无 `System.out.println` 残留 --- ## 10. 故障排查速查 | 现象 | 原因 / 处置 | |---|---| | 启动即 `ContextPath must start with '/'...` | `application.yaml` 的 `context-path` 被改成了 `!/js/a/` 或带尾斜杠 → 改回 `/js/a` | | `Failed to parse mapping resource: mappers/...` | XML 的 namespace/resultType 仍是 `com.qingjian.*` → 改 `com.zsjz.ai.*` | | `PSQLException: 关系 "table_info" 不存在` | 平台库结构未导入(见 §4 已知缺口) | | 登录必失败 / 所有 `StpUtil` 抛异常 | Redis 不可达(硬依赖,无降级) | | 所有接口返回空白 200 | `LicenseFilter.doFilter` 漏了 `chain.doFilter` | | 前端请求 404 | `/js` 代理指向错、或后端 `context-path` 与前端 `urlPrefix+adminPath` 不一致 | | SSE 收不到 / 401 | 用了原生 `EventSource`(带不了 header),或漏传 `isTransformResponse:false` | | MCP 客户端连不上 | `spring.ai.mcp.server.base-url` 未与 `context-path`(`/js/a`)对齐,`/mcp/message` 会 404 | | 菜单图标缺失 | 图标类名未进 `uno.config.ts` 的 `safelist` | | `npm install` 被拒 | 项目 `preinstall: only-allow pnpm`,只能用 pnpm | | 上传大文件失败 | `max-file-size: 200MB` / `max-request-size: 500MB`(`application.yaml`) | --- ## 11. 延伸阅读 | 文档 | 说明 | |---|---| | [AI_AGENT.md](AI_AGENT.md) | **仓库级编码约定**:分层落位、命名规范、Agent 工具清单与硬约束、前后端渲染契约 | | `ai-server/CODE_WIKI.md` | 模块级说明,⚠️ **部分章节仍是 Solon / `com.qingjian` 时代内容**(`app.yml`、contextPath `!/js/a/`、启动类 `Solon.start()`),以代码和本文配置为准 | | `continuous-insight-plan.md`、`tool-call-detail-plan.md` 等 | 各特性设计稿(根目录 `*-plan.md` / `*.md`) | | `readme.md` | 离线栅格地图(pmtiles 转换)与 `regInfo` 授权工具的零散说明 |