博客系统(Servlet实现)

目录

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>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网站流量统计分析

网站流量统计分析&#xff1a;洞悉用户行为的关键 在当今数字化时代&#xff0c;网站流量统计分析已经成为了企业成功的关键因素之一。通过深入了解用户的行为和偏好&#xff0c;企业可以更好地调整其营销策略、优化用户体验以及提高转化率。本文将探讨网站流量统计分析的重要性…

av_dump_format经验分析,FFmpeg获取媒体文件总时长(FLV获取总时长的误区)

播放器有个功能&#xff0c;当用户打开视频时&#xff0c;需要读取媒体文件的总时长等信息&#xff0c;不巧的时&#xff0c;获取FLV时总失败&#xff0c;下面来具体分析下FLV和MP4获取总时长的原因和区别&#xff1a; 播放器有个获取MediaInfo的接口&#xff0c;功能如下&am…

【面试干货】矩阵对角线元素之和

【面试干货】矩阵对角线元素之和 1、实现思想2、代码实现 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、实现思想 创建一个3x3的二维数组来表示输入的矩阵。通过嵌套循环读取输入的矩阵元素&#xff0c;并将其保存到数组中。再次嵌套循…

Linux基础入门和帮助-第二篇

马哥教育 Linux SRE 学习笔记 用户登录信息查看命令 whoami: 显示当前登录有效用户 [rootrocky8 ~]$whoami rootwho: 系统当前所有的登录会话 [rootrocky8 ~]$who root pts/0 2024-05-24 12:55 (10.0.0.1)w: 系统当前所有的登录会话及所做的操作 [rootrocky8 ~]…

Springboot开发 -- Postman 使用指南

引言 在 Spring Boot 应用开发过程中&#xff0c;接口测试是必不可少的一环。Postman 作为一款强大的 API 开发和测试工具&#xff0c;可以帮助开发者轻松构建、测试和管理 HTTP 请求。本文将为大家介绍如何在 Spring Boot 开发中使用 Postman 进行接口测试。 一、准备工作 安…

【算法】分治 - 快速排序

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、颜色分类二、排序数组三、数组中的第k个数四、最小的k个数总结 引言 本节主要介绍快速排序&#xf…

机器学习实验------Adaboost算法

第1关:什么是集成学习 任务描述 本关任务:根据本节课所学知识完成本关所设置的选择题。 第2关: Boosting 任务描述 本关任务:根据本节课所学知识完成本关所设置的选择题。 第3关:Adaboost算法流程 任务描述 本关任务:用Python实现Adaboost,并通过鸢尾花数据集…

聚观早报 | 华为畅享 70S真机图赏;vivo Y200 GT开售

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 5月25日消息 华为畅享 70S真机图赏 vivo Y200 GT开售 一加13部分细节曝光 马斯克谈AI未来 三星Galaxy Z Fold6将…

使用SDL_QT直接播放渲染YUV格式文件

0.前要 下载一个文件&#xff0c;名字为 400_300_25.mp4&#xff0c;我们用ffmplay.exe将其转化为yuv文件&#xff0c;具体操作如下&#xff1a; 进入cmd控制台&#xff0c;进入ffmplay.exe文件的目录下&#xff0c;输入ffmpeg -i 文件名.mp4 文件名.yuv 回车&#xff0c;会生…

每日练习之数学——砝码和天平

砝码和天平 题目描述 运行代码 #include<iostream> using namespace std; int main() {int w,m,T;cin>>T;while(T--){cin>>w>>m;while(m){if((m-1)%w0)m(m-1)/w;else if((m1)%w0)m(m1)/w;else if(m%w0)m/w;else break;}if(!m)cout<<"YES&…

检测头篇 | YOLOv8改进之添加小目标检测头 / 添加大目标检测头 / 减少检测头

前言:Hello大家好,我是小哥谈。本文首先给大家展示原始YOLOv8的网络结构图,然后再对其进行改变,即增加小目标检测头、增加大目标检测头和减少检测头。🌈 目录 🚀1.网络结构图

10.2.k8s的附加组件-Metrics-server组件与hpa资源pod水平伸缩

目录 一、概述 二、安装部署Metrics-Server组件 1.下载Metrics-Server资源清单 2.编辑Metrics-Server的资源清单 3.验证Metrics-Server是否成功安装 4.使用top命令测试是否管用 三、hpa资源实现pod水平伸缩&#xff08;自动扩缩容&#xff09; 1.编写deploy资源清单 2.…

什么是创造力?如何判断自己的创造力?

创造力&#xff0c;主要表现为创新思想、发现和创造新事物的能力&#xff0c;是知识&#xff0c;智力和能力的综合能力&#xff0c;尤其是在职业发展方面&#xff0c;创造力具有重要的意义&#xff0c;企业的核心竞争力就来源于创造力&#xff0c;这就需要具有创造力的员工来推…

HCIP-Datacom-ARST自选题库__OSPF单选【80道题】

1.OSPFV2是运行在IPV4网络的IGP&#xff0c;OSPFV3是运行在IPV6网络的ICP&#xff0c;OSPFV3与OSPFv2的报文类型相同&#xff0c;包括Hello报文、DD报文、LSR报文、LSU报文和LSAck报文。关于OSPFv3报文&#xff0c;以下哪个说法是正确的 OSPFv3使用报文头部的认证字段完成报文…

.DS_store文件

感觉mac里的这个.DS_store文件烦人&#xff0c;老是莫名其妙的出现&#xff0c;然后造成困扰 处理方式如下&#xff1a; import os pic_list os.listdir("./mask_pic/") print(len(pic_list)) # 从文件夹中删掉 if(".DS_Store" in pic_list):print(&quo…

如何关闭或者减少屏蔽 CloudFlare 的真人检测

经常浏览境外网站的应该常碰到一个真人检测的提示(如下图所示)。最近,明月就收到了一个知乎上的付费咨询:问我如何去掉这个提示,由此明月也特别的研究了一下这个“真人检测”,这算是 CloudFlare 的一个特色了,基本上大家看到站点访问有这个提示的几乎都是用了 CloudFlar…

亚马逊测评自养号需要解决哪些问题?

我们首先了解一下测评是什么 测评就是类似于国内某宝和某多补销量一样&#xff0c;可以快速提升产品销量和优质的评价&#xff0c;从而让产品的权重上升&#xff0c;可以上升产品排名 也可以防范同行的恶意差评&#xff0c;可以用好评稀释差评&#xff0c;从而控评&#xff0…

VSCODE gcc运行多个.c文件

一、简介 很多时候&#xff0c;开发者需要使用VSCODE进行C语言算法验证。而VSCODE的gcc编译&#xff0c;默认是只编译本文件的内容&#xff0c;其他.c文件是不参与编译的。这就给开发者带来很大的困扰&#xff0c;因为开发者不可能把所有的算法都写在一个.c文件&#xff0c;特别…

2024年5月计算机视觉论文推荐:包括扩散模型、视觉语言模型、图像编辑和生成、视频处理和生成以及图像识别等各个主题

我们今天总结下2024年5月发表的最重要的论文&#xff0c;重点介绍了计算机视觉领域的最新研究和进展&#xff0c;包括扩散模型、视觉语言模型、图像编辑和生成、视频处理和生成以及图像识别等各个主题。 Diffusion Models 1、Dual3D: Efficient and Consistent Text-to-3D Ge…

mac清理软件推荐免费 mac清理系统数据怎么清理 cleanmymac和腾讯柠檬哪个好

macbook是苹果公司的一款高性能的笔记本电脑&#xff0c;受到了很多用户的喜爱。但是&#xff0c;随着使用时间的增长&#xff0c;macbook的系统也会积累一些垃圾文件&#xff0c;影响其运行速度和空间。那么&#xff0c;macbook系统清理软件推荐有哪些呢&#xff1f;macbook用…