本文档介绍短链接服务的核心设计原理和实现方案,澄清了短链接不是”加密”而是”映射”的核心概念。文档详细阐述了基于自增ID + Base62编码的唯一ID映射策略,包括短码生成算法、数据存储模型设计、Spring Boot 2.x + Java 8的完整代码实现,以及如何通过短码获取关联的JSON信息。方案具有长度可控、唯一性保证、高性能、高可靠等优势,是构建短链接服务的经典稳健选择。
你提到了“加密”和“解密”,这通常指一个可逆的算法过程,比如用AES算法加密一段JSON,理论上可以再解密回来。但这个思路在短链接场景下有几个天然的矛盾:
因此,业界主流的短链接方案并不是对长链接或JSON信息进行 “加密”,而是为其生成一个唯一的、短的 “ID”,并在数据库中建立这个 ID 与原始信息的 “映射” 关系。
基于这个核心思想,我们来设计一套符合你要求的、基于 Java 8 和 Spring Boot 2.x 的高效方案。
这套方案的核心是生成一个简短、唯一、不易被猜到的字符串(我们称之为 shortCode),并将其与你的长链接或JSON数据关联存储。
这是整个设计的关键。我们需要一个算法,能持续生成不重复的短码。这里推荐影响力最大、也是业界最成熟的方案:
方案:自增ID + Base62编码
AUTO_INCREMENT 主键)。这个ID天然是唯一的。+、/ 等在URL中需要特殊编码的字符,非常适合用在URL路径中。转换效率高,能极大地缩短字符串长度。例如,十进制的 1,000,000,000 转换为62进制后是 15ftGg,只有6位。
下面是一个将 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编码 + 数据库映射” 的方案是构建短链接服务最经典、最稳健的选择。
/api/info/{shortCode}),可以安全地根据短码查询并返回关联的JSON数据,实现了你“解密”的业务意图。id的生成方式进行优化(如使用Twitter的Snowflake算法构建分布式ID生成器),但核心的映射思想保持不变。希望这个详尽的设计方案能帮助你启动你的项目。如果你有任何进一步的问题,随时可以提出!