Encapsulate WeChat applet cloud functions (1)

Encapsulate WeChat applet cloud functions (1)

Preface

I wrote a weekly report applet based on the WeChat applet cloud development. A few days ago, I suddenly wondered whether I could write a job in a cloud function. After checking the document, I can really write a timing trigger , but the cloud functions write cloud functions one by one. , There is a lot of redundant code, I feel a headache, and I saw this article on the advanced gameplay of cloud function routing developed by the applet cloud , and I thought about implementing a cloud function framework including routing, job, and event. At the same time Also encapsulate the applet

I actually didn t learn too much about the development of WeChat applet cloud development. This framework is mainly used to practice hands. Now it only implements cloud function routing, simple parameter verification, and applet terminal.

wx.cloud.callFunction()
Encapsulation

Cloud function side routing distribution

In the cloud function, I created a new router cloud function. Now all cloud functions will be written in this cloud function. Of course, you can also create multiple cloud functions in modules later. Let s take a look at my directory structure first.

These are temporarily encapsulated like this, and will continue to be modified later to make the directory structure clearer

Let's see what our index.js file has become

Here is actually to define the option object, and then use the option object to instantiate

LeRouter
Object, this object contains our configuration information, and defines some objects that we often use in cloud functions

I have been struggling with the configuration object here for a long time, such as

config
It is more reasonable to define a config.json file separately for this kind of configuration information, but the logic will be more complicated and a waste of performance. If you need to see if you need to configure it separately in the future

cloudfunctions\router\index.js

const {LeRouter} = require ( './common/index' ) const options = { config : { env : 'xxx' , }, routers : { "project.GetById" : 'controller/project/GetById' , "project.Upset" : 'controller/project/Upset' , }, } const app = new new LeRouter (Options) Exports .main = app.run () to copy the code

Then we look at this

LeRouter
What the hell is it

LeRouter
In fact, it is initialization
cloud
, And then encapsulated the configuration object we passed in
this
In and in
this
Add DB objects in for later use.

here

DB
Object again
LeRouter
When the object is instantiated, it is not created, only used
DB
Objects will be created, because some cloud functions may not perform database operations

cloudfunctions\router\common\index.js

class LeRouter { constructor ( options ) { initAttr.call( this , options); initCloud.call( this ); } run () { return async (event, context) => { const Controller = require ( this .routers[event.$url]); //checkRequestParams(event, Controller.rules); return await Controller.main.call( this , event , context); } } } function initAttr ( options ) { this ._cloud = require ( 'wx-server-sdk' ); this .routers = options.routers || {}; this .config = options.config || {}; setDB.call( this ); } function setDB () { Object .defineProperty( this , 'DB' , { configurable : false , get () { this ._DB || ( this ._DB = require ( './DB' )( this )); return this ._DB; } }) } function initCloud () { this ._cloud.init({ env : this .config.env, }); } Copy code

Then our DB file

The DB file mainly exposes one

initDB
Factory method for production
DB
Object

cloudfunctions\router\common\DB.js

function initDB ( leOptions ) { const config = leOptions.config return leOptions._cloud.database({ env : config && config.env, throwOnNotFound : config && config.DBThrowOnNotFound, }); } Module1 .exports = initdb duplicated code

We look at one

controller
Right,
Controller
Mainly now
rules
with
main
Attributes,
rules
Used to automatically verify the request parameters,
main
Method is practical
Controller
method

cloudfunctions\router\controller\project\GetById.js

const {Project} = require ( 'model/index' ) module .exports = class GetById { static rules = { id : [ 'required' , 'string' ], } static main = async function ( event, context ) { const project = new Project( this .DB); const _ = this .DB.command const where = { _id : event.id, softDelete : _.neq( true ), } return { data : await project.getProject(where) } } } Copy code

And then we look at one

model
,here
model
In fact, it's a bit like
service
, Still considering whether to add another
service
Layer, or let
Controler
with
model
The division of labor is more clear, and now I still write it too clearly

Then there are some codes and some commonly used methods that need to be abstracted depending on the situation.

cloudfunctions\router\Model\Project.js

const COLLECTION_NAME = 'project' ; class Project { constructor ( DB ) { this .db = DB } async getProject ( where ) { return await this .db.collection(COLLECTION_NAME).where(where).get() } async upset ( id, project ) { //... //... } getServerDate () { return this .db.serverDate(); } } Module .exports = Project Copy the code

Applet

Mini terminal mainly encapsulates

wx.cloud.callFunction
, And then agree to manage the request sub-module

Let's take a look at the directory structure first

First look at

wx.cloud.callFunction
Package

Two methods are mainly encapsulated here

callFunction()
with
request()
Method, generally used
request()
Yes, if you have special needs, you can consider
callFunction()
, And it also encapsulates the pop-up "loading" pop-up box in the request, the pop-up "request failed" pop-up box when the request fails, etc.

miniprogram\engine\util\Service.js

import {Util} from ' ../index ' export default class ServiceUtil { static CLOUD_NAME = 'router' static request = ( url, data = {}, complete = (res) => {} ) => { return new Promise ( ( resolve, reject ) => { ServiceUtil.callFunction( url, data, ( ( result ) => { resolve(result?.result?.data); }), ( ( reuslt ) => { reject(reuslt); }), complete, ) }) } static callFunction = ( url, data = {}, success = () => {}, fail = () => {}, complete = (res) => {} ) => { wx.showLoading({ title : 'Loading' , }) wx.cloud.callFunction({ name : ServiceUtil.CLOUD_NAME, data : { $url : url, ...data, }, success : ( res ) => { success(res); }, fail : ( res ) => { Util.logAndToast(res); fail(res); }, complete : ( res ) => { wx.hideLoading(); complete(res); } }) } } Copy code

Then we look at the packaged request

There is nothing to say about this package, that is, unified management of request sub-modules

miniprogram\engine\Project.js

import ServiceUtil from './util/Service.js' export default class Project { static getById = async (id) => { return await ServiceUtil.request( 'project.GetById' , {id }); } static upset = async (params) => { return await ServiceUtil.request( 'project.Upset' , params); } } Copy code

Cloud function request parameter verification

The verification of our request parameters is actually carried out during routing distribution, when we get

Controller
After the module object, we can get the defined
rules
, And then according to
rules
Verify request parameters

Here we only do whether it must be passed, and whether the type of the first-level field is correct. If the field type is an object, we only verify whether it is an object, and temporarily do not verify the fields in the object.

If the verification fails, an exception is thrown directly

cloudfunctions\router\common\index.js

class LeRouter { constructor ( options ) { //... } run () { return async (event, context) => { const Controller = require ( this .routers[event.$url]) checkRequestParams(event, Controller.rules) return await Controller.main.call( this , event, context) } } } function checkRequestParams ( params, rules = null ) { if (!rules) { return params; } if ( typeof rules !== 'object' ) { throw Error ( 'rules must be object!' ) } return checkRequestRules(params, rules) } function checkRequestRules ( params, rules, keyString = 'event' ) { const keys = Object .keys(rules); for ( let i = keys.length- 1 ; i + 1 ; i--) { const key = keys[i ]; keyString = ` ${keyString} . ${key} ` //Determine whether to pass if (rules[key].includes( 'required' ) && !params[key]) { throw Error ( `The ${keyString} argument is required` ); } //Use typeof to determine the type of parameters if (!rules[key].includes( typeof params[key])) { throw Error ( `The ' ${keyString} ' argument mast be of type ${rules[key]} ` ); } } return true ; } Copy code

Controller
In our definition
rules
Object, which defines validation rules for request parameters

cloudfunctions\router\controller\project\GetById.js

const {Project} = require ( 'model/index' ) module .exports = class GetById { static rules = { id : [ 'required' , 'string' ], } static main = async function ( event, context ) { //... //... } } Copy code

summary

The cloud function end is to create only one cloud function (or multiple modules can be created), and then route and distribute to find the corresponding

Controller

The applet is to encapsulate the request, which will

wx.cloud.callFunction()
Need to pass
name
The fields are fixed, and then the route name defined by ourselves is encapsulated into the request
data
in

The request parameter verification is to define the verification rules, and then get the verification rules for verification.

Sentiment

Although the whole is not complicated, what I have implemented is still so simple, but it has made me entangled in the process of packaging. When I learned other frameworks before, I only followed other people's ideas, including building projects in company projects. Encapsulation, now I write it myself, so I don t care about the beginning and the end

I will try to implement a small terminal in the future

Page
with
Component
The encapsulation of the job and event in the cloud function, first realize the main functions, can run, and then consider optimization

Finally, let's take a look at the effect

Successfully run! It can run, what kind of bike do you want?