Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)

Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
👍《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

问题分析:为什么我们需要“短链”?

相信很多小伙伴日常生活中都收到过一些营销短信,里面附带了一些链接地址,是否发现链接实际上很短,点击后会再次访问一个相对长的链接。

例如我们收到的短链可能是这样子:

https://www.example.***/aBcDeF

点开它,会跳转到以下原始长链接(甚至更长…参数更多)

https://www.example.***/product/detail?id=134985&from=share&ref=longtext&utm_source=wechat

那就有小伙伴问了,博主博主我直接分享这个长链接不行么?为何多此一举通过短链条转长链?

那我们就来看看常见的短链应用场景:

  • 社交平台分享:如微博、朋友圈、抖音简介中限制字符数;
  • 短信营销:短链可以有效减少短信的字符数,从而减少短信费用;
  • 营销追踪:每个渠道生成不同短链,统计访问量;
  • 二维码应用:短链生成二维码更清晰、美观;
  • 安全控制:短链服务可做访问限制、过期时间等策略。

系统设计分析

要实现一个简易的短链系统,我们至少需要解决以下问题:

模块 说明
短链生成策略 如何将长链接映射成短码? 并保证唯一性和随机性
存储设计 如何保存短码与原始链接的映射关系?
重定向服务 访问短链时,如何高效跳转到原始链接?
过期策略(可选) 是否允许短链设置有效期?
统计分析(可选) 统计点击量、来源等指标。

如果你要实现一个更高性能的短链系统,你还需要引入缓存设计,如 redis , 本章节更多的是探讨短链系统,小伙伴们可以自行在业务层追加缓存来实现减少数据库的访问

数据库设计

这里我们只实现最核心的短链映射功能

字段名 类型 说明
id bigint 主键
short_code varchar(10) 短链编码(唯一索引)
original_url varchar(500) 原始长链接
expire_time datetime 过期时间(可选)
create_time datetime 创建时间

SQL建表语句:

CREATE TABLE short_link (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    short_code VARCHAR(10) NOT NULL UNIQUE,
    original_url VARCHAR(500) NOT NULL,
    expire_time DATETIME NULL,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

短链生成策略设计

短链生成核心是将长链接转换成唯一的短码,常见方式有:

  • 自增ID + Base62编码 (简单可靠,唯一性高)
  • 哈希算法(MD5/SHA)(可重复生成,长度可控)

自增ID + Base62编码 : Base62 使用 [0-9][a-z][A-Z] 共 62 个字符编码,可生成短而唯一的字符串

哈希算法(如 MD5 或 SHA-256)的主要作用是将一个长度不定的输入(如 URL)映射为固定长度的输出(哈希值)

实战开始

博主这里就讲解以上两种的实现方式

❶ 项目依赖

在正式开发前我们先在SpringBoot项目引入相关依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>***.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

❷ 构建实体类

import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@Entity
@Table(name = "short_link")
public class ShortLink {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String shortCode;

    @Column(nullable = false, length = 500)
    private String originalUrl;

    private LocalDateTime expireTime;

    private LocalDateTime createTime = LocalDateTime.now();
}

❸ 工具类

Base62工具类 :使用自增ID + Base62编码

public class Base62Utils {
    private static final String CHARSET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

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

哈希生成工具类(MD5/SHA):我们将实现一个工具类,通过 MD5 或 SHA-256 来生成哈希值,然后从中截取短链

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class HashUtils {

    // 使用 MD5 算法生成短链
    public static String generateHash(String input, String algorithm) {
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            byte[] hashBytes = md.digest(input.getBytes());
            // 转换为 16 进制表示
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("哈希算法异常:" + e.getMessage());
        }
    }

    // 使用 MD5 生成短链的前 8 位
    public static String generateShortLink(String originalUrl) {
        String md5Hash = generateHash(originalUrl, "MD5");
        return md5Hash.substring(0, 8);  // 返回前 8 位
    }

    // 使用 SHA-256 生成短链的前 10 位
    public static String generateShortLinkSHA(String originalUrl) {
        String sha256Hash = generateHash(originalUrl, "SHA-256");
        return sha256Hash.substring(0, 10);  // 返回前 10 位
    }
}

❹ 服务层Service

为了更好的区分两种方式,这里我们将用两个Service来方便区分

Base62ShortLinkService

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class Base62ShortLinkService {

    private final ShortLinkRepository repository;

    @Transactional
    public String createShortLink(String originalUrl) {
        ShortLink entity = new ShortLink();
        entity.setOriginalUrl(originalUrl);
        ShortLink saved = repository.save(entity);

        String shortCode = Base62Utils.encode(saved.getId());
        saved.setShortCode(shortCode);
        repository.save(saved);

        return shortCode;
    }

    public String getOriginalUrl(String shortCode) {
        ShortLink link = repository.findByShortCode(shortCode);
        if (link == null) {
            throw new RuntimeException("短链不存在或已失效");
        }
        return link.getOriginalUrl();
    }
}

HashShortLinkService

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class HashShortLinkService {

    private final ShortLinkRepository repository;

    @Transactional
    public String createShortLink(String originalUrl) {
        // 先生成哈希值(使用 MD5 或 SHA-256)
        String shortCode = HashUtils.generateShortLink(originalUrl);
        
        // 检查数据库中是否已经存在该短链,避免重复
        if (repository.findByShortCode(shortCode) != null) {
            shortCode = HashUtils.generateShortLinkSHA(originalUrl); // 如果冲突,尝试用 SHA-256 生成短链
        }

        // 保存短链映射
        ShortLink entity = new ShortLink();
        entity.setOriginalUrl(originalUrl);
        entity.setShortCode(shortCode);
        repository.save(entity);

        return shortCode;
    }

    public String getOriginalUrl(String shortCode) {
        ShortLink link = repository.findByShortCode(shortCode);
        if (link == null) {
            throw new RuntimeException("短链不存在或已失效");
        }
        return link.getOriginalUrl();
    }
}

❺ 控制层 Controller

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;

@RestController
@RequiredArgsConstructor
public class ShortLinkController {

    private final Base62ShortLinkService base62ShortLinkService;
    private final HashShortLinkService hashShortLinkService;

    // 创建短链
    @PostMapping("/api/shorten")
    public String create(@RequestParam String url) {
        String code = base62ShortLinkService.createShortLink(url);
        //切换Hash
        //String code = hashShortLinkService.createShortLink(url);
        return "短链生成成功:" + "http://localhost:8080/" + code;
    }

    // 访问短链
    @GetMapping("/{code}")
    public RedirectView redirect(@PathVariable String code) {
        String originalUrl = base62ShortLinkService.getOriginalUrl(code);
        //切换Hash
        //String originalUrl = hashShortLinkService.getOriginalUrl(code);

		// 302重定向
        return new RedirectView(originalUrl);
    }
}

❻ 运行与测试

根据控制层的代码演示,小伙伴们可以自行切换两种实现方式,进行测试
以Hash为例生成短链接口:

POST http://localhost:8080/api/shorten?url=https://www.example.***/article/long-url

返回:

短链生成成功:http://localhost:8080/d41d8cd9

浏览器中打开短链,直接看条转效果!

总结

本文通过讲解了两种常见的短链生成方式:自增ID + Base62编码哈希算法。并通过完整的简易示例代码,让小伙伴们可以快速理解并掌握短链系统的设计!

如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家 一键三连 给博主一点点鼓励!


转载请说明出处内容投诉
CSS教程网 » Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买