您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# 怎么用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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。