本文介紹如何使用 Java 實現短鏈接服務,核心採用 Hashids 庫進行對稱加密,將數據庫自增 ID 加密為短字符串,避免暴露業務規律。基於 Blade 輕量框架演示完整流程:數據庫設計、路由控制器、編解碼邏輯、URL 校驗。
原始 URL 暴露自增 ID:
https://example.com/video/1
https://example.com/video/2
https://example.com/video/3
風險:爬蟲易遍歷、業務量洩露、安全隱患。短鏈接加密後:
https://short.ly/aB3dE9
https://short.ly/xY7kL2
https://short.ly/mN4pQ8
Maven 依賴:
<dependency>
<groupId>org.hashids</groupId>
<artifactId>hashids</artifactId>
<version>1.0.3</version>
</dependency>
CREATE TABLE `t_short_url` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`long_url` TEXT NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
import com.blade.kit.Hashids;
import com.blade.kit.StringKit;
import com.blade.mvc.annotation.Path;
import com.blade.mvc.annotation.Route;
import com.blade.mvc.http.HttpMethod;
import com.blade.mvc.http.Request;
import com.blade.mvc.http.Response;
import com.blade.mvc.ui.RestResponse;
import java.util.regex.Pattern;
@Path
public class ShortUrlController {
// 鹽值:項目級機密,防止碰撞與逆向
private static final Hashids HASHIDS = new Hashids("my-project-salt-xyz", 6);
// 簡單內存模型,實際替換為 ORM (MyBatis/JPA/ActiveRecord)
private final UrlModel urlModel = new UrlModel();
// URL 正則校驗
private static final Pattern URL_PATTERN = Pattern.compile(
"^(https?|ftp)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]$"
);
/**
* 短鏈接跳轉:/abc123 → 解碼 ID → 查詢長鏈接 → 302 重定向
*/
@Route("/:key")
public void redirect(Request request, Response response) {
String key = request.pathParam(":key").replaceAll("[^A-Za-z0-9]", "");
long[] numbers = HASHIDS.decode(key);
if (numbers == null || numbers.length == 0) {
response.text("短鏈接不存在");
return;
}
int id = (int) numbers[0];
UrlModel url = urlModel.findById(id);
if (url == null || url.getLongUrl() == null) {
response.text("短鏈接不存在");
return;
}
response.redirect(url.getLongUrl());
}
/**
* 首頁:顯示創建表單
*/
@Route(value = "/", method = HttpMethod.GET)
public String index() {
return "index.html"; // 模板頁面
}
/**
* 創建短鏈接:POST / → 驗證 URL → 存儲 → 編碼 ID → 返回短碼
*/
@Route(value = "/", method = HttpMethod.POST)
public String create(Request request, Response response) {
String longUrl = request.query("url");
// 1. URL 格式校驗
if (!isValidUrl(longUrl)) {
request.attribute("error", "請輸入有效的 URL (以 https:// 或 https:// 開頭)");
return "index.html";
}
// 2. 存儲長鏈接,獲取自增 ID
Integer id = urlModel.insert().param("long_url", longUrl).executeAndCommit();
if (id == null || id == 0) {
request.attribute("error", "創建失敗,請重試");
return "index.html";
}
// 3. Hashids 編碼 ID → 短碼
String hash = HASHIDS.encode(id);
// 4. 返回結果 (模板顯示)
request.attribute("short_code", hash);
request.attribute("short_url", request.uri() + hash);
request.attribute("long_url", longUrl);
return "index.html";
}
private boolean isValidUrl(String url) {
return StringKit.isNotBlank(url) && URL_PATTERN.matcher(url).find();
}
}
public class UrlModel {
private Integer id;
private String longUrl;
private Timestamp createdAt;
// 查詢
public UrlModel findById(int id) {
// 實際: SELECT * FROM t_short_url WHERE id = ?
return new UrlModel(); // 模擬
}
// 插入
public ActiveRecordBuilder insert() {
return new ActiveRecordBuilder(this);
}
// 內部構建器
public static class ActiveRecordBuilder {
private UrlModel model;
public ActiveRecordBuilder(UrlModel m) { this.model = m; }
public ActiveRecordBuilder param(String key, Object val) {
if ("long_url".equals(key)) model.longUrl = (String) val;
return this;
}
public Integer executeAndCommit() {
// 實際: INSERT INTO t_short_url (long_url) VALUES (?)
// 返回自增 ID
model.id = 12345; // 模擬
return model.id;
}
}
// Getters/Setters
public Integer getId() { return id; }
public String getLongUrl() { return longUrl; }
}
短鏈接生成器
短鏈接生成器
#if(error)
${error}
#end
#if(short_code)
#end
// 構造函數完整簽名
public Hashids(String salt) // 默認最小長度 0, 默認字符集
public Hashids(String salt, int minHashLength) // 指定最小長度
public Hashids(String salt, int minHashLength, String alphabet) // 自定義字符集
// 常用配置示例
Hashids h1 = new Hashids("salt"); // 默認
Hashids h2 = new Hashids("salt", 6); // 最短 6 字符
Hashids h3 = new Hashids("salt", 8, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); // 自定義字符集
Hashids hashids = new Hashids("my-salt", 6);
// 編碼單個 ID
String encoded = hashids.encode(12345); // "jR7x9A"
String encoded2 = hashids.encode(1, 2, 3); // 支援多數字編碼
// 解碼
long[] decoded = hashids.decode("jR7x9A"); // [12345]
long[] decoded2 = hashids.decode(encoded2); // [1, 2, 3]
// 編碼十六進制
String hexEncoded = hashids.encodeHex("507f1f77bcf86cd799439011"); // 支援 MongoDB ObjectId
String hexDecoded = hashids.decodeHex(hexEncoded);
| 需求 | 解決方案 |
|---|---|
| 高併發寫入 | Redis 預生成 ID 段 + 異步寫入 DB |
| 訪問統計 | Redis 計數器 + 定時持久化 |
| 自定義短碼 | 用戶提交 alias,唯一性檢查後存儲 |
| 過期/失效 | 表添加 expire_at,定時任務清理 |
| 防惡意刷短 | IP 限流、驗證碼、登錄才能創建 |
| 分佈式部署 | 統一鹽值配置中心、DB 主從/分庫分表 |
| 監控告警 | Prometheus 監控 QPS、延遲、錯誤率 |