背景:系统在进行数据导入的时候要进行唯一性校验

一、导入跟踪

1.查看jeecgBoot文档发现其导入功能使用autoPoi,查看官方文档发现autoPoi暂不支持添加数据校验规则

jeecgBoot文档地址 : http://doc.jeecg.com/2044224
autoPoi文档地址 : http://doc.autopoi.jeecg.com/1623974

jeecgboot 本地微服务开发 jeecgboot官方文档_jeecgboot 本地微服务开发

2.跟踪其导入找到后端接口

前端请求接口

jeecgboot 本地微服务开发 jeecgboot官方文档_jeecgboot 本地微服务开发_02

找到后端接口,类为 org.jeecg.modules.online.cgform.b.a

jeecgboot 本地微服务开发 jeecgboot官方文档_List_03

跟踪发现其调用的方法为

jeecgboot 本地微服务开发 jeecgboot官方文档_spring_04

jeecgboot 本地微服务开发 jeecgboot官方文档_spring_05

jeecgboot 本地微服务开发 jeecgboot官方文档_List_06

可以看到IOnlCgformSqlService是一个接口,路径为 org.jeecg.modules.online.cgform.service.IOnlCgformSqlService

jeecgboot 本地微服务开发 jeecgboot官方文档_jeecgboot 本地微服务开发_07

找到其实现类为g类,路径为 org.jeecg.modules.online.cgform.service.impl.g

jeecgboot 本地微服务开发 jeecgboot官方文档_java_08

根据导入的数据量执行不同的操作,最终都调用了this.a方法

jeecgboot 本地微服务开发 jeecgboot官方文档_java_09

查看a方法发现其调用org.jeecg.modules.online.cgform.util.b.a方法后返回一个可执行的的sql变量var8,然后再执行sql语句

jeecgboot 本地微服务开发 jeecgboot官方文档_jeecgboot 本地微服务开发_10

注意这个类中还有一个saveOrUpdateSubData方法也调用了this.a,所有我们要想在导入时添加校验必须修改saveBatchOnlineTable方法的逻辑。

jeecgboot 本地微服务开发 jeecgboot官方文档_List_11

二、添加校验规则

思路:根据我们前面的代码跟踪发现org.jeecg.modules.online.cgform.b.a类的onlCgformSqlService属性是通过@Autowired注入,那我们重新实现onlCgformSqlService接口,并将我们自己的类添加到spring容器中。

jeecgboot 本地微服务开发 jeecgboot官方文档_jeecgboot 本地微服务开发_12

jeecgboot 本地微服务开发 jeecgboot官方文档_List_13

1.新建接口类IBaseCheckService

@FunctionalInterface
public interface IBaseCheckService<T> {


    /**
     *  校验数据
     * @param t
     */
    void checkData(T t);

}

2.在我们要导入的表的Impl类中实现IBaseCheckService接口,重写其checkData方法。
比如数据库表名为sys_user,对应的Impl类为SysUserServiceImpl

implements IBaseCheckService<String>
@Override
	public void checkData(String studentStr) {
		// 这里写我们的校验逻辑
		// 校验不通过抛出异常 throw new RuntimeException("数据校验不通过");
	}

3.新建BaseCheck类

public class BaseCheck<T> {

    private IBaseCheckService baseCheckService;

    public void setBaseCheckService(IBaseCheckService baseCheckService) {
        this.baseCheckService = baseCheckService;
    }

    public BaseCheck() {
    }

    public void checkData(T t){
        baseCheckService.checkData(t);
    }
}

4.新建工具类,主要作用是将下划线转成驼峰

/**
 * 下划线转驼峰
 * @Author 
 * @Date 
 * @Description
 **/
public class HumpUtil {

    /**
     * 下划线对应的ASCII
     */
    private static final byte ASCII_UNDER_LINE = 95;

    /**
     * 小写字母a的ASCII
     */
    private static final byte ASCII_a = 97;

    /**
     * 大写字母A的ASCII
     */
    private static final byte ASCII_A = 65;

    /**
     * 小写字母z的ASCII
     */
    private static final byte ASCII_z = 122;

    /**
     * 字母a和A的ASCII差距(a-A的值)
     */
    private static final byte ASCII_a_A = ASCII_a - ASCII_A;

    public static String toHump(String column) {
        byte[] bytes = changeIdx(column);
        bytes = removeUnderLine(bytes);
        return new String(bytes);
    }

    /**
     * 交换下划线和其后面字符的下标
     * 将column从下划线命名方式转换成驼峰命名方式
     * 0. 找到‘_’符号的ASCII码(95)对应的下标
     * 1. 将下划线的下标的下一个元素转换为大写字段(如果是小写字母的话)并放到下划线对应的下标
     * 2. 将下划线下标的下一个元素设置为下划线
     * 3. 返回数组
     *
     * @param column 字段名称
     */
    private static byte[] changeIdx(String column) {
        byte[] bytes = column.getBytes();
        for (int i = 0; i < bytes.length; i++) {
            if (bytes[i] == ASCII_UNDER_LINE) {
                if (i < bytes.length - 1) {
                    bytes[i] = toUpper(bytes[i + 1]);
                    bytes[i + 1] = ASCII_UNDER_LINE;
                    i++;
                }
            }
        }
        return bytes;
    }

    /**
     * 将参数b转换为大写字母,小写字母ASCII范围(97~122)
     * 0. 判断参数是否为小写字母
     * 1. 将小写字母转换为大写字母(减去32)
     */
    private static byte toUpper(byte b) {
        if (b >= ASCII_a && b <= ASCII_z) {
            return (byte) (b - ASCII_a_A);
        }
        return b;
    }

    /**
     * 去除所有下划线
     * 0. 新创建一个数组
     * 1. 将所有非下划线字符都放入新数组中
     *
     * @param bytes 原始数组
     * @return 处理后的字节数组
     */
    private static byte[] removeUnderLine(byte[] bytes) {
        // 存放非下划线字符的数量
        int count = 0;
        for (byte b : bytes) {
            if (b == ASCII_UNDER_LINE) {
                continue;
            }
            count++;
        }
        byte[] nBytes = new byte[count];
        count = 0;
        for (byte b : bytes) {
            if (b == ASCII_UNDER_LINE) {
                continue;
            }
            nBytes[count] = b;
            count++;
        }
        return nBytes;
    }

}

5.新建MyIOnlCgformSqlServiceImpl类实现IOnlCgformSqlService接口,复制org.jeecg.modules.online.cgform.service.impl.g类的代码,在saveBatchOnlineTable中调用this.a方法前调用数据校验的方法

jeecgboot 本地微服务开发 jeecgboot官方文档_spring_14

代码如下

/**
 *  org.jeecg.modules.online.cgform.service.impl.g
 * @Author 
 * @Date 
 * @Description
 **/
public class MyIOnlCgformSqlServiceImpl implements IOnlCgformSqlService {

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;
    @Autowired
    private IOnlCgformHeadService onlCgformHeadService;

    public MyIOnlCgformSqlServiceImpl() {
    }

    @Override
    public void saveBatchOnlineTable(OnlCgformHead head, List<OnlCgformField> fieldList, List<Map<String, Object>> dataList) throws BusinessException {
        SqlSession var4 = null;

        try {
            b.a(2, dataList, fieldList);
            var4 = this.sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
            OnlCgformFieldMapper var5 = (OnlCgformFieldMapper)var4.getMapper(OnlCgformFieldMapper.class);
            short var6 = 1000;
            int var7;
            String var8;
            if (var6 >= dataList.size()) {
                for(var7 = 0; var7 < dataList.size(); ++var7) {
                    var8 = JSON.toJSONString(dataList.get(var7));
                    this.checkData(var8, head);
                    this.a(var8, head, fieldList, var5);
                }
            } else {
                for(var7 = 0; var7 < dataList.size(); ++var7) {
                    var8 = JSON.toJSONString(dataList.get(var7));
                    this.checkData(var8, head);
                    this.a(var8, head, fieldList, var5);
                    if (var7 % var6 == 0) {
                        var4.commit();
                        var4.clearCache();
                    }
                }
            }

            var4.commit();
        } catch (Exception var12) {
            throw new BusinessException(var12.getMessage());
        } finally {
            var4.close();
        }

    }

    @Override
    public void saveOrUpdateSubData(String subDataJsonStr, OnlCgformHead head, List<OnlCgformField> subFiledList) throws BusinessException {
        OnlCgformFieldMapper var4 = (OnlCgformFieldMapper) SpringContextUtils.getBean(OnlCgformFieldMapper.class);
        this.a(subDataJsonStr, head, subFiledList, var4);
    }

    private void a(String var1, OnlCgformHead var2, List<OnlCgformField> var3, OnlCgformFieldMapper var4) throws BusinessException {
        JSONObject var5 = JSONObject.parseObject(var1);
        int var6 = this.onlCgformHeadService.executeEnhanceJava("import", "start", var2, var5);
        String var7 = var2.getTableName();
        Map var8;
        if (1 == var6) {
            var8 = org.jeecg.modules.online.cgform.util.b.a(var7, var3, var5);
            var4.executeInsertSQL(var8);
        } else if (2 == var6) {
            var8 = org.jeecg.modules.online.cgform.util.b.b(var7, var3, var5);
            var4.executeUpdatetSQL(var8);
        } else if (0 == var6) {
        }

    }

    /**
     * @param var1 导入的行数据
     * @param var2 表信息
     */
    private void checkData(String var1, OnlCgformHead var2) {
        // 将表明转成驼峰
        String service = HumpUtil.toHump(var2.getTableName());
        BaseCheck<String> studentBaseCheck = new BaseCheck<>();
        // 获取spring容器中对应的bean并设值
        studentBaseCheck.setBaseCheckService(SpringContextUtils.getBean(service + "ServiceImpl", IBaseCheckService.class));
        // 调用校验方法
        studentBaseCheck.checkData(var1);
    }
    
}

6.新建配置类,将spring容器中名为onlCgformSqlServiceImpl的bean移除,并将我们自己实现类的bean添加到spring容器中。beanName对应实现IOnlCgformSqlService接口的g类。

jeecgboot 本地微服务开发 jeecgboot官方文档_java_15

/**
 *  前端页面导入按钮
 * @Author 
 * @Date 
 * @Description
 **/
@Configuration
public class ModifyImportConfig {

    @Bean
    public void modifyFormSqlServiceImplBean(){
        String beanName="onlCgformSqlServiceImpl";
        //获取context.
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) SpringContextUtils.getApplicationContext();
        //获取BeanFactory
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
        Object oldBean = SpringContextUtils.getBean(beanName);
        if (!ObjectUtils.isEmpty(oldBean)){
            // 移除
            defaultListableBeanFactory.removeBeanDefinition(beanName);
        }
        // 重新注册
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MyIOnlCgformSqlServiceImpl.class);
        defaultListableBeanFactory.registerBeanDefinition(beanName,beanDefinitionBuilder.getBeanDefinition());
    }

}

7.重启项目

注意:表对应的impl实现类一定要实现我们自定的校验接口,并重写checkData方法,否则在执行MyIOnlCgformSqlServiceImpl的checkData方法会报错

jeecgboot 本地微服务开发 jeecgboot官方文档_下划线_16