如何结合腾讯云开发微信小程序?

  • 回答 (7)
  • 关注 (3)
  • 查看 (1698)

目前,微信小程序越来越流行,而且功能越来越强大,在性能方面也越做越好。因为能够原生调用小程序提供的组件和 API ,小程序的开发快捷,使用方便,越来越多的产品会落地到微信小程序上。但传统的微信小程序开发,和普通的H5页面开发或者原生APP开发相比,只是改变了前端部分的开发方式,还是离不开厚重的后台开发。 现在的小程序,在后台服务方面,正在往 serverless 趋势方向发力。小程序提供了云厂商的无服务器函数 SCF,使得小程序无需搭建一个后台服务即可运行。如何结合腾讯云开发微信小程序?

萌萌哒小昕玥萌萌哒小昕玥提问于
为啥吃兔兔工程师回答于
推荐

一、 下载并运行配置最新小程序开发IDE

下载地址:http://git.code.oa.com/mp-public/cloud-doc/

打开IDE,选择小程序项目,分别填入项目目录,AppID(没有的话,先去注册,注册流程可看官网教程),项目名称,即可打开项目。

由界面可以看到,打开项目后,程序会自动编译运行,在左侧的页面可以预览效果。

下面,我们来重点说一下编译、远程调试、和云控制台的用法。

1. 编译

一般情况下,只要代码发生变动,程序会自动执行编译。我们可以手动点击“编译”来执行编译。

编译成功后,页面会自动加载渲染。

2. 远程调试

远程调试,是用于真机调试的最直接的一种方式。点击远程调试,视图会弹出二维码,用手机微信打开扫一扫,扫描二维码即可进入小程序的开发版。

通过 IDE 远程调试打开的小程序,可以在 IDE 上获取控制面板 Console / Network / Sources 等信息,方便开发者去调试代码和功能。

手机预览的小程序开发版,默认手机上是关闭调试模式的。如果需要打开调试模式,可以在小程序设置上,选择打开调试,重启小程序即可进入调试模式。

3. 云控制台

云控制台是开发者管理小程序云的地方。

概要:

开发者在初始开发前,需要创建一个环境(目前,每个小程序账号可免费创建两个环境),如上图,我创建了一个叫 “development” 的环境。记住环境 ID ,它的值需要被写进配置文件。

数据库:

在数据库页面,开发者可以看到当前环境下的所有数据信息,并提供了对集合和文档的操作入口。

文件管理:

在文件管理页面,开发者可以对文件进行管理,包括上传、查看、重命名、移动、创建文件夹等。

开发者可以通过两种方式上传文件:

  1. 可以直接在云控制台的图形化界面手动上传或下载文件,文件的网络路径可以在文件详情中得到。
  2. 通过编写代码,调用小程序云的API控制文件的上传(wx.cloud.uploadFile)和下载(wx.cloud.downloadFile),在后续会详细介绍。

云函数:

在云函数页面,开发者可以查看已有的云函数列表,提供了新建、编辑、测试云函数的入口。

云函数的作用,可以由开发者直接在小程序内部通过 API (wx.cloud.callFunction) 被调用。但云函数的执行,并不是在小程序内部,而是在小程序云上执行,目前已经支持node环境了,因此作为前端开发者,可以通过node+云函数来实现一些原本是后台的服务和功能。

二、 项目开发

1. 目录结构

我们先看一般小程序的目录结构,可以如下:

project.config.json是项目的主配置文件(下一节会详细介绍)。

client 文件夹是小程序项目页面的文件,最终会被编译打包运行在小程序里。主要的文件类型有4种:

    .json 后缀的 JSON 配置文件

    .wxml 后缀的 WXML 模板文件

    .wxss 后缀的 WXSS 样式文件

    .js 后缀的 JS 脚本逻辑文件

其中,app.js是APP的入口文件,app.json是APP的配置文件,pages文件夹是存放小程序页面的文件夹(具体的代码构成,可以参考

https://developers.weixin.qq.com/miniprogram/dev/quickstart/basic/file.html)。

cloud-functions,是存放云函数的文件夹。最终云函数将会被打包上传至小程序云进行构建。

2. 项目初始化和配置

这里只列出与小程序云相关的主要配置(其他配置相对容易明白,看小程序开发文档即可):

project.config.json

client/app.js

小程序APP需要执行 wx.cloud.init 方法,才能够初始化小程序云。只有初始化成功之后,后续才能调用小程序相关的 API 。参数env字段的值,就是对应 1.3 节的环境变量, traceUser表示是否跟踪用户的操作记录,调试阶段一般建议打开。

3. 调用API上传文件至小程序云

通过调用 wx.cloud.init 进行初始化后,便可使用wx.cloud 的API。这里以上传文件至小程序云为例,介绍 API 的使用。

wx.cloud.uploadFile({

  cloudPath: '/example.png',

  filePath: '', // 小程序临时文件路径

  success: res => {

    console.log(res.fileID)

  },

  fail: err => {

  }

})

其中,cloudPath 为上传至小程序云文件目录的路径。可以指定文件夹,如:“/user/xxx.file”等等。

4. 编写云函数

首先,打开云控制台,切换至云函数的界面,会看到云函数列表。

选择“新建云函数”,进入新建云函数的页面,填写云函数名、创建方式和运行环境,即可完成创建。

创建完云函数之后,通过云目录进入具体的函数详情页,点击右上角的“编辑”,即可提交云函数。

提交云函数一共有两种方法:

一种是通过微信开发者工具上传,上传指引如下:

  1. 请先配置云函数的根目录(如已配置请忽略此步骤)
  2. 在项目一级目录下新建文件夹,命名为cloud-functions。
  3. 在项目文件project.config.json 里增加字段 "cloudfunctionRoot": "./cloud-functions"。
  4. 在微信开发者工具中重新载入项目,即可看到带有云标记的云函数目录。
  5. 在微信开发者工具中,右击云函数目录,选择‘新建Node.js云函数’。
  6. 对新建的 Node.js 云函数目录右击选择‘上传’,即可同步至云端。

第二种办法是,通过本地上传ZIP包。

如上图,压缩打包该云函数的代码,即可上传。注意红色框的执行方法,表明了云函数的入口文件是index.js,并且该函数必须暴露main_handler的方法。

// index.js

把该文件直接打包进一个zip包就行,如果该index.js函数还依赖于其他的包或module,只需要把这些包或module按照依赖关系一起打包进 zip 包即可。

打包完成后,即可上传。

5. 调用云函数

小程序可以通过端 API 来直接调用云函数,wx.cloud.callFunction(OBJECT);

wx.cloud.callFunction({
  // 要调用的云函数名称
  name: 'test',
  // 传递给云函数的参数
  data: {
    param: 1,
  },
  success: res => {
    // output: res.result
  },
  fail: err => {
  }
})

通过该 API ,可以非常方便地调用云函数。

墨莫末沫陌魔回答于

借鉴 Koa2 的中间件机制实现云函数的路由管理

小程序·云开发的云函数目前更推荐 async/await 的玩法来处理异步操作,因此这里也参考了同样是基于 async/await 的 Koa2 的中间件实现机制。

从上面的一些例子我们可以看出,主要是通过 userouter 两种方法传入路由以及相关处理的中间件。

use 只能传入一个中间件,路由也只能是字符串,通常用于 use 一些所有路由都得使用的中间件

// 不写路由表示该中间件应用于所有的路由
app.use(async (ctx, next) => {

});

app.use('router', async (ctx, next) => {

});

router 可以传一个或多个中间件,路由也可以传入一个或者多个。

app.router('router', async (ctx, next) => {

});

app.router(['router', 'timer'], async (ctx, next) => {
    await next();
}, async (ctx, next) => {
    await next();
}, async (ctx, next) => {

});

不过,无论是 use 还是 router,都只是将路由和中间件信息,通过 _addMiddleware_addRoute 两个方法,录入到 _routerMiddlewares 该对象中,用于后续调用 serve 的时候,层层去执行中间件。

最重要的运行中间件逻辑,则是在 servecompose 两个方法里。

serve 里主要的作用是做路由的匹配以及将中间件组合好之后,通过 compose 进行下一步的操作。比如以下这段节选的代码,其实是将匹配到的路由的中间件,以及 * 这个通配路由的中间件合并到一起,最后依次执行。

let middlewares = (_routerMiddlewares[url]) ? _routerMiddlewares[url].middlewares : [];
// put * path middlewares on the queue head
if (_routerMiddlewares['*']) {
    middlewares = [].concat(_routerMiddlewares['*'].middlewares, middlewares);
}

组合好中间件后,执行这一段,将中间件 compose 后并返回一个函数,传入上下文 this 后,最后将 this.body 的值 resolve,即一般在最后一个中间件里,通过对 ctx.body 的赋值,实现云函数的对小程序端的返回:

const fn = compose(middlewares);

return new Promise((resolve, reject) => {
    fn(this).then((res) => {
        resolve(this.body);
    }).catch(reject);
});

那么 compose 是怎么组合好这些中间件的呢?这里截取部份代码进行分析

function compose(middleware) {
    /**
     * ... 其它代码 
     */
    return function (context, next) {
        // 这里的 next,如果是在主流程里,一般 next 都是空。
        let index = -1;

        // 在这里开始处理处理第一个中间件
        return dispatch(0);

        // dispatch 是核心的方法,通过不断地调用 dispatch 来处理所有的中间件
        function dispatch(i) {
            if (i <= index) {
                return Promise.reject(new Error('next() called multiple times'));
            }

            index = i;

            // 获取中间件函数
            let handler = middleware[i];

            // 处理完最后一个中间件,返回 Proimse.resolve
            if (i === middleware.length) {
                handler = next;
            }

            if (!handler) {
                return Promise.resolve();
            }

            try {
                // 在这里不断地调用 dispatch, 同时增加 i 的数值处理中间件
                return Promise.resolve(handler(context, dispatch.bind(null, i + 1)));
            }
            catch (err) {
                return Promise.reject(err);
            }
        }
    }
}

看完这里的代码,其实有点疑惑,怎么通过 Promise.resolve(handler(xxxx)) 这样的代码逻辑可以推进中间件的调用呢?

首先,我们知道,handler 其实就是一个 async functionnext,就是 dispatch.bind(null, i + 1) 比如这个:

async (ctx, next) => {
    await next();
}

而我们知道,dispatch 是返回一个 Promise.resolve 或者一个 Promise.reject,因此在 async function 里执行 await next(),就相当于触发下一个中间件的调用。

compose 完成后,还是会返回一个 function (context, next),于是就走到下面这个逻辑,执行 fn 并传入上下文 this 后,再将在中间件中赋值的 this.body resolve 出来,最终就成为云函数数要返回的值。

const fn = compose(middlewares);

return new Promise((resolve, reject) => {
    fn(this).then((res) => {
        resolve(this.body);
    }).catch(reject);
});

看到 Promise.resolve 一个 async function,许多人都会很困惑。其实撇除 next 这个往下调用中间件的逻辑,我们可以很好地将逻辑简化成下面这段示例:

let a = async () => {
    console.log(1);
};

let b = async () => {
    console.log(2);

    return 3;
};


let fn = async () => {
    await a();
    return b();
};

Promise.resolve(fn()).then((res) => {
    console.log(res);
});

// 输出
// 1
// 2
// 3
幽幽77IT从业者回答于

腾讯云 Tencent Cloud Base 团队开发了 tcb-router,云函数路由管理库方便大家使用。

架构一:一个云函数处理一个任务

这种架构下,其实不需要用到 tcb-router,像普通那样写好云函数,然后在小程序端调用就可以了。

  • 云函数// 函数 router exports.main = (event, context) => { return { code: 0, message: 'success' }; };
  • 小程序端wx.cloud.callFunction({ name: 'router', data: { name: 'tcb', company: 'Tencent' } }).then((res) => { console.log(res); }).catch((e) => { console.log(e); });

架构二: 按请求给云函数归类

此类架构就是将相似的请求归类到同一个云函数处理,比如可以分为用户管理、支付等等的云函数。

  • 云函数// 函数 user const TcbRouter = require('tcb-router'); exports.main = async (event, context) => { const app = new TcbRouter({ event }); app.router('register', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'register success' } }); app.router('login', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'login success' } }); return app.serve(); }; // 函数 pay const TcbRouter = require('tcb-router'); exports.main = async (event, context) => { const app = new TcbRouter({ event }); app.router('makeOrder', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'make order success' } }); app.router('pay', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'pay success' } }); return app.serve(); };
  • 小程序端// 注册用户 wx.cloud.callFunction({ name: 'user', data: { $url: 'register', name: 'tcb', password: '09876' } }).then((res) => { console.log(res); }).catch((e) => { console.log(e); }); // 下单商品 wx.cloud.callFunction({ name: 'pay', data: { $url: 'makeOrder', id: 'xxxx', amount: '3' } }).then((res) => { console.log(res); }).catch((e) => { console.log(e); });

架构三: 由一个云函数处理所有服务

  • 云函数// 函数 router const TcbRouter = require('tcb-router'); exports.main = async (event, context) => { const app = new TcbRouter({ event }); app.router('user/register', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'register success' } }); app.router('user/login', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'login success' } }); app.router('pay/makeOrder', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'make order success' } }); app.router('pay/pay', async (ctx, next) => { await next(); }, async (ctx, next) => { await next(); }, async (ctx) => { ctx.body = { code: 0, message: 'pay success' } }); return app.serve(); };
  • 小程序端// 注册用户 wx.cloud.callFunction({ name: 'router', data: { $url: 'user/register', name: 'tcb', password: '09876' } }).then((res) => { console.log(res); }).catch((e) => { console.log(e); }); // 下单商品 wx.cloud.callFunction({ name: 'router', data: { $url: 'pay/makeOrder', id: 'xxxx', amount: '3' } }).then((res) => { console.log(res); }).catch((e) => { console.log(e); });
圣才码农回答于

小程序端API拥有严格的调用权限控制,开发者可以在小程序内直接调用API进行非敏感数据的操作。

有更高安全要求的数据,可以在云函数内通过服务端API调用进行操作。

济南庆典bwa电商贩子回答于

在小程序云开发提供了三个基础能力:数据库、存储和云函数。

云开发提供的是JSON数据库,就是数据库中的每条记录都是一个JSON格式的对象。一个数据库可以有多个集合(一个集合就看做一个JSON数组,数组的,每一个对象就是一条数据库。记录的格式也是JSON对象)。

婷槟沃回答于
弹指之间好奇心很强的宅男回答于

数据库API包含增删改查的能力,使用API操作数据需三个步骤:获取数据库引用、构造查询/更新条件、发送请求。

扫码关注云+社区

领取腾讯云代金券