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

痛点

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

DBUnit

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

java 复制代码
	<!-- 省份树节点 -->
	<SYS_TREE_NODE TREE_NODE_ID="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"/>
	<SYS_TREE_NODE TREE_NODE_ID="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"/>
	<SYS_TREE_NODE TREE_NODE_ID="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来实现加载过程中的数据过滤/替换,因为操作的执行接口:

java 复制代码
    public abstract void execute(IDatabaseConnection connection,
            IDataSet dataSet) throws DatabaseUnitException, SQLException;

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

替换处理器

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

java 复制代码
public interface ReplacementProcessor {
	Object replacementSubStrToObject(ITable table, String column,  String string, IDatabaseConnection connection, DBTYPE dbType);
	String getStartDelim();
	String getEndDelim();
}

我们实现的ReplacementDataSet如下

java 复制代码
package org.ccframe.commons.dbunit;

import org.ccframe.commons.util.DbUnitUtils.DBTYPE;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

public class ReplacementDataSet extends AbstractDataSet {

    /**
     * Logger for this class
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(ReplacementDataSet.class);

    private final IDataSet dataSet;
    private IDatabaseConnection connection;
    private DBTYPE dbType;
    private List<ReplacementProcessor> processorList = new ArrayList<ReplacementProcessor>();

    private static int tableId = 0;

    public ReplacementDataSet(IDataSet dataSet, IDatabaseConnection connection, DBTYPE dbType, List<ReplacementProcessor> processorList)
    {
        this.dataSet = dataSet;
        this.connection = connection;
        this.dbType = dbType;
        this.processorList.addAll(processorList);
    }

    private ReplacementTable createReplacementTable(ITable table)
    {
        tableId ++;
        return  new ReplacementTable(table, tableId, connection, dbType, processorList);
    }

    
    // AbstractDataSet class

    protected ITableIterator createIterator(boolean reversed)
            throws DataSetException
    {
        return new ReplacementIterator(reversed ?
                dataSet.reverseIterator() : dataSet.iterator());
    }

    
    // IDataSet interface

    public String[] getTableNames() throws DataSetException
    {
        LOGGER.debug("getTableNames() - start");

        return dataSet.getTableNames();
    }

    public ITableMetaData getTableMetaData(String tableName)
            throws DataSetException
    {
        return dataSet.getTableMetaData(tableName);
    }

    public ITable getTable(String tableName) throws DataSetException
    {
        return createReplacementTable(dataSet.getTable(tableName));
    }

    
    // ReplacementIterator class

    private class ReplacementIterator implements ITableIterator
    {

//        private final Logger logger = LoggerFactory.getLogger(ReplacementIterator.class);

        private final ITableIterator iterator;

        public ReplacementIterator(ITableIterator iterator)
        {
            this.iterator = iterator;
        }

        
        // ITableIterator interface

        public boolean next() throws DataSetException
        {
            return iterator.next();
        }

        public ITableMetaData getTableMetaData() throws DataSetException
        {
            return iterator.getTableMetaData();
        }

        public ITable getTable() throws DataSetException
        {
            return createReplacementTable(iterator.getTable());
        }
    }
}

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

java 复制代码
package org.ccframe.commons.dbunit;

import com.google.common.base.CaseFormat;
import org.ccframe.commons.helper.SpringContextHelper;
import org.ccframe.commons.util.DbUnitUtils.DBTYPE;
import org.ccframe.config.Global;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.ITableMetaData;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class ReplacementTable implements ITable{

    /**
     * Logger for this class
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(ReplacementTable.class);
    
    private static final int OBJECT_CACHE_SIZE = 50; //50个field

    private final ITable table;
    private IDatabaseConnection connection;
    private DBTYPE dbType;
    private List<ReplacementProcessor> processorList;
    /**
     * 每切换一个不同的表代表一个新的tableId,用于结合行号标记唯一的XML表位置。例如XML1和XML2的SYS_USER表的tableId就会不一样.
     */
    private int tableId;
    private String pkId;
    private RAtomicLong atomicLong;

	private RAtomicLong getAtomicLong() {
		if(atomicLong == null) {
			atomicLong = SpringContextHelper.getBean(RedissonClient.class).getAtomicLong(Global.REDIS_PERFIX + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, this.pkId));
			if(atomicLong.get() == 0) {
				atomicLong.getAndIncrement();
			}
		}
		return atomicLong;
	}

	private Map<String, Object> getRowObjectCache = new LinkedHashMap<String, Object>(5){
		private static final long serialVersionUID = 1L;
		@Override
		protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
		    // 当前记录数大于设置的最大的记录数,删除最旧记录(即最近访问最少的记录)
		    return size() > OBJECT_CACHE_SIZE;
		}
	};

	private Map<String, Object> getRowIdCache = new LinkedHashMap<String, Object>(5){
		private static final long serialVersionUID = 1L;
		@Override
		protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
		    // 当前记录数大于设置的最大的记录数,删除最旧记录(即最近访问最少的记录)
		    return size() > OBJECT_CACHE_SIZE;
		}
	};

    public ReplacementTable(ITable table, int tableId, IDatabaseConnection connection, DBTYPE dbType, List<ReplacementProcessor> processorList)
    {
        this.table = table;
        this.connection = connection;
        this.dbType = dbType;
        this.processorList = processorList;
        this.tableId = tableId;
        this.pkId = table.getTableMetaData().getTableName();
        this.pkId = this.pkId.substring(this.pkId.indexOf("_") + 1) + "_ID";
    }

    
    // ITable interface

    public ITableMetaData getTableMetaData()
    {
        return table.getTableMetaData();
    }

    public int getRowCount()
    {
        return table.getRowCount();
    }

    public Object getValue(int row, String column) throws DataSetException{
		//注意会进来2次,第一次是ignore mapping,第二次才是取值。因此要cache住行号列号位置,避免某些替换的动作重复了2次
    	String value = (String)table.getValue(row, column);

    	if(pkId.equals(column)) { //主键
        	if(value == null) {
        		Object objectCached = getRowIdCache.get(tableId + "-" + row);
        		if(objectCached == null) {
        			objectCached = new Long(getAtomicLong().getAndIncrement()).intValue();
        			getRowIdCache.put(tableId + "-" + row, objectCached);
        		}
        		return objectCached;
        	} else { //同步ID
        		long next = getAtomicLong().get();
        		int dbId = Integer.parseInt(value); //id必须是Integer
        		if(next <= dbId) {
        			getAtomicLong().set(dbId + 1);
        		}
        		return dbId;
        	}
    	}else { //非主键
            if (value == null){
            	return null;
            }
            for(ReplacementProcessor processor: processorList){
                if (processor.getStartDelim() != null && processor.getEndDelim() != null && value.toString().startsWith(processor.getStartDelim()) && value.toString().endsWith( processor.getEndDelim())){
                	Object objectCached = getRowObjectCache.get(tableId + "-" + row + "-" + column);
                	if(objectCached == null) {
                		objectCached = processor.replacementSubStrToObject(table, column, value.substring(processor.getStartDelim().length(), value.length() - processor.getEndDelim().length()), connection, dbType); 
                    	getRowObjectCache.put(tableId + "-" + row + "-" + column, objectCached);
                	}
    				return objectCached;
                }
            }
            return value;
    	}
    }

    public String toString()
    {
    	StringBuffer sb = new StringBuffer();
    	sb.append(getClass().getName()).append("[");
    	sb.append(", table=").append(table);
    	sb.append("]");
    	return sb.toString();
    }
}

语法

接下来我们实现一个替换处理器,该处理器实现了随机数、相对时间等常用的逻辑。为了避免和正常数据冲突,我定义了一组前缀/后缀正则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*
java 复制代码
package org.ccframe.commons.dbunit;

import org.apache.commons.lang3.RandomUtils;
import org.ccframe.commons.util.DbUnitUtils.DBTYPE;
import org.ccframe.commons.util.UtilDateTime;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.ITable;

import java.util.Date;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 随机数生成器,根据字段的类型自动填充数据.
 *
 * 内容格式:类型标识符+是否动态(-或+)+长度+是否必输(*),由于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*
 * 
 * @author JIM
 *
 */
public class RandomReplacementProcessor implements ReplacementProcessor {

	private static final String FILE_START_DELIMITER = "$R{";
	private static final String FILE_END_DELIMITER = "}R$";

//	private Logger logger = Logger.getLogger(RandomReplacementProcessor.class);
	
	private static Pattern extractPattern = Pattern.compile("([SILTBY])([-+]{1})?(\\d+)?(\\*)?"); 
	
	@Override
	public Object replacementSubStrToObject(ITable table, String column, String substring, IDatabaseConnection connection, DBTYPE dbType){
		Matcher matcher = extractPattern.matcher(substring);
		if(!matcher.find()) {
			return null;
		}
		String mode = matcher.group(1);
		Boolean growFlag = (matcher.group(2) == null ? null: "+".equals(matcher.group(2)));
		Integer maxLength = (matcher.group(3) == null ? null: Integer.parseInt(matcher.group(3)));
		boolean notNull = (matcher.group(4) != null);
		
		switch(mode.charAt(0)) {
			case 'S': //字符
				return randomString(growFlag, maxLength, notNull);
			case 'I': //
				return randomInteger(growFlag, maxLength, notNull);
			case 'L':
				return randomLong(growFlag, maxLength, notNull);
			case 'T':
				return randomTime(growFlag, maxLength, notNull);
			case 'B':
				return randomByteString(growFlag, maxLength, notNull);
			case 'Y':
				return randomBooleanString(notNull);
			case 'E':
				return randomEnumString(notNull);
			case 'D':
				return randomDouble(maxLength, notNull);
			default:
				return null;
		}
	}

	private static final boolean randomNull() { //如果可以为空,1/10 概率出现NULL或空串
		return RandomUtils.nextInt(0, 10) == 0;
	}
	
	private static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

	private String randomString(Boolean growFlag, Integer maxLength, boolean notNull) {
		
		if(!notNull && randomNull()) {
			return "";
		}

		if(maxLength == null){
			maxLength = 16;
		}

		StringBuilder sb = new StringBuilder();

		int length = maxLength - 1;
		if (growFlag != null) {
			length = RandomUtils.nextInt(0, maxLength);
		}
		for (int i = 0; i <= length; i++) {
			sb.append(ALLCHAR.charAt(RandomUtils.nextInt(0, ALLCHAR.length())));
		}
		return sb.toString();
	}

	private Integer randomInteger(Boolean growFlag, Integer maxLength, boolean notNull) {
		return 0; //BoolCodeEnum.fromValue(RandomUtils.nextBoolean());
	}

	private Integer randomLong(Boolean growFlag, Integer maxLength, boolean notNull) {
		return 0;
	}

	private static final Random LONG_RANDOM = new Random();
	private static final Date NOW_TIME = new Date(); //系统初始化的时间
	private Date randomTime(Boolean growFlag, Integer maxLength, boolean notNull) {
		if(!notNull && randomNull()) {
			return null;
		}
		if(maxLength == null || maxLength == 0) {
			return NOW_TIME;
		}
		if(growFlag == null) { //随机范围时间
			long grow = LONG_RANDOM.nextLong() % (maxLength*2*24L*3600L*1000L); //随机天数范围
			return new Date(NOW_TIME.getTime() - (maxLength*24L*3600L*1000L) + grow);
		}else { //固定时间差
			return UtilDateTime.addDays(UtilDateTime.getDayStartTime(NOW_TIME), growFlag ? maxLength: -maxLength);
		}
	}

	private byte[] randomByteString(Boolean growFlag, Integer maxLength, boolean notNull) {
		String value = randomString(growFlag, maxLength, notNull);
		return value == null ? null : value.getBytes();
	}

	private String randomBooleanString(boolean notNull) {
		return null;
	}

	private String randomEnumString(boolean notNull) {
		return null;
	}

	private Double randomDouble(Integer maxLength, boolean notNull) { //double默认就是grow取范围
		return null;
	}

	@Override
	public String getStartDelim() {
		return FILE_START_DELIMITER;
	}

	@Override
	public String getEndDelim() {
		return FILE_END_DELIMITER;
	}
}

最终效果

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

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!-- DBUnit flatXml DataFile -->
<!-- 测试数据文件,注意.此文件的指定ID必须>70000 -->
<dataset>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
    <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"/>
</dataset>
相关推荐
前行的小黑炭18 分钟前
设计模式:为什么使用模板设计模式(不相同的步骤进行抽取,使用不同的子类实现)减少重复代码,让代码更好维护。
android·java·kotlin
Java技术小馆23 分钟前
如何设计一个本地缓存
java·面试·架构
XuanXu1 小时前
Java AQS原理以及应用
java
风象南4 小时前
SpringBoot中6种自定义starter开发方法
java·spring boot·后端
mghio13 小时前
Dubbo 中的集群容错
java·微服务·dubbo
JavaGuide16 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
咖啡教室18 小时前
java日常开发笔记和开发问题记录
java
咖啡教室18 小时前
java练习项目记录笔记
java
鱼樱前端18 小时前
maven的基础安装和使用--mac/window版本
java·后端