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