12 KiB
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 用法模板
use result::code_error::CodeError;
use result::error_code;
use result::http_result::{build_http_result, HttpResult};
pub async fn handler(State(state): State<AppState>, Json(req): Json<Req>) -> 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()。
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<DeviceContext>,永不拒绝请求:
| Header | DeviceContext 字段 |
|---|---|
X-Original-Forwarded-For / X-Forwarded-For |
ip |
User-Agent |
user_agent |
Identifier |
identifier |
Login-Type |
login_type |
Handler 通过 Extension(device): Extension<DeviceContext> 提取,然后覆盖 JSON body 中的对应字段:
pub async fn user_login(
State(state): State<AppState>,
Extension(device): Extension<DeviceContext>,
Json(mut req): Json<UserLoginRequest>,
) -> 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<AuthContext>。
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 用法
// 生成 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 用法
// 编码(新密码)
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 用法
// 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<dyn XxxRepo>),dialect-agnostic。 find_one_by_method(AuthRepo)返回Result<Auth, sqlx::Error>,不是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:已确认暂不实现