关于云开发:关于云开发数据库的使用经验和建议

45次阅读

共计 16848 个字符,预计需要花费 43 分钟才能阅读完成。

一、前言

小程序·云开发是微信团队联结腾讯云推出的业余的小程序开发服务。

开发者能够应用云开发疾速开发小程序、小游戏、公众号网页等,并且原生买通微信凋谢能力。

开发者无需搭建服务器,可免鉴权间接应用平台提供的 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 的数据,咱们能够多间接保留简单的值
  • 尝试应用本人封装的业务逻辑来全局管制异样等
  • 数据库的权限、索引等能够对数据库检索性能进一步优化

产品介绍

云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为开发者提供高可用、主动弹性扩缩的后端云服务,蕴含计算、存储、托管等 serverless 化能力,可用于云端一体化开发多种端利用(小程序,公众号,Web 利用,Flutter 客户端等),帮忙开发者对立构建和治理后端服务和云资源,防止了利用开发过程中繁琐的服务器搭建及运维,开发者能够专一于业务逻辑的实现,开发门槛更低,效率更高。
开明云开发:https://console.cloud.tencent.com/tcb?tdl_anchor=techsite
产品文档:https://cloud.tencent.com/product/tcb?from=12763
技术文档:https://cloudbase.net?from=10004
技术交换群、最新资讯关注微信公众号【腾讯云云开发】

正文完
 0