版本: v1.0 | 更新: 2026-08-13 | 服务: https://lsink.tesmusic.top
LSink 提供企业级短链接生成服务,其他系统可通过开放 API 接入,将长 URL 转换为短链接并嵌入自己的业务系统。
支付框架已上线(订单/回调/额度发放),当前为模拟支付渠道;微信/支付宝真实渠道接入中(实现
PaymentChannel接口即可)。
X-Api-Key 与额度套餐;也可在控制台自助下单(模拟支付)ssh-keygen -t rsa -b 2048 -m PEM 或项目 KeyGenTool),私钥自留/api/urls 验证通过后上线额度按"短链接条数"计费,生成成功即扣减 1 条,可在额度接口查询余量。
| 项 | 值 |
|---|---|
| Base URL | https://lsink.tesmusic.top |
| 数据格式 | JSON (UTF-8) |
| 请求签名 | RSA-2048 (SHA256withRSA),见第四节 |
| 时间戳容忍 | ±5 分钟 |
| 限流 | 生成接口 10 次/秒/客户,统计 20 次/分钟,二维码 30 次/分钟 |
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| POST | /api/urls |
RSA 签名 | 生成短链接(核心) |
| GET | /{code} |
无 | 302 跳转到长 URL |
| GET | /api/qr/{code} |
无 | 获取短链接二维码 PNG |
| GET | /api/stats/{code} |
无 | 点击统计(总点击 + 7 天趋势) |
| GET | /api/status |
无 | 平台状态(池余量、缓存命中率) |
| GET | /actuator/health |
无 | 健康检查 |
所有写接口(POST /api/urls)必须携带 4 个请求头:
| 请求头 | 说明 |
|---|---|
X-Api-Key |
客户标识(购买时分配) |
X-Timestamp |
当前毫秒级时间戳,与服务器时间差超过 ±5 分钟拒绝 |
X-Nonce |
随机字符串(UUID 即可),同一 nonce 10 分钟内不可重复使用(防重放) |
X-Sign |
RSA-SHA256 签名(Base64),见下 |
X-Timestamp + X-Nonce,值转字符串key=value,value 做 form-urlencoded 编码(与 Java URLEncoder 一致):- _ . * 不转义+%XX(UTF-8 字节,大写十六进制)& 连接,得到规范串SHA256withRSA 签名,Base64 编码后放入 X-Sign⚠️ 注意:不是 RFC 3986(
%20)编码!非 Java 客户端必须按本表实现,否则验签必然失败。
请求体 {"longUrl":"https://a.com/x"},X-Timestamp=1700000000123,X-Nonce=n1:
规范串: longUrl=https%3A%2F%2Fa.com%2Fx&nonce=n1&ts=1700000000123
签名: Base64(SHA256withRSA(规范串, 私钥))
| 错误 | 原因 |
|---|---|
缺少签名头 |
4 个请求头未带全 |
未知客户 |
X-Api-Key 未注册 |
请求已过期或时间戳非法 |
本地时间与服务器差超 5 分钟(NTP 同步) |
验签失败 |
规范串拼错 / 编码不一致 / 请求被篡改 |
重放攻击 |
nonce 已被使用过 |
POST /api/urls(需签名)生成一条短链接,返回短码与完整短链地址。
请求体:
{
"longUrl": "https://www.example.com/path?query=1",
"customCode": "mycode" // 可选,自定义短码,1-20 位 [A-Za-z0-9-_]
}
响应 200:
{
"shortUrl": "https://lsink.tesmusic.top/mycode",
"code": "mycode"
}
错误响应:
| 状态码 | 场景 |
|---|---|
| 400 | 参数不合法(longUrl 为空/过长、自定义短码被占用或与系统池冲突) |
| 401 | 签名校验失败(见 4.4) |
| 403 | 额度不足,需购买扩容 |
| 429 | 触发限流 |
| 503 | 短码池耗尽(平台故障,重试即可) |
GET /{code}(公开)访问短链时自动 302 重定向到长 URL。响应带 Cache-Control: public, s-maxage=60,CDN 可缓存 60 秒。
GET /api/qr/{code}(公开)返回短链对应的二维码 PNG 图片,可直接 <img src="https://lsink.tesmusic.top/api/qr/abc123"> 嵌入页面。
GET /api/stats/{code}(公开){
"code": "abc123",
"total": 100,
"daily": [
{"date": "2026-08-07", "clicks": 10},
{"date": "2026-08-13", "clicks": 15}
]
}
GET /api/status(公开){
"poolRemaining": 90000,
"cacheHitRate": 0.87
}
GET /actuator/health(公开){"status": "UP"}
# 1. 拼规范串(按 4.2 规则)
# longUrl=https%3A%2F%2Fa.com%2Fx&nonce=<nonce>&ts=<ts>
# 2. 签名(openssl,私钥为 PEM 格式)
SIGN=$(printf '%s' "longUrl=https%3A%2F%2Fa.com%2Fx&nonce=$NONCE&ts=$TS" \
| openssl dgst -sha256 -sign private.pem -binary | base64 -w0)
curl -X POST https://lsink.tesmusic.top/api/urls \
-H "Content-Type: application/json" \
-H "X-Api-Key: <your-api-key>" \
-H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" \
-H "X-Sign: $SIGN" \
-d '{"longUrl":"https://a.com/x"}'
import base64, hashlib, json, time, uuid
from urllib.parse import quote
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
import requests
PRIVATE_KEY_PATH = "private.pem"
API_KEY = "your-api-key"
BASE = "https://lsink.tesmusic.top"
def encode(v: str) -> str:
# form-urlencoded:空格 -> '+'
return quote(v, safe="-_.*").replace("%20", "+")
def sign(long_url: str) -> dict:
ts = str(int(time.time() * 1000))
nonce = str(uuid.uuid4())
params = {"longUrl": long_url, "ts": ts, "nonce": nonce}
canonical = "&".join(f"{k}={encode(params[k])}" for k in sorted(params))
with open(PRIVATE_KEY_PATH, "rb") as f:
key = serialization.load_pem_private_key(f.read(), password=None)
sig = base64.b64encode(key.sign(
canonical.encode(), padding.PKCS1v15(), hashes.SHA256())).decode()
return {"X-Api-Key": API_KEY, "X-Timestamp": ts,
"X-Nonce": nonce, "X-Sign": sig, "canonical": canonical}
def create_short_url(long_url: str) -> dict:
h = sign(long_url)
r = requests.post(f"{BASE}/api/urls", json={"longUrl": long_url}, headers=h)
return r.json()
# 生成密钥对(公钥自动上传注册 + 打印私钥)
java -jar lsink.jar --keygen
# 运行签名调用演示(正常 200 / 篡改 401 / 重放 401)
java -jar lsink.jar --client-demo
| 状态码 | 含义 | 处理建议 |
|---|---|---|
| 200 | 成功 | - |
| 302 | 跳转(GET /{code}) | - |
| 400 | 请求参数错误 | 检查 longUrl / customCode |
| 401 | 鉴权失败 | 按 4.4 排查签名 |
| 403 | 额度不足 | 联系管理员购买 |
| 404 | 短码不存在 | 检查 code |
| 429 | 触发限流 | 降频重试 |
| 500 | 服务器错误 | 联系管理员 |
| 503 | 服务暂不可用 | 稍后重试 |
Q: 短链有效期多久? 当前演示配置 TTL 60 秒,生产配置可为 2 年。到期后自动回收,短码可被重新分配。
Q: 支持自定义短码吗?
支持,customCode 1-20 位 [A-Za-z0-9-_],与系统预生成池隔离。已被使用会返回 400。
Q: 能统计每个短链的点击量吗?
可以,GET /api/stats/{code} 返回总点击与 7 天趋势。
Q: 如何接入自己的域名? 支持,联系管理员配置域名绑定(CDN 缓存 60 秒)。
Q: 额度用完了怎么办?
403 提示额度不足。控制台可自助购买套餐(当前模拟支付渠道),或联系管理员(账号 Lemon)人工扩容。
LSink · 短链接平台 · 技术支持:联系 Lemon