
最近在做一个小型社区运营工具的时候一直想让用户之间产生更有趣的互动。后来发现一个很好玩的思路每天抛出一个“数字估算题”让所有用户用直觉给出估计值再用大模型生成一个 AI 估算值把双方的答案和真实值放在同一张卡片上对比。这种玩法既轻量又能借助 AI 制造话题点。于是我就把它整理成了一个从零到一、可直接运行的完整项目每日估算游戏大众答案对战 AI。这篇文章会围绕这个项目展开完整拆解“每日一题、用户估算、AI 估算、真实答案对比”四个核心环节。不管你是刚开始学前后端开发还是想做一个 AI 互动小应用都可以照着下面的代码一步步搭起来。最终你会得到一个能运行的小型游戏应用并且知道如何接入大模型 API、如何处理跨域、如何设计评分规则、如何做每日题目轮换。1. 背景当“大众估算”遇上 AI1.1 每日估算游戏到底是什么先来说说这个游戏的基本玩法。每天系统会抛出一个“可估算但不容易查到”的问题比如地球赤道一圈大概是多少公里一只成年大象的每分钟心跳次数大约是多少国际空间站绕地球一圈需要多少分钟全世界每天发送的电子邮件总量约为多少封用户看到题目后不查资料凭自己的经验和直觉填写一个数值。与此同时系统调用大模型让 AI 也针对同一个问题给出一个估算值。等到用户提交后后端会把“人类的答案”“AI 的答案”“真实参考值”三者放到一起比较并计算相对误差。误差更小的一方获胜。这种玩法的核心不是“谁能查到正确答案”而是“谁的估算更接近真实值”。正因为大家都没法立刻查资料所以大众估算和 AI 估算之间的对比才有悬念。从产品角度看它天然具有话题性、复玩性和社交传播潜力从技术角度看它涉及前端交互、后端 API、第三方大模型接入、日期轮换算法和误差计算是一个麻雀虽小但五脏俱全的实战项目。1.2 为什么值得做一个这样的项目这个项目很适合作为 AI 应用开发练手案例原因有三个。第一它足够小不需要复杂的微服务架构也不需要独立部署大模型。用 Node.js 写一个轻量后端配合前端页面再加上一个 OpenAI 兼容的大模型接口就能完成闭环。第二它把 AI 接入了真实的业务逻辑中而不是单纯做一个聊天机器人。你需要处理 AI 返回结果的解析、异常兜底、超时等待、模型响应格式不固定等问题这些都是实际 AI 工程中经常遇到的细节。第三它具备继续扩展的空间。比如连续多日的胜负统计、用户排名、题目贡献机制、分享海报、每日定时推送等。先做一个能跑通的最小版本后续可以按需增加功能。1.3 本文读者与学习收益这篇文章主要面向以下读者想学习如何把大模型 API 集成到 Web 应用里的前端或后端开发者。想做一个 AI 互动小游戏或社区趣味玩法的产品体验型开发者。刚接触 Node.js、Express想看一个完整前后端项目的初学者。读完这篇文章你将掌握以下内容如何设计每日一题的轮换机制保证不同日期显示不同题目。如何调用大模型接口让 AI 输出结构化的估算结果。如何用相对误差公式判断人类和 AI 谁更接近真实值。如何搭建前端页面完成题目展示、用户输入、结果对比的完整交互。如何在没有 API Key 的情况下通过 Mock 模式先把整个流程跑通。2. 系统设计与技术选型2.1 核心玩法流程我们先从用户视角梳理一遍完整流程再根据流程设计系统模块。用户打开游戏页面。前端向后端请求“今日题目”。后端根据当前日期从题库中选取一题返回给前端。用户看到题目后在输入框内填写估算值并提交。前端把用户答案发送给后端。后端内部调用大模型 API让 AI 估算同一个问题。后端根据题库中预置的真实参考值分别计算用户答案和 AI 答案的相对误差。后端返回包含所有数据的对比结果前端渲染展示。从这个流程可以看出后端承担了大部分核心逻辑题库管理、每日题目轮换、AI 调用、误差计算。前端则负责展示和交互。这样设计的优势是后续如果想增加排行榜、历史记录等功能只需要扩展后端接口前端不用大改。2.2 技术栈选择整体方案采用轻量技术栈确保读者能够快速复制运行。模块技术选型说明后端Node.js Express轻量、上手快、和前端同语言前端原生 HTML CSS JavaScript不需要构建工具复制即用AI 调用OpenAI 兼容 Chat Completions API通过环境变量配置方便切换服务商数据存储内存数组 本地常量现阶段不需要数据库便于演示这种选择的好处很明显项目依赖少目录结构清晰重点放在核心逻辑上。如果你更熟悉 Python也可以把后端替换成 FastAPI接口设计思路完全一致。2.3 项目目录结构项目采用前后端同仓库的方式组织。daily-estimation-game/ ├── package.json ├── server.js ├── .env.example └── public/ ├── index.html ├── style.css └── app.jspackage.json管理依赖和启动脚本。server.js后端主服务包含题库、每日轮换、AI 调用、评分接口。.env.example环境变量示例保存 API Key 等敏感配置。public/前端静态资源。由于前端是纯静态页面Express 可以直接把public目录作为静态资源目录无需额外配置跨域服务。这样既适合本地开发也方便部署到 Vercel、Railway 等平台。3. 环境准备与项目初始化3.1 运行环境要求在开始之前请确认本机已经安装以下软件Node.js 18 或更高版本。npm 或 yarn 任一包管理器。一个现代的浏览器推荐 Chrome 或 Edge。如果还没有安装 Node.js建议到官网下载 LTS 版本。安装完成后在终端输入node -v和npm -v可以查看版本号确认环境正常。3.2 初始化 Node.js 项目新建一个项目目录并进入该目录。mkdir daily-estimation-game cd daily-estimation-game执行 npm 初始化一路回车即可。npm init -y这一步会生成package.json文件。接着安装依赖npm install express cors dotenvexpressWeb 服务框架。cors处理跨域请求。虽然前端由后端托管时可能用不到但保留它可以方便前后端分离部署。dotenv加载.env文件中的环境变量。安装完成之后我们把启动脚本写入package.json。{ name: daily-estimation-game, version: 1.0.0, description: 每日估算游戏大众答案对战 AI, main: server.js, scripts: { start: node server.js, dev: node --watch server.js }, dependencies: { cors: ^2.8.5, dotenv: ^16.4.5, express: ^4.19.2 } }dev脚本中的node --watch是 Node.js 18.11 之后支持的监听模式修改代码后会自动重启服务适合本地开发。3.3 准备环境变量文件在项目根目录创建.env文件用于存放 API 相关配置。# .env PORT3000 AI_API_URLhttps://api.openai.com/v1/chat/completions AI_MODELgpt-4o-mini AI_MOCKtruePORT服务启动端口。AI_API_URL大模型接口地址。如果你使用的是国内大模型服务商或 OpenAI 兼容网关这里替换成对应地址即可。AI_MODEL模型名称需要根据你实际使用的服务商调整。AI_MOCK是否开启 Mock 模式。先设置为true在没有 API Key 的情况下也能跑通整个流程。为了安全不要把真实 API Key 写死在代码里。可以在.env中继续添加一行AI_API_KEYsk-xxxxxxxxxxxxxxxx同时在项目根目录创建一个.gitignore避免把密钥提交到代码仓库。node_modules/ .env4. 后端核心实现4.1 题库设计与数据模型题库是整个游戏的内容基础。每一道题需要包含以下字段id题目唯一编号。question题目的完整描述。unit答案的单位。answer真实参考值。source参考值来源或说明。在设计题目时有一个关键点问题必须适合“估算”也就是说普通人可以通过生活常识或逻辑推导给出一个量级正确的数字但很难精确猜到。比如问“地球赤道一圈有多长”很多人知道大约四万公里但不知道精确到百公里的数值。这类题目既能体现直觉判断力又能让 AI 和人类处在相对公平的起跑线上。下面给出一个初始题库const questions [ { id: 1, question: 地球赤道一圈大约是多少公里, unit: 公里, answer: 40075, source: NASA 地球数据 }, { id: 2, question: 一只成年大象平均每分钟心跳大约多少次, unit: 次/分钟, answer: 30, source: 动物生理学参考值 }, { id: 3, question: 国际空间站绕地球飞行一圈大约需要多少分钟, unit: 分钟, answer: 93, source: NASA 国际空间站轨道数据 }, { id: 4, question: 太阳表面温度大约是多少摄氏度, unit: 摄氏度, answer: 5500, source: 太阳物理学参考值 }, { id: 5, question: 正常成年人全身大约有多少块骨骼, unit: 块, answer: 206, source: 人体解剖学参考值 } ];这个题库是示例你可以根据实际情况增加更多题目。需要注意的是题目答案应该是相对稳定、有公共参考值的数字避免使用容易引起争议的问题。4.2 每日一题轮换算法每日一题的核心需求是不同日期访问时显示不同题目同一天内所有用户看到同一道题。最简单的实现方式是“根据日期字符串生成一个确定性索引”。function getTodayIndex(length) { const today new Date().toISOString().slice(0, 10); const dateNum today.split(-).map(Number).reduce((acc, cur) acc * 31 cur, 0); return dateNum % length; }这里有两个细节需要说明。第一toISOString()返回的是 UTC 时间。如果项目要面向国内用户更推荐使用本地时间格式避免出现“北京时间已经换题UTC 日期还没换”的问题。可以改为function getTodayString() { const now new Date(); const year now.getFullYear(); const month String(now.getMonth() 1).padStart(2, 0); const day String(now.getDate()).padStart(2, 0); return ${year}-${month}-${day}; }第二取模运算会带来一个现象当题库数量不能被题目数量整除时某些题目的出现频率会略高。对于每日一题这种场景影响不大可以接受。但如果未来变成“每小时一题”建议使用更均匀的伪随机算法。4.3 调用大模型生成 AI 估算值AI 估算功能是本项目接入 AI 能力的核心环节。这里采用 OpenAI 兼容格式的 Chat Completions 接口通过fetch发起请求。在 Node.js 18 及以上版本中fetch是全局方法不需要额外安装依赖。以下是核心函数async function getAIEstimate(question, unit) { if (process.env.AI_MOCK true) { const mockSeed question.id * 7 % 13 1; const mockValue Math.max(1, Math.round(question.answer / mockSeed * 100) / 100); return { value: mockValue, raw: String(mockValue), mock: true }; } const apiUrl process.env.AI_API_URL; const apiKey process.env.AI_API_KEY || ; const model process.env.AI_MODEL; const messages [ { role: system, content: 你是一个擅长估算的助手。面对无法精确查证的问题时你只能凭知识给出一个最合理的数字估计。你必须只输出一个纯数字不要输出任何解释、单位或标点。 }, { role: user, content: 请估算${question.question}答案单位是「${unit}」。只输出一个数字。 } ]; const response await fetch(apiUrl, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey} }, body: JSON.stringify({ model, temperature: 0.7, messages }) }); if (!response.ok) { const errorText await response.text(); throw new Error(AI API request failed: ${response.status} ${errorText}); } const data await response.json(); const content data.choices data.choices[0] data.choices[0].message ? data.choices[0].message.content : ; const numericValue extractNumericValue(content); if (numericValue null) { throw new Error(AI response is not a valid number: ${content}); } return { value: numericValue, raw: content.trim(), mock: false }; } function extractNumericValue(text) { const match String(text || ).trim().match(/-?\d(\.\d)?/); return match ? parseFloat(match[0]) : null; }这里有几个需要注意的地方。首先Mock 模式的设计非常实用。在没有 API Key、或者不想消耗真实调用量的情况下可以通过设置AI_MOCKtrue让接口返回一个基于题目 id 的伪随机值。这样整个前后端流程依然可以完整跑通方便调试页面样式和交互逻辑。其次大模型的输出不一定规范。你可能得到“大约是 12345”、“12345 公里”甚至一段解释文字。因此必须通过extractNumericValue从输出文本中提取第一个数字。这是实际 AI 工程中非常常见的容错处理。再次调用外部 API 需要关注超时。fetch默认没有超时时间如果模型服务响应很慢用户会一直等待。可以使用AbortController设置一个合理的超时时间比如 15 秒。const controller new AbortController(); const timeout setTimeout(() controller.abort(), 15000); try { const response await fetch(apiUrl, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey} }, body: JSON.stringify({ model, temperature: 0.7, messages }), signal: controller.signal }); clearTimeout(timeout); } catch (error) { clearTimeout(timeout); throw error; }4.4 评分逻辑与接口设计评分是游戏的核心判断规则。我们使用相对误差来衡量“猜测值”和“真实值”之间的差距。相对误差公式如下相对误差 绝对值(猜测值 - 真实值) / 真实值 × 100%误差越小说明估算越准确。人类和 AI 分别计算相对误差之后比较大小即可得出胜负。后端提供两个接口GET /api/question返回今日题目不包含真实答案。POST /api/guess接收用户估算值返回 AI 估算值、真实答案、双方误差和胜负结果。为什么GET /api/question不直接返回真实答案因为前端在用户提交之前不能展示答案否则游戏就没有悬念了。真实答案只应该在后端计算出对比结果之后返回。后端主服务代码如下const path require(path); require(dotenv).config(); const express require(express); const cors require(cors); const app express(); const PORT process.env.PORT || 3000; app.use(cors()); app.use(express.json()); app.use(express.static(path.join(__dirname, public))); const questions [ ... ]; // 前面定义的题库 function getTodayString() { const now new Date(); const year now.getFullYear(); const month String(now.getMonth() 1).padStart(2, 0); const day String(now.getDate()).padStart(2, 0); return ${year}-${month}-${day}; } function getTodayQuestion() { const todayStr getTodayString(); const dateNum todayStr.split(-).map(Number).reduce((acc, cur) acc * 31 cur, 0); const index dateNum % questions.length; return questions[index]; } app.get(/api/question, (req, res) { const question getTodayQuestion(); res.json({ id: question.id, question: question.question, unit: question.unit }); }); app.post(/api/guess, async (req, res) { try { const userGuess Number(req.body.guess); if (!userGuess || userGuess 0) { return res.status(400).json({ error: 请输入一个大于 0 的数字。 }); } const question getTodayQuestion(); const aiResult await getAIEstimate(question, question.unit); const userError Math.abs(userGuess - question.answer) / question.answer * 100; const aiError Math.abs(aiResult.value - question.answer) / question.answer * 100; let winner draw; if (userError aiError) winner human; if (aiError userError) winner ai; res.json({ question: question.question, unit: question.unit, userGuess, userError: Math.round(userError * 100) / 100, aiGuess: aiResult.value, aiRaw: aiResult.raw, aiError: Math.round(aiError * 100) / 100, answer: question.answer, winner }); } catch (error) { console.error(guess error:, error); res.status(500).json({ error: AI 估算服务暂时不可用请稍后再试。 }); } }); app.listen(PORT, () { console.log(Daily estimation game server is running at http://localhost:${PORT}); });在POST /api/guess中先解析用户输入再获取今日题目然后调用 AI。这里有一个值得思考的顺序问题是否应该先调用 AI 再校验用户输入从接口设计角度建议先校验用户输入再调用外部 AI 接口。这样可以避免无效请求白白消耗第三方 API 的调用额度。同样如果题库中真实答案没有配置好也应该提前拦截。用户输入的校验不能只用if (!userGuess)。因为Number(abc)会返回NaN而Number(0)返回0。所以更稳妥的写法是const userGuess Number(req.body.guess); if (!Number.isFinite(userGuess) || userGuess 0) { return res.status(400).json({ error: 请输入一个大于 0 的数字。 }); }5. 前端页面实现5.1 页面结构与交互逻辑前端采用原生 HTML、CSS、JavaScript 构建。页面整体分为两个状态答题状态展示今日题目和输入框。结果状态展示用户估算、AI 估算、真实答案和胜负结果。先看public/index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title每日估算游戏大众答案对战 AI/title link relstylesheet hrefstyle.css /head body div classcontainer header h1每日估算游戏/h1 p大众答案对战 AI看看谁离真相更近。/p /header section classquestion-card idquestionCard div classquestion-meta今日题目/div div classquestion-text idquestionText加载中.../div div classquestion-unit idquestionUnit/div input typenumber idguessInput placeholder输入你的估算值 min0 stepany button idsubmitBtn提交估算/button div classerror-text iderrorText/div /section section classresult-card idresultCard styledisplay: none; div classresult-title idresultTitle/div div classresult-grid div classresult-item human span classlabel我的估算/span span classvalue iduserGuessText/span span classerror iduserErrorText/span /div div classresult-item ai span classlabelAI 估算/span span classvalue idaiGuessText/span span classerror idaiErrorText/span /div /div div classanswer-line 真实参考值span idanswerText/span /div button idresetBtn再来一次/button /section /div script srcapp.js/script /body /html对应的交互逻辑在public/app.js中实现。const questionText document.getElementById(questionText); const questionUnit document.getElementById(questionUnit); const guessInput document.getElementById(guessInput); const submitBtn document.getElementById(submitBtn); const errorText document.getElementById(errorText); const questionCard document.getElementById(questionCard); const resultCard document.getElementById(resultCard); const resultTitle document.getElementById(resultTitle); const userGuessText document.getElementById(userGuessText); const userErrorText document.getElementById(userErrorText); const aiGuessText document.getElementById(aiGuessText); const aiErrorText document.getElementById(aiErrorText); const answerText document.getElementById(answerText); const resetBtn document.getElementById(resetBtn); async function loadQuestion() { try { const res await fetch(/api/question); const data await res.json(); questionText.textContent data.question; questionUnit.textContent 单位${data.unit}; } catch (error) { errorText.textContent 题目加载失败请刷新页面重试。; } } async function submitGuess() { const guess guessInput.value.trim(); errorText.textContent ; if (!guess) { errorText.textContent 请先输入你的估算值。; return; } submitBtn.disabled true; submitBtn.textContent AI 思考中...; try { const res await fetch(/api/guess, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ guess: Number(guess) }) }); const data await res.json(); if (!res.ok) { errorText.textContent data.error || 提交失败请重试。; return; } questionCard.style.display none; resultCard.style.display block; userGuessText.textContent data.userGuess (data.unit ? ${data.unit} : ); userErrorText.textContent 误差 ${data.userError}%; aiGuessText.textContent data.aiGuess (data.unit ? ${data.unit} : ); aiErrorText.textContent 误差 ${data.aiError}%; answerText.textContent data.answer (data.unit ? ${data.unit} : ); if (data.winner human) { resultTitle.textContent 你赢了 ; } else if (data.winner ai) { resultTitle.textContent AI 赢了 ; } else { resultTitle.textContent 平局; } } catch (error) { errorText.textContent 请求失败请检查服务是否启动。; } finally { submitBtn.disabled false; submitBtn.textContent 提交估算; } } function resetGame() { resultCard.style.display none; questionCard.style.display block; guessInput.value ; loadQuestion(); } submitBtn.addEventListener(click, submitGuess); resetBtn.addEventListener(click, resetGame); loadQuestion();这里有一个细节在提交过程中按钮被禁用并显示“AI 思考中...”这样做可以防止用户重复提交。因为 AI 接口存在延迟如果用户连续点击多次会造成重复调用浪费 API 额度。5.2 结果展示与文案设计在结果展示页面用户最关心三个信息我猜了多少、AI 猜了多少、真实值是多少。因此页面采用左右两栏对比的形式把人类和 AI 的估算值放在同等位置再在下方单独展示真实参考值。这种设计能够强化“对战感”。当两个数值并排出现时用户会不自觉地和自己的直觉比较进而产生分享和讨论的欲望。如果未来要在社区中传播可以考虑把结果卡片做成图片方便用户截图分享。前端页面还需要考虑极端情况。比如 AI 返回了一个特别离谱的数字或者用户输入了极大值都应该在 UI 上正常展示不出现布局崩溃。CSS 中建议给数值区域设置word-break: break-all避免长数字撑破卡片。6. 完整运行验证6.1 启动服务在项目根目录执行npm start如果一切正常终端会输出Daily estimation game server is running at http://localhost:3000打开浏览器访问http://localhost:3000即可看到游戏页面。6.2 请求示例与效果说明在 Mock 模式下AI 不会发起真实请求而是根据题目 id 生成一个估算值。这样适合快速验证整个前端交互和后端逻辑。以下是正常请求的流程。浏览器请求GET /api/question返回今日题目{ id: 1, question: 地球赤道一圈大约是多少公里, unit: 公里 }用户输入 40000 并提交后端请求 AI 后返回{ question: 地球赤道一圈大约是多少公里, unit: 公里, userGuess: 40000, userError: 0.19, aiGuess: 5712, aiRaw: 5712, aiError: 85.75, answer: 40075, winner: human }从数据可以看出用户猜 40000 公里相对误差只有 0.19%非常接近真实值。AI 在 Mock 模式下给出的估值偏离较大。这说明正确配置 AI 后AI 的表现会更接近真实水平。6.3 真实调用大模型的效果当AI_MOCKfalse并配置好AI_API_KEY后后端会真实调用大模型接口。以 GPT 系列模型为例你会看到 AI 给出的估算值往往具备一定的知识基础。比如对于“地球赤道一圈大约是多少公里”AI 可能估算为 40075 公里。对于“太阳表面温度大约是多少摄氏度”AI 可能估算为 5500 摄氏度。对于“正常成年人全身大约有多少块骨骼”AI 可能估算为 206 块。这些结果已经接近参考值因此人类想赢过 AI 并不轻松。这也是游戏有意思的地方AI 掌握大量世界知识而人类靠生活经验和直觉双方各有优势。7. 常见问题与排查思路以下是项目开发和运行中比较常见的问题按照“现象 - 原因 - 解决方式”的方式进行说明。问题现象常见原因解决思路访问页面时题目加载失败后端服务未启动或静态资源路径错误确认npm start已执行检查public目录是否和server.js同级提交估算后一直停留在“AI 思考中”大模型 API 响应过慢或网络不通先开启AI_MOCKtrue验证流程设置 fetch 超时时间AI 返回内容无法解析为数字模型输出包含解释性文字或返回空内容使用正则提取第一个数字增加解析失败兜底逻辑真实答案在提交前被泄露前端直接请求了包含答案的接口确认答案只在POST /api/guess中返回题目没有按日期切换后端日期使用 UTC和本地时区不一致改用本地日期字符串生成今日索引API Key 泄露到代码仓库.env没有被 git 忽略检查.gitignore确认.env在忽略列表中同一题在题库中占用不平均题库数量和取模结果不对齐增加题库数量或使用更均匀的轮换算法用户输入字符串导致服务报错前端只做了简单非空校验后端使用Number.isFinite(userGuess)做二次校验7.1 大模型输出不稳定的处理大模型在实际使用中可能输出各种格式这是 AI 应用开发中比较常见的挑战。一个更稳妥的做法是使用“两轮验证”第一轮让模型输出估算值第二轮让模型把结果格式化为 JSON。但这会增加一次 API 调用实际项目中需要根据成本和稳定性做权衡。另一个思路是在 prompt 里给出更严格的要求。示例 prompt 已经要求“只输出一个数字”但模型仍然可能不遵循。因此代码层面的解析兜底是必不可少的。7.2 每日一题缓存问题如果游戏访问量较大每次请求都实时计算“今日题目”是没有问题的因为算法复杂度极低。但如果后续题库规模增大、题目附带图片等内容可以考虑在服务启动时预计算今日题目并缓存到变量中直到日期发生变化。以下是一个简单的缓存实现思路let cachedQuestion null; let cachedDate null; function getCachedTodayQuestion() { const today getTodayString(); if (cachedQuestion cachedDate today) { return cachedQuestion; } const question getTodayQuestion(); cachedQuestion question; cachedDate today; return question; }这样可以避免每次请求都重新做日期解析和取模计算也能为将来引入“题目锁定”机制提供基础。8. 最佳实践与工程建议8.1 把 API Key 放在后端这个项目虽然简单但 API Key 的安全边界必须从第一天就保持正确。前端代码是公开的任何放在前端代码或本地存储中的密钥都会被用户看到。因此所有涉及大模型 API 的调用都必须放在后端由后端统一持有和处理密钥。如果你打算把项目部署到公网建议加上简单的访问频率限制防止接口被恶意刷量。Express 生态中有express-rate-limit这类中间件可以很容易地为接口添加限流。8.2 题库数据与代码逻辑分离目前题库直接写在server.js中优点是简单直接。但题目的更新频率通常比代码更新频率高如果每次加题都要重新部署代码就不够灵活。更合理的做法是将题库放到独立的 JSON 文件或数据库中让运营人员可以通过后台管理界面增删题目。如果你不想引入数据库也可以先把题库抽取到questions.json文件然后在server.js中加载const questions require(./questions.json);这样以后加题只需要修改 JSON 文件不需要改动代码逻辑。8.3 日志记录与异常监控在实际项目中AI 接口调用属于不稳定环节。建议给关键节点增加日志输出尤其是发起 AI 请求的模型名和请求时间。AI 返回的原始内容。AI 解析成功或失败的结果。用户输入值、AI 估算值和误差结果。这些日志能帮助你快速定位问题。在本地开发时输出到控制台即可生产环境可以集成到日志平台。8.4 用户输入边界与安全尽管这只是一个轻量游戏仍然需要对用户输入做边界防护。前端限制输入框类型为 number后端再次做数值校验。如果后续增加用户名、留言等功能还需要考虑 XSS 过滤和参数长度限制。8.5 前端体验优化AI 接口通常需要几秒甚至更长的响应时间因此提交按钮的“加载态”必不可少。此外可以在等待过程中加入一句提示例如“AI 正在调动知识库...”让用户感知到系统正在工作而不是卡死了。如果后续用户量增加可以考虑把“历史记录”“每日胜负统计”等数据存入数据库。现阶段使用 localStorage 保存本地战绩即可不需要引入服务端数据库。9. 总结与扩展方向至此一个完整的“每日估算游戏大众答案对战 AI”已经搭建完成。你做了一个轻量后端它包含每日题目轮换算法、大模型接口调用、结果解析容错和相对误差评分又做了一个前端页面能够展示题目、提交估算、对比结果。整个项目代码量不大但涵盖了 AI 应用开发中较有代表性的几个环节接入第三方模型、处理模型输出、设计业务规则、保证前后端数据闭环。如果接下来继续深入可以从这几个方向迭代。第一增加连续多日的战绩统计。利用 localStorage 保存每一局的胜负和误差展示用户的 7 日胜率趋势。第二开放用户提交题目的入口。把优质用户题目经过审核后加入题库解决题库扩充问题。第三接入排行榜和分享功能。每天生成一张结果海报用户可以通过社交媒体分享自己的“大众估算”成绩增强传播效果。第四把后端迁移到 TypeScript 或 Python FastAPI增加单元测试和接口文档提升工程化程度。第五接入更多大模型服务商。目前项目兼容 OpenAI 的 Chat Completions 格式OpenAI 兼容网关、国内大模型服务商通常也支持这种格式改一下环境变量即可切换。这个项目的核心价值不在代码量而在于它把“人机对比”的玩法完整落地了。AI 不再只是一个聊天框而是变成了游戏中的一个对手。顺着这个思路你还可以设计出更多有趣的 AI 互动玩法比如“AI 猜价格”“AI 猜年份”“AI 猜距离”等。只要把自己擅长的业务场景和 AI 能力结合起来就能做出有真实用户价值的应用。