From 3798572b0c6518b24929c68a39511b0f0831a988 Mon Sep 17 00:00:00 2001 From: soul-walker <31162815+soul-walker@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:45:48 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E8=A1=A8=E7=BB=93=E6=9E=84=E5=AE=9A=E4=B9=89=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0feature=E6=95=B0=E6=8D=AE=E5=BA=93=E8=AE=BF=E9=97=AE?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=8A=E5=AE=9E=E7=8E=B0=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0feature=E5=87=A0=E4=B8=AAapi=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=8F=8A=E5=AE=9E=E7=8E=B0=20=E8=B0=83=E6=95=B4=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E6=A8=A1=E5=9E=8B=E6=9E=9A=E4=B8=BE=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E4=BD=BF=E7=94=A8=E6=9E=9A=E4=B8=BE=E5=8F=96=E4=BB=A3?= =?UTF-8?q?i32=20=E6=B7=BB=E5=8A=A0=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/rtss_api/src/apis/draft_data.rs | 17 +- crates/rtss_api/src/apis/feature.rs | 136 +++++++ crates/rtss_api/src/apis/mod.rs | 12 +- crates/rtss_api/src/apis/release_data.rs | 12 +- crates/rtss_api/src/apis/user.rs | 5 +- crates/rtss_api/src/user_auth/mod.rs | 9 +- crates/rtss_db/src/db_access/draft_data.rs | 73 ++-- crates/rtss_db/src/db_access/feature.rs | 351 +++++++++++++++++++ crates/rtss_db/src/db_access/mod.rs | 2 + crates/rtss_db/src/db_access/release_data.rs | 32 +- crates/rtss_db/src/model.rs | 139 ++------ crates/rtss_dto/build.rs | 18 +- crates/rtss_dto/src/pb/common.rs | 83 ++++- crates/rtss_dto/src/pb/iscs_graphic_data.rs | 44 ++- docs/DESIGN.md | 8 + docs/image-1.png | Bin 0 -> 11049 bytes migrations/20240830095636_init.up.sql | 157 +++++---- rtss-proto-msg | 2 +- 18 files changed, 802 insertions(+), 298 deletions(-) create mode 100644 crates/rtss_api/src/apis/feature.rs create mode 100644 crates/rtss_db/src/db_access/feature.rs create mode 100644 docs/DESIGN.md create mode 100644 docs/image-1.png diff --git a/crates/rtss_api/src/apis/draft_data.rs b/crates/rtss_api/src/apis/draft_data.rs index e811f96..7166080 100644 --- a/crates/rtss_api/src/apis/draft_data.rs +++ b/crates/rtss_api/src/apis/draft_data.rs @@ -5,7 +5,7 @@ use base64::Engine; use chrono::NaiveDateTime; use rtss_db::DraftDataAccessor; use rtss_db::RtssDbAccessor; -use rtss_dto::common::DataType; +use rtss_dto::common::{DataType, Role}; use serde_json::Value; use crate::apis::{PageDto, PageQueryDto}; @@ -15,7 +15,7 @@ use super::common::{DataOptions, IscsDataOptions}; use super::release_data::ReleaseDataId; use super::user::UserId; -use crate::user_auth::{Role, RoleGuard, Token, UserAuthCache}; +use crate::user_auth::{RoleGuard, Token, UserAuthCache}; #[derive(Default)] pub struct DraftDataQuery; @@ -35,7 +35,7 @@ impl DraftDataQuery { ) -> async_graphql::Result> { let db_accessor = ctx.data::()?; let paging_result = db_accessor - .query_draft_data(query.into(), paging.into()) + .paging_query_draft_data(query.into(), paging.into()) .await?; Ok(paging_result.into()) } @@ -55,7 +55,7 @@ impl DraftDataQuery { query.data_type = Some(DataType::Iscs); let db_accessor = ctx.data::()?; let paging_result = db_accessor - .query_draft_data(query.into(), paging.into()) + .paging_query_draft_data(query.into(), paging.into()) .await?; Ok(paging_result.into()) } @@ -70,7 +70,7 @@ impl DraftDataQuery { let db_accessor = ctx.data::()?; query.data_type = Some(DataType::Iscs); let paging_result = db_accessor - .query_draft_data(query.into(), paging.into()) + .paging_query_draft_data(query.into(), paging.into()) .await?; Ok(paging_result.into()) } @@ -86,6 +86,7 @@ impl DraftDataQuery { async fn draft_data_exist( &self, ctx: &Context<'_>, + data_type: DataType, name: String, ) -> async_graphql::Result { let user = ctx @@ -94,7 +95,9 @@ impl DraftDataQuery { .await?; let user_id = user.id_i32(); let db_accessor = ctx.data::()?; - let exist = db_accessor.is_draft_data_exist(user_id, &name).await?; + let exist = db_accessor + .is_draft_data_exist(user_id, &data_type, &name) + .await?; Ok(exist) } } @@ -328,7 +331,7 @@ impl From for DraftDataDto { Self { id: value.id, name: value.name, - data_type: DataType::try_from(value.data_type).unwrap(), + data_type: value.data_type, options: value.options, data: value .data diff --git a/crates/rtss_api/src/apis/feature.rs b/crates/rtss_api/src/apis/feature.rs new file mode 100644 index 0000000..c548b99 --- /dev/null +++ b/crates/rtss_api/src/apis/feature.rs @@ -0,0 +1,136 @@ +use async_graphql::{ + dataloader::DataLoader, ComplexObject, Context, InputObject, Object, SimpleObject, +}; +use chrono::NaiveDateTime; +use rtss_db::{FeatureAccessor, RtssDbAccessor}; +use rtss_dto::common::FeatureType; +use serde_json::Value; + +use crate::{ + apis::{PageDto, PageQueryDto}, + loader::RtssDbLoader, +}; + +use super::user::UserId; + +#[derive(Default)] +pub struct FeatureQuery; + +#[derive(Default)] +pub struct FeatureMutation; + +#[Object] +impl FeatureQuery { + /// 分页查询特征(系统管理) + async fn feature_paging( + &self, + ctx: &Context<'_>, + page: PageQueryDto, + query: FeatureQueryDto, + ) -> async_graphql::Result> { + let dba = ctx.data::()?; + let paging = dba + .paging_query_features(page.into(), &query.into()) + .await?; + Ok(paging.into()) + } + + /// id获取特征 + async fn feature(&self, ctx: &Context<'_>, id: i32) -> async_graphql::Result { + let dba = ctx.data::()?; + let feature = dba.get_feature(id).await?; + Ok(feature.into()) + } + + /// id列表获取特征 + async fn features( + &self, + ctx: &Context<'_>, + ids: Vec, + ) -> async_graphql::Result> { + let dba = ctx.data::()?; + let features = dba.get_features(ids.as_slice()).await?; + Ok(features.into_iter().map(|f| f.into()).collect()) + } +} + +#[Object] +impl FeatureMutation { + /// 上下架特征 + async fn publish_feature( + &self, + ctx: &Context<'_>, + id: i32, + is_published: bool, + ) -> async_graphql::Result { + let dba = ctx.data::()?; + let feature = dba.set_feature_published(id, is_published).await?; + Ok(feature.into()) + } +} + +#[derive(Debug, InputObject)] +pub struct FeatureQueryDto { + pub name: Option, + pub feature_type: Option, + pub is_published: Option, +} + +impl From for rtss_db::FeaturePagingFilter { + fn from(value: FeatureQueryDto) -> Self { + Self { + name: value.name, + feature_type: value.feature_type, + is_published: value.is_published, + } + } +} + +#[derive(Debug, SimpleObject)] +#[graphql(complex)] +pub struct FeatureDto { + pub id: i32, + pub feature_type: FeatureType, + pub name: String, + pub description: String, + pub config: Value, + pub is_published: bool, + pub creator_id: i32, + pub updater_id: i32, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +#[ComplexObject] +impl FeatureDto { + /// 创建用户name + async fn creator_name(&self, ctx: &Context<'_>) -> async_graphql::Result> { + let loader = ctx.data_unchecked::>(); + let name = loader.load_one(UserId::new(self.creator_id)).await?; + Ok(name) + } + + /// 更新用户name + async fn updater_name(&self, ctx: &Context<'_>) -> async_graphql::Result> { + let loader = ctx.data_unchecked::>(); + let name = loader.load_one(UserId::new(self.updater_id)).await?; + Ok(name) + } +} + +impl From for FeatureDto { + fn from(value: rtss_db::model::FeatureModel) -> Self { + Self { + id: value.id, + feature_type: value.feature_type, + name: value.name, + description: value.description, + config: value.config, + is_published: value.is_published, + creator_id: value.creator_id, + updater_id: value.updater_id, + created_at: value.created_at.naive_local(), + updated_at: value.updated_at.naive_local(), + } + } +} diff --git a/crates/rtss_api/src/apis/mod.rs b/crates/rtss_api/src/apis/mod.rs index 96f68be..89127b7 100644 --- a/crates/rtss_api/src/apis/mod.rs +++ b/crates/rtss_api/src/apis/mod.rs @@ -1,5 +1,6 @@ use async_graphql::{Enum, InputObject, MergedObject, OutputType, SimpleObject}; use draft_data::{DraftDataMutation, DraftDataQuery}; +use feature::{FeatureMutation, FeatureQuery}; use release_data::{ReleaseDataMutation, ReleaseDataQuery}; mod simulation_definition; @@ -9,15 +10,21 @@ use user::{UserMutation, UserQuery}; mod common; mod draft_data; +mod feature; mod release_data; mod simulation; mod user; #[derive(Default, MergedObject)] -pub struct Query(UserQuery, DraftDataQuery, ReleaseDataQuery); +pub struct Query(UserQuery, DraftDataQuery, ReleaseDataQuery, FeatureQuery); #[derive(Default, MergedObject)] -pub struct Mutation(UserMutation, DraftDataMutation, ReleaseDataMutation); +pub struct Mutation( + UserMutation, + DraftDataMutation, + ReleaseDataMutation, + FeatureMutation, +); #[derive(Enum, Copy, Clone, Default, Eq, PartialEq, Debug)] #[graphql(remote = "rtss_db::common::SortOrder")] @@ -57,6 +64,7 @@ impl From for rtss_db::common::PageQuery { name = "ReleaseIscsDataPageDto", params(release_data::ReleaseIscsDataWithoutVersionDto) ))] +#[graphql(concrete(name = "FeaturePageDto", params(feature::FeatureDto)))] pub struct PageDto { pub total: i64, pub items: Vec, diff --git a/crates/rtss_api/src/apis/release_data.rs b/crates/rtss_api/src/apis/release_data.rs index 8b31f26..16bbd74 100644 --- a/crates/rtss_api/src/apis/release_data.rs +++ b/crates/rtss_api/src/apis/release_data.rs @@ -8,7 +8,7 @@ use chrono::NaiveDateTime; use rtss_db::model::*; use rtss_db::prelude::*; use rtss_db::{model::ReleaseDataModel, ReleaseDataAccessor, RtssDbAccessor}; -use rtss_dto::common::DataType; +use rtss_dto::common::{DataType, Role}; use serde_json::Value; use crate::apis::draft_data::DraftDataDto; @@ -18,7 +18,7 @@ use super::common::{DataOptions, IscsDataOptions}; use super::user::UserId; use super::{PageDto, PageQueryDto}; -use crate::user_auth::{Role, RoleGuard, Token, UserAuthCache}; +use crate::user_auth::{RoleGuard, Token, UserAuthCache}; #[derive(Default)] pub struct ReleaseDataQuery; @@ -38,7 +38,7 @@ impl ReleaseDataQuery { ) -> async_graphql::Result> { let db_accessor = ctx.data::()?; let paging = db_accessor - .query_release_data_list(query.into(), page.into()) + .paging_query_release_data_list(query.into(), page.into()) .await?; Ok(paging.into()) } @@ -54,7 +54,7 @@ impl ReleaseDataQuery { let db_accessor = ctx.data::()?; query.data_type = Some(DataType::Iscs); let paging = db_accessor - .query_release_data_list(query.into(), page.into()) + .paging_query_release_data_list(query.into(), page.into()) .await?; Ok(paging.into()) } @@ -96,7 +96,7 @@ impl ReleaseDataQuery { ) -> async_graphql::Result> { let db_accessor = ctx.data::()?; let paging = db_accessor - .query_release_data_version_list(data_id, page.into()) + .paging_query_release_data_version_list(data_id, page.into()) .await?; Ok(paging.into()) } @@ -422,7 +422,7 @@ impl From for ReleaseDataDto { Self { id: model.id, name: model.name, - data_type: DataType::try_from(model.data_type).unwrap(), + data_type: model.data_type, options: model.options, used_version_id: model.used_version_id, user_id: model.user_id, diff --git a/crates/rtss_api/src/apis/user.rs b/crates/rtss_api/src/apis/user.rs index 4e6ee14..2170d71 100644 --- a/crates/rtss_api/src/apis/user.rs +++ b/crates/rtss_api/src/apis/user.rs @@ -6,9 +6,10 @@ use rtss_db::{DbAccessError, RtssDbAccessor, UserAccessor}; use crate::{ loader::RtssDbLoader, - user_auth::{build_jwt, Claims, Role, RoleGuard, Token, UserAuthCache, UserInfoDto}, + user_auth::{build_jwt, Claims, RoleGuard, Token, UserAuthCache, UserInfoDto}, UserAuthClient, }; +use rtss_dto::common::Role; use super::{PageDto, PageQueryDto}; @@ -128,7 +129,7 @@ impl From for UserDto { name: value.username, mobile: value.mobile, email: value.email, - roles: serde_json::from_value(value.roles).unwrap(), + roles: value.roles.0, created_at: value.created_at.naive_local(), updated_at: value.updated_at.naive_local(), } diff --git a/crates/rtss_api/src/user_auth/mod.rs b/crates/rtss_api/src/user_auth/mod.rs index cf14a50..8c3d6fa 100644 --- a/crates/rtss_api/src/user_auth/mod.rs +++ b/crates/rtss_api/src/user_auth/mod.rs @@ -3,21 +3,16 @@ use std::{ sync::{Arc, Mutex}, }; -use async_graphql::{Enum, Guard}; +use async_graphql::Guard; use axum::http::HeaderMap; use chrono::{DateTime, Local}; +use rtss_dto::common::Role; use rtss_log::tracing::error; use serde::{Deserialize, Serialize}; mod jwt_auth; pub use jwt_auth::*; -#[derive(Eq, PartialEq, Clone, Copy, Debug, Hash, Enum, Serialize, Deserialize)] -pub enum Role { - Admin, - User, -} - pub struct RoleGuard { role: Role, } diff --git a/crates/rtss_db/src/db_access/draft_data.rs b/crates/rtss_db/src/db_access/draft_data.rs index fc07551..6d4a508 100644 --- a/crates/rtss_db/src/db_access/draft_data.rs +++ b/crates/rtss_db/src/db_access/draft_data.rs @@ -16,15 +16,20 @@ use super::RtssDbAccessor; #[allow(async_fn_in_trait)] pub trait DraftDataAccessor { /// 查询所有草稿数据 - async fn query_draft_data( + async fn paging_query_draft_data( &self, query: DraftDataQuery, page: PageQuery, ) -> Result, DbAccessError>; /// 根据id查询草稿数据 async fn query_draft_data_by_id(&self, id: i32) -> Result; - /// 是否user_id+name的数据已存在 - async fn is_draft_data_exist(&self, user_id: i32, name: &str) -> Result; + /// 是否user_id+data_type+name的数据已存在 + async fn is_draft_data_exist( + &self, + user_id: i32, + data_type: &DataType, + name: &str, + ) -> Result; /// 创建草稿数据基本信息 async fn create_draft_data( &self, @@ -201,7 +206,7 @@ impl CreateDraftData { } impl DraftDataAccessor for RtssDbAccessor { - async fn query_draft_data( + async fn paging_query_draft_data( &self, query: DraftDataQuery, page: PageQuery, @@ -251,19 +256,25 @@ impl DraftDataAccessor for RtssDbAccessor { Ok(draft_data) } - async fn is_draft_data_exist(&self, user_id: i32, name: &str) -> Result { + async fn is_draft_data_exist( + &self, + user_id: i32, + data_type: &DataType, + name: &str, + ) -> Result { let table = DraftDataColumn::Table.name(); - let filter = format!( - "WHERE {} = '{}' AND {} = {}", - DraftDataColumn::Name.name(), - name, - DraftDataColumn::UserId.name(), - user_id - ); - let sql = format!("SELECT COUNT(*) FROM {table} {filter}"); + let user_id_column = DraftDataColumn::UserId.name(); + let data_type_column = DraftDataColumn::DataType.name(); + let name_column = DraftDataColumn::Name.name(); + let sql = format!("SELECT COUNT(*) FROM {table} WHERE {user_id_column} = $1 AND {data_type_column} = $2 AND {name_column} = $3",); // log sql debug!("draft data exist check sql: {}", sql); - let count: i64 = sqlx::query_scalar(&sql).fetch_one(&self.pool).await?; + let count: i64 = sqlx::query_scalar(&sql) + .bind(user_id) + .bind(data_type) + .bind(name) + .fetch_one(&self.pool) + .await?; Ok(count > 0) } @@ -274,7 +285,7 @@ impl DraftDataAccessor for RtssDbAccessor { ) -> Result { // 检查是否已存在 let exist = self - .is_draft_data_exist(create.user_id, &create.name) + .is_draft_data_exist(create.user_id, &create.data_type, &create.name) .await?; if exist { return Err(DbAccessError::RowExist); @@ -297,7 +308,7 @@ impl DraftDataAccessor for RtssDbAccessor { // 插入数据 let draft_data: DraftDataModel = sqlx::query_as(&sql) .bind(create.name) - .bind(create.data_type as i32) + .bind(create.data_type) .bind(create.options) .bind(create.user_id) .bind(create.data) @@ -411,14 +422,10 @@ impl DraftDataAccessor for RtssDbAccessor { user_id: i32, ) -> Result { let draft_data = self.query_draft_data_by_id(draft_id).await?; - let create = CreateDraftData::new( - name, - DataType::try_from(draft_data.data_type).unwrap(), - user_id, - ) - .with_option_options(draft_data.options) - .with_data(draft_data.data.as_ref().unwrap()) - .with_option_default_release_data_id(draft_data.default_release_data_id); + let create = CreateDraftData::new(name, draft_data.data_type, user_id) + .with_option_options(draft_data.options) + .with_data(draft_data.data.as_ref().unwrap()) + .with_option_default_release_data_id(draft_data.default_release_data_id); self.create_draft_data(create).await } @@ -429,7 +436,7 @@ mod tests { use crate::{SyncUserInfo, UserAccessor}; use super::*; - use rtss_dto::common::IscsStyle; + use rtss_dto::common::{IscsStyle, Role}; use rtss_log::tracing::Level; use serde::{Deserialize, Serialize}; use sqlx::{types::chrono::Local, PgPool}; @@ -439,12 +446,6 @@ mod tests { pub style: IscsStyle, } - #[derive(Debug, Clone, Serialize, Deserialize)] - enum Role { - User, - Admin, - } - // You could also do `use foo_crate::MIGRATOR` and just refer to it as `MIGRATOR` here. #[sqlx::test(migrator = "crate::MIGRATOR")] async fn basic_use_test(pool: PgPool) -> Result<(), DbAccessError> { @@ -539,7 +540,7 @@ mod tests { // 查询确认当前数据已删除 let page = accessor - .query_draft_data(DraftDataQuery::new(), PageQuery::new(1, 100)) + .paging_query_draft_data(DraftDataQuery::new(), PageQuery::new(1, 100)) .await?; assert_eq!(page.total, 0); @@ -574,7 +575,7 @@ mod tests { } let page = accessor - .query_draft_data( + .paging_query_draft_data( DraftDataQuery::new() .with_user_id(2) .with_name("test".to_string()) @@ -585,13 +586,13 @@ mod tests { assert_eq!(page.total, 5); let page = accessor - .query_draft_data(DraftDataQuery::new(), PageQuery::new(1, 100)) + .paging_query_draft_data(DraftDataQuery::new(), PageQuery::new(1, 100)) .await?; assert_eq!(page.total, 20); // 查询共享数据 let page = accessor - .query_draft_data( + .paging_query_draft_data( DraftDataQuery::new().with_is_shared(true), PageQuery::new(1, 10), ) @@ -600,7 +601,7 @@ mod tests { // 查询options let page = accessor - .query_draft_data( + .paging_query_draft_data( DraftDataQuery::new().with_options( serde_json::to_value(IscsDataOptions { style: IscsStyle::DaShiZhiNeng, diff --git a/crates/rtss_db/src/db_access/feature.rs b/crates/rtss_db/src/db_access/feature.rs new file mode 100644 index 0000000..f5a1e2e --- /dev/null +++ b/crates/rtss_db/src/db_access/feature.rs @@ -0,0 +1,351 @@ +use rtss_dto::common::FeatureType; +use serde_json::Value; + +use crate::{ + common::{PageQuery, PageResult, TableColumn}, + model::{FeatureColumn, FeatureModel}, + DbAccessError, +}; + +use super::RtssDbAccessor; + +/// 功能特性管理 +#[allow(async_fn_in_trait)] +pub trait FeatureAccessor { + /// 创建功能特性 + async fn create_feature(&self, feature: &CreateFeature) -> Result; + /// 更新功能特性 + async fn update_feature(&self, feature: &UpdateFeature) -> Result; + /// 上下架功能特性 + async fn set_feature_published( + &self, + feature_id: i32, + is_published: bool, + ) -> Result; + /// 分页查询功能特性 + async fn paging_query_features( + &self, + page: PageQuery, + filter: &FeaturePagingFilter, + ) -> Result, DbAccessError>; + /// 获取id对应的功能特性 + async fn get_feature(&self, id: i32) -> Result; + /// 根据id列表获取功能特性基本信息 + async fn get_features(&self, ids: &[i32]) -> Result, DbAccessError>; +} + +impl FeatureAccessor for RtssDbAccessor { + async fn create_feature(&self, feature: &CreateFeature) -> Result { + let table = FeatureColumn::Table.name(); + let feature_type_column = FeatureColumn::FeatureType.name(); + let name_column = FeatureColumn::Name.name(); + let description_column = FeatureColumn::Description.name(); + let config_column = FeatureColumn::Config.name(); + let creator_id_column = FeatureColumn::CreatorId.name(); + let updater_id_column = FeatureColumn::UpdaterId.name(); + let sql = format!( + "INSERT INTO {table} ({feature_type_column}, {name_column}, {description_column}, {config_column}, {creator_id_column}, {updater_id_column}) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *", + ); + let row = sqlx::query_as::<_, FeatureModel>(&sql) + .bind(feature.feature_type) + .bind(&feature.name) + .bind(&feature.description) + .bind(&feature.config) + .bind(feature.creator_id) + .bind(feature.creator_id) + .fetch_one(&self.pool) + .await?; + Ok(row) + } + + async fn update_feature(&self, feature: &UpdateFeature) -> Result { + let table = FeatureColumn::Table.name(); + let name_column = FeatureColumn::Name.name(); + let description_column = FeatureColumn::Description.name(); + let config_column = FeatureColumn::Config.name(); + let updater_id_column = FeatureColumn::UpdaterId.name(); + let update_at_column = FeatureColumn::UpdatedAt.name(); + let sql = format!( + "UPDATE {table} SET {name_column} = $1, {description_column} = $2, {config_column} = $3, {updater_id_column} = $4, {update_at_column} = 'now()' WHERE id = $5 RETURNING *", + ); + let row = sqlx::query_as::<_, FeatureModel>(&sql) + .bind(&feature.name) + .bind(&feature.description) + .bind(&feature.config) + .bind(feature.updater_id) + .bind(feature.id) + .fetch_one(&self.pool) + .await?; + Ok(row) + } + + async fn set_feature_published( + &self, + feature_id: i32, + is_published: bool, + ) -> Result { + let table = FeatureColumn::Table.name(); + let is_published_column = FeatureColumn::IsPublished.name(); + let update_at_column = FeatureColumn::UpdatedAt.name(); + let sql = format!( + "UPDATE {table} SET {is_published_column} = $1, {update_at_column} = 'now()' WHERE id = $2 RETURNING *", + ); + let row = sqlx::query_as::<_, FeatureModel>(&sql) + .bind(is_published) + .bind(feature_id) + .fetch_one(&self.pool) + .await?; + Ok(row) + } + + async fn paging_query_features( + &self, + page: PageQuery, + filter: &FeaturePagingFilter, + ) -> Result, DbAccessError> { + let table = FeatureColumn::Table.name(); + let where_clause = filter.to_where_clause(); + let total = + sqlx::query_scalar::<_, i64>(&format!("SELECT COUNT(*) FROM {table} {where_clause}",)) + .fetch_one(&self.pool) + .await?; + if total == 0 { + return Ok(PageResult::new(total, vec![])); + } + let update_at_column = FeatureColumn::UpdatedAt.name(); + let select_columns = [ + FeatureColumn::Id.name(), + FeatureColumn::FeatureType.name(), + FeatureColumn::Name.name(), + FeatureColumn::Description.name(), + FeatureColumn::IsPublished.name(), + FeatureColumn::CreatorId.name(), + FeatureColumn::UpdaterId.name(), + FeatureColumn::CreatedAt.name(), + FeatureColumn::UpdatedAt.name(), + ]; + let select_columns = select_columns.join(", "); + let limit_clause = page.to_limit_clause(); + let sql = format!( + "SELECT {select_columns} FROM {table} {where_clause} ORDER BY {update_at_column} DESC {limit_clause}", + ); + let rows = sqlx::query_as::<_, FeatureModel>(&sql) + .fetch_all(&self.pool) + .await?; + Ok(PageResult::new(total, rows)) + } + + async fn get_feature(&self, id: i32) -> Result { + let table = FeatureColumn::Table.name(); + let id_column = FeatureColumn::Id.name(); + let sql = format!("SELECT * FROM {table} WHERE {id_column} = $1",); + let row = sqlx::query_as::<_, FeatureModel>(&sql) + .bind(id) + .fetch_one(&self.pool) + .await?; + Ok(row) + } + + async fn get_features(&self, ids: &[i32]) -> Result, DbAccessError> { + let table = FeatureColumn::Table.name(); + let id_column = FeatureColumn::Id.name(); + let select_columns = [ + FeatureColumn::Id.name(), + FeatureColumn::FeatureType.name(), + FeatureColumn::Name.name(), + FeatureColumn::Description.name(), + FeatureColumn::IsPublished.name(), + FeatureColumn::CreatorId.name(), + FeatureColumn::UpdaterId.name(), + FeatureColumn::CreatedAt.name(), + FeatureColumn::UpdatedAt.name(), + ]; + let select_columns = select_columns.join(", "); + let sql = format!("SELECT {select_columns} FROM {table} WHERE {id_column} = ANY($1)",); + let rows = sqlx::query_as::<_, FeatureModel>(&sql) + .bind(ids) + .fetch_all(&self.pool) + .await?; + Ok(rows) + } +} + +#[derive(Debug)] +pub struct CreateFeature { + pub feature_type: FeatureType, + pub name: String, + pub description: String, + pub config: Value, + pub creator_id: i32, +} + +#[derive(Debug)] +pub struct UpdateFeature { + pub id: i32, + pub name: String, + pub description: String, + pub config: Value, + pub updater_id: i32, +} + +#[derive(Debug, Default)] +pub struct FeaturePagingFilter { + pub name: Option, + pub feature_type: Option, + pub is_published: Option, +} + +impl FeaturePagingFilter { + fn to_where_clause(&self) -> String { + let mut clauses = vec![]; + if let Some(name) = &self.name { + clauses.push(format!("name like '%{}%'", name)); + } + if let Some(feature_type) = self.feature_type { + clauses.push(format!("feature_type = {}", feature_type)); + } + if let Some(is_published) = self.is_published { + clauses.push(format!("is_published = {}", is_published)); + } + if clauses.is_empty() { + "".to_string() + } else { + format!("WHERE {}", clauses.join(" AND ")) + } + } +} + +#[cfg(test)] +mod tests { + use rtss_dto::common::Role; + use rtss_log::tracing::Level; + use sqlx::{types::chrono::Local, PgPool}; + + use crate::{SyncUserInfo, UserAccessor}; + + use super::*; + + #[sqlx::test(migrator = "crate::MIGRATOR")] + async fn test_basic_use(pool: PgPool) -> Result<(), DbAccessError> { + rtss_log::Logging::default().with_level(Level::DEBUG).init(); + let accessor = crate::db_access::RtssDbAccessor::new(pool); + // 同步10个用户 + let mut users = vec![]; + for i in 0..10 { + let user = SyncUserInfo { + id: i + 1, + name: format!("user{}", i + 1), + password: "password".to_string(), + roles: serde_json::to_value(&vec![Role::User]).unwrap(), + email: None, + mobile: None, + created_at: Local::now(), + updated_at: None, + }; + users.push(user); + } + accessor.sync_user(users.as_slice()).await?; + + // 创建测试 + let creator_id = 1; + let create_config = Value::String("create".to_string()); + let feature = CreateFeature { + feature_type: FeatureType::Ur, + name: "test".to_string(), + description: "test".to_string(), + config: create_config.clone(), + creator_id, + }; + let feature = accessor.create_feature(&feature).await?; + println!("create feature: {:?}", feature); + assert_eq!(feature.creator_id, creator_id); + assert_eq!(feature.updater_id, creator_id); + assert_eq!(feature.is_published, true); + assert_eq!(feature.config, create_config); + let feature_id = feature.id; + // 获取功能特性测试 + let feature = accessor.get_feature(feature_id).await?; + println!("get feature: {:?}", feature); + assert_eq!(feature.id, feature_id); + assert_eq!(feature.creator_id, creator_id); + assert_eq!(feature.updater_id, creator_id); + assert_eq!(feature.is_published, true); + assert_eq!(feature.config, create_config); + // 更新测试 + let updater_id = 2; + let config = Value::String("config".to_string()); + let feature = UpdateFeature { + id: feature_id, + name: "test update".to_string(), + description: "test update".to_string(), + config: config.clone(), + updater_id: updater_id, + }; + let feature = accessor.update_feature(&feature).await?; + println!("update feature: {:?}", feature); + assert_eq!(feature.updater_id, updater_id); + assert_eq!(feature.config, config); + assert!(feature.updated_at > feature.created_at); + // 上下架测试 + let feature = accessor.set_feature_published(feature_id, false).await?; + println!("set feature published: {:?}", feature); + assert_eq!(feature.is_published, false); + // 分页查询测试 + // 创建10个功能特性,5个发布的,5个未发布的 + for i in 0..10 { + let feature = CreateFeature { + feature_type: FeatureType::Ur, + name: format!("test{}", i), + description: format!("test{}", i), + config: Value::Null, + creator_id, + }; + let feature = accessor.create_feature(&feature).await?; + if i < 5 { + accessor.set_feature_published(feature.id, false).await?; + } + } + // 无过滤条件分页查询测试 + let filter = FeaturePagingFilter::default(); + let item_per_page = 10; + let page_result = accessor + .paging_query_features(PageQuery::new(1, item_per_page), &filter) + .await?; + assert_eq!(page_result.total, 11); + assert_eq!(page_result.data.len(), item_per_page as usize); + // 上架过滤条件分页查询测试 + let filter = FeaturePagingFilter { + is_published: Some(true), + ..Default::default() + }; + let page_result = accessor + .paging_query_features(PageQuery::new(1, item_per_page), &filter) + .await?; + println!("published features: {:?}", page_result); + assert_eq!(page_result.total, 5); + assert_eq!(page_result.data.len(), 5); + // 名称过滤条件分页查询测试 + let filter = FeaturePagingFilter { + name: Some("test".to_string()), + ..Default::default() + }; + let page_result = accessor + .paging_query_features(PageQuery::new(1, item_per_page), &filter) + .await?; + println!("name filter features: {:?}", page_result); + assert_eq!(page_result.total, 11); + assert_eq!(page_result.data.len(), item_per_page as usize); + + // 根据id列表获取功能特性测试 + let ids = page_result + .data + .iter() + .map(|f| f.id) + .skip(5) + .collect::>(); + let features = accessor.get_features(&ids).await?; + println!("get features: {:?}", features); + assert_eq!(features.len(), ids.len()); + Ok(()) + } +} diff --git a/crates/rtss_db/src/db_access/mod.rs b/crates/rtss_db/src/db_access/mod.rs index d9a5956..bbd5300 100644 --- a/crates/rtss_db/src/db_access/mod.rs +++ b/crates/rtss_db/src/db_access/mod.rs @@ -4,6 +4,8 @@ mod release_data; pub use release_data::*; mod user; pub use user::*; +mod feature; +pub use feature::*; #[derive(Clone)] pub struct RtssDbAccessor { diff --git a/crates/rtss_db/src/db_access/release_data.rs b/crates/rtss_db/src/db_access/release_data.rs index 153bf8d..6470f6a 100644 --- a/crates/rtss_db/src/db_access/release_data.rs +++ b/crates/rtss_db/src/db_access/release_data.rs @@ -31,7 +31,7 @@ pub trait ReleaseDataAccessor { user_id: Option, ) -> Result<(ReleaseDataModel, ReleaseDataVersionModel), DbAccessError>; /// 分页查询发布数据列表 - async fn query_release_data_list( + async fn paging_query_release_data_list( &self, query: ReleaseDataQuery, page: PageQuery, @@ -53,7 +53,7 @@ pub trait ReleaseDataAccessor { release_ids: &[i32], ) -> Result, DbAccessError>; /// 查询发布数据所有版本信息 - async fn query_release_data_version_list( + async fn paging_query_release_data_version_list( &self, release_id: i32, page: PageQuery, @@ -252,7 +252,7 @@ impl ReleaseDataAccessor for RtssDbAccessor { } // 判断发布数据名称是否已存在 if self - .is_release_data_name_exist(DataType::try_from(draft.data_type).unwrap(), name) + .is_release_data_name_exist(draft.data_type, name) .await? { return Err(DbAccessError::DataError("发布数据名称已存在".to_string())); @@ -364,7 +364,7 @@ impl ReleaseDataAccessor for RtssDbAccessor { Ok((rd, rdv)) } - async fn query_release_data_list( + async fn paging_query_release_data_list( &self, query: ReleaseDataQuery, page: PageQuery, @@ -449,7 +449,7 @@ impl ReleaseDataAccessor for RtssDbAccessor { Ok(rd) } - async fn query_release_data_version_list( + async fn paging_query_release_data_version_list( &self, release_id: i32, page: PageQuery, @@ -633,7 +633,7 @@ impl ReleaseDataAccessor for RtssDbAccessor { // 创建草稿数据 let draft = self .create_draft_data( - CreateDraftData::new(&name, DataType::try_from(rd.data_type).unwrap(), user_id) + CreateDraftData::new(&name, rd.data_type, user_id) .with_option_options(rdv.options) .with_data(&rdv.data) .with_default_release_data_id(rd.id), @@ -649,7 +649,7 @@ mod tests { use super::*; use chrono::Local; - use rtss_dto::common::IscsStyle; + use rtss_dto::common::{IscsStyle, Role}; use rtss_log::tracing::Level; use serde::{Deserialize, Serialize}; use sqlx::PgPool; @@ -692,12 +692,6 @@ mod tests { pub style: IscsStyle, } - #[derive(Debug, Clone, Serialize, Deserialize)] - enum Role { - User, - Admin, - } - // You could also do `use foo_crate::MIGRATOR` and just refer to it as `MIGRATOR` here. #[sqlx::test(migrator = "crate::MIGRATOR")] async fn test_basic_use(pool: PgPool) -> Result<(), DbAccessError> { @@ -818,7 +812,7 @@ mod tests { // 查询发布数据所有版本 let versions = accessor - .query_release_data_version_list( + .paging_query_release_data_version_list( release_data.id, PageQuery { page: 1, @@ -871,27 +865,27 @@ mod tests { // 分页查询发布数据,无条件 let query = ReleaseDataQuery::new(); let page = PageQuery::new(1, 100); - let page_result = accessor.query_release_data_list(query, page).await?; + let page_result = accessor.paging_query_release_data_list(query, page).await?; assert_eq!(page_result.total, 9); // 分页查询发布数据,按是否上架过滤 let query = ReleaseDataQuery::new().with_is_published(true); let page = PageQuery::new(1, 100); - let page_result = accessor.query_release_data_list(query, page).await?; + let page_result = accessor.paging_query_release_data_list(query, page).await?; assert_eq!(page_result.total, 8); // 分页查询发布数据,按名称过滤 let query = ReleaseDataQuery::new().with_name("test_2".to_string()); let page = PageQuery::new(1, 10); - let page_result = accessor.query_release_data_list(query, page).await?; + let page_result = accessor.paging_query_release_data_list(query, page).await?; assert_eq!(page_result.total, 2); // 分页查询发布数据,按用户id过滤 let query = ReleaseDataQuery::new().with_user_id(2); let page = PageQuery::new(1, 10); - let page_result = accessor.query_release_data_list(query, page).await?; + let page_result = accessor.paging_query_release_data_list(query, page).await?; assert_eq!(page_result.total, 2); // 分页查询发布数据,按数据类型过滤 let query = ReleaseDataQuery::new().with_data_type(rtss_dto::common::DataType::Em); let page = PageQuery::new(1, 10); - let page_result = accessor.query_release_data_list(query, page).await?; + let page_result = accessor.paging_query_release_data_list(query, page).await?; assert_eq!(page_result.total, 8); println!("分页查询发布数据测试成功"); diff --git a/crates/rtss_db/src/model.rs b/crates/rtss_db/src/model.rs index 6d23b13..1f5d10e 100644 --- a/crates/rtss_db/src/model.rs +++ b/crates/rtss_db/src/model.rs @@ -1,5 +1,9 @@ +use rtss_dto::common::{DataType, FeatureType, Role}; use serde_json::Value; -use sqlx::types::chrono::{DateTime, Local}; +use sqlx::types::{ + chrono::{DateTime, Local}, + Json, +}; use crate::common::TableColumn; @@ -25,7 +29,7 @@ pub struct UserModel { pub email: Option, #[sqlx(default)] pub mobile: Option, - pub roles: Value, + pub roles: Json>, pub created_at: DateTime, pub updated_at: DateTime, } @@ -50,7 +54,7 @@ pub enum DraftDataColumn { pub struct DraftDataModel { pub id: i32, pub name: String, - pub data_type: i32, + pub data_type: DataType, #[sqlx(default)] pub options: Option, #[sqlx(default)] @@ -84,7 +88,7 @@ pub enum ReleaseDataColumn { pub struct ReleaseDataModel { pub id: i32, pub name: String, - pub data_type: i32, + pub data_type: DataType, /// 从发布版本复制的选项,主要用于查询 #[sqlx(default)] pub options: Option, @@ -130,6 +134,7 @@ pub(crate) enum FeatureColumn { FeatureType, Name, Description, + Config, IsPublished, CreatorId, UpdaterId, @@ -140,9 +145,11 @@ pub(crate) enum FeatureColumn { #[derive(Debug, sqlx::FromRow)] pub struct FeatureModel { pub id: i32, - pub feature_type: i32, + pub feature_type: FeatureType, pub name: String, pub description: String, + #[sqlx(default)] + pub config: Value, pub is_published: bool, pub creator_id: i32, pub updater_id: i32, @@ -150,82 +157,25 @@ pub struct FeatureModel { pub updated_at: DateTime, } -/// 数据库表 rtss.feature_release_data 列映射 +/// 数据库表 rtss.user_config 列映射 #[derive(Debug)] #[allow(dead_code)] -pub(crate) enum FeatureReleaseDataColumn { - Table, - FeatureId, - ReleaseDataId, -} - -#[derive(Debug, sqlx::FromRow)] -pub struct FeatureReleaseDataModel { - pub feature_id: i32, - pub release_data_id: i32, -} - -/// 数据库表 rtss.feature_group 列映射 -#[derive(Debug)] -#[allow(dead_code)] -pub(crate) enum FeatureGroupColumn { - Table, - Id, - Name, - Description, - IsPublished, - CreatorId, - UpdaterId, - CreatedAt, - UpdatedAt, -} - -#[derive(Debug, sqlx::FromRow)] -pub struct FeatureGroupModel { - pub id: i32, - pub name: String, - pub description: String, - pub is_published: bool, - pub creator_id: i32, - pub updater_id: i32, - pub created_at: DateTime, - pub updated_at: DateTime, -} - -/// 数据库表 rtss.feature_group_feature 列映射 -#[derive(Debug)] -#[allow(dead_code)] -pub(crate) enum FeatureGroupFeatureColumn { - Table, - FeatureGroupId, - FeatureId, -} - -#[derive(Debug, sqlx::FromRow)] -pub struct FeatureGroupFeatureModel { - pub feature_group_id: i32, - pub feature_id: i32, -} - -/// 数据库表 rtss.feature_config 列映射 -#[derive(Debug)] -#[allow(dead_code)] -pub(crate) enum FeatureConfigColumn { +pub(crate) enum UserConfigColumn { Table, Id, UserId, - FeatureId, + ConfigType, Config, CreatedAt, UpdatedAt, } #[derive(Debug, sqlx::FromRow)] -pub struct FeatureConfigModel { +pub struct UserConfigModel { pub id: i32, pub user_id: i32, - pub feature_id: i32, - pub config: Vec, + pub config_type: i32, + pub config: Value, pub created_at: DateTime, pub updated_at: DateTime, } @@ -304,6 +254,7 @@ impl TableColumn for FeatureColumn { FeatureColumn::FeatureType => "feature_type", FeatureColumn::Name => "name", FeatureColumn::Description => "description", + FeatureColumn::Config => "config", FeatureColumn::IsPublished => "is_published", FeatureColumn::CreatorId => "creator_id", FeatureColumn::UpdaterId => "updater_id", @@ -313,52 +264,16 @@ impl TableColumn for FeatureColumn { } } -impl TableColumn for FeatureReleaseDataColumn { +impl TableColumn for UserConfigColumn { fn name(&self) -> &str { match self { - FeatureReleaseDataColumn::Table => "rtss.feature_release_data", - FeatureReleaseDataColumn::FeatureId => "feature_id", - FeatureReleaseDataColumn::ReleaseDataId => "release_data_id", - } - } -} - -impl TableColumn for FeatureGroupColumn { - fn name(&self) -> &str { - match self { - FeatureGroupColumn::Table => "rtss.feature_group", - FeatureGroupColumn::Id => "id", - FeatureGroupColumn::Name => "name", - FeatureGroupColumn::Description => "description", - FeatureGroupColumn::IsPublished => "is_published", - FeatureGroupColumn::CreatorId => "creator_id", - FeatureGroupColumn::UpdaterId => "updater_id", - FeatureGroupColumn::CreatedAt => "created_at", - FeatureGroupColumn::UpdatedAt => "updated_at", - } - } -} - -impl TableColumn for FeatureGroupFeatureColumn { - fn name(&self) -> &str { - match self { - FeatureGroupFeatureColumn::Table => "rtss.feature_group_feature", - FeatureGroupFeatureColumn::FeatureGroupId => "feature_group_id", - FeatureGroupFeatureColumn::FeatureId => "feature_id", - } - } -} - -impl TableColumn for FeatureConfigColumn { - fn name(&self) -> &str { - match self { - FeatureConfigColumn::Table => "rtss.feature_config", - FeatureConfigColumn::Id => "id", - FeatureConfigColumn::UserId => "user_id", - FeatureConfigColumn::FeatureId => "feature_id", - FeatureConfigColumn::Config => "config", - FeatureConfigColumn::CreatedAt => "created_at", - FeatureConfigColumn::UpdatedAt => "updated_at", + UserConfigColumn::Table => "rtss.user_config", + UserConfigColumn::Id => "id", + UserConfigColumn::UserId => "user_id", + UserConfigColumn::ConfigType => "config_type", + UserConfigColumn::Config => "config", + UserConfigColumn::CreatedAt => "created_at", + UserConfigColumn::UpdatedAt => "updated_at", } } } diff --git a/crates/rtss_dto/build.rs b/crates/rtss_dto/build.rs index 0be9148..c7b8224 100644 --- a/crates/rtss_dto/build.rs +++ b/crates/rtss_dto/build.rs @@ -21,14 +21,22 @@ fn main() { } Config::new() .out_dir("src/pb") - .type_attribute("common.DataType", "#[derive(sqlx::Type)]") - .type_attribute("common.DataType", "#[derive(async_graphql::Enum)]") - .type_attribute("common.IscsStyle", "#[derive(async_graphql::Enum)]") + .type_attribute( + "common.Role", + "#[derive(sqlx::Type, async_graphql::Enum, serde::Serialize, serde::Deserialize)]", + ) + .type_attribute( + "common.DataType", + "#[derive(sqlx::Type, async_graphql::Enum, serde::Serialize, serde::Deserialize)]", + ) .type_attribute( "common.IscsStyle", - "#[derive(serde::Serialize, serde::Deserialize)]", + "#[derive(async_graphql::Enum, serde::Serialize, serde::Deserialize)]", + ) + .type_attribute( + "common.FeatureType", + "#[derive(sqlx::Type, async_graphql::Enum, serde::Serialize, serde::Deserialize)]", ) - .type_attribute("common.FeatureType", "#[derive(sqlx::Type)]") .compile_protos( &[ "../../rtss-proto-msg/src/em_data.proto", diff --git a/crates/rtss_dto/src/pb/common.rs b/crates/rtss_dto/src/pb/common.rs index 49f6395..67a07e3 100644 --- a/crates/rtss_dto/src/pb/common.rs +++ b/crates/rtss_dto/src/pb/common.rs @@ -88,10 +88,59 @@ pub struct CommonInfo { #[prost(message, repeated, tag = "4")] pub child_transforms: ::prost::alloc::vec::Vec, } +/// 用户角色 +#[derive( + sqlx::Type, + async_graphql::Enum, + serde::Serialize, + serde::Deserialize, + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration, +)] +#[repr(i32)] +pub enum Role { + /// 未知 + Unknown = 0, + /// 系统管理员 + Admin = 1, + /// 普通用户 + User = 2, +} +impl Role { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Role::Unknown => "Role_Unknown", + Role::Admin => "Role_Admin", + Role::User => "Role_User", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "Role_Unknown" => Some(Self::Unknown), + "Role_Admin" => Some(Self::Admin), + "Role_User" => Some(Self::User), + _ => None, + } + } +} /// 数据类型 #[derive( sqlx::Type, async_graphql::Enum, + serde::Serialize, + serde::Deserialize, Clone, Copy, Debug, @@ -108,10 +157,6 @@ pub enum DataType { Unknown = 0, /// 电子地图数据 Em = 1, - /// IBP数据 - Ibp = 2, - /// PSL数据 - Psl = 3, /// ISCS数据 Iscs = 4, } @@ -124,8 +169,6 @@ impl DataType { match self { DataType::Unknown => "DataType_Unknown", DataType::Em => "DataType_Em", - DataType::Ibp => "DataType_Ibp", - DataType::Psl => "DataType_Psl", DataType::Iscs => "DataType_Iscs", } } @@ -134,8 +177,6 @@ impl DataType { match value { "DataType_Unknown" => Some(Self::Unknown), "DataType_Em" => Some(Self::Em), - "DataType_Ibp" => Some(Self::Ibp), - "DataType_Psl" => Some(Self::Psl), "DataType_Iscs" => Some(Self::Iscs), _ => None, } @@ -170,28 +211,40 @@ impl IscsStyle { pub fn as_str_name(&self) -> &'static str { match self { IscsStyle::Unknown => "IscsStyle_Unknown", - IscsStyle::DaShiZhiNeng => "DaShiZhiNeng", + IscsStyle::DaShiZhiNeng => "IscsStyle_DaShiZhiNeng", } } /// Creates an enum from field names used in the ProtoBuf definition. pub fn from_str_name(value: &str) -> ::core::option::Option { match value { "IscsStyle_Unknown" => Some(Self::Unknown), - "DaShiZhiNeng" => Some(Self::DaShiZhiNeng), + "IscsStyle_DaShiZhiNeng" => Some(Self::DaShiZhiNeng), _ => None, } } } /// 功能特性类型 #[derive( - sqlx::Type, Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration, + sqlx::Type, + async_graphql::Enum, + serde::Serialize, + serde::Deserialize, + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration, )] #[repr(i32)] pub enum FeatureType { /// 未知 Unknown = 0, - /// 仿真 - Simulation = 1, + /// 城轨信号系统仿真(Urban Rail) + Ur = 1, /// 运行图编制 RunPlan = 2, } @@ -203,7 +256,7 @@ impl FeatureType { pub fn as_str_name(&self) -> &'static str { match self { FeatureType::Unknown => "FeatureType_Unknown", - FeatureType::Simulation => "FeatureType_Simulation", + FeatureType::Ur => "FeatureType_Ur", FeatureType::RunPlan => "FeatureType_RunPlan", } } @@ -211,7 +264,7 @@ impl FeatureType { pub fn from_str_name(value: &str) -> ::core::option::Option { match value { "FeatureType_Unknown" => Some(Self::Unknown), - "FeatureType_Simulation" => Some(Self::Simulation), + "FeatureType_Ur" => Some(Self::Ur), "FeatureType_RunPlan" => Some(Self::RunPlan), _ => None, } diff --git a/crates/rtss_dto/src/pb/iscs_graphic_data.rs b/crates/rtss_dto/src/pb/iscs_graphic_data.rs index c472a4f..7d5bd74 100644 --- a/crates/rtss_dto/src/pb/iscs_graphic_data.rs +++ b/crates/rtss_dto/src/pb/iscs_graphic_data.rs @@ -2,16 +2,12 @@ #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct IscsGraphicStorage { - #[prost(message, optional, tag = "1")] - pub canvas: ::core::option::Option, + #[prost(message, repeated, tag = "1")] + pub cctv_of_station_control_storages: ::prost::alloc::vec::Vec< + CctvOfStationControlStorage, + >, #[prost(message, repeated, tag = "2")] - pub arrows: ::prost::alloc::vec::Vec, - #[prost(message, repeated, tag = "3")] - pub iscs_texts: ::prost::alloc::vec::Vec, - #[prost(message, repeated, tag = "4")] - pub rects: ::prost::alloc::vec::Vec, - #[prost(message, repeated, tag = "5")] - pub cctv_buttons: ::prost::alloc::vec::Vec, + pub fas_platform_alarm_storages: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -177,3 +173,33 @@ pub struct TemperatureDetector { #[prost(string, tag = "2")] pub code: ::prost::alloc::string::String, } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CctvOfStationControlStorage { + #[prost(string, tag = "1")] + pub station_name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub canvas: ::core::option::Option, + #[prost(message, repeated, tag = "3")] + pub arrows: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "4")] + pub iscs_texts: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "5")] + pub rects: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "6")] + pub cctv_buttons: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FasPlatformAlarmStorage { + #[prost(string, tag = "1")] + pub station_name: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub canvas: ::core::option::Option, + #[prost(message, repeated, tag = "3")] + pub arrows: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "4")] + pub iscs_texts: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "5")] + pub rects: ::prost::alloc::vec::Vec, +} diff --git a/docs/DESIGN.md b/docs/DESIGN.md new file mode 100644 index 0000000..41d89e0 --- /dev/null +++ b/docs/DESIGN.md @@ -0,0 +1,8 @@ +# 系统设计 + +## 数据库设计 +- 核心数据如下图所示 + + ![alt text](image-1.png) + + - 所有数据从草稿数据开始,草稿数据发布为发布数据,功能定义引用发布数据,同时功能还可以引用其他功能实现功能目录树/功能组/功能包 diff --git a/docs/image-1.png b/docs/image-1.png new file mode 100644 index 0000000000000000000000000000000000000000..af684e1ffab7bf959e930179e8c8cd148be75d9d GIT binary patch literal 11049 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 literal 0 HcmV?d00001 diff --git a/migrations/20240830095636_init.up.sql b/migrations/20240830095636_init.up.sql index 6b2eda1..3e5330c 100644 --- a/migrations/20240830095636_init.up.sql +++ b/migrations/20240830095636_init.up.sql @@ -14,6 +14,38 @@ CREATE TABLE updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP -- 更新时间 ); +-- 创建用户名称索引 +CREATE INDEX ON rtss.user (username); + +-- 创建用户邮箱索引 +CREATE INDEX ON rtss.user (email); + +-- 创建用户手机号索引 +CREATE INDEX ON rtss.user (mobile); + +-- 创建用户角色索引 +CREATE INDEX ON rtss.user USING GIN (roles); + +-- 注释用户表 +COMMENT ON TABLE rtss.user IS '用户表'; + +-- 注释用户表字段 +COMMENT ON COLUMN rtss.user.id IS 'id 自增主键'; + +COMMENT ON COLUMN rtss.user.username IS '用户名'; + +COMMENT ON COLUMN rtss.user.password IS '密码'; + +COMMENT ON COLUMN rtss.user.email IS '邮箱'; + +COMMENT ON COLUMN rtss.user.mobile IS '手机号'; + +COMMENT ON COLUMN rtss.user.roles IS '角色列表'; + +COMMENT ON COLUMN rtss.user.created_at IS '创建时间'; + +COMMENT ON COLUMN rtss.user.updated_at IS '更新时间'; + -- 创建草稿数据表 CREATE TABLE rtss.draft_data ( @@ -28,7 +60,7 @@ CREATE TABLE created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间 updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间 FOREIGN KEY (user_id) REFERENCES rtss.user (id) ON DELETE CASCADE, -- 用户外键 - UNIQUE (name, user_id) -- 一个用户的草稿名称唯一 + UNIQUE (name, data_type, user_id) -- 一个用户的某个类型的草稿名称唯一 ); -- 创建草稿数据用户索引 @@ -78,6 +110,18 @@ CREATE TABLE UNIQUE(data_type, name) -- 数据类型和名称唯一 ); +-- 创建发布数据名称索引 +CREATE INDEX ON rtss.release_data (name); + +-- 创建发布数据用户索引 +CREATE INDEX ON rtss.release_data (user_id); + +-- 创建发布数据类型索引 +CREATE INDEX ON rtss.release_data (data_type); + +-- 创建发布数据配置项索引 +CREATE INDEX ON rtss.release_data USING GIN (options); + -- 注释发布数据表 COMMENT ON TABLE rtss.release_data IS '发布数据表'; @@ -114,6 +158,15 @@ CREATE TABLE FOREIGN KEY (release_data_id) REFERENCES rtss.release_data (id) ON DELETE CASCADE ); +-- 创建发布数据版本发布数据索引 +CREATE INDEX ON rtss.release_data_version (release_data_id); + +-- 创建发布数据版本用户索引 +CREATE INDEX ON rtss.release_data_version (user_id); + +-- 创建发布数据版本配置项索引 +CREATE INDEX ON rtss.release_data_version USING GIN (options); + -- 创建发布数据当前版本外键 ALTER TABLE rtss.release_data ADD FOREIGN KEY (used_version_id) REFERENCES rtss.release_data_version (id) ON DELETE SET NULL; @@ -145,6 +198,7 @@ CREATE TABLE feature_type INT NOT NULL, -- feature类型 name VARCHAR(128) NOT NULL UNIQUE, -- feature名称 description TEXT NOT NULL, -- feature描述 + config JSONB NOT NULL, -- feature配置 is_published BOOLEAN NOT NULL DEFAULT TRUE, -- 是否上架 creator_id INT NOT NULL, -- 创建用户id updater_id INT NOT NULL, -- 更新用户id @@ -154,6 +208,12 @@ CREATE TABLE FOREIGN KEY (updater_id) REFERENCES rtss.user (id) ON DELETE CASCADE -- 用户外键 ); +-- 创建feature类型索引 +CREATE INDEX ON rtss.feature (feature_type); + +-- 创建feature名称索引 +CREATE INDEX ON rtss.feature (name); + -- 注释仿真feature表 COMMENT ON TABLE rtss.feature IS 'feature表'; @@ -166,6 +226,8 @@ COMMENT ON COLUMN rtss.feature.name IS 'feature名称'; COMMENT ON COLUMN rtss.feature.description IS 'feature描述'; +COMMENT ON COLUMN rtss.feature.config IS 'feature配置'; + COMMENT ON COLUMN rtss.feature.is_published IS '是否上架'; COMMENT ON COLUMN rtss.feature.creator_id IS '创建用户id'; @@ -174,95 +236,36 @@ COMMENT ON COLUMN rtss.feature.created_at IS '创建时间'; COMMENT ON COLUMN rtss.feature.updated_at IS '更新时间'; --- 创建仿真feature和发布数据关联表 +-- 创建用户配置表 CREATE TABLE - rtss.feature_release_data ( - feature_id INT NOT NULL, -- 仿真feature id - release_data_id INT NOT NULL, -- 发布数据id - PRIMARY KEY (feature_id, release_data_id), - FOREIGN KEY (feature_id) REFERENCES rtss.feature (id) ON DELETE CASCADE, - FOREIGN KEY (release_data_id) REFERENCES rtss.release_data (id) ON DELETE CASCADE - ); - --- 注释仿真feature和发布数据关联表 -COMMENT ON TABLE rtss.feature_release_data IS '仿真feature和发布数据关联表'; - --- 注释仿真feature和发布数据关联表字段 -COMMENT ON COLUMN rtss.feature_release_data.feature_id IS '仿真feature id'; - -COMMENT ON COLUMN rtss.feature_release_data.release_data_id IS '发布数据id'; - --- 创建feature group表 -CREATE TABLE - rtss.feature_group ( - id SERIAL PRIMARY KEY, -- id 自增主键 - name VARCHAR(128) NOT NULL UNIQUE, -- feature group名称 - description TEXT NOT NULL, -- feature group描述 - is_published BOOLEAN NOT NULL DEFAULT TRUE, -- 是否上架 - creator_id INT NOT NULL, -- 创建用户id - updater_id INT NOT NULL, -- 更新用户id - created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间 - updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间 - FOREIGN KEY (creator_id) REFERENCES rtss.user (id) ON DELETE CASCADE, -- 用户外键 - FOREIGN KEY (updater_id) REFERENCES rtss.user (id) ON DELETE CASCADE -- 用户外键 - ); - --- 注释仿真feature group表 -COMMENT ON TABLE rtss.feature_group IS 'feature group表'; - --- 注释仿真feature group表字段 -COMMENT ON COLUMN rtss.feature_group.id IS 'id 自增主键'; - -COMMENT ON COLUMN rtss.feature_group.name IS 'feature group名称'; - -COMMENT ON COLUMN rtss.feature_group.description IS 'feature group描述'; - -COMMENT ON COLUMN rtss.feature_group.is_published IS '是否上架'; - -COMMENT ON COLUMN rtss.feature_group.creator_id IS '创建用户id'; - -COMMENT ON COLUMN rtss.feature_group.created_at IS '创建时间'; - -COMMENT ON COLUMN rtss.feature_group.updated_at IS '更新时间'; - --- 创建feature group和feature关联表 -CREATE TABLE - rtss.feature_group_feature ( - feature_group_id INT NOT NULL, -- feature group id - feature_id INT NOT NULL, -- feature id - PRIMARY KEY (feature_id, feature_group_id), - FOREIGN KEY (feature_id) REFERENCES rtss.feature (id) ON DELETE CASCADE, - FOREIGN KEY (feature_group_id) REFERENCES rtss.feature_group (id) ON DELETE CASCADE - ); - --- 注释仿真feature group和feature关联表 -COMMENT ON TABLE rtss.feature_group_feature IS '仿真feature group和feature关联表'; - --- 创建用户feature配置表 -CREATE TABLE - rtss.feature_config ( + rtss.user_config ( id SERIAL PRIMARY KEY, -- id 自增主键 user_id INT NOT NULL, -- 用户id - feature_id INT NOT NULL, -- 仿真feature id + config_type INT NOT NULL, -- 配置类型 config BYTEA NOT NULL, -- 配置 created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间 updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间 - FOREIGN KEY (user_id) REFERENCES rtss.user (id) ON DELETE CASCADE, -- 用户外键 - FOREIGN KEY (feature_id) REFERENCES rtss.feature (id) ON DELETE CASCADE + FOREIGN KEY (user_id) REFERENCES rtss.user (id) ON DELETE CASCADE -- 用户外键 ); +-- 创建用户配置用户索引 +CREATE INDEX ON rtss.user_config (user_id); + +-- 创建用户配置类型索引 +CREATE INDEX ON rtss.user_config (config_type); + -- 注释用户feature配置表 -COMMENT ON TABLE rtss.feature_config IS '用户feature配置表'; +COMMENT ON TABLE rtss.user_config IS '用户feature配置表'; -- 注释用户feature配置表字段 -COMMENT ON COLUMN rtss.feature_config.id IS 'id 自增主键'; +COMMENT ON COLUMN rtss.user_config.id IS 'id 自增主键'; -COMMENT ON COLUMN rtss.feature_config.user_id IS '用户id'; +COMMENT ON COLUMN rtss.user_config.user_id IS '用户id'; -COMMENT ON COLUMN rtss.feature_config.feature_id IS '仿真feature id'; +COMMENT ON COLUMN rtss.user_config.config_type IS '配置类型'; -COMMENT ON COLUMN rtss.feature_config.config IS '配置'; +COMMENT ON COLUMN rtss.user_config.config IS '配置'; -COMMENT ON COLUMN rtss.feature_config.created_at IS '创建时间'; +COMMENT ON COLUMN rtss.user_config.created_at IS '创建时间'; -COMMENT ON COLUMN rtss.feature_config.updated_at IS '更新时间'; +COMMENT ON COLUMN rtss.user_config.updated_at IS '更新时间'; diff --git a/rtss-proto-msg b/rtss-proto-msg index 1672a8c..02d9ad3 160000 --- a/rtss-proto-msg +++ b/rtss-proto-msg @@ -1 +1 @@ -Subproject commit 1672a8c0e2b41c4076c1dc9ec852f425dcba21c3 +Subproject commit 02d9ad3b44876fc470e460bb6975c3d08f698b1b