怎样设计一个好用的短链接服务?

Posted androidstarjack

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了怎样设计一个好用的短链接服务?相关的知识,希望对你有一定的参考价值。

点击上方蓝色“终端研发部”,选择“设为星标”

学最好的别人,做最好的我们 

设计一个类似于TinyURL的缩短链接长度的服务,用户在访问短链接的时候可以自动跳转到长链接。

思路:

  • 将长网址用md5算法生成32位签名串,分为4段,,每段8个字符

  • 对这4段循环处理,取每段的8个字符, 将他看成16进制字符串与0x3fffffff(30位1)的位与操作,超过30位的忽略处理

  • 将每段得到的这30位又分成6段,每5位的数字作为字母表的索引取得特定字符,依次进行获得6位字符串

  • 这样一个md5字符串可以获得4个6位串,取里面的任意一个就可作为这个长url的短url地址

  • 得到是唯一的,但是我们能够取出4组,这样几乎不会出现太大的重复

  • key 可以自定义生成 MD5 加密字符传前的混合 KEY

private Object getShortUrl(String shortUriPrefix,String originUrl) {
        UrlMap urlMap=urlMapService.getByOriginUrl(originUrl);
        if(urlMap!=null&&urlMap.getId()!=null){
            return urlMap.getShortUrl();
        }
        String[] sortUriArr = ShortUrlUtils.getShortUri(originUrl,System.currentTimeMillis()+"");

        //如有重复,使用sortUriArr内4组循环
        String shortUrl="";
        int i=0;
        boolean b=true;
        while (b){
            shortUrl=domain.getShortLink()+shortUriPrefix+sortUriArr[i];
            try {
                urlMap=addUrlMap(shortUrl,originUrl);
                b=false;
            }catch (Exception e){
                ++i;
                if(i>3){
                    b=false;
                }
            }
        }
        if(i>3){
            shortUrl=domain.getShortLink()+shortUriPrefix+ShortUrlUtils.getShortUriBySnowFlake();
            urlMap=addUrlMap(shortUrl,originUrl);
        }
        return urlMap.getShortUrl();
    }

    private UrlMap addUrlMap(String shortUrl,String originUrl) {
        UrlMap urlMap=new UrlMap();
        urlMap.setId(IdUtils.getId());
        urlMap.setCreateTime(new Date());
        urlMap.setOriginUrl(originUrl);
        urlMap.setShortUrl(shortUrl);
        urlMapService.insert(urlMap);
        return urlMap;
    }

工具类


/**
 * @ClassName SortUrlUtils
 * @Author yupanpan
 * @Date 2021/3/16 11:52
 */
public class ShortUrlUtils {

    // 十六进制下数字到字符的映射数组
    private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};


    public static String getShortUriBySnowFlake(){
        SnowFlake snowFlake = new SnowFlake(0, 0);
        return ConversionUtils.base10EncodeTo62(snowFlake.nextId());
    }

    /**
     * 将长网址用md5算法生成32位签名串,分为4段,,每段8个字符
     * 对这4段循环处理,取每段的8个字符, 将他看成16进制字符串与0x3fffffff(30位1)的位与操作,超过30位的忽略处理
     * 将每段得到的这30位又分成6段,每5位的数字作为字母表的索引取得特定字符,依次进行获得6位字符串
     * 这样一个md5字符串可以获得4个6位串,取里面的任意一个就可作为这个长url的短url地址
     * 得到是唯一的,但是我们能够取出4组,这样几乎不会出现太大的重复
     * key 可以自定义生成 MD5 加密字符传前的混合 KEY
     */
    public static String[] getShortUri(String url,String key) {
        // 要使用生成 URL 的字符
        String[] chars = new String[]{"a", "b", "c", "d", "e", "f", "g", "h",
                "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
                "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
                "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
                "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
                "U", "V", "W", "X", "Y", "Z"
        };
        // 对传入网址进行 MD5 加密
        String sMD5EncryptResult = md5(key + url);
        String hex = sMD5EncryptResult;
        String[] resUrl = new String[4];
        for (int i = 0; i < 4; i++) {
            // 把加密字符按照 8 位一组 16 进制与 0x3FFFFFFF 进行位与运算
            String sTempSubString = hex.substring(i * 8, i * 8 + 8);
            // 这里需要使用 long 型来转换,因为 Inteper .parseInt() 只能处理 31 位 , 首位为符号位 , 如果不用 long ,则会越界
            long lHexLong = 0x3FFFFFFF & Long.parseLong(sTempSubString, 16);
            String outChars = "";
            for (int j = 0; j < 6; j++) {
                // 把得到的值与 0x0000003D 进行位与运算,取得字符数组 chars 索引
                long index = 0x0000003D & lHexLong;
                // 把取得的字符相加
                outChars += chars[(int) index];
                // 每次循环按位右移 5 位
                lHexLong = lHexLong >> 5;
            }
            // 把字符串存入对应索引的输出数组
            resUrl[i] = outChars;
        }
        return resUrl;
    }

    /**
     * 把inputString加密
     */
    public static String md5(String inputStr) {
        return encodeByMD5(inputStr);
    }

    /**
     * 对字符串进行MD5编码
     */
    private static String encodeByMD5(String originString) {
        if (originString != null) {
            try {
                // 创建具有指定算法名称的信息摘要
                MessageDigest md5 = MessageDigest.getInstance("MD5");
                // 使用指定的字节数组对摘要进行最后更新,然后完成摘要计算
                byte[] results = md5.digest(originString.getBytes());
                // System.out.println(results.length);
                // 将得到的字节数组变成字符串返回
                String result = byteArrayToHexString(results);
                //    System.out.println("encode "+result);
                return result;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 轮换字节数组为十六进制字符串
     *
     * @param b 字节数组
     * @return 十六进制字符串
     */
    private static String byteArrayToHexString(byte[] b) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            resultSb.append(byteToHexString(b[i]));
        }
        return resultSb.toString();
    }

    // 将一个字节转化成十六进制形式的字符串
    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n = 256 + n;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }


}
public class ConversionUtils {

    private static String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    private static int scale = 62;
    private static int minLength = 5;

    //10进制数字转62进制
    public static String base10EncodeTo62(long num) {
        StringBuilder sb = new StringBuilder();
        int remainder;
        while (num > scale - 1) {
            //对 scale 进行求余,然后将余数追加至 sb 中,由于是从末位开始追加的,因此最后需要反转字符串
            remainder = Long.valueOf(num % scale).intValue();
            sb.append(chars.charAt(remainder));
            //除以进制数,获取下一个末尾数
            num = num / scale;
        }
        sb.append(chars.charAt(Long.valueOf(num).intValue()));
        String value = sb.reverse().toString();
        return StringUtils.leftPad(value, minLength, '0');
    }

    //62进制转为10进制数字
    public static long str62DecodeTo10(String str) {
        //将 0 开头的字符串进行替换
        str = str.replace("^0*", "");
        long value = 0;
        char tempChar;
        int tempCharValue;
        for (int i = 0; i < str.length(); i++) {
            //获取字符
            tempChar = str.charAt(i);
            //单字符值
            tempCharValue = chars.indexOf(tempChar);
            //单字符值在进制规则下表示的值
            value += (long) (tempCharValue * Math.pow(scale, str.length() - i - 1));
        }
        return value;
    }

    public static void main(String[] args) {
        System.out.println(100);
    }
}

实体类

@Data
public class UrlMap {

    private Long id;
    private Date createTime;
    private String shortUrl;
    private String originUrl;
    private Date expireTime;
}

CREATE TABLE `t_url_map` (
  `id` bigint(20) NOT NULL,
  `create_time` datetime NOT NULL,
  `short_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '短链接',
  `origin_url` varchar(255) NOT NULL COMMENT '原始链接地址',
  `expire_time` datetime DEFAULT NULL COMMENT '过期时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_unique_short_url` (`short_url`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Dao/Mapper

@Mapper
public interface UrlMapMapper {

    @Select("SELECT * FROM t_url_map WHERE short_url=#{shortUrl} LIMIT 1")
    UrlMap getByShortUrl(@Param("shortUrl") String shortUrl);

    @Select("SELECT * FROM t_url_map WHERE origin_url=#{originUrl} AND(expire_time>=now() OR expire_time IS NULL) LIMIT 1")
    UrlMap getByOriginUrl(@Param("originUrl") String originUrl);

    @Insert("INSERT INTO t_url_map(id,create_time,short_url,origin_url) VALUES(#{entity.id},#{entity.createTime},#{entity.shortUrl},#{entity.originUrl})")
    void insert(@Param("entity") UrlMap urlMap);
}

可以生成几百亿条短链接,但不排除100%不会重复,几率极小,混合key我用的当前毫秒

也可以用作唯一标识code等

如果重复呢?

这里没有去查数据库是否存在,如果并发量太高,会给数据库产生压力。所以添加唯一索引,要么报错,基本一次就可以过。

为了重复添加报错,然后重新添加,当4组短链接用完之后,采用SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。

如果是分布式环境(同一时间点会产品同样的id)就需要自己设置数据中心datacenterId或者机器码machineId。

采用SnowFlake 算法生成的id为18位,我这里转成了62进制,长度为10.比getShortUri多了4位。自己斟酌平衡

shortUriPrefix为短链接前路径,我这里设置的为/g前路径。比如有业务区分的话,我这里就用的/g。当然也可以使用/xx等,尽量一个字母代替,毕竟是短链接

最终的效果,存储在mysql

最终成型的设计:

上面只是一种思路和解决方案。一般的系统可以直接存在mysql。如果并发量非常高,对QPS、RPS等要求很高,可以先存储在redis等缓存中,在缓存中操作,做好容灾,定期同步至mysql

原文链接:blog.csdn.net/ypp91zr/article/details/114983761

BAT等大厂Java面试经验总结 想获取 Java大厂面试题学习资料扫下方二维码回复「BAT」就好了回复 【加群】获取github掘金交流群回复 【电子书】获取2020电子书教程回复 【C】获取全套C语言学习知识手册回复 【Java】获取java相关的视频教程和资料回复 【爬虫】获取SpringCloud相关多的学习资料回复 【Python】即可获得Python基础到进阶的学习教程回复 【idea破解】即可获得intellij idea相关的破解教程回复 【BAT】即可获得intellij idea相关的破解教程关注我gitHub掘金,每天发掘一篇好项目,学习技术不迷路!



回复 【idea激活】即可获得idea的激活方式
回复 【Java】获取java相关的视频教程和资料
回复 【SpringCloud】获取SpringCloud相关多的学习资料
回复 【python】获取全套0基础Python知识手册
回复 【2020】获取2020java相关面试题教程
回复 【加群】即可加入终端研发部相关的技术交流群
阅读更多
为什么HTTPS是安全的
因为BitMap,白白搭进去8台服务器...
《某厂内部SQL大全 》.PDF
字节跳动一面:i++ 是线程安全的吗?
大家好,欢迎加我微信,很高兴认识你!
在华为鸿蒙 OS 上尝鲜,我的第一个“hello world”,起飞!

相信自己,没有做不到的,只有想不到的在这里获得的不仅仅是技术!



喜欢就给个“在看”

以上是关于怎样设计一个好用的短链接服务?的主要内容,如果未能解决你的问题,请参考以下文章

6款超好用的短链接在线批量生成与接口api(新浪t.cn腾讯url.cn)

怎样使用新浪短网址进行网址缩短-推荐15个在线生成工具

这可能是东半球最接地气的短链接系统设计

如何生成高性能的短链接?

最通俗易懂的短链接原理讲解

构建基于 OpenResty + Lua 的短地址服务