博客系统(前后端分离)

✏️作者:银河罐头
系列专栏:JavaEE

“种一棵树最好的时间是十年前,其次是现在”

目录

  • 准备工作
  • 数据库设计
    • 表设计
  • 封装数据库的连接操作
  • 创建实体类
  • 封装数据库的增删改查
  • 实现博客列表页
    • 约定前后端交互接口
    • 开发后端代码
    • 开发前端代码
  • 实现博客详情页
    • 约定前后端交互接口
    • 开发后端代码
    • 开发前端代码
  • 实现博客登录页
    • 约定前后端交互接口
    • 开发前端代码
    • 开发后端代码
  • 实现强制要求登陆
    • 约定前后端交互接口
    • 开发后端代码
    • 开发前端代码
  • 实现显示用户信息
    • 约定前后端交互接口
    • 列表页
    • 详情页
  • 退出登录状态
    • 约定前后端交互接口
    • 开发后端代码
    • 开发前端代码
  • 发布博客
    • 约定前后端交互接口
    • 开发后端代码
    • 开发前端代码

准备工作

创建项目,引入依赖,把之前写的前端页面拷贝进去。

依赖主要是:servlet, mysql, jackson

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

数据库设计

表设计

在当前的博客系统中,主要涉及到 2 个实体,博客 和 用户。

创建 2 张表,来表示博客和用户。用户和博客之间的关系是一对多。

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

把一些基本的数据库操作先封装好,以备后用。

-- 这个文件主要写建库建表语句
-- 建议 在建表的时候把 sql 语句保留下来,以便后续部署其他机器的时候就方便了。

create database if not exists java_blog_system;
use java_blog_system;
-- 删除旧表,重新创建新表
drop table if exists user;
drop table if exists blog;

-- 真正创建表
create table blog(
    blogId int primary key auto_increment,
    title varchar(128),
    content varchar(4096),
    postTime datetime,
    useId int
);

create table user(
    userId int primary key auto_increment,
    username varchar(20) unique, -- 要求用户名和别人不重复
    password varchar(20)
);

封装数据库的连接操作

public class DBUtil {
    private static DataSource dataSource = new MysqlDataSource();
    static {
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java_blog_system?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("123456");
    }

    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
        if(resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

创建实体类

实体类,和表中记录对应的类。

blog 表,Blog 类对应。Blog 的一个对象,就对应表中的一条记录。

user 表,User 类对应。User 的一个对象,就对应表中的一条记录。

实体类里要有哪些属性,是和表里的列是密切相关的。

public class Blog {
    private int blogId;
    private String title;
    private String content;
    private Timestamp postTime;
    private int userId;

    public int getBlogId() {
        return blogId;
    }

    public void setBlogId(int blogId) {
        this.blogId = blogId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Timestamp getPostTime() {
        return postTime;
    }

    public void setPostTime(Timestamp postTime) {
        this.postTime = postTime;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }
}
public class User {
    private int userId;
    private String username;
    private String password;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

封装数据库的增删改查

针对博客表,创建 BlogDao

针对用户表,创建 UserDao

提供一些方法,来进行增删改查。

Dao, Data Access Object 访问数据的对象

//通过这个类,封装对博客表的基本操作
public class BlogDao {
    //1.新增一个博客
    public void add(Blog blog) {

    }
    //2.根据 博客 id 来查询博客(博客详情页中)
    public Blog selectById(int blogId){
        return null;
    }
    //3.查询出数据库中所有的博客列表(博客列表页)
    public List<Blog> selectAll(){
        return null;
    }
    //4.删除指定博客
    public void delete(int blogId){

    }
}

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

//通过这个类,封装对博客表的基本操作
public class BlogDao {
    //1.新增一个博客
    public void add(Blog blog) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            //1.建立连接
            connection = DBUtil.getConnection();
            //2.构造 sql
            String sql = "insert into blog values(null, ?, ?, ?, ?)";
            statement = connection.prepareStatement(sql);
            statement.setString(1, blog.getTitle());
            statement.setString(2,blog.getContent());
            statement.setTimestamp(3,blog.getPostTime());
            statement.setInt(4,blog.getUserId());
            //3.执行 sql
            statement.executeUpdate();

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //4.断开连接,释放资源
            DBUtil.close(connection, statement, null);
        }
    }
    //2.根据 博客 id 来查询博客(博客详情页中)
    public Blog selectById(int blogId){
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //1.和数据库建立连接
            connection = DBUtil.getConnection();
            //2.构造 SQL
            String sql = "select * from blog while blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,blogId);
            //3.执行 SQL
            resultSet = statement.executeQuery();
            //4.遍历结果集合
            //blogId 是自增主键,是唯一的。所以要么是没有查到,要么是查到了一条记录
            //此处可以不使用 where, 直接 if 判定即可
            if (resultSet.next()){
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blog.setUserId(resultSet.getInt("userId"));
                return blog;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //5.释放资源,断开连接
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }
    //3.查询出数据库中所有的博客列表(博客列表页)
    public List<Blog> selectAll(){
        List<Blog> blogs = new ArrayList<>();
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //1.建立连接
            connection = DBUtil.getConnection();
            //2.构造 SQL 语句
            String sql = "select * from blog";
            statement = connection.prepareStatement(sql);
            //3.执行 sql 语句
            statement.executeQuery();
            //4.遍历结果集合
            while(resultSet.next()){
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                //注意这里的正文,博客列表页不需要把整个正文都显示出来
                String content = resultSet.getString("content");
                if(content.length() >= 100){
                    content = content.substring(0,100) + "...";
                }
                blog.setContent(content);
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blog.setUserId(resultSet.getInt("userId"));
                blogs.add(blog);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection, statement, resultSet);

        }
        return blogs;
    }
    //4.删除指定博客
    public void delete(int blogId){
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            //1.建立连接
            connection = DBUtil.getConnection();
            //2.构建 SQL 语句
            String sql = "delete from blog while blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,blogId);
            //3.执行 sql
            statement.executeUpdate();

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //4.关闭连接
            DBUtil.close(connection,statement,null);
        }
    }
}
//针对用户表提供的基本操作
//由于没写注册功能,此处就不写 add 了
//也没有用户删号这个功能,也就不必 delete
public class UserDao {
    //根据 userId 来查用户信息
    public User selectById(int userId){
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //1.建立连接
            connection = DBUtil.getConnection();
            //2.构造 SQL
            String sql = "select * from user where userId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,userId);
            //3.执行 sql
            resultSet = statement.executeQuery();
            //4.遍历结果集
            if(resultSet.next()){
                User user = new User();
                user.setUserId(resultSet.getInt("userId"));
                user.setUsername(resultSet.getString("username"));
                user.setPassword(resultSet.getString("password"));
                return user;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }
    //根据 username 来查用户信息(登录的时候)
    public User selectByUsername(String username){
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //1.建立连接
            connection = DBUtil.getConnection();
            //2.构造 SQL
            String sql = "select * from user where username = ?";
            statement = connection.prepareStatement(sql);
            statement.setString(1,username);
            //3.执行 sql
            resultSet = statement.executeQuery();
            //4.遍历结果集
            if(resultSet.next()){
                User user = new User();
                user.setUserId(resultSet.getInt("userId"));
                user.setUsername(resultSet.getString("username"));
                user.setPassword(resultSet.getString("password"));
                return user;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }
}

实现博客列表页

当前博客列表页上的数据都是写死的,正确的做法应该是从服务器获取数据并显示到页面上。

让博客列表页,加载的时候,通过 ajax 给服务器发一个请求,服务器查数据库获取博客列表数据,返回给浏览器,浏览器根据数据构造页面内容。

这样的交互过程,称为 “前后端分离”。

前端只是向后端索要数据,而不是请求具体的页面。后端也仅仅是返回数据。

这样设定的目的就是让前端和后端更加解耦。

由浏览器进行具体的页面渲染,减少了服务器的工作量。

上述是实现博客列表页的基本思路。

接下来需要:

1.约定前后端交互接口

获取博客列表功能,前端要发啥请求,后端要返回啥响应。

2.开发后端代码

3.开发前端代码

约定前后端交互接口

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

开发后端代码

@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> blogs = blogDao.selectAll();
        //需要 把 blogs 转成符合要求的 json 字符串
        String respJson = objectMapper.writeValueAsString(blogs);
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(respJson);
    }
}

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

把 servlet 放到 api 包里,api 不一定非得是 类/方法,也可以是 “网络请求”(处理 http 请求,返回 http 响应)

开发前端代码

在博客列表页加载过程中,通过 ajax 访问服务器数据,再把拿到的数据构造到页面中。

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

<script src="./js/jquery.min.js"></script>
    <script>
        //在页面加载的时候,向服务器发起请求,获取博客列表数据
        function getBlogs() {
            $.ajax({
                type:'get',
                url: 'blog',
                success: function(body) {
                    //响应的正文是 json 字符串,已经被 jquery 自动解析成 js 对象数组了
                    // for 循环遍历即可
                    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 = '查看全文 >>';
                        //希望点击之后能跳转到博客详情页
                        //为了让博客详情页知道是点了哪个博客,把 blogId 传过去
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                        blogDiv.appendChild(a);

                        //把 blogDiv 加到父元素中
                        containerRight.appendChild(blogDiv);
                    }
                }
            });
        }
        //记得调用函数
        getBlogs();
    </script>

此时,博客列表页实现完成。

博客系统(前后端分离)_第7张图片

此时看的的博客列表页是空着的,因为博客列表的数据来自于数据库,而现在数据库是空着的。

image-20230321195908856

-- 构造测试数据
insert into blog values(1, '这是我的第一篇博客', '从今天开始我要认真敲代码', now(),1);
insert into blog values(2, '这是我的第二篇博客', '从昨天开始我要认真敲代码', now(),1);
insert into blog values(3, '这是我的第三篇博客', '从前天开始我要认真敲代码', now(),1);

此处只是把 idea 当做是个 记事本,来记录下 sql 而已。真正要执行,要复制到 sql 客户端里执行。

博客系统(前后端分离)_第8张图片

有 2 个 bug,

1.不应该显示时间戳,而应该显示格式化时间

需要用到一个 格式化时间的类,转换一下。

SimpleDateFormat

博客系统(前后端分离)_第9张图片

public Timestamp getPostTimestamp() {
    return postTime;
}

public String getPostTime(){
    //把时间戳转换成格式化时间
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    return simpleDateFormat.format(postTime);
}

2.顺序,新的博客在上面,老的博客在下面。

image-20230321204218665

实现博客详情页

接下来实现博客详情页,点击"查看全文"按钮,就能跳转到博客详情页中。跳转之后,在博客详情页中,对服务器发起 ajax 请求,从服务器获取数据,然后显示出来。

博客系统(前后端分离)_第10张图片

约定前后端交互接口

博客系统(前后端分离)_第11张图片

开发后端代码

博客系统(前后端分离)_第12张图片

@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();
        //尝试获取下 query string 中的 blogId字段
        String blogId = req.getParameter("blogId");
        if (blogId == null) {
            //说明 query string 不存在,说明这次请求是在获取博客列表页

            List<Blog> blogs = blogDao.selectAll();
            //需要 把 blogs 转成符合要求的 json 字符串
            String respJson = objectMapper.writeValueAsString(blogs);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respJson);
        } else {
            //说明 query string 存在,说明这次请求是在获取博客详情页
            Blog blog = blogDao.selectById(Integer.parseInt(blogId));
            if(blog == null) {
                System.out.println("当前 blogId = " + blogId + " 对应的博客不存在!");
            }
            String respJson = objectMapper.writeValueAsString(blog);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respJson);
        }
    }
}

开发前端代码

在 blog_datail.html 中,加入 ajax,来获取数据。

博客系统(前后端分离)_第13张图片

博客系统(前后端分离)_第14张图片

博客系统(前后端分离)_第15张图片

博客系统(前后端分离)_第16张图片

<script src="js/jquery.min.js"></script>
<!-- 保证 这几个 js 的加载要在 jquery 之后,因为 edit.md 依赖 jquery-->
<script src="editor.md/lib/marked.min.js"></script>
<script src="editor.md/lib/prettify.min.js"></script>
<script src="editor.md/editormd.js"></script>
<script>
    $.ajax({
    type: 'get',
    url: 'blog' + location.search,
    success: function(body) {//回调函数
        //处理响应结果,此处的 body 就表示一个博客的 js 对象
        //1.更新标题
        let titleDiv = document.querySelector('.title');
        titleDiv.innerHTML = body.title;
        //2.更新日期
        let dateDiv = document.querySelector('.date');
        dateDiv.innerHTML = body.postTime;
        //3.更新博客正文
        //此处不应该直接把博客内容填充到标签里
        editormd.markdownToHTML('content',{ markdown: blog.content });
    }
});
</script>

代码改完之后,重新启动服务器,发现此时博客详情页的结果还是之前未修改的状态。

博客系统(前后端分离)_第17张图片

始端用户访问加速节点时,如果该节点有缓存住了要被访问的数据时就叫做命中,如果没有的话需要回原服务器取,就是没有命中。

博客系统(前后端分离)_第18张图片

缓冲问题解决了,但是页面仍然不是预期的效果。

博客系统(前后端分离)_第19张图片

博客系统(前后端分离)_第20张图片

博客系统(前后端分离)_第21张图片

修改上述代码之后,发现正文有了,但是标题没出来。

就需要抓包,看下服务器返回结果是否符合预期,就可以确定是前端问题还是后端问题了。

image-20230323192807260

响应结果是正确的,后端代码应该没问题。

接下来检查前端代码。

博客系统(前后端分离)_第22张图片

解决方案,把选择器写的更准确一些。

image-20230323193642071

博客系统(前后端分离)_第23张图片

image-20230323195937105

博客系统(前后端分离)_第24张图片

实现博客登录页

博客系统(前后端分离)_第25张图片

此处输入用户名,密码,点击登录,就会触发一个 http 请求。服务器验证用户名和密码,如果登陆成功,就跳转到博客列表页。

当前还只是一个 输入框,还不能提交请求,需要给改成 form 表单。

约定前后端交互接口

博客系统(前后端分离)_第26张图片

开发前端代码

在页面里加上 form 表单,使点击登录操作能 触发请求。


<div class="login-dialog">
    <form action="login" method="post">
        <h3>登录h3>
        <div class="row">
            <span>用户名span>
            <input type="text" id="username" placeholder="手机号/邮箱" 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>

博客系统(前后端分离)_第27张图片

博客系统(前后端分离)_第28张图片

请求已经构造出来了。

开发后端代码

此处需要价格 servlet 来处理 登录请求。

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置请求的编码,告诉 servlet 按照啥格式理解请求
        req.setCharacterEncoding("utf8");
        //设置响应的编码,告诉 servlet 以啥样的格式构造响应
//        resp.setCharacterEncoding("utf8");
        resp.setContentType("text/html;charset=utf8");
        //1.读取参数中的用户名,密码
        //注意,如果用户名密码中存在中文,这里读取可能会乱码
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if(username == null || "".equals(username) || password == null || "".equals(password)){
            //登陆失败!
            String html = "

登录失败! 缺少 username 或者 password 字段

"
; resp.getWriter().write(html); return; } //2.读一下数据库,看用户名是否存在,密码是否匹配 UserDao userDao = new UserDao(); User user = userDao.selectByUsername(username); if(user == null){ //用户不存在 String html = "

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

"
; resp.getWriter().write(html); return; } if(!password.equals(user.getPassword())){ //密码错误 String html = "

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

"
; resp.getWriter().write(html); return; } //3.用户名密码验证通过,登陆成功,接下来创建会话,使用该会话保存用户信息 HttpSession session = req.getSession(true); session.setAttribute("username", username); //4.进行重定向,跳转到博客列表页 resp.sendRedirect("blog_list.html"); } }

这样还是无法登录成功,因为数据库没有保存用户名密码。

博客系统(前后端分离)_第29张图片

构造一些测试数据,保存到数据库里。

博客系统(前后端分离)_第30张图片

登陆成功。

实现强制要求登陆

当用户访问博客列表页/详情页/编辑页,要求用户必须是已登录的状态,如果用户还没有登录,就会强制跳转到登录页面。

博客系统(前后端分离)_第31张图片

约定前后端交互接口

博客系统(前后端分离)_第32张图片

开发后端代码

博客系统(前后端分离)_第33张图片

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setContentType("application/json;charset=utf8");
    //使用这个方法来获取用户的登录状态
    HttpSession session = req.getSession(false);
    if(session == null){
        User user = new User();
        String respJson = objectMapper.writeValueAsString(user);
        resp.getWriter().write(respJson);
        return;
    }
    User user = (User)session.getAttribute("user");
    if(user == null){
        user = new User();
        String respJson = objectMapper.writeValueAsString(user);
        resp.getWriter().write(respJson);
        return;
    }
    //确实取出了 user 对象,直接返回即可
    String respJson = objectMapper.writeValueAsString(user);
    resp.getWriter().write(respJson);
}

开发前端代码

image-20230324111917355

function checkLogin() {
    $.ajax({
        type: 'get',
        url: 'login',
        success: function(body) {
            if(body.userId && body.userId > 0){
                //登陆成功
                console.log("当前用户已登录!");
            } else {
                //当前未登录,强制跳转到登录页
                location.assign('blog_login.html');
            }
        }
    });
}
checkLogin();

把这段代码加到 列表页,详情页,编辑页的代码中。

重启服务器之后,之前的登录状态就没了。

博客系统(前后端分离)_第34张图片

实现显示用户信息

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

博客系统(前后端分离)_第35张图片

这个地方时写死的,希望能够动态生成。

1.如果是博客列表页,此处显示登录用户的信息。

2.如果是博客详情页,此处显示该文章的作者。

比如我以"张三"的身份登录,查看博客列表页时,左侧应该显示"张三"的用户信息,然后我点开李四写的博客,跳转到博客详情页,应该显示作者"李四"的信息。

约定前后端交互接口

博客系统(前后端分离)_第36张图片

列表页

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('blog_login.html');
            }
        }
    });
}

博客系统(前后端分离)_第37张图片

详情页

@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);
    }
}
function getAuthor() {
    $.ajax({
        type: 'get',
        url: 'author',
        success: function(body) {
            //把 username 设置到界面上
            let h3 = document.querySelector('.container-left .card h3');
            h3.innerHTML = body.username;
        }
    });
}
getAuthor();

博客系统(前后端分离)_第38张图片

博客系统(前后端分离)_第39张图片

抓包后发现是少了 query string.

改下前端代码。

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();

博客系统(前后端分离)_第40张图片

退出登录状态

注销(sign out)

判定登录状态:

1.看是否能查到 http session 对象;

2.看 session 对象里有没有 user 对象

博客系统(前后端分离)_第41张图片

实现退出登录,要么把 session 干掉,要么把 user 干掉。只要干掉一个就可以。

HttpSession 对象要想干掉,麻烦点。getSession 能够创建/获取会话,没有删除会话的方法。直接删除还不好删,可以通过设置会话的过期时间来达到类似的效果,但并不优雅。

更好的办法是把 user 干掉,通过 removeAttribute 就删了。

之前的逻辑中, httpSession 和 user 是一荣俱荣一损俱损的情况,但是引入注销逻辑后,就出现有 httpSession 没 user 的情况。

明确思路之后,先设计前后端交互接口。

约定前后端交互接口

博客系统(前后端分离)_第42张图片

博客系统(前后端分离)_第43张图片

开发后端代码

@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");
        resp.sendRedirect("login.html");
    }
}

开发前端代码

把 blog_detail.html, blog_edit.html, blog_list.html 这几个的 a 标签改下就行。

博客系统(前后端分离)_第44张图片

发布博客

博客系统(前后端分离)_第45张图片

写博客写了一些内容,点击"发布文章",没有反应。

约定前后端交互接口

博客系统(前后端分离)_第46张图片

开发后端代码

@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();
        //尝试获取下 query string 中的 blogId字段
        String blogId = req.getParameter("blogId");
        if (blogId == null) {
            //说明 query string 不存在,说明这次请求是在获取博客列表页

            List<Blog> blogs = blogDao.selectAll();
            //需要 把 blogs 转成符合要求的 json 字符串
            String respJson = objectMapper.writeValueAsString(blogs);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respJson);
        } else {
            //说明 query string 存在,说明这次请求是在获取博客详情页
            Blog blog = blogDao.selectById(Integer.parseInt(blogId));
            if(blog == null) {
                System.out.println("当前 blogId = " + blogId + " 对应的博客不存在!");
            }
            String respJson = objectMapper.writeValueAsString(blog);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respJson);
        }
    }

    @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;
        }
        //确保登陆之后,就可以把作者拿到了

        //获取博客标题和正文
        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 blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        blog.setUserId(user.getUserId());
        //发布时间,在 java 中生成 / 在数据库中生成都行
        blog.setPostTime(new Timestamp(System.currentTimeMillis()));
        //插入数据库
        BlogDao blogDao = new BlogDao();
        blogDao.add(blog);
        //跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }
}

开发前端代码

把页面改造下,能够构造出请求。

博客系统(前后端分离)_第47张图片

<div class="blog-edit-container">
    <form action="blog" method="post">
        
        <div class="title">
            <input type="text" id="title" placeholder="输入文章标题" name="title">
            <input type="submit" id="submit" value="发布文章">
        div>
        
        <div id="editor">
            <textarea name="content" style="display: none;">textarea>
        div>
    form>
div>

博客系统(前后端分离)_第48张图片

代码改完之后,再次运行,发现只显示一小段了。

前端代码有问题,看 chrome 开发者工具。

image-20230330114134163

之前给编辑器设置的高度。

博客系统(前后端分离)_第49张图片

<form action="blog" method="post" style="height: 100%;">

博客系统(前后端分离)_第50张图片

发现乱码了?!

乱码是提交博客的时候乱的,还是获取博客的时候乱的?

这里大概率是提交的时候乱的,因为获取数据这个功能前面已经测试过了,而提交这个功能还没有测试过。

只要看下数据库是不是乱的。

博客系统(前后端分离)_第51张图片

应该是提交的时候就乱了。

把乱码的第4条删掉。

博客系统(前后端分离)_第52张图片

//获取博客标题和正文
req.setCharacterEncoding("utf8");
String title = req.getParameter("title");
String content = req.getParameter("content");

博客系统(前后端分离)_第53张图片

你可能感兴趣的:(JavaEE初阶,java,数据库,mysql)