java-URL短连接的生成(保证生成的唯一性)

20 篇文章 12 订阅
11 篇文章 1 订阅

业务场景:

短信平台发送带有跳转链接的短信时,会存在一个问题,有的时候链接会特别长,而且可能会有一些特殊字符,影响点:

  • 这样手机的短信会将链接会切开来,直接点击无法跳转.
  • 复制粘贴时浏览器可能无法将其直接跳转,会将其默认为搜索条件而不是链接地址来跳转.

正是以上的这些原因(只是一小部分的原因),就衍生了短连接的生成这个需求

短链接的生成方式:

  • 加密方式:
    最简单的办法就是通过MD5方式来加密,也是目前网上最多的一种实现方式,我也是借鉴了其方法,不过亲测高并发下无法支持其生成的唯一性,可能引起短连接地址不唯一,导致bug.
    解决办法下面再讲.先看看我们的加密方式,代码如下:
import java.security.MessageDigest;
import java.util.Map;
import java.util.Random;

public class URLUtils {
    /**
     * 根据MD5加密方式生成短链接
     *@author DingYongJun
     *@date 2020/8/18
     *@return List<Map<String, Object>>
     */
    public static String getShortURL(String URL){
        // 可以自定义生成 MD5 加密字符传前的混合 KEY
        String key = "QDGYJ";
        // 要使用生成 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 hex = md5ByHex(key + URL);

        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;
        }
        Random random=new Random();
        int j=random.nextInt(4);
        //随机取一个作为短链
        return resUrl[j];
    }

    /**
     * MD5加密(32位大写)
     *@author DingYongJun
     *@date 2020/8/18
     *@return List<Map<String, Object>>
     */
    public static String md5ByHex(String src) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] b = src.getBytes();
            md.reset();
            md.update(b);
            byte[] hash = md.digest();
            String hs = "";
            String stmp = "";
            for (int i = 0; i < hash.length; i++) {
                stmp = Integer.toHexString(hash[i] & 0xFF);
                if (stmp.length() == 1)
                    hs = hs + "0" + stmp;
                else {
                    hs = hs + stmp;
                }
            }
            return hs.toUpperCase();
        } catch (Exception e) {
            return "";
        }
    }
}

以上是借鉴网上其他博主的代码,在此不做过多赘述,有兴趣研究其具体的生成方式的可以使用断点的方式来进去了解.

  • 问题所在:
    大家可以很轻易的发现一些问题,比如每次传的url一毛一样的时候,我们只生成四个短连接和随机数,当并发量大到一定的程度时,就会发生不唯一性的问题.

  • 解决办法:
    为了不使短连接地址太长(不超过12位,否则和长链接本质上区别不大了).我们可以在业务层调用两次getShortURL方法.
    具体代码如下:

		Date date = new Date();
		String URL = paramsMap.get("url").toString();
        String shortUrl = URLUtils.getShortURL(URL);
        //为了保证数据的唯一性,再加个时间戳压缩成短链接,两个拼接在一起,确保数据的唯一性
        String timeStamp = String.valueOf(date.getTime());
        String shotTime = URLUtils.getShortURL(timeStamp);
        String duanlianjie = shortUrl+shotTime;

解释:
为了保证数据的唯一性,再加个时间戳压缩成短链接,两个拼接在一起,确保数据的唯一性,系统时间是一直在变化的,所以在一定程度上就保证了这个短链接的唯一性!
可能出现的问题:
这个在高并发的情况也是可能会出现重复的情况,但是这个保证了同样的长链接不会生成重复的短链接.又因为短信平台不会同一时间多次调用.基本上满足了我的业务需求.
解决办法:
在此我就不具体实现解决办法了,只提供一个大概思路给大家.

  • 1.在getShortURL加上同步锁Synchronized ,保证每次只能有一个线程可以调用该方法,虽然在一定程度上可能会损失性能,但是会确保短链接的唯一性!
  • 2.可以加上其他的算法来和时间戳拼接到一起,然后再调用生成方法.如:(雪花算法等).有兴趣的可以自己去研究一下.

自此全部结束,以上都是个人所言,如有不对,尽请指出,大家一起交流进步。谢谢!

有需要源码的,或者有不理解,欢迎关注微信公众号来讨论,留言会回复的哦~

欢迎微信搜索关注:《全员格子》!!!

生成防伪码并保证唯一性,可以采用以下方法: 1. 选择一个长度适当的随机字符串作为前缀,如8位的随机数字。 2. 将前缀和当前时间戳拼接在一起,得到一个较长的字符串。 3. 对拼接后的字符串进行哈希计算,得到一个防伪码的后缀。 4. 将前缀和后缀拼接在一起,得到最终的防伪码。 5. 检查生成的防伪码是否已经存在于数据库中,如果不存在,则将其保存到数据库中,否则重新生成防伪码并重复上述步骤。 下面是一个Java代码示例: ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Random; public class AntiFakeCodeGenerator { private static final int PREFIX_LENGTH = 8; private static final String JDBC_URL = "jdbc:mysql://localhost:3306/anti_fake?useSSL=false&serverTimezone=UTC"; private static final String JDBC_USERNAME = "root"; private static final String JDBC_PASSWORD = "password"; public static void main(String[] args) { String antiFakeCode = generateAntiFakeCode(); System.out.println("Generated anti-fake code: " + antiFakeCode); } public static String generateAntiFakeCode() { String prefix = generateRandomString(PREFIX_LENGTH); Timestamp timestamp = new Timestamp(System.currentTimeMillis()); String str = prefix + timestamp.toString(); String suffix = String.valueOf(str.hashCode()).replace("-", ""); String antiFakeCode = prefix + suffix.substring(0, 12 - PREFIX_LENGTH); if (isAntiFakeCodeExist(antiFakeCode)) { return generateAntiFakeCode(); } else { saveAntiFakeCode(antiFakeCode); return antiFakeCode; } } private static String generateRandomString(int length) { String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; Random random = new Random(); StringBuilder sb = new StringBuilder(); for(int i = 0; i < length; i++) { int number = random.nextInt(62); sb.append(str.charAt(number)); } return sb.toString(); } private static boolean isAntiFakeCodeExist(String antiFakeCode) { boolean exist = false; try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USERNAME, JDBC_PASSWORD); PreparedStatement stmt = conn.prepareStatement( "SELECT COUNT(*) AS count FROM anti_fake_code WHERE code = ?")) { stmt.setString(1, antiFakeCode); ResultSet rs = stmt.executeQuery(); if (rs.next() && rs.getInt("count") > 0) { exist = true; } } catch (SQLException e) { e.printStackTrace(); } return exist; } private static void saveAntiFakeCode(String antiFakeCode) { try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USERNAME, JDBC_PASSWORD); PreparedStatement stmt = conn.prepareStatement( "INSERT INTO anti_fake_code(code) VALUES(?)")) { stmt.setString(1, antiFakeCode); stmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } } } ``` 该代码使用了一个长度为8的随机字符串作为前缀,然后将前缀和当前时间戳拼接在一起得到一个较长的字符串,再对其进行哈希计算得到后缀,最后将前缀和后缀拼接在一起得到最终的防伪码。如果生成的防伪码已经存在于数据库中,则重新生成防伪码,直到生成一个未存在于数据库中的防伪码。这样可以确保防伪码的唯一性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值