JavaMail實(shí)現(xiàn)注冊(cè)郵箱驗(yàn)證案例

在日常生活中,我們?cè)谝粋€(gè)網(wǎng)站中注冊(cè)一個(gè)賬戶(hù)時(shí),往往在提交個(gè)人信息后,網(wǎng)站還要我們通過(guò)手機(jī)或郵件來(lái)驗(yàn)證,郵件的話(huà)大概會(huì)是下面這個(gè)樣子的:

用戶(hù)通過(guò)點(diǎn)擊鏈接從而完成注冊(cè),然后才能登錄。

也許你會(huì)想,為什么要這么麻煩直接提交注冊(cè)不就行了嗎?這其中很大一部分原因是為了防止惡意注冊(cè)。接下來(lái)讓我們一起來(lái)使用最簡(jiǎn)單的JSP+Servlet的方式來(lái)完成一個(gè)通過(guò)郵箱驗(yàn)證注冊(cè)的小案例吧。

準(zhǔn)備工作

前提知識(shí)

動(dòng)手實(shí)踐之前,你最好對(duì)以下知識(shí)有所了解:

如果對(duì)郵件收發(fā)過(guò)程完全不了解的話(huà),可以花三分鐘的時(shí)間到慕課網(wǎng)了解一下,講得算是非常清楚了,這里就不贅述了。放張圖回憶一下:

郵件收發(fā)過(guò)程

郵箱準(zhǔn)備

在了解的上述內(nèi)容之后,要實(shí)現(xiàn)這個(gè)案例,首先我們還得有兩個(gè)郵箱賬號(hào),一個(gè)用來(lái)發(fā)送郵件,一個(gè)用來(lái)接收郵件。本案例使用QQ郵箱向163郵箱發(fā)送激活郵件,因此需要登錄QQ郵箱,在設(shè)置->賬戶(hù)面板中開(kāi)啟POP3/SMTP服務(wù),以允許我們通過(guò)第三方客戶(hù)端發(fā)送郵件:

還要注意的是,登錄以下服務(wù): POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服務(wù)時(shí),需要用到授權(quán)碼而不是QQ密碼,授權(quán)碼是用于登錄第三方郵件客戶(hù)端的專(zhuān)用密碼。因此我們需要獲得授權(quán)碼,以在后面的程序中使用。

好了,到此準(zhǔn)備工作就差不多了,下面開(kāi)始動(dòng)手吧。

實(shí)現(xiàn)注冊(cè)Demo

創(chuàng)建Maven工程

本次案例基于Maven,因此你要先創(chuàng)建一個(gè)Maven的Web工程,并引入相關(guān)依賴(lài):

<dependencies>
        <!-- JavaEE依賴(lài) -->
        <dependency>
            <groupId>javaee</groupId>
            <artifactId>javaee-api</artifactId>
            <version>5</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
        <!-- mysql驅(qū)動(dòng)依賴(lài) -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.40</version>
        </dependency>
        <!-- c3p0依賴(lài) -->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <!-- JavaMail相關(guān)依賴(lài) -->
        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>mail</artifactId>
            <version>1.4.7</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>

</dependencies>

創(chuàng)建數(shù)據(jù)庫(kù)表

接下來(lái)使用MySQL創(chuàng)建一張簡(jiǎn)單的用戶(hù)表:

create table `user`(
    id int(11) primary key auto_increment comment '用戶(hù)id',
    username varchar(255) not null comment '用戶(hù)名',
    email varchar(255) not null comment '用戶(hù)郵箱',
    password varchar(255) not null comment '用戶(hù)密碼',
    state int(1) not null default 0 comment '用戶(hù)激活狀態(tài):0表示未激活,1表示激活',
    code varchar(255) not null comment '激活碼'
)engine=InnoDB default charset=utf8;

其中要注意的地方是state字段(用來(lái)判斷用戶(hù)賬號(hào)是否激活)和code字段(激活碼)。

創(chuàng)建注冊(cè)頁(yè)面

使用JSP創(chuàng)建一個(gè)最簡(jiǎn)單的注冊(cè)頁(yè)面(請(qǐng)自行忽略界面):

嗯,果然夠簡(jiǎn)單。

主要的業(yè)務(wù)邏輯

先想一下,我們的整個(gè)流程應(yīng)該是這樣的:

  1. 用戶(hù)填寫(xiě)相關(guān)信息,點(diǎn)擊注冊(cè)按鈕
  2. 系統(tǒng)先將用戶(hù)記錄保存到數(shù)據(jù)庫(kù)中,其中用戶(hù)狀態(tài)為未激活
  3. 系統(tǒng)發(fā)送一封郵件并通知用戶(hù)去驗(yàn)證
  4. 用戶(hù)登錄郵箱并點(diǎn)擊激活鏈接
  5. 系統(tǒng)將用戶(hù)狀態(tài)更改為已激活并通知用戶(hù)注冊(cè)成功

搞清楚了整個(gè)流程,實(shí)現(xiàn)起來(lái)應(yīng)該就不難了。下圖是我建立的包結(jié)構(gòu):

ps:完整代碼請(qǐng)見(jiàn)后文鏈接,這里只討論主要的思路

首先是,用戶(hù)提交注冊(cè)信息后,相應(yīng)的servlet會(huì)將相關(guān)信息傳給service層去處理,在service中需要做的就是講記錄保存到數(shù)據(jù)庫(kù)中(調(diào)用dao層),然后再給用戶(hù)發(fā)送一封郵件,UserServiceImpl相關(guān)代碼如下:

public boolean doRegister(String userName, String password, String email) {
    // 這里可以驗(yàn)證各字段是否為空
    
    //利用正則表達(dá)式(可改進(jìn))驗(yàn)證郵箱是否符合郵箱的格式
    if(!email.matches("^\\w+@(\\w+\\.)+\\w+$")){
        return false;
    }
    //生成激活碼
    String code=CodeUtil.generateUniqueCode();
    User user=new User(userName,email,password,0,code);
    //將用戶(hù)保存到數(shù)據(jù)庫(kù)
    UserDao userDao=new UserDaoImpl();
    //保存成功則通過(guò)線(xiàn)程的方式給用戶(hù)發(fā)送一封郵件
    if(userDao.save(user)>0){
        new Thread(new MailUtil(email, code)).start();;
        return true;
    }
    return false;
}

需要注意的是,應(yīng)該新建一個(gè)線(xiàn)程去執(zhí)行發(fā)送郵件的任務(wù),不然被罵估計(jì)是免不了了。
數(shù)據(jù)庫(kù)的操作比較簡(jiǎn)單,此處就不貼出來(lái)了,無(wú)非是將用戶(hù)記錄插到數(shù)據(jù)庫(kù)中。值得一提的是,此處使用c3p0來(lái)作為數(shù)據(jù)源來(lái)替代DriverManager,在頻繁獲取釋放數(shù)據(jù)庫(kù)連接時(shí)效率會(huì)大大提高,c3p0最簡(jiǎn)單的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <named-config name="mysql">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/test1?useSSL=false</property>
        <property name="user">root</property>
        <property name="password">123456</property>
        <!-- 初始化時(shí)一個(gè)連接池嘗試獲得的連接數(shù)量,默認(rèn)是3,大小應(yīng)該在maxPoolSize和minPoolSize之間 -->
        <property name="initialPoolSize">5</property>
        <!-- 一個(gè)連接最大空閑時(shí)間(單位是秒),0意味著連接不會(huì)過(guò)時(shí) -->
        <property name="maxIdleTime">30</property>
        <!-- 任何指定時(shí)間的最大連接數(shù)量 ,默認(rèn)值是15 -->
        <property name="maxPoolSize">20</property>
        <!-- 任何指定時(shí)間的最小連接數(shù)量 ,默認(rèn)值是3 -->
        <property name="minPoolSize">5</property>
    </named-config>
</c3p0-config> 

提供一個(gè)工具類(lèi)DBUtil以獲取,釋放連接:

<pre>
public class DBUtil {
private static ComboPooledDataSource cpds=null;

static{
    cpds=new ComboPooledDataSource("mysql");
}

public static Connection getConnection(){
    Connection connection=null;
    try {
        connection = cpds.getConnection();
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return connection;
}

public static void close(Connection conn,PreparedStatement pstmt,ResultSet rs){
    try {
        if(rs!=null){
            rs.close();
        }
        if(pstmt!=null){
            pstmt.close();
        }
        if(rs!=null){
            rs.close();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    
}

}
</pre>

要特別注意的一點(diǎn)是:即使是使用連接池,使用完Connection后調(diào)用close方法,當(dāng)然這不意味著關(guān)閉與數(shù)據(jù)庫(kù)的TCP 連接,而是將連接還回到池中去,如果不close掉的話(huà),這個(gè)連接將會(huì)一直被占用,直到連接池中的連接耗盡為止。

使用JavaMail發(fā)送郵件

使用JavaMail發(fā)送郵件非常簡(jiǎn)單,也是三步曲:

  1. 創(chuàng)建連接對(duì)象javax.mail.Session
  2. 創(chuàng)建郵件對(duì)象 javax.mail.Message
  3. 發(fā)送郵件

直接看代碼,詳細(xì)的注釋在代碼中,MailUtil代碼如下:

    public class MailUtil implements Runnable {
    private String email;// 收件人郵箱
    private String code;// 激活碼

    public MailUtil(String email, String code) {
        this.email = email;
        this.code = code;
    }

    public void run() {
        // 1.創(chuàng)建連接對(duì)象javax.mail.Session
        // 2.創(chuàng)建郵件對(duì)象 javax.mail.Message
        // 3.發(fā)送一封激活郵件
        String from = "xxx@qq.com";// 發(fā)件人電子郵箱
        String host = "smtp.qq.com"; // 指定發(fā)送郵件的主機(jī)smtp.qq.com(QQ)|smtp.163.com(網(wǎng)易)

        Properties properties = System.getProperties();// 獲取系統(tǒng)屬性

        properties.setProperty("mail.smtp.host", host);// 設(shè)置郵件服務(wù)器
        properties.setProperty("mail.smtp.auth", "true");// 打開(kāi)認(rèn)證

        try {
            //QQ郵箱需要下面這段代碼,163郵箱不需要
            MailSSLSocketFactory sf = new MailSSLSocketFactory();
            sf.setTrustAllHosts(true);
            properties.put("mail.smtp.ssl.enable", "true");
            properties.put("mail.smtp.ssl.socketFactory", sf);


            // 1.獲取默認(rèn)session對(duì)象
            Session session = Session.getDefaultInstance(properties, new Authenticator() {
                public PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication("xxx@qq.com", "xxx"); // 發(fā)件人郵箱賬號(hào)、授權(quán)碼
                }
            });

            // 2.創(chuàng)建郵件對(duì)象
            Message message = new MimeMessage(session);
            // 2.1設(shè)置發(fā)件人
            message.setFrom(new InternetAddress(from));
            // 2.2設(shè)置接收人
            message.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
            // 2.3設(shè)置郵件主題
            message.setSubject("賬號(hào)激活");
            // 2.4設(shè)置郵件內(nèi)容
            String content = "<html><head></head><body><h1>這是一封激活郵件,激活請(qǐng)點(diǎn)擊以下鏈接</h1><h3><a href='http://localhost:8080/RegisterDemo/ActiveServlet?code="
                    + code + "'>http://localhost:8080/RegisterDemo/ActiveServlet?code=" + code
                    + "</href></h3></body></html>";
            message.setContent(content, "text/html;charset=UTF-8");
            // 3.發(fā)送郵件
            Transport.send(message);
            System.out.println("郵件成功發(fā)送!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

ps:需要把上面的賬號(hào)、授權(quán)碼進(jìn)行相應(yīng)修改。

完成后,再有用戶(hù)提交注冊(cè)信息時(shí),應(yīng)該就能收到驗(yàn)證郵件了:

用戶(hù)點(diǎn)擊鏈接后,我們要做的工作就是根據(jù)code(可以利用UUID生成)更改數(shù)據(jù)庫(kù)中相應(yīng)用戶(hù)的狀態(tài),然后提示用戶(hù)注冊(cè)結(jié)果了。

總結(jié)

簡(jiǎn)單介紹了如何使用JavaMail完成了一個(gè)帶郵箱驗(yàn)證的注冊(cè)案例,當(dāng)然在實(shí)際開(kāi)發(fā)中還有許多細(xì)節(jié)要注意,例如對(duì)用戶(hù)提交信息的校驗(yàn),密碼進(jìn)行加密等,此處的簡(jiǎn)單案例并未詳盡處理這些細(xì)節(jié)。

代碼

RegisterDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,692評(píng)論 19 139
  • 22年12月更新:個(gè)人網(wǎng)站關(guān)停,如果仍舊對(duì)舊教程有興趣參考 Github 的markdown內(nèi)容[https://...
    tangyefei閱讀 35,435評(píng)論 22 257
  • 本文包括:1、名詞解釋2、郵件收發(fā)過(guò)程3、JavaMail 知識(shí)概要4、發(fā)送一封符合 MIME 協(xié)議的 JavaM...
    廖少少閱讀 4,314評(píng)論 2 13
  • 傻逼 干嘛要爬那么高 你不知道冷嗎 而且危險(xiǎn) 你身在云里霧里 望不見(jiàn)別人 別人也望不見(jiàn)你呀 當(dāng)心別掉下去了 掉下去...
    吻章閱讀 325評(píng)論 1 1
  • 昆明梁艷分享第九十天。網(wǎng)絡(luò)五期初級(jí)。2017.08.15 自暑期到來(lái),我的生活進(jìn)入到從未有過(guò)的“忙碌”的...
    詩(shī)心小鹿閱讀 208評(píng)論 0 0

友情鏈接更多精彩內(nèi)容