APP开发采纳的APICloud平台的AVM 多端利用开发框架

应用 avm.js 一个技术栈可同时开发 Android & iOS 原生 App、小程序和 iOS 轻 App,且多端渲染成果对立;

全新的 App 引擎 3.0 不依赖 webView,提供百分百的原生渲染,保障 App 性能和体验与原生 App 统一;

现有 api 间接映射兼容小程序接口,连续已有开发习惯;

后盾应用的PHP的thinkphp框架,通过composer集成各类插件。

思维导图

性能介绍
1.创立会议,确认会议工夫、参会人员、会议主题、确定会议主持人(默认为发起人)可开启会议;同时会通过利用音讯和短信告诉参会人员。

2.退出会议,可通过会议大厅找的会议列表间接退出,也可通过输出会议编号退出会议;退出会议的前提是会议已在进行中。

3.疾速会议,可间接确认会议人员而后发动实时视频会议,参会人员实时接管利用音讯或短信,疾速进入会议。

3.历史会议,分为我主持的会议、我参加的会议。

4.会议大厅,列表显示明天须要加入的会议。

5.会议纪要,会议完结后,会议主持人可通过APP或后盾零碎,把会议纪要整顿公布到相干会议中,参会人员可在会议详情中查看会议纪要。

6.会议附件,主持人员可在会议详情中,把会议相干的附件上传至相干会议中,参加人员可在会议详情中下载附件。

7.通讯录,展现零碎内的联系人,在创立会议时,会议中邀请人的时候会用到。

利用模块

我的项目目录

利用展现

开发介绍
利用导航
应用的是tabLayout布局作为利用的导航。

零碎首页应用tabLayout,能够将相干参数配置在JSON文件中,再在config.xml中将content的 值设置成该JSON文件的门路。如果底部导航没有非凡需要这里强烈建议大家应用tabLayout为APP进行布局,官网曾经将各类手机屏幕及不同的分辨率进行了适配,免去了很多对于适配方面的问题。

{    "name": "root",    "hideNavigationBar": true,    "navigationBar": {      "background": "#ffffff",      "color": "#333333",      "shadow": "#ffffff",      "hideBackButton": true    },    "tabBar": {      "scrollEnabled": false,      "background": "#fff",      "shadow": "#dddddd",      "color": "#aaaaaa",      "selectedColor": "#333333",      "index":0,      "preload": 0,      "frames": [{        "name": "home",        "url": "pages/main/home.stml",        "title": "会议"      }, {        "name": "classify-index",        "url": "pages/classify/classify-index.stml",        "title": "音讯"      }, {        "name": "shopping-index",        "url": "pages/shopping/shopping-index.stml",        "title": "文档"      }, {        "name": "my-index",        "url": "pages/my/my-index.stml",        "title": "我的"      }],      "list": [{        "text": "会议",        "iconPath": "image/tabbar/meeting.png",        "selectedIconPath": "image/tabbar/meeting-o.png",        "scale":3      }, {        "text": "音讯",        "iconPath": "image/tabbar/message.png",        "selectedIconPath": "image/tabbar/message-o.png",        "scale":3      }, {        "text": "文档",        "iconPath": "image/tabbar/doc.png",        "selectedIconPath": "image/tabbar/doc-o.png",        "scale":3      }, {        "text": "我的",        "iconPath": "image/tabbar/user.png",        "selectedIconPath": "image/tabbar/user-o.png",        "scale":3      }]    }  }

动静权限   
安卓10之后,对利用的权限要求进步,不在像老版本一样配置上就会主动获取,必须进行提醒。

根据官网给出的教程进行了动静权限的设置。

1.增加 mianfest.xml文件

<?xml versinotallow="1.0" encoding="UTF-8"?><manifest>    <application name="targetSdkVersion" value="30"/></manifest>

具体的应用阐明,在官方论坛中有专门的帖子,APP动静权限及Android平台targetSdkVersion设置 

在零碎主页进行动静权限获取,也可在非凡页面的中获取本页面所需的权限,这个可依据具体的业务需要进行解决。本零碎波及到了文件存储、摄像头、麦克风的获取,具体的获取形式见如下代码,因为本零碎的初始化页面时home.stml,所以在本页面的apiready()中进行权限验证。

apiready(){                let limits=[];        //获取权限        var resultList = api.hasPermission({          list: ['storage', 'camera', 'microphone']        });        if (resultList[0].granted) {          // 已受权,能够持续下一步操作        } else {          limits.push(resultList[0].name);        }        if (resultList[1].granted) {          // 已受权,能够持续下一步操作        } else {          limits.push(resultList[1].name);        }        if (resultList[2].granted) {          // 已受权,能够持续下一步操作        } else {          limits.push(resultList[2].name);        }         if(limits.length>0){          api.requestPermission({            list: limits,          }, (res) => {                      });        }            }

WebSocket
用于即时通话的时候,监听用户在线状态,可告诉用户退出会议。

具体的通信原理步骤是:

会议发起人发动会议-》通过websocket给参会人员发送音讯指令-》参会人员接管发送的websocket音讯,通过监听触发进入会议房间,同时给会议发起人发送进入会议房间的音讯-》会议发起人收到有人进入了会议房间音讯后,通过监听触发进入会议房间的操作。 这种流程是会议发起人不用先进入回房间进行期待,不必启用RTC模块,只有当有其余人员收到揭示进入会议房间后才会启用RTC模块进入房间。能够无效的防止资源节约。

还有一中繁难模式,会议发起人发动会议,并启用RTC模块,进入会议房间进行期待(判断等待时间,比方超过3分钟没有其余人员退出房间,主动退出会议房间完结会议)-》通过websocket给参会人员发送音讯指令-》参会人员接管发送的websocket音讯,通过监听触发进入会议房间。这种模式如果其余参会人员不及时加入会议的时候会造成局部资源的节约。

进入会议后其余后续的操作,就能够通过tencnetTRTC模块中的办法进行解决。

websocket的目标就是即时的告诉参会人员有会议要加入,因为RTC模块自身没有集成这个性能。这部分操作是在进入会议房间之前的操作。

本APP用的是websocket模块,本模块可配置全局变量,不便实用。当然也能够尝试其余的websocket模块。

 AVM框架里官网就集成了websocket。

apiready(){    //链接websocket    var webSocket = api.require('webSocket');    //音讯监听,能够监听连贯,断开,接管音讯等事件    webSocket.addEventListener((ret, err) => {      console.log(JSON.stringify(ret) + "  " + JSON.stringify(err));      //断开重连      if(ret.evenType=='Closed'){        webSocket.open({          url : 'ws://192.168.1.5:8888/socket'        }, (ret, err) => {          console.log(JSON.stringify(ret) + "  " + JSON.stringify(err));        });      }      //收到音讯      if(ret.evenType=='ReturnData'){        //解析data中的内容,获取会议房间ID进入会议      }    });    //获取以后的websocket链接状态    var webSocketStatus = webSocket.getConnectState();    //未链接则进行链接,如果已链接则有效操作    if(webSocketStatus.State =='CLOSED'){      webSocket.open({        url : 'ws://192.168.1.5:8888/socket'      }, (ret, err) => {        console.log(JSON.stringify(ret) + "  " + JSON.stringify(err));      });    }  },

 视频通话 RTC

 首先须要去申请腾讯云 SDKAppId,进入腾讯云实时音视频。

为什要用tencnetTRTC呢,因为tencnetTRTC模块不会把SDKAppId与利用进行绑定,这样就能够应用一个SDKAppId来实现两个不同的APP之间的视频通话了,共用腾讯云的通话时长。

而且tencnetTRTC的接口相比拟其余RTC模块更丰盛,能够更好的满足一些个性化的需要。

音讯事件
通过sendEvent把事件播送进来,而后在其余页面通过addEventListener监听事件,通过事件名和附带的参数进行其余操作。

举例说明

1.当创立会议胜利之后,须要发送一个会议创立胜利的事件;在会议列表或者其余展现会议的页面,须要监听此事件,而后在监听胜利的回调中做刷新的操作。

2.当会议开始或者完结之后,须要发送相应的事件,在会议列表或者其余展现会议的页面,须要监听此类事件,在监听胜利的回调中做刷新列表或者更改会议状态的操作。

音讯推送
ajpush模块封装了极光推送平台的SDK,应用此模块可实现接管推送告诉和透传音讯性能。

对于模块应用及注意事项,请仔细阅读模块阐明文档

//初始化JpushSDK    initJpush(){      var jpush = api.require('ajpush');      jpush.init((ret, err)=>{        if(ret && ret.status){          //绑定别名          if(api.getPrefs({sync: true,key: 'userid'})){            jpush.bindAliasAndTags({              alias:api.getPrefs({sync: true,key: 'userid'}),              tags:['APPUSER']            }, (ret, err)=>{              if(ret.statusCode==0){                api.toast({ msg: '推送服务初始化胜利'});              }              else{                api.toast({ msg: '绑定别名失败'});              }            });          }          //监听音讯          jpush.setListener((ret) => {            // var content = ret.content;            api.toast({ msg: ret.content});          });        }      else{          api.toast({ msg: '推送服务初始化失败'});        }      });      api.addEventListener({name:'pause'}, function(ret,err) {        jpush.onResume();//监听利用进入后盾,告诉jpush暂停事件      })      api.addEventListener({name:'resume'}, function(ret,err) {        jpush.onResume();//监听利用复原到前台,告诉jpush复原事件      })          },

短信验证码
用户注册的时候须要通过手机短信验证码进行校验,以保障手机号真实有效,可能失常接管利用推送的各类短信告诉揭示。

本利用中应用的是AVM模块库中的verification-code-input组件,可自定义验证码长度和再次获取工夫距离,主动校验验证码有效性。

<template>  <view class="page">    <safe-area></safe-area>    <verification-code-input :limitSecnotallow={seconds} :limitCode={codeLen} notallow="getCode"></verification-code-input>  </view></template><script>  import '../../components/verification-code-input.stml'  export default {    name: 'demo-verification-code-input',    apiready(){    },    data() {      return{        code:'',        seconds:60,        codeLen:4      }    },    methods: {          getCode(e){        // console.log(JSON.stringify(e.detail));        this.data.code = e.detail;      }    }  }</script>

对于验证码的无效工夫,是通过后盾进行设定的,通过session缓存每个手机号的验证码,并设置缓存无效工夫,表单提交的时候通过session去获取验证码,如果session生效,则无奈获取验证码,接口可间接返回验证码生效提醒。 

清空缓存
首先通过getCacheSize获取利用的缓存数量,并在标签中显示,而后给标签增加点击事件,在事件中通过clearCache革除利用缓存。

计算以后利用的缓存大小,保留认为小数。 

apiready(){  //获取APP缓存 异步返回后果:  api.getCacheSize((ret) => {    this.data.cache = parseInt(ret.size/1024/1024).toFixed(1);  });},

执行革除缓存,并提示信息。 

clearCache(){
api.clearCache(() => {

this.data.cache=0.0;api.toast({  msg:'革除实现'})

});
}
1.
2.
3.
4.
5.
6.
7.
8.
AVM组件应用
我的项目中应用了很多的AVM组件,其中包含视频通话组件、通讯录组件、滑动单元格组件、日期工夫Picker组件、数字键盘组件等等。

其中视频通话组件(easy-video-call、easy-voice-communication、multi-person-video-call)用的是声网的SDK,这里借用了款式,把模块换成了TencentRTC。

音讯列表列表中应用了easy-swiper-cell滑动单元格组件,来实现滑动操作已读。

期间和工夫抉择用到了time-picker、date-picker组件。

通讯录应用的是address-book组件。

在通过会议编号进入会议时,因为会议编号全是数字,这里应用了number-keyboard数组键盘组件。

文档下载、图片浏览
会议完结后会上传会议纪要,会议相干文件等各类文档,次要包含doc、excel、pdf和图片。

对于doc、excel、pdf这类文件应用的是docReader模块。形式是先通过api.download办法下载文,而后在回调中通过docReader模块唤醒三方工具进行文件浏览。

//下载、浏览附件    loadfile(url){      api.download({          url: url,          // savePath: 'fs://appDownload/',//不选主动创立门路          report: true,          cache: true,          allowResume: true      }, (ret, err)=> {          if (ret.state == 1) {              //下载胜利              api.hideProgress();              var path=ret.savePath;              // alert('下载胜利,文件门路:'+ret.savePath);              var docReader = api.require('docReader');              docReader.open({                  path: path,                  autorotation: false              }, (ret, err) => {                  if (!ret.status) {                      if(err.code=='1'){                        alert('关上文件谬误,请自行查找文件关上,门路:'+path);                      }                      else if(err.code=='2'){                        alert('文件格式谬误,请自行查找文件关上,门路:'+path);                      }                  }              });          }          else if(ret.state == 0){            api.showProgress({              title: '致力下载中...',              text: ret.percent+'%',              modal: false            });          }          else if(ret.state == 2) {              api.hideProgress();              alert('下载失败,请重试。');          }      });    }

图片应用的是photoBrowser模块进行浏览

picturePreview(e){  let images = e.currentTarget.dataset.list;  //预览图片  var photoBrowser = api.require('photoBrowser');  photoBrowser.open({    images: images,    bgColor: '#000'  }, function(ret, err) {    if(ret.eventType=='click'){      photoBrowser.close();    }  });}

单设施登陆
本APP做了繁多设施登陆的限度,具体实现形式是,通过api.deviceId能够获取到收的设施ID,用户登陆胜利之后进行设施绑定;APP初始化的时候进行设施验证,先通过接口获取数据库中记录的用户上次登录的设施ID,而后与本机设备ID进行比对,如果设施ID不统一则跳转登陆页面。

//注销设施          setDeviceID(){        var data={          secret:'',          userid:api.getPrefs({sync: true,key: 'userid'}),          deviceid:api.deviceId        };        api.showProgress();        POST('updatedeviceid',data,{}).then(ret =>{          // console.log(JSON.stringify(ret));          if(ret.flag=='Success'){            api.toast({              msg:'设施注销胜利'            })          }                 api.hideProgress();        }).catch(err =>{          api.toast({            msg:JSON.stringify(err)          })        })      }
//验证设施    checkDeviceID(){      var data={        secret:'',        userid:api.getPrefs({sync: true,key: 'userid'})      };      api.showProgress();      POST('querydeviceidbynew',data,{}).then(ret =>{        // console.log(JSON.stringify(api.deviceId));        if(ret.flag=='Success'){          if(ret.data.deviceid != api.deviceId){            api.toast({              msg:'您的设施已在其余设施上登录,请从新登录。'            })            $util.openWin({              name: 'login',              url: 'widget://pages/seeting/login.stml',              title: '',              hideNavigationBar:true            });          }        }               api.hideProgress();      }).catch(err =>{        api.toast({          msg:'设施登陆异样,请从新登陆。'        })        $util.openWin({          name: 'login',          url: 'widget://pages/seeting/login.stml',          title: '',          hideNavigationBar:true        });      })    }

接口调用
封装了 req.js进行接口调用,采纳了ES6语法中的Promise是异步编程的一种解决方案(比传统的回调函数更加正当、弱小),用同步操作将异步流程表达出来。防止层层嵌套回调。promise 对象提供对立接口,使得管制异步操作更加容易。有趣味的同学能够多钻研一下Promise。

const config = {    schema: 'http',    host: '192.168.1.5',    path: 'index.php/Home/api/',    secret:'1f3ef6ac********6deecd990f'}function req(options) {    const baseUrl = `${config.schema}://${config.host}/${config.path}/`;    options.url = baseUrl + options.url;    return new Promise((resolve, reject) => {        api.ajax(options,  (ret, err) => {            console.log('[' + options.method + '] ' + options.url + ' [' + api.winName + '/' + api.frameName + ']\n' + JSON.stringify({                ...options, ret, err            }))            if (ret) {                resolve(ret);                api.hideProgress();            } else {                reject(err);                 api.hideProgress();            }        });    })}/** * GET申请快捷办法 * @constructor * @param url {string} 地址 * @param options {Object} 附加参数 */function GET(url, options = {}) {    return req({        ...options, url, method: 'GET'    });}/** * POST 申请快捷办法 * @param url * @param data * @param options {Object} 附加参数 * @returns {Promise<Object>} * @constructor */function POST(url, data, options = {}) {    data.secret = config.secret;    return req({        ...options, url, method: 'POST', data: {            values: data        }    });}export {    req, GET, POST, config}

在stml页面中,首先要援用封装好的req.js,目前只封装了POST、GET两种形式,如果接口中有其余的形式,能够在此基础上进行封装。

上面以登录页为例,展现具体的应用。

<template>    <scroll-view class="page">        <safe-area></safe-area>        <view class="top">            <text class="top-title">登录</text>            <text class="top-sub-title">欢送应用逍遥自在云视频会议,让您从此无忧工作!</text>        </view>        <view class="input-box">            <image class="item-ico" src='../../image/user.png' mode="widthFix"></image>            <input class="item-input" placeholder="请输出账号" v-model="username"/>        </view>        <view class="input-box">            <image class="item-ico" src='../../image/psw.png' mode="widthFix"></image>            <input class="item-input" type="password" placeholder="请输出明码" v-model="password"/>        </view>        <view class="btn-box">            <button class="btn" notallow={this.login}>确定</button>        </view>    </scroll-view></template><script>    import {POST} from '../../script/req.js'    export default {        name: 'login',        apiready(){            //监听返回  双击退出程序            api.setPrefs({                key: 'time_last',                value: '0'            });            api.addEventListener({                name : 'keyback'                }, function(ret, err) {                var time_last = api.getPrefs({sync: true,key: 'time_last'});                var time_now = Date.parse(new Date());                if (time_now - time_last > 2000) {                    api.setPrefs({key:'time_last',value:time_now});                    api.toast({                        msg : '再按一次退出APP',                        duration : 2000,                        location : 'bottom'                    });                } else {                    api.closeWidget({                        silent : true                    });                }            });        },        data() {            return{                username:'',                password:''            }        },        methods: {            login(){                if (!this.data.username) {                    this.showToast("姓名不能为空");                    return;                }                if (!this.data.password) {                    this.showToast("明码不能为空");                    return;                }                 var data={                    secret:'',                    user:this.data.username,                    psw:this.data.password                };                api.showProgress();                POST('loginuser',data,{}).then(ret =>{                    // console.log(JSON.stringify(ret));                    if(ret.flag=='Success'){                        api.setPrefs({key:'username',value:ret.data.username});                        api.setPrefs({key:'userid',value:ret.data.id});                        api.setPrefs({key:'deviceid',value:ret.data.deviceid});                        api.setPrefs({key:'phone',value:ret.data.phone});                        //注销设施                        this.setDeviceID();                        api.sendEvent({                            name: 'loginsuccess',                        });                        api.closeWin();                    }                    else{                        api.toast({                            msg:'登录失败!请稍后再试。'                        })                    }                    api.hideProgress();                }).catch(err =>{                    api.toast({                        msg:JSON.stringify(err)                    })                })            },            //注销设施            setDeviceID(){                var data={                    secret:'',                    userid:api.getPrefs({sync: true,key: 'userid'}),                    deviceid:api.deviceId                };                api.showProgress();                POST('updatedeviceid',data,{}).then(ret =>{                    // console.log(JSON.stringify(ret));                    if(ret.flag=='Success'){                        api.setPrefs({key:'deviceid',value:api.deviceid});                        api.toast({                            msg:'设施注销胜利'                        })                    }                                   api.hideProgress();                }).catch(err =>{                    api.toast({                        msg:JSON.stringify(err)                    })                })            }        }    }</script><style>    .page {        height: 100%;        background-color:#ffffff;    }    .top{        margin-top: 50px;        margin-left: 20px;        margin-bottom: 100px;    }    .top-title{        font-size: 25px;        font-weight: bold;    }    .top-sub-title{        font-size: 13px;        font-weight: bold;    }    .input-box{        margin: 20px;        border-bottom: 1px solid #ccc;        padding-bottom: 5px;        flex-flow: row nowrap;        align-items: center;    }    .item-input{        width: auto;        border: 0;        font-size: 18px;        margin-left: 10px;    }    .item-ico{        width: 35px;    }    .btn-box{        margin-top: 50px;        margin-left: 10px;        margin-right: 10px;    }    .btn{        background-color: #256fff;        color: #ffffff;        font-size: 20px;        border-radius: 20px;        padding: 10px 0;        font-weight: bold;    }</style>

后盾代码
代码示例

<?phpnamespace Home\Controller;require 'vendor/autoload.php';    // 留神地位肯定要在 引入ThinkPHP入口文件 之前use Think\Controller;use JPush\Client as JPushClient;use AlibabaCloud\Client\AlibabaCloud;use AlibabaCloud\Client\Exception\ClientException;use AlibabaCloud\Client\Exception\ServerException;class ApiController extends Controller {    public function index(){        $this->show('');    }    //用户登录    public function loginuser(){        checkscret('secret');//验证受权码        checkdataPost('user');//账号        checkdataPost('psw');//明码          $map['username']=$_POST['user'];        $map['password']=$_POST['psw'];        $map['zt']='T';                $releaseInfo=M()->table('user')->field('id,username,phone,deviceid,role')->where($map)->find();          if($releaseInfo){            returnApiSuccess('登录胜利',$releaseInfo);          }          else{            returnApiError( '登录失败,请稍后再试');            exit();          }      }       //记录登录设施ID      public function updatedeviceid(){        checkscret('secret');//验证受权码        checkdataPost('userid');//用户ID        checkdataPost('deviceid');//设施ID        $userid=$_POST['userid'];        $deviceid=$_POST['deviceid'];        $map['id']=$userid;        $data['deviceid']=$deviceid;        $releaseInfo=M()->table('user')->where($map)->save($data);        if($releaseInfo){          returnApiSuccess('注销胜利',$releaseInfo);        }        else{          returnApiError( '注销失败,请稍后再试');          exit();        }    }    //获取最新的登录用户设施ID    public function querydeviceidbynew(){        checkscret('secret');//验证受权码        checkdataPost('userid');//用户ID        $userid=$_POST['userid'];        $map['id']=$userid;        $releaseInfo=M()->table('user')->field('deviceid')->where($map)->find();        if($releaseInfo){          returnApiSuccess('查问胜利',$releaseInfo);        }        else{          returnApiError( '查问失败,请稍后再试');          exit();        }    }    //APP批改明码    public function updatepassword(){        checkscret('secret');//验证受权码        checkdataPost('userid');//用户ID         checkdataPost('password');//明码            $userid=$_POST['userid'];        $password=$_POST['password'];          $map['id']=$userid;        $data['password']=$password;        $releaseInfo=M()->table('user')->where($map)->save($data);        if($releaseInfo){            returnApiSuccess('批改胜利',$releaseInfo);        }        else{            returnApiError( '批改失败,请稍后再试');            exit();        }      }    //新增会议    public function addhuiyi(){        checkscret('secret');//验证受权码        checkdataPost('userid');//ID        $userid=$_POST['userid'];        $title=$_POST['title'];        $cnotallow=$_POST['content'];        $users=$_POST['users'];        $hysj=$_POST['hysj'];        $hylx=$_POST['hylx'];          $data['title']=$title;        $data['content']=$content;        $data['fqr']=$userid;        $data['cyr']=$users;        $data['hysj']=$hysj;        $data['flag']='01';//未开始        $data['cjsj']=time();        $data['type']=$hylx;        $data['txsj']=date('Y-m-d H:i:s',strtotime("$hysj-10 minute"));        $data['istip']='01';        $arruser=explode(',',$users);               $releaseInfo=M()->table('meeting')->data($data)->add();        if($releaseInfo){                 //发送音讯            $this->setmessage($users,'您有一个视频会议须要加入,工夫:'.$hysj);            //发送短信告诉            //$this->pushmsgbyusers($users,$hysj);            //极光推送            try{              $jpush = new JPushClient(C('JPUSH_APP_KEY'), C('JPUSH_MASTER_SECRET'));              $response = $jpush->push()                  ->setPlatform('all')  //机型 IOS ANDROID                  ->addAlias($arruser)                  ->androidNotification($content)                  ->iosNotification($content,'',0,true)                  ->options(array(                      'apns_production' => true,                  ))                  ->send();                        returnApiSuccess('增加胜利');              }              catch(\Exception $e){                returnApiSuccess('增加胜利');                exit();              }                 }        else{          returnApiError('增加失败,请稍后再试!');          exit();        }    }    //查问会议大厅    public function querymeeting(){      checkscret('secret');//验证受权码      checkdataPost('userid');//用户ID      checkdataPost('limit');//下一次加载多少条      $userid=$_POST['userid'];      $where['fqr']=$userid;      $where['_string']='find_in_set('.$userid.',cyr)';      $where['_logic']='or';      $map['_complex']=$where;      $map['flag']=array('neq','03');               $limit=$_POST['limit'];      $skip=$_POST['skip'];      if(empty($skip)){        $skip=0;      }      $releaseInfo=M()->table('meeting')->field('id,title,flag,hysj,sjzd(type,\'会议类型\') hylx,cyr,fqr,type')->where($map)->limit($skip,$limit)->order('hysj desc')->select();         if($releaseInfo){        returnApiSuccess('查问胜利',$releaseInfo);      }      else{        returnApiError( '没有查问到任何数据');        exit();      }    }    //设置会议状态    public function setmeeting(){      checkscret('secret');//验证受权码      checkdataPost('id');//会议ID      checkdataPost('flag');//会议状态      $id=$_POST['id'];      $flag=$_POST['flag'];      $map['id']=$id;      $data['flag']=$flag;      if($flag=='02'){        $data['start']=time();      }      else if($flag=='03'){        $data['end']=time();      }      $releaseInfo=M()->table('meeting')->where($map)->save($data);      if($releaseInfo){        returnApiSuccess('更新胜利',$releaseInfo);      }      else{        returnApiError( '没有查问到任何数据');        exit();      }    }    //上传会议纪要    public function addhyjy(){      checkscret('secret');//验证受权码      checkdataPost('id');//会议ID      checkdataPost('hyjy');//会议纪要      $id=$_POST['id'];      $hyjy=$_POST['hyjy'];      $map['id']=$id;      $data['jiyao']=$hyjy;      $releaseInfo=M()->table('meeting')->where($map)->save($data);      if($releaseInfo){        returnApiSuccess('上传胜利',$releaseInfo);      }      else{        returnApiError( '没有查问到任何数据');        exit();      }    }    //查问历史会议    public function queryhistory(){      checkscret('secret');//验证受权码      checkdataPost('userid');//用户ID      checkdataPost('limit');//下一次加载多少条      $userid=$_POST['userid'];      $where['fqr']=$userid;      $where['_string']='find_in_set('.$userid.',cyr)';      $where['_logic']='or';      $map['_complex']=$where;      $map['flag']=array('eq','03');               $limit=$_POST['limit'];      $skip=$_POST['skip'];      if(empty($skip)){        $skip=0;      }      $releaseInfo=M()->table('meeting')->field('id,title,hysj')->where($map)->limit($skip,$limit)->order('hysj desc')->select();         if($releaseInfo){        returnApiSuccess('查问胜利',$releaseInfo);      }      else{        returnApiError( '没有查问到任何数据');        exit();      }    }    //查问会议详情    public function queryhistoryinfo(){      checkscret('secret');//验证受权码      checkdataPost('id');//会议ID            $id=$_POST['id'];      $map['id']=$id;      $releaseInfo=M()->table('meeting')->field('id,title,hysj,content,getusers(cyr) users,sjzd(type,\'会议类型\') type,jiyao,getmeetinglong(id) sc')->where($map)->find();         if($releaseInfo){        returnApiSuccess('查问胜利',$releaseInfo);      }      else{        returnApiError( '没有查问到任何数据');        exit();      }    }    //发送音讯告诉    function setmessage($users,$content){      $arruser=explode(',',$users);      foreach ($arruser as $item) {        $data['user']=$item;        $data['content']=$content;        $data['shijian']=time();        $data['sfyd']='01';        $info=M()->table('sp_message')->data($data)->add();      }    }        //查问音讯    public function querymessage(){      checkscret('secret');//验证受权码      checkdataPost('userid');//用户ID      checkdataPost('limit');//下一次加载多少条      $userid=$_POST['userid'];      $map['user']=$userid;            $limit=$_POST['limit'];      $skip=$_POST['skip'];      if(empty($skip)){        $skip=0;      }      $releaseInfo=M()->table('message')->field('id,content,sfyd,from_unixtime(shijian,\'%Y-%m-%d %H:%i:%s\') sj')->where($map)->limit($skip,$limit)->order('sj desc')->select();         if($releaseInfo){        returnApiSuccess('查问胜利',$releaseInfo);      }      else{        returnApiError( '没有查问到任何数据');        exit();      }    }    //设置音讯已读    public function setxxyd(){      checkscret('secret');//验证受权码      checkdataPost('id');//ID      $id=$_POST['id'];      $map['id']=$id;      $data['sfyd']='02';      $releaseInfo=M()->table('message')->where($map)->save($data);      if($releaseInfo){        returnApiSuccess('设置胜利',$data);      }      else{        returnApiError( '设置失败,请稍后再试');        exit();      }          }  //推送用户短信揭示  function pushmsgbyusers($users,$shijian){    $map['_string']='find_in_set(id,\''.$users.'\')';    $data=M()->table('user')->field('group_concat(trim(phone)) phones')->where($map)->find();    if($data){        $phnotallow=$data['phones'];        //发送验证码               AlibabaCloud::accessKeyClient(C('accessKeyId'), C('accessSecret'))                          ->regionId('cn-beijing')                          ->asDefaultClient();        try {            $param = array("datetime"=>$shijian);            $result = AlibabaCloud::rpc()                      ->product('Dysmsapi')                      // ->scheme('https') // https | http                      ->version('2017-05-25')                      ->action('SendSms')                      ->method('POST')                      ->host('dysmsapi.aliyuncs.com')                      ->options([                            'query' => [                            'RegionId' => "cn-beijing",                            'PhoneNumbers' =>$phones,                            'SignName' => "****有限公司",                            'TemplateCode' => "SMS_****",                            'TemplateParam' => json_encode($param),                          ],                      ])                      ->request();        }catch (ClientException $e) {                  }        return $result;    }  }  //获取腾讯视频RTC usersig  public function getQQrtcusersig(){    checkscret('secret');//验证受权码    checkdataPost('userid');//用户ID    $sdkappid=C('sdkappid');    $key=C('usersig_key');    $userid=$_POST['userid'];        require 'vendor/autoload.php';        $api = new \Tencent\TLSSigAPIv2($sdkappid, $key);    $sig = $api->genSig($userid);        if($sig){      returnApiSuccess('查问胜利',$sig);    }    else{      returnApiError( '查问失败,请稍后再试');      exit();    }  }}

插件援用
用到了阿里短信插件、极光推送插件、腾讯RTC签名插件;通过composer装置。

composer.json文件

{    "config": {          "secure-http": false      },    "require": {        "jpush/jpush": "^3.6",        "tencent/tls-sig-api-v2": "1.0",        "alibabacloud/client": "^1.5"    }}