feat:完成短链接生成基本功能
This commit is contained in:
@@ -31,3 +31,5 @@ build/
|
|||||||
|
|
||||||
### VS Code ###
|
### VS Code ###
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
application-prod.properties
|
||||||
@@ -30,6 +30,16 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mybatis.spring.boot</groupId>
|
||||||
|
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||||
|
<version>2.1.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>5.6.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package top.naccl.dwz.controller;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import top.naccl.dwz.entity.R;
|
||||||
|
import top.naccl.dwz.service.UrlService;
|
||||||
|
import top.naccl.dwz.util.HashUtils;
|
||||||
|
import top.naccl.dwz.util.UrlUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description:
|
||||||
|
* @Author: Naccl
|
||||||
|
* @Date: 2021-03-21
|
||||||
|
*/
|
||||||
|
@Controller
|
||||||
|
public class IndexController {
|
||||||
|
@Autowired
|
||||||
|
UrlService urlService;
|
||||||
|
private static String host;
|
||||||
|
|
||||||
|
@Value("${server.host}")
|
||||||
|
public void setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/")
|
||||||
|
public String index() {
|
||||||
|
return "index";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/generate")
|
||||||
|
@ResponseBody
|
||||||
|
public R generateShortURL(@RequestParam String longURL) {
|
||||||
|
if (UrlUtils.checkURL(longURL)) {
|
||||||
|
String shortURL = urlService.saveUrlMap(HashUtils.hashToBase62(longURL), longURL);
|
||||||
|
return R.ok("请求成功", host + shortURL);
|
||||||
|
} else {
|
||||||
|
return R.create(400, "URL有误");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package top.naccl.dwz.entity;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 响应结果封装
|
||||||
|
* @Author: Naccl
|
||||||
|
* @Date: 2021-03-21
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class R {
|
||||||
|
private Integer code;
|
||||||
|
private String msg;
|
||||||
|
private Object data;
|
||||||
|
|
||||||
|
public R(Integer code, String msg) {
|
||||||
|
this.code = code;
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static R ok(String msg, Object data) {
|
||||||
|
return new R(200, msg, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static R create(Integer code, String msg, Object data) {
|
||||||
|
return new R(code, msg, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static R create(Integer code, String msg) {
|
||||||
|
return new R(code, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package top.naccl.dwz.entity;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 长短链接映射
|
||||||
|
* @Author: Naccl
|
||||||
|
* @Date: 2021-03-22
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class UrlMap {
|
||||||
|
private Long id;
|
||||||
|
private String surl;//短链接
|
||||||
|
private String lurl;//长链接
|
||||||
|
private Date createTime;//创建时间
|
||||||
|
|
||||||
|
public UrlMap(String surl, String lurl, Date createTime) {
|
||||||
|
this.surl = surl;
|
||||||
|
this.lurl = lurl;
|
||||||
|
this.createTime = createTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package top.naccl.dwz.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import top.naccl.dwz.entity.UrlMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 长短链接映射持久层接口
|
||||||
|
* @Author: Naccl
|
||||||
|
* @Date: 2021-03-22
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
@Repository
|
||||||
|
public interface UrlMapper {
|
||||||
|
String getLongUrlByShortUrl(String surl);
|
||||||
|
|
||||||
|
int saveUrlMap(UrlMap urlMap);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package top.naccl.dwz.service;
|
||||||
|
|
||||||
|
public interface UrlService {
|
||||||
|
String getLongUrlByShortUrl(String shortURL);
|
||||||
|
|
||||||
|
String saveUrlMap(String shortURL, String longURL);
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package top.naccl.dwz.service.impl;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import top.naccl.dwz.entity.UrlMap;
|
||||||
|
import top.naccl.dwz.mapper.UrlMapper;
|
||||||
|
import top.naccl.dwz.service.UrlService;
|
||||||
|
import top.naccl.dwz.util.HashUtils;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: 长短链接映射业务层实现
|
||||||
|
* @Author: Naccl
|
||||||
|
* @Date: 2021-03-22
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class UrlServiceImpl implements UrlService {
|
||||||
|
@Autowired
|
||||||
|
UrlMapper urlMapper;
|
||||||
|
private static final String DUPLICATE = " *";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLongUrlByShortUrl(String shortURL) {
|
||||||
|
return urlMapper.getLongUrlByShortUrl(shortURL).replace(DUPLICATE, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String saveUrlMap(String shortURL, String longURL) {
|
||||||
|
//在布隆过滤器中查找是否存在
|
||||||
|
if (judgeExist()) {
|
||||||
|
//存在,在长链接后加上指定字符串,重新hash
|
||||||
|
longURL += DUPLICATE;
|
||||||
|
shortURL = HashUtils.hashToBase62(longURL);
|
||||||
|
shortURL = saveUrlMap(shortURL, longURL);
|
||||||
|
return shortURL;
|
||||||
|
} else {
|
||||||
|
//不存在,直接存入数据库
|
||||||
|
try {
|
||||||
|
urlMapper.saveUrlMap(new UrlMap(shortURL, longURL, new Date()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (e instanceof DuplicateKeyException) {
|
||||||
|
//数据库已经存在此短链接,则可能是布隆过滤器误判,在长链接后加上指定字符串,重新hash
|
||||||
|
longURL += DUPLICATE;
|
||||||
|
shortURL = HashUtils.hashToBase62(longURL);
|
||||||
|
shortURL = saveUrlMap(shortURL, longURL);
|
||||||
|
return shortURL;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shortURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean judgeExist() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package top.naccl.dwz.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.hash.MurmurHash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: URL hash并转换base62
|
||||||
|
* @Author: Naccl
|
||||||
|
* @Date: 2021-03-22
|
||||||
|
*/
|
||||||
|
public class HashUtils {
|
||||||
|
private static char[] CHARS = new char[]{
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
|
||||||
|
};
|
||||||
|
private static int SIZE = CHARS.length;
|
||||||
|
|
||||||
|
private static String convertDecToBase62(long num) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
while (num > 0) {
|
||||||
|
int i = (int) (num % SIZE);
|
||||||
|
sb.append(CHARS[i]);
|
||||||
|
num /= SIZE;
|
||||||
|
}
|
||||||
|
return sb.reverse().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String hashToBase62(String str) {
|
||||||
|
int i = MurmurHash.hash32(str);
|
||||||
|
long num = i < 0 ? Integer.MAX_VALUE - (long) i : i;
|
||||||
|
return convertDecToBase62(num);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package top.naccl.dwz.util;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: URL校验
|
||||||
|
* @Author: Naccl
|
||||||
|
* @Date: 2021-03-24
|
||||||
|
*/
|
||||||
|
public class UrlUtils {
|
||||||
|
private static final Pattern URL_REG = Pattern.compile("^(((ht|f)tps?):\\/\\/)?[\\w-]+(\\.[\\w-]+)+([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?$");
|
||||||
|
|
||||||
|
public static boolean checkURL(String url) {
|
||||||
|
return URL_REG.matcher(url).matches();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
server.port=8060
|
||||||
|
server.host=http://localhost:8060/
|
||||||
|
|
||||||
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
|
spring.datasource.url=jdbc:mysql://localhost:3306/dwz?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
|
||||||
|
spring.datasource.username=root
|
||||||
|
spring.datasource.password=root
|
||||||
|
|
||||||
|
spring.redis.host=192.168.17.132
|
||||||
|
spring.redis.password=123456
|
||||||
|
spring.redis.port=6379
|
||||||
|
spring.redis.database=1
|
||||||
|
spring.redis.timeout=10000ms
|
||||||
|
|
||||||
|
mybatis.mapper-locations=classpath:mapper/*.xml
|
||||||
|
mybatis.configuration.map-underscore-to-camel-case=true
|
||||||
|
|
||||||
|
logging.level.root=info
|
||||||
|
logging.level.top.naccl.dwz=debug
|
||||||
@@ -1 +1,3 @@
|
|||||||
|
spring.thymeleaf.mode=HTML
|
||||||
|
|
||||||
|
spring.profiles.active=dev
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||||
|
<mapper namespace="top.naccl.dwz.mapper.UrlMapper">
|
||||||
|
<select id="getLongUrlByShortUrl" resultType="java.lang.String">
|
||||||
|
select lurl from url_map where surl=#{surl}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="saveUrlMap" parameterType="top.naccl.dwz.entity.UrlMap" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
insert into url_map (surl, lurl, create_time) values (#{surl}, #{lurl}, #{createTime})
|
||||||
|
</insert>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
hsl(170, 80%, 70%),
|
||||||
|
hsl(190, 80%, 70%),
|
||||||
|
hsl(250, 80%, 70%),
|
||||||
|
hsl(320, 80%, 70%)
|
||||||
|
);
|
||||||
|
background-size: 200% 200%;
|
||||||
|
animation: gradient-move 15s ease alternate infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradient-move {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 0%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 100% 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>短链接生成</title>
|
||||||
|
<link rel="stylesheet" href="/css/base.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="box">
|
||||||
|
<input type="text" id="long">
|
||||||
|
<button type="button" id="generate">生成</button>
|
||||||
|
<input type="text" id="short">
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$('#generate').click(() => {
|
||||||
|
let longURL = $('#long').val();
|
||||||
|
if (longURL) {
|
||||||
|
$.post("/generate", {
|
||||||
|
longURL
|
||||||
|
}, function (res) {
|
||||||
|
if (res.code === 200) {
|
||||||
|
$('#short').val(res.data);
|
||||||
|
} else {
|
||||||
|
alert(res.msg);
|
||||||
|
}
|
||||||
|
}, "json").fail(() => {
|
||||||
|
alert('异常错误');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('请输入原始链接');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user