first commit

This commit is contained in:
2026-02-13 15:57:29 +08:00
commit aacda0b66a
53 changed files with 10029 additions and 0 deletions

View File

@@ -0,0 +1,232 @@
use anyhow::Result;
use argon2::{
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Argon2,
};
use rand::Rng;
use crate::utils::jwt::TokenService;
use crate::domain::dto::auth::{RegisterRequest, LoginRequest, DeleteUserRequest};
use crate::domain::entities::users;
use crate::config::auth::AuthConfig;
use crate::infra::redis::{redis_client::RedisClient, redis_key::{BusinessType, RedisKey}};
use crate::repositories::user_repository::UserRepository;
pub struct AuthService {
user_repo: UserRepository,
redis_client: RedisClient,
auth_config: AuthConfig,
}
impl AuthService {
pub fn new(user_repo: UserRepository, redis_client: RedisClient, auth_config: AuthConfig) -> Self {
Self { user_repo, redis_client, auth_config }
}
/// 哈希密码
pub fn hash_password(&self, password: &str) -> Result<String> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(password.as_bytes(), &salt)
.map_err(|e| anyhow::anyhow!("密码哈希失败: {}", e))?
.to_string();
Ok(password_hash)
}
/// 生成用户 ID
pub fn generate_user_id(&self) -> String {
let mut rng = rand::thread_rng();
rng.gen_range(1_000_000_000i64..10_000_000_000i64)
.to_string()
}
/// 生成唯一的用户 ID
pub async fn generate_unique_user_id(&self) -> Result<String> {
let mut attempts = 0;
const MAX_ATTEMPTS: u32 = 10;
loop {
let candidate_id = self.generate_user_id();
let existing = self.user_repo.count_by_id(&candidate_id).await?;
if existing == 0 {
return Ok(candidate_id);
}
attempts += 1;
if attempts >= MAX_ATTEMPTS {
return Err(anyhow::anyhow!("生成唯一用户 ID 失败"));
}
}
}
/// 保存 refresh_token 到 Redis
async fn save_refresh_token(&self, user_id: &str, refresh_token: &str, expiration_days: i64) -> Result<()> {
let key = RedisKey::new(BusinessType::Auth)
.add_identifier("refresh_token")
.add_identifier(user_id);
let expiration_seconds = expiration_days * 24 * 3600;
self.redis_client
.set_ex(&key.build(), refresh_token, expiration_seconds as u64)
.await
.map_err(|e| anyhow::anyhow!("Redis 保存失败: {}", e))?;
Ok(())
}
/// 获取并删除 refresh_token
async fn get_and_delete_refresh_token(&self, user_id: &str) -> Result<String> {
let key = RedisKey::new(BusinessType::Auth)
.add_identifier("refresh_token")
.add_identifier(user_id);
let token: Option<String> = self.redis_client
.get(&key.build())
.await
.map_err(|e| anyhow::anyhow!("Redis 查询失败: {}", e))?;
if token.is_some() {
self.redis_client
.delete_key(&key)
.await
.map_err(|e| anyhow::anyhow!("Redis 删除失败: {}", e))?;
}
token.ok_or_else(|| anyhow::anyhow!("刷新令牌无效或已过期"))
}
/// 删除用户的 refresh_token
pub async fn delete_refresh_token(&self, user_id: &str) -> Result<()> {
let key = RedisKey::new(BusinessType::Auth)
.add_identifier("refresh_token")
.add_identifier(user_id);
self.redis_client
.delete_key(&key)
.await
.map_err(|e| anyhow::anyhow!("Redis 删除失败: {}", e))?;
Ok(())
}
/// 注册用户
pub async fn register(
&self,
request: RegisterRequest,
) -> Result<(users::Model, String, String)> {
// 1. 检查邮箱是否已存在
let existing = self.user_repo.count_by_email(&request.email).await?;
if existing > 0 {
return Err(anyhow::anyhow!("邮箱已注册"));
}
// 2. 哈希密码
let password_hash = self.hash_password(&request.password)?;
// 3. 生成用户 ID
let user_id = self.generate_unique_user_id().await?;
// 4. 插入数据库并获取包含真实 created_at 的用户对象
let user = self.user_repo.insert(user_id.clone(), request.email, password_hash).await?;
// 5. 生成 token
let (access_token, refresh_token) = TokenService::generate_token_pair(
&user_id,
self.auth_config.access_token_expiration_minutes,
self.auth_config.refresh_token_expiration_days,
&self.auth_config.jwt_secret,
)?;
// 6. 保存 refresh_token
self.save_refresh_token(&user_id, &refresh_token, self.auth_config.refresh_token_expiration_days as i64).await?;
Ok((user, access_token, refresh_token))
}
/// 登录
pub async fn login(
&self,
request: LoginRequest,
) -> Result<(users::Model, String, String)> {
// 1. 查询用户
let user = self.user_repo.find_by_email(&request.email).await?
.ok_or_else(|| anyhow::anyhow!("邮箱或密码错误"))?;
// 2. 验证密码
let password_hash = self.user_repo.get_password_hash(&request.email).await?
.ok_or_else(|| anyhow::anyhow!("邮箱或密码错误"))?;
let parsed_hash = PasswordHash::new(&password_hash)
.map_err(|e| anyhow::anyhow!("解析密码哈希失败: {}", e))?;
let argon2 = Argon2::default();
argon2
.verify_password(request.password.as_bytes(), &parsed_hash)
.map_err(|_| anyhow::anyhow!("邮箱或密码错误"))?;
// 3. 生成 token
let (access_token, refresh_token) = TokenService::generate_token_pair(
&user.id,
self.auth_config.access_token_expiration_minutes,
self.auth_config.refresh_token_expiration_days,
&self.auth_config.jwt_secret,
)?;
// 4. 保存 refresh_token
self.save_refresh_token(&user.id, &refresh_token, self.auth_config.refresh_token_expiration_days as i64).await?;
Ok((user, access_token, refresh_token))
}
/// 使用 refresh_token 刷新 access_token
pub async fn refresh_access_token(
&self,
refresh_token: &str,
) -> Result<(String, String)> {
// 1. 从 refresh_token 中解码出 user_id
let user_id = TokenService::decode_user_id(refresh_token, &self.auth_config.jwt_secret)?;
// 2. 从 Redis 获取存储的 token 并删除
let stored_token = self.get_and_delete_refresh_token(&user_id).await?;
// 3. 验证 token 是否匹配
if stored_token != refresh_token {
return Err(anyhow::anyhow!("刷新令牌无效"));
}
// 4. 生成新的 token 对
let (new_access_token, new_refresh_token) = TokenService::generate_token_pair(
&user_id,
self.auth_config.access_token_expiration_minutes,
self.auth_config.refresh_token_expiration_days,
&self.auth_config.jwt_secret,
)?;
// 5. 保存新的 refresh_token
self.save_refresh_token(&user_id, &new_refresh_token, self.auth_config.refresh_token_expiration_days as i64).await?;
Ok((new_access_token, new_refresh_token))
}
/// 删除用户
pub async fn delete_user(&self, request: DeleteUserRequest) -> Result<()> {
let password_hash = self.user_repo.get_password_hash(&request.user_id).await?
.ok_or_else(|| anyhow::anyhow!("用户不存在"))?;
let parsed_hash = PasswordHash::new(&password_hash)
.map_err(|e| anyhow::anyhow!("解析密码哈希失败: {}", e))?;
let argon2 = Argon2::default();
argon2
.verify_password(request.password.as_bytes(), &parsed_hash)
.map_err(|_| anyhow::anyhow!("密码错误"))?;
self.user_repo.delete_by_id(&request.user_id).await?;
Ok(())
}
}