update to v1.0.17 重构了下载链接判定逻辑,使其能够直接下载

This commit is contained in:
kohgylw 2019-04-03 11:58:06 +08:00
parent a480c79d94
commit be003781de
17 changed files with 275 additions and 13 deletions

View File

@ -62,7 +62,7 @@ kiftd项目 计划表-2018-10-9 by 青阳龙野
【已完成】文件管理——新增了一个较为强大的本地文件管理器或者说“文件夹视图”现在终于能够直接在本地操作kiftd文件系统中的文件了
【已完成】增加存储路径显示——显示在一个文本框内。
【已完成】优化了下载过程——将原本的NIO读取换回简单的RandomAccessFile读取。
【已完成】将音乐播放器的播放不播放同步
【已完成】修正了音乐播放器的播放不播放同步问题
【已完成】修正了断点续传中下载日志会被重复记录的问题。
【已完成】修正了在Safari浏览器中下载中文文件名时出现乱码的问题。
【已完成】MySQL数据库——针对一些对数据库要求较高的用户现在提供将外部MySQL作为文件节点存储数据库的功能。
@ -100,9 +100,12 @@ kiftd项目 计划表-2018-10-9 by 青阳龙野
计划版本 v1.0.17
--------------
【已完成】所有常见的视频文件格式均已支持在线播放——这是针对在线播放模块呼声最高的建议。
通过在在线播放模块中集成JAVE多媒体解码器现在用户可以直接在线播放mp4/mov/wmv/flv/mkv/webm/avi格式的全部视频了。
【已完成】名副其实的分享下载链接——现在,指定文件的下载链接将动态生成,持有该链接的用户将可直接下载指定资源(无需再进行权限认证),支持跨域请求。
【已完成】优化了上传文件的权限检查机制,现在上传文件不会由于时间过长而导致权限失效了。
【已完成】文件列表中文件名过长时能够自动换行,以避免文字溢出。
【已完成】新增了“返回顶部”按钮——当用户浏览得过于深入底部时,这个按钮可以将其快速带回。
【计划中】增加更多视频格式支持,引入自动转码模块。

View File

@ -0,0 +1,39 @@
package kohgylw.kiftd.server.controller;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import kohgylw.kiftd.server.service.ExternalDownloadService;
/**
*
* <h2>外部链接控制器</h2>
* <p>该控制器主要处理来自外部链接的请求例如用于分享的下载链接该控制器内的所有请求均允许跨域</p>
* @author 青阳龙野(kohgylw)
* @version 1.0
*/
@Controller
@CrossOrigin
@RequestMapping({"/externalLinksController"})
public class ExternalLinksController {
@Resource
private ExternalDownloadService eds;//分享下载链接的相关处理
@RequestMapping("/getDownloadKey.ajax")
public @ResponseBody String getDownloadKey(HttpServletRequest request) {
return eds.getDownloadKey(request);
}
@RequestMapping("/downloadFileByKey.do")
public void downloadFileByKey(HttpServletRequest request,HttpServletResponse response) {
eds.downloadFileByKey(request, response);
}
}

View File

@ -0,0 +1,35 @@
package kohgylw.kiftd.server.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* <h2>外部下载链接处理服务</h2>
* <p>该服务主要包含了生成获取外部下载链接以及使用它们进行下载的相关操作详见各个方法</p>
* @author 青阳龙野(kohgylw)
* @version 1.0
*/
public interface ExternalDownloadService {
/**
*
* <h2>获取一个下载凭证</h2>
* <p>针对指定资源获取一个下载凭证要求该凭证的签收者必须具备下载权限该凭证在服务器关闭前将一直有效</p>
* @author 青阳龙野(kohgylw)
* @param request javax.servlet.http.HttpServletRequest 请求对象
* @return java.lang.String 下载凭证
*/
String getDownloadKey(HttpServletRequest request);
/**
*
* <h2>使用凭证下载指定文件</h2>
* <p>根据请求所持凭证下载指定文件资源</p>
* @author 青阳龙野(kohgylw)
* @param request javax.servlet.http.HttpServletRequest 请求对象
* @param response javax.servlet.http.HttpServletResponse 响应对象
*/
void downloadFileByKey(HttpServletRequest request,HttpServletResponse response);
}

View File

@ -0,0 +1,101 @@
package kohgylw.kiftd.server.service.impl;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
import kohgylw.kiftd.server.enumeration.AccountAuth;
import kohgylw.kiftd.server.mapper.NodeMapper;
import kohgylw.kiftd.server.model.Node;
import kohgylw.kiftd.server.service.ExternalDownloadService;
import kohgylw.kiftd.server.util.ConfigureReader;
import kohgylw.kiftd.server.util.FileBlockUtil;
import kohgylw.kiftd.server.util.LogUtil;
import kohgylw.kiftd.server.util.RangeFileStreamWriter;
@Service
public class ExternalDownloadServiceImpl extends RangeFileStreamWriter implements ExternalDownloadService {
private static Map<String, String> downloadKeyMap = new HashMap<>();// 凭证池用于存储生成好的下载凭证
private static final String CONTENT_TYPE = "application/octet-stream";
@Resource
private NodeMapper nm;
@Resource
private LogUtil lu;
@Resource
private FileBlockUtil fbu;
@Override
public String getDownloadKey(HttpServletRequest request) {
// 首先进行权限检查
final String account = (String) request.getSession().getAttribute("ACCOUNT");
// 权限检查
if (ConfigureReader.instance().authorized(account, AccountAuth.DOWNLOAD_FILES)) {
// 找到要下载的文件节点
final String fileId = request.getParameter("fId");
if (fileId != null) {
final Node f = this.nm.queryById(fileId);
if (f != null) {
// 获取凭证
synchronized (downloadKeyMap) {
// 查找该资源是否已经生成了一个凭证如有则直接使用否则新生成一个加入到凭证表
this.lu.writeShareFileURLEvent(request, f);
if (downloadKeyMap.containsValue(f.getFileId())) {
Entry<String, String> k = downloadKeyMap.entrySet().parallelStream()
.filter((e) -> e.getValue().equals(f.getFileId())).findFirst().get();
return k.getKey();
} else {
String dKey = UUID.randomUUID().toString();
downloadKeyMap.put(dKey, f.getFileId());
return dKey;
}
}
}
}
}
return "ERROR";
}
@Override
public void downloadFileByKey(HttpServletRequest request, HttpServletResponse response) {
final String dkey = request.getParameter("dkey");
// 权限凭证有效性并确认其对应的资源
if (dkey != null) {
// 找到要下载的文件节点
String fId = null;
synchronized (downloadKeyMap) {
fId = downloadKeyMap.get(dkey);
}
if (fId != null) {
Node f = this.nm.queryById(fId);
if (f != null) {
File target = this.fbu.getFileFromBlocks(f);
if (target != null && target.isFile()) {
writeRangeFileStream(request, response, target, f.getFileName(), CONTENT_TYPE);
if (request.getHeader("Range") == null) {
this.lu.writeDownloadFileByKeyEvent(f);
}
return;
}
}
}
}
try {
//  处理无法下载的资源
response.sendError(404);
} catch (IOException e) {
}
}
}

View File

@ -36,6 +36,7 @@ public class PlayVideoServiceImpl implements PlayVideoService {
final String suffix = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
switch (suffix) {
case "mp4":
case "mov":
// 对于mp4后缀的视频进一步检查其编码是否为h264如果是则设定无需转码直接播放
MultimediaObject mo = new MultimediaObject(fbu.getFileFromBlocks(f));
try {
@ -50,7 +51,6 @@ public class PlayVideoServiceImpl implements PlayVideoService {
vi.setNeedEncode("Y");
return vi;
case "webm":
case "mov":
case "avi":
case "wmv":
case "mkv":

View File

@ -215,6 +215,59 @@ public class LogUtil {
t.start();
}
}
/**
*
* <h2>简述</h2>
* <p>详细描述</p>
* @author 青阳龙野(kohgylw)
* @param xxx 参数描述
* @return xxx
*/
public void writeDownloadFileByKeyEvent(Node f) {
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
Thread t = new Thread(() -> {
Folder folder = fm.queryById(f.getFileParentFolder());
List<Folder> l = fu.getParentList(folder.getFolderId());
String pl = new String();
for (Folder i : l) {
pl = pl + i.getFolderName() + "/";
}
String content = ">OPERATE [Download file By Shared URL]\r\n>PATH [" + pl
+ folder.getFolderName() + "]\r\n>NAME [" + f.getFileName() + "]";
writeToLog("Event", content);
});
t.start();
}
}
/**
*
* <h2>记录分享下载链接事件</h2>
* <p>当用户试图获取一个资源的下载链接时记录此事件</p>
* @author 青阳龙野(kohgylw)
*/
public void writeShareFileURLEvent(HttpServletRequest request, Node f) {
if (ConfigureReader.instance().inspectLogLevel(LogLevel.Event)) {
String account = (String) request.getSession().getAttribute("ACCOUNT");
if (account == null || account.length() == 0) {
account = "Anonymous";
}
String a = account;
Thread t = new Thread(() -> {
Folder folder = fm.queryById(f.getFileParentFolder());
List<Folder> l = fu.getParentList(folder.getFolderId());
String pl = new String();
for (Folder i : l) {
pl = pl + i.getFolderName() + "/";
}
String content = ">ACCOUNT [" + a + "]\r\n>OPERATE [Share Download file URL]\r\n>PATH [" + pl
+ folder.getFolderName() + "]\r\n>NAME [" + f.getFileName() + "]";
writeToLog("Event", content);
});
t.start();
}
}
/**
* 以格式化记录重命名文件日志

View File

@ -1,5 +1,5 @@
#Generated by Maven Integration for Eclipse
#Tue Apr 02 17:19:43 CST 2019
#Wed Apr 03 11:57:11 CST 2019
version=1.0.17-SNAPSHOT
groupId=kohgylw
m2e.projectName=kiftd

View File

@ -91,8 +91,12 @@ body {
width: 150px;
height: 35px;
background-color: #FCFCFC;
color: #104E8B;
alpha (Opacity=70);
color: #104E8B; alpha (Opacity=70);
-moz-opacity: 0.7;
opacity: 0.7;
}
}
td {
word-break: break-all;
word-wrap: break-all;
}

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}.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}
@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}td{word-break:break-all;word-wrap:break-all}

View File

@ -448,7 +448,7 @@
</h4>
</div>
<div class="modal-body">
<h5 id="deleteFileMessage"></h5>
<h5 id="deleteFileMessage" class="wordbreak"></h5>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>

View File

@ -302,6 +302,11 @@ $(function() {
$('#gobacktotopbox').addClass("hidden");
}
});
// 打开查看下载链接时向后台生成/获取下载链接
$('#downloadURLCollapse').on('shown.bs.collapse', function () {
getDownloadURL();
});
});
// 根据屏幕大小增删表格显示内容
@ -1324,14 +1329,15 @@ function showUploadFileAlert(txt) {
// 显示下载文件模态框
function showDownloadModel(fileId, fileName) {
$("#downloadModal").modal('toggle');
$("#downloadFileName").text("提示:您确认要下载文件:[" + fileName + "]么?");
$("#downloadHrefBox").html("<a href='"+window.location.protocol+"//"+window.location.host+"/homeController/downloadFile.do?fileId="+fileId+"'>"+window.location.protocol+"//"+window.location.host+"/homeController/downloadFile.do?fileId="+fileId+"</a>");
$("#downloadHrefBox").html("<span class='text-muted'>正在生成...</span>");
getDownloadFileId=fileId;
$("#downloadFileBox")
.html(
"<button id='dlmbutton' type='button' class='btn btn-primary' onclick='dodownload("
+ '"' + fileId + '"' + ")'>开始下载</button>");
$("#dlmbutton").attr('disabled', false);
$("#downloadModal").modal('show');
}
// 执行下载操作
@ -2216,6 +2222,27 @@ function doSearchFile(){
endLoading();
}
// 返回顶部实现
function goBackToTop(){
$('html,body').animate({scrollTop: 0},'slow');
}
var getDownloadFileId;
// 获取某一文件的下载链接
function getDownloadURL(){
$.ajax({
url:'externalLinksController/getDownloadKey.ajax',
type:'POST',
dataType:'text',
data:{
fId:getDownloadFileId
},
success:function(result){
$("#downloadHrefBox").html("<a href='"+window.location.protocol+"//"+window.location.host+"/externalLinksController/downloadFileByKey.do?dkey="+result+"'>"+window.location.protocol+"//"+window.location.host+"/externalLinksController/downloadFileByKey.do?dkey="+result+"</a>");
},
error:function(){
$("#downloadHrefBox").html("<span class='text-muted'>获取失败,请检查网络状态或<a href='javascript:void(0);' onclick='getDownloadURL()'>点此</a>重新获取。</span>");
}
});
}

File diff suppressed because one or more lines are too long