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目录下的所有都放行

    }
}

总结

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS-页面跳转Router实例演示

本文将以APP的登录和修改昵称为例演示官网的几种页面跳转、返回以及这些流程携带参数&#xff0c;实例的形式记录学习HarmonyOS的页面跳转。 “页面路由指在应用程序中实现不同页面之间的跳转和数据传递。HarmonyOS提供了Router模块&#xff0c;通过不同的url地址&#xff0c;…

47、C++/引用,函数重载,类相关学习20240312

一、自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c; 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void show()。 代码&…

在 Rust 中使用 Serde 处理json

在 Rust 中使用 Serde 处理json 在本文中&#xff0c;我们将讨论 Serde、如何在 Rust 应用程序中使用它以及一些更高级的提示和技巧。 什么是serde&#xff1f; Rust中的serde crate用于高效地序列化和反序列化多种格式的数据。它通过提供两个可以使用的traits来实现这一点&a…

数字孪生+工业互联网标识解析,打造智能工厂新标杆!

当前&#xff0c;工业4.0浪潮愈发澎湃&#xff0c;加快数字化、网络化、智能化发展成为了制造业转型升级的必然要求。 51WORLD基于数字孪生技术与工业互联网标识解析体系&#xff0c;打造了一个集协同化供应、个性化定制、智能化生产于一体的全连接产线孪生平台&#xff08;以…

AndroidStudio设计登录页源码(音悦app)

目录 一、代码 二、效果 一、代码 1.在activity_main.xml里的代码 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent&quo…

(一)RabbitMQ实战——rabbitmq的核心组件及其工作原理介绍

前言 RabbitMQ是一个开源的消息代理软件&#xff0c;它实现了高级消息队列协议&#xff08;AMQP&#xff09;标准&#xff0c;提供可靠的消息传递机制。RabbitMQ可以用于在应用程序之间传递消息&#xff0c;实现不同应用系统之间的解耦和通信。它支持多种编程语言&#xff0c;…

11.Java---语法总结之一个小项目

图书管理系统 Java学习了很久了,今天将运用之前学习的所有东西整理做个小小的小项目. 1.首先是各种包和操作方法建好 2.然后是项目的大框架搭好 3.然后就开始实现各个部分了 看看最后的运行结果吧! 管理员测试 1.登录&显示图书的运行结果 2.查找&新增图书的运行结…

C goto 语句

C 语言中的 goto 语句允许把控制无条件转移到同一函数内的被标记的语句。 注意&#xff1a;在任何编程语言中&#xff0c;都不建议使用 goto 语句。因为它使得程序的控制流难以跟踪&#xff0c;使程序难以理解和难以修改。任何使用 goto 语句的程序可以改写成不需要使用 goto 语…

安卓逆向 安装frida

1,准备工作 安装adb环境,python最高支持到3.8 2,下载雷电模拟器5.0(安卓是7.0)要64位的 雷电安卓模拟器-手游模拟器安卓版_android手机模拟器电脑版_雷电模拟器官网 3,下载frida服务端(版本要与安卓版本匹配;与客户端版本要一致) 在模拟器查看架构 adb shell getprop ro.pr…

数组和指针笔试题目解析---掌握他们拿下指针魔鬼

目录 一、前言二、一维数组2.1代码2.2分析2.3验证运行结果 三、字符数组3.1代码13.1.1分析3.1.2验证运行结果 3.2代码23.2.1分析3.2.2验证运行结果 3.3代码33.3.1分析3.3.2验证运行结果 3.4代码43.4.1分析3.4.2验证运行结果 3.5代码53.5.1分析3.5.2验证运行结果 3.6代码63.6.1分…

D7805 ——体积小,成本低,性能好

D7805 构成的 5V 稳压电源为输出电压5V&#xff0c;输出电流 1000mA 的稳压电源它由滤波电容 C1,C3,防止自激电容 C2、C3 和一只固定三端稳压器&#xff08;7805&#xff09;后级加 LC 滤波极为简洁方便地搭成&#xff0c;输入直流电压范围为 7~35V&#xff0c;此直流电压经过D…

机械女生,双非本985硕,目前学了C 基础知识,转嵌入式还是java更好?

作为单片机项目开发的卖课佬&#xff0c;个人建议&#xff0c;先转嵌入式单片机开发方向&#xff0c;哈哈。 java我也学过&#xff0c;还学过oracle、mysql数据库&#xff0c;只是当时没做笔记&#xff0c;找不好充分的装逼证据了。 从实习通过业余时间&#xff0c;学到快正式毕…

(黑马出品_高级篇_03)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式

&#xff08;黑马出品_高级篇_03&#xff09;SpringCloudRabbitMQDockerRedis搜索分布式 微服务技术——多级缓存 今日目标1.什么是多级缓存2.JVM进程缓存2.1.导入案例2.1.1.安装MySQL2.1.1.1.准备目录2.1.1.2.运行命令2.1.1.3.修改配置 2.1.1.4.…

学编程前需要知道哪些编程语言呢?

编程语言的选择对学习编程的重要性不可低估。不同编程语言各有独特特点和适用场景&#xff0c;合理选择适合自身需求的编程语言能事半功倍。初学者而言&#xff0c;选择一门简洁易读的编程语言如Python&#xff0c;可以快速掌握编程基础原理&#xff0c;轻松编写程序&#xff0…

202003 青少年软件编程(Scratch)等级考试试卷(二级)

第1题:【 单选题】 在以下积木中,( )可以将声音的音量增大 A: B: C: D: 【正确答案】: B 【试题解析】 : 第2题:【 单选题】 如下图所示脚本运行的结果是( ) A:画一条直线 B:画一个三角形 C:画一个圆形 D:画一条虚线 【正确答案】: D 【试题解析】 : 第3…

【新书推荐】26.3 模块间的信息传递

模块间的信息传递主要表现为模块间过程调用时的参数传递。在第十一章介绍的过程调用参数传递原则和方法依然有效。少量的参数可以利用寄存器传递或堆栈传递&#xff0c;大量的参数可先组织在一个缓冲区中&#xff0c;然后利用寄存器或堆栈传递相应的指针。 如果利用约定的存储…

ES分片均衡策略分析与改进

从故障说起 某日早高峰收到 Elasticsearch 大量查询超时告警&#xff0c;不同于以往&#xff0c;查看 Elasticsearch 查询队列监控后发现&#xff0c;仅123节点存在大量查询请求堆积。 各节点查询队列堆积情况 查看节点监控发现&#xff0c;123节点的 IO 占用远高于其他节点。…

CcFrame Logo完成

Compact celerity framework CCFrame&#xff1a;一个快速开发、精简压缩的SAAS微服务后端架构 仅需要一台阿里云2核2G机器即可完整运行平台系统及其所有的运行及数据支持 微服务、远程-本地二级缓存架构、ES索引、队列、分库分表、集群定时器&#xff0c;一个都不能少 谁说2…

基于YOLOv8深度学习的木薯病害智能诊断与防治系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

【R语言实战】——fGARCH包在金融时序上的模拟应用

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…
最新文章