美文网首页
短连设计方案描述

短连设计方案描述

作者: 团长plus | 来源:发表于2018-07-27 09:36 被阅读39次

    短连接设计说明书 v1

    功能概述

    短连接服务系统Url Shorten Service, 简称USS, 主要提供长连接缩短为短连接、还原短连接等功能

    数据库设计

    CREATE TABLE `uss_urls` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `code` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '短链接的串码',
      `originUrl` text COLLATE utf8_unicode_ci,
      `sha1` varchar(50) CHARACTER SET utf8 NOT NULL,
      `seqId` bigint(20) DEFAULT NULL,
      `createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `expiredAt` datetime NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `USS_CODE_UNIQUE` (`code`),
      UNIQUE KEY `USS_SEQID` (`seqId`),
      KEY `USS_EXPIRED_AT` (`expiredAt`,`createdAt`),
      KEY `USS_SHA1` (`sha1`)
    ) ENGINE=InnoDB AUTO_INCREMENT=173293 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    
    

    短连接算法:

    1. 将"原始链接(长链接)+ key(随机字符串,防止算法泄漏)"MD5后得到32位的一个字符串A
    2. 将上面的A字符串分为4段处理, 每段的长度为8 , 比如四段分别为 M、N、O、P
    3. 将M字符串当作一个16进制格式的数字来处理, 将其转换为一个Long类型。 比如转换为L
    4. 此时L的二进制有效长度为32位, 需要将前面两位去掉,留下30位 , 可以 & 0x3fffffff 进行位与运算得到想要的结果。(30位才能转换62进制,否则超出)
    5. 此时L的二进制有效长度为30位, 分为6段处理, 每段的长度为5位
    6. 依次取出L的每一段(5位),进行位操作 & 0x0000003D 得到一个 <= 61的数字,来当做index
    7. 根据index 去预定义的62进制字符表里面去取一个字符, 最后能取出6个字符,然后拼装这6个字符成为一个6位字符串,作为短链接码
      拿出第②步剩余的三段,重复3-7 步
    8. 这样总共可以获得 4 个6位字符串,取里面的任意一个就可作为这个长链接的短网址
    9. 串码添加校验位checksum,用于简单校验。所以总共7位码

    接口设计 : 主要设计三个接口

    Map<String, Object> shorten(MMURL mmurl);
    图1.png
    生成短连接码 见 图1.png
    1. 第一步验证长度 大于40小于5000
    2. 第二步拿到环境域名
    3. 第三部 使用SHA-1算法加密 16进制
      SHA-1算法:
    public static String encode(String originTxt, String encodingAlgorithm, String charsetEncoding) {
            if (originTxt == null) {
                 return null;
            } else {
                 try {
                      MessageDigest messageDigest = MessageDigest.getInstance(encodingAlgorithm);
                      if (StringUtil.isNotBlank(charsetEncoding)) {
                           messageDigest.update(originTxt.getBytes(charsetEncoding));
                      } else {
                           messageDigest.update(originTxt.getBytes("UTF-8"));
                      }
    
                      byte[] digest = messageDigest.digest();
                      return HexUtil.bytesToHex(digest);
                 } catch (NoSuchAlgorithmException var5) {
                      throw new SecurityException(var5);
                 } catch (UnsupportedEncodingException var6) {
                      throw new RuntimeException(var6);
                 }
            }
       }
    
    1. 第四部查询当前html是否转换过短连接(读取redis缓存)
    2. 第五步计算seqid 主要依赖于redis incr操作实现
      部分代码实现如下:
    /**
         * 同步方法, 获取redis的sequence累加值,如果redis无数据,先从DB里面获取预热。
         * @return
         */
        public Long incrSeqId() {
            long redisSeqId = getStr2long(UssConstants.REDIS_USS_SEQ_KEY);
    
            if (redisSeqId < UssConstants.INIT_SEQ_ID) {
                synchronized (INCR_LOCK) {
                    redisSeqId = getStr2long(UssConstants.REDIS_USS_SEQ_KEY);
    
                    if (redisSeqId < UssConstants.INIT_SEQ_ID) {
                        Long dbSeqId = ussService.queryMaxSeqId();
                        //DB存在就以DB为启动基数, DB不存在就是初始值1982
                        final long seqId = (dbSeqId != null && dbSeqId > UssConstants.INIT_SEQ_ID) ?
                                dbSeqId:
                                UssConstants.INIT_SEQ_ID;
    
                        //设置永久的Key
                        String oldSeqId = stringOpr.getAndSet(UssConstants.REDIS_USS_SEQ_KEY,
                                String.valueOf(seqId));
    
                        //set之前Redis中已经存在一个比较大的值,说明集群其他节点并发插入了
                        //减少Lock复杂度的实现,直接在当前的基础上补上(差值+500),减少冲突的可能性即可。
                        long oldSeqIdLng = (oldSeqId != null ? Long.parseLong(oldSeqId) : 0);
                        if (oldSeqIdLng > seqId) {
                            longOpr.increment(UssConstants.REDIS_USS_SEQ_KEY,
                                    oldSeqIdLng - seqId + 500);
                        }
                        stringOpr.getOperations().persist(UssConstants.REDIS_USS_SEQ_KEY);
                    }
                }
            }
    
            //用随机数来自增
            return longOpr.increment(UssConstants.REDIS_USS_SEQ_KEY,
                    RandomUtils.nextInt(1, 10));
        }
    
    1. 第六步生成短码code,短码code分为 分为4到5个步骤个部分 第一位随机混淆,第二位字符表示序列号转成62进制后的长度(用于反向转换),第三位开始是自增长序列,倒数第二位随机混淆,最后一位校验位
      代码:
     /**
         * 自增序列基础上的缩短算法
         *         第一位         第二位          中间位数         倒数第二位     最后一位
         *       随机混淆字符  62进制序号的长度  自增序号的62进制   随机混淆字符     校验位
         *
         * @param seqId
         * @return
         */
        public static String shorten(long seqId) {
            if (seqId < 1L) {
                throw new RuntimeException("SeqId is too small!");
            }
    
            String seqIdStr62 = Base62Utils.fromBase10(seqId);
            int bits = seqIdStr62.length();
    
            StringBuffer shortenCode = new StringBuffer();
            //第一位随机混淆
            shortenCode.append(DIGITS[RandomUtils.nextInt(0, 62)]);
    
            //第二位字符表示序列号转成62进制后的长度(用于反向转换)
            shortenCode.append(Base62Utils.fromBase10(bits));
    
            //第三位开始是自增长序列
            shortenCode.append(seqIdStr62);
    
            //补充混淆字段保证短链7位起
            if (bits < 3) {
                shortenCode.append(RandomStringUtils.randomAlphanumeric(3 - bits));
            }
    
            //倒数第二位随机混淆
            shortenCode.append(DIGITS[RandomUtils.nextInt(0, 62)]);
            //最后一位校验位
            return LuhnUtil.mmGenCodeWithCheckSum(shortenCode.toString());
        }
    
    1. 生成短连接 保存数据库,redis

    Map<String, Object> restore(String shortenCode);


    图2.png
    还原短链接 说明详见图2.png
    1. 第一步校验短码是否匹配 主要是使用了

      lunh算法实现原理
    1. 从校验位开始,从右往左,偶数位乘2,然后将两位数字的个位与十位相加;
    2. 计算所有数字的和(67);
    3. 乘以9(603);
    4. 取其个位数字(3),得到校验位。

    lunh算法代码:

    public static boolean luhnCheck(String code) {
              if (StringUtil.isBlank(code)) {
                   return false;
              } else {
                   int[] checkArr = convertStrToInArr(code);
    
                   int sum;
                   for(sum = 1; sum < checkArr.length; sum += 2) {
                        checkArr[sum] <<= 1;
                        checkArr[sum] = checkArr[sum] / 10 + checkArr[sum] % 10;
                   }
    
                   sum = 0;
    
                   for(int i = 0; i < checkArr.length; ++i) {
                        sum += checkArr[i];
                   }
    
                   return sum % 10 == 0;
              }
         }
    
    1. 第二步读取redis缓存 读取不到从数据库读取 redis不存在记录一天缓存

    Map<String, Object> revoke(String shortenCode);

    图3.png
    销毁短连接 详见图3.png
    1. 第一步 判断短码是否redis不存在记录 存在直接返回 不存在直接di
    2. 第二步 修改数据库的短码过期时间 删除redis缓存

    相关文章

      网友评论

          本文标题:短连设计方案描述

          本文链接:https://www.haomeiwen.com/subject/mlsbmftx.html