mirror of
https://github.com/continew-org/continew-admin.git
synced 2025-12-09 20:57:12 +08:00
feat(system/file): 新增支持文件回收站
This commit is contained in:
@@ -37,6 +37,7 @@ import top.continew.starter.core.util.URLUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -55,6 +56,9 @@ public class FileRecorderImpl implements FileRecorder {
|
||||
|
||||
@Override
|
||||
public boolean save(FileInfo fileInfo) {
|
||||
if (fileInfo.getAttr() == null) {
|
||||
return true;
|
||||
}
|
||||
// 保存文件信息
|
||||
FileDO file = new FileDO(fileInfo);
|
||||
StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false));
|
||||
@@ -87,7 +91,7 @@ public class FileRecorderImpl implements FileRecorder {
|
||||
public boolean delete(String url) {
|
||||
FileDO file = this.getFileByUrl(url);
|
||||
if (file == null) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return fileMapper.lambdaUpdate().eq(FileDO::getId, file.getId()).remove();
|
||||
}
|
||||
@@ -131,7 +135,7 @@ public class FileRecorderImpl implements FileRecorder {
|
||||
// 结合存储配置进行匹配
|
||||
List<StorageDO> storageList = storageMapper.selectByIds(CollUtils.mapToList(list, FileDO::getStorageId));
|
||||
Map<Long, StorageDO> storageMap = storageList.stream()
|
||||
.collect(Collectors.toMap(StorageDO::getId, storage -> storage));
|
||||
.collect(Collectors.toMap(StorageDO::getId, Function.identity(), (existing, replacement) -> existing));
|
||||
return list.stream().filter(file -> {
|
||||
// http://localhost:8000/file/user/avatar/6825e687db4174e7a297a5f8.png => http://localhost:8000/file/user/avatar
|
||||
String urlPrefix = StrUtil.subBefore(url, StringConstants.SLASH, true);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.system.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import top.continew.admin.system.model.query.FileQuery;
|
||||
import top.continew.admin.system.model.resp.file.FileResp;
|
||||
import top.continew.admin.system.service.FileRecycleService;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
|
||||
/**
|
||||
* 文件回收站管理 API
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/11/11 21:28
|
||||
*/
|
||||
@Tag(name = "文件回收站管理 API")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/system/file/recycle")
|
||||
public class FileRecycleController {
|
||||
|
||||
private final FileRecycleService baseService;
|
||||
|
||||
@Operation(summary = "分页查询列表", description = "分页查询列表")
|
||||
@SaCheckPermission("system:fileRecycle:list")
|
||||
@GetMapping
|
||||
public PageResp<FileResp> page(@Valid FileQuery query, @Valid PageQuery pageQuery) {
|
||||
return baseService.page(query, pageQuery);
|
||||
}
|
||||
|
||||
@Operation(summary = "还原文件", description = "还原文件")
|
||||
@SaCheckPermission("system:fileRecycle:restore")
|
||||
@PutMapping("/restore/{id}")
|
||||
public void restore(@PathVariable Long id) {
|
||||
baseService.restore(id);
|
||||
}
|
||||
|
||||
@Operation(summary = "删除文件", description = "删除文件")
|
||||
@SaCheckPermission("system:fileRecycle:delete")
|
||||
@DeleteMapping("/{id}")
|
||||
public void delete(@PathVariable Long id) {
|
||||
baseService.delete(id);
|
||||
}
|
||||
|
||||
@Operation(summary = "清空回收站", description = "清空回收站")
|
||||
@SaCheckPermission("system:fileRecycle:clean")
|
||||
@DeleteMapping("/clean")
|
||||
public void clean() {
|
||||
baseService.clean();
|
||||
}
|
||||
}
|
||||
@@ -89,5 +89,10 @@ public enum StorageTypeEnum implements BaseEnum<Integer> {
|
||||
if (StrUtil.isNotBlank(req.getDomain())) {
|
||||
req.setDomain(StrUtil.appendIfMissing(req.getDomain(), StringConstants.SLASH));
|
||||
}
|
||||
// 回收站路径需要以 “/” 结尾
|
||||
if (Boolean.TRUE.equals(req.getRecycleBinEnabled())) {
|
||||
req.setRecycleBinPath(StrUtil.appendIfMissing(StrUtil.removePrefix(req
|
||||
.getRecycleBinPath(), StringConstants.SLASH), StringConstants.SLASH));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,14 @@
|
||||
|
||||
package top.continew.admin.system.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
import top.continew.admin.system.model.entity.FileDO;
|
||||
import top.continew.admin.system.model.resp.file.FileStatisticsResp;
|
||||
import top.continew.starter.data.mapper.BaseMapper;
|
||||
@@ -40,4 +46,57 @@ public interface FileMapper extends BaseMapper<FileDO> {
|
||||
*/
|
||||
@Select("SELECT type, COUNT(1) number, SUM(size) size FROM sys_file WHERE deleted = 0 AND type != 0 GROUP BY type")
|
||||
List<FileStatisticsResp> statistics();
|
||||
|
||||
/**
|
||||
* 分页查询回收站列表
|
||||
*
|
||||
* @param page 分页条件
|
||||
* @param queryWrapper 查询条件
|
||||
* @return 回收站分页列表信息
|
||||
*/
|
||||
@Select("SELECT * FROM sys_file ${ew.customSqlSegment}")
|
||||
Page<FileDO> selectPageInRecycleBin(@Param("page") IPage<FileDO> page,
|
||||
@Param(Constants.WRAPPER) LambdaQueryWrapper<FileDO> queryWrapper);
|
||||
|
||||
/**
|
||||
* 根据 ID 查询(文件已进入回收站)
|
||||
*
|
||||
* @param id ID
|
||||
* @return 文件信息
|
||||
*/
|
||||
@Select("SELECT * FROM sys_file WHERE id = #{id} AND deleted = 1")
|
||||
FileDO selectByIdInRecycleBin(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 查询回收站文件列表
|
||||
*
|
||||
* @return 回收站文件列表
|
||||
*/
|
||||
@Select("SELECT * FROM sys_file WHERE deleted = 1")
|
||||
List<FileDO> selectListInRecycleBin();
|
||||
|
||||
/**
|
||||
* 从回收站恢复文件
|
||||
*
|
||||
* @param id ID
|
||||
* @param userId 用户 ID
|
||||
*/
|
||||
@Update("UPDATE sys_file SET deleted = 0, update_user = #{userId}, update_time = NOW() WHERE id = #{id}")
|
||||
void restoreInRecycleBin(@Param("id") Long id, @Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 删除文件(不进入回收站)
|
||||
*
|
||||
* @param ids ID 列表
|
||||
* @param userId 用户 ID
|
||||
*/
|
||||
void deleteWithoutRecycleBin(@Param("ids") List<Long> ids, @Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 清空文件回收站
|
||||
*
|
||||
* @param userId 用户 ID
|
||||
*/
|
||||
@Update("UPDATE sys_file SET deleted = id, update_user = #{userId}, update_time = NOW() WHERE deleted = 1")
|
||||
void cleanRecycleBin(@Param("userId") Long userId);
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package top.continew.admin.system.model.entity;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -114,6 +115,12 @@ public class FileDO extends BaseDO {
|
||||
*/
|
||||
private Long storageId;
|
||||
|
||||
/**
|
||||
* 是否已删除(0:否;1:回收站)
|
||||
*/
|
||||
@TableLogic(value = "0", delval = "1")
|
||||
private Long deleted;
|
||||
|
||||
/**
|
||||
* 基于 {@link FileInfo} 构建文件信息对象
|
||||
*
|
||||
@@ -143,7 +150,7 @@ public class FileDO extends BaseDO {
|
||||
/**
|
||||
* 转换为 {@link FileInfo} 文件信息对象
|
||||
*
|
||||
* @param storage 存储配置信息
|
||||
* @param storage 存储配置
|
||||
* @return {@link FileInfo} 文件信息对象
|
||||
*/
|
||||
public FileInfo toFileInfo(StorageDO storage) {
|
||||
|
||||
@@ -22,8 +22,8 @@ import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.admin.common.base.model.entity.BaseDO;
|
||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.admin.system.enums.StorageTypeEnum;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.encrypt.field.annotation.FieldEncrypt;
|
||||
@@ -86,6 +86,16 @@ public class StorageDO extends BaseDO {
|
||||
*/
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 启用回收站
|
||||
*/
|
||||
private Boolean recycleBinEnabled;
|
||||
|
||||
/**
|
||||
* 回收站路径
|
||||
*/
|
||||
private String recycleBinPath;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package top.continew.admin.system.model.req;
|
||||
|
||||
import cn.sticki.spel.validator.constrain.SpelNotBlank;
|
||||
import cn.sticki.spel.validator.jakarta.SpelValid;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -38,6 +40,7 @@ import java.io.Serializable;
|
||||
* @since 2023/12/26 22:09
|
||||
*/
|
||||
@Data
|
||||
@SpelValid
|
||||
@Schema(description = "存储创建或修改请求参数")
|
||||
public class StorageReq implements Serializable {
|
||||
|
||||
@@ -109,6 +112,20 @@ public class StorageReq implements Serializable {
|
||||
@NotBlank(message = "访问路径不能为空", groups = ValidationGroup.Storage.Local.class)
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 启用回收站
|
||||
*/
|
||||
@Schema(description = "启用回收站", example = "true")
|
||||
@NotNull(message = "启用回收站无效")
|
||||
private Boolean recycleBinEnabled;
|
||||
|
||||
/**
|
||||
* 回收站路径
|
||||
*/
|
||||
@Schema(description = "回收站路径", example = ".RECYCLE.BIN/")
|
||||
@SpelNotBlank(condition = "#this.recycleBinEnabled == true", message = "回收站路径不能为空")
|
||||
private String recycleBinPath;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
|
||||
@@ -85,6 +85,18 @@ public class StorageResp extends BaseDetailResp {
|
||||
@Schema(description = "域名", example = "http://localhost:8000/file")
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 启用回收站
|
||||
*/
|
||||
@Schema(description = "启用回收站", example = "true")
|
||||
private Boolean recycleBinEnabled;
|
||||
|
||||
/**
|
||||
* 回收站路径
|
||||
*/
|
||||
@Schema(description = "回收站路径", example = ".RECYCLE.BIN/")
|
||||
private String recycleBinPath;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.system.service;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import top.continew.admin.system.model.query.FileQuery;
|
||||
import top.continew.admin.system.model.resp.file.FileResp;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
|
||||
/**
|
||||
* 文件回收站业务接口
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/11/11 21:28
|
||||
*/
|
||||
public interface FileRecycleService {
|
||||
|
||||
/**
|
||||
* 分页查询列表
|
||||
*
|
||||
* @param query 查询参数
|
||||
* @param pageQuery 分页参数
|
||||
* @return 文件列表
|
||||
*/
|
||||
PageResp<FileResp> page(@Valid FileQuery query, @Valid PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 还原文件
|
||||
*
|
||||
* @param id ID
|
||||
*/
|
||||
void restore(Long id);
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param id ID
|
||||
*/
|
||||
void delete(Long id);
|
||||
|
||||
/**
|
||||
* 清空回收站
|
||||
*/
|
||||
void clean();
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.admin.system.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.x.file.storage.core.FileInfo;
|
||||
import org.dromara.x.file.storage.core.FileStorageService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import top.continew.admin.common.context.UserContextHolder;
|
||||
import top.continew.admin.system.mapper.FileMapper;
|
||||
import top.continew.admin.system.model.entity.FileDO;
|
||||
import top.continew.admin.system.model.entity.StorageDO;
|
||||
import top.continew.admin.system.model.query.FileQuery;
|
||||
import top.continew.admin.system.model.resp.file.FileResp;
|
||||
import top.continew.admin.system.service.FileRecycleService;
|
||||
import top.continew.admin.system.service.StorageService;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.exception.BusinessException;
|
||||
import top.continew.starter.data.util.QueryWrapperHelper;
|
||||
import top.continew.starter.extension.crud.model.query.PageQuery;
|
||||
import top.continew.starter.extension.crud.model.resp.PageResp;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 文件回收站业务实现
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2025/11/11 21:28
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class FileRecycleServiceImpl implements FileRecycleService {
|
||||
|
||||
private final FileMapper fileMapper;
|
||||
private final StorageService storageService;
|
||||
private final FileStorageService fileStorageService;
|
||||
|
||||
@Override
|
||||
public PageResp<FileResp> page(FileQuery query, PageQuery pageQuery) {
|
||||
QueryWrapper<FileDO> queryWrapper = QueryWrapperHelper.build(query, pageQuery.getSort());
|
||||
Page<FileDO> page = fileMapper.selectPageInRecycleBin(new Page<>(pageQuery.getPage(), pageQuery
|
||||
.getSize()), queryWrapper.lambda().eq(FileDO::getDeleted, 1L));
|
||||
return PageResp.build(page, FileResp.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void restore(Long id) {
|
||||
FileDO file = this.getById(id);
|
||||
// 恢复记录
|
||||
fileMapper.restoreInRecycleBin(id, UserContextHolder.getUserId());
|
||||
// 还原文件
|
||||
StorageDO storage = storageService.getById(file.getStorageId());
|
||||
FileInfo fileInfo = file.toFileInfo(storage);
|
||||
fileInfo.setPath(storage.getRecycleBinPath() + fileInfo.getPath());
|
||||
String newPath = fileInfo.getPath().replace(storage.getRecycleBinPath(), StringConstants.EMPTY);
|
||||
fileStorageService.move(fileInfo).setPath(newPath).move();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
FileDO file = this.getById(id);
|
||||
// 删除记录
|
||||
fileMapper.deleteWithoutRecycleBin(List.of(id), UserContextHolder.getUserId());
|
||||
// 删除文件
|
||||
StorageDO storage = storageService.getById(file.getStorageId());
|
||||
FileInfo fileInfo = file.toFileInfo(storage);
|
||||
fileInfo.setPath(storage.getRecycleBinPath() + fileInfo.getPath());
|
||||
fileStorageService.delete(fileInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void clean() {
|
||||
// 查询文件
|
||||
List<FileDO> list = fileMapper.selectListInRecycleBin();
|
||||
// 删除记录
|
||||
fileMapper.cleanRecycleBin(UserContextHolder.getUserId());
|
||||
// 删除文件
|
||||
// 批量获取存储配置
|
||||
Map<Long, List<FileDO>> fileListGroup = list.stream().collect(Collectors.groupingBy(FileDO::getStorageId));
|
||||
List<StorageDO> storageList = storageService.listByIds(fileListGroup.keySet());
|
||||
Map<Long, StorageDO> storageGroup = storageList.stream()
|
||||
.collect(Collectors.toMap(StorageDO::getId, Function.identity(), (existing, replacement) -> existing));
|
||||
// 删除文件
|
||||
for (Map.Entry<Long, List<FileDO>> entry : fileListGroup.entrySet()) {
|
||||
StorageDO storage = storageGroup.get(entry.getKey());
|
||||
// 清空回收站
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
fileInfo.setPlatform(storage.getCode());
|
||||
fileInfo.setBasePath(StringConstants.EMPTY);
|
||||
fileInfo.setPath(storage.getRecycleBinPath());
|
||||
fileStorageService.delete(fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 查询
|
||||
*
|
||||
* @param id ID
|
||||
* @return 文件信息
|
||||
*/
|
||||
private FileDO getById(Long id) {
|
||||
FileDO file = fileMapper.selectByIdInRecycleBin(id);
|
||||
if (file == null) {
|
||||
throw new BusinessException("ID 为 [%s] 的文件已不存在".formatted(id));
|
||||
}
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,10 @@ import org.dromara.x.file.storage.core.ProgressListener;
|
||||
import org.dromara.x.file.storage.core.upload.UploadPretreatment;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import top.continew.admin.common.base.service.BaseServiceImpl;
|
||||
import top.continew.admin.common.context.UserContextHolder;
|
||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||
import top.continew.admin.system.enums.FileTypeEnum;
|
||||
import top.continew.admin.system.mapper.FileMapper;
|
||||
@@ -45,6 +47,7 @@ import top.continew.admin.system.service.FileService;
|
||||
import top.continew.admin.system.service.StorageService;
|
||||
import top.continew.starter.cache.redisson.util.RedisLockUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.CollUtils;
|
||||
import top.continew.starter.core.util.StrUtils;
|
||||
import top.continew.starter.core.util.validation.CheckUtils;
|
||||
import top.continew.starter.core.util.validation.ValidationUtils;
|
||||
@@ -52,6 +55,7 @@ import top.continew.starter.core.util.validation.ValidationUtils;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -71,25 +75,32 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
|
||||
private StorageService storageService;
|
||||
|
||||
@Override
|
||||
public void beforeDelete(List<Long> ids) {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(List<Long> ids) {
|
||||
List<FileDO> fileList = baseMapper.lambdaQuery().in(FileDO::getId, ids).list();
|
||||
if (CollUtil.isEmpty(fileList)) {
|
||||
return;
|
||||
}
|
||||
// 批量获取存储配置
|
||||
Map<Long, List<FileDO>> fileListGroup = fileList.stream().collect(Collectors.groupingBy(FileDO::getStorageId));
|
||||
List<StorageDO> storageList = storageService.listByIds(fileListGroup.keySet());
|
||||
Map<Long, StorageDO> storageGroup = storageList.stream()
|
||||
.collect(Collectors.toMap(StorageDO::getId, Function.identity(), (existing, replacement) -> existing));
|
||||
// 删除记录
|
||||
for (Map.Entry<Long, List<FileDO>> entry : fileListGroup.entrySet()) {
|
||||
StorageDO storage = storageService.getById(entry.getKey());
|
||||
for (FileDO file : entry.getValue()) {
|
||||
if (!FileTypeEnum.DIR.equals(file.getType())) {
|
||||
FileInfo fileInfo = file.toFileInfo(storage);
|
||||
fileStorageService.delete(fileInfo);
|
||||
} else {
|
||||
// 不允许删除非空文件夹
|
||||
boolean exists = baseMapper.lambdaQuery()
|
||||
.eq(FileDO::getParentPath, file.getPath())
|
||||
.eq(FileDO::getStorageId, entry.getKey())
|
||||
.exists();
|
||||
CheckUtils.throwIf(exists, "文件夹 [{}] 不为空,请先删除文件夹下的内容", file.getName());
|
||||
}
|
||||
StorageDO storage = storageGroup.get(entry.getKey());
|
||||
List<Long> idList = CollUtils.mapToList(entry.getValue(), FileDO::getId);
|
||||
if (Boolean.TRUE.equals(storage.getRecycleBinEnabled())) {
|
||||
baseMapper.deleteByIds(idList);
|
||||
} else {
|
||||
baseMapper.deleteWithoutRecycleBin(idList, UserContextHolder.getUserId());
|
||||
}
|
||||
}
|
||||
// 删除实际文件
|
||||
for (Map.Entry<Long, List<FileDO>> entry : fileListGroup.entrySet()) {
|
||||
StorageDO storage = storageGroup.get(entry.getKey());
|
||||
entry.getValue().forEach(file -> this.deleteFile(file, storage));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -322,4 +333,32 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除实际文件
|
||||
*
|
||||
* @param file 文件
|
||||
* @param storage 存储配置
|
||||
*/
|
||||
private void deleteFile(FileDO file, StorageDO storage) {
|
||||
Long storageId = storage.getId();
|
||||
if (FileTypeEnum.DIR.equals(file.getType())) {
|
||||
// 不允许删除非空文件夹
|
||||
boolean exists = baseMapper.lambdaQuery()
|
||||
.eq(FileDO::getParentPath, file.getPath())
|
||||
.eq(FileDO::getStorageId, storageId)
|
||||
.exists();
|
||||
CheckUtils.throwIf(exists, "文件夹 [{}] 不为空,请先删除文件夹下的内容", file.getName());
|
||||
return;
|
||||
}
|
||||
FileInfo fileInfo = file.toFileInfo(storage);
|
||||
if (Boolean.TRUE.equals(storage.getRecycleBinEnabled())) {
|
||||
// 移动到回收站目录
|
||||
fileInfo.setId(file.getId().toString());
|
||||
fileStorageService.move(fileInfo).setPath(storage.getRecycleBinPath() + fileInfo.getPath()).move();
|
||||
} else {
|
||||
// 删除文件
|
||||
fileStorageService.delete(fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,9 +91,11 @@ public class StorageServiceImpl extends BaseServiceImpl<StorageMapper, StorageDO
|
||||
if (StorageTypeEnum.OSS.equals(req.getType())) {
|
||||
req.setSecretKey(this.decryptSecretKey(req.getSecretKey(), oldStorage));
|
||||
}
|
||||
// 校验存储编码、存储类型、状态
|
||||
// 校验存储类型、存储编码、回收站配置、状态
|
||||
CheckUtils.throwIfNotEqual(req.getType(), oldStorage.getType(), "不允许修改存储类型");
|
||||
CheckUtils.throwIfNotEqual(req.getCode(), oldStorage.getCode(), "不允许修改存储编码");
|
||||
CheckUtils.throwIfNotEqual(req.getRecycleBinEnabled(), oldStorage.getRecycleBinEnabled(), "不允许修改回收站配置");
|
||||
CheckUtils.throwIfNotEqual(req.getRecycleBinPath(), oldStorage.getRecycleBinPath(), "不允许修改回收站配置");
|
||||
DisEnableStatusEnum newStatus = req.getStatus();
|
||||
CheckUtils.throwIf(Boolean.TRUE.equals(oldStorage.getIsDefault()) && DisEnableStatusEnum.DISABLE
|
||||
.equals(newStatus), "[{}] 是默认存储,不允许禁用", oldStorage.getName());
|
||||
|
||||
12
continew-system/src/main/resources/mapper/FileMapper.xml
Normal file
12
continew-system/src/main/resources/mapper/FileMapper.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="top.continew.admin.system.mapper.FileMapper">
|
||||
<update id="deleteWithoutRecycleBin">
|
||||
UPDATE sys_file
|
||||
SET deleted = id, update_user = #{userId}, update_time = NOW()
|
||||
WHERE id IN
|
||||
<foreach item="item" index="index" collection="ids" separator="," open="(" close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</update>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user