新闻详情

新闻详情

首页 / 资讯中心 / 详情

Spring Boot 请求体重复读取实战:过滤器与RequestWrapper解决RequestBody为null

发布时间:2026/9/10 10:03:10来源:尧图网络
Spring Boot 请求体重复读取实战:过滤器与RequestWrapper解决RequestBody为null
做后端时间长了一定会遇到一个让人抓狂的场景我想在过滤器里把请求体的 JSON 打出来看日志或者做一个统一的签名校验结果日志打完了Controller 里的 RequestBody 参数变成了 null。明明 Postman 里传得好好的一上代码就翻车。我第一次踩这个坑的时候排查了大半天最后才意识到问题出在 Servlet 规范本身——HttpServletRequest 的输入流只能读一次读完之后就跟倒空的矿泉水瓶一样再也倒不出水了。这篇博文的主题很明确如何让 Spring Boot 里的 RequestBody 支持“重复读取”并且不只是在 Demo 里能用而是能直接放进生产项目里的工程化实现。我会先讲清楚底层原理再把三种主流方案的优缺点掰开揉碎最后给出我目前在项目里实际使用的 Filter 自定义 RequestWrapper 完整代码连排坑经验一起打包。适合正在被这个坑折磨的后端开发以及想搞懂 Spring MVC 请求体处理链路的人。1. 为什么 RequestBody“读一次就没了”——先从底层原理说起1.1 Servlet 流模型InputStream 为什么不能回头在 Servlet 规范里请求体Request Body是通过一个 InputStream 暴露给开发者的只要是流就天然是单向、一次性消费的。这不是 Spring 的限制而是整个 Java Web 容器Tomcat、Jetty、Undertow 都一样的底层设计。我经常用一个水管来类比请求体是一条水流你拿一个杯子去接接完之后水就流走了。想再接第二杯对不起水管里的水已经流到下游了。HTTP 请求从客户端传来时本质是一个字节流服务器端为了性能和内存考虑不会把整个请求体在内存里存一份副本而是让应用边读边用。这样做的好处是能支撑大文件上传、大 body 传输代价就是流一旦读完就没办法回到开头重新读。Java 的 InputStream 本身也不是完全不能重读的比如 ByteArrayInputStream 就可以反复读但它要求数据已经完整地存在于内存字节数组里。Servlet 容器给我们的是 Socket 上来的网络流数据没被完整读出来之前服务器不知道后面还有多少字节也没有办法把数据“塞回去”所以只能设计成一次性。1.2 RequestBody 读取请求体的完整链路明白了流是一次性的接下来看 Spring MVC 是怎么用这个流的。你可能会觉得 RequestBody 是 Spring 直接帮我们从请求里取参数其实它的内部链路比想象中要长得多。一个典型的 JSON POST 请求进入 Spring Boot 应用后会走这样一条链路Tomcat/Servlet 容器 - FilterChain过滤器链 - DispatcherServlet前端控制器 - HandlerAdapterHandlerMethodArgumentResolver 解析参数 - AbstractMessageConverterMethodArgumentResolver - HttpMessageConverter消息转换器 - MappingJackson2HttpMessageConverter - ObjectMapper.readValue(inputStream, User.class)Spring MVC 在解析 RequestBody 时真正干活的组件是RequestResponseBodyMethodProcessor它会调用readWithMessageConverters()方法把请求体包装成一个HttpInputMessage。这个HttpInputMessage本质上就是对HttpServletRequest的一层抽象其中核心的一个方法是HttpInputMessage.getBody()对于 Web 请求来说getBody()最终返回的就是request.getInputStream()。也就是说Spring 本身并没有魔法它也是通过流来读取请求体的。如果这个流在你自己的过滤器里已经被读过了那么 Spring 再去读的时候流已经处于 EOF文件结束状态InputStream 读出来是 -1RequestBody 自然就解析失败或得到 null。还有一个隐藏坑Servlet 规范要求 getReader() 和 getInputStream() 只能调用其中一个一旦调用过其中任意一个再调用另一个就会抛IllegalStateException。这是因为同一个请求的 body stream 是被容器标记的不能同时以字符流和字节流两种方式消费。1.3 什么时候最容易踩中这个坑我总结了一下下面这几类场景几乎是必踩的全局日志打印想在 Filter 里打印所有请求的 body 内容结果日志有了业务接口参数全没了。AOP 参数审计想在方法执行前后记录请求参数在 AOP 里读了一次 body后面真正执行业务时参数缺失。签名/防重放校验在拦截器或过滤器里出于安全需要读取 body 计算签名或加密字段校验通过后放行业务接口读不到参数。网关或公共组件上游组件读了 body 之后传给下游 Filter下游再需要读时就没了。其实不只是你自己写的代码会读 body很多第三方框架也会读。比如 Spring Security 的 CSRF Token、OAuth2 资源服务器、某些 API 网关组件都可能在过滤器链的某一环消耗掉请求体。只要过滤器链里有任何一环读过 body且没有重新包装请求后面的环节都有概率拿不到 body。这就是为什么解决“可重复读取”不只是一个技巧而是一个工程化的必选项。2. 方案选型三种“可重复读”实现思路对比2.1 Spring 自带的 ContentCachingRequestWrapper最常用但坑也不少Spring Web 里提供了一个现成的ContentCachingRequestWrapper很多初学者一看名字就叫“能缓存请求”.实际上它确实能缓存但它并不是万能的我在项目里用它踩过几个大坑。它的工作机制是这样的当你通过 wrapper 的getInputStream()读取 body 时它会一边向业务代码返回数据一边把读过的内容复制一份到内部的ByteArrayOutputStream缓存中。到了请求处理完之后可以通过getContentAsByteArray()拿到缓存内容。问题来了这个 wrapper 不会自动预读 body。也就是说如果你在 Filter 里只是把原始 request 包装成ContentCachingRequestWrapper然后直接放行后面的 RequestBody 确实能正常读取因为数据流向没有改变但如果你想在 Filter 里先打印 body 再放行你手动调了一次getInputStream()并读完流就被消费了后面的 RequestBody 拿到空流。Spring 官方提供了一个AbstractRequestLoggingFilter它有个属性叫afterContentRead默认是 false。如果设置成 true它会在请求结束后从 wrapper 里拿缓存内容打日志但它不会主动读 body。要是你在自己的业务过滤器中硬读了一次 body再传下去就是坑。我在一个项目里曾经图省事用这个 wrapper 做请求日志结果发现 POST 请求被记录后 Controller 参数为空。仔细排查才发现RequestLoggingFilter为了打日志已经把 body 读完了后面的组件跟着遭殃。这个方案适合只做单向日志记录的场景不适合“又要读又要用”的双向场景。2.2 自定义 RequestWrapper 缓存全部 body最稳的工程化方案既然 Spring 自带的类不够用最直接的做法就是自己造一个轮子。思路很简单在请求进入过滤器时立刻把 body 读出来存成byte[]数组然后每次调用getInputStream()都返回一个基于这个byte[]的新的字节流。这样无论 RequestBody 读几次每次拿到的都是同一份数据。这个方案本质上是“一次拷贝多次使用”比 ContentCachingRequestWrapper 的“边读边记录”更彻底。它没有预读不预读的问题因为你知道 body 内容已经完整地存在内存里了后面任何组件随便读读多少遍都行。我目前所有需要日志、签名、审计功能的项目都是用这个方案来实现的。代码量不大但需要理解 Servlet API 的几个细节后面第 3 章会给出完整代码。2.3 另类路线自定义 HandlerMethodArgumentResolver 或装饰器模式除了 Filter 方案还有一些进阶思路。比如实现一个自定义的HandlerMethodArgumentResolver专门处理被注解标注的参数在解析时直接读取缓存的 body 并转换成对象。这种方式的好处是只在 Spring MVC 层生效不需要经过 Servlet 过滤器链性能更可控。但我不太推荐这个方案。第一实现复杂度较高你可能要与 Spring 的消息转换器体系做对抗处理各种 Content-Type第二它只能解决 Controller 层参数解析的问题不能解决过滤器链里其它组件的读取需求比如 Spring Security、日志过滤器等。装饰器模式Decorator Pattern在这里其实就是HttpServletRequestWrapper的思想。因为它内部持有一个原始 HttpServletRequest 实例默认把所有方法转发给这个实例所以你只需要覆写getInputStream()和getReader()这两个方法就能在不改动业务代码的情况下改变请求读取行为。这种方式也是把“可重复读取”和业务解耦的最好形式。2.4 选型表格对比方案优点缺点适用场景ContentCachingRequestWrapper零依赖、Spring 内置集成方便不会预读 body日志和业务可能相互影响对 form 表单支持一般仅做被动记录日志自定义 Filter RequestWrapper实现彻底、可重复读取无限次、兼容性好、逻辑完全可控需要自己写代码大 body 时内存会翻倍日志业务并行、签名校验、审计自定义 HandlerMethodArgumentResolver只影响 Spring MVC 层性能好实现复杂、无法覆盖 Servlet 层其它组件需求、侵入性强对性能极敏感的特定接口我个人的建议是如果是新项目直接采用自定义 Filter RequestWrapper 方案如果只是想临时给老项目加个日志可以先用 ContentCachingRequestWrapper 解燃眉之急但要明白它的边界。3. 工程化落地基于过滤器 自定义 Wrapper 的完整实现3.1 准备工作项目环境和基础假设我下面的代码基于 Spring Boot 2.x / 3.x核心依赖只有spring-boot-starter-web不需要任何额外引入。因为包装请求是 Servlet 层的功能和 Spring 版本没有强绑定Spring Boot 2.x 到 3.x 都能用。在设计之前先定几个约定只处理POST、PUT、PATCH这类请求体可能有内容的请求。GET 请求虽然理论上也能带 body但大多数场景下没人这么用我的过滤器不会去处理。Content-Type 是application/json、application/xml、text/plain等需要读 body 的场景。文件上传的multipart/form-data直接跳过因为 multipart 的解析机制不同不在本次缓存范围内。请求体大小如果超过 10MB放弃缓存直接使用原始请求流防止内存被大 body 打爆。3.2 自定义 RepeatableReadRequestWrapper 完整代码先看核心类我要实现一个继承了HttpServletRequestWrapper的类在构造时读取一次原始 body之后所有读取操作都在缓存副本上进行。import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import org.springframework.util.StreamUtils; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; public class RepeatableReadRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public RepeatableReadRequestWrapper(HttpServletRequest request) throws IOException { super(request); // 构造时就把原始请求体完整读取并缓存到内存中 this.body StreamUtils.copyToByteArray(request.getInputStream()); } Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream new ByteArrayInputStream(body); return new ServletInputStream() { Override public boolean isFinished() { return byteArrayInputStream.available() 0; } Override public boolean isReady() { return true; } Override public void setReadListener(ReadListener readListener) { // 同步读取场景下一般不需要异步监听 throw new UnsupportedOperationException(setReadListener is not supported); } Override public int read() { return byteArrayInputStream.read(); } Override public int read(byte[] b, int off, int len) { return byteArrayInputStream.read(b, off, len); } }; } Override public BufferedReader getReader() throws IOException { Charset charset StandardCharsets.UTF_8; String encoding getCharacterEncoding(); if (encoding ! null !encoding.isEmpty()) { charset Charset.forName(encoding); } return new BufferedReader(new InputStreamReader(getInputStream(), charset)); } /** * 暴露一个方法让外部代码直接拿原始 body 字节数组。 */ public byte[] getBodyBytes() { return this.body; } }有几点要解释一下StreamUtils.copyToByteArray()是 Spring 提供的工具方法会把输入流完整读成字节数组省得自己写循环。如果没有 Spring也可以自己用 ByteArrayOutputStream 加 byte[] 缓冲来读效果一样。重写getInputStream()时我每次返回的都是一个新的ByteArrayInputStream。这意味着每次调用getInputStream()都会从缓存副本的起始位置开始读这是实现“可重复读取”的关键。另外ServletInputStream是抽象类必须实现isFinished()、isReady()、setReadListener()和read()。其中isFinished()用于判断流是否读完isReady()表示是否可读同步场景下直接返回 true。异步场景如果你不需要setReadListener 抛异常即可大部分 Web 场景用不到。getReader()的字符编码处理是个容易被忽略的细节。如果请求头里没带 Content-Type 的 charsetrequest.getCharacterEncoding()可能返回 null直接使用无参的InputStreamReader会导致编码不确定。所以我这里显式判断默认使用 UTF-8减少中文乱码的坑。3.3 过滤器注册与顺序调整光有 Wrapper 不够还需要一个过滤器来把原始请求替换成可重复读的包装请求。import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; Component Order(Ordered.HIGHEST_PRECEDENCE 10) public class RepeatableReadFilter extends OncePerRequestFilter { /** * 超过 10MB 的请求体不再缓存避免内存压力 */ private static final long MAX_CACHE_BODY_SIZE 10 * 1024 * 1024L; Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 只处理有 body 的请求方法 boolean hasBody HttpMethod.POST.matches(request.getMethod()) || HttpMethod.PUT.matches(request.getMethod()) || HttpMethod.PATCH.matches(request.getMethod()); if (hasBody request.getContentLengthLong() MAX_CACHE_BODY_SIZE) { filterChain.doFilter(new RepeatableReadRequestWrapper(request), response); } else { // GET、DELETE、上传文件等场景直接放行原请求 filterChain.doFilter(request, response); } } }这里我用了 Spring 的OncePerRequestFilter而不是原生的Filter接口原因是OncePerRequestFilter能保证一次请求只被过滤一次。你可能会问Feign 内部转发、Servlet 容器内部 dispatch 时原始 Filter 可能会执行两次导致 Wrapper 被重复包装。实际上重复包装并不会太致命但会白白多读一次 body性能损耗不划算。所以优先用 OncePerRequestFilter。Order注解用来控制过滤器执行顺序。这里我设置为HIGHEST_PRECEDENCE 10在一个非常靠前的位置执行确保它在业务代码、Spring Security、AOP 之前就已经把 body 缓存好了。如果你项目里用了 Spring Security这个顺序需要注意因为 Security 的过滤器链默认会包一层HttpServlet请求理论上你在这个优先级下能把请求放心地传给 Security。还有一个细节Content-Length可能为 -1比如 chunked 传输所以request.getContentLengthLong() MAX_CACHE_BODY_SIZE时-1 也会进入缓存分支。对于 chunked 请求这个判断不完全准确但一般请求 body 都不大问题不大。如果你的接口就是纯大文件建议再多判断一层 Content-Type 是不是 multipart。3.4 日志打印、签名校验等真实场景用法有了上面的 Wrapper业务代码就变得很自由。看下面两个典型场景。场景 1在过滤器里打印请求日志又不想影响后面的 RequestBodyOverride protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { RepeatableReadRequestWrapper requestWrapper new RepeatableReadRequestWrapper(request); // 读取 body 并打印这里只是示例可以考虑异步输出到日志系统 String body new String(requestWrapper.getBodyBytes(), StandardCharsets.UTF_8); System.out.println( 请求body: body); // 注意这里传下去的是 requestWrapper而不是原始 request filterChain.doFilter(requestWrapper, response); }关键点在于过滤器里读取 body 使用的是 wrapper 的缓存副本即使打印完之后wrapper 依然保有完整 body。后续链路上的 RequestBody 调用request.getInputStream()时由于传入的是 wrapper它拿到的还是同一份缓存副本。如果这里传下去的是原始 request那后面的 RequestBody 还是会读到空。场景 2签名校验 防重放校验我在做开放 API 网关时需要在过滤器里读取 body 和某个签名头做 HMAC 校验校验通过后还要让 Controller 继续接参。boolean valid verifySignature(requestWrapper.getBodyBytes(), request.getHeader(X-Signature)); if (!valid) { response.sendError(403, invalid signature); return; } filterChain.doFilter(requestWrapper, response);这种方式最大的价值是校验逻辑和业务逻辑共用了同一份 body而不是校验的时候读一次、业务执行的时候又得读一次。对于整个请求链路来说只需在过滤器一开始做一次字节拷贝后面都是内存操作性能开销完全可以接受。3.5 一个常见但容易忽略的细节JSON 日志格式化如果你只是想记录 JSON 格式的请求体建议不要直接new String(bodyBytes)就完事最好先用 Jackson 把它转成一个 JsonNode 再格式化输出。这样避免中文乱码显示不完整、也便于日志系统结构化存储。比如ObjectMapper mapper new ObjectMapper(); JsonNode node mapper.readTree(bodyBytes); String prettyJson mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);这里有个细节我们在过滤器里已经取到了 body 字节数组不管后面业务是否解析成功过滤器的日志都不会被影响。如果业务解析 JSON 失败至少我们能从日志里看到原始请求内容这是排查问题的利器。4. 常见问题与排查技巧实录4.1 RequestBody 读到的 body 为空两个典型的“灵异事件”第一个灵异事件用了 ContentCachingRequestWrapper 之后RequestBody 还是 null。原因我前面分析过ContentCachingRequestWrapper 并不会预读 body它只是在读取时记录。如果你在过滤器里先调用了getInputStream()去读数据比如为了打印日志原始流转到 RequestBody 时已经 EOF。不是 wrapper 的问题是你对它的预期不对。第二个灵异事件自定义 wrapper 后Controller 明明拿到了数据但到了某个第三方过滤器中又读不到。这是因为过滤器链里某个组件自己又从原始 request 里取了一次流而你这个组件没把 wrapper 传下去。解决办法很朴素一旦在链路起始处包装了请求后面的所有传递都要用包装后的对象不要让任何中间环节把 request 重新替换成原始对象。排查技巧在过滤器入口和业务入口各打一行日志输出同一个 request 对象的哈希码或者输出request.getClass().getName()就能快速判断传入业务层的到底是不是你包装后的类。这个方法治好了我无数次的“怎么又没了”。4.2 过滤器和请求参数解析器的顺序冲突如果你在项目里同时启用了自定义 Filter 和 Spring MVC 的拦截器Interceptor要注意时序。Filter 是 Servlet 层面的Interceptor 是 Spring MVC 层面的Filter 执行时机一定在 Interceptor 之前。也就是说只要你的 Filter 正确包装了 requestInterceptor 里再去读 request 就是可重复读的。但如果你在 Filter 里读了一次 body 之后放行时传的还是原始 requestInterceptor 和 Controller 都会受影响。所以一条铁律包装后的 wrapper 对象要从触发它的那个 Filter 开始一直向下传递直到请求结束。4.3 GET/POST 混合场景、文件上传时不要做缓存GET 请求不带 body所以过滤器的getContentLengthLong()可能是 -1 或者 0这时候去缓存 body 会得到一个空数组没有意义。文件上传走的是multipart/form-databody 里不只是 JSON还有二进制数据和 boundary 分隔符。如果把这个 body 整个缓存下来再传给 Spring 的 multipart 解析器很容易出问题因为 multipart 解析器可能会直接读取原始请求的输入流或临时文件。所以我在设计时加了一层判断Content-Type 包含multipart/form-data就跳过缓存使用原始 request。另外如果 body 超过 10MB为了保险也跳过缓存。4.4 性能与内存优化建议自定义 wrapper 的本质是用内存换可重复读取所以有一个潜在的 OOM 风险。如果一个请求的 body 有 500MB你的应用要把它整段复制到内存明显不现实。有三条优化建议设置合理的 body 大小上限超过限制直接返回 413 或交给原始流处理。如果项目里的请求普遍是几百 KB 级别完全不用担心内存一次字节数组拷贝的成本毫秒级都不到。如果确实需要缓存超大 body考虑把数据先落盘或者放到本地缓存不要全放堆内存。另外注意线程安全问题。每个请求都会 new 一个 RepeatableReadRequestWrapper这个 wrapper 只会被当前请求的线程使用不会跨线程共享所以不需要考虑并发访问问题。但如果你在 wrapper 里加了新的字段也要注意别把它设计成静态或全局共享状态。4.5 常见的其它坑编码、压缩、请求体为空编码客户端如果发送Content-Type: application/json; charsetGBK而你服务端默认用 UTF-8 去解码中文就会乱码。我在 getReader() 里根据 getCharacterEncoding() 动态设置编码正是为了规避这一点。压缩如果客户端使用Content-Encoding: gzip发送 body你的过滤器拿到的 inputStream 读出来的还是压缩后的字节直接缓存下来后面 RequestBody 也解不了压。这种情况需要在过滤器里先做解压再缓存。不过绝大多数后端服务都不会直接接收 gzip 的 body我在这里提醒一下真遇到的时候不要懵。请求体为空有些 POST 请求的 body 为空字符串这个时候copyToByteArray会得到一个空数组RequestBody 的解析会失败。通常这类请求的 Content-Type 可能并不匹配建议在业务层做兼容也可以在过滤器里判断 body 长度空内容直接放行原请求。4.6 工程化落地后的调试建议最后给一个调试套路用浏览器或 Postman 测试时请求体打印为空不要一上来就改代码。先打开spring-boot-starter-logging的 debug 日志看Spring MVC HandlerMapping这层有没有加载到你的过滤器。然后在过滤器的 doFilterInternal 第一行输出原始请求的 URI、Method、Content-Type再输出包装后对象的getBodyBytes()长度。只要长度和 Postman 里的一致后面基本不会有大问题。我自己的使用习惯是过滤器里只做 body 缓存业务逻辑需要 body 的时候统一通过request.getAttribute(requestBodyCache)或者直接强转RepeatableReadRequestWrapper来获取这样不会把日志、验签代码全部堆在过滤器里后期维护也轻松。结尾一点个人经验和最后的建议这个“RequestBody 重复读取”的需求看起来是个小功能但真正把它做好、做对要对 Servlet 规范、Spring MVC 的消息转换链路、请求包装机制都有一定的了解。我在从 ContentCachingRequestWrapper 转向自定义 wrapper 的过程中踩过不少坑最后总结出一个结论如果只是记录日志Spring 自带的缓存包装器能用如果是日志、验签、审计、业务参数解析共存一定要自定义 Filter Wrapper 的组合并且保证从过滤器入口开始整个链路都用包装后的请求对象。最后再分享一个小技巧包装器的缓存逻辑其实也能抽出复用我一般会做两个工具方法一个负责判断请求是否需要缓存Content-Type 白名单、大小限制一个负责把原始请求包装成可重复读的 wrapper。这样在多个项目里只需要复制一个类加一个过滤器几行配置就能生效不必每次重复造轮子。如果你也在维护一些公共服务或者基础组件值得提前把这件事沉淀下来。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

expo-notifications 如何绕过 Expo 推送服务直接用 FCM 和 APNs 发送通知 2026/9/10 10:42:17

expo-notifications 如何绕过 Expo 推送服务直接用 FCM 和 APNs 发送通知

expo-notifications 如何绕过 Expo 推送服务直接用 FCM 和 APNs 发送通知 【免费下载链接】expo An open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web. 项目地址: https://gitcode.com/GitHub_Trending/ex/expo …

阅读更多 →
ruflo-cost-tracker 成本燃烧率观测:用 `cost-burn` 追踪生产环境的日烧钱速率与漂移告警 2026/9/10 10:42:17

ruflo-cost-tracker 成本燃烧率观测:用 `cost-burn` 追踪生产环境的日烧钱速率与漂移告警

ruflo-cost-tracker 成本燃烧率观测:用 cost-burn 追踪生产环境的日烧钱速率与漂移告警 【免费下载链接】ruflo 🌊 The original agent meta-harness. Deploy intelligent multi-player swarms, coordinate autonomous workflows, and build conversatio…

阅读更多 →
管理软件化时代:为什么年轻人和资深CIO站在同一起跑线? 2026/9/10 10:42:17

管理软件化时代:为什么年轻人和资深CIO站在同一起跑线?

1. 管理模式软件化:从"人治经验"到"系统沉淀"的迁移这几年我明显感受到一个趋势:管理这件事,正在从"脑袋里的经验"变成"软件里的流程"。过去我们聊企业数字化,聊的往往是业务系统的上线—…

阅读更多 →
Spark调用大模型实战:mapPartitions并发控制与工程化落地 2026/9/10 10:42:17

Spark调用大模型实战:mapPartitions并发控制与工程化落地

如果你在数据团队里待过一阵子,大概率会遇到这种需求:给几百万条文本打标签、抽取实体、做情感分析,或者把用户聊天记录批量生成模型训练样本。业务方开口就是“用大模型跑一下就行”,但真到动手阶段才发现,把大模型接…

阅读更多 →
MarkItDown实战:用Python将PDF/Office批量转Markdown,高效对接LLM与RAG 2026/9/10 10:42:17

MarkItDown实战:用Python将PDF/Office批量转Markdown,高效对接LLM与RAG

做AI项目这几年,我最大的感受是:模型选型、Prompt调优这些事反而是最不占时间的,真正磨人的是把各种格式的资料喂给模型之前那一段“预处理”。领导甩来一个几十页的PDF培训材料,同事发来一个满是透视表的Excel,客户那…

阅读更多 →
CVAT 国际化完全指南:3 个 i18n 入口与语言包配置一次讲清 2026/9/10 10:39:16

CVAT 国际化完全指南:3 个 i18n 入口与语言包配置一次讲清

CVAT 国际化完全指南:3 个 i18n 入口与语言包配置一次讲清 【免费下载链接】cvat Computer Vision Annotation Tool (CVAT) is a leading platform for building high-quality visual datasets for vision AI. It offers open-source, cloud, and enterprise produc…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞