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:
@@ -61,6 +61,10 @@ VALUES
|
|||||||
(1116, '下载', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:download', 6, 1, 1, NOW()),
|
(1116, '下载', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:download', 6, 1, 1, NOW()),
|
||||||
(1117, '创建文件夹', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:createDir', 7, 1, 1, NOW()),
|
(1117, '创建文件夹', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:createDir', 7, 1, 1, NOW()),
|
||||||
(1118, '计算文件夹大小', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:calcDirSize', 8, 1, 1, NOW()),
|
(1118, '计算文件夹大小', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:calcDirSize', 8, 1, 1, NOW()),
|
||||||
|
(1119, '回收站文件列表', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:fileRecycle:list', 9, 1, 1, NOW()),
|
||||||
|
(1120, '还原回收站文件', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:fileRecycle:restore', 10, 1, 1, NOW()),
|
||||||
|
(1121, '删除回收站文件', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:fileRecycle:delete', 11, 1, 1, NOW()),
|
||||||
|
(1122, '清空回收站', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:fileRecycle:clean', 12, 1, 1, NOW()),
|
||||||
|
|
||||||
(1130, '字典管理', 1000, 2, '/system/dict', 'SystemDict', 'system/dict/index', NULL, 'bookmark', b'0', b'0', b'0', NULL, 7, 1, 1, NOW()),
|
(1130, '字典管理', 1000, 2, '/system/dict', 'SystemDict', 'system/dict/index', NULL, 'bookmark', b'0', b'0', b'0', NULL, 7, 1, 1, NOW()),
|
||||||
(1131, '列表', 1130, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:list', 1, 1, 1, NOW()),
|
(1131, '列表', 1130, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:list', 1, 1, 1, NOW()),
|
||||||
@@ -278,10 +282,10 @@ INSERT INTO `sys_role_dept` (`role_id`, `dept_id`) VALUES (547888897925840927, 5
|
|||||||
|
|
||||||
-- 初始化默认存储
|
-- 初始化默认存储
|
||||||
INSERT INTO `sys_storage`
|
INSERT INTO `sys_storage`
|
||||||
(`id`, `name`, `code`, `type`, `access_key`, `secret_key`, `endpoint`, `bucket_name`, `domain`, `description`, `is_default`, `sort`, `status`, `create_user`, `create_time`)
|
(`id`, `name`, `code`, `type`, `access_key`, `secret_key`, `endpoint`, `bucket_name`, `domain`, `recycle_bin_enabled`, `recycle_bin_path`, `description`, `is_default`, `sort`, `status`, `create_user`, `create_time`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, '开发环境', 'local_dev', 1, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file/', '本地存储', b'1', 1, 1, 1, NOW()),
|
(1, '开发环境', 'local_dev', 1, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file/', b'1', '.RECYCLE.BIN/', '本地存储', b'1', 1, 1, 1, NOW()),
|
||||||
(2, '生产环境', 'local_prod', 1, NULL, NULL, NULL, '../data/file/', 'http://api.continew.top/file/', '本地存储', b'0', 2, 2, 1, NOW());
|
(2, '生产环境', 'local_prod', 1, NULL, NULL, NULL, '../data/file/', 'http://api.continew.top/file/', b'1', '.RECYCLE.BIN/', '本地存储', b'0', 2, 2, 1, NOW());
|
||||||
|
|
||||||
-- 初始化客户端数据
|
-- 初始化客户端数据
|
||||||
INSERT INTO `sys_client`
|
INSERT INTO `sys_client`
|
||||||
|
|||||||
@@ -283,24 +283,26 @@ CREATE TABLE IF NOT EXISTS `sys_notice_log` (
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公告日志表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公告日志表';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `sys_storage` (
|
CREATE TABLE IF NOT EXISTS `sys_storage` (
|
||||||
`id` bigint(20) AUTO_INCREMENT COMMENT 'ID',
|
`id` bigint(20) AUTO_INCREMENT COMMENT 'ID',
|
||||||
`name` varchar(100) NOT NULL COMMENT '名称',
|
`name` varchar(100) NOT NULL COMMENT '名称',
|
||||||
`code` varchar(30) NOT NULL COMMENT '编码',
|
`code` varchar(30) NOT NULL COMMENT '编码',
|
||||||
`type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:本地存储;2:对象存储)',
|
`type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:本地存储;2:对象存储)',
|
||||||
`access_key` varchar(255) DEFAULT NULL COMMENT 'Access Key',
|
`access_key` varchar(255) DEFAULT NULL COMMENT 'Access Key',
|
||||||
`secret_key` varchar(255) DEFAULT NULL COMMENT 'Secret Key',
|
`secret_key` varchar(255) DEFAULT NULL COMMENT 'Secret Key',
|
||||||
`endpoint` varchar(255) DEFAULT NULL COMMENT 'Endpoint',
|
`endpoint` varchar(255) DEFAULT NULL COMMENT 'Endpoint',
|
||||||
`bucket_name` varchar(255) NOT NULL COMMENT 'Bucket',
|
`bucket_name` varchar(255) NOT NULL COMMENT 'Bucket',
|
||||||
`domain` varchar(255) DEFAULT NULL COMMENT '域名',
|
`domain` varchar(255) DEFAULT NULL COMMENT '域名',
|
||||||
`description` varchar(200) DEFAULT NULL COMMENT '描述',
|
`recycle_bin_enabled` bit(1) NOT NULL DEFAULT b'1' COMMENT '启用回收站',
|
||||||
`is_default` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为默认存储',
|
`recycle_bin_path` varchar(255) DEFAULT NULL COMMENT '回收站路径',
|
||||||
`sort` int NOT NULL DEFAULT 999 COMMENT '排序',
|
`description` varchar(200) DEFAULT NULL COMMENT '描述',
|
||||||
`status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)',
|
`is_default` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为默认存储',
|
||||||
`create_user` bigint(20) NOT NULL COMMENT '创建人',
|
`sort` int NOT NULL DEFAULT 999 COMMENT '排序',
|
||||||
`create_time` datetime NOT NULL COMMENT '创建时间',
|
`status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)',
|
||||||
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
|
`create_user` bigint(20) NOT NULL COMMENT '创建人',
|
||||||
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
|
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||||
`deleted` bigint(20) NOT NULL DEFAULT 0 COMMENT '是否已删除(0:否;id:是)',
|
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
|
||||||
|
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`deleted` bigint(20) NOT NULL DEFAULT 0 COMMENT '是否已删除(0:否;id:是)',
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE INDEX `uk_code`(`code`, `deleted`),
|
UNIQUE INDEX `uk_code`(`code`, `deleted`),
|
||||||
INDEX `idx_create_user`(`create_user`),
|
INDEX `idx_create_user`(`create_user`),
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ VALUES
|
|||||||
(1116, '下载', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:download', 6, 1, 1, NOW()),
|
(1116, '下载', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:download', 6, 1, 1, NOW()),
|
||||||
(1117, '创建文件夹', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:createDir', 7, 1, 1, NOW()),
|
(1117, '创建文件夹', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:createDir', 7, 1, 1, NOW()),
|
||||||
(1118, '计算文件夹大小', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:calcDirSize', 8, 1, 1, NOW()),
|
(1118, '计算文件夹大小', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:file:calcDirSize', 8, 1, 1, NOW()),
|
||||||
|
(1119, '回收站文件列表', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:fileRecycle:list', 9, 1, 1, NOW()),
|
||||||
|
(1120, '还原回收站文件', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:fileRecycle:restore', 10, 1, 1, NOW()),
|
||||||
|
(1121, '删除回收站文件', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:fileRecycle:delete', 11, 1, 1, NOW()),
|
||||||
|
(1122, '清空回收站', 1110, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:fileRecycle:clean', 12, 1, 1, NOW()),
|
||||||
|
|
||||||
(1130, '字典管理', 1000, 2, '/system/dict', 'SystemDict', 'system/dict/index', NULL, 'bookmark', false, false, false, NULL, 7, 1, 1, NOW()),
|
(1130, '字典管理', 1000, 2, '/system/dict', 'SystemDict', 'system/dict/index', NULL, 'bookmark', false, false, false, NULL, 7, 1, 1, NOW()),
|
||||||
(1131, '列表', 1130, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:list', 1, 1, 1, NOW()),
|
(1131, '列表', 1130, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'system:dict:list', 1, 1, 1, NOW()),
|
||||||
@@ -278,10 +282,10 @@ INSERT INTO "sys_role_dept" ("role_id", "dept_id") VALUES (547888897925840927, 5
|
|||||||
|
|
||||||
-- 初始化默认存储
|
-- 初始化默认存储
|
||||||
INSERT INTO "sys_storage"
|
INSERT INTO "sys_storage"
|
||||||
("id", "name", "code", "type", "access_key", "secret_key", "endpoint", "bucket_name", "domain", "description", "is_default", "sort", "status", "create_user", "create_time")
|
("id", "name", "code", "type", "access_key", "secret_key", "endpoint", "bucket_name", "domain", "recycle_bin_enabled", "recycle_bin_path", "description", "is_default", "sort", "status", "create_user", "create_time")
|
||||||
VALUES
|
VALUES
|
||||||
(1, '开发环境', 'local_dev', 1, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file/', '本地存储', true, 1, 1, 1, NOW()),
|
(1, '开发环境', 'local_dev', 1, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file/', true, '.RECYCLE.BIN/', '本地存储', true, 1, 1, 1, NOW()),
|
||||||
(2, '生产环境', 'local_prod', 1, NULL, NULL, NULL, '../data/file/', 'http://api.continew.top/file/', '本地存储', false, 2, 2, 1, NOW());
|
(2, '生产环境', 'local_prod', 1, NULL, NULL, NULL, '../data/file/', 'http://api.continew.top/file/', true, '.RECYCLE.BIN/', '本地存储', false, 2, 2, 1, NOW());
|
||||||
|
|
||||||
-- 初始化客户端数据
|
-- 初始化客户端数据
|
||||||
INSERT INTO "sys_client"
|
INSERT INTO "sys_client"
|
||||||
|
|||||||
@@ -466,49 +466,53 @@ COMMENT ON COLUMN "sys_notice_log"."read_time" IS '读取时间';
|
|||||||
COMMENT ON TABLE "sys_notice_log" IS '公告日志表';
|
COMMENT ON TABLE "sys_notice_log" IS '公告日志表';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "sys_storage" (
|
CREATE TABLE IF NOT EXISTS "sys_storage" (
|
||||||
"id" int8 NOT NULL,
|
"id" int8 NOT NULL,
|
||||||
"name" varchar(100) NOT NULL,
|
"name" varchar(100) NOT NULL,
|
||||||
"code" varchar(30) NOT NULL,
|
"code" varchar(30) NOT NULL,
|
||||||
"type" int2 NOT NULL DEFAULT 1,
|
"type" int2 NOT NULL DEFAULT 1,
|
||||||
"access_key" varchar(255) DEFAULT NULL,
|
"access_key" varchar(255) DEFAULT NULL,
|
||||||
"secret_key" varchar(255) DEFAULT NULL,
|
"secret_key" varchar(255) DEFAULT NULL,
|
||||||
"endpoint" varchar(255) DEFAULT NULL,
|
"endpoint" varchar(255) DEFAULT NULL,
|
||||||
"bucket_name" varchar(255) NOT NULL,
|
"bucket_name" varchar(255) NOT NULL,
|
||||||
"domain" varchar(255) DEFAULT NULL,
|
"domain" varchar(255) DEFAULT NULL,
|
||||||
"description" varchar(200) DEFAULT NULL,
|
"recycle_bin_enabled" bool NOT NULL DEFAULT true,
|
||||||
"is_default" bool NOT NULL DEFAULT false,
|
"recycle_bin_path" varchar(255) DEFAULT NULL,
|
||||||
"sort" int4 NOT NULL DEFAULT 999,
|
"description" varchar(200) DEFAULT NULL,
|
||||||
"status" int2 NOT NULL DEFAULT 1,
|
"is_default" bool NOT NULL DEFAULT false,
|
||||||
"create_user" int8 NOT NULL,
|
"sort" int4 NOT NULL DEFAULT 999,
|
||||||
"create_time" timestamp NOT NULL,
|
"status" int2 NOT NULL DEFAULT 1,
|
||||||
"update_user" int8 DEFAULT NULL,
|
"create_user" int8 NOT NULL,
|
||||||
"update_time" timestamp DEFAULT NULL,
|
"create_time" timestamp NOT NULL,
|
||||||
"deleted" int8 NOT NULL DEFAULT 0,
|
"update_user" int8 DEFAULT NULL,
|
||||||
|
"update_time" timestamp DEFAULT NULL,
|
||||||
|
"deleted" int8 NOT NULL DEFAULT 0,
|
||||||
PRIMARY KEY ("id")
|
PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX "uk_storage_code" ON "sys_storage" ("code", "deleted");
|
CREATE UNIQUE INDEX "uk_storage_code" ON "sys_storage" ("code", "deleted");
|
||||||
CREATE INDEX "idx_storage_create_user" ON "sys_storage" ("create_user");
|
CREATE INDEX "idx_storage_create_user" ON "sys_storage" ("create_user");
|
||||||
CREATE INDEX "idx_storage_update_user" ON "sys_storage" ("update_user");
|
CREATE INDEX "idx_storage_update_user" ON "sys_storage" ("update_user");
|
||||||
CREATE INDEX "idx_storage_deleted" ON "sys_storage" ("deleted");
|
CREATE INDEX "idx_storage_deleted" ON "sys_storage" ("deleted");
|
||||||
COMMENT ON COLUMN "sys_storage"."id" IS 'ID';
|
COMMENT ON COLUMN "sys_storage"."id" IS 'ID';
|
||||||
COMMENT ON COLUMN "sys_storage"."name" IS '名称';
|
COMMENT ON COLUMN "sys_storage"."name" IS '名称';
|
||||||
COMMENT ON COLUMN "sys_storage"."code" IS '编码';
|
COMMENT ON COLUMN "sys_storage"."code" IS '编码';
|
||||||
COMMENT ON COLUMN "sys_storage"."type" IS '类型(1:本地存储;2:对象存储)';
|
COMMENT ON COLUMN "sys_storage"."type" IS '类型(1:本地存储;2:对象存储)';
|
||||||
COMMENT ON COLUMN "sys_storage"."access_key" IS 'Access Key';
|
COMMENT ON COLUMN "sys_storage"."access_key" IS 'Access Key';
|
||||||
COMMENT ON COLUMN "sys_storage"."secret_key" IS 'Secret Key';
|
COMMENT ON COLUMN "sys_storage"."secret_key" IS 'Secret Key';
|
||||||
COMMENT ON COLUMN "sys_storage"."endpoint" IS 'Endpoint';
|
COMMENT ON COLUMN "sys_storage"."endpoint" IS 'Endpoint';
|
||||||
COMMENT ON COLUMN "sys_storage"."bucket_name" IS 'Bucket';
|
COMMENT ON COLUMN "sys_storage"."bucket_name" IS 'Bucket';
|
||||||
COMMENT ON COLUMN "sys_storage"."domain" IS '域名';
|
COMMENT ON COLUMN "sys_storage"."domain" IS '域名';
|
||||||
COMMENT ON COLUMN "sys_storage"."description" IS '描述';
|
COMMENT ON COLUMN "sys_storage"."recycle_bin_enabled" IS '启用回收站';
|
||||||
COMMENT ON COLUMN "sys_storage"."is_default" IS '是否为默认存储';
|
COMMENT ON COLUMN "sys_storage"."recycle_bin_path" IS '回收站路径';
|
||||||
COMMENT ON COLUMN "sys_storage"."sort" IS '排序';
|
COMMENT ON COLUMN "sys_storage"."description" IS '描述';
|
||||||
COMMENT ON COLUMN "sys_storage"."status" IS '状态(1:启用;2:禁用)';
|
COMMENT ON COLUMN "sys_storage"."is_default" IS '是否为默认存储';
|
||||||
COMMENT ON COLUMN "sys_storage"."create_user" IS '创建人';
|
COMMENT ON COLUMN "sys_storage"."sort" IS '排序';
|
||||||
COMMENT ON COLUMN "sys_storage"."create_time" IS '创建时间';
|
COMMENT ON COLUMN "sys_storage"."status" IS '状态(1:启用;2:禁用)';
|
||||||
COMMENT ON COLUMN "sys_storage"."update_user" IS '修改人';
|
COMMENT ON COLUMN "sys_storage"."create_user" IS '创建人';
|
||||||
COMMENT ON COLUMN "sys_storage"."update_time" IS '修改时间';
|
COMMENT ON COLUMN "sys_storage"."create_time" IS '创建时间';
|
||||||
COMMENT ON COLUMN "sys_storage"."deleted" IS '是否已删除(0:否;id:是)';
|
COMMENT ON COLUMN "sys_storage"."update_user" IS '修改人';
|
||||||
COMMENT ON TABLE "sys_storage" IS '存储表';
|
COMMENT ON COLUMN "sys_storage"."update_time" IS '修改时间';
|
||||||
|
COMMENT ON COLUMN "sys_storage"."deleted" IS '是否已删除(0:否;id:是)';
|
||||||
|
COMMENT ON TABLE "sys_storage" IS '存储表';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "sys_file" (
|
CREATE TABLE IF NOT EXISTS "sys_file" (
|
||||||
"id" int8 NOT NULL,
|
"id" int8 NOT NULL,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import top.continew.starter.core.util.URLUtils;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,6 +56,9 @@ public class FileRecorderImpl implements FileRecorder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean save(FileInfo fileInfo) {
|
public boolean save(FileInfo fileInfo) {
|
||||||
|
if (fileInfo.getAttr() == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// 保存文件信息
|
// 保存文件信息
|
||||||
FileDO file = new FileDO(fileInfo);
|
FileDO file = new FileDO(fileInfo);
|
||||||
StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false));
|
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) {
|
public boolean delete(String url) {
|
||||||
FileDO file = this.getFileByUrl(url);
|
FileDO file = this.getFileByUrl(url);
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
return fileMapper.lambdaUpdate().eq(FileDO::getId, file.getId()).remove();
|
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));
|
List<StorageDO> storageList = storageMapper.selectByIds(CollUtils.mapToList(list, FileDO::getStorageId));
|
||||||
Map<Long, StorageDO> storageMap = storageList.stream()
|
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 -> {
|
return list.stream().filter(file -> {
|
||||||
// http://localhost:8000/file/user/avatar/6825e687db4174e7a297a5f8.png => http://localhost:8000/file/user/avatar
|
// http://localhost:8000/file/user/avatar/6825e687db4174e7a297a5f8.png => http://localhost:8000/file/user/avatar
|
||||||
String urlPrefix = StrUtil.subBefore(url, StringConstants.SLASH, true);
|
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())) {
|
if (StrUtil.isNotBlank(req.getDomain())) {
|
||||||
req.setDomain(StrUtil.appendIfMissing(req.getDomain(), StringConstants.SLASH));
|
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;
|
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.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.apache.ibatis.annotations.Select;
|
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.entity.FileDO;
|
||||||
import top.continew.admin.system.model.resp.file.FileStatisticsResp;
|
import top.continew.admin.system.model.resp.file.FileStatisticsResp;
|
||||||
import top.continew.starter.data.mapper.BaseMapper;
|
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")
|
@Select("SELECT type, COUNT(1) number, SUM(size) size FROM sys_file WHERE deleted = 0 AND type != 0 GROUP BY type")
|
||||||
List<FileStatisticsResp> statistics();
|
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.date.DateUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -114,6 +115,12 @@ public class FileDO extends BaseDO {
|
|||||||
*/
|
*/
|
||||||
private Long storageId;
|
private Long storageId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已删除(0:否;1:回收站)
|
||||||
|
*/
|
||||||
|
@TableLogic(value = "0", delval = "1")
|
||||||
|
private Long deleted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于 {@link FileInfo} 构建文件信息对象
|
* 基于 {@link FileInfo} 构建文件信息对象
|
||||||
*
|
*
|
||||||
@@ -143,7 +150,7 @@ public class FileDO extends BaseDO {
|
|||||||
/**
|
/**
|
||||||
* 转换为 {@link FileInfo} 文件信息对象
|
* 转换为 {@link FileInfo} 文件信息对象
|
||||||
*
|
*
|
||||||
* @param storage 存储配置信息
|
* @param storage 存储配置
|
||||||
* @return {@link FileInfo} 文件信息对象
|
* @return {@link FileInfo} 文件信息对象
|
||||||
*/
|
*/
|
||||||
public FileInfo toFileInfo(StorageDO storage) {
|
public FileInfo toFileInfo(StorageDO storage) {
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import cn.hutool.core.util.URLUtil;
|
import cn.hutool.core.util.URLUtil;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
|
||||||
import top.continew.admin.common.base.model.entity.BaseDO;
|
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.admin.system.enums.StorageTypeEnum;
|
||||||
import top.continew.starter.core.constant.StringConstants;
|
import top.continew.starter.core.constant.StringConstants;
|
||||||
import top.continew.starter.encrypt.field.annotation.FieldEncrypt;
|
import top.continew.starter.encrypt.field.annotation.FieldEncrypt;
|
||||||
@@ -86,6 +86,16 @@ public class StorageDO extends BaseDO {
|
|||||||
*/
|
*/
|
||||||
private String domain;
|
private String domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用回收站
|
||||||
|
*/
|
||||||
|
private Boolean recycleBinEnabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回收站路径
|
||||||
|
*/
|
||||||
|
private String recycleBinPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 描述
|
* 描述
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package top.continew.admin.system.model.req;
|
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 com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
@@ -38,6 +40,7 @@ import java.io.Serializable;
|
|||||||
* @since 2023/12/26 22:09
|
* @since 2023/12/26 22:09
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@SpelValid
|
||||||
@Schema(description = "存储创建或修改请求参数")
|
@Schema(description = "存储创建或修改请求参数")
|
||||||
public class StorageReq implements Serializable {
|
public class StorageReq implements Serializable {
|
||||||
|
|
||||||
@@ -109,6 +112,20 @@ public class StorageReq implements Serializable {
|
|||||||
@NotBlank(message = "访问路径不能为空", groups = ValidationGroup.Storage.Local.class)
|
@NotBlank(message = "访问路径不能为空", groups = ValidationGroup.Storage.Local.class)
|
||||||
private String domain;
|
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")
|
@Schema(description = "域名", example = "http://localhost:8000/file")
|
||||||
private String domain;
|
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.dromara.x.file.storage.core.upload.UploadPretreatment;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import top.continew.admin.common.base.service.BaseServiceImpl;
|
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.common.enums.DisEnableStatusEnum;
|
||||||
import top.continew.admin.system.enums.FileTypeEnum;
|
import top.continew.admin.system.enums.FileTypeEnum;
|
||||||
import top.continew.admin.system.mapper.FileMapper;
|
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.admin.system.service.StorageService;
|
||||||
import top.continew.starter.cache.redisson.util.RedisLockUtils;
|
import top.continew.starter.cache.redisson.util.RedisLockUtils;
|
||||||
import top.continew.starter.core.constant.StringConstants;
|
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.StrUtils;
|
||||||
import top.continew.starter.core.util.validation.CheckUtils;
|
import top.continew.starter.core.util.validation.CheckUtils;
|
||||||
import top.continew.starter.core.util.validation.ValidationUtils;
|
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.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,25 +75,32 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
|
|||||||
private StorageService storageService;
|
private StorageService storageService;
|
||||||
|
|
||||||
@Override
|
@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();
|
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));
|
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()) {
|
for (Map.Entry<Long, List<FileDO>> entry : fileListGroup.entrySet()) {
|
||||||
StorageDO storage = storageService.getById(entry.getKey());
|
StorageDO storage = storageGroup.get(entry.getKey());
|
||||||
for (FileDO file : entry.getValue()) {
|
List<Long> idList = CollUtils.mapToList(entry.getValue(), FileDO::getId);
|
||||||
if (!FileTypeEnum.DIR.equals(file.getType())) {
|
if (Boolean.TRUE.equals(storage.getRecycleBinEnabled())) {
|
||||||
FileInfo fileInfo = file.toFileInfo(storage);
|
baseMapper.deleteByIds(idList);
|
||||||
fileStorageService.delete(fileInfo);
|
} else {
|
||||||
} else {
|
baseMapper.deleteWithoutRecycleBin(idList, UserContextHolder.getUserId());
|
||||||
// 不允许删除非空文件夹
|
|
||||||
boolean exists = baseMapper.lambdaQuery()
|
|
||||||
.eq(FileDO::getParentPath, file.getPath())
|
|
||||||
.eq(FileDO::getStorageId, entry.getKey())
|
|
||||||
.exists();
|
|
||||||
CheckUtils.throwIf(exists, "文件夹 [{}] 不为空,请先删除文件夹下的内容", file.getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 删除实际文件
|
||||||
|
for (Map.Entry<Long, List<FileDO>> entry : fileListGroup.entrySet()) {
|
||||||
|
StorageDO storage = storageGroup.get(entry.getKey());
|
||||||
|
entry.getValue().forEach(file -> this.deleteFile(file, storage));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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())) {
|
if (StorageTypeEnum.OSS.equals(req.getType())) {
|
||||||
req.setSecretKey(this.decryptSecretKey(req.getSecretKey(), oldStorage));
|
req.setSecretKey(this.decryptSecretKey(req.getSecretKey(), oldStorage));
|
||||||
}
|
}
|
||||||
// 校验存储编码、存储类型、状态
|
// 校验存储类型、存储编码、回收站配置、状态
|
||||||
CheckUtils.throwIfNotEqual(req.getType(), oldStorage.getType(), "不允许修改存储类型");
|
CheckUtils.throwIfNotEqual(req.getType(), oldStorage.getType(), "不允许修改存储类型");
|
||||||
CheckUtils.throwIfNotEqual(req.getCode(), oldStorage.getCode(), "不允许修改存储编码");
|
CheckUtils.throwIfNotEqual(req.getCode(), oldStorage.getCode(), "不允许修改存储编码");
|
||||||
|
CheckUtils.throwIfNotEqual(req.getRecycleBinEnabled(), oldStorage.getRecycleBinEnabled(), "不允许修改回收站配置");
|
||||||
|
CheckUtils.throwIfNotEqual(req.getRecycleBinPath(), oldStorage.getRecycleBinPath(), "不允许修改回收站配置");
|
||||||
DisEnableStatusEnum newStatus = req.getStatus();
|
DisEnableStatusEnum newStatus = req.getStatus();
|
||||||
CheckUtils.throwIf(Boolean.TRUE.equals(oldStorage.getIsDefault()) && DisEnableStatusEnum.DISABLE
|
CheckUtils.throwIf(Boolean.TRUE.equals(oldStorage.getIsDefault()) && DisEnableStatusEnum.DISABLE
|
||||||
.equals(newStatus), "[{}] 是默认存储,不允许禁用", oldStorage.getName());
|
.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