# HasChatApp **Repository Path**: thc/has-chat-app ## Basic Information - **Project Name**: HasChatApp - **Description**: 一款极简聊天应用,比较完整,略好看 - **Primary Language**: JavaScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: https://howcode.online/haschatapp - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 90 - **Created**: 2025-07-22 - **Last Updated**: 2025-08-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

HasChat

基于uni-app+socket.io的聊天应用
[![star](https://gitee.com/howcode/has-chat-app/badge/star.svg)](https://gitee.com/howcode/has-chat-app)  [![fork](https://gitee.com/howcode/has-chat-app/badge/fork.svg)](https://gitee.com/howcode/has-chat-app)  [![](https://img.shields.io/badge/QQ群-149091283-red)](https://jq.qq.com/?_wv=1027&k=XivFMfBQ) ```shell 无偿开源!你们的Star是我的动力! ``` ---
# 介绍 自我做的客服聊天以来,让我做一套聊天应用的呼声越来越多,加上那套客服聊天由于没有组件化、UI 设计等问题,也让我一直心有遗憾做的不够完美,于是利用空余时间做了一套相对完整的聊天应用。HasChat 是一套使用全新技术完成的通讯聊天网页。 - [PC 网页版前端:Vue3 + Vite + TypeScript + Naive UI + Socket.io](https://gitee.com/howcode/has-chat) - [移动版前端:uni-app + Socket.io](https://gitee.com/howcode/has-chat-app) - [后端:Express.js](https://gitee.com/howcode/has-chat-service) 作者开源目的旨在给刚学习该领域的新人一些引路,不管你是前端还是后端,都能对你在通讯聊天这个领域有一点点的启发。 # 预览图 ![PC端](https://howcode.online/photo/project/zIQeBR.png) ![PC端](https://howcode.online/photo/project/zIQmH1.png) ![移动端](https://howcode.online/photo/project/pSeE2m8.jpg) # 在线预览 [haschat-PC 端](https://howcode.online/haschat) [haschat-移动端](https://howcode.online/haschatapp) # 功能一览 - 登陆、随机获取用户登陆 - 发送邮箱验证码注册 - 发送表情+文字组合的富文本内容 - 发送图片内容,查看大图 - enter 发送信息,enter+ctrl 换行输入内容 - 消息提醒 - 未读消息标记 - 记录历史会话 - 记录历史聊天内容 - 切换主题 - 发送视频 - 发送语音(仅移动端具备) - 社区发帖、点赞、评论、回复 # 版本说明 - 前端:暂无版本区别 - 后端:分为 mysql 版本(分支 main)和 json 版本(分支 master);json 版本主要是为了不懂 mysql 的新手学习; # 环境部署 ```javascript Node.Js >= 15.0.0 Mysql >= 5.7.0 (仅mysql版本需要,但执行mysql文件需要8.0以上版本) ``` ## 下载项目 前端 ```javascript https://gitee.com/howcode/has-chat.git ``` 后端 mysql 版本 ```javascript git clone -b main https://gitee.com/howcode/has-chat-service.git ``` 后端 json 版本 ```javascript git clone -b master https://gitee.com/howcode/has-chat-service.git ``` ## 启动项目 ### 安装依赖 前、后端 ```javascript npm install ``` ### mysql 配置(json 版本跳过) - 在 mysql 的版本中,找到目录 store 下的 sql 文件,运行 sql 文件 ![在这里插入图片描述](https://howcode.online/photo/md/p91dOwF.png) ![在这里插入图片描述](https://howcode.online/photo/md/p91dLeU.png) ![在这里插入图片描述](https://howcode.online/photo/md/p91dHyV.png) ![在这里插入图片描述](https://howcode.online/photo/md/p91dbLT.png) - 依次运行全部 sql 文件,并且刷新数据库表就可看到了 ![在这里插入图片描述](https://howcode.online/photo/md/p91wpS1.png) - 找到 config.js 文件 ```javascript const db = mysql.createConnection({ host: "", // 主机地址 (默认:localhost) user: "", // 用户名 password: "", // 密码 database: "", // 数据库 }); ``` 更多配置查看:[nodejs-mysql](https://www.runoob.com/nodejs/nodejs-mysql.html) ### 邮箱配置(json 版本跳过) - 找到 config.js 文件 ```javascript emailConfig: { //邮箱配置 host: "smtp.qq.com",//邮箱服务器 这里我用的QQ邮箱 port: 465,//邮箱使用端口 secure: true,//是否使用默认的465端口 auth: { user: "", // 发送方邮箱地址 pass: "" // smtp 验证码 } } ``` 详情使用可以查看该文章:[nodejs 发送邮箱信息](https://www.cnblogs.com/kusaki/p/11801769.html) ### 启动项目/服务 - 后端 ```javascript node app.js ``` - 前端 ```javascript npm run dev ``` _到此,项目可以正常运行_ # 视频教程 [本地构建 HasChat 项目](https://www.bilibili.com/video/bv1iY411K7La) [宝塔部署 HasChat 项目](https://www.bilibili.com/video/BV1wd4y117pj) # 项目目录 ```javascript haschat ├── src │ └── api // 接口文件 │ └── assets // 资源文件 │ └── css // css样式 │ └── emo // 表情包 │ └── icon // 字体图标 │ └── img // 图片资源 │ └── mp3 // 消息提示音频 │ └── class // 类文件 │ └── components // 组件 │ └── ChatContent.vue // 聊天窗口 │ └── ChatDomain.vue // 功能组件 │ └── ChatEditor.vue // 聊天输入框 │ └── ChatFoot.vue // 聊天框底部 │ └── ChatHead.vue // 聊天头部 │ └── ChatNav.vue // 菜单栏 │ └── HasChat.vue // 聊天入口 │ └── enums // 通用枚举 │ └── json // JSON数据处理 │ └── router // 路由 │ └── store // 数据仓库 │ └── utils // 工具 │ └── views // 页面 ``` # 重大升级日志 - 2022.12.14 原先的历史会话使用`user`表的`HistorySessionList`字段记录,但随着用户量的提升,这种方式的弊端越来越明显,严重影响了查询性能,故新建`history_session`表来记录用户的历史会话记录,如果需要将会话历史会话记录迁移到新表,使用`/api/changeHistorySession`接口来迁移数据 - 2022.12.14 原先的会话列表内容在 socket 的`joinSuccess`方法返回,但这种方式随着数据量的增多,导致进程严重阻塞,故单独改成`/api/getContentBySendId`接口分页调用 # 免责声明 一、本项目宗旨在于为广大的正在学习通讯方面的新手提供学习、思路 二、本项目资源全部免费分享,包括前端源码、后端转源码等。本项目不会利用任何资源进行任何的销售盈利活动。 三、任何情况下,因使用本项目进行违法犯罪的行为,本人不承担任何法律责任。 # 未来计划 - 发送视频功能(已完成) - 发送语音功能(已完成) - uni-app 开发混合 app/h5/小程序版本(已完成) - 将历史会话接口改成分页查询提高查询速度(已完成) - 将会话消息改成单独接口调用(已完成) - 群聊功能 # 部署文档 ![戴戴的Linux](https://gitee.com/StephenJose_Dai/dockerhaschat/raw/master/daidailinux.jpg) - 微信扫一扫二维码关注公众号 - 发送haschatdocker可获取docker版的haschat部署教程 - 发送haschat可获取手动部署教程 - 发送haschatbt可获取宝塔部署教程 # 交流群 - [![](https://img.shields.io/badge/QQ群-149091283-red)](https://jq.qq.com/?_wv=1027&k=XivFMfBQ)(关于 HasChat 的技术解答) - 微信群,添加`howcoder`微信(关于技术、行业、兴趣交流) # 😊 捐助作者
👍👍👍👍👍👍 您的捐助和赞赏,将会是作者howcode更好的维护动力! # 伊言一语 这是一个基于uni-app开发的智能咨询助手应用,支持与AI模型进行对话咨询。 ## 功能特点 - 智能咨询:与AI模型进行对话咨询 - 历史记录:保存历史咨询记录,方便随时查看 - 多媒体支持:支持发送文字、图片、语音、视频等多种消息类型 ## 页面结构 - 咨询页面:显示历史咨询记录,支持新建咨询 - 聊天页面:与AI模型进行对话交流 - 我的页面:用户个人信息和设置 ## 使用方法 1. 打开应用,进入咨询页面 2. 点击"新建咨询"按钮,开始新的咨询会话 3. 在聊天界面输入问题,与AI模型进行对话 4. 返回咨询页面可以查看历史咨询记录 ## AI模型信息 默认使用default模型,作为Cursor IDE的核心功能之一,可协助完成各类开发任务。 ## 后端API接口文档 应用支持与后端API交互,实现数据持久化存储。以下是主要API接口: ### 1. 获取咨询列表 ``` GET /api/consultation/list ``` **响应示例:** ```json { "code": 200, "message": "success", "data": [ { "id": "1234567890", "title": "咨询标题", "lastMessage": "最后一条消息", "lastTime": "2023-07-01T12:00:00.000Z", "unreadCount": 0, "avatar": "../../static/img/ai_avatar.png" } ] } ``` ### 2. 获取咨询消息 ``` GET /api/consultation/messages?id={consultationId} ``` **响应示例:** ```json { "code": 200, "message": "success", "data": [ { "id": "msg123456", "SendId": "ai", "ReciverId": "user123", "Content": "

您好,我是由default模型提供支持...

", "Type": 0, "State": 1, "NoCode": "1234567890", "CreateDateUtc": "2023-07-01T12:00:00.000Z", "Avatar": "../../static/img/ai_avatar.png", "ReadFlag": true, "SoundStatus": 0 } ] } ``` ### 3. 创建咨询 ``` POST /api/consultation/create ``` **请求参数:** ```json { "title": "新咨询", "avatar": "../../static/img/ai_avatar.png" } ``` **响应示例:** ```json { "code": 200, "message": "success", "data": { "id": "1234567890", "title": "新咨询", "lastMessage": "开始您的智能咨询吧", "lastTime": "2023-07-01T12:00:00.000Z", "unreadCount": 0, "avatar": "../../static/img/ai_avatar.png" } } ``` ### 4. 发送咨询消息 ``` POST /api/consultation/message/send ``` **请求参数:** ```json { "consultationId": "1234567890", "message": { "SendId": "user123", "ReciverId": "ai", "Content": "

您好,我有个问题

", "Type": 0, "Avatar": "../../static/img/user_avatar.png" } } ``` **响应示例:** ```json { "code": 200, "message": "success", "data": { "id": "msg123456", "SendId": "user123", "ReciverId": "ai", "Content": "

您好,我有个问题

", "Type": 0, "State": 1, "NoCode": "1234567890", "CreateDateUtc": "2023-07-01T12:00:00.000Z", "Avatar": "../../static/img/user_avatar.png", "ReadFlag": false, "SoundStatus": 0 } } ``` ### 5. 删除咨询 ``` POST /api/consultation/delete ``` **请求参数:** ```json { "id": "1234567890" } ``` **响应示例:** ```json { "code": 200, "message": "success", "data": true } ``` ## 开发注意事项 1. 应用优先使用后端API存储数据,当API不可用时会回退到本地存储模式 2. 本地存储仅作为临时方案,不保证长期数据持久性 3. 要实现完整功能,需要按照API文档实现相应的后端服务 # 伊言一语后端API文档 ## Socket.io 事件列表 ### 客户端发送事件 1. **joinChat** - 客户端连接时发送 ```javascript { SendId: "用户ID", SendName: "用户名称", ReviceId: -1, ReviceName: "", NoCode: "唯一码" } ``` 2. **sendMsg** - 发送消息 ```javascript { Conversition: { id: "消息ID", SendId: "发送者ID", ReciverId: "接收者ID", // 0 表示发给AI Content: "

消息内容

", Type: 0, // 0: 文本, 1: 图片, 2: 视频, 3: 语音 State: 0, // 0: 发送中, 1: 已发送 NoCode: "唯一码", CreateDateUtc: "2023-07-01T12:00:00.000Z", Avatar: "头像URL", ReadFlag: false, SoundStatus: 0, consultationId: "咨询ID" // 新增字段,标识所属咨询 }, ReciverId: 0, // 接收者ID,0 表示发给AI Sender: { /* 发送者信息 */ }, consultationId: "咨询ID", // 咨询ID isConsultation: true // 标记这是一个咨询消息 } ``` 3. **getConsultationList** - 获取咨询列表 ```javascript { userId: "用户ID" } ``` 4. **getConsultationMessages** - 获取咨询消息 ```javascript { consultationId: "咨询ID", userId: "用户ID" } ``` 5. **createConsultation** - 创建咨询 ```javascript { id: "咨询ID", title: "咨询标题", lastMessage: "最后一条消息", lastTime: "2023-07-01T12:00:00.000Z", unreadCount: 0, avatar: "头像URL", userId: "用户ID" } ``` 6. **deleteConsultation** - 删除咨询 ```javascript { consultationId: "咨询ID", userId: "用户ID" } ``` ### 服务端发送事件 1. **reviceMsg** - 接收消息 ```javascript { // 消息对象,与sendMsg中的Conversition结构相同 ReciverId: "接收者ID", SendId: "发送者ID", Content: "

消息内容

", // ...其他字段 isConsultation: true // 标记这是一个咨询消息 } ``` 2. **aiResponse** - AI回复 ```javascript { Content: "

AI回复内容

", consultationId: "咨询ID" } ``` 3. **consultationList** - 咨询列表响应 ```javascript [ { id: "咨询ID", title: "咨询标题", lastMessage: "最后一条消息", lastTime: "2023-07-01T12:00:00.000Z", unreadCount: 0, avatar: "头像URL" }, // ...更多咨询 ] ``` 4. **consultationMessages** - 咨询消息响应 ```javascript [ { id: "消息ID", SendId: "发送者ID", // 0 表示AI发送 ReciverId: "接收者ID", Content: "

消息内容

", Type: 0, State: 1, NoCode: "唯一码", CreateDateUtc: "2023-07-01T12:00:00.000Z", Avatar: "头像URL", ReadFlag: false, SoundStatus: 0, consultationId: "咨询ID" }, // ...更多消息 ] ``` 5. **consultationCreated** - 咨询创建响应 ```javascript { id: "咨询ID", title: "咨询标题", lastMessage: "最后一条消息", lastTime: "2023-07-01T12:00:00.000Z", unreadCount: 0, avatar: "头像URL" } ``` 6. **consultationDeleted** - 咨询删除响应 ```javascript { success: true, consultationId: "咨询ID" } ``` ## 后端实现指南 ### 1. 数据库表结构 #### 咨询表 (consultations) ```sql CREATE TABLE consultations ( id VARCHAR(50) PRIMARY KEY, title VARCHAR(100) NOT NULL, lastMessage TEXT, lastTime DATETIME, unreadCount INT DEFAULT 0, avatar VARCHAR(255), userId VARCHAR(50) NOT NULL, createTime DATETIME DEFAULT CURRENT_TIMESTAMP, updateTime DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); ``` #### 咨询消息表 (consultation_messages) ```sql CREATE TABLE consultation_messages ( id VARCHAR(50) PRIMARY KEY, consultationId VARCHAR(50) NOT NULL, SendId VARCHAR(50) NOT NULL, ReciverId VARCHAR(50) NOT NULL, Content TEXT, Type INT DEFAULT 0, State INT DEFAULT 0, NoCode VARCHAR(50), CreateDateUtc DATETIME, Avatar VARCHAR(255), ReadFlag BOOLEAN DEFAULT FALSE, SoundStatus INT DEFAULT 0, createTime DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (consultationId) REFERENCES consultations(id) ON DELETE CASCADE ); ``` ### 2. Socket.io 事件处理 #### 连接处理 ```javascript io.on('connection', (socket) => { console.log('用户连接: ' + socket.id); // 加入聊天 socket.on('joinChat', (data) => { // 存储用户信息 socket.userId = data.SendId; socket.userName = data.SendName; // 加入用户房间 socket.join(data.SendId); console.log(`用户 ${data.SendName}(${data.SendId}) 加入聊天`); }); // 断开连接 socket.on('disconnect', () => { console.log('用户断开连接: ' + socket.id); }); // 其他事件处理... }); ``` #### 发送消息处理 ```javascript socket.on('sendMsg', async (data) => { try { // 检查是否是咨询消息 if (data.isConsultation) { // 保存咨询消息到数据库 const message = data.Conversition; await db.query( 'INSERT INTO consultation_messages (id, consultationId, SendId, ReciverId, Content, Type, State, NoCode, CreateDateUtc, Avatar, ReadFlag, SoundStatus) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [message.id, data.consultationId, message.SendId, message.ReciverId, message.Content, message.Type, message.State, message.NoCode, message.CreateDateUtc, message.Avatar, message.ReadFlag, message.SoundStatus] ); // 更新咨询的最后消息和时间 await db.query( 'UPDATE consultations SET lastMessage = ?, lastTime = ? WHERE id = ?', [message.Content.replace(/<[^>]*>/g, ''), new Date().toISOString(), data.consultationId] ); // 如果是发送给AI的消息,生成AI回复 if (message.ReciverId === 0) { // 模拟AI处理延迟 setTimeout(() => { const aiResponse = { Content: '

您好,我是由default模型提供支持,作为Cursor IDE的核心功能之一,可协助完成各类开发任务,只要是编程相关的问题,都可以问我!您现在有什么想做的吗?

', consultationId: data.consultationId }; // 发送AI回复给客户端 io.to(message.SendId).emit('aiResponse', aiResponse); // 创建AI回复消息 const aiMessage = { id: Date.now().toString(), consultationId: data.consultationId, SendId: 0, ReciverId: message.SendId, Content: aiResponse.Content, Type: 0, State: 1, NoCode: Date.now() + 1 + "", CreateDateUtc: new Date().toISOString(), Avatar: '../../static/img/ai_avatar.png', ReadFlag: false, SoundStatus: 0 }; // 保存AI回复消息到数据库 db.query( 'INSERT INTO consultation_messages (id, consultationId, SendId, ReciverId, Content, Type, State, NoCode, CreateDateUtc, Avatar, ReadFlag, SoundStatus) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [aiMessage.id, aiMessage.consultationId, aiMessage.SendId, aiMessage.ReciverId, aiMessage.Content, aiMessage.Type, aiMessage.State, aiMessage.NoCode, aiMessage.CreateDateUtc, aiMessage.Avatar, aiMessage.ReadFlag, aiMessage.SoundStatus] ); // 更新咨询的最后消息和时间 db.query( 'UPDATE consultations SET lastMessage = ?, lastTime = ? WHERE id = ?', [aiResponse.Content.replace(/<[^>]*>/g, ''), new Date().toISOString(), data.consultationId] ); // 发送消息给接收者 io.to(aiMessage.ReciverId).emit('reviceMsg', { ...aiMessage, isConsultation: true }); }, 1000); } // 发送消息给接收者 if (message.ReciverId !== 0) { io.to(message.ReciverId).emit('reviceMsg', { ...message, isConsultation: true }); } } else { // 处理普通聊天消息(原有逻辑) // ... } } catch (error) { console.error('发送消息错误:', error); } }); ``` #### 获取咨询列表 ```javascript socket.on('getConsultationList', async (data) => { try { // 从数据库获取咨询列表 const [consultations] = await db.query( 'SELECT * FROM consultations WHERE userId = ? ORDER BY lastTime DESC', [data.userId] ); // 发送咨询列表给客户端 socket.emit('consultationList', consultations); } catch (error) { console.error('获取咨询列表错误:', error); socket.emit('consultationList', []); } }); ``` #### 获取咨询消息 ```javascript socket.on('getConsultationMessages', async (data) => { try { // 从数据库获取咨询消息 const [messages] = await db.query( 'SELECT * FROM consultation_messages WHERE consultationId = ? ORDER BY CreateDateUtc ASC', [data.consultationId] ); // 发送咨询消息给客户端 socket.emit('consultationMessages', messages); } catch (error) { console.error('获取咨询消息错误:', error); socket.emit('consultationMessages', []); } }); ``` #### 创建咨询 ```javascript socket.on('createConsultation', async (data) => { try { // 保存咨询到数据库 await db.query( 'INSERT INTO consultations (id, title, lastMessage, lastTime, unreadCount, avatar, userId) VALUES (?, ?, ?, ?, ?, ?, ?)', [data.id, data.title, data.lastMessage, data.lastTime, data.unreadCount, data.avatar, data.userId] ); // 发送创建成功响应给客户端 socket.emit('consultationCreated', data); } catch (error) { console.error('创建咨询错误:', error); socket.emit('consultationCreated', null); } }); ``` #### 删除咨询 ```javascript socket.on('deleteConsultation', async (data) => { try { // 从数据库删除咨询 await db.query( 'DELETE FROM consultations WHERE id = ? AND userId = ?', [data.consultationId, data.userId] ); // 从数据库删除咨询消息 await db.query( 'DELETE FROM consultation_messages WHERE consultationId = ?', [data.consultationId] ); // 发送删除成功响应给客户端 socket.emit('consultationDeleted', { success: true, consultationId: data.consultationId }); } catch (error) { console.error('删除咨询错误:', error); socket.emit('consultationDeleted', { success: false, consultationId: data.consultationId }); } }); ``` ## 注意事项 1. 确保数据库连接稳定,建议使用连接池 2. 添加适当的错误处理和日志记录 3. 考虑添加消息队列,处理高并发场景 4. 实现用户认证和授权,确保安全性 5. 对敏感数据进行加密处理 6. 定期备份数据库 7. 考虑添加消息读取状态更新功能 8. 实现消息分页加载,提高性能