【JavaEE初阶系列】——博客系统(编写服务器/前后端交互代码)

目录

🚩部署页面需求

🚩准备工作

🚩获取博客列表页

🚩博客详情页

🚩实现登录页面

🎈强制要求登录

🎈显示用户信息

🚩退出登录

 🚩发布博客


🚩部署页面需求

博客系统,基本情况,主要是四个页面

  • 博客列表页,显示出当前网站上都有哪些博客

  • 博客详情页,点击列表上的某个博客,就能进入对应详情页(显示出博客的具体内容)
  • 博客编辑页,让用户输入博客内容,并且发送到服务器

这个部分是一个markdown编辑器,markdown(md)是程序员常用的一种用来写文档的语言。

  • 登录页

当下要完成的任务:

基于上述页面,编写服务器/前后端交互代码l,通过这些代码,完成博客系统完整的功能。

📝实现博客列表页

让页面从服务器拿到博客数据(数据库)

📝实现博客详情页

让页面从服务器拿到博客数据(数据库)

📝实现登录功能

📝实现强制要求登录

(当前处于未登录的状态下,其他的页面,博客列表,博客详情,博客编辑.....就会强制跳转到登录页)要求用户登录之后才能使用

📝实现显示用户信息

从服务器获取到,博客列表页,拿到的是当前登录的用户的信息,博客详情页,拿到的是文章作者的信息。

📝实现退出登录

📝发布博客

博客编辑页,输入文章标题和内容之后,点击发布,就能把这个数据上传到服务器上并保存

这些功能搞定,就基本上的博客系统搞定


🚩准备工作

写一个复杂一些的代码,往往需要先理清楚思路,相对于细节来说,理清思路是更复杂的。(为了实现这个代码,要写哪些类,有哪些方法(方法的具体细节,我们先不用写))

1.创建项目,引入依赖,把当前的这些前端页面也导入到项目中

<?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>blog_system</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/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.0</version>
        </dependency>
    </dependencies>

</project>
  • 2.数据库设计

设计好对应的表结构,并且把数据库相关代码,也进行封装

a>找到实体

     博客(blog表) 用户(user表)

b>确认实体之间的关系

    一对多  一个博客,只能属于一个用户,一个用户,可以发布多个博客

create table blog (
    blogId int primary key auto_increment,
    title varchar(1024),
    content varchar(4096),
    postTime datetime,
    userId int
);

create table user (
    userId int primary key auto_increment,
    username varchar(50) unique,    -- 用户名也要求是不能重复的.
    password varchar(50)
);
  • 3.把数据操作的代码进行一些封装

进行网站开发的过程中,一种常见的代码组织结构,MVC结构

M model:操作数据的代码

V view:操作/构造界面的代码

C controller:业务逻辑,处理前端请求

  • 📝DBUtil完成对于数据库建立连接和关闭连接的实现(这里需要用到懒汉模式)懒汉模式是不安全的,当前sevlet本身就是在多线程环境下执行的,tomcat收到多个请求的是,就会使用多线程的方式,执行不同的servlet代码
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;

// 通过这个类, 封装数据库建立连接的操作.
// 由于接下来代码中, 有多个 Servlet 都需要使用数据库. 就需要有一个单独的地方来把 DataSource 这里的操作进行封装.
// 而不能只是放到某个 Servlet 的 init 中了.
// 此处可以使用 单例模式 来表示 dataSource
public class DBUtil {
    private volatile 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/java109_blog_system?characterEncoding=utf8&useSSL=false");
                    ((MysqlDataSource) dataSource).setUser("root");
                    ((MysqlDataSource) dataSource).setPassword("105528clzyf.");
                }
            }
        }
        return dataSource;
    }
    public static Connection getConnection() throws SQLException {
        return getDataSource().getConnection();
    }
    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                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);
            }
        }
    }
}

  • 📝创建实体类,每个表都需要搞一个专门的类来表示,表里的一条数据,就会对应到这个类的一个对象。这样数据库中的数据和代码联系在一起了


Blog类对应blog表,User类对应user表

package model;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;

// Blog 对象就是对应到 blog 表中的一条记录.
// 表里有哪些列, 这个类里就应该有哪些属性
public class Blog {
    private int blogId;
    private String title;
    private String content;
    private Timestamp postTime;
    private int userId;

    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 String getPostTime() {
        // Java 标准库提供了一个 SimpleDateFormat 类, 完成时间戳到格式化时间的转换.
        // 这个类的使用, 千万不要背!!! 都要去查一下!! 背大概率会背错!!!
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return simpleDateFormat.format(postTime);
    }
    public void setPostTime(Timestamp postTime) {
        this.postTime = postTime;
    }
    public int getUserId() {
        return userId;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
    @Override
    public String toString() {
        return "Blog{" +
                "blogId=" + blogId +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", postTime=" + postTime +
                ", userId=" + userId +
                '}';
    }
}
package model;

// 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;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
 
  • 📝还需要创建两个类,来完成针对博客表和用户表的增删改查操作 BlogDao  UserDao(后续写)

BlogDao进行对博客表增删查改

package model;

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

// 通过 BlogDao 来完成针对 blog 表的操作
public class BlogDao {
    // 1. 新增操作 (提交博客就会用到)
    public void insert(Blog blog) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            // 1. 建立连接
            connection = DBUtil.getConnection();
            // 2. 构造 SQL
            String sql = "insert into blog values (null, ?, ?, now(), ?)";
            statement = connection.prepareStatement(sql);
            statement.setString(1, blog.getTitle());
            statement.setString(2, blog.getContent());
            statement.setInt(3, blog.getUserId());
            // 3. 执行 SQL
            statement.executeUpdate();

        } catch (SQLException e) {
            throw new RuntimeException();
        } finally {
            DBUtil.close(connection, statement, null);
        }
    }

    // 2. 查询博客列表 (博客列表页)
    //    把数据库里所有的博客都拿到.
    public List<Blog> getBlogs() {
        List<Blog> blogList = new ArrayList<>();
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DBUtil.getConnection();
            String sql = "select * from blog order by postTime desc";
            statement = connection.prepareStatement(sql);
            resultSet = statement.executeQuery();
            while (resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                // 此处读到的正文是整个文章内容. 太多了. 博客列表页, 只希望显示一小部分. (摘要)
                // 此处需要对 content 做一个简单截断. 这个截断长度 100 这是拍脑门出来的. 具体截取多少个字好看, 大家都可以灵活调整.
                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"));
                blogList.add(blog);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return blogList;
    }

    // 3. 根据博客 id 查询指定的博客
    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 作为主键, 是唯一的.
            // 查询结果非 0 即 1 , 不需要使用 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 throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }

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

UserDao进行对用户表增删查改

package model;

import java.lang.ref.PhantomReference;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

// 通过 UserDao 完成针对 user 表的操作
public class UserDao {
    // 新增暂时不需要. 不需要实现 "注册" 功能.
    // 删除暂时不需要. 不需要实现 "注销帐户" 功能.

    // 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;
    }
}

Dao=>Data Access Object  数据访问对象,通过这两个类的对象,来完成针对数据库表的操作


🚩获取博客列表页

实现逻辑 

  • 1>约定好前后端交互接口
  • 2>编写前端代码,构造http请求(form/ajax)
  • 3>编写后端代码,处理这个请求,返回响应
  • 4>编写前端代码,解析http请求,构造页面

在博客列表页加载的时候,通过ajax给服务器发起请求,从服务器(数据库)拿到博客列表数据,并且显示在页面上。

📝约定前后端交互接口

请求GET  /blog

响应

HTTP/1.1  200  OK

Content-Type:application/json

[

  {
     blogId:1,

     title:"这是标题",

     content:"这是正文",

     postTime:"2024-5-12  20:00:00",

     userId:1

   }

]

📝让浏览器给服务器发起请求了

<script>
        function getBlogs(){
            $.ajax({
                type:'get',
                url:'blog',
                success:function(body){
                    //服务器成功响应之后,调用回调函数
                    //TODO 根据返回的响应数据,构造页面的片段

                }
            })
        }
        //定义完函数,还需要调用,才能执行
        getBlogs();
    </script>

📝服务器处理上述请求,返回响应数据(查询数据库)

  private ObjectMapper objectMapper=new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         //查询数据库,得到博客列表
        BlogDao blogDao=new BlogDao();
        List<Blog>blogs=blogDao.getBlogs();
        //把博客列表数据按照json格式返回回去
        String respJson=objectMapper.writeValueAsString(blogs);
        System.out.println("respJson"+respJson);
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(respJson);
    }

List<Blog>blogs,List,jackson就会把结果转成数组,每个元素又是一个Blog对象,转成的数据的每个元素也就是json构成的blog对象。

📝让前端代码处理上述响应数据并构造页面

构造html片段,显示到页面上。

 

我们在数据库中添加新的博客,看效果是什么样子的。 

按照时间的降序排序.


🚩博客详情页

点不同的博客,跳转过去之后, 都会带有不同的blogId的query string,后续在博客详情页中,就可以也给服务器发起ajax请求,根据这里的blogId,查询数据库,博客的具体内容再返回,前端还是把得到的数据给构造到页面上。

📝约定前后端交互接口

请求:GET  /blog?blogId=1

响应:

HTTP/1.1  200 OK

Content-Type:application/json

{

  blogId:1

  title:"这是第一篇博客”

  content:"这是博客正文",

  postTime:"2024-5-13 0:43:00",

 userId:1

}

📝让前端代码,通过ajax发起请求

此处有个问题发起ajax请求的时候,要带有blogId,blogId当前就处于博客详情页的url中,这里可以通过location.search方式拿到页面url中的query string

    <script>
        function getBlog(){
            $.ajax({
                type: 'get',
                url: 'blog' + location.search,
                success:function(blog){
                    
                }
            })
        }
    </script>

url:'blog'+location.search一个路径,对应一个servlet,当前是使用一个servlet处理两种请求,博客列表页,不带query string,博客详情页,带有query string,就可以根据query string是否存在的请求,区分是哪种请求,分别返回不同的数据即可。使用两个servlet处理这里的两个请求,也可以,就约定成不同的路径即可,使用一个servlet也可以,没有明确的标准。

📝让服务器处理这个请求

  @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        BlogDao blogDao = new BlogDao();
        String respJson = "";

        String blogId = req.getParameter("blogId");
        if (blogId == null) {
            // 请求中没有 query string, 请求来自博客列表页.
            // 查询数据库, 得到博客列表.
            List<Blog> blogs = blogDao.getBlogs();
            // 把博客列表数据按照 json 格式返回回去.
            respJson = objectMapper.writeValueAsString(blogs);
        } else {
            // 请求中存在 query string, 请求来自博客详情页.
            Blog blog = blogDao.getBlog(Integer.parseInt(blogId));
            respJson = objectMapper.writeValueAsString(blog);
        }
        System.out.println("respJson: " + respJson);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(respJson);
    }

📝前端拿到响应之后,把响应数据,构造成页面的html片段

function getBlog() {
            $.ajax({
                type: 'get',
                url: 'blog' + location.search,   // 这里需要带上 blogId 参数.
                success: function(blog) {
                    // blog 就是返回的一篇博客的内容.
                    // 形如 { blogId: 1, title: "这是标题", ..... }
                    let h3 = document.querySelector('.container-right h3');
                    h3.innerHTML = blog.title;
                    let dateDiv = document.querySelector('.container-right .date');
                    dateDiv.innerHTML = blog.postTime;
                    // 这种设置方式, 页面显示的是 md 的原始内容. 希望对这个内容进行渲染成 html
                    let contentDiv = document.querySelector('.container-right .content');
                    contentDiv.innerHTML = blog.content;
            
                }
            })
        }

        getBlog();

博客列表页中,需要循环遍历,构造的页面内容也更复杂,此处就简单一些,只需要设置这三个内容即可。

当前博客详情页,虽然能够显示出博客的正文了,但是显示的是正文的md原始数据,作为博客网站,正确的做法应该是显示出md渲染后的效果。

此处的渲染,仍然是通过 第三方库(editor.md),editor.md官方文档上,给出了具体的例子,来完成上述的操作。

 editormd.markdownToHTML('content', { markdown: blog.content });

是editormd这个库给的一个全局变量,把依赖正确引入了这个变量就能直接使用,这个方法的效果,就是把blog.content这里的md的原始数据,渲染成html,放到id为content的div中。

一个html标签,可以有很多的属性,class属性,往往是用来和css样式配合的。id属性,则是一个“身份标识”要求一个页面中,id必须是唯一的。


🚩实现登录页面

在登录页面中,在输入框中填写用户名和密码,点击登录,就会给服务器发起HTTP请求(可以使用ajax,也可以使用form)。

服务器处理登录请求,读取用户名密码,在数据库查询,匹配,如果正确就登录成功,创建会话,跳转到博客列表页。

由于这里,登录成功,直接进行重定向跳转,这种重定向操作,就不需要浏览器额外写代码处理,直接浏览器就自动跳转了。

📝约定前后端交互接口

请求:POST  /login

          Content-Type:application/x-www-form-urlencoded

           username=zhangsan&password=123

input标签,name属性就是这里body的key

响应: Http/1.1   302

         Location:blog_list.html

form表单,提交成功,可以直接使用302重定向跳转,如果使用ajax,ajax处理响应就需要写代码来完成跳转(不是浏览器自动完成了)

📝让前端发起请求

form

form方式比ajax表单使用简单,但是功能没有那么强大。

📝让服务器处理请求并做出响应

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //1.读取参数中的用户名和密码
        req.setCharacterEncoding("utf8");
        String username=req.getParameter("username");
        String password=req.getParameter("password");
        //验证一个参数,看下是否合理
        if(username==null||username.length()==0||password==null||password.length()==0){
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您输入用户名或者密码为空");
            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.setAttribute("user",user);
        //4.跳转到主页了
        resp.sendRedirect("blog_list.html");
    }

 点击登录,即可跳转到对应博客列表页。


🎈强制要求登录

博客列表页,详情页,编辑页,判定当前用户是否已经登录,如果未登录,则强制跳转到登录页(要求用户必须登录后才能使用)

在上述几个页面中,页面加载时,给服务器发起ajax,从服务器获取一下当前的登录状态。

1>约定前后端交互接口

请求 GET  /login

登录成功  HTTP/1.1  200

登录失败  HTTP/1.1  403

2>让前端代码发起这个请求

因为响应的处理也比较简单就顺便写了

由于我们每个页面进行访问的时候,如果没有登录,那么就都需要重新跳转到登录页面,所以我们创建一个js文件夹,包含js代码,在每个前端页面调用该函数

可以把公共的js代码,单独提取出来,放到某个.js文件中,然后通过html中的script标签,来引用这样的文件内容,此时就可以在html中调用对应的公共代码了。

// 定义新的函数, 获取登录状态
function getLoginStatus() {
   $.ajax({
        type:'get',
        url:'login',
        success:function(body){
            //已经登录的状态
            console.log("已经登录了");
        },
        error:function(){
            location.assign('login.html');
        }
   })
}
getLoginStatus();

3>让服务器处理上述请求

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过这个方法, 来反馈当前用户的登录状态.
        // 一个简单的判定方式, 直接看会话是否存在.
        // 此处使用一个更严格的方式. 不仅要求会话要存在, 还要求会话中要能保存 user 对象.
        // (之所以给出这种设定, 也是为了后面实现 "退出登录" 这样的功能来做个铺垫)
       HttpSession session=req.getSession(false);
       if(session==null){
           //会话不存在,用户属于未登录状态
           resp.setStatus(403);
           return;
       }
       User user=(User) session.getAttribute("user");
       if(user==null){
           //user对象不存在,同样也处于未登录状态
           resp.setStatus(403);
           return;
       }
       //俩个都存在,返回200
        //此处200不写也行,默认时200
        resp.setStatus(200);
    }

当前虽然登录过了,一旦重新启动服务器,此时仍然会判定为未登录状态。

登录状态是通过服务器这里的session来存储的,session这里服务器内存中类似于hashmap这样的结构,一旦服务器重启了,hashmap里面原有的内容就没了

其实这种设定,严格的说,并不科学。相比之下有更好的解决方案

  • 可以把会话进行持久性保存(文件数据库,redis...)
  • 可以使用令牌的方式(把用户信息,在服务器加密,还是保存在浏览器这边)相当于服务器没有在内存中存储当前用户的身份

javaEE进阶会学到。


🎈显示用户信息

  • 博客列表页:显示的是当前登录的用户的信息

  • 博客详情页:显示的是当前文章的作者信息

在页面加载的时候,给服务器发起ajax请求,服务器返回对应的用户数据,根据发起请求的不同页面,服务器返回不同的信息即可。


📝约定前后端交互接口

​​


📝前端代码发起请求


📝编写服务器代码,来处理上述请求

博客列表页

package servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
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;

@WebServlet("/userInfo")
public class UserInfoServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 从会话中, 拿到用户的信息返回即可.
       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;cahrset=utf8");
           resp.getWriter().write("当前用户未登录");
           return;
       }
       //此时把user对象转成json,并返回给浏览器
        resp.setContentType("application/json;charset=utf8");
       //注意,user中还有password属性,把密码返回回去,不太合适
        user.setPassword("");
        String respJson=objectMapper.writeValueAsString(user);
        resp.getWriter().write(respJson);
    }
}

博客详情页

package servlet;

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;

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

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 先拿到请求中的 blogId
        String blogId=req.getParameter("blogId");
        if(blogId==null){
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("请求中缺少blogId");
            return;
        }
        //2.blog表中查询对应的blog对象
        BlogDao blogDao=new BlogDao();
        Blog blog=blogDao.getBlog(Integer.parseInt(blogId));
        if(blog==null){
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("blogId没有找到");
            return;
        }
        //3.根据blog对象中的userId,从user表中查到对应的作者
        UserDao userDao=new UserDao();
        User user=userDao.getUserById(blog.getUserId());
        if(user==null){
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("userId没有找到");
        }
        //把这个user对象,返回到浏览器中
        user.setPassword("");
        String respJson=objectMapper.writeValueAsString(user);
        resp.setContentType("tapplication/json;charset=utf8");
        resp.getWriter().write(respJson);
    }
}

此处通过两步sql分别查询的,先查blog表里面的blog对象,再查user表。 


📝前端代码处理响应

博客详情页


        // 获取当前登录的用户信息       
        function getUserInfo() {
            $.ajax({
                type: 'get',
                url: 'userInfo',
               success: function(user) {
                    // 把拿到的响应数据, 取出其中的 username, 设置到页面的 h3 标签中!-->
                    let h3 = document.querySelector('.card h3');
                    h3.innerHTML = user.username;
               }
            });
        }
        getUserInfo();
    </script>

  博客列表页

    function getAuthorInfo() {
        $.ajax({
            type: 'get',
            url: 'getAuthorInfo' + location.search,
            success: function(user) {
                // 把拿到的 user 对象, 取出其中的 username, 设置到页面上.-->
                let h3 = document.querySelector('.card h3');
                h3.innerHTML = user.username;
        }
        });
    }
      getAuthorInfo();

🚩退出登录

博客列表/博客详情/博客编辑  导航栏中,都有一个“注销”按钮

让用户点击“注销”的时候,就能够触发一个HTTP请求(GET请求)

服务器收到这个GET请求的时候,就会把会话里的user这个Attribute给删了,由于判定用户是否是登录状态的逻辑中,需要同时验证,会话存在,且这里的user Attribute也存在,只要破坏一个,就可以使登录状态发生改变了。

为啥不直接删除session本身??主要因为,servlet没有提供,删除session方法。虽然有间接的方式(session可以设置过期时间,设置一个非常短的过期时间),也可以起到删除的效果。

session提供了removeAttribute这样的方法,可以把user这个Attribute给删了。

这个东西是a标签,可以有一个href属性,点击就会触发一个http请求,并且可能会引起浏览器跳转到另一个页面。


📝约定前后端交互接口 

请求 GET   /logout

响应 直接重定向到登录页 Http/1.1  302

                                        Location:login.html


📝编写前端代码,发送请求

不用写ajax,直接给a标签设置href属性即可


📝编写后端代码,处理这个请求,完成退出登录的操作

@WebServlet("/logout")
public class LoginoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 先拿到会话对象
        HttpSession session = req.getSession(false);
        if (session == null) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前您尚未登录");
            return;
        }
        // 再把会话中的 user 的属性给删除掉
        session.removeAttribute("user");
        // 跳转到博客登录页
        resp.sendRedirect("login.html");
    }
}

 🚩发布博客

当点击提交的时候,就需要构造HTTP请求,把此时的页面中的标题和正文都传输到服务器这边,服务器把这个数据存入数据库即可。

此时这里的http请求,可以使用ajax,也可以使用form(这种填写输入框,提交数据的场景,使用form会更方便)


📝约定前后端交互接口


📝编写前端代码构造请求

标题,本身就是一个咱们自己写的input,给它加上name属性,很容易,但是博客正文,是由editor md构成的一个编译器,这里如何添加name属性呢?

这个div就是editor.md的编译器的容器,在这个div里,搞一个隐藏的textarea标签.

textarea多行编辑框,把name属性加到这个textarea上。并且在初始化editormd对象的时候,加上一个对应的属性即可。

display:none让这个textarea隐藏起来。


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

saveHTMLToTextarea:true 会把用户在编译器中输入内容,自动也保存到textarea里一份。


📝编写服务器代码,处理刚才的请求

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 读取请求中的参数
        req.setCharacterEncoding("utf8");
        String title = req.getParameter("title");
        String content = req.getParameter("content");
        if (title == null || title.length() == 0 || content == null || content.length() == 0) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前传过来的标题或正文为空! 无法新增博客!");
            return;
        }
        // 2. 从会话中, 拿到当前登录的用户的 userId
        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;
        }
        // 3. 构造 blog 对象
        Blog blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        // 从会话中拿到 当前正在登录的用户的 userId, 设置进去即可
        blog.setUserId(user.getUserId());
        // 4. 插入到数据库中
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);
        // 5. 返回一个 302 这样的重定向报文, 跳转到博客列表页.
        resp.sendRedirect("blog_list.html");
    }

输入标题,写内容,即可。显示出来。


我们不怕掉眼泪,但要值得。

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

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

相关文章

宝塔助手v1.4.1/手机操控云服务器的神器软件

宝塔助手是以宝塔Linux面板提供的API开发的一款可以随时随地管理服务器的APP。通过这款APP你可以随时随地的查看一台或多台服务器的运行情况&#xff0c;对服务器网站、FTP、数据库、文件进行管理。内置文件编辑器&#xff0c;可以对网站文件进行修改。 链接&#xff1a;https:…

三极管 导通条件

一、三极管理解 三极管是电子行业常用的元器件之一&#xff0c;他是一种电流型控制的器件&#xff0c;他有三种工作状态&#xff1a;截止区&#xff0c;放大区、饱和区。当三极管当做开关使用时&#xff0c;他工作在饱和区。下面简短讲解三极管作为开关使用的方法&#xff0c;只…

李飞飞团队关于2024年人工智能发展报告总结 (Artificial Intelligence Index Report)

目录 1 10大核心信息2 AI研究和发展2.1 核心要点2.2 核心对比信息2.3 模型是否会用尽数据2.4 基础模型发展2.5 训练模型成本 3 技术性能3.1 核心要点3.2 重要模型发布情况3.3 AI表现情况3.4 多学科、高难度评估集 (MMMU & GPQA & ARC)3.5 Agents3.6 RLHF & RLAIF3.…

花了3天编制了236份excel财务明细收支报表,自动公式,直接用

财务明细收支报表能够帮助管理者清晰地了解企业的财务状况&#xff0c;及时调整经营策略。财务收支报表也是评估企业偿债能力和盈利能力的重要依据。 一份标准的财务明细收支报表通常包括以下部分&#xff1a;标题、报表期间、收入明细、支出明细、净收入或净支出等。 在制作…

在cmd中,如何使用cd进入指定文件目录

在cmd中&#xff0c;如何使用cd进入指定文件目录 1.要进入的磁盘与当前磁盘一致 例如: cd C:\Program Files (x86)\Google\Chrome\Application 2.进入到其他磁盘&#xff0c; 例如 cd /d D:\JAVA\codes\01\1.4 或者下面的方式&#xff08;直接输入磁盘F&#xff1a;和文件名…

【UE5 C++】基础学习笔记——01 UObject的创建与使用

目录 步骤 一、创建UObject 二、创建基于UObject的蓝图类 三、在UObject中使用变量和函数 步骤 一、创建UObject 在内容浏览器中新建一个C类 父类选择“Object” 类的类型设置为公有&#xff0c;这里就命名为“MyObject”&#xff0c;点击“创建类”来创建头文件和源文…

【联合索引】最左匹配原则是什么?

什么是联合索引 联合索引&#xff08;Composite Index&#xff09;是一种索引类型&#xff0c;它由多个列组成。 MySQL的联合索引&#xff08;也称为复合索引&#xff09;是建立在多个字段上的索引。这种索引类型允许数据库在查询时同时考虑多个列的值&#xff0c;从而提高查询…

SpringCloud 集成 RocketMQ 及配置解析

文章目录 前言一、SpringCloud 集成 RocketMQ1. pom 依赖2. yml 配置3. 操作实体4. 生产消息4.1. 自动发送消息4.2. 手动发送消息 5. 消费消息 二、配置解析1. spring.cloud.stream.function.definition 前言 定义 Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力…

中国海洋大学整体来说值得报考吗?中国海洋大学考情分析。

中国海洋大学&#xff08;Ocean University of China&#xff09;&#xff0c;简称中国海大。位于中国青岛&#xff0c;是中华人民共和国教育部直属的综合性重点大学&#xff0c;国家“双一流”、“985工程”和“211工程”重点建设高校之一&#xff0c;入选“2011计划”、“111…

迄今为止最全- 前端性能优化

简介 当我们说前端性能优化的时候&#xff0c;指的可能是不同场景的性能优化。前端涉及性能优化的场景主要有&#xff1a; 项目构建性能优化 页面性能优化 加载时性能优化 运行时性能优化 构建性能主要指构建速度&#xff0c;优化方法和打包工具直接相关&#xff0c;主要…

学习软考----数据库系统工程师32

NoSQL非关系型数据库 CAP理论和BASE特性 关系型数据库主要使用ACID理论 各种NoSQL数据 库的分类与特点

实操Linux磁盘管理(分区、格式化、挂载)

在Linux系统中&#xff0c;磁盘管理是一个必学的知识点。正确地进行磁盘分区、格式化和挂载可以确保我们能够充分利用磁盘空间并高效地存储和访问数据。 相比于Windows系统中的简单盘符管理&#xff0c;Linux中的磁盘管理更加复杂且灵活。在Linux系统中&#xff0c;一切设备都…

企业使用合同档案管理系统软件有什么好处

使用合同档案管理系统软件可以带来以下好处&#xff1a; 1. 提高效率&#xff1a;合同管理软件可以自动化合同流程&#xff0c;包括创建、审批、签署和归档等。通过自动化&#xff0c;可以节省大量时间和精力&#xff0c;提高工作效率。 2. 降低风险&#xff1a;玖拓档案合同管…

鸿蒙内核源码分析(内核态锁篇) | 如何实现快锁Futex(下)

本篇为快锁下篇&#xff0c;说清楚快锁在内核态的实现&#xff0c;解答以下问题&#xff0c;它们在上篇的末尾被提出来。 鸿蒙内核进程池默认上限是64个&#xff0c;除去两个内核进程外&#xff0c;剩下的都归属用户进程&#xff0c;理论上用户进程可以创建很多快锁&#xff0…

YOLOv8小白中的小白安装环境教程!没一个字废话,看一遍不踩坑!

文章目录 去哪里下代码&#xff1f;怎么下代码&#xff1f;怎么装环境&#xff1f;命令行界面(CLI)指令和Python脚本区别&#xff1f;附录1 conda常用指令附录2 git常用指令附录3 项目代码文件作用 去哪里下代码&#xff1f; 下载代码请大家直接去 YOLOv8的官方仓库下载&#…

【性能测试】基础知识篇-并发用户、RPS、TPS的解读

本文介绍并发用户、RPS、TPS的基本概念以及三者之间的关系。 术语定义 并发用户&#xff1a;在性能测试工具中&#xff0c;一般称为虚拟用户&#xff08;Virtual User&#xff0c;简称VU&#xff09;&#xff0c;指的是现实系统中操作业务的用户。 说明 并发用户与注册用户、在…

2025考研 | 北京师范大学计算机考研考情分析

北京师范大学&#xff08;Beijing Normal University&#xff09;简称“北师大”&#xff0c;由中华人民共和国教育部直属&#xff0c;中央直管副部级建制&#xff0c;位列“211工程”、“985工程”&#xff0c;入选国家“双一流”、“珠峰计划”、“2011计划”、“111计划”、…

C++入门-stack和queue(下)

大家好啊&#xff0c;在这先祝天下的母亲节日快乐啦&#xff01;现在呢&#xff0c;给大家带来C中priority_queue和容器适配器的相关知识点 3.1 C 中的优先队列&#xff08;priority_queue&#xff09;介绍 优先队列&#xff08;priority_queue&#xff09;是一种特殊的队列…

LeetCode343:整数拆分

题目描述 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 代码 动态规划 class Solution { public:int integerBreak(int n) {/*dp[i]&#xff1a;表示对…

CANopen总线_CANOpen开源协议栈

CANopen是自动化中使用的嵌入式系统的通信协议栈和设备配置文件规范。就OSI 模型而言&#xff0c;CANopen 实现了以上各层&#xff0c;包括网络层。 CANopen 标准由一个寻址方案、几个小型通信协议和一个由设备配置文件定义的应用层组成。通信协议支持网络管理、设备监控和节点…