diff --git a/src/main/java/im/zhaojun/zfile/core/util/OnlyOfficeKeyCacheUtils.java b/src/main/java/im/zhaojun/zfile/core/util/OnlyOfficeKeyCacheUtils.java index 5ee513f..56036b8 100644 --- a/src/main/java/im/zhaojun/zfile/core/util/OnlyOfficeKeyCacheUtils.java +++ b/src/main/java/im/zhaojun/zfile/core/util/OnlyOfficeKeyCacheUtils.java @@ -2,11 +2,15 @@ package im.zhaojun.zfile.core.util; import cn.hutool.cache.Cache; import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.CacheObj; import im.zhaojun.zfile.module.onlyoffice.model.OnlyOfficeFile; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.RandomStringUtils; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @@ -83,6 +87,45 @@ public class OnlyOfficeKeyCacheUtils { return onlyOfficeFile; } + /** + * 清理缓存中的文件信息与 Key 的映射关系.(文件发生了变化, 需要重新生成 OnlyOffice 预览链接时调用) + * + * @param onlyOfficeFile + * OnlyOffice 文件信息 + */ + public static OnlyOfficeFile removeByFile(OnlyOfficeFile onlyOfficeFile) { + String key = ONLY_OFFICE_FILE_KEY_MAP.get(onlyOfficeFile); + if (key == null) { + return null; + } + ONLY_OFFICE_FILE_KEY_MAP.remove(onlyOfficeFile); + ONLY_OFFICE_KEY_FILE_MAP.remove(key); + return onlyOfficeFile; + } + + + /** + * 清理缓存中的某个文件夹下所有文件信息与 Key 的映射关系.(文件发生了变化, 需要重新生成 OnlyOffice 预览链接时调用) + * + * @param onlyOfficeFile + * OnlyOffice 文件信息 + */ + public static List removeByFolder(OnlyOfficeFile onlyOfficeFile) { + List caches = new ArrayList<>(); + Iterator> cacheObjIterator = ONLY_OFFICE_FILE_KEY_MAP.cacheObjIterator(); + while (cacheObjIterator.hasNext()) { + CacheObj cacheObj = cacheObjIterator.next(); + OnlyOfficeFile cacheOnlyOfficeFile = cacheObj.getKey(); + if (cacheOnlyOfficeFile.getStorageKey().equals(onlyOfficeFile.getStorageKey()) + && StringUtils.startWith(cacheOnlyOfficeFile.getPathAndName(), onlyOfficeFile.getPathAndName())) { + ONLY_OFFICE_FILE_KEY_MAP.remove(cacheObj.getKey()); + ONLY_OFFICE_KEY_FILE_MAP.remove(cacheObj.getValue()); + caches.add(cacheOnlyOfficeFile); + } + } + return caches; + } + /** * 获取文件锁, 防止并发操作文件缓存时出现问题. * diff --git a/src/main/java/im/zhaojun/zfile/module/onlyoffice/controller/OnlyOfficeController.java b/src/main/java/im/zhaojun/zfile/module/onlyoffice/controller/OnlyOfficeController.java index b1d245a..f0b5cf4 100644 --- a/src/main/java/im/zhaojun/zfile/module/onlyoffice/controller/OnlyOfficeController.java +++ b/src/main/java/im/zhaojun/zfile/module/onlyoffice/controller/OnlyOfficeController.java @@ -101,6 +101,9 @@ public class OnlyOfficeController { throw new BizException("文件不存在"); } + String currentUserBasePath = fileService.getCurrentUserBasePath(); + fileItemRequest.setPath(currentUserBasePath + fileItemRequest.getPath()); + boolean hasUploadPermission = userStorageSourceService.hasCurrentUserStorageOperatorPermission(storageId, FileOperatorTypeEnum.UPLOAD); return Pair.of(fileItem, hasUploadPermission); } catch (Exception e) { @@ -163,17 +166,17 @@ public class OnlyOfficeController { log.debug("OnlyOffice 回调信息: {}, {}", onlyOfficeCallback.getStatus(), onlyOfficeCallback); boolean useOnlyOfficeSecret = StrUtil.isNotBlank(systemConfigService.getSystemConfig().getOnlyOfficeSecret()); if (useOnlyOfficeSecret) { - + if (StrUtil.isBlank(onlyOfficeCallback.getToken())) { log.error("OnlyOffice 回调 Token 为空: {}", onlyOfficeCallback); return CALLBACK_ERROR_MSG; } - + if (!JWTUtil.verify(onlyOfficeCallback.getToken(), StrUtil.bytes(systemConfigService.getSystemConfig().getOnlyOfficeSecret(), StandardCharsets.UTF_8))) { log.error("OnlyOffice 回调 Token 验证失败: {}", onlyOfficeCallback); - return CALLBACK_ERROR_MSG; + return CALLBACK_ERROR_MSG; } - + } // 文件发送了变化,清空缓存中该文件的 key 信息. if (SUPPORTED_STATUS.contains(onlyOfficeCallback.getStatus())) { diff --git a/src/main/java/im/zhaojun/zfile/module/storage/aspect/FileOperatorCheckAspect.java b/src/main/java/im/zhaojun/zfile/module/storage/aspect/FileOperatorCheckAspect.java index b967baf..63951fe 100644 --- a/src/main/java/im/zhaojun/zfile/module/storage/aspect/FileOperatorCheckAspect.java +++ b/src/main/java/im/zhaojun/zfile/module/storage/aspect/FileOperatorCheckAspect.java @@ -1,16 +1,21 @@ package im.zhaojun.zfile.module.storage.aspect; -import im.zhaojun.zfile.core.util.ZFileAuthUtil; -import im.zhaojun.zfile.module.user.model.entity.User; -import org.apache.commons.lang3.BooleanUtils; import im.zhaojun.zfile.core.exception.biz.StorageSourceIllegalOperationBizException; +import im.zhaojun.zfile.core.util.CollectionUtils; +import im.zhaojun.zfile.core.util.OnlyOfficeKeyCacheUtils; +import im.zhaojun.zfile.core.util.StringUtils; +import im.zhaojun.zfile.core.util.ZFileAuthUtil; +import im.zhaojun.zfile.module.onlyoffice.model.OnlyOfficeFile; import im.zhaojun.zfile.module.storage.annotation.StoragePermissionCheck; import im.zhaojun.zfile.module.storage.model.enums.FileOperatorTypeEnum; import im.zhaojun.zfile.module.storage.service.StorageSourceService; import im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService; +import im.zhaojun.zfile.module.user.model.entity.User; import im.zhaojun.zfile.module.user.model.entity.UserStorageSource; import im.zhaojun.zfile.module.user.service.UserStorageSourceService; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.BooleanUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; @@ -18,8 +23,9 @@ import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; import java.lang.reflect.Method; +import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -85,7 +91,8 @@ public class FileOperatorCheckAspect { @Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.fileList(..)) || " + "execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.getFileItem(..))") public Object availableAround(ProceedingJoinPoint point) throws Throwable { - return check(point, FileOperatorTypeEnum.AVAILABLE); + checkPermission(point, FileOperatorTypeEnum.AVAILABLE); + return point.proceed(); } /** @@ -98,7 +105,8 @@ public class FileOperatorCheckAspect { */ @Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.newFolder(..))") public Object newFolderAround(ProceedingJoinPoint point) throws Throwable { - return check(point, FileOperatorTypeEnum.NEW_FOLDER); + checkPermission(point, FileOperatorTypeEnum.NEW_FOLDER); + return point.proceed(); } /** @@ -111,7 +119,19 @@ public class FileOperatorCheckAspect { */ @Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.delete*(..))") public Object deleteAround(ProceedingJoinPoint point) throws Throwable { - return check(point, FileOperatorTypeEnum.DELETE); + checkPermission(point, FileOperatorTypeEnum.DELETE); + + Object result = point.proceed(); + + boolean isFolder = point.getSignature().getName().equals("deleteFolder"); + AbstractBaseFileService targetService = (AbstractBaseFileService) point.getTarget(); + String path = (String) point.getArgs()[0]; + String name = (String) point.getArgs()[1]; + String currentUserBasePath = targetService.getCurrentUserBasePath(); + String fullPath = StringUtils.concat(currentUserBasePath, path, name); + clearOnlyOfficeCache(fullPath, targetService.storageId, isFolder); + + return result; } /** @@ -125,7 +145,26 @@ public class FileOperatorCheckAspect { @Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.getUploadUrl(..)) || " + "execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractProxyTransferService.uploadFile(..))") public Object uploadAround(ProceedingJoinPoint point) throws Throwable { - return check(point, FileOperatorTypeEnum.UPLOAD); + checkPermission(point, FileOperatorTypeEnum.UPLOAD); + + Object[] args = point.getArgs(); + AbstractBaseFileService targetService = (AbstractBaseFileService) point.getTarget(); + String currentUserBasePath = targetService.getCurrentUserBasePath(); + + String fullPath; + String methodName = point.getSignature().getName(); + if (Objects.equals(methodName, "getUploadUrl")) { + fullPath = StringUtils.concat(currentUserBasePath, (String) args[0], (String) args[1]); + } else if (Objects.equals(methodName, "uploadFile")) { + fullPath = StringUtils.concat(currentUserBasePath, (String) args[0]); + } else { + throw new IllegalArgumentException("上传校验异常."); + } + + Object result = point.proceed(); + clearOnlyOfficeCache(fullPath, targetService.storageId, false); + + return result; } /** @@ -138,7 +177,22 @@ public class FileOperatorCheckAspect { */ @Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.rename*(..))") public Object renameAround(ProceedingJoinPoint point) throws Throwable { - return check(point, FileOperatorTypeEnum.RENAME); + checkPermission(point, FileOperatorTypeEnum.RENAME); + + AbstractBaseFileService targetService = (AbstractBaseFileService) point.getTarget(); + String currentUserBasePath = targetService.getCurrentUserBasePath(); + + Object[] args = point.getArgs(); + String path = (String) args[0]; + String name = (String) args[1]; + String newName = (String) args[2]; + String sourceFullPath = StringUtils.concat(currentUserBasePath, path, name); + String targetFullPath = StringUtils.concat(currentUserBasePath, path, newName); + + Object result = point.proceed(); + clearOnlyOfficeCache(sourceFullPath, targetService.storageId, Objects.equals(point.getSignature().getName(), "renameFolder")); + + return result; } /** @@ -151,7 +205,17 @@ public class FileOperatorCheckAspect { */ @Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.move*(..))") public Object moveAround(ProceedingJoinPoint point) throws Throwable { - return check(point, FileOperatorTypeEnum.MOVE); + checkPermission(point, FileOperatorTypeEnum.MOVE); + Object result = point.proceed(); + + AbstractBaseFileService targetService = (AbstractBaseFileService) point.getTarget(); + String path = (String) point.getArgs()[0]; + String name = (String) point.getArgs()[1]; + String currentUserBasePath = targetService.getCurrentUserBasePath(); + String fullPath = StringUtils.concat(currentUserBasePath, path, name); + clearOnlyOfficeCache(fullPath, targetService.storageId, Objects.equals(point.getSignature().getName(), "moveFolder")); + + return result; } /** @@ -164,7 +228,8 @@ public class FileOperatorCheckAspect { */ @Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.copy*(..))") public Object copyAround(ProceedingJoinPoint point) throws Throwable { - return check(point, FileOperatorTypeEnum.COPY); + checkPermission(point, FileOperatorTypeEnum.COPY); + return point.proceed(); } /** @@ -175,10 +240,8 @@ public class FileOperatorCheckAspect { * * @param fileOperatorType * 文件操作类型 - * - * @return 方法运行结果 */ - private Object check(ProceedingJoinPoint point, FileOperatorTypeEnum fileOperatorType) throws Throwable { + private void checkPermission(ProceedingJoinPoint point, FileOperatorTypeEnum fileOperatorType) { // 获取对应的存储源 service AbstractBaseFileService targetService = (AbstractBaseFileService) point.getTarget(); Integer storageId = targetService.storageId; @@ -188,8 +251,6 @@ public class FileOperatorCheckAspect { if (BooleanUtils.isFalse(allowAccess)) { throw new StorageSourceIllegalOperationBizException(storageId, fileOperatorType); } - - return point.proceed(); } @@ -215,4 +276,32 @@ public class FileOperatorCheckAspect { return permissions.contains(fileOperatorType.getValue()); } + /** + * 清除 OnlyOffice 缓存 + * + * @param fullPath + * 文件全路径(包含用户路径) + * + * @param storageId + * 存储源 ID + */ + private void clearOnlyOfficeCache(String fullPath, Integer storageId, boolean isFolder) { + try { + String storageKey = storageSourceService.findStorageKeyById(storageId); + if (isFolder) { + List caches = OnlyOfficeKeyCacheUtils.removeByFolder(new OnlyOfficeFile(storageKey, fullPath)); + if (CollectionUtils.isNotEmpty(caches)) { + log.debug("删除/重命名文件夹时, 清除 OnlyOffice 缓存 {} 个", caches); + } + } else { + OnlyOfficeFile onlyOfficeFile = OnlyOfficeKeyCacheUtils.removeByFile(new OnlyOfficeFile(storageKey, fullPath)); + if (onlyOfficeFile != null) { + log.debug("删除/重命名文件时, 清除 OnlyOffice 缓存: {}", onlyOfficeFile); + } + } + } catch (Exception e) { + log.error("清除 OnlyOffice 缓存失败", e); + } + } + } \ No newline at end of file