前端网络请求全攻略:从fetch封装到跨域并发实战
前端的网络请求这块属于那种用起来简单用好了很难的知识点。刚入行的时候可能觉得不就是调个接口拿数据吗可一旦涉及到跨域、并发、取消请求、错误重试甚至只是处理一个竞态条件背后需要搞清楚的东西瞬间就多起来了。这篇是我整理的前端网络请求笔记会顺着实际开发的思路把这几年积累的经验、踩过的坑都梳理一遍希望能给正在学这块或者被各种请求问题折磨的朋友一些参照。1. 从XHR到fetch真正的现代写法长什么样很多教程都会从基础开始讲网络请求但真正需要先理解的是前后端的数据交互到底经历了哪些变化。过去我们用XMLHttpRequest现在我们更倾向于用fetch再加上一层封装就构成了现代前端项目的标准请求模型。1.1 XMLHttpRequest为什么被嫌弃XHR这套API设计得比较早回调式的写法在复杂场景下会显得不够优雅。虽然也能用但代码组织起来确实费劲。举一个最简单的例子普通的GET请求如果要封装通常会这样写function getData(url, callback) { const xhr new XMLHttpRequest(); xhr.open(GET, url); xhr.onreadystatechange function () { // 4表示请求完成 if (xhr.readyState 4) { // 2xx是成功状态码 if (xhr.status 200 xhr.status 300) { callback(null, JSON.parse(xhr.responseText)); } else { callback(new Error(请求失败: xhr.status)); } } }; xhr.send(); }这个写法你看着是不是很眼熟早年间的jQuery.ajax本质也是对这个东西的封装。但问题在于一旦请求变得复杂比如需要并发多个请求再比如请求失败要重试回调嵌套就很容易膨胀回调地狱就是这么来的。提示即便现在fetch是主流但在某些老项目或者特殊浏览器环境里XHR还是会有用武之地。搞清楚它的事件机制对你理解整个HTTP请求的生命周期有好处。1.2 用fetch写出清晰可控的请求fetch能够流行主要是因为两点一是返回Promise能用async/await来组织代码二是API设计更贴近浏览器底层的Request和Response概念。你不需要再去记忆readyState、responseText这些偏底层的属性名语义清晰了很多。同样是GET请求用fetch实现就直观得多async function fetchUser(id) { const response await fetch(/api/user/${id}); // 注意fetch只在网络错误时reject // HTTP状态码是404、500这类错误时它不会进入catch if (!response.ok) { throw new Error(请求出错: ${response.status}); } return response.json(); }初次用fetch的开发者十有八九会在状态码判断上踩坑。fetch默认不会把404、500当作Promise的reject处理它认为只要服务器有响应就算请求完成。所以在实际项目中我们往往要再包一层简单的拦截器把非2xx状态统一处理掉。1.3 为什么说axios这类库仍然值得装原生fetch够用了吗如果项目复杂度不高完全够用。但如果你在一个中大型项目里工作会发现有几个需求是fetch原生不太好满足的请求超时控制原生fetch需要结合AbortController手动实现请求取消也就是用户离开页面后需要中断已发出的请求拦截器机制便于统一处理token、错误弹窗、loading状态上传进度的实时反馈fetch的进度事件支持得不如XHR成熟。所以像axios这类库依然有它的生态价值。不过我的观点是不要为了用库而用库。先把fetch本身吃透再用库的时候你会更容易理解它到底帮你做了什么。2. 请求封装每个项目都该有的那一层拦截器前面提到fetch的某些不足在大型项目里这些不足会被进一步放大。你会发现如果每个页面都自己写一份请求逻辑那么一旦要改动公共逻辑比如给请求头统一加上token就要把所有页面都翻一遍这是非常痛苦的。所以几乎所有成熟的前端项目都会做一层请求的二次封装。2.1 统一处理BaseURL与超时时间先想清楚一件最简单的事你的接口路径到底是什么。开发环境可能是http://localhost:8080/api生产环境可能是一个正式域名下的/api直接用相对路径虽然方便但会遇到开发环境跨域问题后面会详细讲。如果不同环境的地址前缀不同一个干净的做法是定义一个公共变量。以axios的封装为例结构大致是这样的// request.js import axios from axios; const service axios.create({ // 根据不同环境切换请求根路径 baseURL: import.meta.env.VITE_API_BASE || /api, timeout: 15000 }); // 请求拦截器 service.interceptors.request.use( (config) { // 从本地存储里取出登录态token const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }, (error) Promise.reject(error) ); export default service;这里有两个小点值得展开。一是baseURL的配置用环境变量来区分开发和线上是很标准的做法不同的环境构建时注入不同的值代码里不需要手动改来改去。二是timeout不要不设置或者设得太长网络故障时一个迟迟没有响应的死请求会卡住整个页面逻辑。15秒是一个在很多团队里都常见的折中值具体还得看你们接口的平均耗时。2.2 响应拦截器里做状态码路由如果你经历过接口要多个后端系统同时配合的场景你会发现后端返回的数据结构千奇百怪。有的返回{code: 0, data: ...}有的返回{success: true, result: ...}。如果每个页面都去判断一次代码冗余极高。所以我会在响应拦截器里把业务状态码的判断统一收口。service.interceptors.response.use( (response) { // 后端约定的业务码比如0代表成功 const res response.data; if (res.code 0) { return res.data; } // 这里是业务层面的错误处理 // 比如未登录(401)、无权限(403)、服务端异常(500) switch (res.code) { case 401: // 跳转登录页 break; case 403: // 提示无权限 break; default: // 统一弹错误提示 } return Promise.reject(new Error(res.message || 请求出错)); }, (error) Promise.reject(error) );这种封装的核心思路是让调用方只关心业务数据而把怎么处理各种非预期状态统一放到这一层。这样页面里的请求代码可以很干净const userInfo await getUserInfo(userId); // 直接使用 userInfo无需处理状态码2.3 封装时最容易忽略的重复请求响应拦截器里还有一个高阶玩法就是防重复请求和请求串扰。举个再常见不过的例子用户快速双击了一个提交按钮同一个POST请求被发出了两次紧接着后端创建了两条同样的订单。解决思路是在请求发出前记录标识响应后清除标识如果标识存在就不允许再次发起。简单的防重逻辑可以这样写const pendingMap new Map(); function getRequestKey(config) { return [config.method, config.url, JSON.stringify(config.params)].join(); } service.interceptors.request.use((config) { const key getRequestKey(config); if (pendingMap.has(key)) { // 重复请求直接取消或者抛错 return Promise.reject(new Error(重复请求)); } pendingMap.set(key, true); return config; }); service.interceptors.response.use( (response) { const key getRequestKey(response.config); pendingMap.delete(key); return response; }, (error) { const key getRequestKey(error.config); pendingMap.delete(key); return Promise.reject(error); } );这段代码看着简单但解决的真实痛点并不小。尤其是列表页的筛选按钮、表单提交按钮经常会因为用户操作过快导致重复请求。当然这个方案的粒度算是手动防重复更精细的方案还有取最近的请求或合并相同请求后面在竞态那一节会提到。3. 跨域问题前后端联调时的那道隐形墙要说网络请求里最让人头疼的跨域肯定排得上号。本地开发好好的代码一部署到测试环境或者后端联调时换了个端口请求就发不出去了浏览器控制台飘红一片。这背后的原理到底是什么很多做前端的人其实没有完全搞透。3.1 同源策略到底在防护什么同源策略是浏览器层面的安全机制。所谓同源要求协议、域名、端口三者完全一致但凡有一个不同浏览器默认就会拦截跨域请求的响应。为什么要有这个机制很简单假设你登录了A网站如果浏览器不限制跨域请求那么你访问B网站时B网站的脚本就可以带着你的登录态去请求A网站的接口这样你的数据就泄露了。但这套机制放到前后端分离的开发模式下又显得矛盾前端跑在一个服务器上后端接口跑在另一个服务器上天然就是不同源的。所以就有了各种跨域解决方案。理解同源策略是浏览器在保护你你才能理解为什么有些跨域问题后端加了响应头就能解决而有些却无论如何都拦不住。3.2 CORS最正规的跨域方案CORS跨域资源共享是目前最标准、也是你应该优先选择的方案。它的核心是后端在返回响应时增加一些特定的响应头告诉浏览器这个跨域请求是允许的。常见的响应头就这几个响应头作用Access-Control-Allow-Origin允许哪些源访问可指定一个域名或通配符Access-Control-Allow-Methods允许哪些请求方法GET、POST、PUT等Access-Control-Allow-Headers允许携带哪些请求头如Authorization、Content-TypeAccess-Control-Allow-Credentials是否允许携带Cookie凭证比如后端用Node的Express框架最简配置可以这样写app.use((req, res, next) { res.setHeader(Access-Control-Allow-Origin, http://localhost:5173); res.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); res.setHeader(Access-Control-Allow-Headers, Content-Type, Authorization); res.setHeader(Access-Control-Allow-Credentials, true); // 处理预检请求 if (req.method OPTIONS) { res.sendStatus(204); return; } next(); });这里有一个很多人没注意到的细节当请求携带自定义头部比如Authorization或者使用PUT、DELETE等方法时浏览器会先发一个OPTIONS预检请求去探测服务器是否允许这个跨域动作。如果后端没处理OPTIONS前端就会看到CORS error的提示但其实真正的接口根本还没有被调用到。联调时看到请求多了个OPTIONS不用慌这是正常的。3.3 开发环境的代理devServer和Vite的配置后端接口配上CORS之后生产环境基本没问题了。但开发环境有一个更省事的方案——代理转发。原理很简单前端开发服务器运行在本地同源的请求先打到开发服务器再由开发服务器把请求转发给后端接口。因为服务器之间的请求没有浏览器同源策略限制所以绕过了跨域问题。在Vite项目里配置大致长这样// vite.config.js export default { server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };配置完之后你在代码里请求/api/user开发服务器会自动转发到http://localhost:8080/api/user。这里需要留意的坑是changeOrigin它的作用是改变请求头里的Host字段。某些后端服务会校验Host如果你不设置这一项即使代理通了后端也可能返回异常。我见到过不少团队生产环境CORS配得好好的开发环境却还顶着代理的配置在跑。严格来说这两者不是二选一的关系代理主要解决开发便利性CORS解决的是生产环境的真实跨域问题。如果条件允许两边都配好是最稳妥的。4. 并发与竞态接口响应顺序错乱怎么破如果说跨域是网络请求的第一大坑那么并发与竞态问题就是第二大坑。你可能会遇到这样的情况页面上快速切换筛选条件发了两个请求第一次的先请求后返回第二次的后请求先返回导致页面最终显示的是旧数据。这类问题最难排查因为它不是必现的而是依赖网络的实时状态。4.1 前端里的竞态条件Race Condition竞态条件的本质是多个请求的完成顺序和发出顺序不一致。以搜索框为例用户输入苹果然后马上又输入苹果手机触发两次请求。正常情况下应该展示苹果手机的结果但如果苹果的响应比苹果手机的响应慢页面最终可能被更早发出的请求覆盖。解决思路通常有几种。方案一忽略过期响应给每一次请求生成一个递增或唯一的ID只响应ID最大的那次。这是最直接的方式。let requestId 0; async function search(keyword) { const currentId requestId; const res await fetch(/api/search?q${keyword}); const data await res.json(); // 只有当当前请求还是最新请求时才更新数据 if (currentId requestId) { render(data); } }方案二取消旧请求利用AbortController在发起新请求时把上一次还没完成的请求直接中断掉。这种方式更彻底还能节省网络资源。let currentController null; async function search(keyword) { // 中断上一次未完成的请求 if (currentController) { currentController.abort(); } currentController new AbortController(); try { const res await fetch(/api/search?q${keyword}, { signal: currentController.signal }); const data await res.json(); render(data); } catch (err) { // 如果是手动中断不要当成普通报错处理 if (err.name AbortError) return; handleError(err); } finally { currentController null; } }方案二在实际应用中更推荐因为AbortController不仅解决了竞态还能在组件卸载时自动中断请求避免在页面已经关闭后还去更新状态产生Cant perform a React state update on an unmounted component这类警告。4.2 列表页的并发请求优化有些场景不是要停掉旧请求而是多个请求本来就需要并行发出但性能上想优化一下。比如一个详情页需要同时拿用户信息、订单列表、评论列表三个接口的数据。使用Promise.all是最常见的做法async function loadDetail(userId) { const [userInfo, orders, comments] await Promise.all([ fetch(/api/user/${userId}).then(r r.json()), fetch(/api/user/${userId}/orders).then(r r.json()), fetch(/api/user/${userId}/comments).then(r r.json()) ]); renderDetail({ userInfo, orders, comments }); }Promise.all的语义是全部成功才算成功只要其中一个reject整个Promise就会reject。这在接口容错性要求高的场景下不一定合适可以考虑用Promise.allSettled它会在所有请求结束后返回每个请求各自的结果而不是被某一个失败拖垮。4.3 请求超时与重试策略另外一个和并发相关的点是超时与重试。不是说所有请求都要做重试这要看接口的幂等性。GET请求天然适合重试POST请求则要慎重因为你重试一次后端可能就重复插入了一条数据。实现超时控制原生fetch需要配合AbortControllerfunction fetchWithTimeout(url, options {}, timeout 10000) { const controller new AbortController(); const timer setTimeout(() controller.abort(), timeout); return fetch(url, { ...options, signal: controller.signal }).finally(() clearTimeout(timer)); }重试的逻辑也很容易封装基本思路是判断错误类型如果是网络错误或超时可以做几轮重试并加入简单的退避策略。不知道你见过没有某些请求一失败马上重试反而会造成雪崩间隔时间逐渐拉长比如1秒、2秒、4秒后端通常能喘得过气来。5. 一个真实场景登录态失效与Token刷新的处理前面讲的都是技术细节现在把它们串起来看一个最常见的实战场景——登录态失效。这个场景几乎每个项目都有处理得好不好直接关系到用户体验。5.1 请求拦截器与响应拦截器的配合演出流程是这样的用户在页面上操作点击某个按钮前端带着token发请求如果token过期了后端返回401。这时候前端应该怎么做最朴素的方案是直接跳登录页让用户重新登录。但如果用户正在填写一个很长的表单跳转登录页意味着他要重新填一遍体验很差。更好的做法是静默刷新token拿到新token后把刚才失败的请求重新发一次用户完全无感知。实现思路大致是响应拦截器里遇到401先判断是否已经在刷新token如果没有就调用刷新接口拿新token把当前失败的请求先暂存起来等待token刷新完成后重放所有暂存的请求。let isRefreshing false; let pendingQueue []; service.interceptors.response.use( (response) response, async (error) { const { response, config } error; // 只有401且不是刷新接口自身时才走刷新逻辑 if (response response.status 401 !config._retry) { if (isRefreshing) { // 已经在刷新把请求放入队列 return new Promise((resolve) { pendingQueue.push({ resolve, config }); }); } isRefreshing true; try { const newToken await refreshToken(); localStorage.setItem(token, newToken); // 重放队列中的请求 pendingQueue.forEach(({ resolve, config: savedConfig }) { savedConfig.headers.Authorization Bearer ${newToken}; resolve(service(savedConfig)); }); pendingQueue []; // 重放当前请求 config._retry true; config.headers.Authorization Bearer ${newToken}; return service(config); } catch (refreshError) { // 刷新失败跳转登录页 redirectToLogin(); return Promise.reject(refreshError); } finally { isRefreshing false; } } return Promise.reject(error); } );这里最关键的地方在于isRefreshing这个标志位。如果不加这个标志位并发多个请求同时遇到401就会同时触发多次刷新token的接口调用不仅浪费还可能因为老token同时被多个刷新接口处理导致刷新失败。加了这个标志和队列就能保证同一个小周期内只有一个刷新请求在飞其他请求都在排队等。5.2 Loading状态与错误提示的全局控制除了Token响应拦截器里另一个常见的活是全局Loading控制和错误提示。虽然现在很多团队用React Query、SWR这类数据请求库帮你自动管理了缓存、加载状态和错误状态但你依然可以做一个全局的请求加载指示器。最简单的做法是在请求拦截器里记录当前待处理请求数当请求数为1时显示全局loading在响应拦截器里递减归零时关闭loading。错误提示的全局控制则更精细。要分情况有些错误是全局性的比如网络断连可以弹全局提示有些错误是局部的比如某个接口的校验失败页面需要单独处理。我的习惯是请求封装层只处理全局性错误局部错误通过reject抛给调用方自行决定。5.3 上传与下载的特殊处理如果你处理的不是普通JSON接口还有上传下载那网络请求的知识点又得加一层。上传文件时一定要设置Content-Type为multipart/form-data而且不要自己手动设置Content-Type交给浏览器自动带上boundary否则后端解析不出文件内容。// 不要手动设置 Content-Type让浏览器自动处理 const formData new FormData(); formData.append(file, fileInput.files[0]); formData.append(remark, 项目文件); const res await fetch(/api/upload, { method: POST, // 注意不要写 headers: {Content-Type: multipart/form-data} body: formData });下载文件也有讲究。如果你的接口返回的是文件流用response.blob()拿到二进制数据然后生成一个临时的对象URL创建a标签模拟点击下载。async function downloadFile(fileId) { const response await fetch(/api/file/${fileId}/download); const blob await response.blob(); const url URL.createObjectURL(blob); const link document.createElement(a); link.href url; link.download 文件名.pdf; // 或从响应头里取filename link.click(); // 用完记得释放内存 URL.revokeObjectURL(url); }6. 网络请求调试与性能优化动手前先看这些这一节更多是讲实践中的排查思路和调优手段。很多同学接手一个项目发现某个接口加载很慢第一反应就是后端太慢了。但实际排查下来原因可能是前端发起的请求头太重也可能是没有用缓存甚至是被浏览器排队机制卡住了。6.1 浏览器Network面板的正确打开方式Network面板你肯定打开过但里面的信息是否都看懂了那是另一回事。排查网络问题我一般按这个顺序来看请求是否发出有没有在Network里看请求耗时分布TTFB、Content Download哪个阶段耗时高看请求头、响应头有没有预期中的缓存字段、CORS头等看响应体内容后端到底返回了什么。几个常用概念值得记一下TTFBTime To First Byte从发起请求到接收到响应第一个字节的时间。这个时间包含网络往返和后端处理时间。如果TTFB很高多半是后端慢或者网络链路长如果TTFB很低但Content Download很高那就是响应体太大。请求队列Queued at如果排队时间很长说明同域名下的并发连接数被浏览器限制了。HTTP/1.1下浏览器对同一域名的并发连接数一般是6个左右超出部分会排队。这种时候考虑域名分片或者升级HTTP/2能缓解。6.2 缓存策略也不是所有请求都要走到后端前端能做的一个非常大的性能优化是合理利用HTTP缓存。先分清两个基础概念强缓存和协商缓存。强缓存浏览器判断缓存没过期直接使用本地副本不发请求到服务器。看响应头里的Cache-Control和Expires。协商缓存浏览器会问一下服务器这个资源是否更新了如果服务器说没更新返回304也省去了下载响应体的时间。看Last-Modified和ETag。普通接口一般不会做太长的强缓存因为数据可能随时变。但某些相对静态的资源比如图片验证码几分钟内有效、用户头像、版本更新不频繁的配置数据是可以合理设置缓存的。这个决策要和后端一起商量不能让某一方单独拍板。前端侧的代码层面上还有一种缓存是内存缓存。比如同一个详情页用户一天之内反复打开每次都重新请求接口其实挺浪费的。简单的实现可以是一个带过期时间的Mapconst cache new Map(); async function fetchWithCache(url, ttl 5 * 60 * 1000) { const cached cache.get(url); if (cached Date.now() - cached.timestamp ttl) { return cached.data; } const data await fetch(url).then(r r.json()); cache.set(url, { data, timestamp: Date.now() }); return data; }这个cache是有局限性的它不能处理某用户修改了数据后另一个用户看到的还是旧数据的场景。所以只在低频变更的接口上使用比如字典数据、省市区列表这类的。6.3 请求体积与并发数的控制最后一个优化点是请求本身能不能瘦身。一个常见的场景是详情页需要调5个接口如果你能把它们合并成一个批量接口当然是最优解如果后端不愿意改那前端可以做的是把首屏必需的请求优先发出非必需的接口等页面加载完成后再发使用React的startTransition或Vue的调度机制把低优先级请求延后对小图标、小图片用Base64内联或者雪碧图减少请求次数。另外如果你在做性能优化可以打开Chrome的Lighthouse看它的Network Diagnostics部分。它经常会提示你Minimize main-thread work或者Reduce server response time这些都是优化方向的具体引导。我自己排查性能问题时最常做的动作是 先把Network面板按耗时排序看最慢的几个请求分别是什么 再逐个看它们的瓶颈在TTFB还是Content Download 如果TTFB高去找后端确认有没有慢查询或者需不需要加缓存 如果是响应体本身大前端考虑压缩或者按需加载字段。这套流程虽然朴素但绝大多数接口慢的问题都能定位到大致方向避免毫无头绪地瞎调。网络请求这块说实话知识点分散又细碎但用到的频率极高。从最初的XMLHttpRequest到现在的fetch、axios、React Query工具在变底层逻辑却没怎么变怎么把请求安全、高效、稳定地发出去再把响应正确、适时地拿回来。希望这篇偏实战的梳理能帮你少踩一些我已经踩过的坑。如果后面有时间我打算再整理一篇关于请求日志与埋点的实践那个覆盖面又会不太一样。

相关新闻

前端AI对话Demo搭建:五分钟跑通浏览器聊天应用

前端AI对话Demo搭建:五分钟跑通浏览器聊天应用

最近好几个做前端的朋友都在问同一个问题:怎么让页面里那个聊天框真正“开口说话”?问的人多了,我就把反复演示过的一套最小方案整理成了这篇文章。你可以把它理解成前端版的 Hello World,只不过这次要打招呼的对象是 AI——一个五…

2026/10/18 19:41:29 阅读更多 →
Flutter组件适配鸿蒙:MongoDB连接池与数据一致性治理实战

Flutter组件适配鸿蒙:MongoDB连接池与数据一致性治理实战

把 Android 和 iOS 上跑得稳稳的 Flutter 组件搬到鸿蒙 HarmonyOS 上,很多人下意识觉得“也就是重编一下的事”。可真动手你才会发现,平台差异带来的坑远比想象中多,尤其是涉及到数据库连接管理这种偏底层的组件,稍不留神就是线上…

2026/10/18 19:41:29 阅读更多 →
基于python-pptx与BM25的课件结构化与本地检索实践

基于python-pptx与BM25的课件结构化与本地检索实践

简介:这份《人工智能简述》课件面向计算机专业学生、AI入门学习者及需要课堂讲授素材的教师,系统梳理人工智能从概念定义到行业落地的完整知识框架,帮助读者在有限时间内建立对AI领域的整体认知。资源为单个PPT文件,压缩包约13.74…

2026/10/18 19:41:29 阅读更多 →

最新新闻

OSPF多区域实战:LSA类型、ABR机制与特殊区域配置

OSPF多区域实战:LSA类型、ABR机制与特殊区域配置

很多人学OSPF时,单区域配置玩得很溜,一到多区就懵了:明明就是两条network命令的事,为什么还要分骨干区、特殊区,甚至搞出一堆LSA类型?更麻烦的是,多区之后再看路由表,你会发现区域间…

2026/10/18 20:43:12 阅读更多 →
Ruby on Rails 机票电商项目实战:搜索、下单、支付回调全链路拆解

Ruby on Rails 机票电商项目实战:搜索、下单、支付回调全链路拆解

简介:eyoo:旅游机票电商平台(重构版)是一份基于Ruby技术栈的在线机票销售系统源码,面向具备一定Web开发基础、希望深入理解电商平台架构的开发者与学习者。项目围绕Ruby语言与Rails框架展开,涵盖MVC分层、ActiveRecord…

2026/10/18 20:43:12 阅读更多 →
Cursor与Claude Code Skills实战:8类技能提升AI编程效率

Cursor与Claude Code Skills实战:8类技能提升AI编程效率

过去半年,我把 Cursor 和 Claude Code 当成了日常开发的主力编辑器,基本告别了“自己写一遍再让 AI 重写一遍”的低效循环。真正让这两个工具从“聊天机器人”变成“团队协作者”的,不是模型版本升级,而是 Skills。这个词最近火得…

2026/10/18 20:43:12 阅读更多 →
JSP+SSM医院住院系统:真实临床业务闭环实现

JSP+SSM医院住院系统:真实临床业务闭环实现

简介:这是一套面向计算机专业本科生的Java Web毕业设计实战项目,聚焦医院住院管理业务场景,适用于课程设计、毕设选题与SSM框架入门实践。资源完整包含可运行源码、功能演示视频及详细说明文档,帮助学习者掌握B/S架构下医疗信息系…

2026/10/18 20:43:12 阅读更多 →
Android Compose AI组件库:从流式文本到图像生成的架构实践

Android Compose AI组件库:从流式文本到图像生成的架构实践

最近在忙一个跨平台的智能应用项目,AI能力要在Android端做完整的对话、图片生成、语义搜索体验。搭到第三块页面的时候我就开始烦了:每次都是把网络流、状态管理、UI拼装从头粘一遍,代码长得几乎一样,效果却总有细微出入。后来看到…

2026/10/18 20:43:12 阅读更多 →
MCP Client 与 LLM 协作实战:工具调用与上下文管理全解析

MCP Client 与 LLM 协作实战:工具调用与上下文管理全解析

1. 从项目背景说起:MCP 到底解决了什么问题说到 MCP(Model Context Protocol,模型上下文协议),我先讲一个痛点场景。假设你手里有 5 个 AI 应用,想给每个应用接上 3 类外部能力:数据库查询、文件…

2026/10/18 20:42:12 阅读更多 →

日新闻

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

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

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

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

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

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

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

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

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

2026/10/18 0:00:55 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 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/18 0:55:02 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →