@@ -4,15 +4,22 @@ import com.ruoyi.common.core.controller.BaseController; | |||||
import com.ruoyi.common.core.domain.AjaxResult; | import com.ruoyi.common.core.domain.AjaxResult; | ||||
import com.ruoyi.file.model.FileModel; | import com.ruoyi.file.model.FileModel; | ||||
import com.ruoyi.file.model.FileSystemFilter; | import com.ruoyi.file.model.FileSystemFilter; | ||||
import com.ruoyi.file.object.UploadResp; | |||||
import com.ruoyi.file.request.PutFileReq; | |||||
import com.ruoyi.file.service.FileSystemService; | import com.ruoyi.file.service.FileSystemService; | ||||
import io.swagger.annotations.Api; | import io.swagger.annotations.Api; | ||||
import io.swagger.annotations.ApiOperation; | import io.swagger.annotations.ApiOperation; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import org.springframework.security.access.prepost.PreAuthorize; | import org.springframework.security.access.prepost.PreAuthorize; | ||||
import org.springframework.web.bind.annotation.GetMapping; | import org.springframework.web.bind.annotation.GetMapping; | ||||
import org.springframework.web.bind.annotation.PathVariable; | |||||
import org.springframework.web.bind.annotation.PostMapping; | |||||
import org.springframework.web.bind.annotation.RequestMapping; | import org.springframework.web.bind.annotation.RequestMapping; | ||||
import org.springframework.web.bind.annotation.RequestParam; | |||||
import org.springframework.web.bind.annotation.RestController; | import org.springframework.web.bind.annotation.RestController; | ||||
import org.springframework.web.multipart.MultipartFile; | |||||
import javax.validation.Valid; | |||||
import java.util.List; | import java.util.List; | ||||
/** | /** | ||||
@@ -42,4 +49,16 @@ public class FileSystemController extends BaseController | |||||
return AjaxResult.success(list); | return AjaxResult.success(list); | ||||
} | } | ||||
/** | |||||
* 上传文件 | |||||
*/ | |||||
@ApiOperation("上传文件") | |||||
@PreAuthorize("@ss.hasPermi('admin:fs:put')") | |||||
@PostMapping("/put") | |||||
public AjaxResult put(@Valid PutFileReq req, MultipartFile file) | |||||
{ | |||||
FileModel upload = fileSystemService.put(req, file); | |||||
return AjaxResult.success(upload); | |||||
} | |||||
} | } |
@@ -48,7 +48,7 @@ public class OpenFileController | |||||
* @param fileName 文件名称 | * @param fileName 文件名称 | ||||
* @param delete 是否删除 | * @param delete 是否删除 | ||||
*/ | */ | ||||
@GetMapping("/download") | |||||
//@GetMapping("/download") | |||||
public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) | public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) | ||||
{ | { | ||||
try | try | ||||
@@ -95,7 +95,7 @@ public final class PathUtils | |||||
else if(i == part.length() - 1) | else if(i == part.length() - 1) | ||||
return part; | return part; | ||||
else | else | ||||
return part.substring(0, i); | |||||
return part.substring(0, i + 1); | |||||
} | } | ||||
private PathUtils() {} | private PathUtils() {} | ||||
@@ -6,10 +6,12 @@ import com.ruoyi.file.enums.FileType; | |||||
import lombok.Data; | import lombok.Data; | ||||
import java.io.File; | import java.io.File; | ||||
import java.util.Comparator; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Objects; | |||||
@Data | @Data | ||||
public final class FileModel | |||||
public final class FileModel implements Comparator | |||||
{ | { | ||||
private String fileName; | private String fileName; | ||||
private String filePath; | private String filePath; | ||||
@@ -42,4 +44,14 @@ public final class FileModel | |||||
filePath = PathUtils.normalizeSlash(filePath); | filePath = PathUtils.normalizeSlash(filePath); | ||||
type = FileType.FileType(file); | type = FileType.FileType(file); | ||||
} | } | ||||
@Override | |||||
public int compare(Object o1, Object o2) | |||||
{ | |||||
FileModel a = (FileModel)o1; | |||||
FileModel b = (FileModel)o2; | |||||
if(Objects.equals(a.type, b.type)) | |||||
return a.fileName.compareToIgnoreCase(b.fileName); | |||||
return -(a.type - b.type); | |||||
} | |||||
} | } |
@@ -0,0 +1,23 @@ | |||||
package com.ruoyi.file.request; | |||||
import lombok.Data; | |||||
import javax.validation.constraints.NotEmpty; | |||||
@Data | |||||
public final class PutFileReq | |||||
{ | |||||
@NotEmpty(message = "上传路径不能为空") | |||||
private String path; | |||||
private Integer ifExists; | |||||
public boolean allowOverride() | |||||
{ | |||||
return null != ifExists && ifExists == 2; | |||||
} | |||||
public boolean allowRename() | |||||
{ | |||||
return null != ifExists && ifExists == 1; | |||||
} | |||||
} |
@@ -1,13 +1,19 @@ | |||||
package com.ruoyi.file.service; | package com.ruoyi.file.service; | ||||
import cn.hutool.core.io.FileUtil; | |||||
import com.ruoyi.common.exception.ASSERT; | import com.ruoyi.common.exception.ASSERT; | ||||
import com.ruoyi.common.utils.StringUtils; | import com.ruoyi.common.utils.StringUtils; | ||||
import com.ruoyi.common.utils.file.PathUtils; | |||||
import com.ruoyi.file.model.FileSystemFilter; | import com.ruoyi.file.model.FileSystemFilter; | ||||
import com.ruoyi.file.model.FileModel; | import com.ruoyi.file.model.FileModel; | ||||
import com.ruoyi.file.request.PutFileReq; | |||||
import org.apache.commons.io.FilenameUtils; | |||||
import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
import org.springframework.web.multipart.MultipartFile; | |||||
import java.io.File; | import java.io.File; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Comparator; | |||||
import java.util.List; | import java.util.List; | ||||
@Service | @Service | ||||
@@ -36,6 +42,61 @@ public class FileSystemService | |||||
list.add(new FileModel(f)); | list.add(new FileModel(f)); | ||||
} | } | ||||
} | } | ||||
list.sort(new FileModel()); | |||||
return list; | return list; | ||||
} | } | ||||
public FileModel put(PutFileReq req, MultipartFile file) | |||||
{ | |||||
String dst = PathUtils.appendPaths(req.getPath(), file.getOriginalFilename()); | |||||
File dstFile = new File(dst); | |||||
if(dstFile.exists()) | |||||
{ | |||||
if(dstFile.isDirectory()) | |||||
throw new RuntimeException("目标文件是目录: " + dst); | |||||
if(req.allowRename()) | |||||
{ | |||||
int i = 1; | |||||
do | |||||
{ | |||||
String originalFilename = file.getOriginalFilename(); | |||||
String extension = FilenameUtils.getExtension(originalFilename); | |||||
originalFilename = PathUtils.removeExtension(originalFilename); | |||||
originalFilename += "(" + i++ + ")"; | |||||
if(StringUtils.isNotEmpty(extension)) | |||||
originalFilename += "." + extension; | |||||
dst = PathUtils.appendPaths(req.getPath(), originalFilename); | |||||
dstFile = new File(dst); | |||||
} while(dstFile.exists()); | |||||
} | |||||
else if(req.allowOverride()) | |||||
{ | |||||
// override | |||||
} | |||||
else | |||||
throw new RuntimeException("目标文件已存在: " + dst); | |||||
} | |||||
File parentFile = dstFile.getParentFile(); | |||||
if(null == parentFile) | |||||
throw new RuntimeException("必须指定存储目录: " + dst); | |||||
if(parentFile.exists() && !parentFile.isDirectory()) | |||||
throw new RuntimeException("存储目录必须是文件夹: " + dst); | |||||
if(!parentFile.exists()) | |||||
{ | |||||
if(!parentFile.mkdirs()) | |||||
throw new RuntimeException("存储目录不存在且创建失败: " + dst); | |||||
} | |||||
try | |||||
{ | |||||
byte[] bytes = file.getBytes(); | |||||
FileUtil.writeBytes(bytes, dstFile); | |||||
return new FileModel(dstFile); | |||||
} | |||||
catch(Exception e) | |||||
{ | |||||
e.printStackTrace(); | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
} | } |
@@ -60,6 +60,17 @@ export default { | |||||
this.$emit('input', data.filePath); | this.$emit('input', data.filePath); | ||||
this.$emit('selected', data); | this.$emit('selected', data); | ||||
}, | }, | ||||
reload() { | |||||
this.files = []; | |||||
let query = { | |||||
filePath: null, | |||||
fileType: this.fileType, | |||||
showHidden: this.showHidden, | |||||
}; | |||||
ls(query).then((resp) => { | |||||
this.files = resp.data; | |||||
}); | |||||
}, | |||||
} | } | ||||
}; | }; | ||||
</script> | </script> | ||||
@@ -0,0 +1,204 @@ | |||||
<template> | |||||
<div class="app-container"> | |||||
<el-row :gutter="10" class="mb8"> | |||||
<el-col :span="1.5"> | |||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="upload" v-hasPermi="['admin:fs:put']">Put</el-button> | |||||
</el-col> | |||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="refresh"></right-toolbar> | |||||
</el-row> | |||||
<el-row> | |||||
<el-col :span="8"> | |||||
<ServerFileChooser ref="fileBrowser" v-model="form.path" :file-type="2" @selected="getFiles"/> | |||||
</el-col> | |||||
<el-col :span="16"> | |||||
<el-table :data="files"> | |||||
<el-table-column type="index" label="序号" width="50" align="center" /> | |||||
<el-table-column label="文件名" align="left" header-align="center" prop="fileName" sortable/> | |||||
</el-table> | |||||
</el-col> | |||||
</el-row> | |||||
<el-dialog title="上传文件" :visible.sync="open" width="700px" append-to-body> | |||||
<el-form ref="form" :model="form" label-width="100px" :rules="rules"> | |||||
<el-form-item label="上传至"> | |||||
<div>{{form.path}}</div> | |||||
</el-form-item> | |||||
<el-form-item label="如果已存在" prop="ifExists"> | |||||
<el-select v-model="form.ifExists" placeholder="请选择如果已存在行为"> | |||||
<el-option | |||||
v-for="dict in ifExistsDict" | |||||
:key="dict.value" | |||||
:label="dict.label" | |||||
:value="dict.value" | |||||
></el-option> | |||||
</el-select> | |||||
</el-form-item> | |||||
<el-form-item label="文件" prop="file"> | |||||
<el-upload | |||||
ref="upload" | |||||
drag | |||||
:action="uploadUrl" | |||||
:auto-upload="false" | |||||
:data="form" | |||||
:limit="1" | |||||
:multiple="false" | |||||
:headers="header" | |||||
:on-success="onUploadSuccess" | |||||
:on-error="onUploadError" | |||||
:before-upload="startUpload" | |||||
> | |||||
<i class="el-icon-upload"></i> | |||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> | |||||
</el-upload> | |||||
</el-form-item> | |||||
</el-form> | |||||
<div slot="footer" class="dialog-footer"> | |||||
<el-button type="primary" @click="put" :loading="uploading">上 传</el-button> | |||||
<el-button @click="cancel">取 消</el-button> | |||||
</div> | |||||
</el-dialog> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import DeptSelect from "@/components/common/DeptSelect.vue"; | |||||
import ServerFileChooser from "@/components/ServerFileChooser.vue"; | |||||
import FullscreenDialog from "@/components/FullscreenDialog.vue"; | |||||
import ProjectState from "@/views/file/state/ProjectState.vue"; | |||||
import {stateList, gc, stateMachine, dump, runtimeState} from "@/api/file/state"; | |||||
import Descriptions from "@/components/common/Descriptions.vue"; | |||||
import {formatFileSize} from "@/utils/file"; | |||||
import {ls} from "@/api/file/filesystem"; | |||||
import {getToken} from "@/utils/auth"; | |||||
export default { | |||||
name: "State", | |||||
components: { | |||||
Descriptions, | |||||
ProjectState, | |||||
FullscreenDialog, | |||||
ServerFileChooser, | |||||
DeptSelect | |||||
}, | |||||
dicts: ['sys_bool'], | |||||
data() { | |||||
return { | |||||
loading: false, | |||||
uploading: false, | |||||
showSearch: false, | |||||
// 总条数 | |||||
total: 0, | |||||
uploadUrl: '/api/filesystem/put', | |||||
open: false, | |||||
queryParams: { | |||||
// 分页 | |||||
pageNum: 1, | |||||
pageSize: 10, | |||||
// 查询排序 | |||||
//orderByColumn: "id", | |||||
//isAsc: "desc", | |||||
// 翻译字典 | |||||
//translate_dict: "1", | |||||
}, | |||||
form: { | |||||
path: '', | |||||
ifExists: 0, | |||||
}, | |||||
files: [], | |||||
ifExistsDict: [ | |||||
{label: '中止', value: 0,}, | |||||
{label: '重命名', value: 1,}, | |||||
{label: '覆盖', value: 2,}, | |||||
], | |||||
rules: { | |||||
ifExists: [ | |||||
{ required: true, message: "如果已存在行为不能为空", trigger: "change" } | |||||
], | |||||
}, | |||||
header: { | |||||
Authorization: "Bearer " + getToken(), | |||||
}, | |||||
}; | |||||
}, | |||||
created() { | |||||
}, | |||||
methods: { | |||||
// 取消按钮 | |||||
cancel() { | |||||
this.open = false; | |||||
}, | |||||
// 表单重置 | |||||
reset() { | |||||
this.form = { | |||||
path: '', | |||||
ifExists: 0, | |||||
}; | |||||
this.$refs.form.resetField(); | |||||
}, | |||||
refresh() { | |||||
this.$refs.fileBrowser.reload(); | |||||
this.files = []; | |||||
}, | |||||
getFiles() { | |||||
this.files = []; | |||||
if(this.form.path) | |||||
{ | |||||
let query = { | |||||
filePath: this.form.path, | |||||
showHidden: true, | |||||
}; | |||||
this.loading = true; | |||||
ls(query).then((resp) => { | |||||
this.files = resp.data; | |||||
}).finally(() => this.loading = false); | |||||
} | |||||
}, | |||||
resetQuery() { | |||||
this.resetForm("queryForm"); | |||||
this.handleQuery(); | |||||
}, | |||||
fileSizeFormatter(row, col, val) { | |||||
return formatFileSize(val); | |||||
}, | |||||
upload() { | |||||
if(!this.form.path) | |||||
{ | |||||
this.$message.error('请先选择存储目录'); | |||||
return; | |||||
} | |||||
this.uploading = false; | |||||
this.open = true; | |||||
this.$nextTick(() => { | |||||
this.$refs.upload.clearFiles(); | |||||
}); | |||||
}, | |||||
put() { | |||||
this.$refs.upload.submit(); | |||||
}, | |||||
onUploadSuccess(response, file, fileList) { | |||||
this.uploading = false; | |||||
if(response.code == 200) | |||||
{ | |||||
this.$message.success('上传成功! 路径为: ' + response.data.filePath); | |||||
this.cancel(); | |||||
this.getFiles(); | |||||
} | |||||
else | |||||
this.$message.error('上传失败: ' + response.msg); | |||||
}, | |||||
onUploadError(err, file, fileList) { | |||||
this.uploading = false; | |||||
this.$message.error('上传失败: ' + err); | |||||
}, | |||||
startUpload(file) { | |||||
this.uploading = true; | |||||
}, | |||||
}, | |||||
filters: { | |||||
formatFileSize, | |||||
}, | |||||
}; | |||||
</script> |