设计一个短连接系统: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 合法性,防止恶意输入。

总结

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

相关推荐
用户48221371677515 分钟前
C++——复合数据类型(数组、字符串)
后端
用户9037001671519 分钟前
分布式阻塞式限流学习及分享
后端
熊猫片沃子25 分钟前
Mybatis中进行批量修改的方法
java·后端·mybatis
养鱼的程序员27 分钟前
零基础搭建个人网站:从 Astro 框架到 GitHub 自动部署完全指南
前端·后端·github
白应穷奇36 分钟前
编写高性能数据处理代码 01
后端·python
杨充1 小时前
03.接口vs抽象类比较
前端·后端
一只叫煤球的猫1 小时前
基于Redisson的高性能延迟队列架构设计与实现
java·redis·后端
卡尓1 小时前
使用 Layui 替换 Yii 基础模板的默认 Bootstrap 样式并尝试重写导航栏组件
后端
WhyWhatHow1 小时前
JEnv:新一代Java环境管理器,让多版本Java管理变得简单高效
java·后端
Rust语言中文社区1 小时前
Rust 训练营二期来袭: Rust + AI 智能硬件
开发语言·后端·rust