项目地址
https://github.com/AmosWang0626/short-url
源码解析
短链接表结构
@Getter
@Setter
@Entity
@Table(name = "dev_short_url", indexes = {
@Index(columnList = "url"),
@Index(columnList = "fullUrl")
})
@Accessors(chain = true)
public class ShortUrlEntity extends BaseEntity {
/**
* 短链接
*/
@Column(nullable = false)
private String url;
/**
* 完整链接
*/
@Column(nullable = false)
private String fullUrl;
/**
* 过期时间-时间戳(-1表示永久有效)
*/
@Column(nullable = false)
private Long expireTime;
}
生成短链接
ShortUrlBusinessImpl#save
,判断短链是否存在,补充数据的完整性,生成短链接。
@Override
public ShortUrlVO save(ShortUrlForm form) {
ShortUrlEntity entity = new ShortUrlEntity();
entity.setFullUrl(form.getFullUrl());
entity.setExpireTime(-1L);
// 校验 full_url, 存在就直接返回
Optional<ShortUrlEntity> byFullUrl = shortUrlService.findByFullUrl(entity.getFullUrl());
if (byFullUrl.isPresent()) {
return parseVO(byFullUrl.get());
}
// 设置默认过期时间
if (form.getExpire() == null || form.getTimeUnit() == null) {
form.setExpire(-1);
}
// 设置过期时间(时间戳)
if (form.getExpire() != -1) {
LocalDateTime expireDateTime = form.getTimeUnit().setTime(LocalDateTime.now(), form.getExpire());
entity.setExpireTime(expireDateTime.toEpochSecond(ZoneOffset.UTC));
}
entity.setUrl(UniqueShortUrl.getShortUrl(entity.getFullUrl()));
shortUrlService.save(entity);
return parseVO(entity);
}
ShortUrlServiceImpl#save
,将有过期时间的数据存放到Redis中。
@Override
public void save(ShortUrlEntity entity) {
shortUrlDao.save(entity);
// 如果过期时间不为-1,则将其置入Redis
if (entity.getExpireTime() != -1) {
expireService.addExpireInfo(entity.getId(), entity.getExpireTime());
}
}
获取短链接
ShortUrlBusinessImpl#find
@Override
@Cacheable(value = "short:url", cacheManager = "caffeine", key = "'short_url_' + #key")
public String find(String key) {
return shortUrlService.find(key);
}
清除过期数据
ExpireServiceImpl#initExpireInfoJob
,按照时间查询过期的短链接,修改过期状态;即将过期的数据存放在Redis中。
@Override
public void initExpireInfoJob() {
List<ShortUrlEntity> deleteEntities = new ArrayList<>();
List<ShortUrlEntity> needInitExpireInfoEntities = new ArrayList<>();
Optional<List<ShortUrlEntity>> byExpireTime = shortUrlDao.findAllByExpireTime();
byExpireTime.ifPresent(shortUrlEntities -> shortUrlEntities.forEach(shortUrlEntity -> {
if (shortUrlEntity.getExpireTime() <= System.currentTimeMillis()) {
shortUrlEntity.setDeleteFlag(true);
deleteEntities.add(shortUrlEntity);
} else {
needInitExpireInfoEntities.add(shortUrlEntity);
}
}));
// 删除过期数据
shortUrlDao.saveAll(deleteEntities);
// 过期信息保存至 Redis
needInitExpireInfoEntities.forEach(entity -> redisTemplate.opsForZSet().add(SHORT_URL_EXPIRE, entity.getId(), entity.getExpireTime()));
}
数据统计
MonitorCaffeineController#caffeine
,使用caffeine
做统计数据。
@GetMapping("stats")
@ApiOperation("根据缓存key查询缓存监控信息")
public SingleResponse<Map<String, Object>> caffeine(@RequestParam String cacheName) {
CaffeineCache caffeineCache = (CaffeineCache) caffeine.getCache(cacheName);
if (caffeineCache == null) {
return SingleResponse.buildFailure("", String.format("缓存 [%s] 不存在 !!!", cacheName));
}
CacheStats stats = caffeineCache.getNativeCache().stats();
Map<String, Object> map = new HashMap<>(16);
map.put("请求次数", stats.requestCount());
map.put("命中次数", stats.hitCount());
map.put("未命中次数", stats.missCount());
map.put("加载成功次数", stats.loadSuccessCount());
map.put("加载失败次数", stats.loadFailureCount());
map.put("加载失败占比", stats.loadFailureRate());
map.put("加载总耗时", stats.totalLoadTime());
map.put("回收总次数", stats.evictionCount());
map.put("回收总权重", stats.evictionWeight());
return SingleResponse.of(map);
}
分库分表
主要是配置信息和JPA
,不作详细研究。分库分表后面单独写一篇原理分析。该开源项目使用的是最新的版本。
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.0.0-alpha</version>
</dependency>