初始化项目:添加后端代码、ThinkPHP框架、前端资源
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model\live;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class Message extends Model
|
||||
{
|
||||
|
||||
|
||||
// 表名
|
||||
protected $name = 'live_message';
|
||||
|
||||
// 自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'integer';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = false;
|
||||
protected $deleteTime = false;
|
||||
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
* 保存消息
|
||||
* @param $userId
|
||||
* @param $courseId
|
||||
* @param $topic
|
||||
* @param $type
|
||||
* @param $message
|
||||
* @param $status
|
||||
* @return void
|
||||
*/
|
||||
public static function sendMessage($userId,$courseId,$topic,$type,$message,$status){
|
||||
// 读取直播间使用的消息服务商
|
||||
$provider = \app\common\library\live\message\Manager::providerNameByCourseId($courseId);
|
||||
|
||||
// 阿里云 MsgType 映射:text→10001, image→10002, gift→10003
|
||||
$msgType = \app\common\library\live\message\Message::mapMsgType($type);
|
||||
|
||||
// 阿里云模式下,发送者ID需传入用户ID(过滤后),便于客户端识别消息来源
|
||||
$senderId = '';
|
||||
if ($provider === 'aliyun') {
|
||||
$senderId = $userId ? ('u-' . $userId) : 'system';
|
||||
}
|
||||
|
||||
// 先落库(阿里云模式下 msg_tid 暂为空,发送成功后回填)
|
||||
$data = [
|
||||
'uniacid'=>UNIACID,
|
||||
'course_id'=>$courseId,
|
||||
'user_id'=>$userId,
|
||||
'type'=>$type,
|
||||
'is_send'=>$status,
|
||||
'content'=>$message,
|
||||
'status'=>$status,
|
||||
'createtime'=>time(),
|
||||
'msg_tid'=>'',
|
||||
];
|
||||
self::insert($data);
|
||||
$messageId = self::getLastInsID();
|
||||
|
||||
if($status){
|
||||
$formatMessage = self::formatMessage($userId,$type,$message,$messageId);
|
||||
$result = \app\common\library\live\Message::sendByProvider($provider,$topic,$formatMessage,$msgType,$senderId);
|
||||
|
||||
// 阿里云模式下,保存返回的 msg_tid 用于撤回
|
||||
if ($provider === 'aliyun' && $messageId && is_array($result) && isset($result['msg_tid']) && $result['msg_tid']) {
|
||||
self::where(['id' => $messageId])->update(['msg_tid' => $result['msg_tid']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 格式化消息
|
||||
* @param int $userId
|
||||
* @param string $type
|
||||
* @param string $message
|
||||
* @param int $messageId 数据库消息ID,用于前端定位(删除等操作)
|
||||
* @return false|string
|
||||
*/
|
||||
public static function formatMessage($userId, $type, $message, $messageId = 0){
|
||||
|
||||
if($userId == 0){
|
||||
$userInfo = [
|
||||
'id'=>0,
|
||||
'avatar'=>letter_avatar('管'),
|
||||
'nickname'=>'管理员'
|
||||
];
|
||||
}else{
|
||||
$userInfo = \app\common\model\User::getUserInfo($userId);
|
||||
}
|
||||
|
||||
|
||||
if(!$userInfo){
|
||||
throw new \app\common\exception\Exception("获取用户信息出错");
|
||||
}
|
||||
|
||||
return json_encode([
|
||||
'id'=>$messageId,
|
||||
'user_id'=>$userInfo['id'],
|
||||
'nickname'=>$userInfo['nickname'],
|
||||
'avatar'=>$userInfo['avatar'],
|
||||
'type'=>$type,
|
||||
'message'=>$message,
|
||||
'time'=>time()
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 效验敏感词
|
||||
* @param $message
|
||||
* @param $banWords array
|
||||
* @return void
|
||||
*/
|
||||
public static function check($message,$banWords){
|
||||
|
||||
$banWords = array_filter($banWords);
|
||||
if(!$banWords){
|
||||
return true;
|
||||
}
|
||||
// 将敏感词列表转换成正则表达式
|
||||
$pattern = '/(' . implode('|', $banWords) . ')/i';
|
||||
// 使用正则表达式匹配段落中的敏感词
|
||||
$matches = array();
|
||||
preg_match_all($pattern, $message, $matches);
|
||||
// 输出检测结果
|
||||
if (!empty($matches[1])) {
|
||||
|
||||
$sensitive_words_found = array_unique($matches[1]);
|
||||
$sensitive_words_str = implode(', ', $sensitive_words_found);
|
||||
return $sensitive_words_str;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('app\common\model\User', 'user_id', 'id', [], 'LEFT')->setEagerlyType(0);
|
||||
}
|
||||
|
||||
public function liveuser()
|
||||
{
|
||||
return $this->belongsTo('app\admin\model\live\User', 'user_id', 'user_id', [], 'LEFT')->setEagerlyType(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取消息列表 历史消息
|
||||
* @return void
|
||||
*/
|
||||
public function getMessageList($courseId,$offset,$limit,$role = ''){
|
||||
|
||||
$query = self::with(['user','liveuser']);
|
||||
|
||||
if($role == 'user'){
|
||||
$query->where('message.user_id','<>',0);
|
||||
}
|
||||
|
||||
if($role == 'admin'){
|
||||
$query->where('message.user_id',0);
|
||||
}
|
||||
|
||||
$list = $query
|
||||
->where([
|
||||
'course_id'=>$courseId,
|
||||
'message.status'=>1
|
||||
])
|
||||
->where(function($query) {
|
||||
$query->where('message.user_id', 0)
|
||||
->whereOr(function($query) {
|
||||
$query->where('message.user_id', '<>', 0)
|
||||
->where('liveuser.bantalk', 0)
|
||||
->where('liveuser.black', 0);
|
||||
});
|
||||
})
|
||||
->group('message.id')
|
||||
->order('createtime', 'desc')
|
||||
->limit($offset,$limit)
|
||||
->select();
|
||||
|
||||
$data = [];
|
||||
|
||||
if(!empty($list)){
|
||||
foreach ($list as $item){
|
||||
if($item['user_id'] == 0){
|
||||
$userInfo = [
|
||||
'user_id'=>$item['user_id'],
|
||||
'avatar'=>letter_avatar('管'),
|
||||
'nickname'=>'管理员'
|
||||
];
|
||||
}else{
|
||||
$userInfo = [
|
||||
'user_id'=>$item['user_id'],
|
||||
'avatar'=>$item->user->avatar ? $item->user->avatar: letter_avatar($item->user->nickname) ,
|
||||
'nickname'=>$item->user->nickname
|
||||
];
|
||||
}
|
||||
$temp = [
|
||||
'id'=>$item['id'],
|
||||
'time'=>$item['createtime'],
|
||||
'message'=>$item['content'],
|
||||
'type'=>$item['type'],
|
||||
'liveuser'=>$item['liveuser']
|
||||
];
|
||||
|
||||
$data[] = array_merge($temp,$userInfo);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model\live;
|
||||
|
||||
use think\Model;
|
||||
use fast\Random;
|
||||
|
||||
class Playback extends Model
|
||||
{
|
||||
|
||||
|
||||
// 表名
|
||||
protected $name = 'live_playback';
|
||||
|
||||
// 自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'integer';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = false;
|
||||
protected $deleteTime = false;
|
||||
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取是否有回放视频
|
||||
* @return void
|
||||
*/
|
||||
public static function checkHasPlaybackVideo($courseId){
|
||||
$data = self::where([
|
||||
'course_id'=>$courseId,
|
||||
'status'=>1
|
||||
])->find();
|
||||
|
||||
if($data){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model\live;
|
||||
|
||||
use think\Model;
|
||||
use fast\Random;
|
||||
|
||||
class Room extends Model
|
||||
{
|
||||
|
||||
|
||||
// 表名
|
||||
protected $name = 'live_room';
|
||||
|
||||
// 自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'integer';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = false;
|
||||
protected $deleteTime = false;
|
||||
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 通过课程获取直播间信息
|
||||
* @param $courseId
|
||||
* @return false
|
||||
*/
|
||||
public function courseGetRoom($courseId){
|
||||
$data = self::with(['course'])
|
||||
->where([
|
||||
'course_id'=>$courseId
|
||||
])->find();
|
||||
// ->cache(\app\common\constant\cache\Keys::LIVE_ROOM_CONFIG.$courseId)
|
||||
|
||||
if(!$data){
|
||||
return false;
|
||||
}
|
||||
|
||||
if($data['course']['live_type'] == 2){
|
||||
//这里是伪直播
|
||||
$liveVideo = json_decode($data['course']['live_video'],true);
|
||||
|
||||
$liveVideo['fullurl'] = parse_file_url($liveVideo['url'],$liveVideo['storage']);
|
||||
|
||||
$data['push_url'] = [
|
||||
'hls'=>$liveVideo['fullurl']
|
||||
];
|
||||
|
||||
}elseif($data['course']['live_type'] == 3){
|
||||
//自定义播流地址
|
||||
$data['push_url'] = [
|
||||
'hls'=>$data['course']['live_custom_url']
|
||||
];
|
||||
|
||||
}else{
|
||||
$data['push_url'] = \app\common\library\live\Url::playUrl($data['app_name'],$data['stream_name']);
|
||||
|
||||
// 阿里云直播且开启多清晰度时,生成各清晰度播流地址
|
||||
if(isset($data['course']['live_type']) && $data['course']['live_type'] == 1){
|
||||
$liveConfig = \app\common\model\config\System::getConfig('ali_live');
|
||||
$multiQualityEnabled = isset($liveConfig['multi_quality_enabled']) ? boolval($liveConfig['multi_quality_enabled']) : false;
|
||||
$qualityList = isset($liveConfig['quality_list']) ? $liveConfig['quality_list'] : '[]';
|
||||
$qualityList = is_string($qualityList) ? json_decode($qualityList, true) : $qualityList;
|
||||
|
||||
if($multiQualityEnabled && !empty($qualityList) && is_array($qualityList)){
|
||||
$qualities = [];
|
||||
foreach($qualityList as $quality){
|
||||
if(!isset($quality['template_id']) || $quality['template_id'] === ''){
|
||||
continue;
|
||||
}
|
||||
$qualities[] = [
|
||||
'name' => isset($quality['name']) ? $quality['name'] : '',
|
||||
'template_id' => strval($quality['template_id']),
|
||||
'level' => isset($quality['level']) ? intval($quality['level']) : 0,
|
||||
'url' => \app\common\library\live\Url::playUrl($data['app_name'], $data['stream_name'], $quality['template_id'])
|
||||
];
|
||||
}
|
||||
if(!empty($qualities)){
|
||||
// 按 level 降序排列(最高画质在前),前端默认使用第一个
|
||||
usort($qualities, function($a, $b) {
|
||||
return $b['level'] - $a['level'];
|
||||
});
|
||||
$data['qualities'] = $qualities;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$globalConfig = \app\common\model\app\Config::getConfig('live');
|
||||
$config = json_decode($data['config'],true);
|
||||
$config = array_merge($globalConfig,$config);
|
||||
|
||||
// 未安装送礼物插件时,强制关闭礼物功能,避免前端仍请求礼物接口
|
||||
if(!\app\admin\library\project\App::isInstall('live_gift') || !class_exists('app\admin\model\live\GiftOptions')){
|
||||
$config['gift'] = 0;
|
||||
}
|
||||
|
||||
$intField = ['goods','comment_audit','bantalk','gift'];
|
||||
|
||||
foreach ($intField as $field){
|
||||
if(isset($config[$field])){
|
||||
$config[$field] = intval($config[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
unset($data['course']);
|
||||
$data['config'] = $config;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取直播间状态
|
||||
* @return void
|
||||
*/
|
||||
public function getRoomStatus($courseId){
|
||||
|
||||
$data = self::with(['course'])
|
||||
->where([
|
||||
'course_id'=>$courseId
|
||||
])->find();
|
||||
|
||||
if(!$data){
|
||||
return false;
|
||||
}
|
||||
|
||||
$livePushStatus = 'offline';
|
||||
|
||||
if($data['course']['live_type'] == 2 || $data['course']['live_type'] == 3){
|
||||
//判断当前是否在直播时间
|
||||
if($data['course']['live_start_time'] < time() && $data['course']['live_end_time'] > time()){
|
||||
$livePushStatus = 'online';
|
||||
}
|
||||
}else{
|
||||
try{
|
||||
$livePushStatus = \app\common\library\live\Room::checkLiveStatus($data['app_name'],$data['stream_name']);
|
||||
}catch (\Exception $e){
|
||||
$livePushStatus = 'offline';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if($livePushStatus == 'online'){
|
||||
return $livePushStatus;
|
||||
}
|
||||
$data['config'] = json_decode($data['config'],true);
|
||||
|
||||
if($data->course['live_start_time'] > time()){
|
||||
//暂未开始
|
||||
return 'not_start';
|
||||
}
|
||||
|
||||
if(time() > $data->course['live_end_time']){
|
||||
//直播已经结束
|
||||
//判断是否开始回放,
|
||||
if($data['config']['play_back'] == 1){
|
||||
//判断是否开启限制回放时间
|
||||
if($data['config']['play_back_type'] == 1 || (isset($data['config']['play_back_validity_time']) && strtotime($data['config']['play_back_validity_time']) > time())){
|
||||
//开启后判断有无回放视频
|
||||
if(\app\common\model\live\Playback::checkHasPlaybackVideo($courseId)){
|
||||
//回放中
|
||||
return 'run_playback';
|
||||
}
|
||||
|
||||
if($data['course']['live_type'] == 2 && $data['course']['live_video_convert_replay'] == 1){
|
||||
return 'run_playback';
|
||||
}
|
||||
|
||||
//待上传回放
|
||||
return 'wait_playback';
|
||||
}
|
||||
}
|
||||
|
||||
//已结束 未开启回放
|
||||
return 'end';
|
||||
}
|
||||
|
||||
|
||||
|
||||
//在直播时间内,判断推流状态
|
||||
return $livePushStatus;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取直播状态列表
|
||||
* @return string[]
|
||||
*/
|
||||
public function getLiveStatusList(){
|
||||
return [
|
||||
'wait_playback',
|
||||
'end',
|
||||
'run_playback',
|
||||
'not_start',
|
||||
'online',
|
||||
'offline',
|
||||
'forbidden',
|
||||
'onlinefaild'
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function course()
|
||||
{
|
||||
return $this->belongsTo('app\common\model\course\Course', 'course_id', 'id', [], 'LEFT')->setEagerlyType(0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user