Files
ppanel-web/AGENTS.md
T
Ember Moth 29f022abe9 Inaitial
2026-07-05 20:28:19 +08:00

12 KiB
Raw Blame History

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

  • 仅包含命名常量,如 SUCCESSERRORINVALID_PARAMSUSER_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.rsBALANCE_TYPE_*COMMISSION_TYPE_* 等)。

2.2 应用操作日志 — tracing

运维/调试日志使用 tracing::info! / tracing::error!,由 main.rs 根据 LogConfig 初始化。 请求日志由 src/middleware/logger_middleware.rstower-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-oauthTelegram 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_methodAuthRepo)返回 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/smsAlibabaCloud / Twilio / SmsBao / Abosend 完成
src/adaptergtmpl 模板引擎 + Proxy/Client/Adapter 完成
service/authlogin / register / reset / device / telephone 完成
service/auth/oauthGoogle / Apple / Telegram 完成
service/commonheartbeat / globalConfig / stat / client / ads / privacy / tos 完成
service/servergetConfig / getUserList / pushStatus / pushTraffic / pushOnline / queryProtocol 完成
service/nodeconfigGlobalValues / ApplyOverride / OverrideResponse / OverrideModel / CloneValues 完成
service/subscribesubscribeLogic + userAgent UA 匹配) 完成
service/notifyAlipay RSA2 / ePay MD5 / Stripe webhook 完成
service/telegrambot / 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/serviceemail / sms / order / traffic / subscription / task 完成
scheduler4 个定时任务注册) 完成
handler/auth(所有端点,DeviceContext 注入) 完成
handler/common(全部 10 个端点) 完成
handler/server(全部 7 个端点) 完成
handler/admin(全部子域,~150 个端点) 完成
handler/public(全部子域,~90 个端点) 完成
handler/subscribe(泛域名订阅) 完成
handler/notifyAlipay / ePay / Stripe 回调) 完成
handler/telegram 完成
routes.rs(按分组应用中间件) 完成
repository 层(全部 16 个域) 完成
plugin API 暂不实现(已确认跳过)

编译状态

cargo check0 errors(截至 2026-07-05

已知 TODO 项(功能存根)

  • plugin 相关 handler:已确认暂不实现