diff --git a/crates/erp-auth/src/handler/auth_handler.rs b/crates/erp-auth/src/handler/auth_handler.rs index 582357c..0a33740 100644 --- a/crates/erp-auth/src/handler/auth_handler.rs +++ b/crates/erp-auth/src/handler/auth_handler.rs @@ -10,6 +10,17 @@ use crate::auth_state::AuthState; use crate::dto::{LoginReq, LoginResp, RefreshReq}; use crate::service::auth_service::{AuthService, JwtConfig}; +#[utoipa::path( + post, + path = "/api/v1/auth/login", + request_body = LoginReq, + responses( + (status = 200, description = "登录成功", body = ApiResponse), + (status = 400, description = "请求参数错误"), + (status = 401, description = "用户名或密码错误"), + ), + tag = "认证" +)] /// POST /api/v1/auth/login /// /// Authenticates a user with username and password, returning access and refresh tokens. @@ -48,6 +59,16 @@ where Ok(Json(ApiResponse::ok(resp))) } +#[utoipa::path( + post, + path = "/api/v1/auth/refresh", + request_body = RefreshReq, + responses( + (status = 200, description = "刷新成功", body = ApiResponse), + (status = 401, description = "刷新令牌无效或已过期"), + ), + tag = "认证" +)] /// POST /api/v1/auth/refresh /// /// Validates an existing refresh token, revokes it (rotation), and issues @@ -71,6 +92,16 @@ where Ok(Json(ApiResponse::ok(resp))) } +#[utoipa::path( + post, + path = "/api/v1/auth/logout", + responses( + (status = 200, description = "已成功登出"), + (status = 401, description = "未授权"), + ), + security(("bearer_auth" = [])), + tag = "认证" +)] /// POST /api/v1/auth/logout /// /// Revokes all refresh tokens for the authenticated user, effectively diff --git a/crates/erp-auth/src/handler/org_handler.rs b/crates/erp-auth/src/handler/org_handler.rs index c39ecd9..87bd09b 100644 --- a/crates/erp-auth/src/handler/org_handler.rs +++ b/crates/erp-auth/src/handler/org_handler.rs @@ -19,6 +19,17 @@ use erp_core::rbac::require_permission; // --- Organization handlers --- +#[utoipa::path( + get, + path = "/api/v1/organizations", + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// GET /api/v1/organizations /// /// List all organizations within the current tenant as a nested tree. @@ -37,6 +48,18 @@ where Ok(Json(ApiResponse::ok(tree))) } +#[utoipa::path( + post, + path = "/api/v1/organizations", + request_body = CreateOrganizationReq, + responses( + (status = 200, description = "创建成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// POST /api/v1/organizations /// /// Create a new organization within the current tenant. @@ -67,6 +90,20 @@ where Ok(Json(ApiResponse::ok(org))) } +#[utoipa::path( + put, + path = "/api/v1/organizations/{id}", + params(("id" = Uuid, Path, description = "组织ID")), + request_body = UpdateOrganizationReq, + responses( + (status = 200, description = "更新成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "组织不存在"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// PUT /api/v1/organizations/{id} /// /// Update editable organization fields (name, code, sort_order). @@ -87,6 +124,19 @@ where Ok(Json(ApiResponse::ok(org))) } +#[utoipa::path( + delete, + path = "/api/v1/organizations/{id}", + params(("id" = Uuid, Path, description = "组织ID")), + responses( + (status = 200, description = "组织已删除"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "组织不存在"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// DELETE /api/v1/organizations/{id} /// /// Soft-delete an organization by ID. @@ -113,6 +163,18 @@ where // --- Department handlers --- +#[utoipa::path( + get, + path = "/api/v1/organizations/{org_id}/departments", + params(("org_id" = Uuid, Path, description = "组织ID")), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// GET /api/v1/organizations/{org_id}/departments /// /// List all departments for an organization as a nested tree. @@ -132,6 +194,19 @@ where Ok(Json(ApiResponse::ok(tree))) } +#[utoipa::path( + post, + path = "/api/v1/organizations/{org_id}/departments", + params(("org_id" = Uuid, Path, description = "组织ID")), + request_body = CreateDepartmentReq, + responses( + (status = 200, description = "创建成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// POST /api/v1/organizations/{org_id}/departments /// /// Create a new department under the specified organization. @@ -164,6 +239,20 @@ where Ok(Json(ApiResponse::ok(dept))) } +#[utoipa::path( + put, + path = "/api/v1/departments/{id}", + params(("id" = Uuid, Path, description = "部门ID")), + request_body = UpdateDepartmentReq, + responses( + (status = 200, description = "更新成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "部门不存在"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// PUT /api/v1/departments/{id} /// /// Update editable department fields (name, code, manager_id, sort_order). @@ -184,6 +273,19 @@ where Ok(Json(ApiResponse::ok(dept))) } +#[utoipa::path( + delete, + path = "/api/v1/departments/{id}", + params(("id" = Uuid, Path, description = "部门ID")), + responses( + (status = 200, description = "部门已删除"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "部门不存在"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// DELETE /api/v1/departments/{id} /// /// Soft-delete a department by ID. @@ -210,6 +312,18 @@ where // --- Position handlers --- +#[utoipa::path( + get, + path = "/api/v1/departments/{dept_id}/positions", + params(("dept_id" = Uuid, Path, description = "部门ID")), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// GET /api/v1/departments/{dept_id}/positions /// /// List all positions for a department. @@ -229,6 +343,19 @@ where Ok(Json(ApiResponse::ok(positions))) } +#[utoipa::path( + post, + path = "/api/v1/departments/{dept_id}/positions", + params(("dept_id" = Uuid, Path, description = "部门ID")), + request_body = CreatePositionReq, + responses( + (status = 200, description = "创建成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// POST /api/v1/departments/{dept_id}/positions /// /// Create a new position under the specified department. @@ -261,6 +388,20 @@ where Ok(Json(ApiResponse::ok(pos))) } +#[utoipa::path( + put, + path = "/api/v1/positions/{id}", + params(("id" = Uuid, Path, description = "岗位ID")), + request_body = UpdatePositionReq, + responses( + (status = 200, description = "更新成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "岗位不存在"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// PUT /api/v1/positions/{id} /// /// Update editable position fields (name, code, level, sort_order). @@ -281,6 +422,19 @@ where Ok(Json(ApiResponse::ok(pos))) } +#[utoipa::path( + delete, + path = "/api/v1/positions/{id}", + params(("id" = Uuid, Path, description = "岗位ID")), + responses( + (status = 200, description = "岗位已删除"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "岗位不存在"), + ), + security(("bearer_auth" = [])), + tag = "组织管理" +)] /// DELETE /api/v1/positions/{id} /// /// Soft-delete a position by ID. diff --git a/crates/erp-auth/src/handler/role_handler.rs b/crates/erp-auth/src/handler/role_handler.rs index d2d8a46..0e3bcfa 100644 --- a/crates/erp-auth/src/handler/role_handler.rs +++ b/crates/erp-auth/src/handler/role_handler.rs @@ -13,6 +13,18 @@ use crate::service::permission_service::PermissionService; use crate::service::role_service::RoleService; use erp_core::rbac::require_permission; +#[utoipa::path( + get, + path = "/api/v1/roles", + params(Pagination), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "角色管理" +)] /// GET /api/v1/roles /// /// List roles within the current tenant with pagination. @@ -43,6 +55,18 @@ where }))) } +#[utoipa::path( + post, + path = "/api/v1/roles", + request_body = CreateRoleReq, + responses( + (status = 200, description = "创建成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "角色管理" +)] /// POST /api/v1/roles /// /// Create a new role within the current tenant. @@ -75,6 +99,19 @@ where Ok(Json(ApiResponse::ok(role))) } +#[utoipa::path( + get, + path = "/api/v1/roles/{id}", + params(("id" = Uuid, Path, description = "角色ID")), + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "角色不存在"), + ), + security(("bearer_auth" = [])), + tag = "角色管理" +)] /// GET /api/v1/roles/:id /// /// Fetch a single role by ID within the current tenant. @@ -94,6 +131,20 @@ where Ok(Json(ApiResponse::ok(role))) } +#[utoipa::path( + put, + path = "/api/v1/roles/{id}", + params(("id" = Uuid, Path, description = "角色ID")), + request_body = UpdateRoleReq, + responses( + (status = 200, description = "更新成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "角色不存在"), + ), + security(("bearer_auth" = [])), + tag = "角色管理" +)] /// PUT /api/v1/roles/:id /// /// Update editable role fields (name, description). @@ -123,6 +174,19 @@ where Ok(Json(ApiResponse::ok(role))) } +#[utoipa::path( + delete, + path = "/api/v1/roles/{id}", + params(("id" = Uuid, Path, description = "角色ID")), + responses( + (status = 200, description = "角色已删除"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "角色不存在"), + ), + security(("bearer_auth" = [])), + tag = "角色管理" +)] /// DELETE /api/v1/roles/:id /// /// Soft-delete a role by ID within the current tenant. @@ -148,6 +212,20 @@ where })) } +#[utoipa::path( + post, + path = "/api/v1/roles/{id}/permissions", + params(("id" = Uuid, Path, description = "角色ID")), + request_body = AssignPermissionsReq, + responses( + (status = 200, description = "权限分配成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "角色不存在"), + ), + security(("bearer_auth" = [])), + tag = "角色管理" +)] /// POST /api/v1/roles/:id/permissions /// /// Replace all permission assignments for a role. @@ -180,6 +258,19 @@ where })) } +#[utoipa::path( + get, + path = "/api/v1/roles/{id}/permissions", + params(("id" = Uuid, Path, description = "角色ID")), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "角色不存在"), + ), + security(("bearer_auth" = [])), + tag = "角色管理" +)] /// GET /api/v1/roles/:id/permissions /// /// Fetch all permissions assigned to a role. @@ -199,6 +290,17 @@ where Ok(Json(ApiResponse::ok(perms))) } +#[utoipa::path( + get, + path = "/api/v1/permissions", + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "权限管理" +)] /// GET /api/v1/permissions /// /// List all permissions within the current tenant. diff --git a/crates/erp-auth/src/handler/user_handler.rs b/crates/erp-auth/src/handler/user_handler.rs index be0b5cd..2752f9c 100644 --- a/crates/erp-auth/src/handler/user_handler.rs +++ b/crates/erp-auth/src/handler/user_handler.rs @@ -2,6 +2,7 @@ use axum::Extension; use axum::extract::{FromRef, Path, Query, State}; use axum::response::Json; use serde::{Deserialize, Serialize}; +use utoipa::{IntoParams, ToSchema}; use validator::Validate; use erp_core::error::AppError; @@ -14,7 +15,7 @@ use crate::service::user_service::UserService; use erp_core::rbac::require_permission; /// Query parameters for user list endpoint. -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, IntoParams)] pub struct UserListParams { pub page: Option, pub page_size: Option, @@ -22,6 +23,18 @@ pub struct UserListParams { pub search: Option, } +#[utoipa::path( + get, + path = "/api/v1/users", + params(UserListParams), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "用户管理" +)] /// GET /api/v1/users /// /// List users within the current tenant with pagination and optional search. @@ -62,6 +75,18 @@ where }))) } +#[utoipa::path( + post, + path = "/api/v1/users", + request_body = CreateUserReq, + responses( + (status = 200, description = "创建成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "用户管理" +)] /// POST /api/v1/users /// /// Create a new user within the current tenant. @@ -92,6 +117,19 @@ where Ok(Json(ApiResponse::ok(user))) } +#[utoipa::path( + get, + path = "/api/v1/users/{id}", + params(("id" = Uuid, Path, description = "用户ID")), + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "用户不存在"), + ), + security(("bearer_auth" = [])), + tag = "用户管理" +)] /// GET /api/v1/users/:id /// /// Fetch a single user by ID within the current tenant. @@ -111,6 +149,20 @@ where Ok(Json(ApiResponse::ok(user))) } +#[utoipa::path( + put, + path = "/api/v1/users/{id}", + params(("id" = Uuid, Path, description = "用户ID")), + request_body = UpdateUserReq, + responses( + (status = 200, description = "更新成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "用户不存在"), + ), + security(("bearer_auth" = [])), + tag = "用户管理" +)] /// PUT /api/v1/users/:id /// /// Update editable user fields. @@ -131,6 +183,19 @@ where Ok(Json(ApiResponse::ok(user))) } +#[utoipa::path( + delete, + path = "/api/v1/users/{id}", + params(("id" = Uuid, Path, description = "用户ID")), + responses( + (status = 200, description = "用户已删除"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "用户不存在"), + ), + security(("bearer_auth" = [])), + tag = "用户管理" +)] /// DELETE /api/v1/users/:id /// /// Soft-delete a user by ID within the current tenant. @@ -156,17 +221,31 @@ where } /// Assign roles request body. -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] pub struct AssignRolesReq { pub role_ids: Vec, } /// Assign roles response. -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] pub struct AssignRolesResp { pub roles: Vec, } +#[utoipa::path( + post, + path = "/api/v1/users/{id}/roles", + params(("id" = Uuid, Path, description = "用户ID")), + request_body = AssignRolesReq, + responses( + (status = 200, description = "角色分配成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "用户不存在"), + ), + security(("bearer_auth" = [])), + tag = "用户管理" +)] /// POST /api/v1/users/:id/roles /// /// Replace all role assignments for a user within the current tenant. diff --git a/crates/erp-config/src/handler/dictionary_handler.rs b/crates/erp-config/src/handler/dictionary_handler.rs index 9749613..f1891d7 100644 --- a/crates/erp-config/src/handler/dictionary_handler.rs +++ b/crates/erp-config/src/handler/dictionary_handler.rs @@ -15,6 +15,18 @@ use crate::dto::{ }; use crate::service::dictionary_service::DictionaryService; +#[utoipa::path( + get, + path = "/api/v1/dictionaries", + params(Pagination), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "字典管理" +)] /// GET /api/v1/dictionaries /// /// 分页查询当前租户下的字典列表。 @@ -47,6 +59,18 @@ where }))) } +#[utoipa::path( + post, + path = "/api/v1/dictionaries", + request_body = CreateDictionaryReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "字典管理" +)] /// POST /api/v1/dictionaries /// /// 在当前租户下创建新字典。 @@ -80,6 +104,19 @@ where Ok(Json(ApiResponse::ok(dictionary))) } +#[utoipa::path( + put, + path = "/api/v1/dictionaries/{id}", + params(("id" = Uuid, Path, description = "字典ID")), + request_body = UpdateDictionaryReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "字典管理" +)] /// PUT /api/v1/dictionaries/:id /// /// 更新字典的可编辑字段(名称、描述)。 @@ -103,6 +140,19 @@ where Ok(Json(ApiResponse::ok(dictionary))) } +#[utoipa::path( + delete, + path = "/api/v1/dictionaries/{id}", + params(("id" = Uuid, Path, description = "字典ID")), + request_body = DeleteVersionReq, + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "字典管理" +)] /// DELETE /api/v1/dictionaries/:id /// /// 软删除字典,设置 deleted_at 时间戳。 @@ -137,6 +187,18 @@ where })) } +#[utoipa::path( + get, + path = "/api/v1/dictionaries/items-by-code", + params(("code" = String, Query, description = "字典编码")), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "字典管理" +)] /// GET /api/v1/dictionaries/items-by-code?code=xxx /// /// 根据字典编码查询所有字典项。 @@ -159,6 +221,19 @@ where Ok(Json(ApiResponse::ok(items))) } +#[utoipa::path( + post, + path = "/api/v1/dictionaries/{dict_id}/items", + params(("dict_id" = Uuid, Path, description = "字典ID")), + request_body = CreateDictionaryItemReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "字典管理" +)] /// POST /api/v1/dictionaries/:dict_id/items /// /// 向指定字典添加新的字典项。 @@ -185,6 +260,22 @@ where Ok(Json(ApiResponse::ok(item))) } +#[utoipa::path( + put, + path = "/api/v1/dictionaries/{dict_id}/items/{item_id}", + params( + ("dict_id" = Uuid, Path, description = "字典ID"), + ("item_id" = Uuid, Path, description = "字典项ID"), + ), + request_body = UpdateDictionaryItemReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "字典管理" +)] /// PUT /api/v1/dictionaries/:dict_id/items/:item_id /// /// 更新字典项的可编辑字段(label、value、sort_order、color)。 @@ -213,6 +304,22 @@ where Ok(Json(ApiResponse::ok(item))) } +#[utoipa::path( + delete, + path = "/api/v1/dictionaries/{dict_id}/items/{item_id}", + params( + ("dict_id" = Uuid, Path, description = "字典ID"), + ("item_id" = Uuid, Path, description = "字典项ID"), + ), + request_body = DeleteVersionReq, + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "字典管理" +)] /// DELETE /api/v1/dictionaries/:dict_id/items/:item_id /// /// 软删除字典项,设置 deleted_at 时间戳。 @@ -247,7 +354,7 @@ pub struct ItemsByCodeQuery { } /// 删除操作的乐观锁版本号。 -#[derive(Debug, serde::Deserialize)] +#[derive(Debug, serde::Deserialize, utoipa::ToSchema)] pub struct DeleteVersionReq { pub version: i32, } diff --git a/crates/erp-config/src/handler/language_handler.rs b/crates/erp-config/src/handler/language_handler.rs index 0dad963..37085bb 100644 --- a/crates/erp-config/src/handler/language_handler.rs +++ b/crates/erp-config/src/handler/language_handler.rs @@ -10,6 +10,17 @@ use crate::config_state::ConfigState; use crate::dto::{LanguageResp, SetSettingParams, UpdateLanguageReq}; use crate::service::setting_service::SettingService; +#[utoipa::path( + get, + path = "/api/v1/languages", + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "语言管理" +)] /// GET /api/v1/languages /// /// 获取当前租户的语言配置列表。 @@ -56,6 +67,19 @@ where Ok(JsonResponse(ApiResponse::ok(languages))) } +#[utoipa::path( + put, + path = "/api/v1/languages/{code}", + params(("code" = String, Path, description = "语言编码")), + request_body = UpdateLanguageReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "语言管理" +)] /// PUT /api/v1/languages/:code /// /// 更新指定语言配置的激活状态。 diff --git a/crates/erp-config/src/handler/menu_handler.rs b/crates/erp-config/src/handler/menu_handler.rs index d7c07da..cc2dedf 100644 --- a/crates/erp-config/src/handler/menu_handler.rs +++ b/crates/erp-config/src/handler/menu_handler.rs @@ -9,9 +9,20 @@ use erp_core::types::{ApiResponse, TenantContext}; use uuid::Uuid; use crate::config_state::ConfigState; -use crate::dto::{BatchSaveMenusReq, CreateMenuReq, MenuResp}; +use crate::dto::{BatchSaveMenusReq, CreateMenuReq, MenuResp, UpdateMenuReq}; use crate::service::menu_service::MenuService; +#[utoipa::path( + get, + path = "/api/v1/config/menus", + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "菜单管理" +)] /// GET /api/v1/config/menus /// /// 获取当前租户下当前用户角色可见的菜单树。 @@ -36,6 +47,18 @@ where Ok(JsonResponse(ApiResponse::ok(menus))) } +#[utoipa::path( + post, + path = "/api/v1/config/menus", + request_body = CreateMenuReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "菜单管理" +)] /// POST /api/v1/config/menus /// /// 创建单个菜单项。 @@ -65,6 +88,19 @@ where Ok(JsonResponse(ApiResponse::ok(resp))) } +#[utoipa::path( + put, + path = "/api/v1/config/menus/{id}", + params(("id" = Uuid, Path, description = "菜单ID")), + request_body = UpdateMenuReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "菜单管理" +)] /// PUT /api/v1/config/menus/{id} /// /// 更新单个菜单项。 @@ -72,7 +108,7 @@ pub async fn update_menu( State(state): State, Extension(ctx): Extension, Path(id): Path, - Json(req): Json, + Json(req): Json, ) -> Result>, AppError> where ConfigState: FromRef, @@ -84,6 +120,19 @@ where Ok(JsonResponse(ApiResponse::ok(resp))) } +#[utoipa::path( + delete, + path = "/api/v1/config/menus/{id}", + params(("id" = Uuid, Path, description = "菜单ID")), + request_body = DeleteMenuVersionReq, + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "菜单管理" +)] /// DELETE /api/v1/config/menus/{id} /// /// 软删除单个菜单项。需要请求体包含 version 字段用于乐观锁校验。 @@ -111,6 +160,18 @@ where Ok(JsonResponse(ApiResponse::ok(()))) } +#[utoipa::path( + put, + path = "/api/v1/config/menus/batch", + request_body = BatchSaveMenusReq, + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "菜单管理" +)] /// PUT /api/v1/config/menus/batch /// /// 批量保存菜单列表。 @@ -132,7 +193,7 @@ where match item.id { Some(id) => { let version = item.version.unwrap_or(0); - let update_req = crate::dto::UpdateMenuReq { + let update_req = UpdateMenuReq { title: Some(item.title.clone()), path: item.path.clone(), icon: item.icon.clone(), @@ -176,7 +237,7 @@ where } /// 删除菜单的乐观锁版本号请求体。 -#[derive(Debug, serde::Deserialize)] +#[derive(Debug, serde::Deserialize, utoipa::ToSchema)] pub struct DeleteMenuVersionReq { pub version: i32, } diff --git a/crates/erp-config/src/handler/numbering_handler.rs b/crates/erp-config/src/handler/numbering_handler.rs index 3b6ec79..bb7af14 100644 --- a/crates/erp-config/src/handler/numbering_handler.rs +++ b/crates/erp-config/src/handler/numbering_handler.rs @@ -14,6 +14,18 @@ use crate::dto::{ }; use crate::service::numbering_service::NumberingService; +#[utoipa::path( + get, + path = "/api/v1/numbering-rules", + params(Pagination), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "编号规则" +)] /// GET /api/v1/numbering-rules /// /// 分页查询当前租户下的编号规则列表。 @@ -44,6 +56,18 @@ where }))) } +#[utoipa::path( + post, + path = "/api/v1/numbering-rules", + request_body = CreateNumberingRuleReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "编号规则" +)] /// POST /api/v1/numbering-rules /// /// 创建新的编号规则。 @@ -75,6 +99,19 @@ where Ok(Json(ApiResponse::ok(rule))) } +#[utoipa::path( + put, + path = "/api/v1/numbering-rules/{id}", + params(("id" = Uuid, Path, description = "编号规则ID")), + request_body = UpdateNumberingRuleReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "编号规则" +)] /// PUT /api/v1/numbering-rules/:id /// /// 更新编号规则的可编辑字段。 @@ -96,6 +133,18 @@ where Ok(Json(ApiResponse::ok(rule))) } +#[utoipa::path( + post, + path = "/api/v1/numbering-rules/{id}/generate", + params(("id" = Uuid, Path, description = "编号规则ID")), + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "编号规则" +)] /// POST /api/v1/numbering-rules/:id/generate /// /// 根据编号规则生成新的编号。 @@ -117,6 +166,19 @@ where Ok(Json(ApiResponse::ok(result))) } +#[utoipa::path( + delete, + path = "/api/v1/numbering-rules/{id}", + params(("id" = Uuid, Path, description = "编号规则ID")), + request_body = DeleteNumberingVersionReq, + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "编号规则" +)] /// DELETE /api/v1/numbering-rules/:id /// /// 软删除编号规则,设置 deleted_at 时间戳。 @@ -152,7 +214,7 @@ where } /// 删除编号规则的乐观锁版本号请求体。 -#[derive(Debug, serde::Deserialize)] +#[derive(Debug, serde::Deserialize, utoipa::ToSchema)] pub struct DeleteNumberingVersionReq { pub version: i32, } diff --git a/crates/erp-config/src/handler/setting_handler.rs b/crates/erp-config/src/handler/setting_handler.rs index f004af3..3cccfe6 100644 --- a/crates/erp-config/src/handler/setting_handler.rs +++ b/crates/erp-config/src/handler/setting_handler.rs @@ -11,6 +11,22 @@ use crate::config_state::ConfigState; use crate::dto::{SetSettingParams, SettingResp, UpdateSettingReq}; use crate::service::setting_service::SettingService; +#[utoipa::path( + get, + path = "/api/v1/settings/{key}", + params( + ("key" = String, Path, description = "设置键名"), + ("scope" = Option, Query, description = "作用域"), + ("scope_id" = Option, Query, description = "作用域ID"), + ), + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "系统设置" +)] /// GET /api/v1/settings/:key?scope=tenant&scope_id=xxx /// /// 获取设置值,支持分层回退查找。 @@ -36,6 +52,19 @@ where Ok(Json(ApiResponse::ok(setting))) } +#[utoipa::path( + put, + path = "/api/v1/settings/{key}", + params(("key" = String, Path, description = "设置键名")), + request_body = UpdateSettingReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "系统设置" +)] /// PUT /api/v1/settings/:key /// /// 创建或更新设置值。 @@ -78,6 +107,23 @@ pub struct SettingQuery { pub scope_id: Option, } +#[utoipa::path( + delete, + path = "/api/v1/settings/{key}", + params( + ("key" = String, Path, description = "设置键名"), + ("scope" = Option, Query, description = "作用域"), + ("scope_id" = Option, Query, description = "作用域ID"), + ), + request_body = DeleteSettingVersionReq, + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "系统设置" +)] /// DELETE /api/v1/settings/:key /// /// 软删除设置值,设置 deleted_at 时间戳。 @@ -117,7 +163,7 @@ where } /// 删除设置的乐观锁版本号请求体。 -#[derive(Debug, serde::Deserialize)] +#[derive(Debug, serde::Deserialize, utoipa::ToSchema)] pub struct DeleteSettingVersionReq { pub version: i32, } diff --git a/crates/erp-config/src/handler/theme_handler.rs b/crates/erp-config/src/handler/theme_handler.rs index 6f8732e..731a8ca 100644 --- a/crates/erp-config/src/handler/theme_handler.rs +++ b/crates/erp-config/src/handler/theme_handler.rs @@ -10,6 +10,17 @@ use crate::config_state::ConfigState; use crate::dto::{SetSettingParams, ThemeResp}; use crate::service::setting_service::SettingService; +#[utoipa::path( + get, + path = "/api/v1/themes", + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "主题设置" +)] /// GET /api/v1/theme /// /// 获取当前租户的主题配置。 @@ -33,6 +44,18 @@ where Ok(JsonResponse(ApiResponse::ok(theme))) } +#[utoipa::path( + put, + path = "/api/v1/themes", + request_body = ThemeResp, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "主题设置" +)] /// PUT /api/v1/theme /// /// 更新当前租户的主题配置。 diff --git a/crates/erp-core/Cargo.toml b/crates/erp-core/Cargo.toml index bbc2541..7d3b6df 100644 --- a/crates/erp-core/Cargo.toml +++ b/crates/erp-core/Cargo.toml @@ -15,3 +15,4 @@ tracing.workspace = true axum.workspace = true sea-orm.workspace = true async-trait.workspace = true +utoipa.workspace = true diff --git a/crates/erp-core/src/types.rs b/crates/erp-core/src/types.rs index 8c69bfe..6a29350 100644 --- a/crates/erp-core/src/types.rs +++ b/crates/erp-core/src/types.rs @@ -16,7 +16,7 @@ pub struct BaseFields { } /// 分页请求 -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::IntoParams)] pub struct Pagination { pub page: Option, pub page_size: Option, @@ -120,7 +120,7 @@ mod tests { } /// 分页响应 -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, utoipa::ToSchema)] pub struct PaginatedResponse { pub data: Vec, pub total: u64, @@ -130,7 +130,7 @@ pub struct PaginatedResponse { } /// API 统一响应 -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, utoipa::ToSchema)] pub struct ApiResponse { pub success: bool, pub data: Option, diff --git a/crates/erp-message/src/dto.rs b/crates/erp-message/src/dto.rs index b249fe6..782e5b4 100644 --- a/crates/erp-message/src/dto.rs +++ b/crates/erp-message/src/dto.rs @@ -75,7 +75,7 @@ fn default_priority() -> String { } /// 消息列表查询参数 -#[derive(Debug, Deserialize, ToSchema)] +#[derive(Debug, Deserialize, ToSchema, utoipa::IntoParams)] pub struct MessageQuery { pub page: Option, pub page_size: Option, diff --git a/crates/erp-message/src/handler/message_handler.rs b/crates/erp-message/src/handler/message_handler.rs index fe11db9..c60e75e 100644 --- a/crates/erp-message/src/handler/message_handler.rs +++ b/crates/erp-message/src/handler/message_handler.rs @@ -12,6 +12,18 @@ use crate::dto::{MessageQuery, MessageResp, SendMessageReq, UnreadCountResp}; use crate::message_state::MessageState; use crate::service::message_service::MessageService; +#[utoipa::path( + get, + path = "/api/v1/messages", + params(MessageQuery), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "消息管理" +)] /// 查询消息列表。 pub async fn list_messages( State(_state): State, @@ -40,6 +52,17 @@ where }))) } +#[utoipa::path( + get, + path = "/api/v1/messages/unread-count", + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "消息管理" +)] /// 获取未读消息数量。 pub async fn unread_count( State(_state): State, @@ -55,6 +78,18 @@ where Ok(Json(ApiResponse::ok(result))) } +#[utoipa::path( + post, + path = "/api/v1/messages", + request_body = SendMessageReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "消息管理" +)] /// 发送消息。 pub async fn send_message( State(_state): State, @@ -82,6 +117,18 @@ where Ok(Json(ApiResponse::ok(resp))) } +#[utoipa::path( + put, + path = "/api/v1/messages/{id}/read", + params(("id" = Uuid, Path, description = "消息ID")), + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "消息管理" +)] /// 标记消息已读。 pub async fn mark_read( State(_state): State, @@ -96,6 +143,17 @@ where Ok(Json(ApiResponse::ok(()))) } +#[utoipa::path( + put, + path = "/api/v1/messages/read-all", + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "消息管理" +)] /// 标记所有消息已读。 pub async fn mark_all_read( State(_state): State, @@ -109,6 +167,18 @@ where Ok(Json(ApiResponse::ok(()))) } +#[utoipa::path( + delete, + path = "/api/v1/messages/{id}", + params(("id" = Uuid, Path, description = "消息ID")), + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "消息管理" +)] /// 删除消息。 pub async fn delete_message( State(_state): State, diff --git a/crates/erp-message/src/handler/subscription_handler.rs b/crates/erp-message/src/handler/subscription_handler.rs index 8099fce..284ee19 100644 --- a/crates/erp-message/src/handler/subscription_handler.rs +++ b/crates/erp-message/src/handler/subscription_handler.rs @@ -9,6 +9,18 @@ use crate::dto::UpdateSubscriptionReq; use crate::message_state::MessageState; use crate::service::subscription_service::SubscriptionService; +#[utoipa::path( + put, + path = "/api/v1/message-subscriptions", + request_body = UpdateSubscriptionReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "消息订阅" +)] /// 更新消息订阅偏好。 pub async fn update_subscription( State(_state): State, diff --git a/crates/erp-message/src/handler/template_handler.rs b/crates/erp-message/src/handler/template_handler.rs index 090bf38..9d6b8dd 100644 --- a/crates/erp-message/src/handler/template_handler.rs +++ b/crates/erp-message/src/handler/template_handler.rs @@ -12,12 +12,24 @@ use crate::dto::{CreateTemplateReq, MessageTemplateResp}; use crate::message_state::MessageState; use crate::service::template_service::TemplateService; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::IntoParams)] pub struct TemplateQuery { pub page: Option, pub page_size: Option, } +#[utoipa::path( + get, + path = "/api/v1/message-templates", + params(TemplateQuery), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "消息模板" +)] /// 查询消息模板列表。 pub async fn list_templates( State(_state): State, @@ -46,6 +58,18 @@ where }))) } +#[utoipa::path( + post, + path = "/api/v1/message-templates", + request_body = CreateTemplateReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "消息模板" +)] /// 创建消息模板。 pub async fn create_template( State(_state): State, diff --git a/crates/erp-server/src/handlers/openapi.rs b/crates/erp-server/src/handlers/openapi.rs index 1eba8c4..a2b9605 100644 --- a/crates/erp-server/src/handlers/openapi.rs +++ b/crates/erp-server/src/handlers/openapi.rs @@ -1,14 +1,18 @@ use axum::response::Json; use serde_json::Value; -use utoipa::openapi::OpenApiBuilder; +use utoipa::OpenApi; + +use crate::{ApiDoc, AuthApiDoc, ConfigApiDoc, WorkflowApiDoc, MessageApiDoc}; /// GET /docs/openapi.json /// -/// 返回 OpenAPI 3.0 规范 JSON 文档 +/// 返回 OpenAPI 3.0 规范 JSON 文档,合并所有模块的路径和 schema。 pub async fn openapi_spec() -> Json { - let mut info = utoipa::openapi::Info::new("ERP Platform API", env!("CARGO_PKG_VERSION")); - info.description = Some("ERP 平台底座 REST API 文档".to_string()); + let mut spec = ApiDoc::openapi(); + spec.merge(AuthApiDoc::openapi()); + spec.merge(ConfigApiDoc::openapi()); + spec.merge(WorkflowApiDoc::openapi()); + spec.merge(MessageApiDoc::openapi()); - let spec = OpenApiBuilder::new().info(info).build(); Json(serde_json::to_value(spec).unwrap_or_default()) } diff --git a/crates/erp-server/src/main.rs b/crates/erp-server/src/main.rs index 4c1256e..af8d276 100644 --- a/crates/erp-server/src/main.rs +++ b/crates/erp-server/src/main.rs @@ -5,16 +5,167 @@ mod middleware; mod outbox; mod state; -/// OpenAPI 规范定义(预留,未来可通过 utoipa derive 合并各模块 schema)。 +/// OpenAPI 规范定义 — 通过 utoipa derive 合并各模块 schema。 #[derive(OpenApi)] #[openapi(info( title = "ERP Platform API", version = "0.1.0", description = "ERP 平台底座 REST API 文档" ))] -#[allow(dead_code)] struct ApiDoc; +/// Auth 模块的 OpenAPI 路径收集 +#[derive(OpenApi)] +#[openapi( + paths( + erp_auth::handler::auth_handler::login, + erp_auth::handler::auth_handler::refresh, + erp_auth::handler::auth_handler::logout, + erp_auth::handler::user_handler::list_users, + erp_auth::handler::user_handler::create_user, + erp_auth::handler::user_handler::get_user, + erp_auth::handler::user_handler::update_user, + erp_auth::handler::user_handler::delete_user, + erp_auth::handler::user_handler::assign_roles, + erp_auth::handler::role_handler::list_roles, + erp_auth::handler::role_handler::create_role, + erp_auth::handler::role_handler::get_role, + erp_auth::handler::role_handler::update_role, + erp_auth::handler::role_handler::delete_role, + erp_auth::handler::role_handler::assign_permissions, + erp_auth::handler::role_handler::get_role_permissions, + erp_auth::handler::role_handler::list_permissions, + ), + components( + schemas( + erp_auth::dto::LoginReq, + erp_auth::dto::LoginResp, + erp_auth::dto::RefreshReq, + erp_auth::dto::UserResp, + erp_auth::dto::CreateUserReq, + erp_auth::dto::UpdateUserReq, + erp_auth::dto::RoleResp, + erp_auth::dto::CreateRoleReq, + erp_auth::dto::UpdateRoleReq, + erp_auth::dto::PermissionResp, + erp_auth::dto::AssignPermissionsReq, + ) + ) +)] +struct AuthApiDoc; + +/// Config 模块的 OpenAPI 路径收集 +#[derive(OpenApi)] +#[openapi( + paths( + erp_config::handler::dictionary_handler::list_dictionaries, + erp_config::handler::dictionary_handler::create_dictionary, + erp_config::handler::dictionary_handler::update_dictionary, + erp_config::handler::dictionary_handler::delete_dictionary, + erp_config::handler::dictionary_handler::list_items_by_code, + erp_config::handler::dictionary_handler::create_item, + erp_config::handler::dictionary_handler::update_item, + erp_config::handler::menu_handler::get_menus, + erp_config::handler::menu_handler::create_menu, + erp_config::handler::menu_handler::update_menu, + erp_config::handler::menu_handler::delete_menu, + erp_config::handler::numbering_handler::list_numbering_rules, + erp_config::handler::numbering_handler::create_numbering_rule, + erp_config::handler::numbering_handler::update_numbering_rule, + erp_config::handler::numbering_handler::generate_number, + erp_config::handler::numbering_handler::delete_numbering_rule, + erp_config::handler::theme_handler::get_theme, + erp_config::handler::theme_handler::update_theme, + erp_config::handler::language_handler::list_languages, + erp_config::handler::language_handler::update_language, + erp_config::handler::setting_handler::get_setting, + erp_config::handler::setting_handler::update_setting, + erp_config::handler::setting_handler::delete_setting, + ), + components( + schemas( + erp_config::dto::DictionaryResp, + erp_config::dto::CreateDictionaryReq, + erp_config::dto::UpdateDictionaryReq, + erp_config::dto::DictionaryItemResp, + erp_config::dto::CreateDictionaryItemReq, + erp_config::dto::UpdateDictionaryItemReq, + erp_config::dto::MenuResp, + erp_config::dto::CreateMenuReq, + erp_config::dto::UpdateMenuReq, + erp_config::dto::NumberingRuleResp, + erp_config::dto::CreateNumberingRuleReq, + erp_config::dto::UpdateNumberingRuleReq, + erp_config::dto::ThemeResp, + ) + ) +)] +struct ConfigApiDoc; + +/// Workflow 模块的 OpenAPI 路径收集 +#[derive(OpenApi)] +#[openapi( + paths( + erp_workflow::handler::definition_handler::list_definitions, + erp_workflow::handler::definition_handler::create_definition, + erp_workflow::handler::definition_handler::get_definition, + erp_workflow::handler::definition_handler::update_definition, + erp_workflow::handler::definition_handler::publish_definition, + erp_workflow::handler::instance_handler::start_instance, + erp_workflow::handler::instance_handler::list_instances, + erp_workflow::handler::instance_handler::get_instance, + erp_workflow::handler::instance_handler::suspend_instance, + erp_workflow::handler::instance_handler::terminate_instance, + erp_workflow::handler::instance_handler::resume_instance, + erp_workflow::handler::task_handler::list_pending_tasks, + erp_workflow::handler::task_handler::list_completed_tasks, + erp_workflow::handler::task_handler::complete_task, + erp_workflow::handler::task_handler::delegate_task, + ), + components( + schemas( + erp_workflow::dto::ProcessDefinitionResp, + erp_workflow::dto::CreateProcessDefinitionReq, + erp_workflow::dto::UpdateProcessDefinitionReq, + erp_workflow::dto::ProcessInstanceResp, + erp_workflow::dto::StartInstanceReq, + erp_workflow::dto::TaskResp, + erp_workflow::dto::CompleteTaskReq, + erp_workflow::dto::DelegateTaskReq, + ) + ) +)] +struct WorkflowApiDoc; + +/// Message 模块的 OpenAPI 路径收集 +#[derive(OpenApi)] +#[openapi( + paths( + erp_message::handler::message_handler::list_messages, + erp_message::handler::message_handler::unread_count, + erp_message::handler::message_handler::send_message, + erp_message::handler::message_handler::mark_read, + erp_message::handler::message_handler::mark_all_read, + erp_message::handler::message_handler::delete_message, + erp_message::handler::template_handler::list_templates, + erp_message::handler::template_handler::create_template, + erp_message::handler::subscription_handler::update_subscription, + ), + components( + schemas( + erp_message::dto::MessageResp, + erp_message::dto::SendMessageReq, + erp_message::dto::MessageQuery, + erp_message::dto::UnreadCountResp, + erp_message::dto::MessageTemplateResp, + erp_message::dto::CreateTemplateReq, + erp_message::dto::MessageSubscriptionResp, + erp_message::dto::UpdateSubscriptionReq, + ) + ) +)] +struct MessageApiDoc; + use axum::Router; use axum::middleware as axum_middleware; use config::AppConfig; diff --git a/crates/erp-workflow/src/handler/definition_handler.rs b/crates/erp-workflow/src/handler/definition_handler.rs index a195a05..1652185 100644 --- a/crates/erp-workflow/src/handler/definition_handler.rs +++ b/crates/erp-workflow/src/handler/definition_handler.rs @@ -12,6 +12,18 @@ use crate::dto::{CreateProcessDefinitionReq, ProcessDefinitionResp, UpdateProces use crate::service::definition_service::DefinitionService; use crate::workflow_state::WorkflowState; +#[utoipa::path( + get, + path = "/api/v1/workflow/definitions", + params(Pagination), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "流程定义" +)] /// GET /api/v1/workflow/definitions pub async fn list_definitions( State(state): State, @@ -39,6 +51,18 @@ where }))) } +#[utoipa::path( + post, + path = "/api/v1/workflow/definitions", + request_body = CreateProcessDefinitionReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "流程定义" +)] /// POST /api/v1/workflow/definitions pub async fn create_definition( State(state): State, @@ -65,6 +89,19 @@ where Ok(Json(ApiResponse::ok(resp))) } +#[utoipa::path( + get, + path = "/api/v1/workflow/definitions/{id}", + params(("id" = Uuid, Path, description = "流程定义ID")), + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "流程定义不存在"), + ), + security(("bearer_auth" = [])), + tag = "流程定义" +)] /// GET /api/v1/workflow/definitions/{id} pub async fn get_definition( State(state): State, @@ -81,6 +118,20 @@ where Ok(Json(ApiResponse::ok(resp))) } +#[utoipa::path( + put, + path = "/api/v1/workflow/definitions/{id}", + params(("id" = Uuid, Path, description = "流程定义ID")), + request_body = UpdateProcessDefinitionReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "流程定义不存在"), + ), + security(("bearer_auth" = [])), + tag = "流程定义" +)] /// PUT /api/v1/workflow/definitions/{id} pub async fn update_definition( State(state): State, @@ -98,6 +149,19 @@ where Ok(Json(ApiResponse::ok(resp))) } +#[utoipa::path( + post, + path = "/api/v1/workflow/definitions/{id}/publish", + params(("id" = Uuid, Path, description = "流程定义ID")), + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "流程定义不存在"), + ), + security(("bearer_auth" = [])), + tag = "流程定义" +)] /// POST /api/v1/workflow/definitions/{id}/publish pub async fn publish_definition( State(state): State, diff --git a/crates/erp-workflow/src/handler/instance_handler.rs b/crates/erp-workflow/src/handler/instance_handler.rs index dc79f4c..29343f8 100644 --- a/crates/erp-workflow/src/handler/instance_handler.rs +++ b/crates/erp-workflow/src/handler/instance_handler.rs @@ -12,6 +12,18 @@ use crate::dto::{ProcessInstanceResp, StartInstanceReq}; use crate::service::instance_service::InstanceService; use crate::workflow_state::WorkflowState; +#[utoipa::path( + post, + path = "/api/v1/workflow/instances", + request_body = StartInstanceReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "流程实例" +)] /// POST /api/v1/workflow/instances pub async fn start_instance( State(state): State, @@ -38,6 +50,18 @@ where Ok(Json(ApiResponse::ok(resp))) } +#[utoipa::path( + get, + path = "/api/v1/workflow/instances", + params(Pagination), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "流程实例" +)] /// GET /api/v1/workflow/instances pub async fn list_instances( State(state): State, @@ -65,6 +89,19 @@ where }))) } +#[utoipa::path( + get, + path = "/api/v1/workflow/instances/{id}", + params(("id" = Uuid, Path, description = "流程实例ID")), + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "流程实例不存在"), + ), + security(("bearer_auth" = [])), + tag = "流程实例" +)] /// GET /api/v1/workflow/instances/{id} pub async fn get_instance( State(state): State, @@ -81,6 +118,19 @@ where Ok(Json(ApiResponse::ok(resp))) } +#[utoipa::path( + post, + path = "/api/v1/workflow/instances/{id}/suspend", + params(("id" = Uuid, Path, description = "流程实例ID")), + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "流程实例不存在"), + ), + security(("bearer_auth" = [])), + tag = "流程实例" +)] /// POST /api/v1/workflow/instances/{id}/suspend pub async fn suspend_instance( State(state): State, @@ -97,6 +147,19 @@ where Ok(Json(ApiResponse::ok(()))) } +#[utoipa::path( + post, + path = "/api/v1/workflow/instances/{id}/terminate", + params(("id" = Uuid, Path, description = "流程实例ID")), + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "流程实例不存在"), + ), + security(("bearer_auth" = [])), + tag = "流程实例" +)] /// POST /api/v1/workflow/instances/{id}/terminate pub async fn terminate_instance( State(state): State, @@ -113,6 +176,19 @@ where Ok(Json(ApiResponse::ok(()))) } +#[utoipa::path( + post, + path = "/api/v1/workflow/instances/{id}/resume", + params(("id" = Uuid, Path, description = "流程实例ID")), + responses( + (status = 200, description = "成功"), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "流程实例不存在"), + ), + security(("bearer_auth" = [])), + tag = "流程实例" +)] /// POST /api/v1/workflow/instances/{id}/resume pub async fn resume_instance( State(state): State, diff --git a/crates/erp-workflow/src/handler/task_handler.rs b/crates/erp-workflow/src/handler/task_handler.rs index 277520c..efbaa4f 100644 --- a/crates/erp-workflow/src/handler/task_handler.rs +++ b/crates/erp-workflow/src/handler/task_handler.rs @@ -12,6 +12,18 @@ use crate::dto::{CompleteTaskReq, DelegateTaskReq, TaskResp}; use crate::service::task_service::TaskService; use crate::workflow_state::WorkflowState; +#[utoipa::path( + get, + path = "/api/v1/workflow/tasks/pending", + params(Pagination), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "流程任务" +)] /// GET /api/v1/workflow/tasks/pending pub async fn list_pending_tasks( State(state): State, @@ -40,6 +52,18 @@ where }))) } +#[utoipa::path( + get, + path = "/api/v1/workflow/tasks/completed", + params(Pagination), + responses( + (status = 200, description = "成功", body = ApiResponse>), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + ), + security(("bearer_auth" = [])), + tag = "流程任务" +)] /// GET /api/v1/workflow/tasks/completed pub async fn list_completed_tasks( State(state): State, @@ -68,6 +92,20 @@ where }))) } +#[utoipa::path( + post, + path = "/api/v1/workflow/tasks/{id}/complete", + params(("id" = Uuid, Path, description = "任务ID")), + request_body = CompleteTaskReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "任务不存在"), + ), + security(("bearer_auth" = [])), + tag = "流程任务" +)] /// POST /api/v1/workflow/tasks/{id}/complete pub async fn complete_task( State(state): State, @@ -96,6 +134,20 @@ where Ok(Json(ApiResponse::ok(resp))) } +#[utoipa::path( + post, + path = "/api/v1/workflow/tasks/{id}/delegate", + params(("id" = Uuid, Path, description = "任务ID")), + request_body = DelegateTaskReq, + responses( + (status = 200, description = "成功", body = ApiResponse), + (status = 401, description = "未授权"), + (status = 403, description = "权限不足"), + (status = 404, description = "任务不存在"), + ), + security(("bearer_auth" = [])), + tag = "流程任务" +)] /// POST /api/v1/workflow/tasks/{id}/delegate pub async fn delegate_task( State(state): State,