服务器版博客系统、前后端交互1

一、准备工作

1). 创建 maven 项目

2). 引入依赖 servlet,jackson,mysql

<dependencies>
    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </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.6.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
</dependencies>

3). 创建必要的目录

在这里插入图片描述

web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>
</web-app>

4). 编写代码

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        resp.getWriter().write("hello");
    }
}

5). 6). 打包部署 (直接基于 smart tomcat)

Content Path: /blog_system

7). 在浏览器中验证

启动服务器,访问:127.0.0.1:8080/blog_system/hello

浏览器页面:hello


二、拷贝前端代码

在这里插入图片描述

当前 V 已经实现的差不多~~ 就可以把前端页面给引入到项目中,直接拷贝到 webapp 目录下,一定要注意,目录不要放错了!

这些文件都可以理解成静态资源
后续打包部署的时候,这些静态资源也会被一并打包部署,后续也就可以在浏览器中访问到了~

C 和 M,先来实现 Model 层,实现数据库相关的代码


三、编写数据库的操作代码

1、创建数据库 / 表结构 => (数据库设计)

设计数据库,需要根据当前的需求来进行设计

博客页面:

  1. 博客列表页:显示博客的列表
  2. 博客详情页:点击博客列表页,上面列出的博客条目,跳转到的页面,显示博客的完整内容
  3. 登录页
  4. 博客编辑页:基于 editor.md 搞了一个 markdown 编辑器~~ 基于这个页面来发布博客了

存储博客,当点击发布的时候,博客被发布到服务器上,就要被存起来
获取博客,在博客列表页和博客详情页,能够拿到博客的内容,还能够进行登录校验~~

设计表,就需要抓住需求中的实体 (关键性的名词)

  • 博客表,用来存储所有的博客数据
  • 用户表,用户登录,就需要用到这个表

在 main 目录下,创建 db.sql

-- 编写建库建表的 sql

-- 创建数据库
create database if not exists java_blog;

use java_blog;

-- 创建博客表
drop table if exists blog;
create table blog (
    blogId int primary key auto_increment,
    title varchar(1024),
    content mediumtext, -- 正文用更长的类型
    userId int, -- 文章作者的 id
    postTime datetime -- 发布时间
);

-- 创建用户表
drop table if exists user;
create table user (
    userId int primary key auto_increment,
    username varchar(128) unique, -- 基于用户名登录,不能重复
    password varchar(128)
);

insert into user values(null, 'zhangsan', '123');
insert into user values(null, 'lisi', '123');

2、封装数据库

2.1、数据库连接 DBUtil

创建 DBUtil 类,放入 model 包下,封装数据库连接的操作

package model;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

// 使用这个类 和数据库建立连接
public class DBUtil {
    
    
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/java_blog?characterEncoding=utf8&userSL=false";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "11111";

    private static volatile DataSource dataSource = null;

    private static DataSource getDataSource() {
    
    
        if (dataSource == null) {
    
    
            synchronized (DBUtil.class) {
    
    
                if (dataSource == null) {
    
    
                    dataSource = new MysqlDataSource();
                    ((MysqlDataSource) dataSource).setURL(URL);
                    ((MysqlDataSource) dataSource).setUser(USERNAME);
                    ((MysqlDataSource) dataSource).setPassword(PASSWORD);
                }
            }
        }
        return dataSource;
    }

    public static Connection getConnection() throws SQLException {
    
    
        return getDataSource().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();
            }
        }
    }
}

2.2、实体类 Blog User

创建实体类,使用实体类来表示数据库中的一条记录

此处主要是创建了 Blog 类 和一个 User 类~~

package model;

import java.sql.Timestamp;

// 每个 model.Blog 对象, 对应 blog 表里的一条记录
public class Blog {
    
    
    private int blogId;
    private String title;
    private String content;
    private int userId;
    private Timestamp postTime;
    
    // 生成它们的 get set 方法...
}
package model;

// 每个 model.User 对象, 期望能够表示 user 表中的一条记录
public class User {
    
    
    private int userId;
    private String username;
    private String password;

    // 生成它们的 get set 方法...
}

2.3、封装针对数据的增删改查

把提供了增删改查这样的类,称为 DAO

BlogDao
package model;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

// 这个类用于去封装博客表的基本操作
public class BlogDao {
    
    
    // 1. 往博客表里, 插入一个博客.
    public void insert(Blog blog) {
    
    
        // JDBC 基本代码
        Connection connection = null;
        PreparedStatement statement = null;
        try {
    
    
            // 1) 和数据库建立连接.
            connection = DBUtil.getConnection();
            // 2) 构造 SQL 语句
            String sql = "insert into blog values(null, ?, ?, ?, now())";
            statement = connection.prepareStatement(sql);
            statement.setString(1, blog.getTitle());
            statement.setString(2, blog.getContent());
            statement.setInt(3, blog.getUserId());
            // 3) 执行 SQL
            statement.executeUpdate();
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 4) 关闭连接, 释放资源
            DBUtil.close(connection, statement ,null);
        }
    }

    // 2. 能够获取到博客表中的所有博客的信息 (用于在博客列表页, 此处每篇博客不一定会获取到完整的正文)
    public List<Blog> selectAll() {
    
    
        List<Blog> blogs = new ArrayList<>();
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
    
    
            connection = DBUtil.getConnection();
            String sql = "select * from blog";
            statement = connection.prepareStatement(sql);
            resultSet = statement.executeQuery();
            while (resultSet.next()) {
    
     // 遍历 获取每条博客 放入 blogs
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                // 博客列表页的摘要 太长了 要截取
                //  这个数字具体写多少, 都可以灵活应对!
                String content = resultSet.getString("content");
                if (content.length() > 50) {
    
    
                    content = content.substring(0, 50) + "...";
                }
                blog.setContent(content);
                blog.setUserId(resultSet.getInt("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blogs.add(blog);
            }
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            DBUtil.close(connection, statement, resultSet);
        }
        return blogs;
    }

    // 3. 能够根据博客 id 获取到指定的博客内容 (用于在博客详情页)
    public Blog selectOne(int blogId) {
    
    
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
    
    
            connection = DBUtil.getConnection();
            String sql = "select * from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, blogId);	
            resultSet = statement.executeQuery();
            // 此处我们是使用 主键 来作为查询条件的. 查询结果, 要么是 1 , 要么是 0.
            if (resultSet.next()) {
    
    
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setUserId(resultSet.getInt("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                return blog;
            }
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            DBUtil.close(connection, statement, resultSet);
        }
        return null; // 没有找到此条博客
    }

    // 4. 从博客表中, 根据博客 id 删除博客.
    public void delete(int blogId) {
    
    
        Connection connection = null;
        PreparedStatement statement = null;
        try {
    
    
            connection = DBUtil.getConnection();
            String sql = "delete from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, blogId);
            statement.executeUpdate();
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        } finally {
    
    
            DBUtil.close(connection, statement, null);
        }
    }

    // 注意, 上述操作是 增删查, 没有改~~
}

UserDao
package model;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

// 提供了针对 用户表 的基本操作
public class UserDao {
    
    
    // 需要实现的操作
    // 针对这个类来说, 就简化的写就行了. 像注册/销号这样的功能就不考虑

    // 主要实现:
    // 1. 根据用户名来查找用户信息.
    //    会在登录逻辑中使用~~
    public User selectByName(String username) {
    
    
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
            try {
    
    
                connection = DBUtil.getConnection();
                String sql = "select * from user where username = ?";
                statement = connection.prepareStatement(sql);
                statement.setString(1, username);
                resultSet = statement.executeQuery();
                // 此处 username 使用 unique 约束, 要么能查到一个, 要么一个都查不到.
                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 throwables) {
    
    
                throwables.printStackTrace();
            } finally {
    
    
                DBUtil.close(connection, statement, resultSet);
            }
            return null;
    }

    // 2. 根据用户 id 来找用户信息.
    //    博客详情页, 就可以根据用户 id 来查询作者的名字, 把作者名字显示出来.
    public User selectById(int userId) {
    
    
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
    
    
            connection = DBUtil.getConnection();
            String sql = "select * from user where userId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, userId);
            resultSet = statement.executeQuery();
            // 此处 username 使用 unique 约束, 要么能查到一个, 要么一个都查不到.
            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 throwables) {
    
    
            throwables.printStackTrace();
        } finally {
    
    
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }
}

Model 搞定了,接下里写 Controller,实现服务器后续代码

在这里插入图片描述


四、博客列表页

1、约定交互接口

针对这里的这四个页面
分别来 “约定前后端交互接口”,“编写服务器代码”,“编写客户端代码”

1.先从博客列表页开始

这个页面需要能够展示出数据库中的博客的列表~~

请求:

GET /blog

响应:

这里的 content 与其说是 “正文” 不如说是正文的摘要~~
博客列表页中,主要是显示都有哪些博客~~
因此,就需要在这个地方把正文进行截取 (如果正文太长,就只截取前面的一小部分)

[
	{
    
    
		blogld: 1,
		title: '这是第一篇博客',content: '这是博客正文',userld: 1,
		postTime: '2022-05-21 20:00:00'),
    },
	{
    
    
		blogld: 2,
		title: '这是第二篇博客',
		content: '这是博客正文',userld: 1,
		postTime: '2022-05-21 20:00:00'
    },
]

2、服务器代码 BlogServlet

创建 BlogServlet 类,放入 controller 包中

package controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

// 通过这个类, 来处理 /blog 路径对应的请求
@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);
        // 构造响应的时候 这里的这两行代码 顺序不能颠倒.
        // 如果先 write 了 body, 再设置 ContentType,设置的 ContentType就会不生效!!
        // 包括说 即使不是写 body,构造 sendRedirect 这种, 其实也是类似的情况~~
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(respJson);
    }
}

——验证 修改 postTime

启动服务器,Postman 构造 Get 请求:127.0.0.1:8080/blog_system/blog

显示 body 为:[]

-- 给博客表中插入点数据, 方便测试.
insert into blog values(null, '这是第一篇博客', '从今天开始, 我要认真学 Java', 1, now());
insert into blog values(null, '这是第二篇博客', '从昨天开始, 我要认真学 Java', 1, now());
insert into blog values(null, '这是第三篇博客', '从前天开始, 我要认真学 Java', 1, now());
insert into blog values(null, '这是第一篇博客', '从今天开始, 我要认真学 C++', 2, now());

在这里插入图片描述

此处得到的响应,和预期的效果,有一点点差别,时间格式~~
预期得到的是一个格式化时间,实际上得到了一个毫秒时间戳,此处就需要把时间戳转成格式化时间
(可以在前端来做,也可以在后端来做)

修改方法:在 Blog 类中,修改 getPostTime

	// 改为格式化时间
    // 把这里的 getter 方法给改了, 不是返回一个时间戳对象, 而是返回一个 String (格式化好的时间)
    public String getPostTime() {
    
    
        // 使用 SimpleDateFormat 来完成时间戳到格式化日期时间的转换.
        // 这个转换过程, 需要在构造方法中指定要转换的格式, 然后调用 format 来进行转换
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyy-mm-dd hh:mm:ss");
        return simpleDateFormat.format(postTime);
    }

重新启动,构造,此时的结果:"postTime": "2022-07-26 05:07:16"


3、客户端代码 blog_list.html

在页面加载的时候,让页面通过 ajax 访问服务器,获取到数据库中的博客数据,并且填到页面中!

在 blog_list.html 最后加入 ajax:

<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
    // 在页面加载的时候, 通过 ajax 给服务器发送数据, 获取到博客列表信息, 并且显示在界面上. 
    function getBlogList() {
    
    
        $.ajax({
    
    
            type: 'get',
            url: 'blog',
            success: function(body) {
    
    
                // 获取到的 body 就是一个 js 对象数组, 每个元素就是一个 js 对象, 根据这个对象构造 div
                // 1. 先把 .right 里原有的内容给清空
                let rightDiv = document.querySelector('.right');
                rightDiv.innerHTML = '';
                // 2. 遍历 bo	dy, 构造出一个个的 blogDiv
                for (let blog of body) {
    
    
                    let blogDiv = document.createElement('div'); // 创建标签
                    blogDiv.className = 'blog'; // class 名
                    // -- 构造标题
                    let titleDiv = document.createElement('div');
                    titleDiv.className = 'title';
                    titleDiv.innerHTML = blog.title;
                    blogDiv.appendChild(titleDiv); // 挂入 dom 树
                    // -- 构造发布时间
                    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 = '查看全文 &gt;&gt;';
                    // 此处希望点击之后能够跳转到 博客详情页 !!
                    // 这个跳转过程需要告知服务器要访问的是哪个博客的详情页. 根据博客 id
                    a.href = 'blog_detail.html?blogId=' + blog.blogId;
                    blogDiv.appendChild(a);

                    // 把 blogDiv 挂到 dom 树上!
                    rightDiv.appendChild(blogDiv);
                }
            },
            error: function() {
    
    
                alert("获取博客列表页失败!");
            }
        });
    }
    // 调用
    getBlogList();
</script>

——验证 修改博客列表顺序、加载页面

启动服务器,访问:127.0.0.1:8080/blog_system/blog_list.html

带到了博客列表页,貌似没有问题,但是还有两个小问题:

问题一: 当前拿到的博客列表顺序,是不太科学的~~

添加一条博客信息:

insert into blog values(null, '这是第二篇博客', '从昨天开始, 我要认真学 C++', 2, now());

观察发现,加入的博客在最下方,不利于用户访问

需要保证咱们最新的博客得在最上面才行!!
如果在 sql 中没有指定 order by,此时查询到的结果顺序,是不确定的!! 因此,在 sql 中不加 order by 之前,是不应该依赖查询结果的顺序的!!!

可能你在当前数据库这里发现查询结果顺序都是一定的,随着你插入删除更多的数据
甚至说你更新了数据库版本,此时的查询结果都可能存在发生变化的风险~~

修改代码: BlogDao 类,修改 selectAll 方法中的查询 sql

String sql = "select * from blog order by postTime desc"; // 降序 最新数据放上面

问题二: 刷新页面的时候,内容一哆嗦~~

打断点观察:旧的内容 (测试时写死的数据) => 新的内容 (从数据库里查的数据)
是通过网络来交互的,花个几十 ms 都很正常,人眼是能捕捉到这个变化的~~

直接把旧的测试数据给删了就行了~~

在这里插入图片描述

<!-- 右侧内容详情 -->
<div class="right">
    <!-- .blog 对应一个博客 -->
    <!-- <div class="blog">
        博客标题
        <div class="title">
            我的第一篇博客
        </div>
        博客发布时间
        <div class="date">
            2022-05-05 15:00:00
        </div>
        博客摘要
        <div class="desc">
            从今天起,我要认真写博客,Lorem ipsum dolor sit amet consectetur adipisicing elit. Impedit fugit libero deleniti a distinctio exercitationem mollitia adipisci repudiandae aliquid reiciendis, quae consequatur laboriosam illum et dicta iure, error eligendi iusto?
        </div>
        ">>" 转义
        <a href="#">查看全文 &gt;&gt; </a>
    </div> -->
</div>

猜你喜欢

转载自blog.csdn.net/qq_56884023/article/details/125930259