第一句子大全,网罗天下好句子,好文章尽在本站!

如何使用Java和Spring Boot创建短链接生成器

时间:2024-01-07

通常,短链接的长度只有原始URL的三分之一、甚至四分之一

友情提示:本文共有 9151 个字,阅读大概需要 19 分钟。

URL短链接生成器是一种根据冗长的URL,创建短链接的服务。通常,短链接的长度只有原始URL的三分之一、甚至四分之一。因此它们更容易被输入、呈现、以及推送。用户只需单击短链接,便可被自动重定向到原始的URL处。

目前,tiny.cc、bitly.com和cutt.ly都能够提供在线式的URL缩短服务。当然,您也可以为应用系统自行设计和开发出缩短URL的服务。下面,我和您讨论具体的实现过程。首先,让我们来探讨一下与之相关的功能性和非功能性的需求。

功能要求:

保存用户输入的长URL,并据此生成相应的短链接。

允许用户选择到期日期,以便生成的短链接在该日期后自动无效。

方便用户在单击短链接后,重定向到原始的长链接处。

作为可选的方式,允许用户创建服务帐户,并让生成的短链接仅对该账户有效。

以可选的方式,允许用户自行创建短链接。

以可选的方式,允许用户标记出那些最常访问的链接。

非功能性要求:

生成服务具有持续的有效性和可访问性。

重定向的用时应不超过2秒。

URL转换的方式

URL短链接生成器中最重要的是转换算法。不同的转换方式通常会产生不同的输出,而且它们各有优、缺点。假设我们需要一个最长为7个字符的短链接。那么我们可以采用MD5或SHA-2之类的哈希函数,对原始的URL进行散列处理。由于散列的结果会超过7个字符,因此我们只取前7个字符。不过,由于前7个字符可能已经被用于其他短链接,并由此会引发冲突,因此我们需要依次截取后面的7个字符,直至找到一个被使用过的短链接为止。

生成短链接的第二种方法是使用UUID。UUID被复制的概率近似为零,因此可以完全忽略冲突的可能。由于UUID是由36个字符组成,仍然可能遇到上述问题,因此我们应当截取前7个字符,然后检查该组合是否已被占用。

第三种方法是将数字从Base 10转换为Base 62。Base是可用于表示特定数字的字符数。Base 10是我们日常生活中使用的数字,即:[0-9],而Base 62则是:[0-9][az][AZ]。这意味着,以10为Base的四位数字,将与以62为Base、但具有两个字符的数字相同。因此在URL转换中,使用最大长度为7个字符的Base 62,将允许我们为短链接提供62^7个唯一值。

Base 62的转换机制

我使用如下算法,将一个Base为10的数字转换为Base为62:

while(number > 0) remainder = number % 62 number = number / 62 attach remainder to start of result collection

据此,我们只需要将结果集中的数字映射到Base为62的字符 [0,1,2,...,a,b,c...,A,B,C,...]即可。

下面,我通过将1000从Base 10转换为Base 62的例子,来讨论其工作机制。

1st iteration: number = 1000 remainder = 1000 % 62 = 8 number = 1000 / 62 = 16 result list = [8] 2nd iteration: number = 16 remainder = 16 % 62 = 16 number = 16 / 62 = 0 result list = [16,8] There is no more iterations since number = 0 after 2nd iteration

[16,8] 被映射到Base 62后为g8,即1000base10 = g8base62。

而从Base 62转换为Base 10的过程也很简单,即:

i = 0 while(i < inputString lenght) counter = i + 1 mapped = base62alphabet.indexOf(inputString[i]) // map character to number based on its index in alphabet result = result + mapped * 62^(inputString lenght - counter) i++

所以其对应的代码示例为:

inputString = g8 inputString length = 2 i = 0 result = 0 1st iteration counter = 1 mapped = 16 // index of g in base62alphabet is 16 result = 0 + 16 * 62^1 = 992 2nd iteration counter = 2 mapped = 8 // index of 8 in base62alphabet is 8 result = 992 + 8 * 62^1 = 1000

实现

我使用Spring Boot和MySQL来实现该服务。我用到了数据库的自动递增功能来实现Base 62的转换。当然,您也可以使用任何其他具有自动递增功能的数据库。

首先,请访问Spring initializr,并选择Spring Web与MySQL Driver。接着,请单击“生成(Generate)”按钮,并下载对应的zip文件。完成解压缩之后,我们就可以在自己的IDE中打开该项目了。

我通过创建文件夹:控制器、实体、服务、存储库、dto和配置,实现在逻辑上划分程序代码。

在“实体”文件夹中,我创建了一个具有id、longUrl、createdDate和expiresDate四个属性的Url.java类。

请注意,此处既没有短链接的属性,也不会去保存短链接。每次只要有GET请求的出现,我们都会将id属性从Base 10转换为Base 62,以便节省数据库中的空间。

用户在访问该短链接时,应根据longURL属性重定向到目标网站。createdDate则只是为了查看longURL何时被保存(并不重要)。而如果用户希望在一段时间后让短链接失效的话,可以对expiresDate进行设置。

接着,我在“服务”文件夹中,创建了一个BaseService.java文件。其中包含了从Base 10到Base 62相互转换的方法。

private static final String allowedString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; private char[] allowedCharacters = allowedString.toCharArray(); private int base = allowedCharacters.length;

正如前面所提到的,若要使用Base 62转换,则需要有一个被称为allowedCharacters的Base 62的字母表。此外,为了方便按需更改被允许的字符,我们可根据字符的长度,计算出基本变量的值。其中,编码(encode)方法会将一个数字作为输入,返回一个短链接;而解码(decode)方法则会接受一个字符串(如:短链接)作为输入,并返回一个数字。

在存储库文件夹中,我创建了UrlRepository.java文件。它只是JpaRepository的一个扩展,并给出了诸如“findById”,“save”等方法。在此,我们无需进行任何添加。

然后,我在“控制器”文件夹中创建了一个URLController.java文件(请参见如下代码)。它提供一种用于创建短链接的POST方法,以及一种被用于重定向到原始URL的GET方法。

@PostMapping("create-short") public String convertToShortUrl(@RequestBody UrlLongRequest request) { return urlService.convertToShortUrl(request); } @GetMapping(value = "{shortUrl}") public ResponseEntity getAndRedirect(@PathVariable String shortUrl) { var url = urlService.getOriginalUrl(shortUrl); return ResponseEntity.status(HttpStatus.FOUND) .location(URI.create(url)) .build(); }

其中,POST方法会将UrlLongRequest作为请求体。它是一个具有longURL和expiresDate属性的类。而GET方法会将一个短的URL作为路径变量,以获取并重定向到原始的URL处。

在控制器的上层,urlService会作为依赖项被注入,以便后续进行解释。

UrlService.java既包含了大量逻辑,又为控制器提供了服务。ConvertToShortUrl仅供控制器的POST方法所使用。它只是在数据库中创建了一条新的记录,并获取一个id,以便将其转换为Base 62的短链接,并返回给控制器。

控制器使用GetOriginalUrl方法,首先将字符串转换为Base 10类型的id。然后,它通过该id从数据库中获取一条记录。当然,如果该记录不存在的话,则会抛出异常。最后,它会将原始的URL返回给控制器。

下面,我将和您讨论Swagger文档、应用的dockerization(容器化)、缓存以及MySQL的计划事件。

Swagger的用户界面

在开发过程中文档记录无疑能够使得API更易于理解和使用。在该项目中,我使用Swagger UI来记录API。Swagger UI允许任何人在没有任何实现逻辑的情况下,可视化API资源,并与之交互。它不但能够自动生成,而且带有可视化的文档,以便于后端的实现和客户端的使用。

我通过执行如下步骤,在项目中引入了Swagger UI。首先,我在pom.xml文件中添加了Maven依赖项:

XML io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2

添加了Maven依赖项后,我们便可以添加Swagger的相关配置了。我在“配置”文件夹中,创建了一个新的类--SwaggerConfig.java,请参考如下代码段。

Java

@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket apiDocket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(metadata()) .select() .apis(RequestHandlerSelectors.basePackage("com.amarin")) .build(); } private ApiInfo metadata(){ return new ApiInfoBuilder() .title("Url shortener API") .description("API reference for developers") .version("1.0") .build(); } }

在该类的顶部,我添加了如下注释:

@Configuration表示一个类声明了一到多个@Beans方法,并且可以由Spring容器通过处理,在运行时为这些bean生成相应的定义和服务请求。

@EnableSwagger2表示应该启用Swagger支持。

接下来,我添加了Docket bean。它提供的主要API配置,带有各种合理的默认值、以及便捷的配置方法。

此处的apiInfo()方法除了可以使用默认值,还能够接受ApiInfo对象,以便我们配置所有必要的API信息。为了使代码更加简洁,我们可以创建一个私有的方法—metadata(),来配置和返回ApiInfo对象,并将该方法作为apiInfo()方法的参数进行传递。同时,apis()方法也允许我们过滤那些被文档化的包。

在完成了Swagger UI的配置后,我们便可以开始文档化API了。在UrlController内部的每个端点上,我们可以使用@ApiOperation来添加描述性的注释。当然,您也可以按需使用其他类型的注释。

我们还可以文档化DTO,并使用@ApiModelProperty来添加各种允许的值和描述。

缓存

根据维基百科的定义,缓存是存储数据的软、硬件组件,可用来更快地处理后续对于相同数据的请求。而存储在缓存中的数据,往往是早期计算的结果、或是已存储在其他地方的数据副本。

目前,最常用的缓存类型是内存缓存(in-memory cache)。它能够将缓存的数据存储到RAM中。当被请求数据与缓存一致时,它是从RAM、而非从数据库被提取。据此,我们避免频繁调用后端的开销。

由于URL短链接生成器可以被认为是一种读取多于写入的请求应用,因此它是使用缓存的理想应用场景。若想在Spring Boot应用中启用缓存,我们只需要在UrlShortenerApiApplication类中添加@EnableCaching注释即可。

接着,在控制器中,我们需要在GET方法上设置@Cachable注解,以实现自动将方法调用的结果存入缓存中。在@Cachable的注解中,我设置了缓存名称的value参数和缓存键的key参数。鉴于缓存键的唯一性,我使用了“shortUrl”,并将Sync参数设置为true,以确保只有一个线程正在构建缓存值。

至此,当我们首次加载带有短链接的URL时,其结果将会被保存到缓存中。后续,任何端点若想调用相同短链接,都会从缓存、而非从数据库中检索结果。

Dockerization

Dockerization是将应用程序及其依赖项打包到Docker容器中的过程。一旦配置了Docker容器,我们便可以轻松地在任何支持Docker的服务器、或主机上运行应用程序。

因此,我们首先需要创建一个包含所有命令的Dockerfile文本文件,以便用户通过调用命令行的方式,挂载某个镜像。

Dockerfile

FROM openjdk:13-jdk-alpine COPY ./target/url-shortener-api-0.0.1-SNAPSHOT.jar /usr/src/app/url-shortener-api-0.0.1-SNAPSHOT.jar EXPOSE 8080 ENTRYPOINT ["java","-jar","/usr/src/app/url-shortener-api-0.0.1-SNAPSHOT.jar"]

FROM:表示需要构建的基础镜像。我使用的是Java免费开源版--OpenJDK v13。您也可以在共享的Docker镜像平台--Docker hub(https://hub.docker.com/)上,找到其他类型base镜像。

COPY:此命令会将文件从本地文件系统,复制到指定路径的容器文件系统中。在此,我将目标文件夹中的JAR文件,复制到容器中的/usr/src/app文件夹中(稍后我将解释如何创建JAR文件)。

EXPOSE:负责通知Docker容器在运行时,侦听指定网络端口的指令。其默认协议为TCP,您也可以使用UDP。

ENTRYPOINT:负责配置可执行的容器。在此,我通过命令为“java -jar

为了在项目中创建.jar文件,以便Dockerfile中的COPY命令能够正常工作,我使用Maven来创建可执行的.jar。如果您的pom.xml缺少Maven,请用如下方式进行添加:

XML

org.springframework.boot spring-boot-maven-plugin

随后,我运行命令:mvn clean package,以构建出一个Docker镜像。接着,在Dockerfile文件夹中,我运行了命令:docker build -t url-shortener:latest。其中,-t可用于标记一个镜像,并实现版本控制。在此,即为最新的存储库URL-shortener。我们可以使用命令“docker images”来创建镜像。屏幕上的显示结果为:

最后,我还需要在docker容器中构建MySQL服务器镜像,以方便数据库容器与应用容器相隔离。为此,我在Docker容器中运行了如下命令:

$ docker run --name shortener -e MYSQL_ROOT_PASSWORD=my-secret-pw -d -p 3306:3306 mysql:8

为了在容器内运行数据库,我通过配置,将现有的应用程序连接上该MySQL服务器。即:在application.properties中设置spring.datasource.url,以连接到shortener容器。

然后,我使用以下命令来运行已构建好的Docker 镜像容器:

docker run -d –-name url-shortener-api -p 8080:8080 --link shortener url-shortener

-d表示Docker容器在终端的后台运行。

--name可设置容器的名称。

-p host-port:docker-port:是将本地端口映射到容器内的端口上。在本例中,我在容器内公开了端口8080,并映射到了本地的8080上。

--link:用于链接应用容器与数据库容器,以实现容器间的相互发现和安全传输。

url-shortener:则指明了待运行的Docker镜像名称。

至此,我们便可以在浏览器中访问http://localhost:8080/swagger-ui.html了。通过将镜像发布到Docker Hub上,任何计算机和服务器都可以轻松地运行该应用。

当然,为了改善该Docker的使用体验,我们需要注意多阶段构建,以及docker-compose两个方面。

多阶段构建

使用多阶段构建,您将可以在Dockerfile中使用多个FROM语句。每个FROM指令都可以使用不同的base,并且每个指令都能够开启构建的新阶段。您可以有选择性地将各个工件(artifacts)从一个阶段复制到另一个阶段,并在最终镜像中去掉不想要的内容。

多阶段构建有利于我们避免每次对代码进行更改后,都必须手动重建.jar文件。据此,我们可以定义一个构建阶段,来执行Maven包命令。而另一个阶段会将来自第一次构建的结果,直接复制到Docker容器的文件系统中。

Docker-compose

Compose是一个用于定义和运行多容器Docker应用的工具。借助Compose,您可以使用YAML文件,来配置应用程序的服务,然后使用单个命令,从配置中创建并启动所有的服务。

使用docker-compose,我们能够将应用程序和数据库打包到一个配置文件中,以便立即运行所有的内容。据此,我们避免了每次去运行MySQL容器,将其链接到应用容器的繁琐。

由Docker-compose.yml文件的具体配置内容可知:首先,我们通过设置镜像mysql v8.0和MySQL服务器的凭据,来配置MySQL容器。接着,我们通过设置构建参数,来配置应用容器,毕竟我们需要的是镜像,而非使用MySQL进行拉取。此外,我们还需要通过设置,让应用容器依赖于MySQL容器。最终,我们可以使用命令“docker-compose up”,来运行整个项目。

MySQL计划事件(Scheduled Event)

说到短链接的到期设置,我们既可以让用户自定义,又可以保持默认值。为此,我们可以在数据库中设置一个计划事件。通过每x分钟运行一次该事件,到期时间只要小于当前时间,数据库就会自动删除某一行,就这么简单。这非常适用于保持数据库中的少量数据。不过,该方法有两个问题值得注意:

首先,该事件只会从数据库中删除记录,而不会从缓存中删除数据。如前所述,如果缓存可以找到匹配的数据的话,就不会去查看数据库。因此,某条短链接即便已经在数据库中被删除了,我们仍然可以从缓存中获取它。

其次,在示例脚本中,我设置该事件为每隔2分钟运行一次。如果数据库的记录变动较大,则可能出现前一个事件尚未在其预定的间隔周期内执行完毕,后一个事件已被触发,进而出现多个事件实例同时在执行的混乱局面。

小结

通过上述示例和讨论,我向您展示了如何使用Java和Spring Boot,来创建URL短链接生成器的API。这是一个十分常见的面试问题,您既可以据此创建自己的改进版本,又可以从上述GitHub处克隆项目的存储库,并创建自己的前端。

本文如果对你有帮助,请点赞收藏《如何使用Java和Spring Boot创建短链接生成器》,同时在此感谢原作者。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。
显示评论内容(3)
  1. y z2024-01-16 05:54y z[江西省网友]202.14.127.63
    Java和Spring+Boot的组合真是太强大了,创建短链接生成器变得如此简单!
    顶3踩0
  2. Gaaraㄣ2024-01-13 05:42Gaaraㄣ[新疆网友]27.106.206.18
    这个教程提供了很好的指导,让我能够迅速创建一个短链接生成器。
    顶48踩0
  3. 鲜橙柠檬*2024-01-10 05:29鲜橙柠檬*[陕西省网友]203.0.24.208
    很有用的教程!学习了如何利用Java和Spring+Boot创建短链接生成器。
    顶6踩0
相关阅读
大牛巧用一文带你彻底搞懂解释器的内部构造和解释执行过程

大牛巧用一文带你彻底搞懂解释器的内部构造和解释执行过程

...它抽象出一个存放机器代码片段的队列,当模板解释器的生成器生成机器代码时会将代码片段放入该队列。StubQueue只是一个队列抽象,真正存放机器代码的片段是_stub_buffer,它由BufferBlob::create()创建。CodeCache在HotSpot VM中,除了模...

2008-09-27 #经典句子

最理想的语言之一:GO为何如此与众不同?

最理想的语言之一:GO为何如此与众不同?

...领域,并继续打造新的工具、包、框架、驱动、API、代码生成器,甚至更多厉害的东西,让新的开发者相信Go确实也可以成为他们的下一个主语言。那么我们就来看看Go能干什么吧:· 本地云web服务开发,特别是微服务的使用:Go...

2023-11-20 #经典句子

iOS——转换文本到语音

iOS——转换文本到语音

...,请使用nsspeech合成器类。)合成语音需要两个主要步骤:创建一个或多个avspeech hutterance对象,其中包含要说话的文本。可选地,为每个话语配置语音参数(如语音和速率)。将话语传递给语音合成器对象以产生语音。可选地,使用...

2023-06-23 #经典句子

基于 Tensorflow eager 的文本生成 注意力 图像注释的完整代码

基于 Tensorflow eager 的文本生成 注意力 图像注释的完整代码

...,我们使用DCGAN生成手写数字。生成式对抗网络(GAN)由生成器和鉴别器组成。生成器的工作是创建令人信服的图像以欺骗鉴别器。鉴别器的工作是在真实图像和伪图像(由生成器创建)之间进行分类。下面看到的输出是在使用 ...

2023-10-03 #经典句子

深入 Java 源码剖析之字符串常量|CSDN 博文精选

深入 Java 源码剖析之字符串常量|CSDN 博文精选

...al charvalue[];字符串在内存中的保存方式我们都知道如何去创建一个字符串,那么, 字符串在内存中的保存方式是怎样的呢?在内存中有一个区域叫做常量池,而当我们以这样的方式去创建字符串:String s1 = "abc";String s2 = "abc";这...

2023-10-09 #经典句子

解决自然语言歧义 IJCAI 卓越研究奖得主提出 SenseBERT 模型

解决自然语言歧义 IJCAI 卓越研究奖得主提出 SenseBERT 模型

...统,如 OpenAI 的 GPT-2 和华盛顿大学的 Grover。大多数文本生成器基于人类写的前提合成文本,但是生成的文本常常会跑题、不连贯,或者与原始语境相悖。HAIM 的工作原理稍有不同:先为模型提供开头和结尾,然后模型使用切题的...

2014-01-07 #经典句子

「Java设计模式」图文代码案例详解Java五大创建者模式 建造者 原型 (抽象)工厂 单例模式

「Java设计模式」图文代码案例详解Java五大创建者模式 建造者 原型 (抽象)工厂 单例模式

...种模式,又称工厂方法模式,简单说 在工厂类中提供个创建对象的法, 允许实际调用类决定实例化对象的类型。就是为了提供代码结构的扩展性,屏蔽每个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调即可,同...

2015-06-03 #经典句子

使用Java输出字符流FileWriter创建配置文件

使用Java输出字符流FileWriter创建配置文件

...码、下载文件的存储路径等配置属性。本次编程任务要求创建一个配置文件,该配置文件存储三个属性,分别是数据库连接地址、访问数据库的账户、访问数据库的密码。程序启动后,要求用户输入数据库连接地址、访问数据库...

2015-06-09 #经典句子