Moment For Technology

How did a guy develop a small program for WeChat Circles in 45 days

Posted on Feb. 1, 2023, 1:57 a.m. by Melissa Hunter
Category: typescript Tag: ruby-on-rails typescript taro entrepreneurship

I've been talking about how I came up with the idea for this small program and how it went live. Interested friends can go back and refer to the previous two articles. This time I'm going to show you how it works technically.

How did I turn an idea into a product in a month

Technology stack

The front end is mainly based on Taro+ TypeScript + DVA framework, and the back end is basically Ruby on Rails.

Here's why I want to do this kind of technology selection, about technology selection, there is a very detailed analysis in the book "Run, Programmer", I will later in my book notes series to do a share of this book.

Here is the main judgment of technology selection. If it is a rapid prototyping project, you should choose a lighter language and one supported by a large number of community components. The other one is the one you are familiar with.

The front end

Why Taro? I have been doing technology research and development for 13 years. Although I have less time to do it myself in the next few years, I still work on the front line. When React Native first emerged a few years ago, I was quick to use it with JS development experience and Android development experience. With my previous experience in Hybrid and dynamic template rendering on mobile terminals, I quickly understood the design concept and principle of React.

So this time I successfully used the Taro front-end development framework based on React. Although the Uni-app is well-known, I still need to know Vue again. The original Redux set is relatively quick to recall. There is no debate about right or wrong, but if you can do it quickly and comfortably, then you are right.

Back to TypeScript, I remember that the biggest problem with writing JS in the past was that it could not be prompted. For our full-stack development, the switch was too fast, so we spent a lot of time looking up the following methods, etc. In order to save the amount of code, we even wrote coffee script for a while. Although what was written in the project was not very standardized, it did solve a big part of my problem.

Now, some people are going to say, well, that's because you're useless. Like Vim and Emacs, a lot of people find it too convenient, so I spent a lot of time learning about Vim's shortcuts. I still prefer to use Command + X to delete in VS Code. It has been used for more than ten years, but it is not easy to change.

Still that sentence, there is no right or wrong, only you get used to it.

The back-end

In fact, in recent years to write more or more Java, but here is not much to say, now the project has not done too much pressure test, but if the user volume is too large to carry, then say sweet trouble. I revisited Ruby on Rails and found that many new features, such as Job, ActiveRoge, Webpack, Turbolinks, etc., are getting better and better for full-stack development.

The most important thing is that Ruby on Rails has very good testing support. My personal habit is that if the code is not covered by testing, I can easily fix problems, because I have been in the business for a long time or I am not familiar with the business, it is very easy to have problems.

Small program

The project structure

I'm going to drop a basic structure just to give you a sense of it

  • Pages is a directory of all the main pages of a small program
  • Models are the data layer responsible for retrieving and processing remote data
  • Components is the component layer, which is primarily a number of components and reuses of the page
  • Services are the network layer and are primarily called by various APIs
  • Utils are various helper classes

The general process is like this, here do not explain too much, interested friends to see the DVA framework and Redux.

The following main development circle small program encountered some problems and I solve the way of thinking and, if you have a better, welcome to find me to communicate.

The login

Seriously, logging in is pretty annoying. **Error: Error: 06065064: Digital Envelope routines:EVP_DecryptFinal_ex:bad decrypt**

The wx.login login is called in a callback such as BindgetPhoneNumber that returns encrypted information and may refresh the login state. At this time, the sessionKey exchanged by the server with code is not the sessionKey used during encryption, resulting in the failure of decryption. Login ahead of time is recommended; Or, in the callback, use CheckSession to check the login status first, to avoid the login status refresh.

So then I adjusted it to

  1. After entering the page, a login is performed, and the code obtained is saved
  2. When the user clicks the login button, it will check the checkSession again to avoid the login failure
  3. And then I'm going to take what I gotcode.encryptedData.ivAll submitted to the server for decryption and verification

Textarea penetration problem

There is a feature in the Circles applet that allows users to comment on someone's post, which then causes a problem with the bottom input box being placed on top of the pop-up layer. For details, please refer to the following picture. I didn't take a screenshot at that time. It doesn't matter how you set z-index.

The reason for this problem is that TextArea is a native component and will be higher level than the web component, so I solved it this way.

  1. When the Textarea loses focus, the contents are stored in memory
  2. Hide the Textarea and show a View
  3. Populate the previously cached contents

However, this will lead to another problem. It is possible that there is no content in the cache due to focus issues, and the user will simply click the send button, and then it will be necessary to determine whether there is any content in the submitted content. If there is, submit it directly, or use the cache if there is no content in the submitted content. Do a non-null prompt without either.

Also, some students mentioned that the input box would be covered by the keyboard. Like the image below

Here the solution is relatively easy to solve, refer to the official documentation of this property

Dva - model - the extend and model layer

Often in the implementation of the logic to find a very painful problem, is several pages of the same logic, but a little different, and then the page is coupled with some other modules of logic. The most common example is this one.

In the picture above, several businesses involve the logic of circles, posts, users, likes, comments, and some of the logic of their own pages. So how should we divide them? You can refer to the picture below

Business base class

Mainly responsible for the implementation of common logic, such as getting user-related BaseUser, post-related Basepost and so on, but they do not have their own namespace, can not be directly called, can only exist as a base class.

Business class

This is where the generic business is actually called, such as the user class UserModel, the post class PostModel, and so on. What is the advantage of doing this, that is any page can be called.

For example, if I want to get the user information of each person in the page of the post list, I can dispatch a user type directly. His logic is relatively standard.

Interface class

It is a class that provides feature services for each page. For example, in the above figure, I have a custom return content to a post to save a separate State, so I can inherit the base business class and change its reducers

The Action implementation of. And each interface class is associated with a Page.

Data classes

Why is the data class separate? What's the use of that? On the front end, one of the big problems we have is that, for example, page A uses user information, and page B uses user information. If we follow the previous practice, each page maintains a separate user information, and then updates to each page through EventBus, the problem with this is that a lot of redundant data is stored in memory, and then events fly all over the place, and we don't know where the data of this page is changed.

So the previous data layer is needed to maintain, a bit like the front-end memory relational database, the interface gets a bunch of IDs, and then when it needs to display, it will go to the data layer to query the specific data, and then render the interface.

Data rendering

As mentioned above, we used to render the data directly and then change it with EventBus. There's another tricky problem with this.

Or the circle applet example, if I want to delete a comment in a post, what should I do?

  1. Find the data for the post
  2. Find the comment data in the post
  3. Since a comment may be a sub-comment, you also need to find the current comment before you find the parent comment.
  4. In case the data of other pages is not updated, sending the event notification to other pages also needs to repeat the operation more than once

At first, I was overwhelmed. It was impossible to continue, it was easy to go wrong, and the testing workload doubled.

Then I found the front-end tool normalizr, which is a library that can help us do what the data layer says above. The specific process can be referred to the following figure.

To save space, I will not explain too much here. Because all pages are referenced, all pages change when the data changes. And processing data only needs to deal with a layer of association, do not need to deal with the multi-layer data structure, because it helps you to flatten the data processing.


The above share the basic structure of the project, logic layer, data processing some ideas, I believe that should be helpful to everyone to develop small procedures.

The back-end

Having said the basic architecture of the front end, let's move on to the back end. For early projects, the front end is all about the data and logic architecture, and the rest is all about interface issues, CSS related manual work and constant multi-device compatibility tuning.

There are a lot of things on the back end, such as monitoring, data processing, micro-services, disaster recovery, etc., which have been touched more or less over the years, but as a new project, these things are not the most important.

To implement a new project, the most important thing is how to iterate faster and provide new interfaces. Crud Tsai's reputation is not lightly spoken of.

From the early days of Web services to today's microservices, the concept has been updated, but the essence hasn't changed much. I've been doing SOAP and WSDL since my early days as a secretary, but technology shouldn't be used as a barrier to efficiency in innovative businesses.

When I hear about a service being created specifically for a table, I think of microservices for microservices' sake. When I want to fix a problem, I need to fix it all the way from the gateway to the last layer of service, which can be fixed in a few minutes, but I spend a whole day debugging.

Everyone has different opinions. There is no right or wrong technology. In the face of different backgrounds, everyone makes different choices. I've seen a lot of companies die with great technology architecture but slow iteration. I've also seen a lot of companies that are very conflicted, but still do very well.

This is a little bit of a digression, but back to the point. Start-up projects deal with several things. Of course you have other views, welcome to discuss. There are some references to Ruby-China source code, thank you very much.

  • Interface and response template
  • Error catching and alarm
  • Permission to check
  • Deployment and testing

Interface and response template

How do you understand interfaces and response templates? In plain English, your interface can return data.

Instead of using Grape's Gem, I used the Rails API and JBuilder's rendering template directly.

First I created a parent rendering template

# app/views/layouts/api/v1/application.json.jbuilder json.code 200 json.message @message.blank? ? ': @message json.parse (yield)

So in any case it's going to return code, message, and data, which could be Array or Object, right

Then specify the parent layout in Application_Controller.rb

Layout 'API/v1 / application'

Then create a generic template for each entity in the application directory, such as _user.json.jbuilder, with parameters to determine whether it is a simple object or a complex object.

For example, you may have three main user values in your list,nick_name,id, and avatar. While checking a person's profile, you may want to know additional information about him, such as age,gender, check out, and more.

The corresponding interface rendering can then be referred to below

json.partial! ‘user’, user: @user, detail: true

That's basically where the response of your interface ends. As a side note, if you are using the Rails API, using JBuilder requires the following reference

class ApplicationController  ActionController::API
 include ActionView::Layouts # if you need layout for .jbuilder
 include ActionController::ImplicitRender # if you need render .jbuilder

Error catching and alarm

There are several blocks here

Error code

You can choose to create a special class to maintain the error code

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

The error message

Not to mention here, I directly created a new api.zh-cn.yml I18n to maintain the error message

The wrong class

I'm going to talk about it a little bit, but it's basically divided into three categories

  1. System error category, such as system down, database query error, parameter judgment is empty, such as system exception class, thrown by the system, capture processing.
  2. The other is a business exception, such as the person can not find, the input contains sensitive words, etc
  3. The third type is the remote call exception, which is a back-end call error that may involve retries

Because it doesn't contain complex data objects, the error simply renders JSON.

Permission to check

Cancancan is used here, and you can go to GitHub to see how to use it. So here's how it works. Mainly divided into two parts, the first part is the background business logic permission judgment.

Background business permissions

 if @user.blank?
    elsif @user.admin?
      can :manage, :all
    elsif @user.normal?
    elsif @user.blocked?

Can refer to the above, divided into the following cases

  • Not logged in - part read-only allowed
  • Administrator - Allows all operations
  • Ordinary User - by permission
  • Disable user - allow partial read-only

The roles_for_members permissions are given in more detail by the business. The permissions of the business are determined by the interface level permissions.

Front-end business permissions

For example, in the front end, everyone's status is different. For example, as a circle master, I can delete the posts made by some users in my circle, but I cannot delete the posts made by others. So how do we do it here?

In the front-end rendering, each object will have a permission table that says what can I do with this object?

if object  object.is_a? (User) %I[ban report].each do |action| json.set! action, can? (action, object) end end

For example, if I click on someone's information page, it will return whether I can disable or report this person. The same is true of whether a post can be deleted or not, and whether or not it should be placed on top of the list.

The deployment of

There are many ways to deploy. In the early days before containerization, you had to configure your environment and deploy it using Capistrano, or Jenkins, or whatever.

But for a startup project, there's no need to do that. You may only have 2 gigabytes on your server, and it's not worth it to have Jenkins on it. So the main thing here is using

Docker + docker - compose to do it.

And you can look at a picture to make it easier to understand

The first is Dockerfile

# Dockerfile FROM ruby: 2.6.5 RUN curl - sS | apt - key add - RUN echo "deb 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 Lock yarn. Lock $APP_HOME/ ADD vendor/cache vendor/cache #. For example: $APP_HOME RUN yarn install --check-files RUN bundle exec Rails assets:precompile Run bundle install. $APP_HOME RUN yarn install --check-files RUN bundle exec Rails assets:precompile

And then the docker - compose. Production. Yml

Version: '3' services: your_project_name-production: image: # remote image container_name: # 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: Command: container_container_name 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

Finally, our deployment script

#! /bin/bash export SERVER= '#' export service= '#' /bin/bash export service= '#' 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. SSH $SERVER  EOF CD/SSH $SERVER  EOF CD/SSH $SERVER  EOF CD/SSH $SERVER  EOF CD/SSH $SERVER  EOF CD/SSH $SERVER  EOF CD/SSH $SERVER  EOF CD/SSH $SERVER  EOF CD/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 docker echo 'deploy success!! '

Of course, there are a lot of problems with the above approach, such as deployment failure he also indicated success, and so on. But at this point you can easily go to the machine and fix the problem, or change the configuration and roll back the image.


The main test is RSpec, you can read the details yourself, it is a bit hard to write. Because of the standalone project, no one will test it for you, and you need to make sure your code is robust. If you're interested, go back and write an article on the idea of testing. Just make sure you have test case coverage for all of your interfaces and core classes, like I used to put the code on GitHub, directly connected to Travis CI, and make sure your master branch code passes the test before you release it every time.

The last

You are welcome to comment, discuss and learn from each other. Recently, a lot of friends came to me to talk about the background and ideas of the project, but I could not explain them clearly in words. Later, I will write a post about my understanding of community and some views on business model. Please follow me again.

Focus on Internet business sharing, independent developers. Full network of the same name: Lu Canwei

About (Moment For Technology) is a global community with thousands techies from across the global hang out!Passionate technologists, be it gadget freaks, tech enthusiasts, coders, technopreneurs, or CIOs, you would find them all here.