From 3add1a1098f3c23e356662a9ccb05451f17cf463 Mon Sep 17 00:00:00 2001 From: ZhuoQinghui <1302344380@qq.com> Date: Fri, 29 Jul 2022 16:22:40 +0800 Subject: [PATCH] ShortUrl.java --- .gitignore | 35 +++ pom.xml | 22 ++ zy-shorturl-core/pom.xml | 48 ++++ .../main/java/cn/shorturl/core/ShortUrl.java | 77 ++++++ .../java/cn/shorturl/core/ShortUrlConfig.java | 27 +++ .../java/cn/shorturl/core/enums/HashType.java | 48 ++++ .../cn/shorturl/core/enums/StoreType.java | 34 +++ .../java/cn/shorturl/core/util/Base62.java | 223 ++++++++++++++++++ .../java/cn/shorturl/core/ShortUrlTest.java | 26 ++ zy-shorturl-spring-boot-starter/pom.xml | 20 ++ 10 files changed, 560 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 zy-shorturl-core/pom.xml create mode 100644 zy-shorturl-core/src/main/java/cn/shorturl/core/ShortUrl.java create mode 100644 zy-shorturl-core/src/main/java/cn/shorturl/core/ShortUrlConfig.java create mode 100644 zy-shorturl-core/src/main/java/cn/shorturl/core/enums/HashType.java create mode 100644 zy-shorturl-core/src/main/java/cn/shorturl/core/enums/StoreType.java create mode 100644 zy-shorturl-core/src/main/java/cn/shorturl/core/util/Base62.java create mode 100644 zy-shorturl-core/src/test/java/cn/shorturl/core/ShortUrlTest.java create mode 100644 zy-shorturl-spring-boot-starter/pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b425f09 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7cf67ac --- /dev/null +++ b/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + cn.zzzykj + zy-short-url + pom + 1.0-SNAPSHOT + + zy-shorturl-core + zy-shorturl-spring-boot-starter + + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/zy-shorturl-core/pom.xml b/zy-shorturl-core/pom.xml new file mode 100644 index 0000000..e5c3b0b --- /dev/null +++ b/zy-shorturl-core/pom.xml @@ -0,0 +1,48 @@ + + + + zy-short-url + cn.zzzykj + 1.0-SNAPSHOT + + 4.0.0 + + zy-short-url-core + + + 8 + 8 + UTF-8 + + + + + + com.google.guava + guava + 31.1-jre + true + + + + + org.projectlombok + lombok + 1.18.24 + compile + true + + + + + junit + junit + 4.13.2 + test + true + + + + \ No newline at end of file diff --git a/zy-shorturl-core/src/main/java/cn/shorturl/core/ShortUrl.java b/zy-shorturl-core/src/main/java/cn/shorturl/core/ShortUrl.java new file mode 100644 index 0000000..6d23866 --- /dev/null +++ b/zy-shorturl-core/src/main/java/cn/shorturl/core/ShortUrl.java @@ -0,0 +1,77 @@ +package cn.shorturl.core; + +import cn.shorturl.core.enums.HashType; +import cn.shorturl.core.util.Base62; +import com.google.common.hash.HashCode; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.nio.charset.StandardCharsets; + +/** + * @author Lenovo + */ +@Data +@Accessors(chain = true) +public class ShortUrl { + + private ShortUrlConfig config; + + private final HashFunction hashFunction; + + private static final Base62 base62 = Base62.createInstance(); + + public ShortUrl() { + config = new ShortUrlConfig(); + hashFunction = getHashFunction(config); + } + + public ShortUrl(ShortUrlConfig config) { + this.config = config; + hashFunction = getHashFunction(config); + } + + public String gen(String url) { + HashCode hashCode = hashFunction.hashString(url, StandardCharsets.UTF_8); + byte[] bytes = hashCode.asBytes(); + return new String(base62.encode(bytes)); + } + + private static HashFunction getHashFunction(ShortUrlConfig config) { + return getHashFunction(config.getHashType(), config.getHashKey()); + } + + private static HashFunction getHashFunction(HashType type, String key) { + if (type == null) { + return Hashing.murmur3_32_fixed(); + } + if (key == null) { + key = ShortUrl.class.getName(); + } + byte[] keyBytes = key.getBytes(); + switch (type) { + case MD5: return Hashing.md5(); + case GOOD_FAST_HASH: return Hashing.goodFastHash(32); + case SHA1: return Hashing.sha1(); + case SHA256: return Hashing.sha256(); + case SHA384: return Hashing.sha384(); + case SHA512: return Hashing.sha512(); + case HMAC_MD5: return Hashing.hmacMd5(keyBytes); + case HMAC_SHA1: return Hashing.hmacSha1(keyBytes); + case HMAC_SHA256: return Hashing.hmacSha256(keyBytes); + case HMAC_SHA512: return Hashing.hmacSha512(keyBytes); + case CRC32_C: return Hashing.crc32c(); + case ADLER32: return Hashing.adler32(); + case FARM_HASH_FINGERPRINT64: return Hashing.farmHashFingerprint64(); + case FINGERPRINT2011: return Hashing.fingerprint2011(); + case MURMUR3_32: return Hashing.murmur3_32(); + case MURMUR3_128: return Hashing.murmur3_128(); + case SIP_HASH24: return Hashing.sipHash24(); + case MURMUR3_32_FIXED: + default: return Hashing.murmur3_32_fixed(); + } + } + +} diff --git a/zy-shorturl-core/src/main/java/cn/shorturl/core/ShortUrlConfig.java b/zy-shorturl-core/src/main/java/cn/shorturl/core/ShortUrlConfig.java new file mode 100644 index 0000000..dd21e9e --- /dev/null +++ b/zy-shorturl-core/src/main/java/cn/shorturl/core/ShortUrlConfig.java @@ -0,0 +1,27 @@ +package cn.shorturl.core; + +import cn.shorturl.core.enums.HashType; +import cn.shorturl.core.enums.StoreType; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @author Lenovo + */ +@Data +@Accessors(chain = true) +public class ShortUrlConfig { + + /** + * Hash方式 + */ + private HashType hashType = HashType.MURMUR3_32_FIXED; + + private String hashKey = ShortUrlConfig.class.getName(); + + /** + * 缓存类型 + */ + private StoreType storeType = StoreType.MEMORY; + +} diff --git a/zy-shorturl-core/src/main/java/cn/shorturl/core/enums/HashType.java b/zy-shorturl-core/src/main/java/cn/shorturl/core/enums/HashType.java new file mode 100644 index 0000000..273db77 --- /dev/null +++ b/zy-shorturl-core/src/main/java/cn/shorturl/core/enums/HashType.java @@ -0,0 +1,48 @@ +package cn.shorturl.core.enums; + +/** + * @author Lenovo + */ + +public enum HashType { + + /** + * MD5 + */ + MD5, + + GOOD_FAST_HASH, + + SHA1, + + SHA256, + + SHA384, + + SHA512, + + HMAC_MD5, + + HMAC_SHA1, + + HMAC_SHA256, + + HMAC_SHA512, + + CRC32_C, + + ADLER32, + + FARM_HASH_FINGERPRINT64, + + FINGERPRINT2011, + + MURMUR3_32, + + MURMUR3_32_FIXED, + + MURMUR3_128, + + SIP_HASH24; + +} diff --git a/zy-shorturl-core/src/main/java/cn/shorturl/core/enums/StoreType.java b/zy-shorturl-core/src/main/java/cn/shorturl/core/enums/StoreType.java new file mode 100644 index 0000000..9c5cb35 --- /dev/null +++ b/zy-shorturl-core/src/main/java/cn/shorturl/core/enums/StoreType.java @@ -0,0 +1,34 @@ +package cn.shorturl.core.enums; + +/** + * data store type + * @author Lenovo + */ +public enum StoreType { + + /** + * mysql, sqlserver, oracle 等支持jdbc的数据库 + */ + JDBC, + + /** + * redis + */ + REDIS, + + /** + * mongodb + */ + MONGODB, + + /** + * Hazelcast + */ + HAZELCAST, + + /** + * 内存 + */ + MEMORY, + +} diff --git a/zy-shorturl-core/src/main/java/cn/shorturl/core/util/Base62.java b/zy-shorturl-core/src/main/java/cn/shorturl/core/util/Base62.java new file mode 100644 index 0000000..0edf669 --- /dev/null +++ b/zy-shorturl-core/src/main/java/cn/shorturl/core/util/Base62.java @@ -0,0 +1,223 @@ +package cn.shorturl.core.util; + +import java.io.ByteArrayOutputStream; + +/** + * Base62编码解码实现,常用于短URL + * From https://github.com/seruco/base62 + * A Base62 encoder/decoder. + * + * @author Sebastian Ruhleder, sebastian@seruco.io + */ +public class Base62 { + + private static final int STANDARD_BASE = 256; + + private static final int TARGET_BASE = 62; + + private final byte[] alphabet; + + private byte[] lookup; + + private Base62(final byte[] alphabet) { + this.alphabet = alphabet; + createLookupTable(); + } + + /** + * Creates a {@link Base62} instance. Defaults to the GMP-style character set. + * + * @return a {@link Base62} instance. + */ + public static Base62 createInstance() { + return createInstanceWithGmpCharacterSet(); + } + + /** + * Creates a {@link Base62} instance using the GMP-style character set. + * + * @return a {@link Base62} instance. + */ + public static Base62 createInstanceWithGmpCharacterSet() { + return new Base62(CharacterSets.GMP); + } + + /** + * Creates a {@link Base62} instance using the inverted character set. + * + * @return a {@link Base62} instance. + */ + public static Base62 createInstanceWithInvertedCharacterSet() { + return new Base62(CharacterSets.INVERTED); + } + + /** + * Encodes a sequence of bytes in Base62 encoding. + * + * @param message a byte sequence. + * @return a sequence of Base62-encoded bytes. + */ + public byte[] encode(final byte[] message) { + final byte[] indices = convert(message, STANDARD_BASE, TARGET_BASE); + + return translate(indices, alphabet); + } + + /** + * Decodes a sequence of Base62-encoded bytes. + * + * @param encoded a sequence of Base62-encoded bytes. + * @return a byte sequence. + * @throws IllegalArgumentException when {@code encoded} is not encoded over the Base62 alphabet. + */ + public byte[] decode(final byte[] encoded) { + if (!isBase62Encoding(encoded)) { + throw new IllegalArgumentException("Input is not encoded correctly"); + } + + final byte[] prepared = translate(encoded, lookup); + + return convert(prepared, TARGET_BASE, STANDARD_BASE); + } + + /** + * Checks whether a sequence of bytes is encoded over a Base62 alphabet. + * + * @param bytes a sequence of bytes. + * @return {@code true} when the bytes are encoded over a Base62 alphabet, {@code false} otherwise. + */ + public boolean isBase62Encoding(final byte[] bytes) { + if (bytes == null) { + return false; + } + + for (final byte e : bytes) { + if ('0' > e || '9' < e) { + if ('a' > e || 'z' < e) { + if ('A' > e || 'Z' < e) { + return false; + } + } + } + } + + return true; + } + + /** + * Uses the elements of a byte array as indices to a dictionary and returns the corresponding values + * in form of a byte array. + */ + private byte[] translate(final byte[] indices, final byte[] dictionary) { + final byte[] translation = new byte[indices.length]; + + for (int i = 0; i < indices.length; i++) { + translation[i] = dictionary[indices[i]]; + } + + return translation; + } + + /** + * Converts a byte array from a source base to a target base using the alphabet. + */ + private byte[] convert(final byte[] message, final int sourceBase, final int targetBase) { + /** + * This algorithm is inspired by: http://codegolf.stackexchange.com/a/21672 + */ + + final int estimatedLength = estimateOutputLength(message.length, sourceBase, targetBase); + + final ByteArrayOutputStream out = new ByteArrayOutputStream(estimatedLength); + + byte[] source = message; + + while (source.length > 0) { + final ByteArrayOutputStream quotient = new ByteArrayOutputStream(source.length); + + int remainder = 0; + + for (int i = 0; i < source.length; i++) { + final int accumulator = (source[i] & 0xFF) + remainder * sourceBase; + final int digit = (accumulator - (accumulator % targetBase)) / targetBase; + + remainder = accumulator % targetBase; + + if (quotient.size() > 0 || digit > 0) { + quotient.write(digit); + } + } + + out.write(remainder); + + source = quotient.toByteArray(); + } + + // pad output with zeroes corresponding to the number of leading zeroes in the message + for (int i = 0; i < message.length - 1 && message[i] == 0; i++) { + out.write(0); + } + + return reverse(out.toByteArray()); + } + + /** + * Estimates the length of the output in bytes. + */ + private int estimateOutputLength(int inputLength, int sourceBase, int targetBase) { + return (int) Math.ceil((Math.log(sourceBase) / Math.log(targetBase)) * inputLength); + } + + /** + * Reverses a byte array. + */ + private byte[] reverse(final byte[] arr) { + final int length = arr.length; + + final byte[] reversed = new byte[length]; + + for (int i = 0; i < length; i++) { + reversed[length - i - 1] = arr[i]; + } + + return reversed; + } + + /** + * Creates the lookup table from character to index of character in character set. + */ + private void createLookupTable() { + lookup = new byte[256]; + + for (int i = 0; i < alphabet.length; i++) { + lookup[alphabet[i]] = (byte) (i & 0xFF); + } + } + + private static class CharacterSets { + + private static final byte[] GMP = { + (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', + (byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', + (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', + (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', + (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', + (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', + (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', + (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z' + }; + + private static final byte[] INVERTED = { + (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', + (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', + (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', + (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', + (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', + (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', + (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', + (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z' + }; + + } + +} diff --git a/zy-shorturl-core/src/test/java/cn/shorturl/core/ShortUrlTest.java b/zy-shorturl-core/src/test/java/cn/shorturl/core/ShortUrlTest.java new file mode 100644 index 0000000..967e1c1 --- /dev/null +++ b/zy-shorturl-core/src/test/java/cn/shorturl/core/ShortUrlTest.java @@ -0,0 +1,26 @@ +package cn.shorturl.core; + +import cn.shorturl.core.enums.HashType; +import org.junit.Before; +import org.junit.Test; + +public class ShortUrlTest { + + ShortUrlConfig config; + + ShortUrl shortUrl; + + @Before + public void init() { + config = new ShortUrlConfig().setHashType(HashType.MURMUR3_32_FIXED); + shortUrl = new ShortUrl(config); + } + + @Test + public void testGen() { + String url = "http://www.baidu.com//?asdasdasdaas=das==wqe===gfa=sd=asf=g=eq=w===a=sd=as=fg=w=d=as=d=as=f=w="; + String gen = shortUrl.gen(url); + System.out.println(gen); + } + +} \ No newline at end of file diff --git a/zy-shorturl-spring-boot-starter/pom.xml b/zy-shorturl-spring-boot-starter/pom.xml new file mode 100644 index 0000000..90cf7e5 --- /dev/null +++ b/zy-shorturl-spring-boot-starter/pom.xml @@ -0,0 +1,20 @@ + + + + zy-short-url + cn.zzzykj + 1.0-SNAPSHOT + + 4.0.0 + + zy-short-url-spring-boot-starter + + + 8 + 8 + UTF-8 + + + \ No newline at end of file