mirror of
https://gitee.com/bookshelfplus/bookshelfplus
synced 2025-09-01 22:53:29 +08:00
删除文件对象功能完成;其他小调整
This commit is contained in:
@@ -5,6 +5,8 @@ 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.exception.CosClientException;
|
||||
import com.qcloud.cos.exception.CosServiceException;
|
||||
import com.qcloud.cos.http.HttpMethodName;
|
||||
import com.qcloud.cos.http.HttpProtocol;
|
||||
import com.qcloud.cos.model.GeneratePresignedUrlRequest;
|
||||
@@ -84,6 +86,13 @@ public class QCloudCosUtils {
|
||||
return _cosClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否存在
|
||||
*
|
||||
* @param folder
|
||||
* @param objectKey
|
||||
* @return
|
||||
*/
|
||||
public Boolean doesObjectExist(String folder, String objectKey) {
|
||||
return doesObjectExist(folder + objectKey);
|
||||
}
|
||||
@@ -97,6 +106,36 @@ public class QCloudCosUtils {
|
||||
return cosClient.doesObjectExist(bucketName, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param folder
|
||||
* @param objectKey
|
||||
* @return
|
||||
*/
|
||||
public Boolean deleteObject(String folder, String objectKey) {
|
||||
return deleteObject(folder + objectKey);
|
||||
}
|
||||
|
||||
public Boolean deleteObject(String filePath) {
|
||||
COSClient cosClient = createCOSClient();
|
||||
// 存储桶的命名格式为 BucketName-APPID,此处填写的存储桶名称必须为此格式
|
||||
String bucketName = qCloudCosConfig.getBucketName();
|
||||
// 对象键(Key)是对象在存储桶中的唯一标识。详情请参见 [对象键](https://cloud.tencent.com/document/product/436/13324)
|
||||
String key = qCloudCosConfig.getKeyName() + filePath;
|
||||
|
||||
// refer: https://cloud.tencent.com/document/product/436/65939#.E5.88.A0.E9.99.A4.E5.AF.B9.E8.B1.A1
|
||||
try {
|
||||
cosClient.deleteObject(bucketName, key);
|
||||
return true;
|
||||
} catch (CosServiceException e) {
|
||||
e.printStackTrace();
|
||||
} catch (CosClientException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成预签名URL
|
||||
* <p>
|
||||
|
@@ -1,20 +1,16 @@
|
||||
package plus.bookshelf.Controller.Controller;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.aventrix.jnanoid.jnanoid.NanoIdUtils;
|
||||
import com.qcloud.cos.http.HttpMethodName;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import plus.bookshelf.Common.Enum.FileStorageMediumEnum;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import plus.bookshelf.Common.Error.BusinessErrorCode;
|
||||
import plus.bookshelf.Common.Error.BusinessException;
|
||||
import plus.bookshelf.Common.FileManager.QCloudCosUtils;
|
||||
import plus.bookshelf.Common.Response.CommonReturnType;
|
||||
import plus.bookshelf.Config.QCloudCosConfig;
|
||||
import plus.bookshelf.Controller.VO.FileObjectVO;
|
||||
@@ -144,180 +140,6 @@ public class FileController extends BaseController {
|
||||
return CommonReturnType.create(fileVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件操作预授权URL
|
||||
*
|
||||
* @param httpMethod 请求的 HTTP 方法,上传请求用 PUT,下载请求用 GET,删除请求用 DELETE
|
||||
* @param token 当前登录用户的 token
|
||||
* @param fileName 文件名
|
||||
* @param expireMinute 过期时间(分钟)
|
||||
* @return
|
||||
* @throws BusinessException
|
||||
*/
|
||||
@ApiOperation(value = "【用户|管理员】创建腾讯云 COS 预授权 URL", notes = "")
|
||||
@RequestMapping(value = "/cos/{httpMethod}", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
|
||||
@ResponseBody
|
||||
public CommonReturnType cos(@PathVariable(value = "httpMethod") String httpMethod,
|
||||
@RequestParam(value = "token") String token,
|
||||
@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 = "fileExt", required = false) String fileExt,
|
||||
@RequestParam(value = "fileId", required = false) Integer fileId, // 关联的文件ID,创建新文件则为0
|
||||
|
||||
// 以下为 GET 请求必传参数
|
||||
@RequestParam(value = "fileNameAndExt", required = false) String fileNameAndExt,
|
||||
@RequestParam(value = "visitorId", required = false) String visitorFingerprint
|
||||
) throws BusinessException, InvocationTargetException, IllegalAccessException {
|
||||
if (expireMinute == null) {
|
||||
expireMinute = 30;
|
||||
} else if (expireMinute > 60) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "expireMinute 参数不能大于 60");
|
||||
} else if (expireMinute < 1) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "expireMinute 参数不能小于 1");
|
||||
}
|
||||
|
||||
// 已经在 getUserByToken 方法中判断了 token 为空、不合法;用户不存在情况,此处无需再判断
|
||||
UserModel userModel = userService.getUserByToken(redisTemplate, token);
|
||||
|
||||
// 判断httpMethod 是否合法
|
||||
HttpMethodName httpMethodName;
|
||||
try {
|
||||
httpMethodName = HttpMethodName.valueOf(httpMethod.toUpperCase());
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "httpMethod 参数不合法");
|
||||
}
|
||||
|
||||
QCloudCosUtils qCloudCosUtils = new QCloudCosUtils(qCloudCosConfig, cosPresignedUrlGenerateLogService);
|
||||
|
||||
// 当次生成下载链接的全局唯一Id
|
||||
String urlGUID = NanoIdUtils.randomNanoId();
|
||||
String bookSaveFolder = QCloudCosUtils.BOOK_SAVE_FOLDER;
|
||||
|
||||
// 判断对象是否存在
|
||||
Boolean isExist = qCloudCosUtils.doesObjectExist(bookSaveFolder, fileSha1);
|
||||
String url = null;
|
||||
Map resultMap = new HashMap();
|
||||
resultMap.put("guid", urlGUID);
|
||||
switch (httpMethodName) {
|
||||
case PUT:
|
||||
// 上传文件
|
||||
if (isExist) throw new BusinessException(BusinessErrorCode.FILE_ALREADY_EXIST, "文件已存在");
|
||||
|
||||
Integer[] integers = fileObjectService.uploadFile(fileId, fileName, bookSaveFolder + fileSha1, fileSize,
|
||||
fileSha1, fileExt, FileStorageMediumEnum.QCLOUD_COS, "", lastModified);
|
||||
Integer realFileId = integers[0];
|
||||
Integer fileObjectId = integers[1];
|
||||
|
||||
// fileId 可能为 0 (创建新文件)
|
||||
// realFileId 是从数据库中查询出来的,真实的文件id
|
||||
resultMap.put("fileId", realFileId);
|
||||
resultMap.put("fileObjectId", fileObjectId);
|
||||
url = qCloudCosUtils.generatePresignedUrl(userModel.getId(), httpMethodName, bookSaveFolder, fileSha1, expireMinute, urlGUID);
|
||||
break;
|
||||
case GET:
|
||||
if (!isExist) throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "文件不存在");
|
||||
if (visitorFingerprint == null || !visitorFingerprintLogService.saveFingerprint("FailureFeedback", userModel.getId(), visitorFingerprint)) {
|
||||
throw new BusinessException(BusinessErrorCode.OPERATION_NOT_ALLOWED, "参数错误,请联系管理员处理");
|
||||
}
|
||||
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 参数暂不支持");
|
||||
}
|
||||
|
||||
resultMap.put("url", url);
|
||||
return CommonReturnType.create(resultMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 腾讯云 COS 文件上传成功回调方法
|
||||
*
|
||||
* @param eventStr
|
||||
* @param contextStr
|
||||
* @return
|
||||
* @throws BusinessException
|
||||
*/
|
||||
@ApiOperation(value = "【COS】腾讯云 COS 文件上传成功回调", notes = "客户端向腾讯云 COS 存储桶上传文件完毕,有云函数触发此请求")
|
||||
@RequestMapping(value = "/upload/cos-check-file-state", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
|
||||
@ResponseBody
|
||||
public CommonReturnType cosCheckFileStatus(
|
||||
// @RequestParam() Map<String, String> params,
|
||||
@RequestParam(value = "event") String eventStr,
|
||||
@RequestParam(value = "context", required = false) String contextStr
|
||||
) throws BusinessException, InvocationTargetException, IllegalAccessException {
|
||||
|
||||
JSONObject eventObject;
|
||||
|
||||
try {
|
||||
eventObject = JSON.parseObject(eventStr);
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "event参数不合法");
|
||||
}
|
||||
|
||||
String cosBucketName, appid, cosRegion, eventName, key;
|
||||
try {
|
||||
// 获取 Records 节点
|
||||
JSONArray records = eventObject.getJSONArray("Records");
|
||||
JSONObject record = records.getJSONObject(0);
|
||||
|
||||
JSONObject cos = record.getJSONObject("cos");
|
||||
JSONObject cosBucket = cos.getJSONObject("cosBucket");
|
||||
appid = cosBucket.getString("appid");
|
||||
cosBucketName = cosBucket.getString("name");
|
||||
if (Objects.equals(appid, "1253970026") && Objects.equals(cosBucketName, "testpic")) {
|
||||
// 执行的是腾讯云云函数的测试请求
|
||||
return CommonReturnType.create("您的云函数配置成功。");
|
||||
}
|
||||
cosRegion = cosBucket.getString("cosRegion");
|
||||
|
||||
JSONObject cosObject = cos.getJSONObject("cosObject");
|
||||
key = cosObject.getString("key");
|
||||
// 获取 /1000000000/bookshelfplus/fileSha1 中的 fileSha1 部分
|
||||
key = key.substring(key.indexOf("/", key.indexOf("/", key.indexOf("/") + 1) + 1) + 1);
|
||||
|
||||
JSONObject event = record.getJSONObject("event");
|
||||
eventName = event.getString("eventName");
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "JSON解析出错!");
|
||||
}
|
||||
|
||||
// 判断是否是由系统的存储桶触发
|
||||
if (qCloudCosConfig.getBucketName().equals(cosBucketName + "-" + appid) &&
|
||||
qCloudCosConfig.getRegionName().equals(cosRegion) &&
|
||||
"cos:ObjectCreated:Put".equals(eventName)) {
|
||||
// 是由系统的存储桶触发的,则认为是文件上传成功
|
||||
|
||||
// 通过文件 key 获取文件对象
|
||||
FileObjectModel fileObject = fileObjectService.getFileObjectByFilePath(QCloudCosUtils.BOOK_SAVE_FOLDER + key);
|
||||
|
||||
// 如果找不到,就抛出异常
|
||||
if (fileObject == null) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "文件不存在!");
|
||||
}
|
||||
// 更新文件对象状态
|
||||
Boolean isSuccess1 = fileObjectService.updateFileStatus(fileObject.getId(), "SUCCESS");
|
||||
|
||||
if (!isSuccess1) {
|
||||
throw new BusinessException(BusinessErrorCode.UNKNOWN_ERROR, "更新文件状态失败");
|
||||
}
|
||||
} else {
|
||||
// 不是由系统的存储桶触发的
|
||||
return CommonReturnType.create("Not triggered by the bucket specified in the configuration file, skip.");
|
||||
}
|
||||
return CommonReturnType.create("success");
|
||||
}
|
||||
|
||||
private FileVO convertFileVOFromModel(FileModel fileModel) {
|
||||
if (fileModel == null) {
|
||||
return null;
|
||||
@@ -329,68 +151,3 @@ public class FileController extends BaseController {
|
||||
return fileVO;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
示例返回
|
||||
event
|
||||
{
|
||||
"Records": [
|
||||
{
|
||||
"cos": {
|
||||
"cosBucket": {
|
||||
"appid": "1302260381",
|
||||
"cosRegion": "ap-shanghai",
|
||||
"name": "bookshelfplus",
|
||||
"region": "sh",
|
||||
"s3Region": "ap-shanghai"
|
||||
},
|
||||
"cosNotificationId": "unkown",
|
||||
"cosObject": {
|
||||
"key": "/1302260381/bookshelfplus/!!!!!!!",
|
||||
"meta": {
|
||||
"Content-Type": "text/plain",
|
||||
"ETag": "\"0d7316832fef232e5dcdcf81f39bfdba\"",
|
||||
"x-cos-request-id": "NjI1OTNmOWNfNGIzN2YyMDlfMmJhZjVfNDJkZTBmYQ==",
|
||||
"x-cos-storage-class": "Standard"
|
||||
},
|
||||
"size": 557,
|
||||
"url": "http://bookshelfplus-1302260381.cos.ap-shanghai.myqcloud.com/%21%21%21%21%21%21%21",
|
||||
"vid": ""
|
||||
},
|
||||
"cosSchemaVersion": "1.0"
|
||||
},
|
||||
"event": {
|
||||
"eventName": "cos:ObjectCreated:Put",
|
||||
"eventQueue": "qcs:0:scf:ap-shanghai:appid/1302260381:default.bookshelf-scf.$DEFAULT",
|
||||
"eventSource": "qcs::cos",
|
||||
"eventTime": 1650016156,
|
||||
"eventVersion": "1.0",
|
||||
"reqid": 0,
|
||||
"requestParameters": {
|
||||
"requestHeaders": {
|
||||
"Authorization": "q-sign-algorithm=sha1&q-ak=AKIDgEWYJo2yd7KGvIPFn45pJWT9YgX8RTEi&q-sign-time=1650016155;1650017955&q-key-time=1650016155;1650017955&q-header-list=host&q-url-param-list=by;guid;userid&q-signature=0ceb85180d6b0b665662d5d139d4276cdc0fbbbd"
|
||||
},
|
||||
"requestSourceIP": "117.154.65.144"
|
||||
},
|
||||
"reservedInfo": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
context
|
||||
{
|
||||
"callbackWaitsForEmptyEventLoop": true,
|
||||
"memory_limit_in_mb": 512,
|
||||
"time_limit_in_ms": 3000,
|
||||
"request_id": "279ba5cc-e435-4af9-8ede-baa71373d75b",
|
||||
"environment": "{\"SCF_NAMESPACE\":\"default\"}",
|
||||
"environ": "SCF_NAMESPACE=default;SCF_NAMESPACE=default",
|
||||
"function_version": "$LATEST",
|
||||
"function_name": "bookshelf-scf",
|
||||
"namespace": "default",
|
||||
"tencentcloud_region": "ap-shanghai",
|
||||
"tencentcloud_appid": "1302260381",
|
||||
"tencentcloud_uin": "100014397291"
|
||||
}
|
||||
*/
|
@@ -1,14 +1,17 @@
|
||||
package plus.bookshelf.Controller.Controller;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.aventrix.jnanoid.jnanoid.NanoIdUtils;
|
||||
import com.qcloud.cos.http.HttpMethodName;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import plus.bookshelf.Common.Enum.FileStorageMediumEnum;
|
||||
import plus.bookshelf.Common.Error.BusinessErrorCode;
|
||||
import plus.bookshelf.Common.Error.BusinessException;
|
||||
@@ -18,16 +21,12 @@ import plus.bookshelf.Config.QCloudCosConfig;
|
||||
import plus.bookshelf.Controller.VO.FileObjectVO;
|
||||
import plus.bookshelf.Service.Impl.*;
|
||||
import plus.bookshelf.Service.Model.FailureFeedbackModel;
|
||||
import plus.bookshelf.Service.Model.FileModel;
|
||||
import plus.bookshelf.Service.Model.FileObjectModel;
|
||||
import plus.bookshelf.Service.Model.UserModel;
|
||||
import plus.bookshelf.Service.Service.CosPresignedUrlGenerateLogService;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
@Api(tags = "文件对象管理")
|
||||
@Controller("file/object")
|
||||
@@ -37,15 +36,15 @@ public class FileObjectController extends BaseController {
|
||||
@Autowired
|
||||
QCloudCosConfig qCloudCosConfig;
|
||||
|
||||
@Autowired
|
||||
CosPresignedUrlGenerateLogService cosPresignedUrlGenerateLogService;
|
||||
|
||||
@Autowired
|
||||
UserServiceImpl userService;
|
||||
|
||||
@Autowired
|
||||
FileServiceImpl fileService;
|
||||
|
||||
@Autowired
|
||||
CosPresignedUrlGenerateLogService cosPresignedUrlGenerateLogService;
|
||||
|
||||
@Autowired
|
||||
FileObjectServiceImpl fileObjectService;
|
||||
|
||||
@@ -178,6 +177,199 @@ public class FileObjectController extends BaseController {
|
||||
return CommonReturnType.create(isSuccess);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "【管理员】删除文件对象", notes = "管理员在后台删除文件对象")
|
||||
@RequestMapping(value = "delete", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
|
||||
@ResponseBody
|
||||
@Transactional
|
||||
public CommonReturnType deleteFileObject(@RequestParam(value = "token", required = false) String token,
|
||||
@RequestParam(required = true, value = "id") Integer fileObjectId) throws BusinessException {
|
||||
// 已经在 getUserByToken 方法中判断了 token 为空、不合法;用户不存在情况,此处无需再判断
|
||||
UserModel userModel = userService.getUserByToken(redisTemplate, token);
|
||||
|
||||
// 删除书籍
|
||||
Integer affectRows = fileObjectService.deleteFileObject(fileObjectId);
|
||||
|
||||
if (affectRows > 0) {
|
||||
return CommonReturnType.create("success");
|
||||
} else {
|
||||
return CommonReturnType.create("failed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件操作预授权URL
|
||||
*
|
||||
* @param httpMethod 请求的 HTTP 方法,上传请求用 PUT,下载请求用 GET,删除请求用 DELETE
|
||||
* @param token 当前登录用户的 token
|
||||
* @param fileName 文件名
|
||||
* @param expireMinute 过期时间(分钟)
|
||||
* @return
|
||||
* @throws BusinessException
|
||||
*/
|
||||
@ApiOperation(value = "【用户|管理员】创建腾讯云 COS 预授权 URL", notes = "")
|
||||
@RequestMapping(value = "/cos/{httpMethod}", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
|
||||
@ResponseBody
|
||||
public CommonReturnType cos(@PathVariable(value = "httpMethod") String httpMethod,
|
||||
@RequestParam(value = "token") String token,
|
||||
@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 = "fileExt", required = false) String fileExt,
|
||||
@RequestParam(value = "fileId", required = false) Integer fileId, // 关联的文件ID,创建新文件则为0
|
||||
|
||||
// 以下为 GET 请求必传参数
|
||||
@RequestParam(value = "fileNameAndExt", required = false) String fileNameAndExt,
|
||||
@RequestParam(value = "visitorId", required = false) String visitorFingerprint
|
||||
) throws BusinessException, InvocationTargetException, IllegalAccessException {
|
||||
if (expireMinute == null) {
|
||||
expireMinute = 30;
|
||||
} else if (expireMinute > 60) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "expireMinute 参数不能大于 60");
|
||||
} else if (expireMinute < 1) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "expireMinute 参数不能小于 1");
|
||||
}
|
||||
|
||||
// 已经在 getUserByToken 方法中判断了 token 为空、不合法;用户不存在情况,此处无需再判断
|
||||
UserModel userModel = userService.getUserByToken(redisTemplate, token);
|
||||
|
||||
// 判断httpMethod 是否合法
|
||||
HttpMethodName httpMethodName;
|
||||
try {
|
||||
httpMethodName = HttpMethodName.valueOf(httpMethod.toUpperCase());
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "httpMethod 参数不合法");
|
||||
}
|
||||
|
||||
QCloudCosUtils qCloudCosUtils = new QCloudCosUtils(qCloudCosConfig, cosPresignedUrlGenerateLogService);
|
||||
|
||||
// 当次生成下载链接的全局唯一Id
|
||||
String urlGUID = NanoIdUtils.randomNanoId();
|
||||
String bookSaveFolder = QCloudCosUtils.BOOK_SAVE_FOLDER;
|
||||
|
||||
// 判断对象是否存在
|
||||
Boolean isExist = qCloudCosUtils.doesObjectExist(bookSaveFolder, fileSha1);
|
||||
String url = null;
|
||||
Map resultMap = new HashMap();
|
||||
resultMap.put("guid", urlGUID);
|
||||
switch (httpMethodName) {
|
||||
case PUT:
|
||||
// 上传文件
|
||||
if (isExist) throw new BusinessException(BusinessErrorCode.FILE_ALREADY_EXIST, "文件已存在");
|
||||
|
||||
Integer[] integers = fileObjectService.uploadFile(fileId, fileName, bookSaveFolder + fileSha1, fileSize,
|
||||
fileSha1, fileExt, FileStorageMediumEnum.QCLOUD_COS, "", lastModified);
|
||||
Integer realFileId = integers[0];
|
||||
Integer fileObjectId = integers[1];
|
||||
|
||||
// fileId 可能为 0 (创建新文件)
|
||||
// realFileId 是从数据库中查询出来的,真实的文件id
|
||||
resultMap.put("fileId", realFileId);
|
||||
resultMap.put("fileObjectId", fileObjectId);
|
||||
url = qCloudCosUtils.generatePresignedUrl(userModel.getId(), httpMethodName, bookSaveFolder, fileSha1, expireMinute, urlGUID);
|
||||
break;
|
||||
case GET:
|
||||
if (!isExist) throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "文件不存在");
|
||||
if (visitorFingerprint == null || !visitorFingerprintLogService.saveFingerprint("FailureFeedback", userModel.getId(), visitorFingerprint)) {
|
||||
throw new BusinessException(BusinessErrorCode.OPERATION_NOT_ALLOWED, "参数错误,请联系管理员处理");
|
||||
}
|
||||
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 参数暂不支持");
|
||||
}
|
||||
|
||||
resultMap.put("url", url);
|
||||
return CommonReturnType.create(resultMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 腾讯云 COS 文件上传成功回调方法
|
||||
*
|
||||
* @param eventStr
|
||||
* @param contextStr
|
||||
* @return
|
||||
* @throws BusinessException
|
||||
*/
|
||||
@ApiOperation(value = "【COS】腾讯云 COS 文件上传成功回调", notes = "客户端向腾讯云 COS 存储桶上传文件完毕,有云函数触发此请求")
|
||||
@RequestMapping(value = "/upload/cos-check-file-state", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
|
||||
@ResponseBody
|
||||
public CommonReturnType cosCheckFileStatus(
|
||||
// @RequestParam() Map<String, String> params,
|
||||
@RequestParam(value = "event") String eventStr,
|
||||
@RequestParam(value = "context", required = false) String contextStr
|
||||
) throws BusinessException, InvocationTargetException, IllegalAccessException {
|
||||
|
||||
JSONObject eventObject;
|
||||
|
||||
try {
|
||||
eventObject = JSON.parseObject(eventStr);
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "event参数不合法");
|
||||
}
|
||||
|
||||
String cosBucketName, appid, cosRegion, eventName, key;
|
||||
try {
|
||||
// 获取 Records 节点
|
||||
JSONArray records = eventObject.getJSONArray("Records");
|
||||
JSONObject record = records.getJSONObject(0);
|
||||
|
||||
JSONObject cos = record.getJSONObject("cos");
|
||||
JSONObject cosBucket = cos.getJSONObject("cosBucket");
|
||||
appid = cosBucket.getString("appid");
|
||||
cosBucketName = cosBucket.getString("name");
|
||||
if (Objects.equals(appid, "1253970026") && Objects.equals(cosBucketName, "testpic")) {
|
||||
// 执行的是腾讯云云函数的测试请求
|
||||
return CommonReturnType.create("您的云函数配置成功。");
|
||||
}
|
||||
cosRegion = cosBucket.getString("cosRegion");
|
||||
|
||||
JSONObject cosObject = cos.getJSONObject("cosObject");
|
||||
key = cosObject.getString("key");
|
||||
// 获取 /1000000000/bookshelfplus/fileSha1 中的 fileSha1 部分
|
||||
key = key.substring(key.indexOf("/", key.indexOf("/", key.indexOf("/") + 1) + 1) + 1);
|
||||
|
||||
JSONObject event = record.getJSONObject("event");
|
||||
eventName = event.getString("eventName");
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "JSON解析出错!");
|
||||
}
|
||||
|
||||
// 判断是否是由系统的存储桶触发
|
||||
if (qCloudCosConfig.getBucketName().equals(cosBucketName + "-" + appid) &&
|
||||
qCloudCosConfig.getRegionName().equals(cosRegion) &&
|
||||
"cos:ObjectCreated:Put".equals(eventName)) {
|
||||
// 是由系统的存储桶触发的,则认为是文件上传成功
|
||||
|
||||
// 通过文件 key 获取文件对象
|
||||
FileObjectModel fileObject = fileObjectService.getFileObjectByFilePath(QCloudCosUtils.BOOK_SAVE_FOLDER + key);
|
||||
|
||||
// 如果找不到,就抛出异常
|
||||
if (fileObject == null) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "文件不存在!");
|
||||
}
|
||||
// 更新文件对象状态
|
||||
Boolean isSuccess1 = fileObjectService.updateFileStatus(fileObject.getId(), "SUCCESS");
|
||||
|
||||
if (!isSuccess1) {
|
||||
throw new BusinessException(BusinessErrorCode.UNKNOWN_ERROR, "更新文件状态失败");
|
||||
}
|
||||
} else {
|
||||
// 不是由系统的存储桶触发的
|
||||
return CommonReturnType.create("Not triggered by the bucket specified in the configuration file, skip.");
|
||||
}
|
||||
return CommonReturnType.create("success");
|
||||
}
|
||||
|
||||
public static FileObjectVO convertFileObjectVOFromModel(FileObjectModel fileObjectModel) {
|
||||
if (fileObjectModel == null) {
|
||||
return null;
|
||||
@@ -192,3 +384,68 @@ public class FileObjectController extends BaseController {
|
||||
return fileObjectVO;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
示例返回
|
||||
event
|
||||
{
|
||||
"Records": [
|
||||
{
|
||||
"cos": {
|
||||
"cosBucket": {
|
||||
"appid": "1302260381",
|
||||
"cosRegion": "ap-shanghai",
|
||||
"name": "bookshelfplus",
|
||||
"region": "sh",
|
||||
"s3Region": "ap-shanghai"
|
||||
},
|
||||
"cosNotificationId": "unkown",
|
||||
"cosObject": {
|
||||
"key": "/1302260381/bookshelfplus/!!!!!!!",
|
||||
"meta": {
|
||||
"Content-Type": "text/plain",
|
||||
"ETag": "\"0d7316832fef232e5dcdcf81f39bfdba\"",
|
||||
"x-cos-request-id": "NjI1OTNmOWNfNGIzN2YyMDlfMmJhZjVfNDJkZTBmYQ==",
|
||||
"x-cos-storage-class": "Standard"
|
||||
},
|
||||
"size": 557,
|
||||
"url": "http://bookshelfplus-1302260381.cos.ap-shanghai.myqcloud.com/%21%21%21%21%21%21%21",
|
||||
"vid": ""
|
||||
},
|
||||
"cosSchemaVersion": "1.0"
|
||||
},
|
||||
"event": {
|
||||
"eventName": "cos:ObjectCreated:Put",
|
||||
"eventQueue": "qcs:0:scf:ap-shanghai:appid/1302260381:default.bookshelf-scf.$DEFAULT",
|
||||
"eventSource": "qcs::cos",
|
||||
"eventTime": 1650016156,
|
||||
"eventVersion": "1.0",
|
||||
"reqid": 0,
|
||||
"requestParameters": {
|
||||
"requestHeaders": {
|
||||
"Authorization": "q-sign-algorithm=sha1&q-ak=AKIDgEWYJo2yd7KGvIPFn45pJWT9YgX8RTEi&q-sign-time=1650016155;1650017955&q-key-time=1650016155;1650017955&q-header-list=host&q-url-param-list=by;guid;userid&q-signature=0ceb85180d6b0b665662d5d139d4276cdc0fbbbd"
|
||||
},
|
||||
"requestSourceIP": "117.154.65.144"
|
||||
},
|
||||
"reservedInfo": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
context
|
||||
{
|
||||
"callbackWaitsForEmptyEventLoop": true,
|
||||
"memory_limit_in_mb": 512,
|
||||
"time_limit_in_ms": 3000,
|
||||
"request_id": "279ba5cc-e435-4af9-8ede-baa71373d75b",
|
||||
"environment": "{\"SCF_NAMESPACE\":\"default\"}",
|
||||
"environ": "SCF_NAMESPACE=default;SCF_NAMESPACE=default",
|
||||
"function_version": "$LATEST",
|
||||
"function_name": "bookshelf-scf",
|
||||
"namespace": "default",
|
||||
"tencentcloud_region": "ap-shanghai",
|
||||
"tencentcloud_appid": "1302260381",
|
||||
"tencentcloud_uin": "100014397291"
|
||||
}
|
||||
*/
|
@@ -8,10 +8,13 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import plus.bookshelf.Common.Enum.FileStorageMediumEnum;
|
||||
import plus.bookshelf.Common.Error.BusinessErrorCode;
|
||||
import plus.bookshelf.Common.Error.BusinessException;
|
||||
import plus.bookshelf.Common.FileManager.QCloudCosUtils;
|
||||
import plus.bookshelf.Config.QCloudCosConfig;
|
||||
import plus.bookshelf.Dao.DO.FileObjectDO;
|
||||
import plus.bookshelf.Dao.Mapper.FileObjectDOMapper;
|
||||
import plus.bookshelf.Service.Model.FileModel;
|
||||
import plus.bookshelf.Service.Model.FileObjectModel;
|
||||
import plus.bookshelf.Service.Service.CosPresignedUrlGenerateLogService;
|
||||
import plus.bookshelf.Service.Service.FileObjectService;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -35,6 +38,12 @@ public class FileObjectServiceImpl implements FileObjectService {
|
||||
@Autowired
|
||||
FileServiceImpl fileService;
|
||||
|
||||
@Autowired
|
||||
QCloudCosConfig qCloudCosConfig;
|
||||
|
||||
@Autowired
|
||||
CosPresignedUrlGenerateLogService cosPresignedUrlGenerateLogService;
|
||||
|
||||
/**
|
||||
* 通过书本Id获取关联文件,进而获取所有关联文件对应的文件对象
|
||||
*
|
||||
@@ -252,4 +261,41 @@ public class FileObjectServiceImpl implements FileObjectService {
|
||||
|
||||
return fileObjectModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件对象
|
||||
*
|
||||
* @param fileObjectId
|
||||
* @return
|
||||
* @throws BusinessException
|
||||
*/
|
||||
@Override
|
||||
public Integer deleteFileObject(Integer fileObjectId) throws BusinessException {
|
||||
if (fileObjectId == null || fileObjectId == 0) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "文件对象 Id 不能为空");
|
||||
}
|
||||
|
||||
FileObjectDO fileObjectDO = fileObjectDOMapper.selectByPrimaryKey(fileObjectId);
|
||||
if (fileObjectDO == null) {
|
||||
throw new BusinessException(BusinessErrorCode.PARAMETER_VALIDATION_ERROR, "文件对象不存在,或许您已经删掉这个文件了,刷新下页面吧");
|
||||
}
|
||||
|
||||
// 删除对象存储中的文件
|
||||
QCloudCosUtils qCloudCosUtils = new QCloudCosUtils(qCloudCosConfig, cosPresignedUrlGenerateLogService);
|
||||
Boolean isSuccess = qCloudCosUtils.deleteObject(fileObjectDO.getFilePath());
|
||||
if (!isSuccess) {
|
||||
throw new BusinessException(BusinessErrorCode.UNKNOWN_ERROR, "删除文件对象失败,请稍后重试");
|
||||
}
|
||||
|
||||
// 确认文件是否被删除
|
||||
Boolean isStillExist = qCloudCosUtils.doesObjectExist(fileObjectDO.getFilePath());
|
||||
if (isStillExist) {
|
||||
//
|
||||
throw new BusinessException(BusinessErrorCode.UNKNOWN_ERROR, "删除腾讯云COS存储桶中文件出现异常,请稍后重试");
|
||||
}
|
||||
|
||||
// 删除数据库中记录
|
||||
int affecctRows = fileObjectDOMapper.deleteByPrimaryKey(fileObjectId);
|
||||
return affecctRows;
|
||||
}
|
||||
}
|
||||
|
@@ -93,4 +93,13 @@ public interface FileObjectService {
|
||||
* @throws BusinessException
|
||||
*/
|
||||
List<FileObjectModel> getFileObjectListByFileId(Integer fileId) throws InvocationTargetException, IllegalAccessException, BusinessException;
|
||||
|
||||
/**
|
||||
* 删除文件对象
|
||||
*
|
||||
* @param fileObjectId
|
||||
* @return
|
||||
* @throws BusinessException
|
||||
*/
|
||||
Integer deleteFileObject(Integer fileObjectId) throws BusinessException;
|
||||
}
|
||||
|
Reference in New Issue
Block a user