项目实战十一-扩展:MyBatis-Plus、MyBatis Generator、EasyCode、Freemarker、Swagger、Shiro

MyBatis-Plus

  1. MyBatis-Plus(简称MP)是MyBatis的增强工具,由国人开发,中文
  2. 在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生
  3. 官网:https://mp.baomidou.com/

MyBatis-Plus简单使用

  1. 添加pom.xml依赖

     <!-- mybatis-plus,因为这个依赖mybaits,因此可以注释mybatis-spring-boot-starter依赖-->
     <dependency>
         <groupId>com.baomidou</groupId>
         <artifactId>mybatis-plus-boot-starter</artifactId>
         <version>3.4.1</version>
     </dependency>
    
  2. application.yml中添加别名配置

     # 设置别名
     mybatis-plus:
       type-aliases-package: com.zh.jk.pojo
    
  3. mapper改造
    1. 继承BaseMapper,并告知对应的po对象,因为BaseMapper封装了很多sql方法,其中就有list方法
     //继承BaseMapper,并告知对应的po对象DictType
     //因为BaseMapper封装了很多sql方法
     public interface DictTypeMapper extends BaseMapper<DictType> {}
    
  4. service层改造
    1. 接口继承自IService,impl继承自ServiceImpl
     //DictTypeService,继承IService并指定DictType
     public interface DictTypeService extends IService<DictType> {}
        
     //DictTypeServiceImpl,继承自ServiceImpl,并指定mapper跟po类型
     @Service
     @Transactional
     public class DictTypeServiceImpl extends ServiceImpl<DictTypeMapper,DictType> implements DictTypeService {}
    
  5. 控制器

     @Controller
     @RequestMapping("/dictTypes")
     public class DictTypeController {
        
         @Autowired
         private DictTypeService service;
        
         @GetMapping("/list")
         public String list(DictTypeQuery query, Model model) {
             //list方法是自动封装的,而且还有很多其他方法
             model.addAttribute("data",service.list());
             //默认情况下回自动添加前缀:classpath:/templates/,默认后置:.ftlh
             return "page/dictType";
         }
     }
    

MyBatis Generator

简介

  1. 官方网址http://mybatis.org/generator/
  2. MyBatis Generator是MyBatis官方提供的代码生成器,可以根据数据库信息,逆向生成Model类、Mpper类、Mapper配置文件xml,大大节省开发者的编码时间,提高开发效率

使用步骤

  1. 在项目的test文件夹下新建一个resources目录,然后在下面新建一个generatorConfig.xml文件

     <?xml version="1.0" encoding="UTF-8" ?>
     <!DOCTYPE generatorConfiguration PUBLIC
                     "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
                     "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
     <generatorConfiguration>
        
     <context id="default" targetRuntime="MyBatis3Simple">
         <commentGenerator>
             <!--生成的类、xml中,去除所有的注释-->
             <property name="suppressAllComments" value="true"/>
         </commentGenerator>
         <!--数据库连接:要将哪个数据库中的表逆向生成-->
         <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                         connectionURL="jdbc:mysql://localhost:3306/jiakaox?servieTimerzone=GMT%2B8" userId="root" password="root"/>
                            
         <!--生成Model的位置:src/test/java/com.zh.jk.pojo.po
         targetPackage: 目标包名
         targetProject: 目标工程
         -->
            
         <javaModelGenerator targetPackage="com.zh.jk.pojo.po" targetProject="src/test/java"/>
            
         <!--XML的位置:在src/test/resources/com.zh.jk.mapper下-->
         <sqlMapGenerator targetPackage="com.zh.jk.mapper" targetProject="src/test/resources"/>
            
         <!--Mapper的位置src/test/java/com.zh.jk.mapper-->
         <javaClientGenerator type="XMLMAPPER" targetPackage="com.zh.jk.mapper" targetProject="src/test/java"/>
            
         <!--数据库中哪些表需要生成-->
         <!--%代表所有表-->
          <table tableName="%" />
         <!--指定几张表生成-->
     <!--    <table tableName="coach" />-->
     <!--    <table tableName="dict_type" />-->
     </context>
     </generatorConfiguration>
    
  2. 项目的pom.xml文件添加插件依赖

     <build>
         <plugins>
             <plugin>
                 <groupId>org.mybatis.generator</groupId>
                 <artifactId>mybatis-generator-maven-plugin</artifactId>
                 <version>1.3.7</version>
                 <configuration>
                     <!-- 指定配置文件的位置 -->
                     <configurationFile>src/test/resources/generatorConfig.xml</configurationFile>
                     <!--每次重新生成会覆盖原来生成的-->
                     <overwrite>true</overwrite>
                     <verbose>true</verbose>
                 </configuration>
                 <!-- 插件依赖的第三方库 -->
                 <dependencies>
                     <dependency>
                         <groupId>mysql</groupId>
                         <artifactId>mysql-connector-java</artifactId>
                         <version>5.1.49</version>
                     </dependency>
                 </dependencies>
             </plugin>
         </plugins>
     </build>
    
  3. 执行插件生成

    1. 点击右侧maven菜单->找到对应项目->plugins->找到mybatis-generator插件,展开,点击mybatis-generator:generate
    2. 就可以看到test文件夹下的对应目录生成了相应的文件
    3. 然后将src/test/java/com.zh.jk.pojo.po下面的类全部复制到src/main/java/com.zh.jk.pojo.po中

总结

  1. 插件尽管自动生成了Model类、Mpper类、Mapper配置文件xml,但是通常仅仅只需要po中的类
  2. 该插件会自动将数据库的下划线转化为驼峰标识,比如数据库的字段为created_time,转化为模型类的属性为createdTime

EasyCode

  1. 一款国产的代码生成插件,使用文档:https://gitee.com/makejava/EasyCode/wikis/pages
  2. 注意:搜索插件时,Easy和Code之间有一个空格
  3. Preferences->plugins->搜索Easy Code ->点击install即可
  4. 点击IDEA右侧DataBase->找到数据库-找到要生成的表->dic_type->右击->easycodermybytiscodehelper->Generate Code(直接生成)/Config Table(可以设置哪些字段不用生成)
  5. 使用步骤
    1. 设置类型映射
      1. 就是设置好MYSQL的数据类型对应Java的哪些类型
      2. Preferences->Easy Code-> Type Mapper->点击右边的+新增一些类型转化,新增的具体如下图所示

        图1

    2. 生成某种格式可以进行配置
      1. Preferences->Easy Code->Template Setting->Group Name 选择MybatisPlus,点击右边的复制图标,可以复制默认的MybatisPlus,然后自定义名称MybatisPlus_ZH
      2. 这里面可以配置各个层的生成模板

         entity: po对象,跟表一一对应的模型对象
         dao:dao对象
         service:service层
         serviceImpl: 
         controller:
         ...
        
      3. 具体的可以参照写好的模板(已上传到JiaKaoBE项目中,MyBatisPlus_ZH),复制粘贴
      4. 然后apply即可
    3. 使用
      1. 点击DataBase->找到数据库-找到要生成的表->dic_type(可以同时选择多张表)->右击->Generate Code
      2. 配置:

         Module:当前项目
         Package: 要生成到哪个package下: com.zh.jk
         path:项目的项目的实际代码路径
         Template: 勾选你要生成的所有类,勾选:统一配置、禁止提示
        
      3. 点击OK即可
      4. 会在com.zh.jk下生成对应的数据
    4. 注意: EasyCode如果有bug,可以直接使用EasyCode-MybatisCodeHepler插件,使用方法跟上面一样

Freemarker

  1. 官方网站http://freemarker.foofun.cn
  2. 跟此前学过的JSP、Thymeleaf一样,Freemarker也是一款优秀的模板引擎
    1. 可以生成任意格式的文本文件(HTML、XML、Java等),与web环境无关,不依赖web容器
    2. 意思就是如果一个文件(任意格式)中有一些数据是变量,需要动态插入,都可以用Freemarker自动生成,不仅仅是xml/html文件,可以是任何其他的文件,比如有n个类,代码基本一样,就名称不一样,可以用Freemarker生成。
    3. 是在公司中非常受欢迎的一款模板引擎
  3. 对比JSP、Thymeleaf
    1. JSP:只能用在web环境中,依赖于web容器
    2. Thymeleaf:虽然不依赖web容器,但它只能生成XML规范的文本文件(比如HTML、XML)

Freemarker简单使用

  1. 新建一个项目24_Freemarker
  2. pom.xml添加依赖

     <dependencies>
         <dependency>
             <groupId>org.freemarker</groupId>
             <artifactId>freemarker</artifactId>
             <version>2.3.23</version>
         </dependency>
     </dependencies>
    
  3. 准备一个模板文件,用来根据这个模板动态生成目标文件
    1. 它的文件扩展名是:ftl(FreeMarker Template Language)
    2. 在电脑的某个磁盘或者其他位置创建一个文件夹,在该文件夹下新建一个mapper.ftl
    3. 比如:/Users/mac/Desktop/JavaStudy/Codes/JavaProject/24_Freemarker/ftl/mapper.ftl

       package com.zh.jk.mapper;
       import com.baomidou.mybatisplus.core.mapper.BaseMapper;
       import com.zh.jk.pojo.po.${type};
       public interface ${type}Mapper extends BaseMapper<${type}> {}
      
    4. 新建Main类测试

       //com.zh
       public class Main {
           public static void main(String[] args) throws Exception {
               //1. 告知使用freemarker版本是多少
               Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
               // 设置编码
               cfg.setDefaultEncoding("UTF-8");
               // 2. 模板文件的存放目录
       //        cfg.setDirectoryForTemplateLoading(new File("F:/templates"));
               cfg.setDirectoryForTemplateLoading(new File("/Users/mac/Desktop/JavaStudy/Codes/JavaProject/24_Freemarker/ftl"));
              
               // 3. 获取指定的模板文件
               Template tpl = cfg.getTemplate("mapper.ftl");
              
               // 4. 将要填充的数据
               Map<String, Object> data = new HashMap<String,Object>();
               data.put("type", "DictType");
              
               // 5. 将填充后的类生成到指定的文件路径下
       //        FileWriter out=  new FileWriter(new File("F:/templates/Data.java"));
               try (FileWriter out = new FileWriter(new File("/Users/mac/Desktop/JavaStudy/Codes/JavaProject/24_Freemarker/ftl/DictType.java"))) {
                   tpl.process(data, out);
               }
           }
       }
      
    5. 运行,就会看到在指定路径下生成了一个DictType.java类

       package com.zh.jk.mapper;
       import com.baomidou.mybatisplus.core.mapper.BaseMapper;
       import com.zh.jk.pojo.po.DictType;
       public interface DictTypeMapper extends BaseMapper<DictType> {}
      
    6. 注释: <#-- -->: 这种注释不会渲染到HTML中

Freemarker集成到SpringBoot

  1. pom.xml添加依赖

     <!-- Freemarker-->
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-freemarker</artifactId>
     </dependency>
    
  2. 跟前面讲的Thymeleaf一样的地方
    1. 静态资源:可以放classpath:/static/目录下,里面没有需要设置变量的文件,比如html、js、css、png等文件,浏览器能直接访问
    2. 模板文件:可以放classpath:/templates/目录下,文件里面需要插入一些变量的文件,比如html,浏览器不能直接访问
  3. 主要注意的是,在SpringBoot中,freemarker的模板文件扩展名要用.ftlh,而不是.ftl,默认的视图解析器会增加前缀:classpath:/templates/,增加后缀:.ftlh
  4. 模板中获取项目的根路径
    1. 通常情况下html中引用的css、js文件都放在static文件目录下了,那么如何动态引用这些路径呢?
    2. 可以在ftlh中直接获取项目根路径

       <!--获取contextpath,赋值给变量ctx-->
       <#assign ctx="${springMacroRequestContext.getContextPath()}">
      
  5. 项目举例:
    1. 在项目JiaKao的resources下新建static、templates文件夹
    2. 将准备好的静态资源文件放到static中
    3. 模板文件放到templates下的page文件夹下:dictType.ftlh

       <#assign ctx="${springMacroRequestContext.getContextPath()}">
       <!--定义一个变量name-->
       <#assign name="ok!!!">
       <!DOCTYPE html>
       <html dir="ltr" lang="zh">
       <head>
           <meta charset="UTF-8">
           <!-- Favicon icon -->
           <link rel="icon" type="image/png" sizes="16x16" href="${ctx}/assets/imgs/favicon.png">
           <title>数据字典类型</title>
           <link href="${ctx}/assets/libs/sweetalert2/sweetalert2.min.css" rel="stylesheet">
           <!-- Custom CSS -->
           <link href="${ctx}/assets/css/style.css" rel="stylesheet">
           <link href="${ctx}/assets/css/main.css" rel="stylesheet">
       </head>
       <body>
       ${name}
       ...
       <!--遍历将变量数据插入-->
       <#list data as item>
           <tr>
           <td>
               <input type="checkbox" id="cb_0" class="material-inputs filled-in chk-col-light-blue">
               <label for="cb_0"></label>
           </td>
           <td>${item.name}</td>
           <!--加!作用是,没有值也不会报错-->
           <td>${item.value!}</td>
           <td>${item.intro}</td>
           <td>
               <div class="btn-group">
                   <button class="btn btn-info btn-edit"><i class="ti-pencil-alt"></i></button>
                   <button class="btn btn-danger btn-remove"><i class="ti-trash"></i></button>
               </div>
           </td>
       </tr>
       </#list>
       ....
       </body>
      
    4. DictTypeController改造如下

       @Controller
       @RequestMapping("/dictTypes")
       public class DictTypeController {
              
           @Autowired
           private DictTypeService service;
          
           @GetMapping("/list")
           public String list(Model model) {
               model.addAttribute("data",service.list());
               //默认情况下回自动添加前缀:classpath:/templates/,默认后置:.ftlh
               return "page/dictType";
           }
       }
      
  6. 相同标签的抽取
    1. 如果n个页面有相同的模块代码可以抽取,然后共同引用,抽取head标签内容如下

       //style.ftlh
       <meta charset="UTF-8">
       <title>数据字典类型</title>
       <!-- Favicon icon -->
       <link rel="icon" type="image/png" sizes="16x16" href="${ctx}/assets/imgs/favicon.png">
       <link href="${ctx}/assets/libs/sweetalert2/sweetalert2.min.css" rel="stylesheet">
       <!-- Custom CSS -->
       <link href="${ctx}/assets/css/style.css" rel="stylesheet">
       <link href="${ctx}/assets/css/main.css" rel="stylesheet">
      
    2. 实际页面中引用

       <#include "common/style.ftlh">
      

Swagger

  1. swagger可以快速生成接口文档,极大地节省了开发人员编写接口文档的时间
  2. 添加pom依赖
    1. 方式一:添加官方依赖

       <!-- 接口文档 -->
       <dependency>
           <groupId>io.springfox</groupId>
           <artifactId>springfox-swagger2</artifactId>
           <version>2.9.2</version>
       </dependency>
       <dependency>
           <groupId>io.springfox</groupId>
           <artifactId>springfox-swagger-ui</artifactId>
           <version>2.9.2</version>
       </dependency>
      
    2. 方式二:添加springboot集成的依赖(更常用)

       <dependency>
           <groupId>io.springfox</groupId>
           <artifactId>springfox-boot-starter</artifactId>
           <version>3.0.0</version>
       </dependency>
      
  3. 基本使用
    1. 在容器中直接添加一个配置即可

       //com.zh.jk.common.cfg
       //将该来添加到容器
       @Configuration
       @EnableSwagger2
       public class SwaggerCfg implements InitializingBean {
           @Bean
           //参数自动注入,注入的是application.xml中的spring
           public Docket docket(Environment environment) {
               //判断是否是开发、测试环境,profiles:active: 的设置值
               boolean enable = environment.acceptsProfiles(Profiles.of("dev","test"));
               Docket docket = new Docket(DocumentationType.SWAGGER_2)
                       //生成文档的时候忽略某些参数
                       .ignoredParameterTypes(HttpSession.class, HttpServletRequest.class, HttpServletResponse.class)
                       //指定这个文档归属哪个组
                       .groupName("元数据")
                       .enable(enable) //接口文档在测试、开发环境下才能使用
                       .apiInfo(apiInfo());
              
               ApiSelectorBuilder builder = docket.select();
               //指定哪些API开放出来,路径中以dict开头的所有url
               //builder.paths(PathSelectors.ant("/dict*/**"));
               //拥有RestController注解的开放
               //builder.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class));
               //指定哪个包下的api被开放
               builder.apis(RequestHandlerSelectors.basePackage("com.zh.jk.controller"));
               //正则表达式设置开放api
               //builder.paths(PathSelectors.regex("/dict.+"));
               //带有GetMapping注解的接口方法
               //builder.apis(RequestHandlerSelectors.withMethodAnnotation(GetMapping.class));
               return docket;
           }
              
           private ApiInfo apiInfo() {
               return new ApiInfoBuilder()
                       .title("小MG考系统接口文档")
                       .description("这是一份比较详细的接口文档")
                       .version("1.0.0")
                       .build();
           }
       }
      
    2. 注意:如果使用的是starter,需要去掉@EnableSwagger2

  4. 运行项目,直接访问如下地址
    1. 如果没有使用starter
      1. http://localhost:8888/swagger-ui.html
    2. 如果使用了starter
      1. http://localhost:8888/swagger-ui/index.html

使用高级

  1. 可以将项目中所有的controller接口,根据业务区分成不同的分组来展示接口文档
  2. 以及如果每个接口都要传递一个token,可以给接口文档全局设置这个参数

     @Configuration
     @EnableSwagger2
     public class SwaggerCfg implements InitializingBean {
         @Autowired
         private Environment environment;
         private boolean enable;
        
         /**根据不同业务模块,文档分n个组**/
         @Bean
         public Docket sysDocket() {
             return groupDocket(
                     "01_系统",
                     "/sys.*",
                     "系统模块文档",
                     "用户,角色,资源");
         }
        
         @Bean
         public Docket metadataDocket() {
             return groupDocket(
                     "02_元数据",
                     "/(dict.*|plate.*)",
                     "元数据模块文档",
                     "数据字典类型,数据字典条目,省份,城市");
         }
        
         @Bean
         public Docket examDocket() {
             return groupDocket(
                     "03_考试",
                     "/exam.*",
                     "考试模块文档",
                     "考场,科1科4,科2科3");
         }
        
         private Docket groupDocket(String group,
                                    String regex,
                                    String title,
                                    String description) {
             return basicDocket()
                     .groupName(group)
                     .apiInfo(apiInfo(title, description))
                     .select()
                     .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                     .paths(PathSelectors.regex(regex))
                     .build();
         }
        
         private Docket basicDocket() {
     //        RequestParameter token = new RequestParameterBuilder()
     //                .name(TokenFilter.HEADER_TOKEN)
     //                .description("用户登录令牌")
     //                .in(ParameterType.HEADER)
     //                .build();
             return new Docket(DocumentationType.SWAGGER_2)
                     //设置全局参数,每个请求都会携带的一个参数,比如token
     //                .globalRequestParameters(List.of(token))
                     .ignoredParameterTypes(
                             HttpSession.class,
                             HttpServletRequest.class,
                             HttpServletResponse.class)
                     .enable(enable);
         }
        
         private ApiInfo apiInfo(String title, String description) {
             return new ApiInfoBuilder()
                     .title(title)
                     .description(description)
                     .version("1.0.0")
                     .build();
         }
        
         //初始化完成,设置enable属性
         @Override
         public void afterPropertiesSet() throws Exception {
             enable = environment.acceptsProfiles(Profiles.of("dev", "test"));
         }
     }
    

常用注解

@Api(tags = "模块名",description = "描述"): 用在controller上
@ApiOperation(value="作用", notes = "备注"): 用在请求方法上
@ApiModel(value="实体名", description = "描述"): 用在entity上
@ApiModelProperty(value="属性名", hidden=false, required = false)
    在entity的属性上
@ApiParam(value = "参数名", hidden = false, required = false)
    用在请求参数上
@ApiImplicitParams、@ApiImplicitParam:用在请求方法上,描述参数信息
@ApiResponses、@ApiResponse:用在请求方法上,描述响应信息

Shiro

Shiro简介

  1. Shiro是apache推出的安全管理框架,比SpringSecurity更加简单易用,官方手册:http://shiro.apache.org/reference.html
  2. Shiro的2大核心功能
    1. 认证:比如登录认证,只有合法用户才能登录进入系统
    2. 授权:比如设置每个合法用户的权限范围,可以对哪些资源进行哪些操作(C?R?U?D?)

核心类

  1. SecurityManager: 安全管理器,Shiro最核心的类型之一
  2. Subject: 需要进行认证和授权的主体,比如用户,相当于前端
  3. Authenticator: 认证器,用来认证用户的,到Realm查询用户的信息,进行认证
  4. Authorizer: 授权器,用来用户授权的,到Realm查询用户的权限,判断用户是否有权限
  5. Realm: 相当于数据源,可以用于获取主体的权限信息,相当于数据库,专门存储用户的认证信息、权限信息

图1

内置的filter

  1. anon: org.apache.shiro.web.filter.authc.AnonymousFilter
  2. authc: org.apache.shiro.web.filter.authc.Form AuthenticationFilter
  3. authcBasic:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
  4. authcBearer:org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter
  5. invalidRequest: org.apache.shiro.web.filter.InvalidRequestFilter
  6. logout:org.apache.shiro.web.filter.authc.LogoutFilter
  7. noSessionCreation:org.apache.shiro.web.filter.session.NoSessionCreationFilter
  8. perms: org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
  9. port: org.apache.shiro.web.filter.authz.PortFilter
  10. rest: org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
  11. roles: org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
  12. ssl: org.apache.shiro.web.filter.authz.SslFilter
  13. user:org.apache.shiro.web.filter.authc.UserFilter

简单使用

1. 简单使用

  1. 新建一个项目25_TestShiro项目
  2. pom.xml添加依赖

     <dependencies>
         <dependency>
             <groupId>org.apache.shiro</groupId>
             <artifactId>shiro-core</artifactId>
             <version>1.7.0</version>
         </dependency>
    
         <!--shiro用到了SLF4J这个日志文件,但是缺乏实现,因此 可以根据自身需要选择SLF4J的实现-->
         <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
             <version>1.2.3</version>
         </dependency>
    
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <version>1.18.16</version>
             <scope>provided</scope>
         </dependency>
     </dependencies>
    
  3. 在resources下新建一个realm.inl文件
    1. 相当于存储数据的地方,类似数据库

       [users]
       //用户名    密码    角色
       zhangsan = 123456, admin
       lisi = 123456, normal
       zh = 123456, normal
              
       [roles]
       //角色    权限1         权限2     权限3         权限4
       admin = user:create, user:read, user:update, user:delete
       normal = user:read
      
  4. 创建一个Main类,main函数测试代码如下:

     // 安全管理器:DefaultSecurityManager是SecurityManager的实现类型
     DefaultSecurityManager mgr = new DefaultSecurityManager();
     // 设置安全管理器
     SecurityUtils.setSecurityManager(mgr);
     // 设置Realm,通常资源是数据库,这里先搞一个ini最为数据源
     mgr.setRealm(new IniRealm("classpath:realm.ini"));
    
     // 主体:Subject
     Subject subject = SecurityUtils.getSubject();
    
     // 登录
     String username = "zh";
     String password = "123456";
     UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    
     try {
         //【登录之前】isAuthenticated -> false
         System.out.println("【登录之前】isAuthenticated -> " + subject.isAuthenticated());
    
         subject.login(token);
         //【登录之后】isAuthenticated -> true
         System.out.println("【登录之后】isAuthenticated -> " + subject.isAuthenticated());
         //判断用户是否有相应的权限
         //【权限】user:read -> true
         System.out.println("【权限】user:read -> " + subject.isPermitted("user:read"));
         //【权限】user:update -> false
         System.out.println("【权限】user:update -> " + subject.isPermitted("user:update"));
         //【权限】user:delete -> false
         System.out.println("【权限】user:delete -> " + subject.isPermitted("user:delete"));
         //【权限】user:create -> false
         System.out.println("【权限】user:create -> " + subject.isPermitted("user:create"));
    
         //判断用户是否有相应的角色
         //【角色】admin -> false
         System.out.println("【角色】admin -> " + subject.hasRole("admin"));
         //【角色】normal -> true
         System.out.println("【角色】normal -> " + subject.hasRole("normal"));
    
         // 退出登录
         subject.logout();
         //【退出登录】isAuthenticated -> false
         System.out.println("【退出登录】isAuthenticated -> " + subject.isAuthenticated());
         //【角色】normal -> false
         System.out.println("【角色】normal -> " + subject.hasRole("normal"));
     } catch (UnknownAccountException e) {
         System.out.println("用户名不存在");
     } catch (IncorrectCredentialsException e) {
         System.out.println("密码不正确");
     } catch (AuthenticationException e) {
         System.out.println("认证失败");
     }
    

2. 存储源为数据库

  1. 创建一个类Dbs模拟数据库

     public class Dbs {
         //到数据库中查询用户的用户名、密码
         public static SysUser get(String username) {
             if ("zh666".equals(username)) {
                 SysUser user = new SysUser();
                 user.setUsenrname("zh666");
                 user.setPassword("zh666");
                 return user;
             } else if ("zh333".equals(username)) {
                 SysUser user = new SysUser();
                 user.setUsenrname("zh333");
                 user.setPassword("zh333");
                 return user;
             }
             return null;
         }
        
         //到数据库查询用户的角色
         public static List<String> getRoles(String username) {
             if ("zh666".equals(username)) {
                 return  List.of("admin");
             } else if ("zh333".equals(username)) {
                 return List.of("normal");
             }
             return null;
         }
        
         //到数据库中查询用户的权限
         public static List<String> getPermissions(String username) {
             if ("zh666".equals(username)) {
                 return List.of("user:create", "user:update", "user:delete");
             } else  if ("zh333".equals(username)) {
        
                 return List.of("user:read");
             }
             return null;
         }
     }
    
  2. SysUser

     @Data
     public class SysUser {
         private String usenrname;
         private String password;
     }
    
  3. 自定义Realm类CustomRealm

     public class CustomRealm extends AuthorizingRealm {
    
         /**
          * 当主体(subject)要进行权限\角色(isPermitted、hasRole)判断时,就会调用
          *
          * 开发者需要在这个方法中干啥?【一般】
          * 根据用户名查询用户的角色\权限信息
          *
          * @return 用户的角色\权限信息
          */
         @Override
         protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
             //获取用户名
             String username = (String) principals.getPrimaryPrincipal();
                
             //数据源
             SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
                
             //填充当前用户的角色数据源
             List<String> roles = Dbs.getRoles(username);
             if (roles != null) {
                 info.addRoles(roles);
             }
             //填充当前用户的权限数据源
             List<String> permissions = Dbs.getPermissions(username);
             if (permissions != null) {
                 info.addStringPermissions(permissions);
             }
        
             return info;
         }
        
         /**
          * 当主体(subject)要进行认证(login、logout)时,就会调用
          *
          * 开发者需要在这个方法中干啥?【一般】
          * 根据用户名查询用户的具体信息(用户名、密码)
          *
          * @param token subject.login()登录时传进来的token
          * @return 用户名的具体信息(用户名、密码)
          */
         @Override
         protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
             // 获取token
             UsernamePasswordToken tk = (UsernamePasswordToken) token;
        
             //根据token获取用户名
             String username = (String) tk.getPrincipal();
        
             // 根据用户名去数据库中查询用户信息
             SysUser user = Dbs.get(username);
             if (user == null) return null;
        
             // 数据库查询出来的用户名密码,与当前传递的用户进行认证
             return new SimpleAuthenticationInfo(user.getUsenrname(), user.getPassword(), getName());
         }
     }
    
  4. main函数代码
    1. 仅仅将原来的mgr.setRealm代码替换成如下

       // 设置Realm
       mgr.setRealm(new CustomRealm());
      
  5. 分析
    1. 自定义CustomRealm类的原因是可以自定义Realm的数据源
    2. 认证流程如下:
      1. Subject.login(token)
      2. SecurityManager -> Authenticator -> Realm【AuthorizingRealm】
      3. info = AuthorizingRealm.doGetAuthenticationInfo(token),根据token信息查询对应的用户信息(比如去数据库中查找)
      4. CredentialsMatcher.doCredentialsMatch(token, info),判断token、info的Credentials是否匹配
        1. CredentialsMatcher:专门用来判断Credentials是否正确
        2. 即专门用来匹配传值与存储值的算法类
        3. 也可以自定义CredentialsMatcher的实现类,使用自己的算法类
    3. 判断权限、角色流程:
      1. Subject.isPermitted(permission)、Subject.hasRole(role)
      2. SecurityManager -> Authorizer -> Realm【AuthorizingRealm】
      3. info = AuthorizingRealm.doGetAuthorizationInfo(principal的集合),根据principal查询对应的角色、权限信息
      4. 根据返回的info信息与传进来的判断权限、角色是否正确
    4. 注意:上面流程要与上面的图做参照理解

逻辑删除

  1. 物理删除:真正从数据库中删除了,永久消失
  2. 逻辑删除:数据还保留在数据库中,只是对用户来说,数据被删除掉了
  3. 逻辑删除实现原理
    1. 增加一个字段标识数据是否被删除
    2. 增删改查

       //删
       UPDATE user SET deteleted =1 WHERE id = 2
       //查
       SELECT * FROM user WHERE xxx AND deteleted = 0
       //改
       UPDATE user SET age = 20 WHERE deteleted = 0
      

MyBatis-plus项目中实现逻辑删除

前提:数据库每张表必须有deleted字段

  1. 值针对一张表实现逻辑删除
    1. 比如用户表user对应po类为user,添加deleted字段

       @Data
       public class User {
           private Long id;
           private String name;
                  
           // 逻辑删除的局部配置,0代表不删除,delval为1,代表删除
           @TableLogic(value = "0", delval = "1")
           // 告诉sql,不要查询这个字段,这个字段只放在数据库中就好
           @TableField(select = false)
           private Short deleted;
       }
      
    2. 这样以来,之前的使用MP的针对user的所有增删改查方法都不变,当删除时,本质是逻辑删除,其他不需要做任何修改

  2. 如果想要将整个项目的所有表都使用逻辑删除
    1. 在application.yml中,做如下配置

       mybatis-plus:
         global-config:
           db-config:
             #id自动增长
             id-type: auto
             # 全局配置
             # 逻辑删除字段
             logic-delete-field: deleted
             # 代表已删除的字段值
             logic-delete-value: 1
             # 代表未删除的字段值
             logic-not-delete-value: 0
      
    2. 所有的po都要添加如下字段,以及注解

       // 告诉sql,不要查询这个字段,这个字段只放在数据库中就好
       @TableField(select = false)
       private Short deleted;
      
    3. 这样一来,项目中的所有删除、添加都是逻辑删除,其他不需要做任何改动

注意:自定义SQL语句时(不使用MP自带的api操作),MP并不会自动加上逻辑删除相关的功能,需要自实现逻辑删除

  1. 比如之前项目中讲的,有些查询方法是联合几张表查询,而且字段不一样,MP没有实现对应的api方法,此时就需要在mapper中自定义查询方法,然后在xml中实现查询sql,在此时写查询sql时,如果需要逻辑删除,就需要自己写相应的语句了
Table of Contents