buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

MD5是个好东西 / MD5 is a nice guy

md5是一种摘要生成算法,通过对消息生成唯一摘要,可校验消息是否被篡改。


众所周知,md5广泛用在http接口通讯的安全控制上,通过在签名原始串后加上商户通信秘钥,进行MD5运算,形成的摘要字符串即为签名结果。接口双方据此来判断报文是否一致。

 

只要消息不变,其md5值就不变。正因为这一点,md5也不光用在接口通讯上,我们的程序中也大可借用这一特性来保证数据的唯一性。

 

举一个例子,我们经常有判重机制,比如验证重复请求,比如保证一段逻辑不能被重复执行(汇票系统里,用户提醒消息不能重发;定时任务防重--定时任务在特定时间段内被重复触发,但其内部逻辑不允许重复执行)。这些场景,用redis分布式锁配合md5将是完美的解决方案。

    public boolean remind(TradeOrder order, TradeRemindTypeEnum remindTypeEnum) {

        log.info("订单发送提醒开始。orderNo={},通知类型={}", order.getOrderNo(), remindTypeEnum);

        int exSeconds = 60 * 60;//过期时间为1小时
        //如果key不存在,执行set操作,返回字符串OK;如果key已经存在,则返回null
        String redisKey = remindTypeEnum + "_" + order.getOrderNo() + "_" + MD5Util.md5(JSONObject.toJSONString(order));
        if (null == JedisClusterUtil.getJedisCluster().set(redisKey, UUID.randomUUID().toString(), "NX", "EX", exSeconds)) {
            log.info("订单已经提醒过,本次不再提醒。 {}", redisKey);
            return false;
        }
        
        ....发送提醒消息...
        
    }
View Code

 

再举一个例子,票据贴息金额可按年利率或每十万手续费这两种方式来计算,需求里有一个规则是当其中一个没有值时,要根据有值的那个将其反算出来。这个算法前几天被QA测试发现,提交的交易单的贴息金额会存在几分钱的差异。同事将算法改造完毕后,再测试后并未复现这样的问题。但,这并不代表就彻底fix了。因此,我写了个testcase,批量随机生成一些测试案例,模拟穷举客户的输入,这其中,在比较两次计算结果时,就用到了md5,如果两次结果的md5不一样,那就证明算法依然存在问题。最后,经运行testcase,还真发现算法的不足,再次改正迭代测试后,bug不再复现。

import com.emaxcard.codec.MD5Util;
import com.emaxcard.rpc.payment.model.FeeCalculateReq;
import com.emaxcard.rpc.payment.model.FeeCalculateRes;
import org.apache.commons.lang.math.RandomUtils;
import org.joda.time.DateTime;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class FeeCalculateServiceImplTest {

    //    @Test
//    public void feeCalculate() {
    public static void main(String[] args) throws Exception {

        for (int i = 1; i < 10000; i++) {
            System.out.println(i);
            int rand = RandomUtils.nextInt(10);
            BigDecimal divide = BigDecimal.valueOf(i).divide(BigDecimal.valueOf(rand == 0 ? Math.PI : rand), 12, RoundingMode.HALF_UP);
            BigDecimal draftAmt = BigDecimal.valueOf(500000).add(divide).setScale(0, RoundingMode.HALF_UP);
            BigDecimal unitAmt = BigDecimal.valueOf(RandomUtils.nextInt(10)).add(divide).setScale(2, RoundingMode.HALF_UP);
            BigDecimal saleRate = divide.divide(BigDecimal.valueOf(100)).setScale(6, RoundingMode.HALF_UP);
            calculateFee(draftAmt,unitAmt, saleRate);
        }
    }

    static void calculateFee(BigDecimal draftAmt, BigDecimal unitAmt, BigDecimal saleRate) throws Exception {
        if (null == unitAmt) {
            unitAmt = BigDecimal.ZERO;
        }
        if (null == saleRate) {
            saleRate = BigDecimal.ZERO;
        }
        FeeCalculateReq feeCalculateReq = new FeeCalculateReq();
        feeCalculateReq.setDraftAmt(draftAmt.toPlainString());
        feeCalculateReq.setUnitAmt(unitAmt.toPlainString());
        feeCalculateReq.setSaleRate(saleRate.toPlainString());
        feeCalculateReq.setDraftEndDate(DateTime.now().plusYears(1).toString("yyyy-MM-dd"));

        FeeCalculateRes feeCalculateRes = FeeCalculateServiceImpl.calculateFee(feeCalculateReq);
        String s1 = MD5Util.md5(feeCalculateRes.toString());

        feeCalculateReq.setUnitAmt(feeCalculateRes.getUnitAmt());
        feeCalculateReq.setSaleRate(feeCalculateRes.getSaleRate());
        feeCalculateRes = FeeCalculateServiceImpl.calculateFee(feeCalculateReq);
        String s2 = MD5Util.md5(feeCalculateRes.toString());

        boolean equals = s1.equals(s2);
        if (equals == false) {
            throw new Exception("bad luck!!!!");
        }
    }
}
View Code

 

 


 

 

谁的手总紧紧牵住我的手
不回头在人群沙漠中漂泊
你别用含着泪的眼睛看我
听蝉声沉落
请抬头今宵露重

是谁用带露的草叶医治我
愿共我顶风暴泥泞中跋涉
是谁说经过的路都是必需 磨难尽收获
山云做幕攀岩观火

请由我引吭高歌 面迎啊海上风
在世界之外 在时间之中 无问西东
就奋身做个英雄 不枉那青春勇
愿心之自由共天地俊秀
有情有梦

是谁用带露的草叶医治我
愿共我顶风暴泥泞中跋涉
是谁说经过的路都是必需 磨难尽收获
山云做幕攀岩观火

请由我引吭高歌 面迎啊海上风
在世界之外 在时间之中 无问西东
就奋身做个英雄 不枉那青春勇
愿心之自由共天地俊秀
有情有梦

posted on 2019-07-18 11:18  buguge  阅读(462)  评论(0编辑  收藏  举报