feat(auth): 添加微信小程序登录支持
- 新增 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:
@@ -2,3 +2,4 @@ pub mod auth_handler;
|
||||
pub mod org_handler;
|
||||
pub mod role_handler;
|
||||
pub mod user_handler;
|
||||
pub mod wechat_handler;
|
||||
|
||||
77
crates/erp-auth/src/handler/wechat_handler.rs
Normal file
77
crates/erp-auth/src/handler/wechat_handler.rs
Normal 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)))
|
||||
}
|
||||
Reference in New Issue
Block a user