该仓库为聊天应用 coo 的前端项目,后端项目:coo-server。
- GUI 技术:
electron
。electron
是一种使用前端网页技术开发用户界面程序的跨操作系统的 GUI 技术,它将nodejs
运行时和浏览器运行时合成到一个运行时,让你使用前端网页技术开发界面的同时拥有完整的后端能力。 - 前端框架:
react
+react hooks
+react router
,整个项目全部使用函数组件进行开发。 - 状态管理:
redux
+redux-toolkit
+redux-persist
,redux-toolkit
是redux
官方维护的工具集,使用它可以减少用户配置,避免编写样板代码,提高开发效率。使用redux-persist
进行状态持久化,将应用状态保存在本地 JSON 文件,下次再次启动应用时可以恢复上次关闭应用时的状态。 - UI 库:
ant design v4
,可能是最早一批上 v4 船的开发者,使用 UI 库省去编写基础组件的时间,大大节省开发时间。 - 数据请求:
axios
,使用拦截器处理认证和请求错误。 - 语言:
TypeScript
,包括webpack
配置,整个前端项目基本上都是使用TypeScript
编写的。 - 打包方案:
webpack
,渲染进程的打包配置以及主进程自动重启都完全是定制的,使用DllPlugin
等手段优化打包速度。
- 开发语言:
nodejs
。 - 服务端框架:
koa2
,自定义了处理异常,静态文件服务中间件。 - 数据库:采用的是非关系型数据库
mongoDB
,使用的ORM
库是mongoose
。 - 服务端推送:目前采用的是
socket.io
,后续可能会替换成ws
。 - 认证方案:
RESTful API
和socket.io
都是使用JWT
进行认证,RESTful API
要求每次请求都在请求头Authorization
带上认证token
(少部分请求例如登入不需要),而socket.io
需要在建立链接时带上 token 参数进行认证。
假设有用户 userA
使用客户端 clientA
,用户 userB
使用客户端 clientB
,下面是 usetA
发送给 userB
一条消息 "hello"
的请求流程:
-
clientA
和clientB
启动时都会和服务器的socket.io
服务建立websocket
链接socketA
和socketB
,建立链接的 URL 参数会带上认证token
和已登入用户的id
,服务器会保存用户id
和对应socket
的映射,也就是说可以通过userB
的用户id
拿到socketB
。当我们需要对一个用户推送消息的时候只需要根据它的用户
id
拿到对应的socket
,然后调用socket.io
的 APIsocket.emit(eventName, data)
推送即可。 -
clientA
通过 RESTful API 发送 HTTP 请求给服务器,请求格式:# 该路由处理私信文本消息 POST /messages/private/text # from 表示发送方的用户 id,这里也就是 userA 的用户 id from=userA_id # to 表示接受方的用户 id,这里也就是 userB 的用户 id to=userB_id # content 表示请求内容,这里就是 "hello" content="hello"
-
服务器根据请求内容在
mongoDB
的message
集合中插入一条新的message
数据,设置该message
的状态为created
,再根据接受方用户id
即userB_id
获取到socketB
,使用该socketB
向clientB
推送消息,格式为:socket.emit( // 和聊天相关的消息都通过 chat 事件推送 'chat', // 消息内容 { // 发送方 id from:userA_id, // 场景是私聊 situation: 'private', // 发送内容是 'hello' content: 'hello', // 内容格式是文本消息 contentType: 'text', // 消息创建时间是以服务器数据库新增 message 的时间为准 createdAt: newMessage.createdAt, }, (data) => { // 接受到客户端响应后表示客户端已经接受消息 // 将数据库中对应的那条 message 的 status 更新为 received newMessage.status = 'received'; }, );
-
clientB
接受到服务端推送的上面那条消息,更新前端界面中的消息列表,将clientA
的会话项置顶。
当用户 userA
在群 group
中发送一消息 "hello"
时,请求流程是这样的:
-
客户端请求群聊消息接口:
# 该路由处理私信文本消息 POST /messages/group/text # from 表示发送方的用户 id,这里就是 userA 的用户 id from=userA_id # to 表示群 id to=group_id # content 表示请求内容 content="hello"
-
服务器根据请求内容在
mongoDB
的message
集合中插入一条新的message
数据,根据群id
查询群表中该群的记录,拿到在这个群的所有用户id
数组,遍历这个id
数组,向所有用户推送消息,消息格式:group.members.forEach((userId) => { const userIdStr = userId.toString(); if (userIdStr === from) return; const socket = ctx.sockets.get(userIdStr); if (socket) { socket.emit('chat', { // 发送消息的用户id, 也就是 userA 的 id from, // 发送消息的用户信息 fromUser: fromUser.toObject(), // 群 id groupId: to, // 场景是群消息 situation: 'group', // 发送内容,这里是 'hello' content, // 消息格式是文本格式 contentType: 'text', // 消息新建时间,以服务器为准 createdAt: newMessage.createdAt, }); } });
目前只支持邮箱注册,服务器不保存明文密码,保存的是 hmac
摘要。
支持私聊,群聊,消息格式支持文字,图片。
点击侧边栏头像进入,支持修改修改头像,昵称,密码。
支持搜索,添加,删除好友,支持创建群,解散群,搜索群,加入群。