首頁 > 軟體

基於SpringBoot實現圖片上傳及圖片回顯

2022-08-24 18:04:19

案例:圖書管理(SpringBoot+Thymeleaf+SpringData-JPA)

新增圖書:圖書基本資訊及封面圖片的上傳及入庫

圖書詳細:圖書基本資訊和封面圖片顯示

  • SpringData JPA 使用
  • 上傳頁面的三個必須要求
  • 圖片上傳接收和處理
  • 資源對映(圖片回顯)
  • 全域性例外處理

資料庫指令碼

CREATE DATABASE wdzldb`
 
USE `wdzldb`;
 
DROP TABLE IF EXISTS `book`;
 
CREATE TABLE `book` (
  `bookid` int(11) NOT NULL AUTO_INCREMENT,
  `bookName` varchar(120) DEFAULT NULL,
  `price` float DEFAULT NULL,
  `pubDate` date DEFAULT NULL,
  `author` varchar(20) DEFAULT NULL,
  `version` int(11) DEFAULT '0',
  `state` int(11) DEFAULT NULL,
  `pic` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`bookid`)
) ENGINE=InnoDB AUTO_INCREMENT=157 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
 
/*Data for the table `book` */
 
insert  into `book`(`bookid`,`bookName`,`price`,`pubDate`,`author`,`version`,`state`,`pic`) values
(22,'Java實戰開發3',34,'2021-07-28','王磊',1,1,NULL),
(53,'Java實戰開發666',120,'2021-07-24','諸葛亮',0,1,NULL),
(61,'Java實戰開發1',39,'2021-07-29','王磊',0,1,NULL),
(62,'Java實戰開發1',39,'2021-07-29','王磊',0,0,NULL),
(66,'Java實戰開發1',39,'2021-07-29','王磊',0,0,NULL),
(67,'SpringCloud微服務實戰',45,'2021-08-11','王帆',0,0,NULL),
(68,'SPringBoot整合JDBC',56,'2021-08-11','周瑜',0,1,NULL),
(70,'SpringBoot入門與提高',78,'2021-08-11','曹操',0,1,NULL),
(71,'Java實戰開發5',100,'2021-07-23','諸葛亮',0,0,NULL),
(72,'Java虛擬機器器深入',23,'2021-08-11','趙紫陽',0,1,NULL),
(73,'深入學習Java虛擬機器器',69,'2021-08-05','黃蓋',0,0,NULL),
(74,'JSP開發技術',34,'2021-08-12','王超',0,1,NULL)

框架搭建

先搭建基本框架,完成業務層、DAO 層、pojo 等模組編寫,並偵錯通過 springdata 正常使用。

pom.xml 依賴

web 啟動器、thymeleaf 模板啟動器、springdata 啟動器及資料庫驅動等

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
 
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

組態檔

application.yml 資料來源的基本資訊設定、jpa 是否顯示 sql 及 下劃線等

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

application.properties

下面多數是在使用阿里的初始化工具時,自動生成的

除了下面的上傳儲存的路徑是自定義的

# 應用名稱
spring.application.name=springboot_other
# 應用服務 WEB 存取埠
server.port=8080
# THYMELEAF (ThymeleafAutoConfiguration)
# 開啟模板快取(預設值: true )
spring.thymeleaf.cache=false
# 檢查模板是否存在,然後再呈現
spring.thymeleaf.check-template=true
# 檢查模板位置是否正確(預設值 :true )
spring.thymeleaf.check-template-location=true
#Content-Type 的值(預設值: text/html )
spring.thymeleaf.content-type=text/html
# 開啟 MVC Thymeleaf 檢視解析(預設值: true )
spring.thymeleaf.enabled=true
# 模板編碼
spring.thymeleaf.encoding=UTF-8
# 要被排除在解析之外的檢視名稱列表,⽤逗號分隔
spring.thymeleaf.excluded-view-names=
# 要運⽤於模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 預設值: HTML5)
spring.thymeleaf.mode=HTML
# 在構建 URL 時新增到檢視名稱前的字首(預設值: classpath:/templates/ )
spring.thymeleaf.prefix=classpath:/templates/
# 在構建 URL 時新增到檢視名稱後的字尾(預設值: .html )
spring.thymeleaf.suffix=.html
 
#上傳的絕對路徑
file.upload.path=d://save/images/
#絕對路徑下的相對路徑
file.upload.relativePath=/images/
#檔案上傳大小限制
spring.servlet.multipart.max-file-size=2048000

實體類

注意:日期和圖片處理

@Data
@Entity(name = "book") //要求必須有@Id 也
@ApiModel(value = "圖書實體", description = "圖書中明細屬性")
public class Book {
 
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY) //注意:預設的是序列,針對Oracle的
  private Integer bookId;
 
  @ApiModelProperty(value = "圖書名")
  private String bookName;
 
  @ApiModelProperty(value = "圖書作者")
  private String author;
 
  private Float price;
 
  @Column(name = "pic")
  private String picpath; // 封面
 
  @DateTimeFormat(pattern = "YYYY-MM-dd")
  private Date pubDate; //出版日期
}

DAO

dao 介面直接使用 springdata 提供的統一介面 JpaRepository ,其中已經包含了基本的操作。也可以按約定規則自己定義其他方法

注意:繼承介面時需要指定泛型

public interface IBookDao extends JpaRepository<Book, Integer> {
  List<Book> queryBooksByBookName(String bookName);
  List<Book> findBooksByPriceBetween(Float min, Float max);
  List<Book> findBooksByBookNameLike(String bookName);
 
  List<Book> findAllByPriceOrderByPrice(float price);
 
  @Query(
    "select bookId,bookName,price,author from Book where bookName like :bookname"
  )
  Object[] queryBook(@Param("bookname") String bookName);
 
  //HQL 語句  select book from Book book where ...
  @Query("from Book where bookName like :bookname")
  List<Book> queryBooks(@Param("bookname") String bookName);
}

Service

介面和實現類

public interface IBookService {
  void add(Book book);
  void delete(Integer bookId);
  Book detail(Integer bookId);
  List<Book> queryAll();
  void update(Book book);
}
package com.wdzl.service.impl;
 
import com.wdzl.dao.IBookDao;
import com.wdzl.pojo.Book;
import com.wdzl.service.IBookService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
/**
 * @Author: zhang
 * @Date:2022/8/12
 * @Description:
 */
@Service
public class BookService implements IBookService {
 
  @Autowired
  private IBookDao bookDao;
 
  @Override
  public void add(Book book) {
    System.out.println(book.getBookId());
    bookDao.save(book); // 注意: 如果物件在資料庫中存在的,執行修改。
    System.out.println(book.getBookId());
  }
 
  @Override
  public void delete(Integer bookId) {
    bookDao.deleteById(bookId);
  }
 
  @Override
  public Book detail(Integer bookId) {
    return bookDao.findById(bookId).get();
  }
 
  @Override
  public List<Book> queryAll() {
    return bookDao.findAll();
  }
 
  @Override
  public void update(Book book) {
    //如果物件是存在時,就是修改操作,如果不存在則插入操作
    bookDao.save(book);
  }
}

到這裡,就可以使用單元測試來測試 springdata 是否能正常使用了。

檔案上傳

新增頁面

頁面必須

1.method必須是post

2.enctype="multipart/form-data" 必須

3.<input  type="file"/>必須

static 目錄 下的 add.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body>
    <!--
    檔案上傳時:頁面必須
    1.method必須是post
    2.enctype="multipart/form-data"  必須
    3.<input type="file">  必須
-->
 
    <h2>新增圖書</h2>
    <form action="add" method="post" enctype="multipart/form-data">
      <p>圖書名字:<input name="bookName" /></p>
      <p>圖書價格:<input name="price" /></p>
      <p>圖書作者:<input name="author" /></p>
      <p>出版日期:<input name="pubDate" type="date" /></p>
      <p>圖書封面:<input name="pic" type="file" /></p>
      <p><input type="submit" value="儲存" /></p>
    </form>
  </body>
</html>

控制器

注意檔案上傳處理:單獨上傳、檔名重新命名、儲存路徑的設定等

package com.wdzl.controller;
 
import com.wdzl.pojo.Book;
import com.wdzl.service.IBookService;
import com.wdzl.util.FileUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
 
/**
 * @Author: zhang
 * @Date:2022/8/12
 * @Description:
 *
 */
@Controller
public class BookController {
 
  @Autowired
  private IBookService bookService;
 
  @Value("${file.upload.savepath}")
  private String savePath; //儲存檔案根目錄
 
  @Value("${file.upload.relativePath}")
  private String relativePath;
 
  /**
   * 日期處理
   * 檔案上傳
   * @param book
   * @return
   */
  @PostMapping("add")
  public String add(Book book, MultipartFile pic) {
    System.out.println("============add()========");
 
    String oriName = pic.getOriginalFilename(); //原始檔案命名
 
    //判斷目錄是否存在並建立
    File rootDir = new File(savePath);
    if (rootDir.exists() == false) {
      rootDir.mkdirs();
    }
    if (!pic.isEmpty()) {
      //檔名
      String fileName = FileUtils.rename(oriName);
 
      File saveFile = new File(rootDir, fileName);
      //轉存到指定檔案中
      try {
        pic.transferTo(saveFile);
        System.out.println(">>>>>>檔案儲存在:" + saveFile.getAbsolutePath());
      } catch (IOException e) {
        e.printStackTrace();
      }
 
      // 檔案相對路徑  用來入庫和回顯
      fileName = relativePath + fileName;
      book.setPicpath(fileName);
    }
 
    //入庫
    bookService.add(book);
    return "redirect:list"; // /list
  }
 
  @GetMapping("list")
  public String list(ModelMap modelMap) {
    List<Book> list = bookService.queryAll();
    modelMap.put("booklist", list);
    return "list"; ///templates/list.html
  }
 
  @GetMapping("detail")
  public String detail(Integer bookId, ModelMap modelMap) {
    Book book = bookService.detail(bookId);
    modelMap.put("book", book);
    return "detail";
  }
}

列表頁面

新增成功後,跳轉到列表頁面顯示,下面使用的 thymeleaf 遍歷顯示

注意:下面圖片路徑、連結等處理

templates 下的 list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>圖書列表</title>
    <style type="text/css" rel="stylesheet">
      div {
        margin: 30px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <a href="add.html">新增圖書</a>
    <hr />
    <div>
      <table width="75%">
        <thead>
          <tr bgcolor="#556b2f">
            <th>序號</th>
            <th>書名</th>
            <th>作者</th>
            <th>價格</th>
            <th>出版日期</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr th:each="book,st:${booklist}" th:bgcolor="${st.even?'#aac':''}">
            <td th:text="${st.count}"></td>
            <td>
              <a
                th:href="${'detail?bookId='+book.bookId}"
                th:text="${book.bookName}"
              ></a>
            </td>
            <td th:text="${book.author}"></td>
            <td th:text="${book.price}"></td>
            <td th:text="${#dates.format(book.pubDate,'yyyy年MM月dd日')}"></td>
            <td>
              <a th:href="${'del?bookId='+book.bookId}">刪除</a>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </body>
</html>

執行測試問題

到此,可以通過前端的 add.html 來實現新增和上傳操作了。

注意:預設檔案上傳大小是 1M,大於 1M 的會 500 異常。

可以通過設定修改預設檔案大小限制:

#檔案上傳大小限制
spring.servlet.multipart.max-file-size=2048000

全域性例外處理

在專案發布執行中,不希望直接顯示 500 異常頁面時,可以設定全域性異常解析器來進行處理

例外處理在 springboot 中有多種方式,下面介紹兩種

1. @ControllerAdvice + @ExceptionHandler

位置:在啟動類同包及子包下定義類

@ControllerAdvice
public class GlobalExceptionHander {
 
  @ExceptionHandler(Exception.class)
  public String doException(Exception ex) { // 不能使用model 無法傳參到頁面顯示
    System.out.println(">>>>==異常了:" + ex.toString());
    return "error"; // 轉發到 error.html 頁面
  }
}

上面的 @ControllerAdvice 註解中已經包含 @Component 註解,所以直接會被 spring 掃描加入容器中。

2. @Configuration+SimpleMappingExceptionResolver

/**
 * @Author: zhang
 */
@Configuration
public class ApplicationConfig {
 
  /**
   * 全域性異常設定
   * 頁面可以通過 exception物件來獲取
   */
  @Bean
  public SimpleMappingExceptionResolver doException() {
    SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
    Properties properties = new Properties();
    properties.setProperty("java.lang.Exception", "error"); //對映異常型別和轉發的頁面對應關係
    resolver.setExceptionMappings(properties);
    System.out.println("===========異常設定======");
    return resolver;
  }
}

上面是一個 @Configuration 標註的設定類。異常物件會被轉發到頁面。

圖片回顯

在完成上面的檔案上傳和圖書資訊新增之後,跳轉到圖書列表頁面,可以通過圖書名連結開啟圖書詳細資訊。

但在顯示圖片靜態資源時,路徑問題導致圖片無法正常顯示。

下面來再來處理下檔案上傳和回顯下載或顯示問題

1. 回顧儲存方式

先來回顧上傳時對於圖片儲存路徑的設定

首先我們先在組態檔中自定義了兩個路徑:絕對路徑和相對路徑

#上傳的絕對路徑, 檔案儲存的真正的目錄位置
file.upload.path=d://save/images/
#絕對路徑下的相對路徑  用於前端請求呼叫的邏輯地址,預設是不能直接使用的
file.upload.relativePath=/images/

在控制器儲存圖片時,使用上面地址儲存圖片和入庫記錄

@PostMapping("add") //注意這裡 pic 需要單獨和頁面元素對應,實體類中只是不同名的字串用來存地址
public String add(Book book, @ApiParam(value = "圖片檔案", required = true)MultipartFile pic){
    System.out.println(book+"===="+pic);
    String fileName = pic.getOriginalFilename();
    //重新命名
    fileName = FileUtils.rename(fileName);
    // 儲存到磁碟
    File saveDir = new File(savePath);  //====================這裡是真實儲存目錄
    if(saveDir.exists()==false){
        saveDir.mkdirs();//建立儲存圖片的目錄
    }
    File saveFile = new File(saveDir,fileName);
    try {
        pic.transferTo(saveFile);
        System.out.println("圖片儲存在:"+saveFile.getAbsolutePath());
    } catch (IOException e) {
        e.printStackTrace();
    }
 
    //儲存的相對路徑
    String picPath = relativePath + fileName;  //==========這裡邏輯目錄  虛擬的
    System.out.println(">>>入庫路徑:"+picPath);
    book.setPicpath(picPath); //儲存到實體類 入庫
 
 
    //入庫
    bookService.add(book);
    return "redirect:list";
}

首先從上面程式碼中可以看到,儲存的磁碟的目錄和入庫的路徑是不同的,預設是不對應不能存取的。

頁面中使用的路徑為資料庫中的相對邏輯路徑

<img th:src="${book.picpath}" width="200" />
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <title>圖書明細</title>
  </head>
  <body>
    <div>
      <p>
        <img th:src="${book.picpath}" width="200" />
      </p>
      <p>書名:<span th:text="${book.bookName}"></span></p>
      <p>作者:<span th:text="${book.author}"></span></p>
      <p>價格:<span th:text="${book.price}"></span></p>
      <p>
        出版日期:<span
          th:text="${#dates.format(book.pubDate,'yyyy-MM-dd')}"
        ></span>
      </p>
    </div>
  </body>
</html>

2. 設定資源對映

如果需要能正常的存取,則使用下面的設定進行對映

實現 WebMvcConfigurer 同時 標註 @Configuration

/**
 * @Author: zhang
 * @Date:2022/8/11
 * @Description:
 */
@Configuration
public class ApplicationConfig implements WebMvcConfigurer {
 
  @Value("${file.upload.path}")
  private String savePath;
 
  @Value("${file.upload.relativePath}")
  private String relativePath;
 
  /**
   * 資源路徑對映
   * 注意:路徑前加 "file:/"
   * @param registry
   */
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    System.out.println(relativePath + "==============" + savePath);
    registry
      .addResourceHandler(relativePath + "/**")
      .addResourceLocations("file:/" + savePath);
  }
}

通過上面的設定後,再去存取就可以正常顯示圖片了。

以上就是基於SpringBoot實現圖片上傳及圖片回顯的詳細內容,更多關於SpringBoot圖片上傳 回顯的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com