发送邮件
1. 邮箱设置
启用客户端SMTP服务
2. Spring Email
2.1 导入 jar 包
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
2.2 邮箱参数配置
#MailProperties
spring.mail.host=smtp.qq.com
spring.mail.port=465
spring.mail.username=xxxxxxxxxxx@qq.com
spring.mail.password=xxxxxxxxx # 这里不是邮箱密码,是smtp的设备码
spring.mail.protocol=smtps
spring.mail.properties.mail.smtp.ssl.enable=true
2.3使用 JavaMailSender 发送邮件
// 发邮件
// to: 发给谁 subject: 主题 content: 标题
public void sendMail(String to, String subject, String content){
// 利用JavaMailSender:构建MimeMessage类,就可以用其中的方法
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true); // true表示支持html文本
mailSender.send(helper.getMimeMessage());
} catch (MessagingException e) {
e.printStackTrace();
// 记录错误日志
logger.error("发送邮件失败:"+ e.getMessage());
}
}
3. 模板引擎
使用 Thymeleaf 发送 HTML 邮件
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>邮件示例</title>
</head>
<body>
<p>欢迎你, <span style="color:red;" th:text="${username}"></span>!</p>
</body>
</html>
// 主动调用模板引擎
@Autowired
private TemplateEngine templateEngine;
@Test
public void testHtmlMail(){
Context context = new Context();
context.setVariable("username", "sunday");
String content = templateEngine.process("/mail/demo", context);
System.out.println(content);
mailClient.sendMail("3338302451@qq.com", "Html", content);
}
开发自动注册功能
按照请求去拆解,然后按照三层去开发:数据访问层 - 业务层 - 视图层
1. 访问注册页面
- 点击顶部区域内的链接,打开注册页面。
利用Thymeleaf复用html标签
<!--便于复用-->
<header class="bg-dark sticky-top" th:fragment="header">
2. 提交注册数据
- 导入commons Lang,便于处理字符串
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
- 配置网站的域名
# 配置网站的域名
# community
community.path.domain=http://localhost:8080
- 写工具类
- 生成随机字符串
- MD5加密
public static String generateUUID(){
return UUID.randomUUID().toString().replaceAll("-", "");
}
// MD5加密 - 使用MD5算法对密码进行加密
// MD5算法只能加密,不能解密,每次加密的值都一样
// hello -> abc123def456
// hello ->? abv123def456adc (在密码后加一个随机字符串,提高安全性)
public static String md5(String key){
// 判定key是否为空
if(StringUtils.isBlank(key)){
return null;
}
// md5算法加密成16进制的字符串,再转换回来
return DigestUtils.md5DigestAsHex(key.getBytes());
}
接下来就可以进行业务层的处理了
2.1 通过表单提交数据
UserService.java
- 配置网站的域名和项目名,注入相应的数据
@Autowired
private MailClient mailClient;
@Autowired
private TemplateEngine templateEngine;
// 注入配置文件中的域名和项目名
// 注入固定的值使用“value"注解
@Value("${community.path.domain}")
private String domain;
@Value("${server.servlet.context-path}")
private String contextPath;
- 编写业务方法
- /**
-
- 对参数进行判断:是不是为空?属性有无问题?
-
- 验证账号:把传入的username和邮箱去库里查一下看有没有
-
- 如果前两步都没有问题,第三步就是注册用户:把用户注册到库里
-
- 发送激活邮件
*/
- 发送激活邮件
2.2 服务端验证账号是否已存在、邮箱是否已注册。
2.3 服务端发送激活邮件
public Map<String, Object> register(User user){
Map<String, Object> map = new HashMap<>();
// 空值处理
if (user == null) {
throw new IllegalArgumentException("参数不能为空!");
}
if (StringUtils.isBlank(user.getUsername())) {
map.put("usernameMsg", "账号不能为空!");
return map;
}
if (StringUtils.isBlank(user.getPassword())) {
map.put("passwordMsg", "密码不能为空!");
return map;
}
if (StringUtils.isBlank(user.getEmail())) {
map.put("emailMsg", "邮箱不能为空!");
return map;
}
// 验证账号
User u = userMapper.selectByName(user.getUsername());
if(u != null){
map.put("usernameMsg", "该账号已存在");
return map;
}
// 验证邮箱
u = userMapper.selectByEmail(user.getEmail());
if (u != null) {
map.put("emailMsg", "该邮箱已被注册!");
return map;
}
// 注册用户
user.setSalt(CommunityUtil.generateUUID().substring(0, 6));
user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));
user.setType(0);
user.setActivationCode(CommunityUtil.generateUUID()); // 生成激活码
user.setHeaderUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000))); // 生成随机头像
user.setCreateTime(new Date());
userMapper.insertUser(user);
// 激活邮件
Context context = new Context();
context.setVariable("email", user.getEmail());
// http://localhost:8080/community/activation/101/code
String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode(); // userMapper.insertUser(user)
context.setVariable("url", url);
String content = templateEngine.process("/mail/activation", context);
mailClient.sendMail(user.getEmail(), "激活账号", content);
return map;
}
3. 激活注册账号
点击邮件中的链接,访问服务端的激活服务。
如果提交不成功,需要对register页面对错误信息进行处理
定义激活的接口并实现
CommunityConstant
ublic interface CommunityConstant {
/**
* 激活成功
*/
int ACTIVATION_SUCCESS = 0;
/**
* 重复激活
*/
int ACTIVATION_REPEAT = 1;
/**
* 激活失败
*/
int ACTIVATION_FAILURE = 2;
}
实现激活的方法
返回激活的信息
/**
* 先查用户,再判断激活码对不对
*/
private int activation(int userId, String code){
User user = userMapper.selectById(userId);
// 已经激活过了
if(user.getStatus() == 1){
return ACTIVATION_REPEAT;
}else if(user.getActivationCode().equals(code)){
userMapper.updateStatus(userId, 1);
return ACTIVATION_SUCCESS;
}else {
return ACTIVATION_FAILURE;
}
}
```java
实现注册后页面跳转
// http://localhost:8080/community/activation/101/code
// 返回激活结果的页面
@RequestMapping(path = "/activation/{userId}/{code}", method = RequestMethod.GET)
//@PathVariable: 从路径里取值
public String activation(Model model, @PathVariable("userId") int userId, @PathVariable("code") String code) {
int result = userService.activation(userId, code);
if(result == ACTIVATION_SUCCESS) {
model.addAttribute("msg", "激活成功,您的账号已经可以正常使用了!");
model.addAttribute("target", "/login");
} else if (result == ACTIVATION_REPEAT) {
model.addAttribute("msg", "无效操作,该账号已经激活过了!");
model.addAttribute("target", "/index");
} else {
model.addAttribute("msg", "激活失败,您提供的激活码不正确!");
model.addAttribute("target", "/index");
}
return "/site/operate-result";
}
访问注册页面
@RequestMapping(path = "/login", method = RequestMethod.GET)
public String getLoginPage() {
return "/site/login";
}
会话管理
1. HTTP的基本性质
- HTTP是简单的
- HTTP是可扩展的
- HTTP是无状态的,有会话的
无状态:两个执行成功的请求之间没有关系,因此用户没有办法在同一个网站进行连续的交互
HTTP Cookies可以解决这个问题:把Cookles添加到头部,创建一个会话让每次请求都能共享相同的上下文信息,达成相同的状态
HTTP本质是无状态的,使用Cookies可以创建有状态的会话
2. Cookie
是服务器发送到浏览器,并保存在浏览器端的一小块数据。
浏览器下次访问该服务器时,会自动携带块该数据,将其发送给服务器。
创建cookie
@RequestMapping(path = "/cookie/set", method = RequestMethod.GET)
@ResponseBody
public String setCookie(HttpServletResponse response) {
// 创建cookie
Cookie cookie = new Cookie("code", CommunityUtil.generateUUID());
// 设置cookie生效的范围:指定访问的哪些路径会发送cookie,不需要的就不会发送cookie
// 在该路径和子路径下有效
cookie.setPath("/community/alpha");
// 设置cookie的生存时间
cookie.setMaxAge(60 * 10);
// 发送cookie
response.addCookie(cookie);
return "set cookie";
}
获取cookie
@RequestMapping(path = "/cookie/get", method = RequestMethod.GET)
@ResponseBody
public String getCookie(@CookieValue("code") String code) {
System.out.println(code);
return "get cookie";
}
@RequestMapping(path = "/cookie/get", method = RequestMethod.GET)
@ResponseBody
public String getCookie(@CookieValue("code") String code) {
System.out.println(code);
return "get cookie";
}
3. Session
cookie的数据是存在在客户端的,每次访问服务器都会对流量造成影响 -> session
是JavaEE的标准,用于在服务端记录客户端信息。
数据存放在服务端更加安全,但是也会增加服务端的内存压力。
示例
// session示例
@RequestMapping(path = "/session/set", method = RequestMethod.GET)
@ResponseBody
public String setSession(HttpSession session) {
session.setAttribute("id", 1);
session.setAttribute("name", "Test");
return "set session";
}
@RequestMapping(path = "/session/get", method = RequestMethod.GET)
@ResponseBody
public String getSession(HttpSession session) {
System.out.println(session.getAttribute("id"));
System.out.println(session.getAttribute("name"));
return "get session";
}
在分布式部署下使用session
存在问题
https://segmentfault.com/a/1190000022404396
- 客户端存储
- session复制
- session绑定
- 基于redis存储session方案
生成验证码
Kaptcha
1. 导入 jar 包
<!-- https://mvnrepository.com/artifact/com.github.penggle/kaptcha -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
2. 编写 Kaptcha 配置类
@Configuration
public class KaptchaConfig {
@Bean
public Producer KaptchaProducer() {
Properties properties = new Properties();
properties.setProperty("kaptcha.image.width", "100");
properties.setProperty("kaptcha.image.height", "40");
properties.setProperty("kaptcha.textproducer.font.size", "32");
properties.setProperty("kaptcha.textproducer.font.color", "0,0,0");
properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYAZ");
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
DefaultKaptcha kaptcha = new DefaultKaptcha();
Config config = new Config(properties);
kaptcha.setConfig(config);
return kaptcha;
}
}
3. 生成随机字符、生成图片
需要把验证码信息存到sessionl里
// 生成验证码的方法
// 验证码不能存在浏览器端 -> 使用session
@RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
public void getKacha(HttpServletResponse response, HttpSession session) {
// 生成验证码
String text = kaptchaProducer.createText();
BufferedImage image = kaptchaProducer.createImage(text);
// 将验证码存入session
session.setAttribute("kaptcha", text);
// 将图片输出给浏览器
response.setContentType("image/png");
try {
OutputStream os = response.getOutputStream();
ImageIO.write(image, "png", os);
} catch (IOException e) {
logger.error("响应验证码失败:" + e.getMessage());
}
}
实现js方法,刷新验证码
<!--实现Js方法 刷新验证码:这个js将调用这个方法,实现逻辑-->
<a href="javascript:refresh_kaptcha();" class="font-size-12 align-bottom">刷新验证码</a>
<script>
function refresh_kaptcha() {
var path = CONTEXT_PATH + "/kaptcha?p=" + Math.random();
$("#kaptcha").attr("src", path);
}
}
</script>
开发登录、退出功能
1. 访问登录页面
- 点击顶部区域内的链接,打开登录页面。(上一步已经实现)
2. 登录
2.1 dao层
- 实现实体类LoginTicket
- 实现接口(这里在接口文件里写sql语句)
@Mapper
public interface LoginTicketMapper {
// 插入数据
@Insert({
"insert into login_ticket(user_id,ticket,status,expired) ",
"values(#{userId},#{ticket},#{status},#{expired})"})
@Options(useGeneratedKeys = true, keyProperty = "id") // 能让key自增
int insertLoginTicket(LoginTicket loginTicket);
@Select({
"select id,user_id,ticket,status,expired ",
"from login_ticket where ticket=#{ticket}"
})
LoginTicket selectByTicket(String ticket);
// 修改凭证的状态
@Update({
"<script>" //script标签表示脚本
"update login_ticket set status=#{status} where ticket=#{ticket} ",
"<if test=\"ticket!=null\"> ",
"and 1=1 ",
"</if>",
})
int updateStatus(String ticket, int status);
}
写完之后进行测试
2.2 业务层
验证账号、密码、验证码
- 空值处理
- 验证账号、状态、密码是否和数据库一样(密码是明文,需要+salt后再调用md5算法)
用map返回错误信息(和注册时的一样)
成功时,生成登录凭证,发放给客户端。
(把敏感信息存到数据库里login_ticket)
expired:过期时间-凭证什么时候过期?
Status: 0-有效 1-无效
这个表起到类似session的作用,只需要ticket作为登录凭证,只要在map里存ticket
退出
- 将登录凭证修改为失效状态,status状态改为1(失效)
- 转至网站首页
public void logout(String ticket) {
loginTicketMapper.updateStatus(ticket, 1);
}
2.3controller层
- 设置默认登录凭证和记住状态的登录凭证超时时间
/**
* 默认状态的登录凭证的超时时间
*/
int DEFAULT_EXPIRED_SECONDS = 3600 * 12;
/**
* 记住状态的登录凭证超时时间
*/
int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;
- 检查验证码是否正确
- 看用户是否勾选了“记住我”
失败时,跳转回登录页。,如果没勾选,就是默认状态,如果勾选,就是记住状态
// 检查账号密码:根据rememberme检查
int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
Map<String, Object> map = userService.login(username, password, expiredSeconds);
if(map.containsKey("ticket")) {
// 向客户端发送一个cookie,带ticket
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
cookie.setPath(contextPath);
cookie.setMaxAge(expiredSeconds);
response.addCookie(cookie);
return "redirect:/index";
}else{
// 如果错误,就返回错误信息给model
model.addAttribute("usernameMsg", map.get("usernameMsg"));
model.addAttribute("passwordMsg", map.get("passwordMsg"));
return "/site/login";
}
}
- 修改html表单信息
- 表单默认值处理: th:value=“${param.username}”
退出
- 获得存在cookie里的ticket,调用service层中的logout方法,从而把数据库中的ticket的status改为失效
- 重定向到login
- 修改index.html中的链接
@RequestMapping(path = "/logout", method = RequestMethod.GET)
public String logout(@CookieValue("ticket") String ticket) {
userService.logout(ticket);
return "redirect:/login";
}
显示登录信息
1. 拦截器简介
根据登录与否需要调整头部显示的内容,每个请求都需要进行统一的处理 -> 拦截器
以非常低的耦合度解决通用的问题
拦截器属于请求,放在controller里
- 拦截器示例
- 定义拦截器,实现HandlerInterceptor
- 配置拦截器,为它指定拦截、排除的路径
- 拦截器应用
- 在请求开始时查询登录用户
- 在本次请求中持有用户数据
- 在模板视图上显示用户数据
- 在请求结束时清理用户数据
2. 代码示例
定义拦截器,实现HandlerInterceptor
@Component
public class AlphaInterceptor implements HandlerInterceptor {
// HandlerInterceptor 不强制实现所有的方法
private static final Logger logger = LoggerFactory.getLogger(AlphaInterceptor.class);
// 在Controller之前执行。false就是不往下执行了,一般返回true
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.debug("preHandle: " + handler.toString());
return true;
}
// 在Controller之后执行 模板之前执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.debug("postHandle: " + handler.toString());
}
// 在模板引擎执行之后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.debug("afterCompletion: " + handler.toString());
}
}
配置拦截器,为它指定拦截、排除的路径
// 一般实现config是为了配置bean,但是拦截器是为了实现接口
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 注入拦截器
@Autowired
private AlphaInterceptor alphaInterceptor;
// 利用调用的对象注册interceptor
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(alphaInterceptor)
// 排除所有目录下的css文件 双星号**表示在任意目录下
.excludePathPatterns("/**/*.css\", \"/**/*.js\", \"/**/*.png\", \"/**/*.jpg\", \"/**/*.jpeg")
// 添加拦截的路径
.addPathPatterns("/register", "login");
}
}
3. 项目实现
一个工具:持有用户信息,用于代替session对象
因为实际上可能有多个用户访问,是多线程,所以需要线程隔离-利用Threadlocal实现(Threadlocal是通过map实现线程隔离的)
/**
* 持有用户信息,用于代替session对象
* Threadlocal能实现线程隔离(通过map实现)
*/
@Component
public class HostHolder {
private ThreadLocal<User> users = new ThreadLocal<>();
public void setUser(User user) {
users.set(user);
}
public User getUser() {
return users.get();
}
}
定义拦截器
- preHandle:
查询凭证,根据凭证查询用户,再存入hostholder里 - postHandle
postHandle是在模板引擎之前调用的,从hostholder里得到当前线程user,把它存进model里 - afterCompletion
在模板执行完之后,清空hostHolder
@Component
public class LoginTicketInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Autowired
private HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 通过cookie获得ticket
String ticket = CookieUtil.getValue(request, "ticket");
if(ticket != null) {
// 查询凭证
LoginTicket loginTicket = userService.findLoginTicket(ticket);
// 检查凭证是否有效:不为空 && 状态=0 && 超时时间晚于当前时间
if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
// 根据凭证查询用户
User user = userService.findUserById(loginTicket.getUserId());
// 在本次请求中持有用户(要考虑多线程的情况,考虑线程的隔离)处理完请求之后线程被销毁,其中的时间里线程一直都在
hostHolder.setUser(user);
}
}
return true;
}
/**
* postHandle是在模板引擎之前调用的,
* 可以从hostholder里得到当前线程user,把它存进model里
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
User user = hostHolder.getUser();
if(user != null && modelAndView != null) {
modelAndView.addObject("loginUser", user);
}
}
/**
* 在模板执行完之后,清空hostHolder
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
hostHolder.clear();
}
}
配置拦截器
- 注入
- 配置
registry.addInterceptor(loginTicketInterceptor)
// 排除所有目录下的css文件 双星号**表示在任意目录下
.excludePathPatterns("/**/*.css\", \"/**/*.js\", \"/**/*.png\", \"/**/*.jpg\", \"/**/*.jpeg")
在模板上处理
- 改写index-header
th:if="${loginUser!=null} - 换掉头像的url和当前登录用户的用户名
th:src=“ l o g i n U s e r . h e a d e r U r l " t h : u t e x t = " {loginUser.headerUrl}" th:utext=" loginUser.headerUrl"th:utext="{loginUser.username}”
用th:text不会解析html,用th:utext会解析html,在页面中显示相应的样式
账号设置
1. 上传文件
- 请求:必须是POST请求
- 表单:enctype=“multipart/form-data”
- Spring MVC:通过 MultipartFile 处理上传文件
2. 开发步骤
- 访问账号设置页面
- 上传头像
- 获取头像
3. 代码实现
上传文件
- 设置上传路径
community.path.upload=d:/work/data/upload
- 请求:必须是POST请求
- 表单:enctype=“multipart/form-data”
- Spring MVC:通过 MultipartFile 处理上传文件
上传之后,改变用户的Url
service层
// 更新用户头像
public int updateHeader(int userId, String headerUrl) {
return userMapper.updateHeader(userId, headerUrl);
}
controller层
- 生成随机的名字
- 获得文件名的后缀:.jpg .png之类的
- 生成随机的文件名
- 拼接前两个
- 确定文件的存放路径
- 存储文件
- 更新当前用户的头像的路径(是web访问路径)
- 重定向到首页
@RequestMapping(path = "upload", method = RequestMethod.POST)
public String uploadHeader(MultipartFile headerImage, Model model) {
if(headerImage == null) {
model.addAttribute("error", "您还没有选择图片!");
return "/site/setting";
}
// 生成随机的名字,防止重复
String filename = headerImage.getOriginalFilename();
// lastIndexOf() 方法返回调用String 对象的指定值最后一次出现的索引
// 获得文件的后缀 substring(int beginIndex)
String suffix = filename.substring(filename.lastIndexOf("."));
if(StringUtils.isBlank(suffix)) {
model.addAttribute("error", "文件的格式不正确");
return "/site/setting";
}
// 生成随机的文件名
filename = CommunityUtil.generateUUID() + suffix;
// 确定文件存放的路径
File dest = new File(uploadPath + "/" + filename);
try {
// 存储文件
headerImage.transferTo(dest);
} catch (IOException e) {
logger.error("上传文件失败" + e.getMessage());
throw new RuntimeException("上传文件失败,服务器发生异常!", e);
}
// 更新当前用户的头像的路径(web访问路径)
// http://localhost:8080/community/user/header/xxx.png
User user = hostHolder.getUser();
String headerUrl = domain + contextPath + "/user/header/" + filename;
userService.updateHeader(user.getId(), headerUrl);
// 重定向到首页,看图像有没有更新
return "redirect:/index";
}
- 向浏览器响应图片
/**
* 向浏览器响应图片
*/
@RequestMapping(path = "/header/{filename}", method = RequestMethod.GET)
public void getHeader(@PathVariable("filename") String fileName, HttpServletResponse response) {
// 找到服务器存放的路径名字
fileName = uploadPath + "/" + fileName;
// 向浏览器输出图片 文件后缀
String suffix = fileName.substring(fileName.lastIndexOf("."));
// 响应图片
// 设置发送到客户端的响应的内容类型
response.setContentType("image/" + suffix);
try (
// what the hell ? -> java7语法 似乎可以自动关闭
FileInputStream fis = new FileInputStream(fileName);
OutputStream os = response.getOutputStream();
){
// 每次最多输入1024字节个数据
byte[] buffer = new byte[1024];
int b = 0;
// b != -1 代表读到数据,可以继续读下去,=-1就代表结束
while ((b = fis.read(buffer)) != -1){
os.write(buffer, 0, b);
}
} catch (IOException e) {
logger.error("读取头像失败:" + e.getMessage());
}
}
配置setting.html页面的表单
<form class="mt-5" method="post" enctype="multipart/form-data" th:action="@{/user/upload}">
检查登录状态
如果不检查登录状态的话,假设用户知道路径,就可以直接通过路径访问敏感信息
如果用户试图访问这些信息,进行判断:没登录拒绝,登录同意
使用注解
- 使用拦截器
- 在方法前标注自定义注解
- 拦截所有请求,只处理带有该注解的方法
- 自定义注解
自定义注解需要用元注解进行定义- 常用的元注解:
- @Target:声明自定义注解可以写在哪个位置,作用于什么类型
- @Retention:声明自定义注解保留的时间
- @Document:声明生成文档时要不要把这个注解带上去
- @Inherited:声明子类和父类的关系,如果父类有注解,那么子类要不要继承下来
- 常用的元注解:
- 如何读取注解:
- Method.getDeclaredAnnotations ()
- Method.getAnnotation (Class annotationClass)
代码实现
- 新建注解包:annotation
使用元注解定义这个注解
@Target(ElementType.METHOD) // 注解可以写在方法上
@Retention(RetentionPolicy.RUNTIME) // 声明这个注解在程序运行时才有效
public @interface LoginRequired {
}
- 在controller的方法上加注解
- 使用拦截器拦截这个带有注解的方法
@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;
// getMethod方法可以获取拦截到的method对象
Method method = handlerMethod.getMethod();
// 从method对象中取注解
LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
if(loginRequired != null && hostHolder.getUser() == null) {
// 强制重定向到页面
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}
return true;
}
}
- 对拦截器进行配置
registry.addInterceptor(loginRequiredInterceptor)
// 排除所有目录下的css文件 双星号**表示在任意目录下
.excludePathPatterns("/**/*.css\", \"/**/*.js\", \"/**/*.png\", \"/**/*.jpg\", \"/**/*.jpeg");