This commit is contained in:
2026-02-13 12:10:31 +08:00
parent bd61454e17
commit 1326b069cd
10 changed files with 517 additions and 86 deletions

View File

@@ -8,6 +8,7 @@ mod m20260211_140550_player_matchups;
mod m20260211_140551_messages;
mod m20260211_140552_map_comments;
mod m20260211_140553_tournament_settings;
mod m20260213_094956_add_refresh_tokens;
pub struct Migrator;
@@ -23,6 +24,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260211_140551_messages::Migration),
Box::new(m20260211_140552_map_comments::Migration),
Box::new(m20260211_140553_tournament_settings::Migration),
Box::new(m20260213_094956_add_refresh_tokens::Migration),
]
}
}

View File

@@ -0,0 +1,42 @@
use sea_orm_migration::{prelude::*, schema::*};
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(User::Table)
.add_column(ColumnDef::new(User::OsuRefreshToken).string().null())
.add_column(ColumnDef::new(User::GuRefreshToken).string().null())
.to_owned(),
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(User::Table)
.drop_column(User::OsuRefreshToken)
.drop_column(User::GuRefreshToken)
.to_owned(),
)
.await?;
Ok(())
}
}
#[derive(DeriveIden)]
enum User {
Table,
OsuRefreshToken,
GuRefreshToken,
}

View File

@@ -1,3 +1,4 @@
use crate::middleware::auth::AuthUser;
use axum::{
Json, Router,
extract::{Query, State},
@@ -34,6 +35,11 @@ struct OsuCallbackQuery {
state: String,
}
#[derive(Debug, Deserialize)]
struct RefreshTokenQuery {
server: Option<String>,
}
#[derive(Debug, Deserialize)]
struct OsuTokenResponse {
access_token: String,
@@ -178,7 +184,13 @@ async fn osu_callback(
approved: None,
season: None,
};
user_service.update(existing.id, update_request).await?
let updated_user = user_service.update(existing.id, update_request).await?;
// 保存 OSU refresh_token
user_service
.update_osu_refresh_token(updated_user.id, token_response.refresh_token.clone())
.await?;
updated_user
}
None => {
// 创建新用户
@@ -190,7 +202,13 @@ async fn osu_callback(
country: user_data.country.as_ref().map(|c| c.code.clone()),
country_rank: user_data.statistics.as_ref().and_then(|s| s.country_rank),
};
user_service.create(user_data.id, create_request).await?
let new_user = user_service.create(user_data.id, create_request).await?;
// 保存 OSU refresh_token
user_service
.update_osu_refresh_token(new_user.id, token_response.refresh_token.clone())
.await?;
new_user
}
};
@@ -217,20 +235,158 @@ async fn osu_callback(
#[utoipa::path(
get,
path = "/api/auth/refresh",
params(
("server" = Option<String>, Query, description = "服务器类型可选值osu, gu默认为osu"),
),
responses(
(status = 200, description = "令牌刷新成功"),
(status = 401, description = "未授权"),
(status = 400, description = "请求参数错误"),
(status = 401, description = "未授权或refresh_token过期"),
(status = 500, description = "内部服务器错误"),
),
tag = "auth"
)]
async fn refresh_token() -> Result<Json<ApiResponse<AuthResponse>>> {
// 实现令牌刷新逻辑
// 这里需要从请求中获取refresh_token然后调用OSU API刷新令牌
// 暂时返回未实现
Err(crate::error::AppError::NotImplemented(
"Token refresh not implemented".into(),
))
async fn refresh_token(
auth_user: AuthUser,
Query(params): Query<RefreshTokenQuery>,
State(state): State<ServiceState>,
) -> Result<Json<ApiResponse<AuthResponse>>> {
let server = params.server.unwrap_or_else(|| "osu".to_string());
if server != "osu" && server != "gu" {
return Err(crate::error::AppError::ValidationError(
"server参数必须是'osu'或'gu'".into(),
));
}
let user_service = UserService::new(state.db);
// 根据服务器类型获取对应的refresh_token
let refresh_token = match server.as_str() {
"osu" => user_service.get_osu_refresh_token(auth_user.id).await?,
"gu" => user_service.get_gu_refresh_token(auth_user.id).await?,
_ => unreachable!(),
};
let refresh_token = refresh_token.ok_or_else(|| {
crate::error::AppError::Unauthorized(format!("{} refresh_token不存在", server))
})?;
// 根据服务器类型调用相应的API刷新令牌
let (client_id, client_secret, token_url, api_url) = match server.as_str() {
"osu" => (
env::var("OSU_CLIENT_ID").expect("OSU_CLIENT_ID not set"),
env::var("OSU_CLIENT_SECRET").expect("OSU_CLIENT_SECRET not set"),
"https://osu.ppy.sh/oauth/token",
"https://osu.ppy.sh/api/v2/me",
),
"gu" => (
env::var("GU_CLIENT_ID").expect("GU_CLIENT_ID not set"),
env::var("GU_CLIENT_SECRET").expect("GU_CLIENT_SECRET not set"),
"https://lazer-api.g0v0.top/oauth/token",
"https://lazer-api.g0v0.top/api/v2/me",
),
_ => unreachable!(),
};
let client = Client::new();
// 使用refresh_token获取新的访问令牌
let token_response = client
.post(token_url)
.json(&serde_json::json!({
"client_id": client_id,
"client_secret": client_secret,
"refresh_token": refresh_token,
"grant_type": "refresh_token"
}))
.send()
.await
.map_err(|e| {
// 如果refresh_token过期返回401提示重新登录
if e.status() == Some(reqwest::StatusCode::UNAUTHORIZED) {
crate::error::AppError::Unauthorized(format!(
"{} refresh_token已过期请重新登录",
server
))
} else {
crate::error::AppError::ExternalApiError(format!("Failed to refresh token: {}", e))
}
})?
.json::<OsuTokenResponse>()
.await
.map_err(|e| {
crate::error::AppError::ExternalApiError(format!(
"Failed to parse token response: {}",
e
))
})?;
// 获取用户信息
let user_data = client
.get(api_url)
.header(
"Authorization",
format!("Bearer {}", token_response.access_token),
)
.send()
.await
.map_err(|e| {
crate::error::AppError::ExternalApiError(format!("Failed to get user info: {}", e))
})?
.json::<OsuUserData>()
.await
.map_err(|e| {
crate::error::AppError::ExternalApiError(format!("Failed to parse user info: {}", e))
})?;
// 更新用户信息
let update_request = crate::dto::user::UpdateUserRequest {
username: Some(user_data.username.clone()),
avatar_url: user_data.avatar_url,
pp: user_data.statistics.as_ref().and_then(|s| s.pp),
global_rank: user_data.statistics.as_ref().and_then(|s| s.global_rank),
country: user_data.country.as_ref().map(|c| c.code.clone()),
country_rank: user_data.statistics.as_ref().and_then(|s| s.country_rank),
approved: None,
season: None,
};
let user = user_service.update(auth_user.id, update_request).await?;
// 更新数据库中的refresh_token
match server.as_str() {
"osu" => {
user_service
.update_osu_refresh_token(auth_user.id, token_response.refresh_token.clone())
.await?;
}
"gu" => {
user_service
.update_gu_refresh_token(auth_user.id, token_response.refresh_token.clone())
.await?;
}
_ => unreachable!(),
}
// 生成新的JWT令牌
let osu_id = user.osu_id.clone();
let user_group_str = match user.user_group {
crate::entity::sea_orm_active_enums::UserGroup::Player => "player",
crate::entity::sea_orm_active_enums::UserGroup::Admin => "admin",
};
let jwt_token =
crate::utils::jwt::generate_token(osu_id, user_group_str.to_string()).map_err(|e| {
crate::error::AppError::InternalError(format!("Failed to generate token: {}", e))
})?;
let auth_response = AuthResponse {
access_token: jwt_token,
refresh_token: token_response.refresh_token,
user: crate::dto::user::UserResponse::from(user),
};
Ok(Json(ApiResponse::success(auth_response)))
}
#[utoipa::path(
@@ -238,13 +394,25 @@ async fn refresh_token() -> Result<Json<ApiResponse<AuthResponse>>> {
path = "/api/auth/logout",
responses(
(status = 200, description = "登出成功"),
(status = 401, description = "未授权"),
),
tag = "auth"
)]
async fn logout() -> Result<Json<ApiResponse<()>>> {
// 实现登出逻辑
// 这里需要清除客户端的令牌
Ok(Json(ApiResponse::success_with_message((), "登出成功")))
async fn logout(
auth_user: AuthUser,
State(state): State<ServiceState>,
) -> Result<Json<ApiResponse<()>>> {
// 清除用户的refresh_token
let user_service = UserService::new(state.db);
// 清除所有refresh_tokenOSU和GU
user_service.clear_all_refresh_tokens(auth_user.id).await?;
// 返回成功消息
Ok(Json(ApiResponse::success_with_message(
(),
"登出成功已清除OSU和GU refresh_token",
)))
}
#[utoipa::path(
@@ -340,6 +508,14 @@ async fn gu_callback(
let updated_user_with_gu_info = user_service
.update_gu_server_info(updated_user.id, gu_user_id, username.clone())
.await?;
// 保存 GU refresh_token
user_service
.update_gu_refresh_token(
updated_user_with_gu_info.id,
token_response.refresh_token.clone(),
)
.await?;
updated_user_with_gu_info
}
None => {
@@ -359,6 +535,14 @@ async fn gu_callback(
let new_user_with_gu_info = user_service
.update_gu_server_info(new_user.id, gu_user_id, username.clone())
.await?;
// 保存 GU refresh_token
user_service
.update_gu_refresh_token(
new_user_with_gu_info.id,
token_response.refresh_token.clone(),
)
.await?;
new_user_with_gu_info
}
};

View File

@@ -46,7 +46,7 @@ async fn get_tournament_settings(
State(state): State<ServiceState>,
Query(params): Query<TournamentSettingQueryParams>,
) -> Result<Json<ApiResponse<PaginatedTournamentSettingsResponse>>> {
let service = TournamentSettingService::new(state.db);
let service = TournamentSettingService::new(state);
let page = params.page.unwrap_or(1);
let page_size = params.page_size.unwrap_or(20);
let (settings, total) = service.find_all(params).await?;
@@ -96,10 +96,10 @@ async fn update_tournament_settings(
// 验证用户是否为admin
require_admin(&auth_user)?;
let service = TournamentSettingService::new(state.db);
let service = TournamentSettingService::new(state);
// 检查是否存在设置,如果不存在则初始化
let settings = match service.get_current_settings().await? {
let settings = match service.get_current_settings_cached().await? {
Some(existing) => {
// 更新现有设置
service.update(existing.id, data).await?

View File

@@ -23,13 +23,16 @@ pub fn routes() -> Router<ServiceState> {
Router::new()
.route("/", get(get_users))
.route("/", post(create_user))
.route("/{id}", get(get_user))
.route("/{id}", put(update_user))
.route("/{id}", delete(delete_user))
.route("/{id}/user-group", put(update_user_group))
.route("/{id}/registration-status", put(update_registration_status))
.route("/{id}/season", put(update_season))
.route("/{id}/approve", put(update_approved))
.route("/{osu_id}", get(get_user))
.route("/{osu_id}", put(update_user))
.route("/{osu_id}", delete(delete_user))
.route("/{osu_id}/user-group", put(update_user_group))
.route(
"/{osu_id}/registration-status",
put(update_registration_status),
)
.route("/{osu_id}/season", put(update_season))
.route("/{osu_id}/approve", put(update_approved))
}
#[utoipa::path(
@@ -37,7 +40,7 @@ pub fn routes() -> Router<ServiceState> {
path = "/api/users",
params(UserQueryParams),
responses(
(status = 200, description = "获取用户列表成功", body = PaginatedUserResponse),
(status = 200, description = "获取osu玩家列表成功", body = PaginatedUserResponse),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
@@ -62,22 +65,25 @@ async fn get_users(
#[utoipa::path(
get,
path = "/api/users/{id}",
path = "/api/users/{osu_id}",
params(
("id" = i32, Path, description = "用户ID")
("osu_id" = String, Path, description = "osuID")
),
responses(
(status = 200, description = "获取用户成功", body = UserResponse),
(status = 404, description = "用户不存在"),
(status = 200, description = "获取osu玩家成功", body = UserResponse),
(status = 404, description = "osu玩家不存在"),
),
tag = "users"
)]
async fn get_user(
State(state): State<ServiceState>,
Path(id): Path<i32>,
Path(osu_id): Path<String>,
) -> Result<Json<ApiResponse<UserResponse>>> {
let service = UserService::new(state.db);
let user = service.find_by_id(id).await?;
let user = service
.find_by_osu_id(&osu_id)
.await?
.ok_or_else(|| crate::error::AppError::NotFound(format!("osu玩家 {} 不存在", osu_id)))?;
Ok(Json(ApiResponse::success(UserResponse::from(user))))
}
@@ -87,80 +93,83 @@ async fn get_user(
path = "/api/users",
request_body = CreateUserRequest,
responses(
(status = 201, description = "创建用户成功", body = UserResponse),
(status = 201, description = "创建osu玩家成功", body = UserResponse),
(status = 400, description = "请求参数错误"),
(status = 401, description = "未授权"),
),
tag = "users"
)]
async fn create_user() -> Result<Json<ApiResponse<UserResponse>>> {
// 用户创建将通过OAuth回调处理
// osu玩家创建将通过OAuth回调处理
Err(crate::error::AppError::BusinessError(
"请通过OAuth注册用户".into(),
"请通过OAuth注册osu玩家".into(),
))
}
#[utoipa::path(
put,
path = "/api/users/{id}",
path = "/api/users/{osu_id}",
params(
("id" = i32, Path, description = "用户ID")
("osu_id" = String, Path, description = "osu玩家ID")
),
request_body = UpdateUserRequest,
responses(
(status = 200, description = "更新用户成功", body = UserResponse),
(status = 200, description = "更新osu玩家成功", body = UserResponse),
(status = 400, description = "请求参数错误"),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
(status = 404, description = "用户不存在"),
(status = 404, description = "osu玩家不存在"),
),
tag = "users"
)]
#[axum::debug_handler]
async fn update_user(
State(state): State<ServiceState>,
Path(id): Path<i32>,
Path(osu_id): Path<String>,
auth_user: AuthUser,
Json(data): Json<UpdateUserRequest>,
) -> Result<Json<ApiResponse<UserResponse>>> {
// 检查是否为管理员,或者是否更新自己的信息
// 注意这里需要从数据库获取用户的osu_id来匹配
// 暂时简化处理,实际项目中需要查询数据库
let service = UserService::new(state.db);
// 获取要更新的osu玩家信息
let target_user = service
.find_by_osu_id(&osu_id)
.await?
.ok_or_else(|| crate::error::AppError::NotFound(format!("osu玩家 {} 不存在", osu_id)))?;
// 检查权限管理员或osu玩家本人可以更新
let is_admin = auth_user.user_group == "admin";
let is_self = auth_user.osu_id == id.to_string(); // 这里需要根据实际情况调整
let is_self = auth_user.osu_id == target_user.osu_id;
if !is_admin && !is_self {
return Err(crate::error::AppError::Forbidden(
"只有管理员或用户本人可以更新用户信息".into(),
));
return Err(crate::error::AppError::Forbidden("权限错误".into()));
}
let service = UserService::new(state.db);
let user = service.update(id, data).await?;
let user = service.update(target_user.id, data).await?;
Ok(Json(ApiResponse::success(UserResponse::from(user))))
}
#[utoipa::path(
put,
path = "/api/users/{id}/user-group",
path = "/api/users/{osu_id}/user-group",
params(
("id" = i32, Path, description = "用户ID")
("osu_id" = String, Path, description = "osuID")
),
request_body = UpdateUserGroupRequest,
responses(
(status = 200, description = "更新用户组成功", body = UserResponse),
(status = 200, description = "更新osu玩家组成功", body = UserResponse),
(status = 400, description = "请求参数错误"),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
(status = 404, description = "用户不存在"),
(status = 404, description = "osu玩家不存在"),
),
tag = "users"
)]
#[axum::debug_handler]
async fn update_user_group(
State(state): State<ServiceState>,
Path(id): Path<i32>,
Path(osu_id): Path<String>,
user: AuthUser,
Json(data): Json<UpdateUserGroupRequest>,
) -> Result<Json<ApiResponse<UserResponse>>> {
@@ -168,96 +177,121 @@ async fn update_user_group(
require_admin(&user)?;
let service = UserService::new(state.db);
let user = service.update_user_group(id, data).await?;
let target_user = service
.find_by_osu_id(&osu_id)
.await?
.ok_or_else(|| crate::error::AppError::NotFound(format!("osu玩家 {} 不存在", osu_id)))?;
let user = service.update_user_group(target_user.id, data).await?;
Ok(Json(ApiResponse::success(UserResponse::from(user))))
}
#[utoipa::path(
delete,
path = "/api/users/{id}",
path = "/api/users/{osu_id}",
params(
("id" = i32, Path, description = "用户ID")
("osu_id" = String, Path, description = "osuID")
),
responses(
(status = 200, description = "删除用户成功"),
(status = 200, description = "删除osu玩家成功"),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
(status = 404, description = "用户不存在"),
(status = 404, description = "osu玩家不存在"),
),
tag = "users"
)]
async fn delete_user(
State(state): State<ServiceState>,
Path(id): Path<i32>,
Path(osu_id): Path<String>,
user: AuthUser,
) -> Result<Json<ApiResponse<()>>> {
// 检查是否为管理员
require_admin(&user)?;
let service = UserService::new(state.db);
service.delete(id).await?;
let target_user = service
.find_by_osu_id(&osu_id)
.await?
.ok_or_else(|| crate::error::AppError::NotFound(format!("osu玩家 {} 不存在", osu_id)))?;
Ok(Json(ApiResponse::success_with_message((), "用户删除成功")))
service.delete(target_user.id).await?;
Ok(Json(ApiResponse::success_with_message(
(),
"osu玩家删除成功",
)))
}
#[utoipa::path(
put,
path = "/api/users/{id}/registration-status",
path = "/api/users/{osu_id}/registration-status",
params(
("id" = i32, Path, description = "用户ID")
("osu_id" = String, Path, description = "osuID")
),
request_body = UpdateRegistrationStatusRequest,
responses(
(status = 200, description = "更新注册状态成功", body = UserResponse),
(status = 400, description = "请求参数错误"),
(status = 401, description = "未授权"),
(status = 404, description = "用户不存在"),
(status = 404, description = "osu玩家不存在"),
),
tag = "users"
)]
async fn update_registration_status(
State(state): State<ServiceState>,
Path(id): Path<i32>,
Path(osu_id): Path<String>,
Json(data): Json<UpdateRegistrationStatusRequest>,
) -> Result<Json<ApiResponse<UserResponse>>> {
let service = UserService::new(state.db);
let user = service.update_registration_status(id, data).await?;
let target_user = service
.find_by_osu_id(&osu_id)
.await?
.ok_or_else(|| crate::error::AppError::NotFound(format!("osu玩家 {} 不存在", osu_id)))?;
let user = service
.update_registration_status(target_user.id, data)
.await?;
Ok(Json(ApiResponse::success(UserResponse::from(user))))
}
#[utoipa::path(
put,
path = "/api/users/{id}/season",
path = "/api/users/{osu_id}/season",
params(
("id" = i32, Path, description = "用户ID")
("osu_id" = String, Path, description = "osuID")
),
request_body = UpdateSeasonRequest,
responses(
(status = 200, description = "更新赛季成功", body = UserResponse),
(status = 400, description = "请求参数错误"),
(status = 401, description = "未授权"),
(status = 404, description = "用户不存在"),
(status = 404, description = "osu玩家不存在"),
),
tag = "users"
)]
async fn update_season(
State(state): State<ServiceState>,
Path(id): Path<i32>,
Path(osu_id): Path<String>,
Json(data): Json<UpdateSeasonRequest>,
) -> Result<Json<ApiResponse<UserResponse>>> {
let service = UserService::new(state.db);
let user = service.update_season(id, data).await?;
let target_user = service
.find_by_osu_id(&osu_id)
.await?
.ok_or_else(|| crate::error::AppError::NotFound(format!("osu玩家 {} 不存在", osu_id)))?;
let user = service.update_season(target_user.id, data).await?;
Ok(Json(ApiResponse::success(UserResponse::from(user))))
}
#[utoipa::path(
put,
path = "/api/users/{id}/approve",
path = "/api/users/{osu_id}/approve",
params(
("id" = i32, Path, description = "用户ID")
("osu_id" = String, Path, description = "osuID")
),
request_body = UpdateApprovedRequest,
responses(
@@ -265,14 +299,14 @@ async fn update_season(
(status = 400, description = "请求参数错误"),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
(status = 404, description = "用户不存在"),
(status = 404, description = "osu玩家不存在"),
),
tag = "users"
)]
#[axum::debug_handler]
async fn update_approved(
State(state): State<ServiceState>,
Path(id): Path<i32>,
Path(osu_id): Path<String>,
user: AuthUser,
Json(data): Json<UpdateApprovedRequest>,
) -> Result<Json<ApiResponse<UserResponse>>> {
@@ -280,7 +314,12 @@ async fn update_approved(
require_admin(&user)?;
let service = UserService::new(state.db);
let user = service.update_approved(id, data, true).await?;
let target_user = service
.find_by_osu_id(&osu_id)
.await?
.ok_or_else(|| crate::error::AppError::NotFound(format!("osu玩家 {} 不存在", osu_id)))?;
let user = service.update_approved(target_user.id, data, true).await?;
Ok(Json(ApiResponse::success(UserResponse::from(user))))
}

View File

@@ -24,6 +24,12 @@ pub struct Config {
pub osu_redirect_uri: String,
pub gu_client_id: String,
pub gu_client_secret: String,
pub gu_redirect_uri: String,
pub cors_origin: String,
pub log_level: String,
@@ -65,6 +71,16 @@ impl Config {
crate::error::AppError::ConfigError("OSU_REDIRECT_URI未设置".into())
})?,
gu_client_id: env::var("GU_CLIENT_ID")
.map_err(|_| crate::error::AppError::ConfigError("GU_CLIENT_ID未设置".into()))?,
gu_client_secret: env::var("GU_CLIENT_SECRET").map_err(|_| {
crate::error::AppError::ConfigError("GU_CLIENT_SECRET未设置".into())
})?,
gu_redirect_uri: env::var("GU_REDIRECT_URI")
.map_err(|_| crate::error::AppError::ConfigError("GU_REDIRECT_URI未设置".into()))?,
cors_origin: env::var("CORS_ORIGIN")
.unwrap_or_else(|_| "http://localhost:3000".to_string()),

View File

@@ -25,6 +25,8 @@ pub struct Model {
pub registration_status: Option<RegistrationStatus>,
pub gu_server_user_id: Option<i32>,
pub gu_server_username: Option<String>,
pub osu_refresh_token: Option<String>,
pub gu_refresh_token: Option<String>,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
}

View File

@@ -9,11 +9,14 @@ pub mod tournament_settings;
pub mod user;
use sea_orm::DatabaseConnection;
use std::sync::Arc;
use tokio::sync::RwLock;
/// 服务层共享状态
#[derive(Clone)]
pub struct ServiceState {
pub db: DatabaseConnection,
pub tournament_settings_cache: Arc<RwLock<Option<crate::entity::tournament_settings::Model>>>,
}
/// 确保 ServiceState 实现了 Send + Sync traits
@@ -23,6 +26,9 @@ unsafe impl Sync for ServiceState {}
impl ServiceState {
/// 创建新的服务状态
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
Self {
db,
tournament_settings_cache: Arc::new(RwLock::new(None)),
}
}
}

View File

@@ -2,6 +2,8 @@ use sea_orm::{
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, PaginatorTrait, QueryFilter,
QueryOrder, Set,
};
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::{
dto::tournament_settings::{
@@ -10,15 +12,20 @@ use crate::{
},
entity::tournament_settings,
error::{AppError, Result},
service::ServiceState,
};
pub struct TournamentSettingService {
db: DatabaseConnection,
current_settings_cache: Arc<RwLock<Option<tournament_settings::Model>>>,
}
impl TournamentSettingService {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
pub fn new(state: ServiceState) -> Self {
Self {
db: state.db,
current_settings_cache: state.tournament_settings_cache,
}
}
pub async fn find_all(
@@ -71,7 +78,13 @@ impl TournamentSettingService {
..Default::default()
};
Ok(tournament_setting.insert(&self.db).await?)
let result = tournament_setting.insert(&self.db).await?;
// 更新缓存
let mut cache = self.current_settings_cache.write().await;
*cache = Some(result.clone());
Ok(result)
}
pub async fn update(
@@ -108,10 +121,29 @@ impl TournamentSettingService {
tournament_setting.updated_at = Set(chrono::Utc::now());
Ok(tournament_setting.update(&self.db).await?)
let result = tournament_setting.update(&self.db).await?;
// 更新缓存
let mut cache = self.current_settings_cache.write().await;
*cache = Some(result.clone());
Ok(result)
}
pub async fn delete(&self, id: i32) -> Result<()> {
// 检查缓存中是否有这个项目
{
let cache = self.current_settings_cache.read().await;
if let Some(cached_settings) = cache.as_ref() {
if cached_settings.id == id {
// 删除的是缓存的项目,清除缓存
drop(cache); // 释放读锁
let mut cache = self.current_settings_cache.write().await;
*cache = None;
}
}
}
let result = tournament_settings::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
@@ -127,4 +159,33 @@ impl TournamentSettingService {
.one(&self.db)
.await?)
}
pub async fn get_current_settings_cached(&self) -> Result<Option<tournament_settings::Model>> {
// 首先尝试从缓存读取
{
let cache = self.current_settings_cache.read().await;
if let Some(settings) = cache.as_ref() {
return Ok(Some(settings.clone()));
}
}
// 缓存未命中,从数据库读取
let settings = tournament_settings::Entity::find()
.order_by_desc(tournament_settings::Column::CreatedAt)
.one(&self.db)
.await?;
// 更新缓存
if let Some(ref settings) = settings {
let mut cache = self.current_settings_cache.write().await;
*cache = Some(settings.clone());
}
Ok(settings)
}
pub async fn invalidate_cache(&self) {
let mut cache = self.current_settings_cache.write().await;
*cache = None;
}
}

View File

@@ -4,8 +4,14 @@ use sea_orm::{
};
use crate::{
dto::user::{CreateUserRequest, UpdateApprovedRequest, UpdateRegistrationStatusRequest, UpdateSeasonRequest, UpdateUserGroupRequest, UpdateUserRequest, UserQueryParams},
entity::{sea_orm_active_enums::{RegistrationStatus, UserGroup}, user},
dto::user::{
CreateUserRequest, UpdateApprovedRequest, UpdateRegistrationStatusRequest,
UpdateSeasonRequest, UpdateUserGroupRequest, UpdateUserRequest, UserQueryParams,
},
entity::{
sea_orm_active_enums::{RegistrationStatus, UserGroup},
user,
},
error::{AppError, Result},
};
@@ -165,7 +171,11 @@ impl UserService {
.await?)
}
pub async fn update_registration_status(&self, id: i32, data: UpdateRegistrationStatusRequest) -> Result<user::Model> {
pub async fn update_registration_status(
&self,
id: i32,
data: UpdateRegistrationStatusRequest,
) -> Result<user::Model> {
let status_enum = match data.registration_status.as_str() {
"pending" => RegistrationStatus::Pending,
"confirmed" => RegistrationStatus::Confirmed,
@@ -188,7 +198,12 @@ impl UserService {
Ok(user.update(&self.db).await?)
}
pub async fn update_approved(&self, id: i32, data: UpdateApprovedRequest, is_admin: bool) -> Result<user::Model> {
pub async fn update_approved(
&self,
id: i32,
data: UpdateApprovedRequest,
is_admin: bool,
) -> Result<user::Model> {
if !is_admin {
return Err(AppError::Forbidden("只有管理员可以更新审批状态".into()));
}
@@ -208,7 +223,12 @@ impl UserService {
Ok(user.update(&self.db).await?)
}
pub async fn update_gu_server_info(&self, id: i32, gu_user_id: i32, gu_username: String) -> Result<user::Model> {
pub async fn update_gu_server_info(
&self,
id: i32,
gu_user_id: i32,
gu_username: String,
) -> Result<user::Model> {
let mut user: user::ActiveModel = self.find_by_id(id).await?.into();
user.gu_server_user_id = Set(Some(gu_user_id));
user.gu_server_username = Set(Some(gu_username));
@@ -216,4 +236,63 @@ impl UserService {
Ok(user.update(&self.db).await?)
}
pub async fn update_osu_refresh_token(
&self,
id: i32,
refresh_token: String,
) -> Result<user::Model> {
let mut user: user::ActiveModel = self.find_by_id(id).await?.into();
user.osu_refresh_token = Set(Some(refresh_token));
user.updated_at = Set(chrono::Utc::now());
Ok(user.update(&self.db).await?)
}
pub async fn update_gu_refresh_token(
&self,
id: i32,
refresh_token: String,
) -> Result<user::Model> {
let mut user: user::ActiveModel = self.find_by_id(id).await?.into();
user.gu_refresh_token = Set(Some(refresh_token));
user.updated_at = Set(chrono::Utc::now());
Ok(user.update(&self.db).await?)
}
pub async fn get_osu_refresh_token(&self, id: i32) -> Result<Option<String>> {
let user = self.find_by_id(id).await?;
Ok(user.osu_refresh_token)
}
pub async fn get_gu_refresh_token(&self, id: i32) -> Result<Option<String>> {
let user = self.find_by_id(id).await?;
Ok(user.gu_refresh_token)
}
pub async fn clear_osu_refresh_token(&self, id: i32) -> Result<user::Model> {
let mut user: user::ActiveModel = self.find_by_id(id).await?.into();
user.osu_refresh_token = Set(None);
user.updated_at = Set(chrono::Utc::now());
Ok(user.update(&self.db).await?)
}
pub async fn clear_gu_refresh_token(&self, id: i32) -> Result<user::Model> {
let mut user: user::ActiveModel = self.find_by_id(id).await?.into();
user.gu_refresh_token = Set(None);
user.updated_at = Set(chrono::Utc::now());
Ok(user.update(&self.db).await?)
}
pub async fn clear_all_refresh_tokens(&self, id: i32) -> Result<user::Model> {
let mut user: user::ActiveModel = self.find_by_id(id).await?.into();
user.osu_refresh_token = Set(None);
user.gu_refresh_token = Set(None);
user.updated_at = Set(chrono::Utc::now());
Ok(user.update(&self.db).await?)
}
}