first commit
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
use crate::AppState;
|
||||
use axum::{
|
||||
extract::{Request, State},
|
||||
http::{HeaderMap, StatusCode},
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
};
|
||||
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Claims {
|
||||
pub sub: String, // user_id
|
||||
#[allow(dead_code)]
|
||||
pub exp: usize,
|
||||
}
|
||||
|
||||
/// JWT 认证中间件
|
||||
pub async fn auth_middleware(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
mut req: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, StatusCode> {
|
||||
// 1. 提取 Authorization header
|
||||
let auth_header = headers
|
||||
.get("Authorization")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.ok_or(StatusCode::UNAUTHORIZED)?;
|
||||
|
||||
if !auth_header.starts_with("Bearer ") {
|
||||
return Err(StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
let token = &auth_header[7..];
|
||||
|
||||
// 2. 验证 JWT
|
||||
let jwt_secret = &state.config.auth.jwt_secret;
|
||||
|
||||
let token_data = decode::<Claims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(jwt_secret.as_ref()),
|
||||
&Validation::default(),
|
||||
)
|
||||
.map_err(|_| StatusCode::UNAUTHORIZED)?;
|
||||
|
||||
// 3. 将 user_id 添加到请求扩展
|
||||
req.extensions_mut().insert(token_data.claims.sub);
|
||||
|
||||
Ok(next.run(req).await)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use axum::{extract::Request, response::Response};
|
||||
use std::time::Instant;
|
||||
|
||||
/// Request ID 标记
|
||||
#[derive(Clone)]
|
||||
pub struct RequestId(pub String);
|
||||
|
||||
/// 请求日志中间件
|
||||
pub async fn request_logging_middleware(
|
||||
mut req: Request,
|
||||
next: axum::middleware::Next,
|
||||
) -> Response {
|
||||
let start = Instant::now();
|
||||
|
||||
// 提取请求信息
|
||||
let method = req.method().clone();
|
||||
let path = req.uri().path().to_string();
|
||||
let query = req.uri().query().map(|s| s.to_string());
|
||||
|
||||
// 生成请求 ID
|
||||
let request_id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
// 将 request_id 存储到请求扩展中
|
||||
req.extensions_mut().insert(RequestId(request_id.clone()));
|
||||
|
||||
// 第1条日志:请求开始
|
||||
let separator = "=".repeat(80);
|
||||
let header = format!("{} {}", method, path);
|
||||
|
||||
tracing::info!("{}", separator);
|
||||
tracing::info!("{}", header);
|
||||
tracing::info!("{}", separator);
|
||||
|
||||
let now_beijing = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
|
||||
let query_str = query.as_deref().unwrap_or("无");
|
||||
tracing::info!(
|
||||
"[{}] 📥 查询参数: {} | 时间: {}",
|
||||
request_id,
|
||||
query_str,
|
||||
now_beijing
|
||||
);
|
||||
|
||||
// 调用下一个处理器
|
||||
let response = next.run(req).await;
|
||||
|
||||
// 第3条日志:请求完成
|
||||
let duration = start.elapsed();
|
||||
let status = response.status();
|
||||
tracing::info!(
|
||||
"[{}] ✅ 状态码: {} | 耗时: {}ms",
|
||||
request_id,
|
||||
status.as_u16(),
|
||||
duration.as_millis()
|
||||
);
|
||||
|
||||
tracing::info!("{}", separator);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
/// 请求日志辅助工具
|
||||
pub fn log_info<T: std::fmt::Debug>(request_id: &RequestId, label: &str, data: T) {
|
||||
let data_str = format!("{:?}", data);
|
||||
let truncated = if data_str.len() > 300 {
|
||||
format!("{}...", &data_str[..300])
|
||||
} else {
|
||||
data_str
|
||||
};
|
||||
|
||||
tracing::info!("[{}] 🔧 {} | {}", request_id.0, label, truncated);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod auth;
|
||||
pub mod logging;
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod middleware;
|
||||
pub mod redis;
|
||||
@@ -0,0 +1,20 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// Redis 错误类型
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RedisError {
|
||||
#[error("Redis 连接失败: {0}")]
|
||||
ConnectionError(#[from] redis::RedisError),
|
||||
|
||||
#[error("Redis 序列化失败: {0}")]
|
||||
SerializationError(#[from] serde_json::Error),
|
||||
|
||||
#[error("Redis 数据不存在: {key}")]
|
||||
NotFound { key: String },
|
||||
|
||||
#[error("Redis 操作失败: {message}")]
|
||||
OperationError { message: String },
|
||||
|
||||
#[error("Failed to create redis pool: {0}")]
|
||||
PoolCreation(#[from] deadpool_redis::CreatePoolError),
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod redis_client;
|
||||
pub mod redis_key;
|
||||
@@ -0,0 +1,135 @@
|
||||
use super::redis_key::RedisKey;
|
||||
use redis::aio::MultiplexedConnection;
|
||||
use redis::{AsyncCommands, Client};
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// Redis 客户端(使用 MultiplexedConnection)
|
||||
#[derive(Clone)]
|
||||
pub struct RedisClient {
|
||||
conn: Arc<Mutex<MultiplexedConnection>>,
|
||||
}
|
||||
|
||||
impl RedisClient {
|
||||
/// 创建新的 Redis 客户端
|
||||
pub async fn new(url: &str) -> redis::RedisResult<Self> {
|
||||
let client = Client::open(url)?;
|
||||
let conn = client.get_multiplexed_async_connection().await?;
|
||||
Ok(Self {
|
||||
conn: Arc::new(Mutex::new(conn)),
|
||||
})
|
||||
}
|
||||
|
||||
/// 设置字符串值
|
||||
pub async fn set(&self, k: &str, v: &str) -> redis::RedisResult<()> {
|
||||
let mut c = self.conn.lock().await;
|
||||
c.set(k, v).await
|
||||
}
|
||||
|
||||
/// 获取字符串值
|
||||
pub async fn get(&self, k: &str) -> redis::RedisResult<Option<String>> {
|
||||
let mut c = self.conn.lock().await;
|
||||
c.get(k).await
|
||||
}
|
||||
|
||||
/// 设置字符串值并指定过期时间(秒)
|
||||
pub async fn set_ex(&self, k: &str, v: &str, seconds: u64) -> redis::RedisResult<()> {
|
||||
let mut c = self.conn.lock().await;
|
||||
c.set_ex(k, v, seconds).await
|
||||
}
|
||||
|
||||
/// 删除键
|
||||
pub async fn del(&self, k: &str) -> redis::RedisResult<()> {
|
||||
let mut c = self.conn.lock().await;
|
||||
c.del(k).await
|
||||
}
|
||||
|
||||
/// 设置键的过期时间(秒)
|
||||
pub async fn expire(&self, k: &str, seconds: u64) -> redis::RedisResult<()> {
|
||||
let mut c = self.conn.lock().await;
|
||||
c.expire(k, seconds as i64).await
|
||||
}
|
||||
|
||||
/// 使用 RedisKey 设置 JSON 值
|
||||
pub async fn set_key<T: Serialize>(
|
||||
&self,
|
||||
key: &RedisKey,
|
||||
value: &T,
|
||||
) -> redis::RedisResult<()> {
|
||||
let json = serde_json::to_string(value).map_err(|e| {
|
||||
redis::RedisError::from((
|
||||
redis::ErrorKind::TypeError,
|
||||
"JSON serialization failed",
|
||||
e.to_string(),
|
||||
))
|
||||
})?;
|
||||
let mut c = self.conn.lock().await;
|
||||
c.set(key.build(), json).await
|
||||
}
|
||||
|
||||
/// 使用 RedisKey 设置 JSON 值并指定过期时间(秒)
|
||||
pub async fn set_key_ex<T: Serialize>(
|
||||
&self,
|
||||
key: &RedisKey,
|
||||
value: &T,
|
||||
expiration_seconds: u64,
|
||||
) -> redis::RedisResult<()> {
|
||||
let json = serde_json::to_string(value).map_err(|e| {
|
||||
redis::RedisError::from((
|
||||
redis::ErrorKind::TypeError,
|
||||
"JSON serialization failed",
|
||||
e.to_string(),
|
||||
))
|
||||
})?;
|
||||
let mut c = self.conn.lock().await;
|
||||
c.set_ex(key.build(), json, expiration_seconds).await
|
||||
}
|
||||
|
||||
/// 使用 RedisKey 获取字符串值
|
||||
pub async fn get_key(&self, key: &RedisKey) -> redis::RedisResult<Option<String>> {
|
||||
let mut c = self.conn.lock().await;
|
||||
let json: Option<String> = c.get(key.build()).await?;
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
/// 使用 RedisKey 获取并反序列化 JSON 值
|
||||
pub async fn get_key_json<T: for<'de> serde::Deserialize<'de>>(
|
||||
&self,
|
||||
key: &RedisKey,
|
||||
) -> redis::RedisResult<Option<T>> {
|
||||
let mut c = self.conn.lock().await;
|
||||
let json: Option<String> = c.get(key.build()).await?;
|
||||
match json {
|
||||
Some(data) => {
|
||||
let value = serde_json::from_str(&data).map_err(|e| {
|
||||
redis::RedisError::from((
|
||||
redis::ErrorKind::TypeError,
|
||||
"JSON deserialization failed",
|
||||
e.to_string(),
|
||||
))
|
||||
})?;
|
||||
Ok(Some(value))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// 使用 RedisKey 删除键
|
||||
pub async fn delete_key(&self, key: &RedisKey) -> redis::RedisResult<()> {
|
||||
let mut c = self.conn.lock().await;
|
||||
c.del(key.build()).await
|
||||
}
|
||||
|
||||
/// 使用 RedisKey 检查键是否存在
|
||||
pub async fn exists_key(&self, key: &RedisKey) -> redis::RedisResult<bool> {
|
||||
let mut c = self.conn.lock().await;
|
||||
c.exists(key.build()).await
|
||||
}
|
||||
|
||||
/// 使用 RedisKey 设置键的过期时间(秒)
|
||||
pub async fn expire_key(&self, key: &RedisKey, seconds: u64) -> redis::RedisResult<()> {
|
||||
let mut c = self.conn.lock().await;
|
||||
c.expire(key.build(), seconds as i64).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
/// 业务类型枚举
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum BusinessType {
|
||||
#[serde(rename = "auth")]
|
||||
Auth,
|
||||
#[serde(rename = "user")]
|
||||
User,
|
||||
#[serde(rename = "cache")]
|
||||
Cache,
|
||||
#[serde(rename = "session")]
|
||||
Session,
|
||||
#[serde(rename = "rate_limit")]
|
||||
RateLimit,
|
||||
}
|
||||
|
||||
impl BusinessType {
|
||||
pub fn prefix(self) -> &'static str {
|
||||
match self {
|
||||
BusinessType::Auth => "auth",
|
||||
BusinessType::User => "user",
|
||||
BusinessType::Cache => "cache",
|
||||
BusinessType::Session => "session",
|
||||
BusinessType::RateLimit => "rate_limit",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Redis 键构建器
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RedisKey {
|
||||
business: BusinessType,
|
||||
identifiers: Vec<String>,
|
||||
}
|
||||
|
||||
impl RedisKey {
|
||||
pub fn new(business: BusinessType) -> Self {
|
||||
Self {
|
||||
business,
|
||||
identifiers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_identifier(mut self, id: impl Into<String>) -> Self {
|
||||
self.identifiers.push(id.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(&self) -> String {
|
||||
format!("{}:{}", self.business.prefix(), self.identifiers.join(":"))
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容现有格式
|
||||
impl fmt::Display for RedisKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.build())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user