第2章Spring Boot实践,开发社区登录模块【仿牛客网社区论坛项目】

第2章Spring Boot实践,开发社区登录模块【仿牛客网社区论坛项目】

  • 前言
  • 推荐
  • 项目总结
    • 第2章Spring Boot实践,开发社区登录模块
      • 1.发送邮件
        • 配置
        • MailClient
        • 测试
      • 2.开发注册功能
        • 访问注册页面
        • 提交注册数据
        • 激活注册账号
      • 3.会话管理
        • 体验cookie
        • 体验session
      • 4.生成验证码
        • 配置
        • KaptchaConfig
        • 前端
      • 5.开发登录、退出功能
        • LoginTicket
        • LoginTicketMapper
        • 测试Dao
        • UserService
        • LoginController
        • 前端
        • 退出功能
        • 忘记密码
      • 6.显示登录信息
        • 体验拦截器
        • 配置拦截器
        • 测试
        • 登录拦截器
        • 前端
      • 7.账号设置
        • UserController
        • 前端
        • 配置
        • UserService
        • UserController
        • 前端
        • 测试
        • 修改密码
      • 8.检查登录状态
        • LoginRequired
        • LoginRequiredInterceptor
        • WebMvcConfig
  • 最后

前言

2023-4-30 20:42:51

以下内容源自【Java面试项目】
仅供学习交流使用

推荐

仿牛客网项目【面试】

项目总结

第2章Spring Boot实践,开发社区登录模块

1.发送邮件

配置

pom.xml

        <!-- spring 邮箱       --><!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId><version>2.7.4</version></dependency>

配置

# MailProperties
spring.mail.host=smtp.qq.com
spring.mail.port=465
spring.mail.username=邮箱地址
spring.mail.password=密钥
spring.mail.protocol=smtps
spring.mail.properties.mail.smtp.ssl.enable=true
MailClient

新增:/util/MailClient

package com.jsss.community.util;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;/*** 邮箱工具类*/
@Component
public class MailClient {private static final Logger logger = LoggerFactory.getLogger(MailClient.class);@Autowired(required = true)private JavaMailSender mailSender;@Value("${spring.mail.username}")private String from;public void sendMail(String to, String subject, String content) {try {MimeMessage message = mailSender.createMimeMessage();MimeMessageHelper helper = new MimeMessageHelper(message);helper.setFrom(from);helper.setTo(to);helper.setSubject(subject);helper.setText(content, true);mailSender.send(helper.getMimeMessage());} catch (MessagingException e) {logger.error("发送邮件失败" + e.getMessage());}}
}

新增:/templates/mail/demo.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>邮件实例</title>
</head>
<body><p>欢迎你,<span style="color: red" th:text="${username}"></span>!</p>
</body>
</html>
测试

新增:test:MailTests

package com.jsss.community;import com.jsss.community.util.MailClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MailTests {@Autowiredprivate MailClient mailClient;@Autowiredprivate TemplateEngine templateEngine;String to="3063494684@qq.com";@Testpublic void testTextMail(){mailClient.sendMail(to,"Test","Welcome");}@Testpublic void testHtmlMail(){Context context=new Context();context.setVariable("username","sunday");String content = templateEngine.process("/mail/demo", context);System.out.println(content);mailClient.sendMail(to,"HTML",content);}
}

测试结果:
在这里插入图片描述

在这里插入图片描述

2.开发注册功能

访问注册页面

新增:controller/LoginController

package com.jsss.community.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;@Controller
public class LoginController {@GetMapping (path = "/register")public String getRegisterPage(){return "site/register";}}

修改:index.html
修改头部尾部,用来复用

修改:register.html

修改html标签
修改静态路径
复用index头部

测试:点击顶部区域内的链接,打开注册页面。

提交注册数据

配置:

pom.xml

        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version></dependency>

配置:

# community
community.path.domain=http://localhost:8080
community.path.upload=d:/work/data/upload

新增:/util/CommunityUtil

package com.jsss.community.util;import org.springframework.util.DigestUtils;import java.util.UUID;import org.apache.commons.lang3.StringUtils;public class CommunityUtil {// 生成随机字符串public static String generateUUID() {return UUID.randomUUID().toString().replaceAll("-", "");}// MD5加密// hello -> abc123def456// hello + 3e4a8 -> abc123def456abcpublic static String md5(String key) {if (StringUtils.isBlank(key)) {return null;}return DigestUtils.md5DigestAsHex(key.getBytes());}}

修改:/templates/mail/activation.html

<!doctype html>
<html lang="en" xmlns:th="http://www.thymleaf.com">
<head><meta charset="utf-8"><link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/><title>牛客网-激活账号</title>
</head>
<body><div><p><b th:text="${email}">xxx@xxx.com</b>, 您好!</p><p>您正在注册牛客网, 这是一封激活邮件, 请点击 <a th:href="${url}}">http://www.nowcoder.com/activation/abcdefg123456.html</a>,激活您的牛客账号!</p></div>
</body>
</html>

新增:UserService.register()

package com.jsss.community.service;import com.jsss.community.dao.UserMapper;
import com.jsss.community.entity.User;
import com.jsss.community.util.CommunityUtil;
import com.jsss.community.util.MailClient;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;@Service
public class UserService{@AutowiredUserMapper userMapper;@Autowiredprivate MailClient mailClient;@Autowiredprivate TemplateEngine templateEngine;@Value("${community.path.domain}")private String domain;@Value("${server.servlet.context-path}")private String contextPath;public User findUserById(int id){return userMapper.selectById(id);}public Map<String,Object> register(User user){Map<String,Object> map=new HashMap<>();// 空值处理if (user == null) {throw new IllegalArgumentException("参数不能为空!");}if (StringUtils.isBlank(user.getUsername())) {map.put("usernameMsg", "账号不能为空!");return map;}if (StringUtils.isBlank(user.getPassword())) {map.put("passwordMsg", "密码不能为空!");return map;}if (StringUtils.isBlank(user.getEmail())) {map.put("emailMsg", "邮箱不能为空!");return map;}// 验证账号User u = userMapper.selectByName(user.getUsername());if (u != null) {map.put("usernameMsg", "该账号已存在!");return map;}// 验证邮箱u = userMapper.selectByEmail(user.getEmail());if (u != null) {map.put("emailMsg", "该邮箱已被注册!");return map;}// 注册用户user.setSalt(CommunityUtil.generateUUID().substring(0, 5));user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));user.setType(0);user.setStatus(0);user.setActivationCode(CommunityUtil.generateUUID());user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));user.setCreateTime(new Date());userMapper.insertUser(user);// 激活邮件Context context = new Context();context.setVariable("email", user.getEmail());// http://localhost:8080/community/activation/101/codeString url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();context.setVariable("url", url);String content = templateEngine.process("/mail/activation", context);mailClient.sendMail(user.getEmail(), "激活账号", content);return map;}}

新增:LoginController.register()

package com.jsss.community.controller;@Controller
public class LoginController {@AutowiredUserService userService;@GetMapping (path = "/register")public String getRegisterPage(){return "site/register";}@PostMapping("/register")public String register(Model model, User user){Map<String, Object> map = userService.register(user);if (map==null|| map.isEmpty()){model.addAttribute("msg","注册成功,我们已经向你的邮箱发送了一封激活邮件,请尽快激活");model.addAttribute("target","index");return "site/operate-result";}else {model.addAttribute("usernameMsg",map.get("usernameMsg"));model.addAttribute("passwordMsg",map.get("passwordMsg"));model.addAttribute("emailMsg",map.get("emailMsg"));return "site/register";}}
}

修改:operate-result.html

修改:register.html
修改:表单的提交

测试

  • 通过表单提交数据。
  • 服务端验证账号是否已存在、邮箱是否已注册。
  • 服务端发送激活邮件。
激活注册账号

新增:/util/CommunityConstant

package com.jsss.community.util;public interface CommunityConstant {/*** 激活成功*/int ACTIVATION_SUCCESS = 0;/*** 重复激活*/int ACTIVATION_REPEAT = 1;/*** 激活失败*/int ACTIVATION_FAILURE = 2;}

新增:UserService.activation()

    public int activation(int userId,String code){User user = userMapper.selectById(userId);if (user.getStatus() == 1) {return ACTIVATION_REPEAT;} else if (user.getActivationCode().equals(code)) {userMapper.updateStatus(userId, 1);return ACTIVATION_SUCCESS;} else {return ACTIVATION_FAILURE;}}

新增:LoginController.activation()

    @GetMapping (path = "/login")public String getLoginPage(){return "site/login";}// http://localhost:8080/community/activation/101/code@RequestMapping(path = "/activation/{userId}/{code}",method = RequestMethod.GET)public String activation(Model model, @PathVariable("userId") int userId,@PathVariable("code") String code){int result = userService.activation(userId, code);if (result== CommunityConstant.ACTIVATION_SUCCESS){model.addAttribute("msg","激活成功,您的账号已经可以正常使用了!");model.addAttribute("target","/login");}else if (result==CommunityConstant.ACTIVATION_REPEAT){model.addAttribute("msg","无效操作,该账号已经激活过了!");model.addAttribute("target","/index");}else{model.addAttribute("msg","激活失败,您提供的激活码不正确!");model.addAttribute("target","/index");}return "site/operate-result";}

修改:login.html
静态路径、头部

修改:index.html
头部:登录路径

测试

  • 点击邮件中的链接,访问服务端的激活服务。

修改:login.html
修改验证码静态图像路径

3.会话管理

体验cookie

新增:AlphaController.setCookie()

新增:AlphaController.getCookie()

 //Cookie示例@RequestMapping(path = "/cookie/set",method = RequestMethod.GET)@ResponseBodypublic String setCookie(HttpServletResponse response){// 创建cookieCookie cookie=new Cookie("code", CommunityUtil.generateUUID());// 设置cookie生效的范围cookie.setPath("/community/alpha");// 设置cookie的生存时间cookie.setMaxAge(60*10);// 发送cookieresponse.addCookie(cookie);return "set cookie";}@RequestMapping(path = "/cookie/get",method = RequestMethod.GET)@ResponseBodypublic String getCookie(@CookieValue("code") String code){System.out.println(code);return "get cookie";}

测试:

访问:http://localhost:8080/community/alpha/cookie/set

在这里插入图片描述
访问:http://localhost:8080/community/alpha/cookie/get

在这里插入图片描述

体验session

新增:AlphaController.setSession()

新增:AlphaController.getSession()

    //session示例@RequestMapping(path = "/session/set",method = RequestMethod.GET)@ResponseBodypublic String setSession(HttpSession session){session.setAttribute("id",1);session.setAttribute("name","Test");return "set session";}@RequestMapping(path = "/session/get",method = RequestMethod.GET)@ResponseBodypublic String getSession(HttpSession session){System.out.println(session.getAttribute("id"));System.out.println(session.getAttribute("name"));return "get session";}

测试:

访问:http://localhost:8080/community/alpha/session/set

在这里插入图片描述
访问:http://localhost:8080/community/alpha/session/get

在这里插入图片描述

4.生成验证码

配置

pom.xml

        <!-- 验证码       --><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency>
KaptchaConfig

新建:/config/KaptchaConfig.java

package com.jsss.community.config;import com.google.code.kaptcha.Producer;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.Properties;@Configuration
public class KaptchaConfig {@Beanpublic Producer kaptchaProducer() {Properties properties = new Properties();properties.setProperty("kaptcha.image.width", "100");properties.setProperty("kaptcha.image.height", "40");properties.setProperty("kaptcha.textproducer.font.size", "32");properties.setProperty("kaptcha.textproducer.font.color", "0,0,0");properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYAZ");properties.setProperty("kaptcha.textproducer.char.length", "4");properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");DefaultKaptcha kaptcha = new DefaultKaptcha();Config config = new Config(properties);kaptcha.setConfig(config);return kaptcha;}
}

新增:LoginController.getKaptcha()

private static final Logger logger= LoggerFactory.getLogger(LoginController.class);@Autowiredprivate Producer kaptchaProducer;@RequestMapping(path = "/kaptcha",method = RequestMethod.GET)public void getKaptcha(HttpServletResponse response, HttpSession session){//生成验证码String text = kaptchaProducer.createText();BufferedImage image = kaptchaProducer.createImage(text);//将验证码存入sessionsession.setAttribute("kaptcha",text);//将图片输出给浏览器response.setContentType("image/png");try {ServletOutputStream os = response.getOutputStream();ImageIO.write(image,"png",os);} catch (IOException e) {logger.error("响应验证码失败:"+e.getMessage());}}

测试:

访问:http://localhost:8080/community/kaptcha

在这里插入图片描述

前端

修改:login.html:验证码

新增:global.js:CONTEXT_PATH

var CONTEXT_PATH="/community"

5.开发登录、退出功能

LoginTicket

新增:/entity/LoginTicket.java

package com.jsss.community.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;import java.util.Date;@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class LoginTicket {private int id;private int userId;private String ticket;private int status;private Date expired;
}
LoginTicketMapper

新增:/dao/LoginTicketMapper.java

package com.jsss.community.dao;import com.jsss.community.entity.LoginTicket;
import org.apache.ibatis.annotations.*;@Mapper
public interface LoginTicketMapper {@Insert({"insert into login_ticket(user_id,ticket,status,expired) ","values(#{userId},#{ticket},#{status},#{expired})"})@Options(useGeneratedKeys = true, keyProperty = "id")int insertLoginTicket(LoginTicket loginTicket);@Select({"select id,user_id,ticket,status,expired ","from login_ticket where ticket=#{ticket}"})LoginTicket selectByTicket(String ticket);//演示动态sql@Update({"<script>","update login_ticket set status=#{status} where ticket=#{ticket} ","<if test=\"ticket!=null\"> ","and 1=1 ","</if>","</script>"})int updateStatus(@Param("ticket") String ticket,@Param("status") int status);}
测试Dao

新增:MapperTest.testInsertLoginTicket()

新增:MapperTest.testSelectLoginTicket()

@Autowiredprivate LoginTicketMapper loginTicketMapper;@Testpublic void testInsertLoginTicket() {LoginTicket loginTicket = new LoginTicket();loginTicket.setUserId(101);loginTicket.setTicket("abc");loginTicket.setStatus(0);loginTicket.setExpired(new Date(System.currentTimeMillis() + 1000 * 60 * 10));loginTicketMapper.insertLoginTicket(loginTicket);}@Testpublic void testSelectLoginTicket() {LoginTicket loginTicket = loginTicketMapper.selectByTicket("abc");System.out.println(loginTicket);loginTicketMapper.updateStatus("abc", 1);loginTicket = loginTicketMapper.selectByTicket("abc");System.out.println(loginTicket);}
UserService

新增:UserService.login()

    @Autowiredprivate LoginTicketMapper loginTicketMapper;public Map<String,Object> login(String username,String password,int expiredSeconds){Map<String,Object> map=new HashMap<>();// 空值处理if (StringUtils.isBlank(username)) {map.put("usernameMsg", "账号不能为空!");return map;}if (StringUtils.isBlank(password)) {map.put("passwordMsg", "密码不能为空!");return map;}// 验证账号User user = userMapper.selectByName(username);if (user == null) {map.put("usernameMsg", "该账号不存在!");return map;}// 验证状态if (user.getStatus() == 0) {map.put("usernameMsg", "该账号未激活!");return map;}// 验证密码password = CommunityUtil.md5(password + user.getSalt());if (!user.getPassword().equals(password)) {map.put("passwordMsg", "密码不正确!");return map;}// 生成登录凭证LoginTicket loginTicket = new LoginTicket();loginTicket.setUserId(user.getId());loginTicket.setTicket(CommunityUtil.generateUUID());loginTicket.setStatus(0);loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));loginTicketMapper.insertLoginTicket(loginTicket);map.put("ticket", loginTicket.getTicket());return map;}
LoginController

新增:CommunityConstant

  • DEFAULT_EXPIRED_SECONDS
  • REMEMBER_EXPIRED_SECONDS
    /*** 默认状态的登录凭证的超时时间*/int DEFAULT_EXPIRED_SECONDS = 3600 * 12;/*** 记住状态的登录凭证超时时间*/int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;

新增:LoginController.login()

@Controller
public class LoginController implements CommunityConstant{@Value("${server.servlet.context-path}")private String contextPath;@RequestMapping(path = "/login", method = RequestMethod.POST)public String login(String username, String password, String code, boolean rememberme,Model model, HttpSession session, HttpServletResponse response) {//检查验证码String kaptcha = (String) session.getAttribute("kaptcha");if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {model.addAttribute("codeMsg", "验证码不正确!");return "site/login";}// 检查账号,密码int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;Map<String, Object> map = userService.login(username, password, expiredSeconds);if (map.containsKey("ticket")) {Cookie cookie = new Cookie("ticket", map.get("ticket").toString());cookie.setPath(contextPath);cookie.setMaxAge(expiredSeconds);response.addCookie(cookie);return "redirect:/index";} else {model.addAttribute("usernameMsg", map.get("usernameMsg"));model.addAttribute("passwordMsg", map.get("passwordMsg"));return "site/login";}}
前端

修改:login.html:表单提交

退出功能

新增:UserService.logout()

    public void logout(String ticket) {loginTicketMapper.updateStatus(ticket, 1);}

新增:UserController.logout()

    @RequestMapping(path = "/logout", method = RequestMethod.GET)public String logout(@CookieValue("ticket") String ticket) {userService.logout(ticket);return "redirect:/login";}

修改:index.html:头部

忘记密码

开发忘记密码的功能:

  • 点击登录页面上的“忘记密码”链接,打开忘记密码页面。

  • 在表单中输入注册的邮箱,点击获取验证码按钮,服务器为该邮箱发送一份验证码。

  • 在表单中填写收到的验证码及新密码,点击重置密码,服务器对密码进行修改。

修改后端

配置

        <!--fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.58</version></dependency>

新增:CommunityUtil.getJSONString()

    public static String getJSONString(int code, String msg, Map<String,Object> map){JSONObject json=new JSONObject();json.put("code",code);json.put("msg",msg);if (map!=null){for (String key: map.keySet()) {json.put(key,map.get(key));}}return json.toJSONString();}public static String getJSONString(int code,String msg){return getJSONString(code,msg,null);}public static String getJSONString(int code){return getJSONString(code,null,null);}

新增:UserService.resetPassword()

    // 重置密码public Map<String, Object> resetPassword(String email, String password) {Map<String, Object> map = new HashMap<>();// 空值处理if (StringUtils.isBlank(email)) {map.put("emailMsg", "邮箱不能为空!");return map;}if (StringUtils.isBlank(password)) {map.put("passwordMsg", "密码不能为空!");return map;}// 验证邮箱User user = userMapper.selectByEmail(email);if (user == null) {map.put("emailMsg", "该邮箱尚未注册!");return map;}// 重置密码password = CommunityUtil.md5(password + user.getSalt());userMapper.updatePassword(user.getId(), password);map.put("user", user);return map;}

新增:LoginController.resetPassword()

    // 忘记密码页面@RequestMapping(path = "/forget", method = RequestMethod.GET)public String getForgetPage() {return "/site/forget";}// 获取验证码@RequestMapping(path = "/forget/code", method = RequestMethod.GET)@ResponseBodypublic String getForgetCode(String email, HttpSession session) {if (StringUtils.isBlank(email)) {return CommunityUtil.getJSONString(1, "邮箱不能为空!");}// 发送邮件Context context = new Context();context.setVariable("email", email);String code = CommunityUtil.generateUUID().substring(0, 4);context.setVariable("verifyCode", code);String content = templateEngine.process("/mail/forget", context);mailClient.sendMail(email, "找回密码", content);// 保存验证码session.setAttribute("verifyCode", code);return CommunityUtil.getJSONString(0);}// 重置密码@RequestMapping(path = "/forget/password", method = RequestMethod.POST)public String resetPassword(String email, String verifyCode, String password, Model model, HttpSession session) {String code = (String) session.getAttribute("verifyCode");if (StringUtils.isBlank(verifyCode) || StringUtils.isBlank(code) || !code.equalsIgnoreCase(verifyCode)) {model.addAttribute("codeMsg", "验证码错误!");return "/site/forget";}Map<String, Object> map = userService.resetPassword(email, password);if (map.containsKey("user")) {return "redirect:/login";} else {model.addAttribute("emailMsg", map.get("emailMsg"));model.addAttribute("passwordMsg", map.get("passwordMsg"));return "/site/forget";}}

修改前端

login.html:忘记密码路径

添加:forget.js

修改:mail/forget.html

修改:site/forget.html

测试:忘记密码功能

6.显示登录信息

体验拦截器

新增:controller/interceptor/AlphaInterceptor.java

package com.jsss.community.controller.interceptor;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class AlphaInterceptor implements HandlerInterceptor {private static final Logger logger = LoggerFactory.getLogger(AlphaInterceptor.class);// 在Controller之前执行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {logger.debug("preHandle: " + handler.toString());return true;}// 在Controller之后执行@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {logger.debug("postHandle: " + handler.toString());}// 在TemplateEngine之后执行@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {logger.debug("afterCompletion: " + handler.toString());}
}
配置拦截器

新增:config/WebMvcConfig.java

package com.jsss.community.config;import com.jsss.community.controller.interceptor.AlphaInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Autowiredprivate AlphaInterceptor alphaInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(alphaInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg").addPathPatterns("/register", "/login");}}
测试

访问:http://localhost:8080/community/login

2023-05-03 15:36:59,431 DEBUG [http-nio-8080-exec-8] c.j.c.c.i.AlphaInterceptor [AlphaInterceptor.java:19] preHandle: com.jsss.community.controller.LoginController#getLoginPage()
2023-05-03 15:36:59,433 DEBUG [http-nio-8080-exec-8] c.j.c.c.i.AlphaInterceptor [AlphaInterceptor.java:26] postHandle: com.jsss.community.controller.LoginController#getLoginPage()
2023-05-03 15:36:59,463 DEBUG [http-nio-8080-exec-8] c.j.c.c.i.AlphaInterceptor [AlphaInterceptor.java:32] afterCompletion: com.jsss.community.controller.LoginController#getLoginPage()
登录拦截器

新增:util/CookieUtil.java

package com.jsss.community.util;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;public class CookieUtil {public static String getValue(HttpServletRequest request, String name) {if (request == null || name == null) {throw new IllegalArgumentException("参数为空!");}Cookie[] cookies = request.getCookies();if (cookies != null) {for (Cookie cookie : cookies) {if (cookie.getName().equals(name)) {return cookie.getValue();}}}return null;}}

新增:UserService.findLoginTicket()

    public LoginTicket findLoginTicket(String ticket) {return loginTicketMapper.selectByTicket(ticket);}

视频38:58左右

HostUser:
在本次请求中持有用户
响应请求是多线程的
解决存储的并发性
使用ThreadLocal解决
每一个线程,有一份数据

新增:util/HostHolder.java

package com.jsss.community.util;import com.jsss.community.entity.User;
import org.springframework.stereotype.Component;/*** 持有用户信息,用于代替session对象.*/
@Component
public class HostHolder {private ThreadLocal<User> users = new ThreadLocal<>();public void setUser(User user) {users.set(user);}public User getUser() {return users.get();}public void clear() {users.remove();}}

新增:interceptor/LoginTicketInterceptor.java

package com.jsss.community.controller.interceptor;import com.jsss.community.entity.LoginTicket;
import com.jsss.community.entity.User;
import com.jsss.community.service.UserService;
import com.jsss.community.util.CookieUtil;
import com.jsss.community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;@Component
public class LoginTicketInterceptor implements HandlerInterceptor {@Autowiredprivate UserService userService;@Autowiredprivate HostHolder hostHolder;//Controller执行之前,拿到登录用户@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从cookie中获取凭证String ticket = CookieUtil.getValue(request, "ticket");if (ticket != null) {// 查询凭证LoginTicket loginTicket = userService.findLoginTicket(ticket);// 检查凭证是否有效if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {// 根据凭证查询用户User user = userService.findUserById(loginTicket.getUserId());// 在本次请求中持有用户hostHolder.setUser(user);}}return true;}//Controller执行之后:把loginUser返回mav@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {User user = hostHolder.getUser();if (user != null && modelAndView != null) {modelAndView.addObject("loginUser", user);}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {hostHolder.clear();}
}

新增:WebMvcConfig:登录拦截器

package com.jsss.community.config;@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Autowiredprivate AlphaInterceptor alphaInterceptor;@Autowiredprivate LoginTicketInterceptor loginTicketInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(alphaInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg").addPathPatterns("/register", "/login");registry.addInterceptor(loginTicketInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");}}
前端

修改:index.html:头部

7.账号设置

UserController

新建:controller/UserController.java

package com.jsss.community.controller;import com.jsss.community.util.CommunityConstant;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;@Controller
@RequestMapping("/user")
public class UserController implements CommunityConstant {@RequestMapping(path = "/setting",method = RequestMethod.GET)public String getSettingPage(){return "/site/setting";}}
前端

修改:setting.html

  • html标签
  • 静态路径

修改:index.html

  • 头部:账号设置路径

测试

  • 点击:账号设置
配置
community.path.upload=d:/work/data/upload
UserService

新增:UserService.updateHeader()

    public int updateHeader(int userId,String headUrl){return userMapper.updateHeader(userId,headUrl);}
UserController

新增:UserController.uploadHeader()

新增:UserController.getHeader()

package com.jsss.community.controller;@Controller
@RequestMapping("/user")
public class UserController implements CommunityConstant {private static Logger logger= LoggerFactory.getLogger(UserController.class);@Value("${community.path.upload}")private String uploadPath;@Value("${community.path.domain}")private String domain;@Value("${server.servlet.context-path}")private String contextPath;@Autowiredprivate UserService userService;@Autowiredprivate HostHolder hostHolder;@RequestMapping(path = "/setting",method = RequestMethod.GET)public String getSettingPage(){return "/site/setting";}@RequestMapping(path = "/upload", method = RequestMethod.POST)public String uploadHeader(MultipartFile headerImage, Model model) {if (headerImage == null) {model.addAttribute("error", "您还没有选择图片!");return "site/setting";}String fileName = headerImage.getOriginalFilename();String suffix = fileName.substring(fileName.lastIndexOf("."));if (StringUtils.isBlank(suffix)) {model.addAttribute("error", "文件的格式不正确!");return "site/setting";}// 生成随机文件名fileName = CommunityUtil.generateUUID() + suffix;// 确定文件存放的路径File dest = new File(uploadPath + "/" + fileName);try {// 存储文件headerImage.transferTo(dest);} catch (IOException e) {logger.error("上传文件失败: " + e.getMessage());throw new RuntimeException("上传文件失败,服务器发生异常!", e);}// 更新当前用户的头像的路径(web访问路径)// http://localhost:8080/community/user/header/xxx.pngUser user = hostHolder.getUser();String headerUrl = domain + contextPath + "/user/header/" + fileName;userService.updateHeader(user.getId(), headerUrl);return "redirect:/index";}@RequestMapping(path = "/header/{fileName}", method = RequestMethod.GET)public void getHeader(@PathVariable("fileName") String fileName, HttpServletResponse response) {// 服务器存放路径fileName = uploadPath + "/" + fileName;// 文件后缀String suffix = fileName.substring(fileName.lastIndexOf("."));// 响应图片response.setContentType("image/" + suffix);try (FileInputStream fis = new FileInputStream(fileName);OutputStream os = response.getOutputStream();) {byte[] buffer = new byte[1024];int b = 0;while ((b = fis.read(buffer)) != -1) {os.write(buffer, 0, b);}} catch (IOException e) {logger.error("读取头像失败: " + e.getMessage());}}
}
前端

修改:setting.html:修改头像

测试

访问:账户设置

测试:上传头像

图片下载到:D:\work\data\upload

修改密码

新增:UserService.updatePassword()

	// 修改密码public Map<String, Object> updatePassword(int userId, String oldPassword, String newPassword) {Map<String, Object> map = new HashMap<>();// 空值处理if (StringUtils.isBlank(oldPassword)) {map.put("oldPasswordMsg", "原密码不能为空!");return map;}if (StringUtils.isBlank(newPassword)) {map.put("newPasswordMsg", "新密码不能为空!");return map;}// 验证原始密码User user = userMapper.selectById(userId);oldPassword = CommunityUtil.md5(oldPassword + user.getSalt());if (!user.getPassword().equals(oldPassword)) {map.put("oldPasswordMsg", "原密码输入有误!");return map;}// 更新密码newPassword = CommunityUtil.md5(newPassword + user.getSalt());userMapper.updatePassword(userId, newPassword);return map;}

新增:UserController.updatePassword()

	@RequestMapping(path = "/update_password", method = RequestMethod.POST)public String updatePassword(@CookieValue("ticket") String ticket, String oldPassword, String password, Model model) {User user=hostHolder.getUser();int userId=user.getId();oldPassword=CommunityUtil.md5(oldPassword+user.getSalt());if (user.getPassword().equals(oldPassword)&&!StringUtils.isBlank(password)){password=CommunityUtil.md5(password+user.getSalt());if (userService.updatePassword(userId,password)>0){model.addAttribute("msg","修改密码成功");model.addAttribute("target","/login");//取消登录状态userService.logout(ticket);return "site/operate-result";}}model.addAttribute("msg","修改密码失败");model.addAttribute("target","/user/setting");return "site/operate-result";}

修改:前端

				<!-- 修改密码 --><h6 class="text-left text-info border-bottom pb-2 mt-5">修改密码</h6><form class="mt-5" th:action="@{/user/updatePassword}" method="post"><div class="form-group row mt-4"><label for="old-password" class="col-sm-2 col-form-label text-right">原密码:</label><div class="col-sm-10"><input type="password" th:class="|form-control ${oldPasswordMsg!=null?'is-invalid':''}|"name="oldPassword" th:value="${param.oldPassword}" id="old-password" placeholder="请输入原始密码!" required><div class="invalid-feedback" th:text="${oldPasswordMsg}">密码长度不能小于8位!</div>							</div></div><div class="form-group row mt-4"><label for="new-password" class="col-sm-2 col-form-label text-right">新密码:</label><div class="col-sm-10"><input type="password" th:class="|form-control ${newPasswordMsg!=null?'is-invalid':''}|"name="newPassword" th:value="${param.newPassword}" id="new-password" placeholder="请输入新的密码!" required><div class="invalid-feedback" th:text="${newPasswordMsg}">密码长度不能小于8位!</div>							</div></div><div class="form-group row mt-4"><label for="confirm-password" class="col-sm-2 col-form-label text-right">确认密码:</label><div class="col-sm-10"><input type="password" class="form-control" th:value="${param.newPassword}" id="confirm-password" placeholder="再次输入新密码!" required><div class="invalid-feedback">两次输入的密码不一致!</div>								</div></div>				<div class="form-group row mt-4"><div class="col-sm-2"></div><div class="col-sm-10 text-center"><button type="submit" class="btn btn-info text-white form-control">立即保存</button></div></div></form>			

8.检查登录状态

LoginRequired

新增:/annotation/LoginRequired.java

package com.jsss.community.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {}

修改:UserController

给需要登录的功能添加注解

  • 账号设置功能
LoginRequiredInterceptor

新增:interceptor/LoginRequiredInterceptor.java

package com.jsss.community.controller.interceptor;import com.jsss.community.annotation.LoginRequired;
import com.jsss.community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {@AutowiredHostHolder hostHolder;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof HandlerMethod){HandlerMethod handlerMethod= (HandlerMethod) handler;Method method = handlerMethod.getMethod();LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);if (loginRequired != null &&hostHolder.getUser()==null) {response.sendRedirect(request.getContextPath()+"/login");return false;}}return true;}
}
WebMvcConfig

修改:WebMvcConfig

添加登录拦截

    @Autowiredprivate LoginRequiredInterceptor loginRequiredInterceptor;registry.addInterceptor(loginRequiredInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");

测试

访问:http://localhost:8080/community/user/setting

会跳到登录页面

最后

2023-7-30 16:26:24

这篇博客能写好的原因是:站在巨人的肩膀上

这篇博客要写好的目的是:做别人的肩膀

开源:为爱发电

学习:为我而行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/698400.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VRRP虚拟路由器冗余协议

VRRP概述 VRRP是什么 VRRP&#xff1a;虚拟路由器冗余协议过把几台路由设备联合组成一台虚拟的路由设备&#xff0c;将虚拟路由设备的IP地址作为用户的默认网关实现与外部网络通信当网关设备发生故障时&#xff0c;VRRP能够选举新的网关设备承担数据流量&#xff0c;从而保障…

研发管理-选择研发管理系统-研发管理系统哪个好

选择研发管理系统-研发管理系统哪个好 选择研发管理系统时&#xff0c;并没有一个绝对的“最好”的系统&#xff0c;因为每个企业的需求和情况都是独特的。然而&#xff0c;我可以向您介绍一些在市场上广受欢迎且功能强大的研发管理系统&#xff0c;供您参考&#xff1a; 1、彩…

2024第二届区块链、物联网与金融管理国际会议(ICBITFM2024)

2024第二届区块链、物联网与金融管理国际会议(ICBITFM2024) 会议简介 2024第二届区块链、物联网与金融管理国际会议(ICBITFM 2024)是一个重要的学术会议&#xff0c;旨在促进区块链、物联网和金融管理领域的交流和合作&#xff0c;对于推动这些领域的发展和创新具有重要意义。…

Spring MVC(五) 文件上传

1 单文件上传 在程序开发中&#xff0c;有时候需要上传一些文件。我们在学习Servlet的时候&#xff0c;也做过文件上传的操作&#xff0c;只不过基于Servlet的文件上传操作起来过于复杂&#xff0c;因此所有的MVC框架都提供了自己的文件上传操作&#xff0c;基本上都是基于File…

【CSP CCF记录】202009-1 称检测点查询

题目 过程 难点&#xff1a;编号和位置的一一对应&#xff0c;不同位置的距离可能相等。 所以使用一个结构体记录不同检测点的编号和到居民地的距离。 sort函数进行排序。Sort函数使用方法 参考&#xff1a;http://t.csdnimg.cn/Y0Hpi 代码 #include <bits/stdc.h>…

一觉醒来 AI科技圈发生的大小事儿 05月13日

&#x1f4f3;博弈论让 AI 更加正确、高效&#xff0c;LLM 与自己竞争 研究团队设计了共识博弈&#xff0c;通过让语言模型的生成器和判别器相互博弈来提高模型的准确性和内部一致性。这种方法不需要对基础模型进行训练或修改&#xff0c;可以在笔记本电脑上快速执行。研究结果…

uview-plus在uniapp项目中单选和复选框不显示问题

在我的uniapp小程序项目中&#xff0c;我使用了vue3ts的组合&#xff0c;ui组件库使用了uview-plus这个组件库&#xff0c;但是在使用个别组件的时候&#xff0c;没有显示出效果&#xff0c;就像单选或者复选框&#xff0c;官方效果&#xff1a; 但是当我用到自己项目中的时候&…

前端 finalShell 与 docker 创建 服务连接 部署项目

准备 fianlShell 下载地址 官网下载地址 要哪个下那个&#xff0c; 我的是第一个 下载后 安装 打开 选择 SSH 进行配置 配置后点击确定即可 1、yum install -y docker -y 表示不询问&#xff0c;使用默认配置进行安装 检测版本 装最新的 2、 yum list installed | gre…

the7主题下载,探索WordPress主题的无限可能

在数字时代&#xff0c;一个出色的网站是任何企业或个人品牌的必备。但在这个竞争激烈的网络世界中&#xff0c;如何让您的网站脱颖而出&#xff1f;答案就是 the7 —— 一款专为创造独特和视觉冲击力强的网站而设计的 WordPress 主题。 1. 无限设计可能性 the7 以其独特的设…

基于svm的水果识别

1、程序界面介绍 该程序GUI界面包括待检测水果图片加载、检测结果输出、清空可视化框等。其中包括训练模型、加载图片、重置、识别检测按钮。 程序GUI界面 识别玉米识别西瓜 分类器识别水果基本原理&#xff1a; 由于每种水果的外形存在很大差异&#xff0c;比如西瓜与玉米&…

idea使用gitee基本操作流程

1.首先&#xff0c;每次要写代码前&#xff0c;先切换到自己负责的分支 点击签出。 然后拉取一次远程master分支&#xff0c;保证得到的是最新的代码。 写完代码后&#xff0c;在左侧栏有提交按钮。 点击后&#xff0c;选择更新的文件&#xff0c;输入描述内容&#xff08;必填…

ansible -playbook运维工具、语法、数据结构、命令用法、触发器、角色

目录 配置文件 基本语法规则&#xff1a; YAML支持的数据结构 playbook核心元素 ansible-playbook用法&#xff1a; 触发器 特点&#xff1a; 角色&#xff1a; 习题&#xff1a; 配置文件 playbook配置文件使用yaml语法&#xff0c;YAML 是一门标记性语言,专门用来写配…