个人博客系统[SpringBoot+SpringMVC+MyBais]

news2024/11/23 3:21:15

文章目录

  • 🎇 前言
  • 1.项目目录介绍
  • 2.项目前准备
    • 2.1 使用到的第三方库
    • 2.1 配置文件(application.properties)
    • 2.2 数据库介绍
  • 3.common目录工具类介绍
    • 3.1 AjaxResult类
    • 3.2 AppVariable类
    • 3.3 CaptchaUtils类
    • 3.4 PasswordUtils类
    • 3.5 UserSessionUtils类
  • 4.config目录
    • 4.1 LoginInterceptor---登录拦截器
    • 4.2 ResponseAdvice类
  • 5.功能演示
    • 5.1 UserController类
      • 5.1.1 注册功能
      • 5.1.2 登录功能
      • 5.1.3 显示左边个人信息
      • 5.1.4 注册功能
      • 5.1.5 根据文章中UID查询用户
    • 5.2 CaptchaController类
    • 5.3 ArticleController类
      • 5.3.1 获取登录用户的所有文章
      • 5.3.2 删除文章
      • 5.3.3 根据 文章id 返回文章
      • 5.3.4 修改文章阅读量
      • 5.3.5 用户写博客添加博客
      • 5.3.6 修改博客
      • 5.3.7 分页功能
  • 🎆总结

🎇 前言

这个项目是一个基于SSM的个人博客系统项目,项目一个前后端分离的项目,目前已经完工。它的主要技术包含SpringAOP、手动实现的加盐算法、登录验证码、Redis存储Session等.
这个项目已开源,我会将项目的gitee连接放到最下面。

1.项目目录介绍

在这里插入图片描述

  • common 目录 : 存放工具类、统一返回格式和全局变量
  • config 目录 : 配置相关的东西,例如:登录拦截器
  • controller 目录 : 处理前端返回的数据
  • entity 目录 : 存放实体类
  • mapper 目录 : 里面是提供给 MyBatis 的接口
  • service 目录 : 这个是统一调用 mapper 的接口
  • resources/Mybatis 目录 : 实现 mapper 中接口,对接数据库
  • resources/static 目录 : 存放前端的内容
  • dp.sql: 存放使用的sql语句

2.项目前准备

2.1 使用到的第三方库

Lombok、Spring Web、Spring Session、MyBatis Framework、MySQL Driver、Hutool、Slf4j

2.1 配置文件(application.properties)

# 数据库连接
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=9root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

## 设置时间格式 对 LocalDateTime 和 LocalDate 不起作用
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

# mybatis xml 配置
mybatis.mapper-locations=classpath:Mybatis/*Mapper.xml

#   控制台打印mybatis 执行的sql语句
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.com.example.demo=debug


# redisd配置代码
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0 #默认数据库编号是0
spring.session.store-type=redis
server.servlet.session.timeout=1800
spring.session.redis.flush-mode=on_save
spring.session.redis.namespace=spring:session
spring.redis.host=127.0.0.1

2.2 数据库介绍

数据库中一共有三个表,分别是:用户表(userinfo)、文章表(articleinfo)。用户表用来存储用户信息,文章表就是用来存储文章。

3.common目录工具类介绍

3.1 AjaxResult类

该类是对返回数据进行统一的封装,为什么要统一封装呢?对于前端来说,它们是不懂后端的代码的,因此我们需要将它的数据统一进行封装返回前端,这里使用的方法就是将内容封装成一个类,在需要返回的数据的时候,将这个类实例化,然后将它转化为 Json 的形式传给前端。

package com.example.demo.common;

import lombok.Data;

import java.io.Serializable;

/**
 *  统一数据返回类型
 *
 */
@Data
public class AjaxResult implements Serializable {
    //Serializable接口是为了实现序列化和反序列化不报错
    //状态码
    private Integer code;
    //状态码描述
    private String msg;
    //返回的数据
    private Object data;

    /**
     * 操作成功返回的结果
     * 进行多次重载提供选择
     */
    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(int code,String msg,Object data) {
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }

    /**
     * 返回失败结果
     * 进行多次重载提供选择
     *
     */
    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(int code,String msg,Object data) {
        AjaxResult result = new AjaxResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}

3.2 AppVariable类

这个就类就简单描述;主要就是存储一些全局变量;为啥要单独使用一个类?这样是为了降低代码的耦合度。

package com.example.demo.common;

/**
 * 保存全局变量
 */
public class AppVariable {

    // 定义 session 的key值
    public static final String USER_SESSION_KEY = "USER_SESSION_KEY";
    // 图片存储在 session 的key值
    public static final String CAPTCHA_SESSION_KEY = "CAPTCHA";

}

3.3 CaptchaUtils类

这个类是配合Hutool库使用的,主要的作用是生成验证码图片和验证验证码的正确性。

package com.example.demo.common;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class CaptchaUtils {

	// 生成验证码
    public static void generateCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 50);

        // 将验证码存储在Session中用于验证
        request.getSession().setAttribute(AppVariable.CAPTCHA_SESSION_KEY, captcha.getCode());

        // 设置响应头
        response.setContentType("image/png");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0L);

        // 将验证码图片写入响应流
        ServletOutputStream outputStream = response.getOutputStream();
        ImageIO.write(captcha.getImage(), "png", outputStream);
        outputStream.flush();
        outputStream.close();
    }

    /**
     * 判断输入的验证码的正确性
     *
     * @param request
     * @param userInput 用户输入的密码
     * @return
     */
    public static boolean validateCaptcha(HttpServletRequest request, String userInput) {
        String storedCaptcha = (String) request.getSession().getAttribute(AppVariable.CAPTCHA_SESSION_KEY);
        return userInput != null && userInput.equalsIgnoreCase(storedCaptcha);
    }
}

3.4 PasswordUtils类

这个类中实现了加盐算法,这里解释一下加盐算法。我们规定的加盐算法是65位,前32为是盐值通过UUID生成,中间加一个分隔符,之后后面是使用盐值加上明文(密码)再使用MD5加密。具体看下图:
在这里插入图片描述
对于解密,我们可以通过分隔符提取出盐值,将盐值传给辅助方法,通过之前一样加盐的方式进行加盐,因为我们同一个数据,我们使用相同的操作,故如果输入的密码和设置的密码一样,那么两次操作后的数据应该是一样的。

package com.example.demo.common;


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

import java.util.UUID;

/**
 * 实现加盐算法
 *
 */
public class PasswordUtils {

    /**
     * 1.给用户传过来的明文密码加密
     *
     * @param password 明文密码
     * @return  保存在数据库中的密码
     */
    public static String encrypt(String password) {
        // 1.生成盐值(32位)
        String salt = UUID.randomUUID().toString().replace("-","");
        // 2.加盐后的密码:(盐值+明文)再用MD5加密
        String saltPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
        // 3.最终密码:【盐值+$+加盐后的密码】(65位)
        String finalPassword = salt+"$"+saltPassword;
        return finalPassword;

    }

    /**
     *  2.辅助解密方法;方法1的重载
     *
     * @param password 用户输入的密码
     * @param salt      盐值(需要从数据库中的密码提取出来)
     * @return
     */
    private static String encrypt(String password,String salt) {
        // 1.加盐后的密码 (32位)
        String saltPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
        // 2.最终密码:【盐值+$+加盐后的密码】(65位)
        String finalPassword = salt+"$"+saltPassword;
        return finalPassword;
    }

    /**
     * 3.解密方法
     *
     * @param inputPassword // 用户输入的密码
     * @param sqlPassword // 数据库中的密码
     * @return
     */
    public static boolean check(String inputPassword,String sqlPassword) {
        // 参数校验
        if(StringUtils.hasLength(inputPassword) && StringUtils.hasLength(sqlPassword)
                && sqlPassword.length() == 65) {
            // 获取盐值
            String salt = sqlPassword.substring(0,32);
            // 通过方法1的方式加密
            String confirmPassword = encrypt(inputPassword,salt);
            // 比较两次的结果
            if(confirmPassword.equals(sqlPassword)) {
                return true;
            }
        }
        return false;
    }
}

3.5 UserSessionUtils类

这个类辅助我们获得session中的用户,以及判断是否登录。

package com.example.demo.common;


import com.example.demo.entity.UserInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 操作当前session的工具类
 */
public class UserSessionUtils {

    /**
     * 得到当前用户
     *
     * @param request
     * @return
     */
    public static UserInfo getSessUser(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;
    }
}

4.config目录

这个目录中主要是实现了一个登录拦截器和 统一返回数据的保底类

4.1 LoginInterceptor—登录拦截器

它是实现原理用到了AOP的思想;这个类需要实现HandlerInterceptor接口,并且重写preHandle方法,判断是否用户登录,如果没有登录,无法访问我们配置之外的接口和网页。
这里的配置是指的AppConfig这个类中的数据

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;

/**
 * 登录拦截器
 *
 */
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);
        //判断session不为空,并且session的key不为空,说明用户已登录
        if(session != null && session.getAttribute(AppVariable.USER_SESSION_KEY) != null) {
            //用户已登录
            return true;
        }
        // 未登录,跳转到登录页
        response.sendRedirect("/login.html");
        return false;
    }
}

4.2 ResponseAdvice类

这个类是统一返回类型的保底机制,如果我们忘了统一返回类型,那么他会帮我们包装成Json格式。

package com.example.demo.config;


import com.example.demo.common.AjaxResult;
import com.fasterxml.jackson.core.JsonProcessingException;
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;

/**
 * 实现统一返回数据的保底类
 * 说明:如果返回数据时,检查是否为统一返回类型,如果不是就改为统一类型
 *
 */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }


    /**
     * 对数据格式校验和封装
     *
     */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 判断数据是否封装
        if(body instanceof AjaxResult) return body;
        // 如果是String类型的特殊处理,手动将String转换成json格式
        if(body instanceof String) {
            return objectMapper.writeValueAsString(AjaxResult.success(body));
        }
        //如果不是ajax的格式,并且不上String类型
        return AjaxResult.success(body);
    }
}

5.功能演示

5.1 UserController类

5.1.1 注册功能

前端通过Ajax的方式以Json形式将数据发送给后端,如果注册成功就会询问你是否跳转页面。
在这里插入图片描述

/**
     * 注册功能
     *
     * @param userInfo
     * @return
     */
    @RequestMapping("/reg")
    public AjaxResult reg(UserInfo userInfo){
        //非空校验和参数有效性校验
        if(userInfo==null ||  !StringUtils.hasLength(userInfo.getUsername())
                ||  !StringUtils.hasLength(userInfo.getPassword())) {
            return AjaxResult.fail(-1,"参数非法");
        }
        // 对密码加盐加密
        userInfo.setPassword(PasswordUtils.encrypt(userInfo.getPassword()));
        return AjaxResult.success(userService.reg(userInfo));
    }

5.1.2 登录功能

登录我们需要输入用户名和密码和验证码,必须三个都正确才能登录,登录后会直接跳转到个人博客的列表页。
在这里插入图片描述

/**
     * 登录功能
     *
     * @param username
     * @param password
     * @param request
     * @return
     */
    @RequestMapping("/login")
    public AjaxResult login(String username,String password,HttpServletRequest request,String captcha) {
        // 非空验证和参数合法性验证
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return  AjaxResult.fail(-1,"参数非法");
        }
        // 查询出来数据库中的对象
        UserInfo userInfo = userService.getUserByName(username);
        // 判断验证码的正确性
        boolean isCaptchaValid = CaptchaUtils.validateCaptcha(request, captcha);
        // 判断用户有效性
        if(userInfo !=null && userInfo.getId()>0 && isCaptchaValid) {
            if(PasswordUtils.check(password,userInfo.getPassword())) {
                // 登录成功
                userInfo.setPassword("");//返回数据,隐藏敏感(密码)信息
                // 存储 session
                HttpSession session = request.getSession(true);
                session.setAttribute(AppVariable.USER_SESSION_KEY,userInfo);
                return AjaxResult.success(userInfo);
            }
        }
        return  AjaxResult.success(0,null);
    }

5.1.3 显示左边个人信息

我们通过session读取当前登录的用户,之后再统计这个用户写的文章用户,我们创建一个新的实体类UserInfoVO,这个类主要是辅助我们统计用户的文章数量。
在这里插入图片描述

5.1.4 注册功能

直接删除session中的键就算是注销了,我们通过调用session中的removeAttribute方法实现

/**
     * 注销功能
     *
     * @param session
     * @return
     */
    @RequestMapping("/logout")
    public AjaxResult logOut(HttpSession session) {
        session.removeAttribute(AppVariable.USER_SESSION_KEY);
        return AjaxResult.success(1);
    }

5.1.5 根据文章中UID查询用户

当我们点击查看全文的时候,我们需要加载根据文章作者加载作者,我们下方这个方法实现。
在这里插入图片描述

 /**
     * 根据文章中UID查询用户
     *
     * @param uid
     * @return
     */
    @RequestMapping("/getuserbyid")
    public AjaxResult getUserById(Integer uid) {
        if(uid==null && uid<=0) {
            return  AjaxResult.fail(-1,"用户id非法!");
        }
        UserInfoVO userInfoVO = new UserInfoVO();
        // 通过前端传的uid查出用户
        UserInfo userInfo = userService.getUserById(uid);
        // 传值方便查询文章篇数
        BeanUtils.copyProperties(userInfo,userInfoVO);
        // 存储文章篇数
        userInfoVO.setArtCount(articleService.getArtCountByUid(uid));
        userInfoVO.setPassword("");// 屏蔽密码
        return AjaxResult.success(userInfoVO);
    }

5.2 CaptchaController类

这个类中主要是配合Hutool库来使用,调用工具类来生成验证码。

package com.example.demo.controller;


import com.example.demo.common.CaptchaUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

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

@Controller
@RequestMapping("/captcha")
public class CaptchaController {

    @GetMapping("/generate")
    public void generateCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        CaptchaUtils.generateCaptcha(request, response);
    }

    @GetMapping("/validate")
    @ResponseBody
    public boolean validateCaptcha(HttpServletRequest request, String userInput) {
        return CaptchaUtils.validateCaptcha(request, userInput);
    }
}

5.3 ArticleController类

5.3.1 获取登录用户的所有文章

通过session获得当前登录的用户,然后查询他的所有文章,并返回给前端。
在这里插入图片描述

/**
     * 获取登录用户的所有文章
     *
     * @param request
     * @return
     */
    @PostMapping("/mylist")
    public AjaxResult getMyList(HttpServletRequest request) {
        // 1.得到用户
        UserInfo userInfo = UserSessionUtils.getSessUser(request);
        // 判断用户是否成功登录
        if (userInfo == null) {
            // 登录失败的情况
            return AjaxResult.fail(-1,"非法请求");
        }
        // 2.查询用户所有文章
        List<ArticleInfo> list = articleService.getAllArtByUId(userInfo.getId());
        for (ArticleInfo a:list) {
            String s = a.getContent();
            s=s.replaceAll("#","");
            if(s.length()>50) {
                a.setContent(s.substring(0,50)+"...");
            }else {
                a.setContent(s);
            }

        }
        return AjaxResult.success(list);
    }

5.3.2 删除文章

删除文章时,我们需要判断登录的这个人是不是这篇文章的作者,如果这篇文章是张三写的,李四把他删了,那就杯具了!!
在这里插入图片描述

 /**
     * 这里需要注意,我们在删除的时候,需要确认删除的这篇文章是现在登录用户的文章
     *
     * @param request
     * @param id 文章id
     * @return
     */
    @RequestMapping("/del")
    public AjaxResult delArtById(HttpServletRequest request,Integer id) {
        // 1.得到用户
        UserInfo userInfo = UserSessionUtils.getSessUser(request);
        // 判断用户是否成功登录
        if (userInfo == null || userInfo.getId()<=0) {
            // 登录失败的情况
            return AjaxResult.fail(-1,"非法请求");
        }
        // 判断文章id 的合法性
        if(id<=0 || id==null) {
            return AjaxResult.fail(-1,"非法参数");
        }
        // 这个表示影响的行数
        int res = articleService.delArtById(id,userInfo.getId());
        return AjaxResult.success(res);
    }

5.3.3 根据 文章id 返回文章

前端发送过来文章的id,我们通过文章id 返回文章。
在这里插入图片描述

/**
     * 根据 文章id 返回文章
     *
     * @param id 文章id
     * @return
     */
    @RequestMapping("/detail")
    public AjaxResult getArtDetailById(Integer id) {
        if(id==null || id<=0) {
            return  AjaxResult.fail(-1,"文章id非法");
        }
        // 通过文章id 查询文章
        ArticleInfo articleInfo = articleService.getArtDetailById(id);
        return AjaxResult.success(articleInfo);
    }

5.3.4 修改文章阅读量

前端发过来文章id,为了保证操作的原子性,我们通过数据库操作来对阅读量加一。

/**
     * 修改文章阅读量
     *
     * @param id 文章id
     * @return
     */
    @RequestMapping("/updatercount")
    public AjaxResult inCrRCount(Integer id) {
        if(id==null || id<=0) {
            return  AjaxResult.fail(-1,"文章id非法");
        }
        return AjaxResult.success(articleService.inCrRCount(id));
    }

5.3.5 用户写博客添加博客

我们直接使用使用通过ArticleInfo类来接收前端发送的信息,这样的好处是不管别人传的什么,我们都不要取修改参数的个数。

/**
     * 用户写博客添加博客
     *
     * @param request
     * @param articleInfo
     * @return
     */
    @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.给文章添加上uid
        // a.获得user对象
        UserInfo userInfo = UserSessionUtils.getSessUser(request);
        // b.user的合法性验证
        if(userInfo==null || userInfo.getId()<=0){
            return AjaxResult.fail(-1,"参数非法");
        }
        // b.赋值art的uid
        articleInfo.setUid(userInfo.getId());
        return AjaxResult.success(articleService.add(articleInfo));
    }

5.3.6 修改博客

我们得先获得当前的登录对象,得到登录对象是为了给修改后的文章添加UID,为啥不让前端直接传输UID呢,这是因为有风险(防止别人抓包恶搞),因此我们通过session获取用户再写入UID

/**
     * 修改博客
     *
     * @param articleInfo
     * @param request
     * @return
     */
    @RequestMapping("/update")
    public AjaxResult upDate(ArticleInfo articleInfo,HttpServletRequest request) {
        // 1.参数效验
        if(articleInfo==null || !StringUtils.hasLength(articleInfo.getTitle())
        || !StringUtils.hasLength(articleInfo.getContent()) || articleInfo.getId()==null) {
            return AjaxResult.fail(-1,"参数非法");
        }
        // 2.修改数据库中的数据
        // a.获得 user 对象
        UserInfo userInfo = UserSessionUtils.getSessUser(request);
        // 判断 user 对象和合法性
        if(userInfo == null || userInfo.getId()<=0) {
            return AjaxResult.fail(-1,"无效用户");
        }
        // 这里不能让前端传uid,别人抓包可以抓到,有风险
        articleInfo.setUid(userInfo.getId());
        // 修改时间
        articleInfo.setUpdatetime(LocalDateTime.now());
        // b.将数据保存到数据库中
        return AjaxResult.success(articleService.upDate(articleInfo));
    }

5.3.7 分页功能

前端需要给我们传过来页码和一页的文章数量,这样是为了方便我们计算到底需要共几页。

/**
     * 分页功能的实现
     *
     * @param pindex 页码(从一开始)
     * @param psize 一页所展现的文章数量
     * @return
     */
    @RequestMapping("/listbypage")
    public AjaxResult getListByPage(Integer pindex,Integer psize) {
        // 1.参数矫正
        if(pindex==null || pindex<=1) {
            pindex =1;
        }
        if(psize==null || psize<=1) {
            psize = 2;
        }
        // 分页公式
        int offsize =(pindex-1)*psize;
        List<ArticleInfo> list = articleService.getListByPage(psize,offsize);
        // 获取总页数
        // a.获得文章总条数
        int artCount = articleService.getCountALLArt();
        // b.计算页数: 文章文章总条数/一页所展现的文章数量(无论获得的值小数点多小,都需要进1)
        double pcountdb = artCount/(psize*1.0);
        int pcount = (int) Math.ceil(pcountdb);
        HashMap<String,Object> result = new HashMap<>();
        result.put("list",list);
        result.put("pcount",pcount);
        return AjaxResult.success(result);
    }

🎆总结

总的来说,这个项目内容的覆盖率非常大,项目的亮点在于登录拦截器的实现、登录验证码的实现、文章分页、手动实现加盐算法、Redis存储Session。

最后附上代码仓库:点击跳转:个人博客项目代码

如果你有任何疑问,可以添加下方微信!

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

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

相关文章

VSCode打开终端的方法

VScode打开终端的方法 第一种&#xff1a;快捷键 Ctrl ~ 第二种&#xff1a;选中某个文件&#xff0c;右键&#xff0c;点击“在集成终端中打开” 第三种&#xff1a;在VSCode的页面上方的选项&#xff0c;点击“终端”&#xff0c;再点击“新建终端” 打开后&#xff0c;…

事务,不只ACID

大家好&#xff0c;我是 方圆。一提到事务&#xff0c;最先让我想到的就是ACID和倒背如流的隔离级别。它确实和这些相关&#xff0c;但是在我读了《数据密集型应用系统设计》之后&#xff0c;我想把事务这个主题讲的不那么“传统”。本文的部分内容可能读起来会有些老生常谈的感…

【Vue】在el-table的el-table-column中,如何控制单行、单列、以及根据内容单独设置样式。例如:修改文字颜色、背景颜色

用cell-style表属性来实现。在官网中是这样表述这个属性的。 在el-table中用v-bind绑定此属性。&#xff08;v-bind的简写是&#xff1a;&#xff09; <el-table:data"options":cell-style"cell"><el-table-column prop"id" label"…

医疗小程序:提升服务质量与效率的智能平台

在医疗行业&#xff0c;公司小程序成为提高服务质量、优化管理流程的重要工具。通过医疗小程序&#xff0c;可以方便医疗机构进行信息传播、企业展示等作用&#xff0c;医疗机构也可以医疗小程序提供更便捷的预约服务&#xff0c;优化患者体验。 医疗小程序的好处 提升服务质量…

四章:Constrained-CNN losses for weakly supervised segmentation——弱监督分割的约束CNN损失函数

0.摘要 基于部分标记图像或图像标签的弱监督学习目前在CNN分割中引起了极大关注&#xff0c;因为它可以减轻对完整和繁琐的像素/体素注释的需求。通过对网络输出施加高阶&#xff08;全局&#xff09;不等式约束&#xff08;例如&#xff0c;约束目标区域的大小&#xff09;&am…

具身智能controller---RT-1(Robotics Transformer)(中---实验介绍)

6 实验 实验目的是验证以下几个问题: RT-1可以学习大规模指令数据&#xff0c;并且可以在新任务、对象和环境上实现zero-shot的泛化能力&#xff1f;训练好的模型可以进一步混合多种其他数据&#xff08;比如仿真数据和来自其他机器人的数据&#xff09;吗&#xff1f;多种方…

玄子Share - Redis 双系统安装教程 Linux Windows(附安装包)

玄子Share - Redis 双系统安装教程 Linux Windows&#xff08;附安装包&#xff09; Linux 安装 Redis 前置条件 Linux 本地体验需安装 Linux 虚拟机 Linux 安装 Redis 需学到第 15 节 【小白入门 通俗易懂】2021韩顺平 一周学会Linux https://www.bilibili.com/video/BV1Sv…

基于POX交叉的遗传算法求解车间调度

对于流水车间调度问题&#xff0c;n个工件在m台设备上加工&#xff0c;已知每个工件每个工序使用的机器和每个工件每个工序所用时间&#xff0c;通过决策每个机器上工件的加工顺序和每个工序的开始时间&#xff0c;使完成所有工序所用时间(makespan)最小。具有下列约束&#xf…

【计算机网络】应用层协议 -- HTTP协议

文章目录 1. 认识HTTP协议2. 认识URL3. HTTP协议格式3.1 HTTP请求协议格式3.2 HTTP响应协议格式 4. HTTP的方法5. HTTP的状态码6. HTTP的Header7. Cookie和Session 1. 认识HTTP协议 协议。网络协议的简称&#xff0c;网络协议是通信计算机双方必须共同遵守的一组约定&#xff0…

直线导轨的主要功能

直线导轨是一种常见的机械结构&#xff0c;用于工业机器人、数控机床和其他自动化装置中。它的作用是提供一个准确的直线运动轨道&#xff0c;使得设备能够在预定的路径上进行精确的移动。 直线导轨作为一种重要的机械基础件&#xff0c;在现代工业中得到了广泛的应用。它主要的…

双非二本想进嵌入式行业?

二本的话学历上会吃点亏&#xff0c;但也没有特别夸张。嵌入式毕竟是技术岗&#xff0c;主要还是看自己的技术能力。嵌入式的话&#xff0c;在北上广深&#xff0c;稍微好点的企业研究生学历都能开到20K以上&#xff0c;本科生会低个2K左右&#xff0c;像大疆、华为更高&#x…

Too many files with unapproved license: 2 See RAT report

解决方案 mvn -Prelease-nacos -Dmaven.test.skiptrue -Dpmd.skiptrue -Dcheckstyle.skiptrue -Drat.numUnapprovedLicenses100 clean install 或者 mvn -Prelease-nacos -Dmaven.test.skiptrue -Drat.numUnapprovedLicenses100 clean install

CPLD在线升级

文章目录 前言一、JTAG芯片介绍二、JTAG协议分析1.TAP状态机 前言 CPLD&#xff08;Complex Programmable Logic Device&#xff09;是一种可编程逻辑器件&#xff0c;可以用于实现数字逻辑电路的功能。CPLD通常包含可编程逻辑单元&#xff08;如逻辑门阵列&#xff09;和可编…

单向链表SingleLink

1.实现单向链表 public class SingleLink {private Node head;private int size;private class Node{private Object data;private Node next;public Node(Object data) {this.data data;}}public SingleLink() {// TODO Auto-generated constructor stubhead null;size 0;}…

Android 通用带箭头提示窗

简介 自定义PopupWindow, 适用于提示类弹窗。 使用自定义Drawable设置带箭头的背景&#xff0c;测试控件和弹窗的尺寸&#xff0c;自动设置弹窗的显示位置&#xff0c;让箭头指向锚点控件的中间位置&#xff0c;且根据锚点控件在屏幕的位置&#xff0c;自动适配弹窗显示位置。…

作为前端应该了解的后端常识

1、前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 什么是服务端 服务端&#xff0c;又称后端、server 端前端是用户可见、可操作的部分&#xff0c;如树枝树叶服务端为前端提供 “支撑”和 “营养”&…

Ubuntu22.04 locale出错

问题&#xff1a; locale: Cannot set LC_CTYPE to default locale: No such file or directory locale: Cannot set LC_MESSAGES to default locale: No such file or directory locale: Cannot set LC_ALL to default locale: No such file or directory 解决参考&#xff…

下载Google113版本无更新组件,禁止更新

自动化测试下载谷歌驱动需要与浏览器版本一致&#xff0c;需要设置google浏览器禁止自动更新&#xff0c;这样google就可以不再自动更新了&#xff0c;目的是防止浏览器更新后&#xff0c;那么浏览器驱动也需要同时更新&#xff0c;这样在工作中会十分麻烦。 因此这里提供无更…

day45-SpringMVC

0目录 SpringMVC 1.2.3 1.SpringMVC 1.1 引入依赖&#xff1a; <!--SpringMVC的依赖--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0&…

如何在 docker hub 分享自己的镜像?看完不踩坑

前言&#xff1a;前几天vip课讲了如何创建配置jenkins容器&#xff0c;怕大家踩坑&#xff0c;我提前打好了jenkins镜像&#xff0c;直接让大家通过命令去拉取镜像就可以了。 然而&#xff0c;很多好学的同学来问这个是怎么操作的。今天就来聊一聊&#xff0c;怎么将自己打好的…