From 372a22e2e4912ecdef2e9e2c222c99368a7f767c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E5=B0=8F=E5=A2=A8?= <2291200076@qq.com> Date: Sat, 16 Apr 2022 22:19:46 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=9A=E8=BF=87=E4=B9=A6=E6=9C=ACID=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=89=80=E6=9C=89=E6=96=87=E4=BB=B6=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=EF=BC=9B=E5=89=8D=E7=AB=AF=E4=B9=A6=E7=B1=8D?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E9=A1=B5=E9=9D=A2=E7=9B=B4=E9=93=BE=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E5=8A=9F=E8=83=BD=E5=AE=8C=E6=88=90=EF=BC=9B=E5=90=8E?= =?UTF-8?q?=E7=AB=AFCOS=E9=83=A8=E5=88=86=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E5=96=84=EF=BC=9B=E4=B8=8B=E8=BD=BD=E6=96=87=E4=BB=B6=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE"Content-Disposition"=E4=B8=BA"attachment;=20filename?= =?UTF-8?q?=3D"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bookshelfplus-frontend/views/book.html | 79 ++++++++++++++++--- .../Common/FileManager/QCloudCosUtils.java | 63 ++++++++++++++- .../Controller/Controller/FileController.java | 33 ++++++-- .../Dao/Mapper/FileObjectDOMapper.java | 8 ++ .../Service/Impl/FileObjectServiceImpl.java | 19 +++++ .../Service/Service/FileObjectService.java | 10 +++ .../resources/mapping/FileObjectDOMapper.xml | 7 ++ 7 files changed, 196 insertions(+), 23 deletions(-) diff --git a/bookshelfplus-frontend/views/book.html b/bookshelfplus-frontend/views/book.html index 79bfc3e..252ba01 100644 --- a/bookshelfplus-frontend/views/book.html +++ b/bookshelfplus-frontend/views/book.html @@ -196,8 +196,8 @@ // 由于多个请求之后都需要调用该方法,为避免多次压缩,使用计数器,等最后一个请求完成后执行一次 var requestCount = 2; function doFontmin() { - console.log("所有请求完毕,开始获取字体"); if (--requestCount == 0) { + console.log("开始获取字体"); fontmin(getPageText()); } } @@ -373,6 +373,7 @@ + \ No newline at end of file diff --git a/bookshelfplus/src/main/java/plus/bookshelf/Common/FileManager/QCloudCosUtils.java b/bookshelfplus/src/main/java/plus/bookshelf/Common/FileManager/QCloudCosUtils.java index 3e2072c..4b06d38 100644 --- a/bookshelfplus/src/main/java/plus/bookshelf/Common/FileManager/QCloudCosUtils.java +++ b/bookshelfplus/src/main/java/plus/bookshelf/Common/FileManager/QCloudCosUtils.java @@ -2,10 +2,13 @@ package plus.bookshelf.Common.FileManager; import com.qcloud.cos.COSClient; import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.Headers; import com.qcloud.cos.auth.BasicCOSCredentials; import com.qcloud.cos.auth.COSCredentials; import com.qcloud.cos.http.HttpMethodName; import com.qcloud.cos.http.HttpProtocol; +import com.qcloud.cos.model.GeneratePresignedUrlRequest; +import com.qcloud.cos.model.ResponseHeaderOverrides; import com.qcloud.cos.region.Region; import plus.bookshelf.Common.Error.BusinessErrorCode; import plus.bookshelf.Common.Error.BusinessException; @@ -86,7 +89,7 @@ public class QCloudCosUtils { // 存储桶的命名格式为 BucketName-APPID,此处填写的存储桶名称必须为此格式 String bucketName = qCloudCosConfig.getBucketName(); // 对象键(Key)是对象在存储桶中的唯一标识。详情请参见 [对象键](https://cloud.tencent.com/document/product/436/13324) - String key = qCloudCosConfig.getKeyName() + objectKey; + String key = qCloudCosConfig.getKeyName() + folder + objectKey; return cosClient.doesObjectExist(bucketName, key); } @@ -104,13 +107,12 @@ public class QCloudCosUtils { public String generatePresignedUrl(Integer userId, HttpMethodName httpMethodName, String savePath, String objectKey, Integer expireMinute, String urlGUID) throws BusinessException { // 调用 COS 接口之前必须保证本进程存在一个 COSClient 实例,如果没有则创建 // 详细代码参见本页:简单操作 -> 创建 COSClient - // COSClient cosClient = createCOSClient(); COSClient cosClient = createCOSClient(); // 存储桶的命名格式为 BucketName-APPID,此处填写的存储桶名称必须为此格式 String bucketName = qCloudCosConfig.getBucketName(); // 对象键(Key)是对象在存储桶中的唯一标识。详情请参见 [对象键](https://cloud.tencent.com/document/product/436/13324) - String key = qCloudCosConfig.getKeyName() + objectKey; + String key = qCloudCosConfig.getKeyName() + savePath + objectKey; // 设置签名过期时间(可选), 若未进行设置则默认使用 ClientConfig 中的签名过期时间(1小时) // 这里设置签名在 expireMinute 分钟后过期 @@ -142,6 +144,61 @@ public class QCloudCosUtils { return url.toString(); } + /** + * 生成预签名URL (下载使用,用于下载时指定下载的文件名) + *

+ * refer: https://cloud.tencent.com/document/product/436/35217 + * + * @param userId 当前登录用户的 id + * @param savePath + * @param objectKey 文件对象的 key + * @param expireMinute 过期时间 + * @param urlGUID + * @param fileNameForUser + * @return + * @throws BusinessException + */ + public String generatePresignedUrlForGET(Integer userId, String savePath, String objectKey, Integer expireMinute, String urlGUID, String fileNameForUser) throws BusinessException { + // 调用 COS 接口之前必须保证本进程存在一个 COSClient 实例,如果没有则创建 + // 详细代码参见本页:简单操作 -> 创建 COSClient + COSClient cosClient = createCOSClient(); + + // 存储桶的命名格式为 BucketName-APPID,此处填写的存储桶名称必须为此格式 + String bucketName = qCloudCosConfig.getBucketName(); + // 对象键(Key)是对象在存储桶中的唯一标识。详情请参见 [对象键](https://cloud.tencent.com/document/product/436/13324) + String key = qCloudCosConfig.getKeyName() + savePath + objectKey; + + GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(bucketName, key, HttpMethodName.GET); + + // 设置下载时返回的 http 头 + ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides(); + responseHeaders.setContentDisposition("attachment; filename=\"" + fileNameForUser + "\""); // 作为附件下载;设置返回头部里包含文件名信息 + responseHeaders.setCacheControl("no-cache"); + req.setResponseHeaders(responseHeaders); + + // 设置签名过期时间(可选),若未进行设置,则默认使用 ClientConfig 中的签名过期时间(1小时) + // 这里设置签名在半个小时后过期 + Date expirationDate = new Date(System.currentTimeMillis() + 30L * 60L * 1000L); + req.setExpiration(expirationDate); + + // 填写本次请求的参数,需与实际请求相同,能够防止用户篡改此签名的 HTTP 请求的参数 + Map params = new HashMap<>(); + req.addRequestParameter("by", "书栖网 bookshelf.plus"); + req.addRequestParameter("userId", String.valueOf(userId)); + req.addRequestParameter("guid", urlGUID); + // 填写本次请求的头部 + // host 必填 + req.putCustomRequestHeader(Headers.HOST, cosClient.getClientConfig().getEndpointBuilder().buildGeneralApiEndpoint(bucketName)); + // req.putCustomRequestHeader("header1", "value1"); + + URL url = cosClient.generatePresignedUrl(req); + + // 记录用户操作日志 + cosPresignedUrlGenerateLogService.log(userId, expireMinute, HttpMethodName.GET.name(), key, urlGUID); + + return url.toString(); + } + /** * 销毁 CosClient 对象方法 */ diff --git a/bookshelfplus/src/main/java/plus/bookshelf/Controller/Controller/FileController.java b/bookshelfplus/src/main/java/plus/bookshelf/Controller/Controller/FileController.java index 98b2f06..6488273 100644 --- a/bookshelfplus/src/main/java/plus/bookshelf/Controller/Controller/FileController.java +++ b/bookshelfplus/src/main/java/plus/bookshelf/Controller/Controller/FileController.java @@ -53,7 +53,7 @@ public class FileController extends BaseController { @ApiOperation(value = "书籍下载页面获取文件提供的下载方式", notes = "") @RequestMapping(value = "getFile", method = {RequestMethod.GET}) @ResponseBody - public CommonReturnType getFile(@RequestParam(value = "bookId", required = false) Integer bookId) throws BusinessException { + public CommonReturnType getFile(@RequestParam(value = "bookId", required = false) Integer bookId) throws BusinessException, InvocationTargetException, IllegalAccessException { List fileModels = fileService.getFile(bookId); List fileVOS = new ArrayList<>(); @@ -61,7 +61,19 @@ public class FileController extends BaseController { FileVO fileVO = convertFileVOFromModel(fileModel); fileVOS.add(fileVO); } - return CommonReturnType.create(fileVOS); + + List fileObjectModels = fileObjectService.getFileObjectByBookId(bookId); + List fileObjectVOS = new ArrayList<>(); + for (FileObjectModel fileObjectModel : fileObjectModels) { + FileObjectVO fileObjectVO = convertFileObjectVOFromModel(fileObjectModel); + fileObjectVOS.add(fileObjectVO); + } + + Map map = new HashMap<>(); + map.put("file", fileVOS); + map.put("fileObject", fileObjectVOS); + + return CommonReturnType.create(map); } @ApiOperation(value = "【管理员】查询文件列表", notes = "查询文件列表") @@ -142,16 +154,19 @@ public class FileController extends BaseController { @ResponseBody public CommonReturnType cos(@PathVariable(value = "httpMethod") String httpMethod, @RequestParam(value = "token") String token, - @RequestParam(value = "fileName") String fileName, // 不含扩展名 + @RequestParam(value = "fileSha1") String fileSha1, @RequestParam(value = "expireMinute", required = false) Integer expireMinute, // 以下为 PUT 请求必传参数 + @RequestParam(value = "fileName", required = false) String fileName, // 不含扩展名 @RequestParam(value = "fileSize", required = false) Long fileSize, // @RequestParam(value = "fileType", required = false) String fileType, @RequestParam(value = "lastModified", required = false) Long lastModified, - @RequestParam(value = "fileSha1", required = false) String fileSha1, @RequestParam(value = "fileExt", required = false) String fileExt, - @RequestParam(value = "fileId", required = false) Integer fileId // 关联的文件ID,创建新文件则为0 + @RequestParam(value = "fileId", required = false) Integer fileId, // 关联的文件ID,创建新文件则为0 + + // 以下为 GET 请求必传参数 + @RequestParam(value = "fileNameAndExt", required = false) String fileNameAndExt ) throws BusinessException, InvocationTargetException, IllegalAccessException { if (expireMinute == null) { expireMinute = 30; @@ -180,26 +195,28 @@ public class FileController extends BaseController { // 判断对象是否存在 Boolean isExist = qCloudCosUtils.doesObjectExist(bookSaveFolder, fileSha1); - switch (httpMethodName) { + String url = null; + switch (httpMethodName) { case PUT: // 上传文件 if (isExist) throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "文件已存在"); fileObjectService.uploadFile(fileId, fileName, bookSaveFolder + fileSha1, fileSize, fileSha1, fileExt, fileName, FileStorageMediumEnum.QCLOUD_COS, "", lastModified); + url = qCloudCosUtils.generatePresignedUrl(userModel.getId(), httpMethodName, bookSaveFolder, fileSha1, expireMinute, urlGUID); break; case GET: if (!isExist) throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "文件不存在"); + url = qCloudCosUtils.generatePresignedUrlForGET(userModel.getId(), bookSaveFolder, fileSha1, expireMinute, urlGUID, fileNameAndExt); break; case DELETE: if (!isExist) throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "文件不存在"); + url = qCloudCosUtils.generatePresignedUrl(userModel.getId(), httpMethodName, bookSaveFolder, fileSha1, expireMinute, urlGUID); break; default: throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "httpMethod 参数暂不支持"); } - String url = qCloudCosUtils.generatePresignedUrl(userModel.getId(), httpMethodName, bookSaveFolder, fileSha1, 30, urlGUID); - Map map = new HashMap(); map.put("url", url); map.put("guid", urlGUID); diff --git a/bookshelfplus/src/main/java/plus/bookshelf/Dao/Mapper/FileObjectDOMapper.java b/bookshelfplus/src/main/java/plus/bookshelf/Dao/Mapper/FileObjectDOMapper.java index 63b08ad..528f629 100644 --- a/bookshelfplus/src/main/java/plus/bookshelf/Dao/Mapper/FileObjectDOMapper.java +++ b/bookshelfplus/src/main/java/plus/bookshelf/Dao/Mapper/FileObjectDOMapper.java @@ -67,4 +67,12 @@ public interface FileObjectDOMapper { * @return */ FileObjectDO selectByFilePath(String filePath); + + /** + * 通过书本Id获取关联文件,进而获取所有关联文件对应的文件对象 + * + * @param bookId 书本Id + * @return + */ + FileObjectDO[] selectFileObjectByBookId(Integer bookId); } \ No newline at end of file diff --git a/bookshelfplus/src/main/java/plus/bookshelf/Service/Impl/FileObjectServiceImpl.java b/bookshelfplus/src/main/java/plus/bookshelf/Service/Impl/FileObjectServiceImpl.java index 41992f9..3d84fd6 100644 --- a/bookshelfplus/src/main/java/plus/bookshelf/Service/Impl/FileObjectServiceImpl.java +++ b/bookshelfplus/src/main/java/plus/bookshelf/Service/Impl/FileObjectServiceImpl.java @@ -36,6 +36,25 @@ public class FileObjectServiceImpl implements FileObjectService { @Autowired FileServiceImpl fileService; + /** + * 通过书本Id获取关联文件,进而获取所有关联文件对应的文件对象 + * + * @return + */ + @Override + public List getFileObjectByBookId(Integer bookId) throws InvocationTargetException, IllegalAccessException { + + FileObjectDO[] fileObjectDOS = fileObjectDOMapper.selectFileObjectByBookId(bookId); + + List fileObjectModels = new ArrayList<>(); + for (FileObjectDO fileObjectDO : fileObjectDOS) { + FileObjectModel fileObjectModel = convertFromDataObject(fileObjectDO); + fileObjectModels.add(fileObjectModel); + } + + return fileObjectModels; + } + /** * 列出所有文件对象 * diff --git a/bookshelfplus/src/main/java/plus/bookshelf/Service/Service/FileObjectService.java b/bookshelfplus/src/main/java/plus/bookshelf/Service/Service/FileObjectService.java index 16295d4..e59bce2 100644 --- a/bookshelfplus/src/main/java/plus/bookshelf/Service/Service/FileObjectService.java +++ b/bookshelfplus/src/main/java/plus/bookshelf/Service/Service/FileObjectService.java @@ -10,6 +10,16 @@ import java.util.List; public interface FileObjectService { + /** + * 通过书本Id获取关联文件,进而获取所有关联文件对应的文件对象 + * + * @param bookId + * @return + * @throws InvocationTargetException + * @throws IllegalAccessException + */ + List getFileObjectByBookId(Integer bookId) throws InvocationTargetException, IllegalAccessException; + /** * 列出所有文件对象 * diff --git a/bookshelfplus/src/main/resources/mapping/FileObjectDOMapper.xml b/bookshelfplus/src/main/resources/mapping/FileObjectDOMapper.xml index 5ad4171..2afa27f 100644 --- a/bookshelfplus/src/main/resources/mapping/FileObjectDOMapper.xml +++ b/bookshelfplus/src/main/resources/mapping/FileObjectDOMapper.xml @@ -228,4 +228,11 @@ from file_object_info where file_path = #{filePath} + \ No newline at end of file