Compare commits

..

2 Commits

Author SHA1 Message Date
soul-walker
e8fa4d119c 修改草稿另存为bug
All checks were successful
build / build-rust (push) Successful in 3m28s
2024-09-20 15:31:11 +08:00
soul-walker
df4c9940f3 修改从发布版本创建草稿接口bug
添加发布数据查询description功能(通过dataloader)
2024-09-20 15:23:31 +08:00
10 changed files with 191 additions and 29 deletions

24
Cargo.lock generated
View File

@ -160,10 +160,13 @@ dependencies = [
"chrono", "chrono",
"fast_chemail", "fast_chemail",
"fnv", "fnv",
"futures-channel",
"futures-timer",
"futures-util", "futures-util",
"handlebars", "handlebars",
"http", "http",
"indexmap", "indexmap",
"lru",
"mime", "mime",
"multer", "multer",
"num-traits", "num-traits",
@ -1158,6 +1161,12 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-timer"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.30" version = "0.3.30"
@ -1544,6 +1553,15 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "lru"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904"
dependencies = [
"hashbrown 0.14.5",
]
[[package]] [[package]]
name = "matchers" name = "matchers"
version = "0.1.0" version = "0.1.0"
@ -2978,15 +2996,13 @@ dependencies = [
[[package]] [[package]]
name = "tower-http" name = "tower-http"
version = "0.5.2" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" checksum = "41515cc9e193536d93fd0dbbea0c73819c08eca76e0b30909a325c3ec90985bb"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"bytes", "bytes",
"http", "http",
"http-body",
"http-body-util",
"pin-project-lite", "pin-project-lite",
"tower-layer", "tower-layer",
"tower-service", "tower-service",

View File

@ -12,8 +12,8 @@ chrono = { version = "0.4.38", features = ["serde"] }
axum = "0.7.5" axum = "0.7.5"
axum-extra = { version = "0.9.3", features = ["typed-header"] } axum-extra = { version = "0.9.3", features = ["typed-header"] }
# jsonwebtoken = "9.3.0" # jsonwebtoken = "9.3.0"
tower-http = { version = "0.5.0", features = ["cors"] } tower-http = { version = "0.6.0", features = ["cors"] }
async-graphql = { version = "7.0.7", features = ["chrono"] } async-graphql = { version = "7.0.7", features = ["chrono", "dataloader"] }
async-graphql-axum = "7.0.6" async-graphql-axum = "7.0.6"
base64 = "0.22.1" base64 = "0.22.1"
sysinfo = "0.31.3" sysinfo = "0.31.3"

View File

@ -112,7 +112,9 @@ impl DraftDataMutation {
data: String, // base64编码的数据 data: String, // base64编码的数据
) -> async_graphql::Result<DraftDataDto> { ) -> async_graphql::Result<DraftDataDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?; let db_accessor = ctx.data::<RtssDbAccessor>()?;
let bytes = BASE64_STANDARD.decode(data).expect("base64 decode error"); let bytes = BASE64_STANDARD
.decode(data)
.map_err(|e| async_graphql::Error::new(format!("base64 decode error: {}", e)))?;
let draft_data = db_accessor.update_draft_data_data(id, &bytes).await?; let draft_data = db_accessor.update_draft_data_data(id, &bytes).await?;
Ok(draft_data.into()) Ok(draft_data.into())
} }
@ -303,3 +305,17 @@ impl From<rtss_db::model::DraftDataModel> for DraftIscsDataDto {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_base64() {
let data = b"hello world";
let encoded = BASE64_STANDARD.encode(data);
let decoded = BASE64_STANDARD.decode(&encoded).unwrap();
assert_eq!(data, decoded.as_slice());
println!("encoded: {}, decoded: {:?}", encoded, decoded);
}
}

View File

@ -1,8 +1,12 @@
use async_graphql::MergedObject; use async_graphql::dataloader::DataLoader;
use async_graphql::{EmptySubscription, MergedObject, Schema};
use async_graphql::{Enum, InputObject, OutputType, SimpleObject}; use async_graphql::{Enum, InputObject, OutputType, SimpleObject};
use draft_data::{DraftDataMutation, DraftDataQuery}; use draft_data::{DraftDataMutation, DraftDataQuery};
use release_data::{ReleaseDataMutation, ReleaseDataQuery}; use release_data::{ReleaseDataMutation, ReleaseDataQuery};
use crate::simulation_definition::MutexSimulationManager;
use crate::ServerConfig;
mod common; mod common;
mod draft_data; mod draft_data;
mod release_data; mod release_data;
@ -70,3 +74,25 @@ impl<T: OutputType, M: Into<T>> From<rtss_db::common::PageResult<M>> for PageDto
) )
} }
} }
pub struct RtssDbLoader {
pub(crate) db_accessor: rtss_db::RtssDbAccessor,
}
impl RtssDbLoader {
pub fn new(db_accessor: rtss_db::RtssDbAccessor) -> Self {
Self { db_accessor }
}
}
pub type RtssAppSchema = Schema<Query, Mutation, EmptySubscription>;
pub async fn new_schema(config: &ServerConfig) -> RtssAppSchema {
let dba = rtss_db::get_db_accessor(&config.database_url).await;
let loader = RtssDbLoader::new(dba.clone());
Schema::build(Query::default(), Mutation::default(), EmptySubscription)
.data(dba)
.data(DataLoader::new(loader, tokio::spawn))
.data(MutexSimulationManager::default())
.finish()
}

View File

@ -1,7 +1,12 @@
use async_graphql::{Context, InputObject, Object, SimpleObject}; use std::collections::HashMap;
use std::sync::Arc;
use async_graphql::dataloader::*;
use async_graphql::{ComplexObject, Context, InputObject, Object, SimpleObject};
use base64::prelude::*; use base64::prelude::*;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use rtss_db::model::*; use rtss_db::model::*;
use rtss_db::prelude::*;
use rtss_db::{model::ReleaseDataModel, ReleaseDataAccessor, RtssDbAccessor}; use rtss_db::{model::ReleaseDataModel, ReleaseDataAccessor, RtssDbAccessor};
use rtss_dto::common::DataType; use rtss_dto::common::DataType;
use serde_json::Value; use serde_json::Value;
@ -9,7 +14,7 @@ use serde_json::Value;
use crate::apis::draft_data::DraftDataDto; use crate::apis::draft_data::DraftDataDto;
use super::common::{DataOptions, IscsDataOptions}; use super::common::{DataOptions, IscsDataOptions};
use super::{PageDto, PageQueryDto}; use super::{PageDto, PageQueryDto, RtssDbLoader};
#[derive(Default)] #[derive(Default)]
pub struct ReleaseDataQuery; pub struct ReleaseDataQuery;
@ -59,6 +64,17 @@ impl ReleaseDataQuery {
Ok(model.into()) Ok(model.into())
} }
/// 是否已经存在相同name的发布数据
async fn is_release_data_name_exists(
&self,
ctx: &Context<'_>,
name: String,
) -> async_graphql::Result<bool> {
let db_accessor = ctx.data::<RtssDbAccessor>()?;
let result = db_accessor.is_release_data_name_exist(&name).await?;
Ok(result)
}
/// 查询发布数据的版本 /// 查询发布数据的版本
async fn release_data_version_paging( async fn release_data_version_paging(
&self, &self,
@ -162,12 +178,12 @@ impl ReleaseDataMutation {
async fn create_draft_data_from_release_data_version( async fn create_draft_data_from_release_data_version(
&self, &self,
ctx: &Context<'_>, ctx: &Context<'_>,
release_data_id: i32,
version_id: i32, version_id: i32,
user_id: i32,
) -> async_graphql::Result<DraftDataDto> { ) -> async_graphql::Result<DraftDataDto> {
let db_accessor = ctx.data::<RtssDbAccessor>()?; let db_accessor = ctx.data::<RtssDbAccessor>()?;
let result = db_accessor let result = db_accessor
.create_draft_from_release_version(release_data_id, version_id) .create_draft_from_release_version(version_id, user_id)
.await?; .await?;
Ok(result.into()) Ok(result.into())
} }
@ -215,6 +231,7 @@ impl From<ReleaseDataFilterDto> for rtss_db::ReleaseDataQuery {
} }
#[derive(Debug, SimpleObject)] #[derive(Debug, SimpleObject)]
#[graphql(complex)]
pub struct ReleaseDataDto { pub struct ReleaseDataDto {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
@ -227,6 +244,53 @@ pub struct ReleaseDataDto {
pub updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
} }
#[ComplexObject]
impl ReleaseDataDto {
async fn description(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<String>> {
if let Some(version_id) = self.used_version_id {
let loader = ctx.data_unchecked::<DataLoader<RtssDbLoader>>();
let description = loader
.load_one(ReleaseDataVersionId::new(version_id))
.await?;
Ok(description)
} else {
Ok(None)
}
}
}
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub struct ReleaseDataVersionId {
pub id: i32,
}
impl ReleaseDataVersionId {
pub fn new(id: i32) -> Self {
Self { id }
}
}
impl Loader<ReleaseDataVersionId> for RtssDbLoader {
type Value = String;
type Error = Arc<DbAccessError>;
async fn load(
&self,
keys: &[ReleaseDataVersionId],
) -> Result<HashMap<ReleaseDataVersionId, Self::Value>, Self::Error> {
let ids: Vec<i32> = keys.iter().map(|k| k.id).collect();
let rows = self
.db_accessor
.query_release_data_version_descriptions(ids.as_slice())
.await?;
let map: HashMap<ReleaseDataVersionId, String> = rows
.into_iter()
.map(|r| (ReleaseDataVersionId { id: r.0 }, r.1))
.collect();
Ok(map)
}
}
#[derive(Debug, SimpleObject)] #[derive(Debug, SimpleObject)]
pub struct ReleaseIscsDataWithoutVersionDto { pub struct ReleaseIscsDataWithoutVersionDto {
pub release_data: ReleaseDataDto, pub release_data: ReleaseDataDto,

View File

@ -1,5 +1,4 @@
use async_graphql::*; use async_graphql::*;
use async_graphql::{EmptySubscription, Schema};
use async_graphql_axum::{GraphQLRequest, GraphQLResponse}; use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
use axum::extract::State; use axum::extract::State;
use axum::http::HeaderMap; use axum::http::HeaderMap;
@ -14,8 +13,7 @@ use rtss_log::tracing::{debug, info};
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tower_http::cors::CorsLayer; use tower_http::cors::CorsLayer;
use crate::apis::{Mutation, Query}; use crate::apis::RtssAppSchema;
use crate::simulation_definition::MutexSimulationManager;
pub struct ServerConfig { pub struct ServerConfig {
pub database_url: String, pub database_url: String,
@ -36,7 +34,7 @@ impl ServerConfig {
} }
pub async fn serve(config: ServerConfig) -> anyhow::Result<()> { pub async fn serve(config: ServerConfig) -> anyhow::Result<()> {
let schema = new_schema(&config).await; let schema = crate::apis::new_schema(&config).await;
let app = Router::new() let app = Router::new()
.route("/", get(graphiql).post(graphql_handler)) .route("/", get(graphiql).post(graphql_handler))
@ -59,7 +57,7 @@ pub async fn serve(config: ServerConfig) -> anyhow::Result<()> {
} }
async fn graphql_handler( async fn graphql_handler(
State(schema): State<SimulationSchema>, State(schema): State<RtssAppSchema>,
_headers: HeaderMap, _headers: HeaderMap,
req: GraphQLRequest, req: GraphQLRequest,
) -> GraphQLResponse { ) -> GraphQLResponse {
@ -80,13 +78,3 @@ async fn graphql_handler(
async fn graphiql() -> impl IntoResponse { async fn graphiql() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new("/"))) Html(playground_source(GraphQLPlaygroundConfig::new("/")))
} }
pub type SimulationSchema = Schema<Query, Mutation, EmptySubscription>;
pub async fn new_schema(config: &ServerConfig) -> SimulationSchema {
let dba = rtss_db::get_db_accessor(&config.database_url).await;
Schema::build(Query::default(), Mutation::default(), EmptySubscription)
.data(dba)
.data(MutexSimulationManager::default())
.finish()
}

View File

@ -178,6 +178,14 @@ impl CreateDraftData {
self self
} }
pub fn with_option_default_release_data_id(
mut self,
default_release_data_id: Option<i32>,
) -> Self {
self.default_release_data_id = default_release_data_id;
self
}
pub fn with_default_release_data_id(mut self, default_release_data_id: i32) -> Self { pub fn with_default_release_data_id(mut self, default_release_data_id: i32) -> Self {
self.default_release_data_id = Some(default_release_data_id); self.default_release_data_id = Some(default_release_data_id);
self self
@ -400,7 +408,10 @@ impl DraftDataAccessor for RtssDbAccessor {
DataType::try_from(draft_data.data_type).unwrap(), DataType::try_from(draft_data.data_type).unwrap(),
user_id, user_id,
) )
.with_data(draft_data.data.as_ref().unwrap()); .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 self.create_draft_data(create).await
} }
} }
@ -472,7 +483,12 @@ mod tests {
println!("{:?}", new_draft); println!("{:?}", new_draft);
assert_eq!(new_draft.name, "new draft"); assert_eq!(new_draft.name, "new draft");
assert_eq!(new_draft.user_id, 11); assert_eq!(new_draft.user_id, 11);
assert_eq!(new_draft.options, res.options);
assert_eq!(new_draft.data.unwrap(), data); assert_eq!(new_draft.data.unwrap(), data);
assert_eq!(
new_draft.default_release_data_id,
res.default_release_data_id
);
// delete测试 // delete测试
accessor.delete_draft_data(&[res.id, new_draft.id]).await?; accessor.delete_draft_data(&[res.id, new_draft.id]).await?;

View File

@ -3,6 +3,7 @@ pub use draft_data::*;
mod release_data; mod release_data;
pub use release_data::*; pub use release_data::*;
#[derive(Clone)]
pub struct RtssDbAccessor { pub struct RtssDbAccessor {
pool: sqlx::PgPool, pool: sqlx::PgPool,
} }

View File

@ -51,6 +51,11 @@ pub trait ReleaseDataAccessor {
&self, &self,
version_id: i32, version_id: i32,
) -> Result<ReleaseDataVersionModel, DbAccessError>; ) -> Result<ReleaseDataVersionModel, DbAccessError>;
/// 根据id列表查询发布版本数据description
async fn query_release_data_version_descriptions(
&self,
version_ids: &[i32],
) -> Result<Vec<(i32, String)>, DbAccessError>;
/// 查询发布数据详情 /// 查询发布数据详情
async fn query_release_data_with_used_version( async fn query_release_data_with_used_version(
&self, &self,
@ -445,6 +450,25 @@ impl ReleaseDataAccessor for RtssDbAccessor {
Ok(rdv) Ok(rdv)
} }
async fn query_release_data_version_descriptions(
&self,
version_ids: &[i32],
) -> Result<Vec<(i32, String)>, DbAccessError> {
// 查询发布数据版本
let rdv_table = ReleaseDataVersionColumn::Table.name();
let rdv_id = ReleaseDataVersionColumn::Id.name();
let rdv_description = ReleaseDataVersionColumn::Description.name();
let select_columns = format!("{rdv_id}, {rdv_description}");
let rdv_query_clause =
format!("SELECT {select_columns} FROM {rdv_table} WHERE {rdv_id} = ANY($1)",);
let rdv = sqlx::query_as::<_, (i32, String)>(&rdv_query_clause)
.bind(version_ids)
.fetch_all(&self.pool)
.await?;
Ok(rdv)
}
async fn query_release_data_with_used_version( async fn query_release_data_with_used_version(
&self, &self,
release_id: i32, release_id: i32,
@ -780,6 +804,17 @@ mod tests {
assert_eq!(page_result.total, 8); assert_eq!(page_result.total, 8);
println!("分页查询发布数据测试成功"); println!("分页查询发布数据测试成功");
// 测试根据数据版本id查询descriptions
let version_ids: Vec<i32> = page_result
.data
.into_iter()
.map(|d| d.used_version_id.unwrap())
.collect();
let description_map = accessor
.query_release_data_version_descriptions(version_ids.as_slice())
.await?;
println!("{:?}", description_map);
Ok(()) Ok(())
} }
} }

View File

@ -4,7 +4,7 @@ use thiserror::Error;
pub enum DbAccessError { pub enum DbAccessError {
#[error("未知的数据库访问错误")] #[error("未知的数据库访问错误")]
Unknown, Unknown,
#[error("sqlx 错误: {0}")] #[error("数据访问错误: {0}")]
SqlxError(#[from] sqlx::Error), SqlxError(#[from] sqlx::Error),
#[error("数据已存在")] #[error("数据已存在")]
RowExist, RowExist,