26 |
27 |
28 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/main/java/top/naccl/dwz/controller/IndexController.java:
--------------------------------------------------------------------------------
1 | package top.naccl.dwz.controller;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.stereotype.Controller;
6 | import org.springframework.web.bind.annotation.GetMapping;
7 | import org.springframework.web.bind.annotation.PathVariable;
8 | import org.springframework.web.bind.annotation.PostMapping;
9 | import org.springframework.web.bind.annotation.RequestBody;
10 | import org.springframework.web.bind.annotation.ResponseBody;
11 | import top.naccl.dwz.annotation.AccessLimit;
12 | import top.naccl.dwz.entity.GenerateCmd;
13 | import top.naccl.dwz.entity.R;
14 | import top.naccl.dwz.service.UrlService;
15 | import top.naccl.dwz.util.HashUtils;
16 | import top.naccl.dwz.util.UrlUtils;
17 |
18 | /**
19 | * @Description:
20 | * @Author: Naccl
21 | * @Date: 2021-03-21
22 | */
23 | @Controller
24 | public class IndexController {
25 | @Autowired
26 | UrlService urlService;
27 | private static String host;
28 |
29 | @Value("${server.host}")
30 | public void setHost(String host) {
31 | IndexController.host = host;
32 | }
33 |
34 | @GetMapping("/")
35 | public String index() {
36 | return "index";
37 | }
38 |
39 | @AccessLimit(seconds = 10, maxCount = 1, msg = "10秒内只能生成一次短链接")
40 | @PostMapping("/generate")
41 | @ResponseBody
42 | public R generateShortURL(@RequestBody GenerateCmd cmd) {
43 | String longURL = cmd.getLongURL();
44 | if (UrlUtils.checkURL(longURL)) {
45 | if (!longURL.startsWith("http")) {
46 | longURL = "http://" + longURL;
47 | }
48 | String shortURL = urlService.saveUrlMap(HashUtils.hashToBase62(longURL), longURL, longURL);
49 | return R.ok("请求成功", host + shortURL);
50 | }
51 | return R.create(400, "URL有误");
52 | }
53 |
54 | @GetMapping("/{shortURL}")
55 | public String redirect(@PathVariable String shortURL) {
56 | String longURL = urlService.getLongUrlByShortUrl(shortURL);
57 | if (longURL != null) {
58 | urlService.updateUrlViews(shortURL);
59 | //查询到对应的原始链接,302重定向
60 | return "redirect:" + longURL;
61 | }
62 | //没有对应的原始链接,直接返回首页
63 | return "redirect:/";
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/top/naccl/dwz/interceptor/AccessLimitInterceptor.java:
--------------------------------------------------------------------------------
1 | package top.naccl.dwz.interceptor;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.data.redis.core.StringRedisTemplate;
5 | import org.springframework.stereotype.Component;
6 | import org.springframework.web.method.HandlerMethod;
7 | import org.springframework.web.servlet.HandlerInterceptor;
8 | import top.naccl.dwz.annotation.AccessLimit;
9 | import top.naccl.dwz.entity.R;
10 | import top.naccl.dwz.util.IpAddressUtils;
11 | import top.naccl.dwz.util.JacksonUtils;
12 |
13 | import javax.servlet.http.HttpServletRequest;
14 | import javax.servlet.http.HttpServletResponse;
15 | import java.io.PrintWriter;
16 | import java.util.concurrent.TimeUnit;
17 |
18 | /**
19 | * @Description: 访问控制拦截器
20 | * @Author: Naccl
21 | * @Date: 2021-09-16
22 | */
23 | @Component
24 | public class AccessLimitInterceptor implements HandlerInterceptor {
25 | @Autowired
26 | StringRedisTemplate redisTemplate;
27 |
28 | @Override
29 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
30 | if (handler instanceof HandlerMethod) {
31 | HandlerMethod handlerMethod = (HandlerMethod) handler;
32 | AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
33 | //方法上没有访问控制的注解,直接通过
34 | if (accessLimit == null) {
35 | return true;
36 | }
37 | int seconds = accessLimit.seconds();
38 | int maxCount = accessLimit.maxCount();
39 | String ip = IpAddressUtils.getIpAddress(request);
40 | String method = request.getMethod();
41 | String requestURI = request.getRequestURI();
42 |
43 | String redisKey = ip + ":" + method + ":" + requestURI;
44 | Object redisResult = redisTemplate.opsForValue().get(redisKey);
45 | Integer count = JacksonUtils.convertValue(redisResult, Integer.class);
46 | if (count == null) {
47 | //在规定周期内第一次访问,存入redis
48 | redisTemplate.opsForValue().increment(redisKey, 1);
49 | redisTemplate.expire(redisKey, seconds, TimeUnit.SECONDS);
50 | } else {
51 | if (count >= maxCount) {
52 | //超出访问限制次数
53 | response.setContentType("application/json;charset=utf-8");
54 | PrintWriter out = response.getWriter();
55 | R result = R.create(403, accessLimit.msg());
56 | out.write(JacksonUtils.writeValueAsString(result));
57 | out.flush();
58 | out.close();
59 | return false;
60 | } else {
61 | //没超出访问限制次数
62 | redisTemplate.opsForValue().increment(redisKey, 1);
63 | }
64 | }
65 | }
66 | return true;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/top/naccl/dwz/service/impl/UrlServiceImpl.java:
--------------------------------------------------------------------------------
1 | package top.naccl.dwz.service.impl;
2 |
3 | import cn.hutool.bloomfilter.BitMapBloomFilter;
4 | import cn.hutool.bloomfilter.BloomFilterUtil;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.dao.DuplicateKeyException;
7 | import org.springframework.data.redis.core.StringRedisTemplate;
8 | import org.springframework.stereotype.Service;
9 | import top.naccl.dwz.entity.UrlMap;
10 | import top.naccl.dwz.mapper.UrlMapper;
11 | import top.naccl.dwz.service.UrlService;
12 | import top.naccl.dwz.util.HashUtils;
13 |
14 | import java.util.concurrent.TimeUnit;
15 |
16 | /**
17 | * @Description: 长短链接映射业务层实现
18 | * @Author: Naccl
19 | * @Date: 2021-03-22
20 | */
21 | @Service
22 | public class UrlServiceImpl implements UrlService {
23 | @Autowired
24 | UrlMapper urlMapper;
25 | @Autowired
26 | StringRedisTemplate redisTemplate;
27 | //自定义长链接防重复字符串
28 | private static final String DUPLICATE = "*";
29 | //最近使用的短链接缓存过期时间(分钟)
30 | private static final long TIMEOUT = 10;
31 | //创建布隆过滤器
32 | private static final BitMapBloomFilter FILTER = BloomFilterUtil.createBitMap(10);
33 |
34 | @Override
35 | public String getLongUrlByShortUrl(String shortURL) {
36 | //查找Redis中是否有缓存
37 | String longURL = redisTemplate.opsForValue().get(shortURL);
38 | if (longURL != null) {
39 | //有缓存,延迟缓存时间
40 | redisTemplate.expire(shortURL, TIMEOUT, TimeUnit.MINUTES);
41 | return longURL;
42 | }
43 | //Redis没有缓存,从数据库查找
44 | longURL = urlMapper.getLongUrlByShortUrl(shortURL);
45 | if (longURL != null) {
46 | //数据库有此短链接,添加缓存
47 | redisTemplate.opsForValue().set(shortURL, longURL, TIMEOUT, TimeUnit.MINUTES);
48 | }
49 | return longURL;
50 | }
51 |
52 | @Override
53 | public String saveUrlMap(String shortURL, String longURL, String originalURL) {
54 | //保留长度为1的短链接
55 | if (shortURL.length() == 1) {
56 | longURL += DUPLICATE;
57 | shortURL = saveUrlMap(HashUtils.hashToBase62(longURL), longURL, originalURL);
58 | }
59 | //在布隆过滤器中查找是否存在
60 | else if (FILTER.contains(shortURL)) {
61 | //存在,从Redis中查找是否有缓存
62 | String redisLongURL = redisTemplate.opsForValue().get(shortURL);
63 | if (redisLongURL != null && originalURL.equals(redisLongURL)) {
64 | //Redis有缓存,重置过期时间
65 | redisTemplate.expire(shortURL, TIMEOUT, TimeUnit.MINUTES);
66 | return shortURL;
67 | }
68 | //没有缓存,在长链接后加上指定字符串,重新hash
69 | longURL += DUPLICATE;
70 | shortURL = saveUrlMap(HashUtils.hashToBase62(longURL), longURL, originalURL);
71 | } else {
72 | //不存在,直接存入数据库
73 | try {
74 | urlMapper.saveUrlMap(new UrlMap(shortURL, originalURL));
75 | FILTER.add(shortURL);
76 | //添加缓存
77 | redisTemplate.opsForValue().set(shortURL, originalURL, TIMEOUT, TimeUnit.MINUTES);
78 | } catch (Exception e) {
79 | if (e instanceof DuplicateKeyException) {
80 | //数据库已经存在此短链接,则可能是布隆过滤器误判,在长链接后加上指定字符串,重新hash
81 | longURL += DUPLICATE;
82 | shortURL = saveUrlMap(HashUtils.hashToBase62(longURL), longURL, originalURL);
83 | } else {
84 | throw e;
85 | }
86 | }
87 | }
88 | return shortURL;
89 | }
90 |
91 | @Override
92 | public void updateUrlViews(String shortURL) {
93 | urlMapper.updateUrlViews(shortURL);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |