1.7.1 客户端核心SDK

客户端核心SDK分为两个版本,一个是Web版,使用Typescript 语言编写;一个是原生版,使用c++11 语言。两者的架构跟业务功能是一致的,因此只要了解其中一版即可。下面以Web版(源码版本v1.10)为例,理解客户端SDK的设计。

1.源码架构

|-- echatim
|   |-- common
|   |   |-- Topic.ts # 社区版Topic的定义, api规约定义
|   |   |-- TopicProfessional.ts # 专业版Topic的定义, api规约定义
|   |   |-- UUID.ts
|   |   |-- WebToolkit.ts # 一些Web常用工具类
|   |   `-- utils.ts # js 通用方法
|   |-- core
|   |   |-- apis # 以下是http api与socket.io api的接口方法定义以及实现细节
|   |   |   |-- ApiResponse.ts
|   |   |   |-- EChatIMApis.ts
|   |   |   |-- HttpApi.ts
|   |   |   |-- IApi.ts
|   |   |   |-- IHttpConfig.ts
|   |   |   `-- SocketIOApi.ts
|   |   |-- cache
|   |   |   `-- jwt.ts # 登录用户的数字签名(jwt)
|   |   |-- fileclient # 文件服务器客户端功能
|   |   |   |-- FileServerClient.ts # 文件服务器客户端工厂
|   |   |   `-- support # 以下针对各种不同平台的实现方式, 如Web, ReactNative, 微信小程序
|   |   |       |-- AbstractClient.ts
|   |   |       |-- AbstractXMLHttpClient.ts
|   |   |       |-- BrowserXMLHttpClient.ts
|   |   |       |-- Fetch.ts
|   |   |       |-- PluploadClient.ts
|   |   |       |-- ReactNativeFetchClient.ts
|   |   |       |-- ReactNativeXMLHttpClient.ts
|   |   |       |-- SuffixMime.ts
|   |   |       `-- WxClient.ts
|   |   `-- sdk
|   |       |-- EChatIMSDK.ts # 主要sdk功能
|   |       |-- Errors.ts # sdk 错误码
|   |       |-- Socket.ts # 实现了socket.io 的代理, 连接,监听等
|   |       `-- SocketStatusManage.ts # socket.io 连接状态管理器
|   |-- log
|   |   `-- Logger.ts  # sdk日志方法
|   `-- model # sdk中用到的模型结构
|       |-- dto
          ... 省略 ...
|       |-- form
          ... 省略 ...
|       `-- protocol
          ... 省略 ...
|-- echatim.ts # 入口文件, 对引用模块导出EChatIMSDK实例 
`-- version.js # 记录sdk的版本信息

2.引用客户端sdk

在Web html中, 只需要以下的一行代码:

<script src="echatim-sdk.js"></script>

即可实现引入sdk, 这一过程是在echatim.ts中处理的:

if(typeof window !== 'undefined'){
    window['im'] = echatIMSDK;
    window['im_webtoolkit'] = WebToolkit;
    window['imsdk_version'] = version;
}

在加载echatim-sdk.js后, 使用window.im 即可访问sdk 实例echatIMSDK。

3.使用客户端sdk

echatIMSDK是单例的,是EChatIMSDK的一个对象。在Web Demo中,可见以下使用sdk的代码片段:

    var im = window.im;
    im.init(sdkConfig, function (sdk) {
        if (sdk) {
            console.info('echatIMSDK 成功连接, 可以使用 EChatIMApis 请求数据了.');
        } else {
            throw Error("echatIMSDK 初始化失败");
        }
    });

初始化时调用了echatIMSDK.init 方法, 来看看echatIMSDK.init 方法中做了什么事情:

init(config:EChatIMSDKConfig, callback: (sdk:EChatIMSDK) => void): EChatIMSDK {
        const self = this;
        // 1. 保存配置信息
        self.config.host = config.host ? config.host : 'localhost';
        self.config.socketPort = config.socketPort ? config.socketPort : 80;
        self.config.httpPort = config.httpPort ? config.httpPort : 80;
        self.transport = config.apiTransport ? config.apiTransport : 'HTTP';
        self.config.key = config.key;
        self.config.secret = config.secret;
        self.config.loginToken = config.loginToken;
        self.config.loginAuid = config.loginAuid;
        self.config.listeners = config.listeners;
        self.config.fileServerConfig = config.fileServerConfig;
        if(config.autoLogin !== undefined){
            self.config.autoLogin = config.autoLogin;
        }
        Logger.info(`ready to connect:${'http://' + self.config.host + ':' + self.config.socketPort}`);
        // 2. 开启socket.io 连接
        socket.connect('http://' + self.config.host + ':' + self.config.socketPort);
        if(!firstMonitorSocket){
            firstMonitorSocket = true;
            // 加入socket连接事件监听(可选)
            if(typeof self.config.listeners.onSocketConnectEvent === 'function'){
                const socketManage = new SocketStatusManage(socket['socket']);
                socketManage.addListener((status, data)=>{
                    self.config.listeners.onSocketConnectEvent(status, data);
                })
            }
        }
        // 3. 监听socket.io connect事件
        socket.listen('connect', function(msg: any) {
            Logger.info('apiSocket.io connected!');
            self.socketConnected = true;
            if(!firstInit && !self.config.autoLogin){
                callback(self);
                firstInit = true;
            }
            // 连接时自动登录
            if(self.config.autoLogin){
                self.autoLogin(function () {
                    self.logined = true;
                    callback(self); // 仅用户登录成功时, 初始化才完成.
                    if(self.config.listeners){
                        self.initConfigSocketioMessageListener();
                        self.initConfigImListener();
                    }
                });
            }
            // sdk 初始化时不自动登录, 需要外部应用自行管理用户的登录状态.
            else {
                // 仅初始化socketio 相关的消息监听即可
                self.initConfigSocketioMessageListener();
            }
            if(self.config.listeners && typeof self.config.listeners.onConnected === 'function'){
                self.config.listeners.onConnected();
            }
        });
       // 3. 监听socket.io disconnect事件
        socket.listen('disconnect', function(msg: any) {
            Logger.info('apiSocket.io disconnected!');
            self.socketConnected = false;
            self.logined = false;
            if(self.config.listeners && typeof self.config.listeners.onDisConnected === 'function'){
                self.config.listeners.onDisConnected();
            }
        });


        return this;
    }

在echatIMSDK.init 方法中,主要做了三件事情:

  1. 保存配置信息到sdk中;
  2. 开启socket.io 的连接;
  3. 监听socket.io 的connect, disconnect事件;

socket.io 的连接成功的事件监听中,sdk还处理了用户的自动登录(实现用户身份与socket.io通道的绑定); 自动请求部分api, 如: 获取好友列表,获取会话列表等,请求结果通过回调函数返回给应用层。

4.文件服务器客户端

在echatIMSDK 中,实现了newFileClient方法,通过该方法可以实例化一个文件上传实例。

    public newFileClient():IFileServerClient {
        // 根据sdk传入的配置信息,调用工厂方法,创建对应的客户端
        if(this.config.fileServerConfig){
            return FileServerClientFactory.create(this.config.fileServerConfig, this.config.key);
        }
        else {
            throw new Error('没有fileServerConfig配置, 无法创建file client 实例');
        }
    }

在业务系统中,有两种方式使用该文件上传功能:

// 方式一: 直接绑定到一个dom节点, 适用于Web版
html:
<button id="sendFileMessage" />
js:    
window.im.newFileClient().init({
            domId:domId, // 绑定的dom节点
            maxFileSize: 100,// 文件大小上限. 100mb
            type:'FILE',
            allowSuffix:[]// 允许上传的文件后缀名
        },{
            beforeUploadCallback:function (fileInfo) {
                return fileInfo;// 1.可以自定义fileInfo内容; 2.返回'cancel'时可以取消上传; 
            },
            progressCallback:function (fileInfo) {

            },
            uploadedCallback:function (fileInfo) {

            },
            errorCallback:function (fileInfo, errInfo) {

            },
        });


// 方式二: 使用返回的IFileServerClient实例, 手动调用upload方法
// 1.创建实例
this.fileClient = im.newFileClient();
// 2.初始化实例
this.fileClient.init({
            maxFileSize: 100,// 文件大小上限. 100mb
            type:'FILE',
            allowSuffix:[]// 允许上传的文件后缀名
        },{
            beforeUploadCallback:function (fileInfo) {
                return fileInfo;// 1.可以自定义fileInfo内容; 2.返回'cancel'时可以取消上传; 
            },
            progressCallback:function (fileInfo) {

            },
            uploadedCallback:function (fileInfo) {

            },
            errorCallback:function (fileInfo, errInfo) {

            },
        });
// 3.手动上传
this.fileClient.upload(filePath);// filePath: 文件的物理路径

5.拓展现有的api

在Web版源码v1.10后,echatIMSDK提供了httpApiCall, socketioApiCall 两个高级方法,可用于http api 和socket.io 的API拓展。

5.1 相关源码

    // http常规调用接口, 使用http POST方式访问后台api
    // url: 请求完整路径; headers: 请求头, json格式; body: 请求体, json格式
    public httpApiCall(url:string, headers:any, body: any): Promise<ApiResponse<any>> {
        const api = new HttpApi<any, any>(url, "通用http api");
        return api.rawCall(headers, body);
    }
    // socket.io常规调用接口, 使用socket.io方式访问后台api
    // url: 请求完整路径; body: 请求体, json格式
    public socketioApiCall(url:string, body: any): Promise<ApiResponse<any>> {
        const api = new SocketIOApi<any, any>(url, "通用socket.io api");
        return api.rawCall(body);
    }

5.2 一个例子

    // http api 例子
    const httpcall = window.im.httpApiCall.bind(window.im);
    function httpFetch(url, request) {
      return httpcall(url, request.headers, request.body)
    }
    // 使用: 定义header, body
    const headers = {
      'Content-Type': 'application/json',
      'Request-Id': new Date().getTime() + '' + parseInt(Math.random() * 1000 + ''),
      'timestamp': new Date().getTime(),
    };
    if (needAuth) {
      headers.authorization = 'client ' + jwt;
    }
    const params = {};
    const request = {headers: headers, body: params};
    httpFetch(url, request);
    // socket api 例子
    const socketcall = window.im.socketioApiCall.bind(window.im);
    function socketFetch(url, body) {
      return socketcall(url, body)
    }
   // 使用: socketFetch('topic.你的TOPIC定义' + '/' + '你的方法名', {})

results matching ""

    No results matching ""