权限管理开发-准备
一、核心类生成 - 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、登录是否过期等进行监听。