news 2026/5/9 19:22:51

从付费软件到自主开发:我用AI和FFmpeg实现了一个录屏工具杖

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从付费软件到自主开发:我用AI和FFmpeg实现了一个录屏工具杖

我为什么会发出这个疑问呢?是因为我研究Web开发中的一个问题时,HTTP请求体在 Filter(过滤器)处被读取了之后,在 Controller(控制层)就读不到值了,使用 @RequestBody 的时候。

无论是字节流(InputStream / OutputStream)还是字符流(Reader / Writer),所有基于流的读取操作都会维护一个 "位置指针"。

初始状态下,指针指向流的起始位置(position = 0);

每次调用 read() / read(byte[]) / read(char[]) 等读取方法时,指针会向后移动对应字节数;

当指针移动到流的末尾(没有更多数据),read() 方法会返回 -1,表示流读取完毕;

指针移动后不会自动回退,也无法反向移动(除非流显式支持重置),因此再次读取只能得到 -1。

类比:IO 流的读取过程,就像用 磁带播放器听磁带 —— 磁头(对应流的位置指针)从磁带开头(指针 0)开始移动,每读一个字节 / 字符,磁头就往后走一步;当磁头走到磁带末尾,再继续播放(读取)就只能听到 "沙沙声"(流返回 -1),并且磁头不会自动回到开头。

当然,不是所有流都只能读一次,基于内存的流(如 ByteArrayInputStream / CharArrayReader)支持重置指针,因为它们的数据源是内存中的数组(数据不会消失),可以通过 mark() 和 reset() 方法将指针 恢复 到标记位置。

需要注意:

调用 reset() 前必须先调用 mark(int readlimit);

不是所有流都支持 mark() / reset(),可以通过 inputStream.markSupported() 来进行判断。

使用 mark() 和 reset() 方法:

// 仅适用于支持mark的流

public void processWithMark(InputStream input) throws IOException {

if (!input.markSupported()) {

throw new IOException("Mark not supported");

}

// 标记当前位置,参数100表示最多可回退100字节

input.mark(100);

// 第一次读取

byte[] firstRead = new byte[50];

input.read(firstRead);

System.out.println("First read: " + new String(firstRead));

// 重置到标记位置

input.reset();

// 第二次读取(相同内容)

byte[] secondRead = new byte[50];

input.read(secondRead);

System.out.println("Second read: " + new String(secondRead));

}

使用 包装类 解决上文我们提到的 HTTP请求体多次读取 的问题:

public class MyRequestWrapper extends HttpServletRequestWrapper {

private final byte[] body; // 缓存请求体的字节数组

public MyRequestWrapper(HttpServletRequest request) throws IOException {

super(request);

// 关键步骤:在构造时一次性读取并存储原始请求流

body = StreamUtils.copyToByteArray(request.getInputStream());

}

// 提供一个便捷方法,用于在过滤器中获取请求体内容(例如记录日志)

// 使用时,直接调用 getBodyString() 即可

public String getBodyString() throws UnsupportedEncodingException {

return new String(body, this.getCharacterEncoding());

}

@Override

public ServletInputStream getInputStream() throws IOException {

// 每次调用都返回一个基于缓存数据的新流

ByteArrayInputStream bais = new ByteArrayInputStream(body);

return new ServletInputStream() {

@Override

public int read() {

return bais.read();

}

@Override

public boolean isFinished() {

return bais.available() == 0;

}

@Override

public boolean isReady() {

return true;

}

@Override

public void setReadListener(ReadListener readListener) {

// 无需实现

}

};

}

@Override

public BufferedReader getReader() throws IOException {

return new BufferedReader(new InputStreamReader(this.getInputStream(), this.getCharacterEncoding()));

}

}

然后在 过滤器 处包装请求:

@Slf4j

@Configuration

public class RequestCachingFilterConfig {

@Bean

public FilterRegistrationBean requestCachingFilter() {

FilterRegistrationBean registrationBean = new FilterRegistrationBean();

// 核心:创建过滤器,包装请求为 ContentCachingRequestWrapper

registrationBean.setFilter(new OncePerRequestFilter() {

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)

throws ServletException, IOException {

// 1. 仅包装 HTTP 请求(排除 WebSocket 等)

if (request instanceof HttpServletRequest && !(request instanceof ContentCachingRequestWrapper)) {

log.info("==========进入requestCachingFilter========");

// 2. 包装请求(自动缓存请求体)

MyRequestWrapper wrappedRequest = new MyRequestWrapper(request);

filterChain.doFilter(wrappedRequest, response); // 传递包装后的请求

} else {

filterChain.doFilter(request, response); // 无需包装,直接放行

}

}

});

// 3. 配置拦截所有请求(可根据需求调整 URL 模式)

registrationBean.addUrlPatterns("/*");

registrationBean.setOrder(1); // 优先级最高,确保先于其他过滤器执行

registrationBean.setName("requestCachingFilter");

return registrationBean;

}

}

IO 流只能读取一次,是 精心设计的,贴合操作系统文件 / 网络 IO 的 "顺序消费" 特性,保持和底层系统的一致性。月衔炮锌

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

雪女-斗罗大陆-造相Z-Turbo体验报告:一键生成,效果超乎想象

雪女-斗罗大陆-造相Z-Turbo体验报告:一键生成,效果超乎想象 1. 镜像简介与快速体验 1.1 模型核心能力 雪女-斗罗大陆-造相Z-Turbo是基于Z-Image-Turbo的LoRA版本专门优化的人物生成模型,专注于呈现斗罗大陆中"雪女"这一经典角色…

作者头像 李华
网站建设 2026/4/15 16:03:06

2025—2030年全球CRM系统市场研究与趋势展望

在技术领域,我们常常被那些闪耀的、可见的成果所吸引。今天,这个焦点无疑是大语言模型技术。它们的流畅对话、惊人的创造力,让我们得以一窥未来的轮廓。然而,作为在企业一线构建、部署和维护复杂系统的实践者,我们深知…

作者头像 李华
网站建设 2026/4/14 1:36:50

【Blazor 2026架构权威白皮书】:首发全球仅3家微软MVP联合验证的7层现代Web架构图(含WASM+Hybrid双模部署拓扑)

第一章:Blazor 2026架构演进全景与白皮书发布背景Blazor 2026代表了微软在Web前端框架领域的一次范式跃迁,其核心目标是弥合客户端交互性、服务端可扩展性与开发体验一致性之间的长期鸿沟。本次演进并非简单功能叠加,而是围绕“统一渲染生命周…

作者头像 李华
网站建设 2026/4/17 9:04:51

Flutter 动画控制器:打造流畅的动画体验

Flutter 动画控制器:打造流畅的动画体验掌握 Flutter 动画控制器的高级技巧,创造流畅而优雅的动画效果。一、动画控制器概述 作为一名把代码当散文写的 UI 匠人,我对 Flutter 动画控制器有着独特的见解。动画控制器是 Flutter 动画系统的核心…

作者头像 李华
网站建设 2026/4/13 16:33:15

【紧急预警】GraalVM 24.0.2起默认启用ZGC导致静态镜像OOM?权威解决方案:3种GC策略对比+JDK21-LTS兼容性矩阵(含生产环境压测数据)

第一章:Java GraalVM 静态镜像内存优化 插件下载与安装 GraalVM 的 Native Image 功能可将 Java 应用编译为独立、启动极快的静态可执行文件,但默认构建的镜像常存在堆内存预留过大、元空间冗余、反射资源未精简等问题。内存优化需依托官方推荐的 native…

作者头像 李华
网站建设 2026/4/14 1:06:58

高效光伏电池建模技术分享:Boost Buck电路实现最大功率追踪

光伏电池PV建模,基于Boost/Buck电路实现最大功率追踪MPPT,包括扰动观察法,电导增量法,改进型电导增量法,滑模变结构法等控制算法,模型仿真效果较好,适合借鉴学习。 图片为模型图,功率…

作者头像 李华