介绍 这是一个长链接转短链接的SpringBoot项目,利用Redis存储长短链接的键值关系
github地址:https://github.com/xinhaojin/short-url
演示站点:https://s.xinhaojin.top
实现 创建项目 用Spring Initializr创建一个空项目,使用Java8、SpringBoot2版本,勾选spring web
前端页面 在resource/static下创建index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 <!DOCTYPE html > <html > <head > <title > 短链接生成器</title > <meta charset ="UTF-8" > <link rel ="icon" href ="https://xinhaojin.github.io/imgs-host/20230821/tutu.7cfpopzisy40.webp" sizes ="16*16" > <style > body { font-family : Arial, sans-serif; margin : 0 ; margin-top : 10% ; padding : 0 ; background-color : #f4f4f4 ; } .container { max-width : 800px ; margin : auto; padding : 20px ; text-align : center; } h1 { color : #333 ; } input [type="text" ] { width : 80% ; padding : 10px ; margin-bottom : 10px ; border : 1px solid #ccc ; border-radius : 5px ; } button { padding : 10px 20px ; background-color : #007bff ; color : #fff ; border : none; border-radius : 5px ; cursor : pointer; } button :hover { background-color : #0056b3 ; } #shortUrl { font-weight : bold; color : #007bff ; } .copy-message { display : none; color : #28a745 ; font-weight : bold; opacity : 0 ; transition : opacity 0.5s ease-in-out; } </style > </head > <body > <div class ="container" > <h1 > 短链接生成器</h1 > <input type ="text" id ="longUrl" placeholder ="请输入原始长链接" > <button onclick ="shortenURL()" > 生成短链接</button > <p > 短链接: <span id ="shortUrl" > </span > </p > <button onclick ="copyToClipboard()" > 复制到剪贴板</button > <div class ="copy-message" id ="copyMessage" > 复制成功!</div > </div > <script > function shortenURL ( ) { const longUrl = document .getElementById ('longUrl' ).value ; fetch (`/api/shorten?url=${encodeURIComponent (longUrl)} ` ) .then (response => response.text ()) .then (shortUrl => { const fullShortUrl = `https://s.xinhaojin.top/s/${shortUrl} ` ; const shortUrlElement = document .getElementById ('shortUrl' ); shortUrlElement.innerHTML = `<a href="${fullShortUrl} " target="_blank">${fullShortUrl} </a>` ; }); } function copyToClipboard ( ) { const shortUrl = document .getElementById ('shortUrl' ).textContent ; const tempInput = document .createElement ('input' ); tempInput.value = shortUrl; document .body .appendChild (tempInput); tempInput.select (); document .execCommand ('copy' ); document .body .removeChild (tempInput); const copyMessage = document .getElementById ('copyMessage' ); copyMessage.style .display = 'block' ; setTimeout (() => { copyMessage.style .opacity = '1' ; }, 10 ); setTimeout (() => { copyMessage.style .opacity = '0' ; }, 1500 ); } </script > </body > </html >
controller MyController.java,给前端提供生成短链接和恢复长链接的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package top.xinhaojin.shorturl.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import top.xinhaojin.shorturl.service.MyService;@RestController public class MyController { private final MyService myService; @Autowired public MyController (MyService myService) { this .myService = myService; } @GetMapping("/api/shorten") public String shortenURL (@RequestParam String url) { return myService.shortenURL(url); } @GetMapping("/api/restore") public String restoreURL (@RequestParam String key) { return myService.restoreURL(key); } }
RedirectController.java,处理用户访问短链接时的重定向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package top.xinhaojin.shorturl.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import top.xinhaojin.shorturl.service.MyService;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Controller @RequestMapping("/s") public class RedirectController { private final MyService myService; @Autowired public RedirectController (MyService myService) { this .myService = myService; } @GetMapping("/{shortKey}") public void redirect (@PathVariable String shortKey, HttpServletResponse response) throws IOException { String originalUrl = myService.restoreURL(shortKey); if (originalUrl != null ) { response.sendRedirect(originalUrl); } else { response.sendError(HttpServletResponse.SC_NOT_FOUND); } } }
service MyService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 package top.xinhaojin.shorturl.service;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.dao.DataAccessException;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.stereotype.Service;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.regex.Pattern;@Service public class MyService { private final StringRedisTemplate redisTemplate; @Autowired public MyService (StringRedisTemplate redisTemplate) { this .redisTemplate = redisTemplate; } public String shortenURL (String longURL) { try { if (!longURL.startsWith("http://" ) && !longURL.startsWith("https://" )) { longURL = "http://" + longURL; } if (!isValidURL(longURL)) { return "invalid" ; } String shortKey = generateShortKey(longURL); String existingLongURL = redisTemplate.opsForValue().get(shortKey); if (existingLongURL != null ) { return shortKey; } redisTemplate.opsForValue().set(shortKey, longURL); return shortKey; } catch (Exception e) { e.printStackTrace(); return "error" ; } } public String restoreURL (String shortKey) { try { return redisTemplate.opsForValue().get(shortKey); } catch (DataAccessException e) { e.printStackTrace(); return "error" ; } } private String generateShortKey (String longURL) { try { MessageDigest md = MessageDigest.getInstance("MD5" ); md.update(longURL.getBytes()); byte [] hashBytes = md.digest(); StringBuilder sb = new StringBuilder (); for (byte b : hashBytes) { sb.append(String.format("%02x" , b & 0xff )); } return sb.substring(0 , 8 ); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return "default123" ; } } private boolean isValidURL (String url) { String regex = "^(http|https)://[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}(/.*)?$" ; return Pattern.matches(regex, url); } }
application.properties 1 2 3 4 spring.redis.host =1.15 .xx.xxxspring.redis.port =6379 server.port =7777
pom.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.7.12</version > <relativePath /> </parent > <groupId > top.xinhaojin</groupId > <artifactId > short-url</artifactId > <version > 0.0.1-SNAPSHOT</version > <name > short-url</name > <description > short-url</description > <properties > <java.version > 8</java.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > redis.clients</groupId > <artifactId > jedis</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
docker部署 在项目根目录终端下执行以下maven命令,会在target下生成jar文件
新建一个名为Dockerfile的文件,内容如下 1 2 3 4 5 6 7 8 9 10 11 FROM openjdk:8 -jdk-alpineCOPY your-application-name-version.jar /app.jar EXPOSE 7777 CMD ["java" , "-jar" , "/app.jar" ]
在有docker环境的地方如安装了docker的Ubuntu服务器上,创建docker镜像 1 2 docker build -t short-url:v1.0 .
运行docker镜像 1 2 docker run --restart=always --name shorturl -d -p 7777:7777 short-url:v1.0
检查运行情况
绑定域名,用nginx做好反向代理 解析一个二级域名到目标服务器,如s.xinhaojin.top
查看nginx版本,如果没有,就安装 1 2 3 nginx -vsudo apt updatesudo apt install nginx
修改/etc/nginx/nginx.conf 在http标签里添加一个server
1 2 3 4 5 6 7 8 9 10 11 12 server { listen 80 ; server_name s.xinhaojin.top; location / { proxy_pass http://1.15.xx.xxx:7777; proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $scheme ; } }
检查 Nginx 服务是否已经存在于 systemd 中: 1 sudo systemctl is-enabled nginx
如果返回 disabled,则表示 Nginx 目前不会开机自启。 如果 Nginx 尚未开机自启,可以使用以下命令启用:
1 sudo systemctl enable nginx
申请SSL证书 安装certbot
1 2 sudo apt updatesudo apt install certbot
临时关闭nginx以释放80端口
1 2 sudo systemctl stop nginxsudo certbot certonly --standalone -d s.xinhaojin.top
修改nginx配置/etc/nginx/nginx.conf 把上一个server删去,替换以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 server { listen 80 ; server_name s.xinhaojin.top; return 301 https://$host $request_uri ; }server { listen 443 ssl; server_name s.xinhaojin.top; ssl_certificate /etc/letsencrypt/live/s.xinhaojin.top/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/s.xinhaojin.top/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3 ; ssl_prefer_server_ciphers off ; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; location / { proxy_pass http://127.0.0.1:7777; proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $scheme ; } location ~ /.well-known { allow all; } }
重启nginx 1 2 sudo systemctl start nginxsudo systemctl reload nginx