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

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

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

  3. 现有 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 version="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(){//链接websocketvar 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,使用此模块可实现接收推送通知和透传消息功能。

关于模块使用及注意事项,请仔细阅读模块说明文档

//初始化JpushSDKinitJpush(){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 :limitSecond={seconds} :limitCode={codeLen} onsetCode="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:'清除完成'})});
}

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" onclick={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>

后台代码

代码示例

<?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();}}//记录登录设备IDpublic function updatedeviceid(){checkscret('secret');//验证授权码checkdataPost('userid');//用户IDcheckdataPost('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();}}//获取最新的登录用户设备IDpublic 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');//用户IDcheckdataPost('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');//会议IDcheckdataPost('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');//会议IDcheckdataPost('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');//用户IDcheckdataPost('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');//用户IDcheckdataPost('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 usersigpublic 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"}
}

APICloud AVM框架 开发视频会议APP相关推荐

  1. APICloud AVM框架开发消防检查助手APP

    把消防检查过程中,需要手写填报的文档,在APP端以表单填写进行实现.同时可以添加手写签名,关联照片,而且APP端表单填报很多项目进行下拉选择,极大的提高了工作效率:表单填报完成之后可通过系统后台生成w ...

  2. 使用APICloud AVM框架开发预约应用

    前段时间跟朋友一起搞了一个预约的项目,前端用的APICloud的AVM框架做的,后端用的php开发的,用的tp5框架,没几天就搞出来了.简单跟大家分享一下开发中的一些功能点的实现吧.也欢迎大家一起探讨 ...

  3. 使用APICloud AVM框架开发人事档案管理助手APP

    由于人事档案具有涉密性,所以本应用没有使用后台服务,全部功能都在APP本地实现. 数据库采用sqllite,没有使用UI框架,个人觉得AVM本身支持的flex布局配合自写CSS样式,完全可以实现市面上 ...

  4. APICloud AVM框架 开发企业OA办公项目

    这是用AVM框架开发的第一个项目,踩了很多的坑,但是也成长了很多. 本项目主要是针对企业内部员工使用,除了大部分OA办公常用的功能模块,也有部分定制化的功能模块.后台用的PHP+BootStrap+E ...

  5. 使用YonBuilder移动开发平台开发视频会议App

    过去两年多时间里,视频会议成为职场工作乃至社会常态,在各类场景中得到广泛应用.例如企业会议.培训赋能.远程咨询.产品发布.远程面试等.本案例中的视频会议app来自开发者实战,采用YonBuilder移 ...

  6. APICloud AVM框架 封装日历组件

    AVM(Application-View-Model)前端组件化开发模式基于标准Web Components组件化思想,提供包含虚拟DOM和Runtime的编程框架avm.js以及多端统一编译工具,完 ...

  7. APICloud AVM框架 封装SKU(商品规格选择)组件

    AVM(Application-View-Model)前端组件化开发模式基于标准Web Components组件化思想,提供包含虚拟DOM和Runtime的编程框架avm.js以及多端统一编译工具,完 ...

  8. php语言能开发app吗_怎么利用PHP框架语言开发手机app?

    原标题:怎么利用PHP框架语言开发手机app? 一般的PHP框架都可以用来做app后台服务器.因为原理上客户端从你这边拿的都是字符串数据,所以就算你不用框架也没有问题,不过会引发后续的问题.PHP提供 ...

  9. APICloud AVM多端开发 | 《外卖app开发》项目源码深度解析(上)

    为了让开发者更加快速的学习和了解APICloud多端开发技术,APICloud平台特别推出一款多端源码-<餐饮点餐>,可以体验一套代码编译Android和iOS app+小程序. AVM多 ...

最新文章

  1. 日文版visual studio2005的安装
  2. BZOJ 4872 luogu P3750 [六省联考2017]分手是祝愿
  3. 【旧文章搬运】Win7可变对象头结构之InfoMask解析
  4. BW对于SAP SD模块有哪些作用
  5. linux kernel基本构成的内容有下列哪些项_Linux_GUI加速(2)_Linux中的DRM-KMS分析
  6. 【网络通信与信息安全】之深入解析TCP连接中如何确定客户端的端口号
  7. ST发布世界上首款LoRa Soc单片机STM32WL
  8. POSIX多线程API函数
  9. Oracle行转列语法总结大全
  10. 当我们谈微服务,我们在谈什么?谈谈我对微服务的理解!
  11. Maven 仓库使用与私有仓库搭建
  12. pool win10提示bad_快速解决Win10出现Bad pool caller蓝屏故障的技巧
  13. 为什么最近「骚扰电话」明显越来越多了?
  14. 关于大学生阶段团队类型选择
  15. linux系统上查询ip地址归属
  16. Unity独立游戏大集合
  17. 项目管理成熟度模型及项目量化管理
  18. 最新江苏水利水电安全员模拟真题及答案解析
  19. python 常见算法题
  20. 【前端单元测试入门03】Sinon

热门文章

  1. 4.3 脉冲响应不变法
  2. 杨旸:从边缘智能迈向泛在智能
  3. macOS 使用 X11 运行远端 linux 中的 x11 client 图形程序
  4. 实现轮播图,仅需3步
  5. 利用MATLAB命令求解运输问题
  6. 2020.7.8比赛总结
  7. USRP B210同步采集
  8. SLAM--VICP(Velocity Updating Iterative Closest Point Algorithm)学习笔记
  9. 浏览器打不开某些网站是什么原因导致,试试用这些方法来解决
  10. 【往届EI已检索】2023年第三届应用数学、建模与智能计算国际研讨会(CAMMIC2023)