美文网首页人工智能 大数据 计算机科学
微信小程序与服务器的交互原理

微信小程序与服务器的交互原理

作者: joyitsai | 来源:发表于2018-12-13 17:07 被阅读0次

    1. 在linux服务器中配置nginx

    简单说,微信小程序要正常运行起来,就要跟服务器进行数据交互,微信小程序服务器配置需要使用https安全域名,现在https证书可以免费申请,我用的是阿里云服务器,可以在阿里云上申请免费的https证书。
    关于阿里云服务器配置nginx,请参见《在linux上通过nginx配置微信小程序服务器》。

    pm2的安装使用

    在nginx配置微信小程序服务器的文章里,我们安装的pm2,是一个能够启动运行nodejs项目的工具,内建负载均衡(使用 Node cluster 集群模块)。

    • 安装很简单:
    npm install -g pm2
    
    • 安装完成之后输入:
    pm2 start app.js
    

    问题来了:

    -bash: pm2: command not found
    

    提示没有pm2这个命令,这是怎么回事呢?回头查看安装完成时的信息:

    • 安装命令执行完成时候请注意终端输出内容
    npm install -g pm2
    /www/node-v8.2.1-linux-x64/bin/pm2 -> /www/node-v8.2.1-linux-x64/lib/node_modules/pm2/bin/pm2
    /www/node-v8.2.1-linux-x64/bin/pm2-dev -> /www/node-v8.2.1-linux-x64/lib/node_modules/pm2/bin/pm2-dev
    /www/node-v8.2.1-linux-x64/bin/pm2-docker -> /www/node-v8.2.1-linux-x64/lib/node_modules/pm2/bin/pm2-docker
    /www/node-v8.2.1-linux-x64/bin/pm2-runtime -> /www/node-v8.2.1-linux-x64/lib/node_modules/pm2/bin/pm2-runtime
    

    第一行箭头后面部分是/www/node-v8.2.1-linux-x64/lib/node_modules/pm2/bin/pm2,是pm2的安装目录,显然pm2默认目录不是全局的,下面设置pm2的安装目录软连接到全局下:

    ln -s /www/node-v8.2.1-linux-x64/lib/node_modules/pm2/bin/pm2 /usr/local/bin
    
    • pm2设置为全局,启动一个项目:
    pm2 start app.js
    [PM2] Spawning PM2 daemon with pm2_home=/root/.pm2
    [PM2] PM2 Successfully daemonized
    [PM2] Starting /data/release/weapp/app.js in fork_mode (1 instance)
    [PM2] Done.
    ┌──────────┬────┬─────────┬──────┬───────┬────────┬─────────┬────────┬─────┬──────────┬──────┬──────────┐
    │ App name │ id │ version │ mode │ pid   │ status │ restart │ uptime │ cpu │ mem      │ user │ watching │
    ├──────────┼────┼─────────┼──────┼───────┼────────┼─────────┼────────┼─────┼──────────┼──────┼──────────┤
    │ app      │ 0  │ 1.0.0   │ fork │ 25378 │ online │ 0       │ 0s     │ 0%  │ 6.8 MB   │ root │ disabled │
    └──────────┴────┴─────────┴──────┴───────┴────────┴─────────┴────────┴─────┴──────────┴──────┴──────────┘
     Use `pm2 show <id|name>` to get more details about an app
    

    2. 微信小程序与服务器交互的流程及原理

    官方demo服务端源码解析(了解即可,可略过)

    • 查看小程序服务端的源码,不难发现,在服务器上配置数据库及预创建需连接的数据库是在
      \server\tools\cAuth.sql中实现的:
    SET NAMES utf8;
    SET FOREIGN_KEY_CHECKS = 0;
    -- ----------------------------
    -- Table structure for `cSessionInfo`
    -- ----------------------------
    DROP TABLE IF EXISTS `cSessionInfo`;
    
    CREATE TABLE `cSessionInfo` (
    `open_id` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
    `uuid` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
    `skey` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `last_visit_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `session_key` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
    `user_info` varchar(2048) COLLATE utf8mb4_unicode_ci NOT NULL,
    PRIMARY KEY (`open_id`),
    KEY `openid` (`open_id`) USING BTREE,
    KEY `skey` (`skey`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='会话管理用户信息';
    SET FOREIGN_KEY_CHECKS = 1;
    
    • 然后,在\server\tools\initdb.js中通过knex实例化我们的mysql数据库对象,通过对象DB的raw方法执行我们上面的SQL语句,执行成功后,将在cAuth数据库中创建成功cSessionInfo表:
    /**
    * 腾讯云微信小程序解决方案
    * Demo 数据库初始化脚本
    * @author Jason
    */
    const fs = require('fs')
    const path = require('path')
    // 导入config.js文件作为config对象,调用其中的配置参数
    const { mysql: config } = require('../config')
    
    console.log('\n======================================')
    console.log('开始初始化数据库...')
    // 初始化 SQL 文件路径
    const INIT_DB_FILE = path.join(__dirname, './cAuth.sql')
    const DB = require('knex')({    //通过knex框架链接数据库并实例化为DB对象
      client: 'mysql',
      connection: {
        host: config.host,
        port: config.port,
        user: config.user,
        password: config.pass,
        database: config.db,
        charset: config.char,
        multipleStatements: true
      }
    })
    console.log(`准备读取 SQL 文件:${INIT_DB_FILE}`)
    // 读取 .sql 文件内容
    const content = fs.readFileSync(INIT_DB_FILE, 'utf8')
    console.log('开始执行 SQL 文件...')
    // 执行 .sql 文件内容
    DB.raw(content).then(res => {
      console.log('数据库初始化成功!')
      process.exit(0)
    }, err => {
      throw new Error(err)
    })
    

    以上便是官方测试接口程序在服务端的数据库创建脚本程序。

    编写小程序客户端与服务端交互的代码(重点)

    首先,我们还是要理顺一下小程序客户端与服务端交互的流程与原理,如下图:


    图2.1:客户端与服务端交互原理图
    1. 我自己是通过sequelize框架对数据库进行增删改查的(sequelize和knex都是nodejs框架,用于数据库操作的模块),至于官方demo时通过脚本的方式对数据库初始化,我们完全可以直接在mysql命令行对数据库及表结构进行创建:
    //在mysql中用原生sql语句创建数据库
    mysql>create database meetdata;
    
    // 在数据库中以原生sql语句生成数据库的表结构,以及定义其字段和数据类型
    mysql>create table joiners(
                id varchar(50) COLLATE utf8mb4_unicode_ci not null,
                name varchar(50) COLLATE utf8mb4_unicode_ci not null,
                gender bool not null,
                phone bigint not null,
                company varchar(100) COLLATE utf8mb4_unicode_ci not null,
                imgPath varchar(100) COLLATE utf8mb4_unicode_ci not null,
                createdAt bigint not null, 
                updatedAt bigint not null,
                primary key(id)
     )  ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='备注信息';
    
    1. 上面只能算是对服务器中数据库的创建和表结构的创建,下面是小程序客户端与服务端交互(数据发送和请求):
    • 小程序端上传表单数据和文件的源码(结合图2.1):
      (1)在客户端上传form表单数据和文件或图片(通过wx.uploadFile()实现的示例代码):
    // 提交数据,包括form表单中的数据,和上传的图片到指定的URL中(POST请求方式)
      formSubmit: function (e) {
        var that = this;
        uploadFile(
            "https://www.joyitsai.cn/weapp/uploadFile",    // 上传文件到指定url
            tongzhiImgPath,    //wx.chooseImage()的缓存图片地址
            "uploadImg",     //上传文件时的备注名
            {  // 上传文件时可以将附加的表单数据提交到url,formData为{}格式
              name: e.detail.value.meetname,
              zhuban: e.detail.value.meetZhuban,
              chengban: e.detail.value.meetChengban,
              jieshao: e.detail.value.meetjieshao,
              file1: e.detail.value.file1,
              file2: e.detail.value.file2,
              latitude: Latitude,
              longitude: Longitude,
              Location: location
            });
          // 暂时没有更好的上传多张图片的方式,暂且用多个uploadFile上传多张图片
          uploadFile(
            "https://www.joyitsai.cn/weapp/uploadImage",
            logoImgPath,
            "logo",
            {
            });
      }
    

    (2)在服务器配置路由分发器:
    接着在服务端的路由配置文件中,配置与客户端对应的URL路由:

    /*
    ajax 服务路由集合
     */
    // 配置在客户端的请求URL
    const router = require('koa-router')({
        prefix: '/weapp'
    })
    const controllers = require('../controllers')
    
    // 从 sdk 中取出中间件
    // 这里展示如何使用 Koa 中间件完成登录态的颁发与验证
    const { auth: { authorizationMiddleware, validationMiddleware } } = require('../qcloud')
    
    // 图片上传接口,对应小程序中url的"https://www.joyitsai.cn/weapp/uploadImage"
    router.post('/uploadImage', controllers.uploadImage)
    
    // 文件上传接口,对应小程序中url的"https://www.joyitsai.cn/weapp/uploadFile"
    router.post('/uploadFile', controllers.uploadFile)
    

    (3)编写接受数据存储数据的服务器js脚本:

    const Sequelize = require('sequelize');
    const multer = require('koa-multer');
    const config = require('../config');
    let path = require('path')
    
    var meetImgPath = '';
    var meet_name = '';
    var zhuban_company = '';
    var chengban_company = '';
    var now = null;
    var filetitle = '';
    
    // 创建一个Sequelize对象实例,并通过config.js文件中的连接数据库参数,将sequelize连接到指定数据库
    // 同时说明以mysql的驱动进行操作(因为我们用的是mysql数据库)
    var sequelize = new Sequelize(config.mysql.userdb, config.mysql.user, config.mysql.pass, {
        host: config.mysql.host,
        dialect: 'mysql',
        pool: {
            max: 5,
            min: 0,
            idle: 30000
        }
    });
    
    const storage = multer.diskStorage({
        destination: (req, file, cb) => {     //声明文件存储位置
            meetImgPath = path.resolve(__dirname, '../uploadFiles/');
            console.log(req.body);
    
            // 从request请求中获取上传的文件名
            name = req.body.name
           // 上传的文件存储的路径
            cb(null, path.resolve(__dirname, '../uploadFiles/'));
        },
        // 定义文件名,file即在小程序端上传的原文件
        filename: (req, file, cb) => {
            //.pop()弹出文件扩展名,合并成存储在服务器上时的文件名
            cb(null, `${name}${filetitle}.${file.originalname.split('.').pop()}`);   
        }
    });
    
    // 为multer(opts)定义的opts参数
    const uploadCfg = {
        storage: storage,
        limits: {
            //上传文件的大小限制,单位bytes
            fileSize: 1024 * 1024 * 20
        }
    };
    
    module.exports = async (req, res) => {
        console.log('you are uploading the user informations into the mysql...');
        let upload = multer(uploadCfg).any();
        upload(req, res, async (err) => {
            if(err){
                console.log(err);
                return;
            }else{
                console.log('the file has uploaded into' + path.resolve(__dirname, '../uploadFiles/${uploadFile.filename}'));
            }
        })
    }
    
    • 小程序端向服务器请求数据(结合图2.1):
      (1)通过wx.request()向指定url请求数据
    onLoad: function () {
      var that = this;
      // wx.request时GET请求
      wx.request({
          url: 'https://www.joyitsai.cn/weapp/downloads',
          data: {
    
          },//给服务器传递数据,本次请求不需要数据,可以不填
          header: {
             // 默认值,返回的数据设置为json数组格式
            'content-type': 'application/json' 
          },
          success: function (res) {
            var data = res.data;
            if (data.data.downloads) {
              var downloads = data.data.downloads;
              for(let i=0; i<downloads.length; i++){
                // 下面就是通过请求获取的json合适的数据
                // 依据具体的需求,获取需要的数据,传递到前端
                var download = downloads[i];
              }
              that.setData({
                proList: filelist,
              })
            }
          }, // success: function(){}
          fail: function (res) {
            console.log('下载失败');
          }
        });
    

    (2)在服务器为小程序端的请求url分配路由:

    /*
    ajax 服务路由集合
     */
    // 配置在客户端的请求URL
    const router = require('koa-router')({
        prefix: '/weapp'
    })
    const controllers = require('../controllers')
    
    // 从 sdk 中取出中间件
    // 这里展示如何使用 Koa 中间件完成登录态的颁发与验证
    const { auth: { authorizationMiddleware, validationMiddleware } } = require('../qcloud')
    
    // 图片上传接口,对应小程序中url的"https://www.joyitsai.cn/weapp/uploadImage"
    router.post('/uploadImage', controllers.uploadImage)
    
    // 文件上传接口,对应小程序中url的"https://www.joyitsai.cn/weapp/uploadFile"
    router.post('/uploadFile', controllers.uploadFile)
    
    // 小程序端数据请求接口,对应wx.request中url的'https://www.joyitsai.cn/weapp/downloads'
    router.get('/downloads', controllers.downloads)
    

    (3)编写服务器请求数据库数据并返回给小程序端的js脚本:

    const Sequelize = require('sequelize');
    const multer = require('koa-multer');
    const config = require('../config');
    let path = require('path')
    
    // 创建一个Sequelize对象实例,并通过config.js文件中的连接数据库参数,将sequelize连接到指定数据库
    // 同时说明以mysql的驱动进行操作(因为我们用的是mysql数据库)
    var sequelize = new Sequelize(config.mysql.userdb, config.mysql.user, config.mysql.pass, {
        host: config.mysql.host,
        dialect: 'mysql',
        pool: {
            max: 5,
            min: 0,
            idle: 30000
        }
    });
    
    
    // 定义一个数据模型Download,告诉sequelize如何按照字段及其数据类型去映射到downloads数据库
    var Download = sequelize.define('meetlist', {
            id: {
                type: Sequelize.STRING(50),
                primaryKey: true
            },
            name: Sequelize.STRING(100),
            file1: Sequelize.STRING(100),
            file2: Sequelize.STRING(100),
            createdAt: Sequelize.BIGINT,
            updatedAt: Sequelize.BIGINT,
            latitude: Sequelize.DOUBLE,
            longitude:Sequelize.DOUBLE,
            Location: Sequelize.STRING(100),
            logo: Sequelize.STRING(100)
        },{ timestamps: false }
    );
    
    module.exports = async(ctx) => {
    
        var download_list = new Array();
        // 查找数据库中所有joiner数据
        var downloads = await Download.findAll({
        });
    
        for (let download of downloads) {
            // 对downloads中每个元素按一定需求进行处理后
            // 返回所需的格式
            download_list.push(download);
        }
        console.log(`find ${downloads.length} downloads:`);
    
        let url=ctx.url; //获取url
        // 从上下文中直接获取数据
        let ctx_query = ctx.query; //query返回格式化的对象
        let ctx_querystring = ctx.querystring; //querystring返回原字符
        // 从上下文的request对象中获取
        let request=ctx.request;
        let req_query=request.query; //query返回格式化好的对象
        let req_querystring=request.querystring; //querystring返回原字符串。
        ctx.body={
            data: {downloads: download_list},
            url,
            ctx_query,
            ctx_querystring,
            req_query,
            req_querystring
        }
    }
    

    (4)关于ctx,简单的console.log一下,就知道它是什么了:

    { request:
       { method: 'GET',
         url: '/weapp/downloads',
         header:
          { connection: 'upgrade',
            host: 'www.joyitsai.cn',
            pragma: 'no-cache',
            'cache-control': 'no-cache',
            'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1 wechatdevtools/1.02.1811290 MicroMessenger/6.7.3 Language/zh_CN webview/ token/c6a74c101254905a6526830ac4c466aa',
            'content-type': 'application/json',
            accept: '*/*',
            referer: 'https://servicewechat.com/wx80d2948ba1252c7d/devtools/page-frame.html',
            'accept-encoding': 'gzip, deflate, br' } },
      response: { status: 404, message: 'Not Found', header: {} },
      app: { subdomainOffset: 2, proxy: false, env: 'development' },
      originalUrl: '/weapp/downloads',
      req: '<original node req>',
      res: '<original node res>',
      socket: '<original node socket>' }
    

    相关文章

      网友评论

        本文标题:微信小程序与服务器的交互原理

        本文链接:https://www.haomeiwen.com/subject/xgyocqtx.html