jeecg开源代码学习(1)


1. 从登录到增删查改整体流程

首先在拦截器上打一个断点,分析登录的过程

简单来说就是用户在请求中携带X_ACCESS_TOKEN这个参数,然后将这个参数转交给后端,后端将其解析为JWT对象进行验证,调用底层的login接口,如果没有抛exception就是登录成功了

这个TOKEN的有效时间是一个小时

权限管理

权限管理的包在下面这里

package org.jeecg.modules.system.controller;

保存角色权限的逻辑

@RequestMapping(value = "/saveRolePermission", method = RequestMethod.POST)
//@RequiresRoles({ "admin" })<====
public Result<String> saveRolePermission(@RequestBody JSONObject json) {
	long start = System.currentTimeMillis();
	Result<String> result = new Result<>();
	try {
		String roleId = json.getString("roleId");
		String permissionIds = json.getString("permissionIds");
		String lastPermissionIds = json.getString("lastpermissionIds");
		this.sysRolePermissionService.saveRolePermission(roleId, permissionIds, lastPermissionIds);
		//update-begin---author:wangshuai ---date:20220316  for:[VUEN-234]用户管理角色授权添加敏感日志------------
           LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
		baseCommonService.addLog("修改角色ID: "+roleId+" 的权限配置,操作人: " +loginUser.getUsername() ,CommonConstant.LOG_TYPE_2, 2);
           //update-end---author:wangshuai ---date:20220316  for:[VUEN-234]用户管理角色授权添加敏感日志------------
		result.success("保存成功!");
		log.info("======角色授权成功=====耗时:" + (System.currentTimeMillis() - start) + "毫秒");
	} catch (Exception e) {
		result.error500("授权失败!");
		log.error(e.getMessage(), e);
	}
	return result;
}

String roleId = json.getString("roleId");
String permissionIds = json.getString("permissionIds");
String lastPermissionIds = json.getString("lastpermissionIds");
baseCommonService.addLog("修改角色ID: "+roleId+" 的权限配置,操作人: " +loginUser.getUsername() ,CommonConstant.LOG_TYPE_2, 2);

这里的话可以看出,这里的话这个框架做了个优化,按照一般的方法是前端把这个表单上所有的授权人的ID都拿到,然后删除对应的表中全部授权,然后再把这个这次的授权全部刷回去。

但是它做了一个优化,把用户做出操作之前的授权人ID先存下来,然后再存一个做出操作后的授权人ID,然后在service中做一个比较,根据比较的差异来进行删除、增加操作。

经过以上的流程就是一个添加权限的过程

增删查改的支持

jeecg-boot的增删查改的支持是通过查询生成器来完成的

package org.jeecg.common.system.query;

这个类中包含了大量组装SQL的方法,通过前端发送的信息,来组装SQL,最终完成一键建表,在前台CRUD的功能

1.1 Shiro框架简介

Shiro是一个功能强大而且容易使用的Java安全框架,执行身份验证授权加密会话管理,使用Shiro易于理解的api,可以快速搭建安全验证框架。

  • Subject:是主体的含义,外部应用与subject进行交互,subject中记录了当前的操作用户,将用户的概念理解为当前操作的主体。外部程序通过subject进行认证授权,而subject是通过SecurityManager安全管理器进行认证授权的

  • SecurityManager:也就是安全管理器,对全部的subject进行安全管理,是shiro的核心,负责对所有的subject进行安全管理,通过SM可以完成对subject的认证,授权等,实际上SM是通过Authenticator进行认证的,通过Authorizer进行授权,通过SessionManager进行会话管理。

简单来说,SecrityManager集成了认证,授权,连接会话管理的三大功能。

  • Authenticator:认证器,对用户身份进行认证,它是一个接口标准,shiro提供了ModularRealmAuthenticator实现子类,也可以自定义认证器。
  • Authorizer:也就是授权器,用户通过认证器通过,在访问功能的时候需要通过授权器判断用户是否具有此功能的操作权限。
  • Realm:领域,相当于dataSource数据源,SM进行安全认证的时候需要通过Relam获取用户权限数据
  • SessionManager:会话管理,shiro框架定义了一套会话管理,它不依赖于web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点进行管理,此特性可以使得它实现单点登录。

1.2 认证

身份认证:就是判断一个用户是否是合法用户的处理过程,最常用的简单身份认证方式就是通过核对用户输入的口令,看其是否与系统中存储的该用户名和口令一致,来判断用户身份是否正确。

  • principal:身份信息,是主体进行身份认证的标识,标识必须具有唯一性,一个主体可以有多个身份,但是必须有一个主身份。
  • credential:凭证信息,是只有主体知道的安全信息,如密码,证书等。

1.3 helloworld程序

// 1、创建安全管理器对象
      DefaultSecurityManager securityManager = new DefaultSecurityManager();
      // 2、给安全管理器设置realm,这个里面会存储一个键值对,可以由用户自行规定
//比如说我们这里设定的是username = password
      securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
      // 3、给全局安全工具类SecurityUtils设置安全管理器
      SecurityUtils.setSecurityManager(securityManager);
      // 4、拿到当前的subject
      Subject subject = SecurityUtils.getSubject();
      // 5、创建令牌
      AuthenticationToken token = new UsernamePasswordToken("christy","123456");

      try {
          // 6、用户认证
          System.out.println("认证状态:"+subject.isAuthenticated());
          subject.login(token);
          System.out.println("认证状态:"+subject.isAuthenticated());
      } catch (UnknownAccountException e){
          e.printStackTrace();
          System.out.println("认证失败:用户不存在!");
      } catch (IncorrectCredentialsException e){
          e.printStackTrace();
          System.out.println("认证失败:密码不正确!");
      } catch (Exception e){
          e.printStackTrace();
      }
  }

1.4 使用自定义的Realm实现认证

首先我们需要搞清楚其内部是如何运作的。

在这一步中,我们首先是通过UsernamePasswordToken("christy","123456"),处理后得到一个token对象,然后调用了主体的login方法,这个方法需要一个token

然后这里做了一个装饰器,将login交给底层的securityManager来做login,同样的,这次的认证需要一个被认证的主体,以及根据用户和口令产生的token进行验证

做方法分发,将token分发到认证器。

对token进行认证

我们直接来看到这个doGet的方法,这个方法中,校验了用户是否被锁定或者被禁用。那么应该还有一个密码的校验方法。

然后用户名通过了校验,那么就会调用断言方法进行密码匹配。

/**
 * 自定义realm
 */
public class CustomerRealm extends AuthorizingRealm {
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 在token中获取用户名
        String principal = (String) token.getPrincipal();
        System.out.println(principal);
        // 模拟根据身份信息从数据库查询
        if("christy".equals(principal)){
            // 参数说明:用户名 | 密码 | 当前realm的名字
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"123456",this.getName());
            return simpleAuthenticationInfo;
        }

        return null;
    }
}

2.业务代码源代码阅读

以一个简单的对Room实体类的增删查改进行分析。

2.1 Swagger框架简介

Swagger是一套围绕OpenAPI规范构建的开源工具,可以帮助设计,构建和记录使用Restful API,api文档和api定义同步更新。

  • 导入swagger
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.10.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-common -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-common</artifactId>
    <version>2.10.5</version>
</dependency>
  • 开启swagger
@Configuration
@EnableSwagger2
class SwaggerConfig{
    //配置Swagger的实例
    @Bean
    public Docket getDocket(){
        return new Docket(DocumentationType.SWAGGER_2);
    }
}

2.2 常用配置

@Configuration
@EnableSwagger2
class SwaggerConfig{
    //配置Swagger的实例
    @Bean
    public Docket getDocket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")//配置分组
                .apiInfo(webApiInfo())
                .select()
                .paths(Predicates.and(PathSelectors.regex("/api/.*")))
                .build();
    }

    @Bean
    public Docket adminApiConfig(){

        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("adminApi")
                .apiInfo(adminApiInfo())
                .select()
                //只显示admin路径下的页面
                .paths(Predicates.and(PathSelectors.regex("/admin/.*")))
                .build();

    }

    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("网站-API文档")
                .description("本文档描述了os-system中后端接口定义")
                .version("1.0")
                .contact(new Contact("Lumxi", "https://lumxi.github.io/", "1274356440@qq.com"))
                .build();
    }

    private ApiInfo adminApiInfo(){
        return new ApiInfoBuilder()
                .title("模拟操作系统-API文档")
                .description("本文档描述了os-system中后端接口定义")
                .version("1.0")
                .contact(new Contact("Lumxi", "https://lumxi.github.io/", "1274356440@qq.com"))
                .build();
    }

}

2.3 常用注解

  • @ApiModel:修饰整个类,描述controller的作用
@ApiModel(value="room对象", description="房间表")//表示这个类是什么,是拿来干什么的
public class Room implements Serializable {}

在实体类上使用,标记类时swagger会解析这个类

  • @ApiOperation:描述一个类的一个方法,或者一个接口
@ApiOperation(value="房间表-分页列表查询", notes="房间表-分页列表查询")
public Result<IPage<Room>> queryPageList(){}
  • @ApiParam:单个参数的描述

Rest接口上或者Rest接口参数前使用,为Rest接口参数添加其他元数据

public CommonResult updatePassword(
    @ApiParam(value = "手机号码",required = true) @RequestParam String userPhone,
    @ApiParam(value = "验证码",required = true) @RequestParam String authCode){

    return userMemberService.updatePassword(userPhone,authCode);
}
  • @ApiModelProperty:用对象来接收参数的时候,用来描述对象的一个字段

在使用被@ApiModelProperty注解的模型类的属性上,表示对model属性的说明或者数据操作更改。

@ApiModelProperty(value = "主键")
private String id;
  • @ApilmplicitParam:一个请求参数

  • @ApiImplicitParams:多个请求参数

@ApiImplicitParams({
        @ApiImplicitParam(name = "mobile", value = "手机号码", dataType = "string", paramType = "query", example = "13802780104", required = true),
        @ApiImplicitParam(name = "user_name", value = "登录账号", dataType = "string", paramType = "query", example = "lihailin9073", required = true),
        @ApiImplicitParam(name = "password", value = "登录密码", dataType = "string", paramType = "query", example = "123456", required = true),
        @ApiImplicitParam(name = "validate_code", value = "注册验证码", dataType = "string", paramType = "query", example = "3679", required = true)
})

2.4 实体类阅读总结

实体类中定义了我们在使用低代码时使所定义的字段,同时通过Swagger来生成对应的文档字段,其中@Excel字段是autopoi中的注解,应该是@Excel相关的注解。

2.5 DAO&Service层阅读总结

在代码生成器中,DAO 层全部使用的是MP的接口,需要注意的是,如果要正确注入mapper,需要在启动项上加上mapperScan

2.6 Controller

  • RequsestParam

将请求参数绑定到控制器的方法参数上

@RequestParam(value="参数名",required="true/false",defaultValue="")

文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录