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


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

    目录

    在这里插入图片描述

    一、发送邮件

    在这里插入图片描述

    1、启用客户端SMTP服务

    在这里插入图片描述

    在这里插入图片描述
    bofryuzursekbiab——密码

    2、导入jar包

    在这里插入图片描述

    <!-- 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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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());
            }
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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("1724206051@qq.com", "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("1724206051@qq.com", "HTML", content);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    总结

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

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

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

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

    • JavaMailSender
    • TemplateEngine

    二、开发注册功能

    在这里插入图片描述

    1、访问注册页面

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

    修改——thymeleaf

    在这里插入图片描述

    在这里插入图片描述

    首页—超链接——index.html

    在这里插入图片描述

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

    在这里插入图片描述

    LoginController

    在这里插入图片描述
    返回注册页面

    @Controller
    public class LoginController {
        @Autowired
        private UserService userService;
    
        @RequestMapping(path = "/register", method = RequestMethod.GET)
        public String getRegisterPage() {
            return "/site/register";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2、提交注册数据

    通过表单提交数据。

    添加依赖和配置

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

    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());
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    注入——UserService

    在这里插入图片描述

    • 注入邮件客户端
    • 注入模板引擎
    @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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    牛客网随机头像
    在这里插入图片描述

    改造模板——activation.html

    在这里插入图片描述

    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}">xxx@xxx.comb>, 您好!
    	p>
    	<p>
    		您正在注册牛客网, 这是一封激活邮件, 请点击
    		<a th:href="${url}">此链接a>,
    		激活您的牛客账号!
    	p>
    div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    控制层

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

    在这里插入图片描述

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

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

    注册成功或有错误返回——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";
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    激活成功模板——operate-result.html
    账号、密码、邮箱错误——返回register.html

    默认值的显示
    在这里插入图片描述
    在这里插入图片描述

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

    3、激活注册账号

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

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

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

    三种结果:

    • 成功
    • 重复激活
    • 失败

    常量接口——CommunityConstant

    在这里插入图片描述

    public interface CommunityConstant {
    
        /**
         * 激活成功
         */
        int ACTIVATION_SUCCESS = 0;
    
        /**
         * 重复激活
         */
        int ACTIVATION_REPEAT = 1;
    
        /**
         * 激活失败
         */
        int ACTIVATION_FAILURE = 2;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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;
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    返回页面——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";
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    login添加到模板

    在这里插入图片描述

    在这里插入图片描述

    验证码更改

    在这里插入图片描述

    首页更改——index

    在这里插入图片描述

    激活,跳到登录页面

    在这里插入图片描述

    三、会话管理

    在这里插入图片描述
    HTTP教程

    HTTP 是无状态,有会话的

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

    1、HTTP Cookie

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

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

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

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

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

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

    缺点:

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

    在这里插入图片描述

    • 浏览器访问服务器,服务器会产生一个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";
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    get cookie

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

    在这里插入图片描述

    2、Session

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

    服务器靠什么区分Session

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

    在这里插入图片描述

    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";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    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";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

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

    • 分布式部署——同时部署多台服务器,同时向浏览器提供支持
    分布式部署下,有什么问题:

    在这里插入图片描述

    解决方法

    1、粘性Session

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

    2、同步Session

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

    3、共享Session

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

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

    在这里插入图片描述

    优点:

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

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

    在这里插入图片描述

    目前没部署Redis,怎么办?

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

    四、生成验证码——Kaptcha

    在这里插入图片描述

    1、Kaptcha

    Kaptcha官方手册

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

    在这里插入图片描述

    导入依赖

    		<dependency>
    			<groupId>com.github.pengglegroupId>
    			<artifactId>kaptchaartifactId>
    			<version>2.3.2version>
    		dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    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());
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    4、刷新验证码

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    结果
    在这里插入图片描述

    5、总结

    关于Kaptcha的描述

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

    关于使用Kaptcha的描述

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

    关于Kaptcha配置的描述

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

    五、开发登录、退出功能

    在这里插入图片描述

    访问登录页面

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

    1、数据库——login_ticket

    在这里插入图片描述

    • 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;
       
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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);
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    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);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

        @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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    登录

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

    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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    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";
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    3、登录页面

    发送请求时,修改表单提交方式、路径、名字
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述
    错误信息展示,给默认值

    • 账号
    • 密码
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    账号相关的提示是动态的

    退出

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

    1、状态标识——UserService

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

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

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

    在这里插入图片描述

    在这里插入图片描述

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

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

    六、显示登录信息

    在这里插入图片描述

    拦截器

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

    拦截器应用

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

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

    在这里插入图片描述

    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());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    配置类——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");   // 拦截注册 和登录
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2、拦截器应用

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

    拦截器——LoginTicketInterceptor

    每次请求的过程
    在这里插入图片描述

    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;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    查询登录凭证——UserService
        //查询登录凭证
        public LoginTicket findLoginTicket(String ticket) {
            return loginTicketMapper.selectByTicket(ticket);
        }
    
    • 1
    • 2
    • 3
    • 4
    找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();
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    拦截器主体代码-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();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    配置——WebMvcConfig
      @Autowired
        private LoginTicketInterceptor loginTicketInterceptor;
    
    	  @Override
        public void addInterceptors(InterceptorRegistry registry) {
            /*
                拦截一切请求,不拦截的 加后边
             */
            registry.addInterceptor(loginTicketInterceptor)
                    .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    首页——index

    登录 才能看到 消息

    在这里插入图片描述

    没登录 才显示 注册

    在这里插入图片描述

    没登录 才显示 登录

    在这里插入图片描述

    调整登录账号显示

    在这里插入图片描述

    在这里插入图片描述

    3、运行结果:

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    4、总结

    关于Spring MVC拦截器:

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

    关于配置Spring MVC拦截器

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

    关于ThreadLocal的描述

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

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

    在这里插入图片描述
    上传文件

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

    开发步骤

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

    完成这个页面
    在这里插入图片描述

    1、可以访问这个页面

    返回访问页面——UserController

    在这里插入图片描述

        @RequestMapping(path = "/setting", method = RequestMethod.GET)
        public String getSettingPage() {
            return "/site/setting";
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    显示页面——setting.html

    修改路径等
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    index中修改——链接

    在这里插入图片描述

    2、上传头像

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

    配置文件

    添加 存储 上传文件的路径

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

    UserService

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

        public int updateHeader(int userId, String headerUrl) {
            return userMapper.updateHeader(userId, headerUrl);
        }
    
    • 1
    • 2
    • 3

    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());
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    账号设置——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;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    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";
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    setting.html

    更改

    总结

    上传文件的必要条件

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

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

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

    关于MultipartFile类型的描述

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

    八、检查登录状态

    在这里插入图片描述

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

    使用拦截器

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

    自定义注解

    常用的元注解:

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

    如何读取注解:

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

    1、写注解——LoginRequired

    新建一个包,写注解
    在这里插入图片描述

    在这里插入图片描述

    @Target(ElementType.METHOD)   // 方法上
    @Retention(RetentionPolicy.RUNTIME)  //程序运行时有效
    public @interface LoginRequired {
    
            // 里边不用写内容,标注解就行
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2、加上注解——UserController

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

    在这里插入图片描述

    3、拦截器——LoginRequiredInterceptor

    在这里插入图片描述

    @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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4、拦截器配置——WebMvcConfig

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

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

      // 拦截指定方法
        @Autowired
        private LoginRequiredInterceptor loginRequiredInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            /*
                拦截一切请求,不拦截的 加后边
             */
            //  /**/*.css _ static 目录下
    
            registry.addInterceptor(loginRequiredInterceptor)
                    .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    总结

    关于元注解

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

    关于解析注解

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

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

    • 在Controller的方法里,通过返回以”redirect”开头的字符串实现重定向
    • 在Controller的方法里,通过response对象的sendRedirect方法实现重定向
    • 在拦截器中,通过response对象的sendRedirect方法实现重定向
  • 相关阅读:
    docker network create命令
    离职,问题就解决了吗?
    MySQL数据库主从同步,一致性解决方案
    程序员想要网上接单却不知道如何是好?那这篇文章你可得收藏好了!
    R语言和医学统计学(9):多重检验
    MyBatis简述
    负载均衡(SLB与ELB)
    qrcodejs2 生成二维码 并上传oss
    如何在不损失质量的情况下调整图像大小
    Redis -- 基本知识说明
  • 原文地址:https://blog.csdn.net/qq_47540091/article/details/125533107