安装与使用 Ecat
从 cargo install 到运行中的 HTTP 与 gRPC 服务,再看 ORM、19 个数据后端与已知限制。示例与仓库 examples 使用同一套 API。
让服务跑起来
四个具体步骤。右栏保留完整命令序列。
准备 Rust 与 protoc
Ecat 使用 edition 2024,需要 Rust 1.85 或更新版本。CLI 和生成的代码还需要 protoc。
# Ubuntu / Debian
sudo apt install protobuf-compiler
# macOS
brew install protobuf
# verify
rustc --version
protoc --version
安装 CLI
从 crates.io 安装。二进制随包携带宠物资源与 ASSETS-LICENSE 说明。
cargo install ecat-cli
ecat --version
创建服务
创建项目,添加 proto 文件,再生成客户端与服务端代码。生成的 build.rs 已接好 tonic-build。
ecat new helloworld
cd helloworld
ecat proto add proto/service.proto
ecat proto client proto/service.proto
ecat proto server proto/service.proto -t internal/service
运行
默认 HTTP 服务监听 0.0.0.0:8000。使用 --watch 在 src/ 变更时重启。
ecat run
ecat run --watch
用 Docker、Kubernetes 或 Helm 部署
模板以仓库根目录作为构建上下文。
docker build -t ecat-app -f ecat-deploy/Dockerfile .
kubectl apply -f ecat-deploy/k8s-deployment.yaml
helm install my-ecat ecat-deploy/helm/
使用各个部件
聚合 crate 按 feature 重新导出模块;应用、中间件、数据、ORM、CLI 与参考表都在下面。
应用生命周期
AppBuilder 组合服务器、生命周期钩子和信号处理。run() 阻塞到 SIGTERM 或 SIGINT。
- HTTP 与 gRPC 服务监听不同端口。
- on_start 与 on_stop 钩子是异步的。
- 聚合 crate 让导入路径更短。
- features:http / grpc / middleware / auth / client / events / metrics / tracing / circuit-breaker / consul / remote / redis / orm / mssql,默认 http + grpc。
use ecat::App;
use ecat_transport_http::HttpServer;
use ecat_transport_grpc::GrpcServer;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let app = App::builder()
.name("my-service")
.version("v1.0.0")
.server(HttpServer::new("0.0.0.0:8000"))
.server(GrpcServer::new("0.0.0.0:9000"))
.on_start(|| async {
tracing::info!("service started");
Ok(())
})
.on_stop(|| async {
tracing::info!("service stopped");
Ok(())
})
.build()?;
app.run().await?; // 阻塞到 SIGTERM 或 SIGINT
Ok(())
}
组合中间件
每个 layer 都是 tower::Layer。顺序有影响:恢复与追踪在外层,认证与安全靠近 Handler。
- JWT 密钥至少 32 字节。
- 超时与熔断保护下游调用。
- SecurityLayer 阻断高风险请求模式。
use tower::ServiceBuilder;
use ecat_middleware::{RecoveryLayer, TracingLayer, LoggingLayer, TimeoutLayer};
use ecat_circuit_breaker::CircuitBreakerLayer;
use ecat_security::SecurityLayer;
use ecat_auth::JwtAuthLayer;
use std::time::Duration;
// JWT 密钥至少 32 字节
let jwt = JwtAuthLayer::new("change-me-32-bytes-minimum-secret")
.expect("valid jwt secret");
// 顺序有影响
let layer = ServiceBuilder::new()
.layer(RecoveryLayer)
.layer(TracingLayer)
.layer(LoggingLayer)
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.layer(CircuitBreakerLayer::new())
.layer(jwt)
.layer(SecurityLayer::new());
连接数据后端
每个后端都有 XxxConfig 与 from_config()。Redis、SQLx、ClickHouse 和其余后端形状一致。
- 配置可以来自 JSON 或 YAML。
- 出站超时与熔断按 client 配置。
- feature 名与后端名对应。
- Memcached 是内存实现,不能当生产持久缓存;其余后端均带超时与熔断。
use ecat_data_redis::{RedisCache, RedisConfig};
use ecat_data_sqlx::{SqlxClient, SqlxConfig};
use std::time::Duration;
// 从 JSON 或 YAML 加载配置
let config: serde_json::Value = serde_json::from_str(r#"{
"redis": {"url": "redis://localhost:6379"},
"sql": {"url": "postgres://user:pass@localhost/db"}
}"#)?;
// Redis
let redis_cfg: RedisConfig = serde_json::from_value(config["redis"].clone())?;
let cache = RedisCache::from_config(redis_cfg).await?;
cache.set("key", b"value", Duration::from_secs(60)).await?;
// RDBMS
let sql_cfg: SqlxConfig = serde_json::from_value(config["sql"].clone())?;
let db = SqlxClient::from_config(sql_cfg).await?;
let rows = db.query("SELECT * FROM users").await?;
CLI 工作流
CLI 覆盖脚手架、proto 生成、开发运行、构建、升级与宠物导出。
- ecat run --watch 在文件变更时重启。
- ecat upgrade 改写 ecat-* 依赖版本。
- ecat pet 导出全部 13 个矢量形态。
ecat new helloworld
ecat run --watch
ecat build
ecat upgrade
# 浏览形态
ecat pet list
# 输出单个 SVG
ecat pet show smile
# 导出全部 13 个
ecat pet export ./pet
ORM:实体、查询与迁移
#[derive(Entity)] 生成表映射;类型状态查询构建器让没有过滤条件的删改无法通过编译;同一套 API 同时接受 &db 与 &tx。
- 五套方言:SQLite、PostgreSQL、MySQL、SQL Server、ANSI。
- 分页、关联预加载(IN 查询,杜绝 N+1)、软删除与乐观锁。
- Migrator 维护版本表,create_table 生成建表 SQL。
use ecat_orm::query::{Op, Order};
use ecat_orm::{Entity, Migrator, create_table};
use time::OffsetDateTime;
#[derive(Entity, Debug, Clone)]
#[entity(table = "users")]
pub struct User {
#[entity(pk, auto_increment)]
pub id: i64,
pub name: String,
#[entity(created_at)]
pub created_at: Option<OffsetDateTime>,
#[entity(soft_delete)]
pub deleted_at: Option<OffsetDateTime>,
#[entity(version)]
pub version: i64,
#[entity(has_many = "Post", foreign_key = "user_id")]
pub posts: Vec<Post>,
}
// 迁移建表;&db 与 &tx 共用同一套 CRUD
Migrator::new(&db)
.add("001_users", create_table::<User>())
.run().await?;
let id = User::insert(&db, &user).await?;
let mut got = User::find_by_id(&db, id).await?.unwrap();
got.name = "alice-v2".into();
User::update(&db, &got).await?;
// 分页 + 预加载:每个关联一条 IN 查询
let page = User::query()
.filter("name", Op::Like, "ali%")?
.order_by("id", Order::Desc)?
.with(&[UserRelation::Posts])
.paginate(&db, 1, 20)
.await?;
支持的数据库(19 个)
超时与熔断按 client 配置;Memcached 是内存实现,不能用于生产持久缓存。
- RDBMSSQLite · PostgreSQL · MySQL · TiDB · SQL Server✅ 超时 + 熔断
- 缓存Redis✅ 超时 + 熔断
- 缓存Memcached⚠️ 内存实现(非生产)
- 搜索OpenSearch · Elasticsearch✅ 超时 + 熔断
- 图Neo4j · NebulaGraph · ArangoDB✅ 超时 + 熔断
- 时序InfluxDB · IoTDB · QuestDB · TDengine✅ 超时 + 熔断
- 文档 / 对象 / OLAPMongoDB · S3 / MinIO · ClickHouse✅ 超时 + 熔断
默认查询超时 30 秒(0 = 禁用);熔断阈值 0.5 / 窗口 30 秒 / 半开探测 3 / 打开 10 秒;HTTP 后端另有 max_concurrency(默认 32)。
除 ecat-data-sqlx 与 ecat-data-memcached 外,各后端 Config 支持可选 tls;SqlxConfig 配 tls 会启动报错,Memcached 的 tls 字段静默无效。
技术栈
运行时 tokio · HTTP axum · gRPC tonic · Protobuf prost + tonic-build · 中间件 tower::Service / Layer · 日志/追踪 tracing · 指标 prometheus · 序列化 serde + prost · 攻击检测 security-rust · RDBMS sqlx · Redis redis-rs · JWT jsonwebtoken · HTTP Client reqwest · CLI clap
Kratos 概念映射
kratos.New()App::builder()Builder 模式http.Handlertower::ServiceRust 生态标准 traithttp.Serveraxum::Router社区主流 HTTP 框架grpc.Servertonic::transport::Server成熟的 gRPC 实现proto generateprost + tonic-build社区标准 protobufregistry.DiscoveryRegistrytrait可插拔注册发现config.SourceConfigSourcetrait多源配置加载
已知限制
- GraphQL 支持字段参数与嵌套 selection,但不支持别名、fragment 与多顶层字段,请勿暴露为通用 GraphQL 端点。
- OAuth2 内省缓存 key 是 token 的 SHA-256;缓存值经白名单过滤,默认 TTL 300 秒。
- Kafka 默认 auto_commit=false 且无手动 commit;重启从分区末尾(latest)重读,停机期间的消息会被跳过。
- sqlite::memory: 不支持跨连接事务;需要多连接时改用文件路径。
- join 的列清单不带表前缀;被连表与主体有同名列时真库报 ambiguous column name。
- 批量 update_many 的乐观锁冲突无法定位到行;只能比较返回行数,或逐行 update。
- join 的表名与 ON 条件不做白名单校验,与 filter_raw 同一信任边界。