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

简介: APP开发采用的APICloud平台的AVM多端应用开发框架,使用 avm.js 一个技术栈可同时开发 Android & iOS 原生 App、小程序和 iOS 轻 App,且多端渲染效果统一;全新的 App 引擎 3.0 不依赖 webView,提供百分百的原生渲染,保障 App 性能和体验与原生 App 一致;

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 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(){

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




示例代码


<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();

         }

     }

 

      //记录登录设备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"

}

}


目录
相关文章
|
2天前
|
存储 移动开发 Android开发
使用kotlin Jetpack Compose框架开发安卓app, webview中h5如何访问手机存储上传文件
在Kotlin和Jetpack Compose中,集成WebView以支持HTML5页面访问手机存储及上传音频文件涉及关键步骤:1) 添加`READ_EXTERNAL_STORAGE`和`WRITE_EXTERNAL_STORAGE`权限,考虑Android 11的分区存储;2) 配置WebView允许JavaScript和文件访问,启用`javaScriptEnabled`、`allowFileAccess`等设置;3) HTML5页面使用`<input type="file">`让用户选择文件,利用File API;
|
6天前
|
运维 Android开发 开发者
你知道APP是怎么开发的吗?
【7月更文挑战第9天】你知道APP是怎么开发的吗?
|
23小时前
|
Android开发 Kotlin
kotlin开发安卓app,如何让布局自适应系统传统导航和全面屏导航
使用`navigationBarsPadding()`修饰符实现界面自适应,自动处理底部导航栏的内边距,再加上`.padding(bottom = 10.dp)`设定内容与屏幕底部的距离,以完成全面的布局适配。示例代码采用Kotlin。
30 15
|
1天前
|
存储 API Android开发
kotlin开发安卓app,使用webivew 触发 onShowFileChooser, 但只能触发一次,第二次无法触发,是怎么回事。 如何解决
在Android WebView开发中,`onShowFileChooser`方法用于开启文件选择。当用户只能选择一次文件可能是因为未正确处理选择回调。解决此问题需确保:1) 实现`WebChromeClient`并覆写`onShowFileChooser`;2) 用户选择文件后调用`ValueCallback.onReceiveValue`传递URI;3) 传递结果后将`ValueCallback`设为`null`以允许再次选择。下面是一个Kotlin示例,展示如何处理文件选择和结果回调。别忘了在Android 6.0+动态请求存储权限,以及在Android 10+处理分区存储。
|
11天前
|
JSON 小程序 数据格式
uni-app 使用vscode开发uni-app
uni-app 使用vscode开发uni-app
41 0
|
12天前
|
JSON 前端开发 API
移动端---------app开发03----apicloud必须掌握的代码
移动端---------app开发03----apicloud必须掌握的代码
|
22天前
|
编解码 Java Android开发
FFmpeg开发笔记(三十一)使用RTMP Streamer开启APP直播推流
RTMP Streamer是一款开源的安卓直播推流框架,支持RTMP、RTSP和SRT协议,适用于各种直播场景。它支持H264、H265、AV1视频编码和AAC、G711、OPUS音频编码。本文档介绍了如何使用Java版的RTMP Streamer,建议使用小海豚版本的Android Studio (Dolphin)。加载项目时,可添加国内仓库加速依赖下载。RTMP Streamer包含五个模块:app、encoder、rtmp、rtplibrary和rtsp。完成加载后,可以在手机上安装并运行APP,提供多种直播方式。开发者可以从《FFmpeg开发实战:从零基础到短视频上线》获取更多信息。
53 7
FFmpeg开发笔记(三十一)使用RTMP Streamer开启APP直播推流
|
19天前
|
数据可视化 数据处理 Swift
Swift开发——简单App设计
SwiftUI教程概述:简化App设计,通过代码展示了如何创建一个计算两个数之和的界面。工程`MyCh0902`包含`ContentView.swift`,其中定义了`ContentView`和`MyView`结构体。`MyView`负责界面布局,使用`VStack`和`HStack`组织元素,如`TextField`和`Button`。点击`Button`调用`calc`方法处理输入并更新结果。界面设计可在Xcode的Inspector窗口中可视化配置。推荐将界面逻辑移到单独的`MyView.swift`文件中以清晰分离视图设计。
186 1
Swift开发——简单App设计
|
25天前
|
开发框架 移动开发 JavaScript
SpringCloud微服务实战——搭建企业级开发框架(四十七):【移动开发】整合uni-app搭建移动端快速开发框架-添加Axios并实现登录功能
在uni-app中,使用axios实现网络请求和登录功能涉及以下几个关键步骤: 1. **安装axios和axios-auth-refresh**: 在项目的`package.json`中添加axios和axios-auth-refresh依赖,可以通过HBuilderX的终端窗口运行`yarn add axios axios-auth-refresh`命令来安装。 2. **配置自定义常量**: 创建`project.config.js`文件,配置全局常量,如API基础URL、TenantId、APP_CLIENT_ID和APP_CLIENT_SECRET等。
161 60
|
28天前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要: