ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java短链接生成工具源码解析:短码生成、302跳转与访问统计

Java短链接生成工具源码解析:短码生成、302跳转与访问统计

简介:这份源码资源面向Java后端开发者与全栈学习者,提供一套完整的短链接生成工具实现方案,可用于链接管理、访问数据统计与AB测试等场景。项目融合Java、Vue、JavaScript、CSS与HTML等技术栈,共280个文件,其中187个Java源文件构成核心业务逻辑,19个Vue组件负责前端交互,另有XML、YAML、JS、JSON等配置与脚本文件,以及PNG、SVG等界面素材,压缩包约1.44MB,结构清晰便于二次开发。功能上支持将原始网页链接缩短为简洁美观的短链,记录每次访问的详细信息,并生成地区分布、设备类型等专业统计图表;同时允许随时修改跳转目标、批量创建短链,兼顾灵活性与管理效率。目前已有329人学习下载,适合希望掌握短链系统设计、数据统计与前后端协作的中高级开发者参考借鉴。

1. 从一堆散乱文件到可跑的短链接服务:这套 Java 源码到底值不值得拆

拿到一个压缩包,解压后第一眼看到的是.eslintrc.cjs、base.css、main.css、.env.development、notfound.html、index.html、favicon.ico、short-Link.iml这一串文件,很多人会先愣一下——这到底是前端项目还是后端项目?答案藏在文件构成里:280 个文件,其中 187 个 Java 源文件、19 个 Vue 组件、22 张 PNG、11 个 XML 和若干 JavaScript、JSON 配置。这是一个前后端分离的短链接生成工具,后端用 Java 扛住链接映射与访问统计,前端用 Vue 做管理界面,核心能力是把冗长的原始网页链接压缩成短小精悍的跳转地址,同时记录每一次点击的地区、设备、时间,并支持 AB 测试分流。适合谁?正在做课程设计、需要一套能跑通「生成—跳转—统计」闭环的 Java 开发者,以及想拿真实项目练手 Spring Boot + MyBatis + Vue 全栈链路的人。它不解决高并发亿级短链的架构问题,但把短链系统该有的基础模块都摆在了桌面上。

2. 拆开 187 个 Java 文件:短链接生成与跳转的核心链路

2.1 短码生成策略:自增 ID 转 62 进制还是哈希截断

短链接工具最核心的一步,是把一个可能几百字符的原始 URL 映射成一个 6 到 8 位的短码。这套源码里常见做法是维护一张自增主键表,拿到 ID 后转成 62 进制字符串。为什么不用 MD5 或 MurmurHash 直接截断?因为哈希截断存在碰撞概率,一旦碰撞就需要重新哈希并查重,逻辑分支多,而自增 ID 转 62 进制天然唯一,实现干净。62 进制指的是0-9、a-z、A-Z共 62 个字符,6 位长度可表达 62^6 ≈ 568 亿个组合,对课程设计或中小规模部署完全够用。

下面是我从源码里提炼出的短码生成核心逻辑,用 Java 写出来大致是这样:

public class ShortCodeGenerator { // 62 进制字符表,顺序固定,保证编码解码可逆 private static final String CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final int BASE = CHARS.length(); /** * 将自增 ID 转为 62 进制短码 * @param id 数据库自增主键,从 1 开始 * @return 短码字符串,如 1 -> "1", 62 -> "10" */ public static String encode(long id) { StringBuilder sb = new StringBuilder(); while (id > 0) { int remainder = (int) (id % BASE); sb.append(CHARS.charAt(remainder)); id = id / BASE; } return sb.reverse().toString(); } /** * 将短码还原为 ID,用于跳转时反查原始链接 */ public static long decode(String code) { long id = 0; for (int i = 0; i < code.length(); i++) { int index = CHARS.indexOf(code.charAt(i)); id = id * BASE + index; } return id; } }

逻辑说明:encode方法不断取模得到当前位字符,再整除进入下一轮,最后反转字符串。decode是逆运算,逐位累加。参数上唯一需要留意的是CHARS的顺序一旦上线就不能改,否则已生成的短码全部失效。常见做法是在数据库里存短码和原始 URL 的映射,跳转时先decode拿到 ID 再查库,或者直接拿短码做唯一索引查询,后者更直观。

2.2 跳转控制器与 302 重定向的取舍

生成短链只是第一步,用户点击短链后能不能正确跳到原始地址,才是这个工具能不能用的分水岭。源码里跳转逻辑通常落在一个@Controller或@RestController上,路径映射为/{shortCode}。这里有一个容易被忽略的选型点:用 301 还是 302?

301 是永久重定向,浏览器会缓存,后续点击不再请求你的服务器,访问统计就丢了。302 是临时重定向,每次点击都会打到后端,统计才能记全。这套工具既然主打访问量统计和地区设备分析,就必须用 302。我一般会在控制器里这样写:

@RestController public class RedirectController { @Autowired private ShortLinkService shortLinkService; @Autowired private AccessLogService accessLogService; /** * 短码跳转入口 * @param shortCode 路径中的短码 * @param request 用于提取 IP、User-Agent * @return 302 重定向到原始 URL */ @GetMapping("/{shortCode}") public ResponseEntity<Void> redirect(@PathVariable String shortCode, HttpServletRequest request) { // 1. 查短码对应的原始链接,查不到返回 404 String originalUrl = shortLinkService.getOriginalUrl(shortCode); if (originalUrl == null) { return ResponseEntity.notFound().build(); } // 2. 异步记录访问日志,避免阻塞跳转 accessLogService.asyncLog(shortCode, request); // 3. 302 临时重定向,保证每次点击都经过服务端 HttpHeaders headers = new HttpHeaders(); headers.setLocation(URI.create(originalUrl)); return new ResponseEntity<>(headers, HttpStatus.FOUND); } }

逻辑说明:先查原始链接,查不到直接 404,避免无效跳转。访问日志用异步方式写入,防止数据库写入延迟拖慢跳转响应。HttpStatus.FOUND就是 302。参数上,shortCode来自路径变量,request用来拿X-Forwarded-For或RemoteAddr以及User-Agent,后续统计地区分布和设备信息全靠这两个字段。注意如果部署在 Nginx 后面,要确保X-Forwarded-For被正确透传,否则统计到的 IP 全是 Nginx 本机地址。

2.3 访问统计的数据落点:日志表字段怎么设计

统计功能能不能出图表,取决于访问日志表字段设计得够不够细。源码里通常有一张access_log表,核心字段包括短码、访问 IP、User-Agent、Referer、访问时间。地区分布靠 IP 解析,设备信息靠 User-Agent 解析。常见做法是引入一个 IP 库(如 ip2region)做离线解析,避免调用外部接口。User-Agent 解析可以用user-agent-utils这类库,识别出浏览器、操作系统、是否移动端。

表结构大致如下:

字段名类型说明
idbigint自增主键
short_codevarchar(16)短码,关联短链表
access_ipvarchar(64)访问者 IP,兼容 IPv6
user_agentvarchar(512)原始 UA 字符串
referervarchar(512)来源页面
access_timedatetime访问时间,建索引
regionvarchar(64)IP 解析后的地区
device_typevarchar(32)设备类型:PC / Mobile

参数说明:short_code加索引,因为按短码聚合查询最频繁。access_time加索引,用于按天、按小时出趋势图。region和device_type是解析后的冗余字段,写入时算好,查询时不用再解析,用空间换时间。如果数据量预期不大,这张表可以一直留着;如果要做 AB 测试分流统计,还需要加一个ab_group字段标记用户落在哪个实验组。

3. 把项目跑起来:环境配置、数据库初始化与前端联调

3.1 后端启动前的环境变量与数据库准备

这套源码里出现了.env.development,说明前端有环境变量配置,后端一般对应application-dev.yml或application.properties。启动前需要确认三件事:JDK 版本、MySQL 连接、Redis 是否必须。187 个 Java 文件的项目通常基于 Spring Boot,JDK 8 或 11 都能跑,但要看pom.xml里的编译插件配置。数据库方面,短链映射表和访问日志表需要先建好,源码里一般带schema.sql或init.sql,没有的话按上一节的表结构手动建。

我一般会先改配置文件里的数据库连接:

# application-dev.yml spring: datasource: url: jdbc:mysql://localhost:3306/shortlink?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 # 如果源码里用 Redis 做短码缓存,这里必须配

逻辑说明:serverTimezone必须设对,否则访问时间统计会出现 8 小时偏差,图表按天聚合时数据会错位。Redis 不是所有短链项目都强依赖,但如果源码里用了缓存注解或手动缓存短码到原始链接的映射,不启动 Redis 会直接报连接异常。判断方法很简单:全局搜一下RedisTemplate或@Cacheable,有就装一个,没有就注释掉相关配置。

3.2 前端 Vue 工程的依赖安装与接口代理

19 个 Vue 组件说明前端有一定规模,不是单页面 demo。前端目录下通常有package.json,先装依赖再跑开发服务器。.eslintrc.cjs的存在意味着代码有 lint 规则,如果本地 ESLint 版本和项目不一致,启动时可能报一堆格式错误,但不影响运行,可以先忽略。

# 进入前端目录 cd short-link-frontend # 安装依赖,建议用 npm ci 而不是 npm install,锁版本更稳 npm ci # 启动开发服务器 npm run serve

逻辑说明:npm ci会严格按照package-lock.json安装,避免依赖版本漂移导致构建失败。启动后前端一般跑在 8080 或 8081,后端跑在 9090 或 8080,端口冲突时改vue.config.js里的devServer.port。接口代理通常在vue.config.js的proxy字段配置,把/api前缀转发到后端地址。如果前端请求全部 404,先看代理有没有配,再看后端有没有开跨域。常见做法是后端加@CrossOrigin或者前端代理解决,二选一即可。

3.3 批量创建与修改跳转目标的接口调用

摘要里提到支持批量创建和随时修改短链跳转的原始链接,这两个功能对应后端两个接口:批量插入和更新。批量创建时,如果循环调用单条插入,数据库压力大且慢,常见做法是用 MyBatis 的<foreach>标签拼批量插入 SQL。修改跳转目标则是UPDATE short_link SET original_url = ? WHERE short_code = ?,注意改完之后如果 Redis 里有缓存,要同步删除或更新缓存,否则跳转还是旧地址。

// 批量创建示例,Mapper 层 @Insert("<script>" + "INSERT INTO short_link (short_code, original_url, create_time) VALUES " + "<foreach collection='list' item='item' separator=','>" + "(#{item.shortCode}, #{item.originalUrl}, NOW())" + "</foreach>" + "</script>") int batchInsert(@Param("list") List<ShortLink> list);

逻辑说明:<foreach>把 List 展开成多条 VALUES,一次 SQL 完成批量插入。参数list是待插入的短链对象集合,每个对象需要提前生成好短码。注意批量插入时短码不能重复,如果短码基于自增 ID 生成,批量场景下需要先批量取号再生成,否则会冲突。修改跳转目标后,缓存处理是血泪经验:只改库不删缓存,测试时怎么点都是旧链接,排查半天才发现是 Redis 在作祟。

4. 避坑与排查:短链跳转失败、统计不准、AB 测试串组

4.1 短链 404:短码生成后没落库或落库事务未提交

现象:生成接口返回了短码,但浏览器访问短链直接 404。原因通常有两种,一是生成短码后插入数据库失败但接口没抛异常,二是插入操作在事务里但事务还没提交,跳转请求已经打过来了。解决:生成接口里插入后立即返回,确保事务提交;或者在跳转查询时加一个短延迟重试。更稳妥的做法是生成和查询走同一个数据库连接池,避免主从延迟。

4.2 统计图表地区全是「未知」:IP 解析库没加载或内网 IP 未处理

现象:访问日志有记录,但地区分布图全是「未知」或空白。原因:IP 解析库文件路径不对,或者访问 IP 是127.0.0.1、192.168.x.x这类内网地址,解析库查不到。解决:确认 ip2region 的xdb文件放在 resources 目录下且被正确读取;本地测试时手动把内网 IP 映射为「本地」,或者用 Nginx 透传真实公网 IP。

4.3 设备信息全显示 PC:User-Agent 解析顺序写反

现象:手机访问短链,统计里设备类型还是 PC。原因:User-Agent 解析时先判断了桌面浏览器特征,再判断移动端特征,而很多移动端 UA 同时包含桌面标识。解决:调整判断顺序,先匹配Mobile、Android、iPhone等移动关键词,再 fallback 到 PC。这个坑很隐蔽,因为 UA 字符串本身没错,错的是解析逻辑的优先级。

4.4 AB 测试流量串组:分组标识没绑定用户或短码

现象:同一个用户刷新页面,一会儿进 A 组一会儿进 B 组,统计数据混乱。原因:AB 分组时用了随机数,每次请求都重新随机。解决:分组标识要绑定一个稳定维度,比如短码本身、用户 IP 哈希、或者 Cookie 里的用户 ID。常见做法是对短码做哈希取模,保证同一个短链的所有访问稳定落在同一组,除非业务要求按用户分流。

4.5 修改原始链接后跳转仍旧地址:缓存未失效

现象:后台把短链的原始 URL 改了,前台点击还是跳到旧地址。原因:短码到原始链接的映射被缓存了,更新数据库时没删缓存。解决:更新逻辑里加一步缓存删除,或者设置较短的缓存过期时间。如果用的是 Spring Cache,@CacheEvict注解别忘了加。这个坑我踩过不止一次,后来养成习惯:凡是更新操作,先想缓存键是什么,再写删除逻辑。

5. 进阶玩法:用 AB 测试字段做灰度分流与统计验证

AB 测试是这套源码里比较有意思的一块。基础短链只做跳转,AB 测试短链要在跳转时决定去哪个目标地址,同时把分组信息写进访问日志,后续统计才能对比两组的表现。实现上通常是在短链表里加一个ab_config字段,存 JSON 格式的分组配置,比如{"A":"https://a.com","B":"https://b.com","ratio":50},跳转时根据短码哈希取模决定走 A 还是 B。

下面是一个简化的分流逻辑:

public String resolveAbTarget(ShortLink link, String shortCode) { // 没有 AB 配置,直接返回原始链接 if (link.getAbConfig() == null) { return link.getOriginalUrl(); } AbConfig config = JSON.parseObject(link.getAbConfig(), AbConfig.class); // 用短码哈希保证同一短链稳定分流,避免同一用户来回跳 int hash = Math.abs(shortCode.hashCode()); int bucket = hash % 100; String group = bucket < config.getRatio() ? "A" : "B"; // 把分组写回访问日志上下文,供统计使用 AccessContext.setAbGroup(group); return "A".equals(group) ? config.getUrlA() : config.getUrlB(); }

逻辑说明:shortCode.hashCode()对同一个短码永远返回相同值,保证分流稳定。ratio是 A 组百分比,比如 50 表示五五开。AccessContext是一个 ThreadLocal 上下文,跳转控制器在记录日志时从里面取abGroup写入数据库。参数上唯一要调的是ratio,改完立即生效,不需要重启。验证方法:连续访问同一个短链 20 次,看日志里 A/B 比例是否接近配置值,如果偏差过大,检查哈希分布是否均匀,必要时换成 MurmurHash。

统计验证这块,我一般会写一个简单的对账 SQL,按天和分组聚合访问量:

SELECT DATE(access_time) AS day, ab_group, COUNT(*) AS pv, COUNT(DISTINCT access_ip) AS uv FROM access_log WHERE short_code = 'abc123' GROUP BY DATE(access_time), ab_group ORDER BY day DESC;

逻辑说明:pv是页面访问量,uv按 IP 去重。对比 A/B 两组的uv和后续转化(如果有的话),就能判断哪个目标地址更有效。注意access_time的时区要和业务预期一致,否则按天聚合会把凌晨的数据算到前一天。从那以后我每次部署短链服务,都会先跑一遍这个对账 SQL,确认统计链路没有断,再去看图表。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进