第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 {

    @Autowired
    private MailClient mailClient;

    @Autowired
    private TemplateEngine templateEngine;

    String to="3063494684@qq.com";
    @Test
    public void testTextMail(){
        mailClient.sendMail(to,"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(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 -> abc123def456abc
    public 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{
    @Autowired
    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<>();
        // 空值处理
        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/code
        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;
    }

}

新增:LoginController.register()

package com.jsss.community.controller;


@Controller
public class LoginController {

    @Autowired
    UserService 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)
    @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";

    }

    @RequestMapping(path = "/cookie/get",method = RequestMethod.GET)
    @ResponseBody
    public 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)
    @ResponseBody
    public String setSession(HttpSession session){
        session.setAttribute("id",1);
        session.setAttribute("name","Test");
        return "set 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";
    }

测试:

访问: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 {

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

新增:LoginController.getKaptcha()


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

    @Autowired
    private Producer kaptchaProducer;
    
    @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 {
            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()


    @Autowired
    private LoginTicketMapper loginTicketMapper;

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

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

UserService

新增:UserService.login()

    @Autowired
    private 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)
    @ResponseBody
    public 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之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.debug("preHandle: " + handler.toString());
        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());
    }
}

配置拦截器

新增: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 {

    @Autowired
    private AlphaInterceptor alphaInterceptor;



    @Override
    public 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 {

    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;

    //Controller执行之前,拿到登录用户
    @Override
    public 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
    @Override
    public 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);
        }
    }

    @Override
    public 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 {

    @Autowired
    private AlphaInterceptor alphaInterceptor;

    @Autowired
    private LoginTicketInterceptor loginTicketInterceptor;

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

    @Autowired
    private UserService userService;

    @Autowired
    private 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.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:修改头像

测试

访问:账户设置

测试:上传头像

图片下载到: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 {

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

WebMvcConfig

修改:WebMvcConfig

添加登录拦截

    @Autowired
    private LoginRequiredInterceptor loginRequiredInterceptor;


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

测试

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

会跳到登录页面

最后

2023-7-30 16:26:24

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

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

开源:为爱发电

学习:为我而行

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

idea使用gitee基本操作流程

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

数据结构--链表的基本操作

1. 链表的概念及结构 概念&#xff1a;链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。 链表也是线性表的一种。 链表的结构跟⽕⻋⻋厢相似&#xff0c;淡季时⻋次的⻋厢会相应减少&#xff0c;旺季时…

Java(四)---方法的使用

文章目录 前言1.方法的概念和使用2.方法的定义3.实参和形参的关系4.方法重载4.1.改进4.2.注意事项 5.递归5.1 生活中的故事5.2 递归的概念 5.3.练习 前言 前面一章我们学习到了程序逻辑语句&#xff0c;在写代码的过程中&#xff0c;我们会遇到需要重复使用的代码块&#xff0…

运维别卷系列 - 云原生监控平台 之 05.prometheus alertManager 实践

文章目录 [toc]Alertmanager 简介Alertmanager 实现的核心概念GroupingInhibitionSilencesClient behaviorHigh Availability Alertmanager 配置文件globaltemplatesrouteinhibit_rulesreceivers Alertmanager 部署创建 cm创建 svc创建 stsPrometheus 配置告警Prometheus 配置文…

React Native 开发心得分享

有一段时间没更新了&#xff0c;花了点时间研究了下 React Native&#xff08;后续用 RN 简称&#xff09;&#xff0c;同时也用该技术作为我的毕设项目(一个校园社交应用&#xff0c;仿小红书)&#xff0c;经过了这段时间的疯狂折腾&#xff0c;对 RN 生态有了一定的了解&…

3.TCP的三次握手和四次挥手

一、前置知识 TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。在传输数据前通信双方必须建立连接&#xff08;所谓连接&#xff0c;是指客户端和服务端各自保存一份关于对方的信息&#xff0c;比如ip地址&#xff0c;端口号等&#xff09;。TCP通过三次握手建立一个…

iOS 创建pch文件

1.参考链接&#xff08;xcode8添加方法&#xff0c;之前的跟这个差不多&#xff09;&#xff1a; 参考链接 2.自我总结&#xff1a; &#xff08;1&#xff09;创建pch文件: 注意点&#xff1a;1&#xff09;注意选中所有的targets&#xff08;看图明义&#xff09; 2&…

风电功率预测 | 基于CNN卷积神经网络的风电功率预测(附matlab完整源码)

风电功率预测 风电功率预测完整代码风电功率预测 基于卷积神经网络(Convolutional Neural Network, CNN)的风电功率预测可以通过以下步骤实现: 数据准备:收集与风电场发电功率相关的数据,包括风速、风向、温度、湿度等气象数据以及风电场的历史功率数据。 数据预处理:对…

ACM8628 2×41W立体声1×82W单通道数字功放中文寄存器表

ACM8628M是一款国产高度集成、高效率的双通道数字输入功放。供电电压范围在4.5V-26.4V,数字接口电源支持3.3V或1.8V。在8欧负载下&#xff0c;输出功率可以到241W&#xff0c;PBTL模式下单通道可以输出182W1% THDN. 器件介绍见 内置DSP多种音频处理效果ACM8628M-241W立体声…

「51媒体」家居生活发布会,展览展会有哪些媒体邀约资源

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 家居生活类媒体包括多种类型&#xff0c;包括门户网站家居生活消费频道&#xff0c;专业的家居消费生活门户&#xff0c;以及行业媒体&#xff0c;平面媒体&#xff0c;KOL和意见领袖。下…

高通Android 11/12/13 通过包名设置默认launcher

背景&#xff1a;最近在封装供第三应用系统SDK 接口&#xff0c;遇到一个无法通过包名设置主launcher代码坑所以记录下。 涉及类roles.xml # <!---~ see com.android.settings.applications.defaultapps.DefaultHomePreferenceController~ see com.android.settings.appl…

Git 基础使用(2) 分支管理

文章目录 分支概念分支使用查看分支分支创建分支切换分支合并合并冲突分支删除 分支管理快进模式分支策略内容保存错误处理 分支概念 &#xff08;1&#xff09;分支概念 Git分支是指在版本控制系统Git中&#xff0c;用来表示项目的不同工作流程或开发路径的一个重要概念。通过…

海豚调度器如何看工作流是在哪个worker节点执行

用海豚调度器&#xff0c;执行一个工作流时&#xff0c;有时成功&#xff0c;有时失败&#xff0c;怀疑跟worker节点环境配置不一样有关。要怎样看是在哪个worker节点执行&#xff0c;在 海豚调度器 Web UI 中&#xff0c;您可以查看任务实例&#xff0c;里面有一列显示host&a…

从零开始详解OpenCV条形码区域分割

前言 在识别二维码之前&#xff0c;首先要划分出二维码的区域&#xff0c;在本篇文章中将从零开始实现二维码分割的功能&#xff0c;并详细介绍用到的方法。 我们需要处理的图像如下&#xff1a; 完整代码 首先我们先放出完整代码&#xff0c;然后根据整个分割流程介绍用到…

windows 使用 workerman

简单示例 workerman从3.5.3版本开始已经能够同时支持linux系统和windows系统。 1、需要PHP>5.4&#xff0c;并配置好PHP的环境变量。 2、Windows版本的Workerman不依赖任何扩展。 3、安装使用以及使用限制这里。 4、由于Workerman在Windows下有诸多使用限制&#xff0c…

【万字面试题】Redis

文章目录 常见面试题布隆过滤器原理和数据结构&#xff1a;特点和应用场景&#xff1a;缺点和注意事项&#xff1a;在python中使用布隆过滤器 三种数据删除策略LRU (Least Recently Used)工作原理&#xff1a;应用场景&#xff1a; LFU (Least Frequently Used)工作原理&#x…

vs2019 c++中模板 enable_if_t 的使用

&#xff08;1&#xff09; 该模板的定义如下&#xff1a; template <bool _Test, class _Ty void> struct enable_if {}; // no member "type" when !_Testtemplate <class _Ty> struct enable_if<true, _Ty> { // type is _Ty for _Testusing …

OSEK应用模式

1 前言 应用模式&#xff08;Application modes)用于区分不同的场景&#xff0c;以便在系统运行时&#xff0c;组织各自相互独立的OS相关的资源集合&#xff0c;是一种分而治之的思想体现。不同的应用模式是互斥的&#xff0c;即系统当前必须在一种应用模式&#xff08;且只能在…

OV SSL证书的特点

OV SSL证书&#xff0c;全称为Organization Validation SSL Certificate&#xff08;组织验证型SSL证书&#xff09;&#xff0c;是一种中级的SSL证书类型。与仅验证域名所有权的DV&#xff08;Domain Validation&#xff09;证书不同&#xff0c;OV证书在颁发前会执行更加严格…

01.认识HTML及常用标签

目录 URL&#xff08;统一资源定位系统&#xff09; HTML&#xff08;超文本标记语言&#xff09; 1&#xff09;html标签 2&#xff09;head标签 3&#xff09;title标签 4&#xff09;body标签 标签的分类 DTD文档声明 基础标签 1&#xff09;H系列标签 2&#xff09…