Build Status

Arhat Service 使用指南

Arhat Service 是将arhat-core这款基于内存增量Map/Reduce的运算核心封装成RESTful Web服务的服务器程序。这里 重点介绍的是arhat-service工程所对应的RESTful API的规则与用法,并会介绍arhat-core的核心抽象模型,但并不会 介绍arhat-core的编程API。


一 · 版本、代号

版本是指库依赖时所使用的x.y.z的版本号,z的更改是指修补错误/漏洞,y的更改是指有新功能加入且不影响旧 功能的使用,x的更改是指API有不能向后兼容大的变动。

arhat-core工程还引入了代号(CODE_NAME)的概念,它用意是标记一个重要的里程碑/代码分支,可以认为是一种介乎 版本号yx变更的状态。

当前,arhat-service的版本与代号都是由arhat-core工程决定的,但不排除日后由于RESTful API的演变而更改代号, 所以将代号视为arhat-service的RESTful API版本是比较稳妥的。当前的约定就是代号不变的情况下,RESTful API一 定是向后兼容的。

当前的版本是1.0.0,代号jubo,即【举钵罗汉】。


二 · 编译、打包

在你检出arhat-service和arhat-core工程源代码前,请确保:

  1. 你已安装好64位JDK 8,且已设置好JAVA_HOME环境变量;
  2. 下载并配置好SBT 0.13.6+,且在PATH环境变量后加上SBT安装目录下的bin目录。

当准备好环境后,检出arhat-core工程,并在其目录下输入以下命令以便确认代码都是能够通过编译的:

D:\path\to\arhat-core> sbt clean compile

在你第一次运行sbt命令或第一次碰到新的库依赖时,SBT会下载所有必要的库,如果由于【伟大的墙】阻隔了你,请自备 【梯子】解决这个问题。下载新依赖库时速度很可能不给力,请耐心等候。

当你构建好arhat-core,接下来就是检出arhat-service工程,并在其目录下输入一下命令,构建出可用于Windows和 Unix下直接运行的ZIP包:

D:\path\to\arhat-service> sbt clean compile stage dist

构建完毕之后,你就可以在D:\path\to\arhat-service\target\universal目录下发现arhat-service-x.y.z.zip 包。解压该ZIP包,进入其bin目录,执行arhat-service便可启动服务器。

当然,嫌麻烦还可以直接在SBT的交互环境中直接运行,与打包运行没有什么大区别,都是些配置文件位置上的差异而已:

D:\path\to\arhat-service> sbt run

运行起来后,注意命令行输出/日志文件,可以看到默认绑定了9000端口。


三 · 配置文件

JVM配置文件

JVM的配置文件在类Unix服务器的环境下是安装目录/conf/application.ini,该配置文件需要为JVM参数添加-J的 前缀,例如:

-J-server
-J-d64
-J-showversion
-J-Xms1024M
-J-Xmx4096M
-J-XX:MaxMetaspaceSize=256M
-J-Xmn512M
-J-XX:SurvivorRatio=2
-J-XX:+UseConcMarkSweepGC
-J-XX:+CMSParallelRemarkEnabled
-J-XX:+UseCMSInitiatingOccupancyOnly
-J-XX:CMSInitiatingOccupancyFraction=80
-J-XX:+ScavengeBeforeFullGC
-J-XX:+CMSScavengeBeforeRemark
-J-Dsun.net.inetaddr.ttl=60

而在Windows服务器的环境下,则是安装目录/ARHAT_SERVICE_config.txt,JVM参数不需要添加前缀:

-server
-d64
-showversion
-Xms1024M
-Xmx4096M
-XX:MaxMetaspaceSize=256M
-Xmn512M
-XX:SurvivorRatio=2
-XX:+UseConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=80
-XX:+ScavengeBeforeFullGC
-XX:+CMSScavengeBeforeRemark
-Dsun.net.inetaddr.ttl=60

日志配置文件

服务使用的是LogBack,配置文件在安装目录/conf/logback.xml,默认使用的是异步模式的日志、默认级别为DEBUG

服务参数配置文件

服务所需要配置的参数不多,文件是安装目录/conf/application.conf,默认配置如下:

production-mode = false // 是否产品模式,默认为false,为true的时候会启用appIds访问控制
compact-json = true     // 是否在协议交互时使用去掉缩进的紧凑JSON格式,默认为true
idle-timeout = 5        // 视图的最长闲置时间,单位为分钟,默认为5分钟
appIds = ["08ea58d6-baba-402f-a9a4-2a70d647db36"] // 当production-mode=true时才生效,以AppId的形式
                                                  // 控制哪些客户端可以接入服务。
table-source {
  89345 {                                       // 在创建该受管表格以“arhat://89345”作为URI进行引用
    uri = "file://D:/生育手术 - 整理 - 副本.xls"  // 具体格式内容请参考下文【创建数据表】API
  }
}

http {
  interface = "0.0.0.0" // 绑定哪个网卡地址,默认是0.0.0.0
  port = 9000           // 绑定哪个端口,默认是9000
  allow-domain = "*"    // 允许哪些跨域访问,可选配置,如果指定了,则按指定域限制跨域访问,若不指定且
                        // production-mode为false,则为"*"、即允许任何跨域访问,若production-mode为
                        // true,则只允许当前绑定的域访问。
}

akka { // Akka的配置详情可以参考它的官方网站文档
  loglevel = "DEBUG"
  loggers = ["akka.event.slf4j.Slf4jLogger"]
  http.server.idle-timeout = 5 min
}

四 · arhat-core的核心抽象概念

ArhatContext,即上下文,一个JVM程序可以同时使用多个上下文,不同的上下文以名字作为区分、各自持有各自的 并行ForkJoin线程池,不同上下文的表不能相互操作,一个上下文可持有多个表。默认的上下文名字为_DEFAULT_

Table,即数据表、简称表,它从TableSource获取一个表的实际数据及其Schema元数据,它是内存运算时存放实际 数据的位置。同一个上下文里的表使用字符串ID进行区分,该ID的形式是【时分秒@随机数】。它除了存放实际数据,还 持有着一个脚本执行引擎、所有从该表衍生出来的索引(使用引用计数的方式进行复用)、以及所有从该表派生出来的视 图。每个表在加载完数据后会自动产生一个Schema与TableSource中获取的一致的视图,该视图即该表的默认视图,其视 图ID为_DEFAULT_,这个默认视图代表着数据表加载完成后的原始状态。所有分组、求值都必须基于视图进行。

View,即视图,一个表可以派生出多个视图,不同的视图间使用ID进行区分,该ID的形式是【时分秒@随机数】。每 个视图逻辑上持有自己的Schema元数据与索引(Index)列表,视图的Schema用于针对数据表原有的Schema做映射 (Projection)、以便隐藏/创建数据列,视图的索引则用于针对该视图映射过后的各列进行各种快速的选择、排序操作 。从一个表上创建一个新视图时,可以使用一个新的Schema与/或一个针对每行数据的Filter作为参数,在Schema与 Filter确定的前提下,索引列表是一样的,由于索引是个比较占用内存的对象,所以在这里视图是逻辑上持有各自的索引 ,实际上是通过引用计数的方式引用索引的,这样便可以在创建多个Schema、Filter一样的视图时节省内存。这两个参 数都是可选的,如果两个参数都不指定的时候,将会创建一个与该表默认视图持有相同索引的视图。每个不同的视图实例 内部都有自己的分组、选中状态,即便持有着相同的索引列表间的视图操作也是互不干扰的。

Group,即分组,一个视图可以创建并持有多个分组,每个分组以字符串ID区分,形式为【by-列号列名分组特征 后缀@随机数】。现时分组支持以下几种:

  • GroupByAll,即将整个视图作为一个分组,ID形式为【by-*@随机数】;
  • GroupByIdentity,即按值的特征分组,常用于字符串类型的数据列,ID形式为【by-列号_列名@随机数】;
  • GroupByDivides,即按数值范围分组,只能用于数值类型的数据列,ID形式为【by-列号列名数值范围@随机数】;
  • GroupByTimestamp,即按时间跨度分组,只能用于时间戳类型的数据列,ID形式为【by-列号列名时间戳精度@随机 数】。

除了可以从视图创建分组外,每个分组自身也是可以创建子分组的,即多重分组,例如数据表中有“省”、“市”、“区”之类 的列,则可以利用类似以下伪代码进行多重分组,以达到从数据表中获取树形的结算结果:

view.createGroupByIdentity("省").createGroupByIdentity("市").createGroupByIdentity("区")
// 可以继续创建一些Reduce求值
view.results // 这个结果将会有树形结构

Reduce,即求值,每个分组都可以创建各种不同的求值,求值的逻辑可以通过扩展Reducer接口实现,也可以直接 利用power.report.arhat.core.reduce包中的各种已实现的通用求值逻辑。不同的求值中间变量/结果以名字进行区 分,名字的格式为【求值类名(列号列名)】。求值类如果是在power.report.arhat.core.reduce包中的,则会忽略 其包名;求值类如果是RowReducer的实现(例如Count),`列号列名则会变成*。当然,在不同的业务中,求值结 果可能有不同的命名,你也可以在创建求值时给结果一个别名(alias)。同一个求值类和数据列的求值结果肯定是一样 的,所以即使你命名不同的别名,也不会重复计算。有些求值是通过组合别的求值实现的,例如Mean是通过组合Sum和 Count实现的,这类型CompositedReducer`在求值时会带出各种中间变量,这些变量可能是你没要求运算核心创建的, 但这不会影响你的使用,你甚至可以继续对这些中间变量放心地命名别名。在获取(多重)分组的求值结果时,求值类还 负责了各自逻辑下的结果汇总,这对于检验自身求值结果、树状结构汇总都是一个十分方便的特性。

所以总的来说,arhat-core的核心抽象模型就是定义了从ArhatContext到Table、View、Group、Reduce的一层层的一 对多关系,其中Group还可以与其他子Group有着递归的一对多关系。所以每个对象调用destroy方法销毁自身时,会将其 子对象一并销毁。


五 · RESTful API详解

基本的HTTP状态码、URI、内容格式约定

本服务常见的HTTP状态码:

  • 200 OK:请求正常;
  • 201 Created:请求创建对象成功;
  • 204 No Content:删除某个对象成功后的状态;
  • 400 Bad Request:请求格式错误;
  • 404 Not Found:因资源超时被回收或被客户端主动要求删除,资源不存在;
  • 500 Internal Server Error:服务器报错了,而且无法容错恢复。

URI总体上采用【/名词复数/对象标识】的设计。当GET【/名词复数】这一层的URI时,一般都会返回该层下面的直接 子对象的所有ID或者名字列表,对于数据本身,则会返回数据内容列表;当POST【/名词复数】这一层的URI时,则会 创建下一层的子对象;当DELETE【/名词复数/对象标识】这一层的URI是,则会删除该对象。URI的末端斜杠为可选, 带上与不带上时都具有相同的语义,即:

GET /jubo/tables/152602@9267
等同于
GET /jubo/tables/152602@9267/

POST /jubo/tables
等同于
POST /jubo/tables/

下文皆以不带末端斜杠的方式描述API。为了方面描述,中间还会省略掉HTTP报文头的描述。

请求与相应内容格式方面,除了错误状态的内容是text/plain格式外,其他全部为application/json,其中根据规 范,application/json的编码已经限制为只能是UTF-8,但由于部分HTTP客户端的实现有所不同,也可以强制使用 application/json; charset=UTF-8以保证内容不会乱码。


查看版本、代号

Arhat Service 的根目录并不提供任何服务,每个版本都会以arhat-core工程的代号(CODE_NAME)作为Web服务的起点 ,当访问该起点时,便可得知该服务的上下文名字、版本号、代号、与构建时间:

GET /:CODE_NAME HTTP/1.1

当前的代号为jubo,即:

GET /jubo HTTP/1.1

返回:

HTTP/1.1 200 OK

{
  "context_name": "_DEFAULT_",
  "version": "0.0.1",
  "code_name": "jubo",
  "build_time": "2015-09-17T06:48:34.404Z"
}

Table(表) API

获取数据表实例ID列表:

GET /jubo/tables HTTP/1.1

返回:

HTTP/1.1 200 OK

["150732@f3ac", "150731@3664", "150730@bfad", "152602@9267"]

创建数据表实例:

POST /jubo/tables HTTP/1.1
Conten-Type: application/json;charset=UTF-8
{
    "uri": "file://D:/SourceCode/BitBucket/h8/src/main/resources/chengbaoshangkoufen.csv"
}

返回该数据表下的默认视图_DEFAULT_的状态:

HTTP/1.1 201 Created

{
    "tableId": "152602@9267", // 新创建的表ID
    "viewId": "_DEFAULT_",
    "selected": 1355,
    "size": 1355
}

其中,创建表的请求JSON格式如下:

属性名 类型 必要 说明
uri String 地址,可以是“file://”开头的服务器本地地址,也可以是各种数据库的访问地址。如果是以“arhat://”开头的URI,则可以根据配置文件中对应的table-source配置创建数据表
trim Boolean 当指定为true时,会将所有单元格的值都进行一次trim,默认为true
emptyStrAsNull Boolean 当指定为true时,会视所有空字符的值为null,默认是true
strMaxLen Int 用于指定字符串单元格内容的最大长度,默认是256
doNotUseFloat32 Boolean 只对CSV与Excel表格有效,当指定为true时,将不会使用32位浮点数储存数字,默认是true
hasHeader Boolean 只对CSV与Excel表格有效,当指定为true时,会视第一行数据为表头,默认是true
sheet Int 当URI地址为一个Excel文件时,可以使用它指定打开第几个表,默认是【第0个】
username String 当URI地址为JDBC/mongodb/GridFS访问地址时,需要指定登录用户名
password String 当URI地址为JDBC/mongodb/GridFS访问地址时,需要指定登录密码
query String 当URI地址为JDBC/mongodb/GridFS访问地址时,需要指定查询语句
bucket String 当URI地址为GridFS访问地址时,需要指定数据桶
collection String 当URI地址为mongodb访问地址时,需要指定数据集
timeout Int 当创建的数据源是JDBC或者MongoDB的表时,可是指定多长时间内不须重复创建相同查询条件的数据表,单位为分钟
sizeHint Int 当创建的数据源是JDBC的表时,可指定相应查询可能对应的行大小,以便提升加载性能,该值在不指定时为100000

各种情况下的样例:

// JDBC select 语句,以 MySQL 为例:
{
    "uri": "jdbc:mysql://10.10.38.173:3306/database0",
    "username": "root",
    "password": "123",
    "query": "SELECT * FROM table0"
}

// JDBC 存储过程,以 Oracle 为例:
{
    "uri": "jdbc:oracle:thin:@10.10.38.173:1521:hyq",
    "username": "scott",
    "password": "tiger",
    "query": "{call TESTA(100,TestOne)}"
}

// Local File,以 Excel 为例(以安装目录进行相对定位):
{
    "uri": "./superstore.xlsx",
    "sheet": 2
}

// GridFS File,以 CSV 为例:
{
    "uri": "mongodb://10.10.38.173:27017/database0",
    "username": "admin",
    "password": "123",
    "bucket": "fs",
    "query": "51299e0881b8e10011000001"
}

// MongoDB collection:
{
    "uri": "mongodb://10.10.38.173:27017/database0",
    "username": "admin",
    "password": "123",
    "collection": "collection0",
    "query": "{ 'age': { '$lt': 30, '$gte': 20 } }"
}

需要注意,如果数据源来自于文件(含本地文件与GridFS远程文件),Arhat服务会缓存该数据表,即不会重复创建具有 相同MD5文件摘要的数据表。如果数据源来自于JDBC或者MongoDB的表,则需要在创建的时候指定timeout属性才可以在指 定时间内缓存已加载的表。如果对应的数据表长时间没有请求访问,则在心跳超时后会回收该表,不管该表是否被缓存下 来。当使用批量创建API时,如果指定的表或者视图已经被缓存,则其后续groups创建会被忽略。


获取数据表的当前运算结果状态:

GET /jubo/tables/152602@9267 HTTP/1.1

返回:

HTTP/1.1 200 OK

{
  "tableId": "152602@9267",
  "viewId": "_DEFAULT_",
  "selected": 1355, // 默认选中所有行
  "size": 1355
}

实际上,该调用返回的是该数据表的默认视图_DEFAULT_的运算结果,所有在数据表上直接的运算调用都被视为了对其 默认视图的操作。运算结果中还含有groups属性用来存放分组运算的结果,下面介绍分组API时会详细说明。


删除数据表:

DELETE /jubo/tables/152602@9267 HTTP/1.1

返回:

HTTP/1.1 204 No Content

删除数据表时会同时将其下辖的所有视图也同时删除。


View(视图) API

获取某数据表下的所有视图实例ID列表:

GET /jubo/tables/152602@9267/views HTTP/1.1

返回:

HTTP/1.1 200 OK

["153340@e908", "153345@3c53", "_DEFAULT_"]

在某数据表下创建视图:

POST /jubo/tables/152602@9267/views HTTP/1.1
Conten-Type: application/json;charset=UTF-8
{
    "alias": "testView",
    "projection": {
        "hide": [8],
        "create": {
            "Voltage": "$(6) == null || $(6) == '' ? '????' : $(6).substring(0, $(6).indexOf('V'))"
        }
    },
    "filter": "$(17) < 1.0"
}

返回:

HTTP/1.1 201 Created

{
  "tableId": "152602@9267",
  "viewId": "testView",  // 带着别名创建的视图ID就是所给出的别名
  "selected": 1349,
  "size": 1349
}

其中,创建视图的请求JSON格式如下:

属性名 类型 必要 说明
alias String 指定视图的别名,不能与已有的视图ID重复
projection Object 指定数据列的映射
projection.hide Array 指定要隐藏的数据列,可以是列的编号(从0开始、以原数据表的顺序为准),也可是列名(如果有重复的列名,则只隐藏首个匹配列名的数据列)
projection.create Object 指定要生成的数据列的名字和相应的JavaScript求值表达式
filter JS脚本 指定过滤数据所用的JavaScript布尔表达式

需要注意JS脚本的使用,求值/布尔表达式可以使用$(colNum)$(colName)两种方式引用列的实际值。使用列名匹 配时以第一个匹配的列为准。filter的JS脚本如果使用列号的方式引用,且projection时有隐藏/增加列的操作时,列号 以做完映射后的列号为准,即有projection时必然是先做映射再做过滤的。

如果有些情况下需要创建于默认视图_DEFAULT_内容一样、但运算状态不一样的视图,可以在创建视图时传入{}。同 理,如果创建视图没有指定alias属性的话,相同的projection与filter内容的视图创建是允许的,这样就可以拥有两个 内容一样但是运算状态不一样的视图。


获取某数据表指定视图的当前运算结果状态:

GET /jubo/tables/152602@9267/views/_DEFAULT_ HTTP/1.1

当视图ID为_DEFAULT_时,即等同于:

GET /jubo/tables/152602@9267 HTTP/1.1

返回:

HTTP/1.1 200 OK

{
  "tableId": "152602@9267",
  "viewId": "_DEFAULT_",
  "selected": 1355,
  "size": 1355
}

删除视图:

DELETE /jubo/tables/152602@9267/views/153345@3c53 HTTP/1.1

返回:

HTTP/1.1 204 No Content

删除视图后,其下辖的所有分组信息、运算状态也会一并删除。

注意:不允许删除_DEFAULT_视图。


获取视图的所有数据列/字段信息:

GET /jubo/tables/152602@9267/views/testView/fields HTTP/1.1

当视图ID为_DEFAULT_时,即等同于:

GET /jubo/tables/152602@9267/fields HTTP/1.1

返回:

HTTP/1.1 200 OK

[{
  "nullable": false,
  "name": "分省公司",
  "colNum": 0,     // 列号,从0开始
  "valuesSize": 8, // valuesSize是指这个列有多少种不同的值,包含null值
  "range": [2, 3], // 对于TEXT类型,range是指非空值的长度范围,两端都是闭区间
  "type": "TEXT",
  "unique": false
}, {
    ...中略...
}, {
  "nullable": false,
  "name": "检查日期",
  "precision": "DAY", // 对于TIMESTAMP类型,precision是指两两时间戳之间最可能的间隔精度
  "colNum": 12,
  "valuesSize": 66,
  "range": [1372608000000, 1379001600000], // 以毫秒为单位,两端都是闭区间
  "type": "TIMESTAMP",
  "unique": false
}, {
    ...中略...
}, {
  "nullable": false,
  "name": "处罚分数",
  "precision": "DOUBLE", // 对于NUMBER类型,precision是指整列数据最可能使用哪种数据精度记录
  "colNum": 17,
  "valuesSize": 8,
  "range": [0.0, 0.5],   // 非空的实际值的范围,两端都是闭区间
  "type": "NUMBER",
  "unique": false
}, {
  "nullable": false,
  "name": "Voltage",  // 对于生成出来的虚拟数据列,在进行实际运算的时候不会进行任何区别对待
  "colNum": 18,
  "valuesSize": 9,
  "range": [3, 5],
  "type": "TEXT",
  "unique": false
}]

关于数据列/字段类型的说明:

  • TEXT:字符串类型,如果某列数据为TEXT类型时且valuesSize是个十分小(例如个位数)的值时,该列数据可能适合 用于做枚举、饼图。
  • NUMBER:数字类型,但具体是哪种数字类型,还需要依赖precision的提示,最常见的会是INT、DOUBLE。
  • TIMESTAMP:时间类型,它本质上也是数字类型,所以一些针对数字类型的操作在它上面也可以使用,但是它的precision 提示是用来指示两两时间戳之间最可能的间隔精度,最常见的可能是DAY。
  • BOOLEAN:布尔类型,只能允许truefalsenull三种值。
  • 如果一个数据列有各种混合的值,系统会尽可能找出最大可能的类型进行转换,其他不符合的类型会变成null,如果 各种类型比例都差不多,最大的可能性会转换成TEXT类型。

关于时间戳精度的说明:

  • MILLI:毫秒
  • SECOND:秒
  • MINUTE:分
  • HOUR:小时
  • DAY:日
  • WEEK:周
  • MONTH:月
  • QUARTER:季
  • YEAR:年
  • DECADE:年代
  • CENTURY:世纪

关于数值类型精度的说明:

  • BYTE:字节,8位有符号整数
  • SHORT:短整型,16位有符号整数
  • CHAR:字符,16位无符号整数
  • INT:整型,32位有符号整数
  • LONG:长整型,64位有符号整数
  • BIGINT:大整型,无限制整数
  • FLOAT:单精度浮点数,32位有符号浮点数
  • DOUBLE:双精度浮点数,64位有符号浮点数
  • BIGDECIMAL:无限制数字

获取视图的指定数据列/字段信息:

GET /jubo/tables/152602@9267/views/testView/fields/分省公司 HTTP/1.1

也可以使用列号:

GET /jubo/tables/152602@9267/views/testView/fields/0 HTTP/1.1

当视图ID为_DEFAULT_时,即等同于:

GET /jubo/tables/152602@9267/fields/0 HTTP/1.1

返回:

HTTP/1.1 200 OK

{
  "nullable": false,
  "name": "分省公司",
  "colNum": 0,
  "valuesSize": 8,
  "range": [2, 3],
  "type": "TEXT",
  "unique": false
}

获取视图所选中的数据:

GET /jubo/tables/152602@9267/views/testView/selections HTTP/1.1

当视图ID为_DEFAULT_时,即等同于:

GET /jubo/tables/152602@9267/selections HTTP/1.1

返回:

HTTP/1.1 200 OK

[["超高压", "超高压", "分子公司", "主网", ...中略...], ...后略...]

另外,还可以附加URL参数以便获取各种形式的选中数据:

参数名 类型 必要 说明
orderBy String 以哪个数据列/字段名/序号进行排序
reverse Boolean 是否反向排序,默认为false
skip int 跳过多少行,默认为0,小于0的值将会被忽略
limit int 限制输出多少行,默认为20,小于等于0的值将会被忽略

重置视图的选中状态:

PUT /jubo/tables/152602@9267/views/testView/reset HTTP/1.1

当视图ID为_DEFAULT_时,即等同于:

PUT /jubo/tables/152602@9267/reset HTTP/1.1

返回:

HTTP/1.1 200 OK

{
  "tableId": "152602@9267",
  "viewId": "testView",
  "selected": 1349,  // 重置后选中的数量会变回size的值
  "size": 1349
}

发送心跳信息以维持视图:

PUT /jubo/tables/152602@9267/views/testView/heartbeat HTTP/1.1

当视图ID为_DEFAULT_时,即等同于:

PUT /jubo/tables/152602@9267/heartbeat HTTP/1.1

返回:

HTTP/1.1 204 No Content

每个视图默认最长只能闲置5分钟,如果需要长时间使用视图,请在限时前发送心跳信息到服务器。如果一个数据表的 所有视图都限制超过5分钟被销毁,该数据表也会被同时销毁。

注意,一般对视图的各种调用都会被视作是心跳等效的,所以只需要在闲置、不再调用任何API的情况下调用心跳信息API。


以某个时间分组作为时间轴播放/运算各个分组的结果:

POST /jubo/tables/152602@9267/views/testView/play HTTP/1.1
Conten-Type: application/json;charset=UTF-8

当视图ID为_DEFAULT_时,即等同于:

POST /jubo/tables/152602@9267/play HTTP/1.1
Conten-Type: application/json;charset=UTF-8
{
    "groupId": "by-12_检查日期_DAY@87ab",
    "from": [1372636800000, 1372896000000], // (2013-07-01, 2013-07-04)
    "to": [1377561600000, 1377907200000]    // (2013-08-27, 2013-08-31)
}

返回:

HTTP/1.1 200 OK

{
  "viewId": "testView",
  "size": 1349,
  "frames": [{
    "range": ["1372608000000", "1372867200000"],
    "selected": 239,
    "groups": {
      "by-12_检查日期_DAY@87ab": {
        "values": {
          "Mean(17_处罚分数)": 0.26443514644351446,
          "总处罚分数": 63.19999999999996,
          "Count(*)": 239.0
        },
        "children": [{
          "key": "1372608000000",
          "values": {
            "Mean(17_处罚分数)": 0.2567901234567899,
            "总处罚分数": 41.599999999999966,
            "Count(*)": 162.0
          },
          "selected": true
        }, {
          "key": "1372694400000",
          "values": {
            "Mean(17_处罚分数)": 0.2733333333333333,
            "总处罚分数": 8.2,
            "Count(*)": 30.0
          },
          "selected": true
        }, {
          "key": "1372780800000",
          "values": {
            "Mean(17_处罚分数)": 0.2761904761904762,
            "总处罚分数": 5.8,
            "Count(*)": 21.0
          },
          "selected": true
        }, {
          "key": "1372867200000",
          "values": {
            "Mean(17_处罚分数)": 0.2923076923076923,
            "总处罚分数": 7.6000000000000005,
            "Count(*)": 26.0
          },
          "selected": true
        }, {
          ...略...
        }]
      }
    }
  }, {
    "range": ["1372780800000", "1373040000000"],
    "selected": 63,
    "groups": {
      "by-12_检查日期_DAY@87ab": {
        "values": {
          "Mean(17_处罚分数)": 0.27301587301587305,
          "总处罚分数": 17.200000000000003,
          "Count(*)": 63.0
        },
        "children": [{
          ...略...
        }, {
          "key": "1372780800000",
          "values": {
            "Mean(17_处罚分数)": 0.2761904761904762,
            "总处罚分数": 5.8,
            "Count(*)": 21.0
          },
          "selected": true
        }, {
          "key": "1372867200000",
          "values": {
            "Mean(17_处罚分数)": 0.2923076923076923,
            "总处罚分数": 7.6000000000000005,
            "Count(*)": 26.0
          },
          "selected": true
        }, {
          "key": "1372953600000",
          "values": {
            "Mean(17_处罚分数)": 0.23750000000000004,
            "总处罚分数": 3.8000000000000007,
            "Count(*)": 16.0
          },
          "selected": true
        }, {
          "key": "1373040000000",
          "values": {
            "Mean(17_处罚分数)": null,
            "总处罚分数": 0.0,
            "Count(*)": 0.0
          },
          "selected": true
        }, {
          ...略...
        }]
      }
    }
  }, {
    ...略...
  }, {
    "range": ["1377532800000", "1377878400000"],
    "selected": 77,
    "groups": {
      "by-12_检查日期_DAY@87ab": {
        "values": {
          "Mean(17_处罚分数)": 0.2688311688311688,
          "总处罚分数": 20.7,
          "Count(*)": 77.0
        },
        "children": [{
          ...略...
        }, {
          "key": "1377532800000",
          "values": {
            "Mean(17_处罚分数)": 0.2888888888888889,
            "总处罚分数": 7.800000000000001,
            "Count(*)": 27.0
          },
          "selected": true
        }, {
          "key": "1377619200000",
          "values": {
            "Mean(17_处罚分数)": 0.25925925925925924,
            "总处罚分数": 6.999999999999999,
            "Count(*)": 27.0
          },
          "selected": true
        }, {
          "key": "1377705600000",
          "values": {
            "Mean(17_处罚分数)": 0.25833333333333325,
            "总处罚分数": 3.0999999999999988,
            "Count(*)": 12.0
          },
          "selected": true
        }, {
          "key": "1377792000000",
          "values": {
            "Mean(17_处罚分数)": 0.2545454545454545,
            "总处罚分数": 2.8,
            "Count(*)": 11.0
          },
          "selected": true
        }, {
          "key": "1377878400000",
          "values": {
            "Mean(17_处罚分数)": null,
            "总处罚分数": 0.0,
            "Count(*)": 0.0
          },
          "selected": true
        }, {
          ...略....
        }]
      }
    }
  }],
  "to": [1377561600000, 1377907200000],
  "from": [1372636800000, 1372896000000],
  "tableId": "152602@9267"
}

其中,play请求的JSON格式如下:

属性名 类型 必要 说明
groupId String 作为时间轴用的分组ID
from (long, long) 从哪段时间戳开始
to (long, long) 到哪段时间戳为止
frameSize int 该次请求一次性运算出多少帧结果,默认是24

Group(分组) API

获取视图下辖的所有分组:

GET /jubo/tables/152602@9267/views/testView/groups HTTP/1.1

当视图ID为_DEFAULT_时,即等同于:

GET /jubo/tables/152602@9267/groups HTTP/1.1

返回:

HTTP/1.1 200 OK

["by-17_处罚分数_0.2,0.6,0.8@882c", "by-11_检查单位@c02b", "by-0_分省公司@720e", "by-12_检查日期_DAY@d1b8"]

注意,在多重分组的情况下,这样只可以获取最顶层分组的所有ID,如果要获取子分组的ID列表,可以如下请求:

GET /jubo/tables/152602@9267/views/testView/groups/by-0_分省公司@720e/groups HTTP/1.1

当视图ID为_DEFAULT_时,即等同于:

GET /jubo/tables/152602@9267/groups/by-0_分省公司@720e/groups HTTP/1.1

返回:

HTTP/1.1 200 OK

["by-1_检查单位@6d19"]

在视图下创建分组:

POST /jubo/tables/152602@9267/views/testView/groups HTTP/1.1
Conten-Type: application/json;charset=UTF-8

当视图ID为_DEFAULT_时,即等同于:

POST /jubo/tables/152602@9267/groups HTTP/1.1
Conten-Type: application/json;charset=UTF-8
{
    "type": "ALL"
}
属性名 类型 说明
type String 合法值为:ALL、IDENTITY、TIMESTAMP、DIVIDES,如果是多重分组的情况下不需要指定该属性
field String 对于ALL类型,并不需要使用该属性,对于其他类型都是需要指定分组所依据的数据列/字段名/序号
precision String 对于TIMESTAMP类型,一定要指定该属性,具体值见【关于时间戳精度的说明】
divides Array 对于DIVIDES类型,一定要指定该属性,数组中的元素为有序的数字,例如[1,2,3]是代表按(-∞,1)、[1,2)、[2,3)、[3,+∞)分组,生成key的规则也是这个
keys Array 对于DIVIDES类型,可选择指定,数组中的元素为字符串, 例如["<1","1~2","2~3",null],如果某个元素为null或者"",则按照原有的生成规则为分组生成key值,例如本例最后的一个key指定了null,实际上会变成“[3,+∞)”
orderBy String 让分组按照哪个reduce的结果值进行排序,reduce的名字见下面文档详述
top int 只显示且只运算/汇总按小到大排倒序后的前n个分组
bottom int 只显示且只运算/汇总按小到大排序后的前n个分组,不能与top同时使用
ignoreWTF boolean 是否忽略未知键值“(?)”的分组,默认为false
distinct Array 指定根据那些数据列进行去重过滤,数组元素可以是列名或者字符串形式的列号
reduces Array 指定创建哪些reduce运算,详细格式见下文
groups Array 创建多重分组,用于创建有从属关系的分组,数组中的元素为单个创建任意分组格式的对象,例如[{type:"IDENTITY",field:"省"},{type:"IDENTITY",field:"市"},{type:"IDENTITY",field:"区"}]

实例:

// >>> 按处罚分数分组:
{
    "type": "DIVIDES",
    "field": "17", // 使用数据列号,注意是需要使用字符串的形式
    "divides": [0.2, 0.6, 0.8],
    "keys": ["not too bad", "some problem", "bad", "epic fail"]
}
// <<< 返回:
{
  "viewId": "testView",
  "size": 1349,
  "selected": 1349,
  "groups": {
    "by-17_处罚分数_0.2,0.6,0.8@882c": {
      "selected": true,
      "children": [{
        "key": "not too bad",
        "selected": true
      }, {
        "key": "some problem",
        "selected": true
      }, {
        "key": "bad",
        "selected": true
      }, {
        "key": "epic fail",
        "selected": true
      }]
    }
  },
  "groupId": "by-17_处罚分数_0.2,0.6,0.8@882c", // 新创建的组返回时会带上该组的ID
  "tableId": "152602@9267"
}

// >>> 按检查日期分组:
{
    "type": "TIMESTAMP",
    "field": "12",
    "precision": "DAY",
    "orderBy": "Count(*)"
}
// <<< 返回:
{
  "viewId": "testView",
  "size": 1349,
  "selected": 1349,
  "groups": {
    "by-17_处罚分数_0.2,0.6,0.8@882c": {  // DIVIDES默认是按分区的先后次序排序的
      "selected": true,
      "children": [{
        "key": "not too bad",
        "selected": true
      }, {
        "key": "some problem",
        "selected": true
      }, {
        "key": "bad",
        "selected": true
      }, {
        "key": "epic fail",
        "selected": true
      }]
    },
    "by-12_检查日期_DAY@d1b8": { // 按Count计数值排序,但是该Reduce尚未创建,
      "selected": true,         // 所以此时是按照Key值的字母顺序排序的。
      "children": [{
        "key": "1372608000000",
        "selected": true
      }, {
        "key": "1372694400000",
        "selected": true
      }, {
        ...中略...
      }, {
        "key": "1378915200000",
        "selected": true
      }, {
        "key": "1379001600000",
        "selected": true
      }]
    }
  },
  "groupId": "by-12_检查日期_DAY@d1b8",
  "tableId": "152602@9267"
}

// >>> 按检查单位分组:
{
    "type": "IDENTITY",
    "field": "11"
}
// <<< 返回:
{
  "viewId": "testView",
  "size": 1349,
  "selected": 1349,
  "groups": {
    "by-17_处罚分数_0.2,0.6,0.8@882c": {
      "selected": true,
      "children": [{
        "key": "not too bad",
        "selected": true
      }, {
        "key": "some problem",
        "selected": true
      }, {
        "key": "bad",
        "selected": true
      }, {
        "key": "epic fail",
        "selected": true
      }]
    },
    "by-12_检查日期_DAY@d1b8": {
      "selected": true,
      "children": [{
        "key": "1372608000000",
        "selected": true
      }, {
        "key": "1372694400000",
        "selected": true
      }, {
        ...中略...
      }, {
        "key": "1378915200000",
        "selected": true
      }, {
        "key": "1379001600000",
        "selected": true
      }]
    },
    "by-11_检查单位@c02b": {
      "selected": true,
      "children": [{
        "key": "万宁局",
        "selected": true
      }, {
        ...中略...
      }, {
        "key": "龙门局",
        "selected": true
      }]
    }
  },
  "groupId": "by-11_检查单位@c02b",
  "tableId": "152602@9267"
}

// >>> 按省市进行多重分组:
{
    "groups": [{
        "type": "IDENTITY",
        "field": "分省公司"
    },{
        "type": "IDENTITY",
        "field": "1"
    }]
}
// <<< 返回:
{
  "viewId": "testView",
  "groupIds": ["by-0_分省公司@720e", "by-1_检查单位@6d19"], // 创建多重分组会一次性返回多个分组ID
  "size": 1349,
  "selected": 1349,
  "groups": {
    "by-0_分省公司@720e": { // 可以看出有多重的父子关系
      "selected": true,
      "children": [{
        "key": "云南",
        "selected": true,
        "groups": {
          "by-1_检查单位@6d19": {
            "selected": true,
            "children": [{
              "key": "云南::云南", // 父子联合组成的key,用“::”进行链接
              "selected": true
            }, {
              ...中略...
            }, {
              "key": "云南::西双版纳",
              "selected": true
            }]
          }
        }
      }, {
        "key": "广东",
        "selected": true,
        "groups": {
          "by-1_检查单位@6d19": {
            "selected": true,
            "children": [{
              "key": "广东::东莞",
              "selected": true
            }, {
              ...中略...
            }, {
              "key": "广东::韶关",
              "selected": true
            }]
          }
        }
      }, {
        "key": "广州",
        "selected": true,
        "groups": {
          "by-1_检查单位@6d19": {
            "selected": true,
            "children": [{
              "key": "广州::广州",
              "selected": true
            }]
          }
        }
      }, {
        ...中略...
      }]
    },
    "by-17_处罚分数_0.2,0.6,0.8@882c": {
      "selected": true,
      "children": [{
        "key": "not too bad",
        "selected": true
      }, {
        "key": "some problem",
        "selected": true
      }, {
        "key": "bad",
        "selected": true
      }, {
        "key": "epic fail",
        "selected": true
      }]
    },
    "by-12_检查日期_DAY@d1b8": {
      "selected": true,
      "children": [{
        "key": "1372608000000",
        "selected": true
      }, {
        "key": "1372694400000",
        "selected": true
      }, {
        ...中略...
      }, {
        "key": "1378915200000",
        "selected": true
      }, {
        "key": "1379001600000",
        "selected": true
      }]
    },
    "by-11_检查单位@c02b": {
      "selected": true,
      "children": [{
        "key": "万宁局",
        "selected": true
      }, {
        ...中略...
      }, {
        "key": "龙门局",
        "selected": true
      }]
    }
  },
  "tableId": "152602@9267"
}

// >>> 根据高度范围分组之余,还需要根据id列进行去重过滤:
{
    "type": "DIVIDES",
    "field": "height",
    "divides": [160, 170, 180],
    "distinct": ["id"],
    "reduces": [{
        "type": "Mean",
        "field": "3"
    }]
}
// <<< 返回:
{
  "viewId": "_DEFAULT_",
  "size": 9,
  "selected": 9,
  "groups": {
    "by-2_date_MONTH@60c5": {
      "values": {
        "Count(*)": 9.0
      },
      "selected": true,
      "children": [{
        "key": "1435680000000",
        "values": {
          "Count(*)": 3.0
        },
        "selected": true
      }, {
        "key": "1438358400000",
        "values": {
          "Count(*)": 3.0
        },
        "selected": true
      }, {
        "key": "1441036800000",
        "values": {
          "Count(*)": 3.0
        },
        "selected": true
      }]
    },
    "by-1_height_160,170,180@DISTINCT(0)32b8": {
      "values": {
        "Sum(3_point)": 300.0,
        "Count(*)": 4.0, // 去重后的个数小于9
        "Mean(3_point)": 75.0
      },
      "selected": true,
      "children": [{
        "key": "(-∞, 160)",
        "values": {
          "Sum(3_point)": 70.0,
          "Count(*)": 1.0,
          "Mean(3_point)": 70.0
        },
        "selected": true
      }, {
        "key": "[160, 170)",
        "values": {
          "Sum(3_point)": 90.0,
          "Count(*)": 1.0,
          "Mean(3_point)": 90.0
        },
        "selected": true
      }, {
        "key": "[170, 180)",
        "values": {
          "Sum(3_point)": 80.0,
          "Count(*)": 1.0,
          "Mean(3_point)": 80.0
        },
        "selected": true
      }, {
        "key": "[180, +∞)",
        "values": {
          "Sum(3_point)": 60.0,
          "Count(*)": 1.0,
          "Mean(3_point)": 60.0
        },
        "selected": true
      }]
    }
  },
  "tableId": "122706@1a31"
}

另外,如果在创建多重分组时,在最外层若指定了orderBy、top/bottom、reduces的值,这些值会隐含地传递到该次创 建的所有父子分组,若分组中自身又指定了这些值,也是可以生效的,如两者有冲突,则以最外层的为准。只有外层的值 可以会向下传递到所有父子分组。

多重分组的创建,除了上面的例子那样批量式地创建,也可以视情况逐层创建,下面的例子是与上面的等价的:

POST /jubo/tables/152602@9267/views/testView/groups HTTP/1.1
Conten-Type: application/json;charset=UTF-8
{
    "type": "IDENTITY",
    "field": "分省公司"
}
POST /jubo/tables/152602@9267/views/testView/groups/by-0_分省公司@720e/groups HTTP/1.1
Conten-Type: application/json;charset=UTF-8
{
    "type": "IDENTITY",
    "field": "1"
}

顺带一提,后文的分组API在父子分组的情况下,也是同样适用的,即分组API的URI具有以下的递归规则:

/jubo/tables/:表ID[/views/:视图ID]/groups/:分组ID[/groups/:子分组ID[/groups/:子分组ID[...略...]]]

获取指定分组状态:

GET /jubo/tables/152602@9267/views/testView/groups/by-0_分省公司@720e HTTP/1.1

当视图ID为_DEFAULT_时,即等同于:

GET /jubo/tables/152602@9267/groups/by-0_分省公司@720e HTTP/1.1

返回:

{
  "id": "by-0_分省公司@720e",
  "size": 8, // 标识该分组有多少个Key值
  "groups": ["by-1_检查单位@6d19"] // 多重分组时会在这里显示出其自分组的所有ID
}

删除指定分组:

DELETE /jubo/tables/152602@9267/views/testView/groups/by-0_分省公司@720e HTTP/1.1

当视图ID为_DEFAULT_时,即等同于:

DELETE /jubo/tables/152602@9267/groups/by-0_分省公司@720e HTTP/1.1

返回删除分组后的视图状态:

HTTP/1.1 200 OK

{
  "viewId": "testView",
  "size": 1349,
  "selected": 1349,
  "groups": {
    "by-17_处罚分数_0.2,0.6,0.8@882c": {
      "selected": true,
      "children": [{
        "key": "not too bad",
        "selected": true
      }, {
        "key": "some problem",
        "selected": true
      }, {
        "key": "bad",
        "selected": true
      }, {
        "key": "epic fail",
        "selected": true
      }]
    },
    "by-11_检查单位@c02b": {
      "selected": true,
      "children": [{
        "key": "万宁局",
        "selected": true
      }, {
        ...中略...
      }, {
        "key": "龙门局",
        "selected": true
      }]
    },
    "by-12_检查日期_DAY@d1b8": {
      "selected": true,
      "children": [{
        "key": "1372608000000",
        "selected": true
      }, {
        ...中略...
      }, {
        "key": "1379001600000",
        "selected": true
      }]
    }
  },
  "tableId": "152602@9267"
}

注意,删除分组后,其子分组、Reduce求值运算都会被同时删除。


选中分组下的指定值:

POST /jubo/tables/152602@9267/views/testView/groups/by-17_处罚分数_0.2,0.6,0.8@882c/selectExact HTTP/1.1
Conten-Type: application/json;charset=UTF-8
["some problem"] // 元素皆为字符串,且必须是分组Key值,可以有若干个值

返回选择后的视图状态:

HTTP/1.1 200 OK

{
  "viewId": "testView",
  "size": 1349,
  "selected": 1159, // 选中的数据行数变少
  "groups": {
    "by-17_处罚分数_0.2,0.6,0.8@882c": {
      "children": [{
        "key": "not too bad",
        "selected": false
      }, {
        "key": "some problem",
        "selected": true  // 只有选中的组selected为true
      }, {
        "key": "bad",
        "selected": false // selected为false的将不参与Reduce求值与汇总
      }, {
        "key": "epic fail",
        "selected": false
      }]
    },
    "by-11_检查单位@c02b": {
      ...后略...
    }
  }
}

选中分组下的指定范围:

POST /jubo/tables/152602@9267/views/testView/groups/by-12_检查日期_DAY@d1b8/selectRange HTTP/1.1
Conten-Type: application/json;charset=UTF-8
["1372608000000", "1377878400000"] // 元素为两个字符串,且必须是分组Key的值,按原始顺序排序

返回选择后的视图状态:

HTTP/1.1 200 OK

{
  "viewId": "testView",
  "size": 1349,
  "selected": 1127, // 选中的数据行数变少
  "groups": {
    "by-17_处罚分数_0.2,0.6,0.8@882c": {
      "children": [{
        "key": "not too bad",
        "selected": false
      }, {
        "key": "some problem",
        "selected": true
      }, {
        "key": "bad",
        "selected": false
      }, {
        "key": "epic fail",
        "selected": false
      }]
    },
    "by-11_检查单位@c02b": {
      ...中略...
    },
    "by-12_检查日期_DAY@d1b8": {
      "children": [{
        "key": "1372608000000",
        "selected": true   // 选中
      }, {
        ...中略...
      }, {
        "key": "1377878400000",
        "selected": true   // 选中
      }, {
        "key": "1377964800000",
        "selected": false  // 没选中
      }, {
        ...后略...
      }]
    }
  },
  "tableId": "152602@9267"
}

选中分组下的所有值:

PUT /jubo/tables/152602@9267/views/testView/groups/by-17_处罚分数_0.2,0.6,0.8@882c/selectAll HTTP/1.1

且:

PUT /jubo/tables/152602@9267/views/testView/groups/by-12_检查日期_DAY@d1b8/selectAll HTTP/1.1

返回选择后的视图状态:

HTTP/1.1 200 OK

{
  "viewId": "testView",
  "size": 1349,
  "selected": 1349, // 选中的数据行数又恢复回原始状态
  "groups": {
    ...后略...
  }
}

Reduce(求值) API

获取分组下辖的所有求值运算:

GET /jubo/tables/152602@9267/views/testView/groups/by-17_处罚分数_0.2,0.6,0.8@882c/reduces HTTP/1.1

返回:

HTTP/1.1 200 OK

{
  "Mean(17_处罚分数)": [],
  "Sum(17_处罚分数)": ["总处罚分数"], // 会分别列出别名与原始名字
  "Count(*)": []
}

创建求值运算:

POST /jubo/tables/152602@9267/views/testView/groups/by-17_处罚分数_0.2,0.6,0.8@882c/reduces HTTP/1.1
{
    "type": "Count"
}

返回:

HTTP/1.1 201 Created

{
  "viewId": "testView",
  "size": 1349,
  "reduceName": "Count(*)",  // 新创建的Reduce求值运算名称
  "selected": 1349,
  "groups": {
    "by-17_处罚分数_0.2,0.6,0.8@882c": {
      "values": {
        "Count(*)": 1349.0  // 所有Key值对应的Reduce值汇总处
      },
      "selected": true,
      "children": [{
        "key": "not too bad",
        "values": {
          "Count(*)": 190.0 // 分组运算值
        },
        "selected": true
      }, {
        "key": "some problem",
        "values": {
          "Count(*)": 1159.0
        },
        "selected": true
      }, {
        "key": "bad",
        "values": {
          "Count(*)": 0.0
        },
        "selected": true
      }, {
        "key": "epic fail",
        "values": {
          "Count(*)": 0.0
        },
        "selected": true
      }]
    },
    ...后略...
}

其中,创建Reduce求值的请求JSON格式如下:

属性名 类型 必要 说明
type String 求值的类型,现在支持Count、CountPct、Sum、SumPct、Mean、Mode、Min、Max、MidRange、Min90th、Max90th、Median、Var、VarP、Stdev、StdevP,注意大小写敏感,如果使用的是自定义求值,请实现power.report.arhat.core.reduce.Reducer接口并且使用时在该属性写上类的全路径名
field String 求值要使用的数据列名字或者编号,在Count求值时不是必要的,其它则必须指定该属性
alias String 为求值重命名,例如可以将“Mean(17_处罚分数)”这样的自动生成名字重命名为“平均处罚分数”

另外,有些求值运算是对别的求值运算有依赖关系的,例如Mean会同时依赖Count与Sum,如果创建Mean运算,则会同时创 建Count与Sum运算。对同一个分组创建同样的Reduce求值,不会重复进行运算,这也意味着可以对已存在的运算进行后 期重命名,例如:

// >>> 创建Mean运算:
// POST /jubo/tables/152602@9267/views/testView/groups/by-12_检查日期_DAY@d1b8/reduces HTTP/1.1
{
    "type": "Mean",
    "field": "17"
}
// <<< 返回的状态会包含Sum与Count运算:
{
  "viewId": "testView",
  "size": 1349,
  "reduceName": "Mean(17_处罚分数)",
  "selected": 1349,
  "groups": {
    "by-17_处罚分数_0.2,0.6,0.8@882c": {
      ...中略...
    },
    "by-11_检查单位@c02b": {
      ...中略...
    },
    "by-12_检查日期_DAY@d1b8": {
      "values": {
        "Mean(17_处罚分数)": 0.2268346923647146,  // 创建的是Mean运算
        "Sum(17_处罚分数)": 306.0,               // 连带创建了Sum与Count
        "Count(*)": 1349.0
      },
      "selected": true,
      "children": [{
        "key": "1373731200000",
        "values": {
          "Count(*)": 0.0,
          "Mean(17_处罚分数)": null,
          "Sum(17_处罚分数)": 0.0
        },
        "selected": true
      }, {
        "key": "1375545600000",
        "values": {
          "Count(*)": 0.0,
          "Mean(17_处罚分数)": null,
          "Sum(17_处罚分数)": 0.0
        },
        "selected": true
      }, {
        ...中略...
      }, {
        "key": "1375113600000",
        "values": {
          "Count(*)": 70.0,
          "Mean(17_处罚分数)": 0.2335714285714286,
          "Sum(17_处罚分数)": 16.35
        },
        "selected": true
      }, {
        "key": "1372608000000",  // 注意观察排序,由于创建该分组时指定了按照Count(*)排序,所以这里的顺序
        "values": {           // 便没有维持原始顺序。如果Count(*)被重命名为其它名字,便不会按照这个值
          "Count(*)": 165.0,  // 排序。
          "Mean(17_处罚分数)": 0.2533333333333332,
          "Sum(17_处罚分数)": 41.799999999999976
        },
        "selected": true
      }]
    }
  },
  "tableId": "152602@9267"
}
// >>> 重复创建Sum运算并且重命名:
// POST /jubo/tables/152602@9267/views/testView/groups/by-12_检查日期_DAY@d1b8/reduces HTTP/1.1
{
    "type": "Sum",
    "field": "17",
    "alias": "总处罚分数"
}
// <<< 运算结果依然正常:
{
  "viewId": "testView",
  "size": 1349,
  "reduceName": "总处罚分数", // 重命名后的名字
  "selected": 1349,
  "groups": {
    "by-17_处罚分数_0.2,0.6,0.8@882c": {
      ...中略...
    },
    "by-11_检查单位@c02b": {
      ...中略...
    },
    "by-12_检查日期_DAY@d1b8": {
      "values": {
        "Mean(17_处罚分数)": 0.2268346923647146,
        "总处罚分数": 306.0,
        "Count(*)": 1349.0
      },
      "selected": true,
      "children": [{
        "key": "1373731200000",
        "values": {
          "Count(*)": 0.0,
          "Mean(17_处罚分数)": null,
          "总处罚分数": 0.0
        },
        "selected": true
      }, {
        "key": "1375545600000",
        "values": {
          "Count(*)": 0.0,
          "Mean(17_处罚分数)": null,
          "总处罚分数": 0.0
        },
        "selected": true
      }, {
        ...中略...
      }, {
        "key": "1375113600000",
        "values": {
          "Count(*)": 70.0,
          "Mean(17_处罚分数)": 0.2335714285714286,
          "总处罚分数": 16.35
        },
        "selected": true
      }, {
        "key": "1372608000000",
        "values": {
          "Count(*)": 165.0,
          "Mean(17_处罚分数)": 0.2533333333333332,
          "总处罚分数": 41.799999999999976       // 只执行了重命名,并没有重新运算
        },
        "selected": true
      }]
    }
  },
  "tableId": "152602@9267"
}

获取指定求值运算的状态:

GET /jubo/tables/152602@9267/views/testView/groups/by-12_检查日期_DAY@d1b8/reduces/总处罚分数 HTTP/1.1

返回:

HTTP/1.1 200 OK

{
  "Sum(17_处罚分数)": ["总处罚分数"]
}

删除求值运算:

DELETE /jubo/tables/152602@9267/views/testView/groups/by-12_检查日期_DAY@d1b8/reduces/Mean(17_处罚分数) HTTP/1.1

返回:

HTTP/1.1 200 OK

{
  "viewId": "testView",
  "size": 1349,
  "selected": 1349,
  "groups": {
    "by-17_处罚分数_0.2,0.6,0.8@882c": {
      ...中略...
    },
    "by-11_检查单位@c02b": {
      ...中略...
    },
    "by-12_检查日期_DAY@d1b8": {
      "values": {
        "总处罚分数": 306.0, // 可以观察到,“Mean(17_处罚分数)”及“Count(*)”已经被删除,
      },                    // 而“总处罚分数”由于是被重复创建的,它的引用计数没有归0,
      "selected": true,     // 则不会被删除。
      "children": [{
        "key": "1372608000000", // 在排序依据的“Count(*)”已被删除的情况,排序又恢复为自然排序
        "values": {
          "总处罚分数": 41.799999999999976
        },
        "selected": true
      }, {
        ...后略...
      }]
    }
  },
  "tableId": "152602@9267"
}

一次性批量创建API

为了方便API调用,服务还提供了从创建数据表->创建视图->创建分组->创建求值运算的一次性批量创建API,总体逻 辑就是通过JSON请求格式的嵌套组合实现的:

POST /jubo/tables HTTP/1.1
Conten-Type: application/json;charset=UTF-8
{ // 创建Table的内容
    "uri": "file://D:/SourceCode/BitBucket/h8/src/main/resources/chengbaoshangkoufen.csv",
    // 创建View的内容
    "view": {
        "alias": "testView",
        "projection": {
            "hide": [8],
            "create": {
                "Voltage": "$(6) == null || $(6) == '' ? '????' : $(6).substring(0, $(6).indexOf('V'))"
            }
        },
        "filter": "$(17) < 1.0",
        // 创建Group的内容,只对该视图范围生效
        "groups": [{
            "type": "DIVIDES",
            "field": "17",
            "divides": [0.2, 0.6, 0.8],
            "keys": ["not too bad", "some problem", "bad", "epic fail"],
            // 创建Reduce的内容
            "reduces": [{
                "type": "Count"
            }],
            // 指定选择的内容
            "selectExact": ["some problem"]
        }, {
            "type": "TIMESTAMP",
            "field": "12",
            "precision": "DAY",
            "orderBy": "Count(*)",
            // 创建Reduce的内容
            "reduces": [{
                "type": "Mean",
                "field": "17"
            }, {
                "type": "Sum",
                "field": "17",
                "alias": "总处罚分数"
            }],
            // 指定选择的内容
            "selectRange": ["1372608000000", "1377878400000"]
        }, {
            "type": "IDENTITY",
            "field": "11"
        }, {
            "groups": [{
                "type": "IDENTITY",
                "field": "分省公司"
            },{
                "type": "IDENTITY",
                "field": "1"
            }]
        }]
    }
    // 此处也可以指定“groups”属性的内容,这里的分组将会在默认视图“_DEFAULT_”下创建
}

返回:

HTTP/1.1 201 Created

{
  "viewId": "testView",
  "size": 1349,
  "selected": 1127, // 创建出来的时候已经有选择状态
  "groups": {
    "by-11_检查单位@00ac": {
      "selected": true,
      "children": [{
        "key": "万宁局",
        "selected": true
      }, {
        ...中略...
      }, {
        "key": "龙门局",
        "selected": true
      }]
    },
    "by-12_检查日期_DAY@0aa7": {
      "values": {
        "总处罚分数": 291.9,
        "Count(*)": 1127.0,
        "Mean(17_处罚分数)": 0.2590062111801242
      },
      "children": [{
        "key": "1373040000000",
        "values": {
          "Mean(17_处罚分数)": null,
          "Count(*)": 0.0,
          "总处罚分数": 0.0
        },
        "selected": true
      }, {
        ...中略...
      }, {
        "key": "1372608000000",
        "values": {
          "Mean(17_处罚分数)": 0.2567901234567899,
          "Count(*)": 162.0,
          "总处罚分数": 41.599999999999966
        },
        "selected": true
      }]
    },
    "by-0_分省公司@0b77": {
      "selected": true,
      "children": [{
        "key": "云南",
        "selected": true,
        "groups": {
          "by-1_检查单位@ddff": {
            "selected": true,
            "children": [{
              "key": "云南::云南",
              "selected": true
            }, {
              ...中略...
            }, {
              "key": "云南::西双版纳",
              "selected": true
            }]
          }
        }
      }, {
        "key": "广东",
        "selected": true,
        "groups": {
          "by-1_检查单位@ddff": {
            "selected": true,
            "children": [{
              "key": "广东::东莞",
              "selected": true
            }, {
              ...中略...
            }, {
              "key": "广东::韶关",
              "selected": true
            }]
          }
        }
      }, {
        ...中略...
      }]
    },
    "by-17_处罚分数_0.2,0.6,0.8@b10d": {
      "children": [{
        "key": "not too bad",
        "selected": false
      }, {
        "key": "some problem",
        "selected": true
      }, {
        "key": "bad",
        "selected": false
      }, {
        "key": "epic fail",
        "selected": false
      }]
    }
  },
  "tableId": "154112@c8b9"
}

也就是说,这套协议的每一级都有创建下级对象的嵌套API。


六 · 访问控制

在生产环境中,有必要进行访问控制,尤其是如果你打算直接将绑定为外网可以访问的IP地址。Arhat Service提供一种 基于APP_ID与ACCESS_TOKEN的机制为服务添加简要的 访问控制。

当application.conf配置文件中指定production-mode为true时,可以通过添加appIds列表允许持有某个APP_ID的客户 端访问服务。单个APP_ID为任意字符串,建议使用随机UUID。

当Arhat客户端加载时,会持有着该客户端的APP_ID,并且在客户端GET /jubo的时候,通过HTTP报头换取一个ACCESS_TOKEN, 且随后的所有请求,该客户端实例都必须在报头上附上该ACCESS_TOKEN以及相应请求的数字签名,否则服务会返回403的 状态码。具体的交互过程这里不展开,以下为使用例子:

# applicaiton.conf
production-mode = true
appIds = ["08ea58d6-baba-402f-a9a4-2a70d647db36"]
<!-- APP_ID的赋值要在加载客户端脚本前面 -->
<script>var ARHAT_APP_ID = '08ea58d6-baba-402f-a9a4-2a70d647db36';</script>
<script src="dist/js/arhat-client.min.js"></script>

另外,对于特定用户创建的视图,可以在客户端指定ARHAT_PRINCIPAL

<!-- ARHAT_PRINCIPAL的赋值要在加载客户端脚本前面 -->
<script>var ARHAT_PRINCIPAL = 'userA';</script>
<script src="dist/js/arhat-client.min.js"></script>

这样子配置后,userA创建的视图(包括默认视图_DEFAULT_),只有带有该用户标识的客户端可以访问;不带用户标识或者带 有其他用户标识的客户端访问这些视图时将会得到403的状态;如果创建视图时没有附上用户标识,这些资源将会被视为可以共享 访问。

该访问控制机制不需要指定production-mode。


测试数据库连通性:

POST /jubo/test-connectivity HTTP/1.1

{
    "uri": "[jdbc|mongodb]:......",
    "username": "...", // optional
    "password": "..."  // optional
}

成功:

HTTP/1.1 200 OK

{
    "ok": true
}

失败:

HTTP/1.1 400 BAD REQUEST

{
    "ok": false,
    "error": "..."
}

results matching ""

    No results matching ""