本文目录导读:

我将为您实现一个完整的短链接服务案例,包含核心功能和使用示例。
核心短链接生成器
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;
}
}
这个完整的短链接服务实现包含了:
- 三种生成算法:自增ID法、MD5哈希法、随机数法
- 内存和数据库存储两种方案
- 完整的CRUD操作和统计功能
- 并发安全设计
- 过期机制和缓存优化
可以根据实际需求选择合适的实现方案,对于小型项目,使用内存版本即可;对于生产环境,建议使用数据库版本配合适当的缓存策略。