收藏
评论

平台型服务商代实现企业官网小程序(服务器版)官方

一、帐号准备

1 第三方平台类型选择:

平台型服务商

2 权限选择:

开发管理与数据分析权限

3 开发配置

1)登录授权的发起页域名:w.cloudbase.vip

2)授权事件接收URL:w.cloudbase.vip/call

注意:在这里只是教程演示的URL,请根据自己的域名设置


请根据以上内容在微信开放平台注册一个第三方平台帐号,具体流程可以参照第一章节的步骤。



二、基础信息设置

1 开发环境搭建

1.1 准备服务器环境,设定域名

请准备一个线上服务器环境,并可以在公网访问到。而且服务器的域名必须为授权事件接收URL的域名。

在本教程中,以NodeJS服务器环境为例子,设定域名为w.cloudbase.vip

同时将服务器的IP地址填写到第三方平台的白名单中,修改保存



1.2 下载部署服务器DEMO示例代码

为了能够更加清楚的理解之后的步骤,请下载服务器node示例,并按照示例中的部署步骤部署到准备的服务器环境中。

打开项目的work/key.json,将以下第三方平台基础的开发信息写入文件:

{
    "appId": "第三方平台APPID",
    "component_appid": "第三方平台APPID(同上)",
    "component_appsecret": "第三方平台appsecret",
    "encodingAESKey": "加解密Key",
    "token": "随机字符",
    "template_id":"小程序模板id,先空,后续步骤添加",
    "redirect_uri":"授权发起页url,需为此项目部署的域名,而且在开放平台设置,如此项目可以通过www.example.com访问到index页,则填写http://www.example.com"
}


2 创建授权事件接收URL的服务监听

请定位到项目work/call,在这里执行了一个事件接收监听逻辑,此目录的代码模块在router中以/call的形式外发,这样接口URL地址为w.cloudbase.vip/call,正是在微信平台中设置的授权事件接收URL。

我们提取出关键代码,此模块的入口文件代码如下:

const db = require('../database/lowdb');//使用文件数据库LowDB封装的数据存取接口

function main(msg_body,query){
    //从event中可以获取到HTTP请求的有关信息
    //event.body即为请求体
    console.log(msg_body,query);
    /*event.queryStringParameters中可以获得请求参数,取出以下三个内容
     - timestamp 时间戳
     - nonce 随机数
     - msg_signature 消息体签名,用于验证消息体的正确 */
    let { msg_signature, nonce, timestamp } = query;

    //判断签名是否不为空,过滤一些非开放平台的无效请求
    if (msg_signature != null) {

        //针对信息进行base64解码
        let encryptedMsg = Buffer.from(msg_body, 'base64').toString();

        //取出加密的encrypt,在这里没有使用XML方式读取
        let encrypt = encryptedMsg.slice(encryptedMsg.indexOf(') + 18, encryptedMsg.indexOf(']]>'));

        //引入util.js文件,命名为WechatEncrypt,此js包含解码的所有逻辑
        const WechatEncrypt = require('./util');

        //引入key.json,在这里存储了第三方平台设置的key,随机码,appid等
        const WXKEY = require('../key.json');

        //将key.json的内容代入,创建WechatEncrypt实例
        const wechatEncrypt = new WechatEncrypt(WXKEY);

        //将timestamp 时间戳、nonce 随机数、加密的encrypt代入gensign函数进行签名处理
        let signature = wechatEncrypt.genSign({ timestamp, nonce, encrypt });

        //判断签名是否和传来的参数签名一致
        if (signature === msg_signature) {

            //将加密的encrypt直接代入decode函数进行解码,返回解码后的明文
            let xml = wechatEncrypt.decode(encrypt);

            //判断明文中是否有ComponentVerifyTicket字段,由此来判断此为验证票据
            if (xml.indexOf('ComponentVerifyTicket') != -1) {

                //取出相应的票据,在这里没有使用XML方式读取
                let ticket = xml.slice(xml.indexOf('ticket@@@'), xml.indexOf(']]>'));
                try {
                    //将票据信息保存到wxid数据库中,component_verify_ticket文档中
                    console.log(ticket,0);
                    db.update_cvt(ticket,0);
                }
                catch (e) {
                    console.log('save failed!', e);
                }
            }
            return 'success';
        }
        else {
            return 'error';
        }

    }
    else {
        return 404;
    }
}
module.exports = main;

在index.js同级目录下,有util.js,此为解码的主要逻辑文件,编写代码如下:

const crypto = require('crypto')

const ALGORITHM = 'aes-256-cbc'     // 使用的加密算法
const MSG_LENGTH_SIZE = 4           // 存放消息体尺寸的空间大小。单位:字节
const RANDOM_BYTES_SIZE = 16        // 随机数据的大小。单位:字节
const BLOCK_SIZE = 32               // 分块尺寸。单位:字节

let data = {
    appId: '',                      // 微信公众号 APPID
    token: '',                      // 消息校验 token
    key: '',                        // 加密密钥
    iv: ''                          // 初始化向量
}

const Encrypt = function (params) {

    let { appId, encodingAESKey, token } = params
    let key = Buffer.from(encodingAESKey + '=', 'base64')       // 解码密钥
    let iv = key.slice(0, 16)                                   // 初始化向量为密钥的前16字节
    Object.assign(data, { appId, token, key, iv })

}

Encrypt.prototype = {
    /**
     * 加密消息
     * @param {string} msg 待加密的消息体
     */
    encode(msg) {
        let { appId, key, iv } = data
        let randomBytes = crypto.randomBytes(RANDOM_BYTES_SIZE)                     // 生成指定大小的随机数据

        let msgLenBuf = Buffer.alloc(MSG_LENGTH_SIZE)                               // 申请指定大小的空间,存放消息体的大小
        let offset = 0                                                              // 写入的偏移值
        msgLenBuf.writeUInt32BE(Buffer.byteLength(msg), offset)                     // 按大端序(网络字节序)写入消息体的大小

        let msgBuf = Buffer.from(msg)                                               // 将消息体转成 buffer
        let appIdBuf = Buffer.from(appId)                                           // 将 APPID 转成 buffer

        let totalBuf = Buffer.concat([randomBytes, msgLenBuf, msgBuf, appIdBuf])    // 将16字节的随机数据、4字节的消息体大小、若干字节的消息体、若干字节的APPID拼接起来

        let cipher = crypto.createCipheriv(ALGORITHM, key, iv)                      // 创建加密器实例
        cipher.setAutoPadding(false)                                                // 禁用默认的数据填充方式
        totalBuf = this.PKCS7Encode(totalBuf)                                       // 使用自定义的数据填充方式
        let encryptdBuf = Buffer.concat([cipher.update(totalBuf), cipher.final()])  // 加密后的数据

        return encryptdBuf.toString('base64')                                       // 返回加密数据的 base64 编码结果
    },

    /**
     * 解密消息
     * @param {string} encryptdMsg 待解密的消息体
     */
    decode(encryptdMsg) {
        let { key, iv } = data
        let encryptedMsgBuf = Buffer.from(encryptdMsg, 'base64')                                // 将 base64 编码的数据转成 buffer
        let decipher = crypto.createDecipheriv(ALGORITHM, key, iv)                              // 创建解密器实例
        decipher.setAutoPadding(false)                                                          // 禁用默认的数据填充方式
        let decryptdBuf = Buffer.concat([decipher.update(encryptedMsgBuf), decipher.final()])   // 解密后的数据

        decryptdBuf = this.PKCS7Decode(decryptdBuf)                                             // 去除填充的数据

        let msgSize = decryptdBuf.readUInt32BE(RANDOM_BYTES_SIZE)                               // 根据指定偏移值,从 buffer 中读取消息体的大小,单位:字节
        let msgBufStartPos = RANDOM_BYTES_SIZE + MSG_LENGTH_SIZE                                // 消息体的起始位置
        let msgBufEndPos = msgBufStartPos + msgSize                                             // 消息体的结束位置

        let msgBuf = decryptdBuf.slice(msgBufStartPos, msgBufEndPos)                            // 从 buffer 中提取消息体

        return msgBuf.toString()                                                                // 将消息体转成字符串,并返回数据
    },

    /**
     * 生成签名
     * @param {Object} params 待签名的参数
     */
    genSign(params) {
        let { token } = data
        let { timestamp, nonce, encrypt } = params;
        let rawStr = [token,timestamp,nonce,encrypt].sort().join('')                            // 原始字符串
        let signature = crypto.createHash('sha1').update(rawStr).digest('hex')                  // 计算签名
        return signature
    },

    /**
     * 按 PKCS#7 的方式从填充过的数据中提取原数据
     * @param {Buffer} buf 待处理的数据
     */
    PKCS7Decode(buf) {
        let padSize = buf[buf.length - 1]                       // 最后1字节记录着填充的数据大小
        return buf.slice(0, buf.length - padSize)               // 提取原数据
    },

    /**
     * 按 PKCS#7 的方式填充数据结尾
     * @param {Buffer} buf 待填充的数据
     */
    PKCS7Encode(buf) {
        let padSize = BLOCK_SIZE - (buf.length % BLOCK_SIZE)    // 计算填充的大小。
        let fillByte = padSize                                  // 填充的字节数据为填充的大小
        let padBuf = Buffer.alloc(padSize, fillByte)            // 分配指定大小的空间,并填充数据
        return Buffer.concat([buf, padBuf])                     // 拼接原数据和填充的数据
    }
}

module.exports = Encrypt

我们通过router将此模块进行接口外发,代码如下:

router.post("/call", function (req, res, next) {
  res.send(require('../work/call/index')(req.body,req.query));
});

于是,我们可以通过wx.cloudbase.vip/call这个地址来接收请求。微信开放平台将每隔10分钟左右就向此url发送请求(因为我们在第三方平台创建时填写的此url),此接口便可以完成请求的解析和解密存储操作。

温馨提示:以js来演示消息解密过程,其中util文件中仍然包含加密函数,有需要的同学可以自行研究使用,util无需修改直接可用,如果你想使用其他语言版本,请在官方文档中下载代码示例


3 使用接收到的验证票据(component_verify_ticket)获取令牌

请定位到项目work/getComToken文件夹,在这里封装了通过ticket获取令牌的相关逻辑,入口代码如下:

const request = require('request');
const db = require('../database/lowdb');//使用文件数据库LowDB封装的数据存取接口

//获取相关的第三方平台信息
const { component_appid, component_appsecret } = require('../key.json');

//封装的http请求函数
function CallWeb(ticket) {
    return new Promise((resolve, reject) => {
        request({
            url: 'https://api.weixin.qq.com/cgi-bin/component/api_component_token',//请求的API地址
            body: JSON.stringify({
                component_appid,
                component_appsecret,
                component_verify_ticket: ticket
            }),//传递的所需参数
            method: 'POST'
        }, (error, response, body) => {
            if (error) {
                reject(error);
            }
            resolve(response.body);
        });
    });
}

async function main(event){
    try {
        //由于令牌有一定时效性,所以我们没必要每一次都要请求,而是将令牌保存重复利用,我们将令牌保存在wxid数据库中的component_access_token文档里

        //首先取出文档的信息
        let access_token = db.get_cat();

        //以当前时间的往后一分钟来作为上限时间
        let overtime = new Date((new Date()).valueOf() + 60 * 1000);

        //如果文档的令牌超时时间大于上限时间,则证明令牌还有效,直接返回令牌
        if (access_token.time > overtime) {
            return access_token.value;
        }
        else {
            //如果小于则证明令牌过期,需要重新申请
            console.log('token timeover!');
            try {
                //取出ticket票据信息
                let ticket = db.get_cvt();
                //将票据信息传入http请求函数,等待请求结果
                let result = await CallWeb(ticket.value);

                //结果是一个json字符串,验证是否有component_access_token字样,如果有则没有报错
                if (result.indexOf('component_access_token') != -1) {

                    //解析字符串为json
                    let { component_access_token, expires_in } = JSON.parse(result);
                    try {
                        //更新令牌,并设定超时时间为当前时间的有效时效后,expires_in为有效秒数
                        db.update_cat(component_access_token,expires_in);
                        //返回新的令牌
                        return component_access_token;
                    }
                    catch (e) {
                        console.log('access save failed!', e);
                        return null;
                    }
                }
                else {
                    console.log('wxcall failed!', result);
                    return result;
                }
            } catch (e) {
                console.log('ticket failed!', e);
                return null;
            }
        }
    }
    catch (e) {
        console.log('access get failed!', e);
        return null;
    }
}

module.exports = main;

如此,当其他模块需要使用第三方平台的access_token,就可以直接用以下方式来调用了,将会返回最新的access_token:

//通过调用getComToken接口获取第三方令牌
let access_token = await require('../getComToken/index')();



三、授权流程配置

1 使用令牌获取预授权码并拼接用户授权链接

根据官方文档-获取预授权码的描述,我们需要component_access_token(第三方平台令牌)、component_appid(第三方平台appid),API接口为:https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=${component_access_token}

在示例项目中定位work/getPreAuth目录,此包含获取预授权码并封装授权链接的逻辑,代码如下:

const request = require('request');
//获取相关的第三方平台信息
const { component_appid } = require('../key.json');
const db = require('../database/lowdb');//使用文件数据库LowDB封装的数据存取接口

//封装的http请求函数
function CallWeb(token) {
    return new Promise((resolve, reject) => {
        request({
            url: 'https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=' + token,
            body: JSON.stringify({
                component_appid
            }),
            method: 'POST'
        }, (error, response, body) => {
            if (error) {
                reject(error);
            }
            resolve(response.body);
        });
    });
}

async function main(event){
    //此接口是由后台管理页面请求的,在调用此接口时就意味着想要创建一个授权的对象,name是对这个对象的备注
    if(event.name==null||event.name=='') return null;
    try {
        //通过调用getComToken接口获取第三方令牌
        let access_token = await require('../getComToken/index')();

        if (access_token != null) {
            //将令牌信息传入http请求函数,等待请求结果
            let result = await CallWeb(access_token);

            //结果是一个json字符串,验证是否有pre_auth_code字样,如果有则没有报错
            if (result.indexOf('pre_auth_code') != -1) {

                //解析字符串为json
                let { pre_auth_code, expires_in } = JSON.parse(result);

                //拼接授权链接,根据此文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Authorization_Process_Technical_Description.html
                let auth_url = `https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=${component_appid}&pre_auth_code=${pre_auth_code}&redirect_uri=https://wx.cloudbase.vip/cloud&auth_type=2`;

                //将授权链接保存到mini数据库内
                return db.update_mini(null,{
                    status: 0,
                    name:event.name,
                    url: auth_url,
                    time: new Date(new Date().getTime() + 1000 * expires_in)
                })
            }
            else {
                console.log('wxcall failed!', result);
                return result;
            }
        }
        else {
            console.log('token get failed');
            return null;
        }
    }
    catch (e) {
        console.log('get failed!', e);
        return null;
    }
}

module.exports = main;

由于所有的模块都遵循work/模块名称/index的路径逻辑,所以在router中可以进行统一的中转外发,如下代码所示:

router.post("/webcall", async function (req, res, next) {
  let {name,data} = req.body;
  if(name){
    res.send(await require(`../work/${name}/index`)(data));
  }
  else{
      res.send(404);
  }
});

我们定位到views/admin.ejs,这是示例项目中简单的管理平台,主要实现了整个的授权小程序管理和部署操作。

其中,在admin.ejs中,我们可以调用getPreAuth模块获取授权路径,如下代码所示:

function getPreAuth() {
  let name = prompt("请输入授权备注名", "");
  if (name) {
      callFunction({
          name: 'getPreAuth',
          data: {
              name: name
          }
      }).then(res => {
          if (res != null) {
              console.log('获取成功', res.id);
              init();
          }
          else {
              alert('获取授权链接失败!')
          }
      });
  }
}

function deletepre(id) {
  callFunction({
      name: 'admindelete',
      data: {
          id: id
      }
  }).then(res => {
      init();
  });
}

function callFunction(obj){
  return new Promise((resolve, reject) => {
      let xml=new XMLHttpRequest();
      xml.open("POST","webcall",true)
      xml.setRequestHeader("Content-type","application/json");
      xml.send(JSON.stringify({
          name:obj.name,
          data:obj.data==null?{}:obj.data
      }));
      xml.responseType='text';
      xml.onreadystatechange=function() {
          if (xml.readyState === 4 && xml.status === 200) {
              let e = JSON.parse(xml.responseText);
              resolve(e);
          }
      }
  });
}

admin页面的router外发配置如下:

router.get("/admin", function (req, res, next) {
  res.render("admin", {});
});

我们可以在页面中通过新增授权来获取一个授权链接了。


2 通过授权发起页域名打开授权链接引导用户扫码

当授权链接创建成功后,我们就能够在管理端admin中得到授权的列表了,列表中每一个项都由数据库文档id来唯一标示,当我们在页面中点击链接时,就可以跳转到域名跟路径了。

域名根路径下是index页面,相关路由代码如下:

router.get("/", function (req, res, next) {
  res.render("index", {});
});

在项目中定位到views/index.ejs文件,在这里是模拟客户的页面。

代码如下:

    客户授权title>
    
head>


    这是微信第三方平台测试——客户端接口div>
    
        let msg_text = document.getElementById('text');
        let query = getQueryString();
        if (query.do != null) {
            msg_text.innerText = "拉取授权页面中……"
            callFunction({
                name: 'authGetOne',
                data: {
                    id: query.do
                }
            }).then(res => {
                console.log(res);
                if (res) {
                    if (res.status == 0) {
                        window.localStorage.setItem('open_auth_id', query.do);
                        window.location = res.url;
                    }
                    else {
                        msg_text.innerText = "已授权!"
                    }
                }
                else {
                    msg_text.innerText = "授权路径不存在!"
                }
            });
        }

        function getQueryString() {
            var qs = location.search.substr(1),
                args = {},
                items = qs.length ? qs.split("&") : [],
                item = null,
                len = items.length;
            for (var i = 0; i < len; i++) {
                item = items[i].split("=");
                var name = decodeURIComponent(item[0]),
                    value = decodeURIComponent(item[1]);
                if (name) {
                    args[name] = value;
                }
            }
            return args;
        }
        function callFunction(obj) {
            return new Promise((resolve, reject) => {
                let xml = new XMLHttpRequest();
                xml.open("POST", "webcall", true)
                xml.setRequestHeader("Content-type", "application/json");
                xml.send(JSON.stringify({
                    name: obj.name,
                    data: obj.data == null ? {} : obj.data
                }));
                xml.responseType = 'text';
                xml.onreadystatechange = function () {
                    if (xml.readyState === 4 && xml.status === 200) {
                        let e = JSON.parse(xml.responseText);
                        resolve(e);
                    }
                }
            });
        }
    script>
body>

html>

这个页面在打开的时候,会根据参数想authGetOne接口去获取授权链接,而参数其实就是创建授权链接时保存在数据库中的文档id。

我们定位到work/authGetOne目录,此处实现了通过文档id获取整个授权信息的逻辑。

const db = require('../database/lowdb');//使用LOWDB实现的简易数据库接口。

function main(event){
    if(event.id!=null){
      //我们在前面步骤已经存入了一个id
        return db.get_lowmini(event.id);
    }
    else{
        return null;
    }
}

module.exports = main;

我们就可以使用w.cloudbase.vip?do=+(上一步保存的id)这个链接来实现授权页面的跳转了。(w.cloudbase.vip为本教程专用,实际实践需要改写为自己的)


3 获取用户授权信息

3.1 根据用户授权后的授权码获取用户授权账号的信息

用户授权后,会在回调的页面中回传授权码,具体表现如下:

其中auth_code参数即为授权码的信息,我们改造一下上一步的index.ejs,在if逻辑下添加else if逻辑

else if (query.auth_code != null) {
  msg_text.innerText = "校验中……"
  let temp_id = window.localStorage.getItem('open_auth_id');
  if (temp_id != null) {
      callFunction({
          name: 'authUpdateOne',
          data: {
              code: query.auth_code,
              id: temp_id
          }
      }).then(res => {
          console.log(res);
          if (res != null) {
              window.localStorage.removeItem('open_auth_id');
              console.log(res);
              msg_text.innerText = "校验成功!";
          }
          else {
              msg_text.innerText = "校验失败,请刷新此页面重试!"
          }
      });
  }
  else {
      msg_text.innerText = "路径已经刷新,请联系平台!"
  }
}

当用户授权成功后,回调回此网址,并附带参数,我们便可以根据参数来进行令牌的获取了。在上面代码中调用了authUpdateOne接口。

我们定位到work/authUpdateOne目录,此实现了获取用户授权的开发相关信息。代码如下

const request = require('request');
//获取相关的第三方平台信息
const { component_appid } = require('../key.json');
const db = require('../database/lowdb');

//封装的http请求函数
function CallWeb(token,code) {
    return new Promise((resolve, reject) => {
        request({
            //获取授权信息的API
            url: 'https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=' + token,
            body: JSON.stringify({
                component_appid,
                authorization_code:code
            }),
            method: 'POST'
        }, (error, response, body) => {
            if (error) {
                reject(error);
            }
            resolve(response.body);
        });
    });
}

async function main(event){
    if(event.code==null||event.id==null) return null;
    try {
        //通过调用getComToken接口获取第三方令牌
        let access_token = await require('../getComToken/index')();

        if (access_token != null) {
             //将令牌信息和授权码传入http请求函数,等待请求结果
            let result = await CallWeb(access_token,event.code);

            console.log(result);

            //结果是一个json字符串,验证是否有authorization_info字样,如果有则没有报错
            if (result.indexOf('authorization_info') != -1) {
                //解析字符串为json
                let { authorization_info } = JSON.parse(result);

                //取出字段
                let { authorizer_access_token,authorizer_appid,authorizer_refresh_token,expires_in,func_info} = authorization_info;

                //存储到mini中相应的文档里,并置状态为1
                return db.update_mini(event.id,{
                    status:1,
                    time:new Date(),
                    func_info,
                    access_token:authorizer_access_token,
                    access_time:new Date(new Date().getTime() + 1000 * expires_in),
                    appid:authorizer_appid,
                    refresh_token:authorizer_refresh_token
                });
            }
            else {
                console.log('wxcall failed!', result);
                return result;
            }
        }
        else {
            console.log('token get failed');
            return null;
        }
    }
    catch (e) {
        console.log('get failed!', e);
        return null;
    }
}

module.exports = main;

当获取成功后,就会在页面中提示授权成功的信息,用户授权步骤完成。

以上是在用户的角度来看到的,在第三方平台角度看到的信息如下,我们可以获得授权账户的appid、接口令牌、刷新令牌、授权集信息。

由于授权用户可以自主修改授权的信息,所以我们需要判断用户授权的授权集是否满足我们的业务开发要求,在下图字段func_info我们可以获得权限数据的列表,我们应该有相应的判断,来进行复核确认,官方文档中已经清楚的写明了。

一切确定了之后,我们便可以根据这些来进行后续的开发操作了。


3.2 过期的授权令牌如何重新刷新

在官方文档中描述,每次提供令牌时,都附带刷新令牌,我们需要通过刷新令牌来去获取最新的令牌,由此反复。

需要注意的是,要好好保存刷新令牌,如果丢失需要用户重新授权才可以。

我们定位到work/getAuthToken目录,此实现了获取刷新令牌的逻辑,代码如下:

const request = require('request');
const { component_appid } = require('../key.json');
const db = require('../database/lowdb');


function CallWeb(refresh_token, access_token, auth_appid) {
    return new Promise((resolve, reject) => {
        request({
            url: 'https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=' + access_token,
            body: JSON.stringify({
                component_appid: component_appid,
                authorizer_appid: auth_appid,
                authorizer_refresh_token: refresh_token
            }),
            method: 'POST'
        }, (error, response, body) => {
            if (error) {
                reject(error);
            }
            resolve(response.body);
        });
    });
}

async function main (event){
    console.log(event);
    if(event.appid==null)return;
    try {
        let auth_data = db.query_mini(event.appid);
        console.log(auth_data);

        if (auth_data) {
            let { access_token, access_time, refresh_token , uuid} = auth_data[0];
            let overtime = new Date((new Date()).valueOf() + 60 * 1000);

            if (access_time > overtime) {
                return access_token;
            }
            else {
                console.log('token timeover!');
                let access_token = await require('../getComToken/index')();

                if (access_token != null) {
                    let result = await CallWeb(refresh_token, access_token, event.appid);
                    console.log(result);

                    if (result.indexOf('authorizer_access_token') != -1) {
                        let { authorizer_access_token, authorizer_refresh_token, expires_in } = JSON.parse(result);
                        db.update_mini(uuid,{
                            access_token: authorizer_access_token,
                            access_time: new Date(new Date().getTime() + 1000 * expires_in),
                            refresh_token: authorizer_refresh_token
                        });
                        return authorizer_access_token
                    }
                    else {
                        console.log('wxcall failed!', result);
                        return null;
                    }
                }
                else {
                    console.log('token get failed');
                    return null;
                }
            }
        }
        else {
            console.log('can not get appid!');
            return null;
        }
    }
    catch (e) {
        console.log('access get failed!', e);
        return null;
    }
}

module.exports = main;

我们可以通过此接口直接获取令牌,如果令牌过期会自动刷新令牌并保存,以备下一次使用。



四、 构建小程序模版

我们在了解完模板小程序的创建过程以及部署流程之后,我们就开始开发编写一个小程序,为了能够较为完整的演示所有可能性,此小程序是一个使用后端服务的企业展示小程序,具体效果如下:

界面数据直接使用wx.request来获取业务服务API的信息(此处需要自行开发)

具体代码可以点击这里下载,其中关键的逻辑信息如下:

const app = getApp()
var that = null;

Page({
  onShow() {
    //加载本地存储
    let data = wx.getStorageSync('data');
    //如果没有存储,直接请求云端数据
    if(data==null||data==""){
      this.init();
    }
    else{
      //有数据即直接部署数据
      this.setData(data);
      wx.setNavigationBarTitle({
        title: data.name,
      })
    }
  },
  //云端请求数据
  init(){
    that = this;
    wx.request({
      url: 'https://ex.com/get', //仅为示例,请更换为自己的api链接
      header: {
        'content-type': 'application/json'
      },
      success (res) {
        //部署数据,设置标题栏内容
        if(res.data.name!=null){
          that.setData(res.data);
          wx.setStorageSync('data', res.data);
          wx.setNavigationBarTitle({
            title: res.data.name,
          })
        }
      },
      fail(e){
        wx.showModal({
          title:"测试失败",
          content:JSON.stringify(e)
        });
      }
    });
  },
  telcall(){
    wx.makePhoneCall({
      phoneNumber: this.data.tel,
    })
  }
})

服务器API返回的数据格式如下(需自行在自己服务器实现返回):

data:{
  content:'企业的描述',
  logo:"企业的logo照片地址",
  name:"企业的名称",
  person:"企业的代表人",
  tel:"企业的电话"
}

在这里特别说明一下,使用传统服务器形式通信的小程序,直接进行request请求调用自定义的url接口(如上边代码的https://ex.com/get),需要注意此处调用的url域名需要在设置的白名单中。

在开发完毕后,即可上传到第三方平台的草稿箱,参照第一章节的步骤将其转化为小程序模版,即可使用模版ID进行下面的部署步骤了。



五、小程序模版部署到小程序中(上传代码与提审)

1 小程序模版部署到授权小程序

在这里我们将使用第三方平台的API接口来执行部署过程。由于所有第三方API接口都需要在白名单中才可以调用。

我们定位到work/uploadcode目录,这里实现了整个小程序部署上传的逻辑:

const request = require('request');

function CallWeb(access_token, data) {
    return new Promise((resolve, reject) => {
        request({
            url: `https://api.weixin.qq.com/wxa/commit?access_token=${access_token}`,
            body: JSON.stringify(data),
            method: 'POST'
        }, (error, response, body) => {
            if (error) {
                reject(error);
            }
            resolve(response.body);
        });
    });
}

async function main(event){
    let { appid,name } = event;
    if (appid != null && name != null) {
        let access_token = await require('../getAuthToken/index')({
            appid
        });
        if (access_token != null) {
            let result = await CallWeb(access_token, {
                template_id : "2",
                user_version : "1.0.1",
                user_desc : "第三方平台代开发",
                ext_json : `{
                    "extEnable": true,
                    "extAppid": "${appid}",
                    "directCommit": false,
                    "window":{
                      "navigationBarTitleText": "${name}"
                    }}`
            });
            try{
                let res = JSON.parse(result);
                console.log(res);
                return res;
            }
            catch(e){
                console.log(e,result);
                return result;
            }
        }
        else {
            return {
                code: -1,
                msg: 'access_token is null'
            }
        }
    }
    else {
        console.log(event);
        return 404;
    }
}

module.exports = main;

在admin页面中,有调用此接口进行小程序模版上传部署,相关代码如下:

function uploadCode(){
  showdesw('开始上传小程序代码……');
  callFunction({
      name: 'uploadCode',
      data: {
          appid: input.appid,
          name:input.name
      }
  }).then(res => {
      console.log(res);
      showdesw('小程序代码上传成功!');
  }).catch(e => {
      console.log(e);
  });
}


2 授权小程序服务器域名配置

我们需要通过官方文档-设置服务器域名来进行域名的设置,与小程序代码的请求地址保持对应,不要设置错。

关于服务器域名设置这里,需要特别注意的是需要先将域名登记到第三方平台的小程序服务器域名中,才可以调用接口进行配置。而且配置时会自动删除原先配置的域名,需要特别注意

具体代码如下:

const request = require('request');

//封装的http请求函数
function CallWeb(access_token,data) {
    return new Promise((resolve, reject) => {
        request({
            url: `https://api.weixin.qq.com/wxa/modify_domain?access_token=${access_token}`,//拼接相应的name,以及令牌
            body: JSON.stringify(data),//请求的数据参数
            method: 'POST'
        }, (error, response, body) => {
            if (error) {
                reject(error);
            }
            resolve(response.body);
        });
    });
}

exports.main = async (event) => {
    //获取appid,参数
    let { appid } = event;
    if (appid != null && name != null && data != null) {
        //传入appid,获得相应的操作令牌
        let access_token = await require('../getAuthToken/index')({
            appid
        });
        if (access_token != null) {
            //传入参数,等待数据
            let result = await CallWeb(access_token,{
                action:"add",
                requestdomain: ["https://www.qq.com", "https://www.qq.com"]
            });
            try{
                let res = JSON.parse(result);
                console.log(res);
                return res;
            }
            catch(e){
                console.log(e,result);
                return result;
            }
        }
        else {
            return {
                code: -1,
                msg: 'access_token is null'
            }
        }
    }
    else {
        console.log(event);
        return 404;
    }
}



3 授权小程序提交审核

当小程序模版上传、服务器配置完毕之后,就需要提交审核了。

相关代码如下:

const request = require('request');

//封装的http请求函数
function CallWeb(access_token,data) {
    return new Promise((resolve, reject) => {
        request({
            url: `https://api.weixin.qq.com/wxa/submit_audit?access_token=${access_token}`,//拼接相应的name,以及令牌
            body: JSON.stringify(data),//请求的数据参数
            method: 'POST'
        }, (error, response, body) => {
            if (error) {
                reject(error);
            }
            resolve(response.body);
        });
    });
}

exports.main = async (event) => {
    //获取appid,参数
    let { appid } = event;
    if (appid != null && name != null && data != null) {
        //传入appid,获得相应的操作令牌
        let access_token = await require('../getAuthToken/index')({
            appid
        });
        if (access_token != null) {
            //传入参数,等待数据
            let result = await CallWeb(access_token,{
                version_desc:"这是我的小程序,请审核"
            });
            try{
                let res = JSON.parse(result);
                console.log(res);
                return res;
            }
            catch(e){
                console.log(e,result);
                return result;
            }
        }
        else {
            return {
                code: -1,
                msg: 'access_token is null'
            }
        }
    }
    else {
        console.log(event);
        return 404;
    }
}



代码示例

本教程用到的示例代码如下:

最后一次编辑于  2021-09-09
赞 4
收藏
登录 后发表内容
课程标签