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

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

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

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

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

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

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

准备工作

  1. 创建 web 项目

  2. 创建目录结构

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

  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.0</modelVersion>

<groupId>org.example</groupId>

<artifactId>博客系统(前后端分离)</artifactId>

<version>1.0-SNAPSHOT</version>

<!-- 指定属性信息 -->

<properties>

<encoding>UTF-8</encoding>

<maven.compiler.source>1.8</maven.compiler.source>

<maven.compiler.target>1.8</maven.compiler.target>

</properties>

<dependencies>

<!-- 加入 servlet 依赖 -->

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>javax.servlet-api</artifactId>

<!-- servlet 版本和 tomcat 版本有对应关系,切记 -->

<version>3.1.0</version>

<!-- 这个意思是我们只在开发阶段需要这个依赖,部署到 tomcat 上时就不需要了 -->

<scope>provided</scope>

</dependency>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.11</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.45</version>

</dependency>

<!–

https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -

->

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.12.3</version>

</dependency>

</dependencies>

<packaging>war</packaging>

<build>

<!-- 指定最终 war 包的名称 -->

<finalName>BlogSystem</finalName>

</build>

</project>

数据库设计

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

表结构 (文章表, 用户表) DBUtil 类

Blog 类, User 类

BlogDao 类, UserDao 类

准备前端页面

拷贝页面

把之前写好的博客系统的静态页面拷贝到 webapp 目录中. 此处不需要 templates 目录了.

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

封装 ajax

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

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

实现博客列表

约定前后端交互接口

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

格式的数据.

实现服务器代码

创建 BlogServlet , 放到 api 包中.

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

部署程序, 验证服务器是否能正确返回数据 (使用 URL

可).

实现客户端代码

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

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

使用 ajax 给服务器发送 HTTP 请求.

服务器返回的响应是一个 JSON 格式的数据, 根据这个响应数据使用 DOM API 构造页面内容. 响应中的 postTime 字段为 ms 级时间戳, 需要转成格式化日期.

列表页中拿到的 “content” 字段其实是已经裁剪过的摘要.

跳转到博客详情页的 url 形如

是要访问哪篇博客.

这样就可以让博客详情页知道当前

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 = “查看全文 &gt;>”;

blogDiv.appendChild(detailA);

// 把 blog 对象挂到 container 中

container.appendChild(blogDiv);

}

}

其中的 formatDate 函数实现为:

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

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

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

理解数据交互过程

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

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

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

第二次请求: blog_list.html 中触发了 ajax 请求, 获得到 博客列表 数据. 在前后端分离的模式中, 往往一个页面的显示需要多次 HTTP 交互过程.

实现博客详情

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

约定前后端交互接口

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

实现服务器代码

修改 BlogServlet 的 doGet 方法

根据 blogId 参数是否存在, 判定当前是获取博客列表还是获取博客详情.

部署程序, 验证服务器是否能正确返回数据 (使用 URL

即可).

实现客户端代码

修改 blog_content.html

根据当前页面 URL 中的 blogId 参数(使用 location.search 即可得到形如 服务器发送 GET /blog 请求.

根据获取到的响应数据, 通过 editor.md 转换成 html, 并显示.

  1. 引入 editor.md

    的数据), 给

  1. 修改 html 部分, 去掉原来写死的博客标题, 日期, 然后把博客正文的 p 标签, 改成 并且加上

  2. 新增 js 代码, 从服务器获取博客详情数据.

部署程序, 验证效果.

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

实现登陆

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

登陆页面提供一个 form 表单, 通过 form 的方式把用户名密码提交给服务器. 服务器端验证用户名密码是否正确.

如果密码正确, 则在服务器端创建 Session , 并把 sessionId 通过 Cookie 返回给浏览器.

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

约定前后端交互接口

实现服务器代码

创建 LoginServlet

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

实现客户端代码

修改 login.html

给输入框套上一层 form 标签. action 为 login, method 为 POST

给 input 加上 name 属性. 把提交按钮改成

部署程序, 验证效果.

实现强制要求登陆

当用户访问 博客列表页 和 博客详情页 时, 如果用户当前尚未登陆, 就自动跳转到登陆页面. 之前的 “跳转到登陆页面” 是直接服务器返回 302 实现的. 现在需要通过页面的 JS 代码来实现.

实现服务器代码

  1. 创建 Util 类, 实现 checkLoginStatus 方法, 检测当前用户的登陆状态.
  1. 修改 BlogServlet, 在 doGet 的开头调用 checkLoginStatus 检测该用户是否登陆, 如果未登录则返 回一个 403 响应.

实现客户端代码

  1. 修改 blog_list.html

在 ajax 的回调函数中, 判定响应状态码是否为 403 .

使用 location.assign 进行页面跳转.

  1. 修改 blog_detail.html

    修改方式同上

部署程序, 验证效果.

实现显示用户信息

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

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

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

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

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

约定前后端交互接口

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

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

实现服务器代码

创建 UserServlet

实现客户端代码

  1. 修改 blog_list.html

新增一个 ajax 函数的调用, 以 GET 请求 /user 路径. 在响应回调函数中, 根据响应中的用户名, 更新界面的显示.

  1. 修改 blog_content.html

    修改方式同上

部署程序, 验证效果.

实现注销登陆

约定前后端交互接口

实现服务器代码

创建 LogoutServlet

从 session 中删除掉保存的 User 对象. 响应重定向到 login.html 页面.

客户端代码不需要调整.

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

实现发布博客

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

约定前后端交互接口

实现服务器代码

修改 BlogServlet, 新增 doPost 方法.

实现客户端代码

修改 blog_edit.html 页面结构,

增加 form 标签, action 为 , method 为

给 form 指定 防止编辑器高度不能正确展开.

给标题的 input 标签加上 name 属性 把提交按钮改成

在 里面加上一个隐藏的 textarea

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

部署程序, 验证效果.

实现删除博客

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

需要实现两件事:

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

约定前后端交互接口

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

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

isYourBlog 为 true 表示当前博客就是登陆用户自己写的.

  1. 删除博客

使用 DELETE 请求表示删除一个博客.

实现服务器代码

  1. 给 User 类新增一个字段
  1. 修改 UserServlet

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

  2. 修改 BlogServlet

增加 doDelete 方法, 处理删除逻辑.

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

实现客户端代码

修改 blog_content.html

修改 changeUser 函数, 当获取到的响应中的 isYourBlog 为 true 的时候, 则在导航上添加一个 a

标签作为删除按钮.

当点击删除按钮的时候, 给服务器发送一个 ajax 请求.

部署程序, 验证效果.

总结

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

主要原因:

前后端分离更便于分工协作: 开发开始时, 前端工程师和后端工程师共同约定好交互接口, 然后就可 以分别开发, 各自测试. 直到最终双方开发完毕再在一起联调.

网络带宽越来越大: 因此渲染一个页面多使用几个 HTTP 请求-响应 也问题不大. 用户主机的计算能力越来越强: 无论是手机还是PC, 算力都在突飞猛进的增长. 因此这样的渲染工作 对于客户端来说不是什么负担, 但是能降低服务器的负荷.

更便于多端开发: 比如同一份服务器代码, 就既可以给网页端提供服务, 也可以给手机app 提供服务.

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

使用不同的 HTTP 方法, 表示要执行的动作. 例如 GET 用于获取数据, POST 用于新增数据, PUT 用 于修改数据, DELETE 用于删除数据.

使用 URL 中的 PATH 表示要操作的资源. 使用响应的状态码表示不同的响应结果.

使用 JSON 格式作为 body 中的数据组织方式.

我们上面的代码模仿了 Restful 风格, 但是还不算特别严格. 比如我们在提交博客的时候不是使用

JSON 格式的数据.

实际开发的时候也不必完全拘泥于这样的格式. 都可以灵活对待.

其他功能(课后作业)

实现文章数目统计

提示:

根据当前用户, 查询该用户有多少文章, 显示在博客列表页和博客详情页的左侧用户信息区即可. 这个数据可以通过 GET /user 这个接口返回.

实现编辑博客

提示:

1. 给博客详情页中的导航栏中增加 “编辑按钮”, 也是一个 a 标签, href 为 blogUpdate?blogId=1 , 如 果当前登陆的用户和文章作者相同, 则显示编辑按钮.

2. 创建 BlogUpdateServlet. 点击 “编辑按钮” 进入访问该 Servlet 的 doGet 方法, 并返回一个博客编

辑页面. 这个页面基于 blog_edit.html 修改, 使编辑页面中的编辑框内已经显示出原来文章的内容. 3. 点击 “提交按钮” 则访问 BlogUpdateServlet 的 doPost 方法, 此时根据请求中提交的新的博客标题

和正文, 修改数据库的内容.

4. 给 BlogDao 新增一个 update 方法, 用于修改数据库中的博客数据.

实现思路和之前一致

实现头像管理

提示:

1. 在数据库的 User 表中, 新增一列, 表示该用户的头像图片的地址, 形如 avtar/1.jpg . 使用 alter table 可以修改数据库表结构(同学们自行查找资料, 学习 alter table 的用法).

2. 在博客列表页中, 点击用户头像, 则弹出对话框要求用户选择一张图片. 此时需要使用一个 form 标 签把头像部分包裹起来.

3. 服务器端创建一个 Servlet , 用来处理用户上传头像的请求, 把用户上传的图片保存到

webapps/avator 目录中. 同时修改数据库 User 表中的头像列为新的头像文件名.

4. 修改 blog_list.html, blog_content.html, BlogListServlet, BlogContentServlet 使博客列表页和详 情页能正确显示用户头像. 头像数据通过 GET /user 这个接口返回.

实现用户 github 链接管理

提示:

1. 在数据库的 User 表中, 新增一列, 表示该用户 github 主页. 使用 alter table 可以修改数据库表 结构(同学们自行查找资料, 学习 alter table 的用法).

2. 在博客列表页中, 如果用户当前的 github 为空, 则显示一个 a 标签, 提示 “设置 github 链接”. 点击

后跳转到一个新的页面, 新页面中包含一个 form 用来输入 github 链接.

3. 这个数据也通过 GET /user 这个接口返回.

实现文章分类管理

提示:

1. 在数据库中创建一个分类表, 包含 id, 名称, 所属用户id 三列.

2. 给 blog 表新增一列, 表示该博客的 分类 id (每个文章只能属于一个分类, 每个分类可以包含多个文 章)

3. 在用户新增博客的时候, 让用户在页面中指定当前文章的分类名, 并在提交的时候, 由服务器把分类 名转成分类id, 并保存到 blog 表中.

4. 在显示博客的页面中显示该博客所属的分类.

5. 在左侧用户信息显示区域, 显示出当前用户所拥有的分类的数量(从数据库中统计)

6. 点击该数字, 进入分类详细页, 页面中能显示当前一共都有哪些分类(实现一个新的 Servlet 实现). 点

击具体分类能够展示该分类下的博客列表(复用 blog_list.html 的代码, 但是需要给 BlogDao 新增方 法, 按照分类来查找博客列表).

更多其他功能

大家可以参考 CSDN 等博客网站自由发挥.

猜你喜欢

转载自blog.csdn.net/qq_43398758/article/details/125155944