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