parent
7a04220205
commit
32960b1b01
@ -0,0 +1,174 @@ |
||||
package com.keyware.shandan.control.controller; |
||||
|
||||
import cn.hutool.core.date.DateUtil; |
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
import com.keyware.shandan.bianmu.entity.DirectoryVo; |
||||
import com.keyware.shandan.bianmu.service.DirectoryService; |
||||
import com.keyware.shandan.common.entity.Result; |
||||
import com.keyware.shandan.common.enums.SystemTypes; |
||||
import com.keyware.shandan.frame.properties.CustomProperties; |
||||
import com.keyware.shandan.system.entity.SysFile; |
||||
import com.keyware.shandan.system.entity.SysFileChunk; |
||||
import com.keyware.shandan.system.entity.SysSetting; |
||||
import com.keyware.shandan.system.service.SysFileService; |
||||
import com.keyware.shandan.system.service.SysUserService; |
||||
import com.keyware.shandan.system.utils.FileChunkUploadUtil; |
||||
import com.keyware.shandan.system.utils.SysSettingUtil; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.util.StringUtils; |
||||
import org.springframework.web.bind.annotation.*; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
import org.springframework.web.servlet.ModelAndView; |
||||
|
||||
import java.io.File; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
@Slf4j |
||||
@RestController |
||||
@RequestMapping("/sys/file/repair") |
||||
public class FIleRepairController { |
||||
|
||||
private final String storagePath; |
||||
private final SysUserService userService; |
||||
private final DirectoryService directoryService; |
||||
private final SysFileService fileService; |
||||
|
||||
public FIleRepairController(CustomProperties customProperties, |
||||
SysUserService userService, |
||||
DirectoryService directoryService, |
||||
SysFileService fileService) { |
||||
storagePath = customProperties.getFileStorage().getPath(); |
||||
this.userService = userService; |
||||
this.directoryService = directoryService; |
||||
this.fileService = fileService; |
||||
} |
||||
|
||||
/** |
||||
* 数据补录页面 |
||||
* |
||||
* @return |
||||
*/ |
||||
@GetMapping("/index") |
||||
public ModelAndView repair(ModelAndView mav) { |
||||
mav.setViewName("sys/file/fileRepair"); |
||||
SysSetting bianmuSetting = SysSettingUtil.getSysSetting(SystemTypes.BIANMU.name()); |
||||
mav.addObject("bianmuAddress", bianmuSetting.getSysAddress()); |
||||
return mav; |
||||
} |
||||
|
||||
@GetMapping("/list") |
||||
public Result<Object> invalidFile(@RequestParam String dirId) { |
||||
Map<String, Object> fileMap = new HashMap<>(); |
||||
directoryService.handlerAllChildrenListById(dirId, (context) -> { |
||||
DirectoryVo dir = context.getResultObject(); |
||||
if (StringUtils.hasText(dir.getResourceId())) { |
||||
// 查询对应的文件资源
|
||||
SysFile file = fileService.getById(dir.getResourceId()); |
||||
if (file != null && !validate(file)) { |
||||
Map<String, Object> data = new HashMap<>(); |
||||
data.put("id", file.getId()); |
||||
data.put("dirId", dir.getId()); |
||||
data.put("directoryPath", dir.getDirectoryPath()); |
||||
data.put("fileName", file.getFileName()); |
||||
data.put("fileType", file.getFileType()); |
||||
data.put("fileSize", file.getFileSize()); |
||||
data.put("createTime", DateUtil.format(file.getModifyTime(), "yyyy-MM-dd HH:mm")); |
||||
data.put("createUser", userService.getById(file.getModifyUser()).getUserName()); |
||||
fileMap.put(file.getId(), data); |
||||
} |
||||
} |
||||
}); |
||||
return Result.of(fileMap.values()); |
||||
} |
||||
|
||||
/** |
||||
* 清理数据 |
||||
* |
||||
* @param fileId |
||||
* @return |
||||
*/ |
||||
@GetMapping("/clean/{fileId}") |
||||
public Result<Object> remove(@PathVariable String fileId) { |
||||
try { |
||||
fileService.deleteById(fileId); |
||||
cleanDirectory(fileId); |
||||
} catch (Exception e) { |
||||
log.error("清理文件异常", e); |
||||
} |
||||
return Result.of(null); |
||||
} |
||||
|
||||
/** |
||||
* 递归清理文件管理的目录资源,以及引用的资源 |
||||
* |
||||
* @param resourceId |
||||
*/ |
||||
private void cleanDirectory(String resourceId) { |
||||
LambdaQueryWrapper<DirectoryVo> query = new LambdaQueryWrapper<>(); |
||||
query.eq(DirectoryVo::getResourceId, resourceId); |
||||
List<DirectoryVo> dirs = directoryService.list(query); |
||||
dirs.forEach(dir -> { |
||||
cleanDirectory(dir.getId()); |
||||
directoryService.deleteById(dir.getId()); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* 验证文件是否存在 |
||||
* |
||||
* @param sysFile |
||||
* @return |
||||
*/ |
||||
private boolean validate(SysFile sysFile) { |
||||
if (sysFile == null) { |
||||
return false; |
||||
} |
||||
File file = new File(storagePath + File.separator + sysFile.getPath()); |
||||
return file.exists(); |
||||
} |
||||
|
||||
@PostMapping("/upload/check") |
||||
public Result<Object> uploadFileCheck() { |
||||
Result<Object> result = Result.of(null, true); |
||||
result.setCode(0); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 大文件分片上传 |
||||
* |
||||
* @param file 分片文件 |
||||
* @param fileChunk 分片数据 |
||||
* @return 结果集 |
||||
*/ |
||||
@PostMapping("/upload/chunk") |
||||
public Result<Object> uploadChunk(MultipartFile file, SysFile fileInfo, SysFileChunk fileChunk) throws Exception { |
||||
return Result.of(FileChunkUploadUtil.uploadChunk(file, fileInfo, fileChunk)); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 文件分片上传完成,需要自动生成目录结构,并保存目录文件关系 |
||||
* |
||||
* @param file 文件信息 |
||||
* @return 结果 |
||||
*/ |
||||
@PostMapping("/upload/chunk/complete") |
||||
public Result<Object> mergerChunk(SysFile file) throws Exception { |
||||
FileChunkUploadUtil.mergeChunk(file, sysFile -> { |
||||
SysFile oldFile = fileService.getById(file.getId()); |
||||
if (oldFile == null) { |
||||
oldFile = sysFile; |
||||
} |
||||
QueryWrapper<SysFile> query = new QueryWrapper<>(); |
||||
query.eq("MD5", oldFile.getMD5()).eq("PATH", oldFile.getPath()); |
||||
List<SysFile> files = fileService.list(query); |
||||
files.forEach(f -> f.setPath(sysFile.getPath())); |
||||
fileService.updateBatchById(files); |
||||
}); |
||||
return Result.of(true); |
||||
} |
||||
} |
@ -0,0 +1,238 @@ |
||||
/** |
||||
* <p> |
||||
* repair |
||||
* </p> |
||||
* |
||||
* @author Administrator |
||||
* @since 2021/6/1 |
||||
*/ |
||||
// 目录树数据缓存
|
||||
const dirCache = new Map(); |
||||
|
||||
function startRender() { |
||||
layui.use(['uploader'], function () { |
||||
const uploader = layui.uploader; |
||||
// 目录树
|
||||
let dirTree, fileTable, activeNode; |
||||
let currentNode, currentParentNode; |
||||
let treeChildrenUrl = `${ctx}/business/directory/tree?busType=` + bus_type |
||||
|
||||
// 加载并渲染目录树
|
||||
let treeOps = { |
||||
id: 'directoryTree', |
||||
url: treeChildrenUrl, |
||||
cache: false, |
||||
// type: 'all',
|
||||
initLevel: 1, // 默认展开一级
|
||||
scroll: '#tree-toobar-div', |
||||
width: 'fit-content', |
||||
toolbar: true, |
||||
toolbarShow: [], //置空默认菜单项
|
||||
sendSuccess: function (res) { |
||||
if (res.flag) { |
||||
res.data.forEach(item => { |
||||
dirCache.set(item.id, item); |
||||
}) |
||||
|
||||
setTimeout(() => { |
||||
$('.dtree-nav-div.dtree-theme-item').each((index, elem) => { |
||||
let data = $(elem).data('basic'); |
||||
if (data) { |
||||
if (typeof data === 'string') { |
||||
data = JSON.parse(data); |
||||
} |
||||
let title = data.metadataComment || data.directoryName || data.fileName + data.fileSuffix; |
||||
$(elem).attr('title', title) |
||||
} |
||||
}); |
||||
}, 200) |
||||
} |
||||
}, |
||||
done: function (nodes, elem) { |
||||
// 模拟鼠标点击事件展开第一层目录
|
||||
elem.find('li:first>div:first>i:first').click(); |
||||
elem.find('li:first>div:first').click(); |
||||
}, |
||||
onClick: function (node) { |
||||
activeNode = node; |
||||
currentNode = getCurrentNode(); |
||||
currentParentNode = getCurrentParentNode(); |
||||
const {basicData} = node.param; |
||||
setLocation(basicData.directoryPath) |
||||
loadFileList(activeNode.param) |
||||
}, |
||||
toolbarFun: { |
||||
// 显示右键菜单之前的回调,用于设置显示哪些菜单
|
||||
loadToolbarBefore: function (buttons, param, $div) { |
||||
return buttons; |
||||
} |
||||
}, |
||||
toolbarExt: [ |
||||
{ |
||||
toolbarId: "toolbar_dir_rename", |
||||
icon: "dtreefont dtree-icon-bianji", |
||||
title: "修改目录", |
||||
handler: function (node, elem) { |
||||
const {basicData} = node; |
||||
openDirectoryEditLayer(basicData, function (data) { |
||||
dirTree.partialRefreshEdit(elem, data); |
||||
dirTree.getChild(elem); |
||||
elem[0].click(); |
||||
}); |
||||
} |
||||
}, |
||||
{ |
||||
toolbarId: "toolbar_dir_delete", |
||||
icon: "dtreefont dtree-icon-delete1", |
||||
title: "删除目录", |
||||
handler: function (node, elem) { |
||||
const {id, parentId} = node; |
||||
deleteDirectory(id).then(() => { |
||||
//dirTree.partialRefreshDel(elem)
|
||||
reZhanKai(currentParentNode); |
||||
// 点击父节点
|
||||
$(`div[data-id="${parentId}"]`).click(); |
||||
}) |
||||
} |
||||
} |
||||
], |
||||
formatter: { |
||||
title: function (data) { // 文字过滤,返回null,"",undefined之后,都不会改变原有的内容返回。
|
||||
return formatterTitle(data); |
||||
} |
||||
} |
||||
} |
||||
dirTree = globalTree.init(treeOps); |
||||
|
||||
/** |
||||
* 加载数据资源列表 |
||||
*/ |
||||
function loadFileList(directory) { |
||||
const {basicData} = directory, dirId = basicData.id; |
||||
if (!basicData) return; |
||||
Util.get(`/sys/file/repair/list?dirId=${dirId}`).then(({flag, data, msg}) => { |
||||
if (!flag) { |
||||
showErrorMsg(msg); |
||||
return; |
||||
} |
||||
data && renderFileTable(data); |
||||
}); |
||||
} |
||||
|
||||
function renderFileTable(data) { |
||||
fileTable = listPage.init({ |
||||
table: { |
||||
id: 'dirMetadataTable', |
||||
toolbar: '#tableToolBar', |
||||
searchFieldNames: 'metadataName', |
||||
height: 'full-110', |
||||
data: data, |
||||
cols: [[ |
||||
{field: 'id', title: 'ID', hide: true}, |
||||
{field: 'fileName', title: '文件名称', width: 300}, |
||||
{field: 'directoryPath', title: '所属目录'}, |
||||
{field: 'fileType', title: '文件类型', width: 160, align: 'center'}, |
||||
{ |
||||
field: 'fileSize', |
||||
title: '文件大小', |
||||
width: 160, |
||||
align: 'center', |
||||
templet: (data) => Util.fileSizeFormat(data.fileSize, "M") |
||||
}, |
||||
{field: 'createTime', title: '最后修改时间', width: 160, align: 'center'}, |
||||
{field: 'createUser', title: '最后修改用户', width: 160, align: 'center'}, |
||||
{ |
||||
fixed: 'right', |
||||
title: '操作', |
||||
toolbar: `#rowToolBar`, |
||||
width: 160, |
||||
align: 'center' |
||||
} |
||||
]], |
||||
}, |
||||
}); |
||||
// 查看按钮监听
|
||||
fileTable.addTableRowEvent('reupload', reupload) |
||||
fileTable.addTableRowEvent('clean', cleanData) |
||||
} |
||||
|
||||
function reupload(obj) { |
||||
const fileId = obj.id; |
||||
const layerId = 'reupload-layer'; |
||||
let uploadObject; |
||||
let layerIndex = layer.open({ |
||||
title: '重新上传文件', |
||||
content: `<div id="${layerId}" style="width: 800px; height: 160px;"></div>`, |
||||
btn: ['上传', '取消'], |
||||
success: function (layObj, index) { |
||||
uploadObject = renderUploader(); |
||||
}, |
||||
yes: function () { |
||||
uploadObject.uploadToServer(); |
||||
}, |
||||
}); |
||||
|
||||
function renderUploader() { |
||||
return uploader.render({ |
||||
url: `${ctx}/sys/file/repair/upload/chunk`,//上传文件服务器地址,必填
|
||||
fileCheckUrl: `${ctx}/sys/file/repair/upload/check`,//文件校验地址
|
||||
fileMergeUrl: `/sys/file/repair/upload/chunk/complete`, |
||||
fileBoxEle: `#${layerId}`,//上传容器
|
||||
height: '130px', |
||||
multiple: false, |
||||
getFormData: () => ({repairId: fileId}), |
||||
uploadFinished: function (ok) { |
||||
loadFileList(activeNode.param); |
||||
layer.close(layerIndex); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 移除数据 |
||||
* @param obj |
||||
*/ |
||||
function cleanData(obj) { |
||||
layer.confirm('该清理操作将删除该文件记录以及对该文件引用的资源数据,是否确定清理?', {}, function (index) { |
||||
layer.close(index); |
||||
Util.get(`/sys/file/repair/clean/${obj.id}`, {},).then(res => { |
||||
if (res.flag) { |
||||
showOkMsg('清理成功') |
||||
loadFileList(activeNode.param) |
||||
} else { |
||||
showErrorMsg(res.msg); |
||||
} |
||||
}).catch(() => showErrorMsg()); |
||||
}) |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* 获取当前节点 |
||||
* @returns {*|jQuery|HTMLElement} |
||||
*/ |
||||
function getCurrentNode() { |
||||
return $('li.dtree-nav-item div.dtree-theme-item-this'); |
||||
} |
||||
|
||||
/** |
||||
* 获取当前节点的父级节点 |
||||
* @returns {*|jQuery} |
||||
*/ |
||||
function getCurrentParentNode() { |
||||
return getCurrentNode().parent().parent().prev(); |
||||
} |
||||
|
||||
function reZhanKai(node) { |
||||
let iconNode = node.find('i:first'); |
||||
if (iconNode.data('spread') === 'open') { |
||||
iconNode.click(); |
||||
iconNode.click(); |
||||
} else { |
||||
iconNode.click(); |
||||
} |
||||
} |
||||
}); |
||||
} |
@ -0,0 +1,125 @@ |
||||
<!DOCTYPE html> |
||||
<!--解决idea thymeleaf 表达式模板报红波浪线--> |
||||
<!--suppress ALL --> |
||||
<html xmlns:th="http://www.thymeleaf.org"> |
||||
<head> |
||||
<!-- 引入公用部分 --> |
||||
<script th:replace="common/head::static"></script> |
||||
<!-- 样式 --> |
||||
<link th:href="@{/css/common/contianer.css}" rel="stylesheet" type="text/css"/> |
||||
<script th:inline="javascript"> |
||||
const user_str = sessionStorage.getItem("login_user"); |
||||
const user = JSON.parse(user_str); |
||||
const ReadPermisDirIds = /*[[${readDirIds}]]*/ []; |
||||
const WritePermisDirIds = /*[[${writeDirIds}]]*/ []; |
||||
// 目录业务类型,0:资源目录;1:基础数据目录 |
||||
const bus_type = 0; |
||||
</script> |
||||
</head> |
||||
<body onselectstart="return false"> |
||||
<div class="layui-row"> |
||||
<!-- 树 --> |
||||
<div class="layui-col-md3"> |
||||
<div class="layui-card" style="border-right: 1px solid #ebebeb;"> |
||||
<div class="layui-card-body"> |
||||
<div class="layui-tab layui-tab-brief" id="directoryTreeBody" lay-filter="directoryTreeBody"> |
||||
<ul class="layui-tab-title"> |
||||
<li class="layui-this metadata-table">资源目录</li> |
||||
</ul> |
||||
<div class="layui-tab-content"> |
||||
<div class="layui-form" lay-filter="directoryFilterForm"> |
||||
<div class="layui-form-item" style="margin-bottom: 0"> |
||||
<div class="layui-inline" style="width: 100%"> |
||||
<div class="layui-input-inline" style="width: 100%"> |
||||
<input type="text" class="layui-input layui-icon-search" placeholder="请输入关键词检索" |
||||
style="padding-right: 40px;" id="directoryTreeSearchBox"> |
||||
<i class="layui-icon layui-icon-search" |
||||
style="font-size: 24px;position: absolute;top: 7px;right: 10px;color: #b5b5b5;"></i> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="layui-tab-item layui-show" style="overflow: auto; padding: 10px 0" |
||||
id="tree-toobar-div"> |
||||
<ul id="directoryTree" class="dtree" data-id="ROOT" data-value="ROOT" |
||||
style="min-width: 100%"></ul> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<!-- 数据表格 --> |
||||
<div class="layui-col-md9"> |
||||
<div class="layui-card"> |
||||
<div class="layui-card-body right-card"> |
||||
<div class="layui-tab layui-tab-brief" id="metadataListCardBody" lay-filter="metadataListCardBody"> |
||||
<ul class="layui-tab-title"> |
||||
<li class="layui-this metadata-table">无效文件列表</li> |
||||
</ul> |
||||
<div class="layui-tab-content"> |
||||
<div class="layui-tab-item layui-show"> |
||||
<div class="current-position">当前位置:<label id="currentPosition"></label></div> |
||||
<table class="layui-hide" id="dirMetadataTable" lay-filter="dirMetadataTable"></table> |
||||
<script type="text/html" id="tableToolBar"> |
||||
<div class="layui-btn-container"> |
||||
<div class="layui-layout-left" style="top:10px; left: 20px"> |
||||
<input type="text" id="searchKeyInput" name="searchKeyInput" |
||||
autocomplete="off" |
||||
placeholder="请输入关键字查询" class="layui-input layui-btn-sm"> |
||||
<button class="layui-btn layui-btn-sm" lay-event="query">查询</button> |
||||
</div> |
||||
<div class="layui-layout-right" style="padding-right: 20px; top:10px"> |
||||
</div> |
||||
</div> |
||||
</script> |
||||
<script type="text/html" id="rowToolBar"> |
||||
<div class="layui-btn-container"> |
||||
<button class="layui-btn layui-btn-xs remove-link" lay-event="reupload">重新上传</button> |
||||
<button class="layui-btn layui-btn-xs remove-link" lay-event="clean">清理数据</button> |
||||
</div> |
||||
</script> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</body> |
||||
|
||||
<!-- js --> |
||||
<script th:replace="common/head::static-foot"></script> |
||||
<script th:src="@{/js/business/directory/dirCommon.js}"></script> |
||||
<script th:src="@{/js/sys/file/repair.js}"></script> |
||||
<script type="text/javascript"> |
||||
const toggleRightCard = (showType, callback) => { |
||||
if (showType === 'metadata') { |
||||
$('div.layui-card-body.right-card div.layui-tab.layui-tab-brief').addClass('layui-hide'); |
||||
$("#metadataCardBody").removeClass('layui-hide').addClass('layui-show'); |
||||
} else if (showType === 'directory') { |
||||
$('div.layui-card-body.right-card div.layui-tab.layui-tab-brief').addClass('layui-hide'); |
||||
$("#metadataListCardBody").removeClass('layui-hide').addClass('layui-show'); |
||||
} else if (showType === 'file') { |
||||
$('div.layui-card-body.right-card div.layui-tab.layui-tab-brief').addClass('layui-hide'); |
||||
$("#fileCardBody").removeClass('layui-hide').addClass('layui-show'); |
||||
} |
||||
callback && callback(); |
||||
} |
||||
|
||||
|
||||
let imageWidth; |
||||
let imageHeight; |
||||
const sizeInit = () => { |
||||
$(".layui-card-body").height(window.innerHeight - 20); |
||||
$("#tree-toobar-div").height($(".layui-card-body").height() - 40 - 25 - 45); |
||||
$('#fileCardBody').height(window.innerHeight - 40 - 20); |
||||
$('#fileViewer .file-viewer').height(window.innerHeight - 40 - 20 - 80); |
||||
imageWidth = $('.layui-col-md9:first').innerWidth() - 54; |
||||
imageHeight = $('#file-viewer-image').innerHeight(); |
||||
} |
||||
window.onresize = sizeInit; |
||||
sizeInit(); |
||||
</script> |
||||
</html> |
Reference in new issue