业界首创?六类请求对象把公网、内网与鉴权写进类型系统
架构判断: 请求对象不只是字段容器。它应该让方法签名直接暴露流量方向、登录要求和业务参数形态,让错误的接口声明在代码评审阶段就显形。
很多 Java 项目为每个接口创建一个 Request DTO,业务字段很清楚,信任边界却是隐形的:这是公网请求还是服务间调用?是否必须登录?签名、密文和用户身份应该在哪一层消失?
当这些答案藏在 URL、注解组合和开发者记忆里,接口越多,漏鉴权、重复解析 Token、外部字段穿透内部服务的概率就越高。问题不在 DTO 数量,而在类型没有表达架构事实。
MetaLite 把请求定义成一个正交类型系统:流量方向 × 登录要求 × 业务参数形态,最终收敛为六个请求类;业务参数再统一实现 Param,由泛型和校验注解建立硬约束。本文结合请求基类、Gateway/Admin 两侧 SysUserApi 与 WebUtil 源码验证这套设计。
这里的“首创”是作者对完整组合的原创设计主张,不等同于已经穷尽全球代码库、论文或专利。真正值得讨论的不是标签,而是它能否用类型消灭一批本可避免的接口错误。
先把整套设计压缩成一张首屏判定表:
| 代码元素 | 只负责表达什么 | 形成的硬约束 |
|---|---|---|
External* / Internal* | 公网或内网信任边界 | 评审时直接识别流量方向 |
ExternalLogin* | 是否要求登录 | 登录要求不再藏在注解组合里 |
*BizParamReq<T extends Param> | 是否携带业务载荷 | Entity、DTO、Map 不能误入参数槽位 |
@NotNull + @Valid | 对象存在且字段合法 | 缺少载荷与字段非法走统一校验入口 |
核心公式只有一句:方法名描述业务动作,请求外壳描述信任边界,Param 描述业务输入。 六个请求类不是为了穷举所有组合,而是主动删除“内网再次携带公网登录凭证”等非法状态。
一、一个接口签名,至少应该暴露四层事实
传统接口只暴露业务参数:
Resp<UserDto> create(@RequestBody SaveUserRequest request)
阅读者仍要去翻注解、过滤器和文档,才能确定安全规则。MetaLite 希望签名本身回答四个问题:
| 必须回答的问题 | 由什么表达 | 错选类型的直接风险 |
|---|---|---|
| 请求来自公网还是内网 | External* / Internal* | 公网协议穿透业务服务,或内网接口误暴露 |
| 是否要求登录 | ExternalLogin* | 漏校验 Token,或登录接口陷入“先登录才能登录” |
| 是否携带业务参数 | *BizParamReq<T> | 空壳请求混入业务字段,校验入口不统一 |
| 业务参数是什么 | T extends Param | 使用裸 Map、Object 或任意 POJO,契约不可检索 |
这不是“多建几个类”,而是把接口评审从“相信开发者记得规则”变成“检查类型是否选对”。
二、三个维度为什么收敛成六种请求
InternalReq
└── InternalBizParamReq<T extends Param>
ExternalReq
├── ExternalBizParamReq<T extends Param>
└── ExternalLoginReq
└── ExternalLoginBizParamReq<T extends Param>
| 请求类型 | 边界 | 代表性接口声明 |
|---|---|---|
ExternalReq | 公网、免登录、无参数 | Resp<SystemStatusDto> status(ExternalReq req)(标准模板) |
ExternalBizParamReq<T> | 公网、免登录、有参数 | Resp<LoginDto> login(ExternalBizParamReq<LoginParam> req) |
ExternalLoginReq | 公网、登录、无参数 | Resp<SysUserEntity> getMyself(ExternalLoginReq req) |
ExternalLoginBizParamReq<T> | 公网、登录、有参数 | Resp<Void> createUser(ExternalLoginBizParamReq<SaveSysUserParam> req) |
InternalReq | 内网、无参数 | Resp<SysUserEntity> getMyself(InternalReq req) |
InternalBizParamReq<T> | 内网、有参数 | Resp<Void> createUser(InternalBizParamReq<SaveSysUserParam> req) |
三个二元维度理论上能组成八种组合,MetaLite 却主动删掉了“内网再次携带登录凭证”的两种。用户身份应由 Gateway 建立,再通过受控 Header 与 ThreadContext 传播;内部服务不应每跳重新解析公网 Token。
少掉的两个类型,恰恰是设计价值。 类型系统不是枚举所有可能,而是删除架构上不应存在的状态。
三、Param 不是命名后缀,而是请求载荷的准入证
Param 本身很薄:
public interface Pojo extends Serializable {}
public interface Param extends Pojo {}
薄不等于没有约束。首屏表格中的三道门分别在编译期、对象校验期和字段校验期生效。例如 SaveSysUserParam implements Param,用户名和手机号使用 @NotBlank;类型先证明它属于业务入参,@NotNull 再证明载荷存在,@Valid 最后递归验证字段。
需要把边界说清:Param 目前是标记接口,不会自动禁止业务逻辑、可变字段或 Entity 继承;这些仍需要包结构规范和 ArchUnit 等静态规则继续收紧。但它已经让所有带参请求拥有统一、可搜索、可扩展的准入点。
除 ExternalReq 一行是框架支持模板外,其余类型均对应当前 Gateway/Admin 的真实接口形态。
四、同一个业务接口,公网与内网只改变外壳
以登录为例,Gateway 接收公网类型:
@PostMapping("/login")
public Resp<LoginDto> login(
@RequestBody @Valid ExternalBizParamReq<LoginParam> req,
BindingResult bindingResult) {
return internalServiceClient.callOneInstanceRtnData(
RpcRequest.builder()
.param(WebUtil.genInternalReq(req, LoginParam.class))
.build(),
LoginDto.class);
}
Admin 的同路径接口只接收内部类型:
@PostMapping("/login")
public Resp<LoginDto> login(
@RequestBody @Valid InternalBizParamReq<LoginParam> req,
BindingResult bindingResult) {
return sysUserService.login(req.getBizParam());
}
LoginParam 没变,业务语义没变,改变的是信任外壳。Gateway 负责调用方、时间、签名、加密和用户协议;Admin 只消费已经收敛后的业务参数。
五、公网系统字段为什么不能塞进 Param
ExternalReq 统一承载公网协议字段:
| 字段 | 责任 | 不属于业务 Param 的原因 |
|---|---|---|
apiVersion | 外部协议版本 | 协议演进不应修改每个业务类 |
appId | 调用方身份 | 属于 Gateway 安全域 |
timestamp | 时间窗与防重放输入 | 不是业务发生时间 |
sign | 系统参数签名 | 签名算法由调用方配置决定 |
plaintext | 明文业务 JSON | 是外部传输形态,不是 Service 入参 |
encryptData | 加密业务 JSON | 不应进入内部业务模型 |
ExternalLoginReq 只追加 userToken。而外部 *BizParamReq<T> 中的 bizParam 主要用于 OpenAPI Schema 展示,源码已注明调用方不要直接上送;真实载荷走 plaintext 或 encryptData,由 Gateway 解密后按 Param 类型恢复。
六、类型如何驱动处理器,而不是只服务命名
| 类型命中 | 框架职责 |
|---|---|
ExternalReq | 调用方、时间、IP、签名等校验 |
ExternalLoginReq | Token 校验与用户上下文建立 |
| 外部请求含业务载荷 | 解密并生成指定 Param |
| 内部 RPC 发起 | 填充 provider、consumer 与上下文 Header |
| 外部响应要求加密 | data 加密为 encryptData,再清空明文 |
CallerAuthHandler 查找 ExternalReq,UserAuthHandler 查找 ExternalLoginReq。因此 ExternalLoginBizParamReq 会自然继承调用方认证与用户认证,而不是在 Controller 上重复声明两组开关。
WebUtil.genInternalReq 则完成信任收敛:外部无参请求变成 InternalReq;外部有参请求读取 plaintext、校验 JSON 对象形态、按指定 Param 类型反序列化,再生成 InternalBizParamReq<T>。
七、首创式设计最需要公开的边界
InternalReq是协议类型,不是服务身份证书;内部端口仍需要网络隔离或服务间认证。- 当前类型没有表达 HTTP Method、幂等性、租户和数据权限,不能让请求基类承担所有治理职责。
ExternalReq的无业务参数标准场景已有实现能力,但当前没有直接业务 Controller 实例,不能把模板写成既成案例。Param是准入标识而非不可变对象约束,仍需静态架构测试防止 Entity、DTO 和 Param 相互污染。- 如果内部接口允许绕过 Gateway 并伪造身份 Header,类型系统无法独自建立可信身份。
八、发布前只需要破坏这七条不变量
| 破坏场景 | 必须断言 |
|---|---|
| 公网 appId、时间戳或签名无效 | 目标方法执行前拒绝 |
| 登录接口使用无登录类型 | 代码评审或架构测试阻断 |
| userToken 无效 | 不建立 loginUserId |
| plaintext 不是对象 JSON | 不生成内部 Param |
| Param 为空或字段非法 | @NotNull/@Valid 阻断 Service |
| provider/consumer 缺失 | RPC 发出前失败 |
| 绕过 Gateway 伪造身份 Header | 网络或服务认证层拒绝 |
六个请求类最有价值的地方,不是数量恰好为六,而是让错误选择留下明确证据。MetaLite 的接口哲学可以压缩成一句话:安全策略不要只写在文档里,要尽可能写进类型、继承关系、泛型上界和处理器选择规则。
框架简介
MetaLite 是面向企业生产环境的新一代 Java 微服务技术底座。系列文章重点分享代码背后的设计思路、技术取舍与工程实践。
源码基线
JDK 21、Spring Boot 3.2.9、Spring Cloud 2023.0.1、Spring Cloud Alibaba 2023.0.1.3,具体组件版本以项目 backend-bom 为准。
作者简介
15 年 Spring 体系企业级开发经验,专注于 Java 微服务架构、工程治理与生产实践。
持续更新
MetaLite 系列内容将持续更新,围绕核心设计、源码链路、技术取舍与生产实践展开。欢迎关注作者,及时获取后续内容。
在线演示
演示地址: https://admin.metalite.top/
演示账号: guess
演示密码: admin@2026

451

被折叠的 条评论
为什么被折叠?



