news 2026/10/7 13:07:42

Next.js + LangGraph.js 构建可落地的AI Agent简历工作流

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Next.js + LangGraph.js 构建可落地的AI Agent简历工作流

1. 这不是“又一个AI简历生成器”,而是可落地的Agent工作流闭环

我去年帮三位朋友做过简历优化,每次都要花3小时:先看JD,再翻他们过往项目,接着调格式、改动词、删冗余,最后还得反复核对技术栈匹配度。直到今年初,我把整个流程塞进一个Next.js页面里——用户上传PDF,输入目标岗位,点击“生成”,5秒后返回结构化分析+三版差异化文案+适配HR系统的纯文本版本。没有弹窗、不跳转、不调外部API,所有逻辑跑在本地浏览器里。关键在于,它背后不是调用大模型API的简单封装,而是一个用LangGraph.js编排的真实Agent工作流:能自主判断“这个Java项目要不要提Spring Boot”、能主动追问“你在这个项目里具体负责哪块模块”、甚至会在生成后自动检查“是否遗漏了JD中强调的Kubernetes关键词”。这和市面上90%的“AI简历工具”有本质区别:它们是静态prompt的复读机,而这是具备状态记忆、条件分支、工具调用能力的轻量级Agent。核心关键词就三个:Next.js做前端容器,LangGraph.js做决策中枢,简历工具是垂直场景切口。适合两类人:想快速验证AI Agent落地可行性的前端/全栈开发者,以及需要把AI真正嵌入业务流程的产品经理。它不教你怎么从零造轮子,只告诉你,在2024年,用现有工具链把Agent跑通到生产环境,到底要踩哪些坑、绕哪些弯、卡在哪个环节。

2. 为什么非得用LangGraph.js?而不是LangChain或手写状态机

很多人看到“AI Agent”第一反应是LangChain,但当我真正把简历分析流程拆解到原子级时,发现LangChain的默认链式调用根本撑不住。举个真实例子:当用户上传一份含12个项目经历的PDF,Agent需要先做粗筛(剔除实习期<3个月的条目),再对剩余项目做技术栈提取(识别出Spring Boot、React、Docker等关键词),然后根据目标JD中的“要求熟悉CI/CD流程”这一条,反向检索每个项目里是否有Jenkins/GitLab CI相关描述。这个过程涉及条件判断→并行处理→结果聚合→二次决策四个阶段,如果用LangChain的SequentialChain硬套,代码会变成这样:

const chain = new SequentialChain({ chains: [ new LLMChain({ llm, prompt: extractProjectsPrompt }), new LLMChain({ llm, prompt: filterShortInternshipsPrompt }), new LLMChain({ llm, prompt: extractTechStackPrompt }), new LLMChain({ llm, prompt: matchCIPrompt }), // 这里要传入前几步的所有输出 ], inputVariables: ["pdfText", "jobDescription"], });

问题立刻暴露:第四步的matchCIPrompt需要前三步的全部中间结果,但SequentialChain只传递上一步的输出,要么把所有数据塞进单个字符串(导致token爆炸),要么自己手动拼接上下文(违背链式设计初衷)。更致命的是,当用户中途修改JD描述,整个链必须重跑,无法复用已计算的项目筛选结果。

LangGraph.js的解法是把Agent当成一个有状态的有限状态机。我定义了五个节点:parsePDF、filterProjects、extractTech、matchRequirements、generateDrafts,每个节点输出结构化数据(如filterProjects返回{ validProjects: [...], removedCount: 2 }),节点间通过边(edge)连接,边的条件由函数决定:

const workflow = new StateGraph({ nodes: { parsePDF: (state) => ({ ...state, projects: parsePDF(state.pdfText) }), filterProjects: (state) => { const valid = state.projects.filter(p => p.durationMonths > 3); return { ...state, validProjects: valid, removedCount: state.projects.length - valid.length }; }, extractTech: (state) => ({ ...state, techStack: extractTechFromProjects(state.validProjects) }), matchRequirements: (state) => { const matched = state.jobDescription.requirements.map(req => state.techStack.some(t => t.toLowerCase().includes(req.toLowerCase())) ); return { ...state, requirementMatch: matched }; }, generateDrafts: (state) => ({ ...state, drafts: generateThreeVersions(state) }) }, edges: [ { from: 'parsePDF', to: 'filterProjects' }, { from: 'filterProjects', to: 'extractTech' }, { from: 'extractTech', to: 'matchRequirements' }, { from: 'matchRequirements', to: 'generateDrafts' } ] });

提示:LangGraph.js的边(edge)不是简单的线性连接,而是可编程的路由逻辑。比如当matchRequirements发现JD中“要求掌握AWS”但用户简历完全没提时,可以动态插入askForCloudExperience节点,向用户发起追问——这种条件分支能力,是LangChain原生链式调用无法实现的。

我实测过三种方案的响应时间:纯LangChain SequentialChain平均耗时8.2秒(含3次LLM调用),手写状态机约5.7秒(需自行管理状态和错误重试),而LangGraph.js工作流稳定在4.3秒。差距看似不大,但体现在用户体验上就是“等待感”和“即时感”的分水岭。更重要的是,LangGraph.js的节点可独立测试——我能单独给extractTech节点喂入模拟项目文本,验证它是否准确识别出“Docker Compose”而非误判为“Docker Swarm”,这种可测试性让调试效率提升3倍以上。

3. Next.js的边界在哪里?为什么不能把Agent全塞进服务端

很多团队一上来就想把Agent部署成独立服务,用FastAPI或Express暴露REST接口,前端只负责调用。我在早期原型中也这么干过,结果遇到三个无法回避的痛点:首屏加载慢、错误定位难、成本失控。

先说首屏加载。当用户打开/resume页面,传统方案需要先加载Next.js前端框架,再发起HTTP请求到Agent服务,服务端再调用LLM API,最后把结果返回。整个链路至少经过三次网络往返(浏览器→Agent服务→LLM→Agent服务→浏览器),在弱网环境下,用户看到空白页的时间超过6秒。而我的方案是把LangGraph.js工作流直接运行在Next.js的App Router中,利用React Server Components(RSC)的流式渲染能力:

// app/resume/page.tsx export default async function ResumePage() { // 在服务端预执行Agent的初始化逻辑 const graph = await createResumeGraph(); // 加载配置、初始化LLM客户端 return ( <div> <ResumeUploader /> <Suspense fallback={<LoadingSpinner />}> <ResumeResult graph={graph} /> </Suspense> </div> ); } // app/resume/components/ResumeResult.tsx async function ResumeResult({ graph }: { graph: ResumeGraph }) { // 这里触发Agent工作流,但结果通过RSC流式返回 const result = await graph.invoke({ pdfText, jobDescription }); return ( <div> <h2>匹配度分析</h2> <ProgressRing value={result.matchScore} /> <DraftList drafts={result.drafts} /> </div> ); }

关键点在于graph.invoke()的调用发生在服务端,但Next.js的RSC会把渲染结果分块传输:先返回骨架HTML(含进度条),再流式注入匹配度分数,最后推送三版文案。用户感知到的是“页面秒开,内容渐进式出现”,而非“白屏等待”。

第二个痛点是错误定位。当Agent服务报错“LLM token超限”,你得查服务日志→定位到具体节点→回溯输入数据→复现问题。而RSC模式下,错误直接抛在Next.js服务端,配合Vercel的实时日志,我能精准看到是matchRequirements节点处理某份含27个技术名词的简历时触发了token限制。解决方案也更直接:在该节点增加预处理,把技术名词列表截断到前15个(经测试,覆盖95%的JD匹配需求),而不是在服务端全局调低max_tokens。

第三个痛点是成本。按调用量计费的LLM API,如果Agent服务被恶意刷请求,账单会指数级飙升。而Next.js RSC天然带请求节流——Vercel平台自动限制同一IP每分钟最多10次/resume页面访问,且RSC的流式响应会中断长时间未完成的请求。我上线首周监控发现,异常请求集中在凌晨3-5点,全部被Vercel的速率限制拦截,没产生一分钱LLM费用。

注意:这不是鼓吹“所有Agent都该跑在前端”。当你的Agent需要调用数据库、执行Python脚本或处理GB级文件时,服务端仍是唯一选择。但简历工具这类轻量级场景,RSC+LangGraph.js的组合,把复杂度从“运维一个微服务”降维到“维护一个Next.js页面”。

4. 简历工具的Agent工作流设计:从PDF解析到文案生成的七步闭环

把“AI简历优化”拆解成Agent工作流,绝不是简单地把“写简历”这个动作交给大模型。真正的难点在于如何让Agent理解简历的隐含语义,并与JD建立动态映射关系。我最终确定的七步闭环,每一步都对应一个可验证的节点:

4.1 PDF解析:不用Puppeteer,用PDF.js的Web Worker方案

市面上90%的简历工具用pdf-lib或服务端解析,但用户上传PDF后,前端要等服务端返回解析结果,体验割裂。我的方案是用PDF.js在浏览器端解析,但关键细节在于避免阻塞主线程:

// utils/pdfParser.ts export async function parsePDFInWorker(pdfBytes: Uint8Array): Promise<string> { // 创建Web Worker隔离解析任务 const worker = new Worker(new URL('./pdfWorker.ts', import.meta.url)); return new Promise((resolve, reject) => { worker.postMessage({ pdfBytes }); worker.onmessage = (e) => { if (e.data.type === 'success') resolve(e.data.text); if (e.data.type === 'error') reject(e.data.error); }; }); } // pdfWorker.ts importScripts('https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.min.js'); self.onmessage = async (e) => { try { const loadingTask = pdfjsLib.getDocument(e.data.pdfBytes); const pdf = await loadingTask.promise; let fullText = ''; for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const textContent = await page.getTextContent(); fullText += textContent.items.map((item: any) => item.str).join(' '); } self.postMessage({ type: 'success', text: fullText }); } catch (err) { self.postMessage({ type: 'error', error: err.message }); } };

实测对比:主线程解析10MB PDF平均卡顿4.2秒,Web Worker方案全程无卡顿,解析耗时仅2.8秒。更重要的是,它规避了服务端PDF解析的合规风险——用户简历数据永不离开浏览器。

4.2 项目经历提取:用正则+LLM双校验,拒绝幻觉

纯LLM提取项目经历容易出错,比如把“参与XX系统开发”误判为独立项目。我的方案是规则先行,LLM兜底:

  1. 先用正则匹配常见项目标题模式:/^\d{4}-\d{4}\s+.*?(?:项目|系统|平台)/mi
  2. 对匹配到的段落,用LLM做二次确认:“以下文本是否描述一个独立项目?请只回答是/否:[文本]”
  3. 对LLM返回“否”的段落,人工规则再校验:是否包含“协助”、“参与”、“支持”等弱主语动词

这样做的准确率从纯LLM的73%提升到92%。关键参数是LLM的temperature设为0.1(抑制创造性),且提示词强制要求“只回答是/否,不要解释”。

4.3 技术栈标准化:构建领域词典,解决大小写/缩写歧义

简历里“k8s”、“Kubernetes”、“kubernetes”、“K8S”都指向同一技术,但LLM可能当成不同实体。我维护了一个JSON格式的领域词典:

{ "kubernetes": ["k8s", "K8S", "kubernetes", "Kubernetes"], "react": ["React", "react.js", "ReactJS", "reactjs"], "docker": ["Docker", "docker-compose", "Docker Compose"] }

在extractTech节点中,先对原始文本做小写归一化,再遍历词典进行模糊匹配(Levenshtein距离≤2)。实测发现,未经标准化的匹配准确率仅61%,加入词典后达89%。

4.4 JD需求映射:动态权重分配,而非简单关键词匹配

JD里“熟悉Spring Boot”和“精通Java虚拟机原理”权重不同。我的方案是让Agent学习JD的句式强度:

  • “熟悉/了解/接触过” → 权重0.3
  • “掌握/熟练使用/具备...经验” → 权重0.7
  • “精通/深入理解/主导过...架构设计” → 权重1.0

Agent在matchRequirements节点中,先用LLM识别JD中每个需求的强度等级,再结合用户简历中的技术出现频次(如“Spring Boot”在3个项目中出现 vs 仅1次),计算加权匹配分。这比单纯统计关键词出现次数,更能反映真实匹配度。

4.5 文案生成:三版本策略,覆盖不同投递场景

生成的不是“一份简历”,而是:

  • 版本A(技术导向):突出技术深度,用STAR法则重构项目描述,动词全部替换为“设计/实现/优化/重构”
  • 版本B(业务导向):强调商业价值,把“使用Redis缓存”改为“将订单查询响应时间从1200ms降至200ms,支撑日均50万订单”
  • 版本C(应届生友好):弱化技术细节,强化学习能力,增加“通过自学掌握XX技术并应用于XX项目”

每个版本用不同system prompt驱动,且生成后自动执行事实核查:抽取文案中的技术名词,反向验证是否存在于简历原始文本中,杜绝幻觉。

4.6 格式适配:针对ATS系统的HTML-to-Plain-Text转换

HR系统(ATS)对简历格式极其敏感。我的方案不是生成Word文档,而是生成语义化HTML,再用定制化转换器输出纯文本:

<!-- 生成的HTML --> <section class="experience"> <h3 class="job-title">高级前端工程师</h3> <p class="company">XX科技有限公司 <time>2022.03-2024.06</time></p> <ul class="responsibilities"> <li>主导React组件库重构,降低Bundle体积35%</li> <li>设计WebSocket实时通知方案,消息延迟<100ms</li> </ul> </section>

转换器会忽略所有class名,只保留语义标签的层级关系,输出:

工作经验 高级前端工程师 XX科技有限公司 2022.03-2024.06 • 主导React组件库重构,降低Bundle体积35% • 设计WebSocket实时通知方案,消息延迟<100ms

实测ATS通过率从纯Markdown生成的62%提升至89%。

4.7 用户反馈闭环:用隐式信号替代显式评分

传统工具让用户打1-5星,回收率不足15%。我的方案是捕获行为信号:

  • 用户复制文案后立即关闭页面 → 认定为“满意”
  • 用户修改文案超过3处再下载 → 认定为“部分满意”
  • 用户反复切换三个版本 → 认定为“需求未满足”

这些信号实时上报,用于优化generateDrafts节点的prompt权重。上线两周后,版本A的生成占比从45%降至32%,版本B升至51%,证明用户更倾向业务导向文案。

5. 并发瓶颈在哪?LangGraph.js的并发模型与Next.js的应对策略

网络热词里“ai agent 怎么扛并发”问得非常实在。当100个用户同时上传PDF,LangGraph.js工作流会不会崩?答案是:瓶颈不在LangGraph.js,而在LLM API的并发限制和Next.js的Serverless执行环境。

先看LLM层。我用的OpenAI GPT-4-turbo,免费额度是10000 TPM(Tokens Per Minute)。假设每个简历分析平均消耗1200 tokens,理论最大并发数是8.3(10000÷1200)。但实际中,由于网络抖动和排队延迟,稳定并发上限是5。我的应对策略是两级队列:

  1. 前端队列:Next.js客户端用useSWR的mutate控制提交频率,同一用户连续点击间隔不低于3秒
  2. 服务端队列:在LangGraph.js工作流外加一层Redis队列,用Lua脚本保证原子性:
-- redisQueue.lua local queueName = KEYS[1] local maxConcurrent = tonumber(ARGV[1]) local current = redis.call('GET', queueName .. ':active') if not current then current = 0 end if tonumber(current) < maxConcurrent then redis.call('INCR', queueName .. ':active') redis.call('LPUSH', queueName .. ':pending', ARGV[2]) return 1 -- 允许执行 else return 0 -- 拒绝,需排队 end

当队列满时,Next.js返回HTTP 429,前端显示“当前请求繁忙,请稍后再试”,而非让用户干等。

再看Next.js层。Vercel的Serverless函数默认超时10秒,而复杂简历分析可能耗时12秒。我的解法是拆分长任务:把parsePDF和filterProjects放在首屏RSC中同步执行(通常<3秒),extractTech及后续步骤放入app/api/generate/route.ts的API Route中异步处理。用户看到的是:

  • 第一阶段(0-3秒):页面加载,显示“正在解析PDF...”
  • 第二阶段(3-5秒):返回初步结果(项目数、技术栈概览)
  • 第三阶段(5-12秒):API Route完成深度分析,通过Server-Sent Events(SSE)推送最终结果

这样既规避了超时,又保持了用户体验连贯性。实测在Vercel Pro套餐下,稳定支撑200 QPS,峰值可达350 QPS(持续5分钟)。

提示:别迷信“高并发=堆机器”。我曾用AWS EC2部署独立Agent服务,QPS做到150,但月成本$1200;换成Vercel Serverless+两级队列,QPS 200时月成本仅$47。真正的并发优化,是让每一美元都花在刀刃上。

6. 部署即交付:Vercel一键部署的六个关键配置项

很多人卡在“写完代码怎么上线”。Next.js+LangGraph.js的组合,在Vercel上部署看似简单,但有六个配置项不调好,轻则功能异常,重则服务不可用:

6.1 环境变量隔离:NEXT_PUBLIC_前缀的陷阱

Next.js规定以NEXT_PUBLIC_开头的环境变量会暴露到前端,但LangGraph.js的LLM API Key绝不能暴露。我的做法是:

  • .env.local中定义OPENAI_API_KEY=sk-xxx
  • 在app/api/generate/route.ts中,通过process.env.OPENAI_API_KEY安全读取
  • 前端只暴露NEXT_PUBLIC_LLM_MODEL=gpt-4-turbo,用于UI展示,不参与认证

Vercel后台的Environment Variables中,OPENAI_API_KEY设为Secret类型,确保不会被前端JavaScript访问。

6.2 构建缓存策略:禁用默认缓存,防止旧图谱污染

Next.js默认对node_modules启用缓存,但LangGraph.js的StateGraph类在v0.1.0和v0.1.1有breaking change。我的vercel.json强制禁用缓存:

{ "builds": [ { "src": "package.json", "use": "@vercel/next" } ], "cache": { "key": "nextjs-build-cache", "ttl": 0 } }

每次部署都重新安装依赖,避免因缓存导致的版本冲突。

6.3 函数超时设置:API Route的15秒硬限制

Vercel Hobby套餐API Route超时是10秒,Pro套餐是15秒。简历分析必须在15秒内完成,否则返回504。我在app/api/generate/route.ts顶部添加超时控制:

export const runtime = 'nodejs'; export const maxDuration = 15; // 显式声明,避免隐式超时 export async function POST(request: Request) { const controller = new AbortController(); setTimeout(() => controller.abort(), 14000); // 留1秒缓冲 try { const result = await graph.invoke(data, { signal: controller.signal }); return Response.json(result); } catch (error) { if (error.name === 'AbortError') { return Response.json({ error: '处理超时,请重试' }, { status: 408 }); } throw error; } }

6.4 静态资源路径:PDF.js Worker的CDN加载

PDF.js的Worker脚本必须从同源加载,否则浏览器会报CSP错误。我的next.config.js配置:

module.exports = { webpack: (config, { isServer }) => { if (!isServer) { config.resolve.alias['pdfjs-dist/build/pdf.worker.entry'] = 'pdfjs-dist/build/pdf.worker.entry'; } return config; } };

并在app/layout.tsx中预加载:

<head> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.min.js" strategy="beforeInteractive" /> </head>

6.5 错误监控集成:Vercel Analytics + 自定义埋点

Vercel自带的Analytics只能看PV/UV,我需要知道“哪个节点失败率最高”。在lib/monitoring.ts中封装:

export function trackNodeError(nodeName: string, error: Error) { if (process.env.NODE_ENV === 'production') { fetch('/api/track', { method: 'POST', body: JSON.stringify({ nodeName, errorMessage: error.message, timestamp: Date.now() }) }); } } // 在每个LangGraph.js节点中调用 const workflow = new StateGraph({ nodes: { parsePDF: (state) => { try { return { ...state, projects: parsePDF(state.pdfText) }; } catch (error) { trackNodeError('parsePDF', error); throw error; } } } });

6.6 回滚机制:Vercel的Instant Rollback

Vercel部署后自动生成Git commit hash,我要求团队每次上线前:

  • 在Vercel后台记录本次部署的Deployment ID
  • 将Deployment ID和变更说明写入内部Wiki
  • 当线上故障时,直接在Vercel控制台点击“Rollback to previous deployment”,30秒内完成回滚

这套流程让平均故障恢复时间(MTTR)从47分钟降至92秒。

7. 踩过的坑:那些文档里绝不会写的实战教训

最后分享三个血泪教训,都是文档里找不到,但上线当天就暴雷的问题:

7.1 PDF解析的字体缺失问题:中文简历乱码的根源

用户上传的中文简历PDF,用PDF.js解析后全是方框。查了一整天,发现是PDF.js默认不加载中文字体。解决方案不是换库,而是动态注入字体:

// utils/pdfParser.ts import { getDocument, version } from 'pdfjs-dist'; // 必须在解析前设置字体 pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js'; // 注入思源黑体 pdfjsLib.fonts = { 'SourceHanSansSC': 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.16.105/cmaps/SourceHanSansSC-Regular.otf' }; export async function parsePDFInWorker(pdfBytes: Uint8Array) { // 解析逻辑不变 }

7.2 LangGraph.js的循环检测:无限重试的隐形杀手

当matchRequirements节点发现JD要求“熟悉TypeScript”但简历未提及,按设计应跳转到askForTSExperience节点。但若askForTSExperience的返回值未被正确注入到state中,LangGraph.js会因state未更新而判定为“无变化”,触发无限循环。我的修复方案是在每个节点末尾强制添加updatedAt字段:

const workflow = new StateGraph({ nodes: { askForTSExperience: (state) => ({ ...state, tsExperience: 'unknown', // 显式设置默认值 updatedAt: Date.now() // 强制更新时间戳 }) } });

7.3 Next.js的Server Components缓存:RSC的“假成功”陷阱

RSC默认对相同props的组件结果缓存,导致用户上传新PDF后,页面仍显示旧结果。解决方案是在RSC中禁用缓存:

// app/resume/components/ResumeResult.tsx export default async function ResumeResult({ pdfText, jobDescription }: { pdfText: string; jobDescription: string; }) { // 添加随机key破坏缓存 const cacheKey = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const result = await graph.invoke({ pdfText, jobDescription, cacheKey }); return <div>{/* 渲染逻辑 */}</div>; }

这三个坑,每一个都让我加班到凌晨三点。但正是这些细节,决定了AI Agent是玩具还是生产力工具。现在回头看,所谓“完整落地”,不过是把每个环节的毛刺都磨平,让技术安静地服务于人,而不是让人去适应技术。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 13:07:17

森林火灾图像分类实操:13,000张标注数据的训练与避坑指南

简介&#xff1a;一套约13000张已标注的森林火灾图像分类数据集&#xff0c;围绕“有火/无火”二分类任务构建&#xff0c;面向计算机视觉与深度学习方向的研究人员、算法工程师&#xff0c;以及森林火情智能监测相关项目的开发者。压缩包内共2000个文件&#xff0c;以1998张jp…

作者头像 李华
网站建设 2026/10/7 13:05:14

BqLog日志优化:结构化事件流与执行路径重构

1. 项目概述&#xff1a;BqLog不是“日志打印器”&#xff0c;而是游戏性能的隐形守门人你打开《王者荣耀》打一局排位&#xff0c;从加载界面到水晶爆炸&#xff0c;全程不到20分钟——但后台可能已生成超过80MB原始日志数据。这些日志不显示在屏幕上&#xff0c;却真实存在&a…

作者头像 李华
网站建设 2026/10/7 13:03:55

Java LLM网关生产实践:K8s+Redis+SSE实现他化自在

1. 从“荒天帝”说起&#xff1a;一个LLM网关的封帝之路“荒天帝炼大模型网关”这个系列一路写到了第18境&#xff0c;终于到了“仙帝境”。如果你追过这个系列&#xff0c;大概知道前面17境都在折腾什么——从最开始的接口转发&#xff0c;到后来的多模型适配、流式响应、限流…

作者头像 李华
网站建设 2026/10/7 13:03:51

Flutter容器交互进阶:自研TransformableContainer实现平移缩放旋转

1. 为什么绕开InteractiveViewer&#xff0c;自己动手写容器变换1.1 系列走到第十一篇&#xff0c;前面到底铺垫了什么这一篇继续我们Flutter容器交互系列的实战。前面十篇把容器的布局结构、坐标换算、单指平移、事件拦截这些内容拆得差不多了&#xff0c;这次要解决的问题很具…

作者头像 李华
网站建设 2026/10/7 13:03:39

零依赖+WebRTC P2P,打造免服务器的网页小游戏

最近在独立游戏群里看到有人问&#xff1a;为什么 mikutap 这类音乐互动网页&#xff0c;不用装任何东西&#xff0c;复制链接到浏览器就能玩&#xff0c;手感还那么流畅&#xff1f;没过多久&#xff0c;又有人追问“webrtc怎么关闭”&#xff0c;说是不小心授权了浏览器权限&…

作者头像 李华