From 8791aee96c6414fa7e2a13cc111573ba3f83f701 Mon Sep 17 00:00:00 2001 From: soul-walker <31162815+soul-walker@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:27:23 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=84=E7=BB=87=E3=80=81=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=AD=89=E6=95=B0=E6=8D=AE=E8=AE=BF=E9=97=AE=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E8=B0=83=E6=95=B4=20=E8=8E=B7=E5=8F=96=E7=BB=84=E7=BB=87?= =?UTF-8?q?=E3=80=81=E7=94=A8=E6=88=B7=E7=AD=89=E6=8E=A5=E5=8F=A3=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=88=96=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/rtsa_db/src/db_access/org.rs | 44 ++++++++------------ crates/rtsa_db/src/db_access/org_user.rs | 11 +++-- crates/rtsa_db/src/db_access/user.rs | 38 +++++++++++++----- manager/src/apis/org.rs | 11 +++++ manager/src/apis/org_user.rs | 33 +++++++++++++++ manager/src/apis/user.rs | 47 +++++++++++++++++++++- manager/src/user_auth/mod.rs | 51 ++++++++++++++++++++++++ 7 files changed, 193 insertions(+), 42 deletions(-) diff --git a/crates/rtsa_db/src/db_access/org.rs b/crates/rtsa_db/src/db_access/org.rs index abaab4a..505603d 100644 --- a/crates/rtsa_db/src/db_access/org.rs +++ b/crates/rtsa_db/src/db_access/org.rs @@ -36,11 +36,6 @@ pub trait OrgAccessor { &self, parent_id: i32, ) -> Result, DbAccessError>; - /// 通过id列表获取组织code和name - async fn query_org_name_by_ids( - &self, - ids: Vec, - ) -> Result, DbAccessError>; /// 更新组织名称 async fn update_org_name( &self, @@ -175,9 +170,14 @@ impl OrgAccessor for RtsaDbAccessor { ); let row = sqlx::query_as::<_, OrganizationModel>(&sql) .bind(id) - .fetch_one(&self.pool) + .fetch_optional(&self.pool) .await?; - Ok(row) + match row { + Some(row) => Ok(row), + None => Err(DbAccessError::RowNotExist( + OrganizationColumn::Table.name().to_string(), + )), + } } async fn query_org_names(&self, ids: &[i32]) -> Result, DbAccessError> { @@ -204,9 +204,14 @@ impl OrgAccessor for RtsaDbAccessor { ); let row = sqlx::query_as::<_, OrganizationModel>(&sql) .bind(code) - .fetch_one(&self.pool) + .fetch_optional(&self.pool) .await?; - Ok(row) + match row { + Some(row) => Ok(row), + None => Err(DbAccessError::RowNotExist( + OrganizationColumn::Table.name().to_string(), + )), + } } async fn query_org_top_page( @@ -292,23 +297,6 @@ impl OrgAccessor for RtsaDbAccessor { Ok(rows) } - async fn query_org_name_by_ids( - &self, - ids: Vec, - ) -> Result, DbAccessError> { - let table = OrganizationColumn::Table.name(); - let id_column = OrganizationColumn::Id.name(); - let name_column = OrganizationColumn::Name.name(); - let sql = format!( - "SELECT {id_column}, {name_column} FROM {table} WHERE {id_column} = ANY($1)", - id_column = id_column, - name_column = name_column, - table = table - ); - let rows: Vec<(i32, String)> = sqlx::query_as(&sql).bind(ids).fetch_all(&self.pool).await?; - Ok(rows) - } - async fn update_org_name( &self, id: i32, @@ -642,7 +630,7 @@ mod tests { } #[sqlx::test(migrator = "crate::MIGRATOR")] - async fn test_query_org_name_by_ids(pool: PgPool) -> Result<(), DbAccessError> { + async fn test_query_org_names(pool: PgPool) -> Result<(), DbAccessError> { let accessor = RtsaDbAccessor::new(pool); let user = init_user(&accessor).await?; @@ -661,7 +649,7 @@ mod tests { } // Query organization names by ids - let names = accessor.query_org_name_by_ids(ids).await?; + let names = accessor.query_org_names(&ids).await?; assert_eq!(names.len(), 3); Ok(()) diff --git a/crates/rtsa_db/src/db_access/org_user.rs b/crates/rtsa_db/src/db_access/org_user.rs index d35a506..e2041ce 100644 --- a/crates/rtsa_db/src/db_access/org_user.rs +++ b/crates/rtsa_db/src/db_access/org_user.rs @@ -387,9 +387,14 @@ impl OrgUserAccessor for RtsaDbAccessor { let org_user = sqlx::query_as(&query_clause) .bind(org_id) .bind(user_id) - .fetch_one(&self.pool) + .fetch_optional(&self.pool) .await?; - Ok(org_user) + match org_user { + Some(org_user) => Ok(org_user), + None => Err(DbAccessError::RowNotExist( + OrganizationUserColumn::Table.name().to_string(), + )), + } } async fn query_org_user_by_student_id( @@ -528,7 +533,7 @@ mod tests { accessor.unbind_org_user(org.id, new_user.id).await?; // Verify the user is unbound let result = accessor.query_org_user(org.id, new_user.id).await; - assert!(matches!(result, Err(DbAccessError::SqlxError(_)))); + assert!(matches!(result, Err(DbAccessError::RowNotExist(_)))); Ok(()) } diff --git a/crates/rtsa_db/src/db_access/user.rs b/crates/rtsa_db/src/db_access/user.rs index 94a275c..167d070 100644 --- a/crates/rtsa_db/src/db_access/user.rs +++ b/crates/rtsa_db/src/db_access/user.rs @@ -344,11 +344,16 @@ impl UserAccessor for RtsaDbAccessor { let table = UserColumn::Table.name(); let id_column = UserColumn::Id.name(); let query_clause = format!("SELECT * FROM {table} WHERE {id_column} = {id}",); - let user: UserModel = sqlx::query_as(&query_clause) + let user = sqlx::query_as(&query_clause) .bind(id) - .fetch_one(&self.pool) + .fetch_optional(&self.pool) .await?; - Ok(user) + match user { + None => Err(DbAccessError::RowNotExist( + UserColumn::Table.name().to_string(), + )), + Some(user) => Ok(user), + } } async fn query_user_by_username(&self, username: &str) -> Result { @@ -361,9 +366,14 @@ impl UserAccessor for RtsaDbAccessor { ); let user = sqlx::query_as(&query_clause) .bind(username) - .fetch_one(&self.pool) + .fetch_optional(&self.pool) .await?; - Ok(user) + match user { + None => Err(DbAccessError::RowNotExist( + UserColumn::Table.name().to_string(), + )), + Some(user) => Ok(user), + } } async fn query_user_by_email(&self, email: &str) -> Result { @@ -376,9 +386,14 @@ impl UserAccessor for RtsaDbAccessor { ); let user = sqlx::query_as(&query_clause) .bind(email) - .fetch_one(&self.pool) + .fetch_optional(&self.pool) .await?; - Ok(user) + match user { + None => Err(DbAccessError::RowNotExist( + UserColumn::Table.name().to_string(), + )), + Some(user) => Ok(user), + } } async fn query_user_by_mobile(&self, mobile: &str) -> Result { @@ -391,9 +406,14 @@ impl UserAccessor for RtsaDbAccessor { ); let user = sqlx::query_as(&query_clause) .bind(mobile) - .fetch_one(&self.pool) + .fetch_optional(&self.pool) .await?; - Ok(user) + match user { + None => Err(DbAccessError::RowNotExist( + UserColumn::Table.name().to_string(), + )), + Some(user) => Ok(user), + } } async fn update_user_info(&self, id: i32, info: Value) -> Result { diff --git a/manager/src/apis/org.rs b/manager/src/apis/org.rs index e4aed07..745e691 100644 --- a/manager/src/apis/org.rs +++ b/manager/src/apis/org.rs @@ -28,6 +28,17 @@ impl OrgQuery { Ok(org.into()) } + /// 通过组织代码获取组织信息 + async fn get_org_by_code( + &self, + ctx: &Context<'_>, + code: String, + ) -> async_graphql::Result { + let dba = ctx.data::()?; + let org = dba.query_org_by_code(&code).await?; + Ok(org.into()) + } + /// 获取默认组织信息 async fn get_default_org(&self, ctx: &Context<'_>) -> async_graphql::Result { let dba = ctx.data::()?; diff --git a/manager/src/apis/org_user.rs b/manager/src/apis/org_user.rs index 61353a4..70812c6 100644 --- a/manager/src/apis/org_user.rs +++ b/manager/src/apis/org_user.rs @@ -8,6 +8,7 @@ use async_graphql::{ use chrono::NaiveDateTime; use rtsa_db::prelude::*; use rtsa_dto::common::Role; +use serde_json::json; use super::{org::OrgId, user::UserId}; @@ -33,6 +34,38 @@ impl OrgUserQuery { .await?; Ok(org_user.into()) } + + /// 获取登录用户的组织用户信息 + #[graphql(guard = "RoleGuard::new(Role::User)")] + async fn login_org_user_info(&self, ctx: &Context<'_>) -> async_graphql::Result { + let claims = ctx.data::()?.decode()?; + let org_user = ctx + .data::()? + .query_org_user(claims.oid, claims.uid) + .await + .or_else(|e| { + match e { + DbAccessError::RowNotExist(_) => { + // 组织用户不存在,构造组织游客角色用户 + let org_user = OrganizationUserModel { + id: 0, + organization_id: claims.oid, + user_id: claims.uid, + student_id: None, + roles: json!([Role::OrgGuest]), + info: None, + creator_id: claims.uid, + created_at: chrono::Local::now(), + updater_id: claims.uid, + updated_at: chrono::Local::now(), + }; + Ok(org_user) + } + _ => Err(e), + } + })?; + Ok(org_user.into()) + } } #[Object] diff --git a/manager/src/apis/user.rs b/manager/src/apis/user.rs index 8b615e1..4440331 100644 --- a/manager/src/apis/user.rs +++ b/manager/src/apis/user.rs @@ -2,7 +2,10 @@ use std::{collections::HashMap, sync::Arc}; use async_graphql::{dataloader::Loader, Context, InputObject, Object, SimpleObject}; use chrono::NaiveDateTime; -use rtsa_db::{model::UserModel, DbAccessError, RtsaDbAccessor, UserAccessor}; +use rtsa_db::{ + model::{OrganizationModel, OrganizationUserModel, UserModel}, + DbAccessError, RtsaDbAccessor, UserAccessor, +}; use serde_json::json; use crate::{ @@ -35,8 +38,8 @@ impl UserQuery { /// 获取用户信息 #[graphql(guard = "RoleGuard::new(Role::User)")] async fn login_user_info(&self, ctx: &Context<'_>) -> async_graphql::Result { - let claims = ctx.data::()?.decode()?; let db_accessor = ctx.data::()?; + let claims = ctx.data::()?.decode()?; let user = db_accessor.query_user(claims.uid).await?; Ok(user.into()) } @@ -170,6 +173,46 @@ impl From for rtsa_db::UserPageFilter { } } +#[derive(Debug, SimpleObject)] +pub struct LoginUserInfoDto { + pub id: i32, + pub username: String, + pub nickname: String, + pub mobile: Option, + pub email: Option, + /// 用户角色 + pub roles: Vec, + /// 组织 + pub org: LoginUserOrgInfoDto, +} + +#[derive(Debug, SimpleObject)] +pub struct LoginUserOrgInfoDto { + pub org_id: i32, + pub org_code: Option, + pub org_name: String, + pub roles: Vec, +} + +impl From<(UserModel, OrganizationUserModel, OrganizationModel)> for LoginUserInfoDto { + fn from(value: (UserModel, OrganizationUserModel, OrganizationModel)) -> Self { + Self { + id: value.0.id, + username: value.0.username.clone(), + nickname: value.0.nickname.clone(), + mobile: value.0.mobile.clone(), + email: value.0.email.clone(), + roles: serde_json::from_value(value.0.roles).unwrap(), + org: LoginUserOrgInfoDto { + org_id: value.2.id, + org_code: value.2.code, + org_name: value.2.name, + roles: serde_json::from_value(value.1.roles).unwrap(), + }, + } + } +} + #[derive(Debug, SimpleObject)] pub struct UserDto { pub id: i32, diff --git a/manager/src/user_auth/mod.rs b/manager/src/user_auth/mod.rs index 54961af..a4fd1c9 100644 --- a/manager/src/user_auth/mod.rs +++ b/manager/src/user_auth/mod.rs @@ -2,6 +2,7 @@ use async_graphql::Guard; use rtsa_db::prelude::*; use rtsa_dto::common::Role; use rtsa_log::tracing::{info, warn}; +use serde_json::json; mod jwt_auth; use crate::{apis::UserLoginDto, error::BusinessError, sys_init::DEFAULT_ORG_CODE}; @@ -120,6 +121,36 @@ pub(crate) async fn handle_login( } } +/// 查询登录用户、组织等信息 +#[allow(dead_code)] +pub(crate) async fn query_login_user_info( + db_accessor: &RtsaDbAccessor, + claims: Claims, +) -> Result<(UserModel, OrganizationUserModel, OrganizationModel), BusinessError> { + let user = db_accessor.query_user(claims.uid).await?; + let org = db_accessor.query_org(claims.oid).await?; + let org_user = db_accessor.query_org_user(claims.oid, claims.uid).await; + match org_user { + Ok(org_user) => Ok((user, org_user, org)), + Err(_) => { + // 组织用户不存在,构造组织游客角色用户 + let org_user = OrganizationUserModel { + id: 0, + organization_id: claims.oid, + user_id: claims.uid, + student_id: None, + roles: json!([Role::OrgGuest]), + info: None, + creator_id: 0, + created_at: chrono::Local::now(), + updater_id: 0, + updated_at: chrono::Local::now(), + }; + Ok((user, org_user, org)) + } + } +} + #[cfg(test)] mod tests { use crate::sys_init::{init_default_user_and_org, ADMIN_USER_NAME, ADMIN_USER_PASSWORD}; @@ -154,4 +185,24 @@ mod tests { assert_eq!(claims.oid, org.id); Ok(()) } + + #[sqlx::test(migrator = "rtsa_db::MIGRATOR")] + async fn test_query_login_user_info(pool: PgPool) -> anyhow::Result<()> { + Logging::default().with_level(Level::DEBUG).init(); + let accessor = RtsaDbAccessor::new(pool); + rtsa_db::set_default_db_accessor(accessor.clone()); + init_default_user_and_org().await.unwrap(); + + let info = UserLoginDto { + username: ADMIN_USER_NAME.to_string(), + password: ADMIN_USER_PASSWORD.to_string(), + org_id: None, + }; + let jwt = handle_login(&accessor, info).await.unwrap(); + let claims = jwt.decode().unwrap(); + let (user, org_user, org) = query_login_user_info(&accessor, claims).await.unwrap(); + assert_eq!(user.username, ADMIN_USER_NAME); + assert_eq!(org_user.organization_id, org.id); + Ok(()) + } }