拦截器
为什么使用拦截器,之前我们做的所有页面,用户只要输入正确的访问地址就可以进行一系列操作,尽管我们做了后台验证,但是这仍是不安全的,比如,如果用户没有登录就访问之前的http://loaclhoat:8080/publish,就可以进入到发布文章页面,为此我们需要做拦截器,当访问资源的时候,对其进行拦截并处理,选择通过还是不通过
配置:
编写一个拦截器类SessionIntercetpor.java,然后实现HandlerInterceptor接口,并重写preHandle、postHandle、afterCompletion方法
preHandle方法是在请求之前执行(Controller之前)
postHandle方法在preHandle方法返回true后执行(Controller之后)
afterCompletion方法也是在preHandle方法返回true后执行(整个请求完成之后)
SessionIntercetpor.java
@Component
public class SessionIntercetpor implements HandlerInterceptor {
@Autowired
private UserMapper userMapper;
//preHandle方法是在请求之前执行(Controller之前)
//处理用户Cookie验证,如果用户没登录,验证不通过,不允许访问,反之将用户信息写入session,用户后面页面访问获取
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//其他类中的这段代码将删除,具体可查看我的源码
User user;
Cookie[] cookies = request.getCookies();
if(cookies!=null && cookies.length>0){
for (Cookie cookie:cookies){
if("token".equals(cookie.getName())){
String token = cookie.getValue();
user = userMapper.selectUserByToken(token);
if(user!=null){
request.getSession().setAttribute("user",user);
}
}
}
}
return true;
}
//postHandle方法在preHandle方法返回true后执行(Controller之后)
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
//afterCompletion方法也是在preHandle方法返回true后执行(整个请求完成之后)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
编写一个配置类,用于拦截器配置:
WebConfig.java
//编写配置类,用于Spring管理我们的自定义配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
//注入编写好的拦截器
@Autowired
private SessionIntercetpor sessionIntercetpor;
//添加拦截器,并拦截所有请求
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截所有请求
registry.addInterceptor(sessionIntercetpor).addPathPatterns("/**");
}
}
然后在Controller类中编写如下代码,用户session用户验证,获取不到用户信息将重定向到首页
//检查用户是否登录
User user = (User) request.getSession().getAttribute("user");
if(user==null){
return "redirect:/";
}
做完如上处理后,我们就可以对所有资源进行拦截,如果没有登录,只能访问首页和登录页
个人文章列表展示
登录成功后,我们点击用户名称下拉菜单,然后选择我的文章,如下:
点击之后页面会跳转到个人文章列表,这里只展示自己发布过的文章信息
具体代码
html页面代码模板化引入,如下
我们将共用的头部导航栏和底部分页栏抽离成一个模板,这个模板可以在多个html中引入,避免html页面臃肿、代码重复等问题
顶部导航栏模板navigation.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<!--定义一个div然后起个名字,用于后面引入使用-->
<div th:fragment="nav">
<!--顶部导航栏-->
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">个人博客</span>
</button>
<a class="navbar-brand" href="/">个人博客</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="请搜索问题">
</div>
<button type="submit" class="btn btn-default">搜索</button>
</form>
<ul class="nav navbar-nav navbar-right">
<li th:if="${session.user!=null}">
<a href="/publish"><i class="iconfont icon-fabu2"></i> 写文章</a>
</li>
<li th:if="${session.user==null}">
<a href="/login">登录</a>
</li>
<li class="dropdown" th:if="${session.user!=null}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span th:text="${session.user.name}"></span><span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/main/personal">个人中心</a></li>
<li><a href="/main/article">我的文章</a></li>
<li><a href="/main/message">我的消息</a></li>
<li role="separator" class="divider"></li>
<li><a href="/logout">退出登录</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
</div>
</body>
</html>
底部分页栏pagination.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<!--定义一个div然后起个名字,用于后面引入使用-->
<div th:fragment="page">
<!--分页-->
<nav aria-label="Page navigation" class="page-right" th:if="${paginationInfo.countPage != 0}">
<ul class="pagination">
<li th:if="${paginationInfo.isHasFirstPage()}">
<a th:href="@{${#httpServletRequest.getContextPath()}(page=1)}" aria-label="First">
<span aria-hidden="true"><<</span>
</a>
</li>
<li th:if="${paginationInfo.isHasPrePage()}">
<a th:href="@{${#httpServletRequest.getContextPath()}(page=${paginationInfo.page-1})}" aria-label="Previous">
<span aria-hidden="true"><</span>
</a>
</li>
<li th:each="page: ${paginationInfo.getPageList()}" th:class="${page==paginationInfo.page}? 'active'"><a th:href="@{${#httpServletRequest.getContextPath()}(page = ${page})}" th:text="${page}"></a></li>
<li th:if="${paginationInfo.isHasNextPage()}">
<a th:href="@{${#httpServletRequest.getContextPath()}(page=${paginationInfo.page+1})}" aria-label="Next">
<span aria-hidden="true">></span>
</a>
</li>
<li th:if="${paginationInfo.isHasLastPage()}">
<a th:href="@{${#httpServletRequest.getContextPath()}(page=${paginationInfo.getCountPage()})}" aria-label="Last">
<span aria-hidden="true">>></span>
</a>
</li>
</ul>
</nav>
<div class="noArticle" th:if="${paginationInfo.countPage == 0}">暂无数据,点击 <a th:href=" @{/publish}">这里</a>编写第一篇文章吧</div>
</div>
</body>
</html>
我们可以使用如下方式进行引入:
<!-- 使用thymelaf的th:replace语法进行引入,::之前是html页面名字,后面是之前起的div名字-->
<div th:replace="pagination :: page"></div>
我的文章列表页面布局article.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<!-- 踩过的坑,springboot引入静态资源路径不要加/static/,否则会报404-->
<title>个人博客</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="/bootstrap-3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/community.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_1643567_rm1fqucxtan.css">
<link rel="stylesheet" href="/bootstrap-3.3.7/css/bootstrap-theme.min.css">
<script src="/jquery-1.12.4/jquery-1.12.4.min.js"></script>
<script src="/bootstrap-3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div th:replace="navigation :: nav"></div>
<div class="row main">
<div class="col-lg-9 col-md-12 col-sm-12 col-xs-12 col-left">
<div class="row">
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-3"><h4><i class="iconfont icon-liebiao"></i> 我的文章</h4></div>
</div>
<div class="media media_list" th:each="article : ${myArticleList}">
<div class="media-left">
<a href="#">
<img class="media-object img-rounded" th:src="${article.user.getAvatarUrl()}" >
</a>
</div>
<div class="media-body">
<h4 class="media-heading" th:text="${article.title}">标题标题</h4>
<span th:text="${article.answerCount}"></span> 个回复 • <span th:text="${article.readCount}"></span> 次浏览 • 发布时间<span th:text="${#dates.format(article.createTime,'yyyy-MM-dd HH:mm:ss')}"></span>
</div>
</div>
<!-- 分页 -->
<div th:replace="pagination :: page"></div>
</div>
<div class="col-lg-3 col-md-12 col-sm-12 col-xs-12">
<div class="list-group">
<a href="/main/personal" th:class="${selection=='personal'?'active list-group-item':'list-group-item'} ">个人中心</a>
<a href="/main/article" th:class="${selection=='article'?'active list-group-item':'list-group-item'} ">我的文章</a>
<a href="/main/message" th:class="${selection=='message'?'active list-group-item':'list-group-item'} ">我的消息<span class="badge">4</span></a>
</div>
</div>
</div>
</body>
</html>
MainSelectionController.java
@Controller
public class MainSelectionController{
@Autowired
private ArticleMapper articleMapper;
@Autowired
private PaginationService paginationService;
@GetMapping("/main/{selection}")
public String article(@PathVariable(name = "selection") String selection,
@RequestParam(name = "page" ,defaultValue = "1") int page,
@RequestParam(name = "size" ,defaultValue = "5") int size,
HttpServletRequest request,
Model model){
//检查用户是否登录
User user = (User) request.getSession().getAttribute("user");
if(user==null){
return "redirect:/";
}
int totalCount = articleMapper.count(user.getId());
PaginationDTO paginationDTO = paginationService.getPageInfo(page,size,totalCount);
model.addAttribute("paginationInfo",paginationDTO);
switch (selection){
case "article":
//修改articleMapper中的list列表方法,我们这里传入用户id,如果id为空则表示查询所有,如果id不为空,则查询当前id的用户信息
List<Article> myArticleList = articleMapper.list(user.getId(), paginationDTO.getOffset(), size);
model.addAttribute("myArticleList",myArticleList);
//用于判断当前的操作
model.addAttribute("selection",selection);
return "article";
case "personal":
return "personal";
case "message":
return "message";
default:
return "redirect:/";
}
}
}
ArticleMapper.java中查询语句修改
//查询文章列表信息
//手动拼接sql语句,可做一些判断
@Select({"<script> " +
"select u.*,a.id as aid,a.title,a.author_id, a.description,a.read_count,a.answer_count,a.like_count,a.create_time from article a,user u " +
"where a.author_id = u.id " +
"<if test='uId!=null'> and a.author_id=#{uId}</if>"+
"limit #{page},#{size}"+
"</script>"})
@Results(id="ArticleUser",value = {
@Result(id=true,property = "id",column = "id"),
@Result(property = "id",column = "aid"),
@Result(property = "title",column = "title"),
@Result(property = "description",column = "description"),
@Result(property = "authorId",column = "author_id"),
@Result(property = "readCount",column = "read_count"),
@Result(property = "answerCount",column = "answer_count"),
@Result(property = "likeCount",column = "like_count"),
@Result(property = "user.name",column = "name"),
@Result(property = "user.avatarUrl",column = "avatar_url")
})
List<Article> list(@Param(value = "uId") Long uId, @Param(value = "page") int page,@Param(value = "size") int size);
//查询总文章数
@Select({"<script> " +
"select count(1) from article a,user u where a.author_id = u.id"+
"<if test='uId!=null'> and u.id=#{uId}</if>"+
"</script>"})
int count(@Param(value = "uId") Long uId);
源码
个人博客系统长期更新,所有源码都放在了我的GitHub上了。