text: const path = require("path");
  const defaultDbNameObj = {
      mysql: ["information_schema", "performance_schema", "mysql", "test"],
      mariadb: ["information_schema", "mysql", "performance_schema", "sys"],
      postgres: ["postgres"],
      mssql: ["master", "model", "tempdb", "msdb", "northwind", "pubs"],
      sqlite: ["main"]
  };
  
  module.exports = async function(conf){
      if (!await ast.noEmpJson(conf, {error: false})){
          return {};
      }
  
      // 判断数据库配置是数组还是json并分别判断
      let {
          connects, // 数据库链接参数。
          entityPath, // 实体路径。
          createScheame, // 是否创建数据库。
          freezeTableName, // 表名是否按复数。
          underscored, // 是否不使用驼峰式命令规则。
          syncTable, // 同步的表名（表不存在才同步）。
          forceTable, // 强制同步的表名，同步基础为syncTable的范围。
          matchTable, // 强制同步的表名【正则匹配】，matchTable和forceTable都存在的话则强制同步他们的并集，同步基础为syncTable的范围。
          autoCol // 是否自动向每个模型添加 `createdAt`、`updatedAt`、`deletedAt` 字段，可为0个或一个或多个。
      } = conf;
  
      let dbConfObj = {
          connects: {},
          entityPath: "entity",
          createScheame: true,
      };
  
      let errInfo = {message: "无效的数据库链接信息"};
  
      // 实体路径
      if (typeof entityPath === "string"){
          dbConfObj.entityPath = entityPath;
      }
  
      let matchType = Object.prototype.toString.call(matchTable);
      await ast.ok( matchType === "[object RegExp]" || matchType === "[object String]", {message: "【matchTable】只能为正则表达式或字符串"} );
  
      // 数组格式
      if ( await ast.noEmpStrictArray(connects, {error: false}) ){
          // 单条数据和多条数据是两个处理逻辑。
          // 单条数据是可以没有名称的，多条数据必须有名称。
  
          // 单条数据
          if (connects.length === 1){
              let connect = connects[0];
              await ast.noEmpJson(connect, errInfo);
              let name = connect.name || "default";
              connect.name = null;
              delete connect.name;
              let tableObj = await collectEntity(dbConfObj.entityPath, name, "single");
              dbConfObj.connects[name] = await astDBConfObj(
                  connect, name, freezeTableName, underscored,
                  tableObj, syncTable, forceTable, matchTable, autoCol);
          } else {
              // 多条数据
              for (let index = 0; index < connects; ++index){
                  let connect = connects[index];
                  await ast.noEmpJson(connect, errInfo);
                  let name = connect.name;
                  connect.name = null;
                  delete connect.name;
                  let tableObj = await collectEntity(dbConfObj.entityPath, name, "multi");
                  dbConfObj.connects[name] = await astDBConfObj(
                      connect, name, freezeTableName, underscored,
                      tableObj, syncTable, forceTable, matchTable, autoCol);
              }
          }
  
      } else if ( await ast.noEmpJson(connects, {error: false}) ){
      // json格式
          // 判断是单个的链接参数json还是多个带名称的链接参数json
          if (
              connects.database || connects.username || connects.password || connects.options
          ){
              // 只要有database、username、password、options其中一个属性，就代表可能是单个的链接参数json
              let connect = connects;
              await ast.noEmpJson(connect, errInfo);
              let name = connect.name || "default";
              connect.name = null;
              delete connect.name;
              let tableObj = await collectEntity(dbConfObj.entityPath, name, "single");
              dbConfObj.connects[name] = await astDBConfObj(
                  connect, name, freezeTableName, underscored,
                  tableObj, syncTable, forceTable, matchTable, autoCol);
          } else {
              // 否则就是多个链接参数
              for (let key in connects){
                  let connect = connects[key];
                  await ast.noEmpJson(connect, errInfo);
                  let name = connect.name || key;
                  connect.name = null;
                  delete connect.name;
                  let tableObj = await collectEntity(dbConfObj.entityPath, name, "multi");
                  dbConfObj.connects[name] = await astDBConfObj(
                      connect, name, freezeTableName, underscored,
                      tableObj, syncTable, forceTable, matchTable, autoCol);
              }
          }
      } else {
          return {};
      }
  
      // 是否创建数据库
      if (typeof createScheame === "boolean"){
          dbConfObj.createScheame = createScheame;
      }
  
      return dbConfObj;
  };
  
  /***
   * @description 校验并格式化数据库配置信息
   * @param {json} connect - 基础路径
   * @param {string} name - 数据库名称
   * @param {boolean} freezeTableName - 表名是否按复数。
   * @param {boolean} underscored - 是否使用驼峰式命令规则。自动设置字段为蛇型命名规则,不会覆盖已定义的字段选项属性。
   * @param {string|string[]} tableObj - 此数据库下的表数据。
   * @param {string|string[]} syncTable - 数据库要同步的表， 如果表已存在则会跳过。
   * @param {string|string[]} forceTable - 数据库要强制同步的表， 即使表已存在也会重建，同步基础为syncTable的范围。。
   * @param {string|string[]} matchTable - 正则匹配数据库要同步的表， 即使表已存在也会重建，matchTable和forceTable都存在的话则强制同步他们的并集，同步基础为syncTable的范围 。
   * @param {string|string[]} autoCol - 是否自动向每个模型添加 `createdAt`、`updatedAt`、`deletedAt` 字段，可为0个或一个或多个。
   */
  async function astDBConfObj(
      connect, name, freezeTableName, underscored,
      tableObj, syncTable, forceTable,
      matchTable, autoCol){
  
      // 要返回的结果
      let result = {
          // 库链接配置
          connect: {
              username: null,
              password: null,
          },
          // 库链接选项配置
          opts: {
              freezeTableName: false,
              underscored: false,
              autoCol: {
                  "timestamps": false,
                  "createdAt": false,
                  "updatedAt": false,
                  "paranoid": false,
                  "deletedAt": false,
              },
          },
          // 表信息配置
          tableObj: {}
      };
  
  
      // --------------------
      // 校验库链接配置
      // sequelize只支持 mysql、mariadb、sqlite、postgres、mssql 这几个数据库
      let errInfo = {message: `【${name}】的数据库配置信息无效`};
      await ast.noEmpJson(connect.options, errInfo);
  
      connect.options.dialect = `${connect.options.dialect}`.toLowerCase();
      await ast.ok(["mysql", "mariadb", "sqlite", "postgres", "mssql"].indexOf(connect.options.dialect) !== -1, errInfo);
      result.connect.options = connect.options;
  
      if (typeof connect.database === "string"){
          result.connect.database = connect.database;
      } else {
          result.connect.database = defaultDbNameObj[connect.options.dialect][0];
          logger.warn(`【${name}】的数据库配置信息内没有要操作的数据库信息，已为您自动切换至数据库【${result.connect.database}】`);
      }
  
      await ast.ok(typeof connect.username === "string" && typeof connect.password === "string", errInfo);
  
      result.connect.username = connect.username;
      result.connect.password = connect.password;
  
      // --------------------
      // 校验库链接选项参数
      // 表名是否按复数。
      if (typeof freezeTableName === "boolean"){
          result.opts.freezeTableName = freezeTableName;
      }
  
      // 是否使用驼峰式命令规则。自动设置字段为蛇型命名规则,不会覆盖已定义的字段选项属性.
      if (typeof underscored === "boolean"){
          result.opts.underscored = underscored;
      }
  
      // 是否自动向每个模型添加 `createdAt`、`updatedAt`、`deletedAt` 字段。
      // 注意使用autoCol时，如果要添加任意一个字段，都需要打开timestmp。
      // 如果添加deletedAt字段，还需要打开paranoid
      if (autoCol){
          // 为string则代表添加所有或只添加一个，且名称为默认名称
          if (typeof autoCol === "string"){
              result.opts.autoCol = await setAutoCol(result.opts.autoCol, autoCol);
          } else if (await ast.noEmpArray(autoCol, {error: false})){
              // 为数组时，可能是多个字段组合
              for (let i = 0; i < autoCol.length; ++i){
                  result.opts.autoCol = await setAutoCol(result.opts.autoCol, autoCol[i]);
              }
          } else if (await ast.noEmpJson(autoCol, {error: false})){
              // 为json时，可能是字段指定字段名
              // 且为json时，只会识别`createdAt`、`updatedAt`、`deletedAt`单个字段，不识别all
              for (let col in autoCol){
                  if (col.toLowerCase() !== "all"){
                      result.opts.autoCol = await setAutoCol(result.opts.autoCol, col, autoCol[col]);
                  }
              }
          }
      }
  
      // --------------------
      // 校验表名信息
      for (let tableName in tableObj){
          result.tableObj[tableName] = tableObj[tableName];
          result.tableObj[tableName].sync = false;
          result.tableObj[tableName].force = false;
  
          if (syncTable === "all"){
              result.tableObj[tableName].sync = true;
          } else if (await ast.noEmpArray(syncTable, {error: false})){
              if (syncTable.findIndex(item => `${tableName}`.toLowerCase() === item.toLowerCase()) !== -1){
                  result.tableObj[tableName].sync = true;
              }
          }
  
          if (forceTable === "all"){
              result.tableObj[tableName].sync = true;
              result.tableObj[tableName].force = true;
          } else if (await ast.noEmpArray(forceTable, {error: false})){
              if (forceTable.findIndex(item => `${tableName}`.toLowerCase() === item.toLowerCase()) !== -1){
                  result.tableObj[tableName].sync = true;
                  result.tableObj[tableName].force = true;
              }
          }
  
          if (Object.prototype.toString.call(matchTable) === "[object RegExp]" || Object.prototype.toString.call(matchTable) === "[object String]"){
              if (new RegExp(matchTable).test(tableName) || matchTable.toLowerCase() === "all"){
                  result.tableObj[tableName].sync = true;
                  result.tableObj[tableName].force = true;
              }
          }
  
      }
      return result;
  }
  
  /***
   * @description 设置自动字段参数
   * @param {json} autoCol - 自动字段对象
   * @param {string} col - 字段名
   * @param {string} value -参数
   */
  async function setAutoCol(autoCol, col, value = true){
      switch (col.toLowerCase()) {
          case "all":
              autoCol.timestamps = true;
              autoCol.createdAt = true;
              autoCol.updatedAt = true;
              autoCol.paranoid = true;
              autoCol.deletedAt = true;
              break;
          case "createdat":
              autoCol.timestamps = true;
              autoCol.createdAt = value;
              break;
          case "updatedat":
              autoCol.timestamps = true;
              autoCol.updatedAt = value;
              break;
          case "deletedat":
              autoCol.timestamps = true;
              autoCol.paranoid = true;
              autoCol.deletedAt = value;
      }
      return autoCol;
  }
  
  /***
   * @description 收集实体文件
   * @param {json} basicPath - 基础路径
   * @param {string} dbName - 数据库名称
   * @param {string} type -数据库链接配置类型，single是单个数据库链接。
   */
  async function collectEntity(basicPath, dbName, type) {
      // 基础路径
      basicPath = path.resolve(basicPath);
      let introducePath = path.join(basicPath, dbName);
      await filePath.recursionMkdir(introducePath);
      // 声明表信息的数组
      let tableObj = {};
  
      // 实体一般是分布在基础实体路径下的数据库名目录内，所以先收集这个目录下的实体文件
      let introduceFileArray = await filePath.collectFile(introducePath, ["js"], "short");
      tableObj = await disposeTableInfoObj(tableObj, dbName, introducePath, introduceFileArray);
  
      // type为single时，代表是单个数据库链接。单个数据库链接时，有可能都分布在根目录。
      if (type === "single"){
          let basicFileArray = await filePath.collectFile(basicPath, ["js"], "short");
          tableObj = await disposeTableInfoObj(tableObj, dbName, basicFileArray, basicFileArray);
      }
  
      return tableObj;
  }
  
  /***
   * @description 处理表信息集合
   * @param {json} tableObj - 表信息集合
   * @param {string} dbName - 数据库名称
   * @param {string} filePath - 文件路径
   * @param {string[]} fileArray - 文件数组
   * @returns {json}
   */
  async function disposeTableInfoObj(tableObj, dbName, filePath, fileArray){
      for (let i = 0; i < fileArray.length; ++i){
          let tableName;
          let fileName = fileArray[i];
          let tableInfo = require(path.join(filePath, fileName));
          if (await ast.noEmpJson(tableInfo.options, {error: false})){
              tableName = tableInfo.options.tableName || fileName;
              tableInfo.options.tableName = null;
              delete tableInfo.options.tableName;
          } else {
              tableInfo.options = null;
              delete tableInfo.options;
          }
  
          if (await ast.noEmpJson(tableObj[tableName], {error: false})){
              await ast.report({message: `数据库【${dbName}】的实体表【${tableName}】与【${filePath}】下实体文件的【${fileName}】表名重复`});
          }
  
          if (!await ast.noEmpJson(tableInfo.attributes, {error: false})){
              logger.warn(`实体【${tableName}】无任何字段信息，实体文件路径为【${path.join(filePath, fileName)}】,已跳过此实体`);
              continue;
          }
          tableObj[tableName] = tableInfo;
      }
      return tableObj;
  }
  