NowCoder.github.io —— 《仿牛客论坛社区项目实战》——SpringBoot实践 开发社区登录模块 —— SpringBoot+SpringMVC+MyBatis+Thymeleaf

📖 NowCoder.github.io

NowCoder 仿牛客论坛项目

📑 SpringBoot 实践 开发社区登录模块

🔖 发送邮件

在这里插入图片描述

步骤一:设置 邮件的 转发和 POP / IMAP 启用

在这里插入图片描述

步骤二:导入邮件服务发送的相关依赖

在这里插入图片描述

<!-- 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>

步骤三:配置Mail所需的配置到 application.yaml

这里用qq邮箱进行测试哦

spring:
  mail:
    host: smtp.qq.com
    port: 465
    username: xxxxxxxxx@qq.com
    password: xxxxxxxxx
    protocol: smtps
    properties:
      mail.smtp.ssl.enable=true

步骤四:编写 MailClient类作为组件进行发送邮件

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/21 23:05
 * @description: MailClient 代发邮件 客户端
 * @modified By: Alascanfu
 **/
@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 context){
        try {
            MimeMessage mimeMessage = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage);
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(context,true);
            mailSender.send(helper.getMimeMessage());
        } catch (MessagingException e) {
            e.printStackTrace();
            logger.error("MailClient 发送邮件失败 , 异常信息为 =>{" + e.getMessage() + "}");
        }
    }
}

步骤五:编写测试类MailTestApplication.java

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/21 23:22
 * @description:
 * @modified By: Alascanfu
 **/
@SpringBootTest
public class MailTestApplication {
    @Autowired
    public MailClient mailClient;
    @Test
    public void testMail(){
        mailClient.sendMail("3201256097@qq.com","TEST","Welcome.");
    }
}

步骤六:通过单元测试

在这里插入图片描述

邮件发送成功 ~

在这里插入图片描述

步骤七:通过发送 Thymeleaf 邮件模板进行邮件发送

/mail/codeHTMLTemplate.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <includetail>
        <div align="center">
            <div class="open_email" style="margin-left: 8px; margin-top: 8px; margin-bottom: 8px; margin-right: 8px;">
                <div>
                    <br>
                    <span class="genEmailContent">
                        <div id="cTMail-Wrap"
                             style="word-break: break-all;box-sizing:border-box;text-align:center;min-width:320px; max-width:660px; border:1px solid #f6f6f6; background-color:#f7f8fa; margin:auto; padding:20px 0 30px; font-family:'helvetica neue',PingFangSC-Light,arial,'hiragino sans gb','microsoft yahei ui','microsoft yahei',simsun,sans-serif">
                            <div class="main-content" style="">
                                <table style="width:100%;font-weight:300;margin-bottom:10px;border-collapse:collapse">
                                    <tbody>
                                    <tr style="font-weight:300">
                                        <td style="width:3%;max-width:30px;"></td>
                                        <td style="max-width:600px;">
                                            <div id="cTMail-logo" style="width:92px; height:25px;">
                                                <a href="">
                                                    <img border="0" src="https://imgcache.qq.com/open_proj/proj_qcloud_v2/mc_2014/cdn/css/img/mail/logo-pc.png"
                                                         style="width:92px; height:25px;display:block">
                                                </a>
                                            </div>
                                            <p style="height:2px;background-color: #00a4ff;border: 0;font-size:0;padding:0;width:100%;margin-top:20px;"></p>

                                            <div id="cTMail-inner" style="background-color:#fff; padding:23px 0 20px;box-shadow: 0px 1px 1px 0px rgba(122, 55, 55, 0.2);text-align:left;">
                                                <table style="width:100%;font-weight:300;margin-bottom:10px;border-collapse:collapse;text-align:left;">
                                                    <tbody>
                                                    <tr style="font-weight:300">
                                                        <td style="width:3.2%;max-width:30px;"></td>
                                                        <td style="max-width:480px;text-align:left;">
                                                            <h1 id="cTMail-title" style="font-size: 20px; line-height: 36px; margin: 0px 0px 22px;">
                                                                【XX平台】欢迎注册XXX-互动问答平台
                                                            </h1>

                                                            <p id="cTMail-userName" style="font-size:14px;color:#333; line-height:24px; margin:0;">
                                                                尊敬的 <span style="color: red" th:utext="${username}"></span> 用户,您好!
                                                            </p>

                                                            <p class="cTMail-content" style="line-height: 24px; margin: 6px 0px 0px; overflow-wrap: break-word; word-break: break-all;">
                                                                <span style="color: rgb(51, 51, 51); font-size: 14px;">
                                                                    欢迎注册 ⭐ XXX-互动问答平台 ⭐。
                                                                </span>
                                                            </p>

                                                            <p class="cTMail-content" style="line-height: 24px; margin: 6px 0px 0px; overflow-wrap: break-word; word-break: break-all;">
                                                                <span style="color: rgb(51, 51, 51); font-size: 14px;">请将下述 激活码 进行提交验证邮箱,以此来完成注册。
                                                                    <span style="font-weight: bold;">非本人操作可忽略。</span>
                                                                </span>
                                                            </p>

                                                            <p class="cTMail-content"
                                                               style="font-size: 14px; color: rgb(51, 51, 51); line-height: 24px; margin: 6px 0px 0px; word-wrap: break-word; word-break: break-all;">
                                                                <a id="cTMail-btn" href="" title=""
                                                                   style="font-size: 16px; line-height: 45px; display: block; background-color: rgb(0, 164, 255); color: rgb(255, 255, 255); text-align: center; text-decoration: none; margin-top: 20px; border-radius: 3px;">
                                                                    <span th:utext="${code}"></span>
                                                                </a>
                                                            </p>

                                                            <p class="cTMail-content" style="line-height: 24px; margin: 6px 0px 0px; overflow-wrap: break-word; word-break: break-all;">
                                                                <span style="color: rgb(51, 51, 51); font-size: 14px;">
                                                                    <br>
                                                                    无法正常显示?是否是您的网络延迟所导致的~
                                                                    <br>
                                                                    <a href="" title=""
                                                                       style="color: rgb(0, 164, 255); text-decoration: none; word-break: break-all; overflow-wrap: normal; font-size: 14px;">
                                                                        点击这里进行刷新
                                                                    </a>
                                                                </span>
                                                            </p>
                                                            <dl style="font-size: 14px; color: rgb(51, 51, 51); line-height: 18px;">
                                                                <dd style="margin: 0px 0px 6px; padding: 0px; font-size: 12px; line-height: 22px;">
                                                                    <p id="cTMail-sender" style="font-size: 14px; line-height: 26px; word-wrap: break-word; word-break: break-all; margin-top: 32px;">
                                                                        此致
                                                                        <br>
                                                                        <strong>乌尼泰美 团队</strong>
                                                                    </p>
                                                                </dd>
                                                            </dl>
                                                        </td>
                                                        <td style="width:3.2%;max-width:30px;"></td>
                                                    </tr>
                                                    </tbody>
                                                </table>
                                            </div>
                                            <div id="cTMail-copy" style="text-align:center; font-size:12px; line-height:18px; color:#999">
                                                <table style="width:100%;font-weight:300;margin-bottom:10px;border-collapse:collapse">
                                                    <tbody>
                                                    <tr style="font-weight:300">
                                                        <td style="width:3.2%;max-width:30px;"></td>
                                                        <td style="max-width:540px;">

                                                            <p style="text-align:center; margin:20px auto 14px auto;font-size:12px;color:#999;">
                                                                此为系统邮件,请勿回复。
                                                            </p>

                                                            <p id="cTMail-rights" style="max-width: 100%; margin:auto;font-size:12px;color:#999;text-align:center;line-height:22px;">
                                                                <img border="0" src="http://imgcache.qq.com/open_proj/proj_qcloud_v2/tools/edm/css/img/wechat-qrcode-2x.jpg"
                                                                     style="width:64px; height:64px; margin:0 auto;">
                                                                <br>
                                                                关注服务号,移动管理云资源
                                                                <br>
                                                                <img src="https://imgcache.qq.com/open_proj/proj_qcloud_v2/gateway/mail/cr.svg" style="margin-top: 10px;">
                                                            </p>
                                                        </td>
                                                        <td style="width:3.2%;max-width:30px;"></td>
                                                    </tr>
                                                    </tbody>
                                                </table>
                                            </div>
                                        </td>
                                        <td style="width:3%;max-width:30px;"></td>
                                    </tr>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </span>
                </div>
            </div>
        </div>
    </includetail>
</div>
</body>
</html>

MailTestApplication.java

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/21 23:22
 * @description:
 * @modified By: Alascanfu
 **/
@SpringBootTest
public class MailTestApplication {
    @Autowired
    public MailClient mailClient;
    
    @Autowired
    private TemplateEngine templateEngine;
    
    @Test
    public void testHTMLMail(){
        Context context = new Context();
        context.setVariable("username","Alascanfu");
        context.setVariable("code","NAVL-RK72-URYA-CINR");
        String content = templateEngine.process("/mail/codeHTMLTemplate", context);
        mailClient.sendMail("3201256097@qq.com","HTML",content);
    }
}

结果测试

在这里插入图片描述

🔖 注册功能的开发

在这里插入图片描述

步骤一:编写LoginController

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 0:31
 * @description: LoginController 用于注册的Controller层 类
 * @modified By: Alascanfu
 **/
@Controller
public class LoginController {
    
    @RequestMapping(path = "/register" , method = RequestMethod.GET)
    public String getRegisterPage(){
        return "/site/register";
    }
}

步骤二:改写 register 页面 与 index 页面头部 链接

index.html

<!-- 头部 -->
<header class="bg-dark sticky-top" th:fragment="header">
    <div class="container">
        <!-- 导航 -->
        <nav class="navbar navbar-expand-lg navbar-dark">
            <!-- logo -->
            <a class="navbar-brand" href="#"></a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <!-- 功能 -->
            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav mr-auto">
                    <li class="nav-item ml-3 btn-group-vertical">
                        <a class="nav-link" th:href="@{/index}">首页</a>
                    </li>
                    <li class="nav-item ml-3 btn-group-vertical">
                        <a class="nav-link position-relative" href="site/letter.html">消息<span class="badge badge-danger">12</span></a>
                    </li>
                    <li class="nav-item ml-3 btn-group-vertical">
                        <a class="nav-link" th:href="@{/register}">注册</a>
                    </li>
                    <li class="nav-item ml-3 btn-group-vertical">
                        <a class="nav-link" href="site/login.html">登录</a>
                    </li>
                    <li class="nav-item ml-3 btn-group-vertical dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                            <img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
                        </a>
                        <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                            <a class="dropdown-item text-center" href="site/profile.html">个人主页</a>
                            <a class="dropdown-item text-center" href="site/setting.html">账号设置</a>
                            <a class="dropdown-item text-center" href="site/login.html">退出登录</a>
                            <div class="dropdown-divider"></div>
                            <span class="dropdown-item text-center text-secondary">nowcoder</span>
                        </div>
                    </li>
                </ul>
                <!-- 搜索 -->
                <form class="form-inline my-2 my-lg-0" action="site/search.html">
                    <input class="form-control mr-sm-2" type="search" aria-label="Search" />
                    <button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
                </form>
            </div>
        </nav>
    </div>
</header>

register.html

这里先不展示后面还需要进行大改

步骤三:导入依赖与简单配置

<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-lang3</artifactId>
	<version>3.12.0</version>
</dependency>

application.properties

community.path.domain=http://localhost:8080

步骤四:创建简单的社区论坛工具类——实现生成随机激活码以及采用md5密码加密

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 0:50
 * @description: CommunityUtil 社区论坛的工具类
 * @modified By: Alascanfu
 **/
public class CommunityUtil {
    /** 生成随机的激活码*/
    public static String getActivationCode(){
        return UUID.randomUUID().toString().replaceAll("-","").substring(0,4)
            + "-" + UUID.randomUUID().toString().replaceAll("-","").substring(4,8)
            + "-" + UUID.randomUUID().toString().replaceAll("-","").substring(0,4)
            + "-" + UUID.randomUUID().toString().replaceAll("-","").substring(4,8);
    }
    
    /** MD5算法加密 */
    public static String md5Encrypt(String key){
        if (StringUtils.isBlank(key)){
            return null ;
        }
        return DigestUtils.md5DigestAsHex(key.getBytes());
    }
    
    /** 生成随机盐字符串 */
    public static String getSalt(){
        return UUID.randomUUID().toString().replaceAll("-","").substring(0,5);
    }
}

步骤四:编写 UserService 层中注册实际逻辑

  • 首先对所填信息的判空处理
  • 其次针对于 信息是否已经注册过 进行处理
  • 注册完成之后 给用户发送邮件 进行激活
/***
 * @author: Alascanfu
 * @date : Created in 2022/6/21 9:57
 * @description: UserService 的具体业务实现类
 * @modified By: Alascanfu
 **/
@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<>();
        /** 判空处理 */
        if (user == null){
            throw new IllegalArgumentException("参数不能为空!");
        }
        if (StringUtils.isBlank(user.getUsername())){
            map.put("usernameMsg", "账号不能为空!");
        }
        if (StringUtils.isBlank(user.getPassword())){
            map.put("passwordMsg", "密码不能为空!");
        }
        if (StringUtils.isBlank(user.getEmail())){
            map.put("emailMsg","邮箱不能为空!");
        }
        /** 匹配邮箱格式的正则 */
        if (!Pattern.matches("^(\\w+([-.][A-Za-z0-9]+)*){3,18}@\\w+([-.][A-Za-z0-9]+)*\\.\\w+([-.][A-Za-z0-9]+)*$",user.getEmail())){
            map.put("emailMsg","请输入合法的邮箱地址!");
        }
        
        /** 验证账号 */
        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.getSalt());
        user.setPassword(CommunityUtil.md5Encrypt(user.getSalt() + user.getPassword()));
        user.setType(0);
        user.setStatus(0);
        user.setActivationCode(CommunityUtil.getActivationCode());
        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("username",user.getUsername());
        context.setVariable("activationCode",user.getActivationCode());
        // http://localhost:8080/community/activation/userId/activationCode
        String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();
        context.setVariable("url",url);
        String content = templateEngine.process("/mail/codeHTMLTemplate", context);
        String subject = "Dear " + user.getUsername() + ","+ user.getActivationCode() + ",是您的激活码,请妥善保管!" ;
        mailClient.sendMail(user.getEmail(),subject,content);
        return map ;
    }
    
}

步骤五:改写 LoginController

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 0:31
 * @description: LoginController 用于注册的Controller层 类
 * @modified By: Alascanfu
 **/
@Controller
public class LoginController {
    @Autowired
    private UserService userService;
    
    @RequestMapping(path = "/register" , method = RequestMethod.GET)
    public String getRegisterPage(){
        return "/site/register";
    }
    
    @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";
        }
    }
}

**步骤六:改写 /site/operate-result.html 中间提示激活页面 改写 /site/register.html 逻辑 **

operate-result.html

<!-- 内容 -->
<div class="main">
    <div class="container mt-5">
        <div class="jumbotron">
            <p class="lead" th:utext="${msg}"></p>
            <hr class="my-4">
            <p>
                系统会在 <span id="seconds" class="text-danger">8</span> 秒后自动跳转,
                您也可以点此 <a id="target" th:href="@{${target}}" class="text-primary">链接</a>, 手动跳转!
            </p>
        </div>
    </div>
</div>

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">&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>
                    <div class="invalid-feedback" th:utext="${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:utext="${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:utext="${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>

步骤七:进行启动测试

http://localhost:8080/community/register

测试注册的特判

在这里插入图片描述

在这里插入图片描述

当输入数据均合法时——完成跳转

在这里插入图片描述

检查数据库中是否插入数据成功

在这里插入图片描述

检查注册用户的邮箱是否收到了来自验证激活邮件

在这里插入图片描述

步骤八:编写用于验证激活码对应的业务逻辑

  • 首先我们需要创建好一个 全局可复用的 社区论坛实体常量接口 CommunityConstant
/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 2:37
 * @description: Community 论坛社区的常量接口
 * @modified By: Alascanfu
 **/
public interface CommunityConstant {
    /** 激活成功 */
    int ACTIVATION_SUCCESS = 0 ;
    
    /** 重复激活 */
    int ACTIVATION_REPEAT = 1 ;
    
    /** 激活失败 */
    int ACTIVATION_FAILURE = 2 ;
}
  • 其次我们需要编写 UserService 中的验证激活的逻辑
    • 分为三类——激活成功,重复激活,激活失败三种情况
/***
 * @author: Alascanfu
 * @date : Created in 2022/6/21 9:57
 * @description: UserService 的具体业务实现类
 * @modified By: Alascanfu
 **/
@Service
public class UserService implements CommunityConstant {

    // ...
    
    public int activation(String activationCode , int userId){
        User user = userMapper.selectById(userId);
        if (user.getStatus() == 1 ){
            return ACTIVATION_REPEAT;
        }else if (user.getActivationCode().equals(activationCode)){
            userMapper.updateStatus(userId,1);
            return ACTIVATION_SUCCESS;
        }else {
            return ACTIVATION_FAILURE;
        }
    }
}
  • 编写 UserController 业务逻辑层
/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 0:31
 * @description: LoginController 用于注册的Controller层 类
 * @modified By: Alascanfu
 **/
@Controller
public class LoginController implements CommunityConstant {

    // ...
    
    @RequestMapping(path = "/login" , method = RequestMethod.GET)
    public String getLoginPage(){
        return "/site/login";
    }
    // http://localhost:8080/community/activation/userId/activationCode
    @RequestMapping(path = "/activation/{userId}/{activationCode}" , method = RequestMethod.GET)
    public String activation(Model model , @PathVariable("userId") int userId , @PathVariable("activationCode") String activationCode){
        int result = userService.activation(activationCode, userId);
        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";
    }
}
  • 改写登录页为Thymeleaf渲染
<!-- 内容 -->
<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">				
            <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" class="form-control is-invalid" id="username" placeholder="请输入您的账号!" required>
                    <div class="invalid-feedback">
                        该账号不存在!
                    </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" class="form-control is-invalid" id="password" placeholder="请输入您的密码!" required>
                    <div class="invalid-feedback">
                        密码长度不能小于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" class="form-control is-invalid" id="verifycode" placeholder="请输入验证码!">
                    <div class="invalid-feedback">
                        验证码不正确!
                    </div>
                </div>
                <div class="col-sm-4">
                    <img th:src="@{/img/captcha.png}" style="width:100px;height:40px;" class="mr-2"/>
                    <a href="javascript:;" 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" checked="checked">
                    <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>

步骤九:启动并测试激活邮箱

  • 重新部署项目

  • 点击之前发送给邮箱的邮件激活码即可实现自动跳转进行激活

在这里插入图片描述

  • 此时刷新数据库则可以观察到刚才注册好的账号已经激活完成了~

在这里插入图片描述

📚 大功告成了~

🔖 会话管理

Cookie与Session 入门

在这里插入图片描述

🔖 生成验证码

Google Code Archive - Long-term storage for Google Code Project Hosting.

在这里插入图片描述

步骤一:导入相关的依赖进入到对应的项目当中

<!-- https://mvnrepository.com/artifact/com.github.penggle/kaptcha -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

步骤二:因为这里的Kaptcha并没有被SpringBoot进行自动装配 故我们需要手动进行相关的配置 将所需Bean实例注入到我们的IOC Container

KaptchaConfig.java

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 14:39
 * @description: KaptchaConfig 类 用于作为验证码相关的配置类
 * @modified By: Alascanfu
 **/
@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","0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        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 编写一个获取验证码图片接口的方法

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 0:31
 * @description: LoginController 用于注册的Controller层 类
 * @modified By: Alascanfu
 **/
@Controller
public class LoginController implements CommunityConstant {
    
    // ... 
    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*/
        BufferedImage image = kaptchaProducer.createImage(text);
        
        /** 将验证码存入 session*/
        session.setAttribute("kaptcha",text);
        
        /** 将图片输出给浏览器前端 */
        response.setContentType("image/png");
        try {
            /** 获取输出流 */
            OutputStream outputStream = response.getOutputStream();
            /** 通过 ImageIO 将图片流数据以png的格式输出到前端 */
            ImageIO.write(image,"png",outputStream);
        } catch (IOException e) {
            logger.error("响应验证码失败=>{" + e.getMessage() + "}");
        }
    }
}

步骤四:启动服务器进行测试生成验证码图片

http://localhost:8080/community/kaptcha

在这里插入图片描述

步骤五:通过修改 login.html 进行页面刷新 验证码 —— 动态获取所以需要编写一个简单的 JS

login.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">&nbsp;&nbsp;</h3>
        <form class="mt-5">				
            <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" class="form-control is-invalid" id="username" placeholder="请输入您的账号!" required>
                    <div class="invalid-feedback">
                        该账号不存在!
                    </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" class="form-control is-invalid" id="password" placeholder="请输入您的密码!" required>
                    <div class="invalid-feedback">
                        密码长度不能小于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" class="form-control is-invalid" id="verifycode" placeholder="请输入验证码!">
                    <div class="invalid-feedback">
                        验证码不正确!
                    </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" checked="checked">
                    <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>
<script>
    function refresh_kaptcha(){
        var path = CONTEXT_PATH + "/kaptcha?p=" +Math.random() ;
        $("#kaptcha").attr("src",path);
    }
</script>

结果演示

在这里插入图片描述

🔖 开发登录、退出功能

在这里插入图片描述

步骤一:编写 LoginTicket 实体类

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 15:38
 * @description: LoginTicket 类 用于登录凭证数据的实体类
 * @modified By: Alascanfu
 **/
public class LoginTicket {
    private Integer id ;
    
    private Integer userId ;
    
    private String ticket ;
    
    private Integer status ;
    
    private Date expired ;
    
    public Integer getId() {
        return id;
    }
    
    public void setId(Integer id) {
        this.id = id;
    }
    
    public Integer getUserId() {
        return userId;
    }
    
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
    
    public String getTicket() {
        return ticket;
    }
    
    public void setTicket(String ticket) {
        this.ticket = ticket;
    }
    
    public Integer getStatus() {
        return status;
    }
    
    public void setStatus(Integer status) {
        this.status = status;
    }
    
    public Date getExpired() {
        return expired;
    }
    
    public void setExpired(Date expired) {
        this.expired = expired;
    }
    
    @Override
    public String toString() {
        return "LoginTicket{" +
            "id=" + id +
            ", userId=" + userId +
            ", ticket='" + ticket + '\'' +
            ", status=" + status +
            ", expired=" + expired +
            '}';
    }
}

步骤二:编写 LoginTicketMapper 与 LoginTickerMapper.xml与数据库进行简单的业务交互

com.alascanfu.dao.LoginTicketMapper

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 15:44
 * @description: LoginTicketMapper 类 与数据库相连处理 用户凭证数据处理
 * @modified By: Alascanfu
 **/
@Mapper
public interface LoginTicketMapper {
    /** 插入用户登录凭证 */
    int insertLoginTicket(LoginTicket loginTicket);
    
    LoginTicket selectByTicket(String ticket);
    
    int updateTicketStatus(String ticket , int status );
}

LoginTicketMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.alascanfu.dao.LoginTicketMapper">
    <sql id="insertFields">
        user_id ,ticket ,status ,expired
    </sql>

    <sql id="selectFields">
        id, user_id ,ticket ,status ,expired
    </sql>

    <insert id="insertLoginTicket" parameterType="com.alascanfu.entity.LoginTicket" keyProperty="id" useGeneratedKeys="true">
        insert into login_ticket(<include refid="insertFields"></include>)
        values (#{userId},#{ticket},#{status},#{expired})
    </insert>

    <select id="selectByTicket" resultType="com.alascanfu.entity.LoginTicket" parameterType="java.lang.String" >
        select <include refid="selectFields"></include>
        from login_ticket
        where ticket = #{ticket}
    </select>

    <update id="updateTicketStatus" >
        update login_ticket set status=#{status} where ticket=#{ticket}
    </update>
</mapper>

步骤四:测试类进行测试数据

TestForLoginTicket

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 16:08
 * @description: 用于测试 LoginTicket 相关业务数据逻辑
 * @modified By: Alascanfu
 **/
@SpringBootTest
public class TestForLoginTicket {
    @Autowired
    private LoginTicketMapper loginTicketMapper ;
    @Test
    public void test(){
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(101);
        loginTicket.setTicket(CommunityUtil.getRandomString());
        loginTicket.setStatus(0);
        loginTicket.setExpired(new Date(System.currentTimeMillis() + 1000 * 10 * 6));
        
        loginTicketMapper.insertLoginTicket(loginTicket);
    }
    
    @Test
    public void test1(){
        LoginTicket loginTicket = loginTicketMapper.selectByTicket("4cd408827ffb4c20");
        System.out.println(loginTicket);
    
        int i = loginTicketMapper.updateTicketStatus("4cd408827ffb4c20", 1);
        System.out.println(i);
    }
}

步骤五:编写 UserService 中的用户登录业务逻辑

com.alascanfu.service.UserService

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/21 9:57
 * @description: UserService 的具体业务实现类
 * @modified By: Alascanfu
 **/
@Service
public class UserService implements CommunityConstant {
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private MailClient mailClient;
    
    @Autowired
    private TemplateEngine templateEngine ;
    
    @Autowired
    private LoginTicketMapper loginTicketMapper ;
    
    // ...
    
    /** 账号登录失败的情况有几种 => 1 账号没有激活 2=> 账号密码错误 等...*/
    /** 注意这里的密码是用户传入的明文密码 所以必须通过对当前用户的 salt
     * 字段进行对应的字符串拼接然后通过 md5加密生成对应的加密密文 与 数据库中的加密密文密码进行匹配
     * 如果成功 则登录成功
     * 反之登录失败
     * */
    @RequestMapping(path = "/login" , method = RequestMethod.POST)
    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 ;
        }
        
        /** 先对账号进行验证 => 先对其 账号进行查询查询数据库当中是否有对应的
         * 用户账号 : => 1 如果没有则直接返回用户账号不存在的 错误提示 信息
         * :=> 2 用户账号存在的情况下又要进行判断其是否对应了其账号密码是否匹配
         * */
        User u = userMapper.selectByName(username);
        if (u == null ){
            map.put("usernameMsg" , "该账号不存在!");
            return map ;
        }
        /** 验证状态 */
        if (u.getStatus() == 0 ){
            map.put("usernameMsg" , "该账号暂未激活!");
            return map ;
        }
        
        /** 验证密码 */
        password = CommunityUtil.md5Encrypt(u.getSalt() + password);
        if (!u.getPassword().equals(password)){
            map.put("passwordMsg" , "密码输入错误,请重试!");
            return map ;
        }
        
        /** 生成对应的登录凭证 */
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(u.getId());
        loginTicket.setTicket(CommunityUtil.getRandomString());
        loginTicket.setStatus(0);
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000 ));
        loginTicketMapper.insertLoginTicket(loginTicket);
        
        map.put("ticket",loginTicket.getTicket());
        
        return map ;
    }
}

步骤六:编写 LoginController 层 login 接口

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 0:31
 * @description: LoginController 用于注册的Controller层 类
 * @modified By: Alascanfu
 **/
@Controller
public class LoginController implements CommunityConstant {
    private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
    
    @Autowired
    private UserService userService;
    
    // ... 
    
    @Value("${server.servlet.context-path}")
    private String contextPath ;
    
    @RequestMapping(path = "/login" ,method = RequestMethod.POST)
    public String login(Model model , String username , String password ,
                        String code , boolean rememberMe,
        HttpServletResponse response, HttpSession session){
        /** 从session中取出 kaptcha */
        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 ? REMEBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS ;
        Map<String, Object> map = userService.login(username, password, expiredSeconds);
        /** 只有当登录成功之后才会将 ticket 置于map 当中 重定向回首页 */
        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"));
            model.addAttribute("codeMsg",map.get("codeMsg"));
            return "/site/login";
        }
    }
}

步骤七:改写前端 Login 页面渲染返回数据显示

<!-- 内容 -->
<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="@{/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-incalid':''}|"
                           th:value="${param.username}"
                           id="username" name="username" placeholder="请输入您的账号!" required>
                    <div class="invalid-feedback"  th:utext="${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:utext="${passwordMsg}">
                    </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:utext="${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>

步骤八:打开页面进行对应的页面功能测试

  • 如:第一次输入完全错误的信息、包括错误的验证码以及其他用户数据

在这里插入图片描述

在这里插入图片描述

  • 验证码输入正确之后进行 判断非法用户名判断

在这里插入图片描述

  • 当登录成功之后 会自动跳转到 论坛社区首页

在这里插入图片描述

步骤九:退出功能的编写

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

UserService.java

/** 退出并且清除登录凭证 */
public void logout(String ticket){
    loginTicketMapper.updateTicketStatus(ticket,1);
}

LoginController.java

@RequestMapping(path = "/logout" , method = RequestMethod.GET)
public String logout(@CookieValue("ticket") String ticket){
    userService.logout(ticket);
    return "redirect:/login"; // 重定向是 Get 请求
}
  • 修改 index 的 header 使得模板复用
<a class="dropdown-item text-center" th:href="@{/logout}">退出登录</a>

进行测试

在这里插入图片描述

当我们点击了退出登陆之后

  • 我们在后端进行ticket是否还是对外提供有效服务的

在这里插入图片描述

🔖 显示登录信息

在这里插入图片描述

配置用户登录请求拦截器——在登录之前会尝试获取凭证

  • 配置之前先写两个工具类

com.alascanfu.util.CookieUtil

主要是用于 查找Cookie中指定的 attribute 如这里我们需要找到当前用户请求是否携带了 ticket

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 23:03
 * @description: CookieUtil
 * @modified By: Alascanfu
 **/
public class CookieUtil {
    /** 获取 Cookie 中的值 */
    public static String getValue(HttpServletRequest httpServletRequest , String name ){
        if (httpServletRequest == null || name == null){
            throw new IllegalArgumentException("参数为空!无法进行处理");
        }
        Cookie[] cookies = httpServletRequest.getCookies();
        if (cookies != null){
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(name)){
                    return cookie.getValue();
                }
            }
        }
        return null ;
    }
}

com.alascanfu.util.HostHolder

这里的作用是对 ThreadLocal 进行了简单的封装 实用类,如这里我们需要在同一请求线程中的用户数据即可这样实现。

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 23:15
 * @description: HostHolder 类 起到一个容器的作用 用于代替 session 对象
 * @modified By: Alascanfu
 **/
@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();
    }
}

com.alascanfu.interceptor.LoginTicketInterceptor

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 23:00
 * @description: LoginTicketInterceptor 登录拦截器
 * @modified By: Alascanfu
 **/
@Component
public class LoginTicketInterceptor implements HandlerInterceptor {
    @Autowired
    private UserService userService ;
    @Autowired
    private HostHolder hostHolder ;
    
    /** 在请求发送之初 会去尝试从 Cookie 中获取凭证 ticket
     *  根据 用户凭证到数据库中获取对应的 LoginTicket 信息
     *  对其进行判空处理以及查询是否过期
     *      若没有过期凭证有效 则会直接根据 ticket 进行查询到对应的用户
     *      将这个凭证用户存入 ThreadLocal 中 也就是存入到当前线程的 ThreadLocalMap中
     * */
    @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.getLoginTicket(ticket);
            /** 检查凭证是否有效 */
            if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())){
                /** 根据 ticket 查询到用户 */
                User u = userService.findUserById(loginTicket.getUserId());
                /** 在本次请求中持有用户 */
                hostHolder.setUser(u);
            }
        }
        return true ;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response
        , Object handler, ModelAndView modelAndView) throws Exception {
        User u = hostHolder.getUser();
        if (u != null && modelAndView != null){
            modelAndView.addObject("loginUser" , u);
        }
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        hostHolder.clear();
    }
}

com.alascanfu.config.WebMvcConfig

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 22:56
 * @description: WebMvcConfig
 * @modified By: Alascanfu
 **/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Autowired
    private LoginTicketInterceptor loginTicketInterceptor ;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginTicketInterceptor)
            .excludePathPatterns("/**/*.css","/**/*.js","/**/*.jpg","/**/*.jpeg");
    }
}

启动并进行对应的测试即可

🔖 账号设置

在这里插入图片描述

步骤一:构建UserController 编写用户设置业务逻辑

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 21:35
 * @description: UserController 用户设置业务逻辑
 * @modified By: Alascanfu
 **/
@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping(path = "/setting" , method = RequestMethod.GET)
    public String getSettingPage(){
        return "/site/setting";
    }
}

步骤二:改写 /site/setting.html 以及 index 首部复用跳转的 页面

<a class="dropdown-item text-center" th:href="@{/user/setting}">账号设置</a>

步骤三:进行页面测试

在这里插入图片描述

步骤四:设置 文件上传路径

community.path.uploadPath=d:/data/upload

步骤五:改写 UserService 追加修改 用户头像的功能 以及编写 UserController 层进行追加请求处理上传头像的请求

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

步骤六:UserController的编写

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/22 21:35
 * @description: UserController 用户设置业务逻辑
 * @modified By: Alascanfu
 **/
@Controller
@RequestMapping("/user")
public class UserController {
    
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);
    /** 文件上传所需的 路径 */
    
    @Value("${community.path.uploadPath}")
    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("errorMsg" ,"您还没有选择图片!");
            return "/site/setting";
        }
    
        String fileName = headerImage.getOriginalFilename();
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        if (StringUtils.isBlank(suffix)){
            model.addAttribute("errorMsg" , "上传的文件格式有问题!");
            return "/site/setting";
        }
        
        /** 生成随机的文件名称 */
        fileName = CommunityUtil.getRandomString() + suffix;
        /** 确定文件存放的位置 */
        File dest = new File(uploadPath + "/" + fileName);
    
        try {
            headerImage.transferTo(dest);
        } catch (IOException e) {
            logger.error("上传文件失败 => {"+e.getMessage()+"}");
            throw new RuntimeException("上传文件失败!服务器发生异常 => {"+e.getMessage() +"}");
        }
        /** 更新当前用户的头像路径需要 web 访问路径 */
        /** http://localhost:8080/community/user/header/xxx.png */
        User u = hostHolder.getUser();
        String headerUrl = domain + contextPath + "/user/header/" + fileName;
        userService.updateHeader(u.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);
        OutputStream os = null ;
        FileInputStream fis = null;
        
        try {
            os = response.getOutputStream();
            fis = new FileInputStream(filename);
            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() + "}");
        } finally {
            try {
                if (fis != null){
                    fis.close();
                }
        
                if (os!= null){
                    os.close();
                }
            } catch (IOException e) {
                logger.error("流关闭失败 => {" + e.getMessage() + "}");
            }
        }
    }
}

步骤七:改写 setting 页面

<form class="mt-5" method="post" enctype="multipart/form-data" th:action="@{/user/upload}">
    <div class="form-group row mt-4">
        <label class="col-sm-2 col-form-label text-right">选择头像:</label>
        <div class="col-sm-10">
            <div class="custom-file">
                <input type="file" th:class="|custom-file-input ${errorMsg==null?'is-invalid':''}|"
                       id="head-image" name="headerImage" lang="es" required="">
                <label class="custom-file-label" for="head-image" data-browse="文件">选择一张图片</label>
                <div class="invalid-feedback"  th:utext="${errorMsg}" ></div>
            </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>

步骤八:修改用户密码

  • 编写对应用户 id 修改其密码的 UserService 服务层代码
public int rePassword(int userId,String password){
    return userMapper.updatePassword(userId, password);
}
  • 针对业务逻辑书写 UserController 层的控制逻辑代码
	@RequestMapping(path = "/rePassword" , method = RequestMethod.POST)
    public String rePassword(@CookieValue("ticket") String ticket, String oldPassword , String newPassword ,
                             String confirmPassword , Model model){
        if (!newPassword.equals(confirmPassword)){
            model.addAttribute("rePwdMsg","两次输入的密码不一致,请重新输入~");
            return "/site/setting";
        }
    
        LoginTicket loginTicket = userService.getLoginTicket(ticket);
        User u = userService.findUserById(loginTicket.getUserId());
        oldPassword = CommunityUtil.md5Encrypt(u.getSalt() + oldPassword );
        String encryptPassword = u.getPassword();
        if (!oldPassword.equals(encryptPassword)){
            model.addAttribute("originalPwdMsg","原密码输入错误,请重试~");
            return "/site/setting";
        }
        
        newPassword = CommunityUtil.md5Encrypt(u.getSalt() + newPassword);
        userService.rePassword(u.getId(),newPassword);
        
        return "redirect:/index";
    }
  • 前端页面的改写
<!-- 修改密码 -->
<h6 class="text-left text-info border-bottom pb-2 mt-5">修改密码</h6>
<form class="mt-5" method="post" th:action="@{/user/rePassword}">
    <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 ${originalPwdMsg!=null?'is-invalid':''}|" id="old-password"
                th:value="${param.oldPassword}" name="oldPassword" placeholder="请输入原始密码!" required>
            <div class="invalid-feedback" th:utext="${originalPwdMsg}">
                密码长度不能小于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 ${rePwdMsg!=null?'is-invalid':''}|"
                th:value="${param.newPassword}" name="newPassword" id="new-password" placeholder="请输入新的密码!" required>
            <div class="invalid-feedback" th:utext="${rePwdMsg}">
                密码长度不能小于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" th:class="|form-control ${rePwdMsg!=null?'is-invalid':''}|"
                th:value="${param.confirmPassword}" name="confirmPassword" id="confirm-password" placeholder="再次输入新密码!"
                required>
            <div class="invalid-feedback" th:utext="${rePwdMsg}">
                两次输入的密码不一致!
            </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>

🔖 检查用户登录状态

在这里插入图片描述

通过自定义注解与拦截器的相结合进行配置登录状态检查、以免未登录时可以访问到敏感数据

com.alascanfu.anotation.LoginRequired

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/23 1:40
 * @description: 登录状态检查的相关注解
 * @modified By: Alascanfu
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {

}

在对应的包含非登录不可访问的路径请求方法上标注上述注解

创建一个 LoginRequiredInterceptor

com.alascanfu.interceptor.LoginRequiredInterceptor

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/23 1:43
 * @description: 登录拦截器
 * @modified By: Alascanfu
 **/
@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 annotation = method.getAnnotation(LoginRequired.class);
            if (annotation != null && hostHolder.getUser() == null){
                response.sendRedirect(request.getContextPath() + "/login");
                return false ;
            }
        }
        return true ;
    }
}

最后进行测试即可

📚 社区登录模块开发完成了~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
智能排班系统是一个应用了SpringSpring MVC、MyBatis、PageHelper等技术的系统。Spring是一个开源的Java开发框架,提供了依赖注入和面向切面编程等功能,可以简化应用程序开发的复杂性。Spring框架的主要特点是易于扩展和集成其他框架。 Spring MVC是Spring框架中的模块,用于开发基于Model-View-Controller模式的Web应用程序。它通过请求映射和视图解析等功能,将用户请求和响应进行有效地处理和分发。 MyBatis是一种Java持久层框架,通过XML或注解对数据库操作进行配置,提供了对SQL语句的执行和结果的映射功能。它简化了数据库访问的复杂性,提供了更好的SQL控制和性能优化。 PageHelper是一个开源的MyBatis物理分页插件,可以自动地对查询结果进行分页处理。它提供了简单的配置和使用方式,能够有效地减轻数据库的查询压力,提高系统性能。 GitHub是一个基于Git版本控制系统的代码托管平台,开发人员可以在上面创建和管理项目的代码仓库。它提供了多人协同开发、版本控制、代码审查、问题追踪等功能,能够有效地提高开发效率和代码质量。 综上所述,智能排班系统应用了SpringSpring MVC、MyBatis和PageHelper等技术,通过依赖注入、面向切面编程、模型-视图-控制器模式和物理分页等功能,实现了对数据库操作的简化、Web应用程序的高效处理和查询结果的分页处理。同时,利用GitHub进行代码托管,实现了多人协同开发和版本控制,提高了系统的可维护性和代码的质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alascanfu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值