切换虚拟生产环境

688 阅读4分钟

功能概括

提供环境切换的入口,方便验证不同环境数据(以微信小程序为例,仅供内部使用,因此【开发者-环境切换】入口在对外的生产环境不显示)。如:开发人员上传测试环境的小程序包到微信管理后台,产品或测试人员进入【开发者-环境切换】,切换为预发布环境,验收预发布数据,这样可以省去开发人员上传不同环境的小程序版本到后台的操作。

主要流程

原理:读取envEnumerate.js配置的ISPROD字段配置,如果 ISPROD=false,显示【开发者-环境切换】入口。切换环境后,设置切换的环境值到storage,重新进入小程序检查是否存在环境storage,有则获取storage的值,否则获取脚本配置的值。

流程如图:

效果如图:

准备工作

envEnumerate.js
/**
 * 环境变量枚举
 */
const dev = {
  NAME: 'dev',
  DESC: '测试环境',
  ISPROD: false,
};

const pre = {
  NAME: 'pre',
  DESC: '预发布环境',
  ISPROD: false,
};

const gray = {
  NAME: 'gray',
  DESC: '灰度环境',
  ISPROD: true,
};

const prod = {
  NAME: 'prod',
  DESC: '生产环境',
  ISPROD: true,
};

module.exports = {
  dev,
  pre,
  gray,
  prod,
};

配置主要代码 dev.js

import { developEnv } from 'developEnv.js';		// 获取缓存的环境,如:字符串dev/pre/gray/prod
import envEnumerate from 'envEnumerate.js';

/**
 * 脚本生成的配置
 * NAME: 环境变量名称
 * DESC: 环境变量描述: 测试环境/预发布环境/灰度环境/生产环境
 * ISPROD: 环境变量,是否正式环境,true-正式环境, false-测试环境
 */
const scriptConfig = {
  "NAME": "dev",
  "DESC": "测试环境",
  "ISPROD": false
};

const { ISPROD, NAME, DESC } = scriptConfig;

let currentEnvDesc = DESC,	// 当前的环境描述
    currentEnvName = NAME,	// 当前的环境name
    isDev = false;	// 是否测试环境(布尔值)

// 非生产、灰度环境且有缓存, 取缓存值
if (!ISPROD && developEnv && developEnv === envEnumerate[developEnv].NAME) {
    currentEnvName = envEnumerate[developEnv].NAME;
    currentEnvDesc = envEnumerate[developEnv].DESC;
}

// 返回是否测试环境(布尔类型,方便后续的判断)
if (currentEnvName === envEnumerate.dev.NAME) {
    isDev = true;
}

module.exports = {
    isProd: ISPROD,
    isDev,
    currentEnvDesc,
    currentEnvName,
};

参数说明

  1. currentEnvName
    • 参数说明:字符串,指示当前是什么环境。如:dev-测试环境,pre-预发布环境,gray-灰度环境,prod-生产环境
    • 值来源:
      1. 脚本配置。读取脚本 NAME 字段,如:运行npm run start:dev后,脚本返回的currentEnvName是dev
      2. 读取缓存。若当前是非生产和灰度环境,并且有缓存值,currentEnvName读取缓存值。
    • 可能影响的业务:
      1. 请求的域名环境
      2. http请求头,如:header-region: dev
  2. isProd
    • 参数说明:布尔值,当前是否生产或灰度环境。
    • 值来源:脚本配置。脚本的 ISPROD 字段。
    • 可能影响的业务:是否显示【开发者-环境切换】入口。true-不显示,false-显示。
  3. isDev
    • 参数说明:布尔值,是否测试环境。如:true-测试环境,false-非测试环境(预发布、灰度、生产)
    • 值来源:默认值是false,如果currentEnvName = 'dev' 返回true
    • 可能影响的业务:云函数环境值等

附:执行switch.js脚本文件修改dev.js

/**
 * 根据命令行运行参数,动态地将 envEnumerate.js 下的配置文件的内容写入到 dev.js 中
 */
const fs = require('fs');
const envEnumerate = require('./envEnumerate.js');

// 获取命令行参数
const cliArgs = process.argv.splice(2);
const env = cliArgs[0].slice(2);

// 根据不同环境选择不同的环境配置信息
const targetConfig = envEnumerate[env];

// 写入的目标文件
const targetFiles = [
  {
    prefix: '/files/',
    filename: 'dev.js',
  },
];

fs.readFile(
  __dirname + targetFiles[0].prefix + targetFiles[0].filename,
  (readErr, data) => {
    if (readErr) {
      throw new Error(
        `Error occurs when reading file ${targetFiles[0].prefix}${targetFiles[0].filename}.\nError detail: ${readErr}`
      );
      process.exit(1);
    }

    // 获取目标文件原来的内容
    const targetFileData = data.toString();
    const writeData = targetFileData.replace(
      /scriptConfig[\s\S]*?;/,
      `scriptConfig = ${JSON.stringify(targetConfig, null, 2)};`
    );
    console.log(`已切换到${targetConfig.DESC}`);

    // 将获取的内容写入到目标文件中
    targetFiles.forEach(function (item) {
      let result = null;
      if (item.filename === 'dev.js') {
        result = writeData;
      }
      // 写入文件(这里只做简单的强制替换整个文件的内容)
      fs.writeFile(
        __dirname + item.prefix + item.filename,
        result,
        'utf8',
        (writeErr) => {
          if (writeErr) {
            throw new Error(
              `error occurs when writing file. Error detail: ${writeErr}`
            );
            process.exit(1);
          }
        }
      );
    });
  }
);

package.json添加如下配置:

"scripts": {
        "start:dev": "node switch.js --dev",
        "start:pre": "node switch.js --pre",
        "start:prod": "node switch.js --prod",
        "start:gray": "node switch.js --gray"
    },

执行 npm run start:pre 即可修改dev.js的scriptConfig变量为预发布环境(其他环境同理)。

方案缺陷

这种不发请求接口到后端数据库存储当前切换的环境,仅读取小程序缓存来判断环境的方式,加上微信小程序的storage缓存,是不隔离版本的(体验版和发布版本共用storage缓存),可能会出现脚本npm run start:pre并上传到小程序后台作为体验版,此时扫开发板二维码进入页面后,点击切换到测试环境,这时候再访问体验版,仍然是显示测试环境的内容。即命中了如下的判断:

// 非生产、灰度环境且有缓存, 取缓存值
if (!ISPROD && developEnv && developEnv === envEnumerate[developEnv].NAME) {
    currentEnvName = envEnumerate[developEnv].NAME;
    currentEnvDesc = envEnumerate[developEnv].DESC;
}

如果是比较重视的功能,需要另外的精细化处理,比如:如何隔离微信小程序不同版本的缓存、ISPROD字段更加精细化的定义(可能需要新增多几个字段来区分)、不同环境的判断。但如果是日常的测试/开发内部使用,可以先这样简单处理。