├── src
├── test
│ └── main
│ │ ├── resources
│ │ ├── app.properties
│ │ └── logback.xml
│ │ └── java
│ │ ├── VChainTest.java
│ │ ├── RecursionTest.java
│ │ ├── LatchLockTest.java
│ │ ├── CopierTest.java
│ │ ├── UtilsTest.java
│ │ ├── DBTest.java
│ │ ├── CacheTest.java
│ │ ├── DevourerTest.java
│ │ └── AppTest.java
└── main
│ └── java
│ └── cn
│ └── xnatural
│ └── app
│ ├── v
│ ├── VProcessor.java
│ └── VChain.java
│ ├── Inject.java
│ ├── Pause.java
│ ├── Lazier.java
│ ├── LatchLock.java
│ ├── Recursion.java
│ ├── util
│ ├── Tailer.java
│ ├── Copier.java
│ ├── Httper.java
│ └── DB.java
│ ├── ServerTpl.java
│ ├── Devourer.java
│ ├── Utils.java
│ ├── CacheSrv.java
│ └── AppContext.java
├── .gitignore
├── pom.xml
├── LICENSE
└── README.md
/src/test/main/resources/app.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xnat9/tiny/HEAD/src/test/main/resources/app.properties
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | build/
3 | classes/
4 | data/
5 | out/
6 | /build/
7 | /target/*
8 | !/build/libs *.jar
9 | .gradle/
10 | .settings/
11 | .idea/*
12 | *.iml
13 | .project
14 | .classpath
15 | /log
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/v/VProcessor.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app.v;
2 |
3 | /**
4 | * V 链{@link VChain}中的处理器节点
5 | */
6 | public interface VProcessor {
7 |
8 | /**
9 | * V 链中先执行
10 | * @param input 入参
11 | * @return 返回
12 | */
13 | Object pre(Object input);
14 |
15 | /**
16 | * V 链中后执行
17 | * @param input 入参
18 | * @return 返回
19 | */
20 | Object post(Object input);
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/Inject.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.Target;
6 |
7 | import static java.lang.annotation.ElementType.FIELD;
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 |
10 | /**
11 | *
12 | * 对象注入
13 | * 匹配规则:
14 | * 1. 如果 {@link #name()} 为空
15 | * 1.1. 匹配: 字段类型 和 字段名
16 | * 1.2. 匹配: 字段类型
17 | * 2. 匹配: 字段类型 和 {@link #name()}
18 | *
19 | */
20 | @Target({ FIELD })
21 | @Retention(RUNTIME)
22 | @Documented
23 | public @interface Inject {
24 | /**
25 | * 指定bean对象名
26 | */
27 | String name() default "";
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/Pause.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app;
2 |
3 | import java.time.Duration;
4 |
5 |
6 | /**
7 | * 暂停器
8 | */
9 | public class Pause {
10 | /**
11 | * 开始时间
12 | */
13 | protected long start = System.currentTimeMillis();
14 | /**
15 | * 暂停时长
16 | */
17 | protected Duration duration;
18 |
19 | public Pause(Duration duration) {
20 | if (duration == null) throw new NullPointerException("Param duration required");
21 | this.duration = duration;
22 | }
23 |
24 |
25 | /**
26 | * 暂停时间是否已过
27 | * @return true: 时间已过
28 | */
29 | public boolean isTimeout() { return left() < 0; }
30 |
31 |
32 | /**
33 | * 剩余时长(单位:ms)
34 | * @return 小于等于0: 没有剩余时时, 大于0: 剩余时长
35 | */
36 | public long left() { return (start + duration.toMillis()) - System.currentTimeMillis(); }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/main/java/VChainTest.java:
--------------------------------------------------------------------------------
1 | import cn.xnatural.app.v.VChain;
2 | import cn.xnatural.app.v.VProcessor;
3 | import org.junit.jupiter.api.Test;
4 |
5 | public class VChainTest {
6 |
7 |
8 | @Test
9 | void testVChain() {
10 | new VChain().add(new VProcessor() {
11 | @Override
12 | public Object pre(Object input) {
13 | return null;
14 | }
15 |
16 | @Override
17 | public Object post(Object input) {
18 | return null;
19 | }
20 | }).add(new VProcessor() {
21 | @Override
22 | public Object pre(Object input) {
23 | return null;
24 | }
25 |
26 | @Override
27 | public Object post(Object input) {
28 | return null;
29 | }
30 | }).run("xx");
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/v/VChain.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app.v;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.util.Iterator;
7 | import java.util.LinkedList;
8 |
9 | /**
10 | * V链: 执行过程先进后出(类似V型)
11 | */
12 | public class VChain {
13 | protected static final Logger log = LoggerFactory.getLogger(VChain.class);
14 | protected final LinkedList ps = new LinkedList<>();
15 |
16 |
17 | /**
18 | * 执行链
19 | * 按v型依次执行, 上个处理结果为下个处理器的入参
20 | * @param input 入参
21 | * @return 链路执行结果
22 | */
23 | public Object run(Object input) {
24 | Object result = input;
25 | for (Iterator it = ps.iterator(); it.hasNext(); ) {
26 | result = it.next().pre(result);
27 | }
28 | for (Iterator it = ps.descendingIterator(); it.hasNext(); ) {
29 | result = it.next().post(result);
30 | }
31 | return result;
32 | }
33 |
34 |
35 | /**
36 | * 添加V处理器
37 | * @param vProcessor v处理器
38 | * @return {@link VChain}
39 | */
40 | public VChain add(VProcessor vProcessor) { ps.offer(vProcessor); return this; }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/Lazier.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app;
2 |
3 | import java.util.function.Supplier;
4 |
5 | /**
6 | * Groovy @Lazy 实现
7 | * @param
8 | */
9 | public class Lazier implements Supplier {
10 | private final Supplier supplier;
11 | // 只执行一次
12 | private boolean once = false;
13 | private T result;
14 |
15 | public Lazier(Supplier supplier) {
16 | if (supplier == null) throw new NullPointerException("Param supplier is null");
17 | this.supplier = supplier;
18 | }
19 |
20 |
21 | /**
22 | * 清除
23 | */
24 | public void clear() {
25 | synchronized (this) {
26 | once = false;
27 | result = null;
28 | }
29 | }
30 |
31 |
32 | @Override
33 | public T get() {
34 | if (!once) {
35 | synchronized (this) {
36 | if (!once) {
37 | result = supplier.get();
38 | if (result != null) once = true; //为空,则重新取
39 | }
40 | }
41 | }
42 | return result;
43 | }
44 |
45 |
46 | /**
47 | * 是否已执行
48 | */
49 | public boolean done() { return once; }
50 |
51 |
52 | @Override
53 | public String toString() { return result == null ? null : result.toString(); }
54 | }
55 |
--------------------------------------------------------------------------------
/src/test/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%-6thread] [%-36.36C :%-4L] => %m%n
11 |
12 |
13 |
14 |
15 | ${log_path}/${log_file_name}.log
16 |
17 | [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%-6thread] [%-36.36C :%-4L] => %m%n
18 | utf-8
19 |
20 |
21 | ${log_path}/${log_file_name}.%d{yyyyMMdd}.%i.log
22 | ${log.maxHistory:-500}
23 | ${log.maxFileSize:-50MB}
24 | ${log.totalSizeCap:-100GB}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/test/main/java/RecursionTest.java:
--------------------------------------------------------------------------------
1 | import cn.xnatural.app.Recursion;
2 | import org.junit.jupiter.api.Test;
3 |
4 | public class RecursionTest {
5 |
6 |
7 | @Test
8 | void factorial() throws Exception {
9 | System.out.println(factorialTailRecursion(1, 10_000_000).invoke());
10 | }
11 |
12 |
13 | @Test
14 | void fibonacciTest() {
15 | System.out.println(fibonacciMemo(100));
16 | }
17 |
18 |
19 | /**
20 | * 使用同一封装的备忘录模式 执行斐波那契策略
21 | * @param n 第n个斐波那契数
22 | * @return 第n个斐波那契数
23 | */
24 | long fibonacciMemo(long n) {
25 | return Recursion.memo((fib, number) -> {
26 | if (number == 0 || number == 1) {
27 | new Exception().printStackTrace();
28 | return 1L;
29 | }
30 | return fib.apply(number -1 ) + fib.apply(number-2);
31 | }, n);
32 | }
33 |
34 |
35 | /**
36 | * 阶乘计算 -- 使用尾递归接口完成
37 | * @param factorial 当前递归栈的结果值
38 | * @param number 下一个递归需要计算的值
39 | * @return 尾递归接口,调用invoke启动及早求值获得结果
40 | */
41 | Recursion factorialTailRecursion(final long factorial, final long number) {
42 | if (number == 1) {
43 | // new Exception().printStackTrace();
44 | return Recursion.done(factorial);
45 | }
46 | else {
47 | return Recursion.call(() -> factorialTailRecursion(factorial + number, number - 1));
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/test/main/java/LatchLockTest.java:
--------------------------------------------------------------------------------
1 | import cn.xnatural.app.LatchLock;
2 | import org.junit.jupiter.api.Test;
3 |
4 | import java.util.concurrent.ExecutorService;
5 | import java.util.concurrent.Executors;
6 | import java.util.concurrent.ThreadFactory;
7 | import java.util.concurrent.atomic.AtomicBoolean;
8 | import java.util.concurrent.atomic.AtomicInteger;
9 |
10 | public class LatchLockTest {
11 |
12 |
13 | @Test
14 | void testRelease() throws Exception {
15 | LatchLock lock = new LatchLock();
16 |
17 | ExecutorService exec = Executors.newFixedThreadPool(5, new ThreadFactory() {
18 | final AtomicInteger i = new AtomicInteger();
19 | @Override
20 | public Thread newThread(Runnable r) {
21 | return new Thread(r,"t-" + i.incrementAndGet());
22 | }
23 | });
24 |
25 | final AtomicBoolean stop = new AtomicBoolean(false);
26 |
27 | exec.execute(() -> {
28 | while (!stop.get()) {
29 | lock.tryLock();
30 | }
31 | });
32 | Runnable releaseFn = () -> {
33 | while (!stop.get()) {
34 | int i = lock.release();
35 | if (i < 0) {
36 | System.out.println("error: after release lock < 0");
37 | }
38 | }
39 | };
40 | exec.execute(releaseFn);
41 | exec.execute(releaseFn);
42 |
43 | Thread.sleep(1000 * 60 * 2);
44 | stop.set(true);
45 | exec.shutdown();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/LatchLock.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app;
2 |
3 | import java.util.concurrent.atomic.AtomicInteger;
4 |
5 | /**
6 | * 流量限制锁
7 | */
8 | public class LatchLock {
9 | /**
10 | * 流量限制大小
11 | */
12 | protected int limit = 1;
13 | /**
14 | * 当前并发大小
15 | */
16 | protected final AtomicInteger latchSize = new AtomicInteger();
17 |
18 |
19 | /**
20 | * 设置限制
21 | * @param limit >0
22 | */
23 | public LatchLock limit(int limit) {
24 | if (limit < 1) throw new IllegalArgumentException("Param limit must >0");
25 | this.limit = limit;
26 | return this;
27 | }
28 |
29 |
30 | /**
31 | * 获取一个锁
32 | *
33 | * final LatchLock lock = new LatchLock();
34 | * lock.limit(3); // 设置并发限制. 默认为1
35 | * if (lock.tryLock()) { // 尝试获取一个锁
36 | * try {
37 | * // 被执行的代码块
38 | * } finally {
39 | * lock.release(); // 释放一个锁
40 | * }
41 | * }
42 | *
43 | */
44 | public boolean tryLock() {
45 | int latch = latchSize.get();
46 | if (latch >= limit) return false;
47 | if (latchSize.compareAndSet(latch, latch + 1)) return true;
48 | return tryLock();
49 | }
50 |
51 |
52 | /**
53 | * 释放一个锁
54 | * 一般是 {@link #tryLock()} 成功后 调一次
55 | * @return 释放后当前锁的个数
56 | */
57 | public int release() {
58 | int latch = latchSize.get();
59 | if (latch <= 0) return latch;
60 | if (latchSize.compareAndSet(latch, latch - 1)) {
61 | return latch - 1;
62 | }
63 | return latchSize.get();
64 | }
65 |
66 |
67 | /**
68 | * 当前已获取锁个数
69 | */
70 | public int getLatchSize() { return latchSize.get(); }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/Recursion.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app;
2 |
3 |
4 | import java.util.HashMap;
5 | import java.util.Map;
6 | import java.util.function.BiFunction;
7 | import java.util.function.Function;
8 | import java.util.stream.Stream;
9 |
10 |
11 | /**
12 | * java无限递归优化实现
13 | * https://www.cnblogs.com/invoker-/p/7723420.html
14 | * https://www.cnblogs.com/invoker-/p/7728452.html
15 | * @param
16 | */
17 | @FunctionalInterface
18 | public interface Recursion {
19 | /**
20 | * 用于递归栈帧之间的连接,惰性求值
21 | * @return 下一个递归栈帧
22 | */
23 | Recursion apply();
24 |
25 |
26 | /**
27 | * 判断当前递归是否结束
28 | * @return 默认为false,因为正常的递归过程中都还未结束
29 | */
30 | default boolean isFinished(){ return false; }
31 |
32 |
33 | /**
34 | * 获得递归结果,只有在递归结束才能调用,这里默认给出异常,通过工具类的重写来获得值
35 | * @return 递归最终结果
36 | */
37 | default T getResult() {
38 | if (!isFinished()) throw new RuntimeException("递归还没有结束,调用获得结果异常!");
39 | else return null;
40 | }
41 |
42 |
43 | /**
44 | * 及早求值,执行者一系列的递归,因为栈帧只有一个,所以使用findFirst获得最终的栈帧,接着调用getResult方法获得最终递归值
45 | * @return 及早求值,获得最终递归结果
46 | */
47 | default T invoke() {
48 | return Stream.iterate(this, Recursion::apply)
49 | .filter(Recursion::isFinished)
50 | .findFirst()
51 | .map(Recursion::getResult)
52 | .orElse(null);
53 | }
54 |
55 |
56 | /**
57 | * 统一结构的方法,获得当前递归的下一个递归
58 | *
59 | * @param nextFrame 下一个递归
60 | * @param T
61 | * @return 下一个递归
62 | */
63 | static Recursion call(final Recursion nextFrame) { return nextFrame; }
64 |
65 |
66 | /**
67 | * 结束当前递归,重写对应的默认方法的值,完成状态改为true,设置最终返回结果,设置非法递归调用
68 | * @param value 最终递归值
69 | * @param T
70 | * @return 一个isFinished状态true的尾递归, 外部通过调用接口的invoke方法及早求值, 启动递归求值。
71 | */
72 | static Recursion done(T value) {
73 | return new Recursion() {
74 | @Override
75 | public Recursion apply() { throw new RuntimeException("递归已经结束,非法调用apply方法"); }
76 |
77 | @Override
78 | public boolean isFinished() { return true; }
79 |
80 | @Override
81 | public T getResult() { return value; }
82 | };
83 | }
84 |
85 |
86 | /**
87 | * 备忘录模式 函数封装
88 | * @param function 递归策略算法
89 | * @param input 输入值
90 | * @param 输出值类型
91 | * @param 返回值类型
92 | * @return 将输入值输入递归策略算法,计算出的最终结果
93 | */
94 | static R memo(final BiFunction, I, R> function, final I input) {
95 | final Map cache = new HashMap<>();
96 | return new Function() {
97 | @Override
98 | public R apply(final I input) {
99 | return cache.computeIfAbsent(input, key -> function.apply(this, key));
100 | }
101 | }.apply(input);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/test/main/java/CopierTest.java:
--------------------------------------------------------------------------------
1 | import cn.xnatural.app.Utils;
2 | import com.alibaba.fastjson.JSON;
3 | import org.junit.jupiter.api.Test;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | import java.math.BigInteger;
8 | import java.text.SimpleDateFormat;
9 | import java.util.Date;
10 | import java.util.HashMap;
11 | import java.util.Map;
12 |
13 | public class CopierTest {
14 | static final Logger log = LoggerFactory.getLogger(CopierTest.class);
15 |
16 | @Test
17 | void mapToMapTest() {
18 | Map src = new HashMap<>();
19 | src.put("p1", "1111");
20 | src.put("time", System.currentTimeMillis());
21 | String result = Utils.copier(
22 | src,
23 | new HashMap<>()
24 | )
25 | .addConverter("time", o -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date((long) o)))
26 | .mapProp( "p1", "prop1")
27 | .mapProp( "time", "date")
28 | // .showClassProp()
29 | .ignoreNull(true)
30 | .build()
31 | .toString();
32 | log.info("map to map: " + result);
33 | }
34 |
35 |
36 | @Test
37 | void toMapTest() {
38 | String result = Utils.copier(
39 | new Object() {
40 | public String p1 = "xxx";
41 | Integer p2 = 1;
42 | String p3 = "p3";
43 | public String p4 = "p4";
44 | public long time = System.currentTimeMillis();
45 | public String p5;
46 | public BigInteger p6 = BigInteger.valueOf(6);
47 |
48 | public Integer getP2() { return p2; }
49 | private String getPri() { return "pri";}
50 | },
51 | new HashMap<>()
52 | )
53 | .addConverter("time", o -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date((long) o)))
54 | .addConverter("p4", o -> "pp4")
55 | .mapProp( "p1", "prop1")
56 | .add("pp", () -> "pp")
57 | //.showClassProp()
58 | .ignoreNull(true)
59 | .build()
60 | .toString();
61 | log.info("javabean to map: " + result);
62 | }
63 |
64 |
65 | @Test
66 | void toJavabeanTest() {
67 | Object result = Utils.copier(
68 | new Object() {
69 | public String p1 = "xxx";
70 | Integer p2 = 1;
71 | String p3 = "p3";
72 | public String p4 = "p4";
73 | public long time = System.currentTimeMillis();
74 | public String p5;
75 | public BigInteger p6 = BigInteger.valueOf(6);
76 |
77 | public Integer getP2() { return p2; }
78 | private String getPri() { return "pri";}
79 | },
80 | new Object() {
81 | public String p1;
82 | private Integer p2;
83 | public Date time;
84 | public void setP2(Integer p2) { this.p2 = p2; }
85 | public Integer getP2() { return p2; }
86 | public void setR(String r) {}
87 | public String getR() {return p1;}
88 | }
89 | )
90 | .addConverter("time", o -> new Date((long) o))
91 | .build();
92 | log.info("javabean to javabean: " + JSON.toJSONString(result));
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/test/main/java/UtilsTest.java:
--------------------------------------------------------------------------------
1 | import cn.xnatural.app.Utils;
2 | import com.alibaba.fastjson.JSONObject;
3 | import org.junit.jupiter.api.Test;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | import java.io.*;
8 | import java.nio.charset.Charset;
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | public class UtilsTest {
13 | static final Logger log = LoggerFactory.getLogger(UtilsTest.class);
14 |
15 |
16 | public static void main(String[] args) {
17 | Utils.tailer().tail("/media/xnat/store/code_repo/gy/log/app.log", 10);
18 | }
19 |
20 |
21 | @Test
22 | void pid() {
23 | log.info(Utils.pid());
24 | }
25 |
26 |
27 | @Test
28 | void isLinux() {
29 | log.info(Utils.isLinux() + "");
30 | }
31 |
32 |
33 | @Test
34 | void baseDir() {
35 | log.info(Utils.baseDir(null).getAbsolutePath());
36 | }
37 |
38 |
39 | @Test
40 | void ipv4() {
41 | log.info(Utils.ipv4());
42 | }
43 |
44 |
45 | @Test
46 | void md5Hex() {
47 | log.info(Utils.md5Hex("000".getBytes(Charset.forName("utf-8"))));
48 | }
49 |
50 |
51 | @Test
52 | void nanoIdTest() {
53 | log.info("nanoId: " + Utils.nanoId());
54 | log.info("nanoId: " + Utils.nanoId(1));
55 | }
56 |
57 |
58 | @Test
59 | void toTest() {
60 | log.info("to Integer: " + Utils.to(1, Integer.class));
61 | log.info("to String: " + Utils.to("s", String.class));
62 | }
63 |
64 |
65 | @Test
66 | void http() throws Exception {
67 | Utils.http().get("http://xnatural.cn:9090/test/cus?p2=2")
68 | .param("p1", 1)
69 | .debug().execute();
70 | Utils.http().post("http://xnatural.cn:9090/test/cus")
71 | .debug().execute();
72 | Utils.http().post("http://xnatural.cn:9090/test/form")
73 | .param("p1", "p1")
74 | .debug().execute();
75 | Utils.http().post("http://xnatural.cn:9090/test/json")
76 | .jsonBody(new JSONObject().fluentPut("p1", 1).toString())
77 | .debug().execute();
78 | Utils.http().post("http://xnatural.cn:9090/test/upload")
79 | .param("version", "1.0.1")
80 | .param("file", new File("d:/tmp/tmp.json"))
81 | .debug().execute();
82 | Utils.http().post("http://xnatural.cn:9090/test/upload")
83 | .fileStream("file", "testfile.md", new FileInputStream("d:/tmp/test.md"))
84 | .debug().execute();
85 | }
86 |
87 |
88 | @Test
89 | void buildUrl() {
90 | Map params = new HashMap<>();
91 | params.put("p1", "111");
92 | params.put("p2", 222);
93 | System.out.println(
94 | Utils.buildUrl("http://xnatural.cn:9090/test", null)
95 | );
96 | System.out.println(
97 | Utils.buildUrl("http://xnatural.cn:9090/test", params)
98 | );
99 | System.out.println(
100 | Utils.buildUrl("http://xnatural.cn:9090/test?", params)
101 | );
102 | System.out.println(
103 | Utils.buildUrl("http://xnatural.cn:9090/test?test=", params)
104 | );
105 | System.out.println(
106 | Utils.buildUrl("http://xnatural.cn:9090/test?test=aaa", params)
107 | );
108 | }
109 |
110 |
111 | @Test
112 | void ioCopy() throws Exception {
113 | try (InputStream is = new FileInputStream("d:/tmp/1.txt"); OutputStream os = new FileOutputStream("d:/tmp/2.txt")) {
114 | Utils.ioCopy(is, os);
115 | }
116 | }
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/util/Tailer.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app.util;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.io.RandomAccessFile;
7 | import java.util.LinkedList;
8 | import java.util.Queue;
9 | import java.util.Random;
10 | import java.util.concurrent.Executor;
11 | import java.util.function.Function;
12 |
13 | /**
14 | * 文件内容监控器(类linux tail)
15 | */
16 | public class Tailer {
17 | protected static final Logger log = LoggerFactory.getLogger(Tailer.class);
18 | protected Thread th;
19 | protected boolean stopFlag;
20 | protected Function lineFn;
21 | protected Executor exec;
22 |
23 | /**
24 | * 处理输出行
25 | * @param lineFn 函数返回true 继续输出, 返回false则停止输出
26 | */
27 | public Tailer handle(Function lineFn) {this.lineFn = lineFn; return this;}
28 | /**
29 | * 设置处理线程池
30 | * @param exec 线程池
31 | */
32 | public Tailer exec(Executor exec) {this.exec = exec; return this;}
33 | /**
34 | * 停止
35 | */
36 | public void stop() {this.stopFlag = true;}
37 |
38 | /**
39 | * tail 文件内容监控
40 | * @param file 文件全路径
41 | * @return {@link Tailer}
42 | */
43 | public Tailer tail(String file) { return tail(file, 5); }
44 | /**
45 | * tail 文件内容监控
46 | * @param file 文件全路径
47 | * @param follow 从最后第几行开始
48 | * @return {@link Tailer}
49 | */
50 | public Tailer tail(String file, Integer follow) {
51 | if (lineFn == null) lineFn = (line) -> {System.out.println(line); return true;};
52 | Runnable fn = () -> {
53 | String tName = Thread.currentThread().getName();
54 | try {
55 | Thread.currentThread().setName("Tailer-" + file);
56 | run(file, (follow == null ? 0 : follow));
57 | } catch (Exception ex) {
58 | log.error("Tail file " + file + " error", ex);
59 | } finally {
60 | Thread.currentThread().setName(tName);
61 | }
62 | };
63 | if (exec != null) {
64 | exec.execute(fn);
65 | } else {
66 | th = new Thread(fn, "Tailer-" + file);
67 | // th.setDaemon(true);
68 | th.start();
69 | }
70 | return this;
71 | }
72 |
73 | private void run(String file, Integer follow) throws Exception {
74 | try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
75 | Queue buffer = (follow != null && follow > 0) ? new LinkedList<>() : null; // 用来保存最后几行(follow)数据
76 |
77 | // 当前第一次到达文件结尾时,设为true
78 | boolean firstEnd = false;
79 | String line;
80 | while (!stopFlag) {
81 | line = raf.readLine();
82 | if (line == null) { // 当读到文件结尾时(line为空即为文件结尾)
83 | if (firstEnd) {
84 | Thread.sleep(100L * new Random().nextInt(10));
85 | // raf.seek(file.length()) // 重启定位到文件结尾(有可能文件被重新写入了)
86 | continue;
87 | }
88 | firstEnd = true;
89 | if (buffer != null) { // 第一次到达结尾后, 清理buffer数据
90 | do {
91 | line = buffer.poll();
92 | if (line == null) break;
93 | this.stopFlag = !lineFn.apply(line);
94 | } while (!stopFlag);
95 | buffer = null;
96 | }
97 | } else { // 读到行有数据
98 | line = new String(line.getBytes("ISO-8859-1"),"utf-8");
99 | if (firstEnd) { // 直接处理行字符串
100 | stopFlag = !lineFn.apply(line);
101 | } else if (follow != null && follow > 0) {
102 | buffer.offer(line);
103 | if (buffer.size() > follow) buffer.poll();
104 | }
105 | }
106 | }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/test/main/java/DBTest.java:
--------------------------------------------------------------------------------
1 | import cn.xnatural.app.util.DB;
2 | import org.junit.jupiter.api.Assertions;
3 | import org.junit.jupiter.api.Test;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | import java.text.SimpleDateFormat;
8 | import java.util.Date;
9 |
10 | public class DBTest {
11 | static final Logger log = LoggerFactory.getLogger(DBTest.class);
12 |
13 | @Test
14 | void getJdbcUrlTest() throws Exception {
15 | try (DB repo = new DB("jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root&allowPublicKeyRetrieval=true")) {
16 | log.info(repo.getJdbcUrl());
17 | }
18 | }
19 |
20 |
21 | @Test
22 | void rowTest() throws Exception {
23 | try (DB repo = new DB("jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root&allowPublicKeyRetrieval=true")) {
24 | log.info(repo.row("select * from test order by id desc").toString());
25 | }
26 | }
27 |
28 |
29 | @Test
30 | void rowsTest() throws Exception {
31 | try (DB repo = new DB("jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root&allowPublicKeyRetrieval=true")) {
32 | log.info(repo.rows("select * from test where id=? limit 0, ?", 2, 10).toString());
33 | }
34 | }
35 |
36 |
37 | @Test
38 | void inTest() throws Exception {
39 | try (DB repo = new DB("jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root&allowPublicKeyRetrieval=true")) {
40 | log.info(repo.rows("select * from test where id in (?, ?)", 2, 7).toString());
41 | }
42 | }
43 |
44 |
45 | @Test
46 | void singleTest() throws Exception {
47 | try (DB repo = new DB("jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root&allowPublicKeyRetrieval=true")) {
48 | Assertions.assertNotNull(repo.single("select count(1) from test", Integer.class));
49 | }
50 | }
51 |
52 |
53 | @Test
54 | void insertTest() throws Exception {
55 | try (DB repo = new DB("jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root&allowPublicKeyRetrieval=true")) {
56 | log.info("插入一条: " + repo.execute("insert into test(name, age, create_time) values(?, ?, ?)",
57 | "nn" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()), new Date().getSeconds(), new Date()));
58 | }
59 | }
60 |
61 |
62 | @Test
63 | void updateTest() throws Exception {
64 | try (DB repo = new DB("jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root&allowPublicKeyRetrieval=true")) {
65 | log.info("更新数据: " + repo.execute("update test set age=? where id=?", 30, 7));
66 | }
67 | }
68 |
69 |
70 | @Test
71 | void insertWithGeneratedKeyTest() throws Exception {
72 | try (DB repo = new DB("jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root&allowPublicKeyRetrieval=true")) {
73 | Object id = repo.insertWithGeneratedKey("insert into test(name, age, create_time) values(?, ?, ?)",
74 | "nn" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()), new Date().getSeconds(), new Date());
75 | Assertions.assertNotNull(id);
76 | log.info("插入一条返回主键: " + id);
77 | }
78 | }
79 |
80 |
81 | @Test
82 | void transTest() throws Exception {
83 | try (DB repo = new DB("jdbc:mysql://localhost:3306/test?useSSL=false&user=root&password=root&allowPublicKeyRetrieval=true")) {
84 | Integer count = repo.single("select count(1) from test", Integer.class);
85 | try {
86 | repo.trans(() -> {
87 | repo.execute("insert into test(name, age, create_time) values(?, ?, ?)",
88 | "nn" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()), new Date().getSeconds(), new Date());
89 | throw new RuntimeException("xxxx");
90 | });
91 | } catch (Exception ex) {}
92 | Assertions.assertTrue(
93 | repo.single("select count(1) from test", Integer.class) > count,
94 | "trans test fail"
95 | );
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/test/main/java/CacheTest.java:
--------------------------------------------------------------------------------
1 | import cn.xnatural.app.AppContext;
2 | import cn.xnatural.app.CacheSrv;
3 | import org.junit.jupiter.api.Assertions;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.time.Duration;
7 | import java.util.Objects;
8 |
9 | public class CacheTest {
10 |
11 |
12 | @Test
13 | void cacheGet() {
14 | final AppContext app = new AppContext();
15 | app.addSource(new CacheSrv());
16 | app.start();
17 |
18 | CacheSrv cache = app.bean(CacheSrv.class, null);
19 | cache.set("c1", "aaa");
20 | Assertions.assertTrue(Objects.equals(cache.get("c1"), "aaa"));
21 | Assertions.assertTrue(Objects.equals(app.ep().fire("cacheSrv.get", "c1"), "aaa"));
22 | cache.hset("key", "dataKey", 1);
23 | Assertions.assertTrue(Objects.equals(cache.hget("key", "dataKey"), 1));
24 | Assertions.assertTrue(Objects.equals(app.ep().fire("cacheSrv.hget", "key", "dataKey"), 1));
25 | }
26 |
27 |
28 | @Test
29 | void cacheLimit() {
30 | int limit = 10;
31 | final AppContext app = new AppContext();
32 | app.addSource(new CacheSrv());
33 | app.env().put("cacheSrv.itemLimit", limit);
34 | app.start();
35 |
36 | CacheSrv cache = app.bean(CacheSrv.class, null);
37 | for (int i = 0; i < 200; i++) {
38 | cache.set("key_" + i, i);
39 | int v = i;
40 | cache.hset("key", "" + i, (AutoCloseable) () -> System.out.println("=====h close " + v));
41 | }
42 | Assertions.assertTrue(cache.count() <= limit);
43 | }
44 |
45 |
46 | @Test
47 | void cacheExpire() throws Exception {
48 | int limit = 10;
49 | final AppContext app = new AppContext();
50 | app.addSource(new CacheSrv());
51 | app.env().put("cacheSrv.itemLimit", limit);
52 | app.start();
53 |
54 | String key = "a";
55 |
56 | CacheSrv cache = app.bean(CacheSrv.class, null);
57 | cache.set(key, 1, Duration.ofSeconds(5));
58 | Thread.sleep(4000);
59 | Assertions.assertTrue((Integer) cache.get(key) == 1, "");
60 | Thread.sleep(1000);
61 | Assertions.assertTrue(cache.get(key) == null, "");
62 |
63 | long now = System.currentTimeMillis();
64 | cache.set(key, 1, r -> 5000 + now);
65 | Thread.sleep(4000);
66 | Assertions.assertTrue((Integer) cache.get(key) == 1, "");
67 | Thread.sleep(1000);
68 | Assertions.assertTrue(cache.get(key) == null, "");
69 |
70 |
71 | String dataKey = "dataKey";
72 | cache.hset(key, dataKey, 1, Duration.ofSeconds(5));
73 | cache.hset(key, dataKey + "1", 1, Duration.ofSeconds(5));
74 | cache.hset(key, dataKey + "2", 1, Duration.ofSeconds(5));
75 | Thread.sleep(4000);
76 | Assertions.assertTrue((Integer) cache.hget(key, dataKey) == 1, "");
77 | Thread.sleep(1000);
78 | Assertions.assertTrue(cache.hget(key, dataKey) == null, "");
79 | Assertions.assertTrue(cache.hget(key, dataKey + "1") == null, "");
80 | }
81 |
82 |
83 | @Test
84 | void cleanAllExpireRecord() throws Exception {
85 | int limit = 10;
86 | final AppContext app = new AppContext();
87 | app.addSource(new CacheSrv());
88 | app.env().put("cacheSrv.itemLimit", limit);
89 | app.start();
90 |
91 | CacheSrv cache = app.bean(CacheSrv.class, null);
92 |
93 | for (int i = 0; i < 4; i++) {
94 | int v = i;
95 | cache.set("a" + i, (AutoCloseable) () -> System.out.println("=====close " + v), Duration.ofSeconds(4));
96 | cache.hset("a" + i, "" + i, (AutoCloseable) () -> System.out.println("=====h close " + v), Duration.ofSeconds(4));
97 | }
98 | Thread.sleep(3000L);
99 | cache.getAndUpdate("a0");
100 | Thread.sleep(1000L);
101 | Assertions.assertTrue(cache.count() == 8);
102 | cache.set("a", "a", Duration.ofSeconds(2)); // 此时会清理所有过期的缓存
103 | Thread.sleep(3000L);
104 | Assertions.assertTrue(cache.count() == 2);
105 | }
106 |
107 |
108 | @Test
109 | void testClose() {
110 | final AppContext app = new AppContext();
111 | app.addSource(new CacheSrv());
112 | app.start();
113 |
114 | String key = "a";
115 |
116 | CacheSrv cache = app.bean(CacheSrv.class, null);
117 | cache.set(key, (AutoCloseable) () -> System.out.println("=====close"));
118 | cache.remove(key);
119 |
120 | String dataKey = "a";
121 | cache.hset(key, dataKey, (AutoCloseable) () -> System.out.println("=====h close"));
122 | cache.hremove(key, dataKey);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/test/main/java/DevourerTest.java:
--------------------------------------------------------------------------------
1 | import cn.xnatural.app.Devourer;
2 | import org.junit.jupiter.api.Test;
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.time.Duration;
7 | import java.util.concurrent.ExecutorService;
8 | import java.util.concurrent.Executors;
9 | import java.util.concurrent.ThreadFactory;
10 | import java.util.concurrent.atomic.AtomicBoolean;
11 | import java.util.concurrent.atomic.AtomicInteger;
12 |
13 | public class DevourerTest {
14 | static final Logger log = LoggerFactory.getLogger(DevourerTest.class);
15 |
16 | @Test
17 | void testParallel() throws Exception {
18 | Devourer devourer = new Devourer("parallel");
19 | devourer.parallel(3);
20 | long start = System.currentTimeMillis();
21 | AtomicBoolean stop = new AtomicBoolean(false);
22 | while (System.currentTimeMillis() - start < 1000 * 15 && !stop.get()) {
23 | devourer.offer(() -> {
24 | int p = devourer.getParallel();
25 | if (p > 3) {
26 | log.info("========================" + p);
27 | stop.set(true);
28 | }
29 | log.info("wait " + devourer.getWaitingCount() + ", " + p);
30 | // Utils.http().get("http://xnatural.cn:9090/test/cus?p1=" + count.getAndIncrement()).debug().execute();
31 | // try {
32 | // Thread.sleep(2000 + new Random().nextInt(10000));
33 | // } catch (InterruptedException e) {
34 | // e.printStackTrace();
35 | // }
36 | });
37 | // if (devourer.getWaitingCount() > 40) Thread.sleep(100 + new Random().nextInt(10000));
38 | }
39 | Thread.sleep(1000 * 60 * 30);
40 | }
41 |
42 |
43 | @Test
44 | void testParallel2() throws Exception {
45 | Devourer devourer = new Devourer("parallel2");
46 | devourer.parallel(3);
47 | for (int i = 0; i < 10; i++) {
48 | int j = i + 1;
49 | devourer.offer(() -> {
50 | log.info("start-" + j);
51 | try {
52 | Thread.sleep(1000 * 5);
53 | } catch (InterruptedException e) {
54 | e.printStackTrace();
55 | }
56 | log.info("end-" + j);
57 | });
58 | }
59 | Thread.sleep(1000 * 60 * 3);
60 | }
61 |
62 |
63 | @Test
64 | void testSuspend() throws Exception {
65 | Devourer devourer = new Devourer("suspend");
66 | devourer.offer(() -> {
67 | log.info("执行");
68 | devourer.suspend(Duration.ofMillis(1500));
69 | });
70 |
71 | for (int i = 0; i < 50; i++) {
72 | int finalI = i;
73 | Thread.sleep(100);
74 | devourer.offer(() -> {
75 | log.info("执行 " + finalI);
76 | });
77 | log.info("added" + i);
78 | }
79 | devourer.resume();
80 | Thread.sleep(60 * 1000);
81 | }
82 |
83 |
84 | @Test
85 | void testUseLast() throws Exception {
86 | Devourer devourer = new Devourer("useLast").useLast(true);
87 | for (int i = 0; i < 20; i++) {
88 | Thread.sleep(100);
89 | int finalI = i;
90 | devourer.offer(() -> {
91 | log.info("==========" + finalI);
92 | try {
93 | Thread.sleep(500);
94 | } catch (InterruptedException e) {
95 | log.error("", e);
96 | }
97 | });
98 | }
99 | Thread.sleep(20 * 1000);
100 | }
101 |
102 |
103 | @Test
104 | void testSpeed() throws Exception {
105 | Devourer devourer = new Devourer();
106 | devourer.speed("8/s");
107 |
108 | ExecutorService exec = Executors.newFixedThreadPool(2, new ThreadFactory() {
109 | final AtomicInteger i = new AtomicInteger();
110 | @Override
111 | public Thread newThread(Runnable r) {
112 | return new Thread(r,"t-" + i.incrementAndGet());
113 | }
114 | });
115 |
116 | final AtomicBoolean stop = new AtomicBoolean(false);
117 |
118 | exec.execute(() -> {
119 | while (!stop.get()) {
120 | devourer.offer(() -> {
121 | log.info("=====left: " + devourer.getWaitingCount());
122 | });
123 | try {
124 | Thread.sleep(100);
125 | } catch (InterruptedException e) {
126 | log.error("offer error", e);
127 | }
128 | }
129 | });
130 |
131 |
132 | Thread.sleep(1000 * 60);
133 | stop.set(true);
134 | Thread.sleep(1000 * 20);
135 | exec.shutdown();
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/test/main/java/AppTest.java:
--------------------------------------------------------------------------------
1 | import cn.xnatural.app.AppContext;
2 | import cn.xnatural.app.Inject;
3 | import cn.xnatural.app.ServerTpl;
4 | import cn.xnatural.app.Utils;
5 | import cn.xnatural.app.util.DB;
6 | import cn.xnatural.enet.event.EL;
7 | import cn.xnatural.http.HttpServer;
8 | import cn.xnatural.remoter.Remoter;
9 | import cn.xnatural.sched.Sched;
10 |
11 | import java.time.Duration;
12 | import java.util.Random;
13 |
14 | public class AppTest {
15 |
16 | public static void main(String[] args) {
17 | new AppContext()
18 | .addSource(
19 | new ServerTpl("server1") {
20 | @Inject DB db;
21 |
22 | @EL(name = "sys.starting")
23 | void start() { log.info("{} start", name); }
24 |
25 | @EL(name = "sys.started", async = true)
26 | void started() {
27 | db.execute("create table IF NOT EXISTS test(id int auto_increment, name varchar(255));");
28 | db.execute("insert into test(name) values('aa');");
29 | log.info("测试sql: " + db.single("select count(1) from test", Integer.class));
30 | }
31 | }
32 | , sched()
33 | , web()
34 | , db()
35 | , remoter()
36 | , 压测()
37 | ).start();
38 | }
39 |
40 |
41 | /**
42 | * 创建数据库操作
43 | */
44 | static ServerTpl db() {
45 | return new ServerTpl("db_local") { //数据库
46 | DB DB;
47 | @EL(name = "sys.starting", async = true)
48 | void start() {
49 | DB = new DB(getStr("url", null));
50 | attrs().forEach((k, v) -> DB.dsAttr(k, v));
51 | exposeBean(DB, "db_local");
52 | ep.fire(name + ".started");
53 | }
54 |
55 | @EL(name = "sys.stopping", async = true, order = 2f)
56 | void stop() {
57 | if (DB != null) {
58 | try { DB.close(); } catch (Exception e) {
59 | log.error("", e);
60 | }
61 | }
62 | }
63 | };
64 | }
65 |
66 |
67 | /**
68 | * 创建 web服务
69 | */
70 | static ServerTpl web() {
71 | return new ServerTpl("web") { //添加http服务
72 | HttpServer server;
73 |
74 | @EL(name = "sys.starting", async = true)
75 | void start() {
76 | server = new HttpServer(attrs(), exec());
77 | server.buildChain(chain -> {
78 | chain.get("test", hCtx -> hCtx.render("xxxxxx"));
79 | });
80 | server.start();
81 | server.enabled = false;
82 | }
83 |
84 | @EL(name = "sys.started", async = true)
85 | void started() {
86 | for (Object ctrl : server.getCtrls()) exposeBean(ctrl);
87 | server.enabled = true;
88 | }
89 |
90 | @EL(name = "sys.stop")
91 | void stop() { if (server != null) server.close(); }
92 | };
93 | }
94 |
95 |
96 | /**
97 | * 添加时间调度服务
98 | */
99 | static ServerTpl sched() {
100 | return new ServerTpl("sched") { // 定时任务
101 | Sched sched;
102 | @EL(name = "sys.starting", async = true)
103 | void start() {
104 | sched = new Sched(attrs(), exec()).init();
105 | exposeBean(sched);
106 | ep.fire(name + ".started");
107 | }
108 |
109 | @EL(name = "sched.after")
110 | void after(Duration duration, Runnable fn) {sched.after(duration, fn);}
111 |
112 | @EL(name = "sys.stopping", async = true)
113 | void stop() { if (sched != null) sched.stop(); }
114 | };
115 | }
116 |
117 |
118 | static ServerTpl remoter() {
119 | return new ServerTpl("remoter") {
120 | @EL(name = "sched.started")
121 | void start() {
122 | Remoter remoter = new Remoter(app().name(), app().id(), attrs(), exec(), ep, bean(Sched.class));
123 | exposeBean(remoter);
124 | exposeBean(remoter.xioClient, "xioClient");
125 | ep.fire(name + ".started");
126 | }
127 | };
128 | }
129 |
130 |
131 | static ServerTpl 压测() {
132 | return new ServerTpl("testExec") {
133 | @Inject Sched sched;
134 |
135 | @EL(name = "sys.started", async = true)
136 | void test() {
137 | // 新增任务的速度必须是任务执行的时长的step倍, 才会新增线程
138 | sched.fixedDelay(Duration.ofMillis(300), () -> {
139 | async(() -> {
140 | Utils.http().get("http://39.104.28.131:8080/test/timeout?wait=" + (800 + (new Random().nextInt(500)))).debug().execute();
141 | log.info("===== " + exec());
142 | });
143 | });
144 | }
145 | };
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/ServerTpl.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app;
2 |
3 | import cn.xnatural.enet.event.EC;
4 | import cn.xnatural.enet.event.EL;
5 | import cn.xnatural.enet.event.EP;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import java.util.HashMap;
10 | import java.util.Iterator;
11 | import java.util.Map;
12 | import java.util.concurrent.ExecutorService;
13 | import java.util.function.Consumer;
14 |
15 | /**
16 | * 服务模板类
17 | */
18 | public class ServerTpl {
19 | protected final Logger log = LoggerFactory.getLogger(getClass().getName().contains("$") ? getClass().getSuperclass() : getClass());
20 | /**
21 | * 服务名字标识.(保证唯一)
22 | * 可用于命名空间:
23 | * 1. 可用于属性配置前缀
24 | * 2. 可用于事件名字前缀
25 | */
26 | protected final String name;
27 | /**
28 | * 1. 当此服务被加入核心时, 此值会自动设置为核心的EP.
29 | * 2. 如果要服务独立运行时, 请手动设置
30 | */
31 | @Inject protected EP ep;
32 | private final Lazier _app = new Lazier<>(() -> bean(AppContext.class));
33 | private final Lazier _exec = new Lazier<>(() -> bean(ExecutorService.class));
34 |
35 |
36 | public ServerTpl(String name) {
37 | if (name == null || name.isEmpty()) throw new IllegalArgumentException("Param name required");
38 | this.name = name;
39 | }
40 | public ServerTpl() {
41 | String n = getClass().getName().contains("$") ? getClass().getName() : getClass().getSimpleName();
42 | this.name = Character.toLowerCase(n.charAt(0)) + (n.length() > 1 ? n.substring(1) : "");
43 | }
44 |
45 | /**
46 | * bean 容器. {@link #localBean}
47 | */
48 | protected Map beanCtx;
49 | @EL(name = {"bean.get", "{name}.bean.get"})
50 | protected T localBean(EC ec, Class bType, String bName) {
51 | if (beanCtx == null) return null;
52 |
53 | Object bean = null;
54 | if (bName != null && bType != null) {
55 | bean = beanCtx.get(bName);
56 | if (bean != null && !bType.isAssignableFrom(bean.getClass())) bean = null;
57 | } else if (bName != null && bType == null) {
58 | bean = beanCtx.get(bName);
59 | } else if (bName == null && bType != null) {
60 | if (bType.isAssignableFrom(getClass())) bean = this;
61 | else {
62 | for (Iterator> it = beanCtx.entrySet().iterator(); it.hasNext(); ) {
63 | Map.Entry e = it.next();
64 | if (bType.isAssignableFrom(e.getValue().getClass())) {
65 | bean = e.getValue(); break;
66 | }
67 | }
68 | }
69 | }
70 | return (T) bean;
71 | }
72 |
73 |
74 | /**
75 | * 本地查找 对象
76 | * @param bType 对象类型
77 | * @param bName 对象名字
78 | * @return bean
79 | */
80 | protected T localBean(Class bType, String bName) { return localBean(null, bType, bName); }
81 |
82 | /**
83 | * 本地查找 对象
84 | * @param bType 对象类型
85 | * @return bean
86 | */
87 | protected T localBean(Class bType) { return localBean(null, bType, null); }
88 |
89 |
90 | /**
91 | * 全局查找 bean
92 | * @param type bean类型
93 | * @param name bean名字
94 | * @return bean
95 | */
96 | protected T bean(Class type, String name) {
97 | return ep == null ? null : (T) ep.fire(new EC("bean.get", this).sync().args(type, name));
98 | }
99 |
100 | /**
101 | * 全局查找 bean
102 | * @param type bean类型
103 | * @return bean
104 | */
105 | protected T bean(Class type) { return bean(type, null); }
106 |
107 |
108 | /**
109 | * 异步执行. 拦截异常
110 | * @param fn 异步执行的函数
111 | * @param exFn 错误处理函数
112 | */
113 | public ServerTpl async(Runnable fn, Consumer exFn) {
114 | _exec.get().execute(() -> {
115 | try {fn.run();} catch (Throwable ex) {
116 | if (exFn != null) exFn.accept(ex);
117 | else log.error("", ex);
118 | }
119 | });
120 | return this;
121 | }
122 | /**
123 | * 异步执行. 拦截异常
124 | * @param fn 异步执行的函数
125 | */
126 | public ServerTpl async(Runnable fn) { return async(fn, null); }
127 |
128 |
129 | /**
130 | * 对列执行
131 | * @param qName 对列名, 默认当前server名称
132 | * @param fn 要执行的函数
133 | * @return {@link Devourer}当前对列
134 | */
135 | public Devourer queue(String qName, Runnable fn) { return _app.get().queue(qName, fn); }
136 | /**
137 | * 获取执行对列
138 | * @param qName 对列名
139 | * @return {@link Devourer}当前对列
140 | */
141 | public Devourer queue(String qName) { return _app.get().queue(qName, null); }
142 | /**
143 | * 对列执行, 加入到当前{@link ServerTpl#name}为对列名的对列
144 | * @param fn 要执行的函数
145 | * @return {@link Devourer}当前对列
146 | */
147 | public Devourer queue(Runnable fn) { return _app.get().queue(name, fn); }
148 |
149 |
150 | /**
151 | * 暴露 bean 给其它模块用. {@link #localBean}
152 | * @param bean bean实例
153 | * @param names bean 名字
154 | */
155 | protected ServerTpl exposeBean(Object bean, String...names) {
156 | if (bean == null) return this;
157 | if (beanCtx == null) beanCtx = new HashMap<>(7);
158 | if (names == null || names.length < 1) {
159 | String n = bean.getClass().getName().contains("$") ? bean.getClass().getName() : bean.getClass().getSimpleName();
160 | names = new String[]{Character.toLowerCase(n.charAt(0)) + (n.length() > 1 ? n.substring(1) : "")};
161 | }
162 | for (String n : names) {
163 | if (beanCtx.get(n) != null) log.warn("Override exist bean name '{}'", n);
164 | beanCtx.put(n, bean);
165 | }
166 | ep.addListenerSource(bean); _app.get().inject(bean);
167 | return this;
168 | }
169 |
170 |
171 | /**
172 | * 当前应用上下文
173 | * @return {@link AppContext}
174 | */
175 | public AppContext app() { return _app.get(); }
176 |
177 |
178 | /**
179 | * 线程池
180 | * @return {@link ExecutorService}
181 | */
182 | protected ExecutorService exec() { return _exec.get(); }
183 |
184 |
185 | /**
186 | * 服务名
187 | * @return 服务名
188 | */
189 | public String getName() { return name; }
190 |
191 |
192 | /**
193 | * 当前服务的属性集
194 | * @return 属性集
195 | */
196 | public Map attrs() { return app().attrs(name); }
197 |
198 |
199 | /**
200 | * 设置属性
201 | * @param aName 属性名
202 | * @param aValue 属性值
203 | * @return {@link ServerTpl}
204 | */
205 | public ServerTpl setAttr(String aName, Object aValue) {
206 | app().env().put(name+ "." +aName, aValue);
207 | return this;
208 | }
209 |
210 |
211 | /**
212 | * 获取属性
213 | * @param key 属性key
214 | * @param type 值类型
215 | * @param defaultValue 默认值
216 | * @return 属性值
217 | */
218 | public T getAttr(String key, Class type, T defaultValue) {
219 | Object obj = null;
220 | for (Map.Entry entry : app().env().entrySet()) {
221 | if ((name + "." + key).equals(entry.getKey())) {
222 | obj = entry.getValue(); break;
223 | }
224 | }
225 | T v = Utils.to(obj, type);
226 | if (v == null) return defaultValue;
227 | return v;
228 | }
229 |
230 |
231 | protected Long getLong(String key, Long defaultValue) { return getAttr(key, Long.class, defaultValue); }
232 |
233 | protected Integer getInteger(String key, Integer defaultValue) { return getAttr(key, Integer.class, defaultValue); }
234 |
235 | protected Double getDouble(String key, Double defaultValue) { return getAttr(key, Double.class, defaultValue); }
236 |
237 | protected Float getFloat(String key, Float defaultValue) { return getAttr(key, Float.class, defaultValue); }
238 |
239 | protected String getStr(String key, String defaultValue) { return getAttr(key, String.class, defaultValue); }
240 |
241 | protected Boolean getBoolean(String key, Boolean defaultValue) { return getAttr(key, Boolean.class, defaultValue); }
242 | }
243 |
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/Devourer.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.time.Duration;
7 | import java.util.Arrays;
8 | import java.util.Deque;
9 | import java.util.concurrent.*;
10 | import java.util.concurrent.atomic.AtomicInteger;
11 | import java.util.function.BiConsumer;
12 | import java.util.function.Predicate;
13 |
14 | /**
15 | * 对列执行器
16 | * 按顺序执行添加的Runnable
17 | * 当需要按顺序控制任务 一个一个, 两个两个... 的执行任务时
18 | */
19 | public class Devourer {
20 | protected static final Logger log = LoggerFactory.getLogger(Devourer.class);
21 | /**
22 | * 线程池
23 | */
24 | protected final Executor exec;
25 | /**
26 | * 队列标识
27 | */
28 | protected final String key;
29 | /**
30 | * 对列中失败的最大保留个数,
31 | * 如果 大于0 执行失败, 暂时保留, 直至 排对的个数大于此值
32 | * 否则 失败丢弃
33 | * 默认: 执行失败,丢弃
34 | */
35 | protected Integer failMaxKeep = 0;
36 | /**
37 | * 流量限制锁
38 | */
39 | protected final LatchLock lock = new LatchLock();
40 | /**
41 | * 任务执行对列
42 | */
43 | protected final Deque waiting = new ConcurrentLinkedDeque<>();
44 | /**
45 | * 错误处理函数
46 | */
47 | protected BiConsumer errorHandler;
48 | /**
49 | * 暂停执行条件
50 | */
51 | protected Predicate pauseCondition;
52 | /**
53 | * 使用最后入队想任务/任务最后有效 {@link #useLast(boolean)}
54 | */
55 | protected boolean useLast;
56 | /**
57 | * 速度限制
58 | * 平均每个占用执行时间
59 | */
60 | protected Long perSpend;
61 |
62 |
63 | /**
64 | * 创建对列
65 | * @param key 对列标识
66 | * @param exec 线程池
67 | */
68 | public Devourer(String key, Executor exec) {
69 | this.key = (key == null || key.isEmpty()) ? Devourer.class.getSimpleName() + "@" + Integer.toHexString(hashCode()) : key;
70 | this.exec = exec == null ? new ThreadPoolExecutor(2, 4, 6L, TimeUnit.HOURS,
71 | new LinkedBlockingQueue(100000) {
72 | boolean threshold() { // 让线程池创建(除核心线程外)新的线程的临界条件
73 | int size = super.size();
74 | // 这个 size > 1: 有时 添加任务后 size==1, 但还没被poll, 但线程还有空闲,就是取size的值有时稍快于线程poll任务
75 | if (size <= 1) return false;
76 | ThreadPoolExecutor e = ((ThreadPoolExecutor) Devourer.this.exec);
77 | if (lock.limit <= e.getCorePoolSize()) return false;
78 | int ps = e.getPoolSize();
79 | // 不超过最大线程数
80 | if (ps >= e.getMaximumPoolSize()) return false;
81 | // 所有线程都在忙并且有超过一定比例的当前忙的线程的任务在等待, 非关闭状态
82 | return size >= (int) (ps * 0.5);
83 | }
84 | @Override
85 | public boolean offer(Runnable r) { return !threshold() && super.offer(r); }
86 | },
87 | new ThreadFactory() {
88 | final AtomicInteger i = new AtomicInteger();
89 | @Override
90 | public Thread newThread(Runnable r) { return new Thread(r, Devourer.this.key + "-" + i.incrementAndGet()); }
91 | }) : exec;
92 | }
93 |
94 | /**
95 | * 创建对列
96 | * @param key 对列标识
97 | */
98 | public Devourer(String key) { this(key, null); }
99 |
100 | public Devourer() { this(null, null); }
101 |
102 |
103 | /**
104 | * 任务入对列
105 | * @param fn 任务函数
106 | * @return {@link Devourer}
107 | */
108 | public Devourer offer(Runnable fn) {
109 | if (fn == null) return this;
110 | if (useLast) waiting.clear();
111 | waiting.offer(fn);
112 | trigger();
113 | return this;
114 | }
115 |
116 |
117 | /**
118 | * 不断的从 {@link #waiting} 对列中取出执行
119 | */
120 | protected void trigger() {
121 | Predicate condition = this.pauseCondition; // 重新弄个本地变量是为了防止多线程下边可能为空的情况
122 | if (condition != null) { // 是否设置了暂停
123 | if (condition.test(this)) return;
124 | else this.pauseCondition = null;
125 | }
126 | if (waiting.isEmpty()) return;
127 | // 1.必须保证正在执行的函数不超过 parallelLimit
128 | // 2.必须保证这里waiting对列中不为空
129 | // 3.必须保证不能出现情况: waiting 对列中有值, 但没有被执行
130 | if (!lock.tryLock()) return;
131 | exec.execute(() -> {
132 | final long start = perSpend == null ? 0 : System.currentTimeMillis(); // 执行开始时间, 用于计算执行花费时长
133 | Runnable task = null;
134 | try {
135 | task = waiting.poll();
136 | if (task != null) task.run(); // 5个并发添加5个任务, 锁限制3, 第一次执行完3个任务, 第2次再同时获取3个锁, 但任务只有2个, 所以有可能poll出来为空
137 | } catch (Throwable ex) {
138 | // 不用担心顺序, 因为如果并发为1, 一定是顺序的; 如果并发大于1, 执行顺序就不一定顺序了
139 | if (task != null && failMaxKeep != null && failMaxKeep > 0 && (getWaitingCount() < failMaxKeep)) waiting.addFirst(task);
140 | if (errorHandler != null) {
141 | try {
142 | errorHandler.accept(ex, this);
143 | } catch (Throwable exx) {
144 | log.error(key + " errorHandler error", exx);
145 | }
146 | } else {
147 | log.error(key, ex);
148 | }
149 | } finally {
150 | // 速度限制
151 | if (perSpend != null && perSpend > 0) {
152 | // 剩余时长 = 应该花费时长 - 已花费时长
153 | long left = perSpend - (System.currentTimeMillis() - start);
154 | if (left > 1) {
155 | try {
156 | Thread.sleep(left - 1);
157 | } catch (InterruptedException e) {
158 | log.error(key + " speed sleep error", e);
159 | }
160 | }
161 | }
162 | lock.release();
163 | if (!waiting.isEmpty()) trigger(); // 持续不断执行对列中的任务
164 | }
165 | });
166 | }
167 |
168 |
169 | /**
170 | * 设置并发数
171 | * @param parallel >=1
172 | * @return {@link Devourer}
173 | */
174 | public Devourer parallel(int parallel) {
175 | if (parallel < 1) throw new IllegalArgumentException("Param parallel >= 1");
176 | lock.limit(parallel);
177 | return this;
178 | }
179 |
180 |
181 | /**
182 | * 速度限制
183 | * 线程会按一定频率sleep
184 | * @param speed /s, /m, /h, /d; null: 不阻速
185 | * @return {@link Devourer}
186 | */
187 | public Devourer speed(String speed) {
188 | if (speed == null) {
189 | perSpend = null;
190 | return this;
191 | }
192 | String[] arr = speed.split("/");
193 | // 速度大小
194 | final int limit = Integer.valueOf(arr[0].trim());
195 | if (limit < 1) throw new IllegalArgumentException("speed must > 0");
196 | // 速度单位
197 | final String unit = arr[1].trim().toLowerCase();
198 | if (!Arrays.asList("s", "m", "h", "d").contains(unit)) {
199 | throw new IllegalArgumentException("speed format 10/s, 10/m, 10/h, 10/d");
200 | }
201 | // 单位时间长
202 | long unitDuration = 0;
203 | if ("s".equals(unit)) unitDuration = 1000;
204 | else if ("m".equals(unit)) unitDuration = 1000 * 60;
205 | else if ("h".equals(unit)) unitDuration = 1000 * 60 * 60;
206 | else if ("d".equals(unit)) unitDuration = 1000 * 60 * 60 * 24;
207 | perSpend = unitDuration / limit;
208 | return this;
209 | }
210 |
211 |
212 | /**
213 | * 排对个数
214 | * @return 排对个数
215 | */
216 | public int getWaitingCount() { return waiting.size(); }
217 |
218 |
219 | /**
220 | * 错误处理
221 | * @param handler 错误处理器
222 | * @return {@link Devourer}
223 | */
224 | public Devourer errorHandle(BiConsumer handler) {
225 | this.errorHandler = handler;
226 | return this;
227 | }
228 |
229 |
230 | /**
231 | * 执行失败时, 保留最大个数
232 | * NOTE: 失败的任务会不断的重试执行, 直到成功或者对列中的个数大于此值被删除
233 | * 典型应用: 数据上报场景
234 | * @return {@link Devourer}
235 | */
236 | public Devourer failMaxKeep(Integer maxKeep) { this.failMaxKeep = maxKeep; return this; }
237 |
238 |
239 | /**
240 | * 暂停一段时间
241 | * NOTE 继续执行条件: 必须有新的任务入对, 或者手动调用 {@link #resume()}
242 | * @param duration 一段时间
243 | * @return {@link Devourer}
244 | */
245 | public Devourer suspend(Duration duration) {
246 | pauseCondition = new Predicate() {
247 | final Pause pause = new Pause(duration);
248 | @Override
249 | public boolean test(Devourer devourer) { return !pause.isTimeout(); }
250 | };
251 | return this;
252 | }
253 |
254 |
255 | /**
256 | * 设置暂停条件
257 | * 使用 {@link #resume()} 恢复
258 | * @param pauseCondition 暂停条件
259 | * @return {@link Devourer}
260 | */
261 | public Devourer suspend(Predicate pauseCondition) {
262 | this.pauseCondition = pauseCondition;
263 | return this;
264 | }
265 |
266 |
267 | /**
268 | * 手动恢复执行
269 | * @return {@link Devourer}
270 | */
271 | public Devourer resume() { pauseCondition = null; trigger(); return this; }
272 |
273 |
274 | /**
275 | * 是否只使用队列最后一个, 清除队列前面的任务
276 | * 适合: 入队的频率比出队高, 前面的任务可有可无
277 | */
278 | public Devourer useLast(boolean useLast) { this.useLast = useLast; return this; }
279 |
280 |
281 | /**
282 | * 是否使用了 userLast 特性
283 | */
284 | public boolean isUseLast() { return useLast; }
285 |
286 |
287 | /**
288 | * 是否是暂停状态
289 | * @return true: 暂停中
290 | */
291 | public boolean isSuspended() {
292 | try {
293 | if (pauseCondition != null && pauseCondition.test(this)) return true;
294 | } catch (NullPointerException npt) { /** trigger方法并发有可能把pause置null **/ }
295 | return false;
296 | }
297 |
298 |
299 | /**
300 | * 正在执行的任务数
301 | */
302 | public int getParallel() { return lock.getLatchSize(); }
303 |
304 |
305 | /**
306 | * 关闭
307 | */
308 | public void shutdown() {
309 | waiting.clear();
310 | if (exec instanceof ExecutorService) ((ExecutorService) exec).shutdown();
311 | }
312 |
313 |
314 | @Override
315 | public String toString() {
316 | return key + "{parallel: " + getParallel() + ", waitingCount: " + getWaitingCount() + ", suspended: "+ isSuspended() + ", useLast: " + isUseLast() +"}";
317 | }
318 | }
319 |
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/Utils.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app;
2 |
3 | import cn.xnatural.app.util.Copier;
4 | import cn.xnatural.app.util.Httper;
5 | import cn.xnatural.app.util.Tailer;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import java.io.*;
10 | import java.lang.management.ManagementFactory;
11 | import java.lang.reflect.Field;
12 | import java.lang.reflect.Method;
13 | import java.math.BigDecimal;
14 | import java.math.BigInteger;
15 | import java.net.*;
16 | import java.security.MessageDigest;
17 | import java.security.NoSuchAlgorithmException;
18 | import java.security.SecureRandom;
19 | import java.util.*;
20 | import java.util.function.Consumer;
21 | import java.util.stream.Collectors;
22 |
23 | /**
24 | * 常用工具集
25 | */
26 | public class Utils {
27 | protected static final Logger log = LoggerFactory.getLogger(Utils.class);
28 | /**
29 | * 得到jvm进程号
30 | * @return pid
31 | */
32 | public static String pid() { return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; }
33 |
34 |
35 | /**
36 | * 判断系统是否为 linux 系统
37 | * 判断方法来源 io.netty.channel.epoll.Native#loadNativeLibrary()
38 | * @return true: linux
39 | */
40 | public static boolean isLinux() {
41 | return System.getProperty("os.name").toLowerCase(Locale.UK).trim().startsWith("linux");
42 | }
43 |
44 |
45 | /**
46 | * 项目根目录下边找目录或文件
47 | * @param child 项目根目录下的子目录/文件
48 | */
49 | public static File baseDir(String child) {
50 | File p = new File(System.getProperty("user.dir"));
51 | if (child != null) {return new File(p, child);}
52 | return p;
53 | }
54 |
55 |
56 | /**
57 | * 本机ipv4地址
58 | */
59 | public static String ipv4() {
60 | try {
61 | for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
62 | NetworkInterface current = en.nextElement();
63 | if (!current.isUp() || current.isLoopback() || current.isVirtual()) continue;
64 | Enumeration addresses = current.getInetAddresses();
65 | while (addresses.hasMoreElements()) {
66 | InetAddress addr = addresses.nextElement();
67 | if (addr.isLoopbackAddress()) continue;
68 | if (addr instanceof Inet4Address) {
69 | return addr.getHostAddress();
70 | }
71 | }
72 | }
73 | } catch (SocketException e) {
74 | throw new RuntimeException(e);
75 | }
76 | return null;
77 | }
78 |
79 |
80 | /**
81 | * sha1 加密
82 | * @param bs 被加密码byte[]
83 | * @return 加密后 byte[]
84 | */
85 | public static byte[] sha1(byte[] bs) {
86 | try {
87 | MessageDigest digest = MessageDigest.getInstance("SHA-1");
88 | digest.update(bs);
89 | return digest.digest();
90 | } catch (NoSuchAlgorithmException e) {
91 | throw new RuntimeException(e);
92 | }
93 | }
94 |
95 |
96 | /**
97 | * md5 hex
98 | * @param bs 被加密的byte[]
99 | * @return md5加密后的字符串
100 | */
101 | public static String md5Hex(byte[] bs) {
102 | try {
103 | byte[] secretBytes = MessageDigest.getInstance("md5").digest(bs);
104 | String md5code = new BigInteger(1, secretBytes).toString(16);
105 | for (int i = 0; i < 32 - md5code.length(); i++) {
106 | md5code = "0" + md5code;
107 | }
108 | return md5code;
109 | } catch (NoSuchAlgorithmException e) {
110 | throw new RuntimeException(e);
111 | }
112 | }
113 |
114 |
115 | public static final char[] CS = new char[]{'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'};
116 | public static final SecureRandom SR = new SecureRandom();
117 |
118 | public static String nanoId() { return nanoId(21); }
119 |
120 | /**
121 | * nano id 生成
122 | * @param length 生成的长度
123 | */
124 | public static String nanoId(int length) {
125 | if (length < 1) throw new IllegalArgumentException("Param length must >= 1");
126 | final int mask = (2 << (int)Math.floor(Math.log(CS.length - 1) / Math.log(2))) - 1;
127 | final int step = (int)Math.ceil(1.6 * mask * length / CS.length);
128 | final StringBuilder sb = new StringBuilder();
129 | while(true) {
130 | byte[] bytes = new byte[step];
131 | SR.nextBytes(bytes);
132 | for(int i = 0; i < step; ++i) {
133 | int alphabetIndex = bytes[i] & mask;
134 | if (alphabetIndex < CS.length) {
135 | sb.append(CS[alphabetIndex]);
136 | if (sb.length() == length) {
137 | return sb.toString();
138 | }
139 | }
140 | }
141 | }
142 | }
143 |
144 |
145 | /**
146 | * 类型转换
147 | * @param v 值
148 | * @param type 转换的类型
149 | * @return 转换后的结果
150 | */
151 | public static T to(Object v, Class type) {
152 | if (type == null) return (T) v;
153 | if (v == null) {
154 | if (boolean.class.equals(type)) return (T) Boolean.FALSE;
155 | if (short.class.equals(type)) return (T) Short.valueOf("0");
156 | if (byte.class.equals(type)) return (T) Byte.valueOf("0");
157 | if (int.class.equals(type)) return (T) Integer.valueOf(0);
158 | if (long.class.equals(type)) return (T) Long.valueOf(0);
159 | if (double.class.equals(type)) return (T) Double.valueOf(0);
160 | if (float.class.equals(type)) return (T) Float.valueOf(0);
161 | if (char.class.equals(type)) return (T) Character.valueOf('\u0000');
162 | return null;
163 | }
164 | else if (type.isAssignableFrom(v.getClass())) return (T) v;
165 | else if (String.class.equals(type)) return (T) v.toString();
166 | else if (Boolean.class.equals(type) || boolean.class.equals(type)) return (T) Boolean.valueOf(v.toString());
167 | else if (Short.class.equals(type) || short.class.equals(type)) return (T) Short.valueOf(v.toString());
168 | else if (Byte.class.equals(type) || byte.class.equals(type)) return (T) Byte.valueOf(v.toString());
169 | else if (Integer.class.equals(type) || int.class.equals(type)) return (T) Integer.valueOf(v.toString());
170 | else if (BigInteger.class.equals(type)) return (T) new BigInteger(v.toString());
171 | else if (Long.class.equals(type) || long.class.equals(type)) return (T) Long.valueOf(v.toString());
172 | else if (Double.class.equals(type) || double.class.equals(type)) return (T) Double.valueOf(v.toString());
173 | else if (Float.class.equals(type) || float.class.equals(type)) return (T) Float.valueOf(v.toString());
174 | else if (BigDecimal.class.equals(type)) return (T) new BigDecimal(v.toString());
175 | else if (URI.class.equals(type)) return (T) URI.create(v.toString());
176 | else if (type.isEnum()) return Arrays.stream(type.getEnumConstants()).filter((o) -> v.equals(((Enum) o).name())).findFirst().orElse(null);
177 | return (T) v;
178 | }
179 |
180 |
181 | /**
182 | * 遍历所有方法并处理
183 | * @param clz Class
184 | * @param fn 函数
185 | */
186 | public static void iterateMethod(final Class clz, Consumer fn) {
187 | if (fn == null) return;
188 | Class c = clz;
189 | do {
190 | for (Method m : c.getDeclaredMethods()) fn.accept(m);
191 | c = c.getSuperclass();
192 | } while (c != null);
193 | }
194 |
195 |
196 | /**
197 | * 查找字段
198 | * @param clz Class
199 | * @param fn 函数
200 | */
201 | public static void iterateField(final Class clz, Consumer fn) {
202 | if (fn == null) return;
203 | Class c = clz;
204 | do {
205 | for (Field f : c.getDeclaredFields()) fn.accept(f);
206 | c = c.getSuperclass();
207 | } while (c != null);
208 | }
209 |
210 |
211 | /**
212 | * 构建一个 http 请求, 支持 get, post. 文件上传.
213 | * @return {@link Httper}
214 | */
215 | public static Httper http() { return new Httper(); }
216 |
217 |
218 | /**
219 | * 把查询参数添加到 url 后边
220 | * @param urlStr url
221 | * @param params 参数
222 | * @return 完整url
223 | */
224 | public static String buildUrl(String urlStr, Map params) {
225 | if (params == null || params.isEmpty()) return urlStr;
226 | String queryStr = params.entrySet().stream()
227 | .map(e -> {
228 | try {
229 | return e.getKey() + "=" + URLEncoder.encode(e.getValue().toString(), "utf-8");
230 | } catch (UnsupportedEncodingException ex) {
231 | throw new RuntimeException(ex);
232 | }
233 | })
234 | .collect(Collectors.joining("&"));
235 | if (urlStr.endsWith("?") || urlStr.endsWith("&")) urlStr += queryStr;
236 | else if (urlStr.contains("?")) urlStr += "&" + queryStr;
237 | else urlStr += "?" + queryStr;
238 | return urlStr;
239 | }
240 |
241 |
242 | /**
243 | * 文件内容监控器(类linux tail)
244 | * @return {@link Tailer}
245 | */
246 | public static Tailer tailer() { return new Tailer(); }
247 |
248 |
249 | /**
250 | * 对象 复制器
251 | * @param src 源对象
252 | * @param target 目标对象
253 | * @param 源对象类型
254 | * @param 目标对象类型
255 | * @return {@link Copier}
256 | */
257 | public static Copier copier(S src, T target) { return new Copier(src, target); }
258 |
259 |
260 | /**
261 | * io 流 copy
262 | *
263 | * try (InputStream is = new FileInputStream("d:/tmp/1.txt"); OutputStream os = new FileOutputStream("d:/tmp/2.txt")) {
264 | * Utils.ioCopy(is, os);
265 | * }
266 | *
267 | * @param is 输入流
268 | * @param os 输出流
269 | * @param bufSize 每次读取字节大小
270 | * @return 总复制大小
271 | * @throws IOException {@link OutputStream#write(byte[], int, int)}
272 | */
273 | public static long ioCopy(InputStream is, OutputStream os, Integer bufSize) throws IOException {
274 | byte[] buf = new byte[bufSize == null || bufSize < 1 ? 1024 : bufSize];
275 | long count = 0;
276 | int n = 0;
277 | while (-1 != (n = is.read(buf))) {
278 | os.write(buf, 0, n);
279 | count += n;
280 | }
281 | return count;
282 | }
283 |
284 | /**
285 | * io 流 copy
286 | * @param is 输入流
287 | * @param os 输出流
288 | * @return 总复制大小
289 | * @throws IOException {@link OutputStream#write(byte[], int, int)}
290 | */
291 | public static long ioCopy(InputStream is, OutputStream os) throws IOException {return ioCopy(is, os, 4096);}
292 | }
293 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | cn.xnatural
8 | tiny
9 | jar
10 | 1.1.9
11 |
12 | tiny
13 | 小巧的java应用微内核并发框架
14 | https://gitee.com/xnat/tiny
15 |
16 |
17 |
18 | cn.xnatural
19 | enet
20 | 1.1.0
21 |
22 |
23 | ch.qos.logback
24 | logback-classic
25 | 1.2.11
26 |
27 |
28 |
29 |
30 | com.alibaba
31 | druid
32 | 1.2.11
33 | test
34 |
35 |
36 | org.junit.jupiter
37 | junit-jupiter-api
38 | 5.9.0
39 | test
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | cn.xnatural
49 | http
50 | 1.1.5
51 | test
52 |
53 |
54 | cn.xnatural
55 | sched
56 | 1.0.3
57 | test
58 |
59 |
60 | cn.xnatural
61 | remoter
62 | 1.1.1
63 | test
64 |
65 |
66 | mysql
67 | mysql-connector-java
68 | 8.0.29
69 | test
70 |
71 |
72 | com.h2database
73 | h2
74 | 2.1.212
75 | test
76 |
77 |
78 |
79 |
80 |
81 | The Apache Software License, Version 2.0
82 | http://www.apache.org/licenses/LICENSE-2.0.txt
83 | repo
84 |
85 |
86 |
87 |
88 | https://gitee.com/xnat/tiny
89 | https://gitee.com/xnat/tiny.git
90 | https://gitee.com/xnat/tiny
91 |
92 |
93 |
94 |
95 | hubert
96 | dlr
97 | xnatural@msn.cn
98 | xnatural
99 |
100 | manager
101 |
102 |
103 |
104 |
105 |
106 | hubert
107 |
108 |
109 |
110 |
111 |
112 | UTF-8
113 | UTF-8
114 | 1.8
115 | 1.8
116 |
117 |
118 |
119 |
120 | aliyun
121 | https://maven.aliyun.com/repository/public/
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 | org.apache.maven.plugins
180 | maven-source-plugin
181 | 3.0.1
182 |
183 |
184 | attach-sources
185 |
186 | jar
187 |
188 |
189 |
190 |
191 |
192 | org.apache.maven.plugins
193 | maven-scm-publish-plugin
194 | 3.0.0
195 |
196 |
197 | javadocs
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 | release
207 |
208 |
209 |
210 |
211 | org.apache.maven.plugins
212 | maven-source-plugin
213 | 3.0.1
214 |
215 |
216 | package
217 |
218 | jar-no-fork
219 |
220 |
221 |
222 |
223 |
224 |
225 | org.apache.maven.plugins
226 | maven-javadoc-plugin
227 | 3.1.0
228 |
229 |
230 | package
231 |
232 | jar
233 |
234 |
235 |
236 |
237 | false
238 | none
239 |
240 |
241 |
242 |
243 | org.apache.maven.plugins
244 | maven-gpg-plugin
245 | 1.5
246 |
247 |
248 | verify
249 |
250 | sign
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 | oss
260 | https://oss.sonatype.org/content/repositories/snapshots/
261 |
262 |
263 | oss
264 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
265 |
266 |
267 |
268 |
269 |
270 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/main/java/cn/xnatural/app/util/Copier.java:
--------------------------------------------------------------------------------
1 | package cn.xnatural.app.util;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.Method;
5 | import java.lang.reflect.Modifier;
6 | import java.util.*;
7 | import java.util.function.*;
8 |
9 | import static cn.xnatural.app.Utils.iterateField;
10 | import static cn.xnatural.app.Utils.iterateMethod;
11 |
12 | /**
13 | * 对象拷贝工具
14 | * 支持:
15 | * 对象 转 map
16 | * 对象 转 javabean
17 | * @param 源对象类型
18 | * @param 目标对象类型
19 | */
20 | public class Copier {
21 | /**
22 | * 源对象
23 | */
24 | protected final S src;
25 | /**
26 | * 目标对象
27 | */
28 | protected final T target;
29 | /**
30 | * 忽略空属性
31 | * 默认不忽略空值属性
32 | */
33 | protected boolean ignoreNull = false;
34 | /**
35 | * 客外属性源 计算器
36 | */
37 | protected Map> valueGetter;
38 | /**
39 | * 属性值转换器配置
40 | * 源属性名 -> 值转换函数
41 | */
42 | protected Map valueConverter;
43 | /**
44 | * 属性别名
45 | * 目标属性名 -> 源属性名
46 | */
47 | protected Map mapProps;
48 | /**
49 | * 忽略属性
50 | */
51 | protected final Set ignore = new HashSet<>(Arrays.asList("class"));
52 |
53 |
54 | public Copier(S src, T target) {
55 | this.src = src;
56 | this.target = target;
57 | }
58 |
59 |
60 | /**
61 | * 忽略属性
62 | * @param propNames 属性名
63 | * @return {@link Copier}
64 | */
65 | public Copier ignore(String... propNames) {
66 | if (propNames == null) return this;
67 | for (String name : propNames) ignore.add(name);
68 | return this;
69 | }
70 |
71 | /**
72 | * 包含 class 属性
73 | * @return {@link Copier}
74 | */
75 | public Copier showClassProp() { ignore.remove("class"); return this; }
76 |
77 | /**
78 | * 属性名 映射
79 | * {@link #ignore} 也能控制别名
80 | * @param srcPropName 源属性名
81 | * @param targetPropName 目标对象属性名
82 | * @return {@link Copier}
83 | */
84 | public Copier mapProp(String srcPropName, String targetPropName) {
85 | if (targetPropName == null || targetPropName.isEmpty()) throw new IllegalArgumentException("Param targetPropName required");
86 | if (srcPropName == null || srcPropName.isEmpty()) throw new IllegalArgumentException("Param srcPropName required");
87 | if (mapProps == null) mapProps = new HashMap<>(7);
88 | mapProps.put(targetPropName, srcPropName);
89 | mapProps.put(srcPropName, targetPropName);
90 | return this;
91 | }
92 |
93 | /**
94 | * 忽略空值属性
95 | * @param ignoreNull 是否忽略
96 | * @return {@link Copier}
97 | */
98 | public Copier ignoreNull(boolean ignoreNull) { this.ignoreNull = ignoreNull; return this; }
99 |
100 | /**
101 | * 手动添加额外属性源
102 | * NOTE: 会优先于 源对象中的属性
103 | * @param srcPropName 属性源名
104 | * @param valuer 属性值计算器
105 | * @return {@link Copier}
106 | */
107 | public Copier add(String srcPropName, Supplier