update to v1.0.20 完成了文件夹上传的核心逻辑

This commit is contained in:
kohgylw 2019-08-13 18:00:34 +08:00
parent 33bc31034e
commit 69c717c2d3
32 changed files with 787 additions and 144 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -55,6 +55,8 @@ kiftd项目 计划表-2019-08-05 by 青阳龙野
【已完成】优化了配置检查机制,当配置出现错误时不再自动还原初始配置,方便用户对其进行检查和修改操作。
【已完成】优化了文件列表显示逻辑,将最新上传的文件显示在最上方,便于用户查找。
【已完成】升级文件列表的排序功能——文件列表可以对文件的各项属性进行切换式的双向(升序/降序)排序。
【已完成】进一步完善了文件上传操作的合法性检查流程,提高文件系统的安全性。
【已完成】其他一些细节优化。

BIN
filesystem/.DS_Store vendored

Binary file not shown.

View File

@ -55,11 +55,12 @@ public class HomeController {
public String doLogin(final HttpServletRequest request, final HttpSession session) {
return this.as.checkLoginRequest(request, session);
}
//获取一个新验证码并存入请求者的Session中
// 获取一个新验证码并存入请求者的Session中
@RequestMapping({ "/getNewVerCode.do" })
public void getNewVerCode(final HttpServletRequest request, final HttpServletResponse response,final HttpSession session) {
as.getNewLoginVerCode(request, response,session);
public void getNewVerCode(final HttpServletRequest request, final HttpServletResponse response,
final HttpSession session) {
as.getNewLoginVerCode(request, response, session);
}
@RequestMapping(value = { "/getFolderView.ajax" }, produces = { CHARSET_BY_AJAX })
@ -105,6 +106,20 @@ public class HomeController {
return this.fis.checkUploadFile(request, response);
}
// 上传文件夹的前置检查流程
@RequestMapping(value = { "/checkImportFolder.ajax" }, produces = { CHARSET_BY_AJAX })
@ResponseBody
public String checkImportFolder(final HttpServletRequest request) {
return this.fis.checkImportFolder(request);
}
// 执行文件夹上传操作
@RequestMapping(value = { "doImportFolder.ajax" }, produces = { CHARSET_BY_AJAX })
@ResponseBody
public String doImportFolder(final HttpServletRequest request, final MultipartFile file) {
return fis.doImportFolder(request, file);
}
@RequestMapping({ "/deleteFile.ajax" })
@ResponseBody
public String deleteFile(final HttpServletRequest request) {
@ -211,11 +226,14 @@ public class HomeController {
public String sreachInCompletePath(final HttpServletRequest request) {
return fvs.getSreachViewToJson(request);
}
/**
*
* <h2>应答机制</h2>
* <p>该机制旨在防止某些长耗时操作可能导致Session失效的问题例如上传视频播放等方便用户持续操作</p>
* <p>
* 该机制旨在防止某些长耗时操作可能导致Session失效的问题例如上传视频播放等方便用户持续操作
* </p>
*
* @author 青阳龙野(kohgylw)
* @return String pong
*/

View File

@ -0,0 +1,23 @@
package kohgylw.kiftd.server.pojo;
public class CheckImportFolderRespons {
private String result;
private String maxSize;
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getMaxSize() {
return maxSize;
}
public void setMaxSize(String maxSize) {
this.maxSize = maxSize;
}
}

View File

@ -17,7 +17,6 @@ import java.util.List;
public class CheckUploadFilesRespons {
private String checkResult;//检查结果
private String uploadKey;//上传凭证
private List<String> pereFileNameList;//重复列表
private String overSizeFile;//超限文件
private String maxUploadFileSize;//最大上传体积
@ -28,12 +27,6 @@ public class CheckUploadFilesRespons {
public void setCheckResult(String checkResult) {
this.checkResult = checkResult;
}
public String getUploadKey() {
return uploadKey;
}
public void setUploadKey(String uploadKey) {
this.uploadKey = uploadKey;
}
public List<String> getPereFileNameList() {
return pereFileNameList;
}

View File

@ -6,7 +6,7 @@ import javax.servlet.http.*;
public interface FileService {
String checkUploadFile(final HttpServletRequest request, final HttpServletResponse response);
String doUploadFile(final HttpServletRequest request,final HttpServletResponse response, final MultipartFile file);
String doUploadFile(final HttpServletRequest request, final HttpServletResponse response, final MultipartFile file);
String deleteFile(final HttpServletRequest request);
@ -25,4 +25,43 @@ public interface FileService {
String confirmMoveFiles(final HttpServletRequest request);
String doMoveFiles(final HttpServletRequest request);
/**
*
* <h2>上传文件夹前置检查</h2>
* <p>
* 用于验证上传文件夹的合法性包括权限是否重名文件是否超限等并使用不同的返回结果告知前端应进行的下一步操作
* </p>
*
* @author 青阳龙野(kohgylw)
* @param request
* javax.servlet.http.HttpServletRequest 请求对象
* @return java.lang.String
* 检查结果的json对象其中的maxSize属性标明能够上传的最大文件体积而result属性标明前端应进行的操作对应如下
* <ul>
* <li>noAuthorized 权限不合法无法上传</li>
* <li>errorParameter 参数不合法无法上传</li>
* <li>fileOverSize 文件体积超限无法上传</li>
* <li>coverOrBoth 允许上传但存在同名文件夹可选择覆盖与保留二者</li>
* <li>onlyBoth 允许上传但存在同名文件夹仅能保留二者</li>
* <li>permitUpload 允许直接上传</li>
* </ul>
*/
String checkImportFolder(final HttpServletRequest request);
/**
*
* <h2>执行上传文件夹的操作</h2>
* <p>
* 处理上传文件夹请求并生成对应的文件结构来存储上传的文件再返回信息告知前端是否上传成功
* </p>
*
* @author 青阳龙野(kohgylw)
* @param request
* javax.servlet.http.HttpServletRequest 请求对象
* @param file
* org.springframework.web.multipart.MultipartFile 上传文件的封装对象
* @return java.lang.String 处理结果
*/
String doImportFolder(final HttpServletRequest request, final MultipartFile file);
}

View File

@ -7,8 +7,8 @@ import kohgylw.kiftd.server.mapper.*;
import javax.annotation.*;
import kohgylw.kiftd.server.enumeration.*;
import kohgylw.kiftd.server.model.*;
import kohgylw.kiftd.server.pojo.CheckImportFolderRespons;
import kohgylw.kiftd.server.pojo.CheckUploadFilesRespons;
import kohgylw.kiftd.server.pojo.UploadKeyCertificate;
import org.springframework.web.multipart.*;
@ -39,8 +39,8 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
private static final String NO_AUTHORIZED = "noAuthorized";// 权限错误标识
private static final String UPLOADSUCCESS = "uploadsuccess";// 上传成功标识
private static final String UPLOADERROR = "uploaderror";// 上传失败标识
private static Map<String, UploadKeyCertificate> keyEffecMap = new HashMap<>();// 上传次数凭证表用于记录用次数有限但时间不限的上传凭证
private static Set<String> pathsKeys;//文件夹上传安全锁避免同时对同文件夹重复导入
@Resource
private NodeMapper fm;
@ -56,6 +56,10 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
private FolderUtil fu;
private static final String CONTENT_TYPE = "application/octet-stream";
{
pathsKeys = new HashSet<>();
}
// 检查上传文件列表的实现
public String checkUploadFile(final HttpServletRequest request, final HttpServletResponse response) {
@ -64,8 +68,17 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
final String nameList = request.getParameter("namelist");
final String maxUploadFileSize = request.getParameter("maxSize");
final String maxUploadFileIndex = request.getParameter("maxFileIndex");
// 先行权限检查
if (!ConfigureReader.instance().authorized(account, AccountAuth.UPLOAD_FILES)) {
// 目标文件夹合法性检查
if (folderId == null || folderId.length() == 0) {
return ERROR_PARAMETER;
}
Folder folder = flm.queryById(folderId);
if (folder == null) {
return ERROR_PARAMETER;
}
// 权限检查
if (!ConfigureReader.instance().authorized(account, AccountAuth.UPLOAD_FILES)
|| !ConfigureReader.instance().accessFolder(folder, account)) {
return NO_AUTHORIZED;
}
// 获得上传文件名列表
@ -104,12 +117,6 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
pereFileNameList.add(fileName);
}
}
// 为客户端分发一个凭证该凭证可使用本次声明要上传的次数但不限时长
String key = UUID.randomUUID().toString();
synchronized (keyEffecMap) {
keyEffecMap.put(key, new UploadKeyCertificate(namelistObj.size(), account));
}
cufr.setUploadKey(key);// 分配一个凭证
// 如果存在同名文件则写入同名文件的列表否则直接允许上传
if (pereFileNameList.size() > 0) {
cufr.setCheckResult("hasExistsNames");
@ -154,22 +161,13 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
if (folderId == null || folderId.length() <= 0 || originalFileName == null || originalFileName.length() <= 0) {
return UPLOADERROR;
}
// 检查上传凭证如果有则允许上传否则丢弃该资源该凭证用完后立即销毁
String uploadKey = request.getParameter("uploadKey");
if (uploadKey != null) {
synchronized (keyEffecMap) {
UploadKeyCertificate c = keyEffecMap.get(uploadKey);
if (c != null && c.isEffective()) {// 比对凭证有效性
c.checked();// 使用一次
account = c.getAccount();
if (!c.isEffective()) {
keyEffecMap.remove(uploadKey);// 用完后销毁这个凭证
}
} else {
return UPLOADERROR;
}
}
} else {
Folder folder = flm.queryById(folderId);
if (folder == null) {
return UPLOADERROR;
}
// 检查上传权限
if (!ConfigureReader.instance().authorized(account, AccountAuth.UPLOAD_FILES)
|| !ConfigureReader.instance().accessFolder(folder, account)) {
return UPLOADERROR;
}
// 检查上传文件体积是否超限
@ -211,7 +209,6 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
return UPLOADERROR;
}
} catch (Exception e) {
// TODO 自动生成的 catch
return UPLOADERROR;
}
}
@ -324,8 +321,6 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
//  处理无法下载的资源
response.sendError(404);
} catch (IOException e) {
// TODO 自动生成的 catch
}
}
@ -517,7 +512,6 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
@Override
public String doMoveFiles(HttpServletRequest request) {
// TODO 自动生成的方法存根
final String strIdList = request.getParameter("strIdList");
final String strFidList = request.getParameter("strFidList");
final String strOptMap = request.getParameter("strOptMap");
@ -675,7 +669,6 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
@Override
public String confirmMoveFiles(HttpServletRequest request) {
// TODO 自动生成的方法存根
final String strIdList = request.getParameter("strIdList");
final String strFidList = request.getParameter("strFidList");
final String locationpath = request.getParameter("locationpath");
@ -738,4 +731,230 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
return NO_AUTHORIZED;
}
@Override
public String checkImportFolder(HttpServletRequest request) {
final String account = (String) request.getSession().getAttribute("ACCOUNT");
final String folderId = request.getParameter("folderId");
final String folderName = request.getParameter("folderName");
final String maxUploadFileSize = request.getParameter("maxSize");
CheckImportFolderRespons cifr = new CheckImportFolderRespons();
// 先行权限检查
if (!ConfigureReader.instance().authorized(account, AccountAuth.UPLOAD_FILES)) {
cifr.setResult(NO_AUTHORIZED);
return gson.toJson(cifr);
}
// 开始文件上传体积限制检查
try {
// 获取最大文件体积以Byte为单位
long mufs = Long.parseLong(maxUploadFileSize);
long pMaxUploadSize = ConfigureReader.instance().getUploadFileSize(account);
if (pMaxUploadSize >= 0) {
if (mufs > pMaxUploadSize) {
cifr.setResult("fileOverSize");
cifr.setMaxSize(formatMaxUploadFileSize(ConfigureReader.instance().getUploadFileSize(account)));
return gson.toJson(cifr);
}
}
} catch (Exception e) {
cifr.setResult(ERROR_PARAMETER);
return gson.toJson(cifr);
}
// 开始文件夹命名冲突检查若无重名则允许上传
final List<Folder> folders = flm.queryByParentId(folderId);
try {
Folder testfolder = folders.stream().parallel()
.filter((n) -> n.getFolderName().equals(
new String(folderName.getBytes(Charset.forName("UTF-8")), Charset.forName("UTF-8"))))
.findAny().get();
// 若有重名则判定该用户是否具备删除权限这是能够覆盖的第一步
if (ConfigureReader.instance().authorized(account, AccountAuth.DELETE_FILE_OR_FOLDER)) {
// 接下来判断其是否具备冲突文件夹的访问权限这是能够覆盖的第二步
if (ConfigureReader.instance().accessFolder(testfolder, account)) {
cifr.setResult("coverOrBoth");
return gson.toJson(cifr);
}
}
// 如果上述条件不满足则只能允许保留两者
cifr.setResult("onlyBoth");
return gson.toJson(cifr);
} catch (NoSuchElementException e) {
// 通过所有检查允许上传
cifr.setResult("permitUpload");
return gson.toJson(cifr);
}
}
@Override
public String doImportFolder(HttpServletRequest request, MultipartFile file) {
String account = (String) request.getSession().getAttribute("ACCOUNT");
String folderId = request.getParameter("folderId");
final String originalFileName = new String(file.getOriginalFilename().getBytes(Charset.forName("UTF-8")),
Charset.forName("UTF-8"));
final String folderConstraint = request.getParameter("folderConstraint");
// 再次检查上传文件名与目标目录ID
if (folderId == null || folderId.length() <= 0 || originalFileName == null || originalFileName.length() <= 0) {
return UPLOADERROR;
}
Folder folder = flm.queryById(folderId);
if (folder == null) {
return UPLOADERROR;
}
// 检查上传权限
if (!ConfigureReader.instance().authorized(account, AccountAuth.UPLOAD_FILES)
|| !ConfigureReader.instance().accessFolder(folder, account)) {
return UPLOADERROR;
}
// 检查上传文件体积是否超限
long mufs = ConfigureReader.instance().getUploadFileSize(account);
if (mufs >= 0 && file.getSize() > mufs) {
return UPLOADERROR;
}
// 计算相对路径的文件夹ID即真正要保存的文件夹目标
String[] paths = getParentPath(originalFileName);
//将当前操作的文件夹路径加入到安全锁中确保同一时间内无法对该文件夹进行重复导入避免发生文件冲突的问题
String pathskey = Arrays.toString(paths);
synchronized (pathsKeys) {
if(pathsKeys.contains(pathskey)) {
return UPLOADERROR;
}else {
pathsKeys.add(pathskey);
}
}
for (String pName : paths) {
try {
Folder target = flm.queryByParentId(folderId).parallelStream()
.filter((e) -> e.getFolderName().equals(pName)).findAny().get();
if (ConfigureReader.instance().accessFolder(target, account)) {
folderId = target.getFolderId();// 向下迭代直至将父路径全部迭代完毕并找到最终路径
} else {
synchronized (pathsKeys) {
pathsKeys.remove(pathskey);//解除安全锁便于下一次上传
}
return UPLOADERROR;
}
} catch (NoSuchElementException e) {
Folder newFolder = fu.createNewFolder(flm.queryById(folderId), account, pName, folderConstraint);
if (newFolder != null) {
folderId = newFolder.getFolderId();
} else {
synchronized (pathsKeys) {
pathsKeys.remove(pathskey);//解除安全锁便于下一次上传
}
return UPLOADERROR;
}
}
}
String fileName = getFileNameFormPath(originalFileName);
// 检查是否存在同名文件存在则直接失败确保上传的文件夹内容的原始性
final List<Node> files = this.fm.queryByParentFolderId(folderId);
if (files.parallelStream().anyMatch((e) -> e.getFileName().equals(fileName))) {
synchronized (pathsKeys) {
pathsKeys.remove(pathskey);//解除安全锁便于下一次上传
}
return UPLOADERROR;
}
// 将文件存入节点并获取其存入生成路径型如UUID.block形式
final String path = this.fbu.saveToFileBlocks(file);
if (path.equals("ERROR")) {
synchronized (pathsKeys) {
pathsKeys.remove(pathskey);//解除安全锁便于下一次上传
}
return UPLOADERROR;
}
final String fsize = this.fbu.getFileSize(file);
final Node f2 = new Node();
f2.setFileId(UUID.randomUUID().toString());
if (account != null) {
f2.setFileCreator(account);
} else {
f2.setFileCreator("\u533f\u540d\u7528\u6237");
}
f2.setFileCreationDate(ServerTimeUtil.accurateToDay());
f2.setFileName(fileName);
f2.setFileParentFolder(folderId);
f2.setFilePath(path);
f2.setFileSize(fsize);
int i = 0;
// 尽可能避免UUID重复的情况发生重试10次
while (true) {
try {
if (this.fm.insert(f2) > 0) {
this.lu.writeUploadFileEvent(f2, account);
synchronized (pathsKeys) {
pathsKeys.remove(pathskey);//解除安全锁便于下一次上传
}
return UPLOADSUCCESS;
}
break;
} catch (Exception e) {
f2.setFileId(UUID.randomUUID().toString());
i++;
}
if (i >= 10) {
break;
}
}
synchronized (pathsKeys) {
pathsKeys.remove(pathskey);
}
return UPLOADERROR;
}
/**
*
* <h2>解析相对路径字符串</h2>
* <p>
* 根据相对路径获得文件夹的层级名称并以数组的形式返回若无层级则返回空数组若层级名称为空字符串则忽略
* </p>
* <p>
* 示例1输入"aaa/bbb/ccc.c"返回["aaa","bbb"]
* </p>
* <p>
* 示例2输入"bbb.c"返回[]
* </p>
* <p>
* 示例3输入"aaa//bbb/ccc.c"返回["aaa","bbb"]
* </p>
*
* @author 青阳龙野(kohgylw)
* @param path
* java.lang.String 原路径字符串
* @return java.lang.String[] 解析出的目录层级
*/
private String[] getParentPath(String path) {
if (path != null) {
String[] paths = path.split("/");
List<String> result = new ArrayList<String>();
for (int i = 0; i < paths.length - 1; i++) {
if (paths[i].length() > 0) {
result.add(paths[i]);
}
}
return result.toArray(new String[0]);
}
return new String[0];
}
/**
*
* <h2>解析相对路径中的文件名</h2>
* <p>
* 从相对路径中获得文件名若解析失败则返回null
* </p>
*
* @author 青阳龙野(kohgylw)
* @param java.lang.String
* 需要解析的相对路径
* @return java.lang.String 文件名
*/
private String getFileNameFormPath(String path) {
if (path != null) {
String[] paths = path.split("/");
if (paths.length > 0) {
return paths[paths.length - 1];
}
}
return null;
}
}

View File

@ -42,52 +42,10 @@ public class FolderServiceImpl implements FolderService {
if (fm.queryByParentId(parentId).parallelStream().anyMatch((e) -> e.getFolderName().equals(folderName))) {
return "nameOccupied";
}
Folder f = new Folder();
// 设置子文件夹约束等级不允许子文件夹的约束等级比父文件夹低
int pc = parentFolder.getFolderConstraint();
if (folderConstraint != null) {
try {
int ifc = Integer.parseInt(folderConstraint);
if (ifc > 0 && account == null) {
return "errorParameter";
}
if (ifc < pc) {
return "errorParameter";
} else {
f.setFolderConstraint(ifc);
}
} catch (Exception e) {
// TODO: handle exception
return "errorParameter";
}
} else {
return "errorParameter";
}
f.setFolderId(UUID.randomUUID().toString());
f.setFolderName(folderName);
f.setFolderCreationDate(ServerTimeUtil.accurateToDay());
if (account != null) {
f.setFolderCreator(account);
} else {
f.setFolderCreator("匿名用户");
}
f.setFolderParent(parentId);
int i = 0;
while (true) {
try {
final int r = this.fm.insertNewFolder(f);
if (r > 0) {
this.lu.writeCreateFolderEvent(request, f);
return "createFolderSuccess";
}
break;
} catch (Exception e) {
f.setFolderId(UUID.randomUUID().toString());
i++;
}
if (i >= 10) {
break;
}
Folder f=fu.createNewFolder(parentFolder, account, folderName, folderConstraint);
if(f!=null) {
lu.writeCreateFolderEvent(request, f);
return "createFolderSuccess";
}
return "cannotCreateFolder";
}

View File

@ -50,4 +50,54 @@ public class FolderUtil {
}
this.fm.deleteById(folderId);
}
public Folder createNewFolder(Folder parentFolder,String account,String folderName,String folderConstraint) {
Folder f = new Folder();
// 设置子文件夹约束等级不允许子文件夹的约束等级比父文件夹低
int pc = parentFolder.getFolderConstraint();
if (folderConstraint != null) {
try {
int ifc = Integer.parseInt(folderConstraint);
if (ifc > 0 && account == null) {
return null;
}
if (ifc < pc) {
return null;
} else {
f.setFolderConstraint(ifc);
}
} catch (Exception e) {
// TODO: handle exception
return null;
}
} else {
return null;
}
f.setFolderId(UUID.randomUUID().toString());
f.setFolderName(folderName);
f.setFolderCreationDate(ServerTimeUtil.accurateToDay());
if (account != null) {
f.setFolderCreator(account);
} else {
f.setFolderCreator("匿名用户");
}
f.setFolderParent(parentFolder.getFolderId());
int i = 0;
while (true) {
try {
final int r = this.fm.insertNewFolder(f);
if (r > 0) {
return f;
}
break;
} catch (Exception e) {
f.setFolderId(UUID.randomUUID().toString());
i++;
}
if (i >= 10) {
break;
}
}
return null;
}
}

View File

@ -1,5 +1,5 @@
#Generated by Maven Integration for Eclipse
#Sun Aug 11 17:11:03 CST 2019
#Tue Aug 13 17:54:53 CST 2019
version=1.0.20-SNAPSHOT
groupId=kohgylw
m2e.projectName=kiftd

View File

@ -99,13 +99,16 @@
class="glyphicon glyphicon-cog"></span> 操作 <span
class="caret"></span></a>
<ul class="dropdown-menu" id="fileListDropDown">
<li id="createFolderButtonLi"><a>新建文件夹 <span
class="pull-right"><span
class="glyphicon glyphicon-arrow-up" aria-hidden="true"></span>+N</span></a></li>
<li role="separator" class="divider"></li>
<li id="uploadFileButtonLi"><a>上传文件 <span
class="pull-right"><span
class="glyphicon glyphicon-arrow-up" aria-hidden="true"></span>+U</span></a></li>
<li id="uploadFolderButtonLi"><a>上传文件夹 <span
class="pull-right"><span
class="glyphicon glyphicon-arrow-up" aria-hidden="true"></span>+F</span></a></li>
<li role="separator" class="divider"></li>
<li id="createFolderButtonLi"><a>新建文件夹 <span
class="pull-right"><span
class="glyphicon glyphicon-arrow-up" aria-hidden="true"></span>+N</span></a></li>
<li role="separator" class="divider"></li>
<li id="cutFileButtonLi"><a><span id='cutSignTx'>剪切
<span class="pull-right"><span
@ -322,7 +325,7 @@
</div>
</div>
</div>
<div id="newfolderalert" role="alert"></div>
<div id="editfolderalert" role="alert"></div>
</form>
</div>
<div class="modal-footer">
@ -372,7 +375,7 @@
<div id="uploadstatus" class="uploadstatusbox"></div>
</div>
</div>
<div id="uploadFileAlert" role="alert"></div>
<div id="uploadFileAlert" role="alert" class="alert alert-danger"></div>
<div id="selectFileUpLoadModelAlert" class="alert alert-danger"
role="alert">
<h4>提示:存在同名文件!</h4>
@ -404,6 +407,80 @@
</div>
</div>
<!-- end 上传文件 -->
<!-- 上传文件夹框 -->
<div class="modal fade" id="importFolderModal" tabindex="-1"
role="dialog" aria-labelledby="importFolderMolderTitle">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="importFolderMolderTitle">
<span class="glyphicon glyphicon-cloud-upload"></span> 上传文件夹
</h4>
</div>
<div class="modal-body">
<h5>选择文件夹:</h5>
<div class="input-group">
<input type="text" id="folderpath" class="form-control"
onclick="checkimportpath()" onfocus="this.blur()"
placeholder="请点击选择要上传的文件夹……" folderConstraintLevel="0">
<div class="input-group-btn">
<button id="importFolderLevelBtn" type="button" class="btn btn-default dropdown-toggle"
data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
&nbsp;<span id="importfoldertype">公开的</span>&nbsp;<span
class="caret"></span>
</button>
<ul id="importfoldertypelist"
class="dropdown-menu dropdown-menu-right">
</ul>
</div>
</div>
<input type="file" id="importfolder" style="display: none;"
onchange="getInputImport()" multiple="multiple" webkitdirectory>
<h5>
上传进度:<span id="importcount"></span>
</h5>
<div class="progress">
<div id="importpros" class="progress-bar" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
style="width: 0%;">
<span class="sr-only"></span>
</div>
</div>
<h5>上传状态:</h5>
<div class="panel panel-default">
<div class="panel-body">
<div id="importstatus" class="uploadstatusbox"></div>
</div>
</div>
<div id="importFolderAlert" class="alert alert-danger" role="alert"></div>
<div id="selectFolderImportModelAlert" class="alert alert-danger"
role="alert">
<h4>提示:存在同名文件夹!</h4>
<p>
您要上传的文件夹“<span id="repeFolderName"></span>”已存在于该路径下,您希望:
</p>
<p>
<button id="importcoverbtn" type="button"
class="btn btn-danger btn-sm" onclick="doImportFolder('cover')">覆盖</button>
<button type="button" class="btn btn-default btn-sm" onclick="abortImport()">取消</button>
<button type="button" class="btn btn-default btn-sm" onclick="doImportFolder('both')">保留两者</button>
</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" onclick='abortImport()'>取消</button>
<button id="importbutton" type='button' class='btn btn-primary'
onclick='checkImportFolder()'>开始上传</button>
</div>
</div>
</div>
</div>
<!-- end 上传文件夹 -->
<!-- 下载提示框 -->
<div class="modal fade" id="downloadModal" tabindex="-1" role="dialog"
aria-labelledby="downloadModelTitle">
@ -663,5 +740,5 @@
<!-- 音乐播放器 -->
<script type="text/javascript" src="js/APlayer.min.js"></script>
<!-- 页面操作定义 -->
<script type="text/javascript" src="js/home.min.js"></script>
<script type="text/javascript" src="js/home.js"></script>
</html>

View File

@ -10,11 +10,13 @@ var zipTimer;// 打包下载计时器
var folderView;// 返回的文件系统视图对象
var originFolderView;// 保存原始的文件视图对象
var fs;// 选中的要上传的文件列表
var ifs;// 选中的要上传的文件夹内的文件列表
var checkedMovefiles;// 移动文件的存储列表
var constraintLevel;// 当前文件夹限制等级
var account;// 用户账户
var isUpLoading=false;// 是否正在执行其他上传操作
var xhr;// 文件上传请求对象
var isUpLoading=false;// 是否正在执行上传操作
var isImporting=false;// 是否正在执行上传文件夹操作
var xhr;// 文件或文件夹上传请求对象
var viewerPageSize = 15; // 显示图片页的最大长度注意最好是奇数
var viewer; // viewer对象用于预览图片功能
var viewerPageIndex; // 分页预览图片已浏览图片页号
@ -22,7 +24,6 @@ var viewerTotal; // 分页预览图片——总页码数
var pvl;// 预览图片列表的JSON格式对象
var checkFilesTip="提示:您还未选择任何文件,请先选中一些文件后再执行本操作:<br /><br /><kbd>单击</kbd>:选中某一文件<br /><br /><kbd><kbd>Shift</kbd>+<kbd>单击</kbd></kbd>:选中多个文件<br /><br /><kbd><kbd>Shift</kbd>+<kbd>双击</kbd></kbd>:选中连续的文件<br /><br /><kbd><kbd>Shitf</kbd>+<kbd>A</kbd></kbd>:选中/取消选中所有文件";// 选取文件提示
var winHeight;// 窗口高度
var uploadKey;// 上传所用的一次性密钥
var pingInt;// 定时应答器的定时装置
// 界面功能方法定义
@ -128,8 +129,8 @@ $(function() {
$("#foldername").focus();
});
// 关闭上传模态框时自动提示如何查看上传进度
$('#uploadFileModal').on('hidden.bs.modal', function(e) {
if(isUpLoading){
$('#uploadFileModal,#importFolderModal').on('hidden.bs.modal', function(e) {
if(isUpLoading || isImporting){
$('#operationMenuBox').attr("data-placement", "top");
$('#operationMenuBox').attr("data-trigger", "focus");
$('#operationMenuBox').attr("data-title", "上传中");
@ -148,10 +149,10 @@ $(function() {
});
// 开启编辑文件夹框自动初始化状态
$('#renameFolderModal').on('show.bs.modal', function(e) {
$("#newfolderalert").removeClass("alert");
$("#newfolderalert").removeClass("alert-danger");
$("#editfolderalert").removeClass("alert");
$("#editfolderalert").removeClass("alert-danger");
$("#folderrenamebox").removeClass("has-error");
$("#newfolderalert").text("");
$("#editfolderalert").text("");
$("#editfoldertypelist").html("");
if(account!=null){
for(var i=constraintLevel;i<folderTypes.length;i++){
@ -181,7 +182,7 @@ $(function() {
}
if (folderView.authList != null) {
if (checkAuth(folderView.authList, "U")) {// 如果有上传权限且未进行其他上传
if(isUpLoading){
if(isUpLoading || isImporting){
alert("提示:您正在执行另一项上传任务,请在上传窗口关闭后再试。");
}else{
if (!(window.ActiveXObject||"ActiveXObject" in window)){// 判断是否为IE
@ -241,7 +242,7 @@ $(function() {
alert("提示:您不具备上传权限,无法上传文件。");
}
}
// Shift+A全选文件/反选文件Shift+N新建文件夹Shift+U上传文件Shift+C&V剪切粘贴Shift+D批量删除
// Shift+A全选文件/反选文件Shift+N新建文件夹Shift+U上传文件Shift+F导入文件夹Shift+C&V剪切粘贴Shift+D批量删除
$(document).keypress(function (e) {
if($('.modal.shown').length == 0 || ($('.modal.shown').length == 1 && $('.modal.shown').attr('id') == 'loadingModal')){
var keyCode = e.keyCode ? e.keyCode : e.which ? e.which : e.charCode;
@ -259,6 +260,9 @@ $(function() {
case 68:
$('#deleteSeelectFileButtonLi a').click();
break;
case 70:
$('#uploadFolderButtonLi a').click();
break;
case 67:
if((!$("#cutSignTx").hasClass("cuted"))&&checkedMovefiles==undefined){
$('#cutFileButtonLi a').click();
@ -672,6 +676,10 @@ function showAccountView(folderView) {
if (checkAuth(authList, "U")) {
$("#uploadFileButtonLi").removeClass("disabled");
$("#uploadFileButtonLi a").attr("onclick","showUploadFileModel()");
if(isSupportWebkitdirectory()){// 若浏览器支持文件夹选择则允许进行文件夹导入
$("#uploadFolderButtonLi").removeClass("disabled");
$("#uploadFolderButtonLi a").attr("onclick","showUploadFolderModel()");
}
}
if (checkAuth(authList, "L")) {
$("#packageDownloadBox")
@ -1147,16 +1155,15 @@ function renameFolder(folderId) {
// 显示重命名文件夹状态提示
function showRFolderAlert(txt) {
$("#newfolderalert").addClass("alert");
$("#newfolderalert").addClass("alert-danger");
$("#editfolderalert").addClass("alert");
$("#editfolderalert").addClass("alert-danger");
$("#folderrenamebox").addClass("has-error");
$("#newfolderalert").text(txt);
$("#editfolderalert").text(txt);
}
// 显示上传文件模态框
function showUploadFileModel() {
$("#uploadFileAlert").removeClass("alert");
$("#uploadFileAlert").removeClass("alert-danger");
$("#uploadFileAlert").hide();
$("#uploadFileAlert").text("");
if(isUpLoading==false){
$("#filepath").removeAttr("disabled");
@ -1166,7 +1173,7 @@ function showUploadFileModel() {
$("#pros").attr('aria-valuenow','0');
$("#umbutton").attr('disabled', false);
$("#filecount").text("");
$("#uploadstatus").text("");
$("#uploadstatus").html("");
$("#selectcount").text("");
$("#selectFileUpLoadModelAsAll").removeAttr("checked");
$("#selectFileUpLoadModelAlert").hide();
@ -1202,16 +1209,15 @@ function showfilepath() {
$("#filepath").val(filename);
}
// 检查是否能够上传
// 检查文件是否能够上传
function checkUploadFile() {
if(isUpLoading==false){
if(isUpLoading==false && isImporting == false){
if(fs!=null&&fs.length>0){
$("#filepath").attr("disabled","disabled");
$("#umbutton").attr('disabled', true);
isUpLoading=true;
repeModelList=null;
$("#uploadFileAlert").removeClass("alert");
$("#uploadFileAlert").removeClass("alert-danger");
$("#uploadFileAlert").hide();
$("#uploadFileAlert").text("");
var filenames = new Array();
var maxSize = 0;
@ -1245,7 +1251,6 @@ function checkUploadFile() {
showUploadFileAlert("提示:您的操作未被授权,无法开始上传");
} else {
var resp=eval("("+result+")");
uploadKey=resp.uploadKey;
if(resp.checkResult == "fileTooLarge"){
showUploadFileAlert("提示:文件["+resp.overSizeFile+"]的体积超过最大限制("+resp.maxUploadFileSize+"),无法开始上传");
}else if(resp.checkResult == "hasExistsNames"){
@ -1267,6 +1272,8 @@ function checkUploadFile() {
}else{
showUploadFileAlert("提示:您未选择任何文件,无法开始上传");
}
}else{
showUploadFileAlert("提示:另一项上传文件或文件夹的任务尚未完成,无法开始上传");
}
}
@ -1286,6 +1293,7 @@ function selectFileUpLoadModelStart(){
$("#repeFileName").text(repeList[repeIndex]);
}
// 设定重名文件的处理方法
function selectFileUpLoadModelEnd(t){
if(repeModelList == null){
repeModelList={};
@ -1327,7 +1335,6 @@ function doupload(count) {
fd.append("file", uploadfile);// 将文件对象添加到FormData对象中字段名为uploadfile
fd.append("folderId", locationpath);
fd.append("uploadKey", uploadKey);
if(repeModelList != null && repeModelList[fname] != null){
if(repeModelList[fname] == 'skip'){
$("#uls_" + count).text("[已完成]");
@ -1414,6 +1421,7 @@ function doupload(count) {
}
}
// 显示上传文件进度
function uploadProgress(evt) {
if (evt.lengthComputable) {
// evt.loaded文件上传的大小 evt.total文件总的大小
@ -1428,12 +1436,21 @@ function uploadProgress(evt) {
function showUploadFileAlert(txt) {
isUpLoading=false;
$("#filepath").removeAttr("disabled");
$("#uploadFileAlert").addClass("alert");
$("#uploadFileAlert").addClass("alert-danger");
$("#uploadFileAlert").show();
$("#uploadFileAlert").text(txt);
$("#umbutton").attr('disabled', false);
}
// 取消上传文件
function abortUpload() {
isUpLoading=false;
if (xhr != null) {
xhr.abort();
}
$('#uploadFileModal').modal('hide');
showFolderView(locationpath);
}
// 显示下载文件模态框
function showDownloadModel(fileId, fileName) {
$("#downloadFileName").text("提示:您确认要下载文件:[" + fileName + "]么?");
@ -1578,24 +1595,6 @@ function showRFileAlert(txt) {
$("#newFileNamealert").text(txt);
}
// 取消上传
function abortUpload() {
isUpLoading=false;
if (xhr != null) {
xhr.abort();
$("#umbutton").attr('disabled', false);
$("#pros").width("0%");
$("#pros").attr('aria-valuenow',"0");
$("#filecount").text("");
}
$("#uploadfile").val("");
$("#filepath").val("");
$("#uploadstatus").html("");
$("#selectcount").text("");
$('#uploadFileModal').modal('hide');
showFolderView(locationpath);
}
// 获取文件名的后缀名以小写形式输出
function getSuffix(filename) {
var index1 = filename.lastIndexOf(".");
@ -2120,7 +2119,7 @@ function sortbyfs(){
$("#sortByFN").removeClass();
$("#sortByCD").removeClass();
$("#sortByCN").removeClass();
//正倒序判断
// 正倒序判断
if($("#sortByFS").hasClass("glyphicon-triangle-bottom")){
$("#sortByFS").removeClass();
$("#sortByFS").addClass("glyphicon glyphicon-triangle-top");
@ -2493,4 +2492,269 @@ function ping(){
}
}
});
}
}
// 判断浏览器是否支持webkitdirectory属性是否能进行文件夹上传
function isSupportWebkitdirectory() {
var testWebkitdirectory = document.createElement("input");
if("webkitdirectory" in testWebkitdirectory) {
return true;
} else {
return false;
}
};
// 显示上传文件夹模态框
function showUploadFolderModel(){
$("#importFolderAlert").hide();
$("#importFolderAlert").text("");
if(isImporting == false){// 如果未进行上传则还原上传文件夹的基本状态
$("#folderpath").val("");
$("#importfolder").val("");
$("#importpros").width("0%");
$("#importpros").attr('aria-valuenow','0');
$("#importstatus").html("");
$("#folderpath").attr("disabled",false);
$("#importFolderLevelBtn").attr("disabled",false);
$("#importcount").text("");
$("#importbutton").attr('disabled', false);
$("#selectFolderImportModelAlert").hide();
$("#importfoldertypelist").html("");
if(account!=null){
$("#folderpath").attr("folderConstraintLevel",constraintLevel+"");
$("#importfoldertype").text(folderTypes[constraintLevel]);
if(checkAuth(folderView.authList, "C")){
for(var i=constraintLevel;i<folderTypes.length;i++){
$("#importfoldertypelist").append("<li><a onclick='changeImportFolderType("+i+")'>"+folderTypes[i]+"</a></li>");
}
}else{
$("#importfoldertypelist").append("<li><a onclick='changeImportFolderType("+constraintLevel+")'>"+folderTypes[constraintLevel]+"</a></li>");
}
}else{
$("#importfoldertypelist").append("<li><a onclick='changeImportFolderType(0)'>"+folderTypes[0]+"</a></li>");
}
}
$("#importFolderModal").modal('show');
}
// 点击上传路径文本框时弹出文件夹选择窗口
function checkimportpath(){
$('#importfolder').click();
}
// 用户选择文件夹后回填路径
function getInputImport(){
ifs = $("#importfolder")[0].files;
if(ifs.length > 0) {
var folderName = ifs[0].webkitRelativePath.substring(0, ifs[0].webkitRelativePath.indexOf("/"));
$("#folderpath").val(folderName);
}
}
// 检查文件夹是否能够上传
function checkImportFolder(){
if(isUpLoading == false && isImporting ==false){
if(ifs != null && ifs.length > 0){// 必须选中文件
$("#folderpath").attr("disabled",true);
$("#importFolderLevelBtn").attr("disabled",true);
$("#importbutton").attr('disabled', true);
$("#importFolderAlert").hide();
$("#importFolderAlert").text("");
isImporting = true;
var folderName = ifs[0].webkitRelativePath.substring(0, ifs[0].webkitRelativePath.indexOf("/"));
var maxSize = 0;
var maxFileIndex = 0;
// 找出最大体积的文件避免服务器进行效验
for (var i = 0; i < ifs.length; i++) {
if(ifs[i].size > maxSize){
maxSize = ifs[i].size;
maxFileIndex = i;
}
}
// 发送合法性检查请求
$.ajax({
url:'homeController/checkImportFolder.ajax',
type:'POST',
dataType:'text',
data:{
folderName : folderName,
maxSize : maxSize,
folderId : locationpath
},
success:function(result){
var resJson = eval("("+result+")");
switch (resJson.result) {
case 'noAuthorized':
showImportFolderAlert("提示:您的操作未被授权,无法开始上传");
break;
case 'errorParameter':
showImportFolderAlert("提示:参数不正确,无法开始上传");
break;
case 'fileOverSize':
showImportFolderAlert("提示:文件["+ifs[maxFileIndex].webkitRelativePath+"]的体积超过最大限制("+resJson.maxSize+"),无法开始上传");
break;
case 'coverOrBoth':
$("#importcoverbtn").show();
$("#selectFolderImportModelAlert").show();
$("#repeFolderName").text(folderName);
break;
case 'onlyBoth':
$("#importcoverbtn").hide();
$("#selectFolderImportModelAlert").show();
$("#repeFolderName").text(folderName);
break;
case 'permitUpload':
doImportFolder('none');// 直接允许上传
break;
default:
showImportFolderAlert("提示:出现意外错误,无法开始上传");
break;
}
},
error:function(){
showImportFolderAlert("提示:出现意外错误,无法开始上传");
}
});
}else{
showImportFolderAlert("提示:您未选择任何文件夹,无法开始上传");
}
}else{
showImportFolderAlert("提示:另一项上传文件或文件夹的任务尚未完成,无法开始上传");
}
}
// 显示上传文件夹错误提示
function showImportFolderAlert(txt) {
isImporting=false;
$("#folderpath").attr("disabled",false);
$("#importFolderLevelBtn").attr("disabled",false);
$("#importFolderAlert").show();
$("#importFolderAlert").text(txt);
$("#importbutton").attr('disabled', false);
}
// 执行上传文件夹操作包括前置操作和迭代操作
function doImportFolder(type) {
// 前置操作用于处理存在同名文件夹的情况
if(type == 'both'){
// 保留两者时则预先创建一个新的空文件夹作为上传目标
}else if(type == 'cover'){
// 覆盖时则删除原同名文件夹后再进行上传
}
// 执行迭代上传操作
iteratorImport(0);
}
// 显示上传文件夹进度
function importProgress(evt) {
if (evt.lengthComputable) {
// evt.loaded文件上传的大小 evt.total文件总的大小
var percentComplete = Math.round((evt.loaded) * 100 / evt.total);
// 加载进度条同时显示信息
$("#importpros").width(percentComplete + "%");
$("#importpros").attr('aria-valuenow',""+percentComplete);
}
}
// 迭代上传文件夹内的文件
function iteratorImport(i){
$("#importpros").width("0%");// 先将进度条置0
$("#importpros").attr('aria-valuenow',"0");
var uploadfile = ifs[i];// 获取要上传的文件
var fcount = ifs.length;
var fc=$("#folderpath").attr("folderConstraintLevel");// 文件夹访问级别
if (uploadfile != null) {
var fname = uploadfile.webkitRelativePath;
if (fcount > 1) {
$("#importcount").text("" + (i+1) + "/" + fcount + "");// 显示当前进度
}
$("#importstatus").prepend(
"<p>" + fname + "<span id='ils_" + i
+ "'>[正在上传...]</span></p>");
xhr = new XMLHttpRequest();// 这东西类似于servlet里面的request
var fd = new FormData();// 用于封装文件数据的对象
fd.append("file", uploadfile);// 将文件对象添加到FormData对象中字段名为uploadfile
fd.append("folderId", locationpath);
fd.append("folderConstraint",fc);
xhr.open("POST", "homeController/doImportFolder.ajax", true);// 上传目标
xhr.upload.addEventListener("progress", importProgress, false);// 这个是对上传进度的监听
// 上面的三个参数分别是事件名指定名称回调函数是否冒泡一般是false即可
xhr.send(fd);// 上传FormData对象
if(pingInt == null){
pingInt = setInterval("ping()",60000);// 上传中开始计时应答
}
// 上传结束后执行的回调函数
xhr.onloadend = function() {
// 停止应答计时
if(pingInt != null){
window.clearInterval(pingInt);
pingInt = null;
}
if (xhr.status === 200) {
// TODO 上传成功
var result = xhr.responseText;
if (result == "uploadsuccess") {
$("#ils_" + i).text("[已完成]");
var ni=i+1;
if(ni < fcount){
iteratorImport(ni);
}else{
// 完成全部上传后清空所有提示信息并还原上传窗口
isImporting=false;
$("#folderpath").removeAttr("disabled");
$("#importFolderLevelBtn").removeAttr("disabled");
$("#importfolder").val("");
$("#folderpath").val("");
$("#importpros").width("0%");
$("#importpros").attr('aria-valuenow',"0");
$("#importbutton").attr('disabled', false);
$("#importcount").text("");
$("#importstatus").text("");
$('#importFolderModal').modal('hide');
showFolderView(locationpath);
}
} else if (result == "uploaderror") {
showImportFolderAlert("提示:出现意外错误,文件:[" + fname
+ "]上传失败,上传被中断。");
$("#ils_" + i).text("[失败]");
} else {
showImportFolderAlert("提示:出现意外错误,文件:[" + fname
+ "]上传失败,上传被中断。");
$("#ils_" + i).text("[失败]");
}
} else {
showImportFolderAlert("提示:出现意外错误,文件:[" + fname + "]上传失败,上传被中断。");
$("#ils_" + i).text("[失败]");
}
};
} else {
showImportFolderAlert("提示:要上传的文件不存在。");
$("#importstatus").prepend(
"<p>未找到要上传的文件<span id='ils_" + i + "'>[失败]</span></p>");
}
}
// 取消文件夹上传
function abortImport(){
isImporting=false;
if (xhr != null) {
xhr.abort();
}
$('#importFolderModal').modal('hide');
showFolderView(locationpath);
}
// 修改上传文件夹约束等级
function changeImportFolderType(type){
$("#importfoldertype").text(folderTypes[type]);
$("#folderpath").attr("folderConstraintLevel",type+"");
}