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

总结

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

相关推荐
来自星星的坤5 小时前
SpringBoot 与 Vue3 实现前后端互联全解析
后端·ajax·前端框架·vue·springboot
AUGENSTERN_dc6 小时前
RaabitMQ 快速入门
java·后端·rabbitmq
烛阴6 小时前
零基础必看!Express 项目 .env 配置,开发、测试、生产环境轻松搞定!
javascript·后端·express
燃星cro6 小时前
参照Spring Boot后端框架实现序列化工具类
java·spring boot·后端
追逐时光者9 小时前
C#/.NET/.NET Core拾遗补漏合集(25年4月更新)
后端·.net
FG.9 小时前
GO语言入门
开发语言·后端·golang
转转技术团队10 小时前
加Log就卡?不加Log就瞎?”——这个插件治好了我的精神
java·后端
谦行10 小时前
前端视角 Java Web 入门手册 5.5:真实世界 Web 开发——控制反转与 @Autowired
java·后端
uhakadotcom10 小时前
PyTorch 2.0:最全入门指南,轻松理解新特性和实用案例
后端·面试·github
bnnnnnnnn10 小时前
前端实现多服务器文件 自动同步宝塔定时任务 + 同步工具 + 企业微信告警(实战详解)
前端·javascript·后端