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();
}
}
此时登录也算告一段落,大功告成,坚信自己,男人不喊累,没有一段努力是白费,还有一口气,就得坚持哦!!!