note

文档概述

本文档介绍短链接服务的核心设计原理和实现方案,澄清了短链接不是”加密”而是”映射”的核心概念。文档详细阐述了基于自增ID + Base62编码的唯一ID映射策略,包括短码生成算法、数据存储模型设计、Spring Boot 2.x + Java 8的完整代码实现,以及如何通过短码获取关联的JSON信息。方案具有长度可控、唯一性保证、高性能、高可靠等优势,是构建短链接服务的经典稳健选择。

使用场景

核心概念澄清:这不是“加密”,而是“映射”

你提到了“加密”和“解密”,这通常指一个可逆的算法过程,比如用AES算法加密一段JSON,理论上可以再解密回来。但这个思路在短链接场景下有几个天然的矛盾:

  1. 长度不可控:加密后的密文长度通常与原文长度正相关。一个复杂的JSON加密后,得到的字符串依然会很长,违背了“短链接”的初衷。
  2. 性能开销:加解密是CPU密集型操作。对于需要高并发处理的短链接服务,每次请求都进行加解密会带来不必要的性能损耗。

因此,业界主流的短链接方案并不是对长链接或JSON信息进行 “加密”,而是为其生成一个唯一的、短的 “ID”,并在数据库中建立这个 ID 与原始信息的 “映射” 关系。

基于这个核心思想,我们来设计一套符合你要求的、基于 Java 8 和 Spring Boot 2.x 的高效方案。


设计方案:唯一ID映射策略

这套方案的核心是生成一个简短、唯一、不易被猜到的字符串(我们称之为 shortCode),并将其与你的长链接或JSON数据关联存储。

1. 短码生成策略 (控制长度和唯一性)

这是整个设计的关键。我们需要一个算法,能持续生成不重复的短码。这里推荐影响力最大、也是业界最成熟的方案:

方案:自增ID + Base62编码

下面是一个将 long 类型的ID转换为Base62字符串的工具类方法。

    public class Base62Converter {

        private static final String BASE62_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        private static final int BASE = BASE62_CHARS.length();

        /**
         * 将十进制ID转换为62进制字符串
         * @param id 必须是正数
         * @return 62进制字符串
         */
        public static String fromBase10(long id) {
            if (id < 0) {
                throw new IllegalArgumentException("ID must be a positive number.");
            }
            if (id == 0) {
                return String.valueOf(BASE62_CHARS.charAt(0));
            }

            StringBuilder sb = new StringBuilder();
            long num = id;
            while (num > 0) {
                sb.append(BASE62_CHARS.charAt((int) (num % BASE)));
                num /= BASE;
            }
            // 转换后需要反转字符串
            return sb.reverse().toString();
        }
    }
    ```
    **为什么这样设计更好**
    *   **保证唯一性**依赖数据库自增ID的绝对唯一性从根本上避免了冲突相比之下基于哈希的方案如MD5理论上存在碰撞可能需要额外的冲突解决机制增加了系统复杂度
    *   **性能优异**数字进制转换是纯内存计算速度极快远胜于哈希计算或加密算法
    *   **长度可预测**短码的长度随着ID的增长而平滑可预测地增加便于规划

#### 2. 数据存储模型 (关联JSON信息)

你需要一张表来存储映射关系

**数据表设计 (`short_link_map`)**

| 字段名         | 类型          | 约束/说明                                    |
| -------------- | ------------- | -------------------------------------------- |
| `id`           | `BIGINT`      | 主键自增 (Primary Key, Auto Increment)     |
| `short_code`   | `VARCHAR(16)` | **唯一索引**用于存储生成的Base62短码     |
| `original_url` | `TEXT`        | 你需要重定向的长链接                       |
| `extra_data`   | `JSON` / `TEXT` | **用于存储你的JSON信息**如果数据库支持JSON类型更好 |
| `created_at`   | `DATETIME`    | 创建时间                                   |

**重要**必须在 `short_code` 字段上建立唯一索引(`UNIQUE INDEX`)确保快速查询和数据一致性

#### 3. 接口设计与代码实现 (Spring Boot 2.x & Java 8)

我们设计两个核心接口一个用于生成短链接一个用于通过短链接重定向或获取信息)。

**实体类 (Entity) 和数据访问层 (Repository)**

```java
// 使用 JPA (Java Persistence API)
@Entity
@Table(name = "short_link_map")
public class ShortLinkMap {
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @Column(unique = true)
 private String shortCode;

 @Column(length = 2048) // 假设URL最长2048
 private String originalUrl;

 @Lob //对于TEXT或JSON等大字段
 private String extraData; // 存储JSON字符串

 // ... Getters and Setters
}

// 使用 Spring Data JPA
public interface ShortLinkRepository extends JpaRepository<ShortLinkMap, Long> {
 Optional<ShortLinkMap> findByShortCode(String shortCode);
}

业务逻辑层 (Service)

@Service
public class ShortLinkService {

 @Autowired
 private ShortLinkRepository repository;

 @Transactional // 保证事务性
 public String generateShortLink(String originalUrl, String jsonData) {
 // 1. 先保存,获取自增ID
 ShortLinkMap linkMap = new ShortLinkMap();
 linkMap.setOriginalUrl(originalUrl);
 linkMap.setExtraData(jsonData);

 // 先保存一次,让JPA/Hibernate为我们生成ID
 linkMap = repository.save(linkMap);

 // 2. 使用ID生成shortCode
 String shortCode = Base62Converter.fromBase10(linkMap.getId());

 // 3. 更新记录,存入shortCode
 linkMap.setShortCode(shortCode);
 repository.save(linkMap);

 // 4. 返回完整的短链接(域名可以配置在配置文件中)
 return "http://your.domain/" + shortCode;
 }

 public Optional<ShortLinkMap> getMappingByShortCode(String shortCode) {
 return repository.findByShortCode(shortCode);
 }
}

为什么是 save -> generate -> update 的两步走? 这种方式利用了数据库事务来确保ID生成和 shortCode 回填的原子性。它简单、可靠,足以应对绝大多数场景。在高并发下,save 操作首先会获取数据库行锁,保证了 ID 的唯一分配,避免了在应用层生成ID可能带来的竞态条件。

控制层 (Controller)

@RestController
public class ShortLinkController {

 @Autowired
 private ShortLinkService shortLinkService;

 // DTO用于接收请求体
 public static class GenerateRequest {
 private String url;
 private Map<String, Object> data; // 接收任意JSON结构
 // getters and setters
 }

 // 1. 生成短链接的接口
 @PostMapping("/api/generate")
 public String createShortLink(@RequestBody GenerateRequest request) throws JsonProcessingException {
 // 将Map类型的data转换为JSON字符串存储
 ObjectMapper objectMapper = new ObjectMapper();
 String jsonData = objectMapper.writeValueAsString(request.getData());

 return shortLinkService.generateShortLink(request.getUrl(), jsonData);
 }

 // 2. 访问短链接并重定向的接口
 @GetMapping("/{shortCode}")
 public ResponseEntity<Void> redirectToOriginalUrl(@PathVariable String shortCode) {
 return shortLinkService.getMappingByShortCode(shortCode)
 .map(linkMap -> ResponseEntity.status(HttpStatus.FOUND) // 302 Found
 .location(URI.create(linkMap.getOriginalUrl()))
 .build())
 .orElse(ResponseEntity.notFound().build()); // 404 Not Found
 }

 // 3. [可选] 根据你的需求:通过短码获取JSON信息的接口
 @GetMapping("/api/info/{shortCode}")
 public ResponseEntity<?> getInfoByShortCode(@PathVariable String shortCode) {
 return shortLinkService.getMappingByShortCode(shortCode)
 .map(linkMap -> {
 try {
 // 将存储的JSON字符串解析回Map或特定对象
 ObjectMapper objectMapper = new ObjectMapper();
 Map<String, Object> data = objectMapper.readValue(linkMap.getExtraData(), new TypeReference<Map<String, Object>>(){});
 return ResponseEntity.ok(data);
 } catch (IOException e) {
 // JSON解析失败,返回服务器错误
 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to parse JSON data.");
 }
 })
 .orElse(ResponseEntity.notFound().build());
 }
}

总结与优势

这套 “自增ID + Base62编码 + 数据库映射” 的方案是构建短链接服务最经典、最稳健的选择。

希望这个详尽的设计方案能帮助你启动你的项目。如果你有任何进一步的问题,随时可以提出!


← 返回文档首页