feat(auth): 添加 Refresh Token 双 Token 机制
- 添加 /api/refresh-token 接口支持 Token 续期 - 登录接口返回 access_token 和 refresh_token - 新增 refresh_tokens 表存储 refresh_token - 部署脚本添加数据库备份和迁移功能 - deploy.sh 添加 4 项 API 测试 - 更新 AGENTS.md 文档
This commit is contained in:
88
src/db.rs
88
src/db.rs
@@ -1,4 +1,4 @@
|
||||
use sqlx::{PgPool, postgres::PgPoolOptions};
|
||||
use sqlx::{PgPool, postgres::PgPoolOptions, FromRow};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use chrono::Utc;
|
||||
@@ -476,3 +476,89 @@ pub async fn set_weather_favorite(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ===== Refresh Token DB 函数 =====
|
||||
|
||||
#[derive(Debug, FromRow)]
|
||||
pub struct RefreshToken {
|
||||
pub id: i32,
|
||||
pub user_id: i32,
|
||||
pub token: String,
|
||||
pub expires_at: chrono::DateTime<chrono::Utc>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
pub async fn create_refresh_token(
|
||||
pool: &PgPool,
|
||||
user_id: i32,
|
||||
token: &str,
|
||||
expires_at: chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<(), AppError> {
|
||||
let query = r#"
|
||||
INSERT INTO refresh_tokens (user_id, token, expires_at)
|
||||
VALUES ($1, $2, $3)
|
||||
"#;
|
||||
|
||||
sqlx::query(query)
|
||||
.bind(user_id)
|
||||
.bind(token)
|
||||
.bind(expires_at)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| AppError::Database(format!("创建 refresh_token 失败: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn verify_refresh_token(
|
||||
pool: &PgPool,
|
||||
token: &str,
|
||||
) -> Result<RefreshToken, AppError> {
|
||||
let query = r#"
|
||||
SELECT id, user_id, token, expires_at, created_at
|
||||
FROM refresh_tokens
|
||||
WHERE token = $1 AND expires_at > NOW()
|
||||
"#;
|
||||
|
||||
sqlx::query_as::<_, RefreshToken>(query)
|
||||
.bind(token)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
.map_err(|e| AppError::Database(format!("验证 refresh_token 失败: {}", e)))?
|
||||
.ok_or_else(|| AppError::Unauthorized("Refresh token 无效或已过期".to_string()))
|
||||
}
|
||||
|
||||
pub async fn delete_refresh_token(pool: &PgPool, user_id: i32) -> Result<(), AppError> {
|
||||
let query = r#"DELETE FROM refresh_tokens WHERE user_id = $1"#;
|
||||
|
||||
sqlx::query(query)
|
||||
.bind(user_id)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| AppError::Database(format!("删除 refresh_token 失败: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_specific_refresh_token(pool: &PgPool, token: &str) -> Result<(), AppError> {
|
||||
let query = r#"DELETE FROM refresh_tokens WHERE token = $1"#;
|
||||
|
||||
sqlx::query(query)
|
||||
.bind(token)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| AppError::Database(format!("删除 refresh_token 失败: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cleanup_expired_refresh_tokens(pool: &PgPool) -> Result<u64, AppError> {
|
||||
let query = r#"DELETE FROM refresh_tokens WHERE expires_at <= NOW()"#;
|
||||
|
||||
let result = sqlx::query(query)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| AppError::Database(format!("清理过期 refresh_token 失败: {}", e)))?;
|
||||
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user