Spring Boot 短链接系统实现
前言
短链接系统是一种将长URL转换为短URL的服务,方便用户分享和复制。本文使用了SpringBoot开发了一个简易的短链接转换接口,和短链接重定向接口。
一、短链接系统入门🍉
1. 什么是短链接系统?
短链接系统能够将长URL转换为短URL,用户点击后会被重定向至原始URL。这在社交媒体等字符限制的平台上尤为重要。
短链接的优势:
- 便捷分享:短链接更易于分享,外观更加美观。
- 提升用户体验:简化用户输入,减少访问链接时的操作。
- 推广与营销:跟踪广告点击和转化率,优化营销策略。
2. 准备工作
(1)创建 Maven 项目
(2)引入相关依赖
在 pom.xml
中添加以下依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
</dependencies>
增加 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)自定义响应结果封装类
public class ResponseResult<T> {
private String code;
private String msg;
private T data;
}
(5)创建响应工具类
package org.shortenservice.common;
public class ResultUtils {
private ResultUtils() {}
public static <T> ResponseResult<T> success(T data) {
return build("200", "success", data);
}
public static ResponseResult<Void> success() {
return build("200", "success", null);
}
public static boolean isSuccess(String code) {
return "200".equals(code);
}
public static ResponseResult<Void> failure(String msg) {
return build("500", msg, null);
}
public static ResponseResult<Void> failure(String code, String msg) {
return build(code, msg, null);
}
public static <T> ResponseResult<T> failure(String code, String msg, T data) {
return build(code, msg, data);
}
public static <T> ResponseResult<T> 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方法:
- 创建一个StringBuilder对象,用于存储转换后的字符串。
- 使用while循环,当id大于0时,执行循环体。在循环体中,首先计算id除以62的余数,然后将余数对应的BASE62字符添加到StringBuilder对象中。接着,将id除以62,更新id的值。
- 当id小于等于0时,跳出循环。此时,StringBuilder对象中的字符串长度可能小于6。为了确保字符串长度为6,使用另一个while循环,在StringBuilder对象的开头添加0,直到其长度达到6。
- 最后,将StringBuilder对象反转,并将其转换为字符串返回。
shortKeyToId方法:
- 创建一个名为id的长整型变量,初始值为0。
- 使用for循环遍历shortKey字符串中的每个字符。在循环体中,首先计算当前字符在BASE62字符串中的索引值,然后将id乘以62,再加上当前字符的索引值。将结果赋值给id。
- 当所有字符都遍历完毕后,返回id作为最终结果。
2. 创建实体类
package org.shortenservice.model;
import jakarta.persistence.*;
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 org.shortenservice.dao;
import org.shortenservice.model.UrlMap;
import org.springframework.data.repository.CrudRepository;
import java.time.Instant;
import java.util.List;
public interface UrlMapDao extends CrudRepository<UrlMap, Long> {
UrlMap findFirstByLongUrl(String longUrl);
List<UrlMap> 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<String> 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<Map> 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 -X POST "localhost:8888/shorten?longUrl=https://i.csdn.net/#/user-center/profile?spm=1011.2415.
3001.9999.1&c=4"
访问短链接:
curl -L "http://127.0.0.1:8888/shortKey"
三、系统优化🍱
1. 缓存简介
缓存技术能提升数据处理速度,特别适用于高并发和频繁访问的场景。
2. 引入 Guava
在 UrlMapService
中利用 Guava 实现本地缓存,提升短链接的解码效率。
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
@Service
@Slf4j
public class UrlMapService {
private final Cache<Long, String> cache = CacheBuilder.newBuilder().maximumSize(1000).build();
public Optional<String> decode(String shortKey) {
long id = Base62Utils.shortKeyToId(shortKey);
return Optional.ofNullable(cache.getIfPresent(id)).or(() -> {
return urlMapDao.findById(id).map(urlMap -> {
cache.put(id, urlMap.getLongUrl());
return urlMap.getLongUrl();
});
});
}
}
结论
短链接系统的实现需高效且可扩展,尤其要注重数据保护和安全标准。希望本文能为您提供一个清晰的实现思路。
评论区