博客系统的前后端实现

前面的学习中, 我们基于 HTML, CSS, JavaScript 实现了一个简单的博客系统的页面.
接下来我们基于博客系统页面来实现一个带服务器版本的博客程序.

1.准备工作

1.创建项目

2.引入依赖


<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.0modelVersion>

    <groupId>org.examplegroupId>
    <artifactId>blog_systemartifactId>
    <version>1.0-SNAPSHOTversion>

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

    <dependencies>
        
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>3.1.0version>
            <scope>providedscope>
        dependency>

        
        <dependency>
            <groupId>com.fasterxml.jackson.coregroupId>
            <artifactId>jackson-databindartifactId>
            <version>2.15.2version>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.49version>
        dependency>

    dependencies>

project>

3.创建必要的目录
博客系统的前后端实现_第1张图片

2.数据库设计

设计表结构,有几个表,每个表里面都有啥~~

要存储的主要是两个部分

1.博客数据

blog 博客id,标题,正文,userid,发布时间

2.用户存储

user userid,username,password[用户的头像,用户的github…暂时不搞]

--一般对于建表的SQL都会单独搞一个.sql文件来保存.
--后续程序可能需要在不同的主机上部署,部署的时候就需要在对应的主机上把数据库也给创建好.
--把建表sql保存好,方便在不同的机器上进行建库建表

create database if not exists blog_system;

use blog_system;

drop table if exists blog;
create table blog
(
    blogId   int primary key auto_increment, -- 博客 id
    title    varchar(128),                   -- 博客标题
    content  varchar(4096),                  -- 博客正文
    userId   int,                            -- 博客作者 id
    postTime datetime
);

drop table if exists user;
-- 一般都会在建表的时候删一下,删一下的目的是为了清空之前残留的数据~~
create table user(
    userId int primary key auto_increment,
    username varchar(128),
    password varchar(128)
);

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

3.封装数据库操作

JDBC

1.封装数据库的连接操作

一个经典的 web 项目结构:MVC
M —> Model(和数据相关的部分)
V —> View(和界面相关的部分)
C —> COntroller(联系界面和数据之间的业务逻辑)

博客系统的前后端实现_第2张图片
懒汉单例模式
真正使用的时候,才创建实例
如果不用,就不创建了

package model;

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

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

/**
 * @projectName: blog_system
 * @package: model
 * @className: DBUtil
 * @author: 王嘉辉
 * @description:
 * @date: 2023/10/23 14:46
 * @version: 1.0
 */
/*
* 通过这个类,封装数据库的连接操作
* */
public class DBUtil {
    //这个类中要提供DataSource,DataSource 对于一个项目来说,有一个就行了(单例)
    private static 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?characterEncoding=utf8&useSSL=false");
                    ((MysqlDataSource) dataSource).setUser("root");
                    ((MysqlDataSource) dataSource).setPassword("0126");
                }
            }
        }
        return dataSource;
    }

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

2.创建两个表对应的实体类

package model;

/**
 * @projectName: blog_system
 * @package: model
 * @className: User
 * @author: 王嘉辉
 * @description:
 * @date: 2023/10/23 15:05
 * @version: 1.0
 */
/*
 * 这个类表示数据库中User表的内容
 * 每个 User 对象,就对应 User 表中的一条记录
 * */
public class User {
    private int userId;
    private String username;
    private String password;

    public int getUserId() {
        return userId;
    }

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

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

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

package model;

import java.sql.Timestamp;

/**
 * @projectName: blog_system
 * @package: model
 * @className: Blog
 * @author: 王嘉辉
 * @description:
 * @date: 2023/10/23 15:05
 * @version: 1.0
 */

/*
* 这个类表示数据库中blog表的内容
* 每个 blog 对象,就对应 blog 表中的一条记录
* */
public class Blog {
    private int blogId;
    private String title;
    private String content;
    private int userId;
    private Timestamp postTime;

    public int getBlogId() {
        return blogId;
    }

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

    public String getTitle() {
        return title;
    }

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

    public String getContent() {
        return content;
    }

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

    public int getUserId() {
        return userId;
    }

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

    public Timestamp getPostTime() {
        return postTime;
    }

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

3.封装一些必要的增删改查操作

在这里插入图片描述

在这里插入图片描述
DAO —> Data Access Object
通过这样的对象来访问数据

package model;


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

/**
 * @projectName: blog_system
 * @package: model
 * @className: BlogDao
 * @author: 王嘉辉
 * @description:
 * @date: 2023/10/23 15:14
 * @version: 1.0
 */
public class BlogDao {
    // 把一个博客对象插入到 blog 表中
    public void insert(Blog blog) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //建立连接
            connection = DBUtil.getConnection();
            //构造sql语句
            String sql = "insert into blog values(null, ?, ?, ?, ?)";
            statement = connection.prepareStatement(sql);
            statement.setString(1, blog.getTitle());
            statement.setString(2, blog.getContent());
            statement.setInt(3, blog.getUserId());
            statement.setTimestamp(4, blog.getPostTime());
            //执行sql
            statement.executeUpdate();
        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            //关闭
            DBUtil.close(connection, statement, null);
        }
    }
    // 从 blog 表中查找到所有的 blog 对象
    public List<Blog> selectAll() {
        List<Blog> blogs = new ArrayList<>();
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
       try {
           connection = DBUtil.getConnection();
           String sql = "select * from blog";
           statement = connection.prepareStatement(sql);

           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 e) {
           e.printStackTrace();
       }finally {
           DBUtil.close(connection, statement, resultSet);
       }
       return blogs;
    }
    // 从 blog 表中查找到指定的 blog 对象
    public Blog selectOne(int blogId) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "select * from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, blogId);
            resultSet = statement.executeQuery();

            if (resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setUserId(resultSet.getInt("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                return blog;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }
    // 删除指定的 blog 对象
    public void delete(int blogId) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "delete from blog where blogId = ?";
            statement.setInt(1, blogId);
            statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection, statement, null);
        }
    }
}

package model;


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

/**
 * @projectName: blog_system
 * @package: model
 * @className: BlogDao
 * @author: 王嘉辉
 * @description:
 * @date: 2023/10/23 15:14
 * @version: 1.0
 */
public class BlogDao {
    // 把一个博客对象插入到 blog 表中
    public void insert(Blog blog) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //建立连接
            connection = DBUtil.getConnection();
            //构造sql语句
            String sql = "insert into blog values(null, ?, ?, ?, ?)";
            statement = connection.prepareStatement(sql);
            statement.setString(1, blog.getTitle());
            statement.setString(2, blog.getContent());
            statement.setInt(3, blog.getUserId());
            statement.setTimestamp(4, blog.getPostTime());
            //执行sql
            statement.executeUpdate();
        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            //关闭
            DBUtil.close(connection, statement, null);
        }
    }
    // 从 blog 表中查找到所有的 blog 对象
    public List<Blog> selectAll() {
        List<Blog> blogs = new ArrayList<>();
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
       try {
           connection = DBUtil.getConnection();
           String sql = "select * from blog";
           statement = connection.prepareStatement(sql);
           resultSet = statement.executeQuery();
           while (resultSet.next()) {
               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 e) {
           e.printStackTrace();
       }finally {
           DBUtil.close(connection, statement, resultSet);
       }
       return blogs;
    }
    // 从 blog 表中查找到指定的 blog 对象
    public Blog selectOne(int blogId) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "select * from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, blogId);
            resultSet = statement.executeQuery();

            if (resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setUserId(resultSet.getInt("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                return blog;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }
    // 删除指定的 blog 对象
    public void delete(int blogId) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "delete from blog where blogId = ?";
            statement.setInt(1, blogId);
            statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection, statement, null);
        }
    }
}

4.前后端交互逻辑的实现

1.博客列表页

展示博客列表

1.约定前后端交互接口

请求:
GET/blog

响应:

博客系统的前后端实现_第3张图片

2.写后端代码

创建一个Servlet来处理对应的请求

package api;

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

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

/**
 * @projectName: blog_system
 * @package: api
 * @className: BlogServlet
 * @author: 王嘉辉
 * @description:
 * @date: 2023/10/24 11:10
 * @version: 1.0
 */
@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();
        String respString = objectMapper.writeValueAsString(blogs);
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(respString);
    }
}

3.写前端代码

构造ajax请求,按照上述约定,发送HTTP请求
此时得修改前端代码.(先把前端代码拷贝到咱们的项目中)

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>博客列表页title>

    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/list.css">

    
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js">script>


head>
<body>
    
    <div class="nav">
        
        <img src="image/1.jpg" alt="">
        
        <div class="title" >我的博客系统div>
        
        <div class="spacer">div>
        <a href="list.html">主页a>
        <a href="edit.html">写博客a>
        
        <a href="">注销a>
    div>


    
    <div class="container">
        
        <div class="container-left">
            
            <div class = "card">
                
                <img src = "image/3.jpg" alt = "">
                
                <h3>南北h3>
                
                <a href="https://gitee.com/D2814413094">Gitee 地址a>
                
                <div class="counter">
                    <span>文章span>
                    <span>分类span>
                div>
                <div class="counter">
                    <span>3span>
                    <span>1span>
                div>

            div>
        div>
        
        <div class="container-right">
            
        div>
    div>

    <script>
        //通过ajax给服务器发请求,获取到所有的博客数据,并且构造到页面上
        function getBlogs() {
            $.ajax({
                type:'get',
                url:'blog',
                success:function (body) {
                    //根据返回的数据,构造出页面中对应的元素
                    let containerRight = document.querySelector('.container-right');
                    for (let blog of body) {
                        let blogDiv = document.createElement("div");
                        blogDiv.className = 'blog';
                        let titleDiv = document.createElement("div")
                        titleDiv.className = 'title';
                        titleDiv.innerHTML = blog.title;
                        let dateDiv = document.createElement("div");
                        dateDiv.className = 'data';
                        dateDiv.innerHTML = blog.postTime;
                        let descDiv = document.createElement("div");
                        descDiv.className = 'desc';
                        descDiv.innerHTML = blog.content;
                        let a = document.createElement("a");
                        a.href = 'detail.html?blogId=' + blog.blogId;
                        a.innerHTML = '查看全文 >>';

                        //把上述标签构造好了之后,还需要组合起来
                        blogDiv.appendChild(titleDiv);
                        blogDiv.appendChild(dateDiv);
                        blogDiv.appendChild(descDiv);
                        blogDiv.appendChild(a);
                        containerRight.appendChild(blogDiv);
                    }
                }
            });
        }
        //要记得在定义完函数以后调用函数.
        getBlogs();
    script>
body>
html>

前端代码要做的事情,就是根据咱们当前这个页面的结构,来构造出内容~~

页面结构是啥,这里的内容就怎么构造~

我们在数据库中插入数据后发现当前发现有一些问题.

1.显示的时间不对

针对web程序的问题,首先要做的就是先明确问题是由前端还是后端引起的

通过抓包就可以知道问题是哪边的!

如果抓包结果返回的数据就是时间戳,不是格式化时间,说明是后端问题.
如果抓包的结果是格式化时间,说明是前端问题.

在这里插入图片描述

当前可以看到后端返回的就是时间戳了!!

在这里插入图片描述
后端返回的数据,是Jackson把blogs转成json字符串
直接返回的
jackson会遍历当前的blogs List,取到每个blog对象
去调用blog对象的每个getter方法,拿到对应的属性,构造json字符串

博客系统的前后端实现_第4张图片
由于这个方法返回的是时间戳
导致最终构造json字符串的时候往里构造的内容就是时间戳了~

如果此处可以得到一个格式化时间,后续的json字符串里,也就是格式化时间了~~

具体如何生成格式化时间?
要用到SimpleDateFormat这个类~

在这里插入图片描述
咱们要生成格式化时间,这个时间具体是啥样的格式?需要描述一下~~

所以Blog里的方法要改为

 public String getPostTime() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return format.format(postTime);
    }

同时BlogDao里的数据库操作也要改成

statement.setString(4, blog.getPostTime());

2.博客文章的顺序应该是新的文章在上面

只需把BlogDao里的操作改为

String sql = "select * from blog order by postTime desc";

3.博客列表页,显示的是正文的一段摘要

我们就需要更改BlogDao中的Content

String content = resultSet.getString("content");
if (content.length() > 50) {
	content = content.substring(0,50) + "...";
}
blog.setContent(content);

我们在数据库中在插入数据然后观察:

2.博客详情页

1.约定前后端交互接口

请求:
GET/blog?blogId=1

响应

{
    blogId:1,
    title:"第一篇博客",
    content:"这是正文",
    //这里要获取到完整的博客正文
    userId:1,
    postTime:"2023-01-26 12:00:00"
}

2.编写后端代码

每个Servlet类都是关联了一个路径的.
如果路径确定了,对应的Servlet也就明确了.

所以我们还是只需调整BlogServlet即可!

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
        //从 query string 中查询一下是否有blogId,如果有,就查询指定博客;如果没有就是查询所有博客
        BlogDao blogDao = new BlogDao();
        String blogId = req.getParameter("blogId");
        if(blogId == null) {
            List<Blog> blogs = blogDao.selectAll();
            String respString = objectMapper.writeValueAsString(blogs);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respString);
        }else {
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            String respString = objectMapper.writeValueAsString(blog);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respString);
        }
    }

此处的关键就是根据 blogId 这个query string是否存在,来分两个情况讨论~

3.编写前端代码

在这里插入图片描述
我们预期的url:blog?blogId=1
?blogId=1这一段就是从location,search来的
location是一个全局标量
相当于Document一样

博客系统的前后端实现_第5张图片
由于当前响应的Content-Type 是 application/json
此时jquery就会直接把这个响应数据,转成js对象

blog.blogId
body.title

设置正文,正文内容应该是 markdown 格式的数据
此处要显示的应该是渲染过的markdown的内容,而不是markdown的原始字符串.
第一个参数,是一个html元素的id,接下来渲染的结果会放到对应的元素中.

在这里插入图片描述

DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>博客详情页title>
  <link rel = "stylesheet" href="css/common.css">
  <link rel = "stylesheet" href="css/detail.css">

  
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js">script>
  
  <link rel="stylesheet" href="editor.md/css/editormd.min.css" />
  <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>


head>
<body>

<div class="nav">
  
  <img src="image/1.jpg" alt="">
  
  <div class="title" >我的博客系统div>
  
  <div class="spacer">div>
  <a href="list.html">主页a>
  <a href="edit.html">写博客a>
  
  <a href="">注销a>
div>


<div class="container">
  
  <div class="container-left">
    
    <div class = "card">
      
      <img src = "image/3.jpg" alt = "">
      
      <h3>南北h3>
      
      <a href="https://gitee.com/D2814413094">Gitee 地址a>
      
      <div class="counter">
        <span>文章span>
        <span>分类span>
      div>
      <div class="counter">
        <span>3span>
        <span>1span>
      div>

    div>
  div>
  
  <div class="container-right">
  <h3>h3>
    <div class="data">div>
    <div id="content">

    div>
  div>
  <script>
    function getBlog() {
      $.ajax({
        type:'get',
        url:'blog'+location.search,
        success:function (body) {
          //设置博客标题
          let h3 = document.querySelector('.container-right h3');
          h3.innerHTML = body.title;
          //设置发布时间
          let dataDiv = document.querySelector('.container-right .data');
          dataDiv.innerHTML = body.postTime;
          //设置正文,正文内容应该是 markdown 格式的数据
          //此处要显示的应该是渲染过的markdown的内容,而不是markdown的原始字符串.
          //第一个参数,是一个html元素的id,接下来渲染的结果会放到对应的元素中.
          editormd.markdownToHTML('content',{markdown:body.content});
        }
      });
    }
    getBlog();
  script>
div>

body>
html>

当我们修改完代码以后,点击博客详情,如果发现内容还是之前的内容,就需要使用 Ctrl + f5 强制刷新,避免浏览器读取缓存~~

3.博客登录页

博客系统的前后端实现_第6张图片
输入用户名,密码
点击登录,就会触发登录操作~~

期望,点击的时候,给后端发起一个http请求
后端做一些登录相关的逻辑(验证用户名密码之类的)

1.约定前后端交互接口

此处提交用户名密码,可以使用form也可以使用ajax
form是更简单的做法~

请求
POST/login
Content-Type:application/x-www-form-urlencoded
专属于form表单的类型

username=zhangsan&password=123
query string 就是这个格式,在服务器直接使用getParameter就能获取到内容

响应:
HTTP/1.1 302
Location:list.html
登陆成功,直接跳转主页

如果是通过302跳转
前端必须使用form,不能使用ajax
ajax的话,收到302响应,不会触发页面跳转~~
(必须使用js来跳转)

2.编写后端代码

需要创建一个Servlet来处理上述login的请求!!
博客系统的前后端实现_第7张图片

如果前端传过来的用户名带有中文~~,此时这个代码拿到的username可能是乱码
需要明显的告诉Servlet按照啥样的编码方式来理解请求参数!!

在这里插入图片描述

会话,让服务器保存当前用户的一些数据!

在这里插入图片描述

会话可以理解成一个hash表~
每个用户(每个客户端)都会对应到这个表里的一个键值对
其中键是一个字符串,sessionId(唯一的字符串)
其中值是HttpSession对象了
这个对象,也可以按照键值对的方式来存储其他数据(getAttribute,setAttribute)

package api;

import model.User;
import model.UserDao;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @projectName: test-2023-10-27
 * @package: api
 * @className: LoginServlet
 * @author: 王嘉辉
 * @description:
 * @date: 2023/10/27 17:36
 * @version: 1.0
 */
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        //1.从请求中获取到用户名和密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null || username.equals("") || password == null || password.equals("")) {
            String html = "

登录失败!缺少用户名或者密码!

"
; resp.setContentType("text/html;charset=utf8"); resp.getWriter().write(html); return; } //2.读取数据库,看这里的用户名密码是否匹配 UserDao userDao = new UserDao(); User user = userDao.selectUserByName(username); if(user == null) { String html = "

用户名或密码错误!

"
; resp.setContentType("text/html;charset=utf8"); resp.getWriter().write(html); return; } if(!password.equals(user.getPassword())) { String html = "

用户名或密码错误!

"
; resp.setContentType("text/html;charset=utf8"); resp.getWriter().write(html); return; } //3.需要设置会话 HttpSession session = req.getSession(true); //此处就把用户对象存储到session中了,下次用户访问其他页面,就可以直接拿到会话,进一步拿到之前的user对象了. session.setAttribute("user",user); //4.返回一个重定向相应,能够跳转到博客列表页 resp.sendRedirect("list.html"); } }

3,编写前端代码

在这里插入图片描述

id属性,只是针对html生效,只是用来方便获取到该元素.
name属性则是针对form表单构造http请求的~

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>博客登录页title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/login.css">
head>
<body>
    
    <div class="nav">
    
    <img src="image/1.jpg" alt="">
    
    <div class="title" >我的博客系统div>
    
    <div class="spacer">div>
    <a href="list.html">主页a>
    <a href="edit.html">写博客a>

div>

    
    <div class="login-container">
        
        <div class="login-dialog">
            <h3>登录h3>
            
            <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>
body>
html>

此时,输入正确的账号密码,就会跳转到博客列表页~


输入错误

博客系统的前后端实现_第8张图片

4.实现强制要求登录

做出以下设定:
当用户访问博客列表页/详情页/编辑页的时候,必须是登录状态,如果未登录,则直接跳转到登录页要求用户登录

在博客列表页/详情页/登录页,页面加载的时候,发起一个ajax请求,通过这个请求,访问服务器,获取到当前的登录状态.
如果当前未登录,则跳转到登录页面
如果已登录,则不做任何操作~~

1.约定前后端交互接口

请求:
GET/login

响应:
登录成功,就返回200这样的响应
登录失败(未登录状态),返回403这样的响应

2.编写后端代码

在loginServlet写一个doGet判定当前的登录状态.

    //通过这个方法,判定用户的登录状态,已登录,返回200,未登录,返回403
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //看当前请求是否存在会话,并且当前的会话是否包含user对象
        //getSession(boolean create)意思是返回当前reqeust中的HttpSession ,如果当前reqeust中的HttpSession 为null,当create为true,就创建一个新的Session,否则返回null;
        HttpSession session = req.getSession(false);
        if (session == null) {
            resp.setStatus(403);
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            //虽然会话对象存在,但是用户对象没有,未登录状态
            resp.setStatus(403);
            return;
        }
        //已经登陆了
        //200 是默认的状态码,此处不写200也是可以的
        resp.setStatus(200);
    }
}

3.编写前端代码

需要在博客列表页/详情页加入

  <script>
        function getLoginStatus() {
            $.ajax({
                type:'get',
                url:'login',
                success:function (body) {
                    //响应200的时候,执行success回调
                    //用户已经登录啥都不用干
                    console.log("用户已经登录了.");
                },
                error:function (body) {
                    //响应403的时候,执行error回调(只要返回不是2开头的,都会触发error)
                    //跳转到login.html主页
                    location.assign("login.html");
                }
            });
        }
        getLoginStatus();
script>

5.显示用户信息

在博客列表页,此处显现的是登录的用户信息!!

在博客详情页,显示的是文章作者的信息!!

针对这两个部分的数据,就需要分别进行获取~~

1.约定前后端交互接口

在博客列表页,要获取到登录用户的信息~~
可以直接复用刚刚的那个 getLoginStatus 方法.

让这个方法在登录成功之后,就把当前用户信息,给返回回来~~

请求:
GET/login

响应:
HTTP/1.1 200
Content-Type:application/json;

{
	userId:1
	username:zhangsan,
}

页面拿到这个数据就可以显示到界面上了~~

2.开发后端代码

基于现有代码进行修改

        //200 是默认的状态码,此处不写200也是可以的
        resp.setStatus(200);
        resp.setContentType("application/json;charset=utf8");
        user.setPassword("");//避免把密码也返回给前端,减少密码泄露的风险
        String respJson = objectMapper.writeValueAsString(user);
        resp.getWriter().write(respJson);

3.开发前端代码

基于现有代码进行修改

三个页面都在使用 getLoginStatus
实际上,只是需要在博客列表页,做出这样的调整,另外两个页面不变!!

<script>
        function getLoginStatus() {
            $.ajax({
                type:'get',
                url:'login',
                success:function (body) {
                    //响应200的时候,执行success回调
                    //用户已经登录啥都不用干
                    console.log("用户已经登录了.");
                    //把返回的用户名,设置到页面中
                    let h3 = document.querySelector('.card h3');
                    //body 已经是一个 js 对象了,就是前面服务器返回的 json 格式的 user 对象
                    h3.innerHTML = body.username;
                },
                error:function (body) {
                    //响应403的时候,执行error回调(只要返回不是2开头的,都会触发error)
                    //跳转到login.html主页
                    location.assign("login.html");
                }
            });
        }
script>

在针对详情页,进行处理.
详情页这里.显示的是当前文章的作者信息.

先根据blogId查询到文章对象,进一步拿到文章的作者的id
再根据作者id查询对应作者名字,显示到页面上~

1.约定前后端交互接口

请求:
GET/user?blogId = 1

响应:
HTTP/1.1 200
Content-Type:application/json;

{
	userId:1
	username:zhangsan,
}

2.编写后端代码

创建一个新的Servlet处理上述请求.

package api;

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

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @projectName: test-2023-10-30
 * @package: api
 * @className: UserServlet
 * @author: 王嘉辉
 * @description:
 * @date: 2023/10/30 16:35
 * @version: 1.0
 */
@WebServlet("/user")
public class UserServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf8");
        //1.先读取出blogId
        String blogId = req.getParameter("blogId");
        if (blogId == null || blogId.equals("")) {
            //直接返回一个userId为0的对象,因为最终返回的是一个json数据
            //此处也是返回 json 格式比较好,如果返回一个html,前端处理就会很麻烦
            String respJson = objectMapper.writeValueAsString(new User());
            resp.getWriter().write(respJson);
            System.out.println("参数给定的 blogId 为空!!");
            return;
        }
        //2.查询数据库,查询对应的Blog对象
        BlogDao blogDao = new BlogDao();
        Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
        if (blog == null) {
            String respJson = objectMapper.writeValueAsString(new User());
            resp.getWriter().write(respJson);
            System.out.println("参数给定的 blogId 不存在!!");
            return;
        }
        //根据 blog 中的 userId,查询作者信息
        UserDao userDao = new UserDao();
        User user = userDao.selectUserById(blog.getUserId());
        if(user == null) {
            String respJson = objectMapper.writeValueAsString(new User());
            resp.getWriter().write(respJson);
            System.out.println("该博客对应的作者不存在!!");
            return;
        }
        //4.把user对象返回给页面
        String respJson = objectMapper.writeValueAsString(user);
        resp.getWriter().write(respJson);
    }
}

3.编写前端代码

 <script>
    function getAuthor() {
      $.ajax({
        type:'get',
        url:'user' + location.search,
        success:function (body) {
          //把响应中的得到的user对象的数据,给构造到页面上
          if (body.userId == 0) {
            //服务器没有找到匹配的用户
            alert("当前未找到作者信息!")
            return;
          }
          //body 是一个合法的 user 对象
          let h3 = document.querySelector('.card h3');
          h3.innerHTML = body.username;
        }
      });
    }
    getAuthor();
  script>

博客系统的前后端实现_第9张图片

6.用户退出登录(注销)

在这里插入图片描述

这个注销,是一个a标签
这个a标签在点击的时候,就会触发一个GET请求
服务器收到这个get请求,就可以把当前用户的会话中的user对象给删掉即可!!

1.约定前后端交互接口

请求:
GET/logout

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

2.编写后端代码

创建新的Servlet

package api;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @projectName: test-2023-10-30
 * @package: api
 * @className: LogoutServlet
 * @author: 王嘉辉
 * @description:
 * @date: 2023/10/30 17:14
 * @version: 1.0
 */
@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;
        }
        //user 存在,就删除了,不存在,也不会有副作用
        session.removeAttribute("user");
        resp.sendRedirect("login.html");
    }
}

3.编写前端代码

只需给a标签写个href的值即可!

在这里插入图片描述

登录后

点击注销

7.实现发布博客

在博客编辑页进行相关操作~~

用户写的博客标题和正文,就可以随着点击按钮而进行上传
服务器就可以保存上述数据到数据库中
接下来,后续就可以在博客列表页和博客详情页中看到了刚才新的博客了~

1.约定前后端交互接口

使用form提交博客~~

请求:
POST/blog
Content-Type:application/x-www-form-urlencoded

title=标题&content=…

响应:
HTTP/1.1 302
Location:list.html

发布成功,直接跳转到列表页~

2.编写后端代码

博客系统的前后端实现_第10张图片

package api;

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

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.List;

/**
 * @projectName: blog_system
 * @package: api
 * @className: BlogServlet
 * @author: 王嘉辉
 * @description:
 * @date: 2023/10/24 11:10
 * @version: 1.0
 */
@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,如果有,就查询指定博客;如果没有就是查询所有博客
        BlogDao blogDao = new BlogDao();
        String blogId = req.getParameter("blogId");
        if(blogId == null) {
            List<Blog> blogs = blogDao.selectAll();
            String respString = objectMapper.writeValueAsString(blogs);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respString);
        }else {
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            String respString = objectMapper.writeValueAsString(blog);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respString);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        //1.先从请求中拿到标题和正文
        String title = req.getParameter("title");
        String content = req.getParameter("content");
        if(title == null || title.equals("") || content == null || content.equals("")) {
            String html = "

title 或者 content 为空!! 新增博客失败!

"
; resp.setContentType("text/html;charset=utf8"); resp.getWriter().write(html); return; } //2.从会话中拿到作者id HttpSession session = req.getSession(false); if (session == null) { String html = "

当前用户未登录 新增博客失败!

"
; resp.setContentType("text/html;charset=utf8"); resp.getWriter().write(html); return; } User user = (User) session.getAttribute("user"); if (user == null) { String html = "

当前用户未登录 新增博客失败!

"
; resp.setContentType("text/html;charset=utf8"); resp.getWriter().write(html); return; } //3.构造blog对象 Blog blog = new Blog(); blog.setUserId(user.getUserId()); blog.setTitle(title); blog.setContent(content); blog.setPostTime(new Timestamp(System.currentTimeMillis())); //4.插入 Blog 对象到数据库中 BlogDao blogDao = new BlogDao(); blogDao.insert(blog); //5.跳转到博客列表页 resp.sendRedirect("list.html"); } }

3.编写前端代码

修改页面,加上form表单,同时让页面提交的时候按照咱们的约定,构造出HTTP请求.

博客系统的前后端实现_第11张图片

博客系统的前后端实现_第12张图片
这里是md编辑器
这个div内部的内容,其实是被editor.md做出了各种修改~~

如何才能把编辑框内部内容
作为form的一部份,提交给服务器?

editor.md的官方文档的Demo里给了答案~

(1)先给#editor div 中放一个隐藏的 textarea.后续编辑器输入框的内容就会被自动放到这个 textarea 中.

(2)在editor.md的初始化代码中,需要新增一个选项saveHTMLToTextarea:true

博客系统的前后端实现_第13张图片

博客系统的前后端实现_第14张图片

你可能感兴趣的:(Java项目实现,java-ee)