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// ============================================================================
657// KeyRotation — JWT 签名密钥轮换机制
658// ============================================================================
659
660use parking_lot::RwLock;
661use sha2::{Digest, Sha256};
662use std::time::Duration;
663
664/// JWT 密钥轮换管理器
665///
666/// 支持多密钥并存验证:当前密钥签发/验证,旧密钥在 grace period 内仍可验证。
667/// 轮换任务定期生成新密钥,旧密钥移入 previous 列表,超期后删除。
668pub struct KeyRotation {
669    /// 当前签名密钥
670    current: RwLock<String>,
671    /// 旧密钥列表(key, expires_at)
672    previous: RwLock<Vec<(String, std::time::Instant)>>,
673    /// 轮换间隔(默认 24h)
674    rotation_interval: Duration,
675    /// 旧密钥宽限期(默认 1h)
676    grace_period: Duration,
677    /// 最大保留旧密钥数(默认 3)
678    max_previous: usize,
679}
680
681impl std::fmt::Debug for KeyRotation {
682    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
683        f.debug_struct("KeyRotation")
684            .field("current", &"[REDACTED]")
685            .field("previous", &"[REDACTED]")
686            .field("rotation_interval", &self.rotation_interval)
687            .field("grace_period", &self.grace_period)
688            .field("max_previous", &self.max_previous)
689            .finish()
690    }
691}
692
693/// 密钥轮换错误
694#[derive(Debug, thiserror::Error)]
695pub enum KeyRotationError {
696    /// 环境变量未设置
697    #[error("SZ300_JWT_SECRET 环境变量未设置")]
698    SecretMissing,
699    /// token 验证失败
700    #[error("Token 验证失败:所有密钥均无法解码")]
701    InvalidToken,
702    /// token 签发失败
703    #[error("Token 签发失败:{0}")]
704    SignError(String),
705}
706
707impl KeyRotation {
708    /// 从环境变量创建密钥轮换管理器
709    ///
710    /// - `SZ300_JWT_SECRET`(必填):当前密钥
711    /// - `SZ300_JWT_ROTATION_INTERVAL`:轮换间隔秒数(默认 86400 = 24h)
712    /// - `SZ300_JWT_GRACE_PERIOD`:宽限期秒数(默认 3600 = 1h)
713    pub fn from_env() -> Result<Self, KeyRotationError> {
714        let current =
715            std::env::var("SZ300_JWT_SECRET").map_err(|_| KeyRotationError::SecretMissing)?;
716        if current.is_empty() {
717            return Err(KeyRotationError::SecretMissing);
718        }
719
720        let rotation_interval = std::env::var("SZ300_JWT_ROTATION_INTERVAL")
721            .ok()
722            .and_then(|s| s.parse().ok())
723            .map(Duration::from_secs)
724            .unwrap_or(Duration::from_secs(86400));
725
726        let grace_period = std::env::var("SZ300_JWT_GRACE_PERIOD")
727            .ok()
728            .and_then(|s| s.parse().ok())
729            .map(Duration::from_secs)
730            .unwrap_or(Duration::from_secs(3600));
731
732        Ok(Self {
733            current: RwLock::new(current),
734            previous: RwLock::new(Vec::new()),
735            rotation_interval,
736            grace_period,
737            max_previous: 3,
738        })
739    }
740
741    /// 直接构造(用于测试或显式配置)
742    pub fn new(
743        current: String,
744        rotation_interval: Duration,
745        grace_period: Duration,
746        max_previous: usize,
747    ) -> Self {
748        Self {
749            current: RwLock::new(current),
750            previous: RwLock::new(Vec::new()),
751            rotation_interval,
752            grace_period,
753            max_previous,
754        }
755    }
756
757    /// 用当前密钥签发 token
758    pub fn sign_token(
759        &self,
760        claims: &sz_rust_orm_facade::jwt::JwtClaims,
761    ) -> Result<String, KeyRotationError> {
762        let secret = self.current.read().clone();
763        let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&secret);
764        encoder
765            .encode(claims)
766            .map_err(|e| KeyRotationError::SignError(e.to_string()))
767    }
768
769    /// 验证 token:先尝试当前密钥,失败则遍历旧密钥(grace period 内)
770    pub fn verify_token(
771        &self,
772        token: &str,
773    ) -> Result<sz_rust_orm_facade::jwt::JwtClaims, KeyRotationError> {
774        // 先用当前密钥验证
775        let current_secret = self.current.read().clone();
776        let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&current_secret);
777        if let Ok(claims) = encoder.decode(token) {
778            return Ok(claims);
779        }
780
781        // 遍历旧密钥(grace period 内)
782        let now = std::time::Instant::now();
783        let previous = self.previous.read();
784        for (key, expires_at) in previous.iter() {
785            if now < *expires_at {
786                let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(key);
787                if let Ok(claims) = encoder.decode(token) {
788                    return Ok(claims);
789                }
790            }
791        }
792
793        Err(KeyRotationError::InvalidToken)
794    }
795
796    /// 启动密钥轮换定时任务
797    pub fn spawn_rotation_task(self: std::sync::Arc<Self>) -> tokio::task::JoinHandle<()> {
798        let interval = self.rotation_interval;
799        let grace_period = self.grace_period;
800        let max_previous = self.max_previous;
801
802        tokio::spawn(async move {
803            let mut ticker = tokio::time::interval(interval);
804            ticker.tick().await; // 跳过首次立即触发
805
806            loop {
807                ticker.tick().await;
808                if let Err(e) = Self::do_rotation(&self, grace_period, max_previous).await {
809                    tracing::error!("JWT_KEY_ROTATION_FAILED: {e}");
810                }
811            }
812        })
813    }
814
815    /// 执行一次密钥轮换
816    pub async fn do_rotation(
817        &self,
818        grace_period: Duration,
819        max_previous: usize,
820    ) -> Result<(), String> {
821        // 生成新密钥(随机 32 字节 hex)
822        let new_key = {
823            use rand::Rng;
824            let mut rng = rand::thread_rng();
825            let bytes: [u8; 32] = rng.gen();
826            hex::encode(bytes)
827        };
828
829        let now = std::time::Instant::now();
830        let expires_at = now + grace_period;
831
832        // 旧 current 移入 previous,新密钥成为 current
833        let old_current = {
834            let mut current = self.current.write();
835            let old = current.clone();
836            *current = new_key.clone();
837            old
838        };
839
840        {
841            let mut previous = self.previous.write();
842            previous.push((old_current.clone(), expires_at));
843            // 超出 max_previous 的旧密钥删除
844            while previous.len() > max_previous {
845                previous.remove(0);
846            }
847            // 删除已过期的旧密钥
848            previous.retain(|(_, exp)| now < *exp);
849        }
850
851        let old_fp = Self::fingerprint(&old_current);
852        let new_fp = Self::fingerprint(&new_key);
853        tracing::info!("JWT_KEY_ROTATED: old_fingerprint={old_fp}, new_fingerprint={new_fp}");
854
855        Ok(())
856    }
857
858    /// 计算密钥指纹(SHA256 前 8 位十六进制)
859    pub fn fingerprint(key: &str) -> String {
860        let mut hasher = Sha256::new();
861        hasher.update(key.as_bytes());
862        let result = hasher.finalize();
863        hex::encode(&result[..4])
864    }
865
866    /// 获取当前密钥指纹(用于审计日志)
867    pub fn current_fingerprint(&self) -> String {
868        let current = self.current.read();
869        Self::fingerprint(&current)
870    }
871}
872
873#[cfg(test)]
874mod tests {
875    use super::*;
876    use axum::body::Body;
877    use axum::http::{Method, Request, StatusCode};
878    use http_body_util::BodyExt;
879    use serde_json::json;
880    use tower::ServiceExt;
881
882    /// 测试用 Mock 控制器(仅使用默认实现)
883    struct MockController;
884
885    impl SzController for MockController {}
886
887    fn make_json_request(body: &str, query: Option<&str>) -> Request<Body> {
888        let uri = match query {
889            Some(q) => format!("/?{q}"),
890            None => "/".to_string(),
891        };
892        Request::builder()
893            .method(Method::POST)
894            .uri(&uri)
895            .header("content-type", "application/json")
896            .body(Body::from(body.to_string()))
897            .unwrap()
898    }
899
900    fn make_get_request(query: &str) -> Request<Body> {
901        Request::builder()
902            .method(Method::GET)
903            .uri(format!("/?{query}"))
904            .body(Body::empty())
905            .unwrap()
906    }
907
908    async fn collect_body(resp: Response) -> String {
909        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
910        String::from_utf8(bytes.to_vec()).unwrap()
911    }
912
913    // ====================================================================
914    // render_json 测试
915    // ====================================================================
916
917    #[test]
918    fn test_render_json_field_order() {
919        // 严格验证字段顺序:code → msg → data(对齐 PHP compact())
920        let ctrl = MockController;
921        let value = ctrl.render_json(1, "ok", json!({"id": 1}));
922        let obj = value.as_object().unwrap();
923        let keys: Vec<&String> = obj.keys().collect();
924        assert_eq!(keys, vec!["code", "msg", "data"]);
925    }
926
927    #[test]
928    fn test_render_json_default_values() {
929        // PHP 默认值:$code=1, $msg='', $data=[]
930        let ctrl = MockController;
931        let value = ctrl.render_json(1, "", Value::Object(Map::new()));
932        assert_eq!(value["code"], 1);
933        assert_eq!(value["msg"], "");
934        assert!(value["data"].is_object());
935        assert!(value["data"].as_object().unwrap().is_empty());
936    }
937
938    #[test]
939    fn test_render_json_custom_code() {
940        let ctrl = MockController;
941        let value = ctrl.render_json(-1, "未登录", json!({}));
942        assert_eq!(value["code"], -1);
943        assert_eq!(value["msg"], "未登录");
944    }
945
946    #[test]
947    fn test_render_json_compact_semantics() {
948        // 验证 PHP compact('code','msg','data') 的等价语义:
949        // 变量名 → 值的映射,保序序列化
950        let ctrl = MockController;
951        let value = ctrl.render_json(0, "失败", json!({"field": "name"}));
952        let json_str = value.to_string();
953        // 字段顺序必须是 code → msg → data
954        assert_eq!(
955            json_str,
956            r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#
957        );
958    }
959
960    #[test]
961    fn test_render_json_returns_value_not_response() {
962        // PHP renderJson 返回数组(非 Response),renderSuccess/renderError 才返回 json()
963        let ctrl = MockController;
964        let value = ctrl.render_json(1, "ok", json!({}));
965        // 应该是 Value,不是 Response
966        assert!(value.is_object());
967    }
968
969    // ====================================================================
970    // render_success 测试
971    // ====================================================================
972
973    #[test]
974    fn test_render_success_returns_response() {
975        let ctrl = MockController;
976        let resp = ctrl.render_success("success", json!({"id": 1}));
977        assert_eq!(resp.status(), StatusCode::OK);
978        assert_eq!(
979            resp.headers().get("content-type").unwrap(),
980            "application/json; charset=utf-8"
981        );
982    }
983
984    #[tokio::test]
985    async fn test_render_success_body_format() {
986        // 对齐 PHP renderSuccess('success', $data)
987        let ctrl = MockController;
988        let resp = ctrl.render_success("success", json!({"id": 1, "name": "alice"}));
989        let body = collect_body(resp).await;
990        assert_eq!(
991            body,
992            r#"{"code":1,"msg":"success","data":{"id":1,"name":"alice"}}"#
993        );
994    }
995
996    #[tokio::test]
997    async fn test_render_success_default_msg() {
998        // PHP 默认 msg='success'
999        let ctrl = MockController;
1000        let resp = ctrl.render_success("success", json!({}));
1001        let body = collect_body(resp).await;
1002        assert_eq!(body, r#"{"code":1,"msg":"success","data":{}}"#);
1003    }
1004
1005    #[tokio::test]
1006    async fn test_render_success_via_axum_router() {
1007        // 端到端验证:通过 axum Router 调用 render_success
1008        struct UserController;
1009        impl SzController for UserController {}
1010
1011        async fn handler() -> Response {
1012            let ctrl = UserController;
1013            ctrl.render_success("ok", json!({"id": 1}))
1014        }
1015
1016        let router = axum::Router::new().route("/", axum::routing::get(handler));
1017        let req = Request::builder()
1018            .method(Method::GET)
1019            .uri("/")
1020            .body(Body::empty())
1021            .unwrap();
1022        let resp = router.oneshot(req).await.unwrap();
1023
1024        assert_eq!(resp.status(), StatusCode::OK);
1025        let body = collect_body(resp).await;
1026        assert_eq!(body, r#"{"code":1,"msg":"ok","data":{"id":1}}"#);
1027    }
1028
1029    // ====================================================================
1030    // render_error 测试
1031    // ====================================================================
1032
1033    #[test]
1034    fn test_render_error_returns_response() {
1035        let ctrl = MockController;
1036        let resp = ctrl.render_error("error", json!({}), 0);
1037        assert_eq!(resp.status(), StatusCode::OK); // 业务错误 HTTP 仍 200
1038    }
1039
1040    #[tokio::test]
1041    async fn test_render_error_default_code() {
1042        // PHP 默认 $code=0
1043        let ctrl = MockController;
1044        let resp = ctrl.render_error("参数错误", json!({}), 0);
1045        let body = collect_body(resp).await;
1046        assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
1047    }
1048
1049    #[tokio::test]
1050    async fn test_render_error_custom_code() {
1051        // PHP renderError($msg, $data, $code=-1) 用于未登录场景
1052        let ctrl = MockController;
1053        let resp = ctrl.render_error("not_login", json!({}), -1);
1054        let body = collect_body(resp).await;
1055        assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1056    }
1057
1058    #[tokio::test]
1059    async fn test_render_error_with_data() {
1060        let ctrl = MockController;
1061        let resp = ctrl.render_error("失败", json!({"field": "name"}), 0);
1062        let body = collect_body(resp).await;
1063        assert_eq!(body, r#"{"code":0,"msg":"失败","data":{"field":"name"}}"#);
1064    }
1065
1066    #[tokio::test]
1067    async fn test_render_error_via_axum_router() {
1068        struct UserController;
1069        impl SzController for UserController {}
1070
1071        async fn handler() -> Response {
1072            let ctrl = UserController;
1073            ctrl.render_error("参数错误", json!({}), 0)
1074        }
1075
1076        let router = axum::Router::new().route("/", axum::routing::post(handler));
1077        let req = Request::builder()
1078            .method(Method::POST)
1079            .uri("/")
1080            .body(Body::empty())
1081            .unwrap();
1082        let resp = router.oneshot(req).await.unwrap();
1083
1084        assert_eq!(resp.status(), StatusCode::OK);
1085        let body = collect_body(resp).await;
1086        assert_eq!(body, r#"{"code":0,"msg":"参数错误","data":{}}"#);
1087    }
1088
1089    // ====================================================================
1090    // post_data 测试(对齐 PHP postData())
1091    // ====================================================================
1092
1093    #[tokio::test]
1094    async fn test_post_data_json_body() {
1095        let ctrl = MockController;
1096        let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1097        let data = ctrl.post_data(req).await.unwrap();
1098        assert_eq!(data["name"], "alice");
1099        assert_eq!(data["age"], 30);
1100    }
1101
1102    #[tokio::test]
1103    async fn test_post_data_query_only() {
1104        let ctrl = MockController;
1105        let req = make_json_request("", Some("page=1&size=10"));
1106        let data = ctrl.post_data(req).await.unwrap();
1107        assert_eq!(data["page"], "1");
1108        assert_eq!(data["size"], "10");
1109    }
1110
1111    #[tokio::test]
1112    async fn test_post_data_body_overrides_query() {
1113        // body 优先级高于 query(对齐 PHP param() 行为)
1114        let ctrl = MockController;
1115        let req = make_json_request(r#"{"page":99}"#, Some("page=1&size=10"));
1116        let data = ctrl.post_data(req).await.unwrap();
1117        assert_eq!(data["page"], 99);
1118        assert_eq!(data["size"], "10");
1119    }
1120
1121    #[tokio::test]
1122    async fn test_post_data_by_key_exists() {
1123        let ctrl = MockController;
1124        let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1125        let name = ctrl.post_data_by_key(req, "name").await.unwrap();
1126        assert_eq!(name, Some(json!("alice")));
1127    }
1128
1129    #[tokio::test]
1130    async fn test_post_data_by_key_missing() {
1131        let ctrl = MockController;
1132        let req = make_json_request(r#"{"name":"alice"}"#, None);
1133        let age = ctrl.post_data_by_key(req, "age").await.unwrap();
1134        assert_eq!(age, None);
1135    }
1136
1137    // ====================================================================
1138    // get_data 测试(对齐 PHP getData())
1139    // ====================================================================
1140
1141    #[test]
1142    fn test_get_data_query() {
1143        let ctrl = MockController;
1144        let req = make_get_request("page=1&size=10");
1145        let data = ctrl.get_data(&req);
1146        assert_eq!(data["page"], "1");
1147        assert_eq!(data["size"], "10");
1148    }
1149
1150    #[test]
1151    fn test_get_data_empty_query() {
1152        let ctrl = MockController;
1153        let req = make_get_request("");
1154        let data = ctrl.get_data(&req);
1155        assert!(data.as_object().unwrap().is_empty());
1156    }
1157
1158    #[test]
1159    fn test_get_data_by_key_exists() {
1160        let ctrl = MockController;
1161        let req = make_get_request("page=1&size=10");
1162        let page = ctrl.get_data_by_key(&req, "page");
1163        assert_eq!(page, Some(json!("1")));
1164    }
1165
1166    #[test]
1167    fn test_get_data_by_key_missing() {
1168        let ctrl = MockController;
1169        let req = make_get_request("page=1");
1170        let size = ctrl.get_data_by_key(&req, "size");
1171        assert_eq!(size, None);
1172    }
1173
1174    // ====================================================================
1175    // 控制器多实例隔离测试
1176    // ====================================================================
1177
1178    #[tokio::test]
1179    async fn test_multiple_controllers_independent() {
1180        // 验证多个控制器实例互不干扰(无状态 trait)
1181        struct CtrlA;
1182        struct CtrlB;
1183        impl SzController for CtrlA {}
1184        impl SzController for CtrlB {}
1185
1186        let a = CtrlA;
1187        let b = CtrlB;
1188
1189        let req_a = make_json_request(r#"{"k":"a"}"#, None);
1190        let req_b = make_json_request(r#"{"k":"b"}"#, None);
1191
1192        let data_a = a.post_data(req_a).await.unwrap();
1193        let data_b = b.post_data(req_b).await.unwrap();
1194
1195        assert_eq!(data_a["k"], "a");
1196        assert_eq!(data_b["k"], "b");
1197
1198        // 验证 render_json 互不影响
1199        let va = a.render_json(1, "a", json!({}));
1200        let vb = b.render_json(0, "b", json!({}));
1201        assert_eq!(va["code"], 1);
1202        assert_eq!(va["msg"], "a");
1203        assert_eq!(vb["code"], 0);
1204        assert_eq!(vb["msg"], "b");
1205    }
1206
1207    // ====================================================================
1208    // PHP 一致性综合测试
1209    // ====================================================================
1210
1211    #[tokio::test]
1212    async fn test_php_consistency_full_flow() {
1213        // 模拟 PHP 控制器典型流程:读取 postData → 业务处理 → renderSuccess
1214        struct OrderController;
1215        impl SzController for OrderController {}
1216
1217        let ctrl = OrderController;
1218
1219        // 1. 读取 POST 数据
1220        let req = make_json_request(r#"{"order_id":12345,"amount":99.5}"#, None);
1221        let data = ctrl.post_data(req).await.unwrap();
1222        let order_id = data["order_id"].as_i64().unwrap();
1223        let amount = data["amount"].as_f64().unwrap();
1224
1225        // 2. 业务处理(模拟)
1226        let result = json!({
1227            "order_id": order_id,
1228            "amount": amount,
1229            "status": "paid"
1230        });
1231
1232        // 3. 返回成功响应
1233        let resp = ctrl.render_success("支付成功", result);
1234        let body = collect_body(resp).await;
1235
1236        // 验证字段顺序与 PHP compact() 一致
1237        assert_eq!(
1238            body,
1239            r#"{"code":1,"msg":"支付成功","data":{"order_id":12345,"amount":99.5,"status":"paid"}}"#
1240        );
1241    }
1242
1243    #[tokio::test]
1244    async fn test_php_consistency_error_flow() {
1245        // 模拟 PHP 控制器错误流程:校验失败 → renderError
1246        struct UserController;
1247        impl SzController for UserController {}
1248
1249        let ctrl = UserController;
1250
1251        let req = make_json_request(r#"{"name":""}"#, None);
1252        let data = ctrl.post_data(req).await.unwrap();
1253        let name = data["name"].as_str().unwrap();
1254
1255        if name.is_empty() {
1256            let resp = ctrl.render_error("用户名不能为空", json!({"field": "name"}), 0);
1257            let body = collect_body(resp).await;
1258            assert_eq!(
1259                body,
1260                r#"{"code":0,"msg":"用户名不能为空","data":{"field":"name"}}"#
1261            );
1262        } else {
1263            panic!("should be empty");
1264        }
1265    }
1266
1267    #[tokio::test]
1268    async fn test_php_consistency_not_login_flow() {
1269        // 模拟 PHP 控制器未登录流程:renderError($msg, $data, $code=-1)
1270        struct PassportController;
1271        impl SzController for PassportController {}
1272
1273        let ctrl = PassportController;
1274        let resp = ctrl.render_error("not_login", json!({}), -1);
1275        let body = collect_body(resp).await;
1276        assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
1277    }
1278
1279    // ====================================================================
1280    // BaseController trait 测试
1281    // ====================================================================
1282
1283    /// 实现 BaseController 的 Mock 控制器(同时自动实现 SzController)
1284    struct MockBaseController;
1285
1286    impl SzController for MockBaseController {}
1287    impl BaseController for MockBaseController {}
1288
1289    #[test]
1290    fn test_base_controller_default_batch_validate() {
1291        // PHP 默认 $batchValidate = false
1292        let ctrl = MockBaseController;
1293        assert!(!ctrl.batch_validate());
1294    }
1295
1296    #[test]
1297    fn test_base_controller_default_middlewares_empty() {
1298        // PHP 默认 $middleware = []
1299        let ctrl = MockBaseController;
1300        assert!(ctrl.middlewares().is_empty());
1301    }
1302
1303    #[test]
1304    fn test_base_controller_default_initialize_no_panic() {
1305        // PHP 默认 initialize() 为空,调用应无副作用
1306        let ctrl = MockBaseController;
1307        ctrl.initialize(); // 不应 panic
1308    }
1309
1310    #[test]
1311    fn test_base_controller_default_validate_returns_ok() {
1312        // require 规则 + 字段存在 → 应通过
1313        let ctrl = MockBaseController;
1314        let data = json!({"name": "alice"});
1315        let rules = [("name", "require")];
1316        let messages: [(&str, &str); 0] = [];
1317        let result = ctrl.validate(&data, &rules, &messages);
1318        assert!(result.is_ok());
1319    }
1320
1321    #[test]
1322    fn test_validate_require_pass_with_value() {
1323        // require 规则:字段存在且非空 → 通过
1324        let ctrl = MockBaseController;
1325        let data = json!({"name": "alice", "age": 30});
1326        let rules = [("name", "require"), ("age", "require|integer")];
1327        let messages: [(&str, &str); 0] = [];
1328        assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1329    }
1330
1331    #[test]
1332    fn test_validate_require_fail_when_missing() {
1333        // require 规则:字段缺失 → 失败(Single 模式,返回第一条错误)
1334        let ctrl = MockBaseController;
1335        let data = json!({"name": "alice"});
1336        let rules = [("name", "require"), ("age", "require|integer")];
1337        let messages: [(&str, &str); 0] = [];
1338        let result = ctrl.validate(&data, &rules, &messages);
1339        assert!(result.is_err());
1340        let err = result.unwrap_err();
1341        // 错误信息应包含 age 字段(require 验证失败)
1342        assert!(err.contains("age"), "error: {err}");
1343    }
1344
1345    #[test]
1346    fn test_validate_integer_fail_on_string() {
1347        // integer 规则:字段非整数 → 失败
1348        let ctrl = MockBaseController;
1349        let data = json!({"age": "not-a-number"});
1350        let rules = [("age", "require|integer")];
1351        let messages: [(&str, &str); 0] = [];
1352        let result = ctrl.validate(&data, &rules, &messages);
1353        assert!(result.is_err());
1354    }
1355
1356    #[test]
1357    fn test_validate_custom_message_applied() {
1358        // 自定义消息应被使用(对齐 PHP message[field.type])
1359        let ctrl = MockBaseController;
1360        let data = json!({}); // name 缺失
1361        let rules = [("name", "require")];
1362        let messages = [("name.require", "名称必填")];
1363        let result = ctrl.validate(&data, &rules, &messages);
1364        assert!(result.is_err());
1365        assert_eq!(result.unwrap_err(), "名称必填");
1366    }
1367
1368    #[test]
1369    fn test_validate_batch_mode_returns_multiple_errors() {
1370        // 批量模式:收集所有错误,以 "; " 分隔
1371        struct BatchController;
1372        impl SzController for BatchController {}
1373        impl BaseController for BatchController {
1374            fn batch_validate(&self) -> bool {
1375                true
1376            }
1377        }
1378
1379        let ctrl = BatchController;
1380        let data = json!({}); // name 和 age 都缺失
1381        let rules = [("name", "require"), ("age", "require")];
1382        let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1383        let result = ctrl.validate(&data, &rules, &messages);
1384        assert!(result.is_err());
1385        let err = result.unwrap_err();
1386        // 两条错误都应出现(顺序按字段注册顺序)
1387        assert!(err.contains("名称必填"), "err: {err}");
1388        assert!(err.contains("年龄必填"), "err: {err}");
1389        // 应以 "; " 分隔(ValidateError::Batch 的 Display 格式)
1390        assert!(err.contains("; "), "err: {err}");
1391    }
1392
1393    #[test]
1394    fn test_validate_single_mode_returns_first_error_only() {
1395        // 非批量模式:仅返回第一条错误
1396        let ctrl = MockBaseController;
1397        let data = json!({}); // name 和 age 都缺失
1398        let rules = [("name", "require"), ("age", "require")];
1399        let messages = [("name.require", "名称必填"), ("age.require", "年龄必填")];
1400        let result = ctrl.validate(&data, &rules, &messages);
1401        assert!(result.is_err());
1402        let err = result.unwrap_err();
1403        // 仅包含第一条错误,不包含第二条
1404        assert!(err.contains("名称必填"), "err: {err}");
1405        assert!(!err.contains("年龄必填"), "err: {err}");
1406    }
1407
1408    #[test]
1409    fn test_validate_in_rule_pass() {
1410        // in 规则:值在列表中 → 通过
1411        let ctrl = MockBaseController;
1412        let data = json!({"status": "active"});
1413        let rules = [("status", "require|in:active,inactive")];
1414        let messages: [(&str, &str); 0] = [];
1415        assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1416    }
1417
1418    #[test]
1419    fn test_validate_in_rule_fail() {
1420        // in 规则:值不在列表中 → 失败
1421        let ctrl = MockBaseController;
1422        let data = json!({"status": "deleted"});
1423        let rules = [("status", "require|in:active,inactive")];
1424        let messages: [(&str, &str); 0] = [];
1425        assert!(ctrl.validate(&data, &rules, &messages).is_err());
1426    }
1427
1428    #[test]
1429    fn test_validate_empty_rules_always_pass() {
1430        // 空规则列表:无验证规则,总应通过
1431        let ctrl = MockBaseController;
1432        let data = json!({"anything": "value"});
1433        let rules: [(&str, &str); 0] = [];
1434        let messages: [(&str, &str); 0] = [];
1435        assert!(ctrl.validate(&data, &rules, &messages).is_ok());
1436    }
1437
1438    #[test]
1439    fn test_base_controller_inherits_sz_controller_methods() {
1440        // BaseController: SzController,自动获得 render_json 等方法
1441        let ctrl = MockBaseController;
1442        let value = ctrl.render_json(1, "ok", json!({}));
1443        assert_eq!(value["code"], 1);
1444        assert_eq!(value["msg"], "ok");
1445
1446        let resp = ctrl.render_success("ok", json!({"id": 1}));
1447        assert_eq!(resp.status(), StatusCode::OK);
1448    }
1449
1450    /// 覆盖默认值的子类控制器
1451    struct CustomController;
1452
1453    impl SzController for CustomController {}
1454    impl BaseController for CustomController {
1455        fn batch_validate(&self) -> bool {
1456            true
1457        }
1458
1459        fn middlewares(&self) -> Vec<String> {
1460            vec!["auth".to_string(), "cors".to_string()]
1461        }
1462
1463        fn initialize(&self) {
1464            // 子类自定义初始化(无状态,仅作演示)
1465        }
1466    }
1467
1468    #[test]
1469    fn test_base_controller_override_batch_validate() {
1470        let ctrl = CustomController;
1471        assert!(ctrl.batch_validate());
1472    }
1473
1474    #[test]
1475    fn test_base_controller_override_middlewares() {
1476        let ctrl = CustomController;
1477        let mws = ctrl.middlewares();
1478        assert_eq!(mws, vec!["auth", "cors"]);
1479    }
1480
1481    #[test]
1482    fn test_base_controller_override_initialize() {
1483        let ctrl = CustomController;
1484        ctrl.initialize(); // 不应 panic
1485    }
1486
1487    /// 模拟 PHP 业务控制器:带状态的 initialize 钩子
1488    /// PHP 中 $this->batchValidate = true; 在 initialize() 中设置
1489    struct StatefulController {
1490        initialized: parking_lot::Mutex<bool>,
1491        custom_batch: bool,
1492    }
1493
1494    impl StatefulController {
1495        fn new() -> Self {
1496            Self {
1497                initialized: parking_lot::Mutex::new(false),
1498                custom_batch: false,
1499            }
1500        }
1501    }
1502
1503    impl SzController for StatefulController {}
1504    impl BaseController for StatefulController {
1505        fn batch_validate(&self) -> bool {
1506            self.custom_batch
1507        }
1508
1509        fn initialize(&self) {
1510            *self.initialized.lock() = true;
1511            // 模拟 PHP: $this->batchValidate = true;
1512            // Rust 中由于 trait 方法不能修改 self,需要在调用方处理
1513        }
1514    }
1515
1516    #[test]
1517    fn test_base_controller_stateful_initialize() {
1518        let ctrl = StatefulController::new();
1519        assert!(!*ctrl.initialized.lock()); // 初始未初始化
1520        ctrl.initialize(); // 调用初始化钩子
1521        assert!(*ctrl.initialized.lock()); // 已初始化
1522    }
1523
1524    /// 模拟 PHP 控制器典型流程:initialize → validate → renderSuccess
1525    #[tokio::test]
1526    async fn test_base_controller_php_full_flow() {
1527        struct UserController;
1528        impl SzController for UserController {}
1529        impl BaseController for UserController {}
1530
1531        let ctrl = UserController;
1532
1533        // 1. 初始化钩子
1534        ctrl.initialize();
1535
1536        // 2. 读取 POST 数据
1537        let req = make_json_request(r#"{"name":"alice","age":30}"#, None);
1538        let data = ctrl.post_data(req).await.unwrap();
1539
1540        // 3. 验证(validate() 已实现 require/integer/gt 等规则)
1541        let rules = [("name", "require"), ("age", "require|integer|gt:0")];
1542        let messages: [(&str, &str); 0] = [];
1543        ctrl.validate(&data, &rules, &messages).unwrap();
1544
1545        // 4. 返回成功响应
1546        let resp =
1547            ctrl.render_success("success", json!({"name": data["name"], "age": data["age"]}));
1548        let body = collect_body(resp).await;
1549        assert_eq!(
1550            body,
1551            r#"{"code":1,"msg":"success","data":{"name":"alice","age":30}}"#
1552        );
1553    }
1554
1555    /// 测试多控制器继承链独立性
1556    #[test]
1557    fn test_base_controller_multiple_instances_independent() {
1558        struct ControllerA;
1559        struct ControllerB;
1560
1561        impl SzController for ControllerA {}
1562        impl BaseController for ControllerA {
1563            fn middlewares(&self) -> Vec<String> {
1564                vec!["auth".to_string()]
1565            }
1566        }
1567
1568        impl SzController for ControllerB {}
1569        impl BaseController for ControllerB {
1570            fn middlewares(&self) -> Vec<String> {
1571                vec!["cors".to_string(), "log".to_string()]
1572            }
1573        }
1574
1575        let a = ControllerA;
1576        let b = ControllerB;
1577
1578        assert_eq!(a.middlewares(), vec!["auth"]);
1579        assert_eq!(b.middlewares(), vec!["cors", "log"]);
1580
1581        // 默认 batch_validate 不互相影响
1582        assert!(!a.batch_validate());
1583        assert!(!b.batch_validate());
1584    }
1585
1586    /// PHP 继承链验证:BaseController → SzController 方法可用性
1587    #[test]
1588    fn test_base_controller_inheritance_chain() {
1589        // 业务控制器实现 BaseController,自动获得 SzController 的所有方法
1590        struct BusinessController;
1591        impl SzController for BusinessController {}
1592        impl BaseController for BusinessController {}
1593
1594        let ctrl = BusinessController;
1595
1596        // SzController 方法(来自父 trait)
1597        let value = ctrl.render_json(0, "error", json!({}));
1598        assert!(value.is_object());
1599
1600        // BaseController 方法
1601        assert!(!ctrl.batch_validate());
1602        assert!(ctrl.middlewares().is_empty());
1603        ctrl.initialize();
1604    }
1605
1606    // ====================================================================
1607    // AddonsBaseController trait 测试
1608    // ====================================================================
1609
1610    /// 实现 AddonsBaseController 的 Mock 控制器(同时自动实现 BaseController + SzController)
1611    struct MockAddonsController;
1612
1613    impl SzController for MockAddonsController {}
1614    impl BaseController for MockAddonsController {}
1615    impl AddonsBaseController for MockAddonsController {}
1616
1617    #[test]
1618    fn test_addons_default_allow_all_action() {
1619        // PHP 默认 $allowAllAction = ['/passport/login', '/task/task/userClerk']
1620        let ctrl = MockAddonsController;
1621        let allow = ctrl.allow_all_action();
1622        assert!(allow.contains(&"/passport/login"));
1623        assert!(allow.contains(&"/task/task/userClerk"));
1624        assert_eq!(allow.len(), 2);
1625    }
1626
1627    #[test]
1628    fn test_addons_parse_route_info_two_segments() {
1629        // /passport/login → controller=passport, action=login, group=passport
1630        let ctrl = MockAddonsController;
1631        let info = ctrl.parse_route_info("/passport/login");
1632        assert_eq!(info.controller, "passport");
1633        assert_eq!(info.action, "login");
1634        assert_eq!(info.group, "passport");
1635        assert_eq!(info.route_uri, "/passport/login");
1636    }
1637
1638    #[test]
1639    fn test_addons_parse_route_info_three_segments() {
1640        // /task/task/userClerk → controller=task/task, action=userClerk
1641        // PHP bug 复刻:group=controller=task/task(详见 parse_route_info 文档注释)
1642        let ctrl = MockAddonsController;
1643        let info = ctrl.parse_route_info("/task/task/userClerk");
1644        assert_eq!(info.controller, "task/task");
1645        assert_eq!(info.action, "userClerk");
1646        assert_eq!(info.group, "task/task"); // PHP bug: group === controller
1647        assert_eq!(info.route_uri, "/task/task/userClerk");
1648    }
1649
1650    #[test]
1651    fn test_addons_parse_route_info_single_segment() {
1652        // /passport → controller=passport, action="", group=passport
1653        let ctrl = MockAddonsController;
1654        let info = ctrl.parse_route_info("/passport");
1655        assert_eq!(info.controller, "passport");
1656        assert_eq!(info.action, "");
1657        assert_eq!(info.group, "passport");
1658        assert_eq!(info.route_uri, "/passport/");
1659    }
1660
1661    #[test]
1662    fn test_addons_parse_route_info_root() {
1663        // / → 所有字段为空,route_uri="/"
1664        let ctrl = MockAddonsController;
1665        let info = ctrl.parse_route_info("/");
1666        assert_eq!(info.controller, "");
1667        assert_eq!(info.action, "");
1668        assert_eq!(info.group, "");
1669        assert_eq!(info.route_uri, "/");
1670    }
1671
1672    #[test]
1673    fn test_addons_parse_route_info_empty_uri() {
1674        let ctrl = MockAddonsController;
1675        let info = ctrl.parse_route_info("");
1676        assert_eq!(info.controller, "");
1677        assert_eq!(info.route_uri, "/");
1678    }
1679
1680    #[test]
1681    fn test_addons_parse_route_info_with_query_string() {
1682        // 含 query string 的 URI 应被剥离
1683        let ctrl = MockAddonsController;
1684        let info = ctrl.parse_route_info("/user/info?foo=bar&baz=1");
1685        assert_eq!(info.controller, "user");
1686        assert_eq!(info.action, "info");
1687        assert_eq!(info.route_uri, "/user/info");
1688    }
1689
1690    #[test]
1691    fn test_addons_parse_route_info_trailing_slash() {
1692        // 末尾斜杠
1693        let ctrl = MockAddonsController;
1694        let info = ctrl.parse_route_info("/user/info/");
1695        // split 会产生空字符串末尾段
1696        assert_eq!(info.controller, "user/info");
1697        assert_eq!(info.action, "");
1698        assert_eq!(info.route_uri, "/user/info/");
1699    }
1700
1701    #[test]
1702    fn test_addons_check_login_whitelist_pass() {
1703        // /passport/login 在白名单中,应通过
1704        let ctrl = MockAddonsController;
1705        let result = ctrl.check_login("/passport/login", false);
1706        assert!(result.is_ok());
1707    }
1708
1709    #[test]
1710    fn test_addons_check_login_whitelist_pass_2() {
1711        // /task/task/userClerk 在白名单中,应通过
1712        let ctrl = MockAddonsController;
1713        let result = ctrl.check_login("/task/task/userClerk", false);
1714        assert!(result.is_ok());
1715    }
1716
1717    #[test]
1718    fn test_addons_check_login_logged_in_pass() {
1719        // 不在白名单但已登录,应通过
1720        let ctrl = MockAddonsController;
1721        let result = ctrl.check_login("/user/info", true);
1722        assert!(result.is_ok());
1723    }
1724
1725    #[test]
1726    fn test_addons_check_login_not_logged_in_fail() {
1727        // 不在白名单且未登录,应返回 not_login
1728        let ctrl = MockAddonsController;
1729        let result = ctrl.check_login("/user/info", false);
1730        assert!(result.is_err());
1731        assert_eq!(result.unwrap_err(), "not_login");
1732    }
1733
1734    #[test]
1735    fn test_addons_get_token_default_returns_none() {
1736        // 未配置 SZ_JWT_SECRET 环境变量时,JWT_CONFIG 为 None,验证跳过返回 Ok(None)
1737        // 注:生产环境通过 `validate_jwt_config()` 在启动时校验,测试环境允许跳过
1738        let ctrl = MockAddonsController;
1739        let result = ctrl.get_token(Some("Bearer xxx.yyy.zzz"));
1740        assert!(result.is_ok());
1741        assert_eq!(result.unwrap(), None);
1742    }
1743
1744    #[test]
1745    fn test_addons_get_token_no_authorization() {
1746        let ctrl = MockAddonsController;
1747        let result = ctrl.get_token(None);
1748        assert!(result.is_ok());
1749        assert_eq!(result.unwrap(), None);
1750    }
1751
1752    #[test]
1753    fn test_addons_get_token_empty_authorization() {
1754        let ctrl = MockAddonsController;
1755        let result = ctrl.get_token(Some(""));
1756        assert!(result.is_ok());
1757        assert_eq!(result.unwrap(), None);
1758    }
1759
1760    #[test]
1761    fn test_addons_get_token_invalid_format_returns_none() {
1762        // 非法 JWT 格式(非三段式)应返回 Ok(None) 而非 Err
1763        // 注:仅在 SZ_JWT_SECRET 已设置时才会进入格式校验
1764        let ctrl = MockAddonsController;
1765        let result = ctrl.get_token(Some("Bearer not.a.valid.jwt.token"));
1766        assert!(result.is_ok());
1767        // 无论是否配置密钥,无效 token 都应返回 None
1768        assert_eq!(result.unwrap(), None);
1769    }
1770
1771    #[test]
1772    fn test_strip_bearer_prefix_uppercase() {
1773        assert_eq!(strip_bearer_prefix("Bearer abc.def.ghi"), "abc.def.ghi");
1774    }
1775
1776    #[test]
1777    fn test_strip_bearer_prefix_lowercase() {
1778        assert_eq!(strip_bearer_prefix("bearer abc.def.ghi"), "abc.def.ghi");
1779    }
1780
1781    #[test]
1782    fn test_strip_bearer_prefix_mixed_case() {
1783        assert_eq!(strip_bearer_prefix("BEARER abc.def.ghi"), "abc.def.ghi");
1784    }
1785
1786    #[test]
1787    fn test_strip_bearer_prefix_no_prefix() {
1788        // 无 Bearer 前缀时,应保持原样(去除首尾空格)
1789        assert_eq!(strip_bearer_prefix("abc.def.ghi"), "abc.def.ghi");
1790    }
1791
1792    #[test]
1793    fn test_strip_bearer_prefix_empty() {
1794        assert_eq!(strip_bearer_prefix(""), "");
1795    }
1796
1797    #[test]
1798    fn test_strip_bearer_prefix_with_extra_spaces() {
1799        assert_eq!(
1800            strip_bearer_prefix("  Bearer   abc.def.ghi  "),
1801            "abc.def.ghi"
1802        );
1803    }
1804
1805    /// JWT 端到端验证测试:使用真实 JwtEncoder 签发 token,再通过 verify_token_with_config 验证
1806    ///
1807    /// 通过 verify_token_with_config 注入测试配置,避免依赖全局环境变量,
1808    /// 确保 CI 环境也能完整执行 JWT 验证流程测试。
1809    #[test]
1810    fn test_get_token_valid_jwt_returns_user_info() {
1811        let config = JwtConfig {
1812            secret: "test-secret".to_string(),
1813            issuer: String::new(), // 不验证 iss,
1814            audience: String::new(),
1815        };
1816
1817        // 签发一个有效 token
1818        let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1819        let exp = std::time::SystemTime::now()
1820            .duration_since(std::time::UNIX_EPOCH)
1821            .unwrap()
1822            .as_secs() as i64
1823            + 3600; // 1 小时后过期
1824        let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1825        let token = encoder.encode(&claims).unwrap();
1826
1827        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1828        assert!(result.is_ok());
1829        let user = result.unwrap();
1830        assert!(user.is_some());
1831        let user = user.unwrap();
1832        assert_eq!(user.user_id, 12345);
1833        assert!(user.is_login);
1834    }
1835
1836    /// 测试 JWT 签名密钥错误时返回 None
1837    #[test]
1838    fn test_get_token_wrong_secret_returns_none() {
1839        let config = JwtConfig {
1840            secret: "correct-secret".to_string(),
1841            issuer: String::new(),
1842            audience: String::new(),
1843        };
1844
1845        // 用错误密钥签发 token
1846        let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new("wrong-secret");
1847        let exp = std::time::SystemTime::now()
1848            .duration_since(std::time::UNIX_EPOCH)
1849            .unwrap()
1850            .as_secs() as i64
1851            + 3600;
1852        let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1853        let token = encoder.encode(&claims).unwrap();
1854
1855        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1856        assert!(result.is_ok());
1857        assert_eq!(result.unwrap(), None); // 签名验证失败
1858    }
1859
1860    /// 测试 JWT 过期时返回 None
1861    #[test]
1862    fn test_get_token_expired_returns_none() {
1863        let config = JwtConfig {
1864            secret: "test-secret".to_string(),
1865            issuer: String::new(),
1866            audience: String::new(),
1867        };
1868
1869        let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1870        // 过期时间为 1 小时前
1871        let exp = std::time::SystemTime::now()
1872            .duration_since(std::time::UNIX_EPOCH)
1873            .unwrap()
1874            .as_secs() as i64
1875            - 3600;
1876        let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(12345);
1877        let token = encoder.encode(&claims).unwrap();
1878
1879        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1880        assert!(result.is_ok());
1881        assert_eq!(result.unwrap(), None); // 已过期
1882    }
1883
1884    /// 测试 JWT 缺少 user_id claim 时返回 None(兼容旧版 token)
1885    #[test]
1886    fn test_get_token_no_user_id_claim_returns_none() {
1887        let config = JwtConfig {
1888            secret: "test-secret".to_string(),
1889            issuer: String::new(),
1890            audience: String::new(),
1891        };
1892
1893        let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1894        let exp = std::time::SystemTime::now()
1895            .duration_since(std::time::UNIX_EPOCH)
1896            .unwrap()
1897            .as_secs() as i64
1898            + 3600;
1899        // 不调用 with_user_id,user_id 为 None
1900        let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp);
1901        let token = encoder.encode(&claims).unwrap();
1902
1903        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1904        assert!(result.is_ok());
1905        assert_eq!(result.unwrap(), None); // 缺少 user_id
1906    }
1907
1908    /// 测试 iss 验证:iss 不匹配时返回 None
1909    #[test]
1910    fn test_get_token_iss_mismatch_returns_none() {
1911        let config = JwtConfig {
1912            secret: "test-secret".to_string(),
1913            issuer: "https://expected-issuer.com".to_string(),
1914            audience: String::new(),
1915        };
1916
1917        let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1918        let exp = std::time::SystemTime::now()
1919            .duration_since(std::time::UNIX_EPOCH)
1920            .unwrap()
1921            .as_secs() as i64
1922            + 3600;
1923        // 使用错误的 issuer 签发
1924        let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp)
1925            .with_issuer("https://wrong-issuer.com")
1926            .with_user_id(12345);
1927        let token = encoder.encode(&claims).unwrap();
1928
1929        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1930        assert!(result.is_ok());
1931        assert_eq!(result.unwrap(), None); // iss 不匹配
1932    }
1933
1934    /// 测试 iss 验证:iss 匹配时返回 UserInfo
1935    #[test]
1936    fn test_get_token_iss_match_returns_user_info() {
1937        let config = JwtConfig {
1938            secret: "test-secret".to_string(),
1939            issuer: "https://mall.ljclz.shop".to_string(),
1940            audience: String::new(),
1941        };
1942
1943        let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1944        let exp = std::time::SystemTime::now()
1945            .duration_since(std::time::UNIX_EPOCH)
1946            .unwrap()
1947            .as_secs() as i64
1948            + 3600;
1949        let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp)
1950            .with_issuer(&config.issuer)
1951            .with_user_id(67890);
1952        let token = encoder.encode(&claims).unwrap();
1953
1954        let result = verify_token_with_config(Some(&format!("Bearer {token}")), &config);
1955        assert!(result.is_ok());
1956        let user = result.unwrap().unwrap();
1957        assert_eq!(user.user_id, 67890);
1958        assert!(user.is_login);
1959    }
1960
1961    /// 测试密钥未配置时返回 None(禁用 JWT 验证)
1962    #[test]
1963    fn test_get_token_empty_secret_returns_none() {
1964        let config = JwtConfig::default(); // secret 为空
1965
1966        let result = verify_token_with_config(Some("Bearer any.token.here"), &config);
1967        assert!(result.is_ok());
1968        assert_eq!(result.unwrap(), None); // 密钥未配置
1969    }
1970
1971    /// 测试无 Bearer 前缀的 token 也能正常解析
1972    #[test]
1973    fn test_get_token_without_bearer_prefix() {
1974        let config = JwtConfig {
1975            secret: "test-secret".to_string(),
1976            issuer: String::new(),
1977            audience: String::new(),
1978        };
1979
1980        let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
1981        let exp = std::time::SystemTime::now()
1982            .duration_since(std::time::UNIX_EPOCH)
1983            .unwrap()
1984            .as_secs() as i64
1985            + 3600;
1986        let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(99999);
1987        let token = encoder.encode(&claims).unwrap();
1988
1989        // 不带 Bearer 前缀,应仍能解析(对齐 PHP str_ireplace 兼容行为)
1990        let result = verify_token_with_config(Some(&token), &config);
1991        assert!(result.is_ok());
1992        let user = result.unwrap().unwrap();
1993        assert_eq!(user.user_id, 99999);
1994    }
1995
1996    /// 覆盖白名单的子类控制器
1997    struct CustomAddonsController;
1998
1999    impl SzController for CustomAddonsController {}
2000    impl BaseController for CustomAddonsController {}
2001    impl AddonsBaseController for CustomAddonsController {
2002        fn allow_all_action(&self) -> Vec<&'static str> {
2003            vec!["/custom/public", "/custom/health"]
2004        }
2005    }
2006
2007    #[test]
2008    fn test_addons_override_allow_all_action() {
2009        let ctrl = CustomAddonsController;
2010        let allow = ctrl.allow_all_action();
2011        assert_eq!(allow, vec!["/custom/public", "/custom/health"]);
2012        // 不应包含默认白名单
2013        assert!(!allow.contains(&"/passport/login"));
2014    }
2015
2016    #[test]
2017    fn test_addons_override_check_login_uses_new_whitelist() {
2018        let ctrl = CustomAddonsController;
2019        // /custom/public 在新白名单中
2020        assert!(ctrl.check_login("/custom/public", false).is_ok());
2021        // /passport/login 不在新白名单中(被覆盖)
2022        assert!(ctrl.check_login("/passport/login", false).is_err());
2023    }
2024
2025    /// 模拟 PHP addons 控制器典型流程:parse_route_info → get_token → check_login → renderSuccess
2026    #[tokio::test]
2027    async fn test_addons_php_full_flow_whitelist() {
2028        struct PassportController;
2029        impl SzController for PassportController {}
2030        impl BaseController for PassportController {}
2031        impl AddonsBaseController for PassportController {}
2032
2033        let ctrl = PassportController;
2034
2035        // 1. 解析路由信息
2036        let info = ctrl.parse_route_info("/passport/login");
2037        assert_eq!(info.route_uri, "/passport/login");
2038
2039        // 2. 获取 token(占位返回 None)
2040        let user = ctrl.get_token(None).unwrap();
2041
2042        // 3. 检查登录(白名单通过,user_is_login 不影响)
2043        let is_login = user.as_ref().is_some_and(|u| u.is_login);
2044        ctrl.check_login(&info.route_uri, is_login).unwrap();
2045
2046        // 4. 返回成功响应(模拟登录成功)
2047        let resp = ctrl.render_success("登录成功", json!({"token": "fake.jwt.token"}));
2048        let body = collect_body(resp).await;
2049        assert_eq!(
2050            body,
2051            r#"{"code":1,"msg":"登录成功","data":{"token":"fake.jwt.token"}}"#
2052        );
2053    }
2054
2055    #[tokio::test]
2056    async fn test_addons_php_full_flow_not_login() {
2057        struct UserController;
2058        impl SzController for UserController {}
2059        impl BaseController for UserController {}
2060        impl AddonsBaseController for UserController {}
2061
2062        let ctrl = UserController;
2063
2064        // 1. 解析路由信息
2065        let info = ctrl.parse_route_info("/user/info");
2066
2067        // 2. 获取 token(占位返回 None,即未登录)
2068        let user = ctrl.get_token(None).unwrap();
2069        let is_login = user.as_ref().is_some_and(|u| u.is_login);
2070
2071        // 3. 检查登录(白名单不通过,且未登录)
2072        let result = ctrl.check_login(&info.route_uri, is_login);
2073        assert!(result.is_err());
2074        assert_eq!(result.unwrap_err(), "not_login");
2075
2076        // 4. 返回未登录响应(模拟 PHP BaseException ['code' => -1, 'msg' => 'not_login'])
2077        let resp = ctrl.render_error("not_login", json!({}), -1);
2078        let body = collect_body(resp).await;
2079        assert_eq!(body, r#"{"code":-1,"msg":"not_login","data":{}}"#);
2080    }
2081
2082    #[tokio::test]
2083    async fn test_addons_php_full_flow_logged_in() {
2084        struct OrderController;
2085        impl SzController for OrderController {}
2086        impl BaseController for OrderController {}
2087        impl AddonsBaseController for OrderController {
2088            // 模拟已登录用户(覆盖 get_token 返回 UserInfo)
2089            fn get_token(&self, _authorization: Option<&str>) -> Result<Option<UserInfo>, String> {
2090                Ok(Some(UserInfo {
2091                    user_id: 12345,
2092                    is_login: true,
2093                }))
2094            }
2095        }
2096
2097        let ctrl = OrderController;
2098
2099        // 1. 解析路由信息
2100        let info = ctrl.parse_route_info("/order/list");
2101
2102        // 2. 获取 token(返回已登录用户)
2103        let user = ctrl.get_token(None).unwrap();
2104        let is_login = user.as_ref().is_some_and(|u| u.is_login);
2105
2106        // 3. 检查登录(白名单不通过,但已登录)
2107        ctrl.check_login(&info.route_uri, is_login).unwrap();
2108
2109        // 4. 返回订单列表
2110        let resp = ctrl.render_success(
2111            "success",
2112            json!({"list": [{"id": 1}, {"id": 2}], "total": 2}),
2113        );
2114        let body = collect_body(resp).await;
2115        assert_eq!(
2116            body,
2117            r#"{"code":1,"msg":"success","data":{"list":[{"id":1},{"id":2}],"total":2}}"#
2118        );
2119    }
2120
2121    /// 测试 RouteInfo / UserInfo 类型
2122    #[test]
2123    fn test_route_info_debug_clone_eq() {
2124        let info1 = RouteInfo {
2125            controller: "user".to_string(),
2126            action: "info".to_string(),
2127            route_uri: "/user/info".to_string(),
2128            group: "user".to_string(),
2129        };
2130        let info2 = info1.clone();
2131        assert_eq!(info1, info2);
2132        let debug_str = format!("{info1:?}");
2133        assert!(debug_str.contains("RouteInfo"));
2134        assert!(debug_str.contains("user"));
2135    }
2136
2137    #[test]
2138    fn test_user_info_debug_clone_eq() {
2139        let user1 = UserInfo {
2140            user_id: 12345,
2141            is_login: true,
2142        };
2143        let user2 = user1.clone();
2144        assert_eq!(user1, user2);
2145        let debug_str = format!("{user1:?}");
2146        assert!(debug_str.contains("UserInfo"));
2147        assert!(debug_str.contains("12345"));
2148    }
2149
2150    /// 继承链验证:AddonsBaseController → BaseController → SzController
2151    #[test]
2152    fn test_addons_inheritance_chain_all_methods() {
2153        struct BusinessController;
2154        impl SzController for BusinessController {}
2155        impl BaseController for BusinessController {}
2156        impl AddonsBaseController for BusinessController {}
2157
2158        let ctrl = BusinessController;
2159
2160        // SzController 方法(祖父 trait)
2161        let value = ctrl.render_json(1, "ok", json!({}));
2162        assert_eq!(value["code"], 1);
2163
2164        // BaseController 方法(父 trait)
2165        assert!(!ctrl.batch_validate());
2166        assert!(ctrl.middlewares().is_empty());
2167        ctrl.initialize();
2168
2169        // AddonsBaseController 方法(当前 trait)
2170        let info = ctrl.parse_route_info("/test/action");
2171        assert_eq!(info.route_uri, "/test/action");
2172        assert!(ctrl.check_login("/passport/login", false).is_ok());
2173        assert!(ctrl.get_token(None).unwrap().is_none());
2174    }
2175
2176    // ========================================================================
2177    // P0-SEC-02:JWT 密钥空字符串处理(认证绕过防护)
2178    // ========================================================================
2179
2180    /// P0-SEC-02 回归测试:空密钥配置下 verify_token_with_config 必须返回 Ok(None)
2181    ///
2182    /// 安全铁律:空密钥 = 无密钥 = 禁用 JWT 验证。绝不能以空密钥尝试解码 token
2183    /// (某些 JWT 库在空密钥下会接受 alg=none 的伪造 token,导致认证绕过)。
2184    #[test]
2185    fn test_p0_sec_02_empty_secret_returns_none_not_accept_token() {
2186        let config = JwtConfig {
2187            secret: String::new(), // 空密钥
2188            issuer: String::new(),
2189            audience: String::new(),
2190        };
2191
2192        // 即使传入看似有效的 token,空密钥下也必须拒绝验证(返回 None)
2193        let result = verify_token_with_config(Some("any.token.here"), &config);
2194        assert!(
2195            result.is_ok(),
2196            "空密钥不应导致 panic 或 Err(应优雅降级为 None)"
2197        );
2198        assert_eq!(
2199            result.unwrap(),
2200            None,
2201            "P0-SEC-02: 空密钥时必须返回 None,绝不能接受任何 token"
2202        );
2203    }
2204
2205    /// P0-SEC-02 回归测试:空密钥下即使传入 alg=none 的伪造 token 也必须拒绝
2206    ///
2207    /// alg=none 攻击:某些 JWT 实现在密钥为空时会接受 header 中 alg=none 的 token。
2208    /// 此测试确保我们的实现不会落入此陷阱。
2209    #[test]
2210    fn test_p0_sec_02_rejects_alg_none_token_with_empty_secret() {
2211        let config = JwtConfig::default(); // 空密钥
2212
2213        // 构造一个 alg=none 的伪造 JWT(header: {"alg":"none","typ":"JWT"})
2214        // base64url({"alg":"none","typ":"JWT"}) = eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
2215        // base64url({"user_id":1,"username":"hacker","iat":0,"exp":9999999999})
2216        //   = eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImhhY2tlciIsImlhdCI6MCwiZXhwIjo5OTk5OTk5OTk5fQ
2217        let alg_none_token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.\
2218            eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImhhY2tlciIsImlhdCI6MCwiZXhwIjo5OTk5OTk5OTk5fQ.";
2219
2220        let result = verify_token_with_config(Some(alg_none_token), &config);
2221        assert!(result.is_ok(), "空密钥下不应 panic");
2222        assert_eq!(
2223            result.unwrap(),
2224            None,
2225            "P0-SEC-02: 空密钥下 alg=none 伪造 token 必须被拒绝(返回 None)"
2226        );
2227    }
2228
2229    // ========================================================================
2230    // P1-SEC-10:JWT Audience (aud) 验证
2231    // ========================================================================
2232    // 注:aud 字段验证依赖 sz-orm-auth JwtClaims.aud 字段(v1.2.2+)。
2233    // 当前 JwtClaims 尚未暴露 aud setter,audience 验证逻辑待 sz-orm-auth 升级后恢复。
2234
2235    /// P1-SEC-10 回归测试:未配置 audience 时,跳过 aud 验证(向后兼容)
2236    #[test]
2237    fn test_p1_sec_10_skips_audience_check_when_not_configured() {
2238        let config = JwtConfig {
2239            secret: "test-secret".to_string(),
2240            issuer: String::new(),
2241            audience: String::new(),
2242        };
2243
2244        let encoder = sz_rust_orm_facade::jwt::JwtEncoder::new(&config.secret);
2245        let exp = std::time::SystemTime::now()
2246            .duration_since(std::time::UNIX_EPOCH)
2247            .unwrap()
2248            .as_secs() as i64
2249            + 3600;
2250        let claims = sz_rust_orm_facade::jwt::JwtClaims::new("user123", exp).with_user_id(42);
2251        let token = encoder.encode(&claims).unwrap();
2252
2253        let result = verify_token_with_config(Some(&token), &config);
2254        assert!(result.is_ok());
2255        assert!(
2256            result.unwrap().is_some(),
2257            "未配置 audience 时应跳过 aud 验证"
2258        );
2259    }
2260
2261    // ========================================================================
2262    // P1-SEC-12:JwtConfig Debug 实现不泄漏 secret
2263    // ========================================================================
2264
2265    /// P1-SEC-12 回归测试:JwtConfig 的 Debug 输出必须脱敏 secret 字段
2266    ///
2267    /// 若 JwtConfig 使用派生 Debug,secret 字段会以明文出现在日志和 panic 信息中。
2268    /// 手动实现的 Debug 应将 secret 替换为 "[REDACTED]"。
2269    #[test]
2270    fn test_p1_sec_12_jwt_config_debug_redacts_secret() {
2271        let config = JwtConfig {
2272            secret: "super-secret-key-12345".to_string(),
2273            issuer: "https://example.com".to_string(),
2274            audience: String::new(),
2275        };
2276
2277        let debug_output = format!("{:?}", config);
2278
2279        // secret 绝不能出现在 Debug 输出中
2280        assert!(
2281            !debug_output.contains("super-secret-key-12345"),
2282            "P1-SEC-12: JwtConfig::Debug 泄漏了 secret 字段: {debug_output}"
2283        );
2284        // 必须包含脱敏标记
2285        assert!(
2286            debug_output.contains("[REDACTED]"),
2287            "P1-SEC-12: JwtConfig::Debug 应包含 [REDACTED] 标记: {debug_output}"
2288        );
2289        // issuer 可以正常显示
2290        assert!(
2291            debug_output.contains("https://example.com"),
2292            "issuer 字段应正常显示: {debug_output}"
2293        );
2294    }
2295}