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

news2024/11/25 2:27:46

目录

🚩部署页面需求

🚩准备工作

🚩获取博客列表页

🚩博客详情页

🚩实现登录页面

🎈强制要求登录

🎈显示用户信息

🚩退出登录

 🚩发布博客


🚩部署页面需求

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

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

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

这个部分是一个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");
    }

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


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

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

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

相关文章

5.nginx常用命令和日志定时切割

一. nginx常用的相关命令介绍 1.强制关闭nginx: ./nginx -s stop 2.优雅的关闭nginx: ./nginx -s quit 3.检查配置文件是否正确&#xff1a; ./nginx -t 4.查看nginx版本&#xff1a; ./nginx -v 5.查看nginx版本相关的配置环境信息&#xff1a;./nginx -V 6.nginx帮助信…

Lists.partition用法详解

文章目录 一、引入依赖二、用法三、输出 一、引入依赖 依赖于谷歌的guava 包 <!-- guava --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>28.1-jre</version></dependency&…

C++语法|volatile关键字!从CPU角度进行理解

个人认为C有着复杂、臃肿的语法系统&#xff0c;但是也正是因为这些特性&#xff0c;让我们在使用C时既能深入到操作系统级的控制&#xff0c;也能抽象出来完全专注于一些业务问题。 这里为大家推荐一本书和汇编代码阅读网站&#xff01; 《CPU眼里的C/C》 Compiler Explorer 我…

ssm+vue的公务用车管理智慧云服务监管平台查询统计(有报告)。Javaee项目,ssm vue前后端分离项目

演示视频&#xff1a; ssmvue的公务用车管理智慧云服务监管平台查询统计&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&…

GCP谷歌云有什么数据库类型,该怎么选择

GCP谷歌云提供的数据库类型主要包括&#xff1a; 关系型数据库&#xff1a;这类数据库适用于结构化数据&#xff0c;通常用于数据结构不经常发生变化的场合。在GCP中&#xff0c;关系型数据库选项包括Cloud SQL和Cloud Spanner。Cloud SQL提供托管的MySQL、PostgreSQL和SQL Se…

高效项目管理:如何利用zz-plan在线甘特图工具

作为项目管理人员&#xff0c;使用 zz-plan https://zz-plan.com/这样的在线甘特图协作软件可以极大地提高项目管理的效率和效果。以下是结合zz-plan特点的一些关键步骤&#xff1a; 1. 制定项目计划 在zz-plan上创建新的项目&#xff0c;定义项目目标、关键里程碑和最终期限。…

【数据可视化01】matplotlib实例介绍2

目录 一、引言二、实例介绍1.箱线图2.热力图3.线条形式 一、引言 接着上一文章【数据可视化01】matplotlib实例介绍1继续介绍matplotlib的实例。 二、实例介绍 在matplotlib中&#xff0c;常用的图形类型包括&#xff1a; 箱线图&#xff08;Box plot&#xff09;&#xff1…

d17(154-168)-勇敢开始Java,咖啡拯救人生

目录 方法递归 字符集 编码-解码 IO流 字节流 字节输入流 InputSream FileInputStream 字节输出流 OutputSream FileOutputSream 释放资源的方式 try-catch-finallly try-with-resource 字符流 字符输入流 Reader FileReader 文件字符输出流 Writer FileWriter …

(done) 什么是马尔可夫链?Markov Chain

参考视频&#xff1a;https://www.bilibili.com/video/BV1ko4y1P7Zv/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 如下图所示&#xff0c;马尔可夫链条实际上就是 “状态机”&#xff0c;只不过状态机里不同状态之间的边上是 “…

李廉洋:5.13黄金原油美盘行情分析,必看策略。

黄金消息面分析&#xff1a;机构最新调查中的一些受访者表示&#xff0c;美国最大的科技股不仅是对创新行业的押注&#xff0c;而且可能是对冲通胀的工具。46%的受访者表示&#xff0c;数十年来一直是避险之选的黄金仍被视为抵御价格上涨风险的最佳保障。但近三分之一的人表示&…

【eclipse】如何在IDE里创建一个Java Web项目?

如何在eclipse中创建一个动态Web项目并成功运行&#xff1f; 一、 最终效果 懒得写那么多了…我也不知道该怎么写了&#xff0c;有点乱&#xff0c;有问题可以在评论里留言&#xff0c;我看到会解决的&#xff0c;在这个过程中也踩到了一些坑&#xff0c;但好在有CSDN帮助解决…

GEE数据集——东南亚区域油棕种种植分布(油棕榈树种植园的概率)数据集

森林数据伙伴关系围绕对全球商品驱动的森林砍伐、森林退化和恢复工作的全球监测&#xff0c;加强合作与应用。 世界各国政府和公司都承诺帮助制止砍伐森林和加快恢复&#xff0c;以避免气候变化带来的最坏影响&#xff0c;防止生物多样性丧失&#xff0c;保护森林对人类和自然…

JavaEE之线程(4)——线程安全、线程安全的原因,synchronized关键字

前言 在本栏的前面的内容中&#xff0c;我们介绍了线程的创建、Thread 类及常见方法、线程的状态&#xff0c;今天我们来介绍一下关于线程的另一个重点知识——线程安全。 一、线程安全 基本概念&#xff1a; 线程安全的确切定义是复杂的&#xff0c;但我们可以这样认为&…

微前端无界方案

微前端无界 无界 官方文档 主应用 1、引入 // 无框架时使用wujie import Wujie from wujie // 当结合框架时使用wujie-xxx // import Wujie from "wujie-vue2"; // import Wujie from "wujie-vue3"; // import Wujie from "wujie-react";cons…

想搭建AI知识库的企业看这篇就够了

企业要想在激烈的竞争中脱颖而出&#xff0c;有一套高效、智能的知识管理系统是非常重要的。搭建AI知识库能够帮助企业整合、分类、检索和应用知识&#xff0c;因此成为众多企业的第一选择。对于想要搭建AI知识库的企业来说&#xff0c;应该注意哪些方面呢&#xff1f;本文将从…

专业网站设计方案

当前互联网的快速发展和普及&#xff0c;使得网站设计成为了一个极其重要的环节。一个好的网站设计方案将能够吸引更多的访问者&#xff0c;提高用户体验&#xff0c;增强品牌形象。下面将为您介绍一个专业的网站设计方案。 首先&#xff0c;一个专业的网站设计方案应该具备清晰…

APP反抓包 - 客户端证书验证进阶(代码混淆)

1.关于混淆 在安卓开发中,对于第三方的包是可以进行混淆的,例如:OKHttp3.Http.Cert.check 被混淆后可以是a.f.c.b 形式。在安卓开发中,系统包是无法混淆的,例如:java.security.KeyStore不会被混淆。由于这种的情况的存在,再次审示我们之前的通用脚本,就会发现他是不通用…

2000-2022年上市公司供应链效率数据(含原始数据+结果)

2000-2022年上市公司供应链效率数据&#xff08;含原始数据结果&#xff09; 1、时间&#xff1a;2000-2022年 2、指标&#xff1a;年份、股票代码、省份、城市、区县、省份代码、城市代码、区县代码、首次上市年份、上市状态、股票简称、行业名称、行业代码、库存周转率、供…

单页源码加密屋zip文件加密API源码

简介&#xff1a; 单页源码加密屋zip文件加密API源码 api源码里面的参数已改好&#xff0c;往服务器或主机一丢就行&#xff0c;出现不能加密了就是加密次数达到上限了&#xff0c;告诉我在到后台修改加密次数 点击下载

解决宝塔Nginx和phpMyAdmin配置端口冲突问题

问题描述 在对基于宝塔面板的 Nginx 配置文件进行端口修改时&#xff0c;我注意到 phpMyAdmin 的端口配置似乎也随之发生了变化&#xff01; 解决方法 官方建议在处理 Nginx 配置时&#xff0c;应避免直接修改默认的配置文件&#xff0c;以确保系统的稳定性和简化后续的维护…