introduction
Un raccourcisseur d’URL est un service utilisé pour créer des liens courts à partir d’URL très longues. Habituellement, les liens courts ont la taille d’un tiers ou même d’un quart de l’URL d’origine, ce qui les rend plus faciles à saisir, à présenter ou à tweeter. En cliquant sur un lien court, l’utilisateur sera automatiquement redirigé vers l’URL d’origine.
Il existe de nombreux services de raccourcissement d’URL disponibles en ligne, tels que tiny.cc, bitly.com et cutt.ly. La mise en œuvre d’un service de raccourcissement d’URL n’est pas une tâche complexe et fait souvent partie des entretiens de conception du système. Dans cet article, je vais essayer d’expliquer le processus de mise en œuvre du service.
Théorie
Avant la mise en œuvre, c’est toujours une bonne idée d’écrire ce qu’il faut faire sous la forme d’exigences fonctionnelles et non fonctionnelles.
Exigences fonctionnelles :
-
Les utilisateurs doivent pouvoir saisir une URL longue. Notre service devrait enregistrer cette URL et générer un lien court.
-
Les utilisateurs doivent avoir la possibilité de saisir la date d’expiration. Après cette date, le lien court devrait être invalide.
-
Cliquer sur le lien court devrait rediriger l’utilisateur vers l’URL longue d’origine.
-
Les utilisateurs doivent créer un compte pour utiliser le service. Le service peut avoir une limite d’utilisation par utilisateur.*
-
L’utilisateur est autorisé à créer son propre lien court.*
-
Le service doit avoir des métriques, par exemple, les liens les plus visités.*
Prérogatives non fonctionnelles:
*Les exigences sont facultatives
Conversion d’URL :
Disons que nous voulons avoir un lien court d’une longueur maximale de 7. La chose la plus importante dans un raccourcisseur d’URL est l’algorithme de conversion. La conversion d’URL peut être mise en œuvre de plusieurs manières différentes, et chaque manière a ses avantages et ses inconvénients.
Une façon de générer des liens courts serait de hacher l’URL d’origine avec une fonction de hachage (par exemple, MD5 ou SHA-2). Lorsque vous utilisez une fonction de hachage, il est certain que différentes entrées entraîneront différentes sorties. Le résultat du hachage est plus long que sept caractères, nous aurions donc besoin de prendre les sept premiers caractères. Mais, dans ce cas, il pourrait y avoir une collision, car les sept premiers caractères pourraient déjà être utilisés comme lien court. Ensuite, nous prenons les sept caractères suivants, jusqu’à ce que nous trouvions un lien court qui n’est pas utilisé.
La deuxième façon de générer un lien court consiste à utiliser des UUID. La probabilité qu’un UUID soit dupliqué n’est pas nulle, mais elle est suffisamment proche de zéro pour être négligeable. Puisqu’un UUID a 36 caractères, cela signifie que nous avons le même problème que ci-dessus. Nous devrions prendre les sept premiers caractères et vérifier si cette combinaison est déjà utilisée.
La troisième façon serait de convertir les nombres de la base 10 en base 62. Une base est un nombre de chiffres ou de caractères qui peuvent être utilisés pour représenter un nombre particulier. La base 10 sont des chiffres [0-9] que nous utilisons dans la vie de tous les jours et la base 62 sont [0-9][a-z][A-Z]. Cela signifie que, par exemple, un nombre en base 10 avec quatre chiffres serait le même nombre en base 62 mais avec deux caractères.
L’utilisation de la base 62 dans la conversion d’URL avec une longueur maximale de sept caractères nous permet d’avoir 62^7 valeurs uniques pour les liens courts.
Alors, comment fonctionne la conversion en base 62 ?
Nous avons un nombre en base 10 que nous voulons convertir en base 62. Nous allons utiliser l’algorithme suivant :
while(number > 0)
remainder = number % 62
number = number / 62
attach remainder to start of result collection
Après cela, nous avons juste besoin de mapper les nombres de la collection de résultats à la base 62 Alphabet = [0,1,2,…,a,b,c…,A,B,C,…].
Voyons comment cela fonctionne avec un exemple réel. Dans cet exemple, convertissons 1000 de la base 10 à la 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
Cartographie [16,8] à la base 62 serait g8. Cela signifie que 1000base10 = g8base62.
La conversion de la base 62 à la base 10 est également simple :
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++
Exemple réel :
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
Mise en œuvre
Noter: Toute la solution est sur mon Github. J’ai implémenté ce service en utilisant Spring Boot et MySQL.
Nous allons utiliser la fonction d’auto-incrémentation de notre base de données. Le nombre auto-incrémenté va être utilisé pour la conversion en base 62. Vous pouvez utiliser n’importe quelle autre base de données dotée d’une fonction d’incrémentation automatique.
Tout d’abord, visitez Spring initializr et sélectionnez Spring Web and MySQL Driver. Après cela, cliquez sur le bouton Générer et téléchargez le fichier zip. Décompressez le fichier et ouvrez le projet dans votre IDE préféré.
Chaque fois que je démarre un nouveau projet, j’aime créer des dossiers pour diviser logiquement mon code. Mes dossiers dans ce cas sont : controller, entity, service, repository, dto et config.
Dans le dossier de l’entité, créons une classe Url.java avec quatre attributs : id, longUrl, createdDate, expiresDate.
Notez qu’il n’y a pas d’attribut de lien court. Nous n’enregistrerons pas les liens courts. Nous allons convertir l’attribut id de la base 10 en base 62 à chaque fois qu’il y a une requête GET. De cette façon, nous économisons de l’espace dans notre base de données.
L’attribut longUrl est l’URL vers laquelle nous devons rediriger une fois qu’un utilisateur accède à un lien court. Le createdDate est juste pour voir quand le longUrl est enregistré (ce n’est pas important) et expiresDate est là si un utilisateur veut rendre un lien court indisponible après un certain temps.
Ensuite, créons un BaseService.java dans le dossier de service. BaseService contient des méthodes pour convertir de la base 10 à la base 62 et vice versa.
private static final String allowedString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private char[] allowedCharacters = allowedString.toCharArray();
private int base = allowedCharacters.length;
Comme je l’ai mentionné précédemment, si nous voulons utiliser des conversions en base 62, nous devons disposer d’un alphabet en base 62, qui dans ce cas, s’appelle allowCharacters. De plus, la valeur de la variable de base est calculée à partir de la longueur des caractères autorisés au cas où nous voudrions modifier les caractères autorisés.
La méthode d’encodage prend un nombre en entrée et renvoie un lien court. La méthode decode prend une chaîne (lien court) en entrée et renvoie un nombre. Les algorithmes doivent être mis en œuvre comme ils ont été expliqués ci-dessus.
Après cela, dans le dossier du référentiel, créons le fichier UrlRepository.java, qui n’est qu’une extension de JpaRepository et qui nous donne beaucoup de méthodes comme « findById », « save », etc. Nous n’avons pas besoin d’ajouter quoi que ce soit d’autre pour ça.
Ensuite, créons un fichier UrlController.java dans le dossier du contrôleur. Le contrôleur doit avoir une méthode POST pour créer des liens courts et une méthode GET pour rediriger vers l’URL d’origine.
@PostMapping("create-short")
public String convertToShortUrl(@RequestBody UrlLongRequest request) {
return urlService.convertToShortUrl(request);
}
@GetMapping(value = "{shortUrl}")
public ResponseEntity<Void> getAndRedirect(@PathVariable String shortUrl) {
var url = urlService.getOriginalUrl(shortUrl);
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create(url))
.build();
}
La méthode POST a une UrlLongRequest comme corps de requête. C’est juste une classe avec les attributs longUrl et expiresDate.
La méthode GET prend une URL courte comme variable de chemin, puis obtient et redirige vers l’URL d’origine.
En haut du contrôleur, urlService est injecté en tant que dépendance, ce qui sera expliqué par la suite.
UrlService.java est l’endroit où se trouve la plupart des logiques et est le service utilisé par le contrôleur. ConvertToShortUrl est utilisé par la méthode POST du contrôleur. Il crée simplement un nouvel enregistrement dans la base de données et obtient un identifiant. Cet identifiant est ensuite converti en un lien court de base 62 et renvoyé au contrôleur.
GetOriginalUrl est une méthode utilisée par la méthode GET du contrôleur. Il convertit d’abord une chaîne en base 10, et le résultat est un identifiant. Ensuite, il obtient un enregistrement de la base de données par cet identifiant et lève une exception s’il n’existe pas. Après cela, il renvoie l’URL d’origine au contrôleur.
Et c’est tout pour la première partie. Dans la prochaine partie, je me concentrerai sur des choses plus « avancées ».
Sujets « avancés »
Dans cette partie, je parlerai de la documentation Swagger, dockérisation de l’application, du cache d’application et de l’événement planifié MySQL.
Interface utilisateur de Swagger
Chaque fois que vous développez une API, il est bon de la documenter d’une manière ou d’une autre. La documentation rend les API plus faciles à comprendre et à utiliser. L’API de ce projet est documentée à l’aide de l’interface utilisateur Swagger.
L’interface utilisateur Swagger permet à quiconque de visualiser et d’interagir avec les ressources de l’API sans avoir mis en place aucune logique de mise en œuvre. Il est généré automatiquement, avec la documentation visuelle facilitant l’implémentation back-end et la consommation côté client.
Nous devons suivre plusieurs étapes pour inclure l’interface utilisateur Swagger dans le projet.
Tout d’abord, nous devons ajouter des dépendances Maven au fichier pom.xml :
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
Pour référence, vous pouvez voir le fichier pom.xml complet ici.
Après avoir ajouté les dépendances Maven, il est temps d’ajouter la configuration Swagger.
Dans le dossier config, nous devons créer une nouvelle classe, SwaggerConfig.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();
}
}
Au sommet de la classe, nous devons ajouter quelques annotations.
@Configuration indique qu’une classe déclare un ou plusieurs @Haricots et peuvent être traitées par le conteneur Spring pour générer des définitions de bean et des demandes de service pour ces beans au moment de l’exécution.
@ActiverSwagger2 indique que la prise en charge de Swagger doit être activée.
Ensuite, il faut ajouter Rôle bean qui fournit la configuration principale de l’API avec des valeurs par défaut sensées et des méthodes pratiques pour la configuration.
Les info api () La méthode prend l’objet ApiInfo où nous pouvons configurer toutes les informations API nécessaires. Sinon, il utilise des valeurs par défaut. Pour rendre le code plus propre, nous devons créer une méthode privée qui configurera et retournera l’objet ApiInfo et passera cette méthode en tant que paramètre de info api () méthode. Dans ce cas, c’est le métadonnées() méthode.
Les apis() méthode…