Java牛客项目课_仿牛客网讨论区_第二章

文章目录

    • 总体认识
    • 查看浏览器里cookie的两种办法
    • 第二章:Spring Boot实践,开发社区登录模块
      • 2.1、发送邮件
      • 2.7、开发注册功能
      • 2.11、会话管理(关于cookie和session)(只进行测试,未修改项目所需代码)
      • 2.17、 生成验证码
      • 2.23、开发登录、退出功能
      • 2.27 显示登录信息
      • 2.33、账号设置
      • 2.41、检查登录状态(只进行测试,未修改项目代码)

总体认识

Java牛客项目课_仿牛客网讨论区_第二章_第1张图片

查看浏览器里cookie的两种办法

  • 1、Network
  • 2、Application
    Java牛客项目课_仿牛客网讨论区_第二章_第2张图片

第二章:Spring Boot实践,开发社区登录模块

2.1、发送邮件

pom.xml

		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-mailartifactId>
			<version>2.1.5.RELEASEversion>
		dependency>

application.properties

# MailProperties
spring.mail.host=smtp.sina.com
spring.mail.port=465
[email protected]
spring.mail.password=刚才复制的授权码
spring.mail.protocol=smtps
spring.mail.properties.mail.smtp.ssl.enable=true

要注意的点:SpringBoot项目启动报错:javax.mail.AuthenticationFailedException: 535 5.7.8 authentication failed_夜中听雪的博客-CSDN博客

MailClient.java

package com.nowcoder.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
    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());
        }
    }
}

下方两个是用于测试的,不需要放到项目里。

MailTests.java

package com.nowcoder.community;

import com.nowcoder.community.util.MailClient;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

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

    @Autowired
    private MailClient mailClient;

    @Autowired
    private TemplateEngine templateEngine;

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

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

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

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

/mail/demo.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>

2.7、开发注册功能

前端代码复用:

index.html

		<header class="bg-dark sticky-top" th:fragment="header">
		。。。
		header>

register.html

		<header class="bg-dark sticky-top" th:replace="index::header">
		header>

pom.xml

		<dependency>
			<groupId>org.apache.commonsgroupId>
			<artifactId>commons-lang3artifactId>
			<version>3.9version>
		dependency>

application.properties

# community
community.path.domain=http://localhost:8080
community.path.upload=d:/work/data/upload

CommunityUtil.java

package com.nowcoder.community.util;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.DigestUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

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

0803的UserService.java

@Service
public class UserService implements CommunityConstant {

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

//    @Autowired
//    private LoginTicketMapper loginTicketMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    public User findUserById(int id) {
//        return userMapper.selectById(id);
        User user = getCache(id);
        if (user == null) {
            user = initCache(id);
        }
        return user;
    }

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

    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);
            clearCache(userId);
            return ACTIVATION_SUCCESS;
        } else {
            return ACTIVATION_FAILURE;
        }
    }

    // 1.优先从缓存中取值
    private User getCache(int userId) {
        String redisKey = RedisKeyUtil.getUserKey(userId);
        return (User) redisTemplate.opsForValue().get(redisKey);
    }

    // 2.取不到时初始化缓存数据
    private User initCache(int userId) {
        User user = userMapper.selectById(userId);
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
        return user;
    }

    // 3.数据变更时清除缓存数据
    private void clearCache(int userId) {
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.delete(redisKey);
    }
}

0803的LoginController.java
注释:
会自动把user放到model里。

@Controller
public class LoginController implements CommunityConstant {

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

    @Autowired
    private UserService userService;

    @Autowired
    private Producer kaptchaProducer;

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

    @Autowired
    private RedisTemplate redisTemplate;

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

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

    @RequestMapping(path = "/register", method = RequestMethod.POST)
    public String register(Model model, User user) {//会自动把user放到model里
        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";
        }
    }

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

register.html
注释:
1、"${usernameMsg}"首次访问是空的没有问题,因为没有下级调用,不会产生空指针异常的问题。

该账号已存在!

2、确认密码和密码是否一致由前端判断。确认密码和密码一致,才会提交给后台。
3、该账号已存在!
",用两个竖线表面一个固定值拼接一个变量。有class为is-invalid,才显示下方
标签。

<div class="main">
	<div class="container pl-5 pr-5 pt-3 pb-3 mt-3 mb-3">
		<h3 class="text-center text-info border-bottom pb-3">&nbsp;&nbsp;</h3>
		<form class="mt-5" method="post" th:action="@{/register}">
			<div class="form-group row">
				<label for="username" class="col-sm-2 col-form-label text-right">账号:</label>
				<div class="col-sm-10">
					<input type="text"
						   th:class="|form-control ${usernameMsg!=null?'is-invalid':''}|"
						   th:value="${user!=null?user.username:''}"
						   id="username" name="username" placeholder="请输入您的账号!" required>
					<!--"${usernameMsg}"首次访问是空的没有问题,因为没有下级调用,不会产生空指针异常的问题-->
					<div class="invalid-feedback" th:text="${usernameMsg}">
						该账号已存在!
					</div>
				</div>
			</div>
			<div class="form-group row mt-4">
				<label for="password" class="col-sm-2 col-form-label text-right">密码:</label>
				<div class="col-sm-10">
					<input type="password"
						   th:class="|form-control ${passwordMsg!=null?'is-invalid':''}|"
						   th:value="${user!=null?user.password:''}"
						   id="password" name="password" placeholder="请输入您的密码!" required>
					<div class="invalid-feedback" th:text="${passwordMsg}">
						密码长度不能小于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="${user!=null?user.password:''}"
						   id="confirm-password" placeholder="请再次输入密码!" required>
					<!--确认密码和密码是否一致由前端判断。确认密码和密码一致,才会提交给后台。-->
					<div class="invalid-feedback">
						两次输入的密码不一致!
					</div>
				</div>
			</div>
			<div class="form-group row">
				<label for="email" class="col-sm-2 col-form-label text-right">邮箱:</label>
				<div class="col-sm-10">
					<input type="email"
						   th:class="|form-control ${emailMsg!=null?'is-invalid':''}|"
						   th:value="${user!=null?user.email:''}"
						   id="email" name="email" placeholder="请输入您的邮箱!" required>
					<div class="invalid-feedback" th:text="${emailMsg}">
						该邮箱已注册!
					</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>				
	</div>
</div>

2.11、会话管理(关于cookie和session)(只进行测试,未修改项目所需代码)

  • cookie
    Java牛客项目课_仿牛客网讨论区_第二章_第3张图片
  • session
    Java牛客项目课_仿牛客网讨论区_第二章_第4张图片
    在这里插入图片描述
    JSESSIONID就是cookie(sessionid)。如果没有maxage的声明,那么浏览器关了,这个JSESSIONID就没有了。

演示cookie和session的使用。

@Controller
@RequestMapping("/alpha")
public class AlphaController {

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

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

Java牛客项目课_仿牛客网讨论区_第二章_第5张图片
分布式部署的问题:某浏览器与服务器一交互,在服务器一创建了一个session,并得到了sessionid,然后,该浏览器与服务器三交互,把sessionid传过去,却没得到值,只好在服务器三再创建一个session。
解决方案:

  • 1、粘性session。同一个ip,永远分到同一个服务器处理。
    • 缺点:难以保证负载均衡。
  • 2、同步session。某服务器创建一个session后,把session同步到其他的服务器。
    • 缺点:1、同步会对服务器性能产生影响。2、服务器与服务器间产生耦合,对部署有影响。
  • 3、共享session。专门搞一台服务器来存session,其他服务器都找这台服务器来获取session。
    • 缺点:1、这台专门存session的服务器挂了,其他的处理业务的服务器就都没办法工作了。2、分布式部署是为了解决性能瓶颈,现在又搞出一个单体的session服务器,那么这个单体的session服务器就成为了瓶颈。3、如果搞一个session服务器集群的话,那和同步session这个解决方案差不多,也要处理同步问题。

共享session方案的图:
Java牛客项目课_仿牛客网讨论区_第二章_第6张图片

  • 主流解决方案:用户数据不存到session里。能存cookie里就存cookie里,敏感数据不能存到cookie里,就存到数据库里。数据库可以做集群主从备份,数据库集群性能是ok的,同步数据没有问题,数据库做集群,方案是很成熟的。所有服务器都可以访问数据库的集群,来得到客户端的会话的数据。
    • 缺点:传统的关系型数据库一般都存在硬盘里,性能没有session那样保存在内存里好。并发量大时会成为瓶颈。
      Java牛客项目课_仿牛客网讨论区_第二章_第7张图片
  • 最终解决方案:能存cookie里就存cookie里,敏感数据不能存到cookie里,就存到Redis里。
    Java牛客项目课_仿牛客网讨论区_第二章_第8张图片

2.17、 生成验证码

pom.xml

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

KaptchaConfig.java

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

0803的最终版LoginController.java

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

        // 验证码的归属
        String kaptchaOwner = CommunityUtil.generateUUID();
        Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);
        cookie.setMaxAge(60);
        cookie.setPath(contextPath);
        response.addCookie(cookie);
        // 将验证码存入Redis
        String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
        redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);

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

login.html

<div class="col-sm-4">
	<img th:src="@{/kaptcha}" id="kaptcha" style="width:100px;height:40px;" class="mr-2"/>
	<a href="javascript:refresh_kaptcha();" class="font-size-12 align-bottom">刷新验证码a>
div>
<script>
	function refresh_kaptcha() {
		<!--参数p没有用,只是用于欺骗浏览器,请求改变了。防止浏览器因为两次请求都是访问/community/kaptcha然后就不去进行第二次访问-->
		var path = CONTEXT_PATH + "/kaptcha?p=" + Math.random();
		$("#kaptcha").attr("src", path);
	}
</script>

global.js

var CONTEXT_PATH = "/community";

2.23、开发登录、退出功能

可以有两个路径为/login的Controller的方法,只要他们的请求方式(get / post)不一样。

Controller的方法上有个规则:
@RequestMapping(path = "/login", method = RequestMethod.POST) public String login(String username, String password, String code, boolean rememberme, Model model, HttpSession session, HttpServletResponse response,)

  • 1、如果参数不是普通的参数,而是一个实体比如User,那么SpringMVC会把User对象自动装到Model的对象里,所以在页面上可以直接获得User对象的数据,从Model对象里获取的。
  • 2、如果是比较普通的参数,字符串、基本类型,那么Spring不会把参数放到Model里,model里没有。那么有两种办法在页面上得到它:
    • 1、人为的把它加到Model里。
    • 2、因为参数是从请求里携带过来的,String username, String password, String code, boolean rememberme这几个参数是存在于HttpServletRequest对象里的,所以Controller的方法里request.getParameter(username)也可以得到这几个参数。当程序执行到html页面上的时候,request还没有销毁,因为请求还没有结束,所以我们在页面上也可以从request中取值,th:value="${param.username},相当于Controller方法中的request.getParameter(username)

新建实体类LoginTicket.java。

UserService.java里补充方法public Map login(String username, String password, long expiredSeconds)public void logout(String ticket)

0803的最终版,部分LoginController.java

@Controller
public class LoginController implements CommunityConstant {

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

    @Autowired
    private UserService userService;

    @Autowired
    private Producer kaptchaProducer;

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

    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping(path = "/login", method = RequestMethod.GET)
    public String getLoginPage() {
        return "/site/login";
    }
    
    @RequestMapping(path = "/login", method = RequestMethod.POST)
    public String login(String username, String password, String code, boolean rememberme,
                        Model model, /*HttpSession session, */HttpServletResponse response,
                        @CookieValue("kaptchaOwner") String kaptchaOwner) {
        // 检查验证码
        // String kaptcha = (String) session.getAttribute("kaptcha");
        String kaptcha = null;
        if (StringUtils.isNotBlank(kaptchaOwner)) {
            String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
            kaptcha = (String) redisTemplate.opsForValue().get(redisKey);
        }

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

    @RequestMapping(path = "/logout", method = RequestMethod.GET)
    public String logout(@CookieValue("ticket") String ticket) {
        userService.logout(ticket);
        SecurityContextHolder.clearContext();
        return "redirect:/login";//重定向时,默认重定向到get请求。
    }
}

login.html
里面一个注释:标签的class里有is-invalid,那么就显示下方的错误提示信息。

<div class="main">
	<div class="container pl-5 pr-5 pt-3 pb-3 mt-3 mb-3">
		<h3 class="text-center text-info border-bottom pb-3">  h3>
		<form class="mt-5" method="post" th:action="@{/login}">
			<div class="form-group row">
				<label for="username" class="col-sm-2 col-form-label text-right">账号:label>
				<div class="col-sm-10">
					
					<input type="text" th:class="|form-control ${usernameMsg!=null?'is-invalid':''}|"
						   th:value="${param.username}"
						   id="username" name="username" placeholder="请输入您的账号!" required>
					<div class="invalid-feedback" th:text="${usernameMsg}">
						该账号不存在!
					div>
				div>
			div>
			<div class="form-group row mt-4">
				<label for="password" class="col-sm-2 col-form-label text-right">密码:label>
				<div class="col-sm-10">
					<input type="password" th:class="|form-control ${passwordMsg!=null?'is-invalid':''}|"
						   th:value="${param.password}"
						   id="password" name="password" placeholder="请输入您的密码!" required>
					<div class="invalid-feedback" th:text="${passwordMsg}">
						密码长度不能小于8位!
					div>							
				div>
			div>
			<div class="form-group row mt-4">
				<label for="verifycode" class="col-sm-2 col-form-label text-right">验证码:label>
				<div class="col-sm-6">
					<input type="text" th:class="|form-control ${codeMsg!=null?'is-invalid':''}|"
						   id="verifycode" name="code" placeholder="请输入验证码!">
					<div class="invalid-feedback" th:text="${codeMsg}">
						验证码不正确!
					div>
				div>
				<div class="col-sm-4">
					<img th:src="@{/kaptcha}" id="kaptcha" style="width:100px;height:40px;" class="mr-2"/>
					<a href="javascript:refresh_kaptcha();" class="font-size-12 align-bottom">刷新验证码a>
				div>
			div>				
			<div class="form-group row mt-4">
				<div class="col-sm-2">div>
				<div class="col-sm-10">
					<input type="checkbox" id="remember-me" name="rememberme"
						   th:checked="${param.rememberme}">
					<label class="form-check-label" for="remember-me">记住我label>
					<a href="forget.html" class="text-danger float-right">忘记密码?a>
				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>				
	div>
div>

index.html

<a class="dropdown-item text-center" th:href="@{/logout}">退出登录a>

2.27 显示登录信息

AlphaInterceptor:测试拦截器。
注释:

/*
    Object handler是拦截的目标,比如我们访问方法:

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

    打印的handler.toString()为:public java.lang.String com.nowcoder.community.controller.LoginController.getLoginPage()
*/
@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());
    }
}

在这里插入图片描述
WebMvcConfig

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private AlphaInterceptor alphaInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(alphaInterceptor)
                //访问静态资源:localhost:8080/community/css/xxx.css这样,拦截时"/*/*.css"或者"/css/*.css"
                .excludePathPatterns("/*/*.css", "/*/*.js", "/*/*.png", "/*/*.jpg", "/*/*.jpeg")
                //访问controller:localhost:8080/community/register
                .addPathPatterns("/register", "/login");//也可以用通配符,如"/user/*"
}

Java牛客项目课_仿牛客网讨论区_第二章_第9张图片
LoginTicketInterceptor :

package com.nowcoder.community.controller.interceptor;

import com.nowcoder.community.entity.LoginTicket;
import com.nowcoder.community.entity.User;
import com.nowcoder.community.service.UserService;
import com.nowcoder.community.util.CookieUtil;
import com.nowcoder.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;

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

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

CookieUtil.java:

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

HostHolder.java:

package com.nowcoder.community.util;

import com.nowcoder.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();
    }
}

2.33、账号设置

• 开发步骤

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

2.41、检查登录状态(只进行测试,未修改项目代码)

• 使用拦截器
• 自定义注解

测试

LoginRequired:自定义注解,注解在方法上,被注解的方法在访问时需要登录,如果没有登录,则会重定向到登录页面。

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 {

}

WebMvcConfigurer

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private LoginRequiredInterceptor loginRequiredInterceptor;

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

UserController类的getSettingPage()方法上加上自定义注解进行测试。

    @LoginRequired
    @RequestMapping(path = "/setting", method = RequestMethod.GET)
    public String getSettingPage(Model model) {
        // 上传文件名称
        String fileName = CommunityUtil.generateUUID();
        // 设置响应信息
        StringMap policy = new StringMap();
        policy.put("returnBody", CommunityUtil.getJSONString(0));
        // 生成上传凭证
        Auth auth = Auth.create(accessKey, secretKey);
        String uploadToken = auth.uploadToken(headerBucketName, fileName, 3600, policy);

        model.addAttribute("uploadToken", uploadToken);
        model.addAttribute("fileName", fileName);

        return "/site/setting";
    }

你可能感兴趣的:(项目:Java仿牛客网讨论区,spring,boot,spring,session,cookie)