Skip to content

Latest commit

 

History

History
380 lines (295 loc) · 17.3 KB

File metadata and controls

380 lines (295 loc) · 17.3 KB

4.1 概述

Web MVC(Model-View-Controller)是一种设计模式,用于将 Web 应用程序的用户界面逻辑与数据处理逻辑分离。它是一种架构模式,主要用于构建 Web 应用程序。FIT 提供了高度灵活和功能强大的 Web MVC 后端服务,非常适用于 Web 应用程序开发。如果你还没有开发过 Web 服务,请参考快速入门指南-构建一个 Web 服务,可以帮助你快速入门。

MVC的主要组成部分包括:

  1. Model(模型):模型代表应用程序的数据和业务逻辑。它是应用程序的“内容”,包括数据的结构和行为。
  2. View(视图):视图是用户界面的表示。它是用户看到的内容,通常是 HTML 页面,但也可以是其他格式,如 PDF 或 JSON。视图从模型中获取数据,并将其显示给用户。
  3. Controller(控制器):控制器是连接模型和视图的中间层。它接收用户的输入(通常是 HTTP 请求),处理业务逻辑,并将结果传递给视图。控制器从模型中获取数据,并将其传递给视图进行显示。

通过这种分离,MVC 模式使得开发者可以专注于单一的任务,而不必混合处理用户界面和数据处理逻辑。此外,这种模式还提高了代码的可维护性和可测试性。

MVC使用准备:在pom文件中添加依赖fit-http-classic ,使得应用程序具备 Web 服务的基本能力。

<dependency>
    <groupId>org.fitframework.service</groupId>
    <artifactId>fit-http-classic</artifactId>
    <version>${fit.version}</version>
</dependency>

4.2 注解式 Controller

在Web应用后端开发中,我们主要通过实现控制器(Controller)来负责处理用户请求、调度业务逻辑以及返回适当的响应,接下来将给出一个简单的 Controller 示例,用于后续介绍。

package modelengine.fit.demo1.controller;

import modelengine.fit.demo1.domain.User;
import modelengine.fit.http.annotation.GetMapping;
import modelengine.fit.http.annotation.RequestParam;
import modelengine.fitframework.annotation.Component;

/**
 * 表示用户资源的控制器。
 */
@Component
public class UserController {
    @GetMapping(path = "/user")
    public User getUser(@RequestParam("name") String name, @RequestParam("age") String age) {
        return new User(name, age);
    }
}

4.2.1 声明

控制器作为一个组件,通过在类上标识 @Component 注解,FIT框架会在启动时自动扫描并注册为Bean交由容器管理。

4.2.2 请求映射(Request Mapping)

示例中 HTTP 的调用是 Get 方法,所以在对应的调用方法上需要打上 @GetMapping 注解进行标识,并通过path属性确定 HTTP 的访问路径。我们也可以使用@RequestMapping注解,再加上method = HttpRequestMethod.GET属性,可以达到相同的效果。其余的指示请求方法的注解如下所示:

注解 解释 Spring 的相应注解
@RequestMapping 表示 REST 接口的请求映射。该注解可以在类和方法上使用,注解在方法上的 @RequestMapping 路径会继承注解在类上的路径。 @RequestMapping
@GetMapping 表示 REST 接口的 HttpRequestMethod.GET 请求映射。 @GetMapping
@DeleteMapping 表示 REST 接口的 HttpRequestMethod.DELETE 请求映射。 @DeleteMapping
@PatchMapping 表示 REST 接口的 HttpRequestMethod.PATCH 请求映射。 @PatchMapping
@PostMapping 表示 REST 接口的 HttpRequestMethod.POST 请求映射。 @PostMapping
@PutMapping 表示 REST 接口的 HttpRequestMethod.PUT 请求映射。 @PutMapping

4.2.3 处理器方法(Handler Method)

FIT 框架接受 Http 请求,获取请求的相关信息,并自动查找相应的方法处理器实现方法的调用,并返回正确的结果。这个过程中 FIT 将自动进行如动态路由、负载均衡、请求解析、请求过滤、方法查找、方法调用、异常处理、序列化与序列化等操作,而开发者只需关注如何编写后端代码,无需关注内部实现。

对于HTTP请求,FIT 框架提供了一系列注解用于提取HTTP请求中的信息或对处理器方法进行设定,方便开发者将HTTP请求的参数用于处理器方法中,如下所示:

注解 解释
@PathVariable 表示 REST 接口的请求映射中的路径参数。
@ExceptionHandler 表示 REST 请求的异常处理器
@RequestBean 表示 REST 接口的请求映射中的 Http 值聚合到方法参数上。
@RequestBody 表示 REST 接口的请求映射中的消息体参数。
@RequestCookie 表示 REST 接口的请求映射中的 Cookie 参数。
@RequestForm 表示 REST 接口的请求映射中的表单参数。
@RequestHeader 表示 REST 接口的请求映射中的消息头参数。
@RequestParam 表示 REST 接口的请求映射中的查询参数或表单参数。
@RequestQuery 表示 REST 接口的请求映射中的查询参数。
@ResponseBody 表示 REST 接口的响应映射中的消息体返回值。
@ResponseStatus 表示 REST 接口的响应映射中的状态码。

部分接口的使用示例如下:

//获取请求中的路径参数,返回响应状态码
@GetMapping(path = "/user/{name}")
@ResponseStatus(HttpResponseStatus.OK)
public String returnName(@PathVariable("name") String name){
    return name;
}
//获取Post请求体及表单参数
@PostMapping(path = "/example")
public String handleExample(@RequestBody String body, @RequestParam(value = "param") String param) {
    return StringUtils.format("Received body: {0}, param: {1}", body, param);
}

当参数所需信息来自HTTP请求的不同部分时,可以使用@RequestBean注解进行传递,使用示例如下:

//定义类,对属性添加注解指示数据来自请求的哪一部分
public class Person {
    @RequestHeader("name")
    @Property(description = "表示姓名")
    private String name;

    @RequestCookie("phoneNumber")
    @Property(description = "表示电话列表")
    private String phoneNumber;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhoneNumber() {
        return this.phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
}

//定义处理器方法,获取HTTP请求中各项参数注入对象中
@PostMapping("/person")
public String personExample(@RequestBean @Property(description = "表示人员信息") Person person) {
    return "Received: " + person;
}

4.2.4 Error响应

FIT 提供@ExceptionHandler注解来指定处理的异常类型,示例如下:

@ExceptionHandler(Exception.class)
public Map<String, Object> handleException(Exception cause) {
    return MapBuilder.<String, Object>get().put("error", cause.getMessage()).build();
}

开发者可以根据需要添加更多的异常处理方法,以处理不同类型的异常,例如为404错误、401错误等添加特定的处理方法。

4.3 过滤器(Filter)

FIT 提供了HttpServerFilter接口,开发者可通过实现该接口,自定义过滤行为,实现个性化的处理。例如在一个 Web 服务器中,可能需要对请求进行一些处理,如身份验证、日志记录、请求限流等,这时就可以使用HttpServerFilter

4.3.1 示例

@Component
public class SampleFilter implements HttpServerFilter {
    /** 表示过滤器的名字。*/
    @Override
    public String name() {
        return "sampleFilter";
    }

    /** 表示过滤器的优先级。*/
    @Override
    public int priority() {
        return HIGHEST;
    }

    /** 表示过滤器的过滤路径样式列表。*/
    @Override
    public List<String> matchPatterns() {
        return Collections.singletonList("/url/**");
    }

    /** 表示过滤器的不匹配的过滤路径样式列表。*/
    @Override
    public List<String> mismatchPatterns() {
        return Collections.singletonList("/url/h*");
    }

    /** 表示过滤器的自定义过滤逻辑。*/
    @Override
    public void doFilter(HttpClassicServerRequest request, HttpClassicServerResponse response,
                         HttpServerFilterChain chain) throws DoHttpServerFilterException {
        // 自定义过滤逻辑
        chain.doFilter(request, response);
        // 自定义过滤逻辑
    }

    /** 表示过滤器的范围。*/
    @Override
    public Scope scope() {
        return Scope.GLOBAL;
    }
}
  1. priority 表示该过滤器的优先级,数字越小,越小执行,可使用 FIT 提供的@Order注解,也可自定义优先级;
  2. matchPatterns 和 mismatchPatterns 进行组合实现对 Http 请求的 url 的筛选,其具体配置规则如下:
  3. 当路径样式为 '/a' 时,只有完全匹配的请求 '/a' 才能过滤;
  4. 当路径样式为 '/a?' 时,可以模糊匹配 1 个字符,如 '/aa' 或 '/ab' 这样的请求;
  5. 当路径样式为 '/a*' 时,可以在一段路径内模糊匹配任意个字符,如 '/a' 或 '/abc' 这样的请求;
  6. 当路径样式为 '/a**' 时,可以在任意段路径内模糊匹配任意个字符,如 '/a' 或 '/a/b/c' 这样的请求;
  7. doFilter 由开发者自定义实现过滤逻辑;
  8. scope 表示过滤器的范围,根据FIT的插件化开发思想,提供PLUGINGLOBAL两种范围,支持插件的特有过滤器及公用过滤器。

4.3.2 过滤链(FilterChain)

开发者对于 Http 请求可能有多个不同的处理需求, FIT 支持开发者自定义实现多个过滤器,并自动管理为一条过滤链,根据每个过滤器的优先级和匹配原则等进行顺序过滤,最终处理请求。 开发者无需关注过滤链的具体实现,只需关注过滤器的具体设置、合理安排优先级顺序即可。

public interface HttpServerFilterChain {
    /**
     * 继续执行下一个过滤器。
     *
     * @param request 表示当前 Http 请求的 {@link HttpClassicServerRequest}。
     * @param response 表示当前 Http 响应的 {@link HttpClassicServerResponse}。
     * @throws HttpServerException 当执行当前请求发生异常时。
     */
    void doFilter(HttpClassicServerRequest request, HttpClassicServerResponse response) throws HttpServerException;
}

4.4 MVC 能力规格

4.4.1 请求映射

根据请求的类型,请求映射可以分为 GetMappingPostMappingPutMappingDeleteMapping 等等。在这些注解中,我们通过设置 path 来设置请求映射的路径。

注意:设置 path 时,path 字符串中请勿包含特殊字符,其中特殊字符包括 "#%@()+,/:;<=>?@\\| 等。使用需要转移的特殊字符会导致请求无法路由到期望 Controller。 例子:

@PostMapping(path = "/this/is/an/example")

反例:

@PostMapping(path = "/this/is/an/error example")

目前,请求映射的 path 并不支持 .././// 等,这意味着请求映射中设置的 path 与实际访问时的 uri 应该保持一致。

@PostMapping(path = "/first/second")
void twoLevelMethod() {}

@PostMapping(path = "/first/second/third")
void threeLevelMethod() {}

在上面的例子中,我们无法通过 /first/second/third/../ 来调用 twoLevelMethod

4.4.2 处理器方法

使用 @RequestParam 注解可以在入参中注入请求中添加的 query 参数, 最常用用法是设置一个 key 来注入对应的 value。比如 @RequestParam("helloKey") String hello 会将 query 参数中键为 helloKey 所对应的值注入到入参 hello 中。

url 中使用 ? 表示接下来都是 query 参数,且多个 query 参数间使用 & 进行区分。这里 ; 被看作是普通的字符。比如 k1=v1;k2=v2 表示一个键为 k1,且值为 v1;k2=v2 的 query 参数。

目前限制了 query 参数的数量,当 query 参数的数量超过 1000 时,会抛出异常,并返回 500 的 HTTP 状态码。

4.5 文件消息体处理方法

4.5.1 消息体类

FIT 提供了多个实体类用于处理消息体数据,其中几个实体类可用于处理 MVC 中文件上传、下载的功能,定义如下:

类名 定义
PartitionedEntity 表示分块的消息体数据
NamedEntity 表示带名字的消息体数据
FileEntity 表示文件类型的消息体数据
TextEntity 表示文本格式的消息体数据

4.5.2 文件上传

用户可以通过定义一个Post方法来接收文件的上传,将消息体数据接收为一个PartitionedEntity实体,通过该实体获取带名字的消息体数据后,使用NamedEntity::asFile()将之转换为FileEntity,再进行文件的处理。

@Component
public class UploadFileController {
    @PostMapping(path = "/save", description = "上传文件")
    public void saveUploadFile(PartitionedEntity receivedFiles) {
        notNull(receivedFiles, "The file to be uploaded cannot be null.");
        List<NamedEntity> entityList =
                receivedFiles.entities().stream().filter(NamedEntity::isFile).collect(Collectors.toList());
        for(NamedEntity namedEntity : entityList){
            FileEntity file = namedEntity.asFile();
            //文件处理逻辑
        }
    }
}

4.6 Swagger使用方法

4.6.1 Swagger概述

Swagger 是一个开源工具集,用于设计、构建、记录和使用 RESTful API。它提供了一种简单的方法来生成 API 文档,并通过交互式界面让开发者可以轻松地测试 API,Swagger的主要特点如下:

  1. API 文档生成
  • 使用 Swagger 注解,自动生成清晰的 API 文档,支持 OpenAPI 规范。
  1. 交互式界面
  • 提供 Swagger UI,允许开发者在浏览器中查看 API 文档并直接进行 API 调用。
  1. 规范化
  • 使用 OpenAPI 规范(之前称为 Swagger 规范),标准化 API 的描述,使得不同工具和语言之间能够互操作。

FIT 框架内置整合了Swagger以方便开发者进行接口的查询及使用,本章将介绍如何启动Swagger以及如何将接口文档接入Swagger中。

4.6.2 访问 Swagger-UI 页面

首先对开发的所有插件进行构建编译:

mvn clean install

打开 bash 窗口,并进入到自定义的用户插件目录,输入命令 fit start,其中 start 表示应用的启动;

Running command: java -D"plugin.fit.dynamic.plugin.directory=/d/WorkSpace/custom" -jar fit-discrete-launcher-3.7.0-SNAPSHOT.jar
[yyyy-MM-dd hh:mm:ss.SSS] [INFO ] [main] [modelengine.fitframework.runtime.discrete.DiscreteFitRuntime] Prepare to start FIT application...
[yyyy-MM-dd hh:mm:ss.SSS] [INFO ] [main] [modelengine.fitframework.runtime.discrete.DiscreteFitRuntime] FIT application started.
[yyyy-MM-dd hh:mm:ss.SSS] [INFO ] [registry-client-thread-0] [modelengine.fit.service.RegistryClient] Register fitables successfully. All fitable services are ready.
[yyyy-MM-dd hh:mm:ss.SSS] [INFO ] [netty-http-server-thread-0] [modelengine.fit.http.server.netty.NettyHttpClassicServer] Start netty http server successfully. [port=8080]

当出现以上段落时,表明应用启动成功。我们也可以通过 debug 参数对应用进行调试,具体可以通过命令 fit help 来获取各个命令参数的详细信息和作用。 FIT 框架会自动扫描插件目录,访问http://localhost:8080/openapi.html 可进入至Swagger-UI页面,此处localhost:8080请按照服务器实际地址及端口进行访问。

以第七章数据访问MyBatis用例为例,具体数据访问操作请参考第七章文档,以下为用例中的HTTP方法的控制器:

/**
 * 表示用户资源的控制器。
 */
@Component
@RequestMapping("/data")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    /**
     * 通过用户 id 获取信息
     */
    @GetMapping(path = "/getUserById")
    public UserData getTask(@RequestParam("id") Integer userId) {
        return this.userService.getUserById(userId);
    }

    /**
     * 添加用户
     */
    @GetMapping(path = "/user")
    public void getTask(@RequestParam("name") String name, @RequestParam("email") String email) {
        UserData user = new UserData();
        user.setName(name);
        user.setEmail(email);
        this.userService.insertUser(user);
    }
}

将插件打包后载入用户插件目录,访问http://localhost:8080/openapi.html

image

Swagger-UI页面中可以查看各接口的请求方法、路径、注释,开发者可以在该页面中进行接口的测试执行,以获取接口的使用方法信息以及响应格式。

如果需要去除某一个 API 的文档显示,可以在对应方法上添加@DocumentIgnored注解; 通过访问http://localhost:8080/v3/openapi 可以获取所有接口信息的 Json 格式。