最近开始了一个新的项目,在这个项目中有很多对文件的操作,其实文件的上传已经不是什么新鲜的事儿了。作为一名开发者我自己也不是第一次处理文件方面的操作了,但是总是感觉自己对这些操作还不够熟练,仿佛有一层神秘的面纱没有被揭开,所以希望今天能将这个问题来总结一下,以便在日后的工作中能够拈手就来。
一、首先我们先来分析一下文件的上传过程
一个本地的文件上传到服务器中的过程:
首先我们要有一个界面可以让我们操作我们本地的文件
然后这个界面需要将我们选择的文件信息传递给一个逻辑处理中心(也就是我们的后台接口服务)
后台的接口服务程序接收到文件后需要对文件进行处理(比如我们要判断这个文件的类型、文件的大小、文件名称等一些信息)
处理完成之后我们需要选择文件的持久化方式,是需要存入数据库里面还是存入文件服务器的文件夹中,这一步是根据自己的实际需求决定
将文件存入服务器文件夹或者数据库中,操作就完成了
简单地做了个流程图:
二、文件上传的具体实现
1.前提条件
首先我们需要一个页面可以选择上传的文件,然后我们需要一个可以接收到文件对象的接口,最后我们还需要一个存放文件的服务器。在这篇文章里面作者采用的技术:页面——jsp和html;接口——SSM框架技术;数据库——mysql 5.7.20。
因为这次我们是分析对文件的操作,所以不细讲上面这些技术的使用。
2.设计逻辑思路
我采用的逻辑是:数据库——>接口——>页面——>测试
从底层数据库的表开始设计,然后处理接口和配置,再然后编写页面,最后千万不能忘了测试一把。
3.数据库设计
数据库的设计其实比较简单,直接在数据库中新建一张表就行了。
下面我贴一下sql代码:
CREATE TABLE `NewTable` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`up_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`up_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`up_time` datetime(0) NOT NULL,
`up_content` blob NULL,
`up_desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
)
这段代码我自己测试过,没问题!
4.接口服务
数据库表建好之后我们可以建一个与表相关的实体对象类
import java.util.Date;
/**
* Created by Viking on 2019/3/3
*/
public class FileUpload {
public Integer id;
public String upName;
public String upType;
public Date upTime;
public byte[] upContent;
public String upDesc;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUpName() {
return upName;
}
public void setUpName(String upName) {
this.upName = upName;
}
public String getUpType() {
return upType;
}
public void setUpType(String upType) {
this.upType = upType;
}
public Date getUpTime() {
return upTime;
}
public void setUpTime(Date upTime) {
this.upTime = upTime;
}
public byte[] getUpContent() {
return upContent;
}
public void setUpContent(byte[] upContent) {
this.upContent = upContent;
}
public String getUpDesc() {
return upDesc;
}
public void setUpDesc(String upDesc) {
this.upDesc = upDesc;
}
}
在SSM项目中上传文件还要先配置一下spring-mvc.xml
也贴一下配置的代码:
<!--配置视图解析器,访问WEB-INF下面的资源文件-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--在jsp文件中需要导入外部js库(jquery等)-->
<mvc:resources location="/lib" mapping="/lib/**"/>
<!--servlet默认处理器,在web.xml文件中映射地址为*.js-->
<mvc:default-servlet-handler/>
<!-- 配置文件上传,如果没有使用文件上传可以不用配置,当然如果不配,那么配置文件中也不必引入上传组件包 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 默认编码 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 文件大小最大值 -->
<property name="maxUploadSize" value="10485760000"/>
<!-- 内存中的最大值 -->
<property name="maxInMemorySize" value="40960"/>
</bean>
如果要访问静态资源,那么还需要在web.xml文件中添加以下配置:
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
<url-pattern>*.css</url-pattern>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
配置完成之后我们开始完成接口:
接口代码:
import com.center.model.FileUpload;
import com.center.service.FileUploadService;
import com.spider.aibaidu.SimilarityService;
import com.spider.util.FileUtils;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Date;
/**
* Created by Vikingon 2019/3/3
* 文件上传接口
*/
@RestController
@RequestMapping("file")
public class FileUploadController {
@Autowired
private FileUploadService service;
@RequestMapping(value = "upload", method = RequestMethod.POST)
public Object fileUpload(@RequestParam("file") MultipartFile[] files, String description){
for (MultipartFile file : files) {
System.out.println("文件类型:" + file.getContentType());
String filename = file.getOriginalFilename();
String suffix = filename.substring(filename.lastIndexOf("."));
System.out.println("文件名:" + filename);
System.out.println("文件大小:" + file.getSize() / 1024 + "KB");
System.out.println("文件后缀:" + suffix);
try {
FileUpload bean = new FileUpload();
bean.upName = filename;
bean.upType = suffix;
bean.upTime = new Date();
bean.upContent = file.getBytes();
bean.upDesc = description;
service.save(bean);
} catch (IOException e) {
e.printStackTrace();
}
}
return "文件上传成功";
}
@RequestMapping("down")
public void fileDownload(String fileName, HttpServletResponse response) throws Exception{
System.out.println("fileName:"+fileName);
FileUpload file = service.getFileByName(fileName);
if (null == file) throw new Exception("文件不存在");
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.upName,"UTF-8"));
response.setContentType("multipart/form-data");
response.getOutputStream().write(file.upContent);
}
}
上传和下载的都贴出来了,接口部分处理也就完成了。
5.前端页面
作者的这个工程配置是支持前后端分离的,当然前后端在一起也不影响。做分离的目的是可以降低耦合,这样的话后端不管用什么方式实现,前端都能够通用。
先讲一下前后端一起的jsp实现方式
FileUpload.jsp代码:
<%--
Created by IntelliJ IDEA.
User: Viking
Date: 2019/3/3
Time: 12:15
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<%--导入jQuery包--%>
<script type="text/javascript" src="../lib/js/jquery-1.9.0.min.js"></script>
<title>文件上传</title>
<style>
#box{
position:fixed;
overflow:hidden;
top:20%;
left:50%;
width: 300px;
height: 304px;
margin-top: -152px;
margin-left: -150px;
}
#img_box{
width: 300px;
height: 200px;
}
#form_box{
width: 300px;
height: 80px;
}
#down_box{
width: 300px;
height: 24px;
}
</style>
</head>
<body>
<div id="box" >
<div id="img_box">
<img width="100%" height="100%" id="show" src="../lib/img/skyTest04.jpg">
</div>
<div id="form_box">
<form id="form" method="post" enctype="multipart/form-data">
<table class="" style="margin: 0 auto;text-align: center">
<tr>
<td align="right"><span class="">选择文件:</span></td>
<td align="left">
<div class="">
<input id='location' type="text" disabled><%-- 显示上传文件的本地路径 --%>
<input type="button" id="choices" value="浏览" onclick="$('#file').click();"><%-- 将按钮的点击事件传递给文件标签 --%>
</div>
<input id="file" name="file" type="file" multiple="multiple" style="display: none" onchange="showImg(this.value)"/>
</td>
</tr>
<tr>
<td align="right">
<span class="">文件描述:</span>
</td>
<td align="left">
<div>
<input id="desc" name="description" type="text"/>
</div>
</td>
</tr>
<tr>
<td colspan="2">
<input id="upload" name="upload" type="button" value="上传" onclick="uploadFile()"/>
</td>
</tr>
</table>
</form>
</div>
<div id="down_box">
文件名称:
<input id="fileName" type="text">
<input id="down" type="button" value="下载" onclick="downLoadFile()">
</div>
</div>
</body>
<script>
function showImg(path) {
$('#show').attr("src",path);
$('#location').val(path);
console.log(path);
}
function uploadFile() {
var form = $('#form');
var formData = new FormData(form[0]);
var desc =$('#desc').val();
if (null == desc || desc.length < 1) {
alert("请填写文件描述");
return false;
}
console.log("表单:" + form);
console.log("文件:" + formData);
console.log("描述:"+desc);
console.log("form.size():" + form.size());
//ajax请求
$.ajax({
url: "/file/upload",//后台的接口地址
type: "post",//post请求方式
data: formData,//参数
cache: false,
processData: false,
contentType: false,
success: function (data) {
alert(data);
}, error: function () {
alert("文件上传失败");
}
})
}
function downLoadFile() {
var fileName = $('#fileName').val();
if (fileName == null ||fileName.length < 1){
alert("请输入文件名称");
return false;
}
console.log("文件名称")
location.href = "/file/down?fileName="+fileName;
}
</script>
</html>
作者是采用的jQuery和ajax方式实现发送请求,当然如果直接使用表单提交那只会更简单。
因为我不喜欢每次提交都跳转页面,所以就选用了ajax提交。
这里面有两个地方需要使用者根据自己的实际配置来修改,第一个就是表单上面的预览图片,我的图片你们肯定是没有的,路径也不一定会相同,所以自己改。第二就是jQuery的包,这个也是不可能没人都一样的,更具自己的jQuery版本和存放路径进行修改。
ps:另外申明一下,这个界面的布局长相,全是作者自己个人喜好,若有不同意见,那就自己去改。另外,文中的图若看不清楚,请点击查看大图!
接下来就是见证奇迹的时刻,运行一下我的代码先!
为什么这个图片看着这么丑!!!
算了,将就看吧~~~
ps:作者目前能力还有限,代码是经过反复调试才成功的,不管怎样,这算是大功告成了。
6.测试
然后我们挑一个文件来测试一下
先选一张图
看下预览效果:
预览没问题!
ps:其实预览并没有什么卵用,纯属作者喜欢装逼~~
然后我们提交一个请求看看:
请求接收到了,但是!!!!
装逼失败!!!!
这里有一个知识点,我们下节课再讲,不过咱们还是得先把这节课的内容完成啊。
施个魔法~:
你猜结果怎么着~
文件保存成功!
看看后台的数据流向
日志显示insert成功了一条记录,
去数据看看:
有这条记录了,整个流程算是走完了!
先喝口水~
接下来我们来把html写的前端分离页面运行一下:
这是一个html的文件,我们用它来测试一下:
前面选了个图片没问题,接下来提交请求,看看后台数据流向:
成功了!
日志正常!
数据库记录正常 !
OK,那么我就把源码拉出来:
<!doctype html>
<html>
<head>
<!--导入jQuery包-->
<script type="text/javascript" src="jquery-1.9.0.min.js"></script>
<title>文件上传</title>
<style>
#box{
position:fixed;
overflow:hidden;
top:20%;
left:50%;
width: 300px;
height: 304px;
margin-top: -152px;
margin-left: -150px;
}
#img_box{
width: 300px;
height: 200px;
}
#form_box{
width: 300px;
height: 80px;
}
#down_box{
width: 300px;
height: 24px;
}
</style>
</head>
<body>
<div id="box" >
<div id="img_box">
<img width="100%" height="100%" id="show" src="picture.jpeg">
</div>
<div id="form_box">
<form id="form" method="post" enctype="multipart/form-data">
<table class="" style="margin: 0 auto;text-align: center">
<tr>
<td align="right"><span class="">选择文件:</span></td>
<td align="left">
<div class="">
<input id='location' type="text" disabled><!-- 显示上传文件的本地路径 -->
<input type="button" id="choices" value="浏览" onclick="$('#file').click();"><!-- 将按钮的点击事件传递给文件标签 -->
</div>
<input id="file" name="file" type="file" multiple="multiple" style="display: none" onchange="showImg(this.value)"/>
</td>
</tr>
<tr>
<td align="right">
<span class="">文件描述:</span>
</td>
<td align="left">
<div>
<input id="desc" name="description" type="text"/>
</div>
</td>
</tr>
<tr>
<td colspan="2">
<input id="upload" name="upload" type="button" value="上传" onclick="uploadFile()"/>
</td>
</tr>
</table>
</form>
</div>
<div id="down_box">
文件名称:
<input id="fileName" type="text">
<input id="down" type="button" value="下载" onclick="downLoadFile()">
</div>
</div>
</body>
<script>
function showImg(path) {
$('#show').attr("src",path);
$('#location').val(path);
console.log(path);
}
function uploadFile() {
var form = $('#form');
var formData = new FormData(form[0]);
var desc =$('#desc').val();
if (null == desc || desc.length < 1) {
alert("请填写文件描述");
return false;
}
console.log("表单:" + form);
console.log("文件:" + formData);
console.log("描述:"+desc);
console.log("form.size():" + form.size());
//ajax请求
$.ajax({
url: "http://127.0.0.1:8181/file/upload",//后台的接口地址
type: "post",//post请求方式
data: formData,//参数
cache: false,
processData: false,
contentType: false,
success: function (data) {
alert(data);
}, error: function () {
alert("文件上传失败");
}
})
}
function downLoadFile() {
var fileName = $('#fileName').val();
if (fileName == null ||fileName.length < 1){
alert("请输入文件名称");
return false;
}
console.log("文件名称")
location.href = "http://127.0.0.1:8181/file/down?fileName="+fileName;
}
</script>
</html>
其实和jsp没什么区别,唯一的区别就是html可以单独运行,并且html是经过跨域请求到接口服务器的。
上传的知识介绍完了,下面把文件的下载捋一下 。
三、文件的下载
文件的下载其实就是将文件以流的形式从服务器复制到本地客户端。
1.从文件服务器的文件夹中下载
这种方式就是最简单的一种,它的原理就是将前端的<a>标签的href属性指向文件的存放路径,那么<a>标签的点击事件就会自动将链接中的文件下载下来。所以这种方式对于前端还是后端来说都很简单,后端接口只需要返回一个路径地址的字符串就可以了。
2.从数据库中读取文件并下载
从数据库中读取文件后下载相对来说会复杂一点点,需要配置响应的响应头、响应数据类型和Servlet的输出流。
代码:
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.upName,"UTF-8"));
response.setContentType("multipart/form-data");
response.getOutputStream().write(file.upContent);
对于文件名称,作者使用了URL的编码方式,这样能保证中文名称下载时不会乱码或识别不了。
完整的代码在前面上传的接口代码中已经给出了。
文件下载的前端代码不能再使用ajax请求的方式了,因为我们这里是要下载一个文件,而我参考了网上的资料,有朋友指出ajax请求的方式只能接收到数据的文本形式,并不能触发浏览器下载该文件。
所以我的前端代码写了这样一个js函数:
function downLoadFile() {
var fileName = $('#fileName').val();
if (fileName == null ||fileName.length < 1){
alert("请输入文件名称");
return false;
}
console.log("文件名称")
location.href = "/file/down?fileName="+fileName;
}
这样相当于让浏览器去打开这个链接,当浏览器识别到链接的响应类型为文件时,就会自动下载文件了。
我们来测试一下下载的结果:
输入一个earth.jpg,这个文件我们刚才已经上传到数据库了。
点击下载后浏览器的反应:
最后在下载保存的文件夹中可以看到我们下载的结果:
所有的流程都是没有问题的,但是这里有几个小问题要特别说明一下。
如果细心的朋友可能会发现,我一开始使用的是Chrome浏览器,但是后面怎么全部换成IE了?
因为现在大多数的浏览器在上传文件后预览的时候都拿不到文件的真实路径了,全部是C:\fakepath\xxx.jpg了,而这个路径是不可用的,所以在chrome浏览器上显示不了预览效果。但是在IE浏览器上这样操作是没有问题的,IE把真实路径给出来了,所以可以预览图片。
另一个就是我的后台程序有一次报错,我施了个魔法,然后它又能工作了,为啥呢?
这里是因为我的mysql服务器有一些配置没有设置,导致上传到数据库中的数据包过大时,mysql拒绝了我的请求。具体的配置我会在下一篇文章中解释。
四、总结
文件上传下载虽然已经不是什么很难的问题了,但是由于我们大部分开发者在日常工作中并不会经常性地去写这些代码,所以当某一天又要使用这个东西的时候,并不能像我们写平时写的那些代码能一气呵成就搞定。因此作者希望将这些东西保留下来,以便日后参考,另外就是也从一定程度上的梳理了一遍文件的上传下载的原理和过程,加深自己的理解。
希望这篇文章会对你有所帮助,如果文中存在不足与错误的地方,欢迎大家留言指正,若觉得文章写得还不错,请给作者点一个赞,谢谢!