SpringBoot实战项目——博客笔记项目

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、项目介绍
  • 二、项目的整体框架
  •       2.1 数据库模块
  •       2.2 前端模块
  •       2.3 后端模块
  • 三、项目图片展示
  • 四、项目的实现
  •       4.1 准备工作
  •             4.1.1 数据库,表的创建
  •             4.1.2 配置数据库和MyBatis
  •             4.1.3 引入前端项目
  •       4.2 统一功能处理
  •             4.2.1 统一异常功能处理
  •             4.2.2 统一数据格式返回
  •             4.2.3 统一用户登录验证
  •       4.3 博客登录页的实现
  •             4.3.1 实体类的创建
  •             4.3.2 前后端交互
  •             4.3.3 后端代码实现
  •       4.4 博客列表页的实现
  •       4.5 博客修改删除功能实现
  •       4.6 博客分页功能实现
  • 总结


前言


一、项目介绍

项目名称:博客笔记项目

主要操作的对象是:文章和用户,用户可在该系统上发表自己的博客,查看自己或别人已经发表的文章


需要用到两张表:userinfo(用户表)、articleinfo(文章表)


需要实现的功能:

登录+注册

博客的分页列表功能

新增发表博客

修改、删除自己的博客


 项目亮点

  1. 手动对用户密码实现随机加盐
  2. 统一异常处理、拦截器
  3. 用户登录持久化(session内存)
  4. 分页功能

项目技术栈

SSM(SpringBoot + SpringMVC + MyBatis)

MySQL

jQuery


二、项目的整体框架

2.1 数据库模块

两张表:用户表 + 文章表


2.2 前端模块

前端设计到7个页面

  • login.html登录页
  • reg.html注册页
  • blog_list.html总的博客列表页——》用到了blog_l
  • myblog_list.html个人博客列表页
  • blog_content.html博客详情页
  • blog_edit.html博客编辑页
  • blog_update.html博客修改页


2.3 后端模块

后端模块:


  • 控制层(controller包)——》控制器
  • 服务层(service包)——》服务类
  • 持久层--数据访问层(mapper类 + mapper.xml)——》mapper
  • 实体层(model包)——》实体类
  • 工具层(config包+util包)——》统一异常处理、统一返回、随机加盐


 三、项目具体展示

四、项目的实现

4.1 准备工作

首先我们新建一个springboot项目,项目具体的创建流程我这里就不在赘述。

详细见链接:SpringBoot的创建icon-default.png?t=N7T8http://t.csdnimg.cn/XCd3R


引入需要的依赖pom.xml代码:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.8</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.3.0</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.8.10</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
		<finalName>sxy_blog_system</finalName>
	</build>

</project>

4.1.1 数据库,表的创建

1.用户表的创建


2.文章表的创建


4.1.2 配置数据库和MyBatis

1.配置数据库的连接信息


注意: 这里的很多内容是固定的

# 数据库连接配置
Spring:
  datasource:
    url: jdbc:mysql://localhost:3306/你要连接的数据库名?characterEncoding=utf8&useSSL=false
    username: 用户名
    password: 自己的密码
    driver-class-name: com.mysql.cj.jdbc.Driver #只要你数据库用的是mysql这个是固定的

2.配置MyBatis XML存放规则和命名规则


此时我们已经在通过maven将MySQL Driver和MyBatis Framework这两个包导入了进来,此时启动项目,项目依然能够正常运行,说明我们数据库连接是正常的。


4.1.3 引入前端项目

下面的前端的静态资源复制到我们resource/static目录下面:


4.2 统一功能处理

上面我们说了,当程序出现了异常获取其他情况,我们不统一处理。

4.2.1 统一异常功能处理

所以我们需要单独在工具层中(我们的common包下面,建一个统一异常处理的类)


4.2.2 统一数据格式返回

一般在web项目中,我们前后端都是通过json这种数据格式来交换数据格式

——》我们后端需要给前端返回json格式的数据。

我们前后端用户交互的数据个数一般是统一的,不会出现你一个接口用一种数据格式,而那个接口又换了,这样就会有很多问题,不利于开发。


统一数据的优点:

  1. 方便前端程序员更好的接受和解析后端数据接口返回的数据。
  2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接口都是这样返回的。
  3. 有利于项目统一数据的维护和修改。
  4. 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容。

统一数据格式的返回有两种实现方式,返回一个公共对象或者重写。

这里我们采用第二种重写(不过他的灵活性有待提升) 

具体的我们可以使用@ControllerAdvice+ResponseBodyAdvice的方式实现,

具体实现代码如下:

但是正如我们上面所说的,通过重写来进行统一数据格式的返回,他的灵活性的确有待提高。你看我们上面只是处理了成功的情况,但要是失败的情况呢?——》并且即使失败,也是分好几种情况呢!!!


我们不如再创建一个工具类,用来自定义返回hashmap数据(再通过@ResponseBody转成json格式)


那么对应的我们的统一数据格式返回类就发生了变化

package com.example.demo.common;
 
import com.fasterxml.jackson.databind.ObjectMapper;
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.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
 
import java.util.HashMap;
 
/**
 * 统一数据格式返回(灵活性有待提高)
 *  通过统一数据格式的返回,不管我们控制层的方法返回了什么类型的数据
 *  通过重写末尾都能把他转成hashmap格式的数据,然后又通过@ResponseBody注解,将java对象转成了json对象。
 */
@ControllerAdvice
@ResponseBody
public class ResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true; // 这个值为true的时候,才会对返回的数据进行重写
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 在有了我们自定义数据返回后,我们的这个统一数据格式返回类就像是一个托地的。
        // 因为controller层可以直接调用AjaxResult,来返回hashmap(通过@ResponseBody转成json)
        // 但如果controller没有调用AjaxResult,直接返回了
        if (body instanceof HashMap) {
            return body;  // 此时已经是hashmap格式了
        }
        if (body instanceof Integer){ // 当controller层中的方法直接返回int类型时候
            int num = (int) body;
            if (num <= 0) {
                // 应对int类型错误返回(查询文章列表,新增和删除博客可能会用到)——》也可能用不到,如果新增或查询失败,我直接就在controller就返回了(通过调用AjaxResult)
                // 新增、删除或查询失败(非得在controller返回int值,再通过这里返回json对象的话,不灵活(出错信息显示的不具体
                // 所以说这里我们只是以防万一,我们还是选择再controller层直接返回json对象,这样更信息具体,更有怎针对性)
                return AjaxResult.fail("抱歉,本次操作失败,请稍后再试!"); // 这里无法区分是新增失败还是删除失败
                // 这里我们本来返回的是一个hashmap格式的对象,但加了@ResponseBody,把我们的java对象转成的了json格式
            }
        }
 
        if (body == null) { // (比如查询操作,直接返回查询到的UserInfo对象,然后直接返回该对象)
            // 这里我们本来返回的是一个hashmap格式的对象,但加了@ResponseBody,把我们的java对象转成的了json格式
            return AjaxResult.fail("抱歉,查询失败!"); // 这时对查询当前用户的特判
        }
        // 这里我们本来返回的是一个hashmap格式的对象,但加了@ResponseBody,把我们的java对象转成的了json格式
        return AjaxResult.success("操作成功", body);
        // 前端是通过result中的status值来判断操作是否成功的,这个类用来处理操作成功的情况(为操作成功的情况兜底)
        // 但这可能存在问题,如果操作失败,并且在controller层没有调用AjaxResult中的fail方法(而是直接返回,通过这个类来返回统一的数据格式,就会出现问题——》在这个类我们都是按成功的处理的)
        // 解决方案,在该类中提前判断body(判断操作失败的情况)--->我们约定如果操作失败就返回负数(在controller层调用AjaxResult的情况)
    }
}

那么与之对应的我们在controller层的类也就发生了改变

package com.example.demo.controller;
 
import com.example.demo.common.AjaxResult;
import com.example.demo.model.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;
 
import javax.jws.soap.SOAPBinding;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
/**
 * 而Spring Boot框架项目接口返回 JSON格式的数据比较简单:
 * 在 Controller 类中使用@RestController注解即可返回 JSON格式的数据。
 *  @RestController是@Controller和@ResponseBody两者的结合,使用这个注解后该controller的所有方法都会返回json格式的数据,
 *  因为@ResponseBody的作用就是把返回的对象转换为json格式,并把json数据写入response的body中,前台收到response时就可以获取其body中的json数据了。
 * 如果在整个controller类上方添加@RestController,其作用就相当于把该controller下的所有方法都加上@ResponseBody,使每个方法直接返回response对象。
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    public UserService userService;
    @RequestMapping("/reg")
    public Object reg(String username, String password1, String password2) {
        HashMap<String, Object> result = new HashMap<>();
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password1) || !StringUtils.hasLength(password2)) {
 
            return AjaxResult.fail("你输入的参数有误,请重新输入!");
        }
        else {
            if (!password1.equals(password2)) {
                return AjaxResult.fail("前后密码不一致,请重新输入!");
            }
            else {
                UserInfo userInfo = new UserInfo();
                userInfo.setUsername(username);
                userInfo.setPassword(password1);
                int ret = userService.reg(userInfo);
                if (ret != 1) {
                   return AjaxResult.fail("数据库添加用户失败,请稍后再试!");
                }
                else {
                    return AjaxResult.success("恭喜,注册成功!", ret);
                }
            }
        }
    }
    @RequestMapping("/login")
    public Object login(String username, String password) {
        HashMap<String, Object> result = new HashMap<>();
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return AjaxResult.fail("你输入的参数有误,请重新输入!");
        }
        else {
            // 需要在数据库中查询当前登录的用户是否存在
            UserInfo userInfo = userService.selectByUsername(username);
            if (userInfo == null || !password.equals(userInfo.getPassword())) {
               return AjaxResult.fail("你当前的用户名或密码错误,请重新输入!");
            }
            else  {
                return AjaxResult.success("恭喜,登录成功!", "");
            }
        }
    }
}

4.2.3 统一用户登录验证

spring拦截器

对于以上问题Spring中提供了具体的实现拦截器:HandlerInterceptor,

拦截器的实现分为以下两个步骤:

1、创建自定义拦截器,实现 HandlerInterceptor 接口的perHandle(执行具体方法之前的预处理)方法。

2、将自定义拦截器加入 WebMvcConfiger的 addInterceptors方法中。


步骤1:创建用户登录拦截器


步骤2:

 将该自定义拦截器放到系统的配置文件中

(将自定义拦截器加入 WebMvcConfiger的 addInterceptors方法中)


检验:用浏览器测试一下是否真的拦截了


4.3 博客登录页的实现

4.3.1 实体类的创建

首先根据我们数据库的表,在我们的程序中创建想对应的实体类——我们的MyBatis是ORM框架,我们的程序对象与关系数据库数据之间有响应的映射关系


只有在启动类的同级目录下,springboot才会对该目录下的类进行扫描,配合类注解存到spring容器中。


4.3.2 前后端交互

1.首先用户在前端页面输入了用户信息


2.然后前端紧接着就把用户输入的信息传递给后端(提交到后端指定的接口上,比如登录提交的就是/user/reg 


3.后端接收到用户信息,存到数据库中,并返回注册的结果(成功了?还是失败了?) 


4.前端接受到后端返回的结果后做进一步的处理


4.3.3 后端代码实现

后端流程图:


下面是根据上图流程构建的目录

在controller层中调用了service服务层的reg方法
service服务层又调用了持久层中的mapper接口
mapper接口的实现:UserMapper.xml文件

注册前后端流程交互


4.4 博客列表页的实现

 controller层代码:

1.ArticleController代码实现:

package com.example.demo.controller;

import com.example.demo.config.AjaxResult;
import com.example.demo.model.ArticleInfo;
import com.example.demo.model.UserInfo;
import com.example.demo.service.ArticleService;
import com.example.demo.service.UserService;
import com.example.demo.util.ConstVariable;

import lombok.SneakyThrows;
import org.apache.ibatis.annotations.ResultMap;
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;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.*;

/**
 * 而Spring Boot框架项目接口返回 JSON格式的数据比较简单:
 * 在 Controller 类中使用@RestController注解即可返回 JSON格式的数据。
 */
@RestController
@RequestMapping("/article")
public class ArticleController {
    @Autowired
    private ArticleService articleService;// 属性注入


    // 新增操作(发表博客)
    @RequestMapping("/add")
    public Object add(HttpServletRequest request, String title, String content) {
        // todo 非空校验
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute(ConstVariable.USER_SESSION_KEY) == null) {
            return AjaxResult.fail(-1, "当前用户未登录,博客发表失败!");
        }
        UserInfo userInfo = (UserInfo) session.getAttribute(ConstVariable.USER_SESSION_KEY);
        int uid = userInfo.getId(); // 标记当前博客是那个用户发布的
        int rcount = 1; // 表示当前博客的阅读量,初始值(新发表的博客阅读量)==1
        int state = 1; // state表示当前博客状态 1 ==>已发布, 2==》编辑中,保存草稿
        // 新增博客
        ArticleInfo articleInfo = new ArticleInfo();
        articleInfo.setTitle(title);
        articleInfo.setContent(content);
        articleInfo.setUid(uid);
        articleInfo.setRcount(rcount);
        articleInfo.setState(state);
        int result = articleService.add(articleInfo);
        if (result == 1) {
            return AjaxResult.success("博客发表成功!", 1);
        }
        else {
            return AjaxResult.fail(-1, "数据库插入失败,博客发表失败,请稍后再试!");
        }
    }
    // 查询指定用户id的所有博客
    @SneakyThrows
    @RequestMapping("/get_mylist")
    public Object getMyBlogList(Integer uid, HttpServletResponse response) {
//        if (uid == null) {
//            response.sendRedirect("/login.html"); // 当前用户未登录
//        }
//        int uid = Integer.parseInt(id);
        if (uid <= 0) {
            // response.sendRedirect("/lolgin.html"); // 当前用户未登录
            return AjaxResult.fail(401, "当前用户未登录");
        }
        return AjaxResult.success("查找成功",articleService.getMyBlogList(uid));
    }

    // 查看指定博客id的博客详情-->博客详情页,此id为blogId,形参名称要和前端传过来的参数一致
    @RequestMapping("/get_by_blog_id")
    public Object selectByBlogId(int id) {
        if (id <= 0) {
            return AjaxResult.fail(-1, "参数出错,无法找到指定博客的相关信息!");
        }
        ArticleInfo articleInfo = articleService.selectByBlogId(id);
        if (articleInfo == null) {
            return AjaxResult.fail(-1, "数据库查询出错");
        }
        return AjaxResult.success("查找博客详情成功", articleInfo);
    }

    // 获取总的博客列表_无分页功能
    @RequestMapping("/get_list")
    public List<ArticleInfo> getBlogList() {
        return articleService.getBlogList();
    }
    // 获取总的博客列表(实现分页功能)
    @RequestMapping("/get_list_by_page")
    public Object getListByPage(Integer pindex, Integer psize) {
        HashMap<String, Object> data = new HashMap<>();
        // 登录进去后,第一次初始化页面时候url = list.html,即pindex是空的,username等也是空的
        if (pindex == null || pindex < 1) {
            pindex = 1;
        }
        if (psize == null || psize <= 0) {
            psize = 2;
        }
        // 上面这些特殊情况的处理,很重要(就是应对url = list.html这种情况,如果没有特判,会出错(查询不到数据)
        // 我们前后端都对这些特殊情况做了处理——不多余(双重保障,你进入公司后前端可不是你写的,所以我们就要把我们负责的后端写的扎实(不管前端怎么传,我后端都能应对)
        int offset = (pindex - 1) * psize;
        List<ArticleInfo> list = articleService.getListByPage(psize, offset);
        // 得出用户名为username,地址为address,邮箱为email的所有用户个数
        // getPageCount函数————》得到总的博客条数
        int totalCount = articleService.getPageCount();
        data.put("list", list);
        data.put("count", totalCount);
        return AjaxResult.success("分页功能的实现", data);

    }
    // 删除指定博客id的博客
    @SneakyThrows
    @RequestMapping("/del")
    public Object del(HttpServletRequest request, int id, HttpServletResponse response) {
        ArticleInfo articleInfo = articleService.selectByBlogId(id);
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute(ConstVariable.USER_SESSION_KEY) == null) {
            // response.sendRedirect("/login.html");
            return AjaxResult.fail(401, "当前用户未登录!");
        }
        // 这篇博客的作者(uid)和当前登录的用户id相同才有权限删除
        UserInfo userInfo = (UserInfo) session.getAttribute(ConstVariable.USER_SESSION_KEY);
        if (articleInfo.getUid() != userInfo.getId()) {
//            response.sendRedirect("/blog_list.html");
            return AjaxResult.fail(-1, "你不是该篇博客的作者,你无权限进行删除操作");
        }
        if (id <= 0) {
            return AjaxResult.fail(-1, "找不到要删除的博客,删除失败");
        }
        int result = articleService.del(id);
        if (result <= 0) {
            return AjaxResult.fail(-1, "数据库删除操作失败!");
        }
        // 获取该篇博客的作者id

        Queue<Integer> queue = new LinkedList<>();
        ArrayList<Integer> list = new ArrayList<>();
        ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
        // response.sendRedirect("/blog_list.html");
        return AjaxResult.success("删除博客成功!", result);
    }
    /**
     * 修改指定id的博客内容或标题
     */
    @RequestMapping("/update")
    public Object update(HttpServletRequest request, int id, String title, String content) {
        if (id <= 0) return AjaxResult.fail(-1, "你指定的博客id有误,稍后再试!");

        ArticleInfo articleInfo = articleService.selectByBlogId(id);
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute(ConstVariable.USER_SESSION_KEY) == null) {
            // response.sendRedirect("/login.html");
            return AjaxResult.fail(401, "当前用户未登录!");
        }
        // 这篇博客的作者(uid)和当前登录的用户id相同才有权限删除
        UserInfo userInfo = (UserInfo) session.getAttribute(ConstVariable.USER_SESSION_KEY);
        if (articleInfo.getUid() != userInfo.getId()) {
//            response.sendRedirect("/blog_list.html");
            return AjaxResult.fail(-1, "你不是该篇博客的作者,你无权限进行修改操作");
        }
        else {
            int ret = articleService.update(id, title, content);
            if (ret <= 0) {
                return AjaxResult.fail(-1, "数据库更新操作失败!");
            }
            else {
                return AjaxResult.success("更新博客成功", ret);
            }
        }
    }

}

2.UserController代码实现:

package com.example.demo.controller;

import com.example.demo.config.AjaxResult;
import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import com.example.demo.util.ConstVariable;
import com.example.demo.util.PasswordUtil;

import lombok.SneakyThrows;
import org.apache.ibatis.annotations.ResultMap;
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;

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

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

    /**
     * 用户注册
     * @param username
     * @param password1
     * @param password2
     * @return
     */
    @RequestMapping("/reg")
    public Object reg(String username, String password1, String password2) {
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password1) || !StringUtils.hasLength(password2)) {
            return AjaxResult.fail(-1, "参数输入错误");
        }
        else {
            if (!password1.equals(password2)) {
                return AjaxResult.fail(-1, "前后密码不一致");
            }
            else {
                int result = userService.reg(username, PasswordUtil.encrypt(password1));
                if (result != 1) {
                    return AjaxResult.fail(-1, "数据库添加出错");
                }
                return AjaxResult.success("注册成功", "1");
            }
        }
    }

    /**
     * 用户登录
     * @param request
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/login")
    public Object login(HttpServletRequest request, String username, String password) {

        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return AjaxResult.fail(-1, "参数输入错误,登录失败");
        }
        UserInfo userInfo = userService.selectByUsername(username);
        // 随机加盐算法,未加密的密码用同样的加密算法和数据库中的加密算法进行比较
        if (userInfo == null || ! PasswordUtil.decrypt(password, userInfo.getPassword()) || userInfo.getId() <= 0) {

            return AjaxResult.fail(-1, "账号或密码错误,登录失败");
        }
        // 如果程序走到这里,说明登陆成功了
        // 每次登录成功都要对数据库中的密码进行重新生成,这样才符合随机加密
        // userService.passwordRefresh()
        HttpSession session = request.getSession(); // 获取session,如果没有就创建一个

        session.setAttribute(ConstVariable.USER_SESSION_KEY, userInfo);
        return AjaxResult.success("登录成功", 1);
    }
    /**
     *   获取当前登录的用户名
     */
    @RequestMapping("/get_user")
    public Object getUser(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return AjaxResult.fail(401, "当前用户未登录");
        }
        UserInfo userInfo = (UserInfo) session.getAttribute(ConstVariable.USER_SESSION_KEY);
        if (userInfo == null) {
            return AjaxResult.fail(401, "当前用户未登录");
        }
        return userInfo; // 这里我们可以直接用AjaxResult自定义统一格式返回,也可以传统的统一格式返回
    }

    /**
     * 退出登录
     * @param request
     * @param response
     */
    @SneakyThrows
    @RequestMapping("/logout")
    public Object logout(HttpServletRequest request, HttpServletResponse response) {
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute(ConstVariable.USER_SESSION_KEY) == null) {
            // response.sendRedirect("/login.html"); // 当前用户未登录
            return AjaxResult.fail(401, "当前用户未登录!");
        }
        session.setAttribute(ConstVariable.USER_SESSION_KEY, null);
        //response.sendRedirect("/login.html");
        return AjaxResult.success("当前状态正常,可以正常退出!", "true");
    }

    /**
     * 通过个人id(uid)查询个人详细信息
     * @param authorId
     * @return
     */
    @RequestMapping("/get_detail_by_uid")
    public Object selectByUid(Integer authorId) {
        int uid = authorId;
        if (uid <= 0) {
            return AjaxResult.fail(-1, "uid《=0,查询出错!");
        }
        UserInfo userInfo = userService.selectByUid(uid);
        if (userInfo == null) {
            return AjaxResult.fail(-1, "在数据库中查询不到指定用户id的详细信息,请稍后再试!");
        }
        return AjaxResult.success("通过uid查询个人详细信息成功", userInfo);
    }
}

在controller层中调用了service服务层的reg方法


 service服务层又调用了持久层中的mapper接口


4.5 博客修改删除功能实现

mapper层代码:

1.Articlemapper:

package com.example.demo.mapper;

import com.example.demo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface ArticleMapper {
    // 发布新文章
    public int add(@Param("articleinfo")ArticleInfo articleinfo);
    // 查询指定博客id的博客详情--》用于博客详情页
    public ArticleInfo selectByBlogId(@Param("blogId") int blogId);

    // 查询个人的博客列表——》个人博客详情页
    public List<ArticleInfo> getMyBlogList(@Param("uid") int uid);
    // 查询总的博客列表(无分页功能)
    public List<ArticleInfo> getBlogList();

    // 删除指定博客id的博客
    public int del(@Param("id") int id);

    // 得到当前的博客总数目
    public int getPageCount();

    // 查询总的博客列表(分页功能的实现)_>注意limit和offset的位置(相对位置(参数的相对位置,不要弄反——》查询数据库会出错
    public List<ArticleInfo> getListByPage(@Param("limit") int psize, @Param("offset") int offset);
    // psize对应limit--》表示每页显示的记录条数。pindex对应offset——》表示当前查看的是第几页的数据

    // 修改指定博客id的博客title和content----------->注意形参的顺序
    public int update(@Param("title") String title, @Param("content") String content, @Param("id") int id);

}

2.Usermapper代码实现:

package com.example.demo.mapper;

import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper {
    // 用户注册
    public int reg(@Param("username") String username, @Param("password") String password);


    // 通过名称查询用户-->用于登录的验证
    public UserInfo selectByUsername(@Param("username") String username);

    // 通过个人id(uid)查询个人详细信息
    public UserInfo selectByUid(@Param("uid") int uid);
}

1.AjaxResult代码:

用户登录拦截器,自定义拦截器添加到系统的配置文件中。

package com.example.demo.config;


import org.springframework.stereotype.Controller;

import java.util.HashMap;

/**
 * 自定义返回数据(我们通过AjaxResult类返回的都是hashmap格式的对象
 * 之后还有通过@ResponseBody将java对象转成json格式的数据
 */
public class AjaxResult {
    /**
     * 操作成功(无msg)
     * @param data
     * @return
     */
    public static Object  success(Object data) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("state", 200);
        result.put("msg", "");
        result.put("data", data);
        return result;
    }

    /**
     * 操作成功,有msg
     * @param msg
     * @param data
     * @return
     */
    public static Object success(Object msg, Object data) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("state", 200);
        result.put("msg", msg);
        result.put("data", data);
        return result;
    }

    /**
     * 操作失败(无data
     * @param state
     * @param msg
     * @return
     */
    public static Object fail(int state, Object msg) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("state", state);
        result.put("msg", msg);
        result.put("data", "");
        return result;
    }

    /**
     * 操作失败(有data)
     * @param state
     * @param msg
     * @param data
     * @return
     */
    public static Object fail(int state, Object msg,  Object data) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("state", state);
        result.put("msg", msg);
        result.put("data", data);
        return result;
    }
}

2.Logininterception代码:

用户登录拦截器(查看当前用户是否登录)

package com.example.demo.config;

import com.example.demo.util.ConstVariable;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashSet;

/**
 * 用户登录拦截器(查看当前用户是否登录)
 */
@Component
public class LoginInterception implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute(ConstVariable.USER_SESSION_KEY) == null) {
            // 当前用户未登录,进行拦截--->跳转到登录页面--->后端我们只用给前端返回状态和数据,我们后端不用管页面怎么,跳转工作是前端的事情
            // response.sendRedirect("/login.html");
            // 401表示未登录,没有权限,403不是因为没有登录而没有权限,而是该用户没有权限,管理员和普通用户的区别
            response.setStatus(401);
            response.sendError(401, "当前用户为登录,没有权限访问该接口");
            return false;
        }
        return true;
    }
}

3.ResponseAdvice代码:

数据格式同样返回(这个是兜底的,即使controller层返回的是int或string类型的数据,也可以转换成json格式)

注意:只能兜底int类的返回(可以应对操作成功和失败两种情况)

如果是String或其他格式的返回(可以应对操作成功的情况,但无法应对操作失败的情况,要继续对body进行特判)

package com.example.demo.config;

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.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.beans.PropertyEditorSupport;
import java.util.HashMap;

/**
 * 数据格式同样返回(这个是兜底的,即使controller层返回的是int或string类型的数据,也可以转换成json格式)
 * 注意:只能兜底int类的返回(可以应对操作成功和失败两种情况)
 * 如果是String或其他格式的返回(可以应对操作成功的情况,但无法应对操作失败的情况,要继续对body进行特判)
 *
 * 这里我们只是以防万一,我们尽量还是选择再controller层直接返回json对象,这样更信息具体,更有怎针对性)
 */
@ControllerAdvice // 统一功能处理需要加这个注解
@ResponseBody
// @ResponseBody的作用就是把返回的对象转换为json格式,并把json数据写入response的body中,前台收到response时就可以获取其body中的json数据了。
public class ResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true; // 返回true才会继续执行下面的代码
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof HashMap) {
            return body;  // 此时已经是hashmap格式了
        }
//        HashMap<String, Object> result = new HashMap<>();
//        result.put("state", 200);
//        result.put("msg", "");
//        result.put("data", body);
        if (body instanceof Integer){
            int num = (int) body;
            if (num <= 0) {
                // 应对int类型错误返回(查询文章列表,新增和删除博客可能会用到)——》也可能用不到,如果新增或查询失败,我直接就在controller就返回了(通过调用AjaxResult)
                // 新增、删除或查询失败(非得在controller返回int值,再通过这里返回json对象的话,不灵活(出错信息显示的不具体
                // 所以说这里我们只是以防万一,我们还是选择再controller层直接返回json对象,这样更信息具体,更有怎针对性)
                return AjaxResult.fail(-1,"抱歉,本次操作失败,请稍后再试!"); // 这里无法区分是新增失败还是删除失败
                // 这里我们本来返回的是一个hashmap格式的对象,但加了@ResponseBody,把我们的java对象转成的了json格式
            }
        }

        if (body == null) { // 应对String类型错误返回
            // 这里我们本来返回的是一个hashmap格式的对象,但加了@ResponseBody,把我们的java对象转成的了json格式
            return AjaxResult.fail(-1,"抱歉,查询失败!"); // 这时对查询当前用户的特判
        }
        if (body instanceof String) { // 以String类型正确返回
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(body); // 用jackson中的工具类返回(没弄明白)——》直接返回String吧(不序列化了,详见https://zhuanlan.zhihu.com/p/196372502)
        }
        // 这里我们本来返回的是一个hashmap格式的对象,但加了@ResponseBody,把我们的java对象转成的了json格式
        return AjaxResult.success(body);
        // 前端是通过result中的state值来判断操作是否成功的,这个类用来处理操作成功的情况(为操作成功的情况兜底)
        // 但这可能存在问题,如果操作失败,并且在controller层没有调用AjaxResult中的fail方法(而是直接返回,通过这个类来返回统一的数据格式,就会出现问题——》在这个类我们都是按成功的处理的)
        // 解决方案,在该类中提前判断body(判断操作失败的情况)--->我们约定如果操作失败就返回负数(在controller层调用AjaxResult的情况)

    }
}

4.6 博客分页功能实现

1.ExcptionAdvice代码:

统一异常处理

@ResponseBody的作用就是把返回的对象转换为json格式,并把json数据写入response的body中,前台收到response时就可以获取其body中的json数据了

package com.example.demo.config;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;

/**
 * 统一异常处理
 */
@ControllerAdvice
@ResponseBody
// @ResponseBody的作用就是把返回的对象转换为json格式,并把json数据写入response的body中,前台收到response时就可以获取其body中的json数据了。
public class ExceptionAdvice {
    @ExceptionHandler(Exception.class)
    public Object exceptionAdvice(Exception e) {
        // 这里我们本来返回的是一个hashmap格式的对象,但加了@ResponseBody,把我们的java对象转成的了json格式
        return AjaxResult.fail(-1, e.getMessage());
    }
    // 你这里的统一异常处理,并不完整

}

2.ApplicationConfig代码:

package com.example.demo.config;

import org.springframework.beans.factory.annotation.Autowired;
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 ApplicationConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterception loginInterception; // 用户登录拦截器
    // 自定义拦截器添加到系统的配置文件中

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterception)
                .addPathPatterns("/**")       // 设置拦截规则
//                .excludePathPatterns("/login.html")// 下面这些指定的url,不会检查是否登录——进行拦截
//                .excludePathPatterns("/reg.html")
                .excludePathPatterns("/**/*.html")
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/reg")
                // 总的博客的列表,和博客详情,我们需要开放权限,即使用户未登录也可以查看
                .excludePathPatterns("/article/get_list_by_page") // 放行总博客列表
                .excludePathPatterns("/article/get_by_blog_id") // 放行博客详情页面
                .excludePathPatterns("/user/get_detail_by_uid")
                //.excludePathPatterns("/css/**")--->和下一行的作用是一样的
                //.excludePathPatterns("/**/*.css")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/js/**")
                .excludePathPatterns("/editor.md/**")
                .excludePathPatterns("/img/**"); // 直接img目录下的所有都放行

    }
}

总结

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当前课程中博客项目实战源码是我在 GitHub上开源项目 My-Blog,目前已有 3000 多个 star:本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 大部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 个人博客项目功能的讲解,通过本课程的学习,不仅仅让你掌握基本的 Spring Boot 开发能力以及 Spring Boot 项目的大部分开发使用场景,同时帮你提前甄别和处理掉将要遇到的技术难点,认真学完这个课程后,你将会对 Spring Boot 有更加深入而全面的了解,同时你也会得到一个大家都在使用的博客系统源码,你可以根据自己的需求和想法进行改造,也可以直接使用它来作为自己的个人网站,这个课程一定会给你带来巨大的收获。作者寄语本课程录制于 2020 年,代码基于 Spring Boot 2.x 版本。到目前为止,Spring Boot 技术栈也有一些版本升级,比如 Spring Boot 2.7 发版、Spring Boot 3.x 版本发布正式版本。对于这些情况,笔者会在本课程实战项目的开源仓库中创建不同的代码分支,保持实战项目的源码更新,保证读者朋友们不会学习过气的知识点。课程特色 课程内容紧贴 Spring Boot 技术栈,涵盖大部分 Spring Boot 使用场景。开发教程详细完整、文档资源齐全、实验过程循序渐进简单明了。实践项目页面美观且实用,交互效果完美。包含从零搭建项目、以及完整的后台管理系统和博客展示系统两个系统的功能开发流程。技术栈新颖且知识点丰富,学习后可以提升大家对于知识的理解和掌握,对于提升你的市场竞争力有一定的帮助。实战项目预览    
SpringBoot项目实战笔记可以按照以下步骤进行: 1. 首先,你可以通过观看B站上的教程视频来学习SpringBoot项目实战。在视频中,你可以学习到如何使用SpringBoot、MyBatis和MySQL创建一个电脑商城项目。 2. 确保你的SpringBoot项目能够成功启动。找到被@SpringBootApplication注解修饰的入口启动类,并运行该类。如果你能够观察到图形化的界面,那么说明你的项目成功启动了。 3. 如果你还没有创建SpringBoot项目,你可以使用Spring Initializr来初始化一个新的项目。Spring Initializr是一个Web应用程序,可以为你生成Spring Boot项目的基本结构。你可以选择使用Maven或Gradle作为构建工具,并添加适合你的项目的依赖。然后,你只需要编写应用程序的代码即可。 希望以上信息对你有帮助!如果还有其他问题,请随时提问。123 #### 引用[.reference_title] - *1* *2* [SpringBoot项目实战笔记:电脑商城项目实战SpringBoot+MyBatis+MySQL)](https://blog.csdn.net/weixin_44260350/article/details/127746667)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] - *3* [《SpringBoot实战》读书笔记](https://blog.csdn.net/sanhewuyang/article/details/104494202)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值