Files

265 lines
9.4 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\common\library\live\message;
use app\common\model\config\System;
/**
* 直播消息服务商管理器
* 服务商统一由通用设置 live_basic.live_message_type 决定,切换后对所有直播间(含历史直播间)立即生效
*/
class Manager
{
/**
* 服务商名称与实现类的映射
* 新增服务商时在此注册即可
* @var array
*/
protected static $providers = [
'aodianyun' => \app\common\library\live\message\provider\Aodianyun::class,
'aliyun' => \app\common\library\live\message\provider\Aliyun::class,
];
/**
* 兼容历史中文值(前端 radio 曾误保存 label)到标准 provider key 的映射
* @var array
*/
protected static $aliasMap = [
'奥点云消息' => 'aodianyun',
'奥点云' => 'aodianyun',
'阿里云消息' => 'aliyun',
'阿里云' => 'aliyun',
];
/**
* 已实例化的服务商(按 provider 名缓存)
* @var array
*/
protected static $instances = [];
/**
* 归一化服务商名称
* 兼容历史中文值;无效值降级为奥点云
* @param string|null $providerName
* @return string
*/
public static function normalizeProvider($providerName)
{
if (!is_string($providerName) || $providerName === '') {
return 'aodianyun';
}
if (isset(self::$aliasMap[$providerName])) {
$providerName = self::$aliasMap[$providerName];
}
return isset(self::$providers[$providerName]) ? $providerName : 'aodianyun';
}
/**
* 判断消息服务是否已开启
* 读取 live_basic.live_message_status 配置,"1" 为开启,"0" 为关闭
* @return bool
*/
public static function isMessageEnabled()
{
try {
$config = System::getConfig('live_basic');
} catch (\Exception $e) {
return true;
}
if (!is_array($config) || !isset($config['live_message_status'])) {
return true;
}
return $config['live_message_status'] !== '0' && $config['live_message_status'] !== 0;
}
/**
* 获取全局配置的服务商名称
* 统一从 System::getConfig('live_basic')['live_message_type'] 读取
* @return string
*/
public static function getGlobalProviderName()
{
try {
$config = System::getConfig('live_basic');
} catch (\Exception $e) {
return 'aodianyun';
}
$providerName = (is_array($config) && isset($config['live_message_type'])) ? $config['live_message_type'] : '';
return self::normalizeProvider($providerName);
}
/**
* 获取服务商实例
* @param string|null $providerName 服务商名称(aodianyun/aliyun),为空则读取全局配置;非空也会归一化,避免中文值导致错误降级
* @return ProviderInterface
*/
public static function provider($providerName = null)
{
if ($providerName === null || $providerName === '') {
$providerName = self::getGlobalProviderName();
} else {
$providerName = self::normalizeProvider($providerName);
}
if (!isset(self::$instances[$providerName])) {
$class = self::$providers[$providerName];
self::$instances[$providerName] = new $class();
}
return self::$instances[$providerName];
}
/**
* 根据课程ID获取服务商实例
* 服务商由全局配置统一决定,参数保留仅为兼容旧调用签名
* @param int $courseId 课程ID(保留参数以兼容既有调用)
* @return ProviderInterface
*/
public static function providerByCourseId($courseId)
{
return self::provider(self::getGlobalProviderName());
}
/**
* 根据课程ID获取服务商名称
* 服务商由全局配置统一决定,参数保留仅为兼容旧调用签名
* @param int $courseId 课程ID(保留参数以兼容既有调用)
* @return string
*/
public static function providerNameByCourseId($courseId)
{
return self::getGlobalProviderName();
}
/**
* 注册自定义服务商(便于扩展)
* @param string $name 服务商名称
* @param string $class 实现类(必须实现 ProviderInterface
* @return void
*/
public static function register($name, $class)
{
self::$providers[$name] = $class;
// 清除已缓存实例以便下次使用新注册类
unset(self::$instances[$name]);
}
/**
* 确保阿里云互动消息群组已创建
* 用于兼容历史直播间:直播间创建时若不是阿里云模式则未建群,切换到阿里云后需要幂等地补建
* 已存在的群会被阿里云识别为重复请求(GroupExist / 类似错误码),此处按成功处理
* @param string $groupId 群组ID(对应 tuzhi_live_room.message_topic
* @param string $groupName 群组名称
* @return bool 是否可用(true=群存在或补建成功;false=补建失败)
*/
public static function ensureAliyunGroup($groupId, $groupName = '')
{
if (!$groupId) {
return false;
}
try {
$result = self::provider('aliyun')->createGroup($groupId, $groupName, '');
if ($result->isSuccess()) {
return true;
}
// 阿里云群组已存在类错误按成功处理,保证幂等
$error = is_string($result->error) ? $result->error : '';
if ($error !== '' && (
stripos($error, 'exist') !== false ||
stripos($error, 'duplicate') !== false ||
stripos($error, 'already') !== false
)) {
return true;
}
\think\Log::error('[live Manager ensureAliyunGroup] createGroup failed: ' . $error . ' group_id=' . $groupId);
return false;
} catch (\Exception $e) {
\think\Log::error('[live Manager ensureAliyunGroup] exception: ' . $e->getMessage() . ' group_id=' . $groupId);
return false;
}
}
/**
* 清理已删除课程对应的阿里云互动消息群组
*
* 逻辑:
* 1. 查找 status=-1(已删除)且课程类型为 live 的课程
* 2. 关联 tuzhi_live_room 获取 message_topic
* 3. 调用 deleteGroup 删除阿里云端的群组
* 4. 清空 live_room.message_topic 标记已清理
*
* @param int $batchSize 单次最多处理数量,防止超时
* @param bool $dryRun 仅预览,不实际删除
* @return array ['deleted' => int, 'failed' => int, 'skipped' => int, 'items' => array]
*/
public static function cleanupOrphanGroups($batchSize = 50, $dryRun = false)
{
$provider = self::getGlobalProviderName();
if ($provider !== 'aliyun') {
return ['deleted' => 0, 'failed' => 0, 'skipped' => 0, 'items' => [], 'msg' => '当前消息服务非阿里云,无需清理'];
}
// 查找已删除课程中仍保留 message_topic 的直播间
$rows = \think\Db::table('tuzhi_live_room r')
->join('tuzhi_course c', 'c.id = r.course_id', 'LEFT')
->where('r.message_topic', '<>', '')
->where('r.message_topic', 'IS NOT NULL')
->where(function ($q) {
// 课程已删除 或 课程不存在(孤儿记录)
$q->where('c.status', -1)->whereOr('c.id', 'IS NULL');
})
->limit($batchSize)
->select(['r.id', 'r.course_id', 'r.message_topic']);
$deleted = 0;
$failed = 0;
$items = [];
$aliyunProvider = self::provider('aliyun');
foreach ($rows as $row) {
$groupId = $row['message_topic'];
$roomId = $row['id'];
$courseId = $row['course_id'];
if ($dryRun) {
$items[] = ['room_id' => $roomId, 'course_id' => $courseId, 'group_id' => $groupId, 'action' => 'preview'];
continue;
}
try {
$result = $aliyunProvider->deleteGroup($groupId, 'system-cleanup');
if ($result->isSuccess()) {
// 清空 message_topic 标记已清理
\think\Db::table('tuzhi_live_room')->where('id', $roomId)->update(['message_topic' => '']);
$deleted++;
$items[] = ['room_id' => $roomId, 'course_id' => $courseId, 'group_id' => $groupId, 'action' => 'deleted'];
} else {
$failed++;
$items[] = ['room_id' => $roomId, 'course_id' => $courseId, 'group_id' => $groupId, 'action' => 'failed', 'error' => $result->error];
\think\Log::error('[live Manager cleanupOrphanGroups] deleteGroup failed: ' . $result->error . ' group_id=' . $groupId);
}
} catch (\Exception $e) {
$failed++;
$items[] = ['room_id' => $roomId, 'course_id' => $courseId, 'group_id' => $groupId, 'action' => 'error', 'error' => $e->getMessage()];
\think\Log::error('[live Manager cleanupOrphanGroups] exception: ' . $e->getMessage() . ' group_id=' . $groupId);
}
}
return [
'deleted' => $deleted,
'failed' => $failed,
'skipped' => count($rows) - $deleted - $failed,
'items' => $items,
];
}
}