目录
一、准备工作
1.1 基本环境的搭建
主要分为7个步骤:
创建maven项目、引入依赖、创建目录结构、写一个测试代码、打包、部署、验证
具体操作见博客Lesson9:Tomcat 和 Servlet(基础篇)_刘减减的博客-CSDN博客中的第三小节。
1.2 对博客系统的分析
主要包含三个部分的逻辑:
①处理数据库的部分 M(Model)
②处理前端请求的部分 C(controller)
③前端展示的细节部分 V(View)
这三个部分称为MVC,是一个通用的概念,可以使用该框架开发web程序。
二、数据库
2.1 数据库设计
主要包含四个部分的数据:博客数据、用户数据、评论数据、问题数据
2.2 封装数据库相关操作并验证
2.2.1 创建相关的类
分别创建Blog、User、Comment、Question四个类,类中的成员变量和数据库中保持一致,生成成员变量的get、set和toString方法。以创建Blog类为例:
public class Blog {
private int blogId;
private String blogTitle;
private String blogContent;
private Timestamp blogTime;
private int blogAuthor;
private int visitCount;
private int commentCount;
public int getVisitCount() {
return visitCount;
}
public void setVisitCount(int visitCount) {
this.visitCount = visitCount;
}
public int getCommentCount() {
return commentCount;
}
public void setCommentCount(int commentCount) {
this.commentCount = commentCount;
}
public int getBlogId() {
return blogId;
}
public void setBlogId(int blogId) {
this.blogId = blogId;
}
public String getBlogTitle() {
return blogTitle;
}
public void setBlogTitle(String blogTitle) {
this.blogTitle = blogTitle;
}
public String getBlogContent() {
return blogContent;
}
public void setBlogContent(String blogContent) {
this.blogContent = blogContent;
}
public Timestamp getBlogTime() {
return blogTime;
}
public void setBlogTime(Timestamp blogTime) {
this.blogTime = blogTime;
}
public int getBlogAuthor() {
return blogAuthor;
}
public void setBlogAuthor(int blogAuthor) {
this.blogAuthor = blogAuthor;
}
@Override
public String toString() {
return "Blog{" +
"blogId=" + blogId +
", blogTitle='" + blogTitle + '\'' +
", blogContent='" + blogContent + '\'' +
", blogTime=" + blogTime +
", blogAuthor=" + blogAuthor +
", visitCount=" + visitCount +
", commentCount=" + commentCount +
'}';
}
}
2.2.2 封装数据库的相关操作
DBUtil:封装数据库的连接
BlogDao:实现对博客的增删改查。
insert方法:增加一个博客
delete方法:删除一个博客
selectOne方法:根据博客Id查找博客。用户博客详情页。
selectAll方法:查找所有的博客。用户博客列表页的展示。
updateBlog方法:修改博客的内容和标题。用于博客修改页。
updateVisistCount:更新博客的访问次数。在用户的信息卡片上展示。
updateCommentCount:更新博客的评论次数。在用户的信息卡片上展示。
selectVisitAllCount:根据用户Id查找用户的总访问次数。在用户的信息卡片上展示。
selectVisitCount:根据博客Id查找博客的总访问次数。在用户的信息卡片上展示。
main方法:测试以上方法是否正确
UserDao:实现用户信息的增删改查
login:注册。注意在注册十,只需要输入用户名和密码,用户的其他属性可以通过其他方法增加。
getBlogCount:根据用户Id,得到用户总的博客数量。用户信息卡片的展示会用到。
selectByName:根据用户名 查找用户。登录时使用的是用户名密码登录,因此需要实现该功能。
uploadImages:上传头像。用户可以修改自己的头像。
changeBlogCount:修改用户的博客数量。新增一篇博客时会用到该方法。
deleteUser:删除用户。
main方法:测试以上方法是否正确
CommentDao:实现评论的增加、删除和选择。
insert:新增一个评论。
delete:删除一个评论。
selelctAll:查找该博客的所有评论。
QuestionDao:实现问题的增加、删除和选择。
insert:新增一个问题。
delete:删除一个问题。
selelctAll:查找所有问题。
selelctOne:根据问题ID查找问题。主要用户回答页面的使用。
以上的方法都是类似的。只展示DButil和BlogDao的代码,其他代码可以在这个基础上修改得到。
public class DBUtil {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/BlogSystem?characterEncoding=utf8&useSSL=false"; // BlogSystem是数据库的名称
private static final String username = "root";
private static final String password = "1111"; // 输入数据库的密码
private static DataSource dataSource = new MysqlDataSource();
static {
((MysqlDataSource)dataSource).setURL(URL);
((MysqlDataSource)dataSource).setUser(username);
((MysqlDataSource)dataSource).setPassword(password);
}
public Connection getConnectin() throws SQLException {
return dataSource.getConnection();
}
public void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(connection == null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
package Model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class BlogDao {
public static void insert(Blog blog){
DBUtil dbUtil = new DBUtil();
Connection connection = null;
PreparedStatement statement = null;
try {
connection = dbUtil.getConnectin();
String sql = "insert into blog (blogId,blogTitle,blogContent,blogTime,blogAuthor) values(null,?,?,?,?)";
statement = connection.prepareStatement(sql);
statement.setString(1,blog.getBlogTitle());
statement.setString(2,blog.getBlogContent());
statement.setTimestamp(3,blog.getBlogTime());
statement.setInt(4,blog.getBlogAuthor());
int ret = statement.executeUpdate();
if(ret == 1){
UserDao userDao = new UserDao();
int blogcount = userDao.getBlogCount(blog.getBlogAuthor());
System.out.println(blogcount);
blogcount +=1;
userDao.changeBlogCount(blogcount,blog.getBlogAuthor());
System.out.println("插入成功");
}else{
System.out.println("插入失败");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dbUtil.close(connection,statement,null);
}
}
public static void delete(int blogId){
DBUtil dbUtil = new DBUtil();
Connection connection = null;
PreparedStatement statement = null;
try {
connection = dbUtil.getConnectin();
String sql = "delete from blog where blogId=?";
statement = connection.prepareStatement(sql);
statement.setInt(1,blogId);
int ret = statement.executeUpdate();
if(ret == 1){
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dbUtil.close(connection,statement,null);
}
}
public static Blog selectOne(int blogId){
DBUtil dbUtil = new DBUtil();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
Blog blog = new Blog();
try {
connection = dbUtil.getConnectin();
String sql = "select * from blog where blogId=?";
statement = connection.prepareStatement(sql);
statement.setInt(1,blogId);
resultSet = statement.executeQuery();
if(resultSet.next()){
blog.setBlogTitle(resultSet.getString("blogTitle"));
blog.setBlogTime(resultSet.getTimestamp("blogTime"));
blog.setBlogContent(resultSet.getString("blogContent"));
blog.setBlogAuthor(resultSet.getInt("blogAuthor"));
blog.setBlogId(resultSet.getInt("blogId"));
blog.setCommentCount(resultSet.getInt("commentCount"));
blog.setVisitCount(resultSet.getInt("visitCount"));
return blog;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dbUtil.close(connection,statement,resultSet);
}
return null;
}
public static List<Blog> selectAll(){
DBUtil dbUtil = new DBUtil();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
List<Blog> blogs = new ArrayList<>();
try {
connection = dbUtil.getConnectin();
String sql = "select * from blog order by blogTime desc";
statement = connection.prepareStatement(sql);
resultSet = statement.executeQuery();
while (resultSet.next()){
Blog blog = new Blog();
blog.setBlogTitle(resultSet.getString("blogTitle"));
blog.setBlogTime(resultSet.getTimestamp("blogTime"));
String content = resultSet.getString("blogContent");
if(content.length() > 100){
content = content.substring(0,100) + "...";
System.out.println(100);
}
blog.setBlogContent(content);
blog.setBlogAuthor(resultSet.getInt("blogAuthor"));
blog.setBlogId(resultSet.getInt("blogId"));
blog.setCommentCount(resultSet.getInt("commentCount"));
blog.setVisitCount(resultSet.getInt("visitCount"));
blogs.add(blog);
}
return blogs;
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dbUtil.close(connection,statement,resultSet);
}
return null;
}
public static void updateBlog(String blogTitle, String blogContent, int blogId){
DBUtil dbUtil = new DBUtil();
Connection connection = null;
PreparedStatement statement = null;
try {
connection = dbUtil.getConnectin();
String sql = "update blog set blogTitle = ?,blogContent=? where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setString(1,blogTitle);
statement.setString(2,blogContent);
statement.setInt(3,blogId);
int ret = statement.executeUpdate();
if(ret == 1){
System.out.println("修改成功");
}else{
System.out.println("修改失败");
}
return;
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dbUtil.close(connection,statement,null);
}
}
public static void updateVisitCount(int count,int blogId){
DBUtil dbUtil = new DBUtil();
Connection connection = null;
PreparedStatement statement = null;
try {
connection = dbUtil.getConnectin();
String sql = "update blog set visitCount = ? where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,count);
statement.setInt(2,blogId);
int ret = statement.executeUpdate();
if(ret == 1){
System.out.println("博客访问次数修改成功");
}else{
System.out.println("博客访问次数修改失败");
}
return;
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dbUtil.close(connection,statement,null);
}
}
public static void updateCommentCount(int count,int blogId){
DBUtil dbUtil = new DBUtil();
Connection connection = null;
PreparedStatement statement = null;
try {
connection = dbUtil.getConnectin();
String sql = "update blog set commentCount = ? where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,count);
statement.setInt(2,blogId);
int ret = statement.executeUpdate();
if(ret == 1){
System.out.println("评论次数修改成功");
}else{
System.out.println("评论次数修改失败");
}
return;
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dbUtil.close(connection,statement,null);
}
}
public static int selectVisitAllCount(int userId){
DBUtil dbUtil = new DBUtil();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
int count = 0;
try {
connection = dbUtil.getConnectin();
String sql = "select sum(visitCount) from blog where blogAuthor = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,userId);
resultSet = statement.executeQuery();
if(resultSet.next()){
count = resultSet.getInt("sum(visitCount)");
return count;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dbUtil.close(connection,statement,null);
}
return 0;
}
public static int selectVisitCount(int blogId){
DBUtil dbUtil = new DBUtil();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
int count = 0;
try {
connection = dbUtil.getConnectin();
String sql = "select visitCount from blog where blogId = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1,blogId);
resultSet = statement.executeQuery();
if(resultSet.next()){
count = resultSet.getInt("visitCount");
return count;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dbUtil.close(connection,statement,null);
}
return 0;
}
三、Thymeleaf的相关操作
3.1 Thymeleaf初始化的优化
如果我们按照博客Lesson11:模板引擎_刘减减的博客-CSDN博客中的操作实现博客列表页,就会在博客列表页中创建一个TemplateEngine实例,并且进行初始化操作。现在如果我们要在其他的页面,如博客详情页中使用TemplateEngine,就没法使用之前创建好的TemplateEngine对象。解决方案有以下几种:
①如果其他页面需要用到TemplateEngine,就再创建TemplateEngine实例,并再初始化一遍。
评价:可行但是不建议采用。TemplateEngine和数据库中的DataSource类似,只需要一份实例即可。如果搞了多份,每份TemplateEngine实例都要加载模板,会多消耗一份内存。会导致同一份模板数据,在数据库中存在了多份。
②能否把TemplateEngine做成一个单例呢?
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(getServletContext());
resolver.setPrefix("/WEB-INF/template/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("utf-8");
从TemplateEngine的初始化代码可以看到,初始化必须用到ServletContext对象。这个对象又只能在Servlet的环境下来获取到,即将上面的初始化代码,随便在servlet的某个方法里都好写。但是放在一个和Servlet无关的单独的类中,都不好写。因此这种方法并不推荐。
③借助Servlet中自身关于ServletContext的机制
ServletContext是上下文对象,每个webapp都有一个上下文对象 。可以让一个webapp内部的servlet之间共享同一个ServletContext。这样就可以依据ServletContext来进行多个Servlet之间的数据共享操作。基于这个机制,我们可以创建好一个TemplateEngine,保存到ServletContext中,这样,所有的servlet都可以使用这个TemplateEngine。
具体的解决方案:
在ServletContext被Tomcat创建好了之后,就立刻进行初始化TemplateEngine,并且把engine保存到ServletContext里面。servlet可以拿到ServletContext,进一步就可以拿到engine。
我们怎样才能捕获到Tomcat创建ServletContext呢?
使用Servlet的监听器可以允许程序员监听某些动作,当动作被Tomcat触发的时候,可以执行程序员自己写的代码。此时我们要监听的就是Tomcat创建ServletContext这个动作。
// 通过这个注解,Tomcat才能认识这是一个监听器对象
@WebListener
public class ThymeLeafListener implements ServletContextListener {
// servletContext被创建时触发
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
// 1.创建一个模板引擎对象
TemplateEngine engine = new TemplateEngine();
// 2.创建一个ServletContextTemplateResolver对象,功能就是从磁盘上加载模板引擎文件
// 这里需要改一下获取方式
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(servletContextEvent.getServletContext());
//3对resolver对象设置一些属性 加载 /WEB-INF/template/ 目录中, 以 .html 结尾的文件, 作为模板引擎
resolver.setPrefix("/WEB-INF/template/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("utf-8");
// 4 把resolver和engine关联起来
engine.setTemplateResolver(resolver);
// 5.将创建好的engine放到servletContext里面
servletContextEvent.getServletContext().setAttribute("engine",engine);
System.out.println("Themeleaf初始化完毕");
}
// ServletContext被销毁前触发 ,暂时不需要写
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
注意:@WebListener这个注解一定要写。
3.2 编写模板文件
step1:把之前写的静态页面,全都拷贝到webapp目录中
step2:修改html文件,制作成Thymeleaf的模板文件
将需要动态替换的部分,使用占位符占个位置。
step3:新建目录结构,将刚刚制作好的模板文件移到该目录下。具体操作见博客
Lesson11:模板引擎_刘减减的博客-CSDN博客的3.3小节。
四、实现登录页面、强制登录功能和注册页面
4.1 实现登录页面
登录页面是一个纯静态的页面,不需要通过servlet来渲染。登录页面的实现流程见博客Lesson10:ServletAPI详解(HttpServlet、HttpServletRequest、HttpServletResponse)_刘减减的博客-CSDN博客的第4.5小节。
@WebServlet("/login")
public class loginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username == null || "".equals(username) || password == null || "".equals(password)){
resp.getWriter().write("<h3>输入不合法</h3>");
return;
}
User user = UserDao.selectByName(username);
if(user == null){
resp.getWriter().write("<h3>用户名或者密码错误</h3>");
return;
}
boolean result = user.getPassword().equals(password);
if(!result){
resp.getWriter().write("<h3>用户名或者密码错误</h3>");
return;
}
HttpSession session = req.getSession(true);
session.setAttribute("user",user); // 将用户保存在session中
resp.sendRedirect("bloglist.html"); // 登录成功后,跳转到博客列表页
}
}
4.2 实现强制登录功能
我们约定,只有用户登录后,才能进入到博客系统中,访问到博客系统的各个页面。如果不登陆,直接尝试访问博客系统中的某一个页面,就会自动的跳转到登录页面。
如何实现呢?我们可以在进入代码入口的时候,先判定一下当前是否在已经登录的状态。这个功能可以通过session机制实现,在登录的时候 将user信息保存在session中,然后从session中读取user,如果可以读取到,就证明已经登录过了,执行正常的页面渲染。如果未读取到,则是未登录状态,就跳转到登录页面。由于每个页面都会用到强制登录功能,因此将其封装在一个类中。
public class Util {
public static User checkLogin(HttpServletRequest req){
HttpSession session = req.getSession(false);
if(session == null){
return null;
}
User user = (User) session.getAttribute("user");
if(user == null){
return null;
}
return user;
}
}
4.3 实现注册页面
注册页面的前端实现部分只需要在登录页面上进行微调就可以。因此再次不做赘述。注册页面的实现逻辑:
点击登录页面上的注册——》跳转到注册页面——》输入用户名和两次密码——》点击注册,触发一个POST请求——》后端进行处理
后端拿到用户名和密码后:先从请求中读出用户名和密码——》判断输入是否合法——》在数据库中查询该用户是否已经存在——》不存在,判断两次密码输入是否一致——》一致就往数据库中的user表中插入该数据。
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
if(username == null || "".equals(username) || password == null || "".equals(password)){
resp.getWriter().write("<h3>输入不合法</h3>");
return;
}
User user = UserDao.selectByName(username);
if(user == null){
resp.getWriter().write("<h3>用户名或者密码错误</h3>");
return;
}
boolean result = user.getPassword().equals(password);
if(!result){
resp.getWriter().write("<h3>用户名或者密码错误</h3>");
return;
}
HttpSession session = req.getSession(true);
session.setAttribute("user",user); // 将用户保存在session中
resp.sendRedirect("bloglist.html"); // 登录成功后,跳转到博客列表页
}
}
五、实现博客详情页
5.1 博客详情展示
实现流程:先判断是否登录——》从请求中读到blogId——》判断blogId的合法性——》根据blogId去数据库查询博客——》查询出来之后就更新博客的访问次数。
注意,此时信息卡片页显示的是博客作者的信息,根据博客的作者姓名在用户表里查到博客作者的信息。
@WebServlet("/blogdetail.html")
public class DetailServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType ("text/html;charset=utf-8");
// loginuser 表示当前登录的用户
User Loginuser = Util.checkLogin(req);
if(Loginuser == null){
resp.sendRedirect("login.html");
return;
}
String blogId = req.getParameter("blogId");
HttpSession session = req.getSession(false);
session.setAttribute("blogId",blogId);
if(blogId == null || "".equals(blogId)){
resp.getWriter().write("<h3>blogId不存在</h3>");
return;
}
Blog blog = BlogDao.selectOne(Integer.parseInt(blogId));
if(blog == null){
resp.getWriter().write("<h3>查询的博客不存在</h3>");
return;
}else{
// 此页的信息卡片需要显示作者的信息,根据博客的作者姓名在用户表里查到博客作者的信息
User BlogUser = UserDao.selectOne(blog.getBlogAuthor());
if(BlogUser == null){
resp.getWriter().write("<h3>查询的博客作者不存在</h3>");
return;
}
// 从评论表中查询出所有的博客
List<Comment> comments = CommentDao.selectAll(Integer.parseInt(blogId));
ServletContext servletContext = getServletContext();
TemplateEngine engine = (TemplateEngine) servletContext.getAttribute("engine");
WebContext webContext = new WebContext(req,resp,servletContext);
// 如果读出的评论不为空,就显示评论
if(comments != null){
System.out.println(comments);
webContext.setVariable("comments",comments);
}
// 进入到博客的详情页,该博客的访问量需要+1,先读出访问量,+1后,再写回到数据库
int visitCount = blog.getVisitCount();
// System.out.println("访问前的次数"+visitCount);
visitCount +=1;
BlogDao.updateVisitCount(visitCount,Integer.parseInt(blogId));
// Blog blog2 = BlogDao.selectOne(Integer.parseInt(blogId));
// int visitCount2 = blog2.getVisitCount();
// System.out.println("访问后的次数"+visitCount2);
webContext.setVariable("blog",blog);
webContext.setVariable("bloguser",BlogUser);
// System.out.println("登录用户"+Loginuser);
// 在评论时,显示的是登录用户的信息
webContext.setVariable("loginuser",Loginuser);
int count = BlogDao.selectVisitCount(Integer.parseInt(blogId));
webContext.setVariable("visitCount",count);
// 只有登录的用户和博客作者是同一个人,才能删除博客、修改博客,为了避免发生错误,只有是同一个人才显示这两个按钮
webContext.setVariable("showDeleteButton",Loginuser.getUserID()==blog.getBlogAuthor());
webContext.setVariable("showAmendButton",Loginuser.getUserID()==blog.getBlogAuthor());
engine.process("blogdetail",webContext, resp.getWriter());
}
}
}
5.2 评论的显示
实现流程和博客列表页的实现流程类似,在此不做赘述。
5.3 评论功能
先判断是否登录——》从请求中读到评论内容——》判断评论内容的合法性——》从session中读到blogId和user——》构造comment对象——》插入comment数据库——》根据blogID去blog中查询出被评论的博客,更新博客的评论次数。
@WebServlet("/comment")
public class CommentServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
User user = Util.checkLogin(req);
if(user == null){
resp.sendRedirect("login.html");
return;
}
Comment comment = new Comment();
String commentContent = req.getParameter("comment");
System.out.println("从req中读到的评论内容"+commentContent);
if(commentContent == null || "".equals(commentContent)){
resp.getWriter().write("输入的评论不能为空");
return;
}
HttpSession session = req.getSession(false);
String blogId = (String) session.getAttribute("blogId");
// System.out.println("从session中读到的blogId" + blogId);
comment.setCommentContent(commentContent);
comment.setCommentTime(new Timestamp(System.currentTimeMillis()));
comment.setCommentUser(user.getUserName());
comment.setBlogId(Integer.parseInt(blogId));
// System.out.println(user.getUserPicture());
comment.setCommentUserImage(user.getUserPicture());
// System.out.println("登录用户的图片地址"+ user.getUserPicture());
// System.out.println(comment);
// comment表中新增一个数据后,评论数量+1
CommentDao.insert(comment);
Blog blog = BlogDao.selectOne(Integer.parseInt(blogId));
// System.out.println("找出来的blog"+blog);
int commentCount = blog.getCommentCount();
// System.out.println("commentCount更新前" + commentCount);
commentCount = commentCount + 1;
BlogDao.updateCommentCount(commentCount,Integer.parseInt(blogId));
// Blog blog2 = BlogDao.selectOne(Integer.parseInt(blogId));
// int commentCount2 = blog2.getCommentCount();
// System.out.println(blog2);
// commentCount += 1;
// System.out.println("commentCount更新后" + commentCount2);
resp.sendRedirect("blogdetail.html?blogId=" + blogId);
}
}
5.4 提问功能
提问功能的实现流程和评论功能类似,再此不做赘述。
问题提交后,会触发一个post请求。在servlet中处理post请求主要是将请求中的问题内容读出来并插入到数据库中。同时,问题提交后会跳转到问题展示页。此时会触发一个get请求,servlet在处理get请求时主要是显示所有的问题。
@WebServlet("/question.html")
public class QuestionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
// loginuser 表示当前登录的用户
User Loginuser = Util.checkLogin(req);
if(Loginuser == null){
resp.sendRedirect("login.html");
return;
}
// String blogId = req.getParameter("blogId");
List<Question> questions = QuestionDao.selectAll();
if(questions != null){
ServletContext servletContext = getServletContext();
TemplateEngine engine = (TemplateEngine) servletContext.getAttribute("engine");
WebContext webContext = new WebContext(req,resp,servletContext);
webContext.setVariable("questions",questions);
engine.process("question",webContext, resp.getWriter());
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
// System.out.println("进入到questionServlet的post请求");
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
User user = Util.checkLogin(req);
if(user == null){
resp.sendRedirect("KnowledgeList_list.html");
return;
}
Question question = new Question();
String commentContent = req.getParameter("comment");
// System.out.println("从req中读到的问题内容"+commentContent);
if(commentContent == null || "".equals(commentContent)){
resp.getWriter().write("输入的问题不能为空");
return;
}
HttpSession session = req.getSession(false);
String blogId = (String) session.getAttribute("blogId");
// System.out.println("从session中读到的blogId" + blogId);
question.setQuestionContent(commentContent);
question.setUserId(user.getUserID());
question.setBlogId(Integer.parseInt(blogId));
QuestionDao.insert(question);
resp.sendRedirect("question.html");
}
}
5.5 问题删除功能
实现流程和删除博客类似,再此不做赘述。
5.6 注意
①commentservlet是博客评论功能构建的form表单触发的get请求,在列表页的servlet代码的session中加入blogID,这样在commentservlet中就能知道被评论的博客是谁了,为更新博客的评论次数做铺垫。
②此时登录的用户就是评价的用户。所以评论中博客的相关用户信息就是登录用户的相关信息。
③注意,博客详情页涉及到两个用户,一个是登录用户,一个是博客作者。只有登录的用户和博客作者是同一个人,才能删除博客、修改博客。为了保险起见,只有是同一个人才显示删除博客和修改博客这两个按钮。
④一个input输入框会对应两个按钮,此时不能使用form标签了。可以用函数来实现。
⑤如何将页面按照markdown的形式显示。
借助editor.md的内置功能,
<script>
// 先从html中拿到要渲染的字符串
var markdown = document.querySelector("#content").innerHTML;
// 清空原来的div
document.querySelector("#content").innerHTML = "";
// 通过内置的方式完成渲染,要想能够调用, 也需要先引入 editor.md 的 js
editormd.markdownToHTML('content',{markdown:markdown})
</script>
5.7 前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./css/common.css">
<link rel="stylesheet" href="./css/detail.css">
<link rel="stylesheet" href="./editor.md/css/editormd.css" />
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="./editor.md/lib/marked.min.js"></script>
<script src="./editor.md/lib/prettify.min.js"></script>
<script src="./editor.md/editormd.js"></script>
</head>
<body>
<!-- 所有的页面的导航栏都相同-->
<div class="nav">
<img src="pictures/logo.jpg" alt="图标">
<span>我的博客系统</span>
<span id="spacer"></span>
<a href="KnowledgeList_list.html">主页</a>
<a href="KnowledgeList_edit.html">写博客</a>
<a th:if = "${showDeleteButton}" th:href="${'deleteBlog?blogId=' + blog.blogId}">删除博客</a>
<a th:if = "${showAmendButton}" th:href="${'amendBlog?blogId=' + blog.blogId}">修改博客</a>
<a href="question.html">问答区</a>
<a href="logout" id="logout">注销</a>
</div>
<div class="container">
<div class="left">
<div class="card">
<img th:src="${bloguser.userPicture}" alt="头像">
<!-- <img src="./pictures/piyo.jpg" alt="头像">-->
<h3 th:text = "${bloguser.userName}"></h3>
<a href="#">GitHub地址</a>
<div class="count">
<span>文章</span>
<span>访问量</span>
</div>
<div class="count">
<span th:text = ${bloguser.blogCount}></span>
<span th:text = ${visitCount}></span>
</div>
</div>
<div class="comment">
<div class="commentedit">
<div><img th:src="${loginuser.userPicture}" alt="头像"></div>
<form name="form" method="post">
<input type="textarea" name="comment" id="comment" placeholder="请发表有价值的评论,博客评论不欢迎灌水,良好的社区氛围需大家一起维护。" >
<div class="button">
<input type="button" value="提交评论" id="button1" onclick="addComment()">
<input type="button" value="提交问题" id="button1" onclick="addQuestion()">
</div>
</form>
</div>
<div class="commentList">
<div class="commentInfo" th:each = "comment: ${comments}">
<div class="commentListLeft">
<img th:src="${comment.commentUserImage}" alt="用户头像">
<!-- <img src="./pictures/piyo.jpg" alt="用户头像">-->
</div>
<div class="commentListRight">
<div class="commentListUserInfo">
<span th:text = "${comment.commentUser}"></span>
<span th:text = "${comment.commentTime}"></span>
</div>
<div class="commentListdetail">
<span th:text = "${comment.commentContent}"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="right">
<div class="essay">
<div class="title" th:text = "${blog.blogTitle}"></div>
<div class="date" th:text = "${blog.blogTime}"></div>
<div class="content" th:text = "${blog.blogContent}" style="background-color:transparent" id="content"></div>
</div>
</div>
</div>
<script>
// 先从html中拿到要渲染的字符串
var markdown = document.querySelector("#content").innerHTML;
// 清空原来的div
document.querySelector("#content").innerHTML = "";
// 通过内置的方式完成渲染,要想能够调用, 也需要先引入 editor.md 的 js
editormd.markdownToHTML('content',{markdown:markdown})
</script>
<script type="text/javascript">
function addComment(){
document.form.action="comment";//提交的url
document.form.submit();
}
function addQuestion(){
document.form.action="question.html";//提交的url
document.form.submit();
}
</script>
</body>
</html>
六、博客删除页
先判断是否登录——》从请求中读取博客Id——》判断blogId是否为空——》去数据库中查询是否存在——》存在就删除——》跳转到博客列表页。
@WebServlet("/deleteBlog")
public class DeleteBlogServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
User user = Util.checkLogin(req);
if(user == null){
resp.sendRedirect("login.html");
return;
}
String blogId = req.getParameter("blogId");
if(blogId == null){
resp.getWriter().write("<h3>blogId不存在</h3>");
return;
}
Blog blog = BlogDao.selectOne(Integer.parseInt(blogId));
if(blog == null){
resp.getWriter().write("<h3>您要删除的博客不存在</h3>");
return;
}
BlogDao.delete(Integer.parseInt(blogId));
resp.sendRedirect("bloglist.html");
}
}
七、实现博客编辑页
7.1 前端页面的设计
①加入form表单,点击提交按钮可以触发一个HTTP请求。
②需要提交一个标题和正文,标题已经是一个input标签了,正文可以在editor.md这个第三方库中进行编辑,editor.md也支持form表单,具体设置为:
先在博客编辑区的div中放一个隐藏的text area。再在初始化editor对象的时候,指定一个特殊的选项saveHTMLToTextArea:true,这个选项的功能就是editor.md把用户编辑的正文放到这个text area里面。
<div class="container">
<div class="container-edit">
<!-- 包含两个部分,标题编辑区和内容编辑区 -->
<form action="edit" method="post" style="height: 100%">
<div class="container-edit-title">
<input type="text" id="title" autocomplete="off" placeholder="在这里输入标题" name="title">
<!-- <input type="button" value="发布文章" > -->
<input type="submit" value="发布文章" class="submit-button">
</div>
<div id="editor" >
<textarea name="content" style="display: none"></textarea>
</div>
</form>
</div>
</div>
<script>
// 初始化 editor.md
var editor = editormd("editor", {
// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
width: "1000px",
// 设定编辑器高度
height: "calc(100% - 60px)",
// 编辑器中的初始内容
markdown: "# 在这里写下一篇博客",
// 指定 editor.md 依赖的插件路径
path: "./editor.md/lib/",
saveHTMLToTextArea:true
});
</script>
7.2 后端实现逻辑
先判断是否登录——》从请求中读去博客标题和博客内容——》判断博客标题和博客内容是否合法——》构造博客对象并插入到数据库中——》跳转到博客列表页。
@WebServlet("/edit")
public class EditServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
//1. 判断是否登录
User user = Util.checkLogin(req);
if(user == null){
resp.sendRedirect("login.html");
return;
}
// 2.从rep中拿到title和content,并校验
String title = req.getParameter("title");
// System.out.println(title);
String content = req.getParameter("content");
// System.out.println(content);
if(title == null || "".equals(title) || content == null || "".equals(content)){
resp.getWriter().write("<h3>标题或者正文缺失</h3>");
return;
}
//3.构建一个blog,插入到数据库中
Blog blog = new Blog();
blog.setBlogContent(content);
blog.setBlogTitle(title);
blog.setBlogTime(new Timestamp(System.currentTimeMillis()));
blog.setBlogAuthor(user.getUserID());
BlogDao blogDao = new BlogDao();
blogDao.insert(blog);
//4.跳转到博客列表页
resp.sendRedirect("bloglist.html");
}
}
八、实现博客修改页
和博客列表页的前端页面类似,只不过博客的标题和内容是从数据库中读出来的。可以先用thymeleaf进行占位,将从数据库中读到的数据再给渲染进去。
<div class="container">
<div class="container-edit">
<!-- 包含两个部分,标题编辑区和内容编辑区 -->
<form action="amendBlog" method="post" style="height: 100%">
<div class="container-edit-title">
<input type="text" id="title" autocomplete="off" th:value="${blog.blogTitle}" name="title">
<!-- <input type="button" value="发布文章" > -->
<input type="submit" value="确认修改" class="submit-button">
</div>
<div id="editor" >
<textarea name="content" style="display: none" th:text = "${blog.blogContent}"></textarea>
</div>
</form>
</div>
</div>
<script th:inline="javascript">
// 初始化 editor.md
let content = /*[[${blog.blogContent}]]*/ "123";
var editor = editormd("editor", {
// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
width: "1000px",
// 设定编辑器高度
height: "calc(100% - 60px)",
// 编辑器中的初始内容
markdown: content,
// 指定 editor.md 依赖的插件路径
path: "./editor.md/lib/",
saveHTMLToTextArea:true
});
</script>
当跳转到博客修改页,会触发一个get请求,在处理这个get请求时需要将要修改的博客的标题的内容返回。
当修改完成点击提交按钮,会触发一个post请求,对该请求的处理流程为:先判断是否登录——》从请求中读取博客标题和博客内容——》判断博客标题和博客内容是否合法——》更新博客——》跳转到博客详情页,跳转时需要带上博客的id。
@WebServlet("/amendBlog")
public class AmendServlet extends HttpServlet {
public static int blogId = 0;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
User user = Util.checkLogin(req);
if(user == null){
resp.sendRedirect("login.html");
return;
}
String blogID = req.getParameter("blogId");
if(blogID == null){
resp.getWriter().write("<h3>blogId不存在</h3>");
return;
}
blogId = Integer.parseInt(blogID);
Blog blog = BlogDao.selectOne(blogId);
if(blog == null){
resp.getWriter().write("<h3>您要修改的博客不存在</h3>");
return;
}
System.out.println("要修改的博客:"+ blog);
ServletContext servletContext = getServletContext();
TemplateEngine engine = (TemplateEngine) servletContext.getAttribute("engine");
WebContext webContext = new WebContext(req,resp,servletContext);
webContext.setVariable("blog",blog);
engine.process("blog_amend.html",webContext, resp.getWriter());
// resp.sendRedirect("blog_amend.html");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
//1. 判断是否登录
User user = Util.checkLogin(req);
if(user == null){
resp.sendRedirect("KnowledgeList_login.html");
return;
}
// 2.从rep中拿到title和content,并校验
String title = req.getParameter("title");
// System.out.println(title);
String content = req.getParameter("content");
// System.out.println(content);
if(title == null || "".equals(title) || content == null || "".equals(content)){
resp.getWriter().write("<h3>标题或者正文缺失</h3>");
return;
}
BlogDao.updateBlog(title,content,blogId);
resp.sendRedirect("blogdetail.html?blogId="+blogId);
}
}
九、实现上传头像功能
先创建一个images文件夹,将用户的头像保存到这个文件夹里。
判断是否登录——》获取到images所在的磁盘路径——》获取到图片的part对象——》将头像存放在images目录中,并以用户名命名——》跳转到登录页面。
此时用户session中的user信息是没有头像属性的,会导致上传的头像无法正常显示出来。用户重新登录后,才能更新session中的user信息,此时才能将头像正常显示出来。
@MultipartConfig
@WebServlet("/imageUpload")
public class UploadImageServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
User user = Util.checkLogin(req);
if(user == null){
resp.sendRedirect("login.html");
return;
}
String username = user.getUserName();
System.out.println("用户的名字"+ username);
// 获取到image所在的磁盘路径
String path = getServletContext().getRealPath("/images");
path = path + "/" + username + ".jpg";
// System.out.println(path);
// 获取到图片的Part对象
Part part = req.getPart("image");
// 获取到图片名字
//String name = part.getSubmittedFileName();
//System.out.println(name);
// 把图片放在指定目录中
String path2 = "./images/"+ username + ".jpg";
part.write(path);
UserDao userDao = new UserDao();
userDao.uploadImages(username,path2);
resp.sendRedirect("login.html");
}
}
十、实现问题回答页
10.1 前端页面设计
前端页面的设计参考Lesson6:JS的WEBAPI实例练习_刘减减的博客-CSDN博客
中4.2TodoList的设计。此时约定:点击查看答案,博客的内容才会显示出来。使用display这个参数来控制,读出display的属性,如果display=none,点击后就将display=block。如果此时display=block,点击后就将display=none。可以用一个函数来实现这个功能。
<div class="container1" id="container">
<div class="left">
<h3>答题区</h3>
<input type="textarea" name="answer" id="answer" width="200px" height="200px" >
</div>
<div class="right">
<input type="button" value="点击查看答案" id="answerbutton">
<span th:text = "${blog.blogContent}" style="display: none;" id="contentspan"></span>
</div>
</div>
<script>
let button = document.querySelector("#answerbutton");
button.onclick = function(){
let span = document.querySelector("#contentspan");
if(span.style.display == "none"){
span.style.display = "block";
}else {
span.style.display = "none";
}
}
</script>
10.2 后端设计
点击回答按钮,会触发一个get请求,对该请求的处理流程为:先判断是否登录——》从请求中读取问题ID——》判断问题id是否合法——》根据问题ID在问题数据库中查找问题——》如果查找到问题,根据问题中的博客ID属性在博客数据库中找出博客信息——》将博客信息渲染到页面中。
@WebServlet("/answer.html")
public class AnswerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
req.setCharacterEncoding("utf-8");
User user = Util.checkLogin(req);
if(user == null){
resp.sendRedirect("login.html");
return;
}
String questionId = req.getParameter("questionId");
// String blogId = req.getParameter("blogId");
if(questionId == null){
resp.getWriter().write("<h3>您要回答的问题不存在</h3>");
}
// if(blogId == null){
// resp.getWriter().write("<h3>您要回答的问题相关的博客不存在</h3>");
// }
Question question = new Question();
question = QuestionDao.selectOne(Integer.parseInt(questionId));
if(question == null){
resp.getWriter().write("<h3>您要回答的问题相关的博客没有查询到</h3>");
}
Blog blog = BlogDao.selectOne(question.getBlogId());
if(blog == null){
resp.getWriter().write("<h3>您要回答的问题相关的博客没有查询到</h3>");
}
WebContext webContext = new WebContext(req,resp,getServletContext());
TemplateEngine engine = (TemplateEngine) getServletContext().getAttribute("engine");
webContext.setVariable("blog",blog);
engine.process("answer",webContext, resp.getWriter());
}
}
十一、实现注销功能
实现流程:判断是否登录——》获取会话session——》从session中删除user字段——》跳转到登录页面。
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
User user = Util.checkLogin(req);
if(user == null){
resp.getWriter().write("<h3>未登录</h3>");
resp.sendRedirect("login.html");
}
HttpSession session = req.getSession();
session.removeAttribute("user");
resp.sendRedirect("login.html");
}
}
十二、实现博客列表页
12.1 实现流程
通过服务器代码,查询数据库,再将用户的信息和博客列表展示在页面上。此处需要用到Thymeleaf。
12.2 生成模板
将需要动态替换的信息使用th进行占位。博客列表的显示需要用到循环,具体步骤为:
①去掉原来页面多余的div,只保留一个页面的div。
②通过th:each的方式,根据服务器查询到包含所有博客信息的博客数组,来循环构建出这里的博客div。
③根据数据库中查询出的每个博客,将博客的标题、时间、博客内容信息,显示到页面上。
④没必要将博客正文的所有内容都显示出来,可以只显示一部分,增加一个查看全文按钮。此时,需要将blogId给拼接到a标签的url中,这时才能够知道需要从服务器中获取哪个博客。
12.3 博客的内容太多,超过设置的背景
给背景加上个css属性:overflow:auto 如果当前元素的内容溢出,就会自动加个滚动条。此时指的是给标签加上滚动条,而不是给页面加上滚动条。
<div class="container">
<div class="left">
<div class="card">
<img th:src="${user.userPicture}" alt="头像">
<!-- <img src="./pictures/piyo.jpg" alt="头像">-->
<h3 th:text = "${user.userName}"></h3>
<a th:href = "${user.userLink}">GitHub地址</a>
<div class="count">
<span>文章</span>
<span>总访问量</span>
</div>
<div class="count">
<span th:text = ${user.blogCount}></span>
<span th:text = ${visitCount}></span>
</div>
</div>
</div>
<div class="right">
<div class="essay" th:each = "blog: ${blogs}">
<div class="title" th:text = "${blog.blogTitle}"></div>
<div class="date" th:text = "${blog.blogTime}"></div>
<div class="content" th:text = "${blog.blogContent}"> </div>
<a th:href="${'KnowledgeList_detail.html?blogId=' + blog.blogId }" class="detail">查看全文>></a>
</div>
</div>
</div>
12.4 完成Servlet的代码,实现整个页面的渲染
@WebServlet("/bloglist.html")
public class ListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset = utf-8");
User user = Util.checkLogin(req);
if(user == null){
resp.sendRedirect("login.html");
return;
}
// 从数据库中读出所有的博客
List<Blog> blogs = BlogDao.selectAll();
// 在登录页面时已经将用户的信息保存到session中,在强制登录方法中从session中读到登录信息,返回一个User
int visitCount = BlogDao.selectVisitAllCount(user.getUserID());
WebContext webContext = new WebContext(req,resp,getServletContext());
TemplateEngine engine = (TemplateEngine) getServletContext().getAttribute("engine");
webContext.setVariable("blogs",blogs);
webContext.setVariable("user",user);
webContext.setVariable("visitCount",visitCount);
engine.process("bloglist",webContext, resp.getWriter());
}
}
如果需要源代码,可以在评论区留言呀!!!