update to v1.0.17 进一步优化了上传证书机制,增加了“返回顶部”快捷按钮。

This commit is contained in:
kohgylw 2019-04-01 14:25:23 +08:00
parent 9204345baa
commit 558ce0c4df
26 changed files with 219 additions and 56 deletions

View File

@ -101,6 +101,7 @@ kiftd项目 计划表-2018-10-9 by 青阳龙野
计划版本 v1.0.17
--------------
【已完成】优化了上传文件的权限检查机制,现在上传文件不会由于时间过长而导致权限失效了。
【已完成】新增了“返回顶部”按钮——当用户浏览得过于深入底部时,这个按钮可以将其快速带回。
【计划中】增加更多视频格式支持,引入自动转码模块。

View File

@ -0,0 +1,41 @@
package kohgylw.kiftd.server.pojo;
import java.util.List;
/**
*
* <h2>上传文件检查结果封装</h2>
* <p>
* 该POJO用于封装一次上传文件前置检查的结果并以JSON格式传回前端其中包括字段
* checkResult代表结果如为hasExistsNames则存在同名文件如为permitUpload则可直接上传
* uploadKey代表上传凭证必须使用该凭证上传
* 和pereFileNameList重名列表仅当checkResult为hasExistsNames时需要检查否则可忽略
* </p>
* @author 青阳龙野(kohgylw)
* @version 1.0
*/
public class CheckUploadFilesRespons {
private String checkResult;//检查结果
private String uploadKey;//上传凭证
private List<String> pereFileNameList;//重复列表
public String getCheckResult() {
return checkResult;
}
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;
}
public void setPereFileNameList(List<String> pereFileNameList) {
this.pereFileNameList = pereFileNameList;
}
}

View File

@ -0,0 +1,60 @@
package kohgylw.kiftd.server.pojo;
/**
*
* <h2>上传凭证证书</h2>
* <p>该类封装了上传时分发的证书用于记录谁具备多少次上传权限用以在其注销后依然能完成已经开始的上传操作</p>
* @author 青阳龙野(kohgylw)
* @version 1.0
*/
public class UploadKeyCertificate {
private int term;//有效次数
private String account;//对应用户
/**
*
* <h2>创建该证书</h2>
* <p>使用该构造器创建一个上传凭证必须指定有效次数及其创建者</p>
* @author 青阳龙野(kohgylw)
* @param term int 有效次数每使用执行上传一次会减一
* @param account java.lang.String 创建者
*/
public UploadKeyCertificate(int term,String account) {
this.term=term;
this.account=account;
}
/**
*
* <h2>使用该证书</h2>
* <p>执行本方法代表使用了该证书一次将有效次数减一</p>
* @author 青阳龙野(kohgylw)
*/
public void checked() {
term--;
}
/**
*
* <h2>检查有效性</h2>
* <p>返回当前证书是否仍有效</p>
* @author 青阳龙野(kohgylw)
* @return boolean true代表仍有效否则无效
*/
public boolean isEffective() {
return term > 0;
}
/**
*
* <h2>得到本证书的创建者</h2>
* <p>返回该证书创建者账户名</p>
* @author 青阳龙野(kohgylw)
* @return java.lang.String 创建者
*/
public String getAccount() {
return account;
}
}

View File

@ -7,6 +7,9 @@ import kohgylw.kiftd.server.mapper.*;
import javax.annotation.*;
import kohgylw.kiftd.server.enumeration.*;
import kohgylw.kiftd.server.model.*;
import kohgylw.kiftd.server.pojo.CheckUploadFilesRespons;
import kohgylw.kiftd.server.pojo.UploadKeyCertificate;
import org.springframework.web.multipart.*;
import javax.servlet.http.*;
@ -32,13 +35,12 @@ import com.google.gson.reflect.TypeToken;
*/
@Service
public class FileServiceImpl extends RangeFileStreamWriter implements FileService {
private static final String KIFTD_UPLOAD_KEY = "KIFTDFILUPLOADKEY";
private static final String ERROR_PARAMETER = "errorParameter";// 参数错误标识
private static final String NO_AUTHORIZED = "noAuthorized";// 权限错误标识
private static final String UPLOADSUCCESS = "uploadsuccess";// 上传成功标识
private static final String UPLOADERROR = "uploaderror";// 上传失败标识
private static Map<String,Integer> keyMap = new HashMap<>();// 上传钥匙表用于记录用完一批就扔的钥匙
private static Map<String, UploadKeyCertificate> keyEffecMap = new HashMap<>();// 上传次数凭证表用于记录用次数有限但时间不限的上传凭证
@Resource
private NodeMapper fm;
@ -79,24 +81,29 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
pereFileNameList.add(fileName);
}
}
// 为客户端分发一把钥匙该钥匙可使用本次声明要上传的次数但不限时长
// 为客户端分发一个凭证该凭证可使用本次声明要上传的次数但不限时长
String key = UUID.randomUUID().toString();
synchronized (keyMap) {
keyMap.put(key, namelistObj.size());
synchronized (keyEffecMap) {
keyEffecMap.put(key, new UploadKeyCertificate(namelistObj.size(), account));
}
Cookie c = new Cookie(KIFTD_UPLOAD_KEY, key);
response.addCookie(c);
// 如果存在同名文件返回同名文件的JSON数据否则直接允许上传
// 装订检查结果
CheckUploadFilesRespons cufr = new CheckUploadFilesRespons();
cufr.setUploadKey(key);// 分配一个凭证
// 如果存在同名文件则写入同名文件的列表否则直接允许上传
if (pereFileNameList.size() > 0) {
return "duplicationFileName:" + gson.toJson(pereFileNameList);
cufr.setCheckResult("hasExistsNames");
cufr.setPereFileNameList(pereFileNameList);
} else {
cufr.setCheckResult("permitUpload");
cufr.setPereFileNameList(new ArrayList<String>());
}
return "permitUpload";
return gson.toJson(cufr);//以JSON格式写回该结果
}
// 执行上传操作接收文件并存入文件节点
public String doUploadFile(final HttpServletRequest request, final HttpServletResponse response,
final MultipartFile file) {
final String account = (String) request.getSession().getAttribute("ACCOUNT");
String account = (String) request.getSession().getAttribute("ACCOUNT");
final String folderId = request.getParameter("folderId");
final String originalFileName = new String(file.getOriginalFilename().getBytes(Charset.forName("UTF-8")),
Charset.forName("UTF-8"));
@ -106,27 +113,22 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
if (folderId == null || folderId.length() <= 0 || originalFileName == null || originalFileName.length() <= 0) {
return UPLOADERROR;
}
// 比对上传钥匙如果有则允许上传否则丢弃该资源该钥匙用完后立即销毁
boolean isUpload = false;
for (Cookie c : request.getCookies()) {
if (KIFTD_UPLOAD_KEY.equals(c.getName())) {
synchronized (keyMap) {
Integer i=keyMap.get(c.getValue());
if (i!=null) {// 比对钥匙有效性
isUpload = true;
i--;
if(i<=0) {
keyMap.remove(c.getValue());// 销毁这把钥匙
c.setMaxAge(0);
response.addCookie(c);
}
} else {
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;
}
}
}
if (!isUpload) {
} else {
return UPLOADERROR;
}
// 检查是否存在同名文件不存在直接存入新节点存在检查repeType代表的上传类型覆盖跳过保留两者
@ -157,7 +159,7 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
f.setFileCreator("\u533f\u540d\u7528\u6237");
}
if (fm.update(f) > 0) {
this.lu.writeUploadFileEvent(request, f);
this.lu.writeUploadFileEvent(f, account);
return UPLOADSUCCESS;
} else {
return UPLOADERROR;
@ -202,7 +204,7 @@ public class FileServiceImpl extends RangeFileStreamWriter implements FileServic
f2.setFilePath(path);
f2.setFileSize(fsize);
if (this.fm.insert(f2) > 0) {
this.lu.writeUploadFileEvent(request, f2);
this.lu.writeUploadFileEvent(f2, account);
return UPLOADSUCCESS;
}
return UPLOADERROR;

View File

@ -25,7 +25,7 @@ public class LogUtil {
public LogUtil() {
sep = File.separator;
logs = ConfigureReader.instance().getPath()+sep+"logs";
logs = ConfigureReader.instance().getPath() + sep + "logs";
File l = new File(logs);
if (!l.exists()) {
l.mkdir();
@ -72,7 +72,7 @@ public class LogUtil {
pl = pl + i.getFolderName() + "/";
}
String content = ">ACCOUNT [" + a + "]\r\n>OPERATE [Create new folder]\r\n>PATH [" + pl + "]\r\n>NAME ["
+ f.getFolderName() + "]CONSTRAINT ["+f.getFolderConstraint()+"]";
+ f.getFolderName() + "]CONSTRAINT [" + f.getFolderConstraint() + "]";
writeToLog("Event", content);
});
t.start();
@ -85,7 +85,7 @@ public class LogUtil {
* 写入重命名文件夹信息
* </p>
*/
public void writeRenameFolderEvent(HttpServletRequest request, Folder f, String newName,String newConstraint) {
public void writeRenameFolderEvent(HttpServletRequest request, Folder f, String newName, String newConstraint) {
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
String account = (String) request.getSession().getAttribute("ACCOUNT");
if (account == null || account.length() == 0) {
@ -99,7 +99,8 @@ public class LogUtil {
pl = pl + i.getFolderName() + "/";
}
String content = ">ACCOUNT [" + a + "]\r\n>OPERATE [Edit folder]\r\n>PATH [" + pl + "]\r\n>NAME ["
+ f.getFolderName() + "]->[" + newName + "]CONSTRAINT ["+f.getFolderConstraint()+"]->["+newConstraint+"]";
+ f.getFolderName() + "]->[" + newName + "]CONSTRAINT [" + f.getFolderConstraint() + "]->["
+ newConstraint + "]";
writeToLog("Event", content);
});
t.start();
@ -166,9 +167,8 @@ public class LogUtil {
* 写入上传文件信息
* </p>
*/
public void writeUploadFileEvent(HttpServletRequest request, Node f) {
public void writeUploadFileEvent(Node f, String account) {
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
String account = (String) request.getSession().getAttribute("ACCOUNT");
if (account == null || account.length() == 0) {
account = "Anonymous";
}
@ -243,15 +243,21 @@ public class LogUtil {
t.start();
}
}
/**
*
* <h2>日志记录移动文件</h2>
* <p>记录移动文件操作在什么时候将哪个文件移动到哪</p>
* <p>
* 记录移动文件操作在什么时候将哪个文件移动到哪
* </p>
*
* @author 青阳龙野(kohgylw)
* @param request HttpServletRequest 请求对象
* @param f Node 被移动的文件节点
* @param locationpath String 被移动到的位置
* @param request
* HttpServletRequest 请求对象
* @param f
* Node 被移动的文件节点
* @param locationpath
* String 被移动到的位置
*/
public void writeMoveFileEvent(HttpServletRequest request, Node f) {
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
@ -274,7 +280,7 @@ public class LogUtil {
t.start();
}
}
public void writeMoveFileEvent(HttpServletRequest request, Folder f) {
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
String account = (String) request.getSession().getAttribute("ACCOUNT");

View File

@ -1,5 +1,5 @@
#Generated by Maven Integration for Eclipse
#Fri Mar 29 19:30:16 CST 2019
#Mon Apr 01 14:24:24 CST 2019
version=1.0.17-SNAPSHOT
groupId=kohgylw
m2e.projectName=kiftd

View File

@ -77,4 +77,22 @@ body {
height: 100%;
margin-top: 15px;
overflow: hidden;
}
.gobacktopbox {
width: 100%;
height: 30px;
position: fixed;
top: 0;
left: 0;
}
.gobacktopbutton {
width: 150px;
height: 35px;
background-color: #FCFCFC;
color: #104E8B;
alpha (Opacity=70);
-moz-opacity: 0.7;
opacity: 0.7;
}

View File

@ -1 +1 @@
@charset "UTF-8";.titlebox{height:30px;line-height:30px;padding-top:5px;padding-bottom:5px;padding-left:10px;padding-right:10px;position:relative;bottom:-7px}.titletext{color:#000;font-size:22px}.graytext{color:#8b8b8b}.subtitle{color:#5cacee;text-align:center}.rightbtn{border-right:10px;float:right;margin-left:10px}.heading{margin-top:-10px;margin-bottom:-10px}.centerText{text-align:center}.uploadstatusbox{color:#9c9c9c;height:48px;overflow:auto}html{height:100%}body{height:100%}.loading{line-height:56px;color:#545454;padding-left:60px;font-size:15px;background:#fff url(loading.gif) no-repeat 10px 50%;opacity:.7;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;filter:alpha(opacity=70)}.wordbreak{word-break:break-all}.filetableheaderstyle{height:40px;float:left;height:100%;margin-top:15px;overflow:hidden}
@charset "UTF-8";.titlebox{height:30px;line-height:30px;padding-top:5px;padding-bottom:5px;padding-left:10px;padding-right:10px;position:relative;bottom:-7px}.titletext{color:#000;font-size:22px}.graytext{color:#8b8b8b}.subtitle{color:#5cacee;text-align:center}.rightbtn{border-right:10px;float:right;margin-left:10px}.heading{margin-top:-10px;margin-bottom:-10px}.centerText{text-align:center}.uploadstatusbox{color:#9c9c9c;height:48px;overflow:auto}html{height:100%}body{height:100%}.loading{line-height:56px;color:#545454;padding-left:60px;font-size:15px;background:#fff url(loading.gif) no-repeat 10px 50%;opacity:.7;-moz-border-radius:20px;-webkit-border-radius:20px;border-radius:20px;filter:alpha(opacity=70)}.wordbreak{word-break:break-all}.filetableheaderstyle{height:40px;float:left;height:100%;margin-top:15px;overflow:hidden}.gobacktopbox{width:100%;height:30px;position:fixed;top:0;left:0}.gobacktopbutton{width:150px;height:35px;background-color:#fcfcfc;color:#104e8b;alpha(Opacity=70);-moz-opacity:.7;opacity:.7}

View File

@ -6,7 +6,8 @@
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>KIFT</title>
<!-- Bootstrap基本框架 -->
<link rel="stylesheet" href="css/bootstrap.min.css">
@ -382,7 +383,8 @@
全部应用
</p>
<p>
<button id="uploadcoverbtn" type="button" class="btn btn-danger btn-sm"
<button id="uploadcoverbtn" type="button"
class="btn btn-danger btn-sm"
onclick="selectFileUpLoadModelEnd('cover')">覆盖</button>
<button type="button" class="btn btn-default btn-sm"
onclick="selectFileUpLoadModelEnd('skip')">跳过</button>
@ -591,7 +593,8 @@
全部应用
</p>
<p>
<button id="movecoverbtn" type="button" class="btn btn-danger btn-sm"
<button id="movecoverbtn" type="button"
class="btn btn-danger btn-sm"
onclick="selectFileMoveModel('cover')">覆盖</button>
<button type="button" class="btn btn-default btn-sm"
onclick="selectFileMoveModel('skip')">跳过</button>
@ -639,6 +642,13 @@
</div>
</div>
<!-- end 文件夹详情模态框 -->
<!-- 返回顶部按钮(隐藏式) -->
<div id="gobacktotopbox" class="gobacktopbox text-center hidden">
<button type="button" onclick="goBackToTop()" class="gobacktopbutton">
返回顶部 <span class="glyphicon glyphicon-eject" aria-hidden="true"></span>
</button>
</div>
<!-- end 返回顶部按钮 -->
</body>
<!-- jquery基本框架 -->
<script type="text/javascript" src="js/jquery-1.12.4.min.js"></script>
@ -652,5 +662,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

@ -21,6 +21,8 @@ var viewerPageIndex; // 分页预览图片——已浏览图片页号
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;// 上传所用的一次性密钥
// 界面功能方法定义
// 页面初始化
@ -286,6 +288,20 @@ $(function() {
$('#downloadModal').on('hidden.bs.modal', function(e) {
$('#downloadURLCollapse').collapse('hide');
});
// 获取窗口高度
if (window.innerHeight){
winHeight = window.innerHeight;
}else if ((document.body) && (document.body.clientHeight)){
winHeight = document.body.clientHeight;
}
// 根据屏幕下拉程度自动显示隐藏返回顶部按钮
$(window).scroll(function(){
if($(this).scrollTop() > 2*winHeight){
$('#gobacktotopbox').removeClass("hidden");
}else{
$('#gobacktotopbox').addClass("hidden");
}
});
});
// 根据屏幕大小增删表格显示内容
@ -1115,14 +1131,18 @@ function checkUploadFile() {
showUploadFileAlert("提示:参数不正确,无法开始上传");
} else if (result == "noAuthorized") {
showUploadFileAlert("提示:您的操作未被授权,无法开始上传");
} else if (result.startsWith("duplicationFileName:")) {
repeList=eval("("+result.substring(20)+")");
repeIndex=0;
selectFileUpLoadModelStart();
} else if (result == "permitUpload") {
doupload(1);
} else {
showUploadFileAlert("提示:出现意外错误,无法开始上传");
var resp=eval("("+result+")");
uploadKey=resp.uploadKey;
if(resp.checkResult == "hasExistsNames"){
repeList=resp.pereFileNameList;
repeIndex=0;
selectFileUpLoadModelStart();
}else if(resp.checkResult == "permitUpload"){
doupload(1);
}else {
showUploadFileAlert("提示:出现意外错误,无法开始上传");
}
}
}
},
@ -1193,6 +1213,7 @@ 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("[已完成]");
@ -2182,4 +2203,8 @@ function doSearchFile(){
alert("错误:搜索关键字有误。请在特殊符号(例如“*”)前加上“\\”进行转义。");
}
endLoading();
}
function goBackToTop(){
$('html,body').animate({scrollTop: 0},'slow');
}