feat: add utoipa path annotations to all API handlers and wire OpenAPI spec

- Add #[utoipa::path] annotations to all 70+ handler functions across
  auth, config, workflow, and message modules
- Add IntoParams/ToSchema derives to Pagination, PaginatedResponse, ApiResponse
  in erp-core, and MessageQuery/TemplateQuery in erp-message
- Collect all module paths into OpenAPI spec via AuthApiDoc, ConfigApiDoc,
  WorkflowApiDoc, MessageApiDoc structs in erp-server main.rs
- Update openapi_spec handler to merge all module specs
- The /docs/openapi.json endpoint now returns complete API documentation
  with all endpoints, request/response schemas, and security requirements
This commit is contained in:
iven
2026-04-15 01:23:27 +08:00
parent ee65b6e3c9
commit e44d6063be
21 changed files with 1165 additions and 22 deletions

View File

@@ -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<LoginResp>),
(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<LoginResp>),
(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

View File

@@ -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<Vec<OrganizationResp>>),
(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<OrganizationResp>),
(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<OrganizationResp>),
(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<Vec<DepartmentResp>>),
(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<DepartmentResp>),
(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<DepartmentResp>),
(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<Vec<PositionResp>>),
(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<PositionResp>),
(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<PositionResp>),
(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.

View File

@@ -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<PaginatedResponse<RoleResp>>),
(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<RoleResp>),
(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<RoleResp>),
(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<RoleResp>),
(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<Vec<PermissionResp>>),
(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<Vec<PermissionResp>>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "权限管理"
)]
/// GET /api/v1/permissions
///
/// List all permissions within the current tenant.

View File

@@ -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<u64>,
pub page_size: Option<u64>,
@@ -22,6 +23,18 @@ pub struct UserListParams {
pub search: Option<String>,
}
#[utoipa::path(
get,
path = "/api/v1/users",
params(UserListParams),
responses(
(status = 200, description = "成功", body = ApiResponse<PaginatedResponse<UserResp>>),
(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<UserResp>),
(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<UserResp>),
(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<UserResp>),
(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<Uuid>,
}
/// Assign roles response.
#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, ToSchema)]
pub struct AssignRolesResp {
pub roles: Vec<RoleResp>,
}
#[utoipa::path(
post,
path = "/api/v1/users/{id}/roles",
params(("id" = Uuid, Path, description = "用户ID")),
request_body = AssignRolesReq,
responses(
(status = 200, description = "角色分配成功", body = ApiResponse<AssignRolesResp>),
(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.

View File

@@ -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<PaginatedResponse<DictionaryResp>>),
(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<DictionaryResp>),
(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<DictionaryResp>),
(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<Vec<DictionaryItemResp>>),
(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<DictionaryItemResp>),
(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<DictionaryItemResp>),
(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,
}

View File

@@ -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<Vec<LanguageResp>>),
(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<LanguageResp>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "语言管理"
)]
/// PUT /api/v1/languages/:code
///
/// 更新指定语言配置的激活状态。

View File

@@ -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<Vec<MenuResp>>),
(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<MenuResp>),
(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<MenuResp>),
(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<S>(
State(state): State<ConfigState>,
Extension(ctx): Extension<TenantContext>,
Path(id): Path<Uuid>,
Json(req): Json<crate::dto::UpdateMenuReq>,
Json(req): Json<UpdateMenuReq>,
) -> Result<JsonResponse<ApiResponse<MenuResp>>, AppError>
where
ConfigState: FromRef<S>,
@@ -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,
}

View File

@@ -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<PaginatedResponse<NumberingRuleResp>>),
(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<NumberingRuleResp>),
(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<NumberingRuleResp>),
(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<GenerateNumberResp>),
(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,
}

View File

@@ -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<String>, Query, description = "作用域"),
("scope_id" = Option<Uuid>, Query, description = "作用域ID"),
),
responses(
(status = 200, description = "成功", body = ApiResponse<SettingResp>),
(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<SettingResp>),
(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<Uuid>,
}
#[utoipa::path(
delete,
path = "/api/v1/settings/{key}",
params(
("key" = String, Path, description = "设置键名"),
("scope" = Option<String>, Query, description = "作用域"),
("scope_id" = Option<Uuid>, 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,
}

View File

@@ -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<ThemeResp>),
(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<ThemeResp>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "主题设置"
)]
/// PUT /api/v1/theme
///
/// 更新当前租户的主题配置。

View File

@@ -15,3 +15,4 @@ tracing.workspace = true
axum.workspace = true
sea-orm.workspace = true
async-trait.workspace = true
utoipa.workspace = true

View File

@@ -16,7 +16,7 @@ pub struct BaseFields {
}
/// 分页请求
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, utoipa::IntoParams)]
pub struct Pagination {
pub page: Option<u64>,
pub page_size: Option<u64>,
@@ -120,7 +120,7 @@ mod tests {
}
/// 分页响应
#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct PaginatedResponse<T> {
pub data: Vec<T>,
pub total: u64,
@@ -130,7 +130,7 @@ pub struct PaginatedResponse<T> {
}
/// API 统一响应
#[derive(Debug, Serialize)]
#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct ApiResponse<T: Serialize> {
pub success: bool,
pub data: Option<T>,

View File

@@ -75,7 +75,7 @@ fn default_priority() -> String {
}
/// 消息列表查询参数
#[derive(Debug, Deserialize, ToSchema)]
#[derive(Debug, Deserialize, ToSchema, utoipa::IntoParams)]
pub struct MessageQuery {
pub page: Option<u64>,
pub page_size: Option<u64>,

View File

@@ -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<PaginatedResponse<MessageResp>>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "消息管理"
)]
/// 查询消息列表。
pub async fn list_messages<S>(
State(_state): State<MessageState>,
@@ -40,6 +52,17 @@ where
})))
}
#[utoipa::path(
get,
path = "/api/v1/messages/unread-count",
responses(
(status = 200, description = "成功", body = ApiResponse<UnreadCountResp>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "消息管理"
)]
/// 获取未读消息数量。
pub async fn unread_count<S>(
State(_state): State<MessageState>,
@@ -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<MessageResp>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "消息管理"
)]
/// 发送消息。
pub async fn send_message<S>(
State(_state): State<MessageState>,
@@ -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<S>(
State(_state): State<MessageState>,
@@ -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<S>(
State(_state): State<MessageState>,
@@ -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<S>(
State(_state): State<MessageState>,

View File

@@ -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<crate::dto::MessageSubscriptionResp>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "消息订阅"
)]
/// 更新消息订阅偏好。
pub async fn update_subscription<S>(
State(_state): State<MessageState>,

View File

@@ -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<u64>,
pub page_size: Option<u64>,
}
#[utoipa::path(
get,
path = "/api/v1/message-templates",
params(TemplateQuery),
responses(
(status = 200, description = "成功", body = ApiResponse<PaginatedResponse<MessageTemplateResp>>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "消息模板"
)]
/// 查询消息模板列表。
pub async fn list_templates<S>(
State(_state): State<MessageState>,
@@ -46,6 +58,18 @@ where
})))
}
#[utoipa::path(
post,
path = "/api/v1/message-templates",
request_body = CreateTemplateReq,
responses(
(status = 200, description = "成功", body = ApiResponse<MessageTemplateResp>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "消息模板"
)]
/// 创建消息模板。
pub async fn create_template<S>(
State(_state): State<MessageState>,

View File

@@ -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<Value> {
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())
}

View File

@@ -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;

View File

@@ -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<PaginatedResponse<ProcessDefinitionResp>>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "流程定义"
)]
/// GET /api/v1/workflow/definitions
pub async fn list_definitions<S>(
State(state): State<WorkflowState>,
@@ -39,6 +51,18 @@ where
})))
}
#[utoipa::path(
post,
path = "/api/v1/workflow/definitions",
request_body = CreateProcessDefinitionReq,
responses(
(status = 200, description = "成功", body = ApiResponse<ProcessDefinitionResp>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "流程定义"
)]
/// POST /api/v1/workflow/definitions
pub async fn create_definition<S>(
State(state): State<WorkflowState>,
@@ -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<ProcessDefinitionResp>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
(status = 404, description = "流程定义不存在"),
),
security(("bearer_auth" = [])),
tag = "流程定义"
)]
/// GET /api/v1/workflow/definitions/{id}
pub async fn get_definition<S>(
State(state): State<WorkflowState>,
@@ -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<ProcessDefinitionResp>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
(status = 404, description = "流程定义不存在"),
),
security(("bearer_auth" = [])),
tag = "流程定义"
)]
/// PUT /api/v1/workflow/definitions/{id}
pub async fn update_definition<S>(
State(state): State<WorkflowState>,
@@ -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<ProcessDefinitionResp>),
(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<S>(
State(state): State<WorkflowState>,

View File

@@ -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<ProcessInstanceResp>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "流程实例"
)]
/// POST /api/v1/workflow/instances
pub async fn start_instance<S>(
State(state): State<WorkflowState>,
@@ -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<PaginatedResponse<ProcessInstanceResp>>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "流程实例"
)]
/// GET /api/v1/workflow/instances
pub async fn list_instances<S>(
State(state): State<WorkflowState>,
@@ -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<ProcessInstanceResp>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
(status = 404, description = "流程实例不存在"),
),
security(("bearer_auth" = [])),
tag = "流程实例"
)]
/// GET /api/v1/workflow/instances/{id}
pub async fn get_instance<S>(
State(state): State<WorkflowState>,
@@ -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<S>(
State(state): State<WorkflowState>,
@@ -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<S>(
State(state): State<WorkflowState>,
@@ -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<S>(
State(state): State<WorkflowState>,

View File

@@ -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<PaginatedResponse<TaskResp>>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "流程任务"
)]
/// GET /api/v1/workflow/tasks/pending
pub async fn list_pending_tasks<S>(
State(state): State<WorkflowState>,
@@ -40,6 +52,18 @@ where
})))
}
#[utoipa::path(
get,
path = "/api/v1/workflow/tasks/completed",
params(Pagination),
responses(
(status = 200, description = "成功", body = ApiResponse<PaginatedResponse<TaskResp>>),
(status = 401, description = "未授权"),
(status = 403, description = "权限不足"),
),
security(("bearer_auth" = [])),
tag = "流程任务"
)]
/// GET /api/v1/workflow/tasks/completed
pub async fn list_completed_tasks<S>(
State(state): State<WorkflowState>,
@@ -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<TaskResp>),
(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<S>(
State(state): State<WorkflowState>,
@@ -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<TaskResp>),
(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<S>(
State(state): State<WorkflowState>,