通过这篇文章你可以了解到:
[TOC]
首先我们需要准备好开发环境,本文测试环境是 SSM(Spring 4.3.9 + SpringMVC 4.3.9 + MyBatis 3.4.4) ,数据库为 MySQL 5.5,数据库连接池 C3P0 0.9.5.2,构建包 Maven 3.5.0,Tomcat 8.5。
限于篇幅原因,关于 SSM 框架的整合方法,在这篇文章中就不做详细的讲解啦,有关图片上传和下载的相关配置,我会特别标注出来说明的。
我们假定有这样一个很常见的需求场景:用户注册。
首先我们来做一下简单的业务分析,在注册页面,用户填写自己的相关信息,然后选择上传头像图片,注册成功后显示个人信息,并将图片显示在页面上。
一看就是一个很简单的需求吧,那我们就来做相应的数据准备工作吧。
数据库非常简单,就一张表:t_user
字段 | 类型 | 长度 | 主键 | 描述 |
---|---|---|---|---|
user_id | int | 11 | PK,自增 | 用户表主键 |
user_name | varchar | 50 | 用户名 | |
user_tel | varchar | 20 | 手机号 | |
user_password | varchar | 20 | 密码 | |
user_pic | varchar | 255 | 用户头像地址 |
对应数据库表 t_user 创建实体类:User
这里我使用 mybatis-generate 代码生成器根据 t_user 表结构自动生成实体类 和 Mybatis 的 mapper 文件。
User 实体类的代码如下(省略了 getter/setter):
package com.uzipi.entity;
public class User {
private Integer userId;
private String userName;
private String userTel;
private String userPassword;
private String userPic;
}
生成的 dao 层 java 代码如下:
package com.uzipi.dao;
import com.uzipi.entity.User;
import org.mybatis.spring.annotation.MapperScan;
@MapperScan // 允许 Spring 扫描该 Mapper
public interface UserMapper {
// 删除指定 key 的记录
int deleteByPrimaryKey(Integer userId);
// 插入一条记录(完整记录)
int insert(User record);
// 插入一条记录(对象中有值时写入字段,没有值的置空)
int insertSelective(User record);
// 查询指定 key 的记录
User selectByPrimaryKey(Integer userId);
// 将对象中的内容更新入库(对象中有值时更新字段,没有值的属性不修改)
int updateByPrimaryKeySelective(User record);
// 将对象中的内容更新入库(全属性)
int updateByPrimaryKey(User record);
}
生成的 mapper.xml 文件内容比较多,在文章里就不展示了,后面附件中提供了下载文件供参考。
4.0.0
com.uzipi
house
war
1.0-SNAPSHOT
house Maven Webapp
http://maven.apache.org
junit
junit
4.12
log4j
log4j
1.2.17
mysql
mysql-connector-java
5.1.24
com.mchange
c3p0
0.9.5.2
org.springframework
spring-webmvc
4.3.9.RELEASE
org.springframework
spring-aspects
4.3.9.RELEASE
org.springframework
spring-jdbc
4.3.9.RELEASE
commons-fileupload
commons-fileupload
1.3.1
com.fasterxml.jackson.core
jackson-databind
2.8.7
org.mybatis
mybatis
3.4.4
org.mybatis
mybatis-spring
1.3.1
javax.servlet
javax.servlet-api
3.1.0
provided
javax.servlet.jsp
jsp-api
2.2
provided
javax.servlet
jstl
1.2
house
框架的整合配置 xml 文件请查看附件。
这里我特别说明一下涉及到图片(文件)上传相关的 spring-mvc 配置:
package com.uzipi.controller;
import com.uzipi.entity.User;
import com.uzipi.service.UserService;
import com.uzipi.util.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
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.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService; // Spring 注入 UserService
/**
* 跳转到注册页面
* @param model
* @return
*/
@RequestMapping(value="/register", method = RequestMethod.GET)
public String register(Model model){
/*
为什么这里要 new 一个 User 对象?
因为我们在 JSP 页面中使用了 spring form 标签
spring form 标签的 modelAttribute 默认需要一个对象用于接收数据
这里我们是新增,所以用无参构造创建一个空对象(不是null)
*/
User user = new User();
model.addAttribute("user", user); // user 加入到 request 域
return "user/register"; // 跳转到 user/register.jsp 页面
}
/**
* 处理用户注册的表单请求
* @param user
* @param file
* @return
*/
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String doRegister(User user,
@RequestParam("imgFile") MultipartFile file,
Model model){
if (userService.saveRegister(user, file)){
model.addAttribute("user", user);
return "user/show"; // 注册成功,跳转到显示页面
}
return "redirect:/user/register"; // 注册失败,重定向到注册页面
}
/**
* 处理图片显示请求
* @param fileName
*/
@RequestMapping("/showPic/{fileName}.{suffix}")
public void showPicture(@PathVariable("fileName") String fileName,
@PathVariable("suffix") String suffix,
HttpServletResponse response){
File imgFile = new File(Constants.IMG_PATH + fileName + "." + suffix);
responseFile(response, imgFile);
}
/**
* 处理图片下载请求
* @param fileName
* @param response
*/
@RequestMapping("/downloadPic/{fileName}.{suffix}")
public void downloadPicture(@PathVariable("fileName") String fileName,
@PathVariable("suffix") String suffix,
HttpServletResponse response){
// 设置下载的响应头信息
response.setHeader("Content-Disposition",
"attachment;fileName=" + "headPic.jpg");
File imgFile = new File(Constants.IMG_PATH + fileName + "." + suffix);
responseFile(response, imgFile);
}
/**
* 响应输出图片文件
* @param response
* @param imgFile
*/
private void responseFile(HttpServletResponse response, File imgFile) {
try(InputStream is = new FileInputStream(imgFile);
OutputStream os = response.getOutputStream();){
byte [] buffer = new byte[1024]; // 图片文件流缓存池
while(is.read(buffer) != -1){
os.write(buffer);
}
os.flush();
} catch (IOException ioe){
ioe.printStackTrace();
}
}
}
在 Controller 中,有几个地方是需要我们注意的,不然会遇到坑:
MultipartFile
接口来接收,最好是用注解 @RequestParam("inputName")
指明该文件对应表单中的 input 标签的 name 属性。如果 name 都是同名的,可以使用 MultipartFile []
文件数组来接收。{fileName}.{suffix}
这段代码将图片名和图片的后缀区分开,因为 GET 方式的 URL 请求地址中的 "." 点号会被当作通配符处理掉,有多种方式可以解决。我这种方式是一种,你也可以用 "." 转义字符来避免其通配符的作用。response.setHeader("Content-Disposition","attachment;fileName=" + "headPic.jpg");
当设置了该响应头时,使用 response
输出流将会被当作附件提供给客户端下载,反之就是将流中的内容输出到页面上。buffer
的大小,过小会导致下载速度变慢,过大会占用较多的带宽,需要考虑平衡。package com.uzipi.service;
import com.uzipi.dao.UserMapper;
import com.uzipi.entity.User;
import com.uzipi.util.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@Service
public class UserService {
@Autowired
private UserMapper userMapper; // Spring 注入 UserMapper 对象
/**
* 用户注册,记录用户信息并处理上传的图片
* @param user
* @param file
* @return
*/
public boolean saveRegister(User user, MultipartFile file){
if (file != null){
// 原始文件名
String originalFileName = file.getOriginalFilename();
// 获取图片后缀
String suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
// 生成图片存储的名称,UUID 避免相同图片名冲突,并加上图片后缀
String fileName = UUID.randomUUID().toString() + suffix;
// 图片存储路径
String filePath = Constants.IMG_PATH + fileName;
File saveFile = new File(filePath);
try {
// 将上传的文件保存到服务器文件系统
file.transferTo(saveFile);
// 记录服务器文件系统图片名称
user.setUserPic(fileName);
} catch (IOException e) {
e.printStackTrace();
}
}
// 持久化 user
return userMapper.insertSelective(user) > 0;
}
/**
* 查找指定 key 的 user 对象
* @param userId
* @return
*/
public User findByUserId(int userId){
return userMapper.selectByPrimaryKey(userId);
}
}
Service 层中要注意的几个问题:
spring-mvc.xml
中配置临时目录的位置。但存储在临时目录中的图片并不长久,重启服务器之后会被清理掉。我们可以利用 MultipartFile
接口提供的 transferTo(File dest)
方法将临时文件转移到我们设置的文件系统目录中。页面没有加样式,仅实现了功能,所以不是很好看啦。
注册页面中使用了 spring form 标签。关于 spring form 标签,这里简单提一下,在没有 减轻 JSP 代码工作量
的需求前提下,还是推荐使用原生的 form 表单标签,因为 spring form 最终还是会被渲染成原生的 form 标签的样子,中间多了一道转换,必然会降低些许页面的渲染速度。
register.jsp 代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
用户注册
register.jsp 需要注意的地方:
enctype="multipart/form-data"
,这大家应该都知道吧。modelAttribute="user"
这段属性。因此我们要在跳转到该页面之前,往 request 域中添加一个 user
对象(名字可以自定义),如果不写上这个属性,SpringMVC会默认给一个 "command"。modelAttribute
对象中有引用类型的成员属性,恰好我们要填写的表单元素中有一个值正好是该引用对象的属性值,我们可以直接使用 xxx.xxx
的形式指明该属性值,提交表单时,springMVC 会自动帮助我们封装该属性对象。用户显示页面比较简单,主要是为了区分出 “显示图片” 和 “下载图片” 两种请求。
show.jsp 代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
用户个人信息
个人信息
用户名:${user.userName}
手机号:${user.userTel}
下载头像图片
页面比较简单,就一个地方可以说明下,可能有的同学还不太明白:
我在 标签中加入了
这段代码,目的是为了将当前页面的相对位置定位到 webapp 的根目录下,这样可以避免请求跳转之后,出现同一个 JSP 页面的相对路径不一样的情况。
百度网盘: https://pan.baidu.com/s/1c3SSvj6 密码:goma
转自:https://www.jianshu.com/p/5fc5b10fc8dc