让 Java 微服务也能被 AI 自主验证:本地 Harness 环境改造实践

Java 微服务想用好 AI Coding,关键是先搭好可本地验证的 Harness 环境。

原文标题:都是 AI Coding,为什么 Java 体验差了一个量级?五条方法论帮你构建自己的 Harness 环境

原文作者:阿里云开发者

冷月清谈:

文章指出,AI Coding 在前端、CLI、Python 脚本等本地依赖较轻的项目中体验更顺畅,因为 AI 可以完成“写代码—运行—测试—修复”的闭环;而 Java 微服务项目通常依赖 OSS、远程沙箱、TDDL、配置中心、消息队列等云端基础设施,本地无法启动,导致 AI 每次修改都要靠人工推预发、验证、反馈。作者将问题归纳为 Harness Engineering:为 AI 构建可运行、可验证的工程环境。核心方法包括:通过依赖倒置抽象存储、命令执行等外部能力;用 Spring Profile 隔离本地与线上实现,尽量做到零侵入;将配置查询、服务诊断等工具 CLI 化,让 AI 能直接调用。实践中,作者用本地文件系统替代 OSS、ProcessBuilder 替代远程沙箱、H2 替代 TDDL 数据源、AtomicLong 替代分布式序列,并配合 CLAUDE.md、启动脚本、验证脚本和冒烟测试,使 AI 能在本地自主迭代,将原本半小时的预发验证流程缩短到数分钟内完成。

怜星夜思:

1、你们觉得 Java 微服务项目要不要为了 AI Coding 专门改造本地环境?投入产出比高吗?
2、文章里提到“替代而非模拟”,比如用 H2 替代 MySQL、用本机 bash 替代远程沙箱。你觉得这种本地替代会不会带来线上线下行为不一致的坑?
3、如果一个老 Java 项目依赖特别多,本地完全跑不起来,你会从哪里开始做 Harness 改造?
4、AI Agent 能跑命令、读日志、调 CLI 之后,团队内部的 Web 管理台还有必要继续作为主要工具吗?

原文内容

1. 体验差距到底在哪

在依赖比较轻的项目里(比如前端、CLI 工具、纯本地的 Python 脚本),AI Coding 的体验是这样的:
编辑代码 → 本地运行 → 测试验证 → AI 读取结果 → 自动修复 → 再次验证 → ...

整个循环在本地完成,不需要人盯着。你甚至可以用上 autoresearch 的思想,和 AI 说:「帮我持续优化迭代这个功能,排查并修复bug,至少10轮」,AI Agent 就可以自己迭代几十轮,直到功能跑通。

但 Java 微服务项目里,这个闭环基本是断的。

这周让 AI 给一个 Agent 应用加一个新的 Tool 实现,逻辑不复杂,大概 150 行代码。AI 写完之后很自信地说"已完成",但它没有任何办法验证自己写的东西能不能跑。因为这个项目依赖 OSS、远程沙箱、HSF 一整套云端基础设施,本地 mvn spring-boot:run 直接启动失败。

于是进入了经典循环:我把代码推到预发,等 5 分钟部署完成,手动触发一次调用,发现 NPE,截图贴回给 AI,AI 改了两行,我再推预发,再等 5 分钟……三轮下来半小时过去了,改的只是一个参数注入顺序的问题。

如果是一个本地能跑的项目,AI 自己跑一下测试就能发现这个错误,整个修复可能 30 秒就结束了。同样的模型、同样的 Prompt,差距不在 AI 能力,在如何构建 AI 友好的工程环境,即 Harness Engineering。

2. 问题的本质

这个体验差距不是偶然的,根源在微服务时代的技术架构。

通常 Java 项目都重度依赖云端微服务基础设施:HSF 做服务调用、TDDL 做数据库路由、Diamond/Switch 做配置下发、MetaQ 做消息队列。一个看起来很简单的 @Autowired,背后可能牵着一整套分布式基础设施。这些东西在云端跑得很好,但在本地全部不可用。

说白了,微服务架构天然不 AI 友好。AI Coding Agent 需要一个能在本地跑起来的环境来验证自己的输出,但微服务架构把运行时依赖全部推到了云端。项目在本地跑不起来,AI 就没办法自主验证,只能靠人去推预发、看结果、再反馈。

于是就出现了一个很常见的工作流:

本地 Vibe Coding 开发
  → 推预发部署
    → 人工在预发环境验证结果
      → 人工把验证结果反馈给 AI
        → AI 继续改
          → 再次推预发 → 再次人工验证 → ...

人在每个环节都是阻塞点。跟那些本地能直接跑的项目里 AI 能自己迭代几十轮比起来,Java 微服务项目里 AI 每做一步都得等人。

最近社区关于 Harness Engineering 的讨论很多,大家聊的焦点通常在 CLAUDE.md 怎么写、lint 规则怎么配、验证脚本怎么跑。但对于 Java 微服务项目,还有一个更基础的问题:项目在本地能跑起来吗? CLAUDE.md 写得再好,AI 连代码能不能编译通过都验证不了,后面的一切都是空谈。

这篇文章聊的就是这个问题:怎么让 Java 微服务项目的本地环境变得 AI 友好,让 AI 拥有自主验证的能力。做法来自一个 Agent 应用的实际改造,算是抛砖引玉。

3. 三条改造原则

我在这次改造中总结出三条原则,后面逐个展开。

3.1 依赖倒置,接口先行

上层逻辑依赖抽象接口,不依赖具体实现。云端和本地只是接口的不同实现。

这是经典的依赖倒置(DIP),但在本地 Harness 的场景下有了实际的意义:它决定了你的系统能不能在本地跑起来。

拿我的实际改造举例。线上环境中,Agent 的文件系统(AgentFs)基于 OSS 对象存储,命令执行走远程沙箱(Sandbox),两者通过 OSS Mount 共享文件系统。这套架构在云端跑得好好的,但本地完全不可用,没有 OSS 服务,也没有远程沙箱。

改造前的依赖关系:

FilesystemService → OssStorageAdapter (具体类,直接调 OSS SDK)
AgentWorkspace → SandboxCommandExecutor (具体类,调远程沙箱 API)

上层直接依赖了具体的云端实现,想在本地跑就得把整个 OSS 和 Sandbox 搬过来。不现实。

改造后,我抽了一个 StorageAdapter 接口,把上传、下载、判断存在、删除、列举这些操作统一定义好。线上的 OSS 实现加一行 implements StorageAdapter,本地新写一个 LocalStorageAdapter,用 java.nio.file 把逻辑 key 映射到本地路径。工厂类在创建文件系统时,检测到有本地参数就走本地实现,否则走 OSS 实现。

StorageAdapter (接口)
  ├── OssStorageAdapter (线上,走 OSS SDK)
  └── LocalStorageAdapter (本地,走 java.nio.file)
CommandExecutor (接口)
  ├── SandboxCommandExecutor (线上,调远程沙箱 API)
  └── LocalCommandExecutor (本地,ProcessBuilder + bash -c)

上层所有用到文件操作和命令执行的代码完全不用改。它们只依赖接口,不关心底层是 OSS 还是本地文件系统。切换运行环境就是换一个接口实现,不用重写上层逻辑。

3.2 零侵入,Profile 隔离

本地改造不能让线上代码路径多走一行额外的代码。

这条容易被忽视。很多团队做本地化适配时,会在主流程里加 if (isLocal) 分支,线上代码路径变复杂了,出问题的风险也跟着上去。

我的做法是严格的零侵入:

Spring Profile 隔离:本地专属的 Bean 通过 @Profile("local") 装配,线上专属的通过 @Profile("!local") 守卫。两套配置编译期就彼此不可见。

@Configuration
@Profile("local")
public class LocalRepositoryConfig {
    @Bean
    CommandExecutor localCommandExecutor() { ... }
    @Bean("localFsBasePath")
    String localFsBasePath() { ... }
    // 用 AtomicLong 替代 TDDL GroupSequence
    @Bean("sessionSequence")
    Sequence sessionSequence() { return new LocalSequence(); }
}

同时在本地启动入口通过 @ComponentScan 的正则过滤,把线上专属的包(远程沙箱、OpenTelemetry 观测等)整个排除掉,不用在每个类上加条件注解。

@Nullable 参数注入:可选依赖标 @Nullable,不存在时 Spring 注入 null,不用写 @ConditionalOnBean

public AgentRunner(@Nullable @Qualifier("localFsBasePath") String localFsBasePath, ...) {
    // 线上环境:localFsBasePath = null,走 OSS 路径
    // 本地环境:localFsBasePath = "/tmp/agentfs",走本地路径
}

条件守卫:运行时通过 null 检查决定走哪条路径。localFsBasePath 不为空就走本地,ossClient 不为空就走线上,逻辑非常简单。

最终效果按侵入性分级:

侵入级别

改动类型

示例

零侵入

新建文件

新接口、新的本地实现类

极低

加一行声明

线上实现类加 implements 接口

类型上提

字段类型从实现类改为接口

中等

新增可选参数

构造器加可选的本地路径参数(原有构造器不动)

线上代码路径中,这些新增代码全部被跳过:null 检查不通过、Profile 不匹配、接口多态自然走原有实现。一个检验标准:删掉所有本地相关代码后,线上行为完全不变。

3.3 工具 AI 化:CLI 优先

AI Agent 的能力边界 = 它能调用的工具的边界。GUI 对 AI 不可见,CLI 才是 AI 能用的东西。

Java 生态有大量运维和管理工具,但大多是 Web 控制台:Switch 管理台、Diamond 配置中心、HSF 服务治理台。人用着方便,但 AI Coding Agent 完全用不了。

我的做法是用 CLI 桥接企业内部系统:

# 通过 mw-cli 查询 Diamond 运行时配置值
mw diamond get --unit online --data-id application.properties --group DEFAULT_GROUP
# 通过 mw-cli 查询 HSF 服务地址
mw hsf address --unit daily --app my-application

这些命令的输出是结构化文本,AI 直接就能解析。改造过程中,我写了一个 scripts/fetch-switch-config.sh,用 mw-cli 从预发 Diamond 拉 Switch 配置,解析成 properties 格式写到本地:

# fetch-switch-config.sh 核心逻辑
mw diamond get idealab-agent-runtime asp-switch --env staging --unit pre -o json
# → 解析 JSON,写入 switch-config-local.properties
# → switch.modelApiKey=xxx
# → switch.defaultTimeoutMs=60000
# → switch.maxReactIterations=10

AI 也能直接跑这个脚本来同步最新的线上配置,不用人工登录管理台复制粘贴。

我还通过 Skill 把这些 CLI 能力注册到 AI Agent 的工具箱里,让 AI 需要的时候自己调用。

工具 AI 化的优先级:

优先级

工具形态

AI 可用性

示例

1

CLI

直接可用

mw-cli, mvn, git, arthas

2

MCP Server

协议适配

数据库查询、监控数据

3

Skill / Tool

自定义封装

配置查询、服务诊断

4

GUI

不可用

Web 管理台、IDE 插件

4. 实践案例:从"推预发验证"到"本地闭环"

4.1 改造前

这个 Agent 应用是一个 AI Agent 运行时平台,核心能力之一是让 AI Agent 可以操作文件(ReadFile/WriteFile)和执行命令(Bash)。

线上架构:

  • 文件操作:AgentFs → OSS 对象存储

  • 命令执行:远程 Sandbox(容器沙箱)

  • 文件共享:Sandbox 通过 OSS Mount 挂载 Agent 的 OSS 存储桶

本地开发时:没有 OSS、没有 Sandbox、没有 OSS Mount。Agent 的 ReadFile 写到 OSS 里的文件,Bash 在本地根本看不到。文件系统是割裂的,没法用。

4.2 改造方案

核心思路:一个接口,两套实现,Profile 条件装配。工厂类根据参数自动选择实现,上层完全无感。

改造后,本地 @Profile("local") 激活时:

  • 文件系统工厂检测到本地参数,自动创建 LocalStorageAdapter,文件存到 /tmp/agentfs/{sessionId}/...

  • Bash 走 LocalCommandExecutor,用 ProcessBuilder 在本机执行 bash -c 命令,工作目录指向同一个本地路径

  • ReadFile/WriteFile 和 Bash 看到的是同一套文件,闭环打通了

除了文件系统和命令执行,还有一整套配套改造(详见附录):H2 替代 TDDL 数据源,AtomicLong 替代分布式 Sequence,本地 Switch 配置文件替代 Switch Center,ComponentScan 排除线上专属的 sandbox 和 observation 包。最终效果是一个完全自包含的本地运行环境,不依赖任何远程基础设施。

4.3 改造效果

对比项

改造前

改造后

文件操作验证

推预发,通过 OSS 控制台查看

本地直接 ls 查看

Bash 执行验证

推预发,登录沙箱查看

本地 Terminal 直接看

AI 自主验证

做不到

ReadFile → 验证 WriteFile 结果

单次迭代耗时

5-10 分钟(含部署等待)

秒级

AI 自主修复轮数

0(每轮都要人工介入)

平均 3-5 轮后自行收敛

改造前后的核心差异:一个完整的 bug fix 流程,改造前需要 3-4 轮人工推预发验证、总耗时 30 分钟以上;改造后 AI 在本地自主迭代,通常 2 分钟内收敛,人只需要最后 review 一下结果。

5. 配合 Harness Engineering 落地

本地 Harness 跑起来之后,还需要配合 Context Engineering 才能让 AI 真正高效地工作。具体来说,有几件事需要做:

5.1 CLAUDE.md:给 AI 一张地图

项目根目录放一份 CLAUDE.md,告诉 AI 这个项目是什么、怎么构建、怎么测试、本地环境怎么启动。不需要写得很长,100 行以内,重点是让 AI 在拿到任务后能快速定位该看哪些代码、该跑什么命令。

# 项目简介
AI Agent 运行时平台,支持 ReadFile/WriteFile/Bash 等 Tool。
# 本地开发
- 启动:`mvn spring-boot:run -Dspring.profiles.active=local`
- 测试:`mvn test`
- 本地文件系统根目录:`/tmp/agentfs/`
# 架构约束
- 上层模块只依赖接口,不依赖具体实现类
- 本地专属代码通过 @Profile("local") 隔离
- 新增本地适配不得修改线上代码路径

5.2 验证脚本:让 AI 自己检查

Checklist 是给人看的,AI 需要的是可执行的验证命令。我在项目里加了一个 scripts/verify-local.sh

#!/bin/bash
# 本地 Harness 验证脚本:AI 可以直接跑这个来确认本地环境是否正常
set -e
echo "=== 1. 编译检查 ==="
mvn compile -q -Dspring.profiles.active=local
echo "=== 2. 单元测试 ==="
mvn test -q 2>&1 | tail -5
echo "=== 3. 本地启动检查 ==="
timeout 30 mvn spring-boot:run -Dspring.profiles.active=local &
PID=$!
sleep 15
if curl -s http://localhost:8080/actuator/health | grep -q "UP"; then
    echo "✓ 本地启动成功,health check 通过"
else
    echo "✗ 本地启动失败"
    exit 1
fi
kill $PID 2>/dev/null
echo "=== 4. 文件系统闭环检查 ==="
# 验证 ReadFile/WriteFile 和 Bash 看到的是同一套文件
TEST_FILE="/tmp/agentfs/test-$(date +%s)/verify.txt"
mkdir -p $(dirname $TEST_FILE)
echo "hello" > $TEST_FILE
if [ -f "$TEST_FILE" ] && [ "$(cat $TEST_FILE)" = "hello" ]; then
    echo "✓ 文件系统闭环正常"
else
    echo "✗ 文件系统闭环异常"
    exit 1
fi
rm -rf $(dirname $TEST_FILE)
echo "=== 全部通过 ==="

AI 改完代码后跑一次 bash scripts/verify-local.sh,不需要人工介入就能知道本地环境是不是还能正常工作。这比 Checklist 有用得多。

6. Harness Engineering Checklist

这份清单可以用来评估任意 Java 项目的本地 AI Coding 友好度,也可以当改造的行动指引。

  • 项目能否在本地通过一条命令启动?(mvn spring-boot:run 或等价命令)

  • 启动是否依赖外部中间件?如果是,有没有本地替代?(H2 替代 MySQL、内存队列替代 MetaQ 等)

  • 外部依赖是否通过接口抽象?能否通过 Profile 切换实现?

可测试性
  • AI 能否在本地运行测试并读取结果?(mvn test 输出是否结构化)

  • 核心逻辑是否有单元测试?AI 改完代码后能否立即验证?

  • 端到端验证是否需要人工?能否通过 API 调用 + 断言自动完成?

可观测性
  • 日志是否结构化(JSON 格式)、可 grep?

  • 是否集成了 CLI 化的 JVM 诊断工具?(Arthas、jstack 脚本等)

  • AI 是否能通过命令行获取运行时状态?(health check endpoint、metrics 输出等)

工具 AI 化
  • 团队使用的运维工具是否有 CLI 入口?

  • 配置管理(Switch/Diamond)是否可通过命令行查询?

  • 是否有 MCP Server 或 Skill 将内部系统能力暴露给 AI Agent?

隔离性
  • 本地改造是否通过 @Profile 隔离?

  • 本地代码是否对线上代码路径零侵入?

  • 删除所有本地相关代码后,线上行为是否完全不变?

7. 方法论总结

回过头来看这次改造,有几个可以复用的方法论。

第一,找到最小可运行子集。 不需要把所有线上能力都搬到本地,只需要找到核心链路跑通所需的最小依赖集合。这个项目的核心链路是"接收请求 → 调 LLM → 执行 Tool → 返回结果",围绕这条链路需要的就是数据库、文件系统、命令执行三个基础设施。其他的比如监控、链路追踪、服务发现,本地不需要,排除就好。

第二,替代而非模拟。 H2 不是在"模拟" MySQL,它就是一个真实的关系型数据库,跑的是真实的 SQL。LocalCommandExecutor 不是在"模拟"沙箱,它就是在执行真实的 bash 命令。替代方案要能真实运行,不能只是返回 mock 数据。这样 AI 在本地发现的问题,线上大概率也会有。

第三,脚本化一切人工操作。 但凡需要人登录某个管理台、复制某个配置、点击某个按钮才能完成的操作,都应该有一个对应的脚本。fetch-switch-config.sh 替代了"登录 Switch 管理台 → 找到应用 → 复制配置"的人工流程。start-local.sh 替代了"先编译、再配置环境变量、再选对 main 类启动"的多步操作。脚本就是 AI 的手,没有脚本 AI 就是残废的。

第四,分层隔离,逐层验证。 不要想着一步到位把所有东西都改完再测。先让项目能编译,再让它能启动,再让核心接口能调通,再跑端到端测试。每一层都有对应的验证手段:mvn compile/checkpreload.htm、API 冒烟测试、Playwright E2E。AI 也可以按这个顺序逐层排查问题。

第五,让 AI 成为改造的参与者。 这次改造本身就是和 AI 一起完成的。我先手动搞定了 H2 替换和核心接口抽象,确保项目能在本地启动。之后 StorageAdapter 的实现、LocalCommandExecutor 的异常处理、冒烟测试脚本,都是 AI 在本地闭环里自己迭代出来的。一旦 AI 能跑测试、能看到报错,它的效率就上来了。这形成了一个正向循环:每完成一步改造,AI 能做的事情就多一点,下一步改造就更快。

8. 后续方向

本地环境还可以再往前走一步,把 JVM 诊断能力也工具化。比如应用响应超时时自动跑 jstack 把结果输出到 AI 能读的地方,或者通过 Skill 封装 Arthas 的 watchtracett 命令,让 AI 实时观测方法调用。异常信息如果能以 JSON 格式输出,AI 直接就能解析定位到具体代码行。

理想的状态是 AI 不光能跑测试,还能理解测试为什么失败:

AI 修改代码 → mvn test → 解析 Surefire 报告 → 定位失败用例
  → 读取相关源码 → 分析原因 → 修复 → 再次测试 → ...

这要求测试输出是结构化的、错误信息是可解析的、日志是可追溯的。目标就是让 AI 在本地能完全自主地跑完"发现问题→定位原因→修复代码→验证修复"这整个循环,跟人类开发者拥有一样的本地开发能力。

9. 附录:本项目本地化改造清单

下面列举这个 Agent 应用为了本地化部署做的所有改动,可以作为其他 Java 项目本地化改造的参考。

9.0 这个应用依赖了什么

改造之前先盘一下,这个应用到底挂在哪些东西上面:

  • 数据库:TDDL 连 MySQL 集群,本地没有 TDDL 数据源,Spring 启动直接报 Bean 创建失败

  • 分布式 ID:TDDL GroupSequence 靠数据库表做号段分配,TDDL 起不来它也起不来

  • 对象存储:文件系统基于 OSS,本地没有 OSS 服务,Agent 的 ReadFile/WriteFile 全废

  • 命令执行:走远程沙箱,本地没有沙箱服务,Bash Tool 直接报错

  • 配置中心:API Key、超时时间、功能开关这些运行时配置走 Switch Center 动态下发,本地读不到

  • 中间件:EagleEye、HSF、Sunfire、Pandora 这些的 AutoConfiguration 启动时会尝试初始化,本地没有这些基础设施,启动直接挂

  • 监控:OpenTelemetry 埋点、metrics 导出,本地不需要但不排除也会报错

叠在一起的效果就是 mvn spring-boot:run 一跑一屏报错。下面逐个说怎么解决。

9.1 数据源:H2 替代 TDDL

线上用 TDDL 连 MySQL 集群,本地用 H2 文件数据库,零外部依赖。

# application-local.properties
spring.datasource.url=jdbc:h2:file:./data/local-db;AUTO_SERVER=TRUE;MODE=MySQL
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

配合 schema-h2.sql 自动建表。表结构和线上 MySQL 保持一致,字段名、索引、约束都对齐。Repository 层用的是同一套 JDBC 实现,不需要为本地单独写一套。

spring.sql.init.mode=always 让每次启动自动执行建表脚本,H2 的 MODE=MySQL 兼容 MySQL 语法,基本不用改 SQL。

9.2 分布式 ID:AtomicLong 替代 GroupSequence

线上用 TDDL GroupSequence 生成全局唯一 ID,本地用 AtomicLong 包一层就够了:

public class LocalSequence implements Sequence {
    private final AtomicLong counter = new AtomicLong(
        System.currentTimeMillis() % 1_000_000
    );
    @Override
    public long nextValue() { return counter.incrementAndGet(); }
}

本地配置类里注册了 session、message、checkpoint 等多个 Sequence Bean,覆盖线上所有需要分布式 ID 的地方。用时间戳取模做种子,本地够用了,不会冲突。

9.3 Switch/Diamond 配置:脚本拉取到本地

线上通过 Switch Center 动态下发配置,本地通过 switch-config-local.properties 文件模拟。

scripts/fetch-switch-config.sh 做的事情:

  1. 调 mw diamond get 从预发环境拉 Switch 配置(JSON 格式)

  2. 用 Python 脚本解析 JSON,提取 SwitchConfig.* 前缀的字段

  3. 写入 switch-config-local.properties,格式类似 switch.modelApiKey=xxx

  4. 缺失的字段补上默认值(defaultTimeoutMs=60000maxReactIterations=10 等)

本地配置类的 @PostConstruct 方法会读这个文件,把值设到 Switch 配置的静态字段上。API Key 的优先级是:properties 文件 > 环境变量 > application 配置 > 空值。

这样做的好处是 AI 随时可以跑 bash scripts/fetch-switch-config.sh 来同步最新配置,不需要人工登管理台。

9.4 包排除:ComponentScan 正则过滤

本地启动入口用 @ComponentScan 的 excludeFilters 把线上专属的包整个排掉:远程沙箱包、OpenTelemetry 观测包,本地不需要的整包排除。

再加上 application-local.properties 里的 spring.autoconfigure.exclude,把 EagleEye、HSF、TDDL、Sunfire 等中间件的自动配置全部跳过。

排除的原则很简单:如果一个包的所有类都是线上专属的,用 ComponentScan 排除;如果是某个自动配置类,用 exclude 排除。不需要在每个类上加 @Profile  @ConditionalOnProperty

9.5 一键启动:start-local.sh

# 一条命令启动本地环境
bash scripts/start-local.sh

这个脚本做了三件事:

  1. 检查 switch-config-local.properties 是否存在,不存在就自动跑 fetch-switch-config.sh

  2. mvn compile -q 编译项目

  3. 拼接 classpath,直接 java -cp 启动 LocalApplication,监听 7001 端口

启动后 http://localhost:7001/checkpreload.htm 返回 "success" 就说明环境正常。AI 在 CLAUDE.md 里看到这条命令,改完代码直接跑就行。

9.6 端到端冒烟测试

tests/smoke/local-chat-smoke.mjs 是一个 Node.js 写的冒烟测试,覆盖三个层面:

  1. Health CheckGET /checkpreload.htm 返回 "success",H2 控制台 API 能正常查表

  2. API 验证:创建 Session → SSE 流式调用 → 验证 message 和 MODEL_STOP 事件 → 查询消息历史 → 确认数据落库

  3. UI E2E:Playwright 打开页面,创建会话,发送消息,等待 AI 回复,截图保存到 /tmp

AI 改完代码跑一次 node tests/smoke/local-chat-smoke.mjs,几秒钟就能知道核心链路有没有挂。比人工在预发点点点快得多。

9.7 改造全景图

把上面这些改动汇总一下:

线上依赖

本地替代

改动方式

OSS 对象存储

本地文件系统 (java.nio.file)

新增接口 + 本地实现

远程沙箱

本机 bash 执行 (ProcessBuilder)

新增本地实现

TDDL + MySQL

H2 文件数据库 (MODE=MySQL)

application-local.properties

TDDL GroupSequence

AtomicLong 本地序号

新增本地实现

Switch Center

switch-config-local.properties

脚本从预发拉取

Diamond 配置中心

application-local.properties

Spring Profile

EagleEye / HSF / Sunfire

排除自动配置

spring.autoconfigure.exclude

Pandora 启动器

标准 java -cp 启动

start-local.sh

OpenTelemetry

排除观测包

ComponentScan excludeFilters


我的顺序是:先能编译,再能启动,再能跑一个健康检查,最后再做端到端冒烟测试。很多团队一开始就想做完整 E2E,结果卡在数据源、配置中心、服务发现上,最后谁也不想管了。

3 个赞

回答“本地替代会不会有坑”:肯定会有。H2 的 MySQL 兼容模式再像,也不等于 MySQL;本机 bash 也不等于容器沙箱。关键是不要把本地 Harness 当成最终验收环境,它解决的是快速反馈,不是完全替代预发。

2 个赞

关于 H2 替 MySQL,我踩过坑:函数、索引、事务隔离、大小写规则都可能不一样。所以我更倾向于能用 Testcontainers 就用 Testcontainers,不能用的时候再退到 H2。只是企业内网环境有时候拉镜像也挺麻烦。

1 个赞

说句不太严肃的:如果你的项目连人都不愿意本地启动,那 AI 肯定更不愿意。先别问 AI 体验差不差,先问问新人入职第一天能不能把项目跑起来。

1 个赞

如果是我接手老项目,会先写脚本。哪怕项目现在还跑不起来,也先把“怎么编译、怎么启动、怎么查看日志”固化下来。脚本是最便宜的 Harness,后面 AI 和新人都能复用。

2 个赞

我觉得未来内部工具会变成“GUI 给人,CLI/API 给 AI”。以前很多后台系统只做页面,是因为使用者默认都是人。现在 AI Agent 也成了使用者,工具形态肯定要变。

1 个赞

我会从数据库和配置开始。老 Java 项目最常见的启动失败就是数据源起不来、配置读不到。先用本地配置文件和轻量数据库顶上,项目能起来之后,再慢慢处理 MQ、缓存、远程调用这些依赖。

2 个赞

我站“值得改造”这一边。文章里说的其实不只是 AI Coding 的问题,而是很多后端团队早就存在的痛点:本地起不来、测试靠预发、排错靠玄学。AI 只是把这个问题放大了。

2 个赞