Java实现短链接案例

wen java案例 2

本文目录导读:

Java实现短链接案例

  1. 核心短链接生成器
  2. 完整短链接服务类
  3. 数据库存储版本(使用JDBC)
  4. 使用示例
  5. 性能优化版本

我将为您实现一个完整的短链接服务案例,包含核心功能和使用示例。

核心短链接生成器

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class ShortLinkGenerator {
    // 短链接字符集
    private static final String BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final int BASE = 62;
    // 存储映射关系
    private static final Map<String, String> shortToLongMap = new ConcurrentHashMap<>();
    private static final Map<String, String> longToShortMap = new ConcurrentHashMap<>();
    // 自增ID
    private static final AtomicLong idGenerator = new AtomicLong(1000000);
    // 短链接前缀
    private static final String DOMAIN = "http://short.link/";
    /**
     * 方法1:基于自增ID生成短链接
     */
    public static String generateByID(String longUrl) {
        // 如果已经生成了,直接返回
        if (longToShortMap.containsKey(longUrl)) {
            return DOMAIN + longToShortMap.get(longUrl);
        }
        // 获取自增ID并转换为62进制
        long id = idGenerator.getAndIncrement();
        String shortCode = encode(id);
        // 存储映射关系
        shortToLongMap.put(shortCode, longUrl);
        longToShortMap.put(longUrl, shortCode);
        return DOMAIN + shortCode;
    }
    /**
     * 方法2:基于MD5+Hash生成短链接
     */
    public static String generateByMD5(String longUrl) {
        if (longToShortMap.containsKey(longUrl)) {
            return DOMAIN + longToShortMap.get(longUrl);
        }
        String md5 = getMD5(longUrl);
        String shortCode = md5.substring(0, 8);
        // 处理冲突
        while (shortToLongMap.containsKey(shortCode)) {
            shortCode = md5.substring(0, shortCode.length() + 1);
        }
        shortToLongMap.put(shortCode, longUrl);
        longToShortMap.put(longUrl, shortCode);
        return DOMAIN + shortCode;
    }
    /**
     * 方法3:基于随机数生成短链接
     */
    public static String generateByRandom(String longUrl) {
        if (longToShortMap.containsKey(longUrl)) {
            return DOMAIN + longToShortMap.get(longUrl);
        }
        String shortCode = generateRandomCode(6);
        // 处理冲突
        while (shortToLongMap.containsKey(shortCode)) {
            shortCode = generateRandomCode(6);
        }
        shortToLongMap.put(shortCode, longUrl);
        longToShortMap.put(longUrl, shortCode);
        return DOMAIN + shortCode;
    }
    /**
     * 将长链接转换为短链接代码
     */
    private static String encode(long num) {
        StringBuilder sb = new StringBuilder();
        while (num > 0) {
            sb.insert(0, BASE62.charAt((int) (num % BASE)));
            num = num / BASE;
        }
        return sb.toString();
    }
    /**
     * 生成MD5哈希值
     */
    private static String getMD5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] messageDigest = md.digest(input.getBytes());
            StringBuilder hexString = new StringBuilder();
            for (byte b : messageDigest) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) hexString.append('0');
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 生成随机短代码
     */
    private static String generateRandomCode(int length) {
        StringBuilder sb = new StringBuilder();
        java.util.Random random = new java.util.Random();
        for (int i = 0; i < length; i++) {
            int index = random.nextInt(BASE);
            sb.append(BASE62.charAt(index));
        }
        return sb.toString();
    }
    /**
     * 根据短代码获取原始URL
     */
    public static String getLongUrl(String shortCode) {
        if (shortCode.startsWith(DOMAIN)) {
            shortCode = shortCode.substring(DOMAIN.length());
        }
        return shortToLongMap.get(shortCode);
    }
    /**
     * 获取映射表大小
     */
    public static int getMapSize() {
        return shortToLongMap.size();
    }
}

完整短链接服务类

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ShortLinkService {
    // 短链接存储
    private final Map<String, LinkInfo> shortLinkMap = new ConcurrentHashMap<>();
    private final Map<String, String> longLinkMap = new ConcurrentHashMap<>();
    // 配置
    private final String domain;
    private final int shortCodeLength;
    private final boolean cacheEnabled;
    private static final String CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static final int BASE = 62;
    public ShortLinkService(String domain, int shortCodeLength, boolean cacheEnabled) {
        this.domain = domain;
        this.shortCodeLength = shortCodeLength;
        this.cacheEnabled = cacheEnabled;
    }
    /**
     * 链接信息类
     */
    public static class LinkInfo {
        private String longUrl;
        private Date createdAt;
        private Date expiresAt;
        private long clickCount;
        private Map<String, Integer> dailyClicks = new HashMap<>();
        public LinkInfo(String longUrl) {
            this.longUrl = longUrl;
            this.createdAt = new Date();
            this.clickCount = 0;
        }
        // Getters and Setters
        public String getLongUrl() { return longUrl; }
        public void setLongUrl(String longUrl) { this.longUrl = longUrl; }
        public Date getCreatedAt() { return createdAt; }
        public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; }
        public Date getExpiresAt() { return expiresAt; }
        public void setExpiresAt(Date expiresAt) { this.expiresAt = expiresAt; }
        public long getClickCount() { return clickCount; }
        public void incrementClickCount() { this.clickCount++; }
        public Map<String, Integer> getDailyClicks() { return dailyClicks; }
        public void incrementDailyClick() {
            String today = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new Date());
            dailyClicks.merge(today, 1, Integer::sum);
        }
    }
    /**
     * 生成短链接
     */
    public String createShortLink(String longUrl, Integer expirationDays) {
        // 检查长链接是否已存在
        if (cacheEnabled && longLinkMap.containsKey(longUrl)) {
            String existingShortCode = longLinkMap.get(longUrl);
            return domain + existingShortCode;
        }
        // 生成短代码
        String shortCode = generateShortCode();
        while (shortLinkMap.containsKey(shortCode)) {
            shortCode = generateShortCode();
        }
        // 创建链接信息
        LinkInfo info = new LinkInfo(longUrl);
        if (expirationDays != null && expirationDays > 0) {
            info.setExpiresAt(new Date(System.currentTimeMillis() + expirationDays * 24 * 60 * 60 * 1000));
        }
        // 存储映射关系
        shortLinkMap.put(shortCode, info);
        longLinkMap.put(longUrl, shortCode);
        return domain + shortCode;
    }
    /**
     * 解析短链接
     */
    public String resolveShortLink(String shortUrl) {
        String shortCode = extractShortCode(shortUrl);
        if (shortCode == null) return null;
        LinkInfo info = shortLinkMap.get(shortCode);
        if (info == null) return null;
        // 检查过期
        if (info.getExpiresAt() != null && info.getExpiresAt().before(new Date())) {
            return null; // 链接已过期
        }
        // 更新统计数据
        info.incrementClickCount();
        info.incrementDailyClick();
        return info.getLongUrl();
    }
    /**
     * 从URL中提取短代码
     */
    private String extractShortCode(String url) {
        if (url == null || url.isEmpty()) return null;
        // 如果包含域名,提取最后的短代码
        if (url.contains(domain)) {
            return url.substring(url.lastIndexOf('/') + 1);
        }
        // 如果只是短代码
        return url;
    }
    /**
     * 生成短代码
     */
    private String generateShortCode() {
        StringBuilder sb = new StringBuilder();
        java.util.Random random = new java.util.Random();
        for (int i = 0; i < shortCodeLength; i++) {
            int index = random.nextInt(BASE);
            sb.append(CHARACTERS.charAt(index));
        }
        return sb.toString();
    }
    /**
     * 获取链接信息
     */
    public LinkInfo getLinkInfo(String shortUrl) {
        String shortCode = extractShortCode(shortUrl);
        return shortCode != null ? shortLinkMap.get(shortCode) : null;
    }
    /**
     * 删除短链接
     */
    public boolean deleteShortLink(String shortUrl) {
        String shortCode = extractShortCode(shortUrl);
        LinkInfo info = shortLinkMap.remove(shortCode);
        if (info != null) {
            longLinkMap.remove(info.getLongUrl());
            return true;
        }
        return false;
    }
    /**
     * 获取统计信息
     */
    public String getStats(String shortUrl) {
        LinkInfo info = getLinkInfo(shortUrl);
        if (info == null) {
            return "短链接不存在";
        }
        StringBuilder stats = new StringBuilder();
        stats.append("原链接: ").append(info.getLongUrl()).append("\n");
        stats.append("创建时间: ").append(info.getCreatedAt()).append("\n");
        if (info.getExpiresAt() != null) {
            stats.append("过期时间: ").append(info.getExpiresAt()).append("\n");
        }
        stats.append("总点击量: ").append(info.getClickCount()).append("\n");
        stats.append("最近7天点击统计:\n");
        // 计算最近7天数据
        for (int i = 6; i >= 0; i--) {
            String date = new java.text.SimpleDateFormat("yyyy-MM-dd")
                .format(new Date(System.currentTimeMillis() - i * 24 * 60 * 60 * 1000));
            Integer clicks = info.getDailyClicks().get(date);
            stats.append(date).append(": ").append(clicks != null ? clicks : 0).append("\n");
        }
        return stats.toString();
    }
}

数据库存储版本(使用JDBC)

import java.sql.*;
import java.util.Optional;
public class DatabaseShortLinkService {
    private final String jdbcUrl;
    private final String username;
    private final String password;
    public DatabaseShortLinkService(String jdbcUrl, String username, String password) {
        this.jdbcUrl = jdbcUrl;
        this.username = username;
        this.password = password;
        initDatabase();
    }
    /**
     * 初始化数据库表
     */
    private void initDatabase() {
        String createTableSQL = """
            CREATE TABLE IF NOT EXISTS short_links (
                id INT AUTO_INCREMENT PRIMARY KEY,
                short_code VARCHAR(10) NOT NULL UNIQUE,
                long_url TEXT NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                expires_at TIMESTAMP NULL,
                click_count INT DEFAULT 0,
                INDEX idx_short_code (short_code),
                INDEX idx_long_url (long_url(255))
            )
            """;
        try (Connection conn = getConnection();
             Statement stmt = conn.createStatement()) {
            stmt.execute(createTableSQL);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /**
     * 获取数据库连接
     */
    private Connection getConnection() throws SQLException {
        return DriverManager.getConnection(jdbcUrl, username, password);
    }
    /**
     * 创建短链接
     */
    public String createShortLink(String longUrl, int expirationDays) {
        String shortCode = generateUniqueShortCode();
        String insertSQL = """
            INSERT INTO short_links (short_code, long_url, expires_at)
            VALUES (?, ?, ?)
            """;
        try (Connection conn = getConnection();
             PreparedStatement pstmt = conn.prepareStatement(insertSQL)) {
            pstmt.setString(1, shortCode);
            pstmt.setString(2, longUrl);
            if (expirationDays > 0) {
                pstmt.setTimestamp(3, new Timestamp(
                    System.currentTimeMillis() + expirationDays * 24 * 60 * 60 * 1000));
            } else {
                pstmt.setNull(3, Types.TIMESTAMP);
            }
            pstmt.executeUpdate();
            return shortCode;
        } catch (SQLException e) {
            throw new RuntimeException("创建短链接失败", e);
        }
    }
    /**
     * 查询长链接
     */
    public Optional<String> resolveShortLink(String shortCode) {
        String querySQL = """
            SELECT long_url FROM short_links 
            WHERE short_code = ? 
            AND (expires_at IS NULL OR expires_at > NOW())
            """;
        try (Connection conn = getConnection();
             PreparedStatement pstmt = conn.prepareStatement(querySQL)) {
            pstmt.setString(1, shortCode);
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                // 增加点击量
                incrementClickCount(shortCode);
                return Optional.of(rs.getString("long_url"));
            }
        } catch (SQLException e) {
            throw new RuntimeException("查询短链接失败", e);
        }
        return Optional.empty();
    }
    /**
     * 增加点击量
     */
    private void incrementClickCount(String shortCode) {
        String updateSQL = "UPDATE short_links SET click_count = click_count + 1 WHERE short_code = ?";
        try (Connection conn = getConnection();
             PreparedStatement pstmt = conn.prepareStatement(updateSQL)) {
            pstmt.setString(1, shortCode);
            pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /**
     * 生成唯一的短代码
     */
    private String generateUniqueShortCode() {
        String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        StringBuilder sb = new StringBuilder();
        java.util.Random random = new java.util.Random();
        // 生成6位短代码
        for (int i = 0; i < 6; i++) {
            sb.append(characters.charAt(random.nextInt(62)));
        }
        return sb.toString();
    }
    /**
     * 删除过期的短链接
     */
    public void deleteExpiredLinks() {
        String deleteSQL = "DELETE FROM short_links WHERE expires_at < NOW()";
        try (Connection conn = getConnection();
             Statement stmt = conn.createStatement()) {
            stmt.executeUpdate(deleteSQL);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

使用示例

public class ShortLinkDemo {
    public static void main(String[] args) {
        // 示例1:使用基础短链接生成器
        System.out.println("=== 基础短链接生成器 ===");
        String longUrl1 = "https://www.example.com/articles/2024/product-launch/announcement/details";
        String shortUrl1 = ShortLinkGenerator.generateByID(longUrl1);
        System.out.println("原始URL: " + longUrl1);
        System.out.println("短链接ID法: " + shortUrl1);
        String shortUrl2 = ShortLinkGenerator.generateByMD5(longUrl1);
        System.out.println("短链接MD5法: " + shortUrl2);
        String shortUrl3 = ShortLinkGenerator.generateByRandom(longUrl1);
        System.out.println("短链接随机法: " + shortUrl3);
        // 解析短链接
        String shortCode = shortUrl1.substring(shortUrl1.lastIndexOf('/') + 1);
        System.out.println("解析短链接: " + ShortLinkGenerator.getLongUrl(shortCode));
        System.out.println("\n=== 高级短链接服务 ===");
        // 示例2:使用高级短链接服务
        ShortLinkService service = new ShortLinkService(
            "https://s.example.com/", 
            8, 
            true
        );
        String longUrl = "https://www.example.com/very/long/path/to/article?id=123&category=tech";
        // 创建短链接(7天过期)
        String shortUrl = service.createShortLink(longUrl, 7);
        System.out.println("创建短链接: " + shortUrl);
        // 模拟访问
        String resolvedUrl = service.resolveShortLink(shortUrl);
        System.out.println("解析得到: " + resolvedUrl);
        // 再次访问,点击量增加
        service.resolveShortLink(shortUrl);
        // 查看统计信息
        System.out.println("\n统计信息:");
        System.out.println(service.getStats(shortUrl));
        // 示例3:数据库版本
        System.out.println("\n=== 数据库版本示例 ===");
        // 数据库配置
        String jdbcUrl = "jdbc:mysql://localhost:3306/urldb";
        String dbUser = "root";
        String dbPassword = "password";
        // 创建服务(需要MySQL驱动)
        try {
            DatabaseShortLinkService dbService = new DatabaseShortLinkService(
                jdbcUrl, dbUser, dbPassword
            );
            String dbShortCode = dbService.createShortLink(longUrl, 30);
            System.out.println("数据库生成的短代码: " + dbShortCode);
            Optional<String> dbResolvedUrl = dbService.resolveShortLink(dbShortCode);
            dbResolvedUrl.ifPresent(url -> 
                System.out.println("数据库解析结果: " + url)
            );
            // 清理过期链接
            dbService.deleteExpiredLinks();
        } catch (Exception e) {
            System.out.println("数据库操作失败(可能需要配置MySQL)");
        }
    }
}

性能优化版本

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class OptimizedShortLinkService {
    // 使用读写锁提高并发性能
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    // 使用LRU缓存(简化版)
    private final int maxCacheSize = 10000;
    private final Map<String, String> cache = new java.util.LinkedHashMap<>(maxCacheSize, 0.75f, true) {
        @Override
        protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
            return size() > maxCacheSize;
        }
    };
    // 自增ID
    private final AtomicLong counter = new AtomicLong();
    // 短链接算法版本
    public String generateShortLink(String longUrl) {
        // 先检查缓存(读操作)
        readLock.lock();
        try {
            if (cache.containsKey(longUrl)) {
                return cache.get(longUrl);
            }
        } finally {
            readLock.unlock();
        }
        // 生成新链接(写操作)
        writeLock.lock();
        try {
            String shortCode = convertBase62(counter.incrementAndGet());
            cache.put(longUrl, shortCode);
            return shortCode;
        } finally {
            writeLock.unlock();
        }
    }
    /**
     * 转换为62进制
     */
    private String convertBase62(long number) {
        String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuilder sb = new StringBuilder();
        while (number > 0) {
            sb.insert(0, chars.charAt((int) (number % 62)));
            number /= 62;
        }
        return sb.toString();
    }
    /**
     * 批量生成短链接
     */
    public List<String> batchGenerate(List<String> longUrls) {
        List<String> shortCodes = new ArrayList<>();
        long startId = counter.getAndAdd(longUrls.size());
        for (int i = 0; i < longUrls.size(); i++) {
            String shortCode = convertBase62(startId + i);
            cache.put(longUrls.get(i), shortCode);
            shortCodes.add(shortCode);
        }
        return shortCodes;
    }
}

这个完整的短链接服务实现包含了:

  1. 三种生成算法:自增ID法、MD5哈希法、随机数法
  2. 内存和数据库存储两种方案
  3. 完整的CRUD操作和统计功能
  4. 并发安全设计
  5. 过期机制和缓存优化

可以根据实际需求选择合适的实现方案,对于小型项目,使用内存版本即可;对于生产环境,建议使用数据库版本配合适当的缓存策略。

上一篇ZXing案例

下一篇百度OCR案例

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