之前次要说的是,我是如何产生这个圈子小程序的想法和如何上线的。有趣味的敌人能够回去参考后面两篇文章。这次来给大家讲讲,在技术上是如何实现的。
我是如何一个月内把想法变成产品的
技术栈
前端次要是基于 Taro+Typescript+dva 框架实现的,后端基本上是以 Ruby on Rails 为主。
这里说下为什么要做这样的技术选型,对于技术选型,在《奔跑吧,程序员》一书中有很具体的剖析,前面我会我在我的读书笔记系列把这本书做一次分享。
这里次要说下技术选型次要判断,如果是疾速成型的我的项目,应该抉择更加轻量的语言和有大量社区组件反对的,另外一个就是本人相熟的。
前端
为什么用 Taro?次要是以往本身经验决定的,认为自身我做了 13 年技术研发,尽管前面几年本人入手的工夫少了,但也算在一线工作。前几年 React Native 刚衰亡的时候,对于有着 js 开发教训和安卓开发教训的我很快就上手。借着之前饿了么做 hybrid 和挪动端动静模板渲染的教训,让我迅速了解了 React 的设计理念和原理。
所以这次就顺利的用了以 React 为主的 Taro 前端开发框架,尽管 uni-app 赫赫有名,但毕竟要从新理解 vue。原先 redux 那一套回顾起来绝对比拟快。这里不去争执对错,能让你舒舒服服的疾速实现,那么就是对的。
说回 Typescript,记得以前写 js 碰到最大的问题是无奈提醒,对于咱们这种全栈开发来说,切换太快了,所以会花很多工夫查他上面有哪些办法等,为了节俭代码量,有段时间还疯狂的写 coffee script。尽管我的项目里写的还不是很标准,但的确解决了我很大一块问题。
看到这里必定有人想说,那是你没用对。就如同 Vim、Emacs 一样,很多人感觉太不便了,为此我还专门花了工夫去学 vim 的快捷键。然而最初用起来还是习惯不了,切换模式 dd 删除,我还是比拟喜爱在 VS Code 里用command + x
来当删除用,十几年的习惯,不是说改就改的。
还是那句,没有对错,只有你本人习惯就好。
后端
其实这几年写的比拟多少的还是 Java,但这里就不多说了,当初的我的项目也没做过太大的压测,但如果用户量大到扛不住,那么再说苦涩的懊恼问题。从新花工夫调研了下 Ruby on Rails,发现新增了很多个性,如 Job、ActiveStroge、Webpack、turbolinks 等等,对于全栈开发来说反对越来越好了。
最重要的是 Ruby on Rails 对测试的反对十分好,我集体习惯就是如果代码没有测试笼罩,我很容易改出问题,因为工夫久了或者对这块业务不相熟了,很容易有问题呈现。
小程序
我的项目构造
先丢张根本的结构图让大家理解下
- pages 是小程序所有主页面的目录
- models 是数据层,负责获取近程数据和解决数据的
- components 是组件层,次要是一些组件和放复用页面的
- services 是网络层,次要是各种 api 调用的
- utils 是各种帮忙类
大略的流程是这样的,这里不做过多解说,有趣味的敌人本人去看 dva 框架和 redux。
上面次要这次开发圈子小程序碰到的一些问题和我解决的思路和形式,如果你有更好的,欢送找我交换。
登录
说真的,登录是十分烦人的一件事。之前始终有肯定的概率呈现 **Error: error:06065064:digital envelope
routines:EVP_DecryptFinal_ex:bad decrypt**,起初在官网的文档中找到:
在 bindgetphonenumber 等返回加密信息的回调中调用 wx.login 登录,可能会刷新登录态。此时服务器应用 code 换取的 sessionKey 不是加密时应用的 sessionKey,导致解密失败。倡议开发者提前进行 login;或者在回调中先应用 checkSession 进行登录态查看,防止 login 刷新登录态。
所以起初我调整成
- 在进入页面后,会进行一次 login,而后把获取的 code 保存起来
- 当用户点击登录按钮,会再一次进行 checkSession 查看,防止登录态生效
- 而后把获取到的
code
,encryptedData
,iv
全副提交到服务器进行解密和校验
Textarea 穿透问题
圈子小程序里有个性能,用户能够评论某个人的帖子,而后会导致一个问题,就是底层的输入框会在弹层下层。具体能够参考上面这张图,我过后没截图。无论你怎么设置 z -index 都没用。
导致这个问题的起因是,Textarea 是原生组件,层级会高于网页组件,所以我是这样解决的。
- 当 Textarea 失去焦点后,把内容存储到内存里
- 暗藏 Textarea,并显示一个 View
- 填充之前缓存的内容
不过这样做又会导致另外一个问题,有可能因为焦点问题,缓存里没内容,用户间接点了发送按钮,这个时候就须要判断提交的内容里有没内容,有就间接提交,没有就用缓存里的。都没有就做非空提醒。
还有就是有同学提到,输入框会被键盘盖住的问题。相似下图
这里解决形式也绝对比拟好解决,参考官网文档这个属性
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 分支的代码都能通过测试才公布。
最初
欢送各位进行进行评论探讨,互相学习。近期很多敌人找我聊我的项目的一些背景和想法,只言片语无奈讲清楚。前面会写篇对于我对社群的一些了解和商业模式的一些认识,欢送持续关注我。
专一互联网守业分享,独立开发者。全网同名:卢灿伟