• 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

    APIJSONClientApp.apk

  • 相关阅读:
    PHP 连接oracle
    PHP 简易导出excel 类解决Excel 打开乱码
    Linux下Apache网站目录读写权限的设置
    PHP behavior 机制简单实现
    RewriteCond和13个mod_rewrite应用举例Apache伪静态
    PHP event 事件机制
    PHP 函数引用传值
    django数据库连接快速配置
    解决Django项目数据库无法迁移问题
    生成uuid唯一标识符
  • 原文地址:https://www.cnblogs.com/tommylemon/p/6509548.html
Copyright © 2020-2023  润新知