基于typeScript请求服务端的js-api-sdk统一封装思路

项目地址

在前端开发中向服务端请求数据是非常重要的,特别是在复杂的项目中对后台的api接口进行友好的调用是非常重要的(这里不得不说typeScript写起代码的体验是很爽的)。

基本思路,可以想后台一样进行接口封装,比如用户相关的接口一个MemberServices 里面有关于用户所有的api接口 例如getMemberById等,这样尽量语义话的调用。

思路如下:
(1)首先抽象出一个Api请求的对象接口(抽象类)

/**
 * Api客户端请求接口
 */
export abstract class ApiClientInterface<T> {

    /**
     * 请求服务端client工具对象
     * 非必填
     */
    private _client?: any;

    constructor(client: any) {
        this._client = client;
    }


    get client(): any {
        return this._client;
    }

    /**
     * post请求
     * @param option
     */
    post = (option: T): any => {
    }

    /**
     * get请求
     * @param option
     */
    get = (option: T): any => {
    }

    /**
     * 抓取数据
     * @param option
     */
    fetch = (option: T): any => {
    }
}

其中这个泛型T是一个请求配置对象,可以根据不同的项目或者是实现进行切换,这里提供一个基于阿里weex开源项目中stream请求对象封装的 ApiClient和API配置对象的示例

/**
 * 请求服务端的api统一接口失效
 */
class ApiClient extends ApiClientInterface<WeexStreamOption> {


    /**
     * @param client WEEX 中的steam对象
     */
    constructor(client: any) {
        super(client);
    }

    /**
     * post请求
     *
     */
    post = (option: WeexStreamOption) => {
        option.method = ReqMethod.POST;
        return this.fetch(option);
    }


    /**
     * get请求
     */
    get = (option: WeexStreamOption) => {
        option.method = ReqMethod.GET;
        return this.fetch(option);
    }


    /**
     * 获取数据
     * @param option
     * 默认 Content-Type 是 ‘application/x-www-form-urlencoded’。
     * 如果你需要通过 POST json , 你需要将 Content-Type 设为 ‘application/json’。
     */
    fetch = (option: WeexStreamOption): any => {

        option.data = isUndefined(option.data) ? {} : option.data;
        const sign = this.sign(option.signFields, option.data);  //获取签名字符串

        if (option.method === ReqMethod.GET) {
            //拼接参数
            let params = null;

            if (option.url.indexOf("?") > 0) {
                params = "&";
            } else {
                params = "?";
            }
            params += this.joinParamByReq(option.data);
            option.url += params;
            option.url += "&sign" + sign;
        } else if (option.method === ReqMethod.POST) {
            option.data['sign'] = sign;
        }

        console.log("请求url--> " + option.url);
        console.log("请求method--> " + option.method);
        console.log("请求headers--> " + option.headers);
        console.log("请求params--> " + JSON.stringify(option.data));

        option.type = isUndefined(option.type) ? DataType.JSON : option.type;

        let num = Math.round(Math.random() * 20)
        let data = {
            isSuccess: num % 2 === 0,
            num: num
        };
        option.progressCallback(data);
        setTimeout(function () {
            option.callBack(data);
        }, 1500)

        //WEEX stream对象 https://weex.apache.org/cn/references/modules/stream.html
        // this.client.fetch({
        //     method: ReqMethod[option.method],               //请求方法get post
        //     url: option.url,                     //请求url
        //     type: DataType[option.type],                    //响应类型, json,text 或是 jsonp {在原生实现中其实与 json 相同)
        //     headers: option.headers,             //headers HTTP 请求头
        //     body: JSON.stringify(option.data)     //参数仅支持 string 类型的参数,请勿直接传递 JSON,必须先将其转为字符串。请求不支持 body 方式传递参数,请使用 url 传参。
        // }, function (response) {
        //     console.log(response);
        //     /**
        //      * 响应结果回调,回调函数将收到如下的 response 对象:
        //      * status {number}:返回的状态码
        //      * ok {boolean}:如果状态码在 200~299 之间就为真
        //      * statusText {string}:状态描述文本
        //      * data {Object | string}: 返回的数据,如果请求类型是 json 和 jsonp,则它就是一个 object ,如果不是,则它就是一个 string。
        //      * headers {Object}:响应头
        //      */
        //     if (!response.ok) {
        //         //请求没有正确响应
        //         console.log("响应状态码:" + status + " 状态描述:" + response.statusText);
        //         return;
        //     }
        //     option.callBack(response.data, response.headers);
        // }, function (resp) {
        //     console.log(resp);
        //     /**
        //      * 关于请求状态的回调。 这个回调函数将在请求完成后就被调用:
        //      * readyState {number}:当前状态state:’1’: 请求连接中opened:’2’: 返回响应头中received:’3’: 正在加载返回数据
        //      * status {number}:响应状态码.
        //      * length {number}:已经接受到的数据长度. 你可以从响应头中获取总长度
        //      * statusText {string}:状态文本
        //      * headers {Object}:响应头
        //      */
        //
        //     if (option.progressCallback === null) {
        //         return;
        //     }
        //     option.progressCallback(resp);
        // });
    }

    /**
     * 将一个对象转换成url参数字符串
     * @param req
     * @return {string}
     */
    private joinParamByReq(req: Object): String {

        var result = "";
        for (let key in req) {
            result += key + "=" + req[key] + "&";
        }
        result = result.substr(0, result.length - 1);

        return result;
    }

    /**
     * ap请求时签名
     * @param fields
     * @param params
     * @return {string}
     */
    private sign = (fields: Array<String>, params: Object): String => {

        if (isUndefined(fields) || isNull(fields)) {
            return "";
        }
        let value = "";
        fields.forEach(function (item) {
            let param = params[item.toString()];
            if (isUndefined(param)) {
                // console.warn("参与签名的参数:" + item + " 未传入!");
                throw new Error("参与签名的参数:" + item + " 未传入!");
            }
            value += item + "=" + param + "&";
        });
        value = "timestamp=" + params['timestamp'];  //加入时间戳参与签名
        let sign = md5(value);

        return sign;
    }

}
export default ApiClient;
/**
 * weex stream请求参数对象
 */
export interface WeexStreamOption {

    /**
     * 请求url
     */
    url?: String;

    /**
     * 请求方法
     */
    method?: ReqMethod;

    /**
     * 结果数据类型
     */
    type?: DataType;

    /**
     * 请求头
     */
    headers?: Object;

    /**
     * 请求参数,仅post请求需要
     */
    data?: Object;

    /**
     * 参与签名的参数列表
     */
    signFields?:Array<String>;

    /**
     * 响应回调
     */
    callBack?:Function;

    /**
     * 关于请求状态的回调。 这个回调函数将在请求完成后就被调用:
     */
    progressCallback?:Function;
}

(2) 提供一个统一的实现,减少工作量,比起前端不是后端,不可能每一个接口都去实现一次,而且接口的流程非常统一,都是获取数据,只不过是url不同,参数不同,结果处理不同罢了。(这边实现使用Proxy对象)
抽象一个ServiceProxy对象

import ApiClient from "../ApiClient";
import {isUndefined} from "util";
import {DataType} from "./DataType";
import {ReqMethod} from "./ReqMethod";


/**
 * 服务代理
 */
export default class ServiceProxy {


    /**
     *
     * @param client       用于请求数据的客户端对象,非必填
     * @param handler      接口对象
     * @return {ServiceProxy} 返回代理对象
     */
    constructor(client: any = null) {

        const api = new ApiClient(client);
        const handler = this;
        console.log(handler);
        console.log(api)
        return new Proxy({}, {
            get: function (target, key, receiver) {
                return function () {
                    let params = arguments[0];
                    let options = arguments[1];
                    let methodName = arguments[2].toUpperCase();      //请求方法
                    let resultDataType = arguments[3].toUpperCase();  //结果数据类型
                    return new Promise(function (resove, reject) {
                        let config = handler[key]();  //获取配置
                        if (isUndefined(config)) {
                            throw new Error("请求的方法: " + key.toString() + " 未定义");
                        }
                        options.url = config.url;                 //请求的url
                        options.signFields = config.signFields;   //参与签名的请求参数
                        options.method = isUndefined(methodName) ? config.method : ReqMethod[methodName];        //请求方法 post、get
                        options.type = isUndefined(resultDataType) ? DataType.JSON : DataType[resultDataType];  //结果数类型
                        options.callBack = function (data) {
                            console.log("api接口" + config.url + " 返回数据-> " + data);
                            if (data.isSuccess) {
                                resove(data);
                            } else {
                                reject(data)
                            }
                        };
                        options.data=params;
                        let method = ReqMethod[options.method];
                        console.log(options);
                        api[method.toLowerCase()](options);
                    });
                };
            },
            set: function (target, key, value, receiver) {
                throw new Error("接口不允许设置值!");
            }
        });
    }
}
(3)剩下的就是写具体的实现了,示例如下:

import ServiceProxy from “./api/base/ServiceProxy”;
import ApiConfig from “./api/base/ApiConfig”;
import {WeexStreamOption} from “./api/WeexStreamOption”;
import {TestReq} from “./TestReq”;

/**
* 测试服务接口
*/
export default class TestService extends ServiceProxy {

constructor(client?: any) {
    super(client);
}


/**
 * 该方法写法固定,可以考虑使用自动生成
 * @param params 请求参数
 * @param option 请求配置
 * @param method 请求类型
 * @param dataType 结果数据类型
 * @return {ApiConfig}
 */
testApi(params: TestReq, option: WeexStreamOption = {}, method?: String, dataType?: String): any {

    return ApiConfig.newInstance("/api/test.htm", ["userName", "phoneCode"]);
};

}
服务中的方法实现非常统一,只是方法名和放回的ApiConfig对象中的数据不一样。这样实现以后对于服务调用就非常清晰了。

更多细节可以参考代码,项目地址在文章开头。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值