博客系统目录
- 创建项目
1. 数据库涉及实现
- 创建数据库表
用户,博客,
一对多
blog(blogId,title,content,postTime,userId)
user(userId,username,passwd)
- 数据库代码
- 记录建表SQL语句
- 封装数据库的连接操作DBUtil
- 创建实体类
和数据库表中对应的类
blog表 => Blog类对应Blog的一个对象,就对应表中的一条记录。
user表 => User类对应User的一个对象,就对应表中的一个记录。 - 封装数据库的增删改查 Dao(Data Access Object) 数据访问对象
针对 博客表,创建BlogDao
针对 用户表,创建UserDao
对表提供一些方法,进行增删改查。
- 前后端交互
前后端分离:前端只像后端请求数据,而不请求具体的页面,后端也仅仅是返回数据。这样设定的目的是为了让前端和后端更加解耦合
前后端交互步骤:
- 约定前后端交互接口
- 开发后端接口
- 开发前端接口
2.博客列表页
博客列表页:让博客列表页在加载的时候,通过Ajax给服务器发送一个请求,服务器查数据库,获取到博客列表数据,返回给浏览器,浏览器在根据数据构造页面内容。
- 约定前后端交互接口
在博客列表页,获取博客列表功能,前端发什么请求,后端返回什么响应。
请求:GET /blog
响应:json格式数据(数组)
[
{
blogId:1,
title:"this is a blog",
content:"aaaaaaaaaaa",
postTime:"2023-03-20 20:52:00",
userId:1
},
{
blogId:2,
title:"this is a blog2",
content:"bbbbbbbbbbbbb",
postTime:"2023-03-20 20:53:00",
userId:1
},
]
model:
api:
- 开发后端接口
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BlogDao blogDao = new BlogDao();
List<Blog> blogList = blogDao.selectAll();
//把 blogList 转成符合要求的json格式字符串
String respJson = objectMapper.writeValueAsString(blogList);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(respJson);
}
}
- 开发前端接口
之前写的HTML代码
<div class="container-right">
<div class="blog">
<!-- blog 标题 -->
<div class="title">我的第一篇博客</div>
<div class="date">2022-10-11</div>
<div class="desc">
从今天起,Lorem ipsum, dolor sit amet consectetur adipisicing elit. Dolore, earum, exercitationem iste corporis labore, vero dolores tenetur dolorum quae architecto consectetur aspernatur corrupti dicta excepturi laboriosam culpa voluptatum laudantium minima.
</div>
<a href="#">查看全文 >> </a>
</div>
</div>
</div>
根据上述代码的格式来构造。
<script src="./js/jquery.min.js"></script>
<script>
//在页面加载时,向服务器发起请求,获取博客列表数据
function getBlogs() {
$.ajax({
type:'get',
url:'blog',
success:function(body) {
let containerRight = document.querySelector('.container-right');
for (let blog of body) {
//构造页面内容,参考之前写的HTML代码
//构造整个博客的div
let blogDiv = document.createElement('div');
blogDiv.className = 'blog';
//构造标题
let titleDiv = document.createElement('div');
titleDiv.className = 'title';
titleDiv.innerHTML = blog.title;
blogDiv.appendChild(titleDiv);
//构造发布时间
let dateDiv = document.createElement('div');
dateDiv.className = 'date';
dateDiv.innerHTML = blog.postTime;
blogDiv.appendChild(dateDiv);
//构造博客摘要
let descDiv = document.createElement('div');
descDiv.className = 'desc';
descDiv.innerHTML = blog.content;
blogDiv.appendChild(descDiv);
//构造查看全文按钮
let a = document.createElement('a');
a.innerHTML = '查看全文 >>';
//期望点击之后可以跳转到博客详情页,为了让博客详情页知道时点了哪个博客
a.href = 'blog_detail.html?blogId=' + blogDiv;
blogDiv.appendChild(a);
//把blogDiv加到父元素中
containerRight.appendChild(blogDiv);
}
}
});
}
//记得调用
getBlogs();
</script>
3.博客详情页
点击查看全文按钮,就能跳转到博客详情页。
跳转过去后,在博客详情页发起一个Ajax请求,从服务器获取到当前的博客的具体内容,显示出来
1. 约定前后端交互接口
请求
GET /blog?blogId=1
之前的博客列表中,请求里没有query string,此处博客详情中,带有query string。
如果存在query string,就返回指定的博客详情,不存在,返回博客列表。
这里的路径blog,设置的和博客列表页是同一个,此处是不是同一个都行,这里约定成同一个路径,就直接在BlogServlet中修改,约定成不同的路径,就创建新的类,在新的类中实现。
响应:
HTTP/1.1 200 OK
json数据表示:json字符串
获取博客详情,此处的content是完整的
{
blogId:1,
title:"这是标题",
content:"这是内容",
postTime:"2023-03-22 20:00:00",
userId:1
}
2. 后端代码
如果存在query string,就返回指定的博客详情,不存在,返回博客列表。
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//尝试获取 query string中的 blogId 字段
String blogId = req.getParameter("blogId"); //getParameter规定返回字符串,只能在后续使用时,转成需要的类型
BlogDao blogDao = new BlogDao();
if (blogId == null) {
// query string 不存在,说明本次请求的是博客详情页
List<Blog> blogList = blogDao.selectAll();
//把 blogList 转成符合要求的json格式字符串
String respJson = objectMapper.writeValueAsString(blogList);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(respJson);
} else {
//query string 存在,说明本次请求获取的时指定 id 的博客
Blog blog = blogDao.selectById(Integer.parseInt(blogId));
String respJson = objectMapper.writeValueAsString(blog);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(respJson);
}
}
}
3. 前端代码
数据库中保存的是markdown渲染前的内容,而我们需要在页面中显示markdown渲染后的内容。这就我们需要使用editor.md对markdown内容进行转换。
具体转换,editor.md提供了一个方法:editormd.markdownToHTML
。把md字符串,转成HTML片段。
4.博客登录页
在此处输入用户名和密码,点击登录,就会触发一个HTTP请求。
服务器验证用户名,密码,根据结果判断是否登录成功。
前后端交互接口
请求
POST /login
username=root&password=123
响应
HTTP/1.1 302
Location:blog_list.html
form表单
前端
在页面中加入form表单
后端
5.实现:让页面强制要求登录
当用户访问 博客列表页 / 详情页 / 编辑页时,要求用户必须是已经登录的状态,如果用户没有登录,就会强制跳转到登录页面。
实现思路:
在页面加载的时候,专门发起一个新的Ajax。(一个页面可以发送N个Ajax请求)
以博客列表页为例,会先发送一个请求,获取博客列表,再发一个Ajax获取用户的登录状态。
如果用户已登录,相安无事,如果未登录,页面跳转到登录页。
前后端交互接口
请求 GET /login
响应
响应返回用户登录信息,已登录正常返回,未登录返回userId:0
HTTP /1.1 200 OK
{
userId:1,
username:"zzz"
}
HTTP /1.1 200 OK
{
userId:0,
username:"111"
}
前端
function checkLogin() {
$.ajax({
type: 'get',
url: 'login',
success:function(body) {
if (body.userId && body.userId > 0) {
console.log("当前用户已经登陆");
} else {
//未登录
location.assign('login.html');
}
}
});
}
checkLogin();
后端
6.显示用户信息
博客列表页
前后端交互接口
直接复用检测登录状态的接口。
这里的后端代码已经有了,稍微调整前端的代码,把得到的用户信息显示出来即可。
请求
GET /login
响应
HTTP /1.1 200 OK
{
userId:1,
username:"zhangsan",
password:"123"
}
前端
function checkLogin() {
$.ajax({
type: 'get',
url: 'login',
success:function(body) {
if (body.userId && body.userId > 0) {
console.log("当前用户已经登陆");
//加上功能:把当前用户的名字显示到界面上
let h3 = document.querySelector('.container-left .card h3');
h3.innerHTML = body.username;
} else {
location.assign('login.html');
}
}
});
}
后端
User user = (User) session.getAttribute("user"); //取 user 对象
if (user == null) {
user = new User();
String respJson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
return;
}
//成功取出user对象,resp返回
String respJson = objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
博客详情页
如何判断程序什么时候需要一个新的servlet?
看当前请求的路径,是否已经有了,每个servlet都会绑定到一个请求路径上
前后端交互接口
重新实现。
请求
GET /author?blogId=1
响应
HTTP /1.1 200 OK
{
userId:1,
username:"zhangsan",
password:"123"
}
后端
@WebServlet("/author")
public class authorServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String blogId = req.getParameter("blogId");
if (blogId == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("参数非法,缺少blogId");
return;
}
//根据blogId查找 Blog 对象
BlogDao blogDao = new BlogDao();
Blog blog = blogDao.selectById(Integer.parseInt(blogId));
if (blog == null) {
//博客不存在
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("没有找到指定博客:blogId = " + blogId);
return;
}
//根据blog中的userid找到对应的用户信息
UserDao userDao = new UserDao();
User author = userDao.selectById(blog.getUserId());
String respJson = objectMapper.writeValueAsString(author);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(respJson);
}
}
前端
在blog_detail.html中添加
function getAuthor() {
$.ajax({
type: 'get',
url: 'author' + location.search,
success: function(body) {
// 把username设置到页面上
let h3 = document.querySelector('.container-left .card h3');
h3.innerHTML = body.username;
}
});
}
//函数调用
getAuthor();
7.实现退出登录状态
判定登陆状态:
- 看是否能够查到 http session 对象
- 看session对象中有没有user对象
实现退出登录:
要么删掉http session,要么删掉user,只要删掉一个就行。
httpsession的删除有些麻烦,getSession能够创建/获取会话,但没有删除会话的方法。可以通过设置会话的过期时间来达到类似的效果。
更好的选择是:删除user对象:removeAttribute
前后端交互接口
请求
GET /logout
响应
HTTP /1,1 302
Location:login.html
这里是通过a标签触发的页面跳转,不是Ajax。
a标签和form表单都可以触发页面跳转,Ajax不能触发页面跳转。
前端
<a href="blog_list.html">主页</a>
<a href="blog_edit.html">写博客</a>
<a href="logout">注销</a>
后端
@WebServlet("/logout")
public class logoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession httpSession = req.getSession(false);
if (httpSession == null) {
//未登录状态,直接提示出错
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("未登录");
return;
}
httpSession.removeAttribute("user"); //移除 user 对象
resp.sendRedirect("login.html");
}
}
8. 发布博客
前后端交互接口
使用form表单,得让页面中多个form表单,同时让form里面可以感知到博客的内容
请求
POST /blog
title=标题&content=内容
响应
HTTP /1.1 302
Location:blog_list.html
前端
<div class="blog-edit-container">
<form action="blog" method="post">
<!-- 博客标题编辑区 -->
<div class="title">
<input type="text" id="title" placeholder="输入文章标题" name="title">
<!-- <button id="submit">发布文章</button> -->
<input type="submit" id="submit" value="发布文章">
</div>
<!-- 博客编辑器
这里用id是为了和markdown对接-->
<div id="editor">
<!-- editor.md文档要求的写法,editor.md对form表单是支持的 -->
<textarea name="content" style="display: none;"></textarea>
</div>
</form>
</div>
后端
blogServlet
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//发布博客
//读取请求,构造blog请求,插入数据库即可
HttpSession httpSession = req.getSession(false);
if (httpSession == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("未登录,无法发布");
return;
}
User user = (User) httpSession.getAttribute("user");
if (user == null) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("未登录,无法发布");
return;
}
req.setCharacterEncoding("utf8");
String title = req.getParameter("title");
String content = req.getParameter("content");
if (title == null || "".equals(title) || content == null || "".equals(content)) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("数据空");
return;
}
Blog blog = new Blog();
blog.setTitle(title);
blog.setContent(content);
blog.setUserId(user.getUserId());
blog.setPostTime(new Timestamp(System.currentTimeMillis()));
BlogDao blogDao = new BlogDao();
blogDao.add(blog);
resp.sendRedirect("blog_list.html");
}