当前位置: 代码网 > it编程>编程语言>Java > SpringBoot4实现邮件发送与文件上传下载的常见业务场景实战教学

SpringBoot4实现邮件发送与文件上传下载的常见业务场景实战教学

2026年09月20日 Java 我要评论
本篇《spring boot 4 学习从入门到大神》邮件通知、文件上传下载是几乎所有后台系统的刚需。本文将系统讲解 spring boot 4 邮件整合、模板邮件、文件上传下载、大小与格式限制,并延伸

本篇《spring boot 4 学习从入门到大神》邮件通知、文件上传下载是几乎所有后台系统的刚需

本文将系统讲解 spring boot 4 邮件整合、模板邮件、文件上传下载、大小与格式限制,并延伸到大文件分片上传、断点续传与 minio 对象存储,帮你覆盖从简单到复杂的真实业务场景。

一、邮件发送:从简单到模板化

引入依赖

<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-mail</artifactid>
</dependency>
<!-- 模板邮件(可选) -->
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-thymeleaf</artifactid>
</dependency>

邮箱配置(application.yml)

qq 邮箱​ 为例(其他邮箱类似):

spring:
  mail:
    host: smtp.qq.com
    port: 587
    username: 123456@qq.com
    password: xxxx              # 邮箱授权码,不是登录密码
    protocol: smtp
    default-encoding: utf-8
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true
          ssl:
            enable: false

授权码获取:qq 邮箱 → 设置 → 账号 → 开启 pop3/smtp → 获取授权码

发送简单文本邮件

@service
public class mailservice {

    @autowired
    private javamailsender mailsender;

    @value("${spring.mail.username}")
    private string from;

    public void sendsimplemail(string to, string subject, string content) {
        simplemailmessage message = new simplemailmessage();
        message.setfrom(from);
        message.setto(to);
        message.setsubject(subject);
        message.settext(content);
        mailsender.send(message);
    }
}
mailservice.sendsimplemail(
        "test@test.com",
        "测试邮件",
        "这是一封来自 spring boot 4 的测试邮件"
);

发送 html 邮件

public void sendhtmlmail(string to, string subject, string htmlcontent) throws messagingexception {
    mimemessage message = mailsender.createmimemessage();
    mimemessagehelper helper = new mimemessagehelper(message, true, "utf-8");

    helper.setfrom(from);
    helper.setto(to);
    helper.setsubject(subject);
    helper.settext(htmlcontent, true); // true = html

    mailsender.send(message);
}
string html = "<h1>欢迎注册</h1><p>请点击链接激活账号:<a href='#'>激活</a></p>";
sendhtmlmail("test@test.com", "账号激活", html);

发送带附件的邮件

public void sendattachmentmail(string to, string subject, string content, string filepath)
        throws messagingexception {

    mimemessage message = mailsender.createmimemessage();
    mimemessagehelper helper = new mimemessagehelper(message, true, "utf-8");

    helper.setfrom(from);
    helper.setto(to);
    helper.setsubject(subject);
    helper.settext(content, true);

    filesystemresource file = new filesystemresource(new file(filepath));
    helper.addattachment("附件.pdf", file);

    mailsender.send(message);
}

模板邮件(thymeleaf,生产推荐 ⭐)

模板:templates/mail/welcome.html

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h1>欢迎您,<span th:text="${username}"></span>!</h1>
<p>您的注册邮箱是:<span th:text="${email}"></span></p>
<p>请点击以下链接激活账号:</p>
<a th:href="${activeurl}" rel="external nofollow" >激活账号</a>
</body>
</html>

发送模板邮件

@service
public class mailservice {

    @autowired
    private javamailsender mailsender;

    @autowired
    private springtemplateengine templateengine;

    public void sendtemplatemail(string to, string username, string email, string activeurl)
            throws messagingexception {

        context context = new context();
        context.setvariable("username", username);
        context.setvariable("email", email);
        context.setvariable("activeurl", activeurl);

        string content = templateengine.process("mail/welcome", context);

        mimemessage message = mailsender.createmimemessage();
        mimemessagehelper helper = new mimemessagehelper(message, true, "utf-8");
        helper.setfrom(from);
        helper.setto(to);
        helper.setsubject("账号激活邮件");
        helper.settext(content, true);

        mailsender.send(message);
    }
}

模板邮件优点:

  • 样式可控
  • 逻辑与视图分离
  • 支持复杂业务数据

异步发送邮件(强烈推荐)

@async("taskexecutor")
public completablefuture<void> sendtemplatemailasync(...) throws messagingexception {
    sendtemplatemail(...);
    return completablefuture.completedfuture(null);
}

避免邮件发送阻塞主流程(如注册接口)。

二、文件上传:从基础到安全

基础文件上传(multipartfile)

@restcontroller
@requestmapping("/files")
public class filecontroller {

    @postmapping("/upload")
    public result<?> upload(@requestparam("file") multipartfile file) throws ioexception {

        string originalfilename = file.getoriginalfilename();
        string suffix = originalfilename.substring(originalfilename.lastindexof("."));
        string filename = uuid.randomuuid() + suffix;

        string uploaddir = system.getproperty("user.dir") + "/uploads/";
        file dest = new file(uploaddir + filename);
        if (!dest.getparentfile().exists()) {
            dest.getparentfile().mkdirs();
        }

        file.transferto(dest);

        return result.success(map.of(
                "filename", filename,
                "originalname", originalfilename,
                "size", file.getsize()
        ));
    }
}

文件上传配置(大小限制)

spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 50mb        # 单文件大小
      max-request-size: 100mb    # 单次请求总大小
      file-size-threshold: 2mb   # 超过则写入磁盘

文件类型校验(安全必做 ⭐)

private static final list<string> allowed_types =
        arrays.aslist("jpg", "jpeg", "png", "pdf", "doc", "docx");

private boolean isallowed(string filename) {
    string suffix = filename.substring(filename.lastindexof(".") + 1).tolowercase();
    return allowed_types.contains(suffix);
}

永远不要相信前端传的文件名和 mime 类型。

多文件上传

@postmapping("/upload/multiple")
public result<?> uploadmultiple(@requestparam("files") multipartfile[] files) throws ioexception {
    list<string> filenames = new arraylist<>();
    for (multipartfile file : files) {
        if (!file.isempty()) {
            // 保存文件...
            filenames.add(file.getoriginalfilename());
        }
    }
    return result.success(filenames);
}

三、文件下载与断点续传

基础文件下载

@getmapping("/download")
public void download(httpservletresponse response) throws ioexception {

    file file = new file("uploads/test.pdf");
    response.setcontenttype("application/pdf");
    response.setheader("content-disposition",
            "attachment;filename=" + urlencoder.encode(file.getname(), "utf-8"));

    try (inputstream in = new fileinputstream(file);
         outputstream out = response.getoutputstream()) {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = in.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }
    }
}

断点续传(range 支持,大文件必备 ⭐)

@getmapping("/download/range")
public void downloadwithrange(httpservletrequest request, httpservletresponse response)
        throws ioexception {

    file file = new file("uploads/bigfile.zip");
    long filelength = file.length();

    long start = 0;
    long end = filelength - 1;

    string range = request.getheader("range");
    if (range != null && range.startswith("bytes=")) {
        string[] ranges = range.substring(6).split("-");
        start = long.parselong(ranges[0]);
        if (ranges.length > 1 && !ranges[1].isempty()) {
            end = long.parselong(ranges[1]);
        }
    }

    long contentlength = end - start + 1;

    response.setstatus(httpservletresponse.sc_partial_content);
    response.setcontenttype("application/octet-stream");
    response.setheader("content-range", "bytes " + start + "-" + end + "/" + filelength);
    response.setheader("accept-ranges", "bytes");
    response.setcontentlengthlong(contentlength);

    try (randomaccessfile raf = new randomaccessfile(file, "r");
         outputstream out = response.getoutputstream()) {

        raf.seek(start);
        byte[] buffer = new byte[1024];
        long need = contentlength;
        int len;
        while (need > 0 &&
               (len = raf.read(buffer, 0, (int) math.min(buffer.length, need))) != -1) {
            out.write(buffer, 0, len);
            need -= len;
        }
    }
}

支持浏览器/下载工具断点续传。

四、大文件分片上传(生产级方案)

分片上传流程

前端:

  • 计算文件 md5
  • 将文件切成 5mb 分片
  • 依次上传分片(带 index)
  • 所有分片上传完成后,通知后端合并

后端:

  • 接收分片,按 md5 + index 存储
  • 记录分片上传状态
  • 合并分片,校验 md5

分片上传接口

@postmapping("/upload/chunk")
public result<?> uploadchunk(
        @requestparam("file") multipartfile chunk,
        @requestparam("md5") string md5,
        @requestparam("index") int index,
        @requestparam("total") int total) throws ioexception {

    string chunkdir = "uploads/chunks/" + md5 + "/";
    file dir = new file(chunkdir);
    if (!dir.exists()) {
        dir.mkdirs();
    }

    chunk.transferto(new file(chunkdir + index));

    return result.success("分片 " + index + " 上传成功");
}

合并分片

@postmapping("/upload/merge")
public result<?> mergechunks(@requestparam("md5") string md5,
                             @requestparam("filename") string filename) throws ioexception {

    string chunkdir = "uploads/chunks/" + md5 + "/";
    file dir = new file(chunkdir);
    file[] chunks = dir.listfiles();
    if (chunks == null) {
        return result.error("分片不存在");
    }

    arrays.sort(chunks, comparator.comparingint(f -> integer.parseint(f.getname())));

    string mergepath = "uploads/" + filename;
    try (fileoutputstream fos = new fileoutputstream(mergepath)) {
        byte[] buffer = new byte[1024];
        for (file chunk : chunks) {
            try (fileinputstream fis = new fileinputstream(chunk)) {
                int len;
                while ((len = fis.read(buffer)) != -1) {
                    fos.write(buffer, 0, len);
                }
            }
        }
    }

    // 删除分片
    for (file chunk : chunks) {
        chunk.delete();
    }
    dir.delete();

    return result.success("文件合并完成");
}

五、对象存储:minio 实战(企业级推荐 ⭐)

为什么不用本地磁盘?

本地磁盘对象存储
容量有限无限扩容
单机故障高可用
难做 cdn天然支持 cdn
难迁移云厂商通用

生产环境:本地 + 对象存储(minio / oss / cos / s3)

引入 minio

<dependency>
    <groupid>io.minio</groupid>
    <artifactid>minio</artifactid>
    <version>8.5.9</version>
</dependency>

配置 minio

minio:
  endpoint: http://localhost:9000
  access-key: minioadmin
  secret-key: minioadmin
  bucket-name: test-bucket

minio 配置类

@configuration
public class minioconfig {

    @value("${minio.endpoint}")
    private string endpoint;

    @value("${minio.access-key}")
    private string accesskey;

    @value("${minio.secret-key}")
    private string secretkey;

    @bean
    public minioclient minioclient() {
        return minioclient.builder()
                .endpoint(endpoint)
                .credentials(accesskey, secretkey)
                .build();
    }
}

上传文件到 minio

@service
public class minioservice {

    @autowired
    private minioclient minioclient;

    @value("${minio.bucket-name}")
    private string bucketname;

    public string upload(multipartfile file) throws exception {

        string filename = uuid.randomuuid() + "-" + file.getoriginalfilename();

        minioclient.putobject(
                putobjectargs.builder()
                        .bucket(bucketname)
                        .object(filename)
                        .stream(file.getinputstream(), file.getsize(), -1)
                        .contenttype(file.getcontenttype())
                        .build()
        );

        return endpoint + "/" + bucketname + "/" + filename;
    }
}

六、spring boot 4 新变化

  • 文件上传与虚拟线程结合,高并发下吞吐更高
  • multipartfile 与 aot 编译兼容性更好
  • 邮件发送支持更严格的 tls 配置

七、常见坑位总结

解决
邮件发送阻塞接口@async 异步发送
邮件被当成垃圾邮件配置 spf / dkim
文件上传大小超限配置 max-file-size
恶意文件上传校验后缀 + 内容类型
大文件上传失败分片上传 + 断点续传
本地磁盘满了使用 minio / oss
文件下载中文乱码urlencoder.encode

八、本篇总结

  1. 邮件发送:简单 → html → 模板 → 异步
  2. 文件上传必须做:大小限制 + 类型校验
  3. 大文件:分片上传 + 断点续传
  4. 生产环境文件存储:优先对象存储(minio)
  5. 下载接口支持 range,提升用户体验
  6. 所有耗时操作(邮件 / 文件处理)异步化

到此这篇关于springboot4实现邮件发送与文件上传下载的常见业务场景实战教学的文章就介绍到这了,更多相关springboot邮件发送与文件上传下载内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com