Skip to content

让接口和文档见鬼去吧!客户端定义服务端返回JSON的结构!Let interfaces and documents go to hell! Clients define JSON structures which server returned!

master
Go to file
Code
This branch is 1352 commits behind APIJSON:master.

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 

README.md

APIJSON Java API Android API Gradle Version License

Java Android JavaScript

English Document

1.简介

APIJSON是一种JSON传输结构协议。

客户端可以定义任何JSON结构去向服务端发起请求,服务端就会返回对应结构的JSON字符串,所求即所得。
一次请求任意结构任意数据,方便灵活,不需要专门接口或多次请求。
支持增删改查、模糊搜索、远程函数调用等。还能去除重复数据,节省流量提高速度!

从此HTTP传输JSON数据没有接口,更不需要文档!
客户端再也不用和服务端沟通接口或文档问题了!再也不会被文档各种错误坑了!
服务端再也不用为了兼容旧版客户端写新版接口和文档了!再也不会被客户端随时随地没完没了地烦了!

举几个栗子:

查询用户

请求:


{
  "User":{
  }
}

点击这里测试

返回:


{
  "User":{
    "id":38710,
    "sex":0,
    "name":"TommyLemon",
    "certified":true,
    "tag":"Android&Java",
    "phone":13000038710,
    "head":"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
    "date":1485948110000,
    "pictureList":[
      "http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
      "http://common.cnblogs.com/images/icon_weibo_24.png"
    ]
  },
  "status":200,
  "message":"success"
}

查询用户列表

请求:


{
  "[]":{
    "count":3,
    "User":{
      "@column":"id,name"
    }
  }
}

点击这里测试

返回:


{
  "[]":[
    {
      "User":{
        "id":38710,
        "name":"TommyLemon"
      }
    },
    {
      "User":{
        "id":70793,
        "name":"Strong"
      }
    },
    {
      "User":{
        "id":82001,
        "name":"Android"
      }
    }
  ],
  "status":200,
  "message":"success"
}

查询类似微信朋友圈的动态列表

请求:


{
  "[]":{                             //请求一个Array
    "page":0,                        //Array条件
    "count":2,
    "User":{                         //请求一个名为User的Table
      "sex":0,                       //Object条件
      "@column":"id,name,head"       //指定返回字段
    },
    "Moment":{
      "userId@":"/User/id"           //缺省依赖路径,从所处容器的父容器路径开始
    },
    "Comment[]":{                    //请求一个名为Comment的Array
      "count":2,
      "Comment":{
        "momentId@":"[]/Moment/id"   //完整依赖路径
      }
    }
  }
}

点击这里测试

返回:


{
  "[]":[
    {
      "User":{
        "id":38710,
        "name":"TommyLemon",
        "head":"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000"
      },
      "Moment":{
        "id":235,
        "userId":38710,
        "date":1486541171000,
        "pictureList":[
          "http://static.oschina.net/uploads/img/201604/22172508_mpwj.jpg"
        ]
      },
      "Comment[]":[
        {
          "Comment":{
            "id":160,
            "toId":0,
            "userId":82001,
            "momentId":235,
            "date":1488434166000,
            "content":"This is a Content...-160"
          }
        },
        {
          "Comment":{
            "id":163,
            "toId":0,
            "userId":82001,
            "momentId":235,
            "date":1488434166000,
            "content":"This is a Content...-163"
          }
        }
      ]
    },
    {
      "User":{
        "id":70793,
        "name":"Strong",
        "head":"http://static.oschina.net/uploads/user/585/1170143_50.jpg?t=1390226446000"
      },
      "Moment":{
        "id":12,
        "userId":70793,
        "date":1486541171000,
        "content":1111534034,
        "pictureList":[
          "http://static.oschina.net/uploads/img/201604/22172508_eGDi.jpg",
          "http://static.oschina.net/uploads/img/201604/22172507_rrZ5.jpg"
        ]
      },
      "Comment[]":[
        {
          "Comment":{
            "id":162,
            "toId":0,
            "userId":93793,
            "momentId":12,
            "date":1488776625000,
            "content":"This is a Content...-162"
          }
        },
        {
          "Comment":{
            "id":164,
            "toId":0,
            "userId":93793,
            "momentId":12,
            "date":1488776625000,
            "content":"This is a Content...-164"
          }
        }
      ]
    }
  ],
  "status":200,
  "message":"success"
}

2.对比传统RESTful方式

2.1 开发流程

开发流程 传统方式 APIJSON
接口传输 等服务端编辑接口,然后更新文档,客户端再按照文档编辑请求和解析代码 客户端按照自己的需求编辑请求和解析代码。
没有接口,更不需要文档!客户端再也不用和服务端沟通接口或文档问题了!
兼容旧版 服务端增加新接口,用v2表示第2版接口,然后更新文档 什么都不用做!

2.2 客户端请求

客户端请求 传统方式 APIJSON
要求 客户端按照文档在对应url后面拼接键值对 客户端按照自己的需求在固定url后拼接JSON
结构 base_url/lowercase_table_name?key0=value0&key1=value1...
&currentUserId=100&loginPassword=1234

其中currentUserId和loginPassword只在请求部分接口时需要
base_url/{TableName0:{key0:value0, key1:value1 ...}, TableName1:{...}...
, currentUserId:100, loginPassword:1234}

其中currentUserId和loginPassword只在请求部分接口时需要
URL 不同的请求对应不同的url 相同的请求方法(GET,POST等)都用同一个url
键值对 key=value key:value

2.3 服务端操作

服务端操作 传统方式 APIJSON
解析和返回 取出键值对,把键值对作为条件用预设的的方式去查询数据库,最后封装JSON并返回给客户端 把Parser#parse方法的返回值返回给客户端就行
返回JSON结构的设定方式 由服务端设定,客户端不能修改 由客户端设定,服务端不能修改

2.4 客户端解析

客户端解析 传统方式 APIJSON
查看方式 查文档或等请求成功后看log 看请求就行,所求即所得。也可以等请求成功后看log
方法 解析JSONObject 可以用JSONResponse解析JSONObject或传统方式

2.5 客户端对应不同需求的请求

客户端对应不同需求的请求 传统方式 APIJSON
User base_url/get/user?id=1 base_url/get/{"User":{"id":1}}
Moment和对应的User 分两次请求
Moment: base_url/get/moment?userId=1
User: base_url/get/user?id=1
base_url/get/{"Moment":{"userId":1}, "User":{"id":1}}
User列表 base_url/get/user/list?page=0&count=3&sex=0 base_url/get/{"[]":{"page":0, "count":3, "User":{"sex":0}}}
Moment列表,每个Moment包括发布者User和前3条Comment Moment里必须有User的Object和Comment的Array
base_url/get/moment/list?page=0&count=3&commentCount=3
base_url/get/{"[]":{"page":0, "count":3, "Moment":{}, "User":{"id@":"/Moment/userId"}, "[]":{"count":3, "Comment":{"momentId@":"[]/Moment/id"}}}}
User发布的Moment列表,每个Moment包括发布者User和前3条Comment Moment里必须有User的Object和Comment的Array
base_url/get/moment/list?page=0&count=3&commentCount=3&userId=1
有以下几种方法:
① 把以上请求里的"Moment":{}, "User":{"id@":"/Moment/userId"}改为"Moment":{"userId":1}, "User":{"id":1}

② 或这样省去重复的User
base_url/get/{"User":{"id":1}, "[]":{"page":0, "count":3, "Moment":{"userId":1}, "[]":{"count":3, "Comment":{"momentId@":"[]/Moment/id"}}}}

③ 如果User之前已经获取到了,还可以这样省去所有重复User
base_url/get/{"[]":{"page":0, "count":3, "Moment":{"userId":1}, "[]":{"count":3, "Comment":{"momentId@":"[]/Moment/id"}}}}

2.6 服务端对应不同请求的返回结果

服务端对应不同请求的返回结果 传统方式 APIJSON
User {"status":200, "message":"success", "data":{"id":1, "name":"xxx"...}} {"status":200, "message":"success", "User":{"id":1, "name":"xxx"...}}
Moment和对应的User 分别返回两次请求的结果
Moment: {"status":200, "message":"success", "data":{"id":1, "name":"xxx"...}}
User: {"status":200, "message":"success", "data":{"id":1, "name":"xxx"...}}
{"status":200, "message":"success", "Moment":{"id":1, "content":"xxx"...}, "User":{"id":1, "name":"xxx"...}}
User列表 {"status":200, "message":"success", "data":[{"id":1, "name":"xxx"...}, {"id":2...}...]} {"status":200, "message":"success", "[]":[{"User":{"id":1, "name":"xxx"...}}, {"User":{"id":2...}}...]}
Moment列表,每个Moment包括发布者User和前3条Comment {"status":200, "message":"success", "data":[{"id":1, "content":"xxx"..., "User":{...}, "Comment":[...]}, {"id":2...}...]} {"status":200, "message":"success", "[]":[{"Moment":{"id":1, "content":"xxx", ...}, "User":{...}, "[]":[{"Comment":{...}}, ...]}, ...]}
User发布的Moment列表,每个Moment包括发布者User和前3条Comment {"status":200, "message":"success", "data":[{"id":1, "content":"xxx"..., "User":{...}, "Comment":[...]}, {"id":2...} ...]} 以上不同请求方法的结果:
① {"status":200, "message":"success", "[]":[{"User":{"id":1, "name":"xxx", ...}, "Moment":{...}, "[]":[{"Comment":{...}}, ...]}, ...]}

② {"status":200, "message":"success", "User":{...}, "[]":[{"Moment":{"id":1, "content":"xxx", ...}, "[]":[{"Comment":{...}, ...}, ...]}, ...]}

③ {"status":200, "message":"success", "[]":[{"Moment":{"id":1, "content":"xxx", ...}, "[]":[{"Comment":{}}, ...]}, ...]}

1.base_url指基地址,一般是顶级域名,其它分支url都是在base_url后扩展。如base_url:http://www.google.com/ ,对应的GET分支url:http://www.google.com/get/ ,下同。
2.请求中的 / 需要转义。JSONRequest.java已经用URLEncoder.encode转义,不需要再写;但如果是浏览器或Postman等直接输入url/request,需要把request中的所有 / 都改成 %252F ,下同。
3.status,指返回结果中的状态码,200表示成功,其它都是错误码,值全部都是HTTP标准状态码。下同。
4.message,指返回结果中的状态信息,对成功结果或错误原因的详细说明。下同。
5.status和message总是在返回结果的同一层级成对出现。对所有请求的返回结果都会在最外层有一对总结式status和message。对非GET类请求,返回结果里面的每个JSONObject里都会有一对status和message说明这个JSONObject的状态。下同。
6.id等字段对应的值仅供说明,不一定是数据库里存在的,请求里用的是真实存在的值。下同。

3.请求方法、URL、Request、Response对应关系总览

 方法及说明 URL Request Response
GET:普通获取请求,明文,可用浏览器调试 base_url/get/ {TableName:{…}},{…}内为限制条件。
例如获取一个id为1的Moment:
{"Moment":{"id":1}}
{TableName:{...}, "status":200, "message":"success"}
例如
{"Moment":{"id":1, "userId":1, "content":"APIJSON,let interfaces and documents go to hell !"}, "status":200, "message":"success"}
HEAD:普通获取数量请求,明文,可用浏览器调试 base_url/head/ {TableName:{…}},{…}内为限制条件。
例如获取一个id为1的User所发布的Moment总数:
{"Moment":{"userId":1}}
{TableName:{"status":200, "message":"success", "count":10}, "status":200, "message":"success"}
例如
{"Moment":{"status":200, "message":"success", "count":10}, "status":200, "message":"success"}
POST_GET:安全/私密获取请求,非明文,用于获取钱包等对安全性要求高的数据 base_url/post_get/ 最外层加一个"tag":tag,其它同GET 同GET
POST_HEAD:安全/私密获取数量请求,非明文,用于获取银行卡数量等对安全性要求高的数据 base_url/post_head/ 最外层加一个"tag":tag,其它同HEAD 同HEAD
POST:新增数据,非明文 base_url/post/ {TableName:{…}, "tag":tag},{…}中id由服务端生成,客户端不能传。
例如一个id为1的User发布一个新Moment:
{"Moment":{"userId":1, "content":"APIJSON,let interfaces and documents go to hell !"}, "tag":"Moment"}
{TableName:{"status":200, "message":"success", "id":1}, "status":200, "message":"success"}
例如
{"Moment":{"status":200, "message":"success", "id":1}, "status":200, "message":"success"}
PUT:修改数据,非明文,只修改所传的字段 base_url/put/ {TableName:{"id":id,…}, "tag":tag},{…}中id必传。
例如修改id为1的Moment的content:
{"Moment":{"id":1,"content":"APIJSON,let interfaces and documents go to hell !"}, "tag":"Moment"}
同POST
DELETE:删除数据,非明文 base_url/delete/ {TableName:{"id":id}, "tag":tag},{…}中id必传,一般只传id。
例如删除id为1的Moment:
{"Moment":{"id":1}, "tag":"Moment"}
同POST

1.TableName指要查询的table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值为内部所传字段符合对应Table的JSONObject,结构是{...}
2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的key,一般是要查询的table的名称,由服务端Request表中指定。
3.非GET、HEAD请求,其对应的 方法、tag、结构 必须和 服务端Request表中指定的 一一对应,否则请求将不被通过。
4.POST_GET与GET、POST_HEAD与HEAD分别为同类方法,请求方式不同但返回结果相同。下同。
5.在HTTP通信中,GET、HEAD方法一般用HTTP GET请求,其它一般用HTTP POST请求。下同。

4.功能符

功能 键值对格式 使用示例
查询数组 "key[]":{},后面是JSONObject,key可省略。当key和内部Table名相同时,JSONResponse#format会把Table提取出来,即将 {Table:{Content}} 转化为 {Content} {"User[]":{"User":{}}},查询一个User数组。这里key和Table名都是User,会提取User,即将 {"User":{"id", ...}} 转化为 {"id", ...}
匹配选项范围 "key{}":[],后面是JSONArray,作为key可取的值的选项 "id{}":[38710,82001,70793],查询id符合38710,82001,70793中任意一个的一个User数组
匹配条件范围 "key{}":"条件0,条件1...",条件为任意SQL比较表达式字符串,非Number类型必须用''包含条件的值,如'a' "id{}":"<=80000,>90000",查询id符合id<=80000 | id>90000的一个User数组
包含选项范围 "key<>":Object => "key<>":[Object],key对应值的类型必须为JSONArray,Object类型不能为JSON "friendIdList<>":38710,查询friendIdList包含38710的一个User数组
远程调用函数 "key()":"函数表达式",函数表达式为 function(Type0:value0,Type1:value1...)。函数参数类型为Object或泛型时可省略类型,即 Object:value 改写为 value "isPraised()":"isContain(Collection:praiseUserIdList,userId)",请求完成后会调用 boolean isContain(Collection collection, Object object) 函数,然后变为 "isPraised":true 这种(假设点赞用户id列表包含了userId,即这个User点了赞)
依赖引用 "key@":"依赖路径",依赖路径为用/分隔的字符串。以/开头的是缺省依赖路径,从声明key所处容器的父容器路径开始;其它是完整依赖路径,从最外层开始。被依赖的对象必须在声明key的上面 "userId@":"/User/id",userId依赖引用与所处容器同级的User内的id值,假设id=1,则请求完成后会变成 "userId":1
模糊搜索 "key$":"SQL搜索表达式" => "key$":["SQL搜索表达式"],任意SQL搜索表达式字符串,如 %key%, %k%e%y% 等 "name$":"%m%",查询name包含"m"的一个User数组
新建别名 "name:alias",name映射为alias,用alias替代name。可用于 column,Table,SQL函数 等。只用于GET类方法、HEAD类方法的请求 "@column":"toId:parentId",将查询的字段toId变为parentId返回
增加 或 扩展 "key+":key指定类型的Object,且类型为Number,String,JSONArray中的一种。如 1,"apijson",["url0","url1"] 等。只用于PUT请求 "praiseUserIdList+":[1],添加一个点赞用户id,即该用户点了赞
减少 或 去除 "key-":key指定类型的Object,同"key+" "balance-":100.00,余额减少100.00,即花费了100元
逻辑运算 &, |, ! 逻辑运算符。
① & 可用于"key&{}":"条件"等
② | 可用于"key|{}":"条件", "key|{}":[]等,一般可省略
③ ! 可单独使用,如"key!":Object,也可像&,|一样配合其他功能符使用
"id&{}":">80000,<=90000",即id满足id>80000 & id<=90000
"id|{}":">90000,<=80000",同"id{}":">90000,<=80000",即id满足id>90000 | id<=80000
"id!{}":[82001,38710],即id满足 ! (id=82001 | id=38710),可过滤黑名单的消息
关键词,可自定义 "@key":key指定类型的Object,@key为JSONObject中的关键词
① "@column":"key0,key1...", 指定返回字段
② "@order":"key0,key1+,key2-...",指定排序方式
③ "@group":"key0,key1,key2...",指定分组方式。如果@column里声明了Table主键(一般是id),则该主键也必须在@group中声明;其它情况下必须满足至少一个条件:1.分组的key在@column里声明;2.Table主键在@group中声明
④ "@having":"function0(...)?valu0,function1(...)?valu1,function2(...)?value2...",指定函数条件,一般和@group一起用,函数一般在@column里声明
① 只查询id,sex,name这几列并且请求结果也按照这个顺序:
"@column":"id,sex,name"
② 查询按 name降序、id默认顺序 排序的User数组:
"@order":"name-,id"
③ 查询按userId分组的Moment数组:
"@group"="userId,id"
④ 查询 按userId分组、id最大值>=100 的Moment数组:
"@column":"userId,max(id)",
"@group":"userId",
"@having":"max(id)>=100"

还可以指定函数返回名:
"@column":"userId,max(id):maxId",
"@group":"userId",
"@having":"maxId>=100"

⑤ 从pictureList获取第0张图片:
"@position":0, //这里@position为自定义关键词
"firstPicture()":"get(Collection:pictureList,int:@position)"

...

5.使用方法

5.1 下载后解压APIJSON工程

Clone or download > Download ZIP > 解压到一个路径并记住这个路径。

你可以跳过步骤5.2和步骤5.3,用我的服务器IP地址 139.196.140.118:8080 来测试服务端对客户端请求的返回结果。

5.2 导入MySQL table文件

服务端需要MySQL Server和MySQLWorkbench,没有安装的都先下载安装一个。
我的配置是Windows 7 + MySQL Community Server 5.7.16 + MySQLWorkbench 6.3.7 和 OSX EI Capitan + MySQL Community Server 5.7.16 + MySQLWorkbench 6.3.8,其中系统和软件都是64位的。

启动MySQLWorkbench > 进入一个Connection > 点击Server菜单 > Data Import > 选择刚才解压路径下的APIJSON-Master/table > Start Import > 刷新SCHEMAS, 左下方sys/tables会出现添加的table。

5.3 用Eclipse for JavaEE或IntellIJ IDEA Ultimate运行服务端工程

如果以上编辑器一个都没安装,运行前先下载安装一个。
我的配置是Windows 7 + JDK 1.7.0_71 + Eclipse 4.6.1 + IntellIJ 2016.3 和 OSX EI Capitan + JDK 1.8.0_91 + Eclipse 4.6.1 + IntellIJ 2016.2.5

Eclipse for JavaEE

1.导入
File > Import > Maven > Existing Maven Projects > Next > Browse > 选择刚才解压路径下的APIJSON-Master/APIJSON(Server)/APIJSON(Eclipse_JEE) > Finish

2.运行
Run > Run As > Java Application > 选择APIJSONApplication > OK

IntellIJ IDEA Ultimate

1.导入
Open > 选择刚才解压路径下的APIJSON-Master/APIJSON(Server)/APIJSON(Idea) > OK

2.运行
Run > Run APIJSONApplication

5.4 用ADT Bundle或Android Studio运行客户端工程

可以跳过这个步骤,直接下载下方提供的客户端App。

如果以上编辑器一个都没安装,运行前先下载安装一个。
我的配置是Windows 7 + JDK 1.7.0_71 + ADT Bundle 20140702 + Android Studio 2.2 和 OSX EI Capitan +(JDK 1.7.0_71 + ADT Bundle 20140702)+(JDK 1.8.0_91 + Android Studio 2.1.2),其中系统和软件都是64位的。

ADT Bundle

1.导入
File > Import > Android > Existing Android Code Into Workspace > Next > Browse > 选择刚才解压路径下的APIJSON-Master/APIJSON(Android)/APIJSON(ADT) > Finish

2.运行
Run > Run As > Android Application

Android Studio

1.导入
Open an existing Android Studio project > 选择刚才解压路径下的APIJSON-Master/APIJSON(Android)/APIJSON(AndroidStudio)/APIJSONApp (或APIJSONTest) > OK

2.运行
Run > Run app

5.5 操作客户端App

选择发送APIJSON请求并等待显示结果。
如果默认url不可用,修改为一个可用的,比如正在运行APIJSON服务端工程的电脑的IPV4地址,然后点击查询按钮重新请求。

6.其它

6.1 相关推荐

APIJSON, 让接口和文档见鬼去吧!

仿QQ空间和微信朋友圈,高解耦高复用高灵活

3步创建APIJSON服务端新表及配置

6.2 关于作者

TommyLemon:https://github.com/TommyLemon
如果有什么问题或建议可以发我邮件,交流技术,分享经验 ^_^

6.3 下载试用客户端App

仿微信朋友圈动态实战项目
APIJSONApp.apk

测试及自动生成代码工具
APIJSONTest.apk

6.4 更新日志

https://github.com/TommyLemon/APIJSON/commits/master

6.5 点Star支持我,点Fork研究它

https://github.com/TommyLemon/APIJSON

About

让接口和文档见鬼去吧!客户端定义服务端返回JSON的结构!Let interfaces and documents go to hell! Clients define JSON structures which server returned!

Resources

License

Packages

No packages published

Languages

You can’t perform that action at this time.