springboot项目系列-论坛系统04登录注册实现

springboot项目系列-论坛系统04登录注册实现

论坛地址:http://www.cywloveyou.top

注册(使用AJAX,邮件任务,RabbitMQ)

首先跳转到注册页面,进行注册,跳转到后台,判断数据库里是否有该用户,如果有,注册失败,如果没有,注册成功,使用RabbitMQ发送消息给消费者,消费者给用户发邮件,因为此时用户注册字段里面有邮件输入,假如用户使用的是真实邮件,则可以收到,考虑到发邮件会有时间间隔导致用户体验不好,所以加了RabbitMQ,多线程来实现,这样的好处就是,另外一个线程发邮件,完全不影响用户的体验,注册完即跳转…是不是很nice,完全可以使用springboot的异步任务,但是为了多学一些东西,还是用MQ吧,以后会有用…

前台代码
<!DOCTYPE html>
<html lang="en"  xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
  <title>登录注册表单</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <script type="application/x-javascript"> addEventListener("load", function() {
     
      setTimeout(hideURLbar, 0); }, false); function hideURLbar(){
     
      window.scrollTo(0,1); } </script>
  <link rel="stylesheet" th:href="@{/css/style.css}" href="./../../static/css/style.css" type="text/css" media="all">
  <script th:src="@{/js/jquery-1.7.2.js}" type="text/javascript"></script>
</head>
<body>
<h1>快来注册登录吧</h1>
<div class="container w3layouts agileits">
  <div class="login w3layouts agileits">
    <h2>登 录</h2>
    <div>
      <input id="username" type="text" name="username" placeholder="账号" required="required">
      <input id="password" type="password" name="password" placeholder="密码" required="required">

      <ul class="tick w3layouts agileits">
        <li>
          <input type="checkbox" id="brand1" value="">
          <label for="brand1"><span></span>记住我</label>
        </li>
      </ul>
      <div class="send-button w3layouts agileits">
        <input  id="btn" type="submit" value="登 录">
      </div>
    </div>
    <a th:href="@{/admin/toEmailLogin}"><span style="color: red" id="span">忘记密码?邮箱验证登录</span></a>
    <span  style="visibility: visible"></span>
    <div class="social-icons w3layouts agileits">
      <p>- 其他方式登录 -</p>
      <ul>
        <li class="qq"><a href="#">
          <span class="icons w3layouts agileits"></span>
          <span class="text w3layouts agileits">QQ</span></a></li>
        <li class="weixin w3ls"><a href="#">
          <span class="icons w3layouts"></span>
          <span class="text w3layouts agileits">微信</span></a></li>
        <li class="weibo aits"><a href="#">
<!--        <li class="email"><a href="#">-->
          <span class="icons agileits"></span>
          <span class="text w3layouts agileits">微博</span></a></li>
        <div class="clear"> </div>
      </ul>
    </div>
    <div class="clear"></div>
  </div>

  <div class="register w3layouts agileits">
    <h2>注 册</h2>
    <div action="#" method="post">
      <input type="text" id="r_username" name="username" placeholder="账号" required="required">
      <input type="text" id="r_nickname" name="nickname" placeholder="昵称" required="required">
      <input type="text" id="r_email" name="email" placeholder="QQ邮箱(请填写真实邮箱)" required="required">
      <input type="password" id="r_password" name="password" placeholder="密码" required="required">
<!--      <input type="text" placeholder="确认密码" required="">-->

      <div class="send-button w3layouts agileits">
        <input id="btnRegister" type="submit" value="免费注册"><br>
        <span id="r_span"></span>
      </div>
    </div>
    <div class="clear"></div>
  </div>

  <div class="clear"></div>

</div>

<div class="footer w3layouts agileits">
  <a th:href="@{http://beian.miit.gov.cn/}" target="_blank">京ICP备2020046619号</a>
</div>

<script type="text/javascript">
    $(function () {
     
     
       $("#btn").click(function () {
     
     
         var span = $("#span");
         var username = $("#username").val();
            var password = $("#password").val();
         //定义邮箱正则
         var emailReg = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;


            if (username == ""){
     
     
              // var msg = "用户名不能为空"
              // span.css("color","red");
              // span.html(msg);
              alert('亲,账号只能是6位以上纯数字!!')
              return false;
            }if (password == ""){
     
     
              // var msg = "密码不能为空"
              // span.css("color","red");
              // span.html(msg);
           alert('亲,请输入密码');
              return false;
            }
            $.post("/admin/ajaxLogin",{
     
     username:username,password:password},function (data) {
     
     
                var span = $("#span");
                if (data.toString() == "fail" ){
     
     
                    // var msg = "用户名或密码错误啦,重来吧"
                    //  span.css("color","red");
                    //   span.html(msg);
                  alert('用户名或密码错误,重新来');
                  }else if(data.toString() == "success"){
     
     
                   alert('登录成功');
                  window.location.href="/admin/toIndex"
                 }else if (data.toString() == "over"){
     
     
                  // var msg = "三次了,怀疑你在搞事情,一分钟后再来"
                  // span.css("color","red");
                  // span.html(msg);
                  alert('你在搞事情吗?一分钟以后再来吧');
                  }
              });
          });



       $("#btnRegister").click(function () {
     
     
         var span = $("#r_span");

         var username = $("#r_username").val();
            var password = $("#r_password").val();
            var nickname = $("#r_nickname").val();
            var email = $("#r_email").val();
            //定义邮箱正则
            var emailReg = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;

            var usernameReg = /^-?[1-9]\d*$/;
            if (!usernameReg.test(username)||username.length<6){
     
     
              var msg = "账号只能是6位以上纯数字";
              span.css("color","red");
              span.html(msg);
              return false;
            }
         if (nickname == ""){
     
     
           var msg = "请输入昵称"
           span.css("color","red");
           span.html(msg);
           return false;
         }


         if (!emailReg.test(email)){
     
     
              var msg = "请输入正确邮箱"
              span.css("color","red");
              span.html(msg);
              return false;
            }
         if (password.length<6){
     
     
           var msg = "请输入6位以上密码"
           span.css("color","red");
           span.html(msg);
           return false;
         }
            $.post("/user/ajaxRegister",{
     
     username:username,password:password,nickname:nickname,email:email},function (data) {
     
     
                if (data.toString() == "fail" ){
     
     
                    alert('此账号已存在,换个吧');
                  }else if (data.toString() == "EmailFail"){
     
     
                  alert('邮箱已被注册,如果是你的邮箱,请去邮箱登录');
                } else if(data.toString() == "success"){
     
     
                   alert('注册成功,去登录吧');
                 }
              });
          });



    });



</script>
</body>
</html>

后台注册逻辑代码

   /**
     * ajaxRegister
     * @param user
     * @Author Cyw
     * @return
     */
    @RequestMapping("/ajaxRegister")
    @ResponseBody
    public String ajaxRegister(User user){
    
    
        User u = userService.queryUserByName(user.getUsername());
        User uEmail = userService.queryUserByEmail(user.getEmail());
        if (u != null){
    
    
            System.out.println("账号已经存在");
            return "fail";
        }
        if (uEmail != null){
    
    
            System.out.println("邮箱已存在");
            return "EmailFail";
        }
        userService.addUser(user);
        return "success";
    }

  

添加用户到数据库业务层

 @Override
    public int addUser(User user) {
    
    
        redisUtil.incr("userCount",1);
        user.setPassword(MD5Utils.code(user.getPassword()));
        user.setCreateTime(new Date());
        rabbitTemplate.convertAndSend("hello",user.getEmail());
        return userDao.addUser(user);
    }

RabbitMQ消费者消费消息

/**
 * @Author: CYW
 * @Date: 2021/1/5 11:53
 * 监听用户注册,注册成功给用户发邮件
 */
@Component
@RabbitListener(queues = "hello")
public class HelloConsumer {
    
    
    @Autowired
    private SendMail sendMail;
    @RabbitHandler
    public void executeHello(String email){
    
    
        try {
    
    
            sendMail.sendmail(email,"欢迎来到Cyw的小家");
        }catch (Exception e){
    
    
            System.out.println(email+"不是一个正确的邮箱");
        }

    }
}
邮件任务

首先配置

# 邮件任务
[email protected]
# 这个是你的qq邮箱li打开权限的验证,不是qq密码,
# 具体自己百度
spring.mail.password=xxxx
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true

邮件工具类


/**
 * @Author: CYW
 * @Date: 2020/12/20 14:11
 */
@Component
public class SendMail {
    
    
    @Autowired
    private JavaMailSenderImpl mailSender;

    public void sendmail(String userEmail){
    
    
        //邮件设置1:一个简单的邮件
        SimpleMailMessage message = new SimpleMailMessage();
        message.setSubject("通知");
        message.setText("欢迎加入Cyw的小家,感谢陪伴");
        message.setTo(userEmail);
        message.setFrom("[email protected]");
        mailSender.send(message);
    }

}

此时注册的功能算是完成

登录功能,一些样式可以不要

前台代码和注册的是一个页面
后台代码(shiro+redis)前提你要有redis

redis的配置

#Redis连接信息
# Redis服务器地址
spring.redis.host=你的机器ip
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# Redis服务器超时时间(毫秒)
spring.redis.timeout=5000
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)默认-1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0

controller:

 /**
     * ajaxLogin
     * @Author Cyw
     * @param username
     * @param password
     * @param attributes
     * @param session
     * @return
     */
        @RequestMapping("/ajaxLogin")
        @ResponseBody
        public String ajaxLogin(String username,
                                 String password,
                        HttpSession session) {
    
    
            //String msg = "";
            //获取当前用户的信息
            System.out.println("ajaxLogin......");
            System.out.println("账号:"+username+"密码"+password);
            try {
    
    
                System.out.println("执行shiro");
                AuthenticationToken token=new UsernamePasswordToken(username,MD5Utils.code(password));
                //调用Shiro进行认证
                SecurityUtils.getSubject().login(token);
                //从Shiro中拿出User对象,放到session中
                User user=(User)SecurityUtils.getSubject().getPrincipal();
                session.setAttribute("user",user);
                System.out.println("登录成功!");
                //msg = "success";
            } catch (UnknownAccountException e) {
    
    //shiro抛出的异常
                System.out.println("登录失败");
                //msg = "fail";
                return "fail";
            } catch (IncorrectCredentialsException e){
    
    
                System.out.println("登录失败");
                //msg = "fail";
                return "fail";
            } catch (DisabledAccountException e) {
    
    
                return "over";
            }
            return "success";
        }
    
shiro+redis部分(判断登录的权限可以进那些Controller,和一些配置)

shiro+redis实现登录次数验证,超过三次锁定一分钟

package com.cyw.shiroConfig;

import com.cyw.entity.User;
import com.cyw.service.UserService;
import com.cyw.util.MD5Utils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.util.concurrent.TimeUnit;

/**
 * @Author: CYW
 * @Date: 2020/11/12 17:14
 */
//自定义的UserRealm类
public class UserRealm extends AuthorizingRealm {
    
    
    @Autowired
    private UserService userService;
//=========================redis+shiro===============================
    //redisTemplate
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //用户登录次数计数  redisKey 前缀
    private String SHIRO_LOGIN_COUNT = "login_count_";
    //用户登录是否被锁定  redisKey 前缀
    private String SHIRO_IS_LOCK = "lock_";
//=========================redis+shiro===============================

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        System.out.println("执行了授权");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //拿到当前登录对象,得到权限
        Subject subject = SecurityUtils.getSubject();
        User currentUser = (User) subject.getPrincipal();//拿到User对象
        info.addStringPermission(currentUser.getPerms());//获取当前用户的权限
        return info;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    
        System.out.println("执行了认证");


        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        String userName=userToken.getUsername();
        String userPassword=new String(userToken.getPassword());


// =================redis+shiro==============
        //访问一次,计数一次
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        opsForValue.increment(SHIRO_LOGIN_COUNT+userName, 1);  //每次增加1
        System.out.println(userName+":账号登陆的次数是:"+opsForValue.get(SHIRO_LOGIN_COUNT+userName)) ;
        //如果这个账号登陆异常,则在登陆页面提醒。
        if(Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT+userName))>=2) {
    
    
            if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK + userName))) {
    
    
                //计数大于3次,设置用户被锁定一分钟
                throw new DisabledAccountException("输入错误已超过3次,我怀疑你在搞事情,禁登1分钟!");
            }
        }
        //实现锁定
        if(Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT+userName))>=2){
    
    
            opsForValue.set(SHIRO_IS_LOCK+userName, "LOCK");  //锁住这个账号,值是LOCK。
            stringRedisTemplate.expire(SHIRO_IS_LOCK+userName, 1, TimeUnit.MINUTES);  //expire  变量存活期限
        }
//========================redis+shiro============================
        User user = userService.queryUserByName(userName);

        if (user == null) {
    
    
            throw new UnknownAccountException("用户名或密码错误!");
        }else if (!user.getPassword().equals(userPassword)){
    
    
            throw new IncorrectCredentialsException("用户名或密码错误");
        }
        //================redis+shiro===================
        //清空登录计数
        opsForValue.set(SHIRO_LOGIN_COUNT+userName, "0");
        //清空锁
        opsForValue.set(SHIRO_IS_LOCK+userName, "");
        //================redis+shiro===================



        //密码认证,shiro做,第一个参数将user传到授权方法中
        return new SimpleAuthenticationInfo(user, user.getPassword(), "");
    }
}

shiro判断哪些权限可以访问哪些东西

package com.cyw.shiroConfig;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import lombok.experimental.Accessors;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Author: CYW
 * @Date: 2020/11/12 17:04
 */
@Accessors(chain = true)
@Configuration
@SuppressWarnings("all")
public class ShiroConfig {
    
    

    //1.创建realm对象 ,需要自定义类
    @Bean
    public UserRealm userRealm() {
    
    
        return new UserRealm();
    }

    //2.DefaultWebSecurityManager
    @Bean("securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm user) {
    
    
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联UserRealm
        securityManager.setRealm(userRealm());
        return securityManager;
    }

    //3.ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
    
    
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);

        /**
         * 添加shiro内置的过滤器
         * anon:无需认证就可以访问
         * authc:必须认证了才可以访问
         * user:必须拥有记住我功能才可以访问
         * perms:拥有对某个 资源 的权限才可以访问
         * role:拥有某个角色权限才能访问
         */
        //拦截
        Map<String, String> filterMap = new LinkedHashMap<>();
       /* filterMap.put("/user/add","authc");
        filterMap.put("/user/update","authc");*/

        //正常情况下没有授权会跳转到未授权页面
//        filterMap.put("/user/add", "perms[admin]");
//        filterMap.put("/user/update", "perms[admin]");


        filterMap.put("/admin/login","anon");
        filterMap.put("/admin/logout","perms[user]");
        filterMap.put("/admin/blogs/input","perms[user]");
        filterMap.put("/admin/blogs","perms[user]");
        filterMap.put("/admin/toIndex","anon");
        filterMap.put("/admin/ajaxLogin","anon");
        filterMap.put("/user/toRegister","anon");
        filterMap.put("/user/register","anon");
        filterMap.put("/user/likeCount","anon");
        filterMap.put("/admin/*", "perms[admin]");
        filterMap.put("/user/*", "perms[user]");

        bean.setFilterChainDefinitionMap(filterMap);

        //设置登录的请求
        bean.setLoginUrl("/admin");

        //设置跳转到未授权页面
        bean.setUnauthorizedUrl("/unauth");
        return bean;
    }

    //配置整合shrio-thymeleaf的bean
    @Bean
    public ShiroDialect getShiroDialect() {
    
    
        return new ShiroDialect();
    }

}

为了方便,加了一个redis的工具类

package com.cyw.shiroConfig;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis配置类
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    
    

    /**
     * retemplate相关配置
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    
    

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSeial.setObjectMapper(om);

        // 值采用json序列化
        template.setValueSerializer(jacksonSeial);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        // 设置hash key 和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jacksonSeial);
        template.afterPropertiesSet();

        return template;
    }

    /**
     * 对hash类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
    
    
        return redisTemplate.opsForHash();
    }

    /**
     * 对redis字符串类型数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
    
    
        return redisTemplate.opsForValue();
    }

    /**
     * 对链表类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
    
    
        return redisTemplate.opsForList();
    }

    /**
     * 对无序集合类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
    
    
        return redisTemplate.opsForSet();
    }

    /**
     * 对有序集合类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
    
    
        return redisTemplate.opsForZSet();
    }

}

此时登录也算告一段落,大功告成,坚信自己,男人不喊累,没有一段努力是白费,还有一口气,就得坚持哦!!!

猜你喜欢

转载自blog.csdn.net/weixin_44219219/article/details/111771241
今日推荐