mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-31 01:01:27 +08:00
JeecgBoot2.4.3版本发布——企业级低代码平台
This commit is contained in:
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>jeecg-boot-starter</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>2.4.2</version>
|
||||
<version>2.4.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jeecg-boot-starter-cloud</artifactId>
|
||||
|
||||
@ -7,9 +7,11 @@ import feign.codec.Encoder;
|
||||
import feign.form.spring.SpringFormEncoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.config.FeignConfig;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
@ -19,7 +21,6 @@ import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>jeecg-boot-starter</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>2.4.2</version>
|
||||
<version>2.4.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jeecg-boot-starter-job</artifactId>
|
||||
|
||||
@ -30,11 +30,14 @@ public class XxlJobConfiguration {
|
||||
@ConditionalOnClass()
|
||||
public XxlJobSpringExecutor xxlJobExecutor() {
|
||||
log.info(">>>>>>>>>>> xxl-job config init.");
|
||||
//log.info(">>>> ip="+xxlJobProperties.getIp()+",Port="+xxlJobProperties.getPort()+",address="+xxlJobProperties.getAdminAddresses());
|
||||
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
|
||||
xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdminAddresses());
|
||||
xxlJobSpringExecutor.setAppname(xxlJobProperties.getAppname());
|
||||
//update-begin--Author:scott -- Date:20210305 -- for:system服务和demo服务有办法同时使用xxl-job吗 #2313---
|
||||
//xxlJobSpringExecutor.setIp(xxlJobProperties.getIp());
|
||||
//xxlJobSpringExecutor.setPort(xxlJobProperties.getPort());
|
||||
//update-end--Author:scott -- Date:20210305 -- for:system服务和demo服务有办法同时使用xxl-job吗 #2313---
|
||||
xxlJobSpringExecutor.setAccessToken(xxlJobProperties.getAccessToken());
|
||||
xxlJobSpringExecutor.setLogPath(xxlJobProperties.getLogPath());
|
||||
xxlJobSpringExecutor.setLogRetentionDays(xxlJobProperties.getLogRetentionDays());
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>jeecg-boot-starter</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>2.4.2</version>
|
||||
<version>2.4.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jeecg-boot-starter-lock</artifactId>
|
||||
|
||||
@ -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 "";
|
||||
@ -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 "";
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package org.jeecg.boot.starter.lock.test;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TestUser {
|
||||
private String userId;
|
||||
private String userName;
|
||||
}
|
||||
@ -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
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>jeecg-boot-starter</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>2.4.2</version>
|
||||
<version>2.4.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jeecg-boot-starter-rabbitmq</artifactId>
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>jeecg-boot-starter</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>2.4.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jeecg-boot-starter-redis</artifactId>
|
||||
<description>redis插件</description>
|
||||
<dependencies>
|
||||
<!-- SpringBoot Boot Redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -1,44 +0,0 @@
|
||||
package org.jeecg.boot.starter.redis.client;
|
||||
|
||||
import org.jeecg.common.base.BaseMap;
|
||||
import org.jeecg.common.constant.GlobalConstants;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* redis客户端
|
||||
*/
|
||||
@Configuration
|
||||
public class JeecgRedisClient {
|
||||
|
||||
@Resource(name = "starterRedisTemplate")
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param handlerName
|
||||
* @param params
|
||||
*/
|
||||
public void sendMessage(String handlerName, BaseMap params) {
|
||||
params.put(GlobalConstants.HANDLER_NAME, handlerName);
|
||||
redisTemplate.convertAndSend(GlobalConstants.REDIS_TOPIC_NAME, params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据key查询缓存
|
||||
*
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
public <T> T get(String key) {
|
||||
return key == null ? null : (T) redisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,95 +0,0 @@
|
||||
package org.jeecg.boot.starter.redis.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.boot.starter.redis.prop.JeecgRedisProperties;
|
||||
import org.jeecg.boot.starter.redis.service.RedisReceiver;
|
||||
import org.jeecg.common.constant.GlobalConstants;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
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.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* redis配置
|
||||
*
|
||||
* @author jeecg
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(JeecgRedisProperties.class)
|
||||
@ConditionalOnProperty(value = "spring.redis.enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class RedisConfiguration {
|
||||
|
||||
|
||||
/**
|
||||
* RedisTemplate配置
|
||||
*
|
||||
* @param lettuceConnectionFactory
|
||||
* @return
|
||||
*/
|
||||
@Bean("starterRedisTemplate")
|
||||
public RedisTemplate<String, Object> starterRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
|
||||
log.info(" --- redis config init --- ");
|
||||
// 设置序列化
|
||||
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
jackson2JsonRedisSerializer.setObjectMapper(om);
|
||||
// 配置redisTemplate
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
|
||||
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
|
||||
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
|
||||
redisTemplate.setKeySerializer(stringSerializer);// key序列化
|
||||
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
|
||||
redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
|
||||
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* redis 监听配置
|
||||
*
|
||||
* @param redisConnectionFactory redis 配置
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory, RedisReceiver redisReceiver, MessageListenerAdapter commonListenerAdapter) {
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(redisConnectionFactory);
|
||||
container.addMessageListener(commonListenerAdapter, new ChannelTopic(GlobalConstants.REDIS_TOPIC_NAME));
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessageListenerAdapter commonListenerAdapter(RedisReceiver redisReceiver) {
|
||||
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(redisReceiver, "onMessage");
|
||||
messageListenerAdapter.setSerializer(jacksonSerializer());
|
||||
return messageListenerAdapter;
|
||||
}
|
||||
|
||||
private Jackson2JsonRedisSerializer jacksonSerializer() {
|
||||
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
|
||||
return jackson2JsonRedisSerializer;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
package org.jeecg.boot.starter.redis.listener;
|
||||
|
||||
import org.jeecg.common.base.BaseMap;
|
||||
|
||||
/**
|
||||
* 自定义消息监听
|
||||
*/
|
||||
public interface JeecgRedisListerer {
|
||||
|
||||
void onMessage(BaseMap message);
|
||||
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
package org.jeecg.boot.starter.redis.prop;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* redis配置
|
||||
*
|
||||
* @author pangu
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ConfigurationProperties(JeecgRedisProperties.PREFIX)
|
||||
public class JeecgRedisProperties {
|
||||
/**
|
||||
* 前缀
|
||||
*/
|
||||
public static final String PREFIX = "spring.redis";
|
||||
/**
|
||||
* 是否开启Lettuce
|
||||
*/
|
||||
private Boolean enable = true;
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package org.jeecg.boot.starter.redis.service;
|
||||
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.Data;
|
||||
import org.jeecg.boot.starter.redis.listener.JeecgRedisListerer;
|
||||
import org.jeecg.common.base.BaseMap;
|
||||
import org.jeecg.common.constant.GlobalConstants;
|
||||
import org.jeecg.common.util.SpringContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@Data
|
||||
public class RedisReceiver {
|
||||
|
||||
|
||||
/**
|
||||
* 接受消息并调用业务逻辑处理器
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
public void onMessage(BaseMap params) {
|
||||
Object handlerName = params.get(GlobalConstants.HANDLER_NAME);
|
||||
JeecgRedisListerer messageListener = SpringContextHolder.getHandler(handlerName.toString(), JeecgRedisListerer.class);
|
||||
if (ObjectUtil.isNotEmpty(messageListener)) {
|
||||
messageListener.onMessage(params);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.jeecg.boot.starter.redis.config.RedisConfiguration
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-parent</artifactId>
|
||||
<version>2.4.2</version>
|
||||
<version>2.4.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jeecg-boot-starter</artifactId>
|
||||
@ -20,7 +20,6 @@
|
||||
<module>jeecg-boot-starter-job</module>
|
||||
<module>jeecg-boot-starter-lock</module>
|
||||
<module>jeecg-boot-starter-rabbitmq</module>
|
||||
<module>jeecg-boot-starter-redis</module>
|
||||
</modules>
|
||||
<dependencies>
|
||||
<!--jeecg-tools-->
|
||||
|
||||
Reference in New Issue
Block a user