chore: apply cargo fmt across workspace and update docs

- Run cargo fmt on all Rust crates for consistent formatting
- Update CLAUDE.md with WASM plugin commands and dev.ps1 instructions
- Update wiki: add WASM plugin architecture, rewrite dev environment docs
- Minor frontend cleanup (unused imports)
This commit is contained in:
iven
2026-04-15 00:49:20 +08:00
parent e16c1a85d7
commit 9568dd7875
113 changed files with 4355 additions and 937 deletions

View File

@@ -29,6 +29,7 @@ mod m20260413_000026_create_audit_logs;
mod m20260414_000027_fix_unique_indexes_soft_delete;
mod m20260414_000028_add_standard_fields_to_tokens;
mod m20260414_000029_add_standard_fields_to_process_variables;
mod m20260414_000032_fix_settings_unique_index_null;
mod m20260415_000030_add_version_to_message_tables;
mod m20260416_000031_create_domain_events;
@@ -69,6 +70,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260414_000029_add_standard_fields_to_process_variables::Migration),
Box::new(m20260415_000030_add_version_to_message_tables::Migration),
Box::new(m20260416_000031_create_domain_events::Migration),
Box::new(m20260414_000032_fix_settings_unique_index_null::Migration),
]
}
}

View File

@@ -11,12 +11,7 @@ impl MigrationTrait for Migration {
Table::create()
.table(Tenant::Table)
.if_not_exists()
.col(
ColumnDef::new(Tenant::Id)
.uuid()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Tenant::Id).uuid().not_null().primary_key())
.col(ColumnDef::new(Tenant::Name).string().not_null())
.col(
ColumnDef::new(Tenant::Code)

View File

@@ -11,12 +11,7 @@ impl MigrationTrait for Migration {
Table::create()
.table(Users::Table)
.if_not_exists()
.col(
ColumnDef::new(Users::Id)
.uuid()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Users::Id).uuid().not_null().primary_key())
.col(ColumnDef::new(Users::TenantId).uuid().not_null())
.col(ColumnDef::new(Users::Username).string().not_null())
.col(ColumnDef::new(Users::Email).string().null())

View File

@@ -25,7 +25,11 @@ impl MigrationTrait for Migration {
.not_null()
.default("password"),
)
.col(ColumnDef::new(UserCredentials::CredentialData).json().null())
.col(
ColumnDef::new(UserCredentials::CredentialData)
.json()
.null(),
)
.col(
ColumnDef::new(UserCredentials::Verified)
.boolean()

View File

@@ -11,12 +11,7 @@ impl MigrationTrait for Migration {
Table::create()
.table(Roles::Table)
.if_not_exists()
.col(
ColumnDef::new(Roles::Id)
.uuid()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Roles::Id).uuid().not_null().primary_key())
.col(ColumnDef::new(Roles::TenantId).uuid().not_null())
.col(ColumnDef::new(Roles::Name).string().not_null())
.col(ColumnDef::new(Roles::Code).string().not_null())

View File

@@ -12,7 +12,11 @@ impl MigrationTrait for Migration {
.table(RolePermissions::Table)
.if_not_exists()
.col(ColumnDef::new(RolePermissions::RoleId).uuid().not_null())
.col(ColumnDef::new(RolePermissions::PermissionId).uuid().not_null())
.col(
ColumnDef::new(RolePermissions::PermissionId)
.uuid()
.not_null(),
)
.col(ColumnDef::new(RolePermissions::TenantId).uuid().not_null())
.col(
ColumnDef::new(RolePermissions::CreatedAt)

View File

@@ -18,7 +18,11 @@ impl MigrationTrait for Migration {
.primary_key(),
)
.col(ColumnDef::new(DictionaryItems::TenantId).uuid().not_null())
.col(ColumnDef::new(DictionaryItems::DictionaryId).uuid().not_null())
.col(
ColumnDef::new(DictionaryItems::DictionaryId)
.uuid()
.not_null(),
)
.col(ColumnDef::new(DictionaryItems::Label).string().not_null())
.col(ColumnDef::new(DictionaryItems::Value).string().not_null())
.col(

View File

@@ -11,12 +11,7 @@ impl MigrationTrait for Migration {
Table::create()
.table(Menus::Table)
.if_not_exists()
.col(
ColumnDef::new(Menus::Id)
.uuid()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Menus::Id).uuid().not_null().primary_key())
.col(ColumnDef::new(Menus::TenantId).uuid().not_null())
.col(ColumnDef::new(Menus::ParentId).uuid().null())
.col(ColumnDef::new(Menus::Title).string().not_null())

View File

@@ -11,12 +11,7 @@ impl MigrationTrait for Migration {
Table::create()
.table(Settings::Table)
.if_not_exists()
.col(
ColumnDef::new(Settings::Id)
.uuid()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Settings::Id).uuid().not_null().primary_key())
.col(ColumnDef::new(Settings::TenantId).uuid().not_null())
.col(ColumnDef::new(Settings::Scope).string().not_null())
.col(ColumnDef::new(Settings::ScopeId).uuid().null())

View File

@@ -17,7 +17,11 @@ impl MigrationTrait for Migration {
.not_null()
.primary_key(),
)
.col(ColumnDef::new(ProcessDefinitions::TenantId).uuid().not_null())
.col(
ColumnDef::new(ProcessDefinitions::TenantId)
.uuid()
.not_null(),
)
.col(ColumnDef::new(ProcessDefinitions::Name).string().not_null())
.col(ColumnDef::new(ProcessDefinitions::Key).string().not_null())
.col(
@@ -27,7 +31,11 @@ impl MigrationTrait for Migration {
.default(1),
)
.col(ColumnDef::new(ProcessDefinitions::Category).string().null())
.col(ColumnDef::new(ProcessDefinitions::Description).text().null())
.col(
ColumnDef::new(ProcessDefinitions::Description)
.text()
.null(),
)
.col(
ColumnDef::new(ProcessDefinitions::Nodes)
.json_binary()
@@ -58,8 +66,16 @@ impl MigrationTrait for Migration {
.not_null()
.default(Expr::current_timestamp()),
)
.col(ColumnDef::new(ProcessDefinitions::CreatedBy).uuid().not_null())
.col(ColumnDef::new(ProcessDefinitions::UpdatedBy).uuid().not_null())
.col(
ColumnDef::new(ProcessDefinitions::CreatedBy)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(ProcessDefinitions::UpdatedBy)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(ProcessDefinitions::DeletedAt)
.timestamp_with_time_zone()

View File

@@ -18,15 +18,27 @@ impl MigrationTrait for Migration {
.primary_key(),
)
.col(ColumnDef::new(ProcessInstances::TenantId).uuid().not_null())
.col(ColumnDef::new(ProcessInstances::DefinitionId).uuid().not_null())
.col(ColumnDef::new(ProcessInstances::BusinessKey).string().null())
.col(
ColumnDef::new(ProcessInstances::DefinitionId)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(ProcessInstances::BusinessKey)
.string()
.null(),
)
.col(
ColumnDef::new(ProcessInstances::Status)
.string()
.not_null()
.default("running"),
)
.col(ColumnDef::new(ProcessInstances::StartedBy).uuid().not_null())
.col(
ColumnDef::new(ProcessInstances::StartedBy)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(ProcessInstances::StartedAt)
.timestamp_with_time_zone()
@@ -50,8 +62,16 @@ impl MigrationTrait for Migration {
.not_null()
.default(Expr::current_timestamp()),
)
.col(ColumnDef::new(ProcessInstances::CreatedBy).uuid().not_null())
.col(ColumnDef::new(ProcessInstances::UpdatedBy).uuid().not_null())
.col(
ColumnDef::new(ProcessInstances::CreatedBy)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(ProcessInstances::UpdatedBy)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(ProcessInstances::DeletedAt)
.timestamp_with_time_zone()

View File

@@ -11,12 +11,7 @@ impl MigrationTrait for Migration {
Table::create()
.table(Tokens::Table)
.if_not_exists()
.col(
ColumnDef::new(Tokens::Id)
.uuid()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Tokens::Id).uuid().not_null().primary_key())
.col(ColumnDef::new(Tokens::TenantId).uuid().not_null())
.col(ColumnDef::new(Tokens::InstanceId).uuid().not_null())
.col(ColumnDef::new(Tokens::NodeId).string().not_null())

View File

@@ -11,12 +11,7 @@ impl MigrationTrait for Migration {
Table::create()
.table(Tasks::Table)
.if_not_exists()
.col(
ColumnDef::new(Tasks::Id)
.uuid()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Tasks::Id).uuid().not_null().primary_key())
.col(ColumnDef::new(Tasks::TenantId).uuid().not_null())
.col(ColumnDef::new(Tasks::InstanceId).uuid().not_null())
.col(ColumnDef::new(Tasks::TokenId).uuid().not_null())

View File

@@ -18,7 +18,11 @@ impl MigrationTrait for Migration {
.primary_key(),
)
.col(ColumnDef::new(ProcessVariables::TenantId).uuid().not_null())
.col(ColumnDef::new(ProcessVariables::InstanceId).uuid().not_null())
.col(
ColumnDef::new(ProcessVariables::InstanceId)
.uuid()
.not_null(),
)
.col(ColumnDef::new(ProcessVariables::Name).string().not_null())
.col(
ColumnDef::new(ProcessVariables::VarType)
@@ -27,8 +31,16 @@ impl MigrationTrait for Migration {
.default("string"),
)
.col(ColumnDef::new(ProcessVariables::ValueString).text().null())
.col(ColumnDef::new(ProcessVariables::ValueNumber).double().null())
.col(ColumnDef::new(ProcessVariables::ValueBoolean).boolean().null())
.col(
ColumnDef::new(ProcessVariables::ValueNumber)
.double()
.null(),
)
.col(
ColumnDef::new(ProcessVariables::ValueBoolean)
.boolean()
.null(),
)
.col(
ColumnDef::new(ProcessVariables::ValueDate)
.timestamp_with_time_zone()

View File

@@ -18,16 +18,8 @@ impl MigrationTrait for Migration {
.primary_key(),
)
.col(ColumnDef::new(MessageTemplates::TenantId).uuid().not_null())
.col(
ColumnDef::new(MessageTemplates::Name)
.string()
.not_null(),
)
.col(
ColumnDef::new(MessageTemplates::Code)
.string()
.not_null(),
)
.col(ColumnDef::new(MessageTemplates::Name).string().not_null())
.col(ColumnDef::new(MessageTemplates::Code).string().not_null())
.col(
ColumnDef::new(MessageTemplates::Channel)
.string()
@@ -50,11 +42,31 @@ impl MigrationTrait for Migration {
.not_null()
.default("zh-CN"),
)
.col(ColumnDef::new(MessageTemplates::CreatedAt).timestamp_with_time_zone().not_null())
.col(ColumnDef::new(MessageTemplates::UpdatedAt).timestamp_with_time_zone().not_null())
.col(ColumnDef::new(MessageTemplates::CreatedBy).uuid().not_null())
.col(ColumnDef::new(MessageTemplates::UpdatedBy).uuid().not_null())
.col(ColumnDef::new(MessageTemplates::DeletedAt).timestamp_with_time_zone().null())
.col(
ColumnDef::new(MessageTemplates::CreatedAt)
.timestamp_with_time_zone()
.not_null(),
)
.col(
ColumnDef::new(MessageTemplates::UpdatedAt)
.timestamp_with_time_zone()
.not_null(),
)
.col(
ColumnDef::new(MessageTemplates::CreatedBy)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(MessageTemplates::UpdatedBy)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(MessageTemplates::DeletedAt)
.timestamp_with_time_zone()
.null(),
)
.to_owned(),
)
.await?;

View File

@@ -11,12 +11,7 @@ impl MigrationTrait for Migration {
Table::create()
.table(Messages::Table)
.if_not_exists()
.col(
ColumnDef::new(Messages::Id)
.uuid()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Messages::Id).uuid().not_null().primary_key())
.col(ColumnDef::new(Messages::TenantId).uuid().not_null())
.col(ColumnDef::new(Messages::TemplateId).uuid().null())
.col(ColumnDef::new(Messages::SenderId).uuid().null())
@@ -49,26 +44,50 @@ impl MigrationTrait for Migration {
.not_null()
.default(false),
)
.col(ColumnDef::new(Messages::ReadAt).timestamp_with_time_zone().null())
.col(
ColumnDef::new(Messages::ReadAt)
.timestamp_with_time_zone()
.null(),
)
.col(
ColumnDef::new(Messages::IsArchived)
.boolean()
.not_null()
.default(false),
)
.col(ColumnDef::new(Messages::ArchivedAt).timestamp_with_time_zone().null())
.col(ColumnDef::new(Messages::SentAt).timestamp_with_time_zone().null())
.col(
ColumnDef::new(Messages::ArchivedAt)
.timestamp_with_time_zone()
.null(),
)
.col(
ColumnDef::new(Messages::SentAt)
.timestamp_with_time_zone()
.null(),
)
.col(
ColumnDef::new(Messages::Status)
.string()
.not_null()
.default("sent"),
)
.col(ColumnDef::new(Messages::CreatedAt).timestamp_with_time_zone().not_null())
.col(ColumnDef::new(Messages::UpdatedAt).timestamp_with_time_zone().not_null())
.col(
ColumnDef::new(Messages::CreatedAt)
.timestamp_with_time_zone()
.not_null(),
)
.col(
ColumnDef::new(Messages::UpdatedAt)
.timestamp_with_time_zone()
.not_null(),
)
.col(ColumnDef::new(Messages::CreatedBy).uuid().not_null())
.col(ColumnDef::new(Messages::UpdatedBy).uuid().not_null())
.col(ColumnDef::new(Messages::DeletedAt).timestamp_with_time_zone().null())
.col(
ColumnDef::new(Messages::DeletedAt)
.timestamp_with_time_zone()
.null(),
)
.to_owned(),
)
.await?;

View File

@@ -17,23 +17,63 @@ impl MigrationTrait for Migration {
.not_null()
.primary_key(),
)
.col(ColumnDef::new(MessageSubscriptions::TenantId).uuid().not_null())
.col(ColumnDef::new(MessageSubscriptions::UserId).uuid().not_null())
.col(ColumnDef::new(MessageSubscriptions::NotificationTypes).json().null())
.col(ColumnDef::new(MessageSubscriptions::ChannelPreferences).json().null())
.col(
ColumnDef::new(MessageSubscriptions::TenantId)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(MessageSubscriptions::UserId)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(MessageSubscriptions::NotificationTypes)
.json()
.null(),
)
.col(
ColumnDef::new(MessageSubscriptions::ChannelPreferences)
.json()
.null(),
)
.col(
ColumnDef::new(MessageSubscriptions::DndEnabled)
.boolean()
.not_null()
.default(false),
)
.col(ColumnDef::new(MessageSubscriptions::DndStart).string().null())
.col(
ColumnDef::new(MessageSubscriptions::DndStart)
.string()
.null(),
)
.col(ColumnDef::new(MessageSubscriptions::DndEnd).string().null())
.col(ColumnDef::new(MessageSubscriptions::CreatedAt).timestamp_with_time_zone().not_null())
.col(ColumnDef::new(MessageSubscriptions::UpdatedAt).timestamp_with_time_zone().not_null())
.col(ColumnDef::new(MessageSubscriptions::CreatedBy).uuid().not_null())
.col(ColumnDef::new(MessageSubscriptions::UpdatedBy).uuid().not_null())
.col(ColumnDef::new(MessageSubscriptions::DeletedAt).timestamp_with_time_zone().null())
.col(
ColumnDef::new(MessageSubscriptions::CreatedAt)
.timestamp_with_time_zone()
.not_null(),
)
.col(
ColumnDef::new(MessageSubscriptions::UpdatedAt)
.timestamp_with_time_zone()
.not_null(),
)
.col(
ColumnDef::new(MessageSubscriptions::CreatedBy)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(MessageSubscriptions::UpdatedBy)
.uuid()
.not_null(),
)
.col(
ColumnDef::new(MessageSubscriptions::DeletedAt)
.timestamp_with_time_zone()
.null(),
)
.to_owned(),
)
.await?;

View File

@@ -26,20 +26,33 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(AuditLogs::NewValue).json().null())
.col(ColumnDef::new(AuditLogs::IpAddress).string().null())
.col(ColumnDef::new(AuditLogs::UserAgent).text().null())
.col(ColumnDef::new(AuditLogs::CreatedAt).timestamp_with_time_zone().not_null())
.col(
ColumnDef::new(AuditLogs::CreatedAt)
.timestamp_with_time_zone()
.not_null(),
)
.to_owned(),
)
.await?;
manager.get_connection().execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"CREATE INDEX idx_audit_logs_tenant ON audit_logs (tenant_id)".to_string(),
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
manager
.get_connection()
.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"CREATE INDEX idx_audit_logs_tenant ON audit_logs (tenant_id)".to_string(),
))
.await
.map_err(|e| DbErr::Custom(e.to_string()))?;
manager.get_connection().execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"CREATE INDEX idx_audit_logs_resource ON audit_logs (resource_type, resource_id)".to_string(),
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
manager
.get_connection()
.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"CREATE INDEX idx_audit_logs_resource ON audit_logs (resource_type, resource_id)"
.to_string(),
))
.await
.map_err(|e| DbErr::Custom(e.to_string()))?;
Ok(())
}

View File

@@ -1,6 +1,6 @@
use sea_orm_migration::prelude::*;
use sea_orm::Statement;
use sea_orm::DatabaseBackend;
use sea_orm::Statement;
use sea_orm_migration::prelude::*;
/// Recreate unique indexes on roles and permissions to include soft-delete awareness.
#[derive(DeriveMigrationName)]

View File

@@ -0,0 +1,65 @@
use sea_orm_migration::prelude::*;
/// 修复 settings 表唯一索引:原索引使用 scope_id 列,当 scope_id 为 NULL 时
/// PostgreSQL B-tree 不认为两行重复NULL != NULL导致可插入重复数据。
/// 修复方案:使用 COALESCE(scope_id, '00000000-0000-0000-0000-000000000000')
/// 将 NULL 转为固定 UUID使索引能正确约束 NULL scope_id 的行。
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// 删除旧索引
manager
.get_connection()
.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"DROP INDEX IF EXISTS idx_settings_scope_key".to_string(),
))
.await
.map_err(|e| DbErr::Custom(e.to_string()))?;
// 创建新索引,使用 COALESCE 处理 NULL scope_id
manager.get_connection().execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"CREATE UNIQUE INDEX idx_settings_scope_key ON settings (tenant_id, scope, COALESCE(scope_id, '00000000-0000-0000-0000-000000000000'), setting_key) WHERE deleted_at IS NULL".to_string(),
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
// 清理可能已存在的重复数据(保留每组最新的一条)
manager.get_connection().execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
r#"
DELETE FROM settings a USING settings b
WHERE a.id < b.id
AND a.tenant_id = b.tenant_id
AND a.scope = b.scope
AND a.setting_key = b.setting_key
AND a.deleted_at IS NULL
AND b.deleted_at IS NULL
AND COALESCE(a.scope_id, '00000000-0000-0000-0000-000000000000') = COALESCE(b.scope_id, '00000000-0000-0000-0000-000000000000')
"#.to_string(),
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// 回滚:删除新索引,恢复旧索引
manager
.get_connection()
.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"DROP INDEX IF EXISTS idx_settings_scope_key".to_string(),
))
.await
.map_err(|e| DbErr::Custom(e.to_string()))?;
manager.get_connection().execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"CREATE UNIQUE INDEX idx_settings_scope_key ON settings (tenant_id, scope, scope_id, setting_key) WHERE deleted_at IS NULL".to_string(),
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
Ok(())
}
}

View File

@@ -18,7 +18,11 @@ impl MigrationTrait for Migration {
.primary_key(),
)
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
.col(ColumnDef::new(Alias::new("event_type")).string_len(200).not_null())
.col(
ColumnDef::new(Alias::new("event_type"))
.string_len(200)
.not_null(),
)
.col(ColumnDef::new(Alias::new("payload")).json().null())
.col(ColumnDef::new(Alias::new("correlation_id")).uuid().null())
.col(

View File

@@ -1,7 +1,7 @@
use axum::Router;
use axum::extract::{Extension, FromRef, Query, State};
use axum::response::Json;
use axum::routing::get;
use axum::Router;
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder};
use serde::Deserialize;
@@ -35,8 +35,7 @@ where
let page_size = params.page_size.unwrap_or(20).min(100);
let tenant_id = ctx.tenant_id;
let mut q = audit_log::Entity::find()
.filter(audit_log::Column::TenantId.eq(tenant_id));
let mut q = audit_log::Entity::find().filter(audit_log::Column::TenantId.eq(tenant_id));
if let Some(rt) = &params.resource_type {
q = q.filter(audit_log::Column::ResourceType.eq(rt.clone()));

View File

@@ -1,7 +1,7 @@
use axum::Router;
use axum::extract::State;
use axum::response::Json;
use axum::routing::get;
use axum::Router;
use serde::Serialize;
use crate::state::AppState;

View File

@@ -6,14 +6,9 @@ use utoipa::openapi::OpenApiBuilder;
///
/// 返回 OpenAPI 3.0 规范 JSON 文档
pub async fn openapi_spec() -> Json<Value> {
let mut info = utoipa::openapi::Info::new(
"ERP Platform API",
env!("CARGO_PKG_VERSION"),
);
let mut info = utoipa::openapi::Info::new("ERP Platform API", env!("CARGO_PKG_VERSION"));
info.description = Some("ERP 平台底座 REST API 文档".to_string());
let spec = OpenApiBuilder::new()
.info(info)
.build();
let spec = OpenApiBuilder::new().info(info).build();
Json(serde_json::to_value(spec).unwrap_or_default())
}

View File

@@ -7,13 +7,11 @@ mod state;
/// OpenAPI 规范定义(预留,未来可通过 utoipa derive 合并各模块 schema
#[derive(OpenApi)]
#[openapi(
info(
title = "ERP Platform API",
version = "0.1.0",
description = "ERP 平台底座 REST API 文档"
)
)]
#[openapi(info(
title = "ERP Platform API",
version = "0.1.0",
description = "ERP 平台底座 REST API 文档"
))]
#[allow(dead_code)]
struct ApiDoc;
@@ -38,13 +36,15 @@ async fn main() -> anyhow::Result<()> {
// Initialize tracing
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(&config.log.level)),
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&config.log.level)),
)
.json()
.init();
tracing::info!(version = env!("CARGO_PKG_VERSION"), "ERP Server starting...");
tracing::info!(
version = env!("CARGO_PKG_VERSION"),
"ERP Server starting..."
);
// Connect to database
let db = db::connect(&config.database).await?;
@@ -116,19 +116,35 @@ async fn main() -> anyhow::Result<()> {
// Initialize auth module
let auth_module = erp_auth::AuthModule::new();
tracing::info!(module = auth_module.name(), version = auth_module.version(), "Auth module initialized");
tracing::info!(
module = auth_module.name(),
version = auth_module.version(),
"Auth module initialized"
);
// Initialize config module
let config_module = erp_config::ConfigModule::new();
tracing::info!(module = config_module.name(), version = config_module.version(), "Config module initialized");
tracing::info!(
module = config_module.name(),
version = config_module.version(),
"Config module initialized"
);
// Initialize workflow module
let workflow_module = erp_workflow::WorkflowModule::new();
tracing::info!(module = workflow_module.name(), version = workflow_module.version(), "Workflow module initialized");
tracing::info!(
module = workflow_module.name(),
version = workflow_module.version(),
"Workflow module initialized"
);
// Initialize message module
let message_module = erp_message::MessageModule::new();
tracing::info!(module = message_module.name(), version = message_module.version(), "Message module initialized");
tracing::info!(
module = message_module.name(),
version = message_module.version(),
"Message module initialized"
);
// Initialize module registry and register modules
let registry = ModuleRegistry::new()
@@ -136,7 +152,10 @@ async fn main() -> anyhow::Result<()> {
.register(config_module)
.register(workflow_module)
.register(message_module);
tracing::info!(module_count = registry.modules().len(), "Modules registered");
tracing::info!(
module_count = registry.modules().len(),
"Modules registered"
);
// Register event handlers
registry.register_handlers(&event_bus);
@@ -182,7 +201,10 @@ async fn main() -> anyhow::Result<()> {
let public_routes = Router::new()
.merge(handlers::health::health_check_router())
.merge(erp_auth::AuthModule::public_routes())
.route("/docs/openapi.json", axum::routing::get(handlers::openapi::openapi_spec))
.route(
"/docs/openapi.json",
axum::routing::get(handlers::openapi::openapi_spec),
)
.layer(axum::middleware::from_fn_with_state(
state.clone(),
middleware::rate_limit::rate_limit_by_ip,

View File

@@ -15,7 +15,8 @@ struct RateLimitResponse {
message: String,
}
/// 限流参数。
/// 限流参数(预留配置化扩展)
#[allow(dead_code)]
pub struct RateLimitConfig {
/// 窗口内最大请求数。
pub max_requests: u64,
@@ -74,11 +75,7 @@ async fn apply_rate_limit(
}
};
let count: i64 = match redis::cmd("INCR")
.arg(&key)
.query_async(&mut conn)
.await
{
let count: i64 = match redis::cmd("INCR").arg(&key).query_async(&mut conn).await {
Ok(n) => n,
Err(e) => {
tracing::warn!(error = %e, "Redis INCR 失败,跳过限流");