Browse Source

feat:完成短链接生成基本功能

master
Naccl 4 years ago
parent
commit
b8de8e31dc
  1. 2
      .gitignore
  2. 10
      pom.xml
  3. 47
      src/main/java/top/naccl/dwz/controller/IndexController.java
  4. 40
      src/main/java/top/naccl/dwz/entity/R.java
  5. 30
      src/main/java/top/naccl/dwz/entity/UrlMap.java
  6. 18
      src/main/java/top/naccl/dwz/mapper/UrlMapper.java
  7. 7
      src/main/java/top/naccl/dwz/service/UrlService.java
  8. 60
      src/main/java/top/naccl/dwz/service/impl/UrlServiceImpl.java
  9. 33
      src/main/java/top/naccl/dwz/util/HashUtils.java
  10. 16
      src/main/java/top/naccl/dwz/util/UrlUtils.java
  11. 19
      src/main/resources/application-dev.properties
  12. 2
      src/main/resources/application.properties
  13. 11
      src/main/resources/mapper/UrlMapper.xml
  14. 32
      src/main/resources/static/css/base.css
  15. 36
      src/main/resources/templates/index.html

2
.gitignore vendored

@ -31,3 +31,5 @@ build/
### VS Code ###
.vscode/
application-prod.properties

10
pom.xml

@ -30,6 +30,16 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
<groupId>org.springframework.boot</groupId>

47
src/main/java/top/naccl/dwz/controller/IndexController.java

@ -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有误");
}
}
}

40
src/main/java/top/naccl/dwz/entity/R.java

@ -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);
}
}

30
src/main/java/top/naccl/dwz/entity/UrlMap.java

@ -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;
}
}

18
src/main/java/top/naccl/dwz/mapper/UrlMapper.java

@ -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);
}

7
src/main/java/top/naccl/dwz/service/UrlService.java

@ -0,0 +1,7 @@
package top.naccl.dwz.service;
public interface UrlService {
String getLongUrlByShortUrl(String shortURL);
String saveUrlMap(String shortURL, String longURL);
}

60
src/main/java/top/naccl/dwz/service/impl/UrlServiceImpl.java

@ -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;
}
}

33
src/main/java/top/naccl/dwz/util/HashUtils.java

@ -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);
}
}

16
src/main/java/top/naccl/dwz/util/UrlUtils.java

@ -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();
}
}

19
src/main/resources/application-dev.properties

@ -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

2
src/main/resources/application.properties

@ -1 +1,3 @@
spring.thymeleaf.mode=HTML
spring.profiles.active=dev

11
src/main/resources/mapper/UrlMapper.xml

@ -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>

32
src/main/resources/static/css/base.css

@ -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%);
}

36
src/main/resources/templates/index.html

@ -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…
Cancel
Save