第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.rhkb.cn/news/326945.html

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

相关文章

10分钟获取IP SSL证书——建议收藏

IP SSL证书是一种专门为IP地址签发的安全套接字层&#xff08;SSL&#xff09;证书&#xff0c;与常规SSL证书主要绑定到域名&#xff08;如 example.com&#xff09;不同&#xff0c;IP SSL证书直接绑定到服务器的IP地址&#xff08;如 192.0.2.1&#xff09;。 一 . IP地址…

百度文心一言 java 支持流式输出,Springboot+ sse的demo

参考&#xff1a;GitHub - mmciel/wenxin-api-java: 百度文心一言Java库&#xff0c;支持问答和对话&#xff0c;支持流式输出和同步输出。提供SpringBoot调用样例。提供拓展能力。 1、依赖 <dependency> <groupId>com.baidu.aip</groupId> <artifactId…

C语言例题41、八进制转换为十进制

#include<stdio.h>void main() {int x;printf("请输入一个8进制整数&#xff1a;");scanf("%o", &x);printf("转换成十进制后的整数为%d\n", x); }运行结果&#xff1a; 本章C语言经典例题合集&#xff1a;http://t.csdnimg.cn/FK0Qg…

学习软考----数据库系统工程师32

NoSQL非关系型数据库 CAP理论和BASE特性 关系型数据库主要使用ACID理论 各种NoSQL数据 库的分类与特点

前端XHR请求数据

axios封装了XHR(XMLHttpRequest) 效果 项目结构 Jakarta EE9&#xff0c;Web项目。 无额外的maven依赖 1、Web页面 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title&…

强化训练:day7(字符串中找出连续最长的数字串、岛屿数量、拼三角)

文章目录 前言1. 字符串中找出连续最长的数字串1.1 题目描述1.2 解题思路1.3 代码实现 2. 岛屿数量2.1 题目描述2.2 题目描述2.3 代码实现 3. 拼三角3.1 题目描述3.2 解题思路3.3 代码实现 总结 前言 1. 字符串中找出连续最长的数字串   2. 岛屿数量   3. 拼三角 1. 字符串…

11个免费的 android数据恢复应用程序功能分析

在手机上丢失数据是一个很大的错误。但是&#xff0c;在这种情况下&#xff0c;除了惊慌失措之外&#xff0c;最好开始使用android数据恢复应用程序搜索以查找将其取回的方法。您可以检查手机的备份存储以在Android上进行数据恢复&#xff0c;但是如果数据仍然无处可寻&#xf…

【数据库】数据库指令

一。数据库打开 1.命令行 2.进入mysql mysql -uroot -p密码 3.退出 exit&#xff1b; 二。针对数据库的操作 1.创建数据库&#xff08;有分号&#xff09; create database student; 2.使用数据库 use student 3.删除数据库&#xff08;有分号&#xff09; drop database…

计算机Java项目|Springboot学生读书笔记共享

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、Python项目、前端项目、人工智能与大数据、简…

SpringAMQP-消息转换器

这边发送消息接收消息默认是jdk的序列化方式&#xff0c;发送到服务器是以字节码的形式&#xff0c;我们看不懂也很占内存&#xff0c;所以我们要手动设置一下 我这边设置成json的序列化方式&#xff0c;注意发送方和接收方的序列化方式要保持一致 不然回报错。 引入依赖&#…

【HarmonyOS】笔记八-图片处理

概念 开发者经常需要在应用中显示一些图片&#xff0c;例如&#xff1a;按钮中的icon、网络图片、本地图片等。在应用中显示图片需要使用Image组件实现&#xff0c;Image支持多种图片格式&#xff0c;包括png、jpg、bmp、svg和gif&#xff0c;该接口通过图片数据源获取图片&am…

网站设计模板简单又好看

在互联网时代&#xff0c;每个企业都需要拥有一个好看又具有吸引力的网站。一个简单却又好看的网站设计模板可以为企业带来许多好处。本文将探讨一些如何设计一个简单又好看的网站模板的技巧。 首先&#xff0c;一个好的网站设计模板应该具备简洁明了的布局。简单的布局能够使用…

地下车库导航地图怎么做?停车场地图绘制软件哪个好?

上海懒图科技以先进技术和丰富的行业服务经验为用户提供停车场景下的全流程服务平台&#xff0c;用户基于平台可自主快速绘制酷炫的停车场地图&#xff0c;通过提供完善的停车场应用功能集和扩展API服务包&#xff0c;可以方便地实现电子地图服务于您的各类停车场应用中&#x…

鸿蒙——即将是国内全部物联网的搭载系统

国内物联网时代 中国国内物联网时代是指在中国国内&#xff0c;物联网&#xff08;Internet of Things&#xff0c;简称IoT&#xff09;技术得到广泛应用和发展的时代。在这个时代&#xff0c;各种设备和物品都可以通过互联网进行连接和交互&#xff0c;实现信息的采集、传输和…

问题—前端调用接口url多加一个/,本地可以调通,测试环境报错302,分开调两个接口

问题背景 接口url前面多加一个/ &#xff0c;npm run serve 起项目&#xff0c;本地调用正常 npm run build 打包到测试环境&#xff0c;接口出现问题&#xff0c;分开调用接口&#xff0c;且报302错误 问题原因&#xff1a; 本地开发环境和测试环境的URL处理方式不同 本地使…

【前端性能优化】深入解析重绘和回流,构建高性能Web界面

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f3af; 引言&#xff1a;探索Web性能的基石&#x1f3d7;️ 基础概念&#xff1a;什么是重绘和回流&#xff1f;&#x1f4cc; 回流&#xff08;Reflow&#xff09;&#x1f4cc; 重绘&#xff08;Repaint&#xff0…

【C++杂货铺】红黑树

目录 &#x1f308;前言&#x1f308; &#x1f4c1; 红黑树的概念 &#x1f4c1; 红黑树的性质 &#x1f4c1; 红黑树节点的定义 &#x1f4c1; 红黑树的插入操作 &#x1f4c1; 红黑树和AVL树的比较 &#x1f4c1; 全代码展示 &#x1f4c1; 总结 &#x1f308;前言…

Go框架三件套:Gorm的基本操作

1.概述 这里的Go框架三件套是指 Web、RPC、ORM框架&#xff0c;具体如下: Gorm框架 gorm框架是一个已经迭代了10年的功能强大的ORM框架&#xff0c;在字节内部被广泛使用并且拥有非常丰富的开源扩展。 Kitex框架 Kitex是字节内部的Golang微服务RPC框架&#xff0c;具有高性能…

IP定位技术在打击网络犯罪中的作用

随着互联网的普及和信息技术的发展&#xff0c;网络犯罪日益猖獗&#xff0c;给社会治安和个人财产安全带来了严重威胁。而IP定位技术的应用为打击网络犯罪提供了一种有效手段。IP数据云将探讨IP定位技术在打击网络犯罪中的作用及其意义。 1. IP定位技术的原理 IP&#xff08…

kubernetes集群开启ipvs模式

1&#xff09; 需要在所有节点机器安装ipvsadm&#xff1a; apt install ipvsadm 2) 加载ipvs模块 modprobe ip_vs modprobe ip_rr modprobe ip_wrr modprobe ip_sh修改k8s集群内的kube-proxy cm kubectl edit cm kube-proxy -n kube-system修改模式为ipvs&#xff1a; 如图 …