Featured image of post SQLAgent

SQLAgent

SQLAgent

银行存储过程迁移智能体平台:后端执行流程与深挖讲解

1. 后端项目定位

这个项目的后端不是一个简单的“调用大模型生成代码”接口,而是一个面向企业 SQL/存储过程迁移场景的任务编排系统。

它的核心目标是:让用户把一个业务模块涉及的表结构、SQL 或存储过程提交进来,后端自动完成 SQL 解析、模块上下文组装、RAG 知识检索、迁移规则匹配、结构化迁移计划生成、Java/MyBatis 分层代码生成、AI 代码复审、动态编译校验、自动修复、日志追踪和迁移报告生成。

一句话讲法:

我做的是一个银行存储过程迁移 ReAct Agent 后端平台。它把迁移过程拆成 SQL 解析、RAG 检索、规则匹配、迁移计划生成、模型主导代码生成、AI 代码复审、Java Compiler 校验、修复、报告、完成验收和人工复核几个迁移阶段级工具。模型通过原生 Function Calling 自主选择工具,并在代码生成阶段按后端契约生成完整 Java/MyBatis 文件集;后端用工具白名单、taskId 校验、前置条件、契约门禁、编译和终态验收守住安全边界,最终输出可追踪、可复核、可编译的代码。

后端最重要的设计思想:

  • 模块上下文优先:单条 SQL 信息不足,必须结合模块表结构才能生成高质量 Java 对象和 Mapper XML。
  • 模型主导代码生成,后端负责契约和门禁:模型根据迁移计划、模块上下文、RAG 规范和工具观察生成完整源码文件集;后端负责校验 SQL 覆盖、命名质量、危险 DML、参数绑定、编译、复审和终态验收。
  • Agent 决策 + 后端 Guardrails:模型决定下一步工具,后端只做白名单、参数、前置条件和终态验收。
  • RAG 是企业知识增强层:Chroma 中存放代码模板、迁移规范、正反例和风险规则,提升生成质量。
  • 双重质量门禁:AI 复审负责检查 SQL 语义遗漏、敏感字段暴露和危险 DML,Java Compiler API 负责检查语法、类型和引用关系。

2. 后端整体架构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
flowchart TB
    Client["调用方"]
    Controller["Controller API 层"]
    AppService["应用服务层"]
    Agent["ReAct Agent 调度层"]
    Tool["工具白名单层"]
    Sql["SQL 解析层"]
    Rag["RAG/Chroma 知识层"]
    Rule["映射规则层"]
    CodeGen["代码生成层"]
    Review["AI 代码复审层"]
    Compiler["编译校验层"]
    Repo["Repository 持久化层"]
    MySQL["MySQL 元数据"]
    Redis["Redis 运行态"]
    FS["文件系统产物"]
    Model["DeepSeek/内部大模型"]
    Embedding["DashScope Embedding"]

    Client --> Controller
    Controller --> AppService
    AppService --> Agent
    Agent --> Model
    Agent --> Tool
    Tool --> Sql
    Tool --> Rag
    Tool --> Rule
    Tool --> CodeGen
    Tool --> Review
    Tool --> Compiler
    Rag --> Embedding
    AppService --> Repo
    Repo --> MySQL
    AppService --> Redis
    CodeGen --> FS
    Review --> Model

后端按职责可以拆成 8 层:

层级 解决的问题 核心类
API 层 暴露模块、任务、知识库、规则接口 BusinessModuleControllerMigrationTaskControllerKnowledgeBaseControllerMappingRuleController
应用服务层 编排业务流程,控制任务生命周期 BusinessModuleServiceMigrationTaskService
Agent 调度层 原生 Function Calling 工具循环,顺序执行 tool_calls 并校验前置条件 ReActAgentOrchestratorToolRegistry
Tool 层 把每一步能力封装成白名单工具 SqlParseAgentToolRagSearchAgentToolCodeGenerateAgentToolCodeReviewAgentTool
SQL/规则层 解析 SQL 特征,匹配迁移规则 SqlStatementSplitterSqlParserServiceMappingRuleService
RAG/模型层 接入 Chroma、Embedding、大模型 ChromaRagServiceChromaKnowledgeServiceDeepSeekModelClient
代码与校验层 模型主导生成代码、契约门禁、AI 复审、保存产物、动态编译 ModelCodeGenerationServiceCodeGenerationServiceCodeReviewServiceArtifactServiceJavaCompilerService
持久化层 保存任务、模块、表结构、日志、规则 MigrationTaskRepositoryModuleTableRepositoryAgentLogRepository

3. 后端主执行流程

3.1 模块级迁移推荐链路

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
sequenceDiagram
    participant C as Controller
    participant BM as BusinessModuleService
    participant MT as MigrationTaskService
    participant AG as ReActAgentOrchestrator
    participant TL as AgentTool
    participant DB as MySQL/Redis/Artifact

    C->>BM: 创建模块
    C->>BM: 保存表结构 DDL
    BM->>BM: DdlParseService 解析 TableSchema
    C->>BM: 提交一条或多条 SQL
    BM->>BM: SqlStatementSplitter 拆分 SqlUnit
    BM->>MT: 创建 MigrationTask + ModuleContext
    MT->>DB: 保存 CREATED 任务
    C->>MT: start(taskId)
    MT->>AG: 异步执行 run(taskId)
    loop ReAct 决策轮次
        AG->>Model: messages + tools(JSON Schema)
        Model-->>AG: tool_calls[]
        AG->>AG: 顺序校验白名单、taskId 和前置条件
        AG->>TL: 执行被允许的迁移阶段工具
        TL->>DB: 写状态、日志、产物、报告
        TL-->>AG: Observation
        AG->>Model: 回填 tool observation
    end
    alt 满足交付条件
        AG->>TL: finish_migration
    else 信息不足或风险过高
        AG->>TL: request_human_review
    end

3.2 一次任务从创建到结束的后端调用链

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
BusinessModuleController.createMigration
  -> BusinessModuleService.createMigrationTask
    -> 校验模块存在
    -> 查询模块表结构 ModuleTable
    -> SqlStatementSplitter 拆分多 SQL
    -> 组装 ModuleContext
    -> MigrationTaskService.create
      -> MigrationTaskRepository.save

MigrationTaskController.start
  -> MigrationTaskService.start
    -> markRunning 防重复启动
    -> migrationTaskExecutor 异步提交
    -> ReActAgentOrchestrator.run
      -> modelClient.chat(messages + tools)
      -> 模型返回一个或多个 tool_calls
      -> 顺序校验工具白名单、taskId 和工具前置条件
      -> 执行 sql_parse / rag_search / rule_match / generate_plan / generate_code / review_code / compile_java 等迁移阶段工具
      -> 编译失败时由 Agent 决定是否 repair_code
      -> 报告生成后由 Agent 显式调用 finish_migration 或 request_human_review

这个链路的关键点是:HTTP 请求只负责创建任务和启动任务,真正耗时的 Agent 流程走异步线程池。任务状态是审计投影,不再驱动固定流程;下一步由模型基于工具描述和 Observation 决定。日志、产物和报告都落库或落盘,调用方通过任务详情接口查询执行进度。

4. Controller 层类职责

4.1 BusinessModuleController

职责:业务模块相关 API 入口。

它解决的是“单 SQL 无上下文”的问题。用户先创建一个业务模块,再维护这个模块涉及的表结构,最后在模块内创建 SQL 迁移任务。

核心接口:

接口 后端动作 深挖点
POST /api/modules 创建业务模块 生成模块 ID、模块编码、默认基础包名
GET /api/modules 查询模块列表 让业务按模块沉淀迁移上下文
GET /api/modules/{moduleId} 查询模块详情 校验模块是否存在
POST /api/modules/{moduleId}/tables 新增表结构 调用 DdlParseService 解析 DDL
PUT /api/modules/{moduleId}/tables/{tableId} 修改表结构 支持用户修正自动解析结果
DELETE /api/modules/{moduleId}/tables/{tableId} 删除表结构 同步更新时间
POST /api/modules/{moduleId}/migrations 创建模块内迁移任务 组装 ModuleContext,这是核心入口

可被追问:

  • 为什么不直接让用户传 SQL?
    因为单条 SQL 缺少业务和表结构上下文。比如 SELECT * FROM user 只能看出查了 user 表,但不知道 user 表有哪些字段、哪些字段是密码或逻辑删除字段、主键是什么、Java 包名应该是什么、返回对象应该暴露哪些属性。如果只让模型基于 SQL 猜,很容易生成字段不全、字段类型错误、VO 暴露敏感字段或者方法名没有业务语义。模块级迁移先维护表结构,再提交 SQL,本质上是先建立领域上下文,再做代码生成。

  • 表结构上下文如何提升生成质量?
    表结构提供了生成代码所需的确定性信息,包括表名、字段名、字段注释、数据库类型、Java 类型、主键、是否可空、默认值和索引约束。代码生成器可以基于这些信息生成 Entity、QueryDTO、CommandDTO、VO、Mapper XML,并且能把 SELECT * 展开成明确字段列表,还能过滤 password、token、isDelete、createTime、updateTime 等不适合直接返回的字段。也就是说,表结构让生成过程从“猜代码”变成“按真实 schema 生成代码”。

  • 用户 DDL 不规范怎么办?
    当前设计是先用 DdlParseService 做规则解析,把能识别的表名、字段、注释、类型提取成 TableSchema,同时保留用户原始 rawDdl 方便追溯。如果解析结果不准确,前端允许用户修改表结构后再保存,保存时可以标记为人工修正。后续也可以扩展为“规则解析失败后调用模型补充解析”,并通过 parseSource=RULE/MODEL/MANUAL 区分来源。这样不会因为 DDL 不完全标准就阻塞整个迁移流程。

  • 多条 SQL 为什么要在后端二次拆分?
    因为前端传来的 SQL 类型和 SQL 单元不一定可靠。用户可能在一个输入框里粘贴多条 SQL,而且 SELECT、UPDATE、DELETE 可能混在一起,甚至没有分号,只靠前端下拉框会把整段脚本误判成一种类型。后端 SqlStatementSplitter 会根据 SQL 正文、行首关键字、分号和 -- 功能 注释重新拆分,再为每个 SqlUnit 独立推断类型。这样可以避免多 SQL 场景下漏生成 UPDATE/DELETE 方法。

4.2 MigrationTaskController

职责:迁移任务相关 API 入口。

它不负责具体迁移逻辑,只把任务操作转给 MigrationTaskService

核心接口:

接口 后端动作 深挖点
POST /api/migration-tasks 创建独立迁移任务 兼容单 SQL 快速模式
GET /api/migration-tasks 查询任务列表 支持状态和关键词筛选
GET /api/migration-tasks/{taskId} 查询任务详情 返回完整任务聚合
POST /api/migration-tasks/{taskId}/start 启动 Agent 异步执行,避免阻塞 HTTP
POST /api/migration-tasks/{taskId}/retry 重试任务 清空修复次数,复用源 SQL
POST /api/migration-tasks/{taskId}/cancel 取消任务 软中断,避免强杀线程
POST /api/migration-tasks/{taskId}/validate 重新编译校验 用于人工改完产物后复验
GET /api/migration-tasks/{taskId}/artifacts 查询产物列表 返回生成文件元数据和内容
GET /api/migration-tasks/{taskId}/agent-logs 查询 Agent 日志 展示 Thought/Action/Observation
GET /api/migration-tasks/{taskId}/report 查询迁移报告 返回 Markdown 报告

可被追问:

  • 为什么启动任务要异步?
    SQL 解析、RAG 检索、大模型调用、代码生成、Java 编译和自动修复都可能比较耗时。如果在 HTTP 请求线程里同步执行,前端容易超时,用户体验也不好。现在的设计是接口只负责把任务状态改为 RUNNING 并提交到线程池,前端通过任务详情和 Agent 日志轮询进度。这样既不会阻塞 HTTP 线程,也方便以后做任务队列、重试和并发控制。

  • 怎么避免重复启动同一个任务?
    MigrationTaskService 里维护了 RUNNING_STATUSES 集合,包含 RUNNINGSQL_PARSINGRAG_RETRIEVINGCODE_GENERATINGCOMPILINGFIXING 等运行中状态。启动任务时先调用 markRunning 检查当前状态,如果任务已经处于运行态,就直接抛错,避免同一个任务被多个线程同时执行,导致产物文件、编译结果和报告互相覆盖。

  • 取消任务为什么用软中断而不是强制停止线程?
    因为任务执行过程中可能正在写文件、保存数据库记录、调用 Chroma、执行编译或生成报告。如果强制停止线程,容易留下半写入产物、状态不一致或者临时目录未清理。当前做法是设置 cancelRequested=true 并把状态改成 CANCELLED,Agent 调度器在每轮工具执行前后调用 isStopped 检查,等工具执行到安全边界再退出。这种方式更稳,也更符合企业任务处理习惯。

  • 重新校验和重新生成的区别是什么?
    重新校验只针对当前已经生成的 Java 产物重新执行 Java Compiler API,不重新调用模型,也不重新生成代码,适合人工修改产物后做复验。重新生成则会重新跑 Agent 链路,可能重新执行 SQL 解析、RAG 检索、规则匹配和代码生成,会覆盖或刷新当前产物。简单说,validate 是“验现有代码”,retry 是“重新跑一遍迁移流程”。

4.3 KnowledgeBaseController

职责:Chroma 知识库维护入口。

它让项目不再依赖手动执行 Python 脚本灌库,而是通过后端 API 维护企业迁移知识。

核心接口:

接口 后端动作 深挖点
GET /api/knowledge-documents 查询知识片段 从 Chroma collection 拉取文档
POST /api/knowledge-documents 新增知识片段 生成 embedding 后 upsert
PUT /api/knowledge-documents/{id} 更新知识片段 覆盖文档、metadata、embedding
DELETE /api/knowledge-documents/{id} 删除知识片段 按 ID 删除 Chroma 文档
POST /api/knowledge-documents/search 语义检索 使用 query_embeddings 查询
POST /api/knowledge-documents/seed-defaults 写入默认企业知识 初始化 SELECT/UPDATE/INSERT/DELETE 等规则
POST /api/knowledge-documents/import 上传 txt/md 文件入库 调用 KnowledgeDocumentChunker 切分

可被追问:

  • 为什么 Chroma 查询要传 query_embeddings
    当前项目选择由后端自己调用 DashScope Embedding 生成查询向量,再把向量传给 Chroma。这样不依赖 Chroma 服务端内置 embedding function,也能保证和写入知识库时使用的模型一致。Chroma 的 query 接口在这种模式下需要 query_embeddings,如果只传文本而服务端又没有配置 embedding function,就会出现 missing field query_embeddings 这类错误。

  • 为什么写入和查询必须使用同一个 embedding 模型?
    向量检索要求写入向量和查询向量处于同一个语义空间,并且维度一致。比如一个 collection 之前用 384 维模型写入,后来查询时用 DashScope text-embedding-v4 生成 1024 维向量,Chroma 会直接报维度不一致。即使维度碰巧一样,不同模型的向量空间也不一定可比,召回质量会变差。所以写入脚本、后端配置和 Chroma collection 必须绑定同一套 embedding 模型。

  • 文档切分为什么不能简单按固定长度切?
    RAG 的召回质量很依赖 chunk 质量。固定长度切分可能把一个完整规则切断,也可能把代码块、SQL 示例、标题和解释拆散,导致检索时拿到的片段语义不完整。项目里的 KnowledgeDocumentChunker 会识别 Markdown 标题、段落和代码块,保留标题路径,超长内容再用滑动窗口和 overlap 切分。这样每个 chunk 既不会太长稀释语义,也不会太碎丢失上下文。

4.4 MappingRuleController

职责:SQL 特征到 Java 实现模式的规则维护入口。

它支撑 rule_match 工具,让存储过程特征可以被明确映射为 Java 侧实现策略。

典型规则:

  • CURSOR -> Mapper 查询 List 后 Service 层循环。
  • TRANSACTION -> Spring @Transactional
  • EXCEPTION -> try-catch + 业务异常。
  • DYNAMIC_SQL -> 高风险,人工复核。

5. 应用服务层深挖

5.1 BusinessModuleService

这个类是 ToB 场景的核心。它把项目从“单 SQL 翻译器”升级为“模块级迁移平台”。

核心方法:

方法 作用 关键逻辑
create 创建业务模块 默认补齐模块编码、基础包名、源库、目标技术栈
saveTable 保存模块表结构 没有结构化 tableSchema 时调用 DdlParseService.parse(rawDdl)
migrationTasks 查询模块历史任务 根据模块名和基础包名检索任务
createMigrationTask 创建模块级迁移任务 查询表结构、拆分 SQL、组装 ModuleContext、委托任务服务保存
buildSqlUnits 构建 SQL 单元列表 支持显式 sqlUnits,也支持从整段 SQL 自动拆分
resolveSqlType 推断 SQL 类型 SQL 正文优先,调用方传入类型只作为辅助

这里最重要的设计:

  1. 必须先有表结构再创建模块迁移任务
    如果模块没有表结构,直接抛出“当前模块还没有表结构”。这是为了防止生成器凭空猜字段。

  2. 多 SQL 后端二次拆分
    即使调用方传了 sqlUnits,如果某个单元里实际包含多条 SQL,后端还会继续拆开。这样可以避免 SELECT; UPDATE 被整体误判为 SELECT。

  3. 功能注释会保留到 sourceSql
    例如:

    1
    2
    
    -- 功能:根据用户名查询用户
    SELECT * FROM user WHERE userName = '用户名'
    

    这段注释会进入任务源 SQL,后续 SQL 解析和 RAG 检索都能读到业务意图。

  4. SQL 类型以正文为准
    如果外部传 SELECT,但 SQL 正文是 UPDATE user SET ...,最终按 UPDATE 处理。这个是多 SQL 混合任务不漏生成写方法的关键。

面试可以这样讲:

这个类解决的是上下文建模问题。我没有让用户直接丢一段 SQL 给模型,而是先创建业务模块、维护表结构,再在模块里创建迁移任务。这样生成代码时有真实字段、主键、表注释、模块包名和 SQL 功能说明,代码质量会比单 SQL 猜测稳定很多。

5.2 MigrationTaskService

这个类负责任务生命周期管理,是 Controller 和 Agent 之间的桥。

核心方法:

方法 作用 深挖点
create 创建任务聚合 初始状态为 CREATED
start 启动迁移 标记 RUNNING 后异步执行 Agent
retry 重试迁移 清空 retryCount,复用上下文
cancel 取消任务 设置 cancelRequestedCANCELLED
delete 软删除任务 标记 DELETED,不物理删文件
validate 重新校验产物 不调用模型,只重新编译当前 Java 产物
runAgent 后台执行 Agent 捕获异常并转 FAILED_NEED_MANUAL_REVIEW
markRunning 标记运行态 拦截重复启动和已删除任务

任务运行态集合:

1
2
3
4
RUNNING, SQL_PARSING, SQL_PARSED, RAG_RETRIEVING,
RULE_MATCHING, PLAN_GENERATING, CODE_GENERATING,
CODE_REVIEWING, SYNTAX_CHECKING, COMPILING, TEST_GENERATING,
TEST_RUNNING, FIXING

设计重点:

  • 防重复启动:如果任务处于运行中状态,再次 start 会抛错,避免多个线程同时写产物。
  • 异步执行:HTTP 线程只提交任务,Agent 在线程池中跑。
  • 异常可见:后台异常不会被吞掉,会写入任务风险列表并转人工复核。
  • 软取消:取消任务只是标记状态,Agent 在每轮工具执行前后检查 isStopped,避免粗暴中断正在写文件或编译的线程。

可被追问:

  • 如果 Agent 执行到一半服务重启怎么办?
    当前 MySQL 会保存任务状态、模块上下文、解析结果、RAG 结果、命中规则、产物快照、编译结果和报告,Redis 保存运行态上下文和 Agent 记忆。如果服务中途重启,至少不会丢失已经落库的执行快照。严格生产环境可以再加一个任务恢复扫描器:应用启动时扫描长时间停留在 RUNNING/COMPILING/FIXING 等中间状态的任务,根据最后日志判断是转人工复核,还是从某个工具步骤继续执行。这个项目当前更偏面试和演示闭环,所以先采用“异常可见 + 人工复核”策略。

  • 为什么删除是软删除?
    因为迁移任务、Agent 日志、生成产物和报告都有审计价值。银行系统迁移不是普通草稿删除,后续可能需要追溯某段代码为什么生成、哪一步失败、谁做了人工复核。如果直接物理删除,会影响排查和复盘。当前先把任务标记为 DELETED,不立即删除文件和历史记录;如果后续要做物理清理,也应该加审计日志和清理策略,比如只清理超过保留期的已删除任务。

5.3 DdlParseService

职责:把用户粘贴的建表语句解析成结构化 TableSchema

输入可以是类似:

1
2
3
4
5
create table user (
  id bigint auto_increment comment 'id' primary key,
  userName varchar(256) null comment '用户昵称',
  isDelete tinyint default 0 not null comment '是否删除'
) comment '用户';

输出结构:

  • 表名。
  • 表注释。
  • 字段列表。
  • 字段数据库类型。
  • Java 字段名。
  • Java 类型。
  • 是否主键。
  • 是否可空。
  • 字段注释。

深挖点:

  • 为什么 DDL 解析要独立成服务?
    因为后续代码生成、VO 字段过滤、Mapper XML 字段展开都依赖表结构。单独封装后可以替换成更强的解析器或模型解析。

  • 用户输入不规范怎么办?
    当前先用规则解析,保存 parseSource=RULE。如果后续接模型解析或人工修正,可以用 parseSource=MODEL/MANUAL 区分来源。

5.4 ModelCodeGenerationServiceCodeGenerationService

这是代码质量最核心的链路。新的主路径是 ModelCodeGenerationService,旧的 CodeGenerationService 保留为模型不可用时的兜底或测试夹具。

主路径的分工是:

  • 模型负责源码设计和生成:按迁移计划、模块上下文、SQL 单元、表结构、RAG 规范生成完整文件集。
  • 后端负责契约门禁:校验文件路径、package、类名、SQL 覆盖、机械命名、高风险 SQL、动态排序和空文件。
  • 旧模板生成器负责兜底:当模型调用异常时允许生成保守代码,并在风险中标记“已降级到模板生成器”。

模型主导生成的核心流程:

1
2
3
4
5
6
7
8
generate_code
  -> ModelCodeGenerationService.generate
    -> 模型返回代码生成蓝图 GeneratedCodeBlueprint JSON
    -> 校验文件路径、SQL 覆盖、命名和风险门禁
    -> 按蓝图逐个文件生成原始 Java/XML 源码正文
    -> 校验 package、类名、内容完整性和安全风险
    -> ArtifactService.save
  -> 若模型服务不可用,降级 CodeGenerationService.generate 并记录风险

关键设计点:

  1. 结构化文件集返回

    模型第一步必须返回 GeneratedCodeBlueprint JSON,包含 summaryfilesrisks。每个文件只包含 fileNametypepurposerelatedSqlIds,不包含源码正文。

  2. 单文件源码生成

    模型第二步按蓝图一次只生成一个文件的原始 Java/XML 正文,不返回 JSON,不返回 Markdown。这样避免 50 条 SQL 对应的大量源码被塞进一个 JSON content 字符串后截断。

  3. SQL 覆盖门禁

    每个 SqlUnit.sqlId 必须被生成文件的 relatedSqlIds 覆盖;如果不能自动生成,必须在 risks 中明确写出该 SQL 需要人工复核。这样能避免多 SQL 场景只生成第一张表或第一条查询。

  4. 命名质量门禁

    后端会拒绝 TestQueryBankUser2VOcount1AsTotaldateFinishTimeAsStatDate 这类机械命名。模型必须按业务语义设计 DTO、VO、Mapper 和 Service,而不是把 SQL 表达式硬转成字段名。

  5. 安全门禁

    无 WHERE 的 UPDATE/DELETE、无法白名单校验的动态排序、危险 ${}、宽泛 DML 必须声明人工复核风险,不能生成可直接执行的方法。

  6. 类型和结构质量

    金额字段应使用 BigDecimal,时间字段使用 LocalDateTime/LocalDate,ID 使用 Long;返回对象按业务场景拆分,避免一个大而全的 TestVO 承载所有 SQL 字段。

  7. 兜底生成器边界

    CodeGenerationService 仍会保留 SQL 类型推断、SELECT 字段过滤、固定条件处理等保守能力,但它不再是正常代码质量的主路径。

可被追问:

  • 为什么现在改成模型主导生成?
    因为模块级银行场景包含多表、多 SQL、联表、聚合、分页、动态排序和风险控制,模板生成器很难靠堆规则持续提升命名、对象边界和业务语义。模型更适合根据上下文设计文件集,后端则用契约门禁约束它不能乱来。

  • 如何处理 SELECT *
    SELECT * 不会原样照搬到 VO。生成器会先根据 SQL 涉及的表找到模块里的 TableSchema,把 * 展开成明确字段列表,再过滤明显不适合返回的字段,比如 password、token、secret、salt、isDelete、createTime、updateTime、version 等。这样生成的 VO 更接近接口返回对象,而不是把数据库整行直接暴露出去。固定条件例如 isDelete = 0 会保留在 XML 里,不会变成 QueryDTO 字段。

  • 如何避免生成多余代码?
    第一层是在 SQL 拆分阶段把多条 SQL 拆成多个 SqlUnit;第二层是迁移计划要求每条 SQL 有明确用途和风险;第三层是模型生成文件必须声明 relatedSqlIds;第四层是后端契约校验,发现漏覆盖、机械命名或高风险 SQL 未声明复核就拒绝本次代码生成。

5.5 MigrationPlanService

职责:在代码生成前调用大模型生成结构化迁移计划。

这是代码生成前的语义规划位置。它让模型基于源 SQL、模块表结构、SQL 解析结果、RAG 企业规范和映射规则,输出一个可被后端和代码生成模型共同消费的 MigrationPlan

迁移计划通常包含:

  • 每条 SQL 对应的业务意图。
  • 操作类型,例如 QUERY、UPDATE、INSERT、DELETE。
  • 推荐方法名。
  • 输入对象和返回对象建议。
  • 需要保留的 WHERE、ORDER BY、LIMIT、逻辑删除条件。
  • 是否需要事务、批处理、分页、人工复核。
  • 风险说明。

为什么要加这一层:

RAG 和规则本身不会自动改变代码生成结果,必须有一个“结构化计划”把企业规范、SQL 意图和源码生成连接起来。模型先理解“这条 SQL 是做什么”,再在代码生成阶段根据契约生成文件集;后端负责验证这个文件集是否可交付。

可被追问:

  • 为什么不让代码生成阶段直接吃 RAG 原文?
    因为 RAG 返回的是自然语言片段和案例,如果直接塞给生成阶段,模型容易照抄局部示例而忽略当前 SQL 和表结构。MigrationPlanService 会先把自然语言知识转成结构化计划,例如 operation、methodName、riskLevel、manualReviewRequired;ModelCodeGenerationService 再同时读取计划、表结构、SQL 单元和 RAG 规范生成文件集,最后由后端契约门禁兜底。

  • 如果模型计划生成失败怎么办?
    服务会走规则兜底,根据 SQL 类型、表结构和解析特征生成保守计划,并把来源标记为 fallback。这样模型不可用时不会让整条链路直接断掉,但会在风险和报告中提示人工复核。

5.6 CodeReviewService

职责:代码生成后的大模型复审。

它位于 generate_codecompile_java 之间,解决“代码能编译不代表忠实实现 SQL”的问题。编译器只能发现语法、类型和引用错误,无法判断 UPDATE 是否漏了 isDelete = 0,也无法判断 VO 是否暴露了 password。

复审输入包括:

  • 源 SQL。
  • 迁移计划 JSON。
  • SQL 解析结果。
  • RAG 企业规范与案例。
  • 命中的映射规则。
  • 已生成的 Java / XML 代码产物。

复审输出是结构化 CodeReviewResult

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "passed": false,
  "manualReviewRequired": true,
  "summary": "UPDATE 方法遗漏逻辑删除条件",
  "issues": [
    {
      "severity": "HIGH",
      "category": "SQL_SEMANTIC",
      "fileName": "UserMapper.xml",
      "line": 18,
      "message": "UPDATE 丢失 isDelete = 0 条件",
      "suggestion": "在 WHERE 中补充 AND isDelete = 0"
    }
  ],
  "suggestions": ["建议补充更新失败场景测试"]
}

重点检查:

  • 是否遗漏源 SQL 中的 SELECT、INSERT、UPDATE、DELETE。
  • Mapper XML 是否保留 WHERE、ORDER BY、LIMIT 和固定条件。
  • DTO 是否暴露了不该由调用方传入的审计字段。
  • VO 是否泄露 password、token、secret、isDelete 等字段。
  • UPDATE/DELETE 是否存在无 WHERE、宽泛 WHERE 或物理删除风险。
  • 是否生成了 SQL 无关的多余 CRUD、Controller 或伪造业务流程。

可被追问:

  • AI 复审和编译校验有什么区别?
    AI 复审看业务语义和工程规范,例如 SQL 条件是否丢失、字段是否越权暴露、方法是否多生成;编译器看语法、类型、import 和类引用。两者是互补关系:AI 复审像代码评审,编译器像确定性的语法门禁。

  • AI 复审发现问题后为什么还继续编译和生成报告?
    因为复审问题本身也是证据链的一部分。即使复审失败,继续编译能告诉用户“代码语法是否可接手”,最终报告会同时展示复审问题和编译结果。最终状态会综合判断:编译通过但复审要求人工复核时,任务仍进入 FAILED_NEED_MANUAL_REVIEW

  • 模型复审失败怎么办?
    CodeReviewService 会生成 RULE_FALLBACK 结果,标记 manualReviewRequired=true。这样不会因为模型返回非 JSON 或调用异常而误判为通过。

5.7 KnowledgeDocumentChunker

职责:上传文档入 Chroma 前的结构化切分。

它不是简单按固定长度截断,而是做了 Markdown 结构感知:

  • 识别一级到六级标题。
  • 保护代码块,不在代码块中间硬切。
  • 保留标题路径 headingPath
  • 普通段落按目标长度合并。
  • 超长块使用滑动窗口切分,保留 overlap。
  • 记录原文 offset,方便后续溯源。

核心参数:

1
2
3
TARGET_CHARS = 1200
MAX_CHARS = 1800
OVERLAP_CHARS = 180

为什么这是面试加分点:

RAG 的效果不只取决于向量库,还取决于文档切分。如果切得太碎,语义不完整;切得太长,相似度会被稀释。这里按标题、段落、代码块做结构化切分,能让每个 chunk 既有完整语义,又适合向量检索。

5.8 ReportService

职责:生成迁移报告。

报告通常包含:

  • 任务基本信息。
  • 源 SQL 摘要。
  • SQL 特征。
  • 命中规则。
  • RAG 检索结果。
  • 生成产物列表。
  • 编译结果。
  • 风险点。
  • 人工复核建议。

它的意义是让一次 Agent 执行从“黑盒输出代码”变成“可交付、可复核的迁移记录”。

5.7 RedisStateService

职责:保存任务运行态信息和 Agent 记忆。

保存内容:

  • 任务快照。
  • Agent 日志。
  • 短期记忆:按 taskId 保存当前 Agent 的 messages,用于同一次任务的上下文恢复和排障。
  • 长期记忆:按模块名、包名和表名保存最近若干次迁移经验摘要,用于后续迁移计划生成和 AI 代码复审。

为什么同时用 MySQL 和 Redis:

  • MySQL 保存最终事实数据:任务、报告、产物索引、日志。
  • Redis 保存运行态和记忆热缓存:便于长流程任务中快速读取、过期清理和跨任务复用。

短期记忆和长期记忆的区别:

类型 Redis Key 维度 生命周期 应用场景
短期记忆 bank-migration:agent-memory:{taskId} 默认 168 小时 当前任务多轮 ReAct messages、工具 observation、断点续迁排障
长期记忆 bank-migration:long-memory:module:{module} / bank-migration:long-memory:table:{table} 默认 90 天 同模块历史生成策略、复审高风险、人工复核经验

短期记忆由 ReActAgentOrchestrator 每轮保存,启动时如果 Redis 中已有同版本 system prompt 的 messages,会恢复为当前任务上下文。长期记忆由 FinishMigrationAgentTool 在任务通过验收后沉淀,MigrationPlanServiceCodeReviewService 在构造模型提示词时召回。这样 Redis 不只是“缓存日志”,而是真正参与模型计划生成和复审质量提升。

5.8 AgentMemoryService

职责:把迁移任务结果压缩成长期记忆,并把历史记忆格式化为模型可读上下文。

它不会保存完整 SQL 或生成源码,而是保存:

  • 模块名、基础包名、涉及表名。
  • SQL 类型集合。
  • 迁移计划摘要。
  • AI 复审高风险问题。
  • 编译结果摘要。
  • 最终状态和是否需要人工复核。

为什么不把长期记忆直接写进 Chroma:

Chroma 更适合保存企业规范、模板、案例文档;Redis 长期记忆更像“近期热经验”,适合同一模块连续迁移时快速复用。这个项目里 Redis 长期记忆默认有 TTL 和条数上限,避免历史错误无限累积。生产环境可以再把人工确认后的高质量长期记忆同步到 Chroma,形成正式知识库。

6. Agent 调度层深挖

6.1 ReActAgentOrchestrator

这是整个 ReAct 工具循环的控制核心。它不再固定基础工具流,而是把 ToolRegistry 导出的 OpenAI tools JSON Schema 发给模型,让模型基于当前任务上下文和历史 Observation 自主选择下一步工具。

典型路径可能是:

1
sql_parse -> rag_search -> rule_match -> generate_plan -> generate_code -> review_code -> compile_java

但这不是硬编码流程。编译失败时,Agent 可以选择 repair_code;风险过高时,Agent 可以选择 request_human_review;报告生成后,Agent 需要显式调用 finish_migration 才能完成任务。

关键字段:

字段 含义
MAX_ROUNDS = 18 最大 ReAct 决策轮次,防止工具调用和修复循环失控
modelClient 调用 DeepSeek 或内部模型
toolRegistry 工具白名单,并导出 OpenAI tools JSON Schema
agentLogRepository 保存每轮日志
redisStateService 保存模型上下文记忆
toolInputReader 解析工具 JSON 参数并统一处理非法参数

执行逻辑:

  1. 清空当前任务旧日志。
  2. 构建 system prompt。
  3. 每轮把当前任务上下文和工具 JSON Schema 发给模型。
  4. 模型通过原生 Function Calling 返回一个或多个 tool_calls
  5. 调度器按顺序校验工具白名单、taskId 和工具前置条件。
  6. 被允许的工具才会执行;被拒绝的调用会变成 Observation 回填给模型。
  7. 如果同一批次中某个工具失败,后续 tool_call 会补充“未执行”Observation,保证 OpenAI tools 协议完整。
  8. 写入 AgentLog
  9. 把 Observation 回填到模型上下文。
  10. Agent 通过 finish_migrationrequest_human_review 显式结束任务。

最值得讲的点:

  • 模型真正决定下一步工具。后端不替模型改工具,只做白名单、参数和前置条件校验。
  • 如果模型调用错工具,会收到拒绝 Observation。例如未注册工具、缺少 taskId、串任务、没有 SQL 解析结果、没有代码产物、编译未通过等。
  • 每轮都有日志,可以解释 Agent 为什么失败、卡在哪一步。
  • 生成后复审,代码生成后先进入 review_code,让模型检查 SQL 语义、字段暴露和危险 DML,再进入编译。
  • 修复后重新复审repair_code 可能改动源码,所以修复成功后会回到 review_code -> compile_java,避免只审查修复前的代码。
  • 最大轮次保护,防止编译失败、复审和修复形成死循环。

6.2 原生 Function Calling 工具循环

职责:让模型基于真实工具描述和 JSON Schema 自主选择工具,调度器顺序执行并回填 Observation。

协议形态:

1
assistant.tool_calls[] -> 后端执行工具 -> tool observation messages

为什么这样做:

  • 工具调用是模型原生结构化输出,不再依赖正则解析 <|call_tool|> 文本。
  • 每个工具都有真实 namedescriptionparameters JSON Schema。
  • 多个 tool_calls 可以在同一轮返回,但后端按顺序执行,不并行。
  • 失败、拒绝或终态会阻断本批次后续工具,同时补齐未执行工具的 Observation,保证协议完整。

面试讲法:

我使用 OpenAI tools 风格的原生 Function Calling。模型不是输出伪协议文本,而是返回结构化 tool_calls;后端负责白名单、taskId、参数和前置条件校验,既保留 Agent 决策权,也守住企业迁移的安全边界。

6.3 ToolRegistry

职责:工具白名单注册表。

作用:

  • 启动时收集所有 AgentTool Bean。
  • 按工具名建立 Map。
  • 把工具导出为 OpenAI tools JSON Schema。
  • Agent 执行时只能通过工具名获取已注册工具。
  • 如果模型输出未注册工具,拒绝执行并返回 Observation。

安全意义:

  • 模型不能执行系统命令。
  • 模型不能访问未暴露能力。
  • 所有工具入参、出参都由 Java 后端控制。

7. Tool 层执行细节

工具类 工具名 状态推进 作用
SqlParseAgentTool sql_parse SQL_PARSING -> SQL_PARSED 调用 SQL 解析,写回表、字段、特征、风险
RagSearchAgentTool rag_search RAG_RETRIEVING 调用 Chroma 检索迁移知识
RuleMatchAgentTool rule_match RULE_MATCHING 根据 SQL 特征匹配 Java 映射规则
MigrationPlanAgentTool generate_plan PLAN_GENERATING 调用模型生成结构化迁移计划,把 RAG/规则转成生成器可消费的计划
CodeGenerateAgentTool generate_code CODE_GENERATING 调用模型主导代码生成服务,执行契约门禁并保存产物
CodeReviewAgentTool review_code CODE_REVIEWING 生成后 AI 复审,检查 SQL 语义遗漏、危险 DML、敏感字段暴露和多余代码
CompileJavaAgentTool compile_java COMPILING 编译 Java 产物,写回结果
RepairCodeAgentTool repair_code FIXING 根据编译错误修复代码,控制修复次数
JunitGenerateAgentTool generate_junit TEST_GENERATING 生成 JUnit 骨架
GenerateReportAgentTool generate_report RUNNING 汇总报告,但不决定最终终态
FinishMigrationAgentTool finish_migration PASSED 验收报告、编译和 AI 复审结果后完成任务
RequestHumanReviewAgentTool request_human_review FAILED_NEED_MANUAL_REVIEW Agent 主动请求人工复核并记录原因

为什么要拆成这么多工具:

  • 每一步职责单一,失败点清晰。
  • 每一步都能记录日志和耗时。
  • 每一步都可以独立测试。
  • 模型真正决定下一步工具,后端只做白名单、参数和前置条件守门。
  • 后续可以替换某个工具实现,比如把规则 SQL 解析换成更强 Parser。

8. SQL 解析层深挖

8.1 SqlStatementSplitter

职责:把用户提交的一整段 SQL 拆成多条结构化语句。

它需要处理:

  • 多条 SQL 有分号。
  • 多条 SQL 没有分号。
  • SQL 前有 -- 功能:xxx 注释。
  • 字符串里可能包含分号。
  • 一段脚本中同时存在 SELECT、UPDATE、DELETE。

输出:

  • SQL 正文。
  • SQL 类型。
  • 功能注释。

深挖点:

调用方传来的 SQL 类型不可信,尤其是多 SQL 场景。拆分器必须在后端根据 SQL 正文识别类型,否则一个任务里 UPDATE 可能被误当成 SELECT,导致只生成查询方法。

8.2 SqlParserService

职责:识别 SQL 片段、表、字段、特征和风险。

解析结果 SqlParseResult 包含:

  • segments:SQL 片段。
  • tables:涉及表。
  • columns:涉及字段。
  • features:SQL/存储过程特征。
  • risks:风险点。

典型特征:

  • SELECTINSERTUPDATEDELETE
  • CURSOR
  • LOOP
  • TRANSACTION
  • EXCEPTION
  • DYNAMIC_SQL

作用:

  • 给 RAG 构造检索文本。
  • 给规则匹配提供 sourceFeature。
  • 给报告生成提供解析依据。
  • 给风险复核提供原因。

9. RAG 与 Chroma 深挖

9.1 ChromaRagService

职责:任务执行时检索相似迁移知识。

执行流程:

1
2
3
4
5
6
7
search(SqlParseResult)
  -> resolveCollectionId
  -> buildQueryText
  -> embeddingClient.embed
  -> POST Chroma /query with query_embeddings
  -> parseDocuments
  -> List<RagDocument>

为什么使用 query_embeddings

  • Chroma 服务端不一定配置 embedding function。
  • 写入和查询必须使用同一个 embedding 模型,才能保证向量维度和语义空间一致。
  • 之前报错 missing field query_embeddings 的本质就是调用了需要向量的 query 接口,但请求体没有传查询向量。

为什么会有维度不一致:

  • 例如旧 collection 用 384 维模型写入,新脚本用 DashScope text-embedding-v4 生成 1024 维。
  • Chroma collection 的向量维度一旦确定,就不能混用不同维度。
  • 解决方式是删除旧 collection 或新建 collection,并保证写入和查询模型一致。

buildQueryText 为什么包含 features/tables/risks/sql:

  • 单纯拿原 SQL 检索,可能召回不到规则文档。
  • 加上 features 可以命中“UPDATE 生成规则”“动态 SQL 风险”等知识。
  • 加上 tables 可以命中领域表相关模板。
  • 加上 risks 可以命中人工复核规范。

9.2 ChromaKnowledgeService

职责:知识片段维护服务。

核心能力:

  • 新增知识。
  • 更新知识。
  • 删除知识。
  • 列表查询。
  • 语义搜索。
  • 默认知识初始化。
  • 批量创建。

写入 Chroma 时保存:

  • ids:文档 ID。
  • documents:正文。
  • metadatas:标题、类别、SQL 类型、场景、来源、标签、创建时间、更新时间。
  • embeddings:由后端 EmbeddingClient 生成的向量。

buildEmbeddingText 为什么不只向量化正文:

1
2
3
4
5
6
title
category
sqlType
scenario
tags
content

原因是标题、SQL 类型、场景和标签本身就是重要检索信号。比如用户输入 UPDATE SQL,如果正文没写“局部更新”,但 metadata 里有 sqlType=UPDATEscenario=局部更新,检索会更容易命中相关模板。

默认知识库内容覆盖:

  • SELECT 单表查询最小生成规则。
  • UPDATE 局部更新生成规则。
  • INSERT 新增生成规则。
  • DELETE 与逻辑删除生成规则。
  • Keyset 分页查询模板。
  • 批处理 SQL 迁移策略。
  • 动态 SQL 风险处理。
  • 生成代码反例约束。

面试讲法:

RAG 不是为了让模型照抄一段代码,而是给迁移计划和代码生成模型提供企业级迁移规范、反例和风险边界。真正产出的源码由模型按契约生成,但必须经过后端契约门禁、AI 复审和 Java Compiler 校验。

10. 映射规则层深挖

10.1 MappingRuleService

职责:根据 SQL 解析出的 features 匹配迁移规则。

规则模型 MappingRule 包含:

  • sourceFeature:源 SQL/存储过程特征。
  • targetPattern:Java 侧实现模式。
  • confidence:置信度。
  • riskLevel:风险等级。
  • description:说明和人工复核建议。

MySQL 初始化规则示例:

特征 Java 模式 风险
CURSOR Mapper 查询 List 后 Service for 循环处理 MEDIUM
LOOP Java for/while 循环 MEDIUM
TRANSACTION Spring @Transactional HIGH
EXCEPTION try-catch + 业务异常 MEDIUM
DYNAMIC_SQL 标记高风险并人工复核 HIGH
OUT_PARAM OUT 参数迁移为返回 DTO LOW

为什么规则层必要:

  • 模型容易忽略存储过程语义差异。
  • 规则能把特征映射成确定的工程策略。
  • 高风险规则可以影响 manualReviewRequired
  • 报告里能解释为什么要人工复核。

11. 质量校验与修复闭环

11.1 JavaCompilerService

职责:对生成的 Java 产物做真实编译。

核心价值:

  • 不靠肉眼判断代码是否正确。
  • 不只检查字符串格式。
  • 使用 JDK JavaCompiler 返回真实错误。

编译错误结构:

1
2
3
4
5
6
{
  "file": "UserServiceImpl.java",
  "line": 42,
  "message": "cannot find symbol",
  "severity": "ERROR"
}

可被追问:

  • 为什么编译校验重要?
    因为代码生成结果“看起来像 Java”不代表真的能编译。尤其是 Mapper、Service、DTO 之间存在类名、包名、import、方法签名、返回类型的联动,一处不一致就会导致后续开发无法接手。Java Compiler API 是最低成本、最确定的一道质量门禁,可以把错误结构化成文件、行号、错误信息,既方便前端展示,也方便自动修复工具消费。

  • 编译通过是否等于逻辑正确?
    不等于。编译通过只能说明语法、类型、引用关系是正确的,不能证明 SQL 语义和原存储过程完全一致。比如事务边界、异常处理、批处理顺序、金额精度、分页语义、动态 SQL 白名单都需要进一步校验。真实企业场景还要结合 JUnit、原 SQL 与 Java 方法的结果对比、沙箱数据验证和人工复核。所以这个平台定位是“辅助迁移和提高效率”,不是完全替代人工合并。

11.2 RepairCodeAgentTool

职责:编译失败后的自动修复。

设计边界:

  • 最多修复配置次数。
  • 主要处理保守修复。
  • 修不好就转人工复核。

为什么修复不能无限循环:

  • 弱模型或模板错误可能导致反复失败。
  • 无限修复会浪费资源。
  • 企业场景更重视可控性,超过阈值应该交给人工。

12. 持久化模型与数据库设计

12.1 business_module

保存业务模块。

关键字段:

  • module_id:模块 ID。
  • name:模块名称。
  • code:模块编码。
  • base_package:生成代码基础包名。
  • source_db:源数据库。
  • target_stack:目标技术栈。

设计意义:

  • 把 SQL 迁移按业务模块组织。
  • 支持一个模块内持续添加表和 SQL。
  • 生成代码时可以使用稳定基础包名。

12.2 module_table

保存模块表结构。

关键字段:

  • raw_ddl:用户原始 DDL。
  • table_schema_json:解析后的结构化表定义。
  • parse_source:解析来源。

设计意义:

  • 保留原始 DDL,便于追溯。
  • 保存结构化 JSON,便于代码生成。
  • 支持后续人工修正或模型解析替换规则解析。

12.3 migration_task

保存迁移任务主数据。

关键字段:

  • source_sql:原始 SQL 或模块 SQL 汇总。
  • module_context_json:模块上下文。
  • status:任务状态。
  • parse_result_json:SQL 解析结果。
  • rag_documents_json:RAG 检索结果。
  • matched_rules_json:命中规则。
  • migration_plan_json:大模型生成或规则兜底的结构化迁移计划。
  • code_review_result_json:生成后 AI 代码复审结果。
  • artifacts_json:生成产物快照。
  • compile_result_json:编译结果。
  • report_markdown:迁移报告。
  • risks_json:风险点。

设计意义:

  • 一个任务就是一次迁移执行的完整快照。
  • 即使产物文件丢失,也能从 JSON 快照看到执行过程。
  • 支持面试中讲“可观测、可追踪、可复核”。

12.4 agent_log

保存 Agent 每轮执行日志。

关键字段:

  • round_no:轮次。
  • thought:模型思考摘要。
  • action:工具名。
  • action_input:工具入参。
  • observation:工具结果。
  • success:是否成功。
  • cost_ms:耗时。

设计意义:

  • 能定位失败发生在哪一步。
  • 能分析每个工具耗时。
  • 能解释 Agent 为什么生成某些代码。

12.5 mapping_rule

保存迁移规则。

设计意义:

  • 把专家经验数据化。
  • 支持后续持续维护规则。
  • 高风险规则可以进入报告和人工复核。

13. 领域模型类职责

职责
MigrationTask 任务聚合根,保存一次迁移执行的所有状态、上下文、结果和风险。
MigrationStatus 任务状态机枚举,描述任务当前处于解析、检索、生成、编译、修复还是终态。
BusinessModule 业务模块元数据。
ModuleTable 模块表结构记录,包含原始 DDL 和结构化表模型。
ModuleContext 模块级迁移上下文,包含基础包、表结构和 SQL 单元。
TableSchema 单张表的结构化模型。
TableColumn 单个字段的结构化模型。
SqlUnit 一条待迁移 SQL 的结构化模型。
SqlParseResult SQL 解析结果。
SqlSegment SQL 分片。
MappingRule 映射规则。
RiskLevel 风险等级。
RagDocument RAG 检索结果。
KnowledgeDocument Chroma 知识库文档。
Artifact 生成产物。
ArtifactType 产物类型。
CompileResult 编译结果。
CompileError 编译错误明细。
AgentLog Agent 执行日志。

14. Repository 层职责

职责 深挖点
MigrationTaskRepository 保存和查询迁移任务 复杂对象通过 JSON 字段持久化
BusinessModuleRepository 保存和查询业务模块 支持模块工作台
ModuleTableRepository 保存和查询模块表结构 表结构是代码生成上下文
MappingRuleRepository 保存和查询映射规则 支持规则持续维护
AgentLogRepository 保存和查询 Agent 日志 支撑可观测性
JsonColumnMapper 对象和 JSON 字段互转 避免手写重复序列化逻辑

为什么不用大量关系表拆得很细:

  • 迁移任务中的解析结果、RAG 文档、产物列表、编译结果天然是一次执行快照。
  • JSON 字段更适合保存快照型、结构变化较快的数据。
  • 核心检索字段,如状态、模块名、创建时间,单独建索引即可。

15. 配置与中间件

后端真实运行依赖:

中间件 用途
MySQL 保存模块、任务、表结构、规则、日志
Redis 保存运行态任务快照、Agent 记忆、临时日志
Chroma 保存 RAG 知识向量
DeepSeek 或内部模型 通过原生 Function Calling 选择工具,并辅助迁移计划、代码复审和修复建议
DashScope Embedding 生成查询向量和文档向量
文件系统 保存生成的 Java/XML/JUnit/报告产物

配置类对应关系:

配置类 配置内容
MigrationProperties artifact 目录、最大修复次数、Redis TTL、异步线程池
ModelProperties 模型 provider、base-url、api-key、model、timeout
EmbeddingProperties embedding base-url、api-key、model、timeout
RagProperties Chroma base-url、tenant、database、collection、topK

15.1 本轮代码生成踩坑与可讲述知识点

这一轮排查的价值不只是“修了几个 bug”,更适合在面试里讲成:模型主导代码生成在真实工程里一定会遇到输出漂移、语义遗漏和任务边界失控,后端必须用契约、门禁和回归测试把它收住。

坑 1:模型 JSON 结构漂移,导致知识库总结反序列化失败

现象:

1
2
知识库规范总结 返回 JSON 无法解析:
Cannot deserialize value of type `java.lang.String` from Array value

根因是 KnowledgeSummary 里一些字段定义为 String,例如 namingConventions 的 value、CodePatternRule.example/applyToReferenceCodeSnippet.purpose/code。但模型很容易把“多个规则”“多个适用场景”“多行代码片段”输出成数组。JSON 本身是合法的,只是和 Java DTO 的严格类型不一致。

工程处理:

  • 不能只改 prompt,因为模型仍可能在长上下文或多规则场景下输出数组。
  • 不能全局放宽 ObjectMapper,否则会污染迁移计划、工具参数、代码结构规划这些必须严格校验的合同。
  • 最终做法是只在知识库总结的自由文本字段上做局部柔性反序列化:字符串正常读取,数组或简单对象折叠成可读文本,其他阶段继续保持严格契约。

面试讲法:

这说明模型输出不是传统后端 DTO,它有“结构漂移”的概率。我的处理不是简单吞异常,而是按字段业务性质分层:迁移计划、工具参数、代码结构规划属于硬合同,必须严格;知识库总结属于提示词上下文里的规则摘要,可以做局部容错。这样既提升稳定性,又不牺牲关键链路的安全边界。

坑 2:ORDER BY 锚点误判,暴露了“字符串匹配”和“语义等价”的差异

现象:

源 SQL 里有:

1
ORDER BY id DESC

但后端门禁报:

1
生成代码疑似丢失 SQL 语义锚点:缺少 ORDER BY id DESC

这个问题有两层:

  • 第一层是文本归一化不够,ORDER BY id DESC 和内部匹配用的 orderby 因为空白处理不同可能误判。
  • 第二层是生成代码可能不用注解 SQL,而是用 MyBatis-Plus Wrapper,例如 orderBy(true, false, Entity::getId),它和 ORDER BY id DESC 语义等价,但不是同一段字符串。

工程处理:

  • 语义锚点匹配先做统一归一化:大小写、引号、下划线、空白都要处理。
  • 对排序方向做等价识别:DESC 可以对应 MyBatis-Plus 的 isAsc=falseASC 可以对应 isAsc=true
  • 同时保留负例测试:如果生成代码真的没有排序,仍然必须被门禁拦截。

面试讲法:

质量门禁不能只做粗暴字符串包含,否则会误杀合法实现;但也不能因为误杀就放弃门禁。正确做法是把 SQL 的关键语义抽成锚点,再识别几种工程上等价的落地方式,比如注解 SQL 和 MyBatis-Plus Wrapper。这个点体现了后端 Guardrails 的难点:既要严格阻断语义丢失,又要理解框架表达方式。

坑 3:用户只传 7 条 SQL,模型却生成 8 个迁移操作

现象:

用户输入的 SQL 数量是 7 条,但迁移计划阶段出现了 8 个 operation。这类问题本质上是模型把上下文里的案例、旧记忆或自己的推断扩展成了“额外 SQL”。

风险:

  • 后续代码生成会多生成 Service/DTO/VO。
  • SQL 覆盖关系变乱,relatedSqlIds 可能指向不存在的 SQL。
  • 人工看日志会觉得系统“吞了或造了 SQL”,可信度下降。

工程处理:

  • moduleContext.sqlUnits 是当前任务的硬边界,模型不能改变任务 SQL 集合。
  • 迁移计划归一化时只保留当前任务内的 sqlId
  • 未知 sqlId 或重复 sqlId 的 operation 记录风险并丢弃。
  • 如果模型漏掉真实 SQL,则由后端按 SQL 正文补齐兜底 operation。

面试讲法:

模型可以帮助理解 SQL 意图,但不能决定任务边界。任务边界必须来自用户输入和后端拆分出的 SqlUnit。我把模型计划当成建议,而不是事实来源;最终计划必须被收敛到当前任务的 SQL 集合里。这样就避免了“7 条 SQL 变 8 个操作”这种幻觉进入代码生成。

坑 4:只靠 AI 复审不够,必须有确定性契约门禁

这几个问题都说明:AI 复审适合发现语义风险,但不能作为唯一验收者。后端需要确定性门禁拦住这些问题:

  • 文件数量异常,避免一条 SQL 固定生成六件套。
  • 机械命名,例如 VariantAqueryXxxForSqlHash
  • SQL 覆盖缺失,每个 SqlUnit 必须被产物覆盖或声明人工复核。
  • 固定条件、ORDER BYLIMIT/OFFSET 等 SQL 锚点不能丢。
  • 高风险 SQL 不能生成可执行产物。
  • Mapper/Service/Entity 必须符合 MyBatis-Plus 工程结构。

面试讲法:

我没有把模型当成可信执行器,而是当成候选生成器。候选代码必须经过后端契约门禁、AI 复审和 Java Compiler 三层验收。确定性门禁负责拦截可静态判断的问题,AI 复审负责看业务语义,编译器负责类型和引用关系。这三层互补,才能让代码生成从 Demo 走向可交付。

坑 5:调试这类问题要先建立反馈环,而不是盯着模型猜

这轮修复的方式可以作为项目工程能力来讲:

  1. 先用单元测试复现 START_ARRAY 反序列化失败。
  2. 再用单元测试复现 ORDER BY 锚点误判。
  3. 再补测试复现“7 条 SQL 被模型扩成 8 个 operation”。
  4. 每个问题都先看红测,再做局部修复,最后跑完整后端测试。

面试讲法:

大模型链路的问题很容易变成“感觉模型不稳定”。我处理时先把现象变成确定性测试:模型 JSON 漂移、ORDER BY 等价表达、SQL 数量边界分别都有可回放用例。这样修复不是靠调 prompt,而是靠后端合同和回归测试锁住。最终完整后端测试通过,说明修复没有破坏其他链路。

这几个坑可以总结成一句话:

模型主导代码生成的核心难点,不是让模型写出一段看起来像 Java 的代码,而是把模型输出收敛到当前任务、当前 SQL、当前表结构和当前工程规范之内。后端要把“不可信但有创造力的模型输出”变成“可审计、可复核、可编译、边界清晰的工程产物”。

16. 面试深挖题与回答

16.1 为什么要做模块级迁移,而不是单 SQL 迁移?

单 SQL 缺少上下文。比如 SELECT * FROM user 不知道 user 表有哪些字段、哪些字段敏感、主键是什么、Java 包名是什么。模块级迁移让用户先维护表结构,再提交 SQL,代码生成器可以基于真实字段生成 DTO/VO/Mapper/XML。

16.2 为什么现在采用模型主导代码生成?

因为真实模块迁移不是单条 SQL 翻译,而是多 SQL、多表、联表、聚合、分页和风险控制共同决定代码结构。模板生成器容易生成机械类名、大而全 VO、错误字段类型和漏覆盖方法。新的设计让模型根据迁移计划、模块上下文和 RAG 规范生成完整文件集,后端负责契约、门禁、编译和复审。也就是说,模型主导“写什么代码”,后端决定“能不能进入下一步”。

16.3 为什么使用原生 Function Calling?

因为这个项目要实现真正的 ReAct Agent,而不是让模型只配合后端固定流程输出一段工具标记。现在 ToolRegistry 会把每个迁移阶段级工具导出成 OpenAI tools JSON Schema,模型返回结构化 tool_calls,后端按顺序校验白名单、taskId 和前置条件,再执行工具并回填 Observation。

这样有三个好处:

  • 工具名和参数是结构化协议,不再依赖正则解析文本。
  • 模型真的可以根据 Observation 自主选择下一步工具。
  • 后端仍然能拒绝越权、串任务、缺少前置产物或不满足完成条件的调用。

16.4 如果模型调用错工具怎么办?

调度器不会替模型改成预期工具,因为这个项目的目标是真正的 ReAct Agent。模型调用错工具时,后端会拒绝执行,并把原因作为 Observation 回填,例如未注册工具、缺少 taskIdtaskId 与当前任务不一致、缺少 SQL 解析结果、没有代码产物、编译未通过等。模型下一轮需要基于这个 Observation 自我修正。

16.5 RAG 在项目里具体起什么作用?

RAG 提供企业迁移知识,包括代码模板、规则、反例、风险处理、字段规范。它会进入 generate_plangenerate_codereview_code 的上下文:先帮助模型把 SQL 意图转成结构化迁移计划,再帮助代码生成模型按企业规范生成文件集,最后帮助复审模型判断生成代码是否符合规范。

16.6 Chroma 为什么要传 query_embeddings?

因为后端使用自己的 EmbeddingClient 生成向量,保证查询向量和写入向量使用同一个模型。Chroma query 接口在这种模式下需要 query_embeddings 字段,否则会报缺失字段。

16.7 如何保证多条 SQL 不漏生成方法?

调用方传入类型不可信,后端 SqlStatementSplitter 会重新拆分 SQL,迁移计划会按 SQL 正文补齐或修正操作类型。代码生成模型返回的每个文件还必须声明 relatedSqlIds,后端契约门禁会检查每个 SqlUnit 是否被覆盖;未覆盖且未声明人工复核风险时,generate_code 直接失败。

16.8 如何避免 SELECT * 暴露敏感字段?

生成器会结合模块表结构展开字段,并过滤 password、token、secret、salt、isDelete、createTime、updateTime、version 等敏感或技术字段。

16.9 AI 代码复审解决什么问题?

它解决“代码能编译但业务语义不一定对”的问题。比如多条 SQL 里漏生成 UPDATE 方法、Mapper XML 丢失 isDelete = 0、VO 暴露 password、DELETE 没有 WHERE,这些都不是编译器能发现的。CodeReviewService 会把源 SQL、迁移计划、RAG 规范、映射规则和生成代码一起交给模型复审,输出结构化问题清单;只要复审要求人工确认,最终任务就不会被标记为完全通过。

16.10 编译通过是否代表迁移正确?

不代表。编译通过只说明语法和类型正确。业务逻辑还需要 JUnit、原 SQL 对比测试、人工复核和沙箱数据验证。所以系统会生成报告和风险提示,而不是宣称全自动无风险替代人工。

16.11 任务失败后怎么处理?

后台异常会被 MigrationTaskService.runAgent 捕获,任务状态转 FAILED_NEED_MANUAL_REVIEW,异常信息写入 risks。编译失败会进入自动修复,超过最大次数也会转人工复核。

17. 最终推荐讲法

面试时可以这样完整表达:

这个项目的后端核心是一个可审计的 SQL/存储过程迁移 ReAct Agent 平台。用户先按业务模块维护表结构,后端把表结构和多条 SQL 组装成模块上下文,再把真实迁移阶段级工具以 OpenAI tools JSON Schema 暴露给模型。模型通过原生 Function Calling 自主选择 sql_parserag_searchrule_matchgenerate_plangenerate_codereview_codecompile_javarepair_codegenerate_reportfinish_migrationrequest_human_review 等工具;后端不替模型固定流程,只校验白名单、taskId 和工具前置条件。生成代码时采用模型主导:模型基于迁移计划、表结构、SQL 单元、RAG 规范和工具观察生成完整 Java/MyBatis 文件集;后端负责契约门禁,检查 SQL 覆盖、路径、package、类名、机械命名、危险 DML 和动态 SQL 风险。代码生成后还会交给模型复审 SQL 语义和企业规范,再通过 Java Compiler API 做真实编译,失败后由 Agent 决定修复或转人工复核。整个任务过程会落 MySQL、Redis、文件系统和 Agent 日志,所以它是一个可观测、可追踪、可复核的企业级迁移后端,而不是玩具 Demo。

18. 项目最值得强调的后端亮点

  1. 模块上下文设计:解决单 SQL 缺字段、缺语义、缺包名的问题。
  2. 多 SQL 类型识别:后端拆分并二次推断 SQL 类型,避免漏生成 UPDATE/DELETE。
  3. 原生 Function Calling:使用 OpenAI tools JSON Schema 暴露真实工具,由模型返回结构化 tool_calls
  4. ReAct 决策 + 后端 Guardrails:模型决定下一步工具,后端校验白名单、taskId、前置条件和终态验收。
  5. RAG 知识运营能力:支持 Chroma CRUD、文件导入、结构化 chunk、默认企业知识。
  6. 结构化迁移计划:大模型先把 SQL、RAG 和规则转成可控计划,作为代码生成契约的一部分。
  7. 模型主导代码生成:模型生成完整源码文件集,后端用契约门禁阻断机械命名、漏 SQL、高风险 DML 和不可编译结果。
  8. 生成后 AI 复审:检查 SQL 语义遗漏、危险 DML、敏感字段暴露和多余代码。
  9. 编译校验闭环:Java Compiler API 真实编译,失败修复,超过次数人工复核。
  10. 完整任务可观测性:状态机、AgentLog、风险列表、产物、报告全部可追踪。
NovaBryan的博客
使用 Hugo 构建
主题 StackJimmy 设计