Skip to content

Commit b0351f7

Browse files
committed
feat: 更新 JWT 认证逻辑,添加 refresh token 支持;优化用户登录和登出处理;调整跨域配置
1 parent 4ef4175 commit b0351f7

File tree

11 files changed

+257
-104
lines changed

11 files changed

+257
-104
lines changed

backend/config.toml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,29 @@ max_connections = 5
1111
# JWT配置
1212
[jwt]
1313
secret = "your_secret_key_change_this_in_production"
14-
expiration = 1140 # 过期时间(分钟)
14+
expiration = 15 # access token 过期时间(分钟)
15+
refresh_secret = "your_refresh_secret_key_change_this" # refresh token 签名密钥
16+
refresh_expiration = 10080 # refresh token 过期时间(分钟)= 7天
1517

1618
#跨域配置
1719
[cors]
18-
allowed_origins = ["http://localhost:4321", "https://blog.exquisitecore.xyz"]
20+
allowed_origins = ["http://localhost:3000", "https://blog.exquisitecore.xyz"]
1921
allowed_methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
2022
allowed_headers = ["Authorization", "Content-Type"]
2123
allow_credentials = true
2224

2325
# 微信公众号配置
2426
[wechat]
25-
app_id = "your_app_id"
26-
app_secret = "your_app_secret"
27-
token = "your_token"
28-
# encoding_aes_key = "your_encoding_aes_key" # 可选,用于消息加解密
27+
app_id = ""
28+
app_secret = ""
29+
token = ""
30+
# encoding_aes_key = ""
31+
32+
# LLM 配置(硅基流动 SiliconFlow)
33+
[llm]
34+
provider = "siliconflow"
35+
api_key = "your_siliconflow_api_key_here" # 替换为你的硅基流动 API Key
36+
base_url = "https://api.siliconflow.cn" # 硅基流动 API 地址
37+
model = "deepseek-ai/DeepSeek-V3" # 或 deepseek-ai/DeepSeek-R1
38+
max_tokens = 300 # 限制回复长度,加快响应
39+
timeout_secs = 10 # HTTP 超时(代码层面有4秒微信超时保护)

backend/src/api/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub fn create_routes() -> Router<AppState> {
2323
.route("/users/register", post(userapi::register_user))
2424
.route("/users/login", post(userapi::login_user))
2525
.route("/auth/refresh", post(auth::refresh_token_handler))
26+
.route("/auth/logout", post(auth::logout_handler))
2627
.route("/posts", get(postapi::get_posts))
2728
.route("/posts/{id}", get(postapi::get_post_by_id))
2829
.route("/posts/{id}/labels", get(postapi::get_post_labels))

backend/src/api/userapi.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
//!
33
//! 提供用户相关的API端点
44
5+
use axum::http::header::SET_COOKIE;
6+
use axum::response::{IntoResponse, Response};
57
use axum::{Json, extract::{Path, State}};
68
use bcrypt::{DEFAULT_COST, hash};
79
use uuid::Uuid;
@@ -105,21 +107,33 @@ pub async fn register_user(
105107
/// 用户登录API
106108
///
107109
/// 验证用户凭据并生成JWT令牌
110+
/// 返回 access token 在 JSON body,refresh token 在 Set-Cookie
108111
pub async fn login_user(
109112
State(state): State<crate::state::AppState>,
110113
Json(req): Json<LoginRequest>,
111-
) -> Result<Json<serde_json::Value>, AppError> {
114+
) -> Result<Response, AppError> {
112115
// 尝试登录用户
113116
match User::login(&state.pool, req).await {
114117
Ok(Some(user)) => {
115-
// 生成JWT令牌
116-
let token = auth::generate_token(&user)?;
118+
// 生成 access token(短期)
119+
let access_token = auth::generate_token(&user)?;
120+
// 生成 refresh token(长期)
121+
let refresh_token = auth::generate_refresh_token(&user)?;
117122

118-
// 返回用户信息和令牌
119-
Ok(Json(serde_json::json!({
123+
// 构建响应 body
124+
let body = serde_json::json!({
120125
"user": user,
121-
"token": token
122-
})))
126+
"token": access_token
127+
});
128+
129+
// 构建响应,设置 refresh token 到 cookie
130+
let mut response = Json(body).into_response();
131+
response.headers_mut().insert(
132+
SET_COOKIE,
133+
auth::build_refresh_cookie(&refresh_token).parse().unwrap(),
134+
);
135+
136+
Ok(response)
123137
}
124138
Ok(None) => Err(AppError::new_message(
125139
"用户名/邮箱或密码错误",

backend/src/api/wechatapi.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use axum::{
1111
use serde::{Deserialize, Serialize};
1212
use std::collections::HashMap;
1313
use std::sync::Arc;
14-
use tracing::{error, info};
14+
use std::time::Duration;
15+
use tokio::time::timeout;
16+
use tracing::{error, info, warn};
1517
use wechat_oa_sdk::{
1618
WeChatClient,
1719
api::message::IncomingMessage,
@@ -201,14 +203,19 @@ async fn handle_message(message: IncomingMessage, llm_client: Option<&Arc<LlmCli
201203
"对话历史已清除!".to_string()
202204
}
203205
_ => {
204-
// 使用 LLM 回复
206+
// 使用 LLM 回复(4秒超时,微信要求5秒内响应)
205207
if let Some(client) = llm_client {
206-
match client.chat(&msg.from_user_name, &msg.content).await {
207-
Ok(response) => response,
208-
Err(e) => {
208+
let llm_timeout = Duration::from_secs(4);
209+
match timeout(llm_timeout, client.chat(&msg.from_user_name, &msg.content)).await {
210+
Ok(Ok(response)) => response,
211+
Ok(Err(e)) => {
209212
error!("LLM 调用失败: {}", e);
210213
format!("抱歉,AI 服务暂时不可用:{}", e)
211214
}
215+
Err(_) => {
216+
warn!("LLM 调用超时 for user {}", msg.from_user_name);
217+
"AI 正在思考中,请稍后再试...".to_string()
218+
}
212219
}
213220
} else {
214221
// 没有配置 LLM,使用简单回复

backend/src/config.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ pub struct DatabaseConfig {
6464
#[derive(Debug, Serialize, Deserialize, Clone)]
6565
pub struct JwtConfig {
6666
pub secret: String,
67-
pub expiration: u64, // 过期时间(分钟)
67+
pub expiration: u64, // access token 过期时间(分钟)
68+
pub refresh_secret: String,
69+
pub refresh_expiration: u64, // refresh token 过期时间(分钟)
6870
}
6971

7072
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -103,7 +105,9 @@ impl Default for Config {
103105
},
104106
jwt: JwtConfig {
105107
secret: "default_secret_key_change_in_production".to_string(),
106-
expiration: 60, // 60分钟
108+
expiration: 15, // 15分钟
109+
refresh_secret: "default_refresh_secret_change_in_production".to_string(),
110+
refresh_expiration: 10080, // 7天
107111
},
108112
cors: CorsConfig {
109113
allowed_origins: vec!["http://localhost:4321".to_string()],

0 commit comments

Comments
 (0)