项目开发记录:基于SpringBoot+Vue开发的前后端分离博客项目

项目开发记录:基于SpringBoot+Vue开发的前后端分离博客项目

起因: 最近学了一些东西,想通过一个简单的项目将一些学的东西巩固起来,同时希望来增加自己的一点项目经验,以便在以后的学习/面试中能够为自己增添色彩。

所选项目: 【实战】基于SpringBoot+Vue开发的前后端分离博客项目完整教学

原项目视频: https://www.bilibili.com/video/BV1PQ4y1P7hZ/

原官方文档: https://juejin.cn/post/6844903823966732302

原项目仓库: https://github.com/MarkerHub/vueblog

经过学习之后,感觉自己有了一些收获,特此整理出来,与大家交流分享

我的项目笔记: https://editor.csdn.net/md/?articleId=113930864

我的项目仓库: https://gitee.com/geng_kun_yuan/vueblog

一、知识点汇总

对于在做项目的过程中遇到的不熟悉的知识做一个总结

1、MyBatis-Plus

之前只学过MyBatis,对于MyBatis-Plus不是很了解,通过这次机会学习一下。

具体内容参考:https://baomidou.com/guide/


MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

核心功能

1.1 开启mapper接口扫描,添加分页插件

新建一个包:通过@mapperScan注解指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类。

PaginationInterceptor是一个分页插件。

  • com.markerhub.config.MybatisPlusConfig
@Configuration
@EnableTransactionManagement
@MapperScan("com.markerhub.mapper")
public class MybatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        return paginationInterceptor;
    }
}
1.2 代码生成

如果你没再用其他插件,那么现在就已经可以使用mybatis plus了,官方给我们提供了一个代码生成器,然后我写上自己的参数之后,就可以直接根据数据库表信息生成entity、service、mapper等接口和实现类。

官方地址:https://baomidou.com/guide/generator.html


2、页面模板引擎:freemarker

简单了解:https://zhuanlan.zhihu.com/p/135949220

详细学习:https://www.cnblogs.com/best/p/5681511.html

2.1 页面模板引擎

传统的页面开发过程中通常采用的HTML+ JS技术,而现在大部分网站都采用标签化+ 模块化的设计。

模板引擎其实就是根据这种方式,使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档在原有的HTML页面中来填充数据。最终达到渲染页面的目的。

常用的模板引擎技术:

  • Thymleaf
  • FreeMarker
  • Velocity
2.2 什么是FreeMarker

首先咱们先来介绍下Freemarker的概念,FreeMarker是一个使用纯Java编写的基于模板生成文本输出的通用工具,通常它的文件都是.ftl结尾。

FreeMarker最初被用来在MVC模式的Web开发框架中生成HTML页面的,但是它的功能可不仅仅可以用到Web开发生成模板文件,它也可以用于非Web应用环境中,如:生成Java代码。

FreeMarker的主要特点

1.轻量级不像JSP那样是Servlet要嵌入到应用程序中

2.可以生成各种文本文件如:html、xml、java等

3.学习成本低因为用java编写的,所以语法和java很像

接下来我们来看看Spring Boot整合Freemarker的实战操作

Freemarker渲染Web页面

img

img

img

img

3、统一结果封装

用了一个Result的类,这个用于我们的异步统一返回的结果封装。一般来说,结果里面有几个要素必要的

  • 是否成功,可用code表示(如200表示成功,400表示异常)
  • 结果消息
  • 结果数据

所以可得到封装如下:

@Data
public class Result implements Serializable {
    private String code;
    private String msg;
    private Object data;
    public static Result succ(Object data) {
        Result m = new Result();
        m.setCode("0");
        m.setData(data);
        m.setMsg("操作成功");
        return m;
    }
    public static Result succ(String mess, Object data) {
        Result m = new Result();
        m.setCode("0");
        m.setData(data);
        m.setMsg(mess);
        return m;
    }
    public static Result fail(String mess) {
        Result m = new Result();
        m.setCode("-1");
        m.setData(null);
        m.setMsg(mess);
        return m;
    }
    public static Result fail(String mess, Object data) {
        Result m = new Result();
        m.setCode("-1");
        m.setData(data);
        m.setMsg(mess);
        return m;
    }
}

4、异常处理

有时候不可避免服务器报错的情况,如果不配置异常处理机制,就会默认返回tomcat或者nginx的5XX页面,对普通用户来说,不太友好,用户也不懂什么情况。这时候需要我们程序员设计返回一个友好简单的格式给前端。

处理办法如下:

第一步、通过使用@ControllerAdvice来进行统一异常处理,@ExceptionHandler(value = RuntimeException.class)来指定捕获的Exception各个类型异常 ,这个异常的处理,是全局的,所有类似的异常,都会跑到这个地方处理。

  • com.markerhub.common.exception.GlobalExceptionHandler

第二部、定义全局异常处理,@ControllerAdvice表示定义全局控制器异常处理,@ExceptionHandler表示针对性异常处理,可对每种异常针对性处理。

/**
 * 全局异常处理
 */
@Slf4j
@RestControllerAdvice
public class GlobalExcepitonHandler {
    // 捕捉shiro的异常
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(ShiroException.class)
    public Result handle401(ShiroException e) {
        return Result.fail(401, e.getMessage(), null);
    }
    /**
     * 处理Assert的异常
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = IllegalArgumentException.class)
    public Result handler(IllegalArgumentException e) throws IOException {
        log.error("Assert异常:-------------->{}",e.getMessage());
        return Result.fail(e.getMessage());
    }
    /**
     * @Validated 校验错误异常处理
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result handler(MethodArgumentNotValidException e) throws IOException {
        log.error("运行时异常:-------------->",e);
        BindingResult bindingResult = e.getBindingResult();
        ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();
        return Result.fail(objectError.getDefaultMessage());
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = RuntimeException.class)
    public Result handler(RuntimeException e) throws IOException {
        log.error("运行时异常:-------------->",e);
        return Result.fail(e.getMessage());
    }
}

上面我们捕捉了几个异常:

  • ShiroException:shiro抛出的异常,比如没有权限,用户登录异常
  • IllegalArgumentException:处理Assert的异常
  • MethodArgumentNotValidException:处理实体校验的异常
  • RuntimeException:捕捉其他异常

5、实体校验

当我们表单数据提交的时候,前端的校验我们可以使用一些类似于jQuery Validate等js插件实现

后端我们可以使用Hibernate validatior来做校验

我们使用springboot框架作为基础,那么就已经自动集成了Hibernate validatior

那么用起来啥样子的呢?

第一步:首先在实体的属性上添加对应的校验规则,比如:

@TableName("m_user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    @NotBlank(message = "昵称不能为空")
    private String username;
    
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
    
    ...
}

第二步 :这里我们使用@Validated注解方式,如果实体不符合要求,系统会抛出异常,那么我们的异常处理中就捕获到MethodArgumentNotValidException。

补充:

常用的注解有:

约束注解名称 约束注解说明

@null 验证对象是否为空

@notnull 验证对象是否为非空

@asserttrue 验证 boolean 对象是否为 true

@assertfalse 验证 boolean 对象是否为 false

@min 验证 number 和 string 对象是否大等于指定的值

@max 验证 number 和 string 对象是否小等于指定的值

@decimalmin 验证 number 和 string 对象是否大等于指定的值,小数存在精度

@decimalmax 验证 number 和 string 对象是否小等于指定的值,小数存在精度

@size 验证对象(array,collection,map,string)长度是否在给定的范围之内

@digits 验证 number 和 string 的构成是否合法

@past 验证 date 和 calendar 对象是否在当前时间之前

@future 验证 date 和 calendar 对象是否在当前时间之后

@pattern 验证 string 对象是否符合正则表达式的规则

@Email 验证邮箱

@Range 被注释的元素必须在合适的范围内

@Digits(integer,fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内

实际例子:

@size (min=3, max=20, message=“用户名长度只能在3-20之间”)
@size (min=6, max=20, message=“密码长度只能在6-20之间”)
@pattern (regexp="[a-za-z0-9._%±]+@[a-za-z0-9.-]+\.[a-za-z]{2,4}", message=“邮件格式错误”)
@Length(min = 5, max = 20, message = “用户名长度必须位于5到20之间”)
@Email(message = “比如输入正确的邮箱”)
@NotNull(message = “用户名称不能为空”)
@Max(value = 100, message = “年龄不能大于100岁”)
@Min(value= 18 ,message= “必须年满18岁!” )
@AssertTrue(message = “bln4 must is true”)
@AssertFalse(message = “blnf must is falase”)
@DecimalMax(value=“100”,message=“decim最大值是100”)
@DecimalMin(value=“100”,message=“decim最小值是100”)
@NotNull(message = “身份证不能为空”)
@Pattern(regexp="^(\d{18,18}|\d{15,15}|(\d{17,17}[x|X]))$", message=“身份证格式错误”)


6、断言

此模块参考:https://www.cnblogs.com/hujingwei/p/5147236.html


断言:也就是所谓的assertion,是jdk1.4后加入的新功能。

它主要使用在代码开发和测试时期,用于对某些关键数据的判断,如果这个关键数据不是你程序所预期的数据,程序就提出警告或退出。

当软件正式发布后,可以取消断言部分的代码。java中使用assert作为断言的一个关键字,这就可以看出java对断言还是很重视的,因为如果不是很重要的话,直接开发个类就可以了,没必要新定义一个关键字。

语法1:assert expression; //expression代表一个布尔类型的表达式,如果为真,就继续正常运行,如果为假,程序退出

语法2:assert expression1 : expression2; //expression1是一个布尔表达式,expression2是一个基本类型或者Object类型,如果expression1为真,则程序忽略expression2继续运行;如果expression1为假,则运行expression2,然后退出程序。

注意,Eclipse中,断言功能默认是关闭,如果我们需要使用这个功能,需要手动打开它。

注意: 断言功能是用于软件的开发和测试的,也就是说,删去断言的那部分语句后,你程序的结构和运行不应该有任何改变,千万不要把断言当成程序中的一个功能来使用

7、会话共享

推荐阅读SpringSession+Redis实现集群会话共享

1.什么是会话保持?

简单来说就是用户登录百度网站后,然后关闭浏览器,当下次在打开百度时,会发现还是处于登录状态,这个就可以理解为是会话保持功能.

2.为什么要做会话保持?

http协议是无状态时,在同一个连接中,两个执行成功的请求之间是没有关系的.这就带来了一个问题,用户没有办法在同一个网站中进行连续的交互,比如在一个电商网站里,用户把某个商品加入到购物车,切换一个页面后再次添加了商品,这两次添加商品的请求之间没有关联,浏览器无法知道用户最终选择了哪些商品.

3.会话保持实现的原理.

1)cookie:一般存放在浏览器的Cookies中.主要存放信息(sessionID)

2)session:一般存放在服务端.主要存放信息有:(sessionID对应的用户名,登录信息等)


集群和负载均衡

集群: 一群服务器集合在一起提供服务, 多台Tomcat共同提供服务就是集群.

负载均衡: 让集群中每个Tomcat的负载情况保持均衡, 不要出现某一个或几个太空闲.


8、整合Shiro+Redis+Jwt

考虑到后面可能需要做集群、负载均衡等,所以就需要会话共享,而shiro的缓存和会话信息,我们一般考虑使用redis来存储这些数据,所以,我们不仅仅需要整合shiro,同时也需要整合redis。在开源的项目中,我们找到了一个starter可以快速整合shiro-redis,配置简单,这里也推荐大家使用。

而因为我们需要做的是前后端分离项目的骨架,所以一般我们会采用token或者jwt作为跨域身份验证解决方案。所以整合shiro的过程中,我们需要引入jwt的身份验证过程。

我们使用一个shiro-redis-spring-boot-starter的jar包,具体教程可以看 官方文档

详细步骤如下:

具体实现请阅读:超详细!4小时开发一个SpringBoot+vue前后端分离博客项目!!

ShiroConfig

在ShiroConfig中,我们主要做几件事情:

1.引入RedisSessionDAO和RedisCacheManager,为了解决shiro的权限数据和会话信息能保存到redis中,实现会话共享。

2.重写了SessionManager和DefaultWebSecurityManager,同时在DefaultWebSecurityManager中为了关闭shiro自带的session方式,我们需要设置为false,这样用户就不再能通过session方式登录shiro。后面将采用jwt凭证登录。

3.在ShiroFilterChainDefinition中,我们不再通过编码形式拦截Controller访问路径,而是所有的路由都需要经过JwtFilter这个过滤器,然后判断请求头中是否含有jwt的信息,有就登录,没有就跳过。跳过之后,有Controller中的shiro注解进行再次拦截,比如@RequiresAuthentication,这样控制权限访问。

那么,接下来,我们聊聊ShiroConfig中出现的AccountRealm,还有JwtFilter。

AccountRealm

AccountRealm是shiro进行登录或者权限校验的逻辑所在,算是核心了,我们需要重写3个方法,分别是

  • supports:为了让realm支持jwt的凭证校验
  • doGetAuthorizationInfo:权限校验
  • doGetAuthenticationInfo:登录认证校验

其实主要就是doGetAuthenticationInfo登录认证这个方法,可以看到我们通过jwt获取到用户信息,判断用户的状态,最后异常就抛出对应的异常信息,否者封装成SimpleAuthenticationInfo返回给shiro。 接下来我们逐步分析里面出现的新类:

1、shiro默认supports的是UsernamePasswordToken,而我们现在采用了jwt的方式,所以这里我们自定义一个JwtToken,来完成shiro的supports方法。

2、JwtUtils是个生成和校验jwt的工具类,其中有些jwt相关的密钥信息是从项目配置文件中配置的

3、而在AccountRealm我们还用到了AccountProfile,这是为了登录成功之后返回的一个用户信息的载体,

第三步,ok,基本的校验的路线完成之后,我们需要少量的基本信息配置

第四步、另外,如果你项目有使用spring-boot-devtools,需要添加一个配置文件,在resources目录下新建文件夹META-INF,然后新建文件spring-devtools.properties,这样热重启时候才不会报错。

JwtFilter

第五步、定义jwt的过滤器JwtFilter。

这个过滤器是我们的重点,这里我们继承的是Shiro内置的AuthenticatingFilter,一个可以内置了可以自动登录方法的的过滤器,有些同学继承BasicHttpAuthenticationFilter也是可以的。

我们需要重写几个方法:

  1. createToken:实现登录,我们需要生成我们自定义支持的JwtToken
  2. onAccessDenied:拦截校验,当头部没有Authorization时候,我们直接通过,不需要自动登录;当带有的时候,首先我们校验jwt的有效性,没问题我们就直接执行executeLogin方法实现自动登录
  3. onLoginFailure:登录异常时候进入的方法,我们直接把异常信息封装然后抛出
  4. preHandle:拦截器的前置拦截,因为我们是前后端分析项目,项目中除了需要跨域全局配置之外,我们再拦截器中也需要提供跨域支持。这样,拦截器才不会在进入Controller之前就被限制了。

那么到这里,我们的shiro就已经完成整合进来了,并且使用了jwt进行身份校验。


9、跨域问题

推荐阅读什么是跨域问题?以及其解决方案

什么是跨域?

浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域

** 域名:** 
 主域名不同 http://www.baidu.com/index.html -->http://www.sina.com/test.js
 子域名不同 http://www.666.baidu.com/index.html -->http://www.555.baidu.com/test.js
 域名和域名ip http://www.baidu.com/index.html -->http://180.149.132.47/test.js

** 端口:**
 http://www.baidu.com:8080/index.html–> http://www.baidu.com:8081/test.js

** 协议:**
 http://www.baidu.com:8080/index.html–> https://www.baidu.com:8080/test.js

** 备注:**
1、端口和协议的不同,只能通过后台来解决
2、localhost和127.0.0.1虽然都指向本机,但也属于跨域

因为是前后端分析,所以跨域问题是避免不了的,我们直接在后台进行全局跨域处理:

  • com.markerhub.config.CorsConfig
/**
 * 解决跨域问题
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }
}
复制代码

ok,因为我们系统开发的接口比较简单,所以我就不集成swagger2啦,也比较简单而已。下面我们就直接进入我们的正题,进行编写登录接口。

二、问题处理

本模块记录一些在开发过程中遇到的一些问题,以及解决方案

1、SpringBoot2.4跨域问题

参考:https://www.jb51.net/article/201453.htm

出现的异常:BeanCreationException

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*"since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.

出现的原因:

Springboot升级至2.4.0中出现的跨域问题。
在Springboot 2.4.0版本之前使用的是2.3.5.RELEASE,对应的Spring版本为5.2.10.RELEASE。
升级至2.4.0后,对应的Spring版本为5.3.1。

修改方式:

方式1:将allowedPatterns替换为allowedOriginPatterns

/**
 * 解决跨域问题
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*") //修改的地方
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }

}

其他解决方案:

https://blog.csdn.net/weixin_51579730/article/details/111142073

http://www.ruanyifeng.com/blog/2016/04/cors.html

2、类型转换异常

参考:https://blog.csdn.net/andwey/article/details/106053195

SpringBoot整合shiro-用户授权时遇到的问题:java.lang.ClassCastException

解决方式:在授权的方法里面,把principal.getPrimaryPrincipal()获取到的对象复制给User就行了!

package com.lut.util;

import com.lut.shiro.AccountProfile;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.BeanUtils;

public class ShiroUtil {

    public static AccountProfile getProfile() {

        Object obj = SecurityUtils.getSubject().getPrincipal();
        AccountProfile accountProfile=new AccountProfile();
        BeanUtils.copyProperties(obj,accountProfile);

//        AccountProfile accountProfile=(AccountProfile) SecurityUtils.getSubject().getPrincipal();
//        return accountProfile;
        return  accountProfile;
    }

}

image-20210201113136983

image-20210201121836899

image-20210201121859927

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值