feat:添加会议分享功能 - 100%

This commit is contained in:
weifashi 2023-08-30 18:21:37 +08:00
parent 3a2c40a43e
commit 0c4abb5db3
9 changed files with 180 additions and 35 deletions

View File

@ -613,7 +613,10 @@ class UsersController extends AbstractController
*/
public function basic()
{
$sharekey = Request::header('Sharekey');
if(empty($sharekey) || !Meeting::getShareInfo($sharekey)){
User::auth();
}
//
$userid = Request::input('userid');
$array = Base::json2array($userid);
@ -1120,7 +1123,7 @@ class UsersController extends AbstractController
* - join: 加入会议有效参数meetingid (必填)
* @apiParam {String} [meetingid] 频道ID不是数字
* @apiParam {String} [name] 会话ID
* @apiParam {String} [meetingsign] 签名
* @apiParam {String} [sharekey] 分享的key
* @apiParam {String} [username] 用户名称
* @apiParam {Array} [userids] 邀请成员
*
@ -1134,11 +1137,17 @@ class UsersController extends AbstractController
$meetingid = trim(Request::input('meetingid'));
$name = trim(Request::input('name'));
$userids = Request::input('userids');
$meetingsign = trim(Request::input('meetingsign'));
$sharekey = trim(Request::input('sharekey'));
$username = trim(Request::input('username'));
$user = empty($meetingsign) ? User::auth() : null;
$user = null;
if(!empty($sharekey) && $type === 'join'){
if(!Meeting::getShareInfo($sharekey)){
return Base::retError('分享链接已过期');
}
}else{
$user = User::auth();
}
$isCreate = false;
// 创建、加入
if ($type === 'join') {
$meeting = Meeting::whereMeetingid($meetingid)->first();
@ -1169,7 +1178,10 @@ class UsersController extends AbstractController
if (empty($meetingSetting['appid']) || empty($meetingSetting['app_certificate'])) {
return Base::retError('会议功能配置错误,请联系管理员');
}
$uid = intval(str_pad( Request::header('fd'), 6, 9, STR_PAD_LEFT) . $user?->userid);
$uid = intval(str_pad(Base::generatePassword(4,1), 9, 8, STR_PAD_LEFT));
if($user){
$uid = intval(str_pad(Request::header('fd'), 5, 9, STR_PAD_LEFT).$user->userid);
}
try {
$service = new AgoraTokenGenerator($meetingSetting['appid'], $meetingSetting['app_certificate'], $meeting->channel, $uid);
} catch (\Exception $e) {
@ -1198,13 +1210,40 @@ class UsersController extends AbstractController
//
$data['appid'] = $meetingSetting['appid'];
$data['uid'] = $uid;
$data['userimg'] = $meetingsign ? 'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png' : $user?->userimg;
$data['nickname'] = $meetingsign ? $username : $user?->nickname;
$data['userimg'] = $sharekey ? Base::fillUrl('avatar/'.$username.'.png') : $user?->userimg;
$data['nickname'] = $sharekey ? $username : $user?->nickname;
$data['token'] = $token;
$data['msgs'] = $msgs;
$data['sharelink'] = $meeting->getShareLink();
//
Meeting::setTouristInfo($data);
//
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/users/meeting/tourist 16. 【会议】游客信息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName meeting__tourist
*
* @apiParam {String} tourist_id 游客ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function meeting__tourist()
{
$touristId = trim(Request::input('tourist_id'));
if ($touristInfo = Meeting::getTouristInfo($touristId)) {
return Base::retSuccess('success', $touristInfo);
}
return Base::retError('Id不存在');
}
/**
* @api {get} api/users/meeting/invitation 17. 【会议】发送邀请
*

View File

@ -2,6 +2,10 @@
namespace App\Models;
use Cache;
use App\Module\Base;
use Illuminate\Support\Carbon;
/**
* App\Models\Meeting
*
@ -30,5 +34,58 @@ namespace App\Models;
*/
class Meeting extends AbstractModel
{
const CACHE_KEY = 'meeting_share_link_code';
const CACHE_EXPIRED_TIME = 6; // 小时
/**
* 获取分享链接
* @return mixed
*/
public function getShareLink()
{
$code = base64_encode("{$this->meetingid}" . Base::generatePassword());
Cache::put(self::CACHE_KEY.'_'.$code, [
'id' => $this->id,
'meetingid' => $this->meetingid,
'channel' => $this->channel,
], Carbon::now()->addHours(self::CACHE_EXPIRED_TIME));
return Base::fillUrl("meeting/{$this->meetingid}/".$code);
}
/**
* 获取分享信息
* @return mixed
*/
public static function getShareInfo($code)
{
if(Cache::has(self::CACHE_KEY.'_'.$code)){
return Cache::get(self::CACHE_KEY.'_'.$code);
}
return null;
}
/**
* 保存访客信息
* @return mixed
*/
public static function setTouristInfo($data)
{
Cache::put(Meeting::CACHE_KEY.'_'.$data['uid'], [
'uid' => $data['uid'],
'userimg' => $data['userimg'],
'nickname' => $data['nickname'],
], Carbon::now()->addHours(self::CACHE_EXPIRED_TIME));
}
/**
* 获取访客信息
* @return mixed
*/
public static function getTouristInfo($touristId)
{
if(Cache::has(Meeting::CACHE_KEY.'_'.$touristId)){
return Base::retSuccess('success', Cache::get(Meeting::CACHE_KEY.'_'.$touristId) );
}
return null;
}
}

View File

@ -337,7 +337,7 @@ export default {
this.$store.dispatch("call", {
url: 'users/basic',
data: {
userid: [ (uuid+"").substring(6) ]
userid: [ (uuid+"").substring(5) ]
}
}).then(({data}) => {
$A.eeuiAppSendMessage({

View File

@ -5,14 +5,14 @@
v-model="addShow"
:title="$L(addData.type === 'join' ? '加入会议' : '新会议')"
:mask-closable="false"
:closable="!addData.meetingsign">
:closable="!addData.sharekey">
<Form ref="addForm" :model="addData" label-width="auto" @submit.native.prevent>
<template v-if="addData.type === 'join'">
<!-- 加入会议 -->
<FormItem v-if="addData.name" prop="userids" :label="$L('会议主题')">
<Input v-model="addData.name" disabled/>
</FormItem>
<FormItem v-if="addData.meetingsign" prop="username" :label="$L('你的姓名')">
<FormItem v-if="addData.sharekey" prop="username" :label="$L('你的姓名')">
<Input v-model="addData.username" :placeholder="$L('请输入你的姓名')"/>
</FormItem>
<FormItem prop="meetingid" :label="$L('会议频道ID')">
@ -40,7 +40,7 @@
</FormItem>
</Form>
<div slot="footer" class="adaption">
<Button type="default" @click="addShow=false" v-if="!addData.meetingsign">{{$L('取消')}}</Button>
<Button type="default" @click="addShow=false" v-if="!addData.sharekey">{{$L('取消')}}</Button>
<Button type="primary" :loading="loadIng > 0" @click="onSubmit">{{$L(addData.type === 'join' ? '加入会议' : '开始会议')}}</Button>
</div>
</Modal>
@ -76,17 +76,17 @@
<Button type="primary" @click="onInvitation('open')">
<i class="taskfont">&#xe646;</i>
</Button>
<Button type="primary" v-if="!addData.meetingsign" @click="meetingMini = true">
<Button type="primary" v-if="!addData.sharekey" @click="meetingMini = true">
<i class="taskfont">&#xe656;</i>
</Button>
<Button type="warning" v-if="!addData.meetingsign" :loading="loadIng > 0" @click="onClose">
<Button type="warning" :loading="loadIng > 0" @click="onClose">
<i class="taskfont">&#xe612;</i>
</Button>
</template>
<template v-else>
<Button type="primary" @click="onInvitation('open')">{{$L('邀请')}}</Button>
<Button type="primary" v-if="!addData.meetingsign" @click="meetingMini = true">{{$L('最小化')}}</Button>
<Button type="warning" v-if="!addData.meetingsign" :loading="loadIng > 0" @click="onClose">{{$L('离开会议')}}</Button>
<Button type="primary" v-if="!addData.sharekey" @click="meetingMini = true">{{$L('最小化')}}</Button>
<Button type="warning" :loading="loadIng > 0" @click="onClose">{{$L('离开会议')}}</Button>
</template>
</div>
</Modal>
@ -113,7 +113,7 @@
</FormItem>
</Form>
<div slot="footer" class="adaption">
<Button type="default" @click="invitationShow=false">{{$L('取消')}}</Button>
<Button type="default" @click="linkCopy">{{$L('复制链接')}}</Button>
<Button type="primary" :loading="invitationLoad" @click="onInvitation('submit')">{{$L('发送邀请')}}</Button>
</div>
</Modal>
@ -193,7 +193,6 @@ export default {
},
meetingWindow: {
handler(val) {
console.log(val)
switch (val.type) {
case 'add':
this.addShow = val.show;
@ -203,10 +202,10 @@ export default {
this.addShow = val.show;
this.loadIng = 0;
this.addData.type = 'join';
if(val.meetingsign){
if(val.meetingSharekey){
this.addData.sharekey = val.meetingSharekey;
this.addData.meetingid = val.meetingid || '';
this.addData.meetingdisabled = val.meetingsign ? true : false;
this.addData.meetingsign = val.meetingsign;
this.addData.meetingdisabled = val.meetingSharekey ? true : false;
}
break;
case 'invitation':
@ -275,6 +274,9 @@ export default {
}).then(({data}) => {
this.$set(this.addData, 'name', data.name);
this.$set(this.addData, 'meetingid', data.meetingid);
this.$set(this.addData, 'sharelink', data.sharelink);
this.$set(this.localUser, 'nickname', data.nickname);
this.$set(this.localUser, 'userimg', data.userimg);
this.$store.dispatch("saveDialogMsg", data.msgs);
this.$store.dispatch("updateDialogLastMsg", data.msgs);
delete data.name;
@ -294,6 +296,7 @@ export default {
video: this.addData.tracks.includes("video"),
audio: this.addData.tracks.includes("audio"),
meetingid: data.meetingid,
sharelink: data.sharelink,
alert: {
title: this.$L('温馨提示'),
message: this.$L('确定要离开会议吗?'),
@ -338,6 +341,10 @@ export default {
onInvitation(type) {
if (type === 'open') {
if(this.addData.sharekey){
this.linkCopy();
return;
}
this.invitationData = {
userids: [],
meetingid: this.addData.meetingid
@ -369,6 +376,10 @@ export default {
okText: '退出',
onOk: async _ => {
await this.leave()
if(this.addData.sharekey){
this.addShow = true;
this.loadIng = 0;
}
resolve()
}
});
@ -376,7 +387,6 @@ export default {
},
async join(options) {
console.log(options)
this.loadIng++;
//
AgoraRTC.onMicrophoneChanged = async (changedDevice) => {
@ -521,7 +531,16 @@ export default {
if (item) {
await this.agoraClient.unsubscribe(user, mediaType);
}
}
},
linkCopy() {
this.$copyText(this.addData.sharelink).then(_ => {
$A.messageSuccess('已复制会议邀请链接');
}).catch(_ => {
$A.messageError('复制失败');
});
this.invitationShow = false;
},
}
}
</script>

View File

@ -1,7 +1,13 @@
<template>
<div class="meeting-player">
<div :id="id" class="player" :style="playerStyle"></div>
<UserAvatar :userid="userid" :size="36" :borderWitdh="2"/>
<UserAvatar v-if="userid" :userid="userid" :size="36" :borderWitdh="2"/>
<div v-else-if="tourist.userimg" class="common-avatar avatar-wrapper">
<div class="avatar-box online">
<em style="transform: scale(1.0625);"></em>
<EAvatar :size="36" style="border: 2px solid #FFFFFF;" :src="tourist.userimg"></EAvatar>
</div>
</div>
<div class="player-state">
<i v-if="!audio" class="taskfont">&#xe7c7;</i>
<i v-if="!video" class="taskfont">&#xe7c8;</i>
@ -32,7 +38,12 @@ export default {
},
data() {
return {
timer: null
timer: null,
tourist: {
uid: '',
nickname: '',
userimg: '',
}
}
},
mounted() {
@ -52,8 +63,11 @@ export default {
...mapState(['cacheUserBasic']),
userid() {
if (this.player.uid) {
console.log(parseInt( (this.player.uid+"").substring(6) ));
return parseInt( (this.player.uid+"").substring(6) )
if( (this.player.uid + '').indexOf('88888') !== -1 ){
this.getTouristInfo();
return 0;
}
return parseInt( (this.player.uid+"").substring(5) ) || 0
}
return 0
},
@ -63,11 +77,12 @@ export default {
return {
backgroundImage: `url("${user.userimg}")`
}
}else{
}else if(this.tourist.userimg){
return {
backgroundColor: '#000000'
backgroundImage: `url("${this.tourist.userimg}")`
}
}
return null;
},
audio() {
return !!this.player.audioTrack
@ -103,6 +118,18 @@ export default {
console.log("Meeting Player Error", e);
}
})
},
getTouristInfo() {
this.$store.dispatch("call", {
url: 'users/meeting/tourist',
data: {
tourist_id: this.player.uid
}
}).then(({data}) => {
this.tourist = data.data;
}).catch(({msg}) => {
$A.modalError(msg);
});
}
}
}

View File

@ -14,8 +14,8 @@ export default {
mounted() {
this.$store.dispatch("showMeetingWindow",{
type: "join",
meetingsign: 'sign',
meetingid: 10812486455,
meetingid: this.$route.params.meetingId,
meetingSharekey: this.$route.params.sharekey,
meetingdisabled: true,
})
},

View File

@ -11,7 +11,7 @@ export default [
},
{
name: 'meeting',
path: '/meeting',
path: '/meeting/:meetingId?/:sharekey?',
component: () => import('./pages/meeting.vue'),
},
{

View File

@ -112,6 +112,9 @@ export default {
'version': window.systemInfo.version || "0.0.1",
'platform': $A.Platform,
}
if(!state.userToken && state.meetingWindow?.meetingSharekey){
header.sharekey = state.meetingWindow.meetingSharekey;
}
if ($A.isJson(params.header)) {
params.header = Object.assign(header, params.header)
} else {
@ -3608,13 +3611,13 @@ export default {
* @param type
* @param meetingid
*/
showMeetingWindow({state}, {type, meetingid, meetingdisabled, meetingsign}) {
showMeetingWindow({state}, {type, meetingid, meetingdisabled, meetingSharekey}) {
state.meetingWindow = {
show: true,
type: type,
meetingid: meetingid,
meetingdisabled: meetingdisabled,
meetingsign: meetingsign
meetingSharekey: meetingSharekey
};
},

View File

@ -199,7 +199,7 @@ export default {
// 审批待办未读数量
approveUnreadNumber: 0,
// 会议窗口
// 会议
meetingWindow: {
show: false,
type: "",