Skip to main content

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