Java 短链系统设计与实现

211 阅读5分钟

Java 短链系统设计与实现

一、引言

在当今互联网时代,长链接的使用存在诸多不便,例如在短信、社交媒体等场景中,长链接不仅占用大量字符空间,还影响用户体验。短链系统应运而生,它能够将冗长的 URL 转换为简洁的短链接,方便用户传播和使用。本文将详细介绍如何使用 Java 设计和实现一个简单的短链系统。

二、短链系统的工作原理

短链系统的核心工作流程主要包括两个部分:生成短链和短链跳转。

  1. 生成短链:当用户提交一个长链接时,短链系统会为该长链接生成一个唯一的短码,并将短码与长链接的映射关系存储在数据库中。短码通常是一个较短的字符串,由字母和数字组成。
  2. 短链跳转:当用户访问短链接时,短链系统根据短码从数据库中查找对应的长链接,并将用户重定向到该长链接。

三、技术选型

  • 后端语言:Java,作为一种广泛使用的编程语言,具有强大的生态系统和丰富的开发框架。
  • Web 框架:Spring Boot,简化了 Java 开发过程,提供了快速搭建 Web 应用的能力。
  • 数据库:MySQL,用于存储短码与长链接的映射关系。
  • 缓存:Redis,用于缓存短码与长链接的映射关系,提高系统的性能。

四、数据库设计

我们需要设计一个简单的数据库表来存储短码与长链接的映射关系。以下是 MySQL 表结构的示例:

CREATE TABLE `short_url` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `long_url` varchar(2048) NOT NULL COMMENT '长链接',
  `short_code` varchar(10) NOT NULL COMMENT '短码',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_short_code` (`short_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='短链映射表';

该表包含以下字段:

  • id:自增主键。
  • long_url:存储原始的长链接。
  • short_code:存储生成的短码,需要保证唯一性。
  • create_time:记录短链的创建时间。

五、Java 代码实现

1. 项目搭建

首先,使用 Spring Initializr 创建一个 Spring Boot 项目,添加以下依赖:

  • Spring Web
  • Spring Data JPA
  • MySQL Driver
  • Redis

2. 实体类

创建 ShortUrl 实体类,用于映射数据库表:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Date;

@Entity
public class ShortUrl {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String longUrl;
    private String shortCode;
    private Date createTime;

    // 构造函数、Getter 和 Setter 方法
    public ShortUrl() {}

    public ShortUrl(String longUrl, String shortCode) {
        this.longUrl = longUrl;
        this.shortCode = shortCode;
        this.createTime = new Date();
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getLongUrl() {
        return longUrl;
    }

    public void setLongUrl(String longUrl) {
        this.longUrl = longUrl;
    }

    public String getShortCode() {
        return shortCode;
    }

    public void setShortCode(String shortCode) {
        this.shortCode = shortCode;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

3. 数据访问层

创建 ShortUrlRepository 接口,继承 JpaRepository 来实现对 ShortUrl 实体的数据库操作:

import org.springframework.data.jpa.repository.JpaRepository;

public interface ShortUrlRepository extends JpaRepository<ShortUrl, Long> {
    ShortUrl findByShortCode(String shortCode);
    ShortUrl findByLongUrl(String longUrl);
}

4. 短码生成器

实现一个短码生成器,用于生成唯一的短码。这里我们使用简单的自增 ID 转换为 62 进制字符串的方式:

import java.util.HashMap;
import java.util.Map;

public class ShortCodeGenerator {
    private static final String BASE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final int BASE_LENGTH = BASE.length();
    private static final Map<Character, Integer> CHAR_INDEX_MAP = new HashMap<>();

    static {
        for (int i = 0; i < BASE_LENGTH; i++) {
            CHAR_INDEX_MAP.put(BASE.charAt(i), i);
        }
    }

    public static String encode(long num) {
        StringBuilder sb = new StringBuilder();
        while (num > 0) {
            sb.append(BASE.charAt((int) (num % BASE_LENGTH)));
            num /= BASE_LENGTH;
        }
        return sb.reverse().toString();
    }

    public static long decode(String shortCode) {
        long num = 0;
        for (char c : shortCode.toCharArray()) {
            num = num * BASE_LENGTH + CHAR_INDEX_MAP.get(c);
        }
        return num;
    }
}

5. 服务层

创建 ShortUrlService 类,实现短链生成和跳转的业务逻辑:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class ShortUrlService {
    @Autowired
    private ShortUrlRepository shortUrlRepository;
    @Autowired
    private StringRedisTemplate redisTemplate;

    public String generateShortUrl(String longUrl) {
        // 先从数据库中查找是否已经存在该长链接
        ShortUrl existingUrl = shortUrlRepository.findByLongUrl(longUrl);
        if (existingUrl != null) {
            return existingUrl.getShortCode();
        }

        // 获取当前最大 ID
        Long maxId = shortUrlRepository.count();
        String shortCode = ShortCodeGenerator.encode(maxId + 1);

        // 保存短链映射关系到数据库
        ShortUrl shortUrl = new ShortUrl(longUrl, shortCode);
        shortUrlRepository.save(shortUrl);

        // 缓存短码与长链接的映射关系
        redisTemplate.opsForValue().set(shortCode, longUrl);

        return shortCode;
    }

    public String getLongUrl(String shortCode) {
        // 先从缓存中查找
        String longUrl = redisTemplate.opsForValue().get(shortCode);
        if (longUrl != null) {
            return longUrl;
        }

        // 从数据库中查找
        ShortUrl shortUrl = shortUrlRepository.findByShortCode(shortCode);
        if (shortUrl != null) {
            longUrl = shortUrl.getLongUrl();
            // 将映射关系缓存到 Redis
            redisTemplate.opsForValue().set(shortCode, longUrl);
        }

        return longUrl;
    }
}

6. 控制器层

创建 ShortUrlController 类,处理用户的请求:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestController
@RequestMapping("/api")
public class ShortUrlController {
    @Autowired
    private ShortUrlService shortUrlService;

    @PostMapping("/shorten")
    public ResponseEntity<String> shortenUrl(@RequestBody String longUrl) {
        String shortCode = shortUrlService.generateShortUrl(longUrl);
        return new ResponseEntity<>(shortCode, HttpStatus.OK);
    }

    @GetMapping("/{shortCode}")
    public void redirectToLongUrl(@PathVariable String shortCode, HttpServletResponse response) throws IOException {
        String longUrl = shortUrlService.getLongUrl(shortCode);
        if (longUrl != null) {
            response.sendRedirect(longUrl);
        } else {
            response.setStatus(HttpStatus.NOT_FOUND.value());
        }
    }
}

六、测试与部署

  1. 测试:可以使用 Postman 等工具进行接口测试。发送 POST 请求到 /api/shorten 接口,请求体为长链接,获取短码;然后访问生成的短链接,验证是否能正确跳转到长链接。
  2. 部署:将项目打包成可执行的 JAR 文件,部署到服务器上。确保服务器上已经安装了 MySQL 和 Redis,并配置好相应的连接信息。

七、总结

通过以上步骤,我们使用 Java 和 Spring Boot 实现了一个简单的短链系统。该系统通过生成唯一的短码和存储映射关系,实现了长链接到短链接的转换和短链接的跳转功能。在实际应用中,还可以进一步优化系统,例如增加短链的有效期、处理并发请求等,以提高系统的性能和安全性。