项目实战十一-扩展:MyBatis-Plus、MyBatis Generator、EasyCode、Freemarker、Swagger、Shiro
MyBatis-Plus
- MyBatis-Plus(简称MP)是MyBatis的增强工具,由国人开发,中文
- 在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生
- 官网:https://mp.baomidou.com/
MyBatis-Plus简单使用
-
添加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>
-
application.yml中添加别名配置
# 设置别名 mybatis-plus: type-aliases-package: com.zh.jk.pojo
- mapper改造
- 继承BaseMapper,并告知对应的po对象,因为BaseMapper封装了很多sql方法,其中就有list方法
//继承BaseMapper,并告知对应的po对象DictType //因为BaseMapper封装了很多sql方法 public interface DictTypeMapper extends BaseMapper<DictType> {}
- service层改造
- 接口继承自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 {}
-
控制器
@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
简介
- 官方网址http://mybatis.org/generator/
- MyBatis Generator是MyBatis官方提供的代码生成器,可以根据数据库信息,逆向生成Model类、Mpper类、Mapper配置文件xml,大大节省开发者的编码时间,提高开发效率
使用步骤
-
在项目的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>
-
项目的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>
-
执行插件生成
- 点击右侧maven菜单->找到对应项目->plugins->找到mybatis-generator插件,展开,点击mybatis-generator:generate
- 就可以看到test文件夹下的对应目录生成了相应的文件
- 然后将src/test/java/com.zh.jk.pojo.po下面的类全部复制到src/main/java/com.zh.jk.pojo.po中
总结
- 插件尽管自动生成了Model类、Mpper类、Mapper配置文件xml,但是通常仅仅只需要po中的类
- 该插件会自动将数据库的下划线转化为驼峰标识,比如数据库的字段为created_time,转化为模型类的属性为createdTime
EasyCode
- 一款国产的代码生成插件,使用文档:https://gitee.com/makejava/EasyCode/wikis/pages
- 注意:搜索插件时,Easy和Code之间有一个空格
- Preferences->plugins->搜索Easy Code ->点击install即可
- 点击IDEA右侧DataBase->找到数据库-找到要生成的表->dic_type->右击->easycodermybytiscodehelper->Generate Code(直接生成)/Config Table(可以设置哪些字段不用生成)
- 使用步骤
- 设置类型映射
- 就是设置好MYSQL的数据类型对应Java的哪些类型
-
Preferences->Easy Code-> Type Mapper->点击右边的+新增一些类型转化,新增的具体如下图所示
- 生成某种格式可以进行配置
- Preferences->Easy Code->Template Setting->Group Name 选择MybatisPlus,点击右边的复制图标,可以复制默认的MybatisPlus,然后自定义名称MybatisPlus_ZH
-
这里面可以配置各个层的生成模板
entity: po对象,跟表一一对应的模型对象 dao:dao对象 service:service层 serviceImpl: controller: ...
- 具体的可以参照写好的模板(已上传到JiaKaoBE项目中,MyBatisPlus_ZH),复制粘贴
- 然后apply即可
- 使用
- 点击DataBase->找到数据库-找到要生成的表->dic_type(可以同时选择多张表)->右击->Generate Code
-
配置:
Module:当前项目 Package: 要生成到哪个package下: com.zh.jk path:项目的项目的实际代码路径 Template: 勾选你要生成的所有类,勾选:统一配置、禁止提示
- 点击OK即可
- 会在com.zh.jk下生成对应的数据
- 注意: EasyCode如果有bug,可以直接使用EasyCode-MybatisCodeHepler插件,使用方法跟上面一样
- 设置类型映射
Freemarker
- 官方网站http://freemarker.foofun.cn
- 跟此前学过的JSP、Thymeleaf一样,Freemarker也是一款优秀的模板引擎
- 可以生成任意格式的文本文件(HTML、XML、Java等),与web环境无关,不依赖web容器
- 意思就是如果一个文件(任意格式)中有一些数据是变量,需要动态插入,都可以用Freemarker自动生成,不仅仅是xml/html文件,可以是任何其他的文件,比如有n个类,代码基本一样,就名称不一样,可以用Freemarker生成。
- 是在公司中非常受欢迎的一款模板引擎
- 对比JSP、Thymeleaf
- JSP:只能用在web环境中,依赖于web容器
- Thymeleaf:虽然不依赖web容器,但它只能生成XML规范的文本文件(比如HTML、XML)
Freemarker简单使用
- 新建一个项目24_Freemarker
-
pom.xml添加依赖
<dependencies> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.23</version> </dependency> </dependencies>
- 准备一个模板文件,用来根据这个模板动态生成目标文件
- 它的文件扩展名是:ftl(FreeMarker Template Language)
- 在电脑的某个磁盘或者其他位置创建一个文件夹,在该文件夹下新建一个mapper.ftl
-
比如:
/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}> {}
-
新建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); } } }
-
运行,就会看到在指定路径下生成了一个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> {}
- 注释:
<#-- -->
: 这种注释不会渲染到HTML中
Freemarker集成到SpringBoot
-
pom.xml添加依赖
<!-- Freemarker--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
- 跟前面讲的Thymeleaf一样的地方
- 静态资源:可以放classpath:/static/目录下,里面没有需要设置变量的文件,比如html、js、css、png等文件,浏览器能直接访问
- 模板文件:可以放classpath:/templates/目录下,文件里面需要插入一些变量的文件,比如html,浏览器不能直接访问
- 主要注意的是,在SpringBoot中,freemarker的模板文件扩展名要用.ftlh,而不是.ftl,默认的视图解析器会增加前缀:classpath:/templates/,增加后缀:
.ftlh
- 模板中获取项目的根路径
- 通常情况下html中引用的css、js文件都放在static文件目录下了,那么如何动态引用这些路径呢?
-
可以在ftlh中直接获取项目根路径
<!--获取contextpath,赋值给变量ctx--> <#assign ctx="${springMacroRequestContext.getContextPath()}">
- 项目举例:
- 在项目JiaKao的resources下新建static、templates文件夹
- 将准备好的静态资源文件放到static中
-
模板文件放到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>
-
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"; } }
- 相同标签的抽取
-
如果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">
-
实际页面中引用
<#include "common/style.ftlh">
-
Swagger
- swagger可以快速生成接口文档,极大地节省了开发人员编写接口文档的时间
- 添加pom依赖
-
方式一:添加官方依赖
<!-- 接口文档 --> <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>
-
方式二:添加springboot集成的依赖(更常用)
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version> </dependency>
-
- 基本使用
-
在容器中直接添加一个配置即可
//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(); } }
-
注意:如果使用的是starter,需要去掉
@EnableSwagger2
-
- 运行项目,直接访问如下地址
- 如果没有使用starter
http://localhost:8888/swagger-ui.html
- 如果使用了starter
http://localhost:8888/swagger-ui/index.html
- 如果没有使用starter
使用高级
- 可以将项目中所有的controller接口,根据业务区分成不同的分组来展示接口文档
-
以及如果每个接口都要传递一个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简介
- Shiro是apache推出的安全管理框架,比SpringSecurity更加简单易用,官方手册:http://shiro.apache.org/reference.html
- Shiro的2大核心功能
- 认证:比如登录认证,只有合法用户才能登录进入系统
- 授权:比如设置每个合法用户的权限范围,可以对哪些资源进行哪些操作(C?R?U?D?)
核心类
- SecurityManager: 安全管理器,Shiro最核心的类型之一
- Subject: 需要进行认证和授权的主体,比如用户,相当于前端
- Authenticator: 认证器,用来认证用户的,到Realm查询用户的信息,进行认证
- Authorizer: 授权器,用来用户授权的,到Realm查询用户的权限,判断用户是否有权限
- Realm: 相当于数据源,可以用于获取主体的权限信息,相当于数据库,专门存储用户的认证信息、权限信息
内置的filter
- anon: org.apache.shiro.web.filter.authc.AnonymousFilter
- authc: org.apache.shiro.web.filter.authc.Form AuthenticationFilter
- authcBasic:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
- authcBearer:org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter
- invalidRequest: org.apache.shiro.web.filter.InvalidRequestFilter
- logout:org.apache.shiro.web.filter.authc.LogoutFilter
- noSessionCreation:org.apache.shiro.web.filter.session.NoSessionCreationFilter
- perms: org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
- port: org.apache.shiro.web.filter.authz.PortFilter
- rest: org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
- roles: org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
- ssl: org.apache.shiro.web.filter.authz.SslFilter
- user:org.apache.shiro.web.filter.authc.UserFilter
简单使用
1. 简单使用
- 新建一个项目25_TestShiro项目
-
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>
- 在resources下新建一个realm.inl文件
-
相当于存储数据的地方,类似数据库
[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
-
-
创建一个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. 存储源为数据库
-
创建一个类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; } }
-
SysUser
@Data public class SysUser { private String usenrname; private String password; }
-
自定义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()); } }
- main函数代码
-
仅仅将原来的mgr.setRealm代码替换成如下
// 设置Realm mgr.setRealm(new CustomRealm());
-
- 分析
- 自定义CustomRealm类的原因是可以自定义Realm的数据源
- 认证流程如下:
- Subject.login(token)
- SecurityManager -> Authenticator -> Realm【AuthorizingRealm】
- info = AuthorizingRealm.doGetAuthenticationInfo(token),根据token信息查询对应的用户信息(比如去数据库中查找)
- CredentialsMatcher.doCredentialsMatch(token, info),判断token、info的Credentials是否匹配
- CredentialsMatcher:专门用来判断Credentials是否正确
- 即专门用来匹配传值与存储值的算法类
- 也可以自定义CredentialsMatcher的实现类,使用自己的算法类
- 判断权限、角色流程:
- Subject.isPermitted(permission)、Subject.hasRole(role)
- SecurityManager -> Authorizer -> Realm【AuthorizingRealm】
- info = AuthorizingRealm.doGetAuthorizationInfo(principal的集合),根据principal查询对应的角色、权限信息
- 根据返回的info信息与传进来的判断权限、角色是否正确
- 注意:上面流程要与上面的图做参照理解
逻辑删除
- 物理删除:真正从数据库中删除了,永久消失
- 逻辑删除:数据还保留在数据库中,只是对用户来说,数据被删除掉了
- 逻辑删除实现原理
- 增加一个字段标识数据是否被删除
-
增删改查
//删 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字段
- 值针对一张表实现逻辑删除
-
比如用户表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; }
-
这样以来,之前的使用MP的针对user的所有增删改查方法都不变,当删除时,本质是逻辑删除,其他不需要做任何修改
-
- 如果想要将整个项目的所有表都使用逻辑删除
-
在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
-
所有的po都要添加如下字段,以及注解
// 告诉sql,不要查询这个字段,这个字段只放在数据库中就好 @TableField(select = false) private Short deleted;
-
这样一来,项目中的所有删除、添加都是逻辑删除,其他不需要做任何改动
-
注意:自定义SQL语句时(不使用MP自带的api操作),MP并不会自动加上逻辑删除相关的功能,需要自实现逻辑删除
- 比如之前项目中讲的,有些查询方法是联合几张表查询,而且字段不一样,MP没有实现对应的api方法,此时就需要在mapper中自定义查询方法,然后在xml中实现查询sql,在此时写查询sql时,如果需要逻辑删除,就需要自己写相应的语句了