程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> APIJSON,讓接口見鬼去吧!,apijson讓接口見鬼

APIJSON,讓接口見鬼去吧!,apijson讓接口見鬼

編輯:JAVA綜合教程

APIJSON,讓接口見鬼去吧!,apijson讓接口見鬼


 

我:

APIJSON,讓接口見鬼去吧!

https://github.com/TommyLemon/APIJSON

服務端:

什麼鬼?

客戶端:

APIJSON是啥?

我:

APIJSON是一種JSON傳輸結構協議。

客戶端可以定義任何JSON結構去向服務端發起請求,服務端就會返回對應結構的JSON字符串,所求即所得。

一次請求任意結構任意數據,方便靈活,不需要專門接口或多次請求。

還能去除重復數據,節省流量提高速度!

從此HTTP傳輸JSON數據沒有接口,更不需要文檔!

客戶端再也不用和服務端溝通接口或文檔問題了!再也不會被文檔各種錯誤坑了!

服務端再也不用為了兼容舊版客戶端寫新版接口和文檔了!再也不會被客戶端隨時隨地沒完沒了地煩了!

客戶端:

這個APIJSON有這麼好?怎麼做到的?

我:

舉個栗子:

請求:

{
    "[]": {                       //請求一個array
        "page": 1,                //array條件
        "count": 2,        
        "User": {                 //請求查詢名為User的table,返回名為User的JSONObject
            "sex": 0              //object條件
        },
        "Work": {
            "userId": “/User/id”  //缺省依賴路徑,從同級object的路徑開始
        },
        "Comment[]": {            //請求一個名為Comment的array 
            "page": 0,
            "count": 3,
            "Comment": {
                 "workId": “[]/Work/id”  //完整依賴路徑
             }
        }
    }
}

返回:

{
    "[]":{
        "0":{
            "User":{
                "picture":"",
                "id":"38710",
                "sex":"0",
                "phone":"1300038710",
                "name":"Name-38710",
                "head":"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000"
            },
            "Work":{
                "id":470,
                "title":"Title-470",
                "content":"This is a Content...-470",
                "userId":38710,
                "picture":"http://static.oschina.net/uploads/user/585/1170143_50.jpg?t=1390226446000"
            },
            "Comment[]":{
                "0":{
                    "Comment":{
                        "id":4,
                        "parentId":0,
                        "workId":470,
                        "userId":310,
                        "targetUserId":14604,
                        "content":"This is a Content...-4",
                        "targetUserName":"targetUserName-14604",
                        "userName":"userName-93781"
                    }
                },
                "1":{
                    "Comment":{
                        "id":22,
                        "parentId":221,
                        "workId":470,
                        "userId":332,
                        "targetUserId":5904,
                        "content":"This is a Content...-22",
                        "targetUserName":"targetUserName-5904",
                        "userName":"userName-11679"
                    }
                },
                "2":{
                    "Comment":{
                        "id":47,
                        "parentId":4,
                        "workId":470,
                        "userId":10,
                        "targetUserId":5477,
                        "content":"This is a Content...-47",
                        "targetUserName":"targetUserName-5477",
                        "userName":"userName-80271"
                    }
                }
            }
        },
        "1":{
            "User":{
                "picture":"",
                "id":"70793",
                "sex":"0",
                "phone":"1300070793",
                "name":"Name-70793",
                "head":"http://static.oschina.net/uploads/user/1174/2348263_50.png?t=1439773471000"
            },
            "Work":{
                "id":170,
                "title":"Title-73",
                "content":"This is a Content...-73",
                "userId":70793,
                "picture":"http://my.oschina.net/img/portrait.gif?t=1451961935000"
            },
            "Comment[]":{
                "0":{
                    "Comment":{
                        "id":44,
                        "parentId":0,
                        "workId":170,
                        "userId":7073,
                        "targetUserId":6378,
                        "content":"This is a Content...-44",
                        "targetUserName":"targetUserName-6378",
                        "userName":"userName-88645"
                    }
                },
                "1":{
                    "Comment":{
                        "id":54,
                        "parentId":0,
                        "workId":170,
                        "userId":3,
                        "targetUserId":62122,
                        "content":"This is a Content...-54",
                        "targetUserName":"targetUserName-62122",
                        "userName":"userName-82381"
                    }
                },
                "2":{
                    "Comment":{
                        "id":99,
                        "parentId":44,
                        "workId":170,
                        "userId":793,
                        "targetUserId":7166,
                        "content":"This is a Content...-99",
                        "targetUserName":"targetUserName-7166",
                        "userName":"userName-22949"
                    }
                }
            }
        }
    }
}

 

客戶端:

確實是一目了然,不用看文檔了啊!

我被文檔坑過很多次了都,文檔多寫或少寫了一個字段,字段寫錯或多個空格,或者字段類型寫錯,都不知道浪費了多少調試和溝通時間!

還有一次上頭糾結要不要把單層評論改成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://www.xxx.com/user?id=1

 

Moment列表:

http://www.xxx.com/moment/list?page=0&count=3&userId=1

 

現在是不是可以這樣:

User和Moment列表:

http://www.xxx.com/{"User":{"id":1}, "[]":{"page":0, "count":3, "Moment":{"userId":"User/id"}}}

 

我:

對的,就是這樣。

客戶端:

好的。那重復數據怎麼去除呢?

我:

比如QQ空間,裡面有一個動態列表,每條動態裡都有User和對應的動態內容Moment。

如果你進入一個人的空間,那就都是他的動態。

用傳統的方式返回的列表數據裡,每條動態裡都包含同樣的User,造成了數據重復:

請求:

http://www.xxx.com/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://www.xxx.com/{"User":{"id":100}, "[]":{"page":0, "count":5, "Moment":{"userId":"User/id"}}}

 

返回:

{
    "status": 200,
    "message": "success",
    "User": {
        "id": 100,
        "name": "Tommy",
        ...
    },
    "[]": {
        "0": {
            "Moment": {
                "id": 1,
                "content": "xxx1",
                ...
            }
        },
        "1": {
            "Moment": {
                "id": 2,
                "content": "xxx2",
                ...
            }
        },
        ...
    }
}

 

如果之前已經獲取到這個User了,還可以這樣省去所有重復User:

請求:

http://www.xxx.com/{"[]":{"page":0, "count":5, "Moment":{"userId":100}}}

 

返回:

{
    "status": 200,
    "message": "success",
    "[]": {
        "0": {
            "Moment": {
                "id": 1,
                "content": "xxx1",
                ...
            }
        },
        "1": {
            "Moment": {
                "id": 2,
                "content": "xxx2",
                ...
            }
        },
        ...
    }
}

 

客戶端:

傳統方式也可以服務端在接口增加一個返回格式字段,根據這個字段來決定是否去除重復User啊

我:

確實,不過這會導致以下問題:

1.服務端要新增字段和判斷字段來返回不同數據的代碼。

2.服務端要在文檔裡增加相關說明。

3.這個功能不一定用得上,因為客戶端的UI需求往往很容易變化,導致缺少使用這個功能的條件,為了一兩個版本去讓服務端和客戶端都折騰不值得。

而使用APIJSON就沒這些問題,因為根本不需要接口或文檔!而且是否去重只看客戶端的意願,服務端什麼都不用做。

客戶端:

這樣啊,贊!

哦對了,APIJSON相比傳統方式有沒有缺少的功能啊?

我:

傳統方式能做的APIJSON都能做。

客戶端和服務端的http json交互:
客戶端 -封裝request -> 服務端 - 解析request - 生成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表,包括id和需要改權限的table的tableName列表
     * @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或者發我郵件 [email protected],大家一起交流探討哈!

服務端:

感覺我以後不用寫一大堆接口了,不需要寫兼容代碼了,也不需要寫文檔了。可以專注於數據的處理、監控、統計、分析了哈哈!

客戶端:

我也不用等服務端寫好接口才能請求了,現在自己定制返回的JSON,看請求就知道返回的JSON結構,可以直接寫解析代碼了哈哈!

 

(注:以上是對真實對話的改編)

 

APIJSON,讓接口見鬼去吧!

Github源碼及文檔:(歡迎Star,歡迎Fork)

https://github.com/TommyLemon/APIJSON

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved