openapi: 3.1.0 info: title: AIOA API version: 0.12.0 servers: - url: /api/v1 security: - bearerAuth: [] paths: /devices/register: post: operationId: registerCurrentDevice summary: 注册或刷新当前用户设备会话 requestBody: required: true content: application/json: schema: type: object required: [id, name, platform] properties: id: { type: string, format: uuid } name: { type: string, minLength: 1, maxLength: 200 } platform: { type: string, enum: [IOS, ANDROID, OTHER] } appVersion: { type: [string, "null"], maxLength: 64 } responses: "200": { description: 已注册的设备会话 } "401": { description: 设备已被撤销,禁止重新注册 } /devices: get: operationId: listOwnDevices summary: 查询当前用户的登录设备 responses: "200": { description: 当前用户设备列表 } /devices/{id}: delete: operationId: revokeOwnDevice summary: 撤销当前用户的一台设备并使其后续请求失效 parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } responses: "200": { description: 已撤销设备 } "404": { description: 设备不存在或不属于当前用户 } /devices/{id}/push-token: put: operationId: updateCurrentDevicePushToken summary: 注册、刷新或清除当前设备的 FCM 推送令牌 parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } - { name: X-AIOA-Device-Id, in: header, required: true, schema: { type: string, format: uuid } } requestBody: required: true content: application/json: schema: type: object properties: token: { type: [string, "null"], maxLength: 4096 } responses: "200": { description: 推送令牌已更新 } "403": { description: 路径设备与当前设备不一致 } /ai/leave-progress-answers: post: operationId: answerOwnLeaveProgress summary: 使用自然语言只读查询本人请假流程进度 requestBody: required: true content: application/json: schema: type: object required: [text, timezone] properties: text: { type: string, minLength: 1, maxLength: 2000 } timezone: { type: string, default: Asia/Shanghai } selectedRequestId: { type: string, format: uuid } responses: "200": description: 回答或需要用户选择的本人申请候选列表 "400": { $ref: "#/components/responses/BadRequest" } "502": description: AI 上游不可用或输出无效 /ai/leave-draft-suggestions: post: operationId: suggestLeaveDraft summary: 将自然语言转换为需要用户确认的请假草稿建议 requestBody: required: true content: application/json: schema: type: object required: [text, timezone] properties: text: { type: string, minLength: 1, maxLength: 2000 } timezone: { type: string, default: Asia/Shanghai } responses: "200": description: 受约束的草稿建议,不执行业务写操作 content: application/json: schema: { $ref: "#/components/schemas/AiLeaveDraftSuggestion" } "400": { $ref: "#/components/responses/BadRequest" } "502": description: AI 上游不可用或输出无效 /notifications: get: operationId: listOwnNotifications summary: 查询当前用户站内通知 responses: "200": description: 通知列表 content: application/json: schema: type: array items: { $ref: "#/components/schemas/Notification" } /notifications/unread-count: get: operationId: getUnreadNotificationCount summary: 查询未读通知数 responses: "200": description: 未读数 content: application/json: schema: type: object required: [unreadCount] properties: unreadCount: { type: integer, minimum: 0 } /notifications/{id}/read: post: operationId: markNotificationRead summary: 标记本人通知已读 parameters: - name: id in: path required: true schema: { type: string, format: uuid } responses: "200": description: 已读通知 content: application/json: schema: { $ref: "#/components/schemas/Notification" } "404": { $ref: "#/components/responses/NotFound" } /form-definitions/{formKey}: get: operationId: getFormDefinition summary: 获取版本化表单定义 parameters: - name: formKey in: path required: true schema: { type: string } responses: "200": description: JSON Schema 与移动端 UI Schema content: application/json: schema: $ref: "#/components/schemas/FormDefinition" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" /me: get: operationId: getCurrentUser summary: 获取当前登录用户 responses: "200": description: 当前用户 content: application/json: schema: $ref: "#/components/schemas/CurrentUser" "401": $ref: "#/components/responses/Unauthorized" /leave-requests: get: operationId: listOwnLeaveRequests summary: 查询当前用户的请假申请 responses: "200": description: 申请列表 content: application/json: schema: type: array items: $ref: "#/components/schemas/LeaveRequest" "401": $ref: "#/components/responses/Unauthorized" /leave-requests/drafts: post: operationId: createLeaveRequestDraft summary: 创建请假草稿 parameters: - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/SaveLeaveDraft" responses: "201": description: 已创建 content: application/json: schema: $ref: "#/components/schemas/LeaveRequest" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "409": $ref: "#/components/responses/Conflict" /leave-requests/{id}: parameters: - name: id in: path required: true schema: { type: string, format: uuid } get: operationId: getOwnLeaveRequest summary: 查询当前用户的单条请假申请 responses: "200": description: 请假申请 content: application/json: schema: $ref: "#/components/schemas/LeaveRequest" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" put: operationId: updateLeaveRequestDraft summary: 修改当前用户的请假草稿 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/SaveLeaveDraft" responses: "200": description: 已修改 content: application/json: schema: $ref: "#/components/schemas/LeaveRequest" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" /leave-requests/{id}/submit: post: operationId: submitLeaveRequest summary: 提交请假草稿 parameters: - $ref: "#/components/parameters/LeaveRequestId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/TransitionRequest" responses: "200": description: 已提交或幂等重放 content: application/json: schema: $ref: "#/components/schemas/LeaveRequest" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" /leave-requests/{id}/withdraw: post: operationId: withdrawLeaveRequest summary: 撤回待审批申请 parameters: - $ref: "#/components/parameters/LeaveRequestId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/TransitionRequest" responses: "200": description: 已撤回或幂等重放 content: application/json: schema: $ref: "#/components/schemas/LeaveRequest" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" /leave-requests/{id}/timeline: get: operationId: getLeaveRequestTimeline summary: 查询本人申请状态时间线 parameters: - $ref: "#/components/parameters/LeaveRequestId" responses: "200": description: 状态事件列表 content: application/json: schema: type: array items: $ref: "#/components/schemas/LeaveRequestEvent" "404": $ref: "#/components/responses/NotFound" /leave-requests/{id}/attachments/upload-tasks: post: operationId: createLeaveAttachmentUpload summary: 创建附件直传任务 parameters: - $ref: "#/components/parameters/LeaveRequestId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateAttachmentUpload" responses: "201": description: 已创建上传任务 content: application/json: schema: $ref: "#/components/schemas/AttachmentUpload" "400": { $ref: "#/components/responses/BadRequest" } "404": { $ref: "#/components/responses/NotFound" } "409": { $ref: "#/components/responses/Conflict" } /leave-requests/{id}/attachments: get: operationId: listLeaveAttachments summary: 查询请假附件 parameters: - $ref: "#/components/parameters/LeaveRequestId" responses: "200": description: 附件列表 content: application/json: schema: type: array items: { $ref: "#/components/schemas/LeaveAttachment" } /leave-requests/{id}/attachments/{attachmentId}/complete: post: operationId: completeLeaveAttachmentUpload summary: 确认附件上传完成 parameters: - $ref: "#/components/parameters/LeaveRequestId" - $ref: "#/components/parameters/AttachmentId" responses: "200": description: 附件已就绪 content: application/json: schema: { $ref: "#/components/schemas/LeaveAttachment" } /leave-requests/{id}/attachments/{attachmentId}/download: get: operationId: createLeaveAttachmentDownload summary: 获取附件短期下载地址 parameters: - $ref: "#/components/parameters/LeaveRequestId" - $ref: "#/components/parameters/AttachmentId" responses: "200": description: 短期下载地址 content: application/json: schema: type: object required: [downloadUrl] properties: downloadUrl: { type: string, format: uri } /leave-requests/{id}/attachments/{attachmentId}: delete: operationId: deleteLeaveAttachment summary: 删除草稿附件 parameters: - $ref: "#/components/parameters/LeaveRequestId" - $ref: "#/components/parameters/AttachmentId" responses: "204": { description: 已删除 } /approval-tasks: get: operationId: listAssignedApprovalTasks summary: 查询分配给当前用户的审批待办 responses: "200": description: 待办列表 content: application/json: schema: type: array items: $ref: "#/components/schemas/ApprovalTask" /approval-tasks/{taskId}/approve: post: operationId: approveTask summary: 批准审批任务 parameters: - $ref: "#/components/parameters/ApprovalTaskId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ApprovalDecision" responses: "200": description: 已批准或幂等重放 content: application/json: schema: $ref: "#/components/schemas/LeaveRequest" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" /approval-tasks/{taskId}/reject: post: operationId: rejectTask summary: 驳回审批任务 parameters: - $ref: "#/components/parameters/ApprovalTaskId" - $ref: "#/components/parameters/IdempotencyKey" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ApprovalDecision" responses: "200": description: 已驳回或幂等重放 content: application/json: schema: $ref: "#/components/schemas/LeaveRequest" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" components: securitySchemes: bearerAuth: type: oauth2 flows: authorizationCode: authorizationUrl: http://localhost:8081/realms/aioa/protocol/openid-connect/auth tokenUrl: http://localhost:8081/realms/aioa/protocol/openid-connect/token scopes: openid: OpenID Connect identity profile: Basic profile email: Email address parameters: LeaveRequestId: name: id in: path required: true schema: { type: string, format: uuid } IdempotencyKey: name: Idempotency-Key in: header required: true schema: type: string minLength: 16 maxLength: 128 ApprovalTaskId: name: taskId in: path required: true schema: { type: string } AttachmentId: name: attachmentId in: path required: true schema: { type: string, format: uuid } responses: BadRequest: description: 请求无效 content: application/problem+json: schema: $ref: "#/components/schemas/Problem" Unauthorized: description: 未认证 content: application/problem+json: schema: $ref: "#/components/schemas/Problem" Forbidden: description: 无权操作 content: application/problem+json: schema: $ref: "#/components/schemas/Problem" NotFound: description: 资源不存在或当前用户不可见 content: application/problem+json: schema: $ref: "#/components/schemas/Problem" Conflict: description: 幂等键、状态或乐观锁冲突 content: application/problem+json: schema: $ref: "#/components/schemas/Problem" schemas: AiLeaveDraftSuggestion: type: object required: [suggestion, model, requiresUserConfirmation] properties: suggestion: type: object required: [assumptions, needsClarification] properties: type: { type: [string, "null"], enum: [PERSONAL, SICK, ANNUAL, null] } startsAt: { type: [string, "null"], format: date-time } endsAt: { type: [string, "null"], format: date-time } reason: { type: [string, "null"], maxLength: 2000 } assumptions: type: array items: { type: string } needsClarification: type: array items: { type: string } model: { type: string } requiresUserConfirmation: { type: boolean, const: true } Notification: type: object required: [id, type, title, body, createdAt] properties: id: { type: string, format: uuid } type: { type: string } title: { type: string } body: { type: string } resourceType: { type: [string, "null"] } resourceId: { type: [string, "null"] } createdAt: { type: string, format: date-time } readAt: { type: [string, "null"], format: date-time } CreateAttachmentUpload: type: object required: [fileName, contentType, sizeBytes] properties: fileName: { type: string, minLength: 1, maxLength: 255 } contentType: type: string enum: [image/jpeg, image/png, application/pdf] sizeBytes: { type: integer, format: int64, minimum: 1, maximum: 10485760 } AttachmentUpload: type: object required: [attachment, uploadUrl] properties: attachment: { $ref: "#/components/schemas/LeaveAttachment" } uploadUrl: { type: string, format: uri } LeaveAttachment: type: object required: [id, fileName, contentType, sizeBytes, status, createdAt] properties: id: { type: string, format: uuid } fileName: { type: string } contentType: { type: string } sizeBytes: { type: integer, format: int64 } status: { type: string, enum: [PENDING, READY] } createdAt: { type: string, format: date-time } completedAt: { type: [string, "null"], format: date-time } FormDefinition: type: object required: [key, version, dataSchema, uiSchema] properties: key: { type: string } version: { type: integer, minimum: 1 } dataSchema: type: object additionalProperties: true uiSchema: type: object additionalProperties: true CurrentUser: type: object required: [id, tenantId, username, displayName, roles, permissions, dataScopes] properties: id: { type: string, format: uuid } tenantId: { type: string, format: uuid } username: { type: string } displayName: { type: string } email: type: [string, "null"] format: email department: oneOf: - $ref: "#/components/schemas/OrganizationRef" - type: "null" position: oneOf: - $ref: "#/components/schemas/OrganizationRef" - type: "null" roles: type: array uniqueItems: true items: { type: string } permissions: type: array uniqueItems: true items: type: string enum: [LEAVE_REQUEST_READ_OWN, LEAVE_REQUEST_WRITE_OWN, LEAVE_ATTACHMENT_MANAGE_OWN, NOTIFICATION_READ_OWN, AI_LEAVE_DRAFT_SUGGEST, AI_LEAVE_PROGRESS_READ_OWN, APPROVAL_TASK_READ_ASSIGNED, APPROVAL_TASK_DECIDE_ASSIGNED] dataScopes: type: array uniqueItems: true items: { type: string, enum: [OWN, ASSIGNED] } OrganizationRef: type: object required: [id, name] properties: id: { type: string, format: uuid } name: { type: string } SaveLeaveDraft: type: object required: [type, startsAt, endsAt, reason, version] properties: type: type: string enum: [PERSONAL, SICK, ANNUAL] startsAt: { type: string, format: date-time } endsAt: { type: string, format: date-time } reason: { type: string, minLength: 1, maxLength: 2000 } version: { type: integer, format: int64, minimum: 0 } LeaveRequest: type: object required: [id, applicantId, type, startsAt, endsAt, reason, status, version, createdAt, updatedAt] properties: id: { type: string, format: uuid } applicantId: { type: string, format: uuid } type: type: string enum: [PERSONAL, SICK, ANNUAL] startsAt: { type: string, format: date-time } endsAt: { type: string, format: date-time } reason: { type: string } status: type: string enum: [DRAFT, PENDING, APPROVED, REJECTED, WITHDRAWN] version: { type: integer, minimum: 0 } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } processInstanceId: type: [string, "null"] processDefinitionId: type: [string, "null"] TransitionRequest: type: object required: [version] properties: version: { type: integer, format: int64, minimum: 0 } LeaveRequestEvent: type: object required: [id, eventType, fromStatus, toStatus, actorId, traceId, occurredAt] properties: id: { type: string, format: uuid } eventType: { type: string } fromStatus: type: string enum: [DRAFT, PENDING, APPROVED, REJECTED, WITHDRAWN] toStatus: type: string enum: [DRAFT, PENDING, APPROVED, REJECTED, WITHDRAWN] actorId: { type: string, format: uuid } traceId: { type: string } occurredAt: { type: string, format: date-time } ApprovalDecision: type: object required: [version] properties: version: { type: integer, format: int64, minimum: 0 } comment: type: [string, "null"] maxLength: 1000 ApprovalTask: type: object required: [id, name, createdAt, leaveRequest] properties: id: { type: string } name: { type: string } createdAt: { type: string, format: date-time } leaveRequest: $ref: "#/components/schemas/LeaveRequest" Problem: type: object required: [type, title, status, code, traceId] properties: type: { type: string, format: uri-reference } title: { type: string } status: { type: integer } detail: { type: string } code: { type: string } traceId: { type: string }