# PROJECT KNOWLEDGE BASE
Rust 插件系统框架,提供 `Addon → Module → Action` 三层运行时分发架构。
API 通过字符串名称 `{addon}.{module}.{action}` 动态路由(如 `dms.task.table`),非编译时绑定。
## COMMANDS
```bash
# 构建
cargo build # 默认全 feature
cargo build --features "mysql" # 指定单个 feature
cargo build --no-default-features --features "mysql,cache"
# 测试
cargo test # 全部测试
cargo test -- --nocapture # 带 stdout 输出
cargo test create_plugin # 运行单个测试(按函数名匹配)
cargo test test_empty -- --nocapture # 单个测试带输出
# 检查(提交前必须通过)
cargo fmt -- --check
cargo clippy --all-targets --all-features -- -D warnings
# 发布
cargo package --list && cargo publish --dry-run && cargo publish
```
**Features**: `mysql` `sqlite` `mssql` `pgsql` `cache` `email`(默认全启用)
## STRUCTURE
```
src/
├── lib.rs # Plugin trait, ApiResponse, ApiType, 全局状态
├── addon.rs # Addon trait + addon_create() 脚手架生成 + to_pascal_case()
├── module.rs # Module trait + DB helpers (db_find/db_select/db_insert/db_update/db_delete)
├── action.rs # Action trait + check() 参数验证 + Btn/BtnType/BtnColor/Dashboard UI组件
├── request.rs # Request struct + Method/ContentType 枚举
├── tools.rs # Tools 工具集(db/cache/email) + ToolsConfig
├── tables.rs # Tables 表格查询构建器(分页/排序/联表/树形) [feature-gated]
└── swagger.rs # Swagger OpenAPI 3.0 文档生成器(Swagger/Server/Api/RequestBody)
temp/ # 代码生成模板(action_table/action_add/action_del/action_get 等)
examples/addon/ # 完整 Addon→Module→Action 实现参考
tests/main.rs # 脚手架生成测试
```
## CODE STYLE
### 命名规范
| Struct/Enum/Trait | PascalCase | `ApiResponse`, `BtnType`, `DashboardModel` |
| 函数/方法 | snake_case | `module_name()`, `table_key()` |
| 常量/静态变量 | SCREAMING_SNAKE | `GLOBAL_DATA`, `PLUGIN_TOOLS` |
| 私有/内部方法 | 下划线前缀 | `_name()`, `_table_name()`, `_load_apis()` |
### 注释语言
本项目使用**中文注释**,保持一致性。用 `///` 写文档注释。
### Import 顺序
1. `crate::` 内部模块(按字母序)
2. 条件编译的外部 crate(`#[cfg(...)]` 的 `br_db`/`br_cache`/`br_email`)
3. 外部 crate(`json`, `lazy_static`, `log`, `serde`, `std`,按字母序)
```rust
use crate::action::Action;
use crate::addon::Addon;
use crate::module::Module;
#[cfg(any(feature = "mysql", feature = "sqlite", feature = "mssql", feature = "pgsql"))]
use br_db::Db;
use json::{array, object, JsonValue};
use std::collections::HashMap;
```
### Feature 条件编译(必须)
所有数据库相关代码必须用 feature gate 包裹:
```rust
#[cfg(any(feature = "mysql", feature = "sqlite", feature = "mssql", feature = "pgsql"))]
fn db_operation() { }
```
cache 用 `#[cfg(feature = "cache")]`,email 用 `#[cfg(feature = "email")]`。
### JSON 操作
使用 `json` crate(**非 serde_json**)。核心宏和类型:
```rust
use json::{object, array, JsonValue};
let obj = object! { "key" => "value", "num" => 42 }; // 创建对象
let arr = array!["a", "b"]; // 创建数组
obj["key"].as_str().unwrap_or(""); // 安全取值
obj.has_key("key"); // 检查 key
obj.is_empty() / obj.is_null(); // 空值判断
for (k, v) in obj.entries() { } // 遍历对象
for item in arr.members() { } // 遍历数组
```
### 错误处理
- Trait 方法返回 `Result<T, String>`,错误信息用中文
- API 层用 `ApiResponse::fail(code, "消息")` 或 `ApiResponse::error(data, "消息")`
- 错误码规范:`900_xxx` 系列为参数验证错误,`-1` 为通用错误
- 用 `.unwrap_or()` / `.unwrap_or_default()` 处理用户输入,**禁止裸 `.unwrap()`**
- `lock()` 可用 `.expect("描述")` 因为锁失败是不可恢复错误
```rust
// 正确
fn module(name: &str) -> Result<Box<dyn Module>, String> {
Err(format!("模型格式不正确: {name}"))
}
ApiResponse::fail(900_001, "参数错误")
// 错误
request[name].as_str().unwrap() // 禁止
```
### 类型名称推导
通过 `std::any::type_name::<Self>()` 解析模块路径自动推导名称:
- Action: `t[2].t[3].t[4]` → `addon.module.action`
- Module: `t[2].t[3]` → `addon.module`
- 表名: `{addon}_{module}` 自动推导
### `Box::leak` 模式
Module/Addon trait 中用 `Box::leak(s.into_boxed_str())` 返回 `&'static str`。
每个具体类型只泄漏一次,这是有意设计。新增类似方法时保持此模式并添加注释。
### Builder 模式
`Tables`、`Swagger`、`Btn`、`Dashboard` 均使用 builder 模式,方法返回 `&mut Self`:
```rust
self.table()
.main_table_fields(table_name, fields, hidd_field, show_field)
.search_fields(search_fields)
.params(request)
.get_table()
```
### 全局状态
- `PLUGIN_TOOLS`: `OnceLock<Tools>` 写入一次后只读,无锁访问
- `CONFIG`: `lazy_static! Mutex<HashMap>` 存储配置
- `GLOBAL_DATA`: `thread_local! RefCell<JsonValue>` 线程局部变量
- `GLOBAL_HANDLE`: `LazyLock<Mutex<HashMap>>` 监听线程注册
- `GLOBAL_ADDONS/MODULE/ACTION`: `OnceLock<Vec<String>>` 一次性初始化
### 标注规范
- 构造函数和返回新值的方法加 `#[must_use]`(如 `Swagger::new()`、`Btn::new()`)
- `tables.rs` 整个模块允许 `#[allow(clippy::too_many_arguments)]`
## ANTI-PATTERNS(禁止)
- ❌ 非 feature-gated 代码中直接使用 `self.tools().db`
- ❌ 添加新的全局 `lazy_static!` 变量(新增全局状态用 `LazyLock` 或 `OnceLock`)
- ❌ Action 中直接 `panic!`,应使用 `ApiResponse::fail()`
- ❌ 用 `.unwrap()` 处理用户输入
- ❌ 直接 `.leak()` 泄漏字符串,应使用 `Box::leak()` 并添加注释说明
- ❌ 在 `check()` 验证中遗漏 `require` 检查
- ❌ 使用 `serde_json`,本项目统一用 `json` crate
## GIT COMMIT
格式:`<类型> <描述>`(中文描述)
| A | 新增 | `A fields` |
| U | 更新/优化 | `U 优化addon` |
| F | 修复 | `F 修复参数验证` |
| D | 删除 | `D 删除废弃模板` |
| R | 重构 | `R 重构Tables构建器` |
## KEY PATTERNS
### 实现新插件
参考 `examples/addon/` 目录,需实现三层 trait:
1. `Addon` trait → `fn module(&mut self, name: &str) -> Result<Box<dyn Module>, String>`
2. `Module` trait → `fn action(&mut self, name: &str) -> Result<Box<dyn Action>, String>`
3. `Action` trait → `fn title()` + `fn index(&mut self, request: Request) -> ApiResponse`
### Trait Bounds
- `Addon: Send + Sync + 'static`
- `Module: Send + Sync + 'static`
- `Action` 无额外 bounds(但通过 `Box<dyn Action>` 传递)
实现 struct 通常用 `#[derive(Debug, Clone)]`,内部持有对应 Module struct 引用。
### Trait 必须实现 vs 可选方法
大部分 trait 方法有默认实现,只需覆盖必要的:
| `Addon` | `title()`, `module()` | `icon()`, `sort()`, `description()` | `name()` 自动推导 |
| `Module` | `title()`, `action()` | `fields()`, `table()`, `table_key()`, `table_unique()`, `table_index()` | `_name()`, `_table_name()` 自动推导 |
| `Action` | `title()`, `index()` | `params()`, `method()`, `tags()`, `auth()`, `public()`, `description()` | `api()`, `module_name()` 自动推导 |
### Action struct 惯用写法
每个 Action struct 持有对应 Module struct 引用,这是固定模式:
```rust
#[derive(Debug, Clone)]
pub struct DmsTaskTable {
pub module: DmsTask, // 持有 Module struct
}
impl Action for DmsTaskTable {
fn title(&self) -> &'static str { "表格" }
fn index(&mut self, request: Request) -> ApiResponse {
// 通过 self.module 访问 Module 层方法
// 通过 self.tools() 访问 DB/Cache/Email
}
}
```
Module struct 在 `mod.rs` 中定义,所有同模块 Action 共享同一个 Module struct 类型。
### Action 请求生命周期
`run()` 是外部入口,`index()` 是业务逻辑入口。流程:
```
run(request)
→ 检查 HTTP method 是否匹配 self.method()
→ 如果 params_check() == true:
→ check(&mut request.query, self.query()) // 验证地址参数
→ check(&mut request.body, self.params()) // 验证请求体参数
→ index(request) // 执行业务逻辑
→ 根据 ApiResponse.success 转为 Ok/Err 返回
```
`check()` 会自动移除 params 中未定义的字段,并对缺失字段填充 `field["def"]` 默认值。
### ApiResponse 双态返回
`index()` 返回 `ApiResponse`,`run()` 根据 `success` 字段拆分:
```rust
// index() 内部 — 直接返回 ApiResponse
fn index(&mut self, request: Request) -> ApiResponse {
ApiResponse::success(data, "获取完成") // success=true → run() 返回 Ok(...)
ApiResponse::fail(1000, "参数错误") // success=false → run() 返回 Err(...)
}
```
### br_fields 字段定义
`params()` 中用 `br_fields` 定义请求参数,`fields()` 中定义数据库字段:
```rust
fn params(&mut self) -> JsonValue {
let mut fields = object! {};
// Str::new(require, name, title, max_len, default)
fields["name"] = br_fields::str::Str::new(true, "name", "名称", 50, "").field();
// Int::new(require, name, title, max, default)
fields["age"] = br_fields::int::Int::new(false, "age", "年龄", 200, 0).field();
// Switch::new(require, name, title, default)
fields["active"] = br_fields::int::Switch::new(false, "active", "启用", true).field();
fields
}
```
`field()` 生成 check 验证用的 JSON,`swagger()` 生成 API 文档用的 JSON。
### Tools 访问与 DB 查询链
通过 `self.tools()` 获取 Tools 实例,DB 操作使用链式调用:
```rust
// Module 层快捷方法
self.db_find("id_value"); // 按 id 查单条
self.db_insert(data); // 插入
self.db_update("id_value", data); // 按 id 更新
self.db_delete("id_value"); // 按 id 删除
// Action 层完整链式查询
let mut binding = self.tools();
let db = binding.db.table("table_name");
db.where_and("field", "=", value.into());
db.where_and("status", "in", "a,b,c".into());
let result = db.find(); // 单条
let list = db.select(); // 多条
let count = db.count(); // 计数
let col = db.column("field"); // 单列
```
**注意**:`self.tools()` 每次调用都会 clone 整个 Tools,频繁调用时先绑定到变量。
### 脚手架生成
`addon_create()` 从 `temp/` 模板生成代码,模板按 action 名称匹配:
table/add/del/get/put/select/select_tree/tree/menu/down/import
### 参数验证字段类型
`check()` 支持:key, text, table, tree, file, int, timestamp, yearmonth, float,
string, url, time, code, pass, email, location, color, date, barcode, datetime,
editor, tel, dict, switch, select, radio, array, polygon, object
## NOTES
- `dev-dependencies` 使用本地路径 `../br-web-server`,CI 需特殊处理
- `br-db` 当前使用本地路径 `../br-db`(Cargo.toml 中有注释掉的 crates.io 版本)
- 无 rustfmt.toml / clippy.toml,使用默认配置
- 无 CI 配置文件,依赖手动执行 fmt + clippy