【SpringBoot】19 文件/图片下载(MySQL + Thymeleaf)

Git仓库

https://gitee.com/Lin_DH/system

介绍

从 MySQL 中,下载保存的 blob 格式的文件。

代码实现

第一步:配置文件

application.yml

yml 复制代码
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: root
    password: root
 mybatis-plus:
  type-aliases-package: com.lm.system.common
  mapper-locations: classpath:com.lm.system/mapper/*Mapper.xml
  check-config-location: true
  configuration:
    #日志实现,不配置不会输出SQL日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

第二步:编写实体类

SysFile.java

java 复制代码
package com.lm.system.common;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * @author DUHAOLIN
 * @date 2024/10/17
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysFile {

    @TableId(value = "id", type = IdType.INPUT)
    private Integer id;
    private String name;
    private String format;
    private byte[] data;
    private long size;
    private Date createTime;

}

第三步:编写dao层接口

SysFileMapper.java

java 复制代码
package com.lm.system.mapper;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.lm.system.common.SysFile;

import java.util.List;

/**
 * @author DUHAOLIN
 * @date 2024/10/17
 */
//@DS("system")
public interface SysFileMapper {

    int insertFile(SysFile file);

    List<SysFile> queryFiles();

    SysFile queryFileById(Integer id);

}

第四步:编写dao层实现SQL

SysFileMapper.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lm.system.mapper.SysFileMapper">

    <resultMap id="files" type="com.lm.system.common.SysFile">
        <id property="id" column="id" jdbcType="INTEGER" />
        <result property="name" column="name" jdbcType="VARCHAR" />
        <result property="format" column="format" jdbcType="VARCHAR" />
        <result property="data" column="data" jdbcType="BLOB" />
        <result property="size" column="size" jdbcType="DOUBLE" />
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP" />
    </resultMap>

    <insert id="insertFile" parameterType="com.lm.system.common.SysFile">
        INSERT INTO t_file (NAME, FORMAT, DATA, SIZE)
        VALUES (#{name}, #{format}, #{data}, #{size})
    </insert>

    <select id="queryFiles" resultMap="files">
        SELECT ID, NAME, FORMAT, `SIZE`, CREATE_TIME
        FROM t_file
    </select>

<!--    SELECT ID, NAME, FORMAT,-->
<!--    (CASE WHEN `SIZE` <![CDATA[<]]> 1024 THEN CONCAT(`SIZE`, ' b')-->
<!--    WHEN `SIZE` <![CDATA[>=]]> 1024 AND `SIZE` <![CDATA[<]]> 1024000 THEN CONCAT(ROUND(`SIZE` / 1024, 2), ' KB')-->
<!--    WHEN `SIZE` <![CDATA[>=]]> 1024000 AND `SIZE` <![CDATA[<]]> 1024000000 THEN CONCAT(ROUND(`SIZE` / 1024000, 2), ' MB')-->
<!--    END) `SIZE`,-->
<!--    DATE_FORMAT(CREATE_TIME, '%Y-%m-%d %h:%i:%s') CREATE_TIME-->
<!--    FROM t_file-->

    <select id="queryFileById" resultType="com.lm.system.common.SysFile">
        SELECT ID, NAME, FORMAT, DATA, `SIZE`, CREATE_TIME
        FROM t_file
        WHERE ID = #{id}
    </select>

</mapper>

第五步:编写下载页面

download.html

html 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head lang="en">
    <meta charset="UTF-8" />
    <title>文件下载页面</title>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
</head>
<body>
<h1>文件下载页面</h1>
<table border="1">
    <!--  表头  -->
    <thead>
        <tr>
            <th>#</th>
            <th>ID</th>
            <th>文件名</th>
            <th>格式</th>
            <th>文件大小</th>
            <th>创建时间</th>
            <th>操作</th>
        </tr>
    </thead>
    <!--  表体  -->
    <tbody>
        <tr th:each="file,stats:${files}">
            <td th:text="${stats.count}"></td>
            <td th:text="${file.id}"></td>
            <td th:text="${file.name}"></td>
            <td th:text="${file.format}"></td>
            <td th:if="${file.size} < 1024" th:text="${#numbers.formatDecimal(file.size, 0, 0)} + ' b'"></td>
            <td th:if="${file.size} >= 1024 and ${file.size} < 1024000" th:text="${#numbers.formatDecimal(file.size/1024, 0, 0)} + ' KB'"></td>
            <td th:if="${file.size} >= 1024000 and ${file.size} < 1024000000" th:text="${#numbers.formatDecimal(file.size/1024000, 0, 0)} + ' MB'"></td>
            <td th:text="${#dates.format(file.createTime, 'yyyy-MM-dd HH:mm:ss')}"></td>
            <td>
                <button th:onclick="'downloadFile(\'' + ${file.id} + '\');'">下载</button>
            </td>
        </tr>
    </tbody>
</table>

<script th:inline="javascript">
    let files = [[${files}]];

    function downloadFile(id) {
        $.ajax({
            url: '/downloadFile/' + id,
            type: 'GET',
            responseType: 'blob',
            success: function (res, status, xhr) {
                let link = document.createElement("a");
                link.href = this.url;
                let filename = xhr.getResponseHeader("Content-Disposition").split("attachment; filename=")[1];
                link.setAttribute("download", filename);
                link.click();
                window.URL.revokeObjectURL(link.href); //释放内存
            },
            error: function (xhr, status, error) {
                console.log("error", error);
            }
        });
    }
</script>

</body>
</html>

第七步:编写文件 Controller 类

FileController.java

java 复制代码
package com.lm.system.controller;

import com.lm.system.common.SysFile;
import com.lm.system.exception.FileException;
import com.lm.system.mapper.SysFileMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;


/**
 * @author DUHAOLIN
 * @date 2024/10/15
 */
@Controller
public class FileController {

    @Resource
    private SysFileMapper fileMapper;

    private final static String FILE_FORMAT_TXT = "txt";
    private final static String FILE_FORMAT_JPEG = "jpg";
    private final static String FILE_FORMAT_PNG = "png";


    @Value("${file.upload.path}")
    private String path;

    @GetMapping("uploadPage")
    public String uploadPage() {
        return "upload";
    }

    @PostMapping("upload")
    @ResponseBody
    public String upload(@RequestParam("file") MultipartFile file) throws IOException {
        //校验文件
        try {
            String[] fileFormats = { FILE_FORMAT_TXT };
            checkFile(file, fileFormats);
        } catch (FileException e) {
            e.printStackTrace();
            return e.getMessage();
        }

        String filename = path + file.getOriginalFilename().replace(FILE_FORMAT_TXT, "_" + System.currentTimeMillis() + FILE_FORMAT_TXT);
        java.io.File newFile = new java.io.File(filename);
        Files.copy(file.getInputStream(), newFile.toPath());
        return "新文件已生成," + newFile.getAbsolutePath();
    }

    private void checkFile(MultipartFile file, String[] fileFormats) {
        //校验文件大小
        checkSize(file.getSize());

        //校验文件名
        checkFilename(file.getOriginalFilename());

        //校验文件格式
        checkFileFormat(file.getOriginalFilename(), fileFormats);
    }

    private void checkSize(long size) {
        if (size > 10485760L) //10MB
            throw new FileException("文件大于10MB");
    }

    private void checkFilename(String filename) {
        if (!StringUtils.hasText(filename))
            throw new FileException("文件名有误");
    }

    private void checkFileFormat(String filename, String[] fileFormats) {
        int i = filename.lastIndexOf(".");
        String suffix = filename.substring(i + 1); //文件后缀
        long c = Arrays.stream(fileFormats).filter(s -> s.equals(suffix)).count(); //判断是否存在该文件后缀
        if (c < 1) throw new FileException("文件格式有误,该文件类型为:" + suffix);
    }

    @GetMapping("multiFileUploadPage")
    public String multiFileUploadPage() {
        return "multiFileUpload";
    }

    @PostMapping("multiFileUpload")
    @ResponseBody
    public String multiFileUpload(@RequestParam("files") MultipartFile[] files) throws IOException {
        StringBuilder sb = new StringBuilder();

        for (MultipartFile file : files) {
            //校验文件
            boolean b = true;
            try {
                String[] fileFormats = new String[] { FILE_FORMAT_TXT };
                checkFile(file, fileFormats);
            } catch (FileException e) {
                e.printStackTrace();
                sb.append(file.getOriginalFilename()).append(e.getMessage()).append("<br>");
                b = false;
            }

            if (b) { //文件格式不对则不进行上传
                String filename = path + file.getOriginalFilename().replace(FILE_FORMAT_TXT, "_" + System.currentTimeMillis() + FILE_FORMAT_TXT);
                java.io.File newFile = new java.io.File(filename);
                Files.copy(file.getInputStream(), newFile.toPath());
                sb.append("新文件已生成,").append(newFile.getAbsolutePath()).append("<br>");
            }
        }

        return sb.toString();
    }

    @GetMapping("uploadToDBPage")
    public String uploadToDBPage() {
        return "uploadToDB";
    }

    @PostMapping("uploadToDB")
    @ResponseBody
    public String uploadToDB(@RequestParam("file") MultipartFile file) throws IOException {
        //校验文件
        try {
            String[] fileFormats = { FILE_FORMAT_TXT, FILE_FORMAT_JPEG, FILE_FORMAT_PNG };
            checkFile(file, fileFormats);
        } catch (FileException e) {
            e.printStackTrace();
            return e.getMessage();
        }

        //构建存储对象
        SysFile sysFile = SysFile.builder()
                .name(getFilename(file.getOriginalFilename()))
                .format(getFileFormat(file.getOriginalFilename()))
                .data(file.getBytes())
                .size(file.getSize())
                .build();

        int i = fileMapper.insertFile(sysFile);

        return i > 0 ? "添加成功" : "添加失败";
    }

    private String getFileFormat(String filename) {
        int i = filename.lastIndexOf(".");
        return filename.substring(i + 1); //文件后缀
    }

    /**
     * 获取不带后缀的文件名
     */
    private String getFilename(String originalFilename) {
        int i = originalFilename.lastIndexOf(".");
        return originalFilename.substring(0, i);
    }


    @GetMapping("downloadPage")
    public String downloadPage(ModelMap map) {
        List<SysFile> files = fileMapper.queryFiles();
        map.addAttribute("files", files);
        return "download";
    }

    @GetMapping("downloadFile/{id}")
    public void downloadFile(@PathVariable Integer id, HttpServletResponse response) throws IOException {
        SysFile sysFile = fileMapper.queryFileById(id);
        String filename = sysFile.getName() + "_" + System.currentTimeMillis() + "." + sysFile.getFormat();
        //指定下载文件名
        response.setHeader("Content-Disposition", "attachment; filename=" + filename);
        response.setCharacterEncoding("UTF-8");
        //告知浏览器文件大小
        response.addHeader("Content-Length", String.valueOf(sysFile.getSize()));
        //内容类型为通用类型,表示二进制数据流
        response.setContentType(getContentType(sysFile.getFormat()));

        InputStream is = new ByteArrayInputStream(sysFile.getData());
        OutputStream os = response.getOutputStream();
        byte[] buffer = new byte[1024];
        int l = 0;
        while ((l = is.read(buffer)) != -1) {
            os.write(buffer, 0, l);
        }
        is.close();
        os.close();
    }

    private String getContentType(String format) {
        switch (format) {
            case FILE_FORMAT_TXT:
                return MediaType.TEXT_PLAIN_VALUE;
            case FILE_FORMAT_JPEG:
                return MediaType.IMAGE_JPEG_VALUE;
            case FILE_FORMAT_PNG:
                return MediaType.IMAGE_PNG_VALUE;
            default:
                return MediaType.APPLICATION_OCTET_STREAM_VALUE; //通用类型
        }
    }

}

效果图

成功下载 txt 文件。

成功下载 jpg 文件。

成功下载 png 文件。

项目结构图

相关推荐
FJW0208149 小时前
关系型数据库大王Mysql——DDL语句操作示例
数据库·mysql
无名之辈J9 小时前
系统崩溃(OOM)
后端
来旺9 小时前
互联网大厂Java面试全解析及三轮问答专项
java·数据库·spring boot·安全·缓存·微服务·面试
码农刚子9 小时前
ASP.NET Core Blazor简介和快速入门 二(组件基础)
javascript·后端
间彧9 小时前
Java ConcurrentHashMap如何合理指定初始容量
后端
摇滚侠9 小时前
Spring Boot 3零基础教程,yml文件中配置和类的属性绑定,笔记15
spring boot·redis·笔记
thginWalker9 小时前
使用Spring Boot构建消息通信层
spring boot
lang201509289 小时前
Spring Boot 外部化配置最佳实践指南
java·spring boot
catchadmin9 小时前
PHP8.5 的新 URI 扩展
开发语言·后端·php
少妇的美梦10 小时前
Maven Profile 教程
后端·maven