新增:文件补录功能

master
guoxin 1 year ago
parent 7a04220205
commit 32960b1b01
  1. 16
      shandan-browser/src/main/resources/static/js/browser.js
  2. 28
      shandan-common/src/main/resources/static/js/common/common.js
  3. 28
      shandan-common/src/main/resources/static/js/common/layui/extend/uploader/uploader.js
  4. 11
      shandan-control/src/main/java/com/keyware/shandan/control/controller/DirectoryController.java
  5. 174
      shandan-control/src/main/java/com/keyware/shandan/control/controller/FIleRepairController.java
  6. 15
      shandan-system/src/main/java/com/keyware/shandan/bianmu/mapper/DirectoryMapper.java
  7. 4
      shandan-system/src/main/java/com/keyware/shandan/bianmu/service/DirectoryService.java
  8. 7
      shandan-system/src/main/java/com/keyware/shandan/bianmu/service/impl/DirectoryServiceImpl.java
  9. 13
      shandan-system/src/main/java/com/keyware/shandan/system/mapper/SysFileMapper.java
  10. 43
      shandan-system/src/main/java/com/keyware/shandan/system/service/impl/SysFileServiceImpl.java
  11. 2
      shandan-system/src/main/java/com/keyware/shandan/system/utils/FileChunkUploadUtil.java
  12. 3
      shandan-system/src/main/resources/static/js/sys/file/dirUpload.js
  13. 238
      shandan-system/src/main/resources/static/js/sys/file/repair.js
  14. 125
      shandan-system/src/main/resources/view/sys/file/fileRepair.html
  15. 2
      shandan-system/src/main/resources/view/sys/file/fileView.html

@ -625,7 +625,7 @@ class THeadSetLayer {
exerciseData: data => data.exerciseData ? '是' : '否', exerciseData: data => data.exerciseData ? '是' : '否',
resourceType: data => data.resourceType === 'file' ? '文件' : '数据库表', resourceType: data => data.resourceType === 'file' ? '文件' : '数据库表',
fileSize: data => { fileSize: data => {
return fileSizeFormat(data.fileSize, 'M'); return Util.fileSizeFormat(data.fileSize, 'M');
} }
} }
@ -651,17 +651,3 @@ class THeadSetLayer {
return [cols]; return [cols];
} }
} }
const units = ['B', 'K', 'M', 'G', 'T', 'P'];
function fileSizeFormat(size, initUnit = 'B') {
initUnit = initUnit.toUpperCase();
const i = units.indexOf(initUnit);
if (size > 1024 && i < units.length - 1) {
return fileSizeFormat(size / 1024, units[i + 1]);
} else if (size < 1 && i > 0) {
return fileSizeFormat(size * 1024, units[i - 1]);
} else {
return Math.round(size * 100) / 100 + initUnit;
}
}

@ -464,7 +464,7 @@ commonUtil = {
* @param loading 是否显示loading状态 * @param loading 是否显示loading状态
* @returns {Promise<unknown>} * @returns {Promise<unknown>}
*/ */
get: (url, data, loading = true) => Util.send(url, data, 'get', loading), get: (url, data = {}, loading = true) => Util.send(url, data, 'get', loading),
/** /**
* post请求 * post请求
@ -473,7 +473,7 @@ commonUtil = {
* @param loading 是否显示loading状态 * @param loading 是否显示loading状态
* @returns {Promise<unknown>} * @returns {Promise<unknown>}
*/ */
post: (url, data, loading = true) => Util.send(url, data, 'post', loading), post: (url, data = {}, loading = true) => Util.send(url, data, 'post', loading),
/** /**
* 封装ajax异步请求 * 封装ajax异步请求
@ -486,7 +486,7 @@ commonUtil = {
send: (url, data, type = 'get', loading = true) => { send: (url, data, type = 'get', loading = true) => {
if (loading) showLoading() if (loading) showLoading()
let promise = new Promise(function (resolve, reject) { let promise = new Promise(function (resolve, reject) {
if(!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith(ctx)){ if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith(ctx)) {
url = ctx + url; url = ctx + url;
} }
$.ajax({ $.ajax({
@ -526,6 +526,26 @@ commonUtil = {
var uuid = s.join(""); var uuid = s.join("");
return uuid; return uuid;
}, },
/**
* 将文件大小格式化为可读字符串
* @param size
* @param initUnit 格式化之前的单位
* @returns {*|string}
*/
fileSizeFormat: (size, initUnit = 'B') => {
const units = ['B', 'K', 'M', 'G', 'T', 'P'];
initUnit = initUnit.toUpperCase();
const i = units.indexOf(initUnit);
if (size > 1024 && i < units.length - 1) {
return commonUtil.fileSizeFormat(size / 1024, units[i + 1]);
} else if (size < 1 && i > 0) {
return commonUtil.fileSizeFormat(size * 1024, units[i - 1]);
} else {
return Math.round(size * 100) / 100 + initUnit;
}
},
}; };
const Util = commonUtil; const Util = commonUtil;
@ -581,7 +601,7 @@ if (!initTime || (currentTime - initTime) > 1000 * 60) {
const DICT = { const DICT = {
get: function (type, key) { get: function (type, key) {
let _dict = {}, data = _Store.get(STORE_DICT_KEY); let _dict = {}, data = _Store.get(STORE_DICT_KEY);
if(typeof key === "number"){ if (typeof key === "number") {
key = key + ''; key = key + '';
} }
if (type && key !== 'undefined' && key !== '') { if (type && key !== 'undefined' && key !== '') {

@ -67,8 +67,8 @@ layui.extend({
let fileBoxE = options.fileBoxEle || fileBoxEle; let fileBoxE = options.fileBoxEle || fileBoxEle;
$(fileBoxE).html(fileBox); $(fileBoxE).html(fileBox);
table.init(COMP_TABLE_ID, { table.init(COMP_TABLE_ID, {
height: 'full-' + 500, height: options.height || ('full-' + 500),
page: true, page: options.multiple,
limit: 50, limit: 50,
done: function () { done: function () {
that.tableHeader = $(`#${COMP_ID} .layui-table-header thead:first`); that.tableHeader = $(`#${COMP_ID} .layui-table-header thead:first`);
@ -86,7 +86,7 @@ layui.extend({
pick: { pick: {
id: `#${COMP_ID}`,//指定选择文件的按钮容器,不指定则不创建按钮。注意 这里虽然写的是 id, 不仅支持 id, 还支持 class, 或者 dom 节点。 id: `#${COMP_ID}`,//指定选择文件的按钮容器,不指定则不创建按钮。注意 这里虽然写的是 id, 不仅支持 id, 还支持 class, 或者 dom 节点。
innerHTML: btnText, innerHTML: btnText,
multiple: true //开启文件多选 multiple: options.multiple //开启文件多选
}, },
fileNumLimit: undefined,//验证文件总数量, 超出则不允许加入队列,默认值:undefined,如果不配置,则不限制数量 fileNumLimit: undefined,//验证文件总数量, 超出则不允许加入队列,默认值:undefined,如果不配置,则不限制数量
fileSizeLimit: undefined, //1kb=1024*1024,验证文件总大小是否超出限制, 超出则不允许加入队列。 fileSizeLimit: undefined, //1kb=1024*1024,验证文件总大小是否超出限制, 超出则不允许加入队列。
@ -98,7 +98,7 @@ layui.extend({
prepareNextFile: false//在上传当前文件时,准备好下一个文件,请设置成false,不然开启文件多选你浏览器会卡死 prepareNextFile: false//在上传当前文件时,准备好下一个文件,请设置成false,不然开启文件多选你浏览器会卡死
}); });
if (dirUpload) { if (options.multiple && dirUpload) {
setTimeout(function () { setTimeout(function () {
$(`#${COMP_ID} input[type="file"]`).attr('webkitdirectory', ''); $(`#${COMP_ID} input[type="file"]`).attr('webkitdirectory', '');
}, 100); }, 100);
@ -123,6 +123,12 @@ layui.extend({
file.nameBak = file.name; file.nameBak = file.name;
file.name = dirUpload ? file.source.source.webkitRelativePath : file.name; file.name = dirUpload ? file.source.source.webkitRelativePath : file.name;
file.status = STATUS.added; file.status = STATUS.added;
// 单文件上传先删除已选择的
const existsFile = rowData[0]
if (!option.multiple && existsFile) {
upload.removeFile(existsFile.fileId, true);
rowData = removeArray(rowData, existsFile.fileId);
}
rowData.push({ rowData.push({
fileId: file.id, fileId: file.id,
fileName: file.name, fileName: file.name,
@ -185,6 +191,7 @@ layui.extend({
//所有文件上传成功后 //所有文件上传成功后
upload.on('uploadFinished', function (obj) {//成功后 upload.on('uploadFinished', function (obj) {//成功后
rowData = [];
loading.closeAll(); loading.closeAll();
let {interruptNum, queueNum, uploadFailNum} = upload.getStats(); let {interruptNum, queueNum, uploadFailNum} = upload.getStats();
option.uploadFinished && option.uploadFinished(uploadFailNum === 0 && interruptNum === 0 && queueNum === 0); option.uploadFinished && option.uploadFinished(uploadFailNum === 0 && interruptNum === 0 && queueNum === 0);
@ -278,6 +285,9 @@ layui.extend({
_file.entityId = that.formData.entityId; _file.entityId = that.formData.entityId;
_file.fileFullName = file.name; _file.fileFullName = file.name;
_file.labelStr = that.formData.labelStr _file.labelStr = that.formData.labelStr
if(that.formData.repairId){
_file.id = that.formData.repairId;
}
that.saveFileAndDir(_file, () => deferred.resolve()) that.saveFileAndDir(_file, () => deferred.resolve())
} else { } else {
deferred.reject(); deferred.reject();
@ -311,10 +321,10 @@ layui.extend({
let {fileMd5, source} = file; let {fileMd5, source} = file;
Util.post(fileCheckUrl, {fileMd5, name: ''}, false).then(res => { Util.post(fileCheckUrl, {fileMd5, name: ''}, false).then(res => {
if (res.flag) { if (res.flag) {
if (res.code == 0) { if (res.code === 0) {
// 文件不存在 // 文件不存在
uploadIndexMap.set(fileMd5, res.data); uploadIndexMap.set(fileMd5, res.data);
} else if (res.code == 1) { } else if (res.code === 1) {
// 文件存在,秒传 // 文件存在,秒传
let existsFile = res.data; let existsFile = res.data;
existsFile.entityId = that.formData.entityId; existsFile.entityId = that.formData.entityId;
@ -345,8 +355,8 @@ layui.extend({
* @param callback * @param callback
*/ */
Class.prototype.saveFileAndDir = function (file, callback) { Class.prototype.saveFileAndDir = function (file, callback) {
let that = this; let that = this, options = that.options;
Util.post(`/sys/file/upload/chunk/complete`, file, false).then((res) => { Util.post(options.fileMergeUrl, file, false).then((res) => {
that.setFileStatus(file); that.setFileStatus(file);
callback && callback(res); callback && callback(res);
}) })
@ -451,7 +461,7 @@ class Loading {
show(shade = true) { show(shade = true) {
if (this.index === -1) { if (this.index === -1) {
this.index = this.layer.load(0, {shade}); this.index = this.layer.load(0, {shade: shade ? 0.3 : 0});
} }
this.countDownLatch += 1; this.countDownLatch += 1;
} }

@ -2,14 +2,12 @@ package com.keyware.shandan.control.controller;
import com.keyware.shandan.bianmu.entity.DirectoryVo; import com.keyware.shandan.bianmu.entity.DirectoryVo;
import com.keyware.shandan.bianmu.service.DirPermissionService; import com.keyware.shandan.bianmu.service.DirPermissionService;
import com.keyware.shandan.bianmu.service.DirectoryMetadataService;
import com.keyware.shandan.bianmu.service.DirectoryService; import com.keyware.shandan.bianmu.service.DirectoryService;
import com.keyware.shandan.common.controller.BaseController; import com.keyware.shandan.common.controller.BaseController;
import com.keyware.shandan.common.enums.SystemTypes; import com.keyware.shandan.common.enums.SystemTypes;
import com.keyware.shandan.frame.config.security.SecurityUtil; import com.keyware.shandan.frame.config.security.SecurityUtil;
import com.keyware.shandan.system.entity.SysSetting; import com.keyware.shandan.system.entity.SysSetting;
import com.keyware.shandan.system.entity.SysUser; import com.keyware.shandan.system.entity.SysUser;
import com.keyware.shandan.system.service.SysFileService;
import com.keyware.shandan.system.utils.SysSettingUtil; import com.keyware.shandan.system.utils.SysSettingUtil;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -29,15 +27,6 @@ import org.springframework.web.servlet.ModelAndView;
@RequestMapping("/business/directory") @RequestMapping("/business/directory")
public class DirectoryController extends BaseController<DirectoryService, DirectoryVo, String> { public class DirectoryController extends BaseController<DirectoryService, DirectoryVo, String> {
@Autowired
private DirectoryService directoryService;
@Autowired
private DirectoryMetadataService directoryMetadataService;
@Autowired
private SysFileService sysFileService;
@Autowired @Autowired
private DirPermissionService dirPermissionService; private DirPermissionService dirPermissionService;

@ -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);
}
}

@ -3,8 +3,9 @@ package com.keyware.shandan.bianmu.mapper;
import com.keyware.shandan.bianmu.entity.DirectoryVo; import com.keyware.shandan.bianmu.entity.DirectoryVo;
import com.keyware.shandan.bianmu.entity.MetadataBasicVo; import com.keyware.shandan.bianmu.entity.MetadataBasicVo;
import com.keyware.shandan.common.mapper.IBaseMapper; import com.keyware.shandan.common.mapper.IBaseMapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.*;
import org.apache.ibatis.annotations.Update; import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.session.ResultHandler;
import java.util.List; import java.util.List;
@ -20,6 +21,7 @@ import java.util.List;
public interface DirectoryMapper extends IBaseMapper<DirectoryVo> { public interface DirectoryMapper extends IBaseMapper<DirectoryVo> {
/** /**
* 根据父目录ID获取目录信息 * 根据父目录ID获取目录信息
*
* @param parentId 父目录ID * @param parentId 父目录ID
* @param reviewStatus 审核状态 * @param reviewStatus 审核状态
* @return * @return
@ -28,6 +30,7 @@ public interface DirectoryMapper extends IBaseMapper<DirectoryVo> {
/** /**
* 获取目录下的数据资源 * 获取目录下的数据资源
*
* @param id 目录ID * @param id 目录ID
* @return * @return
*/ */
@ -35,11 +38,17 @@ public interface DirectoryMapper extends IBaseMapper<DirectoryVo> {
/** /**
* 获取目录下所有数据资源包含子级 * 获取目录下所有数据资源包含子级
*
* @param id 目录ID * @param id 目录ID
* @return - * @return -
*/ */
List<MetadataBasicVo> selectAllMetadataByDirectory(String id); List<MetadataBasicVo> selectAllMetadataByDirectory(String id);
@Update("update B_DIRECTORY set DIRECTORY_PATH = REPLACE(DIRECTORY_PATH, '${oldPath}', '${newPath}') where DIRECTORY_PATH like '${oldPath}%'") @Update("update B_DIRECTORY set DIRECTORY_PATH = REPLACE(DIRECTORY_PATH, #{oldPath}, #{newPath}) where DIRECTORY_PATH like '${oldPath}%'")
void updateDirectoryPath(String oldPath, String newPath); void updateDirectoryPath(String oldPath, String newPath);
@Select("SELECT * FROM B_DIRECTORY D1 WHERE EXISTS(SELECT * FROM B_DIRECTORY D2 WHERE D2.ID = #{dirId} AND D1.DIRECTORY_PATH LIKE D2.DIRECTORY_PATH || '/%')")
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
@ResultType(DirectoryVo.class)
void handlerAllChildrenListById(@Param("dirId") String dirId, ResultHandler<DirectoryVo> handler);
} }

@ -1,6 +1,5 @@
package com.keyware.shandan.bianmu.service; package com.keyware.shandan.bianmu.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.keyware.shandan.bianmu.entity.DirectoryVo; import com.keyware.shandan.bianmu.entity.DirectoryVo;
import com.keyware.shandan.bianmu.entity.MetadataBasicVo; import com.keyware.shandan.bianmu.entity.MetadataBasicVo;
import com.keyware.shandan.bianmu.enums.ReviewStatus; import com.keyware.shandan.bianmu.enums.ReviewStatus;
@ -8,6 +7,7 @@ import com.keyware.shandan.common.service.IBaseService;
import com.keyware.shandan.system.entity.SysFile; import com.keyware.shandan.system.entity.SysFile;
import com.keyware.shandan.system.entity.SysOrg; import com.keyware.shandan.system.entity.SysOrg;
import com.keyware.shandan.system.entity.SysUser; import com.keyware.shandan.system.entity.SysUser;
import org.apache.ibatis.session.ResultHandler;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
@ -127,4 +127,6 @@ public interface DirectoryService extends IBaseService<DirectoryVo, String> {
boolean saveFileDIr(SysFile file, String parentId); boolean saveFileDIr(SysFile file, String parentId);
List<DirectoryVo> childrenListByParent(String parentId); List<DirectoryVo> childrenListByParent(String parentId);
void handlerAllChildrenListById(String dirId, ResultHandler<DirectoryVo> handler);
} }

@ -23,6 +23,8 @@ import com.keyware.shandan.system.entity.SysOrg;
import com.keyware.shandan.system.entity.SysUser; import com.keyware.shandan.system.entity.SysUser;
import com.keyware.shandan.system.service.SysFileService; import com.keyware.shandan.system.service.SysFileService;
import com.keyware.shandan.system.service.SysOrgService; import com.keyware.shandan.system.service.SysOrgService;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.session.ResultHandler;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -376,6 +378,11 @@ public class DirectoryServiceImpl extends BaseServiceImpl<DirectoryMapper, Direc
return list(new QueryWrapper<>(param)); return list(new QueryWrapper<>(param));
} }
@Override
public void handlerAllChildrenListById(String dirId, ResultHandler<DirectoryVo> handler) {
directoryMapper.handlerAllChildrenListById(dirId, handler);
}
private DirectoryVo createDirByOrg(SysOrg org, String parentDirId) throws Exception { private DirectoryVo createDirByOrg(SysOrg org, String parentDirId) throws Exception {
DirectoryVo dir = DirectoryVo.builder().build(); DirectoryVo dir = DirectoryVo.builder().build();
dir.setParentId(parentDirId); dir.setParentId(parentDirId);

@ -1,8 +1,15 @@
package com.keyware.shandan.system.mapper; package com.keyware.shandan.system.mapper;
import com.keyware.shandan.system.entity.SysFile; import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.keyware.shandan.common.mapper.IBaseMapper; import com.keyware.shandan.common.mapper.IBaseMapper;
import com.keyware.shandan.system.entity.SysFile;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.ResultSetType;
import java.util.List; import java.util.List;
@ -18,4 +25,8 @@ import java.util.List;
public interface SysFileMapper extends IBaseMapper<SysFile> { public interface SysFileMapper extends IBaseMapper<SysFile> {
List<SysFile> listByDirId(String dirId); List<SysFile> listByDirId(String dirId);
@Select("SELECT * FROM SYS_FILE ${ew.customSqlSegment}")
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 2)
Cursor<SysFile> streamList(@Param(Constants.WRAPPER) Wrapper<SysFile> wrapper);
} }

@ -4,7 +4,6 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.keyware.shandan.bianmu.entity.DirectoryVo; import com.keyware.shandan.bianmu.entity.DirectoryVo;
import com.keyware.shandan.bianmu.entity.LabelEntity;
import com.keyware.shandan.bianmu.enums.DirectoryType; import com.keyware.shandan.bianmu.enums.DirectoryType;
import com.keyware.shandan.bianmu.enums.ReviewStatus; import com.keyware.shandan.bianmu.enums.ReviewStatus;
import com.keyware.shandan.bianmu.service.DirectoryService; import com.keyware.shandan.bianmu.service.DirectoryService;
@ -26,7 +25,6 @@ import com.keyware.shandan.system.mapper.SysFileMapper;
import com.keyware.shandan.system.repositories.EsSysFileRepository; import com.keyware.shandan.system.repositories.EsSysFileRepository;
import com.keyware.shandan.system.service.SysFileService; import com.keyware.shandan.system.service.SysFileService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -37,7 +35,6 @@ import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -141,20 +138,6 @@ public class SysFileServiceImpl extends BaseServiceImpl<SysFileMapper, SysFile,
file.setCurrentChunkIndex(chunk.getChunkIndex()); file.setCurrentChunkIndex(chunk.getChunkIndex());
updateById(file); updateById(file);
} }
// 判断是否是最后一片分片
/*if (chunk.isLast()) {
// 文件信息保存之后,该属性会丢失,需要重新赋值
file.setFileFullName(fileInfo.getFileFullName());
// 删除文件分片数据
deleteFileChunkByFile(file);
// 删除文件分片临时文件
deleteChunkFolder(file);
file.setIsMerge(true);
updateById(file);
}*/
return file; return file;
} }
@ -211,7 +194,7 @@ public class SysFileServiceImpl extends BaseServiceImpl<SysFileMapper, SysFile,
esSysFileRepository.delete(file); esSysFileRepository.delete(file);
// 删除存储的文件 // 删除存储的文件
deleteFolder(getFileAbsolutePath(file)); deleteFolder(getFileAbsolutePath(file));
}else{ } else {
// 删除数据的记录 // 删除数据的记录
removeById(id); removeById(id);
} }
@ -219,22 +202,10 @@ public class SysFileServiceImpl extends BaseServiceImpl<SysFileMapper, SysFile,
return Result.of(true); return Result.of(true);
} }
private void mergeSingleFileChunk(@NotNull SysFile file, @NotNull SysFileChunk chunk) throws IOException {
generateFilePath(file);
File targetFile = new File(getFileAbsolutePath(file));
if (!targetFile.exists()) {
targetFile.createNewFile();
}
File chunkFile = new File(getFileChunkAbsolutePath(chunk));
FileOutputStream outputStream = new FileOutputStream(targetFile, true);
FileUtils.copyFile(chunkFile, outputStream);
outputStream.close();
}
@Override @Override
public void clearFiles() { public void clearFiles() {
File file = new File(customProperties.getFileStorage().getPath()); File file = new File(customProperties.getFileStorage().getPath());
if(file.exists()){ if (file.exists()) {
file.delete(); file.delete();
} }
} }
@ -339,16 +310,6 @@ public class SysFileServiceImpl extends BaseServiceImpl<SysFileMapper, SysFile,
remove(query); remove(query);
} }
/**
* 删除文件分片
*
* @param file 文件信息
*/
private void deleteFileChunkByFile(SysFile file) {
QueryWrapper<SysFileChunk> deleteFileChunkQuery = new QueryWrapper<>();
deleteFileChunkQuery.eq("FILE_MD5", file.getMD5());
fileChunkMapper.delete(deleteFileChunkQuery);
}
/** /**
* 生成文件的路径名称包含文件名称及后缀 * 生成文件的路径名称包含文件名称及后缀

@ -117,7 +117,7 @@ public class FileChunkUploadUtil {
@Data @Data
static class TempFile { public static class TempFile {
private SysFile sysFile; private SysFile sysFile;
private ConcurrentHashMap<String, SysFileChunk> chunksCache = new ConcurrentHashMap<>(); private ConcurrentHashMap<String, SysFileChunk> chunksCache = new ConcurrentHashMap<>();

@ -22,8 +22,9 @@ layui.use(['layer', 'uploader', 'element', 'form', 'laydate', 'dict', 'datalabel
let uploader = layui.uploader.render({ let uploader = layui.uploader.render({
url: `${ctx}/sys/file/upload/chunk`,//上传文件服务器地址,必填 url: `${ctx}/sys/file/upload/chunk`,//上传文件服务器地址,必填
fileCheckUrl: `${ctx}/sys/file/upload/check`,//文件校验地址 fileCheckUrl: `${ctx}/sys/file/upload/check`,//文件校验地址
fileMergeUrl: `/sys/file/upload/chunk/complete`,
fileBoxEle: "#uploader-table",//上传容器 fileBoxEle: "#uploader-table",//上传容器
chooseFolder: true, multiple: true,
dirUpload: param.dirUpload, dirUpload: param.dirUpload,
getFormData: getFormVal, getFormData: getFormVal,
uploadFinished: function (ok) { uploadFinished: function (ok) {

@ -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>

@ -17,6 +17,8 @@
const file = /*[[${fileData}]]*/ {}; const file = /*[[${fileData}]]*/ {};
const fileViewUrl = /*[[${fileViewUrl}]]*/ ''; const fileViewUrl = /*[[${fileViewUrl}]]*/ '';
const downloadUrl = /*[[${downloadUrl}]]*/ ''; const downloadUrl = /*[[${downloadUrl}]]*/ '';
const fileNotExists = /*[[${fileNotExists}]]*/ false;
const fileReadError = /*[[${fileReadError}]]*/ false;
</script> </script>
</head> </head>
<body> <body>