观影:豆瓣简介补上导演/主演/上映年份等主要信息
用户原话:「观影模块,添加电影的时候,从豆瓣获取的信息不够,
应该把导演、主要演员,上映年份等主要信息,也都放在简介中。」
**改了什么**
- 新增 `Douban::summaryText($meta, $title, $withPlot)`:把主要信息拼成一段可读文本。
格式(只有有值的行才出现,绝不写"导演:"这种空壳行):
《片名》(年份)
导演:X 主演:A、B、C 类型:… 片长:… 制片国家/地区:… 又名:… 豆瓣评分:…
<空行>
<剧情简介原文>
标签词与详情页 `db-meta` 用的同一套(年份/导演/主演…),同一字段在两处叫同一个名字。
- `movies/douban` 接口新增 `summary` 字段,**在服务端拼**。
为什么不在前端拼:这段文本会**写进 movie_details.info**(落库、与豆瓣脱钩),
拼法必须可测、只有一处;拼在 JS 里既测不到,也没法在"重新抓取"之外复用。
- 前端 `apply()` 改为填 `data.summary`(原来只填 `meta.plot`),
并补了两处**原来缺失的反馈**:
· 简介里已有用户写的内容 → 明确告知"没有覆盖它"(并提示清空后重抓);
· 豆瓣确实没有可用简介文本 → 也明确说一声。
之前这两种情况都是**静默什么都不做**,用户会以为"抓取坏了"。
简介框 rows 3 → 6(内容变多了),placeholder 也说明了会自动带上主要信息。
**验证**
- 新增 `_fb_douban_summary.py` 22 条,覆盖:字段齐全 / 缺字段不留空壳行 /
全空返回空串 / 只有剧情时不带多余空行 / 接口确实吐 summary /
前端写入的值只能是 summary(按 `infoEl.value = …` 的实际赋值断言,
不是"文件里有没有某个词")/ 仍然只填空不覆盖。
- **反向验证**:把拼装改回"简介=plot"→ ① 组 8 条立刻红(片名/年份/导演/主演/
类型/片长/国家/评分全部缺失),还原后复绿。
- **线上真连豆瓣**实测(`哪吒之魔童降世` / `流浪地球`):year/director/actors/
genre/runtime/country/rating/plot 全部有值,拼出的简介如
《哪吒之魔童降世》(2019)/导演:饺子/主演:吕艳婷 / 囧森瑟夫 / 瀚墨 …/
类型:剧情 / 喜剧 / 动画/片长:110分钟/豆瓣评分:8.4 + 剧情原文。
- 部署脚本加 4 条静态断言(summaryText 在服务端、接口吐 summary、
前端不许再只拿 meta.plot、摘不到要提示)。
- 回归:e2e 775/0 · 豆瓣简介 22/0 · 第 5 轮专项 91/0 · 导入 21/0。
This commit is contained in:
@@ -173,6 +173,85 @@ final class Douban
|
|||||||
return isset($d[$k]) && is_scalar($d[$k]) ? (string)$d[$k] : '';
|
return isset($d[$k]) && is_scalar($d[$k]) ? (string)$d[$k] : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼一份**带主要信息**的简介(v24 用户 2026-10-03 第 2 条)。
|
||||||
|
*
|
||||||
|
* 用户原话:「添加电影的时候,从豆瓣获取的信息不够,应该把导演、主要演员,
|
||||||
|
* 上映年份等主要信息,也都放在简介中。」
|
||||||
|
*
|
||||||
|
* 为什么要有这个函数(而不是在路上顺手拼字符串):
|
||||||
|
* · 简介是**写进 movie_details.info 的正文**,一旦存下来就与豆瓣脱钩了
|
||||||
|
* (以后豆瓣改了也不会变)。所以拼法必须是一处定义、可测:
|
||||||
|
* `_fb_douban_summary.php` 直接断言各种组合下的输出。
|
||||||
|
* · 用户要求"主要信息**也都放在简介中**",即这些字段要**落进正文**,
|
||||||
|
* 而不是只躺在隐藏的 douban_meta JSON 里(那个详情页另有展示,
|
||||||
|
* 但编辑表单里看不到、复制出去也没有)。
|
||||||
|
*
|
||||||
|
* 格式(只有有值的行才出现,绝不写"导演:"这种空壳行):
|
||||||
|
* 《片名》(年份)
|
||||||
|
* 导演:X
|
||||||
|
* 主演:A、B、C
|
||||||
|
* 类型:剧情 / 家庭
|
||||||
|
* 片长:125分钟
|
||||||
|
* 制片国家/地区:中国大陆
|
||||||
|
* 豆瓣评分:8.6
|
||||||
|
*
|
||||||
|
* <剧情简介原文>
|
||||||
|
*
|
||||||
|
* ⚠️ 用与详情页 `db-meta` 相同的标签词(年份/类型/片长/导演/主演/制片国家/地区),
|
||||||
|
* 这样同一个字段在表单与详情页叫同一个名字,用户不会以为是两回事。
|
||||||
|
*
|
||||||
|
* @param array $meta fetchDetail() 产出的 meta
|
||||||
|
* @param string $title 片名(可能来自候选/用户输入)
|
||||||
|
* @param bool $withPlot 是否附带剧情简介原文(默认带)
|
||||||
|
* @return string 可直接写进 info 的文本;什么信息都没有时返回 ''
|
||||||
|
*/
|
||||||
|
public static function summaryText(array $meta, string $title = '', bool $withPlot = true): string
|
||||||
|
{
|
||||||
|
$title = trim($title);
|
||||||
|
$year = trim((string)($meta['year'] ?? ''));
|
||||||
|
$lines = [];
|
||||||
|
|
||||||
|
// 第一行:片名(年份)
|
||||||
|
$head = '';
|
||||||
|
if ($title !== '') {
|
||||||
|
$head = '《' . $title . '》';
|
||||||
|
if ($year !== '') {
|
||||||
|
$head .= '(' . $year . ')';
|
||||||
|
}
|
||||||
|
} elseif ($year !== '') {
|
||||||
|
$head = $year . ' 年';
|
||||||
|
}
|
||||||
|
if ($head !== '') {
|
||||||
|
$lines[] = $head;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主要信息:顺序按"用户最关心 → 次要",空的不占行
|
||||||
|
$fields = [
|
||||||
|
'导演' => (string)($meta['director'] ?? ''),
|
||||||
|
'主演' => (string)($meta['actors'] ?? ''),
|
||||||
|
'类型' => (string)($meta['genre'] ?? ''),
|
||||||
|
'片长' => (string)($meta['runtime'] ?? ''),
|
||||||
|
'制片国家/地区' => (string)($meta['country'] ?? ''),
|
||||||
|
'又名' => (string)($meta['alias'] ?? ''),
|
||||||
|
'豆瓣评分' => (string)($meta['rating'] ?? ''),
|
||||||
|
];
|
||||||
|
foreach ($fields as $label => $val) {
|
||||||
|
$val = trim($val);
|
||||||
|
if ($val !== '') {
|
||||||
|
$lines[] = $label . ':' . $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$plot = $withPlot ? trim((string)($meta['plot'] ?? '')) : '';
|
||||||
|
if ($plot !== '') {
|
||||||
|
$lines[] = ''; // 空行:把"事实"与"剧情"分开,读起来清爽
|
||||||
|
$lines[] = $plot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim(implode("\n", $lines));
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------- 搜索候选整理 ----------------
|
// ---------------- 搜索候选整理 ----------------
|
||||||
|
|
||||||
/** 规范化片名用于匹配:去空白、去常见标点、转小写 */
|
/** 规范化片名用于匹配:去空白、去常见标点、转小写 */
|
||||||
|
|||||||
@@ -2396,6 +2396,14 @@ switch ($r) {
|
|||||||
'source' => 'douban',
|
'source' => 'douban',
|
||||||
'title' => (string)($resDb['title'] ?? ''),
|
'title' => (string)($resDb['title'] ?? ''),
|
||||||
'meta' => $resDb['meta'],
|
'meta' => $resDb['meta'],
|
||||||
|
// 拼好的简介(含导演/主演/年份/类型/片长/国家/评分 + 剧情原文)。
|
||||||
|
// 放在服务端拼、由前端填空 —— 见 Douban::summaryText 的注释:
|
||||||
|
// 这份文本会**落库**(movie_details.info),所以拼法要可测、只有一处。
|
||||||
|
'summary' => Douban::summaryText(
|
||||||
|
(array)$resDb['meta'],
|
||||||
|
(string)($resDb['title'] ?? ''),
|
||||||
|
true
|
||||||
|
),
|
||||||
'poster_key' => $posterKey,
|
'poster_key' => $posterKey,
|
||||||
'poster' => $posterShow,
|
'poster' => $posterShow,
|
||||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ $selAttendees = array_map('intval', (array)old('attendees', $curAttendees));
|
|||||||
|
|
||||||
<div class="field full">
|
<div class="field full">
|
||||||
<label for="info">简介</label>
|
<label for="info">简介</label>
|
||||||
<textarea class="input" id="info" name="info" rows="3"
|
<textarea class="input" id="info" name="info" rows="6"
|
||||||
placeholder="剧情简介(可选)"><?= e(old('info', $m['info'] ?? '')) ?></textarea>
|
placeholder="剧情简介;点「自动获取」会把导演/主演/年份/类型等主要信息一起写进来"><?= e(old('info', $m['info'] ?? '')) ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field full">
|
<div class="field full">
|
||||||
@@ -278,8 +278,27 @@ $selAttendees = array_map('intval', (array)old('attendees', $curAttendees));
|
|||||||
if (data.title) titleEl.value = data.title;
|
if (data.title) titleEl.value = data.title;
|
||||||
if (data.meta) {
|
if (data.meta) {
|
||||||
metaEl.value = JSON.stringify(data.meta);
|
metaEl.value = JSON.stringify(data.meta);
|
||||||
// 剧情简介填进「简介」,但只填空白的 —— 用户已经写了就不覆盖
|
}
|
||||||
if (infoEl && data.meta.plot && infoEl.value.trim() === '') infoEl.value = data.meta.plot;
|
/* 简介:用服务端拼好的 summary(**含导演/主演/年份/类型/片长/国家/评分 + 剧情原文**),
|
||||||
|
而不是只塞剧情那一小段。用户 2026-10-03:「从豆瓣获取的信息不够,
|
||||||
|
应该把导演、主要演员,上映年份等主要信息,也都放在简介中。」
|
||||||
|
⚠️ 仍然**只填空白的**:用户已经写过的内容不能被抓取覆盖。
|
||||||
|
⚠️ 而且要把"有东西但没写进简介"这件事说清楚 —— 以前只塞 plot,
|
||||||
|
遇到豆瓣没有 plot 的电影就**什么都不填而且没有提示**,用户会以为抓取坏了。
|
||||||
|
现在:抓到了但简介非空 → 明确提示;什么都没抓到 → 也明确说一声。 */
|
||||||
|
if (infoEl) {
|
||||||
|
var summary = (data.summary || '').trim();
|
||||||
|
if (summary !== '') {
|
||||||
|
if (infoEl.value.trim() === '') {
|
||||||
|
infoEl.value = summary;
|
||||||
|
} else {
|
||||||
|
infoEl.setAttribute('data-db-summary', summary);
|
||||||
|
alert('豆瓣信息已抓取。\n\n「简介」里已经有你写的内容,我没有覆盖它。\n'
|
||||||
|
+ '如果想用豆瓣的简介(含导演/主演/年份等),请先清空「简介」再点一次「自动获取」。');
|
||||||
|
}
|
||||||
|
} else if ((data.meta && (data.meta.plot || data.meta.director)) || data.title) {
|
||||||
|
alert('豆瓣信息已抓取(片名/海报/元数据),但这部电影豆瓣没有可用的简介文本。');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (data.poster_key) {
|
if (data.poster_key) {
|
||||||
lastPosterKey = data.poster_key;
|
lastPosterKey = data.poster_key;
|
||||||
|
|||||||
Reference in New Issue
Block a user