c909ebdf88
- Introduced `getPlatformAccountsByUserId` method in `DyVideoUser` model to fetch active platform accounts for a specified user. - Added new API route for accessing platform accounts under `v1/platform/accounts`, requiring user authentication.
326 lines
11 KiB
PHP
326 lines
11 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
namespace app\api\service;
|
||
|
||
use app\api\model\DyVideoUser;
|
||
use app\api\model\DyVideoUserTitk;
|
||
use app\api\model\Member;
|
||
|
||
/**
|
||
* 平台账号管理服务。
|
||
*
|
||
* 本服务只补齐小程序“平台账号管理”页面所需的最基础只读能力,
|
||
* 关键查询与状态语义按 acgpmw `dyai/controller/platform.php` 的
|
||
* `index()` / `get_zhanghu_list()` 对齐,不擅自扩展解绑、授权等高风险动作。
|
||
*/
|
||
class PlatformService
|
||
{
|
||
/**
|
||
* 平台映射按 acgpmw `data/other/platforms_py.php` 对齐。
|
||
*/
|
||
private const PLATFORM_KEY_MAP = [
|
||
0 => 'douyin',
|
||
1 => 'kuaishou',
|
||
2 => 'baijiahao',
|
||
3 => 'xiaohongshu',
|
||
4 => 'shipinhao',
|
||
5 => 'bilibili',
|
||
6 => 'gongzhonghao',
|
||
10 => 'tiktok',
|
||
];
|
||
|
||
/**
|
||
* 页面展示名称。
|
||
*
|
||
* acgpmw 模板内部会通过 `$this->platform_info[$platform]` 展示中文平台名,
|
||
* 这里在 API 中直接返回前端所需文案,避免小程序自行猜测。
|
||
*/
|
||
private const PLATFORM_NAME_MAP = [
|
||
0 => '抖音',
|
||
1 => '快手',
|
||
2 => '百家号',
|
||
3 => '小红书',
|
||
4 => '视频号',
|
||
5 => 'B站',
|
||
6 => '公众号',
|
||
10 => 'TikTok',
|
||
];
|
||
|
||
/**
|
||
* 获取平台账号管理页列表数据。
|
||
*
|
||
* @param int $userid 当前登录用户ID
|
||
* @param int|null $platform 当前筛选平台;为空时返回全部可见平台
|
||
* @return array
|
||
* @throws \Exception
|
||
*/
|
||
public function getAccountList(int $userid, ?int $platform = null): array
|
||
{
|
||
$member = Member::findByUserid($userid);
|
||
if (!$member) {
|
||
throw new \Exception('用户不存在', 4006);
|
||
}
|
||
|
||
$productInfo = $member->getProductInfo();
|
||
$availablePlatforms = $this->resolveAvailablePlatforms($member, $productInfo);
|
||
|
||
if (empty($availablePlatforms)) {
|
||
throw new \Exception('暂无平台管理权限', 4004);
|
||
}
|
||
|
||
if ($platform !== null && !in_array($platform, $availablePlatforms, true)) {
|
||
throw new \Exception('当前套餐暂无该平台权限', 4004);
|
||
}
|
||
|
||
$targetPlatform = $platform;
|
||
$accounts = DyVideoUser::getPlatformAccountsByUserId($userid, $targetPlatform);
|
||
|
||
$accountItems = [];
|
||
foreach ($accounts as $account) {
|
||
$accountPlatform = (int) $account->platform;
|
||
|
||
if (!in_array($accountPlatform, $availablePlatforms, true)) {
|
||
continue;
|
||
}
|
||
|
||
$accountItems[] = $this->buildAccountItem($account->toArray());
|
||
}
|
||
|
||
$platformTabs = $this->buildPlatformTabs($availablePlatforms, $userid);
|
||
$summary = $this->buildSummary($accountItems);
|
||
|
||
return [
|
||
'filters' => [
|
||
'current_platform' => $targetPlatform,
|
||
'platforms' => $platformTabs,
|
||
],
|
||
'summary' => $summary,
|
||
'list' => $accountItems,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 计算当前用户可见平台。
|
||
*
|
||
* 该逻辑直接对齐 acgpmw `platform::index()`:
|
||
* 1. 从套餐 `product_list.platforms` 读取平台权限
|
||
* 2. 若为 146/147 且不在特殊账号名单中,仅允许抖音平台
|
||
*
|
||
* @param Member $member 当前登录用户
|
||
* @param array|null $productInfo 套餐信息
|
||
* @return array<int>
|
||
*/
|
||
private function resolveAvailablePlatforms(Member $member, ?array $productInfo): array
|
||
{
|
||
$platformText = trim((string) ($productInfo['platforms'] ?? ''), ',');
|
||
if ($platformText === '') {
|
||
return [];
|
||
}
|
||
|
||
$platforms = array_values(array_filter(array_map('intval', explode(',', $platformText)), static function ($item) {
|
||
return array_key_exists($item, self::PLATFORM_NAME_MAP);
|
||
}));
|
||
|
||
$specialAccounts = $this->loadSpecialAccounts();
|
||
$isRestrictedVType = in_array((int) $member->v_type, [146, 147], true);
|
||
$isSpecialAccount = in_array((int) $member->userid, $specialAccounts, true);
|
||
|
||
if ($isRestrictedVType && !$isSpecialAccount) {
|
||
return [0];
|
||
}
|
||
|
||
return $platforms;
|
||
}
|
||
|
||
/**
|
||
* 构建前端平台筛选项。
|
||
*
|
||
* @param array<int> $platforms 当前用户可见平台
|
||
* @param int $userid 当前用户ID
|
||
* @return array<int, array<string, mixed>>
|
||
*/
|
||
private function buildPlatformTabs(array $platforms, int $userid): array
|
||
{
|
||
$tabs = [
|
||
[
|
||
'id' => null,
|
||
'name' => '全部平台',
|
||
'key' => 'all',
|
||
'count' => 0,
|
||
],
|
||
];
|
||
|
||
$allCount = 0;
|
||
foreach ($platforms as $platform) {
|
||
$count = DyVideoUser::where('userid', $userid)
|
||
->where('disabled', 0)
|
||
->where('platform', $platform)
|
||
->count();
|
||
|
||
$allCount += (int) $count;
|
||
$tabs[] = [
|
||
'id' => $platform,
|
||
'name' => self::PLATFORM_NAME_MAP[$platform] ?? ('平台' . $platform),
|
||
'key' => self::PLATFORM_KEY_MAP[$platform] ?? ('platform_' . $platform),
|
||
'count' => (int) $count,
|
||
];
|
||
}
|
||
|
||
$tabs[0]['count'] = $allCount;
|
||
|
||
return $tabs;
|
||
}
|
||
|
||
/**
|
||
* 把数据库记录转换为前端可直接渲染的账号卡片。
|
||
*
|
||
* 状态字段解释按 acgpmw `get_zhanghu_list()` 三列展示对齐:
|
||
* - `is_endauth` => 账号授权状态
|
||
* - `aa_endauth` => 数据授权状态
|
||
* - `browser` => 异常状态
|
||
*
|
||
* @param array $account dy_video_user 单条记录
|
||
* @return array<string, mixed>
|
||
*/
|
||
private function buildAccountItem(array $account): array
|
||
{
|
||
$platform = (int) ($account['platform'] ?? 0);
|
||
$countryName = '';
|
||
|
||
if ($platform === 10) {
|
||
$titkInfo = DyVideoUserTitk::findByVuid((int) $account['id']);
|
||
$countryName = (string) ($titkInfo->country_name ?? '');
|
||
}
|
||
|
||
$accountAuth = (int) ($account['is_endauth'] ?? 0) === 1
|
||
? $this->buildStatusBlock('授权到期', 'danger', '请重新完成平台授权')
|
||
: $this->buildStatusBlock('授权正常', 'success', '当前账号授权可用');
|
||
|
||
$dataAuth = (int) ($account['aa_endauth'] ?? 0) === 1
|
||
? $this->buildStatusBlock('授权到期', 'danger', '请重新完成数据授权')
|
||
: $this->buildStatusBlock('授权正常', 'success', '当前数据授权可用');
|
||
|
||
$exceptionStatus = (int) ($account['browser'] ?? 0) === 1
|
||
? $this->buildStatusBlock('发布异常', 'warning', '发布链路存在异常,需要人工处理')
|
||
: $this->buildStatusBlock('使用正常', 'success', '当前账号发布链路正常');
|
||
|
||
return [
|
||
'id' => (int) $account['id'],
|
||
'platform' => $platform,
|
||
'platform_name' => self::PLATFORM_NAME_MAP[$platform] ?? ('平台' . $platform),
|
||
'platform_key' => self::PLATFORM_KEY_MAP[$platform] ?? ('platform_' . $platform),
|
||
'nickname' => (string) ($account['dy_nickname'] ?? ''),
|
||
'avatar' => (string) ($account['dy_avatar'] ?? ''),
|
||
'intro' => (string) ($account['dy_intro'] ?? ''),
|
||
'phone' => (string) ($account['info_shouji'] ?? ''),
|
||
'country_name' => $countryName,
|
||
'proviceid' => (int) ($account['proviceid'] ?? 0),
|
||
'is_lanv' => (int) ($account['is_lanv'] ?? 0) === 1,
|
||
'is_qyh' => (int) ($account['is_qyh'] ?? 0) === 1,
|
||
'account_status' => $accountAuth,
|
||
'data_status' => $dataAuth,
|
||
'exception_status' => $exceptionStatus,
|
||
'status_overview' => $this->buildOverviewStatus($accountAuth, $dataAuth, $exceptionStatus),
|
||
'display_code' => '#P' . (int) $account['id'],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 构建单个状态块。
|
||
*
|
||
* @param string $text 显示文案
|
||
* @param string $tone 视觉色调:success / warning / danger
|
||
* @param string $hint 补充说明
|
||
* @return array<string, string>
|
||
*/
|
||
private function buildStatusBlock(string $text, string $tone, string $hint): array
|
||
{
|
||
return [
|
||
'text' => $text,
|
||
'tone' => $tone,
|
||
'hint' => $hint,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 汇总账号卡片主状态,便于列表页用一个醒目标记表示当前账号健康度。
|
||
*
|
||
* @param array $accountAuth 账号授权状态
|
||
* @param array $dataAuth 数据授权状态
|
||
* @param array $exceptionStatus 异常状态
|
||
* @return array<string, string>
|
||
*/
|
||
private function buildOverviewStatus(array $accountAuth, array $dataAuth, array $exceptionStatus): array
|
||
{
|
||
if ($accountAuth['tone'] === 'danger' || $dataAuth['tone'] === 'danger') {
|
||
return $this->buildStatusBlock('需重新授权', 'danger', '账号授权或数据授权已到期');
|
||
}
|
||
|
||
if ($exceptionStatus['tone'] === 'warning') {
|
||
return $this->buildStatusBlock('存在异常', 'warning', '当前账号发布链路异常');
|
||
}
|
||
|
||
return $this->buildStatusBlock('状态正常', 'success', '账号与发布状态均正常');
|
||
}
|
||
|
||
/**
|
||
* 统计当前筛选结果。
|
||
*
|
||
* @param array<int, array<string, mixed>> $items 列表项
|
||
* @return array<string, int>
|
||
*/
|
||
private function buildSummary(array $items): array
|
||
{
|
||
$summary = [
|
||
'total' => count($items),
|
||
'need_reauth_count' => 0,
|
||
'browser_exception_count' => 0,
|
||
'normal_count' => 0,
|
||
];
|
||
|
||
foreach ($items as $item) {
|
||
$needReauth = $item['account_status']['tone'] === 'danger' || $item['data_status']['tone'] === 'danger';
|
||
$hasBrowserException = $item['exception_status']['tone'] === 'warning';
|
||
|
||
if ($needReauth) {
|
||
$summary['need_reauth_count']++;
|
||
}
|
||
|
||
if ($hasBrowserException) {
|
||
$summary['browser_exception_count']++;
|
||
}
|
||
|
||
if (!$needReauth && !$hasBrowserException) {
|
||
$summary['normal_count']++;
|
||
}
|
||
}
|
||
|
||
return $summary;
|
||
}
|
||
|
||
/**
|
||
* 加载 acgpmw 的特殊账号名单。
|
||
*
|
||
* 这里优先读取基线项目 `/root/work/acgpmw/data/other/tw_special_account.php`,
|
||
* 保证 146/147 套餐用户的平台权限限制与原系统保持一致;
|
||
* 若文件不存在,则退回空数组,接口仍可工作。
|
||
*
|
||
* @return array<int>
|
||
*/
|
||
private function loadSpecialAccounts(): array
|
||
{
|
||
$specialAccountFile = '/root/work/acgpmw/data/other/tw_special_account.php';
|
||
|
||
if (!is_file($specialAccountFile)) {
|
||
return [];
|
||
}
|
||
|
||
$accounts = require $specialAccountFile;
|
||
|
||
return is_array($accounts)
|
||
? array_values(array_map('intval', $accounts))
|
||
: [];
|
||
}
|
||
}
|