苹果CMS 接入TMDB API:新增每日热门影视独立页

机器人
摘要
Mxchild
生成中...

苹果cms系统的影视热度榜单一直靠站内真实点击量,对于个人站点或规模较小的网站,热榜数据容易失真。 TMDB官方接口可是按天/周粒度拉全网热度,电影、电视剧、动漫(用 tv 当载体)一把抓,关键是免费层 50 QPS 完全够个人影视站用,就决定把它接到苹果 CMS 上做一个独立页,实现真实的影片热度数据。

一、实现功能

下面这些是这次接入真正落地的能力点,每条都能在 /label/tmdb.html 上直接验证:

  • 综合热榜 / 电影热榜 / 电视剧热榜 三种媒体类型切换:type=all|movie|tv 参数控制,对应 TMDB /trending/{all|movie|tv}/{day|week} 三个官方接口。
  • 今日榜 / 本周榜 两种时间窗口切换:time=day|week 参数控制,TMDB 官方按这两粒度维护热度。
  • 可调返回条数:limit 参数支持 1-50,默认 20,前端要 Top10 直接 ?limit=10。
  • 服务端缓存 24 小时:TTL=86400 秒,避免对 TMDB 高频请求撞 QPS 限流。
  • 快捷 URL 路由:除主入口外,单独暴露 /api.php/tmdbhot/top10、/top20、/movie、/tv、/all、/week 一堆快捷接口,前端不用拼 query string。
  • 一键清缓存:/api.php/tmdbhot/refresh 把所有 key 一次性清掉,TMDB 配置改了想立即生效调一次就行。
  • 数据归一化:标题兼容 title/name、日期兼容 release_date/first_air_date、海报与背景图自动拼 image.tmdb.org/t/p/w500、评分/人气/原标题/类型 id/原语言/成人标记全部带回来。
  • 类型过滤:media_type=person 在 provider 层直接跳过,名单里不会出现 TMDB 的「人物」条目污染影视榜。
  • 混合榜按评分排序:type=all 时把电影和电视剧按 vote_average 降序拼一起,避免某类霸榜。

二、效果预览

图片[1] - 苹果CMS 接入TMDB API:新增每日热门影视独立页 - 迅维博客网

三、完整处理流程

接入链条不长,5 跳就到 TMDB 官方 API,下面按 6 段拆,每段都标注了关键文件 + 行号,方便照着抄。

用户访问 /label/tmdb.html
  ↓
ThinkPHP 路由 (route.php:671)
  'label-<file>' → label/index
  ↓
Label::__construct() (Label.php)
  取 file=douban/tmdb → template/ds3/name/label/tmdb.html
  ↓
tmdb.html (前端壳子)
  jQuery ajax → GET /api.php/tmdbhot
  ↓
Tmdbhot::index() (api/controller/Tmdbhot.php)
  参数校验 → 读 Cache → 未命中则调 provider
  ↓
TmdbExternalSourceProvider::fetchRecent() (common/util/TmdbExternalSourceProvider.php)
  拼 URL → curlPostWithTimeout 调 TMDB
  ↓
TMDB 官方: /trending/{type}/{time}?language=zh-CN&api_key=***
  ↓
normalizeList() 归一化 → 写 Cache → return JSON
  ↓
前端 $.each 渲染排名/海报/标题/简介 → 点击跳站内搜索

3.1 模板与前端渲染

template/ds3/name/label/tmdb.html 是个纯壳子,结构就三块:banner + 容器 + jQuery ajax。

<link rel="stylesheet" href="/static/ds3/css/tmdb.css">

<div class="page-banner tmdb-page-banner">
    <h1><i class="iconfont-ylfb yb-paihangbang"></i> TMDB 热榜</h1>
    <p>TMDB热门影视,每日更新</p>
</div>

<div class="box-width-small">
    <div class="tmdb-hot-list" id="tmdbHotList">
        <div class="loading">加载中...</div>
    </div>
</div>

<script>
$(function() {
    $.ajax({
        url: '/api.php/tmdbhot',
        type: 'GET',
        dataType: 'json',
        success: function(res) {
            if (res.code == 200 && res.data && res.data.length > 0) {
                var html = '';
                $.each(res.data, function(i, item) {
                    var rank = i + 1;
                    var rankClass = rank <= 3 ? 'top' + rank : '';
                    html += '<a class="tmdb-item" href="/vodsearch.html?wd='
                         + encodeURIComponent(item.item_title) + '">';
                    html += '<span class="tmdb-rank ' + rankClass + '">' + rank + '</span>';
                    html += '<img class="lazy lazy1" data-src="' + item.item_cover + '"'
                         + ' onerror="..." alt="' + item.item_title + '" />';
                    // 标题、类型、评分、简介...
                });
                $('#tmdbHotList').html(html);
            }
        }
    });
});
</script>

3.2 API 控制器 Tmdbhot

application/api/controller/Tmdbhot.php 是这次的核心入口。它本身是 ThinkPHP 的 api 模块控制器,挂在 api.php 这个入口下,路由规则 ThinkPHP 默认就是 /api.php/{controller}/{action}。

主入口 index() 处理流程

public function index(Request $request)
{
    header('Content-Type: application/json; charset=utf-8');

    // 1. 取参数 + 白名单校验
    $type   = isset($_GET['type'])   ? trim($_GET['type'])   : 'all';
    $time   = isset($_GET['time'])   ? trim($_GET['time'])   : 'day';
    $limit  = isset($_GET['limit'])  ? max(1, min(50, intval($_GET['limit']))) : 20;
    $refresh= isset($_GET['refresh']) && $_GET['refresh'] === '1';

    $type = in_array($type, ['all','movie','tv'])  ? $type : 'all';
    $time = in_array($time, ['day','week'])        ? $time : 'day';

    // 2. 缓存 key
    $cacheKey = 'tmdb_trending_' . $type . '_' . $time . '_' . $limit;

    // 3. 命中缓存直接返回
    if (!$refresh) {
        $cached = Cache::get($cacheKey);
        if (!empty($cached)) {
            echo json_encode([
                'code'=>200, 'msg'=>'success', 'data'=>$cached,
                'cached'=>true, 'type'=>$type, 'time'=>$time, 'limit'=>$limit
            ], JSON_UNESCAPED_UNICODE);
            exit;
        }
    } else {
        Cache::rm($cacheKey);    // 强制刷新
    }

    // 4. 取 TMDB 配置 + 调 provider
    $cfg    = config('maccms.ai_search');
    $tmdbCfg= $cfg['external_sources']['sources']['tmdb'] ?? [];
    $provider = new TmdbExternalSourceProvider($tmdbCfg);
    $data   = $provider->fetchRecent([
        'limit'      => $limit,
        'media_type' => $type,
        'time_window'=> $time,
    ]);

    // 5. 写缓存 + 返回
    if (!empty($data)) {
        Cache::set($cacheKey, $data, 86400);   // TTL 1 天
    }
    echo json_encode([
        'code'=>200, 'msg'=>'success', 'data'=>$data,
        'cached'=>false, 'type'=>$type, 'time'=>$time, 'limit'=>$limit
    ], JSON_UNESCAPED_UNICODE);
    exit;
}

关键设计点

  • 缓存粒度 = type × time × limit:三个参数决定一份独立缓存。
  • TTL = 86400 秒(1 天):和 TMDB 官方 trending/{day} 的更新节奏对齐,超过一天价值也不大。
  • refresh=1 主动失效:调试或配置变更时强制重拉,不需要等 24 小时。

支持的接口完整列表

URL等价参数用途
/api.php/tmdbhot主入口,参数自由组合
/api.php/tmdbhot/alltype=all综合(电影+电视剧)
/api.php/tmdbhot/movietype=movie仅电影
/api.php/tmdbhot/tvtype=tv仅电视剧
/api.php/tmdbhot/weektime=week本周榜
/api.php/tmdbhot/top10limit=10Top10 快捷
/api.php/tmdbhot/top20limit=20Top20 快捷
/api.php/tmdbhot/refresh清掉所有 24 个缓存 key
/api.php/tmdbhot/categories返回可选分类元数据

3.3 三方 Provider

application/common/util/TmdbExternalSourceProvider.php 负责真正和 TMDB 官方 API 对接。它实现了 ExternalSourceProviderInterface 接口,除了 fetchRecent(拉热榜)还提供了 search(关键字搜影片)方法。

配置读取application/extra/maccms.php 第 797 行):

'external_sources' => array(
    'sources' => array(
        'tmdb' => array(
            'enabled'       => '1',
            'base_url'      => 'https://api.themoviedb.org/3',
            'image_base_url'=> 'https://image.tmdb.org/t/p/w500',
            'language'      => 'zh-CN',
            'region'        => 'CN',
            'api_key'       => 'xxxxxxxx',
        ),
    ),
),

fetchRecent() 核心逻辑

public function fetchRecent(array $options = [])
{
    if (!$this->isEnabled()) return [];

    $limit      = max(1, min(50, intval($options['limit'] ?? 20)));
    $mediaType  = $options['media_type']  ?? 'all';
    $timeWindow = $options['time_window'] ?? 'day';

    // 路径: /trending/{all|movie|tv}/{day|week}
    $path   = '/trending/' . $mediaType . '/' . $timeWindow;
    $params = ['language' => $this->getLanguage()];

    $rows = $this->request($path, $params);
    if (!is_array($rows)) return [];

    $data = array_slice($this->normalizeList($rows, $mediaType), 0, $limit);

    // 混合榜按评分排序
    if ($mediaType === 'all' && !empty($data)) {
        usort($data, fn($a, $b) => $b['item_score'] <=> $a['item_score']);
    }
    return $data;
}

private function request($path, array $params)
{
    $params['api_key'] = $this->getApiKey();
    $url = $this->getBaseUrl() . $path . '?' . http_build_query($params);
    $resp = HttpClient::curlPostWithTimeout($url, '', ['Accept: application/json'], 10, false);
    if ($resp === false || $resp === '') return [];
    $json = json_decode((string)$resp, true);
    if (!is_array($json) || empty($json['results'])) return [];
    return $json['results'];
}

normalizeList() 字段归一化——这是整个 provider 的核心,把 TMDB 那套英语字段转成站内统一结构:

$out[] = [
    'provider_code'      => 'tmdb',
    'item_key'           => $itemMediaType . '_' . $id,
    'item_mid'           => $itemMediaType === 'tv' ? 1 : 2, // 1=电视剧 2=电影
    'item_title'         => $title,
    'item_subtitle'      => $this->getMediaTypeLabel($itemMediaType),  // 电影/电视剧
    'item_snippet'       => $overview,
    'item_url'           => 'https://www.themoviedb.org/' . ($itemMediaType === 'tv' ? 'tv' : 'movie') . '/' . $id,
    'item_cover'         => $cover,                                   // 海报
    'item_backdrop'      => $backdrop,                                // 背景图
    'item_score'         => $vote,                                    // 评分
    'item_vote_count'    => $voteCount,
    'item_popularity'    => $popularity,                              // 人气
    'item_release_date'  => $releaseDate,
    'item_original_title'=> $originalTitle,
    'item_language'      => $originalLanguage,
    'item_adult'         => $adult,
    'item_genre_ids'     => $genreIds,
    'item_payload'       => json_encode($row, JSON_UNESCAPED_UNICODE),
];

四、操作步骤

下面按 「检查 → 加配置 → 改文件 → 加路由 SEO → 验证」 的顺序写,照着抄就能落地。

4.1 准备 TMDB API Key

themoviedb.org/settings/api 申请一个 v3 auth key。

4.2 写配置文件

登录苹果cms系统管理台,配置TMDB信息,新版的苹果cms支持TMDB信息配置。

打开 application/extra/maccms.php,找到 maccms.ai_search.external_sources.sources.tmdb(约第 797 行),看 enabled、api_key、base_url、image_base_url、language、region 五项是否填齐。没有就整段新增:

'tmdb' => array(
    'enabled'        => '1',
    'base_url'       => 'https://api.themoviedb.org/3',
    'image_base_url' => 'https://image.tmdb.org/t/p/w500',
    'language'       => 'zh-CN',
    'region'         => 'CN',
    'api_key'        => '你的32位hex key',
),

4.3 新增 TmdbExternalSourceProvider

新增 application/common/util/TmdbExternalSourceProvider.php 文件

4.4 新增 Tmdbhot API 控制器

新增application/api/controller/Tmdbhot.php 文件

4.5 新增模板与样式

新建 template/ds3/name/label/tmdb.html文件,用于前端页面显示。

    4.7 清理缓存 + 验证

    跑 6 个 URL 验证整套链路:

    GET /label/tmdb.html                                  → 200 + 渲染热榜
    GET /api.php/tmdbhot                                  → 200 + cached:false
    GET /api.php/tmdbhot                                  → 200 + cached:true
    GET /api.php/tmdbhot?type=movie&time=week&limit=10    → 200 + 仅电影 10 条
    GET /api.php/tmdbhot/top20                            → 200 + 20 条
    GET /api.php/tmdbhot/refresh                          → 200 + cleared_keys:24

    TMDB 数据在第三方,根本没法人为干预,榜单就是真实的全球热度。所以这套改造的本质:用一次性的 4 个文件改动,换取一个零维护、抗污染、独立 SEO、高用户感知的每日热榜页面。

    五、核心代码下载

    核心php文件下载,前端模板自己基于自己网站风格写吧,或者直接把本地址丢给ai大模型实现。

    本文仅用于学习测试使用,请勿用于商业用途。

    © 版权声明
    THE END
    喜欢就支持一下吧
    点赞7 分享
    评论 抢沙发

    请登录后发表评论

      暂无评论内容