SpringBoot实现短链接系统的使用示例

 更新时间:2023年09月25日 10:13:08   作者:笑的像个child  
由于短链接可能涉及到用户隐私和安全问题,所以短链接系统也需要符合相关的数据保护和安全标准,本文主要介绍了SpringBoot实现短链接系统的使用示例,感兴趣的可以了解一下

前言

短链接系统是一种将较长的URL(统一资源定位符)转换为较短的URL的服务。这种服务通常被用于URL分享,因为较短的URL更加方便用户复制和粘贴,也更容易在社交媒体和其他在线平台分享。本文使用了SpringBoot开发了一个简易的短链接转换接口,和短链接重定向接口。

一、短链接系统入门🍉

1. 什么是短链接系统?

短链接系统是一种将较长的URL转换成较短URL的服务。当用户点击短链接时,他们会被重定向到原始URL。短链接系统在社交媒体平台(如微博)上特别有用,因为在这些平台上,限制了可以发布的文字数量。使用短链接服务可以节省空间,使URL更短,更方便用户输入。

short

短链接有什么优势:

  • 便捷分享: 短链接更短、更易分享,适用于社交媒体、短信、邮件等场景,提供更美观的外观。
  • 提高用户体验: 短链接可以简化用户输入,减少用户访问链接时的操作,提升用户体验
  • 推广和营销: 短链接可以用于推广和营销活动,跟踪广告点击和转化率,帮助优化营销策略

2. 准备工作

(1)创建一个maven项目

image-20230919220537040

(2)引入相关依赖

继承spring boot parent项目

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.0</version>
</parent>

引入spring boot maven插件

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

引入spring boot提供的starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

引入lombok插件

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

引入ORM框架JPA

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

引入Google开源的Java库

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1.1-jre</version>
</dependency>

增加application.yaml配置文件

server:
  port: 8888
spring:
  application:
    name: shorten-service
  datasource:
    url: jdbc:mysql://localhost:3306/shorten_db?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    username: root
    password: xxxx #修改成自己的密码
  jpa:
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        show_sql: true
        format_sql: true

(3)创建启动类

package org.shortenservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ShortenServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShortenServiceApplication.class, args);
    }
}

(4)自定义RESTful结果封装类

public class ResponseResult<T> {
    private String code;
    private String msg;
    private T data;
 }

(5)创建响应工具类

package org.shortenservice.common;
public class ResultUtils {
    private ResultUtils() {}
    public static <T> ResponseResult success(T data) {
        return build("200", "success", data);
    }
    public static ResponseResult success() {
        return build("200", "success", null);
    }
    public static boolean isSuccess(String code) {
        return "200".equals(code);
    }
    public static ResponseResult failure(String msg) {
        return build("500", msg, null);
    }
    public static ResponseResult failure(String code, String msg) {
        return build(code, msg, null);
    }
    public static <T> ResponseResult failure(String code, String msg, T data) {
        return build(code, msg, data);
    }
    public static  <T> ResponseResult<T> build(String code, String msg, T data) {
        return new ResponseResult<>(code, msg, data);
    }
}

二、核心功能实现🧁

1. 实现Base62编码

package org.shortenservice.utils;
public class Base62Utils {
    private static final String BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    private Base62Utils() {
    }
    public static String idToShortKey(long id) {
        StringBuilder stringBuilder = new StringBuilder();
        while (id > 0) {
            stringBuilder.append(BASE62.charAt((int) (id % 62)));
            id = id / 62;
        }
        while (stringBuilder.length() < 6) {
            stringBuilder.append(0);
        }
        return stringBuilder.reverse().toString();
    }
    public static long shortKeyToId(String shortKey) {
        long id = 0;
        for (int i = 0; i < shortKey.length(); i++) {
            id = id * 62 + BASE62.indexOf(shortKey.charAt(i));
        }
        return id;
    }
}

image-20230921102845288

方法解释

idToShortKey方法:

  • 创建一个StringBuilder对象,用于存储转换后的字符串。
  • 使用while循环,当id大于0时,执行循环体。在循环体中,首先计算id除以62的余数,然后将余数对应的BASE62字符添加到StringBuilder对象中。接着,将id除以62,更新id的值。
  • 当id小于等于0时,跳出循环。此时,StringBuilder对象中的字符串长度可能小于6。为了确保字符串长度为6,使用另一个while循环,在StringBuilder对象的开头添加0,直到其长度达到6。
  • 最后,将StringBuilder对象反转,并将其转换为字符串返回。

shortKeyToId方法:

  • 创建一个名为id的长整型变量,初始值为0。
  • 使用for循环遍历shortKey字符串中的每个字符。在循环体中,首先计算当前字符在BASE62字符串中的索引值,然后将id乘以62,再加上当前字符的索引值。将结果赋值给id。
  • 当所有字符都遍历完毕后,返回id作为最终结果。

2. 创建实体类

package org.shortenservice.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import java.time.Instant;
@Entity
@Table(name = "t_url_map", indexes = {@Index(columnList = "longUrl", unique = true),
@Index(columnList = "expireTime", unique = false)})
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UrlMap {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String longUrl;
    private Instant expireTime;
    @CreationTimestamp
    private Instant creationTime;
}

3. 创建Dao层

package com.shorten.dao;
import com.shorten.model.UrlMap;
import org.springframework.data.repository.CrudRepository;
import java.time.Instant;
import java.util.List;
public interface UrlMapDao extends CrudRepository<UrlMap, Long> {
    UrlMap findFirstByLongUrl(String longUrl);
    List<UrlMap> findByExpireTimeBefore(Instant instant);
}

4. 创建service层

package org.shortenservice.service;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.shortenservice.dao.UrlMapDao;
import org.shortenservice.model.UrlMap;
import org.shortenservice.utils.Base62Utils;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
@Service
@Slf4j
public class UrlMapService {
    @Resource
    UrlMapDao urlMapDao;
    public String encode(String longUrl) {
        UrlMap urlMap = urlMapDao.findFirstByLongUrl(longUrl);
        if (urlMap == null) {
            urlMap = urlMapDao.save(UrlMap.builder()
                    .longUrl(longUrl)
                    .expireTime(Instant.now().plus(30, ChronoUnit.DAYS))
                    .build());
            log.info("create urlMap:{}", urlMap);
        }
        return Base62Utils.idToShortKey(urlMap.getId());
    }
    public Optional<String> decode(String shortKey) {
        long id = Base62Utils.shortKeyToId(shortKey);
        return urlMapDao.findById(id).map(UrlMap::getLongUrl);
    }
}

5. 编写测试接口

package org.shortenservice.controller;
import jakarta.annotation.Resource;
import org.shortenservice.common.ResponseResult;
import org.shortenservice.common.ResultUtils;
import org.shortenservice.service.UrlMapService;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;
import java.util.Map;
@RestController
public class UrlMapController {
    private static final String DOMAIN = "http://127.0.0.1:8888/";
    @Resource
    private UrlMapService urlMapService;
    /***
     * 长链接转短链接
     * @param longUrl 长链接
     * @return ResponseResult
     */
    @PostMapping("/shorten")
    public ResponseResult<Map> shorten(@RequestParam("longUrl") String longUrl) {
        String encode = urlMapService.encode(longUrl);
        return ResultUtils.success(Map.of("shortKey", encode,
                "shortUrl", DOMAIN + encode));
    }
    /***
     * 短链接重定向
     * @param shortKey 短链接
     * @return RedirectView
     */
    @GetMapping("/{shortKey}")
    public RedirectView redirect(@PathVariable("shortKey") String shortKey) {
        return urlMapService.decode(shortKey).map(RedirectView::new)
                .orElse(new RedirectView("/sorry"));
    }
    @GetMapping("/sorry")
    public String sorry() {
        return "抱歉,未找到页面!";
    }
}

6. 使用curl测试

#将长链接转为短链接
curl -XPOST "localhost:8888/shorten?longUrl=https://i.csdn.net/#/user-center/profile?spm=1011.2415.3001.5111"
#访问短链接重定向到目标网站
curl -i "http://127.0.0.1:8888/000003"

image-20230921132429849

数据库中也存储了相关数据

image-20230921131954959

三、系统优化🍱

1. 缓存简介

什么是缓存

缓存(Caching)是一种用于提高数据处理速度的技术,涉及到了计算机硬件、操作系统、应用程序等多个领域。缓存的主要原理是将经常使用或最近使用的数据存储在快速访问的存储设备中,这样在需要这些数据时,就可以更快地获取到它们,从而提高系统的整体性能。

在计算机科学中,缓存通常指的是存储临时数据的地方,这些数据可能是来自于计算过程中的结果,也可能是来自于磁盘、网络等慢速存储设备的数据副本。缓存中的数据通常是根据一定的算法进行管理和替换的,以确保缓存中的数据是最需要或最常用的。

引入的缓存用于解决哪些问题

  • 高并发访问: 在高并发情况下,数据源可能会受到过大的负载,通过缓存可以减轻数据源的压力,提高系统的性能和响应速度。
  • 频繁访问: 对于频繁被访问的数据,通过缓存可以减少重复的数据读取,提高效率。
  • 数据计算: 对于一些需要复杂计算的数据,将计算结果缓存起来可以节省计算时间和资源。
  • 数据共享: 缓存可以在不同的组件、模块或服务之间共享数据,提高数据的可用性和共享性。
  • 离线访问: 缓存可以在断网或无法连接数据源的情况下,仍然提供某些数据的访问能力。

本地缓存 VS 分布式缓存

分布式缓存:

image-20230818150100013

  • 概念: 分布式缓存是一种将缓存数据分布在多个服务器节点上的缓存系统,用于存储和管理大量的数据。

  • 优点:

    • 可扩展性: 分布式缓存可以通过增加节点来实现水平扩展,以应对大规模的数据和高并发访问。
    • 高可用性: 分布式缓存通常采用复制和备份机制,确保即使有节点故障,仍然能够提供可靠的缓存服务。
    • 跨节点共享: 多个应用实例可以共享同一分布式缓存,提高数据共享和协作能力。
    • 灵活的存储后端: 分布式缓存可以支持多种后端存储,如内存、磁盘、数据库等。
  • 缺点:

    • 复杂性: 部署、配置和管理分布式缓存系统可能较为复杂,需要考虑分布式系统的一些挑战,如一致性、网络延迟等。
    • 性能开销: 分布式缓存通常需要在网络上进行数据传输,可能引入一些性能开销。

本地缓存:

cache2

  • 概念: 本地缓存是将缓存数据存储在应用程序的本地内存中,用于临时保存常用的数据。

  • 优点:

    • 简单性: 本地缓存相对较简单,不需要搭建额外的分布式缓存系统。
    • 低延迟: 由于数据存储在本地内存中,本地缓存通常具有低延迟的读取速度。
    • 少量数据: 本地缓存适用于存储相对较小的数据量,不需要进行分布式存储和管理。
  • 缺点:

    • 有限的扩展性: 本地缓存只能在单个应用实例内使用,无法满足多实例和分布式应用的需求。
    • 数据一致性: 不同应用实例的本地缓存可能存在数据不一致的问题,需要额外的机制来解决。

2. 引入Guava

更新service层的代码

package org.shortenservice.service;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.shortenservice.dao.UrlMapDao;
import org.shortenservice.model.UrlMap;
import org.shortenservice.utils.Base62Utils;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
@Service
@Slf4j
public class UrlMapService {
    @Resource
    UrlMapDao urlMapDao;
    @Resource
    LoadingCache<String, String> loadingCache;
    @PostConstruct
    public void init() {
        CacheLoader<String, String> cacheLoader = new CacheLoader<String, String>() {
            @Override
            public String load(String s) throws Exception {
                long id = Base62Utils.shortKeyToId(s);
                log.info("load cache: {}", s);
                return urlMapDao.findById(id).map(UrlMap::getLongUrl).orElse(null);
            }
        };
        loadingCache = CacheBuilder.newBuilder()
                .maximumSize(1000000) // 设置最大缓存大小
                .build(cacheLoader);
    }
    public String encode(String longUrl) {
        UrlMap urlMap = urlMapDao.findFirstByLongUrl(longUrl);
        if (urlMap == null) {
            urlMap = urlMapDao.save(UrlMap.builder()
                    .longUrl(longUrl)
                    .expireTime(Instant.now().plus(30, ChronoUnit.DAYS))
                    .build());
            log.info("create urlMap:{}", urlMap);
        }
        return Base62Utils.idToShortKey(urlMap.getId());
    }
    public Optional<String> decode(String shortKey) {
        return Optional.ofNullable(loadingCache.getUnchecked(shortKey));
    }
}

提示:由于短链接系统通常需要处理大量的用户请求和数据,因此需要具有高效和可扩展性。同时,由于短链接可能涉及到用户隐私和安全问题,短链接系统也需要符合相关的数据保护和安全标准。

到此这篇关于SpringBoot实现短链接系统的使用示例的文章就介绍到这了,更多相关SpringBoot 短链接内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mybatis selectKey赋值未生效的原因分析

    mybatis selectKey赋值未生效的原因分析

    这篇文章主要介绍了mybatis selectKey赋值未生效的原因分析,selectKey 会将 SELECT LAST_INSERT_ID()的结果放入到传入的实体类的主键里面,文中通过代码示例给大家讲解非常详细,需要的朋友可以参考下
    2024-02-02
  • java应用占用内存过高排查的解决方案

    java应用占用内存过高排查的解决方案

    这篇文章主要介绍了java应用占用内存过高排查的解决方案,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-03-03
  • java算法题解Leetcode763划分字母区间示例

    java算法题解Leetcode763划分字母区间示例

    这篇文章主要为大家介绍了java算法题解Leetcode763划分字母区间示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • 详解Java内部类——匿名内部类

    详解Java内部类——匿名内部类

    这篇文章主要介绍了详解Java 匿名内部类的相关资料,帮助大家更好的理解和学习java 内部类的相关知识,感兴趣的朋友可以了解下
    2020-08-08
  • ireport数据表格报表的简单使用

    ireport数据表格报表的简单使用

    这篇文章给大家介绍了如何画一个报表模板,这里介绍下画表格需要用到的组件,文中通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2021-10-10
  • 详解如何在Java项目中实现信号的连续接收

    详解如何在Java项目中实现信号的连续接收

    在Java项目中,信号的连续接收是一项重要的任务,特别是在处理异步事件或者需要对外部事件做出响应时,本篇博客将介绍如何在Java项目中实现信号的连续接收,包括信号的监听、处理和停止等步骤,需要的朋友可以参考下
    2023-11-11
  • Spring IOC注入的两种方式详解以及代码示例

    Spring IOC注入的两种方式详解以及代码示例

    在Spring框架中,依赖注入(Dependency Injection,DI)是通过控制反转(Inversion of Control,IOC)实现的,Spring提供了多种方式来实现IOC注入,本文就给大家介绍两种注入的方式:基于XML和基于注解,文中有详细的代码示例,需要的朋友可以参考下
    2023-08-08
  • SpringBoot进行Web开发的实现

    SpringBoot进行Web开发的实现

    Spring Boot让我们可以快速构建项目并运行web应用,大大简化了Spring的复杂配置,本文主要介绍了SpringBoot进行Web开发的实现,感兴趣的可以了解一下
    2023-10-10
  • java 通过发送json,post请求,返回json数据的方法

    java 通过发送json,post请求,返回json数据的方法

    下面小编就为大家分享一篇java 通过发送json,post请求,返回json数据的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-03-03
  • Java服务中的大文件上传和下载优化技巧分享

    Java服务中的大文件上传和下载优化技巧分享

    在Java服务中处理大文件的上传和下载是一项常见但复杂的任务,为了提供优秀的用户体验和高效的系统性能,我们将探索多种策略和技术,并在每一点上都提供代码示例以便实战应用,需要的朋友可以参考下
    2023-10-10

最新评论