📖 本文目录
📖 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">注 册</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">登 录</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>
步骤九:启动并测试激活邮箱
-
重新部署项目
-
点击之前发送给邮箱的邮件激活码即可实现自动跳转进行激活
- 此时刷新数据库则可以观察到刚才注册好的账号已经激活完成了~
📚 大功告成了~
🔖 会话管理
🔖 生成验证码
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">登 录</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">登 录</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 ;
}
}
最后进行测试即可
📚 社区登录模块开发完成了~