短连设计方案描述

短连接设计说明书 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缓存
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容