fix(planting-service): 签名后查看合同返回已签名的PDF

- 修改 /tasks/:orderNo/pdf 接口,检查任务状态
- 如果已签名且有 signedPdfUrl,从 MinIO 下载已签名的 PDF
- 添加 downloadSignedPdf 方法到 MinioStorageService
- 在 ContractSigningTaskDto 中添加 signedPdfUrl 字段

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-26 05:48:22 -08:00
parent 86103e4c4d
commit 666be6ea60
3 changed files with 79 additions and 12 deletions

View File

@ -428,18 +428,23 @@ export class ContractSigningController {
throw new Error('签署任务不存在');
}
// 格式化签署日期
const signingDate = new Date().toISOString().split('T')[0];
let pdfBuffer: Buffer;
// 生成 PDF使用 pdf-lib 直接操作 PDF 模板)
const pdfBuffer = await this.pdfGeneratorService.generateContractPdf({
contractNo: task.contractNo,
userRealName: task.userRealName || '未认证',
userIdCard: task.userIdCardNumber || '',
userPhone: task.userPhoneNumber || '',
treeCount: task.treeCount,
signingDate,
});
// 如果已签名且有签名PDF URL从MinIO下载已签名的PDF
if (task.status === 'SIGNED' && task.signedPdfUrl) {
this.logger.log(`Downloading signed PDF from MinIO for order ${orderNo}`);
try {
pdfBuffer = await this.minioStorageService.downloadSignedPdf(task.signedPdfUrl);
this.logger.log(`Signed PDF downloaded: ${pdfBuffer.length} bytes`);
} catch (downloadError) {
this.logger.warn(`Failed to download signed PDF, regenerating: ${downloadError.message}`);
// 下载失败时回退到重新生成
pdfBuffer = await this.regeneratePreviewPdf(task);
}
} else {
// 未签名生成预览PDF
pdfBuffer = await this.regeneratePreviewPdf(task);
}
// 设置响应头
res.set({
@ -450,8 +455,29 @@ export class ContractSigningController {
return new StreamableFile(pdfBuffer);
} catch (error) {
this.logger.error(`Failed to generate PDF for order ${orderNo}:`, error);
this.logger.error(`Failed to get PDF for order ${orderNo}:`, error);
throw error;
}
}
/**
* PDF
*/
private async regeneratePreviewPdf(task: {
contractNo: string;
userRealName?: string;
userIdCardNumber?: string;
userPhoneNumber?: string;
treeCount: number;
}): Promise<Buffer> {
const signingDate = new Date().toISOString().split('T')[0];
return this.pdfGeneratorService.generateContractPdf({
contractNo: task.contractNo,
userRealName: task.userRealName || '未认证',
userIdCard: task.userIdCardNumber || '',
userPhone: task.userPhoneNumber || '',
treeCount: task.treeCount,
signingDate,
});
}
}

View File

@ -56,6 +56,7 @@ export interface ContractSigningTaskDto {
scrolledToBottomAt?: Date;
acknowledgedAt?: Date;
signedAt?: Date;
signedPdfUrl?: string;
}
/**
@ -466,6 +467,7 @@ export class ContractSigningService {
scrolledToBottomAt: task.scrolledToBottomAt,
acknowledgedAt: task.acknowledgedAt,
signedAt: task.signedAt,
signedPdfUrl: task.signedPdfUrl,
};
}
}

View File

@ -137,6 +137,45 @@ export class MinioStorageService implements OnModuleInit {
}
}
/**
* PDF
* @param signedPdfUrl PDF URL
* @returns PDF Buffer
*/
async downloadSignedPdf(signedPdfUrl: string): Promise<Buffer> {
try {
// 从 URL 中提取对象名称
const objectName = this.extractObjectName(signedPdfUrl);
const dataStream = await this.minioClient.getObject(this.bucketName, objectName);
// 将流转换为 Buffer
const chunks: Buffer[] = [];
for await (const chunk of dataStream) {
chunks.push(Buffer.from(chunk));
}
const buffer = Buffer.concat(chunks);
this.logger.log(`Downloaded signed PDF: ${objectName}, size: ${buffer.length} bytes`);
return buffer;
} catch (error) {
this.logger.error(`Failed to download signed PDF from ${signedPdfUrl}: ${error.message}`);
throw new Error(`已签署合同下载失败: ${error.message}`);
}
}
/**
* URL
*/
private extractObjectName(objectUrl: string): string {
// URL 格式: https://minio.xxx.com/contracts/contracts/orderNo/signed-contract-xxx.pdf
// 需要提取: contracts/orderNo/signed-contract-xxx.pdf
const url = new URL(objectUrl);
const pathParts = url.pathname.split('/').filter(Boolean);
// 第一个是桶名,后面的是对象路径
return pathParts.slice(1).join('/');
}
/**
*
* @param objectUrl URL