一、核心需求
- 功能需求:用户可以通过语音与AI对话,并实现类似ChatGPT的实时交互(流式响应,打字机效果)
- 技术需求:在现有微服务架构中进行扩展(SpringCloud)
二、技术盲点
- 陌生领域
- 语音录入
- 语音流传输
- Java对接大模型API
- 技术选型难点
- WebSocket框架选择
- Spring WebSocket、t-io、Java-WebSocket、Socket.io等
- 流式编程 or 响应式编程 (本质是分块传输)
- Rxjava:UnicastProcessor、Flowable
- Spring WebFlux:Flux、Mono、WebClient
- Java9 Reactive Stream API:Publisher、Subscriber、Subscription、Processor
- Spring MVC:StreamingResponseBody、SseEmitter 、Servlet3.0
三、技术选型与验证
- 第一步,技术选型
- 已验证:Spring WebSocket、t-io、spring-ai-alibaba、百炼灵积等等
- 未验证:Java-WebSocket、Socket.io
- 第二步,验证流程
- 单点功能验证:
- 第三方大模型API对接
- WebSocket服务端与客户端通信
- 功能集成验证:
- 语音录入 + WebSocket传输 + 服务端 + 第三方大模型API对接
- 整合现有微服务架构(语音录入 → 网关 → WebSocket链路验证)
四、AI编程中的卡点问题与解决
- 问题1:消息大小限制的坑:WebSocket 消息未处理(二进制流)
- 现象:
- 服务端无日志、自定义处理器未触发(该问题最大的坑是,无从下手的感觉)
- 分析:
- AI解决:先问AI,效果不佳
- 百度解决:再查百度,效果仍然不佳(有找到正确的解决方案,由于并未真正理解,仍然卡点了很久)
- Debug源码:此时已无从下手,因此只能Debug源码,逐步排查(逐步理解WebSocket协议的实现原理)
- Debug源码+AI提示:随着了解的越深,逐步建立手感,对问题原因有了大致的判断(验证判断是否正确)
- 根因:
- 消息大小超过限制
- 解决:
- 最终方案:调整消息大小,避免超出websocket server端的消息大小限制
# application.yml
spring:servlet:multipart:# 整个请求大小限制max-request-size: 10MB# 上传单个文件大小限制max-file-size: 20MB
- 问题2:网关Filter劫持WebSocket的101响应:网关转发WebSocket请求,导致消息未处理的异常
- 现象:该问题最大的坑是,无从下手的感觉
- 客户端:报错 “java.io.IOException: 你的主机中的软件中止了一个已建立的连接”
- 服务端:握手请求成功,有打印握手日志(但数据请求的处理器没有执行)
- 网关:有打印请求日志,但没有打印响应日志
- 分析:由于无从下手,只能逐步排除各个环节的问题
- 限制排查:下意识的认为还是消息大小超过限制的问题(未触发消息大小限制)
- 网关配置检查:确认网关配置与请求转发的正确性(请求转发正常)
- 握手请求检查:确认握手请求是否正常被执行(自定义拦截器,握手请求正常执行)
- 网关请求头检查:检查握手请求头是否正常透传(websocket请求header正常透传)
- 握手请求的响应检查:到这一步基本确定是response未被正确处理
- 定位到根因:此处结合AI提示,定位到是网关中的过滤器,未正确处理握手请求的response
- AI修复问题:定位到问题根因,此时让AI给出方案,但效果不佳,借助该思路,手动修正代码,解决问题
- 根因:
- 网关过滤器对Response进行了包装,导致握手请求的101状态码未被正确处理,最终导致WebSocket连接建立失败。
- 解决:
- 最终方案:优化网关AccessLogFilter过滤器代码,对websocket请求直接放行
public class AccessLogGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {StopWatch stopWatch = new StopWatch();stopWatch.start();ServerHttpRequest httpRequest = exchange.getRequest();ServerHttpResponse httpResponse = exchange.getResponse();// WebSocket 握手请求,直接进入下一个Filter,并返回原始response,确保 WebSocket 握手正常处理101状态码// 说明:WebSocket Client 通过Http请求与WebSocket Server握手建立长连接,后续的通信都是该长连接进行通信,不会再被filter拦截if (checkWebSocketHandShake(httpRequest)) {return filter("websocket handshake", exchange, chain, stopWatch, httpResponse, httpRequest);}// 网关:判断是否为流式请求,支持ResponseBodyEmitter、SseEmitter的流式响应String acceptHeader = httpRequest.getHeaders().getFirst(HttpHeaders.ACCEPT);boolean isStreamingRequest = acceptHeader != null && acceptHeader.contains(MediaType.TEXT_EVENT_STREAM_VALUE);if (isStreamingRequest) {return filter("stream request", exchange, chain, stopWatch, httpResponse, httpRequest);}// 省略相关代码 ... ...}/*** 执行拦截器链(不包装request和response)*/private Mono<Void> filter(String logPrefix, ServerWebExchange exchange, GatewayFilterChain chain, StopWatch stopWatch, ServerHttpResponse httpResponse, ServerHttpRequest httpRequest) {if (log.isInfoEnabled() && configProperties.isLogEnabled()) {log.info("请求参数 {} [{}] [{}] query:{}, header:{}", logPrefix, httpRequest.getURI().getPath(), httpRequest.getMethod(), httpRequest.getURI().getRawQuery(), exchange.getRequest().getHeaders());}return chain.filter(exchange).doFinally(s -> MdcUtil.removeTraceId())// 清除MDC.then(Mono.fromRunnable(() -> {stopWatch.stop();// 为了方便排查问题,还是打印一个简单的日志if (log.isInfoEnabled()) {log.info("响应参数 {} {} time: {} ms", logPrefix, httpResponse.getRawStatusCode(), stopWatch.getTotalTimeMillis());}}));}/*** 检查是否为WebSocket握手请求** @return true 表示是WebSocket握手请求*/public boolean checkWebSocketHandShake(ServerHttpRequest httpRequest) {// 从请求头中获取 Upgrade 标志,若为websocket,则表示将普通http请求,升级为websocket请求String upgradeHeader = httpRequest.getHeaders().getFirst(HttpHeaders.UPGRADE);if ("websocket".equalsIgnoreCase(upgradeHeader)) {return true;}return false;}
}
五、实践总结
仅针对上述场景的实践总结
- 技术实践经验
- 需深入理解WebSocket协议原理(消息限制、握手流程)
- 网关需特殊处理WebSocket协议(避免过滤器干扰)
- AI工具定位
- 搜索引擎:替代传统搜索,大部分时候,AI的搜索效果,比百度的效果要好
- 辅助神器:明确的问题,AI效果好;未知或复杂的问题,AI效果差,但用AI辅助理解技术原理效果好,可将未知或复杂的问题,转换为明确的问题,再让AI解决
- AI工具效果
- 通义灵码(Chat模式):生成代码效果一般,解决问题效果一般
- Trae(Builder模式):效果较好(有时也会抽疯,需要多轮对话)
- AI工具局限性
- 生成代码需人工校验(无法直接投产)
- 复杂问题需多轮对话(效率较低,很容易放弃)
- AI开发心得
- 复杂场景,需分步拆解验证,再集成整合
- 顽固问题,目前AI无法替代人工,需手动介入处理(给AI明确的问题点,AI才会有更好的效果)
- 陌生领域,对于不了解的技术,上手变得容易很多(如流式编程)
- 开发习惯,过往的开发习惯较难改变,存在习惯性忽略使用AI的情况(需多用多练)
六、开放性思考
- 普通人,使用AI编程,对代码的要求是什么?
- 生成简单的验证性代码,保证功能实现即可,不关注代码质量
- 程序员,使用AI编程,对代码的要求是什么?
- 生成可投产的生产级别代码,保证功能实现的同时,也关注代码质量和测试质量(要求完全自主可控)
- 面对AI编程效果不佳,尤其经过多轮对话,问题还未解决时,你是否萌生退意??
- 期望过高:通过一次性的需求描述,AI就能自动生成完整的代码或解决问题。
- 实际拉垮:AI生成的代码,或用AI解决问题时,困难重重,始终无法达到预期。
- 扪心自问:使用过程中,面对AI也解决不了的问题时,你有产生过,被劝退的感觉吗?
七、WebSocket相关原理
WebSocket握手请求与数据请求的区别
一、握手请求(Handshake Request):
- 本质:握手请求是HTTP协议的升级请求,用于建立WebSocket连接
- 说明:服务器需响应HTTP 101状态码,确认协议升级,并返回Sec-WebSocket-Accept验证字段
二、数据请求(Message Frame)
- 本质:WebSocket连接建立后,客户端和服务端通过数据帧(Frame)进行双向通信。
- 数据以帧格式传输,包含操作码(Opcode)、掩码(Mask)、载荷长度(Payload Length)等信息
- 常见帧类型:文本帧(Opcode=1)、二进制帧(Opcode=2)、控制帧(如Ping/Pong)。