请求校验与响应转换
通过框架统一控制请求大小、复用业务异常和转换响应值,可以让 Handler 专注于参数模型和服务调用。通用校验位于 tio-utils,HTTP 参数绑定位于 tio-http-common,响应转换位于 tio-boot,参数异常与业务异常由 java-model 提供。
统一配置正文大小
# 普通 JSON、表单和文本正文:32 KiB
http.max-request-body-size=32768
# 所有请求的正文总上限:20 MiB
http.multipart.max-request-size=20971520
# multipart 文件相关配置:10 MiB
http.multipart.max-file-size=10485760
所有值均为字节整数。http.max-request-body-size 默认 0,沿用总上限;正数表示普通正文上限,负数会阻止启动。普通正文同时受到总上限约束。multipart/form-data 上传继续使用原来的总请求及文件配置。
框架解码器在接收完整正文之前检查 Content-Length,超限时拒绝解码并关闭连接;这是传输层限制,不经过业务异常处理器。字段必填、长度和枚举仍由应用使用 ParameterValidator 校验。
实体与动态字段
确定字段使用请求和响应实体,动态字段使用 Kv。tio-http-common 提供 nexus.io.tio.http.common.utils.ParameterValidationUtils,统一绑定 HTTP 参数,应用无需重复编写 Content-Type 分支。
body(request, type) 复用 HttpRequest.getRequestMap():JSON 对象字段覆盖同名查询参数,表单和 multipart 使用框架已解码参数,空正文可以使用已有查询或表单参数。没有正文也没有参数时拒绝请求;JSON 数组、标量、null、非法 JSON 和实体类型转换失败统一抛出 ParameterValidationException。空 JSON 对象可以绑定,字段必填规则由调用方使用 ParameterValidator 校验。文件内容仍通过上传文件 API 获取。
该方法使用配置的 JSON provider 将合并后的参数绑定为实体。字段约束、密码策略、手机号规则和业务凭证检查可以封装在应用自己的校验工具中。全局异常处理器捕获 ParameterValidationException 并返回 HTTP 400,业务条件使用 BusinessException.require。
import com.jfinal.kit.Kv;
import nexus.io.tio.http.common.HttpRequest;
public class DynamicParameters {
public Kv read(HttpRequest request) {
return Kv.create().set(request.getRequestMap());
}
}
动态参数直接包装为 Kv,保留参数中的显式 null;确定字段使用下面的实体绑定方式。
一个接口对应一个 Handler
请求模型放在 model 包,服务放在 service 包,Handler 放在 handler 包。下列文件分别保存到对应包中:
package example.model;
public record MaterialRequest(String caseId) { }
package example.service;
import com.jfinal.kit.Kv;
import nexus.io.db.activerecord.Db;
import nexus.io.model.exception.BusinessException;
public class MaterialService {
public Kv detail(long caseId) {
Kv row = Db.findFirstMap("select id, title from case_material where id=?", caseId);
BusinessException.require(row != null, 404, "NOT_FOUND", "Material not found");
return row;
}
}
package example.handler;
import com.jfinal.kit.Kv;
import example.model.MaterialRequest;
import example.service.MaterialService;
import nexus.io.jfinal.aop.Aop;
import nexus.io.model.body.RespBodyVo;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.boot.utils.ResponseValueNormalizer;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.common.utils.ParameterValidationUtils;
import nexus.io.tio.utils.validator.ParameterValidator;
public class MaterialHandler {
public HttpResponse handle(HttpRequest request) {
MaterialRequest input = ParameterValidationUtils.body(request, MaterialRequest.class);
long caseId = ParameterValidator.id(input.caseId(), "caseId");
MaterialService service = Aop.get(MaterialService.class);
Kv data = service.detail(caseId);
RespBodyVo body = RespBodyVo.ok(ResponseValueNormalizer.normalize(data));
return TioRequestContext.getResponse().respond(body);
}
}
import example.handler.MaterialHandler;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.http.server.router.HttpRequestRouter;
HttpRequestRouter router = TioBootServer.me().getRequestRouter();
router.post("/api/material/detail", new MaterialHandler()::handle);
Handler 和拦截器直接创建;Service 使用 Aop.get。认证由已注册的拦截器完成,服务继续检查资源归属与业务权限。
显式响应转换
ResponseValueNormalizer.normalize(data) 不改变全局 JSON 设置,调用方按接口需要手动调用:
- Long 转十进制字符串,保留 JavaScript 客户端读取大整数的精度。
- Timestamp 转 UTC Instant 字符串,保留小数秒。
- Map/Kv、List 和 record 递归转换,保留 null。
- record 支持 accessor 上的 FastJson2
JSONField(name=...)别名;空别名保留字段原名。 - PostgreSQL PGobject 仅对 json/jsonb 解析为结构化数据,再递归转换;其他数据库类型保留原值。
输入实体和数据库对象不会被修改。普通 JavaBean、数组和其他类型保留原值,由所选 JSON provider 负责序列化;需要递归转换时使用上面列出的容器和 record。
统一业务异常
import nexus.io.model.exception.BusinessException;
BusinessException.require(allowed, 403, "FORBIDDEN", "Permission denied");
BusinessException.require(valid, "Invalid operation");
throw new BusinessException(409, "CONFLICT", "Conflicting operation");
全局异常处理器捕获同一个 BusinessException,用 getStatus() 设置 HTTP 状态,用 getMessage() 生成 RespBodyVo.fail,按接口契约选择将 getCode() 放入业务错误信息。无业务码的构造方式仍然可用。参数异常则捕获 ParameterValidationException,不与业务异常混用。
通用工具复用
import java.sql.Timestamp;
import java.time.Instant;
import nexus.io.tio.utils.crypto.Pbkdf2PasswordUtils;
import nexus.io.tio.utils.date.JdbcTimeUtils;
String encoded = Pbkdf2PasswordUtils.hash("example-password-123");
boolean matches = Pbkdf2PasswordUtils.matches("example-password-123", encoded);
Instant timestamp = JdbcTimeUtils.toInstant(Timestamp.from(Instant.now()));
密码工具使用随机盐和 PBKDF2WithHmacSHA256,校验沿用保存的参数格式。JdbcTimeUtils 接受 Timestamp、Instant、OffsetDateTime 和 null。业务专属密钥配置、手机号用途前缀、权限规则保留在应用服务中。
函数路由与 Service 方法
函数路由可以直接注册 Service 方法并省略只负责转发的 Handler。完整示例、参数绑定、异常处理及 HttpRequestRouter.post 适配方式见 HttpRequestFunction:直接注册 Service 方法。
统一参数异常类型
ParameterValidationException 与 BusinessException 统一由 java-model 提供。校验工具 ParameterValidator 仍位于 tio-utils,HTTP 参数绑定位于 tio-http-common;它们抛出同一个参数异常,便于应用集中处理。
import nexus.io.model.exception.ParameterValidationException;
throw new ParameterValidationException("name is required");
全局异常处理器使用上面的导入,捕获后设置 HTTP 400 并返回 RespBodyVo.fail(400, error.getMessage())。完整示例见函数路由的异常处理。迁移已有应用时,将旧包导入改为上述路径,并重新编译依赖方,使抛出与捕获使用同一类型。
