
本文介绍如何让“random story”按钮智能识别文本文件中真实的故事数量(而非硬编码上限),从而在每次点击时从1到当前实际总数之间安全随机选取,避免越界错误。
本文介绍如何让“random story”按钮智能识别文本文件中真实的故事数量(而非硬编码上限),从而在每次点击时从1到当前实际总数之间安全随机选取,避免越界错误。
在当前实现中,country.js 通过 countryData[country].actualTotalStories 静态配置各国故事总数(如 'usa': { file: 'usa.txt', actualTotalStories: 1000 }),而 router.php 却在服务端重复解析文件并计算真实故事数——两者存在逻辑割裂:前端依赖人工维护的“最大预估值”,后端却拥有实时准确的 count($stories)。这正是问题根源:当 usa.txt 实际仅含6个故事时,“Random Story”仍按1000生成随机数,导致99.4%概率请求无效索引(如 story=872),最终返回“故事不存在”错误。
✅ 正确解法是:完全移除前端硬编码的 actualTotalStories,交由后端动态计算并统一控制随机逻辑。前端只需发送语义清晰的请求(如 story=0 或省略 story 参数),后端负责读取文件、分割故事、统计真实数量,并在必要时生成合法随机数。
✅ 推荐重构方案(前后端协同)
1. 前端简化:移除冗余配置,统一触发逻辑
修改 country.js 中的 getRandomButton 事件监听器,不再传递 '0',而是直接省略 story 参数(更语义化):
if (getRandomButton) {
getRandomButton.addEventListener('click', function () {
fetchStory(country); // 不传 story 参数 → 后端自动随机
});
}同时调整 fetchStory 函数,使其支持无 story 参数调用:
function fetchStory(country, storyNumber) {
clearStoryContainer();
let url = 'PHP/router.php?country=' + encodeURIComponent(country);
if (storyNumber !== undefined && storyNumber !== null && storyNumber !== '') {
url += '&story=' + encodeURIComponent(storyNumber);
}
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
// ... 其余 xhr 配置保持不变
}2. 后端统一:动态计算 + 安全随机(核心修复)
重写 router.php 的随机逻辑,彻底删除前端传入的 '0' 判断,改由后端根据真实文件内容决定:
<?php
if (!isset($_GET['country'])) {
die('Invalid request.');
}
$country = $_GET['country'];
$fileMap = [
'argentina' => 'argentina.txt',
'usa' => 'usa.txt',
'sweden' => 'sweden.txt',
];
if (!isset($fileMap[$country])) {
die('The requested country does not exist.');
}
$filePath = '../../Stories/' . $fileMap[$country];
if (!file_exists($filePath)) {
die('The story file does not exist.');
}
// ✅ 动态读取并分割故事(唯一可信来源)
$rawContent = file_get_contents($filePath);
$stories = preg_split('/<!-- Unique delimiter separating stories -->/', $rawContent, -1, PREG_SPLIT_NO_EMPTY);
$actualCount = count($stories);
// ✅ 后端自主决定:若未指定 story 或 story=0,则随机;否则校验范围
$storyNumber = $_GET['story'] ?? '0';
if ($storyNumber === '0') {
$storyNumber = rand(1, $actualCount); // 真实范围:1 ~ $actualCount
} else {
$storyNumber = (int)$storyNumber;
}
// ✅ 严格边界检查(防御性编程)
if ($storyNumber < 1 || $storyNumber > $actualCount) {
die('The requested story does not exist.');
}
// 格式化输出
$story = trim($stories[$storyNumber - 1]);
echo nl2br(str_replace("\r\n", "<br>", $story));
?>? 关键改进点:
- preg_split(..., -1, PREG_SPLIT_NO_EMPTY) 确保空段落不计入计数;
- nl2br() 替代手动 str_replace,更健壮地处理换行;
- die() 替代 echo 错误,避免前端混淆成功/失败响应;
- 移除所有前端硬编码总数,彻底解除维护负担。
3. 额外优化建议
- 缓存层(可选):对大文件添加 APCu 或 Redis 缓存 $stories 数组,避免每次请求重复 IO;
- 错误监控:在 die() 前记录日志(如 error_log("Invalid story $storyNumber for $country")),便于排查数据异常;
- 前端兜底:可在 xhr.onload 中检查响应是否含 "Error:" 字样,触发更友好的 UI 提示。
通过此方案,无论 usa.txt 当前含6个还是999个故事,“Random Story”按钮始终精准命中有效范围,且无需开发者手动更新任何配置——真正实现「一次编写,永久适配」。

















