基于前后端分离的博客系统 (Servlet)(中)

简介: 基于前后端分离的博客系统 (Servlet)(中)

六、实现页面



1. 博客列表页


(1) 作用:博客列表页主要用来展示所有博客的摘要

(2) 约定 GET 请求 的路径:" /BlogList "(前端通过 ajax 这种方式来构造请求)


思想:先按照纯前端代码创建相应的节点,之后再挂在 DOM 树上。


这里需要注意一个点: 为什么这里的节点需要先创建,再往 DOM 树上插入呢?难道直接进行innerHTML 这个方法进行替换不行吗?答案是否定的,因为一旦进行替换,只能更换固定几行代码,而这里是要展示所有的博客摘要,这需要对应着纯前端代码,一个一个创建出来。


<script src="js/jquery.min.js"></script>
<script>
  $.ajax({
      url: 'BlogList',
      method: 'GET',
      success: function(data, status) {
          // 思想: 先按照纯前端代码创建相应的节点, 之后再挂在 DOM 树上
          let blogs = data;
          let rightDiv = document.querySelector('.type_area .right');
          for (let blog of blogs) {
              let blogDiv = document.createElement('div');
              blogDiv.className = 'blog';
              // 博客标题
              let titleH3 = document.createElement('h3');
              titleH3.innerHTML = blog.title;
              titleH3.className = 'title';
              // 博客格式化日期 (年月日-时分秒)
              let dateDiv = document.createElement('div');
              dateDiv.innerHTML =  formatDate(blog.postTime); // 此处的 postTime 原先是一个毫米级的时间戳, 应该转换成格式化的日期
              dateDiv.className = 'date';
              // 博客摘要
              let abstractDiv = document.createElement('abstract');
              abstractDiv.innerHTML = blog.content; // 这里在服务器端已经将截取了字符串
              abstractDiv.className = 'abstract';
              // 跳转博客内容页的链接
              let linkA = document.createElement('a');
              linkA.innerHTML = '查看全文 &gt;&gt';
              linkA.href = 'blog_content.html?blogID=' + blog.blogID;
              // 最后,将所有准备好的节点挂在 DOM 树上
              blogDiv.appendChild(titleH3);
              blogDiv.appendChild(dateDiv);
              blogDiv.appendChild(abstractDiv);
              blogDiv.appendChild(linkA);
              // 最后最后一步, 将 blogDiv 这样的一个一个 blog 盒子挂在博客展示栏上 
              rightDiv.appendChild(blogDiv);
          }        
      }
  })
<script>


(3) 针对服务器端代码:创建一个 BlogListServlet 来处理计算响应,在此类中,我们为 HTTP 响应的正文 body 写入 json 格式的数据。


@WebServlet("/BlogList")
public class BlogListServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置 【写入 HTTP 响应的正文 body 格式】为 json 类型
        resp.setContentType("application/json; charset=UTF-8");
        BlogDB blogDB = new BlogDB();
        List<Blog> blogList = blogDB.searchAll();
        // 将 Java 对象转换成一个 json 格式的类型
        String jsonData = objectMapper.writeValueAsString(blogList);
        resp.getWriter().write(jsonData);
    }
}


抓包结果:


可以发现,json 中的内容形式实际上是以一种 json 数组的形式呈现出来的。


ed84c851b5c9478dad2cb8d81488c3ee.png


(4) 通过字符串截取的方式,来控制页面为用户展示的博客字数

(5) 由于页面的内容过长,这就可能导致溢出页面,或者说,内容溢出版心,此时,我们就可以通过设置 CSS 文件来弥补这一缺点。


如下代码:当内容溢出页面的时候,就会自动设置滑动栏,这很实用。


overflow: auto;


2. 博客内容页


(1) 作用:博客内容页用来展示文章的所有信息

(2) 约定 GET 请求 的路径:" /BlogContent "(前端通过 ajax 这种方式来构造请求)


思想:直接按照前面的纯前端代码,通过 innerHTML 进行替换即可。


这里需要注意一个点: 这里与博客列表页 ajax 中的 success 回调函数不同,由于这里只需要展示一篇博客,所以直接使用 innerHTML 这样的操作,就可以对标签值进行替换。这给人一种模板渲染的感觉,只不过,这里的实现方式都是需要通过 前端的 JavaScript 代码进行完成的,而模板引擎需要前后端配合才行。


<!-- 基于 ajax 的方式来从服务器中获取数据 -->
<script src="js/jquery.min.js"></script>
<!-- 当前的 ajax 请求是期望展示单篇博客的内容 -->
<script>
  $.ajax({
      url: 'BlogContent' + location.search,
      method: 'GET',
      success: function(data, status) {
          let blog = data;
          // 博客标题
          let titleH3 = document.querySelector('.blog .title');
          titleH3.innerHTML = blog.title;
          // 博客格式化日期
          let dateDiv = document.querySelector('.blog .date');
          dateDiv.innerHTML = formatDate(blog.postTime);
          // 单篇博客的内容
          let contentDiv = document.querySelector('.blog .content');
          contentDiv.innerHTML = blog.content;   
      }
  })
<script>


注意:

关于 ajax 代码,我们还需注意一个点,为什么博客内容页 ajax 对应的 url 是下面这样的?

url: 'BlogContent' + location.search,


而博客列表页的 ajax 中的 success 函数的跳转链接又是为什么写成下面这样的?

linkA.href = 'blog_content.html?blogID=' + blog.blogID;


其实 【 location.search 】其实就是对应着【 blog.blogID 】,只不过这是通过 JS 代码的方式呈现出来的而已,它代表获取当前 HTTP请求的参数。


而为什么 url 中是 ’ BlogContent ',而 href 中的路径是 ’ blog_content.html ’ 呢?


实际上,这两者从访问路径的角度看是一样的,但从展示给用户的角度看,却是不一样的,因为 前者拿到的是一个未经过任何处理的 json 文本数据,而后者是通过 JS 代码中的 success 处理后的 HTML界面。


image.png


(3) 针对服务器端代码:创建一个 BlogContentServlet 来处理计算响应,在此类中,我们为 HTTP 响应的正文 body 写入 json 格式的数据。显而易见,这里的 json 数据只是对应着 Java 一个 blog 对象。


@WebServlet("/BlogContent")
public class BlogContentServlet extends HttpServlet {
    ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json; charset=UTF-8");
        String blogID = req.getParameter("blogID");
        BlogDB blogDB = new BlogDB();
        Blog blog = blogDB.searchOne(Integer.parseInt(blogID));
        // 将 Java 对象转换成 json 格式的数据格式
        String jsonData = objectMapper.writeValueAsString(blog);
        resp.getWriter().write(jsonData);
    }
}


抓包结果:


689f22660c054cf287b2a122d2692cf8.png

3. 博客登录页


(1) 作用:博客登录页是用于实现用户登录的页面,它可以判断用户名和密码各自是否正确。

(2) 约定 POST 请求 的路径:" /BlogLogin "

我们打开写死的博客登录页面,点击【登录】,浏览器自然就发送了 POST 请求,因为我们将【登录】放在了 form 表单下,通过 input 标签实现的。

(3) 针对前端代码:通过 form 表单进行 HTTP 请求的提交,在提交的过程中,需要带上【username】 和【password】这两个参数,以便于服务器端进行验证。

(4) 针对服务器端代码:创建一个 BlogLoginServlet 来实现 HTTP 响应,登录成功后,预期跳转到博客列表页。

(5) 博客登录页这里,并不需要展示什么,所以,此页面也无需通过 ajax 的方式进行构造 HTTP 请求,直接通过 form 表单的形式提交请求,这会让整个登录流程变得更加简单。此外,这里的 if 语句进行判断,我们应该考虑到所有的意外情况与不合理的情况

(6) 登录页面的时候,我们可以利用 session 机制。

若登录成功,就将 session 会话创建出来,并将当前登录的用户,以 Java 对象的方式放入会话 session 中,以备后用。若登录失败,就不将 session 会话创建出来。


我们也可以反过来思考:若 session 会话未被创建出来,那就意味着登录失败。

session 机制就和之前的 ServletContext 机制差不多,我们可以将其想象成一个冰箱,随拿随放。


c23d4a8e29d04e7eaa593d5d56b35990.png


(1) 判定登录与注销操作


鉴于上面的思想,我们不仅可以用当前的 session 会话机制判断博客列表页,也可以用它来判断博客内容页,博客发布页的用户登录情况。此外,通过会话机制,也能够应用于注销操作。


所以,我们对上面的代码改进一下,封装一个 Check 类,让上面所说的页面都能够通过这个 Check 类来判断当前用户是否登录了。


public class Check {
    public static User CheckLogin(HttpServletRequest req) {
        HttpSession httpSession = req.getSession(false);
        if (httpSession == null) {
            // 会话未创建出来,意味着用户未登录
            return null;
        }
        User loginUser = (User) httpSession.getAttribute("user");
        if (loginUser == null) {
            // 就算会话创建出来了,但是里面没有对象,也意味着用户未登录
            return null;
        }
        return loginUser;
    }
}


注意 if 语句的顺序,为什么先要判定 httpSession 存在与否呢?这是为了防止空指针异常。【 若 httpSession 为 null,那么,httpSession.getAttribute(“user”) 这行代码就会出现空指针异常 】


这很好理解:当冰箱都没有的时候,我们怎么从冰箱拿东西呢?所以说,一定是先有冰箱了,我们才能从里面拿东西,才能往里面放东西。

图解验证登录与注销用户的思想:


152f6b3c40ee4c55b040c163508c9b98.png


① 注意

这里需要注意一件事情,当用户没有登录的时候,就访问了博客列表页或博客内容页,应该马上让 HTML 页面再次重定向至登录页面。而在当前的 ajax 方式,我们应该让前端去处理重定向操作。这里我们并不建议在后端进行重定向操作,为什么呢?


因为,使用 ajax 这种方式构造 HTTP 请求的时候,服务器端只负责在 HTTP 响应中写入数据,比方说:它应该写入状态码、正文 body、body 格式等等…而前端就应该根据拿到的 HTTP 响应的数据,决定实现什么样的前端页面与样式,所以说,在这里,所有展现给用户看的内容和格式,都应该由前端负责。


然而,如果我们通过服务器端的重定向操作来实现也不是不可以,但会出现一定的问题,例如:重定向之后的页面,展示的效果可能不是一个 HTML 页面,而是一些未经处理的文本数据。


在【基于模板引擎的博客系统】的那个版本中,通过服务器端实现重定向是可以的,因为模板渲染的所有逻辑都是由 Servlet 代码实现的,而前端只是负责提供模板。


② 正确的前后端判定代码

服务器端自定义写入状态码:


403 这个状态码表示的是访问被拒绝,页面通常需要用户具有一定的权限才能访问。

(403 Forbidden)


// 验证用户有没有登录
User loginUser = Check.CheckLogin(req);
if (loginUser == null) {
    System.out.println("当前用户未登录");
    // 若登录不成功,设置状态码为 403
    // 跳转页面的操作,应该在前端写
    resp.setStatus(403);
}

dc45ea1c96a34288995ac46bd782f870.png


前端重定向操作:


error: function() {
    // 此处表示前端形式的重定向, 相当于后端的 resp.sendRedirect()方法
    location.assign('blog_login.html');
}


我们前面提到:error 这个回调函数,表示的是:当发送 HTTP 请求失败的时候,就会被浏览器执行。显然,它会执行了除 【200】 额外的一些状态码,所以,在 error 中的业务逻辑,我们就可以实现一个重定向操作。


(2) 登录用户与文章作者的数据信息


当用户登录成功后,首先跳转到的是博客列表页,那么,博客列表页就应该显示当前用户的一些信息:头像、昵称、文章总数…接着,若用户点击【查看全文】后,就可以跳转到某一篇博客全文,此时,页面显示的应该是作者信息。


鉴于此,

(1) 将博客列表页展示当前登录者的用户信息

(2) 将博客内容页展示文章作者的用户信息


这里就不再展示代码了,它的思想其实和上面的博客内容页的 ajax 思想是相同的,先选中 DOM 树的节点,再对其标签的内容进行更改。同样地,后端应该利用 blogID 这个参数和 session 会话机制,来确定谁是作者,谁是当前登录的用户。


展示效果:


image.png


4. 博客编辑页


(1) 作用:博客编辑页是实现让用户用来撰写博客的,它可以在浏览器上进行提交,而后,服务器经过一些处理,让博客的一些数据放入数据库中。


(2) 约定 POST 请求 的路径:" /BlogWriting "

我们打开写死的博客编辑页面,点击【发布文章】,浏览器自然就发送了 POST 请求,因为我们将【发布文章】放在了 form 表单下,通过 input 标签实现的。


(3) 针对前端代码:通过 form 表单进行 HTTP 请求的提交,在提交的过程中,需要带上 【title】 和【content】这两个参数,以便于服务器端进行验证。


然而,这里的代码较为少见,因为当前是根据 jQuery 提供的依赖,才会有这个编辑页面的展示效果,所以,写死的 HTML页面同时需要配合 JS 代码,并且需要基于 【editor.md】的一些写法规则。


</body>
    <!-- 版心  -->
    <div class="mark">
        <form action="BlogWriting" method="POST" style="height: 100%;">
            <!-- 标题编辑区 -->
            <div class="headline">
                <input type="text" class="title" name="title">
                <!-- <button class="submit">发布文章</button> -->
                <input type="submit" class="submit">
            </div>
            <!-- mardown 编辑区 -->
            <div id="editor">
                <textarea name="content" style="display : none"></textarea>
            </div>
        </form>
    </div>
    <script>
        // 初始化编译器
        let editor = editormd("editor", {
            //这里的尺寸必须在这里设置,设置样式会被 editormd 覆盖掉
            width: "100%",
            //设置编译器高度
            height: "calc(100% - 60px)",
            //编译器的初始内容
            markdown: "# 在这里写下一篇博客",
            //指定 editor.md 依赖的插件路径
            path: "editor.md/lib/",
            // 加上这个选项之后,编辑器中的内容才会被放到 textarea 里面
            saveHTMLToTextArea: true
        });
    </script>
</body>


(4) 针对服务器端代码:创建一个 BlogWritingServlet 来实现 HTTP 响应,登录成功后,预期跳转到博客列表页。


(5) 博客编辑页这里,和博客登录页是一样的思路,并不需要展示什么,所以,此页面并不需要基于 ajax 构造请求。此外,这里的 if 语句进行判断,我们应该考虑到所有的意外情况与不合理的情况。


(6) 由于编辑博客的时候,它是依据 markdown 的语法规则,可以让一些字体变成我们想要的格式,例如:一级标题,二级标题,加粗,删除线等等…

我们当前的 CSDN 就是拥有这样的规则。


而在之前的博客列表页显示博客的时候,它是一种素的、原始的文字。就算经过博客发布了,但展示给用户看的时候,并没有经过博客编辑器处理,所以,同样地,我们为博客列表页引入 【editor.md】这样的依赖,并通过 JS 代码,让文字变成处理后的结果。


success: function(data, status) {
    let blog = data;
    // 博客标题
    let titleH3 = document.querySelector('.blog .title');
    titleH3.innerHTML = blog.title;
    // 博客格式化日期
    let dateDiv = document.querySelector('.blog .date');
    dateDiv.innerHTML = formatDate(blog.postTime);
    // // 单篇博客的内容
    // let contentDiv = document.querySelector('.blog .content');
    // contentDiv.innerHTML = blog.content;   
    // 把得到的 json 数据按照 markdown 的方式处理好,放到 id 为 content 的标签中
    editormd.markdownToHTML('content', {markdown: blog.content});
},


实现思想:

markdown 的原始内容,放在上面的 div 中,我们可以将这个 div 中的内容通过 markdownToHTML 函数进行替换。


展示效果:


a317b6b4d15143e8a5ee4fc0f963fd42.png


删除博客


删除博客,通过 a 标签构造 DELETE 请求,之后在服务器端进行处理。


思想:在博客内容页,若当前登录用户和博客作者是同一个人,即可以删除;若不是同一个人,则不能删除,只能观看。


ajax 构造的 HTTP 请求:


<!-- 当前的 ajax 请求是期望删除一篇博客 -->   
<script>
    $.ajax({
        url: 'BlogContent' + location.search,
        method: 'GET',
        success: function(data, status) {
            let blog = data;
            // 如果 传入的 blog.isAuthor 数据为1,那么就执行下面的语句
            // 以此来区分当前博客到底是登录用户还是文章作者
            // 如果是文章作者,就显示删除链接,反之,则不显示
            if (blog.isAuthor == 1) {
                // 删除节点
                let deleteA = document.createElement('a');
                deleteA.innerHTML = '删除博客';
                deleteA.href = '#';
                // 因为 a 标签的 href 属性只能生成 GET 请求
                // 所以,此处,我们将将 a 标签生成一个点击事件,用 deleteBlog 函数来申请 DELETE 请求
                deleteA.onclick = deleteBlog;
                // 将删除节点挂在注销节点之前
                let navDiv = document.querySelector('.nav');
                let logoutA = document.querySelector('.nav .logout');
                navDiv.insertBefore(deleteA, logoutA);
            }
        }
    })
    function deleteBlog() {
        // 在这个函数中,再次通过 ajax 构造请求
        $.ajax({
            url: 'BlogDelete' + location.search,
            method: 'DELETE',
            success: function(data, status) {
                // 如果删除成功,就让页面重定向到博客列表页
                location.assign('blog_list.html');
            }
        })
    }
</script>


服务器端构造的响应:


@WebServlet("/BlogDelete")
public class BlogDeleteServlet extends HttpServlet {
    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        //resp.setContentType("application/json; charset=UTF-8");
        resp.setContentType("text/html; charset=UTF-8");
        // 验证用户有没有登录
        User loginUser = Check.CheckLogin(req);
        if (loginUser == null) {
            System.out.println("当前用户未登录");
            // 若登录不成功,设置状态码未 403
            // 跳转页面的操作,应该在前端写
            resp.setStatus(403);
            return;
        }
        // 从 HTTP 请求中读取参数
        String blogID = req.getParameter("blogID");
        if (blogID == null || blogID.equals("")) {
            resp.getWriter().write("<h3> 您未选择要删除哪篇文章 </h3>");
            return;
        }
        BlogDB blogDB = new BlogDB();
        Blog blog = blogDB.searchOne(Integer.parseInt(blogID));
        if (blog.getUserID() != loginUser.getUserID()) {
            resp.getWriter().write("<h3> 您所删除的并不是本人的文章 </h3>");
            return;
        }
        // 删除操作
        blogDB.deleteOne(Integer.parseInt(blogID));
        // 删除成功,设置好状态
        // 重定向在前端页面完成
        resp.setStatus(200);
        resp.getWriter().write("<h3> 删除成功 </h3>");
    }
}


注意:


删除博客这里的实现逻辑通过两次 ajax 请求来实现的,第一次是为了显示【删除博客】按钮,第二次才是真正的发送删除请求。此外,由于这里是由 ajax 构造的 HTTP 请求,我们期望重定向的操作应该放在前端实现,后端只需要设置状态和删除操作的提示即可。


目录
相关文章
|
4月前
|
Java
学院管理系统【JSP+Servlet+JavaBean】(Java课设)
学院管理系统【JSP+Servlet+JavaBean】(Java课设)
53 3
学院管理系统【JSP+Servlet+JavaBean】(Java课设)
|
4月前
|
Java
排课系统【JSP+Servlet+JavaBean】(Java课设)
排课系统【JSP+Servlet+JavaBean】(Java课设)
57 5
|
4月前
|
Java
学校教师管理系统【JSP+Servlet+JavaBean】(Java课设)
学校教师管理系统【JSP+Servlet+JavaBean】(Java课设)
50 2
|
1月前
|
供应链 前端开发 Java
JSP+servlet+mybatis+layui服装库存管理系统(大三上学期课程设计)
这篇文章通过一个服装库存管理系统的实例,展示了在Spring Boot项目中使用Ajax、JSON、layui、MVC架构和iframe等技术,涵盖了注册登录、权限管理、用户管理、库存管理等功能,并提供了系统运行环境和技术要求的详细说明。
JSP+servlet+mybatis+layui服装库存管理系统(大三上学期课程设计)
|
21天前
|
监控 前端开发 Java
揭秘Web开发神器:Servlet、过滤器、拦截器、监听器如何联手打造无敌博客系统,让你的用户欲罢不能!
【8月更文挑战第24天】在Java Web开发中,Servlet、过滤器(Filter)、拦截器(Interceptor,特指Spring MVC中的)及监听器(Listener)协同工作,实现复杂应用逻辑。以博客系统为例,Servlet处理文章详情请求,过滤器(如LoginFilter)检查登录状态并重定向,Spring MVC拦截器(如LoggingInterceptor)提供细粒度控制(如日志记录),监听器(如SessionListener)监控会话生命周期事件。这些组件共同构建出高效、有序的Web应用程序。
27 0
|
4月前
|
Java
新闻发布系统【JSP+Servlet+JavaBean】(Java课设)
新闻发布系统【JSP+Servlet+JavaBean】(Java课设)
43 2
|
4月前
|
Java
学校人员管理系统【JSP+Servlet+JavaBean】(Java课设)
学校人员管理系统【JSP+Servlet+JavaBean】(Java课设)
47 2
|
4月前
|
SQL JSON 前端开发
基于 Servlet 的博客系统
基于 Servlet 的博客系统
|
4月前
|
Java
个人信息管理系统【JSP+Servlet+JavaBean】(Java课设)
个人信息管理系统【JSP+Servlet+JavaBean】(Java课设)
37 0
|
15天前
|
缓存 安全 Java
Java服务器端技术:Servlet与JSP的集成与扩展
Java服务器端技术:Servlet与JSP的集成与扩展
13 3

相关实验场景

更多