基于Spring+SpringMVC+MyBatis博客系统的开发教程(七)

第07课:登录之手机快捷登录(ActiveMQ+阿里大于)

前期准备

本项目需要使用阿里大于的短信服务发送手机短信,所以我们首先要开通短信服务,记得再充点钱。

开通短信服务

首先登录阿里云,开通阿里大于短信服务,如下图所示:

注册成功后系统会自动生成 Access Key ID 和 Access Key Secret,在后面代码中会用到它们,需要记住。

也可以通过接口调用 -> 获取 AK,查看 Access Key ID 和 Access Key Secret。

发送短信的格式为:【短信签名名称】+ 短信模板内容(短信签名为自己的真实姓名,申请时较容易通过)。

如下是本项目采用的短信格式:

【梦境网】感谢您使用梦境网手机验证功能,您的验证码为:617760,为了您的账号安全,请勿泄露给他人。

接下来,我们申请短信签名,如下图所示:

申请通过之后如下所示:

申请短信模板,如下所示:

在上图中,按如下填写:

模板名称:梦境网,

模板内容为:感谢您使用梦境网手机验证功能,您的验证码为:${code},为了您的账号安全,请勿泄露给他人。

申请说明:测试使用

模板可以申请多个,申请通过后如下图所示:

ActiveMQ 简介

ActiveMQ 是 Apache 旗下产品,是一款优秀的消息中间件。主要解决应用耦合,异步消息,流量削锋等问题,实现高性能,高可用。

你可以把 ActiveMQ 想象成一个大的容器,首先生产者把消息发送到这个大容器中,然后消费者监听,如果有消息就从这个大容器中消费消息,起到一个缓冲的作用。

下载并运行

ActiveMQ 压缩文件我已放在了文末的百度网盘中,也可自己去官网下载

下载完成后进行解压,进入 apache-activemq-5.15.3\bin\win64 目录(32 为系统进入 Win32),然后点击运行 activemq.bat。

可以通过 http://localhost:8161/ 访问 ActiveMQ 消息管理后台页面,如下图所示:

点击 Manage ActiveMQ broker 进行登录,用户名和密码均为:admin。

登录后可以查看 Queues 消息队列等信息,如下图:

ActiveMQ 相关配置

ActiveMQ 生产者和消费者配置文件 applicationContext-activemq.xml 配置如下:

 <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:amq="http://activemq.apache.org/schema/core"
       xmlns:jms="http://www.springframework.org/schema/jms"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/jms
        http://www.springframework.org/schema/jms/spring-jms.xsd
        http://activemq.apache.org/schema/core
        http://activemq.apache.org/schema/core/activemq-core.xsd ">

    <!-- 扫瞄包-->
    <context:component-scan base-package="wang.dreamland.www.activemq" />
    <!-- ActiveMQ 连接工厂 -->
    <!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供-->
    <!-- 如果连接网络:tcp://ip:61616;未连接网络:tcp://localhost:61616 以及用户名,密码-->
    <amq:connectionFactory id="amqConnectionFactory" brokerURL="tcp://localhost:61616" userName="admin" password="admin"  />

    <!-- Spring Caching连接工厂 -->
    <!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
    <bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
        <!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
        <property name="targetConnectionFactory" ref="amqConnectionFactory"></property>
        <!-- 同上,同理 -->
        <!-- <constructor-arg ref="amqConnectionFactory" /> -->
        <!-- Session缓存数量 -->
        <property name="sessionCacheSize" value="100" />
    </bean>

    <!-- Spring JmsTemplate 的消息生产者 start-->
    <!-- 定义JmsTemplate的Queue类型 -->
    <bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
        <!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
        <constructor-arg ref="connectionFactory" />
        <!-- 非pub/sub模型(发布/订阅),即队列模式 -->
        <property name="pubSubDomain" value="false" />
    </bean>

    <!-- Spring JmsTemplate 的消息生产者 end -->

    <!-- 消息消费者 start-->

    <!-- 定义Queue监听器 -->
    <jms:listener-container destination-type="queue" container-type="default" connection-factory="connectionFactory" acknowledge="auto">
        <!-- 默认注册bean名称,应该是类名首字母小写  -->
        <jms:listener destination="login_msg" ref="smsAuthenCode"/>
    </jms:listener-container>
    <!-- 消息消费者 end -->
    </beans>

主要是 ActiveMQ 的连接配置以及生产者和消费者的配置。上面注释很详细,这里就不做重复介绍了。

web.xml 引入 applicationContext-activemq.xml

代码如下:

     <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      classpath*:spring-mybatis.xml,
      classpath*:applicationContext-redis.xml,
      classpath*:applicationContext-activemq.xml
    </param-value>
      </context-param>

通过 web.xml 加载刚才配置的 applicationContext-activemq.xml。

ActiveMQ 消息监听器的创建(消费者)

配置文件配置完成并引入后,创建消息监听器,监听消息的存在。

在 wang.dreamland.www 包下新建 activemq 包,在 activemq 包下新建消息监听器类 SmsAuthenCode.java:

	@Component
    public class SmsAuthenCode implements MessageListener {
    public void onMessage(Message message) {
        MapMessage mapMessage = (MapMessage) message;
        // 调用SMS服务发送短信   SmsSystem阿里大于发送短信给客户手机实现类

        try {
            // 大于发送短信 Map 来自ActiveMQ 生成者
            SendMessage.sendMessages( mapMessage.getString("code"), mapMessage.getString("telephone") );
            System.out.println( "-----发送消息成功..."+mapMessage.getString("code"));
        } catch (Exception e) {//JMS
            e.printStackTrace();
        }
    }
    }

代码解读如下:

(1)不知道属于哪一层时使用 @Component 注解,将对象交给 Spring 管理;

(2)消息监听类实现消息监听接口 MessageListener 重写 onMessage 方法;

(3)将消息 message 强制转换为 MapMessage;

(4)从 MapMessage 中取出手机验证码和手机号,调用发送短信的方法将短信发送给用户,下文将对这一部分做解释。

阿里大于发送短信类 SendMessage 的创建

创建过程主要包括以下四步。

1.首先,下载阿里大于 SDK,选择 Java下载。

2.然后,将 aliyun-java-sdk-core-3.3.1.jar 和 aliyun-java-sdk-dysmsapi-1.0.0.jar 打包到本地 Maven 仓库。

这两个文件分别在解压目录的 java/api_sdk/aliyun-java-sdk-core 和 java/api_sdk/aliyun-java-sdk-dysmsapi 下。

打开 CMD,输入:

    mvn install:install-file -Dfile=D:\安装包\alidayu\java\api_sdk\aliyun-java-sdk-core\aliyun-java-sdk-core-3.3.1.jar -DgroupId=wang.dreamland.www -DartifactId=dayu-sdk-core -Dversion=3.3.1 -Dpackaging=jar -DgeneratePom=true -DcreateChecksum=true

回车,再输入下面的代码,回车:

    mvn install:install-file -Dfile=D:\安装包\alidayu\java\api_sdk\aliyun-java-sdk-dysmsapi\aliyun-java-sdk-dysmsapi-1.0.0.jar -DgroupId=wang.dreamland.www -DartifactId=dayu-sdk-dysmsapi -Dversion=1.0.0 -Dpackaging=jar -DgeneratePom=true -DcreateChecksum=true

注意: -Dfile 是你的 jar 包所在路径。

在本地仓库的包路径下可查看打包好的 jar 包,如下图所示:

3.接着,在 pom.xml 中引入刚才打包好的阿里大于依赖:

        <!-- 阿里大于依赖 -->
        <dependency>
            <groupId>wang.dreamland.www</groupId>
            <artifactId>dayu-sdk-core</artifactId>
            <version>3.3.1</version>
        </dependency>
        <dependency>
            <groupId>wang.dreamland.www</groupId>
            <artifactId>dayu-sdk-dysmsapi</artifactId>
            <version>1.0.0</version>
        </dependency>

groupId 对应上面的 -DgroupId,artifactId 对应上面的 -DartifactId,version 对应上面的 -Dversion

4.最后,在 activemq 包下新建发送短信类 SendMessage.java:

public class SendMessage {
    private static String accessKeyId = "你的accessKeyId";//你的accessKeyId,参考本文档步骤2
    private static String accessKeySecret = "你的accessKeySecret";//你的accessKeySecret,参考本文档步骤2
    private static String setSignName = "你的短信签名名称";
    private static String dayutemplateCode = "你的短信模板CODE";

    public static void sendMessages(String code,String phone){
        //设置超时时间-可自行调整
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");
        //初始化ascClient需要的几个参数
        final String product = "Dysmsapi";//短信API产品名称
        final String domain = "dysmsapi.aliyuncs.com";//短信API产品域名
        //替换成你的AK
        //初始化ascClient,暂时不支持多region
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId,
                accessKeySecret);
        try {
            DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
        } catch (ClientException e) {
            e.printStackTrace();
        }
        IAcsClient acsClient = new DefaultAcsClient(profile);
        //组装请求对象
        SendSmsRequest request = new SendSmsRequest();
        //必填:待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为20个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式
        request.setPhoneNumbers(phone);
        //必填:短信签名-可在短信控制台中找到
        request.setSignName(setSignName);
        //必填:短信模板-可在短信控制台中找到
        request.setTemplateCode(dayutemplateCode);
        //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
        //"{\"number\":\"" + code + "\"}"
        request.setTemplateParam("{\"code\":\"" + code + "\"}");
        //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
        request.setOutId("yourOutId");
        //请求失败这里会抛ClientException异常
        SendSmsResponse sendSmsResponse = null;
        try {
            sendSmsResponse = acsClient.getAcsResponse(request);
        } catch (ServerException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClientException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if(sendSmsResponse.getCode() != null && sendSmsResponse.getCode().equals("OK")) {
            //请求成功
        }
    }
    }

在 \java\api_demo\alicom-dysms-api\src\main\java\com\alicom\dysms\api 目录下有一个 SmsDemo.java 文件,为发送短信的模板文件,和上面的内容差不多,主要注意上面四个参数改成你自己的,还有模板参数不一样:

request.setTemplateParam("{\"code\":\"" + code + "\"}");

手机快捷登录流程

手机快捷登录的流程,主要包括下面几步。

1.手机号 input 框离焦事件判断。

  • 判断该手机号是否合法;
  • 如果手机号合法,则判断该手机号是否是已激活用户。

2.获取验证码点击事件。

  • 判断步骤1是否返回 true;
  • 返回 true 则发送获取验证码并同时设置倒计时 60s;
  • 倒计时60s结束才能点击重新获取。

3.验证码 input 框离焦事件。

  • 判断验证码是否为6位纯数字(防止恶意登录),是则返回 true。

4.登录点击事件

  • 判断手机号和验证码均返回 true 时则提交表单。
前端 JSP 页面

前端页面,如下图所示:

这里主要说下获取验证码点击事件,其他的和之前的原理一样:

  //获取验证码
    $(function () {
        var go = document.getElementById('go');

        go.onclick = function (ev){
            if(!flag2){
                $("#phone_span").text("手机号码非法或者未注册!").css("color","red");
            }else {
                //  发送短信给用户手机..
                // 1 发送一个HTTP请求,通知服务器 发送短信给目标用户
                var telephone =$("input[name='telephone']").val();// 用户输入的手机号
                    // 用户输入手机号校验通过
                    $("#go").attr("disabled", "disabled");
                    countDown(60);

                    $.ajax({
                        method: 'POST',
                        url: '${ctx}/sendSms',
                        data : {
                            telephone : telephone
                        },
                        success:function(data) {
                            var tt = data["msg"];
                            if(tt){
                                 alert("发送短信成功!");

                            }else{
                                alert("发送短信出错,请联系管理员");
                            }
                        }
                    });
                }


            var oEvent = ev || event;
            //js阻止链接默认行为,没有停止冒泡
            oEvent.preventDefault();

        }
    });

    //倒计时
    function countDown(s){
        if(s <= 0){
            $("#go").text("重新获取");
            $("#go").removeAttr("disabled");
            return;
        }
        /* $("#go").val(s + "秒后重新获取");*/
        $("#go").text(s + "秒后重新获取");
        setTimeout("countDown("+(s-1)+")",1000);
    }

代码解读如下:

(1) $(function () {}); 代表页面加载完成函数;

(2)通过 document.getElementById 获取文档对象 DOM,赋值给 go;

(3)给 go 绑定一个 onclick 点击事件函数;

(4)如果检验手机号返回的是 flag2=false,则给予错误提示;

(5)如果检验手机号成功后,通过属性过滤选择器获取 input 框对象,并通过对象的 val() 方法获取手机号;

(6)通过 $("#go").attr("disabled", "disabled") 方法将获取验证码按钮设置成不可点击;

(7)调用 countDown 倒计时方法,如果倒计时到0秒后,按钮显示重新获取,并移除 disabled 属性,按钮可再次点击,如果 0<s<=60 则按钮上显示 s 秒后重新获取,不可点击,每秒 s-1;

(8)发送 AJAX 请求,映射 URL 为 /sendSms,请求参数是手机号;

(9)success 回调函数返回的结果如果是true,则提示发送成功。否则提示发送失败,联系管理员;

(10)oEvent.preventDefault() 阻止链接默认行为。主要是为了返回错误信息时停留在手机快捷登录标签。

后台创建映射 URL 为 /sendSms 的方法,如下:

  @Autowired
    private UserService userService;
    @Autowired// redis数据库操作模板
    private RedisTemplate<String, String> redisTemplate;// jdbcTemplate HibernateTemplate

    @Autowired
    @Qualifier("jmsQueueTemplate")
    private JmsTemplate jmsTemplate;// mq消息模板.

    /**
     * 发送手机验证码
     * @param model
     * @param telephone
     * @return
     */
    @RequestMapping("/sendSms")
    @ResponseBody
    public Map<String,Object> index(Model model, @RequestParam(value = "telephone",required = false) final String telephone ) {
        Map map = new HashMap<String,Object>(  );
        try { //  发送验证码操作
            final String code = RandStringUtils.getCode();
            redisTemplate.opsForValue().set(telephone, code, 60, TimeUnit.SECONDS);// 60秒 有效 redis保存验证码
            log.debug("--------短信验证码为:"+code);
            // 调用ActiveMQ jmsTemplate,发送一条消息给MQ
            jmsTemplate.send("login_msg", new MessageCreator() {
                public Message createMessage(javax.jms.Session session) throws JMSException {
                    MapMessage mapMessage = session.createMapMessage();
                    mapMessage.setString("telephone",telephone);
                    mapMessage.setString("code", code);
                    return mapMessage;
                }
            });
        } catch (Exception e) {
            map.put( "msg",false );
        }
        map.put( "msg",true );
        return map;

    }

代码解读如下:

(1)通过 @Autowired 注解注入 UserService、RedisTemplate,通过 @Autowired 和 @Qualifier 结合注入 JmsTemplate,参数是配置文件中配置的队列 ID,主要是为了区分有多个类型的情况,指定注入(这里可以不指定),比如,如果再定义一个订阅模式就需要加 @Qualifier 注解进行指定注入:

<!-- 定义JmsTemplate的Topic类型 -->
      <bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
       <!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
          <constructor-arg ref="connectionFactory" />
           <!-- pub/sub模型(发布/订阅) -->
           <property name="pubSubDomain" value="true" />
       </bean>

(2)通过 RandStringUtils 工具类随机生成六位数字验证码,该工具类也放在了文末的百度网盘链接中。

(3)将6位随机验证码保存到 Redis 中,时效为60秒,key=手机号,value=验证码。

(4)调用 ActiveMQ 消息模板对象生产一条消息,发送给 MQ。

注意:第一个参数 login_msg 是配置文件中配置的目的地:

<jms:listener-container destination-type="queue" container-type="default" connection-factory="connectionFactory" acknowledge="auto">
        <!-- 默认注册bean名称,应该是类名首字母小写  -->
        <jms:listener destination="login_msg" ref="smsAuthenCode"/>
    </jms:listener-container>

监听器 smsAuthenCode 监听到消息之后就会调用发送短信功能。

第二个参数是接口 MessageCreator,这里传入的是匿名内部类,通过 new 接口的形式并实现接口方法。

(5)如果有异常则把 false 放入 map,否则把 true 放入 map,最后返回 map。

登录流程

如上图所示,登录流程主要包括以下几个步骤:

  1. 用户点击获取验证码,后台生成6位随机验证码,一份存入 Redis 数据库中,key="手机号"value="验证码"
  2. ActiveMQ 生产者发送消息给 ActiveMQ 中间件,等待消费者消费;
  3. ActiveMQ 消费者监听到消息之后发送短信给用户;
  4. 输入验证码点击登录;
  5. 后台获取用户的手机号以及验证码,根据用户的手机号去 Redis 数据库中取对应的验证码,然后与用户输入的验证码进行比对,一致则登录成功,跳转到 personal.jsp,否则跳转到 login.jsp。

用户输入手机验证码后点击登录,对应的点击事件如下:

 //登录
    $("#phone_btn").click(function () {

        if(checkPhone()&& checkPhoneCode()){
            // 校验用户名和密码
            $("#phone_span").text("").css("color","red");
            $("#phone_form").submit();
        }else {
            alert("请输入手机号和6位验证码!");
        }

    });

以上代码用于检验手机号和手机验证码,正确后提交表单,否则提示错误!

修改 doLogin 方法如下:

@RequestMapping("/doLogin")
    public String doLogin(Model model, @RequestParam(value = "username",required = false) String email,
                          @RequestParam(value = "password",required = false) String password,
                          @RequestParam(value = "code",required = false) String code,
                          @RequestParam(value = "telephone",required = false) String telephone,
                          @RequestParam(value = "phone_code",required = false) String phone_code,
                          @RequestParam(value = "state",required = false) String state,
                          @RequestParam(value = "pageNum",required = false) Integer pageNum ,
                          @RequestParam(value = "pageSize",required = false) Integer pageSize) {

        //判断是否是手机登录
        if (StringUtils.isNotBlank(telephone)) {
            //手机登录
            String yzm = redisTemplate.opsForValue().get( telephone );//从redis获取验证码
            if(phone_code.equals(yzm)){
                //验证码正确
                User user = userService.findByPhone(telephone);
                getSession().setAttribute("user", user);
                model.addAttribute("user", user);
                log.info("手机快捷登录成功");
                return "/personal/personal";

            }else {
                //验证码错误或过期
                model.addAttribute("error","phone_fail");
                return  "../login";
            }

        } else {
            //账号登录

        if (StringUtils.isBlank(code)) {
            model.addAttribute("error", "fail");
            return "../login";
        }
        int b = checkValidateCode(code);
        if (b == -1) {
            model.addAttribute("error", "fail");
            return "../login";
        } else if (b == 0) {
            model.addAttribute("error", "fail");
            return "../login";
        }
        password = MD5Util.encodeToHex(Constants.SALT + password);
        User user = userService.login(email, password);
        if (user != null) {
            if ("0".equals(user.getState())) {
                //未激活
                model.addAttribute("email", email);
                model.addAttribute("error", "active");
                return "../login";
            }
            log.info("用户登录登录成功");
            getSession().setAttribute("user", user);
            model.addAttribute("user", user);
            return "/personal/personal";
        } else {
            log.info("用户登录登录失败");
            model.addAttribute("email", email);
            model.addAttribute("error", "fail");
            return "../login";
        }

    }

    }

代码解读如下:

(1)主要是根据手机号是否为空判断是手机登录还是账号登录,如果手机号不为空则为手机快捷登录;

(2)根据手机号从 Redis 中获取手机验证码,如果 Redis 中的验证码和用户输入的验证码一致,则登录成功,根据手机号查询用户,将用户 user 存入 Session 和 model 中,返回个人中心页面;

(3)如果不一致,说明验证码错误或者过期,将 phone_fail 添加到 model 中,并返回到登录页面。

最后重新启动项目测试,手机快捷登录成功!

第07课百度网盘地址:

链接:https://pan.baidu.com/s/1lm25TwWulc8l8tFaI1wo9A 密码:aozj

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

exodus3

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值