从前端到后台(一个聊天项目带你撸全栈)

差不多花了整整两个星期,终于把这个聊天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.jsexpressexpress-sessionexpress-wsmongodbmongoose
后台逻辑分析 初次写后台,最难的可能就是架构了,因为你要对整个应用的需求、实现的功能、数据的模型等有一个清晰的思路逻辑。我可能也就是在这方面花的时间是最多的,总是不知道该如何下手。很多次都是写着写着就写不下去了,因为逻辑行不通了。
遇到的问题和难点
  • 如何判断用户是否是登录状态?如何记住用户的登录状态?
  • 如何断定当前登录的用户是否成功连接了WebSocket服务器?
  • 当一个来自客户端的websocket请求时,如何判断该用户是否已登录?需要一个身份识别功能,否则谁都可以任意接入websockt服务器了。
  • HTTP服务器与WebSocket服务器之间如何并存?又如何交互?因为只有聊天功能和消息推送功能使用ws,其他所有的请求都是与http服务器通信。
  • ws服务器如何判断消息的转发目标?如果目标用户不在线又如何处理?
  • 如何搭建数据库?对于初次接触的人来说这也是个难题。
  • 如何连接和操作数据库?起码要基本的增删改查。
  • 密码加密问题,这同样是一个很大的难题。
其实问题还有很多很多,这可能对于后台开发人员来说都显得小儿科,但这些真是我开发过程中遇到的问题,当然还不止如此。到目前为止,有的问题已经解决了,有的问题仍未解决,或没有找到更好的解决方案。
其实,学习也就是一个发现问题,然后解决问题的过程。当你把一个一个的问题都解决之后,你也就在不知不觉中慢慢成长起来了。贵在坚持,也难在坚持。
模块介绍
对于上面的问题,我也是自己网上找资料,目前主要引用到了这些模块框架:
  • express:基本上是整个后台应用的支撑,HTTP和ws都是建立在此基础之上。一个Node.js上很强大的东西,可以让你快速创建一个Web应用。
  • express-session:这个是express的插件,主要用来解决上面说到的判断用户是否登录的问题。
  • express-ws:这也是一个express的插件,用来构件一个ws服务器。之前采用的是ws框架,但与express交互性太差,不好在wshttp之间通信。
  • body-parser:一个express框架,主要用来解析POST请求发过来的数据。
  • mongoose:一个用来操作mongodb数据库的框架。还有一个叫做mongolass的框架,比这个量级要轻。
主目录结构
由于是第一次写后台,后台结构分的并不是很清晰。
  • index.js:入口文件,创建一个http服务器和一个ws服务器,并连接到数据库。
  • model:该目录主要写一些与数据库交互的代码。
  • routes:这个目录主要处理路由,大部分的操作都是在该目录下进行的。
主要的架构就是这样,基本操作都在routes目录下,因为后台也就是为前端写接口。在routes目录下又分了不同的子路由,比如:frienduserwsmessage等,分别处理不同的请求。
看起来很简单,但做起来真的不容易,最可怕的是代码量大了,你会陷入一个大量重复代码和无限回调的噩梦,我想大部分人都经历过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,使用起来简单。
后记 我一般写文章都是针对自己实际遇到的问题来的,我目前也是在不断的学习中,过几天就会写一篇文章作个总结。

    推荐阅读