15 changed files with 363 additions and 0 deletions
@ -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> |
Loading…
Reference in new issue