Skip to main content

sz_rust_core/
controller.rs

1//! 控制器模块 — SzController trait
2//!
3//! 对齐 PHP `app\SzController`(abstract class extends BaseController)。
4//! 继承链:BaseController → SzController → AddonsBaseController → 业务控制器。
5//!
6//! ## PHP 对齐
7//!
8//! | PHP 方法 | 签名 | Rust 等价 |
9//! |---------|------|-----------|
10//! | `renderJson($code=1, $msg='', $data=[])` | 返回 `compact('code','msg','data')` 数组 | [`SzController::render_json`] 返回 `Value::Object` |
11//! | `renderSuccess($msg='success', $data=[])` | `json(renderJson(1, $msg, $data))` | [`SzController::render_success`] 返回 `Response` |
12//! | `renderError($msg='error', $data=[], $code=0)` | `json(renderJson($code, $msg, $data))` | [`SzController::render_error`] 返回 `Response` |
13//! | `postData($key=null)` | `$this->request->param(...)` | [`SzController::post_data`] / [`SzController::post_data_by_key`] |
14//! | `getData($key=null)` | `$this->request->get(...)` | [`SzController::get_data`] / [`SzController::get_data_by_key`] |
15//!
16//! ## 参数顺序差异
17//!
18//! PHP `renderSuccess($msg, $data)` 与 Rust `ApiResponse::success(data, msg)` 参数顺序相反。
19//! 本 trait 严格遵循 PHP 顺序(msg 在前),内部调用 `ApiResponse` 时调换参数。
20//!
21//! ## Request 传递
22//!
23//! PHP 通过 `$this->request` 在控制器内部访问当前请求;Rust 控制器无状态,
24//! 由 handler 在每次请求时将 `Request<Body>` 作为参数传入 trait 方法。
25//!
26//! ## async trait
27//!
28//! 使用 Rust 1.75+ 原生 `async fn` in trait。trait 暂不支持 `dyn SzController`;
29//! 若未来需要 trait object,可改用 `#[async_trait]` 宏(届时增加 `async-trait` 依赖)。
30
31use axum::body::Body;
32use axum::http::Request;
33use axum::response::{IntoResponse, Response};
34use indexmap::IndexMap;
35use once_cell::sync::Lazy;
36use serde_json::{Map, Value};
37use std::future::Future;
38
39use crate::request::{
40    fetch_post_data, fetch_post_data_by_key, fetch_query_data, fetch_query_data_by_key,
41};
42use crate::response::ApiResponse;
43use crate::validate::Validate;
44
45// ============================================================================
46// JWT 配置(运行时从环境变量读取,对齐 PHP Token 类私有 $_config)
47// ============================================================================
48//
49// PHP `app\common\service\jwt\Token::$_config` 包含 issuer/audience/id/sign/expire,
50// 这些敏感信息不应硬编码到框架中。Rust 实现通过环境变量注入:
51//
52// - `SZ_JWT_SECRET`:签名密钥(PHP 实际使用 `id` 字段作为 HMAC 密钥,而非 `sign`)
53// - `SZ_JWT_ISSUER`:签发人(对应 PHP `issuer`,如 `https://mall.ljclz.shop`)
54//
55// 注:sz-orm-auth 的 JwtClaims 暂不包含 `aud` 字段,因此 audience 验证由
56// 业务层在解码后自行实现(PHP `PermittedFor` 约束等价)。
57
58/// JWT 配置(运行时从环境变量读取)
59///
60/// 安全约束:`JwtConfig` 未派生 `Serialize`/`Deserialize`,密钥绝不会
61/// 出现在序列化输出中。未来若需派生 `Serialize`,必须为 `secret` 字段
62/// 添加 `#[serde(skip_serializing)]` 防止日志/响应泄露。
63#[derive(Debug, Clone, Default)]
64struct JwtConfig {
65    /// 签名密钥(对应 PHP `$_config['id']`,Lcobucci 用作 HMAC 密钥)
66    secret: String,
67    /// 签发人(对应 PHP `$_config['issuer']`)
68    issuer: String,
69}
70
71/// 全局 JWT 配置实例(启动时从环境变量读取一次)
72///
73/// - `SZ_JWT_SECRET`:签名密钥(必填,空字符串表示禁用 JWT 验证)
74/// - `SZ_JWT_ISSUER`:签发人(可选,空字符串表示跳过 iss 验证)
75static JWT_CONFIG: Lazy<JwtConfig> = Lazy::new(|| JwtConfig {
76    secret: std::env::var("SZ_JWT_SECRET").unwrap_or_default(),
77    issuer: std::env::var("SZ_JWT_ISSUER").unwrap_or_default(),
78});
79
80/// 去除 Authorization header 中的 Bearer 前缀
81///
82/// 对齐 PHP `str_ireplace('bearer', '', $header)`:大小写不敏感地移除 `bearer` 标识。
83/// 支持以下格式(与 PHP 兼容):
84/// - `Bearer xxx.yyy.zzz` → `xxx.yyy.zzz`
85/// - `bearer xxx.yyy.zzz` → `xxx.yyy.zzz`
86/// - `BEARER xxx.yyy.zzz` → `xxx.yyy.zzz`
87/// - `xxx.yyy.zzz`(无前缀)→ `xxx.yyy.zzz`(保持原样)
88fn strip_bearer_prefix(header: &str) -> &str {
89    let trimmed = header.trim();
90    // 大小写不敏感匹配 "bearer"
91    if trimmed.len() >= 6 {
92        let prefix = &trimmed[..6];
93        if prefix.eq_ignore_ascii_case("bearer") {
94            // 跳过 "bearer" 后可能存在的空格
95            return trimmed[6..].trim_start();
96        }
97    }
98    trimmed
99}
100
101/// 使用指定配置验证 JWT token 并提取用户信息
102///
103/// 这是 [`AddonsBaseController::get_token`] 的核心逻辑,抽取为独立函数便于单元测试
104/// 注入不同配置(避免依赖全局 `JWT_CONFIG` 一次性初始化的副作用)。
105///
106/// # 参数
107///
108/// - `authorization`:Authorization header 原始值(可能含 `Bearer ` 前缀)
109/// - `config`:JWT 配置(密钥 + 签发人)
110///
111/// # 返回
112///
113/// - `Ok(Some(UserInfo))`:验证成功
114/// - `Ok(None)`:无 token、密钥未配置、签名/过期/iss 验证失败、缺少 user_id
115fn verify_token_with_config(
116    authorization: Option<&str>,
117    config: &JwtConfig,
118) -> Result<Option<UserInfo>, String> {
119    // 1. 提取 Authorization header 值
120    let header_value = match authorization {
121        Some(v) if !v.is_empty() => v,
122        _ => return Ok(None),
123    };
124
125    // 2. 去除 Bearer 前缀
126    let token = strip_bearer_prefix(header_value).trim();
127    if token.is_empty() {
128        return Ok(None);
129    }
130
131    // 3. 密钥未配置则禁用 JWT 验证
132    if config.secret.is_empty() {
133        return Ok(None);
134    }
135
136    // 4. JwtEncoder 解码并验证签名 + 过期
137    let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
138    let claims = match encoder.decode(token) {
139        Ok(c) => c,
140        Err(_) => return Ok(None),
141    };
142
143    // 5. 验证 iss 字段(仅当配置了 issuer 时)
144    if !config.issuer.is_empty() {
145        match &claims.iss {
146            Some(iss) if iss == &config.issuer => { /* 通过 */ }
147            _ => return Ok(None),
148        }
149    }
150
151    // 6. 提取 user_id
152    let user_id = match claims.user_id {
153        Some(id) => id,
154        None => return Ok(None),
155    };
156
157    Ok(Some(UserInfo {
158        user_id,
159        is_login: true,
160    }))
161}
162
163/// 控制器基础 trait(对齐 PHP `app\SzController`)
164///
165/// 业务控制器实现此 trait 即可获得 `render_json` / `render_success` / `render_error`
166/// 与 `post_data` / `get_data` 等方法。
167///
168/// # 用法
169///
170/// ```ignore
171/// use sz_rust_core::controller::SzController;
172/// use sz_rust_core::response::ApiResponse;
173/// use axum::body::Body;
174/// use axum::http::Request;
175/// use axum::response::Response;
176/// use serde_json::json;
177///
178/// struct UserController;
179///
180/// impl SzController for UserController {}
181///
182/// impl UserController {
183///     pub async fn info(&self, req: Request<Body>) -> Response {
184///         let data = self.post_data(req).await.unwrap();
185///         let name = data.get("name").cloned().unwrap_or(json!(null));
186///         self.render_success("success", json!({"name": name}))
187///     }
188/// }
189/// ```
190pub trait SzController: Send + Sync {
191    /// 返回封装后的 API 数据(对应 PHP `renderJson`)
192    ///
193    /// 严格遵循 PHP `compact('code', 'msg', 'data')` 的字段顺序:`code → msg → data`。
194    /// 返回 `Value::Object`(而非 `Response`),便于调用方进一步处理。
195    ///
196    /// # PHP 对齐
197    ///
198    /// ```php
199    /// protected function renderJson($code = 1, $msg = '', $data = []) {
200    ///     return compact('code', 'msg', 'data');
201    /// }
202    /// ```
203    fn render_json(&self, code: i32, msg: impl Into<String>, data: Value) -> Value {
204        let mut map = Map::new();
205        map.insert("code".to_string(), Value::Number(code.into()));
206        map.insert("msg".to_string(), Value::String(msg.into()));
207        map.insert("data".to_string(), data);
208        Value::Object(map)
209    }
210
211    /// 返回操作成功 json(对应 PHP `renderSuccess`)
212    ///
213    /// 参数顺序:`msg, data`(与 PHP 一致,与 `ApiResponse::success(data, msg)` 相反)。
214    /// 返回 `Response`,HTTP 200,Content-Type: application/json; charset=utf-8。
215    ///
216    /// # PHP 对齐
217    ///
218    /// ```php
219    /// protected function renderSuccess($msg = 'success', $data = []) {
220    ///     return json($this->renderJson(1, $msg, $data));
221    /// }
222    /// ```
223    fn render_success(&self, msg: impl Into<String>, data: Value) -> Response {
224        ApiResponse::success(data, msg).into_response()
225    }
226
227    /// 返回操作失败 json(对应 PHP `renderError`)
228    ///
229    /// 参数顺序:`msg, data, code`(与 PHP 一致)。
230    /// 返回 `Response`,HTTP 200(业务失败 HTTP 仍 200,对齐 PHP 行为)。
231    ///
232    /// # PHP 对齐
233    ///
234    /// ```php
235    /// protected function renderError($msg = 'error', $data = [], $code = 0) {
236    ///     return json($this->renderJson($code, $msg, $data));
237    /// }
238    /// ```
239    fn render_error(&self, msg: impl Into<String>, data: Value, code: i32) -> Response {
240        ApiResponse::error_with_code(code, msg, data).into_response()
241    }
242
243    /// 获取合并后的请求参数(对应 PHP `postData()` 无参形式)
244    ///
245    /// 合并 body + query,body 字段优先级高于 query。
246    /// 对齐 PHP `$this->request->param()`(合并 POST + GET + route)。
247    ///
248    /// # PHP 对齐
249    ///
250    /// ```php
251    /// protected function postData($key = null) {
252    ///     return $this->request->param(is_null($key) ? '' : $key . '/a');
253    /// }
254    /// ```
255    fn post_data(&self, req: Request<Body>) -> impl Future<Output = Result<Value, String>> + Send {
256        async move { fetch_post_data(req).await }
257    }
258
259    /// 获取合并参数中指定 key(对应 PHP `postData($key)`)
260    ///
261    /// # 返回
262    ///
263    /// - `Ok(Some(value))`:字段存在
264    /// - `Ok(None)`:字段不存在
265    /// - `Err(String)`:body 读取或 JSON 解析失败
266    fn post_data_by_key(
267        &self,
268        req: Request<Body>,
269        key: &str,
270    ) -> impl Future<Output = Result<Option<Value>, String>> + Send {
271        async move { fetch_post_data_by_key(req, key).await }
272    }
273
274    /// 获取 query 参数(对应 PHP `getData()` 无参形式)
275    ///
276    /// 对齐 PHP `$this->request->get()`。
277    fn get_data(&self, req: &Request<Body>) -> Value {
278        fetch_query_data(req)
279    }
280
281    /// 获取 query 参数中指定 key(对应 PHP `getData($key)`)
282    ///
283    /// # 返回
284    ///
285    /// - `Some(value)`:字段存在
286    /// - `None`:字段不存在
287    fn get_data_by_key(&self, req: &Request<Body>, key: &str) -> Option<Value> {
288        fetch_query_data_by_key(req, key)
289    }
290}
291
292/// 控制器基础 trait(对齐 PHP `app\BaseController`)
293///
294/// PHP 继承链:`BaseController → SzController → AddonsBaseController → 业务控制器`。
295/// Rust 等价:`BaseController: SzController`(trait 继承),业务控制器同时实现
296/// `BaseController` 和 `SzController`(trait 继承自动传播)。
297///
298/// # PHP 对齐
299///
300/// | PHP 属性/方法 | Rust 等价 |
301/// |--------------|-----------|
302/// | `protected $request` | 通过方法参数 `req: Request<Body>` 传入(见 [`SzController`]) |
303/// | `protected $app` | 通过 axum `State<App>` 提取器获取(不在 trait 中) |
304/// | `protected bool $batchValidate = false` | [`BaseController::batch_validate`] |
305/// | `protected array $middleware = []` | [`BaseController::middlewares`] |
306/// | `protected function initialize() {}` | [`BaseController::initialize`] |
307/// | `protected function validate(...)` | [`BaseController::validate`](占位,完整实现) |
308///
309/// # 状态迁移说明
310///
311/// PHP `BaseController` 是 abstract class,包含状态字段($request/$app)。Rust 控制器
312/// 推荐无状态(每个请求通过参数传入 Request),因此本 trait 不持有状态,仅定义行为。
313pub trait BaseController: SzController {
314    /// 是否批量验证(对齐 PHP `protected bool $batchValidate = false`)
315    ///
316    /// 返回 `true` 时,`validate` 方法会收集所有错误后一次性返回;
317    /// 返回 `false` 时,遇到第一个错误即返回。
318    fn batch_validate(&self) -> bool {
319        false
320    }
321
322    /// 控制器中间件列表(对齐 PHP `protected array $middleware = []`)
323    ///
324    /// 返回中间件标识符列表(如 `["auth", "cors"]`)。
325    /// 实际中间件注册在中间件系统中处理。
326    fn middlewares(&self) -> Vec<String> {
327        Vec::new()
328    }
329
330    /// 初始化钩子(对齐 PHP `protected function initialize() {}`)
331    ///
332    /// 在控制器实例化后、方法调用前执行。
333    /// 子类可重写以执行初始化逻辑(如设置默认值、加载配置)。
334    ///
335    /// # PHP 对齐
336    ///
337    /// ```php
338    /// protected function initialize() {
339    ///     // 子类重写
340    /// }
341    /// ```
342    fn initialize(&self) {}
343
344    /// 数据验证(对齐 PHP `protected function validate(...)`)
345    ///
346    /// # PHP 对齐
347    ///
348    /// ```php
349    /// protected function validate(array $data, $validate, array $message = [], bool $batch = false) {
350    ///     // ... 完整验证逻辑
351    /// }
352    /// ```
353    ///
354    /// # 实现说明
355    ///
356    /// 将 `rules` 与 `messages` 转换为内部 [`Validate`] 构建器调用,
357    /// 批量模式由 [`BaseController::batch_validate`] 决定(对齐 PHP `$batch`
358    /// 参数默认值 `false`,但 Rust 通过 trait 方法覆盖以适配无状态控制器)。
359    ///
360    /// # 参数
361    ///
362    /// - `data`:待验证的数据
363    /// - `rules`:验证规则列表,元组格式 `(字段名, 规则字符串)`
364    ///   - 例:`[("name", "require"), ("age", "require|integer|gt:0")]`
365    /// - `messages`:错误消息列表,元组格式 `(规则键, 消息)`
366    ///   - 例:`[("name.require", "名称必填"), ("age.integer", "年龄须为整数")]`
367    ///
368    /// # 返回
369    ///
370    /// - `Ok(())`:验证通过
371    /// - `Err(String)`:验证失败,包含错误消息(批量模式下以 `; ` 分隔多条错误)
372    fn validate(
373        &self,
374        data: &Value,
375        rules: &[(&str, &str)],
376        messages: &[(&str, &str)],
377    ) -> Result<(), String> {
378        // 构建 Validate 实例,将 (字段名, 规则) 元组列表逐条注册
379        let mut validator = Validate::new();
380        for (name, rule) in rules {
381            validator = validator.rule(name, rule);
382        }
383
384        // 将 (规则键, 消息) 元组列表合并到 IndexMap 后注入
385        let mut msg_map = IndexMap::new();
386        for (key, msg) in messages {
387            msg_map.insert(key.to_string(), msg.to_string());
388        }
389        validator = validator.message(msg_map);
390
391        // 批量模式由控制器配置决定(对齐 PHP $batch 参数)
392        if self.batch_validate() {
393            validator = validator.batch(true);
394        }
395
396        // 执行验证并转换错误类型(Display 实现已处理 Single/Batch 两种格式)
397        match validator.check(data) {
398            Ok(()) => Ok(()),
399            Err(err) => Err(err.to_string()),
400        }
401    }
402}
403
404/// 路由信息(对齐 PHP `addons\BaseController` 的 $controller/$action/$routeUri/$group)
405///
406/// 由 [`AddonsBaseController::parse_route_info`] 从 URI 解析得到。
407#[derive(Debug, Clone, PartialEq, Eq)]
408pub struct RouteInfo {
409    /// 当前控制器名称(对齐 PHP `$this->controller`)
410    pub controller: String,
411    /// 当前方法名称(对齐 PHP `$this->action`)
412    pub action: String,
413    /// 当前路由 URI(对齐 PHP `$this->routeUri`,格式 `/controller/action`)
414    pub route_uri: String,
415    /// 控制器分组(对齐 PHP `$this->group`,controller 的第一段)
416    pub group: String,
417}
418
419/// 用户信息(对齐 PHP `$this->user` 数组中的关键字段)
420///
421/// JWT 验证成功后返回,包含 user_id 与 is_login 状态。
422#[derive(Debug, Clone, PartialEq, Eq)]
423pub struct UserInfo {
424    /// 用户 ID
425    pub user_id: i64,
426    /// 是否已登录(对齐 PHP `$this->user['is_login'] == 1`)
427    pub is_login: bool,
428}
429
430/// addons 控制器基础 trait(对齐 PHP `addons\BaseController`)
431///
432/// PHP 继承链:`BaseController → SzController → AddonsBaseController → 业务控制器`。
433/// Rust 等价:`AddonsBaseController: BaseController: SzController`(trait 继承链)。
434///
435/// # PHP 对齐
436///
437/// | PHP 属性/方法 | Rust 等价 |
438/// |--------------|-----------|
439/// | `protected $user` | handler 中通过 [`AddonsBaseController::get_token`] 获取 [`UserInfo`] |
440/// | `protected string $controller` | [`RouteInfo::controller`](由 [`AddonsBaseController::parse_route_info`] 解析) |
441/// | `protected string $action` | [`RouteInfo::action`] |
442/// | `protected string $routeUri` | [`RouteInfo::route_uri`] |
443/// | `protected string $group` | [`RouteInfo::group`] |
444/// | `protected array $allowAllAction` | [`AddonsBaseController::allow_all_action`] |
445/// | `public function initialize()` | 由 handler 显式调用 `parse_route_info` + `get_token` + `check_login` |
446/// | `public function getToken()` | [`AddonsBaseController::get_token`](占位,JWT 完整实现) |
447/// | `protected function getRouteinfo()` | [`AddonsBaseController::parse_route_info`] |
448/// | `private function checkLogin()` | [`AddonsBaseController::check_login`] |
449///
450/// # 状态迁移说明
451///
452/// PHP `AddonsBaseController::initialize()` 在构造函数中自动调用,执行
453/// `getRouteinfo() + getToken() + checkLogin()`。Rust 控制器无状态,
454/// handler 需显式调用这三个方法(顺序:parse_route_info → get_token → check_login)。
455pub trait AddonsBaseController: BaseController {
456    /// 登录验证白名单(对齐 PHP `protected array $allowAllAction`)
457    ///
458    /// 默认包含 `/passport/login` 和 `/task/task/userClerk`。
459    /// 子类可覆盖以添加更多白名单路径。
460    fn allow_all_action(&self) -> Vec<&'static str> {
461        vec!["/passport/login", "/task/task/userClerk"]
462    }
463
464    /// 解析路由信息(对齐 PHP `protected function getRouteinfo()`)
465    ///
466    /// 从 URI 路径解析 controller/action/group/route_uri。
467    ///
468    /// # 解析规则
469    ///
470    /// - URI 形如 `/controller/action`:controller=controller, action=action, group=controller
471    /// - URI 形如 `/group/controller/action`:controller=group/controller, action=action, group=group/controller
472    /// - URI 形如 `/controller`:controller=controller, action="", group=controller
473    /// - URI 形如 `/`:所有字段为空字符串
474    ///
475    /// # PHP 对齐(含 bug 复刻)
476    ///
477    /// ```php
478    /// protected function getRouteinfo(): void {
479    ///     $this->controller = toUnderScore(Request()->controller());
480    ///     $this->controller = str_replace(".", "/", $this->controller);
481    ///     $this->controller = str_replace("_", "", $this->controller);
482    ///     $this->action = Request()->action();
483    ///     // ⚠️ PHP bug:str_replace 已将 "." 替换为 "/",但 strstr 仍以 "." 为分隔符,
484    ///     // 永远返回 false,因此 $this->group === $this->controller(始终相等)。
485    ///     $groupstr = strstr($this->controller, '.', true);
486    ///     $this->group = $groupstr !== false ? $groupstr : $this->controller;
487    ///     $this->routeUri = '/' . $this->controller . '/' . $this->action;
488    /// }
489    /// ```
490    ///
491    /// **PHP bug 复刻说明**:Rust 严格对齐 PHP 行为,`group` 字段始终等于 `controller`。
492    /// 经核查 PHP 后端全部源码,`$this->group` 为只写不读的死字段,bug 不暴露,
493    /// 但为了 R5(PHP/Rust 行为对比)严格一致性,仍复刻此行为。
494    ///
495    /// 注意:PHP 使用 ThinkPHP 的 `Request()->controller()` 获取控制器名,
496    /// Rust 直接从 URI 路径解析(已通过路由匹配)。
497    fn parse_route_info(&self, uri: &str) -> RouteInfo {
498        let path = uri.split('?').next().unwrap_or("");
499        let path = path.trim_start_matches('/');
500        let segments: Vec<&str> = if path.is_empty() {
501            Vec::new()
502        } else {
503            path.split('/').collect()
504        };
505
506        let (controller, action) = match segments.len() {
507            0 => (String::new(), String::new()),
508            1 => (segments[0].to_string(), String::new()),
509            _ => (
510                segments[..segments.len() - 1].join("/"),
511                segments[segments.len() - 1].to_string(),
512            ),
513        };
514
515        // PHP bug 复刻:group === controller(详见方法文档注释)
516        let group = controller.clone();
517
518        let route_uri = if controller.is_empty() && action.is_empty() {
519            "/".to_string()
520        } else {
521            format!("/{controller}/{action}")
522        };
523
524        RouteInfo {
525            controller,
526            action,
527            route_uri,
528            group,
529        }
530    }
531
532    /// 检查登录状态(对齐 PHP `private function checkLogin()`)
533    ///
534    /// # 行为
535    ///
536    /// 1. 若 `route_uri` 在白名单中,返回 `Ok(())`
537    /// 2. 若 `user_is_login == true`,返回 `Ok(())`
538    /// 3. 否则返回 `Err("not_login")`
539    ///
540    /// # PHP 对齐
541    ///
542    /// ```php
543    /// private function checkLogin(): void {
544    ///     if (in_array($this->routeUri, $this->allowAllAction)) {
545    ///         return;
546    ///     }
547    ///     if(!empty($this->user)){
548    ///         if($this->user['is_login'] == 1){
549    ///             return;
550    ///         }
551    ///     }
552    ///     throw new BaseException(['code' => -1, 'msg' => 'not_login']);
553    /// }
554    /// ```
555    fn check_login(&self, route_uri: &str, user_is_login: bool) -> Result<(), String> {
556        if self.allow_all_action().contains(&route_uri) {
557            return Ok(());
558        }
559        if user_is_login {
560            return Ok(());
561        }
562        Err("not_login".to_string())
563    }
564
565    /// 获取 token 用户信息(对齐 PHP `public function getToken()`)
566    ///
567    /// # 实现说明
568    ///
569    /// 使用 sz-orm-auth 的 `JwtEncoder` 进行 HS256 签名验证与过期检查,
570    /// 配置项(`SZ_JWT_SECRET` / `SZ_JWT_ISSUER`)在启动时从环境变量读取。
571    /// 核心验证逻辑见 `verify_token_with_config`,便于单元测试注入配置。
572    ///
573    /// 验证流程(对齐 PHP `Token::getUserId`):
574    /// 1. 提取 Authorization header 中的 Bearer token
575    /// 2. 通过 `JwtEncoder::decode` 验证签名 + 过期时间
576    /// 3. 验证 `iss` 字段匹配配置的签发人
577    /// 4. 提取 `user_id` claim 返回 [`UserInfo`]
578    ///
579    /// # PHP 对齐
580    ///
581    /// ```php
582    /// public function getToken(){
583    ///     if (!$token = Token::getUserId(request()->header('Authorization'))) {
584    ///         if(in_array($this->routeUri, $this->allowAllAction)) {
585    ///             return true;
586    ///         } else {
587    ///             throw new BaseException(['msg' => '缺少必要的参数,请重新登陆!']);
588    ///         }
589    ///     }
590    ///     return $token;
591    /// }
592    /// ```
593    ///
594    /// # 参数
595    ///
596    /// - `authorization`:`Authorization` 请求头的值(如 `"Bearer xxx.yyy.zzz"`)
597    ///
598    /// # 返回
599    ///
600    /// - `Ok(Some(UserInfo))`:JWT 验证成功,返回用户信息
601    /// - `Ok(None)`:无 token、token 为空、签名密钥未配置或验证失败
602    ///   (调用方根据 route_uri 决定是否抛错,对齐 PHP `if (!$token)` 分支)
603    /// - `Err(String)`:JWT 解析过程中出现异常(如格式错误)
604    fn get_token(&self, authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
605        // 委托给 verify_token_with_config,使用全局 JWT_CONFIG
606        // 抽取独立函数便于单元测试注入不同配置(避免 once_cell 一次性初始化限制)
607        verify_token_with_config(authorization, &JWT_CONFIG)
608    }
609}
610
611#[cfg(test)]
612mod tests {
613    use super::*;
614    use axum::body::Body;
615    use axum::http::{Method, Request, StatusCode};
616    use http_body_util::BodyExt;
617    use serde_json::json;
618    use tower::ServiceExt;
619
620    /// 测试用 Mock 控制器(仅使用默认实现)
621    struct MockController;
622
623    impl SzController for MockController {}
624
625    fn make_json_request(body: &str, query: Option<&str>) -> Request<Body> {
626        let uri = match query {
627            Some(q) => format!("/?{q}"),
628            None => "/".to_string(),
629        };
630        Request::builder()
631            .method(Method::POST)
632            .uri(&uri)
633            .header("content-type", "application/json")
634            .body(Body::from(body.to_string()))
635            .unwrap()
636    }
637
638    fn make_get_request(query: &str) -> Request<Body> {
639        Request::builder()
640            .method(Method::GET)
641            .uri(format!("/?{query}"))
642            .body(Body::empty())
643            .unwrap()
644    }
645
646    async fn collect_body(resp: Response) -> String {
647        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
648        String::from_utf8(bytes.to_vec()).unwrap()
649    }
650
651    // ====================================================================
652    // render_json 测试
653    // ====================================================================
654
655    #[test]
656    fn test_render_json_field_order() {
657        // 严格验证字段顺序:code → msg → data(对齐 PHP compact())
658        let ctrl = MockController;
659        let value = ctrl.render_json(1, "ok", json!({"id": 1}));
660        let obj = value.as_object().unwrap();
661        let keys: Vec<&String> = obj.keys().collect();
662        assert_eq!(keys, vec!["code", "msg", "data"]);
663    }
664
665    #[test]
666    fn test_render_json_default_values() {
667        // PHP 默认值:$code=1, $msg='', $data=[]
668        let ctrl = MockController;
669        let value = ctrl.render_json(1, "", Value::Object(Map::new()));
670        assert_eq!(value["code"], 1);
671        assert_eq!(value["msg"], "");
672        assert!(value["data"].is_object());
673        assert!(value["data"].as_object().unwrap().is_empty());
674    }
675
676    #[test]
677    fn test_render_json_custom_code() {
678        let ctrl = MockController;
679        let value = ctrl.render_json(-1, "未登录", json!({}));
680        assert_eq!(value["code"], -1);
681        assert_eq!(value["msg"], "未登录");
682    }
683
684    #[test]
685    fn test_render_json_compact_semantics() {
686        // 验证 PHP compact('code','msg','data') 的等价语义:
687        // 变量名 → 值的映射,保序序列化
688        let ctrl = MockController;
689        let value = ctrl.render_json(0, "失败", json!({"field": "name"}));
690        let json_str = value.to_string();
691        // 字段顺序必须是 code → msg → data
692        assert_eq!(
693            json_str,
694            r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#
695        );
696    }
697
698    #[test]
699    fn test_render_json_returns_value_not_response() {
700        // PHP renderJson 返回数组(非 Response),renderSuccess/renderError 才返回 json()
701        let ctrl = MockController;
702        let value = ctrl.render_json(1, "ok", json!({}));
703        // 应该是 Value,不是 Response
704        assert!(value.is_object());
705    }
706
707    // ====================================================================
708    // render_success 测试
709    // ====================================================================
710
711    #[test]
712    fn test_render_success_returns_response() {
713        let ctrl = MockController;
714        let resp = ctrl.render_success("success", json!({"id": 1}));
715        assert_eq!(resp.status(), StatusCode::OK);
716        assert_eq!(
717            resp.headers().get("content-type").unwrap(),
718            "application/json; charset=utf-8"
719        );
720    }
721
722    #[tokio::test]
723    async fn test_render_success_body_format() {
724        // 对齐 PHP renderSuccess('success', $data)
725        let ctrl = MockController;
726        let resp = ctrl.render_success("success", json!({"id": 1, "name": "alice"}));
727        let body = collect_body(resp).await;
728        assert_eq!(
729            body,
730            r#"{"code":1,"msg":"success","data":{"id":1,"name":"alice"}}"#
731        );
732    }
733
734    #[tokio::test]
735    async fn test_render_success_default_msg() {
736        // PHP 默认 msg='success'
737        let ctrl = MockController;
738        let resp = ctrl.render_success("success", json!({}));
739        let body = collect_body(resp).await;
740        assert_eq!(body, r#"{"code":1,"msg":"success","data":{}}"#);
741    }
742
743    #[tokio::test]
744    async fn test_render_success_via_axum_router() {
745        // 端到端验证:通过 axum Router 调用 render_success
746        struct UserController;
747        impl SzController for UserController {}
748
749        async fn handler() -> Response {
750            let ctrl = UserController;
751            ctrl.render_success("ok", json!({"id": 1}))
752        }
753
754        let router = axum::Router::new().route("/", axum::routing::get(handler));
755        let req = Request::builder()
756            .method(Method::GET)
757            .uri("/")
758            .body(Body::empty())
759            .unwrap();
760        let resp = router.oneshot(req).await.unwrap();
761
762        assert_eq!(resp.status(), StatusCode::OK);
763        let body = collect_body(resp).await;
764        assert_eq!(body, r#"{"code":1,"msg":"ok","data":{"id":1}}"#);
765    }
766
767    // ====================================================================
768    // render_error 测试
769    // ====================================================================
770
771    #[test]
772    fn test_render_error_returns_response() {
773        let ctrl = MockController;
774        let resp = ctrl.render_error("error", json!({}), 0);
775        assert_eq!(resp.status(), StatusCode::OK); // 业务错误 HTTP 仍 200
776    }
777
778    #[tokio::test]
779    async fn test_render_error_default_code() {
780        // PHP 默认 $code=0
781        let ctrl = MockController;
782        let resp = ctrl.render_error("参数错误", json!({}), 0);
783        let body = collect_body(resp).await;
784        assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
785    }
786
787    #[tokio::test]
788    async fn test_render_error_custom_code() {
789        // PHP renderError($msg, $data, $code=-1) 用于未登录场景
790        let ctrl = MockController;
791        let resp = ctrl.render_error("not_login", json!({}), -1);
792        let body = collect_body(resp).await;
793        assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
794    }
795
796    #[tokio::test]
797    async fn test_render_error_with_data() {
798        let ctrl = MockController;
799        let resp = ctrl.render_error("失败", json!({"field": "name"}), 0);
800        let body = collect_body(resp).await;
801        assert_eq!(body, r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#);
802    }
803
804    #[tokio::test]
805    async fn test_render_error_via_axum_router() {
806        struct UserController;
807        impl SzController for UserController {}
808
809        async fn handler() -> Response {
810            let ctrl = UserController;
811            ctrl.render_error("参数错误", json!({}), 0)
812        }
813
814        let router = axum::Router::new().route("/", axum::routing::post(handler));
815        let req = Request::builder()
816            .method(Method::POST)
817            .uri("/")
818            .body(Body::empty())
819            .unwrap();
820        let resp = router.oneshot(req).await.unwrap();
821
822        assert_eq!(resp.status(), StatusCode::OK);
823        let body = collect_body(resp).await;
824        assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
825    }
826
827    // ====================================================================
828    // post_data 测试(对齐 PHP postData())
829    // ====================================================================
830
831    #[tokio::test]
832    async fn test_post_data_json_body() {
833        let ctrl = MockController;
834        let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
835        let data = ctrl.post_data(req).await.unwrap();
836        assert_eq!(data["name"], "alice");
837        assert_eq!(data["age"], 30);
838    }
839
840    #[tokio::test]
841    async fn test_post_data_query_only() {
842        let ctrl = MockController;
843        let req = make_json_request("", Some("page=1&size=10"));
844        let data = ctrl.post_data(req).await.unwrap();
845        assert_eq!(data["page"], "1");
846        assert_eq!(data["size"], "10");
847    }
848
849    #[tokio::test]
850    async fn test_post_data_body_overrides_query() {
851        // body 优先级高于 query(对齐 PHP param() 行为)
852        let ctrl = MockController;
853        let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
854        let data = ctrl.post_data(req).await.unwrap();
855        assert_eq!(data["page"], 99);
856        assert_eq!(data["size"], "10");
857    }
858
859    #[tokio::test]
860    async fn test_post_data_by_key_exists() {
861        let ctrl = MockController;
862        let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
863        let name = ctrl.post_data_by_key(req, "name").await.unwrap();
864        assert_eq!(name, Some(json!("alice")));
865    }
866
867    #[tokio::test]
868    async fn test_post_data_by_key_missing() {
869        let ctrl = MockController;
870        let req = make_json_request(r#"{"name":"alice"}"#, None);
871        let age = ctrl.post_data_by_key(req, "age").await.unwrap();
872        assert_eq!(age, None);
873    }
874
875    // ====================================================================
876    // get_data 测试(对齐 PHP getData())
877    // ====================================================================
878
879    #[test]
880    fn test_get_data_query() {
881        let ctrl = MockController;
882        let req = make_get_request("page=1&size=10");
883        let data = ctrl.get_data(&req);
884        assert_eq!(data["page"], "1");
885        assert_eq!(data["size"], "10");
886    }
887
888    #[test]
889    fn test_get_data_empty_query() {
890        let ctrl = MockController;
891        let req = make_get_request("");
892        let data = ctrl.get_data(&req);
893        assert!(data.as_object().unwrap().is_empty());
894    }
895
896    #[test]
897    fn test_get_data_by_key_exists() {
898        let ctrl = MockController;
899        let req = make_get_request("page=1&size=10");
900        let page = ctrl.get_data_by_key(&req, "page");
901        assert_eq!(page, Some(json!("1")));
902    }
903
904    #[test]
905    fn test_get_data_by_key_missing() {
906        let ctrl = MockController;
907        let req = make_get_request("page=1");
908        let size = ctrl.get_data_by_key(&req, "size");
909        assert_eq!(size, None);
910    }
911
912    // ====================================================================
913    // 控制器多实例隔离测试
914    // ====================================================================
915
916    #[tokio::test]
917    async fn test_multiple_controllers_independent() {
918        // 验证多个控制器实例互不干扰(无状态 trait)
919        struct CtrlA;
920        struct CtrlB;
921        impl SzController for CtrlA {}
922        impl SzController for CtrlB {}
923
924        let a = CtrlA;
925        let b = CtrlB;
926
927        let req_a = make_json_request(r#"{"k":"a"}"#, None);
928        let req_b = make_json_request(r#"{"k":"b"}"#, None);
929
930        let data_a = a.post_data(req_a).await.unwrap();
931        let data_b = b.post_data(req_b).await.unwrap();
932
933        assert_eq!(data_a["k"], "a");
934        assert_eq!(data_b["k"], "b");
935
936        // 验证 render_json 互不影响
937        let va = a.render_json(1, "a", json!({}));
938        let vb = b.render_json(0, "b", json!({}));
939        assert_eq!(va["code"], 1);
940        assert_eq!(va["msg"], "a");
941        assert_eq!(vb["code"], 0);
942        assert_eq!(vb["msg"], "b");
943    }
944
945    // ====================================================================
946    // PHP 一致性综合测试
947    // ====================================================================
948
949    #[tokio::test]
950    async fn test_php_consistency_full_flow() {
951        // 模拟 PHP 控制器典型流程:读取 postData → 业务处理 → renderSuccess
952        struct OrderController;
953        impl SzController for OrderController {}
954
955        let ctrl = OrderController;
956
957        // 1. 读取 POST 数据
958        let req = make_json_request(r#"{"order_id":12345,"amount":99.5}"#, None);
959        let data = ctrl.post_data(req).await.unwrap();
960        let order_id = data["order_id"].as_i64().unwrap();
961        let amount = data["amount"].as_f64().unwrap();
962
963        // 2. 业务处理(模拟)
964        let result = json!({
965            "order_id": order_id,
966            "amount": amount,
967            "status": "paid"
968        });
969
970        // 3. 返回成功响应
971        let resp = ctrl.render_success("支付成功", result);
972        let body = collect_body(resp).await;
973
974        // 验证字段顺序与 PHP compact() 一致
975        assert_eq!(
976            body,
977            r#"{"code":1,"msg":"支付成功","data":{"order_id":12345,"amount":99.5,"status":"paid"}}"#
978        );
979    }
980
981    #[tokio::test]
982    async fn test_php_consistency_error_flow() {
983        // 模拟 PHP 控制器错误流程:校验失败 → renderError
984        struct UserController;
985        impl SzController for UserController {}
986
987        let ctrl = UserController;
988
989        let req = make_json_request(r#"{"name":""}"#, None);
990        let data = ctrl.post_data(req).await.unwrap();
991        let name = data["name"].as_str().unwrap();
992
993        if name.is_empty() {
994            let resp = ctrl.render_error("用户名不能为空", json!({"field": "name"}), 0);
995            let body = collect_body(resp).await;
996            assert_eq!(
997                body,
998                r#"{"code":0,"msg":"用户名不能为空","data":{"field":"name"}}"#
999            );
1000        } else {
1001            panic!("should be empty");
1002        }
1003    }
1004
1005    #[tokio::test]
1006    async fn test_php_consistency_not_login_flow() {
1007        // 模拟 PHP 控制器未登录流程:renderError($msg, $data, $code=-1)
1008        struct PassportController;
1009        impl SzController for PassportController {}
1010
1011        let ctrl = PassportController;
1012        let resp = ctrl.render_error("not_login", json!({}), -1);
1013        let body = collect_body(resp).await;
1014        assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1015    }
1016
1017    // ====================================================================
1018    // BaseController trait 测试
1019    // ====================================================================
1020
1021    /// 实现 BaseController 的 Mock 控制器(同时自动实现 SzController)
1022    struct MockBaseController;
1023
1024    impl SzController for MockBaseController {}
1025    impl BaseController for MockBaseController {}
1026
1027    #[test]
1028    fn test_base_controller_default_batch_validate() {
1029        // PHP 默认 $batchValidate = false
1030        let ctrl = MockBaseController;
1031        assert!(!ctrl.batch_validate());
1032    }
1033
1034    #[test]
1035    fn test_base_controller_default_middlewares_empty() {
1036        // PHP 默认 $middleware = []
1037        let ctrl = MockBaseController;
1038        assert!(ctrl.middlewares().is_empty());
1039    }
1040
1041    #[test]
1042    fn test_base_controller_default_initialize_no_panic() {
1043        // PHP 默认 initialize() 为空,调用应无副作用
1044        let ctrl = MockBaseController;
1045        ctrl.initialize(); // 不应 panic
1046    }
1047
1048    #[test]
1049    fn test_base_controller_default_validate_returns_ok() {
1050        // require 规则 + 字段存在 → 应通过
1051        let ctrl = MockBaseController;
1052        let data = json!({"name": "alice"});
1053        let rules = [("name", "require")];
1054        let messages: [(&str, &str); 0] = [];
1055        let result = ctrl.validate(&data, &rules, &messages);
1056        assert!(result.is_ok());
1057    }
1058
1059    #[test]
1060    fn test_validate_require_pass_with_value() {
1061        // require 规则:字段存在且非空 → 通过
1062        let ctrl = MockBaseController;
1063        let data = json!({"name": "alice", "age": 30});
1064        let rules = [("name", "require"), ("age", "require|integer")];
1065        let messages: [(&str, &str); 0] = [];
1066        assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1067    }
1068
1069    #[test]
1070    fn test_validate_require_fail_when_missing() {
1071        // require 规则:字段缺失 → 失败(Single 模式,返回第一条错误)
1072        let ctrl = MockBaseController;
1073        let data = json!({"name": "alice"});
1074        let rules = [("name", "require"), ("age", "require|integer")];
1075        let messages: [(&str, &str); 0] = [];
1076        let result = ctrl.validate(&data, &rules, &messages);
1077        assert!(result.is_err());
1078        let err = result.unwrap_err();
1079        // 错误信息应包含 age 字段(require 验证失败)
1080        assert!(err.contains("age"), "error: {err}");
1081    }
1082
1083    #[test]
1084    fn test_validate_integer_fail_on_string() {
1085        // integer 规则:字段非整数 → 失败
1086        let ctrl = MockBaseController;
1087        let data = json!({"age": "not-a-number"});
1088        let rules = [("age", "require|integer")];
1089        let messages: [(&str, &str); 0] = [];
1090        let result = ctrl.validate(&data, &rules, &messages);
1091        assert!(result.is_err());
1092    }
1093
1094    #[test]
1095    fn test_validate_custom_message_applied() {
1096        // 自定义消息应被使用(对齐 PHP message[field.type])
1097        let ctrl = MockBaseController;
1098        let data = json!({}); // name 缺失
1099        let rules = [("name", "require")];
1100        let messages = [("name.require", "名称必填")];
1101        let result = ctrl.validate(&data, &rules, &messages);
1102        assert!(result.is_err());
1103        assert_eq!(result.unwrap_err(), "名称必填");
1104    }
1105
1106    #[test]
1107    fn test_validate_batch_mode_returns_multiple_errors() {
1108        // 批量模式:收集所有错误,以 "; " 分隔
1109        struct BatchController;
1110        impl SzController for BatchController {}
1111        impl BaseController for BatchController {
1112            fn batch_validate(&self) -> bool {
1113                true
1114            }
1115        }
1116
1117        let ctrl = BatchController;
1118        let data = json!({}); // name 和 age 都缺失
1119        let rules = [("name", "require"), ("age", "require")];
1120        let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1121        let result = ctrl.validate(&data, &rules, &messages);
1122        assert!(result.is_err());
1123        let err = result.unwrap_err();
1124        // 两条错误都应出现(顺序按字段注册顺序)
1125        assert!(err.contains("名称必填"), "err: {err}");
1126        assert!(err.contains("年龄必填"), "err: {err}");
1127        // 应以 "; " 分隔(ValidateError::Batch 的 Display 格式)
1128        assert!(err.contains("; "), "err: {err}");
1129    }
1130
1131    #[test]
1132    fn test_validate_single_mode_returns_first_error_only() {
1133        // 非批量模式:仅返回第一条错误
1134        let ctrl = MockBaseController;
1135        let data = json!({}); // name 和 age 都缺失
1136        let rules = [("name", "require"), ("age", "require")];
1137        let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1138        let result = ctrl.validate(&data, &rules, &messages);
1139        assert!(result.is_err());
1140        let err = result.unwrap_err();
1141        // 仅包含第一条错误,不包含第二条
1142        assert!(err.contains("名称必填"), "err: {err}");
1143        assert!(!err.contains("年龄必填"), "err: {err}");
1144    }
1145
1146    #[test]
1147    fn test_validate_in_rule_pass() {
1148        // in 规则:值在列表中 → 通过
1149        let ctrl = MockBaseController;
1150        let data = json!({"status": "active"});
1151        let rules = [("status", "require|in:active,inactive")];
1152        let messages: [(&str, &str); 0] = [];
1153        assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1154    }
1155
1156    #[test]
1157    fn test_validate_in_rule_fail() {
1158        // in 规则:值不在列表中 → 失败
1159        let ctrl = MockBaseController;
1160        let data = json!({"status": "deleted"});
1161        let rules = [("status", "require|in:active,inactive")];
1162        let messages: [(&str, &str); 0] = [];
1163        assert!(ctrl.validate(&data, &rules, &messages).is_err());
1164    }
1165
1166    #[test]
1167    fn test_validate_empty_rules_always_pass() {
1168        // 空规则列表:无验证规则,总应通过
1169        let ctrl = MockBaseController;
1170        let data = json!({"anything": "value"});
1171        let rules: [(&str, &str); 0] = [];
1172        let messages: [(&str, &str); 0] = [];
1173        assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1174    }
1175
1176    #[test]
1177    fn test_base_controller_inherits_sz_controller_methods() {
1178        // BaseController: SzController,自动获得 render_json 等方法
1179        let ctrl = MockBaseController;
1180        let value = ctrl.render_json(1, "ok", json!({}));
1181        assert_eq!(value["code"], 1);
1182        assert_eq!(value["msg"], "ok");
1183
1184        let resp = ctrl.render_success("ok", json!({"id": 1}));
1185        assert_eq!(resp.status(), StatusCode::OK);
1186    }
1187
1188    /// 覆盖默认值的子类控制器
1189    struct CustomController;
1190
1191    impl SzController for CustomController {}
1192    impl BaseController for CustomController {
1193        fn batch_validate(&self) -> bool {
1194            true
1195        }
1196
1197        fn middlewares(&self) -> Vec<String> {
1198            vec!["auth".to_string(), "cors".to_string()]
1199        }
1200
1201        fn initialize(&self) {
1202            // 子类自定义初始化(无状态,仅作演示)
1203        }
1204    }
1205
1206    #[test]
1207    fn test_base_controller_override_batch_validate() {
1208        let ctrl = CustomController;
1209        assert!(ctrl.batch_validate());
1210    }
1211
1212    #[test]
1213    fn test_base_controller_override_middlewares() {
1214        let ctrl = CustomController;
1215        let mws = ctrl.middlewares();
1216        assert_eq!(mws, vec!["auth", "cors"]);
1217    }
1218
1219    #[test]
1220    fn test_base_controller_override_initialize() {
1221        let ctrl = CustomController;
1222        ctrl.initialize(); // 不应 panic
1223    }
1224
1225    /// 模拟 PHP 业务控制器:带状态的 initialize 钩子
1226    /// PHP 中 $this->batchValidate = true; 在 initialize() 中设置
1227    struct StatefulController {
1228        initialized: parking_lot::Mutex<bool>,
1229        custom_batch: bool,
1230    }
1231
1232    impl StatefulController {
1233        fn new() -> Self {
1234            Self {
1235                initialized: parking_lot::Mutex::new(false),
1236                custom_batch: false,
1237            }
1238        }
1239    }
1240
1241    impl SzController for StatefulController {}
1242    impl BaseController for StatefulController {
1243        fn batch_validate(&self) -> bool {
1244            self.custom_batch
1245        }
1246
1247        fn initialize(&self) {
1248            *self.initialized.lock() = true;
1249            // 模拟 PHP: $this->batchValidate = true;
1250            // Rust 中由于 trait 方法不能修改 self,需要在调用方处理
1251        }
1252    }
1253
1254    #[test]
1255    fn test_base_controller_stateful_initialize() {
1256        let ctrl = StatefulController::new();
1257        assert!(!*ctrl.initialized.lock()); // 初始未初始化
1258        ctrl.initialize(); // 调用初始化钩子
1259        assert!(*ctrl.initialized.lock()); // 已初始化
1260    }
1261
1262    /// 模拟 PHP 控制器典型流程:initialize → validate → renderSuccess
1263    #[tokio::test]
1264    async fn test_base_controller_php_full_flow() {
1265        struct UserController;
1266        impl SzController for UserController {}
1267        impl BaseController for UserController {}
1268
1269        let ctrl = UserController;
1270
1271        // 1. 初始化钩子
1272        ctrl.initialize();
1273
1274        // 2. 读取 POST 数据
1275        let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1276        let data = ctrl.post_data(req).await.unwrap();
1277
1278        // 3. 验证(占位实现总返回 Ok)
1279        let rules = [("name", "require"), ("age", "require|integer|gt:0")];
1280        let messages: [(&str, &str); 0] = [];
1281        ctrl.validate(&data, &rules, &messages).unwrap();
1282
1283        // 4. 返回成功响应
1284        let resp =
1285            ctrl.render_success("success", json!({"name": data["name"], "age": data["age"]}));
1286        let body = collect_body(resp).await;
1287        assert_eq!(
1288            body,
1289            r#"{"code":1,"msg":"success","data":{"name":"alice","age":30}}"#
1290        );
1291    }
1292
1293    /// 测试多控制器继承链独立性
1294    #[test]
1295    fn test_base_controller_multiple_instances_independent() {
1296        struct ControllerA;
1297        struct ControllerB;
1298
1299        impl SzController for ControllerA {}
1300        impl BaseController for ControllerA {
1301            fn middlewares(&self) -> Vec<String> {
1302                vec!["auth".to_string()]
1303            }
1304        }
1305
1306        impl SzController for ControllerB {}
1307        impl BaseController for ControllerB {
1308            fn middlewares(&self) -> Vec<String> {
1309                vec!["cors".to_string(), "log".to_string()]
1310            }
1311        }
1312
1313        let a = ControllerA;
1314        let b = ControllerB;
1315
1316        assert_eq!(a.middlewares(), vec!["auth"]);
1317        assert_eq!(b.middlewares(), vec!["cors", "log"]);
1318
1319        // 默认 batch_validate 不互相影响
1320        assert!(!a.batch_validate());
1321        assert!(!b.batch_validate());
1322    }
1323
1324    /// PHP 继承链验证:BaseController → SzController 方法可用性
1325    #[test]
1326    fn test_base_controller_inheritance_chain() {
1327        // 业务控制器实现 BaseController,自动获得 SzController 的所有方法
1328        struct BusinessController;
1329        impl SzController for BusinessController {}
1330        impl BaseController for BusinessController {}
1331
1332        let ctrl = BusinessController;
1333
1334        // SzController 方法(来自父 trait)
1335        let value = ctrl.render_json(0, "error", json!({}));
1336        assert!(value.is_object());
1337
1338        // BaseController 方法
1339        assert!(!ctrl.batch_validate());
1340        assert!(ctrl.middlewares().is_empty());
1341        ctrl.initialize();
1342    }
1343
1344    // ====================================================================
1345    // AddonsBaseController trait 测试
1346    // ====================================================================
1347
1348    /// 实现 AddonsBaseController 的 Mock 控制器(同时自动实现 BaseController + SzController)
1349    struct MockAddonsController;
1350
1351    impl SzController for MockAddonsController {}
1352    impl BaseController for MockAddonsController {}
1353    impl AddonsBaseController for MockAddonsController {}
1354
1355    #[test]
1356    fn test_addons_default_allow_all_action() {
1357        // PHP 默认 $allowAllAction = ['/passport/login', '/task/task/userClerk']
1358        let ctrl = MockAddonsController;
1359        let allow = ctrl.allow_all_action();
1360        assert!(allow.contains(&"/passport/login"));
1361        assert!(allow.contains(&"/task/task/userClerk"));
1362        assert_eq!(allow.len(), 2);
1363    }
1364
1365    #[test]
1366    fn test_addons_parse_route_info_two_segments() {
1367        // /passport/login → controller=passport, action=login, group=passport
1368        let ctrl = MockAddonsController;
1369        let info = ctrl.parse_route_info("/passport/login");
1370        assert_eq!(info.controller, "passport");
1371        assert_eq!(info.action, "login");
1372        assert_eq!(info.group, "passport");
1373        assert_eq!(info.route_uri, "/passport/login");
1374    }
1375
1376    #[test]
1377    fn test_addons_parse_route_info_three_segments() {
1378        // /task/task/userClerk → controller=task/task, action=userClerk
1379        // PHP bug 复刻:group=controller=task/task(详见 parse_route_info 文档注释)
1380        let ctrl = MockAddonsController;
1381        let info = ctrl.parse_route_info("/task/task/userClerk");
1382        assert_eq!(info.controller, "task/task");
1383        assert_eq!(info.action, "userClerk");
1384        assert_eq!(info.group, "task/task"); // PHP bug: group === controller
1385        assert_eq!(info.route_uri, "/task/task/userClerk");
1386    }
1387
1388    #[test]
1389    fn test_addons_parse_route_info_single_segment() {
1390        // /passport → controller=passport, action="", group=passport
1391        let ctrl = MockAddonsController;
1392        let info = ctrl.parse_route_info("/passport");
1393        assert_eq!(info.controller, "passport");
1394        assert_eq!(info.action, "");
1395        assert_eq!(info.group, "passport");
1396        assert_eq!(info.route_uri, "/passport/");
1397    }
1398
1399    #[test]
1400    fn test_addons_parse_route_info_root() {
1401        // / → 所有字段为空,route_uri="/"
1402        let ctrl = MockAddonsController;
1403        let info = ctrl.parse_route_info("/");
1404        assert_eq!(info.controller, "");
1405        assert_eq!(info.action, "");
1406        assert_eq!(info.group, "");
1407        assert_eq!(info.route_uri, "/");
1408    }
1409
1410    #[test]
1411    fn test_addons_parse_route_info_empty_uri() {
1412        let ctrl = MockAddonsController;
1413        let info = ctrl.parse_route_info("");
1414        assert_eq!(info.controller, "");
1415        assert_eq!(info.route_uri, "/");
1416    }
1417
1418    #[test]
1419    fn test_addons_parse_route_info_with_query_string() {
1420        // 含 query string 的 URI 应被剥离
1421        let ctrl = MockAddonsController;
1422        let info = ctrl.parse_route_info("/user/info?foo=bar&baz=1");
1423        assert_eq!(info.controller, "user");
1424        assert_eq!(info.action, "info");
1425        assert_eq!(info.route_uri, "/user/info");
1426    }
1427
1428    #[test]
1429    fn test_addons_parse_route_info_trailing_slash() {
1430        // 末尾斜杠
1431        let ctrl = MockAddonsController;
1432        let info = ctrl.parse_route_info("/user/info/");
1433        // split 会产生空字符串末尾段
1434        assert_eq!(info.controller, "user/info");
1435        assert_eq!(info.action, "");
1436        assert_eq!(info.route_uri, "/user/info/");
1437    }
1438
1439    #[test]
1440    fn test_addons_check_login_whitelist_pass() {
1441        // /passport/login 在白名单中,应通过
1442        let ctrl = MockAddonsController;
1443        let result = ctrl.check_login("/passport/login", false);
1444        assert!(result.is_ok());
1445    }
1446
1447    #[test]
1448    fn test_addons_check_login_whitelist_pass_2() {
1449        // /task/task/userClerk 在白名单中,应通过
1450        let ctrl = MockAddonsController;
1451        let result = ctrl.check_login("/task/task/userClerk", false);
1452        assert!(result.is_ok());
1453    }
1454
1455    #[test]
1456    fn test_addons_check_login_logged_in_pass() {
1457        // 不在白名单但已登录,应通过
1458        let ctrl = MockAddonsController;
1459        let result = ctrl.check_login("/user/info", true);
1460        assert!(result.is_ok());
1461    }
1462
1463    #[test]
1464    fn test_addons_check_login_not_logged_in_fail() {
1465        // 不在白名单且未登录,应返回 not_login
1466        let ctrl = MockAddonsController;
1467        let result = ctrl.check_login("/user/info", false);
1468        assert!(result.is_err());
1469        assert_eq!(result.unwrap_err(), "not_login");
1470    }
1471
1472    #[test]
1473    fn test_addons_get_token_default_returns_none() {
1474        // 未配置 SZ_JWT_SECRET 环境变量时,验证失败应返回 Ok(None)
1475        // 注:测试运行时不应假定环境变量已设置
1476        let ctrl = MockAddonsController;
1477        let result = ctrl.get_token(Some("Bearer xxx.yyy.zzz"));
1478        assert!(result.is_ok());
1479        assert_eq!(result.unwrap(), None);
1480    }
1481
1482    #[test]
1483    fn test_addons_get_token_no_authorization() {
1484        let ctrl = MockAddonsController;
1485        let result = ctrl.get_token(None);
1486        assert!(result.is_ok());
1487        assert_eq!(result.unwrap(), None);
1488    }
1489
1490    #[test]
1491    fn test_addons_get_token_empty_authorization() {
1492        let ctrl = MockAddonsController;
1493        let result = ctrl.get_token(Some(""));
1494        assert!(result.is_ok());
1495        assert_eq!(result.unwrap(), None);
1496    }
1497
1498    #[test]
1499    fn test_addons_get_token_invalid_format_returns_none() {
1500        // 非法 JWT 格式(非三段式)应返回 Ok(None) 而非 Err
1501        // 注:仅在 SZ_JWT_SECRET 已设置时才会进入格式校验
1502        let ctrl = MockAddonsController;
1503        let result = ctrl.get_token(Some("Bearer not.a.valid.jwt.token"));
1504        assert!(result.is_ok());
1505        // 无论是否配置密钥,无效 token 都应返回 None
1506        assert_eq!(result.unwrap(), None);
1507    }
1508
1509    #[test]
1510    fn test_strip_bearer_prefix_uppercase() {
1511        assert_eq!(strip_bearer_prefix("Bearer abc.def.ghi"), "abc.def.ghi");
1512    }
1513
1514    #[test]
1515    fn test_strip_bearer_prefix_lowercase() {
1516        assert_eq!(strip_bearer_prefix("bearer abc.def.ghi"), "abc.def.ghi");
1517    }
1518
1519    #[test]
1520    fn test_strip_bearer_prefix_mixed_case() {
1521        assert_eq!(strip_bearer_prefix("BEARER abc.def.ghi"), "abc.def.ghi");
1522    }
1523
1524    #[test]
1525    fn test_strip_bearer_prefix_no_prefix() {
1526        // 无 Bearer 前缀时,应保持原样(去除首尾空格)
1527        assert_eq!(strip_bearer_prefix("abc.def.ghi"), "abc.def.ghi");
1528    }
1529
1530    #[test]
1531    fn test_strip_bearer_prefix_empty() {
1532        assert_eq!(strip_bearer_prefix(""), "");
1533    }
1534
1535    #[test]
1536    fn test_strip_bearer_prefix_with_extra_spaces() {
1537        assert_eq!(
1538            strip_bearer_prefix("  Bearer   abc.def.ghi  "),
1539            "abc.def.ghi"
1540        );
1541    }
1542
1543    /// JWT 端到端验证测试:使用真实 JwtEncoder 签发 token,再通过 verify_token_with_config 验证
1544    ///
1545    /// 通过 verify_token_with_config 注入测试配置,避免依赖全局环境变量,
1546    /// 确保 CI 环境也能完整执行 JWT 验证流程测试。
1547    #[test]
1548    fn test_get_token_valid_jwt_returns_user_info() {
1549        let config = JwtConfig {
1550            secret: "test-secret".to_string(),
1551            issuer: String::new(), // 不验证 iss
1552        };
1553
1554        // 签发一个有效 token
1555        let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1556        let exp = std::time::SystemTime::now()
1557            .duration_since(std::time::UNIX_EPOCH)
1558            .unwrap()
1559            .as_secs() as i64
1560            + 3600; // 1 小时后过期
1561        let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1562        let token = encoder.encode(&claims).unwrap();
1563
1564        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1565        assert!(result.is_ok());
1566        let user = result.unwrap();
1567        assert!(user.is_some());
1568        let user = user.unwrap();
1569        assert_eq!(user.user_id, 12345);
1570        assert!(user.is_login);
1571    }
1572
1573    /// 测试 JWT 签名密钥错误时返回 None
1574    #[test]
1575    fn test_get_token_wrong_secret_returns_none() {
1576        let config = JwtConfig {
1577            secret: "correct-secret".to_string(),
1578            issuer: String::new(),
1579        };
1580
1581        // 用错误密钥签发 token
1582        let encoder = sz_orm_auth::jwt::JwtEncoder::new("wrong-secret");
1583        let exp = std::time::SystemTime::now()
1584            .duration_since(std::time::UNIX_EPOCH)
1585            .unwrap()
1586            .as_secs() as i64
1587            + 3600;
1588        let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1589        let token = encoder.encode(&claims).unwrap();
1590
1591        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1592        assert!(result.is_ok());
1593        assert_eq!(result.unwrap(), None); // 签名验证失败
1594    }
1595
1596    /// 测试 JWT 过期时返回 None
1597    #[test]
1598    fn test_get_token_expired_returns_none() {
1599        let config = JwtConfig {
1600            secret: "test-secret".to_string(),
1601            issuer: String::new(),
1602        };
1603
1604        let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1605        // 过期时间为 1 小时前
1606        let exp = std::time::SystemTime::now()
1607            .duration_since(std::time::UNIX_EPOCH)
1608            .unwrap()
1609            .as_secs() as i64
1610            - 3600;
1611        let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1612        let token = encoder.encode(&claims).unwrap();
1613
1614        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1615        assert!(result.is_ok());
1616        assert_eq!(result.unwrap(), None); // 已过期
1617    }
1618
1619    /// 测试 JWT 缺少 user_id claim 时返回 None(兼容旧版 token)
1620    #[test]
1621    fn test_get_token_no_user_id_claim_returns_none() {
1622        let config = JwtConfig {
1623            secret: "test-secret".to_string(),
1624            issuer: String::new(),
1625        };
1626
1627        let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1628        let exp = std::time::SystemTime::now()
1629            .duration_since(std::time::UNIX_EPOCH)
1630            .unwrap()
1631            .as_secs() as i64
1632            + 3600;
1633        // 不调用 with_user_id,user_id 为 None
1634        let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp);
1635        let token = encoder.encode(&claims).unwrap();
1636
1637        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1638        assert!(result.is_ok());
1639        assert_eq!(result.unwrap(), None); // 缺少 user_id
1640    }
1641
1642    /// 测试 iss 验证:iss 不匹配时返回 None
1643    #[test]
1644    fn test_get_token_iss_mismatch_returns_none() {
1645        let config = JwtConfig {
1646            secret: "test-secret".to_string(),
1647            issuer: "https://expected-issuer.com".to_string(),
1648        };
1649
1650        let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1651        let exp = std::time::SystemTime::now()
1652            .duration_since(std::time::UNIX_EPOCH)
1653            .unwrap()
1654            .as_secs() as i64
1655            + 3600;
1656        // 使用错误的 issuer 签发
1657        let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp)
1658            .with_issuer("https://wrong-issuer.com")
1659            .with_user_id(12345);
1660        let token = encoder.encode(&claims).unwrap();
1661
1662        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1663        assert!(result.is_ok());
1664        assert_eq!(result.unwrap(), None); // iss 不匹配
1665    }
1666
1667    /// 测试 iss 验证:iss 匹配时返回 UserInfo
1668    #[test]
1669    fn test_get_token_iss_match_returns_user_info() {
1670        let config = JwtConfig {
1671            secret: "test-secret".to_string(),
1672            issuer: "https://mall.ljclz.shop".to_string(),
1673        };
1674
1675        let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1676        let exp = std::time::SystemTime::now()
1677            .duration_since(std::time::UNIX_EPOCH)
1678            .unwrap()
1679            .as_secs() as i64
1680            + 3600;
1681        let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp)
1682            .with_issuer(&config.issuer)
1683            .with_user_id(67890);
1684        let token = encoder.encode(&claims).unwrap();
1685
1686        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1687        assert!(result.is_ok());
1688        let user = result.unwrap().unwrap();
1689        assert_eq!(user.user_id, 67890);
1690        assert!(user.is_login);
1691    }
1692
1693    /// 测试密钥未配置时返回 None(禁用 JWT 验证)
1694    #[test]
1695    fn test_get_token_empty_secret_returns_none() {
1696        let config = JwtConfig::default(); // secret 为空
1697
1698        let result = verify_token_with_config(Some("Bearer any.token.here"), &config);
1699        assert!(result.is_ok());
1700        assert_eq!(result.unwrap(), None); // 密钥未配置
1701    }
1702
1703    /// 测试无 Bearer 前缀的 token 也能正常解析
1704    #[test]
1705    fn test_get_token_without_bearer_prefix() {
1706        let config = JwtConfig {
1707            secret: "test-secret".to_string(),
1708            issuer: String::new(),
1709        };
1710
1711        let encoder = sz_orm_auth::jwt::JwtEncoder::new(&config.secret);
1712        let exp = std::time::SystemTime::now()
1713            .duration_since(std::time::UNIX_EPOCH)
1714            .unwrap()
1715            .as_secs() as i64
1716            + 3600;
1717        let claims = sz_orm_auth::jwt::JwtClaims::new("user123", exp).with_user_id(99999);
1718        let token = encoder.encode(&claims).unwrap();
1719
1720        // 不带 Bearer 前缀,应仍能解析(对齐 PHP str_ireplace 兼容行为)
1721        let result = verify_token_with_config(Some(&token), &config);
1722        assert!(result.is_ok());
1723        let user = result.unwrap().unwrap();
1724        assert_eq!(user.user_id, 99999);
1725    }
1726
1727    /// 覆盖白名单的子类控制器
1728    struct CustomAddonsController;
1729
1730    impl SzController for CustomAddonsController {}
1731    impl BaseController for CustomAddonsController {}
1732    impl AddonsBaseController for CustomAddonsController {
1733        fn allow_all_action(&self) -> Vec<&'static str> {
1734            vec!["/custom/public", "/custom/health"]
1735        }
1736    }
1737
1738    #[test]
1739    fn test_addons_override_allow_all_action() {
1740        let ctrl = CustomAddonsController;
1741        let allow = ctrl.allow_all_action();
1742        assert_eq!(allow, vec!["/custom/public", "/custom/health"]);
1743        // 不应包含默认白名单
1744        assert!(!allow.contains(&"/passport/login"));
1745    }
1746
1747    #[test]
1748    fn test_addons_override_check_login_uses_new_whitelist() {
1749        let ctrl = CustomAddonsController;
1750        // /custom/public 在新白名单中
1751        assert!(ctrl.check_login("/custom/public", false).is_ok());
1752        // /passport/login 不在新白名单中(被覆盖)
1753        assert!(ctrl.check_login("/passport/login", false).is_err());
1754    }
1755
1756    /// 模拟 PHP addons 控制器典型流程:parse_route_info → get_token → check_login → renderSuccess
1757    #[tokio::test]
1758    async fn test_addons_php_full_flow_whitelist() {
1759        struct PassportController;
1760        impl SzController for PassportController {}
1761        impl BaseController for PassportController {}
1762        impl AddonsBaseController for PassportController {}
1763
1764        let ctrl = PassportController;
1765
1766        // 1. 解析路由信息
1767        let info = ctrl.parse_route_info("/passport/login");
1768        assert_eq!(info.route_uri, "/passport/login");
1769
1770        // 2. 获取 token(占位返回 None)
1771        let user = ctrl.get_token(None).unwrap();
1772
1773        // 3. 检查登录(白名单通过,user_is_login 不影响)
1774        let is_login = user.as_ref().is_some_and(|u| u.is_login);
1775        ctrl.check_login(&info.route_uri, is_login).unwrap();
1776
1777        // 4. 返回成功响应(模拟登录成功)
1778        let resp = ctrl.render_success("登录成功", json!({"token": "fake.jwt.token"}));
1779        let body = collect_body(resp).await;
1780        assert_eq!(
1781            body,
1782            r#"{"code":1,"msg":"登录成功","data":{"token":"fake.jwt.token"}}"#
1783        );
1784    }
1785
1786    #[tokio::test]
1787    async fn test_addons_php_full_flow_not_login() {
1788        struct UserController;
1789        impl SzController for UserController {}
1790        impl BaseController for UserController {}
1791        impl AddonsBaseController for UserController {}
1792
1793        let ctrl = UserController;
1794
1795        // 1. 解析路由信息
1796        let info = ctrl.parse_route_info("/user/info");
1797
1798        // 2. 获取 token(占位返回 None,即未登录)
1799        let user = ctrl.get_token(None).unwrap();
1800        let is_login = user.as_ref().is_some_and(|u| u.is_login);
1801
1802        // 3. 检查登录(白名单不通过,且未登录)
1803        let result = ctrl.check_login(&info.route_uri, is_login);
1804        assert!(result.is_err());
1805        assert_eq!(result.unwrap_err(), "not_login");
1806
1807        // 4. 返回未登录响应(模拟 PHP BaseException ['code' => -1, 'msg' => 'not_login'])
1808        let resp = ctrl.render_error("not_login", json!({}), -1);
1809        let body = collect_body(resp).await;
1810        assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1811    }
1812
1813    #[tokio::test]
1814    async fn test_addons_php_full_flow_logged_in() {
1815        struct OrderController;
1816        impl SzController for OrderController {}
1817        impl BaseController for OrderController {}
1818        impl AddonsBaseController for OrderController {
1819            // 模拟已登录用户(覆盖 get_token 返回 UserInfo)
1820            fn get_token(&self, _authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
1821                Ok(Some(UserInfo {
1822                    user_id: 12345,
1823                    is_login: true,
1824                }))
1825            }
1826        }
1827
1828        let ctrl = OrderController;
1829
1830        // 1. 解析路由信息
1831        let info = ctrl.parse_route_info("/order/list");
1832
1833        // 2. 获取 token(返回已登录用户)
1834        let user = ctrl.get_token(None).unwrap();
1835        let is_login = user.as_ref().is_some_and(|u| u.is_login);
1836
1837        // 3. 检查登录(白名单不通过,但已登录)
1838        ctrl.check_login(&info.route_uri, is_login).unwrap();
1839
1840        // 4. 返回订单列表
1841        let resp = ctrl.render_success(
1842            "success",
1843            json!({"list": [{"id": 1}, {"id": 2}], "total": 2}),
1844        );
1845        let body = collect_body(resp).await;
1846        assert_eq!(
1847            body,
1848            r#"{"code":1,"msg":"success","data":{"list":[{"id":1},{"id":2}],"total":2}}"#
1849        );
1850    }
1851
1852    /// 测试 RouteInfo / UserInfo 类型
1853    #[test]
1854    fn test_route_info_debug_clone_eq() {
1855        let info1 = RouteInfo {
1856            controller: "user".to_string(),
1857            action: "info".to_string(),
1858            route_uri: "/user/info".to_string(),
1859            group: "user".to_string(),
1860        };
1861        let info2 = info1.clone();
1862        assert_eq!(info1, info2);
1863        let debug_str = format!("{info1:?}");
1864        assert!(debug_str.contains("RouteInfo"));
1865        assert!(debug_str.contains("user"));
1866    }
1867
1868    #[test]
1869    fn test_user_info_debug_clone_eq() {
1870        let user1 = UserInfo {
1871            user_id: 12345,
1872            is_login: true,
1873        };
1874        let user2 = user1.clone();
1875        assert_eq!(user1, user2);
1876        let debug_str = format!("{user1:?}");
1877        assert!(debug_str.contains("UserInfo"));
1878        assert!(debug_str.contains("12345"));
1879    }
1880
1881    /// 继承链验证:AddonsBaseController → BaseController → SzController
1882    #[test]
1883    fn test_addons_inheritance_chain_all_methods() {
1884        struct BusinessController;
1885        impl SzController for BusinessController {}
1886        impl BaseController for BusinessController {}
1887        impl AddonsBaseController for BusinessController {}
1888
1889        let ctrl = BusinessController;
1890
1891        // SzController 方法(祖父 trait)
1892        let value = ctrl.render_json(1, "ok", json!({}));
1893        assert_eq!(value["code"], 1);
1894
1895        // BaseController 方法(父 trait)
1896        assert!(!ctrl.batch_validate());
1897        assert!(ctrl.middlewares().is_empty());
1898        ctrl.initialize();
1899
1900        // AddonsBaseController 方法(当前 trait)
1901        let info = ctrl.parse_route_info("/test/action");
1902        assert_eq!(info.route_uri, "/test/action");
1903        assert!(ctrl.check_login("/passport/login", false).is_ok());
1904        assert!(ctrl.get_token(None).unwrap().is_none());
1905    }
1906}