上传
上传文件最常见的就是通过form表单提交,但是注意需要添加enctype="multipart/form-data"
:
<%--action如果加“/”就表示从上下文开始,需要加request.getContextPath()--%>
<form name="FirstForm" action="fileUpload.do" method="post" enctype="multipart/form-data">
<h1>采用流的方式上传文件</h1>
<input type="file" name="file">
<input type="submit" value="upload"/>
</form>
不要以为只是在请求头中添加Content-Type:multipart/form-data,它还有一个分隔符boundary,用于分隔多个上传部分,一般情况下浏览器会自动维护添加,但也有不会添加的时候,比如angular中使用的HttpClient发送的post请求,如果只设置内容格式为“multipart/form-data”就会报没有分割多部分标识异常。
用SpringMVC作为后端框架处理:
1.在对应DispatcherServlet的配置文件中添加
<!-- SpringMVC上传文件时,需要配置MultipartResolver处理器 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
<!-- 指定所上传文件的总大小不能超过200KB。注意maxUploadSize属性的限制不是针对单个文件,而是所有文件的容量之和 -->
<property name="maxUploadSize" value="104857600" /><!-- -1表示无限制,单位byte -->
</bean>
<!-- SpringMVC在超出上传文件限制时,会抛出org.springframework.web.multipart.MaxUploadSizeExceededException -->
<!-- 该异常是SpringMVC在检查上传的文件信息时抛出来的,而且此时还没有进入到Controller方法中 -->
<bean id="exceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!-- 遇到MaxUploadSizeExceededException异常时,自动跳转到指定的页面,如果配置了视图会按视图设置加前后缀 -->
<prop
key="org.springframework.web.multipart.MaxUploadSizeExceededException">exception</prop>
</props>
</property>
</bean>
2.处理器方法:
/*
* 采用transferTo来保存上传的文件,使用缓存流
* @RequestParam("file")用于接收form中name为file的文件输入
* SpringMVC把文件封装成CommonsMultipartFile对象,且必须指定
*/
@RequestMapping("fileUpload")
public String fileUpload(@RequestParam("file") CommonsMultipartFile file) throws IOException {
String path = desktopPath + file.getOriginalFilename();
File newFile = new File(path);
file.transferTo(newFile);
return "welcome";
}
即使没有上传的文件,这个请求也不会报异常,只是这个file.getOriginalFilename为“”。
如果上传的表单中有多个name为file的上传文件,那么此种方法只能获取第一个name为file的文件,注意。
为解决这个问题可以用CommonsMultipartFile数组接收,如public String fileUpload(@RequestParam("file") CommonsMultipartFile[] files)
。
CommonsMultipartFile还有其他几种用法,如下演示:
@RequestMapping("springUpload")
public String springUpload(HttpServletRequest request) throws IllegalStateException, IOException {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(
request.getSession().getServletContext());
//检查请求中Content-Type以multipart/开头,且是post请求
if (multipartResolver.isMultipart(request)) {
MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
//获取multiRequest中所有文件的请求名称,非真实文件名
Iterator iter = multiRequest.getFileNames();
while (iter.hasNext()) {
MultipartFile file = multiRequest.getFile(iter.next().toString());
if (file != null) {
String path = desktopPath + file.getOriginalFilename();
//上传
file.transferTo(new File(path));
}
}
}
return "welcome";
}
@RequestMapping("ajaxUpload")
public String ajaxUpload(HttpServletRequest request) throws IOException {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(
request.getSession().getServletContext());
if (multipartResolver.isMultipart(request)) {
MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> map = multipartHttpServletRequest.getFileMap();
Collection<MultipartFile> multipartFiles = map.values();
for (MultipartFile multipartFile : multipartFiles) {
String fileName = multipartFile.getOriginalFilename(); //文件名,如果没传文件为""
String fieldName = multipartFile.getName(); //表单name字段名
if ("".equals(fileName)) continue;
String path = desktopPath + fileName;
multipartFile.transferTo(new File(path));
}
}
return "welcome";
}
那么是否只有通过form表单的形式才能上传文件呢?肯定不是,一般的ajax请求也可以完成。
1.
<form id="uploadForm">
<h1>通过ajax上传文件</h1>
<input type="file" name="file1">
<input type="file" name="file2">
<button onclick="doUpload()">upload</button>
</form>
对应的js代码:
function doUpload() {
var formData = new FormData($("#uploadForm")[0]);
$.ajax({
//写这个域名是为了解决Charles无法抓取localhost,其实就是localhost
url: 'http://localhost.charlesproxy.com:8080/learn/ajaxUpload.do',
type: 'post',
data: formData,
async: true,
contentType: false,
processData: false,
success: function (res) {
console.log(res);
}
});
}
这里我没有给表单设置ContentType,后端同样可以获取到数据,说明只是通过Form表单的action方式提交需要设置。
<div>
<h1>文件数组</h1>
<input type="file" id="file1">
<input type="file" id="file2">
<input type="button" value="upload" onclick="doUpload2()">
</div>
对应的js代码:
function doUpload2(){
var obj = new FormData();
var file1 = document.getElementById("file1").files[0];
var file2 = document.getElementById("file2").files[0];
obj.append("file", file1);
obj.append("file", file2);
$.ajax({
url: 'http://localhost.charlesproxy.com:8080/learn/ajaxUpload2.do',
type: 'post',
data: obj,
async: true,
contentType: false,
processData: false,
success: function (res) {
console.log(res);
}
});
}
自定义请求报文上传
之前写的几个都是浏览器生成的请求报文上传的,我们也可以自定义请求报文,那就必须先熟悉报文的内容。
这是我用Charles截取的报文内容,图片的具体数据我删除了。
请求头的参数说明可以查看
https://blog.csdn.net/qq_30553235/article/details/79282113
只上传一个文件的报文设置:
@RequestMapping("typeDesc")
public String typeDesc(String fileName) {
try {
fileName = URLDecoder.decode(fileName, encode);
//换行符
String newLine = "\r\n";
//分割线前缀
String boundaryPrefix = "--";
//数据分割线
String boundary = "WebKitFormBoundaryEE43EOKHrgdYmN1k";
URL url = new URL("http://localhost.charlesproxy.com:8080/learn/ajaxUpload2.do");
// 此时uconn只是一个连接对象,待连接中
URLConnection uconn = url.openConnection();
if (uconn instanceof HttpURLConnection) {
HttpURLConnection conn = (HttpURLConnection) uconn;
//------------------------设置请求头-----------------------------------
//设置请求方式为POST,需要为大写
conn.setRequestMethod("POST");
//设置连接输出流为true,默认false (post请求是以流的方式隐式的传递参数)
conn.setDoOutput(true);
//设置连接输入流为true
conn.setDoInput(true);
conn.setUseCaches(false);
//持久连接,HTTP/1.1的默认设置,如果需要关闭可以加入 Connection: close
conn.setRequestProperty("Connection", "keep-alive");
conn.setRequestProperty("Accept-Charset", "UTF-8");
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
//建立连接,但请求未开始,以上各个参数设置需在此方法之前进行。当conn.getInputStream()方法调用时才发起请求
conn.connect();
//------------------------按格式设置请求数据-----------------------------------
// 上传文件
File file = new File(desktopPath + fileName);
StringBuilder sb = new StringBuilder();
sb.append(boundaryPrefix);
sb.append(boundary);
sb.append(newLine);
// file是传的字段名
sb.append("Content-Disposition: form-data;name=\"file\";filename=\"" + fileName + "\"" + newLine);
sb.append("Content-Type:application/octet-stream");
// 参数头设置完以后需要两个换行,然后才是参数内容
sb.append(newLine);
sb.append(newLine);
OutputStream out = new DataOutputStream(conn.getOutputStream());
// 将参数头的数据写入到输出流中
out.write(sb.toString().getBytes());
// 数据输入流,用于读取文件数据
DataInputStream in = new DataInputStream(new FileInputStream(file));
byte[] bufferOut = new byte[1024];
int bytes = 0;
// 每次读1KB数据,并且将文件数据写入到输出流中
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
// 最后添加换行
out.write(newLine.getBytes());
in.close();
// 定义最后数据分隔线,即--加上BOUNDARY再加上--。
byte[] end_data = (boundaryPrefix + boundary + boundaryPrefix + newLine)
.getBytes();
// 写上结尾标识
out.write(end_data);
out.flush();
out.close();
//-----------------------请求设置完成并关闭流----------------------------
//--------调用conn.getInputStream()连接发起请求,处理服务器响应------------
//定义BufferedReader输入流来读取URL的响应
BufferedReader reader = new BufferedReader(new InputStreamReader(
conn.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
conn.disconnect();
//----------------关闭流并销毁连接-------------------
}
} catch (Exception e) {
e.printStackTrace();
}
return "welcome";
}
其他的几种请求也类似,可以先查看报文在构造。
下载
传统Java下载可以有两种,一种是浏览器可以直接预览,一种是另存为下载。下面看演示:
/**
*
* @param request 当前请求
* @param response 当前响应
* @param isOnline 在线预览
*/
@RequestMapping("download")
public void download(HttpServletRequest request, HttpServletResponse response, boolean isOnline) {
BufferedInputStream dis = null;
BufferedOutputStream fos = null;
String urlString = "https://cdn.duitang.com/uploads/item/201205/03/20120503103536_uUWSi.jpeg";
String fileName = urlString.substring(urlString.lastIndexOf('/') + 1);
response.reset();
try {
URL url = new URL(urlString);
URLConnection conn = url.openConnection();
if (isOnline) {
response.setContentType(conn.getContentType()); //隐式执行conn.connect();
response.setHeader("Content-Disposition", "inline;filename=" + fileName); //预览
} else {
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment; filename=" + fileName); //下载
response.setHeader("Content-Length", String.valueOf(conn.getContentLength())); //隐式执行conn.connect();
}
dis = new BufferedInputStream(conn.getInputStream());
fos = new BufferedOutputStream(response.getOutputStream());
byte[] buff = new byte[2048];
int bytesRead;
while (-1 != (bytesRead = dis.read(buff, 0, buff.length))) {
fos.write(buff, 0, bytesRead);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (dis != null)
try {
dis.close();
} catch (Exception e) {
e.printStackTrace();
}
if (fos != null)
try {
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
SpringMVC下载方式:
@RequestMapping("/downloadSpringMVC")
public ResponseEntity<byte[]> export(String fileName) throws IOException {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", fileName);
File file = new File(desktopPath + fileName);
return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
}
页面显示图片
上传的图片如果不需要保存到后台,只是页面上显示一下,可以这样做:
<div>
<h1>页面显示图片</h1>
<input type="file" name="file" onchange="showPreview(this)">
<img id="img">
</div>
js代码:
function showPreview(source) {
var file = source.files[0];
if (window.FileReader) {
var fr = new FileReader();
fr.onloadend = function (e) {
document.getElementById("img").src = e.target.result;
};
fr.readAsDataURL(file);
}
}