本文目录导读:

我来介绍几种Java实现短链接的方式,从简单到完整方案:
基础实现:哈希+进制转换
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SimpleShortUrl {
private static final String BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/**
* 将10进制数转为62进制
*/
public static String toBase62(long num) {
StringBuilder sb = new StringBuilder();
while (num > 0) {
sb.append(BASE62.charAt((int)(num % 62)));
num /= 62;
}
return sb.reverse().toString();
}
/**
* 生成短链接
*/
public static String generateShortUrl(String longUrl) {
try {
// 使用MD5生成哈希
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(longUrl.getBytes());
byte[] digest = md5.digest();
// 取前6位作为短码
long hash = 0;
for (int i = 0; i < 6; i++) {
hash = (hash << 8) | (digest[i] & 0xFF);
}
// 转为62进制
return toBase62(hash);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
String url = "https://www.example.com/very/long/url/path";
String shortCode = generateShortUrl(url);
System.out.println("原始URL: " + url);
System.out.println("短链接码: " + shortCode);
System.out.println("短链接: https://short.url/" + shortCode);
}
}
自增ID实现(防止冲突)
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class IncrementShortUrl {
private static final String BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private final AtomicLong counter = new AtomicLong(1000000);
private final ConcurrentHashMap<String, String> urlMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, String> shortMap = new ConcurrentHashMap<>();
/**
* 生成短链接
*/
public String shorten(String longUrl) {
// 检查是否已存在
String existing = urlMap.get(longUrl);
if (existing != null) {
return existing;
}
// 生成新ID并转换
long id = counter.incrementAndGet();
String shortCode = toBase62(id);
// 存储映射关系
urlMap.put(longUrl, shortCode);
shortMap.put(shortCode, longUrl);
return shortCode;
}
/**
* 根据短码获取原始URL
*/
public String getOriginalUrl(String shortCode) {
return shortMap.get(shortCode);
}
private String toBase62(long num) {
StringBuilder sb = new StringBuilder();
do {
sb.insert(0, BASE62.charAt((int)(num % 62)));
num /= 62;
} while (num > 0);
return sb.toString();
}
public static void main(String[] args) {
IncrementShortUrl shortUrl = new IncrementShortUrl();
String url1 = "https://www.example.com/article/2024/01/15/how-to-create-short-url";
String url2 = "https://www.example.com/product/12345/details";
System.out.println("短链接1: " + shortUrl.shorten(url1));
System.out.println("短链接2: " + shortUrl.shorten(url2));
// 还原
System.out.println("还原1: " + shortUrl.getOriginalUrl("4C92"));
System.out.println("还原2: " + shortUrl.getOriginalUrl("4C93"));
}
}
完整Spring Boot实现
1 数据库表结构
CREATE TABLE `short_url` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`short_code` varchar(10) NOT NULL COMMENT '短链接码',
`long_url` varchar(500) NOT NULL COMMENT '原始URL',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`expire_time` datetime DEFAULT NULL,
`click_count` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_short_code` (`short_code`),
KEY `idx_long_url` (`long_url`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2 实体类
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "short_url")
public class ShortUrl {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "short_code", unique = true, length = 10)
private String shortCode;
@Column(name = "long_url", length = 500)
private String longUrl;
@Column(name = "create_time")
private LocalDateTime createTime;
@Column(name = "expire_time")
private LocalDateTime expireTime;
@Column(name = "click_count")
private Integer clickCount = 0;
// getters and setters
}
3 服务层实现
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.Optional;
@Service
public class ShortUrlService {
@Autowired
private ShortUrlRepository repository;
private static final String BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final int SHORT_CODE_LENGTH = 6;
private SecureRandom random = new SecureRandom();
@Transactional
public String createShortUrl(String longUrl, int expireDays) {
// 检查是否已存在
Optional<ShortUrl> existing = repository.findByLongUrl(longUrl);
if (existing.isPresent()) {
return existing.get().getShortCode();
}
// 生成短码
String shortCode = generateShortCode();
ShortUrl shortUrl = new ShortUrl();
shortUrl.setShortCode(shortCode);
shortUrl.setLongUrl(longUrl);
shortUrl.setCreateTime(LocalDateTime.now());
shortUrl.setClickCount(0);
if (expireDays > 0) {
shortUrl.setExpireTime(LocalDateTime.now().plusDays(expireDays));
}
repository.save(shortUrl);
return shortCode;
}
public String getLongUrl(String shortCode) {
Optional<ShortUrl> shortUrl = repository.findByShortCode(shortCode);
if (shortUrl.isPresent()) {
ShortUrl url = shortUrl.get();
// 检查是否过期
if (url.getExpireTime() != null && url.getExpireTime().isBefore(LocalDateTime.now())) {
throw new RuntimeException("短链接已过期");
}
// 增加点击次数
repository.incrementClickCount(shortCode);
return url.getLongUrl();
}
throw new RuntimeException("短链接不存在");
}
private String generateShortCode() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < SHORT_CODE_LENGTH; i++) {
sb.append(BASE62.charAt(random.nextInt(62)));
}
// 检查是否已存在
String code = sb.toString();
if (repository.findByShortCode(code).isPresent()) {
return generateShortCode(); // 递归生成直到唯一
}
return code;
}
}
4 控制器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
@RestController
@RequestMapping("/api")
public class ShortUrlController {
@Autowired
private ShortUrlService shortUrlService;
@PostMapping("/shorten")
public ResponseEntity<?> createShortUrl(@RequestParam String url,
@RequestParam(defaultValue = "0") int expireDays) {
try {
String shortCode = shortUrlService.createShortUrl(url, expireDays);
String shortUrl = "http://localhost:8080/s/" + shortCode;
return ResponseEntity.ok(new ShortUrlResponse(shortUrl, shortCode));
} catch (Exception e) {
return ResponseEntity.badRequest().body("创建短链接失败: " + e.getMessage());
}
}
@GetMapping("/s/{shortCode}")
public ResponseEntity<?> redirect(@PathVariable String shortCode) {
try {
String longUrl = shortUrlService.getLongUrl(shortCode);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(longUrl));
return new ResponseEntity<>(headers, HttpStatus.FOUND);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
}
class ShortUrlResponse {
private String shortUrl;
private String shortCode;
public ShortUrlResponse(String shortUrl, String shortCode) {
this.shortUrl = shortUrl;
this.shortCode = shortCode;
}
// getters
}
5 性能优化版本(使用缓存)
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class CachedShortUrlService {
@Autowired
private ShortUrlRepository repository;
@Cacheable(value = "shortUrls", key = "#shortCode")
public String getLongUrl(String shortCode) {
ShortUrl shortUrl = repository.findByShortCode(shortCode)
.orElseThrow(() -> new RuntimeException("短链接不存在"));
if (shortUrl.getExpireTime() != null &&
shortUrl.getExpireTime().isBefore(LocalDateTime.now())) {
throw new RuntimeException("短链接已过期");
}
return shortUrl.getLongUrl();
}
@CacheEvict(value = "shortUrls", key = "#shortCode")
public void evictCache(String shortCode) {
// 清除缓存
}
}
雪花算法ID生成器
public class SnowflakeIdGenerator {
private final long workerId;
private final long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
private static final long WORKER_ID_BITS = 5L;
private static final long DATACENTER_ID_BITS = 5L;
private static final long SEQUENCE_BITS = 12L;
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
// 时序
protected long timeGen() {
return System.currentTimeMillis();
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << TIMESTAMP_LEFT_SHIFT) |
(datacenterId << DATACENTER_ID_SHIFT) |
(workerId << WORKER_ID_SHIFT) |
sequence;
}
}
使用建议
- 简单场景:使用自增ID或随机字符串
- 高并发场景:使用雪花算法或Redis生成ID
- 防冲突:使用数据库唯一索引+重试机制
- 性能优化:使用缓存(Redis/本地缓存)
- 统计分析:记录点击次数、来源等
这个实现方案可以根据你的具体需求进行调整和优化。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播