实例|APICloud AVM框架开发视频会议APP

helloworld_98548882
• 阅读 316

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文件

<application name="targetSdkVersion" value="30"/>

具体的使用说明,在官方论坛中有专门的帖子,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

使用的是tencnetTRTC模块,查看模块文档

首先需要去申请腾讯云 SDKAppId,进入腾讯云实时音视频控制台 创建应用,即可看到 SDKAppId。

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

而且tencnetTRTC的接口相比较其他RTC模块更丰富,可以更好的满足一些个性化的需求。

消息事件

通过sendEvent把事件广播出去,然后在其他页面通过addEventListener监听事件,通过事件名和附带的参数进行其他操作。API对象说明文档

举例说明

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组件,可自定义验证码长度和再次获取时间间隔,自动校验验证码有效性。

示例代码

关于验证码的有效时间,是通过后台进行设定的,通过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:'清除完成'

})

});

}

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}

  • @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两种方式,如果接口中有其他的方式,可以在此基础上进行封装。

    下面以登录页为例,展示具体的使用。

    后台代码

    代码示例

    <?php

    namespace 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'];
    
        $content=$_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){
    
        $phones=$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"

    }

    }

    点赞
    收藏
    评论区
    推荐文章
    马丁路德 马丁路德
    3年前
    小程序开发不得不爬的坑,我替你爬过了!
    不得不说,目前这个项目做的真是够久,在开发过程中遇到了一些坑,解决了分享出来给大家。在各方面综合考虑之下,鄙人抛弃了各大多端开发框架,使用了原生的小程序框架进行开发。前人掘坑、后人遭殃,祝各位早日成为大牛!!👻👻👻👻自定义动态Tabbar导航栏在默认的小程序开发中,定义tabbar,需要在app.json中配
    目前APP开发有几大类型?
    还有APP开发也有很多类型模式,每个类型的成本和质量都不一样市面上存在的几种主流的开发类型:1、Web页面加壳生成app这种APP的开发方式,基本是用现有的手机网站,或者直接购买一个手机网站模板,加壳打包,直接生成一个APP,做出来的效果不好,消耗流量,用户体验也很差,访问速度慢等等,很多的外包公司利用客户不懂,把这几个小时甚至几分钟速成的东西,当成原生开发
    APICloud平台常用技术点汇总详解
    APICloud移动低代码开发平台介绍:使用APICloud可以开发移动APP、小程序、html5网页应用。如果要实现编写一套代码编译为多端应用(移动APP、小程序、html5),需使用avm.js框架进行开发。如果只开发APP,则可以使用前端技术(HTML5、Vue、react等)、avm.js进行开发,还可以使用模块商店大量的原生
    Stella981 Stella981
    2年前
    Android&Flutter混合开发初体验
    最近flutter不是一般的火呀,但对于一些成熟的产品来说,完全摒弃原有App的历史沉淀,全面转向Flutter是不现实的。因此使用Flutter去统一Android、iOS技术栈,把它作为已有原生App的扩展能力,通过有序推进来提升移动端的开发效率(1)创建flutter模块,创建module后会AS会自动进行flutter的相关配置!在这里
    Wesley13 Wesley13
    2年前
    APP-H5- 小程序区别 (转载)
    APP、H5、小程序区别●运行环境原生App直接运行在操作系统的单独进行中(安卓中可以开启多进程),而小程序运行环境是微信开发团队基于浏览器内核完全重构的一个内置解析器,针对性做了优化,配合自已定义的开发语言标准,提升了小程序的性能。是一种应用,并非完整的浏览器,只用到一部分H5技术,无法调用window对象和docu
    Stella981 Stella981
    2年前
    React Native与ExMobi技术路线探索
    随着Facebook陆续开源ReactNative的iOS和Android版本,这种以JavaScript来开发原生APP的方式在移动应用开发圈里得到广泛关注,虽然ReactNative并不是第一个采用JavaScript编写原生APP的产品,但是其独特的设计思想和实现方式是非常值得借鉴的。而作为国内老字号的移动应用开发平台,同样支持开发原生APP
    Stella981 Stella981
    2年前
    DCloud
    ylbtechDCloudMUI:HellomuiMUI最接近原生App体验的前端框架1.返回顶部1、MUI最接近原生App体验的前端框架极小100k的js文件,60k的css文件。原生编写,不依赖任何三方框架极强xcode和Androidstudio里所有原生控件都具备高
    程昱 程昱
    1个月前
    ChatGPT + Flutter快速开发多端聊天机器人App同步更新
    ChatGPTFlutter快速开发多端聊天机器人App同步更新想要学习ChatGPTFlutter快速开发多端聊天机器人App要先了解ChatGPT是什么其次要知道使用Flutter快速开发多端聊天机器人App时的步骤。download》quangn
    韦康 韦康
    1个月前
    ChatGPT + Flutter快速开发多端聊天机器人App同步更新
    ChatGPTFlutter快速开发多端聊天机器人App同步更新想要学习ChatGPTFlutter快速开发多端聊天机器人App要先了解ChatGPT是什么其次要知道使用Flutter快速开发多端聊天机器人App时的步骤。download》quangn
    陈元 陈元
    1星期前
    RN从0到1系统精讲与小红书APP实战(2023版)|完结无密
    RN从0到1系统精讲与小红书APP实战(2023版)|完结无密download》quangneng.com/159/ReactNative(RN)是一个流行的跨平台移动应用开发框架,允许开发者使用JavaScript和React来构建原生移动应用。下面是一