# 怎么用Java設計一個短鏈接生成系統
## 目錄
1. [系統概述](#系統概述)
2. [核心功能需求](#核心功能需求)
3. [技術選型](#技術選型)
4. [數據庫設計](#數據庫設計)
5. [核心算法實現](#核心算法實現)
6. [系統架構設計](#系統架構設計)
7. [詳細代碼實現](#詳細代碼實現)
8. [性能優化](#性能優化)
9. [安全考慮](#安全考慮)
10. [擴展功能](#擴展功能)
---
## 系統概述
短鏈接系統是將長URL轉換為短字符串的服務(如bit.ly/abc123),具有:
- 節省字符空間(短信/社交媒體場景)
- 便于統計訪問數據
- 隱藏原始URL等優勢
典型技術指標要求:
- 每秒千級寫入能力
- 毫秒級響應時間
- 99.9%可用性
---
## 核心功能需求
| 功能模塊 | 詳細說明 |
|----------------|--------------------------------------------------------------------------|
| URL縮短 | 將長URL轉換為6-8字符的短碼 |
| URL重定向 | 訪問短鏈接時302跳轉到原始URL |
| 自定義短碼 | 允許用戶指定特定短碼(需校驗唯一性) |
| 訪問統計 | 記錄訪問時間、IP、UserAgent等信息 |
| 有效期控制 | 支持設置永久/臨時鏈接 |
---
## 技術選型
```mermaid
graph TD
A[Spring Boot] --> B[Web MVC]
A --> C[Spring Data JPA]
D[Redis] --> E[緩存熱點數據]
F[MySQL] --> G[持久化存儲]
H[Zookeeper] --> I[分布式ID生成]
選型理由: - Spring Boot:快速構建RESTful服務 - Redis:應對高并發查詢(10萬+ QPS) - MySQL:可靠持久化存儲 - Zookeeper:分布式環境協調
CREATE TABLE `short_url` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`short_code` varchar(8) NOT NULL UNIQUE,
`original_url` varchar(2048) NOT NULL,
`create_time` datetime NOT NULL,
`expire_time` datetime DEFAULT NULL,
`creator_ip` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `idx_short_code` (`short_code`)
);
CREATE TABLE `access_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`short_code` varchar(8) NOT NULL,
`access_time` datetime NOT NULL,
`user_agent` varchar(512) DEFAULT NULL,
`ip_address` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `idx_code_time` (`short_code`, `access_time`)
);
方案 | 優點 | 缺點 |
---|---|---|
自增ID+Base62 | 無碰撞風險 | 需暴露數字ID |
Hash算法 | 分布式友好 | 可能沖突(需解決碰撞) |
預生成池 | 高性能 | 需要維護池狀態 |
推薦實現:
// Base62編碼示例
public class Base62Encoder {
private static final String CHARACTERS =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static String encode(long num) {
StringBuilder sb = new StringBuilder();
while (num > 0) {
sb.insert(0, CHARACTERS.charAt((int)(num % 62)));
num /= 62;
}
return sb.toString();
}
}
// 使用Snowflake生成分布式ID
public class ShortCodeGenerator {
public String generate() {
long id = Snowflake.nextId(); // 分布式ID
return Base62Encoder.encode(id);
}
}
sequenceDiagram
participant Client
participant API
participant Cache
participant DB
Client->>API: POST /api/shorten (原始URL)
API->>DB: 保存映射關系
DB-->>API: 返回短碼
API->>Cache: 寫入緩存
API-->>Client: 返回短鏈接
Client->>API: GET /s/短碼
API->>Cache: 查詢原始URL
alt 緩存命中
Cache-->>API: 返回URL
else 緩存未命中
API->>DB: 查詢原始URL
DB-->>API: 返回URL
API->>Cache: 回填緩存
end
API-->>Client: 302重定向
@RestController
@RequestMapping("/api")
public class ShortUrlController {
@Autowired
private ShortUrlService service;
@PostMapping("/shorten")
public ResponseEntity<ShortUrlResponse> createShortUrl(
@RequestBody ShortenRequest request) {
String shortCode = service.createShortUrl(
request.getUrl(),
request.getCustomCode());
return ResponseEntity.ok(
new ShortUrlResponse(shortCode));
}
@GetMapping("/s/{code}")
public void redirect(
@PathVariable String code,
HttpServletResponse response) throws IOException {
String originalUrl = service.getOriginalUrl(code);
response.sendRedirect(originalUrl);
}
}
@Service
public class ShortUrlServiceImpl implements ShortUrlService {
@Autowired
private ShortUrlRepository repository;
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String CACHE_PREFIX = "short_url:";
private static final int MAX_RETRY = 3;
@Override
@Transactional
public String createShortUrl(String originalUrl, String customCode) {
// 校驗URL合法性
validateUrl(originalUrl);
String shortCode;
if (StringUtils.isNotBlank(customCode)) {
// 自定義短碼處理
if (repository.existsByShortCode(customCode)) {
throw new BusinessException("短碼已被占用");
}
shortCode = customCode;
} else {
// 自動生成短碼(帶重試機制)
int retryCount = 0;
do {
shortCode = ShortCodeGenerator.generate();
retryCount++;
} while (repository.existsByShortCode(shortCode)
&& retryCount < MAX_RETRY);
}
// 持久化存儲
ShortUrl entity = new ShortUrl();
entity.setOriginalUrl(originalUrl);
entity.setShortCode(shortCode);
entity.setCreateTime(LocalDateTime.now());
repository.save(entity);
// 寫入緩存
redisTemplate.opsForValue().set(
CACHE_PREFIX + shortCode,
originalUrl,
7, TimeUnit.DAYS);
return shortCode;
}
}
緩存策略:
// Spring Cache配置示例
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory()),
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.disableCachingNullValues()
);
}
}
批量處理:
// 使用Spring Batch進行日志批量插入
@Bean
public JdbcBatchItemWriter<AccessLog> logWriter() {
return new JdbcBatchItemWriterBuilder<AccessLog>()
.sql("INSERT INTO access_log VALUES (...)")
.dataSource(dataSource)
.build();
}
@GetMapping(“/s/{code}”) public ResponseEntity<?> redirect(…) { if (!limiter.tryAcquire()) { throw new TooManyRequestsException(); } // … }
2. **敏感操作審計**:
```java
@Aspect
@Component
public class AuditAspect {
@AfterReturning("execution(* com..ShortUrlService.create*(..))")
public void auditLog(JoinPoint jp) {
// 記錄操作日志
}
}
可視化面板:
// 使用ECharts展示訪問趨勢
myChart.setOption({
xAxis: { data: ['Mon', 'Tue', 'Wed'] },
series: [{ data: [120, 200, 150] }]
});
API限流方案對比:
方案 | 實現復雜度 | 精確度 |
---|---|---|
令牌桶 | 中 | 高 |
固定窗口 | 低 | 低 |
滑動日志 | 高 | 極高 |
最佳實踐建議: 1. 生產環境建議使用分布式Redis集群 2. 短碼長度建議6-8字符(62^6≈568億組合) 3. 重要業務鏈接建議使用HTTPS 4. 定期歸檔冷數據到對象存儲 “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。