CrazyAirhead

疯狂的傻瓜,傻瓜也疯狂——傻方能执著,疯狂才专注!

0%

Web 开发 —— 进阶

说明

在基础篇当中,我们讲究了 Solon 对 MVC 的支持和提供的基础的Web 能力。在这个章节我们会讲解 Solon 提供了哪些能力可以供我们进行控制。

首先我们来看看 Solon Web 的请求过程,理解 Solon 的基础概念,这样方便我们后续的定制。

请求过程

img

从图中,我们可以看到在Web 的请求过程中,会经过几个环节:Filter(全局过滤器)->

RouterInterCeptor(路由拦截器)-> Handler(处理器)-> Interceptor(拦截器)。

Context

Solon 采用 Handler + Context 架构,Context 是请求的上下文。 在 Context 中可以获取请求和响应相关的数据,包括请求相关的对象与接口,会话相关的对象与接口,响应相关的对象与接口等。Solon 提供了多种方式获取Context,也为Context 其他了一些常用的接口,更详细的内容可以查看 https://solon.noear.org/article/216

Filter

过滤器,可以对所有请求(无论动态还是静态的请求)进行全局的控制。通常用于全局的 Web 请求异常处理,全局的性能记录,全局的响应状态调整,全局的上下文日志记录,全局的链路跟踪等等,当然也可以作用在局部上。

RouterInterceptor

路由拦截器,可以对动态请求进行全局的控制,功能基本与 Filter 一致。主要的区别是:1. 只能对动态请求(路由器范围)进行拦截;2. 可以修改参数和返回结果。

Interceptor

拦截器,拦截器使用切面(AOP)的方式实现,通过定义注解、设置切点、注册拦截器,Solon 将对方法进行拦截,从而增加拦截器方法的执行。拦截器可以用于实现缓存、事务等等功能。

Action

实际的请求方法,注册到路由器的方法(通常是@Mapping 函数,可以通过 Controller 的方式定义,也可以通过 Handler 的方式定义,等等)。Action 本质上是 Handler 和 Class Method 的结合,可以请求方法进行局部的控制。

示例 demo-web02

在 IDEA 中通过 File->New->Modules... 可以创建新的模块 demo-web02,基础代码和配置可以从demo-web01中拷贝过来。

这里我们继续补充demo-web01 ,增加定制化的处理。

  • 增加简单的鉴权用于演示的Filter。
  • 增加RouterInterceptor,用于演示Filter和RouterInterceptor的区别。
  • 增加日志审计的功能用于演示 Interceptor 拦截器。

全局过滤器

使用全局的Filter做简单的 token 验证。这里需要指定Component的注解,也就是统一有Solon 来管理。如果有多个过滤器是可以指定Component的 index 参数,参数越小,越外层,可以理解为优先级越高。

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
package com.example.demo.web.common.filter;

import com.jfinal.kit.Kv;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.text.StrUtil;
import org.noear.snack.ONode;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;

/**
* @author airhead
*/
@Slf4j
@Component
public class GlobalFilter implements Filter {
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
String path = ctx.path();
log.info("1. GlobalFilter, path: {}", path);
// 简单逻辑,非静态的路径都需要token
if (!path.contains(".")) {
String token = ctx.param("token");
if (StrUtil.isBlank(token)) {
ctx.outputAsJson(ONode.stringify(Kv.of("state", false)));
return;
}
}

chain.doFilter(ctx);
}
}

路由拦截器

这边只是为了对比和Filter的不同,如果是静态资源请求时,不会进入路由拦截器,也就不会打印对应的日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.example.demo.web.common.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Handler;
import org.noear.solon.core.route.RouterInterceptor;
import org.noear.solon.core.route.RouterInterceptorChain;

/**
* @author airhead
*/
@Slf4j
@Component
public class DemoRouteInterceptor implements RouterInterceptor {
@Override
public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain)
throws Throwable {
String path = ctx.path();
log.info("2. DemoRouteInterceptor, path: {}", path);

chain.doIntercept(ctx, mainHandler);
}
}

局部过滤器

局部过滤器可以通过 Addition 注解直接使用,可以通过注解继承的方式使用。

WhitelistFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.demo.web.common.filter;

import lombok.extern.slf4j.Slf4j;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;

/**
* @author airhead
*/
@Slf4j
public class WhitelistFilter implements Filter {
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
String path = ctx.path();
log.info("3. WhitelistFilter, path: {}", path);
chain.doFilter(ctx);
}
}

Whitelist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.demo.web.common.annotation;

import com.example.demo.web.common.filter.WhitelistFilter;
import java.lang.annotation.*;
import org.noear.solon.annotation.Addition;

/**
* @author airhead
*/
@Addition(WhitelistFilter.class)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Whitelist {}

拦截器

拦截器可以通过 Around 注解直接使用,可以通过注解继承的方式使用。

LogInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.demo.web.common.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.noear.solon.core.aspect.Interceptor;
import org.noear.solon.core.aspect.Invocation;
import org.noear.solon.core.handle.Context;

/**
* @author airhead
*/
@Slf4j
public class LogInterceptor implements Interceptor {
@Override
public Object doIntercept(Invocation inv) throws Throwable {
Context context = Context.current();
String path = context.path();
log.info("4. LogInterceptor, path: {}", path);

return inv.invoke();
}
}

OperateLog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.demo.web.common.annotation;

import com.example.demo.web.common.interceptor.LogInterceptor;
import java.lang.annotation.*;
import org.noear.solon.annotation.Around;

/**
* @author airhead
*/
@Around(LogInterceptor.class)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {}

Controller的调整

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
package com.example.demo.web.manage.controller;

import com.example.demo.web.common.annotation.OperateLog;
import com.example.demo.web.common.annotation.Whitelist;
import com.example.demo.web.common.filter.WhitelistFilter;
import com.example.demo.web.common.interceptor.LogInterceptor;
import com.example.demo.web.manage.dto.DeptDto;
import com.example.demo.web.manage.service.DeptService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import org.noear.solon.annotation.*;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.ModelAndView;
import org.noear.solon.core.handle.UploadedFile;

@Controller
@Mapping("/dept")
@Api("部门管理")
public class DeptController {
@Inject private DeptService service;

@Mapping("/index")
@Get
@ApiOperation("浏览部门")
@OperateLog
@Whitelist
public ModelAndView index() {
ModelAndView mv = new ModelAndView();
mv.put("depts", service.list());
mv.view("dept.shtm");

return mv;
}

@Mapping(value = "list")
@Get
@ApiOperation("获取列表")
@Around(LogInterceptor.class)
@Addition(WhitelistFilter.class)
public List<DeptDto> list() {
return service.list();
}
}

验证

  • 请求 /dept/index,不带token得情况,演示全局的过滤器效果

img

  • 请求 /dept/list?tokent=1,带token得情况,验证请求过程,使用的是 Addition 和 Around 的方式添加局部过滤器和拦截器。

img

img

  • 请求 /dept/index?tokent=1,带token得情况,验证请求过程,使用注解继承的方式添加局部过滤器和拦截器。

img

img

  • 请求 /img/wechat.png,验证静态请求不会通过RouterInterceptor。如果静态资源不存在的情况,依然会继续查找动态路由,此时会进入RouterInterceptor。

img

小结

本章节通过示意图的方式简单描述Solon web的请求过程,并对其中的重要概念做了讲解,通过实例的方式正式了对过滤器,路由拦截器和拦截器的定制。

官网对这些内容做了多篇文章的介绍,可以通过官网进一步的了解这些概念和使用方法。

欢迎关注我的其它发布渠道