online相关知识
-
DM 源码阅读系列文章(八)Online Schema Change 同步支持作者:lan 本文为 DM 源码阅读系列文章的第八篇,上篇文章 对 DM 中的定制化数据同步功能进行详细的讲解,包括库表路由(Table routing)、黑白名单(Black & white table lists)、列值转化(Column mapping)、binlog 过滤(Binlog event filter)四个主要功能的实现。 本篇文章将会以 gh-ost 为例,详细地介绍 DM 是如何支持一些 MySQL 上的第三方 online schema change 方案同步,内容包括 online schema change 方案的简单介绍,online schema change 同
-
5分钟了解MySQL5.7的Online DDL雷区wKioL1ftzjLgjgkZAAA4eVx2Dz8931.jpgPart1:写在最前Online DDL,当新手听到这个名字的时候,非常高兴,以为无论什么情况下,修改表结构都不会锁表,理想很丰满,现实很骨感!读完本文,教你如何避开这些雷区,安全的修改表结构。话不多说,我们分别来看下MySQL5.6和MySQL5.7在修改表结构上的相同和异同。Part2:5.6.25的表现①首先我们构造数据并进行测试mysql> create database helei;Query OK, 1 row affected (0.01 sec)mysql> use helei;Database changedmysql> create table helei( -> id int(10) unsigned NOT NULL AUTO_INCREMENT, -> c1 int(10) NOT NULL DEFAULT '0',&
-
MySQL下使用Inplace和Online方式创建索引的教程MySQL各版本,对于add Index的处理方式是不同的,主要有三种:(1)Copy Table方式这是InnoDB最早支持的创建索引的方式。顾名思义,创建索引是通过临时表拷贝的方式实现的。新建一个带有新索引的临时表,将原表数据全部拷贝到临时表,然后Rename,完成创建索引的操作。这个方式创建索引,创建过程中,原表是可读的。但是会消耗一倍的存储空间。(2)Inplace方式这是原生MySQL 5.5,以及innodb_plugin中提供的创建索引的方式。所谓Inplace,也就是索引创建在原表上直接进行,不会拷贝临时表。相对于Copy Table方式,这是一个进步。Inplace方式创建索引,创建过程中,原表同样可读的,但是不可写。(3)Online方式这是MySQL 5.6.7中提供的创建索引的方式。无论是Copy Table方式,还是Inplace方式,创建索引的过程中,原表只能允许读取,不可写。对应用有较大的限制,因此MySQL最新版本中,InnoDB支持了所谓的Online方式创建索引。Inno
-
pt-online-schema-change和XtraBackup的工作原理一.直接执行 alter table XXX ……1.按照原始表(original_table)的表结构和DDL语句,新建一张不可见的临时表。2.在原表上加write lock,此时对原表的所有U D I DDL 都是阻塞的。3.执行insert into tmp_table select * from oldtable;4.rename oldtable 和 tmp_table,再drop oldtable。5.释放 write lock。二.pt-online-schema-change1.如果存在外键,根据alter-foreign-keys-method参数的值,检测外键相关的表,做相应设置的处理。2.创建一个和你要执行 alter 操作的表一样的新的空表结构(是alter之前的结构)。3.在新表执行alter table 语句,4.在原表中创建触发器(3个)三个触发器分别对应insert,update,delete操作5.从原表拷贝数据到临时表,拷贝过程中通过原表上的触发器在原表进行的
online相关课程
online相关教程
- 2. Django 框架介绍 首先来看看 Django 诞生的过程:Django 是从真实世界的应用中成长起来的,它是由堪萨斯(Kansas)州 Lawrence 城中的一个 网络开发小组编写的。 它诞生于 2003 年秋天,那时 Lawrence Journal-World 报纸的程序员 Adrian Holovaty 和 Simon Willison 开始用 Python 来编写程序。当时他们的 World Online 小组制作并维护当地的几个新闻站点,并在以新闻界特有的快节奏开发环境中逐渐发展。这些站点包括有 LJWorld.com、Lawrence.com 和 KUsports.com, 记者(或管理层) 要求增加的特征或整个程序都能在计划时间内快速地被建立,这些时间通常只有几天或几个小时。 因此,Adrian 和 Simon 开发了一种节省时间的网络程序开发框架, 这是在截止时间前能完成程序的唯一途径。2005 年的夏天,当这个框架开发完成时,它已经用来制作了很多个 World Online 的站点。 当时 World Online 小组中的 Jacob Kaplan-Moss 决定把这个框架发布为一个开源软件,于是 Django 正式出现在大众眼前,并且得到了快速发展,原来的 World Online 的两个开发者(Adrian and Jacob)仍然掌握着 Django,但是其发展方向受社区团队的影响更大。Django 基于 MTV 模式,其框架示意图如下:Django 框架图示Django 框架的核心组件有:用于创建模型的对象关系映射(ORM);基于正则表达式的 URL 分发器;视图系统,用于处理 URL 请求;模板系统,用于将模板文件转换成 HTML 文件,并在浏览器上显示;缓存系统,并提供多种缓存方式。除此之外 Django 框架中还包括如下功能:轻量级的、独立的 Web 服务器,用于开发和测试;表单序列化及验证系统 (Form),用于 HTML 表单和数据库存储的数据之间的转换;中间过滤组件,允许对请求处理的各个阶段进行干涉;序列化系统,能够生成或读取采用 XML 或 JSON 表示的 Django 模型实例。此外,Django 还内置了许多有用的应用,比如一个可扩展的认证系统、动态站点管理页面等等。Django 前身就是在线新闻站点,因此它是非常适合做 Web 站点开发的。
- 2. Navigator 只读属性 Window.navigator 会返回一个 Navigator 对象的引用,可以用于请求运行当前代码的应用程序的相关信息。(MDN)navigator 最常用到的就是 userAgent 属性,通常简称为 UA。console.log(navigator.userAgent);// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36// 不同设备的输出不一定相同该属性包含了一些浏览器、操作系统、设备等信息,通常判断平台,如 ios、android,或者判断浏览器都会使用这个属性。但 userAgent 并不是很准确,特别是一些被拉黑名单的第三方的浏览器厂商,通过伪造 UA 欺骗网站,防止被拦截,比如通将 UA 设置成某个版本的 Chrome 浏览器,达到伪造浏览器的目的。除了 userAgent,其他的内容用到的相对较少。但也有比较有趣的属性,比如 onLine,这个属性可以用于判断网络状态。console.log(navigator.onLine); // true or false如果网站需要支持 i18n (国际化),还会用到 language 属性。其他的一些处于试验性的特性,也会被放在 navigator 下。如以前申请媒体设备需要使用 navigator.getUserMedia,后来随着特性的升级,变成了MediaDevices.getUserMedia()。
- 2.1 服务提供者 首先我们新建服务提供者项目,我们选择 Spring Initializr 来初始化 Spring Boot 项目,这是服务提供者的项目信息。pom.xml初始化完成,在 pom.xml 中加入项目所需的依赖。<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.cdd</groupId> <artifactId>zookeeper-provider</artifactId> <version>0.0.1-SNAPSHOT</version> <name>zookeeper-provider</name> <description>zookeeper-providerDemo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- curator 客户端 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>5.1.0</version> </dependency> <!-- curator 客户端 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>5.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>依赖导入完成后,我们在 application.properties 配置文件中加入端口的配置。application.propertiesserver.port=8090接下来开始编写服务提供者的接口。ProviderService我们在 Spring Boot 主类的同级新建 service 目录,在 service 目录中新建 ProviderService 类。package cn.cdd.zookeeper.provider.service;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Service;import java.net.InetAddress;import java.net.UnknownHostException;@Servicepublic class ProviderService { @Value("${server.port}") private String port; public String callMethod(){ try { return "调用了服务提供者 " + InetAddress.getLocalHost().getHostAddress() + ":" + port + " 的方法"; } catch (UnknownHostException e) { e.printStackTrace(); } return null; }}接下来编写使用 Curator 连接 Zookeeper 服务的代码。CuratorService在 service 目录下新建 CuratorService 类:@Componentpublic class CuratorService implements ApplicationRunner { @Value("${server.port}") private String port; // CuratorFramework 客户端 private static CuratorFramework client; // 服务地址临时节点的父节点 private static final String PROVIDER_NODE = "/imooc/provider"; // 服务地址临时节点的全路径 private static String PROVIDER_ADDRESS; // 服务 ip private static String PROVIDER_IP; @Override public void run(ApplicationArguments args) throws Exception { // 获取客户端,连接 Zookeeper 服务 buildCuratorClient(); // 获取本机 IP PROVIDER_IP = InetAddress.getLocalHost().getHostAddress(); // 注册本机地址到 Zookeeper registeredAddress(); } /** * 构建 CuratorFramework 客户端,并开启会话 */ private void buildCuratorClient() { // 使用 CuratorFrameworkFactory 构建 CuratorFramework client = CuratorFrameworkFactory.builder() .sessionTimeoutMs(10000) // Zookeeper 地址 .connectString("127.0.0.1:2181") // 重连策略 .retryPolicy(new RetryForever(10000)) .build(); // 开启会话 client.start(); System.out.println(">>> 服务提供者连接 Zookeeper "); } /** * 注册服务地址 */ public String registeredAddress() { String address = null; try { Stat stat = client.checkExists().forPath(PROVIDER_NODE); if (stat == null) { client.create().creatingParentsIfNeeded().forPath(PROVIDER_NODE); } // 获取本机地址 address = PROVIDER_IP + ":" + port; // 创建临时节点 /imooc/provider/192.168.0.106:8090 PROVIDER_ADDRESS = client.create() .withMode(CreateMode.EPHEMERAL) .forPath(PROVIDER_NODE + "/" + address); } catch (Exception e) { e.printStackTrace(); } System.out.println(">>> 本服务已上线"); return ">>> 服务提供者 " + address + " 已上线"; } /** * 注销服务地址 */ public String deregistrationAddress() { String address = null; try { Stat stat = client.checkExists().forPath(PROVIDER_ADDRESS); if (stat != null) { client.delete().forPath(PROVIDER_ADDRESS); } // 获取本机地址 address = PROVIDER_IP + ":" + port; } catch (Exception e) { e.printStackTrace(); } System.out.println(">>> 本服务已下线"); return ">>> 服务提供者 " + address + " 已下线"; }}在 CuratorService 类中,我们提供了创建 Curator 客户端的方法,注册服务地址的方法以及注销服务地址的方法。在该服务启动时,就会自动连接 Zookeeper 服务,并且把自身的地址信息注册到 Zookeeper 的临时节点上。ProviderController这里我们使用 RESTful 的风格编写服务提供者对外的接口,在 service 目录同级创建 controller 目录,在 controller 中创建 ProviderController 。@RestController@RequestMapping("/provider")public class ProviderController { @Value("${server.port}") private String port; @Autowired private CuratorService curatorService; @Autowired private ProviderService providerService; /** * 调用方法 * http://localhost:8090/provider/callMethod * * @return String */ @GetMapping("/callMethod") public String callMethod() { return providerService.callMethod(); } /** * 上线服务 * http://localhost:8090/provider/online * * @return String */ @GetMapping("/online") public String registeredAddress() { return curatorService.registeredAddress(); } /** * 下线服务 * http://localhost:8090/provider/offline * * @return String */ @GetMapping("/offline") public String deregistrationAddress() { return curatorService.deregistrationAddress(); }}controller 编写完毕后,我们就可以对我们的服务提供者进行测试了。
- 2.5 服务提供者上下线测试 首先我们来测试服务下线的情况,服务提供者下线分为两种情况,一种是服务出现故障,与 Zookeeper 服务端断开连接时,另一种是业务需求手动对服务进行下线处理。服务停机我们先来测试服务停机时的情况,这里我们手动关闭端口为 8092 的服务,模拟服务停机,等待会话超时,Zookeeper 服务端就会移除会话失效的临时节点,然后再次访问服务消费者的接口 http://localhost:9090/consumer/callMethod :调用了服务提供者 192.168.0.102:8090 的方法刷新页面,再次访问:调用了服务提供者 192.168.0.102:8091 的方法再次刷新页面进行访问:调用了服务提供者 192.168.0.102:8090 的方法我们发现端口为 8092 的服务已经无法被访问了,但是服务消费者并没有发生异常,说明服务下线成功。这里我们还可以在节点监听的回掉方法中发送短信或邮件通知系统管理员,提醒他们服务下线了。接下来我们测试手动把服务提供者下线的情况。手动下线如果某个服务需要手动下线,我们就可以访问我们在服务提供者中提供的下线方法,这里我们把端口为 8091 的服务下线,访问 http://localhost:8091/provider/offline ,查看浏览器:>>> 服务提供者 192.168.0.102:8091 已下线我们发现端口为 8091 的服务已经下线了,手动下线的情况我们不需要等待会话超时,因为这个会话还存活着,接下来我们就可以再次访问服务消费者的接口 http://localhost:9090/consumer/callMethod :调用了服务提供者 192.168.0.102:8090 的方法我们发现现在只有端口为 8090 的服务能提供服务了,说明服务下线成功了。测试完服务下线的情况,我们来对服务进行上线测试,上线也同样分为两种情况,一种是停机的服务重新启动成功时会注册自身的地址到 Zookeeper 服务,另一种是把手动下线的服务手动进行上线,手动上线的过程同样是注册自身的地址信息到 Zookeeper 服务,接下来我们就同时测试上面两种服务上线的情况。服务启动 + 手动上线首先我们启动上面停机的 8092 服务,然后调用端口为 8091 的手动上线方法 http://localhost:8091/provider/online ,执行完成后,我们就可以使用服务消费着来进行测试了,调用服务消费者的接口 http://localhost:9090/consumer/callMethod 查看浏览器内容:调用了服务提供者 192.168.0.102:8090 的方法刷新页面,再次访问:调用了服务提供者 192.168.0.102:8091 的方法再次刷新页面进行访问:调用了服务提供者 192.168.0.102:8092 的方法我们可以发现,服务消费者依次调用了 8090 ,8091 ,8092 这 3 个服务提供者的方法,并且实现了轮询的负载均衡策略,说明我们的服务上线成功
- 4. 如何架构一个成熟的Ktor应用 由上面可知构建一个简单的Ktor Server可以说是非常简单,然而要构建一个成熟的Ktor Server应用也是类似,主要是多了一些如何模块化组织业务模块和更清晰化去架构业务。主要分为以下7个步骤:4.1 选择构建Server的方式构建Ktor Server应用主要分为两种**: 一种是通过embeddedServer方式构建,另一种则是通过EngineMain方式构建。**具体的选择使用方式参考上面第3节4.2 选择Server Engine要想运行Ktor服务器应用程序,就需要首先创建和配置服务器。服务器配置其中就包括服务器引擎配置,各种引擎特定的参数比如主机地址和启动端口等等。 Ktor支持大多数目前主流的Server Engine,其中包括:TomcatNettyJettyCIO(Coroutine-based I/O)此外Ktor框架还提供一种类型引擎TestEngine专门供测试时使用。要想使用上述指定的Server Engine,就需要添加Server Engine相关的依赖,Ktor是既支持Gradle来管理库的依赖也支持Maven来管理。4.3 配置服务参数配置服务引擎参数,由于构建Server方式不同,所以配置引擎参数也不一样。对于embeddedServer函数方式构建的Ktor应用可以直接通过代码函数参数方式指定,对于EngineMain方式则通过修改配置文件 application.conf 。4.3.1 embeddedServer函数方式fun main(args: Array<String>) { embeddedServer(Tomcat, port = 8080) {//配置了服务器引擎类型和启动端口 routing { get("/") { call.respondText("Hello Ktor") } } }.start(wait = true) } //除了服务器引擎类型和启动端口的配置,还支持一些参数的配置 fun main() { embeddedServer(Netty, port = 8080, configure = { connectionGroupSize = 2 //指定用于接收连接的Event Group的大小 workerGroupSize = 5 //指定用于处理连接,解析消息和执行引擎的内部工作的Event Group的大小, callGroupSize = 10 //指定用于运行应用程序代码的Event Group的大小 }) { routing { get("/") { call.respondText("Hello Ktor") } } }.start(wait = true) } //设置可以定制一个EngineEnvironment用于替代默认的ApplicationEngineEnvironment,我们可以通过源码可知,embeddedServer函数内部默认构建一个ApplicationEngineEnvironment。 fun main() { embeddedServer(Netty, environment = applicationEngineEnvironment { log = LoggerFactory.getLogger("ktor.application") config = HoconApplicationConfig(ConfigFactory.load()) module { main() } connector { port = 8080 host = "127.0.0.1" } }).start(true) }4.3.2 EngineMain方式如果是选择EngineMain方式构建Server, 那么就需要通过修改 applicaton.confktor { application { modules = [ com.mikyou.ktor.sample.ApplicationKt.module ] //配置加载需要加载的module模块,这里配置实际上就是Application中module扩展函数 } } //除了可以配置需要加载module模块,还可以配置端口或主机,SSL等 ktor { deployment { port = 8080 //配置端口 sslPort = 8443 //配置SSL端口 watch = [ http2 ] } application { modules = [ com.mikyou.ktor.sample.ApplicationKt.module ] //配置加载需要加载的module模块 } security {//配置SSL签名和密钥 ssl { keyStore = build/test.jks keyAlias = testkey keyStorePassword = test privateKeyPassword = test } } } //application.conf文件包含一个自定义jwt(Json Web Token)组,用于存储JWT设置。 ktor { deployment { port = 8080 //配置端口 sslPort = 8443 //配置SSL端口 watch = [ http2 ] } application { modules = [ com.mikyou.ktor.sample.ApplicationKt.module ] //配置加载需要加载的module模块 } security {//配置SSL签名和密钥 ssl { keyStore = build/test.jks keyAlias = testkey keyStorePassword = test privateKeyPassword = test } } jwt {//JWT配置 domain = "https://jwt-provider-domain/" audience = "jwt-audience" realm = "ktor sample app" } }预定义属性命令行运行可以使用command运行ktor的jar,并且指定端口java -jar sample-app.jar -port=8080可以通过config参数指定xxx.conf的路径java -jar sample-app.jar -config=xxx.conf还可以通过-P指定运行应用程序代码的Event Group的大小java -jar sample-app.jar -P:ktor.deployment.callGroupSize=7代码中读取application.conf中的配置代码中读取application.conf中配置是一件很实用的操作,比如连接数据库时配置都可以通过自定义属性来实现。比如下面这个例子:ktor { deployment {//预定义属性 port = 8889 host = www.youkmi.cn } application { modules = [ com.mikyou.ApplicationKt.module ] } #LOCAL(本地环境)、PRE(预发环境)、ONLINE(线上环境) env = LOCAL//自定义属性 security {//把db相关配置放入security,日志输出会对该部分内容用*进行隐藏处理 localDb {//自定义属性localDb url = "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai" driver = "com.mysql.cj.jdbc.Driver" user = "xxx" password = "xxx" } remoteDb {//自定义属性remoteDb url = "jdbc:mysql://192.168.0.101:3306/mydb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai" driver = "com.mysql.cj.jdbc.Driver" user = "xxx" password = "xxx" } } }在appliction.conf自定义了属性配置后,如何在Ktor代码获取呢?请看如下代码:const val KEY_ENV = "ktor.env" //自定义属性的key,就是根据配置中层级通过.连接,有点类似JSON的取值调用 const val KEY_LOCAL_DB_URL = "ktor.security.localDb.url" const val KEY_REMOTE_DB_URL = "ktor.security.remoteDb.url" const val KEY_LOCAL_DB_DRIVER = "ktor.security.localDb.driver" const val KEY_REMOTE_DB_DRIVER = "ktor.security.remoteDb.driver" const val KEY_LOCAL_DB_USER = "ktor.security.localDb.user" const val KEY_REMOTE_DB_USER = "ktor.security.remoteDb.user" const val KEY_LOCAL_DB_PWD = "ktor.security.localDb.password" const val KEY_REMOTE_DB_PWD = "ktor.security.remoteDb.password" fun Application.configureDb(vararg tables: Table) { //获取当前Env环境 //通过Application中environment实例对象拿到其config对象,通过config以key-value形式获取配置中的值,不过只支持获取String和List val env = environment.config.propertyOrNull(KEY_ENV)?.getString() ?: "LOCAL" val url = environment .config .property(if (env == "LOCAL") KEY_LOCAL_DB_URL else KEY_REMOTE_DB_URL)//如果是LOCAL环境就切换到本地数据库连接方式 .getString() val driver = environment .config .property(if (env == "LOCAL") KEY_LOCAL_DB_DRIVER else KEY_REMOTE_DB_DRIVER) .getString() val user = environment .config .property(if (env == "LOCAL") KEY_LOCAL_DB_USER else KEY_REMOTE_DB_USER) .getString() val pwd =environment .config .property(if (env == "LOCAL") KEY_LOCAL_DB_PWD else KEY_REMOTE_DB_PWD) .getString() //连接数据库 Database.connect(url = url, driver = driver, user = user, password = pwd) //创建数据库表 transaction { tables.forEach { SchemaUtils.create(it) } } }4.4 通过Features添加必要功能构件在Ktor中一个最典型的请求(Request)-响应(Response)管道模型大致是这样的: 它从一个请求开始,该请求会被路由到特定的程序处理,并经由我们的应用程序逻辑处理,最后做出响应。然而在实际的应用开发中,并不会这么简单的,但是本质上Pipeline是不变的。那么在Ktor如何更加将这个简单管道模型给丰富起来呢? 那就是向管道模式添加各种各样的Feature(功能构件或者功能插件)。4.4.1 向管道模型添加功能构件在许多应用开发中经常会用到一些基础通用的功能,比如内容编码、序列化、cookie、session等,这些基础通用的功能在Ktor中统称为**Features(功能构件)。**所有的Features构件都类似一个插件,插入在Request、application Logic和Response切面之间。由上图可知,当一个请求Request进来后,首先会通过Routing路由机制路由给一个特定的Handler进行处理;然而在把Request交由Handler处理之前可能会经过若干个Feature处理;然后Handler处理完这个Request请求,就会将Response响应返回给客户端,然而在将响应发送给客户端之前,它还是可能会经过若干个Feature处理,最终Response响应返回到客户端。可以看出整条从Request到Response链路就类似一个工厂流水线,每个Feature各司其职。4.4.2 Routing本质上也是一个FeatureFeature的灵活性和可插拔性非常强大,它可以出现在Request/Response管道模型中任何一个节点部分。Routing虽然我们称为路由,但其本质也是一个Feature4.4.3 如何安装Feature一般都是在应用初始化的时候去安装Feature即可,安装Feature非常简单。仅仅几行 install 即可搞定,如果是非内置的 Feature 还需要自己引入相关lib依赖. 除了使用现有的Feature, 还可以自定义Feature,关于如何自定义Feature属于Ktor高阶命题,后续再展开。import io.ktor.features.* fun Application.main() { install(Routing) install(Gson) //... } //除了在main函数中安装,还可以在module入口函数中安装 fun Application.module() { install(Routing) install(Gson) //... }4.5 通过Routing处理请求Routing本质上也是一个Feature,所以Routing也需要进行install,然后就可以定义Route Handler处理请求了。4.5.1 安装Routing路由import io.ktor.routing.* install(Routing) { // ... } //或者直接调用Application的routing扩展函数 import io.ktor.routing.* routing { // ... } //因为Application的routing扩展函数内部做了处理,对于未安装Routing会自动安装Routing的容错,可以稍微瞅下源码 @ContextDsl public fun Application.routing(configuration: Routing.() -> Unit): Routing = featureOrNull(Routing)?.apply(configuration) ?: install(Routing, configuration) //通过源码可以发现,如果configuration没有安装Routing就会自动安装Routing,所以大家一般看到的Routing都没有手动install过程,而是直接类似下面的代码。 fun main(args: Array<String>) { embeddedServer(Tomcat, port = 8080) { routing {//直接调用Application的扩展函数routing,内部做了对于未安装Routing会自动安装Routing的容错处理 get("/") { call.respondText("Hello Ktor") } } }.start(wait = true) }4.5.2 定义路由处理的Handler可以看下下面最简单的一个get服务的定义,下面用get源码来解读:fun main(args: Array<String>) { embeddedServer(Tomcat, port = 8080) { routing { get("/") {//可以看到这个处理get请求的handler,它实际上是一个Route的扩展函数,一起来看看源码 call.respondText("Hello Ktor") } } }.start(wait = true) } //Route.get函数源码,其实一个Route对象就是一个对应的Handler, @ContextDsl public fun Route.get(path: String, body: PipelineInterceptor<Unit, ApplicationCall>): Route { return route(path, HttpMethod.Get) { //route函数本质上是一个Route的扩展函数 handle(body) //通过调用Route对象来处理的请求 } } //route函数本质上是一个Route的扩展函数 @ContextDsl public fun Route.route(path: String, method: HttpMethod, build: Route.() -> Unit): Route { val selector = HttpMethodRouteSelector(method) return createRouteFromPath(path).createChild(selector).apply(build)//最终调用apply返回Route对象,build是传入handle(body)执行的lambda, //也就是创建完child后返回一个Route对象,最终再调用它的handle函数 }4.6 应用模块化为了使得Ktor应用更具有可维护性、灵活性以及,Ktor提供一种思路就是将应用按照业务维度进行模块化设计。注意这里模块化概念并不是在项目中的一个Module,而这里module本质上是一个 Application 的扩展函数。并且可以在 application.conf 指定某一个或若干个module进行可插拔式的部署和卸载。然后一个Module又包括了一条或若干条Request/Response的管道模型。应用模块代码例子如下://定义一个accountModule,实际上是一个Application的扩展函数 fun Application.accountModule() { routing { loginRoute() bindPhoneRoute() getSmsCodeRoute() registerRoute() } } //在application.conf配置加载对应的accountModule模块 ktor { #LOCAL、PRE、ONLINE env = LOCAL deployment { port = 8889 host = www.youkmi.cn } application { //可以在modules动态配置所需加载Module,第一个com.mikyou.ApplicationKt.module默认是主Module,用于加载一些基础通用的Features,实现模块的可插拔式的安装和卸载 modules = [ "com.mikyou.ApplicationKt.module","com.mikyou.modules.account.AccountModuleKt.accountModule"]//配置accountModule,注意配置路径,例如定义Account模块的类文件是AccountModule.kt, 所以它对应类名称就是AccountModuleKt,所以accountModule模块类路径就是com.mikyou.modules.account.AccountModuleKt.accountModule。 } //... }4.7 应用结构化Ktor在提供灵活性方面提供多种方式来组织和结构化应用。4.7.1 以文件来形式组织将单个文件中相关的路由分组管理,比如应用处理订单和用户,就会单独建立两个文件: OrderRoutes.kt和CustomerRoutes.kt文件分别管理相关路由请求。OrderRoutes.ktfun Route.orderByIdRoute() { get("/order/{id}") { } } fun Route.createOrderRoute() { post("/order") { } }CustomerRoutes.ktfun Route.customerById() { get("/customer/{id}") { } } fun Route.createCustomer() { post("/customer") { } }4.7.2 以路由定义形式组织fun Application.accountModule() { routing { loginRoute() bindPhoneRoute() registerRoute() } } //登录 private fun Route.loginRoute() { post("/api/login") { //... } } //注册 private fun Route.registerRoute() { post("/api/register") { //... } } //绑定手机号 private fun Route.bindPhoneRoute() { post("/api/bindPhone") { //... } }
- 12.6【速查表】Dockerfile 语法 React17 系统精讲 结合TS打造旅游电商
online相关搜索
-
oauth
object
object c
objective
objective c
objective c基础教程
objective c教程
objectivec
office visio 2003
offsetof
offsetparent
offset函数
okhttp
on on
on time
onbeforeunload
onblur
onclick
oncontextmenu
online