月度归档: 2022年10月

ON DUPLICATE KEY UPDATE踩入一个小坑

评审了新迭代的需求,翻以前功能的代码,看到了一个ON DUPLICATE KEY UPDATE的语句,有点不对劲。

实际数据库设计业务相关内容,这里不写,单独抽象出来一个场景如下

有一张表table_c,主键id无关紧要,其中有一个唯一键索引req_id,这个索引保证了表中每行的req_id都是唯一的

create table table_c
(
    id          int auto_increment
        primary key,
    req_id      int           null,
    req_type    int           null,
    req_version int default 0 null,
    constraint table_c_req_id_uindex
        unique (req_id)
);

需求场景是这样的,从第三方拉取到数据,对应唯一的req_id,如果表中没有则插入一条新数据。如果表中有,则判断req_version字段,只有当新数据的req_version比原表中的大的时候才更新整行数据。于是根据这个需求场景来原先的代码中的SQL如下

INSERT INTO table_c (req_id, req_type, req_version)
VALUES (124, 4, 1)
ON DUPLICATE KEY UPDATE 
    req_version = IF(VALUES(req_version) > req_version, VALUES(req_version), req_version),
    req_type    = IF(VALUES(req_version) > req_version, VALUES(req_type), req_type)

乍一看起来使用了ON DUPLICATE KEY UPDATE,也使用了IF判断了req_version字段,好像没毛病。但是实际上是有问题的,我们跑一个实际数据看下

表中现有数据

执行上面的SQL,按照预期req_type应该会被更新为4,同时req_version被更新为1,执行一下

可以看到执行成功,而表中现在的结果如下

req_version确实更新了但是req_type却没有更新,那么我们可以推断出req_type后面的IF语句没有获得返回结果true,那么可能的情况就是,在前面先执行

req_version = IF(VALUES(req_version) > req_version, VALUES(req_version), req_version)

这一段的时候req_version的值已经被更新为1了,且VALUES(req_version)的值也是1,那么在后面再执行VALUES(req_version) > req_version判断的时候就会得到false

不妨改变下SQL再试下

在UPDATE语句部分直接填上req_version的值,注意不是VALUES(req_version)的值

表中结果如下

可以确定的看到在执行到req_type这行的时候req_version的值确实变成2了。

根据这样的结论,我们再次调整下SQL语句,执行查看结果

Continue reading

一个概率题,4只鸭子在同一个半圆中的概率

老黄历了,几年前就有人拿出来拉了一波流量的问题,今天看到又被人翻出来,再圈一波流量,群里吵翻天,绕得不行。题目大意如下:

有4只小鸭子,随机分布于一个圆形当中。这就是全部的信息

问:这4只小鸭子同时处在同一个半圆中的概率是多少

本着一名码农的简单务实精神,我们先直接撸一份代码试试看

因为小鸭子距离圆心的距离并不会影响到这只小鸭子能否被划分到某个半圆里,所以这里对模型简化下,把小鸭子的位置简化为从圆心到圆边的投射点,那么这个问题就间接地简化为

在一个圆形的边上随机选定4个点,这4个点恰好在某个180°的弧度范围内的概率

又圆的总共角度为360°,且是一个首尾相接的情况,那么在处理数据的时候,我们采用和处理循环链表、数组相似的处理方法,复制双倍长度的方法。即,假设生成了一个在65°上的点,那么我们同步的添加一个(65+360)°的点的数据,这样可以极大地方便计算,代码如下

public class DuckTest {
    private static final Random RANDOM = new Random();
    private static final int[] CNT = new int[2];

    @Test
    public void main() {
        int loop = 2000000000;
        for (int i = 0; i < loop; i++) {
            randomDuck();
        }
        System.out.println("CNT = " + Arrays.toString(CNT));
        System.out.println((float) CNT[1]/loop);
    }

    private void randomDuck(){
        int[] arr = new int[8];
        for (int i = 0; i < 4; i++) {
            int pos = RANDOM.nextInt(360);
            arr[i] = pos;
            arr[i+4] = pos+360;
        }
        Arrays.sort(arr);
        if (isInSemicircle(arr)){
            CNT[1]++;
        }else{
            CNT[0]++;
        }
    }


    private boolean isInSemicircle(int[] arr) {
        for (int i = 0; i < 4; i++) {
            if (arr[i+3] - arr[i] <= 180){
                return true;
            }
        }
        return false;
    }
}

直接揭晓答案,概率是50%

数据不至于有错,不过我这还是有一点点问题,一直都是50.4%的概率,这个`.4`应该是边界值判定的一点误差问题。

所以这个50%的结果怎么理解呢?

首先一个误区,不是先划定半圆,然后算鸭子在这个半圆中的概率,而是4只鸭子先在圆形湖里,然后判断这4个鸭子能否用一个半圆框住的概率。

有了这样的认知之后我们再来看实际情况,在湖里的4只鸭子中,任选一只鸭子,必然可以划出一个半圆将这只鸭子包含在其中。下一步,尝试判断任意另外一只鸭子是否在这个半圆中,这显然依旧是100%的概率,因为即使这两只鸭子是在同一直径划线的两侧,也就是弧度相差为π,角度为180°的最大的情况下,我们依旧可以将这两只鸭子划入一个半圆中

当湖里有4只鸭子的时候,其中必定有3只鸭子在同一侧的半圆中,这里是指必定能找到3只鸭子在同一侧,而不是任意取3只鸭子会在同一侧

此时概率依旧为100%,那么接下来要求的情况,即是剩余两只鸭子在目前划出的这个直径的划线的同一侧的概率,即

1/2 * 1/2  + 1/2 * 1/2 = 1/2

两只鸭子同时在某一侧的概率和同时在另一侧的概率之和

一次DataIntegrityViolationException的生产异常

处理步骤

第一步,检查报异常的方法,发现是一个SQL查询报出来的异常,大意是从MYSQL查询出来的数据转换成数字类型的,内容如下

org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [SELECT colum_xxxxx FROM table_xxxxxx where STATUS = 0 order by LAST_MODIFY_TIME limit 300]; '815996050122815896400122' in column '1' is outside valid range for the datatype BIGINT.; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLDataException: '815996050122815896400122' in column '1' is outside valid range for the datatype BIGINT.

从上的内容来看,想要把815996050122815896400122的结果转换成BIGINT类型失败,因为超出了BIGINT的范围

这里提一下,MySQL BIGINT 类型的范围在 -9223372036854775808 和 9223372036854775807 之间,与Java的Long类型范围一致,为一个19位数字。

而给定的值为一个22位数字,这显然会失败。

接下来去查看对应表结构,发现对应字段是varchar(32)的字符串类型的。那么显然A同事在做开发这个功能的时候,这个22位长度的数字可以正常转成字符串存进去,但是另外一个B同事在取数据的时候根据自己的业务场景想要将Mysql查出来的结果直接映射为Long类型,这时候就出现异常了。

到这一步,可能觉得那把取值的时候改为String型不就可以了么?于是尝试着评估下改为String型会带来哪些影响。以下是是处理过程,涉及到具体业务场景的考虑。

首先这个字段需要对12取模,然后把对应数据存入不同的库里。其次这个字段需要请求淘宝的官方API查询数据,相关调用部分都要进行修改。然后对应的数据需开放了内部服务接口给其他系统调用,这块相关字段类型也需要修改,以及还有其他的一些零碎的地方。瞬间感觉好坑。近几个月迭代的需求都会要做一些修改调整。

但是,重新看下需求,这个字段的来源其实是淘宝给的分销采购单的退款单号,淘宝API上的给的定义类型就是Long类型的,再看下目前线上生产环境的数据是11位的数字,所以实际上来说,B同事在一开始做这块查询的时候用Long类型取值是没有问题的。而A同事在做写入功能和数据库设计的时候,没有考虑具体的业务场景,只是考虑觉得这个ID应该预留长度空间,给数据库设计了个varchar(32)的字段存储,写入的时候用String型存入,咱们也不能说他做得不对。本质上来说是两个模块的人对需求理解不一致,沟通不充分产生的结果。

那么对这个异常处理的方式也就很明显了,在写入的时候对该字段进行校验,如果不能转换成Long类型的退款单号ID字符串,给业务人员报错,这显然是不正确的退款单号。

照着这个思路,我们有两种实现方法

第一种,直接Long.parseLong(idString)来尝试将字符串转换为Long类型对象,用try catch将Long.parseLong()转换的代码包含起来,尝试捕获异常,如果转换失败,那么给业务提示不正确的单号。

但是根据一个开发准则“不应使用异常来实现业务逻辑(或流程控制)”,以及最小惊讶原则(Principle of Least Astonishment),这提醒着我,不应当这样来处理。

http://wiki.c2.com/?DontUseExceptionsForFlowControl

于是试着用第二种方式,自己来实现判断一个字符串能否转换成Long类型对象,代码如下

    public boolean validateLongNumStr(String longNumStr) {
        //清除先导0
        String nonePreZeroNum = longNumStr.replaceFirst("^0+", "");
        String longMax = Long.toString(Long.MAX_VALUE);
        //长度比longMax长的必定错误
        if ( nonePreZeroNum.length() > longMax.length()){
            return false;
        }
        //长度相同的比较字典序即可
        if (nonePreZeroNum.length() == longMax.length() && longMax.compareTo(nonePreZeroNum) < 0){
            return false;
        }
        return true;
    }
  1. 清除先导0,目标字符串需要提前处理清除掉非数字字符
  2. 生成一个Long类型的最大值,即9223372036854775807的字符串
  3. 如果目标字符串长度大于这个longMax的字符串,则必定会转换失败
  4. 如果长度相等,则比较下两个字符串的字典序,如果目标字符比longMax的字典序大,那么也会转换失败
  5. 剩下的就是长度比longMax短的字符串,可以顺利转换成功

当然,别忘了写下对应单元测试

最近写HIVE SQL的一点笔记【2】

最近写HIVE SQL的一点笔记【1】

多行数据字段连接成一个字段的方法,需配合group by 使用

select concat_ws(',',collect_list(oid)) as oid_list,tid 
from HIVE_TABLE_NAME_XXXX 
group by tid

执行结果效果如下

使用`collect_list`获取相同tid行的oid字段,并使用 `concat_ws` 函数拼接这个集合,中间用此处指定的字符串“,”逗号拼接。

collect_list会忽略集合中null值,如果集合内的所有值均为null,那么此时结果是一个空数组。

如果要对结果去重,可以使用collect_set替换collect_list方法

再有就是,只有group by的字段可以直接在select后面使用,其他字段可以使用MAX函数对字段取值,这里和Mysql的 any_value函数有点相似。

如果我们需要得到结果是一个JSON字符串,同样的可以这样操作

select concat('[',concat_ws(',',collect_list(info)),']') as order_json,tid 
from
	(
      select tid,concat('{"tid":',tid,',"oid":',oid,'}') as info
		from HIVE_TABLE_NAME_XXXX 
	)
group by tid

可以得到结果如下,order_json即为所需的json字符串

若需要对同一个tid内的oid按照指定顺序排序,则需要借助

DISTRIBUTE BY  column1 SORT BY column1,column2 desc

语句,我们试下改写下上面的sql

select concat('[',concat_ws(',',collect_list(info)),']') as order_json,tid 
from
	(
      select tid,concat('{"tid":',tid,',"oid":',oid,'}') as info
		from HIVE_TABLE_NAME_XXXX 
      DISTRIBUTE BY tid SORT BY tid,oid DESC
	)
group by tid

查询结果如下

可以看到确实按照oid降序排序了,这里DISTRIBUTE BY的作用是按照指定的字段分区,并根据后面的SORT BY排序

可以参见https://blog.csdn.net/fantasticqiang/article/details/80769316

如果排序是升序的话,可以用cluster by语句替代

1.DISTRIBUTE BY的分区规则是根据分区字段的hash码与reduce的个数进行模除后,余数相同的分到一个区。

2.Hive要求DISTRIBUTE BY语句要写在SORT BY语句之前。

LeetCode刷题【902】最大为 N 的数字组合

给定一个按 非递减顺序 排列的数字数组 digits 。你可以用任意次数 digits[i] 来写的数字。例如,如果 digits = ['1','3','5'],我们可以写数字,如 '13''551', 和 '1351315'

返回 可以生成的小于或等于给定整数 n 的正整数的个数 。

 

示例 1:

输入:digits = ["1","3","5","7"], n = 100
输出:20
解释:
可写出的 20 个数字是:
1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77.

示例 2:

输入:digits = ["1","4","9"], n = 1000000000
输出:29523
解释:
我们可以写 3 个一位数字,9 个两位数字,27 个三位数字,
81 个四位数字,243 个五位数字,729 个六位数字,
2187 个七位数字,6561 个八位数字和 19683 个九位数字。
总共,可以使用D中的数字写出 29523 个整数。

示例 3:

输入:digits = ["7"], n = 8
输出:1

 

提示:

  • 1 <= digits.length <= 9
  • digits[i].length == 1
  • digits[i] 是从 '1' 到 '9' 的数
  • digits 中的所有值都 不同 
  • digits 按 非递减顺序 排列
  • 1 <= n <= 109

数学方法+递归,10月18日,今天的每日一题,有空偷个闲撸一把

看到题目第一反应是动态规划,然后扫了下题目给了示例,看到这么一段

输入:digits = ["1","4","9"], n = 1000000000
输出:29523
解释:
我们可以写 3 个一位数字,9 个两位数字,27 个三位数字,
81 个四位数字,243 个五位数字,729 个六位数字,
2187 个七位数字,6561 个八位数字和 19683 个九位数字。
总共,可以使用D中的数字写出 29523 个整数。

突然灵光一闪,可以直接用数学方法来解

拿其中的一个示例稍微改下来分解运算试试看,粗略的分解如下

输入:digits = ["1","3","5","7"], n = 1571
 1 5 7 1

 4
 16   4*4
 64   4*4*4
 45   0*4*4*4 + 1 * (2*4*4 + 1 * (3*4 + 1*( 0 + 1*(1))))

最终验证得到 4 + 16 + 64 + 45 = 129 确定正确
如果你能看懂上面最后一行 4位数的组合数量计算公式的话,那么说明你就已经GET到了

数字1571一共有4位,那么我们将结果分为两部分,不满足4位的和满足4位的结果分开计算

不满足4位的部分

  1. 如果只列举一位,那么就有String[] digits种方案,每一个数字都可以列举一次
  2. 如果要列举两位数字的情况,那么每一位都可以列举String[] digits种方案,在本示例中即为4 * 4
  3. 如果要列举三位数字的话,那么同理有4 * 4 * 4中,因为位数小于目标数字n = 1571的4位,所以可以任意组合,不要考虑结果是否会大于1571

接下来第二部分,需要组合4位数字的情况

  1. 我们先取出目标数字1571的第一位数字1,拿到digits数组中比较,发现小于1的没有,但是有等于1的情况
  2. 如果digits中存在小于当前位数字的lessCnt个,那么后面的排列组合可以不考虑。即有lessCnt * digits.length * ...... * digits.length种组合情况
  3. 如果digits中没有当前位的数字,在处理了上一位小于的情况之后,说明后面无论怎么样排序都不会有满足结果小于目标值的情况
  4. 如果存在有等于当前位的数字,那么,我们需要再往后看一位,此时回到了和步骤1相同的处理逻辑部分
  5. 那么第二位是5,digits数组中有[1,3]是小于5的,即可以有两种情况,后面无论如何排列都是满足的,那么得到2 * 4 * 4
  6. digits数组中也包含了5,我们继续看下一位数字7
  7. digits数组中有[1,3,5]小于7,于是有新的可能组合数量 3 * 4,且数组中有数字7,那么继续看下一位数字
  8. 最后一位数字1,同样的,数组中小于1的数字不存在,那么即有0种情况,后面没有4要乘了
  9. 同时数组中包含了1,所以继续看下一位
  10. 最后已经没有下一位了,这里需要返回个1,根据前一步的结果返回0的话显然会造成结果不正确

代码

class Solution {
    public int atMostNGivenDigitSet(String[] digits, int n) {
        int size = getSize(n);
        int ans = 0;
        int t = digits.length;
        //低位数的直接相加
        while (--size>0){
            ans += t;
            t *= digits.length;
        }
        int[] intDigtis = getIntDigtis(digits);
        int[] nArr = getNArr(n);
        //最高位数的分区段统计数量
        ans += splitCnt(intDigtis,nArr, 0);
        return ans;
    }

    int splitCnt(int[] digits, int[] arr, int idx){
        if (idx == arr.length){
            return 1;
        }
        //比arr[idx]小的数字有个lessCnt个,相等的有equalCnt个
        int lessCnt = 0;
        int equalCnt = 0;
        for (int i = 0; i < digits.length; i++) {
            if (digits[i] > arr[idx]){
                break;
            }
            if (digits[i] < arr[idx]){
                lessCnt++;
            }
            if (digits[i] == arr[idx]){
                equalCnt++;
            }
        }
        for (int i = idx+1; i < arr.length; i++) {
            lessCnt *= digits.length;
        }
        return lessCnt + (equalCnt * splitCnt(digits,arr,idx+1));
    }

    final static int[] mod = {
            0,
            1,
            10,
            100,
            1000,
            10000,
            100000,
            1000000,
            10000000,
            100000000,
            1000000000
    };

    final static int [] sizeTable = {
            9,
            99,
            999,
            9999,
            99999,
            999999,
            9999999,
            99999999,
            999999999,
            Integer.MAX_VALUE
    };

    static int getSize(int x) {
        for (int i=0; ; i++)
            if (x <= sizeTable[i])
                return i+1;
    }

    int[] getIntDigtis(String[] digtis) {
        int[] intDigtis = new int[digtis.length];
        for (int i = 0; i < digtis.length; i++) {
            intDigtis[i] = digtis[i].charAt(0)-'0';
        }
        return intDigtis;
    }

    int[] getNArr(int n){
        int size = getSize(n);
        int[] nArr = new int[size];
        int i = size;
        while (n > 0){
            nArr[size-i] = n/mod[i];
            n %= mod[i];
            i--;
        }
        return nArr;
    }
}

思路想明白了的话,还是不算太复杂的,主要是全位数部分计算的方法要递归起来实现的话,略微有点点显得绕

MYSQL清除超大表数据

群友聊天,有人求助一个问题,他要清理一张数据量超大的表,大概1.5G(其实也不算大),执行了TRUNCATE命令后10分钟过去都没反应。

以下,给个方案,特地自己造了张表出来

表名`tao_order_sub_info`,我从测试环境扒到本地来的,淘宝订单子表,共计356万行数据,79个字段,表文件大小2.41G

接下来开始正式操作,新建一张表`tao_order_sub_info_bak`,表结构都同原来的表

如果有部分需要保留的数据,可以在这里将需要保留的数据灌入新建的表

insert into tao_order_sub_info_bak (select * from tao_order_sub_info where N_ID < 100)

接下来同时修改新表旧表的表名,用一句SQL执行

rename table tao_order_sub_info to tao_order_sub_info_old,tao_order_sub_info_bak to tao_order_sub_info

将原表改名为一个另外一个名字,同时将刚刚新建的表改名为原来的表名,因为是在一个sql语句中执行的,可以保证本次操作的对两张表的操作是原子的。

接下来,对新的`tao_order_sub_info`表的查询写入就是这张新生成的没有数据的表了。我们可以直接删除旧表(drop table),或者进行相关备份存档操作

实测这2.4G的表执行truncate其实也是很快的,这位网友执行truncate非常慢的原因应该不是数据问题

Autowiring Arrays, Collections, and Maps

翻代码的时候,发现一段@Autowired注解写在Map上,遂点过去又翻了下@Autowired注解的源码,有如下这么一段说明

<h3>Autowiring Arrays, Collections, and Maps</h3>
<p>In case of an array, {@link java.util.Collection}, or {@link java.util.Map}
dependency type, the container autowires all beans matching the declared value
type. For such purposes, the map keys must be declared as type {@code String}
which will be resolved to the corresponding bean names. Such a container-provided
collection will be ordered, taking into account
{@link org.springframework.core.Ordered Ordered} and
{@link org.springframework.core.annotation.Order @Order} values of the target
components, otherwise following their registration order in the container.
Alternatively, a single matching target bean may also be a generally typed
{@code Collection} or {@code Map} itself, getting injected as such.

简单翻一下,大意是 @Autowired注解可以用来注入 Array、Collection和 Map这三种类型的变量,选择类型对应的type来注入的。

其中Map的key必须被定义为String类型,Map的key会被用来保存Bean的name。

Collection类型的在注入的时候会根据@Order注解来对内容进行排序,如果没有@Order注解,则会根据注册顺序决定。

即使只有一个匹配的类型的bean,也可以这么操作。


根据上面说明的内容,简单写一段代码尝试下,代码如下

定义接口

package com.cheungq.demo.service.autowiredTest.simpleServices;

/**
* SimpleInterface<br>
* @author CheungQ
*/
public interface SimpleInterface {

/**
* 实现类自行做点什么
*/
void doSomeThing();
}

写3个实现类

package com.cheungq.demo.service.autowiredTest.simpleServices;

import org.springframework.stereotype.Component;

/**
* FirstSimpleServiceImpl<br>
* @author CheungQ
*/
@Component
public class FirstSimpleServiceImpl implements SimpleInterface{
@Override
public void doSomeThing() {
System.out.println("This is the FIRST implement class");
}
}
package com.cheungq.demo.service.autowiredTest.simpleServices;

import org.springframework.stereotype.Component;

/**
* SecondSimpleServiceImpl<br>
* @author CheungQ
*/
@Component
public class SecondSimpleServiceImpl implements SimpleInterface{
@Override
public void doSomeThing() {
System.out.println("This is the SECOND implement class");
}
}
package com.cheungq.demo.service.autowiredTest.simpleServices;

import org.springframework.stereotype.Component;

/**
* ThirdSimpleServiceImpl<br>
* @author CheungQ
*/
@Component
public class ThirdSimpleServiceImpl implements SimpleInterface{
@Override
public void doSomeThing() {
System.out.println("This is the THIRD implement class");
}
}

写一个Service类,并用@Autowired分别修饰Array、Collection和Map类型

package com.cheungq.demo.service.autowiredTest;

import com.cheungq.demo.service.autowiredTest.simpleServices.SimpleInterface;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * AutowiredTestService<br>
 * @author CheungQ
 */
@Service
public class AutowiredTestService {

    @Autowired
    private List<SimpleInterface> simpleServiceList;

    @Autowired
    private SimpleInterface[] simpleServiceArray;

    @Autowired
    private Map<String,SimpleInterface> simpleServiceMap;

    public SimpleInterface getByName(String name){
        System.out.println(Arrays.toString(simpleServiceArray));
        System.out.println(simpleServiceList);
        System.out.println(simpleServiceMap);
        return simpleServiceMap.getOrDefault(name,null);
    }


}

最后写个Controller调用下看下

package com.cheungq.demo.controller;

import com.cheungq.demo.service.autowiredTest.AutowiredTestService;
import com.cheungq.demo.service.autowiredTest.simpleServices.SimpleInterface;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.NotEmpty;

/**
 * SimpleController<br>
 * @author CheungQ
 */

@RestController
@RequestMapping(value = "simple/")
public class SimpleController {

    @Autowired
    private AutowiredTestService autowiredTestService;

    @RequestMapping(value = "test")
    public void test(@RequestParam @NotEmpty String name) {
        SimpleInterface service = autowiredTestService.getByName(name);
        if (null != service){
            service.doSomeThing();
        }
    }
}

执行代码,并在Service中打个断点,我们看下情况,如下图

可以看到,Spring对Array、Collection和Map类型进行了注入,下一步修改代码,给ThirdSimpleServiceImpl实现类添加@Order注解

package com.cheungq.demo.service.autowiredTest.simpleServices;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
* ThirdSimpleServiceImpl<br>
* @author CheungQ
*/
@Component
@Order(1)
public class ThirdSimpleServiceImpl implements SimpleInterface{
@Override
public void doSomeThing() {
System.out.println("This is the THIRD implement class");
}
}

重启应用,并测试下

Array和Collection类型中的顺序都发生了改变,ThirdSimpleServiceImpl实现类正确的排到了第一位。


简单说下之前做业务开发的时候曾经的一些应用场景。

一个是在批量拉取订单数据系统中,有一个拉取任务配置模块,可以配置任务执行时间片划分参数、分页数、执行线程数、执行的BeanName。

在主要的业务流程中,根据执行的BeanName获取需要执行的bean实例、这些bean实例都要按照约定实现之前约定的接口。接口约定了两个主要的方法,一个是根据给定的参数获取总数据条数,另一个是根据给定的参数按页码拉取同步远端的订单数据。

那么再开发过程中,开发人员需要做的是根据业务需求,实现这个接口,而整个拉取的任务调度则由系统配置,根据指定的bean实例执行。

另一个曾经使用过的场景,在平台活动商户报名系统中。这个系统做得比较灵活点,根据我在这个系统上的开发和使用感受来看,这个系统最初的设计构想可能是想打造成一个低代码平台的模式。为什么会这么说呢?从已经实现的基础框架上来看,各个模块都做成了灵活配置的形式。活动类型可配置,创建活动的时候需要填写的字段可配置,商户报名填写字段可配置,可报名商户资质和商户类型可配置,报名商户、商品相关校验逻辑可配置,报名数据下发下游系统相关逻辑也是可配置的。一整个流程几乎都可以通过配置信息来调整,唯一的问题是,统统都是JSON对象配置,以及部分可在线编辑的代码配置,在可视化方面做的很少,这也导致这一整个的配置工作还是只能是开发人员来操作。稍微有点偏题了。

在这个系统中有一个模块是专门做报名信息校验的,通过配置对应的校验的BeanName,以及对应validate实例的执行顺序、相关校验参数(如字段最大最小值、字符串校验正则等等)这些。这样在执行的时候获取对应bean实例也是这么处理的。