缩短 URL 是现代应用程序中常见的需求,通常用于减少长 URL 的长度,使其更易于分享。URL 缩短服务的核心思路是将长 URL 映射到一个唯一的短代码。较为复杂的场景可能涉及多种功能,例如:
- 缩短的 URL 自动过期(即在一定时间后失效)。
- 统计 URL 的访问量。
- 检查并避免短 URL 重复。
- 添加安全机制,如防止恶意链接。
场景案例
我们可以设计一个场景:
- 用户通过 API 提交长 URL。
- 系统生成短 URL,短 URL 有有效期(例如 7 天),并存储在数据库中。
- 用户可以通过 API 查询短 URL 的访问次数。
- 每当有人访问短 URL,系统会记录访问量,并自动重定向到原始的长 URL。
- 在短 URL 过期后,无法再进行重定向。
技术栈
- Spring Boot: 用于快速构建 RESTful API 服务。
- H2 数据库: 用于存储 URL 和相关元数据。
- Java UUID: 生成唯一短码。
- Java 定时任务: 自动清理过期 URL。
Step 1: 项目结构
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ ├── UrlShortenerApplication.java
│ │ ├── controller/
│ │ │ └── UrlController.java
│ │ ├── model/
│ │ │ └── Url.java
│ │ ├── repository/
│ │ │ └── UrlRepository.java
│ │ └── service/
│ │ └── UrlService.java
│ └── resources/
│ └── application.properties
Step 2: 创建实体类 Url
Url.java
是用于存储长 URL、短 URL 以及相关元数据的实体类。
package com.example.model;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
public class Url {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String originalUrl;
private String shortCode;
private LocalDateTime createdAt;
private LocalDateTime expiresAt;
private int visitCount;
public Url() {}
public Url(String originalUrl, String shortCode, LocalDateTime createdAt, LocalDateTime expiresAt) {
this.originalUrl = originalUrl;
this.shortCode = shortCode;
this.createdAt = createdAt;
this.expiresAt = expiresAt;
this.visitCount = 0;
}
// getters and setters
}
Step 3: 创建 Repository 接口
使用 Spring Data JPA
,我们可以快速创建一个操作数据库的接口。
package com.example.repository;
import com.example.model.Url;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UrlRepository extends JpaRepository<Url, Long> {
Optional<Url> findByShortCode(String shortCode);
void deleteByExpiresAtBefore(LocalDateTime dateTime);
}
Step 4: 编写服务类 UrlService
UrlService.java
包含业务逻辑,例如生成短 URL、处理 URL 重定向、统计访问量等。
package com.example.service;
import com.example.model.Url;
import com.example.repository.UrlRepository;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.UUID;
@Service
public class UrlService {
private final UrlRepository urlRepository;
public UrlService(UrlRepository urlRepository) {
this.urlRepository = urlRepository;
}
public String shortenUrl(String originalUrl, int expirationDays) {
String shortCode = UUID.randomUUID().toString().substring(0, 8); // 生成短码
LocalDateTime createdAt = LocalDateTime.now();
LocalDateTime expiresAt = createdAt.plusDays(expirationDays);
Url url = new Url(originalUrl, shortCode, createdAt, expiresAt);
urlRepository.save(url);
return shortCode;
}
public Optional<Url> getOriginalUrl(String shortCode) {
Optional<Url> urlOptional = urlRepository.findByShortCode(shortCode);
urlOptional.ifPresent(url -> {
if (url.getExpiresAt().isAfter(LocalDateTime.now())) {
url.setVisitCount(url.getVisitCount() + 1);
urlRepository.save(url);
}
});
return urlOptional.filter(url -> url.getExpiresAt().isAfter(LocalDateTime.now()));
}
public int getVisitCount(String shortCode) {
return urlRepository.findByShortCode(shortCode)
.map(Url::getVisitCount)
.orElse(0);
}
// 定时任务清理过期 URL
public void cleanUpExpiredUrls() {
urlRepository.deleteByExpiresAtBefore(LocalDateTime.now());
}
}
Step 5: 编写 Controller
UrlController.java
是与前端或其他客户端交互的层。
package com.example.controller;
import com.example.model.Url;
import com.example.service.UrlService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequestMapping("/api/url")
public class UrlController {
private final UrlService urlService;
public UrlController(UrlService urlService) {
this.urlService = urlService;
}
@PostMapping("/shorten")
public ResponseEntity<String> shortenUrl(@RequestParam String originalUrl, @RequestParam(defaultValue = "7") int expirationDays) {
String shortCode = urlService.shortenUrl(originalUrl, expirationDays);
return ResponseEntity.ok("Shortened URL: http://localhost:8080/api/url/" + shortCode);
}
@GetMapping("/{shortCode}")
public ResponseEntity<?> redirectUrl(@PathVariable String shortCode) {
Optional<Url> urlOptional = urlService.getOriginalUrl(shortCode);
return urlOptional
.map(url -> ResponseEntity.status(302).header("Location", url.getOriginalUrl()).build())
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/{shortCode}/stats")
public ResponseEntity<Integer> getUrlStats(@PathVariable String shortCode) {
int visitCount = urlService.getVisitCount(shortCode);
return ResponseEntity.ok(visitCount);
}
}
Step 6: 定时清理任务
Spring Boot
可以通过 @Scheduled
注解定期执行任务。我们可以创建一个任务来清理过期的 URL。
package com.example.service;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class CleanupTask {
private final UrlService urlService;
public CleanupTask(UrlService urlService) {
this.urlService = urlService;
}
@Scheduled(cron = "0 0 0 * * ?") // 每天午夜执行一次
public void cleanExpiredUrls() {
urlService.cleanUpExpiredUrls();
}
}
Step 7: 配置文件
在 application.properties
中配置 H2 数据库以及其他 Spring Boot 配置。
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update
Step 8: 启动类 UrlShortenerApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class UrlShortenerApplication {
public static void main(String[] args) {
SpringApplication.run(UrlShortenerApplication.class, args);
}
}
运行服务
- 使用 POST 请求
/api/url/shorten
提交长 URL 并获取短 URL。 - 使用 GET 请求
/api/url/{shortCode}
重定向到原始 URL。 - 使用 GET 请求
/api/url/{shortCode}/stats
获取短 URL 的访问量。 - 每天定时任务会清理过期的 URL。
总结
通过 Spring Boot 框架,我们可以快速构建一个带有定时任务、访问统计以及过期处理的 URL 缩短服务。在真实场景中,可能还会涉及更多的功能,如用户身份验证、URL 黑名单过滤等。
转载来源:https://juejin.cn/post/7414321375550783514