• DBUnit增强:填充随机数据和相对时间数据


    痛点

    测试环境验证时,遇到与当前相对时间相关的测试吗?准备一份SQL?隔一段时间就不能用了。每过一段时间去更新脚本或重置系统时间?看上去也不是很合适的解决方案。依赖数据测试时要重新做,演示时候得全部改,如果你遇到一样的问题,可以看下本人使用的这个解决方案

    DBUnit

    DBUnit是一个测试框架,它通过一份XML数据反向设置到数据库,能自动维护数据的格式(有多少人遇到数值字段忘记加引号的BUG的举手)。准备的数据类似这样:

    1. "10000" UPPER_TREE_NODE_ID="5" TREE_NODE_TYPE_CODE="4" SYS_OBJECT_ID="10000" ICON="[null]" TREE_NODE_NM="安徽省" TREE_NODE_POSITION="100" IF_SYS_RESERVE="N" NODE_PATH_DATA="5" CREATE_TIME="$R{T0*}R$" UPDATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    2. "10900" UPPER_TREE_NODE_ID="5" TREE_NODE_TYPE_CODE="4" SYS_OBJECT_ID="10900" ICON="[null]" TREE_NODE_NM="北京市" TREE_NODE_POSITION="100" IF_SYS_RESERVE="N" NODE_PATH_DATA="5" CREATE_TIME="$R{T0*}R$" UPDATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    3. "11800" UPPER_TREE_NODE_ID="5" TREE_NODE_TYPE_CODE="4" SYS_OBJECT_ID="11800" ICON="[null]" TREE_NODE_NM="重庆市" TREE_NODE_POSITION="100" IF_SYS_RESERVE="N" NODE_PATH_DATA="5" CREATE_TIME="$R{T0*}R$" UPDATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>

    由于统一都是字符形式,手写比较舒服。也可以从数据库导出为XML

    方案

    DBUnit有一个扩展点,就是可以添加FilteredDataSet来实现加载过程中的数据过滤/替换,因为操作的执行接口:

    1. public abstract void execute(IDatabaseConnection connection,
    2. IDataSet dataSet) throws DatabaseUnitException, SQLException;

    执行的是一个IDataSet接口,该接口实现数据的状态过程,我们可以基于该接口进行扩展,在状态的过程中,对于我们需要的数据进行替换。这里我们选择从AbstractDataSet进行扩展。

    替换处理器

    我们定义了一组ReplacementProcessor,用于对读取过程的数据进行替换处理

    1. public interface ReplacementProcessor {
    2. Object replacementSubStrToObject(ITable table, String column, String string, IDatabaseConnection connection, DBTYPE dbType);
    3. String getStartDelim();
    4. String getEndDelim();
    5. }

    我们实现的ReplacementDataSet如下

    1. package org.ccframe.commons.dbunit;
    2. import org.ccframe.commons.util.DbUnitUtils.DBTYPE;
    3. import org.dbunit.database.IDatabaseConnection;
    4. import org.dbunit.dataset.*;
    5. import org.slf4j.Logger;
    6. import org.slf4j.LoggerFactory;
    7. import java.util.ArrayList;
    8. import java.util.List;
    9. public class ReplacementDataSet extends AbstractDataSet {
    10. /**
    11. * Logger for this class
    12. */
    13. private static final Logger LOGGER = LoggerFactory.getLogger(ReplacementDataSet.class);
    14. private final IDataSet dataSet;
    15. private IDatabaseConnection connection;
    16. private DBTYPE dbType;
    17. private List processorList = new ArrayList();
    18. private static int tableId = 0;
    19. public ReplacementDataSet(IDataSet dataSet, IDatabaseConnection connection, DBTYPE dbType, List processorList)
    20. {
    21. this.dataSet = dataSet;
    22. this.connection = connection;
    23. this.dbType = dbType;
    24. this.processorList.addAll(processorList);
    25. }
    26. private ReplacementTable createReplacementTable(ITable table)
    27. {
    28. tableId ++;
    29. return new ReplacementTable(table, tableId, connection, dbType, processorList);
    30. }
    31. // AbstractDataSet class
    32. protected ITableIterator createIterator(boolean reversed)
    33. throws DataSetException
    34. {
    35. return new ReplacementIterator(reversed ?
    36. dataSet.reverseIterator() : dataSet.iterator());
    37. }
    38. // IDataSet interface
    39. public String[] getTableNames() throws DataSetException
    40. {
    41. LOGGER.debug("getTableNames() - start");
    42. return dataSet.getTableNames();
    43. }
    44. public ITableMetaData getTableMetaData(String tableName)
    45. throws DataSetException
    46. {
    47. return dataSet.getTableMetaData(tableName);
    48. }
    49. public ITable getTable(String tableName) throws DataSetException
    50. {
    51. return createReplacementTable(dataSet.getTable(tableName));
    52. }
    53. // ReplacementIterator class
    54. private class ReplacementIterator implements ITableIterator
    55. {
    56. // private final Logger logger = LoggerFactory.getLogger(ReplacementIterator.class);
    57. private final ITableIterator iterator;
    58. public ReplacementIterator(ITableIterator iterator)
    59. {
    60. this.iterator = iterator;
    61. }
    62. // ITableIterator interface
    63. public boolean next() throws DataSetException
    64. {
    65. return iterator.next();
    66. }
    67. public ITableMetaData getTableMetaData() throws DataSetException
    68. {
    69. return iterator.getTableMetaData();
    70. }
    71. public ITable getTable() throws DataSetException
    72. {
    73. return createReplacementTable(iterator.getTable());
    74. }
    75. }
    76. }

    这里我们针对ITable实现了自己的替换用的数据表ReplacementTable,用于在读取过程中getValue时进行替换

    1. package org.ccframe.commons.dbunit;
    2. import com.google.common.base.CaseFormat;
    3. import org.ccframe.commons.helper.SpringContextHelper;
    4. import org.ccframe.commons.util.DbUnitUtils.DBTYPE;
    5. import org.ccframe.config.Global;
    6. import org.dbunit.database.IDatabaseConnection;
    7. import org.dbunit.dataset.DataSetException;
    8. import org.dbunit.dataset.ITable;
    9. import org.dbunit.dataset.ITableMetaData;
    10. import org.redisson.api.RAtomicLong;
    11. import org.redisson.api.RedissonClient;
    12. import org.slf4j.Logger;
    13. import org.slf4j.LoggerFactory;
    14. import java.util.LinkedHashMap;
    15. import java.util.List;
    16. import java.util.Map;
    17. public class ReplacementTable implements ITable{
    18. /**
    19. * Logger for this class
    20. */
    21. private static final Logger LOGGER = LoggerFactory.getLogger(ReplacementTable.class);
    22. private static final int OBJECT_CACHE_SIZE = 50; //50个field
    23. private final ITable table;
    24. private IDatabaseConnection connection;
    25. private DBTYPE dbType;
    26. private List processorList;
    27. /**
    28. * 每切换一个不同的表代表一个新的tableId,用于结合行号标记唯一的XML表位置。例如XML1和XML2的SYS_USER表的tableId就会不一样.
    29. */
    30. private int tableId;
    31. private String pkId;
    32. private RAtomicLong atomicLong;
    33. private RAtomicLong getAtomicLong() {
    34. if(atomicLong == null) {
    35. atomicLong = SpringContextHelper.getBean(RedissonClient.class).getAtomicLong(Global.REDIS_PERFIX + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, this.pkId));
    36. if(atomicLong.get() == 0) {
    37. atomicLong.getAndIncrement();
    38. }
    39. }
    40. return atomicLong;
    41. }
    42. private Map getRowObjectCache = new LinkedHashMap(5){
    43. private static final long serialVersionUID = 1L;
    44. @Override
    45. protected boolean removeEldestEntry(Map.Entry eldest) {
    46. // 当前记录数大于设置的最大的记录数,删除最旧记录(即最近访问最少的记录)
    47. return size() > OBJECT_CACHE_SIZE;
    48. }
    49. };
    50. private Map getRowIdCache = new LinkedHashMap(5){
    51. private static final long serialVersionUID = 1L;
    52. @Override
    53. protected boolean removeEldestEntry(Map.Entry eldest) {
    54. // 当前记录数大于设置的最大的记录数,删除最旧记录(即最近访问最少的记录)
    55. return size() > OBJECT_CACHE_SIZE;
    56. }
    57. };
    58. public ReplacementTable(ITable table, int tableId, IDatabaseConnection connection, DBTYPE dbType, List processorList)
    59. {
    60. this.table = table;
    61. this.connection = connection;
    62. this.dbType = dbType;
    63. this.processorList = processorList;
    64. this.tableId = tableId;
    65. this.pkId = table.getTableMetaData().getTableName();
    66. this.pkId = this.pkId.substring(this.pkId.indexOf("_") + 1) + "_ID";
    67. }
    68. // ITable interface
    69. public ITableMetaData getTableMetaData()
    70. {
    71. return table.getTableMetaData();
    72. }
    73. public int getRowCount()
    74. {
    75. return table.getRowCount();
    76. }
    77. public Object getValue(int row, String column) throws DataSetException{
    78. //注意会进来2次,第一次是ignore mapping,第二次才是取值。因此要cache住行号列号位置,避免某些替换的动作重复了2次
    79. String value = (String)table.getValue(row, column);
    80. if(pkId.equals(column)) { //主键
    81. if(value == null) {
    82. Object objectCached = getRowIdCache.get(tableId + "-" + row);
    83. if(objectCached == null) {
    84. objectCached = new Long(getAtomicLong().getAndIncrement()).intValue();
    85. getRowIdCache.put(tableId + "-" + row, objectCached);
    86. }
    87. return objectCached;
    88. } else { //同步ID
    89. long next = getAtomicLong().get();
    90. int dbId = Integer.parseInt(value); //id必须是Integer
    91. if(next <= dbId) {
    92. getAtomicLong().set(dbId + 1);
    93. }
    94. return dbId;
    95. }
    96. }else { //非主键
    97. if (value == null){
    98. return null;
    99. }
    100. for(ReplacementProcessor processor: processorList){
    101. if (processor.getStartDelim() != null && processor.getEndDelim() != null && value.toString().startsWith(processor.getStartDelim()) && value.toString().endsWith( processor.getEndDelim())){
    102. Object objectCached = getRowObjectCache.get(tableId + "-" + row + "-" + column);
    103. if(objectCached == null) {
    104. objectCached = processor.replacementSubStrToObject(table, column, value.substring(processor.getStartDelim().length(), value.length() - processor.getEndDelim().length()), connection, dbType);
    105. getRowObjectCache.put(tableId + "-" + row + "-" + column, objectCached);
    106. }
    107. return objectCached;
    108. }
    109. }
    110. return value;
    111. }
    112. }
    113. public String toString()
    114. {
    115. StringBuffer sb = new StringBuffer();
    116. sb.append(getClass().getName()).append("[");
    117. sb.append(", table=").append(table);
    118. sb.append("]");
    119. return sb.toString();
    120. }
    121. }

    语法

    接下来我们实现一个替换处理器,该处理器实现了随机数、相对时间等常用的逻辑。为了避免和正常数据冲突,我定义了一组前缀/后缀正则$R{<控制语法>}R$来激活我们的替换:

    随机数生成器,根据字段的类型自动填充数据.
    内容格式:类型标识符+是否动态(-或+)+长度+是否必输(*),由于XML的<要转义,故用-代替
    类型标识符:S=字符串 I=整型 L=长整 T=时间 B=二进制 Y=是否 E=ENUM D=浮点
    
    整型的长度是限制输入位数,而时间的长度是限制随机距离当前时间的天数范围.
    
    一个必输的从1个字符到16个字符的随机长度随机字符填充的表达式:S-16*
    一个可以为空的从1位到5位的整数:I-5
    一个不允许为空的11位长整数:L11*
    一个不允许为空的距离当前时间前后30天内的时间:T30*
    一个不允许为空的距离当前时间后60天的时间:T+60*
    一个距离当前3天前时间:T-3*(注意前后的时间均只到日期的00:00:00),不包含时分秒
    一个距离当前4天后时间:T+4*
    一个现在的时间:T0*
    一个不允许为空的10随机字符的二进制:B10*
    1. package org.ccframe.commons.dbunit;
    2. import org.apache.commons.lang3.RandomUtils;
    3. import org.ccframe.commons.util.DbUnitUtils.DBTYPE;
    4. import org.ccframe.commons.util.UtilDateTime;
    5. import org.dbunit.database.IDatabaseConnection;
    6. import org.dbunit.dataset.ITable;
    7. import java.util.Date;
    8. import java.util.Random;
    9. import java.util.regex.Matcher;
    10. import java.util.regex.Pattern;
    11. /**
    12. * 随机数生成器,根据字段的类型自动填充数据.
    13. *
    14. * 内容格式:类型标识符+是否动态(-或+)+长度+是否必输(*),由于XML的<要转义,故用-代替
    15. * 类型标识符:S=字符串 I=整型 L=长整 T=时间 B=二进制 Y=是否 E=ENUM D=浮点
    16. *
    17. * 整型的长度是限制输入位数,而时间的长度是限制随机距离当前时间的天数范围.
    18. *
    19. * 一个必输的从1个字符到16个字符的随机长度随机字符填充的表达式:S-16*
    20. * 一个可以为空的从1位到5位的整数:I-5
    21. * 一个不允许为空的11位长整数:L11*
    22. * 一个不允许为空的距离当前时间前后30天内的时间:T30*
    23. * 一个不允许为空的距离当前时间后60天的时间:T+60*
    24. * 一个距离当前3天前时间:T-3*(注意前后的时间均只到日期的00:00:00),不包含时分秒
    25. * 一个距离当前4天后时间:T+4*
    26. * 一个现在的时间:T0*
    27. * 一个不允许为空的10随机字符的二进制:B10*
    28. *
    29. * @author JIM
    30. *
    31. */
    32. public class RandomReplacementProcessor implements ReplacementProcessor {
    33. private static final String FILE_START_DELIMITER = "$R{";
    34. private static final String FILE_END_DELIMITER = "}R$";
    35. // private Logger logger = Logger.getLogger(RandomReplacementProcessor.class);
    36. private static Pattern extractPattern = Pattern.compile("([SILTBY])([-+]{1})?(\\d+)?(\\*)?");
    37. @Override
    38. public Object replacementSubStrToObject(ITable table, String column, String substring, IDatabaseConnection connection, DBTYPE dbType){
    39. Matcher matcher = extractPattern.matcher(substring);
    40. if(!matcher.find()) {
    41. return null;
    42. }
    43. String mode = matcher.group(1);
    44. Boolean growFlag = (matcher.group(2) == null ? null: "+".equals(matcher.group(2)));
    45. Integer maxLength = (matcher.group(3) == null ? null: Integer.parseInt(matcher.group(3)));
    46. boolean notNull = (matcher.group(4) != null);
    47. switch(mode.charAt(0)) {
    48. case 'S': //字符
    49. return randomString(growFlag, maxLength, notNull);
    50. case 'I': //
    51. return randomInteger(growFlag, maxLength, notNull);
    52. case 'L':
    53. return randomLong(growFlag, maxLength, notNull);
    54. case 'T':
    55. return randomTime(growFlag, maxLength, notNull);
    56. case 'B':
    57. return randomByteString(growFlag, maxLength, notNull);
    58. case 'Y':
    59. return randomBooleanString(notNull);
    60. case 'E':
    61. return randomEnumString(notNull);
    62. case 'D':
    63. return randomDouble(maxLength, notNull);
    64. default:
    65. return null;
    66. }
    67. }
    68. private static final boolean randomNull() { //如果可以为空,1/10 概率出现NULL或空串
    69. return RandomUtils.nextInt(0, 10) == 0;
    70. }
    71. private static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    72. private String randomString(Boolean growFlag, Integer maxLength, boolean notNull) {
    73. if(!notNull && randomNull()) {
    74. return "";
    75. }
    76. if(maxLength == null){
    77. maxLength = 16;
    78. }
    79. StringBuilder sb = new StringBuilder();
    80. int length = maxLength - 1;
    81. if (growFlag != null) {
    82. length = RandomUtils.nextInt(0, maxLength);
    83. }
    84. for (int i = 0; i <= length; i++) {
    85. sb.append(ALLCHAR.charAt(RandomUtils.nextInt(0, ALLCHAR.length())));
    86. }
    87. return sb.toString();
    88. }
    89. private Integer randomInteger(Boolean growFlag, Integer maxLength, boolean notNull) {
    90. return 0; //BoolCodeEnum.fromValue(RandomUtils.nextBoolean());
    91. }
    92. private Integer randomLong(Boolean growFlag, Integer maxLength, boolean notNull) {
    93. return 0;
    94. }
    95. private static final Random LONG_RANDOM = new Random();
    96. private static final Date NOW_TIME = new Date(); //系统初始化的时间
    97. private Date randomTime(Boolean growFlag, Integer maxLength, boolean notNull) {
    98. if(!notNull && randomNull()) {
    99. return null;
    100. }
    101. if(maxLength == null || maxLength == 0) {
    102. return NOW_TIME;
    103. }
    104. if(growFlag == null) { //随机范围时间
    105. long grow = LONG_RANDOM.nextLong() % (maxLength*2*24L*3600L*1000L); //随机天数范围
    106. return new Date(NOW_TIME.getTime() - (maxLength*24L*3600L*1000L) + grow);
    107. }else { //固定时间差
    108. return UtilDateTime.addDays(UtilDateTime.getDayStartTime(NOW_TIME), growFlag ? maxLength: -maxLength);
    109. }
    110. }
    111. private byte[] randomByteString(Boolean growFlag, Integer maxLength, boolean notNull) {
    112. String value = randomString(growFlag, maxLength, notNull);
    113. return value == null ? null : value.getBytes();
    114. }
    115. private String randomBooleanString(boolean notNull) {
    116. return null;
    117. }
    118. private String randomEnumString(boolean notNull) {
    119. return null;
    120. }
    121. private Double randomDouble(Integer maxLength, boolean notNull) { //double默认就是grow取范围
    122. return null;
    123. }
    124. @Override
    125. public String getStartDelim() {
    126. return FILE_START_DELIMITER;
    127. }
    128. @Override
    129. public String getEndDelim() {
    130. return FILE_END_DELIMITER;
    131. }
    132. }

    最终效果

    这样,我们要实现一份相对于当前30天过期的租户就非常简单了,只需要给一个随机时间定义,让它范围在60天之内随机即可,这里的数据创建时间和更新时间都是当前的时间:

    1. "1.0" encoding="UTF-8"?>
    2. <dataset>
    3. <SYS_TENANT TENANT_ID="50001" TENANT_COMPANY="阿里巴巴集团" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="阿里巴巴集团" TENANT_SUBDOMAIN="demo50001" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业阿里巴巴集团" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    4. <SYS_TENANT TENANT_ID="50002" TENANT_COMPANY="腾讯科技有限公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="腾讯科技" TENANT_SUBDOMAIN="demo50002" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业腾讯科技有限公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    5. <SYS_TENANT TENANT_ID="50003" TENANT_COMPANY="百度公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="百度" TENANT_SUBDOMAIN="demo50003" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业百度公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    6. <SYS_TENANT TENANT_ID="50004" TENANT_COMPANY="华为技术有限公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="华为技术" TENANT_SUBDOMAIN="demo50004" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业华为技术有限公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    7. <SYS_TENANT TENANT_ID="50005" TENANT_COMPANY="字节跳动公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="字节跳动" TENANT_SUBDOMAIN="demo50005" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业字节跳动公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    8. <SYS_TENANT TENANT_ID="50006" TENANT_COMPANY="京东集团" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="京东集团" TENANT_SUBDOMAIN="demo50006" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业京东集团" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    9. <SYS_TENANT TENANT_ID="50007" TENANT_COMPANY="美团点评" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="美团点评" TENANT_SUBDOMAIN="demo50007" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业美团点评" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    10. <SYS_TENANT TENANT_ID="50008" TENANT_COMPANY="滴滴出行" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="滴滴出行" TENANT_SUBDOMAIN="demo50008" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业滴滴出行" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    11. <SYS_TENANT TENANT_ID="50009" TENANT_COMPANY="网易集团" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="网易集团" TENANT_SUBDOMAIN="demo50009" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业网易集团" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    12. <SYS_TENANT TENANT_ID="50010" TENANT_COMPANY="新浪公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="新浪" TENANT_SUBDOMAIN="demo50010" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业新浪公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    13. <SYS_TENANT TENANT_ID="50011" TENANT_COMPANY="中国石油天然气集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国石油天然气集团" TENANT_SUBDOMAIN="demo50011" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国石油天然气集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    14. <SYS_TENANT TENANT_ID="50012" TENANT_COMPANY="中国石化集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国石化集团" TENANT_SUBDOMAIN="demo50012" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国石化集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    15. <SYS_TENANT TENANT_ID="50013" TENANT_COMPANY="中国建筑股份有限公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国建筑" TENANT_SUBDOMAIN="demo50013" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国建筑股份有限公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    16. <SYS_TENANT TENANT_ID="50014" TENANT_COMPANY="中国工商银行" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国工商银行" TENANT_SUBDOMAIN="demo50014" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国工商银行" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    17. <SYS_TENANT TENANT_ID="50015" TENANT_COMPANY="中国农业银行" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国农业银行" TENANT_SUBDOMAIN="demo50015" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国农业银行" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    18. <SYS_TENANT TENANT_ID="50016" TENANT_COMPANY="中国银行" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国银行" TENANT_SUBDOMAIN="demo50016" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国银行" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    19. <SYS_TENANT TENANT_ID="50017" TENANT_COMPANY="中国建设银行" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国建设银行" TENANT_SUBDOMAIN="demo50017" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国建设银行" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    20. <SYS_TENANT TENANT_ID="50018" TENANT_COMPANY="中国交通银行" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国交通银行" TENANT_SUBDOMAIN="demo50018" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国交通银行" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    21. <SYS_TENANT TENANT_ID="50019" TENANT_COMPANY="中国人寿保险股份有限公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国人寿保险" TENANT_SUBDOMAIN="demo50019" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国人寿保险股份有限公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    22. <SYS_TENANT TENANT_ID="50020" TENANT_COMPANY="中国平安保险" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国平安保险" TENANT_SUBDOMAIN="demo50020" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国平安保险" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    23. <SYS_TENANT TENANT_ID="50021" TENANT_COMPANY="中国太平洋保险" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国太平洋保险" TENANT_SUBDOMAIN="demo50021" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国太平洋保险" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    24. <SYS_TENANT TENANT_ID="50022" TENANT_COMPANY="中国人民保险集团" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国人民保险集团" TENANT_SUBDOMAIN="demo50022" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国人民保险集团" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    25. <SYS_TENANT TENANT_ID="50023" TENANT_COMPANY="中国人民财产保险" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国人民财产保险" TENANT_SUBDOMAIN="demo50023" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国人民财产保险" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    26. <SYS_TENANT TENANT_ID="50024" TENANT_COMPANY="中国信达资产管理股份有限公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国信达资产管理" TENANT_SUBDOMAIN="demo50024" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国信达资产管理股份有限公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    27. <SYS_TENANT TENANT_ID="50025" TENANT_COMPANY="中国光大银行" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国光大银行" TENANT_SUBDOMAIN="demo50025" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国光大银行" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    28. <SYS_TENANT TENANT_ID="50026" TENANT_COMPANY="中国光大集团" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国光大集团" TENANT_SUBDOMAIN="demo50026" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国光大集团" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    29. <SYS_TENANT TENANT_ID="50027" TENANT_COMPANY="中国进出口银行" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国进出口银行" TENANT_SUBDOMAIN="demo50027" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国进出口银行" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    30. <SYS_TENANT TENANT_ID="50028" TENANT_COMPANY="中国国家开发银行" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国国家开发银行" TENANT_SUBDOMAIN="demo50028" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国国家开发银行" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    31. <SYS_TENANT TENANT_ID="50029" TENANT_COMPANY="中国农业发展银行" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国农业发展银行" TENANT_SUBDOMAIN="demo50029" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国农业发展银行" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    32. <SYS_TENANT TENANT_ID="50030" TENANT_COMPANY="中国银联股份有限公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国银联" TENANT_SUBDOMAIN="demo50030" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国银联股份有限公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    33. <SYS_TENANT TENANT_ID="50031" TENANT_COMPANY="中国邮政储蓄银行" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国邮政储蓄银行" TENANT_SUBDOMAIN="demo50031" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国邮政储蓄银行" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    34. <SYS_TENANT TENANT_ID="50032" TENANT_COMPANY="中国电信股份有限公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国电信" TENANT_SUBDOMAIN="demo50032" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国电信股份有限公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    35. <SYS_TENANT TENANT_ID="50033" TENANT_COMPANY="中国移动通信集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国移动通信集团" TENANT_SUBDOMAIN="demo50033" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国移动通信集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    36. <SYS_TENANT TENANT_ID="50034" TENANT_COMPANY="中国联合网络通信有限公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国联合网络通信" TENANT_SUBDOMAIN="demo50034" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国联合网络通信有限公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    37. <SYS_TENANT TENANT_ID="50035" TENANT_COMPANY="中国广播电影电视集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国广播电影电视集团" TENANT_SUBDOMAIN="demo50035" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国广播电影电视集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    38. <SYS_TENANT TENANT_ID="50036" TENANT_COMPANY="中国中央电视台" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国中央电视台" TENANT_SUBDOMAIN="demo50036" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国中央电视台" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    39. <SYS_TENANT TENANT_ID="50037" TENANT_COMPANY="中国移动" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国移动" TENANT_SUBDOMAIN="demo50037" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国移动" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    40. <SYS_TENANT TENANT_ID="50038" TENANT_COMPANY="中国电信" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国电信" TENANT_SUBDOMAIN="demo50038" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国电信" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    41. <SYS_TENANT TENANT_ID="50039" TENANT_COMPANY="中国联通" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国联通" TENANT_SUBDOMAIN="demo50039" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国联通" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    42. <SYS_TENANT TENANT_ID="50040" TENANT_COMPANY="中国航天科技集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国航天科技集团" TENANT_SUBDOMAIN="demo50040" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国航天科技集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    43. <SYS_TENANT TENANT_ID="50041" TENANT_COMPANY="中国航天科工集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国航天科工集团" TENANT_SUBDOMAIN="demo50041" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国航天科工集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    44. <SYS_TENANT TENANT_ID="50042" TENANT_COMPANY="中国航空工业集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国航空工业集团" TENANT_SUBDOMAIN="demo50042" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国航空工业集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    45. <SYS_TENANT TENANT_ID="50043" TENANT_COMPANY="中国船舶重工集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国船舶重工集团" TENANT_SUBDOMAIN="demo50043" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国船舶重工集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    46. <SYS_TENANT TENANT_ID="50044" TENANT_COMPANY="中国中车集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国中车集团" TENANT_SUBDOMAIN="demo50044" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国中车集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    47. <SYS_TENANT TENANT_ID="50045" TENANT_COMPANY="中国南车集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国南车集团" TENANT_SUBDOMAIN="demo50045" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国南车集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    48. <SYS_TENANT TENANT_ID="50046" TENANT_COMPANY="中国北车集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国北车集团" TENANT_SUBDOMAIN="demo50046" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国北车集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    49. <SYS_TENANT TENANT_ID="50047" TENANT_COMPANY="中国中铁股份有限公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国中铁" TENANT_SUBDOMAIN="demo50047" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国中铁股份有限公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    50. <SYS_TENANT TENANT_ID="50048" TENANT_COMPANY="中国铁路工程集团公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国铁路工程集团" TENANT_SUBDOMAIN="demo50048" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国铁路工程集团公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    51. <SYS_TENANT TENANT_ID="50049" TENANT_COMPANY="中国铁路建设股份有限公司" TENANT_EXPIRE_DATE="$R{T60*}R$" TENANT_NAME="中国铁路建设" TENANT_SUBDOMAIN="demo50049" IF_VALID="Y" MANAGER_MOBILE="[null]" MANAGER="[null]" TENANT_COMPANY_ADDRESS="" REMARK="测试企业中国铁路建设股份有限公司" UPDATE_TIME="$R{T0*}R$" CREATE_TIME="$R{T0*}R$" CREATE_USER_ID="1" UPDATE_USER_ID="1"/>
    52. dataset>

  • 相关阅读:
    人工智能基础第三次作业
    es5的实例__proto__(原型链) prototype(原型对象) {constructor:构造函数}
    看不懂懂车大爆炸,你就错过了国产小车的王炸!
    多肽RGD修饰乳清白蛋白/肌白蛋白/豆清白蛋白/蓖麻蛋白/豌豆白蛋白1b ( PA1b)纳米粒(实验原理)
    js-继承
    Day716. 抛出异常是一个合适的选择吗? -Java8后最重要新特性
    hel-micro
    Docker安装达梦数据库+Java项目使用达梦
    在线OJ项目(3)------实现接口与网页前端进行交互
    北京外国语大学2023年上半年公派英语高级培训班开始招生
  • 原文地址:https://blog.csdn.net/applebomb/article/details/137926397