JeecgBoot2.4.3版本发布——企业级低代码平台

This commit is contained in:
zhangdaiscott
2021-03-17 18:43:42 +08:00
parent 47ea38038d
commit 76e7ee6c09
131 changed files with 3851 additions and 43841 deletions

View File

@ -14,7 +14,7 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DistributedLock {
public @interface JLock {
/**
* 锁的模式:如果不设置,自动模式,当参数只有一个.使用 REENTRANT 参数多个 MULTIPLE
@ -28,8 +28,7 @@ public @interface DistributedLock {
String[] lockKey() default {};
/**
* key的静态常量:当key的spel的值是LIST,数组时使用+号连接将会被spel认为这个变量是个字符串,只能产生一把锁,达不到我们的目的,<br />
* 而我们如果又需要一个常量的话.这个参数将会在拼接在每个元素的后面
* key的静态常量:当key的spel的值是LIST,数组时使用+号连接将会被spel认为这个变量是个字符串
* @return
*/
String keyConstant() default "";

View File

@ -0,0 +1,36 @@
package org.jeecg.boot.starter.lock.annotation;
/**
* @author zyf
*/
import java.lang.annotation.*;
/**
* 防止重复提交的注解
*
* @author 2019年6月18日
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface JRepeat {
/**
* 超时时间
*
* @return
*/
int lockTime();
/**
* redis 锁key的
*
* @return redis 锁key
*/
String lockKey() default "";
}

View File

@ -0,0 +1,72 @@
package org.jeecg.boot.starter.lock.annotation;
/**
* @author zyf
* @date 2019/10/26 18:26
*/
/**
* 分布式锁枚举类
* @author zyf
*/
public enum LockConstant {
/**
* 通用锁常量
*/
COMMON("commonLock:", 1, 500, "请勿重复点击");
/**
* 分布式锁前缀
*/
private String keyPrefix;
/**
* 等到最大时间,强制获取锁
*/
private int waitTime;
/**
* 锁失效时间
*/
private int leaseTime;
/**
* 加锁提示
*/
private String message;
LockConstant(String keyPrefix, int waitTime, int leaseTime, String message) {
this.keyPrefix = keyPrefix;
this.waitTime = waitTime;
this.leaseTime = leaseTime;
this.message = message;
}
public String getKeyPrefix() {
return keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
public int getWaitTime() {
return waitTime;
}
public void setWaitTime(int waitTime) {
this.waitTime = waitTime;
}
public int getLeaseTime() {
return leaseTime;
}
public void setLeaseTime(int leaseTime) {
this.leaseTime = leaseTime;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,67 @@
package org.jeecg.boot.starter.lock.aspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.ArrayList;
import java.util.List;
/**
* @author zyf
*/
@Slf4j
public class BaseAspect {
/**
* 通过spring SpEL 获取参数
*
* @param key 定义的key值 以#开头 例如:#user
* @param parameterNames 形参
* @param values 形参值
* @param keyConstant key的常亮
* @return
*/
public List<String> getValueBySpEL(String key, String[] parameterNames, Object[] values, String keyConstant) {
List<String> keys = new ArrayList<>();
if (!key.contains("#")) {
String s = "redis:lock:" + key + keyConstant;
log.info("lockKey:" + s);
keys.add(s);
return keys;
}
//spel解析器
ExpressionParser parser = new SpelExpressionParser();
//spel上下文
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], values[i]);
}
Expression expression = parser.parseExpression(key);
Object value = expression.getValue(context);
if (value != null) {
if (value instanceof List) {
List value1 = (List) value;
for (Object o : value1) {
addKeys(keys, o, keyConstant);
}
} else if (value.getClass().isArray()) {
Object[] obj = (Object[]) value;
for (Object o : obj) {
addKeys(keys, o, keyConstant);
}
} else {
addKeys(keys, value, keyConstant);
}
}
log.info("表达式key={},value={}", key, keys);
return keys;
}
private void addKeys(List<String> keys, Object o, String keyConstant) {
keys.add("redis:lock:" + o.toString() + keyConstant);
}
}

View File

@ -6,7 +6,7 @@ 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.jeecg.boot.starter.lock.annotation.DistributedLock;
import org.jeecg.boot.starter.lock.annotation.JLock;
import org.jeecg.boot.starter.lock.enums.LockModel;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonRedLock;
@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit;
@Slf4j
@Aspect
@Component
public class DistributedLockHandler {
public class DistributedLockHandler extends BaseAspect{
@Autowired(required = false)
@ -45,20 +45,20 @@ public class DistributedLockHandler {
* 切面环绕通知
*
* @param joinPoint
* @param distributedLock
* @param jLock
* @return Object
*/
@SneakyThrows
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
@Around("@annotation(jLock)")
public Object around(ProceedingJoinPoint joinPoint, JLock jLock) {
Object obj = null;
log.info("进入RedisLock环绕通知...");
RLock rLock = getLock(joinPoint, distributedLock);
RLock rLock = getLock(joinPoint, jLock);
boolean res = false;
//获取超时时间
long expireSeconds = distributedLock.expireSeconds();
long expireSeconds = jLock.expireSeconds();
//等待多久,n秒内获取不到锁则直接返回
long waitTime = distributedLock.waitTime();
long waitTime = jLock.waitTime();
//执行aop
if (rLock != null) {
try {
@ -85,20 +85,20 @@ public class DistributedLockHandler {
}
@SneakyThrows
private RLock getLock(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
String[] keys = distributedLock.lockKey();
private RLock getLock(ProceedingJoinPoint joinPoint, JLock jLock) {
String[] keys = jLock.lockKey();
if (keys.length == 0) {
throw new RuntimeException("keys不能为空");
}
String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
Object[] args = joinPoint.getArgs();
LockModel lockModel = distributedLock.lockModel();
LockModel lockModel = jLock.lockModel();
if (!lockModel.equals(LockModel.MULTIPLE) && !lockModel.equals(LockModel.REDLOCK) && keys.length > 1) {
throw new RuntimeException("参数有多个,锁模式为->" + lockModel.name() + ".无法锁定");
}
RLock rLock = null;
String keyConstant = distributedLock.keyConstant();
String keyConstant = jLock.keyConstant();
if (lockModel.equals(LockModel.AUTO)) {
if (keys.length > 1) {
lockModel = LockModel.REDLOCK;
@ -162,57 +162,6 @@ public class DistributedLockHandler {
rLock = redissonClient.getReadWriteLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0)).writeLock();
break;
}
return rLock;
}
/**
* 通过spring SpEL 获取参数
*
* @param key 定义的key值 以#开头 例如:#user
* @param parameterNames 形参
* @param values 形参值
* @param keyConstant key的常亮
* @return
*/
public List<String> getValueBySpEL(String key, String[] parameterNames, Object[] values, String keyConstant) {
List<String> keys = new ArrayList<>();
if (!key.contains("#")) {
String s = "redis:lock:" + key + keyConstant;
log.info("lockKey:" + s);
keys.add(s);
return keys;
}
//spel解析器
ExpressionParser parser = new SpelExpressionParser();
//spel上下文
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], values[i]);
}
Expression expression = parser.parseExpression(key);
Object value = expression.getValue(context);
if (value != null) {
if (value instanceof List) {
List value1 = (List) value;
for (Object o : value1) {
addKeys(keys, o, keyConstant);
}
} else if (value.getClass().isArray()) {
Object[] obj = (Object[]) value;
for (Object o : obj) {
addKeys(keys, o, keyConstant);
}
} else {
addKeys(keys, value, keyConstant);
}
}
log.info("表达式key={},value={}", key, keys);
return keys;
}
private void addKeys(List<String> keys, Object o, String keyConstant) {
keys.add("redis:lock:" + o.toString() + keyConstant);
}
}

View File

@ -0,0 +1,80 @@
package org.jeecg.boot.starter.lock.aspect;
/**
* @author zyf
*/
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.aspectj.lang.reflect.MethodSignature;
import org.jeecg.boot.starter.lock.annotation.JRepeat;
import org.jeecg.boot.starter.lock.client.RedissonLockClient;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* 防止重复提交分布式锁拦截器
*
* @author 2019年6月18日
*/
@Aspect
@Component
public class RepeatSubmitAspect extends BaseAspect{
@Resource
private RedissonLockClient redissonLockClient;
/***
* 定义controller切入点拦截规则拦截JRepeat注解的业务方法
*/
@Pointcut("@annotation(jRepeat)")
public void pointCut(JRepeat jRepeat) {
}
/**
* AOP分布式锁拦截
*
* @param joinPoint
* @return
* @throws Exception
*/
@Around("pointCut(jRepeat)")
public Object repeatSubmit(ProceedingJoinPoint joinPoint,JRepeat jRepeat) throws Throwable {
String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
if (Objects.nonNull(jRepeat)) {
// 获取参数
Object[] args = joinPoint.getArgs();
// 进行一些参数的处理比如获取订单号操作人id等
StringBuffer lockKeyBuffer = new StringBuffer();
String key =getValueBySpEL(jRepeat.lockKey(), parameterNames, args,"RepeatSubmit").get(0);
// 公平加锁lockTime后锁自动释放
boolean isLocked = false;
try {
isLocked = redissonLockClient.fairLock(key, TimeUnit.SECONDS, jRepeat.lockTime());
// 如果成功获取到锁就继续执行
if (isLocked) {
// 执行进程
return joinPoint.proceed();
} else {
// 未获取到锁
throw new Exception("请勿重复提交");
}
} finally {
// 如果锁还存在,在方法执行完成后,释放锁
if (isLocked) {
redissonLockClient.unlock(key);
}
}
}
return joinPoint.proceed();
}
}

View File

@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@ -21,6 +22,9 @@ public class RedissonLockClient {
@Autowired
private RedissonClient redissonClient;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 获取锁
*/
@ -60,6 +64,25 @@ public class RedissonLockClient {
return getLock;
}
public boolean fairLock(String lockKey, TimeUnit unit, int leaseTime) {
RLock fairLock = redissonClient.getFairLock(lockKey);
try {
boolean existKey = existKey(lockKey);
// 已经存在了,就直接返回
if (existKey) {
return false;
}
return fairLock.tryLock(3, leaseTime, unit);
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
public boolean existKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 锁lockKey
*
@ -92,7 +115,11 @@ public class RedissonLockClient {
* @param lockName 锁名称
*/
public void unlock(String lockName) {
redissonClient.getLock(lockName).unlock();
try {
redissonClient.getLock(lockName).unlock();
} catch (Exception e) {
log.error("解锁异常lockName=" + lockName, e);
}
}

View File

@ -0,0 +1,59 @@
package org.jeecg.boot.starter.lock.test;
import org.jeecg.boot.starter.lock.annotation.JLock;
import org.jeecg.boot.starter.lock.annotation.JRepeat;
import org.jeecg.boot.starter.lock.annotation.LockConstant;
import org.jeecg.boot.starter.lock.client.RedissonLockClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class LockService {
@Resource
private RedissonLockClient redissonLockClient;
int n = 10;
/**
* 模拟秒杀(注解方式)
*/
@JLock(lockKey = "#productId", expireSeconds = 5000)
public void seckill(String productId) {
if (n <= 0) {
System.out.println("活动已结束,请下次再来");
return;
}
System.out.println(Thread.currentThread().getName() + ":秒杀到了商品");
System.out.println(--n);
}
/**
* 模拟秒杀(编程方式)
*/
public void seckill2(String productId) {
redissonLockClient.tryLock(productId, 5000);
if (n <= 0) {
System.out.println("活动已结束,请下次再来");
return;
}
System.out.println(Thread.currentThread().getName() + ":秒杀到了商品");
System.out.println(--n);
redissonLockClient.unlock(productId);
}
/**
* 测试重复提交
*/
@JRepeat(lockKey = "#name", lockTime = 5)
public void reSubmit(String name) {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("提交成功" + name);
}
}

View File

@ -0,0 +1,67 @@
package org.jeecg.boot.starter.lock.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LockTestApplication.class)
public class LockTest {
@Autowired
LockService lockService;
/**
* 测试分布式锁(模拟秒杀)
*/
@Test
public void test1() throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(6);
IntStream.range(0, 30).forEach(i -> executorService.submit(() -> {
try {
lockService.seckill("20120508784");
} catch (Exception e) {
e.printStackTrace();
}
}));
executorService.awaitTermination(30, TimeUnit.SECONDS);
}
/**
* 测试分布式锁(模拟秒杀)
*/
@Test
public void test2() throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(6);
IntStream.range(0, 30).forEach(i -> executorService.submit(() -> {
try {
lockService.seckill2("20120508784");
} catch (Exception e) {
e.printStackTrace();
}
}));
executorService.awaitTermination(30, TimeUnit.SECONDS);
}
/**
* 测试分布式锁(模拟重复提交)
*/
@Test
public void test3() throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(6);
IntStream.range(0, 20).forEach(i -> executorService.submit(() -> {
try {
lockService.reSubmit("test");
} catch (Exception e) {
e.printStackTrace();
}
}));
executorService.awaitTermination(30, TimeUnit.SECONDS);
}
}

View File

@ -0,0 +1,15 @@
package org.jeecg.boot.starter.lock.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication(scanBasePackages = "org.jeecg")
@EnableAspectJAutoProxy
public class LockTestApplication {
public static void main(String[] args) {
SpringApplication.run(LockTestApplication.class, args);
}
}

View File

@ -0,0 +1,9 @@
package org.jeecg.boot.starter.lock.test;
import lombok.Data;
@Data
public class TestUser {
private String userId;
private String userName;
}

View File

@ -0,0 +1,19 @@
spring:
redis:
database: 0
host: 127.0.0.1
lettuce:
pool:
max-active: 8 #最大连接数据库连接数,设 0 为没有限制
max-idle: 8 #最大等待连接中的数量,设 0 为没有限制
max-wait: -1ms #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
min-idle: 0 #最小等待连接中的数量,设 0 为没有限制
shutdown-timeout: 100ms
password: jeecg
port: 6379
jeecg :
redisson:
address: 127.0.0.1:6379
password: jeecg
type: STANDALONE
enabled: true