前言
短链接系统是一种将较长的URL(统一资源定位符)转换为较短的URL的服务。这种服务通常被用于URL分享,因为较短的URL更加方便用户复制和粘贴,也更容易在社交媒体和其他在线平台分享。本文使用了SpringBoot开发了一个简易的短链接转换接口,和短链接重定向接口。
一、短链接系统入门🍉
1. 什么是短链接系统?
短链接系统是一种将较长的URL转换成较短URL的服务。当用户点击短链接时,他们会被重定向到原始URL。短链接系统在社交媒体平台(如微博)上特别有用,因为在这些平台上,限制了可以发布的文字数量。使用短链接服务可以节省空间,使URL更短,更方便用户输入。
短链接有什么优势:
2. 准备工作
(1)创建一个maven项目
(2)引入相关依赖
继承spring boot parent项目
org.springframework.boot
spring-boot-starter-parent
3.1.0
引入spring boot maven插件
org.springframework.boot
spring-boot-maven-plugin
引入spring boot提供的starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
引入lombok插件
org.projectlombok
lombok
true
引入ORM框架JPA
org.springframework.boot
spring-boot-starter-data-jpa
引入Google开源的Java库
com.google.guava
guava
30.1.1-jre
增加application.yaml配置文件
server:
port: 8888
spring:
application:
name: shorten-service
datasource:
url: jdbc:mysql://localhost:3306/shorten_db?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
username: root
password: xxxx #修改成自己的密码
jpa:
hibernate:
ddl-auto: create-drop
properties:
hibernate:
show_sql: true
format_sql: true
(3)创建启动类
package org.shortenservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ShortenServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ShortenServiceApplication.class, args);
}
}
(4)自定义RESTful结果封装类
public class ResponseResult {
private String code;
private String msg;
private T data;
}
(5)创建响应工具类
package org.shortenservice.common;
public class ResultUtils {
private ResultUtils() {}
public static ResponseResult success(T data) {
return build("200", "success", data);
}
public static ResponseResult success() {
return build("200", "success", null);
}
public static boolean isSuccess(String code) {
return "200".equals(code);
}
public static ResponseResult failure(String msg) {
return build("500", msg, null);
}
public static ResponseResult failure(String code, String msg) {
return build(code, msg, null);
}
public static ResponseResult failure(String code, String msg, T data) {
return build(code, msg, data);
}
public static ResponseResult build(String code, String msg, T data) {
return new ResponseResult(code, msg, data);
}
}
二、核心功能实现🧁
1. 实现Base62编码
package org.shortenservice.utils;
public class Base62Utils {
private static final String BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private Base62Utils() {
}
public static String idToShortKey(long id) {
StringBuilder stringBuilder = new StringBuilder();
while (id > 0) {
stringBuilder.append(BASE62.charAt((int) (id % 62)));
id = id / 62;
}
while (stringBuilder.length() < 6) {
stringBuilder.append(0);
}
return stringBuilder.reverse().toString();
}
public static long shortKeyToId(String shortKey) {
long id = 0;
for (int i = 0; i < shortKey.length(); i++) {
id = id * 62 + BASE62.indexOf(shortKey.charAt(i));
}
return id;
}
}
方法解释
idToShortKey方法:
shortKeyToId方法:
2. 创建实体类
package org.shortenservice.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import java.time.Instant;
@Entity
@Table(name = "t_url_map", indexes = {@Index(columnList = "longUrl", unique = true),
@Index(columnList = "expireTime", unique = false)})
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UrlMap {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String longUrl;
private Instant expireTime;
@CreationTimestamp
private Instant creationTime;
}
3. 创建Dao层
package com.shorten.dao;
import com.shorten.model.UrlMap;
import org.springframework.data.repository.CrudRepository;
import java.time.Instant;
import java.util.List;
public interface UrlMapDao extends CrudRepository {
UrlMap findFirstByLongUrl(String longUrl);
List findByExpireTimeBefore(Instant instant);
}
4. 创建service层
package org.shortenservice.service;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.shortenservice.dao.UrlMapDao;
import org.shortenservice.model.UrlMap;
import org.shortenservice.utils.Base62Utils;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
@Service
@Slf4j
public class UrlMapService {
@Resource
UrlMapDao urlMapDao;
public String encode(String longUrl) {
UrlMap urlMap = urlMapDao.findFirstByLongUrl(longUrl);
if (urlMap == null) {
urlMap = urlMapDao.save(UrlMap.builder()
.longUrl(longUrl)
.expireTime(Instant.now().plus(30, ChronoUnit.DAYS))
.build());
log.info("create urlMap:{}", urlMap);
}
return Base62Utils.idToShortKey(urlMap.getId());
}
public Optional decode(String shortKey) {
long id = Base62Utils.shortKeyToId(shortKey);
return urlMapDao.findById(id).map(UrlMap::getLongUrl);
}
}
5. 编写测试接口
package org.shortenservice.controller;
import jakarta.annotation.Resource;
import org.shortenservice.common.ResponseResult;
import org.shortenservice.common.ResultUtils;
import org.shortenservice.service.UrlMapService;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;
import java.util.Map;
@RestController
public class UrlMapController {
private static final String DOMAIN = "http://127.0.0.1:8888/";
@Resource
private UrlMapService urlMapService;
/***
* 长链接转短链接
* @param longUrl 长链接
* @return ResponseResult
*/
@PostMapping("/shorten")
public ResponseResult shorten(@RequestParam("longUrl") String longUrl) {
String encode = urlMapService.encode(longUrl);
return ResultUtils.success(Map.of("shortKey", encode,
"shortUrl", DOMAIN + encode));
}
/***
* 短链接重定向
* @param shortKey 短链接
* @return RedirectView
*/
@GetMapping("/{shortKey}")
public RedirectView redirect(@PathVariable("shortKey") String shortKey) {
return urlMapService.decode(shortKey).map(RedirectView::new)
.orElse(new RedirectView("/sorry"));
}
@GetMapping("/sorry")
public String sorry() {
return "抱歉,未找到页面!";
}
}
6. 使用curl测试
#将长链接转为短链接
curl -XPOST "localhost:8888/shorten?longUrl=https://i.csdn.net/#/user-center/profile?spm=1011.2415.3001.5111"
#访问短链接重定向到目标网站
curl -i "http://127.0.0.1:8888/000003"
数据库中也存储了相关数据
三、系统优化🍱
1. 缓存简介
什么是缓存
缓存(Caching)是一种用于提高数据处理速度的技术,涉及到了计算机硬件、操作系统、应用程序等多个领域。缓存的主要原理是将经常使用或最近使用的数据存储在快速访问的存储设备中,这样在需要这些数据时,就可以更快地获取到它们,从而提高系统的整体性能。
在计算机科学中,缓存通常指的是存储临时数据的地方,这些数据可能是来自于计算过程中的结果,也可能是来自于磁盘、网络等慢速存储设备的数据副本。缓存中的数据通常是根据一定的算法进行管理和替换的,以确保缓存中的数据是最需要或最常用的。
引入的缓存用于解决哪些问题
- 高并发访问: 在高并发情况下,数据源可能会受到过大的负载,通过缓存可以减轻数据源的压力,提高系统的性能和响应速度。
- 频繁访问: 对于频繁被访问的数据,通过缓存可以减少重复的数据读取,提高效率。
- 数据计算: 对于一些需要复杂计算的数据,将计算结果缓存起来可以节省计算时间和资源。
- 数据共享: 缓存可以在不同的组件、模块或服务之间共享数据,提高数据的可用性和共享性。
- 离线访问: 缓存可以在断网或无法连接数据源的情况下,仍然提供某些数据的访问能力。
本地缓存 VS 分布式缓存
分布式缓存:
概念: 分布式缓存是一种将缓存数据分布在多个服务器节点上的缓存系统,用于存储和管理大量的数据。
优点:
- 可扩展性: 分布式缓存可以通过增加节点来实现水平扩展,以应对大规模的数据和高并发访问。
- 高可用性: 分布式缓存通常采用复制和备份机制,确保即使有节点故障,仍然能够提供可靠的缓存服务。
- 跨节点共享: 多个应用实例可以共享同一分布式缓存,提高数据共享和协作能力。
- 灵活的存储后端: 分布式缓存可以支持多种后端存储,如内存、磁盘、数据库等。
缺点:
-
复杂性: 部署、配置和管理分布式缓存系统可能较为复杂,需要考虑分布式系统的一些挑战,如一致性、网络延迟等。
-
性能开销: 分布式缓存通常需要在网络上进行数据传输,可能引入一些性能开销。
本地缓存:
概念: 本地缓存是将缓存数据存储在应用程序的本地内存中,用于临时保存常用的数据。
优点:
- 简单性: 本地缓存相对较简单,不需要搭建额外的分布式缓存系统。
- 低延迟: 由于数据存储在本地内存中,本地缓存通常具有低延迟的读取速度。
- 少量数据: 本地缓存适用于存储相对较小的数据量,不需要进行分布式存储和管理。
缺点:
- 有限的扩展性: 本地缓存只能在单个应用实例内使用,无法满足多实例和分布式应用的需求。
- 数据一致性: 不同应用实例的本地缓存可能存在数据不一致的问题,需要额外的机制来解决。
2. 引入Guava
更新service层的代码
package org.shortenservice.service;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.shortenservice.dao.UrlMapDao;
import org.shortenservice.model.UrlMap;
import org.shortenservice.utils.Base62Utils;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
@Service
@Slf4j
public class UrlMapService {
@Resource
UrlMapDao urlMapDao;
@Resource
LoadingCache loadingCache;
@PostConstruct
public void init() {
CacheLoader cacheLoader = new CacheLoader() {
@Override
public String load(String s) throws Exception {
long id = Base62Utils.shortKeyToId(s);
log.info("load cache: {}", s);
return urlMapDao.findById(id).map(UrlMap::getLongUrl).orElse(null);
}
};
loadingCache = CacheBuilder.newBuilder()
.maximumSize(1000000) // 设置最大缓存大小
.build(cacheLoader);
}
public String encode(String longUrl) {
UrlMap urlMap = urlMapDao.findFirstByLongUrl(longUrl);
if (urlMap == null) {
urlMap = urlMapDao.save(UrlMap.builder()
.longUrl(longUrl)
.expireTime(Instant.now().plus(30, ChronoUnit.DAYS))
.build());
log.info("create urlMap:{}", urlMap);
}
return Base62Utils.idToShortKey(urlMap.getId());
}
public Optional decode(String shortKey) {
return Optional.ofNullable(loadingCache.getUnchecked(shortKey));
}
}
提示:由于短链接系统通常需要处理大量的用户请求和数据,因此需要具有高效和可扩展性。同时,由于短链接可能涉及到用户隐私和安全问题,短链接系统也需要符合相关的数据保护和安全标准。