个人博客系统(SSM版 前端+后端)

news2024/10/7 14:24:00

前言

        在学习Servlet的时候,也写了一个博客系统,主要的就是使用servelet加Tomcat进行实现的,而这个项目 仅仅适合去学习Web项目开发的思想,并不满足当下企业使用框架的思想,进行学习过Spring,Spring Boot,Spring MVC以及MyBatis之后,我们就可以对之前的项目使用SSM框架的形式进行升级,然后对相应的功能进行升级,帮助我们对企业的开发流程进行了解,文章最后会有本项目的Gitee地址,欢迎访问!!💕💕💕


 

下图对此项目对照之前的版本进行了项目升级的总结.

 那话不多说啦,开始我们的新项目.


 

目录

前言

​编辑

1. 前期项目准备

1.1 创建项目

1.2 准备项目

a. 删除项目中无用的文件和目录

b. 引入前端页面     

c. 初始化数据库

d. 项目分层

2. 添加统一的返回格式

3. 配置登录拦截器

4. 实现用户的注册功能

4.1 编写前端Ajax请求代码 

4.2 编写后端代码

a. 用户信息类UserInfo.class

b. Mapper层(UserMapper)

c. MyBatis的xml文件的构造

d. 服务层Service

e. Controller层

4.3 测试功能

5. 实现用户的登录功能

5.1 编写前端Ajax请求代码 

5.2 编写后端代码

a. Mapper层(UserMapper)

b. MyBatis的xml文件的构造

c. service层

d. Controller层

5.3 测试功能 

6. 个人主页展示功能

6.1 左侧个人信息

6.1.1 编写前端Ajax请求代码 

6.1.2 编写后端代码

a. Mapper层(UserMapper)

b. MyBatis的xml文件的构造

c. service层

d. Controller层

6.1.3 测试功能 

6.2 右侧个人博客列表信息 

6.2.1 编写前端Ajax请求代码 

6.2.2 编写后端代码

a. Mapper层(ArticleMapper)

b. MyBatis的xml文件的构造

c. service层

d. Controller层

6.3 删除博客功能 

6.3.1 编写前端Ajax请求代码 

6.3.2 编写后端代码

a. Mapper 层(ArticleMapper)

b. MyBatis的xml文件的构造

c. service层

d. Controller层

6.3.3 测试功能

 6.4 查看全文功能 

6.4.1 编写前端Ajax请求代码 

6.4.2 编写后端代码

a. Mapper层(ArticleMapper)

b. MyBatis的xml文件的构造

c. service层

d. Controller层

6.4.3 测试功能

6.5 修改博客功能 

6.5.1 编写前端Ajax请求代码 

6.5.2 编写后端代码

a. Mapper层(ArticleMapper)

b. MyBatis的xml文件的构造

c. service层

d. Controller层

6.5.3 测试功能

6.6 发布博客功能

6.6.1 编写前端Ajax请求代码 

6.6.2 编写后端代码

a. Mapper层(ArticleMapper)

b. MyBatis的xml文件的构造

c. service层

d. Controller层

6.6.3 功能测试

6.7 注销用户功能

6.7.1 编写前端Ajax请求代码 

6.7.2 编写后端代码

a. Mapper层(ArticleMapper)

b. MyBatis的xml文件的构造

c. service层

d. Controller层

6.7.3 功能测试

7. 博客主页功能实现

7.1 主页的导航栏

7.1.1 编写前端Ajax请求代码 

7.1.2 编写后端代码

7.2 分页功能  

8. 密码使用加盐的算法进行加密

9. Session持久化到Redis

结语


1. 前期项目准备

1.1 创建项目

第一步 : 创建SSM项目 

 

 第二步: 添加项目依赖

 

1.2 准备项目

a. 删除项目中无用的文件和目录

b. 引入前端页面     

(这里就不打算总结前端页面设计的具体代码了,我们把重点放在后端是实现以及前端与后端交互.)(具体的前端代码,我会将此代码放在项目路径下面,然后大家根据下面的步骤进行下载就可以了).

前端代码在项目中路径,大家将有关于前端的代码放在resources/static下面就可以了

c. 初始化数据库

我使用的MySQL版本是5.7版本,下面是建库建表的sql

-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;

-- 使⽤数据数据
use mycnblog;

-- 创建表[⽤户表]
drop table if exists userinfo;
create table userinfo(
    id int primary key auto_increment,
    username varchar(100) not null,
    password varchar(32) not null,
    photo varchar(500) default '',
    createtime timestamp default current_timestamp,
    updatetime timestamp default current_timestamp,
    `state` int default 1
) default charset 'utf8mb4';

alter table userinfo modify password varchar(65);

-- 创建⽂章表
drop table if exists articleinfo;
create table articleinfo(
    id int primary key auto_increment,
    title varchar(100) not null,
    content text not null,
    createtime datetime default CURRENT_TIMESTAMP,
    updatetime datetime default CURRENT_TIMESTAMP,
    uid int not null,
    rcount int not null default 1,
    `state` int default 1
)default charset 'utf8mb4';

-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
    vid int primary key,
    `title` varchar(250),
    `url` varchar(1000),
    createtime timestamp default current_timestamp,
    updatetime timestamp default current_timestamp,
    uid int
)default charset 'utf8mb4';
alter table userinfo add unique(username);
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`,`createtime`, `updatetime`, `state`) 
VALUES(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);

-- ⽂章添加测试数据
insert into articleinfo(title,content,createtime,updatetime,uid) values('Java','Java正文','2021-12-06 17:10:48', '2021-12-06 17:10:48',1);
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
insert into articleinfo(title,content,uid) values('Java','Java正文',1);

d. 项目分层

我们按照统一的格式,将项目进行分层操作

 

e. 项目的配置文件application.yml

# 配置数据库的连接字符串
spring:
#  jackson:
#    date-format: yyyy-MM-dd HH:mm:ss      # 设置时间的格式
#    time-zone: GMT+8               # 设置地域
  datasource:
    url: jdbc:mysql://127.0.0.1/mycnblog?characterEncoding=utf8
    username: root
    password: 111111
    driver-class-name: com.mysql.cj.jdbc.Driver
server:
  port: 8800
# 设置 Mybatis 的 xml 保存路径
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置打印 MyBatis 执行的 SQL
logging:
  level:
    com:
      example:
        demo: debug

至此,我们项目的前期准备就完成了,接下来我们将一一进行实现功能.

2. 添加统一的返回格式

        为了与前端更方便更有效率的进行交互,我们可以规定好统一的返回格式给前端,使用了SpringAOP的思想,在项目的common文件夹中创建一个类为AjaxResult.class.我们返回给前端的数据格式为Json格式的数据,主要包括了状态码,状态码描述信息,以及返回的数据.具体代码如下.

具体分为两个方法,一个是访问成功,一个是失败.然后对这两个方法进行重载.

package com.example.demo.common;

import lombok.Data;
import org.springframework.stereotype.Component;

import java.io.Serializable;

/**
 * Created with IntelliJ IDEA.
 * Description:普通的对象,统一的返回格式
 * User: YAO
 * Date: 2023-07-17
 * Time: 19:17
 */
@Data
public class AjaxResult implements Serializable {
    // 1.状态码
    private Integer code;

    // 2.状态码描述信息
    private String msg;

    // 3.返回的数据
    private Object data;

    /**
     * 1. 操作成功返回的结果
     */
    public static AjaxResult success(Object data){
        AjaxResult result = new AjaxResult();
        result.setCode(200);
        result.setMsg("");
        result.setData(data);
        return result;
    }
    public static AjaxResult success(int code,Object data){
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg("");
        result.setData(data);
        return result;
    }

    public static AjaxResult success(String msg,int code,Object data){
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }

    /**
     * 2. 返回失败结果
     */
    public static AjaxResult fail(int code,String msg){
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(null);
        return result;
    }

    public static AjaxResult fail(String msg,int code,Object data){
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}

为了保证最后的返回格式的正确性,我们最后对数据的返回的格式进行验证.在config文件目录下创建返回格式的保底类:ResponseAdvice,对特殊的String类型进行处理. 

package com.example.demo.config;

import com.example.demo.common.AjaxResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * Created with IntelliJ IDEA.
 * Description:统一返回格式,最保底的返回数据的格式
 *    说明:在返回数据之前,检测数据的类型是否为统一的对象,如果不是,封装成统一的数据格式
 * User: YAO
 * Date: 2023-07-17
 * Time: 19:28
 */
@ControllerAdvice // 统一返回格式
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 只有返回为true的时候,才会调用supports方法
     * @param returnType
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    /**
     * 对数据格式进行校验和封装
     * @param body
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @SneakyThrows   // 抛出异常
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof AjaxResult) {
            return body;
        }
        if (body instanceof String) {
            // String 比较特殊需要单独处理
            // 使用Jackson封装成成功返回的对象的格式
            return objectMapper.writeValueAsString(AjaxResult.success(body));
        }
        return AjaxResult.success(body);
    }
}

3. 配置登录拦截器

我们对有些系统功能进行捕获用户的登录状态,实现必须登录之后才能进行使用功能.在config的文件目录下创建LoginInterceptor.class.实现HandlerInterceptor接口,重写preHandle方法.获取当前登录的用户的SESSION,判断是否为空,为空就重定向到登录页面,然后将自定义好的登录拦截器进行配置到项目中(添加注解@Configuration,实现接口WebMvcConfigurer,重写addInterceptors方法)具体代码如下:

1.创建全局变量的 USER_SESSION_KEY(SESSION的key值)

package com.example.demo.common;

/**
 * Created with IntelliJ IDEA.
 * Description:全局变量
 * User: YAO
 * Date: 2023-07-18
 * Time: 10:58
 */
public class AppVariable {
    // 全局变量,session的key值
    public static final String USER_SESSION_KEY = "USER_SESSION_KEY";
    // 所有用户的key值是一样的但是内容是不一样的.就相当于大家都用的是钥匙但是钥匙的类型是不一样的.
    // 这里只是一个统一的格式,或者是同一种规格
}

2. 自定义拦截器 

package com.example.demo.config;

import com.example.demo.common.AppVariable;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Created with IntelliJ IDEA.
 * Description:自定义登录功能呢的拦截器
 * User: YAO
 * Date: 2023-07-18
 * Time: 11:02
 */
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * true --> 用户已经登录
     * false --> 用户未登录,跳转到登录页
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute(AppVariable.USER_SESSION_KEY) != null){
            // 用户已经登录
            return true;
        }
        // 用户未登录,重定向到登录界面
        response.sendRedirect("/login.html");
        return false;
    }
}

3. 将自定义拦截器加入到项目的配置中.(具体的拦截界面,大家先不要全部添加,可以随着项目的功能的实现,对指定的页面和路由进行拦截.)

package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Created with IntelliJ IDEA.
 * Description:项目的配置文件
 * User: YAO
 * Date: 2023-07-18
 * Time: 11:07
 */
@Configuration
public class AppConfig implements WebMvcConfigurer {

    /**
     * 1. 配置自定义的拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/img/**")
                .excludePathPatterns("/js/**")
                .excludePathPatterns("/editor.md/**")

                .excludePathPatterns("/login.html")
                .excludePathPatterns("/blog_content.html")
                .excludePathPatterns("/blog_list.html")
                .excludePathPatterns("/reg.html")

                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/reg")
                .excludePathPatterns("/art/detail")
                .excludePathPatterns("/user/getuserbyid")
                .excludePathPatterns("/art/incr-rcount")
                .excludePathPatterns("/art/add")
                .excludePathPatterns("/art/listbypage")
                .excludePathPatterns("/user/loginstate");
    }
}

4. 实现用户的注册功能

此处我们就用到了Servlet的和前端交互的思想了.以下所有的功能实现就是下面这两步:

  • 前端向用户发送Ajax请求
  • 后端接收请求,进行响应 

4.1 编写前端Ajax请求代码 

reg.html文件中进行补充代码

我们对这个提交的按钮进行构造请求 

<script>
        // 1.提交注册时间(jQuery进行操作)
        function mysub(){
            // 1.1 非空校验
            var username = jQuery("#username");
            var password = jQuery("#password");
            var password2 = jQuery("#password2");
            if(username.val() == ""){
                alert("请输入用户名");
                username.focus(); // 将鼠标光标设置到指定位置
                return;
            }
            if(password.val() == ""){
                alert("请输入密码");
                password.focus();
                return;
            }
            if(password2.val() == ""){
                alert("请输入确认密码");
                password2.focus();
                return;
            }
            // 1.2 判断两次密码是否一致
            if(password.val() != password2.val()){
                alert("两次输入的密码不一致请检查");
                password.focus();
                return;
            }
            // 1.3 Ajax提交请求
            jQuery.ajax({
                url:"/user/reg",
                type:"post",
                data:{"username":username.val(),"password":password.val()},
                // 回调函数,请求成功得到响应.
                success:function(result){
                    // 判断响应的结果,状态码200 data:修改返回值1
                    if(result != null && result.code == 200 && result.data == 1){
                        // 执行成功 
                        if(confirm("注册成功!是否跳转登录页面?")){
                            location.href="/login.html";
                        }
                    }else{
                        alert("执行失败,请稍后再试");
                    }
                }
            })
        }
    </script>

 具体的注册页面

4.2 编写后端代码

因为要对数据库中的用户表进行操作,所以后段要对用户表进行创建对象.

a. 用户信息类UserInfo.class

package com.example.demo.entity;

import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * Created with IntelliJ IDEA.
 * Description:用户表对象
 * User: YAO
 * Date: 2023-07-18
 * Time: 9:24
 */
@Data
public class UserInfo implements Serializable {
    private Integer id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private Integer state;
}

b. Mapper层(UserMapper)

@Mapper
public interface UserMapper {
    /**
     * 1.注册功能
     */
    int reg(UserInfo userInfo);
}

c. MyBatis的xml文件的构造

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ArticleMapper">

    <select id="getArtCountByUid" resultType="java.lang.Integer">
        select count(*) from articleinfo where uid=#{uid}
    </select>

</mapper>

d. 服务层Service

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    /**
     * 1. 注册功能
     */
    public int reg(UserInfo userInfo){
        return userMapper.reg(userInfo);
    }
}

e. Controller层

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @Autowired
    private ArticleService articleService;

    @RequestMapping("/reg")
    public AjaxResult reg(UserInfo userInfo){
        // 1. 进行非空校验和参数的有效性
        if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())||
                !StringUtils.hasLength(userInfo.getPassword())){
            return AjaxResult.fail(-1,"非法参数");
        }
        // 2.密码加密
        String olderPassword = userInfo.getPassword();
        // 加密
        userInfo.setPassword(PasswordUtils.encrypt(olderPassword));
        // 2.返回成功的修改值
        return AjaxResult.success(userService.reg(userInfo));
    }
}

后端代码完成

4.3 测试功能

注册小白用户 

数据库用户展示(密码是经过加盐加密的,后面会讲到这一点)

我们的注册功能就结束了.

5. 实现用户的登录功能

         用户登录功能与注册功能逻辑差不多,都是先进行前端 提交ajax数据请求给后端,在进行效验,其中后端进行数据库查询判断用户名或者密码是否存在且正确。

5.1 编写前端Ajax请求代码 

 对提交按钮进行方法的实现

<script>
        function mysub(){
            // 1. 非空校验
            var username = jQuery("#username");
            var password = jQuery("#password");
            if(username.val() == ""){
                alert("请输入用户名!");
                username.focus();
                return;
            }
            if(password.val() == ""){
                alert("请输入密码!");
                password.focus();
                return;
            }
            // 2. 构造ajax请求
            jQuery.ajax({
                url:"/user/login",
                type:"post",
                data:{"username":username.val(),"password":password.val()},
                success: function(result){
                    if(result != null && result.code == 200 && result.data != null){
                        // 执行成功 
                        location.href="/myblog_list.html";
                    }else{
                        alert("用户名或密码错误,请重新登录!");
                    }
                }
            })
        }
    </script>

5.2 编写后端代码

a. Mapper 层(UserMapper)

    /**
     * 2.登录功能
     */
    UserInfo getUserByName(@Param("username") String username);

b. MyBatis的xml文件的构造

<!--    2. 登录功能-->
    <select id="getUserByName" resultType="com.example.demo.entity.UserInfo">
        select * from userinfo where username=#{username}
    </select>

c. service层

    /**
     * 2. 根据名字查询用户信息
     */
    public UserInfo getUserByName(String username){
        return userMapper.getUserByName(username);
    }

d. Controller层

(其中代码加入了密码加盐加密的实现,这个具体后面再将)

@RequestMapping("/login")
    public AjaxResult login(HttpServletRequest request,String username, String password){
        System.out.println(username + " " + password);
        // 1. 进行非空校验和参数的有效性
        if (!StringUtils.hasLength(username)|| !StringUtils.hasLength(password)){
            return AjaxResult.fail(-1,"非法参数");
        }
        // 2.查询数据库
        UserInfo userInfo = userService.getUserByName(username);
        if (userInfo != null && userInfo.getId() > 0){
            // 使用加盐的验证
            if (PasswordUtils.check(password,userInfo.getPassword())){
                // 登陆成功
                // 将用户存储带到Session中
                HttpSession session = request.getSession();
                session.setAttribute(AppVariable.USER_SESSION_KEY,userInfo);
                // 返回之前将密码等信息进行隐藏
                userInfo.setPassword("");
                return AjaxResult.success(userInfo);
            }
        }
        return AjaxResult.success(0,null);
    }

5.3 测试功能 

 跳转到登录页

6. 个人主页展示功能

6.1 左侧个人信息

6.1.1 编写前端Ajax请求代码 

myblog_list.html文件进行补充

这里仅仅对名字和文章数量进行动态展示,其他的标签就没实现,.但是实现的原理都是一样的,后续会做扩展.

<script>
        function showInfo(){
            jQuery.ajax({
                url:"/user/showinfo",
                type:"get",
                data:{},
                success: function(result){
                    if(result != null && result.code==200){
                        jQuery("#username").text(result.data.username);
                        jQuery("#artCount").text(result.data.artCount);
                    }else{{
                        alert("当前用户信息加载失败")
                    }}
                } 
            })
        }
        showInfo();
</script>

6.1.2 编写后端代码

因为要进行获取当前登录用户的信息,所以我们得实现一个得到当前用户的方法

我们在common文件目录下创建一个类UserSessionUtils实现用户登录的相关操作

实现方法getSessionUser()

package com.example.demo.common;

import com.example.demo.entity.UserInfo;
import com.example.demo.entity.vo.UserInfoVo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * Created with IntelliJ IDEA.
 * Description:当前登录用户相关的操作
 * User: YAO
 * Date: 2023-07-18
 * Time: 14:28
 */
public class UserSessionUtils {
    /**
     * 1. 得到当前的登录用户
     * @param request
     * @return
     */
    public static UserInfo getSessionUser(HttpServletRequest request){
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute(AppVariable.USER_SESSION_KEY) != null){
            // 当前用户已经正常登录
            return (UserInfo) session.getAttribute(AppVariable.USER_SESSION_KEY);
        }
        return null;
    }
}

a. Mapper 层(UserMapper)

/**
     * 3.根据id返回用户信息
     */
    UserInfo getUserById(@Param("id") Integer id);

b. MyBatis的xml文件的构造

<select id="getUserById" resultType="com.example.demo.entity.UserInfo">
        select * from userinfo where id=#{id}
    </select>

c. service层

/**
     * 3.根据id返回用户信息
     */
    public UserInfo getUserById(Integer id){
        return userMapper.getUserById(id);
    }

d. Controller层

@RequestMapping("/showinfo")
    public AjaxResult showinfo(HttpServletRequest request){
        UserInfoVo userInfoVo = new UserInfoVo();
        // 1. 得到当前登录用户
        UserInfo userInfo = UserSessionUtils.getSessionUser(request);
        if (userInfo == null){
            return AjaxResult.fail(-1,"非法请求");
        }
        // 将获得对象赋值给UserinfoVo
        BeanUtils.copyProperties(userInfo,userInfoVo);// Spring 提供的深拷贝的方法
        // 2. 得到用户发表文章的总数
        userInfoVo.setArtCount(articleService.getArtCountByUid(userInfo.getId()));
        return AjaxResult.success(userInfoVo);
    }

6.1.3 测试功能 

6.2 右侧个人博客列表信息 

6.2.1 编写前端Ajax请求代码 

myblog_list.html文件进行补充

 <script>
        function showInfo(){
            jQuery.ajax({
                url:"/user/showinfo",
                type:"get",
                data:{},
                success: function(result){
                    if(result != null && result.code==200){
                        jQuery("#username").text(result.data.username);
                        jQuery("#artCount").text(result.data.artCount);
                    }else{{
                        alert("当前用户信息加载失败")
                    }}
                } 
            })
        }
        showInfo();

        function getMyArtList(){
                jQuery.ajax({
                    url:"/art/mylist",
                    type:"POST",
                    data:{},
                    success:function(result){
                        if(result!=null && result.code==200){
                           // 有两种情况,一种是发表了文章,一种是没有发表任何文章 
                           if(result.data!=null && result.data.length>0){
                            // 此用户发表文章了
                            var artListDiv ="";
                            for(var i=0;i<result.data.length;i++){
                                var artItem = result.data[i];
                                artListDiv += '<div class="blog">';
                                artListDiv += '<div class="title">'+artItem.title+'</div>';
                                artListDiv += '<div class="date">'+artItem.updatetime+'</div>';
                                artListDiv += '<div class="desc">';
                                artListDiv += artItem.content;
                                artListDiv += '</div>';
                                artListDiv += '<a href="blog_content.html?id='+
                                    artItem.id + '" class="detail">查看全文 &gt;&gt;</a>&nbsp;&nbsp;';
                                artListDiv += '<a href="blog_edit.html?id='+
                                    artItem.id + '" class="detail">修改 &gt;&gt;</a>&nbsp;&nbsp;';  
                                artListDiv += '<a href="javascript:myDel('+
                                    artItem.id+');" class="detail">删除 &gt;&gt;</a>'; 
                                artListDiv += '</div>';    
                            }
                            jQuery("#artDiv").html(artListDiv);
                           }
                        }else{
                            alert("查询文章列表出错,请重试!");
                        }
                    }
                });
        }
        getMyArtList();
</script>

6.2.2 编写后端代码

文章信息表实体对象

import java.time.LocalDateTime;
import java.util.Date;

/**
 * Created with IntelliJ IDEA.
 * Description:文章表
 * User: YAO
 * Date: 2023-07-18
 * Time: 13:52
 */
@Data
public class ArticleInfo {
    private Integer id;
    private String title;
    private String content;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private LocalDateTime createtime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private LocalDateTime updatetime;
    private Integer uid;
    private Integer rcount;
    private Integer state;
}

a. Mapper 层(ArticleMapper)

/**
     * 2.根据uid查询对应用户的文章列表
     */
    List<ArticleInfo> getMyList(@Param("uid")Integer uid);

b. MyBatis的xml文件的构造

<select id="getMyList" resultType="com.example.demo.entity.ArticleInfo">
        select * from articleinfo where uid=#{uid}
    </select>

c. service层

    public List<ArticleInfo> getMyList(Integer uid){
        return articleMapper.getMyList(uid);
    }

d. Controller层

@RestController
@RequestMapping("/art")
public class ArticleController {
    @Autowired
    private ArticleService articleService;

    @RequestMapping("/mylist")
    public AjaxResult getMyList(HttpServletRequest request){
        UserInfo userInfo = UserSessionUtils.getSessionUser(request);
        if (userInfo == null){
            return AjaxResult.fail(-1,"非法请求");
        }
        List<ArticleInfo> list = articleService.getMyList(userInfo.getId());
        for (ArticleInfo articleInfo : list) {
            if (articleInfo.getContent().length() > 100){
                // 当文章的长度大于100字的时候,不完全进行展示
                articleInfo.setContent(articleInfo.getContent().substring(0,100) + "...");
            }
        }
        return AjaxResult.success(list);
    }
}

6.3 删除博客功能 

 

6.3.1 编写前端Ajax请求代码 

myblog_list.html文件进行补充

// 删除文章
        function myDel(id){
            if(confirm("确实删除?")){
                // 删除文章
                jQuery.ajax({
                    url:"art/del",
                    type:"get",
                    data:{"id":id},
                    success:function(result){
                        if(result!=null && result.code==200 && result.data==1){
                            alert("删除成功!");
                            // 刷新当前页面
                            location.href = location.href;
                        }else{
                            alert("抱歉:删除失败,请重试!");
                        }
                    }
                });
            }
        }

6.3.2 编写后端代码

a. Mapper 层(ArticleMapper)

    /**
     * 3.根据id进行删除文章
     */
    int del(@Param("id") Integer id,@Param("uid") Integer uid);

b. MyBatis的xml文件的构造

<delete id="del">
        delete from articleinfo where id=#{id} and uid=#{uid}
    </delete>

c. service层

public Integer del(Integer id,Integer uid){
        return articleMapper.del(id,uid);
    }

d. Controller层

@RequestMapping("/del")
    public AjaxResult del(HttpServletRequest request,Integer id){
        if (id == null || id <= 0){
            return AjaxResult.fail(-1,"参数异常");
        }
        UserInfo userInfo = UserSessionUtils.getSessionUser(request);
        if (userInfo == null){
            return AjaxResult.fail(-2,"用户未登录");
        }
        return AjaxResult.success(articleService.del(id,userInfo.getId()));
    }

6.3.3 测试功能

 6.4 查看全文功能 

6.4.1 编写前端Ajax请求代码 

blog-content.html文件进行补充

        查看全文的功能,要对文章的内容进行展示,以及文章的作者,发布信息,以及文章阅读量进行展示.需要根据url中的id进行获取指定博客的id,我们需要对id的提取进行设置方法,因为后面的修改文章也需要获取文章id,以及主页的列表也需要,所以将此方法写入到公共的js方法中,此处需要注意的操作就是,每次查看的时候就要给文章的阅读量进行加1操作.

common.js 

function getUrlValue(key){
    // 一开始获取的字符串为?id=2&v=1
    var params = location.search;
    if(params.length>1){
        //?id=2&v=1 去除这个问号
        params = location.search.substring(1);   // 从第二个字符开始取出
        var paramArr = params.split("&");   // 差分成key-value的数组
        for(var i=0;i<paramArr.length;i++){
            var kv = paramArr[i].split("=");
            if(kv[0] = key){
                return kv[1];
            }
        } 
    }
    return "";
}
<script type="text/javascript">
            var editormd;
            function initEdit(md){
                editormd = editormd.markdownToHTML("editorDiv", {
                markdown : md, // Also, you can dynamic set Markdown text
                // htmlDecode : true,  // Enable / disable HTML tag encode.
                // htmlDecode : "style,script,iframe",  // Note: If enabled, you should filter some dangerous HTML tags for website security.
                });
            }

            // 获取当前url查询字符串参数的公共方法
            
          
            function getArtDetail(id){
                if(id==""){
                    alert("非法参数!");
                    return;
                }
                jQuery.ajax({
                    url:"art/detail",
                    type:"POST",
                    data:{"id":id},
                    success:function(result){
                        if(result!=null && result.code==200){
                            jQuery("#title").html(result.data.title);
                            jQuery("#updatetime").html(result.data.updatetime);
                            jQuery("#rcount").html(result.data.rcount);
                            initEdit(result.data.content);
                            // 得到用户 id
                            showUser(result.data.uid);
                        }else{
                            alert("查询失败,请重试!");  
                        }
                    }
                });
            }
            getArtDetail(getUrlValue("id"));

            // 查询用户的详情信息
            function showUser(id){
                jQuery.ajax({
                    url:"/user/getuserbyid",
                    type:"POST",
                    data:{"id":id},
                    success:function(result){
                        if(result!=null && result.code==200 && result.data.id>0){
                            jQuery("#username").text(result.data.username);
                            jQuery("#artCount").text(result.data.artCount);
                        }else{
                            alert("抱歉:查询用户信息失败,请重试!");
                        }
                    }
                });
            }
            // 阅读量 +1
            function updataRCount(){
                // 先得到文章 id
                var id = getUrlValue("id");
                if(id!=""){
                    jQuery.ajax({
                        url:"/art/incr-rcount",
                        type:"POST",
                        data:{"id":id},
                        success:function(result){}
                    });
                }
            }
            updataRCount();

    </script> 

6.4.2 编写后端代码

a. Mapper 层(ArticleMapper)

    /**
     * 3.根据id获取文章的具体内容
     */
    ArticleInfo getDetail(@Param("id")Integer id);

/**
     * 4.文章阅读量进行加1
     */
    int incrRCount(@Param("id") Integer id);

b. MyBatis的xml文件的构造

<select id="getDetail" resultType="com.example.demo.entity.ArticleInfo">
        select * from articleinfo where id=#{id}
    </select>

<update id="incrRCount">
        update articleinfo set rcount=rcount+1 where id=#{id};
    </update>

c. service层

public int getArtCountByUid(Integer uid){
        return articleMapper.getArtCountByUid(uid);
    }

public ArticleInfo getDetail(Integer id){
        return articleMapper.getDetail(id);
    }

d. Controller层

@RequestMapping("/incr-rcount")
    public AjaxResult incrRCount(Integer id){
        if (id == null || id <= 0){
            return AjaxResult.fail(-1,"参数异常");
        }
        return AjaxResult.success(articleService.incrRCount(id));
    }

@RequestMapping("/detail")
    public AjaxResult getDetail(HttpServletRequest request,Integer id){
        if (id == null || id <= 0){
            return AjaxResult.fail(-1,"参数异常");
        }
        return AjaxResult.success(articleService.getDetail(id));
    }

6.4.3 测试功能

6.5 修改博客功能 

6.5.1 编写前端Ajax请求代码 

blog_edit.html文件进行补充

修改功能就是点击跳转到编辑页面,首先发送请求得到当前文章的内容,再进行修改提交发送Ajax请求

function mysub(){
            // 1.非空效验
            var title = jQuery("#title");
            if(title.val()==""){
                alert("请先输入标题!");
                title.focus();
                return;
            }
            if(editor.getValue()==""){
                alert("请先输入正文!");
                return;
            }
            // 2.进行修改操作
            if(confirm("确定修改文章?")){
                    jQuery.ajax({
                    url:"/art/update",
                    type:"POST",
                    data:{"id":id,"title":title.val(),"content":editor.getValue()},
                    success:function(result){
                        if(result!=null && result.code==200 && result.data==1){
                            alert("修改成功!");
                            location.href = "myblog_list.html";
                        }else{
                            alert("抱歉:操作失败,请重试!");
                        }
                    }
                });
            }  
            
        }
        // 文章初始化
        function initArt(){
            // 得到当前页面 url 中的参数 id(文章id)
            id = getUrlValue("id");
            if(id==""){
                alert("无效参数");
                location.href = "myblog_list.html";
                return;
            }
            // 请求后端,查询文章的详情信息
            jQuery.ajax({
                url:"art/detail",
                type:"POST",
                data:{"id":id},
                success:function(result){
                    if(result!=null && result.code==200){
                        jQuery("#title").val(result.data.title);
                        initEdit(result.data.content);
                    }else{
                        alert("查询失败,请重试!");  
                    }
                }
            });
        }
        initArt();

6.5.2 编写后端代码

a. Mapper 层(ArticleMapper)

/**
     * 3.根据id获取文章的具体内容
     */
    ArticleInfo getDetail(@Param("id")Integer id);

 /**
     * 6. 修改文章
     * @param articleInfo
     * @return
     */
    int update(ArticleInfo articleInfo);

b. MyBatis的xml文件的构造

<select id="getDetail" resultType="com.example.demo.entity.ArticleInfo">
        select * from articleinfo where id=#{id}
    </select>

<update id="update">
        update articleinfo set title=#{title},content=#{content},updatetime=#{updatetime}
        where id=#{id} and uid=#{uid}
    </update>

c. service层

    public ArticleInfo getDetail(Integer id){
        return articleMapper.getDetail(id);
    }

    public int update(ArticleInfo articleInfo){
        return articleMapper.update(articleInfo);
    }

d. Controller层

@RequestMapping("/detail")
    public AjaxResult getDetail(HttpServletRequest request,Integer id){
        if (id == null || id <= 0){
            return AjaxResult.fail(-1,"参数异常");
        }
        return AjaxResult.success(articleService.getDetail(id));
    }

@RequestMapping("/update")
    public AjaxResult update(HttpServletRequest request, ArticleInfo articleInfo){
        if (articleInfo == null || !StringUtils.hasLength(articleInfo.getTitle())
                || !StringUtils.hasLength(articleInfo.getContent()) || articleInfo.getId()==null){
            return AjaxResult.fail(-1,"非法参数");
        }
        UserInfo userInfo = UserSessionUtils.getSessionUser(request);
        if (userInfo == null || userInfo.getId() == null){
            return AjaxResult.fail(-2,"无效用户");
        }
        articleInfo.setUid(userInfo.getId());
        articleInfo.setUpdatetime(LocalDateTime.now());
        return AjaxResult.success(articleService.update(articleInfo));
    }

6.5.3 测试功能

6.6 发布博客功能

6.6.1 编写前端Ajax请求代码 

blog_add.html文件进行补充

<script>
        var editor;
        function initEdit(md){
            // 编辑器设置
            editor = editormd("editorDiv", {
                // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. 
                width: "100%",
                // 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
                height: "calc(100% - 50px)",
                // 编辑器中的初始内容
                markdown: md,
                // 指定 editor.md 依赖的插件路径
                path: "editor.md/lib/",
                saveHTMLToTextarea: true // 
            });
        }
        initEdit("# 在这里写下一篇博客"); // 初始化编译器的值
        // 提交
        function mysub(){
            if(confirm("确认提交?")){
                // 1.非空效验
                var title = jQuery("#title");
                if(title.val()==""){
                    alert("请先输入标题!");
                    title.focus();
                    return;
                }
                if(editor.getValue()==""){
                    alert("请先输入文章内容!");
                    return;
                }
                // 2.请求后端进行博客添加操作
                jQuery.ajax({
                    url:"/art/add",
                    type:"POST",
                    data:{"title":title.val(),"content":editor.getValue()},
                    success:function(result){
                        if(result!=null && result.code==200 && result.data==1){
                            if(confirm("文章添加成功!是否继续添加文章?")){
                                // 刷新当前页面
                                location.href = location.href;
                            }else{
                                location.href = "/myblog_list.html";
                            }
                        }else{
                            alert("抱歉,文章添加失败,请重试!");
                        }
                    }
                });
            }
        }
        
    </script>

6.6.2 编写后端代码

a. Mapper 层(ArticleMapper)

/**
     * 5. 添加文章
     * @param articleInfo
     * @return
     */
    int add(ArticleInfo articleInfo);

b. MyBatis的xml文件的构造

<insert id="add">
        insert into articleinfo(title,content,uid) values(#{title},#{content},#{uid})
    </insert>

c. service层

public int add(ArticleInfo articleInfo){
        return articleMapper.add(articleInfo);
    }

d. Controller层

@RequestMapping("/add")
    public AjaxResult add(HttpServletRequest request, ArticleInfo articleInfo){
        // 1.进行非空校验
        if (articleInfo == null || !StringUtils.hasLength(articleInfo.getTitle())
        ||  !StringUtils.hasLength(articleInfo.getContent())){
            return AjaxResult.fail(-1,"参数异常");
        }
        // 2.获取当前登录用户的id,将当前登录用户的id进行设置到传入sql的文章信息的对象,尽心添加操作的时候就会准确的直到这是当前登录用户记性发布的
        UserInfo userInfo = UserSessionUtils.getSessionUser(request);
        if (userInfo == null || userInfo.getId() <= 0){
            return AjaxResult.fail(-2,"无效的登录用户");
        }
        articleInfo.setUid(userInfo.getId());
        int result = articleService.add(articleInfo);
        return AjaxResult.success(result);
    }

6.6.3 功能测试

 

6.7 注销用户功能

        我们要给检测到登录状态的页面加注销功能, 如个人主页,写博客,主页.因为是公共操作,所以直接写成一个公共的js,然后在需要的页面直接引入就可以.

6.7.1 编写前端Ajax请求代码 

common.js

// 公共操作 -- 注销登录
function logout(){
    if(confirm("确定注销??")){
        // 注销登录
        jQuery.ajax({
            url:"user/logout",
            type:"get",
            data:{},
            success:function(result){
                if(result!=null && result.code==200){
                    location.href = "/login.html";
                }
            }
        });
    }
}

6.7.2 编写后端代码

a. Mapper 层(ArticleMapper)

/**
     * 5. 添加文章
     * @param articleInfo
     * @return
     */
    int add(ArticleInfo articleInfo);

b. MyBatis的xml文件的构造

<insert id="add">
        insert into articleinfo(title,content,uid) values(#{title},#{content},#{uid})
    </insert>

c. service层

public int add(ArticleInfo articleInfo){
        return articleMapper.add(articleInfo);
    }

d. Controller层

@RequestMapping("/add")
    public AjaxResult add(HttpServletRequest request, ArticleInfo articleInfo){
        // 1.进行非空校验
        if (articleInfo == null || !StringUtils.hasLength(articleInfo.getTitle())
        ||  !StringUtils.hasLength(articleInfo.getContent())){
            return AjaxResult.fail(-1,"参数异常");
        }
        // 2.获取当前登录用户的id,将当前登录用户的id进行设置到传入sql的文章信息的对象,尽心添加操作的时候就会准确的直到这是当前登录用户记性发布的
        UserInfo userInfo = UserSessionUtils.getSessionUser(request);
        if (userInfo == null || userInfo.getId() <= 0){
            return AjaxResult.fail(-2,"无效的登录用户");
        }
        articleInfo.setUid(userInfo.getId());
        int result = articleService.add(articleInfo);
        return AjaxResult.success(result);
    }

6.7.3 功能测试

 

7. 博客主页功能实现

7.1 主页的导航栏

这里需要注意的是,已经登录的用户的导航栏和未登录的有细微的区别

已经登录的主页是可以进行切换到个人主页的,未登录的是没有这个选项的.

 在进入主页上的时候发送请求的时候进行验证登录的状态,检测为登录,就给导航栏拼接个人主页的跳转按钮就可以了

7.1.1 编写前端Ajax请求代码 

在login.html中进行编写代码

 <script>
        // 根据登录的状态进行设置列表页的导航栏信息
        function loginstate(){
            jQuery.ajax({
                url:"/user/loginstate",
                type:"POST",
                data:{},
                success:function(result){
                    var navText='<img src="img/logo2.jpg" alt=""><span class="title">我的博客系统</span><span class="spacer"></span>';
                    if(result.code==200 && result!=null){
                        navText += '<a href="myblog_list.html">个人主页</a><a href="blog_add.html">写博客</a><a href="javascript:logout()">注销</a>'
                        jQuery("#nav").html(navText)
                    }else{
                        navText += '<a href="blog_add.html">写博客</a><a href="login.html">登录</a>'
                        jQuery("#nav").html(navText)
                    }
                }
            })
        }
        loginstate();
 </script>

7.1.2 编写后端代码

因为不用操作数据库,所以只编写Controller层就可以了

@RequestMapping("loginstate")
    public AjaxResult loginstate(HttpServletRequest request){
        UserInfo userInfo = UserSessionUtils.getSessionUser(request);
        if (userInfo == null || userInfo.getId()<=0){
            return AjaxResult.fail(-1,"当前用户未登录");
        }
        return AjaxResult.success(userInfo);
    }

7.2 分页功能  

实现分页功能,就要先获取总的文章数量,以及定义好每页显示多少条内容.具体总结为下面.

8. 密码使用加盐的算法进行加密

为了更形象的展示,我使用了别的画图软件

主要总结了三种加密方式\

  • a. 使用传统的MD5进行加密
  • b. 自己实现加盐算法进行加密
  • c. 使用Spring Security进行加密(Spring官方的)

本项目采用自己写的加盐算法:

package com.example.demo.common;

import org.springframework.beans.BeanUtils;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import java.util.UUID;

/**
 * Created with IntelliJ IDEA.
 * Description:关于密码的工具类
 * User: YAO
 * Date: 2023-07-19
 * Time: 13:49
 */
public class PasswordUtils {
    /**
     * 1.加盐并生成密码
     * @param password 明文密码
     * @return 保存到数据库的密码
     */
    public static String encrypt(String password){
        // 1. 32位盐值
        String salt = UUID.randomUUID().toString().replace("-","");
        // 2. 生成加盐之后的密码
        String saltPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
        // 3. 生成最终保存在数据库的密码   约定:32位盐值+$+32位加盐之后MD5加密之后的密码  -->65位
        String finalPassword = salt + "$" + saltPassword;
        return finalPassword;
    }

    /**
     * 2.生成用于验证的加盐密码
     * @param password  明文密码
     * @param salt  盐值
     * @return 验证的密码
     */
    public static String encrypt(String password,String salt){
        // 1. 生成一个加盐之后的密码
        String saltPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
        // 2.生成最终的密码  约定:32位盐值+$+32位加盐之后MD5加密之后的密码  -->65位
        String finalPassword = salt + "$" + saltPassword;
        return finalPassword;
    }


    /**
     * 3.验证密码
     * @param inputPassword  用户输入的明文密码
     * @param finalPassword  数据库存储的最终的密码
     * @return
     */
    public static Boolean check(String inputPassword, String finalPassword){
        if (StringUtils.hasLength(inputPassword) && StringUtils.hasLength(finalPassword)
        && finalPassword.length() == 65){
            // 1.得到盐值
            String salt = finalPassword.split("\\$")[0];
            // 2.将用户输入的铭文密码进行加盐加密
            String finalInputPassword = encrypt(inputPassword,salt);
            // 3.进行比较
            return finalInputPassword.equals(finalPassword);
        }
        return false;
    }

}

在注册的Controller以及登录的Controller可以看出相应的使用.

9. Session持久化到Redis

Redis的具体使用我会另外进行总结一个系列,这里直接将SSM项目Session持久化到Redis进行总结.

在我们的项目只需要配置好依赖文件配置文件,指定Session存储的类型为redis,指定好Session存储的路径以及仓库.

我们使用redis的可视化工具可以查看我们存储的Session.

结语

        写到这里,这个基础的SSM项目就算实现的差不多了,后续还可以扩展很多的功能,比如评论,草稿,登录验证码登功能,后续时间不紧的话我也会进行响应的更新.好啦,就到这里咯,谢谢!!!可以的话,给个赞,点个关注吧!!!😊😊😊

代码链接👇👇👇👇👇

SSM博客系统(前端+后端)icon-default.png?t=N6B9https://gitee.com/yao-fa/advanced-java-ee/tree/master/MyCnBlog-SSM


 

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

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

相关文章

Elasticsearch API(二)

文章目录 前言一、Elasticsearch指标ES支持的搜索类型ES的能力ES的写入实时性ES不支持事务 二、Elasticsearch名词节点&#xff08;Node&#xff09;角色&#xff08;Roles&#xff09;索引&#xff08;index&#xff09;文档&#xff08;document&#xff09; 三、Elasticsear…

126.【Redis 二刷】

Redis 二刷 (一)、认识NoSQL 与 SQL1.NoSQL与SQL的区别 (二)、认识Redis1.Redis 特征2.Redis安装及启动的三种方式 (基于Window)(1).命令行配置文件 3.Redis 客户端(1).命令行客户端(2).Redis可视化客户端 4.Redis 数据结构(1).Redis 通用命令(2).String 类型(3).Redis 的Key层…

【C++】list 模拟笔记

文章目录 list定义结点类&#xff08;list_node&#xff09;为什么封装迭代器为类 &#xff1f;库里面模板多参数的由来 &#xff1f;为什么普通迭代器不能隐式类型转换成const迭代器&#xff1f;迭代器位置指向及其返回值和整体代码 list list 和前面学习的 string 和 vector …

SAP HANA数据库学习

Systems介绍&#xff1a; Catalog下面存HANA的 Schema。 Content下面建package来 放view 。 Provisioning可以建虚拟表 数据源啥的。 Security下面 是用户角色和权限。 一个calculation view里面的scenario场景下都有啥呢&#xff1f; 首先是semantics下面的节点&#xff0…

跨网段耦合器的作用

你是否曾经遇到过需要跨网段访问设备的问题&#xff1f;比如在工业自动化领域&#xff0c;PLC和数控设备的连接。这时候&#xff0c;远创智控YC8000-NAT就能帮你轻松解决。 1, 远创智控YC8000-NAT是一款功能强大的设备&#xff0c;它可以将LAN1口所连接PLC的IP地址和端口号&a…

MySQL多版本并发控制原理(MVCC)

在数据库系统中&#xff0c;事务是指由一系列数据库操作组成的一个完整的逻辑过程&#xff0c;事务的基本特性是ACID&#xff1a; A : Atomicity (原子性) C: Consistency (一致性) I: Isolation (隔离性) D: 持久性&#xff08;Durability&#xff09; 由于大部分数据库都是高…

听GPT 讲K8s源代码--pkg(五)

在 Kubernetes 中&#xff0c;kubelet 是运行在每个节点上的主要组件之一&#xff0c;它负责管理节点上的容器&#xff0c;并与 Kubernetes 控制平面交互以确保容器在集群中按照期望的方式运行。kubelet 的代码位于 Kubernetes 代码库的 pkg/kubelet 目录下。 pkg/kubelet 目录…

档案数字化扫描完成标准有哪些内容?

档案数字化扫描完成标准是指在进行数字化扫描即将纸质文档或图片等非数字化文件转化为数字格式的文件的过程中&#xff0c;要满足一系列严格的要求。 1.扫描速度快。由于档案数量庞大&#xff0c;数字化扫描需要快速高效地进行&#xff0c;因此需要采用高效的扫描设备和软件&am…

函数-嵌入式C语言

函数-嵌入式C语言 值传递 地址传递

基于C语言的科学计算器

完整资料进入【数字空间】查看——baidu搜索"writebug" 一、产品概述 计算器&#xff1a; 它是一个拥有扁平化优雅用户界面的科学计算器&#xff0c;拥有科学计算与基础计算器功能&#xff0c;可以计算是是数学表达式&#xff1a;从一个简单的表达式&#xff0c;如…

postgresql源码学习(58)—— 删除or重命名WAL日志?这是一个问题

最近因为WAL日志重命名踩到大坑&#xff0c;一直很纠结WAL日志在什么情况下会被删除&#xff0c;什么情况下会被重命名&#xff0c;钻研一下这个部分。 一、 准备工作 1. 主要函数调用栈 首先无用WAL日志的清理发生检查点执行时&#xff0c;检查点执行核心函数为CreateCheckPo…

96、Kafka中Zookeeper的作用

Kafka中zk的作用 它是一个分布式协调框架。很好的将消息生产、消息存储、消息消费的过程结合在一起。在典型的Kafka集群中, Kafka通过Zookeeper管理集群配置&#xff0c;选举leader&#xff0c;以及在Consumer Group发生变化时进行rebalance。Producer使用push模式将消息发布到…

PyQt5:使用PyQtWebEngine

1. PyQt 5.13.0 1.1 安装PyQt pip install PyQt55.13.0 -i https://pypi.tuna.tsinghua.edu.cn/simple1.2 安装PyQtWebEngine pip install PyQtWebEngine5.13.0 -i https://pypi.tuna.tsinghua.edu.cn/simplepip list 1.3 测试 python文件 import sys from PyQt5.QtCore imp…

ARM——点灯实验

循环点灯 RCC寄存器使能GPIOE、GPIOF组寄存器 修改GPIOx组寄存器下的值 通过GPIOx_MODER寄存器设置为输出模式通过GPIOx_OTYOER寄存器设置为推挽输出类型通过GPIOx_OSPEEDR寄存器设置为低速输出通过GPIOx_PUPDR寄存器设置为禁止上下拉电阻点灯 通过GPIOx_ODR寄存器设置为高电…

day33哈希表

1.哈希表 常见的哈希表分为三类&#xff0c;数组&#xff0c;set&#xff0c;map&#xff0c;C语言的话是不是只能用数组和 2.例题 题目一&#xff1a; 分析&#xff1a;题目就是判断两个字符串出现的次数是否相同&#xff1b; 1&#xff09;哈希表26个小写字母次数初始化为0&…

K8S初级入门系列之一-概述

一、前言 K8S经过多年的发展&#xff0c;构建了云原生的基石&#xff0c;成为了云原生时代的统治者。我将用三个博客系列全面&#xff0c;循序渐进的介绍K8S相关知识。 初级入门系列&#xff0c;主要针对K8S初学者&#xff0c;以及希望对K8S有所了解的研发人员&#xff0c;重点…

【贪心算法part05】| 435.无重叠区间、763.划分字母区间、56.合并区间

目录 &#x1f388;LeetCode435. 无重叠区间 &#x1f388;LeetCode763.划分字母区间 &#x1f388;LeetCode 56.合并区间 &#x1f388;LeetCode435. 无重叠区间 链接&#xff1a;435.无重叠区间 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, …

【雕爷学编程】Arduino动手做(55)--DHT11温湿度传感器模块3

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

力扣C++|一题多解之数学题专场(2)

目录 50. Pow(x, n) 60. 排列序列 66. 加一 67. 二进制求和 69. x 的平方根 50. Pow(x, n) 实现 pow(x,n)&#xff0c;即计算 x 的 n 次幂函数&#xff08;即x^n&#xff09;。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;1024.00000 示例…

【SQL应知应会】表分区(五)• MySQL版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle 分区表 • MySQL版 前言一、分区表1.非分区表2.分区…