1. 程式人生 > >9、ssm整合activeMQ、JAVAMail實現郵件非同步註冊和登陸功能

9、ssm整合activeMQ、JAVAMail實現郵件非同步註冊和登陸功能

1、前言

註冊某個網站的時候,往往要你用郵箱註冊,傳送郵件的功能很簡單,但是在點選註冊傳送郵件的時候,總不能等郵件傳送完畢之後才能跳轉頁面吧?或者說,我們應該將發郵件的這個過程異步出去,讓他自己慢慢去發郵件,我的主執行緒直接跳轉到其他頁面,等郵件到了,使用者點選啟用就可以使用了,傳送簡訊驗證碼也是同樣,這一篇利用activeMQ實現訊息佇列非同步傳送。

2、注意

  • 必須先啟動activeMQ才能啟動專案

  • 我這裡將會實現一個完整的從註冊-傳送郵件啟用-登陸的過程,所以程式碼量稍微有所提升,但是我在本地跑是沒有問題的,所以可以嘗試先跑起來再說。

  • 關於郵件傳送,啟用這一塊,由於是本地測試,必須要放到外網才能跟實際一樣操作;我採用的方案是用花生殼軟體實現內網穿透和域名對映,模擬真實環境下的啟用過程;如果沒有條件或者不想搞,那就直接將資料庫中的標誌位改掉吧,這樣相當於啟用成功了。

  • 先下載activeMQ,具體的使用可以參照我的有道雲筆記

3、效果預覽:

  • 動態圖看不清的話,可以右擊複製圖片連結,開啟一個新的網頁重新開啟,顯示的應該比較清楚。

image

4、廢話不多說,開幹,先建立兩張表:

使用者表:

CREATE TABLE t_user(
`id`  bigint(20) NOT NULL AUTO_INCREMENT ,
`username`  varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`password`  varchar(50) CHARACTER SET utf8 COLLATE
utf8_general_ci NOT NULL , `headImgUrl` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , `phoneNumber` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , `register_date` datetime NOT NULL , `sex` tinyint(2) NULL DEFAULT NULL , `email` varchar(255
) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , `role_Id` int(11) NOT NULL DEFAULT 1 , PRIMARY KEY (`id`) );

登錄檔:

CREATE TABLE `t_register` (
`id`  bigint(20) NOT NULL AUTO_INCREMENT ,
`code`  varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`state`  tinyint(2) NULL DEFAULT NULL ,
`user_id`  bigint(20) NOT NULL ,
PRIMARY KEY (`id`)
);

5、引入MQ和mail的依賴:

<!--MQ-->
<dependency>
  <groupId>org.apache.activemq</groupId>
  <artifactId>activemq-all</artifactId>
  <version>5.13.2</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jms</artifactId>
  <version>${spring.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-messaging</artifactId>
  <version>${spring.version}</version>
</dependency>
<dependency>
  <groupId>org.apache.activemq</groupId>
  <artifactId>activemq-pool</artifactId>
  <version>5.13.2</version>
</dependency>
<!--java mail-->
<dependency>
  <groupId>javax.mail</groupId>
  <artifactId>mail</artifactId>
  <version>1.4.7</version>
</dependency>

6、根據使用者表和登錄檔自動生成對應的實體類、dao介面、dao sql對映檔案吧。
此處略,怎麼做,之前整合ssm部落格中已經詳細介紹了。

7、想把頁面做的好看一點,先將我專案中的static下的所有檔案全部拷貝到自己工程相對應的地方吧!下面先寫一個註冊的jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ include file="/WEB-INF/views/includes.jsp" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>AdminLTE 2 | Registration Page</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <!-- Bootstrap 3.3.6 -->
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <!-- Font Awesome -->
    <link rel="stylesheet" href="/static/css/font-awesome.min.css">
    <!-- Ionicons -->
    <link rel="stylesheet" href="/static/css/ionicons.min.css">
    <!-- Theme style -->
    <link rel="stylesheet" href="/static/dist/css/AdminLTE.min.css">
    <!-- iCheck -->
    <link rel="stylesheet" href="/static/plugins/iCheck/square/blue.css">
</head>
<body class="hold-transition register-page">
<div class="register-box">
    <div class="register-logo">
        <a href="../../index2.html"><b>註冊</b></a>
    </div>

    <div class="register-box-body">
        <p class="login-box-msg">蝸牛生活</p>

        <form action="/register/regist" method="post" id="registerFrom">
            <div class="form-group has-feedback">
                <input type="text" class="form-control" placeholder="使用者名稱" name="username" id="username">
                <span class="glyphicon glyphicon-user form-control-feedback"></span>
                <span id="username_alert" style="color: red;visibility: hidden">使用者名稱不能為空</span>
            </div>
            <div class="form-group has-feedback">
                <input type="email" class="form-control" placeholder="郵箱" name="email" id="email">
                <span class="glyphicon glyphicon-envelope form-control-feedback"></span>
                <span id="email_alert" style="color: red;visibility: hidden">郵箱已存在</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                <span id="email_style_alert" style="color: red;visibility: hidden">郵箱格式不正確</span>
            </div>
            <div class="form-group has-feedback">
                <input type="password" class="form-control" placeholder="密碼" name="password" id="password">
                <span class="glyphicon glyphicon-lock form-control-feedback"></span>
                <span id="password_alert" style="color: red;visibility: hidden">密碼不能為空</span>
            </div>
            <div class="form-group has-feedback">
                <input type="password" class="form-control" placeholder="確認密碼" name="rePassword" id="rePassword">
                <span class="glyphicon glyphicon-log-in form-control-feedback"></span>
                <span id="rePassword_alert" style="color: red;visibility: hidden">密碼不一致</span>
            </div>
            <div class="form-group has-feedback">
                <input type="text" class="form-control" placeholder="手機號碼" name="phoneNumber" id="phoneNumber">
                <span class="glyphicon glyphicon-phone form-control-feedback"></span>
                <span id="phoneNumber_alert" style="color: red;visibility: hidden">手機號碼不能為空</span>
            </div>
            <div class="row">
                <div class="col-xs-8">
                </div>
                <!-- /.col -->
                <div class="col-xs-4">
                    <button type="button" class="btn btn-primary btn-block btn-flat" onclick="submitForm();" id="submitBtn">立即註冊</button>
                </div>
                <div class="col-xs-12"> <span id="register_error" style="color: red;visibility: hidden">註冊失敗</span></div>
                <!-- /.col -->
            </div>
        </form>
        <a href="${pageContext.request.contextPath}/login" class="text-center">已有帳號,立即登陸</a>
    </div>
    <!-- /.form-box -->
</div>
<!-- /.register-box -->

<!-- jQuery 2.2.3 -->
<script src="/static/jquery/jquery-2.2.3.min.js"></script>
<!-- Bootstrap 3.3.6 -->
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
<!-- iCheck -->
<script src="/static/plugins/iCheck/icheck.min.js"></script>
<script>
    $(document).ready(function(){
        verify = false;
        checkEmail();
    });

    function checkEmail(){
        $('#email').focusout(function(){
            var email = $(this).val();
            var param="${pageContext.request.contextPath}/register/checkEmail?email="+email+"";
            $.ajax({
                async:false,
                url:param,
                type:"POST",
                dataType:"json",
                contentType: 'application/x-www-form-urlencoded; charset=UTF-8',//防止亂碼
                success:function(data){
                    if(data.code == 400){
                        $("#email_alert").css("visibility","visible");
                        $("#email_style_alert").css("visibility", "hidden");
                        verify = false;
                    }else{
                        $("#email_alert").css("visibility","hidden");
                        verify=true;
                    }
                }
            });
        });
    }
    function submitForm() {
        var flag = true;
        var username = $("#username").val();
        var email = $("#email").val();
        var password = $("#password").val();
        var rePassword = $("#rePassword").val();
        var phoneNumber = $("#phoneNumber").val();
        if(username.length==0){
            $("#username_alert").css("visibility","visible");
            flag=false;
        }else {
            $("#username_alert").css("visibility","hidden");
        }

        if(email.length==0){
            $("#register_error").css("visibility","visible");
            flag=false;
        }else {
            var reg = /^([a-zA-Z0-9_-])[email protected]([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/;
            if(!reg.test(email)){
                $("#email_style_alert").css("visibility","visible");
                flag=false;
            }else {
                $("#email_style_alert").css("visibility", "hidden");
                $("#register_error").css("visibility","hidden");
            }
        }

        if(password.length==0){
            $("#password_alert").css("visibility","visible");
            flag=false;
        }else {
            $("#password_alert").css("visibility","hidden");
        }

        if(rePassword.length==0){
            flag=false;
        }else {
            if(rePassword!=password){
                $("#rePassword_alert").css("visibility","visible");
                flag=false;
            }else{
                $("#rePassword_alert").css("visibility","hidden");
            }
        }

        if(phoneNumber.length==0){
            $("#phoneNumber_alert").css("visibility","visible");
            flag=false;
        }else {
            $("#phoneNumber_alert").css("visibility","hidden");
        }
        if(verify==true&&flag==true){
            $("#submitBtn").attr("disabled", "disabled").text("提交中...");
            $("#registerFrom").submit();
        }else {
            $("#register_error").css("visibility","visible");
        }
    };
</script>
</body>
</html>

這裡主要就是表單驗證和提交嘛,相應的controler是:

@RequestMapping("")
public String register() {
    return "/register/register";
}

//獲取引數,沒什麼問題就進行註冊,即插入值
@RequestMapping("/regist")
public String regist(HttpServletRequest request) throws UnsupportedEncodingException, NoSuchAlgorithmException {
    try{
        System.out.println("===================regist=========================");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String rePassword = request.getParameter("rePassword");
        String email = request.getParameter("email");
        String phoneNumber = request.getParameter("phoneNumber");
        if(StringUtils.isBlank(username) || StringUtils.isBlank(password) || StringUtils.isBlank(rePassword) || StringUtils.isBlank(email) || StringUtils.isBlank(phoneNumber)){
            return null;
        }
        //將收到的註冊資訊存到資料庫中,並且傳送啟用郵件,使用者未點選啟用郵件,狀態設為0,表示不可用,點選之後設為1,表示使用者可用。
        registerService.addNewUser(username,password,rePassword,email,phoneNumber);
    }catch (Exception e){
        log.error("註冊失敗",e);
        throw e;
    }
    return "/register/tip";
}

//檢查郵箱是否已經被使用
@RequestMapping("/checkEmail")
@ResponseBody
public Result checkEmail(HttpServletRequest request){
    Result result = new Result();
    String email = request.getParameter("email");
    if(StringUtils.isBlank(email)){
        result.setCode(Constants.RESP_STATUS_INTERNAL_ERROR);
        return result;
    }
//      從資料庫查詢是否存在這個郵箱
    try{
        if(!registerService.isUserOld(email)){
            //說明這個郵箱已經被註冊了
            result.setMessgae("郵箱已經被註冊");
            result.setCode(Constants.RESP_STATUS_BADREQUEST);
        }
    } catch (Exception e){
        result.setCode(Constants.RESP_STATUS_INTERNAL_ERROR);
        result.setMessgae("未知錯誤");
    }
    return result;
}

addNewUser方法:

@Override
@Transactional
public void addNewUser(String username, String password, String rePassword, String email, String phoneNumber) throws UnsupportedEncodingException, NoSuchAlgorithmException {
    User user = new User();
    user.setUsername(username);
    user.setEmail(email);
    if(password.equals(rePassword)){
        user.setPassword(MD5Util.encryptPassword(password));
    }
    user.setPhonenumber(phoneNumber);
    /*頭像為預設,性別也預設為男*/
    user.setHeadimgurl("snail.jpg");
    user.setSex((byte)1);
    user.setRegisterDate(new Date());
    userMapper.insertSelective(user);
    Register register = new Register();
    register.setUserId(user.getId());
    //使用者一點提交時,狀態肯定是0,即未認證的情況
    register.setState((byte)0);
    //code也隨機生成
    String code = UUID.randomUUID().toString().replace("-", "");
    register.setCode(code);
    registerMapper.insertSelective(register);
    //傳送啟用郵件
    Destination destination = new ActiveMQQueue(SMS_QUEUE);
    Map<String,String> emailParam = new HashMap<>();
    emailParam.put("email",email);
    emailParam.put("code", code);
    emailParam.put("username",username);
    String message = JSON.toJSONString(emailParam);
    emailProcessor.sendEmaillToQueue(destination,message);
}

@Override
public Register getRegisterByCode(String code) throws MyException {
    Register register = registerMapper.getRegisterByCode(code);
    if(register==null){
        throw new MyException("不存在使用者的啟用記錄");
    }
    return register;
}

首先是給使用者表塞值,然後給登錄檔塞值,最後傳送郵件。注意這裡有個小插曲:

在給使用者表塞值後,登錄檔需要這個使用者的id,需要在插入使用者的地方增加:

useGeneratedKeys="true" keyProperty="id"

這樣插入一條記錄之後將會返回制定的主鍵的值。

注意SMS_QUEUE這個常量:

private static final String SMS_QUEUE = "email.queue";

不是亂配的,這個表示佇列的名字,一個是生產者,一個是消費者,都對應這個佇列,在soring-cfg.xml中配置MQ的生產者和消費者:

<!--連結到amq-->
<amq:connectionFactory id="amqConnectionFactory" brokerURL="tcp://localhost:61616" userName="admin" password="admin" />

<!--交給spring管理-->
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
    <property name="targetConnectionFactory" ref="amqConnectionFactory"/>
    <property name="sessionCacheSize" value="10"/>
</bean>

<!-- 配置JMS模版 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
    <property name="connectionFactory" ref="connectionFactory" />
    <!-- 如果為True,則是Topic;如果是false或者預設,則是queue -->
    <property name="pubSubDomain" value="false"/>
</bean>

<jms:listener-container destination-type="queue" connection-factory="connectionFactory">
    <jms:listener destination="email.queue" ref="doSendSmsMessage"/>
</jms:listener-container>

注意不要在頭部引入約束檔案:

xmlns:jms="http://www.springframework.org/schema/jms"
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.0.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core-5.8.0.xsd

我們注意到配置了消費者監聽器一直監聽email.queue這個佇列,然後執行傳送訊息的方法。

好,下面我們來實現emailProcessor.sendEmaillToQueue(destination,message);

@Component
public class EmailProcessor {
    @Autowired
    private JmsTemplate jmsTemplate;

    public void sendEmaillToQueue(Destination destination, String message) {
        System.out.println("---------------------------------------生產----");
        jmsTemplate.convertAndSend(destination, message);
    }
}

這裡就實現了將訊息傳送到email.queue這個隊列了,下面就是消費這個訊息:

@Component("doSendSmsMessage")
public class DoSendEmail implements MessageListener {
    @Autowired
    private EmailSender emailSender;

    @Override
    public void onMessage(Message message) {
        TextMessage textMsg = (TextMessage) message;
        System.out.println("---------------------------------------消費開始----");
        try {
            JSONObject jsonObject = JSON.parseObject(textMsg.getText());
            emailSender.sendEmail(jsonObject.getString("email"),jsonObject.getString("code"),jsonObject.getString("username"));
        } catch (JMSException e) {
            e.printStackTrace();
        }
        System.out.println("---------------------------------------消費結束----");

    }
}

我們可以看到這就是上面配置檔案提到的doSendSmsMessage,這裡實現接受訊息然後傳送出去,具的傳送郵件的方法emailSender.sendEmail是這樣實現的:

@Component
public class EmailSender {

    public void sendEmail(String to, String code, String username) {
        try {
            Properties props = new Properties();
            props.put("username", "你的qq號");
            props.put("password", "你的密碼");
            props.put("mail.transport.protocol", "smtp" );
            props.put("mail.smtp.host", "smtp.qq.com");
            props.put("mail.smtp.port", "465" );

            props.put("mail.smtp.auth","true");
            props.put("mail.smtp.ssl.enable","true");

            Session mailSession = Session.getDefaultInstance(props);

            Message msg = new MimeMessage(mailSession);
            msg.setFrom(new InternetAddress("[email protected]"));
            msg.addRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
            msg.setSubject("啟用郵件");
            msg.setContent(username+",您好,恭喜你成功註冊本系統。點選連結即可完成啟用。<h1>此郵件為蝸牛生活傳送的啟用郵件!請點選下面連結完成啟用操作!</h1><h3><a href='http://1dm8911156.imwork.net/register/active?code="+code+"'>《---蝸牛生活---》</a></h3>","text/html;charset=UTF-8");
            msg.saveChanges();

            Transport transport = mailSession.getTransport("smtp");
            transport.connect(props.getProperty("mail.smtp.host"), props
                    .getProperty("username"), props.getProperty("password"));
            transport.sendMessage(msg, msg.getAllRecipients());
            transport.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e);
        }
    }
}

總之寫自己的真實登陸密碼肯定是不行的,校驗不通過。

這樣,非同步傳送郵件的整個過程算是走完了。我只寫了關鍵部分,沒有涵蓋全部,具體看我的程式碼,畢竟思路最重要嘛。

註冊成功之後,跳轉到一個頁面提示註冊成功tip.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ include file="/WEB-INF/views/includes.jsp" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>提示</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <!-- Bootstrap 3.3.6 -->
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <!-- Font Awesome -->
    <link rel="stylesheet" href="/static/css/font-awesome.min.css">
    <!-- Ionicons -->
    <link rel="stylesheet" href="/static/css/ionicons.min.css">
    <!-- Theme style -->
    <link rel="stylesheet" href="/static/dist/css/AdminLTE.min.css">
    <!-- jQuery 2.2.3 -->
    <script src="/static/jquery/jquery-2.2.3.min.js"></script>
</head>
<body class="hold-transition lockscreen">
<!-- Automatic element centering -->
<div class="lockscreen-wrapper">
    <div class="lockscreen-logo">
        <a href="../../index2.html"><b>蝸牛生活</b></a>
    </div>
    <div class="lockscreen-name">註冊成功</div>
    <div>
        <a href="${basePath}/login"><span id="time">5</span>秒後系統會自動跳轉到登陸頁面,也可點選本處直接跳轉哦</a>
        <script type="text/javascript">
            $(document).ready(function(){
                delayURL("${basePath}/login");
            })

            function delayURL(url) {
                var delay = document.getElementById("time").innerHTML;
                if (delay > 0) {
                    delay--;
                    document.getElementById("time").innerHTML = delay
                } else {
                    window.top.location.href = url
                }
                setTimeout("delayURL('" + url + "')", 1000)
            }
        </script>
    </div>
</div>
<!-- /.center -->
</body>
</html>

8、啟用

這就很簡單的,根據code獲取這個註冊的記錄,將記錄中的標誌位修改一下,就表示激活了。

說到這個code,我可能上面註冊的步驟中沒有是說明,他就是一個隨機字串,然後將他放倒郵件內容中,使用者點選了,我就能獲取這個字串,也就找到了使用者本身,我就能修改使用者的啟用狀態了,簡而言之,我總要有個東西明確一下是哪個使用者激活了哪個郵箱啊。

 @RequestMapping(value = "/active",method = RequestMethod.GET)
    public String active(@RequestParam String code) throws MyException {
        try{
            if(StringUtils.isBlank(code)){
                log.error("使用者code為空");
                throw new MyException("使用者啟用郵件的引數有問題");
            }
//        根據code獲取該使用者資訊,將狀態置為1即啟用
        /*先根據code獲取註冊資訊,拿到使用者id*/
            Register r = registerService.getRegisterByCode(code);
            if(r.getState()==0){
                r.setState((byte)1);
                registerService.updateStatus(r);
                return "/register/active";
            }else{
                return "/register/fail";
            }
        }catch (Exception e){
            log.error("啟用失敗",e);
            throw e;
        }
    }

啟用成功之後,直接跳轉到啟用成功提示頁面active.jsp:

<%@ page contentType="