个人博客系统——SSM框架

项目特点:

1.使用手工加盐算法代替明文,提高用户隐私安全性。

2.登录功能的验证使用了拦截器。

3.支持分布式 Session存储和缓存都放到了Redis里面。

具体实现步骤

1.创建一个SSM项目

个人博客系统——SSM框架_第1张图片

2.准备项目

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

个人博客系统——SSM框架_第2张图片

 引入前端页面(resources-static)

个人博客系统——SSM框架_第3张图片

 添加项目常用配置(在resources下创建一个application.yml,并删除application.properties)

在我的gitee的代码片段中复制SSM常用配置

# 配置数据库的连接字符串
 
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1/mycnblog?characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
 
# 设置 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

复制后注意!看我们所使用的数据库名字是否为mycnblog 密码是否正确

3.初始化数据库

-- 创建数据库
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 datetime default current_timestamp,
    updatetime datetime default current_timestamp,
    `state` int default 1
) default charset 'utf8mb4';
 
-- 创建文章表
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';
 
-- 添加一个用户信息
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,uid)
    values('Java','Java正文',1);

4.创建项目分层

先建这几个 后面再有需要再建

个人博客系统——SSM框架_第4张图片

5.添加统一的返回类型

先在demo下建一个公共包(common)公共类(AjaxResult)

前端和后端使用ajax交互,返回统一的结果 :AjaxResult

//AjaxResult.java
package com.example.demo.common;
 
import lombok.Data;
 
import java.io.Serializable;
 
/**
 * 统一数据格式返回
 * implements Serializable 实现序列化
 */
@Data
public class AjaxResult implements Serializable {
    //状态码
    private Integer code;
    //状态码的描述信息
    private String msg;
    //返回的数据 不知道返回的数据类型是啥 所以使用Object
    private Object data;
 
    /**
     * 操作成功返回的结果
     */
    public static AjaxResult success(Object data){
        AjaxResult result = new AjaxResult();
        result.setCode(200);
        result.setMsg("");
        result.setData(data);
        return result;
    }
    //进行方法的重载 自定义code
    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;
    }
}

//ResponseAdvice.java
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;
 
/**
 * 实现统一数据返回的保底类
 * 说明:在返回数据之前,检测数据的类型是否为统一的对象;如果不是,封装成统一的对象
 */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * 开关,如果是true的时候,才能调用beforeBodyWrite
     */
    @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;
        if(body instanceof String){
            //先把body封装成统一的对象,因其为String类型,再把它转为json类型
            return objectMapper.writeValueAsString(AjaxResult.success(body));
 
        }
        return AjaxResult.success(body);
    }
}

6.实现用户的注册功能

前端代码

1.非空校验

2.判断两次密码是否一致

3.ajax提交请求

#在head中增加jquery
#在提交按钮中添加onclick

前端页面修改完代码不生效,此时极大的概率是缓存

1.先清空idea的缓存,删除项目目录下的target文件夹,然后重启项目

2.强制刷新浏览器

3.如果前两步还没有解决缓存问题,那么尝试给url添加上一个没有意义的参数 比如?v=1

ajax技术

个人博客系统——SSM框架_第5张图片
后端代码

 后端代码写的时候注意顺序!一个调用一个 因此我们先在UserMapper.java/ArticleMapper.java中写,再在对应的*Mapper.xml中写SQL语句,再在*Service.java中写这个方法,最后再在*Controller.java中写具体的实现!后面的后端代码基本都是这个顺序写

//demo-entity-UserInfo.java
package com.example.demo.entity;
 
import lombok.Data;
 
import java.time.LocalDateTime;
 
@Data
public class UserInfo {
    //Integer 比int的兼容性更好
    private Integer id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private Integer state;
}

//mapper-UserMapper.java
package com.example.demo.mapper;
 
import com.example.demo.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
 
@Mapper
public interface UserMapper {
 
    //注册
    int reg(UserInfo userInfo);
}
//resources-mapper-UserMapper.xml



    
        insert into userinfo(username,password) values(#{username},#{password})
    

//service-UserService.java
package com.example.demo.service;
 
import com.example.demo.entity.UserInfo;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
 
@Service
public class UserService {
 
    @Resource
    private UserMapper userMapper;
 
    public int reg(UserInfo userInfo) {
        return userMapper.reg(userInfo);
    }
}

//controller-UserController.java
package com.example.demo.controller;
 
import com.example.demo.common.AjaxResult;
import com.example.demo.entity.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping("/user")
public class UserController {
 
    @Autowired
    private UserService userService;
 
    @RequestMapping("/reg")
    public AjaxResult reg(UserInfo userInfo) {
        //非空校验和参数有效性校验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())||
                !StringUtils.hasLength(userInfo.getPassword())) {
            return AjaxResult.fail(-1,"非法参数");
        }
        return AjaxResult.success(userService.reg(userInfo));
    }
}

7.根据用户查询userinfo对象

前端代码

#在head中增加jquery

#在提交按钮中添加onclick

后端代码

//在UserMapper.java中加入getUserByName方法
package com.example.demo.mapper;
 
import com.example.demo.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
 
@Mapper
public interface UserMapper {
 
    //注册
    int reg(UserInfo userInfo);
 
    /**
     * 登陆 根据用户查询userinfo对象
     * 说明:只传username 在UserController里进行password的比对
     */
    UserInfo getUserByName(@Param("username") String username);
 
}

#在UserMapper.xml中添加select操作



    
        insert into userinfo(username,password) values(#{username},#{password})
    
 
    

//在UserService中加入getUserByName()方法,并返回username
package com.example.demo.service;
 
import com.example.demo.entity.UserInfo;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
 
@Service
public class UserService {
 
    @Resource
    private UserMapper userMapper;
 
    public int reg(UserInfo userInfo) {
        return userMapper.reg(userInfo);
    }
 
    public UserInfo getUserByName(String username){
        return userMapper.getUserByName(username);
    }
}

//在UserController中添加login方法,并传入用户名和密码
在UserController中的login方法里 将用户存储到session中,不然之后的删除博客等操作无法进行
@RequestMapping("/login")
    public AjaxResult login(HttpServletRequest request, String username, String 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(password.equals(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);
 
    }

//在common下建立一个全局变量类 AppVariable.java
public class AppVariable {
    public static final String USER_SESSION_KEY = "USER_SESSION_KEY";
 
}

8.拦截器

先写一个拦截器LoginInterceptor,这个拦截器要实现HandlerInterceptor(拦截管理器)的接口,里面去实现preHandle的方法

然后要把prehandle加到全项目的全局配置文件中

//config-LoginInterceptor.java
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 ->用户未登录
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //如果有与当前的request相关联的HttpSession,则返回该HttpSession;如果没有则返回null;
        HttpSession session =request.getSession(false);
        if(session!=null &&session.getAttribute(AppVariable.USER_SESSION_KEY) != null){
            //用户已登录
            return true;
        }
        //调整到登陆页面
        response.sendRedirect("/login.html");
        return false;
    }
}

//在系统的配置文件中配置拦截规则
//在config下建AppConfig.java 实现一个WebMvcConfigurer的配置文件
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;
 
@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/editor.md/**")
                .excludePathPatterns("/img/**")
                .excludePathPatterns("/js/**")
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/reg.html")
                .excludePathPatterns("/blog_list.html")
                .excludePathPatterns("/blog_content.html")
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/reg");
 
 
    }
}

注意:此时的session是借助cookie储存到cookie中的 后期会部署到Redis上

9.我的博客列表页

获得左侧个人信息

前端代码

#在head中增加jquery

更改这两处 实现动态赋值

个人博客系统——SSM框架_第6张图片

 

后端代码

//此处对应博客列表articleinfo 因此在demo-mapper下建ArticleMapper.java
package com.example.demo.mapper;
 
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
 
@Mapper
public interface ArticleMapper {
    int getArtCountByUid(@Param("uid") Integer uid);
}
#因此在resources-mapper下建ArticleMapper.xml



 
    

此时生成一个单元测试 进行测试

package com.example.demo.mapper;
 
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
 
import javax.annotation.Resource;
 
import static org.junit.jupiter.api.Assertions.*;
 
@SpringBootTest
class ArticleMapperTest {
 
    @Resource
    private ArticleMapper articleMapper;
 
    @Test
    void getArtCountByUid() {
        int result = articleMapper.getArtCountByUid(1);
        System.out.println("文章总数: "+result);
    }
}

个人博客系统——SSM框架_第7张图片

 可以看到这里文章总数为1 因此ArticleMapper没有问题,接着写

//demo-controller-UserController.java
@Autowired
    private ArticleService articleService;

此时返回的数据不再是基础数据类型,而是articlecount,因此新建vo-UserinfoVO.java 展示的一个对象

package com.example.demo.vo;
 
import com.example.demo.entity.UserInfo;
import lombok.Data;
 
@Data
public class UserinfoVO extends UserInfo {
    private Integer artCount;//此人发表的文章总数
}

接着在demo-controller-UserController里写
  1.得到当前登录用户(从session中获取 )
  2.得到用户发表的文章总数
但是此处的session不是只在我的博客列表页进行获取

//在demo-common下建UserSessionUtils.java
package com.example.demo.common;
 
import com.example.demo.entity.UserInfo;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
 
/**
 * 当前登陆用户相关的操作
 */
public class UserSessionUtils {
    /**
     * 得到当前的登录用户
     * @param request
     * @return
     */
    public static UserInfo getUser(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;
    }
}

//demo-controller-UserController.java
@RequestMapping("/showinfo")
    public AjaxResult showInfo(HttpServletRequest request){
        UserinfoVO userinfoVO = new UserinfoVO();
        //1.得到当前登录用户(从session中获取 )
        UserInfo userInfo = UserSessionUtils.getUser(request);
        if(userInfo ==null){
            return AjaxResult.fail(-1,"非法请求");
        }
        //Spring提供的深克隆方法
        BeanUtils.copyProperties(userInfo,userinfoVO);
        //2.得到用户发表的文章总数
        userinfoVO.setArtCount(articleService.getArtCountByUid(userInfo.getId()));
        return AjaxResult.success(userinfoVO);
    }

获得我的文章列表数据

获取我的文章列表要不要传uid给后端?

不需要传递任何参数,并且是一定不能传参的

前端代码

后端代码

//建一个实体类 Articleinfo.java
package com.example.demo.entity;
 
import lombok.Data;
 
import java.time.LocalDateTime;
 
@Data
public class Articleinfo {
    private Integer id;
    private String title;
    private String content;
    private Data createtime;
    private Data updatetime;
    private Integer uid;
    private Integer rcount;
    private Integer state;
}

//在AriticleMapper.java里添加getMyList方法
package com.example.demo.mapper;
 
import com.example.demo.entity.Articleinfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
 
import java.util.List;
 
@Mapper
public interface ArticleMapper {
    int getArtCountByUid(@Param("uid") Integer uid);
 
    List getMyList(@Param("uid") Integer uid);
}

//在AriticleMapper.xml里添加

//ArticleService.java
public class ArticleService {
    @Resource
    private ArticleMapper articleMapper;
 
    public int getArtCountByUid(Integer uid){
        return articleMapper.getArtCountByUid(uid);
    }
 
    public List getMyList(Integer uid) {
        return articleMapper.getMyList(uid);
    }
}
//controller-ArticleController.java
package com.example.demo.controller;
 
import com.example.demo.common.AjaxResult;
import com.example.demo.common.UserSessionUtils;
import com.example.demo.entity.Articleinfo;
import com.example.demo.entity.UserInfo;
import com.example.demo.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import javax.servlet.http.HttpServletRequest;
import java.util.List;
 
@RestController
@RequestMapping("/art")
public class ArticleController {
 
    @Autowired
    private ArticleService articleService;
 
    @RequestMapping("/mylist")
    public AjaxResult getMyList(HttpServletRequest request){
        UserInfo userInfo = UserSessionUtils.getUser(request);
        if(userInfo == null){
            return AjaxResult.fail(-1,"非法请求");
        }
        List list = articleService.getMyList(userInfo.getId());
        return  AjaxResult.success(list);
    }
}

SpringBoot时间格式化

1.通过配置文件,设置全局的时间格式化

#在application.yml里的spring中设置jaskson
# 配置数据库的连接字符串
 
spring:
  jackson:
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: "zy19991227"
    driver-class-name: com.mysql.cj.jdbc.Driver
注意事项:此配置对LocalDateTime/LocalDate,需要使用Date数据类型

2.使用@JsonFormat注解

@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;
}

删除文章操作

前端代码

后端代码

//ArticleMapper.java中加入del方法
@Mapper
public interface ArticleMapper {
    int getArtCountByUid(@Param("uid") Integer uid);
 
    List getMyList(@Param("uid") Integer uid);
 
    int del(@Param("id") Integer id,@Param("uid") Integer uid);
}
//ArticleMapper.xml加入sql语句

        delete from articleinfo where id=#{id} and uid=#{uid}

//ArticleService.java中加入del方法
public int del(Integer id,Integer uid){
        return articleMapper.del(id,uid);
}

注销功能(公共模块)

前端代码

后端代码

//UserController.java
@RequestMapping("/logout")
    public AjaxResult logout(HttpSession session){
        session.removeAttribute(AppVariable.USER_SESSION_KEY);
        return AjaxResult.success(1);
    }

测试

同时登陆zhangsan和admin用户,zhangsan用户点击注销

确认是否注销成功? 看注销后的张三能否删除admin用户的文章 不能

10.文章详情页

实现思路:

1.从url中得到文章id

2.从后端查询当前文章的详情信息(以及uid)

3.根据上一步查询的uid查询用户的信息

4.请求后端接口实现阅读量+1

获取当前url参数的公共方法并查询文章详情

前端代码

//在blog_content.html中修改

后端代码

//ArticleMapper.java中加入getDetail方法
Articleinfo getDetail(@Param("id") Integer id);
#ArticleMapper.xml中加入SQL语句

//ArticleService.java中加入getDetail方法
public Articleinfo getDetail(Integer id) {
        return articleMapper.getDetail(id);
}

!!!注意此处需要在AppConfig.java拦截器中开放一个接口

//因为我们还有个所有人的博客列表页,从那里进入博客详情页也是ok的
.excludePathPatterns("/art/detail")

根据上一步查询的uid查询用户的信息

前端代码

后端代码

//根据id获取用户 UserMapper.java
UserInfo getUserById(@Param("id") Integer id);
#UserMapper.xml中加入SQL语句

//UserService.java中加入getUserById方法
public UserInfo getUserById(Integer id){
        return userMapper.getUserById(id);
}
//UserController.java中加入getUserById方法
@RequestMapping("/getuserbyid")
    public AjaxResult getUserById(Integer id){
        if(id==null || id <= 0){
            //无效参数
            return AjaxResult.fail(-1,"非法参数");
        }
        UserInfo userinfo = userService.getUserById(id);
        if(userinfo == null || userinfo.getId() <=0){
            //无效参数
            return AjaxResult.fail(-1,"非法参数");
        }
        //取出userinfo中的敏感信息 密码
        userinfo.setPassword("");
        UserinfoVO userinfoVO = new UserinfoVO();
        BeanUtils.copyProperties(userinfo,userinfoVO);
        //查询当前用户发表的文章数
        userinfoVO.setArtCount(articleService.getArtCountByUid(id));
        return AjaxResult.success(userinfoVO);
    }

!!!注意此处需要在AppConfig.java拦截器中开放一个接口

因为文章列表页针对所有由用户开放

.excludePathPatterns("/user/getuserbyid")

请求后端接口实现阅读量+1

实现思路1:

先查询文章的阅读量,然后再+1设置到数据库中

实现思路2:

将两步合二为一,update article set rcount=rcount+1 where id =xxx

前端代码

后端代码

//ArticleMapper.java中定义incrRCount
int incrRCount(@Param("id") Integer id);
#ArticleMapper.xml中更新SQL语句

        update articleinfo set rcount=rcount+1 where id=#{id}

//ArticleService.java中定义incrRCount方法
public int incrRCount(Integer id){
        return articleMapper.incrRCount(id);
}
//ArticleController.java中定义incrRCount方法
@RequestMapping("/incr-rcount")
    public AjaxResult incrRCount(Integer id){
        if(id != null && id > 0){
            return  AjaxResult.success(articleService.incrRCount(id));
        }
        return AjaxResult.success(-1,"未知错误");
    }

!!!注意此处需要在AppConfig.java拦截器中开放一个接口 否则出现302错误

.excludePathPatterns("/user/incr-rcount")

11.增加文章页

前端代码

//blog_add.html

后端代码

//ArticleMapper.java
int add(Articleinfo articleinfo);
#ArticleMapper.xml中添加SQL语句

    insert into articleinfo(title,content,uid) values(#{title},#{content},#{uid})

//ArticleService.java中定义 add方法
public int add(Articleinfo articleinfo){
        return articleMapper.add(articleinfo);
}
ArticleController.java中定义 add方法
@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.数据库添加操作
        //a.得到当前登陆用户的id
        UserInfo userInfo = UserSessionUtils.getUser(request);
        if(userInfo == null || userInfo.getId() <= 0){
            //无效的登录用户
            return AjaxResult.fail(-2,"无效的登录用户");
        }
        articleinfo.setUid(userInfo.getId());
        //b.添加数据库并返回结果
        return AjaxResult.success(articleService.add(articleinfo));
    }

12.修改文章页

1.得到文章id

2.去后端查询文章的详情信息并设置到页面上

3.进行文章修改操作(调用后台)

得到文章id并将文章详情显示到页面

前端代码

修改文章

前端代码

后端代码

//ArticleMapper.java
int update(Articleinfo articleinfo);
#ArticleMapper.xml中添加SQL语句

        update articleinfo set title=#{title},content=#{content},updatetime=#{updatetime}
            where id=#{id} and uid=#{uid}

//ArticleService.java中定义 update方法
public int update(Articleinfo articleinfo){
        return articleMapper.update(articleinfo);
}
//ArticleController.java中定义 update方法
@RequestMapping("/update")
    public AjaxResult update(HttpServletRequest request, Articleinfo articleinfo){
        //1.非空校验
        if(articleinfo == null || !StringUtils.hasLength(articleinfo.getTitle()) ||
                !StringUtils.hasLength(articleinfo.getContent())||
                articleinfo.getId() ==null){
            //非法参数
            return AjaxResult.fail(-1,"非法参数");
        }
        //2.得到当前用户id
        UserInfo userInfo = UserSessionUtils.getUser(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));
    }

13.密码加盐算法

为什么要进行加盐加密?

1.铭文不行,会泄露隐私

2.传统的MD5有规律可循,虽然不可逆,但是有规律可循,可以被暴力破解

【彩虹表:记录了几乎所有字符串的MD5】
3.加盐加密

随机,没有规律可言

加盐实现思路

每次调用方法的时候产生盐值(唯一)+密码 = 最终密码

最终密码还使用MD5加密,但是没关系,此时的MD5没有规律

使用UUID实现唯一密码

解密思路

需要两个密码:

1.需要验证的密码(用户输入的密码)

2.最终加密的密码(存在数据库中的密码)

核心思想:得到盐值 这个盐值会放到最终密码的某个位置

最终密码格式(65位):盐值(32位)$加密后的密码(32位)

难以解密的原因:没有绝对安全的密码,解密者不知道加密的规律,就算你知道我的加盐的密码,但是构造彩虹表是需要时间的,一个密码就需要构建一个彩虹表,但是数据库中有成千上万的彩虹表,破解成本极高

验证密码的伪代码:

已知:用户输入的明文密码、此用户在数据库存储的最终密码=盐值$加密后的密码

1.从最终密码中得到盐值

2.将用户输入的明文密码+盐值进行加密操作=加密后的密码

3.使用 盐值$加密后的密码 生成数据库存储的密码

4.对比生成的最终密码和数据库最终的密码是否相等

如果相等,那么用户名和密码就是对的,反之密码输入错误

后端代码

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){
        //a.产生盐值(UUID生成的36位,有4位-,因此删去后为32位)
        String salt = UUID.randomUUID().toString().replace("-","");
        //b.生成加盐之后的密码
        String saltPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
        //c.生成最终密码(保存到数据库中的密码)【约定:盐值(32位)+$+加密后的密码(32位)】
        String finalPassword = salt + "$" + saltPassword;
        return finalPassword;
    }
 
    /**
     * 2.生成加盐密码(步骤1的重载)
     * @param password 明文
     * @param salt 固定的盐值
     * @return 最终密码
     */
    public static String encrypt(String password,String salt){
        //a.生成一个加盐后的密码
        String saltPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
        //b.生成最终的密码【约定:盐值(32位)+$+加密后的密码(32位)】
        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){
            //a.得到盐值
            String salt = finalPassword.split("\\$")[0];
            //b.使用之前加密的步骤,将明文密码和已经得到的盐值进行加密,生成最终的密码
            String confirmPassword = PasswordUtils.encrypt(inputPassword,salt);
            //c.对比两个最终密码是否相同
            return confirmPassword.equals(finalPassword);
        }
        return false;
    }
 
/*    public static void main(String[] args) {
        String password = "admin";
        String finalPassword = PasswordUtils.encrypt(password);
        System.out.println("加密" + finalPassword);
        //对比
*//*        String inputPassword = "12345";
        System.out.println("对比 "+ inputPassword
                +"是否等于" + password + "->" +PasswordUtils.check(inputPassword,finalPassword));*//*
        String inputPassword2 = "admin";
        System.out.println("对比 "+ inputPassword2
                +"是否等于" + password + "->" +PasswordUtils.check(inputPassword2,finalPassword));
    }*/
}

测试

public static void main(String[] args) {
        String password = "123456";
        String finalPassword = PasswordUtils.encrypt(password);
        System.out.println("加密" + finalPassword);
 
        //对比
        String inputPassword = "12345";
        System.out.println("对比 "+ inputPassword
                +"是否等于" + password + "->" +PasswordUtils.check(inputPassword,finalPassword));
        String inputPassword2 = "123456";
        System.out.println("对比 "+ inputPassword2
                +"是否等于" + password + "->" +PasswordUtils.check(inputPassword2,finalPassword));
    }

将数据库明文密码加密

update userinfo set password='12ad512af18f426e8e2248a17d8f2f92$40ffec117f74b4bf2a3fc0a849b7e0db';

在UserController.java的login方法中更改

if(password.equals(userInfo.getPassword()))
if(PasswordUtils.check(password,userInfo.getPassword())

在UserController.java的reg方法中更改

//密码加盐处理
userInfo.setPassword(PasswordUtils.encrypt(userInfo.getPassword()));

14.使用Spring Security 进行加盐和验证实现步骤

1.引入Spring Security 框架


            org.springframework.boot
            spring-boot-starter-security

只用其类库,不用自动注入等功能

2.排除Spring Security自动加载

启动类中加这样一句话

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})

3.调用Spring Security加盐和验证

测试

package com.example.demo;
 
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.DigestUtils;
 
@SpringBootTest
class DemoApplicationTests {
 
    @Test
    void contextLoads() {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = "123456";
        String finalPassword = passwordEncoder.encode(password);
        System.out.println("第1次加密:" + finalPassword);
        System.out.println("第2次加密:" + passwordEncoder.encode(password));
        System.out.println("第3次加密:" + passwordEncoder.encode(password));
        // 验证
        String inputPassword = "12345";
        System.out.println("错误密码比对结果:" +
                (passwordEncoder.matches(inputPassword, finalPassword)));
        String inputPassword2 = "123456";
        System.out.println("正确密码比对结果:" +
                (passwordEncoder.matches(inputPassword2, finalPassword)));
 
    }
 
 
/*    public static void main(String[] args) {
        String password = "123456";
        String mdString = DigestUtils.md5DigestAsHex(password.getBytes());
        System.out.println(mdString);
    }*/
}

个人博客系统——SSM框架_第8张图片

 15.所有博客列表页

分页页面

分页关键实现分析:

前端:当前页面【每页显示条数固定显示最大2条】

后端:当前页码、每页显示最大条数

个人博客系统——SSM框架_第9张图片

公式:(当前页码n-1)*每页显示最大条数psize =offset

后端代码

//ArticleMapper.java
List getListByPage(@Param("psize") Integer psize,
                                    @Param("offsize") Integer offsize);

单元测试

package com.example.demo.mapper;
 
import com.example.demo.entity.Articleinfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
 
import javax.annotation.Resource;
 
import java.util.List;
 
import static org.junit.jupiter.api.Assertions.*;
 
@SpringBootTest
class ArticleMapperTest {
 
    @Resource
    private ArticleMapper articleMapper;
 
    @Test
    void getArtCountByUid() {
        int result = articleMapper.getArtCountByUid(1);
        System.out.println("文章总数: "+result);
    }
 
    @Test
    void getListByPage() {
        List list = articleMapper.getListByPage(3,0);
        System.out.println(list);
        System.out.println("-------------- ");
        List list2 = articleMapper.getListByPage(3,3);
        System.out.println(list);
    }
}

//ArticleService.java中定义 getListByPage方法
public List getListByPage(Integer psize,Integer offsize){
        return articleMapper.getListByPage(psize,offsize);
    }
//ArticleController.java中定义 getListByPage方法
/**
     *查询列表根据分页
     * @param pindex 当前页码(从1开始)
     * @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;
        }
        //分页公式的值 = (当前页码n-1)*每页显示最大条数psize
        int offsize = (pindex - 1) * psize;
        List list = articleService.getListByPage(psize,offsize);
        return AjaxResult.success(list);
    }

前端代码



 

    
    
    
    博客列表
    
    
    
    
    

 

    
    
    
    
               
           
           
                        
           
                                                                               
       
   
   

获取文章总条数

后端代码

//ArticleMapper.java
int getCount();
#ArticleMapper.xml

//ArticleService.java
public int getCount() {
       return articleMapper.getCount();
}
//ArticleController.java
/**
     *查询列表根据分页
     * @param pindex 当前页码(从1开始)
     * @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;
        }
        //分页公式的值 = (当前页码n-1)*每页显示最大条数psize
        int offsize = (pindex - 1) * psize;
        //文章列表数据
        List list = articleService.getListByPage(psize,offsize);
        //当前列表总共多少页
        //a.总共多少条数据
        int totalCount = articleService.getCount();
        //b.总条数/psize(每页显示条数)
        double pcountdb = totalCount / (psize * 1.0);
        //c.使用进一法得到总页数
        int pcount = (int)Math.ceil(pcountdb);
        HashMap result = new HashMap<>();
        result.put("list",list);
        result.put("pcount",pcount);
        return AjaxResult.success(result);
    }

你可能感兴趣的:(java)