本编码规范是《Rust (Actix-web) 开发规范》的补充文档,专门针对编码细节和实现规则。通过遵循本规范,团队可以:
为优化AI辅助开发工具的理解和遵守,本规范采用以下结构化表述:
- 必须(MUST):绝对要求,无例外情况
- 严禁(MUST NOT):绝对禁止,无例外情况
- 应该(SHOULD):强烈建议,除非有充分理由
- 不应该(SHOULD NOT):强烈不建议,除非有充分理由
- 可以(MAY):可选,根据具体情况决定
```
[规则ID] [强度] [规则描述]
适用范围: [模块/类型]
示例:
正例: [代码示例]
反例: [代码示例]
理由: [解释原因]
```
- NAM: 命名规范(Naming)
- STR: 代码结构(Structure)
- ACT: Actix-web特定规范(Actix)
- ERR: 错误处理(Error Handling)
- DB: 数据库(Database)
- TEST: 测试(Testing)
- DTO: 数据传输对象(Data Transfer Object)
- RESP: 接口响应格式(Response Format)
- 使用明确、无歧义的自然语言描述
- 提供正面和反面示例对比
- 解释规则背后的原理和意图
- 使用一致的术语和分类体系
使用建议:
严禁使用其他编程语言的关键字或常用标识符作为 Rust 变量、函数或类型名,即便其在 Rust 中合法。以下表格列出了应避免使用的标识符,按语言和严重程度分类:
| 标识符 | 所属语言 | 类型 | 严重程度 | 原因与替代建议 |
| :--- | :--- | :--- | :--- | :--- |
| desc, limit, date, datetime ,timestamp, where, match, set, map, string, str, foreach, use, using, name, order, group, key, value, index, table, column, schema, view, join, left, right, inner, outer, full, cross, natural, on, as, is, null, not, and, or, between, in, like, ilike, exists, all, any, some, case, when, then, else, end, distinct, over, partition, rows, range, preceding, following, current, row, first, last, lead, lag, ntile, rank, denserank, percentrank, cumedist, rownumber, filter, within, order, asc, desc | SQL | 关键字/函数名 | 严格禁止 | SQL 关键字或常用函数名,易导致 SQL 注入或 ORM 映射错误。替代:添加业务前缀,如 itemdescription、querylimit、start_date |
| class, interface, public, private, protected, static, final, abstract, synchronized, volatile, transient, native, strictfp, throws, throw, try, catch, finally, new, instanceof, extends, implements, import, package, super, this, void, boolean, byte, char, short, int, long, float, double, String, Object, Class, System, out, println, printf, main, args, var, let, yield, record, sealed, permits, non-sealed, when | Java | 关键字/常用类名 | 严格禁止 | Java 关键字或常用类名,影响跨语言代码和生成一致性。替代:使用语义明确的名称,如 userclass、ispublic、static_value |
| async, await, def, lambda, print, input, len, list, dict, tuple, set, frozenset, bytes, bytearray, memoryview, range, slice, str, int, float, complex, bool, True, False, None, Ellipsis, NotImplemented, object, type, isinstance, issubclass, super, classmethod, staticmethod, property, getattr, setattr, delattr, hasattr, callable, hash, id, repr, ascii, format, vars, dir, breakpoint, globals, locals, import, from, as, global, nonlocal, pass, del, return, yield, raise, assert, with, if, elif, else, for, while, try, except, finally, continue, break, and, or, not, is, in | Python | 关键字/内置函数 | 严格禁止 | Python 关键字或内置函数,易导致代码混淆。替代:添加后缀,如 asynctask、printmessage、dict_data |
| echo, printr, vardump, isset, empty, unset, array, list, count, sizeof, inarray, arraysearch, arraykeyexists, arraymap, arrayfilter, arrayreduce, arraywalk, arraywalkrecursive, sort, rsort, asort, arsort, ksort, krsort, usort, uasort, uksort, shuffle, arraymerge, arraymergerecursive, arrayreplace, arrayreplacerecursive, arraychunk, arraycolumn, arraycombine, arraydiff, arrayintersect, arraykeys, arrayvalues, arraypad, arraypop, arraypush, arrayshift, arrayunshift, arrays,lice arraysplice, arraysum, arrayproduct, arrayrand, arrayreverse, arrayflip, arrayunique, arraychangekeycase, arrayfill, arrayfillkeys, range, compact, extract, list, each, current, next, prev, reset, end, key, pos, sizeof, count, strlen, strpos, strrpos, stripos, strripos, strstr, stristr, strchr, strrchr, strreplace, strireplace, strrepeat, strpad, strsplit, strwordcount, strshuffle, strrev, trim, ltrim, rtrim, chop, strtolower, strtoupper, ucfirst, ucwords, lcfirst, striptags, htmlentities, htmlspecialchars, htmlentitydecode, htmlspecialcharsdecode, nl2br, wordwrap, md5, sha1, crypt, passwordhash, passwordverify, date, time, strtotime, mktime, gmdate, idate, getdate, localtime, microtime, datecreate, dateformat, datemodify, dateadd, datesub ,datediff, datetimeset, datedateset, dateisodateset, datetimestampset, dategetlasterrors, timezoneopen, timezonenameget, timezonenamefromabbr, timezoneoffsetget, timezonetransitionsget, timezonelocationget, timezoneidentifierslist, timezoneabbreviationslist, timezoneversionget, datetime, timestamp, interval, period, duration, chronos, carbon | PHP | 内置函数/常用类名 | 建议避免 | PHP 内置函数或常用类名,可能影响跨语言代码生成。替代:使用业务相关名称,如 phparray、echo_output |
| var, final, dynamic, async, await, factory, get, set, import, export, library, part, of, show, hide, external, static, const, late, required, extends, implements, with, mixin, on, class, interface, enum, typedef, void, bool, num, int, double, String, List, Map, Set, Iterable, Runes, Symbol, Type, Function, Null, Never, dynamic, Never, Future, Stream, Iterable, Iterator, List, Map, Set, Queue, LinkedList, HashMap, HashSet, LinkedHashMap, LinkedHashSet, SplayTreeMap, SplayTreeSet, UnmodifiableListView, UnmodifiableMapView, UnmodifiableSetView | Dart | 关键字/常用类型 | 严格禁止 | Dart 关键字或常用类型名,影响跨平台代码一致性。替代:添加前缀,如 dartvar、finalvalue |
| function, typeof, console, window, document, alert, confirm, prompt, setTimeout, setInterval, clearTimeout, clearInterval, requestAnimationFrame, cancelAnimationFrame, fetch, XMLHttpRequest, Promise, async, await, let, const, var, if, else, for, while, do, switch, case, default, break, continue, return, throw, try, catch, finally, new, this, super, class, extends, implements, interface, package, import, export, static, public, private, protected, yield, generator, iterator, Symbol, Map, Set, WeakMap, WeakSet, Array, Object, String, Number, Boolean, Date, RegExp, Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, JSON, Math, Reflect, Proxy, Intl, WebAssembly | JavaScript | 关键字/全局对象 | 严格禁止 | JavaScript 关键字或全局对象,易导致前端代码混淆。替代:使用明确名称,如 jsfunction、consoleoutput |
| fn, struct, trait, impl, mut, const, static, let, pub, use, mod, type, enum, match, if, else, for, while, loop, break, continue, return, self, Self, super, crate, extern, unsafe, async, await, dyn, move, ref, in, where, box, virtual, do, sizeof, alignof, offsetof, typeof, try, macro, union, bool, char, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64, str, String, Vec, Option, Result, Box, Rc, Arc, Cell, RefCell, Mutex, RwLock, Cow, VecDeque, LinkedList, HashMap, HashSet, BTreeMap, BTreeSet, BinaryHeap, None, Some, Ok, Err, true, false | Rust | 关键字/标准类型 | 严格禁止 | Rust 关键字或标准库类型名,易导致编译错误或混淆。替代:使用业务相关名称,避免直接使用语言内置名称 |
使用指导:
正例:itemdescription, querylimit, startdate, asynctask, functionname, username, sourcetype, userarray, resultobject, databasetable, javaclass, pythondict, phparray, dartvar, jsfunction, rustvec_data
反例:desc, limit, date, async, function, name, type, struct, class, var, final, dynamic, echo, print, array, object
目的:明确 "desc" 术语在代码中的使用范围,避免与 SQL 关键字冲突,同时保持团队已有代码库的一致性。
规范:
- 与数据库字段和数据库表直接相关的方法名、字段名
- 示例:getuserdesc()、productdescription、orderdesc
- 通用业务逻辑中的变量名、函数名(除非与数据库直接相关)
- 示例:避免使用 desc 作为临时变量名、通用参数名
- 跨语言传输的 DTO 中,若已有 Java 端定义的 StdDataResponse 包含 desc 字段,Rust 端应保持对应字段名一致
- 在接口返回中,"desc" 字段用于携带可读的业务描述信息,支持多语言
注意事项:
// 模块声明
mod user_service;
// 结构体
pub struct UserService {
// 常量
pub const MAX_USER_COUNT: i32 = 1000;
// 实例变量
user_repository: UserRepository,
}
// 枚举
pub enum OrderStatus {
Pending,
Paid,
Shipped,
}
// 函数
pub fn get_user_by_id(user_id: i64) -> Result {
// 变量
let user_name = String::from("john_doe");
let item_count = 10;
// 业务逻辑
// ...
}
// 反例:使用其他语言关键字
let desc = "description"; // 错误:SQL关键字
let limit = 100; // 错误:SQL关键字
let async = true; // 错误:Rust/JS/Python关键字
let date = "2024-01-01"; // 错误:SQL常用函数名
// 正例:添加业务前缀/后缀
let item_description = "description"; // 正确
let query_limit = 100; // 正确
let is_async_task = true; // 正确
let start_date = "2024-01-01"; // 正确
推荐按功能模块划分,典型结构如下:
src/
├── lib.rs # 库入口,导出公共模块
├── main.rs # 二进制入口(如适用)
├── ctrl/ # 控制器层(HTTP处理)
│ ├── mod.rs
│ ├── user_controller.rs
│ └── order_controller.rs
├── svr/ # 服务层(业务逻辑)
│ ├── mod.rs
│ ├── user_service.rs
│ └── order_service.rs
├── db/ # 数据访问层
│ ├── mod.rs
│ ├── repositories/
│ │ ├── mod.rs
│ │ ├── user_repository.rs
│ │ └── order_repository.rs
│ └── models/ # 数据库模型(可选)
├── dto/ # 数据传输对象
│ ├── mod.rs
│ ├── user_dto.rs
│ └── order_dto.rs
├── tools/ # 工具类
│ ├── mod.rs
│ └── config_provider.rs
└── server/ # 服务器配置
├── mod.rs
├── app_state.rs
└── server_starter.rs
示例 lib.rs:
pub mod ctrl;
pub mod svr;
pub mod db;
pub mod dto;
pub mod tools;
pub mod server;
// 仅导出必要的公共接口
pub use server::app_state::AppState;
pub use server::server_starter::ServerStarter;
// src/lib.rs
pub mod ctrl;
pub mod svr;
pub mod db;
// src/ctrl/mod.rs
pub mod user_controller;
pub mod order_controller;
pub use user_controller::UserController;
pub use order_controller::OrderController;
// src/ctrl/user_controller.rs
use crate::svr::UserService;
use actix_web::{web, HttpResponse, Responder};
pub struct UserController {
user_service: UserService,
}
impl UserController {
pub fn new(user_service: UserService) -> Self {
Self { user_service }
}
pub async fn get_user(&self, user_id: web::Path) -> impl Responder {
// ...
}
}
路由命名规范细节:
- 示例:/GetUserInfo、/CreateOrder、/UpdateProduct
- 原因:与 Java 微服务(Spring Cloud)保持命名一致,便于:
- 跨语言 API 文档生成
- 统一网关路由配置
- 多语言客户端 SDK 生成
- 避免使用 snakecase(/getuser_info)或 kebab-case(/get-user-info)
- 在路径前缀中管理 API 版本,如 /api/v1/GetUserInfo
- 或使用 Scope 进行版本分组:
```rust
web::scope("/api/v1")
.service(getuserinfo)
.service(create_order)
```
- 使用 configure_routes 函数集中注册所有路由
- 避免在 main 函数中分散注册
- 示例:
```rust
pub fn configure_routes(cfg: &mut web::ServiceConfig) {
cfg.service(getuserinfo)
.service(create_order)
.service(health_check) // 健康检查端点
.service(ready_check)
.service(live_check);
}
```
- 使用 {paramname} 格式,参数名使用 snakecase
- 示例:/GetOrderDetail/{order_id}
- 在处理器函数中使用 web::Path 提取
示例:
use actix_web::{get, post, web, HttpResponse, Responder};
#[get("/GetUserInfo")]
pub async fn get_user_info(user_id: web::Path) -> impl Responder {
// ...
}
#[post("/CreateUser")]
pub async fn create_user(user_data: web::Json) -> impl Responder {
// ...
}
// 路由注册
pub fn configure_routes(cfg: &mut web::ServiceConfig) {
cfg.service(get_user_info)
.service(create_user);
}
示例:
#[get("/GetOrderDetail/{order_id}")]
pub async fn get_order_detail(
order_id: web::Path,
query: web::Query,
) -> impl Responder {
let order_id = order_id.into_inner();
let include_items = query.include_items.unwrap_or(false);
// ...
}
#[post("/UpdateOrder")]
pub async fn update_order(
order_data: web::Json,
) -> impl Responder {
// ...
}
示例:
#[derive(Clone)]
pub struct AppState {
pub user_service: UserService,
pub order_service: OrderService,
pub redis_manager: RedisManager,
pub db_pool: MysqlPool,
}
// 在 main 中注册
let app_state = web::Data::new(AppState {
user_service: UserService::new(),
order_service: OrderService::new(),
redis_manager: RedisManager::new(redis_conf),
db_pool: MysqlPool::new(db_conf),
});
App::new()
.app_data(app_state.clone())
.configure(configure_routes)
示例:
#[get("/GetUserInfo")]
pub async fn get_user_info(
data: web::Data,
user_id: web::Path,
) -> impl Responder {
match data.user_service.get_user_by_id(*user_id).await {
Ok(user) => HttpResponse::Ok().json(user),
Err(e) => {
error!("获取用户信息失败: {}", e);
pkg_error_as_http_response(e)
}
}
}
示例:
use actix_cors::Cors;
use actix_web::middleware::Logger;
use actix_web_prom::PrometheusMetrics;
// CORS 配置
let cors = Cors::permissive(); // 开发环境
// 生产环境应限制来源
// let cors = Cors::default()
// .allowed_origin("https://example.com")
// .allowed_methods(vec!["GET", "POST"])
// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
// .max_age(3600);
// Prometheus 指标
let prometheus = PrometheusMetrics::new("api", "/metrics");
App::new()
.wrap(Logger::default())
.wrap(cors)
.wrap(prometheus.clone())
.app_data(web::Data::new(app_state))
.configure(configure_routes)
所有服务必须提供标准健康检查端点,用于容器编排和负载均衡器健康检查:
示例实现:
use actix_web::{get, web, HttpResponse, Responder};
use serde_json::json;
#[get("/health")]
async fn health_check(data: web::Data) -> impl Responder {
// 检查数据库连接
match data.db_pool.get_conn().await {
Ok(_) => {
// 检查 Redis 连接
match data.redis_manager.ping().await {
Ok(_) => HttpResponse::Ok().json(json!({"status": "healthy"})),
Err(e) => HttpResponse::ServiceUnavailable()
.json(json!({"status": "unhealthy", "error": format!("Redis: {}", e)})),
}
}
Err(e) => HttpResponse::ServiceUnavailable()
.json(json!({"status": "unhealthy", "error": format!("Database: {}", e)})),
}
}
#[get("/ready")]
async fn ready_check() -> impl Responder {
// 简化就绪检查,通常检查应用初始化状态
HttpResponse::Ok().json(json!({"status": "ready"}))
}
#[get("/live")]
async fn live_check() -> impl Responder {
// 存活检查,通常只检查进程是否运行
HttpResponse::Ok().json(json!({"status": "alive"}))
}
除了 Prometheus 指标外,建议添加应用自定义指标:
示例配置:
use actix_web_prom::{PrometheusMetrics, PrometheusMetricsBuilder};
// 创建带有自定义标签的 Prometheus 指标
let prometheus = PrometheusMetricsBuilder::new("api")
.endpoint("/metrics")
.const_label("version", env!("CARGO_PKG_VERSION"))
.const_label("service", "user-service")
.build()
.unwrap();
为所有响应添加安全相关的 HTTP 头部,防止常见 Web 漏洞:
use actix_web::middleware::DefaultHeaders;
// 安全头部配置
let security_headers = DefaultHeaders::new()
.add(("X-Content-Type-Options", "nosniff"))
.add(("X-Frame-Options", "DENY"))
.add(("X-XSS-Protection", "1; mode=block"))
.add(("Strict-Transport-Security", "max-age=31536000; includeSubDomains"))
.add(("Content-Security-Policy", "default-src 'self'"));
App::new()
.wrap(security_headers)
// ... 其他中间件
中间件的顺序影响性能和安全性,推荐顺序如下:
示例:
App::new()
.wrap(Logger::default())
.wrap(prometheus.clone())
.wrap(cors)
.wrap(security_headers)
.wrap(Compress::default())
.app_data(web::Data::new(app_state))
.configure(configure_routes)
使用 CatErrorServerModeTrait 提供统一的错误处理:
use catrs_actix_plugin::prelude::CatErrorServerModeTrait;
fn pkg_error_as_http_response(cat_err: CatError) -> HttpResponse {
if cat_err.is_bad_request_error() {
HttpResponse::BadRequest().body(cat_err.to_response_body())
} else if cat_err.is_internal_server_error() {
HttpResponse::InternalServerError().body(cat_err.to_response_body())
} else {
HttpResponse::Ok().body(cat_err.to_result_json())
}
}
示例:
#[get("/GetUserInfo")]
pub async fn get_user_info(
data: web::Data,
user_id: web::Path,
) -> impl Responder {
match data.user_service.get_user_by_id(*user_id).await {
Ok(user) => HttpResponse::Ok().json(user), // 直接返回业务对象
Err(e) => pkg_error_as_http_response(e),
}
}
use actix_web::{get, post, web, HttpResponse, Responder};
use catrs_actix_plugin::prelude::{ActixHeaders, CatErrorServerModeTrait};
use crate::server::ctx::AppState;
#[get("/ServerInfo")]
async fn get_server_info(data: web::Data, headers: ActixHeaders) -> impl Responder {
debug!("get ServerInfo");
match data.server_info(&headers.parent) {
Ok(result) => HttpResponse::Ok().body(result),
Err(e) => {
error!("get ServerInfo error: {}", e.to_debug_info());
pkg_error_as_http_response(e)
}
}
}
#[post("/FastSaveNewUsers")]
pub async fn fast_save_new_users(
data: web::Data,
headers: ActixHeaders,
input: web::Json,
) -> impl Responder {
debug!("FastSaveNewUsers");
match data
.fast_insert_new_users(&input.into_inner(), headers.parent)
.await
{
Ok(result) => HttpResponse::Ok().body(result),
Err(e) => {
error!("FastSaveNewUsers error: {}", e.to_debug_info());
pkg_error_as_http_response(e)
}
}
}
fn pkg_error_as_http_response(cat_err: CatError) -> HttpResponse {
if cat_err.is_bad_request_error() {
HttpResponse::BadRequest().body(cat_err.to_response_body())
} else if cat_err.is_internal_server_error() {
HttpResponse::InternalServerError().body(cat_err.to_response_body())
} else {
HttpResponse::Ok().body(cat_err.to_result_json())
}
}
- 1:成功
- 0:失败(通用)
- 负值:特定业务错误(见项目错误码文档)
示例:
use catherine::catherine_min::error::CatError;
// 创建业务错误
let error = CatError::new_with("用户不存在", -1001);
// 创建 HTTP 错误(通过特质)
let bad_request = CatError::new_bad_request_error("请求参数无效", -1002);
let internal_error = CatError::new_internal_error("数据库连接失败", -1003);
使用 CatResult 作为 Result 的别名:
use catherine::catherine_min::error::CatResult;
pub async fn get_user_by_id(user_id: i64) -> CatResult {
if user_id <= 0 {
return Err(CatError::new_with("用户ID无效", -1001));
}
let user = user_repository.find_by_id(user_id).await?;
if user.is_deleted() {
return Err(CatError::new_with("用户已删除", -1002));
}
Ok(user)
}
示例:
use redis::RedisError;
impl From for CatError {
fn from(err: RedisError) -> Self {
CatError::new_internal_error(format!("Redis操作失败: {}", err), -2001)
}
}
为错误添加上下文信息,便于追踪问题根源:
```rust
use catherine::catherine_min::error::CatError;
pub async fn processuser(userid: i64) -> CatResult<()> {
let user = userrepository.findbyid(userid).await
.maperr(|e| e.withcontext(format!("查找用户失败,用户ID: {}", user_id)))?;
// 处理用户
processuserdata(&user).await
.errmap(|e| e.withcontext(format!("处理用户数据失败,用户: {:?}", user)))?;
Ok(())
}
```
```rust
impl From
fn from(err: sqlx::Error) -> Self {
match err {
sqlx::Error::RowNotFound =>
CatError::new_with("数据库记录不存在", -4001),
=> CatError::newinternal_error(
format!("数据库操作失败: {}", err), -4002
).with_source(err),
}
}
}
```
```rust
pub async fn handler(
data: web::Data
request: HttpRequest,
input: web::Json,
) -> impl Responder {
let request_id = request.headers()
.get("x-request-id")
.andthen(|v| v.tostr().ok())
.unwrap_or("unknown");
let result = data.service.process(&input).await
.maperr(|e| e.withcontext(format!("请求ID: {}", request_id)));
match result {
Ok(res) => HttpResponse::Ok().json(res),
Err(e) => {
error!("请求处理失败: {},上下文: {:?}", e, e.context());
pkgerrorashttpresponse(e)
}
}
}
```
- 可恢复错误:网络超时、数据库连接失败,应重试
- 业务错误:参数无效、权限不足,直接返回客户端
- 系统错误:内存不足、配置错误,触发告警
示例:
use log::{debug, error, info, warn};
#[macro_use]
extern crate log;
// 在函数中使用
pub async fn process_order(order_id: i64) -> CatResult<()> {
info!("开始处理订单: {}", order_id);
let order = order_repository.find_by_id(order_id).await?;
debug!("订单详情: {:?}", order);
if order.status == OrderStatus::Cancelled {
warn!("订单已取消: {}", order_id);
return Err(CatError::new_with("订单已取消", -3001));
}
// 业务逻辑
info!("订单处理完成: {}", order_id);
Ok(())
}
使用 catrs-actix-plugin 提供的日志初始化:
use catrs_actix_plugin::prelude::init_std_cat_rs_server_logger;
// 初始化日志
let logs_dir = "/path/to/logs";
match init_std_cat_rs_server_logger(logs_dir, "my-app", true) {
Ok(_) => info!("日志初始化成功"),
Err(e) => eprintln!("日志初始化失败: {}", e),
}
使用结构化日志(JSON 格式)便于日志收集和分析:
- timestamp:日志时间戳(ISO 8601)
- level:日志级别(INFO、ERROR等)
- message:日志消息
- service:服务名称
- version:服务版本
- request_id:请求ID(跨服务追踪)
- user_id:用户ID(如适用)
- endpoint:请求端点
- duration_ms:请求处理时长
- error_code:错误码(如适用)
- stack_trace:堆栈跟踪(错误时)
```rust
use tracing::{info, error, instrument};
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
// 初始化 tracing 订阅者
tracing_subscriber::registry()
.with(fmt::layer().json()) // JSON 格式输出
.with(EnvFilter::fromdefaultenv())
.init();
#[instrument(skipall, fields(requestid, user_id))]
pub async fn process_request(
request_id: String,
user_id: Option
input: Input,
) -> Result<(), CatError> {
// 记录结构化日志
info!(
requestid = %requestid,
userid = userid,
endpoint = "process_request",
message = "开始处理请求"
);
// 业务逻辑
match process(input).await {
Ok(_) => {
info!(
requestid = %requestid,
message = "请求处理成功"
);
Ok(())
}
Err(e) => {
error!(
requestid = %requestid,
error_code = e.code(),
error_message = %e.message(),
stack_trace = ?e.backtrace(),
message = "请求处理失败"
);
Err(e)
}
}
}
```
- 生产环境:INFO 级别,采样率 100%
- 调试环境:DEBUG 级别,采样率 100%
- 高负载时:动态降低日志级别或采样率
- 使用异步日志记录避免阻塞业务线程
- 使用 ELK(Elasticsearch, Logstash, Kibana)或 Loki 收集日志
- 设置错误日志告警(如 ERROR 日志频率超过阈值)
- 日志保留策略:生产环境 30 天,开发环境 7 天
use catherine::catherine_min::error::{CatError, CatResult};
use log::{error, info};
pub struct UserService {
user_repository: UserRepository,
}
impl UserService {
pub async fn get_user_by_id(&self, user_id: i64) -> CatResult {
info!("查询用户: {}", user_id);
if user_id <= 0 {
return Err(CatError::new_bad_request_error("用户ID必须大于0", -1001));
}
let user = self.user_repository.find_by_id(user_id).await?;
if user.is_deleted() {
warn!("用户已删除: {}", user_id);
return Err(CatError::new_with("用户已删除", -1002));
}
let user_dto = UserDto::from_entity(user);
info!("用户查询成功: {}", user_id);
Ok(user_dto)
}
}
使用 catrs-actix-plugin 提供的数据库工具:
use catrs_actix_plugin::server_tools::{MysqlPoolBuilder, RedisManager};
// MySQL 连接池
let mysql_pool = MysqlPoolBuilder::new(
"mysql://localhost:3306/mydb",
"username",
"password",
);
// Redis 连接
let redis_manager = RedisManager::new(CatSbRedisConf {
host: "localhost".to_string(),
port: 6379,
password: "".to_string(),
});
// 在 AppState 中共享
#[derive(Clone)]
pub struct AppState {
pub mysql_pool: MysqlPoolBuilder,
pub redis_manager: RedisManager,
}
示例:
use mysql::prelude::*;
pub async fn create_user(&self, user: User) -> CatResult {
let mut conn = self.mysql_pool.get_conn().await?;
let tx = conn.start_transaction(TxOpts::default())?;
let user_id = tx.exec_drop(
"INSERT INTO users (name, email) VALUES (?, ?)",
(&user.name, &user.email),
)?;
tx.commit()?;
Ok(user_id as i64)
}
生产环境数据库连接池需要精细配置以平衡性能和资源使用:
```rust
use catrsactixplugin::server_tools::MysqlPoolBuilder;
use std::time::Duration;
let mysql_pool = MysqlPoolBuilder::new(
"mysql://localhost:3306/mydb",
"username",
"password",
)
.max_connections(20) // 最大连接数(根据数据库配置调整)
.min_connections(5) // 最小保持连接数
.connecttimeout(Duration::fromsecs(10)) // 连接超时
.idletimeout(Duration::fromsecs(300)) // 空闲连接超时
.maxlifetime(Duration::fromsecs(1800)) // 连接最大生命周期
.testoncheck_out(true); // 取出连接时测试可用性
```
- 最大连接数 = (核心数 * 2) + 磁盘数量(经验公式)
- 考虑数据库服务器配置(max_connections)
- 考虑应用并发量:每个请求可能需要多个数据库连接
- 监控指标:连接等待时间、使用率
- 事务隔离级别:根据业务需求选择
```rust
use mysql::TxOpts;
let tx_opts = TxOpts::default()
.setisolationlevel(Some(IsolationLevel::RepeatableRead));
let tx = conn.starttransaction(txopts)?;
```
- 事务边界:事务应尽可能短,避免长期持有锁
- 读写分离:读操作使用只读连接,写操作使用读写连接
```rust
use backoff::{ExponentialBackoff, retry};
use std::time::Duration;
pub async fn with_retry
where
F: Fn() -> Result
E: std::fmt::Display,
{
let backoff = ExponentialBackoff {
initialinterval: Duration::frommillis(100),
maxinterval: Duration::fromsecs(1),
maxelapsedtime: Some(Duration::from_secs(5)),
multiplier: 2.0,
..ExponentialBackoff::default()
};
retry(backoff, operation).await
}
// 使用示例
pub async fn getuserwithretry(userid: i64) -> CatResult
with_retry(|| async {
userrepository.findbyid(userid).await
.map_err(backoff::Error::transient) // 临时错误重试
}).await
}
```
- 连接池使用率(活跃连接/总连接)
- 查询延迟(P50、P95、P99)
- 错误率(连接错误、查询错误)
- 事务提交/回滚比例
- 主从复制:读写分离配置
- 分库分表:根据业务维度拆分
- 数据库迁移:零停机迁移策略
示例:
pub async fn process_batch(&self, items: Vec- ) -> CatResult<()> {
let mut futures = Vec::new();
for item in items {
futures.push(self.process_single(item));
}
// 并发处理
let results = futures::future::join_all(futures).await;
// 检查结果
for result in results {
result?;
}
Ok(())
}
async fn process_single(&self, item: Item) -> CatResult<()> {
// 异步操作
let processed = self.process_item(item).await?;
self.save_result(processed).await?;
Ok(())
}
use catrs_actix_plugin::server_tools::MysqlPoolBuilder;
use catherine::catherine_min::error::CatResult;
use mysql::{params, prelude::*};
pub struct UserRepository {
pool: MysqlPoolBuilder,
}
impl UserRepository {
pub fn new(pool: MysqlPoolBuilder) -> Self {
Self { pool }
}
pub async fn find_by_id(&self, user_id: i64) -> CatResult {
let mut conn = self.pool.get_conn().await?;
let users: Vec = conn.exec_map(
"SELECT id, name, email, created_at FROM users WHERE id = :id",
params! { "id" => user_id },
|(id, name, email, created_at)| User {
id,
name,
email,
created_at,
},
)?;
users.into_iter().next()
.ok_or_else(|| CatError::new_with(format!("用户不存在: {}", user_id), -1001))
}
pub async fn save(&self, user: &User) -> CatResult<()> {
let mut conn = self.pool.get_conn().await?;
conn.exec_drop(
"INSERT INTO users (name, email) VALUES (:name, :email)",
params! { "name" => &user.name, "email" => &user.email },
)?;
Ok(())
}
}
必须使用 StdDataResponse 作为所有接口的统一响应包装器,确保响应格式一致,便于前端处理和多语言协作。
规则格式:
[RESP-001] MUST 接口必须返回StdDataResponse包装器
适用范围: 所有HTTP接口的返回值
示例:
正例: StdDataResponse::success(data)
反例: 直接返回Result或裸数据
理由: 统一响应格式,便于前端统一处理,支持错误码和描述信息传递
成功响应必须使用 StdDataResponse::success(data) 方法创建,其中 data 为业务数据。
// 正例:标准成功响应
pub async fn get_user_info(user_id: i64) -> StdDataResponse {
let user = user_service::get_user_by_id(user_id).await?;
StdDataResponse::success(user)
}
// 反例:直接返回数据或Result
pub async fn get_user_info_wrong(user_id: i64) -> Result {
let user = user_service::get_user_by_id(user_id).await?;
Ok(user) // 错误:缺少统一包装
}
错误响应必须使用 StdDataResponse::error(code, "desc") 方法创建,其中 code 为业务错误码(负数),desc 为可读描述信息。
// 正例:标准错误响应
pub async fn create_user(user_dto: UserCreateDto) -> StdDataResponse<()> {
if user_dto.name.is_empty() {
return StdDataResponse::error(-1001, "用户名不能为空");
}
user_service::create_user(user_dto).await?;
StdDataResponse::success(())
}
// 反例:直接返回CatError
pub async fn create_user_wrong(user_dto: UserCreateDto) -> Result<(), CatError> {
if user_dto.name.is_empty() {
return Err(CatError::new_with("用户名不能为空", -1001)); // 错误:缺少统一包装
}
user_service::create_user(user_dto).await?;
Ok(())
}
在Controller中,应将 CatError 转换为 StdDataResponse,推荐使用 pkgerrorashttpresponse 辅助函数或模式匹配:
// 方式1:使用?运算符和map_err转换
pub async fn get_user_by_id(user_id: i64) -> StdDataResponse {
let user = user_service::get_user_by_id(user_id)
.await
.map_err(|e| StdDataResponse::error(e.code, &e.message))?;
StdDataResponse::success(user)
}
// 方式2:使用pkg_error_as_http_response(适用于actix-web handler)
#[get("/GetUserInfo/{user_id}")]
pub async fn get_user_info_handler(user_id: web::Path) -> impl Responder {
let result = user_service::get_user_by_id(user_id.into_inner()).await;
pkg_error_as_http_response(result.map(|user| StdDataResponse::success(user)))
}
// 方式3:模式匹配
pub async fn update_user(user_dto: UserUpdateDto) -> StdDataResponse<()> {
match user_service::update_user(user_dto).await {
Ok(_) => StdDataResponse::success(()),
Err(e) => StdDataResponse::error(e.code, &e.message),
}
}
必须将数据传输对象(DTO)定义在独立的文件中,严禁在Controller或Service中直接定义DTO结构体。
规则格式:
[DTO-001] MUST DTO必须单独定义在独立文件
适用范围: 所有数据传输对象
示例:
正例: src/dto/user_dto.rs中定义UserCreateDto
反例: 在controller函数参数中直接使用匿名结构体
理由: 提高代码可维护性,便于类型复用和文档生成
DTO文件应按照业务模块组织在 src/dto/ 目录下,每个DTO一个文件:
src/
dto/
user_dto.rs # 用户相关DTO
product_dto.rs # 产品相关DTO
order_dto.rs # 订单相关DTO
mod.rs # 模块导出
mod.rs 应导出所有DTO类型,便于统一导入:
// src/dto/mod.rs
pub mod user_dto;
pub mod product_dto;
pub mod order_dto;
pub use user_dto::*;
pub use product_dto::*;
pub use order_dto::*;
必须将所有DTO结构体和其字段标记为 pub,确保类型可在模块外访问。
// 正例:所有字段和结构体均为pub
// 文件:src/dto/user_dto.rs
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct UserCreateDto {
pub name: String,
pub email: String,
pub birth_date: Option,
}
// 反例:字段非pub,导致无法在外部访问
pub struct UserDto {
name: String, // 错误:字段非pub
email: String, // 错误:字段非pub
}
在Controller或Service中使用DTO时,应通过 dto 模块导入:
// 正例:通过模块导入DTO
use crate::dto::{UserCreateDto, UserUpdateDto};
#[post("/CreateUser")]
pub async fn create_user_handler(user_dto: web::Json) -> impl Responder {
// 使用DTO
}
// 反例:直接使用相对路径或重复定义
use super::dto::user_dto::UserCreateDto; // 不推荐:路径复杂
必须为所有DTO类使用以下三个修饰符组合,严禁缺少任意一个修饰符:
规则格式:
[DTO-002] MUST DTO必须使用固定修饰符组合
适用范围: 所有DTO类定义
示例:
正例: #[skip_serializing_none] + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] + #[serde(rename_all = "camelCase")]
反例: 缺少任意一个修饰符
理由: 确保序列化一致性,支持None值跳过,符合多语言协作的命名约定
修饰符必须按以下顺序出现,确保宏扩展正确:
// 正例:正确顺序
#[skip_serializing_none] // 第一:属性宏
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] // 第二:派生宏
#[serde(rename_all = "camelCase")] // 第三:serde属性
pub struct UserCreateDto {
pub name: String,
pub email: String,
pub birth_date: Option,
}
// 反例:错误顺序(可能导致编译错误或行为异常)
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[skip_serializing_none] // 错误:应在derive之前
#[serde(rename_all = "camelCase")]
pub struct UserDto {
pub name: String,
}
// 使用示例
use serde_with::skip_serializing_none;
#[skip_serializing_none]
#[derive(Serialize)]
pub struct UserDto {
pub name: String,
pub email: Option, // 当为None时,不会出现在JSON中
pub phone: Option, // 当为None时,不会出现在JSON中
}
// 序列化结果:{"name": "John", "phone": "123456"} (email字段被跳过)
#[serde(rename_all = "camelCase")]
pub struct UserDto {
pub user_name: String, // 序列化为 "userName"
pub birth_date: CatDateTime, // 序列化为 "birthDate"
pub is_active: bool, // 序列化为 "isActive"
}
// 示例:同时支持camelCase和snake_case
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CategoryConfig {
pub id: String,
pub name: String,
#[serde(alias = "is_required")] // 兼容旧数据中的snake_case字段
pub is_required: bool,
#[serde(alias = "prompt_label")] // 兼容旧数据中的snake_case字段
pub prompt_label: Option,
}
// 必须在DTO前添加警告注释
// ⚠️ 警告:v0.2.0版本将移除snake_case兼容
// 数据库清洗后,删除所有#[serde(alias)]属性
// 严禁在JSON中使用snake_case字段名
// 文件:src/dto/user_dto.rs
use serde_with::skip_serializing_none;
use serde::{Serialize, Deserialize};
use crate::catherine_min::dto::cat_date_time::CatDateTime;
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct UserCreateDto {
pub name: String,
pub email: String,
pub birth_date: Option, // 使用CatDateTime类型
pub phone: Option,
pub address: Option,
}
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct UserUpdateDto {
pub name: Option,
pub email: Option,
pub birth_date: Option,
}
// 错误1:缺少skip_serializing_none
#[derive(Debug, Serialize, Deserialize)] // 错误:缺少skip_serializing_none
pub struct UserDto {
pub name: String,
pub email: Option, // None值会被序列化为null
}
// 错误2:缺少rename_all
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize)] // 错误:缺少rename_all
pub struct ProductDto {
pub product_name: String, // 序列化为 "product_name" (snake_case)
pub unit_price: f64, // 序列化为 "unit_price"
}
// 错误3:缺少必要的derive特质
#[skip_serializing_none]
#[derive(Serialize, Deserialize)] // 错误:缺少Debug, PartialEq, Eq, Clone
#[serde(rename_all = "camelCase")]
pub struct OrderDto {
pub order_id: i64,
}
必须为所有枚举类使用以下修饰符组合:
每个枚举类必须实现以下两个标准方法:
use strum::{EnumIter, IntoEnumIterator};
// ⚠️ 警告:v0.2.0版本将移除枚举值的#[serde(alias)]兼容
// 数据库清洗后,删除所有#[serde(alias)]属性
// 严禁在JSON中使用小写字段名
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, EnumIter)]
pub enum TaskTitleStatus {
#[serde(alias = "initial")]
Initial,
#[serde(alias = "ai_generated")]
AiGenerated,
#[serde(alias = "user_edited")]
UserEdited,
}
impl TaskTitleStatus {
pub fn of>(value: T) -> Option {
let value = value.as_ref();
let value = value.trim().to_lowercase();
for en_val in Self::iter() {
let lower_name = format!("{:?}", en_val).to_lowercase();
let lower_name: &str = &lower_name;
if lower_name == value {
return Some(en_val.clone());
}
}
None
}
pub fn name(&self) -> String {
format!("{:?}", self)
}
}
必须优先使用 CatDateTime 类型表示时间字段,严禁使用 chrono::DateTime 或其他第三方时间库。
规则格式:
[TIME-001] MUST 时间字段必须优先使用CatDateTime
适用范围: 所有DTO、数据库模型、业务逻辑中的时间字段
示例:
正例: created_at: CatDateTime, updated_at: Option
反例: created_at: chrono::DateTime, timestamp: i64
理由: 保持时间处理一致性,避免第三方库依赖冲突,与现有代码库兼容
CatDateTime 是 i128 的类型别名,表示从 Unix 纪元(1970-01-01 00:00:00 UTC)开始的毫秒数:
// catherine-rust 中的定义
pub type CatDateTime = i128;
特点:
对于必须存在的时间字段(如创建时间),直接使用 CatDateTime:
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct UserDto {
pub id: i64,
pub name: String,
pub created_at: CatDateTime, // 必选:用户创建时间
pub updated_at: CatDateTime, // 必选:最后更新时间
}
对于可能不存在的时间字段(如删除时间、过期时间),使用 Option:
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct UserDto {
pub id: i64,
pub name: String,
pub deleted_at: Option, // 可选:删除时间(未删除时为None)
pub expired_at: Option, // 可选:过期时间(永不过期为None)
}
时间字段名应使用 snakecase,后缀为 at,表示时间点:
使用 catherinemin::dto::catdatetime::currentcatdatetime() 函数获取当前时间:
use crate::catherine_min::dto::cat_date_time::{CatDateTime, current_cat_date_time};
pub fn create_user(name: String) -> User {
let now = current_cat_date_time();
User {
id: 0,
name,
created_at: now,
updated_at: now,
deleted_at: None,
}
}
由于 CatDateTime 是 i128 别名,可以直接进行数值比较和计算:
pub fn is_expired(expired_at: Option) -> bool {
match expired_at {
Some(expiry) => {
let now = current_cat_date_time();
now > expiry // 直接比较数值
}
None => false, // 无过期时间表示永不过期
}
}
pub fn add_days(time: CatDateTime, days: i64) -> CatDateTime {
let milliseconds_per_day = 24 * 60 * 60 * 1000;
time + (days as i128 * milliseconds_per_day)
}
需要显示为可读字符串时,使用 catherinemin::dto::catdatetime::formatcatdatetime():
use crate::catherine_min::dto::cat_date_time::format_cat_date_time;
pub fn display_time(time: CatDateTime) -> String {
format_cat_date_time(time, "%Y-%m-%d %H:%M:%S")
}
在数据库模型(如 User 结构体)中,时间字段也应使用 CatDateTime:
// 数据库模型
pub struct User {
pub id: i64,
pub name: String,
pub created_at: CatDateTime,
pub updated_at: CatDateTime,
pub deleted_at: Option,
}
// 数据库查询时,需要将数据库中的时间戳转换为CatDateTime
impl From for CatDateTime {
fn from(value: mysql::Value) -> Self {
match value {
mysql::Value::Int(i) => i as i128,
mysql::Value::UInt(u) => u as i128,
mysql::Value::Bytes(bytes) => {
// 解析时间字符串
String::from_utf8_lossy(&bytes).parse().unwrap_or(0)
}
_ => 0,
}
}
}
// 错误1:使用chrono::DateTime
use chrono::{DateTime, Utc};
pub struct User {
pub created_at: DateTime, // 错误:应使用CatDateTime
}
// 错误2:使用i64或u64时间戳
pub struct Order {
pub created_at: i64, // 错误:应使用CatDateTime(i128)
}
// 错误3:可选时间字段不使用Option
pub struct Product {
pub deleted_at: CatDateTime, // 错误:应使用Option,0值无法表示"未删除"
}
// 错误4:使用字符串表示时间
pub struct Log {
pub timestamp: String, // 错误:应使用CatDateTime,字符串不利于比较和计算
}
现有代码中使用其他时间类型的,应按以下步骤迁移: