原生XMLHttpRequest实现文件上传:进度、取消与并发控制全解析

前两天接手一个内部小工具,需要做一个文件上传功能,前端不能引第三方库,也没法上构建工具,就一个纯静态页面加原生JavaScript。算了一圈,最后回到最基础的XMLHttpRequest,也就是大家常说的原生xhr。很多人觉得xhr太老,但真正上手之后会发现,在文件上传这个场景里,原生xhr的进度事件、取消机制、超时控制都是完整可用的,而且逻辑非常透明,没有黑盒。这篇文章就把我这次实践的过程和踩过的坑完整写出来,包括最小可用代码、进度条的实时计算、上传中断与重试、多文件并发控制,以及几个特别容易被忽略的细节。适合想把上传功能搞清楚、或者需要在没有框架依赖的环境里快速实现上传的开发者。

1. 为什么文件上传这件事,原生xhr依然没有退出历史舞台

1.1 同fetch相比,xhr独有的上传进度事件

fetch是ES6之后非常方便的请求方案,但它的设计目标是面向普通API请求,流式读取进度一直需要借助ReadableStream,并且浏览器支持参差不齐。xhr天生就有progress事件,而且专门区分了下载进度和上传进度:xhr.upload对象可以单独监听progress,拿到loaded和total,这两个值直接对应已经发送的字节数和总字节数。这意味着我们不需要额外依赖,不需要解析流,只需要几行代码就能给用户一个实时百分比。

一个很直接的对比是:fetch想拿到上传进度,需要自己封装Request body的流,还要处理不同浏览器对duplex属性的支持问题,很多时候写着写着就变成调试浏览器兼容性。而xhr的upload.onprogress从IE时代就存在,到现在所有主流浏览器都支持,使用体验非常稳定。

1.2 同axios库相比,原生方案更轻量、可控

axios底层其实也是xhr,它只是包了一层Promise,做了请求拦截、响应拦截、错误归一化这些封装。但如果项目里只是要一个文件上传功能,为了它引入整个axios,不仅增加体积,还多了一层抽象。遇到问题时,比如上传二进制大文件、需要取消请求、或者想拿到网络层原始状态码,反而要去扒axios源码才能知道它做了什么。

原生xhr的逻辑很直白:open指定方法和地址,setRequestHeader设置头部,send发送数据,事件监听器处理状态变化。每个环节都暴露在面前,我可以精确控制哪里加header、哪里加参数、哪里做超时处理。对于学习了基础前端的人来说,熟悉原生xhr也有助于理解axios的封装原理。

1.3 适用场景与选型建议

并不是说所有项目都要退回原生xhr。如果项目本身已经在用axios,就完全没必要把上传逻辑单独拆成原生写法,统一用axios的FormData上传即可。但如果遇到下面这些情况,原生xhr会是更合适的选择:

  • 项目是零依赖的老页面或者轻量工具页,不想为了一个功能引入资源。
  • 需要精细控制上传进度、取消上传、即时重试,而且不想被Promise的catch逻辑干扰网络错误细节。
  • 需要兼容老版本浏览器,尤其是企业内网环境。
  • 想真正理解HTTP上传的底层细节,适合学习或做一些底层的上传SDK。

我在实践中的体会是,对于简单的上传场景,原生xhr不仅代码量少,而且调试起来比封装的库更直观,打开开发者工具能看到非常清晰的网络请求过程。

2. 从零实现一个最小可用的xhr上传模块

2.1 核心代码:FormData + xhr.send

文件上传最基础的方式就是FormData。它可以把文件对象安放到一个multipart/form-data格式的请求体里,浏览器会自动生成boundary分隔符,并把每个字段的name、文件名、Content-Type都带上。下面是最小实现:

function uploadFile(file, url, onProgress) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); const formData = new FormData(); formData.append('file', file); xhr.open('POST', url, true); xhr.upload.onprogress = (e) => { if (e.lengthComputable && onProgress) { const percent = Math.round((e.loaded / e.total) * 100); onProgress(percent, e.loaded, e.total); } }; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.responseText); } else { reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`)); } }; xhr.onerror = () => reject(new Error('network error')); xhr.send(formData); }); }

这段代码已经覆盖了80%的基础需求。FormData.append('file', file)把File对象放进去,浏览器会把文件名作为filename字段发送。xhr.open第三个参数必须是true,确保异步执行,否则UI会卡死。send(formData)会由浏览器自动设置Content-Type: multipart/form-data; boundary=...,不需要手动指定。

2.2 关键细节:为什么不要手动设置Content-Type

很多人会习惯性地加一行xhr.setRequestHeader('Content-Type', 'application/json'),然后上传就失败了。原因是FormData需要使用的是带boundary的multipart/form-data格式,而这个boundary是浏览器在send时动态生成了插入请求体的。如果手动设置一个固定的Content-Type,boundary就丢失了,服务端解析不出文件边界,自然收不到文件。

如果非要手动设置,一定要带上完整的boundary值,而且这个值必须和请求体里实际使用的分隔符完全一致。实际开发中根本没必要这么做,正确的做法就是让浏览器自己处理FormData的Content-Type。如果需要额外传递参数,用formData.append('token', token)添加到表单里,而不是用Header。

2.3 服务端如何接收

以Node环境为例,如果服务端使用框架路由,一般需要内置的body解析器处理multipart/form-data。这里给一个简单思路:

  • 使用Koa框架时,可以配合中间件解析上传的文件,文件到达后会被存成临时文件或Buffer。
  • 使用Express框架时,可以使用对应的上传中间件,把上传的文件信息挂在req.file或req.files上。

服务端拿到文件之后,要验证文件大小、MIME类型、扩展名,再落盘或者转存到对象存储。这里有一个重要的安全点:文件名不能直接使用客户端传来的原始文件名拼接路径,必须做白名单校验和重命名。否则遇到../../这种路径名会出大问题。

我在测试时遇到过一个问题:本地postman直接传文件没问题,但前端xhr传上去之后服务端报字段名不存在。排查到最后发现是前端formData.append('file', file)中的字段名是file,而后端的中间件默认接收的字段名也是file,看起来没有错。问题出在用户选择文件后,我传了一个dataURL字符串而不是Blob对象,导致字段类型不对。后来改成用e.target.files[0]获取原始File对象,立即就通了。

2.4 一个可以直接用的函数封装

在实际项目里,除了基础上传,还要处理超时和取消。我把常用配置做成了参数对象:

function uploadFileWithOptions({ url, file, name = 'file', data = {}, timeout = 30000, withCredentials = false, onProgress, }) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); const formData = new FormData(); formData.append(name, file); Object.entries(data).forEach(([key, value]) => { formData.append(key, value); }); xhr.open('POST', url, true); xhr.timeout = timeout; xhr.withCredentials = withCredentials; xhr.upload.onprogress = (e) => { if (e.lengthComputable && onProgress) { onProgress(e.loaded, e.total); } }; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { let result; try { result = JSON.parse(xhr.responseText); } catch { result = xhr.responseText; } resolve(result); } else { reject(new Error(`HTTP ${xhr.status}`)); } }; xhr.onerror = () => reject(new Error('network error')); xhr.ontimeout = () => reject(new Error('timeout')); xhr.send(formData); }); }

这里把withCredentials透传出来,是为了处理需要携带Cookie的跨域请求。如果是同源页面,不需要设置它。超时时间我习惯给30秒,但如果是视频或大压缩包,要根据实际网络环境调整,网络差的公司内网建议放宽到2-5分钟。

3. 上传进度条的实时计算与展示优化

3.1 progress事件的数据结构

xhr.upload的progress事件会返回ProgressEvent对象,里面有四个关键字段:

  • loaded:已经上传到服务器的字节数,不是内存里读完的数据,而是浏览器确认发送出去的字节数。
  • total:本次上传总字节数,通常等于file.size,但不一定总是有值。
  • lengthComputable:布尔值,表示能否计算总长度。如果服务器或浏览器无法得知总长度,这个值会是false,此时total可能是0。

特别说明一下,监听对象是xhr.upload,不是xhr本身。很多人写xhr.onprogress,结果拿到的响应下载进度,上传时一直不触发,然后以为是浏览器问题。

3.2 进度百分比、上传速度和剩余时间的计算

百分比计算比较简单:

const percent = e.lengthComputable ? Math.round((e.loaded / e.total) * 100) : 0;

上传速率和剩余时间需要自己维护历史数据。我一般会记录上次的loaded和timestamp,每次都计算差值:

let lastLoaded = 0; let lastTime = Date.now(); let speedBytesPerSec = 0; function onProgress(e) { const now = Date.now(); const deltaTime = now - lastTime; if (deltaTime > 200) { const deltaLoaded = e.loaded - lastLoaded; speedBytesPerSec = deltaLoaded / (deltaTime / 1000); lastLoaded = e.loaded; lastTime = now; } const remainingBytes = e.total - e.loaded; const remainingSecs = speedBytesPerSec > 0 ? Math.ceil(remainingBytes / speedBytesPerSec) : 0; }

注意这里要做时间间隔判断,因为progress事件触发频率可能非常高,如果每次都计算速度,数值会跳动得很厉害。200毫秒的间隔既能保证UI的响应感,又能平滑速度波动。

3.3 节流与动画刷新,避免过度渲染

progress事件在快速网络下可能一秒钟触发几十次,如果每一次都去操作DOM更新进度条,页面会频繁重绘。我的做法是借助requestAnimationFrame做标记,达到节流效果:

let rafId = null; function onProgress(e) { if (rafId) return; rafId = requestAnimationFrame(() => { updateUI(e); rafId = null; }); }

这样即使事件风暴来了,每帧最多更新一次,不再浪费性能。如果是纯文本百分比数字,也可以用节流函数限制到100ms更新一次,效果一样。这里有一个经验:进度条数值不要在90%以上反复跳动,如果服务和前端没有做最后完成确认,就把它固定在90%等真实的load事件,否则用户会奇怪为什么最后10%卡那么久。

3.4 文件大小未知时的兜底方案

有些场景下e.total会是0。比如某些老旧的浏览器,或者从流式来源读取文件时,lengthComputable为false。这种情况下不能显示百分比,可以改为显示已上传的字节数,比如"已上传 3.2MB",或者用一个不确定进度的动画条。我的处理逻辑是:

if (e.lengthComputable) { progressContainer.style.width = `${percent}%`; } else { progressContainer.classList.add('indeterminate'); }

不确定进度条用CSS动画去循环滑动,用户至少知道进程还在跑,而不是卡死。

4. 上传取消、超时与网络异常的处理

4.1 abort()的正确姿势

上传过程中用户点了"取消",需要调用xhr.abort()。关键是不要只调abort(),要理解abort之后触发什么事件。abort()会停止当前请求,并触发xhr.onabort。同时,xhr.upload会上抛一个progress事件,但此时loaded不会增长。正确做法是:

let xhr = null; function cancelUpload() { if (xhr) { xhr.onabort = () => { // 在这里清状态,而不是在外部再判断一次 setUploadStatus('cancelled'); }; xhr.abort(); } }

有个常见的坑是:调用abort之后,onload也会触发,而且某些浏览器会把它包装成status=0的结果。如果在上面的Promise封装里,abort后onload执行并走了reject,但马上外部又把xhr置空了,会导致回调混乱。所以封装时最好增加一个isAborted标志:

let aborted = false; xhr.onabort = () => { aborted = true; reject(new Error('upload aborted')); }; xhr.onload = () => { if (aborted) return; // 原有逻辑 };

这样abort事件和load事件不会互相干扰。

4.2 timeout和网络错误的分场景处理

上传大文件时,如果网络波动,可能很久没有任何数据发送出去。xhr.timeout设置的是请求超时时间,但它表示的是整个请求从开始到结束的最大时间,还是空闲超时?不同浏览器对超时的定义不完全一致。规范上是请求超时,但实际体验中,我观察到的行为是:如果在超时前有任何数据传输,计时会重置或至少不会精确按总时长掐断。因此靠timeout不太适合做"断网后马上提示失败"的需求。

更好的做法是用setTimeout做兜底检测,比如每30秒检测一次最近是否还有progress事件,如果没有任何进展就主动abort并提示"上传停滞"。这样才能避免一次大文件上传卡在99%,用户干等了十分钟。

4.3 重试机制:前端能做与不能做的

简单的重试逻辑就是捕获到错误后再次调用上传函数。但要注意区分错误类型:

  • 网络错误:xhr.onerror触发,可能是断网、DNS解析失败、连接被拒绝。这种可以重试,但要设定次数上限,比如最多3次,且每次间隔递增。
  • HTTP错误:比如413(文件过大)、403(权限不足)、422(文件类型不允许),这些重试没有意义,应该直接把错误展示给用户。
  • 超时:如果是网络拥堵导致超时,可以重试,但最好提醒用户检查网络。

前端的重试很容易做到,但有一个关键点是:当上传失败时,服务端可能已经接收到了一部分数据。如果你用的是一次性POST整个文件,那么服务端必须提供幂等性保障,否则重试可能会造成重复数据或半截文件。简单的方案是:每次上传时生成一个唯一的uploadId,服务端根据这个ID判断同一文件的重复上传内容。

4.4 与axios的cancelToken对比

axios提供CancelToken来取消请求,但本质上它的取消机制仍然是调用底层的xhr.abort()。在axios里取消上传会触发cancel事件,并不会抛给.catch一个普通Error。如果对原生xhr的abort事件理解到位了,再看axios的cancelToken就很简单,不过是你自己包了一层Promise而已。我个人不喜欢为了取消一次上传就引入一个第三方库,因为原生xhr给的能力已经完全够了。

5. 多文件、并发与队列控流

5.1 多文件上传的界面与数据结构

多文件上传的交互一般有两种:一次选择多个文件逐个上传,或者用户多次选择文件先加入列表再统一上传。我倾向于第二种,因为用户可以修改、删除,然后再批量提交。数据结构可以这样组织:

const fileList = []; // 每个文件条目 { uid: `file_${Date.now()}_${index}`, file: File对象, status: 'pending' | 'uploading' | 'success' | 'error' | 'cancelled', progress: 0, response: null, error: null, xhr: null, }

每个条目保存自己的xhr引用是为了支持单独取消。如果某个文件上传失败,只修改这一条的状态,并在UI上显示重试按钮。

5.2 并发数限制的实现:简单Promise池

如果一次性上传几十个大文件,所有请求同时发出,浏览器会因为连接数限制而排队,还可能占用大量内存。比较好的做法是限制并发数,比如同时最多3个请求。实现思路不复杂:维护一个待上传队列,始终保证正在执行的请求数不超过并发上限。

async function runWithConcurrency(tasks, limit) { const results = []; const executing = []; for (const task of tasks) { const promise = Promise.resolve().then(() => task()); results.push(promise); if (limit <= tasks.length) { const whenComplete = promise.then( () => executing.splice(executing.indexOf(whenComplete), 1) ); executing.push(whenComplete); if (executing.length >= limit) { await Promise.race(executing); } } } return Promise.all(results); }

这个Promise池每次推入一个任务,当执行数量达到上限时,等待任意一个任务完成再继续推入。如果有100个文件,限制并发3,内存中同时只有3个xhr对象,整个队列很平稳。

5.3 断点续传的前置思路(切片上传)

严格来说,断点续传需要服务端配合。前端能做的核心工作就是切片。思路是:把文件读成若干块,比如每块2MB,依次上传每一块,同时记录哪些块已经成功。xhr上传单个切片时,两边的进度都非常好计算,失败后重试时只需要把未成功的切片重新传一遍。

切片上传有几个细节:

  • 文件名要带上文件的hash或唯一标识,避免服务端把不同文件的同名切片混淆。
  • 每个切片请求要有序号,服务端按序号拼接。
  • 上传完成所有切片后,客户端发一个合并请求,告知服务端“可以合并了”。
  • 切片大小不是越细越好,太细会导致请求数激增,反而降低效率;2MB-8MB通常是比较合理的区间。

用xhr做切片不需要额外能力,只需要循环读取File.slice(begin, end)创建新的Blob,再放进FormData发送。这个能力即使是在老浏览器里也足够用。

5.4 上传性能与内存占用

大文件上传时,不要用FileReader把整个文件读成dataURL或ArrayBuffer再上传,会把内存打爆。直接用FormData.append('file', file)传入File对象,浏览器内部会以流的方式读取文件,内存占用非常小。这一点是原生xhr的优势之一。

如果要在上传前预览图片,也用URL.createObjectURL(file)生成临时URL,用完之后调用URL.revokeObjectURL释放。如果直接把图片文件阵列成base64再上传,页面会很明显地卡顿。

6. 实战中踩过的坑与排查手记

6.1 文件名和字符集问题

上传中文文件名的时候,如果服务端用filename解析文件名,可能会出现乱码。标准做法是前端保证文件名以UTF-8编码,并且服务端解析时也按UTF-8解码。但有些网关或中间件有Bug,会按ISO-8859-1解码。我在项目里遇到过一种情况:文件名里带空格和特殊字符,服务端解析时被截断。

稳妥的做法是:前端除了把文件原样放进FormData之外,再额外通过一个自定义字段传递文件名,服务端优先读取这个字段作为保存文件名,不依赖multipart头里的filename字段。

6.2 请求头被服务端解析成字符串或数组

如果服务端是Node的某些body解析中间件,req.body里可能会把表单字段解析成字符串,或者把同名多个字段解析成数组。上传时在同名字段里塞了多个文件,服务端只取了一个,就会丢失文件。解决方式是前端对每个文件都用不同的字段名,比如file_0、file_1,或者统一观察服务端收到的字段结构再调整。有一次我调试了很久才发现是字段名重复导致的覆盖行为。

6.3 进度事件不触发或total为0

进度事件完全不触发的常见原因有三个:

  1. 监听绑定在xhr而不是xhr.upload上。
  2. 使用了file://协议本地打开页面,且请求的地址是跨域的,浏览器提前拦截,上传根本没有开始。
  3. 在xhr.send()之后才绑定upload.onprogress,有些浏览器允许这样,但为了兼容性,应该先绑定所有事件再send。

total为0则常见于服务端不支持Content-Length计算,或者使用了Transfer-Encoding: chunked。如果必须显示百分比,最好由前端通过file.size自己传入总大小,而不是依赖事件里的total。

6.4 跨域携带Cookie的问题

当上传接口和页面不同源时,默认情况下Cookie不会带上。这时需要xhr实例设置withCredentials = true,同时服务端返回响应时必须携带Access-Control-Allow-Credentials: true,并且Access-Control-Allow-Origin不能是*,必须是指定的完整源地址。这个配置不熟悉的话,排查起来很容易让人崩溃,因为浏览器控制台可能只提示一句泛泛的CORS错误,看不到真实原因。

如果遇到401,先检查Cookie有没有带上;如果请求到达了服务端但请求头没带Cookie,基本都是withCredentials没设。

6.5 最终识别的几个好习惯

  • xhr.upload.onprogress、xhr.onload、xhr.onerror、xhr.ontimeout这些事件要在send()之前全部绑定完成,尤其是封装成Promise时,顺序千万不要乱。
  • 上传成功后务必xhr = null或者释放引用,避免长列表页面的内存被无用的xhr对象占用。
  • 如果同一个文件已经被用户移除,但上传还在内存中,应主动调用abort()中断,避免无效的数据继续跑到服务端。

写在最后,可能有点返璞归真,但我越来越觉得做前端不能只看封装好的库,很多底层的库最终也是封装xhr或者Fetch实现的。文件上传这个功能看起来简单,里面的进度、取消、超时、队列、并发、错误恢复一整套做下来,足够撑起一个完整的模块。如果刚接触这块,建议先不用库,遇到底层API不太懂的地方就查文档,运行一遍再下结论,很多印象中的“历史遗留Bug”其实早就被修好了。原生xhr写出来虽然代码朴素,但每一步都在自己的掌控范围里,查起问题来也是清清楚楚。

相关新闻

高校固定资产管理系统开发实战:SpringBoot+Vue+MySQL全解析

高校固定资产管理系统开发实战:SpringBoot+Vue+MySQL全解析

高校固定资产管理系统&#xff0c;这活儿听起来挺传统的&#xff0c;但真做起来你会发现里面门道不少。我前前后后给两所不同规模的学校搞过类似的系统&#xff0c;从最初单纯记录资产编号&#xff0c;到后来接入扫码盘点、维修报废审批流&#xff0c;算是把这类业务系统的底细…

2026/10/21 4:17:43 阅读更多 →
Agent异常行为实时检测:Harness Engineering的监控告警实战

Agent异常行为实时检测:Harness Engineering的监控告警实战

最近我带的Agent应用一上生产&#xff0c;最先顶不住的已经不是模型回答得对不对&#xff0c;而是它在后台到底干了什么。一套AI Agent如果没有把Harness Engineering那层监控告警做好&#xff0c;异常行为实时检测基本就是靠人肉盯日志&#xff0c;等发现问题时&#xff0c;钱…

2026/10/21 4:17:43 阅读更多 →
AI Agent监控告警实战:从指标设计到Harness落地

AI Agent监控告警实战:从指标设计到Harness落地

做 AI Agent 的人&#xff0c;我相信大部分都经历过那种“前一秒还好好的&#xff0c;后一秒突然开始胡言乱语”的时刻。尤其是当一个 Agent 被投放到真实业务里、拿到了真实工具权限、开始自主执行多步任务之后&#xff0c;它已经不是在跑一段固定的代码&#xff0c;它是在一个…

2026/10/21 4:17:43 阅读更多 →

最新新闻

Windows下PyTorch复现PointNet++点云分类实战

Windows下PyTorch复现PointNet++点云分类实战

1. 为什么要在Windows上折腾PointNet搞点云深度学习的人&#xff0c;绕不开PointNet这个经典网络。它2017年出来之后&#xff0c;几乎成了点云分类和分割任务的基线模型&#xff0c;后续的PointConv、DGCNN、RandLA-Net多多少少都受了它的影响。但真到自己动手复现的时候&#…

2026/10/21 4:58:59 阅读更多 →
别只收藏skills清单:项目式拆解+刻意练习,30天做出作品

别只收藏skills清单:项目式拆解+刻意练习,30天做出作品

1. 先搞清楚一件事&#xff1a;你缺的真是一份“skills清单”吗这个标题其实特别有意思。单独扔过来一个“skills”&#xff0c;乍一看像是职场简历里的那个区块名&#xff0c;又像是一个待办事项列表——今天学Python&#xff0c;明天学剪辑&#xff0c;后天再补个AI绘画。但我…

2026/10/21 4:58:59 阅读更多 →
Windows 10下PyTorch 1.x复现PointNet++点云分类实战

Windows 10下PyTorch 1.x复现PointNet++点云分类实战

1. 点云分类任务的核心价值与复现思路1.1 为什么选择PointNet做点云分类点云分类这件事&#xff0c;放在三维视觉里算是入门级的任务&#xff0c;但真正动手做的人都知道&#xff0c;从数据加载到模型收敛&#xff0c;中间要踩的坑一点都不少。PointNet作为PointNet的升级版&am…

2026/10/21 4:58:59 阅读更多 →
JavaScript时间处理工具类:从时区错位到日期计算的实战指南

JavaScript时间处理工具类:从时区错位到日期计算的实战指南

1. 我先梳理了时间处理的四大痛点场景在动手写任何一行代码之前&#xff0c;我先做了一个动作&#xff1a;把最近几个项目里跟时间打交道的代码全部翻出来&#xff0c;统计了一下到底有多少地方出现过“时间相关bug”。统计结果把我自己都吓了一跳——光一个中等规模的项目里&a…

2026/10/21 4:58:59 阅读更多 →
深度计算 Runtime 并发调度:执行流、事件与异步提交实践

深度计算 Runtime 并发调度:执行流、事件与异步提交实践

1. 并发无上限是个假象&#xff1a;先看清深度计算硬件的真实执行模型1.1 从“线程并发”到“任务并发”的思维转换做深度计算 Runtime 这些年&#xff0c;我见过太多团队在“并发”这件事上栽跟头。最常见的想法是&#xff1a;任务能塞就塞&#xff0c;队列能开多深就开多深&a…

2026/10/21 4:58:59 阅读更多 →
离线环境Dify+Milvus知识库部署:连通性排查与解决全记录

离线环境Dify+Milvus知识库部署:连通性排查与解决全记录

规划内网知识库的时候&#xff0c;最头疼的往往不是算法选型&#xff0c;而是环境本身。最近搭了一套离线环境下的 Dify Milvus 知识库系统&#xff0c;折腾了整整两天&#xff0c;最后发现所有问题都集中在“连不上、起不来、版本对不上”这几个坎上。这里把整个排查过程和解…

2026/10/21 4:57:59 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →