# urlshorter **Repository Path**: imoocmayun/urlshorter ## Basic Information - **Project Name**: urlshorter - **Description**: No description available - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 117 - **Created**: 2018-04-23 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #urlshorter 首先我要说,开源托管,必须得 @红薯 家的。 上一次本人写过一篇博客《长URL转短连接的简单设计与实现》,由于写得比较仓促,是缺少设计的,因此方案也是不完整的,看到大家非常有热情,阅读的阅读收藏的收藏,我就深深的为写了这么不够深入的博客而感到不安,于是就有了这一篇博客,以及背后的开源代码。 确实,这次花费时间比较多,大概有大半天的时间设计并写代码。 #需求 首先说明一下这次的主要关注点: 1. 文本可以满足多种场景下的短链接生成需求 2. 文本可以满足多重序列号机制 3. 文本可以满足多种短链接生成方式 4. 自由&可扩展性--秉承一贯的设计原则,觉得框架实现的好就用,觉得不满足就替换之 #主要接口说明 ##字符串生成接口 ``` package org.tinygroup.shorter; /** * 随机字符串发生器 * Created by luoguo on 2017/3/24. */ public interface StringGenerator { String generate(String url); void setLength(int length); } ``` setLength用于指定生成的长度,generate用于生成对应长度的短链接字符串。 上片文章里面被人喷说生成逻辑不够好,这次好了,随便你写你喜欢的。 当然,为了满足懒人,也有一个默认的实现,大致就是把上次的算法挪下来了 ``` /** * 随机字符串发生器的默认实现类 *

* Created by luoguo on 2017/3/24. */ public class StringGeneratorRandom implements StringGenerator { public static char[] VALID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".toCharArray(); private static Random random = new Random(System.currentTimeMillis()); private int length = 4; public StringGeneratorRandom() { } public StringGeneratorRandom(int length) { setLength(length); } public int getLength() { return length; } public void setLength(int length) { this.length = length; } String generate(int seed) { char[] sortUrl = new char[length]; for (int i = 0; i < length; i++) { sortUrl[i] = VALID_CHARS[seed % VALID_CHARS.length]; seed = random.nextInt(Integer.MAX_VALUE) % VALID_CHARS.length; } return new String(sortUrl); } /** * 这里的实现不考虑url,直接生成随机字符串即可,这个算法如果容量比较大的时候,性能会变低,因此要根据使用情况选择合适的长度 * * @param url * @return */ public String generate(String url) { String shortUrl; shortUrl = generate(random.nextInt(Integer.MAX_VALUE)); return shortUrl; } } ``` 算法非常简单,就不详加说明了。 ##短地址发生器接口 ``` /** * *

* Created by luoguo on 2017/3/24. */ public interface UrlShorterGenerator { /** * 产生一个短链接对象 * * @param url * @return */ T generate(String url); } ``` 这里只有一个方法,就是根据长URL来生成一个短地址。有的同学可能要问,短地址不是只有一个字符串么,为什么这里居然有个T,它还是继承了ShorterGetter接口,ShorterGetter接口又是什么鬼? 这就要看需求里面的一句话了『可以满足多种场景下的短链接生成需求』,因为实际应用场景里不仅仅是只生成一个串,还有要生成带密码的,访问次数限制的,可用时长限制的等等方式。 因此这里就设计了一个接口ShorterGetter ``` /** * 用来获取短地址 * Created by luoguo on 2017/3/24. */ public interface ShorterGetter { String getShorter(); } ``` 它只要求返回一个字符串的短链接,至于其他有什么东西,就不管了。 多种短地址使用场景支持 在工程里,我有5种方式 ###仅短地址: ``` /** * 返回短码和密码 * Created by luoguo on 2017/3/24. */ public class ShorterString implements ShorterGetter { private String shorter; public ShorterString() { } public ShorterString(String shorter) { setShorter(shorter); } public String getShorter() { return shorter; } public void setShorter(String shorter) { this.shorter = shorter; } } ``` ###带密码的短链接 ``` /** * 存放短地址和密码 * Created by luoguo on 2017/3/24. */ public class ShorterWithPassword implements ShorterGetter { private String shorter; private String password; public ShorterWithPassword() { } public ShorterWithPassword(String shorter, String password) { setShorter(shorter); setPassword(password); } public String getShorter() { return shorter; } public void setShorter(String shorter) { this.shorter = shorter; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } ``` ###带可用时间的短链接 ``` /** * 存放短地址和超时时间 * Created by luoguo on 2017/3/24. */ public class ShorterWithPeriod implements ShorterGetter { private String shorter; private long period; public ShorterWithPeriod() { } public ShorterWithPeriod(String shorter, long period) { setShorter(shorter); setPeriod(period); } public String getShorter() { return shorter; } public void setShorter(String shorter) { this.shorter = shorter; } public long getPeriod() { return period; } public void setPeriod(long period) { this.period = period; } } ``` 带超时时间和使用次数的短链接,时间到了或次数用完了都不可以再被使用 ``` /** * 用来存储短地址,超时时间和访问次数 * Created by luoguo on 2017/3/24. */ public class ShorterWithPeriodAndTimes implements ShorterGetter { private String shorter; private long period; private long times; public ShorterWithPeriodAndTimes() { } public ShorterWithPeriodAndTimes(String shorter, long period, long times) { setShorter(shorter); setTimes(times); setPeriod(period); } public long getTimes() { return times; } public void setTimes(long times) { this.times = times; } public String getShorter() { return shorter; } public void setShorter(String shorter) { this.shorter = shorter; } public long getPeriod() { return period; } public void setPeriod(long period) { this.period = period; } } ``` 当然,只要你有需要,也可以增加其他场景的实现类 ##短地址存储接口 当然,生成了短地址总要落地的,否则后面没有办法使用。 ``` /** * 用来存储字符串短地址 * Created by luoguo on 2017/3/24. */ public interface ShorterStorage { String get(String shorter); void clean(String url); void cleanShorter(String shorter); void save(String url, T shorter); void clean(); } ``` 上面定义了获取、保存、清理等相关接口方法。 同样的,我也提供了一个用于测设和验证的内存存储方法 ``` /** * Created by luoguo on 2017/3/24. */ public class ShorterStorageMemory implements ShorterStorage { /** * 存储shorter,url */ Map shorterMap = new ConcurrentHashMap(); /** * 存储url,shorter */ Map urlMap = new ConcurrentHashMap(); /** * 存储shorter.shorter,shorter */ Map shorterUrlMap = new ConcurrentHashMap(); public String get(String shorterKey) { ShorterGetter shorter = shorterUrlMap.get(shorterKey); if (shorter != null) { return shorterMap.get(shorter); } return null; } public void clean(String url) { ShorterGetter shorter = urlMap.get(url); if (shorter != null) { urlMap.remove(url); shorterMap.remove(shorter); shorterUrlMap.remove(shorter.getShorter()); } } public void cleanShorter(String shorterKey) { ShorterGetter shorter = shorterUrlMap.get(shorterKey); if (shorter != null) { urlMap.remove(shorterMap.get(shorter)); shorterMap.remove(shorter); shorterUrlMap.remove(shorter.getShorter()); } } public void save(String url, T shorter) { urlMap.put(url, shorter); shorterMap.put(shorter, url); shorterUrlMap.put(shorter.getShorter(), shorter); } public void clean() { shorterMap.clear(); shorterUrlMap.clear(); urlMap.clear(); } } ``` 需要的,你也可以实现自己的数据库类型的、REDIS,或者你想要的其他方式。 至此,已经把主要的问题都已经解决,然后再看看发生器的实现类 注意:这里没有对限制条件进行控制,实际实现要进行控制,比如get的时候检查是否有可用性,如果没有可用性,就返回null。 发生器的实现类 ##简单短链接 ``` /** * 用于生成指定长度的串 * Created by luoguo on 2017/3/24. */ public class UrlShorterGeneratorSimple implements UrlShorterGenerator { private StringGenerator generator; private ShorterStorage shorterStorage; public ShorterStorage getShorterStorage() { return shorterStorage; } public void setShorterStorage(ShorterStorage shorterStorage) { this.shorterStorage = shorterStorage; } public StringGenerator getGenerator() { return generator; } public void setGenerator(StringGenerator generator) { this.generator = generator; } public ShorterString generate(String url) { String shorter = generator.generate(url); while (shorterStorage.get(shorter) != null) { shorter = generator.generate(url); } ShorterString newShorter = new ShorterString(shorter); shorterStorage.save(url, newShorter); return newShorter; } } ``` ##带密码的 ``` /** * 用于生成指定长度的串 * Created by luoguo on 2017/3/24. */ public class UrlShorterGeneratorWithPassword implements UrlShorterGenerator { private StringGenerator shorterGenerator; private StringGenerator passwordGenerator; private ShorterStorage shorterStorage; public StringGenerator getShorterGenerator() { return shorterGenerator; } public void setShorterGenerator(StringGenerator shorterGenerator) { this.shorterGenerator = shorterGenerator; } public StringGenerator getPasswordGenerator() { return passwordGenerator; } public void setPasswordGenerator(StringGenerator passwordGenerator) { this.passwordGenerator = passwordGenerator; } public ShorterStorage getShorterStorage() { return shorterStorage; } public void setShorterStorage(ShorterStorage shorterStorage) { this.shorterStorage = shorterStorage; } public ShorterWithPassword generate(String url) { String shorter = shorterGenerator.generate(url); while (shorterStorage.get(shorter) != null) { shorter = shorterGenerator.generate(url); } ShorterWithPassword shorterWithPassword = new ShorterWithPassword(shorter, passwordGenerator.generate(url)); shorterStorage.save(url, shorterWithPassword); return shorterWithPassword; } } ``` ##带使用时长的 ``` /** * 用于生成指定长度的串,限制访问次数 * Created by luoguo on 2017/3/24. */ public class UrlShorterGeneratorLimitPeriod implements UrlShorterGenerator { private StringGenerator generator; private ShorterStorage shorterStorage; /** * 有效时长,单位秒 */ private long period; public StringGenerator getGenerator() { return generator; } public void setGenerator(StringGenerator generator) { this.generator = generator; } public ShorterStorage getShorterStorage() { return shorterStorage; } public void setShorterStorage(ShorterStorage shorterStorage) { this.shorterStorage = shorterStorage; } public long getPeriod() { return period; } public void setPeriod(long period) { this.period = period; } public ShorterWithPeriod generate(String url) { String shorter = generator.generate(url); while (shorterStorage.get(shorter) != null) { shorter = generator.generate(url); } ShorterWithPeriod shorterWithPeriod = new ShorterWithPeriod(shorter, period); shorterStorage.save(url, shorterWithPeriod); return shorterWithPeriod; } } ``` ##带使用次数的 ``` /** * 用于生成指定长度的串,限制访问次数 * Created by luoguo on 2017/3/24. */ public class UrlShorterGeneratorLimitTimes implements UrlShorterGenerator { private StringGenerator generator; private ShorterStorage shorterStorage; /** * 有效时长,单位秒 */ private long times; public StringGenerator getGenerator() { return generator; } public void setGenerator(StringGenerator generator) { this.generator = generator; } public ShorterStorage getShorterStorage() { return shorterStorage; } public void setShorterStorage(ShorterStorage shorterStorage) { this.shorterStorage = shorterStorage; } public long getTimes() { return times; } public void setTimes(long times) { this.times = times; } public ShorterWithTimes generate(String url) { String shorter = generator.generate(url); while (shorterStorage.get(shorter) != null) { shorter = generator.generate(url); } ShorterWithTimes shorterWithPeriod = new ShorterWithTimes(shorter, times); shorterStorage.save(url, shorterWithPeriod); return shorterWithPeriod; } } ``` ##带使用次数和时间限制的 ``` /** * 用于生成指定长度的串,限制访问次数 * Created by luoguo on 2017/3/24. */ public class UrlShorterGeneratorLimitPeriodAndTimes implements UrlShorterGenerator { private StringGenerator generator; private ShorterStorage shorterStorage; /** * 有效时长,单位秒 */ private long period; /** * 最多使用次数 */ private long times; public StringGenerator getGenerator() { return generator; } public void setGenerator(StringGenerator generator) { this.generator = generator; } public ShorterStorage getShorterStorage() { return shorterStorage; } public void setShorterStorage(ShorterStorage shorterStorage) { this.shorterStorage = shorterStorage; } public long getTimes() { return times; } public void setTimes(long times) { this.times = times; } public long getPeriod() { return period; } public void setPeriod(long period) { this.period = period; } public ShorterWithPeriodAndTimes generate(String url) { String shorter = generator.generate(url); while (shorterStorage.get(shorter) != null) { shorter = generator.generate(url); } ShorterWithPeriodAndTimes shorterWithPeriodAndTimes = new ShorterWithPeriodAndTimes(shorter, period, times); shorterStorage.save(url, shorterWithPeriodAndTimes); return shorterWithPeriodAndTimes; } } ``` 至此,主体都已经完成,看看示例吧 #示例 ##固定长度的短地址生成 ``` /** * Created by luoguo on 2017/3/24. */ public class UrlShorterGeneratorSimpleTest { @Test public void generate() throws Exception { for (int i = 4; i <= 8; i++) { UrlShorterGeneratorSimple simple = new UrlShorterGeneratorSimple(); simple.setGenerator(new StringGeneratorRandom(i)); simple.setShorterStorage(new ShorterStorageMemory()); for (int j = 0; j < 5; j++) { String shorter = simple.generate("").getShorter(); assert shorter.length()==i; System.out.println(shorter); } } } } ``` 生成的结果 ``` zoHU PcYv 0Lde rsK2 zyTo 3sivy jhZa5 02ir2 Pueqo L4TlI wXAYQB 2caquM rZ8pCn FhocFi QZHroK bxTPWCW 38gUCX3 2Ma4fQr KCgvofA NZNMK3Y Jj7xkUjY FP3XObRf YBrrI8C8 I91HvRNs VITEfp0T ``` ##带密码的 ``` /** * Created by luoguo on 2017/3/25. */ public class UrlShorterGeneratorWithPasswordTest { @Test public void generate() throws Exception { for (int i = 4; i <= 8; i++) { UrlShorterGeneratorWithPassword withPassword = new UrlShorterGeneratorWithPassword(); withPassword.setShorterGenerator(new StringGeneratorRandom(i)); withPassword.setPasswordGenerator(new StringGeneratorRandom(4)); withPassword.setShorterStorage(new ShorterStorageMemory()); for (int j = 0; j < 5; j++) { ShorterWithPassword shorter = withPassword.generate(""); assert shorter.getShorter().length()==i; System.out.printf("%s %s\n",shorter.getShorter(),shorter.getPassword()); } } } } ``` 运行结果 ``` 0yET AYOf 37w1 MBjA SDMg B72n BdTv KAwd KQ1w iwiP mZAVV u8Zx rdUlH 5a7T uZQ5i j38x PUfY0 kfH3 MG3iW bkHO Ea4TJr Nt8v 2fycK1 6eF3 Q6arED rEID wc9yf1 kcGr uGs5uu vKhA upsmJXt 1IIl 6feAOFV Afqm j0qPXCG R9VN 2We0RqM 9722 SdgG0Yy tS6e ZDUyOeeg kiTh 3RGlJuSp OQfl EswJLPlk Jqjx IgeQMtU7 91GP 9LWNni4z xPw8 ``` 呵呵,效果和我想象的一样。 总结 这一版本相对于上一版有了长足进步,设计更合理,功能更强大,自由度也更高。相信会给这方面有需求的小伙伴有一定触发。 由于编写时间太短,里面BUG和不足或设计缺陷一定难以避免,欢迎小伙伴门提交PR,共同完善。 git地址https://git.oschina.net/tinyframework/urlshorter.git 更多精彩博客,请移步前往悠然博客空间,对悠然动态感兴趣的同学们请关注。 认为有用或有价值,也欢迎打赏哦~~~