feat(publish-plan): add publish plan query and API routes
- Introduced `buildPublishPlanQuery` method in `DyVideoCron` model to create a base query for the publish plan module, filtering records based on user ID and project status. - Added new API routes under `v1/publish-plan` for listing, starting, and stopping publish plans, requiring user authentication.
This commit is contained in:
@@ -0,0 +1,713 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\service;
|
||||
|
||||
use app\api\model\DyVideoCron;
|
||||
use app\api\model\DyVideoUser;
|
||||
use app\api\model\Member;
|
||||
use think\Collection;
|
||||
use think\db\Query;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 发布计划服务。
|
||||
*
|
||||
* 本服务按 acgpmw 发布计划模块最小可用范围实现:
|
||||
* 1. 普通发布计划:`controller/cron.php::cron_list()`
|
||||
* 2. AI 项目计划:`controller/ai_project_cron.php::cron_list()`
|
||||
* 3. 启停动作:`controller/cron.php::fabu()` / `controller/cron.php::stop()`
|
||||
*
|
||||
* 当前不擅自补齐删除、编辑等复杂动作,避免偏离原始程序。
|
||||
*/
|
||||
class PublishPlanService
|
||||
{
|
||||
/**
|
||||
* 平台中文名直接按基线常用映射返回给前端,避免小程序自行猜测。
|
||||
*/
|
||||
private const PLATFORM_NAME_MAP = [
|
||||
0 => '抖音',
|
||||
1 => '快手',
|
||||
2 => '百家号',
|
||||
3 => '小红书',
|
||||
4 => '视频号',
|
||||
5 => 'B站',
|
||||
6 => '公众号',
|
||||
10 => 'TikTok',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取发布计划列表。
|
||||
*
|
||||
* @param int $userid 当前登录用户ID
|
||||
* @param array<string, mixed> $params 前端筛选参数
|
||||
* @return array<string, mixed>
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getPlanList(int $userid, array $params = []): array
|
||||
{
|
||||
$member = Member::findByUserid($userid);
|
||||
if (!$member) {
|
||||
throw new \Exception('用户不存在', 4004);
|
||||
}
|
||||
|
||||
$statusFilter = $this->normalizeStatusFilter((string) ($params['status'] ?? 'all'));
|
||||
$page = max(1, (int) ($params['page'] ?? 1));
|
||||
$pageSize = min(50, max(1, (int) ($params['page_size'] ?? 20)));
|
||||
|
||||
// 基础查询范围严格按 acgpmw 发布计划模块两个列表控制器对齐。
|
||||
$baseQuery = DyVideoCron::buildPublishPlanQuery($userid);
|
||||
|
||||
$total = (int) (clone $this->applyStatusFilter(clone $baseQuery, $statusFilter))->count();
|
||||
$records = $this->applyStatusFilter(clone $baseQuery, $statusFilter)
|
||||
->field([
|
||||
'id',
|
||||
'userid',
|
||||
'name',
|
||||
'status',
|
||||
'jrstop',
|
||||
'project_id',
|
||||
'project_type',
|
||||
'platform',
|
||||
'fbvuids',
|
||||
'atvuids',
|
||||
'fbvuids_a',
|
||||
'time_range',
|
||||
'starttime',
|
||||
'endtime',
|
||||
'meirinum',
|
||||
'plnum',
|
||||
'yifanum',
|
||||
'suc_fbnum',
|
||||
'err_fbnum',
|
||||
'fbdatetime',
|
||||
'addtime',
|
||||
'tplids',
|
||||
'videotemplates',
|
||||
])
|
||||
->order(['id' => 'desc'])
|
||||
->page($page, $pageSize)
|
||||
->select();
|
||||
|
||||
$projectNameMap = $this->loadProjectNames($userid, $records);
|
||||
$accountInfoMap = $this->loadAccountInfos($records);
|
||||
|
||||
$items = [];
|
||||
foreach ($records as $record) {
|
||||
$items[] = $this->buildPlanItem($record->toArray(), $projectNameMap, $accountInfoMap);
|
||||
}
|
||||
|
||||
return [
|
||||
'filters' => $this->buildFilters($baseQuery, $statusFilter),
|
||||
'summary' => $this->buildSummary($baseQuery),
|
||||
'pagination' => [
|
||||
'page' => $page,
|
||||
'page_size' => $pageSize,
|
||||
'total' => $total,
|
||||
'has_more' => $page * $pageSize < $total,
|
||||
],
|
||||
'list' => $items,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启发布计划。
|
||||
*
|
||||
* 此处按 acgpmw `cron::fabu()` 对齐,只恢复 `jrstop=0`。
|
||||
*
|
||||
* @param int $userid 当前登录用户ID
|
||||
* @param int $id 计划ID
|
||||
* @return array<string, int>
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function startPlan(int $userid, int $id): array
|
||||
{
|
||||
$plan = $this->getOwnedPlan($userid, $id);
|
||||
if ((int) $plan->status === 3) {
|
||||
throw new \Exception('已完成计划不支持继续开启', 4004);
|
||||
}
|
||||
|
||||
$affected = DyVideoCron::where('id', $id)
|
||||
->where('userid', $userid)
|
||||
->update(['jrstop' => 0]);
|
||||
|
||||
if (!$affected) {
|
||||
throw new \Exception('设置失败', 500);
|
||||
}
|
||||
|
||||
return ['id' => $id, 'jrstop' => 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停发布计划。
|
||||
*
|
||||
* 此处按 acgpmw `cron::stop()` 对齐,除设置 `jrstop=1` 外,
|
||||
* 还尝试把 `dy_cron_account` 中当前待执行记录改成状态 5。
|
||||
*
|
||||
* @param int $userid 当前登录用户ID
|
||||
* @param int $id 计划ID
|
||||
* @return array<string, int>
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function stopPlan(int $userid, int $id): array
|
||||
{
|
||||
$plan = $this->getOwnedPlan($userid, $id);
|
||||
if ((int) $plan->status === 3) {
|
||||
throw new \Exception('已完成计划不支持暂停', 4004);
|
||||
}
|
||||
|
||||
$affected = DyVideoCron::where('id', $id)
|
||||
->where('userid', $userid)
|
||||
->update(['jrstop' => 1]);
|
||||
|
||||
if (!$affected) {
|
||||
throw new \Exception('设置失败', 500);
|
||||
}
|
||||
|
||||
// 按 acgpmw `cron::stop()` 对齐处理新模式账号执行队列。
|
||||
Db::connect('dbmember')
|
||||
->name('dy_cron_account')
|
||||
->where('userid', $userid)
|
||||
->where('cron_id', $id)
|
||||
->where('status', 0)
|
||||
->update(['status' => 5]);
|
||||
|
||||
return ['id' => $id, 'jrstop' => 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载当前页涉及到的项目名称。
|
||||
*
|
||||
* AI 项目计划列表在 acgpmw 中会把 `project_id` 映射成项目名,
|
||||
* 这里同样在服务端完成,减少小程序字段推断。
|
||||
*
|
||||
* @param int $userid 当前登录用户ID
|
||||
* @param Collection<int, DyVideoCron> $records 当前页记录
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function loadProjectNames(int $userid, Collection $records): array
|
||||
{
|
||||
$projectIds = [];
|
||||
foreach ($records as $record) {
|
||||
$projectId = (int) $record->project_id;
|
||||
if ($projectId > 0) {
|
||||
$projectIds[] = $projectId;
|
||||
}
|
||||
}
|
||||
|
||||
$projectIds = array_values(array_unique($projectIds));
|
||||
if (empty($projectIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array<int, string> $projectMap */
|
||||
$projectMap = Db::connect('dbmember')
|
||||
->name('dy_ai_project')
|
||||
->where('userid', $userid)
|
||||
->whereIn('id', $projectIds)
|
||||
->column('name', 'id');
|
||||
|
||||
return $projectMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载当前页计划中用到的账号信息。
|
||||
*
|
||||
* @param Collection<int, DyVideoCron> $records 当前页记录
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function loadAccountInfos(Collection $records): array
|
||||
{
|
||||
$accountIds = [];
|
||||
foreach ($records as $record) {
|
||||
$accountIds = array_merge(
|
||||
$accountIds,
|
||||
$this->decodeIdList($record->fbvuids),
|
||||
$this->decodeIdList($record->atvuids),
|
||||
$this->decodeIdList($record->fbvuids_a)
|
||||
);
|
||||
}
|
||||
|
||||
$accountIds = array_values(array_unique(array_filter(array_map('intval', $accountIds))));
|
||||
if (empty($accountIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$accounts = DyVideoUser::whereIn('id', $accountIds)
|
||||
->field(['id', 'platform', 'dy_nickname'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$accountMap = [];
|
||||
foreach ($accounts as $account) {
|
||||
$accountMap[(int) $account['id']] = $account;
|
||||
}
|
||||
|
||||
return $accountMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把单条计划记录转换成小程序卡片数据。
|
||||
*
|
||||
* 字段映射依据:
|
||||
* - 发布进度:`plnum / yifanum / suc_fbnum / err_fbnum`
|
||||
* - 账号集合:`fbvuids / atvuids / fbvuids_a`
|
||||
* - 时间展示:`time_range / starttime / endtime / fbdatetime`
|
||||
* - 视频信息:`tplids / videotemplates`
|
||||
*
|
||||
* @param array<string, mixed> $record 原始记录
|
||||
* @param array<int, string> $projectNameMap 项目名称映射
|
||||
* @param array<int, array<string, mixed>> $accountInfoMap 账号信息映射
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildPlanItem(array $record, array $projectNameMap, array $accountInfoMap): array
|
||||
{
|
||||
$publishAccountIds = $this->decodeIdList($record['fbvuids'] ?? null);
|
||||
$atAccountIds = $this->decodeIdList($record['atvuids'] ?? null);
|
||||
$syncAccountIds = $this->decodeIdList($record['fbvuids_a'] ?? null);
|
||||
$materialIds = $this->decodeIdList($record['tplids'] ?? null);
|
||||
$videoTemplateIds = $this->decodeIdList($record['videotemplates'] ?? null);
|
||||
|
||||
$publishAccounts = $this->resolveAccountNames($publishAccountIds, $accountInfoMap);
|
||||
$atAccounts = $this->resolveAccountNames($atAccountIds, $accountInfoMap);
|
||||
$syncAccounts = $this->resolveAccountNames($syncAccountIds, $accountInfoMap);
|
||||
|
||||
$statusInfo = $this->buildStatusBlock(
|
||||
(int) ($record['status'] ?? 0),
|
||||
(int) ($record['jrstop'] ?? 0)
|
||||
);
|
||||
$actionInfo = $this->buildActionBlock($statusInfo);
|
||||
|
||||
$projectId = (int) ($record['project_id'] ?? 0);
|
||||
$projectType = (int) ($record['project_type'] ?? 0);
|
||||
$platform = (int) ($record['platform'] ?? 0);
|
||||
|
||||
return [
|
||||
'id' => (int) ($record['id'] ?? 0),
|
||||
'name' => (string) ($record['name'] ?? ''),
|
||||
'plan_type' => $projectId > 0 ? 'ai_project' : 'normal',
|
||||
'plan_type_text' => $projectId > 0 ? 'AI 项目计划' : '发布任务',
|
||||
'project_id' => $projectId,
|
||||
'project_type' => $projectType,
|
||||
'project_name' => $projectId > 0
|
||||
? ($projectNameMap[$projectId] ?? '无项目(异常)')
|
||||
: '',
|
||||
'platform' => $platform,
|
||||
'platform_name' => $projectType === 1
|
||||
? '作品下载'
|
||||
: (self::PLATFORM_NAME_MAP[$platform] ?? ('平台' . $platform)),
|
||||
'status' => $statusInfo,
|
||||
'action' => $actionInfo,
|
||||
'publish_time_text' => $this->buildPublishTimeText($record),
|
||||
'schedule_text' => $this->buildScheduleText($record),
|
||||
'created_at_text' => $this->formatDateTime((int) ($record['addtime'] ?? 0)),
|
||||
'metrics' => [
|
||||
[
|
||||
'key' => 'progress',
|
||||
'label' => '已发 / 总量',
|
||||
'value' => (int) ($record['yifanum'] ?? 0) . ' / ' . (int) ($record['plnum'] ?? 0),
|
||||
'tone' => 'blue',
|
||||
],
|
||||
[
|
||||
'key' => 'result',
|
||||
'label' => '成功 / 失败',
|
||||
'value' => (int) ($record['suc_fbnum'] ?? 0) . ' / ' . (int) ($record['err_fbnum'] ?? 0),
|
||||
'tone' => 'green',
|
||||
],
|
||||
[
|
||||
'key' => 'accounts',
|
||||
'label' => '关联账号',
|
||||
'value' => (string) (count($publishAccountIds) + count($atAccountIds) + count($syncAccountIds)),
|
||||
'tone' => 'amber',
|
||||
],
|
||||
],
|
||||
'account_summary_text' => $this->buildAccountSummaryText($publishAccounts, $atAccounts, $syncAccounts),
|
||||
'video_info_text' => $this->buildVideoInfoText($materialIds, $videoTemplateIds),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建状态筛选项。
|
||||
*
|
||||
* 这里把小程序筛选态映射为更稳定的业务语义:
|
||||
* - `running`:`status != 3 and jrstop = 0`
|
||||
* - `stopped`:`status != 3 and jrstop = 1`
|
||||
* - `finished`:`status = 3`
|
||||
*
|
||||
* @param Query $baseQuery 发布计划基础查询
|
||||
* @param string $currentStatus 当前筛选值
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildFilters(Query $baseQuery, string $currentStatus): array
|
||||
{
|
||||
$items = [];
|
||||
foreach ($this->getStatusFilterMap() as $key => $label) {
|
||||
$count = (int) (clone $this->applyStatusFilter(clone $baseQuery, $key))->count();
|
||||
$items[] = [
|
||||
'key' => $key,
|
||||
'name' => $label,
|
||||
'count' => $count,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'current_status' => $currentStatus,
|
||||
'items' => $items,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建顶部统计卡片。
|
||||
*
|
||||
* @param Query $baseQuery 发布计划基础查询
|
||||
* @return array<string, int>
|
||||
*/
|
||||
private function buildSummary(Query $baseQuery): array
|
||||
{
|
||||
return [
|
||||
'total' => (int) (clone $baseQuery)->count(),
|
||||
'running_count' => (int) (clone $this->applyStatusFilter(clone $baseQuery, 'running'))->count(),
|
||||
'stopped_count' => (int) (clone $this->applyStatusFilter(clone $baseQuery, 'stopped'))->count(),
|
||||
'finished_count' => (int) (clone $this->applyStatusFilter(clone $baseQuery, 'finished'))->count(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用状态筛选。
|
||||
*
|
||||
* @param Query $query 查询对象
|
||||
* @param string $statusFilter 筛选值
|
||||
* @return Query
|
||||
*/
|
||||
private function applyStatusFilter(Query $query, string $statusFilter): Query
|
||||
{
|
||||
switch ($statusFilter) {
|
||||
case 'running':
|
||||
$query->where('status', '<>', 3)->where('jrstop', 0);
|
||||
break;
|
||||
|
||||
case 'stopped':
|
||||
$query->where('status', '<>', 3)->where('jrstop', 1);
|
||||
break;
|
||||
|
||||
case 'finished':
|
||||
$query->where('status', 3);
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化状态筛选参数。
|
||||
*
|
||||
* @param string $statusFilter 前端传入的筛选值
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function normalizeStatusFilter(string $statusFilter): string
|
||||
{
|
||||
$statusFilter = $statusFilter !== '' ? $statusFilter : 'all';
|
||||
if (!array_key_exists($statusFilter, $this->getStatusFilterMap())) {
|
||||
throw new \Exception('状态筛选参数错误', 400);
|
||||
}
|
||||
|
||||
return $statusFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取筛选映射。
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function getStatusFilterMap(): array
|
||||
{
|
||||
return [
|
||||
'all' => '全部',
|
||||
'running' => '进行中',
|
||||
'stopped' => '已停止',
|
||||
'finished' => '已完成',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建计划状态块。
|
||||
*
|
||||
* @param int $status 原始状态码
|
||||
* @param int $jrstop 今日暂停标记
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildStatusBlock(int $status, int $jrstop): array
|
||||
{
|
||||
if ($status === 3) {
|
||||
return [
|
||||
'key' => 'finished',
|
||||
'text' => '已完成',
|
||||
'tone' => 'success',
|
||||
'raw_status' => $status,
|
||||
'jrstop' => $jrstop,
|
||||
];
|
||||
}
|
||||
|
||||
if ($jrstop === 1) {
|
||||
return [
|
||||
'key' => 'stopped',
|
||||
'text' => '已停止',
|
||||
'tone' => 'danger',
|
||||
'raw_status' => $status,
|
||||
'jrstop' => $jrstop,
|
||||
];
|
||||
}
|
||||
|
||||
$statusTextMap = [
|
||||
1 => '发布中',
|
||||
5 => '定时执行',
|
||||
9 => '定时执行',
|
||||
10 => '计划执行中',
|
||||
20 => '作品生成中',
|
||||
];
|
||||
|
||||
return [
|
||||
'key' => 'running',
|
||||
'text' => $statusTextMap[$status] ?? ('状态 ' . $status),
|
||||
'tone' => 'primary',
|
||||
'raw_status' => $status,
|
||||
'jrstop' => $jrstop,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态块构建可执行动作。
|
||||
*
|
||||
* @param array<string, mixed> $statusInfo 状态块
|
||||
* @return array<string, string>|null
|
||||
*/
|
||||
private function buildActionBlock(array $statusInfo): ?array
|
||||
{
|
||||
if ($statusInfo['key'] === 'finished') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($statusInfo['key'] === 'stopped') {
|
||||
return [
|
||||
'type' => 'start',
|
||||
'text' => '继续计划',
|
||||
'tone' => 'primary',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'stop',
|
||||
'text' => '暂停计划',
|
||||
'tone' => 'danger',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成发布时间文案。
|
||||
*
|
||||
* @param array<string, mixed> $record 原始计划记录
|
||||
* @return string
|
||||
*/
|
||||
private function buildPublishTimeText(array $record): string
|
||||
{
|
||||
$fbdatetime = (int) ($record['fbdatetime'] ?? 0);
|
||||
if ($fbdatetime > 0) {
|
||||
return $this->formatDate($fbdatetime);
|
||||
}
|
||||
|
||||
return $this->formatDate((int) ($record['addtime'] ?? 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成执行时间文案。
|
||||
*
|
||||
* 普通计划按 `time_range` / `starttime` / `endtime` 对齐展示,
|
||||
* 下载型项目计划没有明确发布时间段时,返回固定文案。
|
||||
*
|
||||
* @param array<string, mixed> $record 原始计划记录
|
||||
* @return string
|
||||
*/
|
||||
private function buildScheduleText(array $record): string
|
||||
{
|
||||
$projectType = (int) ($record['project_type'] ?? 0);
|
||||
if ($projectType === 1) {
|
||||
return '0点至24点';
|
||||
}
|
||||
|
||||
$timeRange = trim((string) ($record['time_range'] ?? ''));
|
||||
if ($timeRange !== '') {
|
||||
$timePoints = array_values(array_filter(array_map('trim', explode(',', $timeRange)), static function ($item) {
|
||||
return $item !== '';
|
||||
}));
|
||||
|
||||
if (!empty($timePoints)) {
|
||||
return implode('、', array_map(static function ($item) {
|
||||
return $item . '点';
|
||||
}, $timePoints));
|
||||
}
|
||||
}
|
||||
|
||||
$startTime = (int) ($record['starttime'] ?? 0);
|
||||
$endTime = (int) ($record['endtime'] ?? 0);
|
||||
if ($startTime > 0 || $endTime > 0) {
|
||||
return $startTime . '点至' . $endTime . '点';
|
||||
}
|
||||
|
||||
return '未设置';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成账号摘要文案。
|
||||
*
|
||||
* @param array<int, string> $publishAccounts 发布账号名
|
||||
* @param array<int, string> $atAccounts @账号名
|
||||
* @param array<int, string> $syncAccounts 同步账号名
|
||||
* @return string
|
||||
*/
|
||||
private function buildAccountSummaryText(array $publishAccounts, array $atAccounts, array $syncAccounts): string
|
||||
{
|
||||
$parts = [];
|
||||
if (!empty($publishAccounts)) {
|
||||
$parts[] = '发布 ' . count($publishAccounts) . ' 个';
|
||||
}
|
||||
if (!empty($atAccounts)) {
|
||||
$parts[] = '@账号 ' . count($atAccounts) . ' 个';
|
||||
}
|
||||
if (!empty($syncAccounts)) {
|
||||
$parts[] = '同步 ' . count($syncAccounts) . ' 个';
|
||||
}
|
||||
|
||||
if (empty($parts)) {
|
||||
return '暂无关联账号';
|
||||
}
|
||||
|
||||
return implode(' / ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成视频信息摘要。
|
||||
*
|
||||
* @param array<int> $materialIds 素材ID集合
|
||||
* @param array<int> $videoTemplateIds 视频模板ID集合
|
||||
* @return string
|
||||
*/
|
||||
private function buildVideoInfoText(array $materialIds, array $videoTemplateIds): string
|
||||
{
|
||||
$parts = [];
|
||||
if (!empty($materialIds)) {
|
||||
$parts[] = '素材 ' . count($materialIds) . ' 个';
|
||||
}
|
||||
if (!empty($videoTemplateIds)) {
|
||||
$parts[] = '视频模板 ' . count($videoTemplateIds) . ' 个';
|
||||
}
|
||||
|
||||
if (empty($parts)) {
|
||||
return '暂无视频信息';
|
||||
}
|
||||
|
||||
return implode(' / ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析序列化 ID 列表。
|
||||
*
|
||||
* `dy_video_cron` 中多个账号和素材字段都按 PHP serialize 存储,
|
||||
* 这里统一做兼容解析,避免前端处理历史格式。
|
||||
*
|
||||
* @param mixed $serializedValue 原始字段值
|
||||
* @return array<int>
|
||||
*/
|
||||
private function decodeIdList($serializedValue): array
|
||||
{
|
||||
if (empty($serializedValue) || !is_string($serializedValue)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = @unserialize($serializedValue);
|
||||
if (!is_array($decoded)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter(array_map('intval', $decoded))));
|
||||
}
|
||||
|
||||
/**
|
||||
* 把账号 ID 集合映射为账号名称集合。
|
||||
*
|
||||
* @param array<int> $accountIds 账号ID集合
|
||||
* @param array<int, array<string, mixed>> $accountInfoMap 账号信息映射
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function resolveAccountNames(array $accountIds, array $accountInfoMap): array
|
||||
{
|
||||
$names = [];
|
||||
foreach ($accountIds as $accountId) {
|
||||
if (!isset($accountInfoMap[$accountId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$account = $accountInfoMap[$accountId];
|
||||
$platformName = self::PLATFORM_NAME_MAP[(int) ($account['platform'] ?? 0)] ?? '平台';
|
||||
$nickname = trim((string) ($account['dy_nickname'] ?? ''));
|
||||
$names[] = $nickname !== '' ? ($platformName . '·' . $nickname) : ($platformName . '·账号' . $accountId);
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户拥有的发布计划。
|
||||
*
|
||||
* @param int $userid 当前登录用户ID
|
||||
* @param int $id 计划ID
|
||||
* @return DyVideoCron
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getOwnedPlan(int $userid, int $id): DyVideoCron
|
||||
{
|
||||
$plan = DyVideoCron::buildPublishPlanQuery($userid)
|
||||
->where('id', $id)
|
||||
->find();
|
||||
|
||||
if (!$plan) {
|
||||
throw new \Exception('发布计划不存在', 404);
|
||||
}
|
||||
|
||||
return $plan;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期。
|
||||
*
|
||||
* @param int $timestamp Unix 时间戳
|
||||
* @return string
|
||||
*/
|
||||
private function formatDate(int $timestamp): string
|
||||
{
|
||||
if ($timestamp <= 0) {
|
||||
return '未设置';
|
||||
}
|
||||
|
||||
return date('Y-m-d', $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期时间。
|
||||
*
|
||||
* @param int $timestamp Unix 时间戳
|
||||
* @return string
|
||||
*/
|
||||
private function formatDateTime(int $timestamp): string
|
||||
{
|
||||
if ($timestamp <= 0) {
|
||||
return '未设置';
|
||||
}
|
||||
|
||||
return date('Y-m-d H:i', $timestamp);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user