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

news2024/11/29 12:32:02

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

目录

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

在这里插入图片描述

一、发送邮件

在这里插入图片描述

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>

3、邮箱参数配置

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

MailClient

在这里插入图片描述

package com.nowcoder.community.util;

@Component
public class MailClient {

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

    @Autowired
    private JavaMailSender mailSender;

    @Value("${spring.mail.username}")
    private String from;

    public void sendMail(String to, String subject, String content) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message);
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, true);
            mailSender.send(helper.getMimeMessage());
        } catch (MessagingException e) {
            logger.error("发送邮件失败:" + e.getMessage());
        }
    }

}

demo.html

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

MailTests


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

    @Autowired
    private MailClient mailClient;

    @Autowired
    private TemplateEngine templateEngine;

    @Test
    public void testTextMail() {
        mailClient.sendMail("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. 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";
    }
}

2、提交注册数据

通过表单提交数据。

添加依赖和配置

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

service层

  • 服务端发送激活邮件。

工具类——CommunityUtil

生成随机字符串

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

public class CommunityUtil {

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

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


}

注入——UserService

在这里插入图片描述

  • 注入邮件客户端
  • 注入模板引擎
@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;
    }
}

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

改造模板——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.com</b>, 您好!
	</p>
	<p>
		您正在注册牛客网, 这是一封激活邮件, 请点击
		<a th:href="${url}">此链接</a>,
		激活您的牛客账号!
	</p>
</div>
</body>
</html>

控制层

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

在这里插入图片描述

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

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

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

    @RequestMapping(path = "/register", method = RequestMethod.POST)
    public String register(Model model, User user) {
        Map<String, Object> map = userService.register(user);
        if (map == null || map.isEmpty()) {
            model.addAttribute("msg", "注册成功,我们已经向您的邮箱发送了一封激活邮件,请尽快激活!");
            model.addAttribute("target", "/index");
            return "/site/operate-result";
        } else {
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));
            model.addAttribute("emailMsg", map.get("emailMsg"));
            return "/site/register";
        }
    }

激活成功模板——operate-result.html

账号、密码、邮箱错误——返回register.html

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

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

3、激活注册账号

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

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

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

三种结果:

  • 成功
  • 重复激活
  • 失败

常量接口——CommunityConstant

在这里插入图片描述

public interface CommunityConstant {

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

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

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

}

UserService

    public int activation(int userId, String code) {
        User user = userMapper.selectById(userId);
        // 看状态、 激活码
        if (user.getStatus() == 1) {
            return ACTIVATION_REPEAT;
        } else if (user.getActivationCode().equals(code)) {
            userMapper.updateStatus(userId, 1);
            return ACTIVATION_SUCCESS;
        } else {
            return ACTIVATION_FAILURE;
        }
    }

返回页面——LoginController


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

login添加到模板

在这里插入图片描述

在这里插入图片描述

验证码更改

在这里插入图片描述

首页更改——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";
    }

在这里插入图片描述

get cookie

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

为什么在分布式部署下,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.penggle</groupId>
			<artifactId>kaptcha</artifactId>
			<version>2.3.2</version>
		</dependency>

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

package com.nowcoder.community.config;

import com.google.code.kaptcha.Producer;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

@Configuration
public class KaptchaConfig {

    @Bean
    public Producer kaptchaProducer() {
        Properties properties = new Properties();
        properties.setProperty("kaptcha.image.width", "100");       // 图片宽度
        properties.setProperty("kaptcha.image.height", "40");      // 图片高度
        properties.setProperty("kaptcha.textproducer.font.size", "32");      // 字号
        properties.setProperty("kaptcha.textproducer.font.color", "0,0,0");      // 颜色- 黑色
        properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYAZ");      // 随机字符范围
        properties.setProperty("kaptcha.textproducer.char.length", "4");      // 长度
        properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");

        DefaultKaptcha kaptcha = new DefaultKaptcha();  // 默认实现类
        Config config = new Config(properties);   // 配置
        kaptcha.setConfig(config);
        return kaptcha;
    }

}

3、LoginController——生成验证码

@Autowired
    private Producer kaptchaProducer;

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

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

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

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

在这里插入图片描述

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

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({
            "<script>",
            "update login_ticket set status=#{status} where ticket=#{ticket} ",
            "<if test=\"ticket!=null\"> ",
            "and 1=1 ",
            "</if>",
            "</script>"
    })
    int updateStatus(String ticket, int status);

}

4、测试

    @Test
    public void testInsertLoginTicket() {
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(101);
        loginTicket.setTicket("abc");
        loginTicket.setStatus(0);
        loginTicket.setExpired(new Date(System.currentTimeMillis() + 1000 * 60 * 10));

        loginTicketMapper.insertLoginTicket(loginTicket);
    }

在这里插入图片描述

    @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、业务层——UserService

登录失败的原因:

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

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

// 用户在页面输入的密码是明文,存的是加密后的,MD5
    // expiredSeconds 多长时间后,凭证过期
    public Map<String, Object> login(String username, String password, int expiredSeconds) {
        Map<String, Object> map = new HashMap<>();

        // 空值处理
        if (StringUtils.isBlank(username)) {
            map.put("usernameMsg", "账号不能为空!");
            return map;
        }
        if (StringUtils.isBlank(password)) {
            map.put("passwordMsg", "密码不能为空!");
            return map;
        }

        // 验证账号
        User user = userMapper.selectByName(username);
        if (user == null) {
            map.put("usernameMsg", "该账号不存在!");
            return map;
        }

        // 验证状态
        if (user.getStatus() == 0) {
            map.put("usernameMsg", "该账号未激活!");
            return map;
        }

        // 验证密码
        password = CommunityUtil.md5(password + user.getSalt());
        if (!user.getPassword().equals(password)) {
            map.put("passwordMsg", "密码不正确!");
            return map;
        }

        // 生成登录凭证
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(user.getId());
        loginTicket.setTicket(CommunityUtil.generateUUID());
        loginTicket.setStatus(0);
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
        loginTicketMapper.insertLoginTicket(loginTicket);

        map.put("ticket", loginTicket.getTicket());
        return map;
    }

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

2、LoginController

  • 验证账号、密码、验证码。
  • 成功时,生成登录凭证,发放给客户端。
  • 失败时,跳转回登录页。
@RequestMapping(path = "/login", method = RequestMethod.POST)
    public String login(String username, String password, String code, boolean rememberme,
                        Model model, HttpSession session, HttpServletResponse response) {
        // 检查验证码
        String kaptcha = (String) session.getAttribute("kaptcha");
        if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
            model.addAttribute("codeMsg", "验证码不正确!");
            return "/site/login";
        }

        // 检查账号,密码
        int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
        Map<String, Object> map = userService.login(username, password, expiredSeconds);
        if (map.containsKey("ticket")) {
            Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
            cookie.setPath(contextPath);    // cookie路径——整个项目
            cookie.setMaxAge(expiredSeconds);       // cookie 有效时间
            response.addCookie(cookie);         // 把 cookie 发送给页面上
            return "redirect:/index";
        } else {
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));
            return "/site/login";
        }
    }

3、登录页面

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

在这里插入图片描述

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

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

账号相关的提示是动态的

退出

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

1、状态标识——UserService

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

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

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

在这里插入图片描述

在这里插入图片描述

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

配置类——WebMvcConfig

在这里插入图片描述

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

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

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

    }

2、拦截器应用

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

拦截器——LoginTicketInterceptor

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

request获取Cookie——CookieUtil

  • 复用request获取Cookie
  • 返回Cookie中的值
public class CookieUtil {

    public static String getValue(HttpServletRequest request, String name) {
        if (request == null || name == null) {
            throw new IllegalArgumentException("参数为空!");
        }

        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                // cookie 的 name 是不是传入的
                if (cookie.getName().equals(name)) {
                    return cookie.getValue();
                }
            }
        }

        return null;
    }

}

查询登录凭证——UserService

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

找map——HostHolder

  • 持有用户信息,用于代替session对象.
/**
 * 持有用户信息,用于代替session对象.
 */
@Component
public class HostHolder {

    private ThreadLocal<User> users = new ThreadLocal<>();

    public void setUser(User user) {
        users.set(user);
    }

    public User getUser() {
        return users.get();
    }

    public void clear() {
        users.remove();
    }

}

拦截器主体代码-LoginTicketInterceptor

@Component
public class LoginTicketInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;

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

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

        return true;
    }

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

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

配置——WebMvcConfig

  @Autowired
    private LoginTicketInterceptor loginTicketInterceptor;

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

首页——index

登录 才能看到 消息

在这里插入图片描述

没登录 才显示 注册

在这里插入图片描述

没登录 才显示 登录

在这里插入图片描述

调整登录账号显示

在这里插入图片描述

在这里插入图片描述

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

显示页面——setting.html

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

index中修改——链接

在这里插入图片描述

2、上传头像

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

配置文件

添加 存储 上传文件的路径

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

UserService

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

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

UserController

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

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

    @Value("${community.path.upload}")
    private String uploadPath;    // 上传路径

    @Value("${community.path.domain}")
    private String domain;      // 域名

    @Value("${server.servlet.context-path}")
    private String contextPath;     //项目名

    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;      //取 当前用户是谁
    
    @RequestMapping(path = "/upload", method = RequestMethod.POST)
    public String uploadHeader(MultipartFile headerImage, Model model) {
        if (headerImage == null) {
            model.addAttribute("error", "您还没有选择图片!");
            return "/site/setting";
        }

        String fileName = headerImage.getOriginalFilename();   // 读取文件的后缀
        String suffix = fileName.substring(fileName.lastIndexOf("."));      // 截取后缀
        if (StringUtils.isBlank(suffix)) {
            model.addAttribute("error", "文件的格式不正确!");
            return "/site/setting";
        }

        // 生成随机文件名
        fileName = CommunityUtil.generateUUID() + suffix;
        // 确定文件存放的路径
        File dest = new File(uploadPath + "/" + fileName);
        try {
            // 存储文件
            headerImage.transferTo(dest);
        } catch (IOException e) {
            logger.error("上传文件失败: " + e.getMessage());
            throw new RuntimeException("上传文件失败,服务器发生异常!", e);
        }

        // 更新当前用户的头像的路径(web访问路径)
        // http://localhost:8080/community/user/header/xxx.png
        User user = hostHolder.getUser();
        String headerUrl = domain + contextPath + "/user/header/" + fileName;
        userService.updateHeader(user.getId(), headerUrl);

        return "redirect:/index";
    }

    @RequestMapping(path = "/header/{fileName}", method = RequestMethod.GET)
    public void getHeader(@PathVariable("fileName") String fileName, HttpServletResponse response) {
        // 服务器存放路径
        fileName = uploadPath + "/" + fileName;
        // 文件后缀
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        // 响应图片
        response.setContentType("image/" + suffix);
        try (
                FileInputStream fis = new FileInputStream(fileName);
                OutputStream os = response.getOutputStream();
        ) {
            byte[] buffer = new byte[1024];
            int b = 0;
            while ((b = fis.read(buffer)) != -1) {
                os.write(buffer, 0, b);
            }
        } catch (IOException e) {
            logger.error("读取头像失败: " + e.getMessage());
        }
    }

账号设置——setting.html

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

3、修改密码

UserService

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

    // 重置密码
    public Map<String, Object> resetPassword(String email, String password) {
        Map<String, Object> map = new HashMap<>();

        // 空值处理
        if (StringUtils.isBlank(email)) {
            map.put("emailMsg", "邮箱不能为空!");
            return map;
        }
        if (StringUtils.isBlank(password)) {
            map.put("passwordMsg", "密码不能为空!");
            return map;
        }

        // 验证邮箱
        User user = userMapper.selectByEmail(email);
        if (user == null) {
            map.put("emailMsg", "该邮箱尚未注册!");
            return map;
        }

        // 重置密码
        password = CommunityUtil.md5(password + user.getSalt());
        userMapper.updatePassword(user.getId(), password);

        map.put("user", user);
        return map;
    }

    // 修改密码
    public Map<String, Object> updatePassword(int userId, String oldPassword, String newPassword) {
        Map<String, Object> map = new HashMap<>();

        // 空值处理
        if (StringUtils.isBlank(oldPassword)) {
            map.put("oldPasswordMsg", "原密码不能为空!");
            return map;
        }
        if (StringUtils.isBlank(newPassword)) {
            map.put("newPasswordMsg", "新密码不能为空!");
            return map;
        }

        // 验证原始密码
        User user = userMapper.selectById(userId);
        oldPassword = CommunityUtil.md5(oldPassword + user.getSalt());
        if (!user.getPassword().equals(oldPassword)) {
            map.put("oldPasswordMsg", "原密码输入有误!");
            return map;
        }

        // 更新密码
        newPassword = CommunityUtil.md5(newPassword + user.getSalt());
        userMapper.updatePassword(userId, newPassword);

        return map;
    }

UserController

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

setting.html

更改

总结

上传文件的必要条件

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

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

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

关于MultipartFile类型的描述

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

八、检查登录状态

在这里插入图片描述

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

使用拦截器

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

自定义注解

常用的元注解:

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

如何读取注解:

  • 反射
  • Method.getDeclaredAnnotations()
  • Method.getAnnotation(class<T>annotationclass)

1、写注解——LoginRequired

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

在这里插入图片描述

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

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

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

4、拦截器配置——WebMvcConfig

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

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

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

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

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

    }

总结

关于元注解

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

关于解析注解

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第13届蓝桥杯Scratch省赛真题集锦

编程题 第 1 题 问答题 报数游戏 题目说明 背景信息: 5个男生和3个女生&#xff0c;8个人围成一个圆圈&#xff0c;给定一个数字n (2 小于等于n 小于等于5)。从第一个开始依次报数&#xff0c;当报数为n时&#xff0c;这个人离开圆圈。然后下一个从1开始报数&#xff0c;再次报…

MySQL---使用索引优化、大批量插入数据优化

1. 使用索引优化 索引是数据库优化最常用也是最重要的手段之一, 通过索引通常可以帮助用户解决大多数的MySQL 的性能优化问题&#xff1a; create table tb_seller (sellerid varchar (100),name varchar (100),nickname varchar (50),password varchar (60),status varchar…

高级Java多线程面试题及回答

高级Java多线程面试题及回答 1)现在有T1、T2、T3三个线程&#xff0c;你怎样保证T2在T1执行完后执行&#xff0c;T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到&#xff0c;目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单&#xff0c;可…

网络安全的红利还能吃几年?

在我看来这是一个伪命题&#xff0c;因为网络安全的核心和本质是持续对抗&#xff0c;只要威胁持续存在&#xff0c;网络安全的红利就会持续存在&#xff01; 对于网络安全新入行的同学们来说&#xff0c;这是一个最坏的时代&#xff0c;因为你只能自己搭环境才能重现那些大牛们…

网络编程 lesson5 IO多路复用

select 当需要在一个或多个文件描述符上等待事件发生时&#xff0c;可以使用select函数。 select函数是一个阻塞调用&#xff0c;它会一直等待&#xff0c;直到指定的文件描述符上有事件发生或超时。 select函数详解 int select(int nfds, fd_set *readfds, fd_set *writefd…

初识SPDK,从SPDK的软件架构到使用实操

相信很多做存储的同学都听说过SPDK,它是Intel开发的一套开源存储栈。SPDK的全称为存储高性能开发包(Storage Performance Development Kit),从名称可以看出SPDK其实就是一个第三方的程序库。但是这个程序库却是非常强大的,下图是SPDK的软件模块图,从该图可以看出,几乎囊…

Linux---用户管理命令(useradd、userdel、usermod、passwd、id)

1. 用户与用户组 Linux系统是一个多用户多任务的分时操作系统&#xff0c;任何一个要使用系统资源的用户&#xff0c;都必须首先向 系统管理员申请一个账号&#xff0c;然后以这个账号的身份进入系统。 Linux系统中可以&#xff1a; 配置多个用户、配置多个用户组、用户可以…

什么?电路板上还要喷漆?

什么是三防漆&#xff1f; 三防漆是一种特殊配方的涂料&#xff0c;用于保护线路板及其相关设备免受环境的侵蚀。三防漆具有良好的耐高低温性能&#xff1b;其固化后成一层透明保护膜&#xff0c;具有优越的绝缘、防潮、防漏电、防震、防尘、防腐蚀、防老化、耐电晕等性能。 在…

MIT6824——lab4(实现一个分片kv存储)的一些实现,问题,和思考

Part A 分片控制器 1. 整体思路 和lab3A一样&#xff0c;shardctler也是一个服务&#xff0c;由客户端调用。这个服务建立在raft集群上&#xff0c;保证容错。 shardctler也应该保证线性一致性和重复请求的问题&#xff0c;因此也需要记录clientid和messageid。 shardctler保…

BFT 最前线 | 张一鸣成立个人基金;马斯克:AI是双刃剑;阿里首席安全科学家离职;卡内基梅隆究团队:解决农业虫卵问题的机器人

文 | BFT机器人 名人动态 CELEBRITY NEWS 01 字节跳动创始人张一鸣 在香港成立个人投资基金 在卸任CEO两年后&#xff0c;字节跳动创始人张一鸣在香港成立了一家个人投资基金。香港公司注册处网站显示&#xff0c;该基金名为Cool River Venture&#xff0c;性质是私人股份有限…

doris索引

目前 Doris 主要支持两类索引&#xff1a; - 内建的智能索引&#xff1a;包括前缀索引和 ZoneMap 索引。 - 用户创建的二级索引&#xff1a;包括 Bloom Filter 索引 和 Bitmap倒排索引。其中 ZoneMap 索引是在列存格式上&#xff0c;对每一列自动维护的索引信息&#xff0c;包…

Go 语言实战案例:猜谜游戏在线词典SOCKS5代理服务器 Go学习路线

字节跳动后端入门 - Go 语言原理与实践& vscode配置安装Go 3.1猜谜游戏 3.1.2 生成随机数v2 package mainimport ("fmt""math/rand""time" )func main() {maxNum : 100rand.Seed(time.Now().UnixNano())secretNumber : rand.Intn(maxNum)fmt…

OS之页面置换算法

目录 一、最佳置换算法(OPT) 定义 案例 二、先进先出置换算法(FIFO) 定义 案例 FIFO特有的异常 三、最近最久未使用置换算法(LRU) 定义 案例 四、时钟置换算法(CLOCK) 定义 案例 五、改进型的时钟置换算法 定义 案例 一、最佳置换算法(OPT) 定义 每次选择淘汰…

GoWeb -- gin框架的入门和使用(2)

前言 书接上回&#xff0c;在gin的框架使用中&#xff0c;还有着许多方法以及它们的作用&#xff0c;本篇博客将会接着上次的内容继续记录本人在学习gin框架时的思路和笔记。 如果还没有看过上篇博客的可以点此跳转。 map参数 请求url&#xff1a; http://localhost:8080/us…

全志V3S嵌入式驱动开发(驱动开发准备)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 之前的文章都是教大家怎么搭建环境、看原理图、编译内核和根文件系统、做镜像&#xff0c;直到现在才进入驱动开发的主题。毕竟整个专栏的目的&…

Python 基础(十四):类和对象

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 一、面向对象编程二、创建类三、创建实例3.1、访问属性3.2、调用方法 四、属性默认值4…

网络通信协议-ICMP协议

目录 一、ICMP协议 二、ICMP协议通信过程 &#xff08;1&#xff09;机制 &#xff08;2&#xff09;原理 &#xff08;3&#xff09;相关术语 丢包率 网络延时率&#xff08;延迟&#xff09; 请求超时【类似表白对方压根不搭理你】 没有任何回复数据&#xff0c;回复…

DNS/ICMP协议/NAT技术

本博文分享DNS&#xff08;简单认识&#xff09;、ICMP&#xff08;简单认识&#xff09;和NAT技术&#xff08;重点学习&#xff09;。 DNS DNS是一整套从域名映射到IP的系统&#xff0c;TCP/IP中使用IP地址和端口号来确定网络上的一台主机的一个程序。但是IP地址不方便记忆&…

windows下mysql中binlog日志分析和数据恢复

1.首先查看是否开启了binlog show variables like %log_bin%;看到了是没有开启的。 2.开启binlog日志&#xff0c;并重启mysql服务 不能通过命令的方式去打开&#xff0c;因为会提示说这个参数是只读的。如下图&#xff1a; 所以&#xff0c;打开mysql的配置文件&#xff…

Three.js--》实现3d地球模型展示

目录 项目搭建 实现网页简单布局 初始化three.js基础代码 创建环境背景 加载地球模型 实现光柱效果 添加月球模型 今天简单实现一个three.js的小Demo&#xff0c;加强自己对three知识的掌握与学习&#xff0c;只有在项目中才能灵活将所学知识运用起来&#xff0c;话不多…