当前位置: 首页 > news >正文

从付费软件到自主开发:我用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 的 "顺序消费" 特性,保持和底层系统的一致性。焦锌矫臼

http://www.jsqmd.com/news/632463/

相关文章:

  • 2026年口碑好的不锈钢网片长方形/河北不锈钢网片/304不锈钢网片优质供应商推荐参考 - 品牌宣传支持者
  • Step3-VL-10B-Base在计算机组成原理中的应用:硬件模拟与优化
  • 2026年口碑好的公交站台/城市公交站台候车亭/仿古公交站台实力品牌厂家推荐 - 品牌宣传支持者
  • Go语言的sync.Cond条件变量与通道关闭在广播通知中的语义差异
  • 2026年评价高的高空作业升降车/自行走升降车/工地升降车品牌厂家推荐 - 品牌宣传支持者
  • 2026年质量好的工具箱拉杆/户外设备拉杆/东莞工具箱拉杆/工具箱拉杆定制高评分品牌推荐(畅销) - 品牌宣传支持者
  • 2026年比较好的银川网站建设开发/银川电商网站建设/银川网站建设设计专业公司推荐 - 品牌宣传支持者
  • 从暴力枚举到高效剪枝:回溯法求解0-1背包的优化之路
  • 给客户端进行DHCP配置
  • 史上最有故事感的技术报告——Claude最强模型Mythos 7个极其精彩的细节
  • 技术决策中的信息收集与分析判断
  • Hazel游戏引擎结构分析
  • 2026年知名的珍珠棉内衬/高密度珍珠棉/珍珠棉/异型珍珠棉公司口碑推荐 - 行业平台推荐
  • 2026年知名的冷拌沥青混凝土/人行天桥冷拌沥青/坑槽修补冷拌沥青/冷拌沥青料厂家推荐 - 行业平台推荐
  • MiniCPM-V-2_6科研友好设计:RLAIF-V可信训练与本地化部署实践
  • 第11课:Multi-Agent 实战,并行编排的5种模式
  • Forced-BME280:面向MCU的轻量级BME280嵌入式驱动
  • WinClaw安全实战 02|五层纵深防护体系全解析:从原理到实操,打造零风险AI智能体
  • 【GPT-5时代生存指南】:为什么92%的企业微调失败?2026奇点大会首席科学家亲授4步精准对齐法
  • SenseVoice Small效果展示:方言保护项目——吴语/闽南语语音建档成果
  • 你的品牌在AI搜索中「隐身」了吗?一份5步GEO自检指南
  • 2026年质量好的哈尔滨交通设施反光路锥/哈尔滨交通设施百米桩警示柱厂家精选 - 品牌宣传支持者
  • 2026年写食品广告的委托广告语/广州委托广告语/策划广告语/优化广告语实力公司推荐 - 品牌宣传支持者
  • 人脸识别OOD模型实战教程:构建质量分驱动的主动学习闭环
  • 2026年热门的储能集装箱/集装箱驿站/集装箱办公室/移动房屋集装箱口碑好的厂家推荐 - 行业平台推荐
  • 2026年知名的异形不锈钢封头/椭圆形不锈钢封头/非标定制不锈钢封头/不锈钢封头公司推荐 - 品牌宣传支持者
  • 仅限前500名技术决策者获取:2026奇点大会《大模型工具调用成熟度评估矩阵》(含9维打分表+自测链接)
  • 用汇编和8254芯片让蜂鸣器唱歌:一个80年代微机实验的现代复刻(附完整代码)
  • Mac 误删文件别重装!保姆级恢复教程,废纸篓清空也能救回
  • 【2026奇点大会前瞻】:大模型视觉理解的5大技术断层与3个月落地攻坚指南