JavaWeb项目 -- 博客系统

news2024/12/23 12:10:46

JavaWeb项目 -- 博客系统

  • 前言:页面展示
  • 一、创建 Maven 项目
  • 二、设计数据库
  • 三、封装数据库的操作
    • 3.1 创建 DBUtil 类
    • 3.2 创建 Blog 类
    • 3.3 创建 User 类
    • 3.4 创建类 BlogDao
    • 3.5 创建类 UserDao
  • 四、导入准备好的前端代码
  • 五、实现博客列表界面
    • 5.1 约定好前后端交互接口
    • 5.2 实现 BlogServlet
    • 5.3 实现前端代码
  • 六、实现博客详情界面
    • 6.1 约定好前后端交互接口
    • 6.2 实现 BlogServlet
    • 6.3 实现前端代码
  • 七、实现登录界面
    • 7.1 约定好前后端交互接口
    • 7.2 实现 LoginServlet
    • 7.3 实现前端代码
  • 八、强制用户登录 (列表页和详情页)
    • 8.1 约定好前后端交互接口
    • 8.2 实现 LoginServlet
    • 8.3 实现前端代码
  • 九、实现显示用户信息
    • 9.1 约定好前后端交互接口
      • 9.1.1 博客列表页
      • 9.1.2 博客详情页
    • 9.2 实现 UserInfoServlet
    • 9.3 实现前端代码
      • 9.3.1 博客列表页
      • 9.3.2 博客详情页
  • 十、实现注销
    • 10.1 约定好前后端交互接口
    • 10.2 实现 LogoutServlet
    • 10.3 实现前端代码
  • 十一、实现发布博客
    • 11.1 约定好前后端交互接口
    • 11.2 实现 BlogServlet
    • 11.3 实现前端代码
  • 十二、删除博客
    • 12.1 约定好前后端交互接口
    • 12.2 修改 UserInfoServlet
    • 12.4 实现前端代码
    • 12.3 实现 BlogDeleteServlet

前言:页面展示

效果图:

登录页:
在这里插入图片描述

博客列表页:
在这里插入图片描述

博客详情页:
在这里插入图片描述

博客编辑页:
在这里插入图片描述

一、创建 Maven 项目

参考博客:https://blog.csdn.net/yyhgo_/article/details/128468738?spm=1001.2014.3001.5501
按步骤创建出 Maven 项目并引入依赖创建目录
(引入 Servlet 依赖、Jackson 依赖、mysql 依赖)

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.13.4</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>

    </dependencies>
</project>

二、设计数据库

本系统要存入博客文章的信息和用户的信息
1)创建博客表:

博客的 id,博客的标题,博客的内容,博客的日期,博文的博主 id

2)创建用户表:

用户 id 用户名 用户密码

在 main 目录下创建文件 db.sql:

create database if not exists yyhgo;

use yyhgo;

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 blog values(null, "这是第一篇博客", "从今天开始, 我要认真写代码", 1, '2022-11-24 20:00:00');
-- insert into blog values(null, "这是第二篇博客", "从今天开始, 我要认真写代码", 1, '2022-11-25 20:00:00');
-- insert into blog values(null, "这是第三篇博客", "从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码从今天开始, 我要认真写代码", 1, '2022-11-26 20:00:00');

-- insert into user values(null, "张三", "123");
-- insert into user values(null, "李四", "123");

同时在数据库中复制粘贴这段代码。

建议把 sql 写到一个文件中。后续如果需要把数据库往别的主机上部署,就可以直接复制粘贴建库建表语句完成 ~~

三、封装数据库的操作

把一会需要用到的数据库操作的 jdbc 代码封装起来,以备后用 ~~

创建包 model 用来存放数据库的代码!

3.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;

// 通过这个类, 来封装数据库建立连接/断开连接操作
// 懒汉模式要考虑线程安全问题~~
// Servlet 程序天然就是运行在多线程环境中的. 每个请求都可能对应着一个线程(Tomcat 是通过多线程的方式来处理很多请求)
public class DBUtil {
    private volatile static DataSource dataSource = null;

    private static DataSource getDataSource() {
        if (dataSource == null) {
            synchronized (DBUtil.class) {
                if (dataSource == null) {
                    dataSource = new MysqlDataSource();
                    ((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/yyhgo?characterEncoding=utf8&useSSL=false");
                    ((MysqlDataSource)dataSource).setUser("root");
                    ((MysqlDataSource)dataSource).setPassword("632yyh..");
                }
            }
        }
        return dataSource;
    }

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

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

3.2 创建 Blog 类

是实体类,代表一篇博客。

实体类:对应数据库表里的一条记录 ~~

package model;

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

// 这个类的对象, 表示一篇博客
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;
    }

    // 把这个方法魔改一下!! 在方法里面把时间戳构造成格式化时间. 以 String 的方式来返回.
    public String getPostTime() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return simpleDateFormat.format(this.postTime);
    }

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

细节:
在这里插入图片描述

3.3 创建 User 类

是实体类,代表一个用户。

package model;

// 这个类的对象表示一个用户
public class User {
    private int userId;
    private String username;
    private String password;
    private int isYourBlog = 0;

    public int getIsYourBlog() {
        return isYourBlog;
    }

    public void setIsYourBlog(int isYourBlog) {
        this.isYourBlog = isYourBlog;
    }

    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.4 创建类 BlogDao

主要是针对博客表进行增删改查操作。

Dao:Data Access Object
用来访问数据的类。这是习惯命名方式 ~

涉及到大量的 jdbc 操作,参考博客:https://blog.csdn.net/yyhgo_/article/details/128061324?spm=1001.2014.3001.5501

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;

// 针对博客要实现的功能:
// 1. 新增博客 (博客编辑页)
// 2. 查询出博客列表 (博客列表页)
// 3. 查询出指定博客的详情 (博客详情页)
// 4. 删除指定的博客 (可以在博客详情页中加入)
public class BlogDao {
    // 下列代码都是 JDBC 操作. 代码相似性非常高的!!

    // 此处的 Blog 对象是前端提交给后端的.
    public void insert(Blog blog) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            // 1. 先和数据库建立连接
            connection = DBUtil.getConnection();
            // 2. 构造 SQL 语句
            String sql = "insert into blog values(null, ?, ?, ?, now())";
            statement = connection.prepareStatement(sql);
            statement.setString(1, blog.getTitle());
            statement.setString(2, blog.getContent());
            statement.setInt(3, blog.getUserId());
            // 3. 执行 SQL
            int ret = statement.executeUpdate();
            if (ret == 1) {
                System.out.println("插入成功!");
            } else {
                System.out.println("插入失败!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, null);
        }
    }

    // 当前这个方法, 是给博客列表页使用的.
    // 博客列表页里面, 不需要显示博客的完整正文, 只需要有一小部分即可 (作为一个用来预览的摘要)
    public List<Blog> selectAll() {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        List<Blog> blogs = new ArrayList<>();
        try {
            // 1. 和数据库建立连接
            connection = DBUtil.getConnection();
            // 2. 构造 SQL
            // 排序:新插入的博客在上面!
            String sql = "select * from blog order by postTime desc";  
            statement = connection.prepareStatement(sql);
            // 3. 执行 SQL
            resultSet = statement.executeQuery();
            // 4. 遍历结果集合.
            while (resultSet.next()) {
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                String content = resultSet.getString("content");
                // 只截取前 100 个字符作为摘要. 注意! 此处的 100 是拍脑门的!!
                // 具体设置成几~~ 没关系, 只要最终的效果正确好看即可!
                if (content.length() > 100) {
                    content = content.substring(0, 100);
                }
                blog.setContent(content);
                blog.setUserId(resultSet.getInt("userId"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blogs.add(blog);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return blogs;
    }

    public Blog selectOne(int blogId) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            // 1. 和数据库建立连接
            connection = DBUtil.getConnection();
            // 2. 构造 SQL
            String sql = "select * from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1, blogId);
            // 3. 执行 SQL
            resultSet = statement.executeQuery();
            // 4. 遍历结果集. 由于是按照 blogId 来查询. blogId 是自增主键, 不能重复.
            //    此处的查询结果不可能是多条记录. 只能是 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.setPostTime(resultSet.getTimestamp("postTime"));
                blog.setUserId(resultSet.getInt("userId"));
                return blog;
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            // 5. 关闭资源
            DBUtil.close(connection, statement, resultSet);
        }

        return null;
    }

    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);
            int ret = statement.executeUpdate();
            if (ret == 1) {
                System.out.println("删除成功!");
            } else {
                System.out.println("删除失败!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, null);
        }
    }
}

请关注代码注释 ~~

3.5 创建类 UserDao

主要是针对用户表进行增删改查操作。

package model;

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

// 关于 User 表, 涉及到的操作
// 1. 根据用户名来查询用户信息(实现登录的时候)
// 2. 根据用户的 id 来查询用户信息 (获取文章的时候, 根据博客的 userId 拿到作者的信息)
public class UserDao {
    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 throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }

    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 throwables) {
            throwables.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }
}

四、导入准备好的前端代码

在这里插入图片描述

只要把前面的静态页面拷贝到项目的 webapp 目录中,就可以启动 tomcat 通过浏览器访问了 ~~

五、实现博客列表界面

5.1 约定好前后端交互接口

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

5.2 实现 BlogServlet

@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();
        List<Blog> blogs = blogDao.selectAll();
        String jsonString = objectMapper.writeValueAsString(blogs);
        resp.getWriter().write(jsonString);
    }
}

5.3 实现前端代码

在 blog_list.html 中 实现 ajax
注意:引入依赖 (jquery)

参考代码:

        <!-- 右侧内容详情 (参考) -->
        <div class="container-right">
            <!-- 用这个表示一篇博客 -->
            <!-- <div class="blog">
                <div class="title">这是第一篇博客</div>
                <div class="date">2022-09-19 12:00:00</div>
                <div class="desc">从今天起我要认真敲代码. Lorem ipsum dolor sit amet consectetur adipisicing elit. Incidunt amet nobis sed assumenda laboriosam nam ex cupiditate sequi ullam doloremque eius corporis, veritatis est ipsa nesciunt eveniet. Reprehenderit, error consequatur.</div>
                <a href="#">查看全文 &gt;&gt; </a>
            </div> -->
            
        </div>

实现:

    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script src="js/app.js"></script>
    <script>
        // 通过这个函数, 来从服务器获取到博客列表的数据
        function getBlogs() {
            $.ajax({
                type: 'get',
                url: 'blog',
                success: function(body) {
                    // 根据返回的 json 数据, 来构造出页面内容, div.blog
                    // jquery ajax 会自动的把响应得到的 body 按照响应的 Content-Type 进行转换格式. 
                    // 如果响应的 Content-Type 是 json, 此时就会自动把 body 转成 js 的对象
                    let container = document.querySelector('.container-right');
                    for (let blog of body) {
                        let blogDiv = document.createElement('div');
                        blogDiv.className = 'blog';
                        // 创建博客标题
                        let titleDiv = document.createElement('div');
                        titleDiv.className = 'title';
                        titleDiv.innerHTML = blog.title;
                        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 = '查看全文 >>';
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                        blogDiv.appendChild(a);
                        // 把 blogDiv 加入外层元素
                        container.appendChild(blogDiv);
                    }
                }
            });
        }

        // 获取博客列表
        getBlogs();
    </script>

六、实现博客详情界面

在这里插入图片描述

点击"查看全文",先让页面跳转到博客详情页 (blog_detail.html)。跳转的过程中,给 URL 带上当前要获取的博客 id。在 blog_detail.html 页面中,再通过 ajax 从服务器获取博客详情内容 ~~

在这里插入图片描述
前面设计跳转时,已经在 query string 里设置了 博客 id !!!

6.1 约定好前后端交互接口

在这里插入图片描述

6.2 实现 BlogServlet

约定请求的路径是 /blog,代码中当前已经有了一个 /blog 的 Servlet 了,就在之前的 Servlet 基础上做出修改即可!
基于同一个 Servlet,同一个 doGet 方法,让它既可以处理获取博客列表,又能获取博客详情:

  • 获取博客列表,请求是 /blog
  • 获取博客详情,请求是 /blog?blogld=1

在 doGet 里面就可以根据这个参数 是否存在 来决定 是返回博客列表还是博客详情!!!

@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");
        String blogId = req.getParameter("blogId");
        BlogDao blogDao = new BlogDao();
        if (blogId == null) {
            // 不存在 blogId 这个参数, 这就是获取博客列表.
            List<Blog> blogs = blogDao.selectAll();
            String jsonString = objectMapper.writeValueAsString(blogs);
            resp.getWriter().write(jsonString);
        } else {
            // 存在 blogId 参数, 就是获取博客详情.
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            String jsonString = objectMapper.writeValueAsString(blog);
            resp.getWriter().write(jsonString);
        }
    }
}

6.3 实现前端代码

参考代码:

        <!-- 右侧内容详情 -->
        <div class="container-right">
            <!-- 这个 div 里来放博客的内容 -->
            <div class="blog-content">
                <!-- 博客标题 -->
                <h3>我的第一篇博客</h3>
                <!-- 博客时间 -->
                <div class="date">2022-09-21 12:00:00</div>
                <!-- 博客正文 -->
                <div id="content" style="opacity: 80%">
                    
                </div>
            </div>
        </div>

实现:

    <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
    <script src="js/app.js"></script>
    <script>
        function getBlog() {
            $.ajax({
                type: 'get',
                url: 'blog' + location.search,
                success: function(body) {
                    // body 就是得到的一个 json 格式的 博客数据. 由于响应的 Content-Type 是 application/json
                    // 因此 jquery 就会自动把响应数据转成 js 对象. 
                    let h3 = document.querySelector('.blog-content h3');
                    h3.innerHTML = body.title;
                    let divDate = document.querySelector('.blog-content .date');
                    divDate.innerHTML = body.postTime;
                    // 刚才这里是直接把正文内容设置到 innerHTML 中了, 没有渲染的过程的. 
                    // let divContent = document.querySelector('#content');
                    // divContent.innerHTML = body.content;
                    // 靠谱的做法, 应该是先使用 editor.md 进行渲染. 
                    // [重要] 这个方法就是 editor.md 提供的一个方法把 markdown 字符串转成格式化的效果. 
                    // 第一个参数是一个 div 的 id, 表示要把渲染结果放到哪个 div 中. 
                    // 第二个参数是一个 js 对象, 把正文内容传入即可. (还支持很多别的参数属性, 此处暂时不涉及)
                    editormd.markdownToHTML('content', {
                        markdown: body.content
                    });
                }
            });
        }

        // 在页面加载之后, 要调用代码. 
        getBlog();
	</script>

细节:

1)url: 'blog' + location.search,
在 js 中,可以通过这样的方式得到当前页面的 query string,即 ?blogId=1
(location 是 js 中特殊的全局变量)

2)运行后在页面控制台会有这样的警告:
在这里插入图片描述
这里的 404 没事 ~ (favicon 是页面的图标,当前没这个图标就报错了)

类似于 b站 的:
在这里插入图片描述

3)博客编辑页是一个 markdown 格式的数据。就希望当获取到博客详情的时候,也能按照 markdown 来渲染!
1.在页面中引入 editor.md:
在这里插入图片描述
2.通过 editormd.markdownToHTML('content', { markdown: body.content });渲染。
(关注代码注释)

七、实现登录界面

7.1 约定好前后端交互接口

在这里插入图片描述

7.2 实现 LoginServlet

@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");
        if (username == null || password == null || username.equals("") || password.equals("")) {
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您当前的用户名或者密码为空!");
            return;
        }

        // 加上一个打印, 看看服务器读取的用户名密码到底是什么鬼!!
        System.out.println("username=" + username + ", password=" + password);

        // 2. 查询数据库, 看密码是否匹配
        UserDao userDao = new UserDao();
        User user = userDao.selectByName(username);
        if (user == null) {
            // 用户不存在.
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您的用户名或者密码错误!");
            return;
        }
        if (!user.getPassword().equals(password)) {
            // 密码错误!
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您的用户名或者密码错误!");
            return;
        }
        // 3. 登录成功之后, 构造会话.
        HttpSession session = req.getSession(true);
        // 把刚才获取到的 user 对象给存到 session 里, 方便后续使用.
        session.setAttribute("user", user);
        // 4. 返回一个重定向报文, 跳转到博客列表页.
        resp.sendRedirect("blog_list.html");
    }

7.3 实现前端代码

form 表单是可以搭配 302 进行跳转的!
如果是使用 ajax,其响应是不能处理 302,不会有跳转的 (需要使用别的方法来跳转)!

修改代码,放到 form 表单里面去!!!

    <!-- 构造一个页面版心 -->
    <div class="login-container">
        <div class="login-dialog">
            <form action="login" method="post">
                <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">
                    <input type="submit" value="提交" id="submit">
                </div>
            </form>
        </div>
    </div>

在这里插入图片描述

八、强制用户登录 (列表页和详情页)

在博客列表页 / 详情页里,访问页面的时候验证用户的登录状态。

  • 如果用户是已经登录了,自然允许访问;
  • 如果用户未登录,则强制跳转到博客登录页面!

我们登录之后,服务器会在内存中保存 session 对象 (维护了用户的信息)
一旦服务器重启,此时内存中的数据就没了,自然登录状态就丢失了!

8.1 约定好前后端交互接口

当用户进入博客列表 / 博客详情页的时候,先发起一个单独的 ajax 请求,通过这个请求来验证用户登录状态!
服务器这边根据登录 / 未登录,返回不同的结果。客户端就可以根据返回的响应,来决定是否要强制跳转到登录页!!!

一个页面是可以发送多个 ajax 的,想发几个都行 ~~

在这里插入图片描述

8.2 实现 LoginServlet

在原有 LoginServlet 的基础上添加代码!

@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");
        if (username == null || password == null || username.equals("") || password.equals("")) {
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您当前的用户名或者密码为空!");
            return;
        }

        // 加上一个打印, 看看服务器读取的用户名密码到底是什么鬼!!
        System.out.println("username=" + username + ", password=" + password);

        // 2. 查询数据库, 看密码是否匹配
        UserDao userDao = new UserDao();
        User user = userDao.selectByName(username);
        if (user == null) {
            // 用户不存在.
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您的用户名或者密码错误!");
            return;
        }
        if (!user.getPassword().equals(password)) {
            // 密码错误!
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("您的用户名或者密码错误!");
            return;
        }
        // 3. 登录成功之后, 构造会话.
        HttpSession session = req.getSession(true);
        // 把刚才获取到的 user 对象给存到 session 里, 方便后续使用.
        session.setAttribute("user", user);
        // 4. 返回一个重定向报文, 跳转到博客列表页.
        resp.sendRedirect("blog_list.html");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 验证登录状态.
        // 直接去取登录状态. 看能不能取到.
        HttpSession session = req.getSession(false);
        if (session == null) {
            // 未登录, 直接设置状态码 403 即可. body 都不需要~
            resp.setStatus(403);
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            // 未登录, 直接设置状态码 403 即可. body 都不需要~
            resp.setStatus(403);
            return;
        }
        // 已登录!
        resp.setStatus(200);
    }
}

8.3 实现前端代码

function checkLogin() {
    $.ajax({
        type: 'get',
        url: 'login',
        success: function (body) {
            // 200, 登录成功, 不必做任何处理. 
        },
        error: function () {
            // 403 就会触发 error
            // 强行跳转到登录页面. 
            location.assign('login.html');
        }
    });
}

location.assign()是前端代码实现页面跳转的方式!!!

博客列表页和详情页都需要去完成这样一件事,所以把代码写进 app.js 文件,放进 js 目录里 ~~
在 blog_list.html 和 blog_detail.html 文件中引入 app.js:

<script src="js/app.js"></script>

并且调用函数!:

        checkLogin();

ajax 是异步的:发起请求的主体,不负责接受结果,而是由别人主动推送过来!
所以多个 ajax 之间执行间隔极短,就可以近似看作是同时发送!
所以不必关注多个 ajax 之间的先后顺序!!!

九、实现显示用户信息

在这里插入图片描述
在博客列表页,和博客详情页都有用户信息。
这里的信息不要写死,而是能够从服务器动态获取!

  • 如果是博客列表页,此处显示当前登录的用户信息
  • 如果是博客详情页,此处显示文章的作者信息

图片的话一般是保存在一个单独的位置,在数据库里存图片的路径 ~~

9.1 约定好前后端交互接口

9.1.1 博客列表页

在这里插入图片描述

9.1.2 博客详情页

在这里插入图片描述

9.2 实现 UserInfoServlet

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

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String blogId = req.getParameter("blogId");
        if (blogId == null) {
            // 请求来自博客列表页, 直接返回登录的用户信息.
	        HttpSession session = req.getSession(false);
	        if (session == null) {
	            resp.setStatus(403);
	            resp.setContentType("text/html;charset=utf8");
	            resp.getWriter().write("当前未登录!");
	            return;
	        }
	        User user = (User) session.getAttribute("user");
	        if (user == null) {
	            resp.setStatus(403);
	            resp.setContentType("text/html;charset=utf8");
	            resp.getWriter().write("当前未登录!");
	            return;
	        }
            user.setPassword("");
            resp.setContentType("application/json; charset=utf8");
            String jsonString = objectMapper.writeValueAsString(user);
            resp.getWriter().write(jsonString);
        } else {
            // 请求来自博客详情页, 返回文章作者信息.
            BlogDao blogDao = new BlogDao();
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            if (blog == null) {
                resp.setStatus(403);
                resp.setContentType("text/html;charset=utf8");
                resp.getWriter().write("当前 blogId 有误!");
                return;
            }
            UserDao userDao = new UserDao();
            User author = userDao.selectById(blog.getUserId());
            if (author == null) {
                resp.setStatus(403);
                resp.setContentType("text/html;charset=utf8");
                resp.getWriter().write("当前博客对应的作者没有找到!");
                return;
            }
            author.setPassword("");    // 为了安全,隐藏密码~~
            resp.setContentType("application/json; charset=utf8");
            String jsonString = objectMapper.writeValueAsString(author);
            resp.getWriter().write(jsonString);
        }
    }
}

9.3 实现前端代码

参考代码:

        <!-- 左侧个人信息 -->
        <div class="container-left">
            <!-- 用这个 .card 来表示用户的信息 -->
            <div class="card">
                <img src="image/doge.jpg" alt="">
                <h3>小豪</h3>
                <a href="#">github 地址</a>
                <div class="counter">
                    <span>文章</span>
                    <span>分类</span>
                </div>
                <div class="counter">
                    <span>2</span>
                    <span>3</span>
                </div>
            </div>
        </div>

9.3.1 博客列表页

实现:

    <script>
        // 获取当前用户的信息
        function getUserInfo() {
            $.ajax({
                type: 'get',
                url: 'userInfo',
                success: function (body) {
                    // 让后端在查询失败的时候, 不要返回 200 , 而是返回 403 . 
                    // 避免在前端触发 success 分支. 
                    let h3 = document.querySelector('.card h3');
                    h3.innerHTML = body.username;
                }
            });
        }
        
        getUserInfo();
    </script>

9.3.2 博客详情页

实现:

    <script>
        // 获取用户信息
        function getUserInfo() {
            $.ajax({
                type: 'get',
                url: 'userInfo' + location.search,
                success: function (body) {
                    // 让后端在查询失败的时候, 不要返回 200 , 而是返回 403 . 
                    // 避免在前端触发 success 分支. 
                    let h3 = document.querySelector('.card h3');
                    h3.innerHTML = body.username;
                }
            });
        }

	    getUserInfo();
    </script>

十、实现注销

这个功能要做的工作:

  1. 清除当前用户的登录状态 (删除会话)
  2. 跳转到博客登录页

此处的注销就是退出登录。

10.1 约定好前后端交互接口

点击注销的时候,发送一个 GET 请求,并且跳转到博客登录页。

直接借助 a 标签 来实现!
a 标签 点击之后正好是发送了一个 GET 请求,也正好能触发页面跳转 ~~

在这里插入图片描述

10.2 实现 LogoutServlet

@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 注销要做的是删除用户的会话信息. 因此就得先确认用户有没有会话.
        // req 对象没有直接提供一个 删除会话 的操作~~
        // 删除会话有个办法, 就是把过期时间设置成 0. 比较麻烦.
        // 更简单的办法, 虽然保留会话对象, 但是把会话里的 user 给删了.
        HttpSession session = req.getSession(false);
        if (session == null) {
            // resp.setStatus(403);
            resp.sendRedirect("login.html");
            return;
        }
        session.removeAttribute("user");
        resp.sendRedirect("login.html");
    }
}

注销要做的是删除用户的会话信息,因此先确认用户有没有会话。
req 对象没有直接提供一个 删除会话 的操作!删除会话有个办法,就是把过期时间设置成 0,但比较麻烦。
更简单的办法:虽然保留会话对象,但是把会话里的 user 给删了!
在这里插入图片描述

10.3 实现前端代码

直接通过 a 标签 来进行实现,不需要加上任何的 ajax 请求!

    <!-- 导航栏 -->
    <div class="nav">
        <img src="image/logo2.jpg" alt="">
        <span class="title">我的博客系统</span>
        <!-- 这个 spacer 用来占位 -->
        <span class="spacer"></span>
        <!-- 来几个按钮 -->
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="logout">注销</a>  <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
    </div>

博客列表页、详情页、编辑页都进行修改!!!

十一、实现发布博客

11.1 约定好前后端交互接口

在这里插入图片描述

11.2 实现 BlogServlet

把请求中的博客数据拿到,同时写入数据库!

前面实现博客列表界面时,已经使用过 blog 路径,有了 BlogServlet 这个类 (doGet 方法)。
在这个类里添加 doPost 方法即可 ~~

@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");
        String blogId = req.getParameter("blogId");
        BlogDao blogDao = new BlogDao();
        if (blogId == null) {
            // 不存在 blogId 这个参数, 这就是获取博客列表.
            List<Blog> blogs = blogDao.selectAll();
            String jsonString = objectMapper.writeValueAsString(blogs);
            resp.getWriter().write(jsonString);
        } else {
            // 存在 blogId 参数, 就是获取博客详情.
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            String jsonString = objectMapper.writeValueAsString(blog);
            resp.getWriter().write(jsonString);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        // 1. 获取到用户的登录状态.
        HttpSession session = req.getSession(false);
        if (session == null) {
            resp.setStatus(403);
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            resp.setStatus(403);
            return;
        }
        // 2. 读取请求的内容
        String title = req.getParameter("title");
        String content = req.getParameter("content");
        if (title == null || title.equals("") || content == null || content.equals("")) {
            resp.setStatus(400);
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("请求中的标题或正文不完整");
            return;
        }
        // 3. 构造 Blog 对象, 并插入到数据库中.
        Blog blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        // 博客的作者. 作者是谁? 当前谁登录, 作者就是谁!!
        blog.setUserId(user.getUserId());
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);
        // 4. 插入成功之后, 跳转到博客列表页.
        resp.sendRedirect("blog_list.html");
    }
}

11.3 实现前端代码

这个地方可以使用 ajax,也可以使用 form 表单。
此处就使用 form 表单 即可 ~~

    <div class="blog-edit-container">
        <form action="blog" method="post" style="height: 100%">
            <!-- 标题编辑区 -->
            <div class="title">
                <input type="text" id="title" placeholder="请输入文章标题" name="title">
                <input type="submit" id="submit" value="发布文章">
            </div>
            <!-- 博客编辑器标签 -->
            <div id="editor">
                <!-- 需要在这里加上一个隐藏的 textarea -->
                <!-- 属于 editor.md 这个库要求的. -->
                <textarea name="content" style="display: none;" ></textarea>
            </div>
        </form>
    </div>

初始化编辑器代码也要稍作修改:

    <script>
        // 初始化编辑器
        var editor = editormd("editor", {
            // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. 
            width: "100%",
            // 设定编辑器高度
            height: "calc(100% - 50px)",
            // 编辑器中的初始内容
            markdown: "# 在这里写下一篇博客",
            // 指定 editor.md 依赖的插件路径
            path: "editor.md/lib/",
            // 加上这个属性, 效果就是把编辑器里的内容给自动保存到 textarea 里. 
            saveHTMLToTextArea: true,    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        });
    </script>

在这里插入图片描述

有时页面会触发缓存,这时候要强制刷新!Ctrl + F5

记得修改 form 元素的属性 (设置高度什么的 ~)

十二、删除博客

删除按钮可以放在列表页,也可以放在详情页 ~~
这里我们放在详情页。

  • 如果当前博客作者是登录用户自己,则在详情页导航栏中显示这个删除按钮;
  • 如果当前博客作者不是登录的用户,则不显示删除按钮!

所以此处要先根据当前用户的状态,来判断是否显示删除按钮!!!

12.1 约定好前后端交互接口

12.2 修改 UserInfoServlet

需要修改一下 UserInfoServlet 的 doGet 方法,来判断一下登录用户与博客作者 (从数据库中查) 的关系!

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

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String blogId = req.getParameter("blogId");

        // 先获取一下当前是哪个用户登录的
        HttpSession session = req.getSession(false);
        if (session == null) {
            resp.setStatus(403);
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前未登录!");
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            resp.setStatus(403);
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前未登录!");
            return;
        }

        if (blogId == null) {
            // 请求来自博客列表页, 直接返回登录的用户信息.
            user.setPassword("");
            resp.setContentType("application/json; charset=utf8");
            String jsonString = objectMapper.writeValueAsString(user);
            resp.getWriter().write(jsonString);
        } else {
            // 请求来自博客详情页, 返回文章作者信息.
            BlogDao blogDao = new BlogDao();
            Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
            if (blog == null) {
                resp.setStatus(403);
                resp.setContentType("text/html;charset=utf8");
                resp.getWriter().write("当前 blogId 有误!");
                return;
            }
            UserDao userDao = new UserDao();
            // author 是博客的作者
            User author = userDao.selectById(blog.getUserId());
            if (author == null) {
                resp.setStatus(403);
                resp.setContentType("text/html;charset=utf8");
                resp.getWriter().write("当前博客对应的作者没有找到!");
                return;
            }
            author.setPassword("");    // 为了安全,隐藏密码~~
            if (user.getUserId() == author.getUserId()) {
                author.setIsYourBlog(1);
            } else {
                author.setIsYourBlog(0);
            }
            resp.setContentType("application/json; charset=utf8");
            String jsonString = objectMapper.writeValueAsString(author);
            resp.getWriter().write(jsonString);
        }
    }
}

User 类中有一个 isYourBlog 属性 ~~
(使用 int 类型!若使用 boolean 类型可能会与属性名中的 “is” 产生一些冲突,会出问题)

12.4 实现前端代码

在这里复用一下这个接口:在这里插入图片描述
在响应中返回一个 当前博客是否是登录用户自己的文章,根据这个结果来判断是否显示删除按钮 ~~

参考代码:

    <!-- 导航栏 -->
    <div class="nav">
        <img src="image/logo2.jpg" alt="">
        <span class="title">我的博客系统</span>
        <!-- 这个 spacer 用来占位 -->
        <span class="spacer"></span>
        <!-- 来几个按钮 -->
        <a href="blog_list.html">主页</a>
        <a href="blog_edit.html">写博客</a>
        <a href="logout">注销</a>
        <!-- 在这里加一个 删除 按钮 -->
        
    </div> 

实现:

    <script>
        // 获取用户信息
        function getUserInfo() {
            $.ajax({
                type: 'get',
                url: 'userInfo' + location.search,
                success: function (body) {
                    // 让后端在查询失败的时候, 不要返回 200 , 而是返回 403 . 
                    // 避免在前端触发 success 分支. 
                    let h3 = document.querySelector('.card h3');
                    h3.innerHTML = body.username;

                    if (body.isYourBlog) {
                        // 在导航栏中加个按钮, 用来删除文章. 
                        let deleteA = document.createElement('a');
                        // location.search 就是当前页面 url 的 query string, 也就是
                        // ?blogId=1 这样的结果. 
                        deleteA.href = 'blogDelete' + location.search;
                        deleteA.innerHTML = '删除';

                        let navDiv = document.querySelector('.nav');
                        navDiv.appendChild(deleteA);
                    }
                }
            });
        }
        
        getUserInfo();
    </script>

JavaScript 是弱类型。number 和 boolean 是可以相互转换的!不像 Java 要求的严格 ~
因此使用if (body.isYourBlog) {判断完全可以,因为 非0 就为 true !!!

12.3 实现 BlogDeleteServlet

实现具体的删除动作。

前端代码已经完成了,成功运行后会添加一个 a 标签 (删除)!
点击 “删除” 按钮,即发送一个 GET 请求 (a 标签跳转) ~~
处理这个请求!!!
在这里插入图片描述

@WebServlet("/blogDelete")
public class BlogDeleteServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 先判定用户的登录状态, 如果用户未登录, 就不给删除
        HttpSession session = req.getSession(false);
        if (session == null) {
            resp.setStatus(403);
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            resp.setStatus(403);
            return;
        }
        // 2. 获取到 blogId
        String blogId = req.getParameter("blogId");
        if (blogId == null || blogId.equals("")) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("要删除的文章不存在!!");
            return;
        }
        // 3. 删除数据库中的数据
        BlogDao blogDao = new BlogDao();
        blogDao.delete(Integer.parseInt(blogId));
        // 4. 跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }
}

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

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

相关文章

excel函数应用:如何用数位函数分段提取身份证信息 上篇

用Excel处理身份证号&#xff0c;在我们日常工作中是相当普遍的&#xff0c;尤其是对于做人事行政工作、财务工作的同学来说&#xff0c;更显得十分重要。那么一个身份证号&#xff0c;能给予我们多少信息量呢&#xff1f;无论我们需要用Excel处理何种数据&#xff0c;首先都应…

python 使用矢量化替换循环

介绍 &#x1f3b5;&#x1f57a;&#x1f5e3;&#x1f3c0; 循环自然而然地出现在我们身边&#xff0c;我们了解几乎所有编程语言中的循环。因此&#xff0c;默认情况下&#xff0c;只要有重复操作&#xff0c;我们就会开始执行循环。但是当我们处理大量迭代&#xff08;数百…

5G NR标准 第14章 调度

第14章 调度 NR 本质上是一个调度系统&#xff0c;这意味着调度器决定何时以及向哪些设备分配时间、频率和空间资源&#xff0c;以及使用什么传输参数&#xff0c;包括数据速率。 调度可以是动态的或半静态的。 动态调度是基本的操作模式&#xff0c;其中调度程序针对每个时间…

【JVM 从入门到精通系列】 JVM 字节码指令篇 之 Class文件结构

一、概述 字节码文件的跨平台性 Java语言&#xff1a;跨平台的语言 当Java源代码成功编译成字节码后&#xff0c;如果想在不同平台上运行&#xff0c;则无需再次编译。这个优势已经不再那么吸引人了&#xff0c;Python、PHP、Perl、Ruby、Lisp等有强大的编译器。跨平台似乎已…

uniprot蛋白序列数据库,蛋白质结构数据库PDB;pymol pse格式

https://www.bilibili.com/video/BV1p34y1D77Z https://www.bilibili.com/video/BV1Xa4y1W7Dx 蛋白质结构数据库PDB 注意点&#xff1a;很多数据含有共晶配体的结构 很多时候&#xff0c;蛋白晶体结构中不只是蛋白&#xff0c;还可能有核酸、多肽、辅酶、小分子化合物&#…

振动力学——2.单自由度系统无阻尼自由振动能量法

对于不计阻尼即认为没有能量损失的单自由度系统&#xff0c;可利用能量守恒原理建立自由振动微分方程&#xff0c;或直接求出固有频率无阻尼系统为保守系统&#xff0c;其机械能守恒&#xff0c;即动能T和势V之和保持不变 &#xff0c;即&#xff1a; 或 (1-9) 图1-7弹簧质量…

Clickhouse 三节点三分片六实例双副本部署,用户密码权限配置,cpu内存资源优化

文章目录1. rpm安装ck2. 集群规划3. config.xml文件配置&#xff08;1&#xff09;分片副本信息配置&#xff08;2&#xff09;zookeeper信息配置&#xff08;3&#xff09;macros 信息配置&#xff08;4&#xff09;注释掉映射信息&#xff08;5&#xff09;修改实例中的日志路…

深入理解MySQL——master thread分析

1. master thread的线程分析 master thread的线程优先级别最高。其内部由几个循环&#xff08;loop&#xff09;组成&#xff1a;主循环&#xff08;loop&#xff09;、后台循环&#xff08;background loop&#xff09;、刷新循环&#xff08;flush loop&#xff09;、暂停循…

基于springcloud的学习笔记1

概述springcloud的微服务分布式架构对于springboot的服务集成开发最大的优点就是解决了&#xff0c;springboot中模块之间的高耦合度&#xff0c;springcloud进行高粒度的拆分服务之后就可以降低在高并发下会出现的所有模块服务不可用。同理springcloud就是拆分出不同的模块成为…

Window 环境 安装 mycli

Window 环境 安装 Mycli 平时都用 图形化界面操作MySQL 如 navicat, workbench. 为了更专业一点也锻炼一下动手能力&#xff0c;现在打算换成命令行的方式操作。了解到 myclli这个工具。方便体验&#xff0c;就先在window环境装一个玩玩。 mycli 是一个 MySQL 命令行客户端工具…

【LeetCode每日一题】——50.Pow(x, n)

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 数学 二【题目难度】 中等 三【题目编号】 50.Pow(x, n) 四【题目描述】 实现 pow(x,n)pow(x…

Exchange漏洞分析:SSRF RCE

0x00 前言 在今年3月份&#xff0c;微软公布了多个Microsoft Exchange的高危漏洞。ProxyLogon是Exchange历史上最具影响力的漏洞之一&#xff0c;有上千台Exchange服务器被植入了webshell后门。 0x01 漏洞描述 CVE-2021-26855是一个SSRF漏洞&#xff0c;利用该漏洞可以绕过E…

一文搞定Nginx的压缩、黑白名单、防盗链、零拷贝、跨域、双机热备等知识

引言早期的业务都是基于单体节点部署&#xff0c;由于前期访问流量不大&#xff0c;因此单体结构也可满足需求&#xff0c;但随着业务增长&#xff0c;流量也越来越大&#xff0c;那么最终单台服务器受到的访问压力也会逐步增高。时间一长&#xff0c;单台服务器性能无法跟上业…

2020网络安全投融资趋势报告

声明 本文是学习2020网络安全投融资趋势报告. 下载地址 http://github5.com/view/55012而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 物联网安全&#xff1a;5G的商业化推动物联网安全加速落地 在应用安全领域&#xff0c;本文共收录投融资事件13起…

IOT云平台 simple(6)springboot netty实现IOT云平台基本的架构(mqtt、Rabbitmq)

本系列教程包括&#xff1a; IOT云平台 simple&#xff08;0&#xff09;IOT云平台简介 IOT云平台 simple&#xff08;1&#xff09;netty入门 IOT云平台 simple&#xff08;2&#xff09;springboot入门 IOT云平台 simple&#xff08;3&#xff09;springboot netty实现TCP Se…

告别Whitelabel Error Page!

相信在JavaWeb开发中不少小伙伴会遇到这个页面吧&#xff0c;特别是初学者基础不扎实不牢固然后网上说的一大堆莫名其妙的解法&#xff0c;千万不要盲目跟着改&#xff0c;建议多读几篇博客&#xff0c;再根据自己的知识分析一下开发流程。首先status404&#xff0c;肯定是我访…

Unity联网多人游戏技术方案调研

关于联网方案 Listen Server (Host) 和 Relay转发服务器游戏包同时包含客户端和服务端逻辑&#xff0c;联网时一个客户端开主&#xff0c;称为Host&#xff0c;其他客户端连入。局域网和互联网都支持。互联网需要有一个匹配服务器帮助找到不同人建立的主机。如果不使用Relay服…

校招前端二面常考react面试题(边面边更)

高阶组件 高阶函数&#xff1a;如果一个函数接受一个或多个函数作为参数或者返回一个函数就可称之为高阶函数。 高阶组件&#xff1a;如果一个函数 接受一个或多个组件作为参数并且返回一个组件 就可称之为 高阶组件。 react 中的高阶组件 React 中的高阶组件主要有两种形式…

verilog学习笔记- 6)verilog基础知识

目录 Verilog 的逻辑值: Verilog 的标识符&#xff08;类似C中的变量名&#xff09;: 1) 定义: 2) 规范建议: Verilog 的数字进制格式: Verilog 的数据类型: 1) 寄存器类型&#xff1a; 2) 线网类型&#xff1a; 3) 参数类型&#xff1a; Verilog 的运算符&#xff1a…

Logistic Regression 逻辑斯蒂回归

文章目录5、Logistic Regression 逻辑斯蒂回归5.1 回归任务5.1.1 MNIST Dataset5.1.2 CIFAR-10 Dataset5.2 Regression vs Classification 回归 vs 分类5.3 Sigmoid functions5.3.1 Logistic Function [0, 1]5.3.2 Other Functions [-1, 1]5.4 Model 模型5.5.1 torch.sigmoid()…