实现一个博客系统----基于前后端分离

1)我们在java目录里面存放后端的Servlet代码,在webapp里面存放前端的代码
2)服务器渲染:所有的前端动态页面都是由服务器来进行动态生成的,就非常需要模板引擎;
优势:省事,浏览器拿到的就是一个完整的页面,不需要和服务器之间进行多次交互
缺点:没有进行完全分离,前后端耦合比较强,前端要写要关心后端代码;
3)前后端分离:静态页面和动态页面都是由浏览器进行渲染的,服务器只是给浏览器提供一些数据,然后浏览器会结合当前拿到的页面和服务器返回的数据进行综合分析,进行封装,构造前端页面
优势:前端和后端进行完全分离,只需要约定好交互接口就行了
劣势:浏览器和服务器之间可能要进行多次的数据交互,每次走的是HTTP,开销较大,比较吃带宽(合理设计来进行简化)(可以通过一些,尤其是大型项目,前后端分离十分必要,沟通成本太高;
1)比如说一个项目的实现,每一次模块的实现都是给基于前后端分离来进行实现的,如果不进行处理,每一次访问页面的特定功能加载都会特别卡,页面加载时间就会取决于最慢的那次请求;
2)如果这个板块进行折叠起来,就不去进行获取数据,当用户点击这个模块展开的时候,才会进行发送Http请求给服务器;
实现一个博客系统----基于前后端分离
文章图片

1)当浏览器发送数据给服务器的时候,会先进行访问入口服务器, 入口服务器与应用服务器之间相当于是基于前后端分离的方式来进行的,一个入口服务器可能会从多个应用服务器哪里来进行获取数据;
2)入口服务器会根据在多个应用服务器那边拿到的业务数据进行汇总,基于模板引擎的方式来进行渲染出页面,这样的页面渲染效果也是基于前端来进行实现的,然后浏览器会给服务器返回一个html字符串,在浏览器上面就可以进行显示了;
3)缺点:NodeJs技术还不够成熟,整体方案实施和部署比较复杂,门槛会更高
1.创建maven项目 2.创建目录结构并引入依赖
1)在main目录里面创建webapp目录(与Java目录是同级目录),再从webapp目录里面创建WEB-INF目录,创建web.xml文件
2)改pom.xml,我们要用到Servlet(3.1.0),MYSQL(5.1.47),Tymleaf,Jackson(2.13.0),Ajax(前端代码)
3.打包部署基于SmartTomact来进行 4.我们可以把服务器渲染的一些可以进行重用的代码给进行拷贝过来
1)数据库操作,数据库在两个版本的操作代码实现都是一样的,直接拷贝过来就可以了
2)前端代码,把之前写的纯前端页面给进行拷贝过来,全部拷贝到webapp目录里面
实现一个博客系统----基于前后端分离
文章图片

1)实现博客列表页
2)实现博客详情页
3)实现博客登录页
4)实现博客登陆检查
5)实现显示用户信息
6)实现注销功能
7)实现发布博客
8)实现删除博客
5. 实现博客列表页
1)让我们当前的这个博客列表页面,给服务器来进行发送一个ajax这样的请求,服务器进行处理这个请求,就会从数据库里面查询到博客列表,把数据以Json的形式来返回给浏览器
2)我们在让浏览器根据发送过来的Json数据,拼接HTML,得到最终的页面;所以我们要约定好前后端进行交互的接口,请求是啥样子的,响应是啥样子的,客户端和服务器就可以按照约定好的请求来进行开发
请求:GET/blog1Servlet
响应:Http/1.1 200 OK
Content-Type=application/json; charset=utf-8;
JSON格式的数据如下 {{ blogID:1,content:TCP/IP,title:ABC,postTime:2022/12/2/10:25,userID:1,}{ blogID:2,content:TCP/UDP,title:ABCDEFGHIGK,postTime:2023/1/1/23:09,userID:1, } }

1)我们在这里面客户端就要按照这个约定,来进行构造请求,服务器也需要按照这个约定,来进行解析请求,构造响应,此处的前后端交互接口的约定,可以有很多种不同的交互方式;
请求方法不用GET,用POST,都是可以;
2)Jackson会根据每个Blog成员字段的名字来进行构造Json格式的字符串,我们举一个例子,Blog里面有blogID这个属性,blogID这一个变量名,就作为Json数据中的Key,blogID这个对象的值,就作为Json数据的Value;
3)总而言之Json里面的Key的名字和实体类里面的属性名字要保持一致;
4)一个Blog对象对应的就是一个Json对象,一个List得到的就是一个Json数组;
5)发现了当前的预期的时间是一个格式化时间,当前得到的是一个毫秒级的时间戳,这个问题,我们可以在服务器这边把时间戳转化成格式化的时间SimpleDataFormat类可以进行负责时间日期的格式化转换,这里我们在前端进行写一个函数来进行处理,把时间戳转换成时间格式化;
服务器代码:
import MYSQL.Blog; import MYSQL.OperateBlog; import com.fasterxml.jackson.databind.ObjectMapper; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.sql.SQLException; import java.util.List; @WebServlet("/blog1Servlet") public class blog1Servlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("application/json; charset=utf-8"); ObjectMapper mapper=new ObjectMapper(); OperateBlog operateBlog=new OperateBlog(); //1.从数据库里面查询数据 Listlist = null; try { list = operateBlog.SelectAll(); } catch (SQLException e) { e.printStackTrace(); } //2把list中的Blog转成json数据数组返回到响应数组里面 //String jsonstring=mapper.writeValueAsString(list); //resp.getWriter().write(jsonstring); mapper.writeValue(resp.getWriter(),list); } }

客户端代码:
1)此时回调函数的function中的data参数表示响应的body,status就表示相应的状态码描述
2)我们要根据相应的body内容来进行构造出一个HTML片段,此时我们预期的data不是一个String数组,而是一个json数组,这一点是因为服务器返回的数据类型,application/json;

1.我们可能遇到这种情况,可能说博客内容太多导致博客溢出,这时我们就需要加上overflow:auto; 表明内容溢出是加上滚动条;
2.首先我们在网络上输入blog1.html,会首先给服务器发送一个GET请求,想要访问博客列表页,服务器会将blog1.html进行返回
3.浏览器解析html,执行JS代码,发送AJAX(这里的路径和WebServlet的路径要相同)请求,服务器再把博客列表数据以JSON的格式将博客列表数据进行返回
4浏览器在拿到博客列表数据之后,会把页面的内容给构造完整,就不会向模板渲染那样,直接从服务器那边拿到完整的数据了;
2.实现博客详情页
1)前后端交互接口
请求:a.href="https://www.it610.com/article/blog2.html?blogID="+message.blogID;
1.1真正的请求是在博客列表页进行发送,当用户点击查看全文,先进行跳转到博客详情页,这个过程浏览器先给服务器发送了一个GET请求,请求得到blog2.html这个界面
1.2浏览器进行解析这个代码,执行到

3.实现博客登录页
一:当我们来写前后端分离的项目的时候,虽然在大部分情况下,浏览器和服务器的交互是通过JSON来进行完成的,但是也是可以通过form表单的方式来进行交互
二:我们在这里面实现的登陆界面的逻辑和之前是一样的,不需要通过ajax来向服务器进行获取数据,只需要让服务器来进行处理一下登录请求即可1)前后端交互的接口,在前端我们直接通过form表单提供的POST请求就可以直接传递用户名和密码了,我们还要个输入框的哪个标签加上name属性
请求格式 POST/login Content-Type:application/x-www-form-urlencodedname=admin&password=123 响应格式:登陆成功,直接重定向到博客列表页 HTTP/1.1 302 Location:blog1.html

但是服务器这边的代码,和我们之前写的登陆代码是相同的
登录
用户名密码

WebServlet("/login") public class loginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1先获取到请求中的参数包括用户名和密码 resp.setContentType("text/html; charset=utf-8"); req.setCharacterEncoding("utf-8"); String username=req.getParameter("username"); String password=req.getParameter("password"); System.out.println(username); System.out.println(password); if(username==null||username.equals("")||password==null||password.equals("")) {String html="当前的用户名错误或者密码错误
"; resp.getWriter().write(html); return; } //2获取到参数之后,我们再进行判断当前密码是否正确OperateUser operateUser=new OperateUser(); User user=null; try { user=operateUser.selectByName(username); } catch (SQLException e) { e.printStackTrace(); } if(user==null) { String html="当前的用户名或者密码错误
"; resp.getWriter().write(html); return; } if(!user.getPassword().equals(password)) { String html="当前的用户名或者密码错误
"; resp.getWriter().write(html); return; } //3代码走到了这里,说明用户登陆成功了,咱们就可以具体进行设置HttpSession回话了 HttpSession httpSession=req.getSession(true); httpSession.setAttribute("user",user); resp.sendRedirect("blog1.html"); } }

4.实现登陆检测---我们让博客列表页和博客登录页都让其在登陆状态下进行检测(检测httpsession中的user是否为空);
1)我们需要让博客列表页和博客详情页都能够在登陆状态下进行使用,如果用户没有进行登录,那么就直接重定向到博客列表页
2)我们先会进行访问博客列表页,当我们进行点击查看全文的时候,会发送一个ajax,当服务再进行处理ajax请求的时候,会进行检测用户是否登录,并会进行检测用户的登录状态
3)让前端页面来获取到这个登陆状态,如果没有进行登录,就直接跳转到登录页面;
4)实现服务器渲染的方式检测登陆状态是让服务器之间进行重定向,但是我们当前实现的前后端分离的方式是依靠前端代码来进行完成重定向操作;
5)那么我们该如何让客户端知道当前用户是出于未登录状态呢?
例如,我们可以通过状态码来进行设置,正常访问的时候,状态码是200,但是如果当前处于未登录的状态,我们就直接返回一个403的状态码;此时浏览器就会根据这个403的状态码来进行处理了;
如果说得到的状态码是200,那么我们接下来就继续进行渲染页面,如果说状态码是403
我们直接跳转到登陆界面;
后端代码:
resp.setContentType("application/json; utf-8"); HttpSession httpSession=req.getSession(false); if(httpSession==null||httpSession.equals("")) { resp.setStatus(403); String html="当前用户没有进行登录
"; resp.getWriter().write(html); return; } User user=(User)httpSession.getAttribute("user"); if(user==null||user.equals("")) { resp.setStatus(403); String html="当前用户没有进行登录
"; resp.getWriter().write(html); return; }

1)这时候服务器会给浏览器发送一个状态码为403,但是我们看之前写的AJAX代码就发现,里面有一个请求成功的回调函数success:function(data,status),但是在这里面服务器响应失败,这就不能算是响应成功,我们就要使用其他的函数
2)error,是一个请求失败的时候自动进行调用的回调函数,相当于我们在请求中的ajax里面加上了一个error这样的属性;
error:function(data,status) { console.log("进入到error函数:"+status); //此时我们就可以进行页面的重定向跳转即可 //这个操作就是在前端实现重定向 location.assign("blog3.html"); }


5.显示用户的登录信息和作者的信息(我们需要在博客列表页和博客详情页分别要发送两个AJAX来进行获取数据)
1)实现博客列表页的用户身份信息(HttpSession中的user对象就是当前用户的登陆者)
首先我们要约定好前后端进行交互的格式
请求:GET/User
响应:又是一个Json格式的字符串
获取到当前的登陆的用户的信息,这个要在博客列表页使用 { userID:1, name:"zhangsan", password:"12503487", }

2)实现显示博客详情页的作者的身份信息(根据传过来的BlogID来进行获取到当前当前文章的作者)
1)请求:GET/User?blogID=1,2,3,4......
响应:和上边的格式一样:
基于上述两种请求,我们是写一个Servlet来进行实现的,如果进行获取到的BlogID为空,那么这个请求说明就是博客列表页发送过来的请求;
如果获取到的BlogID不为空,那么就说明这个请求就是博客详情页发送过来的请求;我们基于if else语句来进行处理两份代码;
2)后端代码我们要写一份,但是前端代码我们要分别在博客列表页和博客详情页来进行编写

客户端的前端代码是相同的
@WebServlet("/user") public class UserWriteServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //先进行检测用户是否进行了登录 HttpSession session=req.getSession(false); if(session==null||session.equals("")) { resp.sendRedirect("blog3.html"); return; } User user=(User)session.getAttribute("user"); if(user==null||user.equals("")) { resp.sendRedirect("blog3.html"); return; } //获取请求中的参数 resp.setContentType("application/json; charset=utf-8"); String blogID=req.getParameter("blogID"); ObjectMapper mapper=new ObjectMapper(); if(blogID==null||blogID.equals("")) { //处理博客列表页发送过来的请求 String userreturn=mapper.writeValueAsString(user); resp.getWriter().write(userreturn); }else{ //处理博客详情页发送过来的请求 OperateUser operateUser=new OperateUser(); OperateBlog operateBlog=new OperateBlog(); //先根据BlogID来进行从数据库中获取到Blog,再根据blog中的userID来进行获取到User对象 Blog blog=null; User user1=null; try { blog=operateBlog.SelectOne(Integer.parseInt(blogID)); user1=operateUser.selectByUserID(blog.getUserID()); } catch (SQLException e) { e.printStackTrace(); } String userreturn= mapper.writeValueAsString(blog); resp.getWriter().write(userreturn); } } }

我们在这里面又发现了一个问题,我们进入博客列表页或者博客详情页的时候,坐上角的名字有一个快速闪烁的过程,这个名字,默认的是一个名字,但是随着响应的到来,里面的内容就进行了替换;Ajax的响应是非常慢的----删除里面的内容
6.实现注销功能
约定好前后端交互接口:
请求:GET/deleteuser
响应:Http/1.1 302
location:blog3.html
后端需要做的事情就是把session中的user对象给删了
@WebServlet("/deleteuser") public class DeleteUserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/htmk; charset=utf-8"); HttpSession httpSession=req.getSession(false); if(httpSession.equals("")||httpSession==null) { System.out.println("当前用户没有进行登录"); resp.sendRedirect("blog3.html"); return; } User user=(User)httpSession.getAttribute("user"); if(user==null||user.equals("")) { System.out.println("当前用户没有进行登录"); resp.sendRedirect("blog3.html"); return; } httpSession.removeAttribute("user"); resp.sendRedirect("blog3.html"); } }

7.实现发布博客
约定前后端进行交互的接口
请求:
POST/sendblog
Content-Type:application/x-www-form-urlencoded
title=xxxxxxx&content=XXXXX
响应:
Http/1.1 302
location:blog1.html
前端代码加上form表单,name属性,提交按钮;
@WebServlet("/sendblog") public class sendblog extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //先进行检测用户是否进行了登录 HttpSession session=req.getSession(false); if(session==null||session.equals("")) { resp.sendRedirect("blog3.html"); return; } User user=(User)session.getAttribute("user"); if(user==null||user.equals("")) { resp.sendRedirect("blog3.html"); return; } resp.setContentType("text/html; charset=utf-8"); req.setCharacterEncoding("utf-8"); String title=req.getParameter("title"); String content=req.getParameter("content"); if(title==null||title.equals("")||content.equals("")||content==null) { resp.getWriter().write("输入的文章标题和文章内容是有缺失的
"); return; } //构造Blog对象插入到数据库中 Blog blog=new Blog(); blog.setPostTime(new Timestamp(System.currentTimeMillis())); blog.setTitle(title); blog.setContent(content); blog.setUserID(user.getUserID()); OperateBlog operateBlog=new OperateBlog(); try { operateBlog.insert(blog); resp.sendRedirect("blog1.html"); } catch (SQLException e) { e.printStackTrace(); }} }

//首先要初始化编译器 //先创建出一个textinner对象 //editormd相当于是一个函数,生成一个editor对象 //第一个参数是要指定放到哪一个html标签中,元一个html标签关联,参数就是对应的ID; var editor=editormd("editor", { //这里的尺寸必须在这里设置,不能依靠CSS进行设置 width:"100%", // 这是计算makdown正文的高度 height:"1000px", //编辑器的初始内容 markdown:"#在这里写一下第一篇博客", //编译器所用的插件所在的路径 path:"editor.md/lib/", saveHTMLToTextArea:true//把markdown把里面的内容放到textarea里面 });

8.实现删除博客
1)我们需要在博客详情页中的导航栏里面加一个删除按钮,如果说当前的登陆的用户是文章作者,那么就会删除按钮显示,否则就会被隐藏
2)点击删除按钮,发送一个HTTP请求,服务器调用数据库,进行删除博客;
3)我们在博客详情页中发送AJAX请求进行删除,在服务器返回的Json字段中加上isYourBlog属性(在请求获取文章信息的时候的AJAX),如果是1的话那么就是你自己写的博客,如果是0的话就说明不是自己写的博客,这个属性就决定了是否显示这个按钮;
第一个请求:
{ userID:1, name:"zhangsan", password:"12503487", isYourBlog:1 }

第二个接口:使用Http的Delete方法(触发了a标签的GET请求)
DELETE/deleteblog?blogID=1;
我们在服务器使用Http的DELETE方法来进行决定是否删除博客;
响应:
Http/1.1 200
1)先向User对象中加入isYourBlog这个字段,提供Set和Get方法
private int isYourBlog; public int getIsYourBlog() { return isYourBlog; } public void setIsYourBlog(int isYourBlog) { this.isYourBlog = isYourBlog; }

2)再进行修改博客详情页的代码
前端:
if(data.isYourBlog==1) { //说明当前的登录用户和文章作者是同一个人 let nav=document.querySelector(".nav"); let a=document.createElement("a"); a.target="_blank"; a.innerHTML="删除"; a.href="https://www.it610.com/article/#"; nav.appendChild(a); a.onclick=function(){ $.ajax({ method:"DELETE", url:"deleteblog"+location.search, success:function(data,status) { //如果删除成功,就让页面重定向到博客列表页 console.log(1111111); location.assign("blog1.html"); } });


后端:修改请求获取到作者的Servlet代码
user1.setIsYourBlog(user.equals(user1)?0:1);

@WebServlet("/user") public class UserWriteServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //先进行检测用户是否进行了登录 HttpSession session=req.getSession(false); if(session==null||session.equals("")) { resp.sendRedirect("blog3.html"); return; } User user=(User)session.getAttribute("user"); if(user==null||user.equals("")) { resp.sendRedirect("blog3.html"); return; } //获取请求中的参数 resp.setContentType("application/json; charset=utf-8"); String blogID=req.getParameter("blogID"); ObjectMapper mapper=new ObjectMapper(); if(blogID==null||blogID.equals("")) { //处理博客列表页发送过来的请求 String userreturn=mapper.writeValueAsString(user); resp.getWriter().write(userreturn); }else{ //处理博客详情页发送过来的请求 OperateUser operateUser=new OperateUser(); OperateBlog operateBlog=new OperateBlog(); //先根据BlogID来进行从数据库中获取到Blog,再根据blog中的userID来进行获取到User对象 Blog blog=null; User user1=null; try { blog=operateBlog.SelectOne(Integer.parseInt(blogID)); user1=operateUser.selectByUserID(blog.getUserID()); user1.setIsYourBlog(user.equals(user1)?0:1); } catch (SQLException e) { e.printStackTrace(); } String userreturn= mapper.writeValueAsString(user1); resp.getWriter().write(userreturn); } } }

1)user1表示在博客详情页根据blogID查询到的文章作者信息,user是根据Session中设置的键值对的得到的User对象,这里进行比较他们是不是相同,相同返回1,不相同返回0;
2)a标签只能触发GET请求,所以我们给a标签设置了一个点击回调函数,通过ajax给服务器发送DELETE请求;









【实现一个博客系统----基于前后端分离】

    推荐阅读