├── data ├── web │ └── .gitkeep ├── static │ └── .gitkeep ├── upload │ └── .gitkeep ├── img │ └── 1.png └── mask │ └── mask.png ├── src ├── data │ └── mailers │ │ └── welcome │ │ ├── subject.t │ │ ├── text.t │ │ └── html.t ├── model │ ├── sys │ │ ├── args │ │ │ ├── asys_post.rs │ │ │ ├── acache.rs │ │ │ ├── asys_user_dept.rs │ │ │ ├── asys_user_role.rs │ │ │ ├── mod.rs │ │ │ ├── acaptch.rs │ │ │ ├── asys_job_log.rs │ │ │ ├── asys_api_permission.rs │ │ │ ├── asys_white_jwt.rs │ │ │ ├── asys_dict_type.rs │ │ │ ├── asys_oper_log.rs │ │ │ ├── asys_role_api.rs │ │ │ ├── asys_job.rs │ │ │ ├── aserve_info.rs │ │ │ ├── asys_dict_data.rs │ │ │ ├── asys_login_info.rs │ │ │ ├── asys_role.rs │ │ │ └── asys_dept.rs │ │ ├── model │ │ │ ├── msys_post.rs │ │ │ ├── mod.rs │ │ │ ├── msys_role_dept.rs │ │ │ ├── msys_job_log.rs │ │ │ ├── msys_role_menu.rs │ │ │ ├── msys_oper_log.rs │ │ │ ├── msys_user_dept.rs │ │ │ ├── msys_user_role.rs │ │ │ ├── msys_dict_type.rs │ │ │ ├── msys_login_info.rs │ │ │ ├── msys_dict_data.rs │ │ │ ├── msys_job.rs │ │ │ └── msys_api_permission.rs │ │ └── entity │ │ │ ├── mod.rs │ │ │ ├── sys_role_api.rs │ │ │ ├── sys_job_log.rs │ │ │ ├── sys_white_jwt.rs │ │ │ ├── sys_dict_type.rs │ │ │ ├── sys_api_permission.rs │ │ │ ├── sys_dict_data.rs │ │ │ ├── sys_job.rs │ │ │ ├── sys_login_info.rs │ │ │ ├── sys_notice.rs │ │ │ ├── sys_oper_log.rs │ │ │ ├── prelude.rs │ │ │ ├── sys_post.rs │ │ │ ├── sys_role_menu.rs │ │ │ ├── sys_user_dept.rs │ │ │ ├── sys_user_post.rs │ │ │ ├── sys_user_role.rs │ │ │ ├── sys_role_dept.rs │ │ │ ├── sys_menu.rs │ │ │ ├── sys_dept.rs │ │ │ ├── sys_role.rs │ │ │ └── sys_user.rs │ ├── sys.rs │ ├── test.rs │ ├── test │ │ ├── args │ │ │ ├── mod.rs │ │ │ ├── atest_api.rs │ │ │ └── atest_data_scope.rs │ │ ├── model │ │ │ └── mod.rs │ │ └── entity │ │ │ ├── mod.rs │ │ │ ├── prelude.rs │ │ │ ├── test_api.rs │ │ │ └── test_data_scope.rs │ └── prelude.rs ├── model.rs ├── service │ ├── test.rs │ ├── data_scope.rs │ ├── sys │ │ ├── s_sys_user_dept.rs │ │ ├── s_sys_user_role.rs │ │ ├── s_sys_job_log.rs │ │ ├── s_sys_cache.rs │ │ ├── s_sys_operation_log.rs │ │ ├── s_sys_test.rs │ │ ├── s_sys_dict_type.rs │ │ ├── s_sys_dict_data.rs │ │ ├── s_sys_role.rs │ │ ├── s_sys_api_permission.rs │ │ ├── s_sys_role_api.rs │ │ ├── s_sys_white_jwt.rs │ │ ├── s_sys_captcha.rs │ │ ├── s_sys_login_info.rs │ │ ├── s_sys_server_info.rs │ │ ├── s_sys_dashboard.rs │ │ ├── s_sys_upload.rs │ │ └── s_sys_menu.rs │ ├── sys.rs │ ├── prelude.rs │ ├── test │ │ ├── s_test_data_scope.rs │ │ └── s_test_api.rs │ └── data_scope │ │ └── types.rs ├── service.rs ├── main.rs ├── config.rs ├── lib.rs ├── banner.rs ├── midle_ware.rs ├── common.rs ├── db.rs ├── common │ ├── tera.rs │ ├── util.rs │ ├── validatedform.rs │ ├── validatedquery.rs │ ├── validatedjson.rs │ ├── snowflakeid.rs │ └── ser.rs ├── worker │ ├── common │ │ ├── job.rs │ │ ├── scheduled.rs │ │ ├── worker_opts.rs │ │ ├── enqueue_opts.rs │ │ ├── unit_of_work.rs │ │ └── worker.rs │ ├── periodic_manager.rs │ ├── job.rs │ ├── logininfo.rs │ ├── common.rs │ ├── app_worker.rs │ ├── mailer │ │ ├── template.rs │ │ └── email_sender.rs │ ├── processor_manager.rs │ ├── requesturl.rs │ ├── mailer.rs │ └── invokefunction.rs ├── db │ └── id.rs ├── worker.rs ├── api.rs ├── api │ ├── test.rs │ └── web_path.rs ├── cache │ └── traits.rs └── midle_ware │ └── auth.rs ├── .env ├── doc.md ├── migration ├── src │ ├── main.rs │ ├── lib.rs │ └── m20220101_000001_create_table.rs └── Cargo.toml ├── .gitignore ├── Dockerfile ├── LICENSE └── Cargo.toml /data/web/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/upload/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/data/mailers/welcome/subject.t: -------------------------------------------------------------------------------- 1 | XXXX验证 {{name}} 2 | -------------------------------------------------------------------------------- /src/model/sys/args/asys_post.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=mysql://qiluo:Qiluo123@localhost:3306/qiluoopen -------------------------------------------------------------------------------- /src/model.rs: -------------------------------------------------------------------------------- 1 | pub mod sys; 2 | pub mod prelude; 3 | pub mod test; -------------------------------------------------------------------------------- /src/model/sys.rs: -------------------------------------------------------------------------------- 1 | pub mod entity; 2 | pub mod model; 3 | pub mod args; -------------------------------------------------------------------------------- /src/model/test.rs: -------------------------------------------------------------------------------- 1 | pub mod entity; 2 | pub mod model; 3 | pub mod args; -------------------------------------------------------------------------------- /src/service/test.rs: -------------------------------------------------------------------------------- 1 | pub mod s_test_api; 2 | pub mod s_test_data_scope; -------------------------------------------------------------------------------- /src/model/test/args/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod atest_api; 2 | pub mod atest_data_scope; -------------------------------------------------------------------------------- /data/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chelunfu/qiluo_admin/HEAD/data/img/1.png -------------------------------------------------------------------------------- /data/mask/mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chelunfu/qiluo_admin/HEAD/data/mask/mask.png -------------------------------------------------------------------------------- /src/service.rs: -------------------------------------------------------------------------------- 1 | pub mod prelude; 2 | pub mod sys; 3 | pub mod test; 4 | pub mod data_scope; -------------------------------------------------------------------------------- /doc.md: -------------------------------------------------------------------------------- 1 | //编译到linux 2 | cross build --release --target x86_64-unknown-linux-musl 3 | cargo run --target x86_64-pc-windows-msvc -------------------------------------------------------------------------------- /src/data/mailers/welcome/text.t: -------------------------------------------------------------------------------- 1 | 欢迎 {{name}}, 您现在可以登录了。 2 | 使用以下链接验证您的帐户: 3 | 4 | {{host}}/api/sys/verify/{{verifyToken}} 5 | -------------------------------------------------------------------------------- /src/model/test/model/mod.rs: -------------------------------------------------------------------------------- 1 | pub use super::entity as entity; 2 | pub use super::args as args; 3 | pub mod mtest_api; 4 | pub mod mtest_data_scope; -------------------------------------------------------------------------------- /migration/src/main.rs: -------------------------------------------------------------------------------- 1 | use sea_orm_migration::prelude::*; 2 | 3 | #[async_std::main] 4 | async fn main() { 5 | cli::run_cli(migration::Migrator).await; 6 | } 7 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use qiluo::app::App; 2 | use qiluo::banner::BANNER; 3 | #[tokio::main] 4 | async fn main() { 5 | println!("{BANNER}"); 6 | App::run().await; 7 | } -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | pub mod appconfig; 2 | use once_cell::sync::Lazy; 3 | use appconfig::AppConfig; 4 | pub static APPCOFIG: Lazy = Lazy::new(self::AppConfig::init); -------------------------------------------------------------------------------- /src/model/test/entity/mod.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | pub mod prelude; 4 | 5 | pub mod test_api; 6 | pub mod test_data_scope; 7 | -------------------------------------------------------------------------------- /src/service/data_scope.rs: -------------------------------------------------------------------------------- 1 | mod types; 2 | mod context; 3 | mod checker; 4 | 5 | pub use types::*; 6 | pub use context::DataScopeContext; 7 | pub use checker::check_record_scope; 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/model/test/entity/prelude.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | pub use super::test_api::Entity as TestApi; 4 | pub use super::test_data_scope::Entity as TestDataScope; 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod api; 2 | pub mod service; 3 | pub mod db; 4 | pub mod model; 5 | pub mod cache; 6 | pub mod config; 7 | pub mod common; 8 | pub mod app; 9 | pub mod midle_ware; 10 | pub mod worker; 11 | pub mod banner; -------------------------------------------------------------------------------- /src/banner.rs: -------------------------------------------------------------------------------- 1 | pub const BANNER: &str = r" 2 | ____ _ __ 3 | / __ \ (_) / __ ______ 4 | / / / / / / / / / / / __ \ 5 | / /_/ / / / /___/ /_/ / /_/ / 6 | \___\_\/_/_____/\__._/\____/ 7 | 8 | "; -------------------------------------------------------------------------------- /src/midle_ware.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | pub mod jwt; 3 | pub mod operate_log; 4 | pub use auth::auth_fn_mid as AuthMid; 5 | pub use auth::api_fn_mid as ApiMid; 6 | pub use auth::request_log_fn_mid as RequestLogMid; 7 | pub use operate_log::operate_log_fn_mid as OperateLogMid; -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod result; 3 | pub mod util; 4 | pub mod validatedjson; 5 | pub mod tera; 6 | pub mod validatedquery; 7 | pub mod validatedform; 8 | pub mod ser; 9 | pub mod snowflakeid; 10 | pub use result::ApiResponse as ApiResponse; 11 | pub use error::Error as Error; -------------------------------------------------------------------------------- /src/data/mailers/welcome/html.t: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dear {{name}}, 5 | 欢迎使用XXXX系统! 您现在可以登录到您的帐户。 6 | 开始之前,请单击下面的链接验证您的帐户: 7 | 8 | 验证您的帐户 9 | 10 |

Best regards,
XXXX科技

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/db.rs: -------------------------------------------------------------------------------- 1 | mod id; 2 | mod registry; 3 | mod router; 4 | pub use id::generator_id as GID; 5 | pub use registry::{ 6 | db as DB, db_by_index as DB_BY_INDEX, db_by_name as DB_BY_NAME, db_read as DB_READ, 7 | db_write as DB_WRITE, with_read, 8 | }; 9 | pub use router::db_auto as DB_AUTO; 10 | -------------------------------------------------------------------------------- /src/model/sys/model/msys_post.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | pub use super::entity::sys_post::{self, ActiveModel, Model as SysPostModel}; 3 | 4 | 5 | impl SysPostModel{ 6 | pub async fn list() {} 7 | pub async fn add() {} 8 | pub async fn del() {} 9 | pub async fn edit() {} 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | 7 | # These are backup files generated by rustfmt 8 | **/*.rs.bk 9 | /data/log 10 | /data/web/* 11 | !/data/web/.gitkeep 12 | /data/static/* 13 | !/data/static/.gitkeep 14 | /data/upload/* 15 | !/data/upload/.gitkeep -------------------------------------------------------------------------------- /src/service/sys/s_sys_user_dept.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::model::msys_user_dept::SysUserDeptModel; 2 | use crate::service::prelude::*; 3 | 4 | pub async fn user_dept_name_list(userinfo: UserInfo) -> impl IntoResponse { 5 | let r = SysUserDeptModel::user_dept_name_list(userinfo.uid,userinfo.did).await; 6 | ApiResponse::from_result(r) 7 | } -------------------------------------------------------------------------------- /src/common/tera.rs: -------------------------------------------------------------------------------- 1 | use tera::{Context, Tera}; 2 | 3 | use super::error::Result; 4 | 5 | pub fn render_string(tera_template: &str, locals: &serde_json::Value) -> Result { 6 | let connt=Context::from_serialize(locals).unwrap(); 7 | let text = Tera::one_off(tera_template, &connt, false).unwrap(); 8 | Ok(text) 9 | } 10 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_user_role.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::model::msys_user_role::SysUserRoleModel; 2 | use crate::service::prelude::*; 3 | 4 | pub async fn user_role_name_list(userinfo: UserInfo) -> impl IntoResponse { 5 | let r = SysUserRoleModel::user_role_name_list(userinfo.uid, userinfo.rid).await; 6 | ApiResponse::from_result(r) 7 | } 8 | -------------------------------------------------------------------------------- /migration/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use sea_orm_migration::prelude::*; 2 | 3 | mod m20220101_000001_create_table; 4 | 5 | pub struct Migrator; 6 | 7 | #[async_trait::async_trait] 8 | impl MigratorTrait for Migrator { 9 | fn migrations() -> Vec> { 10 | vec![Box::new(m20220101_000001_create_table::Migration)] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/model/sys/args/acache.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone, Validate)] 4 | pub struct CacheSearch { 5 | pub key: Option, 6 | } 7 | 8 | #[derive(Debug, Serialize, Deserialize, Clone)] 9 | pub struct CacheItem { 10 | pub key: String, 11 | pub value: String, 12 | pub ttl: Option, 13 | } 14 | -------------------------------------------------------------------------------- /src/model/sys/args/asys_user_dept.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Deserialize, Serialize, FromQueryResult, Clone, Default)] 4 | pub struct UserDeptResp { 5 | #[serde(with = "i64_to_string")] 6 | pub dept_id: i64, 7 | pub dept_name: String, 8 | } 9 | 10 | #[derive(Debug, Deserialize, Serialize, Clone, Default)] 11 | pub struct UserDeptAndUserResp { 12 | #[serde(with = "i64_to_string")] 13 | pub user_dept_id: i64, 14 | pub depts: Vec, 15 | } -------------------------------------------------------------------------------- /src/model/sys/args/asys_user_role.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Deserialize, Serialize, FromQueryResult, Clone, Default)] 4 | pub struct UserRoleResp { 5 | #[serde(with = "i64_to_string")] 6 | pub role_id: i64, 7 | pub role_name: String, 8 | } 9 | #[derive(Debug, Deserialize, Serialize, Clone, Default)] 10 | pub struct UserRoleAndUserResp { 11 | #[serde(with = "i64_to_string")] 12 | pub user_role_id: i64, 13 | pub roles: Vec, 14 | } 15 | -------------------------------------------------------------------------------- /src/model/sys/args/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | pub mod acache; 3 | pub mod acaptch; 4 | pub mod asys_user; 5 | pub mod asys_menu; 6 | pub mod asys_dept; 7 | pub mod asys_role; 8 | pub mod asys_role_api; 9 | pub mod asys_white_jwt; 10 | pub mod asys_login_info; 11 | pub mod asys_dict_data; 12 | pub mod asys_dict_type; 13 | pub mod asys_job; 14 | pub mod asys_job_log; 15 | pub mod asys_post; 16 | pub mod aserve_info; 17 | pub mod asys_api_permission; 18 | pub mod asys_oper_log; 19 | pub mod asys_user_role; 20 | pub mod asys_user_dept; -------------------------------------------------------------------------------- /src/service/sys/s_sys_job_log.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::model::msys_job_log::{JobLogSearch, SysJobLogModel}; 2 | use crate::service::prelude::*; 3 | 4 | pub async fn list( 5 | VQuery(arg): VQuery, 6 | VQuery(search): VQuery, 7 | ) -> impl IntoResponse { 8 | let rlist = SysJobLogModel::list(arg, search).await; 9 | ApiResponse::from_result(rlist) 10 | } 11 | pub async fn edit() -> impl IntoResponse {} 12 | pub async fn add() -> impl IntoResponse {} 13 | pub async fn delete() -> impl IntoResponse {} 14 | -------------------------------------------------------------------------------- /src/model/sys/args/acaptch.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Clone)] 4 | pub struct CaptchaImage { 5 | pub captcha_on_off: bool, 6 | #[serde(with = "i64_to_string")] 7 | pub uuid: i64, 8 | pub img: String, 9 | } 10 | 11 | #[derive(Debug, Serialize, Deserialize, Clone,Validate)] 12 | pub struct ClientInfo{ 13 | pub client_id: String, 14 | } 15 | 16 | #[derive(Debug, Serialize, Deserialize, Clone)] 17 | pub struct CaptchaCacheInfo { 18 | pub client_id: String, 19 | pub cache_text:String 20 | } -------------------------------------------------------------------------------- /src/model/sys/model/mod.rs: -------------------------------------------------------------------------------- 1 | pub use super::entity as entity; 2 | pub use super::args as args; 3 | pub mod msys_user; 4 | pub mod msys_menu; 5 | pub mod msys_user_role; 6 | pub mod msys_dept; 7 | pub mod msys_role; 8 | pub mod msys_role_menu; 9 | pub mod msys_user_dept; 10 | pub mod msys_role_api; 11 | pub mod msys_login_info; 12 | pub mod msys_white_jwt; 13 | pub mod msys_job; 14 | pub mod msys_job_log; 15 | pub mod msys_dict_data; 16 | pub mod msys_dict_type; 17 | pub mod msys_post; 18 | pub mod msys_api_permission; 19 | pub mod msys_oper_log; 20 | pub mod msys_role_dept; -------------------------------------------------------------------------------- /src/service/sys.rs: -------------------------------------------------------------------------------- 1 | pub mod s_sys_user; 2 | pub mod s_sys_role_api; 3 | pub mod s_sys_test; 4 | pub mod s_sys_menu; 5 | pub mod s_sys_captcha; 6 | pub mod s_sys_user_role; 7 | pub mod s_sys_dept; 8 | pub mod s_sys_role; 9 | pub mod s_sys_login_info; 10 | pub mod s_sys_white_jwt; 11 | pub mod s_sys_dict_type; 12 | pub mod s_sys_dict_data; 13 | pub mod s_sys_job; 14 | pub mod s_sys_server_info; 15 | pub mod s_sys_job_log; 16 | pub mod s_sys_api_permission; 17 | pub mod s_sys_dashboard; 18 | pub mod s_sys_operation_log; 19 | pub mod s_sys_cache; 20 | pub mod s_sys_upload; 21 | pub mod s_sys_user_dept; -------------------------------------------------------------------------------- /src/worker/common/job.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::Value as JsonValue; 3 | 4 | #[derive(Serialize, Deserialize, Debug, Clone)] 5 | pub struct Job { 6 | pub queue: String, 7 | pub args: JsonValue, 8 | pub retry: bool, 9 | pub class: String, 10 | pub jid: i64, 11 | pub created_at: f64, 12 | pub enqueued_at: Option, 13 | pub failed_at: Option, 14 | pub error_message: Option, 15 | pub retry_count: Option, 16 | pub retried_at: Option, 17 | 18 | #[serde(skip)] 19 | pub unique_for: Option, 20 | } 21 | -------------------------------------------------------------------------------- /src/model/sys/entity/mod.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | pub mod prelude; 4 | 5 | pub mod sys_api_permission; 6 | pub mod sys_dept; 7 | pub mod sys_dict_data; 8 | pub mod sys_dict_type; 9 | pub mod sys_job; 10 | pub mod sys_job_log; 11 | pub mod sys_login_info; 12 | pub mod sys_menu; 13 | pub mod sys_notice; 14 | pub mod sys_oper_log; 15 | pub mod sys_post; 16 | pub mod sys_role; 17 | pub mod sys_role_api; 18 | pub mod sys_role_dept; 19 | pub mod sys_role_menu; 20 | pub mod sys_user; 21 | pub mod sys_user_dept; 22 | pub mod sys_user_post; 23 | pub mod sys_user_role; 24 | pub mod sys_white_jwt; 25 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_cache.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::args::acache::*; 2 | use crate::service::prelude::*; 3 | pub async fn list( 4 | VQuery(arg): VQuery, 5 | VQuery(search): VQuery, 6 | ) -> impl IntoResponse { 7 | let page_num = arg.page_num.unwrap_or(1); 8 | let page_per_size = arg.page_size.unwrap_or(10); 9 | let cache = CacheManager::instance().await; 10 | let rlist = cache 11 | .get_all_paginated(page_num, page_per_size, search.key) 12 | .await; 13 | 14 | ApiResponse::from_result(rlist) 15 | } 16 | 17 | pub async fn clear() -> impl IntoResponse {} 18 | -------------------------------------------------------------------------------- /src/db/id.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use lazy_static::lazy_static; 4 | 5 | use crate::common::snowflakeid::SnowflakeIdGenerator; 6 | use crate::config::APPCOFIG; 7 | 8 | lazy_static! { 9 | static ref ID_GENERATOR_GENERATOR: Arc> = { 10 | let config = &APPCOFIG.snowgenera; 11 | Arc::new(tokio::sync::Mutex::new(SnowflakeIdGenerator::new( 12 | config.machine_id, 13 | config.node_id, 14 | ))) 15 | }; 16 | } 17 | 18 | pub async fn generator_id() -> i64 { 19 | let id_generator = ID_GENERATOR_GENERATOR.lock().await; 20 | id_generator.real_time_generate() 21 | } 22 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_operation_log.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::model::msys_oper_log::{SysOperLogAdd, SysOperLogModel,SysOperLogSearch}; 2 | use crate::service::prelude::*; 3 | 4 | pub async fn list(VQuery(arg): VQuery, 5 | VQuery(search):VQuery)->impl IntoResponse{ 6 | let rlist= SysOperLogModel::list(arg,search).await; 7 | ApiResponse::from_result(rlist) 8 | } 9 | pub async fn edit()->impl IntoResponse{ 10 | 11 | } 12 | pub async fn add() -> impl IntoResponse {} 13 | pub async fn delete() -> impl IntoResponse {} 14 | 15 | 16 | pub async fn add_oper_log(arg: SysOperLogAdd) { 17 | let _ = SysOperLogModel::add(arg).await; 18 | } 19 | -------------------------------------------------------------------------------- /src/worker.rs: -------------------------------------------------------------------------------- 1 | pub mod app_worker; 2 | pub mod common; 3 | pub mod invokefunction; 4 | pub mod job; 5 | pub mod logininfo; 6 | pub mod mailer; 7 | pub mod periodic_manager; 8 | pub mod processor_manager; 9 | pub mod requesturl; 10 | 11 | // 重新导出公共接口 12 | pub use app_worker::AppWorker; 13 | pub use common::{Processor, Worker}; 14 | pub use periodic_manager::{clear_periodic_worker, periodic_worker}; 15 | pub use processor_manager::{get_queues, processor_job, DEFAULT_QUEUES}; 16 | 17 | // 重新导出各个worker 18 | use invokefunction::InvokeFunctionWorker; 19 | use job::JobWorker; 20 | use logininfo::LoginInfoWorker; 21 | use mailer::MailerWorker; 22 | use requesturl::RequestUrlWorker; 23 | -------------------------------------------------------------------------------- /src/model/test/entity/test_api.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "test_api")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub id: i64, 11 | pub name: String, 12 | pub age: i32, 13 | pub email: String, 14 | pub created_at: DateTime, 15 | pub updated_at: DateTime, 16 | } 17 | 18 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 19 | pub enum Relation {} 20 | 21 | impl ActiveModelBehavior for ActiveModel {} 22 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_role_api.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_role_api")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub id: i64, 11 | pub role_id: i64, 12 | pub api_id: i64, 13 | pub api: String, 14 | pub method: String, 15 | pub apiname: String, 16 | pub sort: i32, 17 | } 18 | 19 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 20 | pub enum Relation {} 21 | 22 | impl ActiveModelBehavior for ActiveModel {} 23 | -------------------------------------------------------------------------------- /migration/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "migration" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | name = "migration" 9 | path = "src/lib.rs" 10 | 11 | [dependencies] 12 | async-std = { version = "1", features = ["attributes", "tokio1"] } 13 | 14 | [dependencies.sea-orm-migration] 15 | version = "1.0.0-rc.5" 16 | features = [ 17 | # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. 18 | # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. 19 | # e.g. 20 | "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature 21 | "sqlx-mysql", # `DATABASE_DRIVER` feature 22 | ] 23 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_job_log.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_job_log")] 8 | pub struct Model { 9 | pub created_at: DateTime, 10 | #[sea_orm(primary_key, auto_increment = false, unique)] 11 | pub id: i64, 12 | pub job_id: i64, 13 | pub run_count: i32, 14 | pub job_message: Option, 15 | pub status: String, 16 | pub elapsed_time: Option, 17 | } 18 | 19 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 20 | pub enum Relation {} 21 | 22 | impl ActiveModelBehavior for ActiveModel {} 23 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_white_jwt.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_white_jwt")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub jid: i64, 11 | pub uid: i64, 12 | pub token_id: i64, 13 | pub info_id: i64, 14 | pub token_expr: i64, 15 | pub created_at: DateTime, 16 | pub update_at: DateTime, 17 | pub deleted_at: Option, 18 | } 19 | 20 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 21 | pub enum Relation {} 22 | 23 | impl ActiveModelBehavior for ActiveModel {} 24 | -------------------------------------------------------------------------------- /src/worker/periodic_manager.rs: -------------------------------------------------------------------------------- 1 | use super::common::periodic; 2 | 3 | pub async fn clear_periodic_worker() { 4 | let _ = periodic::destroy_all().await; 5 | } 6 | 7 | 8 | pub async fn periodic_worker( 9 | cron_str: &str, 10 | name: &str, 11 | queue: &str, 12 | args: Args, 13 | class_name: String, 14 | ) where 15 | Args: Sync + Send + for<'de> serde::Deserialize<'de> + serde::Serialize + 'static, 16 | { 17 | let d = periodic::builder(cron_str) 18 | .unwrap() 19 | .name(name) 20 | .queue(queue) 21 | .args(args) 22 | .unwrap() 23 | .into_periodic_job(class_name) 24 | .unwrap(); 25 | 26 | let payload = serde_json::to_string(&d).unwrap(); 27 | let _ = d.update(&payload).await; 28 | } 29 | -------------------------------------------------------------------------------- /src/model/sys/args/asys_job_log.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Clone, Serialize, Deserialize,Default,FromQueryResult)] 4 | pub struct JobLogRes { 5 | pub id: i64, 6 | pub job_id: i64, 7 | pub run_count: i32, 8 | pub job_message: Option, 9 | pub status: String, 10 | pub elapsed_time: Option, 11 | } 12 | 13 | 14 | #[derive(Debug, Clone, Serialize, Deserialize,Default)] 15 | pub struct JobLogAdd { 16 | pub job_id: i64, 17 | pub run_count: i32, 18 | pub job_message: Option, 19 | pub status: String, 20 | pub elapsed_time: Option, 21 | } 22 | 23 | #[derive(Debug, Clone, Serialize, Deserialize,Default,Validate)] 24 | pub struct JobLogSearch { 25 | pub job_id: String 26 | } -------------------------------------------------------------------------------- /src/model/sys/entity/sys_dict_type.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_dict_type")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub dict_id: i64, 11 | pub dict_name: String, 12 | #[sea_orm(unique)] 13 | pub dict_type: String, 14 | pub order: i32, 15 | pub created_at: DateTime, 16 | pub updated_at: DateTime, 17 | pub remark: Option, 18 | } 19 | 20 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 21 | pub enum Relation {} 22 | 23 | impl ActiveModelBehavior for ActiveModel {} 24 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_api_permission.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_api_permission")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub id: i64, 11 | #[sea_orm(unique)] 12 | pub api: String, 13 | pub method: String, 14 | pub apiname: String, 15 | pub logcache: String, 16 | pub remark: Option, 17 | pub sort: i32, 18 | pub created_at: DateTime, 19 | } 20 | 21 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 22 | pub enum Relation {} 23 | 24 | impl ActiveModelBehavior for ActiveModel {} 25 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_dict_data.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_dict_data")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub dict_code: i64, 11 | pub dict_sort: i32, 12 | pub dict_label: String, 13 | pub dict_value: String, 14 | pub dict_type_id: i64, 15 | pub created_at: Option, 16 | pub updated_at: Option, 17 | pub remark: Option, 18 | } 19 | 20 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 21 | pub enum Relation {} 22 | 23 | impl ActiveModelBehavior for ActiveModel {} 24 | -------------------------------------------------------------------------------- /src/worker/job.rs: -------------------------------------------------------------------------------- 1 | use crate::common::error::Result; 2 | use crate::service::sys::s_sys_job; 3 | use crate::worker::common::{Worker, WorkerOpts}; 4 | use crate::worker::AppWorker; 5 | use async_trait::async_trait; 6 | use serde::{Deserialize, Serialize}; 7 | #[derive(Deserialize, Serialize, Clone, Default)] 8 | pub struct JobMsg { 9 | pub job_id: i64, 10 | } 11 | 12 | #[derive(Clone)] 13 | pub struct JobWorker {} 14 | 15 | impl AppWorker for JobWorker { 16 | fn new() -> Self { 17 | Self {} 18 | } 19 | } 20 | #[async_trait] 21 | impl Worker for JobWorker { 22 | fn opts() -> WorkerOpts { 23 | WorkerOpts::new().queue("default") 24 | } 25 | 26 | async fn perform(&self, _: JobMsg) -> Result<()> { 27 | s_sys_job::update_job().await; 28 | 29 | Ok(()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/worker/logininfo.rs: -------------------------------------------------------------------------------- 1 | use crate::worker::common::{Worker, WorkerOpts}; 2 | use crate::worker::AppWorker; 3 | use crate::common::error::Result; 4 | use async_trait::async_trait; 5 | 6 | use crate::model::sys::model::msys_login_info::LoginInfoMsg; 7 | use crate::service::sys::s_sys_login_info::update_login_info; 8 | 9 | #[derive(Clone)] 10 | pub struct LoginInfoWorker {} 11 | 12 | 13 | impl AppWorker for LoginInfoWorker { 14 | fn new() -> Self { 15 | Self {} 16 | } 17 | } 18 | 19 | #[async_trait] 20 | impl Worker for LoginInfoWorker { 21 | fn opts() -> WorkerOpts { 22 | WorkerOpts::new().queue("default") 23 | } 24 | 25 | async fn perform(&self, arg: LoginInfoMsg) ->Result<()> { 26 | update_login_info(arg).await; 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/model/test/entity/test_data_scope.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "test_data_scope")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub id: i64, 11 | pub title: String, 12 | #[sea_orm(column_type = "Text", nullable)] 13 | pub content: Option, 14 | pub dept_id: i64, 15 | pub owner_id: i64, 16 | pub status: String, 17 | pub created_at: DateTime, 18 | pub updated_at: DateTime, 19 | pub deleted_at: Option, 20 | } 21 | 22 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 23 | pub enum Relation {} 24 | 25 | impl ActiveModelBehavior for ActiveModel {} 26 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_job.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_job")] 8 | pub struct Model { 9 | pub created_at: DateTime, 10 | pub updated_at: DateTime, 11 | #[sea_orm(primary_key, auto_increment = false, unique)] 12 | pub job_id: i64, 13 | pub task_type: String, 14 | pub task_count: i32, 15 | pub run_count: i32, 16 | pub job_name: String, 17 | pub job_params: Option, 18 | pub job_group: String, 19 | pub cron_expression: String, 20 | pub status: String, 21 | pub remark: String, 22 | } 23 | 24 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 25 | pub enum Relation {} 26 | 27 | impl ActiveModelBehavior for ActiveModel {} 28 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_test.rs: -------------------------------------------------------------------------------- 1 | use crate::common::ser::i64_to_string; 2 | use crate::{common::ApiResponse, model::prelude::VJson}; 3 | use axum::response::IntoResponse; 4 | use serde::{Deserialize, Deserializer, Serialize}; 5 | use validator::Validate; 6 | //use crate::service::prelude::*; 7 | 8 | #[derive(Clone, Deserialize, Debug, Validate)] 9 | pub struct UserId { 10 | #[serde(with = "i64_to_string")] 11 | pub uid: i64, 12 | } 13 | 14 | #[derive(Clone, Deserialize, Serialize, Debug, Validate)] 15 | pub struct UserRId { 16 | #[serde(with = "i64_to_string")] 17 | pub ruid: i64, 18 | } 19 | 20 | pub async fn test(VJson(arg): VJson) -> impl IntoResponse { 21 | let st = UserRId { ruid: 1555 }; 22 | ApiResponse::ok(st) 23 | } 24 | 25 | pub async fn list() -> impl IntoResponse {} 26 | pub async fn edit() -> impl IntoResponse {} 27 | pub async fn add() -> impl IntoResponse {} 28 | pub async fn delete() -> impl IntoResponse {} 29 | -------------------------------------------------------------------------------- /src/worker/common.rs: -------------------------------------------------------------------------------- 1 | use crate::common::error::Result; 2 | use serde::Serialize; 3 | 4 | pub mod enqueue_opts; 5 | pub mod job; 6 | pub mod periodic; 7 | mod processor; 8 | mod scheduled; 9 | pub mod unit_of_work; 10 | pub mod worker; 11 | pub mod worker_opts; 12 | 13 | pub use enqueue_opts::{opts, EnqueueOpts}; 14 | pub use job::Job; 15 | pub use processor::{Processor, WorkFetcher}; 16 | pub use scheduled::Scheduled; 17 | pub use unit_of_work::UnitOfWork; 18 | pub use worker::{Worker, WorkerRef}; 19 | pub use worker_opts::WorkerOpts; 20 | 21 | pub async fn perform_async(class: String, queue: String, args: impl Serialize) -> Result<()> { 22 | opts().queue(queue).perform_async(class, args).await 23 | } 24 | 25 | pub async fn perform_in( 26 | duration: std::time::Duration, 27 | class: String, 28 | queue: String, 29 | args: impl Serialize, 30 | ) -> Result<()> { 31 | opts().queue(queue).perform_in(class, duration, args).await 32 | } 33 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_login_info.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_login_info")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub info_id: i64, 11 | pub uid: i64, 12 | pub user_name: String, 13 | pub device_type: Option, 14 | pub ipaddr: Option, 15 | pub login_location: Option, 16 | pub net_work: Option, 17 | pub browser: Option, 18 | pub os: Option, 19 | pub status: Option, 20 | pub msg: Option, 21 | pub login_time: Option, 22 | } 23 | 24 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 25 | pub enum Relation {} 26 | 27 | impl ActiveModelBehavior for ActiveModel {} 28 | -------------------------------------------------------------------------------- /src/worker/app_worker.rs: -------------------------------------------------------------------------------- 1 | use crate::common::error::Result; 2 | use async_trait::async_trait; 3 | 4 | use super::common::Worker; 5 | 6 | #[async_trait] 7 | #[allow(clippy::module_name_repetitions)] 8 | pub trait AppWorker: Worker 9 | where 10 | Self: Sized, 11 | T: Send + Sync + serde::Serialize + 'static, 12 | { 13 | fn new() -> Self; 14 | 15 | //同步加入队列 16 | async fn enqueue_sync(args: T) -> Result<()> { 17 | Self::perform_async(args).await 18 | } 19 | // 异步加入队列 20 | async fn enqueue_async(args: T) -> Result<()> { 21 | tokio::spawn(async move { Self::perform_async(args).await }); 22 | Ok(()) 23 | } 24 | //异步执行 25 | async fn execute_async(args: T) -> Result<()> { 26 | tokio::spawn(async move { Self::new().perform(args).await }); 27 | Ok(()) 28 | } 29 | //同步执行 30 | async fn execute_sync(args: T) -> Result<()> { 31 | Self::new().perform(args).await 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_dict_type.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::service::prelude::*; 3 | use crate::model::sys::model::msys_dict_type::{ 4 | SysDictTypeModel, 5 | DictTypeAdd,DictTypeEdit,DictDataSearch,DictTypeDel 6 | }; 7 | 8 | pub async fn list( 9 | VQuery(arg): VQuery, 10 | VQuery(search): VQuery, 11 | ) -> impl IntoResponse { 12 | let rlist = SysDictTypeModel::list(arg, search).await; 13 | ApiResponse::from_result(rlist) 14 | } 15 | 16 | pub async fn add(VJson(arg): VJson) -> impl IntoResponse { 17 | let r = SysDictTypeModel::add(arg).await; 18 | ApiResponse::from_result(r) 19 | } 20 | 21 | pub async fn edit(VJson(arg): VJson) -> impl IntoResponse { 22 | let r = SysDictTypeModel::edit(arg).await; 23 | ApiResponse::from_result(r) 24 | } 25 | 26 | pub async fn delete(VQuery(arg):VQuery) -> impl IntoResponse { 27 | let r = SysDictTypeModel::del(arg).await; 28 | ApiResponse::from_result(r) 29 | } -------------------------------------------------------------------------------- /src/model/sys/entity/sys_notice.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_notice")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub notice_id: i64, 11 | pub notice_title: String, 12 | pub notice_type: String, 13 | #[sea_orm(column_type = "custom(\"longblob\")", nullable)] 14 | pub notice_content: Option, 15 | pub status: Option, 16 | pub create_dept: Option, 17 | pub create_by: Option, 18 | pub created_at: Option, 19 | pub update_by: Option, 20 | pub updated_at: Option, 21 | pub remark: Option, 22 | } 23 | 24 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 25 | pub enum Relation {} 26 | 27 | impl ActiveModelBehavior for ActiveModel {} 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 构建阶段 2 | FROM docker.1ms.run/rust:alpine AS builder 3 | 4 | # 安装构建依赖 5 | RUN apk add --no-cache \ 6 | musl-dev \ 7 | openssl-dev \ 8 | pkgconfig \ 9 | perl \ 10 | make 11 | 12 | WORKDIR /app 13 | 14 | COPY . . 15 | 16 | # 安装 MUSL 目标并构建 17 | RUN rustup target add x86_64-unknown-linux-musl && \ 18 | cargo build --release --target x86_64-unknown-linux-musl 19 | 20 | # 剥离调试符号 21 | RUN strip target/x86_64-unknown-linux-musl/release/qiluo 22 | 23 | # 检查二进制大小 24 | RUN ls -lh target/x86_64-unknown-linux-musl/release/qiluo 25 | 26 | # 运行时阶段 27 | FROM docker.1ms.run/alpine:latest 28 | 29 | # 在单层中完成所有设置 30 | RUN apk add --no-cache ca-certificates && \ 31 | addgroup -S qiluo && adduser -S qiluo -G qiluo && \ 32 | mkdir -p /app/data && \ 33 | chown -R qiluo:qiluo /app 34 | 35 | WORKDIR /app 36 | 37 | # 复制二进制文件并立即设置权限(避免额外层) 38 | COPY --from=builder --chown=qiluo:qiluo /app/target/x86_64-unknown-linux-musl/release/qiluo . 39 | 40 | USER qiluo 41 | 42 | # 暴露端口 43 | EXPOSE 5001 44 | 45 | CMD ["./qiluo"] -------------------------------------------------------------------------------- /src/service/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::cache::CacheManager; 2 | pub use crate::model; 3 | pub use crate::model::prelude::{ListData, PageParams}; 4 | pub use crate::{ 5 | common::{ 6 | self, 7 | error::{Error, Result}, 8 | util, 9 | validatedjson::VJson, 10 | validatedquery::VQuery, 11 | ApiResponse, 12 | }, 13 | config::APPCOFIG, 14 | db::{with_read, DB, DB_AUTO, DB_BY_INDEX, DB_BY_NAME, DB_READ, DB_WRITE, GID}, 15 | midle_ware::jwt::{self, AuthPayload, Claims, UserInfo}, 16 | }; 17 | pub use axum::{ 18 | extract::{Multipart, Path, Query}, 19 | response::{Html, IntoResponse}, 20 | }; 21 | pub use chrono::{Duration, Local}; 22 | pub use sea_orm::{ 23 | ActiveModelBehavior, ActiveModelTrait, ActiveValue, ColumnTrait, ConnectionTrait, 24 | DatabaseConnection, DbErr, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter, Set, 25 | TransactionTrait, 26 | }; 27 | pub use serde::{Deserialize, Serialize}; 28 | pub use serde_json::json; 29 | pub use tracing::{error, info}; 30 | -------------------------------------------------------------------------------- /src/common/util.rs: -------------------------------------------------------------------------------- 1 | use argon2::{ 2 | password_hash::{SaltString,rand_core::OsRng}, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, 3 | Version, 4 | }; 5 | 6 | use super::error::{Error, Result}; 7 | 8 | pub fn hash_password(pass: &str) -> Result { 9 | let arg2 = Argon2::new( 10 | argon2::Algorithm::Argon2id, 11 | argon2::Version::V0x13, 12 | Params::default(), 13 | ); 14 | let salt: SaltString = SaltString::generate(&mut OsRng); 15 | 16 | Ok(arg2 17 | .hash_password(pass.as_bytes(), &salt) 18 | .map_err(|err| Error::Message(err.to_string()))? 19 | .to_string()) 20 | } 21 | 22 | pub fn verify_password(pass: &str, hashed_password: &str) -> bool { 23 | let arg2 = Argon2::new( 24 | argon2::Algorithm::Argon2id, 25 | Version::V0x13, 26 | Params::default(), 27 | ); 28 | let Ok(hash) = PasswordHash::new(hashed_password) else { 29 | return false; 30 | }; 31 | 32 | arg2.verify_password(pass.as_bytes(), &hash).is_ok() 33 | } 34 | -------------------------------------------------------------------------------- /src/service/test/s_test_data_scope.rs: -------------------------------------------------------------------------------- 1 | use crate::model::test::model::mtest_data_scope::*; 2 | use crate::service::prelude::*; 3 | pub async fn list( 4 | VQuery(arg): VQuery, 5 | VQuery(search): VQuery, 6 | userinfo: UserInfo, 7 | ) -> impl IntoResponse { 8 | let rlist = TestDataScopeModel::list(arg, search, userinfo).await; 9 | ApiResponse::from_result(rlist) 10 | } 11 | pub async fn edit(userinfo: UserInfo, VJson(arg): VJson) -> impl IntoResponse { 12 | let r = TestDataScopeModel::edit(arg, userinfo).await; 13 | ApiResponse::from_result(r) 14 | } 15 | pub async fn add(userinfo: UserInfo, VJson(arg): VJson) -> impl IntoResponse { 16 | let r = TestDataScopeModel::add(arg, userinfo).await; 17 | ApiResponse::from_result(r) 18 | } 19 | pub async fn delete( 20 | userinfo: UserInfo, 21 | VQuery(arg): VQuery, 22 | ) -> impl IntoResponse { 23 | let r = TestDataScopeModel::del(arg, userinfo).await; 24 | ApiResponse::from_result(r) 25 | } 26 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_oper_log.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_oper_log")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub oper_id: i64, 11 | pub api_name: Option, 12 | pub method: Option, 13 | pub request_method: Option, 14 | pub oper_name: Option, 15 | pub oper_url: Option, 16 | pub oper_ip: Option, 17 | pub oper_location: Option, 18 | pub oper_param: Option, 19 | pub json_result: Option, 20 | pub status: Option, 21 | pub error_msg: Option, 22 | pub oper_time: Option, 23 | pub cost_time: Option, 24 | } 25 | 26 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 27 | pub enum Relation {} 28 | 29 | impl ActiveModelBehavior for ActiveModel {} 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Open 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/model/test/args/atest_api.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | #[derive(Debug, Clone, Serialize, Deserialize, FromQueryResult, Validate)] 3 | pub struct TestApiResp { 4 | #[serde(with = "i64_to_string")] 5 | pub id: i64, 6 | pub name: String, 7 | pub age: i32, 8 | pub email: String, 9 | pub created_at: DateTime, 10 | pub updated_at: DateTime, 11 | } 12 | 13 | #[derive(Debug, Clone, Serialize, Deserialize, Validate)] 14 | pub struct TestApiAdd { 15 | pub name: String, 16 | pub age: i32, 17 | pub email: String 18 | } 19 | 20 | #[derive(Debug, Clone, Serialize, Deserialize, Validate)] 21 | pub struct TestApiEdit { 22 | #[serde(with = "i64_to_string")] 23 | pub id: i64, 24 | pub name: String, 25 | pub age: i32, 26 | pub email: String 27 | } 28 | 29 | #[derive(Debug, Clone, Serialize, Deserialize, Validate)] 30 | pub struct TestApiDel { 31 | #[serde(with = "i64_to_string")] 32 | pub id: i64, 33 | } 34 | 35 | #[derive(Debug, Clone, Serialize, Deserialize, Validate)] 36 | pub struct TestApiSearch { 37 | pub name: Option, 38 | pub email: Option, 39 | } -------------------------------------------------------------------------------- /src/model/sys/entity/prelude.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | pub use super::sys_api_permission::Entity as SysApiPermission; 4 | pub use super::sys_dept::Entity as SysDept; 5 | pub use super::sys_dict_data::Entity as SysDictData; 6 | pub use super::sys_dict_type::Entity as SysDictType; 7 | pub use super::sys_job::Entity as SysJob; 8 | pub use super::sys_job_log::Entity as SysJobLog; 9 | pub use super::sys_login_info::Entity as SysLoginInfo; 10 | pub use super::sys_menu::Entity as SysMenu; 11 | pub use super::sys_notice::Entity as SysNotice; 12 | pub use super::sys_oper_log::Entity as SysOperLog; 13 | pub use super::sys_post::Entity as SysPost; 14 | pub use super::sys_role::Entity as SysRole; 15 | pub use super::sys_role_api::Entity as SysRoleApi; 16 | pub use super::sys_role_dept::Entity as SysRoleDept; 17 | pub use super::sys_role_menu::Entity as SysRoleMenu; 18 | pub use super::sys_user::Entity as SysUser; 19 | pub use super::sys_user_dept::Entity as SysUserDept; 20 | pub use super::sys_user_post::Entity as SysUserPost; 21 | pub use super::sys_user_role::Entity as SysUserRole; 22 | pub use super::sys_white_jwt::Entity as SysWhiteJwt; 23 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_dict_data.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::service::prelude::*; 3 | use crate::model::sys::model::msys_dict_data::{ 4 | SysDictDataModel, 5 | DictDataAdd,DictDataSearch,DictDataEdit 6 | ,DictDataDel,DictDataType 7 | }; 8 | 9 | pub async fn list( 10 | VQuery(arg): VQuery, 11 | VQuery(search): VQuery, 12 | ) -> impl IntoResponse { 13 | let rlist = SysDictDataModel::list(arg, search).await; 14 | ApiResponse::from_result(rlist) 15 | } 16 | 17 | pub async fn add(VJson(arg): VJson) -> impl IntoResponse { 18 | let r = SysDictDataModel::add(arg).await; 19 | ApiResponse::from_result(r) 20 | } 21 | 22 | pub async fn edit(VJson(arg): VJson) -> impl IntoResponse { 23 | let r = SysDictDataModel::edit(arg).await; 24 | ApiResponse::from_result(r) 25 | } 26 | 27 | pub async fn delete(VQuery(arg):VQuery) -> impl IntoResponse { 28 | let r = SysDictDataModel::del(arg).await; 29 | ApiResponse::from_result(r) 30 | } 31 | 32 | pub async fn get_by_type(VQuery(arg): VQuery) -> impl IntoResponse { 33 | let r = SysDictDataModel::get_by_type(arg).await; 34 | ApiResponse::from_result(r) 35 | } -------------------------------------------------------------------------------- /src/model/sys/args/asys_api_permission.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Deserialize, Serialize, Clone, Validate)] 4 | pub struct ApiPermissionSearch { 5 | pub api: Option, 6 | pub method: Option, 7 | pub apiname: Option, 8 | } 9 | 10 | #[derive(Debug, Deserialize, Serialize, Clone, Validate)] 11 | pub struct ApiPermissionAdd { 12 | pub api: String, 13 | pub method: String, 14 | pub apiname: String, 15 | pub sort: i32, 16 | } 17 | 18 | #[derive(Debug, Deserialize, Serialize, Clone, Validate)] 19 | pub struct ApiPermissionEdit { 20 | #[serde(with = "i64_to_string")] 21 | pub id: i64, 22 | pub logcache: String, 23 | pub sort: i32, 24 | pub remark: Option, 25 | } 26 | 27 | #[derive(Debug, Deserialize, Serialize, Clone, Validate)] 28 | pub struct ApiPermissionDel { 29 | #[serde(with = "i64_to_string")] 30 | pub id: i64, 31 | } 32 | 33 | #[derive(Debug, Deserialize, Serialize, Clone, FromQueryResult)] 34 | pub struct ApiPermissionRes { 35 | #[serde(with = "i64_to_string")] 36 | pub id: i64, 37 | pub api: String, 38 | pub method: String, 39 | pub apiname: String, 40 | pub logcache: String, 41 | pub sort: i32, 42 | pub remark: Option, 43 | } 44 | -------------------------------------------------------------------------------- /src/model/sys/args/asys_white_jwt.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Deserialize, Serialize)] 4 | pub struct WhiteJwtAdd { 5 | pub uid: i64, 6 | pub token_id: i64, 7 | pub token_expr: i64, 8 | pub info_id: i64, 9 | } 10 | 11 | #[derive(Deserialize, Serialize, Validate)] 12 | pub struct WhiteJwtSearch { 13 | pub user_name: Option, 14 | } 15 | 16 | #[derive(Deserialize, Serialize, FromQueryResult, Clone)] 17 | pub struct WhiteJwtRes { 18 | #[serde(with = "i64_to_string")] 19 | pub jid: i64, 20 | #[serde(with = "i64_to_string")] 21 | pub token_id: i64, 22 | pub user_name: Option, 23 | pub device_type: Option, 24 | pub ipaddr: Option, 25 | pub login_location: Option, 26 | pub net_work: Option, 27 | pub browser: Option, 28 | pub os: Option, 29 | pub status: Option, 30 | pub msg: Option, 31 | pub login_time: Option, 32 | #[sea_orm(skip)] 33 | pub useronline:bool, 34 | } 35 | 36 | #[derive(Deserialize, Serialize)] 37 | pub struct WhiteJwtEdit { 38 | pub token_id: i64, 39 | pub token_expr: i64 40 | } 41 | 42 | #[derive(Deserialize, Serialize, Validate)] 43 | pub struct WhiteJwtDel { 44 | #[serde(with = "i64_to_string")] 45 | pub jid: i64, 46 | } 47 | -------------------------------------------------------------------------------- /src/model/sys/args/asys_dict_type.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Clone, Serialize, Deserialize,FromQueryResult,Validate)] 4 | pub struct DictTypeRes { 5 | #[serde(with = "i64_to_string")] 6 | pub dict_id: i64, 7 | pub dict_name: Option, 8 | pub dict_type: Option, 9 | pub order: i32, 10 | pub created_at: DateTime, 11 | pub remark: Option, 12 | } 13 | 14 | #[derive(Debug, Clone, Serialize, Deserialize,Validate)] 15 | pub struct DictDataSearch { 16 | pub dict_name: Option, 17 | pub dict_type: Option, 18 | } 19 | 20 | #[derive(Debug, Clone, Serialize, Deserialize,Validate)] 21 | 22 | pub struct DictTypeAdd { 23 | pub dict_name: String, 24 | pub dict_type: String, 25 | pub order: i32, 26 | pub created_at: Option, 27 | pub remark: Option, 28 | } 29 | 30 | #[derive(Debug, Clone, Serialize, Deserialize,Validate)] 31 | 32 | pub struct DictTypeEdit { 33 | #[serde(with = "i64_to_string")] 34 | pub dict_id: i64, 35 | pub dict_name: String, 36 | pub dict_type: String, 37 | pub order: i32, 38 | pub remark: Option, 39 | } 40 | 41 | #[derive(Debug, Clone, Serialize, Deserialize,Validate)] 42 | 43 | pub struct DictTypeDel { 44 | #[serde(with = "i64_to_string")] 45 | pub dict_id: i64, 46 | } -------------------------------------------------------------------------------- /src/model/sys/entity/sys_post.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_post")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub post_id: i64, 11 | pub dept_id: i64, 12 | pub post_code: String, 13 | pub post_category: Option, 14 | pub post_name: String, 15 | pub post_sort: i32, 16 | pub status: String, 17 | pub create_dept: Option, 18 | pub created_at: Option, 19 | pub updated_at: Option, 20 | pub remark: Option, 21 | } 22 | 23 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 24 | pub enum Relation { 25 | #[sea_orm(has_many = "super::sys_user_post::Entity")] 26 | SysUserPost, 27 | } 28 | 29 | impl Related for Entity { 30 | fn to() -> RelationDef { 31 | Relation::SysUserPost.def() 32 | } 33 | } 34 | 35 | impl Related for Entity { 36 | fn to() -> RelationDef { 37 | super::sys_user_post::Relation::SysUser.def() 38 | } 39 | fn via() -> Option { 40 | Some(super::sys_user_post::Relation::SysPost.def().rev()) 41 | } 42 | } 43 | 44 | impl ActiveModelBehavior for ActiveModel {} 45 | -------------------------------------------------------------------------------- /src/api.rs: -------------------------------------------------------------------------------- 1 | pub mod sys_controll; 2 | pub mod test; 3 | pub mod web_path; 4 | use crate::worker::invokefunction::{InvokeFunctionMsg, InvokeFunctionWorker}; 5 | use crate::worker::AppWorker; 6 | use axum::Router; 7 | use web_path::WebPath; 8 | pub struct WebApi; 9 | 10 | impl WebApi { 11 | pub fn routers() -> Router { 12 | let mut webpath = WebPath::new(); 13 | 14 | webpath = webpath.merge(sys_controll::router_sys()); 15 | webpath = webpath.merge(test::router_test()); 16 | 17 | webpath = webpath.final_to_path(); 18 | let expand_path = webpath.get_last_level_paths(); 19 | 20 | let invfun = InvokeFunctionMsg { 21 | job_id: None, 22 | callfun: "updateapi".to_owned(), 23 | parmets: serde_json::to_string(&expand_path).unwrap(), 24 | }; 25 | tokio::spawn(async move { 26 | let _ = InvokeFunctionWorker::execute_async(invfun).await; 27 | }); 28 | let mut router = Router::new(); 29 | for p in expand_path { 30 | if let Some(method_router) = p.method_router.clone() { 31 | router = router.route(&p.final_path, method_router); 32 | } 33 | } 34 | 35 | Router::new().merge(router) 36 | } 37 | pub fn white_routers() -> Router { 38 | Router::new() 39 | .merge(sys_controll::white_sys()) 40 | .merge(test::white_test()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_role_menu.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_role_menu")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub role_id: i64, 11 | #[sea_orm(primary_key, auto_increment = false)] 12 | pub menu_id: i64, 13 | } 14 | 15 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 16 | pub enum Relation { 17 | #[sea_orm( 18 | belongs_to = "super::sys_menu::Entity", 19 | from = "Column::MenuId", 20 | to = "super::sys_menu::Column::Id", 21 | on_update = "Restrict", 22 | on_delete = "Restrict" 23 | )] 24 | SysMenu, 25 | #[sea_orm( 26 | belongs_to = "super::sys_role::Entity", 27 | from = "Column::RoleId", 28 | to = "super::sys_role::Column::RoleId", 29 | on_update = "Restrict", 30 | on_delete = "Restrict" 31 | )] 32 | SysRole, 33 | } 34 | 35 | impl Related for Entity { 36 | fn to() -> RelationDef { 37 | Relation::SysMenu.def() 38 | } 39 | } 40 | 41 | impl Related for Entity { 42 | fn to() -> RelationDef { 43 | Relation::SysRole.def() 44 | } 45 | } 46 | 47 | impl ActiveModelBehavior for ActiveModel {} 48 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_user_dept.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_user_dept")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub user_id: i64, 11 | #[sea_orm(primary_key, auto_increment = false)] 12 | pub dept_id: i64, 13 | } 14 | 15 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 16 | pub enum Relation { 17 | #[sea_orm( 18 | belongs_to = "super::sys_dept::Entity", 19 | from = "Column::DeptId", 20 | to = "super::sys_dept::Column::DeptId", 21 | on_update = "Restrict", 22 | on_delete = "Restrict" 23 | )] 24 | SysDept, 25 | #[sea_orm( 26 | belongs_to = "super::sys_user::Entity", 27 | from = "Column::UserId", 28 | to = "super::sys_user::Column::Id", 29 | on_update = "Restrict", 30 | on_delete = "Restrict" 31 | )] 32 | SysUser, 33 | } 34 | 35 | impl Related for Entity { 36 | fn to() -> RelationDef { 37 | Relation::SysDept.def() 38 | } 39 | } 40 | 41 | impl Related for Entity { 42 | fn to() -> RelationDef { 43 | Relation::SysUser.def() 44 | } 45 | } 46 | 47 | impl ActiveModelBehavior for ActiveModel {} 48 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_user_post.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_user_post")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub user_id: i64, 11 | #[sea_orm(primary_key, auto_increment = false)] 12 | pub post_id: i64, 13 | } 14 | 15 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 16 | pub enum Relation { 17 | #[sea_orm( 18 | belongs_to = "super::sys_post::Entity", 19 | from = "Column::PostId", 20 | to = "super::sys_post::Column::PostId", 21 | on_update = "Restrict", 22 | on_delete = "Restrict" 23 | )] 24 | SysPost, 25 | #[sea_orm( 26 | belongs_to = "super::sys_user::Entity", 27 | from = "Column::UserId", 28 | to = "super::sys_user::Column::Id", 29 | on_update = "Restrict", 30 | on_delete = "Restrict" 31 | )] 32 | SysUser, 33 | } 34 | 35 | impl Related for Entity { 36 | fn to() -> RelationDef { 37 | Relation::SysPost.def() 38 | } 39 | } 40 | 41 | impl Related for Entity { 42 | fn to() -> RelationDef { 43 | Relation::SysUser.def() 44 | } 45 | } 46 | 47 | impl ActiveModelBehavior for ActiveModel {} 48 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_user_role.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_user_role")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub user_id: i64, 11 | #[sea_orm(primary_key, auto_increment = false)] 12 | pub role_id: i64, 13 | } 14 | 15 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 16 | pub enum Relation { 17 | #[sea_orm( 18 | belongs_to = "super::sys_role::Entity", 19 | from = "Column::RoleId", 20 | to = "super::sys_role::Column::RoleId", 21 | on_update = "Restrict", 22 | on_delete = "Restrict" 23 | )] 24 | SysRole, 25 | #[sea_orm( 26 | belongs_to = "super::sys_user::Entity", 27 | from = "Column::UserId", 28 | to = "super::sys_user::Column::Id", 29 | on_update = "Restrict", 30 | on_delete = "Restrict" 31 | )] 32 | SysUser, 33 | } 34 | 35 | impl Related for Entity { 36 | fn to() -> RelationDef { 37 | Relation::SysRole.def() 38 | } 39 | } 40 | 41 | impl Related for Entity { 42 | fn to() -> RelationDef { 43 | Relation::SysUser.def() 44 | } 45 | } 46 | 47 | impl ActiveModelBehavior for ActiveModel {} 48 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_role_dept.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_role_dept")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub role_id: i64, 11 | #[sea_orm(primary_key, auto_increment = false)] 12 | pub dept_id: i64, 13 | } 14 | 15 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 16 | pub enum Relation { 17 | #[sea_orm( 18 | belongs_to = "super::sys_dept::Entity", 19 | from = "Column::DeptId", 20 | to = "super::sys_dept::Column::DeptId", 21 | on_update = "Restrict", 22 | on_delete = "Restrict" 23 | )] 24 | SysDept, 25 | #[sea_orm( 26 | belongs_to = "super::sys_role::Entity", 27 | from = "Column::RoleId", 28 | to = "super::sys_role::Column::RoleId", 29 | on_update = "Restrict", 30 | on_delete = "Restrict" 31 | )] 32 | SysRole, 33 | } 34 | 35 | impl Related for Entity { 36 | fn to() -> RelationDef { 37 | Relation::SysDept.def() 38 | } 39 | } 40 | 41 | impl Related for Entity { 42 | fn to() -> RelationDef { 43 | Relation::SysRole.def() 44 | } 45 | } 46 | 47 | impl ActiveModelBehavior for ActiveModel {} 48 | -------------------------------------------------------------------------------- /src/model/sys/args/asys_oper_log.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Serialize, Clone, Debug, Default, Deserialize,FromQueryResult)] 4 | pub struct SysOperLogRes { 5 | #[serde(with = "i64_to_string")] 6 | pub oper_id: i64, 7 | pub api_name: Option, 8 | pub method: Option, 9 | pub request_method: Option, 10 | pub oper_name: Option, 11 | pub oper_url: Option, 12 | pub oper_ip: Option, 13 | pub oper_location: Option, 14 | pub oper_param: Option, 15 | pub json_result: Option, 16 | pub status: Option, 17 | pub error_msg: Option, 18 | pub oper_time: Option, 19 | pub cost_time: Option, 20 | } 21 | 22 | #[derive(Serialize, Clone, Debug, Default, Deserialize)] 23 | pub struct SysOperLogAdd { 24 | pub api_name: Option, 25 | pub method: Option, 26 | pub request_method: Option, 27 | pub oper_name: Option, 28 | pub oper_url: Option, 29 | pub oper_ip: Option, 30 | pub oper_location: Option, 31 | pub oper_param: Option, 32 | pub json_result: Option, 33 | pub status: Option, 34 | pub error_msg: Option, 35 | pub oper_time: Option, 36 | pub cost_time: Option, 37 | } 38 | 39 | #[derive(Serialize, Clone, Debug, Validate, Deserialize)] 40 | pub struct SysOperLogSearch { 41 | pub oper_name: Option, 42 | } -------------------------------------------------------------------------------- /src/model/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use axum::response::{Html, IntoResponse}; 2 | pub use chrono::{Duration, Local}; 3 | pub use sea_orm::{ 4 | entity::prelude::DateTime, prelude::Expr, ActiveModelBehavior, ActiveModelTrait, ActiveValue, 5 | ColumnTrait, Condition, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbBackend, 6 | DbErr, EntityTrait, FromQueryResult, IntoActiveModel, JoinType, ModelTrait, Order, 7 | PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, QueryTrait, Set, TransactionTrait, 8 | }; 9 | pub use serde::{Deserialize, Serialize}; 10 | pub use serde_json::json; 11 | pub use tracing::{error, info}; 12 | 13 | pub use crate::{ 14 | common::{ 15 | self, 16 | error::{Error, Result}, 17 | ser::*, 18 | util, 19 | validatedjson::VJson, 20 | validatedquery::VQuery, 21 | ApiResponse, 22 | }, 23 | config::APPCOFIG, 24 | db::{with_read, DB, DB_AUTO, DB_BY_INDEX, DB_BY_NAME, DB_READ, DB_WRITE, GID}, 25 | midle_ware::jwt::{self, AuthPayload, Claims, UserInfo}, 26 | }; 27 | pub use validator::Validate; 28 | 29 | #[derive(Debug, Serialize, Clone, Deserialize)] 30 | /// 查 数据返回 31 | pub struct ListData { 32 | pub list: Vec, 33 | pub total: u64, 34 | pub total_pages: u64, 35 | pub page_num: u64, 36 | } 37 | 38 | ///页面 39 | #[derive(Deserialize, Clone, Debug, Serialize, Default, Validate)] 40 | pub struct PageParams { 41 | pub page_num: Option, 42 | pub page_size: Option, 43 | } 44 | pub use crate::cache::CacheManager; 45 | pub use sea_orm::prelude::DateTimeUtc; 46 | -------------------------------------------------------------------------------- /src/common/validatedform.rs: -------------------------------------------------------------------------------- 1 | use crate::common::result::ApiResponse; 2 | use async_trait::async_trait; 3 | use axum::{ 4 | extract::{Form, FromRequest, Request}, 5 | response::{IntoResponse, Response}, 6 | }; 7 | use serde::de::DeserializeOwned; 8 | use thiserror::Error; 9 | use validator::Validate; 10 | 11 | #[derive(Debug, Clone, Copy, Default)] 12 | pub struct VForm(pub T); 13 | 14 | 15 | impl FromRequest for VForm 16 | where 17 | T: DeserializeOwned + Validate, 18 | S: Send + Sync, 19 | { 20 | type Rejection = ServerError; 21 | async fn from_request(req: Request, state: &S) -> Result { 22 | let Form(value) = Form::::from_request(req, state).await?; 23 | value.validate()?; 24 | Ok(VForm(value)) 25 | } 26 | } 27 | 28 | #[derive(Debug, Error)] 29 | pub enum ServerError { 30 | #[error(transparent)] 31 | ValidationError(#[from] validator::ValidationErrors), 32 | 33 | #[error(transparent)] 34 | AxumFormRejection(#[from] axum::extract::rejection::FormRejection), 35 | } 36 | 37 | impl IntoResponse for ServerError { 38 | fn into_response(self) -> Response { 39 | match self { 40 | ServerError::ValidationError(e) => { 41 | tracing::error!("{:?}", e); 42 | ApiResponse::bad_request(e.to_string()) 43 | } 44 | ServerError::AxumFormRejection(e) => { 45 | tracing::error!("{:?}", e); 46 | ApiResponse::bad_request(e.to_string()) 47 | } 48 | } 49 | .into_response() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_role.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::model::msys_role::{ 2 | RoleAddReq, RoleEditReq, RoleReq, RoleSearch, SysRoleModel, 3 | }; 4 | use crate::service::prelude::*; 5 | 6 | pub async fn list( 7 | VQuery(arg): VQuery, 8 | VQuery(search): VQuery, 9 | ) -> impl IntoResponse { 10 | let rlist = SysRoleModel::list(arg, search).await; 11 | ApiResponse::from_result(rlist) 12 | } 13 | pub async fn tree() -> impl IntoResponse { 14 | let rlist = SysRoleModel::tree().await; 15 | ApiResponse::from_result(rlist) 16 | } 17 | pub async fn menu() -> impl IntoResponse { 18 | let r = SysRoleModel::menu().await; 19 | ApiResponse::from_result(r) 20 | } 21 | 22 | pub async fn get_role_menus(VQuery(arg): VQuery) -> impl IntoResponse { 23 | let r = SysRoleModel::get_role_menus(arg.role_id).await; 24 | ApiResponse::from_result(r) 25 | } 26 | 27 | pub async fn edit(VJson(arg): VJson) -> impl IntoResponse { 28 | let r = SysRoleModel::edit(arg).await; 29 | ApiResponse::from_result(r) 30 | } 31 | 32 | pub async fn add(VJson(arg): VJson) -> impl IntoResponse { 33 | let r = SysRoleModel::add(arg).await; 34 | ApiResponse::from_result(r) 35 | } 36 | 37 | pub async fn delete(VQuery(arg): VQuery) -> impl IntoResponse { 38 | let r = SysRoleModel::delete(arg).await; 39 | ApiResponse::from_result(r) 40 | } 41 | 42 | pub async fn get_role_depts(VQuery(arg): VQuery) -> impl IntoResponse { 43 | let r = SysRoleModel::get_role_depts(arg.role_id).await; 44 | ApiResponse::from_result(r) 45 | } 46 | -------------------------------------------------------------------------------- /src/service/data_scope/types.rs: -------------------------------------------------------------------------------- 1 | /// 一条记录的归属信息(部门 + 人) 2 | #[derive(Debug, Clone, Copy)] 3 | pub struct RecordScope { 4 | pub dept_id: i64, 5 | pub owner_id: i64, 6 | } 7 | 8 | /// 写操作类型:创建 / 更新 / 删除 9 | pub enum RecordOp { 10 | /// 创建: 11 | /// - req_dept_id / req_owner_id 是前端“想要”的归属(可空) 12 | /// - 当前用户 + 角色 data_scope 决定最终可用的归属 13 | Create { 14 | req_dept_id: Option, 15 | req_owner_id: Option, 16 | }, 17 | 18 | /// 更新: 19 | /// - old: 旧记录的归属(从 DB 查出来) 20 | /// - new_dept_id / new_owner_id:前端想改成的归属(可空 = 不改) 21 | Update { 22 | old: RecordScope, 23 | new_dept_id: Option, 24 | new_owner_id: Option, 25 | }, 26 | 27 | /// 删除: 28 | /// - old: 旧记录的归属(从 DB 查出来) 29 | Delete { old: RecordScope }, 30 | } 31 | /// 角色的数据范围枚举 32 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 33 | pub enum DataScope { 34 | /// 全部数据 35 | All, 36 | /// 自定义部门 37 | Custom, 38 | /// 本部门 39 | Dept, 40 | /// 本部门及以下 41 | DeptAndSub, 42 | /// 仅本人 43 | SelfOnly, 44 | } 45 | 46 | impl DataScope { 47 | pub fn from_db(v: &str) -> Self { 48 | match v { 49 | "1" | "ALL" => DataScope::All, 50 | "2" | "CUSTOM" => DataScope::Custom, 51 | "3" | "DEPT" => DataScope::Dept, 52 | "4" | "DEPT_SUB" => DataScope::DeptAndSub, 53 | "5" | "SELF" => DataScope::SelfOnly, 54 | _ => { 55 | tracing::warn!("Unknown data_scope value: {}", v); 56 | DataScope::SelfOnly 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/worker/mailer/template.rs: -------------------------------------------------------------------------------- 1 | use crate::common::error::Result; 2 | use crate::common::tera; 3 | use fs_err as fs; 4 | use std::env; 5 | 6 | /// The filename for the subject template file. 7 | const SUBJECT: &str = "subject.t"; 8 | /// The filename for the HTML template file. 9 | const HTML: &str = "html.t"; 10 | /// The filename for the plain text template file. 11 | const TEXT: &str = "text.t"; 12 | 13 | fn embedded_file(dir: String, name: &str) -> Result { 14 | let path = env::current_dir().unwrap(); 15 | let files = path.join(dir).join(name); 16 | let content = fs::read_to_string(files).expect("msg"); 17 | Ok(content) 18 | } 19 | 20 | #[derive(Clone, Debug)] 21 | pub struct Content { 22 | pub subject: String, 23 | pub text: String, 24 | pub html: String, 25 | } 26 | 27 | #[derive(Debug, Clone)] 28 | pub struct Template { 29 | dir: String, 30 | } 31 | 32 | impl Template { 33 | pub const fn new(dir: String) -> Self { 34 | Self { dir } 35 | } 36 | 37 | pub fn render(&self, locals: &serde_json::Value) -> Result { 38 | let subject_t = embedded_file(self.dir.clone(), SUBJECT)?; 39 | let text_t = embedded_file(self.dir.clone(), TEXT)?; 40 | let html_t = embedded_file(self.dir.clone(), HTML)?; 41 | 42 | let text = tera::render_string(&text_t, locals); 43 | 44 | let text = text.unwrap(); 45 | let html = tera::render_string(&html_t, locals)?; 46 | let subject = tera::render_string(&subject_t, locals)?; 47 | 48 | Ok(Content { 49 | subject, 50 | text, 51 | html, 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_api_permission.rs: -------------------------------------------------------------------------------- 1 | use crate::api::web_path::WebPath; 2 | use crate::model::sys::model::msys_api_permission::{ 3 | ApiPermissionAdd, ApiPermissionEdit, ApiPermissionSearch, SysApiPermissionModel, 4 | }; 5 | use crate::service::prelude::*; 6 | pub async fn list( 7 | VQuery(arg): VQuery, 8 | VQuery(search): VQuery, 9 | ) -> impl IntoResponse { 10 | let rlist = SysApiPermissionModel::list(arg, search).await; 11 | ApiResponse::from_result(rlist) 12 | } 13 | 14 | pub async fn edit(VJson(arg): VJson) -> impl IntoResponse { 15 | let r = SysApiPermissionModel::edit(arg).await; 16 | ApiResponse::from_result(r) 17 | } 18 | 19 | pub async fn update_all_api(webstr: String) -> Result { 20 | info!("update_all_api begin"); 21 | let args: Vec = serde_json::from_str(&webstr).unwrap(); 22 | let db = DB().await.begin().await?; 23 | 24 | for (sort, webpath) in args.into_iter().enumerate() { 25 | let apipath = ApiPermissionAdd { 26 | api: webpath.final_path.clone(), 27 | method: webpath.webmethod.as_str().to_string(), 28 | apiname: webpath.apiname.unwrap_or("None".to_owned()), 29 | sort: (sort + 1) as i32, 30 | }; 31 | let apimodel = SysApiPermissionModel::add_or_update(apipath, &db).await?; 32 | let cache = CacheManager::instance().await; 33 | let _ = cache 34 | .set_value(&format!("api:{}", apimodel.api), &apimodel) 35 | .await; 36 | } 37 | info!("update_all_api ok"); 38 | db.commit().await?; 39 | Ok("update_all_api ok".to_string()) 40 | } 41 | -------------------------------------------------------------------------------- /src/model/sys/model/msys_role_dept.rs: -------------------------------------------------------------------------------- 1 | pub use super::entity::{ 2 | sys_dept::{self}, 3 | sys_role_dept::{self, ActiveModel, Model as SysRoleDeptModel}, 4 | }; 5 | use crate::model::prelude::*; 6 | 7 | impl SysRoleDeptModel { 8 | pub async fn update_depts(role_id: i64, dept_ids: Vec) -> Result { 9 | let db = DB().await; 10 | sys_role_dept::Entity::delete_many() 11 | .filter(sys_role_dept::Column::RoleId.eq(role_id)) 12 | .exec(db) 13 | .await?; 14 | let txn = db.begin().await?; 15 | for menu_id in dept_ids { 16 | let mut role_menu = ActiveModel::new(); 17 | role_menu.role_id = Set(role_id); 18 | role_menu.dept_id = Set(menu_id); 19 | sys_role_dept::Entity::insert(role_menu).exec(&txn).await?; 20 | } 21 | txn.commit().await?; 22 | Ok("".into()) 23 | } 24 | 25 | pub async fn listdepts(role_id: i64) -> Result> 26 | where 27 | T: FromQueryResult, 28 | { 29 | let db = DB().await; 30 | let mut depts = sys_role_dept::Entity::find(); 31 | depts = depts.columns([sys_role_dept::Column::DeptId, sys_role_dept::Column::RoleId]); 32 | depts = depts.join_rev( 33 | JoinType::LeftJoin, 34 | sys_dept::Entity::belongs_to(sys_role_dept::Entity) 35 | .from(sys_dept::Column::DeptId) 36 | .to(sys_role_dept::Column::DeptId) 37 | .into(), 38 | ); 39 | depts = depts.filter(sys_role_dept::Column::RoleId.eq(role_id)); 40 | depts.into_model::().all(db).await.map_err(Into::into) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_role_api.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::model::msys_role_api::{ 2 | RoleApiCheckInfo, RoleApiSearch, RoleApiTransferListIdReq, RoleApiTransferReq, SysRoleApiModel, 3 | }; 4 | use crate::service::prelude::*; 5 | 6 | pub async fn list( 7 | VQuery(arg): VQuery, 8 | VQuery(search): VQuery, 9 | ) -> impl IntoResponse { 10 | let rlist = SysRoleApiModel::list(arg, search).await; 11 | ApiResponse::from_result(rlist) 12 | } 13 | pub async fn edit() -> impl IntoResponse {} 14 | pub async fn add() -> impl IntoResponse {} 15 | 16 | pub async fn role_permission_list(uinfo: UserInfo) -> impl IntoResponse { 17 | let rlist = SysRoleApiModel::role_permission_list(uinfo.rid).await; 18 | ApiResponse::from_result(rlist) 19 | } 20 | 21 | pub async fn role_api_transfer_list(VQuery(arg): VQuery) -> impl IntoResponse { 22 | let rlist = SysRoleApiModel::role_api_transfer_list(arg).await; 23 | ApiResponse::from_result(rlist) 24 | } 25 | 26 | pub async fn add_many_role_api_transfer( 27 | VJson(arg): VJson, 28 | ) -> impl IntoResponse { 29 | let rlist = SysRoleApiModel::add_many_role_api_transfer(arg).await; 30 | ApiResponse::from_result(rlist) 31 | } 32 | pub async fn delete() -> impl IntoResponse {} 33 | 34 | pub async fn check_api_permission(rid: i64, api: &str, method: &str) -> bool { 35 | if APPCOFIG.system.super_role.contains(&rid) { 36 | return true; 37 | } 38 | let arg = RoleApiCheckInfo { 39 | role_id: rid, 40 | api: api.to_owned(), 41 | method: method.to_owned(), 42 | }; 43 | SysRoleApiModel::check_api(arg).await 44 | } 45 | -------------------------------------------------------------------------------- /src/service/test/s_test_api.rs: -------------------------------------------------------------------------------- 1 | use crate::model::test::model::mtest_api::{ 2 | TestApiAdd, TestApiDel, TestApiEdit, TestApiModel, TestApiSearch, 3 | }; 4 | use crate::service::prelude::*; 5 | pub async fn list( 6 | VQuery(arg): VQuery, 7 | VQuery(search): VQuery, 8 | ) -> impl IntoResponse { 9 | let rlist = TestApiModel::list(arg, search).await; 10 | ApiResponse::from_result(rlist) 11 | } 12 | pub async fn edit(VJson(arg): VJson) -> impl IntoResponse { 13 | let r = TestApiModel::edit(arg).await; 14 | ApiResponse::from_result(r) 15 | } 16 | pub async fn add(VJson(arg): VJson) -> impl IntoResponse { 17 | let r = TestApiModel::add(arg).await; 18 | ApiResponse::from_result(r) 19 | } 20 | pub async fn delete(VQuery(arg): VQuery) -> impl IntoResponse { 21 | let r = TestApiModel::del(arg).await; 22 | ApiResponse::from_result(r) 23 | } 24 | 25 | 26 | pub async fn db_index_test(VJson(arg): VJson) -> impl IntoResponse { 27 | let r = TestApiModel::db_index_test(arg).await; 28 | ApiResponse::from_result(r) 29 | } 30 | 31 | pub async fn db_name_test(VJson(arg): VJson) -> impl IntoResponse { 32 | let r = TestApiModel::db_name_test(arg).await; 33 | ApiResponse::from_result(r) 34 | } 35 | 36 | pub async fn db_read_write_test(VJson(arg): VJson) -> impl IntoResponse { 37 | let r = TestApiModel::db_read_write_test(arg).await; 38 | ApiResponse::from_result(r) 39 | } 40 | 41 | pub async fn db_auto_test(VJson(arg): VJson) -> impl IntoResponse { 42 | let r = TestApiModel::db_auto_test(arg).await; 43 | ApiResponse::from_result(r) 44 | } -------------------------------------------------------------------------------- /src/common/validatedquery.rs: -------------------------------------------------------------------------------- 1 | use crate::common::result::ApiResponse; 2 | use async_trait::async_trait; 3 | use axum::{ 4 | extract::{FromRequestParts, Query}, 5 | http::request::Parts, 6 | response::{IntoResponse, Response}, 7 | }; 8 | use serde::de::DeserializeOwned; 9 | use thiserror::Error; 10 | use validator::Validate; 11 | 12 | #[derive(Debug, Clone, Copy, Default)] 13 | pub struct VQuery(pub T); 14 | 15 | impl FromRequestParts for VQuery 16 | where 17 | T: DeserializeOwned + Validate, 18 | S: Send + Sync, 19 | { 20 | type Rejection = ServerError; 21 | async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { 22 | let Query(value) = Query::::from_request_parts(parts, _state).await?; 23 | value.validate()?; 24 | Ok(VQuery(value)) 25 | } 26 | } 27 | axum_core::__impl_deref!(VQuery); 28 | 29 | #[derive(Debug, Error)] 30 | pub enum ServerError { 31 | #[error(transparent)] 32 | ValidationError(#[from] validator::ValidationErrors), 33 | 34 | #[error(transparent)] 35 | AxumQueryRejection(#[from] axum::extract::rejection::QueryRejection), 36 | } 37 | 38 | impl IntoResponse for ServerError { 39 | fn into_response(self) -> Response { 40 | match self { 41 | ServerError::ValidationError(e) => { 42 | tracing::error!("{:?}", e); 43 | ApiResponse::bad_request(e.to_string()) 44 | } 45 | ServerError::AxumQueryRejection(e) => { 46 | tracing::error!("{:?}", e); 47 | ApiResponse::bad_request(e.to_string()) 48 | } 49 | } 50 | .into_response() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/worker/processor_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::config::APPCOFIG; 2 | use crate::worker::AppWorker; 3 | use tracing::trace; 4 | 5 | use super::common::Processor; 6 | use super::{InvokeFunctionWorker, JobWorker, LoginInfoWorker, MailerWorker, RequestUrlWorker}; 7 | 8 | pub const DEFAULT_QUEUES: &[&str] = &["default", "mailer", "logininfo"]; 9 | 10 | async fn init_process() -> Processor { 11 | let config = APPCOFIG.clone(); 12 | let queues = get_queues(&config.workers.queues); 13 | let num_workers = config.workers.num_workers; 14 | trace!( 15 | queues = ?queues, 16 | "registering queues (merged config and default)" 17 | ); 18 | Processor::new( 19 | DEFAULT_QUEUES 20 | .iter() 21 | .map(ToString::to_string) 22 | .collect::>(), 23 | num_workers, 24 | ) 25 | } 26 | 27 | pub fn get_queues(config_queues: &Option>) -> Vec { 28 | let mut queues = DEFAULT_QUEUES 29 | .iter() 30 | .map(ToString::to_string) 31 | .collect::>(); 32 | 33 | if let Some(config_queues) = config_queues { 34 | for q in config_queues { 35 | if !queues.iter().any(|aq| q == aq) { 36 | queues.push(q.to_string()); 37 | } 38 | } 39 | } 40 | 41 | queues 42 | } 43 | 44 | pub async fn processor_job() -> Processor { 45 | let mut p = init_process().await; 46 | p.register(MailerWorker::new()); 47 | p.register(LoginInfoWorker::new()); 48 | p.register(JobWorker::new()); 49 | p.register(InvokeFunctionWorker::new()); 50 | p.register(RequestUrlWorker::new()); 51 | 52 | trace!("done registering workers and queues"); 53 | p 54 | } 55 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_white_jwt.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::model::msys_white_jwt::{ 2 | SysWhiteJwtModel, WhiteJwtDel, WhiteJwtRes, WhiteJwtSearch, 3 | }; 4 | use crate::service::prelude::*; 5 | pub async fn list( 6 | VQuery(arg): VQuery, 7 | VQuery(search): VQuery, 8 | ) -> impl IntoResponse { 9 | let rlist = SysWhiteJwtModel::list(arg, search).await; 10 | ApiResponse::from_result(rlist) 11 | } 12 | pub async fn delete(VJson(arg): VJson) -> impl IntoResponse { 13 | let rid = SysWhiteJwtModel::del(arg).await; 14 | if let Ok(r) = rid.clone() { 15 | let cache = CacheManager::instance().await; 16 | let _ = cache.remove(&format!("user:{}", r)).await; 17 | } 18 | ApiResponse::from_result(rid) 19 | } 20 | pub async fn get_token(token_id: i64) -> Result { 21 | tracing::info!("get_token1"); 22 | let cache = CacheManager::instance().await; 23 | let white_jet = cache 24 | .get_value::(&format!("user:{}", token_id)) 25 | .await; 26 | tracing::info!("get_token2"); 27 | match white_jet { 28 | Ok(r) => Ok(r), 29 | Err(_) => { 30 | let rmodel = SysWhiteJwtModel::get_token(token_id).await; 31 | if let Ok(r) = rmodel { 32 | let mut r = r; 33 | r.useronline = true; 34 | let _ = cache 35 | .set_value_ex(&format!("user:{}", token_id), &r, 300) 36 | .await; 37 | return Ok(r); 38 | } 39 | Err("None".into()) 40 | } 41 | } 42 | } 43 | pub async fn clear_user_info() -> Result { 44 | SysWhiteJwtModel::clear_expire_user().await 45 | } 46 | -------------------------------------------------------------------------------- /src/model/test/args/atest_data_scope.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Clone, Serialize, Deserialize, FromQueryResult, Validate)] 4 | pub struct TestDataScopeResp { 5 | #[serde(with = "i64_to_string")] 6 | pub id: i64, 7 | pub title: String, 8 | pub content: Option, 9 | #[serde(with = "i64_to_string")] 10 | pub dept_id: i64, 11 | #[serde(with = "i64_to_string")] 12 | pub owner_id: i64, 13 | pub status: String, 14 | pub created_at: DateTime, 15 | pub updated_at: DateTime, 16 | pub deleted_at: Option, 17 | } 18 | 19 | #[derive(Debug, Clone, Serialize, Deserialize, Validate)] 20 | pub struct TestDataScopeAdd { 21 | pub title: String, 22 | pub content: Option, 23 | pub status: String, 24 | #[serde(with = "option_string_or_i64",default)] 25 | pub dept_id: Option, 26 | #[serde(with = "option_string_or_i64",default)] 27 | pub owner_id: Option, 28 | } 29 | 30 | #[derive(Debug, Clone, Serialize, Deserialize, Validate)] 31 | pub struct TestDataScopeEdit { 32 | #[serde(with = "i64_to_string")] 33 | pub id: i64, 34 | pub title: String, 35 | pub content: Option, 36 | pub status: String, 37 | #[serde(with = "option_string_or_i64",default)] 38 | pub dept_id: Option, 39 | #[serde(with = "option_string_or_i64",default)] 40 | pub owner_id: Option, 41 | } 42 | 43 | #[derive(Debug, Clone, Serialize, Deserialize, Validate)] 44 | pub struct TestDataScopeDel { 45 | #[serde(with = "i64_to_string")] 46 | pub id: i64, 47 | } 48 | 49 | #[derive(Debug, Clone, Serialize, Deserialize, Validate)] 50 | pub struct TestDataScopeSearch { 51 | pub title: Option, 52 | pub content: Option, 53 | } 54 | -------------------------------------------------------------------------------- /src/model/sys/model/msys_job_log.rs: -------------------------------------------------------------------------------- 1 | pub use super::args::asys_job_log::*; 2 | pub use super::entity::sys_job_log::{self, ActiveModel, Model as SysJobLogModel}; 3 | use crate::model::prelude::*; 4 | 5 | impl SysJobLogModel { 6 | pub async fn list(arg: PageParams, search: JobLogSearch) -> Result> { 7 | let page_num = arg.page_num.unwrap_or(1); 8 | let page_per_size = arg.page_size.unwrap_or(10); 9 | let db = DB().await; 10 | let mut rmodel = sys_job_log::Entity::find(); 11 | 12 | rmodel = rmodel.filter(sys_job_log::Column::JobId.eq(search.job_id)); 13 | 14 | let total = rmodel.clone().count(db).await?; 15 | let paginator = rmodel 16 | .order_by_desc(sys_job_log::Column::CreatedAt) 17 | .into_model::() 18 | .paginate(db, page_per_size); 19 | let total_pages = paginator.num_pages().await?; 20 | let list = paginator.fetch_page(page_num - 1).await?; 21 | let res = ListData { 22 | list, 23 | total, 24 | total_pages, 25 | page_num, 26 | }; 27 | Ok(res) 28 | } 29 | pub async fn add(arg: JobLogAdd) -> Result { 30 | let id = GID().await; 31 | let db = DB().await; 32 | let imodel = sys_job_log::ActiveModel { 33 | id: Set(id), 34 | job_id: Set(arg.job_id), 35 | job_message: Set(arg.job_message), 36 | status: Set(arg.status), 37 | run_count: Set(arg.run_count), 38 | elapsed_time: Set(arg.elapsed_time), 39 | ..Default::default() 40 | }; 41 | imodel.insert(db).await?; 42 | Ok("Success".to_string()) 43 | } 44 | pub async fn del() {} 45 | pub async fn edit() {} 46 | } 47 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_menu.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_menu")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub id: i64, 11 | pub name: Option, 12 | pub title: String, 13 | pub i18nkey: Option, 14 | pub pid: i64, 15 | pub order: i32, 16 | pub path: Option, 17 | pub component: Option, 18 | pub redirect: Option, 19 | pub href: Option, 20 | pub no_cache: String, 21 | pub menu_type: String, 22 | pub hidden: String, 23 | pub active_menu: String, 24 | pub always_show: String, 25 | pub breadcrumb: String, 26 | pub affix: String, 27 | pub no_tags_view: String, 28 | pub can_to: String, 29 | pub status: String, 30 | pub perms: Option, 31 | pub icon: Option, 32 | pub created_at: Option, 33 | pub updated_at: Option, 34 | pub remark: Option, 35 | pub deleted_at: Option, 36 | } 37 | 38 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 39 | pub enum Relation { 40 | #[sea_orm(has_many = "super::sys_role_menu::Entity")] 41 | SysRoleMenu, 42 | } 43 | 44 | impl Related for Entity { 45 | fn to() -> RelationDef { 46 | Relation::SysRoleMenu.def() 47 | } 48 | } 49 | 50 | impl Related for Entity { 51 | fn to() -> RelationDef { 52 | super::sys_role_menu::Relation::SysRole.def() 53 | } 54 | fn via() -> Option { 55 | Some(super::sys_role_menu::Relation::SysMenu.def().rev()) 56 | } 57 | } 58 | 59 | impl ActiveModelBehavior for ActiveModel {} 60 | -------------------------------------------------------------------------------- /src/common/validatedjson.rs: -------------------------------------------------------------------------------- 1 | use crate::common::result::ApiResponse; 2 | use async_trait::async_trait; 3 | use axum::{ 4 | extract::{FromRequest, Request}, 5 | response::{IntoResponse, Response}, 6 | Json, 7 | }; 8 | use serde::de::DeserializeOwned; 9 | use thiserror::Error; 10 | use validator::Validate; 11 | 12 | #[derive(Debug, Clone, Copy, Default)] 13 | pub struct VJson(pub T); 14 | 15 | impl FromRequest for VJson 16 | where 17 | T: DeserializeOwned + Validate, 18 | S: Send + Sync, 19 | { 20 | type Rejection = ServerError; 21 | async fn from_request(req: Request, state: &S) -> Result { 22 | let Json(value) = Json::::from_request(req, state).await?; 23 | value.validate()?; 24 | Ok(VJson(value)) 25 | } 26 | } 27 | 28 | #[derive(Debug, Error)] 29 | pub enum ServerError { 30 | #[error(transparent)] 31 | ValidationError(#[from] validator::ValidationErrors), 32 | 33 | #[error(transparent)] 34 | AxumJsonRejection(#[from] axum::extract::rejection::JsonRejection), 35 | 36 | #[error(transparent)] 37 | MissingJsonContentType(#[from] axum::extract::rejection::MissingJsonContentType), 38 | } 39 | 40 | impl IntoResponse for ServerError { 41 | fn into_response(self) -> Response { 42 | match self { 43 | ServerError::ValidationError(e) => { 44 | tracing::error!("{:?}", e); 45 | ApiResponse::bad_request(e.to_string()) 46 | } 47 | ServerError::AxumJsonRejection(e) => { 48 | tracing::error!("{:?}", e); 49 | ApiResponse::bad_request(e.to_string()) 50 | } 51 | ServerError::MissingJsonContentType(e) => { 52 | tracing::error!("{:?}", e); 53 | ApiResponse::bad_request(e.to_string()) 54 | } 55 | } 56 | .into_response() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/model/sys/args/asys_role_api.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Deserialize, Serialize, Clone,FromQueryResult)] 4 | pub struct RoleApiInfo { 5 | #[serde(with = "i64_to_string")] 6 | pub role_id: i64, 7 | pub api: String, 8 | pub method: String, 9 | pub apiname: String, 10 | } 11 | 12 | #[derive(Debug, Deserialize, Serialize, Clone)] 13 | pub struct RoleApiCheckInfo { 14 | #[serde(with = "i64_to_string")] 15 | pub role_id: i64, 16 | pub api: String, 17 | pub method: String 18 | } 19 | #[derive(Debug, Deserialize, Serialize, Clone, Validate)] 20 | pub struct RoleApiAdd { 21 | #[serde(with = "i64_to_string")] 22 | pub role_id: i64, 23 | pub api: String, 24 | pub method: String, 25 | pub apiname: String, 26 | } 27 | 28 | #[derive(Debug, Deserialize, Serialize, Validate)] 29 | pub struct RoleApiSearch { 30 | #[serde(with = "i64_to_string")] 31 | pub role_id: i64, 32 | pub api: Option, 33 | pub apiname: Option, 34 | } 35 | 36 | 37 | 38 | 39 | #[derive(Debug, Deserialize, Serialize, Validate)] 40 | pub struct RoleApiPermissions { 41 | pub permissions: Vec, 42 | } 43 | //返回权限 44 | 45 | #[derive(Debug, Deserialize, Serialize, Validate)] 46 | pub struct RoleApiTransferReq { 47 | #[serde(with = "i64_to_string")] 48 | pub role_id: i64 49 | } 50 | 51 | 52 | #[derive(Debug, Deserialize, Serialize, Clone,FromQueryResult)] 53 | pub struct RoleApiTransferInfo { 54 | #[serde(with = "i64_to_string")] 55 | pub role_id: i64, 56 | #[serde(with = "i64_to_string")] 57 | pub api_id: i64, 58 | pub api: String, 59 | pub method: String, 60 | } 61 | 62 | #[derive(Debug, Deserialize, Serialize, Validate)] 63 | pub struct RoleApiTransferListIdReq { 64 | #[serde(with = "i64_to_string")] 65 | pub role_id: i64, 66 | #[serde(with = "veci64_to_vecstring")] 67 | pub api_ids:Vec< i64> 68 | } -------------------------------------------------------------------------------- /src/model/sys/args/asys_job.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Clone, Serialize, Deserialize,FromQueryResult)] 4 | pub struct JobRes { 5 | #[serde(with = "i64_to_string")] 6 | pub job_id: i64, 7 | pub task_type: String, 8 | pub task_count: i32, 9 | pub run_count: i32, 10 | pub job_name: String, 11 | pub job_params: Option, 12 | pub job_group: String, 13 | pub cron_expression: String, 14 | pub status: String, 15 | pub remark: String 16 | } 17 | 18 | #[derive(Serialize, Clone, Validate, Default, Deserialize)] 19 | pub struct JobAdd { 20 | pub task_type: String, 21 | pub task_count: i32, 22 | pub run_count: i32, 23 | pub job_name: String, 24 | pub job_params: Option, 25 | pub job_group: String, 26 | pub cron_expression: String, 27 | pub status: String, 28 | pub remark: String 29 | } 30 | 31 | 32 | #[derive(Serialize, Clone, Validate, Default, Deserialize)] 33 | pub struct JobEdit { 34 | #[serde(with = "i64_to_string")] 35 | pub job_id: i64, 36 | pub task_type: String, 37 | pub task_count: i32, 38 | pub run_count: i32, 39 | pub job_name: String, 40 | pub job_params: Option, 41 | pub job_group: String, 42 | pub cron_expression: String, 43 | pub status: String, 44 | pub remark: String 45 | } 46 | 47 | #[derive(Serialize, Clone, Validate, Default, Deserialize)] 48 | pub struct JobDel { 49 | #[serde(with = "i64_to_string")] 50 | pub job_id: i64 51 | } 52 | #[derive(Serialize, Clone, Validate, Default, Deserialize)] 53 | pub struct JobExecute { 54 | #[serde(with = "i64_to_string")] 55 | pub job_id: i64 56 | } 57 | 58 | #[derive(Deserialize, Clone, Debug,Serialize,Validate)] 59 | pub struct ValidateCronReq { 60 | pub cron_expression: String, 61 | } 62 | 63 | #[derive(Serialize, Clone, Debug,Deserialize)] 64 | pub struct ValidateCronRes { 65 | pub validate: bool, 66 | pub next_ten: Option>, 67 | } -------------------------------------------------------------------------------- /src/model/sys/args/aserve_info.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Serialize, Default, Clone, Deserialize)] 4 | pub struct SysInfo { 5 | pub server: Server, 6 | pub cpu: Cpu, 7 | pub cpu_load: CpuLoad, 8 | pub memory: Memory, 9 | pub process: Process, 10 | pub network: Vec, 11 | } 12 | 13 | #[derive(Debug, Serialize, Default, Clone, Deserialize)] 14 | pub struct Cpu { 15 | pub name: String, 16 | pub arch: String, 17 | pub processors: usize, 18 | pub frequency: u64, 19 | pub cores: String, 20 | pub total_use: f32, 21 | } 22 | #[derive(Debug, Serialize, Default, Clone, Deserialize)] 23 | pub struct CpuLoad { 24 | pub one: f64, 25 | pub five: f64, 26 | pub fifteen: f64, 27 | } 28 | 29 | #[derive(Debug, Serialize, Default, Clone, Deserialize)] 30 | pub struct Memory { 31 | pub total_memory: u64, 32 | pub used_memory: u64, 33 | pub total_swap: u64, 34 | pub used_swap: u64, 35 | } 36 | 37 | #[derive(Debug, Serialize, Default, Clone, Deserialize)] 38 | pub struct Server { 39 | pub oper_sys_name: String, 40 | pub host_name: String, 41 | pub system_version: String, 42 | pub system_kerne: String, 43 | } 44 | #[derive(Debug, Serialize, Default, Clone, Deserialize)] 45 | pub struct Process { 46 | pub name: String, 47 | pub used_memory: u64, 48 | pub used_virtual_memory: u64, 49 | pub cup_usage: f32, 50 | pub start_time: u64, 51 | pub run_time: u64, 52 | pub disk_usage: DiskUsage, 53 | } 54 | #[derive(Debug, Serialize, Default, Clone, Deserialize)] 55 | pub struct DiskUsage { 56 | pub read_bytes: u64, 57 | pub total_read_bytes: u64, 58 | pub written_bytes: u64, 59 | pub total_written_bytes: u64, 60 | } 61 | 62 | #[derive(Debug, Serialize, Default, Clone, Deserialize)] 63 | pub struct Network { 64 | pub name: String, 65 | pub received: u64, 66 | pub total_received: u64, 67 | pub transmitted: u64, 68 | pub total_transmitted: u64, 69 | } 70 | -------------------------------------------------------------------------------- /src/model/sys/model/msys_role_menu.rs: -------------------------------------------------------------------------------- 1 | pub use super::entity::{ 2 | sys_menu::{self}, 3 | sys_role_menu::{self, ActiveModel, Model as SysRoleMenuModel}, 4 | }; 5 | use crate::model::prelude::*; 6 | impl SysRoleMenuModel { 7 | pub async fn listmenus(role_id: i64) -> Result> 8 | where 9 | T: FromQueryResult, 10 | { 11 | let db = DB().await; 12 | let mut menus = sys_role_menu::Entity::find(); 13 | menus = menus.columns([sys_menu::Column::Id, sys_menu::Column::Title]); 14 | menus = menus.join_rev( 15 | JoinType::LeftJoin, 16 | sys_menu::Entity::belongs_to(sys_role_menu::Entity) 17 | .from(sys_menu::Column::Id) 18 | .to(sys_role_menu::Column::MenuId) 19 | .into(), 20 | ); 21 | menus = menus.filter(sys_role_menu::Column::RoleId.eq(role_id)); 22 | menus.into_model::().all(db).await.map_err(Into::into) 23 | } 24 | 25 | pub async fn update_menus(role_id: i64, menu_ids: Vec) -> Result { 26 | let db = DB().await; 27 | sys_role_menu::Entity::delete_many() 28 | .filter(sys_role_menu::Column::RoleId.eq(role_id)) 29 | .exec(db) 30 | .await?; 31 | for menu_id in menu_ids { 32 | let mut role_menu = ActiveModel::new(); 33 | role_menu.role_id = Set(role_id); 34 | role_menu.menu_id = Set(menu_id); 35 | role_menu.insert(db).await?; 36 | } 37 | Ok("Success".to_string()) 38 | } 39 | pub async fn delete(role_id: i64) -> Result { 40 | let db = DB().await; 41 | let dmodel = sys_role_menu::Entity::delete_many() 42 | .filter(sys_role_menu::Column::RoleId.eq(role_id)) 43 | .exec(db) 44 | .await?; 45 | if dmodel.rows_affected > 0 { 46 | Ok("Success".to_string()) 47 | } else { 48 | Err("delete failed".into()) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/model/sys/args/asys_dict_data.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Clone, Serialize, Deserialize,FromQueryResult,Validate)] 4 | pub struct DictDataRes { 5 | #[serde(with = "i64_to_string")] 6 | pub dict_code: i64, 7 | pub dict_sort: i32, 8 | pub dict_label: String, 9 | pub dict_value: String, 10 | #[serde(with = "i64_to_string")] 11 | pub dict_type_id: i64, 12 | pub created_at: Option, 13 | pub remark: Option, 14 | } 15 | 16 | #[derive(Debug, Clone, Serialize, Deserialize,Validate)] 17 | pub struct DictDataSearch { 18 | #[serde(with = "i64_to_string")] 19 | pub dict_type_id: i64, 20 | pub dict_label: Option, 21 | pub dict_value: Option 22 | } 23 | #[derive(Debug, Clone, Serialize, Deserialize,Validate)] 24 | pub struct DictDataAdd { 25 | pub dict_sort: i32, 26 | pub dict_label: String, 27 | pub dict_value: String, 28 | #[serde(with = "i64_to_string")] 29 | pub dict_type_id: i64, 30 | pub remark: Option, 31 | } 32 | 33 | #[derive(Debug, Clone, Serialize, Deserialize,Validate)] 34 | pub struct DictDataEdit { 35 | #[serde(with = "i64_to_string")] 36 | pub dict_code: i64, 37 | pub dict_sort: i32, 38 | pub dict_label: String, 39 | pub dict_value: String, 40 | #[serde(with = "i64_to_string")] 41 | pub dict_type_id: i64, 42 | pub remark: Option, 43 | } 44 | 45 | #[derive(Debug, Clone, Serialize, Deserialize,Validate)] 46 | 47 | pub struct DictDataDel { 48 | #[serde(with = "i64_to_string")] 49 | pub dict_code: i64, 50 | } 51 | 52 | #[derive(Debug, Clone, Serialize, Deserialize,Validate)] 53 | 54 | pub struct DictDataType { 55 | // #[serde(with = "i64_to_string")] 56 | // pub dict_type_id: i64, 57 | pub dict_type: String, 58 | } 59 | 60 | 61 | #[derive(Debug, Clone, Serialize, Deserialize,FromQueryResult,Validate)] 62 | pub struct DictKeyValueRes { 63 | pub dict_label: String, 64 | pub dict_value: String, 65 | } -------------------------------------------------------------------------------- /src/model/sys/args/asys_login_info.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | 4 | #[derive(Debug, Deserialize, Serialize, Clone, Validate)] 5 | pub struct LoginInfoSearch { 6 | pub user_name: Option 7 | } 8 | 9 | #[derive(Deserialize, Serialize, Clone,FromQueryResult)] 10 | pub struct LoginInfoRes { 11 | pub user_name: String, 12 | pub device_type: Option, 13 | pub ipaddr: Option, 14 | pub login_location: Option, 15 | pub browser: Option, 16 | pub os: Option, 17 | pub status: Option, 18 | pub msg: Option, 19 | pub login_time: Option, 20 | } 21 | 22 | #[derive(Deserialize, Serialize, Clone,Default)] 23 | pub struct LoginInfoMsg { 24 | pub info_id:i64, 25 | pub ipaddr: String, 26 | } 27 | #[derive(Deserialize, Serialize, Clone,Default)] 28 | pub struct LoginInfoAdd { 29 | pub user_name: String, 30 | pub uid:i64, 31 | pub device_type: Option, 32 | pub ipaddr: Option, 33 | pub login_location: Option, 34 | pub browser: Option, 35 | pub os: Option, 36 | pub status: Option, 37 | pub msg: Option, 38 | pub login_time: Option, 39 | pub net_work: Option, 40 | } 41 | 42 | 43 | #[derive(Deserialize, Serialize, Clone,Default)] 44 | pub struct LoginInfoEdit { 45 | pub info_id:i64, 46 | pub device_type: Option, 47 | pub ipaddr: Option, 48 | pub login_location: Option, 49 | pub browser: Option, 50 | pub os: Option, 51 | pub status: Option, 52 | pub msg: Option, 53 | pub login_time: Option, 54 | pub net_work: Option, 55 | } 56 | 57 | #[derive(Deserialize, Clone, Debug, Serialize)] 58 | pub struct ClientNetInfo { 59 | pub ip: String, 60 | pub location: String, 61 | pub net_work: String, 62 | } 63 | 64 | #[derive(Deserialize, Clone, Debug, Serialize)] 65 | pub struct UserAgentInfo { 66 | pub browser: String, 67 | pub os: String, 68 | pub device: String, 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_dept.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_dept")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub dept_id: i64, 11 | pub parent_id: i64, 12 | pub dept_name: Option, 13 | pub lft: i32, 14 | pub rgt: i32, 15 | pub depth: i32, 16 | pub dept_category: Option, 17 | pub order: i32, 18 | pub leader: Option, 19 | pub phone: Option, 20 | pub email: Option, 21 | pub status: String, 22 | pub remark: Option, 23 | pub created_at: Option, 24 | pub updated_at: Option, 25 | pub deleted_at: Option, 26 | } 27 | 28 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 29 | pub enum Relation { 30 | #[sea_orm(has_many = "super::sys_role_dept::Entity")] 31 | SysRoleDept, 32 | #[sea_orm(has_many = "super::sys_user_dept::Entity")] 33 | SysUserDept, 34 | } 35 | 36 | impl Related for Entity { 37 | fn to() -> RelationDef { 38 | Relation::SysRoleDept.def() 39 | } 40 | } 41 | 42 | impl Related for Entity { 43 | fn to() -> RelationDef { 44 | Relation::SysUserDept.def() 45 | } 46 | } 47 | 48 | impl Related for Entity { 49 | fn to() -> RelationDef { 50 | super::sys_role_dept::Relation::SysRole.def() 51 | } 52 | fn via() -> Option { 53 | Some(super::sys_role_dept::Relation::SysDept.def().rev()) 54 | } 55 | } 56 | 57 | impl Related for Entity { 58 | fn to() -> RelationDef { 59 | super::sys_user_dept::Relation::SysUser.def() 60 | } 61 | fn via() -> Option { 62 | Some(super::sys_user_dept::Relation::SysDept.def().rev()) 63 | } 64 | } 65 | 66 | impl ActiveModelBehavior for ActiveModel {} 67 | -------------------------------------------------------------------------------- /src/worker/common/scheduled.rs: -------------------------------------------------------------------------------- 1 | use super::Result; 2 | use super::{periodic::PeriodicJob, UnitOfWork}; 3 | use crate::cache::CacheManager; 4 | use tracing::info; 5 | #[derive(Default)] 6 | pub struct Scheduled {} 7 | 8 | impl Scheduled { 9 | pub async fn enqueue_jobs( 10 | &self, 11 | now: chrono::DateTime, 12 | sorted_sets: &Vec, 13 | ) -> Result { 14 | let mut n = 0; 15 | let cache = CacheManager::instance().await; 16 | for sorted_set in sorted_sets { 17 | let jobs: Vec = cache 18 | .zrangebyscore_limit( 19 | sorted_set, 20 | f64::NEG_INFINITY, 21 | now.timestamp() as f64, 22 | 0, 23 | 100, 24 | ) 25 | .await?; 26 | 27 | n += jobs.len(); 28 | 29 | for job in jobs { 30 | if cache.zrem(sorted_set, job.clone()).await? { 31 | let work = UnitOfWork::from_job_string(job)?; 32 | 33 | work.enqueue_direct().await?; 34 | } 35 | } 36 | } 37 | 38 | Ok(n) 39 | } 40 | 41 | ///定时作业 42 | pub async fn enqueue_periodic_jobs(&self, now: chrono::DateTime) -> Result { 43 | let cache = CacheManager::instance().await; 44 | let periodic_jobs: Vec = cache 45 | .zrangebyscore_limit( 46 | "periodic", 47 | f64::NEG_INFINITY, 48 | now.timestamp() as f64, 49 | 0, 50 | 100, 51 | ) 52 | .await?; 53 | for periodic_job in &periodic_jobs { 54 | let pj = PeriodicJob::from_json_string(periodic_job)?; 55 | if pj.update(periodic_job).await? > 0 { 56 | let job = pj.into_job().await; 57 | let work = UnitOfWork::from_job(job); 58 | 59 | work.enqueue_direct().await?; 60 | } else { 61 | info!("update periodic job failed"); 62 | } 63 | } 64 | 65 | Ok(periodic_jobs.len()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/model/sys/model/msys_oper_log.rs: -------------------------------------------------------------------------------- 1 | pub use super::args::asys_oper_log::*; 2 | pub use super::entity::sys_oper_log::{self, ActiveModel, Model as SysOperLogModel}; 3 | use crate::model::prelude::*; 4 | 5 | impl SysOperLogModel { 6 | pub async fn list( 7 | arg: PageParams, 8 | search: SysOperLogSearch, 9 | ) -> Result> { 10 | let page_num = arg.page_num.unwrap_or(1); 11 | let page_per_size = arg.page_size.unwrap_or(10); 12 | let db = DB().await; 13 | let mut rmodel = sys_oper_log::Entity::find(); 14 | 15 | if let Some(oper_name) = search.oper_name { 16 | rmodel = rmodel.filter(sys_oper_log::Column::OperName.contains(oper_name)); 17 | } 18 | 19 | let total = rmodel.clone().count(db).await?; 20 | let paginator = rmodel 21 | .order_by_desc(sys_oper_log::Column::OperTime) 22 | .into_model::() 23 | .paginate(db, page_per_size); 24 | let total_pages = paginator.num_pages().await?; 25 | let list = paginator.fetch_page(page_num - 1).await?; 26 | let res = ListData { 27 | list, 28 | total, 29 | total_pages, 30 | page_num, 31 | }; 32 | Ok(res) 33 | } 34 | pub async fn add(arg: SysOperLogAdd) -> Result { 35 | let db = DB().await; 36 | let id = GID().await; 37 | let amodel = sys_oper_log::ActiveModel { 38 | oper_id: Set(id), 39 | api_name: Set(arg.api_name), 40 | method: Set(arg.method), 41 | oper_name: Set(arg.oper_name), 42 | oper_url: Set(arg.oper_url), 43 | oper_ip: Set(arg.oper_ip), 44 | oper_location: Set(arg.oper_location), 45 | oper_param: Set(arg.oper_param), 46 | json_result: Set(arg.json_result), 47 | status: Set(arg.status), 48 | error_msg: Set(arg.error_msg), 49 | oper_time: Set(arg.oper_time), 50 | request_method: Set(arg.request_method), 51 | cost_time: Set(arg.cost_time), 52 | }; 53 | amodel.insert(db).await?; 54 | Ok("Success".to_string()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/model/sys/args/asys_role.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Deserialize, Serialize, Clone, Validate)] 4 | pub struct RoleSearch { 5 | pub role_name: Option, 6 | pub role_key: Option, 7 | } 8 | 9 | #[derive(Deserialize, Validate, Serialize)] 10 | pub struct RoleAddReq { 11 | pub role_name: String, 12 | pub role_key: String, 13 | pub order: i32, 14 | pub status: String, 15 | pub data_scope: String, 16 | } 17 | 18 | //菜单 19 | #[derive(Deserialize, Validate, Serialize,Debug)] 20 | pub struct RoleEditReq { 21 | #[serde(with = "i64_to_string")] 22 | pub role_id: i64, 23 | pub role_name: String, 24 | pub role_key: String, 25 | pub order: i32, 26 | pub data_scope: String, 27 | pub status: String, 28 | pub remark: Option, 29 | #[serde(with = "veci64_to_vecstring")] 30 | pub menu: Vec, 31 | 32 | #[serde(with = "option_veci64_to_vecstring")] 33 | pub data_depts: Option>, 34 | } 35 | 36 | #[derive(Deserialize, Validate, Serialize)] 37 | pub struct RoleReq { 38 | #[serde(with = "i64_to_string")] 39 | pub role_id: i64, 40 | } 41 | 42 | #[derive(Debug, Deserialize, Serialize, FromQueryResult, Clone, Default)] 43 | pub struct RoleMenuResp { 44 | #[serde(with = "i64_to_string")] 45 | pub role_id: i64, 46 | pub role_name: String, 47 | pub role_key: String, 48 | pub order: i32, 49 | pub data_scope: String, 50 | pub status: String, 51 | #[sea_orm(skip)] 52 | pub mens: Vec, 53 | } 54 | #[derive(Debug, Deserialize, Serialize, FromQueryResult, Clone, Default)] 55 | pub struct RoleMResp { 56 | #[serde(with = "i64_to_string")] 57 | pub id: i64, 58 | pub title: String, 59 | } 60 | #[derive(Debug, Deserialize, Serialize, FromQueryResult, Clone, Default)] 61 | pub struct RoleResp { 62 | #[serde(with = "i64_to_string")] 63 | pub role_id: i64, 64 | pub role_name: String, 65 | pub role_key: String, 66 | pub order: i32, 67 | pub data_scope: String, 68 | pub status: String, 69 | pub remark: Option, 70 | } 71 | 72 | #[derive(Debug, Deserialize, Serialize, FromQueryResult, Clone, Default)] 73 | pub struct RoleDeptResp { 74 | #[serde(with = "i64_to_string")] 75 | pub dept_id: i64 76 | } -------------------------------------------------------------------------------- /src/model/sys/model/msys_user_dept.rs: -------------------------------------------------------------------------------- 1 | pub use super::args::asys_user_dept::*; 2 | pub use super::entity::{ 3 | sys_dept::{self, Model as SysDeptModel}, 4 | sys_user_dept::{self, ActiveModel, Model as SysUserDeptModel}, 5 | }; 6 | use crate::model::prelude::*; 7 | 8 | impl SysUserDeptModel { 9 | pub async fn add_user_depts(uid: i64, dept_ids: Vec) -> Result { 10 | let db = DB().await; 11 | sys_user_dept::Entity::delete_many() 12 | .filter(sys_user_dept::Column::UserId.eq(uid)) 13 | .exec(db) 14 | .await?; 15 | 16 | let mut imoses = Vec::new(); 17 | for dept_id in dept_ids { 18 | imoses.push(sys_user_dept::ActiveModel { 19 | user_id: Set(uid), 20 | dept_id: Set(dept_id), 21 | }); 22 | } 23 | sys_user_dept::Entity::insert_many(imoses).exec(db).await?; 24 | Ok("success".to_string()) 25 | } 26 | 27 | pub async fn user_dept_list(uid: i64) -> Result> { 28 | let db = DB().await; 29 | let user_depts = sys_user_dept::Entity::find() 30 | .filter(sys_user_dept::Column::UserId.eq(uid)) 31 | .all(db) 32 | .await?; 33 | 34 | let ids = user_depts 35 | .into_iter() 36 | .map(|user_dept| user_dept.dept_id) 37 | .collect(); 38 | Ok(ids) 39 | } 40 | 41 | pub async fn user_dept_name_list(uid: i64, did: i64) -> Result { 42 | let db = DB().await; 43 | let mut rmodel = sys_user_dept::Entity::find(); 44 | rmodel = rmodel.filter(sys_user_dept::Column::UserId.eq(uid)); 45 | rmodel = rmodel.join_rev( 46 | JoinType::LeftJoin, 47 | sys_dept::Entity::belongs_to(sys_user_dept::Entity) 48 | .from(sys_dept::Column::DeptId) 49 | .to(sys_user_dept::Column::DeptId) 50 | .into(), 51 | ); 52 | rmodel = rmodel.columns([sys_dept::Column::DeptName, sys_dept::Column::DeptId]); 53 | let depts = rmodel.into_model::().all(db).await?; 54 | let userdepts = UserDeptAndUserResp { 55 | user_dept_id: did, 56 | depts, 57 | }; 58 | Ok(userdepts) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/worker/common/worker_opts.rs: -------------------------------------------------------------------------------- 1 | use crate::common::error::Result; 2 | use serde::Serialize; 3 | use std::marker::PhantomData; 4 | 5 | use super::{enqueue_opts::EnqueueOpts, Worker}; 6 | 7 | pub struct WorkerOpts + ?Sized> { 8 | queue: String, 9 | retry: bool, 10 | args: PhantomData, 11 | worker: PhantomData, 12 | unique_for: Option, 13 | } 14 | 15 | impl WorkerOpts 16 | where 17 | W: Worker, 18 | { 19 | #[must_use] 20 | pub fn new() -> Self { 21 | Self { 22 | queue: "default".into(), 23 | retry: true, 24 | args: PhantomData, 25 | worker: PhantomData, 26 | unique_for: None, 27 | } 28 | } 29 | 30 | #[must_use] 31 | pub fn retry(self, retry: bool) -> Self { 32 | Self { retry, ..self } 33 | } 34 | 35 | #[must_use] 36 | pub fn queue>(self, queue: S) -> Self { 37 | Self { 38 | queue: queue.into(), 39 | ..self 40 | } 41 | } 42 | 43 | #[must_use] 44 | pub fn unique_for(self, unique_for: std::time::Duration) -> Self { 45 | Self { 46 | unique_for: Some(unique_for), 47 | ..self 48 | } 49 | } 50 | 51 | #[allow(clippy::wrong_self_convention)] 52 | fn into_opts(&self) -> EnqueueOpts { 53 | self.into() 54 | } 55 | 56 | pub async fn perform_async(&self, args: impl Serialize + Send + 'static) -> Result<()> { 57 | self.into_opts().perform_async(W::class_name(), args).await 58 | } 59 | 60 | pub async fn perform_in( 61 | &self, 62 | duration: std::time::Duration, 63 | args: impl Serialize + Send + 'static, 64 | ) -> Result<()> { 65 | self.into_opts() 66 | .perform_in(W::class_name(), duration, args) 67 | .await 68 | } 69 | } 70 | 71 | impl> From<&WorkerOpts> for EnqueueOpts { 72 | fn from(opts: &WorkerOpts) -> Self { 73 | Self { 74 | retry: opts.retry, 75 | queue: opts.queue.clone(), 76 | unique_for: opts.unique_for, 77 | } 78 | } 79 | } 80 | 81 | impl> Default for WorkerOpts { 82 | fn default() -> Self { 83 | Self::new() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_captcha.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::args::acaptch::*; 2 | use crate::service::prelude::*; 3 | use captcha_rust::Captcha; 4 | use image::{DynamicImage, ImageBuffer}; 5 | pub async fn get_captcha(VQuery(arg): VQuery) -> impl IntoResponse { 6 | let res: CaptchaImage = gen_captcha(arg).await; 7 | ApiResponse::ok(res) 8 | } 9 | 10 | async fn gen_captcha(arg: ClientInfo) -> CaptchaImage { 11 | let captcha = Captcha::new(5, 130, 40); 12 | 13 | let cacheinfo=CaptchaCacheInfo { 14 | client_id: arg.client_id, 15 | cache_text: captcha.text.clone(), 16 | }; 17 | let uuid = GID().await; 18 | let cache = CacheManager::instance().await; 19 | let _ = cache 20 | .set_value_ex(&format!("capcha:{}", uuid), &cacheinfo, 300) 21 | .await; 22 | info!("获取验证码:{}", captcha.text); 23 | CaptchaImage { 24 | captcha_on_off: true, 25 | uuid, 26 | img: captcha.base_img, 27 | } 28 | } 29 | 30 | pub async fn gen_wx_captcha() -> String { 31 | let captcha = Captcha::new(5, 130, 40); 32 | let captchtxt = captcha.text; 33 | let wxcaptcha = format!("wx_{}", captchtxt); 34 | let cache = CacheManager::instance().await; 35 | let _ = cache 36 | .set_string_ex(&wxcaptcha, captchtxt.as_str(), 300) 37 | .await; 38 | captchtxt 39 | } 40 | 41 | //滑动验证码生成 42 | async fn solid_captcha() { 43 | let width = 520; 44 | let height = 320; 45 | let mut img = image::open("original_captcha.png").unwrap().into_rgba8(); 46 | 47 | let mut mask_img = ImageBuffer::new(50, 50); 48 | let mask_image = image::open("mask.png").unwrap().into_luma8(); 49 | // 确定缺口的位置和大小 50 | let gap_x = 300; // rng.gen_range(50..450); 51 | let gap_y = 200; //rng.gen_range(50..250); 52 | let gap_width = 50; 53 | let gap_height = 50; 54 | // 绘制缺口(将缺口区域的像素设置为白色) 55 | for x in gap_x..(gap_x + gap_width) { 56 | for y in gap_y..(gap_y + gap_height) { 57 | if mask_image.get_pixel(x - gap_x, y - gap_y)[0] == 255 { 58 | let mut pixel = *img.get_pixel(x, y); 59 | mask_img.put_pixel(x - gap_x, y - gap_y, pixel); 60 | pixel[3] = 150; 61 | img.put_pixel(x, y, pixel); 62 | } 63 | } 64 | } 65 | let mask = DynamicImage::ImageRgba8(mask_img).as_bytes(); 66 | 67 | DynamicImage::ImageRgba8(img).as_bytes(); 68 | } 69 | -------------------------------------------------------------------------------- /src/common/snowflakeid.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | hint::spin_loop, 3 | sync::atomic::{AtomicI64, Ordering}, 4 | time::SystemTime, 5 | }; 6 | 7 | // const DIFFERENCE: u64 = CUSTOM_EPOCH - TARGET_EPOCH; 8 | pub struct SnowflakeIdGenerator { 9 | pub machine_id: i32, 10 | pub node_id: i32, 11 | sequence: AtomicI64, 12 | last_timestamp: AtomicI64, 13 | unix_epoch: SystemTime, 14 | } 15 | 16 | impl SnowflakeIdGenerator { 17 | pub fn new(machine_id: i32, node_id: i32) -> Self { 18 | let sequence = AtomicI64::new(0); 19 | let last_timestamp = AtomicI64::new(0); 20 | 21 | let unix_epoch = std::time::UNIX_EPOCH; 22 | 23 | SnowflakeIdGenerator { 24 | machine_id, 25 | sequence, 26 | node_id, 27 | last_timestamp, 28 | unix_epoch, 29 | } 30 | } 31 | 32 | pub fn real_time_generate(&self) -> i64 { 33 | let current_timestamp = self.get_current_timestamp(); 34 | // 如果时间戳小于上一次生成的时间戳,则说明时钟回退,这里可以选择等待或者报错处理 35 | if current_timestamp < self.last_timestamp.load(Ordering::Relaxed) { 36 | panic!("时钟回退错误"); 37 | } 38 | 39 | if current_timestamp == self.last_timestamp.load(Ordering::Relaxed) { 40 | // 同一时间戳下,递增序列号 41 | let sequence = self.sequence.fetch_add(1, Ordering::SeqCst); 42 | if sequence >= 4096 { 43 | // 序列号超出范围,等待下一毫秒 44 | loop { 45 | if self.get_current_timestamp() > current_timestamp { 46 | break; 47 | } 48 | spin_loop(); 49 | } 50 | return self.real_time_generate(); 51 | } 52 | } else { 53 | // 新的时间戳,重置序列号 54 | self.sequence.store(0, Ordering::SeqCst); 55 | } 56 | 57 | self.last_timestamp 58 | .store(current_timestamp, Ordering::Relaxed); 59 | let mut id = current_timestamp; 60 | 61 | id <<= 22; 62 | id |= (self.machine_id as i64) << 12; 63 | id |= (self.node_id as i64) << 10; 64 | id |= self.sequence.load(Ordering::Relaxed); 65 | id 66 | } 67 | 68 | fn get_current_timestamp(&self) -> i64 { 69 | std::time::SystemTime::now() 70 | .duration_since(self.unix_epoch) 71 | .unwrap() 72 | .as_millis() as i64 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/worker/requesturl.rs: -------------------------------------------------------------------------------- 1 | use crate::common::error::Result; 2 | use crate::model::sys::model::{ 3 | msys_job::SysJobModel, 4 | msys_job_log::{JobLogAdd, SysJobLogModel}, 5 | }; 6 | use crate::worker::common::{Worker, WorkerOpts}; 7 | use crate::worker::AppWorker; 8 | use async_trait::async_trait; 9 | use serde::{Deserialize, Serialize}; 10 | use tracing::info; 11 | #[derive(Deserialize, Serialize, Clone, Default)] 12 | pub struct RequestUrlMsg { 13 | pub url: String, 14 | pub job_id: i64, 15 | pub task_type: String, 16 | pub job_name: String, 17 | pub job_group: String, 18 | } 19 | 20 | #[derive(Clone)] 21 | pub struct RequestUrlWorker {} 22 | 23 | impl AppWorker for RequestUrlWorker { 24 | fn new() -> Self { 25 | Self {} 26 | } 27 | } 28 | #[async_trait] 29 | impl Worker for RequestUrlWorker { 30 | fn opts() -> WorkerOpts { 31 | WorkerOpts::new().queue("default") 32 | } 33 | 34 | async fn perform(&self, arg: RequestUrlMsg) -> Result<()> { 35 | info!("request url: {}", arg.url); 36 | let url = arg.url; 37 | let resp = reqwest::get(url.as_str()).await; 38 | let runcount = SysJobModel::updata_run_count(arg.job_id).await.unwrap_or(0); 39 | let mut jog_add = JobLogAdd { 40 | job_id: arg.job_id, 41 | run_count: runcount, 42 | ..Default::default() 43 | }; 44 | match resp { 45 | Ok(r) => { 46 | let txt = r.text().await.unwrap_or_default(); 47 | jog_add.job_message = Some(if txt.len() > 2048 { 48 | let truncated = &txt[..2000]; 49 | format!("{truncated} ...数据太长,不记录完整内容。") 50 | } else { 51 | txt 52 | }); 53 | jog_add.status.clone_from(&"Sucess".to_owned()); 54 | } 55 | Err(e) => { 56 | let txt = e.to_string(); 57 | jog_add.job_message = Some(if txt.len() > 2048 { 58 | let truncated = &txt[..2000]; 59 | format!("{truncated} ...数据太长,不记录完整内容。") 60 | } else { 61 | txt 62 | }); 63 | jog_add.status.clone_from(&"Failed".to_owned()); 64 | } 65 | }; 66 | let _ = SysJobLogModel::add(jog_add).await; 67 | Ok(()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/worker/common/enqueue_opts.rs: -------------------------------------------------------------------------------- 1 | use crate::common::error::Result; 2 | use crate::db::GID; 3 | use serde::Serialize; 4 | use serde_json::Value as JsonValue; 5 | 6 | use super::{Job, UnitOfWork}; 7 | 8 | #[must_use] 9 | pub fn opts() -> EnqueueOpts { 10 | EnqueueOpts { 11 | queue: "default".into(), 12 | retry: true, 13 | unique_for: None, 14 | } 15 | } 16 | 17 | pub struct EnqueueOpts { 18 | pub queue: String, 19 | pub retry: bool, 20 | pub unique_for: Option, 21 | } 22 | 23 | impl EnqueueOpts { 24 | #[must_use] 25 | pub fn queue>(self, queue: S) -> Self { 26 | Self { 27 | queue: queue.into(), 28 | ..self 29 | } 30 | } 31 | 32 | #[must_use] 33 | pub fn retry(self, retry: bool) -> Self { 34 | Self { retry, ..self } 35 | } 36 | 37 | #[must_use] 38 | pub fn unique_for(self, unique_for: std::time::Duration) -> Self { 39 | Self { 40 | unique_for: Some(unique_for), 41 | ..self 42 | } 43 | } 44 | 45 | async fn create_job(&self, class: String, args: impl Serialize) -> Result { 46 | let args = serde_json::to_value(args)?; 47 | let args = if args.is_array() { 48 | args 49 | } else { 50 | JsonValue::Array(vec![args]) 51 | }; 52 | 53 | Ok(Job { 54 | queue: self.queue.clone(), 55 | class, 56 | jid: GID().await, 57 | created_at: chrono::Utc::now().timestamp() as f64, 58 | enqueued_at: None, 59 | retry: self.retry, 60 | args, 61 | error_message: None, 62 | failed_at: None, 63 | retry_count: None, 64 | retried_at: None, 65 | unique_for: self.unique_for, 66 | }) 67 | } 68 | 69 | pub async fn perform_async(self, class: String, args: impl Serialize) -> Result<()> { 70 | let job = self.create_job(class, args).await?; 71 | UnitOfWork::from_job(job).enqueue().await?; 72 | Ok(()) 73 | } 74 | 75 | pub async fn perform_in( 76 | &self, 77 | class: String, 78 | duration: std::time::Duration, 79 | args: impl Serialize, 80 | ) -> Result<()> { 81 | let job = self.create_job(class, args).await?; 82 | UnitOfWork::from_job(job).schedule(duration).await?; 83 | Ok(()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_role.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_role")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub role_id: i64, 11 | pub role_name: String, 12 | pub role_key: String, 13 | pub order: i32, 14 | pub data_scope: String, 15 | pub status: String, 16 | pub create_dept: Option, 17 | pub remark: Option, 18 | pub created_at: Option, 19 | pub updated_at: Option, 20 | pub deleted_at: Option, 21 | } 22 | 23 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 24 | pub enum Relation { 25 | #[sea_orm(has_many = "super::sys_role_dept::Entity")] 26 | SysRoleDept, 27 | #[sea_orm(has_many = "super::sys_role_menu::Entity")] 28 | SysRoleMenu, 29 | #[sea_orm(has_many = "super::sys_user_role::Entity")] 30 | SysUserRole, 31 | } 32 | 33 | impl Related for Entity { 34 | fn to() -> RelationDef { 35 | Relation::SysRoleDept.def() 36 | } 37 | } 38 | 39 | impl Related for Entity { 40 | fn to() -> RelationDef { 41 | Relation::SysRoleMenu.def() 42 | } 43 | } 44 | 45 | impl Related for Entity { 46 | fn to() -> RelationDef { 47 | Relation::SysUserRole.def() 48 | } 49 | } 50 | 51 | impl Related for Entity { 52 | fn to() -> RelationDef { 53 | super::sys_role_dept::Relation::SysDept.def() 54 | } 55 | fn via() -> Option { 56 | Some(super::sys_role_dept::Relation::SysRole.def().rev()) 57 | } 58 | } 59 | 60 | impl Related for Entity { 61 | fn to() -> RelationDef { 62 | super::sys_role_menu::Relation::SysMenu.def() 63 | } 64 | fn via() -> Option { 65 | Some(super::sys_role_menu::Relation::SysRole.def().rev()) 66 | } 67 | } 68 | 69 | impl Related for Entity { 70 | fn to() -> RelationDef { 71 | super::sys_user_role::Relation::SysUser.def() 72 | } 73 | fn via() -> Option { 74 | Some(super::sys_user_role::Relation::SysRole.def().rev()) 75 | } 76 | } 77 | 78 | impl ActiveModelBehavior for ActiveModel {} 79 | -------------------------------------------------------------------------------- /src/model/sys/args/asys_dept.rs: -------------------------------------------------------------------------------- 1 | use crate::model::prelude::*; 2 | 3 | #[derive(Debug, Deserialize, Serialize, FromQueryResult, Clone, Default)] 4 | pub struct DeptResp { 5 | #[serde(with = "i64_to_string")] 6 | pub dept_id: i64, 7 | #[serde(with = "i64_to_string")] 8 | pub parent_id: i64, 9 | pub dept_name: Option, 10 | pub dept_category: Option, 11 | pub order: i32, 12 | pub leader: Option, 13 | pub phone: Option, 14 | pub email: Option, 15 | pub status: String, 16 | pub remark: Option, 17 | } 18 | #[derive(Serialize, Clone, Debug, Default, Deserialize)] 19 | pub struct DeptTree { 20 | #[serde(flatten)] 21 | pub dept: DeptResp, 22 | pub children: Option>, 23 | } 24 | 25 | #[derive(Serialize, Clone, Validate, Default, Deserialize)] 26 | pub struct SysDeptEdit { 27 | #[serde(with = "i64_to_string")] 28 | pub dept_id: i64, 29 | #[serde(with = "i64_to_string")] 30 | pub parent_id: i64, 31 | pub dept_name: Option, 32 | pub dept_category: Option, 33 | pub order: i32, 34 | pub leader: Option, 35 | pub phone: Option, 36 | pub email: Option, 37 | pub status: String, 38 | pub remark: Option, 39 | } 40 | 41 | #[derive(Serialize, Clone, Validate, Default, Deserialize)] 42 | pub struct SysDeptAdd { 43 | #[serde(with = "i64_to_string")] 44 | pub parent_id: i64, 45 | pub dept_name: Option, 46 | pub dept_category: Option, 47 | pub order: i32, 48 | pub leader: Option, 49 | pub phone: Option, 50 | pub email: Option, 51 | pub status: String, 52 | pub remark: Option, 53 | } 54 | 55 | #[derive(Debug, Deserialize, Serialize, Clone, Validate)] 56 | pub struct SysDeptDel { 57 | #[serde(with = "i64_to_string")] 58 | pub dept_id: i64, 59 | } 60 | 61 | #[derive(Debug, Deserialize, Serialize, Clone, Validate)] 62 | pub struct SysDeptSearch { 63 | pub dept_name: Option, 64 | } 65 | 66 | #[derive(Serialize, Clone, Debug, Default, Deserialize)] 67 | pub struct DeptTreeData { 68 | pub dept_id: i64, 69 | pub parent_id: i64, 70 | pub lft: i32, 71 | pub rgt: i32, 72 | pub depth: i32, 73 | pub children: Option>, 74 | } 75 | 76 | #[derive(Debug, Deserialize, Serialize, FromQueryResult, Clone, Default)] 77 | pub struct DeptData { 78 | pub dept_id: i64, 79 | pub parent_id: i64, 80 | pub lft: i32, 81 | pub rgt: i32, 82 | pub depth: i32, 83 | } 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/model/sys/model/msys_user_role.rs: -------------------------------------------------------------------------------- 1 | pub use super::args::asys_user_role::*; 2 | pub use super::entity::{ 3 | sys_role::{self, Model as SysRoleModel}, 4 | sys_user_role::{self, ActiveModel, Model as SysUserRoleModel}, 5 | }; 6 | use crate::model::prelude::*; 7 | 8 | impl SysUserRoleModel { 9 | pub async fn find_role_id(uid: i64) -> Result { 10 | let db = DB().await; 11 | sys_user_role::Entity::find() 12 | .filter(sys_user_role::Column::UserId.eq(uid)) 13 | .one(db) 14 | .await? 15 | .ok_or("Failed to find role id".into()) 16 | } 17 | 18 | pub async fn add_user_roles(uid: i64, role_ids: Vec) -> Result { 19 | let db = DB().await; 20 | sys_user_role::Entity::delete_many() 21 | .filter(sys_user_role::Column::UserId.eq(uid)) 22 | .exec(db) 23 | .await?; 24 | 25 | let mut imoses = Vec::new(); 26 | for role_id in role_ids { 27 | imoses.push(sys_user_role::ActiveModel { 28 | user_id: Set(uid), 29 | role_id: Set(role_id), 30 | }); 31 | } 32 | sys_user_role::Entity::insert_many(imoses).exec(db).await?; 33 | Ok("success".to_string()) 34 | } 35 | 36 | pub async fn user_role_list(uid: i64) -> Result> { 37 | let db = DB().await; 38 | let user_roles = sys_user_role::Entity::find() 39 | .filter(sys_user_role::Column::UserId.eq(uid)) 40 | .all(db) 41 | .await?; 42 | 43 | let ids = user_roles 44 | .into_iter() 45 | .map(|user_dept| user_dept.role_id) 46 | .collect(); 47 | Ok(ids) 48 | } 49 | 50 | pub async fn user_role_name_list(uid: i64, rid: i64) -> Result { 51 | let db = DB().await; 52 | let mut rmodel = sys_user_role::Entity::find(); 53 | rmodel = rmodel.filter(sys_user_role::Column::UserId.eq(uid)); 54 | rmodel = rmodel.join_rev( 55 | JoinType::LeftJoin, 56 | sys_role::Entity::belongs_to(sys_user_role::Entity) 57 | .from(sys_role::Column::RoleId) 58 | .to(sys_user_role::Column::RoleId) 59 | .into(), 60 | ); 61 | rmodel = rmodel.columns([sys_role::Column::RoleName, sys_role::Column::RoleId]); 62 | let roles = rmodel.into_model::().all(db).await?; 63 | let user_roles = UserRoleAndUserResp { 64 | user_role_id: rid, 65 | roles, 66 | }; 67 | Ok(user_roles) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "qiluo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | migration = { path = "migration" } 8 | axum = { version = "0.8.7", default-features = true, features = ["http1", "http2", "json", "macros", "matched-path", "original-uri", "multipart", "tokio", "ws", "form", "query"] } 9 | fs-err = "3.2.0" 10 | jsonwebtoken ={ version = "10.2.0", features = ["aws_lc_rs"] } 11 | sea-orm ={ version = "1.1.19", features = [ 12 | "sqlx-sqlite", 13 | "sqlx-mysql", 14 | "runtime-tokio-rustls", 15 | "macros", 16 | ] } 17 | serde = "1.0.228" 18 | serde_json = "1.0.145" 19 | serde_variant = "0.1.3" 20 | serde_yaml = "0.9.34" 21 | tera = "1.20.1" 22 | tokio = { version = "1.48.0", features = ["full"] } 23 | tower-http = { version = "0.6.6", features = [ 24 | "trace", 25 | "catch-panic", 26 | "timeout", 27 | "add-extension", 28 | "cors", 29 | "fs", 30 | "set-header", 31 | "compression-full", 32 | ] } 33 | tracing = "0.1.41" 34 | tracing-subscriber = { version = "0.3.20", features = ["env-filter","json","local-time", "registry"]} 35 | validator ={ version = "0.20.0", features = ["derive"] } 36 | hyper = "1.8.1" 37 | bytes = "1.11.0" 38 | http-body-util = "0.1.3" 39 | axum-extra ={ version = "0.12.2", features = ["typed-header"]} 40 | chrono = "0.4.42" 41 | once_cell = "1.21.3" 42 | lazy_static = "1.5.0" 43 | argon2 = "0.5.3" 44 | rand = "0.9.2" 45 | thiserror = "2.0.17" 46 | http-body = "1.0.1" 47 | captcha_rust = "0.1.3" 48 | indexmap = "2.12.0" 49 | reqwest ={ version = "0.12.24", features = ["json"] } 50 | sysinfo = "0.37.2" 51 | user-agent-parser = "0.3.6" 52 | lettre = { version ="0.11.19", default-features = false, features = [ 53 | "builder", 54 | "hostname", 55 | "smtp-transport", 56 | "tokio1-rustls-tls", 57 | ] } 58 | async-trait = "0.1.89" 59 | bb8 = "0.9.0" 60 | time = "0.3.44" 61 | image = "0.25.9" 62 | tokio-util = "0.7.17" 63 | redis ={version= "0.32",features = [ 64 | "aio", 65 | "default", 66 | "tokio-comp", 67 | "connection-manager" 68 | ]} 69 | sha2 = "0.10.9" 70 | axum-core = "0.5.5" 71 | cron_clock = "0.8.0" 72 | convert_case = "0.9.0" 73 | hex = "0.4.3" 74 | headers = "0.4.1" 75 | futures = "0.3.31" 76 | tokio-stream = "0.1.17" 77 | async_static = "0.1.3" 78 | byte-unit = "5.1.6" 79 | base64 = "0.22.1" 80 | sha1 = "0.10.6" 81 | serde-xml-rs = "0.8.2" 82 | dashmap = "6.1.0" 83 | bb8-redis = "0.24.0" 84 | tokio-rustls = "0.26.4" 85 | tracing-appender = "0.2.3" 86 | axum-server = {version="0.7.3",features=["tls-rustls"]} 87 | parking_lot = "0.12.5" 88 | [target.x86_64-unknown-linux-musl.dependencies] 89 | openssl = { version = "0.10", features = ["vendored"] } 90 | -------------------------------------------------------------------------------- /src/worker/mailer.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use crate::worker::common::{Worker,WorkerOpts}; 3 | mod email_sender; 4 | mod template; 5 | pub const DEFAULT_FROM_SENDER: &str = "XXXX科技 "; 6 | use crate::config::APPCOFIG; 7 | use crate::worker::AppWorker; 8 | use crate::common::error::Result; 9 | use async_trait::async_trait; 10 | pub use email_sender::EmailSender; 11 | use tokio::sync::OnceCell; 12 | 13 | static EMAILSENDER: OnceCell = OnceCell::const_new(); 14 | use self::template::Template; 15 | 16 | async fn email_init() -> EmailSender { 17 | let config = APPCOFIG.mailer.clone().unwrap().smtp.unwrap(); 18 | EmailSender::smtp(&config).unwrap() 19 | } 20 | 21 | #[derive(Serialize, Deserialize, Debug, Clone, Default)] 22 | pub struct Email { 23 | /// Mailbox to `From` header 24 | pub from: Option, 25 | /// Mailbox to `To` header 26 | pub to: String, 27 | /// Mailbox to `ReplyTo` header 28 | pub reply_to: Option, 29 | /// Subject header to message 30 | pub subject: String, 31 | /// Plain text message 32 | pub text: String, 33 | /// HTML template 34 | pub html: String, 35 | } 36 | #[derive(Clone)] 37 | pub struct MailerWorker {} 38 | 39 | impl AppWorker for MailerWorker { 40 | fn new() -> Self { 41 | Self {} 42 | } 43 | } 44 | 45 | #[async_trait] 46 | impl Worker for MailerWorker { 47 | /// Returns options for the mailer worker, specifying the queue to process. 48 | fn opts() -> WorkerOpts { 49 | WorkerOpts::new().queue("mailer") 50 | } 51 | 52 | /// Performs the email sending operation using the provided [`AppContext`] 53 | /// and email details. 54 | async fn perform(&self, email: Email) -> Result<()> { 55 | let mailer = EMAILSENDER.get_or_init(email_init).await; 56 | let _ = mailer.mail(&email).await; 57 | Ok(()) 58 | } 59 | } 60 | 61 | #[derive(Debug, Clone, Default)] 62 | pub struct Args { 63 | pub from: Option, 64 | pub to: String, 65 | pub reply_to: Option, 66 | pub locals: serde_json::Value, 67 | } 68 | 69 | pub async fn mail_template(dir: String, args: Args) -> Result<()> { 70 | let content = Template::new(dir).render(&args.locals)?; 71 | mail(&Email { 72 | from: args.from.clone(), 73 | to: args.to.clone(), 74 | reply_to: args.reply_to.clone(), 75 | subject: content.subject, 76 | text: content.text, 77 | html: content.html, 78 | }) 79 | .await 80 | } 81 | 82 | async fn mail(email: &Email) -> Result<()> { 83 | MailerWorker::enqueue_async(email.clone()) 84 | .await 85 | .map_err(Box::from)?; 86 | Ok(()) 87 | } 88 | -------------------------------------------------------------------------------- /src/api/test.rs: -------------------------------------------------------------------------------- 1 | use super::web_path::{WebPath, WebPathType}; 2 | use crate::service::test::*; 3 | 4 | use axum::{ 5 | routing::{delete, get, post, put}, 6 | Router, 7 | }; 8 | pub fn router_test() -> WebPath { 9 | WebPath::new().nest( 10 | "/test", 11 | WebPath::new() 12 | .nest("/test_api", test_test_api()) 13 | .nest("/test_data_scope", test_data_scope_api()), 14 | ) 15 | } 16 | fn test_data_scope_api() -> WebPath { 17 | WebPath::new() 18 | .route( 19 | "/list", 20 | WebPathType::Get, 21 | Some("list"), 22 | get(s_test_data_scope::list), 23 | ) 24 | .route( 25 | "/edit", 26 | WebPathType::Put, 27 | Some("edit"), 28 | put(s_test_data_scope::edit), 29 | ) 30 | .route( 31 | "/add", 32 | WebPathType::Post, 33 | Some("add"), 34 | post(s_test_data_scope::add), 35 | ) 36 | .route( 37 | "/del", 38 | WebPathType::Delete, 39 | Some("Delete"), 40 | delete(s_test_data_scope::delete), 41 | ) 42 | } 43 | 44 | pub fn white_test() -> Router { 45 | Router::new() 46 | } 47 | 48 | fn test_test_api() -> WebPath { 49 | WebPath::new() 50 | .route( 51 | "/list", 52 | WebPathType::Get, 53 | Some("获取列表"), 54 | get(s_test_api::list), 55 | ) 56 | .route( 57 | "/edit", 58 | WebPathType::Put, 59 | Some("编辑TestApi"), 60 | put(s_test_api::edit), 61 | ) 62 | .route( 63 | "/add", 64 | WebPathType::Post, 65 | Some("添加TestApi"), 66 | post(s_test_api::add), 67 | ) 68 | .route( 69 | "/del", 70 | WebPathType::Delete, 71 | Some("删除TestApi"), 72 | delete(s_test_api::delete), 73 | ) 74 | .route( 75 | "/db_index_test", 76 | WebPathType::Put, 77 | Some("db_index_test"), 78 | put(s_test_api::db_index_test), 79 | ) 80 | .route( 81 | "/db_name_test", 82 | WebPathType::Put, 83 | Some("db_name_test"), 84 | put(s_test_api::db_name_test), 85 | ) 86 | .route( 87 | "/db_read_write_test", 88 | WebPathType::Put, 89 | Some("db_read_write_test"), 90 | put(s_test_api::db_read_write_test), 91 | ) 92 | .route( 93 | "/db_auto_test", 94 | WebPathType::Put, 95 | Some("db_auto_test"), 96 | put(s_test_api::db_auto_test), 97 | ) 98 | } 99 | -------------------------------------------------------------------------------- /src/worker/invokefunction.rs: -------------------------------------------------------------------------------- 1 | use crate::common::error::Result; 2 | use crate::model::sys::model::{ 3 | msys_job::SysJobModel, 4 | msys_job_log::{JobLogAdd, SysJobLogModel}, 5 | }; 6 | use crate::service::sys::{s_sys_api_permission, s_sys_white_jwt}; 7 | use crate::worker::common::{Worker, WorkerOpts}; 8 | use crate::worker::AppWorker; 9 | use async_trait::async_trait; 10 | use serde::{Deserialize, Serialize}; 11 | use tracing::info; 12 | #[derive(Deserialize, Serialize, Clone, Default, Debug)] 13 | pub struct InvokeFunctionMsg { 14 | pub job_id: Option, 15 | pub callfun: String, 16 | pub parmets: String, 17 | } 18 | 19 | #[derive(Clone)] 20 | pub struct InvokeFunctionWorker {} 21 | 22 | impl AppWorker for InvokeFunctionWorker { 23 | fn new() -> Self { 24 | Self {} 25 | } 26 | } 27 | #[async_trait] 28 | impl Worker for InvokeFunctionWorker { 29 | fn opts() -> WorkerOpts { 30 | WorkerOpts::new().queue("default") 31 | } 32 | 33 | async fn perform(&self, arg: InvokeFunctionMsg) -> Result<()> { 34 | info!("InvokeFunctionWorker perform: {:?}", arg); 35 | let message = match arg.callfun.as_str() { 36 | "updateapi" => s_sys_api_permission::update_all_api(arg.parmets).await, 37 | "clearuserinfo" => s_sys_white_jwt::clear_user_info().await, 38 | _ => Ok("未找到对应的方法".to_string()), 39 | }; 40 | if let Some(job_id) = arg.job_id { 41 | let runcount = SysJobModel::updata_run_count(job_id).await.unwrap_or(0); 42 | let mut jog_add = JobLogAdd { 43 | job_id, 44 | run_count: runcount, 45 | ..Default::default() 46 | }; 47 | match message { 48 | Ok(r) => { 49 | let txt = r; 50 | jog_add.job_message = Some(if txt.len() > 2048 { 51 | let truncated = &txt[..2000]; 52 | format!("{truncated} ...数据太长,不记录完整内容。") 53 | } else { 54 | txt 55 | }); 56 | jog_add.status.clone_from(&"Sucess".to_owned()); 57 | } 58 | Err(e) => { 59 | let txt = e.to_string(); 60 | jog_add.job_message = Some(if txt.len() > 2048 { 61 | let truncated = &txt[..2000]; 62 | format!("{truncated} ...数据太长,不记录完整内容。") 63 | } else { 64 | txt 65 | }); 66 | jog_add.status.clone_from(&"Failed".to_owned()); 67 | } 68 | }; 69 | let _ = SysJobLogModel::add(jog_add).await; 70 | } 71 | 72 | Ok(()) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/model/sys/entity/sys_user.rs: -------------------------------------------------------------------------------- 1 | //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 2 | 3 | use sea_orm::entity::prelude::*; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] 7 | #[sea_orm(table_name = "sys_user")] 8 | pub struct Model { 9 | #[sea_orm(primary_key, auto_increment = false)] 10 | pub id: i64, 11 | pub dept_id: i64, 12 | pub role_id: i64, 13 | pub user_name: String, 14 | pub nick_name: String, 15 | pub user_type: Option, 16 | pub email: Option, 17 | pub phonenumber: Option, 18 | pub sex: Option, 19 | pub avatar: Option, 20 | pub password: String, 21 | pub status: Option, 22 | pub login_ip: Option, 23 | pub login_date: Option, 24 | pub create_dept: Option, 25 | pub create_by: Option, 26 | pub created_at: Option, 27 | pub update_by: Option, 28 | pub updated_at: Option, 29 | pub deleted_at: Option, 30 | pub remark: Option, 31 | } 32 | 33 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 34 | pub enum Relation { 35 | #[sea_orm(has_many = "super::sys_user_dept::Entity")] 36 | SysUserDept, 37 | #[sea_orm(has_many = "super::sys_user_post::Entity")] 38 | SysUserPost, 39 | #[sea_orm(has_many = "super::sys_user_role::Entity")] 40 | SysUserRole, 41 | } 42 | 43 | impl Related for Entity { 44 | fn to() -> RelationDef { 45 | Relation::SysUserDept.def() 46 | } 47 | } 48 | 49 | impl Related for Entity { 50 | fn to() -> RelationDef { 51 | Relation::SysUserPost.def() 52 | } 53 | } 54 | 55 | impl Related for Entity { 56 | fn to() -> RelationDef { 57 | Relation::SysUserRole.def() 58 | } 59 | } 60 | 61 | impl Related for Entity { 62 | fn to() -> RelationDef { 63 | super::sys_user_dept::Relation::SysDept.def() 64 | } 65 | fn via() -> Option { 66 | Some(super::sys_user_dept::Relation::SysUser.def().rev()) 67 | } 68 | } 69 | 70 | impl Related for Entity { 71 | fn to() -> RelationDef { 72 | super::sys_user_post::Relation::SysPost.def() 73 | } 74 | fn via() -> Option { 75 | Some(super::sys_user_post::Relation::SysUser.def().rev()) 76 | } 77 | } 78 | 79 | impl Related for Entity { 80 | fn to() -> RelationDef { 81 | super::sys_user_role::Relation::SysRole.def() 82 | } 83 | fn via() -> Option { 84 | Some(super::sys_user_role::Relation::SysUser.def().rev()) 85 | } 86 | } 87 | 88 | impl ActiveModelBehavior for ActiveModel {} 89 | -------------------------------------------------------------------------------- /src/cache/traits.rs: -------------------------------------------------------------------------------- 1 | // src/cache/traits.rs 2 | use crate::common::error::Result; 3 | use crate::model::prelude::ListData; 4 | use crate::model::sys::args::acache::CacheItem; 5 | use async_trait::async_trait; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | #[async_trait] 9 | pub trait CacheProvider: Send + Sync + Clone { 10 | async fn recycling(&self); 11 | async fn set_string(&self, k: &str, v: &str) -> Result; 12 | async fn get_string(&self, k: &str) -> Result; 13 | async fn set_string_ex(&self, k: &str, v: &str, t: i32) -> Result; 14 | async fn remove(&self, k: &str) -> Result; 15 | async fn contains_key(&self, k: &str) -> bool; 16 | async fn ttl(&self, k: &str) -> Result; 17 | async fn get_one_use(&self, k: &str) -> Result; 18 | async fn get_all(&self) -> Result>; 19 | async fn get_all_paginated( 20 | &self, 21 | page_num: u64, 22 | page_size: u64, 23 | search_key: Option, 24 | ) -> Result>; 25 | 26 | async fn get_value(&self, k: &str) -> Result 27 | where 28 | T: Serialize + for<'de> Deserialize<'de> + Clone; 29 | 30 | async fn set_value(&self, k: &str, value: &T) -> Result 31 | where 32 | T: Serialize + Sync; 33 | 34 | async fn set_value_ex(&self, k: &str, value: &T, t: i32) -> Result 35 | where 36 | T: Serialize + Sync; 37 | 38 | async fn with_namespace(&self, namespace: String) -> Self; 39 | async fn set_namespace(&self, namespace: String); 40 | async fn namespaced_key(&self, key: &str) -> String; 41 | async fn namespaced_keys(&self, keys: Vec) -> Vec; 42 | 43 | async fn brpop(&self, keys: Vec, timeout: usize) -> Result>; 44 | 45 | async fn sadd(&self, key: &str, members: &[&str]) -> Result; 46 | 47 | async fn set_nx_ex(&self, key: &str, value: V, ttl_in_seconds: usize) -> Result 48 | where 49 | V: ToString + Send + Sync; 50 | 51 | async fn zrange(&self, key: &str, start: i64, stop: i64) -> Result>; 52 | 53 | async fn zrangebyscore_limit( 54 | &self, 55 | key: &str, 56 | min_score: f64, 57 | max_score: f64, 58 | offset: isize, 59 | count: isize, 60 | ) -> Result> 61 | where 62 | S: Into + Send + Sync; 63 | 64 | async fn zadd(&self, key: &str, value: V, score: S) -> Result 65 | where 66 | V: ToString + Send + Sync, 67 | S: Into + Send + Sync; 68 | 69 | async fn lpush(&self, key: &str, value: V) -> Result 70 | where 71 | V: ToString + Send + Sync; 72 | 73 | async fn zadd_ch(&self, key: &str, value: V, score: S) -> Result 74 | where 75 | V: ToString + Send + Sync, 76 | S: Into + Send + Sync; 77 | 78 | async fn zrem(&self, key: &str, value: V) -> Result 79 | where 80 | V: ToString + Send + Sync; 81 | } 82 | -------------------------------------------------------------------------------- /src/worker/common/unit_of_work.rs: -------------------------------------------------------------------------------- 1 | use crate::cache::CacheManager; 2 | use crate::common::error::Result; 3 | use rand::Rng; 4 | use sha2::{Digest, Sha256}; 5 | 6 | use super::Job; 7 | 8 | #[derive(Debug)] 9 | pub struct UnitOfWork { 10 | pub queue: String, 11 | pub job: Job, 12 | } 13 | 14 | impl UnitOfWork { 15 | #[must_use] 16 | pub fn from_job(job: Job) -> Self { 17 | Self { 18 | queue: format!("queue:{}", &job.queue), 19 | job, 20 | } 21 | } 22 | 23 | pub fn from_job_string(job_str: String) -> Result { 24 | let job: Job = serde_json::from_str(&job_str)?; 25 | Ok(Self::from_job(job)) 26 | } 27 | 28 | pub async fn enqueue(&self) -> Result<()> { 29 | self.enqueue_direct().await 30 | } 31 | 32 | pub async fn enqueue_direct(&self) -> Result<()> { 33 | let mut job = self.job.clone(); 34 | job.enqueued_at = Some(chrono::Utc::now().timestamp() as f64); 35 | let cache = CacheManager::instance().await; 36 | if let Some(ref duration) = job.unique_for { 37 | let args_as_json_string: String = serde_json::to_string(&job.args)?; 38 | let args_hash = format!("{:x}", Sha256::digest(&args_as_json_string)); 39 | let redis_key = format!( 40 | "enqueue:unique:{}:{}:{}", 41 | &job.queue, &job.class, &args_hash 42 | ); 43 | if cache 44 | .set_nx_ex(&redis_key, "", duration.as_secs() as usize) 45 | .await? 46 | { 47 | return Ok(()); 48 | } 49 | } 50 | 51 | cache.sadd("queues", &[job.queue.as_str()]).await?; 52 | 53 | cache 54 | .lpush(&self.queue, serde_json::to_string(&job)?) 55 | .await?; 56 | Ok(()) 57 | } 58 | 59 | pub async fn reenqueue(&mut self) -> Result<()> { 60 | if let Some(retry_count) = self.job.retry_count { 61 | let cache = CacheManager::instance().await; 62 | cache 63 | .zadd( 64 | "retry", 65 | serde_json::to_string(&self.job)?, 66 | Self::retry_job_at(retry_count).timestamp() as f64, 67 | ) 68 | .await?; 69 | } 70 | 71 | Ok(()) 72 | } 73 | 74 | fn retry_job_at(count: usize) -> chrono::DateTime { 75 | let seconds_to_delay = 76 | count.pow(4) + 15 + (rand::rng().random_range(0..30) * (count + 1)); 77 | 78 | chrono::Utc::now() + chrono::Duration::seconds(seconds_to_delay as i64) 79 | } 80 | 81 | pub async fn schedule(&mut self, duration: std::time::Duration) -> Result<()> { 82 | let enqueue_at = chrono::Utc::now() + chrono::Duration::from_std(duration).unwrap(); 83 | let cache = CacheManager::instance().await; 84 | let _ = cache 85 | .zadd( 86 | "schedule", 87 | serde_json::to_string(&self.job)?, 88 | enqueue_at.timestamp() as f64, 89 | ) 90 | .await; 91 | Ok(()) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_login_info.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::model::msys_login_info::{ 2 | LoginInfoEdit, LoginInfoMsg, LoginInfoSearch, SysLoginInfoModel, 3 | }; 4 | use crate::service::prelude::*; 5 | 6 | pub async fn list( 7 | VQuery(arg): VQuery, 8 | VQuery(search): VQuery, 9 | ) -> impl IntoResponse { 10 | let rlist = SysLoginInfoModel::list(arg, search).await; 11 | ApiResponse::from_result(rlist) 12 | } 13 | 14 | pub async fn update_login_info(arg: LoginInfoMsg) { 15 | let client_info = login_info_function::get_city_by_ip(&arg.ipaddr).await; 16 | 17 | let logadd = LoginInfoEdit { 18 | info_id: arg.info_id, 19 | login_location: Some(client_info.location), 20 | ipaddr: Some(client_info.ip), 21 | net_work: Some(client_info.net_work), 22 | ..Default::default() 23 | }; 24 | let _ = SysLoginInfoModel::edit(logadd).await; 25 | } 26 | 27 | pub mod login_info_function { 28 | use crate::model::sys::args::asys_login_info::*; 29 | use std::env; 30 | use std::{borrow::Cow, collections::HashMap}; 31 | use user_agent_parser::UserAgentParser; 32 | 33 | pub fn get_user_agent_info(user_agent: &str) -> UserAgentInfo { 34 | let path = env::current_dir().unwrap(); 35 | let file = path.join("config/regexes.yaml"); 36 | 37 | let ua_parser = UserAgentParser::from_path(file).unwrap(); 38 | let product_v = ua_parser.parse_product(user_agent); 39 | let os_v = ua_parser.parse_os(user_agent); 40 | let device_v = ua_parser.parse_device(user_agent); 41 | let browser = product_v.name.unwrap_or(Cow::Borrowed("")).to_string() 42 | + " " 43 | + product_v 44 | .major 45 | .unwrap_or(Cow::Borrowed("")) 46 | .to_string() 47 | .as_str(); 48 | let os = os_v.name.unwrap_or(Cow::Borrowed("")).to_string() 49 | + " " 50 | + os_v.major.unwrap_or(Cow::Borrowed("")).to_string().as_str(); 51 | let device = device_v.name.unwrap_or(Cow::Borrowed("")).to_string(); 52 | UserAgentInfo { 53 | browser: browser.trim().to_string(), 54 | os: os.trim().to_string(), 55 | device, 56 | } 57 | } 58 | pub async fn get_city_by_ip(ip: &str) -> ClientNetInfo { 59 | let mut cliinfo = ClientNetInfo { 60 | ip: ip.to_string(), 61 | location: "Unknown City".to_owned(), 62 | net_work: "Unknown Network".to_owned(), 63 | }; 64 | let url = "http://whois.pconline.com.cn/ipJson.jsp?json=true&ip=".to_string() + ip; 65 | let resp = reqwest::get(url.as_str()).await; 66 | let resp = match resp { 67 | Ok(resp) => resp, 68 | Err(_) => return cliinfo, 69 | }; 70 | let resp = resp.text_with_charset("utf-8").await; 71 | if resp.is_ok() { 72 | let res = serde_json::from_str::>(resp.unwrap().as_str()); 73 | if res.is_ok() { 74 | let res = res.unwrap(); 75 | cliinfo.ip = res["ip"].to_string(); 76 | cliinfo.location = format!("{}{}", res["pro"], res["city"]); 77 | cliinfo.net_work = res["addr"].split(' ').collect::>()[1].to_string(); 78 | } 79 | } 80 | 81 | cliinfo 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/worker/mailer/email_sender.rs: -------------------------------------------------------------------------------- 1 | use super::{Email, DEFAULT_FROM_SENDER}; 2 | use crate::common::error::{Error, Result}; 3 | use lettre::{ 4 | AsyncTransport, Message, Tokio1Executor, Transport, message::{MultiPart, SinglePart}, transport::smtp::authentication::Credentials 5 | }; 6 | use tracing::info; 7 | 8 | #[derive(Clone)] 9 | pub enum EmailTransport { 10 | /// SMTP (Simple Mail Transfer Protocol) transport. 11 | Smtp(lettre::AsyncSmtpTransport), 12 | /// Test/stub transport for testing purposes. 13 | Test(lettre::transport::stub::StubTransport), 14 | } 15 | 16 | #[derive(Clone)] 17 | pub struct EmailSender { 18 | pub transport: EmailTransport, 19 | } 20 | 21 | impl EmailSender { 22 | pub fn smtp(config: &crate::config::appconfig::SmtpMailer) -> Result { 23 | let mut email_builder = if config.secure { 24 | lettre::AsyncSmtpTransport::::relay(&config.host) 25 | .map_err(|error| { 26 | tracing::error!(err.msg = %error, err.detail = ?error, "smtp_init_error"); 27 | Error::Message("error initialize smtp mailer".to_string()) 28 | })? 29 | .port(config.port) 30 | } else { 31 | lettre::AsyncSmtpTransport::::builder_dangerous(&config.host) 32 | .port(config.port) 33 | }; 34 | 35 | if let Some(auth) = config.auth.as_ref() { 36 | email_builder = email_builder 37 | .credentials(Credentials::new(auth.user.clone(), auth.password.clone())); 38 | } 39 | 40 | Ok(Self { 41 | transport: EmailTransport::Smtp(email_builder.build()), 42 | }) 43 | } 44 | 45 | pub async fn mail(&self, email: &Email) -> Result { 46 | let content = if email.html.trim().is_empty() { 47 | // 只有纯文本 48 | MultiPart::alternative().singlepart(SinglePart::plain(email.text.clone())) 49 | } else { 50 | // 同时提供纯文本 + HTML 51 | MultiPart::alternative_plain_html(email.text.clone(), email.html.clone()) 52 | }; 53 | let mut builder = Message::builder() 54 | .from( 55 | email 56 | .from 57 | .clone() 58 | .unwrap_or_else(|| DEFAULT_FROM_SENDER.to_string()) 59 | .parse()?, 60 | ) 61 | .to(email.to.parse()?); 62 | 63 | if let Some(reply_to) = &email.reply_to { 64 | builder = builder.reply_to(reply_to.parse()?); 65 | } 66 | 67 | let msg = builder 68 | .subject(email.subject.clone()) 69 | .multipart(content) 70 | .map_err(|error| { 71 | tracing::error!(err.msg = %error, err.detail = ?error, "email_building_error"); 72 | Error::Message("error building email message".to_owned()) 73 | })?; 74 | 75 | match &self.transport { 76 | EmailTransport::Smtp(xp) => { 77 | // xp.send(msg).await?; 78 | match xp.send(msg).await { 79 | Ok(_) => info!("Email sent successfully!"), 80 | Err(e) => info!("Could not send email: {e:?}"), 81 | } 82 | } 83 | EmailTransport::Test(xp) => { 84 | xp.send(&msg) 85 | .map_err(|_| Error::Message("sending email error".into()))?; 86 | } 87 | }; 88 | Ok("sc".to_owned()) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/model/sys/model/msys_dict_type.rs: -------------------------------------------------------------------------------- 1 | pub use super::args::asys_dict_type::*; 2 | pub use super::entity::sys_dict_type::{self, ActiveModel, Model as SysDictTypeModel}; 3 | use super::msys_dict_data::SysDictDataModel; 4 | use crate::model::prelude::*; 5 | 6 | impl SysDictTypeModel { 7 | pub async fn list(arg: PageParams, search: DictDataSearch) -> Result> { 8 | let page_num = arg.page_num.unwrap_or(1); 9 | let page_per_size = arg.page_size.unwrap_or(10); 10 | let db = DB().await; 11 | let mut rmodel = sys_dict_type::Entity::find(); 12 | 13 | if let Some(dict_type) = search.dict_type { 14 | rmodel = rmodel.filter(sys_dict_type::Column::DictType.eq(dict_type)); 15 | } 16 | if let Some(dict_name) = search.dict_name { 17 | rmodel = rmodel.filter(sys_dict_type::Column::DictName.eq(dict_name)); 18 | } 19 | let total = rmodel.clone().count(db).await?; 20 | let paginator = rmodel 21 | .order_by_asc(sys_dict_type::Column::Order) 22 | .into_model::() 23 | .paginate(db, page_per_size); 24 | let total_pages = paginator.num_pages().await?; 25 | let list = paginator.fetch_page(page_num - 1).await?; 26 | let res = ListData { 27 | list, 28 | total, 29 | total_pages, 30 | page_num, 31 | }; 32 | Ok(res) 33 | } 34 | 35 | pub async fn add(arg: DictTypeAdd) -> Result { 36 | let db = DB().await; 37 | let id = GID().await; 38 | let now = Local::now().naive_local(); 39 | let amodel = sys_dict_type::ActiveModel { 40 | dict_id: Set(id), 41 | dict_name: Set(arg.dict_name), 42 | dict_type: Set(arg.dict_type), 43 | order: Set(arg.order), 44 | remark: Set(arg.remark), 45 | created_at: Set(now), 46 | updated_at: Set(now), 47 | }; 48 | amodel.insert(db).await?; 49 | Ok("Success".to_string()) 50 | } 51 | 52 | pub async fn edit(arg: DictTypeEdit) -> Result { 53 | let db = DB().await; 54 | let now = Local::now().naive_local(); 55 | let rmodel = sys_dict_type::Entity::find_by_id(arg.dict_id) 56 | .one(db) 57 | .await?; 58 | let mut amodel: sys_dict_type::ActiveModel = if let Some(r) = rmodel { 59 | r.into() 60 | } else { 61 | return Err("dict_type not found".into()); 62 | }; 63 | amodel.dict_name = Set(arg.dict_name); 64 | amodel.dict_type = Set(arg.dict_type); 65 | amodel.order = Set(arg.order); 66 | amodel.updated_at = Set(now); 67 | amodel.remark = Set(arg.remark); 68 | amodel.update(db).await?; 69 | Ok("Success".to_string()) 70 | } 71 | pub async fn del(arg: DictTypeDel) -> Result { 72 | let db = DB().await; 73 | let rmodel = sys_dict_type::Entity::find_by_id(arg.dict_id) 74 | .one(db) 75 | .await?; 76 | let model = if let Some(r) = rmodel { 77 | r 78 | } else { 79 | return Err("dict_type not found".into()); 80 | }; 81 | let txn = db.begin().await?; 82 | SysDictDataModel::delete_by_dict_type(model.dict_id, &txn).await?; 83 | sys_dict_type::Entity::delete_by_id(model.dict_id) 84 | .exec(&txn) 85 | .await?; 86 | txn.commit().await?; 87 | Ok("Success".to_string()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/api/web_path.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use axum::routing::MethodRouter; 4 | use serde::{Deserialize, Serialize}; 5 | use tracing::info; 6 | #[derive(Debug, Default, Serialize, Deserialize, PartialEq)] 7 | pub enum WebPathType { 8 | #[default] 9 | None, 10 | Get, 11 | Post, 12 | Put, 13 | Delete, 14 | } 15 | 16 | #[derive(Debug, Default, Serialize, Deserialize)] 17 | pub struct WebPath { 18 | pub final_path: String, 19 | pub webmethod: WebPathType, 20 | #[serde(skip)] 21 | pub method_router: Option, 22 | sub_paths: HashMap, 23 | pub apiname: Option, 24 | } 25 | 26 | impl WebPath { 27 | pub fn new() -> Self { 28 | WebPath::default() 29 | } 30 | pub fn nest(mut self, path: &str, web_path: WebPath) -> Self { 31 | self.sub_paths.insert(String::from(path), web_path); 32 | self 33 | } 34 | 35 | pub fn merge(mut self, web_path: WebPath) -> Self { 36 | for (sub_key, sub_path) in web_path.sub_paths { 37 | self.sub_paths.insert(sub_key, sub_path); 38 | } 39 | self 40 | } 41 | pub fn route( 42 | mut self, 43 | path: &str, 44 | method: WebPathType, 45 | apiname: Option<&str>, 46 | method_router: MethodRouter, 47 | ) -> Self { 48 | self.sub_paths.insert( 49 | String::from(path), 50 | WebPath { 51 | webmethod: method, 52 | apiname: apiname.map(String::from), 53 | method_router: Some(method_router), 54 | sub_paths: HashMap::new(), 55 | ..Default::default() 56 | }, 57 | ); 58 | self 59 | } 60 | 61 | fn concat_sub_paths_final_paths(&mut self, parent_path: &str) { 62 | for (sub_key, sub_path) in &mut self.sub_paths { 63 | let f_path = format!("{}{}", parent_path, sub_key); 64 | sub_path.concat_sub_paths_final_paths(&f_path); 65 | if sub_path.is_last_level() { 66 | sub_path.final_path = f_path; 67 | } 68 | } 69 | } 70 | pub fn final_to_path(mut self) -> Self { 71 | self.concat_sub_paths_final_paths(""); 72 | self 73 | } 74 | 75 | fn is_last_level(&self) -> bool { 76 | self.sub_paths.is_empty() 77 | } 78 | 79 | pub fn get_last_level_paths(&self) -> Vec<&WebPath> { 80 | let mut last_level_paths = Vec::new(); 81 | if self.is_last_level() && self.webmethod != WebPathType::None { 82 | last_level_paths.push(self); 83 | } 84 | for sub_path in self.sub_paths.values() { 85 | last_level_paths.extend(sub_path.get_last_level_paths()); 86 | } 87 | last_level_paths 88 | } 89 | 90 | pub fn print_all_paths(&self) { 91 | for sub_path_data in self.sub_paths.values() { 92 | if sub_path_data.is_last_level() { 93 | info!("{}", sub_path_data.final_path); 94 | } 95 | 96 | sub_path_data.print_all_paths(); 97 | } 98 | } 99 | } 100 | 101 | impl WebPathType { 102 | pub fn as_str(&self) -> &'static str { 103 | match self { 104 | WebPathType::None => "None", 105 | WebPathType::Get => "Get", 106 | WebPathType::Post => "Post", 107 | WebPathType::Put => "Put", 108 | WebPathType::Delete => "Delete", 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /migration/src/m20220101_000001_create_table.rs: -------------------------------------------------------------------------------- 1 | use sea_orm_migration::{prelude::*, schema::*}; 2 | 3 | #[derive(DeriveMigrationName)] 4 | pub struct Migration; 5 | 6 | #[async_trait::async_trait] 7 | impl MigrationTrait for Migration { 8 | async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 9 | let table = table_auto(SysJob::Table) 10 | .col(string(SysJob::JobId).primary_key().unique_key().char_len(32)) 11 | .col(string(SysJob::TaskID).char_len(32)) 12 | .col(integer(SysJob::TaskCount)) 13 | .col(integer(SysJob::RunCount)) 14 | .col(string(SysJob::JobName).char_len(32)) 15 | .col(string_null(SysJob::JobParams).char_len(32)) 16 | .col(string(SysJob::JobGroup).char_len(32)) 17 | .col(string(SysJob::InvokeFunction).char_len(32)) 18 | .col(string(SysJob::CronExpression).char_len(32)) 19 | .col(string(SysJob::MisfirePolicy).char_len(32)) 20 | .col(string_null(SysJob::Concurrent).char_len(32)) 21 | .col(string(SysJob::Status).char_len(32)) 22 | .col(string(SysJob::Remark).string_len(512)) 23 | .col(timestamp_null(SysJob::LastTime).default(Expr::current_timestamp())) 24 | .col(timestamp_null(SysJob::NextTime).default(Expr::current_timestamp())) 25 | .col(timestamp_null(SysJob::EndTime).default(Expr::current_timestamp())) 26 | .to_owned(); 27 | manager.create_table(table).await?; 28 | 29 | let table = table_auto(SysJobLog::Table) 30 | .col(string(SysJobLog::Id).primary_key().unique_key().char_len(32)) 31 | .col(string(SysJobLog::JobId).char_len(32)) 32 | .col(string(SysJobLog::LotId).char_len(32)) 33 | .col(integer(SysJobLog::LotOrder)) 34 | .col(string(SysJobLog::JobName).char_len(32)) 35 | .col(string(SysJobLog::JobGroup).char_len(32)) 36 | .col(string(SysJobLog::InvokeFunction).char_len(32)) 37 | .col(string_null(SysJobLog::JobParams).char_len(32)) 38 | .col(string_null(SysJobLog::JobMessage).string_len(512)) 39 | .col(string(SysJobLog::Status).char_len(32)) 40 | .col(string_null(SysJobLog::ExceptionInfo).string_len(512)) 41 | .col(string_null(SysJobLog::IsOnce).char_len(32)) 42 | .col(timestamp_null(SysJobLog::ElapsedTime).default(Expr::current_timestamp())) 43 | .to_owned(); 44 | manager.create_table(table).await?; 45 | 46 | Ok(()) 47 | } 48 | 49 | async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 50 | let _ = manager 51 | .drop_table(Table::drop().table(SysJob::Table).to_owned()) 52 | .await; 53 | let _ = manager 54 | .drop_table(Table::drop().table(SysJobLog::Table).to_owned()) 55 | .await; 56 | Ok(()) 57 | } 58 | } 59 | 60 | #[derive(DeriveIden)] 61 | enum SysJob { 62 | Table, 63 | JobId, 64 | TaskID, 65 | TaskCount, 66 | RunCount, 67 | JobName, 68 | JobParams, 69 | JobGroup, 70 | InvokeFunction, 71 | CronExpression, 72 | MisfirePolicy, 73 | Concurrent, 74 | Status, 75 | Remark, 76 | LastTime, 77 | NextTime, 78 | EndTime, 79 | } 80 | 81 | #[derive(DeriveIden)] 82 | enum SysJobLog { 83 | Table, 84 | Id, 85 | JobId, 86 | LotId, 87 | LotOrder, 88 | JobName, 89 | JobGroup, 90 | InvokeFunction, 91 | JobParams, 92 | JobMessage, 93 | Status, 94 | ExceptionInfo, 95 | IsOnce, 96 | ElapsedTime, 97 | } 98 | -------------------------------------------------------------------------------- /src/model/sys/model/msys_login_info.rs: -------------------------------------------------------------------------------- 1 | pub use super::args::asys_login_info::*; 2 | pub use super::entity::sys_login_info::{self, ActiveModel, Model as SysLoginInfoModel}; 3 | use crate::model::prelude::*; 4 | 5 | impl SysLoginInfoModel { 6 | pub async fn list(arg: PageParams, search: LoginInfoSearch) -> Result> { 7 | let page_num = arg.page_num.unwrap_or(1); 8 | let page_per_size = arg.page_size.unwrap_or(10); 9 | let db = DB().await; 10 | let mut rmodel = sys_login_info::Entity::find(); 11 | if let Some(user_name) = search.user_name { 12 | rmodel = rmodel.filter(sys_login_info::Column::UserName.contains(user_name)); 13 | } 14 | 15 | let total = rmodel.clone().count(db).await?; 16 | let paginator = rmodel 17 | .order_by_desc(sys_login_info::Column::LoginTime) 18 | .into_model::() 19 | .paginate(db, page_per_size); 20 | let total_pages = paginator.num_pages().await?; 21 | let list = paginator.fetch_page(page_num - 1).await?; 22 | let res = ListData { 23 | list, 24 | total, 25 | total_pages, 26 | page_num, 27 | }; 28 | Ok(res) 29 | } 30 | 31 | pub async fn add(arg: LoginInfoAdd) -> Result { 32 | let db = DB().await; 33 | let id = GID().await; 34 | let amodel = sys_login_info::ActiveModel { 35 | info_id: Set(id), 36 | user_name: Set(arg.user_name), 37 | uid: Set(arg.uid), 38 | device_type: Set(arg.device_type), 39 | ipaddr: Set(arg.ipaddr), 40 | login_location: Set(arg.login_location), 41 | browser: Set(arg.browser), 42 | os: Set(arg.os), 43 | status: Set(arg.status), 44 | net_work: Set(arg.net_work), 45 | msg: Set(arg.msg), 46 | login_time: Set(arg.login_time), 47 | }; 48 | amodel.insert(db).await.map_err(Into::into) 49 | } 50 | 51 | pub async fn edit(arg: LoginInfoEdit) -> Result { 52 | let db = DB().await; 53 | let rmodel = sys_login_info::Entity::find() 54 | .filter(sys_login_info::Column::InfoId.eq(arg.info_id)) 55 | .one(db) 56 | .await?; 57 | let model = if let Some(model) = rmodel { 58 | model 59 | } else { 60 | return Err("None Found".into()); 61 | }; 62 | let mut amodel: sys_login_info::ActiveModel = model.into(); 63 | if arg.device_type.is_some() { 64 | amodel.device_type = Set(arg.device_type); 65 | } 66 | if arg.ipaddr.is_some() { 67 | amodel.ipaddr = Set(arg.ipaddr); 68 | } 69 | if arg.login_location.is_some() { 70 | amodel.login_location = Set(arg.login_location); 71 | } 72 | if arg.browser.is_some() { 73 | amodel.browser = Set(arg.browser); 74 | } 75 | if arg.os.is_some() { 76 | amodel.os = Set(arg.os); 77 | } 78 | if arg.status.is_some() { 79 | amodel.status = Set(arg.status); 80 | } 81 | if arg.msg.is_some() { 82 | amodel.msg = Set(arg.msg); 83 | } 84 | if arg.login_time.is_some() { 85 | amodel.login_time = Set(arg.login_time); 86 | } 87 | if arg.net_work.is_some() { 88 | amodel.net_work = Set(arg.net_work); 89 | } 90 | amodel.update(db).await?; 91 | Ok("Success".to_string()) 92 | } 93 | pub async fn del() {} 94 | } 95 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_server_info.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::args::aserve_info::*; 2 | use axum::response::sse::{Event, Sse}; 3 | use futures::stream::{self, Stream}; 4 | use std::result::Result; 5 | use std::{convert::Infallible, time::Duration}; 6 | use sysinfo::{Networks, System}; 7 | use tokio_stream::StreamExt as _; 8 | 9 | pub async fn server_event() -> Sse>> { 10 | let stream = stream::repeat_with(move || { 11 | let r = get_oper_sys_info(); 12 | let str = serde_json::to_string(&r).unwrap_or_else(|_| "0".to_string()); 13 | Event::default().data(str.clone()) 14 | }) 15 | .map(Ok) 16 | .throttle(Duration::from_secs(2)); 17 | 18 | Sse::new(stream).keep_alive( 19 | axum::response::sse::KeepAlive::new() 20 | .interval(Duration::from_secs(2)) 21 | .text("keep-alive-text"), 22 | ) 23 | } 24 | 25 | fn get_oper_sys_info() -> SysInfo { 26 | let mut sys: System = System::new_all(); 27 | sys.refresh_all(); 28 | let pid = sysinfo::get_current_pid().expect("failed to get PID"); 29 | let server = Server { 30 | oper_sys_name: System::name().unwrap_or_else(|| "unknown".to_owned()), 31 | host_name: System::host_name().unwrap_or_else(|| "unknown".to_owned()), 32 | system_version: System::long_os_version().unwrap_or_else(|| "unknown".to_owned()), 33 | system_kerne: System::kernel_version().unwrap_or_else(|| "unknown".to_owned()), 34 | }; 35 | let process = match sys.process(pid) { 36 | Some(p) => Process { 37 | name: format!("{}", p.name().display()), 38 | used_memory: p.memory(), 39 | used_virtual_memory: p.virtual_memory(), 40 | cup_usage: p.cpu_usage(), 41 | start_time: p.start_time(), 42 | run_time: p.run_time(), 43 | disk_usage: DiskUsage { 44 | read_bytes: p.disk_usage().read_bytes, 45 | total_read_bytes: p.disk_usage().total_read_bytes, 46 | written_bytes: p.disk_usage().written_bytes, 47 | total_written_bytes: p.disk_usage().total_written_bytes, 48 | }, 49 | }, 50 | None => Process { 51 | ..Default::default() 52 | }, 53 | }; 54 | 55 | let mut network: Vec = Vec::new(); 56 | let networks = Networks::new_with_refreshed_list(); 57 | for (interface_name, data) in &networks { 58 | network.push(Network { 59 | name: interface_name.to_string(), 60 | received: data.received(), 61 | total_received: data.total_received(), 62 | transmitted: data.transmitted(), 63 | total_transmitted: data.total_transmitted(), 64 | }); 65 | } 66 | let cpus = sys.cpus(); 67 | let avg_freq: u64 = cpus.iter().map(|c| c.frequency()).sum::() / cpus.len() as u64; 68 | let cpu = Cpu { 69 | name: sys.cpus()[0].brand().to_string(), 70 | arch: std::env::consts::ARCH.to_string(), 71 | cores: System::physical_core_count() 72 | .map(|c| c.to_string()) 73 | .unwrap_or_else(|| "Unknown".to_owned()), 74 | total_use: sys.global_cpu_usage(), 75 | frequency: avg_freq, 76 | processors: sys.cpus().len(), 77 | }; 78 | let load_avg = System::load_average(); 79 | let cpu_load = CpuLoad { 80 | one: load_avg.one, 81 | five: load_avg.five, 82 | fifteen: load_avg.fifteen, 83 | }; 84 | let memory = Memory { 85 | total_memory: sys.total_memory(), 86 | used_memory: sys.used_memory(), 87 | total_swap: sys.total_swap(), 88 | used_swap: sys.used_swap(), 89 | }; 90 | SysInfo { 91 | server, 92 | cpu, 93 | memory, 94 | process, 95 | network, 96 | cpu_load, 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_dashboard.rs: -------------------------------------------------------------------------------- 1 | use crate::service::prelude::*; 2 | 3 | pub async fn analysis_total() -> impl IntoResponse { 4 | let body = json!({ 5 | "users":404, 6 | "messages": 400, 7 | "moneys": 500, 8 | "shoppings": 666 9 | }); 10 | ApiResponse::ok(body) 11 | } 12 | pub async fn user_access_source() -> impl IntoResponse { 13 | let body = json!([ 14 | { "value": 1000, "name": "analysis.directAccess" }, 15 | { "value": 310, "name": "analysis.mailMarketing" }, 16 | { "value": 234, "name": "analysis.allianceAdvertising" }, 17 | { "value": 135, "name": "analysis.videoAdvertising" }, 18 | { "value": 1548, "name": "analysis.searchEngines" } 19 | ]); 20 | ApiResponse::ok(body) 21 | } 22 | pub async fn weekly_user_activity() -> impl IntoResponse { 23 | let body = json!( [ 24 | { "value": 13253, "name": "analysis.monday" }, 25 | { "value": 34235, "name": "analysis.tuesday" }, 26 | { "value": 26321, "name": "analysis.wednesday" }, 27 | { "value": 12340, "name": "analysis.thursday" }, 28 | { "value": 24643, "name": "analysis.friday" }, 29 | { "value": 1322, "name": "analysis.saturday" }, 30 | { "value": 1324, "name": "analysis.sunday" } 31 | ] 32 | ); 33 | ApiResponse::ok(body) 34 | } 35 | pub async fn monthly_sales() -> impl IntoResponse { 36 | let body = json!( [ 37 | { "estimate": 100, "actual": 120, "name": "analysis.january" }, 38 | { "estimate": 120, "actual": 82, "name": "analysis.february" }, 39 | { "estimate": 161, "actual": 91, "name": "analysis.march" }, 40 | { "estimate": 134, "actual": 154, "name": "analysis.april" }, 41 | { "estimate": 105, "actual": 162, "name": "analysis.may" }, 42 | { "estimate": 160, "actual": 140, "name": "analysis.june" }, 43 | { "estimate": 165, "actual": 145, "name": "analysis.july" }, 44 | { "estimate": 114, "actual": 250, "name": "analysis.august" }, 45 | { "estimate": 163, "actual": 134, "name": "analysis.september" }, 46 | { "estimate": 185, "actual": 56, "name": "analysis.october" }, 47 | { "estimate": 118, "actual": 99, "name": "analysis.november" }, 48 | { "estimate": 123, "actual": 123, "name": "analysis.december" } 49 | ]); 50 | ApiResponse::ok(body) 51 | } 52 | 53 | pub async fn workplace_total() -> impl IntoResponse { 54 | let body = json!({ 55 | "project":985, 56 | "access": 8795, 57 | "todo":1879 58 | }); 59 | ApiResponse::ok(body) 60 | } 61 | 62 | pub async fn workplace_project() -> impl IntoResponse { 63 | let now = Local::now().naive_local(); 64 | let body = json!([{ 65 | "name": "Github", 66 | "icon": "akar-icons:github-fill", 67 | "message": "workplace.introduction", 68 | "personal": "fff", 69 | "time":now 70 | }]); 71 | ApiResponse::ok(body) 72 | } 73 | 74 | pub async fn workplace_dynamic() -> impl IntoResponse { 75 | let now = Local::now().naive_local(); 76 | let body = json!([{ 77 | "keys": json!(["workplace.push","message" ]), 78 | "time": now 79 | },{ 80 | "keys": json!(["workplace.push","message" ]), 81 | "time": now 82 | }]); 83 | ApiResponse::ok(body) 84 | } 85 | 86 | pub async fn workplace_team() -> impl IntoResponse { 87 | let body = json!([{ 88 | "name": "Github", 89 | "icon": "icon" 90 | },{ 91 | "name": "Vue", 92 | "icon": "icon" 93 | }]); 94 | ApiResponse::ok(body) 95 | } 96 | 97 | pub async fn workplace_radar() -> impl IntoResponse { 98 | let body = json!([{ 99 | "name": "workplace.quote", 100 | "max":65, 101 | "personal":42, 102 | "team":50 103 | },{ 104 | "name": "workplace.contribution", 105 | "max":65, 106 | "personal":42, 107 | "team":50 108 | }]); 109 | ApiResponse::ok(body) 110 | } 111 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_upload.rs: -------------------------------------------------------------------------------- 1 | use crate::service::prelude::*; 2 | use base64::{engine::general_purpose, Engine}; 3 | use tokio::{fs, io::AsyncWriteExt}; 4 | 5 | pub async fn save_base64_img(base64_data: &str) -> Result<(String, String)> { 6 | let cleaned_str = remove_prefix(base64_data); 7 | let server_config = APPCOFIG.server.clone(); 8 | let now = chrono::Local::now(); 9 | let file_path_t = format!( 10 | "{}/{}", 11 | server_config.static_dir.clone(), 12 | &now.format("%Y-%m") 13 | ); 14 | fs::create_dir_all(&file_path_t).await?; 15 | let fid = GID().await; 16 | let file_name = format!("{}_{}{}", now.format("%d"), fid, ".png"); 17 | let file_path = format!("{}/{}", file_path_t, &file_name); 18 | 19 | let decoded_data = general_purpose::STANDARD.decode(cleaned_str)?; 20 | 21 | let mut file = fs::File::create(file_path).await?; 22 | file.write_all(&decoded_data).await?; 23 | 24 | let static_dir = if server_config.static_dir.starts_with("data/") { 25 | &server_config.static_dir["data/".len()..] 26 | } else { 27 | server_config.static_dir.as_str() 28 | }; 29 | 30 | let url_path = format!( 31 | "{}/{}/{}/{}", 32 | server_config.domainname.clone(), 33 | static_dir, 34 | &now.format("%Y-%m"), 35 | &file_name 36 | ); 37 | let no_domain_path = format!("{}/{}/{}", static_dir, &now.format("%Y-%m"), &file_name); 38 | 39 | Ok((url_path, no_domain_path)) 40 | } 41 | 42 | fn remove_prefix(s: &str) -> &str { 43 | if let Some(send) = s.strip_prefix("data:image/png;base64,") { 44 | send 45 | } else { 46 | s 47 | } 48 | } 49 | pub async fn upload_file(mut multipart: Multipart) -> Result { 50 | if let Some(field) = multipart.next_field().await? { 51 | let server_config = APPCOFIG.server.clone(); 52 | let content_type = field 53 | .content_type() 54 | .map(ToString::to_string) 55 | .unwrap_or_else(|| "".to_string()); 56 | let old_url = field 57 | .file_name() 58 | .map(ToString::to_string) 59 | .unwrap_or_else(|| "".to_string()); 60 | let file_type = get_file_type(&content_type); 61 | let bytes = field.bytes().await?; 62 | let now = chrono::Local::now(); 63 | let file_path_t = format!( 64 | "{}/{}", 65 | server_config.upload_dir.clone(), 66 | &now.format("%Y-%m") 67 | ); 68 | let url_path_t = format!( 69 | "{}/{}", 70 | server_config.domainname.clone(), 71 | &now.format("%Y-%m") 72 | ); 73 | fs::create_dir_all(&file_path_t).await?; 74 | let fid = GID().await; 75 | let file_name = format!("{}-{}{}", now.format("%d"), fid, &file_type); 76 | let file_path = format!("{}/{}", file_path_t, &file_name); 77 | let url_path = format!("{}/{}", url_path_t, &file_name); 78 | let mut file = fs::File::create(&file_path).await?; 79 | file.write_all(&bytes).await?; 80 | if !old_url.is_empty() { 81 | self::delete_file(&old_url).await; 82 | } 83 | Ok(url_path) 84 | } else { 85 | Err("Failed to upload file".into()) 86 | } 87 | } 88 | pub async fn delete_file(file_path: &str) { 89 | let server_config = APPCOFIG.server.clone(); 90 | let path = file_path.replace(&server_config.domainname, &server_config.upload_dir); 91 | match fs::remove_file(&path).await { 92 | Ok(_) => {} 93 | Err(_) => { 94 | tracing::error!("File deletion failed:{}", path); 95 | } 96 | } 97 | } 98 | fn get_file_type(content_type: &str) -> String { 99 | match content_type { 100 | "image/jpeg" => ".jpg".to_string(), 101 | "image/png" => ".png".to_string(), 102 | "image/gif" => ".gif".to_string(), 103 | _ => "".to_string(), 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/midle_ware/auth.rs: -------------------------------------------------------------------------------- 1 | use super::jwt::UserInfo; 2 | use crate::service::sys::s_sys_role_api; 3 | use axum::{ 4 | body::Body, 5 | extract::OriginalUri, 6 | extract::Request, 7 | http::StatusCode, 8 | middleware::Next, 9 | response::{IntoResponse, Response}, 10 | Json, 11 | }; 12 | use http_body_util::BodyExt; 13 | use serde_json::json; 14 | use tracing::info; 15 | #[derive(Clone, Debug, Default)] 16 | pub struct ReqCtx { 17 | pub ori_uri: String, 18 | pub path: String, 19 | pub path_params: String, 20 | pub method: String, 21 | } 22 | 23 | pub async fn auth_fn_mid( 24 | req: Request, 25 | next: Next, 26 | ) -> Result { 27 | let ori_uri_path = if let Some(path) = req.extensions().get::() { 28 | path.0.path().to_owned() 29 | } else { 30 | req.uri().path().to_owned() 31 | }; 32 | let path = ori_uri_path.replacen("/api", "", 1); 33 | let method = req.method().to_string(); 34 | let path_params = req.uri().query().unwrap_or("").to_string(); 35 | 36 | let (parts, body) = req.into_parts(); 37 | 38 | let bytes = body 39 | .collect() 40 | .await 41 | .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response()) 42 | .unwrap() 43 | .to_bytes(); 44 | 45 | let req_ctx: ReqCtx = ReqCtx { 46 | ori_uri: if path_params.is_empty() { 47 | ori_uri_path 48 | } else { 49 | format!("{}?{}", ori_uri_path, path_params) 50 | }, 51 | path, 52 | path_params, 53 | method: method.to_string(), 54 | }; 55 | 56 | let mut req = Request::from_parts(parts, Body::from(bytes)); 57 | req.extensions_mut().insert(req_ctx); 58 | Ok(next.run(req).await) 59 | } 60 | 61 | pub async fn api_fn_mid(req: Request, next: Next) -> Result { 62 | let ctx = req.extensions().get::().expect("ReqCtx not found"); 63 | 64 | let user = req 65 | .extensions() 66 | .get::() 67 | .expect("UserInfo not found"); 68 | let apiauth = s_sys_role_api::check_api_permission(user.rid, &ctx.path, &ctx.method).await; 69 | if apiauth { 70 | Ok(next.run(req).await) 71 | } else { 72 | info!("没有API权限{:?} {}", user, apiauth); 73 | let body = Json(json!({ 74 | "message": "没有API权限", 75 | })); 76 | Err((StatusCode::NOT_FOUND, body.to_string())) 77 | } 78 | } 79 | 80 | pub async fn request_log_fn_mid( 81 | req: Request, 82 | next: Next, 83 | ) -> Result { 84 | // 先分离请求,避免借用冲突 85 | let (parts, body) = req.into_parts(); 86 | 87 | // 从parts中提取信息 88 | let method = parts.method.to_string(); 89 | let uri = parts.uri.clone(); 90 | let path = uri.path(); 91 | let query = uri.query().unwrap_or(""); 92 | 93 | let user_agent = parts 94 | .headers 95 | .get(axum::http::header::USER_AGENT) 96 | .map_or("", |h| h.to_str().unwrap_or("")); 97 | 98 | let content_type = parts 99 | .headers 100 | .get(axum::http::header::CONTENT_TYPE) 101 | .map_or("", |h| h.to_str().unwrap_or("")); 102 | 103 | // 读取请求体 104 | let body_bytes = axum::body::to_bytes(body, usize::MAX) 105 | .await 106 | .map_err(|e| (StatusCode::BAD_REQUEST, format!("读取请求体失败: {}", e)))?; 107 | 108 | let body_content = String::from_utf8_lossy(&body_bytes); 109 | 110 | // 记录日志 111 | tracing::info!( 112 | "http-request method:{} url:{} path:{} query:{} user_agent:{} content_type:{} body_size:{} body:{}", 113 | method, 114 | uri, 115 | path, 116 | query, 117 | user_agent, 118 | content_type, 119 | body_bytes.len(), 120 | if body_content.len() > 1000 { 121 | format!("{}...(truncated)", &body_content[..1000]) 122 | } else { 123 | body_content.to_string() 124 | } 125 | ); 126 | 127 | // 重新构建请求 128 | let rebuilt_request = Request::from_parts(parts, Body::from(body_bytes)); 129 | tracing::info!("auth end"); 130 | Ok(next.run(rebuilt_request).await) 131 | } 132 | -------------------------------------------------------------------------------- /src/model/sys/model/msys_dict_data.rs: -------------------------------------------------------------------------------- 1 | pub use super::args::asys_dict_data::*; 2 | pub use super::entity::sys_dict_data::{self, ActiveModel, Model as SysDictDataModel}; 3 | use super::entity::sys_dict_type; 4 | use crate::model::prelude::*; 5 | 6 | impl SysDictDataModel { 7 | pub async fn list(arg: PageParams, search: DictDataSearch) -> Result> { 8 | let page_num = arg.page_num.unwrap_or(1); 9 | let page_per_size = arg.page_size.unwrap_or(10); 10 | let db = DB().await; 11 | let mut rmodel = sys_dict_data::Entity::find(); 12 | 13 | rmodel = rmodel.filter(sys_dict_data::Column::DictTypeId.eq(search.dict_type_id)); 14 | if let Some(dict_label) = search.dict_label { 15 | rmodel = rmodel.filter(sys_dict_data::Column::DictLabel.eq(dict_label)); 16 | } 17 | 18 | if let Some(dict_value) = search.dict_value { 19 | rmodel = rmodel.filter(sys_dict_data::Column::DictValue.eq(dict_value)); 20 | } 21 | 22 | let total = rmodel.clone().count(db).await?; 23 | let paginator = rmodel 24 | .order_by_asc(sys_dict_data::Column::DictSort) 25 | .into_model::() 26 | .paginate(db, page_per_size); 27 | let total_pages = paginator.num_pages().await?; 28 | let list = paginator.fetch_page(page_num - 1).await?; 29 | let res = ListData { 30 | list, 31 | total, 32 | total_pages, 33 | page_num, 34 | }; 35 | Ok(res) 36 | } 37 | 38 | pub async fn add(arg: DictDataAdd) -> Result { 39 | let db = DB().await; 40 | let id = GID().await; 41 | let amodel = sys_dict_data::ActiveModel { 42 | dict_code: Set(id), 43 | dict_type_id: Set(arg.dict_type_id), 44 | dict_label: Set(arg.dict_label), 45 | dict_value: Set(arg.dict_value), 46 | dict_sort: Set(arg.dict_sort), 47 | remark: Set(arg.remark), 48 | ..Default::default() 49 | }; 50 | amodel.insert(db).await?; 51 | Ok("Success".to_string()) 52 | } 53 | 54 | pub async fn edit(arg: DictDataEdit) -> Result { 55 | let db = DB().await; 56 | let rmodel = sys_dict_data::Entity::find_by_id(arg.dict_code) 57 | .one(db) 58 | .await?; 59 | let mut amodel: sys_dict_data::ActiveModel = if let Some(r) = rmodel { 60 | r.into() 61 | } else { 62 | return Err("dict data not found".into()); 63 | }; 64 | amodel.dict_type_id = Set(arg.dict_type_id); 65 | amodel.dict_label = Set(arg.dict_label); 66 | amodel.dict_value = Set(arg.dict_value); 67 | amodel.dict_sort = Set(arg.dict_sort); 68 | amodel.remark = Set(arg.remark); 69 | amodel.update(db).await?; 70 | Ok("Success".to_string()) 71 | } 72 | pub async fn del(arg: DictDataDel) -> Result { 73 | let db = DB().await; 74 | sys_dict_data::Entity::delete_by_id(arg.dict_code) 75 | .exec(db) 76 | .await?; 77 | Ok("Success".to_string()) 78 | } 79 | 80 | pub async fn get_by_type(arg: DictDataType) -> Result> { 81 | let db = DB().await; 82 | let mut rmodel = sys_dict_data::Entity::find(); 83 | rmodel = rmodel.join_rev( 84 | JoinType::LeftJoin, 85 | sys_dict_type::Entity::belongs_to(sys_dict_data::Entity) 86 | .from(sys_dict_type::Column::DictId) 87 | .to(sys_dict_data::Column::DictTypeId) 88 | .into(), 89 | ); 90 | let rmodels = rmodel 91 | .filter(sys_dict_type::Column::DictType.eq(arg.dict_type)) 92 | .order_by_asc(sys_dict_data::Column::DictSort) 93 | .into_model::() 94 | .all(db) 95 | .await?; 96 | Ok(rmodels) 97 | } 98 | 99 | pub async fn delete_by_dict_type(dict_id: i64, db: &DatabaseTransaction) -> Result { 100 | sys_dict_data::Entity::delete_many() 101 | .filter(sys_dict_data::Column::DictTypeId.eq(dict_id)) 102 | .exec(db) 103 | .await?; 104 | Ok("Success".to_string()) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/common/ser.rs: -------------------------------------------------------------------------------- 1 | pub mod i64_to_string { 2 | use serde::{self, Deserialize, Deserializer, Serializer}; 3 | use std::str::FromStr; 4 | 5 | pub fn serialize(value: &i64, serializer: S) -> Result 6 | where 7 | S: Serializer, 8 | { 9 | serializer.serialize_str(&value.to_string()) 10 | } 11 | 12 | pub fn deserialize<'de, D>(deserializer: D) -> Result 13 | where 14 | D: Deserializer<'de>, 15 | { 16 | let s = String::deserialize(deserializer)?; 17 | i64::from_str(&s).map_err(serde::de::Error::custom) 18 | } 19 | } 20 | 21 | pub mod option_string_or_i64 { 22 | use serde::{self, Deserialize, Deserializer, Serializer}; 23 | use std::str::FromStr; 24 | 25 | pub fn serialize(value: &Option, serializer: S) -> Result 26 | where 27 | S: Serializer, 28 | { 29 | match value { 30 | Some(v) => serializer.serialize_str(&v.to_string()), 31 | None => serializer.serialize_none(), 32 | } 33 | } 34 | 35 | pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> 36 | where 37 | D: Deserializer<'de>, 38 | { 39 | let s = Option::::deserialize(deserializer)?; 40 | s.map(|s| i64::from_str(&s).map_err(serde::de::Error::custom)) 41 | .transpose() 42 | } 43 | } 44 | 45 | pub mod veci64_to_vecstring { 46 | use serde::{self, Deserialize, Deserializer, Serializer}; 47 | #[warn(clippy::ptr_arg)] 48 | pub fn serialize(values: &[i64], serializer: S) -> Result 49 | where 50 | S: Serializer, 51 | { 52 | let serialized = values.iter().map(|v| v.to_string()).collect::>(); 53 | serializer.collect_seq(serialized) 54 | } 55 | 56 | pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> 57 | where 58 | D: Deserializer<'de>, 59 | { 60 | let s: Vec = Vec::deserialize(deserializer)?; 61 | s.into_iter() 62 | .map(|item| item.parse().map_err(serde::de::Error::custom)) 63 | .collect() 64 | } 65 | } 66 | 67 | pub mod option_veci64_to_vecstring { 68 | use serde::{self, Deserialize, Deserializer, Serializer}; 69 | #[warn(clippy::ptr_arg)] 70 | pub fn serialize(values: &Option>, serializer: S) -> Result 71 | where 72 | S: Serializer, 73 | { 74 | match values { 75 | Some(val) => { 76 | let serialized = val.iter().map(|v| v.to_string()).collect::>(); 77 | serializer.collect_seq(serialized) 78 | } 79 | None => serializer.serialize_none(), 80 | } 81 | } 82 | 83 | pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> 84 | where 85 | D: Deserializer<'de>, 86 | { 87 | let s: Option> = Option::>::deserialize(deserializer)?; 88 | match s { 89 | Some(strings) => { 90 | let mut int_vec = Vec::with_capacity(strings.len()); 91 | for string in strings { 92 | if let Ok(num) = string.parse::() { 93 | int_vec.push(num); 94 | } else { 95 | return Err(serde::de::Error::custom(format!("Failed to parse string to i64: {}", string))); 96 | } 97 | } 98 | Ok(Some(int_vec)) 99 | } 100 | None => Ok(None), 101 | } 102 | } 103 | } 104 | 105 | pub mod string_to_bool { 106 | use serde::{self, Deserialize, Deserializer, Serializer}; 107 | pub fn serialize(value: &str, serializer: S) -> Result 108 | where 109 | S: Serializer, 110 | { 111 | match value { 112 | "0" => serializer.serialize_bool(false), 113 | _ => serializer.serialize_bool(true), 114 | } 115 | } 116 | pub fn deserialize<'de, D>(deserializer: D) -> Result 117 | where 118 | D: Deserializer<'de>, 119 | { 120 | let s = bool::deserialize(deserializer)?; 121 | if s { 122 | Ok("1".to_string()) 123 | } else { 124 | Ok("0".to_string()) 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/worker/common/worker.rs: -------------------------------------------------------------------------------- 1 | use crate::common::error::Result; 2 | use async_trait::async_trait; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_json::Value as JsonValue; 5 | use std::future::Future; 6 | use std::pin::Pin; 7 | use std::sync::Arc; 8 | 9 | use super::WorkerOpts; 10 | 11 | #[async_trait] 12 | pub trait Worker: Send + Sync { 13 | fn disable_argument_coercion(&self) -> bool { 14 | false 15 | } 16 | 17 | #[must_use] 18 | fn opts() -> WorkerOpts 19 | where 20 | Self: Sized, 21 | { 22 | WorkerOpts::new() 23 | } 24 | fn max_retries(&self) -> usize { 25 | 25 26 | } 27 | #[must_use] 28 | fn class_name() -> String 29 | where 30 | Self: Sized, 31 | { 32 | use convert_case::{Case, Casing}; 33 | 34 | let type_name = std::any::type_name::(); 35 | let name = type_name.split("::").last().unwrap_or(type_name); 36 | name.to_case(Case::UpperCamel) 37 | } 38 | 39 | async fn perform_async(args: Args) -> Result<()> 40 | where 41 | Self: Sized, 42 | Args: Send + Sync + Serialize + 'static, 43 | { 44 | Self::opts().perform_async(args).await 45 | } 46 | 47 | async fn perform_in_seconds(seconds: u64, args: Args) -> Result<()> 48 | where 49 | Self: Sized, 50 | Args: Send + Sync + Serialize + 'static, 51 | { 52 | let duration = std::time::Duration::from_secs(seconds); 53 | Self::perform_in(duration, args).await 54 | } 55 | 56 | async fn perform_in_milliseconds(milliseconds: u64, args: Args) -> Result<()> 57 | where 58 | Self: Sized, 59 | Args: Send + Sync + Serialize + 'static, 60 | { 61 | let duration = std::time::Duration::from_millis(milliseconds); 62 | Self::perform_in(duration, args).await 63 | } 64 | 65 | async fn perform_in_minutes(minutes: u64, args: Args) -> Result<()> 66 | where 67 | Self: Sized, 68 | Args: Send + Sync + Serialize + 'static, 69 | { 70 | let duration = std::time::Duration::from_secs(minutes * 60); 71 | Self::perform_in(duration, args).await 72 | } 73 | 74 | async fn perform_in(duration: std::time::Duration, args: Args) -> Result<()> 75 | where 76 | Self: Sized, 77 | Args: Send + Sync + Serialize + 'static, 78 | { 79 | Self::opts().perform_in(duration, args).await 80 | } 81 | 82 | async fn perform(&self, args: Args) -> Result<()>; 83 | } 84 | 85 | #[derive(Clone)] 86 | pub struct WorkerRef { 87 | #[allow(clippy::type_complexity)] 88 | work_fn: Arc< 89 | Box Pin> + Send>> + Send + Sync>, 90 | >, 91 | max_retries: usize, 92 | } 93 | 94 | async fn invoke_worker(args: JsonValue, worker: Arc) -> Result<()> 95 | where 96 | Args: Send + Sync + 'static, 97 | W: Worker + 'static, 98 | for<'de> Args: Deserialize<'de>, 99 | { 100 | let args = if worker.disable_argument_coercion() { 101 | args 102 | } else if std::any::TypeId::of::() == std::any::TypeId::of::<()>() { 103 | JsonValue::Null 104 | } else { 105 | match args { 106 | JsonValue::Array(mut arr) if arr.len() == 1 => { 107 | arr.pop().expect("value change after size check") 108 | } 109 | _ => args, 110 | } 111 | }; 112 | 113 | let args: Args = serde_json::from_value(args)?; 114 | worker.perform(args).await 115 | } 116 | 117 | impl WorkerRef { 118 | pub(crate) fn wrap(worker: Arc) -> Self 119 | where 120 | Args: Send + Sync + 'static, 121 | W: Worker + 'static, 122 | for<'de> Args: Deserialize<'de>, 123 | { 124 | Self { 125 | work_fn: Arc::new(Box::new({ 126 | let worker = worker.clone(); 127 | move |args: JsonValue| { 128 | let worker = worker.clone(); 129 | Box::pin(async move { invoke_worker(args, worker).await }) 130 | } 131 | })), 132 | max_retries: worker.max_retries(), 133 | } 134 | } 135 | 136 | #[must_use] 137 | pub fn max_retries(&self) -> usize { 138 | self.max_retries 139 | } 140 | 141 | pub async fn call(&self, args: JsonValue) -> Result<()> { 142 | (Arc::clone(&self.work_fn))(args).await 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/model/sys/model/msys_job.rs: -------------------------------------------------------------------------------- 1 | pub use super::args::asys_job::*; 2 | pub use super::entity::sys_job::{self, ActiveModel, Model as SysJobModel}; 3 | use crate::model::prelude::*; 4 | use crate::worker::job::{JobMsg, JobWorker}; 5 | use crate::worker::AppWorker; 6 | impl SysJobModel { 7 | pub async fn find_by_id(jid: i64) -> Result { 8 | let db = DB().await; 9 | let rmodel = sys_job::Entity::find_by_id(jid) 10 | .into_model::() 11 | .one(db) 12 | .await? 13 | .ok_or_else(|| Error::Message("Not found".to_string()))?; 14 | Ok(rmodel) 15 | } 16 | pub async fn updata_run_count(jid: i64) -> Result { 17 | let db = DB().await; 18 | let rmodel = sys_job::Entity::find_by_id(jid).one(db).await?; 19 | let model = if let Some(r) = rmodel { 20 | r 21 | } else { 22 | return Err("Not found".into()); 23 | }; 24 | 25 | let ncount = model.run_count + 1; 26 | let mut amodel: sys_job::ActiveModel = model.into(); 27 | 28 | amodel.run_count = Set(ncount); 29 | amodel.update(db).await?; 30 | Ok(ncount) 31 | } 32 | 33 | pub async fn list(arg: PageParams) -> Result> { 34 | let page_num = arg.page_num.unwrap_or(1); 35 | let page_per_size = arg.page_size.unwrap_or(10); 36 | 37 | let db = DB().await; 38 | let rmodel = sys_job::Entity::find(); 39 | 40 | let total = rmodel.clone().count(db).await.unwrap(); 41 | let paginator = rmodel 42 | .order_by_asc(sys_job::Column::CreatedAt) 43 | .into_model::() 44 | .paginate(db, page_per_size); 45 | let total_pages = paginator.num_pages().await?; 46 | let list = paginator.fetch_page(page_num - 1).await?; 47 | let res = ListData { 48 | list, 49 | total, 50 | total_pages, 51 | page_num, 52 | }; 53 | Ok(res) 54 | } 55 | 56 | pub async fn all_job() -> Result> { 57 | let db = DB().await; 58 | let mut rmodel = sys_job::Entity::find(); 59 | rmodel = rmodel.order_by_asc(sys_job::Column::UpdatedAt); 60 | rmodel = rmodel.filter(sys_job::Column::Status.eq("0")); 61 | let list = rmodel.into_model::().all(db).await?; 62 | Ok(list) 63 | } 64 | 65 | pub async fn add(arg: JobAdd) -> Result { 66 | let db = DB().await; 67 | let id = GID().await; 68 | let amode = sys_job::ActiveModel { 69 | job_id: Set(id), 70 | job_name: Set(arg.job_name), 71 | job_group: Set(arg.job_group), 72 | task_type: Set(arg.task_type), 73 | task_count: Set(arg.task_count), 74 | run_count: Set(arg.run_count), 75 | job_params: Set(arg.job_params), 76 | cron_expression: Set(arg.cron_expression), 77 | status: Set(arg.status), 78 | remark: Set(arg.remark), 79 | ..Default::default() 80 | }; 81 | amode.insert(db).await?; 82 | Ok("Success".to_string()) 83 | } 84 | pub async fn del(arg: JobDel) -> Result { 85 | let db = DB().await; 86 | let delresult = sys_job::Entity::delete_by_id(arg.job_id).exec(db).await?; 87 | if delresult.rows_affected > 0 { 88 | Ok("Success".to_string()) 89 | } else { 90 | Err("Failed".into()) 91 | } 92 | } 93 | pub async fn edit(arg: JobEdit) -> Result { 94 | let db = DB().await; 95 | let rmodel = sys_job::Entity::find_by_id(arg.job_id).one(db).await?; 96 | let model = if let Some(r) = rmodel { 97 | r 98 | } else { 99 | return Err("Not found".into()); 100 | }; 101 | let now = Local::now().naive_local(); 102 | let mut amodel: sys_job::ActiveModel = model.into(); 103 | amodel.job_group = Set(arg.job_group); 104 | amodel.job_name = Set(arg.job_name); 105 | amodel.task_type = Set(arg.task_type); 106 | amodel.task_count = Set(arg.task_count); 107 | amodel.run_count = Set(arg.run_count); 108 | amodel.job_params = Set(arg.job_params); 109 | amodel.cron_expression = Set(arg.cron_expression); 110 | amodel.status = Set(arg.status.clone()); 111 | amodel.remark = Set(arg.remark); 112 | amodel.updated_at = Set(now); 113 | 114 | let jobmss = JobMsg { job_id: arg.job_id }; 115 | let _ = JobWorker::execute_async(jobmss).await; 116 | 117 | amodel.update(db).await?; 118 | Ok("Success".to_string()) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/model/sys/model/msys_api_permission.rs: -------------------------------------------------------------------------------- 1 | pub use super::args::asys_api_permission::*; 2 | pub use super::entity::sys_api_permission::{self, ActiveModel, Model as SysApiPermissionModel}; 3 | use crate::model::prelude::*; 4 | 5 | impl SysApiPermissionModel { 6 | pub async fn list( 7 | arg: PageParams, 8 | search: ApiPermissionSearch, 9 | ) -> Result> { 10 | let page_num = arg.page_num.unwrap_or(1); 11 | let page_per_size = arg.page_size.unwrap_or(10); 12 | let db = DB().await; 13 | let mut rmodel = sys_api_permission::Entity::find(); 14 | 15 | if let Some(api) = search.api { 16 | rmodel = rmodel.filter(sys_api_permission::Column::Api.contains(api)); 17 | } 18 | 19 | if let Some(apiname) = search.apiname { 20 | rmodel = rmodel.filter(sys_api_permission::Column::Apiname.contains(apiname)); 21 | } 22 | if let Some(method) = search.method { 23 | rmodel = rmodel.filter(sys_api_permission::Column::Method.eq(method)); 24 | } 25 | let total = rmodel.clone().count(db).await?; 26 | let paginator = rmodel 27 | .order_by_asc(sys_api_permission::Column::Sort) 28 | .into_model::() 29 | .paginate(db, page_per_size); 30 | let total_pages = paginator.num_pages().await?; 31 | let list = paginator.fetch_page(page_num - 1).await?; 32 | let res = ListData { 33 | list, 34 | total, 35 | total_pages, 36 | page_num, 37 | }; 38 | Ok(res) 39 | } 40 | 41 | pub async fn edit(arg: ApiPermissionEdit) -> Result { 42 | let db = DB().await; 43 | let rmodel = sys_api_permission::Entity::find_by_id(arg.id) 44 | .one(db) 45 | .await?; 46 | 47 | let mut amodel: sys_api_permission::ActiveModel = if let Some(r) = rmodel { 48 | r.into() 49 | } else { 50 | return Err("role not found".into()); 51 | }; 52 | amodel.logcache = Set(arg.logcache); 53 | amodel.sort = Set(arg.sort); 54 | amodel.remark = Set(arg.remark); 55 | amodel.update(db).await?; 56 | Ok("Success".to_string()) 57 | } 58 | 59 | pub async fn find_by_id(id: i64) -> Result> { 60 | let db = DB().await; 61 | sys_api_permission::Entity::find_by_id(id) 62 | .one(db) 63 | .await 64 | .map_err(Into::into) 65 | } 66 | 67 | pub async fn add_or_update( 68 | arg: ApiPermissionAdd, 69 | db: &DatabaseTransaction, 70 | ) -> Result { 71 | let existing = sys_api_permission::Entity::find() 72 | .filter(sys_api_permission::Column::Api.eq(arg.api.clone())) 73 | .one(db) 74 | .await?; 75 | let model = if let Some(m) = existing { 76 | let mut amodel: sys_api_permission::ActiveModel = m.into(); 77 | amodel.api = Set(arg.api); 78 | amodel.method = Set(arg.method); 79 | amodel.apiname = Set(arg.apiname); 80 | sys_api_permission::Entity::update(amodel).exec(db).await? 81 | } else { 82 | let id = GID().await; 83 | let now = Local::now().naive_local(); 84 | let amode = sys_api_permission::ActiveModel { 85 | id: Set(id), 86 | api: Set(arg.api), 87 | sort: Set(arg.sort), 88 | method: Set(arg.method), 89 | apiname: Set(arg.apiname), 90 | logcache: Set('0'.to_string()), 91 | created_at: Set(now), 92 | remark: Set(None), 93 | }; 94 | sys_api_permission::Entity::insert(amode) 95 | .exec_with_returning(db) 96 | .await? 97 | }; 98 | Ok(ApiPermissionRes { 99 | id: model.id, 100 | api: model.api, 101 | method: model.method, 102 | apiname: model.apiname, 103 | sort: model.sort, 104 | logcache: model.logcache, 105 | remark: model.remark, 106 | }) 107 | } 108 | pub async fn delete_all() -> Result { 109 | let db = DB().await; 110 | sys_api_permission::Entity::delete_many().exec(db).await?; 111 | Ok("Success".to_string()) 112 | } 113 | 114 | pub async fn all_apis() -> Result> { 115 | let db = DB().await; 116 | sys_api_permission::Entity::find() 117 | .all(db) 118 | .await 119 | .map_err(Into::into) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/service/sys/s_sys_menu.rs: -------------------------------------------------------------------------------- 1 | use crate::model::sys::model::msys_menu::{ 2 | ListMeta, MenuAdd, MenuDel, MenuEdit, MenuResp, RouteMeta, SysMenuListTree, SysMenuModel, 3 | SysMenuRouterTree as SysMenuRouteTree, UserMenu, 4 | }; 5 | use crate::service::prelude::*; 6 | 7 | pub async fn list(uinfo: UserInfo) -> impl IntoResponse { 8 | let menus: Vec = SysMenuModel::get_menus(false, false, true, uinfo.rid) 9 | .await 10 | .unwrap(); 11 | let menu_data = self::get_menu_list_data(menus); 12 | let menu_tree = self::get_menu_list_tree(menu_data, 0); 13 | ApiResponse::ok(menu_tree) 14 | } 15 | 16 | pub async fn edit(VJson(arg): VJson) -> impl IntoResponse { 17 | let umodel = SysMenuModel::edit(arg).await; 18 | ApiResponse::from_result(umodel) 19 | } 20 | pub async fn add(VJson(arg): VJson) -> impl IntoResponse { 21 | let amodel = SysMenuModel::add(arg).await; 22 | ApiResponse::from_result(amodel) 23 | } 24 | pub async fn delete(VQuery(arg): VQuery) -> impl IntoResponse { 25 | let dmodel = SysMenuModel::del(arg).await; 26 | ApiResponse::from_result(dmodel) 27 | } 28 | pub async fn tree(uinfo: UserInfo) -> impl IntoResponse { 29 | let menus = SysMenuModel::get_menus(true, false, true, uinfo.rid) 30 | .await 31 | .unwrap(); 32 | let menu_data = self::get_menu_list_data(menus); 33 | let menu_tree = self::get_menu_list_tree(menu_data, 0); 34 | ApiResponse::ok(menu_tree) 35 | } 36 | 37 | pub async fn all_router(uinfo: UserInfo) -> impl IntoResponse { 38 | let menus: Vec = SysMenuModel::get_menus(true, false, true, uinfo.rid) 39 | .await 40 | .unwrap(); 41 | info!("{:?}", menus.len()); 42 | let menu_data = self::get_menu_route_data(menus); 43 | 44 | let menu_tree = self::get_menu_route_tree(menu_data, 0); 45 | 46 | ApiResponse::ok(menu_tree) 47 | } 48 | 49 | fn get_menu_list_tree(user_menus: Vec, pid: i64) -> Vec { 50 | let mut menu_tree: Vec = Vec::new(); 51 | for mut user_menu in user_menus.clone() { 52 | if user_menu.pid == pid { 53 | user_menu.children = Some(get_menu_list_tree(user_menus.clone(), user_menu.id)); 54 | menu_tree.push(user_menu.clone()); 55 | } 56 | } 57 | menu_tree 58 | } 59 | fn get_menu_list_data(menus: Vec) -> Vec { 60 | let mut menu_res: Vec = Vec::new(); 61 | for menu in menus { 62 | let meta = ListMeta { 63 | icon: menu.icon.clone(), 64 | title: menu.title.clone(), 65 | no_cache: menu.no_cache, 66 | hidden: menu.hidden, 67 | active_menu: menu.active_menu, 68 | breadcrumb: menu.breadcrumb, 69 | always_show: menu.always_show, 70 | affix: menu.affix, 71 | can_to: menu.can_to, 72 | no_tags_view: menu.no_tags_view, 73 | i18nkey: menu.i18nkey, 74 | }; 75 | let menu_tree = SysMenuListTree { 76 | meta, 77 | id: menu.id, 78 | pid: menu.pid, 79 | path: menu.path, 80 | order: menu.order, 81 | redirect: menu.redirect, 82 | menu_type: menu.menu_type, 83 | status: menu.status, 84 | component: menu.component, 85 | name: menu.name, 86 | ..Default::default() 87 | }; 88 | menu_res.push(menu_tree); 89 | } 90 | menu_res 91 | } 92 | 93 | fn get_menu_route_data(menus: Vec) -> Vec { 94 | let mut menu_res: Vec = Vec::new(); 95 | for menu in menus { 96 | let meta = RouteMeta { 97 | icon: menu.icon.clone(), 98 | title: menu.title.clone(), 99 | no_cache: menu.no_cache, 100 | href: menu.href, 101 | hidden: menu.hidden, 102 | active_menu: menu.active_menu, 103 | breadcrumb: menu.breadcrumb, 104 | always_show: menu.always_show, 105 | affix: menu.affix, 106 | can_to: menu.can_to, 107 | no_tags_view: menu.no_tags_view, 108 | i18nkey: menu.i18nkey, 109 | }; 110 | let user_menu = UserMenu { 111 | meta, 112 | id: menu.id, 113 | pid: menu.pid, 114 | path: menu.path.clone(), 115 | title: menu.title.clone(), 116 | order: menu.order, 117 | redirect: menu.redirect, 118 | menu_type: menu.menu_type, 119 | status: menu.status, 120 | component: menu.component.clone(), 121 | name: menu.name, 122 | }; 123 | let menu_tree = SysMenuRouteTree { 124 | user_menu, 125 | ..Default::default() 126 | }; 127 | menu_res.push(menu_tree); 128 | } 129 | menu_res 130 | } 131 | 132 | fn get_menu_route_tree(user_menus: Vec, pid: i64) -> Vec { 133 | let mut menu_tree: Vec = Vec::new(); 134 | for mut user_menu in user_menus.clone() { 135 | if user_menu.user_menu.pid == pid { 136 | user_menu.children = Some(get_menu_route_tree( 137 | user_menus.clone(), 138 | user_menu.user_menu.id, 139 | )); 140 | 141 | menu_tree.push(user_menu.clone()); 142 | } 143 | } 144 | menu_tree 145 | } 146 | --------------------------------------------------------------------------------