Rust (Actix-web) 编码规范 V2.0

1. 引言

1.1 文档目的

本编码规范是《Rust (Actix-web) 开发规范》的补充文档,专门针对编码细节和实现规则。通过遵循本规范,团队可以:

1.2 适用范围

1.3 核心原则

  1. **一致性**:同一概念在全代码库中保持相同的命名和实现方式
  2. **简洁性**:避免过度设计,代码应直白表达意图
  3. **协作友好**:兼顾人类阅读习惯和 AI 代码生成/解析需求
  4. **演进兼容**:规范可随技术栈和团队实践逐步调整,但变更需经过评审

1.4 AI友好性指南

为优化AI辅助开发工具的理解和遵守,本规范采用以下结构化表述:

  1. **关键词强度标识**(基于RFC 2119):

- 必须(MUST):绝对要求,无例外情况

- 严禁(MUST NOT):绝对禁止,无例外情况

- 应该(SHOULD):强烈建议,除非有充分理由

- 不应该(SHOULD NOT):强烈不建议,除非有充分理由

- 可以(MAY):可选,根据具体情况决定

  1. **结构化规则格式**:

```

[规则ID] [强度] [规则描述]

适用范围: [模块/类型]

示例:

正例: [代码示例]

反例: [代码示例]

理由: [解释原因]

```

  1. **规则分类体系**:

- NAM: 命名规范(Naming)

- STR: 代码结构(Structure)

- ACT: Actix-web特定规范(Actix)

- ERR: 错误处理(Error Handling)

- DB: 数据库(Database)

- TEST: 测试(Testing)

- DTO: 数据传输对象(Data Transfer Object)

- RESP: 接口响应格式(Response Format)

  1. **AI提示词优化**:

- 使用明确、无歧义的自然语言描述

- 提供正面和反面示例对比

- 解释规则背后的原理和意图

- 使用一致的术语和分类体系

使用建议

2. 命名规范

2.1 通用规则

2.2 多语言团队协作约束

严禁使用其他编程语言的关键字或常用标识符作为 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 映射错误。替代:添加业务前缀,如 itemdescriptionquerylimitstart_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 关键字或常用类名,影响跨语言代码和生成一致性。替代:使用语义明确的名称,如 userclassispublicstatic_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 关键字或内置函数,易导致代码混淆。替代:添加后缀,如 asynctaskprintmessagedict_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 内置函数或常用类名,可能影响跨语言代码生成。替代:使用业务相关名称,如 phparrayecho_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 关键字或常用类型名,影响跨平台代码一致性。替代:添加前缀,如 dartvarfinalvalue |

| 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 关键字或全局对象,易导致前端代码混淆。替代:使用明确名称,如 jsfunctionconsoleoutput |

| 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 关键字或标准库类型名,易导致编译错误或混淆。替代:使用业务相关名称,避免直接使用语言内置名称 |

使用指导

  1. **严格禁止**:任何情况下都不应使用这些标识符,除非在对应语言的代码上下文中(如 SQL 查询字符串)。
  2. **建议避免**:在非必要情况下避免使用,特别是当代码可能被其他语言工具处理时。
  3. **替代策略**:添加业务前缀/后缀(如 `user_`、`item_`、`data_`)、使用完整单词(如 `description` 替代 `desc`)、或使用同义词(如 `maximum` 替代 `max`)。

正例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

2.3 desc 术语使用规范

目的:明确 "desc" 术语在代码中的使用范围,避免与 SQL 关键字冲突,同时保持团队已有代码库的一致性。

规范

  1. **允许使用场景**:

- 与数据库字段和数据库表直接相关的方法名、字段名

- 示例:getuserdesc()productdescriptionorderdesc

  1. **不鼓励使用场景**:

- 通用业务逻辑中的变量名、函数名(除非与数据库直接相关)

- 示例:避免使用 desc 作为临时变量名、通用参数名

  1. **固定用法(特殊情况)**:

- 跨语言传输的 DTO 中,若已有 Java 端定义的 StdDataResponse 包含 desc 字段,Rust 端应保持对应字段名一致

- 在接口返回中,"desc" 字段用于携带可读的业务描述信息,支持多语言

注意事项

2.4 示例

2.4.1 通用命名示例


// 模块声明
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;
    
    // 业务逻辑
    // ...
}

2.4.2 多语言友好命名示例


// 反例:使用其他语言关键字
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";          // 正确

3. 代码结构组织

3.1 模块划分原则

推荐按功能模块划分,典型结构如下:


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

3.2 模块导出规范

示例 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;

3.3 文件组织约定

3.4 示例:模块导出与使用


// 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 {
        // ...
    }
}

4. Actix-web 特定最佳实践

4.1 路由定义规范

4.1.1 路由宏使用

路由命名规范细节

  1. **PascalCase 路径命名**:

- 示例:/GetUserInfo/CreateOrder/UpdateProduct

- 原因:与 Java 微服务(Spring Cloud)保持命名一致,便于:

- 跨语言 API 文档生成

- 统一网关路由配置

- 多语言客户端 SDK 生成

- 避免使用 snakecase(/getuser_info)或 kebab-case(/get-user-info

  1. **版本管理**:

- 在路径前缀中管理 API 版本,如 /api/v1/GetUserInfo

- 或使用 Scope 进行版本分组:

```rust

web::scope("/api/v1")

.service(getuserinfo)

.service(create_order)

```

  1. **路由注册集中化**:

- 使用 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);

}

```

  1. **路径参数规范**:

- 使用 {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);
}

4.1.2 路由参数处理

示例:


#[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 {
    // ...
}

4.2 状态共享与依赖注入

4.2.1 AppState 设计

示例:


#[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)

4.2.2 控制器与服务注入

示例:


#[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)
        }
    }
}

4.3 中间件使用规范

4.3.1 内置中间件

示例:


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)

4.3.2 自定义中间件

4.5 健康检查与监控

4.5.1 健康检查端点

所有服务必须提供标准健康检查端点,用于容器编排和负载均衡器健康检查:

示例实现:


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"}))
}

4.5.2 监控指标

除了 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();

4.5.3 安全头部中间件

为所有响应添加安全相关的 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)
    // ... 其他中间件

4.5.4 中间件顺序最佳实践

中间件的顺序影响性能和安全性,推荐顺序如下:

  1. **日志中间件**(Logger):最先记录原始请求
  2. **Prometheus 指标**:记录指标数据
  3. **CORS 中间件**:处理跨域请求
  4. **安全头部中间件**:添加安全头部
  5. **压缩中间件**(Compress):最后压缩响应

示例:


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)

4.4 响应封装规范

4.4.1 统一错误响应

使用 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())
    }
}

4.4.2 成功响应格式

示例:


#[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),
    }
}

4.5 示例:完整路由处理


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())
    }
}

5. 错误处理与日志

5.1 错误处理体系

5.1.1 CatError 使用规范

- 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);

5.1.2 CatResult 别名

使用 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)
}

5.1.3 错误转换

示例:


use redis::RedisError;

impl From for CatError {
    fn from(err: RedisError) -> Self {
        CatError::new_internal_error(format!("Redis操作失败: {}", err), -2001)
    }
}

5.1.4 错误上下文与链式错误

为错误添加上下文信息,便于追踪问题根源:

  1. **错误上下文**:使用 `CatError` 的上下文方法记录额外信息:

```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(())

}

```

  1. **链式错误**:保留原始错误信息,形成错误链:

```rust

impl From for CatError {

fn from(err: sqlx::Error) -> Self {

match err {

sqlx::Error::RowNotFound =>

CatError::new_with("数据库记录不存在", -4001),

=> CatError::newinternal_error(

format!("数据库操作失败: {}", err), -4002

).with_source(err),

}

}

}

```

  1. **请求上下文传递**:在错误中记录请求ID、用户ID等上下文:

```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)

}

}

}

```

  1. **错误分类与处理策略**:

- 可恢复错误:网络超时、数据库连接失败,应重试

- 业务错误:参数无效、权限不足,直接返回客户端

- 系统错误:内存不足、配置错误,触发告警

5.2 日志记录规范

5.2.1 Logger 创建

示例:


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(())
}

5.2.2 日志级别

5.2.3 日志初始化

使用 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),
}

5.2.4 结构化日志记录

使用结构化日志(JSON 格式)便于日志收集和分析:

  1. **结构化日志字段**:

- timestamp:日志时间戳(ISO 8601)

- level:日志级别(INFO、ERROR等)

- message:日志消息

- service:服务名称

- version:服务版本

- request_id:请求ID(跨服务追踪)

- user_id:用户ID(如适用)

- endpoint:请求端点

- duration_ms:请求处理时长

- error_code:错误码(如适用)

- stack_trace:堆栈跟踪(错误时)

  1. **实现方式**(使用 `tracing` 或 `slog`):

```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)

}

}

}

```

  1. **日志采样与性能**:

- 生产环境:INFO 级别,采样率 100%

- 调试环境:DEBUG 级别,采样率 100%

- 高负载时:动态降低日志级别或采样率

- 使用异步日志记录避免阻塞业务线程

  1. **日志收集与告警**:

- 使用 ELK(Elasticsearch, Logstash, Kibana)或 Loki 收集日志

- 设置错误日志告警(如 ERROR 日志频率超过阈值)

- 日志保留策略:生产环境 30 天,开发环境 7 天

5.3 示例:完整的错误处理


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)
    }
}

6. 数据库与异步处理

6.1 数据库连接管理

6.1.1 连接池使用

使用 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,
}

6.1.2 数据库操作规范

示例:


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)
}

6.1.3 连接池配置优化

生产环境数据库连接池需要精细配置以平衡性能和资源使用:

  1. **连接池参数配置**:

```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); // 取出连接时测试可用性

```

  1. **连接池大小计算原则**:

- 最大连接数 = (核心数 * 2) + 磁盘数量(经验公式)

- 考虑数据库服务器配置(max_connections

- 考虑应用并发量:每个请求可能需要多个数据库连接

- 监控指标:连接等待时间、使用率

  1. **事务管理最佳实践**:

- 事务隔离级别:根据业务需求选择

```rust

use mysql::TxOpts;

let tx_opts = TxOpts::default()

.setisolationlevel(Some(IsolationLevel::RepeatableRead));

let tx = conn.starttransaction(txopts)?;

```

- 事务边界:事务应尽可能短,避免长期持有锁

- 读写分离:读操作使用只读连接,写操作使用读写连接

  1. **重试机制与熔断**:

```rust

use backoff::{ExponentialBackoff, retry};

use std::time::Duration;

pub async fn with_retry(operation: F) -> Result

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

}

```

  1. **数据库监控指标**:

- 连接池使用率(活跃连接/总连接)

- 查询延迟(P50、P95、P99)

- 错误率(连接错误、查询错误)

- 事务提交/回滚比例

  1. **多数据库支持**:

- 主从复制:读写分离配置

- 分库分表:根据业务维度拆分

- 数据库迁移:零停机迁移策略

6.2 异步编程最佳实践

6.2.1 async/await 使用

示例:


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(())
}

6.2.2 性能优化

6.3 示例:数据库服务实现


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(())
    }
}

7. 接口返回格式规范

7.1 统一响应包装器

必须使用 StdDataResponse 作为所有接口的统一响应包装器,确保响应格式一致,便于前端处理和多语言协作。

规则格式


[RESP-001] MUST 接口必须返回StdDataResponse包装器
适用范围: 所有HTTP接口的返回值
示例:
  正例: StdDataResponse::success(data)
  反例: 直接返回Result或裸数据
理由: 统一响应格式,便于前端统一处理,支持错误码和描述信息传递

7.2 成功响应格式

成功响应必须使用 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)  // 错误:缺少统一包装
}

7.3 错误响应格式

错误响应必须使用 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(())
}

7.4 组合使用模式

在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),
    }
}

7.5 注意事项

  1. **字段一致性**:`StdDataResponse` 的 `desc` 字段用于携带可读的业务描述信息,支持多语言
  2. **错误码范围**:业务错误码使用负数范围(如 -1001, -1002),系统错误码使用正数范围
  3. **空数据响应**:对于无返回数据的接口,使用 `StdDataResponse<()>` 或 `StdDataResponse`
  4. **序列化兼容**:确保 `StdDataResponse` 的序列化格式与 Java 等其他语言端保持一致

8. DTO隔离与定义规范

8.1 DTO隔离原则

必须将数据传输对象(DTO)定义在独立的文件中,严禁在Controller或Service中直接定义DTO结构体。

规则格式


[DTO-001] MUST DTO必须单独定义在独立文件
适用范围: 所有数据传输对象
示例:
  正例: src/dto/user_dto.rs中定义UserCreateDto
  反例: 在controller函数参数中直接使用匿名结构体
理由: 提高代码可维护性,便于类型复用和文档生成

8.2 文件组织规范

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::*;

8.3 可见性规范

必须将所有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
}

8.4 导入使用规范

在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;  // 不推荐:路径复杂

8.5 命名规范

  1. **DTO类型名**:使用 `PascalCase`,后缀为 `Dto`,如 `UserCreateDto`、`ProductUpdateDto`
  2. **文件名**:使用 `snake_case`,后缀为 `_dto.rs`,如 `user_dto.rs`、`product_dto.rs`
  3. **字段名**:使用 `snake_case`,序列化时转换为 `camelCase`(通过 `#[serde(rename_all = "camelCase")]`)

9. DTO类固定修饰符规范

9.1 修饰符组合要求

必须为所有DTO类使用以下三个修饰符组合,严禁缺少任意一个修饰符:

  1. `#[skip_serializing_none]` - 跳过`None`值的序列化
  2. `#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]` - 标准特质派生
  3. `#[serde(rename_all = "camelCase")]` - 字段名序列化为camelCase

规则格式


[DTO-002] MUST DTO必须使用固定修饰符组合
适用范围: 所有DTO类定义
示例:
  正例: #[skip_serializing_none] + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] + #[serde(rename_all = "camelCase")]
  反例: 缺少任意一个修饰符
理由: 确保序列化一致性,支持None值跳过,符合多语言协作的命名约定

9.2 修饰符顺序要求

修饰符必须按以下顺序出现,确保宏扩展正确:


// 正例:正确顺序
#[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,
}

9.3 各修饰符作用说明

9.3.1 `#[skip_serializing_none]`


// 使用示例
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字段被跳过)

9.3.2 `#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]`

9.3.3 `#[serde(rename_all = "camelCase")]`


#[serde(rename_all = "camelCase")]
pub struct UserDto {
    pub user_name: String,      // 序列化为 "userName"
    pub birth_date: CatDateTime, // 序列化为 "birthDate"
    pub is_active: bool,        // 序列化为 "isActive"
}

9.3.4 `#[serde(alias)]`兼容性处理(临时)


// 示例:同时支持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字段名

9.4 完整示例


// 文件: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,
}

9.5 常见错误示例


// 错误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,
}

9.6 枚举类规范

9.6.1 枚举定义规范

必须为所有枚举类使用以下修饰符组合:

  1. `#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, EnumIter)]` - 标准特质派生
  2. 枚举变体使用PascalCase命名
  3. **严禁**使用 `#[serde(rename_all = "lowercase")]` 或 `#[serde(rename_all = "snake_case")]`

9.6.2 标准方法要求

每个枚举类必须实现以下两个标准方法:

  1. `of>(value: T) -> Option` - 从字符串解析枚举值
  2. `name(&self) -> String` - 获取枚举变体名称

9.6.3 序列化格式

9.6.4 示例代码


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)
    }
}

10. 时间字段使用规范

10.1 时间类型选择

必须优先使用 CatDateTime 类型表示时间字段,严禁使用 chrono::DateTime 或其他第三方时间库。

规则格式


[TIME-001] MUST 时间字段必须优先使用CatDateTime
适用范围: 所有DTO、数据库模型、业务逻辑中的时间字段
示例:
  正例: created_at: CatDateTime, updated_at: Option
  反例: created_at: chrono::DateTime, timestamp: i64
理由: 保持时间处理一致性,避免第三方库依赖冲突,与现有代码库兼容

10.2 CatDateTime 类型说明

CatDateTimei128 的类型别名,表示从 Unix 纪元(1970-01-01 00:00:00 UTC)开始的毫秒数:


// catherine-rust 中的定义
pub type CatDateTime = i128;

特点

10.3 必选与可选时间字段

10.3.1 必选时间字段

对于必须存在的时间字段(如创建时间),直接使用 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,      // 必选:最后更新时间
}

10.3.2 可选时间字段

对于可能不存在的时间字段(如删除时间、过期时间),使用 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)
}

10.4 时间字段命名规范

时间字段名应使用 snakecase,后缀为 at,表示时间点:

10.5 时间操作与转换

10.5.1 获取当前时间

使用 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,
    }
}

10.5.2 时间比较与计算

由于 CatDateTimei128 别名,可以直接进行数值比较和计算:


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)
}

10.5.3 时间格式转换

需要显示为可读字符串时,使用 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")
}

10.6 数据库映射

在数据库模型(如 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,
        }
    }
}

10.7 常见错误示例


// 错误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,字符串不利于比较和计算
}

10.8 迁移指南

现有代码中使用其他时间类型的,应按以下步骤迁移:

  1. **导入类型**:`use crate::catherine_min::dto::cat_date_time::CatDateTime;`
  2. **替换类型**:将所有 `chrono::DateTime`、`i64`、`String` 时间字段替换为 `CatDateTime` 或 `Option`
  3. **更新转换代码**:使用 `current_cat_date_time()` 获取当前时间,使用 `format_cat_date_time()` 格式化显示
  4. **更新数据库映射**:确保数据库查询结果能正确转换为 `CatDateTime`
返回