昨天刚刚做了一个文件列表上传,后端很简单,用
MultipartFile[] files
获取文件流数组,后端就当IO流操作就可以,似乎好像没啥好写的,但是!!!!!前端是真的糙单.要是自己写一个前端单个文件上传样式是丑了点,不过还是能用的,只是样式是真的丑了....无语了,所有有了这篇.首先来张完成的效果
下面就是实现步骤了,开始对比了Bootstrap fileinput 和jQeury的uploadfile,我使用的功能似乎单一且简单,所以并不需要哪些花狸狐哨的功能,所以选择了这个插件,首先还是感谢大佬,开源这么好的插件
DEMO地址: http://w.twinkling.cn/
官网地址: http://www.twinkling.cn/
我使用的是SpringBoot,上面的demo是基于基本的servlet写的,现在需要整合到我的项目中.
这个需要注意一点,插件需要写一个
/tk 请求,用于生层上传文件的唯一TOKEN,标识文件,其他的还需要一个配置类,基本配置文件,涉及到文件上传的一些配置,等下一起给出来
前端代码:
<head>
<meta charset="UTF-8">
<title>上传数据</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="../../static/files/css/stream-v1.css" rel="stylesheet" type="text/css">
</head>
<style>
body{
width: 90%;
padding: 10px;
margin-left: 5%;
}
#i_select_files{
width: 70%;
height: 5rem;
}
#i_stream_message_container{
position: absolute !important;
float: left !important;
}
#operate{
position: absolute;
width: 50%;
height: 100%;
}
#result{
position: absolute;
width: 50%;
height: 100%;
margin-left: 50%;
}
button{
background: transparent;
border: 1px #2a6496 solid;
width: 3rem;
margin-left: 2.8rem;
margin-top: 2rem;
border-radius: 5px;
background-color: #0099FF;
font-weight: bolder;
}
button:hover{
background-color: #4cae4c;
pointer-events: painted;
}
#i_select_files div{
margin-top: 1rem;
}
</style>
<body>
<div id="operate">
<div id="i_select_files"></div>
<div id="i_stream_files_queue"></div>
<button onclick="javascript:_t.upload();">开始上传</button>
<button onclick="javascript:_t.stop();">停止上传</button>
<button onclick="javascript:_t.cancel();">取消上传</button>
<button onclick="javascript:_t.destroy();_t=null;_t=new Stream(config);">重新上传</button>
<button id="import" style="color: #c9302c">导入文件</button>
</div>
<div id="result">
结果信息:
<div id="i_stream_message_container" class="stream-main-upload-box" style="overflow: auto;height: 93%;color: #3c763d"></div>
</div>
</body>
<script type="text/javascript" src="../../static/js/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="../../static/files/js/stream-v1.js"></script>
<script src="https://cdn.bootcss.com/layer/2.3/layer.js"></script>
<script type="text/javascript">
$("#import").click(function () {
//询问框
layer.confirm('确定导入这个月最新上传的数据?', {
btn: ['确定','我在想想'] //按钮
}, function(){
//加载层
var index = layer.load(0, {shade: false}); //0代表加载的风格,支持0-2
$.ajax({
type: "GET",
url: "/xlxs/setData",
success: function (data) {
if(data != null){
layer.close(index);
layer.alert('导入成功', {
skin: 'layui-layer-lan'
,closeBtn: 0
,anim: 4 //动画类型
});
}else{
layer.msg("导入失败,请重新导入!");
}
}
});
}, function(){
});
})
/**
* 配置文件(如果没有默认字样,说明默认值就是注释下的值)
* 但是,on*(onSelect, onMaxSizeExceed...)等函数的默认行为
* 是在ID为i_stream_message_container的页面元素中写日志
*/
var config = {
browseFileId : "i_select_files", /** 选择文件的ID, 默认: i_select_files */
browseFileBtn : "<div>请把xlxs文件拖到这里</div>", /** 显示选择文件的样式, 默认: <div>请选择文件</div> */
dragAndDropArea: "i_select_files", /** 拖拽上传区域,Id(字符类型"i_select_files")或者DOM对象, 默认: `i_select_files` */
dragAndDropTips: "<span>(文件夹)也是可以的</span>", /** 拖拽提示, 默认: <span>把文件(文件夹)拖拽到这里</span> */
filesQueueId : "i_stream_files_queue", /** 文件上传容器的ID, 默认: i_stream_files_queue */
filesQueueHeight : 200, /** 文件上传容器的高度(px), 默认: 450 */
messagerId : "i_stream_message_container", /** 消息显示容器的ID, 默认: i_stream_message_container */
multipleFiles: true, /** 多个文件一起上传, 默认: false */
onRepeatedFile: function(f) {
alert("文件:"+f.name +" 大小:"+f.size + " 已存在于上传队列中。");
return false;
},
autoUploading: false, /** 选择文件后是否自动上传, 默认: true */
autoRemoveCompleted : true, /** 是否自动删除容器中已上传完毕的文件, 默认: false */
maxSize: 20480000, /** 单个文件的最大大小,默认:2G */
retryCount : 3, /** HTML5上传失败的重试次数 */
// postVarsPerFile : { /** 上传文件时传入的参数,默认: {} */
// param1: "val1",
// param2: "val2"
// },
// swfURL : "/swf/FlashUploader.swf", /** SWF文件的位置 */
// tokenURL : "/tk", /** 根据文件名、大小等信息获取Token的URI(用于生成断点续传、跨域的令牌) */
// frmUploadURL : "/fd;", /** Flash上传的URI */
uploadURL : "/upload", /** HTML5上传的URI */
simLimit: 50, /** 单次最大上传文件个数 */
extFilters: [".xlsx"], /** 允许的文件扩展名, 默认: [] */
// onSelect: function(list) {alert('onSelect')}, /** 选择文件后的响应事件 */
onMaxSizeExceed: function(size, limited, name) {
alert("上传文件太大了,支持20MB以下")
}, /** 文件大小超出的响应事件 */
onFileCountExceed: function(selected, limit) {
alert("最大上传数量是50个");
}, /** 文件数量超出的响应事件 */
onExtNameMismatch: function(name, filters) {
alert(file.name+' 的文件格式不对,换个试试[xlsx]')
}, /** 文件的扩展名不匹配的响应事件 */
// onCancel : function(file) {
//
// }, /** 取消上传文件的响应事件 */
// onComplete: function(file) {alert('onComplete')}, /** 单个文件上传完毕的响应事件 */
onQueueComplete: function() {
_t.destroy();_t=null;_t=new Stream(config);
}, /** 所有文件上传完毕的响应事件 */
onUploadError: function(status, msg) {
alert('上传失败')
}, /** 文件上传出错的响应事件 */
onDestroy: function() {
} /** 文件上传出错的响应事件 */
};
var _t = new Stream(config);
</script>
这里面需要获取两个文件
stream-v1.css
stream-v1.js
其实这个也好获取,要是大家拿不到的话,我就发出来,其他的例子上说明的很详细,按照自己的业务要求修改就可以了.
后台代码:
这里是按照官网给的案例,整合到自己的SpringBoot项目中的,只是稍微修改了下代码,就可以了,只是需要找到修改的地方即可,要是找不到,呵呵,那就又要花费一天的干活.
好啦,开始....基于MVC模式
Controller
@GetMapping("/upload")
public void getUpload(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
StreamServlet streamServlet = new StreamServlet();
streamServlet.doGet(request,response);
}
@PostMapping("/upload")
public void postUpload(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
StreamServlet streamServlet = new StreamServlet();
streamServlet.doPost(request,response);
}
streamServlet:
public class StreamServlet extends HttpServlet {
private static final long serialVersionUID = -8619685235661387895L;
/** when the has increased to 10kb, then flush it to the hard-disk. */
static final int BUFFER_LENGTH = 10240;
static final String START_FIELD = "start";
public static final String CONTENT_RANGE_HEADER = "content-range";
/**
* Lookup where's the position of this file?
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doOptions(req, resp);
final String token = req.getParameter(UploadController.TOKEN_FIELD);
final String size = req.getParameter(UploadController.FILE_SIZE_FIELD);
final String fileName = req.getParameter(UploadController.FILE_NAME_FIELD);
final PrintWriter writer = resp.getWriter();
/** TODO: validate your token. */
JSONObject json = new JSONObject();
long start = 0;
boolean success = true;
String message = "";
try {
File f = IoUtil.getTokenedFile(token);
start = f.length();
} finally {
try {
if (success)
json.put(START_FIELD, start);
json.put(UploadController.SUCCESS, success);
json.put(UploadController.MESSAGE, message);
} catch (JSONException e) {}
writer.write(json.toString());
IoUtil.close(writer);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doOptions(req, resp);
final String token = req.getParameter(UploadController.TOKEN_FIELD);
final String fileName = req.getParameter(UploadController.FILE_NAME_FIELD);
Range range = IoUtil.parseRange(req);
OutputStream out = null;
InputStream content = null;
final PrintWriter writer = resp.getWriter();
//清除旧的文件
if(!IoUtil.deleteFile(fileName)){
writer.write("上传失败");
}
/** TODO: validate your token. */
JSONObject json = new JSONObject();
long start = 0;
boolean success = true;
String message = "";
File f = IoUtil.getTokenedFile(token);
try {
if (f.length() != range.getFrom()) {
/** drop this uploaded data */
throw new StreamException(StreamException.ERROR_FILE_RANGE_START);
}
out = new FileOutputStream(f, true);
content = req.getInputStream();
int read = 0;
final byte[] bytes = new byte[BUFFER_LENGTH];
while ((read = content.read(bytes)) != -1)
out.write(bytes, 0, read);
start = f.length();
}catch (StreamException se) {
success = StreamException.ERROR_FILE_RANGE_START == se.getCode();
message = "Code: " + se.getCode();
}catch (FileNotFoundException fne) {
message = "Code: " + StreamException.ERROR_FILE_NOT_EXIST;
success = false;
} catch (IOException io) {
message = "IO Error: " + io.getMessage();
success = false;
} finally {
IoUtil.close(out);
IoUtil.close(content);
/** rename the file */
if (range.getSize() == start) {
/** fix the `renameTo` bug */
// File dst = IoUtil.getFile(fileName);
// dst.delete();
// TODO: f.renameTo(dst); 重命名在Windows平台下可能会失败,stackoverflow建议使用下面这句
try {
// 先删除
IoUtil.getFile(fileName).delete();
Files.move(f.toPath(), f.toPath().resolveSibling(fileName));
System.out.println("TK: `" + token + "`, NE: `" + fileName + "`");
/** if `STREAM_DELETE_FINISH`, then delete it. */
if (Configurations.isDeleteFinished()) {
IoUtil.getFile(fileName).delete();
}
} catch (IOException e) {
success = false;
message = "Rename file error: " + e.getMessage();
}
}
try {
if (success) {
json.put(START_FIELD, start);
}
json.put(UploadController.SUCCESS, success);
json.put(UploadController.MESSAGE, message);
} catch (JSONException e) {}
writer.write(json.toString());
IoUtil.close(writer);
}
}
@Override
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("application/json;charset=utf-8");
resp.setHeader("Access-Control-Allow-Headers", "Content-Range,Content-Type");
resp.setHeader("Access-Control-Allow-Origin", Configurations.getCrossOrigins());
resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
}
@Override
public void destroy() {
super.destroy();
}
}
IoUtils:
/**
* IO--closing, getting file name ... main function method
*/
public class IoUtil {
static final Pattern RANGE_PATTERN = Pattern.compile("bytes \\d+-\\d+/\\d+");
/**
* According the key, generate a file (if not exist, then create
* a new file).
*
* @param filename
* @return
* @throws IOException
*/
public static File getFile(String filename) throws IOException {
if (filename == null || filename.isEmpty())
return null;
String name = filename.replaceAll("/", Matcher.quoteReplacement(File.separator));
File f = new File(Configurations.getFileRepository() + File.separator + name);
if (!f.getParentFile().exists())
f.getParentFile().mkdirs();
if (!f.exists())
f.createNewFile();
return f;
}
/**
* 清楚旧的文件
*
* @param fileName
* @return
*/
public static boolean deleteFile(String fileName){
boolean flag = false;
File file = new File(new UploadServiceImpl().filePathForNowDay() + File.separator + fileName);
if(file.exists()){
return file.delete();
}
return flag;
}
/**
* Acquired the file.
*
* @param key
* @return
* @throws IOException
*/
public static File getTokenedFile(String key) throws IOException {
if (key == null || key.isEmpty())
return null;
// 文件保存在服务器上的路径
File f = new File(new UploadServiceImpl().filePathForNowDay().toString()+key);
if (!f.getParentFile().exists())
f.getParentFile().mkdirs();
if (!f.exists())
f.createNewFile();
return f;
}
public static void storeToken(String key) throws IOException {
if (key == null || key.isEmpty())
return;
File f = new File(Configurations.getFileRepository() + File.separator + key);
if (!f.getParentFile().exists())
f.getParentFile().mkdirs();
if (!f.exists())
f.createNewFile();
}
/**
* close the IO stream.
*
* @param stream
*/
public static void close(Closeable stream) {
try {
if (stream != null)
stream.close();
} catch (IOException e) {
}
}
/**
* 获取Range参数
*
* @param req
* @return
* @throws IOException
*/
public static Range parseRange(HttpServletRequest req) throws IOException {
String range = req.getHeader(StreamServlet.CONTENT_RANGE_HEADER);
Matcher m = RANGE_PATTERN.matcher(range);
if (m.find()) {
range = m.group().replace("bytes ", "");
String[] rangeSize = range.split("/");
String[] fromTo = rangeSize[0].split("-");
long from = Long.parseLong(fromTo[0]);
long to = Long.parseLong(fromTo[1]);
long size = Long.parseLong(rangeSize[1]);
return new Range(from, to, size);
}
throw new IOException("Illegal Access!");
}
/**
* From the InputStream, write its data to the given file.
*/
public static long streaming(InputStream in, String key, String fileName) throws IOException {
OutputStream out = null;
File f = getTokenedFile(key);
try {
out = new FileOutputStream(f);
int read = 0;
final byte[] bytes = new byte[FormDataServlet.BUFFER_LENGTH];
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
out.flush();
} finally {
close(out);
}
/** rename the file * fix the `renameTo` bug */
File dst = IoUtil.getFile(fileName);
dst.delete();
f.renameTo(dst);
long length = getFile(fileName).length();
/** if `STREAM_DELETE_FINISH`, then delete it. */
if (Configurations.isDeleteFinished()) {
dst.delete();
}
return length;
}
}
TokenUtil:
/**
* Key Util: 1> according file name|size ..., generate a key;
* 2> the key should be unique.
*/
public class TokenUtil {
/**
* 生成Token, A(hashcode>0)|B + |name的Hash值| +_+size的值
* @param name
* @param size
* @return
* @throws Exception
*/
public static String generateToken(String name, String size)
throws IOException {
if (name == null || size == null)
return "";
int code = name.hashCode();
try {
String token = (code > 0 ? "A" : "B") + Math.abs(code) + "_" + size.trim();
/** TODO: store your token, here just create a file */
IoUtil.storeToken(token);
return token;
} catch (Exception e) {
throw new IOException(e);
}
}
}
.............................直接看仓库吧.
官方仓库:https://gitee.com/jiangdx/stream
基本上,看完这些可以缩短40%的时间...哈哈
平凡是我的一个标签