APIJSON,让接口和文档见鬼去吧!
我:
APIJSON,让接口和文档见鬼去吧!
https://github.com/TommyLemon/APIJSON
服务端:
什么鬼?
客户端:
APIJSON是啥?
我:
APIJSON是一种JSON传输结构协议。
客户端可以定义任何JSON结构去向服务端发起请求,服务端就会返回对应结构的JSON字符串,所求即所得。
一次请求任意结构任意数据,方便灵活,不需要专门接口或多次请求。
支持增删改查、模糊搜索、远程函数调用等。还能去除重复数据,节省流量提高速度!
从此HTTP传输JSON数据没有接口,更不需要文档!
客户端再也不用和服务端沟通接口或文档问题了!再也不会被文档各种错误坑了!
服务端再也不用为了兼容旧版客户端写新版接口和文档了!再也不会被客户端随时随地没完没了地烦了!
客户端:
这个APIJSON有这么好?怎么做到的?
我:
举个栗子(查询类似微信朋友圈动态列表):
请求:
{
"[]": { //请求一个array
"page": 0, //array条件
"count": 2,
"User": { //请求查询名为User的table,返回名为User的JSONObject
"sex": 0 //object条件
},
"Moment": {
"userId@": “/User/id” //缺省依赖路径,从同级object的路径开始
},
"Comment[]": { //请求一个名为Comment的array
"page": 0,
"count": 2,
"Comment": {
"momentId@": “[]/Moment/id” //完整依赖路径
}
}
}
}
点击这里测试
返回:
{
"[]":[
{
"User":{
"id":38710,
"sex":0,
"phone":"1300038710",
"name":"Name-38710",
"head":"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000"
},
"Moment":{
"id":470,
"title":"Title-470",
"content":"This is a Content...-470",
"userId":38710,
"pictureList":["http://static.oschina.net/uploads/user/585/1170143_50.jpg?t=1390226446000"]
},
"Comment[]":[
{
"Comment":{
"id":4,
"parentId":0,
"momentId":470,
"userId":310,
"targetUserId":14604,
"content":"This is a Content...-4",
"targetUserName":"targetUserName-14604",
"userName":"userName-93781"
}
},
{
"Comment":{
"id":22,
"parentId":221,
"momentId":470,
"userId":332,
"targetUserId":5904,
"content":"This is a Content...-22",
"targetUserName":"targetUserName-5904",
"userName":"userName-11679"
}
}
]
},
{
"User":{
"id":70793,
"sex":0,
"phone":"1300070793",
"name":"Name-70793",
"head":"http://static.oschina.net/uploads/user/1174/2348263_50.png?t=1439773471000"
},
"Moment":{
"id":170,
"title":"Title-73",
"content":"This is a Content...-73",
"userId":70793,
"pictureList":["http://my.oschina.net/img/portrait.gif?t=1451961935000"]
},
"Comment[]":[
{
"Comment":{
"id":44,
"parentId":0,
"momentId":170,
"userId":7073,
"targetUserId":6378,
"content":"This is a Content...-44",
"targetUserName":"targetUserName-6378",
"userName":"userName-88645"
}
},
{
"Comment":{
"id":54,
"parentId":0,
"momentId":170,
"userId":3,
"targetUserId":62122,
"content":"This is a Content...-54",
"targetUserName":"targetUserName-62122",
"userName":"userName-82381"
}
}
]
}
]
}
客户端:
确实是一目了然,不用看文档了啊!
我被文档坑过很多次了都,文档多写或少写了一个字段,字段写错或多个空格,或者字段类型写错,都不知道浪费了多少调试和沟通时间!
有时候上头用app出了问题把我们叫过去,调试半天才发现原来是服务端改了接口!而且并没有及时通知我们!
有一次上头纠结要不要把单层评论改成QQ微信那种多级评论,自己按照以前的接口写了demo演示给上头看,上头很满意决定实现需求,结果后端都没和我商量自己改了接口返回的json结构,导致我这边不得不重构解析代码,真是醉了!
我:
用APIJSON就可以自己按需定制返回的JSON结构,没有接口,没有文档,就不会被文档坑了,也不会有你说的后端拍脑袋定JSON结构导致的客户端重构问题了哈哈!
客户端:
Nice!
服务端:
部分接口需要currentUserId和loginPassword的,你怎么搞?
我:
直接在最外层传,例如:
{
"currentUserId":100,
"loginPassword":1234,
"User":{
"id":1
}
}
服务端:
返回的状态码和提示信息放哪?
我:
也是在最外层,例如对以上请求的返回结果:
{
"status":200,
"message":"success",
"User":{
"id":"1",
"sex":"0",
"phone":"1234567890",
"name":"Tommy",
"head":"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000"
}
}
客户端:
一次请求任意结构任意数据,方便灵活,不需要专门接口或多次请求?
以前我做了一个界面,上半部分是用户的信息,下半部分是他最近的动态,最多显示3个,类似于微信的详细资料。
我需要分别请求两次:
User:
http://apijson.cn:8080/get/user?id=100
Moment列表:
http://apijson.cn:8080/get/moment/list?page=0&count=3&userId=100
现在是不是可以这样:
User和Moment列表:
http://apijson.cn:8080/get/{"User":{"id":100}, "[]":{"page":0, "count":3, "Moment":{"userId":100}}}
我:
对的,就是这样。
客户端:
好的。那重复数据怎么去除呢?
我:
比如QQ空间,里面有一个动态列表,每条动态里都有User和对应的动态内容Moment。
如果你进入一个人的空间,那就都是他的动态。
用传统的方式返回的列表数据里,每条动态里都包含同样的User,造成了数据重复:
请求:
http://apijson.cn:8080/get/moment/list?page=0&count=5&userId=100
返回:
{
"status":200,
"message":"success",
"data":[
{
"id":1,
"content":"xxx1",
...,
"User":{
"id":100,
"name":"Tommy",
...
}
},
{
"id":2,
"content":"xxx2",
...,
"User":{
"id":100,
"name":"Tommy",
...
}
},
...
]
}
有5条重复的User。
而使用APIJSON可以这样省去4个重复User:
请求:
http://apijson.cn:8080/get/{"User":{"id":100}, "[]":{"page":0, "count":5, "Moment":{"userId":100}}}
返回:
{
"status":200,
"message":"success",
"User":{
"id":100,
"name":"Tommy",
...
},
"[]":[
{
"Moment":{
"id":1,
"content":"xxx1",
...
}
},
{
"Moment":{
"id":2,
"content":"xxx2",
...
}
},
...
]
}
如果之前已经获取到这个User了,还可以这样省去所有重复User:
请求:
http://apijson.cn:8080/get/{"[]":{"page":0, "count":5, "Moment":{"userId":100}}}
返回:
{
"status":200,
"message":"success",
"[]":[
{
"Moment":{
"id":1,
"content":"xxx1",
...
}
},
{
"Moment":{
"id":2,
"content":"xxx2",
...
}
},
...
]
}
客户端:
传统方式也可以服务端在接口增加一个返回格式字段,根据这个字段来决定是否去除重复User啊
我:
确实,不过这会导致以下问题:
1.服务端要新增字段和判断字段来返回不同数据的代码。
2.服务端要在文档里增加相关说明。
3.这个功能不一定用得上,因为客户端的UI需求往往很容易变化,导致缺少使用这个功能的条件,为了一两个版本去让服务端和客户端都折腾不值得。
而使用APIJSON就没这些问题,因为根本不需要接口或文档!而且是否去重只看客户端的意愿,服务端什么都不用做。
客户端:
这样啊,赞!
哦对了,APIJSON相比传统方式有没有缺少的功能啊?
我:
传统方式能做的APIJSON都能做。
客户端和服务端的http json交互:
客户端 - 封装request -> 服务端 - 解析request - 生成response -> 客户端 - 解析response
传统方式request:
base_url/lowercase_table_name?key0=value0&key1=value1...
APIJSON request:
base_url/{TableName:{key0:value0, key1:value1...}}
TableName对应lowercase_table_name,key:value对应key=value,都是严格对应的,所以传统方式request里包含的信息APIJSON request一样可以包含,传统方式能实现的功能APIJSON肯定也都能实现。
客户端:
好的
服务端:
APIJSON怎么保证服务端返回给不同版本客户端的数据一致?
比如我上一个版本一个接口返回的值是a,现在这个版本要对所有版本客户端返回a+b,用传统方法只需要服务端把这个接口的返回值改下就好了,接口和客户端都不用改。
用APIJSON不就会导致对有些版本返回的是a,有些是a+b,这样就不能统一了?
我:
APIJSON对请求的解析和响应的操作都是在服务端完成的,对应的是APIJSON(Server)里的project。
服务端可以拦截到相关请求,比如请求a的值,把原本返回的a改成a+b就能保证对所有版本客户端返回a+b。也不需要客户端改代码,至于接口就更不用管了,因为根本没有接口。
服务端:
那我要不一致呢?给不同版本客户端返回不同的值。
我:
首先这种需求是极少的,比如降低电影票的价格,你不能让新版客户端里降价了,上个版本还是原价吧?
真有这种需求也可以通过客户端在请求里发送下版本号version,服务端根据版本号返回不同的值。
服务端:
也是啊。那用APIJSON怎么做权限处理?有些数据是要相关权限才能操作的。比如登录账号需要登录权限,付款需要支付权限。
我:
服务端获取到客户端的请求request后,在操作对应的table前用一个权限验证类去验证是否有操作权限,通过后才允许操作,否则返回错误信息。
权限验证类可以是这样的:
package zuo.biao.apijson.server.sql; import java.rmi.AccessException; import com.alibaba.fastjson.JSONObject; import zuo.biao.apijson.StringUtil; /**权限验证类
* @author Lemon
*/
public class AccessVerifyer {
private static final String TAG = "AccessVerifyer: "; private static final int ACCESS_LOGIN = 1;
private static final int ACCESS_PAY = 2; public static final String KEY_CURRENT_USER_ID = "currentUserId";
public static final String KEY_LOGIN_PASSWORD = "loginPassword";
public static final String KEY_PAY_PASSWORD = "payPassword"; public static final String[] LOGIN_ACCESS_TABLE_NAMES = {"Work", "Comment"};
public static final String[] PAY_ACCESS_TABLE_NAMES = {"Wallet"}; /**验证权限是否通过
* @param request
* @param tableName
* @return
*/
public static boolean verify(JSONObject request, String tableName) throws AccessException {
try {
verify(request, getAccessId(tableName));
} catch (AccessException e) {
throw new AccessException(TAG + "verify tableName = " + tableName + ", error = " + e.getMessage());
}
return true;
} /**验证权限是否通过
* @param request
* @param accessId 可以直接在代码里写ACCESS_LOGIN等,或者建一个Access表。已实现登录密码自动化处理,不需要写代码。
* @return
* @throws AccessException
*/
public static boolean verify(JSONObject request, int accessId) throws AccessException {
if (accessId < 0 || request == null) {
return true;
}
long currentUserId = request.getLongValue(KEY_CURRENT_USER_ID);
if (currentUserId <= 0) {
throw new AccessException(TAG + "verify accessId = " + accessId
+ " >> currentUserId <= 0, currentUserId = " + currentUserId);
}
String password; switch (accessId) {
case ACCESS_LOGIN:
password = StringUtil.getString(request.getString(KEY_LOGIN_PASSWORD));
if (password.equals(StringUtil.getString(getLoginPassword(currentUserId))) == false) {
throw new AccessException(TAG + "verify accessId = " + accessId
+ " >> currentUserId or loginPassword error"
+ " currentUserId = " + currentUserId + ", loginPassword = " + password);
}
case ACCESS_PAY:
password = StringUtil.getString(request.getString(KEY_PAY_PASSWORD));
if (password.equals(StringUtil.getString(getPayPassword(currentUserId))) == false) {
throw new AccessException(TAG + "verify accessId = " + accessId
+ " >> currentUserId or payPassword error"
+ " currentUserId = " + currentUserId + ", payPassword = " + password);
}
default:
return true;
}
} /**获取权限id
* @param tableName
* @return
*/
public static int getAccessId(String tableName) {
if (StringUtil.isNotEmpty(tableName, true) == false) {
return -1;
}
for (int i = 0; i < LOGIN_ACCESS_TABLE_NAMES.length; i++) {
if (tableName.equals(LOGIN_ACCESS_TABLE_NAMES[i])) {
return ACCESS_LOGIN;
}
}
for (int i = 0; i < PAY_ACCESS_TABLE_NAMES.length; i++) {
if (tableName.equals(PAY_ACCESS_TABLE_NAMES[i])) {
return ACCESS_PAY;
}
}
return -1;
} /**获取登录密码
* @param userId
* @return
*/
public static String getLoginPassword(long userId) {
// TODO 查询并返回对应userId的登录密码
return "123456";//仅测试用
} /**获取支付密码
* @param userId
* @return
*/
public static String getPayPassword(long currentUserId) {
// TODO 查询并返回对应userId的支付密码
return "123456";//仅测试用
} }
服务端:
嗯,的确可行。刚看了项目主页的介绍,感觉APIJSON确实非常强大方便,连接口和文档都不用写了,也不会在健身或者陪女朋友看电影时突然接到客户端的电话了。
不过我还有一个问题,APIJSON是动态拼接SQL的,确实是灵活,但会不会导致SQL注入问题?
我:
APIJSON拼接SQL是在服务端完成的,客户端是不能直接发送SQL给服务端的。整个数据库操作都是服务端完全可控的,服务端可拦截危险注入,风险不比传统方式高。
服务端:
厉害了我的哥!我去下载试试哈哈!
客户端:
哈哈,我也要试试,请问怎么获取源码?免费的吗?
我:
已在Github开源,完全免费。
https://github.com/TommyLemon/APIJSON
服务端:
很棒!已Star!
客户端:
Star +1,顺便还Fork了一份研究嘿嘿!另外文档很详细赞一个!
我:
有什么问题或建议可以提issue或者发我邮件 tommylemon@qq.com,大家一起交流探讨哈!
服务端:
感觉我以后不用写一大堆接口了,不需要写兼容代码了,也不需要写文档了。可以专注于数据的处理、监控、统计、分析了哈哈!
客户端:
我也不用等服务端写好接口才能请求了,现在自己定制返回的JSON,看请求就知道返回的JSON结构,可以直接写解析代码了哈哈!
(注:以上是对真实对话的改编)
APIJSON,让接口和文档见鬼去吧!
源码及文档(记得给个Star哦^_^)
https://github.com/TommyLemon/APIJSON
下载试用(测试服务器地址:http://apijson.cn:8080)
APIJSON,让接口和文档见鬼去吧!的更多相关文章
- APIJSON,让接口见鬼去吧!
我: APIJSON,让接口见鬼去吧! https://github.com/TommyLemon/APIJSON 服务端: 什么鬼? 客户端: APIJSON是啥? 我: APIJSON是一种JSO ...
- 【转】APIJSON,让接口见鬼去吧!
我: APIJSON,让接口和文档见鬼去吧! https://github.com/TommyLemon/APIJSON 服务端: 什么鬼? 客户端: APIJSON是啥? 我: APIJSON是一种 ...
- 让你熟知jquery见鬼去吧
$是jquery最具代表的符号,当然php也是,但是二者不能同日而语;不得不说jquery的选择器是大家赞不绝口的,在它1.x版本中对ie兼容性是最好的,这要归功于$选择器; 现在呢,html5的降临 ...
- 让<未将对象引用到实例>见鬼去吧!
未将对象引用到实例,即NullReferenceException异常,我相信这是c#编程中最常见的错误之一,至少我在做项目的过程中,有很多时候都会抛出这个异常.每当这个异常出现的时候,我都会头皮一紧 ...
- 如何让接口文档自动生成,SpringBoot中Swagger的使用
目录 一.在SpringBoot项目中配置Swagger2 1.pom.xml中对Swagger2的依赖 2.编写配置类启用Swagger 3.配置实体类的文档 4.配置接口的文档 5.访问文档 二. ...
- asp.net core使用Swashbuckle.AspNetCore(swagger)生成接口文档
asp.net core中使用Swashbuckle.AspNetCore(swagger)生成接口文档 Swashbuckle.AspNetCore:swagger的asp.net core实现 项 ...
- Swagger解决你手写API接口文档的痛
首先,老规矩,我们在接触新事物的时候, 要对之前学习和了解过的东西做一个总结. 01 痛 苦 不做.不行 之前,前后端分离的系统由前端和后端不同的编写,我们苦逼的后端工程师会把自己已经写完的A ...
- .net core的Swagger接口文档使用教程(二):NSwag
上一篇介绍了Swashbuckle ,地址:.net core的Swagger接口文档使用教程(一):Swashbuckle 讲的东西还挺多,怎奈微软还推荐了一个NSwag,那就继续写吧! 但是和Sw ...
- SpringBoot接口 - 如何生成接口文档之非侵入方式(通过注释生成)Smart-Doc?
通过Swagger系列可以快速生成API文档,但是这种API文档生成是需要在接口上添加注解等,这表明这是一种侵入式方式: 那么有没有非侵入式方式呢, 比如通过注释生成文档? 本文主要介绍非侵入式的方式 ...
随机推荐
- C# Winform窗口之间传值的多种方法浅析(转)
摘要http://www.jb51.net/article/63837.htm 这篇文章主要介绍了C# Winform窗口之间传值的多种方法浅析,本文起讲解了通过构造器传值.通过属性传递.通过事件携带 ...
- iOS 开发 之 编程知识点
iOS 创建和设置pch iOS 之 时间格式与字符串转换 iOS 之 二维码生成与扫描(LBXScan) iOS 之 定时器 iOS 之 通知 iOS 之 NSString 去除前后空格和回车键 i ...
- Java 之 Spring加载(Java之负基础实战)
1.下载后解压 2.在WEB-INF里面创建lib文件夹 3.拖入jar包 只拖入*.RELEASE.jar包
- Java语言中IO流的操作规律学习笔记
1,明确源和目的. 数据源:就是需要读取,可以使用两个体系:InputStream.Reader: 数据汇:就是需要写入,可以使用两个体系:OutputStream.Writer: 总结: 读:就是把 ...
- iOS 之 Aggregate Target
工程导航栏>选中工程>菜单File>New>Target>Other>Aggregate
- C#的显式接口和隐式接口(转载)
接口的实现分为:隐式实现和显式实现.如果类或者结构要实现的是单个接口,可以使用隐式实现,如果类或者结构继承了多个接口那么接口中相同名称成员就要显式实现.显示实现是通过使用接口的完全限定名来实现接口成员 ...
- HDU2066:一个人的旅行(Dijkstra)
Problem Description 虽然草儿是个路痴(就是在杭电待了一年多,居然还会在校园里迷路的人,汗~),但是草儿仍然很喜欢旅行,因为在旅途中 会遇见很多人(白马王子,^0^),很多事,还能丰 ...
- DataTimePicker
日期时间控件 DataTimePicker 功能:拾取系统时间.日期,并以对应格式输出 重要属性: a. date,拾取的时间. b. Time,拾取的系统时间 举例如:button2.Captio ...
- systemd的命令systemctl set-property testSpeed CPUQuota=10%
总结 systemd 的资源限制一般要写到unit文件中,但是,现在测试发现会有 被值被覆盖的现象:经过排查发现是,没有 使用systemd的接口,凡是使用echo "" > ...
- java IMAGEIO
javax.imageio使用 ImageIO 类的静态方法可以执行许多常见的图像 I/O 操作. 此包包含一些基本类和接口,有的用来描述图像文件内容(包括元数据和缩略图)(IIOImage): 有的 ...