登录模块主要实现的功能有:
① 登录功能: 用户输入学号,密码,验证码验证登录
② 用户登录日志: 用户登录成功后,将用户的学号,登录时间,ip地址存入数据库
一. 登录功能
-
验证码模块实现的思想: 后台将生成验证码传给前端,并同时保存到session中, 用户输入学号,密码,验证码发送给后端, 后端取出session中的验证码进行对比,如果验证码错误,直接返回字符串"验证码错误", 如果验证码正确,继续查找是否存在该用户,如果存在, 返回"用户存在", 不存在返回 “用户不存在”
-
验证码实现: 我是整合了Kaptcha,实现了验证码的功能, 大佬博客: 1.这篇是整合kaptcha步骤,基本上是照着做的 , 2.这篇我参考了这篇的验证码校验部分,将其添加到自己的登录验证代码中
-
遇到的问题: 我做这块的时候,当用户进入登录页面的时候,后台不是向前端发送验证码然后将这个验证码存到session中么,但是当我携带学号,密码,和验证码发送到后台时,第一次存的验证码竟然是null了,看了一篇博客试了试,哎,您猜怎么着,解决了, 大佬博客: 解决axios跨域请求两次session不一样的问题 ,在这里声明一下,我的后端没有配置过跨域请求,都是使用的注解,所以看了这篇文章也是解决了我的问题
-
-
以上就是我在做这个模块遇到的问题以及解决方案和参考的博客, 然后接下来就是代码部分
-
后端代码一: 生成验证码
package com.zhl.controller; import com.google.code.kaptcha.Constants; import com.google.code.kaptcha.Producer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.awt.image.BufferedImage; /** * @author ZhangHailong * @date 2022/3/3 - 12:38 * @project_name */ @Controller @RequestMapping("/kaptcha") public class KaptchaController { @Autowired private Producer captchaProducer = null; @RequestMapping(value = "/getKaptchaImage.do") @CrossOrigin(allowCredentials="true") public void getKaptchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpSession session = request.getSession(); response.setDateHeader("Expires", 0); response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); response.addHeader("Cache-Control", "post-check=0, pre-check=0"); response.setHeader("Pragma", "no-cache"); response.setContentType("image/jpeg"); //生成验证码 String capText = captchaProducer.createText(); System.out.println("生成的验证码: " + capText); session.setAttribute(Constants.KAPTCHA_SESSION_KEY, capText); //向客户端写出 BufferedImage bi = captchaProducer.createImage(capText); ServletOutputStream out = response.getOutputStream(); ImageIO.write(bi, "jpg", out); try { out.flush(); } finally { out.close(); } } }
-
后端代码二: 验证登录信息
// 用户登录 @RequestMapping(value = "/loginUser.do", method = RequestMethod.POST) @ResponseBody @CrossOrigin(allowCredentials="true") public String loginUserService(@RequestBody LoginUser loginUser, HttpServletRequest request) { System.out.println(loginUser); // 先判断验证码 // 拿到session中的验证码的值 String verifyCodeExepected = (String)request.getSession().getAttribute( com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY); System.out.println("session存储的:" + verifyCodeExepected); // 拿到提交的验证码 String code = loginUser.getCode(); System.out.println("提交的验证码:" + code); if(code != null && verifyCodeExepected.equalsIgnoreCase(code)) { // 验证码正确,继续验证登录 User user = new User(); user.setId(Integer.valueOf(loginUser.getId())); user.setPassword(loginUser.getPassword()); // 将用户登录的ip作为loginUser的参数,以供切面拿取 String ipAddress = request.getRemoteAddr(); User userExist = userService.loginUser(user, ipAddress); if (userExist != null) { return "用户存在"; } return "用户不存在"; } return "验证码错误"; }
这里要说明的是,为什么loginUser有两个参数,实际上在做用户验证的时候只需要user这个参数就行了,它里面包含了用户的id和password,第二个参数ipAddress是为登录日志模块服务的,先按下不表,下面会解释
-
前端
handleSubmit(event) { this.$refs.loginForm.validate((valid) => { if (valid) { this.loading = true; this.$http .post("http://localhost:8888/user/loginUser.do", this.loginForm,{withCredentials:true}) .then((res) => { if (res.data == "用户存在") { this.loading = false; // localStorage.setItem("userId", this.loginForm.id); sessionStorage.setItem("userId", this.loginForm.id); this.$router.push({ path: "/" }); // 显示登录成功的提示框 this.$message({ message: this.loginForm.id + ',登录成功', type: 'success' }); } else if (res.data == "验证码错误") { this.loading = false; this.$alert("验证码错误", "提示", { confirmButtonText: "确认", }); this.$refs.codeClickPosition.focus(); this.loginForm.code = ""; } else { this.loading = false; this.$alert("用户名或密码错误!", "提示", { confirmButtonText: "确认", }); this.$refs.idClickPosition.focus(); } }); } else { console.log("error submit!"); return false; } }); },
在这里,如果登录成功了,将用户的学号保存到session中,存这个学号的目的是,在用户登录成功以后,主页的右上角不是要显示他的头像和昵称还有其他信息嘛,所以想到时候用学号做一个查询,用的是sessionStorage而不是localStorage,具体的区别大家可以查看: sessionStorage和sessionStorage的区别, 我这里使用的是sessionStorage,用户在关闭网站后身份信息就清空了,需要重新登录
二.用户登录日志
- 参考的博客: SSM实现登录日志
- 实现思路: 在业务层userServiceImp的loginUser方法后使用 @AfterReturning 后置通知, loginUser我的返回值是User,也就是说,如果有该学生,那么该方法的执行结果是该学生的信息,如果没有该学生,返回值是null,根据是不是null再判断是否要添加这个学生的登录日志,当然你的返回值也可以是int型的,说明该查找操作作用在哪一行,根据是否是-1选择添加登录日志
- 实现步骤
- 配置AspectJ开发环境
-
1.maven依赖
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency> // 其他.... // 插件部分 <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
-
2.基于注解的AOP
-
① 定义业务接口和实现类,我的业务接口就是userService,其实现类就是userServiceImp,如下
package com.zhl.service; import java.util.List; /** * @author ZhangHailong * @date 2022/2/23 - 13:45 * @project_name */ public interface UserService { // 用户登录 User loginUser(User user, String ipAddress); }
package com.zhl.service.impl; import com.zhl.dao.UserDao; import com.zhl.entity.User; import com.zhl.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * @author ZhangHailong * @date 2022/2/23 - 13:48 * @project_name */ @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public User loginUser(User user, String ipAddress) { return userDao.findUserByIdAndPwd(user); }
-
② 定义登录日志实体类
package com.zhl.entity; import org.springframework.format.annotation.DateTimeFormat; import java.util.Date; /** * @author ZhangHailong * @date 2022/3/11 - 19:41 * @project_name 登录日志实体类 */ public class LoginLog { // 用户学号 private Integer userId; // 登录时间 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date loginTime; // ip地址 private String ipAddress; public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public Date getLoginTime() { return loginTime; } public void setLoginTime(Date loginTime) { this.loginTime = loginTime; } public String getIpAddress() { return ipAddress; } public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; } @Override public String toString() { return "LoginLog{" + ", userId=" + userId + ", loginTime=" + loginTime + ", ipAddress='" + ipAddress + '\'' + '}'; } }
-
③ 定义切面类, 目标对象就是userServiceImp包下的loginUser()
package com.zhl.aspect; import com.zhl.dao.LoginLogDao; import com.zhl.entity.LoginLog; import com.zhl.entity.User; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Date; /** * @author ZhangHailong * @date 2022/3/11 - 19:38 * @project_name */ @Aspect @Component public class LoginLogAspect { @Autowired private LoginLogDao loginLogDao; @AfterReturning(value = "execution(* *..UserServiceImpl.loginUser(..))", returning = "result") public void addLoginLog(JoinPoint jp,User result) { if (result != null) { LoginLog loginLog = new LoginLog(); // 登录的id loginLog.setUserId(result.getId()); // 登录时间 loginLog.setLoginTime(new Date()); // 获取loginUser方法的第二个参数,也就是登录的用户的ip,择取需要的填充到LoginLog对象中 loginLog.setIpAddress(jp.getArgs()[1].toString()); loginLogDao.insertLogDao(loginLog); } } }
loginUser的第二个参数ipAddress在这里就用到了,AOP是可以通过JoinPoint获取目标方法的参数的,那么在这里拿到,就可以将信息们写入数据库了
-
④ applicationContext.xml添加
<!-- 包扫描 --> <context:component-scan base-package="com.yy.homework.aspects"/> <!-- 配置登陆日志的 AOP 注解--> <aop:aspectj-autoproxy/>
-
-
3.持久层代码
// Dao接口 package com.zhl.dao; import com.zhl.entity.LoginLog; /** * @author ZhangHailong * @date 2022/3/11 - 19:48 * @project_name */ public interface LoginLogDao { int insertLogDao(LoginLog loginLog); }
// mapper.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zhl.dao.LoginLogDao"> <!--select|insert|update|delete语句--> <insert id="insertLogDao"> INSERT INTO loginLog(userId,loginTime,ipAddress) values(#{userId},#{loginTime},#{ipAddress}) </insert> </mapper>
-
- 配置AspectJ开发环境
登录的两个功能到这也基本上结束了,谢谢您的观看,新手小白,如果有问题,希望大佬们斧正,也欢迎参与评论