博客系统(Servlet实现)

news2025/2/24 17:32:58

目录

1.准备工作

2.数据库设计

2.1表设计

2.2封装数据库操作代码

2.3创建 Blog 类 和 User 类

2.4创建 BlogDao 类和 UserDao 类

3.读取博客列表功能

3.1约定前后端交互接口

3.2实现服务器代码

3.3实现客户端代码

4.实现博客详情

4.1约定前后端交互接口

4.2实现服务器代码

4.3实现客户端代码

 5.实现登录功能

 5.1约定前后端交互接口

5.2实现服务器代码

5.3实现客户端代码

6.实现强制要求登陆

6.1实现服务器代码

6.2实现服务器代码

7.实现显示用户信息

7.1约定前后端交互接口

7.2实现服务器代码

7.3实现客户端代码

8.实现注销登陆

8.1约定前后端交互接口

8.2实现服务器代码

9.实现发布博客

9.1约定前后端交互接口

9.2实现服务器代码

9.3实现客户端代码


如果想要源码可以私信作者

1.准备工作

1.1创建web项目

1.2创建目录结构

1.3配置pom.xml和web.xml

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<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>messageWall</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <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.14.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>

    </dependencies>


</project>

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>

2.数据库设计

2.1表设计

当前需要设计两张表, 文章表和用户表

文章表:

create table blog (
                      blogId int primary key auto_increment,
    -- 博客的标题
                      title varchar(256),
    -- 博客的正文
                      content varchar(4096),
    -- 博客的作者
                      userId int,
    -- 博客的发布时间
                      postTime datetime
);

用户表:

create table user (
                      userId int primary key auto_increment,
    -- 用户名, 约定用户名不能重复.
                      username varchar(64) unique,
    -- 密码
                      password varchar(64)
    -- user 里还可以增加很多别的属性. github 链接, 头像链接.....
);

完整SQL文件

-- 编写 SQL 完成建库建表操作.

create database if not exists blog_system charset utf8;

use blog_system;

drop table if exists user;
drop table if exists blog;

create table blog (
                      blogId int primary key auto_increment,
    -- 博客的标题
                      title varchar(256),
    -- 博客的正文
                      content varchar(4096),
    -- 博客的作者
                      userId int,
    -- 博客的发布时间
                      postTime datetime
);

create table user (
                      userId int primary key auto_increment,
    -- 用户名, 约定用户名不能重复.
                      username varchar(64) unique,
    -- 密码
                      password varchar(64)
    -- user 里还可以增加很多别的属性. github 链接, 头像链接.....
);

-- 构造一些初始数据, 方便后续的测试.
insert into user values(1, 'zhangsan', '123'), (2, 'lisi', '123');

insert into blog values(1, '这是我的第一篇博客', '从今天开始我要好好敲代码', 1, '2023-09-23 19:00:00');
insert into blog values(2, '这是我的第二篇博客', '从昨天开始我要好好敲代码', 1, '2023-09-24 19:00:00');
insert into blog values(3, '这是我的第三篇博客', '从前天开始我要好好敲代码', 1, '2023-09-25 19:00:00');

2.2封装数据库操作代码

创建DBUtil类,通过单例模式来获取数据库连接

// 通过这个类, 把数据库建立连接的逻辑进行封装.
public class DBUtil {
    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("jdbc:mysql://127.0.0.1:3306/blog_system?useSSL=false&characterEncoding=utf8");
                    ((MysqlDataSource)dataSource).setUser("root");
                    ((MysqlDataSource)dataSource).setPassword("123456");
                }
            }
        }
        return dataSource;
    }

    // 提供一个方法, 和数据库建立连接
    public static Connection getConnection() {
        try {
            return getDataSource().getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 提供一个方法, 和数据库断开连接.
    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
        // 如果把 3 个 close 都放到同一个 try 中, 一旦前面的 close 出现异常, 就会导致后续的 close 执行不到了.
        // 相比之下, 还是分开写 try 比较好.
        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.3创建 Blog 类 和 User

Blog表示一篇博客,此处省略get、set和toString方法

public class Blog {
    private int blogId;
    private String title;
    private String content;
    private int userId;
    // SQL 里有 timestamp 类型, 还有 datetime 类型.
    // 使用 SQL 时, 推荐使用 datetime, 因为 timestamp 只有 4 字节, 2038 年就不够用了.
    // 但是 Java 代码中的 Timestamp 是可以使用的.
    private Timestamp postTime;
}

User表示一个用户,此处省略get、set和toString方法

public class User {
    private int userId;
    private String username;
    private String password;
}

2.4创建 BlogDao 类和 UserDao

理解 DAO

DAO 全称为 "data access object",主要的功能就是对于某个数据库表进行增删改查.   一般每张数据库表会对应一个 DAO . 这是一种给类命名的习惯做法, 并不是强制要求.

 创建BlogDao类,针对博客表进行操作

// 通过这个类, 封装针对 blog 表的增删改查操作
public class BlogDao {
    // 1. 新增一个博客
    //    调用 insert 的时候, 需要先构造一个 Blog 对象.
    //    作为 参数 传递给 insert. 再由 insert 内部完成数据库的插入操作.
    public void insert(Blog blog) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            // 1. 和数据库建立连接.
            connection = DBUtil.getConnection();
            // 2. 构造 SQL 语句.
            //    此处的博客发布时间, 正好是执行 SQL 的时刻. 直接使用 SQL 里的 now() 库函数, 完成获取当前时间工作.
            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. 查询 blog 表里所有的博客
    //    正常开发中, 一般不会直接把整个表里的数据都查询出来, 一般都是要指定筛选条件/最大条数的.
    //    此处咱们先不考虑这么多, 就简单粗暴全都查询就行了.
    public List<Blog> getBlogs() {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        List<Blog> blogs = new ArrayList<>();
        try {
            // 1. 和数据库建立连接
            connection = DBUtil.getConnection();
            // 2. 构造 SQL 语句
            String sql = "select * from blog order by postTime desc";
            statement = connection.prepareStatement(sql);
            // 3. 执行 SQL
            resultSet = statement.executeQuery();
            // 4. 遍历结果集合
            while (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"));
                blogs.add(blog);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        // 如果前面的查询出现问题, blogs 就会得到空的 List
        return blogs;
        // return null;
    }

    // 3. 指定 blogId, 查询某一个博客.
    public Blog getBlog(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();
            // 由于此处是按照 blogId 来查询, blogId 又是主键.
            // 查询到的结果要么是 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 throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }

    // 4. 指定博客进行删除
    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 , 实现对于用户表的增删改查.

// 使用这个类封装针对 user 表的增删改查
public class UserDao {
    // 对于新增 user, 主要就是需要实现一个 "注册" 功能. 但是当前不打算实现注册.
    // 对于删除 user, 主要就是需要实现一个 "注销" 功能. 但是当前也不打算实现注销.

    // 1. 根据 userId 来查询用户信息. (后续根据博客查询出作者详情)
    public User getUserById(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();
            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. 根据 username 来查询用户信息. (实现登陆效果)
    public User getUserByName(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();
            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;
    }
}

3.读取博客列表功能

3.1约定前后端交互接口

[请求]
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 格式的数据. 

3.2实现服务器代码

创建 BlogServlet 、实现 doGet, 完成读取博客列表的功能.如果blogId为空则显示博客列表页面,如果点击了查看详情,则就会有blogId,则显示博客详情

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 查询数据库, 获取到数据之后, 构造成要求的 json 格式并返回.
        // 先尝试获取下 blogId 这个参数, 看看能不能获取到.
        BlogDao blogDao = new BlogDao();
        String blogId = req.getParameter("blogId");
        if (blogId == null) {
            // 此时说明是获取博客列表. 没有 blogId 参数
            List<Blog> blogs = blogDao.getBlogs();
            String respJson = objectMapper.writeValueAsString(blogs);
            resp.setContentType("application/json; charset=utf8");
            resp.getWriter().write(respJson);
        } else {
            // 此时说明是获取博客详情. 有 blogId 参数.
            Blog blog = blogDao.getBlog(Integer.parseInt(blogId));
            if (blog == null) {
                // 返回一个 id 为 0 的 blog 对象. 前端再根据这里进行判定.
                blog = new Blog();
            }
            String respJson = objectMapper.writeValueAsString(blog);
            resp.setContentType("application/json; charset=utf8");
            resp.getWriter().write(respJson);
        }
    }

3.3实现客户端代码

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

服务器返回的响应是一个 JSON 格式的数据, 根据这个响应数据使用 DOM API 构造页面内容. 

响应中的 postTime 字段为 ms级时间戳, 需要转成格式化日期.

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

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

function getBlogs() {
        $.ajax({
            type: 'get',
            url: 'blog',
            success: function (body) {
                // 就需要根据响应的内容, 构造出 html 片段, 展示到页面上.
                // 由于服务器响应中已经设置了 Content-Type 为 application/json, 此时
                // jQuery 就能够自动的把此处响应的内容解析成 js 对象数组.
                let containter = document.querySelector('.container-right');
                for (let blog of body) {
                    // 根据当前这个 blog 构造出一个 html 片段.
                    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.href = 'blog_detail.html?blogId=' + blog.blogId;
                    a.innerHTML = '查看全文 &gt;&gt;';
                    blogDiv.appendChild(a);
                    // 最后把拼好的 blogDiv 添加到 container 的后面
                    containter.appendChild(blogDiv);
                }
            }
        });
    }

运行结果:博客列表成功显示

 

理解数据交互过程

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

 

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

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

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

4.实现博客详情

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

4.1约定前后端交互接口

[请求]
GET /blog?blogId=1

[响应] {
        blogId: 1,
        title: "第一篇博客", 
        content: "博客正文", 
        userId: 1,
        postTime: "2021-07-07 12:00:00" 
},

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

4.2实现服务器代码

在之前BlogServlet中,设置了参数blogId,如果是输入网站的话blogId就为空,点击博客列表中的查看详情,就会带一个blogId参数给当前页面,并放到Querystring中,

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 查询数据库, 获取到数据之后, 构造成要求的 json 格式并返回.
        // 先尝试获取下 blogId 这个参数, 看看能不能获取到.
        BlogDao blogDao = new BlogDao();
        String blogId = req.getParameter("blogId");
        if (blogId == null) {
            // 此时说明是获取博客列表. 没有 blogId 参数
            List<Blog> blogs = blogDao.getBlogs();
            String respJson = objectMapper.writeValueAsString(blogs);
            resp.setContentType("application/json; charset=utf8");
            resp.getWriter().write(respJson);
        } else {
            // 此时说明是获取博客详情. 有 blogId 参数.
            Blog blog = blogDao.getBlog(Integer.parseInt(blogId));
            if (blog == null) {
                // 返回一个 id 为 0 的 blog 对象. 前端再根据这里进行判定.
                blog = new Blog();
            }
            String respJson = objectMapper.writeValueAsString(blog);
            resp.setContentType("application/json; charset=utf8");
            resp.getWriter().write(respJson);
        }
    }

4.3实现客户端代码

其中blog_list.html中有跳转到详情页的代码:

当用户点击这个按钮后会携带blogId参数进入blog_detail.html

修改 blog_detail.html:

其中要引入editor_md的依赖

    function getBlog() {
        $.ajax({
            url: 'blog' + location.search,
            type: 'get',
            success: function (body) {
                // 根据拿到的响应数据, 构造页面内容.
                let h3 = document.querySelector('.container-right h3');
                h3.innerHTML = body.title;
                let dateDiv = document.querySelector('.container-right .date');
                dateDiv.innerHTML = body.postTime;
                editormd.markdownToHTML('content', {markdown: body.content});
            }
        });
    }

    getBlog();

运行结果:

 5.实现登录功能

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

服务器端验证用户名密码是否正确.

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

 5.1约定前后端交互接口

[请求]
POST /login
Content-Type: application/x-www-form-urlencoded 
username=test&password=123
[响应]
HTTP/1.1 302
Location: blog_list.html

5.2实现服务器代码

创建 LoginServlet


@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 获取请求中的用户名和密码
        //    给请求对象设置字符集, 保证说请求中的 username 或者 password 是中文, 也能正确处理.
        req.setCharacterEncoding("utf8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null || password == null || "".equals(username) || "".equals(password)) {
            // 当前提交的用户名密码有误!
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前传过来的 username 或者 password 为空");
            return;
        }
        // 2. 和数据库进行验证. 看当前这样的用户名和密码是否匹配.
        UserDao userDao = new UserDao();
        User user = userDao.getUserByName(username);
        if (user == null) {
            // 当前提交的用户名密码有误!
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("您的用户名或者密码错误!");
            return;
        }
        if (!password.equals(user.getPassword())) {
            // 当前提交的用户名密码有误!
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("您的用户名或者密码错误!");
            return;
        }
        // 3. 创建会话
        HttpSession session = req.getSession(true);
        // 把当前登录的用户信息保存到 session 中, 方便后续进行获取.
        session.setAttribute("user", user);
        // 4. 跳转到博客列表页.
        resp.sendRedirect("blog_list.html");
    }

5.3实现客户端代码

修改login.html:

<div class="login-container">
    <!-- 登录对话框 -->
    <div class="login-dialog">
        <h3>登录</h3>
        <!-- 使用 form 包裹一下下列内容, 便于后续给服务器提交数据 -->
        <form action="login" method="post">
            <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>

部署程序验证效果:

6.实现强制要求登陆

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

6.1实现服务器代码

修改LoginServlet代码:添加方法检测登录状态

    // 通过这个方法, 来检测当前的登录状态.
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 会话不存在, 就认为是未登录.
        HttpSession session = req.getSession(false);
        if (session == null) {
            // 未登录
            resp.setStatus(403);
            return;
        }
        // 不仅仅是看 session 对象本身, 还需要看 user 对象存在. (为了后面实现 "退出登录" 功能)
        User user = (User) session.getAttribute("user");
        if (user == null) {
            resp.setStatus(403);
            return;
        }
        // 返回 200 表示已经登陆.
        resp.setStatus(200);
    }

6.2实现服务器代码

单独编写一个js文件,在每个页面中都加上登录检查机制

function checkLogin() {
    $.ajax({
        type: 'get',
        url: 'login',
        success: function(body) {

        },
        error: function(body) {
            location.assign('login.html');
        }
    });
}

7.实现显示用户信息

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

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

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

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

7.1约定前后端交互接口

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

[请求]
GET /user

[响应] {
         userId: 1,
        username: test 
}

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

[请求]
GET /user?blogId=1

[响应] {
        userId: 1,
        username: test 
}

7.2实现服务器代码

创建UserServlet:

@WebServlet("/user")
public class UserServlet 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) {
            // 博客列表页
            // 从 session 中拿到 user 对象.
            HttpSession session = req.getSession(false);
            if (session == null) {
                User user = new User();
                String respJson = objectMapper.writeValueAsString(user);
                resp.setContentType("application/json; charset=utf8");
                resp.getWriter().write(respJson);
                return;
            }
            User user = (User) session.getAttribute("user");
            if (user == null) {
                user = new User();
                String respJson = objectMapper.writeValueAsString(user);
                resp.setContentType("application/json; charset=utf8");
                resp.getWriter().write(respJson);
                return;
            }
            String respJson = objectMapper.writeValueAsString(user);
            resp.setContentType("application/json; charset=utf8");
            resp.getWriter().write(respJson);
        } else {
            // 博客详情页
            // 需要查询数据库了.
            BlogDao blogDao = new BlogDao();
            Blog blog = blogDao.getBlog(Integer.parseInt(blogId));
            if (blog == null) {
                User user = new User();
                String respJson = objectMapper.writeValueAsString(user);
                resp.setContentType("application/json; charset=utf8");
                resp.getWriter().write(respJson);
                return;
            }
            UserDao userDao = new UserDao();
            User user = userDao.getUserById(blog.getUserId());
            if (user == null) {
                user = new User();
                String respJson = objectMapper.writeValueAsString(user);
                resp.setContentType("application/json; charset=utf8");
                resp.getWriter().write(respJson);
                return;
            }
            String respJson = objectMapper.writeValueAsString(user);
            resp.setContentType("application/json; charset=utf8");
            resp.getWriter().write(respJson);
        }
    }
}

7.3实现客户端代码

1. 修改 blog_list.html和blog_detail.html,都要加上下列代码

    function getUser() {
        $.ajax({
            type: 'get',
            url: 'user',
            success: function (body) {
                // body 就是解析后的 user 对象了.
                let h3 = document.querySelector('.card h3');
                h3.innerHTML = body.username;
            }
        })
    }

    getUser();

8.实现注销登陆

8.1约定前后端交互接口

[请求]
GET /logout

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

8.2实现服务器代码

session 中删除掉保存的 User 对象

响应重定向到 login.html 页面.

创建 LogoutServlet:

@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.sendRedirect("login.html");
            return;
        }
        // 之前在登录成功后, 就会给 session 中存储 user 这样的 Attribute .
        // 把这个删掉之后, 自然就会判定为 "未登录" 了.
        session.removeAttribute("user");
        resp.sendRedirect("login.html");
    }
}

9.实现发布博客

9.1约定前后端交互接口

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

9.2实现服务器代码

修改 BlogServlet, 新增 doPost 方法.

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 获取到登录的用户
        //    在博客编辑页, 已经做了登录检查了. 当用户提交的时候, 必然是已经登录的状态.
        HttpSession session = req.getSession(false);
        if (session == null) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("用户未登录! 无法发布博客!");
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("用户未登录! 无法发布博客!");
            return;
        }
        // 2. 获取到请求中传递过来的内容
        req.setCharacterEncoding("utf8");  // 这个操作不要忘, 否则遇到中文可能会乱码
        String title = req.getParameter("title");
        String content = req.getParameter("content");
        if (title == null || content == null || "".equals(title) || "".equals(content)) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("标题或者正文为空");
            return;
        }
        // 3. 构造 Blog 对象, 并且插入到数据库中.
        Blog blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        blog.setUserId(user.getUserId());
        // 由于在 sql 插入数据的时候, 已经使用 sql 自带的 now 获取当前时间, 不需要此处代码中手动设置时间了.
        blog.setPostTime(new Timestamp(System.currentTimeMillis()));
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);
        // 4. 跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }

9.3实现客户端代码

修改 blog_edit.html 页面结构,

增加 form 标签, action  blog_edit , method   POST 

form 指定  height: 100%;  防止编辑器高度不能正确展开. .  给标题的 input 标签加上 name 属性

把提交按钮改成 <input type="submit" value="发布文章"> •   

 <div id="editor"> 里面加上一个隐藏的 textarea

<!-- 博客编辑页的版心 -->
<div class="blog-edit-container">
    <form action="blog" method="post">
        <!-- 标题编辑区 -->
        <div class="title">
            <input type="text" id="title-input" name="title">
            <input type="submit" id="submit">
        </div>
        <!-- 博客编辑器 -->
        <!-- 把 md 编辑器放到这个 div 中 -->
        <div id="editor">
            <textarea name="content" style="display: none;"></textarea>
        </div>
    </form>
</div>

<script src="js/app.js"></script>
<script>
    var editor = editormd("editor", {
        // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
        width: "100%",
        // 设定编辑器高度
        height: "calc(100% - 50px)",
        // 编辑器中的初始内容
        markdown: "# 在这里写下一篇博客",
        // 指定 editor.md 依赖的插件路径
        path: "editor.md/lib/"
    });

    checkLogin();
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1691203.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jmeter之线程组教程

一、线程组的作用 线程组是测试计划的子控件&#xff0c;也是取样器的父控件setup线程组&#xff0c;在所有线程组之前运行&#xff0c;类似于unittest框架中的SetUp函数&#xff0c;初始化环境teardown线程组&#xff0c;在所有线程组之后运行&#xff0c;类似于unittest中的…

如何在Windows 11和10上检查计算机的正常运行时间,这里有两种方法

序言 你的计算机的正常运行时间告诉你打开计算机已经有多长时间了。如果你需要这些信息进行故障排除或其他操作&#xff0c;那么很容易在Windows 11和Windows 10操作系统上找到你的正常运行时间。我们将向你展示图形和命令行方法。让我们开始吧。 使用任务管理器查找Windows正…

YoloV9实战与改进——专栏目录

摘要 &#x1f525;&#x1f680;本专栏教你如何嗨翻Yolov9&#xff01;&#x1f680;&#x1f525; &#x1f680;炸裂升级&#xff1a;嗨&#xff0c;小伙伴们&#xff01;这里有一波Yolov9的升级大招&#xff0c;带你领略最新论文的精华&#xff01;&#x1f4a5; 什么注意…

Star CCM+在电池热管理中SOC计算、充电Map调用、电池内阻调用的方法

前言 众所周知电池充电电流是随着电池温度与容量变化查表获得(形式见下表),其中电池的充电倍率(电流)是阶梯变化的,而内阻是线型变化的。因此为了仿真的准确定,需要在软件中实现数据的调用,计算电池的发热量。 电池内阻/充电倍率表 一 SOC计算 SOC的估算方法有开路电…

fpga 提高有什么进阶书推荐?

到FPGA中后期的时候就要开始接触&#xff0c;如&#xff1a;高速接口、光纤数字信号处理等项目实践了&#xff0c;那么我们可以读一些书进行提升&#xff0c;大家可以收藏下。 高速接口项目《嵌入式高速串行总线技术:基于FPGA实现与应用》作者&#xff1a;张锋 FPGA提升书籍推…

探索切片索引:列表反转的艺术

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;列表反转的挑战 二、切片索引的基本概念 三、切片索引实现列表反转 …

【微信小程序开发】小程序前后端交互--发送网络请求实战解析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【计算机视觉(1)】

基于Python的OpenCV基础入门——图像的处理 计算机视觉简介OpenCV介绍图像的读取图像的显示图像窗口等待时间图像窗口的全部销毁图像的保存 图像的基本操作代码实现 计算机视觉简介 计算机视觉是人工智能 (AI) 的一个领域&#xff0c;是指让计算机和系统能够从图像、视频和其他…

Logstash笔记

目录​​​​​​​ 一、简介 二、单个输入和输出插件 三、多个输入和输出插件 四、pipeline结构 五、队列和数据弹性 六、内存队列 七、持久化队列 八、死信队列 (DLQ) 九、输入插件 1)、beats 2)、dead_letter_queue 3)、elasticsearch 4)、file 5)、redis 十、…

LESS IS MORE: ONE-SHOT-SUBGRAPH LINK PREDICTION ON LARGE-SCALE KNOWLEDGE GRAPHS

LESS IS MORE: ONE-SHOT-SUBGRAPH LINK PREDICTION ON LARGE-SCALE KNOWLEDGE GRAPHS&#xff08;ICLR2024&#xff09; 论文地址&#xff1a;https://arxiv.org/html/2403.10231v1 源码地址&#xff1a;https://github.com/tmlr-group/one-shot-subgraph ABSTRACT 为了推断知…

2024最新 Jenkins + Docker 实战教程(六)- Jenkins配置邮箱接收构建通知

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

软件设计:基于 python 代码快速生成 UML 图

1. 官方文档 PlantUML Language Reference Guide Comate | 百度研发编码助手 百度 Comate (Coding Mate Powered by AI) 是基于文心大模型的智能代码助手&#xff0c;结合百度积累多年的编程现场大数据和外部优秀开源数据&#xff0c;可以生成更符合实际研发场景的优质代码。…

element-plus表格的表单校验如何实现,重点在model和prop

文章目录 vue&#xff1a;3.x element-plus&#xff1a;2.7.3 重点&#xff1a; 1) tableData放到form对象里 2) form-item的prop要写成tableData.序号.属性 <!--table-表单校验--> <template><el-form ref"forms" :model"form"><e…

【Linux系列】软链接使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

基于自抗扰控制器和线性误差反馈控制律(ADRC-LSEF)的控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 ADRC原理 4.2 线性误差反馈控制律(LSEF) 4.3 ADRC-LSEF融合系统 5.完整工程文件 1.课题概述 基于自抗扰控制器和线性误差反馈控制律(ADRC-LSEF)的控制系统simulink建模与仿真。 2.系统仿真结果 …

基于Java的地震震中附近城市分析实战

目录 前言 一、空间数据说明 1、空间查询 二、Java后台开发 1、模型层设计与实现 2、控制层设计与实现 三、Leaflet地图开发 1、地震震中位置展示 2、附近城市展示 3、成果展示 总结 前言 随着全球气候变化和地壳活动的不断演变&#xff0c;地震作为一种自然灾害&…

“AIGC行业投资时机分析:评估当前市场发展阶段与未来需求趋势“

文章目录 每日一句正能量前言行业前景当前发展前景相关领域的发展趋势行业潜力竞争情况结论 市场需求人才需求情况机会挑战结论 选择与规划自我评估行业调研职业规划风险管理个人陈述示例 后记 每日一句正能量 胖了就减&#xff0c;没钱就赚&#xff0c;不会就学&#xff0c;不…

rtk技术的使用, test ok

1. 什么是gnss 2 rtk定位

记录使用 Vue3 过程中的一些技术点

1、自定义组件&#xff0c;并使用 v-model 进行数据双向绑定。 简述&#xff1a; 自定义组件使用 v-model 进行传参时&#xff0c;遵循 Vue 3 的 v-model 机制。在 Vue 3 中&#xff0c;v-model 默认使用了 modelValue 作为 prop 名称&#xff0c;以及 update:modelValue 作为…