Java Web程序员

Spring MVC 实现图片上传、下载、显示

2018-01-28  本文已影响1909人  uzip柚子皮

通过这篇文章你可以了解到:

  1. 使用 SpringMVC 框架,上传图片,并将上传的图片保存到文件系统,并将图片路径持久化到数据库
  2. 在 JSP 页面上实现显示图片、下载图片

[TOC]

1. 准备工作

首先我们需要准备好开发环境,本文测试环境是 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 框架的整合方法,在这篇文章中就不做详细的讲解啦,有关图片上传和下载的相关配置,我会特别标注出来说明的。

我们假定有这样一个很常见的需求场景:用户注册。

首先我们来做一下简单的业务分析,在注册页面,用户填写自己的相关信息,然后选择上传头像图片,注册成功后显示个人信息,并将图片显示在页面上。

一看就是一个很简单的需求吧,那我们就来做相应的数据准备工作吧。

1.1 数据库表准备

数据库非常简单,就一张表:t_user

字段 类型 长度 主键 描述
user_id int 11 PK,自增 用户表主键
user_name varchar 50 用户名
user_tel varchar 20 手机号
user_password varchar 20 密码
user_pic varchar 255 用户头像地址

1.2 实体类 User 和 Mapper(DAO)

对应数据库表 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 文件内容比较多,在文章里就不展示了,后面附件中提供了下载文件供参考。

1.3 pom.xml 依赖包

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.uzipi</groupId>
  <artifactId>house</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>house Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <!-- junit 单元测试 -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>
    <!-- log4j 日志 -->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
    <!-- MySQL 数据库连接驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.24</version>
    </dependency>
    <!-- c3p0 数据库连接池 -->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>
    <!-- spring-webmvc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <!-- spring-aspects -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <!-- spring-jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <!-- 文件上传 -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!-- spring 支持的 json -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.8.7</version>
    </dependency>
    <!-- MyBatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.4</version>
    </dependency>
    <!-- MyBatis 与 Spring 整合 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!-- Servlet API需求包 -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <!-- JSP相关 -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2</version>
      <scope>provided</scope>
    </dependency>
    <!-- JSTL 标准标签库 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>house</finalName>
  </build>
</project>

1.4 SSM 框架的整合配置

框架的整合配置 xml 文件请查看附件。

这里我特别说明一下涉及到图片(文件)上传相关的 spring-mvc 配置:

<!-- 配置文件上传 -->
<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  <!-- 配置文件上传的最大体积 10M -->
  <property name="maxUploadSize" value="10240000"></property>
</bean>

2. 控制器 UserController

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 中,有几个地方是需要我们注意的,不然会遇到坑:

3. 业务层 UserService

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 层中要注意的几个问题:

4. JSP 页面

页面没有加样式,仅实现了功能,所以不是很好看啦。

4.1 用户注册页面 register.jsp

注册页面中使用了 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" %>
<!DOCTYPE html>
<html>
<head>
    <title>用户注册</title>
    <base href="<%=request.getContextPath()%>/"/>
    <style>
        li {list-style: none;}
    </style>
</head>
<body>
    <form:form action="user/register" method="post" enctype="multipart/form-data" modelAttribute="user">
        <li>
            <form:input path="userName" placeholder="用户名"/>
        </li>
        <li>
            <form:password path="userPassword" placeholder="密码"/>
        </li>
        <li>
            <form:input path="userTel" placeholder="手机号"/>
        </li>
        <li>
            <input type="file" name="imgFile" />
        </li>
        <li>
            <input type="submit" value="注册" />
        </li>
    </form:form>
</body>
</html>

register.jsp 需要注意的地方:

4.2 用户信息显示页面 show.jsp

用户显示页面比较简单,主要是为了区分出 “显示图片” 和 “下载图片” 两种请求。

show.jsp 代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <title>用户个人信息</title>
    <base href="<%=request.getContextPath()%>/"/>
    <style>
        li {list-style: none;}
    </style>
</head>
<body>
    <h4>个人信息</h4>
    <li>
        <!-- 头像显示 -->
        <img src="user/showPic/${user.userPic}" style="width:100px; height: 100px;"/>
    </li>
    <li>
        用户名:${user.userName}
    </li>
    <li>
        手机号:${user.userTel}
    </li>
    <li>
        <a href="user/downloadPic/${user.userPic}">下载头像图片</a>
    </li>
</body>
</html>

页面比较简单,就一个地方可以说明下,可能有的同学还不太明白:

我在 <head> 标签中加入了 <base href="<%=request.getContextPath()%>/"/> 这段代码,目的是为了将当前页面的相对位置定位到 webapp 的根目录下,这样可以避免请求跳转之后,出现同一个 JSP 页面的相对路径不一样的情况。

到这里,关于 SpringMVC 上传和下载图片的步骤就算结束啦。

如果各位同学在测试的过程中遇到什么问题,可以留言、邮箱(yotow@foxmail.com)或者QQ我(281901158)。

源代码当然是少不了的啦,java 代码和 SQL 文件打包在一起了。

百度网盘: https://pan.baidu.com/s/1c3SSvj6 密码:goma

上一篇 下一篇

猜你喜欢

热点阅读