# AGENTS.md `ppanel-backend` 项目约定,供 AI agent 及开发者遵循。 ## ppanel-backend 基于 axum 的 Rust 重写项目,Go 源码位于 `../server`。Go 代码库是行为正确性的唯一参考——当 Rust handler 需要对齐 Go 语义时,从对应的 Go 包移植,而非重新设计。 --- ## 一、返回值必须通过 `result` crate 所有 HTTP handler 的响应都必须通过 `result` crate 生成。**禁止**在 handler 中手写 `axum` 响应结构体、自行封装 JSON 或拼装 `axum::Json`——统一使用已有工具,以保持响应格式与 Go `result` 包一致。 ``` crates/result/src/ ├── lib.rs # pub mod code_error / error_code / http_result ├── error_code.rs # 错误码常量 (SUCCESS, ERROR, INVALID_PARAMS, ...) + map_err_msg / is_code_err ├── code_error.rs # CodeError (new_err_code / new_err_code_msg / new_err_msg) + STATUS_NOT_MODIFIED └── http_result.rs # ResponseSuccessBean / ResponseErrorBean / HttpResult + build/http/param 工具函数 ``` ### API 一览 `result::http_result` - `build_http_result(resp, err) -> HttpResult` — 核心构造函数;出错时从 `anyhow::Error` 链中提取 `CodeError`,找不到则回退为 `ERROR` / `"Internal Server Error"`。始终返回 HTTP 200,业务码放在 body 的 `code` 字段(与 Go 行为一致)。 - `build_param_error_result(err) -> HttpResult` — HTTP 200,业务码 `INVALID_PARAMS`。 - `HttpResult` 实现了 `IntoResponse`,可直接作为 handler 的返回值。 `result::code_error` - `CodeError::new_err_code(code)` — 消息由 `map_err_msg` 自动查表。 - `CodeError::new_err_code_msg(code, msg)` — 显式指定 code + 消息。 - `CodeError::new_err_msg(msg)` — 业务码默认为 `ERROR`。 `result::error_code` - 仅包含命名常量,如 `SUCCESS`、`ERROR`、`INVALID_PARAMS`、`USER_NOT_EXIST`。 必须使用这些常量,**禁止魔术数字**。 ### Handler 用法模板 ```rust use result::code_error::CodeError; use result::error_code; use result::http_result::{build_http_result, HttpResult}; pub async fn handler(State(state): State, Json(req): Json) -> HttpResult { let res = some_service(req).await .map_err(|_| anyhow::Error::new(CodeError::new_err_code(error_code::USER_NOT_EXIST))); build_http_result(res.ok(), res.err()) } ``` 规则: - HTTP 状态码固定 200,业务码在 body `code` 字段,禁止用 HTTP 状态码表示业务错误。 - 成功路径 → `Some(data)`;错误路径 → `CodeError` 包装进 `anyhow::Error`。 --- ## 二、日志系统(两层) ### 2.1 业务审计日志 — Telemetry facade **位置**:`src/service/telemetry.rs` 所有业务事件写入 `system_logs` 表,必须通过 `Telemetry` facade,**禁止**在 service 层直接构造 `SystemLog` 并调用 `repos.log.insert()`。 ```rust use crate::service::telemetry::Telemetry; // 登录成功 Telemetry::login(&repos, user_id, "email", &ip, &user_agent, true).await; // 注册成功 Telemetry::register(&repos, user_id, "email", &email, &ip, &user_agent).await; // 余额变动(type_ 用 BALANCE_TYPE_* 常量) Telemetry::balance(&repos, user_id, BALANCE_TYPE_RECHARGE, amount, Some(order_no), balance).await; ``` **全部 14 种方法**: | 方法 | LogType | 优先级 | |------|---------|--------| | `login` | LOGIN (30) | P0 | | `register` | REGISTER (31) | P0 | | `balance` | BALANCE (32) | P1 | | `commission` | COMMISSION (33) | P1 | | `gift` | GIFT (34) | P1 | | `subscribe_access` | SUBSCRIBE (20) | P1 | | `subscribe_traffic` | SUBSCRIBE_TRAFFIC (21) | P2 | | `server_traffic` | SERVER_TRAFFIC (22) | P2 | | `reset_subscribe` | RESET_SUBSCRIBE (23) | P2 | | `email_message` | EMAIL_MESSAGE (10) | P2 | | `mobile_message` | MOBILE_MESSAGE (11) | P2 | | `user_traffic_rank` | USER_TRAFFIC_RANK (40) | P3 | | `server_traffic_rank` | SERVER_TRAFFIC_RANK (41) | P3 | | `traffic_stat` | TRAFFIC_STAT (42) | P3 | 子类型常量定义在 `src/model/entity/log.rs`(`BALANCE_TYPE_*`、`COMMISSION_TYPE_*` 等)。 ### 2.2 应用操作日志 — tracing 运维/调试日志使用 `tracing::info!` / `tracing::error!`,由 `main.rs` 根据 `LogConfig` 初始化。 请求日志由 `src/middleware/logger_middleware.rs`(`tower-http TraceLayer`)自动完成,**handler 无需额外代码**。 --- ## 三、中间件 ### 3.1 DeviceContext(设备上下文) **位置**:`src/middleware/device_middleware.rs` 从 HTTP headers 提取客户端元数据并注入 `Extension`,**永不拒绝请求**: | Header | DeviceContext 字段 | |--------|-------------------| | `X-Original-Forwarded-For` / `X-Forwarded-For` | `ip` | | `User-Agent` | `user_agent` | | `Identifier` | `identifier` | | `Login-Type` | `login_type` | Handler 通过 `Extension(device): Extension` 提取,然后覆盖 JSON body 中的对应字段: ```rust pub async fn user_login( State(state): State, Extension(device): Extension, Json(mut req): Json, ) -> HttpResult { if !device.ip.is_empty() { req.ip = device.ip; } if !device.user_agent.is_empty() { req.user_agent = device.user_agent; } if !device.identifier.is_empty() { req.identifier = device.identifier; } // ... } ``` ### 3.2 AuthContext(认证上下文) **位置**:`src/middleware/auth_middleware.rs` 验证 Bearer JWT,校验 Redis session,查询用户状态,注入 `Extension`。 admin 路由自动检查 `user.is_admin`。 ### 3.3 路由分组中间件映射 | 路由前缀 | 中间件 | |---------|--------| | `/v1/auth/*` | `device_middleware` | | `/v1/auth/oauth/*` | 无 | | `/v1/admin/*` | `auth_middleware` | | `/v1/public/*` | `auth_middleware` + `device_middleware` | | `/v1/common/*` | `device_middleware` | | `/v1/server/*`、`/v1/telegram`、`/v1/subscribe/*` | 无用户认证 | --- ## 四、本地 crate 一览 | crate | 路径 | 说明 | |-------|------|------| | `result` | `crates/result` | HTTP 响应信封、错误码 | | `jwt` | `crates/jwt` | JWT 生成/验证(`Claims::new` / `generate_token` / `validate_token`) | | `password` | `crates/password` | 密码哈希,移植自 `../server/pkg/tool/encryption.go` | | `oauth` | `crates/oauth` | OAuth2 封装(Google/Apple via arctic-oauth,Telegram HMAC 验证) | | `email` | `crates/email` | 邮件发送 | | `ip` | `crates/ip` | IP 工具 | | `payment` | `crates/payment` | 支付集成 | ### jwt crate 用法 ```rust // 生成 token(返回 (Claims, expire_seconds)) let (claims, seconds) = jwt::Claims::new(user_id, session_id, login_type); let token = jwt::generate_token(&claims, &config.jwt_auth.access_secret)?; // 验证 token let claims = jwt::validate_token(&token, &config.jwt_auth.access_secret)?; ``` ### password crate 用法 ```rust // 编码(新密码) let hash = password::encode_password(&plain_text)?; // 校验(支持 md5 / sha256 / md5salt / sha256salt / default(PBKDF2) / bcrypt) let ok = password::multi_password_verify(&algo, &salt, &plain, &stored_hash); ``` ### oauth crate 用法 ```rust // Google — PKCE 授权 URL let google = oauth::Google::new(&client_id, &client_secret, &redirect_uri); let url = google.authorization_url(&state, &["openid", "email", "profile"], &code_verifier); let tokens = google.validate_authorization_code(&code, &code_verifier).await?; let info = oauth::OAuthUserInfo::from_google(&tokens)?; // Apple — 授权 URL(手动构造)+ token 交换 let apple = oauth::Apple::new(&client_id, &team_id, &key_id, &pkcs8_der, &redirect_uri)?; let tokens = apple.validate_authorization_code(&code).await?; let info = oauth::OAuthUserInfo::from_apple(&tokens)?; // Telegram — HMAC 验证 base64 回调 let auth_data = oauth::parse_base64_and_validate(tg_auth_result, bot_token.as_bytes())?; let info = oauth::OAuthUserInfo::from_telegram(&auth_data); ``` --- ## 五、repository 层约定 - 所有 repo 方法均通过 trait 对象调用(`Box`),dialect-agnostic。 - `find_one_by_method`(AuthRepo)返回 `Result`,**不是** `Option`——找不到时返回 `sqlx::Error::RowNotFound`。 - 动态 SQL 字符串必须用 `repository::audit(sql)` 包装(`sqlx::AssertSqlSafe`)。 --- ## 六、移植进度(截至 2026-07-05) | 模块 | 状态 | |------|------| | 日志系统(Telemetry + tracing + TraceLayer) | ✅ 完成 | | 中间件(auth / device / logger) | ✅ 完成 | | 中间件(cors / notify / server / pan_domain / trace) | ✅ 完成 | | crates/sms(AlibabaCloud / Twilio / SmsBao / Abosend) | ✅ 完成 | | src/adapter(gtmpl 模板引擎 + Proxy/Client/Adapter) | ✅ 完成 | | service/auth(login / register / reset / device / telephone) | ✅ 完成 | | service/auth/oauth(Google / Apple / Telegram) | ✅ 完成 | | service/common(heartbeat / globalConfig / stat / client / ads / privacy / tos) | ✅ 完成 | | service/server(getConfig / getUserList / pushStatus / pushTraffic / pushOnline / queryProtocol) | ✅ 完成 | | service/nodeconfig(GlobalValues / ApplyOverride / OverrideResponse / OverrideModel / CloneValues) | ✅ 完成 | | service/subscribe(subscribeLogic + userAgent UA 匹配) | ✅ 完成 | | service/notify(Alipay RSA2 / ePay MD5 / Stripe webhook) | ✅ 完成 | | service/telegram(bot / template / telegram_service) | ✅ 完成 | | service/admin/ads | ✅ 完成 | | service/admin/announcement | ✅ 完成 | | service/admin/document | ✅ 完成 | | service/admin/coupon | ✅ 完成 | | service/admin/payment | ✅ 完成 | | service/admin/auth_method | ✅ 完成 | | service/admin/application(含 adapter 模板预览) | ✅ 完成 | | service/admin/console | ✅ 完成 | | service/admin/tool | ✅ 完成 | | service/admin/marketing(批量邮件 / quota 任务) | ✅ 完成 | | service/admin/order | ✅ 完成 | | service/admin/ticket | ✅ 完成 | | service/admin/log(全部 14 种日志类型) | ✅ 完成 | | service/admin/server(节点 / 服务器 CRUD + 协议配置) | ✅ 完成 | | service/admin/subscribe(订阅计划 CRUD + 排序) | ✅ 完成 | | service/admin/system(全部 26 个配置读写) | ✅ 完成 | | service/admin/user(全部 28 个用户管理操作) | ✅ 完成 | | service/public/announcement | ✅ 完成 | | service/public/document | ✅ 完成 | | service/public/payment | ✅ 完成 | | service/public/subscribe | ✅ 完成 | | service/public/ticket | ✅ 完成 | | service/public/portal(购买流程) | ✅ 完成 | | service/public/order(全部 12 个订单操作) | ✅ 完成 | | service/public/user(全部 30 个用户自助操作) | ✅ 完成 | | queue/service(email / sms / order / traffic / subscription / task) | ✅ 完成 | | scheduler(4 个定时任务注册) | ✅ 完成 | | handler/auth(所有端点,DeviceContext 注入) | ✅ 完成 | | handler/common(全部 10 个端点) | ✅ 完成 | | handler/server(全部 7 个端点) | ✅ 完成 | | handler/admin(全部子域,~150 个端点) | ✅ 完成 | | handler/public(全部子域,~90 个端点) | ✅ 完成 | | handler/subscribe(泛域名订阅) | ✅ 完成 | | handler/notify(Alipay / ePay / Stripe 回调) | ✅ 完成 | | handler/telegram | ✅ 完成 | | routes.rs(按分组应用中间件) | ✅ 完成 | | repository 层(全部 16 个域) | ✅ 完成 | | plugin API | ⏳ 暂不实现(已确认跳过) | ### 编译状态 `cargo check` → **0 errors**(截至 2026-07-05) ### 已知 TODO 项(功能存根) - plugin 相关 handler:已确认暂不实现