设计一个短连接系统:Java实习生面试实践

设计一个短连接系统:Java实习生面试实践

最近我面试了一个 Java 实习生岗位,面试官提出了一个有趣的问题:设计一个短连接系统。这类问题在实际开发中很常见,比如我们经常在社交媒体或营销活动中看到类似 t.cn/abc123 的短链接,它们背后是如何实现的呢?下面我将分享我的思考过程,并基于 MyBatis 和 Spring Boot 提供一个简单的代码实践。

需求分析

短连接系统的核心功能是将一个长 URL(比如 https://www.example.com/very/long/url/with/parameters)转换为一个短链接(比如 http://short.url/abc123),用户访问短链接时能重定向到原始长 URL。主要需求包括:

  1. 生成短链接:输入长 URL,返回一个唯一的短链接。
  2. 重定向:通过短链接访问时,跳转到对应的长 URL。
  3. 持久化:短链接和长 URL 的映射关系需要存储到数据库。
  4. 唯一性:短链接必须唯一,避免冲突。
  5. 扩展性:系统要支持高并发和大规模 URL 转换。

设计思路

短链接生成算法

短链接通常是 6-8 位字符组成,可以用字母和数字(62 进制:0-9, a-z, A-Z)。我选择以下方案:

  • 使用自增 ID(如数据库主键)生成唯一标识。
  • 将 ID 转换为 62 进制字符串,确保短链接简洁且唯一。
  • 例如:ID 12345 转为 62 进制可能是 3D7

系统架构

  • 前端:用户输入长 URL,获取短链接;访问短链接时跳转。
  • 后端:Spring Boot 提供 RESTful API,MyBatis 操作数据库。
  • 数据库:存储长 URL 和短链接的映射。
  • 流程
    1. 用户提交长 URL,后端生成短链接并存入数据库。
    2. 用户访问短链接,后端查询数据库并返回 302 重定向。

技术选型

  • Spring Boot:快速搭建 RESTful 服务。
  • MyBatis:轻量级 ORM,操作数据库。
  • MySQL:存储 URL 映射关系。

代码实践

以下是基于 MyBatis 和 Spring Boot 的实现。

1. 数据库表设计

sql 复制代码
CREATE TABLE short_url (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    long_url VARCHAR(512) NOT NULL,
    short_code VARCHAR(8) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

2. 项目结构

css 复制代码
short-url-service
├── src
│   ├── main
│   │   ├── java/com/example/shorturl
│   │   │   ├── controller/ShortUrlController.java
│   │   │   ├── service/ShortUrlService.java
│   │   │   ├── mapper/ShortUrlMapper.java
│   │   │   ├── entity/ShortUrl.java
│   │   │   └── ShortUrlApplication.java
│   │   └── resources
│   │       ├── application.yml
│   │       └── mapper/ShortUrlMapper.xml

3. 实体类

java 复制代码
package com.example.shorturl.entity;

public class ShortUrl {
    private Long id;
    private String longUrl;
    private String shortCode;
    private String createdAt;

    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getLongUrl() { return longUrl; }
    public void setLongUrl(String longUrl) { this.longUrl = longUrl; }
    public String getShortCode() { return shortCode; }
    public void setShortCode(String shortCode) { this.shortCode = shortCode; }
    public String getCreatedAt() { return createdAt; }
    public void setCreatedAt(String createdAt) { this.createdAt = createdAt; }
}

4. Mapper 接口

java 复制代码
package com.example.shorturl.mapper;

import com.example.shorturl.entity.ShortUrl;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ShortUrlMapper {
    void insert(ShortUrl shortUrl);
    ShortUrl findByShortCode(String shortCode);
}

5. MyBatis XML

文件:resources/mapper/ShortUrlMapper.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.shorturl.mapper.ShortUrlMapper">
    <insert id="insert" parameterType="com.example.shorturl.entity.ShortUrl" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO short_url (long_url, short_code) VALUES (#{longUrl}, #{shortCode})
    </insert>
    <select id="findByShortCode" parameterType="string" resultType="com.example.shorturl.entity.ShortUrl">
        SELECT * FROM short_url WHERE short_code = #{shortCode}
    </select>
</mapper>

6. 服务层

java 复制代码
package com.example.shorturl.service;

import com.example.shorturl.entity.ShortUrl;
import com.example.shorturl.mapper.ShortUrlMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ShortUrlService {
    private static final String BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
    @Autowired
    private ShortUrlMapper shortUrlMapper;

    public String createShortUrl(String longUrl) {
        ShortUrl shortUrl = new ShortUrl();
        shortUrl.setLongUrl(longUrl);
        
        // 插入数据库,获取自增 ID
        shortUrlMapper.insert(shortUrl);
        String shortCode = toBase62(shortUrl.getId());
        shortUrl.setShortCode(shortCode);
        
        // 更新 shortCode
        shortUrlMapper.insert(shortUrl); // 这里应为 update,简化起见直接重插
        return "http://short.url/" + shortCode;
    }

    public String getLongUrl(String shortCode) {
        ShortUrl shortUrl = shortUrlMapper.findByShortCode(shortCode);
        return shortUrl != null ? shortUrl.getLongUrl() : null;
    }

    private String toBase62(long num) {
        StringBuilder sb = new StringBuilder();
        while (num > 0) {
            sb.append(BASE62.charAt((int) (num % 62)));
            num /= 62;
        }
        return sb.reverse().toString();
    }
}

7. 控制器

java 复制代码
package com.example.shorturl.controller;

import com.example.shorturl.service.ShortUrlService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestController
@RequestMapping("/api")
public class ShortUrlController {
    @Autowired
    private ShortUrlService shortUrlService;

    @PostMapping("/shorten")
    public ResponseEntity<String> shortenUrl(@RequestBody String longUrl) {
        String shortUrl = shortUrlService.createShortUrl(longUrl);
        return new ResponseEntity<>(shortUrl, HttpStatus.OK);
    }

    @GetMapping("/{shortCode}")
    public void redirect(@PathVariable String shortCode, HttpServletResponse response) throws IOException {
        String longUrl = shortUrlService.getLongUrl(shortCode);
        if (longUrl != null) {
            response.sendRedirect(longUrl);
        } else {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }
}

8. 配置文件

文件:resources/application.yml

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/short_url_db
    username: root
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath:mapper/*.xml

9. 主应用

java 复制代码
package com.example.shorturl;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ShortUrlApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShortUrlApplication.class, args);
    }
}

测试

  1. 启动 Spring Boot 应用。
  2. POST 请求 http://localhost:8080/api/shorten,Body 为 {"longUrl": "https://www.example.com"},返回短链接。
  3. 访问 http://localhost:8080/api/abc123,跳转到原始 URL。

优化方向

  • 缓存:用 Redis 缓存短链接映射,提升查询性能。
  • 分布式 ID:高并发下用雪花算法替代自增 ID。
  • 安全性:校验长 URL 合法性,防止恶意输入。

总结

通过这个实践,我学会了如何从需求分析到代码实现设计一个短连接系统。面试官可能会关注我的思路清晰度和代码规范性。这个方案虽然简单,但涵盖了核心功能,是个不错的起点。希望这篇博客对你也有帮助!

相关推荐
cainiao0806053 分钟前
《Spring Boot 4.0新特性深度解析》
java·spring boot·后端
-曾牛15 分钟前
Spring AI 与 Hugging Face 深度集成:打造高效文本生成应用
java·人工智能·后端·spring·搜索引擎·springai·deepseek
南玖yy1 小时前
C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )
c语言·开发语言·c++·笔记·后端·游戏引擎·课程设计
计算机学姐1 小时前
基于SpringBoot的小区停车位管理系统
java·vue.js·spring boot·后端·mysql·spring·maven
BUG制造机.1 小时前
Go 语言 slice(切片) 的使用
开发语言·后端·golang
小鸡脚来咯1 小时前
请求参数:Header 参数,Body 参数,Path 参数,Query 参数分别是什么意思,什么样的,分别通过哪个注解获取其中的信息
java·spring boot·后端
天上掉下来个程小白3 小时前
添加购物车-02.代码开发
java·服务器·前端·后端·spring·微信小程序·苍穹外卖
幽络源小助理4 小时前
懒人美食帮SpringBoot订餐系统开发实现
java·spring boot·后端·美食
源码云商6 小时前
基于Spring Boot + Vue的母婴商城系统( 前后端分离)
java·spring boot·后端
还听珊瑚海吗10 小时前
基于SpringBoot的抽奖系统测试报告
java·spring boot·后端