Source: model/action.js

'use strict';

const curl = require('urllib');
const _ = require('lodash');

const MapperJson = require('mapper-json');

const SEND_TYPE_HTTP_REQUEST = 'http-request';

const HTTP_REQUEST_METHOD_POST = 'post';
const HTTP_REQUEST_METHOD_GET = 'get';

module.exports = (sequelize, DataTypes, BaseModel, core) => {
  const { vmCache, logger } = core;
  /**
   * @class
   * Action Model
   * @extends BaseModel
   */
  class Action extends BaseModel {
    /**
     * 通过 script 编程可以修改请求参数
     * @param {string} url
     * @param {object} options
     * @return {object} {url:xxx, options: {}}
     */
    resetRequestOptions(url = '', options = {}) {
      const instance = this;
      let result = {
        url,
        options,
      };

      if (!instance.resetRequestScript) {
        return result;
      }

      const sandBox = {
        url,
        options,
      };
      try {
        result = vmCache.runVmScript(instance.id, 'reset-request', instance.resetRequestScript, sandBox);
      } catch (error) {
        logger.warn(error);
        throw error;
      }

      return result;
    }

    /**
     * 发送http请求
     * @param {string} messageId 消息id
     * @param {object} payload 请求信息载体
     * @return {object} 响应体的 body
     */
    async sendHttp(messageId, payload) {
      const instance = this;
      const {
        httpRequestTargetUri: url,
        httpRequestMethod: method,
        httpRequestTimeout: timeout,
        sendDataTemplate: mapper,
      } = instance;


      const mapperJson = new MapperJson(mapper);

      const targetJson = mapperJson.trans(payload);

      const reqData = {
        ...targetJson,
        messageId,
      };

      const defaultOptions = {
        method,
        dataType: 'json',
        data: reqData,
        timeout,
      };

      // 处理前置脚本
      const reqOptions = instance.resetRequestOptions(url, defaultOptions);

      // 请求结果
      const rep = await curl.curl(reqOptions.url, reqOptions.options);

      const repData = rep.data;

      return repData;
    }


    /**
     * 脚本处理响应结果
     * @param {object} repData 响应结果
     * @return {object}
     */
    collectResponseMatchScript(repData) {
      const instance = this;

      let result = {
        repData,
      };

      if (!instance.responseMatchScript) {
        return result;
      }

      const sandBox = {
        repData,
      };
      try {
        result = vmCache.runVmScript(instance.id, 'response-match', instance.responseMatchScript, sandBox);
      } catch (error) {
        logger.warn(error);
        throw error;
      }

      return result;
    }

    /**
     * 是否符合预期结果
     * @param {object} repData
     * @return {boolean}
     */
    async isMatchResult(repData) {
      const instance = this;

      const scriptData = instance.collectResponseMatchScript(repData);

      return instance.matchResultWithTemplate(scriptData.repData);
    }

    /**
     * 判断结果是否满足条件根据, 与`httpResponseMatchSuccess`相同
     * @param {object} repData
     */
    async matchResultWithTemplate(repData) {
      const instance = this;
      const {
        httpResponseMatchTemplate: mapper,
        httpResponseMatchSuccess: matchResult,
      } = instance;

      const mapperJson = new MapperJson(mapper);

      const actual = mapperJson.trans(repData);

      return _.isEqual(actual, matchResult);
    }
  }

  Action.SEND_TYPE_HTTP_REQUEST = SEND_TYPE_HTTP_REQUEST;

  Action.HTTP_REQUEST_METHOD_POST = HTTP_REQUEST_METHOD_POST;
  Action.HTTP_REQUEST_METHOD_GET = HTTP_REQUEST_METHOD_GET;


  /**
   * Action 行为
   * 属于 try / confirm / channel 之一的具体行为描述
   * 目前基于 http, 在访问指定接口后,对事务实例进行更新
   * @memberof model
   * @namespace Action
   * @property {string}   title                        - 行为标题
   * @property {string}   key                          - 行为key, 唯一不重复
   * @property {string}   desc                         - 描述
   * @property {number}   projectId                    - 对应的项目id
   * @property {number}   proccessId                   - 对应的进程
   * @property {boolean}  status                       - 状态 true 激活、 false 禁用
   * @property {string}   sendType                     - 发送方式 (目前仅支持 http & json)
   * @property {number}   attemptLimit                 - 最大重复次数
   * @property {string}   httpRequestTargetUri         - 请求地址
   * @property {string}   httpRequestMethod            - 请求方式
   * @property {number}   httpRequestTimeout           - 请求超时
   * @property {string}   sendDataTemplate             - 发送模板
   * @property {string}   httpResponseMatchTemplate    - 命中结果的模板
   * @property {string}   httpResponseMatchSuccess     - 命中结果
   * @property {string}   resetRequestScript           - 请求前置可编程脚本
   * @property {string}   responseMatchSuccessScript   - 编程响应脚本
   */
  Action.init({
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
    title: {
      type: DataTypes.STRING(225),
      allowNull: false,
      comment: '行为标题标题',
    },
    key: {
      type: DataTypes.STRING(225),
      allowNull: false,
      comment: '行为key',
    },
    projectId: {
      type: DataTypes.INTEGER,
      allowNull: false,
      comment: '项目id',
    },
    desc: {
      type: DataTypes.TEXT,
      comment: '流程描述',
    },
    status: {
      type: DataTypes.BOOLEAN,
      allowNull: false,
      comment: 'ENABLE 1 激活 DISABLE 0',
    },
    resetRequestScript: {
      type: DataTypes.TEXT,
      allowNull: true,
      defaultValue: '',
      comment: '编程请求脚本, 可缺省',
    },
    sendType: {
      type: DataTypes.ENUM,
      allowNull: true,
      defaultValue: SEND_TYPE_HTTP_REQUEST,
      values: [SEND_TYPE_HTTP_REQUEST],
      comment: '发送方式',
    },
    httpRequestTargetUri: {
      type: DataTypes.STRING(225),
      allowNull: true,
      defaultValue: '',
      comment: 'http 请求地址',
    },
    httpRequestMethod: {
      type: DataTypes.ENUM,
      allowNull: true,
      defaultValue: HTTP_REQUEST_METHOD_POST,
      values: [HTTP_REQUEST_METHOD_POST, HTTP_REQUEST_METHOD_GET],
      comment: 'http 发送方式 post/get',
    },
    httpRequestTimeout: {
      type: DataTypes.INTEGER,
      defaultValue: 1000,
      comment: 'http请求超时',
    },
    sendDataTemplate: {
      type: DataTypes.JSON,
      defaultValue: {},
      comment: '发送的数据模板',
    },
    sendDataTemplateDesc: {
      type: DataTypes.JSON,
      defaultValue: {},
      comment: '发送的数据模板描述',
    },
    httpResponseMatchTemplate: {
      type: DataTypes.JSON,
      defaultValue: {},
      comment: '命中结果模板',
    },
    httpResponseMatchSuccess: {
      type: DataTypes.JSON,
      defaultValue: {},
      comment: 'httpResponse 命中结果',
    },
    httpResponseMatchTemplateDesc: {
      type: DataTypes.JSON,
      defaultValue: {},
      comment: 'httpResponse 命中结果描述',
    },
    responseMatchSuccessScript: {
      type: DataTypes.TEXT,
      allowNull: true,
      defaultValue: '',
      comment: '编程响应脚本, 可缺省',
    },
    attemptLimit: {
      type: DataTypes.INTEGER,
      allowNull: false,
      defaultValue: 5,
      comment: '尝试次数',
    },
    createdAt: DataTypes.DATE,
    updatedAt: DataTypes.DATE,
    version: DataTypes.INTEGER,
  }, {
    timestamps: true,
    paranoid: false,
    version: true,
    freezeTableName: true,
    // define the table's name
    tableName: 'actions',
    modelName: 'Action',
    sequelize,
    indexes: [
      { unique: true, fields: ['key', 'projectId'] },
    ],
  });

  return Action;
};