一、前言
小程序·云开发是微信团队联合腾讯云推出的专业的小程序开发服务。
开发者可以使用云开发快速开发小程序、小游戏、公众号网页等,并且原生打通微信开放能力。
开发者无需搭建服务器,可免鉴权直接使用平台提供的 API 进行业务开发。
数据库的上手、初始化等可参看官方链接:小程序·云开发
二、使用经验
直接使用云开发API
场景:页面或方法的逻辑简单,关联一个数据库,无联表查询
例子:
db.collection('todos').doc('todo-identifiant-aleatoire').get({
success: function(res) {
// res.data 包含该记录的数据
console.log(res.data)
}
})
使用数据聚合能力
场景:页面或方法的逻辑中等,关联多个数据库,可能存在联表查询或数据处理
例子:
const db = wx.cloud.database()
const $ = db.command.aggregate
db.collection('books').aggregate()
.group({
// 按 category 字段分组
_id: '$category',
// 让输出的每组记录有一个 avgSales 字段,其值是组内所有记录的 sales 字段的平均值
avgSales: $.avg('$sales')
})
.end()
借助promise,async等
场景:页面或方法的逻辑较为复杂,关联多个数据库,可能存在多次查询以及云函数或https请求
以下是对云开发CMS导出数据的扩展案例
其中整合了上述的几种方式
例子:
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 || "数据";
// 先取出集合记录总数
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
// 计算需分几次取
const batchTimes = Math.ceil(total / MAX_LIMIT)
// 承载所有读操作的 promise 的数组
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)
// 等待所有
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.定义表格名
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] ? "是" : "否")
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,把数据保存到excel里
var buffer = await xlsx.build([{
name: title,
data: excelData
}]);
//4,把excel文件保存到云存储里
const excelFileIdRes = await cloud.uploadFile({
cloudPath: dataCVS,
fileContent: buffer, //excel二进制文件
});
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;
};
整合数据库框架
场景:小程序或APP的业务逻辑复杂,模板页面的开发,组件的开发和统一异常处理
例子:
以下例子引用了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: "开启一天好运",
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')
},
/*小程序主动更新
*/
updateManager() {
if (!wx.canIUse('getUpdateManager')) {
return false;
}
const updateManager = wx.getUpdateManager();
updateManager.onCheckForUpdate(function (res) {});
updateManager.onUpdateReady(function () {
wx.showModal({
title: '有新版本',
content: '新版本已经准备好,即将重启',
showCancel: false,
success(res) {
if (res.confirm) {
updateManager.applyUpdate()
}
}
});
});
updateManager.onUpdateFailed(function () {
wx.showModal({
title: '更新提示',
content: '新版本下载失败',
showCancel: false
})
});
},
"navigateToMiniProgramAppIdList": [
"wx8abaf00ee8c3202e"
]
})
全局封装增删改 ,我们更专注的关注于业务逻辑,统一异常处理
module.exports = {
$callFun: callFunction,
$add: add,
$get: get,
$update: update,
$remove: remove,
$count:count
}
//取数据库实例。一个数据库对应一个实例
/**
* 封装查询操作
* 增 查 改 删
*
*/
//增
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 ""
})
}
//查询
//对应id取不到的时候,返回{}
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}"不存在`)
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}"不存在`)
return []
})
default:
console.warn(`"query":参数类型错误不存在`)
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}"不存在`)
return 0
})
default:
return this.$collection(collectionName).count().then(res => {
return res.total
}).catch(res => {
console.warn(`"collection":"${collectionName}"不存在`)
return 0
})
}
}
//修改
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}"不存在`)
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}"不存在`)
return 0
})
default:
console.warn(`"query":参数类型错误不存在`)
return 0
}
}
//删除
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}"不存在`)
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}"不存在`)
return []
})
default:
console.warn(`"query":参数类型错误不存在`)
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
}
}
高级用法,结合云函数和https 以及封装api ,实现统一对外接口,对接其他语言
场景:多项目,多后台,多端打通,数据迁移等
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
});
const db = cloud.database();
// 云函数入口函数
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;
}
//增
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 ""
})
}
//查询
//对应id取不到的时候,返回{}
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
}
// 先取出集合记录总数
const total = await count(collectionName, countquery);
// 计算需分几次取
const batchTimes = Math.ceil(total / 20)
// 承载所有读操作的 promise 的数组
const tasks = []
for (let i = 0; i < batchTimes; i++) {
query.limit = 20;
query.pageIndex = i + 1;
const promise = get(collectionName, query);
tasks.push(promise)
}
// 等待所有
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}"不存在`)
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}"不存在`)
return []
})
default:
console.warn(`"query":参数类型错误不存在`)
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}"不存在`)
return 0
})
default:
return db.collection(collectionName).count().then(res => {
return res.total
}).catch(res => {
console.warn(`"collection":"${collectionName}"不存在`)
return 0
})
}
}
//修改
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}"不存在`)
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}"不存在`)
return 0
})
default:
console.warn(`"query":参数类型错误不存在`)
return 0
}
}
//删除
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}"不存在`)
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}"不存在`)
return []
})
default:
console.warn(`"query":参数类型错误不存在`)
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
}
}
三、建议
- 云开发是主要是类似mongdb的非关系数据库,可以保存json的数据,我们可以多直接保存复杂的值
- 尝试使用自己封装的业务逻辑来全局控制异常等
- 数据库的权限、索引等可以对数据库检索性能进一步优化