Chat2DB Java Web Controller 分层契约:从 HTTP 入口到领域服务的职责边界与代码规范
本篇技术指南以 Chat2DB 社区版仓库中的 Java Web Controller Contracts 契约为核心,系统讲解
chat2db-community-web模块中 Web 控制器的命名链、职责边界、结果包装器与服务调用约束。读者将掌握一套可直接用于评审与编码实践的"瘦控制器"规范:如何让 HTTP 层只做路由、绑定与包装,如何用<Domain><Object><Action>语义链统一端到端命名,以及如何借助标准 Result Wrapper 与 domain-api 服务接口杜绝业务逻辑向 Web 层泄漏。
1. 契约定位:控制器只是 HTTP 边界的适配器
Chat2DB 社区版后端采用多模块 Maven 工程(见 chat2db-community-server/pom.xml),其中 chat2db-community-web 承载所有 HTTP 暴露面。该契约首先给 Web 控制器下了一个严格定义:
一个 Web 控制器只适配 HTTP 边界,不得包含业务规则。控制器通过服务接口调用业务能力,并通过标准结果包装器返回 HTTP 结果。业务决策、对象装配、外部依赖调用不允许散落在 Web 层。
契约适用的对象是 chat2db-community-web 中的 @RestController 与 @Controller 端点。过滤器(Filter)、拦截器(Interceptor)、异常处理器(Exception Handler)、转换器(Converter)、适配器(Adapter)以及异步执行组件不视为控制器,但它们仍需遵守各自模块的边界与对象转换契约(分别见 java-module-boundaries.md 与 java-object-converter-contracts.md)。
从源码结构看,chat2db-community-web 的控制器统一收敛在 web/api/controller 目录下,目前共约 40 个控制器,覆盖数据源、SQL、AI、CLI、MCP、任务、运维日志与系统配置等能力域——这正是下节命名链在真实工程中的落地形态。
2. 业务命名链:<Domain><Object><Action> 语义锚点
契约要求:从 HTTP 输入、业务服务到 HTTP 输出,全链路名称必须共享同一个语义锚点:
<Domain><Object><Action>
2.1 三段式语义拆解
| 段 | 含义 | 示例 |
|---|---|---|
Domain |
顶层业务域前缀 | Sys、Db、Ai、Cli、Mcp、Task、Ops、Plugin |
Object |
被操作的业务对象或能力 | Datasource、Connection、Sql、Model、Chat、Permission |
Action |
业务动作 | Create、Update、Delete、Get、List、Execute、Test、Import、Export |
2.2 顶层域前缀语义表
| 前缀 | 含义 |
|---|---|
Sys |
系统设置、账号、权限、OAuth、代理与运行时配置 |
Db |
数据库连接、元数据、SQL、DDL/DML、表、视图、函数、存储过程、触发器及 Redis 数据操作 |
Ai |
AI 对话、模型、补全、RAG、嵌入与 AI 辅助 Schema 能力 |
Cli |
CLI 与无头(headless)能力 |
Mcp |
MCP 协议、工具、资源与授权 |
Task |
导入/导出、异步任务与长时工作流 |
Ops |
操作历史、审计、已保存查询与历史记录 |
Plugin |
插件扩展能力 |
2.3 命名规则(10 条)
- 控制器命名为
<Domain><Object>Controller,例如DbDatasourceController、AiAiChatController、SysSysPermissionController; - 控制器方法名只表达动作,使用 lowerCamel 形式,例如
create、update、delete、get、list、execute、test; - Web 请求 DTO 命名为
<Domain><Object><Action>Request; - Web 动作响应 DTO 命名为
<Domain><Object><Action>Response; - 资源/视图类响应允许使用
<Domain><Object>Response(如DbDatasourceResponse、DbTableResponse),但仍必须带业务域前缀; - 领域服务接口命名为
I<Domain><Object>Service或I<Domain><Object><Capability>Service; - 服务实现命名为
<Domain><Object>ServiceImpl或<Domain><Object><Capability>ServiceImpl; - 同一端点的 Request、Controller、Service、Impl、Response 名称必须暴露一致的
<Domain><Object><Action>语义; - 禁止使用无业务域限定的泛化名称,如
SysSystemController、DbDataSourceController、CreateRequest、ExecuteResponse、ManagerService、ConfigDTO; Rdb、Redis、Database、DataSource、Table、View、Function、Procedure、Trigger不是顶层业务域,它们归属于Db域;共享值对象可以放在model包下,但不得伪装成业务请求或响应。
2.4 契约示例代码
public class DbDatasourceController {
public DataResult<DbDatasourceCreateResponse> create(@Valid @RequestBody DbDatasourceCreateRequest request) {
return DataResult.of(dbDatasourceService.create(request));
}
}
public interface IDbDatasourceService {
DbDatasourceCreateResponse create(DbDatasourceCreateRequest dbDatasourceCreateRequest);
}
public class DbDatasourceServiceImpl implements IDbDatasourceService {
}
2.5 仓库中的真实落地与 legacy 迁移
对照源码可以发现一个值得注意的细节:契约明确把 DbDataSourceController 列为"不允许的泛化名称"(规则 9),而仓库的 DbDataSourceController.java 恰好沿用该历史命名,同时在 db 子包下还有 DbDatabaseController、DbSqlController、DbTableController、DbDmlController 等符合 Db<Object>Controller 形态的类。结合契约第 8 节"legacy 名称需登记迁移"的要求,可以推断:仓库当前处于"新旧命名并存、逐步向规范迁移"的阶段——评审时应把 DbDataSourceController 这类命名识别为待迁移项,而不是新代码的仿效样板。
值得一提的是,虽然类名是 legacy 的,但其方法命名完全符合契约第 2 条的动作化要求:list、create、update、delete、queryById、preConnect、attach、close、importChat2db、exportDataSource,全部只表达动作、不带业务前缀。
3. Controller 职责边界:能做什么、绝不能做什么
3.1 控制器允许做的事(5 项)
- 声明 HTTP 路由、HTTP 方法、请求绑定与响应类型;
- 通过 Bean Validation 注解(
@Valid、@Validated、@NotNull)触发参数校验; - 调用 Web 转换器完成 Web DTO 与领域请求/响应之间的转换;
- 调用
ai.chat2db.community.domain.api.service下的服务接口——禁止在web.api.service下再造一层业务服务; - 用
ActionResult、DataResult<T>、ListResult<T>、WebPageResult<T>或 CLI 专属的CliResult<T>包装服务结果。
3.2 控制器禁止做的事(7 项)
- 评估涉及权限、版本(edition)、环境、归属、存在性、默认补全、状态流转或级联操作的业务规则;
- 直接调用
GatewayUtil、WorkspaceStorageWebFacade、适配器、任务管理器、存储实现、Mapper、Repository、实现类或其它业务实现; - 直接读写文件、数据库连接、OSS、远程 HTTP 服务、线程池、全局配置或进程退出行为;
- 在端点方法内用
if/else、for/while、try/catch编排业务工作流; - 用
new创建业务对象、用连续 setter 拼装对象或手写对象转换; - 捕获异常后返回空列表、空成功、默认对象或原始错误信息;
- 一个端点方法调用另一个端点方法,或把可复用的业务工作流藏在私有 helper 中。
3.3 多步骤编排的正确去处
当端点需要多步骤编排时,正确做法是:在 domain-api 服务接口上新增一个业务语义清晰的方法,把编排逻辑实现在该边界之下。HTTP、CLI 运行时、SSE、上传/下载、遗留 web-facade 桥接,都不是再造一个 web.api.service 层的理由——能力应落到已有或新增的 domain-api 服务接口后面。
以 AiChatController.java 为例,SSE 流式对话、附件解析、模型配置增删改查、会话历史查询等端点,全部直接委托给 IAiChatStreamService、IAiAttachmentParseService、IAiModelConfigService、IAiChatHistoryService 等接口,端点体里只有"取当前用户 → 调服务 → 转 DTO → 包装返回"四步,没有任何业务 if/else 或 try/catch 编排。
4. 标准结果包装器:普通端点唯一的返回通道
4.1 返回类型速查表
| 场景 | 返回类型 |
|---|---|
| 无响应数据的操作 | ActionResult |
| 单个对象、字符串、布尔值或 ID | DataResult<T> |
| 列表 | ListResult<T> |
| 分页 | WebPageResult<T> |
| CLI 运行时 HTTP API | CliResult<T> |
普通端点禁止返回裸对象、集合、字符串、布尔值、Map 或业务模型。
4.2 包装器的仓库实现
标准包装器统一实现在 chat2db-community-tools 的 tools/wrapper/result 包下,包括 ActionResult、DataResult<T>、ListResult<T>、ListResultWithTotal 与 web/WebPageResult<T>,CLI 专属的 CliResult<T> 则在 web/api/model/response/cli/CliResult.java。
以 DataResult.java 为例,其字段结构清晰地支撑了"成功 + 数据 + 可追踪错误"的统一协议:
| 字段 | 类型 | 语义 |
|---|---|---|
success |
Boolean |
是否成功,默认 TRUE |
errorCode |
String |
错误码 |
errorMessage |
String |
错误消息 |
errorDetail |
String |
错误详情 |
solutionLink |
String |
解决方案链接 |
data |
T |
业务数据 |
traceId |
String |
链路追踪 ID |
同时提供三类工厂方法:of(data) 成功装载数据、empty() 空成功、error(errorCode, errorMessage) 构造失败结果;hasData(...) 判空、map(mapper) 在保持 success/errorCode/traceId 不变的前提下转换数据类型。ActionResult 提供 isSuccess() 与 fail(errorCode, errorMessage, errorDetail),ListResult<T> 提供 of(list)、empty()、error(...) 及流式 map。这些能力正是契约"控制器按服务结果选择包装器"的基础设施。
4.3 允许的例外(5 类,不得扩张)
- SSE/流式端点:可返回
SseEmitter,但事件、错误与完成语义必须由专属适配器或服务负责。例如 AiChatController.java 的POST /api/v3/ai/chat/stream返回SseEmitter,实现完全委托给IAiChatStreamService<ChatRequest, SseEmitter>.stream(request); - 文件下载:可返回
ResponseEntity<Resource>或直接写HttpServletResponse,但权限、归属、路径解析与资源读取必须放在服务接口之后; - 接收
MultipartFile:属于 HTTP 绑定;上传后的存储、远程调用与授权必须放在服务接口之后(AiChatController.parseUploadedAttachment即把MultipartFile直接交给IAiAttachmentParseService.parseUpload); - CLI 运行时端点:允许使用 CLI 专属的
CliResult<T>包装器——CliResult.java 定义了success、data、error(CliErrorResponse)、requestId结构及ok(...)/error(...)工厂方法; @Controller的 HTML 路由:可返回 Spring MVC 视图名String,但页面选择、OAuth 回调、Cookie、重定向与 model 属性仍需放在服务接口之后。
契约同时强调:这些例外绝不能扩张为普通业务端点的裸返回。
5. 服务调用边界:只认 domain-api 接口
5.1 核心原则
控制器只能通过 ai.chat2db.community.domain.api.service 下的 IxxxService 接口依赖业务能力。Web 模块不得定义或注入 ai.chat2db.community.web.api.service 下的 controller-service 包装类;当契约缺失时,应扩展 domain-api 而不是在 Web 层补一层。
5.2 允许的控制器依赖(4 类)
- domain-api 服务接口;
- Web 转换器(converters/convertors);
- HTTP 绑定类型:请求/响应 DTO、
MultipartFile、HttpServletResponse; - 标准结果包装器类型。
5.3 禁止的控制器依赖(5 类)
*Impl类、Mapper、Repository、DAO 与存储实现;ai.chat2db.community.web.api.service下的任何接口或实现;GatewayUtil、WorkspaceStorageWebFacade、任务管理器、Web 适配器及一切持有业务行为或外部调用的组件;ApplicationContext#getBean、反射、类名字符串等绕过服务接口的机制;- 具体的 domain-core、storage、SPI 或 plugin 实现模块。
若遗留的 web facade 或适配器仍持有业务入口,应先把能力迁移到 domain-api 服务之后,控制器不得直接调用它,也不得把它藏进 Web 服务包装类。
5.4 仓库实证
domain-api 服务接口统一位于 domain/api/service,按业务域组织为 ai/、cli/、dashboard/、db/、file/、mcp/、ops/、storage/、sys/、task/ 子包,与契约的顶层域划分一一对应。
控制器构造注入的依赖几乎全部是这些接口:DbDataSourceController.java 只注入 IDbDataSourceService、IDbWorkspaceDataSourceService、IDbDataSourceImportService 与 DataSourceWebConverter、SSHWebConverter 两个 Web 转换器;AiChatController.java 只注入 IAiChatStreamService、IAiModelConfigService、IAiChatHistoryService、IAiAttachmentParseService、IIdentityService 与 ChatConverter——没有出现任何 *Impl、Mapper 或仓储类。
6. 委托业务规则:这些逻辑永远不属于端点方法
以下 7 类行为必须下沉到服务层:
- 环境或版本判断:
ConfigUtils.isDesktop()、isCommunity()、isRelease(); - 基于 Cookie、Header 或 Session 的用户、组织、租户、权限与归属决策;
- 对象存在性、归属、删除/更新授权判断;
- 驱动、模型、页码、Schema 等业务默认值;
- 调用 Gateway、AI、License、更新、OSS 或远程 HTTP 服务后的结果组装;
- 批量导入、导出、删除与级联清理循环;
- 外部依赖失败后的重试、降级、空结果或错误码决策。
最后一条底线:控制器可以根据服务结果选择包装器,但绝不允许重新解释业务结果。仓库中 AiChatController.listSessions 的做法是标准范式——通过 identityService.currentUserId() 取身份,把用户 ID 作为参数传给 aiChatHistoryService.listSessions(userId),身份与归属校验完全发生在服务内部。
7. DTO 与转换边界
- 控制器入参使用 Web 请求 DTO,而非 domain 或 storage 模型(明确的 legacy 兼容契约除外);
- 控制器出参使用标准包装器;新端点应返回 Web 响应 DTO;历史端点可以把既有 domain-api 模型作为包装器泛型暴露,但控制器不得拼装或修改它;
- Web DTO 与领域请求/响应之间的转换属于
chat2db-community-web中的转换器(converter); - 控制器禁止使用
BeanUtils.copyProperties、JSON 往返、ObjectMapper.convertValue或手写 setter 做对象转换。像xxxService.setXxx(...)这样的薄委托不算对象转换,但服务实现仍需遵守 java-object-converter-contracts.md 的对象转换契约。
仓库中的转换器集中在 web/api/converter 包:DataSourceWebConverter(含 request2param、request2response、response2storage、storage2response、databaseDto2response 等方法)、SSHWebConverter、ChatConverter(toModelConfigParam、attachment2response、session2response、message2response 等)。从 DbDataSourceController.java 的 create 端点可以看到完整链路:request → DataSourceWebConverter.request2response → response2storage → workspaceDataSourceService.createDataSource → storage2response → DataResult.of(...),一次 HTTP 请求中的全部对象形态转换都由转换器完成,端点方法内零手写装配。
8. 评审检查清单:9 项硬性指标
Web 控制器评审必须逐项验证:
- 控制器类名使用允许的顶层业务域前缀;legacy 名称已登记迁移;
- Web 请求/响应 DTO 使用允许的业务域前缀;legacy 名称已登记迁移;
chat2db-community-web的端点返回标准包装器或明确的允许例外;- 控制器不直接依赖
GatewayUtil、WorkspaceStorageWebFacade、适配器、存储、Mapper、Repository 或实现类; - 控制器只注入 domain-api 服务接口、转换器、HTTP 绑定类型与结果包装器,不存在
web.api.service业务包装层; - 端点方法体不直接调用外部依赖、静态业务 facade、
System.exit、JDBC 连接、OSS 客户端或任务管理器; - 端点方法体不含可疑的业务编排、对象变更、业务对象构造或过长的代码;
- 端点方法不调用其它端点方法或私有控制器 helper;
@RequestBody端点通过@Valid或@Validated触发 Bean Validation。
契约特别强调:即使表面类型满足上述规则,复杂 legacy 端点仍需人工评审。这是该契约与普通命名规范的本质区别——它审查的是行为边界而非静态签名,DbDataSourceController 中 @GetMapping("/datasource/connect") 返回 ListResult<DatabaseResponse>、@Valid @NotNull DataSourceAttachRequest 触发校验的写法,正是清单第 3、9 项的合规示范。
9. 契约体系的协同定位
java-web-controller-contracts.md 是 Chat2DB 社区版 spec/code/server 契约族的一员,与其余四份契约构成完整的后端实现约束体系:
| 契约文档 | 约束对象 |
|---|---|
| java-interface-contracts.md | 服务接口签名与语义契约 |
| java-impl-contracts.md | 服务实现层行为约束 |
| java-module-boundaries.md | 模块间依赖方向与边界 |
| java-object-converter-contracts.md | 对象转换器职责 |
| java-web-controller-contracts.md | Web 控制器职责边界(本文主题) |
| java-plugin-contracts.md | 插件扩展能力契约 |
对开发者的实操价值在于:新增一个端点时,可以按"命名链 → 包装器 → 服务接口 → 转换器"四步自查——类名是否 DomainObjectController、方法名是否纯动作、返回是否标准包装器、依赖是否只剩 domain-api 接口与转换器;只要这四点成立,控制器大概率就是合规的"瘦控制器"。而在评审存量代码时,则应把 legacy 命名(如 DbDataSourceController、SysSystemController)与复杂端点列为人工复核重点,优先推动其背后的业务能力下沉到 domain-api 服务,而不是在 Web 层修修补补。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0634
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java01
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java00
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00