09-upload_download
- 希望将本地的资源发送给服务器,服务器提供给其他人使用、查看
- 将本地的资源上传至服务器,其他的电脑中可以通过访问网站来获取上传资源,打破空间局限性。eg:云存储、云编辑
1. fileUploadLocal
- 在页面中显示一个按钮
- 用户点击按钮后选择本地要上传的文件,在页面中使用
input
标签,type值设置为file
即可
- 用户点击按钮后选择本地要上传的文件,在页面中使用
- 确定上传请求的发送方式
- 上传成功后的响应结果在当前页面显示,使用ajax请求来完成资源的发送
- 上传请求的请求数据及数据格式
- 请求数据:上传的文件本身、用户名、Id、密码等,建议上传功能中不携带除上传资源以外的数据
- 数据格式:
- 传统的请求中,请求数据是以键值对的格式发送给后台服务器
- 上传请求中,没有任何一对键值可以描述上传的数据,因为数据本身是非常大的。键就相当于一个变量,使用一个变量存储一个10G的电影显然是不可能的。在上传请求中,将请求数据以二进制流的方式发送给服务器
- 在
ajax
中如何发送二进制流数据给服务器- 创建
FormData
对象,将请求数据存储到该对象中发送 - 将
processData
属性值设置为false
,告诉浏览器发送对象请求数据- 默认值
true
。默认情况下,通过data选项传递数据,如果是一个对象(技术上讲只要不是字符串),都会处理转化成一个查询字符串,以配合默认内容类型application/x-www-form-urlencoded
。如果要发送DOM树信息或其它不希望转换的信息,设置为false
- 默认值
- 将
contentType
属性值设置为false
,设置请求数据的类型为二进制类型
- 创建
- 上传成功后,后台服务器响应json对象给浏览器
- 文件上传依赖的jar
<!-- 文件上传依赖 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>
spring_mvc.xml
<!-- 配置文件上传解析组件
id值必须是multipartResolver
SpringMVC默认通过该id找到该组件
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="5242880"/>
</bean>
- 准备用户表
-- auto-generated definition
create table mvc_player
(
id int auto_increment comment '主键' primary key,
name varchar(20) null comment '账号',
password varchar(20) null comment '密码',
nickname varchar(20) null comment '玩家昵称',
photo varchar(255) null comment '玩家头像',
filetype varchar(255) null comment '文件类型'
);
- 前端代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>playerAdd.jsp</title>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$("#uploadFile").click(function () {
// 获取要上传的文件
var photoFile = $("#photo")[0].files[0]
if (photoFile == undefined) {
alert("您还未选中文件")
return;
}
// 将文件装入FormData对象
var formData = new FormData();
formData.append("headPhoto", photoFile)
// ajax向后台发送文件
$.ajax({
type: "post",
data: formData,
url: "fileUploadLocal",
processData: false,
contentType: false,
success: function (result) {
// 接收后台响应的信息
console.log(result)
// 图片回显
}
})
})
})
</script>
</head>
<body>
<form action="addPlayer" method="get">
<p>账号<input type="text" name="name"></p>
<p>密码<input type="text" name="password"></p>
<p>昵称<input type="text" name="nickname"></p>
<p>头像:</p>
<br/>
<input id="photo" type="file">
<a id="uploadFile" href="javascript:void(0)">立即上传</a>
<p><input type="submit" value="注册"></p>
</form>
</body>
</html>
- Controller代码
package com.listao.mvc.controller;
@Controller
public class FileController {
/**
* 文件名乱码。web.xml已经配置CharacterEncodingFilter
*/
@ResponseBody
@RequestMapping("/fileUploadLocal")
public String fileUploadLocal(MultipartFile headPhoto) throws IOException {
// 指定文件存储目录
File imgs = new File("/Users/listao/Pictures/imgs");
// 获取文件名
String originalFilename = headPhoto.getOriginalFilename();
// 文件存储位置
File file = new File(imgs, originalFilename);
// 文件保存
headPhoto.transferTo(file);
return "ok";
}
}
2. fileUploadWebapp
访问项目中的资源:http://localhost:8080/spring_mvc_war_exploded/js/jquery.min.js
文件上传中的几个问题
- 中文文件名编码问题:已经通过过滤器解决
- 文件位置存储问题:放在当前项目下,作为静态资源,可以通过URL访问
- 前端页面效果展现
1. spring_mvc.xml
<!-- 静态资源放行 -->
<mvc:resources mapping="/upload/**" location="/upload/"/>
2. 回显java
/**
* 1. 前端上传图片回显
* 2. 文件存储目录 => 项目部署环境下的upload目录
* 3. UUID解决文件名冲突
* 4. 控制上传文件类型
* 5. 规范返回值类型
* 6. 控制文件大小
*/
@ResponseBody
@RequestMapping("/fileUploadWebapp")
public Map<String, String> fileUploadWebapp(MultipartFile headPhoto, HttpServletRequest req) throws IOException {
Map<String, String> map = new HashMap<>();
// 控制文件大小
if (headPhoto.getSize() > 1024 * 1024 * 5) {
map.put("message", "文件大小不能超过5M");
return map;
}
// 指定文件存储目录 => 项目部署环境下的upload目录
String realPath = req.getServletContext().getRealPath("/upload");
File imgs = new File(realPath);
if (!imgs.exists()) {
imgs.mkdir();
}
// 获取文件名
String originalFilename = headPhoto.getOriginalFilename();
// 避免文件名冲突,使用UUID替换文件名
String uuid = UUID.randomUUID().toString();
// 获取拓展名
String extendsName = originalFilename.substring(originalFilename.lastIndexOf("."));
// 控制文件类型
if (!extendsName.equals(".jpg")) {
map.put("message", "文件类型必须是.jpg");
return map;
}
// 新的文件名
String newFileName = uuid.concat(extendsName);
// 文件存储位置
File file = new File(imgs, newFileName);
// 文件保存
headPhoto.transferTo(file);
// 上传成功之后,把文件的名字和文件的类型返回给浏览器
map.put("message", "上传成功");
map.put("newFileName", newFileName);
map.put("filetype", headPhoto.getContentType());
return map;
}
1. 控制文件大小
- 文件上传解析组件控制。会出现异常,后期可以配置一个异常解析器解决
<!-- 配置文件上传解析组件
id值必须是multipartResolver
SpringMVC默认通过该id找到该组件
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 1024 * 1024 * 5 = 5M -->
<property name="maxUploadSize" value="5242880"/>
</bean>
3. 图片回显jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>playerAdd.jsp</title>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$("#uploadFile").click(function () {
// 获取要上传的文件
var photoFile = $("#photo")[0].files[0]
if (photoFile === undefined) {
alert("您还未选中文件")
return;
}
// 将文件装入FormData对象
var formData = new FormData();
formData.append("headPhoto", photoFile)
// ajax向后台发送文件
$.ajax({
type: "post",
data: formData,
url: "fileUploadWebapp",
processData: false,
contentType: false,
success: function (result) {
// 接收后台响应的信息
alert(result.message)
// 图片回显
$("#headImg").attr("src", "upload/" + result.newFileName);
}
})
})
})
</script>
</head>
<body>
<form action="addPlayer" method="get">
<p>账号<input type="text" name="name"></p>
<p>密码<input type="text" name="password"></p>
<p>昵称<input type="text" name="nickname"></p>
<p>头像:</p>
<br/>
<input id="photo" type="file">
<br/>
<%-- 图片回显 --%>
<img id="headImg" style="width: 200px;height: 200px" alt="你还未上传图片">
<br/>
<a id="uploadFile" href="javascript:void(0)">立即上传</a>
<p><input type="submit" value="注册"></p>
</form>
</body>
</html>
4. 进度条
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>playerAdd.jsp</title>
<style>
.progress {
width: 200px;
height: 10px;
border: 1px solid #ccc;
border-radius: 10px;
margin: 10px 0px;
overflow: hidden;
}
/* 初始状态设置进度条宽度为0px */
.progress > div {
width: 0px;
height: 100%;
background-color: yellowgreen;
transition: all .3s ease;
}
</style>
<script type="text/javascript" src="static/js/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$("#uploadFile").click(function () {
// 获取要上传的文件
var photoFile = $("#photo")[0].files[0]
if (photoFile === undefined) {
alert("您还未选中文件")
return;
}
// 将文件装入FormData对象
var formData = new FormData();
formData.append("headPhoto", photoFile)
// ajax向后台发送文件
$.ajax({
type: "post",
data: formData,
url: "fileUpload.do",
processData: false,
contentType: false,
success: function (result) {
// 接收后台响应的信息
alert(result.message)
// 图片回显
$("#headImg").attr("src", "upload/" + result.newFileName);
},
xhr: function () {
var xhr = new XMLHttpRequest();
// 使用XMLHttpRequest.upload监听上传过程,注册progress事件,打印回调函数中的event事件
xhr.upload.addEventListener('progress', function (e) {
// loaded代表上传了多少
// total代表总数为多少
var progressRate = (e.loaded / e.total) * 100 + '%';
// 通过设置进度条的宽度达到效果
$('.progress > div').css('width', progressRate);
})
return xhr;
}
})
})
})
</script>
</head>
<body>
<form action="addPlayer" method="get">
<p>账号<input type="text" name="name"></p>
<p>密码<input type="text" name="password"></p>
<p>昵称<input type="text" name="nickname"></p>
<p>头像:</p>
<br/>
<input id="photo" type="file">
<%-- 图片回显 --%>
<br/>
<img id="headImg" style="width: 200px;height: 200px" alt="你还未上传图片">
<br/>
<%-- 进度条 --%>
<div class="progress">
<div></div>
</div>
<a id="uploadFile" href="javascript:void(0)">立即上传</a>
<p><input type="submit" value="注册"></p>
</form>
</body>
</html>
3. fileUploadServer
分服务器处理目的:让服务器各司其职,从而提高项目的运行效率(注意:此处说的不是服务器集群)
- 数据库服务器:运行数据库
- 缓存和消息服务器:负责处理大并发访问的缓存和消息
- 文件服务器:负责存储用户上传文件的服务器
- 应用服务器:负责部署应用
分服务器工作示意图
1. 文件服务器
- 单独启动一个Tomcat实例作为文件服务器
- 设置远程服务器端口号:Server、Connector
Tomcat:conf > server.xml
<!-- 8005改为8006 -->
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- 8080改为8090 -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
/>
- 远程服务器中设置非只读
Tomcat:conf > web.xml
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<!-- 增加readonly配置 -->
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
- webapps下创建一个upload目录
- 访问新实例
http://ip:8090
2. idea_Tomcat
web.xml
中设置非只读http://localhost:8080/upload/a.jpg
3. SC
<!-- 跨服务文件上传依赖 -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.19</version>
</dependency>
// 文件存储位置
private final static String FILE_SERVER = "http://127.0.0.1:8080/upload/";
@ResponseBody
@RequestMapping("/fileUploadServer")
public Map<String, String> fileUploadServer(MultipartFile headPhoto, HttpServletRequest req) throws IOException {
Map<String, String> map = new HashMap<>();
// 获取文件名
String originalFilename = headPhoto.getOriginalFilename();
// 避免文件名冲突,使用UUID替换文件名
String uuid = UUID.randomUUID().toString();
// 获取拓展名
String extendsName = originalFilename.substring(originalFilename.lastIndexOf("."));
// 新的文件名
String newFileName = uuid.concat(extendsName);
// 创建sun公司提供的jersey包中的client对象
Client client = Client.create();
WebResource resource = client.resource(FILE_SERVER + newFileName);
// 文件保存到另一个服务器上去了
resource.put(String.class, headPhoto.getBytes());
// 上传成功之后,把文件的名字和文件的类型返回给浏览器
map.put("message", "上传成功");
map.put("newFileName", newFileName);
map.put("filetype", headPhoto.getContentType());
return map;
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>playerAdd.jsp</title>
<style>
.progress {
width: 200px;
height: 10px;
border: 1px solid #ccc;
border-radius: 10px;
margin: 10px 0px;
overflow: hidden;
}
/* 初始状态设置进度条宽度为0px */
.progress > div {
width: 0px;
height: 100%;
background-color: yellowgreen;
transition: all .3s ease;
}
</style>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
$("#uploadFile").click(function () {
// 获取要上传的文件
// $("#photo")为jquery对象,获取dom对象
const photoFile = $("#photo")[0].files[0];
if (photoFile === undefined) {
alert("您还未选中文件")
return;
}
// 将文件装入FormData对象
const formData = new FormData();
formData.append("headPhoto", photoFile)
// ajax向后台发送文件
$.ajax({
type: "post",
data: formData,
url: "fileUploadServer",
processData: false,
contentType: false,
success: function (result) {
// 接收后台响应的信息
alert(result.message)
// 1. 图片回显
$("#headImg").attr("src", "http://127.0.0.1:8080/upload/" + result.newFileName);
// 2. 将文件类型和文件名放入form表单
$("#photoInput").val(result.newFileName)
$("#filetypeInput").val(result.filetype)
},
xhr: function () {
var xhr = new XMLHttpRequest();
// 使用XMLHttpRequest.upload监听上传过程,注册progress事件,打印回调函数中的event事件
xhr.upload.addEventListener('progress', function (e) {
// loaded代表上传了多少
// total代表总数为多少
var progressRate = (e.loaded / e.total) * 100 + '%';
// 通过设置进度条的宽度达到效果
$('.progress > div').css('width', progressRate);
})
return xhr;
}
})
})
})
</script>
</head>
<body>
<form action="addPlayer" method="get">
<p>账号:<input type="text" name="name"></p>
<p>密码:<input type="text" name="password"></p>
<p>昵称:<input type="text" name="nickname"></p>
<p>头像:</p>
<hr/>
<input id="photo" type="file"/>
<%-- 图片回显 --%>
<hr/>
<img id="headImg" style="width: 200px;height: 200px" alt="你还未上传图片"/>
<hr/>
<%-- 进度条 --%>
<div class="progress">
<div></div>
</div>
<a id="uploadFile" href="javascript:void(0)">立即上传</a>
<%-- 3. 使用隐藏的输入框存储文件名称和文件类型 --%>
<input id="photoInput" type="hidden" name="photo"/>
<input id="filetypeInput" type="hidden" name="filetype"/>
<p><input type="submit" value="注册"></p>
</form>
</body>
</html>
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Player implements Serializable {
private Integer id;
private String name;
private String password;
private String nickname;
private String photo;
private String filetype;
}
package com.listao.mvc.controller;
@Controller
public class PlayerController {
@Autowired
private PlayerService playerService;
@RequestMapping("/addPlayer")
public String addPlayer(Player player){
playerService.addPlayer(player);
return "redirect:/playerShow.jsp";
}
@RequestMapping("/getAllPlayer")
@ResponseBody
public List<Player> getAllPlayer(){
return playerService.getAllPlayer();
}
}
4. download
下载的基本流程
- 用户发起请求,请求要下载的资源。服务器根据请求,将其硬盘中的文件资源发送给浏览器的过程
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>playerShow.jsp</title>
<style>
#playerTable {
width: 50%;
border: 3px solid cadetblue;
margin: 0 auto;
text-align: center;
}
#playerTable th, td {
border: 1px solid rgb(128, 128, 128);
}
#playerTable img {
width: 100px;
height: 100px;
}
</style>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script>
$(function () {
$.ajax({
type: "get",
url: "getAllPlayer",
success: function (players) {
$.each(players, function (i, e) {
$("#playerTable").append('<tr>\n' +
' <td>' + e.id + '</td>\n' +
' <td>' + e.name + '</td>\n' +
' <td>' + e.password + '</td>\n' +
' <td>' + e.nickname + '</td>\n' +
' <td>\n' +
' <img src="http://127.0.0.1:8080/upload/' + e.photo + '" alt="图片未找到">\n' +
' </td>\n' +
' <td>\n' +
' <a href="fileDownload?photo=' + e.photo + '&filetype=' + e.filetype + '">下载</a>\n' +
' </td>\n' +
' </tr>')
})
}
})
})
</script>
</head>
<body>
<table id="playerTable" cellspacing="0xp" cellpadding="0px">
<tr>
<th>编号</th>
<th>用户名</th>
<th>密码</th>
<th>昵称</th>
<th>头像</th>
<th>操作</th>
</tr>
</table>
</body>
</html>
@RequestMapping("/fileDownload")
public void fileDownLoad(String photo, String filetype, HttpServletResponse response) throws IOException {
// 设置响应头
// 通知浏览器将数据保存到磁盘上,不在浏览器上直接解析
response.setHeader("Content-Disposition", "attachment;filename=" + photo);
// 通知浏览器下载的文件类型
response.setContentType(filetype);
// 获取一个文件的输入流
InputStream inputStream = new URL(FILE_SERVER + photo).openStream();
// 获取一个指向浏览器的输出流
ServletOutputStream outputStream = response.getOutputStream();
// 向浏览器响应文件
IOUtils.copy(inputStream, outputStream);
}