博客系统 —— Servlet 实现(前后端分离)(详细+整体代码+效果图)

news2024/9/23 3:31:51

目录

一、项目效果展示

二、创建 Servlet 项目

三、编写数据库的操作代码

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

2、封装数据库操作(Model)

(1)先创建 DBUtil 封装数据库连接的操作。

(2)创建实体类。使用实体类表示数据库中的一条记录

(3)封装针对数据的增删改查。

四、编写代码

1、博客列表页

(1)约定前后端接口

(2)编写服务器代码

(3)编写客户端代码

2、博客详情页

(1)约定前后端交互接口

(2)实现服务器端代码

(3)实现前端代码

3、博客登录页

(1)约定前后端交互接口

(2)实现客户端代码

(3)实现服务器端代码

4、检测用户登录状态

(1)约定前后端交互接口

(2)实现服务端代码

(3)实现客户端代码

5、显示用户信息

(1)针对博客列表页

 (2)针对博客详情页

 6、注销功能

(1)约定前后端交互接口

(2)实现服务器代码

(2)实现客户端代码

 7、发布博客功能

(1)约定前后端交互接口

 (2)实现服务器端代码

 (3)实现客户端代码

 8、删除博客功能

(1)约定前后端交互接口

(1)实现服务器代码


一、项目效果展示

二、创建 Servlet 项目

整个项目的目录:

pom.xml :

<?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>

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>

 

三、编写数据库的操作代码

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

mysql里,unique + not null = primary key,又要求一个表里只能有一个 primary key。

-- 编写建库建表的 sql

-- 建库
create database if not exists blogsystem;
use BlogSystem;

-- 创建一个博客表
-- 包括:博客ID,博客标题,博客内容,博客作者,博客发布时间
drop table if exists blog;
create table blog (
    blogId int primary key auto_increment,
    title varchar(1024),
    content mediumtext,
    userId int,
    postTime datetime
);

-- 创建一个用户表
-- 包括:用户ID,用户名,用户密码
drop table if exists user;
create table user (
    userId int primary key auto_increment,
    username varchar(128) unique,  -- 后续会使用用户名进行登录,因此用户名是不可以重复的。
    password varchar(128)
);

-- 向表中添加数据测试。
-- now() 返回的是一个时间戳,需要将时间戳转化成格式化时间,
-- 这个转化可以在前端来做,也可以在后端做。
-- 这里选择在 Blog类中进行转换。(修改 getter 方法)
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,'这是第 1 篇博客','从今天开始我要认真学习咯',2,now())    ;
insert into blog values(null,'这是第 2 篇博客','从昨天 开始我要认真学习咯',2,now());
insert into blog values(null,'这是第 3 博客','# 一级标题\n ### 三级标题\n > 这是引用内容',2,now());
insert into blog values(null,'这是第 四 博客','# 一级标题\n',1,now());


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

2、封装数据库操作(Model)

(1)先创建 DBUtil 封装数据库连接的操作。

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;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: WangWZ
 * Date: 2023-05-07
 * Time: 15:11
 */

public class DBUtil {
    private static String URL = "jdbc:mysql://127.0.0.1:3306/blogsystem?characterEncoding=utf8&useSSL=false";
    private static String USERNAME = "root";
    private static String PASSWORD = "12345";

    //创建一个数据源对象
    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(URL);
                    ((MysqlDataSource)dataSource).setUser(USERNAME);
                    ((MysqlDataSource)dataSource).setPassword(PASSWORD);
                }
            }
        }


        return dataSource;
    }

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

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

        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

(2)创建实体类。使用实体类表示数据库中的一条记录

        此处 主要创建了 Blog 类 和 User 类。

package Model;

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

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: WangWZ
 * Date: 2023-05-07
 * Time: 17:22
 */
public class Blog {
    private int blogId;
    private String title;
    private String content;
    private int uesrId;
    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 getUesrId() {
        return uesrId;
    }

    public void setUesrId(int uesrId) {
        this.uesrId = uesrId;
    }

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

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

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: WangWZ
 * Date: 2023-05-07
 * Time: 17:32
 */
public class User {
    private int userId = 0;
    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;
    }
}

(3)封装针对数据的增删改查。

        提供了增删改查这样的类,称为 DAO。

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;

/**
 * Created with IntelliJ IDEA.
 * Description: 这个类用于封装 博客表 的基本操作(没有改的操作)
 * (1)往博客表里插入博客
 * (2)从博客表里,根据 博客ID 删除博客
 * (3)查询所有的博客(用于博客列表页)
 * (4)根据 博客ID 查询博客内容(用于博客详情页)
 *
 * 每个方法都要实现 JDBC 基本代码
 * (1)和数据库建立连接
 * (2)构造 SQL 语句
 * (3)执行 SQL 语句
 * (4)关闭连接,释放资源
 * User: WangWZ
 * Date: 2023-05-07
 * Time: 17:34
 */
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.getUesrId());
            //3.执行 SQL
            statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,null);
        }
    }

    //2、从 博客表 中,根据 博客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) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,null);
        }
    }

    //3、查询所有博客(用于博客列表页,注意这里不一定会获取到完整的正文)
    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 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"));

                //获取博客内容的时候要进行截取
                //自定义只显示前 50 行
//                blog.setContent(resultSet.getString("content"));
                String content = resultSet.getString("content");
                if(content.length() > 50) {
                    content = content.substring(0,50) + ".....";
                }
                blog.setContent(content);

                blog.setUesrId(resultSet.getInt("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blogs.add(blog);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return blogs;
    }

    //4、根据 博客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();
            if (resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setUesrId(resultSet.getInt("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                return blog;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

}
package Model;

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

/**
 * Created with IntelliJ IDEA.
 * Description: 用户类 的基本操作
 * 这里实现的博客系统,不涉及用户的注册和注销操作。因此只有在数据库中进行查找的相关操作。
 * (1)根据 用户ID 查找用户信息(用于博客详情页,可以根据 用户ID 来查询作者的名字,并进行显示)
 * (2)根据 用户名 查找用户信息(用于登录页面)
 *
 * User: WangWZ
 * Date: 2023-05-07
 * Time: 19:05
 */
public class UserDao {
    //1.根据 用户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();
            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) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

    //2.根据 用户名 查找用户信息(用于登录页面)
    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();
            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) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }
}

四、编写代码

        根据前端的四个页面,分别 “约定前后端交互接口”、“编写服务器代码”、“编写客户端代码”。

1、博客列表页

        展示出数据库中的博客的列表。

(1)约定前后端接口

        获取博客列表。

请求:

GET /blog 

响应:(使用json格式)json数组,每一个元素又是一个json对象。

[

        {

                blogId:1,

                title:' 这是第一篇博客 ',

                content:' 博客正文摘要 ',

                userId:1,

                postTimer:' 2023-05-7 20:00:00 '

        },

        {

                blogId:2,

                title:' 这是第二篇博客 ',

                content:' 博客正文摘要 ',

                userId:1,

                postTimer:' 2023-05-7 20:01:00 '

        },

......

]

摘要:如果正文太长,就只截取一小部分。

(2)编写服务器代码

        这里的代码会更新。

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
    resp.setContentType("application/json; charset=utf-8");
    BlogDao blogDao = new BlogDao();
    List<Blog> blogs = blogDao.selectAll();
    String jsonString = objectMapper.writeValueAsString(blogs);
    resp.getWriter().write(jsonString);
}

(3)编写客户端代码

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

       

 <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script>
        // 在页面加载的时候,就通过调用函数 getBlogList 来通过 ajax 给服务器发送数据,获取博客列表信息,并且显示在界面上
        function getBlogList() {
            $.ajax({
                type: 'get',
                url: 'blog',
                success: function(body) {
                    //获取到的 body 就是一个 js 数组。(ajax 自动帮我们转换了)
                    //每个元素就是一个 js 对象,根据这个对象,构造 div

                    //1、先把之前的 div 里的内容清空。
                    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);
                    }
                }
            })
        }
        getBlogList();
    </script>

2、博客详情页

        在 blog_detail.html 页面加载的时候,触发 ajax 请求来访问服务器,获取到博客内容,再次填充到博客详情页里。

(1)约定前后端交互接口

请求:

GET /blog?blogId=1

响应:这里的响应结果不是数组,而是单一的对象

HTTP/1.1 200 OK

Content-Type:application/json;

{

                blogId:1,

                title:' 第一篇博客 ',

                content:' 正文 ',

                userId:1,

                postTimer:' 2023-05-7 20:00:00 '

}

(2)实现服务器端代码

        因为上面传的url还是blog下,所以依旧在 BlogServlet 类中写。

注意:这里都是GET请求,但是只有一个doGet

一个是 GET /blog ,一个是GET /blog?blogId=1,参数不同,我们就可以实现不同的参数实现不同的功能。

package Controller;

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

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;

/**
 * Created with IntelliJ IDEA.
 * Description: 通过这个类,处理 blog 路径对应的请求
 * 用于 博客列表页
 * User: WangWZ
 * Date: 2023-05-07
 * Time: 19:20
 */
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        BlogDao blogDao = new BlogDao();
        resp.setContentType("application/json;charset=utf8");
        //判断传过来的参数中是否有 blogId 的参数
        //1.先尝试获取 req 中的blogId 参数,如果参数存在,说明是要请求博客详情的。
        //如果参数不存在,说明是请求博客的列表。
        String param = req.getParameter("blogId");
        if (param == null) {
            //不存在参数 blogId,说明要请求博客列表。
            //从数据库中查询到博客列表,转成 JSON 格式,然后直接返回即可
            List<Blog> blogs = blogDao.selectAll();
            //把 blog 对象转成 JSON 格式
            String respJson = objectMapper.writeValueAsString(blogs);
            resp.getWriter().write(respJson);
        } else {
            //存在参数 blogId,说明要请求博客的详情
            //这里要赋值 blogId,但是我们使用 req.getParameter 获取的是 String类型的值
            //因此使用 Integer.parseInt(),将 String 类型转为 int类型。
            int blogId = Integer.parseInt(param);
            Blog blog = blogDao.selectOne(blogId);
            //把 blog 对象转成 json 对象格式
            String respJson = objectMapper.writeValueAsString(blog);
            resp.getWriter().write(respJson);
        }


    }
}

(3)实现前端代码

        修改 blog_detail.html,让这个页面加载的时候,能够调用上述接口,来从服务器获取到博客数据。

        在前端代码中,要想构造一个请求获取博客详情,就得知道当前用户点击的博客的ID,而这个ID 已经包含在当前的 blog_detail.html 页面的 url 里了。通过 location.search 可以进行获取,获取后在 ajax 中的 url 后面一加就可以了。

<script>
    function getBlogDetail() {
        $.ajax({
            type: 'get',
            // 注意:
            //① 这里的 blog :相对路径; /blog :绝对路径
            //② loction.search 拿到了形如 '?blog=1' 这样的一段内容
            url: 'blog' + location.search,
            success:function(body) {
                //请求成功,对于服务器返回来的 body 进行页面的构造
                //1.构造博客标题
                let h3 = document.querySelector(".blog-content>h3");
                h3.innerHTML = body.title;
                //2.构造博客发布时间
                let date = document.querySelector('.date');
                date.innerHTML = body.postTime;
                //3.构造正文
                // 注意我们写的博客是用 merkdown 写的,因此如果直接把 content 设为 innerHTML ,此时展示在界面上的内容,是原始的 Markdown 字符串
                // 而我们这里需要的是渲染后的,带有格式的效果的正文。
                //因此这里还要导入 Markdown的依赖,利用里面提供的方法进行转换。
                //第一个参数对应 id=content 的 html 标签,渲染后得到的 html 片段就会被放到这个标签下。
                editormd.markdownToHTML('content',{
                    markdown: body.content
                });
            }
        });
    }
    getBlogDetail();
</script>

3、博客登录页

        实现用户登录功能。

(1)约定前后端交互接口

请求:

POST /login

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

username=zhangsan&password=123

这里的逻辑,可以直接使用 form表单 来进行提交,没必要非得用 ajax(使用 ajax 也可以)。要使用表单,就需要把按钮改成 input type= " submit "

响应:

HTTP/1.1 302

Location:blog_list.html

(2)实现客户端代码

        修改 blog_login.html。

套上 form 标签;

给 input 加上 name 属性,是后续提交数据的键值对的key;

把 button 按钮换成 input 标签;submit

注意页面样式是否会改变(css/js)

    <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>

         代码中约定的路径是/login,因此创建一个新的 Servlet。

(3)实现服务器端代码

        LoginServlet.java。

为了避免Servlet解析请求数据时,汉字出现乱码:

req.setCharacterEncoding("utf8"); 针对请求进行设置,使用utf8格式来解析请求。

resp.setCharacterEncoding("utf8"); 针对响应进行设置,构造的数据要按照utf8构造。

   //1.获取到请求中的参数
    //2.和数据库中的进行比较
    //3.如果比较通过,就创建会话
    //4.返回一个重定向报文,跳转到博客列表页
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //考虑当我们的用户名有中文时,Servlet汉字解析编码不是utf8,所以我们要进行规定。
        req.setCharacterEncoding("utf8");
        resp.setCharacterEncoding("utf8");
        //1.获取到请求中的参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        //2.和数据库中的进行比较
        //特殊情况
        if(username == null || "".equals(username) || password == null || "".equals(password)) {
            //请求的内容缺失,登录失败
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前用户登录用户名或密码为空");
            return;
        }
        //在数据库中找
        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");
    }

4、检测用户登录状态

        调整博客列表页和博客详情页,让这两个页面必须登录后才能访问。在进入博客列表页/博客详情页的时候,先检查一下用户的登录状态,如果用户当前已经是登录状态,才能继续使用。如果是未登录状态,则强制跳转到 login 页面。

        在博客列表页和博客详情页加载的时候,通过 ajax 访问一下服务器,获取当前的登录状态。如果获取到了,就说明当前已经登录了,可以留在这个页面。如果没有获取到,说明未登录,跳转到登录页面。

(1)约定前后端交互接口

请求:

GET /login 

响应:

HTTP/1.1 200 OK

Content-Type:application/json

{

        userId:1,

        username:' zhangsan ',

}

登录了,就直接返回当前登录的用户信息;

没登录,则返回一个 userId 为 0 的对象。(也可以用其他方式来约定:403表示未登录... )

{

        userId:0,

        username:'  ',

}

(2)实现服务端代码

        在LoginServlet.java中添加doGet方法。

   //用这个方法来让前端检测当前的登录状态
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //在服务器这边拿到了 session,并且也拿到了里面的 user。视为是登录成功。
        //(如果登陆成功的话,服务器会给客户端返回 session,浏览器就会保存这个 session。下次请求的事就就会带上这个 id )
        //服务器拿到 sessionId 就可以去 hash 表里查看,就知道了当前的 session 对象是谁。
        resp.setContentType("application/json;charset=utf8");
        HttpSession session = req.getSession(false);
        //因为当用户存在在数据库时,才创建了会话,所以先判断会话是否存在
        //若不存在,说明一定未登录
        if (session == null) {
            User user = new User();
            String respJson = objectMapper.writeValueAsString(user);
            resp.getWriter().write(respJson);
            return;
        }
        //存在会话,继续判断
        User user = (User)session.getAttribute("user");
        if (user == null) {
            //有会话,但是会话里没有 User 对象,也视为未登录
            user = new User();
            String respJson = objectMapper.writeValueAsString(user);
            resp.getWriter().write(respJson);
            return;
        }
        //剩下情况就是登录了的情况
        //注意不要把密码返回给前端
        user.setPassword("");
        String respJson = objectMapper.writeValueAsString(user);
        resp.getWriter().write(respJson);
    }

(3)实现客户端代码

        在blog_list.html 和 blog_detail.html 中添加如下代码。

    <script>
        function getUserInfo(pageName) {
        $.ajax({
            type: 'get',
            url: 'login',
            success:function(body) {
                //判定此处的 body 是不是一个有效的 user 对象(userId 是否非0)
                if(body.userId && body.userId > 0) {
                    //登陆成功不作处理
                    consloe.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");

    </script>

5、显示用户信息

  • 在博客列表页,显示当前登录的用户。
  • 在博客详情页,显示当前作者的信息。

(1)针对博客列表页

        其实前面一斤处理过了(检测用户登录状态的时候)。调整前端代码即可。

        在 common.js 中。

// 这个文件里放一些公共代码

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

                //根据当前用户登录的情况,把当前用户名设置到界面上(博客列表页的左边信息部分)
                changeUserName(body.username);

            } else {
                //登录失败
                //让前端页面,跳转到哦 login.html
                alert("当前您尚未登录!请登录后自访问博客列表!");
                location.assign('blog_login.html');
            }
        },
        error:function() {
            alert("当前您尚未登录!请登录后再访问博客列表!");
            location.assign('blog_login.html');
        }
    });
}
getUserInfo();

//修改用户名,填入列表页的登录用户信息区域的名字里
function changeUserName(username) {
    // .card>h3:通过类名 card 找到card,再找到里面的h3标签。再用innerHTML放入
    let h3 = document.querySelector('.card>h3');
    h3.innerHTML = username;
}

blog_list.html:

    <!-- 因为下面的代码要重复使用(博客列表页和博客详情页),所以写在一个新的文件里,直接引入文件即可 -->
    <!-- 在这里引入 js 代码,就可以执行到里面的代码,也就实现了登录状态的检测了 -->
    <script src="js/common.js"></script>

 注意:这里的头像、github、文章的统计没有设置在数据库中,所以这里没有完全实现显示不同用户的全部信息。(逻辑上没有区别)

 (2)针对博客详情页

        对上面的 html 继续进行修改。让博客列表页(用户)和博客详情页(作者)显示不同的内容。

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

请求:

GET /authorInfo? blogId=6

响应:

{

        userId:6,

        username:' wwz',

}

        然后就在博客详情页,给服务器发送这个接口,来获取到当前的数据。另外对于博客详情页的 html 来说,也需要进行修改(不能设置名字了)。 

common.js

// 这个文件里放一些公共代码

// 加上一个逻辑,通过 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);

                //根据当前用户登录的情况,把当前用户名设置到界面上(博客列表页的左边信息部分)
                //博客详情页,通过其他的API来进行设定页面中的用户信息
                if(pageName == 'blog_list.html') {
                    changeUserName(body.username);
                }
                changeUserName(body.username);
            } else {
                //登录失败
                //让前端页面,跳转到哦 login.html
                alert("当前您尚未登录!请登录后自访问博客列表!");
                location.assign('blog_login.html');
            }
        },
        error:function() {
            alert("当前您尚未登录!请登录后再访问博客列表!");
            location.assign('blog_login.html');
        }
    });
}


//修改用户名,填入列表页的登录用户信息区域的名字里
function changeUserName(username) {
    // .card>h3:通过类名 card 找到card,再找到里面的h3标签。再用innerHTML放入
    let h3 = document.querySelector('.card>h3');
    h3.innerHTML = username;
}

blog_list.html

    <!-- 因为下面的代码要重复使用(博客列表页和博客详情页),所以写在一个新的文件里,直接引入文件即可 -->
    <!-- 在这里引入 js 代码,就可以执行到里面的代码,也就实现了登录状态的检测了 -->
    <script src="js/common.js"></script>
    <script>
        //针对博客列表页,调用的时候传入参数
        getUserInfo('blog_list.html');
    </script>

blog_detail.html

    function getUserInfo(pageName) {
        $.ajax({
            type: 'get',
            url: 'login',
            success:function(body) {
                //判定此处的 body 是不是一个有效的 user 对象(userId 是否非0)
                if(body.userId && body.userId > 0) {
                    //登陆成功不作处理
                    consloe.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");

  创建新类 AuthorServlet 来获取指定博客的作者信息。

package Controller;

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

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;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: WangWZ
 * Date: 2023-05-09
 * Time: 9:02
 */
@WebServlet("/authorInfo")
public class AuthorServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //通过这个方法,来获取到指定博客作者的信息
        resp.setContentType("application/json;charset=utf8");
        String param = req.getParameter("blogId");
        if (param == null || "".equals(param)) {
            //blogId 不存在 或 为0
            resp.getWriter().write("{ \"ok\": false, \"reason\": \"参数缺失!\" }");
            return;
        }

        //根据当前的 blogId 在数据库中进行查找,找到 blog 对象,再根据 blog 对象找到作者信息
        BlogDao blogDao = new BlogDao();
        Blog blog = blogDao.selectOne(Integer.parseInt(param));
        if (blog == null) {
            //数据库中没有要查找的博客
            resp.getWriter().write("{ \"ok\": false, \"reason\": \"要查询的博客不存在\" }");
            return;
        }

        //根据 blog 对象,找到作者信息
        UserDao userDao = new UserDao();
        User author = userDao.selectById(blog.getUesrId());
        if (author == null) {
            //数据库中没有要查找的用户信息
            resp.getWriter().write("{ \"ok\": false, \"reason\": \"要查找的用户不存在\" }");
            return;
        }

        //找到作者信息了,进行返回
        //注意直接返回会返回用户密码,所以要读密码进行覆盖。
        author.setPassword("");
        //注意把 author Java对象转成 Json字符串再进行传输
        resp.getWriter().write(objectMapper.writeValueAsString(author));
    }
}

并且在 blog_detail.html 中添加逻辑,发送 ajax 请求,获取当前博客的作者信息,并让作者信息进行显示。

    // 从服务器获取一下当前博客的作者信息,并显示到界面上
    function getAuthorInfo() {
        $.ajax({
            type:'get',
            url:'authorInfo' + location.search,
            success:function(body) {
                //此处的 body,就是服务器返回的 User 对象
                if(body.username) {
                    //如果响应中的 username 存在,就把这个值设置到界面上
                    changeUserName(body.username);
                } else {
                    console.log("获取作者信息失败!" + body.reason);
                }
            }
        });
    }
    getAuthorInfo();

1、博客列表页,显示登录的用户信息,在检测用户是否登录的接口中,就已经拿到了。只用把拿到的用户信息进行显示即可。(直接在前端代码中修改)

2、博客详情页,提供了一个新的  API,让客户端传一个 博客id过去,然后再服务器这里查询当前的用互信息。查到后返回给页面。 

 6、注销功能

        退出当前登录的状态。在导航栏中设置一个注销按钮,用户点击后,就取消登录状态,并跳转到登录页面。

(1)约定前后端交互接口

        点击注销后,给服务器发送一个请求,实现注销。即把会话中的信息删除。

请求:

GET /logout 

响应:

HTTP/1.1 302 

Location:login.html

(2)实现服务器代码

        创建 LogoutServlet 进行处理这个请求。

登录:用户有一个session ,同时 session 有一个 user 属性,两者同时具备才是登录状态。

注销:只要破坏上面的任意一个条件就行。这里把 user 属性从 session 中删除。

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;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: WangWZ
 * Date: 2023-05-09
 * Time: 9:59
 */
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //通过这个方法实现注销,即从会话中删除 user 属性。
        HttpSession session = req.getSession();
        if (session == null) {
            //没有会话,说明用户没有登录,谈不上注销
            resp.getWriter().write("当前用户尚未登录!无法注销!");
            return;
        }

        //有会话,在会话中将 user 属性删除。
        session.removeAttribute("user");
        //删除后注销了,跳转到登录页面
        resp.sendRedirect("blog_login.html");
    }
}

(2)实现客户端代码

        只用修改注销中的 href 即可。(除了登录页面没有注销按钮,其他页面都要添加)

        <a href="logout">注销</a>

 7、发布博客功能

        在写博客的页面里,点击发布文章按钮,进行博客发布。

(1)约定前后端交互接口

        点击发布后,将博客数据提交到服务器,由服务器存储到数据库中。

请求:

POST /blog 

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

title=这是标题&content=这是正文

(内容都是需要 urlencode 的,浏览器自己自动进行编码实现)

响应:

HTTP/1.1 302

Location:blog_list.html

 (2)实现服务器端代码

        在 blogServlet 代码中,添加 doPost 方法,来处理这个 POST 请求。

   //通过这个方法实现发布博客
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //读取请求中的标题和正文,构造 blog 对象,并插入数据库。最后跳转到博客列表页
        HttpSession session = req.getSession(false);
        if (session == 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 || content == null || "".equals(title) || "".equals(content)) {
            //直接告诉客户端,请求参数不对
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("提交博客失败,缺少必要的参数");
            return;
        }

        //数据正常,构造blog 对象,进行插入数据库
        Blog blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        //这里还需要插入博客作者的 id,而这个id 存放在session 中,因此再使用 session 来获取 user属性中的 userId
        User user =(User)session.getAttribute("user");
        if (user == null) {
            //说明当前用户未登录,不能提交博客。
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前用户未登录,不能提交博客");
            return;
        }
        blog.setUesrId(user.getUserId());

        //将 blog 对象插入数据库
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);
        
        //发布完成,重定向到博客列表页
        resp.sendRedirect("blog_list.html");


    }

 (3)实现客户端代码

        使用 form表单,把这里的内容套上。

① 添加 form 表单,把之前的输入框等部分进行包裹;

② 添加 name 属性

③发布按钮,改成input标签

④创建隐藏的 textarea,为了后续的提交

⑤在初始化编辑器部分,添加设置标志位,使当前的输入框的内容能自动保存到 textarea 中。

⑥更改发布按钮的样式。以为父元素改变,设置新父元素的高度。

   <!-- 包裹整个博客编辑页内容的顶级容器 -->
    <div class="blog-edit-container">
        <form action="blog" method="post" style="height: 100%;">
            <div class="title">
                <input type="text" placeholder="在此处输入标题" name="title">
                <!-- <button>发布文章</button> -->
                <input type="submit" value="发布文章" id="submit">
            </div>
            <!-- 放置 md 编辑器 -->
            <div id="editor" >
                <!-- 为了进行 form 的提交,此处使用一个 textarea 多行编辑框,借助这个编辑框来实现表单的提交 -->
                <!-- 可以设置 editor.md,让编辑器把 markdown 内容也同步的保存到这个隐藏的 textarea 中,从而可以进行 form 提交 -->
                <textarea name="content" style="display: none;"></textarea>
            </div>            
        </form>
    </div>

    <script>
    // 初始化编辑器
    var editor = editormd("editor", {
        // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. 
        width: "100%",
        // 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
        height: "calc(100% - 50px)",
        // 编辑器中的初始内容
        markdown: "# 在这里写下一篇博客",
        // 指定 editor.md 依赖的插件路径
        path: "editor.md/lib/",
        // 注意要加上这个选项,有了这个选项,editor.md就会自动把用户在编辑器输入的内容同步保存到隐藏的 textarea 中了
        saveHTMLTOTextarea: true,
    });
    </script>

css中的样式:将 button 改为#submit。

 8、删除博客功能

        前提,只有自己能删除自己的博客,不能删除别人的博客。

界面上:博客详情页中,判断当前登录用户是否是该博客的作者,如果是,显示一个删除按钮;如果不是,就不显示。

   在博客详情页中,两个 ajax 是异步并发的关系。 第一个 ajax 发出后,不等响应回来,就已经发了第二个 ajax。如果这两个响应到达的顺序不确定,就不好进行判定了,因此必须手动的调整这两个方法,按照一定的顺序来进行发送。

若想先发UserInfo,后发AuthorInfo。在第一个 ajax 中执行第二个 ajax,才能保证两个 ajax 之间获取数据的顺序先后:

    function getUserInfo(pageName) {
        $.ajax({
            type: 'get',
            url: 'login',
            success:function(body) {
                //判定此处的 body 是不是一个有效的 user 对象(userId 是否非0)
                if(body.userId && body.userId > 0) {
                    //登陆成功不作处理
                    consloe.log("当前用户登录成功!用户名: " + body.username);

                    //在 getUserInfo 的回调函数中,用来调用获取作者信息
                    getAuthorInfo(body);
                } else {
                    //登录失败
                    //让前端页面,跳转到 login.html
                    alert("当前您尚未登录!请登录后再访问博客列表!");
                    //这个方法是在前端页面进行跳转的方式。
                    location.assign('blog_login.html');
                }
            },
            error: function() {
                alert("当前您尚未登录!请登录后再访问博客列表!");
                location.assign('blog_login.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);
                }
            }
        });
    }

 

(1)约定前后端交互接口

服务器上:用户点击删除按钮,就发送一个 HTTP 请求,让服务器删除指定的博客,服务器收到请求后,就将该博客从数据库中删除。

请求:

GET /blogDelete?blogId=4

响应:

直接跳转到博客列表页即可。

(1)实现服务器代码

package Controller;

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;

/**
 * Created with IntelliJ IDEA.
 * Description:
 *1.检查当前用户是否登录
 *2.获取到参数中的 blogId
 *3.获取要删除的博客信息
 *4.再次校验,当前用户是否就是博客的作者
 *5.确认无误,开始删除
 *6.重定向到博客列表
 * User: WangWZ
 * Date: 2023-05-09
 * Time: 14:49
 */
@WebServlet("/blogDelete")
public class BlogDeleteServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //通过这个方法来删除博客
        //1.检查当前用户是否登录
        HttpSession session = req.getSession();
        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;
        }

        //2.获取到参数中的 blogId
        String blogId = req.getParameter("blogId");
        if (blogId == null || "".equals(blogId)) {
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前博客Id参数不对");
            return;
        }

        //3.获取要删除的博客信息
        BlogDao blogDao = new BlogDao();
        Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
        if (blog == null) {
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("要删除的博客不存在");
            return;
        }

        //4.再次校验,当前用户是否就是博客的作者
        if (user.getUserId() != blog.getUesrId()) {
            //这里虽然在前端已经判断过了,此处再校验一次
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前登录的用户不是作者,没有权限进行删除");
            return;
        }

        //5.确认无误,开始删除
        blogDao.delete(Integer.parseInt(blogId));
        //6.重定向到博客列表
        resp.sendRedirect("blog_list.html");
    }
}

    

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

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

相关文章

Etsy总是不出单怎么办?出单后怎么操作?

如果您在Etsy上总是无法出单&#xff0c;可以尝试以下几个步骤&#xff1a; 1、检查您的商品列表是否符合Etsy的要求&#xff0c;包括商品描述、价格、运费等信息是否准确无误。 2、确认您的账户信息是否完整&#xff0c;包括银行账户、信用卡信息等。 3、检查您的店铺设置是否…

socket API的使用+client/server代码演示+封装socket模块

前言&#xff1a;本章旨在讲解常见的socket API及其使用练习&#xff0c;尾部有封装好的&#xff08;socket相关的一些系统函数加上错误处理代码包装成新的函数&#xff09;模块wrap.c 目录 一、socket模型创建流程图 1&#xff09;socket函数 2&#xff09;bind函数 3&am…

Altium软件中相同模块布局布线的方法

文章目录 1、原理图设计1、绘制xxx.SchDoc&#xff0c;并设置port。具体方法&#xff1a;Place→Port。2、新建顶层原理图&#xff1a;可以命名为xxx_TOP3、repeat 原理图&#xff0c;将这里从XXX_SingleDut 改为 Repeat(S,1,12)4、以总线的方式出线&#xff0c;如下&#xff1…

网络安全CVE 漏洞分析及复现

漏洞详情 Shiro 在路径控制的时候&#xff0c;未能对传入的 url 编码进行 decode 解码&#xff0c;导致攻击者可以绕过过滤器&#xff0c;访问被过滤的路径。 漏洞影响版本 Shiro 1.0.0-incubating 对应 Maven Repo 里面也有 【一一帮助安全学习&#xff0c;所有资源获取一一…

NetBackup 10.2 新功能介绍:PostgreSQL 和 MySQL 自动化恢复达成

NetBackup 10.2 新功能介绍&#xff1a;PostgreSQL 和 MySQL 自动化恢复达成 原文来自&#xff1a;VERITAS 中文社区 2023-04-27 在执行恢复任务时&#xff0c;手动提取、更新数据库和实例并将其附加到 PostgreSQL 和 MySQL 是常规操作。而在最新的 NetBackup 10.2 版本中&am…

数据可视化工具 - ECharts以及柱状图的编写

1 快速上手 引入echarts 插件文件到html页面中 <head><meta charset"utf-8"/><title>ECharts</title><!-- step1 引入刚刚下载的 ECharts 文件 --><script src"./echarts.js"></script> </head>准备一个…

apc-service-bus项目Docker镜像发布

apc-service-bus项目Docker镜像发布 1. 提交代码到Gitee代码仓&#xff0c;通过建木将项目打包到服务器 1.1 可直接打开访问建木&#xff0c;无有不熟悉建木发布流程的请咨询其他同事或者自行研究 建木地址&#xff1a;http://10.11.148.21/ 1.2 找到bus的开发环境部署执行…

神经网络全连接层数学推导

全连接层分析 对于神经网络为什么都能产生很好的效果虽然其是一个黑盒&#xff0c;但是我们也可以对其中的一些数学推导有一定的了解。 数学背景 目标函数为 f ∣ ∣ m a x ( X W , 0 ) − Y ∣ ∣ F 2 &#xff0c;求 ∂ f ∂ W , ∂ f ∂ X , ∂ f ∂ Y 目标函数为f ||ma…

SpringBoot项目如何打包成exe应用程序

准备 准备工作&#xff1a; 一个jar包&#xff0c;没有bug能正常启动的jar包 exe4j&#xff0c;一个将jar转换成exe的工具 链接: https://pan.baidu.com/s/1m1qA31Z8MEcWWkp9qe8AiA 提取码: f1wt inno setup&#xff0c;一个将依赖和exe一起打成一个安装程序的工具 链接:…

设计模式——代理模式(静态代理、JDK动态代理、CGLIB动态代理)

是什么&#xff1f; 如果因为某些原因访问对象不适合&#xff0c;或者不能直接引用目标对象&#xff0c;这个时候就需要给该对象提供一个代理以控制对该对象的访问&#xff0c;代理对象作为访问对象和目标对象之间的中介&#xff1b; Java中的代理按照代理类生成时机不同又分…

婴儿摇篮语音播放芯片,高品质MP3音乐播放芯片,WT2003H

婴儿摇篮是一种用于帮助婴儿入睡的设备。传统的婴儿摇篮通常只是简单的摇晃&#xff0c;但是带有语音播报芯片的婴儿摇篮则可以更好地模拟妈妈的声音&#xff0c;从而更有效地帮助婴儿入睡。 如果您正在寻找高品质音乐摇篮方案&#xff0c;那么WT2003H语音播放芯片&#xff0c…

5月7日 2H55min|5月8日8H50min|时间轴复盘|14:00~14:30

5月8日 todo list list4 40min ✅ |实际上用了50+50 list6 40min ✅ |实际上用了30+60 阅读+听力连做 100min ✅ 口语 day01 ✅ 口语 day02 口语 day03

6、并发事务控制MVCC汇总

1.并发事务控制 单版本控制-锁 先来看锁&#xff0c;锁用独占的方式来保证在只有一个版本的情况下事务之间相互隔离&#xff0c;所以锁可以理解为单版本控制。 在 MySQL 事务中&#xff0c;锁的实现与隔离级别有关系&#xff0c;在 RR&#xff08;Repeatable Read&#xff0…

vCenter Server 8.0U1 OVF:在 Fusion 和 Workstation 中快速部署 vCSA

vCenter Server 8.0U1 OVF&#xff1a;在 Fusion 和 Workstation 中快速部署 vCSA vCenter Server 8.0U1 系列更新 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-vcenter-8-ovf/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#x…

Win上通过Jconsole查看Java程序资源占用情况(教程总结,一篇就够了)

最近需要读取一个大文件&#xff0c;为了判断有没有读取到内存中&#xff0c;需要一个能查看jar包占用内存的工具&#xff0c;一顿面向百度后&#xff0c;发现了jdk自带的工具Jconsole&#xff0c;将教程分享给大家 一、介绍 JConsole 是一个内置 Java 性能分析器&#xff0c;…

手把手教你使用unisat 交易市场|BRC20|Unisat

开始前先熟悉下这张平台市场标注图&#xff0c;能让你跟得心应手&#xff01; 一、查看实时成交信息&#xff08;已 moon 为例子&#xff09; 搜索进入Token界面&#xff0c;点击 Orders 可以看到 1w 枚成交 87.86U&#xff08;单价 30 聪&#xff0c;大约 0.008786u&#xf…

牛客网剑指offer|中等题day2|JZ76删除链表中的重复节点、JZ23链表中环的入口节点、JZ24 反转链表(简单)

JZ76删除链表中的重复节点 链接&#xff1a;删除链表中重复的结点_牛客题霸_牛客网 参考代码&#xff1a; 自己好像还是偏向双指针这种想法&#xff0c;所以用了两个指针&#xff0c;这样感觉更好理解一些。 对了&#xff0c;去重有两种&#xff0c;我一开始写成了简单的那种&a…

MGV3001_ZG_当贝纯净桌面-线刷固件包

MGV3001_ZG_当贝纯净桌面-线刷固件包-内有教程及短接点 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的没用的软件&#xff0c;运…

【标准化方法】(3) Group Normalization 原理解析、代码复现,附Pytorch代码

今天和各位分享一下深度学习中常用的标准化方法&#xff0c;Group Normalization 数据分组归一化&#xff0c;向大家介绍一下数学原理&#xff0c;并用 Pytorch 复现。 Group Normalization 论文地址&#xff1a;https://arxiv.org/pdf/1803.08494.pdf 1. 原理介绍 在目标检测…

Javascript - Cookie的获取和保存应用

在之前的博客介绍了如何利用 Selenium去搭建 cookie池&#xff0c;进行自动化登录、获取信息等。那什么是cookie呢&#xff1f;它的作用又是什么呢&#xff1f; 这里&#xff0c;再重复简单介绍一下。 cookie 是浏览器储存在用户电脑上的一小段文本文件。该文件里存了加密后的用…