• Springboot 图片上传及图片回显


    SpringBoot 文件上传

    案例:图书管理(SpringBoot+Thymeleaf+SpringData-JPA)

    ​ 添加图书:图书基本信息及封面图片的上传及入库

    ​ 图书详细:图书基本信息和封面图片显示

    • SpringData JPA 使用
    • 上传页面的三个必须要求
    • 图片上传接收和处理
    • 资源映射(图片回显)
    • 全局异常处理

    数据库脚本

    1. CREATE DATABASE wdzldb`
    2. USE `wdzldb`;
    3. DROP TABLE IF EXISTS `book`;
    4. CREATE TABLE `book` (
    5. `bookid` int(11) NOT NULL AUTO_INCREMENT,
    6. `bookName` varchar(120) DEFAULT NULL,
    7. `price` float DEFAULT NULL,
    8. `pubDate` date DEFAULT NULL,
    9. `author` varchar(20) DEFAULT NULL,
    10. `version` int(11) DEFAULT '0',
    11. `state` int(11) DEFAULT NULL,
    12. `pic` varchar(50) DEFAULT NULL,
    13. PRIMARY KEY (`bookid`)
    14. ) ENGINE=InnoDB AUTO_INCREMENT=157 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    15. /*Data for the table `book` */
    16. insert into `book`(`bookid`,`bookName`,`price`,`pubDate`,`author`,`version`,`state`,`pic`) values
    17. (22,'Java实战开发3',34,'2021-07-28','王磊',1,1,NULL),
    18. (53,'Java实战开发666',120,'2021-07-24','诸葛亮',0,1,NULL),
    19. (61,'Java实战开发1',39,'2021-07-29','王磊',0,1,NULL),
    20. (62,'Java实战开发1',39,'2021-07-29','王磊',0,0,NULL),
    21. (66,'Java实战开发1',39,'2021-07-29','王磊',0,0,NULL),
    22. (67,'SpringCloud微服务实战',45,'2021-08-11','王帆',0,0,NULL),
    23. (68,'SPringBoot整合JDBC',56,'2021-08-11','周瑜',0,1,NULL),
    24. (70,'SpringBoot入门与提高',78,'2021-08-11','曹操',0,1,NULL),
    25. (71,'Java实战开发5',100,'2021-07-23','诸葛亮',0,0,NULL),
    26. (72,'Java虚拟机深入',23,'2021-08-11','赵紫阳',0,1,NULL),
    27. (73,'深入学习Java虚拟机',69,'2021-08-05','黄盖',0,0,NULL),
    28. (74,'JSP开发技术',34,'2021-08-12','王超',0,1,NULL)

    框架搭建

    先搭建基本框架,完成业务层、DAO 层、pojo 等模块编写,并调试通过 springdata 正常使用。

    pom.xml 依赖

    web 启动器、thymeleaf 模板启动器、springdata 启动器及数据库驱动等

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-data-jpaartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.springframework.bootgroupId>
    7. <artifactId>spring-boot-starter-thymeleafartifactId>
    8. dependency>
    9. <dependency>
    10. <groupId>org.springframework.bootgroupId>
    11. <artifactId>spring-boot-starter-webartifactId>
    12. dependency>
    13. <dependency>
    14. <groupId>mysqlgroupId>
    15. <artifactId>mysql-connector-javaartifactId>
    16. <scope>runtimescope>
    17. dependency>
    18. <dependency>
    19. <groupId>org.projectlombokgroupId>
    20. <artifactId>lombokartifactId>
    21. <optional>trueoptional>
    22. dependency>

    配置文件

    application.yml 数据源的基本信息配置、jpa 是否显示 sql 及 下划线等

    1. spring:
    2. datasource:
    3. username: root
    4. password: root
    5. url: jdbc:mysql://127.0.0.1:3306/wdzldb?allowMultiQueries=true&useUnicode=true&allowPublicKeyRetrieval=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    6. driver-class-name: com.mysql.cj.jdbc.Driver
    7. jpa:
    8. show-sql: true
    9. hibernate:
    10. naming:
    11. physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

    application.properties

    下面多数是在使用阿里的初始化工具时,自动生成的

    除了下面的上传保存的路径是自定义的

    1. # 应用名称
    2. spring.application.name=springboot_other
    3. # 应用服务 WEB 访问端口
    4. server.port=8080
    5. # THYMELEAF (ThymeleafAutoConfiguration)
    6. # 开启模板缓存(默认值: true )
    7. spring.thymeleaf.cache=false
    8. # 检查模板是否存在,然后再呈现
    9. spring.thymeleaf.check-template=true
    10. # 检查模板位置是否正确(默认值 :true )
    11. spring.thymeleaf.check-template-location=true
    12. #Content-Type 的值(默认值: text/html )
    13. spring.thymeleaf.content-type=text/html
    14. # 开启 MVC Thymeleaf 视图解析(默认值: true )
    15. spring.thymeleaf.enabled=true
    16. # 模板编码
    17. spring.thymeleaf.encoding=UTF-8
    18. # 要被排除在解析之外的视图名称列表,⽤逗号分隔
    19. spring.thymeleaf.excluded-view-names=
    20. # 要运⽤于模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 默认值: HTML5)
    21. spring.thymeleaf.mode=HTML
    22. # 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
    23. spring.thymeleaf.prefix=classpath:/templates/
    24. # 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
    25. spring.thymeleaf.suffix=.html
    26. #上传的绝对路径
    27. file.upload.path=d://save/images/
    28. #绝对路径下的相对路径
    29. file.upload.relativePath=/images/
    30. #文件上传大小限制
    31. spring.servlet.multipart.max-file-size=2048000

    实体类

    注意:日期和图片处理

    1. @Data
    2. @Entity(name = "book") //要求必须有@Id 也
    3. @ApiModel(value = "图书实体", description = "图书中明细属性")
    4. public class Book {
    5. @Id
    6. @GeneratedValue(strategy = GenerationType.IDENTITY) //注意:默认的是序列,针对Oracle的
    7. private Integer bookId;
    8. @ApiModelProperty(value = "图书名")
    9. private String bookName;
    10. @ApiModelProperty(value = "图书作者")
    11. private String author;
    12. private Float price;
    13. @Column(name = "pic")
    14. private String picpath; // 封面
    15. @DateTimeFormat(pattern = "YYYY-MM-dd")
    16. private Date pubDate; //出版日期
    17. }

    DAO

    dao 接口直接使用 springdata 提供的统一接口 JpaRepository ,其中已经包含了基本的操作。也可以按约定规则自己定义其他方法

    注意:继承接口时需要指定泛型

    1. public interface IBookDao extends JpaRepository {
    2. List queryBooksByBookName(String bookName);
    3. List findBooksByPriceBetween(Float min, Float max);
    4. List findBooksByBookNameLike(String bookName);
    5. List findAllByPriceOrderByPrice(float price);
    6. @Query(
    7. "select bookId,bookName,price,author from Book where bookName like :bookname"
    8. )
    9. Object[] queryBook(@Param("bookname") String bookName);
    10. //HQL 语句 select book from Book book where ...
    11. @Query("from Book where bookName like :bookname")
    12. List queryBooks(@Param("bookname") String bookName);
    13. }

    Service

    接口和实现类

    1. public interface IBookService {
    2. void add(Book book);
    3. void delete(Integer bookId);
    4. Book detail(Integer bookId);
    5. List queryAll();
    6. void update(Book book);
    7. }
    1. package com.wdzl.service.impl;
    2. import com.wdzl.dao.IBookDao;
    3. import com.wdzl.pojo.Book;
    4. import com.wdzl.service.IBookService;
    5. import java.util.List;
    6. import org.springframework.beans.factory.annotation.Autowired;
    7. import org.springframework.stereotype.Service;
    8. /**
    9. * @Author: zhang
    10. * @Date:2022/8/12
    11. * @Description:
    12. */
    13. @Service
    14. public class BookService implements IBookService {
    15. @Autowired
    16. private IBookDao bookDao;
    17. @Override
    18. public void add(Book book) {
    19. System.out.println(book.getBookId());
    20. bookDao.save(book); // 注意: 如果对象在数据库中存在的,执行修改。
    21. System.out.println(book.getBookId());
    22. }
    23. @Override
    24. public void delete(Integer bookId) {
    25. bookDao.deleteById(bookId);
    26. }
    27. @Override
    28. public Book detail(Integer bookId) {
    29. return bookDao.findById(bookId).get();
    30. }
    31. @Override
    32. public List<Book> queryAll() {
    33. return bookDao.findAll();
    34. }
    35. @Override
    36. public void update(Book book) {
    37. //如果对象是存在时,就是修改操作,如果不存在则插入操作
    38. bookDao.save(book);
    39. }
    40. }

    到这里,就可以使用单元测试来测试 springdata 是否能正常使用了。

    文件上传

    添加页面

    页面必须

    1. 1.method必须是post 2.enctype="multipart/form-data" 必须 3.<input
    2. type="file"
    3. />
    4. 必须

    static 目录 下的 add.html:

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8" />
    5. <title>Titletitle>
    6. head>
    7. <body>
    8. <h2>添加图书h2>
    9. <form action="add" method="post" enctype="multipart/form-data">
    10. <p>图书名字:<input name="bookName" />p>
    11. <p>图书价格:<input name="price" />p>
    12. <p>图书作者:<input name="author" />p>
    13. <p>出版日期:<input name="pubDate" type="date" />p>
    14. <p>图书封面:<input name="pic" type="file" />p>
    15. <p><input type="submit" value="保存" />p>
    16. form>
    17. body>
    18. html>

    控制器

    注意文件上传处理:单独上传、文件名重命名、保存路径的配置等

    1. package com.wdzl.controller;
    2. import com.wdzl.pojo.Book;
    3. import com.wdzl.service.IBookService;
    4. import com.wdzl.util.FileUtils;
    5. import java.io.File;
    6. import java.io.FileOutputStream;
    7. import java.io.IOException;
    8. import java.util.List;
    9. import org.springframework.beans.factory.annotation.Autowired;
    10. import org.springframework.beans.factory.annotation.Value;
    11. import org.springframework.stereotype.Controller;
    12. import org.springframework.ui.ModelMap;
    13. import org.springframework.web.bind.annotation.ExceptionHandler;
    14. import org.springframework.web.bind.annotation.GetMapping;
    15. import org.springframework.web.bind.annotation.PostMapping;
    16. import org.springframework.web.bind.annotation.RequestMapping;
    17. import org.springframework.web.multipart.MultipartFile;
    18. /**
    19. * @Author: zhang
    20. * @Date:2022/8/12
    21. * @Description:
    22. *
    23. */
    24. @Controller
    25. public class BookController {
    26. @Autowired
    27. private IBookService bookService;
    28. @Value("${file.upload.savepath}")
    29. private String savePath; //保存文件根目录
    30. @Value("${file.upload.relativePath}")
    31. private String relativePath;
    32. /**
    33. * 日期处理
    34. * 文件上传
    35. * @param book
    36. * @return
    37. */
    38. @PostMapping("add")
    39. public String add(Book book, MultipartFile pic) {
    40. System.out.println("============add()========");
    41. String oriName = pic.getOriginalFilename(); //原始文件命名
    42. //判断目录是否存在并创建
    43. File rootDir = new File(savePath);
    44. if (rootDir.exists() == false) {
    45. rootDir.mkdirs();
    46. }
    47. if (!pic.isEmpty()) {
    48. //文件名
    49. String fileName = FileUtils.rename(oriName);
    50. File saveFile = new File(rootDir, fileName);
    51. //转存到指定文件中
    52. try {
    53. pic.transferTo(saveFile);
    54. System.out.println(">>>>>>文件保存在:" + saveFile.getAbsolutePath());
    55. } catch (IOException e) {
    56. e.printStackTrace();
    57. }
    58. // 文件相对路径 用来入库和回显
    59. fileName = relativePath + fileName;
    60. book.setPicpath(fileName);
    61. }
    62. //入库
    63. bookService.add(book);
    64. return "redirect:list"; // /list
    65. }
    66. @GetMapping("list")
    67. public String list(ModelMap modelMap) {
    68. List list = bookService.queryAll();
    69. modelMap.put("booklist", list);
    70. return "list"; ///templates/list.html
    71. }
    72. @GetMapping("detail")
    73. public String detail(Integer bookId, ModelMap modelMap) {
    74. Book book = bookService.detail(bookId);
    75. modelMap.put("book", book);
    76. return "detail";
    77. }
    78. }

    列表页面

    添加成功后,跳转到列表页面显示,下面使用的 thymeleaf 遍历显示

    注意:下面图片路径、链接等处理

    templates 下的 list.html

    1. html>
    2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
    3. <head>
    4. <meta charset="UTF-8" />
    5. <title>图书列表title>
    6. <style type="text/css" rel="stylesheet">
    7. div {
    8. margin: 30px;
    9. text-align: center;
    10. }
    11. style>
    12. head>
    13. <body>
    14. <a href="add.html">添加图书a>
    15. <hr />
    16. <div>
    17. <table width="75%">
    18. <thead>
    19. <tr bgcolor="#556b2f">
    20. <th>序号th>
    21. <th>书名th>
    22. <th>作者th>
    23. <th>价格th>
    24. <th>出版日期th>
    25. <th>操作th>
    26. tr>
    27. thead>
    28. <tbody>
    29. <tr th:each="book,st:${booklist}" th:bgcolor="${st.even?'#aac':''}">
    30. <td th:text="${st.count}">td>
    31. <td>
    32. <a
    33. th:href="${'detail?bookId='+book.bookId}"
    34. th:text="${book.bookName}"
    35. >a>
    36. td>
    37. <td th:text="${book.author}">td>
    38. <td th:text="${book.price}">td>
    39. <td th:text="${#dates.format(book.pubDate,'yyyy年MM月dd日')}">td>
    40. <td>
    41. <a th:href="${'del?bookId='+book.bookId}">删除a>
    42. td>
    43. tr>
    44. tbody>
    45. table>
    46. div>
    47. body>
    48. html>

    运行测试问题

    到此,可以通过前端的 add.html 来实现添加和上传操作了。

    注意:默认文件上传大小是 1M,大于 1M 的会 500 异常。

    可以通过配置修改默认文件大小限制:

    1. #文件上传大小限制
    2. spring.servlet.multipart.max-file-size=2048000

    全局异常处理

    在项目发布运行中,不希望直接显示 500 异常页面时,可以配置全局异常解析器来进行处理

    异常处理在 springboot 中有多种方式,下面介绍两种

    1. @ControllerAdvice + @ExceptionHandler

    位置:在启动类同包及子包下定义类

    1. @ControllerAdvice
    2. public class GlobalExceptionHander {
    3. @ExceptionHandler(Exception.class)
    4. public String doException(Exception ex) { // 不能使用model 无法传参到页面显示
    5. System.out.println(">>>>==异常了:" + ex.toString());
    6. return "error"; // 转发到 error.html 页面
    7. }
    8. }

    上面的 @ControllerAdvice 注解中已经包含 @Component 注解,所以直接会被 spring 扫描加入容器中。

    2. @Configuration+SimpleMappingExceptionResolver

    1. /**
    2. * @Author: zhang
    3. */
    4. @Configuration
    5. public class ApplicationConfig {
    6. /**
    7. * 全局异常配置
    8. * 页面可以通过 exception对象来获取
    9. */
    10. @Bean
    11. public SimpleMappingExceptionResolver doException() {
    12. SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
    13. Properties properties = new Properties();
    14. properties.setProperty("java.lang.Exception", "error"); //映射异常类型和转发的页面对应关系
    15. resolver.setExceptionMappings(properties);
    16. System.out.println("===========异常配置======");
    17. return resolver;
    18. }
    19. }

    上面是一个 @Configuration 标注的配置类。异常对象会被转发到页面。

    图片回显

    在完成上面的文件上传和图书信息添加之后,跳转到图书列表页面,可以通过图书名链接打开图书详细信息。

    但在显示图片静态资源时,路径问题导致图片无法正常显示。

    下面来再来处理下文件上传和回显下载或显示问题

    1. 回顾保存方式

    先来回顾上传时对于图片保存路径的设置

    首先我们先在配置文件中自定义了两个路径:绝对路径和相对路径

    1. #上传的绝对路径, 文件保存的真正的目录位置
    2. file.upload.path=d://save/images/
    3. #绝对路径下的相对路径 用于前端请求调用的逻辑地址,默认是不能直接使用的
    4. file.upload.relativePath=/images/

    在控制器保存图片时,使用上面地址保存图片和入库记录

    1. @PostMapping("add") //注意这里 pic 需要单独和页面元素对应,实体类中只是不同名的字符串用来存地址
    2. public String add(Book book, @ApiParam(value = "图片文件", required = true)MultipartFile pic){
    3. System.out.println(book+"===="+pic);
    4. String fileName = pic.getOriginalFilename();
    5. //重命名
    6. fileName = FileUtils.rename(fileName);
    7. // 保存到磁盘
    8. File saveDir = new File(savePath); //====================这里是真实保存目录
    9. if(saveDir.exists()==false){
    10. saveDir.mkdirs();//创建保存图片的目录
    11. }
    12. File saveFile = new File(saveDir,fileName);
    13. try {
    14. pic.transferTo(saveFile);
    15. System.out.println("图片保存在:"+saveFile.getAbsolutePath());
    16. } catch (IOException e) {
    17. e.printStackTrace();
    18. }
    19. //保存的相对路径
    20. String picPath = relativePath + fileName; //==========这里逻辑目录 虚拟的
    21. System.out.println(">>>入库路径:"+picPath);
    22. book.setPicpath(picPath); //保存到实体类 入库
    23. //入库
    24. bookService.add(book);
    25. return "redirect:list";
    26. }

    首先从上面代码中可以看到,保存的磁盘的目录和入库的路径是不同的,默认是不对应不能访问的。

    页面中使用的路径为数据库中的相对逻辑路径

    <img th:src="${book.picpath}" width="200" />
    1. html>
    2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
    3. <head>
    4. <meta charset="UTF-8" />
    5. <title>图书明细title>
    6. head>
    7. <body>
    8. <div>
    9. <p>
    10. <img th:src="${book.picpath}" width="200" />
    11. p>
    12. <p>书名:<span th:text="${book.bookName}">span>p>
    13. <p>作者:<span th:text="${book.author}">span>p>
    14. <p>价格:<span th:text="${book.price}">span>p>
    15. <p>
    16. 出版日期:<span
    17. th:text="${#dates.format(book.pubDate,'yyyy-MM-dd')}"
    18. >span>
    19. p>
    20. div>
    21. body>
    22. html>

    2. 配置资源映射

    如果需要能正常的访问,则使用下面的配置进行映射

    实现 WebMvcConfigurer 同时 标注 @Configuration

    1. /**
    2. * @Author: zhang
    3. * @Date:2022/8/11
    4. * @Description:
    5. */
    6. @Configuration
    7. public class ApplicationConfig implements WebMvcConfigurer {
    8. @Value("${file.upload.path}")
    9. private String savePath;
    10. @Value("${file.upload.relativePath}")
    11. private String relativePath;
    12. /**
    13. * 资源路径映射
    14. * 注意:路径前加 "file:/"
    15. * @param registry
    16. */
    17. @Override
    18. public void addResourceHandlers(ResourceHandlerRegistry registry) {
    19. System.out.println(relativePath + "==============" + savePath);
    20. registry
    21. .addResourceHandler(relativePath + "/**")
    22. .addResourceLocations("file:/" + savePath);
    23. }
    24. }

    通过上面的配置后,再去访问就可以正常显示图片了。

  • 相关阅读:
    Windows 进程的创建和终止
    【MATLAB】制作一幅钻石沿着圆周运动的动画
    机器人操作系统ROS(19) 雷达和摄像头融合的资料
    RE2文本匹配实战
    SpringBoot 自定义参数校验(5)
    python+pytest接口自动化之参数关联
    【计组】指令和运算1--《深入浅出计算机组成原理》(二)
    Linux下线程池(ThreadPool)
    Java多线程篇(10)——BlockingQueue(数组,链表,同步阻塞队列)
    python 多线程编程
  • 原文地址:https://blog.csdn.net/zp8126/article/details/126311855