news 2026/10/24 16:08:42

Java Web聊天系统课设速通:Spring Boot+WebSocket+MySQL完整实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Web聊天系统课设速通:Spring Boot+WebSocket+MySQL完整实现

简介:一套基于前后端分离架构的Java Web聊天系统大作业项目,适合需要完成课程设计、期末大作业或毕设的在校生。后端按分层规范拆分为config、controller、service、dao、entity、dto、utils、vo等模块,config统一管理配置,controller对外提供API,service遵循接口实现,dao负责JPA数据库操作,entity映射数据表字段,vo降低前端耦合;前端由Vue组件配合scss样式构成,覆盖聊天页面与交互逻辑。资源包共138个文件,以66个Java源码、12个Vue组件、11个scss样式为核心,另有properties、xml、jar等配置依赖文件及docx文档、png截图,压缩包大小仅2.08MB,便于快速下载部署,已有1152人学习/下载。通过源码可学习JPA数据库映射、过滤器与拦截器、VO与前端交互数据封装等关键写法,模块边界明确,利于二次开发;文档说明和目录结构注释能有效减少项目理解成本,适合作为同类聊天系统开发的直接参考模板。

1. Java Web大作业聊天系统:为什么说它是课设里性价比最高的一个题

“Java Web大作业 聊天系统”这行字,几乎每年期末都会出现在任务书里。它看着不起眼,拉开的却是一条完整的实时双向通信链路:浏览器连上WebSocket、服务端路由消息、数据库落消息、会话列表算未读。很多人从拿到题到交作业,时间全耗在“为什么对面那个人收不到消息”上。我的结论是:不引Redis、不碰Netty,用Spring Boot + 原生WebSocket + MySQL,再加一点前端重连逻辑,一个能现场演示的聊天系统,一个周末就能从零跑通。这篇笔记适合正在做课设的Java学习者,也适合想把第一个完整web项目写进简历的初级工程师,后面的代码和参数都能直接复制改包名。

2. 先把模型立住:技术选型、四张表的边界和推送闭环

2.1 技术选型:Spring Boot + WebSocket,还是Servlet + JSP老一套

聊天系统最怕的不是写代码,是选错路。常见做法是两条路线,你先看任务书里有没有限定技术栈。

第一条是学校机房路线,Servlet + JSP + Tomcat。项目结构老,但老师一眼能看懂。JSP页面里写聊天室,消息推送用Tomcat 7以后自带的WebSocket支持,也能跑通。缺点是前端和后端混在一起,会话管理全靠HttpSession,写到后面你自己都不想维护。

第二条是Spring Boot + Thymeleaf + WebSocket + MyBatis Plus。这是目前大多数Java Web大作业的主流方案,也是面试时能讲出东西的组合。Spring Boot负责把项目跑起来,MyBatis Plus帮你省掉大量JDBC样板代码,Thymeleaf渲染首屏页面,WebSocket负责实时推消息。

如果你用的是IDEA 2024创建项目,默认可能会拉到Spring Boot 3.x,它要求JDK 17以上。学校机房如果还是JDK 8,建议创建时直接选Spring Boot 2.7.x,省得到时候启动就报“不支持major version”。

对比项Servlet + JSPSpring Boot + WebSocket
上手门槛低,但代码分散中,但结构清晰
WebSocket支持Tomcat自带Spring原生支持
数据库操作JDBC手写MyBatis Plus自动映射
写简历价值一般能聊清实时通信链路
踩坑面少但坑深多但资料齐全

至于为什么不建议上Netty,原因很简单:课设场景里原生WebSocket已经够用,Netty带来的线程模型和编解码复杂度,够你再熬两个晚上。同理,Redis也别急着引,在线状态和未读数放内存里演示完全没问题,真上了Redis反而多一个部署环节。

2.2 四张核心表:用户、会话、会话成员、消息,建表SQL直接抄

聊天系统的核心不是聊天本身,是会话模型。单聊和群聊如果能用同一套表结构表达,后面所有接口都好写。我一般会建四张表:用户表、会话表、会话成员表、消息表。建表SQL放在下面,直接复制到MySQL 8里执行就能用。

-- 用户表 CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录名,唯一', `password` varchar(100) NOT NULL COMMENT '密码,建议BCrypt或MD5加盐', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL,可空', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 会话表 CREATE TABLE `conversation` ( `id` bigint NOT NULL AUTO_INCREMENT, `type` tinyint DEFAULT '1' COMMENT '1单聊 2群聊', `name` varchar(100) DEFAULT NULL COMMENT '群聊名称,单聊为空', `last_message_id` bigint DEFAULT NULL COMMENT '最后一条消息ID,用于会话排序', `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会话表';
-- 会话成员表 CREATE TABLE `conversation_member` ( `id` bigint NOT NULL AUTO_INCREMENT, `conversation_id` bigint NOT NULL, `user_id` bigint NOT NULL, `unread_count` int DEFAULT '0' COMMENT '该成员的未读消息数', `last_read_msg_id` bigint DEFAULT '0' COMMENT '该成员最后已读的消息ID', `is_muted` tinyint DEFAULT '0' COMMENT '是否免打扰', PRIMARY KEY (`id`), UNIQUE KEY `uk_conv_user` (`conversation_id`, `user_id`), KEY `idx_user_conv` (`user_id`, `conversation_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会话成员表';
-- 消息表 CREATE TABLE `message` ( `id` bigint NOT NULL AUTO_INCREMENT, `conversation_id` bigint NOT NULL, `sender_id` bigint NOT NULL, `content` text COMMENT '消息内容', `msg_type` tinyint DEFAULT '1' COMMENT '1文本 2图片 3系统', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_conv_id` (`conversation_id`, `id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息表';

这里有两个字段设计是返工重灾区。第一,conversation_member里一定要有last_read_msg_id,而不是简单放一个is_read布尔字段。未读数不是靠标记算的,是靠“会话内最新消息ID - 已读消息ID”之间有多少条算出来的。第二,消息表里conversation_id和id必须建联合索引,因为查历史消息永远是这个条件。

我用MyBatis Plus时一般先手写这份SQL把库建好,再让代码生成器生成实体类和Mapper。网上那些“根据Java实体类生成建表SQL”的工具我也试过,生成的表在时间字段默认值、索引设计上经常和你想要的不一致,课设阶段手写SQL反而最稳。

2.3 推送闭环先理清:HTTP轮询为什么会被聊天系统判死刑

聊天系统的核心矛盾是:HTTP协议是单向的,浏览器发请求,服务器才能给响应。可聊天要求服务器主动把消息推给浏览器,这时候你有两个选择。

第一个选择是轮询。前端每秒钟发一次“有没有新消息”的请求,后端查一次数据库。这个方案在30人以下的小demo里勉强能跑,但消息延迟、服务器压力、流量浪费全都不好看。老师演示的时候,你发一条消息,对面要等下一次轮询才能看到,场面很尴尬。

第二个选择是WebSocket。浏览器和服务器建立一条长连接,两边都能随时发数据。它的握手阶段依然是HTTP,但握手成功后协议升级为WebSocket,数据帧直接走TCP。这才是聊天系统该用的方案。

完整的推送闭环是:A用户在浏览器里点发送,消息通过WebSocket连接发到后端,后端先把消息插入MySQL,再从内存里的连接表找到B用户的WebSocket Session,把消息转发过去。这里有个顺序不能反——必须先落库再推送。如果先推给B客户端,数据库写入却失败了,B看到了一条实际上不存在的消息,刷新页面后这条消息就消失了,这就是典型的“幽灵消息”。

2.4 在线状态与未读数:会话列表像不像样,就看这4个字段怎么填

在线状态不用往数据库里存,存了也来不及更新。我一般是在后端维护一个全局的ConcurrentHashMap<Long, Session>,userId 对应 WebSocket Session。用户上线就put,断开就remove,前端直接查这个Map就知道谁在线。

未读数不能只靠内存,因为用户刷新页面后内存状态还在,但页面上的数字应该继续保持。这份数据放在conversation_member表里,unread_count在发送消息时对会话内的其他成员+1,在用户点开会话时清零。

会话列表的排序有个小技巧:不要按updated_at排,按conversation.last_message_id排。因为同一毫秒内可能有多条消息进来,datetime精度不够,而自增ID是严格单调递增的,用ID排序能保证“最后发消息的会话排最上面”。这三个字段——last_message_id、last_read_msg_id、unread_count——决定了会话列表像不像一个产品。

3. 从登录到发消息:把聊天核心链路一行行跑通

3.1 登录态不做重活:一个登录拦截器 + HttpSession 就够了

大作业级别的聊天系统不需要引入Spring Security,那套过滤器链配置够你研究一晚上。常见做法是一个拦截器,判断HttpSession里有没有userId,没有就跳转到登录页。给一段直接能用的配置:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") .excludePathPatterns( "/login", "/register", "/chat/**", "/css/**", "/js/**", "/error" ); } }

这个配置里最关键的是/chat/**必须放行。WebSocket握手阶段虽然会携带Cookie,但Spring Boot的拦截器对WebSocket握手请求的过滤行为和普通HTTP请求不太一样,如果你把拦截器加在/chat/**上,握手请求会被拦下来,前端WebSocket就一直连不上。这里的/chat/**是WebSocket端点的访问路径,不是页面路径。

LoginInterceptor的内部逻辑很简单,从HttpSession里取值:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(false); if (session == null || session.getAttribute("userId") == null) { response.sendRedirect("/login"); return false; } UserHolder.set((Long) session.getAttribute("userId")); return true; } }

密码存储这块,别存明文。最少也要MD5加盐,盐值用用户名或注册时间,存储格式 = MD5(密码 + 盐)。如果你愿意多花半小时,用BCrypt加密,Spring Security里的BCryptPasswordEncoder单独抽出来就能用,这也是面试时能讲一句“我了解密码哈希和非对称加密的区别”的点。

3.2 WebSocket端点这样写:连接、发消息、离线通知三件事一次讲清

这是整个项目最核心的一段代码,直接决定能不能聊天。用@ServerEndpoint注解的方式在Spring Boot里管理WebSocket,需要注意一个Spring Bean注入的坑,我给你写的是绕过这个坑的完整写法。

@Component @ServerEndpoint(value = "/chat/{userId}", configurator = ChatConfigurator.class) public class ChatWebSocketServer { // 全局连接表:userId -> Session private static final Map<Long, Session> ONLINE_MAP = new ConcurrentHashMap<>(); // 静态注入Service,避免@ServerEndpoint里@Autowired失效 private static MessageService messageService; @Autowired public void setMessageService(MessageService service) { ChatWebSocketServer.messageService = service; } private Session session; private Long userId; @OnOpen public void onOpen(Session session, @PathParam("userId") Long userId) { this.session = session; this.userId = userId; Session old = ONLINE_MAP.put(userId, session); if (old != null) { try { old.close(); } catch (IOException ignored) {} } broadcastOnline(userId, true); } @OnMessage public void onMessage(String text) { // 前端发来结构:{"conversationId":1,"receiverId":2,"content":"你好","msgType":1} JSONObject obj = JSON.parseObject(text); Long conversationId = obj.getLong("conversationId"); Long receiverId = obj.getLong("receiverId"); String content = obj.getString("content"); Integer msgType = obj.getInteger("msgType"); // 1. 落库 Message msg = messageService.saveMessage(conversationId, userId, content, msgType); // 2. 推送给接收方 Session toSession = ONLINE_MAP.get(receiverId); if (toSession != null) { toSession.getBasicRemote().sendText(JSON.toJSONString(msg)); } // 3. 更新会话列表置顶信息 messageService.touchConversation(conversationId, msg.getId()); } @OnClose public void onClose() { ONLINE_MAP.remove(userId); broadcastOnline(userId, false); } @OnError public void onError(Session session, Throwable error) { ONLINE_MAP.remove(userId); } }

这段代码有三个容易翻车的点。第一,@ServerEndpoint创建的实例不是Spring管理的,直接在上面写@Autowired MessageService一定是null,所以要用静态setter注入的方式。第二,ONLINE_MAP必须是static,因为WebSocket端点每个连接都会new一个实例,非static的Map等于每个连接各自维护一份,A发的消息B永远看不到。第三,broadcastOnline是给所有在线用户广播一条系统消息,前端收到后更新好友列表里的在线状态,这样不需要查数据库就能知道谁上下线。

ChatConfigurator是为了解决握手阶段跨域校验的问题,下一章详细讲。

3.3 会话列表接口:未读数、最后一条消息与排序一起返回

会话列表页面加载时,需要一个接口返回当前用户参与的所有会话,包括对端信息、最后一条消息内容、未读数。这个接口写不好,前端就得一次发三个请求,代码又乱又慢。

@RestController @RequestMapping("/api/conversation") public class ConversationController { @Autowired private ConversationService conversationService; @GetMapping("/list") public Result list() { Long userId = UserHolder.get(); List<ConversationVO> list = conversationService.listByUserId(userId); return Result.success(list); } }

ConversationServiceImpl里的核心逻辑是:先查conversation_member表里当前用户参与的所有会话ID,再批量查会话基础信息和成员信息,最后填充最后一条消息和未读数。批量查很重要,不要在循环里单条查数据库,一个用户10个会话就是10次查询,加上成员就是20次,课设规模下虽然不至于垮,但老师看代码时会皱眉头。

返回给前端的ConversationVO长这样:

public class ConversationVO { private Long conversationId; private Integer type; // 1单聊 2群聊 private String name; // 群聊名或对方昵称 private String lastMessage; // 最后一条消息预览 private Integer unreadCount; // 未读数 private Long lastMessageId; // 最后消息ID,用于排序 private Boolean online; // 对方是否在线 }

online字段不用查数据库,后端在组装VO的时候直接查一下全局ONLINE_MAP就知道。这也是把在线状态放内存的好处,接口响应速度比查数据库快得多。

3.4 原生JS做最小前端:不引Vue,一个HTML文件跑通聊天页

我见过不少组员在课设里硬上Vue + Element UI,最后卡在node_modules安装和跨域代理上。聊天系统这种规模,原生JS加一个HTML模板页面完全够,部署时还少一个构建步骤。

let ws; let currentUserId = document.getElementById("currentUserId").value; function connect() { ws = new WebSocket("ws://" + location.host + "/chat/" + currentUserId); ws.onopen = function() { console.log("websocket connected"); }; ws.onmessage = function(evt) { const data = JSON.parse(evt.data); if (data.type === "chat") { appendMessage(data); } else if (data.type === "online") { updateUserStatus(data.userId, data.online); } else if (data.type === "read") { updateUnread(data.conversationId); } }; ws.onclose = function() { setTimeout(connect, 2000); }; } connect(); function sendMessage() { const content = document.getElementById("msgInput").value.trim(); if (!content) return; ws.send(JSON.stringify({ conversationId: currentConversationId, receiverId: targetUserId, content: content, msgType: 1 })); document.getElementById("msgInput").value = ""; }

注意connect()在onclose里重试,这是后面要详细讲的断线重连。前端渲染消息时一定要做HTML转义,否则用户发一段<script>标签就能在你的页面上执行任意脚本。这算是大作业级别的web安全第一课:

function escapeHtml(str) { return str.replace(/[&<>"']/g, function(m) { return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[m]; }); }

4. 聊天系统大作业最容易踩的5个坑:现象、原因、解决办法

4.1 对方收到的消息全是问号:中文乱码的根因不在Tomcat

现象:A发“你好”,B收到的是“ä½ å¥½”或者一排问号,数据库里存的也是乱码。

原因:Tomcat 8以后的URI编码默认就是UTF-8,WebSocket的文本帧也是UTF-8。真正出问题的地方在MySQL,连接串没指定字符集,或者建表时用了latin1。

解决:MySQL连接串加characterEncoding=utf8,建库时明确指定utf8mb4:

spring: datasource: url: jdbc:mysql://localhost:3306/chat?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai

然后在MySQL里确认一下表字符集:SHOW TABLE STATUS LIKE 'message';,Collation列是utf8mb4_general_ci或utf8mb4_unicode_ci就对了。

4.2 WebSocket连不上:浏览器控制台直接报403

现象:页面能打开,登录也正常,但WebSocket一直connecting,Network面板里握手请求返回403。

原因:Tomcat的WebSocket容器默认会校验请求的Origin头,如果前端页面的端口和你WebSocket服务端口不一样,或者前端页面是从文件系统打开的,Origin不匹配就会被拒绝。

解决:写一个Configurator,重写checkOrigin放行:

public class ChatConfigurator extends ServerEndpointConfig.Configurator { @Override public boolean checkOrigin(String originHeader) { return true; } }

课设阶段直接返回true没问题,生产环境需要校验白名单。还有一种情况是反向代理没配WebSocket升级头,如果你用Nginx转发,需要加Upgrade和Connection两个Header,课设阶段本地启动没有这个问题。

4.3 前端拿到的ID后几位变成0:Long类型精度丢失

现象:后端返回的消息ID是162331234567890123,前端console里打印出来变成了162331234567890120,最后一位直接变了。

原因:JavaScript的Number类型最大安全整数是2^53,超过这个范围精度就丢。数据库自增ID超过这个数后,Jackson默认把Long序列化成数字,前端解析时精度就坏了,典型的黑匣子问题。

解决:在实体类ID字段上指定toString序列化:

@JsonSerialize(using = ToStringSerializer.class) private Long id;

这样前端拿到的是字符串"162331234567890123",精度完整。同理,前端传给后端的ID要么字符串,要么BigInteger,别让精度问题变成“明明库里有这条消息却查不到”的玄学。

4.4 @Autowired注入为null:@ServerEndpoint端点里拿不到Spring Bean

现象:消息一发送,后端直接NPE,日志显示messageService为null。

原因:@ServerEndpoint的实例不是Spring容器管理的,Spring的依赖注入不会对WebSocket端点生效。你在端点上写@Autowired,等于注入了一个null。

解决:刚才代码里已经用了静态setter注入。或者用一个Spring上下文工具类手动取:

@Component public class SpringContextHolder implements ApplicationContextAware { private static ApplicationContext ctx; @Override public void setApplicationContext(ApplicationContext applicationContext) { ctx = applicationContext; } public static <T> T getBean(Class<T> clazz) { return ctx.getBean(clazz); } }

在@OnMessage里通过SpringContextHolder.getBean(MessageService.class)拿到Service。这两种方式选一种就行,我推荐静态setter,代码更直白,课设答辩时也好讲清楚。

4.5 用户关掉浏览器后仍然显示在线:onClose没被触发的场景

现象:A直接关浏览器标签页,B的好友列表里A依然是绿色在线,发消息也没人回。

原因:浏览器进程被杀、笔记本合盖、断网,这些情况下WebSocket的onClose不一定会立刻触发,服务端的Session还留在ONLINE_MAP里。

解决:双保险。前端在beforeunload事件里主动关闭WebSocket:

window.addEventListener("beforeunload", function() { ws.close(); });

后端再做心跳检测:启动一个定时任务,每30秒扫描一次所有连接,发送一条ping帧,如果在10秒内没有收到pong响应,就调用onClose清理连接并广播离线状态。这个定时任务用Spring的@Scheduled就能做,不需要额外依赖。

5. 从“能交”到“能演示”:断线重连、消息去重与验收动作

5.1 断线自动重连:不要让老师看到你刷新页面才出消息

后端一重启,前端所有WebSocket连接全部断开,这时候如果没有重连逻辑,页面就像死了一样。在onclose里加一个指数退避重连:

let reconnectAttempts = 0; function connect() { ws = new WebSocket("ws://" + location.host + "/chat/" + currentUserId); ws.onopen = function() { reconnectAttempts = 0; }; ws.onclose = function() { const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 15000); setTimeout(connect, delay); reconnectAttempts++; }; }

这是我在聊天系统里加过最值的一行逻辑,重连成功的那一刻,未读数会通过后端重新推过来,页面自己就活了。

5.2 消息幂等与去重:客户端重试导致重复插库

断线重连本身会带来一个新问题:用户点发送,消息发出去了但客户端没收到成功回执,前端自动重发,数据库里就出现了两条一模一样的消息。

解决方式是客户端生成一个clientMsgId,服务端保存消息前先查这个字段是否存在,存在就丢弃。

ALTER TABLE `message` ADD COLUMN `client_msg_id` varchar(64) DEFAULT NULL COMMENT '客户端消息幂等ID'; ALTER TABLE `message` ADD UNIQUE KEY `uk_client_msg_id` (`client_msg_id`);

前端发送时带上这个字段:

const message = { conversationId: currentConversationId, receiverId: targetUserId, content: content, msgType: 1, clientMsgId: Date.now() + "_" + Math.random().toString(16).slice(2) };

5.3 演示前的三个验证动作

第一,开两个浏览器,一个普通窗口一个隐身窗口,分别登两个账号,A发消息看B是否秒出。第二,F12打开Network面板,选WS标签,看WebSocket帧里的收发信息,确认消息推送链路是通的。第三,A发消息后直接断网,B端收到消息后A重连回来,看A的会话列表是否置顶、未读数是否正确。这三个动作全过,课设演示基本不会翻车。

我以前做这个项目时,就是吃了重连和消息重复的亏,答辩现场老师连点两次发送,数据库里多了两条一样的消息,当场被问住。后来才把clientMsgId加进表里。这段血泪经验写出来,希望帮到你。

本文还有配套的精品资源,点击获取

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

DeepSeek大模型本地部署与微调实战:从API调用到LoRA训练完整指南

1. 从一张学习笔记说起&#xff1a;为什么值得系统梳理DeepSeek最早接触DeepSeek是在一个内部技术分享会上&#xff0c;当时有人提到“推理成本能做到这个程度&#xff0c;值得看看”。后来自己上手跑了几轮&#xff0c;从API调用到本地部署&#xff0c;再到微调实验&#xff0…

作者头像 李华
网站建设 2026/10/23 19:55:38

AI日报自动化系统:不依赖大模型的可信信息流水线

1. 这不是一份“新闻简报”&#xff0c;而是一套可复用的AI日报生成系统 “AI 日报 2026-09-30”——看到这个标题&#xff0c;你第一反应是什么&#xff1f;是点开一篇带日期的公众号推文&#xff1f;还是以为这是某家科技媒体的栏目名称&#xff1f;其实都不是。它是我过去14…

作者头像 李华
网站建设 2026/10/23 11:09:46

QuickBlue:企业AI应用底座如何破解重复造轮子困境

写这篇文章之前&#xff0c;我先说个背景。过去两年我参与过好几家企业的 AI 落地项目&#xff0c;从制造、金融到零售都有&#xff0c;一个很强烈的感受是&#xff1a;大家第一步做的事几乎一模一样——接大模型 API、搭知识库、做提示词、做流式输出、做对话界面。每家都觉得…

作者头像 李华
网站建设 2026/10/23 20:06:56

企业知识库RAG实战:腾讯云向量数据库全链路搭建与调优

1. 为什么企业知识库到了必须上 RAG 的时候 做过企业内部知识管理的人都清楚一个现实&#xff1a;文档越攒越多&#xff0c;能找到的东西越来越少。早期大家用关键词搜索&#xff0c;后来上了 Elasticsearch 做全文检索&#xff0c;再后来指望大模型直接问答。但真把大模型接进…

作者头像 李华
网站建设 2026/10/22 12:04:27

精通MFC光盘源码:从VC6迁移到VS2022的实战指南

简介&#xff1a;精通MFC&#xff08;光盘&#xff09;源代码是一套与《精通MFC》书籍配套的完整示例源码&#xff0c;面向从入门到进阶的C/Visual C开发人员&#xff0c;特别适合想深入理解MFC框架结构、窗口消息机制和Windows应用构建流程的读者。压缩包共1114个文件&#xf…

作者头像 李华
网站建设 2026/10/19 10:05:41

AI日报从信息过载到精准筛选:智能体容错与多AI协作实战

1. 一份AI日报背后的信息筛选逻辑做AI日报这件事&#xff0c;我从2024年就开始断断续续地折腾&#xff0c;中间停过几次&#xff0c;原因很简单——信息量太大&#xff0c;每天新出的模型、工具、框架、论文、融资消息铺天盖地&#xff0c;一个人根本消化不完。后来我慢慢摸索出…

作者头像 李华