JavaEE - 博客系统(使用前后端分离)

博客系统(使用前后端分离)

前面的代码中我们基于模板的方式来开发了博客系统.

在基于模板方式中, 主要是通过服务器把数据渲染到页面中, 然后直接返回完整的页面给浏览器.

目前现在更主流的开发方式是 “前后端分离” 的方式. 这种方式下服务器端不关注页面的内容, 而只是给 网页端提供数据.

网页端通过 ajax 的方式和服务器之间交互数据, 网页拿到数据之后再根据数据的内容渲染到页面上.

JavaEE - 博客系统(使用前后端分离)_第1张图片

准备工作

  1. 创建 web 项目

  2. 创建目录结构

JavaEE - 博客系统(使用前后端分离)_第2张图片

  1. 配置 pom.xml

前后端分离的方式不需要使用 Thymeleaf 了.


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <groupId>org.examplegroupId>
    <artifactId>博客系统(前后端分离)artifactId>
    <version>1.0-SNAPSHOTversion>
    
    <properties>
        <encoding>UTF-8encoding>
        <maven.compiler.source>1.8maven.compiler.source>
        <maven.compiler.target>1.8maven.compiler.target>
    properties>
    <dependencies>
        
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            
            <version>3.1.0version>
            
            <scope>providedscope>
        dependency>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.11version>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.45version>
        dependency>
        
        <finalName>BlogSystemfinalName>
    build>
project>

数据库设计

同之前的 博客系统(使用模板技术)
包括:

  • 表结构 (文章表, 用户表)
  • DBUtil 类
  • Blog 类, User 类
  • BlogDao 类, UserDao 类

准备前端页面

拷贝页面

把之前写好的博客系统的静态页面拷贝到 webapp 目录中.

此处不需要 templates 目录了.

JavaEE - 博客系统(使用前后端分离)_第3张图片

封装 ajax

在前后端交互中我们需要用到 ajax 进行数据交互.

我们把之前写过的 ajax 函数拷贝过来, 放到一个单独的 js 文件中, 方便后续使用. 创建 js/common.js

// 参数 args 是一个 JS 对象, 里面包含了以下属性
// method: 请求方法
// url: 请求路径
// body: 请求的正文数据
// contentType: 请求正文的格式
// callback: 处理响应的回调函数, 有两个参数, 响应正文和响应的状态码
function ajax(args) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
        // 0: 请求未初始化
        // 1: 服务器连接已建立
        // 2: 请求已接收
        // 3: 请求处理中
        // 4: 请求已完成,且响应已就绪
        if (xhr.readyState == 4) {
            args.callback(xhr.responseText, xhr.status)
        }
    }
    xhr.open(args.method, args.url);
    if (args.contentType) {
        xhr.setRequestHeader('Content-type', args.contentType);
    }
    if (args.body) {
        xhr.send(args.body);
    } else {
        xhr.send();
    }
}

实现博客列表

约定前后端交互接口

[请求]
GET /blog
[响应]
[
    {
        blogId: 1,
        title: "第一篇博客",
        content: "博客正文",
        userId: 1,
        postTime: "2021-07-07 12:00:00"
    },
    {
        blogId: 2,
        title: "第二篇博客",
        content: "博客正文",
        userId: 1,
        postTime: "2021-07-07 12:10:00"
    },
    ...
]

我们约定, 浏览器给服务器发送一个 GET /blog 这样的 HTTP 请求, 服务器给浏览器返回了一个 JSON格式的数据.

实现服务器代码

创建 BlogServlet , 放到 api 包中.

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
    }
}

实现 doGet, 完成读取博客列表的功能.

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws 
ServletException, IOException {
    resp.setContentType("application/json; charset=utf-8");
    BlogDao blogDao = new BlogDao();
    List<Blog> blogs = blogDao.selectAll();
    String jsonString = objectMapper.writeValueAsString(blogs);
    resp.getWriter().write(jsonString);
}

部署程序, 验证服务器是否能正确返回数据 (使用 URLhttp://127.0.0.1:8080/BlogSystem/blog即可).

实现客户端代码

修改 blog_list.html, 删除之前写死的博客内容(即

), 并新增 js 代码处理 ajax 请求.

  • 使用 ajax 给服务器发送 HTTP 请求.
  • 服务器返回的响应是一个 JSON 格式的数据, 根据这个响应数据使用 DOM API 构造页面内容.
  • 响应中的 postTime 字段为 ms 级时间戳, 需要转成格式化日期.
  • 列表页中拿到的 “content” 字段其实是已经裁剪过的摘要.

跳转到博客详情页的 url 形如blog_content.html?blogId=1这样就可以让博客详情页知道当前是要访问哪篇博客.

ajax({
    url: 'blog',
    method: 'GET',
    callback: function (data, status) {
        if (status == 200) {
            var blogs = JSON.parse(data);
            buildBlogs(blogs)
        } else {
            console.log("status error! " + status);
        }
    }
});
function buildBlogs(blogs) {
    // 获取博客数据的父容器
    var container = document.querySelector(".container-right");
    for (var blog of blogs) {
        // 创建博客 div
        var blogDiv = document.createElement("div");
        blogDiv.className = "blog";
        // 创建博客标题
        var titleDiv = document.createElement("div");
        titleDiv.className = "title";
        titleDiv.innerHTML = blog.title;
        blogDiv.appendChild(titleDiv);
        // 创建日期
        var dateDiv = document.createElement("div");
        dateDiv.className = "date";
        // postTime 是一个 ms 级时间戳, 此处需要转成格式化时间. 
        dateDiv.innerHTML = formatDate(blog.postTime);
        blogDiv.appendChild(dateDiv);
        // 创建描述
        var descDiv = document.createElement("div");
        descDiv.className = "desc";
        descDiv.innerHTML = blog.content;
        blogDiv.appendChild(descDiv);
        // 创建跳转按钮
        var detailA = document.createElement("a");
        detailA.href = "blog_content.html?blogId=" + blog.blogId;
        detailA.className = "detail";
        detailA.innerHTML = "查看全文 >>";
        blogDiv.appendChild(detailA);
        // 把 blog 对象挂到 container 中
        container.appendChild(blogDiv);
    }
}

其中的 formatDate 函数实现为:

function formatDate(time) {
    var date = new Date(time);
    var year = date.getFullYear(),
        month = date.getMonth() + 1,//月份是从0开始的
        day = date.getDate(),
        hour = date.getHours(),
        min = date.getMinutes(),
        sec = date.getSeconds();
    var newTime = year + '-' +
        (month < 10 ? '0' + month : month) + '-' +
        (day < 10 ? '0' + day : day) + ' ' +
        (hour < 10 ? '0' + hour : hour) + ':' +
        (min < 10 ? '0' + min : min) + ':' +
        (sec < 10 ? '0' + sec : sec);
    return newTime;
}

可以把这个函数和 ajax 函数放到一起 (放到 common.js 中). 这个代码不必我们自己来写, 直接网上搜索 “JS格式化时间” 即可找到.

通过 URL http://127.0.0.1:8080/BlogSystem/blog_list.html 访问服务器, 验证效果

JavaEE - 博客系统(使用前后端分离)_第4张图片

理解数据交互过程

在刚才的页面访问过程中, 涉及两次 HTTP 请求-响应 的交互. (不考虑从服务器下载 css, js, 图片等)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vup6wb9q-1654526488366)(media/a7812075fa5d95d371c8206cbf816e19.png)]

第一次请求: 浏览器从服务器下载 blog_list.html 页面.

第二次请求: blog_list.html 中触发了 ajax 请求, 获得到 博客列表 数据.

在前后端分离的模式中, 往往一个页面的显示需要多次 HTTP 交互过程.

实现博客详情

目前点击博客列表页的 “查看全文” , 能进入博客详情页, 但是这个博客详情页是写死的内容. 我们期望能 够根据当前的 博客 id 从服务器动态获取博客内容.

约定前后端交互接口

[请求]
GET /blog?blogId=1
[响应]
{
    blogId: 1,
    title: "第一篇博客",
    content: "博客正文",
    userId: 1,
    postTime: "2021-07-07 12:00:00"
},

相比于博客列表页, 博客详情页的请求中多了一个 blogId 参数, 响应中只获取到一个博客的内容.

实现服务器代码

修改 BlogServlet 的 doGet 方法

  • 根据 blogId 参数是否存在, 判定当前是获取博客列表还是获取博客详情.
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws 
ServletException, IOException {
    resp.setContentType("application/json; charset=utf-8");
    String blogId = req.getParameter("blogId");
    BlogDao blogDao = new BlogDao();
    String jsonString = null;
    if (blogId == null) {
        // 获取博客列表
        List<Blog> blogs = blogDao.selectAll();
        jsonString = objectMapper.writeValueAsString(blogs);
    } else {
        // 获取博客详情
        Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
        jsonString = objectMapper.writeValueAsString(blog);
    }
    resp.getWriter().write(jsonString);
}

部署程序, 验证服务器是否能正确返回数据 (使用 URLhttp://127.0.0.1:8080/BlogSystem/blog? blogId=1即可).

实现客户端代码

修改 blog_content.html

  • 根据当前页面 URL 中的 blogId 参数(使用 location.search 即可得到形如 服务器发送 GET /blog 请求.
  • 根据获取到的响应数据, 通过 editor.md 转换成 html, 并显示.
  1. 引入 editor.md

<link rel="stylesheet" href="editor.md/css/editormd.min.css" />
<script src="js/jquery.min.js">script>
<script src="editor.md/editormd.js">script>
  1. 修改 html 部分, 去掉原来写死的博客标题, 日期, 然后把博客正文的 p 标签, 改成
    并且加上style="background-color: transparent;"

<div class="container-right">
    <div class="blog-content">
        
        <h3>h3>
        
        <div class="date">div>
        
        <div id="content" style="background-color: transparent;">
        div>
    div>
div>
  1. 新增 js 代码, 从服务器获取博客详情数据.
ajax({
    url: 'blog' + location.search,
    method: 'GET',
    callback: function (data, status) {
        if (status == 200) {
            var blog = JSON.parse(data);
            buildBlog(blog);
        } else {
            console.log("status error! " + status);
        }
    }
});
function buildBlog(blog) {
    // 1. 更新标题
    var titleDiv = document.querySelector(".blog-content h3");
    titleDiv.innerHTML = blog.title;
    // 2. 更新时间
    var dateDiv = document.querySelector(".blog-content .date");
    dateDiv.innerHTML = formatDate(blog.postTime);
    // 3. 更新博客正文
    editormd.markdownToHTML('content', { markdown: blog.content });
}

部署程序, 验证效果.

JavaEE - 博客系统(使用前后端分离)_第5张图片

实现登陆

这部分逻辑和之前的版本基本一致.

  • 登陆页面提供一个 form 表单, 通过 form 的方式把用户名密码提交给服务器.
  • 服务器端验证用户名密码是否正确.
  • 如果密码正确, 则在服务器端创建 Session , 并把 sessionId 通过 Cookie 返回给浏览器.

前后端分离的项目中, 虽然主要使用 ajax 进行前后端交互, 但是也不是完全不能用 form.

约定前后端交互接口

[请求]
POST /login
Content-Type: application/x-www-form-urlencoded
username=test&password=123

[响应]
HTTP/1.1 302 
Location: blog_list.html

实现服务器代码

创建 LoginServlet

代码和 博客系统(基于模板技术) 中的 LoginServlet 相同.

@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");
        // 1. 读取用户名和密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null || password == null || "".equals(username) || 
"".equals(password)) {
            String html = "

登陆失败! 缺少 username 或 password 字段

"
; resp.getWriter().write(html); return; } // 2. 在数据库中验证用户名密码 UserDao userDao = new UserDao(); User user = userDao.selectByName(username); if (!password.equals(user.getPassword())) { String html = "

登陆失败! 用户名或者密码错误!

"
; resp.getWriter().write(html); return; } // 3. 登陆成功, 设置 Session HttpSession session = req.getSession(true); session.setAttribute("user", user); // 4. 重定向到博客列表页. resp.sendRedirect("blog_list.html"); } }

实现客户端代码

修改 login.html

  • 给输入框套上一层 form 标签. action 为 login, method 为 POST
  • 给 input 加上 name 属性.
  • 把提交按钮改成
<div class="login-container">
    
    <div class="login-dialog">
        <form action="login" method="POST">
            <h3>登陆h3>
            <div class="row">
                <span>用户名span>
                <input type="text" id="username" name="username">
            div>
            <div class="row">
                <span>密码span>
                <input type="password" id="password" name="password">
            div>
            <div class="row">
                <input type="submit" id="submit" value="提交">
            div>
        form>
    div>
div>

部署程序, 验证效果.

实现强制要求登陆

当用户访问 博客列表页 和 博客详情页 时, 如果用户当前尚未登陆, 就自动跳转到登陆页面.

之前的 “跳转到登陆页面” 是直接服务器返回 302 实现的. 现在需要通过页面的 JS 代码来实现.

实现服务器代码

  1. 创建 Util 类, 实现 checkLoginStatus 方法, 检测当前用户的登陆状态.
public class Util {
    public static User checkLoginStatus(HttpServletRequest req) {
        HttpSession session = req.getSession(false);
        if (session == null) {
            return null;
        }
        User user = (User) session.getAttribute("user");
        return user;
    }
}
  1. 修改 BlogServlet, 在 doGet 的开头调用 checkLoginStatus 检测该用户是否登陆, 如果未登录则返 回一个 403 响应.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws 
ServletException, IOException {
    // ...... 其他代码不变
    
    // 检测用户登陆状态
    User user = Util.checkLoginStatus(req);
    if (user == null) {
        resp.setStatus(403);
        resp.getWriter().write("{ reason: \"当前用户尚未登陆!\" }");
        return;
    }
    
    // ...... 其他代码不变
}

实现客户端代码

  1. 修改 blog_list.html
  • 在 ajax 的回调函数中, 判定响应状态码是否为 403.
  • 使用 location.assign 进行页面跳转.
ajax({
url: 'blog',
    method: 'GET',
    callback: function (data, status) {
        if (status == 200) {
            var blogs = JSON.parse(data);
            buildBlogs(blogs)
        } else if (status == 403) {
            // 当前用户未登录, 重定向到 login.html
            location.assign("login.html");
        } else {
            console.log("status error! " + status);
        }
    }
});
  1. 修改 blog_detail.html

修改方式同上

ajax({
    url: 'blog' + location.search,
    method: 'GET',
    callback: function (data, status) {
        if (status == 200) {
            var blog = JSON.parse(data);
            buildBlog(blog);
        } else if (status == 403) {
            // 如果未登录, 直接重定向到 login.html
            location.assign("login.html");
        } else {
            console.log("status error! " + status);
        }
    }
});

部署程序, 验证效果.

实现显示用户信息

目前页面的用户信息部分是写死的. 形如:

JavaEE - 博客系统(使用前后端分离)_第6张图片

我们期望这个信息可以随着用户登陆而发生改变.

  • 如果当前页面是博客列表页, 则显示当前登陆用户的信息.
  • 如果当前页面是博客详情页, 则显示该博客的作者用户信息.

注意: 当前我们只是实现了显示用户名, 没有实现显示用户的头像以及文章数量等信息.

约定前后端交互接口

在博客列表页, 获取当前登陆的用户的用户信息.

[请求]
GET /user
[响应]
{
    userId: 1,
    username: test
}

在博客详情页, 获取当前文章作者的用户信息

[请求]
GET /user?blogId=1
[响应]
{
    userId: 1,
    username: test
}

实现服务器代码

创建 UserServlet

public class UserServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.setContentType("application/json; charset=utf-8");
        // 1. 先判定当前用户是否已经登陆
        User user = Util.checkLoginStatus(req);
        if (user == null) {
            resp.setStatus(403);
            resp.getWriter().write("{ \"reason\": \"当前尚未登陆\" }");
            return;
        }
        // 2. 读取请求中的 blogId 参数
        String blogId = req.getParameter("blogId");
        String jsonString = null;
        if (blogId == null) {
            // 获取当前登陆用户的信息
            // 这个信息已经在 session 中获取到了.
            jsonString = objectMapper.writeValueAsString(user);
        } else {
            // 获取指定文章作者的用户信息
            BlogDao blogDao = new BlogDao();
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            UserDao userDao = new UserDao();
            User author = userDao.selectById(blog.getUserId());
            jsonString = objectMapper.writeValueAsString(author);
        }
        resp.getWriter().write(jsonString);
    }
}

实现客户端代码

  1. 修改 blog_list.html
  • 新增一个 ajax 函数的调用, 以 GET 请求 /user 路径.
  • 在响应回调函数中, 根据响应中的用户名, 更新界面的显示.
ajax({
    url: 'user',
    method: 'GET',
    callback: function (data, status) {
        if (status == 200) {
            var user = JSON.parse(data);
            changeUser(user);
        } else {
            console.log("status error! " + status);
        }
    }
});
function changeUser(user) {
    var h3 = document.querySelector(".card h3");
    h3.innerHTML = user.username;
}
  1. 修改 blog_content.html

修改方式同上

ajax({
    url: 'user' + location.search,
    method: 'GET',
    callback: function (data, status) {
        if (status == 200) {
            var user = JSON.parse(data);
            changeUser(user);
        } else {
            console.log("status error! " + status);
        }
    }
});
function changeUser(user) {
    var h3 = document.querySelector(".card h3");
    h3.innerHTML = user.username;
}

部署程序, 验证效果.

实现注销登陆

约定前后端交互接口

[请求]
GET /logout

[响应]
HTTP/1.1 302
Location: login.html

实现服务器代码

创建 LogoutServlet

  • 从 session 中删除掉保存的 User 对象.
  • 响应重定向到 login.html 页面.
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        HttpSession session = req.getSession(false);
        if (session == null) {
            resp.setStatus(403);
            return;
        }
        session.removeAttribute("user");
        resp.setStatus(200);
    }
}

客户端代码不需要调整.

注销按钮本来就是一个 , 点击的时候就会发送 GET /logou 这样的请求. 部署程序, 验证效果.

实现发布博客

逻辑和 博客系统(基于模板技术) 基本一致.

约定前后端交互接口

[请求]
POST /blog
Content-Type: application/x-www-form-urlencoded
title=标题&content=正文...
[响应]
HTTP/1.1 302
Location: blog_list.html

实现服务器代码

修改 BlogServlet, 新增 doPost 方法.

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws 
ServletException, IOException {
    req.setCharacterEncoding("utf-8");
    resp.setContentType("application/json; charset=utf-8");
    // 1. 检查用户是否已经登陆
    User user = Util.checkLoginStatus(req);
    if (user == null) {
        resp.setStatus(403);
        return;
    }
    // 2. 读取请求中的数据
    String title = req.getParameter("title");
    String content = req.getParameter("content");
    if (title == null || content == null || "".equals(title) || 
"".equals(content)) {
        String html = "

title 或者 content 字段缺失! 新增博客失败!

"
; resp.getWriter().write(html); return; } // 3. 构造博客对象 Blog blog = new Blog(); blog.setTitle(title); blog.setContent(content); blog.setUserId(user.getUserId()); blog.setPostTime(new Timestamp(System.currentTimeMillis())); // 4. 把博客对象插入到数据库 BlogDao blogDao = new BlogDao(); blogDao.insert(blog); // 5. 重定向到博客列表页 resp.sendRedirect("blog_list.html"); }

实现客户端代码

修改 blog_edit.html 页面结构,

  • 增加 form 标签, action 为 blog_edit, method 为POST
  • 给 form 指定height: 100%; 防止编辑器高度不能正确展开.
  • 给标题的 input 标签加上 name 属性
  • 把提交按钮改成
  • 里面加上一个隐藏的 textarea

<div class="blog-edit-container">
    <form action="blog_edit" method="POST" style="height: 100%;">
        
        <div class="title">
            <input type="text" placeholder="在这里写下文章标题" id="title" 
name="title">
            <input type="submit" id="submit" value="发布文章">input>
        div>
    
    <div id="editor">
        <textarea name="content" style="display: none;">textarea>
    div>
    form>
div>

在 editor.md 的初始化代码中, 新增一个选项saveHTMLToTextarea: true

// 初始化编辑器
var editor = editormd("editor", {
    // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. 
    width: "100%",
    // 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
    height: "calc(100% - 50px)",
    // 编辑器中的初始内容
    markdown: "# 在这里写下一篇博客",
    // 指定 editor.md 依赖的插件路径
    path: "editor.md/lib/",
    // 加上这个属性使 编辑器 的内容能保存到用户自己添加的 textarea 中. 
    saveHTMLToTextarea: true,
});

部署程序, 验证效果.

实现删除博客

进入用户详情页时, 如果当前登陆用户正是文章作者, 则在导航栏中显示 “删除” 按钮, 用户点击时则删除 该文章.

需要实现两件事:

  • 判定当前博客详情页中是否要显示 “删除” 按钮
  • 实现删除逻辑.

约定前后端交互接口

  1. 判定是否要显示删除按钮

修改之前的 获取用户 信息的接口, 在响应中加上一个字段.

  • isYourBlog 为 true 表示当前博客就是登陆用户自己写的.
[请求]
GET /user?blogId=1

[响应]
{
    userId: 1,
    username: test,
    isYourBlog: 1,  // 1 表示当前博客就是登陆者的博客. 0 表示当前博客不是登陆者的博客. 
}
  1. 删除博客
  • 使用 DELETE 请求表示删除一个博客.
[请求]
DELETE /blog?blogId=1

[响应]
HTTP/1.1 200

实现服务器代码

  1. 给 User 类新增一个字段
public class User {
    private int userId;
    private String username;
    private String password;
    // 这个字段只是在判定博客详情页是否显示删除按钮时使用. 
    private int isYourBlog;
}
  1. 修改 UserServlet

其他代码不变. 只处理 “博客详情页” 中的逻辑.

// 获取指定文章作者的用户信息
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
UserDao userDao = new UserDao();
User author = userDao.selectById(blog.getUserId());
author.setYourBlog(author.getUserId() == user.getUserId() ? 1 : 0);
jsonString = objectMapper.writeValueAsString(author);
  1. 修改 BlogServlet
  • 增加 doDelete 方法, 处理删除逻辑.

逻辑和之前版本基本相同. 但是此处删除完毕不必返回 302 了, 由客户端自己决定重定向逻辑.

protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws 
ServletException, IOException {
    // 1. 验证用户是否登陆
    User user = Util.checkLoginStatus(req);
    if (user == null) {
        resp.setStatus(403);
        return;
    }
    // 2. 读取要删除的 blogId
    String blogId = req.getParameter("blogId");
    if (blogId == null) {
        String html = "

blogId 参数错误!

"
; resp.getWriter().write(html); return; } // 3. 从数据库中删除博客 BlogDao blogDao = new BlogDao(); blogDao.delete(Integer.parseInt(blogId)); // 4. 返回响应数据 resp.setStatus(200); }

实现客户端代码

修改 blog_content.html

  • 修改 changeUser 函数, 当获取到的响应中的 isYourBlog 为 true 的时候, 则在导航上添加一个a标签作为删除按钮.
  • 当点击删除按钮的时候, 给服务器发送一个 ajax 请求.
function changeUser(user) {
    var h3 = document.querySelector(".card h3");
    h3.innerHTML = user.username;
    if (user.isYourBlog) {
        // 显示删除按钮
        var navDiv = document.querySelector(".nav");
        var delBtn = document.createElement("a");
        delBtn.innerHTML = "删除";
        delBtn.href = "#";
        delBtn.onclick = deleteBlog;
        navDiv.appendChild(delBtn);
    }
}
function deleteBlog() {
    // 使用 ajax 给服务器发送一个 DELETE 请求
    ajax({
        url: "blog" + location.search,
        method: "DELETE",
        callback: function (data, status) {
            if (status == 200) {
                // 重定向到博客列表页
                location.assign("blog_list.html");
            } else {
                console.log("status error! " + status);
            }
        }
    })
}

部署程序, 验证效果.

总结

服务器渲染和客户端渲染(前后端分离) 都是常见的 web 开发的方式. 目前 前后端分离 的方式更主流一 些.

主要原因:

  • 前后端分离更便于分工协作: 开发开始时, 前端工程师和后端工程师共同约定好交互接口, 然后就可 以分别开发, 各自测试.直到最终双方开发完毕再在一起联调.
  • 网络带宽越来越大: 因此渲染一个页面多使用几个 HTTP 请求-响应 也问题不大.
  • 用户主机的计算能力越来越强: 无论是手机还是PC, 算力都在突飞猛进的增长. 因此这样的渲染工作 对于客户端来说不是什么负担,
    但是能降低服务器的负荷.
  • 更便于多端开发: 比如同一份服务器代码, 就既可以给网页端提供服务, 也可以给手机app 提供服务.

在前后端分离的模式下, 约定前后端交互接口是一件至关重要的事情. 约定的方式也有很多种. 其中一种 比较流行的方式称为 “Restful 风格”

  • 使用不同的 HTTP 方法, 表示要执行的动作. 例如 GET 用于获取数据, POST 用于新增数据, PUT 用 于修改数据,DELETE 用于删除数据.
  • 使用 URL 中的 PATH 表示要操作的资源.
  • 使用响应的状态码表示不同的响应结果.
  • 使用 JSON 格式作为 body 中的数据组织方式.

我们上面的代码模仿了 Restful 风格, 但是还不算特别严格. 比如我们在提交博客的时候不是使用JSON 格式的数据.
实际开发的时候也不必完全拘泥于这样的格式. 都可以灵活对待.

你可能感兴趣的:(java,ajax,前端)