继CMS-Demo的1.0版本完成之后,我们要进入完善阶段,必进只是完成了基本功能,不影响运行而已。本篇开始将1.0优化成为2.0版本,同时在优化中我们重点是要加入ES、Redis等数据技术支持。
首先对于一个CMS来说它的首页一定是重点被查询页面,所以我们优化的第一个点就是首页的响应速度,我们现在可以看一下首页查询用时。
我们可以看到,现在后台查询分页每页6篇,也不多,但是却用了1.5秒,这个速度,我们是无法忍受的,现在而且现在还是本地运行,我们直接开发时访问,假设http://localhost:8080/index.do
这个路径正式使用上线了,大量的查询在加上网络的延迟,那我们就一首凉凉送给自己了,我们先优化第一次,使用多线程
执行这个任务。
/**
*
* @Title: index
* @Description: 进入首页
* @return
* @return: String
*/
@RequestMapping("index.do")
public String index(Model model,Article article,@RequestParam(defaultValue="1")Integer pageNum) {
//封装查询条件
model.addAttribute("article", article);
//使用线程
Thread t1;
Thread t2;
Thread t3;
Thread t4;
//查询所有的栏目,该线程为必须品
t1=new Thread(new Runnable() {
@Override
public void run() {
List<Channel> channels = channelService.selects();
model.addAttribute("channels", channels);
}
});
// 判断栏目ID 不为空 也就是说当前不是查询热点那么就要查询其下分类
t2=new Thread(new Runnable() {
@Override
public void run() {
if(article.getChannelId()!=null){
List<Category> categorys = channelService.selectsByChannelId(article.getChannelId());
model.addAttribute("categorys", categorys);
}else{
//如果栏目id是空的那么就代表这查询的是热点,并为为热点查询广告
List<Slide> slides = slideService.getAll();
model.addAttribute("slides", slides);
//限制查询热点文章
article.setHot(1);
}
}
});
//前两个线程决定查什么文章,第三个线程正式查文章
t3=new Thread(new Runnable() {
@Override
public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//无论是什么情况控制查询文章不是被逻辑删除的
article.setDeleted(0);
//不能查询非审核之后的文章
article.setStatus(1);
//查询符合条件的所有文章
List<Article> selectArticle = articleService.selectArticle(article, pageNum, 6);
PageInfo info=new PageInfo<>(selectArticle);
model.addAttribute("info", info);
}
});
//为首页查询五条最新的文章
t4=new Thread(new Runnable() {
@Override
public void run() {
// 封装该查询条件
Article latest = new Article();
latest.setDeleted(0);
//不能查询非审核之后的文章
latest.setStatus(1);
List<Article> newArticles = articleService.selectArticle(latest, 1, 5);
PageInfo lastArticles=new PageInfo<>(newArticles);
model.addAttribute("lastArticles", lastArticles);
}
});
//启动线程并保证线程顺序
t1.start();
t2.start();
t3.start();
t4.start();
try {
t1.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
return "index/index";
}
我们将首页的Controller使用线程分为四个任务一起跑,现在再执行项目看一下任务需要多少秒。
现在我们可以看到,总耗时不到25毫秒,没有优化前,单一个相应就1.5秒,暂时首页的展示请求响应速度比较满意,先暂时这样。
第二点优化是首页点击详情的时候,只展示一个详情,太单调了,我们在详情页面中搞一个收藏和评论,但是在开发之前,我们要想一个问题,针对与用户的行为,如果他没有登录我们可以做吗?所以我们需要先开发一个登录拦截器
package com.wy.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
*
* @ClassName: MyInterceptor
* @Description: 个人中心拦截器
* @author: charles
* @date: 2020年4月10日 上午9:56:23
*/
public class MyInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//不拦截规则--- 如果用户已经登录则不拦截 false:如果有session 则返回session.如果没有则返回null
HttpSession session = request.getSession(false);
if(session!=null) {//如果存在session
Object user = session.getAttribute("user");//则从session获取登录的user对象
if(user!=null)//如果对象不为空
return true;//放行
}
//没有登录,跳转到登录页面
request.setAttribute("msg", "请登录后再试");
request.getRequestDispatcher("/WEB-INF/view/passport/login.jsp").forward(request, response);
return false;
}
}
但是一个就够了吗?如果后续大家对项目有自己的扩展,总不可能在一个拦截器里写一堆if来判断登录角色吧?所以我们还需要一个管理员的拦截器
package com.wy.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
*
* @ClassName: MyInterceptor
* @Description: 管理员拦截器
* @author: charles
* @date: 2020年4月10日 上午9:56:23
*/
public class AdminInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//不拦截规则--- 如果用户已经登录则不拦截 false:如果有session 则返回session.如果没有则返回null
HttpSession session = request.getSession(false);
if(session!=null) {//如果存在session
Object user = session.getAttribute("admin");//则从session获取登录的user对象
if(user!=null)//如果对象不为空
return true;//放行
}
//没有登录,跳转到登录页面
request.setAttribute("msg", "请登录后再试");
request.getRequestDispatcher("/WEB-INF/view/passport/login_admin.jsp").forward(request, response);
return false;
}
}
随后在springmvc的配置文件中注册这两拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 1个人中心拦截器 -->
<mvc:interceptor>
<!-- 拦截规则 -->
<mvc:mapping path="/my/**" />
<!-- 不拦截规则 -->
<mvc:exclude-mapping path="/resource/**" />
<!-- 拦截器处理类 -->
<bean class="com.wy.interceptor.MyInterceptor" />
</mvc:interceptor>
<!-- 2管理员中心拦截器 -->
<mvc:interceptor>
<!-- 拦截规则 -->
<mvc:mapping path="/admin/**" />
<!-- 不拦截规则 -->
<mvc:exclude-mapping path="/resource/**" />
<!-- 拦截器处理类 -->
<bean class="com.wy.interceptor.AdminInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
现在我们可以运行一下项目,会发现个人中心、管理员两个模块均需要登录,不过这里存在一个功能盲点,就是这个方式跳转的登录由于不是load方式因此长度参照物成了整个浏览器,导致文本框被拉的很长,大家可以自己扩展一下,提供两种思路,一是开发一个新的用于登录的页面,二是使用文档函数把配合js判断当前页面的状态并控制长度,效果可以参考github官网登录那样
现在我们就可以做下一步了,首先在首页的详情页面添加收藏用的按钮以及评论的展示
<!-- 收藏文章 -->
<div align="right">
<c:if test="${isCollect==0 || isCollect==null}">
<a href="javascript:collect()">☆ 收藏</a>
</c:if>
<c:if test="${isCollect==1}">
<span class="text-danger">★ 已收藏</span>
</c:if>
</div>
<!-- 增加评论 -->
<c:if test="${null!=sessionScope.user}">
<div>
输入评论:
<textarea rows="8" cols="110" name="content"></textarea>
<br>
<button class="btn btn-info" onclick="addComment()">提交评论</button>
</div>
</c:if>
<!-- 显示评论 -->
<div>
<c:forEach items="${info.list}" var="comment">
${comment.user.username } <fmt:formatDate
value="${comment.created}" pattern="yyyy-MM-dd HH:mm:ss" />
<br>
<p class="mt-3">${comment.content }</p>
<hr>
</c:forEach>
</div>
此时我们就需要先该改一下,详情页跳转的Controller,我们要查询出当前文章是否被当前用户收藏,以及当前文章的评论,因此我们要准备写评论、收藏Bean的Dao和Service
首先开发数据库中两张表的实体Bean,没有数据库环境的见知识点1
,第一个是收藏Bean
package com.wy.bean;
import java.io.Serializable;
import java.util.Date;
/**
*
* @ClassName: Collect
* @Description: 文章的收藏
* @author: charles
* @date: 2020年2月15日 上午8:41:22
*/
public class Collect implements Serializable{
/**
* @fieldName: serialVersionUID
* @fieldType: long
* @Description: TODO
*/
private static final long serialVersionUID = 1L;
private Integer id;
private String text;//文章的标题
private String url;//文章的url
private Integer userId;//收藏人ID
private User user;//收藏人
private Date created;//收藏时间
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 getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((userId == null) ? 0 : userId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Collect other = (Collect) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (userId == null) {
if (other.userId != null)
return false;
} else if (!userId.equals(other.userId))
return false;
return true;
}
}
随后是评论Bean
package com.wy.bean;
import java.io.Serializable;
import java.util.Date;
/**
*
* @ClassName: Comment
* @Description: 评论表
* @author: charles
* @date: 2020年3月13日 上午11:32:22
*/
public class Comment implements Serializable {
/**
* @fieldName: serialVersionUID
* @fieldType: long
* @Description: TODO
*/
private static final long serialVersionUID = 1L;
private Integer id;
private Integer userId;//用户id
private Integer articleId;//文章id
private String content;//评论
private Date created;//时间
private User user;
private Article article;
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 Integer getArticleId() {
return articleId;
}
public void setArticleId(Integer articleId) {
this.articleId = articleId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Article getArticle() {
return article;
}
public void setArticle(Article article) {
this.article = article;
}
}
有了Bean,我们就要开发对应的Dao和Service层,同样第一个是收藏Dao
package com.wy.dao;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.wy.bean.Collect;
/**
*
* @ClassName: CollectMapper
* @Description: 文章收藏
* @author: charles
* @date: 2020年4月11日 上午9:03:51
*/
public interface CollectMapper {
/**
*
* @Title: insert
* @Description: 增加
* @param collect
* @return
* @return: int
*/
int insert(Collect collect);
/**
*
* @Title: selects
* @Description: 查询
* @param collect
* @return
* @return: List<Collect>
*/
List<Collect> selects(Collect collect);
/**
*
* @Title: selectCount
* @Description: 查询注册用户是否收藏text的文章
* @param text
* @param userId
* @return
* @return: int 1:已收藏 0:未收藏
*/
int selectCount(@Param("text") String text,@Param("userId")Integer userId);
/**
*
* @Title: deleteCollect
* @Description: 根据ID删除收藏
* @param id
* @return
* @return: int
*/
int deleteCollect(Integer id);
}
以及同步的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.wy.dao.CollectMapper">
<select id="selectCount" resultType="int">
select count(id) from cms_collect where user_id=#{userId} and text =#{text}
</select>
<insert id="insert">
insert into cms_collect (text,user_id,url,created)
values(#{text},#{userId},#{url},#{created})
</insert>
<select id="selects" resultType="com.wy.bean.Collect">
select * from cms_collect
<where>
<if test="userId!=null">
user_id =#{userId}
</if>
</where>
order by created desc
</select>
<delete id="deleteCollect">
delete from cms_collect where id =#{id}
</delete>
</mapper>
第二个是评论的Dao
package com.wy.dao;
import java.util.List;
import com.wy.bean.Comment;
/**
*
* @ClassName: CommentMapper
* @Description: 评论
* @author: charles
* @date: 2020年4月11日 上午11:25:04
*/
public interface CommentMapper {
/**
*
* @Title: insert
* @Description: 增加评论
* @param comment
* @return
* @return: int
*/
int insert(Comment comment);
/**
*
* @Title: selects
* @Description: 根据文章id 查询评论
* @param articleId
* @return
* @return: List<Comment>
*/
List<Comment> selects(Integer articleId);
/**
*
* @Title: updateAritlce
* @Description: 增加文章的评论数量
* @param id
* @return
* @return: int
*/
int updateAritlce(Integer id);
}
以及同步的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.wy.dao.CommentMapper">
<insert id="insert">
insert into
cms_comment(user_id,article_id,content,created)
values(#{userId},#{articleId},#{content},#{created})
</insert>
<resultMap type="com.wy.bean.Comment" id="commentResultMap">
<id column="id" property="id" />
<result column="user_id" property="userId" />
<result column="article_id" property="articleId" />
<result column="content" property="content" />
<result column="created" property="created" />
<!-- 评论人 -->
<association property="user" javaType="com.wy.bean.User"
select="selectById" column="user_id"></association>
</resultMap>
<select id="selects" resultMap="commentResultMap">
select * from cms_comment where article_id=#{articleId}
order by created desc
</select>
<select id="selectById" resultType="com.wy.bean.User">
select * from cms_user where id=#{id}
</select>
<!-- 让文章的评论数+1 -->
<update id="updateAritlce">
update cms_article set comment_num =comment_num +1 where id =#{id}
</update>
</mapper>
然后就是收藏的Service
package com.wy.service;
import org.apache.ibatis.annotations.Param;
import com.github.pagehelper.PageInfo;
import com.wy.bean.Collect;
public interface CollectService {
/**
*
* @Title: deleteCollect
* @Description: 根据ID删除收藏
* @param id
* @return
* @return: int
*/
int deleteCollect(Integer id);
/**
*
* @Title: insert
* @Description: 增加
* @param collect
* @return
* @return: int
*/
boolean insert(Collect collect);
/**
*
* @Title: selects
* @Description: 查询
* @param collect
* @return
* @return: List<Collect>
*/
PageInfo<Collect> selects(Collect collect,Integer page,Integer pageSize);
/**
*
* @Title: selectCount
* @Description: 查询注册用户是否收藏text的文章
* @param text
* @param userId
* @return
* @return: int 1:已收藏 0:未收藏
*/
int selectCount(@Param("text") String text,@Param("userId")Integer userId);
}
以及对应的实现类
package com.wy.service;
import java.util.List;
import javax.annotation.Resource;
import com.wy.utils.StringUtil;
import org.springframework.stereotype.Service;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.wy.bean.Collect;
import com.wy.dao.CollectMapper;
import com.wy.utils.CMSException;
@Service
public class CollectServiceImpl implements CollectService {
@Resource
private CollectMapper collectMapper;
@Override
public boolean insert(Collect collect) {
if(!StringUtil.isHttpUrl(collect.getUrl()))
throw new CMSException("url 不合法");
return collectMapper.insert(collect) >0;
}
@Override
public PageInfo<Collect> selects(Collect collect, Integer page, Integer pageSize) {
PageHelper.startPage(page, pageSize);
List<Collect> selects = collectMapper.selects(collect);
return new PageInfo<Collect>(selects);
}
@Override
public int selectCount(String text, Integer userId) {
// TODO Auto-generated method stub
return collectMapper.selectCount(text, userId);
}
@Override
public int deleteCollect(Integer id) {
// TODO Auto-generated method stub
return collectMapper.deleteCollect(id);
}
}
最后是评论的Service
package com.wy.service;
import java.util.List;
import com.github.pagehelper.PageInfo;
import com.wy.bean.Comment;
public interface CommentService {
/**
*
* @Title: insert
* @Description: 增加评论
* @param comment
* @return
* @return: int
*/
int insert(Comment comment);
/**
*
* @Title: selects
* @Description: 根据文章id 查询评论
* @param articleId
* @return
* @return: List<Comment>
*/
PageInfo<Comment> selects(Integer articleId,Integer page,Integer pageSize);
}
以及实现类
package com.wy.service;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.wy.bean.Comment;
import com.wy.dao.CommentMapper;
@Service
public class CommentServiceImpl implements CommentService{
@Resource
private CommentMapper commentMapper;
@Override
public int insert(Comment comment) {
try {
commentMapper.insert(comment);//增加评论
commentMapper.updateAritlce(comment.getArticleId());//让评论数量增加1
return 1;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("评论失败");
}
}
@Override
public PageInfo<Comment> selects(Integer articleId, Integer page, Integer pageSize) {
PageHelper.startPage(page, pageSize);
List<Comment> list = commentMapper.selects(articleId);
return new PageInfo<Comment>(list);
}
}
有了Dao和Service,我们就需要在首页的Controller中注入他们
@Resource
private CollectService collectService;//收藏
@Resource
private CommentService commentService;//评论
注入后我们就可以更改首页查询详情的Controller了,在原先的基础上,查询出是否收藏文章以及文章评论
/**
*
* @Title: detail
* @Description: 文章详情
* @param id
* @return
* @return: String
*/
@RequestMapping("detail.do")
public String detail(Model model, Integer id, HttpSession session, @RequestParam(defaultValue="1")Integer page) {
//查询文章
Article article = articleService.select(id);
model.addAttribute("article", article);
//查询文章是否被当前用户收藏
// 前提:如果用户已经登录则查询是否收藏
User user=(User) session.getAttribute("user");
if (null != user) {
int isCollect = collectService.selectCount(article.getTitle(), user.getId());
model.addAttribute("isCollect", isCollect);
}
//查询评论
PageInfo<Comment> info = commentService.selects(id, page, 5);
model.addAttribute("info", info);
return "index/article";
}
现在我们既可运行项目看一下首页详情的效果。注意详情页没有绑定事件呢,正常应该没有动态效果
,就是说页面有东西,但没有实际效果。
现在我们在个人中心Controller中准备收藏和评论的请求接收
@Resource
private CollectService collectService;//收藏
@Resource
private CommentService commentService;//评论
/**
*
* @Title: collect
* @Description: 收藏文章
* @return
* @return: boolean
*/
@ResponseBody
@RequestMapping("collect.do")
public boolean collect(Collect collect, HttpSession session) {
User user = (User) session.getAttribute("user");
if (user != null) {// 如果已经登录则执行收藏
collect.setUserId(user.getId());// 收藏人
collect.setCreated(new Date());// 收藏时间
return collectService.insert(collect);
}
return false;// 没有登录则不执行收藏
}
/**
*
* @Title: collect
* @Description: 评论文章
* @return
* @return: boolean
*/
@ResponseBody
@RequestMapping("addComment.do")
public boolean addComment(Comment comment, HttpSession session) {
User user = (User) session.getAttribute("user");
if (user != null) {// 如果已经登录则才能评论
comment.setUserId(user.getId());// 评论人
comment.setCreated(new Date());// 收藏时间
return commentService.insert(comment) >0;
}
return false;// 没有登录则不能评论
}
最后还要给首页详情页绑定JS事件
<script type="text/javascript">
//收藏
function collect() {
var text = '${article.title}';//获取文章标题
var url = window.location.href;//获取文章的url
$.post("/my/collect.do", {
text : text,
url : url
}, function(flag) {
if (flag) {
alert("收藏成功");
window.location.reload();
} else {
alert("收藏失败,请登录后再试")
}
})
}
//增加评论
function addComment() {
var content = $("[name='content']").val();
var articleId = '${article.id}';
$.post("/my/addComment.do", {
content : content,
articleId : articleId
}, function(flag) {
if (flag) {
alert("评论成功");
window.location.reload();
} else {
alert("评论失败。请登录后重试")
}
})
}
</script>
最后运行出效果
没有登录是不展示评论的
但是此时当你点击收藏时会有一个bug,就是在没有登录的情况下会显示收藏成功,但是再次刷新页面的时候页面任然正常,这是因为收藏和评论的Controller被拦截器拦截了,可这两个本身有有着是否有Session的判断,后端的数据就是安全的,但前端收不到返回结果,导致程序混乱直接运行了为真的代码,把两个Controller的拦截器释放就行正常了
回复正常后,我们测试登录后执行收藏、评论
到此首页优化完成,至于个人首页中的我的收藏、我的评论
留给大家扩展,因为这两个功能核心点太简单了就是查对应的列表展示,最多再多做一个删除评论和取消收藏,所以这两个小功能大家自己做吧,再往后的知识点就该到ES那些重点组件了,本项目目前以上传github :https://github.com/wangyang159/cmsdemo