Java 企业级权限管理项目笔记(六) - - - 项目准备与核心辅导工具类开发

权限管理开发-准备

一、核心类生成 - Mybatis generate

功能 : MyBatis官方提供了逆向工程 mybatis-generator,可以针对数据库表自动生成MyBatis执行所需要的代码(如Mapper.java、Mapper.xml、POJO)。mybatis-generator 有三种用法:命令行、eclipse插件、maven插件。

1、添加generate插件到permission工程中   

   

根据插件包路径对配置文件 generator.xml 配置文件进行相应修改:

第一点:

修改为mysql-connector-java.5.1.34.jar所在的路径

第二点:

修改数据库连接配置

	<jdbcConnection driverClass="com.mysql.jdbc.Driver"				
	connectionURL="jdbc:mysql://192.168.254.111:3306/test1?characterEncoding=utf8"
			userId="root"
			password="root">  <!-- 2 -->
知识兔

第三点到第五点:

配置生成的类的包及保存路径

第六点:

配置要生成的表及对应的表的实体类名称。

2、执行生成命令:

J:\permission\permission\generator>java -jar mybatis-generator-core-1.3.2.jar -configfile generator.xml
MyBatis Generator finished successfully.
知识兔

执行结果:

                

二、项目接口定义 -json,page

后台收到前台的请求时,一般有两种请求,一种是数据请求,一种页面请求;返回请求结果时,除了返回请求的结果时,还需要返回相关的状态,用于返回异常以及异常描述。

创建JsonData用于封装数据

package com.webcode.springboot.common;

import lombok.Getter;
import lombok.Setter;

import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName JsonData
 * @Description TODO
 * @Author wushaopei
 * @Date 2019/10/11 14:05
 * @Version 1.0
 */
@Setter
@Getter
public class JsonData {

    private boolean ret;

    private String msg;

    private Object data;

    public JsonData(boolean ret) {
        this.ret = ret;
    }
    //需要传数据和异常状态
    public static  JsonData success(Object object,String msg){
        JsonData jsonData = new JsonData(true);
        jsonData.data = object;
        jsonData.msg = msg;
        return jsonData;
    }
    //只需要传数据
    public static JsonData success(Object object){
        JsonData jsonData = new JsonData(true);
        jsonData.data = object;
        return jsonData;
    }
    //不需要数据,只需要传状态
    public static JsonData success(){
        return  new JsonData(true);
    }
    //请求失败
    public  static JsonData fail(String msg){
        JsonData jsonData = new JsonData(false);
        jsonData.msg = msg;
        return jsonData;
    }
  
}
知识兔

ret为true代表请求结果为所需要的数据内容,为false时代表请求被拒绝或权限不足、异常等,配合msg进行描述。

三、接口请求全局异常处理-设计与验证

添加相关依赖:

    <!--jsp-->
    <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>jsp-api</artifactId>
      <version>6.0.36</version>
    </dependency>
知识兔

创建异常拦截类:

/**
 * @ClassName SpringExceptionResolver
 * @Description TODO
 * @Author wushaopei
 * @Date 2019/10/11 14:27
 * @Version 1.0
 */
@Slf4j
public class SpringExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object o, Exception ex) {
        String url = request.getRequestURL().toString();

        String[] split = url.split("");
        String s = split[split.length - 1];
        int i = s.indexOf("/");
        if(s.indexOf("/")==0){
            StringBuffer sb = new StringBuffer(url);
            url= sb.substring(0, sb.length() - 1);
            System.out.println(url);
        }

        ModelAndView mv; //声明一个modelandvie,用于返回
        String defaultMsg = "System error";

        //这里区别对数据请求和页面请求进行的处理
        //.json , .page ,请求的区分根据其后缀进行区分
        //这里要求项目中所有请求json数据,都使用.json结尾
        if (url.endsWith(".json")){
          
        }else{
            
        }
        return null;
    }
}
知识兔

这里使用@Slf4j进行日志的输出。

创建自定义异常类 PermissionException:

package com.webcode.springboot.exception;
/**
 * @ClassName PermissionException
 * @Description TODO
 * @Author wushaopei
 * @Date 2019/10/11 14:36
 * @Version 1.0
 */
public class PermissionException extends RuntimeException {

    public PermissionException() {
        super();
    }

    public PermissionException(String message) {
        super(message);
    }

    public PermissionException(String message, Throwable cause) {
        super(message, cause);
    }

    public PermissionException(Throwable cause) {
        super(cause);
    }

    public PermissionException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
知识兔

JsonData.java 补充:

 public Map<String, Object> toMap(){
        HashMap<String, Object> result = new HashMap<>();
        result.put("ret",ret);
        result.put("msg",msg);
        result.put("data",data);
        return result;
    }
知识兔

创建exception.jsp 异常描述页面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Exception</title>
</head>
<body>

</body>
</html>
知识兔

完整的异常拦截:

 @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object o, Exception ex) {
        String url = request.getRequestURL().toString();
        ModelAndView mv; //声明一个modelandvie,用于返回
        String defaultMsg = "System error";

        //这里区别对数据请求和页面请求进行的处理
        //.json , .page ,请求的区分根据其后缀进行区分
        //这里要求项目中所有请求json数据,都使用.json结尾
        if (url.endsWith(".json")){
            //当前异常是否匹配自定义异常拦截的实例或子类实例
            if(ex instanceof PermissionException){
                //只要异常是我们创建出来的时,才处理自己抛出来的异常
                JsonData fail = JsonData.fail(ex.getMessage());
                mv = new ModelAndView("jsonView",fail.toMap());
            }else {
                log.error("unknown json exception, url:" + url, ex);
                JsonData fail = JsonData.fail(defaultMsg);
                mv = new ModelAndView("jsonView",fail.toMap());
            }
        }else if(url.endsWith(".page")){
            // 这里我们要求项目中所有请求page页面,都使用.page结尾
            log.error("unknown page exception, url:" + url, ex);
            JsonData fail = JsonData.fail(defaultMsg);
            mv = new ModelAndView("exception",fail.toMap());
        }else {
            JsonData fail = JsonData.fail(defaultMsg);
            mv = new ModelAndView("jsonView",fail.toMap());
        }
        return mv;
    }
知识兔

为什么 ModelAndView的构造器加入 ".toMap()"方法:

               

由图中ModelAndView的源码可知,其构造器默认对数据使用Map进行封装,所以为了符合ModelAndView的代码规范,这里对JsonData的异常返回结果进行Map的转化。

而第一个参数“jsonView”对应spring-servlet.xml中的配置:

Bean配置:

    <bean class="com.webcode.springboot.common.SpringExceptionResolver"/>
知识兔

接口测试异常拦截:

使用普通的RuntimeException创建异常对象:

@Controller
@RequestMapping("/test")
@Slf4j
public class TestController {

    @RequestMapping("/hello.json")
    @ResponseBody
    public JsonData hello(){
        log.info("hello");
        throw new RuntimeException("test exception");
    }
}
知识兔

             

使用自定义的PermissionException创建异常对象:

   @RequestMapping("/hello.json")
    @ResponseBody
    public JsonData hello(){
        log.info("hello");
        throw new PermissionException("test exception");
//        return JsonData.success("hello,permission");
    }
知识兔

             

四、校验工具-BeanValidator开发

1、添加环境依赖:

    <!--validator-->
    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <version>1.1.0.Final</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>5.2.4.Final</version>
    </dependency>
知识兔

2、创建BeanValidator类

public class BeanValidator {

    private static ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();

    public static <T> Map<String,String> validate(T t, Class... groups){
        Validator validator = validatorFactory.getValidator();
        Set validateResult = validator.validate(t,groups);
        if (validateResult.isEmpty()){
            return Collections.emptyMap();
        }else {
            LinkedHashMap errors = Maps.newLinkedHashMap();
            Iterator iterator = validateResult.iterator();
            while (iterator.hasNext()){
                ConstraintViolation violation = (ConstraintViolation) iterator.next();
                errors.put(violation.getPropertyPath().toString(),violation.getMessage());
            }
            return errors;
        }
    }

    public static Map<String,String> validateList(Collection<?> collection){
        Preconditions.checkNotNull(collection);
        Iterator<?> iterator = collection.iterator();
        Map errors;

        do {
            if (!iterator.hasNext()){
                return Collections.emptyMap();
            }
            Object object = iterator.next();
            errors = validate(object,new Class[0]);
        }while (errors.isEmpty());
        return errors;
    }
   
}
知识兔

创建TestVo类:

@Getter
@Setter
public class TestVo {

    @NotBlank
    private String msg;

    @NotNull
    private Integer id;
}
知识兔

接口测试:

@ResponseBody
    @RequestMapping("/validate.json")
    public JsonData validate(TestVo vo){
        log.info("validate");
        try {
            Map<String, String> map = BeanValidator.validateObject(vo);
            if (map != null && map.entrySet().size() > 0){
                for (Map.Entry<String, String> entry : map.entrySet()) {
                    log.info("{}->{}",entry.getKey(),entry.getValue());
                }
            }
        }catch (Exception e){

        }
        return JsonData.success("test validate");
    }
知识兔

没有传参的情况下,控制台打印异常报警日志:

带参数的请求:

http://localhost/test/validate.json/?id=1&msg=123123
知识兔

没有异常报警:

BeanValidator的使用:

说明:BeanValidator是基于注解的,

基于@NotBlank、@NotNull、@NotEmpty等注解

@Getter
@Setter
public class TestVo {

    @NotBlank
    private String msg;
    
    @NotNull(message = "id不可以为空")
    @Max(value = 10,message = "id 不能大于10")
    @Min(value = 0,message = "id 至少大于等于0")
    private Integer id;
    
    @NotEmpty
    private List<String> str;
}
知识兔

对Object参数进行判空,不做结果返回:

  public static void check(Object param){
        //参数异常处理
        Map<String, String> map = BeanValidator.validateObject(param);
        if(MapUtils.isNotEmpty(map)){
            throw new ParamException(map.toString());
        }
    }
知识兔

MapUtils用于判空,需要引入相关依赖:

    <!--tools-->
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.2</version>
    </dependency>
    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.10</version>
    </dependency>
知识兔

创建参数异常处理类 ParamException.java

public class ParamException extends RuntimeException {
    public ParamException() {
    

    public ParamException(String message) {
        super(message);
    }
    public ParamException(String message, Throwable cause) {
        super(message, cause);
    }
    public ParamException(Throwable cause) {
        super(cause);
    }
    public ParamException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
知识兔

测试:

  @ResponseBody
    @RequestMapping("/validate2.json")
    public JsonData validate2(TestVo vo){
        log.info("validate2");
        BeanValidator.check(vo);
        return JsonData.success("test validate2");
    }
知识兔

五、权限管理开发 - 工具相关

校验工具 - validator

Json转换工具 - jackson convert

导入相关依赖:

   <!--jackson-->
    <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-core-asl</artifactId>
      <version>1.9.13</version>
    </dependency>
    <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-mapper-asl</artifactId>
      <version>1.9.13</version>
    </dependency>
知识兔

创建JsonMapper.java类,该类可以把类转换为一个json对象,也可以把类转换为我们指定的类对象。

package com.webcode.springboot.util;




import lombok.extern.slf4j.Slf4j;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
import org.codehaus.jackson.type.TypeReference;

/**
 * @ClassName JsonMapper
 * @Description TODO
 * @Author wushaopei
 * @Date 2019/10/11 22:56
 * @Version 1.0
 */
@Slf4j
public class JsonMapper {

    private static ObjectMapper objectMapper = new ObjectMapper();

    static {
        //config
        objectMapper.disable(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES);
        objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false);
        objectMapper.setFilters(new SimpleFilterProvider().setFailOnUnknownId(false));
        objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_EMPTY);
    }

    public static <T> String obj2String(T src){
        if (src == null){
            return null;
        }
        try {
            return src instanceof  String ? (String) src : objectMapper.writeValueAsString(src);
        }catch (Exception e){
            log.warn("parse object to String exception",e);
            return null;
        }
    }

    public static  <T> T  string2Obj(String src, TypeReference<T> typeReference){
        if (src == null || typeReference == null){
            return null;
        }
        try {
            return (T) (typeReference.getType().equals(String.class)? src : objectMapper.readValue(src,typeReference));
        }catch (Exception e){
            log.warn("parse String to Object exception,String:{},TypeReference<T>:{},error{}",src,typeReference.getType());
            return null;
        }
    }
}
知识兔

六、获取Spring上下文工具-ApplicationContextHelper开发

创建一个获取上下文的类:

package com.webcode.springboot.common;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @ClassName ApplicationContextHelper
 * @Description TODO
 * @Author wushaopei
 * @Date 2019/10/11 23:19
 * @Version 1.0
 */
//该注解将当前类交给Spring进行管理
@Component("applicationContextHelper")
public class ApplicationContextHelper implements ApplicationContextAware{

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        applicationContext = applicationContext;
    }

    public static <T> T popBean(Class<T> clazz){
        //如果applicationContext为空,则返回一个空值
        if(applicationContext == null){
            return null;
        }
        //否则,就返回当前applicationContext的Bean映射的类
        return applicationContext.getBean(clazz);
    }
    
    public static <T> T popBean(String name,Class<T> clazz){
        //如果applicationContext为空,则返回一个空值
        if(applicationContext == null){
            return null;
        }
        //否则,就返回name和当前applicationContext的Bean映射的类
        return applicationContext.getBean(name,clazz);
    }
}
知识兔

配置对应的Bean:

    <bean class="com.webcode.springboot.common.ApplicationContextHelper" lazy-init="false"/>
知识兔

测试:通过DB进行验证操作:

    @ResponseBody
    @RequestMapping("/validate3.json")
    public JsonData valitade3(TestVo vo){
        log.info("validate");
        SysAclModuleMapper moduleMapper = ApplicationContextHelper.popBean(SysAclModuleMapper.class);
        SysAclModule module = moduleMapper.selectByPrimaryKey(1);
        BeanValidator.check(vo);
        return JsonData.success("test validate3");
    }
知识兔

            ​\

七、 Http请求前后监听工具-HttpInterceptor开发

创建HttpInterceptor.java类,继承HandlerInterceptorAdapter类,对Http请求前后进行监听

@Slf4j
public class HttpInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURI().toString();
        Map parameterMap = request.getParameterMap();
        log.info("request start. url:{}, params:{}",url, JsonMapper.obj2String(parameterMap));
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        String url = request.getRequestURI().toString();
        Map parameterMap = request.getParameterMap();
        log.info("request finished. url:{}, params:{}",url,JsonMapper.obj2String(parameterMap));
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String url = request.getRequestURI();
        Map parameterMap = request.getParameterMap();
        log.info("request completed. url:{}, params:{}",url,JsonMapper.obj2String(parameterMap));
    }
}
知识兔

配置Bean被Spring进行管理:

   <mvc:interceptors>
        <bean class="com.webcode.springboot.common.HttpInterceptor"/>
    </mvc:interceptors>
知识兔

测试:

   @ResponseBody
    @RequestMapping("/validate3.json")
    public JsonData valitade3(TestVo vo){
        log.info("validate");
        SysAclModuleMapper moduleMapper = ApplicationContextHelper.popBean(SysAclModuleMapper.class);
        SysAclModule module = moduleMapper.selectByPrimaryKey(1);
        log.info(JsonMapper.obj2String(module));
        BeanValidator.check(vo);
        return JsonData.success("test validate3");
    }
知识兔

任何请求在被进行处理前,会先被preHandle方法进行处理,如果该方法处理通过,就会执行postHandle方法,任何请求执行结束后都会被afterCompletion方法进行处理

记录请求开始到结束所花费的时间

  private static final String START_TIME = "requestStartTime";
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURI().toString();
        Map parameterMap = request.getParameterMap();
        log.info("request start. url:{}, params:{}",url, JsonMapper.obj2String(parameterMap));
        long start = System.currentTimeMillis();
        request.setAttribute(START_TIME, start);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        String url = request.getRequestURI().toString();
        Map parameterMap = request.getParameterMap();
        long start = (Long) request.getAttribute(START_TIME);
        long end = System.currentTimeMillis();
        log.info("request finished. url:{}, cost:{}",url,end - start);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String url = request.getRequestURI().toString();
        long start = (Long) request.getAttribute(START_TIME);
        long end = System.currentTimeMillis();
        log.info("request completed. url:{}, cost:{}",url,end - start);
    }
知识兔

监听器除了可以进行登录时间的监控外,还可进行用户的cookie、session、登录是否过期等进行监听。

计算机