【Java项目】完善基于Java+MySQL+Tomcat+maven+Servlet的博客系统

目录

  • 一、准备工作
  • 二、引入依赖
  • 三、创建必要的目录
  • 四、编写代码
  • 五/六、打包部署(直接基于 smart tomcat)
  • 七、验证代码
  • 正式编写服务器代码
    • 编写数据库相关的操作代码
    • 创建数据库/表结构(数据库设计)
      • 数据库代码
      • 封装数据库操作
      • 封装针对数据的增删改查!
    • 博客列表页
      • 约定前后端接口
      • 编写服务器代码
      • 编写客户端代码
    • 问题一:
    • 问题二:刷新页面的时候,发现一哆嗦~
    • 博客详情页
      • 约定前后端交互接口
      • 前端代码
    • 登录页
      • 约定前后端交互接口
      • 后端逻辑
      • 约定前后端交互接口
    • 正确显示用户信息
      • 针对博客列表页
      • 针对博客详情页
    • 实现"注销"功能
      • 约定前后端交互接口
    • 发布博客功能
      • 约定前后端交互接口
    • 删除博客
  • 完整代码

一、准备工作

打开你的idea,创建一个Maven。

  1. File->new->Project;
    在这里插入图片描述

  2. 选择创建maven项目,然后点击next
    在这里插入图片描述

  3. 取个名字
    在这里插入图片描述

二、引入依赖

这里我们需要引入的依赖是servlet,jackson,mysql。
这个操作嘛!直接看JavaEE-实现一个服务器版本的“表白墙”
里面有引入依赖的相关操作~

pox.mal

<?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>
    </properties>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.6.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
    </dependencies>
</project>

三、创建必要的目录

点开我们项目下的src文件,找到main,然后右键创建一个新的目录,webapp;在webapp下创建一个目录叫做WEB-INF,然后在WEB-INF下面创建一个新的xml文件叫做web.xml
在这里插入图片描述
在这里插入图片描述
在web.xml文件中填写以下代码

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
 
<web-app>
    <display-name>Archetype Created Web Application</display-name>
</web-app>

四、编写代码

找到java文件,在java文件下创建一个类叫做HelloServlet
在这里插入图片描述
写一个简单的实验代码

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

public class HelloServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("你好,博客系统!");
    }
}

五/六、打包部署(直接基于 smart tomcat)

这里打包部署在博主之前的博客已经介绍过,使用idea自带的插件smart tomcat来完成
在这里插入图片描述
在这里插入图片描述

七、验证代码

在浏览器验证程序。

正式编写服务器代码

在这里插入图片描述
找到我们以前的博客系统的前端代码,然后复制粘贴到当前的webapp目录下
在这里插入图片描述
在这里插入图片描述
就可以把前端页面给引入项目中。
在这里插入图片描述

C和M,先来实现Model层~~ 先来实现数据库相关的代码

编写数据库相关的操作代码

创建数据库/表结构(数据库设计)

设计数据库,需要根据当前的需求来进行设计~~

这里来介绍一下前面写的博客系统页面:
1.博客列表页:显示博客的列表;
2.博客详情页,点击博客列表页,上面列出的博客篇数,可以跳转到完整的博客内容;
3.登录页面;
4.博客编辑页,基于editor.md整了一个markdown编辑器,根据这个页面来发布博客

存储博客~当点击发布的时候,博客被发布到服务器上,就要被存起来
获取博客~当博客列表页和博客详情页,能够拿到博客的内容,还能够进行登录校验

设计表,就需要抓住需求中的实体(关键性的名词)

博客表,用来存储所有博客数据~
用户表,用户登录,就需要用到这个表~

数据库代码

-- 编写建库建表的 sql

create database if not exists java107_blog;

use java107_blog;

-- 创建一个博客表
drop table if exists blog;
create table blog (
    blogId int primary key auto_increment,
    title varchar(1024),
    content mediumtext,
    userId int,         -- 文章作者的 id
    postTime datetime   -- 发布时间
);

-- 创建一个用户表
drop table if exists user;
create table user (
    userId int primary key auto_increment,
    username varchar(128) unique,       -- 后续会使用用户名来登录,一般用于登录的用户名都是不能重复的
    password varchar(128)
);

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

编写完成之后,打开mysql客户端,然后复制上面的SQL语句,粘贴到我们的mysql客户端
在这里插入图片描述
检查数据库和表是否创建完成
在这里插入图片描述

封装数据库操作

先创建DBUtil封装数据库连接的操作

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

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

// 使用这个类和数据库建立连接
public class DBUtil {
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/java107_blog?characterEncoding=utf8&&useSSL=false";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "1234";

    private static volatile DataSource dataSource = null;

    private static DataSource getDataSource() {
        if (dataSource == null) {
            synchronized (DBUtil.class) {
                if (dataSource == null) {
                    dataSource = new MysqlDataSource();
                    ((MysqlDataSource) dataSource).setURL(URL);
                    ((MysqlDataSource) dataSource).setUser(USERNAME);
                    ((MysqlDataSource) dataSource).setPassword(PASSWORD);
                }
            }
        }
        return dataSource;
    }

    public static Connection getConnection() throws SQLException {
        return getDataSource().getConnection();
    }

    public 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类和User类
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;
    }
}

Blog类

import java.sql.Timestamp;

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

封装针对数据的增删改查!

把提供了增删改查这样的类,称为DAO
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;

// 用于去封装博客表的基本操作
public class BlogDao {
    // 1. 往博客表里,插入一个博客.
    public void insert(Blog blog) {
        // JDBC 基本代码~
        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(e);
        } finally {
            // 4) 关闭连接,释放资源
            DBUtil.close(connection, statement, null);
        }
    }

    // 2. 能够获取到博客表中的所有博客表的信息(用于博客列表页),此处每篇博客不一定获取到完整的正文
    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.getShort("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blogs.add(blog);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return blogs;
    }

    // 3. 能够根据博客 id 获取到指定的博客内容(用于博客详情页)
    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();
            // 此处我们是使用 主键 来作为查询条件的,查询结果,要么是1,要么是0
            if (resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setUserId(resultSet.getShort("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                return blog;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } 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 e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(connection, statement, null);
        }
    }
    // 注意,上述操作是增删查,没有改~

}

UserDao类

package Model;

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

// 提供了针对 用户表 的基本操作
public class UserDao {
    // 需要实现的操作
    // 针对这个类来说,就简化的写就行了,像注册/销号这样的功能就不考虑了。

    // 主要实现,
    // 1. 根据用户名来查找用户信息。
    // 会在登录逻辑中使用~
    public User selectByName(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();
            // 此处 username 使用了 unique 约束,要么能查到一个,要么一个都查不到
            if (resultSet.next()) {
                User user = new User();
                user.setUserId(resultSet.getInt("userId"));
                user.setUsername(resultSet.getString("username"));
                user.setPassword(resultSet.getString("password"));
                return user;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }

    // 2. 根据用户 id 来找用户信息
    //    博客详情页,就可以根据用户 id 来查询作者的名字,把作者名字显示出来。
    public User selectById(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();
            // 此处 username 使用了 unique 约束,要么能查到一个,要么一个都查不到
            if (resultSet.next()) {
                User user = new User();
                user.setUserId(resultSet.getInt("userId"));
                user.setUsername(resultSet.getString("username"));
                user.setPassword(resultSet.getString("password"));
                return user;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }
}


结过上述的逻辑,终于把数据库操作都准备好了~
接下来,就可以实现服务器中的后续代码了~
(Model就搞定了,要去写Controller了)



针对这里的这四个页面
分别来“约定前后端交互接口”,“编写服务器代码”,“编写客户端代码”

博客列表页

这个页面需要能够展示出数据库中的博客的列表

约定前后端接口

请求:
GET /blog

响应:
[
	{
		blogId: 1,
		title: ' 这是第一篇博客',
		content: '这是博客正文',
		 userId: 1,
		  postTime: '2023-03-18  09:00:00'
	},
	{
		blogId: 2,
		title: ' 这是第二篇博客',
		content: '这是博客正文',
		 userId: 1,
		  postTime: '2023-03-18  09:00:00'
	},
]

这里的content与其说是“正文”,不如说是正文的摘要。
博客列表里面,主要显示有哪些博客~
因此,就需要在这个地方把正文进行截取(如果正文太长,就只截取前面的一部分)

编写服务器代码

接下来进一步编写服务器和客户端之间的代码~
我们试着先来写一下,看一下是否能连接成功!

package controller;

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;

// 通过这个类,来处理 /blog 的对应请求
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    // 这个方法用来获取到数据库中的博客列表
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 从数据库中查询到博客列表,转成JSON格式 然后直接返回即可
        BlogDao blogDao = new BlogDao();
        List<Blog> blogs = blogDao.selectAll();
        // 把 blogs 对象转成JSON格式
        String respJson = objectMapper.writeValueAsString(blogs);
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(respJson);
    }
}

在这里插入图片描述
发现连接成功了~

在数据库中插入点信息

-- 给博客表中插入数据,方便测试。
insert into blog values(null, '这是第一篇博客', '从今天开始,我要认真学 Java', 1, now());
insert into blog values(null, '这是第二篇博客', '从昨天开始,我要认真学 Java', 1, now());
insert into blog values(null, '这是第三篇博客', '从前天开始,我要认真学 Java', 1, now());
insert into blog values(null, '这是第一篇博客', '从今天开始,我要认真学 C++', 2, now());

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
此处得到的响应,和预期的效果,有一点点差别,时间格式~
预期得到的是一个格式化时间,实际上得到一个毫秒时间戳
此处就需要把时间戳转成格式化时间(可以在前端来做,也可以在后端来做)
这里在后端来改!
在Blog类中:

public String getPostTime() {
        // 使用 SimpleDateFormat 来完成时间戳到格式化日期的转换
        // 这个转换过程,需要在构造方法中指定要转换格式,然后调用format来进行转换
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return simpleDateFormat.format(postTime);
    }

在这里插入图片描述
这里就看到我们的格式改变了~

编写客户端代码

在页面加载的时候,让页面通过ajax访问服务器,获取到数据库中的博客数据,并且填写到页面中!

处理服务器响应的逻辑:

<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script>
        // 在页面加载的时候,通过 ajax 给服务器发送数据,获取到博客列表信息,并且显示在页面上
        function getBlogList() {
            $.ajax({
                type: 'get',
                url: 'blog',
                success: function(body) {
                    // 获取到的 body 就是一个 js 对象数组,每个元素就是一个 js 对象,根据这个对象来构造一个div
                    // 1. 先把 .right 里原有的内容清空
                    let rightDiv = document.querySelector('.right');
                    rightDiv.innerHTML = '';
                    // 2. 遍历 body,构造出一个个的 blogDiv
                    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;
                        blogDiv.appendChild(titleDiv);
                        // 构造发布时间
                        let dateDiv = document.createElement('div');
                        dateDiv.className = 'date';
                        dateDiv.innerHTML = blog.postTime;
                        blogDiv.appendChild(dateDiv);
                        // 构造博客的摘要
                        let descDiv = document.createElement('div');
                        descDiv.className = 'desc';
                        descDiv.innerHTML = blog.content;
                        blogDiv.appendChild(descDiv);
                        // 构造查看全文
                        let a = document.createElement('a');
                        a.innerHTML = '查看全文 &gt;&gt;';
                        // 此处希望点击之后能够跳转博客详情页
                        // 这个跳转过程需要告知服务器要访问哪个博客的详情页
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                         blogDiv.appendChild(a);
                    
                        // 把 blodDiv 挂到 dom树上
                        rightDiv.appendChild(blogDiv);
                    }
                },
                error: function() {
                    alert("获取博客列表失败!")
                }
            });
        }
        
        getBlogList();
    </script>

完整代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客列表页</title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/blog_list.css">
</head>
<body>
    <!-- 这是导航栏 -->
    <div class="nav">
        <img src="image/log.jpg" alt="">
        <span>我的博客系统</span>
        <!-- 空白元素,用来占位置 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="#">注销</a>
    </div>

    <div class="container">
        <!-- 左侧个人信息 -->
        <div class="left">
            <!-- 表示整个用户信息区 -->
            <div class="card">
                <img src="image/2.jpg" alt="">
                <h3>摸鱼王胖嘟嘟</h3>
                <a href="#">gitee 地址</a>
                <div class="counter">
                    <span>文章</span>
                    <span>分类</span>
                </div>
                <div class="counter">
                    <span>2</span>
                    <span>1</span>
                </div>
            </div>
        </div>
        <!-- 右侧个人信息 -->
        <div class="right">
            <!-- .blog就对应一个博客 -->
            <div class="blog">
                <!-- 博客标题 -->
                <div class="title">
                    我的第一篇博客
                </div>
                <!-- 博客发布时间 -->
                <div class="date">
                    2023-02-11 18:26:00
                </div>
                <!-- 博客摘要 -->
                <div class="desc">
                    从今天起,我要认真敲代码. Lorem, ipsum dolor sit amet consectetur adipisicing elit. Neque exercitationem, ut doloribus blanditiis, eveniet earum culpa accusamus rem eum cum deleniti, quisquam expedita distinctio tempora quaerat adipisci officia esse reprehenderit.
                </div>
                <a href="#">查看全文 &gt;</a>
            </div>
            <!-- .blog就对应一个博客 -->
            <div class="blog">
                <!-- 博客标题 -->
                <div class="title">
                    我的第一篇博客
                </div>
                <!-- 博客发布时间 -->
                <div class="date">
                    2023-02-11 18:26:00
                </div>
                <!-- 博客摘要 -->
                <div class="desc">
                    从今天起,我要认真敲代码. Lorem, ipsum dolor sit amet consectetur adipisicing elit. Neque exercitationem, ut doloribus blanditiis, eveniet earum culpa accusamus rem eum cum deleniti, quisquam expedita distinctio tempora quaerat adipisci officia esse reprehenderit.
                </div>
                <a href="#">查看全文 &gt;></a>
        </div>
    </div>

    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script>
        // 在页面加载的时候,通过 ajax 给服务器发送数据,获取到博客列表信息,并且显示在页面上
        function getBlogList() {
            $.ajax({
                type: 'get',
                url: 'blog',
                success: function(body) {
                    // 获取到的 body 就是一个 js 对象数组,每个元素就是一个 js 对象,根据这个对象来构造一个div
                    // 1. 先把 .right 里原有的内容清空
                    let rightDiv = document.querySelector('.right');
                    rightDiv.innerHTML = '';
                    // 2. 遍历 body,构造出一个个的 blogDiv
                    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;
                        blogDiv.appendChild(titleDiv);
                        // 构造发布时间
                        let dateDiv = document.createElement('div');
                        dateDiv.className = 'date';
                        dateDiv.innerHTML = blog.postTime;
                        blogDiv.appendChild(dateDiv);
                        // 构造博客的摘要
                        let descDiv = document.createElement('div');
                        descDiv.className = 'desc';
                        descDiv.innerHTML = blog.content;
                        blogDiv.appendChild(descDiv);
                        // 构造查看全文
                        let a = document.createElement('a');
                        a.innerHTML = '查看全文 &gt;&gt;';
                        // 此处希望点击之后能够跳转博客详情页
                        // 这个跳转过程需要告知服务器要访问哪个博客的详情页
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                        blogDiv.appendChild(a);
                    
                        // 把 blodDiv 挂到 dom树上
                        rightDiv.appendChild(blogDiv);
                    }
                },
                error: function() {
                    alert("获取博客列表失败!")
                }
            });
        }
        
        getBlogList();
    </script>
</body>
</html>

BlogDao.java逻辑的处理

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;

// 用于去封装博客表的基本操作
public class BlogDao {
    // 1. 往博客表里,插入一个博客.
    public void insert(Blog blog) {
        // JDBC 基本代码~
        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(e);
        } finally {
            // 4) 关闭连接,释放资源
            DBUtil.close(connection, statement, null);
        }
    }

    // 2. 能够获取到博客表中的所有博客表的信息(用于博客列表页),此处每篇博客不一定获取到完整的正文
    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.getShort("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blogs.add(blog);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return blogs;
    }

    // 3. 能够根据博客 id 获取到指定的博客内容(用于博客详情页)
    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();
            // 此处我们是使用 主键 来作为查询条件的,查询结果,要么是1,要么是0
            if (resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                // 这里需要针对内容进行截断(太长了,就去掉后面)
                String content = resultSet.getString("content");
                if (content.length() > 50) {
                    content = content.substring(0, 50) + "...";
                }
                blog.setContent(content);
                blog.setUserId(resultSet.getShort("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                return blog;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } 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 e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(connection, statement, null);
        }
    }
    // 注意,上述操作是增删查,没有改~
}


问题一:

当前这个博客列表页,还有点小问题~
当前拿到的博客列表顺序,是不太科学的

在这里插入图片描述
需要保证我们最新的博客得在最上面才行~

如果在sql中没有指定 order by ~ 此时查询到的结果是不确定的~

因此,在sql中不加 order by 之前,是不应该依赖查询结果的顺序的~

在BlogDao.java中找到此处
在这里插入图片描述
可以看到是降序排序的了

在这里插入图片描述

问题二:刷新页面的时候,发现一哆嗦~

旧的内容(测试时写死的数据) => 新的内容(从数据库里查的数据)
是通过网络交互的,花个几十 ms 都是很正常的,人眼是能捕捉到这个变化的~

直接把旧的测试数据删了就行了~
在这里插入图片描述
像这样~~

在这里插入图片描述

博客详情页

约定前后端交互接口

请求:
GET /blog?blogId= 1
响应:
HTTP/1.1 200 OK
Content-Type: application/json;

{
	blogId: 1,
	title: “第一篇博客”,
	content: "这是正文",
	userId: 1,
	postTime: '2023-03-18 15:58:00'
}

和获取博客列表的区别~
1.请求里面,带有参数,blogId
2.响应结果不是json数组了,而只是单一的对象
3.响应的博客正文,不再截断了。

获取博客列表的请求
GET/ blog
不带参数的~

package controller;

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;

// 通过这个类,来处理 /blog 的对应请求
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();

    // 这个方法用来获取到数据库中的博客列表
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf8");
        BlogDao blogDao = new BlogDao();
        // 先尝试获取到 req 中的 blogId参数,如果该参数存在,说明要请求博客详情
        // 如果不存在,就说明要请求博客列表
        String pararm = req.getParameter("blogId");
        if (pararm == null) {
            // 不存在参数,获取博客列表
            // 从数据库中查询到博客列表,转成JSON格式 然后直接返回即可
            List<Blog> blogs = blogDao.selectAll();
            // 把 blogs 对象转成JSON格式
            String respJson = objectMapper.writeValueAsString(blogs);

            resp.getWriter().write(respJson);
        } else {
            // 存在参数,获取博客详情
            int blogId = Integer.parseInt(pararm);
            Blog blog = blogDao.selectOne(blogId);
            String respJson = objectMapper.writeValueAsString(blog);
            resp.getWriter().write(respJson);
        }
    }
}

在这里插入图片描述
后端代码实现和博客列表页的获取,基本相同,就直接放到一个方法中来实现了,使用blogId参数来区分是获取博客列表还是详情~

前端代码

修改blog_detail.html,让这个页面加载的时候,能够调用上述接口,来从服务器获取到博客数据!
在前端代码这边,要想构造一个请求获取博客详情,就得知道当前用户点击的博客的id!
这个id就已经包含在 当前的 blog_detail.html页面的url里面了~
在这里插入图片描述

希望在博客详情页拿到博客的具体内容。
就需要构造一个请求 /blog?blogId=5
这个请求是 blog_detail.html通过ajax发送的
blog_detail.html就需要构造出blogId(5)的这个参数
关键问题就是这个参数(5)从哪里来的呢?
其实当来到blog_detail.html这个页面的时候,url里就已经带上了这个参数~

通过location.search就能够拿到?blogId=5这段内容,从而构造出/blog?blogId=5这样的请求。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
既然如此,那应该如何处理?能够让我们这里的markdown文本内容,被渲染成带有特定样式的html片段呢?
仍然要使用edlitor.md这个库来实现。
这个库不仅提供了markdown编辑器,也提供了渲染功能~

当我们在进行程序验证的时候,要时刻牢记,浏览器缓存可能会影响到结果.
之前已经访问过blog_detail页面了,因此浏览器就可能把这个页面给保存到本地.
下次再尝试访问的时候就直接访问本地内容了。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客详情页</title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/blog_detail.css">

    <!-- 引入 editor.md 的依赖 -->
    <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/log.jpg" alt="">
        <span>我的博客系统</span>
        <!-- 空白元素,用来占位置 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="#">注销</a>
    </div>
    <div class="container">
        <!-- 左侧个人信息 -->
        <div class="left">
            <!-- 表示整个用户信息区 -->
            <div class="card">
                <img src="image/2.jpg" alt="">
                <h3>摸鱼王胖嘟嘟</h3>
                <a href="#">gitee 地址</a>
                <div class="counter">
                    <span>文章</span>
                    <span>分类</span>
                </div>
                <div class="counter">
                    <span>2</span>
                    <span>1</span>
                </div>
            </div>
        </div>
        <!-- 右侧个人信息 -->
        <div class="right">
            <!-- 使用这个 div 来包裹整个博客内容详情 -->
            <div class="blog-content">
                <!-- 博客标题 -->
                <h3></h3>
                <!-- 博客的时间 -->
                <div class="date"></div>
                <!-- 博客的正文内容 -->
                <div id="content" style="opacity: 80%">

                </div>
            </div>
        </div>

        <script>
            function getBlogDetail() {
                $.ajax({
                    type: 'get',
                    // location.search 拿到了形如 '?blogId=5' 这样的一段内容
                    url: 'blog'+ location.search,
                    success: function(body) {
                        // 根据 body 中的内容来构造页面
                        // 1. 构造博客标题
                        let h3 = document.querySelector(".blog-content>h3");
                        h3.innerHTML = body.title;
                        // 2. 构造博客发布时间
                        let dateDiv = document.querySelector('.date');
                        dateDiv.innerHTML = body.postTime;
                        // 3. 构造博客正文
                        // 如果直接把content设为 innerHtML,此时展示在界面上的内容是原始的markdown字符串
                        // 咱们需要的是渲染后的,带有格式的效果
                        // let content = document.querySelector('#content');
                        // content.innerHTML = body.content;

                        // 第一个参数对应 id=content 的html标签,渲染后得到的HTML片段就会放到这个标签下
                        editormd.markdownToHTML('content', {
                            markdown: body.content
                        });
                    }
                });
            }
            getBlogDetail();
        </script>
    </div>
</body>
</html>

当前已经把博客列表页和详情页搞出来了
接下来来写登录页

登录页

约定前后端交互接口

请求:
POST /login
Content-Type: application/x-ww-form-urlencoded
这里的逻辑,可以直接使用form表单来进行提交,没必要非要使用ajax~
username=zhangsan&password=123
响应:
HTTP/1.1 302
Location:blog.list.html
当登录成功之后,就自动跳转到,主页(博客列表页)

在这里插入图片描述
1)给这部分代码套上一层form标签!
给input加上name属性
2)username=zhangsan&password=123
3)把button按钮换成input标签

在前端页面开发的过程中,html页面结构,是非常重要的,后续的CSS和JS很多都依赖了这个页面的结构
一旦页面结构发生了调整,就可能导致CSS或者js失效

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录页面</title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/blog_login.css">
</head>
<body>
    <!-- 这是导航栏 -->
    <div class="nav">
        <img src="image/log.jpg" alt="">
        <span>我的博客系统</span>
        <!-- 空白元素,用来占位置 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <!-- 注销页面没必要在登陆页面展示 -->
        <!-- <a href="#">注销</a> -->
    </div>
    <div class="login-container">
        <form action="login" method="post">
            <div class="login-dialog">
                <h3>登录</h3>
                <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">
                    <!-- <button>提交</button> -->
                    <input type="submit" id="submit" value="提交">
                </div>
            </div>
        </form>
    </div>
</body>
</html>
/* 登录页面的专用样式文件 */

.login-container {
    width: 100%;
    height: calc(100% - 50px);

    /* 需要让里面的子元素垂直水平居中 */
    display: flex;
    align-items: center;
    justify-content: center;
}

.login-dialog {
    width: 400px;
    height: 350px;

    background-color: rgba(255, 255, 255, 0.8);
    border-radius: 10px;
}

.login-dialog h3 {
    text-align: center;
    padding: 50px 0;
}

.login-dialog .row {
    height: 50px;
    width: 100%;

    display: flex;
    align-items: center;
    justify-content: center;
}

.login-dialog .row span {
    /* 把span转换成块级元素,方便设置尺寸 */
    display: block;
    width: 100px;
    font-weight: 700;
}

#username, #password {
    width: 200px;
    height: 40px;
    font-size: 22px;
    line-height: 40px;
    padding-left: 10px;
    border-radius: 10px;
    /* 去掉边框 */
    border: none;
    /* 去掉轮廓线 */
    outline: none;
}

.row #submit {
    width: 300px;
    height: 50px;
    border-radius: 10px;
    color: white;
    background-color: rgb(0,128,0);
    border: none;
    outline: none;

    margin-top: 50px;
}

.row #submit:active {
    background-color: #666;
}       

后端逻辑

此处约定的路径是/login,这是一个新的路径,就需要使用一个新的servlet来处理
在这里插入图片描述
使用getPararmeter读取参数的时候,如果参数的值是纯英文,那还好,一般没啥问题。
如果参数的值是中文,此时直接读取参数,很容易出现乱码

中文就涉及到乱码。
在这里插入图片描述
但是Servlet默认并不是按照utf8来解析的~

package controller;


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;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        resp.setCharacterEncoding("utf8");
        // 1. 获取到请求中的参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("username=" + username + ", password=" + password);
        if (username == null || "".equals(username) || password == null || "".equals(password)) {
            // 请求的内容缺失,肯定是登录失败~
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前的用户名或密码为空!");
            return;
        }
        // 2. 和数据库中的内容进行比较
        UserDao userDao = new UserDao();
        User user = userDao.selectByName(username);
        if (user == null || !user.getPassword().equals(password)) {
            // 用户没有查到或者密码不匹配,也是登录失败
            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");
    }
}

当登录功能完成之后,就需要调整一下之前的两个页面(博客列表和博客详情)
让这两个页面,必须登录后才能访问~~
此处就做出这样的限制~(这样限制一下,后面实现一些其他功能会更简单)
在进入博客列表页/详情页的时候,先检查一下用户的登录状态~如果用户当前已经是登录状态,才能继续使用,如果是未登录状态,则强制跳转到login页面

如何实现上述功能?
可以在博客列表页/详情页,加载的时候,通过ajax访问一下服务器,获取当前的登录状态~看能不能获取到,如果获取到了,就说明当前确实是已经登录了。此时就可以留在这个页面了。
如果没有获取到,说明就未登录。就需要跳转到登录页面~

约定前后端交互接口

    请求:
	GET /login
	响应:
	HTTP/1.1 200 OK
	Content-Type:application/json
	{
		userId: 1,
		username: 'zhangsan',
	}

登录了,就直接返回当前登录的用户信息.
未登录,则直接返回一个userId为0的对象

{
			userId: 0,
			username: '',
		}

此处只是一种典型的约定方式,完全也可以采用其他的方式来约定~
比如使用403表示当前未登录~

package controller;


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

@WebServlet("/login")
public class LoginServlet extends HttpServlet {

    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        resp.setCharacterEncoding("utf8");
        // 1. 获取到请求中的参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("username=" + username + ", password=" + password);
        if (username == null || "".equals(username) || password == null || "".equals(password)) {
            // 请求的内容缺失,肯定是登录失败~
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前的用户名或密码为空!");
            return;
        }
        // 2. 和数据库中的内容进行比较
        UserDao userDao = new UserDao();
        User user = userDao.selectByName(username);
        if (user == null || !user.getPassword().equals(password)) {
            // 用户没有查到或者密码不匹配,也是登录失败
            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");
    }

    // 这个方法用来让前端检测当前的登录状态
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf8");
        HttpSession session = req.getSession(false);
        if (session == null) {
            // 检测下会话是否存在,不存在说明未登录!
            User user = new User();
            resp.getWriter().write(objectMapper.writeValueAsString(user));
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            // 虽然有会话,但是会话里没有 user 对象,也视为未登录
            user = new User();
            resp.getWriter().write(objectMapper.writeValueAsString(user));
            return;
        }
        // 已经登录的状态!
        // 注意,此处不要把密码返回到前端
        user.setPassword("");
        resp.getWriter().write(objectMapper.writeValueAsString(user));
    }
}

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客列表页</title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/blog_list.css">
</head>
<body>
    <!-- 这是导航栏 -->
    <div class="nav">
        <img src="image/log.jpg" alt="">
        <span>我的博客系统</span>
        <!-- 空白元素,用来占位置 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="#">注销</a>
    </div>

    <div class="container">
        <!-- 左侧个人信息 -->
        <div class="left">
            <!-- 表示整个用户信息区 -->
            <div class="card">
                <img src="image/2.jpg" alt="">
                <h3>摸鱼王胖嘟嘟</h3>
                <a href="#">gitee 地址</a>
                <div class="counter">
                    <span>文章</span>
                    <span>分类</span>
                </div>
                <div class="counter">
                    <span>2</span>
                    <span>1</span>
                </div>
            </div>
        </div>
        <!-- 右侧个人信息 -->
        <div class="right">
            <!-- .blog就对应一个博客 -->
            <!-- <div class="blog"> -->
                <!-- 博客标题 -->
                <!-- <div class="title">
                    我的第一篇博客
                </div> -->
                <!-- 博客发布时间 -->
                <!-- <div class="date">
                    2023-02-11 18:26:00
                </div> -->
                <!-- 博客摘要 -->
                <!-- <div class="desc">
                    从今天起,我要认真敲代码. Lorem, ipsum dolor sit amet consectetur adipisicing elit. Neque exercitationem, ut doloribus blanditiis, eveniet earum culpa accusamus rem eum cum deleniti, quisquam expedita distinctio tempora quaerat adipisci officia esse reprehenderit.
                </div> -->
                <!-- <a href="#">查看全文 &gt;</a> -->
            <!-- </div> -->
            <!-- .blog就对应一个博客 -->
        </div>
    </div>

    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script>
        // 在页面加载的时候,通过 ajax 给服务器发送数据,获取到博客列表信息,并且显示在页面上
        function getBlogList() {
            $.ajax({
                type: 'get',
                url: 'blog',
                success: function(body) {
                    // 获取到的 body 就是一个 js 对象数组,每个元素就是一个 js 对象,根据这个对象来构造一个div
                    // 1. 先把 .right 里原有的内容清空
                    let rightDiv = document.querySelector('.right');
                    rightDiv.innerHTML = '';
                    // 2. 遍历 body,构造出一个个的 blogDiv
                    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;
                        blogDiv.appendChild(titleDiv);
                        // 构造发布时间
                        let dateDiv = document.createElement('div');
                        dateDiv.className = 'date';
                        dateDiv.innerHTML = blog.postTime;
                        blogDiv.appendChild(dateDiv);
                        // 构造博客的摘要
                        let descDiv = document.createElement('div');
                        descDiv.className = 'desc';
                        descDiv.innerHTML = blog.content;
                        blogDiv.appendChild(descDiv);
                        // 构造查看全文
                        let a = document.createElement('a');
                        a.innerHTML = '查看全文 &gt;&gt;';
                        // 此处希望点击之后能够跳转博客详情页
                        // 这个跳转过程需要告知服务器要访问哪个博客的详情页
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                        blogDiv.appendChild(a);
                    
                        // 把 blodDiv 挂到 dom树上
                        rightDiv.appendChild(blogDiv);
                    }
                },
                error: function() {
                    alert("获取博客列表失败!")
                }
        
            });
        }
        getBlogList();

        // 加上一个逻辑,通过 GET /login 这个接口来获取下当前的登录状态~
        function getUserInfo() {
            $.ajax({
                type: 'get',
                url: 'login',
                success: function(body) {
                    // 判定此处的body是不是一个有效的user对象
                    if (body.userId && body.userId > 0) {
                        // 登录成功!
                        // 不做处理
                        console.log("当前用户登录成功!用户名:" + body.username);
                    } else {
                        // 登录失败!
                        // 让前端页面,跳转到 login.hmtl
                        alert("当前您尚未登录!请登录后再访问博客列表!");
                        location.assign('blog_login.html');
                    }
                },
                error: function() {
                    location.assign('blog_login.html');
                }
            });
        }
        getUserInfo();
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客详情页</title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/blog_detail.css">

    <!-- 引入 editor.md 的依赖 -->
    <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/log.jpg" alt="">
        <span>我的博客系统</span>
        <!-- 空白元素,用来占位置 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="#">注销</a>
    </div>
    <div class="container">
        <!-- 左侧个人信息 -->
        <div class="left">
            <!-- 表示整个用户信息区 -->
            <div class="card">
                <img src="image/2.jpg" alt="">
                <h3>摸鱼王胖嘟嘟</h3>
                <a href="#">gitee 地址</a>
                <div class="counter">
                    <span>文章</span>
                    <span>分类</span>
                </div>
                <div class="counter">
                    <span>2</span>
                    <span>1</span>
                </div>
            </div>
        </div>
        <!-- 右侧个人信息 -->
        <div class="right">
            <!-- 使用这个 div 来包裹整个博客内容详情 -->
            <div class="blog-content">
                <!-- 博客标题 -->
                <h3></h3>
                <!-- 博客的时间 -->
                <div class="date"></div>
                <!-- 博客的正文内容 -->
                <div id="content" style="opacity: 80%">

                </div>
            </div>
        </div>

        <script>
            function getBlogDetail() {
                $.ajax({
                    type: 'get',
                    // location.search 拿到了形如 '?blogId=5' 这样的一段内容
                    url: 'blog'+ location.search,
                    success: function(body) {
                        // 根据 body 中的内容来构造页面
                        // 1. 构造博客标题
                        let h3 = document.querySelector(".blog-content>h3");
                        h3.innerHTML = body.title;
                        // 2. 构造博客发布时间
                        let dateDiv = document.querySelector('.date');
                        dateDiv.innerHTML = body.postTime;
                        // 3. 构造博客正文
                        // 如果直接把content设为 innerHtML,此时展示在界面上的内容是原始的markdown字符串
                        // 咱们需要的是渲染后的,带有格式的效果
                        // let content = document.querySelector('#content');
                        // content.innerHTML = body.content;

                        // 第一个参数对应 id=content 的html标签,渲染后得到的HTML片段就会放到这个标签下
                        editormd.markdownToHTML('content', {
                            markdown: body.content
                        });
                    }
                });
            }
            getBlogDetail();
            // 加上一个逻辑,通过 GET /login 这个接口来获取下当前的登录状态~
            function getUserInfo() {
                $.ajax({
                    type: 'get',
                    url: 'login',
                    success: function(body) {
                        // 判定此处的body是不是一个有效的user对象
                        if (body.userId && body.userId > 0) {
                            // 登录成功!
                            // 不做处理
                            console.log("当前用户登录成功!用户名:" + body.username);
                        } else {
                            // 登录失败!
                            // 让前端页面,跳转到 login.hmtl
                            alert("当前您尚未登录!请登录后再访问博客详情!");
                            location.assign('blog_login.html');
                        }
                    },
                    error: function() {
                        location.assign('blog_login.html');
                    }
                });
            }
            getUserInfo();
        </script>
    </div>
</body>
</html>

正确显示用户信息

在这里插入图片描述
当下用户信息,显示的是”摸鱼王胖嘟嘟“,实际登录的用户,显示”zhangsan“。

针对博客列表页

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客列表</title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/blog_list.css">
</head>
<body>
    <!-- 这是导航栏 -->
    <div class="nav">
        <img src="image/log.jpg" alt="">
        <span>我的博客系统</span>
        <!-- 空白元素, 用来占位置 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="#">注销</a>
    </div>
    <!-- 这里的 .container 作为页面的版心 -->
    <div class="container">
        <!-- 左侧个人信息 -->
        <div class="left">
            <!-- 表示整个用户信息区域. -->
            <div class="card">
                <img src="image/2.jpg" alt="">
                <h3></h3>
                <a href="#">github 地址</a>
                <div class="counter">
                    <span>文章</span>
                    <span>分类</span>
                </div>
                <div class="counter">
                    <span>2</span>
                    <span>1</span>
                </div>
            </div>
        </div>
        <!-- 右侧内容详情 -->
        <div class="right">
            <!-- .blog 就对应一个博客 -->
            <!-- <div class="blog">
                <div class="title">
                    我的第一篇博客
                </div>
                <div class="date">
                    2022-05-05 20:52:00
                </div>
                <div class="desc">
                    从今天起, 我要认真敲代码. Lorem ipsum dolor sit amet consectetur adipisicing elit. Nulla alias tenetur ut velit ex voluptatibus consequatur quam exercitationem, assumenda ea blanditiis repudiandae? Repellendus tenetur nostrum asperiores molestias doloremque cupiditate maiores.
                </div>
                <a href="#">查看全文 &gt;&gt; </a>
            </div> -->
        </div>
        </div>
    </div>

    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script>
        // 在页面加载的时候, 通过 ajax 给服务器发送数据, 获取到博客列表信息, 并且显示在界面上. 
        function getBlogList() {
            $.ajax({
                type: 'get',
                url: 'blog',
                success: function(body) {
                    // 获取到的 body 就是一个 js 对象数组, 每个元素就是一个 js 对象, 根据这个对象构造 div
                    // 1. 先把 .right 里原有的内容给清空
                    let rightDiv = document.querySelector('.right');
                    rightDiv.innerHTML = '';
                    // 2. 遍历 body, 构造出一个个的 blogDiv
                    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;
                        blogDiv.appendChild(titleDiv);
                        // 构造发布时间
                        let dateDiv = document.createElement('div');
                        dateDiv.className = 'date';
                        dateDiv.innerHTML = blog.postTime;
                        blogDiv.appendChild(dateDiv);
                        // 构造博客的摘要
                        let descDiv = document.createElement('div');
                        descDiv.className = 'desc';
                        descDiv.innerHTML = blog.content;
                        blogDiv.appendChild(descDiv);
                        // 构造 查看全文
                        let a = document.createElement('a');
                        a.innerHTML = '查看全文 &gt;&gt;';
                        // 此处希望点击之后能够跳转到 博客详情页 !!
                        // 这个跳转过程需要告知服务器要访问的是哪个博客的详情页. 
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                        blogDiv.appendChild(a);

                        // 把 blogDiv 挂到 dom 树上!
                        rightDiv.appendChild(blogDiv);
                    }
                }, 
                error: function() {
                    alert("获取博客列表失败!");
                }
            });
        }

        getBlogList();
    </script>
    <!-- 在这里引入上述的 js 文件, 就可以执行到里面的代码, 也就进行了登录状态的监测了 -->
    <script src="js/common.js"></script>
    <script>
        // 针对博客列表页, 调用的时候传入参数
        getUserInfo('blog_list.html');
    </script>
</body>
</html>

针对博客详情页

当前看到的是,博客详情页,用户名,也是成了zhangsan了,此处就需要处理一下,让博客列表页和详情页,能够做出一些区分~

让服务器,提供一个新的接口~这个接口可以让客户端指定blogId,获取到指定blogId的作者信息!

请求:
GET/authorInfo?blogId=6

响应:
{
	userId:2,
	username: 'lisi',
}

在这里插入图片描述
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客详情页</title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/blog_detail.css">

    <!-- 引入 editor.md 的依赖 -->
    <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/log.jpg" alt="">
        <span>我的博客系统</span>
        <!-- 空白元素, 用来占位置 -->
        <div class="spacer"></div>
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="#">注销</a>
    </div>
    <!-- 这里的 .container 作为页面的版心 -->
    <div class="container">
        <!-- 左侧个人信息 -->
        <div class="left">
            <!-- 表示整个用户信息区域. -->
            <div class="card">
                <img src="image/2.jpg" alt="">
                <h3></h3>
                <a href="#">github 地址</a>
                <div class="counter">
                    <span>文章</span>
                    <span>分类</span>
                </div>
                <div class="counter">
                    <span>2</span>
                    <span>1</span>
                </div>
            </div>
        </div>
        <!-- 右侧内容详情 -->
        <div class="right">
            <!-- 使用这个 div 来包裹整个博客的内容详情 -->
            <div class="blog-content">
                <!-- 博客标题 -->
                <h3></h3>
                <!-- 博客的时间 -->
                <div class="date"></div>
                <!-- 博客的正文内容 -->
                <div id="content" style="opacity: 80%">

                </div>
            </div>
        </div>
    </div>

    <script>
        function getBlogDetail() {
            $.ajax({
                type: 'get',
                // location.search 拿到了形如 '?blogId=5' 这样的一段内容
                url: 'blog' + location.search,
                success: function(body) {
                    // 根据 body 中的内容来构造页面
                    // 1. 构造博客标题
                    let h3 = document.querySelector(".blog-content>h3");
                    h3.innerHTML = body.title;
                    // 2. 构造博客发布时间
                    let dateDiv = document.querySelector('.date');
                    dateDiv.innerHTML = body.postTime;
                    // 3. 构造博客正文
                    // 如果直接把 content 设为 innerHTML, 此时展示在界面上的内容, 是原始的 markdown 字符串
                    // 咱们需要的是渲染后的, 带有格式的效果
                    // let content = document.querySelector('#content');
                    // content.innerHTML = body.content;

                    // 第一个参数对应 id=content 的 html 标签. 渲染后得到的 html 片段就会被放到这个 标签下. 
                    editormd.markdownToHTML('content', {
                        markdown: body.content
                    });
                }
            });
        }

        getBlogDetail();


        // 加上一个逻辑, 通过 GET /login 这个接口来获取下当前的登录状态~
        function getUserInfo(pageName) {
            $.ajax({
                type: 'get',
                url: 'login',
                success: function(body) {
                    // 判定此处的 body 是不是一个有效的 user 对象(userId 是否非 0)
                    if (body.userId && body.userId > 0) {
                        // 登录成功!
                        // 不做处理!
                        console.log("当前用户登录成功! 用户名: " + body.username);

                        // 在 getUserInfo 的回调函数中, 来调用获取作者信息
                        getAuthorInfo(body);
                    } else {
                        // 登录失败!
                        // 让前端页面, 跳转到 login.html
                        alert("当前您尚未登录! 请登录后再访问博客列表!");
                        location.assign('blog_login.html');
                    }
                },
                error: function() {
                    alert("当前您尚未登录! 请登录后再访问博客列表!");
                    location.assign('blog_login.html');
                }
            });
        }

        // 判定用户的登录状态
        getUserInfo("blog_detail.html");

        // 从服务器获取一下当前博客的作者信息, 并显示到界面上. 
        // 参数 user 就是刚才从服务器拿到的当前登录用户的信息
        function getAuthorInfo(user) {
            $.ajax({
                type: 'get',
                url: 'authorInfo' + location.search,
                success: function(body) {
                    // 此处的 body, 就是服务器返回的 User 对象, 是文章的作者信息
                    if (body.username) {
                        // 如果响应中的 username 存在, 就把这个值设置到页面上. 
                        changeUserName(body.username);

                        if (body.username == user.username) {
                            // 作者和登录的用户是一个人, 则显示 "删除按钮"
                            let navDiv = document.querySelector('.nav');
                            let a = document.createElement('a');
                            a.innerHTML = '删除';
                            // 期望点击删除, 构造一个形如 blogDelete?blogId=6 这样的请求
                            a.href = 'blogDelete' + location.search;
                            navDiv.appendChild(a);
                        }
                    } else {
                        console.log("获取作者信息失败! " + body.reason);
                    }
                }
            });
        }

        function changeUserName(username) {
            let h3 = document.querySelector('.card>h3');
            h3.innerHTML = username;
        }
    </script>
</body>
</html>

在这里插入图片描述
在这里插入图片描述

实现"注销"功能

退出当前登录的状态
在导航栏中安排一个“注销”按钮
当用户点击注销之后,就会在服务器上取消登录状态,并且跳转到登录页面。

约定前后端交互接口

请求:
GET / logout
响应:
HTTP/1.1 302
Location:login.html
package controller;

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;

@SuppressWarnings({"all"})
@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.getWriter().write("当前用户尚未登录,无法注销1");
            return;
        }
        // 然后把这个用户的会话中的信息删掉
        session.removeAttribute("user");
        resp.sendRedirect("blog_login.html");
    }
}

发布博客功能

在博客编辑页中,当用户输入了博客标题,和正文之后,点击发布~
此时就会把博客数据提交到服务器,由服务器存储到数据库中~

约定前后端交互接口

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

title=这是标题&content=这是正文....
响应:
HTTP/1.1 302
Location:blog_list.html
package controller;

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.util.List;

// 通过这个类,来处理 /blog 的对应请求
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();

    // 这个方法用来获取到数据库中的博客列表
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf8");
        BlogDao blogDao = new BlogDao();
        // 先尝试获取到 req 中的 blogId参数,如果该参数存在,说明要请求博客详情
        // 如果不存在,就说明要请求博客列表
        String pararm = req.getParameter("blogId");
        if (pararm == null) {
            // 不存在参数,获取博客列表
            // 从数据库中查询到博客列表,转成JSON格式 然后直接返回即可
            List<Blog> blogs = blogDao.selectAll();
            // 把 blogs 对象转成JSON格式
            String respJson = objectMapper.writeValueAsString(blogs);

            resp.getWriter().write(respJson);
        } else {
            // 存在参数,获取博客详情
            int blogId = Integer.parseInt(pararm);
            Blog blog = blogDao.selectOne(blogId);
            String respJson = objectMapper.writeValueAsString(blog);
            resp.getWriter().write(respJson);
        }
    }

    @Override
    protected void doPost(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;charset=utf8");
            // 直接告诉客户端,请求参数不对
            resp.getWriter().write("当前用户未登录,不能提交博客!");
            return;
        }
        // 一定哟啊先指定好请求按照哪种编码来解析
        req.setCharacterEncoding("utf8");
        // 先从请求中,取出参数(博客的标题和正文)
        String title = req.getParameter("title");
        String content = req.getParameter("content");
        if (title == null || "".equals(title) || content == null || "".equals(content)) {
            resp.setContentType("text/html;charset=utf8");
            // 直接告诉客户端,请求参数不对
            resp.getWriter().write("提交博客失败!缺少必要的参数!");
            return;
        }
        // 构造 Blog 对象,把当前的信息填进去,并插入数据库中
        // 此处要给 Blog 设置的属性,主要是 title,content,userId
        // postTime 和 blogId 都不需要手动指定,都是插入数据库的时候自动生成的
        Blog blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        // 作者 id 就是当前提交这个博客的用户的身份信息!
        blog.setUserId(user.getUserId());
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);
        // 重定向到博客列表页
        resp.sendRedirect("blog_list.html");
    }
}

这里就需要整一个form表单,把这里的内容给套上!
在这里插入图片描述
在这里插入图片描述

删除博客

在这里插入图片描述
服务器处理
用户点击删除按钮,触发一个HTTP请求,HTTP请求就会让服务器删除指定的博客~

完整代码

完整代码我放gitee仓库了,需要的可以去博客系统仓库找。

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

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

相关文章

【论文阅读总结】用于目标检测的特征金字塔网络(FPN)

Feature Pyramid Networks for Object Detection1.摘要2.引言2.1 低级特征对于检测小物体很重要2.2 算法目标3. 文献综述3.1 Hand-engineered features and early neural networks3.2 Deep ConvNet object detectors3.3 Methods using multiple layers4.Feature Pyramid Networ…

嵌入式:BSP的理解

BSP概念总结BSP定义BSP的特点BSP的主要工作BSP在嵌入式系统和Windowsx系统中的不同BSP和PC机主板上的BIOS区别BSP与 HAL关系嵌入式计算机系统主要由 硬件层&#xff0c;中间层&#xff0c;系统软件层和应用软件层四层组成。硬件层&#xff1a;包含CPU&#xff0c;存储器(SDRAM&…

Mybatis(一):环境搭建

Mybatis&#xff08;一&#xff09;&#xff1a;环境搭建前言一、MyBatis简介1、MyBatis历史2、MyBatis特性3、MyBatis下载4、和其它持久化层技术对比二、搭建MyBatis1、开发环境2、创建maven工程2.1 打包方式&#xff1a;jar2.2 引入依赖3、创建MyBatis的核心配置文件4、创建m…

通俗简介:操作系统之进程的管理与调度

操作系统是一个复杂的软件&#xff0c;具备许多功能。其中&#xff0c;进程的管理与调度是与我们密切相关的。本文将对操作系统功能中进程管理与调度作出介绍。 目录 一、进程 二、 进程管理 1、进程管理的概念 2、进程结构体的核心属性 3、进程调度 &#xff08;1&#…

如何将pdf文件压缩?pdf压缩软件哪个好

PDF是一种常见的文档格式&#xff0c;因为包括文本格式和图像&#xff0c;我们往往采用这种格式进行文件传输和分享&#xff0c;但是也常常会因为pdf文件过大导致使用起来非常不方便&#xff0c;那么如何如何将pdf文件压缩&#xff08;https://www.yasuotu.com/pdfyasuo&#x…

禁用非必需插件,让 IDEA 飞起

文章首发于个人博客&#xff0c;欢迎访问关注&#xff1a;https://www.lin2j.tech IDEA 为我们提供了众多的插件&#xff0c;但是这些插件并不都是必须的。如果电脑的性能不够强&#xff0c;反而会带来一些不必要的资源消耗。 因此这里整理了一些不常用的插件&#xff0c;可以…

如何让AI帮你干活-娱乐(3)

背景今天的话题会偏代码技巧一些&#xff0c;对于以前没有接触过代码的朋友或者接触代码开发经验较少的朋友会有些吃力。上篇文章介绍了如何广视角的生成相对稳定的视频。昨天的实现相对简单&#xff0c;主要用的是UI界面来做生成。但是生成的效果其实也显而易见&#xff0c;不…

【个人首测】百度文心一言 VS ChatGPT GPT-4

昨天我写了一篇文章GPT-4牛是牛&#xff0c;但这几天先别急,文中我测试了用GPT-4回答ChatGPT 3.5 和 Notion AI的问题&#xff0c;大家期待的图片输入也没有出现。 昨天下午百度发布了文心一言&#xff0c;对标ChatGPT&#xff0c;录屏无实机演示让百度股价暴跌。但是晚上百度就…

不要迷信 QUIC

很多人都在强调 QUIC 能解决 HoL blocking 问题&#xff0c;不好意思&#xff0c;我又要泼冷水了。假设大家都懂 QUIC&#xff0c;不再介绍 QUIC 的细节&#xff0c;直接说问题。 和 TCP 一样&#xff0c;QUIC 也是一个基于连接的&#xff0c;保序的可靠传输协议&#xff0c;T…

【测试开发篇4】测试模型

目录 一、软件测试V模型 编码前 概要设计&#xff1a; 详细设计&#xff1a; 编码后&#xff1a; 单元测试&集成测试 系统测试 验收测试 V模型的特点 优点&#xff1a; 缺点&#xff1a; 二、软件测试W模型 编码之前&#xff1a; 编码的时候&#xff1a; 编…

全网最详细,Jmeter性能测试数据写入文件(总结)看这篇就够了......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 jmeter 性能测试数据…

RK3568平台开发系列讲解(Linux系统篇)消息队列

🚀返回专栏总目录 文章目录 一、创建消息队列二、发送和接收消息三、内核结构沉淀、分享、成长,让自己和他人都能有所收获!😄 📢消息队列在如下两个方面上比管道有所增强: 消息队列中的数据是有边界的,发送端和接收端能以消息为单位进行交流,而不再是无分隔的字节流…

Android---动态权限申请

目录 权限分类 动态权限核心函数 简易实现案例 完整代码 Google 在 Android 6.0 开始引入了权限申请机制&#xff0c;将所有权限分成了正常权限和危险权限。App 每次在使用危险权限时需要动态的申请并得到用户的授权才能使用。 权限分类 系统权限分为两类&#xff1a;正常…

队列实现及leetcode相关OJ题

上一篇写的是栈这一篇分享队列实现及其与队列相关OJ题 文章目录一、队列概念及实现二、队列源码三、leetcode相关OJ一、队列概念及实现 1、队列概念 队列同栈一样也是一种特殊的数据结构&#xff0c;遵循先进先出的原则&#xff0c;例如&#xff1a;想象在独木桥上走着的人&am…

计算机网络管理 TCP三次握手的建立过程,Wireshark抓包分析并验证TCP三次握手建立连接的报文

⬜⬜⬜ ---&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; (*^▽^*)欢迎光临 &#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;---⬜⬜⬜ ✏️write in front✏️ &#x1f4dd;个人主页&#xff1a;陈丹宇jmu &#x1f381;欢迎各位→…

【Linux入门篇】操作系统安装、网络配置

目录 &#x1f341;Linux详解 &#x1f342;1.操作系统 &#x1f342;2.操作系统组成 &#x1f342;3.操作系统历史 &#x1f342;4.常见的Linux系统 &#x1f342;5.centos7下载 &#x1f342;6.安装centos7 &#x1f341;linux初始化配置 &#x1f343;1.虚拟机系统安装后操作…

从零实现深度学习框架——学习率调整策略介绍

引言 本着“凡我不能创造的,我就不能理解”的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导。 要深入理解深度学习,从零开始创建的经验非常重要,从自己可以理解的角度出发,尽量不使用外部完备的框架前提下,实现我…

【微信小程序】-- 案例 - 自定义 tabBar(四十六)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

kali内置超好用的代理工具proxychains

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他所拥有的。所以可以不学无术&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关注WEB安全、网络攻防。…

31. 下一个排列

题目链接&#xff1a;https://leetcode.cn/problems/next-permutation/解题思路&#xff1a;整数数组的 下一个排列 是指其整数的下一个字典序更大的排列&#xff0c;其实也就是把整数所有数字从左往右组合成一个数&#xff0c;则下一个排列就是将数组中的所有元素重新组合成一…
最新文章