From 666be6ea601e6299371e035476c855b1c917f670 Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 26 Dec 2025 05:48:22 -0800 Subject: [PATCH] =?UTF-8?q?fix(planting-service):=20=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E5=90=8E=E6=9F=A5=E7=9C=8B=E5=90=88=E5=90=8C=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E5=B7=B2=E7=AD=BE=E5=90=8D=E7=9A=84PDF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 /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 --- .../contract-signing.controller.ts | 50 ++++++++++++++----- .../services/contract-signing.service.ts | 2 + .../storage/minio-storage.service.ts | 39 +++++++++++++++ 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/backend/services/planting-service/src/api/controllers/contract-signing.controller.ts b/backend/services/planting-service/src/api/controllers/contract-signing.controller.ts index 47eec4e0..8956df61 100644 --- a/backend/services/planting-service/src/api/controllers/contract-signing.controller.ts +++ b/backend/services/planting-service/src/api/controllers/contract-signing.controller.ts @@ -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 { + 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, + }); + } } diff --git a/backend/services/planting-service/src/application/services/contract-signing.service.ts b/backend/services/planting-service/src/application/services/contract-signing.service.ts index 96a7b0b4..e22f3235 100644 --- a/backend/services/planting-service/src/application/services/contract-signing.service.ts +++ b/backend/services/planting-service/src/application/services/contract-signing.service.ts @@ -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, }; } } diff --git a/backend/services/planting-service/src/infrastructure/storage/minio-storage.service.ts b/backend/services/planting-service/src/infrastructure/storage/minio-storage.service.ts index 15288db7..275b9d36 100644 --- a/backend/services/planting-service/src/infrastructure/storage/minio-storage.service.ts +++ b/backend/services/planting-service/src/infrastructure/storage/minio-storage.service.ts @@ -137,6 +137,45 @@ export class MinioStorageService implements OnModuleInit { } } + /** + * 下载已签署的合同 PDF + * @param signedPdfUrl PDF 文件的公开 URL + * @returns PDF 文件 Buffer + */ + async downloadSignedPdf(signedPdfUrl: string): Promise { + 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