发送邮件通知,嵌入链接跳转到系统后台页面,如果链接过长,消息体显示的时候会影响布局与美观,所以需要将原始的http长链接转为短链接显示,当点击短链接跳转时,采用301或302状态码的方式将短链重定向到长链。
1.将长链通过技术手段生成一个短链接去页面展示。
2.点击访问短链接,通过短链接服务去数据库找到对应长链接。
3.重定向跳转。
首先,我们需要建立一个表去映射长链接与短链接。
mysql表:
CREATE TABLE `t_url_mapping` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`lurl` varchar(255) DEFAULT NULL COMMENT '长地址',
`surl` varchar(12) DEFAULT NULL COMMENT '短地址',
`md5` char(32) DEFAULT NULL COMMENT '长链的加盐md5',
`createTime` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `t_url_mapping_surl_IDX` (`surl`) USING BTREE,
KEY `t_url_mapping_md5_IDX` (`md5`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8hgn5AGY-1661930146074)(C:\Users\cherish\AppData\Roaming\Typora\typora-user-images\image-20220831144615566.png)]
短链生成具体步骤:
1.根据长链(longUrl)进行计算,salt_md5=MD5(longUrl + salt),这里可以使用guava 的 MurmurHash 工具类。
guava依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
2.根据salt_md5去mysql查看有没有对应的longUrl_1。
3.如果有对应的longUrl_1,需要判断longUrl_1是否与longUrl相同,相同即返回,不相同继续往下执行。
4.从发号器获取全局唯一id。(可以使用redis,雪花算法,数据库自增序列)
5.生成短链:short_url = (id的62进制值) + “-” + salt_md5的前五位,62进制使用字符0-9,a-z,A-Z。
6.写入数据库。
具体实现代码:
@Component
public class ShortLinkGenerator {
private static final String CHARS_62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final int SCALE = 62;
private static final char SEPARATOR = '-';
private static final String SALT = "HelloWorld";
@Resource
private UrlMappingService urlMappingService;
@Resource
private SequenceGenerator sequenceGenerator;
/**
* 将数字转为62进制
*
* @param num Long 型数字
* @return 62进制字符串
*/
public static StringBuilder encode(long num) {
StringBuilder sb = new StringBuilder();
int remainder = 0;
while (num > SCALE - 1) {
/**
* 对 scale 进行求余,然后将余数追加至 sb 中,由于是从末位开始追加的,因此最后需要反转(reverse)字符串
*/
remainder = Long.valueOf(num % SCALE).intValue();
sb.append(CHARS_62.charAt(remainder));
num = num / SCALE;
}
sb.append(CHARS_62.charAt(Long.valueOf(num).intValue()));
return sb.reverse();
}
/**
* 生成短链的方法
* @param originalUri 原uri
* @return
*/
public String generate(String originalUri) {
// hash
HashCode hashCode = Hashing.murmur3_128().hashString(originalUri + SALT, StandardCharsets.UTF_8);
// 根据hash去数据库中查询是否有对应的记录
String md5 = hashCode.toString();
UrlMapping urlMapping = urlMappingService.queryByMd5(md5);
if (null != urlMapping) {
if (urlMapping.getLurl().equals(originalUri)) {
// 存在直接返回
return urlMapping.getSurl();
}
}
// 获取全局唯一id,这里可以采用redis自增,雪花算法等也可实现。
Long uniqueId = sequenceGenerator.nextValue("shortLink");
StringBuilder encode = ShortLinkGenerator.encode(uniqueId);
// 得到短链
StringBuilder shortUri = encode.append(ShortLinkGenerator.SEPARATOR).append(md5, 0, 4);
String result = shortUri.toString();
UrlMapping entity = UrlMapping.builder()
.surl(result)
.lurl(originalUri)
.md5(md5)
.createTime(LocalDateTime.now())
.build();
// 入库
urlMappingService.insert(entity);
return result;
}
}
@Slf4j
@RequestMapping("/")
@RestController
public class ShortUrlController {
@Resource
private UrlMappingMapper urlMappingMapper;
@GetMapping("/{shortUrl}")
public void redirectView(HttpServletResponse resp, @PathVariable("shortUrl") String shortUrl) {
// 查询对应长链
UrlMapping urlMapping = urlMappingMapper.queryBySurl(shortUrl);
String lurl = urlMapping.getLurl();
// 设置302状态码并重定向
resp.setStatus(302);
resp.setHeader("Location",lurl);
try {
resp.sendRedirect(lurl);
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
1.为什么加盐?
答:为了避免用id反解出md5字符串
2.为什么在生成短链时包含id值?
答:md5值存在hash碰撞,但是id值是全局唯一的,可以保证短链的全局唯一性。
3.既然id全局唯一,为什么不直接用id做短链?
答:只用id做短链,用户可以自己修改短链id,进而非法访问其他短链接。
4.为什么对id进行62进制编码?
答:为了使短链的长度更短。
:为了避免用id反解出md5字符串
2.为什么在生成短链时包含id值?
答:md5值存在hash碰撞,但是id值是全局唯一的,可以保证短链的全局唯一性。
3.既然id全局唯一,为什么不直接用id做短链?
答:只用id做短链,用户可以自己修改短链id,进而非法访问其他短链接。
4.为什么对id进行62进制编码?
答:为了使短链的长度更短。