docs(api): 补全 OpenAPI 元数据与字段描述

This commit is contained in:
2026-03-05 16:43:53 +08:00
parent eb2f6554b2
commit eb941c06c0
4 changed files with 135 additions and 56 deletions

View File

@@ -16,6 +16,7 @@ const handler = new OpenAPIHandler(router, {
info: { info: {
title: name, title: name,
version, version,
description: 'UX 授权服务 OpenAPI 文档:设备授权、任务解密、摘要加密与报告签名打包接口。',
}, },
}, },
docsPath: '/docs', docsPath: '/docs',

View File

@@ -2,65 +2,103 @@ import { oc } from '@orpc/contract'
import { z } from 'zod' import { z } from 'zod'
export const encryptDeviceInfo = oc export const encryptDeviceInfo = oc
.route({
method: 'POST',
path: '/crypto/encrypt-device-info',
operationId: 'encryptDeviceInfo',
summary: '生成设备授权密文',
description:
'按 deviceId 查询已注册设备,使用设备记录中的 platformPublicKey 对 {licence, fingerprint} 做 RSA-OAEP 加密,返回 Base64 密文。',
tags: ['Crypto'],
})
.input( .input(
z.object({ z.object({
deviceId: z.string().min(1), deviceId: z.string().min(1).describe('设备 ID'),
}), }),
) )
.output( .output(
z.object({ z.object({
encrypted: z.string(), encrypted: z.string().describe('Base64 密文(用于设备授权二维码)'),
}), }),
) )
export const decryptTask = oc export const decryptTask = oc
.route({
method: 'POST',
path: '/crypto/decrypt-task',
operationId: 'decryptTask',
summary: '解密任务二维码',
description:
'按 deviceId 查询已注册设备,使用 UTF-8 编码下的 licence 与 fingerprint 直接拼接(无分隔符)后取 SHA256 作为 AES-256-GCM 密钥解密任务密文。',
tags: ['Crypto'],
})
.input( .input(
z.object({ z.object({
deviceId: z.string().min(1), deviceId: z.string().min(1).describe('设备 ID'),
encryptedData: z.string().min(1), encryptedData: z.string().min(1).describe('任务二维码中的 Base64 密文'),
}), }),
) )
.output( .output(
z.object({ z.object({
taskId: z.string(), taskId: z.string().describe('任务 ID'),
enterpriseId: z.string(), enterpriseId: z.string().describe('企业 ID'),
orgName: z.string(), orgName: z.string().describe('单位名称'),
inspectionId: z.string(), inspectionId: z.string().describe('检查 ID'),
inspectionPerson: z.string(), inspectionPerson: z.string().describe('检查人'),
issuedAt: z.number(), issuedAt: z.number().describe('任务发布时间戳(毫秒)'),
}), }),
) )
export const encryptSummary = oc export const encryptSummary = oc
.route({
method: 'POST',
path: '/crypto/encrypt-summary',
operationId: 'encryptSummary',
summary: '加密摘要二维码内容',
description: '按 deviceId 查询已注册设备,使用 HKDF-SHA256 + AES-256-GCM 加密摘要信息,返回二维码 JSON 字符串。',
tags: ['Crypto'],
})
.input( .input(
z.object({ z.object({
deviceId: z.string().min(1), deviceId: z.string().min(1).describe('设备 ID'),
taskId: z.string().min(1), taskId: z.string().min(1).describe('任务 ID'),
enterpriseId: z.string().min(1), enterpriseId: z.string().min(1).describe('企业 ID'),
inspectionId: z.string().min(1), inspectionId: z.string().min(1).describe('检查 ID'),
summary: z.string().min(1), summary: z.string().min(1).describe('摘要明文'),
}), }),
) )
.output( .output(
z.object({ z.object({
qrContent: z.string(), qrContent: z.string().describe('二维码内容 JSON{"taskId":"...","encrypted":"..."}'),
}), }),
) )
export const signAndPackReport = oc export const signAndPackReport = oc
.route({
method: 'POST',
path: '/crypto/sign-and-pack-report',
operationId: 'signAndPackReport',
summary: '签名并打包报告 ZIP',
description:
'接收原始 ZIPmultipart/form-data 文件字段 rawZip由 UX 生成 summary.json、manifest.json、signature.asc并返回 signedZipBase64。',
tags: ['Crypto', 'Report'],
})
.input( .input(
z.object({ z.object({
deviceId: z.string().min(1), deviceId: z.string().min(1).describe('设备 ID'),
taskId: z.string().min(1), taskId: z.string().min(1).describe('任务 ID'),
enterpriseId: z.string().min(1), enterpriseId: z.string().min(1).describe('企业 ID'),
inspectionId: z.string().min(1), inspectionId: z.string().min(1).describe('检查 ID'),
summary: z.string().min(1), summary: z.string().min(1).describe('检查摘要明文'),
rawZip: z.file().mime(['application/zip', 'application/x-zip-compressed']), rawZip: z
.file()
.mime(['application/zip', 'application/x-zip-compressed'])
.describe('原始报告 ZIP 文件multipart/form-data 字段)'),
}), }),
) )
.output( .output(
z.object({ z.object({
deviceSignature: z.string(), deviceSignature: z.string().describe('设备签名HMAC-SHA256 Base64'),
signedZipBase64: z.string(), signedZipBase64: z.string().describe('签名后 ZIP 的 Base64 编码'),
}), }),
) )

View File

@@ -2,29 +2,45 @@ import { oc } from '@orpc/contract'
import { z } from 'zod' import { z } from 'zod'
const deviceOutput = z.object({ const deviceOutput = z.object({
id: z.string(), id: z.string().describe('设备主键 ID'),
licence: z.string(), licence: z.string().describe('设备授权码 licence'),
fingerprint: z.string(), fingerprint: z.string().describe('UX 计算并持久化的设备指纹'),
platformPublicKey: z.string(), platformPublicKey: z.string().describe('平台公钥Base64SPKI DER'),
pgpPublicKey: z.string().nullable(), pgpPublicKey: z.string().nullable().describe('设备 OpenPGP 公钥ASCII armored'),
createdAt: z.date(), createdAt: z.date().describe('记录创建时间'),
updatedAt: z.date(), updatedAt: z.date().describe('记录更新时间'),
}) })
export const register = oc export const register = oc
.route({
method: 'POST',
path: '/device/register',
operationId: 'deviceRegister',
summary: '注册设备',
description: '注册 licence 与平台公钥,指纹由 UX 本机计算,返回设备信息。',
tags: ['Device'],
})
.input( .input(
z.object({ z.object({
licence: z.string().min(1), licence: z.string().min(1).describe('设备授权码 licence'),
platformPublicKey: z.string().min(1), platformPublicKey: z.string().min(1).describe('平台公钥Base64SPKI DER'),
}), }),
) )
.output(deviceOutput) .output(deviceOutput)
export const get = oc export const get = oc
.route({
method: 'POST',
path: '/device/get',
operationId: 'deviceGet',
summary: '查询设备',
description: '按 id 或 licence 查询设备信息。',
tags: ['Device'],
})
.input( .input(
z.object({ z.object({
id: z.string().optional(), id: z.string().optional().describe('设备 ID与 licence 二选一'),
licence: z.string().optional(), licence: z.string().optional().describe('设备授权码,与 id 二选一'),
}), }),
) )
.output(deviceOutput) .output(deviceOutput)

View File

@@ -2,46 +2,70 @@ import { oc } from '@orpc/contract'
import { z } from 'zod' import { z } from 'zod'
const taskOutput = z.object({ const taskOutput = z.object({
id: z.string(), id: z.string().describe('任务记录 ID'),
deviceId: z.string(), deviceId: z.string().describe('设备 ID'),
taskId: z.string(), taskId: z.string().describe('任务业务 ID'),
enterpriseId: z.string().nullable(), enterpriseId: z.string().nullable().describe('企业 ID'),
orgName: z.string().nullable(), orgName: z.string().nullable().describe('单位名称'),
inspectionId: z.string().nullable(), inspectionId: z.string().nullable().describe('检查 ID'),
inspectionPerson: z.string().nullable(), inspectionPerson: z.string().nullable().describe('检查人'),
issuedAt: z.date().nullable(), issuedAt: z.date().nullable().describe('任务发布时间ISO date-time由毫秒时间戳转换后存储'),
status: z.enum(['pending', 'in_progress', 'done']), status: z.enum(['pending', 'in_progress', 'done']).describe('任务状态'),
createdAt: z.date(), createdAt: z.date().describe('记录创建时间'),
updatedAt: z.date(), updatedAt: z.date().describe('记录更新时间'),
}) })
export const save = oc export const save = oc
.route({
method: 'POST',
path: '/task/save',
operationId: 'taskSave',
summary: '保存任务',
description: '保存解密后的任务信息到 UX 数据库。',
tags: ['Task'],
})
.input( .input(
z.object({ z.object({
deviceId: z.string().min(1), deviceId: z.string().min(1).describe('设备 ID'),
taskId: z.string().min(1), taskId: z.string().min(1).describe('任务 ID'),
enterpriseId: z.string().optional(), enterpriseId: z.string().optional().describe('企业 ID'),
orgName: z.string().optional(), orgName: z.string().optional().describe('单位名称'),
inspectionId: z.string().optional(), inspectionId: z.string().optional().describe('检查 ID'),
inspectionPerson: z.string().optional(), inspectionPerson: z.string().optional().describe('检查人'),
issuedAt: z.number().optional(), issuedAt: z.number().optional().describe('任务发布时间戳(毫秒)'),
}), }),
) )
.output(taskOutput) .output(taskOutput)
export const list = oc export const list = oc
.route({
method: 'POST',
path: '/task/list',
operationId: 'taskList',
summary: '查询任务列表',
description: '按设备 ID 查询任务列表。',
tags: ['Task'],
})
.input( .input(
z.object({ z.object({
deviceId: z.string().min(1), deviceId: z.string().min(1).describe('设备 ID'),
}), }),
) )
.output(z.array(taskOutput)) .output(z.array(taskOutput))
export const updateStatus = oc export const updateStatus = oc
.route({
method: 'POST',
path: '/task/update-status',
operationId: 'taskUpdateStatus',
summary: '更新任务状态',
description: '按记录 ID 更新任务状态。',
tags: ['Task'],
})
.input( .input(
z.object({ z.object({
id: z.string().min(1), id: z.string().min(1).describe('任务记录 ID'),
status: z.enum(['pending', 'in_progress', 'done']), status: z.enum(['pending', 'in_progress', 'done']).describe('目标状态'),
}), }),
) )
.output(taskOutput) .output(taskOutput)