腾讯 APIJSON 生态出现了首个面向 Rust 的零代码接口与文档开源项目。它试图解决一个长期存在的问题:业务表和查询条件频繁变化时,后端不必为每个列表、详情和增删改查需求重复编写 Controller、Service、DAO 以及配套接口文档。
APIJSON 既是一套面向 API 的 JSON 网络传输协议,也包含依据协议实现的 ORM 能力。客户端通过结构化 JSON 描述查询目标,服务端完成解析、权限检查、数据库访问和结果组装。对于需求变化快、采用前后端分离架构的中小型项目,这种模式有机会显著压缩接口开发和沟通成本。
零代码省掉的不是所有代码
传统 REST 接口通常按资源和场景拆分端点。例如,一个订单页面可能陆续需要:
- 查询订单及用户信息;
- 按状态、时间和金额筛选;
- 增加分页、排序与聚合字段;
- 为移动端返回稍有不同的数据结构。
常规做法会不断增加 DTO、查询方法和接口文档。APIJSON 则把一部分变化转移到请求 JSON 中,使客户端声明需要哪些表、字段、关联关系和过滤条件。后端保留统一入口,通过协议解释请求。
这里的“零代码”主要指常见 CRUD 接口无需逐个手写,并不意味着系统不再需要工程代码。数据库模型、访问权限、字段暴露规则、事务、审计、限流和复杂业务动作仍然需要明确配置或实现。支付、库存扣减、审批流等带有业务不变量的操作,也不适合被简单数据库写入取代。
Rust 实现带来的工程价值
Rust 适合构建长期运行的网络服务:它强调内存安全、明确的错误处理和可控的运行时成本。将 APIJSON 的协议解析、ORM、接口服务及文档能力带入 Rust 生态,意味着团队可以在 Rust 服务中采用统一的声明式数据接口,而不必额外维护另一种语言编写的网关。
更值得关注的是边界处理。一个 APIJSON 服务不能只是把任意 JSON 拼成 SQL。可靠实现至少要覆盖:
- 将字段名、表名、操作符映射到受控的元数据,而不是直接拼接字符串;
- 使用参数化查询处理外部输入;
- 限制可访问的表、列、关联层数、分页大小和排序字段;
- 在执行前完成身份认证、行级权限及字段级权限判断;
- 为查询耗时、扫描行数、错误类型和调用方建立可观测性。
Rust 的类型系统能帮助实现者约束解析后的请求结构,但类型安全不能自动解决越权查询和昂贵查询。协议层仍需建立独立的安全策略。
可以这样实践:先验证统一查询入口
下面是一个可直接改造的 HTTP 调用示例。由于来源摘要没有给出该 Rust 项目的具体路由、表结构和启动参数,这里假设服务监听 http://127.0.0.1:8080,查询入口为 /get,数据库中存在 User 表。运行前请按照实际项目修改地址、鉴权头、表名和字段名。
curl --fail-with-body \
-X POST 'http://127.0.0.1:8080/get' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer replace-with-your-token' \
-d '{
"User": {
"id": 1001,
"@column": "id,name,createdAt"
}
}'
列表查询可以把分页和排序也放进声明中。以下结构采用 APIJSON 常见的对象数组表达方式,但具体操作符应以该 Rust 项目的实现文档为准:
curl --fail-with-body \
-X POST 'http://127.0.0.1:8080/get' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer replace-with-your-token' \
-d '{
"User[]": {
"count": 20,
"page": 0,
"User": {
"status": "ACTIVE",
"@column": "id,name,status,createdAt",
"@order": "createdAt-"
}
}
}'
验证时不要只看能否返回数据,还应检查生成 SQL、查询计划和权限行为。可以准备三个测试账号,分别拥有管理员、普通用户和匿名权限,然后用同一请求确认返回字段与数据行是否正确收敛。
接入现有 Rust 服务时的安全外壳
如果项目需要在 APIJSON 服务前增加统一鉴权,可以这样实践。下面是一个基于 Axum 的最小代理示例,假设上游 APIJSON 服务运行在 127.0.0.1:8080。它只演示令牌检查和请求转发,生产环境还需加入超时、限流、请求体大小限制和结构化日志。
将以下依赖加入 Cargo.toml:
[package]
name = "apijson-gateway"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.7"
reqwest = { version = "0.12", features = ["json"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
再创建 src/main.rs:
use axum::{
extract::Json,
http::{HeaderMap, StatusCode},
routing::post,
Router,
};
use serde_json::Value;
async fn query(
headers: HeaderMap,
Json(payload): Json<Value>,
) -> Result<Json<Value>, (StatusCode, String)> {
let token = headers
.get("authorization")
.and_then(|value| value.to_str().ok());
if token != Some("Bearer local-demo-token") {
return Err((StatusCode::UNAUTHORIZED, "invalid token".into()));
}
let response = reqwest::Client::new()
.post("http://127.0.0.1:8080/get")
.json(&payload)
.send()
.await
.map_err(|error| (StatusCode::BAD_GATEWAY, error.to_string()))?;
let status = response.status();
let body = response
.json::<Value>()
.await
.map_err(|error| (StatusCode::BAD_GATEWAY, error.to_string()))?;
if !status.is_success() {
return Err((StatusCode::BAD_GATEWAY, body.to_string()));
}
Ok(Json(body))
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/api/query", post(query));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
运行并调用代理:
cargo run
curl --fail-with-body \
-X POST 'http://127.0.0.1:3000/api/query' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer local-demo-token' \
-d '{"User":{"id":1001}}'
这个代理不是完整的权限系统,但它展示了一个重要原则:即使 CRUD 由协议自动生成,身份、租户和安全策略仍应位于可信的服务端边界内。
采用前先做一轮约束测试
APIJSON 最适合数据模型相对清晰、查询变化频繁、业务动作较轻的系统,例如内部管理台、内容后台和中小型前后端分离项目。对于强事务、复杂领域规则或对接口形态有严格长期兼容要求的系统,可以把它用于查询和常规维护接口,同时保留显式编写的领域服务。
正式接入前,建议完成以下检查:
- 确认 Rust 实现支持当前数据库、事务模式和必要的 APIJSON 操作符;
- 建立表、字段、关联、排序和聚合函数的白名单;
- 为分页上限、查询深度、执行时间和返回体大小设置硬限制;
- 验证行级、列级及多租户隔离,特别关注批量更新和删除;
- 将自动文档纳入版本管理,检查协议升级时的兼容性;
- 对高频查询执行压测,并审查数据库执行计划;
- 为复杂业务命令保留专用端点,不把所有行为都降格为 CRUD。
Rust 项目让 APIJSON 的零代码接口模式进入了新的技术栈,但真正决定它能否落地的不是“少写了多少 Controller”,而是团队能否把自动化能力放进严格的权限、资源和业务边界之内。