WeillerBlog

惠维乐个人博客


  • 首页

  • 标签

  • 分类

  • 归档

  • 关于

  • 搜索

mybatis-plus代码生成maven插件

发表于 2019-11-15 | 分类于 java | 阅读次数:

为了在项目中快捷方便的代码生成,将mybatis-plus-generator封装为了一个maven的插件mybatis-plus-generator-maven-plugin,在要使用的项目pom文件引入该插件,执行mvn命令,即可直接生成代码到项目中,生成基于mybatis-plus的mapper、service、controller三层结构,包括entity实体类和mapper.xml文件,生成后直接能够满足基本的条件查询和分页查询。下面介绍该插件的使用步骤:

一、下载插件

方式1:CSDN中下载:jar包地址:mybatis-plus-generator-maven-plugin-1.0.0.jar,pom文件地址:pom.xml
方式2:将源代码导入项目工程中,执行mvn intall,源码地址:https://github.com/xiweile/mybatis-plus-generator-maven-plugin

二、插件上传至本地仓库

在下载好的mybatis-plus-generator-maven-plugin-1.0.0.jar和pom.xml文件目录下打开命令行工具,执行下面命令-DpomFile为pom.xml所在目录,-Dfile是jar所在位置,-Dpackaging固定为 maven-plugin,其他参数此处不介绍。

1
mvn install:install-file -DpomFile=pom.xml -Dfile=mybatis-plus-generator-maven-plugin-1.0.0.jar -DgroupId=com.weiller -DartifactId=mybatis-plus-generator-maven-plugin -Dversion=1.0.0 -Dpackaging=maven-plugin

三、在pom中引入插件

在要使用插件的工程pom文件中引入该插件,如下案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- mybatis-plus generator 自动生成代码插件 -->
<plugin>
<groupId>com.weiller</groupId>
<artifactId>mybatis-plus-generator-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generator/mp-code-generator-config.yaml</configurationFile>
</configuration>
</plugin>
<!-- mybatis-plus generator 自动生成代码插件 -->
</plugins>
</build>

注意configurationFile参数为 下一步中配置文件generator-config的位置,该文件类型为yaml。

四、填写配置文件

配置完整案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
globalConfig:
author: weiller
open: false
idType: INPUT
dateType: ONLY_DATE
enableCache: false
activeRecord: false
baseResultMap: true
baseColumnList: true
swagger2: false
fileOverride: true
dataSourceConfig:
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&useSSL=false&characterEncoding=utf8
driverName: com.mysql.jdbc.Driver
username: root
password: xiweile
packageConfig:
parent: com.weiller
moduleName: rest
entity: model
service: service
serviceImpl: service.impl
mapper: dao
xml: mapper
controller: controller
pathInfo:
entity_path: src\main\java\com\weiller\rest\model
service_path: src\main\java\com\weiller\rest\service
service_impl_path: src\main\java\com\weiller\rest\service\impl
mapper_path: src\main\java\com\weiller\rest\dao
xml_path: src\main\resources\com\weiller\rest\mapper
controller_path: src\main\java\com\weiller\rest\controller
strategyConfig:
naming: underline_to_camel
columnNaming: underline_to_camel
entityLombokModel: true
superMapperClass: com.baomidou.mybatisplus.core.mapper.BaseMapper
superServiceClass: com.baomidou.mybatisplus.extension.service.IService
superServiceImplClass: com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
controllerMappingHyphenStyle: true
restControllerStyle: true
tablePrefix:
include:
- t_user

配置项参数解释:https://mp.baomidou.com/config/generator-config.html#基本配置

五、运行maven命令

在命令工具中,进入到要生成项目的根目录(即pom.xml目录),执行以下命令

1
mvn mybatis-plus-generator:generator

如果是使用InterlliJ IDEA工具,使用更加方便,步骤如下图:
在这里插入图片描述

生成结果如下:

在这里插入图片描述

Java设计模式:静态代理、JDK动态代理和cglib动态代理

发表于 2019-09-15 | 分类于 java | 阅读次数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
静态代理、JDK动态代理和cglib动态代理
/**
* 静态代理案例:增强猫(Cat的代理类)
* 利用装饰者模式
* 要求:1.委托类、代理类必须实现共同的接口 2.代理类需要获得委托类的对象的引用
*
* @author weiller
* @version 1.0,2016-11-27 14:11:36
*/
public class StaticProxyDemo implements Jump{

/** 委托类对象 */
private Cat cat;

/** 委托类对象作为参数传入构造方法 */
public StaticProxyDemo(Cat cat){
this.cat = cat;
}

@Override
public void jump() {

// 增强功能
System.out.println("准备起跳...");
// 原始方法调用
cat.jump();
// 增强功能
System.out.println("回落...");
}
}

/**
* JDC动态代理案例
* 要求:目标类必须实现一个接口
*
* @author weiller
* @version 1.0,2016-11-27 08:34:05
*/
public class JDKProxyDemo {

/**
* 获得代理对象
*
* @param t 目标类对象实现的接口
* @return 代理对象
*/
public static<T> T createProxyObject(T t){
// 5.将被代理的对象修饰为final,便于匿名内部类使用
final Object obj = t;
// 2.获取被代理对象的类加载器
ClassLoader loader = t.getClass().getClassLoader();
// 3.获取被代理对象的所有实现接口
Class<?>[] interfaces = t.getClass().getInterfaces();
// 4.创建调用处理对象
InvocationHandler h = new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 7.增强功能
System.out.println("开启事务...");
// 6.原始方法调用
Object ret = method.invoke(obj, args);
// 增强功能
System.out.println("关闭事务...");
return ret;
}
};

// 1.创建代理对象
T proxyObject = (T) Proxy.newProxyInstance(loader, interfaces, h);

return proxyObject;
}
}

/**
* cglib动态代理案例
* cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强。
* 要求:CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
*
* @author weiller
* @version 1.0,2016-11-27 09:28:11
*/
public class CglibPorxyDemo<T> {

public T createProxyObject(Class clazz){
// 1.在内存中创建动态的增强类
Enhancer enhancer = new Enhancer();
// 2.为该增强类设置父类,与目标类形成继承关系。此类最终完成对原始方法的功能,同时对其功能进行加强。
enhancer.setSuperclass(clazz);
// 3.创建回调处理对象,对目标类的方法进行拦截
Callback callback = new MethodInterceptor() {

@Override
/**
* obj:目标类的实例
* method:被拦截的方法对象
* args:调用参数
* methodProxy:代理方法的对象(通过JDK代理得到)
*/
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
// 增强功能
System.out.println("说相声...");
//System.out.println(obj); // 调用obj产生死循环,溢栈。
// 利用代理方法对象调用原始方法。注意:不能调用invoke()方法,否则产生死循环,溢栈。无法进行代理
// 代理是通过多态的形式进行的。
Object ret = methodProxy.invokeSuper(obj, args);
return ret;
}
};

// 4.设置回调
enhancer.setCallback(callback);
// 5.通过字节码技术动态创建该增强类的实例
T newObject = (T) enhancer.create();

return newObject;
}
}

springcloud zuul实践:自定义异常过滤器,统一异常响应格式

发表于 2019-09-13 | 分类于 springcloud | 阅读次数:

在springcloud项目中,网关发生异常时,响应内容并不是我们想要的格式,内容如下:

1
2
3
4
5
6
7
{
"timestamp": 1481674980376,
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.RuntimeException",
"message": "Exist some errors..."
}

上面的json则是内置异常过滤器封装的一种格式。我们现在想要修改她,就需要自定义异常过滤器。

  • 首先继承抽象类ZuulFilter,实现filterType(),filterOrder(),shouldFilter(), run()四个抽象方法。前三个方法均使用父方法逻辑。仅修改run()中部分内容,主体逻辑步骤依然参考SendErrorFilter。
  • 方法run()中重新定义异常响应格式,将自定义的响应体,设置到原有的响应中。
  • 停用内置的默认异常处理器SendErrorFilter,在application.yml中设置zuul.SendErrorFilter.error.disable: true。
  • CustomSendErrorFilter在内置的默认异常处理器失效时生效。设置注解ConditionalOnProperty属性name="zuul.SendErrorFilter.error.disable"。

过滤器详细介绍可参考往期文章:springcloud zuul源码分析:内置过滤器

下面是案例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Component
@ConditionalOnProperty(name="zuul.SendErrorFilter.error.disable")
public class CustomSendErrorFilter extends SendErrorFilter {
@Override
public String filterType() {
return super.filterType();
}

@Override
public int filterOrder() {
return super.filterOrder();
}

@Override
public boolean shouldFilter() {
return super.shouldFilter();
}

@Override
public Object run() {

RequestContext ctx = RequestContext.getCurrentContext();
HttpServletResponse response = ctx.getResponse();
try {
int responseStatusCode = ctx.getResponseStatusCode();
// 此处自定义响应体start
String cumstomBody = "{}";//内容省略...
// 此处自定义响应体end
response.setStatus(ctx.getResponseStatusCode());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getOutputStream().write(cumstomBody.getBytes());
} catch (IOException e) {
ReflectionUtils.rethrowRuntimeException(e);
} finally {
ThreadLocalUtil.remove();
}
return null;
}
}

springcloud zuul实践:自定义前置过滤器进行权限认证

发表于 2019-09-11 | 分类于 springcloud | 阅读次数:

在springcloud项目中,权限认证逻辑一般放在请求路由之前,如果认证通过,则会执行route类型的过滤器,访问微服务获取数据。如果认证未通过,则要设置不进行路由,从而直接响应给客户端。

过滤器详细介绍可参考往期文章:springcloud zuul源码分析:内置过滤器

因此,我们通过自定义一个前置过滤器,来实现权限认证的逻辑。

  • 首先继承抽象类ZuulFilter,实现filterType(),filterOrder(),shouldFilter(), run()四个抽象方法。
  • filterType() 返回 "pre"定义过滤器为前置过滤器。
  • 方法run()中是执行逻辑,RequestContext.getCurrentContext()获取当前上下文实例ctx,如果认证不通过,可设置ctx.setSendZuulResponse(false)阻止请求进行路由。
  • 前置过滤器内也可以对请求参数进行修改或添加。

下面是案例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@Component
public class AccessFilter extends ZuulFilter {

/**
* 返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型:
* pre:可以在请求被路由之前调用
* route:在路由请求时候被调用
* post:在route和error过滤器之后被调用
* error:处理请求时发生错误时被调用
*
* @return
*/
@Override
public String filterType() {
return "pre"; // 前置过滤器
}

@Override
public int filterOrder() {
return 2; // 过滤器的执行顺序,数字越大优先级越低
}

@Override
public boolean shouldFilter() {
return true;// 是否执行该过滤器,此处为true,说明需要过滤
}

@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletResponse response = ctx.getResponse();

// 访问权限认证逻辑
boolean result = this.exeAuth(request);
if(result ==true){//通过校验
// 添加额外的请求参数为可选逻辑...
// 添加额外的请求参数start
Map<String, List<String>> requestQueryParams = ctx.getRequestQueryParams();
List<String> params = new ArrayList<String>();
params.add("otherParamValue");
requestQueryParams.put("otherParamKey",params);
ctx.setRequestQueryParams(requestQueryParams);
// 添加额外的请求参数end

}else{//校验未通过
ctx.setSendZuulResponse(false); // 过滤该请求,不进行路由
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
ctx.setResponseBody(JSON.toJSONString(res)); // 返回前端内容
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
}

return null;
}

/**
* 认证逻辑
*
* @return
* @throws ZuulException
*/
public boolean exeAuth(HttpServletRequest request){
// 详细认证逻辑...
// 省略...
return false;
}
}

springcloud zuul实践:自定义前置过滤器进行权限认证

发表于 2019-09-11 | 分类于 springcloud | 阅读次数:

在springcloud项目中,权限认证逻辑一般放在请求路由之前,如果认证通过,则会执行route类型的过滤器,访问微服务获取数据。如果认证未通过,则要设置不进行路由,从而直接响应给客户端。

过滤器详细介绍可参考往期文章:springcloud zuul源码分析:内置过滤器

因此,我们通过自定义一个前置过滤器,来实现权限认证的逻辑。

  • 首先继承抽象类ZuulFilter,实现filterType(),filterOrder(),shouldFilter(), run()四个抽象方法。
  • filterType() 返回 "pre"定义过滤器为前置过滤器。
  • 方法run()中是执行逻辑,RequestContext.getCurrentContext()获取当前上下文实例ctx,如果认证不通过,可设置ctx.setSendZuulResponse(false)阻止请求进行路由。
  • 前置过滤器内也可以对请求参数进行修改或添加。

下面是案例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@Component
public class AccessFilter extends ZuulFilter {

/**
* 返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型:
* pre:可以在请求被路由之前调用
* route:在路由请求时候被调用
* post:在route和error过滤器之后被调用
* error:处理请求时发生错误时被调用
*
* @return
*/
@Override
public String filterType() {
return "pre"; // 前置过滤器
}

@Override
public int filterOrder() {
return 2; // 过滤器的执行顺序,数字越大优先级越低
}

@Override
public boolean shouldFilter() {
return true;// 是否执行该过滤器,此处为true,说明需要过滤
}

@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletResponse response = ctx.getResponse();

// 访问权限认证逻辑
boolean result = this.exeAuth(request);
if(result ==true){//通过校验
// 添加额外的请求参数为可选逻辑...
// 添加额外的请求参数start
Map<String, List<String>> requestQueryParams = ctx.getRequestQueryParams();
List<String> params = new ArrayList<String>();
params.add("otherParamValue");
requestQueryParams.put("otherParamKey",params);
ctx.setRequestQueryParams(requestQueryParams);
// 添加额外的请求参数end

}else{//校验未通过
ctx.setSendZuulResponse(false); // 过滤该请求,不进行路由
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
ctx.setResponseBody(JSON.toJSONString(res)); // 返回前端内容
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
}

return null;
}

/**
* 认证逻辑
*
* @return
* @throws ZuulException
*/
public boolean exeAuth(HttpServletRequest request){
// 详细认证逻辑...
// 省略...
return false;
}
}

springcloud zuul源码分析:内置过滤器

发表于 2019-09-10 | 分类于 springcloud | 阅读次数:

springcloud项目中我们常常使用zuul作为网关,用它做一些路由分发、权限校验、统一异常处理、日志收集等等工作。而实现这些功能的重要组件就是它的过滤器ZuulFilter,本篇文章介绍zuul中内置过滤器。

1.ZuulFilter介绍

我们自定义的每个过滤器都需要继承ZuulFilter才能被加载生效。它有四个重要的抽象方法需要重写。

1
2
3
4
5
6
7
public abstract String filterType();

public abstract int filterOrder();

boolean shouldFilter();

Object run();

四个方法介绍:

  • filterType 返回值标识过滤器类型。
    • pre 在请求被路由之前调用。
    • routing 路由时被调用。
    • post 在routing或error过滤器之后被调用。
    • error 在请求发送异常时被调用。
  • filterOrder 返回int值标识过滤器执行顺序,数值越小优先级越高。
  • shouldFilter 返回boolean类型确定是否执行该过滤器。

2.内置过滤器介绍

1)按filterType分类图示

zuul核心过滤器按filterType分类

2)各过滤器功能详细介绍

pre过滤器
  • ServletDetectionFilter:它的执行顺序为-3,是最先被执行的过滤器。该过滤器总是会被执行,主要用来检测当前请求是通过Spring的DispatcherServlet处理运行,还是通过ZuulServlet来处理运行的。它的检测结果会以布尔类型保存在当前请求上下文的isDispatcherServletRequest参数中,这样在后续的过滤器中,我们就可以通过RequestUtils.isDispatcherServletRequest()和RequestUtils.isZuulServletRequest()方法判断它以实现做不同的处理。一般情况下,发送到API网关的外部请求都会被Spring的DispatcherServlet处理,除了通过/zuul/路径访问的请求会绕过DispatcherServlet,被ZuulServlet处理,主要用来应对处理大文件上传的情况。另外,对于ZuulServlet的访问路径/zuul/,我们可以通过zuul.servletPath参数来进行修改。
  • Servlet30WrapperFilter:它的执行顺序为-2,是第二个执行的过滤器。目前的实现会对所有请求生效,主要为了将原始的HttpServletRequest包装成Servlet30RequestWrapper对象。
  • FormBodyWrapperFilter:它的执行顺序为-1,是第三个执行的过滤器。该过滤器仅对两种类请求生效,第一类是Content-Type为application/x-www-form-urlencoded的请求,第二类是Content-Type为multipart/form-data并且是由Spring的DispatcherServlet处理的请求(用到了ServletDetectionFilter的处理结果)。而该过滤器的主要目的是将符合要求的请求体包装成FormBodyRequestWrapper对象。
  • DebugFilter:它的执行顺序为1,是第四个执行的过滤器。该过滤器会根据配置参数zuul.debug.request和请求中的debug参数来决定是否执行过滤器中的操作。而它的具体操作内容则是将当前的请求上下文中的debugRouting和debugRequest参数设置为true。由于在同一个请求的不同生命周期中,都可以访问到这两个值,所以我们在后续的各个过滤器中可以利用这两值来定义一些debug信息,这样当线上环境出现问题的时候,可以通过请求参数的方式来激活这些debug信息以帮助分析问题。另外,对于请求参数中的debug参数,我们也可以通过zuul.debug.parameter来进行自定义。
  • PreDecorationFilter:它的执行顺序为5,是pre阶段最后被执行的过滤器。该过滤器会判断当前请求上下文中是否存在forward.to和serviceId参数,如果都不存在,那么它就会执行具体过滤器的操作(如果有一个存在的话,说明当前请求已经被处理过了,因为这两个信息就是根据当前请求的路由信息加载进来的)。而它的具体操作内容就是为当前请求做一些预处理,比如:进行路由规则的匹配、在请求上下文中设置该请求的基本信息以及将路由匹配结果等一些设置信息等,这些信息将是后续过滤器进行处理的重要依据,我们可以通过RequestContext.getCurrentContext()来访问这些信息。另外,我们还可以在该实现中找到一些对HTTP头请求进行处理的逻辑,其中包含了一些耳熟能详的头域,比如:X-Forwarded-Host、X-Forwarded-Port。另外,对于这些头域的记录是通过zuul.addProxyHeaders参数进行控制的,而这个参数默认值为true,所以Zuul在请求跳转时默认地会为请求增加X-Forwarded-*头域,包括:X-Forwarded-Host、X-Forwarded-Port、X-Forwarded-For、X-Forwarded-Prefix、X-Forwarded-Proto。我们也可以通过设置zuul.addProxyHeaders=false关闭对这些头域的添加动作。
route过滤器
  • RibbonRoutingFilter:它的执行顺序为10,是route阶段第一个执行的过滤器。该过滤器只对请求上下文中存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效。而该过滤器的执行逻辑就是面向服务路由的核心,它通过使用Ribbon和Hystrix来向服务实例发起请求,并将服务实例的请求结果返回。
  • SimpleHostRoutingFilter:它的执行顺序为100,是route阶段第二个执行的过滤器。该过滤器只对请求上下文中存在routeHost参数的请求进行处理,即只对通过url配置路由规则的请求生效。而该过滤器的执行逻辑就是直接向routeHost参数的物理地址发起请求,从源码中我们可以知道该请求是直接通过httpclient包实现的,而没有使用Hystrix命令进行包装,所以这类请求并没有线程隔离和断路器的保护。
  • SendForwardFilter:它的执行顺序为500,是route阶段第三个执行的过滤器。该过滤器只对请求上下文中存在forward.to参数的请求进行处理,即用来处理路由规则中的forward本地跳转配置。
    post过滤器
  • SendErrorFilter:它的执行顺序为0,是post阶段第一个执行的过滤器。该过滤器仅在请求上下文中包含error.status_code参数(由之前执行的过滤器设置的错误编码)并且还没有被该过滤器处理过的时候执行。而该过滤器的具体逻辑就是利用请求上下文中的错误信息来组织成一个forward到API网关/error错误端点的请求来产生错误响应。
  • SendResponseFilter:它的执行顺序为1000,是post阶段最后执行的过滤器。该过滤器会检查请求上下文中是否包含请求响应相关的头信息、响应数据流或是响应体,只有在包含它们其中一个的时候就会执行处理逻辑。而该过滤器的处理逻辑就是利用请求上下文的响应信息来组织需要发送回客户端的响应内容。

网关过滤器执行逻辑

源码逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class ZuulServlet extends HttpServlet {
private ZuulRunner zuulRunner;

public ZuulServlet() {
}

public void init(ServletConfig config) throws ServletException {
super.init(config);
String bufferReqsStr = config.getInitParameter("buffer-requests");
boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true");
this.zuulRunner = new ZuulRunner(bufferReqs);
}

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
// 省略....
try {
this.preRoute();
} catch (ZuulException var13) {
this.error(var13);
this.postRoute();
return;
}
try {
this.route();
} catch (ZuulException var12) {
this.error(var12);
this.postRoute();
return;
}
try {
this.postRoute();
} catch (ZuulException var11) {
this.error(var11);
}
// 省略....
}
void postRoute() throws ZuulException {
this.zuulRunner.postRoute();
}

void route() throws ZuulException {
this.zuulRunner.route();
}

void preRoute() throws ZuulException {
this.zuulRunner.preRoute();
}

void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
this.zuulRunner.init(servletRequest, servletResponse);
}

void error(ZuulException e) {
RequestContext.getCurrentContext().setThrowable(e);
this.zuulRunner.error();
}
}

流程分析:
请求经ZuulServlet 进入内部service处理方法,依次经过preRoute,route,postRoute三组过滤器,如果任何一个过滤器中发生异常都会转发到error过滤器,而error最终也会再次将结果转发postRoute,有postRoute负责将结果给返回客户端。

执行流程图如下:
在这里插入图片描述

在线HTML页面导出pdf或图片,支持模拟跳过登陆验证

发表于 2019-09-10 | 分类于 java | 阅读次数:

项目经常用到把当前页面导出为pdf,或者生成某个页面或部分的快照。如果是简单的页面快照,不没有太多的渲染,可以用html2canvas.min.js导出图片或者html2pdf.bundle.min.js导出为pdf,仅仅在前端就可以完成。但是复杂的页面,以上的导出效果不好,质量也不高。因此我采用PhantomJS,用后台生成pdf、png等进行导出。

1.PhantomJS简介

PhantomJS是一个基于webkit的JavaScript API。它使用QtWebKit作为它核心浏览器的功能,使用webkit来编译解释执行JavaScript代码。任何你可以在基于webkit浏览器做的事情,它都能做到。它不仅是个隐形的浏览器,提供了诸如CSS选择器、支持Web标准、DOM操作、JSON、HTML5、Canvas、SVG等,同时也提供了处理文件I/O的操作,从而使你可以向操作系统读写文件等。PhantomJS的用处可谓非常广泛,诸如网络监测、网页截屏、无需浏览器的 Web 测试、页面访问自动化等。

2.下载及安装

官方下载地址:http://phantomjs.org/download.html。目前官方支持三种操作系统,包括windows\Mac OS\Linux这三大主流的环境。根据运行环境选择要下载的包,下面以Windows7为例,我将phantomjs文件放置到D盘根目录下,里面的内容如下图。

image

3.如何使用

1) 准备一个配置的js文件

在D盘phantomjs目录下新建一个html2pdf.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
* phantomJs 脚本 html转图片
*/
var page = require('webpage').create(),
system = require('system'),
address, output, size,JSESSIONID,domain;

if (system.args.length < 3 || system.args.length > 5) {
phantom.exit(1);
} else {

address = system.args[1];
output = system.args[2];
if(system.args.length>3){
JSESSIONID = system.args[3];
domain = system.args[4];

// 添加cookie
phantom.addCookie({
"name":"JSESSIONID",
"value":JSESSIONID,
"domain":domain,
"path":"/",
"httponly":false,
"secure":false,
"expires": "Fri, 01 Jan 2038 00:00:00 GMT"
});
}
//定义浏览器宽高
page.viewportSize = {
width : 2048,
height : 768
};

// 纸张尺寸
page.paperSize = {
format: 'A3',
orientation: 'landscape',//横向
margin: {
left:"2cm",
right:"2cm",
top:"1cm",
bottom:"1cm"
}

};
page.open(address, function(status) {
console.log('Status: ' + status);
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit(1);
} else {
var bb = page.evaluate(function() {
//var child=document.getElementById("printBt");
//child.parentNode.removeChild(child);//此外可以写一些对页面操作的代码,比如词句代码是删除页面上的打印按钮
return document.getElementsByTagName('html')[0].getBoundingClientRect();//返回内容就是要导出的范围(此处是HTML整页)
});
// 截取范围
page.clipRect = {
top : bb.top,
left : bb.left,
width : bb.width,
height : bb.height
};

// 等待页面渲染
window.setTimeout(function() {
page.render(output, {format: 'pdf', quality: '100'});//这里配置导出的格式pdf,png等,quality为导出质量
page.close();
console.log('渲染成功...');
phantom.exit();
}, 2000);
}

});
}
address = system.args[1];//传入的URL地址
output = system.args[2];//保存的图片路径

2)测试命令

例如:phantomjs文件夹放于D盘根目录,在phantomjs/bin 目录下(也可以配置环境变量)打开控制台。以我的CSDN博客页面https://blog.csdn.net/xiweiller导出为PDF为例。输入如下命令:

1
phantomjs.exe D:/phantomjs/html2pdf.js https://blog.csdn.net/xiweiller D:/phantomjs/blog.pdf

其中 phantomjs.exe是执行命令文件, D:/phantomjs/screenshot.js是执行的配置js文件,https://blog.csdn.net/xiweiller是目标HTML,D:/phantomjs/blog.pdf是导出PDF的路径。

3) java中的使用,springboot项目为例

  • application.yml中添加配置,配置项为phantomjs执行文件路径

    1
    2
    3
    4
    5
    htmltopdf:
    path:
    windows: D:/phantomjs/bin/phantomjs.exe
    linux: /root/soft/phantomjs-2.1.1-linux-x86_64/bin/phantomjs
    mac:
  • Html转Pdf工具类

    参数:

    - htmlPath 目标网页访问地址
    - pdfPath 导出pdf存放目录
    - JSESSIONID  登陆认证时需要传递,用于cookie携带
    - domain cookie中获取
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
* Html转Pdf工具类
*
*/
@Component
public class HtmlToPdf {

@Value("${htmltopdf.path.windows}")
private String WINDOWS_WKHTMLTOPDF_PATH;
@Value("${htmltopdf.path.mac}")
private String MAC_WKHTMLTOPDF_PATH;
@Value("${htmltopdf.path.linux}")
private String LINUX_WKHTMLTOPDF_PATH;
private static Logger log = LoggerFactory.getLogger(HtmlToPdf.class);

/**
* html转pdf核心方法
* @param htmlPath html路径(硬盘和网盘路径都支持)
* @param pdfPath pdf存储路径
* @return 转换成功:true,失败:false
*/
public boolean convert(String htmlPath, String pdfPath,String JSESSIONID,String domain) {
File file = new File(pdfPath);
File parent = file.getParentFile();
if (!parent.exists()) {// 如果pdf保存路径不存在,则创建路径
parent.mkdirs();
}
String toolDir = null;// 转换工具在系统中的路径
if (System.getProperty("os.name").indexOf("Windows") != -1) {//windows 系统
toolDir = WINDOWS_WKHTMLTOPDF_PATH;
}else if(System.getProperty("os.name").indexOf("Mac OS X") != -1){//mac 系统
toolDir = MAC_WKHTMLTOPDF_PATH;
}else if(System.getProperty("os.name").indexOf("Linux") != -1){//linux 系统
toolDir = LINUX_WKHTMLTOPDF_PATH;
}
boolean result = true;//pdf创建成功标识,默认成功
String BLANK = " ";//空格
Process process = null;//图片输出路径
StringBuffer sbf = null;
InputStream is = null;

StringBuilder cmd = new StringBuilder();
cmd.append(toolDir)
.append(BLANK)
.append(new File(toolDir).getParent()).append(File.separator).append("html2pdf.js")
.append(BLANK)
.append(htmlPath)
.append(BLANK)
.append(pdfPath);
if(JSESSIONID!=null){//添加cookie值
cmd.append(BLANK).append(JSESSIONID);
cmd.append(BLANK).append(domain);
}
try {
log.info("报表准备导出 " );
log.info("导出命令:{}",cmd.toString() );
process = Runtime.getRuntime().exec(cmd.toString());// 输出路径

is = process.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
sbf = new StringBuffer();
String tmp = "";

while ((tmp = br.readLine()) != null) {
sbf.append(tmp);
}
log.info("报表导出完成 " );
} catch (IOException e) {
result = false;
log.error("报表导出失败!", e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
is = null;
}
}
return result;
}
}
  • 转换工具的调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public void test( HttpServletRequest request ){
    // 注意不同部署环境,路径不同。
    String htmlUrl = "D:/phantomjs/html2pdf.js";
    String pdfPath = "D:/phantomjs/export.pdf"
    String JSESSIONID = "";
    String domain = request.getServerName();
    Cookie[] cookies = request.getCookies();
    for(Cookie cookie :cookies){
    if("JSESSIONID".equals(cookie.getName())){
    JSESSIONID = cookie.getValue();
    }
    }
    //将html转换为pdf文件
    boolean isno =htmlToPdf.convert(htmlUrl,pdfPath,JSESSIONID,domain);
    }

4) 导出效果图

页面导出效果

4. 注意事项

​ 在实际项目使用时,我们必须保证页面异步加载的速度比较快,否则会出现页面渲染不完整的问题。因此在项目中,被导出的目标页面最好能做到快速响应,尽量减少异步请求加载,或者保证异步接口快速响应。

简单的HTML快照生成,导出pdf或图片

发表于 2019-09-01 | 分类于 javascript | 阅读次数:

一、利用 html2canvas实现HTML页面截图

官方网址: https://html2canvas.hertzen.com/

GitHub:https://github.com/niklasvh/html2canvas

1.HTML页面引入 html2canvas.min.js

2.定义一个截图的触发按钮

1
2
3
4
<button onclick="exprotImg();">导出图片</button> 
<a href="" download="canvas.png" id="save_href" style="display:none">
<img src="" id="save_img"/>
</a>

3.js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function exprotImg() {
// 要截图的元素
var element = document.getElementsByTagName('html')[0];
// 获取元素的大小及其相对于视口的位置等参数
var dd= element.getBoundingClientRect();

var opts = {
scale: 2, // 添加的scale 参数
// logging: true, //日志开关,便于查看html2canvas的内部执行流程
width: dd.width, //dom 原始宽度
height: dd.height,
useCORS: true, // 【重要】开启跨域配置
allowTaint:true
};

html2canvas(element,opts).then(function(canvas) {
// 模拟的下载按钮
var svaeHref = document.getElementById("save_href");
var img = document.getElementById("save_img");
var tempSrc = canvas.toDataURL("image/png");
svaeHref.href=tempSrc;
img.src=tempSrc;
$(img).click();
});
}

二、利用 html2pdf 实现页面导出pdf

GitHub :https://github.com/eKoopmans/html2pdf

1.页面导入 html2pdf.bundle.min.js

2.导出按钮

1
<button onclick="exprotPdf();">导出pdf</button>

3.js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function exprotPdf() {
// 要截图的元素
var element = document.getElementsByTagName('html')[0];
// 获取元素的大小及其相对于视口的位置等参数
var opt = {
margin: 1,
filename: 'myfile.pdf',
image: { type: 'png', quality: 0.98 },
html2canvas: {
scale: 2,
useCORS:true, // 【重要】开启跨域配置
allowTaint:true,
width:dd.width,
height:dd.height
},
jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
};

html2pdf().from(element).set(opt).save();

}

利用自定义注解,统计方法执行时间

发表于 2019-08-15 | 分类于 java | 阅读次数:

项目中需要统计某些方法的执行时间,最简易的方式是方法执行前记录时间戳startTime,在方法结束前,用时间戳endTime-startTime得出该方法耗时。
但是为了避免无代码侵入并实现通用,于是定义一个注解,如果要统计哪个方法,只需在方法上写上注解即可,通过注解可以获取到方法的参数、方法名、返回值等等信息。

下面是一个简单的时间统计实现:

1.定义一个注解TimeConsume

该注解有一个默认的value属性,value值为方法名或自定义的描述

1
2
3
4
5
6
7
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TimeConsume {

String value() default "方法";
}

2.使用Aspect定义该注解的切面TimeConsumeAspect

定义好注解后,需要对该注解使用的类进行监听,利用Spring框架Aspect实现切面,定义环绕通知,获取到方法的参数、方法名等信息,便于统计所需。对执行方法进行异常捕获,在finally代码块中实现时间统计逻辑,避免方法异常无法统计。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Slf4j
@Component
@Aspect
public class TimeConsumeAspect {

/**
* 切点定义为注解@annotation(注解类路径)
*/
@Pointcut("@annotation(com.weiller.demo.common.annotation.TimeConsume)")
public void consume(){
}

@Around("consume()")
public <T> T around(ProceedingJoinPoint pjp) throws Throwable {
Long startTime = System.currentTimeMillis();

Object[] args = pjp.getArgs();
T result;
Method methodClass;
try {

result = (T)pjp.proceed(args);//执行方法

}finally {
long endTime = System.currentTimeMillis();
Signature signature = pjp.getSignature();
String methodName = signature.getName();
Class<?> targetClass = pjp.getTarget().getClass();
Class[] parameterTypes = ((MethodSignature) pjp.getSignature()).getParameterTypes();
methodClass = targetClass.getMethod(methodName, parameterTypes);
Annotation[] annotations = methodClass.getAnnotations();
for (Annotation annotation : annotations){
Class<? extends Annotation> aClass = annotation.annotationType();
String simpleName = aClass.getSimpleName();
if("TimeConsume".equals(simpleName)){
TimeConsume timeConsume = (TimeConsume) annotation;
String value = timeConsume.value();
log.info(value+"[{}] 执行耗时:{}ms",methodName,endTime-startTime);
break;
}
}

}

return result;
}
}

3.测试目标方法中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
@RequestMapping("/test")
public class testController {

@TimeConsume("测试")
@GetMapping("info")
public Object testInfo(){
Object parse = JSONObject.parse("{\n" +
"\t\t\"requestId\":\"\",\n" +
"\t\t\"appId\":\"\",\n" +
"\t\t\"nonce\":\"\",\n" +
"\t\t\"timestamp\":12345676543,\n" +
"\t\t\"signature\":\"\",\n" +
"\t\t\"sjgsd\":\"61000\",\n" +
"\t\t\"starTime\":12345676543\n" +
"\t}");
try {
Thread.sleep(new Random().nextInt(100));//随机时间休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
return parse ;
}
}

在线sql编辑查询工具sql-editor

发表于 2019-07-19 | 分类于 前端 | 阅读次数:

sql-editor是基于CodeMirror的一个在线sql编辑工具,模仿navicat工具,开发的一个简易版。

目前只提供前端代码,由easy-mock提供json测试数据。

Github地址: https://github.com/xiweile/sql-editor

功能更新

  • 左侧提供数据库表字段树形结构,可拖拽到sql编辑框。(2019-7-19新增)
  • 编辑区和数据展示区上下拖动更改大小。(2019-7-19新增)
  • 执行与中断功能。(2019-7-19新增)
  • 自定义外部接口获取sql关键词、函数、表字段等,用于sql编辑时提示补全,键盘录入后自动提示,上下键选择,tab键或点击选中项自动补全。(2019-7-16)
  • sql格式化。(2019-7-16)

一些截图:

image

img

img

12
惠维乐

惠维乐

IT技术文章/JAVA学习分享/个人随笔

12 日志
5 分类
GitHub CSDN E-Mail
© 2019 惠维乐
本站访客数 人次 总访问量 次