SpringBoot實(shí)現(xiàn)短鏈接系統(tǒng)的使用示例
前言
短鏈接系統(tǒng)是一種將較長(zhǎng)的URL(統(tǒng)一資源定位符)轉(zhuǎn)換為較短的URL的服務(wù)。這種服務(wù)通常被用于URL分享,因?yàn)檩^短的URL更加方便用戶復(fù)制和粘貼,也更容易在社交媒體和其他在線平臺(tái)分享。本文使用了SpringBoot開(kāi)發(fā)了一個(gè)簡(jiǎn)易的短鏈接轉(zhuǎn)換接口,和短鏈接重定向接口。
一、短鏈接系統(tǒng)入門??
1. 什么是短鏈接系統(tǒng)?
短鏈接系統(tǒng)是一種將較長(zhǎng)的URL轉(zhuǎn)換成較短URL的服務(wù)。當(dāng)用戶點(diǎn)擊短鏈接時(shí),他們會(huì)被重定向到原始URL。短鏈接系統(tǒng)在社交媒體平臺(tái)(如微博)上特別有用,因?yàn)樵谶@些平臺(tái)上,限制了可以發(fā)布的文字?jǐn)?shù)量。使用短鏈接服務(wù)可以節(jié)省空間,使URL更短,更方便用戶輸入。
短鏈接有什么優(yōu)勢(shì):
- 便捷分享: 短鏈接更短、更易分享,適用于社交媒體、短信、郵件等場(chǎng)景,提供更美觀的外觀。
- 提高用戶體驗(yàn): 短鏈接可以簡(jiǎn)化用戶輸入,減少用戶訪問(wèn)鏈接時(shí)的操作,提升用戶體驗(yàn)
- 推廣和營(yíng)銷: 短鏈接可以用于推廣和營(yíng)銷活動(dòng),跟蹤廣告點(diǎn)擊和轉(zhuǎn)化率,幫助優(yōu)化營(yíng)銷策略
2. 準(zhǔn)備工作
(1)創(chuàng)建一個(gè)maven項(xiàng)目
(2)引入相關(guān)依賴
繼承spring boot parent項(xiàng)目
<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開(kāi)源的Java庫(kù)
<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)創(chuàng)建啟動(dòng)類
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結(jié)果封裝類
public class ResponseResult<T> { private String code; private String msg; private T data; }
(5)創(chuàng)建響應(yīng)工具類
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); } }
二、核心功能實(shí)現(xiàn)??
1. 實(shí)現(xiàn)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; } }
方法解釋
idToShortKey方法:
- 創(chuàng)建一個(gè)StringBuilder對(duì)象,用于存儲(chǔ)轉(zhuǎn)換后的字符串。
- 使用while循環(huán),當(dāng)id大于0時(shí),執(zhí)行循環(huán)體。在循環(huán)體中,首先計(jì)算id除以62的余數(shù),然后將余數(shù)對(duì)應(yīng)的BASE62字符添加到StringBuilder對(duì)象中。接著,將id除以62,更新id的值。
- 當(dāng)id小于等于0時(shí),跳出循環(huán)。此時(shí),StringBuilder對(duì)象中的字符串長(zhǎng)度可能小于6。為了確保字符串長(zhǎng)度為6,使用另一個(gè)while循環(huán),在StringBuilder對(duì)象的開(kāi)頭添加0,直到其長(zhǎng)度達(dá)到6。
- 最后,將StringBuilder對(duì)象反轉(zhuǎn),并將其轉(zhuǎn)換為字符串返回。
shortKeyToId方法:
- 創(chuàng)建一個(gè)名為id的長(zhǎng)整型變量,初始值為0。
- 使用for循環(huán)遍歷shortKey字符串中的每個(gè)字符。在循環(huán)體中,首先計(jì)算當(dāng)前字符在BASE62字符串中的索引值,然后將id乘以62,再加上當(dāng)前字符的索引值。將結(jié)果賦值給id。
- 當(dāng)所有字符都遍歷完畢后,返回id作為最終結(jié)果。
2. 創(chuàng)建實(shí)體類
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. 創(chuàng)建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. 創(chuàng)建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. 編寫測(cè)試接口
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; /*** * 長(zhǎng)鏈接轉(zhuǎn)短鏈接 * @param longUrl 長(zhǎng)鏈接 * @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 "抱歉,未找到頁(yè)面!"; } }
6. 使用curl測(cè)試
#將長(zhǎng)鏈接轉(zhuǎn)為短鏈接 curl -XPOST "localhost:8888/shorten?longUrl=https://i.csdn.net/#/user-center/profile?spm=1011.2415.3001.5111" #訪問(wèn)短鏈接重定向到目標(biāo)網(wǎng)站 curl -i "http://127.0.0.1:8888/000003"
數(shù)據(jù)庫(kù)中也存儲(chǔ)了相關(guān)數(shù)據(jù)
三、系統(tǒng)優(yōu)化??
1. 緩存簡(jiǎn)介
什么是緩存
緩存(Caching)是一種用于提高數(shù)據(jù)處理速度的技術(shù),涉及到了計(jì)算機(jī)硬件、操作系統(tǒng)、應(yīng)用程序等多個(gè)領(lǐng)域。緩存的主要原理是將經(jīng)常使用或最近使用的數(shù)據(jù)存儲(chǔ)在快速訪問(wèn)的存儲(chǔ)設(shè)備中,這樣在需要這些數(shù)據(jù)時(shí),就可以更快地獲取到它們,從而提高系統(tǒng)的整體性能。
在計(jì)算機(jī)科學(xué)中,緩存通常指的是存儲(chǔ)臨時(shí)數(shù)據(jù)的地方,這些數(shù)據(jù)可能是來(lái)自于計(jì)算過(guò)程中的結(jié)果,也可能是來(lái)自于磁盤、網(wǎng)絡(luò)等慢速存儲(chǔ)設(shè)備的數(shù)據(jù)副本。緩存中的數(shù)據(jù)通常是根據(jù)一定的算法進(jìn)行管理和替換的,以確保緩存中的數(shù)據(jù)是最需要或最常用的。
引入的緩存用于解決哪些問(wèn)題
- 高并發(fā)訪問(wèn): 在高并發(fā)情況下,數(shù)據(jù)源可能會(huì)受到過(guò)大的負(fù)載,通過(guò)緩存可以減輕數(shù)據(jù)源的壓力,提高系統(tǒng)的性能和響應(yīng)速度。
- 頻繁訪問(wèn): 對(duì)于頻繁被訪問(wèn)的數(shù)據(jù),通過(guò)緩存可以減少重復(fù)的數(shù)據(jù)讀取,提高效率。
- 數(shù)據(jù)計(jì)算: 對(duì)于一些需要復(fù)雜計(jì)算的數(shù)據(jù),將計(jì)算結(jié)果緩存起來(lái)可以節(jié)省計(jì)算時(shí)間和資源。
- 數(shù)據(jù)共享: 緩存可以在不同的組件、模塊或服務(wù)之間共享數(shù)據(jù),提高數(shù)據(jù)的可用性和共享性。
- 離線訪問(wèn): 緩存可以在斷網(wǎng)或無(wú)法連接數(shù)據(jù)源的情況下,仍然提供某些數(shù)據(jù)的訪問(wèn)能力。
本地緩存 VS 分布式緩存
分布式緩存:
概念: 分布式緩存是一種將緩存數(shù)據(jù)分布在多個(gè)服務(wù)器節(jié)點(diǎn)上的緩存系統(tǒng),用于存儲(chǔ)和管理大量的數(shù)據(jù)。
優(yōu)點(diǎn):
- 可擴(kuò)展性: 分布式緩存可以通過(guò)增加節(jié)點(diǎn)來(lái)實(shí)現(xiàn)水平擴(kuò)展,以應(yīng)對(duì)大規(guī)模的數(shù)據(jù)和高并發(fā)訪問(wèn)。
- 高可用性: 分布式緩存通常采用復(fù)制和備份機(jī)制,確保即使有節(jié)點(diǎn)故障,仍然能夠提供可靠的緩存服務(wù)。
- 跨節(jié)點(diǎn)共享: 多個(gè)應(yīng)用實(shí)例可以共享同一分布式緩存,提高數(shù)據(jù)共享和協(xié)作能力。
- 靈活的存儲(chǔ)后端: 分布式緩存可以支持多種后端存儲(chǔ),如內(nèi)存、磁盤、數(shù)據(jù)庫(kù)等。
缺點(diǎn):
- 復(fù)雜性: 部署、配置和管理分布式緩存系統(tǒng)可能較為復(fù)雜,需要考慮分布式系統(tǒng)的一些挑戰(zhàn),如一致性、網(wǎng)絡(luò)延遲等。
- 性能開(kāi)銷: 分布式緩存通常需要在網(wǎng)絡(luò)上進(jìn)行數(shù)據(jù)傳輸,可能引入一些性能開(kāi)銷。
本地緩存:
概念: 本地緩存是將緩存數(shù)據(jù)存儲(chǔ)在應(yīng)用程序的本地內(nèi)存中,用于臨時(shí)保存常用的數(shù)據(jù)。
優(yōu)點(diǎn):
- 簡(jiǎn)單性: 本地緩存相對(duì)較簡(jiǎn)單,不需要搭建額外的分布式緩存系統(tǒng)。
- 低延遲: 由于數(shù)據(jù)存儲(chǔ)在本地內(nèi)存中,本地緩存通常具有低延遲的讀取速度。
- 少量數(shù)據(jù): 本地緩存適用于存儲(chǔ)相對(duì)較小的數(shù)據(jù)量,不需要進(jìn)行分布式存儲(chǔ)和管理。
缺點(diǎn):
- 有限的擴(kuò)展性: 本地緩存只能在單個(gè)應(yīng)用實(shí)例內(nèi)使用,無(wú)法滿足多實(shí)例和分布式應(yīng)用的需求。
- 數(shù)據(jù)一致性: 不同應(yīng)用實(shí)例的本地緩存可能存在數(shù)據(jù)不一致的問(wèn)題,需要額外的機(jī)制來(lái)解決。
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) // 設(shè)置最大緩存大小 .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)); } }
提示:由于短鏈接系統(tǒng)通常需要處理大量的用戶請(qǐng)求和數(shù)據(jù),因此需要具有高效和可擴(kuò)展性。同時(shí),由于短鏈接可能涉及到用戶隱私和安全問(wèn)題,短鏈接系統(tǒng)也需要符合相關(guān)的數(shù)據(jù)保護(hù)和安全標(biāo)準(zhǔn)。
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)短鏈接系統(tǒng)的使用示例的文章就介紹到這了,更多相關(guān)SpringBoot 短鏈接內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis Interceptor 攔截器的實(shí)現(xiàn)
這篇文章主要介紹了Mybatis Interceptor 攔截器的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12Java基礎(chǔ)篇_有關(guān)接口和抽象類的幾道練習(xí)題(分享)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)篇_有關(guān)接口和抽象類的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06Mybatis如何自動(dòng)生成數(shù)據(jù)庫(kù)表的實(shí)體類
這篇文章主要介紹了Mybatis自動(dòng)生成數(shù)據(jù)庫(kù)表的實(shí)體類的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06