基于ssm实现的简单个人博客

目录

1. 综述

2. 实现步骤

2.1 创建一个SSM项目

2.2 准备项目

2.2.1 删除无用文件

2.2.2 引入前端页面

2.2.3 添加配置文件

2.3 初始化数据库

2.4 进行项目分层

2.5 添加统一返回类型

2.6 实现用户的注册功能

2.7 实现用户的登录功能

2.8 实现用户详情页

2.9 注销功能的实现

2.10 删除文章

2.11 查看文章详情页

2.11 显示文章作者的详情信息

2.12 请求详情页时使得阅读量增加

2.13 文章的添加和修改功能

2.14 密码加盐处理

2.15 所有人的博客页面以及分页功能的实现


1. 综述

博客(Blog)是一种基于网络技术的在线日记,它是由一组文章组成的网站,它允许个人或团体在互联网上发布文章、观点、图片和视频等内容,同时也允许访问者对这些内容进行评论和互动交流。

博客的作用非常广泛,它可以用于个人记录生活、分享经验和见解;也可以用于商业推广、品牌宣传和营销推广;还可以用于新闻报道、时事评论和社会观察等方面。博客不仅是一种媒介,更是一种社交工具,它可以帮助人们建立社交网络和人际关系,增加知识和经验的交流和共享。

博客的特点是实时更新、互动交流、内容多样化和自由性强,它为人们提供了一个开放的平台,让个人和团体可以借助互联网的力量,传递自己的声音和信息,影响和改变社会。

包括我们目前正在使用的CSDN也是博客平台,我们可以在这上面交流技术,分享见解等等等,那么你有没有想过自己实现一个个人博客项目呢?接下来我将使用ssm框架进行个人博客项目的实现。(以专业版IDEA为例)

2. 实现步骤

2.1 创建一个SSM项目

首先,我们要在IDEA中创建出一个SSM项目,其步骤具体如下:

首先要新建一个项目,选择Spring Initializr,同时要记得下面的Java版本要选JDK8,其余具体配置如下图:

 随后点击next,Spring Boot的版本注意选择2.X.X版本的,我这里就选择了2.7.10,注意后面有括号的一般指的是测试版,可能有些不稳定,尽量不要选有括号的

然后我们要分别勾选Spring Boot DevTools,Lombok,Spring Web,MySQL Driver以及MyBatis Framework这五大依赖,随后点击finish即可,如下图:


2.2 准备项目

在进行正式的项目实现前,我们需要先对项目进行初始化。

2.2.1 删除无用文件

 见上图,其中.mvn、HELP.md、mvnw、mvnw.cmd都属于无用文件,可以删除,当然,不删除也是没什么问题的。


2.2.2 引入前端页面

作为一个ssm框架实现的项目,我们是需要前端页面和后端代码实现的,但是我们作为后端程序员,前端页面自然不用我们自己写,关于前端页面的代码可以去我的gitee中引入,这里就放在文章的末尾了,当我们拿到前端的代码的时候,我们要将其放置在resources文件下的static目录中,添加后如下图:


2.2.3 添加配置文件

配置文件分为properties以及yml,由于properties较为老旧,我们通常使用yml的配置文件,因此我们需要选择在resources下新建一个application.yml的文件,同时删除掉application.properties文件

 随后我们要在配置文件中,即application.yml中添加相对应的配置信息,如下

# 配置数据库的连接字符串
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
    username: root
    password: "021226"
    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

以上有两点需要注意,这个配置文件中,password要改成你数据库自己的密码还有url中,

url: jdbc:mysql://127.0.0.1:3306/连接的数据库名字?characterEncoding=utf8,即问号前面要改成你要连接哪个数据库,还有配置打印MyBatis执行的SQL的目录要改成对应的目录,我这里的话是com.example.demo,故就这样写,其他地方可以不需要进行改动


2.3 初始化数据库

我们需要创建一个对应的database,即数据库,随后往数据库中添加两张表,一张为用户表,其字段为id,username,password,photo,createtime,updatetime,state;

另一张为文章表,字段为id,title,content,createtime,updatetime,uid,rcount,state这里可以直接复制这里的代码到数据库中即可

-- 创建数据库
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(65) not null,
    photo varchar(500) default '',
    createtime timestamp default current_timestamp,
    updatetime timestamp 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 timestamp default current_timestamp,
    updatetime timestamp 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';

-- 添加一个用户信息
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);
    
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);

2.4 进行项目分层

一个SSM项目,应该由controller层,service层,mapper层,config层以及实体类,我们要在demo下创建这几个软件包,同时在resoucrs下也创建出一个文件目录,名为mapper用于后面的MyBatis的操作


2.5 添加统一返回类型

我们在demo下新建一个common的package,同时创建一个类名为AjaxResult用于规定统一返回类型

 其中规定返回类型时,有状态码,状态码的具体信息以及返回的数据,我们需要考虑失败时以及成功时的情况,进行不同方法的创建,同时要重载方法,使得传值的时候更加灵活

package com.example.demo.common;

import lombok.Data;

import java.io.Serializable;

/*
统一数据格式返回
 */
@Data
public class AjaxResult implements 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(Object data,int code){
        AjaxResult result=new AjaxResult();
        result.setCode(code);
        result.setMsg("");
        result.setData(data);
        return result;
    }
    public static AjaxResult success(Object data,int code, String msg){
        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;
    }
}

但是并不是所有人都会使用这个类来进行统一数据格式来返回的,于是我们要做出以防万一的手段,使得如果返回的数据类型被检测为不是统一的对象,我们就将其封装为统一的对象,我们在common包下创建出一个ResponseAdvice类用来实现这一功能

实现这一功能需要添加ControllerAdvice注解,同时要实现ResponseBodyAdvice接口,重写supports方法和beforeBodyWrite方法

在进行处理时,如果是AjaxResult类型,直接返回即可;如果是String类型需要进行特殊处理,需要使用jackson进行类型的转化

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;
    // 开关,唯有返回值为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;
        }
        //如果是字符串类型,我们需要引入ObjectMapper进行数据的处理,将其处理为json格式
        if(body instanceof String){
                return objectMapper.writeValueAsString(AjaxResult.success(body));
        }
        return AjaxResult.success(body);
    }
}

2.6 实现用户的注册功能

实现用户的注册功能,我们可以先从前端到后端;我们要先编写reg.html,用于发送ajax请求,这里就不进行前端的编写了,可以直接去我的源码那里复制粘贴即可,这里接下来要演示后端的写法

进行后端的操作时,我们要先在controller下创建一个UserController,控制器是一个安保系统,来验证前端发来的数据的可靠性,再调用UserService进行服务的编排和调度,最后UserService调用UserMapper进行用户的添加

在进行用户的添加时,我们需要先创建出用户这个类,即在entity包下创建一个Userinfo类

package com.example.demo.entity;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class Userinfo {
    private Integer id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private Integer state;
}

其中@Data注解是Lombok提供的注解,可以让我们不需要写get,set方法等也能够使用对应方法

创建好用户类后,我们需要在mapper下创建一个UserMapper接口进行操作的描写

同时要在resources下刚刚创建的mapper文件夹中新建一个名为UserMapper.xml的文件,此时目录结构如下图:

 UserMapper.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.UserMapper">
    
</mapper>

其中要注意修改namespace中的目录结构和类名,随后就可以进行添加操作的描写

添加是insert操作,id为reg,其sql如下:

<?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.UserMapper">
    <insert id="reg">
        insert into userinfo(username,password) values(#{username},#{password})
    </insert>
</mapper>

然后我们再在service包下创建出UserService类,其中要注入userMapper同时,要调用userMapper的reg方法

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

随后我们再在UserMapper类中也进行注册代码的完善,如下:

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

到这里,注册的功能就已经实现了


2.7 实现用户的登录功能

与注册功能类似,我们也需要前端与后端的编写,同样,前端部分可以去我的源码处复制粘贴即可

后端部分,我们同样从mapper部分开始进行写

这里同时也需要在sql中给用户名添加唯一约束,为了后面根据用户名查询user对象时不会报错

alter table userinfo add unique(username);

然后在userMapper中添加一个查询操作,即根据用户名来获取用户对象

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对象
    Userinfo getUserByName(@Param("username") String username);
}

同时在UserMapper.xml中添加对应的sql语句

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

然后再在UserService中添加相应的方法

    public Userinfo getUserByName(String username){
        return userMapper.getUserByName(username);
    }

然后在controller中创建一个相应的方法,其过程具体如下

    @RequestMapping("/login")
    public AjaxResult login(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())){
                //登录成功
                userinfo.setPassword(""); //返回前端之前,隐藏敏感信息
                return AjaxResult.success(userinfo);
            }
        }
        return AjaxResult.success(null,0);
    }

到这里,登陆功能的代码就已经基本实现了,不过上述controller的代码仍有问题,用户在成功登陆后,我们应该将用户的信息存储到session中,我们需要先获取到HttpServletRequest对象,再获取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())){
                //登录成功
                //todo:将用户存储到session中
                HttpSession session=request.getSession();
                session.setAttribute(AppVariable.USER_SESSION_KEY,userinfo);
                userinfo.setPassword(""); //返回前端之前,隐藏敏感信息
                return AjaxResult.success(userinfo);
            }
        }
        return AjaxResult.success(null,0);
    }

同时我们可以在common下创建一个类,这个类中用于存储全局变量,比如说session的key值等

package com.example.demo.common;
/*
全局变量
 */
public class AppVariable {
    //用户 session key
    public static final String USER_SESSION_KEY="USER_SESSION_KEY";
}

在完成了登陆操作后,由于用户可能会在未登陆状态下点击功能,此时我们需要拦截用户,让其去登陆,故我们要写一个登陆拦截器,我们在config下新建一个LoginIntercept类,让其实现HandlerInterceptor接口,并重写preHandle方法

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 LoginIntercept implements HandlerInterceptor {
    /*
    返回true即用户已登陆,false即用户未登陆
    * */
    @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;
    }
}

拦截器写完后,我们要将其配置到系统文件内,我们在config下新建一个AppConfig类,让其实现WebMvcConfigurer接口,并添加@Configuration注解,我们选定部分路径选择不进行拦截

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 LoginIntercept())
                .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");
    }
}

到这里结束后,登陆功能就已经完成了。


2.8 实现用户详情页

用户详情页也同样需要前后端进行实现,前端代码还是可以复制我的源码

后端部分,我们同样开始从mapper进行实现,同时想要获取文章的内容,应该去查文章表,所以我们要先在mapper目录下新建一个ArticleMapper接口

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也创建一个对应的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="Integer">
        select count(*) from articleinfo where uid=#{uid};
    </select>
</mapper>

接下来,跟上面类似,我们要在service下创建出一个ArticleService类

package com.example.demo.service;

import com.example.demo.mapper.ArticleMapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class ArticleService {
    @Resource
    private ArticleMapper articleMapper;
    public int getArtCountByUid(Integer uid){
        return articleMapper.getArtCountByUid(uid);
    }
}

接下来要进行Controller的实现,我们在UserController中注入ArticleService类,并写出相应方法

但是这里多了个新的属性,文章总数,所以我们可以新建一个类,让这个类继承Userinfo,同时在这个类中添加新的属性artCount,我们可以在entity下新建一个package,然后新建一个类进行继承

package com.example.demo.entity.vo;

import com.example.demo.entity.Userinfo;
import lombok.Data;

@Data
public class UserinfoVO extends Userinfo {
    private Integer artCount;//发表的文章总数
}

由于需要多次用到用户的登陆信息,我们可以在common下新建一个公共类,用来提供当前登陆用户相关的操作

package com.example.demo.common;

import com.example.demo.entity.Userinfo;

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

/*
当前登陆用户相关操作
 */
public class UserSessionUtils {
    //得到当前登陆用户
    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;
    }
}

此时,我们就可以进行登陆用户相关的信息补充,即在UserController类中进行用户文章数目的填充

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

        return AjaxResult.success(userinfoVO);
    }

接下来我们要获取右边的信息,即文章列表页的信息,想要获取文章列表的信息,我们需要文章的实体类,于是我们应该在entity下新建一个文章的实体类

package com.example.demo.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

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

@Data
public class Articleinfo {
    private Integer id;
    private String title;
    private String content;
//JsonFormat注解用于规范输出
    @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中添加对应的根据uid获取文章列表的方法

 List<Articleinfo> getMyList(@Param("uid") Integer uid);

再到ArticleMapper.xml中添加对应的sql语句

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

然后再接着进行Service的对应方法

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

然后最后要进行Controller的完成,但是因为我们要进行的是文章表的相关操作,我们需要新建一个ArticleController类

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.getSessionUser(request);
        if(userinfo==null){
            return AjaxResult.fail(-1,"非法请求");
        }
        List<Articleinfo> list=articleService.getMyList(userinfo.getId());
        return  AjaxResult.success(list);
    }
}

2.9 注销功能的实现

注销功能是不需要操作数据库的,我们直接在UserController中进行操作即可,我们直接调用session的removeAttribute方法

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

2.10 删除文章

删除文章同样需要前后端进行交互,前端还是可以看我的源码,后端的部分过程如下:

我们首先要在ArticleMapper中定义一个删除的方法,删除文章我们需要文章的id同时要有用户的id,我们要比对文章的所归属的uid与当前的用户id是否相同,如果不同则不能进行删除操作

  int del(@Param("id") Integer id,@Param("uid") Integer uid);

随后去ArticleMapper.xml中实现相应的删除sql

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

与上面相同,接下来要去ArticleService中进行实现

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

最后前往ArticleController进行相应的方法实现

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

2.11 查看文章详情页

查看文章详情页的过程应该为,先从url中得到文章id,从后端查询当前文章的详细信息,再根据上一步查询的uid来查询用户的信息,最后再实现阅读量的自增

后端的代码同样要从ArticleMapper开始写起,我们创建一个获取文章信息的方法

    Articleinfo getDetail(@Param("id") Integer id);

然后到ArticleMapper.xml中进行对应sql语句的补充

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

同样的,我们接下来再到ArticleService中进行对应方法的实现

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

然后我们再到ArticleController中进行对应方法的实现

   @RequestMapping("/detail")
    public AjaxResult getDetail(Integer id){
        //非空校验
        if(id==null || id<0){
            return AjaxResult.fail(-1,"非法参数");
        }
        return AjaxResult.success(articleService.getDetail(id));
    }

2.11 显示文章作者的详情信息

我们需要根据用户的id获取用户对象,同理我们先在UserMapper中进行对应方法的实现

    //根据用户IdD查询userinfo对象
    Userinfo getUserById(@Param(("id")) Integer id);

然后再到UserMapper.xml中实现相应的sql语句

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

与上面同理,我们再到UserService中进行相应方法的实现

  public Userinfo getUserById(Integer id){
        return userMapper.getUserById(id); 
    }

UserService的方法写完后,我们就可以到UserController中进行方法的实现

    @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.setPassword("");
        //查询当前用户发表的文章数
        UserinfoVO userinfoVO=new UserinfoVO();
        BeanUtils.copyProperties(userinfo,userinfoVO);
        userinfoVO.setArtCount(articleService.getArtCountByUid(id));
        return AjaxResult.success(userinfoVO);
    }

2.12 请求详情页时使得阅读量增加

我们还是从后端部分的ArticleMapper开始写起,我们定义一个incrRCount()方法

    int incrRCount(@Param("id") Integer id);

随后我们进行mapper部分的sql语句的实现

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

然后再进行ArticleService部分的代码实现

  public int incrRCount(Integer id){
        return articleMapper.incrRCount(id);
    }

最后再到ArticleController进行最后的代码实现即可

    @RequestMapping("/incr-rcount")
    public AjaxResult incrRCount(Integer id){
        if(id!=null && id>0){
            return  AjaxResult.success(articleService.incrRCount(id));
        }
        return AjaxResult.fail(-1,"未知错误");
    }

2.13 文章的添加和修改功能

我们先从文章的添加功能说起,文章的添加功能的后端代码方面,我们也先从ArticleMapper进行写起

    int add(Articleinfo articleinfo);

我们再到ArticleMapper.xml中进行对应sql的实现

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

我们再到ArticleService中进行对应的实现

    public int add(Articleinfo articleinfo){
        return articleMapper.add(articleinfo);
    }

最后再到ArticleController中进行对应代码的实现

    @RequestMapping("/add")
    public AjaxResult add(Articleinfo articleinfo,HttpServletRequest request){
        //1.非空校验
        if(articleinfo==null || !StringUtils.hasLength(articleinfo.getTitle()) ||
         !StringUtils.hasLength(articleinfo.getContent())){
            //非法参数
            return AjaxResult.fail(-1,"非法参数");
        }
        //2.数据库添加操作
        //a.得到当前用户的uid
        Userinfo userinfo=UserSessionUtils.getSessionUser(request);
        if(userinfo==null || userinfo.getId()<=0){
            //无效的用户
            return AjaxResult.fail(-2,"无效的登陆用户");
        }
        articleinfo.setUid(userinfo.getId());
        return AjaxResult.success(articleService.add(articleinfo));
    }

博客的修改功能的实现较为复杂,我们要先获取到文章的id,再前往后端得到文章的详情信息并设置在页面上,再进行文章修改操作,前面两个操作我们在之前已经进行实现过了,我们的重点在于实现第三个功能,即进行文章的修改操作,我们也同样从mapper部分开始实现

    int update(Articleinfo articleinfo);

然后我们再进行ArticleMapper.xml部分代码的实现

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

然后我们再进行ArticleService部分的代码实现

    public int update(Articleinfo articleinfo){
        return articleMapper.update(articleinfo);
    }

最后再进行ArticleController部分的代码实现

    @RequestMapping("/update")
    public AjaxResult update(Articleinfo articleinfo,HttpServletRequest request){
        if(articleinfo==null || !StringUtils.hasLength(articleinfo.getTitle())
                || !StringUtils.hasLength(articleinfo.getContent()) ||
                articleinfo.getId()==null){
            //非法参数
            return AjaxResult.fail(-1,"非法参数");
        }
        //得到当前登陆用户的ID
        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));
    }

2.14 密码加盐处理

密码加盐处理的重点在于,每次调用方法的时候都会产生盐值,且这个盐值随机且唯一,我们将这个随机盐值与我们本身的密码相加,得到的就是最终的密码,加密其实较为简单,难的在于解密操作,我们这里的盐值可以选定为uuid,因为uuid都是随机的

解密操作我们需要得到盐值,即我们根据自己设定一个规律,对用户输入的密码进行相应的加密,我们可以设定最终密码格式为:盐值$最终的密码,然后与数据库中的密码进行相应的对比,来判断是否是正确的密码

正确做法是,先从最终密码获取得到盐值,再将盐值与用户输入的密码组合在一起进行md5加密,最后再与盐值结合起来与数据库中存储的密码进行比对

我们可以在common下新建一个关于密码的工具类,写两个主要方法,一个方法用于加盐,一个用于验证密码

package com.example.demo.common;

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

import java.util.UUID;

public class PasswordUtils {
    //1. 加盐并生成密码
    public static String encrypt(String password){
        //盐值
        String salt= UUID.randomUUID().toString().replace("-",""); //32位盐值
        //生成加盐后密码
        String saltPassword= DigestUtils.md5DigestAsHex((salt+password).getBytes());
        //生成最终密码(保存到数据库中的密码,32位盐值+$+32位加盐之后的密码)
        String finalPassword=salt+"$"+saltPassword;
        return finalPassword;

    }
    //2.生成加盐的密码(重载
    public static String encrypt(String password,String salt){
        //生成加盐密码
        String saltPassword= DigestUtils.md5DigestAsHex((salt+password).getBytes());
        //生成最终密码
        String finalPassword=salt+"$"+saltPassword;
        return finalPassword;
    }
    //3.验证密码
    //inputPassword为用户输入明文密码
    //finalPassword为数据库保存的密码
    public static boolean check(String inputPassword,String finalPassword){
        if(StringUtils.hasLength(inputPassword) && StringUtils.hasLength(finalPassword) && finalPassword.length()==65){
            //得到盐值
            String salt=finalPassword.split("\\$")[0];
            // 进行加密得到相应的密码
            String confirmPassword=encrypt(inputPassword,salt);
            //对比两个密码是否相同
            return confirmPassword.equals(finalPassword);
        }
        return false;
    }
}

此时我们需要修改两个地方,一个是userController中登陆时的密码校验,以及注册时的密码存储

    @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(PasswordUtils.check(password,userinfo.getPassword())){
                //登录成功
                //todo:将用户存储到session中
                HttpSession session=request.getSession();
                session.setAttribute(AppVariable.USER_SESSION_KEY,userinfo);
                userinfo.setPassword(""); //返回前端之前,隐藏敏感信息
                return AjaxResult.success(userinfo);
            }
        }
        return AjaxResult.success(null,0);
    }
  @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));
    }

2.15 所有人的博客页面以及分页功能的实现

分页功能的实现关键在于,前端需要知道每页显示的固定博客数量,以及当前页码

后端也需要知道这两个

同时分页功能的实现也需要数据库中的limit offset关键字来进行实现

同理,我们在后端部分的代码也先从ArticleMapper部分写起

List<Articleinfo> getListByPage(@Param("psize")Integer psize,@Param("offsize") Integer offsize);

接下来,我们再进行ArticleMapper.xml部分的sql语句的实现

 <select id="getListByPage" resultType="com.example.demo.entity.Articleinfo">
        select * from articleinfo limit #{psize} offset #{offsize}
    </select>

完成了Mapper部分的设计后,我们接下来继续完成ArticleService的代码实现

   public List<Articleinfo> getListByPage(Integer psize,Integer offsize){
        return articleMapper.getListByPage(psize,offsize);
    }

最后就是完成ArticleController部分的代码实现

   @RequestMapping("/listbypage")
    //pindex为当前页面,且至少为1
    //psize为每页的显示条数
    public AjaxResult getListByPage(Integer pindex,Integer psize){
        //1.参数矫正
        if(pindex==null || pindex<=1){
            pindex=1;
        }
        if(psize==null || psize<=1){
            psize=2;
        }
        //分页公式的值(当前页码-1)*每页显示数
        int offset=(pindex-1)*psize;
        List<Articleinfo> list=articleService.getListByPage(psize,offset);
        return AjaxResult.success(list); 
    }

由于项目繁琐复杂,许多地方可能难以讲清,因为一个项目不仅要后端,还需要一定程度上的前端代码的实现,而这里不便进行相应前端代码的讲解,望理解

源码地址在这,JavaSenior/mycnblog-ssm · 夕夕夕夕夕立/java - 码云 - 开源中国 (gitee.com)

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值