From 146d3469b617fc995b51fcbe823cdc80358cfb79 Mon Sep 17 00:00:00 2001 From: caojia <1301869380@qq.com> Date: Thu, 12 Nov 2026 09:27:10 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=94=AF=E6=8C=81=E5=BA=94?= =?UTF-8?q?=E6=94=B6=E8=B4=A6=E5=8D=95=E5=AF=BC=E5=87=BA=E9=80=9A=E8=BF=87?= =?UTF-8?q?=20OSS=20=E7=AD=BE=E5=90=8D=E9=93=BE=E6=8E=A5=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../receivable/ArBillController.java | 9 +++- .../com/bms/common/utils/file/OssStorage.java | 29 +++++++++++++ .../receivable/service/IArBillService.java | 3 ++ .../service/impl/ArBillServiceImpl.java | 42 +++++++++++++++---- bms-front/src/api/ar/bill.js | 4 ++ bms-front/src/views/ar/bill/index.vue | 18 +++++++- 6 files changed, 94 insertions(+), 11 deletions(-) diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/receivable/ArBillController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/receivable/ArBillController.java index c66b2a04..8a5495c7 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/receivable/ArBillController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/receivable/ArBillController.java @@ -325,9 +325,16 @@ public class ArBillController extends BaseController { } /** - * 下载已成功生成的导出文件;文件内容由服务层从临时文件或配置存储读取。 + * 获取已成功生成导出文件的下载方式;OSS 文件返回短时签名链接,本地历史文件走原下载接口。 */ @PreAuthorize("@ss.hasPermi('ar:bill:download')") + @GetMapping("/export/{exportId}/download-url") + public AjaxResult exportDownloadInfo(@PathVariable Long exportId) { + return success(billService.getExportDownloadInfo(exportId)); + } + + /** 下载本地历史导出文件;OSS 文件由前端通过签名链接直接下载。 */ + @PreAuthorize("@ss.hasPermi('ar:bill:download')") @GetMapping("/export/{exportId}/download") public void download(@PathVariable Long exportId, HttpServletResponse response) throws Exception { billService.downloadExport(exportId, response); diff --git a/bms-back/bms-common/src/main/java/com/bms/common/utils/file/OssStorage.java b/bms-back/bms-common/src/main/java/com/bms/common/utils/file/OssStorage.java index 8226c3b7..f9732481 100644 --- a/bms-back/bms-common/src/main/java/com/bms/common/utils/file/OssStorage.java +++ b/bms-back/bms-common/src/main/java/com/bms/common/utils/file/OssStorage.java @@ -1,18 +1,25 @@ package com.bms.common.utils.file; +import java.net.URL; +import java.net.URLEncoder; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Date; import com.aliyun.oss.ClientBuilderConfiguration; +import com.aliyun.oss.HttpMethod; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.CannedAccessControlList; +import com.aliyun.oss.model.GeneratePresignedUrlRequest; import com.aliyun.oss.model.OSSObject; import com.aliyun.oss.model.ObjectMetadata; import com.aliyun.oss.model.PutObjectRequest; +import com.aliyun.oss.model.ResponseHeaderOverrides; import com.bms.common.config.OssProperties; import com.bms.common.exception.ServiceException; import org.springframework.stereotype.Component; @@ -67,6 +74,28 @@ public class OssStorage { } } + /** 为私有 OSS 对象生成带附件文件名的限时下载链接。 */ + public String generatePresignedDownloadUrl(String storageKey, String fileName, Date expiresAt) { + String objectKey = resolveObjectKey(storageKey); + OSS client = createClient(); + try { + GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest( + properties.getBucket(), objectKey, HttpMethod.GET); + request.setExpiration(expiresAt); + + String downloadName = StringUtils.hasText(fileName) ? fileName : "账单导出.xlsx"; + String encodedFileName = URLEncoder.encode(downloadName, StandardCharsets.UTF_8).replace("+", "%20"); + ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides(); + responseHeaders.setContentDisposition("attachment; filename=" + encodedFileName + + ";filename*=utf-8''" + encodedFileName); + request.setResponseHeaders(responseHeaders); + URL signedUrl = client.generatePresignedUrl(request); + return signedUrl.toExternalForm(); + } finally { + client.shutdown(); + } + } + public void delete(String storageKey) { String objectKey = resolveObjectKey(storageKey); OSS client = createClient(); diff --git a/bms-back/bms-system/src/main/java/com/bms/receivable/service/IArBillService.java b/bms-back/bms-system/src/main/java/com/bms/receivable/service/IArBillService.java index acb25574..a0384626 100644 --- a/bms-back/bms-system/src/main/java/com/bms/receivable/service/IArBillService.java +++ b/bms-back/bms-system/src/main/java/com/bms/receivable/service/IArBillService.java @@ -3,6 +3,7 @@ package com.bms.receivable.service; import java.math.BigDecimal; import java.util.Date; import java.util.List; +import java.util.Map; import com.bms.receivable.domain.ArBill; import com.bms.receivable.domain.ArBankAccount; import com.bms.receivable.domain.ArBillDetail; @@ -85,6 +86,8 @@ public interface IArBillService List selectExports(Long arBillId); + Map getExportDownloadInfo(Long exportId); + void downloadExport(Long exportId, jakarta.servlet.http.HttpServletResponse response) throws Exception; List selectLogList(ArBillLog query); diff --git a/bms-back/bms-system/src/main/java/com/bms/receivable/service/impl/ArBillServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/receivable/service/impl/ArBillServiceImpl.java index 7795d202..8e27d0be 100644 --- a/bms-back/bms-system/src/main/java/com/bms/receivable/service/impl/ArBillServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/receivable/service/impl/ArBillServiceImpl.java @@ -119,6 +119,7 @@ public class ArBillServiceImpl implements IArBillService { private static final Logger log = LoggerFactory.getLogger(ArBillServiceImpl.class); private static final String EXPORT_TEMPLATE_BUSINESS_TYPE = "AR_BILL"; private static final String EXPORT_TEMPLATE_RESOURCE = "templates/receivable/ar_bill_template.xlsx"; + private static final long EXPORT_DOWNLOAD_URL_TTL_MILLIS = 10 * 60 * 1000L; /** * Excel 行号从 1 开始;模板第 4 行是表头,明细从第 5 行开始写入。 */ @@ -1245,17 +1246,29 @@ public class ArBillServiceImpl implements IArBillService { return exports; } + @Override + public Map getExportDownloadInfo(Long exportId) { + ArBillExport export = requireDownloadableExport(exportId); + String resource = export.getStorageKey(); + Map info = new HashMap<>(3); + if (resource.startsWith("oss://")) { + Date expiresAt = new Date(System.currentTimeMillis() + EXPORT_DOWNLOAD_URL_TTL_MILLIS); + info.put("direct", true); + info.put("downloadUrl", ossStorage.generatePresignedDownloadUrl(resource, export.getFileName(), expiresAt)); + info.put("expiresAt", expiresAt.getTime()); + } else { + if (!FileUtils.checkAllowDownload(resource)) { + throw new ServiceException("导出文件路径非法"); + } + info.put("direct", false); + } + return info; + } + @Override public void downloadExport(Long exportId, HttpServletResponse response) throws Exception { long startedAt = System.nanoTime(); - ArBillExport export = exportMapper.selectById(exportId); - if (export == null) { - throw new ServiceException("账单不存在或无权访问"); - } - billScope.requireBillAccessible(export.getArBillId()); - if (!ArExportStatus.SUCCESS.getCode().equals(export.getExportStatus()) || StringUtils.isEmpty(export.getStorageKey())) { - throw new ServiceException("导出文件不存在或尚未生成成功"); - } + ArBillExport export = requireDownloadableExport(exportId); String resource = export.getStorageKey(); boolean ossFile = resource.startsWith("oss://"); if (!ossFile && !FileUtils.checkAllowDownload(resource)) { @@ -1272,6 +1285,19 @@ public class ArBillServiceImpl implements IArBillService { exportId, export.getFileSize(), elapsedMillis(startedAt)); } + private ArBillExport requireDownloadableExport(Long exportId) { + ArBillExport export = exportMapper.selectById(exportId); + if (export == null) { + throw new ServiceException("账单不存在或无权访问"); + } + billScope.requireBillAccessible(export.getArBillId()); + if (!ArExportStatus.SUCCESS.getCode().equals(export.getExportStatus()) + || StringUtils.isEmpty(export.getStorageKey())) { + throw new ServiceException("导出文件不存在或尚未生成成功"); + } + return export; + } + @Override public List selectLogList(ArBillLog query) { long startedAt = System.nanoTime(); diff --git a/bms-front/src/api/ar/bill.js b/bms-front/src/api/ar/bill.js index 6e080c87..e0ffc2f9 100644 --- a/bms-front/src/api/ar/bill.js +++ b/bms-front/src/api/ar/bill.js @@ -91,6 +91,10 @@ export function downloadArBillExport(exportId) { return request({ url: '/ar/bill/export/' + exportId + '/download', method: 'get', responseType: 'blob' }) } +export function getArBillExportDownloadInfo(exportId) { + return request({ url: '/ar/bill/export/' + exportId + '/download-url', method: 'get' }) +} + export function listArBillLogs(query) { return request({ url: '/ar/bill/log/list', method: 'get', params: query }) } diff --git a/bms-front/src/views/ar/bill/index.vue b/bms-front/src/views/ar/bill/index.vue index 0ed33c99..84c2e87b 100644 --- a/bms-front/src/views/ar/bill/index.vue +++ b/bms-front/src/views/ar/bill/index.vue @@ -69,7 +69,7 @@