【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、

如果对你有帮助的话
为博主点个赞吧
点赞是对博主最大的鼓励
爱心发射~

目录

  • 一、发送邮件
    • 1、启用客户端SMTP服务
    • 2、导入jar包
    • 3、邮箱参数配置
    • MailClient
    • demo.html
    • MailTests
    • 总结
  • 二、开发注册功能
    • 1、访问注册页面
      • 修改——`thymeleaf`
      • 首页—超链接——`index.html`
      • 每个`html`头部复用——`index.html`
      • `LoginController`
    • 2、提交注册数据
      • 添加依赖和配置
      • service层
        • 工具类——`CommunityUtil`
        • 注入——`UserService`
        • 改造模板——`activation.html`
      • 控制层
        • 注册成功或有错误返回——LoginController
        • 激活成功模板——`operate-result.html`
        • 账号、密码、邮箱错误——返回`register.html`
    • 3、激活注册账号
      • 常量接口——`CommunityConstant`
      • UserService
      • 返回页面——`LoginController`
      • 将`login`添加到模板
        • 验证码更改
      • 首页更改——`index`
      • 激活,跳到登录页面
  • 三、会话管理
    • 1、HTTP Cookie
      • set cookie
      • get cookie
    • 2、Session
      • set Session
      • get session
      • 为什么在分布式部署下,Session用的少了?实际应用中怎么解决
        • 分布式部署下,有什么问题:
        • 解决方法
  • 四、生成验证码——Kaptcha
    • 1、Kaptcha
    • 2、KaptchaConfig——定义验证码图片
    • 3、LoginController——生成验证码
    • 4、刷新验证码
    • 5、总结
      • 关于Kaptcha的描述
      • 关于使用Kaptcha的描述
      • 关于Kaptcha配置的描述
  • 五、开发登录、退出功能
    • 访问登录页面
      • 1、数据库——login_ticket
      • 2、实体类——LoginTicket
      • 3、LoginTicketMapper——写SQL、通过注解
      • 4、测试
    • 登录
      • 1、业务层——UserService
      • 2、LoginController
      • 3、登录页面
    • 退出
      • 1、状态标识——UserService
      • 2、返回退出页面请求——LoginController
      • 3、配置退出登录页面的链接——index
  • 六、显示登录信息
    • 1、拦截器
      • 拦截器测试——AlphaInterceptor
        • 配置类——WebMvcConfig
    • 2、拦截器应用
      • 拦截器——LoginTicketInterceptor
        • request获取Cookie——CookieUtil
        • 查询登录凭证——UserService
        • 找map——HostHolder
        • 拦截器主体代码-LoginTicketInterceptor
        • 配置——WebMvcConfig
      • 首页——index
        • 登录 才能看到 消息
        • 没登录 才显示 注册
        • 没登录 才显示 登录
        • 调整登录账号显示
    • 3、运行结果:
    • 4、总结
      • 关于Spring MVC拦截器:
      • 关于配置Spring MVC拦截器
      • 关于ThreadLocal的描述
  • 七、账号设置——上传头像、修改密码
    • 1、可以访问这个页面
      • 返回访问页面——UserController
      • 显示页面——setting.html
      • index中修改——链接
    • 2、上传头像
      • 配置文件
      • UserService
      • UserController
      • 账号设置——setting.html
    • 3、修改密码
      • UserService
      • UserController
      • setting.html
    • 总结
  • 八、检查登录状态
    • 1、写注解——LoginRequired
    • 2、加上注解——UserController
    • 3、拦截器——LoginRequiredInterceptor
    • 4、拦截器配置——WebMvcConfig
    • 总结

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第1张图片

一、发送邮件

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第2张图片

1、启用客户端SMTP服务

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第3张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第4张图片
bofryuzursekbiab——密码

2、导入jar包

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第5张图片

<!-- 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.0</version>
</dependency>

3、邮箱参数配置

  • 访问邮箱域名
  • 邮箱端口
  • 账号
  • 密码
  • 协议
  • 详细配置
# MailProperties
spring.mail.host=smtp.sina.com
spring.mail.port=465
spring.mail.username=@.com
spring.mail.password=nowcoder123
spring.mail.protocol=smtps
spring.mail.properties.mail.smtp.ssl.enable=true

MailClient

在这里插入图片描述

package com.nowcoder.community.util;

@Component
public class MailClient {

    private static final Logger logger = LoggerFactory.getLogger(MailClient.class);

    @Autowired
    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());
        }
    }

}

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>

MailTests


@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MailTests {

    @Autowired
    private MailClient mailClient;

    @Autowired
    private TemplateEngine templateEngine;

    @Test
    public void testTextMail() {
        mailClient.sendMail("[email protected]", "TEST", "Welcome.");
    }

    @Test
    public void testHtmlMail() {
        Context context = new Context();
        context.setVariable("username", "sunday");

        String content = templateEngine.process("/mail/demo", context);
        System.out.println(content);

        mailClient.sendMail("[email protected]", "HTML", content);
    }

}

总结

  1. JavaMailSenderSpring Email的核心组件,负责发送邮件
  2. MimeMessage用于封装邮件的相关信息
  3. MimeMessageHelper用于辅助构建MimeMessage对象
  4. TemplateEngine是模板引擎,负责格式化HTML格式的邮件

Spring Boot对发送邮件提供了支持,可以通过MailProperties对邮件进行配置

  • 可以配置邮件服务器的域名和端口
  • 可以配置发件人的账号及密码
  • 可以配置发送邮件的协议类型

哪些会被Spring Boot自动装配到Spring容器中

  • JavaMailSender
  • TemplateEngine

二、开发注册功能

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第6张图片

1、访问注册页面

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

修改——thymeleaf

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第7张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第8张图片

首页—超链接——index.html

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第9张图片

每个html头部复用——index.html

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第10张图片

LoginController

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第11张图片
返回注册页面

@Controller
public class LoginController {
    @Autowired
    private UserService userService;

    @RequestMapping(path = "/register", method = RequestMethod.GET)
    public String getRegisterPage() {
        return "/site/register";
    }
}

2、提交注册数据

通过表单提交数据。

添加依赖和配置

<dependency>
	<groupId>org.apache.commonsgroupId>
	<artifactId>commons-lang3artifactId>
	<version>3.9version>
dependency>
# community
community.path.domain=http://localhost:8080

service层

  • 服务端发送激活邮件。

工具类——CommunityUtil

生成随机字符串

给文件生成随机名字
在这里插入图片描述

public class CommunityUtil {

    // 生成随机字符串
    public static String generateUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    // MD5加密 : 只能加密,不能解密
    // hello -> abc123def456
    // hello + 3e4a8 -> abc123def456abc
    // 先加字符串 , 再加密
    public static String md5(String key) {
        // 参数为空,不加密
        if (StringUtils.isBlank(key)) {
            return null;
        }
        return DigestUtils.md5DigestAsHex(key.getBytes());
    }


}

注入——UserService

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第12张图片

  • 注入邮件客户端
  • 注入模板引擎
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MailClient mailClient;  // 邮件客户端

    @Autowired
    private 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<>();  // map实例化
        // 空值处理
        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:8081/community/activation/101/code
        // 域名——项目名——功能访问名 + 用户 id   激活码
        String 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;
    }
}

牛客网随机头像
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第13张图片

改造模板——activation.html

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第14张图片

doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<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}">[email protected]b>, 您好!
	p>
	<p>
		您正在注册牛客网, 这是一封激活邮件, 请点击
		<a th:href="${url}">此链接a>,
		激活您的牛客账号!
	p>
div>
body>
html>

控制层

  • 服务端验证账号是否已存在、邮箱是否已注册。

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第15张图片

  • 注册成功——到首页进行激活——在登陆

@{}:路径是动态的
${}:里边是变量

注册成功或有错误返回——LoginController

    @RequestMapping(path = "/register", method = RequestMethod.POST)
    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

默认值的显示
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第16张图片
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第17张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第18张图片
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第19张图片

3、激活注册账号

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

在service层加一个业务,几种情况:

  • 激活成功
  • 多次点击激活链接
  • 重复激活给提示
  • 激活码伪造

三种结果:

  • 成功
  • 重复激活
  • 失败

常量接口——CommunityConstant

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第20张图片

public interface CommunityConstant {

    /**
     * 激活成功
     */
    int ACTIVATION_SUCCESS = 0;

    /**
     * 重复激活
     */
    int ACTIVATION_REPEAT = 1;

    /**
     * 激活失败
     */
    int ACTIVATION_FAILURE = 2;

}

UserService

    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


    @RequestMapping(path = "/login", method = RequestMethod.GET)
    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 == ACTIVATION_SUCCESS) {
            model.addAttribute("msg", "激活成功,您的账号已经可以正常使用了!");
            model.addAttribute("target", "/login");
        } else if (result == ACTIVATION_REPEAT) {
            model.addAttribute("msg", "无效操作,该账号已经激活过了!");
            model.addAttribute("target", "/index");
        } else {
            model.addAttribute("msg", "激活失败,您提供的激活码不正确!");
            model.addAttribute("target", "/index");
        }
        return "/site/operate-result";
    }

login添加到模板

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第21张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第22张图片

验证码更改

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第23张图片

首页更改——index

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第24张图片

激活,跳到登录页面

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第25张图片

三、会话管理

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第26张图片
HTTP教程

HTTP 是无状态,有会话的

HTTP 是无状态的:在同一个连接中,两个执行成功的请求之间是没有关系的。这就带来了一个问题,用户没有办法在同一个网站中进行连续的交互,比如在一个电商网站里,用户把某个商品加入到购物车,切换一个页面后再次添加了商品,这两次添加商品的请求之间没有关联,浏览器无法知道用户最终选择了哪些商品。而使用 HTTP 的头部扩展,HTTP Cookies 就可以解决这个问题。把 Cookies 添加到头部中,创建一个会话让每次请求都能共享相同的上下文信息,达成相同的状态。

1、HTTP Cookie

HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。

  • 是服务器发送到浏览器,并保存在浏览器端的一小块数据。

  • 浏览器下次访问该服务器时,会自动携带块该数据,将其发送给服务器。

  • 识别浏览器、记住浏览器

  • 下次再发到浏览器,会携带上次数据

好处:弥补HTTP无状态时的情况,让业务得以延续

缺点:

  • 存在客户端,不安全
  • 增加数据量,影响性能

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第27张图片

  • 浏览器访问服务器,服务器会产生一个Cookie对象
  • 服务器-返回——Cookie,其中携带数据(默认在响应的头里),浏览器保存以下数据
  • 浏览器——服务器,Cookie在请求的头里,服务器记住用户

set cookie

    // cookie示例

    @RequestMapping(path = "/cookie/set", method = RequestMethod.GET)
    @ResponseBody
    public String setCookie(HttpServletResponse response) {
        // 创建cookie
        Cookie cookie = new Cookie("code", CommunityUtil.generateUUID());
        // 设置cookie生效的范围
        cookie.setPath("/community/alpha");
        // 设置cookie的生存时间
        cookie.setMaxAge(60 * 10);
        // 发送cookie
        response.addCookie(cookie);

        return "set cookie";
    }

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第28张图片

get cookie

    @RequestMapping(path = "/cookie/get", method = RequestMethod.GET)
    @ResponseBody
    public String getCookie(@CookieValue("code") String code) {
        System.out.println(code);
        return "get cookie";
    }

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第29张图片

2、Session

  • JavaEE的标准,用于在服务端记录客户端信息。
  • 数据存放在服务端更加安全,但是也会增加服务端的内存压力。

服务器靠什么区分Session

  • 服务器——浏览器发送cookiecookie中携带Session标识

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第30张图片

set Session

    // session示例

    @RequestMapping(path = "/session/set", method = RequestMethod.GET)
    @ResponseBody
    public String setSession(HttpSession session) {
        session.setAttribute("id", 1);
        session.setAttribute("name", "Test");
        return "set session";
    }

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第31张图片

get session

    // session示例
    @RequestMapping(path = "/session/get", method = RequestMethod.GET)
    @ResponseBody
    public String getSession(HttpSession session) {
        System.out.println(session.getAttribute("id"));
        System.out.println(session.getAttribute("name"));
        return "get session";
    }

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第32张图片

为什么在分布式部署下,Session用的少了?实际应用中怎么解决

  • 分布式部署——同时部署多台服务器,同时向浏览器提供支持

分布式部署下,有什么问题:

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第33张图片

解决方法

1、粘性Session

每个浏览器始终分配给一台服务器去处理,固定ip给同一个服务器
缺点:难以保证负载均衡,性能不好

2、同步Session

服务器之间同步Session
缺点: 对服务器性能产生影响,服务器之间耦合

3、共享Session

单独有一台服务器来处理Session
缺点:这台服务器挂了都无法工作

4、能存cookie就存cookie,敏感数据可以存到数据库里

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第34张图片

优点:

  • 很好的共享数据、同步数据
    缺点:
  • 传统的关系型数据库把数据存到硬盘,访问数据到硬盘,性能慢
  • 并发量大出现瓶颈

5、 可以存到非关系型数据库 Redis

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第35张图片

目前没部署Redis,怎么办?

  • 适合存到MySQL,就存
  • 不适合存到session

四、生成验证码——Kaptcha

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第36张图片

1、Kaptcha

Kaptcha官方手册

  • 导入jar
  • 编写Kaptcha配置类
  • 生成随机字符、生成图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第37张图片

导入依赖

		<dependency>
			<groupId>com.github.pengglegroupId>
			<artifactId>kaptchaartifactId>
			<version>2.3.2version>
		dependency>

2、KaptchaConfig——定义验证码图片

package com.nowcoder.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 {

    @Bean
    public 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;
    }

}

3、LoginController——生成验证码

@Autowired
    private Producer kaptchaProducer;

    private static final Logger logger = LoggerFactory.getLogger(LoginController.class);

    @RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
    public void getKaptcha(HttpServletResponse response, HttpSession session) {
        // 生成验证码
        String text = kaptchaProducer.createText();
        BufferedImage image = kaptchaProducer.createImage(text);

        // 将验证码存入session
        session.setAttribute("kaptcha", text);

        // 将突图片输出给浏览器
        response.setContentType("image/png");
        try {
            OutputStream os = response.getOutputStream();
            ImageIO.write(image, "png", os);
        } catch (IOException e) {
            logger.error("响应验证码失败:" + e.getMessage());
        }
    }

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第38张图片

4、刷新验证码

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第39张图片

在这里插入图片描述

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第40张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第41张图片

结果
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第42张图片

5、总结

关于Kaptcha的描述

  • ProducerKaptcha的核心接口
  • DefaultKaptchaKaptcha核心接口的默认实现类
  • Spring Boot没有为Kaptcha提供自动配置

关于使用Kaptcha的描述

  • 可以通过Producer创建随机的验证码文本
  • 可以传入文本,让Producer创建对应的验证码图片
  • 服务端需要将验证码图片输出给浏览器

关于Kaptcha配置的描述

  • 可以配置Kaptcha图片的宽度、高度、字号、颜色
  • 可以配置Kaptcha验证码的字符范围、字符个数

五、开发登录、退出功能

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第43张图片

访问登录页面

  • 点击顶部区域内的链接,打开登录页面。

1、数据库——login_ticket

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第44张图片

  • id——主键
  • user_id——用户id
  • ticket——凭证(唯一标识,唯一字符串)
  • status——0 有效, 1 无效
  • expired——过期时间

2、实体类——LoginTicket

getter and setter
toString

public class LoginTicket {

    private int id;
    private int userId;
    private String ticket;
    private int status;
    private Date expired;
   
}

3、LoginTicketMapper——写SQL、通过注解

依据ticket,来查找

package com.nowcoder.community.dao;

import com.nowcoder.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}"
    })
    // 以 ticket 为凭证查询
    LoginTicket selectByTicket(String ticket);

    @Update({
            ""
    })
    int updateStatus(String ticket, int status);

}

4、测试

    @Test
    public 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);
    }

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第45张图片

    @Test
    public void testSelectLoginTicket() {
        LoginTicket loginTicket = loginTicketMapper.selectByTicket("abc");
        System.out.println(loginTicket);

        loginTicketMapper.updateStatus("abc", 1);
        loginTicket = loginTicketMapper.selectByTicket("abc");
        System.out.println(loginTicket);
    }

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第46张图片

登录

  • 验证账号、密码、验证码。
  • 成功时,生成登录凭证,发放给客户端。
  • 失败时,跳转回登录页。

1、业务层——UserService

登录失败的原因:

  • 账号没输入、不存在、没激活

用户在页面输入的密码是明文

// 用户在页面输入的密码是明文,存的是加密后的,MD5
    // expiredSeconds 多长时间后,凭证过期
    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;
    }

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

2、LoginController

  • 验证账号、密码、验证码。
  • 成功时,生成登录凭证,发放给客户端。
  • 失败时,跳转回登录页。
@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路径——整个项目
            cookie.setMaxAge(expiredSeconds);       // cookie 有效时间
            response.addCookie(cookie);         // 把 cookie 发送给页面上
            return "redirect:/index";
        } else {
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));
            return "/site/login";
        }
    }

3、登录页面

发送请求时,修改表单提交方式、路径、名字
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第47张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第48张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第49张图片
错误信息展示,给默认值

  • 账号
  • 密码
    【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第50张图片
    【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第51张图片
    【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第52张图片

账号相关的提示是动态的

退出

  • 将登录凭证修改为失效状态。
  • 跳转至网站首页。

1、状态标识——UserService

  • ticket 改为 1,无效
    public void logout(String ticket) {
        loginTicketMapper.updateStatus(ticket, 1);
    }

2、返回退出页面请求——LoginController

  • 返回重新登录页面
    @RequestMapping(path = "/logout", method = RequestMethod.GET)
    public String logout(@CookieValue("ticket") String ticket) {
        userService.logout(ticket);
        return "redirect:/login";
    }

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第53张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第54张图片

3、配置退出登录页面的链接——index

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第55张图片
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第56张图片

六、显示登录信息

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第57张图片

拦截器

  • 示例定义拦截器,实现Handlerlnterceptor
  • 配置拦截器,为它指定拦截、排除的路径

拦截器应用

  • 在请求开始时查询登录用户。
  • 在本次请求中持有用户数据
  • 在模板视图上显示用户数据
  • 在请求结束时清理用户数据

拦截器可以拦截请求,在拦截请求的开始和结束,插入一些代码

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第58张图片

1、拦截器

  • 示例定义拦截器,实现Handlerlnterceptor
  • 配置拦截器,为它指定拦截、排除的路径

拦截器测试——AlphaInterceptor

在这里插入图片描述

package com.nowcoder.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 {

    // 日志——debug级别
    private static final Logger logger = LoggerFactory.getLogger(AlphaInterceptor.class);

    // 在Controller之前执行, 请求之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.debug("preHandle: " + handler.toString());   //debug级别
        return true;
    }

    // 在Controller之后执行,  模板之前执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.debug("postHandle: " + handler.toString());
    }

    // 在 TemplateEngine 之后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.debug("afterCompletion: " + handler.toString());
    }
}

配置类——WebMvcConfig

在这里插入图片描述

  • 实现接口——WebMvcConfigurer
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    //拦截器注入
    @Autowired
    private AlphaInterceptor alphaInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /*
            拦截一切请求,不拦截的 加后边
         */
        //  /**/*.css _ static 目录下
        registry.addInterceptor(alphaInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg")
                .addPathPatterns("/register", "/login");   // 拦截注册 和登录

    }

2、拦截器应用

  • 在请求开始时查询登录用户。
  • 在本次请求中持有用户数据
  • 在模板视图上显示用户数据
  • 在请求结束时清理用户数据

拦截器——LoginTicketInterceptor

每次请求的过程
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第59张图片

request获取Cookie——CookieUtil

  • 复用request获取Cookie
  • 返回Cookie中的值
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) {
                // cookie 的 name 是不是传入的
                if (cookie.getName().equals(name)) {
                    return cookie.getValue();
                }
            }
        }

        return null;
    }

}

查询登录凭证——UserService

    //查询登录凭证
    public LoginTicket findLoginTicket(String ticket) {
        return loginTicketMapper.selectByTicket(ticket);
    }

找map——HostHolder

  • 持有用户信息,用于代替session对象.
/**
 * 持有用户信息,用于代替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();
    }

}

拦截器主体代码-LoginTicketInterceptor

@Component
public class LoginTicketInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从 cookie 中获取凭证  cookie——ticket
        String ticket = CookieUtil.getValue(request, "ticket");

        if (ticket != null) {
            // 查询凭证
            LoginTicket loginTicket = userService.findLoginTicket(ticket);
            // 检查凭证是否有效
            // 凭证不为空、状态为 0、超时时间晚于登陆时间
            if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
                // 根据凭证查询用户
                User user = userService.findUserById(loginTicket.getUserId());
                // 在本次请求中持有用户
                hostHolder.setUser(user);
            }
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        User user = hostHolder.getUser();
        if (user != null && modelAndView != null) {
            // 将 user 添加到 model
            modelAndView.addObject("loginUser", user);
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清理数据
        hostHolder.clear();
    }
}

配置——WebMvcConfig

  @Autowired
    private LoginTicketInterceptor loginTicketInterceptor;

	  @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /*
            拦截一切请求,不拦截的 加后边
         */
        registry.addInterceptor(loginTicketInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
    }

首页——index

登录 才能看到 消息

在这里插入图片描述

没登录 才显示 注册

在这里插入图片描述

没登录 才显示 登录

在这里插入图片描述

调整登录账号显示

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第60张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第61张图片

3、运行结果:

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第62张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第63张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第64张图片

4、总结

关于Spring MVC拦截器:

  • 拦截器需实现HandlerInterceptor接口,而WebMvcConfigurer接口是MVC配置类要实现的接口
  • preHandle方法在Controller之前执行,若返回false,则终止执行后续的请求。
  • postHandle方法在Controller之后、模板之前执行。
  • afterCompletion方法在模板之后执行。

关于配置Spring MVC拦截器

  • 配置类需实现WebMvcConfigurer接口
  • 通过addInterceptors方法对拦截器进行配置
  • 可以配置忽略拦截的路径,也可以配置希望拦截的路径

关于ThreadLocal的描述

  • ThreadLocal采用线程隔离的方式存放数据,可以避免多线程之间出现数据访问冲突。
  • ThreadLocal提供set方法,能够以当前线程为key存放数据。
  • ThreadLocal提供get方法,能够以当前线程为key获取数据。

七、账号设置——上传头像、修改密码

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第65张图片
上传文件

  • 请求:必须是POST请求
  • 表单:enctype="multipart/form-data'
  • Spring MVC:通过MultipartFile处理上传文件

开发步骤

  • 访问账号设置页面
  • 上传头像
  • 获取头像

完成这个页面
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第66张图片

1、可以访问这个页面

返回访问页面——UserController

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第67张图片

    @RequestMapping(path = "/setting", method = RequestMethod.GET)
    public String getSettingPage() {
        return "/site/setting";
    }

显示页面——setting.html

修改路径等
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第68张图片
在这里插入图片描述
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第69张图片

index中修改——链接

在这里插入图片描述

2、上传头像

  • 开放时,是Windows
  • 上线时,是 Linux

配置文件

添加 存储 上传文件的路径

community.path.upload=j:/work/data/upload

UserService

更新修改图像的路径,返回更新行数

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

UserController

  • 上传表单提交为post
    请求
  • 项目域名
  • 项目名

	private static final 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;     //项目名

    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;      //取 当前用户是谁
    
    @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.png
        User 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

在这里插入图片描述
在这里插入图片描述

3、修改密码

UserService

    public User findUserByName(String username) {
        return userMapper.selectByName(username);
    }

    // 重置密码
    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;
    }

    // 修改密码
    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

    // 修改密码
    @RequestMapping(path = "/updatePassword", method = RequestMethod.POST)
    public String updatePassword(String oldPassword, String newPassword, Model model) {
        User user = hostHolder.getUser();
        Map<String, Object> map = userService.updatePassword(user.getId(), oldPassword, newPassword);
        if (map == null || map.isEmpty()) {
            return "redirect:/logout";
        } else {
            model.addAttribute("oldPasswordMsg", map.get("oldPasswordMsg"));
            model.addAttribute("newPasswordMsg", map.get("newPasswordMsg"));
            return "/site/setting";
        }
    }

setting.html

更改

总结

上传文件的必要条件

  • 必须在POST请求中上传文件
  • 表单的enctype属性必须设置为“multipart/form-data”

关于上传路径与访问路径的描述

  • 上传路径可以是本地路径也可以是web路径,
  • 访问路径必须是符合HTTP协议的Web路径。

关于MultipartFile类型的描述

  • 一个MultipartFile只能封装一个文件
  • 通过MultipartFilegetOriginalFilename方法,可以获得原始文件名
  • 通过MultipartFiletransferTo方法,可以将文件存入指定位置

八、检查登录状态

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第70张图片

  • 没有登陆也能访问登录后的页面
  • 安全隐患
  • 拦截器——不在配置文件中拦截,用注解在方法上拦截

使用拦截器

  • 在方法前标注自定义注解
  • 拦截所有请求,只处理带有该注解的方法

自定义注解

常用的元注解:

  • @Target、——声明自定义注解作用在哪个位置,例如方法上、类上
  • @Retention、——声明自定义注解的有效时间(编译时、运行时)
  • @Document、——声明自定义注解生成文档的时候要不要把注解带上去
  • @Inherited——用于继承,父类有注解,子类是否要继承

如何读取注解:

  • 反射
  • Method.getDeclaredAnnotations()
  • Method.getAnnotation(classannotationclass)

1、写注解——LoginRequired

新建一个包,写注解
【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第71张图片

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第72张图片

@Target(ElementType.METHOD)   // 方法上
@Retention(RetentionPolicy.RUNTIME)  //程序运行时有效
public @interface LoginRequired {

        // 里边不用写内容,标注解就行
}

2、加上注解——UserController

在需要的方法上,加上注解

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第73张图片

3、拦截器——LoginRequiredInterceptor

【论坛java项目】第二章 Spring Boot实践,开发社区登录模块:发送邮件、开发注册功能、会话管理、生成验证码、开发登录、退出功能、_第74张图片

@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {

    @Autowired
    private HostHolder hostHolder;   // 获取当前用户

    @Override
    public 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;
    }
}

4、拦截器配置——WebMvcConfig

拦截器配置——指定生成的路径

好处,拦截谁,就给谁加注解

  // 拦截指定方法
    @Autowired
    private LoginRequiredInterceptor loginRequiredInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /*
            拦截一切请求,不拦截的 加后边
         */
        //  /**/*.css _ static 目录下

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

    }

总结

关于元注解

  • @Target用于描述该注解可以作用的目标类型
  • @Retention用于描述该注解被保留的时间
  • @Document用于描述该注解是否可以生成到文档里
  • 比如LoginRequired加上了这个@Inherited,那注解LoginRequired的类的子类也会自动注解上LoginRequired

关于解析注解

  • 在程序中,可以通过反射的方式解析注解
  • 通过Method对象可以获取某方法上标注的所有注解
  • 通过Method对象可以获取某方法上指定类型的注解
  • Method对象上还有很多其他的方法,可以获取该方法上标注的注解

在程序中,可以通过哪些方式正确实现重定向

  • 在Controller的方法里,通过返回以”redirect”开头的字符串实现重定向
  • 在Controller的方法里,通过response对象的sendRedirect方法实现重定向
  • 在拦截器中,通过response对象的sendRedirect方法实现重定向

你可能感兴趣的:(论坛项目,java,spring,boot,spring)