自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何使用Java和Spring Boot創(chuàng)建短鏈接生成器

譯文
開發(fā) 前端
本文以完整示例的形式,詳細介紹了如何使用Java和Spring Boot,來創(chuàng)建URL短鏈接生成器。

[[433165]]

【51CTO.com快譯】URL短鏈接生成器是一種根據(jù)冗長的URL,創(chuàng)建短鏈接的服務。通常,短鏈接的長度只有原始URL的三分之一、甚至四分之一。因此它們更容易被輸入、呈現(xiàn)、以及推送。用戶只需單擊短鏈接,便可被自動重定向到原始的URL處。

目前,tiny.cc、bitly.com和cutt.ly都能夠提供在線式的URL縮短服務。當然,您也可以為應用系統(tǒng)自行設計和開發(fā)出縮短URL的服務。下面,我和您討論具體的實現(xiàn)過程。首先,讓我們來探討一下與之相關的功能性和非功能性的需求。

功能要求:

  • 保存用戶輸入的長URL,并據(jù)此生成相應的短鏈接。
  • 允許用戶選擇到期日期,以便生成的短鏈接在該日期后自動無效。
  • 方便用戶在單擊短鏈接后,重定向到原始的長鏈接處。
  • 作為可選的方式,允許用戶創(chuàng)建服務帳戶,并讓生成的短鏈接僅對該賬戶有效。
  • 以可選的方式,允許用戶自行創(chuàng)建短鏈接。
  • 以可選的方式,允許用戶標記出那些最常訪問的鏈接。

非功能性要求:

  • 生成服務具有持續(xù)的有效性和可訪問性。
  • 重定向的用時應不超過2秒。

URL轉換的方式

URL短鏈接生成器中最重要的是轉換算法。不同的轉換方式通常會產(chǎn)生不同的輸出,而且它們各有優(yōu)、缺點。假設我們需要一個最長為7個字符的短鏈接。那么我們可以采用MD5SHA-2之類的哈希函數(shù),對原始的URL進行散列處理。由于散列的結果會超過7個字符,因此我們只取前7個字符。不過,由于前7個字符可能已經(jīng)被用于其他短鏈接,并由此會引發(fā)沖突,因此我們需要依次截取后面的7個字符,直至找到一個被使用過的短鏈接為止。

生成短鏈接的第二種方法是使用UUID。UUID被復制的概率近似為零,因此可以完全忽略沖突的可能。由于UUID是由36個字符組成,仍然可能遇到上述問題,因此我們應當截取前7個字符,然后檢查該組合是否已被占用。

第三種方法是將數(shù)字從Base 10轉換為Base 62。Base是可用于表示特定數(shù)字的字符數(shù)。Base 10是我們?nèi)粘I钪惺褂玫臄?shù)字,即:[0-9],而Base 62則是:[0-9][az][AZ]。這意味著,以10為Base的四位數(shù)字,將與以62為Base、但具有兩個字符的數(shù)字相同。因此在URL轉換中,使用最大長度為7個字符的Base 62,將允許我們?yōu)槎替溄犹峁?2^7個唯一值。

Base 62的轉換機制

我使用如下算法,將一個Base為10的數(shù)字轉換為Base為62:

  1. while(number > 0)     
  2. remainder = number % 62     
  3. number = number / 62     
  4. attach remainder to start of result collection 

據(jù)此,我們只需要將結果集中的數(shù)字映射到Base為62的字符 [0,1,2,...,a,b,c...,A,B,C,...]即可。

下面,我通過將1000從Base 10轉換為Base 62的例子,來討論其工作機制。

  1. 1st iteration: 
  2.          number = 1000 
  3.          remainder = 1000 % 62 = 8 
  4.          number = 1000 / 62 = 16 
  5.          result list = [8] 
  6. 2nd iteration: 
  7.          number = 16 
  8.          remainder = 16 % 62 = 16 
  9.          number = 16 / 62 = 0 
  10.          result list = [16,8] 
  11.          There is no more iterations since number = 0 after 2nd iteration 

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

而從Base 62轉換為Base 10的過程也很簡單,即:

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

所以其對應的代碼示例為:

  1. inputString = g8     
  2. inputString length = 2     
  3. i = 0     
  4. result = 0 
  5. 1st iteration 
  6.         counter = 1 
  7.         mapped = 16 // index of g in base62alphabet is 16 
  8.         result = 0 + 16 * 62^1 = 992 
  9. 2nd iteration 
  10.         counter = 2 
  11.         mapped = 8 // index of 8 in base62alphabet is 8 
  12.         result = 992 + 8 * 62^1 = 1000 

實現(xiàn)

我使用Spring Boot和MySQL來實現(xiàn)該服務。請參看我在Github上的具體代碼。我用到了數(shù)據(jù)庫的自動遞增功能來實現(xiàn)Base 62的轉換。當然,您也可以使用任何其他具有自動遞增功能的數(shù)據(jù)庫。

首先,請訪問Spring initializr,并選擇Spring Web與MySQL Driver。接著,請單擊“生成(Generate)”按鈕,并下載對應的zip文件。完成解壓縮之后,我們就可以在自己的IDE中打開該項目了。

我通過創(chuàng)建文件夾:控制器、實體、服務、存儲庫、dto和配置,實現(xiàn)在邏輯上劃分程序代碼。

在“實體”文件夾中,我創(chuàng)建了一個具有id、longUrl、createdDate和expiresDate四個屬性的Url.java類

請注意,此處既沒有短鏈接的屬性,也不會去保存短鏈接。每次只要有GET請求的出現(xiàn),我們都會將id屬性從Base 10轉換為Base 62,以便節(jié)省數(shù)據(jù)庫中的空間。

用戶在訪問該短鏈接時,應根據(jù)longURL屬性重定向到目標網(wǎng)站。createdDate則只是為了查看longURL何時被保存(并不重要)。而如果用戶希望在一段時間后讓短鏈接失效的話,可以對expiresDate進行設置。

接著,我在“服務”文件夾中,創(chuàng)建了一個BaseService.java文件。其中包含了從Base 10到Base 62相互轉換的方法。

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

正如前面所提到的,若要使用Base 62轉換,則需要有一個被稱為allowedCharacters的Base 62的字母表。此外,為了方便按需更改被允許的字符,我們可根據(jù)字符的長度,計算出基本變量的值。其中,編碼(encode)方法會將一個數(shù)字作為輸入,返回一個短鏈接;而解碼(decode)方法則會接受一個字符串(如:短鏈接)作為輸入,并返回一個數(shù)字。

在存儲庫文件夾中,我創(chuàng)建了UrlRepository.java文件。它只是JpaRepository的一個擴展,并給出了諸如“findById”,“save”等方法。在此,我們無需進行任何添加。

然后,我在“控制器”文件夾中創(chuàng)建了一個URLController.java文件(請參見如下代碼)。它提供一種用于創(chuàng)建短鏈接的POST方法,以及一種被用于重定向到原始URL的GET方法。

  1.  @PostMapping("create-short"
  2.     public String convertToShortUrl(@RequestBody UrlLongRequest request) { 
  3.         return urlService.convertToShortUrl(request); 
  4.     } 
  5.  
  6.     @GetMapping(value = "{shortUrl}"
  7.     public ResponseEntity<Void> getAndRedirect(@PathVariable String shortUrl) { 
  8.         var url = urlService.getOriginalUrl(shortUrl); 
  9.         return ResponseEntity.status(HttpStatus.FOUND) 
  10.         .location(URI.create(url)) 
  11.         .build(); 

其中,POST方法會將UrlLongRequest作為請求體。它是一個具有l(wèi)ongURL和expiresDate屬性的類。而GET方法會將一個短的URL作為路徑變量,以獲取并重定向到原始的URL處。

在控制器的上層,urlService會作為依賴項被注入,以便后續(xù)進行解釋。

UrlService.java既包含了大量邏輯,又為控制器提供了服務。ConvertToShortUrl僅供控制器的POST方法所使用。它只是在數(shù)據(jù)庫中創(chuàng)建了一條新的記錄,并獲取一個id,以便將其轉換為Base 62的短鏈接,并返回給控制器。

控制器使用GetOriginalUrl方法,首先將字符串轉換為Base 10類型的id。然后,它通過該id從數(shù)據(jù)庫中獲取一條記錄。當然,如果該記錄不存在的話,則會拋出異常。最后,它會將原始的URL返回給控制器。

下面,我將和您討論Swagger文檔、應用的dockerization(容器化)、緩存以及MySQL的計劃事件。

Swagger的用戶界面

在開發(fā)過程中文檔記錄無疑能夠使得API更易于理解和使用。在該項目中,我使用Swagger UI來記錄API。Swagger UI允許任何人在沒有任何實現(xiàn)邏輯的情況下,可視化API資源,并與之交互。它不但能夠自動生成,而且?guī)в锌梢暬奈臋n,以便于后端的實現(xiàn)和客戶端的使用。

我通過執(zhí)行如下步驟,在項目中引入了Swagger UI。首先,我在pom.xml文件中添加了Maven依賴項:

  1. XML 
  2. <dependency> 
  3.   <groupId>io.springfox</groupId> 
  4.   <artifactId>springfox-swagger2</artifactId> 
  5.   <version>2.9.2</version> 
  6. </dependency> 
  7. <dependency> 
  8.   <groupId>io.springfox</groupId> 
  9.   <artifactId>springfox-swagger-ui</artifactId> 
  10.   <version>2.9.2</version> 
  11. </dependency> 

添加了Maven依賴項后,我們便可以添加Swagger的相關配置了。我在“配置”文件夾中,創(chuàng)建了一個新的類--SwaggerConfig.java,請參考如下代碼段。

Java

  1.     @Configuration 
  2.     @EnableSwagger2 
  3.     public class SwaggerConfig { 
  4.  
  5.     @Bean     
  6.     public Docket apiDocket() {    
  7.         return new Docket(DocumentationType.SWAGGER_2)   
  8.             .apiInfo(metadata())     
  9.             .select()     
  10.             .apis(RequestHandlerSelectors.basePackage("com.amarin"))     
  11.             .build();     
  12.     } 
  13.                
  14.     private ApiInfo metadata(){ 
  15.         return new ApiInfoBuilder() 
  16.         .title("Url shortener API")     
  17.         .description("API reference for developers")     
  18.         .version("1.0")     
  19.         .build();     
  20.         }   

在該類的頂部,我添加了如下注釋:

  • @Configuration表示一個類聲明了一到多個@Beans方法,并且可以由Spring容器通過處理,在運行時為這些bean生成相應的定義和服務請求。
  • @EnableSwagger2表示應該啟用Swagger支持。

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

此處的apiInfo()方法除了可以使用默認值,還能夠接受ApiInfo對象,以便我們配置所有必要的API信息。為了使代碼更加簡潔,我們可以創(chuàng)建一個私有的方法—metadata(),來配置和返回ApiInfo對象,并將該方法作為apiInfo()方法的參數(shù)進行傳遞。同時,apis()方法也允許我們過濾那些被文檔化的包。

在完成了Swagger UI的配置后,我們便可以開始文檔化API了。在UrlController內(nèi)部的每個端點上,我們可以使用@ApiOperation來添加描述性的注釋。當然,您也可以按需使用其他類型的注釋。

我們還可以文檔化DTO,并使用@ApiModelProperty來添加各種允許的值和描述。

緩存

根據(jù)維基百科的定義,緩存是存儲數(shù)據(jù)的軟、硬件組件,可用來更快地處理后續(xù)對于相同數(shù)據(jù)的請求。而存儲在緩存中的數(shù)據(jù),往往是早期計算的結果、或是已存儲在其他地方的數(shù)據(jù)副本。

目前,最常用的緩存類型是內(nèi)存緩存(in-memory cache)。它能夠?qū)⒕彺娴臄?shù)據(jù)存儲到RAM中。當被請求數(shù)據(jù)與緩存一致時,它是從RAM、而非從數(shù)據(jù)庫被提取。據(jù)此,我們避免頻繁調(diào)用后端的開銷。

由于URL短鏈接生成器可以被認為是一種讀取多于寫入的請求應用,因此它是使用緩存的理想應用場景。若想在Spring Boot應用中啟用緩存,我們只需要在UrlShortenerApiApplication類中添加@EnableCaching注釋即可。

接著,在控制器中,我們需要在GET方法上設置@Cachable注解,以實現(xiàn)自動將方法調(diào)用的結果存入緩存中。在@Cachable的注解中,我設置了緩存名稱的value參數(shù)和緩存鍵的key參數(shù)。鑒于緩存鍵的唯一性,我使用了“shortUrl”,并將Sync參數(shù)設置為true,以確保只有一個線程正在構建緩存值。

至此,當我們首次加載帶有短鏈接的URL時,其結果將會被保存到緩存中。后續(xù),任何端點若想調(diào)用相同短鏈接,都會從緩存、而非從數(shù)據(jù)庫中檢索結果。

Dockerization

Dockerization是將應用程序及其依賴項打包到Docker容器中的過程。一旦配置了Docker容器,我們便可以輕松地在任何支持Docker的服務器、或主機上運行應用程序。

因此,我們首先需要創(chuàng)建一個包含所有命令的Dockerfile文本文件,以便用戶通過調(diào)用命令行的方式,掛載某個鏡像。

Dockerfile

  1. FROM openjdk:13-jdk-alpine    
  2.     COPY ./target/url-shortener-api-0.0.1-SNAPSHOT.jar /usr/src/app/url-shortener-api-0.0.1-SNAPSHOT.jar     
  3.     EXPOSE 8080     
  4. 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:此命令會將文件從本地文件系統(tǒng),復制到指定路徑的容器文件系統(tǒng)中。在此,我將目標文件夾中的JAR文件,復制到容器中的/usr/src/app文件夾中(稍后我將解釋如何創(chuàng)建JAR文件)。
  • EXPOSE:負責通知Docker容器在運行時,偵聽指定網(wǎng)絡端口的指令。其默認協(xié)議為TCP,您也可以使用UDP。
  • ENTRYPOINT:負責配置可執(zhí)行的容器。在此,我通過命令為“java -jar .jar”,指定Docker將如何運行一個.jar文件類型的應用程序。

為了在項目中創(chuàng)建.jar文件,以便Dockerfile中的COPY命令能夠正常工作,我使用Maven來創(chuàng)建可執(zhí)行的.jar。如果您的pom.xml缺少Maven,請用如下方式進行添加:

XML

  1. <build>      
  2.     <plugins>      
  3.         <plugin>      
  4.             <groupId>org.springframework.boot</groupId>      
  5.             <artifactId>spring-boot-maven-plugin</artifactId>      
  6.         </plugin>      
  7.     </plugins>      
  8. </build>  

隨后,我運行命令:mvn clean package,以構建出一個Docker鏡像。接著,在Dockerfile文件夾中,我運行了命令:docker build -t url-shortener:latest。其中,-t可用于標記一個鏡像,并實現(xiàn)版本控制。在此,即為最新的存儲庫URL-shortener。我們可以使用命令“docker images”來創(chuàng)建鏡像。屏幕上的顯示結果為:

最后,我還需要在docker容器中構建MySQL服務器鏡像,以方便數(shù)據(jù)庫容器與應用容器相隔離。為此,我在Docker容器中運行了如下命令:

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

您可以在Docker hub上查看到相關文檔。

為了在容器內(nèi)運行數(shù)據(jù)庫,我通過配置,將現(xiàn)有的應用程序連接上該MySQL服務器。即:在application.properties中設置spring.datasource.url,以連接到shortener容器。

然后,我使用以下命令來運行已構建好的Docker 鏡像容器:

  1. docker run -d –-name url-shortener-api -p 8080:8080 --link shortener url-shortener 
  • -d表示Docker容器在終端的后臺運行。
  • --name可設置容器的名稱。
  • -p host-port:docker-port:是將本地端口映射到容器內(nèi)的端口上。在本例中,我在容器內(nèi)公開了端口8080,并映射到了本地的8080上。
  • --link:用于鏈接應用容器與數(shù)據(jù)庫容器,以實現(xiàn)容器間的相互發(fā)現(xiàn)和安全傳輸。
  • url-shortener:則指明了待運行的Docker鏡像名稱。

至此,我們便可以在瀏覽器中訪問http://localhost:8080/swagger-ui.html了。通過將鏡像發(fā)布到Docker Hub上,任何計算機和服務器都可以輕松地運行該應用。

當然,為了改善該Docker的使用體驗,我們需要注意多階段構建,以及docker-compose兩個方面。

多階段構建

使用多階段構建,您將可以在Dockerfile中使用多個FROM語句。每個FROM指令都可以使用不同的base,并且每個指令都能夠開啟構建的新階段。您可以有選擇性地將各個工件(artifacts)從一個階段復制到另一個階段,并在最終鏡像中去掉不想要的內(nèi)容。

多階段構建有利于我們避免每次對代碼進行更改后,都必須手動重建.jar文件。據(jù)此,我們可以定義一個構建階段,來執(zhí)行Maven包命令。而另一個階段會將來自第一次構建的結果,直接復制到Docker容器的文件系統(tǒng)中。您可以通過鏈接--https://github.com/AnteMarin/UrlShortener-API/blob/develop/Dockerfile,查看完整的Dockerfile。

Docker-compose

Compose是一個用于定義和運行多容器Docker應用的工具。借助Compose,您可以使用YAML文件,來配置應用程序的服務,然后使用單個命令,從配置中創(chuàng)建并啟動所有的服務。

使用docker-compose,我們能夠?qū)贸绦蚝蛿?shù)據(jù)庫打包到一個配置文件中,以便立即運行所有的內(nèi)容。據(jù)此,我們避免了每次去運行MySQL容器,將其鏈接到應用容器的繁瑣。

Docker-compose.yml文件的具體配置內(nèi)容可知:首先,我們通過設置鏡像mysql v8.0和MySQL服務器的憑據(jù),來配置MySQL容器。接著,我們通過設置構建參數(shù),來配置應用容器,畢竟我們需要的是鏡像,而非使用MySQL進行拉取。此外,我們還需要通過設置,讓應用容器依賴于MySQL容器。最終,我們可以使用命令“docker-compose up”,來運行整個項目。

MySQL計劃事件(Scheduled Event)

說到短鏈接的到期設置,我們既可以讓用戶自定義,又可以保持默認值。為此,我們可以在數(shù)據(jù)庫中設置一個計劃事件。通過每x分鐘運行一次該事件,到期時間只要小于當前時間,數(shù)據(jù)庫就會自動刪除某一行,就這么簡單。這非常適用于保持數(shù)據(jù)庫中的少量數(shù)據(jù)。不過,該方法有兩個問題值得注意:

  • 首先,該事件只會從數(shù)據(jù)庫中刪除記錄,而不會從緩存中刪除數(shù)據(jù)。如前所述,如果緩存可以找到匹配的數(shù)據(jù)的話,就不會去查看數(shù)據(jù)庫。因此,某條短鏈接即便已經(jīng)在數(shù)據(jù)庫中被刪除了,我們?nèi)匀豢梢詮木彺嬷蝎@取它。
  • 其次,在示例腳本中,我設置該事件為每隔2分鐘運行一次。如果數(shù)據(jù)庫的記錄變動較大,則可能出現(xiàn)前一個事件尚未在其預定的間隔周期內(nèi)執(zhí)行完畢,后一個事件已被觸發(fā),進而出現(xiàn)多個事件實例同時在執(zhí)行的混亂局面。

小結

通過上述示例和討論,我向您展示了如何使用Java和Spring Boot,來創(chuàng)建URL短鏈接生成器的API。這是一個十分常見的面試問題,您既可以據(jù)此創(chuàng)建自己的改進版本,又可以從上述GitHub處克隆項目的存儲庫,并創(chuàng)建自己的前端。

原文標題:URL Shortener Complete Tutorial,作者:Ante Marin

【51CTO譯稿,合作站點轉載請注明原文譯者和出處為51CTO.com】

 

責任編輯:華軒 來源: 51CTO
相關推薦

2022-12-15 08:49:58

ReactQR生成器

2015-06-24 10:10:38

C#短鏈接生成

2022-10-17 18:29:55

2024-12-23 08:00:00

2017-11-22 10:53:22

2024-11-01 15:51:06

2021-12-10 09:45:19

生成器配置代碼

2023-03-01 00:07:32

JavaScript迭代器生成器

2015-08-25 11:07:58

2017-07-01 16:02:39

分布式ID生成器

2025-01-23 08:36:27

CSS開發(fā)工具

2010-09-07 16:31:17

SQL語句insert

2021-04-22 21:15:38

Generator函數(shù)生成器

2011-12-23 13:42:05

JavaScript

2022-02-22 08:20:04

React工具PDF 文件

2023-07-02 14:14:37

ChatGPTMidjourney

2021-07-23 11:24:54

Create Inc開源G代碼生成器

2015-08-25 15:54:17

程序員代碼生成器

2017-09-06 09:26:03

Python生成器協(xié)程

2021-12-04 22:07:44

Python
點贊
收藏

51CTO技術棧公眾號