从前端到后台:一个聊天项目带你撸全栈
差不多花了整整两个星期,终于把这个聊天APP的后台架构搭建出来了。虽然花的时间比较多,但这也是我第一次写后台,其实也并没有想象中的那么难,但也还是很折腾,尤其是在数据库这一块,几乎全部都是英文文档(看得都只想**)。
项目概述
该聊天App高仿iOS端的微信,当然没这么复杂,目前已实现功能有:
- 用户注册、登录、注销功能;
- 自动缓存已登录用户,关闭浏览器窗口失效;
- 聊天室:所以在线用户之间聊天;
- 与在线用户之间聊天;
- 获取所有在线用户;
- 获取好友列表;
- 添加好友:后台接口已完成,前端目前尚未实现。
现在几乎每天都在更新,争取把它做得更像一个正规的聊天应用。不过由于该应用是基于Web页面的,用户体验和数据持久化等诸多方面肯定没法跟客户端应用相比。
前端Web界面
前端界面在一个多月前就已经差不多写出来了,苦于一直没有后台接口(API)的支持,所以仅仅只是一个界面展示,并无实际聊天的功能。
- github地址:https://github.com/moohng/wchat-vue
- 在线测试地址:http://mohng.com/wchat-vue
对前端我就不做深入的介绍了,主要是基于Vue
来实现的。而且对于一个前端开发者来说,后台实现可能更具有挑战性。
后台实现
为了实现真实的聊天功能,我决定自己来搭建后台,这也是我第一次写后台。整个后台应用基于Node.js
平台,采用express
模块来搭建HTTP服务器,聊天功能采用WebSocket
实现,数据库使用的是MongoDB
。
- github地址:https://github.com/moohng/wchat-sv
主要使用的技术栈包括:Node.js
、express
、express-session
、express-ws
、mongodb
、mongoose
。
后台逻辑分析
初次写后台,最难的可能就是架构了,因为你要对整个应用的需求、实现的功能、数据的模型等有一个清晰的思路逻辑。我可能也就是在这方面花的时间是最多的,总是不知道该如何下手。很多次都是写着写着就写不下去了,因为逻辑行不通了。
遇到的问题和难点
- 如何判断用户是否是登录状态?如何记住用户的登录状态?
- 如何断定当前登录的用户是否成功连接了
WebSocket
服务器? - 当一个来自客户端的
websocket
请求时,如何判断该用户是否已登录?需要一个身份识别功能,否则谁都可以任意接入websockt
服务器了。 - HTTP服务器与
WebSocket
服务器之间如何并存?又如何交互?因为只有聊天功能和消息推送功能使用ws
,其他所有的请求都是与http服务器通信。 -
ws
服务器如何判断消息的转发目标?如果目标用户不在线又如何处理? - 如何搭建数据库?对于初次接触的人来说这也是个难题。
- 如何连接和操作数据库?起码要基本的增删改查。
- 密码加密问题,这同样是一个很大的难题。
其实问题还有很多很多,这可能对于后台开发人员来说都显得小儿科,但这些真是我开发过程中遇到的问题,当然还不止如此。到目前为止,有的问题已经解决了,有的问题仍未解决,或没有找到更好的解决方案。
其实,学习也就是一个发现问题,然后解决问题的过程。当你把一个一个的问题都解决之后,你也就在不知不觉中慢慢成长起来了。贵在坚持,也难在坚持。
模块介绍
对于上面的问题,我也是自己网上找资料,目前主要引用到了这些模块框架:
-
express
:基本上是整个后台应用的支撑,HTTP和ws
都是建立在此基础之上。一个Node.js
上很强大的东西,可以让你快速创建一个Web应用。 -
express-session
:这个是express
的插件,主要用来解决上面说到的判断用户是否登录的问题。 -
express-ws
:这也是一个express
的插件,用来构件一个ws
服务器。之前采用的是ws
框架,但与express
交互性太差,不好在ws
和http
之间通信。 -
body-parser
:一个express
框架,主要用来解析POST
请求发过来的数据。 -
mongoose
:一个用来操作mongodb
数据库的框架。还有一个叫做mongolass
的框架,比这个量级要轻。
主目录结构
由于是第一次写后台,后台结构分的并不是很清晰。
-
index.js
:入口文件,创建一个http服务器和一个ws服务器,并连接到数据库。 -
model
:该目录主要写一些与数据库交互的代码。 -
routes
:这个目录主要处理路由,大部分的操作都是在该目录下进行的。
主要的架构就是这样,基本操作都在routes
目录下,因为后台也就是为前端写接口。在routes
目录下又分了不同的子路由,比如:friend
、user
、ws
、message
等,分别处理不同的请求。
看起来很简单,但做起来真的不容易,最可怕的是代码量大了,你会陷入一个大量重复代码和无限回调的噩梦,我想大部分人都经历过js
的回调噩梦。目前也只是有了个初步的逻辑架构,后面可能会根据需求的不同而变更。代码也需要优化,有的自己一遍一遍写起来就恶心。
两个容易误会的概念
本篇文章主要作个整体的介绍,因为该Web应用目前仍在开发中,很多功能还不确定,等后面整个逻辑清晰了再作总结。下面说两个很经典的问题,也是前端很容易误会的问题,至少我是误会了很久。
跨域
我的前端页面是托管在GitHub上的,通过开启静态页面的功能,可使用域名来访问http://mohng.com/wchat-vue。而我的后台是搭建在自己的服务器中的,所以自然就面临了一个问题:跨域访问。
在这之前,对跨域访问是一知半解,不知道到底该如何解决这个问题。这里要提出,跨域访问不是前端的问题,其实大部分都是后台的问题。对于跨域,网上有两种解决方案:JSONP和Ajax。对于JSONP没什么研究,不作介绍,好像也并不是很实用,这里主要介绍Ajax跨域的问题。
下面是我后台解决跨域问题的方案:
app.use((req, res, next) => {
res.set({
// 跨域cookie 不能为通配符 *
'Access-Control-Allow-Origin': 'http://localhost:8808',
'Access-Control-Allow-Methods': 'GET,POST',
// 跨域cookie必须为true
'Access-Control-Allow-Credentials': true
});
next();
});
简单的说一下,跨域其实浏览器是可以正常的收到来自于服务的响应,只是无法正确的解析。通过在服务器端对响应头写入'Access-Control-Allow-Origin': '*'
和'Access-Control-Allow-Methods': 'GET,POST'
,浏览器才能正确的解析服务器的响应。记住是在服务器端对响应头的操作,我之前一直误会是在前端的请求头中写入,现在想想有点傻逼了。
对于'Access-Control-Allow-Credentials': true
,是用来处理跨域中cookie的问题。因为默认情况下,cookie是不允许在跨域访问中传输的。要解决这个问题,Access-Control-Allow-Origin
的值就不能为通配符*
,并且前端通过Ajax发起请求时也要做处理。
$.ajax(url, {
method: 'GET',
xhrFields: {
withCredentials: true
},
...
})
Cookie
之前对Cookie
的认识一直就是一种类似于缓存的东西,但具体是做什么,怎么用,并不清楚。这是要指出两点:
- Cookie基本上都是由后台来管理的,前端不需要任何操作
- Cookie信息会在每次发起的请求中自动携带
那么,这下就清晰多了。如果你仅仅只是搞前端,基本上是用不到Cookie
的。虽然也可以通过js
代码读取到cookie
数据,但大部分服务器都是禁用掉此操作,也就是让你在前端无法通过js
代码读取到cookie
的内容,读取到的是空字符串。
因为cookie
是每次发起请求都会自动携带的,所以服务器就可以通过cookie
来识别用户的身份、是否处于登录状态等,就像你进入某个网站有时候会自动识别你的身份并登录。而cookie
也是可以设置过期时间的,所以服务器端就可以控制你的身份多久失效,失效之后你就要重新登录了。
你可以自己尝试在浏览器的控制台通过document.cookie
来获取一下网站的cookie
信息。也可以尝试清除浏览器的cookie
,然后再刷新你登录的网站,看是否需要重新登录。
我这个项目中用到的express-session
就是通过cookie
来识别用户身份的。使用express-session
的好处就是你不需要自己要操作cookie
,使用起来简单。
后记
我一般写文章都是针对自己实际遇到的问题来的,我目前也是在不断的学习中,过几天就会写一篇文章作个总结。