【SpringBoot】19 文件/图片下载(MySQL + Thymeleaf)
Git仓库
https://gitee.com/Lin_DH/system
介绍
从 MySQL 中,下载保存的 blob 格式的文件。
代码实现
第一步:配置文件
application.yml
spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: rootpassword: rootmybatis-plus:type-aliases-package: com.lm.system.commonmapper-locations: classpath:com.lm.system/mapper/*Mapper.xmlcheck-config-location: trueconfiguration:#日志实现,不配置不会输出SQL日志log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
第二步:编写实体类
SysFile.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
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 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_TIMEFROM 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_TIMEFROM t_fileWHERE ID = #{id}</select></mapper>
第五步:编写下载页面
download.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
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 {@Resourceprivate 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")@ResponseBodypublic 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) //10MBthrow 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")@ResponseBodypublic 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")@ResponseBodypublic 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 文件。

项目结构图

相关文章:
【SpringBoot】19 文件/图片下载(MySQL + Thymeleaf)
Git仓库 https://gitee.com/Lin_DH/system 介绍 从 MySQL 中,下载保存的 blob 格式的文件。 代码实现 第一步:配置文件 application.yml spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT8datasource:driver-class-name: com.mysql.…...
陪诊问诊APP开发实战:基于互联网医院系统源码的搭建详解
时下,开发一款功能全面、用户体验良好的陪诊问诊APP成为了医疗行业的一大热点。本文将结合互联网医院系统源码,详细解析陪诊问诊APP的开发过程,为开发者提供实用的开发方案与技术指导。 一、陪诊问诊APP的背景与功能需求 陪诊问诊APP核心目…...
Spark 中 RDD 的诞生:原理、操作与分区规则
Spark 的介绍与搭建:从理论到实践-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交:本地与集群模式全解析-CSDN博客 Spark on YARN:Spark集群模式之Yarn模式的原…...
c++构造与析构
构造函数特性 名称与类名相同:构造函数的名称必须与类名完全相同,并且不能有返回值类型(包括void)。 自动调用:构造函数在对象实例化时自动调用,不需要手动调用。 初始化成员变量:构造函数的主…...
C++(函数重载,引用,nullptr)
1.函数重载 C⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。传参时会自动匹配传入的参数,对应该函数的形参类型,进行函数调用,这样C函数调⽤就表现出了多态⾏为&a…...
django+postgresql
PostgreSQL概述 PostgreSQL 是一个功能强大的开源关系数据库管理系统(RDBMS),以其高度的稳定性、扩展性和社区支持而闻名。PostgreSQL 支持 SQL 标准并具有很多先进特性,如 ACID 合规、复杂查询、外键支持、事务处理、表分区、JS…...
前端滚动锚点(点击后页面滚动到指定位置)
三个常用方案:1.scrollintoView 把调用该方法的元素滚动到屏幕的指定位置,中间,底部,或者顶部 优点:方便,只需要获取元素然后调用 缺点:不好精确控制,只能让元素指定滚动到中间&…...
使用SSL加密465端口发送邮件
基于安全考虑,云虚拟主机的25端口默认封闭,如果您有发送邮件的需求,建议使用SSL加密端口(465端口)来对外发送邮件。本文通过提供.NET、PHP和ASP样例来介绍使用SSL加密端口发送邮件的方法,其他语言的实现思路…...
一些面试题总结(一)
1、string为什么是不可变的,有什么好处 原因: 1、因为String类下的value数组是用final修饰的,final保证了value一旦被初始化,就不可改变其引用。 2、此外,value数组的访问权限为 private,同时没有提供方…...
泄露的文档显示 Google 似乎意识到了 Tensor 处理器存在过热问题
Google 知道其 Tensor 芯片存在一些问题,尤其是在过热和电池寿命方面,显然他们正在努力通过即将推出的代号为"Malibu"的 Tensor G6 来解决这一问题。 Android Authority 泄露的幻灯片显示,过热是基于 Tensor 的 Pixel 手机退换货的…...
python爬虫案例——网页源码被加密,解密方法全过程
文章目录 1、任务目标2、网页分析3、代码编写1、任务目标 目标网站:https://jzsc.mohurd.gov.cn/data/company,该网站的网页源码被加密了,用于本文测验 要求:解密该网站的网页源码,请求网站并返回解密后的明文数据,网页内容如下: 2、网页分析 进入网站,打开开发者模式,…...
2.4_SSRF服务端请求伪造
SSRF服务端请求伪造 定义:服务端请求伪造。是一种攻击者构造请求后,交由服务端发起请求的漏洞; 产生原理:该服务器提供了从其他服务器获取数据的功能,但没有对用户提交的数据做严格校验; 利用条件&#…...
数据分析反馈:提升决策质量的关键指南
内容概要 在当今快节奏的商业环境中,数据分析与反馈已成为提升决策质量的重要工具。数据分析不仅能为企业提供全面的市场洞察,还能帮助管理层深入了解客户需求与行为模式。掌握数据收集的有效策略和工具,企业能够确保获得准确且相关的信息&a…...
一步步安装deeponet的详细教学
1.deepoent官网如下: https://github.com/lululxvi/deeponet 需要下载依赖 1.python3 2.DeepXDE(这里安装DeepXDE<0.11.2,这个最方便) Optional: For CNN, install Matlab and TensorFlow 1; for Seq2Seq, install PyTorch࿰…...
Devops业务价值流:版本发布最佳实践
敏捷开发中,版本由多个迭代构建而成,每个迭代都是产品进步的一环。当版本最后一个迭代完成时,便启动了至关重要的上线流程。版本发布流程与规划流程相辅相成,确保每个迭代在版本中有效循环执行,最终达成产品目标。 本…...
背包问题(三)
文章目录 一、二维费用的背包问题二、潜水员三、机器分配四、开心的金明五、有依赖的背包问题 一、二维费用的背包问题 题目链接 #include<iostream> #include<algorithm> using namespace std; const int M 110; int n,m,kg; int f[M][M];int main() {cin >…...
linux之调度管理(2)-调度器 如何触发运行
一、调度器是如何在程序稳定运行的情况下进行进程调度的 1.1 系统定时器 因为我们主要讲解的是调度器,而会涉及到一些系统定时器的知识,这里我们简单讲解一下内核中定时器是如何组织,又是如何通过通过定时器实现了调度器的间隔调度。首先我们…...
深入理解 Vue 3 中的 Props
深入理解 Vue 3 中的 Props Vue 3 引入了 Composition API 等新特性,组件的定义和使用也变得更为灵活。而在组件通信中,Props(属性)扮演了重要角色,帮助父组件向子组件传递数据,形成单向的数据流动&#x…...
校园周边美食探索及分享平台
摘要: 美食一直是与人们日常生活息息相关的产业。传统的电话订餐或者到店消费已经不能适应市场发展的需求。随着网络的迅速崛起,互联网日益成为提供信息的最佳俱渠道和逐步走向传统的流通领域,传统的美食业进而也面临着巨大的挑战࿰…...
内网对抗-信息收集篇SPN扫描DC定位角色区域定性服务探针安全防护凭据获取
知识点: 1、信息收集篇-网络架构-出网&角色&服务&成员 2、信息收集篇-安全防护-杀毒&防火墙&流量监控 3、信息收集篇-密码凭据-系统&工具&网站&网络域渗透的信息收集: 在攻防演练中,当完成边界突破后进入内…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
