Java短链接生成案例

wen java案例 7

本文目录导读:

Java短链接生成案例

  1. 方案一:基于哈希+Base62编码(最常用)
  2. 方案二:完整的短链接服务类
  3. 方案三:使用外部库(Guava)
  4. 使用说明

我来提供一个完整的Java短链接生成案例,包含多种实现方案。

基于哈希+Base62编码(最常用)

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class ShortUrlGenerator {
    // 62个字符的字符表
    private static final String BASE62_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static final int BASE = 62;
    // 存储在内存中的映射(实际应用中用数据库/Redis)
    private static Map<String, String> shortToLongMap = new HashMap<>();
    private static Map<String, String> longToShortMap = new HashMap<>();
    /**
     * 方法1:基于MD5哈希生成短链接
     */
    public static String generateByMD5(String longUrl, String salt) {
        try {
            // 添加随机盐值避免碰撞
            String input = longUrl + salt;
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            byte[] digest = md5.digest(input.getBytes());
            // 取前8个字节
            StringBuilder hexString = new StringBuilder();
            for (int i = 0; i < 8; i++) {
                hexString.append(String.format("%02x", digest[i]));
            }
            // 将16进制转成62进制
            String hexStringStr = hexString.toString();
            StringBuilder shortUrl = new StringBuilder();
            for (int i = 0; i < 6; i++) {  // 生成6位短码
                shortUrl.append(hexToChar(hexStringStr.substring(i * 2, i * 2 + 2)));
            }
            return shortUrl.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 algorithm not found", e);
        }
    }
    /**
     * 方法2:基于UUID生成
     */
    public static String generateByUUID() {
        return UUID.randomUUID().toString().replace("-", "")
                .substring(0, 8)  // 取8位
                .toUpperCase();
    }
    /**
     * 方法3:基于自增ID+Base62编码
     */
    private static long counter = 1000000000L; // 模拟自增ID
    public static synchronized String generateByIncrement() {
        return convertToBase62(counter++);
    }
    /**
     * 方法4:基于32位整数+Base62编码
     */
    public static String generateByRandom() {
        int randomInt = Math.abs(UUID.randomUUID().hashCode());
        return convertToBase62(randomInt);
    }
    /**
     * 将10进制转换为62进制
     */
    private static String convertToBase62(long number) {
        if (number == 0) {
            return "0";
        }
        StringBuilder result = new StringBuilder();
        while (number > 0) {
            int remainder = (int) (number % BASE);
            result.insert(0, BASE62_CHARS.charAt(remainder));
            number /= BASE;
        }
        return result.toString();
    }
    /**
     * 将16进制字符串转换为字符
     */
    private static char hexToChar(String hex) {
        int num = Integer.parseInt(hex, 16);
        return BASE62_CHARS.charAt(num % BASE);
    }
    /**
     * 保存映射关系
     */
    public static void saveMapping(String shortCode, String longUrl) {
        shortToLongMap.put(shortCode, longUrl);
        longToShortMap.put(longUrl, shortCode);
    }
    /**
     * 根据短码获取长URL
     */
    public static String getLongUrl(String shortCode) {
        return shortToLongMap.get(shortCode);
    }
    public static void main(String[] args) {
        String longUrl = "https://www.example.com/blog/article/123456789?page=1&size=10";
        // 测试各种生成方式
        System.out.println("原始URL: " + longUrl);
        System.out.println("长度: " + longUrl.length());
        // 1. MD5方式
        String md5ShortCode = generateByMD5(longUrl, "salt123");
        System.out.println("\nMD5方式生成: " + md5ShortCode);
        System.out.println("长度: " + md5ShortCode.length());
        saveMapping(md5ShortCode, longUrl);
        // 2. UUID方式
        String uuidShortCode = generateByUUID();
        System.out.println("\nUUID方式生成: " + uuidShortCode);
        System.out.println("长度: " + uuidShortCode.length());
        saveMapping(uuidShortCode, longUrl);
        // 3. 自增ID方式
        String incrementShortCode = generateByIncrement();
        System.out.println("\n自增ID方式生成: " + incrementShortCode);
        System.out.println("长度: " + incrementShortCode.length());
        saveMapping(incrementShortCode, longUrl);
        // 4. 随机数方式
        String randomShortCode = generateByRandom();
        System.out.println("\n随机数方式生成: " + randomShortCode);
        System.out.println("长度: " + randomShortCode.length());
        saveMapping(randomShortCode, longUrl);
        // 5. 完整的短链接
        String domain = "https://short.link/";
        System.out.println("\n完整短链接示例: " + domain + md5ShortCode);
        // 6. 验证反向解析
        System.out.println("\n反向解析测试:");
        System.out.println("短码 " + md5ShortCode + " -> " + getLongUrl(md5ShortCode));
        // 7. 批量生成测试
        System.out.println("\n批量生成测试:");
        for (int i = 0; i < 10; i++) {
            String code = generateByIncrement();
            System.out.println("生成短码 " + (i + 1) + ": " + code);
        }
    }
}

完整的短链接服务类

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
/**
 * 完整的短链接服务类
 */
public class ShortUrlService {
    // 基础配置
    private static final String DOMAIN = "https://short.url/";
    private static final int SHORT_CODE_LENGTH = 8;
    private static final long EXPIRATION_TIME = 30L * 24 * 60 * 60 * 1000; // 30天有效
    // 字符表
    private static final String ALPHABET = "BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789";
    private static final int BASE = ALPHABET.length();
    // 存储结构(实际应用使用Redis/数据库)
    private static final Map<String, UrlEntry> urlStore = new HashMap<>();
    // 防止重复生成的缓存
    private static final Map<String, String> longUrlCache = new HashMap<>();
    // URL实体类
    public static class UrlEntry {
        private final String longUrl;
        private final long createdAt;
        private final long expiresAt;
        private long accessCount;
        public UrlEntry(String longUrl) {
            this.longUrl = longUrl;
            this.createdAt = System.currentTimeMillis();
            this.expiresAt = this.createdAt + EXPIRATION_TIME;
            this.accessCount = 0;
        }
        public String getLongUrl() {
            return longUrl;
        }
        public long getCreatedAt() {
            return createdAt;
        }
        public long getExpiresAt() {
            return expiresAt;
        }
        public void incrementAccessCount() {
            this.accessCount++;
        }
        public long getAccessCount() {
            return accessCount;
        }
        public boolean isExpired() {
            return System.currentTimeMillis() > expiresAt;
        }
        @Override
        public String toString() {
            return String.format("UrlEntry{longUrl='%s', createdAt=%s, expiresAt=%s, accessCount=%d}",
                longUrl, new Date(createdAt), new Date(expiresAt), accessCount);
        }
    }
    /**
     * 生成短链接(完整流程)
     */
    public String createShortUrl(String longUrl) {
        // 检查是否已存在
        String existingCode = longUrlCache.get(longUrl);
        if (existingCode != null) {
            UrlEntry entry = urlStore.get(existingCode);
            if (entry != null && !entry.isExpired()) {
                return DOMAIN + existingCode;
            }
        }
        // 生成唯一短码
        String shortCode;
        int attempts = 0;
        do {
            shortCode = generateUniqueCode();
            attempts++;
        } while (urlStore.containsKey(shortCode) && attempts < 10);
        // 保存映射
        UrlEntry entry = new UrlEntry(longUrl);
        urlStore.put(shortCode, entry);
        longUrlCache.put(longUrl, shortCode);
        return DOMAIN + shortCode;
    }
    /**
     * 生成唯一短码
     */
    private String generateUniqueCode() {
        // 方法1:基于时间戳
        long timestamp = System.nanoTime();
        String timeBase62 = base62Encode(timestamp);
        // 方法2:加入随机数
        int random = ThreadLocalRandom.current().nextInt(0, BASE * BASE);
        String randomBase62 = base62Encode(random);
        // 组合并调整长度
        String combined = timeBase62 + randomBase62;
        // 如果太长,取前N位
        if (combined.length() > SHORT_CODE_LENGTH) {
            combined = combined.substring(0, SHORT_CODE_LENGTH);
        }
        // 如果太短,填充
        while (combined.length() < SHORT_CODE_LENGTH) {
            combined += base62Encode(ThreadLocalRandom.current().nextInt(BASE));
        }
        return combined;
    }
    /**
     * Base62编码
     */
    private String base62Encode(long number) {
        if (number == 0) {
            return String.valueOf(ALPHABET.charAt(0));
        }
        StringBuilder sb = new StringBuilder();
        while (number > 0) {
            int mod = (int) (number % BASE);
            sb.append(ALPHABET.charAt(mod));
            number /= BASE;
        }
        return sb.reverse().toString();
    }
    /**
     * 获取原始URL
     */
    public String getOriginalUrl(String shortCode) {
        if (shortCode != null && shortCode.startsWith(DOMAIN)) {
            shortCode = shortCode.substring(DOMAIN.length());
        }
        UrlEntry entry = urlStore.get(shortCode);
        if (entry == null) {
            return null;
        }
        if (entry.isExpired()) {
            urlStore.remove(shortCode);
            longUrlCache.remove(entry.getLongUrl());
            return null;
        }
        entry.incrementAccessCount();
        return entry.getLongUrl();
    }
    /**
     * 删除短链接
     */
    public boolean deleteShortUrl(String shortCode) {
        if (shortCode != null && shortCode.startsWith(DOMAIN)) {
            shortCode = shortCode.substring(DOMAIN.length());
        }
        UrlEntry entry = urlStore.remove(shortCode);
        if (entry != null) {
            longUrlCache.remove(entry.getLongUrl());
            return true;
        }
        return false;
    }
    /**
     * 获取统计数据
     */
    public UrlEntry getStats(String shortCode) {
        if (shortCode != null && shortCode.startsWith(DOMAIN)) {
            shortCode = shortCode.substring(DOMAIN.length());
        }
        return urlStore.get(shortCode);
    }
    /**
     * 清理过期链接
     */
    public int cleanExpiredUrls() {
        int count = 0;
        for (Map.Entry<String, UrlEntry> entry : urlStore.entrySet()) {
            if (entry.getValue().isExpired()) {
                urlStore.remove(entry.getKey());
                longUrlCache.remove(entry.getValue().getLongUrl());
                count++;
            }
        }
        return count;
    }
    /**
     * 生成二维码(如果需要的功能)
     */
    public void generateQRCode(String shortUrl) {
        // 使用zxing库等生成二维码
        // 这里只是示例,实际实现需要添加依赖
        System.out.println("生成二维码: " + shortUrl);
    }
    // 测试主方法
    public static void main(String[] args) {
        ShortUrlService service = new ShortUrlService();
        // 测试用例
        String[] urls = {
            "https://www.google.com/search?q=java+short+url",
            "https://stackoverflow.com/questions/12345678",
            "https://github.com/username/repository",
            "https://www.baidu.com/s?wd=短链接",
            "https://example.com/product/12345?ref=promo&utm_source=test"
        };
        System.out.println("=== 短链接生成测试 ===\n");
        for (String url : urls) {
            String shortUrl = service.createShortUrl(url);
            System.out.println("原始URL: " + url);
            System.out.println("短链接: " + shortUrl);
            // 解析测试
            String code = shortUrl.replace(DOMAIN, "");
            String original = service.getOriginalUrl(code);
            System.out.println("解析URL: " + original);
            System.out.println("解析成功: " + Objects.equals(url, original));
            // 测试重复创建
            String shortUrl2 = service.createShortUrl(url);
            System.out.println("重复创建: " + shortUrl2);
            System.out.println("是否相同: " + shortUrl.equals(shortUrl2));
            System.out.println("------------------------\n");
        }
        // 批量生成测试
        System.out.println("=== 并发批量生成测试 ===");
        service.testConcurrentCalls();
        // 统计信息
        System.out.println("\n=== 统计信息 ===");
        for (String url : urls) {
            String shortUrl = service.createShortUrl(url);
            String code = shortUrl.replace(DOMAIN, "");
            UrlEntry stats = service.getStats(code);
            if (stats != null) {
                System.out.println("URL: " + stats.getLongUrl());
                System.out.println("创建时间: " + new Date(stats.getCreatedAt()));
                System.out.println("过期时间: " + new Date(stats.getExpiresAt()));
                System.out.println("访问次数: " + stats.getAccessCount());
                System.out.println("------------------------");
            }
        }
    }
    /**
     * 并发测试
     */
    private void testConcurrentCalls() {
        Runnable task = () -> {
            for (int i = 0; i < 10; i++) {
                String url = "https://example.com/concurrent/" + 
                             Thread.currentThread().getName() + "/" + i;
                String shortUrl = createShortUrl(url);
                System.out.println("线程 " + Thread.currentThread().getName() + 
                                 " 生成了: " + shortUrl);
            }
        };
        Thread[] threads = new Thread[5];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(task, "Thread-" + (i + 1));
            threads[i].start();
        }
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

使用外部库(Guava)

import com.google.common.hash.Hashing;
import java.nio.charset.StandardCharsets;
public class GuavaShortUrlGenerator {
    private static final String DOMAIN = "https://short.url/";
    private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static final int BASE = ALPHABET.length();
    /**
     * 使用Guava的MurmurHash算法
     */
    public static String generateShortCode(String longUrl) {
        // 计算哈希值
        long hash = Hashing.murmur3_32_fixed()  // 或使用 murmur3_128
                .hashString(longUrl, StandardCharsets.UTF_8)
                .asInt();
        // 转换为正数
        long positiveHash = hash & Long.MAX_VALUE;
        // Base62编码
        StringBuilder shortCode = new StringBuilder();
        while (positiveHash > 0) {
            int index = (int) (positiveHash % BASE);
            shortCode.insert(0, ALPHABET.charAt(index));
            positiveHash /= BASE;
        }
        // 确保至少6位
        while (shortCode.length() < 6) {
            shortCode.insert(0, ALPHABET.charAt(0));
        }
        return shortCode.toString();
    }
    public static void main(String[] args) {
        String[] urls = {
            "https://www.google.com/search?q=java",
            "https://stackoverflow.com/questions",
            "https://github.com/username"
        };
        for (String url : urls) {
            String code = generateShortCode(url);
            System.out.println("URL: " + url);
            System.out.println("短码: " + code);
            System.out.println("短链接: " + DOMAIN + code);
            System.out.println("长度: " + code.length());
            System.out.println("------------------------");
        }
    }
}

使用说明

添加依赖(如果使用方案三)

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

生产环境建议

  • 使用Redis存储映射关系
  • 考虑使用分布式ID生成器(如Snowflake)
  • 添加缓存层(如Caffeine)
  • 实现数据库持久化
  • 设置过期时间
  • 添加访问统计

性能优化建议

  • 预生成ID池
  • 使用连接池
  • 批量操作
  • 异步处理

这个案例提供了多种生成方式和完整的服务实现,可以根据实际需求选择使用。

抱歉,评论功能暂时关闭!