SpringMVC之文件上传和下载

文件上传(Sevlet3.0之前)


1
2
3
4
5
6
7
8
<form action="${pageContext.request.contextPath}/user/insertOrUpdate" method="POST" enctype="multipart/form-data">
<input type="hidden" name="id" value="${user.id }"> <!-- 看不到,隐藏域 -->
<p>姓名<input type="text" name="name" value="${user.name }"></p>
<p>头像<img src="${user.headImage }">上传<input type="file" name="pic"></p>
<p>年龄<input type="text" name="age" value="${user.age }"></p>
<p>生日<input type="text" name="bornDate" value='<fmt:formatDate value="${user.bornDate }" pattern="yyyy-MM-dd"></fmt:formatDate>' ></p>
<input type="submit" value="${empty user.id ? '添加' : '提交' }">
</form>

导入 jar 包:commons-fileuploadcommons-io

配置 mvc.xml

1
2
3
4
5
6
7
<!-- 配置文件上传解析器:bean 的id名是固定的 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸为 1MB -->
<property name="maxUploadSize">
<value>1048576</value>
</property>
</bean>

CommonsMultipartResolver 的 id名是固定的

如何使用:Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@RequestMapping("insertOrUpdate")
public String insertOrUpdate(User user, MultipartFile pic) throws IOException {
user = handleHeadPic(user, pic);
// 获取原来的路径的图片删除
if (user.getId() != null) {
// 更改
service.update(user);
} else {
// 新增
service.save(user);
}
return "redirect:/user/list";
}

/**
* 处理用户头像图片
* @param user
* @param pic
* @throws IOException
*/
private User handleHeadPic(User user, MultipartFile pic) throws IOException {
String baseDir = context.getRealPath("/"); // webapp/
String uploadDir = "/upload/";
if (user.getId() != null) {
// 删除原来的 imageurl 的图片资源
deleteUserHeadImgFile(user);
}
// 生成新的图片链接并保存图片
String uuid = UUID.randomUUID().toString();
String filename = uuid + "." + FilenameUtils.getExtension(pic.getOriginalFilename());
String headImgUrl = uploadDir + filename;
Files.copy(pic.getInputStream(), Paths.get(baseDir, headImgUrl));
user.setHeadImage(headImgUrl);
return user;
}

/**
* 删除用户头像图片资源
* @param user
*/
private void deleteUserHeadImgFile(User user) {
String baseDir = context.getRealPath("/"); // webapp/
String uploadDir = "/upload/";
User oldUser = service.get(user.getId());
String oldImageUrl = oldUser.getHeadImage();
System.out.println(Paths.get(baseDir, uploadDir));
if (StringUtil.isNotEmpty(oldImageUrl))
new File(baseDir + oldImageUrl).delete();
}

文件上传(Servlet3.0之后)


不需要在导入 commons-fileupload

mvc.xml

1
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>

需要在 web.xml 中配置 multipart-config。 对应的的表单提交的类型 multipart/form-data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<!-- 文件上传下载配置 -->
<multipart-config>
<!-- 最大文件尺寸1M -->
<max-file-size>1048576</max-file-size>
<!-- 最大请求体大小 -->
<max-request-size>1048576</max-request-size>
<file-size-threshold>10240</file-size-threshold>
</multipart-config>
</servlet>

即可使用,使用方式与3.0之前一样

Controller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@RequestMapping("/insertOrUpdate")
public String insertOrUpdate(Model model, User user, MultipartFile pic) throws IOException {
String baseDir = context.getRealPath("/");
// 获取连接字符串
String newUrl = getHeadImageUrlWithPic(pic);
String oldUrl = user.getHeadImg();
if (pic.getSize() > 0) {
user.setHeadImg(newUrl);
}
if (user.getId() != null) { // 编辑
service.update(user);
} else { // 添加
service.insert(user);
}
Files.copy(pic.getInputStream(), Paths.get(baseDir, user.getHeadImg()));
return "redirect:/user/list";
}


/**
* 根据 headImageUrl 删除图片
*
* @param imageUrl
*/
private void deleteImageByUrl(String imageUrl) {
String baseDir = context.getRealPath("/"); // webapp/
new File(baseDir + imageUrl).delete();
}

/**
* 获取上传 图片的虚拟路径
*
* @param pic
* @return
* @throws IOException
*/
private String getHeadImageUrlWithPic(MultipartFile pic) throws IOException {
String uploadDir = "/uploads/";
String uuid = UUID.randomUUID().toString();
String filename = uuid + "." + FilenameUtils.getExtension(pic.getOriginalFilename());
String headImageUrl = uploadDir + filename;
return headImageUrl;
}

文件下载


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@RequestMapping("/download")
public ResponseEntity<byte[]> downloadFile(@PathVariable String fileName, HttpServletRequest request) throws Exception {
fileName = fileName + ".xls";
// 1.拼接真实路径
String realPath = context.getRealPath("/") + "/" + fileName;
// 2.读取文件
File file = new File(realPath);

// 4.设置格式
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment", encodeChineseDownloadFileName(request, fileName));
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 5.返回下载
return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.OK);
}

/**
* Description: 根据不同浏览器处理中文字符
*
* @param request
* @param fileName
* @return
* @throws UnsupportedEncodingException
*/
private String encodeChineseDownloadFileName(HttpServletRequest request, String fileName)
throws UnsupportedEncodingException {
String resultFileName = "";
String agent = request.getHeader("User-Agent").toUpperCase();
if (null == agent) {
resultFileName = fileName;
} else {
if (agent.indexOf("FIREFOX") != -1 || agent.indexOf("CHROME") != -1) { // firefox, chrome
resultFileName = new String(fileName.getBytes("UTF-8"), "iso-8859-1");
} else { // ie7+
resultFileName = URLEncoder.encode(fileName, "UTF-8");
resultFileName = StringUtils.replace(resultFileName, "+", "%20"); // 替换空格
}
}
return resultFileName;
}

应用


上传下载头像


  1. 数据库字段 head_img 保存用户头像的 url 连接。
  2. 显示头像,直接加载对应的路径,<img id="headImg" height="50px" src='${not empty emp.headImage ? emp.headImage : "/uploads/default.jpg"}'></p>
  3. 新增上传头像:保存头像
  4. 更新头像:删除旧的头像,保存头像

流程,
保存图片
下面的代码保证上传新的图片后,会显示出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function changImg(e) {
for (var i = 0; i < e.target.files.length; i++) {
var file = e.target.files.item(i);
if (!(/^image\/.*$/i.test(file.type))) {
continue; //不是图片 就跳出这一次循环
}
//实例化FileReader API
var freader = new FileReader();
freader.readAsDataURL(file);
freader.onload = function (e) {
$("#headImg").attr("src", e.target.result);
}
}
}

<form class="form-horizontal" action="/employee/insertOrUpdate" method="post" id="editForm"
enctype="multipart/form-data">
<div class="form-group">
<label class="col-sm-2 control-label">头像:</label>
<div class="col-sm-6">
<p><img id="headImg" height="50px"
src='${not empty emp.headImage ? emp.headImage : "/uploads/default.jpg"}'></p>
<input type="file" class="form-control" name="pic" accept="image/*"
onchange="changImg(event)">
</div>
</div>
</form>

首先,form 表单必须使用 multipart/form-data 提交。

后台代码:使用 MultipartFile 来接收文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@RequestMapping("/insertOrUpdate")
@RequiresPermissions(value = {"Employee:insertOrUpdate", "员工更新"}, logical = Logical.OR)
@ResponseBody
public JsonResult insertOrUpdate(Model model, Employee e,
MultipartFile pic,
Long deptId,
Long[] ids) throws IOException {
JsonResult jsonResult = new JsonResult();
// 如果不是管理员 则角色不能为空
if (e.isAdmin() == false && (ids == null || ids.length == 0)) {
return jsonResult.mark("没有选择角色");
}
String newHeadImageUrl = null;
String oldHeadImageUrl = e.getHeadImage();
if (Optional.ofNullable(pic).filter(p -> p.getSize() > 0).isPresent()) {
// 用户上传了图片才保存
newHeadImageUrl = getHeadImageUrlWithPic(pic);
e.setHeadImage(newHeadImageUrl);
}

try {
Department dept = new Department();
dept.setId(deptId);
e.setDept(dept);
employeeService.save(e, ids);
List<String> permissions = permissionService.getsExpressionsByEmpId(e.getId());

Employee currentEmp = (Employee) SecurityUtils.getSubject().getPrincipal();
// 如果修改了当前用户的权限
if (currentEmp != null && currentEmp.getId() == e.getId()) {
crmRealm.clearCache();
}

// 保存成功才上传图片
if (newHeadImageUrl != null) {
// 上传了新图
if (oldHeadImageUrl != null) {
// 有旧图就删除旧图
deleteImageByUrl(oldHeadImageUrl);
}
Files.copy(pic.getInputStream(), Paths.get(context.getRealPath("/"), e.getHeadImage()));
}
return jsonResult;
} catch (Exception e1) {
return jsonResult.mark(e1.getMessage());
}
}

/**
* 根据 headImageUrl 删除图片
*
* @param imageUrl
*/
private void deleteImageByUrl(String imageUrl) {
String baseDir = context.getRealPath("/"); // webapp/
new File(baseDir + imageUrl).delete();
}

/**
* 获取上传 图片的虚拟路径
*
* @param pic
* @return
* @throws IOException
*/
private String getHeadImageUrlWithPic(MultipartFile pic) throws IOException {
String uploadDir = "/uploads/";
String uuid = UUID.randomUUID().toString();
String filename = uuid + "." + FilenameUtils.getExtension(pic.getOriginalFilename());
String headImageUrl = uploadDir + filename;
return headImageUrl;
}

导入、导出(excel)


首先不要忘记继承文件下载相关环境。然后下面是需求分析

面向对象的思想,每一个层级,都应该是一个对象

导出步骤:

  1. 查询所有员工
  2. 将员工数据写到 excel 文件中
  3. 创建工作薄
    • new HSSFWorkbook() 创建的是 xls,2003版本的
    • new XSSFWorkbook() 创建的是 xlsx,2007版本的
  4. 创建纸张
  5. 为每个员工创建行
  6. 在行中添加单元格
  7. 为单元格设置数据

导入 jar 包

1
2
3
4
5
6
<!--poi依赖,作用是操作Excel-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>

导出


相当于文件下载功能,下载 excel。

1
2
3
4
5
6
7
8
9
10
11
$('.btn-export').click(function () {
// href="/employee/exportXls" target="_blank"
let link = document.createElement('a');
link.href = '/employee/exportXls?' + $('#searchForm').serialize()
link.target = '_blank'
link.click()
$(link).remove()
})
<button type="button" class="btn btn-warning btn-export">
<span class="glyphicon glyphicon-export"></span> 导出
</button>

思路,创建 a 标签完成下载,拼接参数。

employeeController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 导出 excel
* @param model
* @param qe
*/
@RequestMapping("/exportXls")
@RequiresPermissions(value = {"Employee:exportXls", "员工导出"}, logical = Logical.OR)
public void exportXls(Model model, QueryEmployee qe, HttpServletResponse resp) throws IOException {
// 设置下载文件的名称
resp.setHeader("Content-Disposition","attachment;filename=employee.xls");
// 1. 根据条件查询数据库员工
qe.setDeptId(Optional.ofNullable(qe.getDeptId())
.filter(s -> s.intValue() > 0).orElse(null));
Workbook book = employeeService.exportXls(qe);
// 将员工数据写到 excel 文件中
book.write(resp.getOutputStream());
}

employeeServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public Workbook exportXls(QueryEmployee qe) {
List<Employee> employees = employeeMapper.selectAll(qe);
// 3. 创建工作薄 xlt
HSSFWorkbook book = new HSSFWorkbook();
// 4. 创建纸张
HSSFSheet sheet = book.createSheet("员工信息");
// 5. 为每个员工创建行
HSSFRow header = sheet.createRow(0);
header.createCell(0).setCellValue("用户名");
header.createCell(1).setCellValue("邮箱");
header.createCell(2).setCellValue("年龄");
// 6. 在行中添加单元格
for (int i = 0; i < employees.size(); i++) {
HSSFRow row = sheet.createRow(i + 1);
row.createCell(0).setCellValue(employees.get(i).getName());
row.createCell(1).setCellValue(employees.get(i).getEmail());
row.createCell(2).setCellValue(employees.get(i).getAge());
}
// 7. 为单元格设置数据
return book;
}

导入(excel)


导入步骤:

  1. 将员工数据写到 excel 文件中
  2. 上传 excel
  3. 根据 excel file 获取工作薄
  4. 获取纸张
  5. 获取行
  6. 遍历行,获取行中的单元格
  7. 创建员工对象,设置单元格中的值,然后插入数据

一般会给定一个模板文件,然后下载模板文件,填写数据之后,再导入。
导入模态框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 导入功能
$('.btn-import').click(function () {
$('#editModal').modal()
})

$('.btn_save').click(function () {
$('#editForm').submit()
})

$('#editForm').ajaxForm(function (data) {
$('#editModal').modal('hide')
if (data.success) {
$.messager.confirm("温馨提示", "导入成功", function () {
$('#searchForm').submit();
})
} else {
$.messager.alert("温馨提示", "导入失败")
}
})


body ... body

<#--导入模态框-->
<div class="modal fade" id="editModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">员工导入</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" action="/employee/importXls" method="post" id="editForm"
enctype="multipart/form-data">
<div class="form-group">
<div class="col-sm-6">
<input type="file" name="file" accept="application/vnd.ms-excel"><br/>
<a href="/template/employee_import.xls" class="btn btn-success">
<span class="glyphicon glyphicon-download-alt"></span> 下载模板
</a>
</div>

</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn_save">保存</button>
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
</div>
</div>
</div>
</div>

EmployeeController.java

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/importXls")
@ResponseBody
public JsonResult importXls(Model model, MultipartFile file) {
JsonResult jsonResult = new JsonResult();
try {
employeeService.importXls(file);
return jsonResult;
} catch (Exception e) {
return jsonResult.mark("上传失败");
}
}

EmployeeServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public void importXls(MultipartFile file) throws IOException {
// 创建 book
HSSFWorkbook book = new HSSFWorkbook(file.getInputStream());
// 获取纸张
HSSFSheet sheet = book.getSheet("员工信息");
// 获取行
for (int i = 0; i < sheet.getLastRowNum(); i++) {
// 获取单元格
HSSFRow row = sheet.getRow(i + 1);
// 获取值
String name = row.getCell(0).getStringCellValue();
String email = row.getCell(1).getStringCellValue();
int age = Integer.parseInt(row.getCell(2).getStringCellValue());
// 存储数据
Employee employee = new Employee();
Department department = new Department();
department.setId(2l);
employee.setDept(department);
employee.setName(name);
String password = new Md5Hash("1", name).toString();
employee.setPassword(password);
employee.setEmail(email);
employee.setAge(age);
employeeMapper.insert(employee);
}
}
文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2019/08/09/SpringMVC%E4%B9%8B%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E5%92%8C%E4%B8%8B%E8%BD%BD/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论