Designing a Scalable Short‑Link System: From Base62 Encoding to Security
This article explains how to build a production‑grade short‑link service, covering the background of SMS short URLs, Base62 encoding principles, database schema design, request routing, security measures such as custom alphabets, rate limiting, and signed links, and additional management features for operation and monitoring.
1. Background
Since the birth of mobile networks, SMS has been the most direct and widely used notification channel. Marketing messages from banks, e‑commerce platforms, and apps often embed one or more short URLs (e.g., xxx.cn/5fY1ll) to compress long activity links into a few characters, making them easier to send within the character limits of services like Alibaba Cloud or Tencent Cloud SMS.
2. Base62 Encoding
Base62 is a high‑radix encoding that uses 62 characters (0‑9, A‑Z, a‑z). Converting an 8‑bit binary number 10101100 to hexadecimal yields AC, reducing the representation length. In practice, a large integer such as an order ID can be represented with only 7‑8 Base62 characters, which is far shorter than its decimal form.
The character set for Base62 is:
0‑9 → 10 characters A‑Z → 26 characters a‑z → 26 characters Total 62 symbols, each representing 62 possible states.
3. Short‑Link Access Process
Each short link corresponds to a unique numeric ID stored in a mapping table. The ID is generated by a distributed ID generator (e.g., Snowflake) and then encoded to a Base62 string, which becomes the short code.
Example short link: http://34dg.cn/LrCGdQh. When a user accesses it, Nginx forwards the request to a backend service that decodes the code back to the numeric ID, looks up the original long URL in the short_url table, and issues an HTTP 302 redirect.
CREATE TABLE IF NOT EXISTS `short_url` (
`id` BIGINT NOT NULL COMMENT 'primary Snowflake',
`short_code` VARCHAR(16) NOT NULL COMMENT 'Base62 short code',
`long_url` VARCHAR(1024) NOT NULL COMMENT 'original target URL',
`activity_id` BIGINT DEFAULT NULL COMMENT 'optional activity ID',
`creator_user_id` BIGINT NOT NULL COMMENT 'creator ID',
`expire_time` DATETIME DEFAULT NULL COMMENT 'expiration (NULL = permanent)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'creation time',
`access_count` BIGINT NOT NULL DEFAULT 0 COMMENT 'click count',
`status` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '0=disabled,1=enabled',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_short_code` (`short_code`),
KEY `idx_activity_id` (`activity_id`),
KEY `idx_creator_user_id` (`creator_user_id`),
KEY `idx_expire_time` (`expire_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='short link storage'; INSERT INTO `short_url` (`id`,`short_code`,`long_url`,`activity_id`,`creator_user_id`,`expire_time`,`access_count`,`status`) VALUES
(710152731357,'LrCGdQh','https://cuup.com/event?id=123456&source=shorturl',98765,10001,'2026-12-31 23:59:59',0,1); server {
listen 80;
server_name 34dg.cn;
location / {
proxy_pass http://shortlink-service/xxx/queryUrl;
}
} @RestController
public class RedirectController {
@Autowired
private ShortLinkService shortLinkService;
@GetMapping("/{code}")
public ResponseEntity<Void> redirect(@PathVariable String code) {
long id = Base62.decode(code);
String targetUrl = shortLinkService.findLongUrlById(id);
if (targetUrl == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
// async log can be added here
return ResponseEntity.status(HttpStatus.FOUND)
.header("Location", targetUrl)
.build();
}
@PostMapping("/generate")
public ResponseEntity<String> generateShortLink(@RequestBody ShortLinkRequest request) {
long id = shortLinkService.saveLongUrl(request.getLongUrl());
String code = Base62.encode(id);
String shortUrl = "http://34dg.cn/" + code;
return ResponseEntity.ok(shortUrl);
}
}4. Security and Anti‑Scraping
Because Base62 is public, anyone can reverse‑engineer the numeric ID. To mitigate this, a custom shuffled alphabet can be used so that only the service knows the correct order. The alphabet can be rotated periodically (e.g., weekly) to further increase difficulty.
Adding a second‑level directory (e.g., /e/ for marketing, /u/ for user‑generated, /s/ for signed links) creates namespaces that allow different rate‑limiting and policy rules.
Brute‑force analysis shows a 7‑character Base62 code yields ~3.5 trillion possibilities, making exhaustive attacks infeasible under normal traffic, but high‑QPS attacks still require rate limiting. Implement IP‑based sliding‑window limits with Redis, combine with blacklists and risk‑control rules.
Signed short links add a HMAC signature: sig = trunc5(HMAC(K, code)) During access, the server recomputes the signature and compares it with the sig query parameter; mismatches reject the request.
5. Additional Features
To turn the short‑link service into a full platform, the following capabilities are recommended:
Management UI for creating, editing, and monitoring links.
Real‑time analytics dashboard showing clicks, geographic distribution, device types, and source channels.
Expiration policies (time‑based, one‑time use, manual disable).
Reporting for conversion rates, abnormal traffic, and business insights.
Alerting on suspicious spikes or potential attacks.
Multi‑tenant support for different merchants or business lines, each with isolated namespaces and permissions.
Combining these functions with the core encoding, routing, and security mechanisms yields a robust, scalable short‑link system suitable for large‑scale marketing, user invitations, and secure link distribution.
Lin is Dream
Sharing Java developer knowledge, practical articles, and continuous insights into computer engineering.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
