br-addon 0.2.7

This is an addon
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
# 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`(默认全启用)

## 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) + 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`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")]`。

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

### Tables 完整用法参考(tables.rs)

`Tables` 是核心表格查询构建器,驱动所有 CRUD 列表视图(~287 个 table.rs + ~136 个 select.rs + 3 个 tree + 2 个 menu + 2 个 select_tree)。

#### 静态参数方法(用于 Action::params())

根据终端方法选择对应的 params 方法:

| 终端方法 | params 方法 | 额外参数 |
|---------|------------|---------|
| `get_table()` | `Tables::params_table(object!{})` | page, limit, order, search, where_or, where_and, params |
| `get_tree()` | `Tables::params_table_tree(object!{})` | + pid, value, primary_key |
| `get_table_select()` | `Tables::params_table_select(object!{})` | + value, primary_key |
| `get_table_select_tree()` | `Tables::params_table_select_tree(object!{})` | + pid, value |
| `get_table_menu()` | `Tables::params_table(object!{})` | 同 get_table |
| `get_table_edit()` | `Tables::params_table(object!{})` | 同 get_table |

#### Builder 方法(返回 `&mut Self`

| 方法 | 签名 | 说明 |
|------|------|------|
| `main_table_fields` | `(table: &str, fields: JsonValue, hide: Vec<&str>, show: Vec<&str>)` | **必须首调**。设置主表名、字段定义、隐藏/显示白名单 |
| `main_select_fields` | `(table: &str, show_fields: Vec<&str>)` | select 专用。设置主表 + 显示字段(自动加 id) |
| `params` | `(request: JsonValue)` | **必须调用**。解析 page/limit/where_and/where_or/order/search/pid/primary_key/value |
| `search_fields` | `(fields: Vec<&str>)` | 搜索框匹配字段,自动拼接标题为 search_name |
| `filter_fields` | `(fields: Vec<&str>)` | 高级筛选字段(前端渲染筛选器),未知字段会 warn 日志 |
| `edit_fields` | `(fields: Vec<&str>)` | 可内联编辑的字段(配合 `get_table_edit`|
| `fields` | `(fields: JsonValue)` | 直接覆盖字段定义(少用) |
| `hide_fields` | `(hide: Vec<&str>)` | 单独设置隐藏字段(少用,通常在 main_table_fields 中设置) |
| `show_fields` | `(show: Vec<&str>)` | 单独设置显示白名单(少用) |
| `total_fields` | `(fields: Vec<&str>)` | 合计字段(前端底部显示合计行) |
| `join_table` | `(main_table, main_field, join_table, join_field)` | 联表查询(LEFT JOIN) |
| `join_fields` | `(table: &str, fields: JsonValue, index: isize)` | 联表字段定义。index >= 0 插入位置,-1 尾部追加 |
| `order_by` | 无此方法 | 排序通过 `params` 中的 `order` 参数传入,或前端 request 携带 |

#### 终端方法(返回 `JsonValue`

| 方法 | 使用数 | 返回结构 | 说明 |
|------|--------|---------|------|
| `get_table()` | ~287 | `{pages, total, data, columns, filter_fields, search_name, total_fields, btn_all, btn_api, btn_ids}` | 标准分页表格 |
| `get_table_select()` | ~136 | `{pages, total, data}` | 下拉选择器(data 为 `[{value, label}]`|
| `get_tree(pid_field)` | 3 | 同 get_table + isLeaf | 树形表格(按 pid 分层加载) |
| `get_table_menu(label_field)` | 2 | 同 get_table + label_field | 左侧菜单+右侧详情布局 |
| `get_table_select_tree(pid_field, label_field)` | 2 | `{data, label_field, pid_field}` | 树形选择器 |
| `get_table_edit()` | 0(预留) | `{pages, total, data, columns, edit_fields}` | 内联编辑表格 |

#### 内部处理流程

所有终端方法内部按固定顺序调用:
```
db.table(main_table)
→ db_fields()      // 处理 hide/show,构建 all_fields_map 缓存,注册 json/location 字段
→ db_search()      // 搜索框 → WHERE field1|field2 LIKE '%search%'
→ db_where()       // where_and/where_or → DB 条件(table_multiple 用 LIKE 匹配)
→ db_join()        // 联表
→ db_total()       // COUNT(*) 计算总数和页数
→ db_order()       // 排序
→ db.page(page, limit)
→ db.select()      // 执行查询
→ db_table()       // 批量 FK 解析(table/table_multiple/tree 字段 ID→label)
→ columns()        // 构建前端列定义
```

**all_fields_map 缓存**:`db_fields()` 处理 hide/show 后构建 `self.all_fields_map: JsonValue`(字段名→字段定义映射),供后续 6 个方法共享读取。

**批量 FK 解析**(db_table/db_table_tree):
- `resolve_table_field_batch(field)` — 收集所有唯一 ID,单次 `WHERE id IN (...)` 查询
- `resolve_table_multiple_field_batch(field)` — 同上,用于 JSON 数组字段
- `resolve_tree_field(field, tree_data, key)` — 递归解析父链,tree_data 缓存去重

#### 使用模式

**模式 1:标准表格**(最常见,~200+ 文件)
```rust
fn params(&mut self) -> JsonValue {
    Tables::params_table(object! {})
}
fn index(&mut self, request: Request) -> ApiResponse {
    let mut table_info = self.table()
        .main_table_fields(self.module._table_name(), self.module.fields(), vec!["org_org"], vec![])
        .search_fields(vec!["code", "name"])
        .filter_fields(vec!["status", "created_at"])
        .params(request.body.clone())
        .get_table();
    // 设置按钮(见下方按钮模式)
    ApiResponse::success(table_info, "获取成功")
}
```

**模式 2:下拉选择器**(~136 文件)
```rust
fn params(&mut self) -> JsonValue {
    Tables::params_table_select(object! {})
}
fn index(&mut self, request: Request) -> ApiResponse {
    let table_info = self.table()
        .main_select_fields(self.module._table_name(), vec!["name", "code"])
        .params(request.body.clone())
        .get_table_select();
    ApiResponse::success(table_info, "获取成功")
}
```

**模式 3:树形表格**(3 文件:dept, category, area)
```rust
fn params(&mut self) -> JsonValue {
    Tables::params_table_tree(object! {})
}
fn index(&mut self, request: Request) -> ApiResponse {
    let mut table_info = self.table()
        .main_table_fields(self.module._table_name(), self.module.fields(), vec!["org_auth"], vec![])
        .search_fields(vec!["name", "code"])
        .params(request.body.clone())
        .get_tree(self.module._table_name());  // pid_field = 表名(自引用)
    // ...
}
```

**模式 4:菜单布局**(2 文件:org_auth, user_auth)
```rust
let mut table_info = self.table()
    .main_table_fields(table_name, fields, vec!["addon_module", "user_user", "api_api"], vec![])
    .params(request.body.clone())
    .search_fields(vec!["name"])
    .get_table_menu("name");  // label_field 用于左侧菜单显示
```

**模式 5:树形选择器**(2 文件:area, category)
```rust
fn params(&mut self) -> JsonValue {
    Tables::params_table_select_tree(object! {})
}
fn index(&mut self, request: Request) -> ApiResponse {
    let table_info = self.table()
        .main_select_fields(self.module._table_name(), vec!["name", "code"])
        .params(request.body.clone())
        .get_table_select_tree(self.module._table_name(), "name");
    ApiResponse::success(table_info, "获取成功")
}
```

**模式 6:联表查询**(少量复杂表格)
```rust
let mut table_info = self.table()
    .main_table_fields(self.module._table_name(), object!{}, vec![], vec![])
    .params(request.body.clone())
    .join_fields("goods_goods", goods_fields, -1)       // -1 = 尾部追加
    .join_fields(self.module._table_name(), fields, -1)
    .join_fields("dict_hscode", hscode_fields, -1)
    .join_table(self.module._table_name(), "goods_goods", "goods_goods", "id")
    .join_table("goods_goods", "hs_code", "dict_hscode", "code")
    .get_table();
```

**模式 7:条件分支**(admin/org 不同视图)
```rust
let mut table_info = if is_admin {
    self.table()
        .main_table_fields(table_name, fields, vec!["org_org"], vec![])
        .filter_fields(vec!["dict_country", "state", "org_org", "created_at", "model"])
        .search_fields(vec!["code", "name"])
        .params(request.body.clone())
        .get_table()
} else {
    request.body["where_and"].push(array!["org_org", "=", org_org]).unwrap();
    self.table()
        .main_table_fields(table_name, fields, vec!["org_org"], vec![])
        .filter_fields(vec![])
        .search_fields(vec!["code", "name"])
        .params(request.body.clone())
        .get_table()
};
```

#### 按钮模式

按钮在 `get_table()` 返回后手动设置(不是 builder 方法):

```rust
// btn_all: 全局按钮(表格顶部,不依赖选中行)
table_info["btn_all"] = vec![
    Plugins::action("addon.module.add").unwrap().btn()
        .btn_type(BtnType::Form)                    // Form=弹窗表单
        .btn_color(BtnColor::Primary)
        .json(),
    Plugins::action("addon.module.import").unwrap().btn()
        .btn_type(BtnType::Form)
        .btn_color(BtnColor::Green)
        .json(),
    Plugins::action("addon.module.download_template").unwrap().btn()
        .btn_type(BtnType::Download)                // Download=文件下载
        .btn_color(BtnColor::Primary)
        .json(),
].into();

// btn_api: 行按钮(每行操作,可带条件)
table_info["btn_api"] = vec![
    Plugins::action("addon.module.put").unwrap().btn()
        .btn_type(BtnType::Form)
        .btn_color(BtnColor::Primary)
        .cnd(vec![array!["status", "=", "草拟中"]])  // 条件显示
        .json(),
    Plugins::action("addon.module.del").unwrap().btn()
        .btn_type(BtnType::Api)                      // Api=直接调用(确认框)
        .btn_color(BtnColor::Red)
        .cnd(vec![array!["status", "=", "草拟中"]])
        .json(),
    Plugins::action("addon.module.detail").unwrap().btn()
        .btn_type(BtnType::DialogCustom)             // DialogCustom=自定义弹窗
        .btn_color(BtnColor::Primary)
        .json(),
    Plugins::action("addon.module.put_effect").unwrap().btn()
        .btn_type(BtnType::Api)
        .title("自定义标题")                          // 覆盖 action title
        .btn_color(BtnColor::Green)
        .cnd(vec![
            array!["status", "in", "待确认,已作废"],  // in 操作符
            array!["admin", "=", true],               // boolean 条件
        ])
        .json(),
].into();

// btn_ids: 批量操作按钮(多选后显示)
table_info["btn_ids"] = vec![
    Plugins::action("addon.module.batch_del").unwrap().btn()
        .btn_type(BtnType::Api)
        .btn_color(BtnColor::Red)
        .json(),
].into();

// 权限过滤(必须)
table_info["btn_all"] = check_auth(table_info["btn_all"].clone(), request.clone());
table_info["btn_api"] = check_auth(table_info["btn_api"].clone(), request.clone());
table_info["btn_ids"] = check_auth(table_info["btn_ids"].clone(), request.clone());
```

**BtnType 枚举**:`Form`(弹窗表单)、`Api`(直接调用+确认框)、`DialogCustom`(自定义弹窗页面)、`Download`(文件下载)
**BtnColor 枚举**:`Primary`(蓝)、`Red`(红)、`Green`(绿)、`Yellow`(黄)
**cnd 条件**:只支持 `=`/`<>`/`in` 操作符,不支持数值比较

#### 前端请求参数注入

在调用 `self.table()` 之前,可向 `request.body` 注入额外条件:
```rust
// 注入 where_and 条件(最常见)
request.body["where_and"].push(array!["org_org", "=", org_org]).unwrap();
request.body["where_and"].push(array!["status", "<>", "已删除"]).unwrap();

// 注入 where_or 条件
request.body["where_or"].push(array!["org_org", "=", org_org]).ok();
request.body["where_or"].push(array!["org_org", "=", ""]).ok();
```

#### 注意事项

- `main_table_fields``main_select_fields` 必须在 `params` 之前调用
- `search_fields``filter_fields` 可在 `params` 前后调用
- `get_table()` 返回的 `btn_all/btn_api/btn_ids` 默认为空数组,需手动设置
- `get_table_select()``data``[{value, label}]` 格式,label 由 `main_select_fields` 指定的字段用 ` | ` 拼接
- `get_tree()``pid_field` 通常是表名本身(自引用外键,如 `dict_area` 表的 `dict_area` 字段)
- `table_multiple` 字段的 where_and 过滤使用 LIKE 匹配(JSON TEXT 列),不是精确 IN 查询
- 联表字段自动加 `{table}_{field}` 前缀避免冲突

### 全局状态
- `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 方法有默认实现,只需覆盖必要的:

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