diff --git a/Milkomeda/pom.xml b/Milkomeda/pom.xml old mode 100755 new mode 100644 index ac0eaab3..c44518c9 --- a/Milkomeda/pom.xml +++ b/Milkomeda/pom.xml @@ -11,16 +11,20 @@ 1.8 - 1.5.32 - 3.14.1-SNAPSHOT - 2.6.12 - 2021.0.4 - 2.2.2 + 1.6.21 + 3.15.0-SNAPSHOT + 2.7.6 + 2021.0.5 + 3.0.0 3.5.2 - 3.17.7 + 3.18.0 + 0.7.5 + 3.4.4 4.5 0.9.1 + 1.5 1.9.4 + 1.15.3 3.3.4 @@ -69,7 +73,7 @@ sonatype-oss-release - 3.14.1 + 3.15.0 @@ -112,18 +116,24 @@ org.springframework.boot spring-boot-starter-aop + + + org.yaml + snakeyaml + + org.springframework.boot spring-boot-starter-web true - + org.springframework.boot spring-boot-starter-validation - + org.apache.commons commons-pool2 + + org.jsoup + jsoup + ${jsoup.version} + ognl ognl diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomConfig.java index a94f6ccd..e4f6a242 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomConfig.java @@ -21,7 +21,7 @@ package com.github.yizzuide.milkomeda.atom; -import org.springframework.context.annotation.Bean; +import com.github.yizzuide.milkomeda.orbit.OrbitConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -30,16 +30,11 @@ * * @author yizzuide * @since 3.3.0 + * @version 3.15.0 *
* Create at 2020/04/30 15:13 */ @Configuration -@Import({RedisAtomConfig.class, ZkAtomConfig.class}) +@Import({EtcdAtomConfig.class, RedisAtomConfig.class, ZkAtomConfig.class, OrbitConfig.class}) public class AtomConfig { - - @Bean - public AtomLockAspect atomLockAspect() { - return new AtomLockAspect(); - } - } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomLock.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomLock.java index 044df5cb..345ee432 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomLock.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomLock.java @@ -51,13 +51,13 @@ long waitTime() default -1; /** - * 自动释放锁时间ms(ZK不需要这个特性) + * 自动释放锁时间ms(ZK不需要这个特性,断开就删除节点) * @return -1不自动释放锁 */ long leaseTime() default 60000; /** - * 加锁类型(ZK仅支持公平锁、读写锁) + * 加锁类型(ZK仅支持公平锁、读写锁,ETCD不支持设置) * @return AtomLockType */ AtomLockType type() default AtomLockType.FAIR; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomLockWaitTimeoutException.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomLockWaitTimeoutException.java index c38c53dd..7a3f22a7 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomLockWaitTimeoutException.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomLockWaitTimeoutException.java @@ -22,9 +22,9 @@ package com.github.yizzuide.milkomeda.atom; /** - * AtomLockWaitTimeoutException - * 获取锁超时异常 + * Lock wait timeout exception. * + * @since 3.13.0 * @author yizzuide *
* Create at 2020/05/01 02:19 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomProperties.java index 3b8e33bb..c1280993 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomProperties.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomProperties.java @@ -27,6 +27,7 @@ import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.List; /** * AtomProperties @@ -37,8 +38,11 @@ * Create at 2020/04/30 15:26 */ @Data -@ConfigurationProperties("milkomeda.atom") +@ConfigurationProperties(AtomProperties.PREFIX) public class AtomProperties { + + public static final String PREFIX = "milkomeda.atom"; + /** * 策略方式 */ @@ -54,6 +58,63 @@ public class AtomProperties { */ private Zk zk = new Zk(); + /** + * Etcd config. + * @since 3.15.0 + */ + private Etcd etcd = new Etcd(); + + @Data + static class Etcd { + /** + * The URL is used to Connect from ETCD server. + */ + private String endpointUrl; + + /** + * The URL is used to Connect from ETCD servers. + */ + private List endpointUrls; + + /** + * config etcd auth user. + */ + private String user; + + /** + * Etcd auth password. + */ + private String password; + + /** + * Sets the authority used to authenticate connections to servers. + */ + private String authority; + + /** + * Etcd root lock key. + */ + private String rootLockNode; + + /** + * Etcd connect timeout. + */ + @DurationUnit(ChronoUnit.MILLIS) + private Duration connectTimeout = Duration.ofMillis(30000); + + /** + * Set the interval for gRPC keepalive time. + */ + @DurationUnit(ChronoUnit.MILLIS) + private Duration keepaliveTime = Duration.ofMillis(30000); + + /** + * Set timeout for gRPC keepalive time. + */ + @DurationUnit(ChronoUnit.MILLIS) + private Duration keepaliveTimeout = Duration.ofMillis(10000); + } + @Data static class Redis { /** diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomStrategyType.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomStrategyType.java index 3ab53e41..0b3c0142 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomStrategyType.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomStrategyType.java @@ -22,14 +22,32 @@ package com.github.yizzuide.milkomeda.atom; /** - * AtomStrategy + * Distributed lock strategy type which can support. * * @author yizzuide * @since 3.3.0 + * @version 3.15.0 *
* Create at 2020/04/30 15:26 */ public enum AtomStrategyType { + /** + * Redis strategy which is default. + */ REDIS, - ZK + + /** + * Zookeeper strategy. + */ + ZK, + + /** + * Etcd strategy. + */ + ETCD, + + /** + * Custom strategy which need impl of {@link Atom} and register as bean. + */ + Custom } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomUnLockException.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomUnLockException.java new file mode 100644 index 00000000..6fd0068a --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomUnLockException.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.atom; + +/** + * Unlock exception. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/04/29 16:36 + */ +public class AtomUnLockException extends RuntimeException { + private static final long serialVersionUID = 1818244101533374602L; + + public AtomUnLockException(String message) { + super(message); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/EtcdAtom.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/EtcdAtom.java new file mode 100644 index 00000000..858df44d --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/EtcdAtom.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.atom; + +import com.github.yizzuide.milkomeda.light.LightContext; +import com.github.yizzuide.milkomeda.light.Spot; +import com.github.yizzuide.milkomeda.universe.extend.env.Environment; +import io.etcd.jetcd.ByteSequence; +import io.etcd.jetcd.lock.LockResponse; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Etcd distributed lock. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/04/29 01:54 + */ +@Slf4j +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") +public class EtcdAtom implements Atom { + + @Autowired + private EtcdClientInfo clientInfo; + + // Lock监控线程池调度器 + @Autowired + private ThreadPoolTaskScheduler taskScheduler; + + // 线程锁上下文 + private final LightContext lockLightContext = new LightContext<>(); + + @Override + public AtomLockInfo lock(String keyPath, Duration leaseTime, AtomLockType type, boolean readOnly) throws Exception { + return tryLock(keyPath, null, leaseTime, type, readOnly); + } + + @Override + public AtomLockInfo tryLock(String keyPath, AtomLockType type, boolean readOnly) throws Exception { + Spot lockDataSpot = lockLightContext.get(); + LockData existsLockData = lockDataSpot == null ? null : lockDataSpot.getData(); + if (existsLockData != null && existsLockData.isLockSuccess()) { + int lockCount = existsLockData.getLockCount().incrementAndGet(); + if (lockCount < 0) { + throw new Error("Maximum lock count exceeded for ETCD locks"); + } + return AtomLockInfo.builder().isLocked(true).lock(existsLockData).build(); + } + return AtomLockInfo.builder().isLocked(false).lock(null).build(); + } + + @Override + public AtomLockInfo tryLock(String keyPath, Duration waitTime, Duration leaseTime, AtomLockType type, boolean readOnly) throws Exception { + String lockKey = clientInfo.getRootLockNode() + "/" + keyPath; + // 租约时间 + long leaseTTL = leaseTime.getSeconds(); + try { + // 租约Id + long leaseId = clientInfo.getLeaseClient().grant(leaseTTL).get().getID(); + // 续租心跳周期 + long period = leaseTTL - leaseTTL / 5; + // 启动续约监控 + // Etcd分布式锁的有效时间是租约的有效时间,在等待获取锁的过程中可能租约会过期,所以得在获取租约后就得开启守护线程 + ScheduledFuture scheduledFuture = taskScheduler.scheduleAtFixedRate(() -> clientInfo.getLeaseClient().keepAliveOnce(leaseId), + Instant.ofEpochMilli(0L), + Duration.ofSeconds(period)); + // 加锁 + CompletableFuture future = clientInfo.getLockClient().lock(ByteSequence.from(lockKey.getBytes()), leaseId); + LockResponse lockResponse = waitTime == null ? future.get() : future.get(waitTime.toMillis(), TimeUnit.MILLISECONDS); + + String lockPath = null; + if (lockResponse != null) { + lockPath = lockResponse.getKey().toString(StandardCharsets.UTF_8); + } + Thread currentThread = Thread.currentThread(); + LockData lockData = new LockData(lockKey, currentThread); + int lockCount = lockData.getLockCount().incrementAndGet(); + lockData.setLeaseId(leaseId); + lockData.setLockedKeyPath(lockPath); + lockLightContext.set(new Spot<>(lockKey, lockData)); + lockData.setLockSuccess(true); + lockData.setScheduledFuture(scheduledFuture); + if (Environment.isShowLog()) { + log.info("thread[{}] locked on key: {}, lock count: {}", currentThread.getName(), lockData.getLockedKey(), lockCount); + } + return AtomLockInfo.builder().isLocked(true).lock(lockData).build(); + } catch (InterruptedException | ExecutionException e) { + log.error("Etcd lock error with msg: {}", e.getMessage(), e); + } catch (TimeoutException ignore) { // fast fail! + } + return AtomLockInfo.builder().isLocked(false).lock(null).build(); + } + + @Override + public void unlock(Object lock) throws Exception { + LockData lockData = (LockData) lock; + int lockCount = lockData.getLockCount().decrementAndGet(); + if (lockCount > 0) { + return; + } + if (lockCount < 0) { + throw new AtomUnLockException("Abnormal unlocking status for ETCD locks"); + } + try { + // 解锁 + String lockKey = lockData.getLockKey(); + if (lockKey != null) { + clientInfo.getLockClient().unlock(ByteSequence.from(lockKey.getBytes())).get(); + } + // 关闭续约监控 + lockData.getScheduledFuture().cancel(true); + // 删除锁 + if (lockData.getLeaseId() != 0L) { + clientInfo.getLeaseClient().revoke(lockData.getLeaseId()); + } + lockData.setLockSuccess(false); + if (Environment.isShowLog()) { + Thread currentThread = lockData.getCurrentThread(); + log.info("Thread[{}] closed lock with key: {}, lock count: {}", currentThread.getName(), lockData.getLockedKey(), lockCount); + } + } catch (InterruptedException | ExecutionException e) { + log.error("ETCD unlock error with msg: {}", e.getMessage(), e); + } finally { + // 清空线程上下文 + lockLightContext.remove(); + } + } + + @Override + public boolean isLocked(Object lock) { + LockData lockData = (LockData) lock; + return lockData.isLockSuccess(); + } + + @Data + static class LockData { + /** + * Remark lock count for support reentrant lock. + */ + private AtomicInteger lockCount = new AtomicInteger(0); + + /** + * Local thread which holds lock. + */ + private Thread currentThread; + + /** + * Etcd lease ID. + */ + private long leaseId; + + /** + * Etcd lock key. + */ + private String lockKey; + + /** + * Etcd locked the key path. + */ + private String lockedKeyPath; + + /** + * Lock status for check lock has success. + */ + private boolean lockSuccess; + + /** + * Thread pool for watch and lease lock. + */ + private ScheduledFuture scheduledFuture; + + public LockData(String lockKey, Thread currentThread) { + this.lockKey = lockKey; + this.currentThread = currentThread; + } + + public String getLockedKey() { + return lockedKeyPath == null ? lockKey : lockedKeyPath; + } + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/EtcdAtomConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/EtcdAtomConfig.java new file mode 100644 index 00000000..73221c61 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/EtcdAtomConfig.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.atom; + +import com.google.protobuf.ByteString; +import io.etcd.jetcd.ByteSequence; +import io.etcd.jetcd.Client; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.CollectionUtils; + + +/** + * Etcd config. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/04/29 01:39 + */ +@Configuration +@EnableConfigurationProperties(AtomProperties.class) +@ConditionalOnProperty(prefix = "milkomeda.atom", name = "strategy", havingValue = "ETCD") +public class EtcdAtomConfig { + + @Autowired + private AtomProperties atomProperties; + + @Bean + public Atom atom() { + return new EtcdAtom(); + } + + @Bean + public EtcdClientInfo etcdClientInfo() { + AtomProperties.Etcd etcd = atomProperties.getEtcd(); + String[] endpoints = CollectionUtils.isEmpty(etcd.getEndpointUrls()) ? new String[]{etcd.getEndpointUrl()} : + etcd.getEndpointUrls().toArray(new String[]{}); + Client client; + if (etcd.getUser() == null) { + client = Client.builder() + .endpoints(endpoints) + .connectTimeout(etcd.getConnectTimeout()) + .keepaliveTime(etcd.getKeepaliveTime()) + .keepaliveTimeout(etcd.getKeepaliveTime()) + .build(); + } else { + client = Client.builder() + .endpoints(endpoints) + .user(ByteSequence.from((ByteString.copyFromUtf8(etcd.getUser())))) + .password(ByteSequence.from((ByteString.copyFromUtf8(etcd.getPassword())))) + .authority(etcd.getAuthority()) + .connectTimeout(etcd.getConnectTimeout()) + .keepaliveTime(etcd.getKeepaliveTime()) + .keepaliveTimeout(etcd.getKeepaliveTime()) + .build(); + } + return new EtcdClientInfo(client, etcd.getRootLockNode()); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/EtcdClientInfo.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/EtcdClientInfo.java new file mode 100644 index 00000000..46bd2bd4 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/EtcdClientInfo.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.atom; + +import io.etcd.jetcd.Client; +import io.etcd.jetcd.Lease; +import io.etcd.jetcd.Lock; +import lombok.Data; + +/** + * Simple warp with Etcd client and addition info. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/04/29 01:50 + */ +@Data +public class EtcdClientInfo { + /** + * Etcd client. + */ + private Client client; + + /** + * Etcd lock client. + */ + private Lock lockClient; + + /** + * Etcd lease client. + */ + private Lease leaseClient; + + /** + * Etcd root lock key. + */ + private String rootLockNode; + + public EtcdClientInfo(Client client, String rootLockNode) { + this.client = client; + this.lockClient = client.getLockClient(); + this.leaseClient = client.getLeaseClient(); + this.rootLockNode = rootLockNode; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/ZkAtom.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/ZkAtom.java index 2aa9d32c..bb5c0e1f 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/ZkAtom.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/ZkAtom.java @@ -39,6 +39,7 @@ *
* Create at 2020/05/01 12:26 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") public class ZkAtom implements Atom { // 节点分隔符 private final static String SEPARATOR = "/"; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomLockAspect.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/orbit/AtomOrbitAdvice.java similarity index 59% rename from Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomLockAspect.java rename to Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/orbit/AtomOrbitAdvice.java index 9b041681..65cda5ad 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/AtomLockAspect.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/orbit/AtomOrbitAdvice.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 yizzuide All rights Reserved. + * Copyright (c) 2023 yizzuide All rights Reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -19,54 +19,59 @@ * SOFTWARE. */ -package com.github.yizzuide.milkomeda.atom; +package com.github.yizzuide.milkomeda.atom.orbit; +import com.github.yizzuide.milkomeda.atom.*; +import com.github.yizzuide.milkomeda.orbit.OrbitAdvice; +import com.github.yizzuide.milkomeda.orbit.OrbitInvocation; import com.github.yizzuide.milkomeda.universe.engine.el.ELContext; import com.github.yizzuide.milkomeda.util.ReflectUtil; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.annotation.Order; +import org.springframework.core.annotation.AnnotationUtils; import java.time.Duration; /** - * AtomLockAspect + * This advice listen on method which annotated {@link AtomLock} has invoked. * + * @since 3.15.0 * @author yizzuide - * @since 3.3.0 - * @version 3.7.0 *
- * Create at 2020/04/30 16:26 + * Create at 2023/01/27 19:38 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j -@Order -@Aspect -public class AtomLockAspect { +@Data +public class AtomOrbitAdvice implements OrbitAdvice { @Autowired private Atom atom; - @Around("@annotation(atomLock) && execution(public * *(..))") - public Object pointCut(ProceedingJoinPoint joinPoint, AtomLock atomLock) throws Throwable { + @Override + public Object invoke(OrbitInvocation invocation) throws Throwable { + ProceedingJoinPoint joinPoint = invocation.getPjp(); + AtomLock atomLock = AnnotationUtils.findAnnotation(invocation.getMethod(), AtomLock.class); + assert atomLock != null; String keyPath = ELContext.getValue(joinPoint, atomLock.key()); Object lock = null; boolean isLocked = false; try { - if (atomLock.waitTime() > 0) { - AtomLockInfo lockInfo = atom.tryLock(keyPath, atomLock.type(), atomLock.readOnly()); + AtomLockInfo lockInfo = this.getAtom().tryLock(keyPath, atomLock.type(), atomLock.readOnly()); + isLocked = lockInfo.isLocked(); + lock = lockInfo.getLock(); + if (!isLocked) { + lockInfo = this.getAtom().tryLock(keyPath, Duration.ofMillis(atomLock.waitTime()), Duration.ofMillis(atomLock.leaseTime()), atomLock.type(), atomLock.readOnly()); isLocked = lockInfo.isLocked(); lock = lockInfo.getLock(); - if (!isLocked) { - lockInfo = atom.tryLock(keyPath, Duration.ofMillis(atomLock.waitTime()), Duration.ofMillis(atomLock.leaseTime()), atomLock.type(), atomLock.readOnly()); - isLocked = lockInfo.isLocked(); - lock = lockInfo.getLock(); - } - if (isLocked) { - return joinPoint.proceed(); - } + } + if (isLocked) { + return joinPoint.proceed(); + } + // has set wait time? + if (atomLock.waitTime() > 0) { if (atomLock.waitTimeoutType() == AtomLockWaitTimeoutType.THROW_EXCEPTION) { throw new AtomLockWaitTimeoutException(); } else if (atomLock.waitTimeoutType() == AtomLockWaitTimeoutType.FALLBACK) { @@ -75,20 +80,15 @@ public Object pointCut(ProceedingJoinPoint joinPoint, AtomLock atomLock) throws } // here is AtomLockWaitTimeoutType.WAIT_INFINITE } - AtomLockInfo lockInfo = atom.lock(keyPath, Duration.ofMillis(atomLock.leaseTime()), atomLock.type(), atomLock.readOnly()); + lockInfo = this.getAtom().lock(keyPath, Duration.ofMillis(atomLock.leaseTime()), atomLock.type(), atomLock.readOnly()); lock = lockInfo.getLock(); isLocked = lockInfo.isLocked(); return joinPoint.proceed(); - } catch (InterruptedException e) { - log.error("Atom try lock error with msg: {}", e.getMessage(), e); } finally { // 只有加锁的线程需要解锁 - if (isLocked && lock != null && atom.isLocked(lock)) { - atom.unlock(lock); + if (isLocked && lock != null && this.getAtom().isLocked(lock)) { + this.getAtom().unlock(lock); } } - // unreachable code! - return null; } - } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/orbit/AtomOrbitSource.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/orbit/AtomOrbitSource.java new file mode 100644 index 00000000..879110db --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/atom/orbit/AtomOrbitSource.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.atom.orbit; + +import com.github.yizzuide.milkomeda.atom.AtomLock; +import com.github.yizzuide.milkomeda.atom.AtomProperties; +import com.github.yizzuide.milkomeda.orbit.AnnotationOrbitAdvisor; +import com.github.yizzuide.milkomeda.orbit.OrbitAdvisor; +import com.github.yizzuide.milkomeda.orbit.OrbitSource; +import com.github.yizzuide.milkomeda.orbit.OrbitSourceProvider; +import org.springframework.boot.context.properties.bind.BindResult; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.core.env.Environment; + +import java.util.Collections; +import java.util.List; + +/** + * Provide {@link OrbitAdvisor} for Orbit module to register advisor. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/27 19:34 + */ +@OrbitSourceProvider +public class AtomOrbitSource implements OrbitSource { + @Override + public List createAdvisors(Environment environment) { + try { + // 根据配置文件是否加载来判断当前模块是否加载 + BindResult bindResult = Binder.get(environment).bind(AtomProperties.PREFIX, AtomProperties.class); + if (bindResult == null) { + return null; + } + } catch (Exception ignore) { + return null; + } + return Collections.singletonList(AnnotationOrbitAdvisor.forMethod(AtomLock.class, "atom", AtomOrbitAdvice.class, null)); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/collector/CollectorRecorder.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/collector/CollectorRecorder.java index 2ec944e5..46c8feb4 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/collector/CollectorRecorder.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/collector/CollectorRecorder.java @@ -23,17 +23,17 @@ import com.github.yizzuide.milkomeda.comet.core.CometData; import com.github.yizzuide.milkomeda.comet.core.CometRecorder; +import com.github.yizzuide.milkomeda.comet.core.EventDrivenWebCometData; import lombok.extern.slf4j.Slf4j; import javax.servlet.http.HttpServletRequest; /** - * CollectorRecorder * 日志收集器记录器 * * @author yizzuide * @since 1.15.0 - * @version 3.0.0 + * @version 3.15.0 *
* Create at 2019/11/13 19:18 */ @@ -51,6 +51,10 @@ public CollectorRecorder(CollectorFactory collectorFactory) { @Override public void onRequest(CometData prototype, String tag, HttpServletRequest request, Object[] args) { try { + // ignore event driven comet data + if (prototype instanceof EventDrivenWebCometData) { + return; + } collectorFactory.get(tag).prepare(prototype); } catch (IllegalArgumentException e) { if (!e.getMessage().startsWith("type")) throw e; @@ -60,6 +64,10 @@ public void onRequest(CometData prototype, String tag, HttpServletRequest reques @Override public Object onReturn(CometData cometData, Object returnData) { try { + // ignore event driven comet data + if (cometData instanceof EventDrivenWebCometData) { + return returnData; + } collectorFactory.get(cometData.getTag()).onSuccess(cometData); } catch (IllegalArgumentException e) { if (!e.getMessage().startsWith("type")) throw e; @@ -70,6 +78,10 @@ public Object onReturn(CometData cometData, Object returnData) { @Override public void onThrowing(CometData cometData, Exception e) { try { + // ignore event driven comet data + if (cometData instanceof EventDrivenWebCometData) { + return; + } collectorFactory.get(cometData.getTag()).onFailure(cometData); } catch (IllegalArgumentException ex) { if (!ex.getMessage().startsWith("type")) throw ex; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/collector/CometCollectorProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/collector/CometCollectorProperties.java index 3f5ccfa3..3523a9a8 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/collector/CometCollectorProperties.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/collector/CometCollectorProperties.java @@ -67,12 +67,12 @@ public static class Tag { /** * 匹配包含的路径 */ - private List include; + private List includeUrls; /** * 排除路径 */ - private List exclude; + private List excludeUrls; /** diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/AbstractRequestInterceptor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/AbstractRequestInterceptor.java new file mode 100644 index 00000000..6cd01412 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/AbstractRequestInterceptor.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.comet.core; + +import com.github.yizzuide.milkomeda.universe.extend.web.handler.HotHttpHandlerProperty; +import com.github.yizzuide.milkomeda.universe.extend.web.handler.NamedHandler; +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.servlet.http.HttpServletRequest; + +/** + * The abstract request interceptor which provide url filter. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/01 18:35 + */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") +public abstract class AbstractRequestInterceptor implements CometRequestInterceptor { + + @Setter @Getter + private int order; + + @Getter + @Autowired + private CometProperties cometProperties; + + @Override + public String readRequest(HttpServletRequest request, String formName, String formValue, String body) { + String originalValue = formValue == null ? body : formValue; + if (originalValue == null) { + return originalValue; + } + HotHttpHandlerProperty requestInterceptor = cometProperties.getRequestInterceptors().get(handlerName()); + if (!NamedHandler.canHandle(request, requestInterceptor)) { + return originalValue; + } + return doReadRequest(request, formName, formValue, body); + } + + /** + * Hook method for read request and change parameter values. + * @param request HttpServletRequest + * @param formName form field name + * @param formValue form field value + * @param body request body + * @return parameter value + */ + protected abstract String doReadRequest(HttpServletRequest request, String formName, String formValue, String body); + + +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/AbstractResponseInterceptor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/AbstractResponseInterceptor.java new file mode 100644 index 00000000..393020c7 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/AbstractResponseInterceptor.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.comet.core; + +import com.github.yizzuide.milkomeda.universe.context.WebContext; +import com.github.yizzuide.milkomeda.universe.extend.web.handler.HotHttpHandlerProperty; +import com.github.yizzuide.milkomeda.universe.extend.web.handler.NamedHandler; +import com.github.yizzuide.milkomeda.util.JSONUtil; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.FastByteArrayOutputStream; + +import javax.servlet.http.HttpServletResponse; +import java.nio.charset.StandardCharsets; + +/** + * The abstract response interceptor which provide url filter and write the changed response. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/06 02:47 + */ +@Slf4j +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") +public abstract class AbstractResponseInterceptor implements CometResponseInterceptor { + + @Setter @Getter + private int order; + + @Getter + @Autowired + private CometProperties cometProperties; + + @Override + public boolean writeToResponse(FastByteArrayOutputStream outputStream, HttpServletResponse wrapperResponse, HttpServletResponse rawResponse, Object body) { + HotHttpHandlerProperty responseInterceptor = cometProperties.getResponseInterceptors().get(handlerName()); + if (!NamedHandler.canHandle(WebContext.getRequest(), responseInterceptor)) { + return false; + } + try { + Object result = doResponse(rawResponse, body); + if (result == null) { + return false; + } + String content = JSONUtil.serialize(result); + // reset content and length + byte[] bytes = content.getBytes(StandardCharsets.UTF_8); + wrapperResponse.resetBuffer(); + wrapperResponse.setContentLength(bytes.length); + outputStream.write(bytes); + rawResponse.setContentLength(outputStream.size()); + // write to response + outputStream.writeTo(rawResponse.getOutputStream()); + } catch (Exception e) { + log.error("Comet response interceptor error with msg: {}", e.getMessage(), e); + return false; + } + return true; + } + + protected abstract Object doResponse(HttpServletResponse response, Object body); +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/Comet.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/Comet.java index e7028063..83ee7efb 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/Comet.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/Comet.java @@ -21,18 +21,19 @@ package com.github.yizzuide.milkomeda.comet.core; +import com.github.yizzuide.milkomeda.comet.collector.CometCollectorProperties; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Comet - * 请求方法采集器 + * Provide request log, collect log which must config `comet.collector.enable` of {@link CometCollectorProperties}. * * @author yizzuide * @since 0.2.0 - * @version 1.12.0 + * @version 3.15.0 *
* Create at 2019/04/11 19:25 */ @@ -46,20 +47,13 @@ String name() default ""; /** - * 日志描述 - * @deprecated deprecated at 1.12.0,use `name`. - * @return String - */ - String description() default ""; - - /** - * 请求编码 + * 请求标识码 * @return String */ String apiCode() default ""; /** - * 请求类型 1: 前台请求(默认) 2:第三方服务器推送 + * 请求类型(1: 前台请求(默认) 2:第三方服务器推送) * @return String */ String requestType() default "1"; @@ -71,11 +65,11 @@ String tag() default ""; /** - * 设置记录数据 prototype(原型) - * 注意: + * 设置记录数据 prototype(原型)如下: + *
      * 1. CometData类型应该是一个 pojo,需要提供无参构造器
-     * 2. 原则上,一个记录数据原型对应指定一个 tag
-     *
+     * 2. 原则上,一个记录数据原型对应指定一个`tag`
+     * 
* @return WebCometData子类型 */ Class prototype() default WebCometData.class; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometAspect.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometAspect.java index c8bf6f78..fbb747b8 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometAspect.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometAspect.java @@ -26,7 +26,6 @@ import com.github.yizzuide.milkomeda.util.JSONUtil; import com.github.yizzuide.milkomeda.util.NetworkUtil; import com.github.yizzuide.milkomeda.util.ReflectUtil; -import com.github.yizzuide.milkomeda.util.Strings; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -60,10 +59,11 @@ * * @author yizzuide * @since 0.2.0 - * @version 3.0.3 + * @version 3.15.0 *
* Create at 2019/04/11 19:48 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j @Aspect @Order(-99) @@ -120,7 +120,6 @@ public Object around(ProceedingJoinPoint joinPoint) throws Throwable { HttpServletRequest request = WebContext.getRequest(); WebCometData cometData = WebCometData.createFormRequest(request, comet.prototype(), cometProperties.isEnableReadRequestBody()); cometData.setApiCode(comet.apiCode()); - cometData.setDescription(Strings.isEmpty(comet.name()) ? comet.description() : comet.name()); cometData.setRequestType(comet.requestType()); return applyAround(cometData, threadLocal, joinPoint, request, requestTime, comet.name(), comet.tag(), (returnData) -> { if (returnData.getClass() == DeferredResult.class) { @@ -165,6 +164,7 @@ private void applyAfterThrowing(Exception e, ThreadLocal threadLocal) cometData.setResponseData(null); cometData.setDuration(String.valueOf(duration)); cometData.setErrorInfo(e.getMessage()); + // TODO Upgrade: StackTraceElement使用Java 9的StackWalker StackTraceElement[] stackTrace = e.getStackTrace(); if (stackTrace.length > 0) { String errorStack = String.format("exception happened: %s \n invoke root: %s", stackTrace[0], stackTrace[stackTrace.length - 1]); @@ -192,7 +192,7 @@ private Object applyAround(CometData cometData, ThreadLocal threadLoc String host = NetworkUtil.getHost(); cometData.setHost(host); if (milkomedaProperties.isShowLog()) { - log.info("Comet:- before: {}", JSONUtil.serialize(cometData)); + log.info("method[{}] invoke before: {}", signature.getName(), JSONUtil.serialize(cometData)); } // 外部可以扩展记录自定义数据 recorder.onRequest(cometData, cometData.getTag(), request, joinPoint.getArgs()); @@ -240,7 +240,7 @@ private Object applyAround(CometData cometData, ThreadLocal threadLoc // 是否有修改返回值 returnObj = returnObj == null ? returnData : returnObj; if (milkomedaProperties.isShowLog()) { - log.info("Comet:- afterReturn: {}", JSONUtil.serialize(cometData)); + log.info("method[{}] invoke afterReturn: {}", signature.getName(), JSONUtil.serialize(cometData)); } threadLocal.remove(); return returnObj; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometConfig.java index 039cf16d..a19c7754 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometConfig.java @@ -23,40 +23,46 @@ import com.github.yizzuide.milkomeda.pulsar.PulsarConfig; import com.github.yizzuide.milkomeda.universe.config.MilkomedaProperties; +import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; +import com.github.yizzuide.milkomeda.universe.extend.web.handler.HotHttpHandlerProperty; +import com.github.yizzuide.milkomeda.universe.extend.web.handler.NamedHandler; +import com.github.yizzuide.milkomeda.universe.metadata.BeanIds; import com.github.yizzuide.milkomeda.universe.polyfill.SpringMvcPolyfill; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; +import org.springframework.lang.NonNull; +import org.springframework.util.CollectionUtils; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; +import java.util.*; /** * CometConfig * * @author yizzuide * @since 2.0.0 - * @version 3.14.0 + * @version 3.15.0 *
* Create at 2019/12/12 18:10 */ @Configuration @AutoConfigureAfter({WebMvcAutoConfiguration.class, PulsarConfig.class}) @EnableConfigurationProperties({MilkomedaProperties.class, CometProperties.class}) -public class CometConfig { +public class CometConfig implements ApplicationListener { @Autowired CometProperties cometProperties; @@ -88,18 +94,55 @@ public CometInterceptor cometInterceptor() { return new CometInterceptor(); } + @Bean + @ConditionalOnProperty(prefix = "milkomeda.comet.request-interceptors.xss", name = "enable", havingValue = "true") + public CometXssRequestInterceptor cometXssRequestInterceptor() { + return new CometXssRequestInterceptor(); + } + + @Bean + @ConditionalOnProperty(prefix = "milkomeda.comet.request-interceptors.sql-inject", name = "enable", havingValue = "true") + public CometSqlInjectRequestInterceptor cometSqlInjectRequestInterceptor() { + return new CometSqlInjectRequestInterceptor(); + } + @Bean public CometResponseBodyAdvice cometResponseBodyAdvice() { return new CometResponseBodyAdvice(); } + @Override + public void onApplicationEvent(@NonNull ApplicationStartedEvent event) { + Map requestInterceptors = cometProperties.getRequestInterceptors(); + if (!CollectionUtils.isEmpty(requestInterceptors)) { + Map requestInterceptorMap = ApplicationContextHolder.get().getBeansOfType(CometRequestInterceptor.class); + if (!CollectionUtils.isEmpty(requestInterceptorMap)) { + CometHolder.setRequestInterceptors(NamedHandler.sortedList(requestInterceptorMap, requestInterceptors::get)); + } + } + + Map responseInterceptors = cometProperties.getResponseInterceptors(); + if (CollectionUtils.isEmpty(responseInterceptors)) { + return; + } + Map responseInterceptorMap = ApplicationContextHolder.get().getBeansOfType(CometResponseInterceptor.class); + if (CollectionUtils.isEmpty(responseInterceptorMap)) { + return; + } + CometHolder.setResponseInterceptors(NamedHandler.sortedList(responseInterceptorMap, responseInterceptors::get)); + } + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Configuration static class ExtendedConfig implements InitializingBean { + @Autowired private RequestMappingHandlerAdapter adapter; - @Qualifier("requestMappingHandlerMapping") + // Springboot 2.7: Since Spring Framework 5.1, Spring MVC has supported multiple RequestMappingHandlerMapping beans. + // Spring Boot 2.7 no longer defines MVC’s main requestMappingHandlerMapping bean as @Primary. + @Qualifier(BeanIds.REQUEST_MAPPING_HANDLER_MAPPING) @Autowired private RequestMappingHandlerMapping requestMappingHandlerMapping; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometData.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometData.java index 9aa4a5cb..9ab5e1b5 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometData.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometData.java @@ -34,7 +34,7 @@ * * @author yizzuide * @since 0.2.0 - * @version 3.0.5 + * @version 3.15.0 *
* Create at 2019/09/21 00:48 */ @@ -42,72 +42,83 @@ @ToString(exclude = {"attachment", "request", "intentData", "failure"}) public class CometData implements Serializable { private static final long serialVersionUID = -8296355140769902642L; + /** * 日志记录名 */ private String name; + /** - * 日志描述 - * @deprecated deprecated at 1.12.0,use name. - */ - private String description; - /** - * 记录数据 prototype(原型)的相应tag,用于请求分类,用于收集不同的类型数据 + * 记录数据 prototype(原型)的相应tag */ private String tag; + /** * 服务器地址 */ private String host; + /** * 微服务名 */ private String microName; + /** * 类名 */ private String clazzName; + /** * 执行的方法 */ private String execMethod; + /** * 请求时间 */ private Date requestTime; + /** * 方法参数 */ private String requestData; + /** * 响应时间 */ private Date responseTime; + /** * 处理耗时 */ private String duration; + /** * 响应数据 */ private String responseData; + /** * 状态 */ private String status; + /** * 错误信息 */ private String errorInfo; + /** * 栈信息 */ private String traceStack; + /** * 跟踪附件,用于设置日志记录实体 */ @JsonIgnore private transient Object attachment; + /** * 请求对象 */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometHolder.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometHolder.java index e28c2c7e..6a1e3892 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometHolder.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometHolder.java @@ -24,12 +24,14 @@ import com.github.yizzuide.milkomeda.comet.collector.CometCollectorProperties; import com.github.yizzuide.milkomeda.comet.logger.CometLoggerProperties; +import java.util.List; + /** * CometHolder * * @author yizzuide * @since 3.0.0 - * @version 3.11.0 + * @version 3.15.0 *
* Create at 2020/03/28 12:42 */ @@ -40,6 +42,10 @@ public class CometHolder { private static CometCollectorProperties collectorProps; + private static List requestInterceptors; + + private static List responseInterceptors; + static void setProps(CometProperties props) { CometHolder.props = props; } @@ -64,6 +70,22 @@ static CometLoggerProperties getLogProps() { return logProps; } + static void setRequestInterceptors(List requestInterceptors) { + CometHolder.requestInterceptors = requestInterceptors; + } + + static List getRequestInterceptors() { + return requestInterceptors; + } + + static void setResponseInterceptors(List responseInterceptors) { + CometHolder.responseInterceptors = responseInterceptors; + } + + static List getResponseInterceptors() { + return responseInterceptors; + } + /** * 是否需要包装请求 * @return 请求数据是否要被缓存起来采集 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometInterceptor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometInterceptor.java index 925a5258..c5f39967 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometInterceptor.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometInterceptor.java @@ -68,6 +68,7 @@ *
* Create at 2020/03/28 01:08 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j public class CometInterceptor implements AsyncHandlerInterceptor, ApplicationContextAware { @@ -307,12 +308,12 @@ private void collectPreLog(HttpServletRequest request) { String selectTag = null; Map tagMap = cometCollectorProperties.getTags(); for (Map.Entry tag : tagMap.entrySet()) { - if (!CollectionUtils.isEmpty(tag.getValue().getExclude())) { - if (URLPathMatcher.match(tag.getValue().getExclude(), request.getRequestURI())) { + if (!CollectionUtils.isEmpty(tag.getValue().getExcludeUrls())) { + if (URLPathMatcher.match(tag.getValue().getExcludeUrls(), request.getRequestURI())) { continue; } } - if (URLPathMatcher.match(tag.getValue().getInclude(), request.getRequestURI())) { + if (URLPathMatcher.match(tag.getValue().getIncludeUrls(), request.getRequestURI())) { selectTag = tag.getKey(); break; } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometProperties.java index 1ccad775..4b9682f5 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometProperties.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometProperties.java @@ -21,9 +21,12 @@ package com.github.yizzuide.milkomeda.comet.core; +import com.github.yizzuide.milkomeda.universe.extend.web.handler.HotHttpHandlerProperty; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; +import java.util.Map; + /** * CometProperties * @@ -44,6 +47,7 @@ public class CometProperties { /** * 允许开启响应包装类读取响应消息体(获取通过注入HttpServletResponse直接写出响应数据则必须开启) + * * @see CometProperties#enableReadRequestBody */ private boolean enableReadResponseBody = false; @@ -57,4 +61,16 @@ public class CometProperties { * 失败状态码 */ private String statusFailCode = "2"; + + /** + * Config request parameter interceptor. + * @since 3.15.0 + */ + private Map requestInterceptors; + + /** + * Config response interceptor. + * @since 3.15.0 + */ + private Map responseInterceptors; } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometRequestFilter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometRequestFilter.java index 1e57b031..ebaffc45 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometRequestFilter.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometRequestFilter.java @@ -35,11 +35,11 @@ * CometRequestFilter * 请求过滤器 * + * @see org.springframework.web.filter.CharacterEncodingFilter + * @see org.apache.coyote.Response#isCommitted() * @author yizzuide * @since 2.0.0 * @version 3.5.0 - * @see org.springframework.web.filter.CharacterEncodingFilter - * @see org.apache.coyote.Response#isCommitted() *
* Create at 2019/12/12 17:48 */ @@ -57,9 +57,8 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo ServletRequest requestWrapper = servletRequest; if (CometHolder.shouldWrapRequest()) { // 如果有Form表单数据则不读取body,交给SpringMVC框架处理(但@CometParam功能仍然有效) - if (CollectionUtils.isEmpty(servletRequest.getParameterMap())) { - requestWrapper = new CometRequestWrapper((HttpServletRequest) servletRequest); - } + boolean cacheBody = CollectionUtils.isEmpty(servletRequest.getParameterMap()); + requestWrapper = new CometRequestWrapper((HttpServletRequest) servletRequest, cacheBody); } boolean enableAddResponseWrapper = CometHolder.shouldWrapResponse(); if (enableAddResponseWrapper) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometRequestInterceptor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometRequestInterceptor.java new file mode 100644 index 00000000..4d33905f --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometRequestInterceptor.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.comet.core; + +import com.github.yizzuide.milkomeda.universe.extend.web.handler.NamedHandler; +import org.springframework.lang.Nullable; + +import javax.servlet.http.HttpServletRequest; + +/** + * Comet request wrapper interceptor. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/01 02:59 + */ +public interface CometRequestInterceptor extends NamedHandler { + /** + * Invoked when read value from request. + * @param request HttpServletRequest + * @param formName single form name + * @param formValue single form value + * @param body request body + * @return modify value + */ + String readRequest(HttpServletRequest request, @Nullable String formName, @Nullable String formValue, @Nullable String body); + + +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometRequestWrapper.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometRequestWrapper.java index 72d409d9..02d11117 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometRequestWrapper.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometRequestWrapper.java @@ -25,6 +25,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.catalina.connector.ClientAbortException; import org.apache.commons.lang3.StringUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.web.multipart.commons.CommonsMultipartResolver; import org.springframework.web.util.WebUtils; import javax.servlet.ReadListener; @@ -34,7 +36,9 @@ import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.net.SocketTimeoutException; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; /** * CometRequestWrapper @@ -42,7 +46,7 @@ * * @author yizzuide * @since 2.0.0 - * @version 3.5.0 + * @version 3.15.0 * @see org.springframework.web.util.ContentCachingRequestWrapper *
* Create at 2019/12/12 17:37 @@ -50,19 +54,117 @@ @Slf4j public class CometRequestWrapper extends HttpServletRequestWrapper { /** - * 用于可重复获取 + * Cache request body data. */ - private final byte[] body; + private byte[] body; - public CometRequestWrapper(HttpServletRequest request) throws IOException { + private final boolean cacheBody; + + // 文件上传标识 + private boolean fileUpload = false; + + private final HttpServletRequest originalRequest; + + /** + * Create Request wrapper for intercept or cache body. + * @param request HttpServletRequest + * @param cacheBody enable cache body + */ + public CometRequestWrapper(HttpServletRequest request, boolean cacheBody) { super(request); + this.originalRequest = request; + this.cacheBody = cacheBody; + + CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext()); + if (commonsMultipartResolver.isMultipart(request)) { + fileUpload = true; + } + // 将body数据存储起来 - String bodyStr = getBodyString(request); - if (StringUtils.isEmpty(bodyStr)) { - body = new byte[0]; - return; + if (cacheBody) { + String bodyStr = getBodyString(request); + if (StringUtils.isEmpty(bodyStr)) { + body = new byte[0]; + return; + } + body = bodyStr.getBytes(StandardCharsets.UTF_8); + } + } + + @Override + public String getParameter(String name) { + String value = super.getParameter(name); + if (CollectionUtils.isEmpty(CometHolder.getRequestInterceptors())) { + return value; + } + return interceptRequest(name, value, null); + } + + @Override + public String[] getParameterValues(String name) { + String[] values = super.getParameterValues(name); + if (values == null || CollectionUtils.isEmpty(CometHolder.getRequestInterceptors())) { + return values; + } + for (int i = 0; i < values.length; i++) { + values[i] = interceptRequest(name, values[i], null); + } + return values; + } + + @Override + public Map getParameterMap() { + Map parameterMap = super.getParameterMap(); + if (CollectionUtils.isEmpty(parameterMap) || CollectionUtils.isEmpty(CometHolder.getRequestInterceptors())) { + return parameterMap; + } + Map modifyParamMap = new HashMap<>(parameterMap); + modifyParamMap.keySet().forEach(key -> modifyParamMap.replace(key, getParameterValues(key))); + return modifyParamMap; + } + + // 在返回值前拦截 + private String interceptRequest(String name, String value, String body) { + for (CometRequestInterceptor interceptor: CometHolder.getRequestInterceptors()) { + value = interceptor.readRequest(originalRequest, name, value, body); + } + return value; + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + if (!cacheBody) { + return super.getInputStream(); } - body = bodyStr.getBytes(Charset.defaultCharset()); + final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); + return new ServletInputStream() { + @Override + public int read() throws IOException { + if (body.length == 0) { + return -1; + } + return inputStream.read(); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + }; } /** @@ -84,7 +186,7 @@ public static String resolveRequestParams(HttpServletRequest request, boolean fo String body = requestWrapper.getBodyString(); // 删除换行符 body = body == null ? "" : body.replaceAll("\\n?\\t?", ""); - return body; + return body; } return requestData; } @@ -92,26 +194,26 @@ public static String resolveRequestParams(HttpServletRequest request, boolean fo /** * 获取请求Body * - * @param request request * @return String */ - public String getBodyString(final ServletRequest request) { - try { - return inputStream2String(request.getInputStream()); - } catch (IOException e) { - log.error("Comet get input stream error:{}", e.getMessage(), e); - throw new RuntimeException(e); - } + public String getBodyString() { + final InputStream inputStream = new ByteArrayInputStream(body); + return inputStream2String(inputStream); } /** * 获取请求Body * + * @param request request * @return String */ - public String getBodyString() { - final InputStream inputStream = new ByteArrayInputStream(body); - return inputStream2String(inputStream); + private String getBodyString(final ServletRequest request) { + try { + return inputStream2String(request.getInputStream()); + } catch (IOException e) { + log.error("Comet get input stream error:{}", e.getMessage(), e); + throw new RuntimeException(e); + } } /** @@ -121,14 +223,12 @@ public String getBodyString() { * @return String */ private String inputStream2String(InputStream inputStream) { + // 如果消息体没有数据 + if (inputStream == null) { + return null; + } StringBuilder sb = new StringBuilder(); - BufferedReader reader = null; - try { - // 如果消息体没有数据 - if (inputStream == null) { - return null; - } - reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { sb.append(line); @@ -136,48 +236,13 @@ private String inputStream2String(InputStream inputStream) { } catch (ClientAbortException | SocketTimeoutException ignore) { return null; } catch (IOException e) { - log.error("Comet read input stream error:{}", e.getMessage(), e); + log.error("Comet read input stream error with msg: {}", e.getMessage(), e); throw new RuntimeException(e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - log.error("Comet close input stream error:{}", e.getMessage(), e); - } - } } - return sb.toString(); - } - - @Override - public BufferedReader getReader() throws IOException { - return new BufferedReader(new InputStreamReader(getInputStream())); - } - - @Override - public ServletInputStream getInputStream() throws IOException { - final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); - return new ServletInputStream() { - @Override - public int read() throws IOException { - if (body.length == 0) return -1; - return inputStream.read(); - } - - @Override - public boolean isFinished() { - return false; - } - - @Override - public boolean isReady() { - return false; - } - - @Override - public void setReadListener(ReadListener readListener) { - } - }; + String body = sb.toString(); + if (fileUpload || CollectionUtils.isEmpty(CometHolder.getRequestInterceptors())) { + return body; + } + return interceptRequest(null, null, body); } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometResponseInterceptor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometResponseInterceptor.java index 3ddea56e..8dee8082 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometResponseInterceptor.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometResponseInterceptor.java @@ -21,8 +21,8 @@ package com.github.yizzuide.milkomeda.comet.core; +import com.github.yizzuide.milkomeda.universe.extend.web.handler.NamedHandler; import com.mongodb.lang.Nullable; -import org.springframework.core.Ordered; import org.springframework.util.FastByteArrayOutputStream; import javax.servlet.http.HttpServletResponse; @@ -31,13 +31,14 @@ * Comet response wrapper interceptor. * * @since 3.14.0 + * @version 3.15.0 * @author yizzuide *
* Create at 2022/10/10 17:22 */ -public interface CometResponseInterceptor extends Ordered { +public interface CometResponseInterceptor extends NamedHandler { /** - * Start write content to response. + * Change and write content to response. * @param outputStream content of response * @param wrapperResponse wrapper response object * @param rawResponse real response object diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometResponseWrapper.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometResponseWrapper.java index cdea9c15..39e4edd4 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometResponseWrapper.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometResponseWrapper.java @@ -21,11 +21,9 @@ package com.github.yizzuide.milkomeda.comet.core; -import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; import com.github.yizzuide.milkomeda.universe.context.WebContext; import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.core.OrderComparator; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.util.FastByteArrayOutputStream; @@ -39,9 +37,6 @@ import javax.servlet.http.HttpServletResponseWrapper; import java.io.*; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; /** * CometResponseWrapper @@ -69,19 +64,8 @@ public class CometResponseWrapper extends HttpServletResponseWrapper { @Nullable private Integer contentLength; - private static List INTERCEPTOR_LIST; - public CometResponseWrapper(HttpServletResponse response) { super(response); - if (INTERCEPTOR_LIST == null) { - INTERCEPTOR_LIST = new ArrayList<>(); - ObjectProvider beanProvider = ApplicationContextHolder.get().getBeanProvider(CometResponseInterceptor.class); - beanProvider.forEach(INTERCEPTOR_LIST::add); - if (!CollectionUtils.isEmpty(INTERCEPTOR_LIST)) { - INTERCEPTOR_LIST = INTERCEPTOR_LIST.stream() - .sorted(OrderComparator.INSTANCE.withSourceProvider(inter -> inter)).collect(Collectors.toList()); - } - } } @Override @@ -230,8 +214,8 @@ protected void copyBodyToResponse(boolean complete) throws IOException { } boolean intercepted = false; if (complete) { - if (!CollectionUtils.isEmpty(INTERCEPTOR_LIST)) { - for (CometResponseInterceptor interceptor: INTERCEPTOR_LIST) { + if (!CollectionUtils.isEmpty(CometHolder.getResponseInterceptors())) { + for (CometResponseInterceptor interceptor: CometHolder.getResponseInterceptors()) { HttpServletRequest request = WebContext.getRequest(); Object body = request == null ? null : request.getAttribute(CometResponseBodyAdvice.REQUEST_ATTRIBUTE_BODY); intercepted = interceptor.writeToResponse(this.content, this, rawResponse, body); @@ -241,7 +225,7 @@ protected void copyBodyToResponse(boolean complete) throws IOException { } } } - // if not intercepted within interceptor list + // if not intercepted from response interceptors if (!intercepted) { this.content.writeTo(rawResponse.getOutputStream()); } @@ -267,7 +251,7 @@ public void write(int b) throws IOException { } @Override - public void write(byte[] b, int off, int len) throws IOException { + public void write(@NonNull byte[] b, int off, int len) throws IOException { content.write(b, off, len); } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometSqlInjectRequestInterceptor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometSqlInjectRequestInterceptor.java new file mode 100644 index 00000000..8bb00fd0 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometSqlInjectRequestInterceptor.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.comet.core; + +import com.github.yizzuide.milkomeda.universe.extend.annotation.Alias; + +import javax.servlet.http.HttpServletRequest; +import java.util.regex.Pattern; + +/** + * This request interceptor provide web sql inject protection. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/01 21:21 + */ +@Alias("sql-inject") +public class CometSqlInjectRequestInterceptor extends AbstractRequestInterceptor { + + private static final String SQL_REG_EXP = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)"; + + @Override + protected String doReadRequest(HttpServletRequest request, String formName, String formValue, String body) { + String value = formValue == null ? body : formValue; + if (value == null) { + return value; + } + Pattern sqlPattern = Pattern.compile(SQL_REG_EXP, Pattern.CASE_INSENSITIVE); + if (sqlPattern.matcher(value.toLowerCase()).find()) { + throw new RuntimeException("Detected SQL injection with value: " + value); + } + return value; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometXssRequestInterceptor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometXssRequestInterceptor.java new file mode 100644 index 00000000..8303bc09 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/CometXssRequestInterceptor.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.comet.core; + +import com.github.yizzuide.milkomeda.universe.extend.annotation.Alias; +import lombok.Setter; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.safety.Safelist; +import org.springframework.util.CollectionUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * This request interceptor provides web XSS protection. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/01 04:28 + */ +@Alias("xss") +public class CometXssRequestInterceptor extends AbstractRequestInterceptor { + + // 允许常用显示型html标签 + private static final Safelist whitelist = Safelist.basicWithImages(); + + // 输出不格式化 + private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false); + + /** + * White-filed names are not preventing (support form submit type only). + */ + @Setter + private List whiteFieldNames; + + static { + // 标签可以带有style属性 + whitelist.addAttributes(":all", "style"); + } + + @Override + protected String doReadRequest(HttpServletRequest request, String formName, String formValue, String body) { + // 过滤Form表单项 + if (formValue != null) { + // 匹配字段名,过滤白名单 + if (!CollectionUtils.isEmpty(whiteFieldNames)) { + for (String whiteFieldName : whiteFieldNames) { + if (whiteFieldName.equals(formName)) { + return formValue; + } + if (whiteFieldName.startsWith("*") && whiteFieldName.endsWith(whiteFieldName.substring(1))) { + return formValue; + } + if (whiteFieldName.endsWith("*") && whiteFieldName.startsWith(whiteFieldName.substring(0, whiteFieldName.length() - 1))) { + return formValue; + } + } + } + return Jsoup.clean(formValue, "", whitelist, outputSettings); + } + // 过滤请求body + return Jsoup.clean(body, "", whitelist, outputSettings); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/EventDrivenWebCometData.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/EventDrivenWebCometData.java new file mode 100644 index 00000000..305fc8f7 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/EventDrivenWebCometData.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.comet.core; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * An identification class which used indicate for event driven. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/07/12 20:29 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class EventDrivenWebCometData extends WebCometData { + private static final long serialVersionUID = 9122488246826621150L; +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/WebCometData.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/WebCometData.java index 9fc3a54f..c01ab79e 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/WebCometData.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/comet/core/WebCometData.java @@ -49,7 +49,7 @@ public class WebCometData extends CometData { private static final long serialVersionUID = -2078666744044889106L; /** - * 接口请求序号 + * 请求标识码 */ private String apiCode; /** diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/Crust.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/Crust.java index 14ba2ded..f0aede69 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/Crust.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/Crust.java @@ -21,22 +21,19 @@ package com.github.yizzuide.milkomeda.crust; -import com.github.yizzuide.milkomeda.light.Cache; -import com.github.yizzuide.milkomeda.light.CacheHelper; -import com.github.yizzuide.milkomeda.light.LightCacheEvict; -import com.github.yizzuide.milkomeda.light.LightCacheable; +import com.github.yizzuide.milkomeda.light.*; import com.github.yizzuide.milkomeda.universe.context.AopContextHolder; import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; import com.github.yizzuide.milkomeda.universe.context.WebContext; -import com.github.yizzuide.milkomeda.util.JwtUtil; import com.github.yizzuide.milkomeda.util.Strings; -import io.jsonwebtoken.Claims; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.InsufficientAuthenticationException; @@ -51,9 +48,9 @@ import org.springframework.util.StringUtils; import java.io.Serializable; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.*; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; import java.util.stream.Collectors; /** @@ -62,36 +59,29 @@ * * @author yizzuide * @since 1.14.0 - * @version 3.14.1 + * @version 3.15.0 *
* Create at 2019/11/11 15:48 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j public class Crust { /** - * 缓存前辍 + * 用户信息缓存前辍 */ private static final String CATCH_KEY_PREFIX = "crust:user:"; /** - * 缓存标识 - */ - static final String CATCH_NAME = "lightCacheCrust"; - /** - * 用户id - */ - private static final String UID = "uid"; - /** - * 用户名称 + * 帐号验证码缓存前辍 */ - private static final String USERNAME = Claims.SUBJECT; + private static final String CATCH_CODE_KEY_PREFIX = "crust:code:"; /** - * 创建时间 + * 缓存标识 */ - private static final String CREATED = Claims.ISSUED_AT; + static final String CATCH_NAME = "crustLightCache"; /** - * 角色id + * 验证码缓存标识 */ - private static final String ROLE_IDS = "roles"; + static final String CODE_CATCH_NAME = "crustCodeLightCache"; @Getter @Autowired @@ -99,23 +89,85 @@ public class Crust { @Qualifier(Crust.CATCH_NAME) @Autowired(required = false) - private Cache lightCacheCrust; + private Cache crustLightCache; + + @Qualifier(Crust.CODE_CATCH_NAME) + @Autowired(required = false) + private Cache crustCodeLightCache; + + @Autowired + private CrustTokenResolver tokenResolver; private CrustUserDetailsService crustUserDetailsService; + /** + * Generate verify code for login action. + * @param account account name(phone, email, etc.) + * @return verily code + * @since 3.15.0 + */ + public String generateCode(String account) { + String code = RandomStringUtils.randomNumeric(6); + String cacheKey = CATCH_CODE_KEY_PREFIX + account; + // if enableCode is false + if (crustCodeLightCache == null) { + return null; + } + crustCodeLightCache.set(cacheKey, new Spot<>(code)); + return code; + } + + /** + * Get cached code. + * @param account account name(phone, email, etc.) + * @return verily code + */ + String getCode(String account) { + String cacheKey = CATCH_CODE_KEY_PREFIX + account; + if(crustCodeLightCache == null || !crustCodeLightCache.isCacheL2Exists(cacheKey)) { + return null; + } + Spot spot = crustCodeLightCache.get(cacheKey, Serializable.class, String.class); + return spot == null ? null : spot.getData(); + } + /** * 登录认证 - * - * @param username 用户名 - * @param password 密码 + * @param account 帐号 + * @param credentials 登录凭证(如密码,手机验/邮箱验证码) * @param entityClazz 实体类型 * @param 实体类型 * @return CrustUserInfo */ @NonNull - public CrustUserInfo login(@NonNull String username, @NonNull String password, @NonNull Class entityClazz) { - // CrustAuthenticationToken封装了UsernamePasswordAuthenticationToken - CrustAuthenticationToken authenticationToken = new CrustAuthenticationToken(username, password); + public CrustUserInfo login(@NonNull String account, @NonNull String credentials, @NonNull Class entityClazz) { + return login(account, credentials, entityClazz, props.getLoginType(), null); + } + + /** + * 登录认证 + * @param account 帐号 + * @param credentials 登录凭证(如密码,手机验/邮箱验证码) + * @param entityClazz 实体类型 + * @param loginType 登录类型 + * @param customAuthenticationTokenSupplier Custom authentication token + * @param 实体类型 + * @return CrustUserInfo + * @since 3.15.0 + */ + public CrustUserInfo login(@NonNull String account, @NonNull String credentials, @NonNull Class entityClazz, CrustLoginType loginType, Supplier customAuthenticationTokenSupplier) { + AbstractAuthenticationToken authenticationToken = null; + switch (loginType) { + case PASSWORD: + authenticationToken = new CrustAuthenticationToken(account, credentials); + break; + case CODE: + authenticationToken = new CrustCodeAuthenticationToken(account, credentials); + break; + case CUSTOM: + authenticationToken = customAuthenticationTokenSupplier.get(); + break; + } // 设置请求信息 authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(WebContext.getRequest())); AuthenticationManager authenticationManager = ApplicationContextHolder.get().getBean(AuthenticationManager.class); @@ -143,22 +195,14 @@ public CrustUserInfo login(@NonNull */ @NonNull private CrustUserInfo generateToken(@NonNull Authentication authentication, @NonNull Class entityClazz) { - Map claims = new HashMap<>(6); CrustUserInfo userInfo = getCurrentLoginUserInfo(authentication, entityClazz); - claims.put(UID, userInfo.getUidLong()); - claims.put(USERNAME, userInfo.getUsername()); - claims.put(CREATED, new Date()); Object principal = authentication.getPrincipal(); + String roleIds = null; if (principal instanceof CrustUserDetails) { - List roleIds = ((CrustUserDetails) principal).getRoleIds(); - if (!CollectionUtils.isEmpty(roleIds)) { - claims.put(ROLE_IDS, StringUtils.arrayToCommaDelimitedString(roleIds.toArray())); - } + List roleIdList = ((CrustUserDetails) principal).getRoleIds(); + roleIds = CollectionUtils.isEmpty(roleIdList) ? null : StringUtils.arrayToCommaDelimitedString(roleIdList.toArray()); } - long expire = LocalDateTime.now().plusMinutes(props.getExpire().toMinutes()).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); - String token = JwtUtil.generateToken(claims, getSignKey(), expire, props.isUseRsa()); - userInfo.setToken(token); - userInfo.setTokenExpire(expire); + String token = tokenResolver.generate(userInfo, roleIds); // help cache the auth info,that synchronize with the expired token. AopContextHolder.self(this.getClass()).getAuthInfoFromToken(token); return userInfo; @@ -189,7 +233,16 @@ private CrustUserInfo getCurrentLogi } /** - * 获取当前用户信息(外面API调用) + * 获取登录的基本信息,如:uid, username, roleId等 + * @return CrustUserInfo + * @since 3.15.0 + */ + public CrustUserInfo getUserInfo() { + return getUserInfo(null); + } + + /** + * 获取当前用户详细信息 * * @param entityClazz 实体类型 * @param 实体类型 @@ -213,7 +266,7 @@ public CrustUserInfo getUserInfo(Cla } // Session方式开启超级缓存 - return CacheHelper.getFastLevel(lightCacheCrust, spot -> getCurrentLoginUserInfo(authentication, entityClazz)); + return CacheHelper.getFastLevel(crustLightCache, spot -> getCurrentLoginUserInfo(authentication, entityClazz)); } /** @@ -230,37 +283,19 @@ CrustUserInfo getAuthInfoFromToken(String token) { if (authentication != null) { return getCurrentLoginUserInfo(authentication, null); } - // invoke from CrustAuthenticationFilter.doFilterInternal() - String unSignKey = getUnSignKey(); - Claims claims = JwtUtil.parseToken(token, unSignKey); - if (claims == null) { - return null; - } - String username = claims.getSubject(); - if (username == null) { + CrustUserInfo userInfo = tokenResolver.resolve(token, this::loadEntity); + if (userInfo == null) { return null; } - Serializable uid = (Serializable) claims.get(UID); - //long issuedAt = (long) claims.get(CREATED); - long expire = claims.getExpiration().getTime(); - Object RoleIdsObj = claims.get(ROLE_IDS); - List roleIds = null; - if (RoleIdsObj != null) { - roleIds = Arrays.stream(((String) RoleIdsObj).split(",")).map(Long::parseLong).collect(Collectors.toList()); - } - CrustEntity entity = getCrustUserDetailsService().findEntityById(uid); - CrustUserInfo userInfo = new CrustUserInfo<>(uid, username, token, roleIds, entity); - userInfo.setTokenExpire(expire); // active! activeAuthentication(userInfo); return userInfo; } // #token 与 args[0] 等价 - // #target 与 @crust、#this.object、#root.object等价(#this在表达式不同部分解析过程中可能会改变,但是#root总是指向根,object为自定义root对象属性) @LightCacheEvict(value = Crust.CATCH_NAME, keyPrefix = CATCH_KEY_PREFIX, key = "T(org.springframework.util.DigestUtils).md5DigestAsHex(#token?.bytes)", condition = "#target.props.enableCache") - public void removeTokenCache(String token) { + void removeTokenCache(String token) { } /** @@ -295,14 +330,9 @@ public CrustUserInfo refreshToken() { if (!props.isStateless()) { return null; } String refreshedToken; try { - String token = getToken(false); - Claims claims = JwtUtil.parseToken(token, getUnSignKey()); - claims.put(CREATED, new Date()); - long expire = LocalDateTime.now().plusMinutes(props.getExpire().toMinutes()).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); - refreshedToken = JwtUtil.generateToken(claims, getSignKey(), expire, props.isUseRsa()); CrustUserInfo loginUserInfo = getCurrentLoginUserInfo(getAuthentication(), null); - loginUserInfo.setToken(refreshedToken); - loginUserInfo.setTokenExpire(expire); + String token = getToken(false); + refreshedToken = tokenResolver.refresh(token, loginUserInfo); // remove cache (old token with user auth info) AopContextHolder.self(this.getClass()).removeTokenCache(token); // re-cache user auth info with refreshed token, not need change it. @@ -318,18 +348,14 @@ public CrustUserInfo refreshToken() { * 使登录信息失效 */ public void invalidate() { - try { - // Token方式下开启缓存时清空 - if (getProps().isStateless()) { - String token = getAuthentication() == null ? getToken(false) : - getUserInfo(null).getToken(); - if (Strings.isEmpty(token)) { - throw new InsufficientAuthenticationException("Token is not exists."); - } - AopContextHolder.self(this.getClass()).removeTokenCache(token); + // Token方式下开启缓存时清空 + if (getProps().isStateless()) { + String token = getAuthentication() == null ? getToken(false) : + getUserInfo(null).getToken(); + if (Strings.isEmpty(token)) { + throw new InsufficientAuthenticationException("Token is not exists."); } - } finally { - SecurityContextHolder.clearContext(); + AopContextHolder.self(this.getClass()).removeTokenCache(token); } } @@ -360,18 +386,15 @@ public SecurityContext getContext() { * @return Token */ @Nullable - public String getToken(boolean checkIsExists) { + String getToken(boolean checkIsExists) { if (!props.isStateless()) { return null; } - String token = WebContext.getRequestNonNull().getHeader(props.getTokenName()); - if (Strings.isEmpty(token)) { return null; } - // 一般请求头Authorization的值会添加Bearer - String tokenHead = "Bearer "; - if (token.contains(tokenHead)) { - token = token.substring(tokenHead.length()); + String token = tokenResolver.getRequestToken(); + if (props.isCacheInMemory()) { + return token; } if (checkIsExists && props.isEnableCache()) { String cacheKey = CATCH_KEY_PREFIX + DigestUtils.md5DigestAsHex(token.getBytes()); - if (!lightCacheCrust.isCacheL2Exists(cacheKey)) { + if (!crustLightCache.isCacheL2Exists(cacheKey)) { return null; } } @@ -380,7 +403,7 @@ public String getToken(boolean checkIsExists) { /** * Custom for permission any match. - * @param permissions permission list. + * @param permissions permission list * @return ture if match * @since 3.14.0 */ @@ -388,40 +411,19 @@ public boolean permitAny(String ...permissions) { Authentication authentication = getAuthentication(); Assert.notNull(authentication, "Authentication must be not null."); CrustUserInfo loginUserInfo = getCurrentLoginUserInfo(authentication, null); - List perms = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); Boolean isAdmin = loginUserInfo.getIsAdmin(); - if (isAdmin == null) { - return Arrays.stream(permissions).anyMatch(perms::contains); - } - return isAdmin || Arrays.stream(permissions).anyMatch(perms::contains); - } - - /** - * 获取加密key - * @return sign key - */ - private String getSignKey() { - if (props.isUseRsa()) { - return props.getPriKey(); + if (isAdmin != null && isAdmin) { + return true; } - return props.getSecureKey(); + List perms = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); + return Arrays.stream(permissions).anyMatch(perms::contains); } - /** - * 获取解密key - * @return unSign key - */ - private String getUnSignKey() { - if (props.isUseRsa()) { - return props.getPubKey(); - } - return props.getSecureKey(); + @SuppressWarnings("unchecked") + T loadEntity(Serializable uid) { + return (T) getCrustUserDetailsService().findEntityById(uid); } - /** - * 从IoC容器查找UserDetailsService - * @return CrustUserDetailsService - */ private CrustUserDetailsService getCrustUserDetailsService() { if (crustUserDetailsService == null) { crustUserDetailsService = ApplicationContextHolder.get().getBean(CrustUserDetailsService.class); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationConfigurer.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationConfigurer.java index a83601a9..e2d8f82d 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationConfigurer.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationConfigurer.java @@ -89,12 +89,17 @@ CrustAuthenticationConfigurer permissiveRequestUrls(String... urls) { } /** - * 授权成功处理器 --转向--> 刷新Token处理器 + * 授权成功处理器后的刷新Token处理器 */ class RefreshSuccessHandler implements AuthenticationSuccessHandler { - // token刷新间隔 + /** + * token刷新间隔 + */ private final long tokenRefreshInterval; - // token刷新响应字段 + + /** + * token刷新响应字段 + */ private final String refreshTokenName; RefreshSuccessHandler(String refreshTokenName) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationFilter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationFilter.java index 59a5ba57..4b43c487 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationFilter.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationFilter.java @@ -48,9 +48,14 @@ import java.util.List; /** - * CrustAuthenticationFilter - * 安全拦截过滤器 + * Parse token to authentication filter. * + * @see org.springframework.security.web.context.SecurityContextHolderFilter + * @see org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter + * @see org.springframework.security.web.authentication.www.BasicAuthenticationFilter + * @see org.springframework.security.web.authentication.AnonymousAuthenticationFilter + * @see org.springframework.security.web.access.ExceptionTranslationFilter + * @see org.springframework.security.web.access.intercept.FilterSecurityInterceptor * @author yizzuide * @since 1.14.0 * @version 3.12.9 @@ -115,6 +120,7 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull Ht return; } chain.doFilter(request, response); + // Next clear `SecurityContext` within `SecurityContextHolderFilter` } protected void unsuccessfulAuthentication(HttpServletRequest request, @@ -136,20 +142,24 @@ protected boolean requiresAuthentication(HttpServletRequest request, } protected boolean permissiveRequest(HttpServletRequest request) { - if (permissiveRequestMatchers == null) + if (permissiveRequestMatchers == null) { return false; + } for (RequestMatcher permissiveMatcher : permissiveRequestMatchers) { - if (permissiveMatcher.matches(request)) + if (permissiveMatcher.matches(request)) { return true; + } } return false; } public void setPermissiveUrl(String... urls) { - if (permissiveRequestMatchers == null) + if (permissiveRequestMatchers == null) { permissiveRequestMatchers = new ArrayList<>(); - for (String url : urls) + } + for (String url : urls) { permissiveRequestMatchers.add(new AntPathRequestMatcher(url)); + } } public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationProvider.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationProvider.java index a23f07d6..e1a17cb5 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationProvider.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAuthenticationProvider.java @@ -84,4 +84,9 @@ protected void additionalAuthenticationChecks(UserDetails userDetails, UsernameP throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } } + + @Override + public boolean supports(Class authentication) { + return CrustAuthenticationToken.class.isAssignableFrom(authentication); + } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAutoConfigurer.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAutoConfigurer.java new file mode 100644 index 00000000..22c8ce26 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustAutoConfigurer.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.crust; + +import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.userdetails.UserDetailsService; + +/** + * Default impl of CrustConfigurerAdapter with {@link UserDetailsService}. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/06 19:39 + */ +@ConditionalOnProperty(value = "use-auto-config", prefix = "milkomeda.crust", havingValue = "true", matchIfMissing = true) +@Configuration +public class CrustAutoConfigurer extends CrustConfigurerAdapter { + @Override + protected UserDetailsService userDetailsService() { + return ApplicationContextHolder.get().getBean(CrustUserDetailsService.class); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustCodeAuthenticationProvider.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustCodeAuthenticationProvider.java new file mode 100644 index 00000000..1d728d8a --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustCodeAuthenticationProvider.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.crust; + +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetailsService; + +/** + * A Provider supported for code type login with {@link CrustCodeAuthenticationToken}. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/31 20:57 + */ +public class CrustCodeAuthenticationProvider implements AuthenticationProvider { + + private final UserDetailsService userDetailsService; + + public CrustCodeAuthenticationProvider(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + CrustCodeAuthenticationToken authenticationToken = (CrustCodeAuthenticationToken) authentication; + String account = (String) authentication.getPrincipal(); + String code = (String) authentication.getCredentials(); + String cachedCode = CrustContext.get().getCode(account); + if (cachedCode == null) { + throw new BadCredentialsException("Verify code not exists!"); + } + if (!cachedCode.equals(code)) { + throw new BadCredentialsException("Verify code is invalid!"); + } + CrustUserDetails loginUser = (CrustUserDetails) this.userDetailsService.loadUserByUsername(account); + CrustCodeAuthenticationToken authenticationResult = new CrustCodeAuthenticationToken(loginUser, code, loginUser.getAuthorities()); + authenticationResult.setDetails(authenticationToken.getDetails()); + return authenticationResult; + } + + @Override + public boolean supports(Class authentication) { + return CrustCodeAuthenticationToken.class.isAssignableFrom(authentication); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustCodeAuthenticationToken.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustCodeAuthenticationToken.java new file mode 100644 index 00000000..05772a3a --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustCodeAuthenticationToken.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.crust; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +/** + * Supported for code type login. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/31 20:34 + */ +public class CrustCodeAuthenticationToken extends AbstractAuthenticationToken { + + private static final long serialVersionUID = 2459037569753551026L; + + private final Object account; + + private final Object code; + + public CrustCodeAuthenticationToken(Object account, Object code) { + super(null); + this.account = account; + this.code = code; + setAuthenticated(false); + } + + public CrustCodeAuthenticationToken(Object account, Object code, Collection authorities) { + super(authorities); + this.account = account; + this.code = code; + super.setAuthenticated(true); + } + + @Override + public Object getCredentials() { + return code; + } + + @Override + public Object getPrincipal() { + return account; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustConfig.java index 85072af6..eb2be40b 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustConfig.java @@ -54,10 +54,10 @@ *
* Create at 2019/11/11 14:56 */ -@Configuration -@AutoConfigureAfter(LightConfig.class) @ConditionalOnClass({AuthenticationManager.class}) @EnableConfigurationProperties({CrustProperties.class, LightProperties.class}) +@AutoConfigureAfter(LightConfig.class) +@Configuration public class CrustConfig { @Autowired @@ -68,6 +68,11 @@ public Crust crust() { return new Crust(); } + @Bean + public CrustTokenResolver crustTokenResolver() { + return new CrustTokenResolver(crustProps); + } + @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "milkomeda.crust", name = "use-bcrypt", havingValue = "true", matchIfMissing = true) @@ -83,21 +88,30 @@ public LightCacheAspect lightCacheAspect() { } @Bean(Crust.CATCH_NAME) + @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "milkomeda.crust", name = "enable-cache", havingValue = "true", matchIfMissing = true) - public Cache lightCache(LightProperties lightProps) { + public Cache crustLightCache(LightProperties lightProps) { LightCache lightCache = new LightCache(); lightCache.setL1MaxCount(lightProps.getL1MaxCount()); lightCache.setL1DiscardPercent(lightProps.getL1DiscardPercent()); lightCache.setL1Expire(crustProps.getExpire().getSeconds()); - lightCache.setStrategy(lightProps.getStrategy()); - lightCache.setStrategyClass(lightProps.getStrategyClass()); - lightCache.setOnlyCacheL1(!crustProps.isEnableCacheL2()); + lightCache.setStrategy(LightDiscardStrategy.LazyExpire); + lightCache.setOnlyCacheL1(crustProps.isCacheInMemory()); lightCache.setL2Expire(crustProps.getExpire().getSeconds()); - lightCache.setOnlyCacheL2(false); lightCache.setEnableSuperCache(lightProps.isEnableSuperCache()); return lightCache; } + @Bean(Crust.CODE_CATCH_NAME) + @ConditionalOnProperty(prefix = "milkomeda.crust", name = "login-type", havingValue = "CODE") + public Cache crustCodeLightCache() { + LightCache lightCache = new LightCache(); + lightCache.setEnableSuperCache(false); + lightCache.setOnlyCacheL2(true); + lightCache.setL2Expire(crustProps.getCodeExpire().getSeconds()); + return lightCache; + } + @Bean @ConditionalOnMissingBean public LightCacheCleanAstrolabeHandler lightCacheCleanAstrolabeHandler(@Autowired(required = false) LightThreadLocalScope scope) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustConfigurerAdapter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustConfigurerAdapter.java index 71aa6070..a2fe7f85 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustConfigurerAdapter.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustConfigurerAdapter.java @@ -31,17 +31,20 @@ import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; @@ -60,18 +63,17 @@ import java.util.*; /** - * CrustConfigurerAdapter - * Spring Security配置器适配器 + * Spring Security config adapter, need impl with {@link org.springframework.context.annotation.Configuration}. * * @see org.springframework.security.web.session.SessionManagementFilter * @see org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer * @author yizzuide * @since 1.14.0 - * @version 3.11.2 + * @version 3.15.0 *
* Create at 2019/11/11 18:25 */ -public class CrustConfigurerAdapter extends WebSecurityConfigurerAdapter { +public abstract class CrustConfigurerAdapter { @Autowired private CrustProperties props; @@ -82,20 +84,33 @@ public class CrustConfigurerAdapter extends WebSecurityConfigurerAdapter { @Autowired private ApplicationContextHolder applicationContextHolder; - @Override - public void configure(AuthenticationManagerBuilder auth) { + @Bean + public AuthenticationProvider authenticationProvider(AuthenticationManagerBuilder auth) { // 使用继承自DaoAuthenticationProvider CrustAuthenticationProvider authenticationProvider = new CrustAuthenticationProvider(props, passwordEncoder); // 扩展配置(UserDetailsService、PasswordEncoder) configureProvider(authenticationProvider); + // 验证码登录Provider + CrustCodeAuthenticationProvider codeAuthenticationProvider = new CrustCodeAuthenticationProvider(userDetailsService()); // 添加自定义身份验证组件 - auth.authenticationProvider(authenticationProvider); + auth.authenticationProvider(authenticationProvider) + .authenticationProvider(codeAuthenticationProvider); + Map customAuthenticationProviderMap = ApplicationContextHolder.get().getBeansOfType(AuthenticationProvider.class, false, true); + if (!CollectionUtils.isEmpty(customAuthenticationProviderMap)) { + customAuthenticationProviderMap.values().forEach(auth::authenticationProvider); + } + return authenticationProvider; } - @Override - protected void configure(HttpSecurity http) throws Exception { - List allowURLs = new ArrayList<>(props.getPermitURLs()); - // 登录 + @Bean(name = BeanIds.AUTHENTICATION_MANAGER) + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + List allowURLs = new ArrayList<>(props.getPermitUrls()); + // 允许登录 allowURLs.add(props.getLoginUrl()); // 额外添加的排除项 if (!CollectionUtils.isEmpty(props.getAdditionPermitUrls())) { @@ -104,7 +119,8 @@ protected void configure(HttpSecurity http) throws Exception { // 标记匿名访问 // Find URL method map Map handlerMethodMap = applicationContextHolder.getApplicationContext() - .getBean(RequestMappingHandlerMapping.class).getHandlerMethods(); + .getBean(com.github.yizzuide.milkomeda.universe.metadata.BeanIds.REQUEST_MAPPING_HANDLER_MAPPING, + RequestMappingHandlerMapping.class).getHandlerMethods(); Set anonUrls = new HashSet<>(); for (Map.Entry infoEntry : handlerMethodMap.entrySet()) { HandlerMethod handlerMethod = infoEntry.getValue(); @@ -122,11 +138,11 @@ protected void configure(HttpSecurity http) throws Exception { DefaultFailureHandler failureHandler = new DefaultFailureHandler(this); http.csrf() .disable() - .sessionManagement().sessionCreationPolicy(props.isStateless() ? - SessionCreationPolicy.STATELESS : SessionCreationPolicy.IF_REQUIRED).and() - .formLogin().disable() - // 支持跨域,从CorsConfigurationSource中取跨域配置 - .cors() + .sessionManagement().sessionCreationPolicy(props.isStateless() ? + SessionCreationPolicy.STATELESS : SessionCreationPolicy.IF_REQUIRED).and() + .formLogin().disable() + // 支持跨域,从CorsConfigurationSource中取跨域配置 + .cors() .and() // 禁用iframe跨域 .headers() @@ -163,32 +179,34 @@ protected void configure(HttpSecurity http) throws Exception { .sessionFixation().changeSessionId() .sessionAuthenticationErrorUrl(props.getLoginUrl()) .sessionAuthenticationFailureHandler(failureHandler).and() - .logout() + .logout() .logoutUrl(props.getLogoutUrl()) - .addLogoutHandler((req, res, auth) -> CrustContext.invalidate()) .logoutSuccessUrl(props.getLoginUrl()) + .addLogoutHandler((req, res, auth) -> CrustContext.invalidate()) + .clearAuthentication(true) .invalidateHttpSession(true); } + // 异常处理器(如果开启了Hydrogen/Uniform模块,则交由Uniform模块处理) // 认证用户无权限访问处理 http.exceptionHandling().accessDeniedHandler(failureHandler) - // 匿名用户无权限访问处理 + // 认证异常或匿名用户无权限访问处理 .authenticationEntryPoint(failureHandler); // add others http configure additionalConfigure(http, props.isStateless()); + return http.build(); } - /** - * 配置Web资源,资源根路径需要配置静态资源映射
- * @param web WebSecurity - */ - @Override - public void configure(WebSecurity web) { + // 配置Web资源,资源根路径需要配置静态资源映射 + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { // 放开静态资源的限制 - if (!CollectionUtils.isEmpty(props.getAllowStaticUrls())) { - web.ignoring().antMatchers(HttpMethod.GET, props.getAllowStaticUrls().toArray(new String[0])); - } + return (web) -> { + if (props.getAllowStaticUrls() != null) { + web.ignoring().antMatchers(HttpMethod.GET, props.getAllowStaticUrls().toArray(new String[0])); + } + }; } /** @@ -208,6 +226,12 @@ protected void configureProvider(DaoAuthenticationProvider provider) { provider.setUserDetailsService(userDetailsService()); } + /** + * Must implement this method used for {@link DaoAuthenticationProvider}. + * @return UserDetailsService + */ + protected abstract UserDetailsService userDetailsService(); + /** * Custom response for auth or access failure handler. * @param isAuth true if is auth type @@ -218,17 +242,11 @@ protected void configureProvider(DaoAuthenticationProvider provider) { * @since 3.14.0 */ protected void doFailure(boolean isAuth, HttpServletRequest request, HttpServletResponse response, RuntimeException exception) throws IOException { - response.setStatus(HttpStatus.OK.value()); - ResultVO source = UniformResult.error(props.getAuthFailCode(), exception.getMessage()); + response.setStatus(isAuth ? HttpStatus.UNAUTHORIZED.value(): HttpStatus.FORBIDDEN.value()); + ResultVO source = UniformResult.error(String.valueOf(response.getStatus()), exception.getMessage()); UniformHandler.matchStatusToWrite(response, source.toMap()); } - @Bean(name = BeanIds.AUTHENTICATION_MANAGER) - @Override - public AuthenticationManager authenticationManager() throws Exception { - return super.authenticationManager(); - } - @Bean protected CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); @@ -251,20 +269,20 @@ public DefaultFailureHandler(CrustConfigurerAdapter configurerAdapter) { // 认证失败处理器 @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - configurerAdapter.doFailure(true, request, response, exception); + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + configurerAdapter.doFailure(true, request, response, authException); } - // 认证用户无权限访问拒绝处理器 + // 认证异常或匿名用户无权限访问处理器 @Override - public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { - configurerAdapter.doFailure(false, request, response, accessDeniedException); + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + this.onAuthenticationFailure(request, response, authException); } - // 匿名用户无权限访问拒绝处理器 + // 认证用户无权限访问拒绝处理器 @Override - public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { - configurerAdapter.doFailure(false, request, response, authException); + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + configurerAdapter.doFailure(false, request, response, accessDeniedException); } } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustContext.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustContext.java index 6f65105a..7f62b454 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustContext.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustContext.java @@ -21,7 +21,9 @@ package com.github.yizzuide.milkomeda.crust; +import com.github.yizzuide.milkomeda.light.LightContext; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; /** * CrustContext @@ -29,7 +31,7 @@ * * @author yizzuide * @since 1.14.0 - * @version 2.0.4 + * @version 3.15.0 *
* Create at 2019/11/11 17:53 */ @@ -43,11 +45,19 @@ static void set(Crust crust) { INSTANCE = crust; } - @NonNull public static Crust get() { return INSTANCE; } + /** + * 获取登录的基本信息,如:uid, username, roleId等 + * @return CrustUserInfo + * @since 3.15.0 + */ + public static CrustUserInfo getUserInfo() { + return getUserInfo(null); + } + /** * 获取用户信息(支持多次调用,而不会重新创建多份对象) * @@ -56,12 +66,22 @@ public static Crust get() { * @return CrustUserInfo */ @NonNull - public static CrustUserInfo getUserInfo(@NonNull Class entityClass) { return get().getUserInfo(entityClass); } + public static CrustUserInfo getUserInfo(@Nullable Class entityClass) { + Crust crust = get(); + // invoke from microservice + if (crust == null) { + return LightContext.getValue(CrustInterceptor.LIGHT_CONTEXT_ID); + } + return crust.getUserInfo(entityClass); + } /** * 使登录信息失效,清空当前用户的缓存 */ public static void invalidate() { - get().invalidate(); + Crust crust = get(); + if (crust != null) { + crust.invalidate(); + } } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustEntity.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustEntity.java index 9cec8965..aedd5d2d 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustEntity.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustEntity.java @@ -29,9 +29,9 @@ * CrustEntity * 需要用户实体实现的适配接口 * - * @author yizzuide * @since 1.14.0 * @version 2.0.4 + * @author yizzuide *
* Create at 2019/11/11 18:47 */ @@ -43,7 +43,7 @@ public interface CrustEntity extends Serializable { Serializable getUid(); /** - * 用户名 + * 帐号(用户名,手机号等) * @return username */ String getUsername(); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustException.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustException.java index 4958682f..a969737a 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustException.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustException.java @@ -22,7 +22,8 @@ package com.github.yizzuide.milkomeda.crust; /** - * CrustException + * Thrown if an authentication request is rejected because the credentials are not + * sufficiently trusted. * * @author yizzuide * @since 3.12.10 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustInterceptor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustInterceptor.java new file mode 100644 index 00000000..bcb21614 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustInterceptor.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.crust; + +import com.github.yizzuide.milkomeda.hydrogen.uniform.ResultVO; +import com.github.yizzuide.milkomeda.hydrogen.uniform.UniformHandler; +import com.github.yizzuide.milkomeda.hydrogen.uniform.UniformResult; +import com.github.yizzuide.milkomeda.light.LightContext; +import org.springframework.http.HttpStatus; +import org.springframework.lang.NonNull; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Intercept and check token before at the request handle. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/07 00:44 + */ +public class CrustInterceptor implements HandlerInterceptor { + public static final String LIGHT_CONTEXT_ID = "crustLightContext"; + + @Resource + private CrustTokenResolver tokenResolver; + + private LightContext> lightContext; + + @Override + public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception { + String token = tokenResolver.getRequestToken(); + boolean isAuthedSuccess = false; + String errorMsg = "Required token is not set."; + if (StringUtils.hasText(token)) { + CrustUserInfo userInfo = tokenResolver.resolve(token, null); + if (userInfo == null) { + errorMsg = "Token is invalid."; + } else { + isAuthedSuccess = true; + if (lightContext == null) { + lightContext = LightContext.setValue(null, LIGHT_CONTEXT_ID); + } + lightContext.setData(userInfo); + } + } + if (!isAuthedSuccess) { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + ResultVO source = UniformResult.error(String.valueOf(response.getStatus()), errorMsg); + UniformHandler.matchStatusToWrite(response, source.toMap()); + return false; + } + return true; + } + + @Override + public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) throws Exception { + if (lightContext != null) { + lightContext.remove(); + } + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustLoginType.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustLoginType.java new file mode 100644 index 00000000..be78787e --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustLoginType.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.crust; + +import java.util.function.Supplier; + +/** + * Crust login type. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/01 02:44 + */ +public enum CrustLoginType { + /** + * Username and password type. + */ + PASSWORD, + /** + * Verify code type. + */ + CODE, + /** + * Custom login type used with {@link Crust#login(String, String, Class, CrustLoginType, Supplier)}. + */ + CUSTOM +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustMicroConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustMicroConfig.java new file mode 100644 index 00000000..e9f14a89 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustMicroConfig.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.crust; + +import com.github.yizzuide.milkomeda.universe.metadata.BeanIds; +import com.github.yizzuide.milkomeda.universe.polyfill.SpringMvcPolyfill; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.util.CollectionUtils; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Crust config used for microservice. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/07 00:12 + */ +@EnableConfigurationProperties(CrustProperties.class) +@Configuration +public class CrustMicroConfig { + + @Bean + public CrustTokenResolver crustTokenResolver(CrustProperties props) { + return new CrustTokenResolver(props); + } + + @Bean + public CrustInterceptor crustInterceptor() { + return new CrustInterceptor(); + } + + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + @Configuration + static class ExtendedConfig implements InitializingBean { + + @Autowired + private CrustInterceptor crustInterceptor; + + @Autowired + private CrustProperties crustProperties; + + @Qualifier(BeanIds.REQUEST_MAPPING_HANDLER_MAPPING) + @Autowired + private RequestMappingHandlerMapping requestMappingHandlerMapping; + + @Override + public void afterPropertiesSet() throws Exception { + List allowURLs = new ArrayList<>(crustProperties.getPermitUrls()); + List additionPermitUrls = crustProperties.getAdditionPermitUrls(); + if (!CollectionUtils.isEmpty(additionPermitUrls)) { + allowURLs.addAll(additionPermitUrls); + } + SpringMvcPolyfill.addDynamicInterceptor(crustInterceptor, Ordered.HIGHEST_PRECEDENCE + 1, Collections.singletonList("/**"), + allowURLs, requestMappingHandlerMapping); + } + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustPerm.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustPerm.java index 4b4bcca4..ce267b8e 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustPerm.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustPerm.java @@ -97,6 +97,7 @@ public CrustPerm build() { /** * Build spring security authorities. + * @param permissionList permission list * @return authorities list. * @since 3.14.0 */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustPermission.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustPermission.java index dca68c0b..9411f84e 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustPermission.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustPermission.java @@ -169,6 +169,7 @@ public interface CrustPermission extends Ordered { /** * Build perm tree from permission list. * @param permissionList permission list + * @param permClass permission class * @param parentId build start parent id * @return menu tree */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustProperties.java index 4a9a6d3a..dd02a3f3 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustProperties.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustProperties.java @@ -21,7 +21,6 @@ package com.github.yizzuide.milkomeda.crust; -import com.github.yizzuide.milkomeda.light.LightCache; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.convert.DurationUnit; @@ -36,7 +35,7 @@ * * @author yizzuide * @since 1.14.0 - * @version 3.12.10 + * @version 3.15.0 *
* Create at 2019/11/11 15:51 */ @@ -49,14 +48,40 @@ public class CrustProperties { private boolean stateless = true; /** - * 查询认证信息缓存(Session方式下仅开启超级缓存,因为Session本身有Session级缓存) + * Login type config for used with {@link Crust#login(String, String, Class)}. + * @since 3.15.0 + */ + private CrustLoginType loginType = CrustLoginType.PASSWORD; + + /** + * Code verify expire time, must set `CrustLoginType.CODE` with property of {@link CrustProperties#loginType}. + * @since 3.15.0 + */ + @DurationUnit(ChronoUnit.SECONDS) + private Duration codeExpire = Duration.ofSeconds(60); + + /** + * Set false if you need custom config via {@link CrustConfigurerAdapter}. + * @since 3.15.0 + */ + private boolean useAutoConfig = true; + + /** + * Set false if you use stateless token. + * 认证缓存(Session方式下仅开启超级缓存,因为Session本身有Session级缓存) */ private boolean enableCache = true; + + /** + * Set true if cache only used memory. + */ + private boolean cacheInMemory = false; + /** - * Token方式情况下,并且 enableCache=true,是否缓存到Redis
- * 注意:这个配置将覆盖缓存模块 {@link LightCache#getOnlyCacheL2()} 配置的值 + * Set false if you need get entity of user info immediately. + * @since 3.15.0 */ - private boolean enableCacheL2 = true; + private boolean enableLoadEntityLazy = true; /** * 使用非对称方式(默认为false)
@@ -102,7 +127,7 @@ public class CrustProperties { private boolean enableAutoRefreshToken = true; /** - * Token刷新间隔(默认5分钟,单位:分) + * Token访问即将过期的刷新间隔 */ @DurationUnit(ChronoUnit.MINUTES) private Duration refreshTokenInterval = Duration.ofMinutes(5); @@ -112,12 +137,6 @@ public class CrustProperties { */ private String refreshTokenName = "Authorization"; - /** - * default auth fail code. - * @since 3.14.0 - */ - private String authFailCode = "401"; - /** * 登录页面路径(仅在stateless=false时有效,默认/login) */ @@ -132,7 +151,7 @@ public class CrustProperties { * 默认允许访问的URL * @since 3.5.0 */ - private List permitURLs = Arrays.asList("/favicon.ico", "/druid/**", "/doc.html", "/webjars/**", + private List permitUrls = Arrays.asList("/favicon.ico", "/druid/**", "/doc.html", "/webjars/**", "/swagger-resources/**", "/swagger-ui.html"); /** diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustTokenResolver.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustTokenResolver.java new file mode 100644 index 00000000..ef3218af --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustTokenResolver.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.crust; + +import com.github.yizzuide.milkomeda.universe.context.WebContext; +import com.github.yizzuide.milkomeda.util.JwtUtil; +import com.github.yizzuide.milkomeda.util.Strings; +import io.jsonwebtoken.Claims; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Token resolver used for crust. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/08 03:17 + */ +@Data +@AllArgsConstructor +public class CrustTokenResolver { + /** + * 用户id + */ + private static final String UID = "uid"; + + /** + * 用户名称 + */ + private static final String USERNAME = Claims.SUBJECT; + + /** + * 创建时间 + */ + private static final String CREATED = Claims.ISSUED_AT; + + /** + * 角色id + */ + private static final String ROLE_IDS = "roles"; + + private CrustProperties props; + + /** + * Generate token with meta data. + * @param userInfo CrustUserInfo + * @param roleIds login user role id list + * @return token + * @param entity type + */ + public String generate(CrustUserInfo userInfo, String roleIds) { + Map claims = new HashMap<>(8); + claims.put(UID, userInfo.getUidLong()); + claims.put(USERNAME, userInfo.getUsername()); + claims.put(CREATED, new Date()); + if (StringUtils.hasText(roleIds)) { + claims.put(ROLE_IDS, roleIds); + } + long expire = LocalDateTime.now().plusMinutes(props.getExpire().toMinutes()).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + String token = JwtUtil.generateToken(claims, getSignKey(), expire, props.isUseRsa()); + userInfo.setToken(token); + userInfo.setTokenExpire(expire); + return token; + } + + /** + * Parse token to get meta data. + * @param token request token + * @param lazyLoad load entity with lazy callback + * @return CrustUserInfo + */ + @SuppressWarnings("rawtypes") + @Nullable + public CrustUserInfo resolve(String token, @Nullable Function lazyLoad) { + String unSignKey = getUnSignKey(); + Claims claims = JwtUtil.parseToken(token, unSignKey); + if (claims == null) { + return null; + } + String username = claims.getSubject(); + if (username == null) { + return null; + } + Serializable uid = (Serializable) claims.get(UID); + //long issuedAt = (long) claims.get(CREATED); + long expire = claims.getExpiration().getTime(); + Object RoleIdsObj = claims.get(ROLE_IDS); + List roleIds = null; + if (RoleIdsObj != null) { + roleIds = Arrays.stream(((String) RoleIdsObj).split(",")).map(Long::parseLong).collect(Collectors.toList()); + } + CrustEntity entity; + if (lazyLoad == null) { + entity = null; + } else { + entity = getProps().isEnableLoadEntityLazy() ? null : lazyLoad.apply(uid); + } + CrustUserInfo userInfo = new CrustUserInfo<>(uid, username, token, roleIds, entity); + userInfo.setTokenExpire(expire); + return userInfo; + } + + /** + * Refresh token. + * @param oldToken original token + * @param loginUserInfo CrustUserInfo + * @return a new token with next expires + */ + public String refresh(String oldToken, CrustUserInfo loginUserInfo) { + Claims claims = JwtUtil.parseToken(oldToken, getUnSignKey()); + claims.put(CREATED, new Date()); + long expire = LocalDateTime.now().plusMinutes(props.getExpire().toMinutes()).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + String refreshedToken = JwtUtil.generateToken(claims, getSignKey(), expire, props.isUseRsa()); + loginUserInfo.setToken(refreshedToken); + loginUserInfo.setTokenExpire(expire); + return refreshedToken; + } + + /** + * Get token from request header. + * @return token + */ + public String getRequestToken() { + String token = WebContext.getRequestNonNull().getHeader(props.getTokenName()); + if (Strings.isEmpty(token)) { return null; } + // 一般请求头Authorization的值会添加Bearer + String tokenHead = "Bearer "; + if (token.contains(tokenHead)) { + token = token.substring(tokenHead.length()); + } + return token; + } + + /** + * 获取加密key + * @return sign key + */ + private String getSignKey() { + if (props.isUseRsa()) { + return props.getPriKey(); + } + return props.getSecureKey(); + } + + /** + * 获取解密key + * @return unSign key + */ + private String getUnSignKey() { + if (props.isUseRsa()) { + return props.getPubKey(); + } + return props.getSecureKey(); + } + +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustUserDetailsService.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustUserDetailsService.java index bdb64205..2d5f4577 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustUserDetailsService.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustUserDetailsService.java @@ -45,10 +45,10 @@ public abstract class CrustUserDetailsService implements UserDetailsService { @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - CrustEntity entity = findEntityByUsername(username); + public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException { + CrustEntity entity = findEntityByUsername(account); if (entity == null) { - throw new UsernameNotFoundException("Not found entity with username: " + username); + throw new UsernameNotFoundException("Not found entity with account: " + account); } CrustUserInfo userInfo = new CrustUserInfo<>(); CrustPerm crustPerm = findPermissionsById(entity.getUid()); @@ -64,7 +64,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx userInfo.setPermissionList(permissionList); grantedAuthorities = CrustPerm.buildAuthorities(permissionList); } - userInfo.setEntity(entity); + userInfo.setEntity(CrustContext.get().getProps().isEnableLoadEntityLazy() ? null : entity); userInfo.setUid(entity.getUid()); userInfo.setUsername(entity.getUsername()); return new CrustUserDetails(userInfo.getUid(), userInfo.getUsername(), entity.getPassword(), diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustUserInfo.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustUserInfo.java index da4f881c..99d6df5c 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustUserInfo.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/CrustUserInfo.java @@ -39,7 +39,7 @@ * * @author yizzuide * @since 1.14.0 - * @version 1.17.3 + * @version 3.14.0 *
* Create at 2019/11/11 21:51 */ @@ -48,43 +48,49 @@ @NoArgsConstructor public class CrustUserInfo implements Serializable { private static final long serialVersionUID = -3849153553850107966L; + /** * 用户id */ private Serializable uid; + /** * 用户名 */ private String username; + /** * 认证token(stateless=true时有值) */ private String token; + /** * token过期时间 * @since 3.14.0 */ private Long tokenExpire; + /** * Check is admin user. * @since 3.14.0 */ private Boolean isAdmin; + /** * 角色id列表 */ private List roleIds; + /** * 权限列表 * @since 3.14.0 */ private List

permissionList; + /** - * 用户实体对象 - *
- * 注意,这个有没有值根据下面条件:
- * 如果stateless=false,那么是使用session方式,这个一定有实体对象
- * 如果stateless=true,那么是无状态token认证方式,该值默认为null,如果想要有值,可实现:
+ * 用户实体对象,它的值根据以下条件确定:
+ * 1.如果stateless=false,那么使用的是session方式,这个一定有实体对象
+ * 2.如果stateless=true,那么是无状态token认证方式,该值默认为null,如果想要有值,可实现:
*

      * public class XXXDetailsService extends CrustUserDetailsService {
      *   protected CrustEntity findEntityById(String uid) {
@@ -108,8 +114,6 @@ public class CrustUserInfo implements Serializable {
      */
     private Class permClass;
 
-
-
     public CrustUserInfo(Serializable uid, String username, String token, List roleIds, T entity) {
         this.uid = uid;
         this.username = username;
@@ -145,6 +149,10 @@ public Long getUidLong() {
      */
     @SuppressWarnings("unchecked")
     public T getEntity() {
+        Crust crust = CrustContext.get();
+        if (crust != null && crust.getProps().isEnableLoadEntityLazy() && this.entity == null) {
+            this.entity = crust.loadEntity(this.getUid());
+        }
         if (this.entity instanceof Map) {
             if (this.getEntityClass() == null) {
                 return this.entity;
@@ -157,6 +165,10 @@ public T getEntity() {
     @SuppressWarnings("unchecked")
     public void setPermissionList(List

permissionList) { if (!CollectionUtils.isEmpty(permissionList)) { + // user has no permissions! + if (permissionList.get(0) == null) { + return; + } boolean isMap = permissionList.get(0) instanceof Map; if (isMap && this.permClass != null) { this.permissionList = (List

) JSONUtil.parseList(JSONUtil.serialize(permissionList), this.permClass); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/EnableCrust.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/EnableCrust.java index 6ca769f8..30abe8c9 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/EnableCrust.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/EnableCrust.java @@ -36,9 +36,9 @@ *
* Create at 2019/11/11 15:14 */ -@Import(CrustConfig.class) +@Import({CrustConfig.class, CrustAutoConfigurer.class}) +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/EnableCrustMicro.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/EnableCrustMicro.java new file mode 100644 index 00000000..b5f05ab7 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/crust/EnableCrustMicro.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.crust; + +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + * Enable crust used in microservice that not need spring security environment, it just parses token to login info. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/07 00:09 + */ +@Import(CrustMicroConfig.class) +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface EnableCrustMicro { +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/fusion/FusionRegistration.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/fusion/FusionRegistration.java index d920ee3d..fed2a1a9 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/fusion/FusionRegistration.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/fusion/FusionRegistration.java @@ -43,6 +43,7 @@ *
* Create at 2020/05/05 16:23 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j public class FusionRegistration { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/halo/HaloContext.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/halo/HaloContext.java index 4194494a..1eb0bdf4 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/halo/HaloContext.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/halo/HaloContext.java @@ -51,12 +51,14 @@ public class HaloContext implements ApplicationListener { private static Map> tableNameMap = new HashMap<>(); - private static final Map> preTableNameMap = new HashMap<>(); + private static Map> preTableNameMap; - private static final Map> postTableNameMap = new HashMap<>(); + private static Map> postTableNameMap; @Override public void onApplicationEvent(@NonNull ContextRefreshedEvent event) { + preTableNameMap = new HashMap<>(); + postTableNameMap = new HashMap<>(); tableNameMap = SpringContext.getHandlerMetaData(HaloHandler.class, HaloListener.class, (annotation, handlerAnnotation, metaData) -> { HaloListener haloListener = (HaloListener) annotation; // 设置其它属性方法的值 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/halo/HaloInterceptor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/halo/HaloInterceptor.java index 2fef61e3..e9d7a91e 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/halo/HaloInterceptor.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/halo/HaloInterceptor.java @@ -21,6 +21,10 @@ package com.github.yizzuide.milkomeda.halo; +import com.github.yizzuide.milkomeda.universe.aop.invoke.args.ArgumentDefinition; +import com.github.yizzuide.milkomeda.universe.aop.invoke.args.ArgumentMatchType; +import com.github.yizzuide.milkomeda.universe.aop.invoke.args.ArgumentSources; +import com.github.yizzuide.milkomeda.universe.aop.invoke.args.MethodArgumentBinder; import com.github.yizzuide.milkomeda.universe.metadata.HandlerMetaData; import com.github.yizzuide.milkomeda.util.MybatisUtil; import lombok.extern.slf4j.Slf4j; @@ -37,10 +41,10 @@ import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; -import org.apache.ibatis.session.defaults.DefaultSqlSession; import org.apache.ibatis.type.TypeHandlerRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; +import org.springframework.util.ReflectionUtils; import java.lang.reflect.Method; import java.time.Instant; @@ -56,10 +60,11 @@ * * @author yizzuide * @since 2.5.0 - * @version 3.11.4 + * @version 3.15.0 *
* Create at 2020/01/30 20:38 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @@ -158,7 +163,7 @@ private Object warpIntercept(Invocation invocation, MappedStatement mappedStatem } // Mybatis map参数获取不存在时会抛异常,转为普通map - if (param instanceof DefaultSqlSession.StrictMap || param instanceof MapperMethod.ParamMap) { + if (/*param instanceof DefaultSqlSession.StrictMap || */param instanceof MapperMethod.ParamMap) { param = new HashMap((Map) param); } @@ -204,26 +209,18 @@ private void invokeHandler(String tableName, HandlerMetaData handlerMetaData, St Object target = handlerMetaData.getTarget(); // 获取参数类型 Class[] parameterTypes = method.getParameterTypes(); + ReflectionUtils.makeAccessible(method); if (parameterTypes.length == 1 && parameterTypes[0] == HaloMeta.class) { HaloMeta haloMeta = new HaloMeta(sqlCommandType, tableName, param, result); method.invoke(target, haloMeta); } else if (parameterTypes.length > 1) { - // 检测参数是否有SqlCommandType - if (parameterTypes[0] == SqlCommandType.class) { - if (result == null) { - method.invoke(target, sqlCommandType, param); - } else { - method.invoke(target, sqlCommandType, param, result); - } - } else { - if (result == null) { - method.invoke(target, param, sqlCommandType); - } else { - method.invoke(target, param, result, sqlCommandType); - } - } - } else { - method.invoke(target, param); + // invoke and bind args dynamically + ArgumentSources argumentSources = new ArgumentSources(); + argumentSources.add(new ArgumentDefinition(ArgumentMatchType.BY_NAME_PREFIX,"param", Object.class, param)); + argumentSources.add(new ArgumentDefinition(ArgumentMatchType.BY_TYPE,null, SqlCommandType.class, sqlCommandType)); + argumentSources.add(new ArgumentDefinition(ArgumentMatchType.Residual,null, Object.class, result)); + Object[] args = MethodArgumentBinder.bind(argumentSources, method); + method.invoke(target, args); } } catch (Exception e) { log.error("Halo invoke handler [{}] error: {}, with stmt id: {} and sql: {}", handlerMetaData.getTarget(), e.getMessage(), mappedStatement.getId(), sql, e); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/filter/ServletContextListener.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/filter/ServletContextListener.java index 04f61301..cc338ee8 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/filter/ServletContextListener.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/filter/ServletContextListener.java @@ -36,6 +36,7 @@ *
* Create at 2020/04/01 18:18 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") public class ServletContextListener implements ServletContextInitializer { @Autowired diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/filter/TomcatFilterLoader.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/filter/TomcatFilterLoader.java index f6809c15..b346ae21 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/filter/TomcatFilterLoader.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/filter/TomcatFilterLoader.java @@ -48,6 +48,7 @@ *
* Create at 2020/04/01 18:19 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j public class TomcatFilterLoader extends AbstractFilterLoader { /** diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/i18n/I18nConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/i18n/I18nConfig.java index 2f2d85bc..9579180d 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/i18n/I18nConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/i18n/I18nConfig.java @@ -22,6 +22,7 @@ package com.github.yizzuide.milkomeda.hydrogen.i18n; import com.github.yizzuide.milkomeda.hydrogen.core.HydrogenHolder; +import com.github.yizzuide.milkomeda.universe.metadata.BeanIds; import com.github.yizzuide.milkomeda.universe.polyfill.SpringMvcPolyfill; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; @@ -87,7 +88,7 @@ static class ExtendedConfig implements InitializingBean { @Autowired private I18nMessages i18nMessages; - @Qualifier("requestMappingHandlerMapping") + @Qualifier(BeanIds.REQUEST_MAPPING_HANDLER_MAPPING) @Autowired private RequestMappingHandlerMapping requestMappingHandlerMapping; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/interceptor/InterceptorConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/interceptor/InterceptorConfig.java index 52825136..8af712ea 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/interceptor/InterceptorConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/interceptor/InterceptorConfig.java @@ -21,7 +21,9 @@ package com.github.yizzuide.milkomeda.hydrogen.interceptor; +import com.github.yizzuide.milkomeda.universe.metadata.BeanIds; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -48,7 +50,8 @@ public class InterceptorConfig { @Bean @ConditionalOnClass(name = "org.springframework.web.servlet.HandlerInterceptor") - public InterceptorLoader interceptorHandler(InterceptorProperties interceptorProperties, RequestMappingHandlerMapping requestMappingHandlerMapping) { + public InterceptorLoader interceptorHandler(InterceptorProperties interceptorProperties, + @Qualifier(BeanIds.REQUEST_MAPPING_HANDLER_MAPPING) RequestMappingHandlerMapping requestMappingHandlerMapping) { return new WebMvcInterceptorLoader(interceptorProperties, requestMappingHandlerMapping); } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/transaction/TransactionConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/transaction/TransactionConfig.java index d8fef822..4b56fb19 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/transaction/TransactionConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/transaction/TransactionConfig.java @@ -35,6 +35,7 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource; import org.springframework.transaction.interceptor.RollbackRuleAttribute; import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute; @@ -55,32 +56,50 @@ */ @Aspect @Configuration -@EnableConfigurationProperties(TransactionProperties.class) +@EnableTransactionManagement @AutoConfigureAfter(TransactionAutoConfiguration.class) +@EnableConfigurationProperties(TransactionProperties.class) @ConditionalOnProperty(prefix = "milkomeda.hydrogen.transaction", name = "enable", havingValue = "true") public class TransactionConfig { @Autowired private TransactionProperties props; + // PlatformTransactionManager是事务规范接口(实现有JDBC的DataSourceTransactionManager等),事务由具体数据库来现实, + // 而TransactionDefinition和TransactionStatus这两个接口分别是事务的定义和运行状态。 + // Spring的事务通过AOP动态代理:TransactionInterceptor.invoke() -> TransactionAspectSupport.invokeWithinTransaction() @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Bean public TransactionInterceptor txAdvice(PlatformTransactionManager transactionManager) { RuleBasedTransactionAttribute txAttr_REQUIRED = new RuleBasedTransactionAttribute(); - // 设置传播行为:若当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。 + // 设置传播行为: + // PROPAGATION_REQUIRED:若当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 + // PROPAGATION_REQUIRED_NEW:如果当前没有事务,创建一个新的事务;如果当前存在事务,则把当前事务挂起,再创建新事务,使执行相互独立。 + // 事务回滚原则:事务A调用事务B,事务B抛出异常回滚,由于没捕获被事务A监听到而导致事务A也回滚 + // PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。 + // 事务回滚原则:外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。 txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 抛出异常后执行切点回滚 txAttr_REQUIRED.setRollbackRules(props.getRollbackWhenException() .stream().map(RollbackRuleAttribute::new).collect(Collectors.toList())); + // 设置隔离级别 + // ISOLATION_READ_UNCOMMITTED:读未提交,可能会产生脏读、不可重复读(同一事务多次读取记录值时不一样,重点在修改)、幻读(多次读取记录条数不一样,重在插入和删除)。 + // ISOLATION_READ_COMMITTED:读已提交,可能会产生不可重复读、幻读。 + // ISOLATION_REPEATABLE_READ:可重复读,可能会产生幻读。 + // InnoDB存储引擎在 REPEATABLE-READ(可重读)事务隔离级别下使用的是 Next-Key Lock 锁(记录锁+ Cap锁),且不会造成任何性能上的损失,因此可以避免幻读的产生。 + // ISOLATION_SERIALIZABLE:串行化,完全遵行ACID,解决所有问题,但性能会大幅下降。 + txAttr_REQUIRED.setIsolationLevel(props.getIsolationLevel().value()); // 设置超时 txAttr_REQUIRED.setTimeout((int) props.getRollbackWhenTimeout().getSeconds()); + // 为什么要有只读事务? + // 由于MySQL默认对每一个新建立的连接都启用了autocommit模式。在该模式下,每一个发送到 MySQL 服务器的sql语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务。 + // 如果不加@Transactional,每条sql会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值,这样会导致数据不一致。 RuleBasedTransactionAttribute txAttr_REQUIRED_READONLY = new RuleBasedTransactionAttribute(); txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txAttr_REQUIRED_READONLY.setReadOnly(true); NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource(); - // 开启只读, 提高数据库访问性能 if (!CollectionUtils.isEmpty(props.getReadOnlyPrefix())) { for (String prefix : props.getReadOnlyPrefix()) { source.addTransactionalMethod(prefix, txAttr_REQUIRED_READONLY); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/transaction/TransactionProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/transaction/TransactionProperties.java index 681c5131..41d63708 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/transaction/TransactionProperties.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/transaction/TransactionProperties.java @@ -24,11 +24,11 @@ import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.convert.DurationUnit; +import org.springframework.transaction.annotation.Isolation; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Arrays; -import java.util.Collections; import java.util.List; /** @@ -42,27 +42,39 @@ @Data @ConfigurationProperties("milkomeda.hydrogen.transaction") public class TransactionProperties { + /** - * 启用AOP事务 + * 启用AOP事务(只有作用到 public 方法上事务才生效,不推荐在接口上使用) */ private boolean enable = false; + /** * 切点表达式 */ private String pointcutExpression = "execution(* com..service.*.*(..))"; + /** - * 事务超时回滚(默认单位:s。-1:不设置超时回滚) + * Transaction isolation level. + * @since 3.15.0 + */ + private Isolation isolationLevel = Isolation.DEFAULT; + + /** + * 事务超时回滚,单位s(默认-1:不设置超时回滚) */ @DurationUnit(ChronoUnit.SECONDS) private Duration rollbackWhenTimeout = Duration.ofSeconds(-1); + /** - * 指定异常类回滚 + * 指定异常类回滚(默认为RuntimeException和Error) */ - private List> rollbackWhenException = Collections.singletonList(Exception.class); + private List> rollbackWhenException = Arrays.asList(RuntimeException.class, Error.class); + /** * 只读事务方法前辍 */ private List readOnlyPrefix = Arrays.asList("get*", "query*", "find*", "select*", "list*", "count*", "is*"); + /** * 追加只读事务方法前辍 */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/ResultVO.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/ResultVO.java index b209a228..8e85bce5 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/ResultVO.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/ResultVO.java @@ -21,10 +21,21 @@ package com.github.yizzuide.milkomeda.hydrogen.uniform; +import com.github.yizzuide.milkomeda.comet.core.CometResponseInterceptor; +import com.github.yizzuide.milkomeda.universe.parser.yml.YmlResponseOutput; + +import java.util.HashMap; import java.util.Map; /** - * View Object interface. + * This interface is specification as a response. The default implementation is {@link UniformResult}, its used with + * {@link CometResponseInterceptor} of comet module which default implementation is {@link UniformResponseInterceptor}. + * If used this type to declare response, must config the follow: + *

+ *     1. Enable response wrapper with config: milkomeda.comet.enable-read-response-body=true.
+ *     2. Add response interceptor with config: milkomeda.comet.response-interceptors.uniform.enable=true.
+ *     3. (Optional) If you need change the response field name, such as change `message` to `msg` with config: milkomeda.hydrogen.uniform.response.200.message[msg]="".
+ * 
* * @since 3.14.0 * @author yizzuide @@ -33,7 +44,7 @@ */ public interface ResultVO { /** - * code field type. + * Code field type. */ enum CodeType { INT, @@ -57,8 +68,14 @@ enum CodeType { T getData(); /** - * Convert to map. + * Convert to standard response map with filed: code, message, data. * @return Map */ - Map toMap(); + default Map toMap() { + Map map = new HashMap<>(8); + map.put(YmlResponseOutput.CODE, getCode()); + map.put(YmlResponseOutput.MESSAGE, getMessage()); + map.put(YmlResponseOutput.DATA, getData()); + return map; + } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformConfig.java index 39749f8d..4f78314a 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformConfig.java @@ -25,14 +25,26 @@ import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; import com.github.yizzuide.milkomeda.universe.polyfill.SpringMvcPolyfill; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.error.ErrorAttributes; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.lang.NonNull; +import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.stream.Collectors; /** * UniformConfig @@ -47,14 +59,13 @@ * Create at 2020/03/25 22:46 */ @Import(MilkomedaContextConfig.class) -@EnableConfigurationProperties(UniformProperties.class) +@AutoConfigureBefore(ErrorMvcAutoConfiguration.class) +@EnableConfigurationProperties({UniformProperties.class, ServerProperties.class}) @ConditionalOnProperty(prefix = "milkomeda.hydrogen.uniform", name = "enable", havingValue = "true") @Configuration public class UniformConfig { - // 注入需要使用的ApplicationContext(让MilkomedaContextConfig先配置) - @SuppressWarnings("unused") @Autowired - private ApplicationContextHolder applicationContextHolder; + private ServerProperties serverProperties; @Bean public UniformHandler uniformHandler() { @@ -66,9 +77,21 @@ public UniformResponseInterceptor uniformResponseInterceptor() { return new UniformResponseInterceptor(); } + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + @Bean + public UniformErrorController uniformErrorController(ErrorAttributes errorAttributes, + ObjectProvider errorViewResolvers) { + return new UniformErrorController(errorAttributes, this.serverProperties.getError(), + errorViewResolvers.orderedStream().collect(Collectors.toList())); + } + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Configuration static class ExtendedConfig implements InitializingBean { + // 注入需要使用的ApplicationContext(让MilkomedaContextConfig先配置) + @SuppressWarnings("unused") + @Autowired + private ApplicationContextHolder applicationContextHolder; @Autowired private UniformProperties props; @@ -84,7 +107,22 @@ public void afterPropertiesSet() throws Exception { // 动态添加异常切面 private void configExceptionAdvice() { - SpringMvcPolyfill.addDynamicExceptionAdvice(handlerExceptionResolver, ApplicationContextHolder.get(), "uniformHandler"); + SpringMvcPolyfill.addDynamicExceptionAdvice(handlerExceptionResolver, applicationContextHolder.getApplicationContext(), "uniformHandler"); + } + } + + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(UniformProperties.class) + public static class RequestMappingConfigurer implements WebMvcConfigurer { + + @Autowired + private UniformProperties props; + + @Override + public void configurePathMatch(@NonNull PathMatchConfigurer configurer) { + if (StringUtils.hasText(props.getRequestPathPrefix())) { + configurer.addPathPrefix(props.getRequestPathPrefix(), p -> true); + } } } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformErrorController.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformErrorController.java new file mode 100644 index 00000000..5f533213 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformErrorController.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.hydrogen.uniform; + +import com.github.yizzuide.milkomeda.universe.lang.Tuple; +import com.github.yizzuide.milkomeda.universe.parser.yml.YmlResponseOutput; +import com.github.yizzuide.milkomeda.util.JSONUtil; +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.autoconfigure.web.ErrorProperties; +import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; +import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver; +import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Request mapping handle for 404, 406. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/07 18:49 + */ +@Controller +@RequestMapping("${server.error.path:${error.path:/error}}") +public class UniformErrorController extends BasicErrorController { + + public UniformErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) { + super(errorAttributes, errorProperties); + } + + public UniformErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List errorViewResolvers) { + super(errorAttributes, errorProperties, errorViewResolvers); + } + + @Override + public ResponseEntity> error(HttpServletRequest request) { + HttpStatus status = getStatus(request); + if(UniformHandler.tryMatch(status.value())) { + Tuple> mapTuple = uniformMatchResult(request, status); + return ResponseEntity.status(mapTuple.getT1()).body(mapTuple.getT2()); + } + return super.error(request); + } + + // handle http content-type not match + @Override + public ResponseEntity mediaTypeNotAcceptable(HttpServletRequest request) { + HttpStatus status = getStatus(request); + if(UniformHandler.tryMatch(status.value())) { + Tuple> mapTuple = uniformMatchResult(request, status); + return ResponseEntity.status(mapTuple.getT1()).body(JSONUtil.serialize(mapTuple.getT2())); + } + return super.mediaTypeNotAcceptable(request); + } + + @NotNull + private Tuple> uniformMatchResult(HttpServletRequest request, HttpStatus status) { + Map source = new HashMap<>(); + Map originalMap = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); + String error = (String) originalMap.get("error"); + source.put(YmlResponseOutput.MESSAGE, error); + Tuple, Map> mapTuple = UniformHandler.matchStatusResult(status.value(), source); + int resolveStatus = Integer.parseInt(mapTuple.getT1().get(YmlResponseOutput.STATUS).toString()); + Map body = mapTuple.getT2(); + return Tuple.build(HttpStatus.resolve(resolveStatus), body); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformExceptionDataAssert.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformExceptionDataAssert.java index 440ced05..771e6f8d 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformExceptionDataAssert.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformExceptionDataAssert.java @@ -24,7 +24,6 @@ import java.text.MessageFormat; /** - * UniformExceptionAssert * 统一异步断言,最终需要被枚举类实现 * * @author yizzuide @@ -45,7 +44,7 @@ default UniformException newException(Throwable t, Object... args) { } /** - * Subclasses can override and implement different formatting message, {@link MessageFormat} is used by default + * Subclasses can override and implement a different formatting message, {@link MessageFormat} is used by default. * @param msg exception message * @param args exception args * @return format message diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformHandler.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformHandler.java index 3a9e7e82..e351f466 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformHandler.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformHandler.java @@ -22,10 +22,10 @@ package com.github.yizzuide.milkomeda.hydrogen.uniform; import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; -import com.github.yizzuide.milkomeda.universe.context.WebContext; import com.github.yizzuide.milkomeda.universe.lang.Tuple; import com.github.yizzuide.milkomeda.universe.parser.yml.YmlParser; import com.github.yizzuide.milkomeda.universe.parser.yml.YmlResponseOutput; +import com.github.yizzuide.milkomeda.util.DataTypeConvertUtil; import com.github.yizzuide.milkomeda.util.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -37,6 +37,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; @@ -58,22 +59,25 @@ /** * UniformHandler * - * @author yizzuide - * @since 3.0.0 - * @version 3.14.1 * @see org.springframework.boot.SpringApplication#run(java.lang.String...) * #see org.springframework.boot.SpringApplication#registerLoggedException(java.lang.Throwable) * #see org.springframework.boot.SpringBootExceptionHandler.LoggedExceptionHandlerThreadLocal#initialValue() * @see org.springframework.boot.SpringApplication#setRegisterShutdownHook(boolean) * @see org.springframework.context.support.AbstractApplicationContext#registerShutdownHook() + * @author yizzuide + * @since 3.0.0 + * @version 3.15.0 *
* Create at 2020/03/25 22:47 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j // 可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute, 并应用到所有@RequestMapping中 //@ControllerAdvice // 这种方式默认就会扫描并加载到Ioc,不好动态控制是否加载,但好处是外部API对未来版本的兼容性强 public class UniformHandler extends ResponseEntityExceptionHandler { + public static final int REQUEST_BEFORE_EXCEPTION_CODE = 5000; + @Autowired private UniformProperties props; @@ -115,7 +119,9 @@ public void init() { } private Class createExceptionClass(Object clazz) { - if (!(clazz instanceof String)) return null; + if (!(clazz instanceof String)) { + return null; + } Class expClazz = null; try { expClazz = Class.forName(clazz.toString()); @@ -140,8 +146,12 @@ private Class createExceptionClass(Object clazz) { public ResponseEntity constraintViolationException(ConstraintViolationException e) { ConstraintViolation constraintViolation = e.getConstraintViolations().iterator().next(); String value = String.valueOf(constraintViolation.getInvalidValue()); - String message = WebContext.getRequestNonNull().getRequestURI() + - " [" + constraintViolation.getPropertyPath() + "=" + value + "] " + constraintViolation.getMessage(); + String message; + if (props.isIgnoreAddFieldOnValidFail()) { + message = constraintViolation.getMessage(); + } else { + message = "[" + constraintViolation.getPropertyPath() + "=" + value + "] " + constraintViolation.getMessage(); + } log.warn("Hydrogen uniform valid response exception with msg: {} ", message); ResponseEntity responseEntity = handleExceptionResponse(e, HttpStatus.BAD_REQUEST.value(), message); return responseEntity == null ? ResponseEntity.status(HttpStatus.BAD_REQUEST.value()).body(null) : responseEntity; @@ -194,9 +204,11 @@ public ResponseEntity handleException(Throwable e) { private ResponseEntity handleValidBeanExceptionResponse(Exception ex, BindingResult bindingResult) { ObjectError objectError = bindingResult.getAllErrors().get(0); String message = objectError.getDefaultMessage(); - if (objectError.getArguments() != null && objectError.getArguments().length > 0) { - FieldError fieldError = (FieldError) objectError; - message = WebContext.getRequestNonNull().getRequestURI() + " [" + fieldError.getField() + "=" + fieldError.getRejectedValue() + "] " + message; + if (!props.isIgnoreAddFieldOnValidFail()) { + if (objectError.getArguments() != null && objectError.getArguments().length > 0) { + FieldError fieldError = (FieldError) objectError; + message = "[" + fieldError.getField() + "=" + fieldError.getRejectedValue() + "] " + message; + } } log.warn("Hydrogen uniform valid response exception with msg: {} ", message); return handleExceptionResponse(ex, HttpStatus.BAD_REQUEST.value(), message); @@ -248,20 +260,35 @@ private ResponseEntity handleInnerErrorExceptionResponse(Exception ex, M } /** - * Used for external match with status code to get response result. - * @param response response object + * Try match response resolve with code before write. + * @param code response status code + * @return true if matched + * @since 3.15.0 + */ + public static boolean tryMatch(int code) { + UniformProperties props = Binder.get(ApplicationContextHolder.get().getEnvironment()).bind(UniformProperties.PREFIX, UniformProperties.class).get(); + if (props == null) { + return false; + } + Map resolveMap = (Map) props.getResponse().get(String.valueOf(code)); + return resolveMap != null; + } + + /** + * Used for external match with status code to get the response result. + * @param statusCode response status * @param source replace data * @return tuple(yml node map, response content) - * @since 3.14.0 + * @since 3.15.0 */ @SuppressWarnings("unchecked") - public static Tuple, Map> matchStatusResult(HttpServletResponse response, Map source) { + public static Tuple, Map> matchStatusResult(int statusCode, Map source) { UniformProperties props = Binder.get(ApplicationContextHolder.get().getEnvironment()).bind(UniformProperties.PREFIX, UniformProperties.class).get(); Map resolveMap; if (props == null) { resolveMap = createInitResolveMap(); } else { - resolveMap = (Map) props.getResponse().get(String.valueOf(response.getStatus())); + resolveMap = (Map) props.getResponse().get(String.valueOf(statusCode)); if (resolveMap == null) { resolveMap = createInitResolveMap(); } @@ -269,20 +296,38 @@ public static Tuple, Map> matchStatusResult( Map result = new HashMap<>(); // status == 200? - if (response.getStatus() == HttpStatus.OK.value()) { + if (statusCode == HttpStatus.OK.value()) { YmlParser.parseAliasMapPath(resolveMap, result, YmlResponseOutput.CODE, null, source); YmlParser.parseAliasMapPath(resolveMap, result, YmlResponseOutput.MESSAGE, null, source); YmlParser.parseAliasMapPath(resolveMap, result, YmlResponseOutput.DATA, null, source); resultFilter(result); } else { // status != 200 + // 源Code字段为空或已配置了Code的值,就使用配置的值 + Object code = source.get(YmlResponseOutput.CODE); + Object configCode = resolveMap.get(YmlResponseOutput.CODE); + if (code == null || (configCode != null && StringUtils.hasText(configCode.toString()))) { + source.put(YmlResponseOutput.CODE, resolveMap.get(YmlResponseOutput.CODE)); + } YmlResponseOutput.output(resolveMap, result, source, null, false); } return Tuple.build(resolveMap, result); } + /** + * Used for external match with status code to get the response result. + * @param response response object + * @param source replace data + * @return tuple(yml node map, response content) + * @since 3.14.0 + */ + + public static Tuple, Map> matchStatusResult(HttpServletResponse response, Map source) { + return matchStatusResult(response.getStatus(), source); + } + @NotNull private static Map createInitResolveMap() { - Map resolveMap = new HashMap<>(7); + Map resolveMap = new HashMap<>(8); resolveMap.put(YmlResponseOutput.STATUS, HttpStatus.OK.value()); resolveMap.put(YmlResponseOutput.CODE, "0"); resolveMap.put(YmlResponseOutput.MESSAGE, ""); @@ -290,6 +335,27 @@ private static Map createInitResolveMap() { return resolveMap; } + /** + * Used for external match with status code to write. + * @param response response object + * @param status http status code + * @param e exception + * @throws IOException if an input or output exception occurred + * @since 3.15.0 + */ + public static void matchStatusToWrite(HttpServletResponse response, Integer status, Exception e) throws IOException { + response.setStatus(status == null ? REQUEST_BEFORE_EXCEPTION_CODE : status); + ResultVO source; + if (e != null) { + Map exMap = DataTypeConvertUtil.beanToMap(e); + Object code = exMap.get(YmlResponseOutput.CODE); + source = UniformResult.error(String.valueOf(code != null ? code : response.getStatus()), e.getMessage()); + } else { + source = UniformResult.error(String.valueOf(response.getStatus()), ""); + } + UniformHandler.matchStatusToWrite(response, source.toMap()); + } + /** * Used for external match with status code to write. * @param response response object diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformProperties.java index 77988c76..3986c0cc 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformProperties.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformProperties.java @@ -45,12 +45,24 @@ public class UniformProperties { */ private boolean enable = false; + /** + * 添加统一请求访问路径前缀 + * @since 3.15.0 + */ + private String requestPathPrefix; + /** * Response code type. * @since 3.14.0 */ private ResultVO.CodeType codeType = ResultVO.CodeType.INT; + /** + * Ignore add field to message when valid fail. + * @since 3.15.0 + */ + private boolean ignoreAddFieldOnValidFail = false; + /** * 响应数据 */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformQueryData.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformQueryData.java index 28588f84..4238283a 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformQueryData.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformQueryData.java @@ -39,12 +39,28 @@ public class UniformQueryData { * 模型实体 */ private T entity; + /** * 开始时间 */ private Date startDate; + /** * 结束时间 */ private Date endDate; + + public Long getStartUnixTime() { + if (getStartDate() == null) { + return null; + } + return getStartDate().getTime() / 1000; + } + + public Long getEndUnixTime() { + if (getEndDate() == null) { + return null; + } + return getEndDate().getTime() / 1000; + } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformResponseInterceptor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformResponseInterceptor.java index c525a8ea..2ef27d86 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformResponseInterceptor.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformResponseInterceptor.java @@ -21,64 +21,37 @@ package com.github.yizzuide.milkomeda.hydrogen.uniform; -import com.github.yizzuide.milkomeda.comet.core.CometResponseInterceptor; -import com.github.yizzuide.milkomeda.universe.lang.Tuple; +import com.github.yizzuide.milkomeda.comet.core.AbstractResponseInterceptor; +import com.github.yizzuide.milkomeda.universe.extend.annotation.Alias; import com.github.yizzuide.milkomeda.universe.parser.yml.YmlResponseOutput; -import com.github.yizzuide.milkomeda.util.JSONUtil; import lombok.extern.slf4j.Slf4j; -import org.springframework.core.Ordered; -import org.springframework.util.FastByteArrayOutputStream; import javax.servlet.http.HttpServletResponse; -import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; /** - * Uniform impl of response wrapper interceptor. + * Uniform implementation of response interceptor. It works when response type is subclass of {@link ResultVO}. * * @since 3.14.0 + * @version 3.15.0 * @author yizzuide *
* Create at 2022/10/10 17:57 */ @Slf4j -public class UniformResponseInterceptor implements CometResponseInterceptor { +@Alias("uniform") +public class UniformResponseInterceptor extends AbstractResponseInterceptor { @Override - public boolean writeToResponse(FastByteArrayOutputStream outputStream, HttpServletResponse wrapperResponse, HttpServletResponse rawResponse, Object body) { + protected Object doResponse(HttpServletResponse response, Object body) { if (body instanceof ResultVO) { ResultVO resultVO = (ResultVO) body; - Map source = new HashMap<>(5); + Map source = new HashMap<>(8); source.put(YmlResponseOutput.CODE, resultVO.getCode()); source.put(YmlResponseOutput.MESSAGE, resultVO.getMessage()); source.put(YmlResponseOutput.DATA, resultVO.getData()); - try { - Tuple, Map> mapTuple = UniformHandler.matchStatusResult(rawResponse, source); - // has config 200? - if (mapTuple.getT1() == null || mapTuple.getT1().size() == 0) { - return false; - } - Map result = mapTuple.getT2(); - String content = JSONUtil.serialize(result); - // reset content and length - byte[] bytes = content.getBytes(StandardCharsets.UTF_8); - wrapperResponse.resetBuffer(); - wrapperResponse.setContentLength(bytes.length); - outputStream.write(bytes); - rawResponse.setContentLength(outputStream.size()); - // write to response - outputStream.writeTo(rawResponse.getOutputStream()); - return true; - } catch (Exception e) { - log.error("uniform response error with msg: {}", e.getMessage(), e); - return false; - } + return UniformHandler.matchStatusResult(response, source).getT2(); } - return false; - } - - @Override - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE; + return null; } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformResult.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformResult.java index c5b64edc..ab779343 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformResult.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/uniform/UniformResult.java @@ -21,37 +21,34 @@ package com.github.yizzuide.milkomeda.hydrogen.uniform; -import com.github.yizzuide.milkomeda.universe.parser.yml.YmlResponseOutput; import lombok.Data; -import java.util.HashMap; -import java.util.Map; - /** - * Uniformed result view object. + * Uniformed result response object. The usage document see {@link ResultVO}. * * @since 3.14.0 + * @version 3.15.0 * @author yizzuide *
* Create at 2022/10/10 16:24 */ @Data public class UniformResult implements ResultVO { - + /** + * Response code. + */ private String code; + /** + * Response message. + */ private String message; + /** + * Response data. + */ private T data; - public Map toMap() { - Map map = new HashMap<>(5); - map.put(YmlResponseOutput.CODE, getCode()); - map.put(YmlResponseOutput.MESSAGE, getMessage()); - map.put(YmlResponseOutput.DATA, getData()); - return map; - } - /** * Return success. * @param data success data @@ -64,6 +61,22 @@ public static ResultVO ok(T data) { return resultVo; } + /** + * Return success with code and empty message. + * @param code result code + * @param data success data + * @param data type + * @return ResultVO + * @since 3.15.0 + */ + public static ResultVO ok(String code, T data) { + UniformResult resultVo = new UniformResult<>(); + resultVo.setCode(code); + resultVo.setMessage(""); + resultVo.setData(data); + return resultVo; + } + /** * Return failure. * @param code failure code diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/validator/ValidatorConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/validator/ValidatorConfig.java index 70107f56..19b0fce7 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/validator/ValidatorConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/hydrogen/validator/ValidatorConfig.java @@ -22,6 +22,7 @@ package com.github.yizzuide.milkomeda.hydrogen.validator; import com.github.yizzuide.milkomeda.hydrogen.core.HydrogenHolder; +import lombok.Cleanup; import org.hibernate.validator.HibernateValidator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -66,6 +67,7 @@ public MethodValidationPostProcessor methodValidationPostProcessor() { @Bean public Validator validator() { + @Cleanup ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class ) .configure() // 设置validator模式为快速失败(只要有一个校验不通过就不立即返回错误) diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/DelayJob.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/DelayJob.java index 7e15b421..9fd6482d 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/DelayJob.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/DelayJob.java @@ -41,10 +41,12 @@ @Data @NoArgsConstructor public class DelayJob implements Serializable { + private static final long serialVersionUID = -1408197881231593037L; /** - * 延迟任务的唯一标识(长度最好小于62位,因为存储重试次数多占了两位,用于Redis的 ziplist 内存存储优化) + * 延迟任务的唯一标识(当长度都小于64字节时,可用于Redis ZSet数据结构SkipList到ZipList的存储优化) + * 内部存储格式:jodId#retryCount */ private String jodId; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/DelayJobHandler.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/DelayJobHandler.java index 94c09b96..05cbb3b1 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/DelayJobHandler.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/DelayJobHandler.java @@ -22,6 +22,7 @@ package com.github.yizzuide.milkomeda.ice; import com.github.yizzuide.milkomeda.ice.inspector.JobInspector; +import com.github.yizzuide.milkomeda.universe.extend.env.Environment; import com.github.yizzuide.milkomeda.universe.metadata.HandlerMetaData; import com.github.yizzuide.milkomeda.universe.polyfill.RedisPolyfill; import com.github.yizzuide.milkomeda.util.RedisUtil; @@ -106,7 +107,9 @@ public void run() { // 延迟桶处理锁住资源,防止同一桶索引分布式并发执行时出现相同记录问题 if (props.isEnableJobTimerDistributed()) { boolean hasObtainLock = RedisUtil.setIfAbsent(this.lockKey, props.getJobTimerLockTimeoutSeconds().getSeconds(), redisTemplate); - if (!hasObtainLock) return; + if (!hasObtainLock) { + return; + } } DelayJob delayJob = null; @@ -144,7 +147,7 @@ public void run() { processDelayJob(delayJob, job); } } catch (Exception e) { - log.error("Ice Timer处理延迟Job {} 异常:{}", delayJob != null ? + log.error("Ice Timer处理延迟Job[{}]异常:{}", delayJob != null ? delayJob.getJodId() : "[任务数据获取失败]", e.getMessage(), e); } finally { if (props.isEnableJobTimerDistributed()) { @@ -160,11 +163,11 @@ public void run() { @SuppressWarnings({"unchecked", "rawtypes"}) private void processTtrJob(DelayJob delayJob, Job job) { if (delayJob.getRetryCount() > Integer.MAX_VALUE - 1) { - log.error("Ice处理TTR的Job {}, 重试次数超过Integer.MAX_VALUE,放弃重试", delayJob.getJodId()); + log.error("Ice处理TTR的Job[{}], 重试次数超过Integer.MAX_VALUE,放弃重试", delayJob.getJodId()); return; } int currentRetryCount = delayJob.getRetryCount() + 1; - log.warn("Ice处理TTR的Job {},当前重试次数为{}", delayJob.getJodId(), currentRetryCount); + log.warn("Ice处理TTR的Job[{}],当前重试次数为{}", delayJob.getJodId(), currentRetryCount); // 检测重试次数过载 boolean overload = delayJob.getRetryCount() >= job.getRetryCount(); @@ -185,7 +188,7 @@ private void processTtrJob(DelayJob delayJob, Job job) { } if (overload) { - log.error("Ice检测到 Job {} 的TTR超过预设的{}次!", job.getId(), job.getRetryCount()); + log.error("Ice检测到Job[{}]的TTR超过预设的{}次!", job.getId(), job.getRetryCount()); // 调用TTR Overload监听器 List ttrOverloadMetaDataList = IceContext.getTopicTtrOverloadMap().get(job.getTopic()); if (!CollectionUtils.isEmpty(ttrOverloadMetaDataList)) { @@ -213,7 +216,7 @@ private void processTtrJob(DelayJob delayJob, Job job) { delayJob.setRetryCount(0); // 添加dead queue deadQueue.add(operations, delayJob); - log.warn("Ice处理TTR Overload的 Job {} 进入Dead queue", job.getId()); + log.warn("Ice处理TTR Overload的Job[{}]进入Dead queue", job.getId()); } // Record job @@ -262,7 +265,9 @@ private void processTtrJob(DelayJob delayJob, Job job) { * 处理延时任务 */ private void processDelayJob(DelayJob delayJob, Job job) { - log.info("Ice正在处理延迟的Job {},当前状态为:{}", delayJob.getJodId(), job.getStatus()); + if(Environment.isShowLog()) { + log.info("Ice正在处理延迟的Job[{}],当前状态为:{}", delayJob.getJodId(), job.getStatus()); + } RedisUtil.batchOps((operations) -> { // 修改任务池状态 job.setStatus(JobStatus.READY); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIce.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIce.java index 4f1fac3d..eafde8c6 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIce.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIce.java @@ -21,6 +21,7 @@ package com.github.yizzuide.milkomeda.ice; +import com.github.yizzuide.milkomeda.universe.config.RedisGlobalConfig; import org.springframework.context.annotation.Import; import org.springframework.scheduling.annotation.EnableScheduling; @@ -96,6 +97,6 @@ @Target({ElementType.TYPE}) @Inherited @EnableScheduling -@Import({IceConfig.class, IceScheduleConfig.class}) +@Import({RedisGlobalConfig.class, IceConfig.class, IceScheduleConfig.class}) public @interface EnableIce { } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceBasic.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceBasic.java index 64cb2648..722e6f91 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceBasic.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceBasic.java @@ -21,6 +21,7 @@ package com.github.yizzuide.milkomeda.ice; +import com.github.yizzuide.milkomeda.universe.config.RedisGlobalConfig; import org.springframework.context.annotation.Import; import java.lang.annotation.*; @@ -50,6 +51,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Inherited -@Import(IceBasicConfig.class) +@Import({RedisGlobalConfig.class, IceBasicConfig.class}) public @interface EnableIceBasic { } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceClient.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceClient.java index e176743d..a830e309 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceClient.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceClient.java @@ -21,6 +21,7 @@ package com.github.yizzuide.milkomeda.ice; +import com.github.yizzuide.milkomeda.universe.config.RedisGlobalConfig; import org.springframework.context.annotation.Import; import org.springframework.scheduling.annotation.EnableScheduling; @@ -41,6 +42,6 @@ @Target({ElementType.TYPE}) @Inherited @EnableScheduling -@Import({IceClientConfig.class, IceScheduleConfig.class}) +@Import({RedisGlobalConfig.class, IceClientConfig.class, IceScheduleConfig.class}) public @interface EnableIceClient { } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceServer.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceServer.java index 25035bba..e594fe6d 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceServer.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/EnableIceServer.java @@ -21,6 +21,7 @@ package com.github.yizzuide.milkomeda.ice; +import com.github.yizzuide.milkomeda.universe.config.RedisGlobalConfig; import org.springframework.context.annotation.Import; import org.springframework.scheduling.annotation.EnableScheduling; @@ -41,6 +42,6 @@ @Target({ElementType.TYPE}) @Inherited @EnableScheduling -@Import(IceServerConfig.class) +@Import({RedisGlobalConfig.class, IceServerConfig.class}) public @interface EnableIceServer { } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/IceKeys.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/IceKeys.java index a25c1202..7626f16a 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/IceKeys.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/IceKeys.java @@ -51,7 +51,7 @@ public class IceKeys { */ public static Map resolve(JobWrapper jobWrapper) { String applicationName = IceHolder.getApplicationName(); - Map keyMap = new HashMap<>(3); + Map keyMap = new HashMap<>(8); keyMap.put("jobPool", JOB_POOL_KEY_PREFIX.concat(":" + applicationName)); switch (jobWrapper.getQueueType()) { case DelayQueue: diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/IceProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/IceProperties.java index c0b50c1b..5d060db5 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/IceProperties.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/IceProperties.java @@ -50,7 +50,7 @@ public class IceProperties { public final static String MERGE_ID_SEPARATOR = "-"; /** - * 实例名(用于多产品隔离,否则不要修改) + * 服务实例名(用于多产品隔离) */ private String instanceName = DEFAULT_INSTANCE_NAME; @@ -145,8 +145,7 @@ public class IceProperties { private boolean multiTopicListenerPerHandler = false; /** - * TTR超时后是否放入到Dead queue - * @see IceTtrOverloadListener + * TTR超时后是否放入到DeadQueue(设置为false,则超时由开发者处理通过监听器 {@link IceTtrOverloadListener} 处理) */ private boolean enableRetainToDeadQueueWhenTtrOverload = false; @@ -160,7 +159,7 @@ public class IceProperties { @Data public static class Introspect { /** - * Enable export job introspection api. + * Enable export job introspection data. */ private boolean enable = false; @@ -170,7 +169,7 @@ public static class Introspect { private JobInspector.IndexType indexType = JobInspector.IndexType.UPDATE_TIME; /** - * Select strategy which for job inspection store. + * Select strategy for job inspection store. */ private JobInspector.InspectorType inspectorType = JobInspector.InspectorType.REDIS; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/RedisIce.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/RedisIce.java index c934e89e..1b0df304 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/RedisIce.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/RedisIce.java @@ -46,7 +46,6 @@ import java.util.stream.Collectors; /** - * RedisIce * 基于Redis的延迟队列实现 * * @author yizzuide @@ -55,6 +54,7 @@ *
* Create at 2019/11/16 15:20 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j public class RedisIce implements Ice, ApplicationListener { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/AbstractJobInspector.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/AbstractJobInspector.java index bf57b0a6..ff6d990e 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/AbstractJobInspector.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/AbstractJobInspector.java @@ -31,7 +31,7 @@ import java.util.stream.Collectors; /** - * Abstract job inspector with common code. + * Abstract job inspector for persistence storage. * * @author yizzuide * @since 3.14.0 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobInspector.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobInspector.java index 4e0aca19..d0a0e5eb 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobInspector.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobInspector.java @@ -29,7 +29,7 @@ import java.util.function.Consumer; /** - * Inspect accessor for job. + * Inspect accessor for job. * * @author yizzuide * @since 3.14.0 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobStatInfo.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobStatInfo.java index 35cd68c8..835c7ee5 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobStatInfo.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobStatInfo.java @@ -26,7 +26,7 @@ import java.util.Map; /** - * JobStatInfo + * Job stat record info. * * @since 3.14.0 * @author yizzuide diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobWrapper.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobWrapper.java index 2c102b77..954ba6f0 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobWrapper.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/JobWrapper.java @@ -31,7 +31,7 @@ import java.io.Serializable; /** - * Job introspect info with {@link Job} + * Job introspect info with {@link Job}. * * @author yizzuide * @since 3.14.0 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/MongoJobInspector.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/MongoJobInspector.java index 88fddebe..2c3cfb99 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/MongoJobInspector.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/MongoJobInspector.java @@ -46,6 +46,7 @@ *
* Create at 2022/09/26 23:02 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") public class MongoJobInspector extends AbstractJobInspector { @Autowired diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/domain/JobInspection.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/domain/JobInspection.java index d34d77ba..0f9263c2 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/domain/JobInspection.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/domain/JobInspection.java @@ -38,23 +38,53 @@ public class JobInspection implements Serializable { private static final long serialVersionUID = -71079477949653608L; + /** + * Job id. + */ private Long id; + /** + * Job topic name. + */ private String topic; + /** + * Which application does the job come from. + */ private String applicationName; + /** + * Job into what queue type. + */ private Integer queueType; + /** + * Which bucket index does the job come from. + */ private Integer bucketIndex; + /** + * Number of retries already happened. + */ private Integer hadRetryCount; + /** + * The job has need to re-push. + */ private Integer needRePush; + /** + * Next execution time with job. + */ private Date executionTime; + /** + * Job push time. + */ private Date pushTime; + /** + * Latest update time executed. + */ private Date updateTime; } \ No newline at end of file diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/domain/JobInspectionDocument.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/domain/JobInspectionDocument.java index 434331d6..67a3c570 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/domain/JobInspectionDocument.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/domain/JobInspectionDocument.java @@ -31,9 +31,10 @@ import java.io.Serializable; /** - * JobInspectionDocument + * Job inspection document for mongodb. * * @author yizzuide + * @since 3.14.0 *
* Create at 2022/09/26 23:14 */ @@ -44,35 +45,65 @@ public class JobInspectionDocument implements Serializable { public static final String COLLECTION_NAME = "job_inspection"; + /** + * Job id. + */ @MongoId private String id; + /** + * Job topic name. + */ @Field("topic") private String topic; + /** + * Which application does the job come from. + */ @Field("application_name") private String applicationName; + /** + * Job into what queue type. + */ @Field("queue_type") private Integer queueType; + /** + * Which bucket index does the job come from. + */ @Field("bucket_index") private Integer bucketIndex; + /** + * Number of retries already happened. + */ @Field("hadRetry_count") private Integer hadRetryCount; + /** + * The job has need to re-push. + */ @Field("need_re_push") private Integer needRePush; + /** + * Next execution time with job. + */ @Field(name = "execution_time", targetType = FieldType.TIMESTAMP) private Long executionTime; + /** + * Job push time. + */ // create index named 'push_time.index', and created in the background. @Indexed(name = "index", background = true) @Field(name = "push_time", targetType = FieldType.TIMESTAMP) private Long pushTime; + /** + * Latest update time executed. + */ // create index named 'update_time.index', and created in the background. @Indexed(name = "index", background = true) @Field(name = "update_time", targetType = FieldType.TIMESTAMP) diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/mapper/JobInspectionMapper.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/mapper/JobInspectionMapper.java index ee730ad3..a4c7df24 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/mapper/JobInspectionMapper.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/ice/inspector/mapper/JobInspectionMapper.java @@ -29,7 +29,7 @@ import java.util.List; /** - * Job Inspection mybatis mapper + * Job Inspection mybatis mapper. * * @author yizzuide * @since 3.14.0 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/AbstractJupiterRuleEngine.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/AbstractJupiterRuleEngine.java index 46883fba..741ac135 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/AbstractJupiterRuleEngine.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/AbstractJupiterRuleEngine.java @@ -44,6 +44,7 @@ public boolean run(String ruleName, List ruleItemList) { return run(ruleName); } + @Override public void addRule(String ruleName, List ruleItemList) { int incr = 0; for (JupiterRuleItem ruleItem : ruleItemList) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterConfig.java index 54475513..e48e495b 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterConfig.java @@ -44,6 +44,7 @@ *
* Create at 2020/05/19 16:57 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @EnableConfigurationProperties(JupiterProperties.class) public class JupiterConfig implements ApplicationContextAware { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterOnglCompiler.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterOnglCompiler.java index 0e8b8caf..4e319e12 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterOnglCompiler.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterOnglCompiler.java @@ -21,50 +21,28 @@ package com.github.yizzuide.milkomeda.jupiter; -import com.github.yizzuide.milkomeda.universe.engine.ognl.OgnlClassResolver; -import com.github.yizzuide.milkomeda.universe.engine.ognl.OgnlMemberAccess; +import com.github.yizzuide.milkomeda.universe.engine.ognl.SimpleOgnlParser; import lombok.extern.slf4j.Slf4j; -import ognl.ClassResolver; -import ognl.MemberAccess; -import ognl.Ognl; import ognl.OgnlException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - /** * JupiterOnglCompiler * - * @author yizzuide * @since 3.5.0 + * @version 3.15.0 + * @author yizzuide *
* Create at 2020/05/20 11:39 */ @Slf4j public class JupiterOnglCompiler implements JupiterExpressionCompiler { - private final MemberAccess MEMBER_ACCESS = new OgnlMemberAccess(false); - private final ClassResolver CLASS_RESOLVER = new OgnlClassResolver(); - private final Map expressionCache = new ConcurrentHashMap<>(); - - @SuppressWarnings({"unchecked", "rawtypes"}) @Override public T compile(String expression, Object root, Class resultType) { try { - Map ognlContext = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null); - return (T) Ognl.getValue(parseExpression(expression), ognlContext, root, resultType); + SimpleOgnlParser.parse(expression, root, resultType); } catch (OgnlException e) { log.error("Jupiter ongl compiler error with msg: {}", e.getMessage(), e); } return null; } - - // 缓存表达式解析抽象树对象 - private Object parseExpression(String expression) throws OgnlException { - Object node = expressionCache.get(expression); - if (node == null) { - node = Ognl.parseExpression(expression); - expressionCache.put(expression, node); - } - return node; - } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterScopeRuleEngine.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterScopeRuleEngine.java index 93b95ae1..d3bbcb55 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterScopeRuleEngine.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/jupiter/JupiterScopeRuleEngine.java @@ -41,6 +41,7 @@ *
* Create at 2020/05/19 14:39 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j public class JupiterScopeRuleEngine extends AbstractJupiterRuleEngine { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/CacheHelper.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/CacheHelper.java index 88015190..57bf6792 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/CacheHelper.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/CacheHelper.java @@ -185,7 +185,7 @@ public static E get(Cache cache, TypeReference eTypeRef, Serializable id, Spot fastSpot = null; if (cache instanceof LightCache) { LightCache lightCache = (LightCache) cache; - if (lightCache.isEnableSuperCache() && !lightCache.getOnlyCacheL2()) { + if (lightCache.isEnableSuperCache() && !lightCache.isOnlyCacheL2()) { // 方案一:从超级缓存中获取,内存指针引用即可返回(耗时为O(1)) fastSpot = get(cache); if (fastSpot != null) { @@ -266,7 +266,7 @@ public static E put(Cache cache, Serializable id, Spot fastSpot = null; if (cache instanceof LightCache) { LightCache lightCache = (LightCache) cache; - if (lightCache.isEnableSuperCache() && !lightCache.getOnlyCacheL2()) { + if (lightCache.isEnableSuperCache() && !lightCache.isOnlyCacheL2()) { fastSpot = get(cache); if (fastSpot == null) { // 设置超级缓存 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/Discard.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/Discard.java index 7d163d98..b5a5563b 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/Discard.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/Discard.java @@ -26,7 +26,6 @@ /** * Discard - * * 缓存数据丢弃策略接口 * * @since 1.8.0 @@ -42,6 +41,7 @@ public interface Discard { * * @return 缓存数据类 */ + @SuppressWarnings("rawtypes") Class spotClazz(); /** diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/HotDiscard.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/HotDiscard.java index b0690f03..0b6c6129 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/HotDiscard.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/HotDiscard.java @@ -27,7 +27,6 @@ /** * HotDiscard - * * 低频热点丢弃方案 * * @since 1.8.0 @@ -38,6 +37,7 @@ */ public class HotDiscard extends SortDiscard { + @SuppressWarnings("rawtypes") @Override public Class spotClazz() { return HotSpot.class; @@ -55,7 +55,10 @@ public Spot deform(String key, Spot @Override public boolean ascend(Spot spot) { HotSpot hotSpot = (HotSpot) spot; - hotSpot.setStar(hotSpot.getStar() + 1); + // 使用锁保证访问的真实数量 + synchronized (this) { + hotSpot.setStar(hotSpot.getStar() + 1); + } return false; } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LazyExpireDiscard.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LazyExpireDiscard.java index b9f045a4..1c456a23 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LazyExpireDiscard.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LazyExpireDiscard.java @@ -36,6 +36,7 @@ */ public class LazyExpireDiscard extends SortDiscard { + @SuppressWarnings("rawtypes") @Override public Class spotClazz() { return LazyExpireSpot.class; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightCache.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightCache.java index 11d5eb8e..3777a9dd 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightCache.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightCache.java @@ -42,18 +42,17 @@ /** * LightCache - * + *

* 缓存方式:超级缓存(ThreadLocal)| 一级缓存(内存缓存池,缓存个数可控)| 二级缓存(Redis) - * - * V:标识数据 - * E:缓存业务数据 + *

* * @since 1.8.0 - * @version 3.14.0 + * @version 3.15.0 * @author yizzuide *
* Create at 2019/06/28 13:33 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j public class LightCache implements Cache { /** @@ -61,7 +60,7 @@ public class LightCache implements Cache { */ @Setter @Getter - private Integer l1MaxCount; + private volatile Integer l1MaxCount; /** * 一级缓存一次性移除百分比 @@ -81,7 +80,7 @@ public class LightCache implements Cache { */ @Setter @Getter - private Boolean onlyCacheL1; + private boolean onlyCacheL1; /** * 一级缓存丢弃策略 @@ -113,7 +112,7 @@ public class LightCache implements Cache { */ @Setter @Getter - private Boolean onlyCacheL2; + private boolean onlyCacheL2; /** * 开启超缓存 @@ -129,7 +128,7 @@ public class LightCache implements Cache { private final LightContext superCache = new LightContext<>(); /** - * 一级缓存容器(内存池) + * 一级缓存容器(内存池,跳表算法复杂度O(logN),高并发操作效率高,范围数据查找快) */ private final Map> cacheMap = new ConcurrentSkipListMap<>(); @@ -141,15 +140,15 @@ public class LightCache implements Cache { /** - * 设置超级缓存 - * - * 如果在一级缓存池里根据缓存标识符可以取得缓存数据,则不会创建新的缓存数据对象 + * 设置超级缓存(如果在一级缓存池里根据缓存标识符可以取得缓存数据,则不会创建新的缓存数据对象) * * @param id 缓存标识符 */ @Override public void set(Serializable id) { - if (null == id) return; + if (null == id) { + return; + } // 如果一级缓存没有数据,创建新的缓存数据对象 if (cacheMap.size() == 0) { superCache.setId(id); @@ -199,8 +198,8 @@ public void remove() { @SuppressWarnings("unchecked") @Override public void set(String key, Spot spot) { - // 如果是父类型,需要向下转型(会触发初始化排序状态字段) - if (spot.getClass() == Spot.class) { + // 一级缓存下,如果是父类型,需要向下转型(会触发初始化排序字段) + if (spot.getClass() == Spot.class && !onlyCacheL2) { spot = discardStrategy.deform(key, (Spot) spot, l1Expire); } @@ -253,8 +252,13 @@ private void cacheL2(String key, Spot spot) { private boolean cacheL1(String key, Spot spot) { // 一级缓存超出最大个数 if ((cacheMap.size() + 1) > l1MaxCount) { - // 根据选择的策略来丢弃数据 - discardStrategy.discard(cacheMap, l1DiscardPercent); + // 使用双重检测确保在条件满足时只执行一次 + synchronized (this) { + if ((cacheMap.size() + 1) > l1MaxCount) { + // 根据选择的策略来丢弃数据 + discardStrategy.discard(cacheMap, l1DiscardPercent); + } + } } // 排行加分 @@ -270,7 +274,7 @@ private boolean cacheL1(String key, Spot spot) { // 添加到一级缓存池 cacheMap.put(key, spot); - return true; + return true; } @Override @@ -281,7 +285,7 @@ public Spot get(String key) { @Override public Spot get(String key, Class vClazz, Class eClazz) { JavaType javaType = TypeFactory.defaultInstance() - .constructParametricType(discardStrategy.spotClazz(), vClazz, eClazz); + .constructParametricType(onlyCacheL2 ? Spot.class : discardStrategy.spotClazz(), vClazz, eClazz); return get(key, javaType); } @@ -414,9 +418,9 @@ public void copyFrom(LightCache other) { this.setL1Expire(other.getL1Expire()); this.setStrategy(other.getStrategy()); this.setStrategyClass(other.getStrategyClass()); - this.setOnlyCacheL1(other.getOnlyCacheL1()); + this.setOnlyCacheL1(other.isOnlyCacheL1()); this.setL2Expire(other.getL2Expire()); - this.setOnlyCacheL2(other.getOnlyCacheL2()); + this.setOnlyCacheL2(other.isOnlyCacheL2()); this.setEnableSuperCache(other.isEnableSuperCache()); } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightCacheAspect.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightCacheAspect.java index 80b5fb29..15cc4135 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightCacheAspect.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightCacheAspect.java @@ -56,6 +56,7 @@ *
* Create at 2019/12/18 14:45 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j @Order(98) @Aspect diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightContext.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightContext.java index 1b664f5c..c14beb9e 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightContext.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightContext.java @@ -23,36 +23,31 @@ import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; import com.github.yizzuide.milkomeda.universe.context.SpringContext; +import io.netty.util.concurrent.FastThreadLocal; import lombok.Data; import org.springframework.context.ConfigurableApplicationContext; import java.io.Serializable; /** - * LightContext - * - * 线程缓存上下文,可以配合LightCache当作超级缓存,也可以单独使用 - * - * I:上下文id - * E:上下文数据 + * 线程缓存上下文,可以配合LightCache当作超级缓存,也可以单独使用。 * * @since 1.9.0 - * @version 3.13.0 + * @version 3.15.0 * @author yizzuide *
* Create at 2019/06/30 18:57 */ @Data public class LightContext { - // 每个Thread对应ThreadLocalMap - // 每个缓存实例都有自己的超级缓存 - private final ThreadLocal> context; + + private final FastThreadLocal> context; public LightContext() { - context = new ThreadLocal<>(); + context = new FastThreadLocal<>(); } - public LightContext(ThreadLocal> threadLocal) { + public LightContext(FastThreadLocal> threadLocal) { this.context = threadLocal; } @@ -66,6 +61,17 @@ public void setId(ID id) { set(spot); } + /** + * 设置上下文数据 + * @param data 上下文数据 + * @since 3.15.0 + */ + public void setData(V data) { + Spot spot = new Spot<>(); + spot.setData(data); + set(spot); + } + /** * 设置上下文数据 * @param spot Spot @@ -95,14 +101,16 @@ public void remove() { * @param value 任意对象 * @param identifier 唯一标识 * @param 对象类型 + * @return LightContext * @since 3.13.0 */ @SuppressWarnings("unchecked") - public static void setValue(V value, String identifier) { + public static LightContext setValue(V value, String identifier) { LightContext lightContext = SpringContext.registerBean((ConfigurableApplicationContext) ApplicationContextHolder.get(), identifier, LightContext.class); Spot spot = new Spot<>(); spot.setData(value); lightContext.set(spot); + return lightContext; } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightThreadLocalScope.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightThreadLocalScope.java index 93887b7a..7c84701d 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightThreadLocalScope.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/LightThreadLocalScope.java @@ -21,9 +21,9 @@ package com.github.yizzuide.milkomeda.light; +import io.netty.util.concurrent.FastThreadLocal; import org.springframework.cloud.context.scope.GenericScope; import org.springframework.cloud.context.scope.ScopeCache; -import org.springframework.core.NamedThreadLocal; import org.springframework.core.Ordered; import java.util.ArrayList; @@ -67,8 +67,7 @@ public String getConversationId() { // 基于LightContext的Scope的缓存 static class LightScopeCache implements ScopeCache { - private final LightContext> lightContext = new LightContext<>(new NamedThreadLocal>>("light-scope-thread-local"){ - + private final LightContext> lightContext = new LightContext<>(new FastThreadLocal>>(){ // 覆盖初始化,防止获了为空 @Override protected Spot> initialValue() { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/SortDiscard.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/SortDiscard.java index e1f00526..0457fff6 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/SortDiscard.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/SortDiscard.java @@ -32,7 +32,6 @@ /** * SortDiscard - * * 抽象的字段排序方案 * * @since 1.8.0 @@ -75,7 +74,7 @@ public void discard(Map> cacheMap, float l1Di int size = list.size(); int discardCount = Math.round(size * l1DiscardPercent); // 一级缓存百分比太小,直接返回 - if (discardCount == 0) { + if (discardCount < 1) { return; } if (discardCount >= size) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/TimelineDiscard.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/TimelineDiscard.java index e917e021..8f0dc8d5 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/TimelineDiscard.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/light/TimelineDiscard.java @@ -38,6 +38,7 @@ */ public class TimelineDiscard extends SortDiscard { + @SuppressWarnings("rawtypes") @Override public Class spotClazz() { return TimelineSpot.class; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/MetalContainer.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/MetalContainer.java index 291d9830..06928075 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/MetalContainer.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/MetalContainer.java @@ -21,6 +21,7 @@ package com.github.yizzuide.milkomeda.metal; +import com.github.yizzuide.milkomeda.universe.extend.env.Environment; import com.github.yizzuide.milkomeda.util.DataTypeConvertUtil; import com.github.yizzuide.milkomeda.util.ReflectUtil; import com.github.yizzuide.milkomeda.util.Strings; @@ -37,7 +38,7 @@ /** * MetalContainer - * 配置容器,不推荐直接使用该类API,因为有线程安全问题,应该使用 {@link MetalHolder} + * 配置信息容器,不推荐直接使用该类API,因为有线程安全问题,应该使用 {@link MetalHolder} * * @author yizzuide * @since 3.6.0 @@ -116,7 +117,9 @@ public void updateVNode(String key, String oldVal, String newVal) { } for (VirtualNode vNode : cacheSet) { vNode.update(newVal); - log.info("Metal update vnode '{}' with key '{}' from old value '{}' to '{}'", vNode.getSignature(), key, oldVal, newVal); + if (Environment.isShowLog()) { + log.info("Metal update vnode '{}' with key '{}' from old value '{}' to '{}'", vNode.getSignature(), key, oldVal, newVal); + } } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/MetalMessageHandler.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/MetalMessageHandler.java index e7bc77d9..79f12224 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/MetalMessageHandler.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/MetalMessageHandler.java @@ -37,6 +37,7 @@ *
* Create at 2020/05/22 15:59 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") public class MetalMessageHandler { private static String METAL_CHANGE_TOPIC; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/RedisMetalConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/RedisMetalConfig.java index 5ca6bdca..8a8fb2bb 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/RedisMetalConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/metal/RedisMetalConfig.java @@ -43,6 +43,7 @@ *
* Create at 2020/05/22 16:34 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @EnableConfigurationProperties(MetalProperties.class) @ConditionalOnClass(RedisTemplate.class) @AutoConfigureAfter(RedisAutoConfiguration.class) diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/moon/EnableMoon.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/moon/EnableMoon.java index 1b4e4fb6..e1ad3acd 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/moon/EnableMoon.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/moon/EnableMoon.java @@ -21,6 +21,7 @@ package com.github.yizzuide.milkomeda.moon; +import com.github.yizzuide.milkomeda.universe.config.RedisGlobalConfig; import org.springframework.context.annotation.Import; import java.lang.annotation.*; @@ -38,6 +39,6 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Import({MoonConfig.class, MixinMoonConfig.class}) +@Import({RedisGlobalConfig.class, MoonConfig.class, MixinMoonConfig.class}) public @interface EnableMoon { } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/moon/Moon.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/moon/Moon.java index eaeb92f0..ecd9538b 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/moon/Moon.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/moon/Moon.java @@ -155,7 +155,7 @@ public static T getPhase(String key, Moon prototype) { } /** - * 基于分布式锁获取,需要添加 @EnableAspectJAutoProxy(exposeProxy=true)(分布式并发安全) + * 基于分布式锁获取(分布式并发安全) * @param key 缓存key,一个环对应一个key * @return 当前环的当前阶段值 */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AbstractOrbitAdvisor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AbstractOrbitAdvisor.java new file mode 100644 index 00000000..96537514 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AbstractOrbitAdvisor.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.orbit; + +import com.github.yizzuide.milkomeda.util.ReflectUtil; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.aop.support.AbstractGenericPointcutAdvisor; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.util.CollectionUtils; + +import java.util.Map; + +/** + * The base abstract class that orbit advisor subclasses need to extend. + * + * @see org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator + * @see org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator + * @see org.springframework.beans.factory.config.RuntimeBeanNameReference + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/02/26 00:58 + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +public abstract class AbstractOrbitAdvisor implements OrbitAdvisor { + /** + * The advisor id for register in spring context. + */ + private String advisorId; + + /** + * The advice bind with this advisor. + */ + private Class adviceClass; + + /** + * Advice property values. + */ + private Map adviceProps; + + @Override + public void initFrom(OrbitProperties.Item orbitItem) { + this.setAdvisorId(orbitItem.getKeyName()); + this.setAdviceClass(orbitItem.getAdviceClazz()); + this.setAdviceProps(orbitItem.getAdviceProps()); + if (!CollectionUtils.isEmpty(orbitItem.getAdvisorProps())) { + ReflectUtil.setField(this, orbitItem.getAdvisorProps()); + } + } + + @Override + public BeanDefinition createAdvisorBeanDefinition(BeanDefinitionRegistry registry) { + String adviceBeanName = OrbitAdviceRegisterHelper.register(this, registry); + return this.createAdvisorBeanDefinitionBuilder() + // 添加Bean引用,内部创建RuntimeBeanNameReference,延迟对Advice Bean的创建 + .addPropertyReference("advice", adviceBeanName) + // 将Advisor作为基础设施Bean,不添加自动代理 + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) + .getBeanDefinition(); + } + + /** + * Extension hook that subclasses should create an advisor bean definition builder. + * @return the advisor must be extended of {@link AbstractGenericPointcutAdvisor} + */ + protected abstract BeanDefinitionBuilder createAdvisorBeanDefinitionBuilder(); +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AdvisorBeanDefinitionFactory.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AdvisorBeanDefinitionFactory.java new file mode 100644 index 00000000..241d0528 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AdvisorBeanDefinitionFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.orbit; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; + +/** + * Abstract factory for create advisor BeanDefinition. + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/28 20:49 + */ +public interface AdvisorBeanDefinitionFactory { + /** + * Create advisor BeanDefinition for register and discovered with {@link org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator}. + * @param registry BeanDefinition registry + * @return advisor BeanDefinition + */ + BeanDefinition createAdvisorBeanDefinition(BeanDefinitionRegistry registry); +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AnnotationOrbitAdvisor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AnnotationOrbitAdvisor.java new file mode 100644 index 00000000..541b1bf5 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AnnotationOrbitAdvisor.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.orbit; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; + +import java.lang.annotation.Annotation; +import java.util.Map; + +/** + * Simple orbit advisor metadata that looks for a specific Java 5 annotation being present on a class or method. + * + * @see org.springframework.aop.support.AbstractGenericPointcutAdvisor + * @see org.springframework.aop.support.annotation.AnnotationMatchingPointcut + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/27 16:14 + */ +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Data +public class AnnotationOrbitAdvisor extends AbstractOrbitAdvisor { + + /** + * The annotation type to look for at the class level. + */ + private Class classAnnotationType; + + /** + * The annotation type to look for at the method level. + */ + private Class methodAnnotationType; + + public AnnotationOrbitAdvisor(Class classAnnotationType, Class methodAnnotationType, + String advisorId, Class adviceClass, Map props) { + super(advisorId, adviceClass, props); + this.classAnnotationType = classAnnotationType; + this.methodAnnotationType = methodAnnotationType; + } + + public static AnnotationOrbitAdvisor forClass(Class classAnnotationType, String advisorId, Class adviceClass, Map props) { + return new AnnotationOrbitAdvisor(classAnnotationType, null, advisorId, adviceClass, props); + } + + public static AnnotationOrbitAdvisor forMethod(Class methodAnnotationType, String advisorId, Class adviceClass, Map props) { + return new AnnotationOrbitAdvisor(null, methodAnnotationType, advisorId, adviceClass, props); + } + + @Override + public BeanDefinitionBuilder createAdvisorBeanDefinitionBuilder() { + return BeanDefinitionBuilder.rootBeanDefinition(DefaultPointcutAdvisor.class) + .addPropertyValue("pointcut", new AnnotationMatchingPointcut(this.getClassAnnotationType(), this.getMethodAnnotationType())); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AspectJOrbitAdvisor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AspectJOrbitAdvisor.java new file mode 100644 index 00000000..6eb2f066 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/AspectJOrbitAdvisor.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.orbit; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; + +import java.util.Map; + +/** + * AspectJ impl of orbit advisor metadata which support set pointcut expression. + * + * @see org.springframework.aop.aspectj.AspectJExpressionPointcut + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/27 16:12 + */ +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Data +public class AspectJOrbitAdvisor extends AbstractOrbitAdvisor { + + /** + * The pointcut expression value is an AspectJ expression. + */ + private String pointcutExpression; + + public AspectJOrbitAdvisor(String pointcutExpression, String id, Class adviceClass, Map props) { + super(id, adviceClass, props); + this.pointcutExpression = pointcutExpression; + } + + @Override + public void initFrom(OrbitProperties.Item orbitItem) { + if (orbitItem.getPointcutExpression() != null) { + this.setPointcutExpression(orbitItem.getPointcutExpression()); + } + super.initFrom(orbitItem); + } + + @Override + public BeanDefinitionBuilder createAdvisorBeanDefinitionBuilder() { + return BeanDefinitionBuilder.rootBeanDefinition(AspectJExpressionPointcutAdvisor.class) + .addPropertyValue("location", String.format("$$%s##", this.getAdvisorId())) // Set the location for debugging. + .addPropertyValue("expression", this.getPointcutExpression()); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/Orbit.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/Orbit.java index 6c4ff51d..b3d256bf 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/Orbit.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/Orbit.java @@ -27,11 +27,10 @@ import java.lang.annotation.*; /** - * Orbit - * 切面绑定注册 + * Register a aspectJ advisor. * - * @author yizzuide * @since 3.13.0 + * @author yizzuide *
* Create at 2022/02/27 01:34 */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdvice.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdvice.java index 4682eb12..c3307003 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdvice.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdvice.java @@ -5,13 +5,14 @@ import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.aop.ProxyMethodInvocation; import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint; +import org.springframework.aop.support.AopUtils; import java.lang.reflect.Method; /** - * OrbitAdvice - * 拦截切面 + * Extension subclass of {@link MethodInterceptor}. * + * @see org.springframework.aop.aspectj.AspectJAroundAdvice * @author yizzuide * @since 3.13.0 *
@@ -24,9 +25,11 @@ default Object invoke(MethodInvocation invocation) throws Throwable { ProxyMethodInvocation pmi = (ProxyMethodInvocation) invocation; ProceedingJoinPoint pjp = new MethodInvocationProceedingJoinPoint(pmi); Method method = invocation.getMethod(); + Class targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null; OrbitInvocation orbitInvocation = OrbitInvocation.builder() .pjp(pjp) .target(pjp.getTarget()) + .targetClass(targetClass) .method(method) .args(invocation.getArguments()) .build(); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdviceFactoryBean.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdviceFactoryBean.java new file mode 100644 index 00000000..f6e73cc2 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdviceFactoryBean.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.orbit; + +import com.github.yizzuide.milkomeda.util.ReflectUtil; +import lombok.Data; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.util.CollectionUtils; + +import java.util.Map; + +/** + * Packaging the creation process of orbit advice. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/27 21:12 + */ +@Data +public class OrbitAdviceFactoryBean implements FactoryBean { + + /** + * Orbit advice type. + */ + private Class adviceClass; + + /** + * Orbit advice property values. + */ + private Map adviceProps; + + /** + * Spring context. + */ + private ConfigurableListableBeanFactory beanFactory; + + @SuppressWarnings("unchecked") + @Override + public OrbitAdvice getObject() throws Exception { + // 使用ObjectProvider的延迟查找代替getBean()的立即查找,因为getBean()找不到会有异常 + ObjectProvider adviceProvider = (ObjectProvider) beanFactory.getBeanProvider(this.getAdviceClass()); + OrbitAdvice advice = adviceProvider.getIfAvailable(() -> ReflectUtil.newInstance(this.getAdviceClass())); + // autowire it's fields! + beanFactory.autowireBean(advice); + // reflect and set custom props + if (!CollectionUtils.isEmpty(this.getAdviceProps())) { + ReflectUtil.setField(advice, this.getAdviceProps()); + } + return advice; + } + + @Override + public Class getObjectType() { + return OrbitAdvice.class; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdviceRegisterHelper.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdviceRegisterHelper.java new file mode 100644 index 00000000..670ebd67 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdviceRegisterHelper.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.orbit; + +import com.github.yizzuide.milkomeda.util.RecognizeUtil; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Simple helper class to register advice from advisor, it used {@link FactoryBean} for create bean. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/28 13:12 + */ +public class OrbitAdviceRegisterHelper { + /** + * Bean name counter. + */ + // ++操作符包含三条指令: + // 1.把变量count从内存加载到CPU的寄存器;2.在寄存器中执行+1操作;3.把结果写回内存(缓存)。 + // 注:CPU只能保证指令级别的原子操作,而不是高级语言的操作符。 + public static final Map counterMap = new HashMap<>(); + + /** + * Register advice from OrbitAdvisor with {@link BeanDefinitionRegistry}. + * @param orbitAdvisor OrbitAdvisor + * @param registry BeanDefinitionRegistry + * @return advice bean name + */ + public static String register(OrbitAdvisor orbitAdvisor, BeanDefinitionRegistry registry) { + // 根据BeanDefinition生成beanName + //String adviceBeanName = AnnotationBeanNameGenerator.INSTANCE.generateBeanName(beanDefinition, (BeanDefinitionRegistry) beanFactory); + // 根据类名生成beanName + String adviceBeanName = RecognizeUtil.getBeanName(orbitAdvisor.getAdviceClass()); + if (registry.containsBeanDefinition(adviceBeanName)) { + BeanDefinition beanDefinition = registry.getBeanDefinition(adviceBeanName); + Object adviceClass = beanDefinition.getPropertyValues().get("adviceClass"); + if (adviceClass == null || orbitAdvisor.getAdviceClass() == adviceClass) { + return adviceBeanName; + } + adviceBeanName = adviceBeanName + "$" + counterMap.get(adviceBeanName).getAndIncrement(); + } else { + counterMap.putIfAbsent(adviceBeanName, new AtomicInteger(0)); + } + AbstractBeanDefinition orbitAdviceFactoryBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(OrbitAdviceFactoryBean.class) + .addPropertyValue("beanFactory", registry) + .addPropertyValue("adviceClass", orbitAdvisor.getAdviceClass()) + .addPropertyValue("adviceProps", orbitAdvisor.getAdviceProps()) + .getBeanDefinition(); + registry.registerBeanDefinition(adviceBeanName, orbitAdviceFactoryBeanDefinition); + return adviceBeanName; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitNode.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdvisor.java similarity index 59% rename from Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitNode.java rename to Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdvisor.java index 76e7649e..d5c29fc9 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitNode.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 yizzuide All rights Reserved. + * Copyright (c) 2023 yizzuide All rights Reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -21,52 +21,38 @@ package com.github.yizzuide.milkomeda.orbit; -import lombok.Builder; -import lombok.Getter; - -import java.util.HashMap; import java.util.Map; /** - * OrbitNode - * 用于外部扩展的切面节点 + * The top level interface with metadata to assemble advisor bean definition, all subclasses need provide a parameterless constructor. * + * @since 3.15.0 * @author yizzuide - * @since 3.13.0 *
- * Create at 2022/02/26 00:58 + * Create at 2023/01/27 18:35 */ -@Getter -@Builder -public class OrbitNode { - /** - * 切面节点唯一标识 - */ - private String id; +public interface OrbitAdvisor extends AdvisorBeanDefinitionFactory { /** - * 切面节点表达式 + * The advisor id for register in spring context. + * @return advisor id */ - private String pointcutExpression; + String getAdvisorId(); + /** - * 切面实现类 + * The advice bind with this advisor. + * @return advice class */ - private Class adviceClass; + Class getAdviceClass(); + /** - * 切面实现属性配置 + * Advice property values. + * @return property map */ - private Map props; + Map getAdviceProps(); /** - * 添加单个KV - * @param key 键 - * @param value 值 - * @return OrbitNode + * Init meta data from config item. + * @param orbitItem orbit config item */ - public OrbitNode putPropKV(String key, Object value) { - if (props == null) { - props = new HashMap<>(); - } - props.put(key, value); - return this; - } + void initFrom(OrbitProperties.Item orbitItem); } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitInvocation.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitInvocation.java index 1e0e77d2..ff353d5b 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitInvocation.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitInvocation.java @@ -28,11 +28,11 @@ import java.lang.reflect.Method; /** - * OrbitInvocation - * 调用对象 + * Method invoked meta info. * * @author yizzuide * @since 3.13.0 + * @version 3.15.0 *
* Create at 2022/02/23 17:08 */ @@ -43,14 +43,23 @@ public class OrbitInvocation { * 连接点 */ private ProceedingJoinPoint pjp; + /** * 目标对象 */ private Object target; + + /** + * 被代理目标类 + * @since 3.15.0 + */ + private Class targetClass; + /** * 切面方法 */ private Method method; + /** * 调用参数 */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitProperties.java index d1fbf756..35088995 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitProperties.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitProperties.java @@ -30,44 +30,56 @@ import java.util.Map; /** - * OrbitProperties + * Orbit properties config. * * @author yizzuide * @since 3.13.0 + * @version 3.15.0 *
* Create at 2022/02/21 01:27 */ @Data @ConfigurationProperties(prefix = OrbitProperties.PREFIX) public class OrbitProperties { - // 当前配置前缀 public static final String PREFIX = "milkomeda.orbit"; /** - * 实例列表 + * Orbit config item list. */ private List instances = new ArrayList<>(); @Data public static class Item { /** - * 唯一id名 + * The key used for register bean name. */ private String keyName; /** - * 切点表达式,如应用给Mapper的query方法:execution(* com..mapper.*.query*(..)) + * An advice class which impl of {@link OrbitAdvice}. + */ + private Class adviceClazz; + + /** + * Advice property values. + */ + private Map adviceProps = new HashMap<>(); + + /** + * AspectJ pointcut expression,such as "execution(* com..mapper.*.query*(..))". */ private String pointcutExpression; /** - * 方法切面实现类 + * An Advisor class which impl of {@link OrbitAdvisor}. + * @since 3.15.0 */ - private Class adviceClassName; + private Class advisorClazz = AspectJOrbitAdvisor.class; /** - * 切面实现属性注入 + * Advisor config properties to interpolated dynamically. + * @since 3.15.0 */ - private Map props = new HashMap<>(); + private Map advisorProps; } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitRegistrar.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitRegistrar.java index 1038a59a..cbc46540 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitRegistrar.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitRegistrar.java @@ -24,12 +24,7 @@ import com.github.yizzuide.milkomeda.universe.context.SpringContext; import com.github.yizzuide.milkomeda.util.ReflectUtil; import lombok.extern.slf4j.Slf4j; -import org.aopalliance.aop.Advice; -import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; @@ -39,22 +34,20 @@ import org.springframework.lang.NonNull; import org.springframework.util.CollectionUtils; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; /** - * OrbitRegistrar - * 环绕切面注册 - *

- * ImportBeanDefinitionRegistrar是Bean定义阶段注册器,适用于动态注册一个AspectJExpressionPointcutAdvisor
- * BeanPostProcessor是Bean的创建完成后置处理器,然后给它包装一个代理,但这里不适用 + * Register orbit advisor. * - * @author yizzuide - * @since 3.13.0 - * @see org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor * @see org.springframework.aop.support.AbstractExpressionPointcut + * @see org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor * @see org.springframework.beans.factory.config.BeanPostProcessor + * @since 3.13.0 + * @version 3.15.0 + * @author yizzuide *
* Create at 2022/02/21 01:14 */ @@ -63,11 +56,9 @@ public class OrbitRegistrar implements ImportBeanDefinitionRegistrar { // 切面提供者包扫描路径 public static final String ORBIT_SOURCE_PROVIDER_SCAN_BASE_PACKAGES = "com.github.yizzuide.milkomeda.*.orbit"; - private OrbitProperties orbitProperties; - private final Environment environment; - // Spring会自动传入Environment参数,调用这个构造器 + // 注入Environment OrbitRegistrar(Environment environment) { this.environment = environment; } @@ -75,69 +66,55 @@ public class OrbitRegistrar implements ImportBeanDefinitionRegistrar { @SuppressWarnings("unchecked") @Override public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { - // 切面配置绑定 + List orbitAdvisors = new ArrayList<>(); + // 1.YAML配置方式 + OrbitProperties orbitProperties; try { - // YAML配置方式 - this.orbitProperties = Binder.get(this.environment).bind(OrbitProperties.PREFIX, OrbitProperties.class).get(); - } catch (Exception e) { + orbitProperties = Binder.get(this.environment).bind(OrbitProperties.PREFIX, OrbitProperties.class).get(); + } catch (Exception ignore) { // 没用配置过Orbit,创建默认配置 - this.orbitProperties = new OrbitProperties(); + orbitProperties = new OrbitProperties(); + } + List orbitItems = orbitProperties.getInstances(); + if (!CollectionUtils.isEmpty(orbitItems)) { + orbitItems.forEach(item -> { + OrbitAdvisor orbitAdvisor = ReflectUtil.newInstance(item.getAdvisorClazz()); + if (orbitAdvisor != null) { + orbitAdvisor.initFrom(item); + orbitAdvisors.add(orbitAdvisor); + } + }); } - // 框架其它模块桥接切面源提供者 + // 2.框架其它模块桥接切面源提供者 Collection orbitSources = SpringContext.scanBeans(registry, OrbitSourceProvider.class, ORBIT_SOURCE_PROVIDER_SCAN_BASE_PACKAGES); - orbitSources.forEach(orbitSource -> orbitSource.createNodes(this.environment).forEach(this::addNode)); + orbitSources.forEach(orbitSource -> { + List advisors = orbitSource.createAdvisors(this.environment); + if (!CollectionUtils.isEmpty(advisors)) { + orbitAdvisors.addAll(advisors); + } + }); - // 注解注册方式 + // 3.注解注册方式 ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry; Map orbits = beanFactory.getBeansWithAnnotation(Orbit.class); orbits.forEach((id, value) -> { Class adviceClass = (Class) value.getClass(); Orbit orbit = AnnotationUtils.findAnnotation(adviceClass, Orbit.class); - assert orbit != null; - String pointcutExpression = orbit.pointcutExpression(); - OrbitNode orbitNode = OrbitNode.builder().id(id).adviceClass(adviceClass).pointcutExpression(pointcutExpression).build(); - this.addNode(orbitNode); + if (orbit != null) { + orbitAdvisors.add(new AspectJOrbitAdvisor(orbit.pointcutExpression(), id, adviceClass, null)); + } }); - // 注册切面 - List instances = this.orbitProperties.getInstances(); - if (CollectionUtils.isEmpty(instances)) { + if (CollectionUtils.isEmpty(orbitAdvisors)) { return; } - for (OrbitProperties.Item item : instances) { - // 生成advice的beanName - // AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(item.getAdviceClassName()).getBeanDefinition(); - // String adviceBeanName = AnnotationBeanNameGenerator.INSTANCE.generateBeanName(beanDefinition, registry); - - // 使用ObjectProvider的延迟查找代替getBean()的立即查找,因为getBean()找不到会有异常 - ObjectProvider adviceProvider = (ObjectProvider) beanFactory.getBeanProvider(item.getAdviceClassName()); - // 只有XML配置方式需要手动创建实例 - Advice advice = adviceProvider.getIfAvailable(() -> ReflectUtil.newInstance(item.getAdviceClassName())); - // set custom props - if (item.getProps() != null) { - ReflectUtil.setField(advice, item.getProps()); - } - String beanName = "mk_orbit_advisor_" + item.getKeyName(); - BeanDefinition aspectJBean = BeanDefinitionBuilder.genericBeanDefinition(AspectJExpressionPointcutAdvisor.class) - .addPropertyValue("location", String.format("$$%s##", item.getKeyName())) // Set the location for debugging. - .addPropertyValue("expression", item.getPointcutExpression()) - .addPropertyValue("advice", advice) - .getBeanDefinition(); - registry.registerBeanDefinition(beanName, aspectJBean); - } - } - - /** - * 添加切面节点 - * @param orbitNode OrbitNode - */ - private void addNode(OrbitNode orbitNode) { - OrbitProperties.Item item = new OrbitProperties.Item(); - item.setKeyName(orbitNode.getId()); - item.setPointcutExpression(orbitNode.getPointcutExpression()); - item.setAdviceClassName(orbitNode.getAdviceClass()); - item.setProps(orbitNode.getProps()); - this.orbitProperties.getInstances().add(item); + // register advisor + // 默认的自动代理会添加配置的Advisor Bean: AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors() -> + // AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean() -> findEligibleAdvisors() -> + // findCandidateAdvisors() -> BeanFactoryAdvisorRetrievalHelper.findAdvisor() + orbitAdvisors.forEach(orbitAdvisor -> + registry.registerBeanDefinition("mk_orbit_advisor_" + orbitAdvisor.getAdvisorId(), + orbitAdvisor.createAdvisorBeanDefinition(registry))); } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitSource.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitSource.java index 4965d379..464eacea 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitSource.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/orbit/OrbitSource.java @@ -26,8 +26,7 @@ import java.util.List; /** - * OrbitSource - * 切面节点来源扩展接口 + * The OrbitSource interface used for other module to register advisor. * * @author yizzuide * @since 3.13.0 @@ -37,9 +36,9 @@ @FunctionalInterface public interface OrbitSource { /** - * 初始化配置 - * @param environment Environment - * @return Orbit node list + * Create advisor list. + * @param environment spring environment + * @return orbit advisor list */ - List createNodes(Environment environment); + List createAdvisors(Environment environment); } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/EnableParticle.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/EnableParticle.java index 45c51c96..2fc41faf 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/EnableParticle.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/EnableParticle.java @@ -21,6 +21,7 @@ package com.github.yizzuide.milkomeda.particle; +import com.github.yizzuide.milkomeda.universe.config.RedisGlobalConfig; import org.springframework.context.annotation.Import; import java.lang.annotation.*; @@ -37,6 +38,6 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Import(ParticleConfig.class) +@Import({RedisGlobalConfig.class, ParticleConfig.class}) public @interface EnableParticle { } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/LeakyBucketLimiter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/LeakyBucketLimiter.java new file mode 100644 index 00000000..895992ec --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/LeakyBucketLimiter.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.particle; + +import com.github.yizzuide.milkomeda.universe.extend.env.Environment; +import com.github.yizzuide.milkomeda.universe.extend.loader.LuaLoader; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.script.RedisScript; + +import java.util.Collections; + +/** + * The leaky bucket limiter used fixed size bucket, fixed rate outflow, and receive requests at any rate, + * but reject all requests if the bucket is full. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/05/21 18:20 + */ +@Slf4j +@EqualsAndHashCode(callSuper = true) +@Data +public class LeakyBucketLimiter extends LimitHandler implements LuaLoader { + + /** + * Decorated postfix for a limiter key. + */ + private static final String POSTFIX = ":leaky_bucket"; + + /** + * Bucket size. + */ + private long bucketCapacity; + + /** + * loss water count per second (handle request per second). + */ + private long waterRate; + + /** + * Lua script list. + */ + private String[] luaScripts; + + @Override + public R limit(String key, Process process) throws Throwable { + String limiterKey = key + POSTFIX; + RedisScript redisScript = new DefaultRedisScript<>(luaScripts[0], Long.class); + Long waterCount = getJsonRedisTemplate().execute(redisScript, Collections.singletonList(limiterKey), getBucketCapacity(), getWaterRate(), System.currentTimeMillis()); + if (Environment.isShowLog()) { + log.info("particle drop water from bucket, leave water count: {}", waterCount); + } + // -1 is means bucket is fulled. + boolean isOver = waterCount == null || waterCount == -1; + Particle particle = new Particle(this.getClass(), isOver, null); + return next(particle, key, process); + } + + @Override + public String[] luaFilenames() { + return new String[] {"particle_leakyBucket_limiter.lua"}; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/LimitHandler.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/LimitHandler.java index 9f29addf..4c3d3e96 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/LimitHandler.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/LimitHandler.java @@ -41,10 +41,11 @@ @Data public abstract class LimitHandler implements Limiter { /** - * 过期时间 + * expire with second unit. * @since 3.12.10 */ protected Long expire; + /** * 拦截链串,用于记录链条数据 */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/LimiterType.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/LimiterType.java index 61e95000..4f780478 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/LimiterType.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/LimiterType.java @@ -22,11 +22,11 @@ package com.github.yizzuide.milkomeda.particle; /** - * LimiterType - * 限制器类型 + * Used to specify the type of limiter. * - * @author yizzuide * @since 3.1.2 + * @version 3.15.0 + * @author yizzuide *
* Create at 2020/04/22 14:14 */ @@ -41,6 +41,24 @@ public enum LimiterType { */ TIMES, + /** + * 滚动窗口 + * @since 3.15.0 + */ + ROLL_WINDOW, + + /** + * 令牌桶 + * @since 3.15.0 + */ + TOKEN_BUCKET, + + /** + * 漏桶 + * @since 3.15.0 + */ + LEAKY_BUCKET, + /** * 布隆限制器 */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleConfig.java index d2713f21..77fb3916 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleConfig.java @@ -22,7 +22,7 @@ package com.github.yizzuide.milkomeda.particle; import com.github.yizzuide.milkomeda.universe.context.SpringContext; -import com.github.yizzuide.milkomeda.util.IOUtils; +import com.github.yizzuide.milkomeda.universe.extend.loader.LuaLoader; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; @@ -53,7 +53,7 @@ * * @author yizzuide * @since 1.14.0 - * @version 3.12.10 + * @version 3.15.0 *
* Create at 2019/11/11 11:26 */ @@ -120,6 +120,15 @@ public void setApplicationContext(@NonNull ApplicationContext applicationContext case TIMES: limiter.setHandlerClazz(TimesLimiter.class); break; + case ROLL_WINDOW: + limiter.setHandlerClazz(RollWindowLimiter.class); + break; + case TOKEN_BUCKET: + limiter.setHandlerClazz(TokenBucketLimiter.class); + break; + case LEAKY_BUCKET: + limiter.setHandlerClazz(LeakyBucketLimiter.class); + break; case BARRIER: limiter.setHandlerClazz(BarrierLimiter.class); break; @@ -129,13 +138,18 @@ public void setApplicationContext(@NonNull ApplicationContext applicationContext } } LimitHandler limitHandler = SpringContext.registerBean((ConfigurableApplicationContext) applicationContext, limiterName, limiter.getHandlerClazz(), limiter.getProps()); + applicationContext.getAutowireCapableBeanFactory().autowireBean(limitHandler); limitHandler.setExpire(limiter.getKeyExpire().getSeconds()); limiter.setLimitHandler(limitHandler); if (limiter.getHandlerClazz() == BarrierLimiter.class) { barrierLimiters.add(limiter); } // 缓存并设置属性 - cacheHandlerBeans.put(limiterName, limitHandler); + if(null == cacheHandlerBeans.putIfAbsent(limiterName, limitHandler) && + limitHandler instanceof LuaLoader) { + // 仅读取配置限制器的lua脚本 + ((LuaLoader) limitHandler).load(); + } } // 创建barrierLimiter类型限制器链 @@ -157,10 +171,6 @@ public void setApplicationContext(@NonNull ApplicationContext applicationContext List orderLimiters = limiters.stream() .sorted(OrderComparator.INSTANCE.withSourceProvider(limiter -> limiter)).collect(Collectors.toList()); particleProperties.setLimiters(orderLimiters); - - // 读取lua脚本 - String luaScript = IOUtils.loadLua(IOUtils.LUA_PATH, "particle_times_limiter.lua"); - TimesLimiter.setLuaScript(luaScript); } static Map getCacheHandlerBeans() { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleFilter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleFilter.java index 0b454dad..a8c39b81 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleFilter.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleFilter.java @@ -58,6 +58,7 @@ *
* Create at 2020/04/08 11:41 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") public class ParticleFilter implements Filter { @Autowired diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleProperties.java index c937aebe..dedea101 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleProperties.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/ParticleProperties.java @@ -46,7 +46,7 @@ @ConfigurationProperties("milkomeda.particle") public class ParticleProperties { /** - * 开启请求过滤 + * 开启请求过滤(使用配置URL添加拦截时必须开启) */ private boolean enableFilter = false; @@ -81,7 +81,7 @@ public static class Limiter implements Ordered { static final String RESPONSE_CONTENT = "content"; /** - * 限制处理器Bean名(用于注解方式或lazy注入,Bean名不可重复) + * 限制处理器自动注册的Bean名(用于注解方式或lazy注入,Bean名不可重复) */ private String name; @@ -106,7 +106,13 @@ public static class Limiter implements Ordered { private Map props; /** - * 分布式key模板(固定占位符:uri、method、params;请求参数域/自定义解析参数:$params.name;请求头域:$header.name;cookie域:$cookie.name) + * 分布式key模板: + *
+         *  固定占位符:uri、method、params
+         *  请求参数域/自定义解析参数:$params.name
+         *  请求头域:$header.name
+         *  cookie域:$cookie.name
+         * 
*/ private String keyTpl = "limit_{method}_{uri}_{$header.token}"; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/RollWindowLimiter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/RollWindowLimiter.java new file mode 100644 index 00000000..436f2ca0 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/RollWindowLimiter.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.particle; + +import com.github.yizzuide.milkomeda.universe.extend.loader.LuaLoader; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.script.RedisScript; + +import java.util.Collections; + +/** + * Roll window limiter provides more precise time limit relative to {@link TimesLimiter}. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/19 02:57 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class RollWindowLimiter extends LimitHandler implements LuaLoader { + + /** + * Decorated postfix for limiter key. + */ + private static final String POSTFIX = ":roll"; + + /** + * Max limit count. + */ + private Long limitTimes = 1L; + + /** + * Lua script list. + */ + private String[] luaScripts; + + + @Override + public R limit(String key, Process process) throws Throwable { + String decoratedKey = key + POSTFIX; + RedisScript redisScript = new DefaultRedisScript<>(luaScripts[0], Long.class); + long currentTimeMillis = System.currentTimeMillis(); + Long times = getJsonRedisTemplate().execute(redisScript, Collections.singletonList(decoratedKey), expire, currentTimeMillis, limitTimes); + assert times != null; + // first time: times == 0, let it go! + boolean isOver = times >= limitTimes; + Particle particle = new Particle(this.getClass(), isOver, times); + return next(particle, key, process); + } + + @Override + public String[] luaFilenames() { + return new String[]{"particle_rollWindow_limiter.lua"}; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/TimesLimiter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/TimesLimiter.java index daca2fe1..391d22b6 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/TimesLimiter.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/TimesLimiter.java @@ -21,6 +21,7 @@ package com.github.yizzuide.milkomeda.particle; +import com.github.yizzuide.milkomeda.universe.extend.loader.LuaLoader; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -45,7 +46,12 @@ */ @Data @EqualsAndHashCode(callSuper = true) -public class TimesLimiter extends LimitHandler { +public class TimesLimiter extends LimitHandler implements LuaLoader { + /** + * Decorated postfix for limiter key. + */ + private static final String POSTFIX = ":times"; + /** * 限制时间类型 */ @@ -59,11 +65,10 @@ public class TimesLimiter extends LimitHandler { @Setter private Long limitTimes; - // 装饰后缀 - private static final String POSTFIX = ":times"; - - // lua 脚本 - private static String luaScript; + /** + * Lua script list. + */ + private String[] luaScripts; public TimesLimiter() { } @@ -98,7 +103,7 @@ public R limit(String key, Process process) throws Throwable { default: throw new IllegalStateException("Unexpected value: " + timesType); } - RedisScript redisScript = new DefaultRedisScript<>(luaScript, Long.class); + RedisScript redisScript = new DefaultRedisScript<>(luaScripts[0], Long.class); Long times = redisTemplate.execute(redisScript, Collections.singletonList(decoratedKey), limitTimes, expireSeconds); assert times != null; // 判断是否超过次数 @@ -107,7 +112,8 @@ public R limit(String key, Process process) throws Throwable { return next(particle, key, process); } - static void setLuaScript(String luaScript) { - TimesLimiter.luaScript = luaScript; + @Override + public String[] luaFilenames() { + return new String[]{"particle_times_limiter.lua"}; } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/TokenBucketLimiter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/TokenBucketLimiter.java new file mode 100644 index 00000000..14b260d4 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/particle/TokenBucketLimiter.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.particle; + +import com.github.yizzuide.milkomeda.universe.extend.env.Environment; +import com.github.yizzuide.milkomeda.universe.extend.loader.LuaLoader; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +import java.time.Duration; +import java.time.Instant; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; + +/** + * The token bucket can solve the problem of double traffic per unit time which relative to {@link TimesLimiter} and sudden request rejected with {@link LeakyBucketLimiter}. + * At begin, full tokens in bucket before request come in. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/19 21:55 + */ +@Slf4j +@EqualsAndHashCode(callSuper = true) +@Data +public class TokenBucketLimiter extends LimitHandler implements LuaLoader { + + /** + * Decorated postfix for limiter key. + */ + private static final String POSTFIX = ":token_bucket"; + + /** + * Synchronized State for JMM. + */ + private volatile int startState = 0; + + /** + * Sync Lock for wait task put tokens into bucket. + */ + private CountDownLatch countDownLatch = new CountDownLatch(1); + + /** + * Task pool scheduler. + */ + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") + @Autowired + private ThreadPoolTaskScheduler taskScheduler; + + /** + * Lua script list. + */ + private String[] luaScripts; + + /** + * Current limiter key. + */ + private String limiterKey; + + /** + * Bucket size. + */ + private long bucketCapacity; + + /** + * Each time of put token count in bucket. + */ + private long tokensPerTime; + + /** + * Interval of put token in bucket (second unit). + */ + private long interval; + + @Override + public R limit(String key, Process process) throws Throwable { + limiterKey = key + POSTFIX; + // first time, wait tokens added in the bucket. + if (startState == 0) { + synchronized (this) { + if (startState == 0) { + startState = 1; + startTask(); + countDownLatch.await(); + } + } + } + RedisScript redisScript = new DefaultRedisScript<>(luaScripts[1], Long.class); + Long tokenCount = getJsonRedisTemplate().execute(redisScript, Collections.singletonList(limiterKey)); + if (Environment.isShowLog()) { + log.info("particle get tokens from bucket, leave token count: {}", tokenCount); + } + // -1 is means bucket empty + boolean isOver = tokenCount == null || tokenCount == -1; + Particle particle = new Particle(this.getClass(), isOver, null); + return next(particle, key, process); + } + + private void startTask() { + // task for put tokens in bucket + taskScheduler.scheduleAtFixedRate(() -> { + if (startState == 0) { + return; + } + RedisScript redisScript = new DefaultRedisScript<>(luaScripts[0], Long.class); + long currentTimeMillis = System.currentTimeMillis(); + Long tokenCount = getJsonRedisTemplate().execute(redisScript, Collections.singletonList(limiterKey), bucketCapacity, tokensPerTime, interval, currentTimeMillis); + if (Environment.isShowLog()) { + log.info("particle task put tokens into bucket, current token count: {}", tokenCount); + } + // release lock + if (countDownLatch != null && countDownLatch.getCount() > 0) { + countDownLatch.countDown(); + countDownLatch = null; + } + }, Instant.now(), Duration.ofSeconds(interval)); + } + + @Override + public String[] luaFilenames() { + return new String[]{"particle_tokenBucket_limiter.lua", "particle_tokenBucket_consume_limiter.lua"}; + } + +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pillar/PillarEntryContext.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pillar/PillarEntryContext.java index 42d5aab0..e8a16af8 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pillar/PillarEntryContext.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pillar/PillarEntryContext.java @@ -56,7 +56,7 @@ public void onApplicationEvent(@NonNull ContextRefreshedEvent event) { tag = pillarEntryPoint.tag(); } // 设置其它属性方法的值 - Map attrs = new HashMap<>(2); + Map attrs = new HashMap<>(4); attrs.put(ATTR_CODE, pillarEntryPoint.code()); metaData.setAttributes(attrs); return tag; diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pulsar/Pulsar.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pulsar/Pulsar.java index 0f831b96..e53112a8 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pulsar/Pulsar.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pulsar/Pulsar.java @@ -21,20 +21,21 @@ package com.github.yizzuide.milkomeda.pulsar; -import com.github.yizzuide.milkomeda.util.ThreadUtil; +import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; +import com.github.yizzuide.milkomeda.util.Strings; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.beans.factory.annotation.Autowired; +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.ApplicationListener; import org.springframework.core.annotation.Order; import org.springframework.http.ResponseEntity; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import com.github.yizzuide.milkomeda.util.Strings; import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.WebAsyncTask; -import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; import java.util.Map; import java.util.concurrent.Callable; @@ -51,24 +52,24 @@ * * @author yizzuide * @since 0.1.0 - * @version 3.12.10 + * @version 3.15.0 *
* Create at 2019/03/29 10:36 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j @Aspect @Order(66) -public class Pulsar { +public class Pulsar implements ApplicationListener { /** * DeferredResult容器 */ private final Map deferredResultMap; /** - * 线程池执行器(从SpringBoot 2.1.0开始默认已经装配) + * 线程池执行器(从Spring Boot 2.1.0 开始默认已经装配) * @see org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration */ - @Autowired private ThreadPoolTaskExecutor applicationTaskExecutor; /** @@ -81,6 +82,16 @@ public class Pulsar { PulsarHolder.setPulsar(this); } + @Override + public void onApplicationEvent(@NotNull ApplicationStartedEvent event) { + Map threadPoolTaskExecutorMap = ApplicationContextHolder.get().getBeansOfType(ThreadPoolTaskExecutor.class); + if (threadPoolTaskExecutorMap.containsKey("applicationTaskExecutor")) { + applicationTaskExecutor = threadPoolTaskExecutorMap.get("applicationTaskExecutor"); + } else { + applicationTaskExecutor = threadPoolTaskExecutorMap.values().stream().findFirst().orElse(null); + } + } + /** * 记存一个DeferredResult * @@ -141,35 +152,6 @@ public Future postForResult(Callable callable) { return applicationTaskExecutor.submit(callable); } - /** - * 配置默认的Spring MVC异步支持 - * - * @param configurer 配置对象 - * @param timeout 超时时间,ms - * @deprecated since 1.16.0,因为SpringBoot 2.1.0版本开始默认已装配 - */ - public void configure(AsyncSupportConfigurer configurer, long timeout) { - configure(configurer, 5, 10, 200, 100, timeout); - } - - /** - * 自定义配置的异步支持 - * - * @param configurer 配置对象 - * @param corePoolSize 核心池大小 - * @param maxPoolSize 最大线程池数 - * @param queueCapacity 队列容量 - * @param keepAliveSeconds 线程保存存活时间 - * @param timeout 超时时间,ms - * @deprecated since 1.16.0,因为SpringBoot 2.1.0版本开始默认已装配 - */ - public void configure(AsyncSupportConfigurer configurer, int corePoolSize, int maxPoolSize, int queueCapacity, int keepAliveSeconds, long timeout) { - // 默认超时时间 - configurer.setDefaultTimeout(timeout); - ThreadUtil.configTaskExecutor(applicationTaskExecutor, "pulsar-", corePoolSize, maxPoolSize, queueCapacity, keepAliveSeconds); - configurer.setTaskExecutor(applicationTaskExecutor); - } - /** * 对使用了 @PulsarFlow 注解实现环绕切面 * diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pulsar/PulsarConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pulsar/PulsarConfig.java index f7de7b4a..440a08b5 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pulsar/PulsarConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/pulsar/PulsarConfig.java @@ -23,23 +23,19 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.servlet.DispatcherServlet; -import java.util.concurrent.Executor; - /** * PulsarConfig * * @author yizzuide * @since 1.14.0 - * @version 3.0.0 + * @version 3.15.0 *
* Create at 2019/11/11 11:34 */ @@ -49,17 +45,6 @@ @AutoConfigureAfter(TaskExecutionAutoConfiguration.class) public class PulsarConfig { - /** - * 创建线程任务执行器 - * @deprecated since 1.16.0,因为SpringBoot 2.1.0版本开始默认已装配 - * @return ThreadPoolTaskExecutor - */ - @Bean - @ConditionalOnMissingBean({Executor.class}) - public ThreadPoolTaskExecutor taskExecutor() { - return new ThreadPoolTaskExecutor(); - } - @Bean public Pulsar pulsar() { return new Pulsar(); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/EnableQuark.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/EnableQuark.java new file mode 100644 index 00000000..8c768a3b --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/EnableQuark.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.quark; + +import com.github.yizzuide.milkomeda.neutron.NeutronConfig; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + * Enable quark module for fast ordered queue concurrent execution. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/08/19 10:49 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Inherited +@Import(NeutronConfig.class) +public @interface EnableQuark { +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkConfig.java new file mode 100644 index 00000000..90b8472e --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkConfig.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.quark; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.scheduling.annotation.EnableScheduling; + +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Quark config. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/08/19 10:52 + */ +@EnableScheduling +@Configuration +@EnableConfigurationProperties(QuarkProperties.class) +public class QuarkConfig implements ApplicationListener { + + @Autowired + private QuarkProperties props; + + @Autowired + private List> eventHandlerList; + + @Override + public void onApplicationEvent(@NotNull ContextRefreshedEvent event) { + if (Quarks.bufferSize != null) { + return; + } + QuarkProperties.Pool pool = props.getPool(); + ThreadPoolExecutor executor = new ThreadPoolExecutor(pool.getCore(), pool.getMaximum(), + pool.getKeepAliveTime().toMillis(), TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(pool.getQueueSize()), + new ThreadPoolExecutor.DiscardPolicy()); + Quarks.setExecutor(executor); + Quarks.setBufferSize(props.getBufferSize()); + Quarks.setEventHandlerList(eventHandlerList); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkEvent.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkEvent.java new file mode 100644 index 00000000..6d07ba6c --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.quark; + +import lombok.Data; + +/** + * Quark event used for {@link com.lmax.disruptor.dsl.Disruptor}. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/08/19 10:58 + */ +@Data +public class QuarkEvent { + /** + * Event data. + */ + private T data; +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkEventFactory.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkEventFactory.java new file mode 100644 index 00000000..2c429ada --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkEventFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.quark; + +import com.lmax.disruptor.EventFactory; + +/** + * A factory to create quark event. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/08/19 10:01 + */ +public class QuarkEventFactory implements EventFactory> { + @Override + public QuarkEvent newInstance() { + return new QuarkEvent<>(); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkEventHandler.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkEventHandler.java new file mode 100644 index 00000000..955ef628 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkEventHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.quark; + +import com.lmax.disruptor.EventHandler; +import com.lmax.disruptor.WorkHandler; + +/** + * This event handler is empty, just let subclass keeps the event type. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/08/19 14:05 + */ +public class QuarkEventHandler implements EventHandler>, WorkHandler> { + + @Override + public void onEvent(QuarkEvent event, long sequence, boolean endOfBatch) throws Exception { + onEvent(event); + } + + @Override + public void onEvent(QuarkEvent event) throws Exception { + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkProducer.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkProducer.java new file mode 100644 index 00000000..7bed0dbe --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkProducer.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.quark; + +import com.lmax.disruptor.RingBuffer; +import com.lmax.disruptor.dsl.Disruptor; +import lombok.Data; + +/** + * Quark producer for holder {@link RingBuffer} and publish event. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/08/19 13:03 + */ +@Data +public class QuarkProducer { + + private Disruptor> disruptor; + + private RingBuffer> ringBuffer; + + public void publishEventData(T data) { + // first, get and occupy the next sequence + // 通过自旋获取下一个位置,需要均衡当前线程的任务与环形缓存的大小 + long sequence = ringBuffer.next(); + try { + // second, fill event into ring + QuarkEvent event = ringBuffer.get(sequence); + event.setData(data); + } finally { + // last, publish the sequence slot + // publish方法必须放在finally中以确保必须得到调用 + // 如果某个请求的sequence未被提交将会堵塞后续的发布操作或者其他的Producer + // 在事件发布后,这个sequence会传递给消费者(EventHandler) + ringBuffer.publish(sequence); + } + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkProperties.java new file mode 100644 index 00000000..979cd665 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/QuarkProperties.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.quark; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.convert.DurationUnit; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; + +/** + * Quark config properties. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/08/19 10:04 + */ +@Data +@ConfigurationProperties(prefix = "milkomeda.quark") +public class QuarkProperties { + + /** + * Cache buffer size. + */ + private Integer bufferSize = 1 << 13; + + /** + * Thread pool. + */ + private Pool pool = new Pool(); + + + @Data + static class Pool { + + private Integer core = 4; + + private Integer maximum = 8; + + @DurationUnit(ChronoUnit.MILLIS) + private Duration keepAliveTime = Duration.ofSeconds(20); + + private Integer queueSize = 1 << 15; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/Quarks.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/Quarks.java new file mode 100644 index 00000000..affe38ae --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/quark/Quarks.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.quark; + +import com.lmax.disruptor.*; +import com.lmax.disruptor.dsl.Disruptor; +import com.lmax.disruptor.dsl.ProducerType; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; + +/** + * Quark manager to create producer. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/08/20 10:23 + */ +public class Quarks { + + static Integer bufferSize; + + private static Executor executor; + + private static List> eventHandlerList; + + private static final Map producerMap = new ConcurrentHashMap<>(); + + static void setBufferSize(Integer bufferSize) { + Quarks.bufferSize = bufferSize; + } + + static void setExecutor(Executor executor) { + Quarks.executor = executor; + } + + static void setEventHandlerList(List> eventHandlerList) { + Quarks.eventHandlerList = eventHandlerList; + } + + /** + * Bind a producer with identifier. + * @param identifier such as user id. + * @return QuarkProducer + */ + @SuppressWarnings({"deprecation", "unchecked"}) + public static QuarkProducer bindProducer(Long identifier) { + if (producerMap.containsKey(identifier)) { + return producerMap.get(identifier); + } + QuarkEventFactory eventFactory = new QuarkEventFactory<>(); + Disruptor> disruptor = new Disruptor<>(eventFactory, bufferSize, executor, + ProducerType.SINGLE, new YieldingWaitStrategy()); + // handlers execute concurrently + disruptor.handleEventsWith(eventHandlerList.toArray(new EventHandler[]{})); + // this handler executes after upper handlers + //eventHandlerGroup.then(h3); + // h4 and h5 execute concurrently after h3 + //disruptor.after(h3).handleEventsWith(h4, h5); + disruptor.start(); + RingBuffer> ringBuffer = disruptor.getRingBuffer(); + QuarkProducer quarkProducer = new QuarkProducer(); + quarkProducer.setRingBuffer(ringBuffer); + quarkProducer.setDisruptor(disruptor); + producerMap.put(identifier, quarkProducer); + return quarkProducer; + } + + @SuppressWarnings("unchecked") + static QuarkProducer bindInnerProducer(Long identifier) { + if (producerMap.containsKey(identifier)) { + return producerMap.get(identifier); + } + RingBuffer> ringBuffer = RingBuffer.create( + ProducerType.MULTI, + new QuarkEventFactory<>(), + bufferSize, + new YieldingWaitStrategy()); + // Coordination barrier for tracking the cursor for publishers and sequence of dependent EventProcessors for processing a data structure + SequenceBarrier barrier = ringBuffer.newBarrier(); + // WorkerPool contains a pool of WorkProcessors that will consume sequences + WorkerPool> workerPool = new WorkerPool<>(ringBuffer, barrier, + new FatalExceptionHandler(), eventHandlerList.toArray(new WorkHandler[]{})); + // Sync event handler sequence to ringbuffer + ringBuffer.addGatingSequences(workerPool.getWorkerSequences()); + workerPool.start(executor); + QuarkProducer quarkProducer = new QuarkProducer(); + quarkProducer.setRingBuffer(ringBuffer); + producerMap.put(identifier, quarkProducer); + return quarkProducer; + } + + /** + * unbind a producer with identifier for release resource. + * @param identifier such as user id. + */ + public static void unbindProducer(Long identifier) { + if (!producerMap.containsKey(identifier)) { + return; + } + producerMap.get(identifier).getDisruptor().shutdown(); + producerMap.remove(identifier); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/BatchInjector.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/BatchInjector.java new file mode 100644 index 00000000..9072c520 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/BatchInjector.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; +import com.baomidou.mybatisplus.core.metadata.TableInfo; + +import java.util.List; + +/** + * Custom batch sql Injector. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/07/13 14:18 + */ +public class BatchInjector extends DefaultSqlInjector { + @Override + public List getMethodList(Class mapperClass, TableInfo tableInfo) { + List methodList = super.getMethodList(mapperClass, tableInfo); + // 内置的Insert batch, 主键是自增的不保存正常运行(仅支持Mysql) + //methodList.add(new InsertBatchSomeColumn()); + methodList.add(new InsertKeyBatchMethod()); + methodList.add(new InsertBatchMethod()); + methodList.add(new UpdateBatchByIdMethod()); + return methodList; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/BatchMapper.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/BatchMapper.java new file mode 100644 index 00000000..2fec9ccc --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/BatchMapper.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * Extend base mapper to support batch insert and update.
+ * Note: Must add `allowMultiQueries=true` in datasource connect url params. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/07/13 14:30 + */ +public interface BatchMapper extends BaseMapper { + + /** + * Batch insert record with the primary key. + * @param list record list + * @return insert effect count + */ + int insertKeyBatch(@Param("list") List list); + + /** + * Batch insert record without the primary key. + * @param list record list + * @return insert effect count + */ + int insertBatch(@Param("list") List list); + + /** + * Batch update record with id key. + * @param list record list + * @return update effect count + */ + int updateBatchById(@Param("list") List list); +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/IPageableService.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/IPageableService.java index 4ae7797c..0f7169a0 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/IPageableService.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/IPageableService.java @@ -7,6 +7,7 @@ import java.io.Serializable; import java.util.List; +import java.util.Map; import java.util.function.Function; /** @@ -25,6 +26,34 @@ public interface IPageableService extends IService { */ UniformPage selectByPage(UniformQueryPageData queryPageData); + /** + * Query by page data and match group. + * @param queryPageData page data + * @param group match group name + * @return UniformPage + * @since 3.15.0 + */ + UniformPage selectByPage(UniformQueryPageData queryPageData, String group); + + /** + * Query by page data and match data. + * @param queryPageData page data + * @param queryMatchData match data + * @return UniformPage + * @since 3.15.0 + */ + UniformPage selectByPage(UniformQueryPageData queryPageData, Map queryMatchData); + + /** + * Query by page data, match data and group name. + * @param queryPageData page data + * @param queryMatchData match data + * @param group match group name + * @return UniformPage + * @since 3.15.0 + */ + UniformPage selectByPage(UniformQueryPageData queryPageData, Map queryMatchData, String group); + /** * Remove record row before check it reference. * @param entity entity which has key of id value @@ -33,11 +62,11 @@ public interface IPageableService extends IService { boolean removeBeforeCheckRef(T entity); /** - * Assign authority for owner. + * Assign authority for the owner. * @param ownerId owner id * @param itemIds authority item id list - * @param conditionProvider condition which find owner - * @param generator create authority item row to owner + * @param conditionProvider condition which find linked with the owner + * @param generator create authority item row linked with the owner * @return true if success */ boolean assignAuthority(Serializable ownerId, List itemIds, diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/InsertBatchMethod.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/InsertBatchMethod.java new file mode 100644 index 00000000..7b858f95 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/InsertBatchMethod.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +/** + * Inject insert batch without the primary key method in mapper. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/07/13 15:05 + */ +public class InsertBatchMethod extends InsertKeyBatchMethod { + + private static final long serialVersionUID = 6280748690199232360L; + + public InsertBatchMethod() { + super("insertBatch"); + } + + @Override + protected boolean hasSetKey() { + return false; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/InsertKeyBatchMethod.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/InsertKeyBatchMethod.java new file mode 100644 index 00000000..323c15c4 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/InsertKeyBatchMethod.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.executor.keygen.NoKeyGenerator; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlSource; + +/** + * Inject insert batch with the primary key method in mapper. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/07/13 14:28 + */ +@Slf4j +public class InsertKeyBatchMethod extends AbstractMethod { + private static final long serialVersionUID = 6846870839503630813L; + + public InsertKeyBatchMethod(String methodName) { + super(methodName); + } + + public InsertKeyBatchMethod() { + this("insertKeyBatch"); + } + + @Override + public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { + final String sql = ""; + final String fieldSql = prepareFieldSql(tableInfo); + final String valueSql = prepareValuesSql(tableInfo); + final String sqlResult = String.format(sql, tableInfo.getTableName(), fieldSql, valueSql); + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass); + return this.addInsertMappedStatement(mapperClass, modelClass, this.methodName, sqlSource, new NoKeyGenerator(), null, null); + } + + /** + * Has primary key need append in sql. + * @return true if set in sql + */ + protected boolean hasSetKey() { + return true; + } + + private String prepareFieldSql(TableInfo tableInfo) { + StringBuilder fieldSql = new StringBuilder(); + if (hasSetKey()) { + fieldSql.append(tableInfo.getKeyColumn()).append(","); + } + tableInfo.getFieldList().forEach(x -> fieldSql.append(x.getColumn()).append(",")); + fieldSql.delete(fieldSql.length() - 1, fieldSql.length()); + fieldSql.insert(0, "("); + fieldSql.append(")"); + return fieldSql.toString(); + } + + private String prepareValuesSql(TableInfo tableInfo) { + final StringBuilder valueSql = new StringBuilder(); + valueSql.append(""); + if (hasSetKey()) { + valueSql.append("#{item.").append(tableInfo.getKeyProperty()).append("},"); + } + tableInfo.getFieldList().forEach(x -> valueSql.append("#{item.").append(x.getProperty()).append("},")); + valueSql.delete(valueSql.length() - 1, valueSql.length()); + valueSql.append(""); + return valueSql.toString(); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/PageableService.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/PageableService.java index f6a6f99f..16d78c5c 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/PageableService.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/PageableService.java @@ -13,21 +13,24 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.github.yizzuide.milkomeda.hydrogen.uniform.UniformPage; import com.github.yizzuide.milkomeda.hydrogen.uniform.UniformQueryPageData; +import com.github.yizzuide.milkomeda.sirius.wormhole.SiriusInspector; import com.github.yizzuide.milkomeda.universe.context.AopContextHolder; import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; import com.github.yizzuide.milkomeda.util.DataTypeConvertUtil; +import lombok.Data; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils; import java.io.Serializable; import java.lang.reflect.Field; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -35,12 +38,18 @@ * A pageable service impl extends from ServiceImpl. * * @since 3.14.0 + * @version 3.15.0 * @author yizzuide *
* Create at 2022/10/30 14:59 */ public class PageableService, T> extends ServiceImpl implements IPageableService { + /** + * Query matcher default group name. + */ + public static final String DEFAULT_GROUP = "default"; + @Override public M getBaseMapper() { return super.getBaseMapper(); @@ -56,8 +65,20 @@ protected Class currentModelClass() { return (Class) ReflectionKit.getSuperClassGenericType(this.getClass(), PageableService.class, 1); } - @SuppressWarnings("ConstantConditions") public UniformPage selectByPage(UniformQueryPageData queryPageData) { + return selectByPage(queryPageData, DEFAULT_GROUP); + } + + public UniformPage selectByPage(UniformQueryPageData queryPageData, String group) { + return selectByPage(queryPageData, null, group); + } + + public UniformPage selectByPage(UniformQueryPageData queryPageData, Map queryMatchData) { + return selectByPage(queryPageData, queryMatchData, DEFAULT_GROUP); + } + + @SuppressWarnings({"ConstantConditions", "rawtypes", "unchecked"}) + public UniformPage selectByPage(UniformQueryPageData queryPageData, Map queryMatchData, String group) { Page page = new Page<>(); page.setCurrent(queryPageData.getPageStart()); page.setSize(queryPageData.getPageSize()); @@ -70,69 +91,169 @@ public UniformPage selectByPage(UniformQueryPageData queryPageData) { boolean postOrderByASC = true; boolean needPostOrderBy = false; Field orderField = null; + List linkerFields = new ArrayList<>(); + Map> linkerNodes = new HashMap<>(); + Map mappingFields = new HashMap<>(); + // 需要在linker关联结果过滤条件 + Map filterMap = new HashMap<>(); for (Field field : fields) { - QueryMatcher queryMatcher = field.getDeclaredAnnotation(QueryMatcher.class); - if (queryMatcher == null) { + // convert QueryAutoLinker to QueryLinkerNode + QueryAutoLinker queryAutoLinker = AnnotationUtils.findAnnotation(field, QueryAutoLinker.class); + if (queryAutoLinker != null) { + Set nodes = QueryLinkerNode.build(queryAutoLinker); + linkerNodes.put(field.getName(), nodes); + linkerFields.add(field); + } + + // convert QueryLinker to QueryLinkerNode + Set queryLinkers = AnnotatedElementUtils.getMergedRepeatableAnnotations(field, QueryLinker.class, QueryLinkers.class); + if (!CollectionUtils.isEmpty(queryLinkers)) { + Set nodes = QueryLinkerNode.build(queryLinkers); + linkerNodes.put(field.getName(), nodes); + linkerFields.add(field); + } + + // collect mappings + if (!linkerNodes.isEmpty() && linkerNodes.containsKey(field.getName())) { + MappingNode.construct(linkerNodes.get(field.getName()), mappingFields, tableInfo, target); + } + + Set queryMatchers = AnnotatedElementUtils.getMergedRepeatableAnnotations(field, QueryMatcher.class, QueryMatchers.class); + if (CollectionUtils.isEmpty(queryMatchers)) { continue; } - String columnName = null; - // find from table info - for (TableFieldInfo tableFieldInfo : tableInfo.getFieldList()) { - if (field.getName().equals(tableFieldInfo.getProperty())) { - columnName = tableFieldInfo.getColumn(); - break; + for (QueryMatcher queryMatcher : queryMatchers) { + if (!Arrays.asList(queryMatcher.group()).contains(group)) { + continue; } - } - // get from @TableField or convert it - if (columnName == null) { - TableField tableField = field.getDeclaredAnnotation(TableField.class); - if (tableField != null) { - columnName = tableField.value(); - } else { - columnName = DataTypeConvertUtil.humpToLine(field.getName()); + String columnName = null; + // find from table info + for (TableFieldInfo tableFieldInfo : tableInfo.getFieldList()) { + if (field.getName().equals(tableFieldInfo.getProperty())) { + columnName = tableFieldInfo.getColumn(); + break; + } + } + // get from @TableField or convert it + if (columnName == null) { + TableField tableField = field.getDeclaredAnnotation(TableField.class); + if (tableField != null) { + columnName = tableField.value(); + } + if (StringUtils.isEmpty(columnName)) { + columnName = DataTypeConvertUtil.humpToLine(field.getName()); + } } - } - Object fieldValue = null; - if (target != null) { - // can get field value from getter method? - tableInfo.getPropertyValue(target, field.getName()); - // reflect it! - if (fieldValue == null) { - ReflectionUtils.makeAccessible(field); - fieldValue = ReflectionUtils.getField(field, target); + Object fieldValue = null; + if (target != null) { + // can get field value from getter method? + fieldValue = tableInfo.getPropertyValue(target, field.getName()); + // reflect it! + if (fieldValue == null) { + ReflectionUtils.makeAccessible(field); + fieldValue = ReflectionUtils.getField(field, target); + } } - } - boolean fieldNonNull = Objects.nonNull(target) && Objects.nonNull(fieldValue); - if (queryMatcher.prefect() == PrefectType.EQ) { - queryWrapper.eq(fieldNonNull, columnName, fieldValue); - } else if (queryMatcher.prefect() == PrefectType.LIKE) { + boolean fieldNonNull = Objects.nonNull(target) && Objects.nonNull(fieldValue); + if (queryMatcher.prefect() == PrefectType.EQ) { + if (!fieldNonNull && StringUtils.isNotEmpty(queryMatcher.matchDataField())) { + fieldValue = findLinkerValue(linkerNodes, queryMatcher, linkerFields, queryPageData.getEntity(), field, tableInfo, filterMap); + fieldNonNull = fieldValue != null; + } + queryWrapper.eq(fieldNonNull, columnName, fieldValue); + } else if (queryMatcher.prefect() == PrefectType.NEQ) { + queryWrapper.ne(fieldNonNull, columnName, fieldValue); + } else if (queryMatcher.prefect() == PrefectType.EMPTY) { + queryWrapper.eq(ObjectUtils.isEmpty(fieldValue), columnName, fieldValue); + } else if (queryMatcher.prefect() == PrefectType.LIKE) { queryWrapper.like(fieldNonNull && StringUtils.isNotBlank(fieldValue.toString()), columnName, fieldValue); - } else if (queryMatcher.prefect() == PrefectType.PageDate) { - queryWrapper.ge(Objects.nonNull(queryPageData.getStartDate()), columnName, queryPageData.getStartDate()); - queryWrapper.le(Objects.nonNull(queryPageData.getEndDate()), columnName, queryPageData.getEndDate()); - } else if (queryMatcher.prefect() == PrefectType.OrderByPre) { - queryWrapper.orderBy(true, queryMatcher.forward(), columnName); - } else if (queryMatcher.prefect() == PrefectType.OrderByPost) { - needPostOrderBy = true; - postOrderByASC = queryMatcher.forward(); - orderField = field; - } else { - additionParseQueryMatcher(queryWrapper, queryMatcher.prefectString(), columnName, fieldNonNull, fieldValue); + } else if (queryMatcher.prefect() == PrefectType.IN || queryMatcher.prefect() == PrefectType.LINK_EQ_IN) { + Collection valueObjects = Collections.singletonList(fieldValue); + boolean condition = fieldNonNull; + if (queryMatchData != null) { + Object values = queryMatchData.get(field.getName()); + if (values instanceof Object[]) { + valueObjects = Arrays.asList((Object[]) values); + condition = true; + } + } + if (!condition && StringUtils.isNotEmpty(queryMatcher.matchDataField())) { + valueObjects = (Collection) findLinkerValue(linkerNodes, queryMatcher, linkerFields, queryPageData.getEntity(), field, tableInfo, filterMap); + condition = valueObjects != null; + } + queryWrapper.in(condition, columnName, valueObjects); + } else if (queryMatcher.prefect() == PrefectType.PageDate) { + queryWrapper.ge(Objects.nonNull(queryPageData.getStartDate()), columnName, queryPageData.getStartDate()); + queryWrapper.le(Objects.nonNull(queryPageData.getEndDate()), columnName, queryPageData.getEndDate()); + } else if (queryMatcher.prefect() == PrefectType.PageUnixTime) { + queryWrapper.ge(Objects.nonNull(queryPageData.getStartDate()), columnName, queryPageData.getStartUnixTime()); + queryWrapper.le(Objects.nonNull(queryPageData.getEndDate()), columnName, queryPageData.getEndUnixTime()); + } else if (queryMatcher.prefect() == PrefectType.OrderByPre) { + queryWrapper.orderBy(true, queryMatcher.forward(), columnName); + } else if (queryMatcher.prefect() == PrefectType.OrderByPost) { + needPostOrderBy = true; + postOrderByASC = queryMatcher.forward(); + orderField = field; + } else { + additionParseQueryMatcher(queryWrapper, queryMatcher.prefectString(), columnName, fieldNonNull, fieldValue); + } } } - Page recordPage = this.baseMapper.selectPage(page, queryWrapper); + // 设置查询字段 + List includeColumns = new ArrayList<>(); + List excludeColumns = new ArrayList<>(); + for (TableFieldInfo tableFieldInfo : tableInfo.getFieldList()) { + QueryFieldInclude fieldInclude = AnnotationUtils.findAnnotation(tableFieldInfo.getField(), QueryFieldInclude.class); + if (fieldInclude != null && ArrayUtils.contains(fieldInclude.group(), group)) { + includeColumns.add(tableFieldInfo.getColumn()); + continue; + } + QueryFieldExclude fieldExclude = AnnotationUtils.findAnnotation(tableFieldInfo.getField(), QueryFieldExclude.class); + if (fieldExclude != null && ArrayUtils.contains(fieldExclude.group(), group)) { + excludeColumns.add(tableFieldInfo.getColumn()); + } + } + if (!includeColumns.isEmpty() && !includeColumns.contains(tableInfo.getKeyColumn())) { + includeColumns.add(tableInfo.getKeyColumn()); + } + if (!includeColumns.isEmpty() || !excludeColumns.isEmpty()) { + queryWrapper.select(getEntityClass(), fi -> { + if (includeColumns.contains(fi.getColumn())) { + return true; + } + return !excludeColumns.contains(fi.getColumn()); + }); + } + UniformPage uniformPage = new UniformPage<>(); - uniformPage.setTotalSize(recordPage.getTotal()); - uniformPage.setPageCount(recordPage.getPages()); - List records = recordPage.getRecords(); + List records; + // 如果页记录数为-1,则不分页 + if (page.getSize() == -1) { + String sqlSelect = queryWrapper.getSqlSelect(); + queryWrapper.select("*"); + Long totalSize = this.baseMapper.selectCount(queryWrapper); + if (totalSize > 10000) { + throw new UnsupportedOperationException("Record row over more then 10000"); + } + queryWrapper.select(sqlSelect); + records = this.baseMapper.selectList(queryWrapper); + uniformPage.setTotalSize(totalSize); + uniformPage.setPageCount(1L); + } else { + Page recordPage = this.baseMapper.selectPage(page, queryWrapper); + records = recordPage.getRecords(); + uniformPage.setTotalSize(recordPage.getTotal()); + uniformPage.setPageCount(recordPage.getPages()); + } if (!CollectionUtils.isEmpty(records) && needPostOrderBy) { // impl of Ordered if (target instanceof Ordered) { records = records.stream() - .sorted(OrderComparator.INSTANCE.withSourceProvider(t -> t)).collect(Collectors.toList()); + .sorted(OrderComparator.INSTANCE.withSourceProvider(t -> t)) + .collect(Collectors.toList()); } else { Field finalOrderField = orderField; records.sort(Comparator.comparingInt(t -> { @@ -150,6 +271,101 @@ public UniformPage selectByPage(UniformQueryPageData queryPageData) { Collections.reverse(records); } } + + // query link name + if (!CollectionUtils.isEmpty(linkerFields) && !CollectionUtils.isEmpty(records)) { + for (Field linkerField : linkerFields) { + // cache link entity list with the field + Map> linkEntityListCacheMap = new HashMap<>(); + Set queryLinkerNodes = linkerNodes.get(linkerField.getName()); + for (QueryLinkerNode linkerNode : queryLinkerNodes) { + if (!Arrays.asList(linkerNode.getGroup()).contains(group)) { + continue; + } + BaseMapper linkMapper = SiriusInspector.getMapper(linkerNode.getLinkEntityType()); + List linkEntityList; + String linkMapperClassName = linkMapper.getClass().getName(); + // get link entity table info + TableInfo linkTableInfo = TableInfoHelper.getTableInfo(linkerNode.getLinkEntityType()); + if (linkEntityListCacheMap.containsKey(linkMapperClassName)) { + linkEntityList = linkEntityListCacheMap.get(linkMapperClassName); + } else { + Set idValues = records.stream() + .map(e -> (Serializable) tableInfo.getPropertyValue(e, linkerField.getName())) + .filter(e -> !linkerNode.getLinkIdIgnore().equals(e.toString())) + .collect(Collectors.toSet()); + if (CollectionUtils.isEmpty(idValues)) { + continue; + } + QueryWrapper linkQueryWrapper = new QueryWrapper<>(); + boolean isSameIdNamed = linkerNode.getLinkIdFieldName().equals(linkTableInfo.getKeyColumn()); + String linkIdColumn = isSameIdNamed ? linkerNode.getLinkIdFieldName() : findColumnName(linkTableInfo, linkerNode.getLinkIdFieldName()); + linkQueryWrapper.in(linkIdColumn, idValues); + // add condition of mappings + if (!CollectionUtils.isEmpty(mappingFields)) { + mappingFields.forEach((mappingFieldName, mappingNode) -> { + String colName = findColumnName(linkTableInfo ,mappingFieldName); + Object mappingFieldValue = mappingNode.getFieldValue(); + if (mappingNode.getPrefectType() == PrefectType.EQ) { + linkQueryWrapper.eq(colName, mappingFieldValue); + } else if (mappingNode.getPrefectType() == PrefectType.NEQ) { + linkQueryWrapper.ne(colName, mappingFieldValue); + } else if (mappingNode.getPrefectType() == PrefectType.EMPTY) { + linkQueryWrapper.eq(ObjectUtils.isEmpty(mappingFieldValue), colName, mappingFieldValue); + } else if (mappingNode.getPrefectType() == PrefectType.IN) { + linkQueryWrapper.in(colName, mappingFieldValue); + } else if (mappingNode.getPrefectType() == PrefectType.LIKE) { + linkQueryWrapper.like(colName, mappingFieldValue); + } + }); + } + + // 如果有需要过滤的link字段 + if (!filterMap.isEmpty()) { + for (String key : filterMap.keySet()) { + PrefectLinkNode prefectLinkNode = filterMap.get(key); + String linkNameColumn = findColumnName(linkTableInfo, prefectLinkNode.getLinkFieldName()); + if (prefectLinkNode.getPrefectType() == PrefectType.IN) { + linkQueryWrapper.like(linkNameColumn, prefectLinkNode.getTargetFieldValue()); + } else { + linkQueryWrapper.eq(linkNameColumn, prefectLinkNode.getTargetFieldValue()); + } + } + } + + // 设置查询的字段 + List queryColumns = new ArrayList<>(); + queryColumns.add(linkTableInfo.getKeyColumn()); + // 所有当前类型的linkId和LinkName + List linkColumns = queryLinkerNodes.stream() + .filter(ql -> ql.getLinkEntityType() == linkerNode.getLinkEntityType()) + .map(ql -> new String[] { ql.getLinkIdFieldName(), ql.getLinkFieldName() }) + .flatMap(Arrays::stream) + .distinct() + .filter(f -> !queryColumns.contains(f)) + .map(f -> findColumnName(linkTableInfo, f)) + .collect(Collectors.toList()); + queryColumns.addAll(linkColumns); + linkQueryWrapper.select(queryColumns.toArray(new String[]{})); + linkEntityList = linkMapper.selectList(linkQueryWrapper); + linkEntityListCacheMap.put(linkMapperClassName, linkEntityList); + } + if (!CollectionUtils.isEmpty(linkEntityList)) { + for (T record : records) { + for (Object le : linkEntityList) { + Object linkId = linkTableInfo.getPropertyValue(le, linkerNode.getLinkIdFieldName()); + Object nameValue = linkTableInfo.getPropertyValue(le, linkerNode.getLinkFieldName()); + Object matchIdValue = tableInfo.getPropertyValue(record, linkerField.getName()); + if (String.valueOf(matchIdValue).equals(String.valueOf(linkId))) { + tableInfo.setPropertyValue(record, linkerNode.getTargetFieldName(), nameValue); + break; + } + } + } + } + } + } + } uniformPage.setList(records); return uniformPage; } @@ -177,14 +393,7 @@ public boolean removeBeforeCheckRef(T entityWithID) { continue; } if (refMatcher.type() == RefMatcher.RefType.SELF) { - String columnName = null; - // find from table info - for (TableFieldInfo tableFieldInfo : tableInfo.getFieldList()) { - if (field.getName().equals(tableFieldInfo.getProperty())) { - columnName = tableFieldInfo.getColumn(); - break; - } - } + String columnName = findColumnName(tableInfo, field.getName()); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq(columnName, id); Long count = this.baseMapper.selectCount(queryWrapper); @@ -194,12 +403,12 @@ public boolean removeBeforeCheckRef(T entityWithID) { break; } } - // check RefMatcher on mapper type + // check RefMatcher has annotated on a mapper RefMatcher refMatcher = this.mapperClass.getDeclaredAnnotation(RefMatcher.class); if(refMatcher != null && BaseMapper.class.isAssignableFrom(refMatcher.foreignMapper())) { QueryWrapper foreignQueryWrapper = new QueryWrapper(); foreignQueryWrapper.eq(refMatcher.foreignField(), id); - BaseMapper referenceMapper = (BaseMapper) ApplicationContextHolder.get().getBean(refMatcher.foreignMapper()); + BaseMapper referenceMapper = ApplicationContextHolder.get().getBean(refMatcher.foreignMapper()); if (referenceMapper.selectCount(foreignQueryWrapper) > 0) { return false; } @@ -219,7 +428,191 @@ public boolean assignAuthority(Serializable ownerId, List sysUserRoles = itemIds.stream().map(generator).collect(Collectors.toList()); - return AopContextHolder.self(this.getClass()).saveBatch(sysUserRoles); + List Authorities = itemIds.stream().map(generator).collect(Collectors.toList()); + return AopContextHolder.self(this.getClass()).saveBatch(Authorities); + } + + @SuppressWarnings("unchecked") + private Object findLinkerValue(Map> linkerNodes, QueryMatcher queryMatcher, List linkerFields, Object entity, Field field, TableInfo tableInfo, Map filterMap) { + Object searchValue = tableInfo.getPropertyValue(entity, queryMatcher.matchDataField()); + if (searchValue == null) { + return null; + } + for (Field linkerField : linkerFields) { + // find linker field which matches `matchDataField` from QueryMatcher + Set queryLinkerNodes = linkerNodes.get(linkerField.getName()); + if (queryLinkerNodes == null) { + continue; + } + QueryLinkerNode linkerNode = queryLinkerNodes.stream() + .filter(node -> node.getTargetFieldName().equals(queryMatcher.matchDataField())).findFirst().orElse(null); + if (linkerNode == null) { + continue; + } + Class linkClass = linkerNode.getLinkEntityType(); + TableInfo linkTableInfo = TableInfoHelper.getTableInfo(linkClass); + if (linkTableInfo == null) { + return null; + } + + // add this linker field name and value as condition for the link query + PrefectLinkNode prefectLinkNode = new PrefectLinkNode(); + prefectLinkNode.setPrefectType(queryMatcher.prefect()); + prefectLinkNode.setTargetFieldValue(searchValue); + prefectLinkNode.setTargetFieldName(linkerNode.getTargetFieldName()); + prefectLinkNode.setLinkFieldName(linkerNode.getLinkFieldName()); + filterMap.put(linkerNode.getLinkFieldName(), prefectLinkNode); + // find link entity with linker mapper + BaseMapper linkMapper = (BaseMapper) SiriusInspector.getMapper(linkClass); + String linkNameColumn = findColumnName(linkTableInfo, linkerNode.getLinkFieldName()); + QueryWrapper queryExample = new QueryWrapper<>(); + if (queryMatcher.prefect() == PrefectType.IN || queryMatcher.prefect() == PrefectType.LINK_EQ_IN) { + if (queryMatcher.prefect() == PrefectType.IN) { + queryExample.like(linkNameColumn, searchValue); + } else { + queryExample.eq(linkNameColumn, searchValue); + } + String linkIdColumn = findColumnName(linkTableInfo, linkerNode.getLinkIdFieldName()); + queryExample.select(linkTableInfo.getKeyColumn(), linkNameColumn, linkIdColumn); + List linkRecordlist = linkMapper.selectList(queryExample); + if (CollectionUtils.isEmpty(linkRecordlist)) { + return genNonFoundValue(field, queryMatcher.prefect()); + } + // collect and return link entity id list + return linkRecordlist.stream() + .map(record -> linkTableInfo.getPropertyValue(record, linkerNode.getLinkIdFieldName())) + .collect(Collectors.toSet()); + } + if (queryMatcher.prefect() == PrefectType.EQ) { + queryExample.eq(linkNameColumn, searchValue); + queryExample.select(linkTableInfo.getKeyColumn(), linkNameColumn); + Object linkRecord = linkMapper.selectOne(queryExample); + if (linkRecord == null) { + return genNonFoundValue(field, queryMatcher.prefect()); + } + return linkTableInfo.getPropertyValue(linkRecord, linkerNode.getLinkIdFieldName()); + } + } + return null; + } + + private Object genNonFoundValue(Field field, PrefectType type) { + if (field.getType() == Integer.class || field.getType() == Long.class) { + return type == PrefectType.EQ ? -1 : Collections.singletonList(-1); + } + if (field.getType() == String.class) { + return type == PrefectType.EQ ? "-1" : Collections.singletonList("-1"); + } + return null; + } + + private String findColumnName(TableInfo tableInfo, String fieldName) { + for (TableFieldInfo tableFieldInfo : tableInfo.getFieldList()) { + if (fieldName.equals(tableFieldInfo.getProperty())) { + return tableFieldInfo.getColumn(); + } + } + return null; + } + + @Data + static class QueryLinkerNode { + private String targetFieldName; + private String linkFieldName; + private String linkIdFieldName; + private String linkIdIgnore; + private String mappings; + private Class linkEntityType; + private String[] group; + + static Set build(Set queryLinkers) { + return queryLinkers.stream().map(ql -> { + QueryLinkerNode node = new QueryLinkerNode(); + node.setTargetFieldName(ql.targetNameField()); + node.setLinkFieldName(ql.linkNameField()); + node.setLinkIdFieldName(ql.linkIdField()); + node.setLinkIdIgnore(ql.linkIdIgnore()); + node.setMappings(ql.mappings()); + node.setLinkEntityType(ql.linkEntityType()); + node.setGroup(ql.group()); + return node; + }).collect(Collectors.toSet()); + } + + static Set build(QueryAutoLinker queryAutoLinker) { + String[] linkerParts = queryAutoLinker.links().split(","); + int linkerSize = linkerParts.length; + Set nodes = new HashSet<>(linkerSize); + for (int i = 0; i < linkerSize; i++) { + String[] linkerPart = linkerParts[i].split("\\s*->\\s*"); + String targetFieldName = linkerPart[0]; + String linkFieldName = linkerPart.length == 1? targetFieldName : linkerPart[1]; + String linkIdName = linkerPart.length == 3 ? linkerPart[2]: queryAutoLinker.linkIdField(); + String linkIdIgnore = "0"; + if (linkIdName.contains("!")) { + String origLinkIdName = linkIdName; + int sepIndex = origLinkIdName.indexOf('!'); + linkIdName = origLinkIdName.substring(0, sepIndex); + linkIdIgnore = origLinkIdName.substring(sepIndex + 1); + } + String[] matchGroup = new String[] { "default" }; + if (queryAutoLinker.groups() != null && queryAutoLinker.groups().length != 0) { + String groupItem = queryAutoLinker.groups()[i]; + matchGroup = groupItem.split(","); + } + QueryLinkerNode queryLinkerNode = new QueryLinkerNode(); + queryLinkerNode.setTargetFieldName(targetFieldName); + queryLinkerNode.setLinkFieldName(linkFieldName); + queryLinkerNode.setLinkIdFieldName(linkIdName); + queryLinkerNode.setLinkIdIgnore(linkIdIgnore); + queryLinkerNode.setMappings(queryAutoLinker.mappings()); + queryLinkerNode.setLinkEntityType(queryAutoLinker.type()); + queryLinkerNode.setGroup(matchGroup); + nodes.add(queryLinkerNode); + } + return nodes; + } + } + + @Data + static class PrefectLinkNode { + private PrefectType prefectType; + private Object targetFieldValue; + private String targetFieldName; + private String linkFieldName; + } + + @Data + static class MappingNode { + private PrefectType prefectType; + private Object fieldValue; + private String fieldName; + + static void construct(Set nodes, Map container, TableInfo tableInfo, Object target) { + QueryLinkerNode node = nodes.stream().filter(ql -> StringUtils.isNotEmpty(ql.getMappings())).findFirst().orElse(null); + if (node != null) { + String[] mappings = node.getMappings().split(","); + Arrays.stream(mappings) + .map(StringUtils::strip) + .map(m -> m.split("\\s*->\\s*")) + .forEach(m -> { + String targetFieldName = m[0]; + String fieldName = m[1]; + Object fieldValue = tableInfo.getPropertyValue(target, targetFieldName); + MappingNode mappingNode = new MappingNode(); + mappingNode.setFieldName(fieldName); + mappingNode.setFieldValue(fieldValue); + mappingNode.setPrefectType(PrefectType.EQ); + TableFieldInfo fieldInfo = tableInfo.getFieldList().stream().filter(fi -> fi.getField().getName().equals(targetFieldName)).findFirst().orElse(null); + if (fieldInfo != null) { + QueryMatcher mappingQueryMatcher = AnnotationUtils.findAnnotation(fieldInfo.getField(), QueryMatcher.class); + if (mappingQueryMatcher != null) { + mappingNode.setPrefectType(mappingQueryMatcher.prefect()); + } + } + container.put(fieldName, mappingNode); + }); + } + } } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/PrefectType.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/PrefectType.java index 76a0531b..a976ad05 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/PrefectType.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/PrefectType.java @@ -12,23 +12,52 @@ */ public enum PrefectType { /** - * equals field. + * Equals field. */ EQ, + + /** + * Not Equals field. + */ + NEQ, + + /** + * Match empty field (null or ''). + */ + EMPTY, + + /** + * Match using sql `in`. + */ + IN, + /** - * sql like field. + * Match full words with {@link QueryLinker} and using sql `in`. + */ + LINK_EQ_IN, + + /** + * Match using sql `like`. */ LIKE, + /** - * sql order by. + * Match using sql `order by`. */ OrderByPre, + /** - * page result list order by. + * Page result list order by. */ OrderByPost, + + /** + * Using for `startDate` and `endDate` of {@link UniformQueryPageData} query. + */ + PageDate, + /** - * using for `startDate` and `endDate` of {@link UniformQueryPageData} query. + * Query with unix time relative to {@link PrefectType#PageDate}. */ - PageDate + PageUnixTime } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryAutoLinker.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryAutoLinker.java new file mode 100644 index 00000000..207700bd --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryAutoLinker.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import java.lang.annotation.*; + +/** + * Auto generate multi {@link QueryLinker}. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/09/21 10:33 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +@Inherited +public @interface QueryAutoLinker { + + /** + * Link expression. + * @return link expression + */ + String links(); + + /** + * Reference id field name. + * @return field name + */ + String linkIdField() default "id"; + + /** + * Bind link expression in query group. + * @return group name + */ + String[] groups() default {}; + + /** + * Mapping fields from target to linker. + * @return mapping expression + */ + String mappings() default ""; + + /** + * Link entity class. + * @return mapper class + */ + Class type(); +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryFieldExclude.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryFieldExclude.java new file mode 100644 index 00000000..878189d7 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryFieldExclude.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import java.lang.annotation.*; + +/** + * Query select needs exclude, not compatible with {@link QueryFieldInclude}. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/09/15 15:21 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +@Inherited +public @interface QueryFieldExclude { + /** + * select excluding in a group. + * @return group name + */ + String[] group() default {"default"}; +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryFieldInclude.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryFieldInclude.java new file mode 100644 index 00000000..dbf64e05 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryFieldInclude.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import java.lang.annotation.*; + +/** + * Query select needs include, not compatible with {@link QueryFieldExclude}. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/09/15 15:17 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +@Inherited +public @interface QueryFieldInclude { + /** + * select include in a group. + * @return group name + */ + String[] group() default {"default"}; +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryLinker.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryLinker.java new file mode 100644 index 00000000..3e1d4594 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryLinker.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import java.lang.annotation.*; + +/** + * Query linker using for {@link IPageableService}. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/06/07 01:58 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +@Inherited +@Repeatable(QueryLinkers.class) +public @interface QueryLinker { + /** + * Target field for set name. + * @return set field + */ + String targetNameField(); + + /** + * Link name field. + * @return link name + */ + String linkNameField(); + + /** + * Reference id field name. + * @return field name + */ + String linkIdField() default "id"; + + /** + * Link id value should ignore. + * @return id value + */ + String linkIdIgnore() default "0"; + + /** + * Mapping fields from target to linker. + * @return mapping expression + */ + String mappings() default ""; + + /** + * Link entity class. + * @return mapper class + */ + Class linkEntityType(); + + /** + * Bind conditions in query group. + * @return group name + */ + String[] group() default { "default" }; +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryLinkers.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryLinkers.java new file mode 100644 index 00000000..0cf0762a --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryLinkers.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import java.lang.annotation.*; + +/** + * Support repeatable of {@link QueryLinker}. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/06/12 15:55 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +@Inherited +public @interface QueryLinkers { + QueryLinker[] value(); +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryMatcher.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryMatcher.java index aab11c75..eb954ca2 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryMatcher.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryMatcher.java @@ -1,5 +1,7 @@ package com.github.yizzuide.milkomeda.sirius; +import org.springframework.core.annotation.AliasFor; + import java.lang.annotation.*; /** @@ -13,22 +15,47 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +@Inherited +@Repeatable(QueryMatchers.class) public @interface QueryMatcher { /** - * Type to match query. + * Type to match and query. + * @return PrefectType + * @since 3.15.0 + */ + @AliasFor("prefect") + PrefectType value() default PrefectType.EQ; + + /** + * Type to match and query. * @return PrefectType + * */ + @AliasFor("value") PrefectType prefect() default PrefectType.EQ; /** * Custom type of query match with {@link PageableService#additionParseQueryMatcher(com.baomidou.mybatisplus.core.conditions.query.QueryWrapper, java.lang.String, java.lang.String, boolean, java.lang.Object)}. - * @return string of prefect type + * @return string of a prefect type */ String prefectString() default ""; /** - * Query result list is forward type. - * @return true if you need forward + * Query result list order type. + * @return true if you need order with asc */ boolean forward() default true; + + /** + * Query link field with `targetNameField` of {@link QueryLinker}. + * @return match data field name + */ + String matchDataField() default ""; + + /** + * Bundle conditions in a group. + * @return group name + * @since 3.15.0 + */ + String[] group() default { "default" }; } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryMatchers.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryMatchers.java new file mode 100644 index 00000000..d8a679b3 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/QueryMatchers.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import java.lang.annotation.*; + +/** + * Support repeatable of {@link QueryMatcher}. + * + * @author yizzuide + * Create at 2023/06/12 23:38 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +@Inherited +public @interface QueryMatchers { + QueryMatcher[] value(); +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/RefMatcher.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/RefMatcher.java index 2ec5d1fa..b673955d 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/RefMatcher.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/RefMatcher.java @@ -43,16 +43,16 @@ RefType type() default RefType.SELF; /** - * Foreign field referenced it, must add on mapper type. + * Foreign field referenced at, must add on the mapper type. * @return reference field */ String foreignField() default ""; /** - * Foreign mapper class, must add on mapper type. - * @return mapper class + * Foreign mapper type referenced at, must add on the mapper type. + * @return entity class */ - Class foreignMapper() default BaseMapper.class; + Class foreignMapper() default BaseMapper.class; enum RefType { /** diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusConfig.java index f697ba52..6799f3b9 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusConfig.java @@ -24,31 +24,57 @@ import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer; +import com.baomidou.mybatisplus.core.MybatisConfiguration; +import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder; +import com.baomidou.mybatisplus.core.MybatisXMLConfigBuilder; +import com.baomidou.mybatisplus.core.config.GlobalConfig; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler; +import com.baomidou.mybatisplus.core.handlers.StrictFill; +import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import com.github.yizzuide.milkomeda.sirius.wormhole.SiriusInspector; import com.github.yizzuide.milkomeda.universe.extend.env.CollectionsPropertySource; +import com.github.yizzuide.milkomeda.universe.extend.env.SpELPropertySource; +import com.github.yizzuide.milkomeda.util.ReflectUtil; +import lombok.Getter; import org.apache.ibatis.reflection.MetaObject; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import javax.annotation.PostConstruct; -import java.util.List; -import java.util.function.Supplier; +import java.io.IOException; +import java.util.*; /** - * Sirius module config + * Sirius module config. + * 基于YML配置零代码全自动式Mybatis-plus字段填充插件,再也不用添加属性注解@TableField * + * @see MybatisConfiguration + * @see MybatisSqlSessionFactoryBean + * @see MybatisSqlSessionFactoryBuilder + * @since 3.14.0 + * @version 3.15.0 * @author yizzuide *
* Create at 2022/10/30 17:52 */ -@AutoConfigureAfter(MybatisPlusAutoConfiguration.class) +@AutoConfigureBefore(MybatisPlusAutoConfiguration.class) @EnableConfigurationProperties(SiriusProperties.class) @Configuration public class SiriusConfig { @@ -56,6 +82,16 @@ public class SiriusConfig { @Autowired private SiriusProperties props; + @Bean + public SiriusInspector siriusInspector() { + return new SiriusInspector(); + } + + @Bean + public BatchInjector batchInjector() { + return new BatchInjector(); + } + @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); @@ -66,10 +102,40 @@ public MybatisPlusInterceptor mybatisPlusInterceptor() { return mybatisPlusInterceptor; } - // 自定义mybatis配置 + // 属性自定义配置,在Mybatis-plus自动配置前执行 + @Bean + public MybatisPlusPropertiesCustomizer propertiesCustomizer(ResourceLoader resourceLoader) throws IOException { + return properties -> { + GlobalConfig globalConfig = properties.getGlobalConfig(); + globalConfig.setBanner(false); + if (properties.getConfiguration() == null) { + // 如果有设置XML配置 + if (properties.getConfigLocation() != null) { + Resource resource = resourceLoader.getResource(properties.getConfigLocation()); + MybatisXMLConfigBuilder xmlConfigBuilder; + try { + xmlConfigBuilder = new MybatisXMLConfigBuilder(resource.getInputStream(), null, + properties.getConfigurationProperties()); + } catch (IOException e) { + throw new RuntimeException("Sirius load mybatis xml config error", e); + } + // ConfigLocation -> Mybatis Configuration + properties.setConfigLocation(null); + properties.setConfiguration((MybatisConfiguration) xmlConfigBuilder.getConfiguration()); + return; + } + // 没有设置过,创建默认配置 + MybatisConfiguration configuration = new MybatisConfiguration(); + configuration.setDefaultEnumTypeHandler(MybatisEnumTypeHandler.class); + properties.setConfiguration(configuration); + } + }; + } + + // 扩展Mybatis Configuration配置,开发者可照样在应用层定义多个ConfigurationCustomizer Bean(优先级大于XML配置) @Bean public ConfigurationCustomizer configurationCustomizer() { - return configuration -> configuration.setMapUnderscoreToCamelCase(true); + return configuration -> configuration.setDefaultScriptingLanguage(SiriusMybatisXMLLanguageDriver.class); } @Bean @@ -77,49 +143,60 @@ public MybatisPlusMetaObjectHandler mybatisPlusMetaObjectHandler() { return new MybatisPlusMetaObjectHandler(); } + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") static class MybatisPlusMetaObjectHandler implements MetaObjectHandler { @Autowired private SiriusProperties props; + @Getter private List insertFields; + @Getter private List updateFields; - private Supplier insertValueProvider; - - private Supplier updateValueProvider; - @PostConstruct public void init() { List autoInterpolates = props.getAutoInterpolates(); if (CollectionUtils.isEmpty(autoInterpolates)) { return; } - autoInterpolates.forEach(autoInterpolate -> { - if (autoInterpolate.getFieldFill() == FieldFill.INSERT) { - insertFields = autoInterpolate.getFields(); - insertValueProvider = () -> CollectionsPropertySource.of(autoInterpolate.getPsValue()); - } else if (autoInterpolate.getFieldFill() == FieldFill.UPDATE) { - updateFields = autoInterpolate.getFields(); - updateValueProvider = () -> CollectionsPropertySource.of(autoInterpolate.getPsValue()); + autoInterpolates.forEach(autoInterpolate -> appendFields( + autoInterpolate.getFieldFill() != FieldFill.UPDATE, + autoInterpolate.getFieldFill() == FieldFill.UPDATE || + autoInterpolate.getFieldFill() == FieldFill.INSERT_UPDATE, autoInterpolate)); + } + + private void appendFields(boolean insertFill, boolean updateFill, SiriusProperties.AutoInterpolate autoInterpolate) { + if (insertFill) { + if (insertFields == null) { + insertFields = new ArrayList<>(autoInterpolate.getFields()); + } else { + insertFields.addAll(autoInterpolate.getFields()); } - }); + } + if (updateFill) { + if (updateFields == null) { + updateFields = new ArrayList<>(autoInterpolate.getFields()); + } else { + updateFields.addAll(autoInterpolate.getFields()); + } + } } @Override public void insertFill(MetaObject metaObject) { - executeFill(metaObject, insertFields, insertValueProvider); + executeFill(true, metaObject, insertFields); } @Override public void updateFill(MetaObject metaObject) { - executeFill(metaObject, updateFields, updateValueProvider); + executeFill(false, metaObject, updateFields); } @SuppressWarnings("unchecked") - private void executeFill(MetaObject metaObject, List fields, Supplier valueProvider) { + private void executeFill(boolean insertFill, MetaObject metaObject, List fields) { if (CollectionUtils.isEmpty(fields)) { return; } @@ -127,10 +204,115 @@ private void executeFill(MetaObject metaObject, List fields, Supplier if(!metaObject.hasGetter(fieldName)) { return; } - Object value = valueProvider.get(); - Class aClass = (Class) value.getClass(); - this.strictInsertFill(metaObject, fieldName, aClass, value); + Optional valueOp = Optional.ofNullable(findPsValue(fieldName)); + if (valueOp.isPresent()) { + Object value = valueOp.get(); + Class aClass = (Class) value.getClass(); + // convert value type + SiriusProperties.AutoInterpolate interpolate = findInterpolate(fieldName); + if (interpolate != null && interpolate.getConverterClazz() != null) { + GenericConverter converter = ReflectUtil.newInstance(interpolate.getConverterClazz()); + if (converter != null) { + TableInfo tableInfo = this.findTableInfo(metaObject); + for (TableFieldInfo fieldInfo : tableInfo.getFieldList()) { + if (fieldInfo.getProperty().equals(fieldName)) { + Class targetType = fieldInfo.getPropertyType(); + value = converter.convert(value, TypeDescriptor.valueOf(aClass), TypeDescriptor.valueOf(targetType)); + if (value != null) { + aClass = (Class) value.getClass(); + } + } + } + } + } + + if (insertFill) { + this.strictInsertFill(metaObject, fieldName, aClass, value); + } else { + this.strictUpdateFill(metaObject, fieldName, aClass, value); + } + } }); } + + @Override + public MetaObjectHandler strictFill(boolean insertFill, TableInfo tableInfo, MetaObject metaObject, List> strictFills) { + if (props.isAutoAddFill()) { + strictFills.forEach(i -> { + final String fieldName = i.getFieldName(); + final Class fieldType = i.getFieldType(); + tableInfo.getFieldList().stream() + .filter(j -> j.getProperty().equals(fieldName) && fieldType.equals(j.getPropertyType())).findFirst() + .ifPresent(j -> { + conditionFill(insertFill, j); + strictFillStrategy(metaObject, fieldName, i.getFieldVal()); + }); + }); + return this; + } + return MetaObjectHandler.super.strictFill(insertFill, tableInfo, metaObject, strictFills); + } + + private SiriusProperties.AutoInterpolate findInterpolate(@NonNull String fieldName) { + List autoInterpolates = props.getAutoInterpolates(); + for (SiriusProperties.AutoInterpolate autoInterpolate : autoInterpolates) { + if (autoInterpolate.getFields().contains(fieldName)) { + return autoInterpolate; + } + } + return null; + } + + @Nullable + public Object findPsValue(@NonNull String fieldName) { + SiriusProperties.AutoInterpolate selectAutoInterpolate = findInterpolate(fieldName); + if (selectAutoInterpolate != null) { + Object psValue = CollectionsPropertySource.of(selectAutoInterpolate.getPsValue()); + if (!psValue.equals(selectAutoInterpolate.getPsValue())) { + return psValue; + } + try { + psValue = SpELPropertySource.parseElFun(selectAutoInterpolate.getPsValue()); + } catch (Exception ignore) { + psValue = selectAutoInterpolate.getDefaultValue(); + } + if (psValue != null) { + return psValue; + } + return selectAutoInterpolate.getPsValue(); + } + return null; + } + + public void conditionFill(boolean insertFill, @NonNull Object target) { + String propKey = "withUpdateFill"; + List fillFields = this.getUpdateFields(); + if (insertFill) { + propKey = "withInsertFill"; + fillFields = this.getInsertFields(); + } + if (target instanceof TableInfo) { + TableInfo tableInfo = (TableInfo) target; + for (TableFieldInfo fieldInfo: tableInfo.getFieldList()) { + if (fillFields.contains(fieldInfo.getProperty())) { + Map props = new HashMap<>(); + props.put(propKey, true); + ReflectUtil.setField(target, props); + return; + } + } + } else { + TableFieldInfo tableFieldInfo = (TableFieldInfo)target; + if (fillFields.contains(tableFieldInfo.getProperty())) { + Map props = new HashMap<>(); + props.put(propKey, true); + ReflectUtil.setField(target, props); + } + } + } + + public SiriusProperties getProps() { + return props; + } } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusMybatisParameterHandler.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusMybatisParameterHandler.java new file mode 100644 index 00000000..6f543fcb --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusMybatisParameterHandler.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import com.baomidou.mybatisplus.core.MybatisParameterHandler; +import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; +import com.github.yizzuide.milkomeda.util.ReflectUtil; +import org.apache.ibatis.executor.statement.PreparedStatementHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.util.StringUtils; + +import java.sql.PreparedStatement; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Auto interpolate extension of {@link MybatisParameterHandler}. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/02 19:03 + * @see PreparedStatementHandler + * @see MybatisParameterHandler#setParameters(PreparedStatement) + */ +public class SiriusMybatisParameterHandler extends MybatisParameterHandler { + + private SiriusConfig.MybatisPlusMetaObjectHandler metaObjectHandler; + + public SiriusMybatisParameterHandler(MappedStatement mappedStatement, Object parameter, BoundSql boundSql) { + super(mappedStatement, parameter, boundSql); + + if (!this.getMetaObjectHandler().getProps().isAutoAddFill() || + SqlCommandType.SELECT == mappedStatement.getSqlCommandType()) { + return; + } + + String ignoreLogicDelete = this.getMetaObjectHandler().getProps().getIgnoreLogicDelete(); + if ( (StringUtils.hasLength(ignoreLogicDelete) && + boundSql.getSql().contains(String.format("SET %s", ignoreLogicDelete)))) { + return; + } + + boolean findFlag = false; + Object entity = null; + if (parameter instanceof Map) { + Map map = (Map) parameter; + if (map.containsKey(Constants.ENTITY)) { + entity = map.get(Constants.ENTITY); + } + } else { + entity = parameter; + } + if (entity != null) { + TableInfo tableInfo = TableInfoHelper.getTableInfo(entity.getClass()); + if (tableInfo == null) { + return; + } + for (TableFieldInfo tableFieldInfo : tableInfo.getFieldList()) { + if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) { + if (this.getMetaObjectHandler().getInsertFields().contains(tableFieldInfo.getProperty())) { + findFlag = true; + break; + } + } else { + if (this.getMetaObjectHandler().getUpdateFields().contains(tableFieldInfo.getProperty())) { + findFlag = true; + break; + } + } + } + } + + if (!findFlag) { + return; + } + + // 根据值填充后的模型重新构建BoundSql -> PreparedStatementHandler.instantiateStatement() -> PreparedStatementHandler.parameterize() + BoundSql newBoundSql = mappedStatement.getBoundSql(getParameterObject()); + List parameterMappings = boundSql.getParameterMappings(); + parameterMappings.clear(); + parameterMappings.addAll(newBoundSql.getParameterMappings()); + Map props = new HashMap<>(); + props.put("sql", newBoundSql.getSql()); + ReflectUtil.setField(boundSql, props); + } + + @Override + protected void insertFill(MetaObject metaObject, TableInfo tableInfo) { + if (this.getMetaObjectHandler().getProps().isAutoAddFill() && !tableInfo.isWithInsertFill()) { + this.getMetaObjectHandler().conditionFill(true, tableInfo); + } + super.insertFill(metaObject, tableInfo); + } + + @Override + protected void updateFill(MetaObject metaObject, TableInfo tableInfo) { + if (this.getMetaObjectHandler().getProps().isAutoAddFill() && !tableInfo.isWithUpdateFill()) { + this.getMetaObjectHandler().conditionFill(false, tableInfo); + } + super.updateFill(metaObject, tableInfo); + } + + public SiriusConfig.MybatisPlusMetaObjectHandler getMetaObjectHandler() { + if (this.metaObjectHandler == null) { + this.metaObjectHandler = ApplicationContextHolder.get().getBean(SiriusConfig.MybatisPlusMetaObjectHandler.class); + } + return metaObjectHandler; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusMybatisXMLLanguageDriver.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusMybatisXMLLanguageDriver.java new file mode 100644 index 00000000..9f4b1bc0 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusMybatisXMLLanguageDriver.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; + +/** + * Custom extension of {@link MybatisXMLLanguageDriver}. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/02 19:01 + */ +public class SiriusMybatisXMLLanguageDriver extends MybatisXMLLanguageDriver { + @Override + public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { + return new SiriusMybatisParameterHandler(mappedStatement, parameterObject, boundSql); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusProperties.java index fe5f0268..248bac03 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusProperties.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SiriusProperties.java @@ -25,6 +25,7 @@ import com.baomidou.mybatisplus.annotation.FieldFill; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.convert.converter.GenericConverter; import java.util.ArrayList; import java.util.List; @@ -33,6 +34,7 @@ * Sirius module properties * * @since 3.14.0 + * @version 3.15.0 * @author yizzuide *
* Create at 2022/10/30 17:52 @@ -47,6 +49,18 @@ public class SiriusProperties { */ private DbType dbType = DbType.MYSQL; + /** + * Automatically identify entity fill attributes without adding `@TableField(fill = xxx)` annotations + * @since 3.15.0 + */ + private boolean autoAddFill = true; + + /** + * Recognize and ignore delete type operation if uses logic delete. + * @since 3.15.0 + */ + private String ignoreLogicDelete = "is_delete=1"; + /** * Auto value interpolation. */ @@ -55,16 +69,29 @@ public class SiriusProperties { @Data static class AutoInterpolate { /** - * What common field need interpolate to data table. + * What common field needs interpolate to data table? */ private List fields; + /** - * Property source value. + * Property source value which can be with Spring EL using `el(condition, type)`. */ private String psValue; + + /** + * Apply default value when parse Spring EL error. + * @since 3.15.0 + */ + private Object defaultValue = 0; + + /** + * convert property source value. + */ + private Class converterClazz; + /** - * Field fill type (current support insert and update). + * Field fill type. */ - private FieldFill fieldFill; + private FieldFill fieldFill = FieldFill.INSERT; } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SmartMillsConverter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SmartMillsConverter.java new file mode 100644 index 00000000..9dccb07f --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/SmartMillsConverter.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.lang.NonNull; + +import java.util.Collections; +import java.util.Set; + +/** + * Convert unix timestamp if target type is integer. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/06/07 11:56 + */ +public class SmartMillsConverter implements GenericConverter { + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(Long.class, Integer.class)); + } + + @Override + public Object convert(Object source, @NonNull TypeDescriptor sourceType, @NonNull TypeDescriptor targetType) { + if (sourceType.getType() != Long.class) { + return source; + } + Class typeType = targetType.getType(); + if (typeType == Long.class) { + return source; + } + if (typeType == Integer.class) { + return Long.valueOf((long) source / 1000).intValue(); + } + return null; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/UnixMillsConverter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/UnixMillsConverter.java new file mode 100644 index 00000000..62893810 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/UnixMillsConverter.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.lang.NonNull; + +/** + * Unix timestamp converter. + * + * @since 3.5.0 + * @author yizzuide + * Create at 2023/06/09 19:05 + */ +public class UnixMillsConverter extends SmartMillsConverter { + @Override + public Object convert(Object source, @NonNull TypeDescriptor sourceType, @NonNull TypeDescriptor targetType) { + if (sourceType.getType() != Long.class) { + return source; + } + Class typeType = targetType.getType(); + if (typeType == Long.class) { + return (long)source / 1000; + } + if (typeType == Integer.class) { + return Long.valueOf((long) source / 1000).intValue(); + } + return null; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/UpdateBatchByIdMethod.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/UpdateBatchByIdMethod.java new file mode 100644 index 00000000..cb5dbc97 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/UpdateBatchByIdMethod.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius; + +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlSource; + +/** + * Inject update batch with the primary key method in mapper. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/07/13 15:11 + */ +@Slf4j +public class UpdateBatchByIdMethod extends AbstractMethod { + + private static final long serialVersionUID = 1136438483463808038L; + + public UpdateBatchByIdMethod() { + super("updateBatchById"); + } + + @Override + public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { + String sql = ""; + String additional = tableInfo.isWithVersion() ? tableInfo.getVersionFieldInfo().getVersionOli("item", "item.") : tableInfo.getLogicDeleteSql(true, true); + String setSql = sqlSet(tableInfo.isWithLogicDelete(), false, tableInfo, true, "item", "item."); + String sqlResult = String.format(sql, tableInfo.getTableName(), setSql, tableInfo.getKeyColumn(), "item." + tableInfo.getKeyProperty(), additional); + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass); + return this.addUpdateMappedStatement(mapperClass, modelClass, this.methodName, sqlSource); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/wormhole/SiriusApplicationService.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/wormhole/SiriusApplicationService.java new file mode 100644 index 00000000..e60192d6 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/wormhole/SiriusApplicationService.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius.wormhole; + +import com.github.yizzuide.milkomeda.wormhole.ApplicationService; +import com.github.yizzuide.milkomeda.wormhole.TransactionWorkBus; + +/** + * Mybatis plus extend of application service which provide {@link TransactionWorkBus}. + * + * @param the repository type + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/07/14 03:48 + */ +public abstract class SiriusApplicationService implements ApplicationService { + + /** + * Link {@link TransactionWorkBus} belong this application service. + */ + private final TransactionWorkBus transactionWorkBus; + + public SiriusApplicationService() { + transactionWorkBus = new SiriusTransactionWorkBus(false); + transactionWorkBus.setApplicationService(this); + } + + public TransactionWorkBus getTransactionWorkBus() { + return transactionWorkBus; + } + + /** + * Change and enable batch insert with the primary key. + * @param enable true if batch insert with the primary key + */ + public void setUseBatchInsertWithKey(boolean enable) { + ((SiriusTransactionWorkBus) transactionWorkBus).setUseBatchInsertWithKey(enable); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/wormhole/SiriusInspector.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/wormhole/SiriusInspector.java new file mode 100644 index 00000000..aad42413 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/wormhole/SiriusInspector.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius.wormhole; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.toolkit.LambdaUtils; +import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import org.apache.ibatis.reflection.property.PropertyNamer; +import org.jetbrains.annotations.NotNull; +import org.mybatis.spring.mapper.MapperFactoryBean; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +/** + * An inspector for get mapper dynamically to mybatis and mybatis-plus. + * + * @see org.springframework.transaction.support.TransactionSynchronizationManager + * @see org.apache.ibatis.session.SqlSession + * @see org.apache.ibatis.session.SqlSessionManager + * @see com.baomidou.mybatisplus.core.MybatisMapperRegistry + * @see org.mybatis.spring.SqlSessionUtils + * @see com.baomidou.mybatisplus.extension.toolkit.SqlHelper + * @see com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/07/09 15:52 + */ +public class SiriusInspector implements SmartInstantiationAwareBeanPostProcessor { + + private final static Map> mapperInstanceTypes = new HashMap<>(); + + private final static Map mapperBeanNameTypes = new HashMap<>(); + + @Override + public Object postProcessBeforeInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException { + if (bean instanceof MapperFactoryBean) { + Class mapperInterface = ((MapperFactoryBean) bean).getMapperInterface(); + Type[] actualTypeArguments = ((ParameterizedType) (mapperInterface.getGenericInterfaces())[0]) + .getActualTypeArguments(); + String typeName = actualTypeArguments[0].getTypeName(); + mapperBeanNameTypes.put(beanName, typeName); + } + return SmartInstantiationAwareBeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); + } + + @Override + public Object postProcessAfterInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException { + if (bean instanceof BaseMapper) { + String typeName = mapperBeanNameTypes.get(beanName); + mapperInstanceTypes.put(typeName, (BaseMapper) bean); + } + return SmartInstantiationAwareBeanPostProcessor.super.postProcessAfterInitialization(bean, beanName); + } + + /** + * Get mapper from entity class. + * @param entityClass entity class + * @return mapper instance + * @param entity type + */ + @SuppressWarnings("unchecked") + public static BaseMapper getMapper(Class entityClass) { + return (BaseMapper) mapperInstanceTypes.get(entityClass.getName()); + } + + /** + * Get field name with method reference + * @param methodRef method reference + * @return field name + * @param Object type + */ + public static String getFieldName(SFunction methodRef) { + LambdaMeta meta = LambdaUtils.extract(methodRef); + return PropertyNamer.methodToProperty(meta.getImplMethodName()); + } + +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/wormhole/SiriusTransactionWorkBus.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/wormhole/SiriusTransactionWorkBus.java new file mode 100644 index 00000000..393739c0 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sirius/wormhole/SiriusTransactionWorkBus.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sirius.wormhole; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.github.yizzuide.milkomeda.sirius.BatchMapper; +import com.github.yizzuide.milkomeda.universe.exception.NotImplementException; +import com.github.yizzuide.milkomeda.wormhole.ApplicationService; +import com.github.yizzuide.milkomeda.wormhole.TransactionWorkBus; +import lombok.Setter; +import org.springframework.util.CollectionUtils; + +import java.io.Serializable; +import java.util.List; + +/** + * Mybatis plus impl of {@link TransactionWorkBus}. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/07/13 11:44 + */ +public class SiriusTransactionWorkBus implements TransactionWorkBus { + + /** + * Batch insert operation with primary key. + */ + @Setter + private boolean useBatchInsertWithKey; + + /** + * Link application service which belong to. + */ + @Setter + private ApplicationService applicationService; + + public SiriusTransactionWorkBus(boolean useBatchInsertWithKey) { + this.useBatchInsertWithKey = useBatchInsertWithKey; + } + + @SuppressWarnings("unchecked") + @Override + public > A getApplicationService() { + return (A) applicationService; + } + + @Override + public T selectById(Serializable id, Class entityClass) { + BaseMapper mapper = SiriusInspector.getMapper(entityClass); + return mapper.selectById(id); + } + + @SuppressWarnings("unchecked") + @Override + public List selectList(Object queryExample, Class entityClass) { + if (!(queryExample instanceof QueryWrapper)) { + throw new NotImplementException("The queryExample must impl with QueryWrapper for select operation."); + } + BaseMapper mapper = SiriusInspector.getMapper(entityClass); + return mapper.selectList((QueryWrapper) queryExample); + } + + @SuppressWarnings("unchecked") + @Override + public int perform(int operation, T entity) { + BaseMapper mapper = (BaseMapper) SiriusInspector.getMapper(entity.getClass()); + switch (operation) { + case TRANSACTION_OPERATION_SAVE: + return mapper.insert(entity); + case TRANSACTION_OPERATION_UPDATE: + return mapper.updateById(entity); + case TRANSACTION_OPERATION_DELETE: + return mapper.deleteById(entity); + } + return 0; + } + + @SuppressWarnings("unchecked") + @Override + public int performBatch(int operation, List entities) { + if (CollectionUtils.isEmpty(entities)) { + return 0; + } + BaseMapper mapper = (BaseMapper) SiriusInspector.getMapper(entities.get(0).getClass()); + if (!(mapper instanceof BatchMapper)) { + throw new NotImplementException("The mapper must impl with BatchMapper for batch operation."); + } + BatchMapper batchMapper = (BatchMapper) mapper; + switch (operation) { + case TRANSACTION_OPERATION_SAVE: + return useBatchInsertWithKey ? batchMapper.insertKeyBatch(entities) : batchMapper.insertBatch(entities); + case TRANSACTION_OPERATION_UPDATE: + return batchMapper.updateBatchById(entities); + case TRANSACTION_OPERATION_DELETE: + return batchMapper.deleteBatchIds(entities); + } + return 0; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/DataSourceAspect.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/DataSourceAspect.java deleted file mode 100644 index eef75258..00000000 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/DataSourceAspect.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2021 yizzuide All rights Reserved. - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.github.yizzuide.milkomeda.sundial; - -import com.github.yizzuide.milkomeda.util.ReflectUtil; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.springframework.core.annotation.Order; -import com.github.yizzuide.milkomeda.util.Strings; - -/** - * Sundial注解切面 - * @author jsq 786063250@qq.com - * @since 3.4.0 - * @version 3.8.0 - *
- * Create at 2020/5/8 - */ -@Slf4j -@Order(999) -@Aspect -public class DataSourceAspect { - - @Pointcut("@within(com.github.yizzuide.milkomeda.sundial.Sundial) && execution(public * *(..))") - public void classPointCut() { - } - - - @Pointcut("@annotation(com.github.yizzuide.milkomeda.sundial.Sundial) && execution(public * *(..))") - public void actionPointCut() { - } - - @Around("actionPointCut() || classPointCut()") - public Object around(ProceedingJoinPoint joinPoint) throws Throwable { - Sundial sundial = ReflectUtil.getAnnotation(joinPoint, Sundial.class); - if (Strings.isEmpty(sundial.value())) { - return joinPoint.proceed(); - } - SundialHolder.setDataSourceType(sundial.value()); - try { - return joinPoint.proceed(); - } finally { - SundialHolder.clearDataSourceType(); - } - } -} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/ShardingFunction.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/ShardingFunction.java index f1da51a6..c14aa1ba 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/ShardingFunction.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/ShardingFunction.java @@ -59,17 +59,18 @@ public String format(String format, Object... args) { /** * 取模函数 - * @param key 拆分键 + * @param key 路由Key * @param count 节点个数 * @return 取模结果 */ public long mod(long key, long count) { - return key % count; + // hash散列矫正 + return (count - 1) & (Long.hashCode(key) ^ Long.hashCode(key) >>> 16); } /** - * 取ShardingId生成的拆分号 - * @param key 拆分键 + * 取ShardingId生成的拆分号(分表Index) + * @param key 路由Key * @return 拆分号 */ public long id(long key) { @@ -78,7 +79,7 @@ public long id(long key) { /** * 自定义序列号抽取函数 - * @param key 拆分键 + * @param key 路由Key * @param bitStart 二进制开始位(从0开始,且从右边开始计数) * @param bitCount 二进制长度(从右边向左取位数) * @return 取值结果 @@ -90,7 +91,7 @@ public long seq(long key, long bitStart, long bitCount) { /** * FNV132算法一致性哈希函数实现(与Murmur相当的性能和不变流量) - * @param key 拆分键 + * @param key 路由Key * @param nodeCount 节点数 * @param replicas 每个节点复制的虚拟节点数,推荐设置4的倍数 * @return 目标节点 @@ -101,7 +102,7 @@ public long fnv(String key, long nodeCount, int replicas) { /** * Murmur算法一致性哈希实现(高性能,高不变流量) - * @param key 拆分键 + * @param key 路由Key * @param nodeCount 节点数 * @param replicas 每个节点复制的虚拟节点数,推荐设置4的倍数 * @return 目标节点 @@ -112,7 +113,7 @@ public long murmur(String key, long nodeCount, int replicas) { /** * Ketama算法一致性哈希实现(与Murmur相当的高不变流量,高负载平衡性,推荐使用) - * @param key 拆分键 + * @param key 路由Key * @param nodeCount 节点数 * @param replicas 每个节点复制的虚拟节点数,推荐设置4的倍数 * @return 目标节点 @@ -124,7 +125,7 @@ public long ketama(String key, long nodeCount, int replicas) { /** * 自定义算法一致性哈希实现 * @param hashName 哈希算法名 - * @param key 拆分键 + * @param key 路由Key * @param nodeCount 节点数 * @param replicas 每个节点复制的虚拟节点数,推荐设置4的倍数 * @return 目标节点 @@ -136,7 +137,7 @@ public long hash(String hashName, String key, long nodeCount, int replicas) { /** * 时间窗口滑动(可实现按创建日期拆分) - * @param key 时间拆分键 + * @param key 时间路由Key * @param startDate 开始时间(格式:yyyy-MM-dd) * @param daySlideWindow 滑动窗口天数(一个滑动窗口分一次) * @param expandWarnPercent 当前分表扩展警告占百分比,如:0.75 @@ -159,7 +160,7 @@ public long rollDate(Date key, String startDate, long daySlideWindow, double exp /** * 通用窗口滑动(可实现增长类型主键拆分) - * @param key 拆分键 + * @param key 路由Key * @param slideWindow 滑动窗口大小 * @param expandWarnPercent 当前分表扩展警告占百分比,如:0.75 * @return 拆分号 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/ShardingId.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/ShardingId.java index 9aaa882b..0b560ee5 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/ShardingId.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/ShardingId.java @@ -59,9 +59,9 @@ public class ShardingId { /** * 获取序列号 - * @param workerId 机器id,当前业务布署的机器序号,范围[0, 8) - * @param businessId 业务id,范围[0, 128) - * @param sharding 拆分序号,范围[0, 16) + * @param workerId 机器id,当前业务布署的机器序号,范围:[0, 8) + * @param businessId 业务id,范围:[0, 128) + * @param sharding 拆分序号,范围:[0, 16) * @return 唯一序列号 */ public static long nextId(long workerId, long businessId, long sharding) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/Sundial.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/Sundial.java index 4971c84a..ad496a9f 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/Sundial.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/Sundial.java @@ -39,14 +39,18 @@ @Inherited public @interface Sundial { /** - * 选择数据源Key + * 选择数据源Key,使用时分以下两种情况: + *

+ * 1. 仅使用主从策略时,需要配置sundial.strategy,再通过该属性设置RouteKey。
+ * 2. 如果是分库分表+主从策略时,需要配置sundial.sharding.nodes,再通过该属性设置RouteKey。 + *

* @return String */ @AliasFor("key") String value() default DynamicRouteDataSource.MASTER_KEY; /** - * 监选择数据源Key + * 等同value的值 * @return String */ @AliasFor("value") diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialConfig.java index 4d6ea823..69e86b8b 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialConfig.java @@ -60,11 +60,6 @@ public class SundialConfig { @Autowired private SundialProperties props; - @Bean - public DataSourceAspect dataSourceAspect(){ - return new DataSourceAspect(); - } - @Bean public DataSourceFactory dataSourceFactory(){ return new DataSourceFactory(); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialHolder.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialHolder.java index 2dd07ef3..d5f36775 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialHolder.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialHolder.java @@ -21,6 +21,7 @@ package com.github.yizzuide.milkomeda.sundial; +import io.netty.util.concurrent.FastThreadLocal; import lombok.extern.slf4j.Slf4j; /** @@ -34,11 +35,7 @@ @Slf4j public class SundialHolder { - /** - * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, - * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 - */ - private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + private static final FastThreadLocal CONTEXT_HOLDER = new FastThreadLocal<>(); /** * 设置数据源key diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialInterceptor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialInterceptor.java index 3e0aefed..f7c7ce27 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialInterceptor.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialInterceptor.java @@ -53,6 +53,7 @@ *
* Create at 2020/06/16 11:18 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @@ -120,7 +121,7 @@ public Object intercept(Invocation invocation) throws Throwable { // 是否使用主连接 if (sundial.key().equals(DynamicRouteDataSource.MASTER_KEY)) { SundialHolder.setDataSourceType(dataNode.getLeader()); - } else if(sundial.key().equals("follows")) { // 从库任选 + } else if("follows".equals(sundial.key())) { // 从库任选 if (!CollectionUtils.isEmpty(dataNode.getFollows())) { SundialHolder.setDataSourceType(dataNode.getFollows().stream().findAny().orElse(DynamicRouteDataSource.MASTER_KEY)); } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialTweakConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialTweakConfig.java index b13050a8..31a8c018 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialTweakConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/SundialTweakConfig.java @@ -38,6 +38,7 @@ *
* Create at 2020/05/31 14:57 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @ConditionalOnClass(MybatisAutoConfiguration.class) @AutoConfigureAfter(MybatisAutoConfiguration.class) public class SundialTweakConfig { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/OrbitDataSourceAdvice.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AbstractDataSourceOrbitAdvice.java similarity index 78% rename from Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/OrbitDataSourceAdvice.java rename to Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AbstractDataSourceOrbitAdvice.java index f78e107f..9f54711a 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/OrbitDataSourceAdvice.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AbstractDataSourceOrbitAdvice.java @@ -24,36 +24,35 @@ import com.github.yizzuide.milkomeda.orbit.OrbitAdvice; import com.github.yizzuide.milkomeda.orbit.OrbitInvocation; -import com.github.yizzuide.milkomeda.sundial.DynamicRouteDataSource; import com.github.yizzuide.milkomeda.sundial.SundialHolder; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; /** - * Dynamic route advice impl of {@link OrbitAdvice} + * Abstract Dynamic route advice impl of {@link OrbitAdvice}. * - * @author yizzuide * @since 3.4.0 - * @version 3.13.0 + * @version 3.15.0 + * @author yizzuide *
* Create at 2020/05/11 16:29 */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class OrbitDataSourceAdvice implements OrbitAdvice { - - private String keyName = DynamicRouteDataSource.MASTER_KEY; +public abstract class AbstractDataSourceOrbitAdvice implements OrbitAdvice { @Override public Object invoke(OrbitInvocation invocation) throws Throwable { try { // 调用方法前,选择数据源 - SundialHolder.setDataSourceType(getKeyName()); + SundialHolder.setDataSourceType(getRouteKey(invocation)); return invocation.proceed(); } finally { SundialHolder.clearDataSourceType(); } } + + /** + * The subclass should implement this method and return route key. + * @param invocation Orbit invocation + * @return Route key + * @since 3.15.0 + */ + protected abstract String getRouteKey(OrbitInvocation invocation); } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AnnotationDataSourceOrbitAdvice.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AnnotationDataSourceOrbitAdvice.java new file mode 100644 index 00000000..a940a747 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AnnotationDataSourceOrbitAdvice.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sundial.orbit; + +import com.github.yizzuide.milkomeda.orbit.OrbitAdvice; +import com.github.yizzuide.milkomeda.orbit.OrbitInvocation; +import com.github.yizzuide.milkomeda.sundial.DynamicRouteDataSource; +import com.github.yizzuide.milkomeda.sundial.Sundial; +import org.springframework.core.annotation.AnnotationUtils; + +/** + * Dynamic route advice impl of {@link OrbitAdvice} with Annotation. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/04/15 03:07 + */ +public class AnnotationDataSourceOrbitAdvice extends AbstractDataSourceOrbitAdvice { + @Override + protected String getRouteKey(OrbitInvocation invocation) { + Sundial sundial = AnnotationUtils.findAnnotation(invocation.getMethod(), Sundial.class); + if (sundial == null) { + return DynamicRouteDataSource.MASTER_KEY; + } + return sundial.key(); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AnnotationOrbitSource.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AnnotationOrbitSource.java new file mode 100644 index 00000000..c95522a3 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AnnotationOrbitSource.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sundial.orbit; + +import com.github.yizzuide.milkomeda.orbit.AnnotationOrbitAdvisor; +import com.github.yizzuide.milkomeda.orbit.OrbitAdvisor; +import com.github.yizzuide.milkomeda.orbit.OrbitSource; +import com.github.yizzuide.milkomeda.orbit.OrbitSourceProvider; +import com.github.yizzuide.milkomeda.sundial.Sundial; +import com.github.yizzuide.milkomeda.sundial.SundialProperties; +import org.springframework.boot.context.properties.bind.BindResult; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.core.env.Environment; + +import java.util.Collections; +import java.util.List; + +/** + * Register Orbit node from yml config with annotation. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/04/15 02:45 + */ +@OrbitSourceProvider +public class AnnotationOrbitSource implements OrbitSource { + @Override + public List createAdvisors(Environment environment) { + try { + // 根据配置文件是否加载来判断当前模块是否加载 + BindResult bindResult = Binder.get(environment).bind(SundialProperties.PREFIX, SundialProperties.class); + if (bindResult == null) { + return null; + } + } catch (Exception ignore) { + return null; + } + AnnotationOrbitAdvisor annotationOrbitAdvisor = new AnnotationOrbitAdvisor(Sundial.class, Sundial.class, "sundial", AnnotationDataSourceOrbitAdvice.class, null); + return Collections.singletonList(annotationOrbitAdvisor); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AspectJDataSourceOrbitAdvice.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AspectJDataSourceOrbitAdvice.java new file mode 100644 index 00000000..6a03db42 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AspectJDataSourceOrbitAdvice.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.sundial.orbit; + +import com.github.yizzuide.milkomeda.orbit.OrbitAdvice; +import com.github.yizzuide.milkomeda.orbit.OrbitInvocation; +import com.github.yizzuide.milkomeda.sundial.DynamicRouteDataSource; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Dynamic route advice impl of {@link OrbitAdvice} with AspectJ. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/04/15 03:00 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class AspectJDataSourceOrbitAdvice extends AbstractDataSourceOrbitAdvice { + /** + * Route key. + */ + private String keyName = DynamicRouteDataSource.MASTER_KEY; + + @Override + protected String getRouteKey(OrbitInvocation invocation) { + return keyName; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/OrbitAdditionSource.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AspectJOrbitSource.java similarity index 75% rename from Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/OrbitAdditionSource.java rename to Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AspectJOrbitSource.java index f994bdee..8e8759be 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/OrbitAdditionSource.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/sundial/orbit/AspectJOrbitSource.java @@ -21,10 +21,9 @@ package com.github.yizzuide.milkomeda.sundial.orbit; -import com.github.yizzuide.milkomeda.orbit.OrbitNode; -import com.github.yizzuide.milkomeda.orbit.OrbitSource; -import com.github.yizzuide.milkomeda.orbit.OrbitSourceProvider; +import com.github.yizzuide.milkomeda.orbit.*; import com.github.yizzuide.milkomeda.sundial.SundialProperties; +import com.github.yizzuide.milkomeda.util.CollectionsKt; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.core.env.Environment; import org.springframework.util.CollectionUtils; @@ -34,37 +33,35 @@ import java.util.stream.Collectors; /** - * Orbit config extension of {@link OrbitSource} + * Register Orbit node from yml config with aspectJ. * * @author yizzuide * @since 3.13.0 + * @version 3.15.0 *
* Create at 2022/02/23 02:11 */ // 虽然@OrbitSourceProvider有添加`@Component`注解,但由于默认的用户业务应用并不扫描这个路径,所以不会被Spring IoC所识别。 // 但是Orbit模块在BeanDefinition阶段注册了回调,通过手动依赖查找扫描的方式找到*.orbit路径下的这个注解。 @OrbitSourceProvider -public class OrbitAdditionSource implements OrbitSource { +public class AspectJOrbitSource implements OrbitSource { @Override - public List createNodes(Environment environment) { + public List createAdvisors(Environment environment) { SundialProperties sundialProperties; try { sundialProperties = Binder.get(environment).bind(SundialProperties.PREFIX, SundialProperties.class).get(); } catch (Exception ignore) { - // 获取当前模块没有配置,直接返回 + // not config, back! return Collections.emptyList(); } if (CollectionUtils.isEmpty(sundialProperties.getStrategy())) { return Collections.emptyList(); } - // 构建配置源 + // convert strategy config to orbit node return sundialProperties.getStrategy().stream() - .map(strategy -> OrbitNode.builder() - .id(strategy.getKeyName()) - .pointcutExpression(strategy.getPointcutExpression()) - .adviceClass(OrbitDataSourceAdvice.class) - .build().putPropKV(SundialProperties.Strategy.KEY_NAME, strategy.getKeyName())) + .map(strategy -> new AspectJOrbitAdvisor(strategy.getPointcutExpression(), strategy.getKeyName(), + AspectJDataSourceOrbitAdvice.class, CollectionsKt.singletonMap(SundialProperties.Strategy.KEY_NAME, strategy.getKeyName()))) .collect(Collectors.toList()); } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/algorithm/hash/BloomHashWrapper.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/algorithm/hash/BloomHashWrapper.java index f6ad8f70..c2ba52a4 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/algorithm/hash/BloomHashWrapper.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/algorithm/hash/BloomHashWrapper.java @@ -22,12 +22,11 @@ package com.github.yizzuide.milkomeda.universe.algorithm.hash; /** - * BloomHashWrapper - * 布隆过滤器特点: + * 布隆过滤器,有以下特点: * 1. 如果该元素被判断不存在,那么一定不存在(该特性可用于防缓存穿透(击穿)问题) * 2. 如果该元素被判断存在,那么很可能存在 - * @author yizzuide * @since 3.9.0 + * @author yizzuide *
* Create at 2020/06/23 15:17 */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/AbstractArgumentMatcher.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/AbstractArgumentMatcher.java new file mode 100644 index 00000000..85b03e0e --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/AbstractArgumentMatcher.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.aop.invoke.args; + +import org.springframework.aop.support.AopUtils; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; + +import java.lang.reflect.Method; + +/** + * An abstract argument matcher that has ability to discover param name. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/12 02:21 + */ +public abstract class AbstractArgumentMatcher implements ArgumentMatcher { + + private final LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); + + @Override + public int matchIndex(Method method, ArgumentDefinition argumentDefinition) { + // get target method which warped proxy + method = AopUtils.getMostSpecificMethod(method, method.getDeclaringClass()); + String[] parameterNames = discoverer.getParameterNames(method); + Class[] parameterTypes = method.getParameterTypes(); + if (parameterNames == null || parameterNames.length == 0) { + return -1; + } + return doMatchIndex(parameterNames, parameterTypes, argumentDefinition); + } + + /** + * Extension hook that subclasses can override to match index at position of parameters. + * @param parameterNames parameter name list of method which invoked + * @param parameterTypes parameter type of parameter name list + * @param argumentDefinition argument meta data + * @return position of parameters + */ + protected abstract int doMatchIndex(String[] parameterNames, Class[] parameterTypes, ArgumentDefinition argumentDefinition); + + @Override + public void matchToAdd(Object[] args, Method method, ArgumentDefinition argumentDefinition) { + int index = matchIndex(method, argumentDefinition); + if (index > -1) { + args[index] = argumentDefinition.getValue(); + } + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentDefinition.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentDefinition.java new file mode 100644 index 00000000..3ee7d494 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentDefinition.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.aop.invoke.args; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.lang.ref.WeakReference; + +/** + * Argument metadata used with {@link ArgumentSources}. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/11 19:42 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class ArgumentDefinition { + /** + * The match type is used to match the processor. + */ + private ArgumentMatchType matchType; + + /** + * Custom type is used for extent argument matcher. + */ + private String customTypeName; + + /** + * Argument identifier (such as param name etc.). + */ + private Object identifier; + + /** + * Class type of argument value. + */ + private WeakReference> argClassRef; + + /** + * Argument value which need invoke. + */ + private Object value; + + public ArgumentDefinition(ArgumentMatchType matchType, Object identifier, Class argClass, Object value) { + this.matchType = matchType; + this.identifier = identifier; + this.argClassRef = new WeakReference<>(argClass); + this.value = value; + } + + public ArgumentDefinition(String customTypeName, Object identifier, Class argClass, Object value) { + this.customTypeName = customTypeName; + this.identifier = identifier; + this.argClassRef = new WeakReference<>(argClass); + this.value = value; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentMatchType.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentMatchType.java new file mode 100644 index 00000000..146a94d9 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentMatchType.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.aop.invoke.args; + +/** + * A strategy match type for find what impl of {@link ArgumentMatcher}. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/11 19:35 + */ +public enum ArgumentMatchType { + /** + * Match with name using {@link String#contains(CharSequence)}. + */ + BY_NAME_CONTAINS, + + /** + * Match with name using {@link String#startsWith(String)}. + */ + BY_NAME_PREFIX, + + /** + * Match with name using {@link String#endsWith(String)}. + */ + BY_NAME_POSTFIX, + + /** + * Match by type using {@link com.github.yizzuide.milkomeda.universe.aop.invoke.args.ArgumentDefinition}. + */ + BY_TYPE, + + /** + * Match with name using regex of JDK API. + */ + REGEX, + + /** + * match last with imp of {@link ResidualArgumentMatcher}. + */ + Residual, +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentMatcher.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentMatcher.java new file mode 100644 index 00000000..c28aef6a --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentMatcher.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.aop.invoke.args; + +import java.lang.reflect.Method; + +/** + * Base interface for indicate what can support and calculate matched index. The ArgumentMatcher interface allows + * support for different types of ArgumentDefinition. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/11 19:24 + */ +public interface ArgumentMatcher { + + /** + * Indicate what matcher support. + * @param argumentDefinition ArgumentDefinition + * @return true if matched success + */ + boolean support(ArgumentDefinition argumentDefinition); + + /** + * Find the index position of parameters according to method and argument definition. + * @param method invoked method + * @param argumentDefinition ArgumentDefinition + * @return -1 if not find + */ + int matchIndex(Method method, ArgumentDefinition argumentDefinition); + + /** + * Find the index position of parameters and add it to args array. + * @param args argument array + * @param method invoked method + * @param argumentDefinition ArgumentDefinition + */ + void matchToAdd(Object[] args, Method method, ArgumentDefinition argumentDefinition); +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentSources.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentSources.java new file mode 100644 index 00000000..32cc5b16 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ArgumentSources.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.aop.invoke.args; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A argument definition list to ensure correct order. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/11 19:52 + */ +public class ArgumentSources { + + private final List argsDefinitionSource; + + public ArgumentSources() { + argsDefinitionSource = new ArrayList<>(); + } + + public ArgumentSources(ArgumentDefinition ...argumentDefinition) { + this(); + argsDefinitionSource.addAll(Arrays.asList(argumentDefinition)); + } + + public ArgumentSources add(ArgumentDefinition argumentDefinition) { + argsDefinitionSource.add(argumentDefinition); + return this; + } + + public List getList() { + return argsDefinitionSource; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/CompositeArgumentMatcher.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/CompositeArgumentMatcher.java new file mode 100644 index 00000000..06f71b65 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/CompositeArgumentMatcher.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.aop.invoke.args; + +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +/** + * Collecting argument matcher and handle batch. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/11 19:25 + */ +public class CompositeArgumentMatcher implements ArgumentMatcher { + + private final Set argumentMatchers = new HashSet<>(); + + public void addMatcher(ArgumentMatcher matcher) { + argumentMatchers.add(matcher); + } + + @Override + public boolean support(ArgumentDefinition argumentDefinition) { + return argumentMatchers.stream().anyMatch(a -> a.support(argumentDefinition)); + } + + @Override + public int matchIndex(Method method, ArgumentDefinition argumentDefinition) { + Optional selectedArgumentMatcher = argumentMatchers.stream().filter(a -> a.support(argumentDefinition)).findFirst(); + return selectedArgumentMatcher.map(matcher -> matcher.matchIndex(method, argumentDefinition)).orElse(-1); + } + + @Override + public void matchToAdd(Object[] args, Method method, ArgumentDefinition argumentDefinition) { + argumentMatchers.stream().filter(a -> a.support(argumentDefinition)) + .findFirst().ifPresent(a -> a.matchToAdd(args, method, argumentDefinition)); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/JDKRegexArgumentMatcher.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/JDKRegexArgumentMatcher.java new file mode 100644 index 00000000..0aa4fb66 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/JDKRegexArgumentMatcher.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.aop.invoke.args; + +import com.github.yizzuide.milkomeda.util.Strings; + +import java.util.regex.Pattern; + +/** + * Regex match impl of argument matcher which using from JDK API. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/11 19:29 + */ +public class JDKRegexArgumentMatcher extends AbstractArgumentMatcher { + @Override + public boolean support(ArgumentDefinition argumentDefinition) { + return ArgumentMatchType.REGEX.equals(argumentDefinition.getMatchType()) && + (argumentDefinition.getIdentifier() != null && !Strings.isEmpty(argumentDefinition.getIdentifier().toString())); + } + + @Override + protected int doMatchIndex(String[] parameterNames, Class[] parameterTypes, ArgumentDefinition argumentDefinition) { + for (int i = 0; i < parameterNames.length; i++) { + if (Pattern.matches(argumentDefinition.getIdentifier().toString(), parameterNames[i])) { + return i; + } + } + return -1; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/MethodArgumentBinder.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/MethodArgumentBinder.java new file mode 100644 index 00000000..c401060d --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/MethodArgumentBinder.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.aop.invoke.args; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * A method-based argument binder for reflection invoke which auto bind args value at corresponding index with {@link ArgumentDefinition} config. + * + * @see org.springframework.aop.aspectj.AbstractAspectJAdvice#calculateArgumentBindings() + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/11 19:21 + */ +public class MethodArgumentBinder { + + // volatile变量规则:对一个volatile变量的写,happens-before于任意后续对这个volatile变量的读。 + // happens-before:如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见。 + // 实现原理:为实现volatile内存语义,JMM会限制编译器重排序和处理器重排序(编译器在生成字节码时,会在指令序列中插入内存屏障来禁止处理器重排序)。 + // 读写操作:当操作volatile变量写时,JMM把该线程的本地内存(CPU缓存、寄存器)中的所有共享变量刷新到主内存;当读的时候,volatile变量强制从主内存中读取。 + // 应用技巧:释放锁的线程在写volatile变量之前对共享变量进行的修改,在别的线程读取同一个volatile变量后将立即可见。 + private static volatile CompositeArgumentMatcher argumentMatchers; + + /** + * Get bind args from argument definition list. + * @param argumentSources argument definition list + * @param method target method + * @return args + */ + public static Object[] bind(ArgumentSources argumentSources, Method method) { + CompositeArgumentMatcher argumentMatchers = getArgumentMatchers(); + List argumentDefinitions = argumentSources.getList(); + int size = method.getParameters().length; + Object[] args = new Object[size]; + for (ArgumentDefinition argumentDefinition : argumentDefinitions) { + if (argumentMatchers.support(argumentDefinition)) { + argumentMatchers.matchToAdd(args, method, argumentDefinition); + } + } + return args; + } + + /** + * Register argument matcher to match special argument definition. + * @param argumentMatcher ArgumentMatcher + */ + public static void register(ArgumentMatcher argumentMatcher) { + getArgumentMatchers().addMatcher(argumentMatcher); + } + + private static CompositeArgumentMatcher getArgumentMatchers() { + if (argumentMatchers == null) { + // 每个Object都关联一个Monitor(存在于对象头Mark Word),它记录被哪个线程持有、重入次数、block队列、wait队列等,同时notify/notifyAll/wait等方法会使用到Monitor锁对象,所以必须在同步代码块中使用。 + // Synchronized的同步是可重入、非公平抢占方式,在JVM里的实现都是基于MonitorEnter和MonitorExit指令进入退出Monitor对象来实现方法同步和代码块同步。 + synchronized (MethodArgumentBinder.class) { + if (argumentMatchers == null) { + // new操作包含了三条指令: + // 1.分配对象内存M;2.在内存M上初始化对象;3.将M的地址赋值给变量。 + // 如果指令优化重排(1->3->2),可能导致其它线程赋使用变量时是一个未初始化的对象而触发空指针异常。 + argumentMatchers = new CompositeArgumentMatcher(); + argumentMatchers.addMatcher(new NamedTypeArgumentMatcher()); + argumentMatchers.addMatcher(new JDKRegexArgumentMatcher()); + argumentMatchers.addMatcher(new ResidualArgumentMatcher()); + } + } + } + return argumentMatchers; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/NamedTypeArgumentMatcher.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/NamedTypeArgumentMatcher.java new file mode 100644 index 00000000..b70bab42 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/NamedTypeArgumentMatcher.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.aop.invoke.args; + +import com.github.yizzuide.milkomeda.util.Strings; + +/** + * Common using impl of Argument matcher has recognized name and type with {@link ArgumentDefinition}. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/11 19:29 + */ +public class NamedTypeArgumentMatcher extends AbstractArgumentMatcher { + @Override + public boolean support(ArgumentDefinition argumentDefinition) { + ArgumentMatchType type = argumentDefinition.getMatchType(); + if (type == ArgumentMatchType.BY_TYPE) { + return argumentDefinition.getArgClassRef() != null; + } + if (type == ArgumentMatchType.BY_NAME_CONTAINS || + type == ArgumentMatchType.BY_NAME_PREFIX || + type == ArgumentMatchType.BY_NAME_POSTFIX) { + return argumentDefinition.getIdentifier() != null && !Strings.isEmpty(argumentDefinition.getIdentifier().toString()); + } + return false; + } + + @Override + public int doMatchIndex(String[] parameterNames, Class[] parameterTypes, ArgumentDefinition argumentDefinition) { + // by type + if (argumentDefinition.getMatchType() == ArgumentMatchType.BY_TYPE) { + for (int i = 0; i < parameterTypes.length; i++) { + Class argClass = argumentDefinition.getArgClassRef().get(); + if (argClass != null && argClass.equals(parameterTypes[i])) { + return i; + } + } + return -1; + } + // by name + for (int i = 0; i < parameterNames.length; i++) { + String name = parameterNames[i]; + switch (argumentDefinition.getMatchType()) { + case BY_NAME_PREFIX: { + if (name.startsWith(String.valueOf(argumentDefinition.getIdentifier()))) { + return i; + } + break; + } + case BY_NAME_POSTFIX: { + if (name.endsWith(String.valueOf(argumentDefinition.getIdentifier()))) { + return i; + } + break; + } + case BY_NAME_CONTAINS: { + if (name.contains(String.valueOf(argumentDefinition.getIdentifier()))) { + return i; + } + break; + } + } + } + return -1; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ResidualArgumentMatcher.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ResidualArgumentMatcher.java new file mode 100644 index 00000000..5d5e93df --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/aop/invoke/args/ResidualArgumentMatcher.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.aop.invoke.args; + +import java.lang.reflect.Method; + +/** + * A flag impl to tall argument binder full this last one. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/12 02:54 + */ +public class ResidualArgumentMatcher implements ArgumentMatcher { + + /** + * index is flag that indicate last one to add. + */ + static final Integer RESIDUAL_PLACEHOLDER_INDEX = -2; + + @Override + public boolean support(ArgumentDefinition argumentDefinition) { + return ArgumentMatchType.Residual.equals(argumentDefinition.getMatchType()); + } + + @Override + public int matchIndex(Method method, ArgumentDefinition argumentDefinition) { + return RESIDUAL_PLACEHOLDER_INDEX; + } + + @Override + public void matchToAdd(Object[] args, Method method, ArgumentDefinition argumentDefinition) { + for (int i = 0; i < args.length; i++) { + if (args[i] == null) { + args[i] = argumentDefinition.getValue(); + break; + } + } + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaAutoConfiguration.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaAutoConfiguration.java index 8237e6cd..659a5578 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaAutoConfiguration.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaAutoConfiguration.java @@ -21,9 +21,9 @@ package com.github.yizzuide.milkomeda.universe.config; -import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import; /** @@ -31,15 +31,17 @@ * * @author yizzuide * @since 0.2.1 - * @version 2.0.0 + * @version 3.15.0 * @see org.springframework.core.io.support.SpringFactoriesLoader * @see org.springframework.boot.SpringApplication *
* Create at 2019/04/12 11:29 */ -@Slf4j -@Configuration +// Springboot 2.7: @AutoConfiguration should be used to annotate top-level autoconfiguration classes, +// Configuration classes that are nested within or imported by an @AutoConfiguration class should continue to use @Configuration as before. +@AutoConfiguration @Import(MilkomedaContextConfig.class) @EnableConfigurationProperties(MilkomedaProperties.class) +@EnableAspectJAutoProxy(exposeProxy = true) public class MilkomedaAutoConfiguration { } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaContextConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaContextConfig.java index e6874e74..db6926e7 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaContextConfig.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaContextConfig.java @@ -21,29 +21,20 @@ package com.github.yizzuide.milkomeda.universe.config; -import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; import com.github.yizzuide.milkomeda.universe.context.WebContext; import com.github.yizzuide.milkomeda.universe.extend.env.Environment; import com.github.yizzuide.milkomeda.universe.extend.web.handler.DelegatingContextFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.filter.OrderedFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.util.PathMatcher; import org.springframework.web.filter.DelegatingFilterProxy; -import org.springframework.web.util.UrlPathHelper; -import org.springframework.web.util.pattern.PathPatternParser; -import java.io.Serializable; import java.util.Collections; /** @@ -51,13 +42,12 @@ * * @author yizzuide * @since 2.0.0 - * @version 3.14.0 + * @version 3.15.0 *
* Create at 2019/12/13 19:09 */ -@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") -@Configuration @AutoConfigureAfter(WebMvcAutoConfiguration.class) +@Configuration public class MilkomedaContextConfig { @Autowired @@ -71,19 +61,9 @@ public Environment env() { return environment; } - @Bean - @ConditionalOnMissingBean - public ApplicationContextHolder applicationContextHolder(Environment env) { - ApplicationContextHolder applicationContextHolder = new ApplicationContextHolder(); - ApplicationContextHolder.setEnvironment(env); - return applicationContextHolder; - } - @Autowired - public void config(PathMatcher mvcPathMatcher, PathPatternParser mvcPatternParser, UrlPathHelper mvcUrlPathHelper) { + public void config(PathMatcher mvcPathMatcher) { WebContext.setMvcPathMatcher(mvcPathMatcher); - WebContext.setMvcPatternParser(mvcPatternParser); - WebContext.setUrlPathHelper(mvcUrlPathHelper); } @Bean @@ -105,14 +85,4 @@ public FilterRegistrationBean delegatingFilterRegistrationBean() { delegatingFilterRegistrationBean.setOrder(order); return delegatingFilterRegistrationBean; } - - @Bean - @ConditionalOnClass(name = "org.springframework.data.redis.core.RedisTemplate") - public RedisTemplate jsonRedisTemplate(LettuceConnectionFactory redisConnectionFactory) { - RedisTemplate template = new RedisTemplate<>(); - template.setKeySerializer(new StringRedisSerializer()); - template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); - template.setConnectionFactory(redisConnectionFactory); - return template; - } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaContextInitializer.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaContextInitializer.java new file mode 100644 index 00000000..8a8f54c1 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaContextInitializer.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.config; + +import com.github.yizzuide.milkomeda.universe.extend.processor.EarlyLoadBeanDefinitionRegistryPostProcessor; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.lang.NonNull; + +import java.util.Collection; + +/** + * Custom add config spring context initializer. + * + * @see org.springframework.boot.SpringApplication#setInitializers(Collection) + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/04/27 20:50 + */ +// Spring Boot在调用refresh()之前加载上下文初始化器,这时候的Bean Class还没被类加载器加载。 +public class MilkomedaContextInitializer implements ApplicationContextInitializer { + @Override + public void initialize(@NonNull ConfigurableApplicationContext applicationContext) { + applicationContext.addBeanFactoryPostProcessor(new EarlyLoadBeanDefinitionRegistryPostProcessor()); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaProperties.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaProperties.java index e8746668..25d2a079 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaProperties.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/MilkomedaProperties.java @@ -23,7 +23,9 @@ import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.convert.converter.GenericConverter; +import java.util.List; import java.util.Map; /** @@ -35,8 +37,11 @@ * Create at 2019/09/27 18:36 */ @Data -@ConfigurationProperties("milkomeda") +@ConfigurationProperties(MilkomedaProperties.PREFIX) public class MilkomedaProperties { + + public static final String PREFIX = "milkomeda"; + /** * 是否显示日志,默认为false */ @@ -46,4 +51,16 @@ public class MilkomedaProperties { * 自定义参数配置,用于SpEl的#env读取 */ private Map env; + + /** + * Register early bean when load bean definition list. + * @since 3.15.0 + */ + private List> registerEarlyBeans; + + /** + * Register converter used with conversion service. + * @since 3.15.0 + */ + private List> registerConverters; } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/RedisGlobalConfig.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/RedisGlobalConfig.java new file mode 100644 index 00000000..8acdf117 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/config/RedisGlobalConfig.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +/** + * Redis global config. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/08 01:53 + */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") +@AutoConfigureAfter(RedisAutoConfiguration.class) +@Configuration +public class RedisGlobalConfig { + @Bean + @ConditionalOnClass(name = "org.springframework.data.redis.core.RedisTemplate") + public RedisTemplate jsonRedisTemplate(LettuceConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(),true); + TimeZone china = TimeZone.getTimeZone("GMT+08:00"); + objectMapper.setTimeZone(china); + objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); + jackson2JsonRedisSerializer.setObjectMapper(objectMapper); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); + redisTemplate.setConnectionFactory(redisConnectionFactory); + return redisTemplate; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/AopContextHolder.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/AopContextHolder.java index 2936c5f8..a2db40b0 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/AopContextHolder.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/AopContextHolder.java @@ -39,7 +39,6 @@ public final class AopContextHolder { /** * 获得当前切面代理对象 - *
使用前通过@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)开启代理曝露 * * @param clazz 当前类 * @param 当前类型 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/ApplicationContextHolder.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/ApplicationContextHolder.java index 892a9ff9..43a7848a 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/ApplicationContextHolder.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/ApplicationContextHolder.java @@ -24,9 +24,11 @@ import com.github.yizzuide.milkomeda.universe.engine.el.ELContext; import com.github.yizzuide.milkomeda.universe.extend.env.Environment; import lombok.Getter; +import lombok.Setter; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.lang.NonNull; /** @@ -42,24 +44,46 @@ public class ApplicationContextHolder implements ApplicationContextAware { private static ApplicationContextHolder INSTANCE; + // Spring准备好的环境变量 + @Setter @Getter + private static ConfigurableEnvironment pendingConfigurableEnvironment; + + // 环境变量包装类 private static Environment environment; @Getter private ApplicationContext applicationContext; - - public ApplicationContextHolder() { + public ApplicationContextHolder(Environment environment) { + ApplicationContextHolder.environment = environment; INSTANCE = this; } @Override public void setApplicationContext(@NonNull ApplicationContext applicationContext) { this.applicationContext = applicationContext; - // 设置应用上下文到EL解析环境 + // 设置应用上下文到方法EL解析环境 ELContext.setApplicationContext(applicationContext); + if (pendingConfigurableEnvironment != null) { + ApplicationContextHolder.environment.setConfigurableEnvironment(pendingConfigurableEnvironment); + return; + } if (applicationContext instanceof ConfigurableApplicationContext) { - ApplicationContextHolder.environment.setConfigurableEnvironment(((ConfigurableApplicationContext) applicationContext).getEnvironment()); + ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) applicationContext.getEnvironment(); + ApplicationContextHolder.environment.setConfigurableEnvironment(configurableEnvironment); + } + } + + /** + * Try to get Spring Ioc Context. + * @return null if `ApplicationContext` not created yet. + * @since 3.15.0 + */ + public static ApplicationContext tryGet() { + if (INSTANCE == null) { + return null; } + return get(); } /** diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/WebContext.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/WebContext.java index b422d1de..68b49eca 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/WebContext.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/context/WebContext.java @@ -21,14 +21,15 @@ package com.github.yizzuide.milkomeda.universe.context; +import com.github.yizzuide.milkomeda.comet.core.CometResponseWrapper; +import lombok.Getter; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.PathMatcher; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.util.UrlPathHelper; -import org.springframework.web.util.pattern.PathPatternParser; +import org.springframework.web.util.WebUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -48,54 +49,13 @@ public final class WebContext { /** * 路径匹配器 */ + @Getter private static PathMatcher mvcPathMatcher; - /** - * HTTP URL匹配器 - */ - private static PathPatternParser mvcPatternParser; - - /** - * URL路径帮助类 - */ - private static UrlPathHelper urlPathHelper; - public static void setMvcPathMatcher(PathMatcher mvcPathMatcher) { WebContext.mvcPathMatcher = mvcPathMatcher; } - /** - * 路径匹配器 - * @return PathMatcher - */ - public static PathMatcher getMvcPathMatcher() { - return mvcPathMatcher; - } - - public static void setMvcPatternParser(PathPatternParser mvcPatternParser) { - WebContext.mvcPatternParser = mvcPatternParser; - } - - /** - * HTTP URL匹配器 - * @return PathPatternParser - */ - public static PathPatternParser getMvcPatternParser() { - return mvcPatternParser; - } - - public static void setUrlPathHelper(UrlPathHelper urlPathHelper) { - WebContext.urlPathHelper = urlPathHelper; - } - - /** - * 请求路径帮助类 - * @return UrlPathHelper - */ - public static UrlPathHelper getUrlPathHelper() { - return urlPathHelper; - } - /** * 获取请求信息 * @return ServletRequestAttributes @@ -134,6 +94,22 @@ public static HttpServletResponse getResponse() { return requestAttributes.getResponse(); } + /** + * Get servlet response whenever had wrapped. + * @return unwrapped response + * @since 3.15.0 + */ + public static HttpServletResponse getRawResponse() { + HttpServletResponse response = getResponse(); + assert response != null; + CometResponseWrapper responseWrapper = + WebUtils.getNativeResponse(response, CometResponseWrapper.class); + if (responseWrapper == null) { + return response; + } + return (HttpServletResponse) responseWrapper.getResponse(); + } + /** * 获取当前会话 * @return HttpSession diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/AbstractExpressionEvaluator.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/AbstractExpressionEvaluator.java new file mode 100644 index 00000000..3c43f16c --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/AbstractExpressionEvaluator.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.engine.el; + +import com.github.yizzuide.milkomeda.metal.MetalHolder; +import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; +import com.github.yizzuide.milkomeda.universe.context.WebContext; +import com.github.yizzuide.milkomeda.universe.extend.env.Environment; +import org.springframework.context.expression.CachedExpressionEvaluator; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Abstract cached expression evaluator. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/24 19:58 + */ +public abstract class AbstractExpressionEvaluator extends CachedExpressionEvaluator { + /** + * 共享的参数名,基于内部缓存数据 + */ + protected final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer(); + + /** + * 条件缓存 + */ + protected final Map expressionKeyCache = new ConcurrentHashMap<>(64); + + /** + * Config evaluation context variables. + * @param evaluationContext StandardEvaluationContext + * @param root root object + */ + protected void configContext(StandardEvaluationContext evaluationContext, Object root) { + // 目标对象:#target + evaluationContext.setVariable("target", root); + // 添加变量引用:#env[key] + Environment env = ApplicationContextHolder.getEnvironment(); + if (env != null) { + evaluationContext.setVariable("env", env.getProperties()); + } + // 添加Metal配置:#metal[key] + Map metalSourceMap = MetalHolder.getSourceMap(); + if (metalSourceMap != null) { + evaluationContext.setVariable("metal", metalSourceMap); + } + // 请求对象:#request, #reqParams[key] + ServletRequestAttributes requestAttributes = WebContext.getRequestAttributes(); + if (requestAttributes != null) { + evaluationContext.setVariable("request", requestAttributes.getRequest()); + evaluationContext.setVariable("reqParams", requestAttributes.getRequest().getParameterMap()); + } + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ELContext.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ELContext.java index 3e59dd90..6d7269eb 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ELContext.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ELContext.java @@ -31,7 +31,7 @@ import java.lang.reflect.Method; /** - * ELContext + * Method based SpEL context. * * @author yizzuide * @since 2.0.0 @@ -86,7 +86,7 @@ public static T getValue(Object object, Object[] args, Class clazz, Metho return null; } // EL表达式执行器 - ExpressionEvaluator evaluator = new ExpressionEvaluator<>(); + MethodExpressionEvaluator evaluator = new MethodExpressionEvaluator<>(); // 创建AOP方法的执行上下文 StandardEvaluationContext evaluationContext = evaluator.createEvaluationContext(object, clazz, method, args); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ExpressionEvaluator.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/MethodExpressionEvaluator.java similarity index 64% rename from Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ExpressionEvaluator.java rename to Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/MethodExpressionEvaluator.java index a169ea23..bd94e114 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ExpressionEvaluator.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/MethodExpressionEvaluator.java @@ -21,20 +21,13 @@ package com.github.yizzuide.milkomeda.universe.engine.el; -import com.github.yizzuide.milkomeda.metal.MetalHolder; -import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; -import com.github.yizzuide.milkomeda.universe.context.WebContext; -import com.github.yizzuide.milkomeda.universe.extend.env.Environment; +import lombok.AllArgsConstructor; +import lombok.Data; import org.springframework.aop.support.AopUtils; import org.springframework.context.expression.AnnotatedElementKey; -import org.springframework.context.expression.CachedExpressionEvaluator; import org.springframework.context.expression.MethodBasedEvaluationContext; -import org.springframework.core.DefaultParameterNameDiscoverer; -import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.web.context.request.ServletRequestAttributes; import java.lang.reflect.Method; import java.util.Map; @@ -46,18 +39,11 @@ * * @author yizzuide * @since 1.5.0 - * @version 3.13.0 + * @version 3.15.0 *
* Create at 2019/05/30 22:24 */ -public class ExpressionEvaluator extends CachedExpressionEvaluator { - - // 共享的参数名,基于内部缓存数据 - private final ParameterNameDiscoverer paramNameDiscoverer = - new DefaultParameterNameDiscoverer(); - - // 条件缓存 - private final Map conditionCache = new ConcurrentHashMap<>(64); +public class MethodExpressionEvaluator extends AbstractExpressionEvaluator { // 目标方法缓存(提升查询性能) private final Map targetMethodCache = new ConcurrentHashMap<>(64); @@ -73,26 +59,12 @@ public class ExpressionEvaluator extends CachedExpressionEvaluator { public StandardEvaluationContext createEvaluationContext(Object object, Class targetClass, Method method, Object[] args) { Method targetMethod = getTargetMethod(targetClass, method); - // 创建自定义EL Root - ExpressionRootObject root = new ExpressionRootObject(object, args); + // 封装方法根对象数据,表达式引用通过:root.* 和 args[x] + MethodBasedRootObject rootObject = new MethodBasedRootObject(object, args); // 创建基于方法的执行上下文 - MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer); - // 添加变量引用 - Environment env = ApplicationContextHolder.getEnvironment(); - if (env != null) { - evaluationContext.setVariable("env", env.getProperties()); - } - // 添加Metal配置 - Map metalSourceMap = MetalHolder.getSourceMap(); - if (metalSourceMap != null) { - evaluationContext.setVariable("metal", metalSourceMap); - } - evaluationContext.setVariable("target", object); - ServletRequestAttributes requestAttributes = WebContext.getRequestAttributes(); - if (requestAttributes != null) { - evaluationContext.setVariable("request", requestAttributes.getRequest()); - evaluationContext.setVariable("reqParams", requestAttributes.getRequest().getParameterMap()); - } + MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(rootObject, targetMethod, args, this.paramNameDiscoverer); + // 注入其它变量 + configContext(evaluationContext, object); return evaluationContext; } @@ -106,7 +78,7 @@ public StandardEvaluationContext createEvaluationContext(Object object, Class */ public T condition(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class clazz) { - return getExpression(this.conditionCache, elementKey, conditionExpression) + return getExpression(this.expressionKeyCache, elementKey, conditionExpression) .getValue(evalContext, clazz); } @@ -125,4 +97,21 @@ private Method getTargetMethod(Class targetClass, Method method) { } return targetMethod; } + + /** + * Root object for method which current invoked. + * @since 3.15.0 + */ + @AllArgsConstructor + @Data + static class MethodBasedRootObject { + /** + * The root object is current method's target object. + */ + private Object root; + /** + * The args is current method invoked. + */ + private Object[] args; + } } \ No newline at end of file diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ObjectExpressionEvaluator.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ObjectExpressionEvaluator.java new file mode 100644 index 00000000..d4b8b193 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ObjectExpressionEvaluator.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.engine.el; + +import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; +import org.springframework.context.ApplicationContext; +import org.springframework.context.expression.AnnotatedElementKey; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * Object based expression evaluator. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/24 20:02 + */ +public class ObjectExpressionEvaluator extends AbstractExpressionEvaluator { + public T condition(String expression, Object object, Class resultType) { + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); + ApplicationContext beanFactory = ApplicationContextHolder.tryGet(); + if (beanFactory != null) { + BeanFactoryResolver beanFactoryResolver = new BeanFactoryResolver(beanFactory); + evaluationContext.setBeanResolver(beanFactoryResolver); + } + evaluationContext.setRootObject(object); + configContext(evaluationContext, object); + AnnotatedElementKey elementKey = new AnnotatedElementKey(object.getClass(), null); + return getExpression(this.expressionKeyCache, elementKey, expression) + .getValue(evaluationContext, resultType); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/SimpleElParser.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/SimpleElParser.java index 9556f62a..9617a27f 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/SimpleElParser.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/SimpleElParser.java @@ -21,27 +21,28 @@ package com.github.yizzuide.milkomeda.universe.engine.el; -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; - /** - * SimpleElParser + * Simple object based impl of El parser. * - * @author yizzuide * @since 3.8.0 + * @version 3.15.0 + * @author yizzuide *
* Create at 2020/06/16 10:25 */ public class SimpleElParser { - private static final ExpressionParser EL_PARSER = new SpelExpressionParser(); + private static final ObjectExpressionEvaluator EVALUATOR = new ObjectExpressionEvaluator(); - public static T parse(String expression, Object root, Class resultType) { - Expression expressionWrapper = EL_PARSER.parseExpression(expression); - if (root == null) { - return expressionWrapper.getValue(resultType); - } - return expressionWrapper.getValue(root, resultType); + /** + * Parse spring EL expression and return value. + * @param expression EL expression + * @param object root object + * @param resultType result class type + * @return value of result type + * @param result type + */ + public static T parse(String expression, Object object, Class resultType) { + return EVALUATOR.condition(expression, object, resultType); } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/ognl/OgnlMemberAccess.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/ognl/OgnlMemberAccess.java index a5fba2b7..ec5ab864 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/ognl/OgnlMemberAccess.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/ognl/OgnlMemberAccess.java @@ -54,6 +54,7 @@ public OgnlMemberAccess(boolean allowPrivateAccess, boolean allowProtectedAccess this.allowPackageProtectedAccess = allowPackageProtectedAccess; } + @Override public Object setup(Map context, Object target, Member member, String propertyName) { Object result = null; if (isAccessible(context, target, member, propertyName)) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/ognl/SimpleOgnlParser.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/ognl/SimpleOgnlParser.java new file mode 100644 index 00000000..92526ecb --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/ognl/SimpleOgnlParser.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.engine.ognl; + +import ognl.ClassResolver; +import ognl.MemberAccess; +import ognl.Ognl; +import ognl.OgnlException; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Simple impl of Ognl parser. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2022/12/25 00:29 + */ +public class SimpleOgnlParser { + private static final MemberAccess MEMBER_ACCESS = new OgnlMemberAccess(false); + + private static final ClassResolver CLASS_RESOLVER = new OgnlClassResolver(); + + private static final Map expressionCache = new ConcurrentHashMap<>(); + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static T parse(String expression, Object object, Class resultType) throws OgnlException { + Map ognlContext = Ognl.createDefaultContext(object, MEMBER_ACCESS, CLASS_RESOLVER, null); + return (T) Ognl.getValue(parseWithCache(expression), ognlContext, object, resultType); + } + + // 缓存表达式解析抽象树对象 + private static Object parseWithCache(String expression) throws OgnlException { + Object node = expressionCache.get(expression); + if (node == null) { + node = Ognl.parseExpression(expression); + expressionCache.put(expression, node); + } + return node; + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/annotation/Alias.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/annotation/Alias.java new file mode 100644 index 00000000..32113dfb --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/annotation/Alias.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.extend.annotation; + +import java.lang.annotation.*; + +/** + * Add alias bind to bean, but this name can not find in spring context (so its can repeat in each other module). it's just + * used for link and find easily in yml config. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/05 21:54 + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Alias { + /** + * Set bean alias. + * @return bean alias. + */ + String value(); +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/annotation/AliasWrapper.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/annotation/AliasWrapper.java new file mode 100644 index 00000000..e4e0258a --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/annotation/AliasWrapper.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.extend.annotation; + +import com.github.yizzuide.milkomeda.universe.extend.web.handler.NamedHandler; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Alias bind bean in spring context. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/05 22:10 + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +public class AliasWrapper { + + /** + * Alias or bean name (in favor of {@link NamedHandler#handlerName()} maybe return bean name) link to bean. + */ + private String name; + + /** + * Bean name in spring context. + */ + private String beanName; + + /** + * Bean instance. + */ + private T bean; +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/converter/MapToCollectionConverter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/converter/MapToCollectionConverter.java index c6214447..dde8a2b3 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/converter/MapToCollectionConverter.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/converter/MapToCollectionConverter.java @@ -35,9 +35,9 @@ /** * Converts a Map to Collection. - * + *

* For similar implementation, refer to MapToMapConverter or CollectionToCollectionConverter - * + *

* @author yizzuide * @since 3.13.0 * @see org.springframework.core.convert.support.DefaultConversionService diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/CollectionsPropertySource.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/CollectionsPropertySource.java index 508d1b0b..1215c9f5 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/CollectionsPropertySource.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/CollectionsPropertySource.java @@ -52,6 +52,8 @@ public class CollectionsPropertySource extends PropertySource { public static final String COLLECTIONS_OBJECT_DATE = "{date}"; + public static final String COLLECTIONS_OBJECT_MILLS = "{mills}"; + private static final String PREFIX = "collections."; public CollectionsPropertySource() { @@ -76,17 +78,22 @@ public Object getValue(String type) { if ("dateObject".equals(type)) { return COLLECTIONS_OBJECT_DATE; } + if ("millsObject".equals(type)) { + return COLLECTIONS_OBJECT_MILLS; + } return null; } /** - * Get empty object with token + * Get value object with token. * @param psValue token string * @return empty object * @since 3.14.0 */ public static Object of(Object psValue) { - if (psValue == null) return null; + if (psValue == null) { + return null; + } if (psValue.equals(COLLECTIONS_EMPTY_MAP)) { return Collections.emptyMap(); } @@ -96,11 +103,14 @@ public static Object of(Object psValue) { if (psValue.equals(COLLECTIONS_OBJECT_DATE)) { return new Date(); } + if (psValue.equals(COLLECTIONS_OBJECT_MILLS)) { + return System.currentTimeMillis(); + } return psValue; } public static void addToEnvironment(ConfigurableEnvironment environment) { - // 添加在SystemEnvironmentPropertySource后面,而SpringCloud(bootstrap.yml,比application.yml优化级高)和SpringBoot(application.yml)环境里的配置可以读取 + // 添加在SystemEnvironmentPropertySource后面,而SpringCloud(bootstrap.yml,比application.yml优化级高)和Spring Boot(application.yml)环境里的配置可以读取 environment.getPropertySources().addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, new CollectionsPropertySource()); log.trace("CollectionsPropertySource add to Environment"); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/ConditionPropertySource.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/ConditionPropertySource.java index 6e3c7b11..891a6421 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/ConditionPropertySource.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/ConditionPropertySource.java @@ -75,7 +75,7 @@ private boolean getBoolValue(String range) { return parts[0].equals(parts[1]); } - private String getRange(String type, String prefix) { + public static String getRange(String type, String prefix) { if (type.startsWith(prefix)) { int startIndex = prefix.length() + 1; if (type.length() > startIndex) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/CustomPropertySourceLocator.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/CustomPropertySourceLocator.java index 2985cbda..fedaa584 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/CustomPropertySourceLocator.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/CustomPropertySourceLocator.java @@ -31,7 +31,7 @@ * * @author yizzuide * @since 3.0.1 - * @version 3.2.1 + * @version 3.15.0 *
* Create at 2020/04/11 10:48 */ @@ -51,5 +51,6 @@ public PropertySource locate(Environment environment) { public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { CollectionsPropertySource.addToEnvironment(environment); ConditionPropertySource.addToEnvironment(environment); + SpELPropertySource.addToEnvironment(environment); } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/Environment.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/Environment.java index 44502c1c..afdd6746 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/Environment.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/Environment.java @@ -33,7 +33,7 @@ import java.util.Properties; /** - * Environment + * Environment that include {@link ConfigurableEnvironment} and addition {@link Properties}. * * @author yizzuide * @since 3.1.0 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/EnvironmentApplicationListener.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/EnvironmentApplicationListener.java index 7d445cc5..7f353fbd 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/EnvironmentApplicationListener.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/EnvironmentApplicationListener.java @@ -21,24 +21,32 @@ package com.github.yizzuide.milkomeda.universe.extend.env; +import com.github.yizzuide.milkomeda.universe.config.MilkomedaProperties; +import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; import com.github.yizzuide.milkomeda.universe.extend.converter.MapToCollectionConverter; +import com.github.yizzuide.milkomeda.util.ReflectUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; +import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; import org.springframework.lang.NonNull; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Objects; /** * Environment Prepared Listener * * @author yizzuide * @since 3.0.1 - * @version 3.13.0 + * @version 3.15.0 * @see org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor * @see org.springframework.boot.context.config.AnsiOutputApplicationListener *
@@ -55,13 +63,14 @@ public void onApplicationEvent(@NonNull ApplicationEnvironmentPreparedEvent even } // StandardServletEnvironment or StandardReactiveEnvironment + ApplicationContextHolder.setPendingConfigurableEnvironment(environment); // BeanWrapper binding ConversionService process: - // 1.SpringBoot start setting ConversionService + // 1.Spring Boot start setting ConversionService // org.springframework.boot.SpringApplication.configureEnvironment() // -> environment.setConversionService(new ApplicationConversionService()); // -> context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService()); - // 2.SpringBoot create BeanWrapper and set ConversionService + // 2.Spring Boot create BeanWrapper and set ConversionService // org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean() // -> AbstractBeanFactory.initBeanWrapper() // -> bw.setConversionService(getConversionService()); @@ -69,12 +78,20 @@ public void onApplicationEvent(@NonNull ApplicationEnvironmentPreparedEvent even // Register Converter ConfigurableConversionService conversionService = environment.getConversionService(); conversionService.addConverter(new MapToCollectionConverter(conversionService)); + MilkomedaProperties milkomedaProperties = Binder.get(environment).bind(MilkomedaProperties.PREFIX, MilkomedaProperties.class).get(); + List> converters = milkomedaProperties.getRegisterConverters(); + if (!CollectionUtils.isEmpty(converters)) { + converters.stream().map(ReflectUtil::newInstance).filter(Objects::nonNull).forEach(conversionService::addConverter); + } // Get and check conversionService // ((ConfigurableApplicationContext)ApplicationContextHolder.get()).getBeanFactory().getConversionService() // bind property - boolean logEnable = Binder.get(environment).bind("milkomeda.show-log", Boolean.class).orElseGet(() -> false); - log.info("milkomeda log: {}", logEnable ? "enable" : "disable"); + boolean logEnable = false; + try { + logEnable = Binder.get(environment).bind("milkomeda.show-log", Boolean.class).orElseGet(() -> false); + } catch (Exception ignore) {} + log.info("milkomeda log is {}", logEnable ? "enable" : "disable"); } @Override diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/SpELPropertySource.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/SpELPropertySource.java new file mode 100644 index 00000000..555b5496 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/SpELPropertySource.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.extend.env; + +import com.github.yizzuide.milkomeda.universe.engine.el.SimpleElParser; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * Supported SpEL to YML value parse with PropertySource. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/02 19:12 + */ +@Slf4j +public class SpELPropertySource extends PropertySource { + + public static final String EL_PROPERTY_SOURCE_NAME = "el"; + + private static final String PREFIX = "el."; + + private static final String FUN_PARSE = "parse"; + + public static final Map> TYPE_MAP = new HashMap<>(); + + private static Object ROOT; + + public SpELPropertySource(Object root) { + super(EL_PROPERTY_SOURCE_NAME); + ROOT = root; + if (!CollectionUtils.isEmpty(TYPE_MAP)) { + return; + } + TYPE_MAP.put("INT", java.lang.Integer.class); + TYPE_MAP.put("LONG", java.lang.Long.class); + TYPE_MAP.put("BOOL", java.lang.Boolean.class); + TYPE_MAP.put("STRING", java.lang.String.class); + TYPE_MAP.put("DATE", java.util.Date.class); + TYPE_MAP.put("OBJECT", java.lang.Object.class); + } + + @Override + public Object getProperty(String name) { + if (!name.startsWith(PREFIX)) { + return null; + } + String fun = name.substring(PREFIX.length()); + return parseFun(fun, FUN_PARSE); + } + + public static Object parseElFun(String fun) { + return parseFun(fun, EL_PROPERTY_SOURCE_NAME); + } + + private static Object parseFun(String fun, String funPrefix) { + Class type = String.class; + if (fun.startsWith(funPrefix)) { + String condition = ConditionPropertySource.getRange(fun, funPrefix); + if (condition == null) { + return null; + } + if (condition.contains(",")) { + String[] parts = condition.split(","); + String typeKey = parts[1].trim().toUpperCase(); + if (StringUtils.hasText(typeKey)) { + type = TYPE_MAP.get(typeKey); + } + return SimpleElParser.parse(parts[0], ROOT, type); + } + return SimpleElParser.parse(condition, ROOT, type); + } + return null; + } + + public static void addToEnvironment(ConfigurableEnvironment environment) { + environment.getPropertySources().addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, + new SpELPropertySource(environment)); + log.trace("SpELPropertySource add to Environment"); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/YmlPropertySourceFactory.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/YmlPropertySourceFactory.java index def1db73..1daecdaa 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/YmlPropertySourceFactory.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/env/YmlPropertySourceFactory.java @@ -32,7 +32,7 @@ import java.util.Properties; /** - * Add support yaml config with PropertySource + * Add support yaml config with PropertySource. * * @author yizzuide * @since 3.13.0 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/loader/LuaLoader.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/loader/LuaLoader.java new file mode 100644 index 00000000..815af0bf --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/loader/LuaLoader.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.extend.loader; + +import com.github.yizzuide.milkomeda.util.IOUtils; + +import java.io.IOException; + +/** + * The loader Specify loading lua file. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/19 03:20 + */ +public interface LuaLoader { + /** + * Lua script dir path. + * @return lua source dir. + */ + default String resourceDirPath() { + return IOUtils.LUA_PATH; + } + + /** + * Lua script file name list. + * @return list file name + */ + String[] luaFilenames(); + + /** + * Setter of hold content filed. + * @param luaScripts lua script file list + */ + void setLuaScripts(String[] luaScripts); + + /** + * Start load lua script. + * @throws IOException if load file not exists. + */ + default void load() throws IOException { + String[] luaFilenames = luaFilenames(); + if (luaFilenames == null || luaFilenames.length == 0) { + return; + } + String[] luaScripts = new String[luaFilenames.length]; + for (int i = 0; i < luaFilenames.length; i++) { + luaScripts[i] = IOUtils.loadLua(resourceDirPath(), luaFilenames[i]); + } + setLuaScripts(luaScripts); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/processor/EarlyLoadBeanDefinitionRegistryPostProcessor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/processor/EarlyLoadBeanDefinitionRegistryPostProcessor.java new file mode 100644 index 00000000..6bc2cd28 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/processor/EarlyLoadBeanDefinitionRegistryPostProcessor.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.extend.processor; + +import com.github.yizzuide.milkomeda.universe.config.MilkomedaProperties; +import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.AnnotationBeanNameGenerator; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.lang.NonNull; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +/** + * The processor used for registry bean definition early. + * + * @see org.springframework.context.annotation.ConfigurationClassPostProcessor + * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor + * @see org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/04/27 20:44 + */ +// BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor子接口,而postProcessBeanDefinitionRegistry方法比postProcessBeanFactory调用更早, +// 处理BeanFactoryPostProcessor流程:AbstractApplicationContext.refresh() -> +// invokeBeanFactoryPostProcessors() -> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors() +public class EarlyLoadBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { + + @Override + public void postProcessBeanDefinitionRegistry(@NonNull BeanDefinitionRegistry registry) throws BeansException { + // 将ApplicationContextHolder排在四个Spring内部Processor Bean后面注册 + BeanDefinition beanDefinition = new RootBeanDefinition(ApplicationContextHolder.class); + String beanName = AnnotationBeanNameGenerator.INSTANCE.generateBeanName(beanDefinition, registry); + registry.registerBeanDefinition(beanName, beanDefinition); + + // 加载其它配置的早期注册的Bean + ConfigurableEnvironment configurableEnvironment = ApplicationContextHolder.getPendingConfigurableEnvironment(); + if(configurableEnvironment != null) { + MilkomedaProperties milkomedaProperties = Binder.get(configurableEnvironment).bind(MilkomedaProperties.PREFIX, MilkomedaProperties.class).get(); + List> earlyRegisterBeans = milkomedaProperties.getRegisterEarlyBeans(); + if (CollectionUtils.isEmpty(earlyRegisterBeans)) { + return; + } + earlyRegisterBeans.forEach(clazz -> { + BeanDefinition earlyBeanDefinition = new RootBeanDefinition(clazz); + String earlyBeanName = AnnotationBeanNameGenerator.INSTANCE.generateBeanName(earlyBeanDefinition, registry); + registry.registerBeanDefinition(earlyBeanName, earlyBeanDefinition); + }); + } + } + + @Override + public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException {} +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/AstrolabeHandler.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/AstrolabeHandler.java index 10999c24..6b4c8f0c 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/AstrolabeHandler.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/AstrolabeHandler.java @@ -21,24 +21,25 @@ package com.github.yizzuide.milkomeda.universe.extend.web.handler; -import org.springframework.core.Ordered; +import org.springframework.web.context.request.WebRequestInterceptor; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** - * AstrolabeHandler - * 星盘处理器(轻量级请求过滤器,类似线程之上的协程) + * Request aspect handler that impl used filter which compare with {@link WebRequestInterceptor} impl used interceptor. * + * @see DelegatingContextFilter * @author yizzuide * @since 3.3.0 - * @see DelegatingContextFilter *
* Create at 2020/05/06 11:38 */ -public interface AstrolabeHandler extends Ordered { +public interface AstrolabeHandler extends NamedHandler { /** - * 请求前置 + * Handle request before mapping into controller. + * Just throw {@link com.github.yizzuide.milkomeda.hydrogen.uniform.UniformException} if you need intercept request. + * * @param request ServletRequest */ default void preHandle(ServletRequest request) {} @@ -49,9 +50,4 @@ default void preHandle(ServletRequest request) {} * @param response ServletResponse */ default void postHandle(ServletRequest request, ServletResponse response) {} - - @Override - default int getOrder() { - return 0; - } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/DelegatingContextFilter.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/DelegatingContextFilter.java index 43f56f0c..2edf3701 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/DelegatingContextFilter.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/DelegatingContextFilter.java @@ -21,11 +21,16 @@ package com.github.yizzuide.milkomeda.universe.extend.web.handler; +import com.github.yizzuide.milkomeda.hydrogen.uniform.UniformHandler; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.OrderComparator; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.util.CollectionUtils; import javax.annotation.PostConstruct; import javax.servlet.*; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -35,14 +40,15 @@ * DelegatingContextFilter * 代理上下文过滤器 * + * @see org.springframework.web.filter.RequestContextFilter + * @see org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter * @author yizzuide * @since 3.3.1 * @version 3.12.9 - * @see org.springframework.web.filter.RequestContextFilter - * @see org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter *
* Create at 2020/05/06 11:12 */ +@Slf4j public class DelegatingContextFilter implements Filter { @Autowired(required = false) @@ -50,23 +56,37 @@ public class DelegatingContextFilter implements Filter { @PostConstruct public void init() { + if (CollectionUtils.isEmpty(astrolabeHandlers)) { + return; + } // 根据@Order注解排序 - //AnnotationAwareOrderComparator.sort(astrolabeHandlers); + AnnotationAwareOrderComparator.sort(astrolabeHandlers); // 根据Order接口排序 - astrolabeHandlers = astrolabeHandlers.stream() - .sorted(OrderComparator.INSTANCE.withSourceProvider(ha -> ha)).collect(Collectors.toList()); + if (astrolabeHandlers.stream().anyMatch(a -> a.getOrder() != 0)) { + astrolabeHandlers = astrolabeHandlers.stream() + .sorted(OrderComparator.INSTANCE.withSourceProvider(ha -> ha)).collect(Collectors.toList()); + } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { for (AstrolabeHandler astrolabeHandler : astrolabeHandlers) { - astrolabeHandler.preHandle(request); + try { + astrolabeHandler.preHandle(request); + } catch (Exception e) { + UniformHandler.matchStatusToWrite( (HttpServletResponse) response, null, e); + return; + } } try { chain.doFilter(request, response); } finally { - for (AstrolabeHandler astrolabeHandler : astrolabeHandlers) { - astrolabeHandler.postHandle(request, response); + try { + for (AstrolabeHandler astrolabeHandler : astrolabeHandlers) { + astrolabeHandler.postHandle(request, response); + } + } catch (Exception e) { + log.error("astrolabe handler post handle with error msg: {}", e.getMessage(), e); } } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HandlerProperty.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HandlerProperty.java new file mode 100644 index 00000000..df578cce --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HandlerProperty.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.extend.web.handler; + +import lombok.Data; + +import java.util.Map; + +/** + * A base handler config property. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/05 23:53 + */ +@Data +public class HandlerProperty { + + /** + * handler order in list. + */ + private int order = 0; + + /** + * handler addition property kvs need population. + */ + private Map props; +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HotHandlerProperty.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HotHandlerProperty.java new file mode 100644 index 00000000..5b2595e8 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HotHandlerProperty.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.extend.web.handler; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Hot handler means that need enable first before used. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/05 23:55 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class HotHandlerProperty extends HandlerProperty { + /** + * Enable this handler. + */ + private boolean enable = false; +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HotHttpHandlerProperty.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HotHttpHandlerProperty.java new file mode 100644 index 00000000..39e4fdde --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HotHttpHandlerProperty.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.extend.web.handler; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Hot handler means that need enable first before used. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/06 00:14 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class HotHttpHandlerProperty extends HttpHandlerProperty { + /** + * Enable this handler. + */ + private boolean enable = false; +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HttpHandlerProperty.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HttpHandlerProperty.java new file mode 100644 index 00000000..688f3fff --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/HttpHandlerProperty.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.extend.web.handler; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Collections; +import java.util.List; + +/** + * This handler provide URL filter with {@link HandlerProperty}. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/06 00:12 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class HttpHandlerProperty extends HandlerProperty { + + /** + * URL list need include. + */ + private List includeUrls = Collections.singletonList("/**"); + + /** + * URL list need ignore. + */ + private List excludeUrls; +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/NamedHandler.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/NamedHandler.java new file mode 100644 index 00000000..eb68ca21 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/extend/web/handler/NamedHandler.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.universe.extend.web.handler; + +import com.github.yizzuide.milkomeda.universe.extend.annotation.Alias; +import com.github.yizzuide.milkomeda.universe.extend.annotation.AliasWrapper; +import com.github.yizzuide.milkomeda.universe.parser.url.URLPathMatcher; +import com.github.yizzuide.milkomeda.util.RecognizeUtil; +import com.github.yizzuide.milkomeda.util.ReflectUtil; +import org.springframework.core.OrderComparator; +import org.springframework.core.PriorityOrdered; +import org.springframework.util.CollectionUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * This handler interface provides named and ordered in a list. + * It always used with {@link Alias} and {@link AliasWrapper}. + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/05/05 22:36 + */ +public interface NamedHandler extends PriorityOrdered { + /** + * Set order in the list. + * @param order the order used sort in the list + */ + default void setOrder(int order) {} + + /** + * Get order in the list. + * @return the order used sort in the list + */ + @Override + default int getOrder() { + return 0; + } + + /** + * Set handler name. + * @return handler name. + */ + default String handlerName() { + Class handlerClass = this.getClass(); + if (handlerClass.isAnnotationPresent(Alias.class)) { + return handlerClass.getAnnotation(Alias.class).value(); + } + return RecognizeUtil.getBeanName(this.getClass()); + } + + /** + * Convert bean map to {@link AliasWrapper} map. + * @param beanMap bean map that bean name binds bean instance + * @return {@link AliasWrapper} map + * @param handler class type + */ + static Map> mapFrom(Map beanMap) { + return beanMap.entrySet().stream() + .map(e -> new AbstractMap.SimpleEntry<>(e.getValue().handlerName(), new AliasWrapper<>(e.getValue().handlerName(), e.getKey(), e.getValue()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + /** + * Build sorted handler list from bean map. + * @param beanMap bean map that bean name binds bean instance + * @param get get handler property from handler name + * @return handler list + * @param handler class type + */ + static List sortedList(Map beanMap, Function get) { + Map> handlerAliasMap = mapFrom(beanMap); + List handlerList = new ArrayList<>(); + Set interceptorNames = handlerAliasMap.keySet(); + for (String interceptorName : interceptorNames) { + HandlerProperty handlerProperty = get.apply(interceptorName); + T handler = handlerAliasMap.get(interceptorName).getBean(); + // 是否为热开启处理器 + if (handlerProperty instanceof HotHandlerProperty) { + if (!((HotHandlerProperty) handlerProperty).isEnable()) { + continue; + } + } + handler.setOrder(handlerProperty.getOrder()); + if (handlerProperty.getProps() != null) { + ReflectUtil.setField(handler, handlerProperty.getProps()); + } + handlerList.add(handler); + } + if (!CollectionUtils.isEmpty(handlerList)) { + handlerList = handlerList.stream() + .sorted(OrderComparator.INSTANCE.withSourceProvider(itr -> itr)).collect(Collectors.toList()); + } + return handlerList; + } + + /** + * Check can handle with request URL. + * @param request http request + * @param handlerProperty handler config property + * @return true if handle success + */ + static boolean canHandle(HttpServletRequest request, HttpHandlerProperty handlerProperty) { + return canHandle(request, handlerProperty.getIncludeUrls(), handlerProperty.getExcludeUrls()); + } + + /** + * Check can handle with request URL. + * @param request http request + * @param includeUrls need to include URL + * @param excludeUrls must ignore URL + * @return true if handle success + */ + static boolean canHandle(HttpServletRequest request, List includeUrls, List excludeUrls) { + if (request == null) { + return false; + } + String url = request.getRequestURI(); + if (CollectionUtils.isEmpty(includeUrls)) { + return false; + } + if (!CollectionUtils.isEmpty(excludeUrls)) { + if (URLPathMatcher.match(excludeUrls, url)) { + return false; + } + } + return URLPathMatcher.match(includeUrls, url); + } +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/metadata/BeanIds.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/metadata/BeanIds.java index 56105a4a..37b8e4de 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/metadata/BeanIds.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/metadata/BeanIds.java @@ -26,11 +26,16 @@ * * @author yizzuide * @since 3.0.0 - * @version 3.14.0 + * @version 3.15.0 *
* Create at 2020/04/09 15:15 */ public interface BeanIds { + /** + * Spring MVC request handler mapping. + */ + String REQUEST_MAPPING_HANDLER_MAPPING = "requestMappingHandlerMapping"; + /** * Comet Logger URL解析器Bean名 */ diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/placeholder/PlaceholderExtractor.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/placeholder/PlaceholderExtractor.java index 05fff3ef..7780a119 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/placeholder/PlaceholderExtractor.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/placeholder/PlaceholderExtractor.java @@ -28,6 +28,7 @@ import org.springframework.util.PropertyPlaceholderHelper; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -84,7 +85,7 @@ private PlaceholderExtractor(String placeholderPrefix, String placeholderSuffix) } /** - * Create PlaceholderResolver with placeholderPrefix, the default placeholderSuffix is "}" + * Create PlaceholderResolver with placeholderPrefix. * @param placeholderPrefix placeholder prefix * @return PlaceholderResolver * @since 3.13.0 @@ -112,7 +113,7 @@ public static PlaceholderExtractor create(String placeholderPrefix, String place public List getPlaceHolders(String value) { int start = value.indexOf(this.placeholderPrefix); if (start == -1) { - return null; + return Collections.emptyList(); } List keys = new ArrayList<>(); while (start != -1) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/url/URLPlaceholderParser.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/url/URLPlaceholderParser.java index e84dba17..9a674c14 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/url/URLPlaceholderParser.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/url/URLPlaceholderParser.java @@ -22,9 +22,9 @@ package com.github.yizzuide.milkomeda.universe.parser.url; import com.github.yizzuide.milkomeda.comet.core.CometRequestWrapper; +import com.github.yizzuide.milkomeda.universe.parser.placeholder.PlaceholderExtractor; import com.github.yizzuide.milkomeda.util.DataTypeConvertUtil; import com.github.yizzuide.milkomeda.util.JSONUtil; -import com.github.yizzuide.milkomeda.universe.parser.placeholder.PlaceholderExtractor; import lombok.Data; import org.apache.commons.lang3.StringUtils; @@ -95,7 +95,7 @@ public URLPlaceholderParser(String paramsPrefix, String paramsSuffix) { */ public Map> grabPlaceHolders(String tpl) { List placeHolders = placeholderExtractor.getPlaceHolders(tpl); - Map> keyMap = new HashMap<>(6); + Map> keyMap = new HashMap<>(8); keyMap.put(KEY_HEAD, placeHolders.stream().filter(s -> s.startsWith(headerStartToken)).collect(Collectors.toList())); keyMap.put(KEY_COOKIE, placeHolders.stream().filter(s -> s.startsWith(cookieStartToken)).collect(Collectors.toList())); keyMap.put(KEY_PARAMS, placeHolders.stream().filter(s -> s.startsWith(paramsStartToken)).collect(Collectors.toList())); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/yml/YmlParser.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/yml/YmlParser.java index 6e57b7f8..501b451a 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/yml/YmlParser.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/yml/YmlParser.java @@ -22,6 +22,8 @@ package com.github.yizzuide.milkomeda.universe.parser.yml; import com.github.yizzuide.milkomeda.universe.extend.env.CollectionsPropertySource; +import com.github.yizzuide.milkomeda.universe.lang.Tuple; +import com.github.yizzuide.milkomeda.util.Strings; import org.apache.commons.lang3.StringUtils; import java.util.HashMap; @@ -97,7 +99,7 @@ public static Map parseAliasNodePath(Map n * @param replaceData 如果值存在,使用替换的源强制替换配置的字段 */ public static void parseAliasMapPath(Map nodeMap, Map result, String ownerAliasKey, Object defaultValue, Object replaceData) { - Map aliasNodeMap = new HashMap<>(2); + Map aliasNodeMap = new HashMap<>(4); parseAliasNodePath(nodeMap, aliasNodeMap, ownerAliasKey, defaultValue, replaceData); if (aliasNodeMap.isEmpty()) { return; @@ -116,25 +118,18 @@ public static void parseAliasMapPath(Map nodeMap, Map nodeMap, Map aliasNodeMap, String ownerAliasKey, Object defaultValue, Object replaceData) { - String key = ownerAliasKey; - Object value = nodeMap.get(ownerAliasKey); + // 配置中未添加该返回的字段,不计入返回结果 + if (!nodeMap.keySet().contains(ownerAliasKey)) { + return; + } + Tuple aliasNode = extractAliasNode(nodeMap, ownerAliasKey); + String key = aliasNode.getT1(); + Object value = aliasNode.getT2(); // 判定别名节点flag - boolean hasAliasNode = false; + boolean hasAliasNode = !key.equals(ownerAliasKey); // 未指定的配置字段,如果有默认值 if ((value == null || StringUtils.isBlank(value.toString())) && defaultValue != null) { value = defaultValue; - } else if (value instanceof Map) { // 别名替换 - Map valueMap = (Map) value; - if (valueMap.size() > 0) { - key = String.valueOf(valueMap.keySet().toArray()[0]); - value = valueMap.get(key); - hasAliasNode = true; - } - } - - // 配置中未添加该返回的字段,不计入返回结果 - if (value == null) { - return; } // 创建别名节点 @@ -157,11 +152,32 @@ public static void parseAliasNodePath(Map nodeMap, Map extractAliasNode(Map nodeMap, String ownerAliasKey) { + String key = ownerAliasKey; + Object value = nodeMap.get(key); + if (value instanceof Map) { + Map valueMap = (Map) value; + if (valueMap.size() > 0) { + key = String.valueOf(valueMap.keySet().toArray()[0]); + value = valueMap.get(key); + } + } + return Tuple.build(key, value); + } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/yml/YmlResponseOutput.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/yml/YmlResponseOutput.java index a95f341c..004f37b6 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/yml/YmlResponseOutput.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/parser/yml/YmlResponseOutput.java @@ -26,6 +26,7 @@ import com.github.yizzuide.milkomeda.util.DataTypeConvertUtil; import lombok.NonNull; import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; import java.util.Arrays; import java.util.Map; @@ -65,6 +66,12 @@ public static void output(@NonNull Map nodeMap, @NonNull Map exMap = DataTypeConvertUtil.beanToMap(e); + // 如果yml有默认配置,重置到异常message + Object messageDefaultValue = YmlParser.extractAliasNode(nodeMap, MESSAGE).getT2(); + if ((messageDefaultValue != null && StringUtils.hasText(messageDefaultValue.toString())) + && (exMap.get(MESSAGE) != null && StringUtils.hasText(exMap.get(MESSAGE).toString()))) { + exMap.put(MESSAGE, messageDefaultValue); + } // 自定义异常的信息值使用自定义异常属性值替换 YmlParser.parseAliasMapPath(nodeMap, result, CODE, null, exMap); YmlParser.parseAliasMapPath(nodeMap, result, MESSAGE, null, exMap); @@ -73,7 +80,7 @@ public static void output(@NonNull Map nodeMap, @NonNull Map YmlParser.parseAliasMapPath(nodeMap, result, k, null, exMap)); } else { // 非自定义异常基本信息写出,支持默认值源 YmlParser.parseAliasMapPath(nodeMap, result, CODE, defValMap == null ? -1 : defValMap.get(CODE), null); - YmlParser.parseAliasMapPath(nodeMap, result, MESSAGE, defValMap == null ? "Server Internal error!" : defValMap.get(MESSAGE), null); + YmlParser.parseAliasMapPath(nodeMap, result, MESSAGE, defValMap == null ? "Server internal error!" : defValMap.get(MESSAGE), null); YmlParser.parseAliasMapPath(nodeMap, result, DATA, null, null); } // 内部异常详情写出 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/polyfill/SpringMvcPolyfill.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/polyfill/SpringMvcPolyfill.java index 635df050..4f11a201 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/polyfill/SpringMvcPolyfill.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/polyfill/SpringMvcPolyfill.java @@ -80,7 +80,7 @@ public static void addDynamicExceptionAdvice(HandlerExceptionResolver handlerExc // TODO 由于使用底层API, 这个exceptionHandlerAdviceCache属性在未来版本中可能会改 Map resolverMap = ReflectUtil.invokeFieldPath(exceptionResolver, "exceptionHandlerAdviceCache"); if (resolverMap == null) { - resolverMap = new HashMap<>(2); + resolverMap = new HashMap<>(4); } // 仿Spring MVC源码创建advice ControllerAdviceBean adviceBean = new ControllerAdviceBean(beanName, ApplicationContextHolder.get(), null); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/CollectionExtensionsKt.kt b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/CollectionExtensionsKt.kt new file mode 100644 index 00000000..771d6ff4 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/CollectionExtensionsKt.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +@file:JvmName("CollectionsKt") +package com.github.yizzuide.milkomeda.util + +/** + * Collection util + * + * @since 3.15.0 + * @author yizzuide + *
+ * Create at 2023/01/27 19:10 + */ +class CollectionExtensionsKt { +} + +fun singletonMap(key: String, value: Any): Map = mapOf(key to value) \ No newline at end of file diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/DataTypeConvertUtil.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/DataTypeConvertUtil.java index 25794f8c..fa338b4a 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/DataTypeConvertUtil.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/DataTypeConvertUtil.java @@ -23,6 +23,7 @@ import org.apache.commons.lang3.StringUtils; +import org.springframework.cglib.beans.BeanCopier; import org.springframework.util.CollectionUtils; import java.beans.BeanInfo; @@ -40,7 +41,7 @@ * * @author yizzuide * @since 1.13.0 - * @version 3.14.0 + * @version 3.15.0 *
* Create at 2019/09/21 17:23 */ @@ -135,11 +136,11 @@ public static Integer intVal(Long num) { } /** - * Long to Stream + * Create counter stream * @param count count of number * @return Stream */ - public static LongStream countToStream(int count) { + public static LongStream countToStream(int count) { long[] arr = new long[count]; for (int i = 0; i < count; i++) { arr[i] = i; @@ -223,6 +224,17 @@ public static Map beanToMap(Object object) { return map; } + /** + * Copy field values from source to target. + * @param source source object + * @param target target object + * @since 3.15.0 + */ + public static void copy(Object source, Object target) { + BeanCopier beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false); + beanCopier.copy(source, target, null); + } + /** * 返回排序的Map * @param map 源Map @@ -334,7 +346,7 @@ public static String extractValue(String key, Map source, String /** * 从源对象采集key路径的值 * @param keyPath 采集key - * @param source 源Map + * @param source 采集数据源(支持类型有:Map,Object,JSON字符串) * @param defaultValue 默认值 * @return 采集到的字符串 * @since 3.0.3 @@ -426,7 +438,7 @@ public static String extractPath(String keyPath, Map source, Str * @since 3.0.3 */ public static boolean isSugarType(Object obj) { - return !(obj instanceof List) && !(obj instanceof Map); + return !(obj instanceof Collection) && !(obj instanceof Map); } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/HttpServletUtil.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/HttpServletUtil.java index 0b6c7eee..d86759d3 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/HttpServletUtil.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/HttpServletUtil.java @@ -42,13 +42,8 @@ public class HttpServletUtil { * @return json字符串 */ public static String getRequestData(HttpServletRequest request) { - Map names = new HashMap<>(0); - try { - names = request.getParameterMap(); - } catch (IllegalStateException ignore) { - } Map inputs = new HashMap<>(); - for (Map.Entry paramEntry : names.entrySet()) { + for (Map.Entry paramEntry : request.getParameterMap().entrySet()) { String[] value = paramEntry.getValue(); if (value == null) { inputs.put(paramEntry.getKey(), ""); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/IOUtils.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/IOUtils.java index 199b8391..5e8d96be 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/IOUtils.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/IOUtils.java @@ -34,7 +34,7 @@ * * @author yizzuide * @since 3.3.1 - * @version 3.7.0 + * @version 3.15.0 *
* Create at 2020/05/07 14:17 */ @@ -66,7 +66,7 @@ public static String loadLua(String path, String filename) throws IOException { continue; } // 去空行 - if (line.matches("\\s+")) { + if (line.matches("\\s+") || line.matches("^\\s*[\\r\\n]?")) { continue; } // 代码缩减空白 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/RecognizeUtil.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/RecognizeUtil.java index 54f21919..678bc458 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/RecognizeUtil.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/RecognizeUtil.java @@ -21,11 +21,16 @@ package com.github.yizzuide.milkomeda.util; +import org.springframework.util.ClassUtils; + +import java.beans.Introspector; + /** - * RecognizeUtil + * 类型识别工具类 * * @author yizzuide * @since 1.13.9 + * @version 1.15.0 *
* Create at 2019/10/29 15:49 */ @@ -40,4 +45,15 @@ public static boolean isCompoundType(Object obj) { obj instanceof Short || obj instanceof Integer || obj instanceof Long || obj instanceof Double || obj instanceof Float); } + + /** + * Get bean name from class. + * @param beanClass bean class + * @return bean name + * @since 3.15.0 + */ + public static String getBeanName(Class beanClass) { + String shortClassName = ClassUtils.getShortName(beanClass); + return Introspector.decapitalize(shortClassName); + } } diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/ReflectUtil.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/ReflectUtil.java index 35383164..bfe7a0c0 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/ReflectUtil.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/ReflectUtil.java @@ -46,7 +46,7 @@ * * @author yizzuide * @since 0.2.0 - * @version 3.14.1 + * @version 3.15.0 *
* Create at 2019/04/11 19:55 */ @@ -333,7 +333,9 @@ public static Pair getFieldBundlePath(Object target, String field Field field = null; for (String fieldName : fieldNames) { field = ReflectionUtils.findField(Objects.requireNonNull(target).getClass(), fieldName); - if (field == null) return null; + if (field == null) { + return null; + } ReflectionUtils.makeAccessible(field); target = ReflectionUtils.getField(field, target); } @@ -380,11 +382,19 @@ public static void setFieldPath(Object target, String fieldPath, Object value) { public static void setField(Object target, Map props) { for (Map.Entry entry : props.entrySet()) { Field field = ReflectionUtils.findField(target.getClass(), entry.getKey()); - if (field == null) continue; + if (field == null) { + continue; + } ReflectionUtils.makeAccessible(field); // String -> Enum if (Enum.class.isAssignableFrom(field.getType()) && entry.getValue() instanceof String) { ReflectionUtils.setField(field, target, Enum.valueOf((Class) field.getType(), (String) entry.getValue())); + } else if(Class.class.isAssignableFrom(field.getType()) && entry.getValue() instanceof String) { + try { + ReflectionUtils.setField(field, target, Class.forName(String.valueOf(entry.getValue()))); + } catch (ClassNotFoundException e) { + log.error("Set field error with msg: {}", e.getMessage(), e); + } } else if(Long.class.isAssignableFrom(field.getType()) && entry.getValue() instanceof Integer) { ReflectionUtils.setField(field, target, Long.valueOf(String.valueOf(entry.getValue()))); } else if(List.class.isAssignableFrom(field.getType()) && entry.getValue() instanceof Map) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/StringExtensionsKt.kt b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/StringExtensionsKt.kt index 55dc1e28..3d833651 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/StringExtensionsKt.kt +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/util/StringExtensionsKt.kt @@ -45,7 +45,7 @@ class StringExtensionsKt { fun isEmpty(str : String?) = SpringPolyfill.isEmpty(str) /** - * Object to String,return self if null + * Object to String,return self if null. * @param obj object value * @return string value * @since 3.13.0 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/ApplicationService.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/ApplicationService.java new file mode 100644 index 00000000..8712c6a7 --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/ApplicationService.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.wormhole; + +/** + * A simple interface which indicates as an application service in Domain-Driver Design. + * + * @param the repository type + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/07/14 03:31 + */ +public interface ApplicationService { + + /** + * Get {@link TransactionWorkBus} belong this application service. + * @return TransactionWorkBus + */ + TransactionWorkBus getTransactionWorkBus(); + + /** + * Get repository proxy which accessed under this application service. + * @return repository type + */ + R getRepositoryProxy(); +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/EnableWormhole.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/EnableWormhole.java old mode 100755 new mode 100644 diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ExpressionRootObject.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/TransactionAggsEntity.java similarity index 70% rename from Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ExpressionRootObject.java rename to Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/TransactionAggsEntity.java index 3bf97d0b..d82db199 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/universe/engine/el/ExpressionRootObject.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/TransactionAggsEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 yizzuide All rights Reserved. + * Copyright (c) 2023 yizzuide All rights Reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -19,29 +19,23 @@ * SOFTWARE. */ -package com.github.yizzuide.milkomeda.universe.engine.el; +package com.github.yizzuide.milkomeda.wormhole; import lombok.AllArgsConstructor; -import lombok.Getter; +import lombok.NoArgsConstructor; /** - * ExpressionRootObject - * 自定义EL根对象 + * A base aggregates object of the domain model which provides access to {@link TransactionWorkBus}. * + * @since 3.15.0 * @author yizzuide - * @since 1.5.0 - *
- * Create at 2019/05/30 22:10 + * Create at 2023/07/15 17:44 */ -@Getter +@NoArgsConstructor @AllArgsConstructor -public class ExpressionRootObject { +public abstract class TransactionAggsEntity { /** - * 方法所属目标对象,通过#this.object获取 + * The {@link TransactionWorkBus} is associated with the application service. */ - private final Object object; - /** - * 方法参数列表,通过#this.args[index]获取 - */ - private final Object[] args; -} \ No newline at end of file + protected TransactionWorkBus transactionWorkBus; +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/TransactionWorkBus.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/TransactionWorkBus.java new file mode 100644 index 00000000..9a52846e --- /dev/null +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/TransactionWorkBus.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.wormhole; + +import java.io.Serializable; +import java.util.List; + +/** + * The {@link TransactionWorkBus} interface bridge link {@link ApplicationService}, + * which provide transaction operation for support single user case business in domain. + * + * @since 3.15.0 + * @author yizzuide + * Create at 2023/07/13 11:27 + */ +public interface TransactionWorkBus { + /** + * A save type of transaction. + */ + int TRANSACTION_OPERATION_SAVE = 1; + + /** + * An update type of transaction. + */ + int TRANSACTION_OPERATION_UPDATE = 2; + + /** + * A deleted type of transaction. + */ + int TRANSACTION_OPERATION_DELETE = 3; + + /** + * Find record with key id. + * @param id key id + * @param entityClass entity class + * @return entity record + * @param entity type + */ + T selectById(Serializable id, Class entityClass); + + /** + * Query record list with query example. + * @param queryExample query example + * @param entityClass entity class + * @return entity record list + * @param entity type + */ + List selectList(Object queryExample, Class entityClass); + + /** + * Perform transaction action. + * @param operation transaction operation type + * @param entity data entity + * @param entity type + * @return effect count + */ + int perform(int operation, T entity); + + /** + * Perform batch transaction action. + * @param operation transaction operation type + * @param entities data entity list + * @param entity type + * @return effect count + */ + int performBatch(int operation, List entities); + + /** + * Set application service which work with. + * @param applicationService application service + */ + void setApplicationService(ApplicationService applicationService); + + /** + * Get application service which work with. + * @return application service + * @param application service type + */ + > A getApplicationService(); +} diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeAction.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeAction.java index 30d56a4c..0d58b22c 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeAction.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeAction.java @@ -26,7 +26,6 @@ import java.lang.annotation.*; /** - * DomainAction * 领域事件动作 * * @author yizzuide @@ -52,6 +51,13 @@ @AliasFor("value") String name() default ""; + /** + * Ordered execute on event handler. + * @return order in list + * @since 3.15.0 + */ + int order() default 0; + /** * 事务回调执行 * @return WormholeTransactionType diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeCallback.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeCallback.java index 07ba4bd4..5c8c88fe 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeCallback.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeCallback.java @@ -22,7 +22,6 @@ package com.github.yizzuide.milkomeda.wormhole; /** - * WormholeCallback * 领域事件回调 * * @author yizzuide diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEvent.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEvent.java index 325b9205..ed6df2c0 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEvent.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEvent.java @@ -30,7 +30,6 @@ import java.util.UUID; /** - * DomainEvent * 领域事件 * * @author yizzuide @@ -73,7 +72,7 @@ public class WormholeEvent extends EventObject { private T data; /** - * create with event source + * Create with event source. * @param source event source */ public WormholeEvent(Object source) { @@ -83,7 +82,7 @@ public WormholeEvent(Object source) { } /** - * Common create with event source, tag, data + * Common create with event source, tag, data. * @param source event source * @param tag event tag * @param data event data diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventBus.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventBus.java index b37b275a..663870bd 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventBus.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventBus.java @@ -25,6 +25,7 @@ import com.github.yizzuide.milkomeda.util.ReflectUtil; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.aopalliance.intercept.MethodInvocation; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.CollectionUtils; @@ -37,6 +38,8 @@ * WormholeEventBus * 事件总线 * + * @see org.springframework.transaction.interceptor.TransactionInterceptor#invoke(MethodInvocation) + * @see org.springframework.transaction.interceptor.TransactionAspectSupport * @author yizzuide * @since 3.3.0 * @version 3.11.4 @@ -86,7 +89,10 @@ public void publish(WormholeEvent event, String action, if (hangType == WormholeTransactionHangType.NONE) { execute(isAsync, handler, event, action, callback); } else { - // 注删事务回调 + // 注册事务回调 + if (!TransactionSynchronizationManager.isActualTransactionActive()) { + log.error("Action[{}] on transaction is not active!", action); + } TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void beforeCommit(boolean readOnly) { @@ -117,7 +123,7 @@ public void afterCompletion(int status) { } } - // write into event store + // write event data into infrastructure if (trackers != null) { for (WormholeEventTrack> tracker : trackers) { tracker.track(event); diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventHandler.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventHandler.java index 4b047c0e..19e43cb8 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventHandler.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventHandler.java @@ -26,7 +26,6 @@ import java.lang.annotation.*; /** - * DomainEventHandler * 领域事件处理器 * * @author yizzuide diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventTrack.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventTrack.java index 69c8d43e..c7aafeae 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventTrack.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventTrack.java @@ -22,7 +22,6 @@ package com.github.yizzuide.milkomeda.wormhole; /** - * WormholeEventTrack * 领域事件流跟踪 * * @author yizzuide diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventTracker.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventTracker.java index e8748780..d9192252 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventTracker.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeEventTracker.java @@ -26,7 +26,6 @@ import java.lang.annotation.*; /** - * WormholeEventTracker * 领域事件跟踪器注解 * * @author yizzuide diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeRegistration.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeRegistration.java index d121eed4..31806b94 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeRegistration.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeRegistration.java @@ -30,9 +30,11 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.util.CollectionUtils; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * DomainRegistration @@ -44,6 +46,7 @@ *
* Create at 2020/05/05 14:15 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") public class WormholeRegistration { private static Map> actionMap = new HashMap<>(); @@ -58,6 +61,11 @@ public class WormholeRegistration { */ public static final String ATTR_ASYNC = "async"; + /** + * Event handler order name. + */ + private static final String ATTR_ORDER = "order"; + @Autowired private WormholeEventBus eventBus; @@ -69,10 +77,21 @@ public void onApplicationEvent(@NonNull ContextRefreshedEvent event) { Map attrs = new HashMap<>(4); attrs.put(ATTR_HANG_TYPE, wormholeAction.transactionHang()); attrs.put(ATTR_ASYNC, isAsyncPresentOn); + attrs.put(ATTR_ORDER, wormholeAction.order()); metaData.setAttributes(attrs); return wormholeAction.value(); }, false); + // 排序事件处理器 + if (!CollectionUtils.isEmpty(actionMap)) { + actionMap.keySet().forEach(action -> { + List sortedList = actionMap.get(action).stream() + .sorted(Comparator.comparingInt(metaData -> (Integer) metaData.getAttributes().get(ATTR_ORDER))) + .collect(Collectors.toList()); + actionMap.put(action, sortedList); + }); + } + // 领域事件跟踪处理器 List>> trackers = SpringContext.getTypeHandlers(WormholeEventTracker.class); if (!CollectionUtils.isEmpty(trackers)) { diff --git a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeTransactionHangType.java b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeTransactionHangType.java index ac9f2366..258d46da 100644 --- a/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeTransactionHangType.java +++ b/Milkomeda/src/main/java/com/github/yizzuide/milkomeda/wormhole/WormholeTransactionHangType.java @@ -22,7 +22,7 @@ package com.github.yizzuide.milkomeda.wormhole; /** - * WormholeTransactionType + * Hook transaction type. * * @author yizzuide * @since 3.11.0 diff --git a/Milkomeda/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/Milkomeda/src/main/resources/META-INF/additional-spring-configuration-metadata.json old mode 100755 new mode 100644 diff --git a/Milkomeda/src/main/resources/META-INF/additional.properties b/Milkomeda/src/main/resources/META-INF/additional.properties index 43d897cb..0434d29a 100644 --- a/Milkomeda/src/main/resources/META-INF/additional.properties +++ b/Milkomeda/src/main/resources/META-INF/additional.properties @@ -6,7 +6,11 @@ collections.emptyList=[] # Create date object. collections.dateObject={date} +collections.millsObject={mills} # Calculated value is equality condition.equals= -condition.diff= \ No newline at end of file +condition.diff= + +# SpEL parse supported +el.parse= \ No newline at end of file diff --git a/Milkomeda/src/main/resources/META-INF/scripts/particle_leakyBucket_limiter.lua b/Milkomeda/src/main/resources/META-INF/scripts/particle_leakyBucket_limiter.lua new file mode 100644 index 00000000..b198e6e7 --- /dev/null +++ b/Milkomeda/src/main/resources/META-INF/scripts/particle_leakyBucket_limiter.lua @@ -0,0 +1,28 @@ +local key = KEYS[1] +local bucketCapacity = tonumber(ARGV[1]) +local waterRate = tonumber(ARGV[2]) +local currentMills = tonumber(ARGV[3]) + +local lastTimeKey = key..'_update_time' +local waterCount = redis.call('get', key) +if waterCount then + local lastTime = tonumber(redis.call('get', lastTimeKey)) + -- calc leave water in the past time + waterCount = math.max(0, waterCount - ((currentMills - lastTime) / 1000) * waterRate) + if (waterCount + 1) >= bucketCapacity then + return -1 + end + waterCount = waterCount + 1 + redis.call('set', key, waterCount) + redis.call('set', lastTimeKey, currentMills) + return waterCount +else + -- first time, put 1 request in bucket + waterCount = 1 + redis.call('set', key, waterCount) + -- record last time + redis.call('set', lastTimeKey, currentMills) + return waterCount; +end + + diff --git a/Milkomeda/src/main/resources/META-INF/scripts/particle_rollWindow_limiter.lua b/Milkomeda/src/main/resources/META-INF/scripts/particle_rollWindow_limiter.lua new file mode 100644 index 00000000..de1e96c4 --- /dev/null +++ b/Milkomeda/src/main/resources/META-INF/scripts/particle_rollWindow_limiter.lua @@ -0,0 +1,22 @@ +local key = KEYS[1] +-- expire with second unit +local expire = tonumber(ARGV[1]) +local currentMill = tonumber(ARGV[2]) +-- max limit count +local limitCount = tonumber(ARGV[3]) +-- window start time +local windowStartMill = currentMill - expire * 1000 +-- get count between start and current time +local current = redis.call('zcount', key, windowStartMill, currentMill) +-- if current count over max limit count, return current count +if current and tonumber(current) >= limitCount then + return tonumber(current) +end + +-- clear member before window start time +redis.call('ZREMRANGEBYSCORE', key, 0, windowStartMill) + +-- add and return current count +redis.call('zadd', key, tostring(currentMill), currentMill) +redis.call('expire', key, expire) +return tonumber(current) \ No newline at end of file diff --git a/Milkomeda/src/main/resources/META-INF/scripts/particle_tokenBucket_consume_limiter.lua b/Milkomeda/src/main/resources/META-INF/scripts/particle_tokenBucket_consume_limiter.lua new file mode 100644 index 00000000..37b17226 --- /dev/null +++ b/Milkomeda/src/main/resources/META-INF/scripts/particle_tokenBucket_consume_limiter.lua @@ -0,0 +1,8 @@ +local key = KEYS[1] +local tokenCount = tonumber(redis.call('get', key) or '0') +if tokenCount > 0 then + tokenCount = tonumber(redis.call('decr', key)) + return tokenCount +else + return -1; +end \ No newline at end of file diff --git a/Milkomeda/src/main/resources/META-INF/scripts/particle_tokenBucket_limiter.lua b/Milkomeda/src/main/resources/META-INF/scripts/particle_tokenBucket_limiter.lua new file mode 100644 index 00000000..2218b217 --- /dev/null +++ b/Milkomeda/src/main/resources/META-INF/scripts/particle_tokenBucket_limiter.lua @@ -0,0 +1,42 @@ +local key = KEYS[1] +local bucketCapacity = tonumber(ARGV[1]) +-- number of tokens added each time +local addToken = tonumber(ARGV[2]) +-- token addition interval with second unit +local addInterval = tonumber(ARGV[3]) +local currentMills = tonumber(ARGV[4]) + +-- key of bucket last update time +local lastTimeKey = key..'_update_time' + +local tokenCount = redis.call('get', key) +-- maximum time required for bucket reset +local resetTime = math.ceil(bucketCapacity / addToken) * addInterval; +-- has tokens +if tokenCount then + local lastTime = tonumber(redis.call('get', lastTimeKey)) + -- bucket tokens recovery multiple + local multiple = math.floor((currentMills - lastTime) / addInterval) + local recoveryTokenCount = multiple * addToken + -- must not over the capacity size + tokenCount = math.min(bucketCapacity, tokenCount + recoveryTokenCount) - 1 + if tokenCount < 0 then + return -1 + end + -- reset expire time + redis.call('set', key, tokenCount, 'EX', resetTime) + redis.call('set', lastTimeKey, lastTime + multiple * addInterval, 'EX', resetTime) + return tokenCount +else + -- first time, full tokens in bucket + tokenCount = bucketCapacity -1 + -- set expire time + redis.call('set', key, tokenCount, 'EX', resetTime) + redis.call('set', lastTimeKey, currentMills, 'EX', resetTime) + return tokenCount +end + + + + + diff --git a/Milkomeda/src/main/resources/META-INF/spring.factories b/Milkomeda/src/main/resources/META-INF/spring.factories index 7393bd15..ecc22609 100644 --- a/Milkomeda/src/main/resources/META-INF/spring.factories +++ b/Milkomeda/src/main/resources/META-INF/spring.factories @@ -1,6 +1,9 @@ # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.github.yizzuide.milkomeda.universe.config.MilkomedaAutoConfiguration +# Context Initializer +org.springframework.context.ApplicationContextInitializer=com.github.yizzuide.milkomeda.universe.config.MilkomedaContextInitializer + # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ com.github.yizzuide.milkomeda.universe.extend.env.CustomPropertySourceLocator diff --git a/Milkomeda/src/main/resources/META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports b/Milkomeda/src/main/resources/META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports new file mode 100644 index 00000000..e69de29b diff --git a/MilkomedaDemo/pom.xml b/MilkomedaDemo/pom.xml index 8e058b36..55cdd908 100644 --- a/MilkomedaDemo/pom.xml +++ b/MilkomedaDemo/pom.xml @@ -11,7 +11,7 @@ org.springframework.boot spring-boot-starter-parent - 2.6.12 + 2.7.6 @@ -19,14 +19,16 @@ UTF-8 UTF-8 1.8 - 2021.0.4 - 3.14.1-SNAPSHOT + 2021.0.5 + 3.15.0-SNAPSHOT 2.2.2 3.17.7 3.7.1 + 0.7.5 1.2.13 2.3.4 5.3.2 + 1.6.2 @@ -89,6 +91,11 @@ org.quartz-scheduler quartz + + com.alibaba + druid-spring-boot-starter + ${druid.version} + org.springframework.boot spring-boot-starter-jdbc @@ -138,16 +145,21 @@ + + io.etcd + jetcd-core + ${jetcd.version} + + + com.github.whvcse + easy-captcha + ${easy-captcha.version} + org.projectlombok lombok provided - - com.alibaba - druid-spring-boot-starter - ${druid.version} - org.databene contiperf diff --git a/MilkomedaDemo/src/main/doc/bak.sql b/MilkomedaDemo/src/main/doc/bak.sql index ceada0b6..c4a5dbc8 100644 --- a/MilkomedaDemo/src/main/doc/bak.sql +++ b/MilkomedaDemo/src/main/doc/bak.sql @@ -3,7 +3,8 @@ create schema milkomeda; create schema milkomeda_r; create schema milkomeda_01; --- for milkomeda,milkomeda_r,milkomeda_01 +-- apply to milkomeda, milkomeda_r, milkomeda_01 +drop table if exists t_order; create table t_order ( id bigint not null primary key auto_increment, @@ -18,7 +19,8 @@ create table t_order comment 'order table'; --- for milkomeda +-- apply to milkomeda +drop table if exists t_order_001; create table t_order_001 ( id bigint not null primary key auto_increment, @@ -32,7 +34,8 @@ create table t_order_001 ) comment 'order table'; - +-- apply to milkomeda +drop table if exists job_inspection; create table job_inspection ( id bigint(32) not null, diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/EnableMilkomeda.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/EnableMilkomeda.java index 46bec31c..5ac37118 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/EnableMilkomeda.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/EnableMilkomeda.java @@ -17,6 +17,7 @@ import com.github.yizzuide.milkomeda.particle.EnableParticle; import com.github.yizzuide.milkomeda.pillar.EnablePillar; import com.github.yizzuide.milkomeda.pulsar.EnablePulsar; +import com.github.yizzuide.milkomeda.quark.EnableQuark; import com.github.yizzuide.milkomeda.sundial.EnableSundial; import com.github.yizzuide.milkomeda.wormhole.EnableWormhole; @@ -33,6 +34,7 @@ *
* Create at 2019/12/13 01:03 */ +@EnableQuark @EnableOrbit @EnablePillar @EnableMetal diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/MilkomedaDemoApplication.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/MilkomedaDemoApplication.java index 0cb77c41..aa87b7db 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/MilkomedaDemoApplication.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/MilkomedaDemoApplication.java @@ -4,9 +4,7 @@ import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.PropertySource; -import org.springframework.transaction.annotation.EnableTransactionManagement; /** * MilkomedaDemoApplication @@ -20,8 +18,6 @@ @PropertySource(value = "classpath:conf.properties", encoding = "UTF-8") @PropertySource(value = "classpath:api.yml", name = "api", encoding = "UTF-8", factory = YmlPropertySourceFactory.class) @MapperScan(basePackages = "com.github.yizzuide.milkomeda.demo.*.mapper") -@EnableTransactionManagement -@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) // 开启Servlet组件扫描,如:WebFilter、WebServlet //@ServletComponentScan @SpringBootApplication diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/MilkomedaStartListener.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/MilkomedaStartListener.java index 848d9048..9b0d6a1e 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/MilkomedaStartListener.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/MilkomedaStartListener.java @@ -1,12 +1,15 @@ package com.github.yizzuide.milkomeda.demo; import com.github.yizzuide.milkomeda.metal.MetalHolder; +import com.github.yizzuide.milkomeda.moon.Moon; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Lazy; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; +import javax.annotation.Resource; import java.util.HashMap; import java.util.Map; @@ -21,9 +24,9 @@ @Component public class MilkomedaStartListener implements ApplicationListener { - //@Lazy - //@Resource - //private Moon smsMoon; + @Lazy + @Resource + private Moon smsMoon; @Override public void onApplicationEvent(@NonNull ApplicationStartedEvent event) { @@ -42,7 +45,8 @@ private void loadConfig() { // 从配置动态加载数据 -// smsMoon.add("七牛云短信", "阿里云短信", "容联云短信"); + //smsMoon.add("七牛云短信", "阿里云短信", "容联云短信"); + log.info("加载Moon值:{}", smsMoon.getPhaseNames()); } } diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/WebMvcConfig.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/WebMvcConfig.java index 31abb8d0..ef4f13e4 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/WebMvcConfig.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/WebMvcConfig.java @@ -5,6 +5,7 @@ import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.core.env.PropertySource; import org.springframework.test.context.ActiveProfiles; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -36,7 +37,9 @@ public class WebMvcConfig implements WebMvcConfigurer { @Autowired private WebProperties webProperties; - // Springboot 2.6: Spring Boot现在可以清理 /env 和 /configprops 端点中存在的敏感值 + // Springboot 2.7: SanitizingFunction现已支持Ordered排序,在SanitizableData一量赋值后中止其它调用 + // Springboot 2.6: 现在可以清理 /env 和 /configprops 端点中存在的敏感值 + @Order(Integer.MIN_VALUE) @Bean public SanitizingFunction mysqlSanitizingFunction() { return data -> { diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/atom/SeckillService.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/atom/SeckillService.java index b0e5e4e3..e0bd47d8 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/atom/SeckillService.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/atom/SeckillService.java @@ -18,10 +18,10 @@ public class SeckillService { // 模拟库存 private static int count = 10; - // 支持分布式key参数采集 - // waitTime最多等待时间ms,默认-1直到占有锁 - // leaseTime最多占用锁时间,默认60秒(防止Redis方案的服务突然挂掉时锁无法释放问题,ZK方案不需要管这个设置) - @AtomLock(key = "'seckill_' + #productId", waitTime = 10, leaseTime = 120000, + // 分布式锁 + // waitTime:锁获取等待时间ms,默认-1直到占有锁 + // leaseTime:占用锁时间,默认60秒(防止Redis方案的服务突然挂掉时锁无法释放问题,ZK方案不需要管这个设置) + @AtomLock(key = "'seckill_' + #productId", waitTime = 10, leaseTime = 30000, waitTimeoutType = AtomLockWaitTimeoutType.FALLBACK, fallback = "#target.seckillFail(args[0], args[1])") public boolean seckill(Long userId, Long productId) { if (count > 0) { diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/crust/config/WebSecurityConfig.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/crust/config/WebSecurityConfig.java deleted file mode 100644 index 68ee2fbb..00000000 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/crust/config/WebSecurityConfig.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.yizzuide.milkomeda.demo.crust.config; - -import com.github.yizzuide.milkomeda.crust.CrustConfigurerAdapter; -import com.github.yizzuide.milkomeda.demo.crust.provider.UserDetailsService; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * WebSecurityConfig - * - * @author yizzuide - *
- * Create at 2019/11/11 23:35 - */ -@Configuration -public class WebSecurityConfig extends CrustConfigurerAdapter { - @Bean - @Override - public UserDetailsService userDetailsService() { - return new UserDetailsService(); - } -} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/crust/controller/LoginController.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/crust/controller/LoginController.java index 0c28220f..8db34142 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/crust/controller/LoginController.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/crust/controller/LoginController.java @@ -6,10 +6,21 @@ import com.github.yizzuide.milkomeda.demo.crust.pojo.User; import com.github.yizzuide.milkomeda.hydrogen.uniform.ResultVO; import com.github.yizzuide.milkomeda.hydrogen.uniform.UniformResult; +import com.github.yizzuide.milkomeda.universe.context.WebContext; +import com.wf.captcha.SpecCaptcha; +import com.wf.captcha.base.Captcha; +import com.wf.captcha.utils.CaptchaUtil; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.awt.*; +import java.io.IOException; + /** * LoginController * @@ -20,6 +31,24 @@ @RestController public class LoginController { + @GetMapping("verifyCode/render") + public void render(HttpServletRequest request, HttpServletResponse response) throws IOException, FontFormatException { + // 设置请求头为输出图片类型 + response.setContentType(MediaType.IMAGE_PNG_VALUE); + response.setHeader(HttpHeaders.PRAGMA, "No-cache"); + response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache"); + response.setDateHeader(HttpHeaders.EXPIRES, 0); + + SpecCaptcha captcha = new SpecCaptcha(80, 40, 4); + captcha.setFont(new Font("Arial", Font.PLAIN, 24)); + // 字符+数字组合 + captcha.setCharType(Captcha.TYPE_DEFAULT); + CaptchaUtil.out(captcha, request, WebContext.getRawResponse()); + + // 验证输入 + //CaptchaUtil.ver(code, request); + } + @PostMapping("login") public ResultVO> login(String username, String password) { return UniformResult.ok(CrustContext.get().login(username, password, User.class)); diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/crust/provider/UserDetailsService.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/crust/provider/UserDetailsService.java index 9c47610b..c7859c9d 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/crust/provider/UserDetailsService.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/crust/provider/UserDetailsService.java @@ -5,6 +5,7 @@ import com.github.yizzuide.milkomeda.crust.CrustUserDetailsService; import com.github.yizzuide.milkomeda.demo.crust.pojo.User; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; import java.io.Serializable; import java.util.Collections; @@ -16,6 +17,7 @@ *
* Create at 2019/11/11 23:44 */ +@Service public class UserDetailsService extends CrustUserDetailsService { @Override diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/halo/handler/OrderHandler.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/halo/handler/OrderHandler.java index 27d027f6..5f86a548 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/halo/handler/OrderHandler.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/halo/handler/OrderHandler.java @@ -23,23 +23,23 @@ public class OrderHandler { // 前置监听 @Async // 异步执行 @HaloListener(tableName = "t_order", type = HaloType.PRE) - public void handlePre(Object param, SqlCommandType commandType) {// 散装参数方式 - // param可能是实体类型、简单数据类型或Map - log.info("监听到【t_order】表操作:{},参数:{}", commandType, param); + public void handlePre(Object params, SqlCommandType commandType) { // 散装参数方式 + // params可能是实体类型、简单数据类型或Map + log.info("handlePre - 监听到【t_order】表操作:{},参数:{}", commandType, params); } // 默认监听所有的表 @HaloListener(type = HaloType.PRE) public void handlePreAll(HaloMeta haloMeta) { - log.info("监听到【{}】表操作:{},参数:{}", haloMeta.getTableName(), haloMeta.getSqlCommandType(), + log.info("handlePreAll - 监听到【*{}】表操作:{},参数:{}", haloMeta.getTableName(), haloMeta.getSqlCommandType(), haloMeta.getParam()); } // 后置监听 // 默认type = HaloType.POST @HaloListener - public void handlePostAll(HaloMeta haloMeta) { // 推荐使用封装的参数类型 - log.info("监听到【{}】表操作:{},参数:{}, 结果:{}", haloMeta.getTableName(), haloMeta.getSqlCommandType(), + public void handlePostAll(HaloMeta haloMeta) { + log.info("handlePostAll - 监听到【{}】表操作:{},参数:{}, 结果:{}", haloMeta.getTableName(), haloMeta.getSqlCommandType(), haloMeta.getParam(), haloMeta.getResult()); } } diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/handler/IPLimiterFilter.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/handler/IPLimiterFilter.java index 817d17cf..8e0f2742 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/handler/IPLimiterFilter.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/handler/IPLimiterFilter.java @@ -15,6 +15,7 @@ *
* Create at 2020/04/02 18:14 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j public class IPLimiterFilter implements Filter { @Autowired diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/handler/UrlLogInterceptor.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/handler/UrlLogInterceptor.java index c77e6b18..c024417e 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/handler/UrlLogInterceptor.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/handler/UrlLogInterceptor.java @@ -18,6 +18,7 @@ *
* Create at 2020/03/28 01:08 */ +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Slf4j public class UrlLogInterceptor implements AsyncHandlerInterceptor, InitializingBean { diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/service/TOrderService.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/service/TOrderService.java index e668ef22..ee0c598a 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/service/TOrderService.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/hydrogen/service/TOrderService.java @@ -2,7 +2,11 @@ import com.github.yizzuide.milkomeda.demo.halo.domain.TOrder; import com.github.yizzuide.milkomeda.demo.halo.mapper.TOrderMapper; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; import javax.annotation.Resource; import java.util.Date; @@ -19,6 +23,9 @@ public class TOrderService { @Resource private TOrderMapper tOrderMapper; + @Autowired + private PlatformTransactionManager transactionManager; + public void testTx() { TOrder tOrder = new TOrder(); tOrder.setUserId(101L); @@ -35,11 +42,11 @@ public void testTx() { // int i = 1/0; // 模拟事务超时 - try { + /*try { Thread.sleep(5200); } catch (InterruptedException e) { e.printStackTrace(); - } + }*/ TOrder tOrder2 = new TOrder(); tOrder2.setUserId(102L); @@ -51,4 +58,14 @@ public void testTx() { tOrder2.setUpdateTime(now); tOrderMapper.insert(tOrder2); } + + public void testTransactionByManual() { + TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition()); + try { + // do something... + transactionManager.commit(transaction); + } catch (Exception e) { + transactionManager.rollback(transaction); + } + } } diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/ice/controller/JobController.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/ice/controller/JobController.java index 7ca214aa..2a13cda0 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/ice/controller/JobController.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/ice/controller/JobController.java @@ -49,6 +49,12 @@ public ResultVO> getJobPage(@CometParam UniformQueryPage return UniformResult.ok(ice.getJobInspectPage(queryPageData)); } + // 由于上面ResultVO有框架内部自己的处理,这里用于测试@JsonMinix + @GetMapping("jobPage") + public UniformPage jobPage(@CometParam UniformQueryPageData queryPageData) { + return ice.getJobInspectPage(queryPageData); + } + /** * 获取当前job的详情信息 * @param jobId job id diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/ice/polyfill/UniformPageMixin.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/ice/polyfill/UniformPageMixin.java new file mode 100644 index 00000000..b14ffb4e --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/ice/polyfill/UniformPageMixin.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.demo.ice.polyfill; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.yizzuide.milkomeda.hydrogen.uniform.UniformPage; +import org.springframework.boot.jackson.JsonMixin; + +/** + * UniformPageMixin + * + * @author yizzuide + *
+ * Create at 2022/11/30 19:06 + */ +// Springboot 2.7: Spring Boot的Jackson自动配置将扫描应用程序的包以查找带有@JsonMixin注释的类,并将它们注册到自动配置的ObjectMapper, +// 注册动作由Spring Boot的JsonMixinModule执行 +@JsonMixin(UniformPage.class) +public abstract class UniformPageMixin { + // 混入属性并定制的新名称 + @JsonProperty("pageTotalSize") + Long pageCount; +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/light/pojo/Order.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/light/pojo/Order.java index dc1211f8..6b3e2e41 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/light/pojo/Order.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/light/pojo/Order.java @@ -1,6 +1,5 @@ package com.github.yizzuide.milkomeda.demo.light.pojo; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -16,11 +15,18 @@ */ @Data @NoArgsConstructor -@AllArgsConstructor public class Order { - String orderId; - String name; - String amount; - Integer state; - Date createTime; + private String orderId; + private String name; + private String amount; + private Integer state; + private Date createTime; + + public Order(String orderId, String name, String amount, Integer state, Date createTime) { + this.orderId = orderId; + this.name = name; + this.amount = amount; + this.state = state; + this.createTime = createTime; + } } diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/light/service/OrderService.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/light/service/OrderService.java index 9f902baf..ca6e9e16 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/light/service/OrderService.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/light/service/OrderService.java @@ -23,7 +23,7 @@ public class OrderService { private static final String G_KEY = "order_list"; - @LightCacheable(value = "orders", key = G_KEY) + @LightCacheable(value = "ordersLight", key = G_KEY) public List> findList() { String value = LightContext.getValue("test-id"); log.info("lightContext: {}", value); @@ -44,7 +44,7 @@ public List> findList() { * @param orderId 订单id */ // 参数采集方式生成缓存key - @LightCacheable(value = "order", key = "'order:' + #orderId", condition = "#orderId!=null") + @LightCacheable(value = "orderLight", key = "'order:' + #orderId", condition = "#orderId!=null") // 静态方法生成缓存key // @LightCacheable(value = "order", key = "T(com.github.yizzuide.milkomeda.demo.light.pref.CacheKeys).ORDER.key", condition = "#orderId!=null") public Order findById(String orderId) { @@ -52,12 +52,12 @@ public Order findById(String orderId) { return new Order(orderId, "小明", "1200", 0, new Date()); } - @LightCacheEvict(value = "order", key = "'order:' + #orderId") + @LightCacheEvict(value = "orderLight", key = "'order:' + #orderId") public void deleteById(String orderId) { log.info("删除订单:{}", orderId); } - @LightCachePut(value = "order", key = "'order:' + #orderId", condition = "#orderId!=null") + @LightCachePut(value = "orderLight", key = "'order:' + #orderId", condition = "#orderId!=null") public Order updateById(String orderId) { return new Order(orderId, "小红", "2000", 0, new Date()); } diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/metal/MetalController.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/metal/MetalController.java index a143742a..975acd3a 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/metal/MetalController.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/metal/MetalController.java @@ -48,7 +48,7 @@ public String update(String name) { return "OK"; } - // 监听平台名修改 + // 监听配置的修改 @EventListener(condition = "event.key == 'platform'") public void handlePlatformChange(MetalChangeEvent event) { log.info("handlePlatformChange: {}", event); diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/OrbitController.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/OrbitController.java index 07d3af92..a808dad0 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/OrbitController.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/OrbitController.java @@ -24,4 +24,10 @@ public ResponseEntity findOrder(@PathVariable String orderNo) { orderAPI.fetchOrder(orderNo); return ResponseEntity.ok("OK"); } + + @RecordLog + @RequestMapping("pay/{orderNo}") + public ResponseEntity payOrder(@PathVariable String orderNo) { + return ResponseEntity.ok("OK"); + } } diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/PreLogAdvice.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/PreLogAdvice.java index e936e639..5eacf826 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/PreLogAdvice.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/PreLogAdvice.java @@ -1,6 +1,5 @@ package com.github.yizzuide.milkomeda.demo.orbit; -import com.github.yizzuide.milkomeda.orbit.Orbit; import com.github.yizzuide.milkomeda.orbit.OrbitAdvice; import com.github.yizzuide.milkomeda.orbit.OrbitInvocation; import lombok.extern.slf4j.Slf4j; @@ -13,7 +12,7 @@ * Create at 2022/02/21 01:45 */ @Slf4j -// YAML配置方式或这个注解方式二选其一 +// YAML配置方式(推荐,在功能和扩展性更强大)或@Orbit注解方式 //@Orbit(pointcutExpression = "execution(* com..orbit.*API.fetch*(..))") public class PreLogAdvice implements OrbitAdvice { @Override diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/RecordAdvice.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/RecordAdvice.java new file mode 100644 index 00000000..b6458475 --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/RecordAdvice.java @@ -0,0 +1,21 @@ +package com.github.yizzuide.milkomeda.demo.orbit; + +import com.github.yizzuide.milkomeda.orbit.OrbitAdvice; +import com.github.yizzuide.milkomeda.orbit.OrbitInvocation; +import lombok.extern.slf4j.Slf4j; + +/** + * RecordAdvice + * + * @author yizzuide + *
+ * Create at 2023/01/28 01:34 + */ +@Slf4j +public class RecordAdvice implements OrbitAdvice { + @Override + public Object invoke(OrbitInvocation invocation) throws Throwable { + log.info("拦截支付参数:{}", invocation.getArgs()); + return invocation.proceed(); + } +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/RecordLog.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/RecordLog.java new file mode 100644 index 00000000..b60ae8a3 --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/orbit/RecordLog.java @@ -0,0 +1,18 @@ +package com.github.yizzuide.milkomeda.demo.orbit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * RecordLog + * + * @author yizzuide + *
+ * Create at 2023/01/28 01:33 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RecordLog { +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/particle/controller/ParticleController.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/particle/controller/ParticleController.java index 61cf68ec..f7798d30 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/particle/controller/ParticleController.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/particle/controller/ParticleController.java @@ -59,8 +59,6 @@ public ResponseEntity check(String token) throws Throwable { if (particle.isLimited()) { return ResponseEntity.status(406).body("请求勿重复请求"); } - // 模拟业务处理耗时 - Thread.sleep(5000); return ResponseEntity.ok("ok"); }); } @@ -75,8 +73,6 @@ public ResponseEntity check2(String token, Particle particle/*这个状 if (particle.isLimited()) { return ResponseEntity.status(406).body("请求勿重复请求"); } - // 模拟业务处理耗时 - Thread.sleep(5000); return ResponseEntity.ok("ok"); } @@ -90,10 +86,6 @@ public ResponseEntity check3(Particle particle/*这个状态值自动注 if (particle.isLimited()) { return ResponseEntity.status(406).body("请求勿重复请求"); } - - // 模拟业务处理耗时 - Thread.sleep(5000); - return ResponseEntity.ok("ok"); } @@ -135,9 +127,6 @@ public ResponseEntity verify(String phone) throws Throwable { if (p.isLimited()) { return ResponseEntity.status(406).body("超过使用次数:" + p.getValue() + "次"); } - // 模拟业务处理耗时 - Thread.sleep(5000); - return ResponseEntity.ok("发送成功,当前次数:" + p.getValue()); }); @@ -157,8 +146,6 @@ public ResponseEntity verify3(String phone) throws Throwable { return ResponseEntity.status(406).body("超过使用次数:" + particle.getValue() + "次"); } } - // 模拟业务处理耗时 - Thread.sleep(6000); return ResponseEntity.ok("发送成功,当前次数:" + particle.getValue()); }); @@ -179,9 +166,6 @@ public ResponseEntity verify2(String phone, Particle particle/*这个状 } } - // 模拟业务处理耗时 - Thread.sleep(5000); - return ResponseEntity.ok("发送成功,当前次数:" + particle.getValue()); } @@ -195,9 +179,6 @@ public void notify(@RequestBody Map params, Particle particle, H return; } - // 模拟业务处理耗时 - Thread.sleep(5000); - resp.setStatus(200); resp.getWriter().println("OK"); resp.getWriter().flush(); @@ -205,7 +186,21 @@ public void notify(@RequestBody Map params, Particle particle, H @RequestMapping("pay") public String pay(String orderNo) throws InterruptedException { - Thread.sleep(5000); + return "OK"; + } + + @RequestMapping("sendSMS") + public String sendSMS(String phone) { + return "OK"; + } + + @RequestMapping("quota") + public String quota() { + return "OK"; + } + + @RequestMapping("pull") + public String pull() { return "OK"; } } diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/quark/ChatController.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/quark/ChatController.java new file mode 100644 index 00000000..25d5162e --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/quark/ChatController.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.demo.quark; + +import com.github.yizzuide.milkomeda.pulsar.PulsarHolder; +import com.github.yizzuide.milkomeda.quark.Quarks; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * ChatController + * + * @author yizzuide + * Create at 2023/08/19 14:25 + */ +@Slf4j +@RequestMapping("/chat") +@RestController +public class ChatController { + + @RequestMapping("send") + public String sendQuestion(String question) throws InterruptedException { + // 模拟生成数据 + PulsarHolder.getPulsar().post(() -> { + for (int i = 0; i < 10; i++) { + try { + Thread.sleep(RandomUtils.nextLong(0, 5) * 1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + MessageData data = new MessageData(); + data.setId(RandomUtils.nextInt()); + data.setUserId(1); + data.setMsg(RandomStringUtils.randomAlphabetic(6)); + log.warn("put msg id: {}", data.getId()); + Quarks.bindProducer(data.getUserId().longValue()).publishEventData(data); + } + }); + PulsarHolder.getPulsar().post(() -> { + for (int i = 0; i < 10; i++) { + try { + Thread.sleep(RandomUtils.nextLong(0, 5) * 1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + MessageData data = new MessageData(); + data.setId(RandomUtils.nextInt()); + data.setUserId(2); + data.setMsg(RandomStringUtils.randomAlphabetic(6)); + log.warn("put msg id: {}", data.getId()); + Quarks.bindProducer(data.getUserId().longValue()).publishEventData(data); + } + }); + return "OK"; + } +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/quark/MessageData.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/quark/MessageData.java new file mode 100644 index 00000000..a7542058 --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/quark/MessageData.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.demo.quark; + +import lombok.Data; + +/** + * MessageData + * + * @author yizzuide + * Create at 2023/08/19 14:37 + */ +@Data +public class MessageData { + private Integer id; + private Integer userId; + private String msg; +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/quark/MessageEventHandler.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/quark/MessageEventHandler.java new file mode 100644 index 00000000..49733ad2 --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/quark/MessageEventHandler.java @@ -0,0 +1,23 @@ +package com.github.yizzuide.milkomeda.demo.quark; + +import com.github.yizzuide.milkomeda.quark.QuarkEvent; +import com.github.yizzuide.milkomeda.quark.QuarkEventHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomUtils; +import org.springframework.stereotype.Component; + +/** + * MessageEventHandler + * + * @author yizzuide + * Create at 2023/08/19 14:34 + */ +@Slf4j +@Component +public class MessageEventHandler extends QuarkEventHandler { + @Override + public void onEvent(QuarkEvent event, long sequence, boolean endOfBatch) throws Exception { + Thread.sleep(RandomUtils.nextLong(0, 5) * 1000); + log.info("event: {}, seq: {}, end: {}", event.getData().getId(), sequence, endOfBatch); + } +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/sundial/mapper/TOrder2Mapper.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/sundial/mapper/TOrder2Mapper.java index 4c6a6adf..318a0b4f 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/sundial/mapper/TOrder2Mapper.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/sundial/mapper/TOrder2Mapper.java @@ -16,8 +16,8 @@ public interface TOrder2Mapper { // ShardingType.TABLE:仅分表 - // partExp:分表表达式 - // - table: 内部会解析表名 + // partExp/nodeExp:分表/分库表达式 + // - table: 表名占位符,内部会解析真实的表名(仅用于分表) // - p为参数: // - 当只一个参数,p为这个对象的引用 // - 当为多个参数时,p为一个Map,通过p['key']来获到值 @@ -26,8 +26,9 @@ public interface TOrder2Mapper { // - 自定义序列号截取:seq // - 定制序列号截取: id(需要通过ShardingId类生成) // - 一致性Hash函数:ketama、fnv、murmur + // 参数:key(路由Key)、nodeCount(节点总数)、replicas(复制的虚拟节点) // - 自定义Hash函数:hash - // 1. 调用前注册:CachedConsistentHashRing.getInstance().register("hashName", HashFunc实现); + // 1. 调用前注册:CachedConsistentHashRing.getInstance().register("hashName", HashFunc实例); // 2. 表达式调用:fn.hash("hashName", key, nodeCount, replicas) // @Sundial(shardingType = ShardingType.TABLE, partExp = "fn.format(table + '_%03d', fn.ketama(p.orderNo, 2, 4))") // ShardingType.SCHEMA:仅分库 diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/universe/EarlyConfigLoader.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/universe/EarlyConfigLoader.java new file mode 100644 index 00000000..5f9dae55 --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/universe/EarlyConfigLoader.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.demo.universe; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.PostConstruct; + +/** + * EarlyConfigLoader + * + * @author yizzuide + *
+ * Create at 2023/04/27 22:54 + */ +@Slf4j +public class EarlyConfigLoader { + @PostConstruct + public void init() { + log.info("早期注册Bean初始化"); + } +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/universe/RequestAstrolabeHandler.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/universe/RequestAstrolabeHandler.java index cd25d6ed..81d6c7df 100644 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/universe/RequestAstrolabeHandler.java +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/universe/RequestAstrolabeHandler.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Component; import javax.servlet.ServletRequest; +import java.util.Objects; /** * RequestAstrolabeHandler @@ -20,6 +21,6 @@ public class RequestAstrolabeHandler implements AstrolabeHandler { @Override public void preHandle(ServletRequest request) { // ((HttpServletRequest)request).getRequestURI() - log.info("AstrolabeHandler请求前:{}", WebContext.getRequest().getRequestURI()); + log.info("AstrolabeHandler请求前:{}", Objects.requireNonNull(WebContext.getRequest()).getRequestURI()); } } diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/Actions.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/Actions.java deleted file mode 100644 index f4f87bbc..00000000 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/Actions.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.yizzuide.milkomeda.demo.wormhole; - -/** - * Actions - * - * @author yizzuide - *
- * Create at 2020/05/05 16:01 - */ -public class Actions { - public static final String AUDIT_SUCCESS = "action_credit_audit"; -} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/AuditCommand.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/AuditCommand.java deleted file mode 100644 index 4a579b83..00000000 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/AuditCommand.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.github.yizzuide.milkomeda.demo.wormhole; - -import lombok.Data; - -/** - * AuditCommand - * 外部请求命令 - * - * - * @author yizzuide - *
- * Create at 2020/05/05 15:52 - */ -@Data -public class AuditCommand { - private String callbackId; - private String orderId; - private int state; -} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/AuditController.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/AuditController.java deleted file mode 100644 index d241ddd0..00000000 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/AuditController.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.yizzuide.milkomeda.demo.wormhole; - -import com.github.yizzuide.milkomeda.demo.wormhole.service.CreditAuditApplicationService; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; - -/** - * AuditController - * 领域适配器 - * - * @author yizzuide - *
- * Create at 2020/05/05 15:36 - */ -@RestController -@RequestMapping("audit") -public class AuditController { - - @Resource - private CreditAuditApplicationService creditAuditApplicationService; - - // http://localhost:8091/audit/callback?callbackId=123&orderId=12432434&state=0 - @RequestMapping("callback") - public Object audit(AuditCommand auditCommand) { - creditAuditApplicationService.audit(auditCommand); - return "OK"; - } -} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/Credit.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/Credit.java deleted file mode 100644 index 940e58da..00000000 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/Credit.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.yizzuide.milkomeda.demo.wormhole; - -import lombok.Data; - -/** - * Credit - * 领域业务模块(聚合根) - * - * @author yizzuide - *
- * Create at 2020/05/05 15:51 - */ -@Data -public class Credit { - private String orderId; - private Long userId; - private Long quota; - - /** - * 修改额度 - * @param quota 更新额度 - */ - public void updateQuota(Long quota) { - this.setQuota(quota); - } -} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/EventStreamRepository.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/EventStreamRepository.java deleted file mode 100644 index e1558dc8..00000000 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/EventStreamRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.github.yizzuide.milkomeda.demo.wormhole; - -import com.github.yizzuide.milkomeda.wormhole.WormholeEvent; -import com.github.yizzuide.milkomeda.wormhole.WormholeEventTracker; -import com.github.yizzuide.milkomeda.wormhole.WormholeEventTrack; -import lombok.extern.slf4j.Slf4j; - -/** - * EventStreamRepository - * 领域事件流仓储 - * - * @author yizzuide - *
- * Create at 2020/05/05 15:42 - */ -@Slf4j -@WormholeEventTracker -public class EventStreamRepository implements WormholeEventTrack> { - @Override - public void track(WormholeEvent event) { - log.info("存储事件到大数据流:{}", event); - } -} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/QuotaService.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/QuotaService.java deleted file mode 100644 index c8d73cc3..00000000 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/QuotaService.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.yizzuide.milkomeda.demo.wormhole; - -import com.github.yizzuide.milkomeda.wormhole.WormholeAction; -import com.github.yizzuide.milkomeda.wormhole.WormholeEvent; -import com.github.yizzuide.milkomeda.wormhole.WormholeEventHandler; -import com.github.yizzuide.milkomeda.wormhole.WormholeTransactionHangType; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; - -/** - * QuotaService - * 领域远程服务 - * - * @author yizzuide - *
- * Create at 2020/05/05 15:39 - */ -@Slf4j -@Service -@WormholeEventHandler -public class QuotaService { - - @Async - @WormholeAction(value = Actions.AUDIT_SUCCESS, transactionHang = WormholeTransactionHangType.AFTER_COMMIT) - public void onEvent(WormholeEvent event) { - // 更新额度 - event.getData().updateQuota(20000L); - log.info("审核后修改用户额度: {}", event); - } -} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/appearance/aciton/Actions.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/appearance/aciton/Actions.java new file mode 100644 index 00000000..5fe02395 --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/appearance/aciton/Actions.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.demo.wormhole.appearance.aciton; + +/** + * 业务流程的事件动作 + * + * @author yizzuide + *
+ * Create at 2020/05/05 16:01 + */ +public class Actions { + public static final String AUDIT_SUCCESS = "action_credit_audit"; +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/appearance/command/AuditCommand.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/appearance/command/AuditCommand.java new file mode 100644 index 00000000..4a472f40 --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/appearance/command/AuditCommand.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.demo.wormhole.appearance.command; + +import lombok.Data; + +/** + * AuditCommand + * 外部请求命令 + * + * @author yizzuide + *
+ * Create at 2020/05/05 15:52 + */ +@Data +public class AuditCommand { + private String callbackId; + private String orderId; + private int state; +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/appearance/controller/AuditController.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/appearance/controller/AuditController.java new file mode 100644 index 00000000..d4ac956e --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/appearance/controller/AuditController.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.demo.wormhole.appearance.controller; + +import com.github.yizzuide.milkomeda.demo.wormhole.appearance.command.AuditCommand; +import com.github.yizzuide.milkomeda.demo.wormhole.application.CreditApplicationService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * AuditController + * 领域适配器 + * + * @author yizzuide + *
+ * Create at 2020/05/05 15:36 + */ +@RestController +@RequestMapping("audit") +public class AuditController { + + @Resource + private CreditApplicationService creditApplicationService; + + // http://localhost:8091/audit/callback?callbackId=123&orderId=12432434&state=0 + @RequestMapping("callback") + public Object audit(AuditCommand auditCommand) { + creditApplicationService.audit(auditCommand); + return "OK"; + } +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/application/CreditApplicationService.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/application/CreditApplicationService.java new file mode 100644 index 00000000..64d75d31 --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/application/CreditApplicationService.java @@ -0,0 +1,97 @@ +package com.github.yizzuide.milkomeda.demo.wormhole.application; + +import com.github.yizzuide.milkomeda.demo.wormhole.appearance.aciton.Actions; +import com.github.yizzuide.milkomeda.demo.wormhole.appearance.command.AuditCommand; +import com.github.yizzuide.milkomeda.demo.wormhole.domain.model.Credit; +import com.github.yizzuide.milkomeda.wormhole.WormholeEvent; +import com.github.yizzuide.milkomeda.wormhole.WormholeHolder; +import org.springframework.stereotype.Service; + +/** + * 信用应用服务 + * + * @author yizzuide + *
+ * Create at 2020/05/05 15:38 + */ +@Service +public class CreditApplicationService { + + /* + * DDD设计与实现: + * 1. DDD是一种架构设计方法论,通过边界划分将复杂业务领域简单化来实现架构演进。 + * 2. 建立一个核心而稳定的领域模型,有利于领域知识的传递与传承。 + * 3. 领域会细分为不同的子域,子域可以根据自身重要性和功能属性划分为三类子域: + * 1)核心域:决定产品和公司核心竞争力的子域是核心域。 + * 2)通用域:没有太多个性化的诉求通用系统(短信、认证、日志),同时被多个子域使用的通用功能。 + * 3)支撑域:具有企业特性,但不具有通用性系统(数据字典,站内信)。 + * 4. 事件风暴是建立领域模型的主要方法,它是一个从发散(产生很多的实体、命令、事件等领域对象)到收敛(建立领域模型与限界上下文)的过程。 + * 5. 在事件风暴过程中,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言,而这个语言所在的语义环境则是由限界上下文来限定的,以确保语义的唯一性。 + * 6. 通用语言中的名词可以给领域对象命名,如商品、订单等,对应实体对象;而动词则表示一个动作或事件,如商品已下单、订单已付款等,对应领域事件或者命令。 + * 7. 限界上下文(BoundedContext):限界就是领域的边界,而上下文则是语义环境,用来封装通用语言和领域对象。一个限界上下文里通常有多个聚合,聚合逻辑上是相对独立的。 + * 1)在DDD实践中,聚合是事务的边界;聚合之间并不保证事务,只能用最终一致性。任何需要事务保护的逻辑都应该在一个聚合内。 + * 2)在限界上下文里,将其他聚合能力整合在一起对外提供能力的聚合,被称为聚合根;其他聚合也被称为实体。 + * 8. 限界上下文确定了微服务的设计和拆分方向,一般来说一个限界上下文拆分成一个微服务,但还需要考虑服务的粒度、分层、边界划分、依赖关系和集成关系。 + * 9. 实体(Entity):拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致,领域模型中的实体是多个属性、操作或行为的载体,它和值对象是组成领域模型的基础单元。 + * 10. 在事件风暴中,我们可以根据命令、操作或者事件,找出产生这些行为的业务实体对象,进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类,形成聚合。 + * 11. 在DDD里,这些实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现,跨多个实体的领域逻辑则在领域服务(DomainService)中实现。 + * 12. 与传统数据模型设计优先不同,DDD是先构建领域模型,针对实际业务场景构建实体对象和行为,再将实体对象映射到数据持久化对象。 + * 13. 一个实体可能对应0个或多个持久化对象,但大多是一对一、一对多和多对一的关系,而有些实体只是暂驻静态内存(如:多个价格配置数据计算后生成的折扣实体)。 + * 14. 值对象(ValueObject):通过对象属性值来识别的对象(一个没有标识符的对象),它将多个相关属性组合为一个概念整体,且是不可变的。 + * 15. 如果值对象是单一属性,则直接定义为实体类的属性;如果值对象是属性集合,则把它设计为复合类型。 + * 16. 聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,里面一定有一个实体是聚合根。聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。 + * 17. 聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。 + * 18. 聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,封装真正的不变性实现对象数据的一致性。 + * 19. 聚合在DDD分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现(包括微服务中)。 + * 20. 聚合根(AggregateRoot):如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。 + * 21. 聚合根还是聚合对外的接口人,以聚合根ID关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。 + * 22. 如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差。 + * 23. 聚合之间是通过关联外部聚合根ID的方式引用,而不是直接对象引用的方式,因为直接对象容易造成边界不清晰,也会增加聚合之间的耦合度。 + * 24. 聚合内数据强一致性,而聚合之间数据最终一致性。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的方式异步修改相关的聚合,实现聚合之间的解耦。 + * 25. 领域事件:一个领域事件将导致进一步的业务操作,也可能是定时批处理过程中发生的事件。在实现业务解耦的同时,还有助于形成完整的业务闭环。 + * 26. 领域事件的执行需要一系列的组件和技术来支撑,包括:事件构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理等。 + * 27. 事件基本属性至少包括:事件唯一标识、发生时间、事件类型和事件源。还有一项是业务属性,记录事件发生那一刻的业务数据,它会随事件传输到订阅方,以开展下一步的业务操作。 + * 28. 领域事件发生后,事件中的业务数据不再修改,因此业务数据可以以序列化值对象的形式保存,这种存储格式在消息中间件中也比较容易解析和获取。 + * 29. 事件发布的方式可以通过应用服务或者领域服务发布到事件总线或者消息中间件,也可以从事件表中利用定时程序或数据库日志捕获技术获取增量事件数据,发布到消息中间件。 + * 30. 事件数据持久化可用于系统之间的数据对账,或者实现发布方和订阅方事件数据的审计。 + * 31. 事件数据持久化有两种方案: + * 1)持久化到本地业务数据库的事件表中,利用本地事务保证业务和事件数据的一致性。 + * 2)持久化到共享的事件数据库中。它们的数据持久化操作会跨数据库,因此需要分布式事务机制来保证业务和事件数据的强一致性。 + * 32. 事件总线(EventBus):事件总线是实现微服务内聚合之间领域事件的重要组件,它提供事件分发和接收等服务。 + * 33. 事件分发流程: + * 1)如果是微服务内的订阅者(其它聚合),则直接分发到指定订阅者; + * 2)如果是微服务外的订阅者,将事件数据保存到事件库(表)并异步发送到消息中间件; + * 3)如果同时存在微服务内和外订阅者,则先分发到内部订阅者,将事件消息保存到事件库(表),再异步发送到消息中间件。 + * 34. 用户接口层:还需要考虑服务的粒度、分层、边界划分、依赖关系和集成关系。 + * 35. 应用层:应用层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。 + * 1)应用层位于领域层之上,而领域层包含多个聚合,所以它负责协调多个聚合的服务和领域对象完成服务编排和组合,负责处理业务用例的执行顺序以及结果的拼装。 + * 2)应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。 + * 3)应用服务还可以进行安全认证、权限校验、事务控制、发送或订阅领域事件等。 + * 36. 领域层:包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。 + * 1)领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。 + * 2)领域模型的业务逻辑主要是由实体和领域服务来实现的,其中实体会采用充血模型来实现所有与之相关的业务功能。 + * 3)实体和领域服务在实现业务逻辑上不是同级的,当领域中的某些功能,单一实体(或者值对象)不能实现时,领域服务就会出马, + * 它可以组合聚合内的多个实体(或者值对象),实现复杂的业务逻辑。如:多个领域对象作为输入值,结果产生一个值对象。 + * 4)领域包含限界上下文,限界上下文包含子域,子域包含聚合,聚合包含实体和值对象。 + * 37. 基础层:基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。 + * 38. DDD分层架构有一个重要的原则:每层只能与位于其下方的层发生耦合。 + */ + + + // 审核完成回调(一个User Case在Application Service中对应一个处理方法) + public void audit(AuditCommand auditCommand) { + // 审核成功 + if (auditCommand.getState() == 0) { + // 保存订单状态... + + // 模拟从Repository查询用户信用聚合根 + Credit credit = new Credit(); + credit.setOrderId(auditCommand.getOrderId()); + credit.setUserId(1001L); + + // 发送审核成功事件 + WormholeHolder.getEventBus().publish(new WormholeEvent<>(this, "audit", credit), Actions.AUDIT_SUCCESS); + } + + } +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/domain/model/Credit.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/domain/model/Credit.java new file mode 100644 index 00000000..2ca6517c --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/domain/model/Credit.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.demo.wormhole.domain.model; + +import lombok.Data; + +/** + * 信用聚合根 + * + * @author yizzuide + *
+ * Create at 2020/05/05 15:51 + */ +@Data +public class Credit { + private String orderId; + private Long userId; + private Long quota; + + /** + * 修改额度 + * @param quota 更新额度 + */ + public void updateQuota(Long quota) { + this.setQuota(quota); + } +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/domain/service/QuotaService.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/domain/service/QuotaService.java new file mode 100644 index 00000000..c78ad357 --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/domain/service/QuotaService.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.demo.wormhole.domain.service; + +import com.github.yizzuide.milkomeda.demo.wormhole.appearance.aciton.Actions; +import com.github.yizzuide.milkomeda.demo.wormhole.domain.model.Credit; +import com.github.yizzuide.milkomeda.wormhole.WormholeAction; +import com.github.yizzuide.milkomeda.wormhole.WormholeEvent; +import com.github.yizzuide.milkomeda.wormhole.WormholeEventHandler; +import com.github.yizzuide.milkomeda.wormhole.WormholeTransactionHangType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +/** + * 额度领域服务 + * + * @author yizzuide + *
+ * Create at 2020/05/05 15:39 + */ +@Slf4j +@Service +@WormholeEventHandler +public class QuotaService { + + @Async + @WormholeAction(value = Actions.AUDIT_SUCCESS, transactionHang = WormholeTransactionHangType.AFTER_COMMIT) + public void onEvent(WormholeEvent event) { + // 调用信用聚合根,更新额度 + event.getData().updateQuota(20000L); + log.info("审核后修改用户额度: {}", event); + + // 调用其它实体聚合... + + } +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/infrastructure/repository/EventStreamRepository.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/infrastructure/repository/EventStreamRepository.java new file mode 100644 index 00000000..7bfcb249 --- /dev/null +++ b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/infrastructure/repository/EventStreamRepository.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 yizzuide All rights Reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.yizzuide.milkomeda.demo.wormhole.infrastructure.repository; + +import com.github.yizzuide.milkomeda.wormhole.WormholeEvent; +import com.github.yizzuide.milkomeda.wormhole.WormholeEventTracker; +import com.github.yizzuide.milkomeda.wormhole.WormholeEventTrack; +import lombok.extern.slf4j.Slf4j; + +/** + * EventStreamRepository + * 领域事件流仓储 + * + * @author yizzuide + *
+ * Create at 2020/05/05 15:42 + */ +@Slf4j +@WormholeEventTracker +public class EventStreamRepository implements WormholeEventTrack> { + @Override + public void track(WormholeEvent event) { + log.info("存储事件到大数据流:{}", event); + } +} diff --git a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/service/CreditAuditApplicationService.java b/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/service/CreditAuditApplicationService.java deleted file mode 100644 index ea309694..00000000 --- a/MilkomedaDemo/src/main/java/com/github/yizzuide/milkomeda/demo/wormhole/service/CreditAuditApplicationService.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.yizzuide.milkomeda.demo.wormhole.service; - -import com.github.yizzuide.milkomeda.demo.wormhole.Actions; -import com.github.yizzuide.milkomeda.demo.wormhole.AuditCommand; -import com.github.yizzuide.milkomeda.demo.wormhole.Credit; -import com.github.yizzuide.milkomeda.wormhole.WormholeEvent; -import com.github.yizzuide.milkomeda.wormhole.WormholeHolder; -import org.springframework.stereotype.Service; - -/** - * CreditAuditApplicationService - * 信用审核应用服务 - * - * @author yizzuide - *
- * Create at 2020/05/05 15:38 - */ -@Service -public class CreditAuditApplicationService { - - // 审核完成回调(一个User Case在Application Service中对应一个处理方法) - public void audit(AuditCommand auditCommand) { - // 审核成功 - if (auditCommand.getState() == 0) { - // 保存订单状态... - - // 模拟从Repository查询用户信用聚合根 - Credit credit = new Credit(); - credit.setOrderId(auditCommand.getOrderId()); - credit.setUserId(1001L); - - // 发送审核成功事件 - WormholeHolder.getEventBus().publish(new WormholeEvent<>(this, "audit", credit), Actions.AUDIT_SUCCESS); - } - - } -} diff --git a/MilkomedaDemo/src/main/resources/application-dev.yml b/MilkomedaDemo/src/main/resources/application-dev.yml index f4fab710..40639e29 100644 --- a/MilkomedaDemo/src/main/resources/application-dev.yml +++ b/MilkomedaDemo/src/main/resources/application-dev.yml @@ -1,20 +1,20 @@ server: port: 8091 tomcat: - # Springboot 2.3: 设置服务器工作线程 + # Spring Boot 2.3: 设置服务器工作线程 threads: max: 120 - # Springboot 2.4: Configure how long Tomcat will wait for another request before closing a keep-alive connection. + # Spring Boot 2.4: Configure how long Tomcat will wait for another request before closing a keep-alive connection. keep-alive-timeout: 30s - # Springboot 2.4: Control the maximum number of requests that can be made on a keep-alive connection before it is closed + # Spring Boot 2.4: Control the maximum number of requests that can be made on a keep-alive connection before it is closed max-keep-alive-requests: 120 spring: config: - # Springboot 2.4: 根据当前环境变量来激活当前配置 + # Spring Boot 2.4: 根据当前环境变量来激活当前配置 activate: on-profile: dev - # Springboot 2.4: 导入数据配置文件给当前配置 + # Spring Boot 2.4: 导入数据配置文件给当前配置 import: "classpath:develop.properties" datasource: driver-class-name: com.mysql.cj.jdbc.Driver @@ -25,8 +25,11 @@ spring: redis: host: 127.0.0.1 + # Spring Boot 2.7: Support for specifying a username for authenticating to Sentinel(s) + #sentinel: + #username: - # Springboot 2.6: To use embedded mongo, the spring.mongodb.embedded.version property must now be set. + # Spring Boot 2.6: To use embedded mongo, the spring.mongodb.embedded.version property must now be set. #mongodb: #embedded: #version: 3.6 @@ -38,7 +41,7 @@ spring: port: 27017 - # Pulsar模块异步线程池(Async异步任务线程池,SpringBoot 2.1.0+在这里配置) + # Pulsar模块异步线程池(Async异步任务线程池,Spring Boot 2.1.0+在这里配置) task: execution: thread-name-prefix: pulsar- @@ -48,7 +51,7 @@ spring: max-size: 12 keep-alive: 100s - # Ice模块的调度线程池(Spring Task调度线程池,SpringBoot 2.1.0+在这里配置) + # Ice模块的调度线程池(Spring Task调度线程池,Spring Boot 2.1.0+在这里配置) scheduling: thread-name-prefix: ice- pool: @@ -72,7 +75,7 @@ logging: level: com.github.yizzuide.milkomeda.demo.sundial.mapper: DEBUG com.github.yizzuide.milkomeda.ice.inspector.mapper: DEBUG - # Springboot 2.4: The charsets used to by Logback and Log4j logging can now be configured + # Spring Boot 2.4: The charsets used to by Logback and Log4j logging can now be configured charset: console: UTF-8 file: UTF-8 @@ -85,21 +88,24 @@ management: enabled: false health: show-details: always - # Springboot 2.4: A new startup actuator endpoint is now available that shows information about your application startup. + # Spring Boot 2.4: A new startup actuator endpoint is now available that shows information about your application startup. startup: enabled: true endpoints: web: exposure: include: '*' - # Springboot 2.4: HTTP traces no longer include cookie headers by default. + # Spring Boot 2.4: HTTP traces no longer include cookie headers by default. #trace: #http: #include: cookies, errors, request-headers, response-headers info: - # Springboot 2.6: The env info contributor is now disabled by default. It would like them to appear them in the InfoEndpoint. + # Spring Boot 2.6: The env info contributor is now disabled by default. It would like them to appear them in the InfoEndpoint. env: enabled: true - # Springboot 2.6: Expose Java Runtime information under the java key. + # Spring Boot 2.6: Expose Java Runtime information under the java key. java: enabled: true + # Spring Boot 2.7: An OsInfoContributor can expose some information about the Operating System the application is running on. + os: + enabled: on diff --git a/MilkomedaDemo/src/main/resources/application-milkomeda.yml b/MilkomedaDemo/src/main/resources/application-milkomeda.yml index 7d76660e..3a1436b1 100644 --- a/MilkomedaDemo/src/main/resources/application-milkomeda.yml +++ b/MilkomedaDemo/src/main/resources/application-milkomeda.yml @@ -9,12 +9,35 @@ milkomeda: # 自定义业务参数配置(可选,非框架使用) env: version: 1.0 + # 配置需要早期注册的Bean + register-early-beans: + - com.github.yizzuide.milkomeda.demo.universe.EarlyConfigLoader comet: # 读取请求body里的json数据需要开启(默认不开启) enable-read-request-body: true - # 读取Response直接写出的内容时需要开启 + # 读取Response的Body时需要开启(默认不开启) enable-read-response-body: true + # 请求拦截器(拦截器名是通过@Alias("xxx")) + request-interceptors: + # XSS注入过滤 + xss: + enable: true + order: 1 + include-urls: [ "/collect/usage" ] + props: + # Form表单字段名白名单 + whiteFieldNames: ["content", "*WithHtml"] + # SQL注入过滤 + sql-inject: + enable: true + order: 2 + include-urls: [ "/collect/usage" ] + # 响应拦截器(拦截器名是通过@Alias("xxx")) + response-interceptors: + uniform: + enable: true + # 统一请求日志打印 logger: enable: true @@ -28,7 +51,7 @@ milkomeda: tpl: url->{uri},userId:{$x.uid},token:{$header.token:0},method->{method},parameters->{params},dataId->{$params.data.id} - paths: ["/collect/usage"] type: response # 设置打印为响应类型(默认为请求类型) - tpl: "{\"url\": \"{uri}\", \"resp\": {resp}}" + tpl: '{url:"{uri}",resp:{resp}}' - paths: ["/error/**"] tpl: url->{uri},method->{method} # 日志收集器 @@ -38,7 +61,7 @@ milkomeda: enable-tag: true tags: product-tag: - include: ["/collect/product/*"] + include-urls: ["/collect/product/*"] # 异常监控:由于异常可能被 @ControllerAdvice 吞没,拦截器就获取不到异常 exception-monitor: # 忽略正常的响应,业务标识字段为code @@ -73,6 +96,46 @@ milkomeda: message[msg]: 请求超过次数,请稍后再试! addition: data: ${collections.emptyMap} + # 滑动窗口限制器 + - name: rollLimiter + type: roll_window + # 60秒内只允许访问一次 + props: + limitTimes: 1 + key-expire: 60s + key-tpl: limit_sms_{$params.phone} + include-urls: [ "/particle/sendSMS" ] + response: + status: 200 + code: -1 + message[msg]: 请求超过次数,请稍后再试! + # 令牌桶限制器 + - name: tokenLimiter + type: token_bucket + props: + bucketCapacity: 10 + tokensPerTime: 2 + interval: 5 + key-tpl: limit_token_quota + include-urls: [ "/particle/quota" ] + response: + status: 200 + code: -1 + message[msg]: 当前额度有限,请稍后再试! + # 漏桶限制器 + - name: leakyLimiter + type: leaky_bucket + props: + # 桶可装10个请求 + bucketCapacity: 10 + # 1秒处理2个请求(速率恒定) + waterRate: 2 + key-tpl: limit_leaky_pull + include-urls: [ "/particle/pull" ] + response: + status: 200 + code: -1 + message[msg]: 当前拉取频繁,请稍后再试! - name: barrierLimiter type: barrier props: @@ -102,16 +165,16 @@ milkomeda: enable-light-thread-local-scope: true instances: # 缓存实例配置(没有配置的使用默认值) - order: + orderLight: # 缓存策略 strategy: LazyExpire # 不需要ThreadLocal缓存 enable-super-cache: false l1-expire: 1m l2-expire: 1m - #smsMoonCache: + #smsMoonCache: #only-cache-l2: true - #abTestMoonCache: + #abTestMoonCache: #only-cache-l2: true echo: @@ -140,6 +203,7 @@ milkomeda: - /actuator - /actuator/** - /s_login + - /verifyCode/render - /collect/** - /echo/** - /test/** @@ -161,6 +225,8 @@ milkomeda: - /metal/** - /pillar/** - /orbit/** + - /chat/** + # 放开static资源访问 allow-static-urls: - /build/* @@ -169,9 +235,9 @@ milkomeda: ice: # 在分布式布署时,需要设置实例名 instance-name: ${spring.application.name} - # 开启Job作业(作为消费端使用时,设置为false) + # 开启Job服务(作为消费端使用时,设置为false) #enable-job-timer: false - # 是否用于分布式job作业 + # 是否用于分布式job服务(当服务端采用分布式时) #enable-job-timer-distributed: true # Job作业频率(默认5s) delay-bucket-poll-rate: 2s @@ -187,7 +253,7 @@ milkomeda: task-topic-pop-max-size: 5 # 消费轮询间隔(默认5s) task-execute-rate: 2s - # 消费处理器支持多个监听器 + # 消费处理器支持多个监听器(模拟订阅模式) multi-topic-listener-per-handler: true # TTR超载任务进入Dead queue enable-retain-to-dead-queue-when-ttr-overload: true @@ -196,7 +262,7 @@ milkomeda: enable: true # 查询监测数据使用的时间排序类型(默认为update_time:每次状态修改都更新时间) index-type: update_time - # 监测数据存储方案(默认为redis) + # 监测数据存储方案(默认为redis,只有redis方案不支持在任务结束后保存记录) inspector-type: mongodb moon: @@ -303,7 +369,10 @@ milkomeda: filter: #enable: true # 测试表达式计算 - enable: ${condition.equals(${spring.application.name}, milkomeda-demo)} + # condition仅支持简单方法(内部非表达达解析):equals, diff + #enable: ${condition.equals(${spring.application.name}, milkomeda-demo)} + # el用于支持SpEL复杂语句解析,但不支持获取Bean来调用方法(受限于YML解析比创建Bean实例早的问题) + enable: ${el.parse('milkomeda-demo'.equals('${spring.application.name}'), BOOL)} filters: - name: ipLimiterFilter clazz: com.github.yizzuide.milkomeda.demo.hydrogen.handler.IPLimiterFilter @@ -313,7 +382,17 @@ milkomeda: # 分布式锁 atom: - strategy: redis # 默认的策略为redis + # 使用方案类型,支持目前的主流方案:Etcd、Redis、Zookeeper + #strategy: redis + # 策略配置(Redis的连接使用Springboot的配置) + #redis: + # 是否使用集群 + #use-cluster: false + + strategy: etcd + etcd: + endpoint-url: http://127.0.0.1:3379 + root-lock-node: /mk/lock ## 数据源配置 sundial: @@ -342,7 +421,7 @@ milkomeda: original-name-as-index-zero: true # 分库、分库分表需要的节点配置(仅分表不需要配置) nodes: - # 一个主、从配置为一个节点,节点名与拆分key相关(从库可选配置) + # 一个主、从配置为一个节点(节点索引以0开始),节点索引与路由key相关(从库可选配置) node_0: # spring.datasource配置的默认会生成master数据源实例 leader: master @@ -387,5 +466,12 @@ milkomeda: orbit: instances: - key-name: pre-log + advice-clazz: com.github.yizzuide.milkomeda.demo.orbit.PreLogAdvice + # 设置切点表达式(默认advisor-clazz为AspectJ类型) pointcut-expression: execution(* com..orbit.*API.fetch*(..)) - advice-class-name: com.github.yizzuide.milkomeda.demo.orbit.PreLogAdvice \ No newline at end of file + - key-name: pay-log + advice-clazz: com.github.yizzuide.milkomeda.demo.orbit.RecordAdvice + # 该策略为方法注解方式(基于Spring Ioc和Aop底层API实现) + advisor-clazz: com.github.yizzuide.milkomeda.orbit.AnnotationOrbitAdvisor + advisor-props: + methodAnnotationType: com.github.yizzuide.milkomeda.demo.orbit.RecordLog \ No newline at end of file diff --git a/MilkomedaDemo/src/main/resources/application-web.yml b/MilkomedaDemo/src/main/resources/application-web.yml index 71354605..14137733 100644 --- a/MilkomedaDemo/src/main/resources/application-web.yml +++ b/MilkomedaDemo/src/main/resources/application-web.yml @@ -1,5 +1,5 @@ server: - # Springboot 2.6: spring.webflux.session property group move to server.reactive.session + # Spring Boot 2.6: spring.webflux.session property group move to server.reactive.session reactive: session: timeout: 120m @@ -13,33 +13,36 @@ server: http-only: true # 自定义sessionId,用于后台登录唯一标识,防止被第三方平台覆盖 name: mk-sessionId - # Springboot 2.6: SameSite Cookie Attribute Servlet Support + # Spring Boot 2.6: SameSite Cookie Attribute Servlet Support same-site: lax # strict: 完全禁止第三方获取cookie lax: 第三方请求只有和GET表单发送Cookie none: 只能通过https发送 # Spring Boot 2.4: No longer register the DefaultServlet provided by your servlet container. register-default-servlet: true - # Springboot 2.3: 设置服务器编码 + # Spring Boot 2.3: 设置服务器编码 encoding: charset: UTF-8 - # Springboot 2.3: 优雅关机,拒绝新请求,等正在执行的请求完成 + # Spring Boot 2.3: 优雅关机,拒绝新请求,等正在执行的请求完成 shutdown: graceful - # Springboot 2.3: 不再响应默认的错误信息 + # Spring Boot 2.3: 不再响应默认的错误信息 error: include-binding-errors: never include-message: never include-stacktrace: never - - + # Spring Boot 2.7: Embedded web servers can be configured to use SSL with PEM-encoded certificate and private key files + # Management endpoints can be secured using similar management.server.ssl.* properties + #ssl: + #certificate: + #certificate-private-key: spring: - # Springboot 2.3: 优雅关机等待时间 + # Spring Boot 2.3: 优雅关机等待时间 lifecycle: timeout-per-shutdown-phase: 20s main: - # Springboot 2.1: Bean重复定义是否覆盖 + # Spring Boot 2.1: Bean重复定义是否覆盖 allow-bean-definition-overriding: false - # Springboot 2.6: Circular references between beans are now prohibited by default. + # Spring Boot 2.6: Circular references between beans are now prohibited by default. #allow-circular-references: true web: - # Springboot 2.4: 资源不生成mapping映射 + # Spring Boot 2.4: 资源不生成mapping映射 resources: add-mappings: false @@ -48,7 +51,7 @@ spring: async: request-timeout: 20s - # Springboot 2.6: 默认的路径匹配器由AntPathMatcher改为PathPatternParser(包括Actuator,但matching-strategy不作用于Actuator),匹配根路径必须添加"/" + # Spring Boot 2.6: 默认的路径匹配器由AntPathMatcher改为PathPatternParser(包括Actuator,但matching-strategy不作用于Actuator),匹配根路径必须添加"/" #pathmatch: #matching-strategy: ant_path_matcher diff --git a/MilkomedaDemo/src/main/resources/application.yml b/MilkomedaDemo/src/main/resources/application.yml index 0bd05b9e..1492f885 100644 --- a/MilkomedaDemo/src/main/resources/application.yml +++ b/MilkomedaDemo/src/main/resources/application.yml @@ -1,14 +1,14 @@ spring: application: name: @spring.application.name@ - # Springboot2.4:不能用在指定配置环境,但仍然可以用于激活环境及命令行激活,如:--spring.profiles.active=prod + # Spring Boot 2.4:不能用在指定配置环境,但仍然可以用于激活环境及命令行激活,如:--spring.profiles.active=prod profiles: active: @spring.profiles.active@ - # Springboot2.4:include不能用于指定配置环境(一般用于无环境依赖的必须配置),不能用在指定配置环境文件里 + # Spring Boot 2.4:include不能用于指定配置环境(一般用于无环境依赖的必须配置),不能用在指定配置环境文件里 include: web - # Springboot2.4:group可以多环境配置多个子配置,子配置需要声明:spring.config.activate.on-profile + # Spring Boot 2.4:group可以多环境配置多个子配置,子配置需要声明:spring.config.activate.on-profile group: dev: milkomeda - # Springboot 2.4: 使用了新配置文件合并机制,以下可以配置为2.3以前的方式 + # Spring Boot 2.4: 使用了新配置文件合并机制,以下可以配置为2.3以前的方式 #config: #use-legacy-processing: true