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 0000000..af684e1 Binary files /dev/null and b/docs/image-1.png differ 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