-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
单枪匹马撸个聊天室, 支持Web/Android/iOS三端 #3
Comments
开源代码吗? |
mark |
666 |
不错 |
awesome啊,令人惊叹的作品 |
@pengliheng axios 不就是 请求吗。都请求了。const res 是用来存数据的啊。 |
@yinxin630 准备参考这个,撸个vue版本的!! |
先mark 又6又萌 简直不要太优秀 |
仰望大佬 |
仰望js大佬,表情包不错~ |
准备参考这个,撸个vue版本的 |
加油 |
大佬项目可以,最近在学习angular,回头参考这个撸一个ng版的 |
good job |
有vue版本的吗? |
没有 |
不错 学习了 |
mark |
大佬你好,在npm run build的时候出现: |
安装依赖了么 |
大佬,能说明下用react hooks 下拉无限加载历史信息,每次加载滚动条位置还能不动的原理吗 |
前排提醒, 阅读本文需要对JavaScript较为熟悉, 本文将讲解核心功能点的设计思路
源码地址: https://github.com/yinxin630/fiora
在线地址: https://fiora.suisuijiang.com/
前言
该项目起始于2015年底, 也是我刚开始学习 JavaScript 的时候, 当时仅仅是想做个练手项目. 后面随着在前端领域的深入学习, 也一直在更新技术栈, 目前已经是重构后的第五个版本
该图是使用 https://coggle.it/ 制作的
得益于
node.js
和react-native
的出现, 使得 jser 的触手伸到了服务端和APP端. 本项目服务端基于 node.js 技术, 使用了 koa 框架, 所有数据存储在 mongodb 中. 客户端使用 react 框架, 使用 redux 和 immutable.js 管理状态, 自己设计了一套简约范的UI风格, APP端基于 react-native 和 expo 开发. 项目部署在我的乞丐版阿里云ECS上, 学生机配置单核1G内存服务端架构
服务端负责两件事:
服务端使用了 koa-socket 这个包, 它集成了 socket.io 并实现了 socket 中间件机制, 服务端基于该中间件机制, 自己实现了一套接口路由
每个接口都是一个 async 函数, 函数名即接口名, 同时也是 socket 事件名
然后写了个
route
中间件, 用来完成路由匹配, 当判断路由匹配时, 以ctx
对象作为参数执行路由方法, 并将方法返回值作为接口返回值还有一个重要中间件是
catchError
, 它负责捕获全局异常, 业务流程中大量使用assert
判断业务逻辑, 不满足条件时会中断流程并返回错误消息, catchError 将捕获业务逻辑异常, 并取出错误消息返回给客户端这些就是服务端的核心逻辑, 基于该架构下定义接口组成业务逻辑
另外, 服务端还负责提供 index.html 响应, 即客户端首页. 客户端的其它资源是放在 CDN 上的, 这样可以缓解服务端带宽压力, 但是 index.html 不能使用强缓存, 因为会使得客户端更新不可控, 因此 index.html 放在服务端
客户端架构
客户端使用 socket.io-client 连接服务端, 连接成功后请求接口尝试登录, 如果 localStorage 没有 token 或者接口返回 token 过期, 将会以游客身份登录, 登录成功会返回用户信息以及群组、好友列表, 接着去请求各群组、好友的历史消息
客户端需要监听 connect / disconnect / message 三个消息
connect
: socket 连接成功disconnect
socket 连接断开message
接收到新消息客户端使用 redux 管理数据, 需要被组件共享的数据放在 redux 中, 只有自身使用的数据还是放在组件的 state 中, 客户端存储的 redux 数据结构如下:
客户端的数据流, 主要有两条线路
用户系统
User Schema 定义:
createTime
: 创建时间lastLoginTime
: 最后一次登录时间, 用来清理僵尸号用username
: 用户昵称, 同时也是账号salt
: 加密盐password
: 用户密码avatar
: 用户头像URL地址用户注册
注册接口需要
username
/password
两个参数, 首先做判空处理然后判断用户名是否已存在, 同时获取默认群组, 新注册用户要加入到默认群组
存密码明文肯定是不行的, 生成随机盐, 并使用盐加密密码
给用户一个随机默认头像, 全都是萌妹子
^_^
, 保存用户信息到数据库将用户添加到默认群组, 然后生成用户 token
token 是用来免密码登录的凭证, 存储在客户端 localStorage, token里携带用户id、过期时间、客户端信息三个数据,用户id和过期时间容易理解, 客户端信息是为了防token盗用, 之前也试过验证客户端ip一致性, 但是ip可能会有经常改变的情况, 搞得用户每次自动登录都被判定为盗用了...
将用户id与当前 socket 连接关联, 服务端是以
ctx.socket.user
是否为 undefined 来判断登录态的更新 Socket 表中当前 socket 连接信息, 后面获取在线用户会取 Socket 表数据
最后将数据返回客户端
用户登录
fiora 是不限制多登陆的, 每个用户都可以在无限个终端登录
登录有三种情况:
游客登录仅能查看默认群组消息, 并且不能发消息, 主要是为了降低第一次来的用户的体验成本
token登录是最常用的, 客户端首先从 localStorage 取 token, token 存在就会使用 token 登录
首先对 token 解码取出负载数据, 判断 token 是否过期以及客户端信息是否匹配
从数据库查找用户信息, 更新最后登录时间, 查找用户所在的群组, 并将 socket 添加到该群组, 然后查找用户的好友
更新 socket 信息, 与注册相同
最后返回数据
用户名/密码与 token 登录仅一开始的逻辑不同, 没有解码 token 验证数据这步
先验证用户名是否存在, 然后验证密码是否匹配
接下来逻辑就与 token 登录一致了
消息系统
发送消息
sendMessage 接口有三个参数:
to
: 发送的对象, 群组或者用户type
: 消息类型content
: 消息内容因为群聊和私聊共用这一个接口, 所以首先需要判断是群聊还是私聊, 获取群组id或者用户id, 群聊/私聊通过 to 参数区分
群聊时 to 是相应的群组id, 然后获取群组信息
私聊时 to 是发送者和接收者二人id拼接的结果, 去掉发送者id就得到了接收者id, 然后获取接收者信息
部分消息类型需要做些处理, text消息判断长度并做xss处理, invite消息判断邀请的群组是否存在, 然后将邀请人、群组id、群组名等信息存储到消息体中
将新消息存入数据库
接下来构造一个不包含敏感信息的消息数据, 数据中包含发送者的id、用户名、头像, 其中用户名和头像是比较冗余的数据, 以后考虑会优化成只传一个id, 客户端维护用户信息, 通过id匹配出用户名和头像, 能节约很多流量
如果是群聊消息, 直接把消息推送到对应群组即可
私聊消息更复杂一些, 因为 fiora 是允许多登录的, 首先需要推送给接收者的所有在线 socket, 然后还要推送给自身的其余在线 socket
最后把消息数据返回给客户端, 表示消息发送成功. 客户端为了优化用户体验, 发送消息时会立即在页面上显示新信息, 同时请求接口发送消息. 如果消息发送失败, 就删掉该条消息
获取历史消息
getLinkmanHistoryMessages 接口有两个参数:
linkmanId
: 联系人id, 群组或者俩用户id拼接existCount
: 已有的消息个数详细逻辑比较简单, 按创建时间倒序查找已有个数 + 每次获取个数数量的消息, 然后去掉已有个数的消息再反转一下, 就是按时间排序的新消息
返回给客户端
接收推送消息
客户端订阅 message 事件接收新消息
socket.on('message')
接收到新消息时, 先判断 state 中是否存在该联系人, 如果存在则将消息存到对应的联系人下, 如果不存在则是一条临时会话的消息, 构造一个临时联系人并获取历史消息, 然后将临时联系人添加到 state 中. 如果是来自自己其它终端的消息, 则不需要创建联系人
如果当前聊天页是在后台的, 并且打开了消息通知开关, 则会弹出桌面提醒
如果打开了声音开关, 则响一声新消息提示音
如果打开了语言播报开关并且是文本消息, 将消息内的url和#过滤掉, 排除长度大于200的消息, 然后推送到消息朗读队列中
更多中间件
限制未登录请求
大多数接口是只允许已登录用户访问的, 如果接口需要登录且 socket 连接没有用户信息, 则返回"未登录"错误
限制调用频率
为了防止刷接口的情况, 减轻服务器压力, 限制同一 socket 连接每分钟内最多请求 30 次接口
小黑屋
管理员账号可以将用户添加到小黑屋, 被添加到小黑屋的用户无法请求任何接口, 10分钟后自动解禁
其它有意思的东东
表情
表情是一张雪碧图, 点击表情会向输入框插入格式为
#(xx)
的文本, 例如#(滑稽)
. 在渲染消息时, 通过正则匹配将这些文本替换为<img>
, 并计算出该表情在雪碧图中的位置, 然后渲染到页面上不设置 src 会显示一个边框, 需要将 src 设置为一张透明图
表情包搜索
爬的 https://www.doutula.com 上的搜索结果
桌面消息通知
效果如上图, 不同系统/浏览器在样式上会有区别
经常有人问到这个是怎么实现的, 其实是 HTML5 增加的功能
Notification
, 更多信息查看 https://developer.mozilla.org/en-US/docs/Web/API/notification粘贴发图
监听
paste
事件, 获取粘贴内容, 如果包含Files
类型内容, 则读取内容并生成Image
对象. 注意: 通过该方式拿到的图片, 会比原图片体积大很多, 因此最好压缩一下再使用语言播报
这是用的百度的语言合成服务, 感谢百度. 详情请查看 http://ai.baidu.com/tech/speech/tts
历史版本
最初的版本
改了下背景和样式
基于react重写, 定下了
fiora
名称风格开始偏向二次元, 加了些新功能
一个没有上线过的实验版本
目前线上跑的版本
后话
如果你对 Fiora 还有什么疑问, 可以随时来 https://fiora.suisuijiang.com/ 交流, 本人每天都会在线
The text was updated successfully, but these errors were encountered: