仿牛客社区学习笔记-开发社区登录模块

1.发送邮件

邮箱设置

QQ邮箱在设置-账户中启动客户端SMTP服务

采用Spring Email技术

  1. 导入jar包:在mavenrepository.com中找到spring email复制maven代码到pom.xml中
  2. 在application.properties中配置邮箱参数(注意如果使用的是qq邮箱,设置密码要填写qq邮箱的授权码)
#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
  1. 使用JavaMainSender核心组件发送邮件

封装Mail工具类以便后续复用

  1. 新建一个包/util专门用来存放工具类
  2. 在包中新建一个类MapClient.java
@Component
public class MailClient {
    //声明一个logger来记录日志,以当前类命名
    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){
        //构建MineMessage(在JavaMailSender上点击Ctrl键)

        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message);
            //设置发件人
            helper.setFrom(from);
            //设置收件人
            helper.setTo(to);
            //设置主题
            helper.setSubject(subject);
            //设置内容
            helper.setText(content, true);
            mailSender.send(helper.getMimeMessage());
        } catch (MessagingException e) {
            logger.error("发送邮件失败" + e.getMessage());
        }
    }
}
  1. 新建测试类MailTests.java
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MailTests {

    @Autowired
    private MailClient mailClient;

    //主动调用thymeleaf模板引擎,有一个核心类被Spring容器管理
    @Autowired
    private TemplateEngine templateEngine;

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

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

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

        mailClient.sendMail("[email protected]","HTML", content);
    }
}
  1. 新建一个简单邮件版本demo.html
DOCTYPE html>
<html lang="en" xml: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.开发注册功能

1)访问注册页面

  1. Controller调用模板 在/controller新增LoginController.java 添加@Controller注解
  2. 在LoginController中添加getRegister方法,访问注册页面
//访问注册页面,返回模板路径
@RequestMapping(path = "/register", method = RequestMethod.GET)
public String getRegisterPage(){
    return "/site/register";
}
  1. 修改register.html添加使用thymeleaf模板声明xmlns:th="http://www.thymeleaf.org"
  2. 处理register.html中相对路径th:href="@{/css/login.css}"
  3. 处理功能:通过在首页点击注册按钮,跳转到注册页面,所以要修改index.html中header区域
<a class="nav-link" th:href="@{/index}">首页a>
<a class="nav-link" th:href="@{/register}">注册a>
  1. 为了复用头部的thymeleaf设置,在index.html中的header部分添加
<header class="bg-dark sticky-top" th:fragment="header">

在register.html中header部分添加

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

2)提交注册数据

  1. 在pom.xml中配置commons-lang包
<dependency>
       <groupId>org.apache.commonsgroupId>
       <artifactId>commons-lang3artifactId>
       <version>3.12.0version>
dependency>
  1. 在application.properties中配置域名
#community
community.path.domain=http://localhost:8080
  1. 在/util包下新建工具类CommunityUtil.java
public class CommunityUtil {

    //生成随机字符串,UUID是java里自带的工具
    //UUID的字符串通常由字母和横线构成,替换掉-
    public static String generateUUID(){
        return UUID.randomUUID().toString().replaceAll("-","");
    }

    //MD5加密
    //只能加密不能解密,不安全hello->abc123def456
    //在字母后面加上随机字符串(salt)hello +3e4a8->abc123def456abc
    //key:原始密码+随机字符串
    public static String md5(String key){
        //判断参数是否为空,使用工具StringUtils来自包apache.commons.lang3
        if(StringUtils.isBlank(key)){
            return null;
        }
        //调用工具Spring自带工具DigestUtils将返回的结果加密成一个16进制的字符串返回,要求参数传入的是byte,将key转换成byte
        return DigestUtils.md5DigestAsHex(key.getBytes());
    }
}
  1. 编辑UserService.java,因为注册功能是针对用户表的操作
  • 注入邮件客户端和模板引擎
//注入发送邮件的客户端
@Autowired
private MailClient mailClient;

//注入模板引擎
@Autowired
private TemplateEngine templateEngine;
  • 发送邮件要生成激活码,激活码中要包含域名和项目名
    域名:application.properties文件中
#community
community.path.domain=http://localhost:8080

项目名:application.properties文件中

#ServerProperties
server.servlet.context-path=/community
//UserSerivce.java
//注入配置文件中域名和项目名
//注入固定的值需要用到@Value
@Value("${community.path.domain}")
private String domain;

@Value("${server.servlet.context-path}")
private String contextPath;
  • 编写注册业务,实现功能:当账号为空,密码为空,邮箱为空,账号已存在,邮箱已经被注册等多种情况,显示提示信息
// 编写注册业务,写一个公有的方法返回值封装多个内容,内容包括账号为空等错误信息
// 将返回结果封装到map中
// 注册的时候要传入用户内容,包括账号密码和邮箱,所以传入的参数是user对象
public Map<String, Object> register(User user){
    //实例化map
    Map<String, Object> map = new HashMap<>();

    // 对参数空值判断处理
    if(user == null){
        throw new IllegalArgumentException("参数不能为空!");
    }
    // 使用StringUtils工具判断账号为空
    // 账号为空是业务上的漏洞,不是程序的错误,把信息封装到map中返回给客户端,而不是抛一场
    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为数据库中查到的对象,与传入的参数user相区分
    User u = userMapper.selectByName(user.getUsername());
    // u!=null 证明存在一个一样的对象
    if(u !=null){
        map.put("usernameMsg","该账号已存在!");
        return map;
    }

    // 验证邮箱是否已注册
    u = userMapper.selectByEmail(user.getEmail());
    if(u !=null){
        map.put("emailMsg","该邮箱已被注册!");
        return map;
    }

    // 注册用户
    // 对密码加密,生成密码随机salt
    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());
    // 使用format方法设置随机头像路径
    user.setHeaderUrl(String.format("https://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
    user.setCreateTime(new Date());
    userMapper.insertUser(user);
  • 修改模板/mail/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}">[email protected]b>, 您好!
		p>
		<p>
			您正在注册牛客网, 这是一封激活邮件, 请点击 
			<a th:href="${url}">此链接a>,
			激活您的牛客账号!
		p>
	div>
body>
html>
  • 继续修改UserService.java
// 激活邮件
// 创建context对象携带变量
Context context = new Context();
context.setVariable("email",user.getEmail());
// 要求访问路径为 http://localhost:8080/community/activation/101(用户id)/code
// 注册用户不包括Id,在实现insert操作时Mybatis实现Id回填
// 原因是在application.properties中使用useGeneratedKeys自动生成Id机制
String url = domain +contextPath +"/activation/" +user.getId() + "/" +user.getActivationCode();
context.setVariable("url",url);
// 利用模板引擎生成邮件内容
String content = templateEngine.process("/mail/activation",context);
// 调用mailClient发送邮件
mailClient.sendMail(user.getEmail(), "激活账号", content);

return map;
  1. 修改LoginController.java
// 处理注册请求:浏览器提交数据,方法用post
@RequestMapping(path = "/register", method = RequestMethod.POST)
// model参数存数据携带给模板,还需要传入账号密码邮箱存放在user对象中
public String register(Model model, User user){
    Map<String, Object> map = userService.register(user);
    // map为空表示注册成功
    if(map == null || map.isEmpty()){
        // 给中间页面提示消息,跳转到首页
        model.addAttribute("msg","注册成功,我们已经向您的邮箱发送了一封激活邮件,请尽快激活!");
        model.addAttribute("target","/index");
        return "/site/operate-result";
    } else {
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));
            model.addAttribute("emailMsg", map.get("emailMsg"));
            return "/site/register";
        }
}
  1. 修改/site/operate-result.html模板

<p class="lead" th:text="${msg}">您的账号已经激活成功,可以正常使用了!p>
    <hr class="my-4">
        <p>
		系统会在 <span id="seconds" class="text-danger">8span> 秒后自动跳转,
		
		您也可以点此 <a id="target" th:href="@{${target}}" class="text-primary">链接a>, 手动跳转!
p>

7.继续修改register.html


<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="@{/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>
					
					<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"
						   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>

3)激活注册账号

为了实现功能:改变用户的状态

  1. 在/util包下新增常量接口CommunityConstant.java
package com.nowcoder.community.util;

public interface CommunityConstant {

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

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

    /**
     * 激活失败
     */
    int ACTIVATION_FAILURE = 2;
}
  1. 修改UserService.java处理激活逻辑,实现CommunityContant接口
// 激活方法,返回激活状态
// 根据激活链接http://localhost:8080/community/activation/101(用户id)/code设置传入参数为id和code
public int activation(int userId, String code){
    User user = userMapper.selectById(userId);
    // 初始化status为0
    if(user.getStatus() == 1){
        return ACTIVATION_REPEAT;
    } 
    // 如果激活码和传入激活码是一样的
    else if (user.getActivationCode().equals(code)) {
        // 将用户status改为1表示激活成功
        userMapper.updateStatus(userId,1);
        return ACTIVATION_SUCCESS;
    } else {
        return ACTIVATION_FAILURE;
    }
}
  1. 在LoginController.java中处理请求实现CommunityConstant接口
// 处理激活请求,路径为http://localhost:8080/community/activation/101/code
@RequestMapping(path = "/action/{userId}/{code}", method = RequestMethod.GET)
// 用model传参
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";
}

增加访问登录页面的请求

@RequestMapping(path = "/login", method = RequestMethod.GET)
public String getLoginPage(){
    return "/site/login";
}
  1. 在login.html中处理样式文件

你可能感兴趣的:(仿牛客社区课程笔记,学习)