feat(platform): add endpoint to retrieve platform accounts for a user

- 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.
This commit is contained in:
nepiedg
2026-04-02 07:39:37 +00:00
parent 6b46767d86
commit c909ebdf88
5 changed files with 476 additions and 0 deletions
+325
View File
@@ -0,0 +1,325 @@
<?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))
: [];
}
}