实现小文件在浏览器的秒传功能
This commit is contained in:
parent
2342f0202e
commit
26a2be3751
@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import xyz.longicorn.driver.config.BizException;
|
||||
import xyz.longicorn.driver.dto.ApiResult;
|
||||
import xyz.longicorn.driver.dto.FastUploadFile;
|
||||
import xyz.longicorn.driver.pojo.FileInfo;
|
||||
import xyz.longicorn.driver.pojo.FolderInfo;
|
||||
import xyz.longicorn.driver.service.FileService;
|
||||
@ -54,17 +55,11 @@ public class UploadController {
|
||||
}
|
||||
fileInfo.setFolderId(folder.getId());
|
||||
}
|
||||
fileInfo.setUid(1l);
|
||||
fileInfo.setUid(1);
|
||||
fileInfo.setHash(md5);//
|
||||
fileInfo.setName(file.getOriginalFilename());
|
||||
fileInfo.setSize(file.getSize());
|
||||
String type = file.getContentType().toLowerCase();
|
||||
if (FILE_MIME_TYPE.containsKey(type)) {
|
||||
type = FILE_MIME_TYPE.get(type);
|
||||
} else {
|
||||
type = "file";
|
||||
}
|
||||
fileInfo.setType(type);
|
||||
fileInfo.setType(getType(file.getContentType()));
|
||||
if (f != null) { // 系统已经存在了该文件
|
||||
// 不保存上传文件 直接copy数据
|
||||
fileInfo.setPath(f.getPath());
|
||||
@ -77,4 +72,23 @@ public class UploadController {
|
||||
fileService.save(fileInfo);//保存文件信息
|
||||
return ApiResult.success(null);
|
||||
}
|
||||
|
||||
// 将mimetype -> 文件后缀名的类型
|
||||
private String getType(String mimeType) {
|
||||
String type = mimeType.toLowerCase();
|
||||
if (FILE_MIME_TYPE.containsKey(type)) {
|
||||
return FILE_MIME_TYPE.get(type);
|
||||
}
|
||||
return "file";
|
||||
}
|
||||
|
||||
@PostMapping("/fast-upload")
|
||||
public ApiResult fastUpload(@RequestBody FastUploadFile file) {
|
||||
int uid = 1;
|
||||
|
||||
file.setType(getType(file.getType()));
|
||||
|
||||
FileInfo info = fileService.fastUpload(uid, file);
|
||||
return ApiResult.success(info);
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,10 @@ package xyz.longicorn.driver.dao;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import xyz.longicorn.driver.pojo.FolderInfo;
|
||||
|
||||
@Mapper
|
||||
public interface FolderInfoMapper extends BaseMapper<FolderInfo> {
|
||||
public FolderInfo getByPath(@Param("uid") int uid, @Param("path") String path);
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package xyz.longicorn.driver.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class FastUploadFile {
|
||||
private String name;
|
||||
private Long size;
|
||||
private String type;
|
||||
private String hash;
|
||||
private String folder;
|
||||
}
|
@ -19,7 +19,7 @@ import java.util.Date;
|
||||
@Accessors(chain = true)
|
||||
public class FileInfo implements Serializable {
|
||||
private Long id;
|
||||
private Long uid;
|
||||
private Integer uid;
|
||||
private String name;
|
||||
private String hash;
|
||||
private Long folderId;
|
||||
|
@ -3,13 +3,21 @@ package xyz.longicorn.driver.service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
import xyz.longicorn.driver.config.BizException;
|
||||
import xyz.longicorn.driver.dao.FileInfoMapper;
|
||||
import xyz.longicorn.driver.dao.FolderInfoMapper;
|
||||
import xyz.longicorn.driver.dto.FastUploadFile;
|
||||
import xyz.longicorn.driver.pojo.FileInfo;
|
||||
import xyz.longicorn.driver.pojo.FolderInfo;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class FileService extends ServiceImpl<FileInfoMapper, FileInfo> {
|
||||
@Resource
|
||||
private FolderInfoMapper folderInfoMapper;
|
||||
|
||||
/**
|
||||
* 查询了目录下的文件列表集合
|
||||
*
|
||||
@ -31,4 +39,26 @@ public class FileService extends ServiceImpl<FileInfoMapper, FileInfo> {
|
||||
q.last("limit 1");
|
||||
return this.getOne(q);
|
||||
}
|
||||
|
||||
public FileInfo fastUpload(int uid, FastUploadFile file) {
|
||||
long folderId = 0;
|
||||
if (!"/".equals(file.getFolder())) { // 不是根目录
|
||||
final FolderInfo folder = folderInfoMapper.getByPath(uid, file.getFolder());
|
||||
if (null == folder) throw BizException.create("保存目录不存在");
|
||||
folderId = folder.getId(); // 设置上传目录编号
|
||||
}
|
||||
|
||||
FileInfo info = this.getByMd5(file.getHash());//查询是否存在要上传的文件
|
||||
if (info == null) return null; // 没有存在 只能走普通上传
|
||||
//
|
||||
FileInfo newFile = new FileInfo(); // 要保存的数据
|
||||
newFile.setPath(info.getPath());
|
||||
newFile.setHash(info.getHash());
|
||||
newFile.setType(file.getType());
|
||||
newFile.setFolderId(folderId);
|
||||
newFile.setUid(uid);
|
||||
newFile.setName(file.getName());
|
||||
newFile.setSize(info.getSize());
|
||||
return this.save(newFile) ? newFile : null;
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,7 @@ public class FolderService extends ServiceImpl<FolderInfoMapper, FolderInfo> {
|
||||
* @return
|
||||
*/
|
||||
public FolderInfo getByPath(int uid, String path) {
|
||||
QueryWrapper q = new QueryWrapper();
|
||||
q.eq("uid", uid);
|
||||
q.eq("path", path);
|
||||
return this.getOne(q);
|
||||
return this.getBaseMapper().getByPath(uid, path);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,7 +82,7 @@ public class FolderService extends ServiceImpl<FolderInfoMapper, FolderInfo> {
|
||||
fNew.setUid(uid);
|
||||
fNew.setName(name);
|
||||
fNew.setParentId(parentId);
|
||||
fNew.setPath((parent.equals("/")?"":parent) + "/" + name);
|
||||
fNew.setPath((parent.equals("/") ? "" : parent) + "/" + name);
|
||||
return this.save(fNew) ? fNew : null;
|
||||
}
|
||||
}
|
||||
|
7
driver/src/main/resources/mapper/FolderInfoMapper.xml
Normal file
7
driver/src/main/resources/mapper/FolderInfoMapper.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?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="xyz.longicorn.driver.dao.FolderInfoMapper">
|
||||
<select id="getByPath" resultType="FolderInfo">
|
||||
select * from folder_info where uid=#{uid} and path=#{path} limit 1
|
||||
</select>
|
||||
</mapper>
|
1
web/package-lock.json
generated
1
web/package-lock.json
generated
@ -22,6 +22,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^2.3.1",
|
||||
"@vue/reactivity": "^3.2.33",
|
||||
"vite": "^2.9.7"
|
||||
}
|
||||
},
|
||||
|
@ -22,6 +22,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^2.3.1",
|
||||
"@vue/reactivity": "^3.2.33",
|
||||
"vite": "^2.9.7"
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import {dayjs, ElMessage, ElMessageBox} from 'element-plus'
|
||||
import api from "./service/api";
|
||||
import qs from "qs";
|
||||
import FileUploader from "./components/file-uploader/Index.vue";
|
||||
import FileBlockItem from "./components/FileBlockItem.vue";
|
||||
|
||||
export default {
|
||||
setup(){
|
||||
@ -80,7 +81,7 @@ export default {
|
||||
return pathList;
|
||||
}
|
||||
},
|
||||
components: {FileUploader, FileIcon, ArrowDown, Grid, FolderAdd},
|
||||
components: {FileBlockItem, FileUploader, FileIcon, ArrowDown, Grid, FolderAdd},
|
||||
mounted() {
|
||||
// window.addEventListener('popstate',()=>console.log(location.href))
|
||||
window.addEventListener('hashchange', this.handleHashChange) // 添加监听
|
||||
@ -128,18 +129,6 @@ export default {
|
||||
ext = name.substr(extIndex);
|
||||
return name.substr(0, (15 - ext.length)) + "**" + ext;
|
||||
},
|
||||
showFile(file, e) {
|
||||
console.log(e)
|
||||
if (file.type != 'folder') {
|
||||
console.log(this.$refs.fileIcon)
|
||||
this.$refs.fileIcon?.showPreview(file);
|
||||
return;
|
||||
}
|
||||
// this.loadFileByPath(file.path)
|
||||
// console.log(file)
|
||||
// window.history.pushState({},null,'/show?path=' + file.name)
|
||||
location.hash = '?path=' + file.path
|
||||
},
|
||||
fileMenuClick(e) {
|
||||
window.alert(JSON.stringify(e));
|
||||
},
|
||||
@ -335,18 +324,7 @@ export default {
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="12" :sm="6" :md="4" :lg="3" :xl="2" v-for="(file, index) in fileData"
|
||||
:key="index">
|
||||
<div class="file-block-item" @click="showFile(file,$event)"
|
||||
@contextmenu.prevent.stop="$refs.fileMenu.showMenu($event,file)">
|
||||
<div class="file-image">
|
||||
<FileIcon :file="file" style="width:90%"/>
|
||||
</div>
|
||||
<div class="file-name">{{ formatName(file.name) }}</div>
|
||||
<div class="file-info">
|
||||
{{
|
||||
file.type == 'folder' ? formatDate(file.createTime) : formatSize(file.size)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<file-block-item :file="file" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
74
web/src/components/FileBlockItem.vue
Normal file
74
web/src/components/FileBlockItem.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="file-block-item" @click="showFile(file,$event)"
|
||||
@contextmenu.prevent.stop="$parent.$refs.fileMenu.showMenu($event,file)">
|
||||
<div class="file-image">
|
||||
<FileIcon :file="file" ref="fileItemIcon" style="width:90%"/>
|
||||
</div>
|
||||
<div class="file-name">{{ formatName(file.name) }}</div>
|
||||
<div class="file-info">
|
||||
{{
|
||||
file.type == 'folder' ? formatDate(file.createTime) : formatSize(file.size)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ref} from "@vue/reactivity";
|
||||
import {dayjs} from "element-plus";
|
||||
import FileIcon from "./FileIcon.vue";
|
||||
|
||||
export default {
|
||||
name: "FileBlockItem",
|
||||
components: {FileIcon},
|
||||
props: {
|
||||
file: {
|
||||
type: Object, request: true
|
||||
}
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {file:FileItem} props
|
||||
*/
|
||||
setup(props) {
|
||||
/**
|
||||
*
|
||||
* @type {FileIconInstance}
|
||||
*/
|
||||
const fileItemIcon = ref(null)
|
||||
const showFile = (file) => {
|
||||
if (file.type != 'folder') {
|
||||
console.log(fileItemIcon)
|
||||
// fileItemIcon.showPreview();
|
||||
return;
|
||||
}
|
||||
location.hash = '?path=' + file.path
|
||||
}
|
||||
return {
|
||||
file: props.file, showFile, fileItemIcon
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDate(time, format = 'MM-DD') {
|
||||
return dayjs(time).format(format);
|
||||
},
|
||||
formatSize(a, b = 2) {
|
||||
if (0 == a) return "0 B";
|
||||
let c = 1024, d = b || 2, e = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
|
||||
f = Math.floor(Math.log(a) / Math.log(c));
|
||||
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + " " + e[f];
|
||||
},
|
||||
// 对文件名进行处理
|
||||
formatName(name) {
|
||||
if (!name || name.length < 15) return name;
|
||||
const extIndex = name.lastIndexOf('.'),
|
||||
ext = name.substr(extIndex);
|
||||
return name.substr(0, (15 - ext.length)) + "**" + ext;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -75,8 +75,9 @@ import FileUpload from "./FileUpload.vue";
|
||||
import {Upload, CircleCloseFilled, CloseBold, ArrowDownBold} from '@element-plus/icons-vue'
|
||||
import Hash from 'browser-md5-file';
|
||||
import api from "../../service/api";
|
||||
|
||||
// 创建计算hash的对象
|
||||
const hash = new Hash();
|
||||
|
||||
const FileStatus = {
|
||||
Processing: 0,
|
||||
Ready: 1,
|
||||
@ -87,11 +88,11 @@ const FileStatus = {
|
||||
|
||||
export default {
|
||||
name: "Index",
|
||||
emits:{
|
||||
emits: {
|
||||
/**
|
||||
* 文件上传后的触发此事件
|
||||
*/
|
||||
uploadSuccess:null
|
||||
uploadSuccess: null
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
@ -129,19 +130,63 @@ export default {
|
||||
const index = this.fileListData.indexOf(file);// 获取到要删除的下表
|
||||
this.fileListData.splice(index, 1)
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {FileList} files
|
||||
*/
|
||||
add(files) {
|
||||
this.showUploadList = true;
|
||||
Array.from(files).forEach(file => {
|
||||
this.fileListData.push({
|
||||
const size = file.size / 1024 // KB
|
||||
if (size < 1024) { // 小于 1024KB
|
||||
hash.md5(file, (err, md5) => {
|
||||
this.fastUpload(file, md5);
|
||||
}, p => console.log(p))
|
||||
} else {
|
||||
this.pushUploadFile(file, this.currentFolder)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 添加文件到列表 用于展示
|
||||
pushUploadFile(file, folder, status = FileStatus.Ready, progress = 0) {
|
||||
const f = {
|
||||
id: this.fileId++,
|
||||
file,
|
||||
name: file.name,
|
||||
status: FileStatus.Ready,
|
||||
folder:this.currentFolder, // 随当前文件选择的目录
|
||||
progress: 0,
|
||||
status,
|
||||
folder, // 随当前文件选择的目录
|
||||
progress,
|
||||
};
|
||||
this.fileListData.push(f);
|
||||
this.uploadFile(f);//直接上传
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {File} file
|
||||
* @param hash
|
||||
*/
|
||||
fastUpload(file, hash) {
|
||||
const folder = this.currentFolder;
|
||||
api.file.fastUpload({
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
hash,
|
||||
folder
|
||||
}).then(ret => {
|
||||
if (ret) {
|
||||
this.$emit('upload-success', {
|
||||
result: ret,
|
||||
file: file
|
||||
})
|
||||
})
|
||||
this.startUpload();//直接上传
|
||||
// 已经上传成功了
|
||||
this.pushUploadFile(file, folder, FileStatus.Success, 100)
|
||||
} else {
|
||||
this.pushUploadFile(file, folder);
|
||||
}
|
||||
}).catch(e => {
|
||||
this.pushUploadFile(file, folder);
|
||||
});
|
||||
},
|
||||
startUpload() {
|
||||
this.fileListData.forEach(f => {
|
||||
@ -152,8 +197,9 @@ export default {
|
||||
})
|
||||
},
|
||||
uploadFile(f) {
|
||||
if (f.status == FileStatus.Ready) {
|
||||
f.status = FileStatus.Uploading;
|
||||
api.upload(f.folder, f.file, (p) => {
|
||||
api.file.upload(f.folder, f.file, (p) => {
|
||||
f.progress = p.progress
|
||||
}).then(ret => {
|
||||
console.log(ret)
|
||||
@ -164,6 +210,7 @@ export default {
|
||||
})
|
||||
}).catch(e => console.log(e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,15 @@ export default {
|
||||
return request(`/api/folder/create`, 'POST', {parent, name});
|
||||
}
|
||||
},
|
||||
file: {
|
||||
/**
|
||||
* 快速上传
|
||||
* @param params
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
fastUpload(params) {
|
||||
return request('/api/fast-upload', 'POST', params);
|
||||
},
|
||||
upload(parent, file, onProcess = null) {
|
||||
// 上传文件到某个目录
|
||||
const postData = new FormData(); // 将数据封装成form表单
|
||||
@ -110,4 +119,5 @@ export default {
|
||||
})
|
||||
// return request('/api/upload', 'FILE', postData, onProcess)
|
||||
}
|
||||
}
|
||||
}
|
8
web/src/service/type.d.ts
vendored
8
web/src/service/type.d.ts
vendored
@ -12,3 +12,11 @@ declare type FileItem = {
|
||||
type: 'folder' | string,
|
||||
updateTime: string
|
||||
}
|
||||
declare type FileIconInstance = {
|
||||
file: FileItem,
|
||||
ext: String,
|
||||
/**
|
||||
* 显示预览
|
||||
*/
|
||||
showPreview: Function
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user