From 6673306bb83440c6845a8208e97ec2f8eef0eba3 Mon Sep 17 00:00:00 2001 From: ehlxr Date: Wed, 4 Apr 2018 17:00:29 +0800 Subject: [PATCH] reactive --- java-utils.iml | 4 +- pom.xml | 5 + resources/idea/go-settings.jar | Bin 39632 -> 41758 bytes resources/idea/mac-settings.jar | Bin 52297 -> 52405 bytes .../java/me/ehlxr/PrintMatrixClockWisely.java | 74 ++++++++ src/main/java/me/ehlxr/dfd.java | 6 + .../java/me/ehlxr/reactive/HelloRxJava.java | 30 +-- src/main/java/me/ehlxr/reactive/TestRx.java | 38 ++-- src/main/java/me/ehlxr/reactive/TestRx01.java | 54 +++++- .../ehlxr/thread/ThreadPoolExecutorTest.java | 2 +- src/main/java/me/ehlxr/token/Base62.java | 69 +++++++ .../java/me/ehlxr/token/TokenCommonUtil.java | 132 +++++++++++++ .../java/me/ehlxr/token/TokenServerUtil.java | 173 ++++++++++++++++++ .../me/ehlxr/token/TokenVersionConfig.java | 47 +++++ .../java/me/ehlxr/token/TokenVersionEnum.java | 40 ++++ .../me/ehlxr/token/TokenVersionFactory.java | 63 +++++++ src/main/resources/token_version.properties | 18 ++ 17 files changed, 722 insertions(+), 33 deletions(-) create mode 100644 src/main/java/me/ehlxr/PrintMatrixClockWisely.java create mode 100644 src/main/java/me/ehlxr/token/Base62.java create mode 100644 src/main/java/me/ehlxr/token/TokenCommonUtil.java create mode 100644 src/main/java/me/ehlxr/token/TokenServerUtil.java create mode 100644 src/main/java/me/ehlxr/token/TokenVersionConfig.java create mode 100644 src/main/java/me/ehlxr/token/TokenVersionEnum.java create mode 100644 src/main/java/me/ehlxr/token/TokenVersionFactory.java create mode 100644 src/main/resources/token_version.properties diff --git a/java-utils.iml b/java-utils.iml index 482b5bc..c94a0f6 100644 --- a/java-utils.iml +++ b/java-utils.iml @@ -15,7 +15,7 @@ - + @@ -105,5 +105,7 @@ + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index d71fe49..afb08a2 100644 --- a/pom.xml +++ b/pom.xml @@ -258,6 +258,11 @@ guava 22.0 + + org.jodd + jodd-props + 3.6.1 + java-utils diff --git a/resources/idea/go-settings.jar b/resources/idea/go-settings.jar index 3332472e46199647a4aafbdb176a53047ca67c44..1ac9ecc2c88e1240a396f4c040d274b5cdf80ec6 100644 GIT binary patch delta 7881 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 H$khJ>_T=IS diff --git a/resources/idea/mac-settings.jar b/resources/idea/mac-settings.jar index 349a750efe691d8c04c5f69f82920c1fe0e3530c..d0e174fb4a41be9966d9edab6e3007d5a366c970 100644 GIT binary patch delta 11279 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 (start,endX) + * 当前矩阵,最后 1 列 坐标 (start+1,rows-1-start) => (start+1,endY) + * 当前矩阵,最后 1 行 坐标 (start,columns-1-start+1) => (start,endX-1) + * 当前矩阵,第 1 行 坐标 (start+1,columns-1+1) => (start+1,endY-1) + * + * @author WangSai + */ + public static void main(String[] args) { + // TODO Auto-generated method stub + // 初始化矩阵 arr[][] + int[][] arr = new int[5][5]; + for (int i = 0; i < arr.length; i++) { + for (int j = 0; j < arr[i].length; j++) { + arr[i][j] = i * arr[i].length + j; + System.out.print(arr[i][j] + " " + '\t'); + } + System.out.println(); + } + System.out.println("顺时针打印矩阵:"); + // 顺时针打印矩阵 arr[][] + printMatrixWisely(arr); + } + + // 循环打印 + private static void printMatrixWisely(int[][] arr) { + if (arr == null || arr.length < 1 || arr[0].length < 1) + return; + int start = 0; + int rows = arr.length; + int columns = arr[0].length; + while (2 * start < columns && 2 * start < rows) { + printMatrix(arr, rows, columns, start); + start++; + } + } + + // 打印一圈 + private static void printMatrix(int[][] arr, int rows, int columns, int start) { + int endX = columns - 1 - start; // 最后一列的列号 + int endY = rows - 1 - start; // 最后一行的行号 + // 打印该圈第一行 + for (int i = start; i <= endX; i++) + System.out.print(arr[start][i] + " "); + // 打印该圈最后一列 (至少是两行) + if (start < endY) + for (int i = start + 1; i <= endY; i++) + System.out.print(arr[i][endX] + " "); + // 打印该圈最后一行 (至少是两行两列) + if ((start < endX) && (start < endY)) + for (int i = endX - 1; i >= start; i--) + System.out.print(arr[endY][i] + " "); + // 打印该圈的第一列 (至少是三行两列) + if ((start < endX) && (start < endY - 1)) + for (int i = endY - 1; i >= start + 1; i--) + System.out.print(arr[i][start] + " "); + } +} diff --git a/src/main/java/me/ehlxr/dfd.java b/src/main/java/me/ehlxr/dfd.java index 470b66a..c8e1ad6 100644 --- a/src/main/java/me/ehlxr/dfd.java +++ b/src/main/java/me/ehlxr/dfd.java @@ -1,10 +1,16 @@ package me.ehlxr; +import com.google.common.collect.Maps; + /** * Created by lixiangrong on 2016/12/23. */ public class dfd { + public static void main(String[] args) { + var map = Maps.newHashMap(); + map.put("d",1); + System.out.println(map); } public void printCircle(int[][] matrix, int startX, int startY, int endX, int endY) { // only one column left diff --git a/src/main/java/me/ehlxr/reactive/HelloRxJava.java b/src/main/java/me/ehlxr/reactive/HelloRxJava.java index 5f5da84..ba5aede 100644 --- a/src/main/java/me/ehlxr/reactive/HelloRxJava.java +++ b/src/main/java/me/ehlxr/reactive/HelloRxJava.java @@ -13,22 +13,24 @@ public class HelloRxJava { } private static void hello(String... names) { - Observable.from(names).subscribe(new Subscriber() { - @Override - public void onCompleted() { - System.out.println("Completed!"); - } + Observable.from(names).subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("Completed!"); + } - @Override - public void onError(Throwable throwable) { - throwable.printStackTrace(); - } + @Override + public void onError(Throwable throwable) { + throwable.printStackTrace(); + } - @Override - public void onNext(String strings) { - System.out.println("same hello " + strings); - } + @Override + public void onNext(String strings) { + System.out.println("say hello " + strings); + } - }); + }); + + // Observable.from(names).subscribe(name -> System.out.println("say hello " + name), Throwable::printStackTrace, () -> System.out.println("Completed!")); } } diff --git a/src/main/java/me/ehlxr/reactive/TestRx.java b/src/main/java/me/ehlxr/reactive/TestRx.java index 2e366a7..d88a994 100644 --- a/src/main/java/me/ehlxr/reactive/TestRx.java +++ b/src/main/java/me/ehlxr/reactive/TestRx.java @@ -19,29 +19,42 @@ public class TestRx { // Tasks oa -> oc, both in the same thread 1. Observable oa = Observable.just("oa").observeOn(Schedulers.io()).flatMap( - soa -> Observable.fromCallable(new TimeConsumingService("fa", 1000, new String[]{})) + soa -> { + System.out.println("oa Thread: " + Thread.currentThread().getName()); + return Observable.fromCallable(new TimeConsumingService("fa", 2000, new String[]{soa})); + } ); Observable oc = oa.flatMap( res -> { - System.out.println(res); - System.out.println("Executed At: " + (System.currentTimeMillis() - startTime) + "ms"); - return Observable.fromCallable( - new TimeConsumingService("fc", 2000, new String[]{res})); + System.out.println("oc Thread: " + Thread.currentThread().getName()); + // System.out.println(res); + // System.out.println("Executed At: " + (System.currentTimeMillis() - startTime) + "ms"); + return Observable.fromCallable(new TimeConsumingService("fc", 1000, new String[]{res})); }); // tasks ob -> (od,oe), ob, od, oe have special thread 2,3,4. Observable ob = Observable.just("ob").observeOn(Schedulers.io()).flatMap( - sob -> Observable.fromCallable(new TimeConsumingService("fb", 2000, new String[]{})) + sob -> { + System.out.println("ob Thread: " + Thread.currentThread().getName()); + return Observable.fromCallable(new TimeConsumingService("fb", 2000, new String[]{sob})); + } ); Observable od_oe = ob.flatMap( res -> { - System.out.println(res); - System.out.println("Executed At: " + (System.currentTimeMillis() - startTime) + "ms"); + System.out.println("od_oe Thread: " + Thread.currentThread().getName()); + // System.out.println(res); + // System.out.println("Executed At: " + (System.currentTimeMillis() - startTime) + "ms"); Observable od = Observable.just("od").observeOn(Schedulers.io()).flatMap( - sod -> Observable.fromCallable(new TimeConsumingService("fd", 1000, new String[]{res})) + sod -> { + System.out.println("od Thread: " + Thread.currentThread().getName()); + return Observable.fromCallable(new TimeConsumingService("fd", 1000, new String[]{sod})); + } ); Observable oe = Observable.just("oe").observeOn(Schedulers.io()).flatMap( - sod -> Observable.fromCallable(new TimeConsumingService("fe", 1000, new String[]{res})) + soe -> { + System.out.println("oe Thread: " + Thread.currentThread().getName()); + return Observable.fromCallable(new TimeConsumingService("fe", 1000, new String[]{soe})); + } ); return Observable.merge(od, oe); }); @@ -51,8 +64,9 @@ public class TestRx { // tasks join oc,(od_oe) and subscribe Observable.merge(oc, od_oe).toBlocking().subscribe( res -> { - System.out.println(res); - System.out.println("Executed At: " + (System.currentTimeMillis() - startTime) + "ms"); + System.out.println("ss Thread: " + Thread.currentThread().getName()); + // System.out.println(res); + // System.out.println("Executed At: " + (System.currentTimeMillis() - startTime) + "ms"); }); System.out.println("End executed: " + (System.currentTimeMillis() - startTime) + "ms"); diff --git a/src/main/java/me/ehlxr/reactive/TestRx01.java b/src/main/java/me/ehlxr/reactive/TestRx01.java index eb9defd..9ae61ac 100644 --- a/src/main/java/me/ehlxr/reactive/TestRx01.java +++ b/src/main/java/me/ehlxr/reactive/TestRx01.java @@ -2,22 +2,66 @@ package me.ehlxr.reactive; import rx.Observable; +import rx.Subscriber; + +import java.util.concurrent.CountDownLatch; /** * Created by lixiangrong on 2018/1/16. * 链式函数式处理 */ public class TestRx01 { - public static void main(String[] args) { - hello(1, 2, 3, 4, 5); + public static void main(String[] args) throws Exception { + // hello(1, 2, 3, 4, 5); + testRxJavaWithoutBlocking(10000); + + Observable.create((Observable.OnSubscribe) observer -> { + try { + if (!observer.isUnsubscribed()) { + for (int i = 1; i < 5; i++) { + observer.onNext(i); + } + observer.onCompleted(); + } + } catch (Exception e) { + observer.onError(e); + } + }).subscribe(new Subscriber() { + @Override + public void onNext(Integer item) { + System.out.println("Next: " + item); + } + + @Override + public void onError(Throwable error) { + System.err.println("Error: " + error.getMessage()); + } + + @Override + public void onCompleted() { + System.out.println("Sequence complete."); + } + }); } private static void hello(Integer... integers) { Observable workflow = Observable.from(integers) .filter(i -> (i < 10) && (i > 0)) - .map(i -> i * 2); - workflow.subscribe(i -> System.out.print(i + "! ")); - System.out.println(); + .map(i -> i * 2).reduce((x, y) -> x + y); workflow.subscribe(i -> System.out.print(i + "! ")); } + + private static void testRxJavaWithoutBlocking(int count) throws Exception { + CountDownLatch finishedLatch = new CountDownLatch(1); + long t = System.nanoTime(); + + Observable.range(0, count) + .map(i -> 200) + .subscribe(s -> { + }, Throwable::printStackTrace, finishedLatch::countDown); + + finishedLatch.await(); + t = (System.nanoTime() - t) / 1000000; //ms + System.out.println("RxJavaWithoutBlocking TPS: " + count * 1000 / t); + } } diff --git a/src/main/java/me/ehlxr/thread/ThreadPoolExecutorTest.java b/src/main/java/me/ehlxr/thread/ThreadPoolExecutorTest.java index 57bacab..2c208fc 100644 --- a/src/main/java/me/ehlxr/thread/ThreadPoolExecutorTest.java +++ b/src/main/java/me/ehlxr/thread/ThreadPoolExecutorTest.java @@ -19,7 +19,7 @@ public class ThreadPoolExecutorTest { cachedThreadPool.execute (new Runnable() { public void run() { //System.out.println(index); - System.out.println(Thread.currentThread().getName()); + System.out.println("Thread: "+Thread.currentThread().getName()); } }); } diff --git a/src/main/java/me/ehlxr/token/Base62.java b/src/main/java/me/ehlxr/token/Base62.java new file mode 100644 index 0000000..cb1cc17 --- /dev/null +++ b/src/main/java/me/ehlxr/token/Base62.java @@ -0,0 +1,69 @@ +package me.ehlxr.token; + + +import java.io.ByteArrayOutputStream; + +/** + * BASE62编码类 + */ +public class Base62 { + + private static char[] encodes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + .toCharArray(); + private static byte[] decodes = new byte[256]; + + static { + for (int i = 0; i < encodes.length; i++) { + decodes[encodes[i]] = (byte) i; + } + } + + public static StringBuffer encodeBase62(byte[] data) { + StringBuffer sb = new StringBuffer(data.length * 2); + int pos = 0, val = 0; + for (int i = 0; i < data.length; i++) { + val = (val << 8) | (data[i] & 0xFF); + pos += 8; + while (pos > 5) { + char c = encodes[val >> (pos -= 6)]; + sb.append( + /**/c == 'i' ? "ia" : + /**/c == '+' ? "ib" : + /**/c == '/' ? "ic" : String.valueOf(c)); + val &= ((1 << pos) - 1); + } + } + if (pos > 0) { + char c = encodes[val << (6 - pos)]; + sb.append( + /**/c == 'i' ? "ia" : + /**/c == '+' ? "ib" : + /**/c == '/' ? "ic" : String.valueOf(c)); + } + return sb; + } + + public static byte[] decodeBase62(char[] data) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(data.length); + int pos = 0, val = 0; + for (int i = 0; i < data.length; i++) { + char c = data[i]; + if (c == 'i') { + c = data[++i]; + c = + /**/c == 'a' ? 'i' : + /**/c == 'b' ? '+' : + /**/c == 'c' ? '/' : data[--i]; + } + val = (val << 6) | decodes[c]; + pos += 6; + while (pos > 7) { + baos.write(val >> (pos -= 8)); + val &= ((1 << pos) - 1); + } + } + return baos.toByteArray(); + } + + +} diff --git a/src/main/java/me/ehlxr/token/TokenCommonUtil.java b/src/main/java/me/ehlxr/token/TokenCommonUtil.java new file mode 100644 index 0000000..5ebe3f7 --- /dev/null +++ b/src/main/java/me/ehlxr/token/TokenCommonUtil.java @@ -0,0 +1,132 @@ +package me.ehlxr.token; + +import java.security.MessageDigest; +import java.util.Date; + +public class TokenCommonUtil { + + //使用ThreadLocal变量,防止线程冲突 + private static ThreadLocal mdThreadLocal = new ThreadLocal(); + + + /** + * 计算MD5 + * + * @param bytes + * @return + */ + public static byte[] calculateMD5(byte[] bytes) { + byte b[]; + MessageDigest md = getMD(); + md.reset(); + md.update(bytes); + return md.digest(); + } + + private static MessageDigest getMD() { + MessageDigest md = mdThreadLocal.get(); + if (md == null) { + try { + md = MessageDigest.getInstance("MD5"); + mdThreadLocal.set(md); + } catch (Exception e) { + e.printStackTrace(); + } + } + return md; + } + + /** + * 将两个byte数组结合为一个bytes数组 + * + * @param byte_1 + * @param byte_2 + * @return + */ + public static byte[] byteMerger(byte[] byte_1, byte[] byte_2) { + byte[] byte_3 = new byte[byte_1.length + byte_2.length]; + System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length); + System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length); + return byte_3; + } + + /** + * 将int转为bytes + * + * @param byteNum + * @return + */ + public static int bytes2Int(byte[] byteNum) { + int num = 0; + for (int ix = 0; ix < 4; ++ix) { + num <<= 8; + num |= (byteNum[ix] & 0xff); + } + return num; + } + + /** + * 将bytes转为int + * + * @param num + * @return + */ + public static byte[] int2Bytes(int num) { + byte[] byteNum = new byte[4]; + for (int ix = 0; ix < 4; ++ix) { + int offset = 32 - (ix + 1) * 8; + byteNum[ix] = (byte) ((num >> offset) & 0xff); + } + return byteNum; + } + + /** + * 使用32位来表示时间,可以精确到秒 + * + * @return + */ + public static int getSecondTime() { + long timeLong = System.currentTimeMillis(); + Long timeInt = timeLong / 1000; + return timeInt.intValue(); + } + + /** + * 时间转化为日期 + * + * @param time + * @return + */ + public static Date secondTimeToDate(int time) { + long timeStamp = time * 1000l; + Date date = new Date(); + date.setTime(timeStamp); + return date; + } + + public static byte[] long2Bytes(long value) { + int binaryLength = getBytesLength(value); + long temp = value; + byte b[] = new byte[binaryLength]; + for (int i = binaryLength - 1; i > -1; i--) { + b[i] = new Long(temp & 0xff).byteValue(); + temp = temp >> 8; + } + return b; + } + + private static int getBytesLength(Long value) { + return Long.toBinaryString(value).length() % 8 == 0 ? Long.toBinaryString(value).length() / 8 : Long.toBinaryString(value).length() / 8 + 1; + } + + public static long bytes2long(byte[] b, int length) { + int num = 0; + for (int ix = 0; ix < length; ++ix) { + num <<= 8; + num |= (b[ix] & 0xff); + } + return num; + } + + +} diff --git a/src/main/java/me/ehlxr/token/TokenServerUtil.java b/src/main/java/me/ehlxr/token/TokenServerUtil.java new file mode 100644 index 0000000..f23a2e2 --- /dev/null +++ b/src/main/java/me/ehlxr/token/TokenServerUtil.java @@ -0,0 +1,173 @@ +package me.ehlxr.token; + + + +import java.io.IOException; + +/** + * Created by lixiangrong on 2018/2/2. + */ +public class TokenServerUtil { + + + + public static void main(String[] args) throws Exception { + Long uid = 178934567l; + // System.out.println(Long.toBinaryString(uid)); + // System.out.println(Long.toBinaryString(uid).length() % 8 == 0 ? Long.toBinaryString(uid).length() / 8 : Long.toBinaryString(uid).length() / 8 + 1); + //// long ct1 = System.currentTimeMillis() / 1000;//用秒来表示 + //// System.out.println("当前系统时间戳为:" + ct1); + String token1 = generatorToken(uid, TokenVersionEnum.VERSION_2.getValue()); + System.out.println("生成的token1为:" + token1 + "\n长度1为:" + token1.length()); + // + //// long ct2 = System.currentTimeMillis() / 1000;//用秒来表示 + //// System.out.println("当前系统时间戳为:" + ct2); + // String token2 = generatorToken(uid, TokenVersionEnum.VERSION_2.getValue()); + // System.out.println("生成的token2为:" + token2 + "\n长度2为:" + token2.length()); + // String wrongToken = "AVok"; + // String rightToken = "AVRibaN9MFUo9rOc9ocjXqqw"; + // byte[] rBytes = TokenCommonUtil.calculateMD5(rightToken.getBytes()); + // byte[] wBytes = TokenCommonUtil.calculateMD5(wrongToken.getBytes()); + // byte[] a = Base62.decodeBase62(rightToken.toCharArray()); + // byte[] b = Base62.decodeBase62(wrongToken.toCharArray()); + // System.out.println("rightToken MD5:" + rBytes); + // System.out.println("wrongToken MD5:" + wBytes); + // boolean isTrue1 = checkToken(wrongToken); + // System.out.println("对比结果为:" + isTrue1); + // boolean isTrue2 = checkToken(rightToken); + // System.out.println("对比结果为:" + isTrue2); + // TokenInfo tokenInfo = checkToken(token1); + // if (tokenInfo != null) { + // boolean isOK = tokenInfo.isOK(); + // System.out.println("version 1:"); + // System.out.println(isOK); + // } + // TokenInfo tokenInfo1 = checkToken("AVU4uP10CD1XYIlrJgLubtM"); + // if (tokenInfo1 != null) { + // boolean isOK = tokenInfo1.isOK(); + // Long uidR = tokenInfo1.getUid(); + // System.out.println("version 2:"); + // System.out.println(isOK); + // System.out.println(uidR); + // } + } + + + /** + * 计算token + * token规则为: + * {8位版本}{32位时间戳}{64位随机字符串}{32位校验} + * {1字节版本}{4字节时间戳}{8字节随机字符串}{4字节校验} + * + * @param uid 用户的唯一标识 + * @param version token的版本号 + * @return + */ + public static String generatorToken(Long uid, int version) + throws IOException { + + //获取当前系统时间,以秒为单位 + int time = TokenCommonUtil.getSecondTime(); + + return generatorToken(uid, version, time); + + + } + + /** + * 计算token + * token规则为: + * {8位版本}{32位时间戳}{64位随机字符串}{32位校验} + * {1字节版本}{4字节时间戳}{8字节随机字符串}{4字节校验} + * + * @param uid 用户的唯一标识 + * @param version token的版本号 + * @return + */ + public static String generatorToken(Long uid, int version, int time) + throws IOException { + + //读取配置文件,通过版本号取得对应的token的加密解密的密钥,及自校验位的生成方式 + TokenVersionConfig tokenVersionConfig = TokenVersionFactory.getTokenConfig(version); + //生成token时用的密钥 + String create_token_key = tokenVersionConfig.getCreate_token_key(); + //生成自校验位时的密钥 + String check_token_key = tokenVersionConfig.getCheck_token_key(); + //分别取MD5加密后串的哪四个字节做为自校验位 + String check = tokenVersionConfig.getCheck(); + String[] checkArray = check.split(","); + int i1 = Integer.valueOf(checkArray[0]); + int i2 = Integer.valueOf(checkArray[1]); + int i3 = Integer.valueOf(checkArray[2]); + int i4 = Integer.valueOf(checkArray[3]); + // + // //获取当前系统时间,以秒为单位 + // int time = TokenCommonUtil.getSecondTime(); + + //计算{136位随机字符串,共17字节} + byte[] timeBytes = TokenCommonUtil.int2Bytes(time); + + byte[] udidHalfBytes = generate8MD5ByVersion(version, uid, create_token_key); + + //添加{1字节版本}{4字节时间戳} + byte vByte = (byte) version; + byte[] sidBytes = TokenCommonUtil.byteMerger(new byte[]{vByte}, timeBytes); + + //添加{1字节版本}{4字节时间戳}{中间8字节根据不同版本生成方法有变} + sidBytes = TokenCommonUtil.byteMerger(sidBytes, udidHalfBytes); + + //计算校验位 + byte[] sidMd5Bytes = TokenCommonUtil.byteMerger(sidBytes, check_token_key.getBytes()); + sidMd5Bytes = TokenCommonUtil.calculateMD5(sidMd5Bytes); + //根据配置文件中取相应版本对应的元素组成校验位 + byte[] sidCheckBytes = new byte[]{sidMd5Bytes[i1], sidMd5Bytes[i2], sidMd5Bytes[i3], sidMd5Bytes[i4]}; + + //添加校验位{1字节版本}{4字节时间戳}{中间8字节根据不同版本生成方法有变}{4字节校验} + sidBytes = TokenCommonUtil.byteMerger(sidBytes, sidCheckBytes); + //Base62编码 + String token = Base62.encodeBase62(sidBytes).toString(); + + return token; + } + + + /** + * 中间8字节根据不同版本号生成方式不同 + * + * @param version 当前生成token的版本号 + * @param uid 用户唯一标识 + * @param create_token_key token生成的密钥 + * @return 中间8字节 + */ + private static byte[] generate8MD5ByVersion(int version, Long uid, String create_token_key) { + if (version == TokenVersionEnum.VERSION_1.getValue()) { + String udidBuilder = uid + create_token_key + System.currentTimeMillis(); + byte[] udidBytes = TokenCommonUtil.calculateMD5(udidBuilder.toString().getBytes()); + byte[] udidHalfBytes = new byte[8]; + System.arraycopy(udidBytes, 0, udidHalfBytes, 0, udidHalfBytes.length); + return udidHalfBytes; + } else if (version == TokenVersionEnum.VERSION_2.getValue()) { + //{1字节的uid字节长度} + int binaryLength = getBytesLength(uid); + byte lengthBinary = (byte) binaryLength; + //{1字节的uid字节长度}{binaryLength字节的uid串} + byte[] uByte = TokenCommonUtil.long2Bytes(uid); + byte[] sidBytes = TokenCommonUtil.byteMerger(new byte[]{lengthBinary}, uByte); + //{7 - binaryLength字节MD5的串} + String udidBuilder = create_token_key + System.currentTimeMillis(); + byte[] udidBytes = TokenCommonUtil.calculateMD5(udidBuilder.toString().getBytes()); + + byte[] udidHalfBytes = new byte[7 - binaryLength]; + System.arraycopy(udidBytes, 0, udidHalfBytes, 0, udidHalfBytes.length); + //{1字节的uid字节长度}{binaryLength字节的uid串}{7 - binaryLength字节MD5的串} + sidBytes = TokenCommonUtil.byteMerger(sidBytes, udidHalfBytes); + return sidBytes; + } + return null; + } + + private static int getBytesLength(Long uid) { + return Long.toBinaryString(uid).length() % 8 == 0 ? Long.toBinaryString(uid).length() / 8 : Long.toBinaryString(uid).length() / 8 + 1; + } + +} diff --git a/src/main/java/me/ehlxr/token/TokenVersionConfig.java b/src/main/java/me/ehlxr/token/TokenVersionConfig.java new file mode 100644 index 0000000..2f720e2 --- /dev/null +++ b/src/main/java/me/ehlxr/token/TokenVersionConfig.java @@ -0,0 +1,47 @@ +package me.ehlxr.token; + +/** + * token版本相关的配置 + * User: erin + * Date: 14-10-30 + * Time: 下午5:34 + */ +public class TokenVersionConfig { + + private int version;//token的版本号 + private String create_token_key;//生成token的key + private String check_token_key;//自校验token的key + private String check;//获取自校验位时,取哪些元素 + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public String getCreate_token_key() { + return create_token_key; + } + + public void setCreate_token_key(String create_token_key) { + this.create_token_key = create_token_key; + } + + public String getCheck_token_key() { + return check_token_key; + } + + public void setCheck_token_key(String check_token_key) { + this.check_token_key = check_token_key; + } + + public String getCheck() { + return check; + } + + public void setCheck(String check) { + this.check = check; + } +} diff --git a/src/main/java/me/ehlxr/token/TokenVersionEnum.java b/src/main/java/me/ehlxr/token/TokenVersionEnum.java new file mode 100644 index 0000000..490178d --- /dev/null +++ b/src/main/java/me/ehlxr/token/TokenVersionEnum.java @@ -0,0 +1,40 @@ +package me.ehlxr.token; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + +public enum TokenVersionEnum { + + VERSION_1(1), + VERSION_2(2); + + // provider数字与字符串映射字典表 + private static BiMap VERSION_MAPPING_DICT = HashBiMap.create(); + + static { + VERSION_MAPPING_DICT.put("1", VERSION_1.getValue()); + VERSION_MAPPING_DICT.put("2", VERSION_2.getValue()); + } + + private int value; + + TokenVersionEnum(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static String getStringVersion(int version) { + return VERSION_MAPPING_DICT.inverse().get(version); + } + + public static boolean checkKeyIsExist(String version) { + if (VERSION_MAPPING_DICT.containsKey(version)) { + return true; + } + return false; + } + +} diff --git a/src/main/java/me/ehlxr/token/TokenVersionFactory.java b/src/main/java/me/ehlxr/token/TokenVersionFactory.java new file mode 100644 index 0000000..f1f68b0 --- /dev/null +++ b/src/main/java/me/ehlxr/token/TokenVersionFactory.java @@ -0,0 +1,63 @@ +package me.ehlxr.token; + + +import com.google.common.base.Strings; +import com.google.common.collect.Maps; +import jodd.props.Props; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.ConcurrentMap; + +/** + * token版本控制工厂类 + * User: erin + * Date: 14-10-30 + * Time: 下午5:34 + */ +public class TokenVersionFactory { + + private static String TOKEN_VERSION = "tokenVersion"; + private static String RESOURCE_NAME = "token_version.properties"; + + private static Props properties = null; + + protected static ConcurrentMap versionMap = Maps.newConcurrentMap(); + + public static TokenVersionConfig getTokenConfig(int version) throws IOException { + + String versionString = TokenVersionEnum.getStringVersion(version); + TokenVersionConfig oAuthConsumer = versionMap.get(buildVersionKey(versionString)); + if (oAuthConsumer == null) { + oAuthConsumer = newResource(RESOURCE_NAME, versionString); + versionMap.putIfAbsent(buildVersionKey(versionString), oAuthConsumer); + } + return oAuthConsumer; + } + + private static synchronized TokenVersionConfig newResource(String resourceName, String versionStr) throws IOException { + properties = new Props(); + InputStream input = TokenVersionConfig.class.getClassLoader().getResourceAsStream(resourceName); + properties.load(input); + TokenVersionConfig tokenVersionConfig = new TokenVersionConfig(); + tokenVersionConfig.setVersion(Integer.valueOf(versionStr)); + tokenVersionConfig.setCheck_token_key(getValue("check_token_key", versionStr)); + tokenVersionConfig.setCreate_token_key(getValue("create_token_key", versionStr)); + tokenVersionConfig.setCheck(getValue("check", versionStr)); + return tokenVersionConfig; + } + + private static String getValue(String name, String provider) { + name = TOKEN_VERSION + "." + name; + String url = properties.getValue(name, provider); + if (Strings.isNullOrEmpty(url)) { + return null; + } + return url; + } + + private static String buildVersionKey(String versionStr) { + return versionStr + "_" + RESOURCE_NAME; + } + +} diff --git a/src/main/resources/token_version.properties b/src/main/resources/token_version.properties new file mode 100644 index 0000000..a74cd59 --- /dev/null +++ b/src/main/resources/token_version.properties @@ -0,0 +1,18 @@ +###author liuling +###token生成和校验相关配置 +[tokenVersion<1>] +###生成token时的密钥 +create_token_key=QJ=*S:y^s0$!@#&*()%xZ~&U8($*_+r? +###自校验时的密钥 +check_token_key=QJs#!@%er!#E#$%^%@@!@=**I()^%? +###取MD5加密后的哪几位做为自校验位,共计4字节,32位。最大数字不能超过16 +check=1,4,6,7 + + +[tokenVersion<2>] +###生成token时的密钥 +create_token_key=D#$%^@W#Rj8)@9o%&rgyYL_+!Ldcr5td +###自校验时的密钥 +check_token_key=TL9$_q+Yv^zR):f8)e@P@z&*1@)^o? +###取MD5加密后的哪几位做为自校验位,共计4字节,32位。最大数字不能超过16 +check=8,2,2,5 \ No newline at end of file