feat(auth): 添加微信小程序登录支持
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- 新增 wechat_users 表迁移和 SeaORM Entity
- 实现微信登录 Service(code→openid→绑定状态查询)
- 实现手机号绑定 Service(创建/关联 user + 签发 JWT)
- 添加公开路由 POST /auth/wechat/login 和 /auth/wechat/bind-phone
- 新增 WechatConfig 到 AppConfig(appid/secret 通过环境变量配置)
- 添加 reqwest 依赖用于调用微信 jscode2session API
This commit is contained in:
iven
2026-04-24 00:05:43 +08:00
parent 2e9eb55f2c
commit ba132921cc
17 changed files with 515 additions and 2 deletions

View File

@@ -2,3 +2,4 @@ pub mod auth_handler;
pub mod org_handler;
pub mod role_handler;
pub mod user_handler;
pub mod wechat_handler;

View File

@@ -0,0 +1,77 @@
use axum::extract::{FromRef, State};
use axum::response::Json;
use erp_core::error::AppError;
use erp_core::types::ApiResponse;
use crate::auth_state::AuthState;
use crate::dto::{LoginResp, WechatBindPhoneReq, WechatLoginReq, WechatLoginResp};
use crate::service::wechat_service::WechatService;
#[utoipa::path(
post,
path = "/api/v1/auth/wechat/login",
request_body = WechatLoginReq,
responses(
(status = 200, description = "微信登录成功", body = ApiResponse<WechatLoginResp>),
(status = 400, description = "请求参数错误"),
),
tag = "认证"
)]
/// POST /api/v1/auth/wechat/login
///
/// 微信小程序登录:用 code 换 openid查询绑定状态。
/// 已绑定用户直接返回 JWT未绑定用户返回 openid 供后续绑定。
pub async fn wechat_login<S>(
State(state): State<AuthState>,
Json(req): Json<WechatLoginReq>,
) -> Result<Json<ApiResponse<WechatLoginResp>>, AppError>
where
AuthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
if req.code.is_empty() {
return Err(AppError::Validation("code 不能为空".to_string()));
}
let tenant_id = state.default_tenant_id;
let resp = WechatService::login(&state, tenant_id, &req.code).await?;
Ok(Json(ApiResponse::ok(resp)))
}
#[utoipa::path(
post,
path = "/api/v1/auth/wechat/bind-phone",
request_body = WechatBindPhoneReq,
responses(
(status = 200, description = "绑定成功", body = ApiResponse<LoginResp>),
(status = 400, description = "请求参数错误"),
),
tag = "认证"
)]
/// POST /api/v1/auth/wechat/bind-phone
///
/// 微信手机号绑定:解密手机号,创建/关联 user签发 JWT。
pub async fn wechat_bind_phone<S>(
State(state): State<AuthState>,
Json(req): Json<WechatBindPhoneReq>,
) -> Result<Json<ApiResponse<LoginResp>>, AppError>
where
AuthState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
if req.openid.is_empty() {
return Err(AppError::Validation("openid 不能为空".to_string()));
}
let tenant_id = state.default_tenant_id;
let resp = WechatService::bind_phone(
&state,
tenant_id,
&req.openid,
&req.encrypted_data,
&req.iv,
)
.await?;
Ok(Json(ApiResponse::ok(resp)))
}