赞
踩
我们经常看到微博和短信中用到了短链接,其目的就是能够将冗余的长链接精简。
然后在码云上看到一个生成短链接一个项目:urlshorter: 满足多种场景下的短链接生成需求 (gitee.com),然后自己在此基础上修改了一下,本人技术有限,仅供参考
public class SnowFlakeGeneratorRandom implements StringGenerator {
@Override
public String generate(String url) {
SnowFlakeShortUrl snowFlake = new SnowFlakeShortUrl(2, 3);
Long id = snowFlake.nextId(); //10进制
return NumericConvertUtils.toOtherNumberSystem(id, 62); //62进制
}
@Override
public void setLength(int length) {}
}
public class NumericConvertUtils { /** * 在进制表示中的字符集合,0-Z分别用于表示最大为62进制的符号表示 */ private static final char[] digits = {'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'}; /** * 将十进制的数字转换为指定进制的字符串 * @param number 十进制的数字 * @param seed 指定的进制 * @return 指定进制的字符串 */ public static String toOtherNumberSystem(long number, int seed) { if (number < 0) { number = ((long) 2 * 0x7fffffff) + number + 2; } char[] buf = new char[32]; int charPos = 32; while ((number / seed) > 0) { buf[--charPos] = digits[(int) (number % seed)]; number /= seed; } buf[--charPos] = digits[(int) (number % seed)]; return new String(buf, charPos, (32 - charPos)); } }
public class SnowFlakeShortUrl { /** * 起始的时间戳 */ private final static long START_TIMESTAMP = 1480166465631L; /** * 每一部分占用的位数 */ private final static long SEQUENCE_BIT = 12; //序列号占用的位数 private final static long MACHINE_BIT = 5; //机器标识占用的位数 private final static long DATA_CENTER_BIT = 5; //数据中心占用的位数 /** * 每一部分的最大值 */ private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); private final static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT); /** * 每一部分向左的位移 */ private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT; private long dataCenterId; //数据中心 private long machineId; //机器标识 private long sequence = 0L; //序列号 private long lastTimeStamp = -1L; //上一次时间戳 /** * 根据指定的数据中心ID和机器标志ID生成指定的序列号 * @param dataCenterId 数据中心ID * @param machineId 机器标志ID */ public SnowFlakeShortUrl(long dataCenterId, long machineId) { if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) { throw new IllegalArgumentException("DtaCenterId can't be greater than MAX_DATA_CENTER_NUM or less than 0!"); } if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("MachineId can't be greater than MAX_MACHINE_NUM or less than 0!"); } this.dataCenterId = dataCenterId; this.machineId = machineId; } /** * 产生下一个ID * @return */ public synchronized long nextId() { long currTimeStamp = getNewTimeStamp(); if (currTimeStamp < lastTimeStamp) { throw new RuntimeException("Clock moved backwards. Refusing to generate id"); } if (currTimeStamp == lastTimeStamp) { //相同毫秒内,序列号自增 sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列数已经达到最大 if (sequence == 0L) { currTimeStamp = getNextMill(); } } else { //不同毫秒内,序列号置为0 sequence = 0L; } lastTimeStamp = currTimeStamp; return (currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT //时间戳部分 | dataCenterId << DATA_CENTER_LEFT //数据中心部分 | machineId << MACHINE_LEFT //机器标识部分 | sequence; //序列号部分 } private long getNextMill() { long mill = getNewTimeStamp(); while (mill <= lastTimeStamp) { mill = getNewTimeStamp(); } return mill; } private long getNewTimeStamp() { return System.currentTimeMillis(); } }
public class ShorterStorageMemory<T extends ShorterGetter> implements ShorterStorage<T> { /** * 存储shorter,url */ private static RedisUtils redisUtils = SpringUtils.getBean(RedisUtils.class); private static final Long expiration_time = 60 * 60 * 24L; public String get(String shorterKey) { Object o = redisUtils.get(shorterKey); if (ObjectUtils.isNotEmpty(o)) { return o + ""; } return null; } public String getMethod(String shorterKey) { Object o = redisUtils.get(shorterKey); if (ObjectUtils.isNotEmpty(o)) { String url = o + ""; Object o1 = redisUtils.get(url); if (ObjectUtils.isNotEmpty(o1)) { return o1.getClass().getName(); } } return null; } public String getMethodClass(String url) { Object o = redisUtils.get(url); if (ObjectUtils.isNotEmpty(o)) { return JsonUtil.ObjectToJson(o); } return null; } public String getShortUrl(String url) { T o = (T) redisUtils.get(url); if (ObjectUtils.isNotEmpty(o)) { return o.getShorter(); } return null; } public void clean(String url) { ShorterGetter shorter = (ShorterGetter) redisUtils.get(url); if (shorter != null) { redisUtils.delete(url); redisUtils.delete(shorter.getShorter()); } } @Override public void cleanShorter(String shorter) {} /** * 保存映射 * @param url * @param shorter */ public void save(String url, T shorter) { redisUtils.set(url,shorter,expiration_time); redisUtils.set(shorter.getShorter(),url,expiration_time); } /** * 保存带失效时间的映射 * @param url * @param shorter * @param time */ @Override public void save(String url, T shorter, long time) { redisUtils.set(url,shorter,time); redisUtils.set(shorter.getShorter(),url,time); } /** * 短链接续期 * @param url * @param shorter */ @Override public void renew(String url, T shorter) { redisUtils.expire(url,expiration_time, TimeUnit.SECONDS); redisUtils.expire(shorter.getShorter(),expiration_time, TimeUnit.SECONDS); } @Override public void clean() {} }
@Slf4j @RestController @RequestMapping("/") public class UrlController { @Autowired private ShortUrlService shortUrlService; @PostMapping public String createShortUrl(@RequestBody ShortUrlRequest request) { return shortUrlService.generationShortUrl(request.getUrl(),request.getMethod(),1000L,1000L); } @GetMapping("/{shortUrl}") public void url(@PathVariable("shortUrl") String shortUrl,HttpServletResponse response) throws IOException { shortUrlService.getUrl(shortUrl,response); } }
@Slf4j @Service public class ShortUrlService { private static final int urlLen = 6; private static final String HTTP_PREFIX = "http://127.0.0.1:8088/"; @Autowired private RedisUtils redisUtils; /** * 生成短链接 * * @param url * @return */ public String generationShortUrl(String url, String method, Long period, Long time) { if (redisUtils.hasKey(url)) { ShorterStorageMemory storageMemory = new ShorterStorageMemory(); return HTTP_PREFIX + storageMemory.getShortUrl(url); } GeneratorShortUrlMethodEnum generatorShortUrlMethodEnum = GeneratorShortUrlMethodEnum.getShortUrlMethodEnum(method); if (generatorShortUrlMethodEnum == null) { throw new RuntimeException("方法参数有误"); } String shortUrl = ""; synchronized (this) { UrlShorterGeneratorFactory factory = new UrlShorterGeneratorFactory(); UrlShorterGenerator urlShorterGenerator = factory.getShortUrl(generatorShortUrlMethodEnum, period, time); shortUrl = urlShorterGenerator.generate(url).getShorter(); return HTTP_PREFIX + shortUrl; } } /** * 根据短链接获取原本的链接 * * @param shortUrl * @return */ public void getUrl(String shortUrl, HttpServletResponse response) { try { ShorterStorageMemory shorterStringShorterStorageMemory = new ShorterStorageMemory<>(); String url = shorterStringShorterStorageMemory.get(shortUrl); if (StringUtils.isBlank(url)) { throw new RuntimeException("该链接已失效"); } String method = shorterStringShorterStorageMemory.getMethod(shortUrl); String key = shortUrl + "_increment"; if (method.contains("ShorterString")) { ShorterString shorterString = JsonUtil.JsonToObject(shorterStringShorterStorageMemory.getMethodClass(url), ShorterString.class); shorterStringShorterStorageMemory.renew(url,shorterString); } else if (method.contains("ShorterWithPassword")) { ShorterWithPassword shorterWithPassword = JsonUtil.JsonToObject(shorterStringShorterStorageMemory.getMethodClass(url), ShorterWithPassword.class); } else if (method.contains("ShorterWithPeriod")) { ShorterWithPeriod shorterWithPeriod = JsonUtil.JsonToObject(shorterStringShorterStorageMemory.getMethodClass(url), ShorterWithPeriod.class); shorterStringShorterStorageMemory.renew(url,shorterWithPeriod); limitCount(shorterStringShorterStorageMemory, url, key, shorterWithPeriod.getPeriod()); } else if (method.contains("ShorterWithPeriodAndTimes")) { ShorterWithPeriodAndTimes shorterWithPeriodAndTimes = JsonUtil.JsonToObject(shorterStringShorterStorageMemory.getMethodClass(url), ShorterWithPeriodAndTimes.class); limitCount(shorterStringShorterStorageMemory,url,key,shorterWithPeriodAndTimes.getPeriod()); } else if (method.contains("ShorterWithTimes")) { ShorterWithTimes shorterWithTimes = JsonUtil.JsonToObject(shorterStringShorterStorageMemory.getMethodClass(url), ShorterWithTimes.class); } if (ObjectUtils.isNotEmpty(url) && StringUtils.isNotBlank(method)) { response.sendRedirect(url); } } catch (Exception e) { log.error("获取短链接失败:{}", e.getMessage()); throw new RuntimeException("该链接已失效"); } } public void limitCount(ShorterStorageMemory memory, String url, String key, long count) { if (redisUtils.hasKey(key)) { int limit = Integer.parseInt(redisUtils.get(key) + ""); if (limit > count) { memory.clean(url); redisUtils.delete(key); throw new RuntimeException("该链接已失效"); } redisUtils.incrBy(key, 1L); } else { redisUtils.set(key, 1); } }
public class UrlShorterGeneratorFactory { public UrlShorterGenerator getShortUrl(GeneratorShortUrlMethodEnum shortUrlMethodEnum, long period, long time) { UrlShorterGenerator urlShorterGenerator = null; switch (shortUrlMethodEnum) { case SHORT_URL_LIMIT: urlShorterGenerator = getUrlShorterGeneratorSimple(); break; case SHORT_URL_LIMIT_PERIOD: urlShorterGenerator = getUrlShorterGeneratorLimitPeriod(period); break; case SHORT_URL_LIMIT_TIME: urlShorterGenerator = getUrlShorterGeneratorLimitTimes(time); case SHORT_URL_LIMIT_PERIOD_TIME: urlShorterGenerator = getUrlShorterGeneratorLimitPeriodAndTimes(period, time); case SHORT_URL_LIMIT_PASSWORD: urlShorterGenerator = getUrlShorterGeneratorWithPassword(); default: throw new RuntimeException(); } return urlShorterGenerator; } private UrlShorterGeneratorSimple getUrlShorterGeneratorSimple() { UrlShorterGeneratorSimple simple = new UrlShorterGeneratorSimple(); simple.setGenerator(new SnowFlakeGeneratorRandom()); simple.setShorterStorage(new ShorterStorageMemory<ShorterString>()); return simple; } private UrlShorterGeneratorLimitPeriod getUrlShorterGeneratorLimitPeriod(long count) { UrlShorterGeneratorLimitPeriod limitPeriod = new UrlShorterGeneratorLimitPeriod(); limitPeriod.setGenerator(new SnowFlakeGeneratorRandom()); limitPeriod.setPeriod(count); limitPeriod.setShorterStorage(new ShorterStorageMemory<ShorterWithPeriod>()); return limitPeriod; } private UrlShorterGeneratorLimitPeriodAndTimes getUrlShorterGeneratorLimitPeriodAndTimes(long count, long time) { UrlShorterGeneratorLimitPeriodAndTimes limitPeriodAndTimes = new UrlShorterGeneratorLimitPeriodAndTimes(); limitPeriodAndTimes.setGenerator(new SnowFlakeGeneratorRandom()); limitPeriodAndTimes.setPeriod(count); limitPeriodAndTimes.setTimes(time); limitPeriodAndTimes.setShorterStorage(new ShorterStorageMemory<ShorterWithPeriodAndTimes>()); return limitPeriodAndTimes; } private UrlShorterGeneratorLimitTimes getUrlShorterGeneratorLimitTimes(long time) { UrlShorterGeneratorLimitTimes limitTimes = new UrlShorterGeneratorLimitTimes(); limitTimes.setGenerator(new SnowFlakeGeneratorRandom()); limitTimes.setTimes(time); limitTimes.setShorterStorage(new ShorterStorageMemory<ShorterWithTimes>()); return limitTimes; } private UrlShorterGeneratorWithPassword getUrlShorterGeneratorWithPassword() { UrlShorterGeneratorWithPassword withPassword = new UrlShorterGeneratorWithPassword(); withPassword.setShorterGenerator(new SnowFlakeGeneratorRandom()); withPassword.setPasswordGenerator(new StringGeneratorRandom()); withPassword.setShorterStorage(new ShorterStorageMemory<ShorterWithPassword>()); return withPassword; } }
@Getter @NoArgsConstructor @AllArgsConstructor public enum GeneratorShortUrlMethodEnum { SHORT_URL_LIMIT_PERIOD("limit.period","用于生成限制访问次数的短链接"), SHORT_URL_LIMIT_PERIOD_TIME("limit.period.time","用于生成限制访问次数和生效时间的短链接"), SHORT_URL_LIMIT_TIME("limit.time","用于生成限制生效时间的短链接"), SHORT_URL_LIMIT("limit","用于短链接"), SHORT_URL_LIMIT_PASSWORD("limit.password","用于生成带密码的短链接"); private String method; private String methodDesc; public static GeneratorShortUrlMethodEnum getShortUrlMethodEnum(String method) { if (StringUtils.isBlank(method)) { return null; } for (GeneratorShortUrlMethodEnum s : GeneratorShortUrlMethodEnum.values()) { if (method.equals(s.getMethod())) { return s; } } return null; } }
1.若没有指定失效时间,默认失效时间是24小时
2.本次保存短链接映射的使用的是redis,当然也可以用mysql等
3.生成短链接的前缀是自己服务器的域名,这样就可以通过在网页输入短链接直接跳
转到原链接,自己也可以记录访问短链接的次数;
注:这边方法一些类没有写,这个可以去上面作者的链接去看
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。