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
+66
View File
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace app\api\controller\v1;
use app\api\common\Response;
use app\api\controller\BaseController;
use app\api\service\PlatformService;
use think\App;
/**
* 平台账号管理控制器。
*
* 本控制器仅提供小程序“平台账号管理”所需的最基础只读接口,
* 关键字段和状态含义按 acgpmw `platform.php` 列表逻辑对齐。
*/
class Platform extends BaseController
{
protected PlatformService $platformService;
public function __construct(App $app)
{
parent::__construct($app);
$this->platformService = new PlatformService();
}
/**
* 平台账号列表。
*
* GET /api/v1/platform/accounts
*
* 请求参数:
* - `platform`:平台编号,可选;为空时返回全部平台
*
* 返回结构:
* - `filters`:当前平台筛选项
* - `summary`:当前筛选结果统计
* - `list`:账号列表,含账号授权、数据授权、异常状态
*/
public function accounts()
{
try {
$payload = $this->request->payload ?? null;
if (!$payload || empty($payload['userid'])) {
return Response::error('未登录', 401);
}
$platformInput = $this->request->get('platform');
$platform = null;
if ($platformInput !== null && $platformInput !== '' && $platformInput !== 'all') {
if (!is_numeric((string) $platformInput)) {
return Response::error('平台参数格式错误', 400);
}
$platform = (int) $platformInput;
}
$result = $this->platformService->getAccountList((int) $payload['userid'], $platform);
return Response::success($result);
} catch (\Exception $exception) {
return Response::error($exception->getMessage(), $exception->getCode() ?: 500);
}
}
}
+46
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace app\api\model; namespace app\api\model;
use think\Model; use think\Model;
use think\Collection;
/** /**
* 授权账号模型(对应 dy_video_user 表) * 授权账号模型(对应 dy_video_user 表)
@@ -32,4 +33,49 @@ class DyVideoUser extends Model
->where('disabled', 0) ->where('disabled', 0)
->count(); ->count();
} }
/**
* 获取当前用户的平台账号列表。
*
* 这里按 acgpmw `platform::index()` 和 `platform::get_zhanghu_list()` 的查询条件对齐:
* - 仅返回当前用户数据
* - 仅返回 `disabled=0` 的有效账号
* - 排序保持 `is_endauth desc, id desc`
*
* @param int $userid 用户ID
* @param int|null $platform 指定平台;为空时返回全部平台
* @return Collection
*/
public static function getPlatformAccountsByUserId(int $userid, ?int $platform = null): Collection
{
$query = self::where('userid', $userid)
->where('disabled', 0)
->order(['is_endauth' => 'desc', 'id' => 'desc'])
->field([
'id',
'userid',
'platform',
'is_lanv',
'is_endauth',
'aa_endauth',
'browser',
'is_qyh',
'proviceid',
'info_shouji',
'dy_avatar',
'dy_nickname',
'dy_intro',
'dy_account',
'dy_openid',
'dy_unique_id',
'addtime',
'updatetime',
]);
if ($platform !== null) {
$query->where('platform', $platform);
}
return $query->select();
}
} }
+33
View File
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace app\api\model;
use think\Model;
/**
* TikTok 账号附表模型(对应 dy_video_user_titk 表)
*
* 该表在 acgpmw 平台账号管理页用于补充 TikTok 账号的国家信息。
*/
class DyVideoUserTitk extends Model
{
protected $connection = 'dbmember';
protected $name = 'dy_video_user_titk';
protected $pk = 'id';
protected $autoWriteTimestamp = false;
/**
* 根据授权账号 ID 获取 TikTok 附加信息。
*
* @param int $vuid 授权账号主表 ID
* @return self|null
*/
public static function findByVuid(int $vuid): ?self
{
return self::where('vuid', $vuid)->find();
}
}
+6
View File
@@ -3,6 +3,7 @@ declare(strict_types=1);
use think\facade\Route; use think\facade\Route;
use app\api\controller\v1\Auth; use app\api\controller\v1\Auth;
use app\api\controller\v1\Platform;
/** /**
* API 应用路由 * API 应用路由
@@ -19,3 +20,8 @@ Route::group('v1/auth', function () {
Route::post('logout', [Auth::class, 'logout']); Route::post('logout', [Auth::class, 'logout']);
Route::post('password', [Auth::class, 'password']); Route::post('password', [Auth::class, 'password']);
})->middleware(\app\api\middleware\Auth::class); })->middleware(\app\api\middleware\Auth::class);
// v1 平台账号管理接口(需登录)
Route::group('v1/platform', function () {
Route::get('accounts', [Platform::class, 'accounts']);
})->middleware(\app\api\middleware\Auth::class);
+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))
: [];
}
}