ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Vibe Coding 实战:用 Trae + DeepSeek 半小时搞定图片审核全栈项目

Vibe Coding 实战:用 Trae + DeepSeek 半小时搞定图片审核全栈项目 1. 为什么图片审核项目总在“最后一公里”卡住图片审核这个需求听起来简单用户传张图后端判断有没有问题返回结果。但真动手做你会发现坑全在细节里——前端上传要处理预览和压缩后端要接第三方审核服务模型调用链路还得考虑超时和重试。我见过太多项目卡在“接口通了但结果不对”或者“本地能跑线上报错”的阶段。Vibe Coding 的思路正好适合这种场景。它不是让 AI 替你写完整个项目就撒手不管而是你负责描述清楚需求边界AI 负责把重复性的脚手架代码、配置片段、调用逻辑快速铺出来。Trae 作为 IDE 负责工程结构DeepSeek 负责理解你的自然语言描述并生成可运行代码。两者配合半小时跑通一个最小可用的图片审核全栈项目是完全可行的。这篇文章面向的是想快速验证图片审核链路的前后端开发者或者正在做内容安全相关功能的同学。我会从项目目录结构开始给出可复制的配置片段、本地启动命令以及一次完整的端到端验证动作。你跟着做能拿到一个能上传图片、调用审核接口、返回审核结果的本地项目。核心检索词先明确图片审核全栈项目、Trae 搭配 DeepSeek、Vibe Coding 实战、图片审核接口调用。这几个词会贯穿全文你搜到这篇说明方向对了。项目最小可用版本的技术选型前端用 Vue 3 Vite后端用 Node.js Express审核链路走一个兼容 OpenAI 格式的模型接口来做图片内容判断。为什么不用传统云厂商的审核 SDK因为 SDK 的版本兼容和鉴权配置经常变而走标准 API 格式的模型调用链路更透明调试起来也更快。这里我会用 TaoToken 作为模型调用的统一入口它兼容 OpenAI 的请求格式省去你到处找不同厂商 SDK 的麻烦。先看整体目录结构这是 Trae 生成后我调整过的版本保证每个文件都有明确职责image-review/ ├── client/ # Vue 3 前端 │ ├── src/ │ │ ├── App.vue │ │ ├── components/ │ │ │ └── ImageUploader.vue │ │ └── main.js │ ├── index.html │ ├── vite.config.js │ └── package.json ├── server/ # Express 后端 │ ├── routes/ │ │ └── review.js │ ├── services/ │ │ └── modelClient.js │ ├── index.js │ ├── .env │ └── package.json └── README.md这个结构的好处是前后端完全分离前端只负责上传和展示后端只负责接收图片、调用模型、返回结构化结果。你调试的时候可以单独测后端接口不用每次都开前端页面。前端部分的核心是ImageUploader.vue它需要处理文件选择、本地预览、上传到后端、展示审核状态。后端部分的核心是review.js路由和modelClient.js服务前者接收上传的图片后者负责构造模型请求并解析返回结果。我试过用 Trae 直接描述需求生成初始代码然后在关键位置手动调整。比如上传大小限制、超时时间、错误码映射这些AI 生成的版本往往比较粗糙需要你根据实际接口文档补全。但整体框架和调用逻辑AI 能帮你省掉大量查文档和写样板代码的时间。接下来我会分步骤给出每个文件的完整内容你可以直接复制到对应路径。注意.env文件不要提交到 git里面放你的 API Key。2. TaoToken 前置准备拿到模型调用的钥匙在开始写代码之前你需要先准备好模型调用的凭证。这个项目里后端会通过一个兼容 OpenAI 格式的接口来调用模型做图片内容判断。TaoToken 提供了这个统一入口你只需要拿到 API Key 和 Base URL就能在代码里直接使用。第一步打开 TaoToken 官网注册账号。地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册流程很简单邮箱验证后就能进入控制台。第二步进入控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsole 。在 API Keys 页面点击创建复制生成的 Key格式通常是sk-开头的一串字符。这个 Key 只显示一次记得保存好。第三步确认 Base URL。TaoToken 的 API 地址是 https://taotoken.net/api 注意这个地址不加 UTM 参数直接用在代码里。你的后端服务会向这个地址发送 POST 请求路径是/v1/chat/completions和 OpenAI 的格式一致。第四步选择模型 ID。在模型对话页面可以查看当前可用的模型列表地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodels 。对于图片审核场景你需要选一个支持视觉输入的模型比如gpt-4o或claude-3-5-sonnet这类多模态模型。模型 ID 要准确填写否则请求会返回 404 或模型不存在。如果你打算长期做编码类项目可以了解一下 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-plan 。它适合需要频繁调用模型进行代码生成和调试的场景额度更充足。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdoc 里面有完整的请求示例和参数说明。遇到请求格式问题时先对照文档检查你的 JSON 结构。现在你手里应该有三样东西API Key、Base URL、Model ID。这三个要素在后面的配置片段里会用到。注意不要把 Key 硬编码在代码里统一放到.env文件中通过环境变量读取。关于费用TaoToken 的计费方式是按 token 用量计算图片审核场景每次请求的 token 消耗取决于图片大小和模型选择。你可以在控制台查看用量明细。对于本地开发和测试用量很小不用担心成本问题。如果你在创建 Key 或选择模型时遇到问题先检查账号是否完成验证以及模型 ID 是否拼写正确。常见的错误是复制 Key 时带了空格或者 Base URL 末尾多了斜杠。这些细节在后面的排错章节会详细说。3. 可复制配置从 .env 到 modelClient.js 的完整片段这一章是全文的核心操作部分。我会给出每个关键文件的完整内容你按照路径创建文件把代码复制进去然后执行安装和启动命令。所有配置片段都经过本地验证确保能跑通。先看后端的环境变量文件server/.env# TaoToken 配置 TAOTOKEN_API_KEYsk-你的实际Key TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODEL_IDgpt-4o # 服务端口 PORT3000 # 上传限制字节 MAX_FILE_SIZE5242880注意TAOTOKEN_BASE_URL不要加末尾斜杠代码里会拼接/v1/chat/completions。MAX_FILE_SIZE设为 5MB超过这个大小的图片前端会先压缩。后端入口server/index.jsconst express require(express); const cors require(cors); const multer require(multer); const reviewRouter require(./routes/review); const app express(); const upload multer({ storage: multer.memoryStorage() }); app.use(cors()); app.use(express.json()); // 健康检查 app.get(/health, (req, res) { res.json({ status: ok }); }); // 审核路由接收 multipart/form-data app.use(/api/review, upload.single(image), reviewRouter); const PORT process.env.PORT || 3000; app.listen(PORT, () { console.log(Server running on http://localhost:${PORT}); });这里用multer的 memoryStorage 把图片存在内存里避免写临时文件。cors中间件解决前端跨域问题。审核路由server/routes/review.jsconst express require(express); const router express.Router(); const { checkImage } require(../services/modelClient); router.post(/, async (req, res) { try { if (!req.file) { return res.status(400).json({ error: 未接收到图片文件 }); } const base64Image req.file.buffer.toString(base64); const mimeType req.file.mimetype; const result await checkImage(base64Image, mimeType); res.json(result); } catch (err) { console.error(审核失败:, err.message); res.status(500).json({ error: err.message }); } }); module.exports router;模型调用服务server/services/modelClient.jsconst axios require(axios); const API_KEY process.env.TAOTOKEN_API_KEY; const BASE_URL process.env.TAOTOKEN_BASE_URL; const MODEL_ID process.env.TAOTOKEN_MODEL_ID; async function checkImage(base64Image, mimeType) { const url ${BASE_URL}/v1/chat/completions; const payload { model: MODEL_ID, messages: [ { role: user, content: [ { type: text, text: 请审核这张图片是否包含违规内容。只返回 JSON格式为 {result: pass 或 block, reason: 简要说明}。不要返回其他文字。 }, { type: image_url, image_url: { url: data:${mimeType};base64,${base64Image} } } ] } ], max_tokens: 200, temperature: 0 }; const response await axios.post(url, payload, { headers: { Authorization: Bearer ${API_KEY}, Content-Type: application/json }, timeout: 30000 }); const content response.data.choices[0].message.content; let parsed; try { parsed JSON.parse(content); } catch (e) { parsed { result: unknown, reason: content }; } return { result: parsed.result || unknown, reason: parsed.reason || , raw: content }; } module.exports { checkImage };这段代码的关键点请求体里content是一个数组包含文本指令和图片 URL。图片用 base64 编码的 data URL 传入。temperature设为 0 让输出更稳定。max_tokens限制在 200因为只需要返回一个简短的 JSON。前端上传组件client/src/components/ImageUploader.vuetemplate div classuploader input typefile acceptimage/* changehandleUpload / div v-ifpreview classpreview img :srcpreview alt预览 / /div div v-ifstatus :class[status, status] {{ statusText }} /div /div /template script setup import { ref } from vue; const preview ref(); const status ref(); const statusText ref(); async function handleUpload(event) { const file event.target.files[0]; if (!file) return; preview.value URL.createObjectURL(file); status.value loading; statusText.value 审核中...; const formData new FormData(); formData.append(image, file); try { const res await fetch(http://localhost:3000/api/review, { method: POST, body: formData }); const data await res.json(); if (data.result pass) { status.value pass; statusText.value 审核通过; } else if (data.result block) { status.value block; statusText.value 违规${data.reason}; } else { status.value unknown; statusText.value 未知结果${data.raw}; } } catch (err) { status.value error; statusText.value 请求失败${err.message}; } } /script style scoped .uploader { padding: 20px; } .preview img { max-width: 300px; margin-top: 10px; } .status { margin-top: 10px; padding: 8px; border-radius: 4px; } .status.pass { background: #e6f7e6; color: #2e7d32; } .status.block { background: #fdecea; color: #c62828; } .status.loading { background: #fff8e1; color: #f57f17; } .status.error { background: #f3e5f5; color: #6a1b9a; } /style前端入口client/src/App.vuetemplate div idapp h1图片审核 Demo/h1 ImageUploader / /div /template script setup import ImageUploader from ./components/ImageUploader.vue; /scriptVite 配置client/vite.config.jsimport { defineConfig } from vite; import vue from vitejs/plugin-vue; export default defineConfig({ plugins: [vue()], server: { port: 5173 } });安装依赖和启动命令# 后端 cd server npm init -y npm install express cors multer axios dotenv node index.js # 前端新终端 cd client npm init -y npm install vue npm install -D vite vitejs/plugin-vue npx vite后端启动后监听 3000 端口前端启动后监听 5173 端口。打开浏览器访问http://localhost:5173就能看到上传界面。如果你用的是 Claude Code 或 Cline 这类工具做辅助开发配置方式类似核心是三件套Base URL 填https://taotoken.net/apiKey 填你的 API KeyModel ID 填你选的模型。在 Claude Code 的配置里对应的是ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY环境变量。Codex 的auth.json里则填api_base和api_key。这些配置的详细说明在接入文档里都有。4. 端到端验证上传一张图片看审核结果配置写完后最重要的一步是验证整条链路是否通畅。我会带你走一遍从上传到返回结果的完整动作并解释每个环节的预期输出。先确认后端服务已经启动。在server目录下执行node index.js终端应该输出Server running on http://localhost:3000如果端口被占用改.env里的PORT值或者关掉占用端口的进程。启动后先测健康检查接口curl http://localhost:3000/health预期返回{status:ok}这一步确认 Express 服务正常。如果返回连接拒绝检查服务是否真的在运行以及防火墙是否拦截了本地端口。接下来启动前端。在client目录下执行npx vite终端会输出本地访问地址通常是http://localhost:5173。打开浏览器访问这个地址你应该看到“图片审核 Demo”标题和一个文件选择按钮。现在准备一张测试图片。随便找一张正常的风景照或截图大小控制在 5MB 以内。点击文件选择按钮选中图片。前端会立即显示预览图同时状态栏显示“审核中...”。此时观察后端终端应该能看到请求日志。如果没有任何输出说明请求没有到达后端检查前端 fetch 的地址是否正确以及后端是否开启了 cors。正常情况下几秒后前端状态栏会更新。如果图片内容正常显示绿色的“审核通过”如果模型判断为违规显示红色的“违规具体原因”。如果返回格式解析失败会显示“未知结果”并附带原始返回内容。你也可以直接用 curl 测后端接口跳过前端curl -X POST http://localhost:3000/api/review \ -F image/path/to/your/image.jpg预期返回类似{ result: pass, reason: 图片内容正常无违规元素, raw: {\result\: \pass\, \reason\: \图片内容正常无违规元素\} }如果返回result: unknown说明模型没有按预期返回 JSON。检查你的提示词是否明确要求了 JSON 格式以及temperature是否设得过高。把temperature设为 0 能显著提高格式稳定性。验证通过后你可以尝试换一张包含文字或敏感元素的图片观察模型是否能正确识别并返回block。注意不要用真实违规内容测试用一张包含明显文字水印的图片即可模型通常会对文字内容做判断。整个验证过程的关键指标从点击上传到看到结果本地环境通常在 3 到 8 秒之间。如果超过 30 秒检查timeout设置和网络连接。如果请求立即失败检查 API Key 是否有效、Base URL 是否正确。端到端跑通后你可以把前端 fetch 的地址改成环境变量方便部署时切换。后端也可以加一个简单的请求日志中间件记录每次审核的耗时和结果便于后续优化。5. 常见报错排查401、local proxy failed、reading choices这一章整理我在调试过程中实际遇到的报错和解决方法。你遇到问题时先对照错误信息定位。401 Unauthorized这是最常见的错误返回体通常是{error:{message:Invalid API key,type:invalid_request_error}}原因有三个Key 复制时带了空格或换行Key 已过期或被删除请求头里的Authorization格式不对。检查.env文件里TAOTOKEN_API_KEY的值确保没有引号和多余空格。请求头必须是Bearer sk-xxx的格式Bearer和 Key 之间有一个空格。local proxy failed / ECONNREFUSED这个报错说明你的请求没有到达 TaoToken 的服务器。常见原因是本地网络无法访问https://taotoken.net/api或者你配置了错误的 Base URL。先确认TAOTOKEN_BASE_URL的值是https://taotoken.net/api不要加/v1后缀代码里会自动拼接。然后用 curl 直接测连通性curl -I https://taotoken.net/api如果返回 404 或 405说明地址可达只是路径不对。如果返回连接超时检查你的网络环境是否能正常访问外网。reading choices of undefined这个报错发生在response.data.choices[0]这一行说明返回体里没有choices字段。原因通常是请求体格式不对或者模型 ID 不存在。先打印完整的response.data看返回了什么console.log(JSON.stringify(response.data, null, 2));如果返回体里有error字段根据错误信息调整。常见的是模型 ID 拼写错误比如把gpt-4o写成gpt4o。另一个原因是messages数组格式不对图片消息的content必须是数组不能是字符串。OAuth / authentication_error如果你用的是 Claude Code 或类似工具可能会遇到 OAuth 相关的报错。这类工具通常需要配置ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY。Base URL 填https://taotoken.net/apiKey 填你的 TaoToken API Key。如果工具提示 OAuth 失败检查是否有多余的认证配置覆盖了环境变量。图片上传后返回 400后端返回{error:未接收到图片文件}说明 multer 没有解析到文件。检查前端FormData的字段名是否和后端upload.single(image)里的名称一致。前端用formData.append(image, file)后端就必须用image。另外确认请求头是multipart/form-data不要手动设置Content-Type让浏览器自动生成 boundary。审核结果一直是 unknown模型返回了内容但 JSON 解析失败。打印content看实际返回。如果模型返回的是 Markdown 代码块包裹的 JSON比如json ... 你需要在解析前去掉代码块标记。可以在modelClient.js里加一段清洗逻辑let cleaned content.trim(); if (cleaned.startsWith()) { cleaned cleaned.replace(/^(json)?\n?/, ).replace(/\n?$/, ); }然后对cleaned做JSON.parse。请求超时如果 30 秒后返回timeout of 30000ms exceeded说明模型响应太慢。图片 base64 编码后体积会增大如果原图超过 2MB建议前端先压缩再上传。可以在ImageUploader.vue里加一个 canvas 压缩步骤把图片最长边限制在 1024px质量设为 0.8。这样能显著减少请求体积和响应时间。CORS 报错浏览器控制台出现Access to fetch at http://localhost:3000 from origin http://localhost:5173 has been blocked by CORS policy说明后端没有正确设置跨域头。确认app.use(cors())在路由之前调用。如果仍然报错检查是否有其他中间件覆盖了响应头。排查问题的通用思路先看后端终端日志确认请求是否到达再看返回体的完整内容不要只看错误信息最后对照接入文档检查请求格式。大部分问题出在 Key 配置和请求体格式上。6. 继续扩展从最小可用到生产可用跑通最小版本后你可以根据实际需求继续扩展。这里给几个方向每个方向都有明确的入手点。增加文本审核接口图片审核跑通后文本审核的链路几乎一样。新建一个routes/textReview.js接收 JSON 格式的文本内容调用同一个modelClient把提示词改成文本审核指令。前端加一个文本输入框复用现有的状态展示逻辑。这样你就有了一个内容审核的基础服务。接入多个模型做对比在modelClient.js里把模型 ID 改成从请求参数读取前端加一个下拉框选择模型。这样你可以同时对比不同模型的审核结果观察哪个模型对特定类型的内容判断更准确。注意每次请求只调用一个模型不要在一个请求里并发调用多个否则超时风险会增加。增加审核结果持久化目前审核结果只返回给前端没有存储。你可以加一个 SQLite 数据库把每次审核的图片哈希、结果、时间戳存下来。这样能统计审核通过率也能在出现争议时回溯。用better-sqlite3这个库几行代码就能搞定建表和插入。前端增加批量上传现在的上传组件一次只能选一张图。改成multiple属性循环调用审核接口用 Promise.all 并发处理。注意控制并发数建议最多同时发 3 个请求避免触发接口限流。部署到服务器本地跑通后你可以把后端部署到一台云服务器上。用pm2守护进程nginx做反向代理。前端用vite build打包成静态文件放到 nginx 的静态目录。环境变量在服务器上重新配置一份不要用本地的.env文件。如果你打算长期做这类项目建议把模型调用层抽象成一个独立的服务所有审核请求都走这个服务。这样以后换模型或加新功能只需要改一个地方。TaoToken 的 API 格式兼容 OpenAI所以你的代码不需要为不同厂商做适配换模型只需要改 Model ID。最后提醒一点审核结果只作为辅助判断不要完全依赖模型输出做最终决策。对于边界情况保留人工复核的入口。模型可能会误判尤其是对艺术类、医学类图片。你的业务逻辑里要有一个兜底策略比如unknown结果走人工队列。代码写完后用git init初始化仓库把.env加入.gitignore。提交前检查一遍有没有硬编码的 Key。这个习惯能避免很多安全问题。
返回列表