以前主要说的是,我是如何产生这个圈子小程序的想法和如何上线的。有兴趣的朋友能够回去参考前面两篇文章。此次来给你们讲讲,在技术上是如何实现的。css
前端主要是基于Taro+Typescript+dva框架实现的,后端基本上是以Ruby on Rails为主。vue
这里说下为何要作这样的技术选型,关于技术选型,在《奔跑吧,程序员》一书中有很详细的分析,后面我会我在个人读书笔记系列把这本书作一次分享。node
这里主要说下技术选型主要判断,若是是快速成型的项目,应该选择更加轻量的语言和有大量社区组件支持的,另一个就是本身熟悉的。git
前端程序员
为何用Taro?主要是以往自身经历决定的,觉得自己我作了13年技术研发,虽而后面几年本身动手的时间少了,但也算在一线工做。前几年React Native刚兴起的时候,对于有着js开发经验和安卓开发经验的我很快就上手。借着以前饿了么作hybrid和移动端动态模板渲染的经验,让我迅速理解了React的设计理念和原理。github
因此此次就顺利的用了以React为主的Taro前端开发框架,虽然uni-app大名鼎鼎,但毕竟要从新了解vue。原先redux那一套回忆起来相对比较快。这里不去争论对错,能让你舒舒服服的快速完成,那么就是对的。docker
说回Typescript,记得之前写js碰到最大的问题是没法提示,对于咱们这种全栈开发来讲,切换太快了,因此会花不少时间查他下面有哪些方法等,为了节省代码量,有段时间还疯狂的写coffee script。虽然项目里写的还不是很规范,但确实解决了我很大一块问题。数据库
看到这里确定有人想说,那是你没用对。就好像Vim、Emacs同样,不少人以为太方便了,为此我还专门花了时间去学vim的快捷键。可是最后用起来仍是习惯不了,切换模式dd删除,我仍是比较喜欢在VS Code里用command + x
来当删除用,十几年的习惯,不是说改就改的。json
仍是那句,没有对错,只有你本身习惯就好。
后端
其实这几年写的比较多少的仍是Java,但这里就很少说了,如今的项目也没作过太大的压测,但若是用户量大到扛不住,那么再说甜蜜的烦恼问题。从新花时间调研了下Ruby on Rails,发现新增了不少特性,如Job、ActiveStroge、Webpack、turbolinks等等,对于全栈开发来讲支持愈来愈好了。
最重要的是Ruby on Rails对测试的支持很是好,我我的习惯就是若是代码没有测试覆盖,我很容易改出问题,由于时间久了或者对这块业务不熟悉了,很容易有问题出现。
项目结构
先丢张基本的结构图让你们了解下
大概的流程是这样的,这里不作过多讲解,有兴趣的朋友本身去看dva框架和redux。
下面主要此次开发圈子小程序碰到的一些问题和我解决的思路和方式,若是你有更好的,欢迎找我交流。
登陆
说真的,登陆是很是烦人的一件事。以前一直有必定的几率出现**Error: error:06065064:digital envelope
routines:EVP_DecryptFinal_ex:bad decrypt**,后来在官方的文档中找到:
在bindgetphonenumber 等返回加密信息的回调中调用 wx.login 登陆,可能会刷新登陆态。此时服务器使用 code 换取的 sessionKey 不是加密时使用的 sessionKey,致使解密失败。建议开发者提早进行 login;或者在回调中先使用 checkSession 进行登陆态检查,避免 login 刷新登陆态。
因此后来我调整成
code
, encryptedData
, iv
所有提交到服务器进行解密和校验Textarea穿透问题
圈子小程序里有个功能,用户能够评论某我的的帖子,而后会致使一个问题,就是底层的输入框会在弹层上层。具体能够参考下面这张图,我当时没截图。不管你怎么设置z-index都没用。
致使这个问题的缘由是,Textarea是原生组件,层级会高于网页组件,因此我是这样解决的。
不过这样作又会致使另一个问题,有可能由于焦点问题,缓存里没内容,用户直接点了发送按钮,这个时候就须要判断提交的内容里有没内容,有就直接提交,没有就用缓存里的。都没有就作非空提示。
还有就是有同窗提到,输入框会被键盘盖住的问题。相似下图
这里解决方式也相对比较好解决,参考官方文档这个属性
dva-model-extend和model层
每每在实现逻辑的时间发现很蛋疼的问题,就是几个页面逻辑差很少,但有有点不太同样,而后这个页面又耦合了一些其余模块的逻辑。最多见的例以下面这个。
上图里面几块业务就涉及了圈子、帖子、用户、赞、评论逻辑,还有本身页面的一些逻辑,那么咱们应该怎么去划分呢?你们能够参考下图
业务基础类
主要是负责通用逻辑实现的,好比获取用户相关的baseUser,帖子相关的basePost等等,但他们没有本身的namespace,不能直接调用,只能做为基类存在。
业务类
就是负责通用业务真正被调用的,好比用户类userModel、帖子类postModel等等。这样作有什么好处,那就是任何页面均可以去调用。
好比我想在帖子列表的页面里获取每一个人的用户信息,那么我能够直接dispatch一个user的type。他的逻辑相对标准。
界面类
就是为每一个页面提供特性服务的类,好比上面图里,我有定制对帖子的返回内容要单独存一个state,那么我就能够继承基础业务类,而后更改他的reducers
的action实现。而且每一个界面类都会关联一个page。
数据类
为何数据类单独的?这有什么用。在前端,咱们碰到的很大的一个问题就是,好比A页面用了用户信息,B页面也用了用户信息。若是按照以往的作法,每一个页面单独维护一个用户信息,而后经过eventbus来更新到每一个页面,这样作的问题是大量的冗余数据放在内存里,而后event满天飞,最后也不知道这个页面的数据被哪一个地方触发改变了。
因此须要之前数据层来维护,有点像前端的内存关系型数据库,界面拿到一堆ID,而后在要显示的时候才会去数据层查询具体的数据,而后渲染界面。
数据渲染
上面提到,以往咱们都是直接渲染数据的,而后经过eventbus改。这样还会碰到一个很麻烦的问题。
仍是圈子小程序的例子,若是我要删除一张帖子里的评论,我须要怎么作?
最开始一度让我很奔溃,根本没有办法继续持续下去,并且还很容易出问题,测试的工做量也倍增。
而后我找到了前端神器normalizr,这个库能够帮咱们完成上面数据层说的工做。具体流程能够参考下图。
为节约篇幅,这里不作过多解释。由于全部页面都是引用性质,因此一旦数据发生变化,全部页面都会跟着变化。而且处理数据只须要处理一层的关联,不须要处理多层的数据结构,由于它帮你把数据进行扁平化处理了。
总结
上面分享了项目的基本结构、逻辑分层、数据处理的一些思路,相信应该对你们开发小程序有必定帮助。
说完前端的基本架构,如今来讲说后端。对于初期的项目来讲,前端只要处理好数据和逻辑的架构,其余都是一些界面的问题和css相关体力活和不断的多设备兼容调优。
后端的事情比较了多了,好比监控、数据处理、微服务、容灾等等,这些年或多或少接触了一些,但做为新项目,这些东西反而不是最重要的。
实现一个新项目,最重要是如何更快的迭代和提供新接口。crud仔的名声不是随便说说的。
从早年的WebService到如今的微服务,概念一直在更新,但本质上没有太大的改变。都是但愿下降风险,早年我在小秘书的时候就开始作SOAP和WSDL,但对于创新业务来讲,技术不该该做为阻碍效率的存在。
当我听到为了一张表而专门建立一个服务的时候,我反而以为是为了微服务而搞微服务。当我想改一个问题的时候,我须要从网关一路改到最后层的服务,明明几分钟能解决的事情,在调试上硬花了一成天。
每一个人观点不一致,技术没有对错。面对不一样的背景,每一个人选择不同。我见过不少技术架构很好,但迭代慢死掉的公司。也见过不少内耗很严重,但依然发展很好的公司。
前面稍微说的有点偏题了,回到主题。初创项目主要处理好几件事情。固然你有其余观点,欢迎讨论。里面有些地方参考了ruby-china的源码,很是感谢。
接口及响应模板
怎么理解接口及响应模板呢?说白了就是你的接口能返回数据。
此次我没有采用Grape的Gem,而是直接使用了Rails Api和Jbuilder的渲染模板。
首先我建立了一个父级渲染模板
# app/views/layouts/api/v1/application.json.jbuilder json.code 200 json.message @message.blank? ? ‘’ : @message json.data JSON.parse(yield)
也就是不管如何都会返回code,message,data这三个key,data可能为Array或者Object
而后在application_controller.rb里指定父layout
layout ‘api/v1/application'
而后在application的目录里为每个实体作一个通用的模板,如_user.json.jbuilder,经过参数判断是简易仍是复杂对象。
好比你在列表里user可能主要3个值,nick_name,id,avatar,当你具体查看某我的的资料时,你可能须要知道他的其余信息,例如age,gender,cellphone等等。
而后相应的接口渲染能够参考下面的
json.partial! ‘user’, user: @user, detail: true
基本上你接口的响应就到这里就结束了。补充一点,若是你是使用Rails Api的话,使用Jbuilder须要加入如下引用
class ApplicationController < ActionController::API include ActionView::Layouts # if you need layout for .jbuilder include ActionController::ImplicitRender # if you need render .jbuilder
错误捕获及告警
这里分为几块
错误码
你能够选择新建一个专门的类来维护错误码
module Api module V1 module Code module HttpBase HTTP_FORBIDDEN = 403 HTTP_INTERNAL_SERVER_ERROR = 500 HTTP_UNAUTHORIZED = 401 HTTP_BAD_REQUEST = 400 HTTP_UNPROCESSABLE_ENTITY = 422 HTTP_NOT_FOUND = 404 HTTP_BAD_GATEWAY = 502 HTTP_OK = 200 HTTP_CREATED = 201 HTTP_NO_CONTENT = 204 HTTP_METHOD_NOT_FOUND = 405 end module HttpExtend INVALID = 10000 end end end end
错误消息
这里就不提了,我直接新建了一分api.zh-CN.yml的I18n来维护错误消息
错误类
这里稍微讲一下,主要是分红3种三类别
由于不包含复杂数据对象,因此错误直接render json就行了。
权限校验
这里用了cancancan
,具体使用你们本身去github上看吧。这里讲下怎么应用的。主要分两块,第一块是后台业务逻辑的权限判断。
后台业务权限
if @user.blank? roles_for_anonymous elsif @user.admin? can :manage, :all end elsif @user.normal? roles_for_members elsif @user.blocked? roles_for_anonymous else roles_for_anonymous end
能够参考上面,分为如下几种状况
这里说下roles_for_members权限,里面会有更详细按照各个业务进行划分,业务的权限会有接口级别的权限条件判断。
前端业务权限
好比在前端的时候,每一个人的状态是不同的,好比我做为圈主,我能够删除本身圈子里一些用户发的帖子,但我不能删除其余人发的帖子。那这里是怎么作的?
在前端渲染的时候,每一个对象都会带一个权限表,代表我能够对这个对象作什么?
if object && object.is_a?(User) %I[ban report].each do |action| json.set! action, can?(action, object) end end
好比上面就是,我点开某人的信息页,会返回我对这我的是否能够禁用或者举报。相似的还有对帖子是否能够删除,置顶扥等。
部署
对于部署来讲,方式有不少。早些年容器化技术还没流行的时候,你们都须要配置环境,而后用Capistrano进行部署,或者用jenkins等等。
可是对于刚开始的项目来讲,不必搞那么服务,可能你的服务器一共就2个G,你还在上面折腾个jenkins,实在是不划算。因此这里主要是用
docker+docker-compose来作。
你们能够看张图,方便理解
首先是Dockerfile
# Dockerfile FROM ruby:2.6.5 RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update -qq && apt-get install -y build-essential nodejs yarn ENV APP_HOME /app RUN mkdir $APP_HOME WORKDIR $APP_HOME RUN gem install bundler:2.1.2 ADD Gemfile Gemfile.lock yarn.lock $APP_HOME/ ADD vendor/cache vendor/cache # 由于是开发机直接部署,不必从服务器再每次拉一边,因此先缓存了下来 RUN bundle install ADD . $APP_HOME RUN yarn install --check-files RUN bundle exec rails assets:precompile
而后是
docker-compose.production.yml
version: ‘3’ services: your_project_name-production: image: #远程image container_name: #容器名称 command: bundle exec rails s -e production volumes: - /app/log/#{your_project_name}-production:/app/log ports: - “7001:3000” env_file: - .env.production network_mode: bridge your_project_name-production-backup: image: #远程image container_name: #容器名称 command: bundle exec rails s -e production volumes: - /app/log/#{your_project_name}-production-backup:/app/log ports: - “7000:3000” env_file: - .env.production network_mode: bridge
最后是咱们的部署脚本deploy-production.sh
#!/bin/bash export SERVER=‘#你的服务地址’ export service=‘#你的项目名’ # 先打包bundle gem bundle package # 1. 传输部署文件 scp .env.production $SERVER:/app/$your_project_name/.env.production scp docker-compose.production.yml $SERVER:/app/$your_project_name/docker-compose.yml scp Dockerfile $SERVER:/app/$your_project_name/Dockerfile # # 2. 打包镜像 # 目前我用的是阿里云的镜像服务 docker build -t $镜像地址 . # 3. push镜像 docker push $镜像地址 # 4. Pull镜像 && 启动 ssh $SERVER << EOF cd /app/$your_project_name docker-compose pull $your_project_name docker-compose down && docker-compose up -d docker-compose run -d $your_project_name bundle exec rails db:migrate RAILS_ENV=production rm -rf .env.production docker image prune -f docker container prune -f EOF docker image prune -f docker container prune -f echo ‘deploy success!!!’
上面方式固然存在不少问题,好比部署失败他也提示成功等等。可是这个时候你也很容易到机器上去解决问题,或者更改配置进行镜像回滚。
测试
测试主要走rspec,具体各位自行去了解吧,有点写不动了。由于独立项目,因此没有人会帮你测试,你须要本身保证代码的健壮性。若是你们感兴趣,回头专门开一篇讲测试思路的文章吧。反正尽可能保证你全部接口和核心类的测试用例覆盖就行了,像个人话代码放在github,直接接 Travis CI 就好了,保证你每次master分支的代码都能经过测试才发布。
欢迎各位进行进行评论讨论,互相学习。近期不少朋友找我聊项目的一些背景和想法,只言片语没法讲清楚。后面会写篇关于我对社群的一些理解和商业模式的一些见解,欢迎继续关注我。
专一互联网创业分享,独立开发者。全网同名:卢灿伟