使用 AI 开发一款聊天工具

一、为什么用AI做这个聊天工具?

  • AI开发优势:快速生成基础框架、减少重复编码,聚焦核心业务逻辑。
  • 能力增强:即使是自己不熟悉的语音(JAVA, 本职是前端开发),也可以快速开发出自己的一款应用。
  • 能够及时解决开发问题:对应表设计,以及遇到报错等场景问题都能快速给出建议和解答。AI 完全可以充当一个及格的老师。

二、技术栈速览

  • 后端技术栈
    • 基础框架:Spring Boot(快速搭建服务)
    • 安全认证:Spring Security + JWT(无状态登录校验)
    • 数据操作:MyBatis-Plus + MySQL(简化CRUD与数据存储)
    • 网络通信:Netty(高性能Socket连接与消息推送)
    • 工具支撑:Lombok(精简实体类)、Hutool(通用工具)、Jackson(JSON处理)
  • 前端技术栈
    • 跨平台容器:Electron(桌面端打包与多窗口控制)
    • 前端框架:Vue3 + Vite(高效UI开发与构建)

三、项目搭建:AI生成框架的高效流程

  • 后端搭建(AI生成核心骨架)
    1. AI指令设计:明确告知"Spring Boot+Netty+MyBatis-Plus"技术栈,需求为"聊天工具后端,包含用户认证、消息存储、Socket服务"
    2. AI生成内容:自动生成pom.xml依赖、application.yml配置(数据库、Netty端口)、基础实体类(User、Room、Message)
    3. 人工微调点:修改数据库连接信息、调整Netty线程池参数、补充JWT密钥配置
  • 前端搭建(AI搞定Electron与Vue整合)
    1. AI指令设计:"用Vite初始化Vue3项目,整合Electron,支持多窗口打开与IPC通信"
    2. AI生成内容:Electron主进程(main.js)、渲染进程通信模板、Vue3基础页面结构
    3. 人工微调点:配置Electron窗口大小/图标、梳理Vue组件层级(登录页、聊天窗口)

四、核心功能实现

聊天工具的核心是"数据关联"和"实时通信",本文会围绕MVP版本的核心逻辑展开:

  1. 表设计逻辑:如何设计用户、房间、消息的关联表,实现"单聊自动建房间"的核心需求?
  2. 通信:Netty如何搭建WebSocket服务实现消息实时推送?
  3. 前端交互:Electron+Vue3如何实现多聊天窗口管理,以及与后端的Socket通信对接?

核心表结构

用户 A 和用户 B 发送消息的流程

  1. 建立 Friendship

    • 用户 A 和用户 B 通过 Friendship 表建立朋友关系。
    • Friendship 表中插入一条记录,user_id1 为用户 A 的 user_iduser_id2 为用户 B 的 user_id
  2. 创建聊天房间

    • 创建一个新的聊天房间,记录在 RoomItem 表中。

    • RoomItem 表中插入一条记录,room_id 为新生成的房间ID,title 为用户 B 的用户名(假设是单聊房间)。

    • RoomUserRelation 表中插入两条记录,分别表示用户 A 和用户 B 加入了该房间:

      • 第一条记录:roomId 为新房间的 room_iduserId 为用户 A 的 user_id
      • 第二条记录:roomId 为新房间的 room_iduserId 为用户 B 的 user_id
  3. 发送消息

    • 用户 A 向用户 B 发送消息,记录在 MessageItem 表中。

    • MessageItem 表中插入一条记录:

      • id 为新生成的消息ID。
      • room_id 为聊天房间的 room_id
      • message_content 为消息内容。
      • sender_id 为用户 A 的 user_id
      • receiver_id 为用户 B 的 user_id

后端Netty核心:Socket服务启动与消息推送

为什么需要 Netty:说明 Java 原生 NIO API 复杂难用,Netty 是对 NIO 的封装,让开发者不用关心底层细节,就能实现 "同时处理上千个客户端连接" 的高性能服务(对应需求里的 "大量客户端长连接")。

前置知识

  1. Channel(通道) :类比 "一根网线"------ 是客户端和服务器之间的 "数据传输通道",每个客户端连接对应一个 Channel,所有数据(消息)都通过这根 "网线" 传。

  2. ChannelPipeline(管道) :类比 "工厂流水线"------ 数据在 Channel 里传输时,要经过一系列 "处理步骤",每个步骤就是一个 Handler(处理器),比如 "解码→验证→处理业务→编码",需求里的 IdleStateHandler、HttpServerCodec 都是流水线上的 "工人"。

  3. EventLoopGroup(线程组) :类比 "工厂的工人团队"------

    • bossGroup(1 个线程):类比 "门口接待员",只负责接客户(客户端连接),不处理具体业务;
    • workerGroup(CPU 核心数线程):类比 "车间工人",客户进来后,由他们处理后续的 "数据加工"(IO 事件,比如读消息、写消息)。
  4. Handler(处理器) :类比 "流水线的具体工序"------ 每个 Handler 负责一件专门的事,比如 IdleStateHandler 是 "检查有没有人摸鱼"(30 秒没消息就触发空闲),WebSocketServerProtocolHandler 是 "帮客户换通信方式"(HTTP 升级成 WebSocket)

类比名 真实 Netty 组件 一句话职责
网线 Channel 一根网线插到客户端,所有数据都在这根线里跑
流水线 ChannelPipeline 线上绑了一条流水线,数据按顺序被各个工序加工
接待员 bossGroup(EventLoopGroup) 只负责在门口"接客"------把新连接 accept 进来
车间工人 workerGroup(EventLoopGroup) 真正干活的工人,后续读写、编解码、业务计算都由他们线程池处理
工序 ChannelHandler 流水线上的工人,每个只干一件专业活( idle 检查、协议升级、业务逻辑 ...)

socket 服务启动和配置

该类的作用是启动一个基于 Netty 的 WebSocket 服务,实现高并发、低延迟的实时通信能力,适用于聊天、通知等场景。

JAVA 复制代码
@Slf4j
@Configuration
public class NettyWebSocketServer {

    /* =============== 1. 端口与业务处理器 =============== */
    private static final int PORT = 8091;                                  // 对外监听端口
    static final NettyWebSocketServerHandler HANDLER =
            new NettyWebSocketServerHandler();                           // 全局单例:处理WebSocket帧

    /* =============== 2. 线程模型:1个老板 + N个工人 =============== */
    private final EventLoopGroup boss = new NioEventLoopGroup(1);        // 只负责accept
    private final EventLoopGroup worker =                               // CPU核数,负责IO
            new NioEventLoopGroup(NettyRuntime.availableProcessors());

    /* =============== 3. Spring容器ready后自动启动 =============== */
    @PostConstruct
    public void start() throws InterruptedException {
        run();
    }

    /* =============== 4. Spring容器销毁前优雅释放线程 =============== */
    @PreDestroy
    public void stop() {
        boss.shutdownGracefully();
        worker.shutdownGracefully();
    }

    /* =============== 5. 启动入口:装配流水线并绑定端口 =============== */
    public void run() throws InterruptedException {
        new ServerBootstrap()
                .group(boss, worker)                                    // 绑定线程组
                .channel(NioServerSocketChannel.class)                // 非阻塞ServerSocket
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ChannelPipeline p = ch.pipeline();
                        p.addLast(new IdleStateHandler(30, 0, 0));    // 30s心跳
                        p.addLast(new HttpServerCodec());             // HTTP编解码
                        p.addLast(new ChunkedWriteHandler());         // 大文件分块
                        p.addLast(new HttpObjectAggregator(8192));    // 拼完整HTTP报文
                        p.addLast(new HttpHeadersHandler());          // 提取JWT/客户端IP
                        p.addLast(new WebSocketServerProtocolHandler("/"));// 升级WS
                        p.addLast(HANDLER);                           // 业务:文本/二进制帧
                    }
                })
                .bind(PORT).sync();                                    // 同步等待绑定成功
        log.info("WebSocket服务器已在{}端口启动", PORT);
    }
}

Spring Boot与Netty协作:消息处理完整流程

ChannelPipeline 管道配置

JAVA 复制代码
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new IdleStateHandler(30, 0, 0)); // 30秒无读操作则触发空闲事件
// 网络上的字节流"翻译成"HTTP 请求和响应"
pipeline.addLast(new HttpServerCodec());         // HTTP 编解码
pipeline.addLast(new ChunkedWriteHandler());     // 支持分块写入
pipeline.addLast(new HttpObjectAggregator(8192)); // 聚合 HTTP 消息片段
pipeline.addLast(new HttpHeadersHandler());      // 保存客户端 IP 等信息
pipeline.addLast(new WebSocketServerProtocolHandler("/")); // 处理 HTTP 到 WebSocket 协议升级
// 这是你自己写的处理器,继承自 SimpleChannelInboundHandler<WebSocketFrame>
pipeline.addLast(NETTY_WEB_SOCKET_SERVER_HANDLER); // 处理 WebSocket 业务逻辑
java 复制代码
/**
 * 在 HTTP 阶段拦截请求头,做 升级 WebSocket 前的预处理
 */

public class HttpHeadersHandler extends ChannelInboundHandlerAdapter {
    if (msg instanceof FullHttpRequest) {
        FullHttpRequest request = (FullHttpRequest) msg;
        UrlBuilder urlBuilder = UrlBuilder.ofHttp(request.uri());

        // 1. 提取 token 参数
        String token = Optional.ofNullable(urlBuilder.getQuery())
                .map(k -> k.get("token"))
                .map(CharSequence::toString)
                .orElse("");
        NettyUtil.setAttr(ctx.channel(), NettyUtil.TOKEN, token);
        // ... 其他参数获取设置
        
        // 4. 自我移除:只处理一次
        ctx.pipeline().remove(this);

        // 5. 继续传递处理后的 request
        ctx.fireChannelRead(request);
    } else {
        // 非 HTTP 请求,直接传递
        ctx.fireChannelRead(msg);
    }
}
java 复制代码
/**
 * 专门处理 WebSocket 文本消息
 */
@Slf4j
@Sharable
public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    private WebSocketService webSocketService;
    /**
     * 心跳检查
     *
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
             // 需要配置心跳否则会导致连接断开 读空闲
            if (idleStateEvent.state() == IdleState.READER_IDLE) {
                // 关闭用户的连接
                userOffLine(ctx);
            }
        } else if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
            Channel channel = ctx.channel();
            String token = NettyUtil.getAttr(channel, NettyUtil.TOKEN);
            Long userId = (Long) NettyUtil.getAttr(channel, NettyUtil.USER_ID);
            System.out.println(userId);
            this.webSocketService.connect(channel, userId);

        }
        super.userEventTriggered(ctx, evt);
    }

    // 处理异常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.warn("异常发生,异常消息 ={}", cause);

    }


    // 读取客户端发送的请求报文
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println(" 读取客户端发送的请求报文:" + msg.toString());
    }
}

前端核心:Electron窗口管理与Socket通信

聊天流程

  1. 用户登录

    • 用户打开应用并登录。
    • 登录窗口(LoginWindow)建立 WebSocket 连接到 Netty 服务。
    • Netty 服务确认 WebSocket 连接成功。
  2. 用户选择好友

    • 用户在登录窗口选择一个好友,触发 openChatWindow 事件。
    • 登录窗口通过 IPC 向主进程发送 openChatWindow 事件,携带好友 ID。
  3. 主进程处理

    • 主进程检查是否已存在该好友的聊天窗口。
    • 如果存在,聚焦该窗口。
    • 如果不存在,创建新聊天窗口并加载聊天页面。
  4. 聊天窗口建立连接

    • 新创建的聊天窗口(ChatWindow)建立自己的 WebSocket 连接到 Netty 服务。
    • Netty 服务确认 WebSocket 连接成功。
  5. 用户发送消息

    • 用户在聊天窗口输入消息并发送。
    • 聊天窗口通过 WebSocket 向 Netty 服务发送消息。
  6. Netty 服务处理

    • Netty 服务接收消息并处理,将消息发送到目标用户。
  7. 接收消息

    • 聊天窗口通过 WebSocket 接收消息。
    • 聊天窗口更新消息列表并显示新消息。

多聊天窗口管理(Electron核心)

核心功能

  • 用户选择好友后,通过 IPC 向主进程发送 openChatWindow 事件,携带好友 ID。
  • 主进程根据好友 ID 检查是否已存在聊天窗口,若存在则聚焦该窗口,否则创建新窗口。
  • 使用 BrowserWindow 管理窗口,记录窗口与好友 ID 的映射。
js 复制代码
// 主进程代码
const { app, BrowserWindow, ipcMain } = require("electron");
const windows = new Map();

// 创建聊天窗口
function createChatWindow(friendId) {
  const existingWindow = windows.get(friendId);
  if (existingWindow && !existingWindow.isDestroyed()) {
    existingWindow.show();
    existingWindow.focus();
    return existingWindow;
  }

  const chatWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
    },
  });

  windows.set(friendId, chatWindow);
  chatWindow.loadURL(`http://localhost:5173/chat?friendId=${friendId}`);
  chatWindow.on("closed", () => {
    windows.delete(friendId);
  });

  return chatWindow;
}

// IPC 事件处理
ipcMain.on("openChatWindow", (event, friendId) => {
  createChatWindow(friendId);
});

2. 前端 Socket 通信

核心功能

  • 登录成功后,在 Vue 根组件初始化 WebSocket,连接后端 Netty 服务。
  • 聊天窗口输入内容后,通过 WebSocket 发送 JSON 格式消息。
  • 监听 WebSocket 的 message 事件,接收消息后更新对应聊天窗口的消息列表。
js 复制代码
// 前端代码
const socket =  new WebSocket(WS_URL + "?token=" + route?.query?.token + "&userId=" + route?.query?.userId);

socket.onopen = () => {
  console.log("WebSocket 连接已建立");
};

socket.onmessage = (event) => {
  const message = JSON.parse(event.data);
  updateMessageList(message);
};

function sendMessage(toUserId, content) {
  const message = {
    toUserId: toUserId,
    content: content,
  };
  socket.send(JSON.stringify(message));
}

function updateMessageList(message) {
  const chatWindow = findChatWindow(message.toUserId);
  if (chatWindow) {
    chatWindow.webContents.send("new-message", message);
  }
}

结语

以往,前端开发者往往专注于前端技术栈,对于后端的复杂逻辑和架构搭建知之甚少。这种分工虽然高效,但也限制了开发者在技术上的全面发展。然而,AI 的出现打破了这一局限。通过 AI,我能够快速生成后端的基础框架,包括 Spring Boot 的项目结构、Spring Security 的认证逻辑、MyBatis-Plus 的数据操作以及 Netty 的 WebSocket 服务。这些原本需要深厚后端知识和大量时间来搭建的部分,现在只需通过简单的指令,AI 就能生成初步的代码框架,让我能够迅速上手并进行微调。

尽管 AI 为全栈开发提供了强大支持,但这并不意味着前端或后端可以被取代。AI 让开发者跨越技术边界,拓宽视野,快速掌握不同技术栈知识 ,独立完成全栈开发,从容应对复杂需求。甚至开发者应该将更多精力集中在核心业务实现上,深入理解业务需求,关注前线业务场景,与业务团队紧密合作,设计出更符合用户期望的功能和体验,提升产品质量。

你对 AI 的看法是什么,可以评论区讨论下 !

相关推荐
咖啡の猫8 小时前
Vue消息订阅与发布
前端·javascript·vue.js
GIS好难学9 小时前
Three.js 粒子特效实战③:粒子重组效果
开发语言·前端·javascript
申阳9 小时前
Day 2:我用了2小时,上线了一个还算凑合的博客站点
前端·后端·程序员
刺客_Andy9 小时前
React 第四十七节 Router 中useLinkClickHandler使用详解及开发注意事项案例
前端·javascript·react.js
爱分享的鱼鱼9 小时前
Java实践之路(一):记账程序
前端·后端
爱编码的傅同学9 小时前
【HTML教学】成为前端大师的入门教学
前端·html
秋枫969 小时前
使用React Bootstrap搭建网页界面
前端·react.js·bootstrap
不一样的少年_9 小时前
上班摸鱼看掘金,老板突然出现在身后...
前端·javascript·浏览器
Crystal32810 小时前
background属性经典应用(视觉差效果/绘制纸张/绘制棋盘)
前端·css