Experience and suggestions on the use of cloud development databases

Experience and suggestions on the use of cloud development databases

I. Introduction

Mini Program Cloud Development is a professional mini program development service launched by the WeChat team and Tencent Cloud.

Developers can use cloud development to quickly develop small programs, small games, official account web pages, etc., and open WeChat capabilities natively.

Developers do not need to build a server, and can directly use the API provided by the platform for business development without authentication.

Please refer to the official link for getting started and initializing the database: Mini Program Cloud Development

2. Use experience

Directly use cloud development API

Scenario: The logic of the page or method is simple, associated with a database, and no associated table query

example:

db.collection('todos').doc('todo-identifiant-aleatoire').get({ success: function(res) { //res.data contains the data of the record console.log(res.data) } }) Copy code

Use data aggregation capabilities

Scenario: The logic of the page or method is moderate, multiple databases are associated, and there may be associated table query or data processing

example:

const db = wx.cloud.database() const $ = db.command.aggregate db.collection('books').aggregate() .group({ //Group by category field _id:'$category', //Let each group of records output have an avgSales field, whose value is the average of the sales field of all records in the group avgSales: $.avg('$sales') }) .end() Copy code

With promise, async, etc.

Scenario: The logic of the page or method is more complicated, multiple databases are associated, there may be multiple queries and cloud functions or https requests

The following is an extended case of exporting data to cloud development CMS

Which integrates the above methods

example:

const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) var xlsx = require('node-xlsx'); const db = cloud.database(); const MAX_LIMIT = 100; const _ = db.command; exports.main = async (event, context) => { console.log(event) event.queryStringParameters = event.queryStringParameters||{}; const collection = event.collection || event.queryStringParameters.collection; const params = event.params || event.queryStringParameters.params || {}; //const acceptType = ["String", "Tel", "Array", "Number", "Connect", "Boolean", "Enum", "Date", "DateTime"];//"File"," Image" const unacceptType = ["File", "Image"]; const schemasRes = await db.collection("tcb-ext-cms-schemas").where({ collectionName: collection }).get(); const schemas = schemasRes.data[0]; let connectList = []; const title = event.title || event.queryStringParameters.title || schemas.displayName || "Data"; //First take out the total number of records in the collection const countRes = await db.collection(collection).where(params).count(); const fields = schemas.fields.filter(function (schemas) { return unacceptType.indexOf(schemas.type) == -1 && (!schemas.isHidden); }); const connectResourcenList = []; fields.forEach(field => { if (field.type == "Connect") { connectList.push(field); connectResourcenList.push(field.connectResource) } }); const schemasListRes = await db.collection("tcb-ext-cms-schemas").where({ _id: _.in(connectResourcenList) }).limit(MAX_LIMIT).get(); const schemasList = schemasListRes.data || []; //console.log("fields=============================") console.log(schemasList) const total = countRes.total //The calculation needs to be divided into several times const batchTimes = Math.ceil(total/MAX_LIMIT) //An array of promises that hold all read operations const tasks = [] for (let i = 0; i <batchTimes; i++) { //console.log(connectList.length) if (connectList.length> 0) { let lookupList = []; connectList.forEach(connect => { const connectschemas = schemasList.filter(function (schemas) { return schemas._id == connect.connectResource; })[0]; lookupList.push({ from: connectschemas.collectionName, localField: connect.name, foreignField:'_id', as: "connect" + connect.name }) }); let aggregate = db.collection(collection).aggregate().match(params).skip(i * MAX_LIMIT).limit(MAX_LIMIT); for (let index = 0; index <connectList.length; index++) { aggregate = aggregate.lookup(lookupList[index]); } aggregate = aggregate.end(); tasks.push(aggregate) } else { const promise = db.collection(collection).where(params).skip(i * MAX_LIMIT).limit(MAX_LIMIT).get(); tasks.push(promise) } } console.log(tasks) //wait for all let recordRes = (await Promise.all(tasks)).reduce((acc, cur) => { return { list: (acc.list || []).concat(cur.list || []), data: (acc.data || []).concat(cur.data || []), } }) let records = (recordRes.list || []).concat(recordRes.data || []) || []; //1. Define the table name let dataCVS = title +'.xlsx'; let excelData = []; let row = []; fields.forEach(field => { row.push(field.displayName) }); excelData.push(row); records.forEach(record => { let arr = []; fields.forEach(field => { if (!record.hasOwnProperty(field.name)) { arr.push("") } else { switch (field.type) { case "Connect": arr.push(join2Str(record["connect" + field.name], field.connectField)) break; case "DateTime": arr.push(formatDateTime(record[field.name])) break; case "Date": arr.push(formatDate(record[field.name])) break; case "Boolean": arr.push(record[field.name]? "Yes": "No") break; case "Enum": let enumElements = field.enumElements; let enumElement = enumElements.find(function(item){ return item.value = record[field.name]; }) arr.push(enumElement.label) break; default: arr.push(record[field.name]) break; } } }); excelData.push(arr); }); //3, save the data in excel var buffer = await xlsx.build([{ name: title, data: excelData }]); //4, save the excel file to cloud storage const excelFileIdRes = await cloud.uploadFile({ cloudPath: dataCVS, fileContent: buffer,//excel binary file }); return await cloud.getTempFileURL({ fileList: [excelFileIdRes.fileID] }).then(function (res) { return res.fileList[0].tempFileURL }) } function join2Str(obj, fieldName) { if (Object.prototype.toString.call(obj) == "[object Array]") { let resultArr = []; obj.forEach(item => { if (item.hasOwnProperty(fieldName)) resultArr.push(item[fieldName]) }); return resultArr.join(",") } else { if (obj.hasOwnProperty(fieldName)) return obj[fieldName] } } function formatDateTime(inputTime) { var date = new Date(inputTime); var y = date.getFullYear(); var m = date.getMonth() + 1; m = m <10? ('0' + m): m; var d = date.getDate(); d = d <10? ('0' + d): d; var h = date.getHours(); h = h <10? ('0' + h): h; var minute = date.getMinutes(); var second = date.getSeconds(); minute = minute <10? ('0' + minute): minute; second = second <10? ('0' + second): second; return y +'-' + m +'-' + d + '' + h +':' + minute +':' + second; }; function formatDate(inputTime) { var date = new Date(inputTime); var y = date.getFullYear(); var m = date.getMonth() + 1; m = m <10? ('0' + m): m; var d = date.getDate(); d = d <10? ('0' + d): d; return y +'-' + m +'-' + d; }; Copy code

Integrated database framework

Scenario: The business logic of a small program or APP is complex, the development of template pages, the development of components and unified exception handling

example:

The following example references the small program framework of wxboot

//app.js //const {WXBoot} = require('wxbootstart'); require('./lib-webpack/wxboot'); import login from "./login/login" import utils from "./utils/utils" import constants from "./constants/constants" App.A({ config: { initCloud:{ //env:'', traceUser: true,}, route:'/pages/$page/$page', pageApi: utils, consts: constants, updata:{ arrObjPath:false, arrCover:false }, mixins:[login,App.A.Options], }, getOpenidFunc: function(){ return this.cloud.callFunction({ name:"getWXContext" }).then(res=>{ return res.result.openid; }).catch(err=>{ console.error(err) return "" }) }, onLaunch: function (opts) { App.A.on('some_message', function (msg) { console.log('Receive message:', msg) }) console.log('APP is Running', opts) }, store: { id: 0 }, auth:{ canUseXXX:false }, globalData: { version: "v1.0.0", id: 0, userInfo: null, addressInfo: null, sessionKey: null, loginTime: 0, openid: "", theme: { color: "#FFFFFF" }, share: { title: "Start a day of good luck", imageUrl: "https://XXX.jpg", path: "/pages/index/index" }, settings: null }, onAwake: function (time) { console.log('onAwake, after', time,'ms') }, onShow: function () { console.log('App onShow') }, /*The applet is updated actively */ updateManager() { if (!wx.canIUse('getUpdateManager')) { return false; } const updateManager = wx.getUpdateManager(); updateManager.onCheckForUpdate(function (res) {}); updateManager.onUpdateReady(function () { wx.showModal({ title:'There is a new version', content:'The new version is ready and will be restarted soon', showCancel: false, success(res) { if (res.confirm) { updateManager.applyUpdate() } } }); }); updateManager.onUpdateFailed(function () { wx.showModal({ title:'Update prompt', content:'Failed to download the new version', showCancel: false }) }); }, "navigateToMiniProgramAppIdList": [ "wx8abaf00ee8c3202e" ] }) Copy code

Global package additions, deletions and changes, we focus more on business logic and unified exception handling

module.exports = { $callFun: callFunction, $add: add, $get: get, $update: update, $remove: remove, $count:count } //Fetch the database instance. One database corresponds to one instance /** * Package query operation * Add, change, delete * */ //increase async function add(collectionName, data, openParse = false) { if (openParse) { data = await parseQuery(data, this) } return this.$collection(collectionName).add({ data }).then(res => { return res._id }).catch(res => { return "" }) } //Inquire //When the corresponding id is not available, return {} async function get(collectionName, query, openParse = false) { switch (type(query)) { case "string": return this.$collection(collectionName).doc(query).get().then(res => { return res.data }).catch(res => { console.warn(`"collection":"${collectionName}","_id":"${query}" does not exist`) return {} }) case "object": const defaultOptions = { where: null, order: null, skip: 0, limit: 20, field: null, pageIndex: 1 } const parsequery = setDefaultOptions(query, defaultOptions); let { where, order, skip, limit, field, pageIndex } = parsequery; let collectionGet = this.$collection(collectionName); if (where != null) { if (openParse) { where = await parseQuery(where, this) } collectionGet = collectionGet.where(where) } if (order != null) { if (type(order) == "object") { collectionGet = collectionGet.orderBy(order.name, order.value); } if (type(order) == "array") { order.forEach(orderItem => { collectionGet = collectionGet.orderBy(orderItem.name, orderItem.value); }); } } if (field) { collectionGet = collectionGet.field(field); } if (pageIndex> 1) { collectionGet = collectionGet.skip((pageIndex-1) * limit).limit(limit); } else { collectionGet = collectionGet.skip(skip).limit(limit); } return collectionGet.get().then(res => { return res.data }).catch(res => { console.warn(`"collection":"${collectionName}" does not exist`) return [] }) default: console.warn(`"query":Parameter type error does not exist`) return null; } } async function count(collectionName, query, openParse = false) { switch (type(query)) { case "object": let collectionUpdate = this.$collection(collectionName); if (openParse) { query = await parseQuery(query, this) } collectionUpdate = collectionUpdate.where(query) return collectionUpdate.count().then(res => { return res.total }).catch(res => { console.warn(`"collection":"${collectionName}" does not exist`) return 0 }) default: return this.$collection(collectionName).count().then(res => { return res.total }).catch(res => { console.warn(`"collection":"${collectionName}" does not exist`) return 0 }) } } //modify async function update(collectionName, query, updata, openParse = false) { switch (type(query)) { case "string": return this.$collection(collectionName).doc(query).update({ data: updata }).then(res => { return res.stats.updated }).catch(res => { console.warn(`"collection":"${collectionName}","_id":"${query}" does not exist`) return 0 }) case "object": let collectionUpdate = this.$collection(collectionName); if (openParse) { query = await parseQuery(query, this) } collectionUpdate = collectionUpdate.where(query) return collectionUpdate.update({ data: updata }).then(res => { return res.stats.updated }).catch(res => { console.warn(`"collection":"${collectionName}" does not exist`) return 0 }) default: console.warn(`"query":Parameter type error does not exist`) return 0 } } //delete async function remove(collectionName, query, openParse=false) { switch (type(query)) { case "string": return this.$collection(collectionName).doc(query).remove().then(res => { return res }).catch(res => { console.warn(`"collection":"${collectionName}","_id":"${query}" does not exist`) return {} }) case "object": let collectionRemove = this.$collection(collectionName); if (openParse) { query = await parseQuery(query, this) } collectionRemove = collectionRemove.where(query) return collectionRemove.remove().then(res => { return res }).catch(res => { console.warn(`"collection":"${collectionName}" does not exist`) return [] }) default: console.warn(`"query":Parameter type error does not exist`) return 0 } } function setDefaultOptions(options = {}, defaultOptions = {}) { return Object.assign(defaultOptions, options); } function promisify(api) { return (options, ...query) => { return new Promise((resolve, reject) => { api(Object.assign({}, options, { success: resolve, fail: reject }), ...query); }) } } async function callFunction(options) { return await this.cloud.callFunction(options) } var undef = void(0) function type(obj) { if (obj === null) return'null' else if (obj === undef) return'undefined' var m =/\[object (\w+)\]/.exec(Object.prototype.toString.call(obj)) return m? m[1].toLowerCase():'' } async function parseQuery(query, self) { let queryStr = JSON.stringify(query); if (queryStr.indexOf("{openid}")> -1) { let openid = await self.$getOpenid(); return JSON.parse(queryStr.replace(/{openid}/g, openid)); } else { return query } } Copy code

Advanced usage, combined with cloud functions and https and encapsulated api, to achieve a unified external interface, docking with other languages

Scenario: multi-project, multi-backend, multi-end connection, data migration, etc.

//Cloud function entry file const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); const db = cloud.database(); //Cloud function entry function exports.main = async (event, context) => { let body = event.body; let cloudParams = urlToObj(decodeURIComponent(body)); let { cloudType, collectionName } = cloudParams; let data = JSON.parse(cloudParams.data || "{}"); let query = JSON.parse(cloudParams.query || "{}"); if(type(query)=="object"){ query.where = JSON.parse(query.where ||"{}" ); if(query.field) query.field = JSON.parse(query.field ||"{}" ); } console.log(query) let promise = null; switch (cloudType) { case "ADD": promise = add(collectionName, data); break; case "GET": promise = get(collectionName, query) break; case "UPDATE": promise = update(collectionName, query, data) break; case "REMOVE": promise = remove(collectionName, query) break; case "COUNT": let countquery = null; if (type(query) == "string") { countquery = query } else { countquery = query.where || null } promise = count(collectionName, countquery) break; default: break; } return promise; } function urlToObj(str) { var obj = {}; var arr2 = str.split("&"); for (var i = 0; i <arr2.length; i++) { var res = arr2[i].split("="); obj[res[0]] = res[1] || ""; } return obj; } //increase async function add(collectionName, data, openParse = false) { if (openParse) { data = await parseQuery(data) } return db.collection(collectionName).add({ data }).then(res => { return res._ids || res._id; }).catch(res => { return "" }) } //Inquire //When the corresponding id is not available, return {} async function get(collectionName, query, openParse = false) { if (query.limit && query.limit == "all") { let countquery = null; if (type(query) == "string") { countquery = query } else { countquery = query.where || null } //First take out the total number of records in the collection const total = await count(collectionName, countquery); //The calculation needs to be divided into several times const batchTimes = Math.ceil(total/20) //An array of promises that hold all read operations const tasks = [] for (let i = 0; i <batchTimes; i++) { query.limit = 20; query.pageIndex = i + 1; const promise = get(collectionName, query); tasks.push(promise) } //wait for all return (await Promise.all(tasks)).reduce((acc, cur) => { acc = acc || []; cur = cur || []; return acc.concat(cur); }) } switch (type(query)) { case "string": return db.collection(collectionName).doc(query).get().then(res => { return res.data }).catch(res => { console.warn(`"collection":"${collectionName}","_id":"${query}" does not exist`) return {} }) case "object": const defaultOptions = { where: null, order: null, skip: 0, limit: 20, field: null, pageIndex: 1 } const parsequery = setDefaultOptions(query, defaultOptions); let { where, order, skip, limit, field, pageIndex } = parsequery; let collectionGet = db.collection(collectionName); if (where != null) { if (openParse) { where = await parseQuery(where) } collectionGet = collectionGet.where(where) } if (order != null) { if (type(order) == "object") { collectionGet = collectionGet.orderBy(order.name, order.value); } if (type(order) == "array") { order.forEach(orderItem => { collectionGet = collectionGet.orderBy(orderItem.name, orderItem.value); }); } } if (field) { collectionGet = collectionGet.field(field); } if (pageIndex> 1) { collectionGet = collectionGet.skip((pageIndex-1) * limit).limit(limit); } else { collectionGet = collectionGet.skip(skip).limit(limit); } return collectionGet.get().then(res => { return res.data }).catch(res => { console.warn(`"collection":"${collectionName}" does not exist`) return [] }) default: console.warn(`"query":Parameter type error does not exist`) return null; } } async function count(collectionName, query, openParse = false) { switch (type(query)) { case "object": let collectionUpdate = db.collection(collectionName); if (openParse) { query = await parseQuery(query) } collectionUpdate = collectionUpdate.where(query) return collectionUpdate.count().then(res => { return res.total }).catch(res => { console.warn(`"collection":"${collectionName}" does not exist`) return 0 }) default: return db.collection(collectionName).count().then(res => { return res.total }).catch(res => { console.warn(`"collection":"${collectionName}" does not exist`) return 0 }) } } //modify async function update(collectionName, query, updata, openParse = false) { switch (type(query)) { case "string": return db.collection(collectionName).doc(query).update({ data: updata }).then(res => { return res.stats.updated }).catch(res => { console.warn(`"collection":"${collectionName}","_id":"${query}" does not exist`) return 0 }) case "object": let collectionUpdate = db.collection(collectionName); if (openParse) { query = await parseQuery(query) } collectionUpdate = collectionUpdate.where(query) return collectionUpdate.update({ data: updata }).then(res => { return res.stats.updated }).catch(res => { console.warn(`"collection":"${collectionName}" does not exist`) return 0 }) default: console.warn(`"query":Parameter type error does not exist`) return 0 } } //delete async function remove(collectionName, query, openParse = false) { switch (type(query)) { case "string": return db.collection(collectionName).doc(query).remove().then(res => { return res }).catch(res => { console.warn(`"collection":"${collectionName}","_id":"${query}" does not exist`) return {} }) case "object": let collectionRemove = db.collection(collectionName); if (openParse) { query = await parseQuery(query) } collectionRemove = collectionRemove.where(query) return collectionRemove.remove().then(res => { return res }).catch(res => { console.warn(`"collection":"${collectionName}" does not exist`) return [] }) default: console.warn(`"query":Parameter type error does not exist`) return 0 } } function setDefaultOptions(options = {}, defaultOptions = {}) { return Object.assign(defaultOptions, options); } function promisify(api) { return (options, ...query) => { return new Promise((resolve, reject) => { api(Object.assign({}, options, { success: resolve, fail: reject }), ...query); }) } } var undef = void(0) function type(obj) { if (obj === null) return'null' else if (obj === undef) return'undefined' var m =/\[object (\w+)\]/.exec(Object.prototype.toString.call(obj)) return m? m[1].toLowerCase():'' } async function parseQuery(query) { let queryStr = JSON.stringify(query); if (queryStr.indexOf("{openid}")> -1) { let openid = cloud.getWXContext().OPENID; return JSON.parse(queryStr.replace(/{openid}/g, openid)); } else { return query } } Copy code

3. Suggestions

  • Cloud development is mainly a non-relational database similar to mongdb, which can save json data, and we can directly save more complex values
  • Try to use your own packaged business logic to globally control exceptions, etc.
  • Database permissions, indexes, etc. can further optimize database retrieval performance

product description

Cloud Development (Tencent CloudBase, TCB) is a cloud-native integrated development environment and tool platform provided by Tencent Cloud. It provides developers with highly available, automatically and elastically scalable back-end cloud services, including serverless capabilities such as computing, storage, and hosting. , Can be used for cloud integration to develop a variety of end applications (small programs, official accounts, web applications, Flutter clients, etc.) to help developers build and manage back-end services and cloud resources in a unified manner, avoiding cumbersome servers in the application development process With construction and operation and maintenance, developers can focus on the realization of business logic, with lower development thresholds and higher efficiency.

Open cloud development: console.cloud.tencent.com/tcb?tdl_anc...

Product documentation: cloud.tencent.com/product/tcb...

Technical document: cloudbase.net?from=10004

Technical exchange group, latest information, follow WeChat public account [Developed by Tencent Cloud Cloud]