古越剑箫

学习是一种习惯

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: :: 管理 ::

什么是短网址(短链接)?如图

 

 

用处:

1、短信营销

2、网站留言

3、生成二维码

….

 

百度云,阿里云,腾讯云现在基本上都提供这个服务,本文介绍使用java实现自己的短链接服务。

短链接看似很复杂,透过现象看本质,你会发现它的核心就是分布式ID唯一算法。

首先你需要一个够短且外网可以访问的域名,比如t.com , a.cn ,…。本文使用的短域名是tt.cn   ,我直接修改windows host文件  指向127.0.0.1

关于短链接的算法,我也看了网上其它人写的文章,总体来说有两种

1、基于MD5码

2、自增序列

在介绍这两种算法之前,我们要关注,效率和安全两者的关系。太复杂的算法保证安全一般意味着效率的降低,而根据实际业务,放出去的短链接其实并不需要过多的加密保证规范即可。相反,在访问短链接的时候能够快速寻到原链接这个才是最重要的。

 

这两种算法逻辑区别:

1、MD5码生成的短链接可能存在碰撞(概率很小)

2、自增序列需要借助第三方发号器(数据库自增,雪花算法,redis incr)…

 

本文采取的方案是基于google,Hashing + 62进制来完成

 

<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>29.0-jre</version>
        </dependency>

 

public static void main(String[] args){
        String url1="http://www.weather.com.cn/wzfw/ryzp/index.shtml";
        String url2="http://www.gov.cn/xinwen/2020-09/17/content_5544097.htm";
        String url3="http://www.zj.gov.cn/art/2020/9/17/art_1554467_57705747.html";
        String url4="http://www.moe.gov.cn/jyb_xwfb/gzdt_gzdt/s5987/202009/t20200917_488442.html";

        System.out.println(shortUrl(url1));
        System.out.println(shortUrl(url2));
        System.out.println(shortUrl(url3));
        System.out.println(shortUrl(url4));

    }


    public static Object shortUrl(String url){
        //google的Hashing将实际网址hash之后转为long
        long s = Hashing.murmur3_32().hashUnencodedChars(url).padToLong();
        System.out.println(s);
        //将10进制转为62进制
        String encode = ShortLinkUtils.base62Encode(s);
        return encode;
    }

 

 

public class ShortLinkUtils {


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


    /**
     * 将10进制数字转为62进制
     *
     * @param num Long 型数字
     * @return 62进制字符串
     */
    public static String base62Encode(long num) {
        StringBuilder sb = new StringBuilder();
        int remainder;
        while (num > scale - 1) {
            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进制字符串转回十进制数字
     *
     * @param str 编码后的62进制字符串
     * @return 解码后的 10 进制字符串
     */
    public static long base62Decode(String str) {
        str = str.replace("^0*", "");
        long num = 0;
        int index = 0;
        for (int i = 0; i < str.length(); i++) {
            index = chars.indexOf(str.charAt(i));
            num += (long) (index * (Math.pow(scale, str.length() - i - 1)));
        }
        return num;
    }

}

 

 

 

 

这就是一个简单且可用的短链接生成算法,眼尖的小伙伴可能看出来了,ShortLinkUtils#base62Decode,这个反解析的方法是干什么的呢?

假如,你根据mysql自增主键生成短链接,id=100098 ShortLinkUtils#base62Encode  得到短码:N87ja9 。那么ShortLinkUtils#base62Decode 解析N87ja9  得到 100098 ,但是并不建议这样做,因为反解析需要耗费性能,这个算法的时间复杂度是O(n)级别,且算法不能变动,一旦变动前期发出去的短链接无法解析或者需要做适配,增大工作量。因此我们直接用生成的短码映射实际的长链接即可。

 

全部代码如下:

public class ShortLinkUtils {


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


    /**
     * 将10进制数字转为62进制
     *
     * @param num Long 型数字
     * @return 62进制字符串
     */
    public static String base62Encode(long num) {
        StringBuilder sb = new StringBuilder();
        int remainder;
        while (num > scale - 1) {
            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进制字符串转为十进制数字
     *
     * @param str 编码后的62进制字符串
     * @return 解码后的 10 进制字符串
     */
    public static long base62Decode(String str) {
        str = str.replace("^0*", "");
        long num = 0;
        int index = 0;
        for (int i = 0; i < str.length(); i++) {
            index = chars.indexOf(str.charAt(i));
            num += (long) (index * (Math.pow(scale, str.length() - i - 1)));
        }
        return num;
    }

}

 

public class ShortLinkCommon {

    /**
     * 直接修改 windows hosts  tt.cn 127.0.0.1
     */
    public static final String DOMAIN_PREFIX = "tt.cn";

    /**
     * 存储短链接码->原始链接 映射关系,生产环境不可如此使用
     */
    public static Map<String, String> shortLinkMap = new HashMap<>();

}

 

@Controller
public class ShortController {

    /**
     * {s} 可以使用26个英文字母,划分不同的业务
     *
     * @param shortLinkCode 短链接code
     * @param response
     * @throws IOException
     */
    @GetMapping("{s}/{shortLinkCode}")
    public void redirect(@PathVariable("shortLinkCode") String shortLinkCode, HttpServletResponse response) throws IOException {
        String fullLink = ShortLinkCommon.shortLinkMap.get(shortLinkCode);
        response.sendRedirect(fullLink);
    }
}

 

 

@RestController
@RequestMapping("short")
public class ShortLinkController {


    /**
     * create shortLinkCode
     *
     * @param url 原始链接
     * @return
     */
    @PostMapping("create")
    public String create(String url) {
        long num = Hashing.murmur3_32().hashUnencodedChars(url).padToLong();
        String shortLinkCode = ShortLinkUtils.base62Encode(num);
        ShortLinkCommon.shortLinkMap.put(shortLinkCode, url);
        return ShortLinkCommon.DOMAIN_PREFIX.concat("/a").concat("/").concat(shortLinkCode);
    }
}

 

 

@SpringBootApplication
public class ShortLinkApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShortLinkApplication.class, args);
    }
}

 

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.2.6.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>24.0-jre</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
    </dependency>
  </dependencies>
posted on 2020-09-18 19:08  古越剑箫  阅读(761)  评论(0编辑  收藏  举报