博客系统 Java Web 开发(Servlet)

目录

一、准备工作

二、设计数据库

三、编写数据库代码

1、建表sql

2、封装数据库的连接操作

3、创建实体类

4、封装数据库的一些增删改查

(1)BlogDao

新增博客: 

根据博客 id 来查询指定博客(用于博客详情页)

直接查询出数据库中所有的博客列表

删除博客

(2)UserDao

根据 userId 来查询用户信息

 根据 username 来查询用户信息(登录的时候)

四、围绕博客列表页实现获取博客列表功能

1、约定前后端交互接口

2、编写后端代码

3、编写前端代码

五、博客详情页

1、约定前后端交互接口

2、实现后端代码

3、实现前端代码

六、实现登录页

1、约定前后端交互接口

2、修改前端代码

3、修改后端代码

七、页面强制登录

1、约定前后端交互接口

2、编写后端代码

3、实现前端代码

八、显示用户信息

1、约定前后端交互接口

 2、编写后端代码

3、编写前端代码

九、退出登录状态

1、约定前后端接口

2、编写后端代码

3、编写前端代码

十、发布博客

1、约定前后端交互接口

​编辑

2、编写服务器代码

3、编写客户端代码


一、准备工作

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

要引入的依赖有: servlet ,mysql,jackson

引入依赖完成之后,再创建目录,然后将 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>

然后再将之前写的前端代码复制粘贴到 webapp 目录下

 此时,准备工作就完成了


二、设计数据库

结合之前的需求,在当前的博客系统中,主要涉及到两个实体:博客,用户

我们就可以创建两张表来表示 博客 和 用户

这两个实体之间的关系如何?

一对多的关系,一个用户可以写多个博客,但是一个博客只能属于一个用户


三、编写数据库代码

1、建表sql

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

在 src 目录下,创建一个 db.sql,在里面写上数据库的建库建表语句

    --这个文件,主要用来写建库建表语句
    --一般在建表的时候,把建表的 sql 保留下来,以备后续部署其它及其的时候就方便了

    create database if not exists blog_system;
    use 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,
        userId int
    };

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

2、封装数据库的连接操作

在 java 目录下,创建一个 DBUtil 类

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;

//通过这个类,把数据库连接过程封装一下
//此处,把 DBUtil 作为一个工具类,提供 static 方法,供其它方法来调用
public class DBUtil {
    //静态成员是跟随类对象的,类对象在整个进程中,只有唯一一份
    //静态成员相当于也是唯一的实例(单例模式,饿汉模式)
    private static DataSource dataSource = new MysqlDataSource();

    static {
        //使用静态代码块,针对 dataSourse 进行初始化
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java?charactorEncoding=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)  {
        //此处的三个 try catch 分开写更好,避免前面的异常导致后面的代码无法执行
        if (resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

3、创建实体类

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

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

于是,我们便要根据之前表中的列,来创建好 blog 和 user 这两个类:

并且,将里面的每个属性,都手动加上 get set 访问器


4、封装数据库的一些增删改查

针对 博客表 ,创建一个 BlogDao

针对 用户表,创建一个 UserDao

它们提供了一些方法,来进行增删改查

(1)BlogDao

 里面一共有四个方法:

1、新增博客

2、根据博客 id 来查询指定博客(用于博客详情页)

3、直接查询出数据库中所有的博客列表

4、删除博客

新增博客: 

    //1、新增一个博客
    public void add(Blog blog){
        Connection connection = null;
        PreparedStatement statement = null;
        //1、和数据库建立连接
        try {
             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) {
            throw new RuntimeException(e);
        }finally {
            DBUtil.close(connection,statement,null);
        }
    }

根据博客 id 来查询指定博客(用于博客详情页)

    //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 where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,blogId);
            //3、执行 sql
            resultSet = statement.executeQuery();
            //4、遍历结果集合,由于此处的 blogId ,在 blog 表中是唯一的(主键)
            //此时的查询结果,要么是没有查到任何数据,要么是只有一条记录
            //所以,此处使用 if 即可,不用使用 while
            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) {
            throw new RuntimeException(e);
        }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
            resultSet =  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) {
            throw new RuntimeException(e);
        }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 where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,blogId);
            //3、执行 sql
            statement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            //进行关闭
            DBUtil.close(connection,statement,null);
        }
    }

(2)UserDao

这里面一共有两个方法:

1、根据 userId 来查询用户信息

2、根据 username 来查询用户信息(登录的时候)

根据 userId 来查询用户信息

    //1、根据 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) {
            throw new RuntimeException(e);
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

 根据 username 来查询用户信息(登录的时候)

    //2、根据 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) {
            throw new RuntimeException(e);
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

写到这里,就把数据库的操作封装完毕了


四、围绕博客列表页实现获取博客列表功能

接下来,我们围绕博客列表页,实现获取博客列表的功能

当前,我们的博客列表页上面的数据,都是固定的,显然是不科学的

正确的做法,应该是通过数据库读取数据,显示到页面上 

此处,就需要打通前后端交互的操作

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

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

前端只向后端请求数据,而不请求具体的页面,后端也仅仅是返回数据,这样设定的目的就是为了前端和后端更加解耦合

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

接下来,我们需要:

1、约定前后端交互接口

2、开发后端代码

3、开发前端代码

1、约定前后端交互接口

在 博客列表页,获取博客列表的功能下,前端要发什么请求,后端返回什么响应,都需要进行约定

确定了前后端接口之后,前端和后端再分别按照这个格式进行开发


2、编写后端代码

@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);
    }
}

此时,我们会发现,这里面的类,越写越多了,看起来会显得比较杂乱

这个时候,我们就可以对它们进行一个分类来处理

model :管理数据 / 操作数据的部分

把这个 Servlet 放到 api 中,api 不到一定非得是一个 类 / 方法,也可以是  “网络接口”(处理 HTTP 请求,返回 HTTP 响应)


3、编写前端代码

再博客列表页,加载过程中,触发 ajax 访问服务器中的数据

再把拿到的数据,构造到页面中

构造页面内容的时候,我们可以参考之前写好了的 html 代码进行构造

 <script src="./js/jquery.mini.js"></script>
    <script>
        //在页面加载时 ,向服务器发起一个请求,获取博客列表数据
        function getBlogs(){
            $.ajax({
                type:'get',
                url:'blog',
                success:function(body){
                    //响应的 body 是一个 json 字符串,此处已经被 jquery 自动解析成 js 对象数组了
                    //直接 for 循环遍历即可
                    let containerRight = document.querySelector('.containner');
                    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');
                        datteDiv.className = 'date';
                        dateDiv.innerHTML = blog.postTime;
                        blogDiv.appendChild(dateDiv);
                        //构造博客摘要
                        let descDiv = document.createElement('div');
                        descDiv.className = 'desc';
                        desc.innerHTML = blog.content;
                        blogDiv.appendChild(descDiv);
                        //构造查看全文按钮
                        let a  = document.createElement('a');
                        a.innerHTML = '查看全文 &gt;&gt;';
                        //期望点击之后,可以跳转到博客详情页,为了让博客详情页知道点了哪个博客,把  blogId 传过去
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                        blogDiv.appendChild(a);

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

此时,博客列表页功能实现就完成了

此时,我们插入一个测试用例,就会发现两个问题:

 1、时间戳:不应该显示时间戳,而是应该显示格式化的时间

需要用到一个格式化时间的类,SimpleDateFormat 来帮我们转化一下就可以了 

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

直接在 sql 中加一个 order by 即可


五、博客详情页

接下来,实现博客详情页

点击 “查看全文” 按钮,就可以跳转到博客详情页中

跳转过去后,在博客详情页中发起一个 ajax ,从服务器获取到当前博客的具体内容,再显示出来

1、约定前后端交互接口

注意:由于此处是获取博客详情了,所以此处的 content 是完整的,不用像博客列表页一样进行截断 

2、实现后端代码

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //尝试获取一下 query string 中的 blogId 字段
        String blogId = req.getParameter("blogId");
        BlogDao blogDao = new BlogDao();
        if (blogId == null){
            //queryString 不存在,该请求是获取博客列表页
            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 存在,本次请求是获取指定 id 的博客
            Blog blog = blogDao.selectById(Integer.parseInt(blogId));
            if (blog == null){
                System.out.println("当前 bligId = " + blogId + "对应博客不存在!");
            }
            String respJson = objectMapper.writeValueAsString(blog);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respJson);
        }
    }

3、实现前端代码

在 blog_detail.html 中,加入 ajax ,来获取上述数据

location.search 就是获取当前页面的 query string

这里的 location 不是 http header 中 location ,是 js 代码中的一个全局对象,类似于 document

 注意:当前写的 博客内容,是使用 markdown 形式来组织的,比如博客的内容是

内容中,会带有一些 markdown 的符号,最终显示到网页上,希望用户看到的是渲染之后的结果,也就是把 # 转换成一级标题

数据库中保存的 content 是渲染前的内容 ,但是最终显示给用户看,我们希望用户看到的是渲染后的内容

这里就需要使用 editor.md 对 markdown 内容进行转换

具体如何转换呢?

editor.md 提供了一个方法: editormd.markdownToHTML ,效果是把 md 字符串,转换成 html 片段,输出到 #content 这个标签内部(使用时要记得引入 editor.md 的依赖)

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

这个内容,还是之前写死的内容,但是实际上,代码已经把这个内容给删掉了

这个是因为浏览器自身也有缓存,浏览器从服务器这边获取页面,这个操作是通过网络传输完成的,速度是比较慢的

浏览器就会把页面内容给缓存到本地(客户端电脑的硬盘上),后续再访问同一个页面,就直接读缓存了  

这样就带来了一个问题:如果服务器代码改了,客户端命中缓存之后就不一定能及时感知到变化

解决方案:强制刷新, ctrl + f5 ,此时是无视本地缓存的,100%重新访问服务器的

缓存问题解决之后,发现当前页面虽然变化了,但是结果仍然不是很理想,查看报错信息,就会发现是 editormd 的代码写错了,进行修改后再次刷新页面:

修改完之后,发现正文有了,但是标题没有出来

我们通过抓包来看看服务器返回结果是否符合预期,就可以确定是前端问题还是后端问题

根据响应结果,我们可以判断出后端代码应该没什么问题,响应结果是正确的,接下来检查前端代码 

我们会发现,这是由于前端代码中出现了两个 title ,querySelector 默认返回的是第一个 title ,然后导致对标题的修改改错了地方

解决方案:把选择器写的更精确一些即可

重新启动服务器,再次刷新页面,就可以发现,现在标题就正确出现了

        <!-- 右侧信息 -->
        <div class="containner-right">
            <!-- 博客标题 -->
            <h3 class="title"></h3>
            <!-- 博客发布时间 -->
            <div class="date"></div>
            <!-- 博客正文  为了配合 editormd 进行格式转换,此处一定要改成 id -->
            <div id="content">

            </div>
        </div>
    </div>
    <script src="js/jquery.mini.js"></script>
        <!-- 引入 editor.md 的依赖 -->
        <!-- 要保证这个几个 js 的加载,在 jquery 之后,因为 edit.md 依赖了 jquery -->
        <script src="js/jquery.min.js"></script>
        <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('.containner-right .title');
                titleDiv.innerHTML = body.title;
                //2、更新日期
                let dateDiv = document.querySelector('.date');
                dateDiv.innerHTML = body.postTime;
                //3、更新博客正文
                //此处,不应该直接把博客正文填充到这个标签里
                editormd.markdownToHTML('content',{markdown: body.content});

            }
        });
    </script>

六、实现登录页

在此处输入用户名和密码,点击登录,就会触发一个 HTTP 请求

服务器验证用户名和密码,然后就可以根据结果判断是否登录成功,如果登录成功,就跳转到博客列表页

当前,只是一个单纯的输入框,还不能提交请求,所以我们需要把它改成 form 表单

1、约定前后端交互接口

2、修改前端代码

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

1、加上 form 标签,把 input 都包裹进去

2、input 加上 name 属性

3、把按钮改成 input submit 类型

        <!-- 垂直水平居中的对话框 -->
        <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="登录"></button>
                </div>
            </form>
        </div>

3、修改后端代码

此处需要加个 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、读取参数中的用户名和密码
        //注意!!如果用户名密码包含中文,此处的读取可能会乱码,所以设置一下 utf8
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null || "".equals(username) || password == null || "".equals(password)){
            //登录失败
            String html = "<h3> 登陆失败! 缺少 username 或者 password 字段! </h3>";
            resp.getWriter().write(html);
            return;
        }
        //2、读数据库,看看用户名是否存在并且密码是否匹配
        UserDao userDao = new UserDao();
        User user = userDao.selectByUsername(username);
        if (user ==null){
            //用户不存在
            String html = "<h3> 登陆失败! 用户名 或 密码 错误!! </h3>";
            resp.getWriter().write(html);
            return;
        }
        if (!password.equals(user.getPassword())){
            //密码不对
            String html = "<h3> 登陆失败! 用户名 或 密码 错误!! </h3>";
            resp.getWriter().write(html);
            return;
        }
        //3、如果用户名和密码验证通过,登录成功,接下来创建会话,使用该会话来保存用户信息
        HttpSession session = req.getSession(true);
        session.setAttribute("user",user);
        //4、进行重定向,跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }
}

七、页面强制登录

当用户访问 博客列表页/详情页/编辑页,要求用户必须使已经登录的状态

如果用户还未登录,就强制跳转到登录页面

本质就是要求,想要使用系统,就得先登录

实现思路:

在页面加载的时候,专门发起一个新的 ajax (一个页面里可以发起N个 ajax)

以博客列表页为例:

先发一个请求获取到博客列表,再发送一个 ajax 获取用户的登录状态,如果用户已经登录了,就没事,如果未登录,则页面跳转到登录页

1、约定前后端交互接口


2、编写后端代码

    @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 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);
    }

3、实现前端代码

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

                checkLogin();

八、显示用户信息

这个用户信息,目前是已经被写死了的,我们希望能够让它动态生成一个用户信息

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

2、如果是博客详情页,此时现实的是该文章的作者

1、约定前后端交互接口

博客列表页代码调整:

对于博客详情页的代码,我们重新写一个 servlet 

什么时候需要新写一个 servlet?

主要看到当前请求的路径,是新的还是已经有了的


 2、编写后端代码

@WebServlet("/author")
public class authorServlet extends HttpServlet {
    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);
    }
}

3、编写前端代码

        function getAuthor(){
            $.ajax({
                type:'get',
                url:'author' + location.search,
                success: function(body){
                    //把 username 给设置到页面上
                    let h3 = document.querySelector('.containner-left .card h3');
                    h3.innerHTML = body.username;
                }
            });
        }

        getAuthor();

九、退出登录状态

这里的注销指的是退出登录状态,不是指删除账户

判定登录状态:

1、看是否能查到 http session 对象

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

实现退出登录,要么是把 http session 给干掉,要么把 user 给干掉,二者选其一即可

如果有会话,但是没有 user 对象,也视为未登录

如果想要干掉 http session 会比较麻烦,所以我们选择干掉 user 对象


1、约定前后端接口


2、编写后端代码

@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");
    }
}

3、编写前端代码


十、发布博客

1、约定前后端交互接口

使用 form 表单,得让页面中多个 form 标签,同时得让 form 里面能够感知到博客的内容


2、编写服务器代码

@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());
        //发布时间
        blog.setPostTime(new Timestamp(System.currentTimeMillis()));
        //插入数据
        BlogDao blogDao = new BlogDao();
        blogDao.add(blog);
        
        //跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }

3、编写客户端代码

对代码进行调整:

1、加上 form 标签

2、给 input 标签加上 name 属性

3、把 button 改成 input 标签,并且 type 为  submit

4、editor.md 文档要求写法:加上 textarea

editor.md对于 form 表单也是支持的,就是可以在 form 里放一个隐藏的 textarea,editor.md 就会自动的把用户输入的 markdown 内容填写到 textarea 里,后续点击 submit就能自动提交

 此时,我们再运行代码看一下结果

这时候我们发现了一个问题:代码改完之后,编辑器发生了基因突变

前端代码样式有问题,我们只能通过观察 chorme 开发者工具来找问题

我们可以看到,是当前的 form 出现了问题

 具体分析如下:

解决方案:给 form 设置高度

此时,我们就可以正常发布博客了,但是此时却出现了乱码的问题:

乱码有两种情况:

1、提交博客的时候乱码

2、获取博客的时候乱码

我们只需要看一下数据库,就知道是哪种情况; 

这时候就可以发现,是提交的时候乱码了

注意:在获取标题和正文的时候,需要指定请求的字符集,也就是告诉 servlet 按照哪种字符集来进行解析请求

此时,再次点击提交,就可以正常的发布博客了

猜你喜欢

转载自blog.csdn.net/weixin_73616913/article/details/132514383