feat(api): 接入医签通业务接口与类型
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import { unwrapApiResponse, unwrapNullableApiResponse } from '@/utils/api-response'
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
import type {
|
||||
ApiResponseOf,
|
||||
PadBindingRequest,
|
||||
PadSessionUploadInput,
|
||||
SendSmsDeliveryRequest,
|
||||
SignDeliveryResponseDto,
|
||||
SignatureUploadResponse,
|
||||
SigningTokenUploadInput,
|
||||
TokenConsumeRequest,
|
||||
TokenConsumeResponse,
|
||||
} from './types'
|
||||
|
||||
function createIdempotencyKey() {
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return `clinical-web-${crypto.randomUUID()}`
|
||||
}
|
||||
|
||||
return `clinical-web-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
||||
}
|
||||
|
||||
function idempotencyHeaders(value?: string) {
|
||||
return { 'Idempotency-Key': value || createIdempotencyKey() }
|
||||
}
|
||||
|
||||
export function sendSigningSms(
|
||||
taskId: string,
|
||||
payload: SendSmsDeliveryRequest,
|
||||
idempotencyKey?: string,
|
||||
): Promise<SignDeliveryResponseDto | null> {
|
||||
return request
|
||||
.post<ApiResponseOf<SignDeliveryResponseDto | null>>(
|
||||
`/v1/sign-deliveries/${encodeURIComponent(taskId)}/sms/send`,
|
||||
payload,
|
||||
{ headers: idempotencyHeaders(idempotencyKey) },
|
||||
)
|
||||
.then(unwrapNullableApiResponse)
|
||||
}
|
||||
|
||||
export function resendSigningSms(
|
||||
taskId: string,
|
||||
expectedRowVersion?: number,
|
||||
idempotencyKey?: string,
|
||||
): Promise<SignDeliveryResponseDto | null> {
|
||||
const data = expectedRowVersion === undefined ? undefined : { expectedRowVersion }
|
||||
|
||||
return request
|
||||
.post<ApiResponseOf<SignDeliveryResponseDto | null>>(
|
||||
`/v1/sign-deliveries/${encodeURIComponent(taskId)}/sms/resend`,
|
||||
data,
|
||||
{ headers: idempotencyHeaders(idempotencyKey) },
|
||||
)
|
||||
.then(unwrapNullableApiResponse)
|
||||
}
|
||||
|
||||
export function createPadSigningSession(
|
||||
taskId: string,
|
||||
payload: PadBindingRequest = {},
|
||||
idempotencyKey?: string,
|
||||
): Promise<SignDeliveryResponseDto> {
|
||||
return request
|
||||
.post<ApiResponseOf<SignDeliveryResponseDto>>(
|
||||
`/v1/sign-deliveries/${encodeURIComponent(taskId)}/pad/sessions`,
|
||||
payload,
|
||||
{ headers: idempotencyHeaders(idempotencyKey) },
|
||||
)
|
||||
.then(unwrapApiResponse)
|
||||
}
|
||||
|
||||
export function consumeSigningToken(payload: TokenConsumeRequest): Promise<TokenConsumeResponse> {
|
||||
return request
|
||||
.post<ApiResponseOf<TokenConsumeResponse>>('/v1/sign-deliveries/token/consume', payload)
|
||||
.then(unwrapApiResponse)
|
||||
}
|
||||
|
||||
function createSignatureFormData(file: File | Blob, metadata: string) {
|
||||
const formData = new FormData()
|
||||
const fileName =
|
||||
typeof File !== 'undefined' && file instanceof File && file.name ? file.name : 'signature.png'
|
||||
formData.append('file', file, fileName)
|
||||
formData.append('metadata', metadata)
|
||||
return formData
|
||||
}
|
||||
|
||||
export function uploadSigningSignature(
|
||||
payload: SigningTokenUploadInput,
|
||||
): Promise<SignatureUploadResponse> {
|
||||
const formData = createSignatureFormData(payload.file, payload.metadata)
|
||||
|
||||
return request
|
||||
.post<ApiResponseOf<SignatureUploadResponse>>(
|
||||
`/v1/sign-deliveries/${encodeURIComponent(payload.taskId)}/signature`,
|
||||
formData,
|
||||
{
|
||||
params: {
|
||||
deliveryId: payload.deliveryId,
|
||||
uploadToken: payload.uploadToken,
|
||||
},
|
||||
headers: idempotencyHeaders(payload.idempotencyKey),
|
||||
},
|
||||
)
|
||||
.then(unwrapApiResponse)
|
||||
}
|
||||
|
||||
/**
|
||||
* PAD 专用兼容路径。服务端要求同时提交 PAD 元数据和真实 PNG,旧的 JSON-only 提交会返回 409。
|
||||
* 具体的 challenge、文件摘要和 storageKey 由设备适配层生成,页面不应伪造这些值。
|
||||
*/
|
||||
export function uploadPadSigningSignature(
|
||||
payload: PadSessionUploadInput,
|
||||
): Promise<SignatureUploadResponse> {
|
||||
if (!payload.pad) {
|
||||
return Promise.reject(new Error('PAD 上传缺少会话元数据'))
|
||||
}
|
||||
|
||||
const formData = createSignatureFormData(payload.file, payload.metadata)
|
||||
formData.append('challenge', payload.pad.challenge)
|
||||
formData.append('contentType', payload.pad.contentType)
|
||||
formData.append('sizeBytes', String(payload.pad.sizeBytes ?? payload.file.size))
|
||||
formData.append('sha256', payload.pad.sha256)
|
||||
formData.append('storageKey', payload.pad.storageKey)
|
||||
|
||||
return request
|
||||
.post<ApiResponseOf<SignatureUploadResponse>>(
|
||||
`/v1/sign-deliveries/${encodeURIComponent(payload.taskId)}/pad/sessions/${encodeURIComponent(payload.deliveryId)}/upload`,
|
||||
formData,
|
||||
{
|
||||
params: { uploadToken: payload.uploadToken },
|
||||
headers: idempotencyHeaders(payload.idempotencyKey),
|
||||
},
|
||||
)
|
||||
.then(unwrapApiResponse)
|
||||
}
|
||||
|
||||
export function uploadSigningSignatureByProof(
|
||||
payload: SigningTokenUploadInput,
|
||||
): Promise<SignatureUploadResponse> {
|
||||
const formData = createSignatureFormData(payload.file, payload.metadata)
|
||||
|
||||
return request
|
||||
.post<ApiResponseOf<SignatureUploadResponse>>('/v1/sign-deliveries/token/signature', formData, {
|
||||
params: {
|
||||
taskId: payload.taskId,
|
||||
deliveryId: payload.deliveryId,
|
||||
uploadToken: payload.uploadToken,
|
||||
},
|
||||
headers: idempotencyHeaders(payload.idempotencyKey),
|
||||
})
|
||||
.then(unwrapApiResponse)
|
||||
}
|
||||
Reference in New Issue
Block a user