笔记代码

分割线

文件传输

参考: 【狂神说 Java】JavaWeb-文件上传

  • 利用到了 Commons-io,通过 http 和 servlet 传输流文件.

    import org.apache.commons.fileupload.FileItem;
    import org.apache.commons.fileupload.FileUploadException;
    import org.apache.commons.fileupload.disk.DiskFileItemFactory;
    import org.apache.commons.fileupload.servlet.ServletFileUpload;

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;
    import java.util.UUID;


    /**
    * Servlet implementation class FileServlet
    */
    public class FileServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doPost(req, resp);
    }

    /**
    * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
    */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

    // 判断上传的文件普通表单还是带文件的表单
    if (!ServletFileUpload.isMultipartContent(request)) {
    return;//终止方法运行,说明这是一个普通的表单,直接返回
    }

    // 创建上传文件的保存路径,建议在WEB-INF路径下,安全,用户无法直接访问上传的文件
    String tmpPath = this.getServletContext().getRealPath("/WEB-INF/tmp");
    File tempFile = new File(tmpPath);
    if (!tempFile.exists()) {
    tempFile.mkdir();//创建临时目录
    }

    //创建上传文件的保存路径,建议在WEB-INF路径下,安全,用户无法直接访间上传的文件;
    String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
    File uploadFile = new File(uploadPath);
    if (!uploadFile.exists()) {
    uploadFile.mkdir(); //创建这个月录
    }

    /*
    处理上传的文件,一般都需要通过流来获取,我们可以使用 request.getInputStream(),原生态的文件上传流获取,十分麻烦
    建议使用 Apache的文件上传组件来实现, common-fileUpload,它需要 commons-io组件
    */
    try {
    // 获取ServletFileUpload
    ServletFileUpload fileUpload = getServletFileUpload(tempFile);

    // 处理上传文件
    // 把前端请求解析,封装成FileItem对象,需要从ServletFileUpload对象中获取
    boolean flag = uploadParseRequest(fileUpload, request, uploadPath);

    // Servlet请求转发消息
    if (flag) {
    // Servlet请求转发消息
    request.setAttribute("flag", true);
    } else {
    request.setAttribute("flag", false);
    }
    request.getRequestDispatcher("info.jsp").forward(request, response);
    } catch (FileUploadException e) {
    e.printStackTrace();
    }
    }


    public static ServletFileUpload getServletFileUpload(File tempFile) {
    // 创建DiskFileItemFactory对象,处理文件路径或者大小限制
    DiskFileItemFactory factory = new DiskFileItemFactory();
    // 通过这个工厂设置一个缓冲区,当上传的文件大于这个缓冲区的时候,将他放到临时文件中;
    factory.setSizeThreshold((int) 2e20);// 缓冲区大小为1M
    factory.setRepository(tempFile);// 临时目录的保存目录,需要一个file

    ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
    // 处理乱码问题
    servletFileUpload.setHeaderEncoding("UTF-8");
    // 设置总共能够上传文件的大小 10M
    servletFileUpload.setFileSizeMax((long) 2e21);

    /*
    监听长传进度
    pBytesRead:已读取到的文件大小
    pContextLength:文件大小
    */
    servletFileUpload.setProgressListener((long pBytesRead, long pContentLength, int pItems) -> System.out.println("已上传:" + pBytesRead + "\n总大小:" + pContentLength));
    return servletFileUpload;
    }

    public static boolean uploadParseRequest(ServletFileUpload upload, HttpServletRequest request, String uploadPath)
    throws FileUploadException, IOException {
    boolean flag = false; // 是否成功

    // 把前端请求解析,封装成FileItem对象
    List<FileItem> fileItems = upload.parseRequest(request);
    for (FileItem fileItem : fileItems) {
    // 判断上传的文件是普通的表单还是带文件的表单
    if (fileItem.isFormField()) {
    // getFieldName指的是前端表单控件的name;
    String name = fileItem.getFieldName();
    String value = fileItem.getString("UTF-8"); // 处理乱码
    System.out.println(name + ": " + value);
    } else {

    // ============处理文件==============
    // 拿到文件名
    String uploadFileName = fileItem.getName();
    /*
    .trim() 删除前后空格
    下面的写法会导致"uploadFileName == null" 一直判定为false :
    uploadFileName.trim().equals("") || uploadFileName == null
    反过来就好了,原因是字符串对象引用改变了
    */
    if (uploadFileName == null || uploadFileName.trim().equals("")) {
    continue;
    }

    // 获得上传的文件名
    String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("/") + 1);
    // 获得文件的后缀名,如果文件后缀名fileExtName不是我们所需要的 就直按return.不处理,告诉用户文件类型不对。
    String fileExtName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1);

    System.out.println("文件名: " + fileName + "\n后缀名" + fileExtName);
    // ================处理文件完毕==============


    /*
    UUID.randomUUID(),随机生一个唯一识别的通用码,保证文件夹名唯一
    文件真实存在的路径 realPath
    */
    String realPath = uploadPath + "/" + UUID.randomUUID();

    // 给每个文件创建一个对应的文件夹
    File realPathFile = new File(realPath);
    if (!realPathFile.exists()) {
    realPathFile.mkdir();
    }
    // ==============存放地址完毕==============


    // 获得文件上传的流
    InputStream inputStream = fileItem.getInputStream();
    // 创建一个文件输出流
    FileOutputStream fileOutputStream = new FileOutputStream(realPath + "/" + fileName);

    // 创建一个缓冲区
    byte[] buffer = new byte[(int) 2e20]; // 1M
    int len;
    while ((len = inputStream.read(buffer)) > 0) {
    fileOutputStream.write(buffer, 0, len);
    }

    // 关闭流
    fileOutputStream.close();
    inputStream.close();

    flag = true;
    // 上传成功,清除临时文件
    fileItem.delete();
    //=============文件传输完成=============
    }
    }
    return flag;
    }
    }
  • 项目地址

    包含用到的 JSP 页面和 xml 配置文件

    https://github.com/Weidows/Java/tree/master/JavaWeb/Last

  • 问题: 如果 Tomcat 是 10 版本的,运行时会出现 500

    servlet 配置相关:HTTP 状态 500 - 内部服务器错误问题

分割线

邮件发送

  • 原理图:

    邮件传输原理.drawio
  • 依赖

    <dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.mail/mail -->
    <dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
    <version>1.4.7</version>
    </dependency>


简单邮件

  • 代码:

    import com.sun.mail.util.MailSSLSocketFactory;

    import javax.mail.*;
    import javax.mail.internet.InternetAddress;
    import javax.mail.internet.MimeMessage;
    import java.security.GeneralSecurityException;
    import java.util.Properties;

    public class MailDemo1 {
    private static final String fromEmailAddress = "utsuko27@qq.com";
    private static final String toEmailAddress = "utsuko27@163.com";
    private static final String password = "这里填授权码";

    public static void main(String[] args) throws Exception {
    //0. 设置连接配置
    Properties properties = getProperties();

    //1.创建定义整个应用程序所需要的环境信息的session对象
    Session session = Session.getDefaultInstance(properties, new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
    return new PasswordAuthentication(fromEmailAddress, password);
    }
    });
    //开启session的debug模式,这样可以查看到程序发送Email的运行状态
    session.setDebug(true);

    //2.通过session得到transport对象
    Transport transport = session.getTransport();

    //3.使用邮箱的用户名和授权码连上邮件服务器
    transport.connect("smtp.qq.com", fromEmailAddress, password);

    //4.创建邮件:写文件
    MimeMessage message = getMimeMessage(session);

    //5.发送邮件
    transport.sendMessage(message, message.getAllRecipients());

    //6.关闭连接
    transport.close();
    }


    private static Properties getProperties() throws GeneralSecurityException {
    Properties properties = new Properties();
    properties.setProperty("mail.host", "smtp.qq.com");///设置QQ邮件服务器
    properties.setProperty("mail.transport.protocol", "smtp");///邮件发送协议
    properties.setProperty("mail.smtp.auth", "true");//需要验证用户密码
    //设置SSL加密(QQ邮箱需要)
    MailSSLSocketFactory mailSSLSocketFactory = new MailSSLSocketFactory();
    mailSSLSocketFactory.setTrustAllHosts(true);
    properties.put("mail.smtp.ssl.enable", "true");
    properties.put("mail.smtp.ssl.socketFactory", mailSSLSocketFactory);
    return properties;
    }

    private static MimeMessage getMimeMessage(Session session) throws MessagingException {
    MimeMessage message = new MimeMessage(session);
    //指明邮件的发件人
    message.setFrom(new InternetAddress(fromEmailAddress));
    //指明邮件的收件人
    message.setRecipient(Message.RecipientType.TO, new InternetAddress(toEmailAddress));
    //邮件标题
    message.setSubject("发送的标题");
    //邮件的文本内容
    message.setContent("内容", "text/html;charset=UTF-8");
    return message;
    }

    }

  • -> 开启 Debug 后的信息 <-
    DEBUG: setDebug: JavaMail version 1.4.7
    DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Oracle]
    DEBUG SMTP: useEhlo true, useAuth true
    DEBUG SMTP: trying to connect to host "smtp.qq.com", port 465, isSSL true
    220 newxmesmtplogicsvrsza8.qq.com XMail Esmtp QQ Mail Server.
    DEBUG SMTP: connected to host "smtp.qq.com", port: 465

    EHLO DESKTOP-CTDDD3K
    250-newxmesmtplogicsvrsza8.qq.com
    250-PIPELINING
    250-SIZE 73400320
    250-AUTH LOGIN PLAIN XOAUTH XOAUTH2
    250-AUTH=LOGIN
    250-MAILCOMPRESS
    250 8BITMIME
    DEBUG SMTP: Found extension "PIPELINING", arg ""
    DEBUG SMTP: Found extension "SIZE", arg "73400320"
    DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN XOAUTH XOAUTH2"
    DEBUG SMTP: Found extension "AUTH=LOGIN", arg ""
    DEBUG SMTP: Found extension "MAILCOMPRESS", arg ""
    DEBUG SMTP: Found extension "8BITMIME", arg ""
    DEBUG SMTP: Attempt to authenticate using mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM
    DEBUG SMTP: AUTH LOGIN command trace suppressed
    DEBUG SMTP: AUTH LOGIN succeeded
    DEBUG SMTP: use8bit false
    MAIL FROM:<utsuko27@qq.com>
    250 OK.
    RCPT TO:<utsuko27@163.com>
    250 OK
    DEBUG SMTP: Verified Addresses
    DEBUG SMTP: utsuko27@163.com
    DATA
    354 End data with <CR><LF>.<CR><LF>.
    From: utsuko27@qq.com
    To: utsuko27@163.com
    Message-ID: <825249556.0.1619921529857.JavaMail.29845@smtp.qq.com>
    Subject: =?UTF-8?B?5Y+R6YCB55qE5qCH6aKY?=
    MIME-Version: 1.0
    Content-Type: text/html;charset=UTF-8
    Content-Transfer-Encoding: base64

    5YaF5a65
    .
    250 OK: queued as.
    QUIT
    221 Bye.

复杂邮件

  • 比如涉及到附件或者插入图片的邮件,这种邮件就需要进行特殊包装

    20210502102326
  • 代码 (只需要修改 getMimeMessage()方法)

    /**
    * 含带附件的邮件
    *
    * @param session
    * @return
    * @throws MessagingException
    */
    private static MimeMessage getMimeMessage(Session session) throws MessagingException {
    MimeMessage message = new MimeMessage(session);
    //指明邮件的发件人
    message.setFrom(new InternetAddress(fromEmailAddress));
    //指明邮件的收件人
    message.setRecipient(Message.RecipientType.TO, new InternetAddress(toEmailAddress));
    //邮件标题
    message.setSubject("发送的标题");

    //邮件的文本内容
    //=================================准备图片数据
    MimeBodyPart image = new MimeBodyPart();
    //图片需要经过数据化的处理
    DataHandler dataHandler = new DataHandler(new FileDataSource("D:/Game/Weidows/JavaWeb/Last/src/main/java/MailDemo.java"));
    //在part中放入这个处理过图片的数据
    image.setDataHandler(dataHandler);
    //给这个part设置一个ID名字
    image.setContentID("嵌入.png");

    //=================================准备正文数据
    MimeBodyPart text = new MimeBodyPart();
    text.setContent("这是一张正文<img src='嵌入.jpg'>", "text/html;charset=UTF-8");

    //=================================准备附件数据
    MimeBodyPart body = new MimeBodyPart();
    body.setDataHandler(new DataHandler(new FileDataSource("D:/Game/Weidows/JavaWeb/Last/src/main/java/MailDemo.java")));
    body.setFileName("附件.png");

    //描述数据关系
    MimeMultipart mimeMultipart = new MimeMultipart();
    mimeMultipart.addBodyPart(image);
    mimeMultipart.addBodyPart(text);
    mimeMultipart.addBodyPart(body);
    mimeMultipart.setSubType("mixed");

    //设置到消息中,保存修改
    message.setContent(mimeMultipart);
    message.saveChanges();
    return message;
    }
  • 测试过邮件发送没有问题,但是很有可能因为包装问题导致图片或者文件出现编码解析错误问题

分割线

Lombok

  • 通过注解快速生成代码

  • 需要安装插件导入依赖

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
    </dependency>
  • 效果如图:

    20210502141126

分割线

注册-发送邮件 demo

其他资源可以去这里找: 代码

  • 通过 JSP 获取表单中用户名,密码和 email,然后用 Servlet 获取,并开启邮件发送线程通知用户.

  • 发送邮件的类

    package demo;

    import com.sun.mail.util.MailSSLSocketFactory;

    import javax.mail.*;
    import javax.mail.internet.InternetAddress;
    import javax.mail.internet.MimeMessage;
    import java.security.GeneralSecurityException;
    import java.util.Properties;

    public class SendMail extends Thread {
    private static final String fromEmailAddress = "utsuko27@qq.com";
    private static final String toEmailAddress = "utsuko27@163.com";
    private static final String password = "授权码";
    private final User user;

    public SendMail(User user) {
    this.user = user;
    }

    @Override
    public void run() {
    try {
    Session session = Session.getDefaultInstance(getProperties(), new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
    return new PasswordAuthentication(fromEmailAddress, password);
    }
    });
    session.setDebug(true);

    MimeMessage message = getMimeMessage(session);

    send(session, message);
    } catch (Exception e) {
    System.out.println(e);
    }
    }


    private Properties getProperties() throws GeneralSecurityException {
    Properties prop = new Properties();
    prop.setProperty("mail.host", "smtp.qq.com");///设置QQ邮件服务器
    prop.setProperty("mail.transport.protocol", "smtp");///邮件发送协议
    prop.setProperty("mail.smtp.auth", "true");//需要验证用户密码
    //QQ邮箱需要设置SSL加密
    MailSSLSocketFactory sf = new MailSSLSocketFactory();
    sf.setTrustAllHosts(true);
    prop.put("mail.smtp.ssl.enable", "true");
    prop.put("mail.smtp.ssl.socketFactory", sf);
    return prop;
    }

    private MimeMessage getMimeMessage(Session session) throws MessagingException {
    MimeMessage message = new MimeMessage(session);
    //指明邮件的发件人
    message.setFrom(new InternetAddress(fromEmailAddress));
    //指明邮件的收件人
    message.setRecipient(Message.RecipientType.TO, new InternetAddress(toEmailAddress));
    //邮件标题
    message.setSubject("注册通知");
    //邮件的文本内容
    message.setContent(("恭喜你(" + user.getUserName() + ")成功注册!" + "密码:" + user.getPassword())
    , "text/html;charset=UTF-8");
    return message;
    }

    private void send(Session session, MimeMessage message) throws MessagingException {
    Transport transport = session.getTransport();
    transport.connect("smtp.qq.com", fromEmailAddress, password);
    transport.sendMessage(message, message.getAllRecipients());
    transport.close();
    }
    }

  • Servlet 类

    package demo;

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;

    public class RegisterServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String username = req.getParameter("username");
    String password = req.getParameter("pwd");
    String email = req.getParameter("email");

    User user = new User(username, password, email);
    new SendMail(user).start(); // 开启线程
    resp.getWriter().write("正在发送邮件....");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doPost(req, resp);
    }
    }