短连设计方案描述

2018-07-27  本文已影响39人  团长plus

短连接设计说明书 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缓存
上一篇下一篇

猜你喜欢

热点阅读