LeanCloud移植到Parse的坑

Parse基于Node.js的后端框架,被Facebook收购后开源;LeanCloud为国内优秀的开箱即用后端平台,支持数据库、文件存储、即时通讯、推送、短信服务、邮件服务、云引擎等功能。

为什么会考虑从LeanCloud移植到Parse呢,当然是自主掌控性和价格。自主掌控性:之前发生过LeanCloud因为容器中的客户违规导致整个LeanCloud的API域名被CNNIC封掉一段时间,也发生过某个容器流量突增,导致其他容器受影响的情况。价格:LeanCloud按照API请求次数收费,较为昂贵。

架构:所有后端逻辑放在阿里云主机的Docker中的Parse中跑,MongoDB放在同一台主机的Docker中跑或者购买阿里云专门的MongoDB服务;短信和邮件、文件存储使用阿里云和LeanCloud双备份;即时通讯服务使用LeanCloud;推送服务使用JPush和LeanCloud双备份。所有双备份的服务,可以通过环境变量来配置。

移植的过程中充满了艰辛,本文记录2018年初成功将LeanCloud中运行的后端逻辑移植到阿里云中的Docker中的Parse中遇到的坑。时过境迁,可能部分内容已不适用于当下的版本,或者当时对某些方面技术理解不够导致部分内容不够准确。

  1. LeanCloud中的beforeSave和beforeUpdate是分开的,但是Parse中是合并的,所以需要区分。针对LeanCloud中只需要执行beforeSave的情况,Parse中需要在beforeSave中让Update的情况放行;针对LeanCloud中只需要执行beforeUpdate的情况,Parse中需要beforeSave中让Save的情况放行;针对LeanCloud中既需要执行beforeUpdate又需要执行beforeSave并且逻辑还不一样的情况,Parse中需要在beforeSave中区别对待。Parse中区分beforeSave和beforeUpdate的方法是判断createdAt是否为undefined,是的话则是Save,否则为Update。
  2. LeanCloud中的afterSave和afterUpdate是分开的,但是Parse中是合并的,所以需要区分。针对LeanCloud中只需要执行afterSave的情况,Parse中需要在afterSave中将Update的情况屏蔽;针对LeanCloud中只需要执行afterUpdate的情况,Parse中需要afterSave中将Save的情况屏蔽;针对LeanCloud中既需要执行afterUpdate又需要执行afterSave并且逻辑还不一样的情况,Parse中需要在afterSave中区别对待。Parse中区分afterSave和afterUpdate的方法是判断createdAt和updatedAt是否相等,是的话则是Save,否则为Update。
  3. Parse的JS SDK不支持LeanCloud中的如下方式创建已知id的user对象:var user = AV.Object.createWithoutData(‘_User’, userId);只能使用如下方式创建var User = Parse.Object.extend(“_User”);  var user = User.createWithoutData(userId);
  4. Parse的云函数中要开启masterKey需要在save、find、get、delete、fetch等方法中加入如下option参数:{useMasterKey: true}
  5. Parse的云函数中不能通过object.get(‘ObjectId’)获取id,只能object.id。实际上ObjectId就是存储在对象的id字段的。如果强行将某个表中的某个字段的名称定为id,会导致保存Object时id这个字段无法保存数据 。
  6. Parse中无法在dashboard中设置表中某个字段的默认值,所以需要在beforeSave中实现。
  7. Parse中的CLP(Class Level Permission)表权限没有“登录用户”,只有公共和指定用户。所以LeanCloud中某个表的某个操作需要登录用户才赋予权限的时候,只能在Parse中直接public打钩。然后在beforeSave中设定ACL,只添加admin和当前用户的读写权限。但是由于ACL不像CLP那样可以指定插入时的权限,只能指定已有对象的权限,所以此时针对需要将create设置为登录用户才有的权限的时候就无能为力,而且又不能随意在beforeSave中手动判断request.user是否为空,因为这个Object可能是云函数保存的,而不是客户端直接操纵表,此时就没有user。此时唯一的解决方案是,判断request.master是true还是false,若为true则通过,若为false,再手动判断request.user不为空时认为是登录用户通过,前提是云函数内部所有操作都通过master权限。LeanCloud中的指定用户对应Parse中的public不打钩。
  8. Parse支持在MongoDB中分片存储文件,但是Parse中上传文件不会像LeanCloud那样产生一个_File表,并在表中生成一项,而是一切都是默默进行的。如果要可视化管理文件,必须自己建立一个File表并在这个表中开一个字段用来存放文件引用,然后每次上传文件之后,往这个表插入一项并在文件字段中放入文件Pointer。但是服务器上没有文件上传完成的hook,所以只能在客户端插入文件表,又会带来一些文件上传成功但是用来寄存文件的对象插入失败的问题,同时还有安全问题。一个解决方案是写一个云函数,其中一个参数是文件的Base64字符串,通过云函数将文件传给后端,后端云函数中保存文件并插入File表。
  9. 要在Dashboard中上传文件,必须在parse server和Dashboard中设置JavascriptKey。但是一旦设置了之后,Python使用REST API调试的时候也必须加入javascriptKey。
  10. 失去所有引用的文件,并不会消失,而且还无法通过REST API删除,只能通过mongodb的命令删除。
  11. 使用自带的GridStoreAdapter作为文件存储时publicServerURL必须设置为可外部访问的域名,才能保证文件的url正确。虽然寄存在某个对象中的文件的url字段会随着本地ip的改变而改变,但是无法得知外部ip或者外部url,所以在需要外部访问的情况下需要publicServerURL来配置。正因为这个特性,也导致了在某个对象引用了文件时,不能在上传完文件后只在某个字段保存文件url,而必须在某个字段中完整保存对文件对象的引用,才能保证在域名改变的情况下不影响文件的可访问性。但是由于Object或者File无法放在Array中,在一个对象需要对应多个文件的场景下(一个帖子多张图片),就无法实现。通过Relation的话,不知道Relation能不能存放文件,即使可以也不知道Relation保不保存顺序,即使保存顺序,Relation还需要一次异步查询,无法在获取到外层对象的时候直接获取到文件列表(获取到帖子列表时,直接获取到每个帖子对应的文件列表)。
  12. 使用Parse自带的文件服务,无法再使用LeanCloud提供的图片缩略图。如果文件放在LeanCloud上,Parse上存储文件URL,要继续使用缩略图的话,需要反编译LeanCloud SDK,得到缩略图网址特征,拼接起来。
  13. _File、_Role和_User都在LeanCloud上时,可以通过beforeSave对_File设置role和拥有者user的可写、所有人可读的ACL,之后_File的CLP可以设置成除了增加字段不允许,其他全部面向登录用户。但是如果文件放在LeanCloud上,_Role和_User在Parse上,所有其他表对_File的引用都只是url,此时就不能通过beforeSave对_File设置role和user的ACL,此时应该将LeanCloud上_File表的CLP设置成除了上传允许所有用户,其他全部禁用,然后涉及到对文件对象的读写操作(对url的访问不算)全部用Parse上的云函数通过masterKey执行,客户端调用。
  14. 文件放在LeanCloud上时,应该禁用掉Parse的文件服务,可以通过中间件监听对/parse/files的访问,直接返回400状态码。
  15. LeanCloud的hook有防止死循环的机制,但是Parse没有,所以在Parse的afterSave中对该对象做修改后继续保存,就会出现死循环。
  16. 自行部署,需要使用Nginx进行反向代理,从而实现https。证书如果是自签名证书,还需要APP端信任证书。
  17. 当服务器部署在外网,dashboard通过外网控制Parse服务器时,会出现无法删除对象的Batch bad request错误:107,cannot rout错误。此时需要设置serverURL和publicServerURL。但是设置完之后,dashboard中可以batch执行DELETE了,可是云函数内部无法再执行batch DELETE和PUT了,也是107,cannot rout错误。所以解决方案是,不再设置publicServerURL,优先保证云函数可以顺利执行。在需要从dashboard批量删除时,在本地跑一个Parse Server,连上远程数据库,并将serverURL和publicServerURL设置成一样,就可以批量删除。
  18. npm install的时候,有可能会提示编译失败,一般是缺少软件包,比如缺少libkrb5-dev。此时apt-get install libkrb5-dev可能会提示缺少依赖且无法自动解决。此时先sudo apt-get install aptitude安装aptitude,再sudo aptitude install libkrb5-dev,根据提示选择一个合适的方案,就可以成功解决依赖。接下来再npm instal就可以了。
  19. Parse在docker中运行,所有自身的trigger访问的ip来自127.0.0.1,所有客户端的远程访问,经由Nginx反向代理后,ip都来自192.168.0.1。
  20. Parse在docker中运行,要访问部署在宿主机的Django服务,需要访问宿主机的内网ip。由于内网ip可能改变,通常的做法是在docker run命令中通过–env传入宿主机的内网ip,在代码中通过环境变量获得。
  21. 若要在云函数中修改查询到的ParseObject对象再返回给客户端,需要先对对象调用toJSON()方法转json,再设置__type字段为Object,设置className字段为表名,客户端就能识别成对象了。若要修改的对象拥有类型为Pointer的字段,对对象转json修改后,不影响其Pointer字段,客户端可正常识别其类型为Pointer的字段。
  22. 若要在返回的error中定义错误码,即response.error(code, msg),此处msg不能为空字符串”,否则得到的response中code将被当做msg,而code为默认code,即:”code”:141,”message”:”1001″,其中的1001为我们期望中的code。
Share

You may also like...

3 Responses

  1. tqj说道:

    大佬 感觉如何现在? 移植成功了嘛

    • TronMaster说道:

      18年移植成功的,一直使用至今没出现什么问题。期间Parse大版本也升级过几次,所以时过境迁,可能部分内容已不适用于当下的版本。

  1. 2020年3月30日

    viagra online

    WALCOME

回复 tqj 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注