代码初始化提交。
This commit is contained in:
+60
@@ -0,0 +1,60 @@
|
||||
[{
|
||||
"id": "jero-system",
|
||||
"order": 0,
|
||||
"predicates": [{
|
||||
"name": "Path",
|
||||
"args": {
|
||||
"_genkey_0": "/sys/**",
|
||||
"_genkey_1": "/eoa/**",
|
||||
"_genkey_2": "/joa/**",
|
||||
"_genkey_3": "/jmreport/**",
|
||||
"_genkey_4": "/bigscreen/**",
|
||||
"_genkey_5": "/desform/**",
|
||||
"_genkey_6": "/online/**",
|
||||
"_genkey_8": "/act/**",
|
||||
"_genkey_9": "/plug-in/**",
|
||||
"_genkey_10": "/generic/**",
|
||||
"_genkey_11": "/v1/**"
|
||||
}
|
||||
}],
|
||||
"filters": [],
|
||||
"uri": "lb://jero-system"
|
||||
}, {
|
||||
"id": "jero-demo",
|
||||
"order": 1,
|
||||
"predicates": [{
|
||||
"name": "Path",
|
||||
"args": {
|
||||
"_genkey_0": "/mock/**",
|
||||
"_genkey_1": "/test/**",
|
||||
"_genkey_2": "/bigscreen/template1/**",
|
||||
"_genkey_3": "/bigscreen/template2/**"
|
||||
}
|
||||
}],
|
||||
"filters": [],
|
||||
"uri": "lb://jero-demo"
|
||||
}, {
|
||||
"id": "jero-system-websocket",
|
||||
"order": 2,
|
||||
"predicates": [{
|
||||
"name": "Path",
|
||||
"args": {
|
||||
"_genkey_0": "/websocket/**",
|
||||
"_genkey_1": "/eoaSocket/**",
|
||||
"_genkey_2": "/newsWebsocket/**"
|
||||
}
|
||||
}],
|
||||
"filters": [],
|
||||
"uri": "lb:ws://jero-system"
|
||||
}, {
|
||||
"id": "jero-demo-websocket",
|
||||
"order": 3,
|
||||
"predicates": [{
|
||||
"name": "Path",
|
||||
"args": {
|
||||
"_genkey_0": "/vxeSocket/**"
|
||||
}
|
||||
}],
|
||||
"filters": [],
|
||||
"uri": "lb:ws://jero-demo"
|
||||
}]
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
# 标准配置的模板,可自行删减
|
||||
server:
|
||||
tomcat:
|
||||
max-swallow-size: -1
|
||||
error:
|
||||
include-exception: true
|
||||
include-stacktrace: ALWAYS
|
||||
include-message: ALWAYS
|
||||
compression:
|
||||
enabled: true
|
||||
min-response-size: 1024
|
||||
mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
|
||||
management:
|
||||
health:
|
||||
mail:
|
||||
enabled: false
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*" #暴露所有节点
|
||||
health:
|
||||
sensitive: true #关闭过滤敏感信息
|
||||
endpoint:
|
||||
health:
|
||||
show-details: ALWAYS #显示详细信息
|
||||
spring:
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 10MB
|
||||
max-request-size: 10MB
|
||||
mail:
|
||||
host: smtp.163.com
|
||||
username: test@163.com
|
||||
password: ??
|
||||
properties:
|
||||
mail:
|
||||
smtp:
|
||||
auth: true
|
||||
starttls:
|
||||
enable: true
|
||||
required: true
|
||||
## quartz定时任务,采用数据库方式
|
||||
quartz:
|
||||
job-store-type: jdbc
|
||||
initialize-schema: embedded
|
||||
#定时任务启动开关,true-开 false-关
|
||||
auto-startup: true
|
||||
#启动时更新己存在的Job
|
||||
overwrite-existing-jobs: true
|
||||
properties:
|
||||
org:
|
||||
quartz:
|
||||
scheduler:
|
||||
instanceName: MyScheduler
|
||||
instanceId: AUTO
|
||||
jobStore:
|
||||
class: org.quartz.impl.jdbcjobstore.JobStoreTX
|
||||
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
|
||||
tablePrefix: QRTZ_
|
||||
isClustered: true
|
||||
misfireThreshold: 60000
|
||||
clusterCheckinInterval: 10000
|
||||
threadPool:
|
||||
class: org.quartz.simpl.SimpleThreadPool
|
||||
threadCount: 10
|
||||
threadPriority: 5
|
||||
threadsInheritContextClassLoaderOfInitializingThread: true
|
||||
#json 时间戳统一转换
|
||||
jackson:
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
time-zone: GMT+8
|
||||
jpa:
|
||||
open-in-view: false
|
||||
activiti:
|
||||
check-process-definitions: false
|
||||
#启用作业执行器
|
||||
async-executor-activate: false
|
||||
#启用异步执行器
|
||||
job-executor-activate: false
|
||||
aop:
|
||||
proxy-target-class: true
|
||||
#配置freemarker
|
||||
freemarker:
|
||||
# 设置模板后缀名
|
||||
suffix: .ftl
|
||||
# 设置文档类型
|
||||
content-type: text/html
|
||||
# 设置页面编码格式
|
||||
charset: UTF-8
|
||||
# 设置页面缓存
|
||||
cache: false
|
||||
prefer-file-system-access: false
|
||||
# 设置ftl文件路径
|
||||
template-loader-path:
|
||||
- classpath:/templates
|
||||
# 设置静态文件路径,js,css等
|
||||
mvc:
|
||||
static-path-pattern: /**
|
||||
resource:
|
||||
static-locations: classpath:/static/,classpath:/public/
|
||||
autoconfigure:
|
||||
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
|
||||
datasource:
|
||||
druid:
|
||||
stat-view-servlet:
|
||||
enabled: true
|
||||
loginUsername: druid
|
||||
loginPassword: druid
|
||||
allow:
|
||||
web-stat-filter:
|
||||
enabled: true
|
||||
dynamic:
|
||||
druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
|
||||
# 连接池的配置信息
|
||||
# 初始化大小,最小,最大
|
||||
initial-size: 5
|
||||
min-idle: 5
|
||||
maxActive: 20
|
||||
# 配置获取连接等待超时的时间
|
||||
maxWait: 60000
|
||||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||
timeBetweenEvictionRunsMillis: 60000
|
||||
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||
minEvictableIdleTimeMillis: 300000
|
||||
validationQuery: SELECT 1
|
||||
testWhileIdle: true
|
||||
testOnBorrow: false
|
||||
testOnReturn: false
|
||||
# 打开PSCache,并且指定每个连接上PSCache的大小
|
||||
poolPreparedStatements: true
|
||||
maxPoolPreparedStatementPerConnectionSize: 20
|
||||
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
|
||||
filters: stat,wall,slf4j
|
||||
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
|
||||
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000;druid.stat.logSlowSql\=true
|
||||
datasource:
|
||||
master:
|
||||
url: jdbc:mysql://127.0.0.1:3306/jero-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: 123456
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://127.0.0.1:3306/jero-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
|
||||
# username: root
|
||||
# password: 123456
|
||||
# driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
# 多数据源配置
|
||||
#multi-datasource1:
|
||||
#url: jdbc:mysql://localhost:3306/jero-boot2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
|
||||
#username: root
|
||||
#password: root
|
||||
#driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
#redis 配置
|
||||
redis:
|
||||
database: 3
|
||||
host: 121.36.69.172
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8 #最大连接数据库连接数,设 0 为没有限制
|
||||
max-idle: 8 #最大等待连接中的数量,设 0 为没有限制
|
||||
max-wait: -1ms #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
|
||||
min-idle: 0 #最小等待连接中的数量,设 0 为没有限制
|
||||
shutdown-timeout: 100ms
|
||||
password: 'hzwlsoft.com'
|
||||
port: 4780
|
||||
#rabbitmq 配置
|
||||
rabbitmq:
|
||||
host: 121.36.69.172
|
||||
username: admin
|
||||
password: hzwlsoft.com
|
||||
port: 5672
|
||||
publisher-confirm-type: correlated
|
||||
publisher-returns: true
|
||||
virtual-host: /
|
||||
listener:
|
||||
simple:
|
||||
acknowledge-mode: manual
|
||||
#消费者的最小数量
|
||||
concurrency: 1
|
||||
#消费者的最大数量
|
||||
max-concurrency: 1
|
||||
#是否支持重试
|
||||
retry:
|
||||
enabled: true
|
||||
#mybatis plus 设置
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:com/jero/**/xml/*Mapper.xml
|
||||
global-config:
|
||||
# 关闭MP3.0自带的banner
|
||||
banner: false
|
||||
db-config:
|
||||
#主键类型 0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
|
||||
id-type: ASSIGN_ID
|
||||
# 默认数据库表下划线命名
|
||||
table-underline: true
|
||||
configuration:
|
||||
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
# 返回类型为Map,显示null对应的字段
|
||||
call-setters-on-nulls: true
|
||||
#jero专用配置
|
||||
jero :
|
||||
# 签名密钥串(前后端要一致,正式发布请自行修改)
|
||||
signatureSecret: dd05f1c54d63749eda95f9fa6d49v442a
|
||||
# 本地:local\Minio:minio\阿里云:alioss
|
||||
uploadType: local
|
||||
path :
|
||||
#文件上传根目录 设置
|
||||
upload: D://opt//upFiles
|
||||
#webapp文件路径
|
||||
webapp: D://opt//webapp
|
||||
shiro:
|
||||
excludeUrls: /test/jeroDemo/demo3,/test/jeroDemo/redisDemo/**,/category/**,/visual/**,/map/**,/jmreport/bigscreen2/**
|
||||
#阿里云oss存储配置
|
||||
oss:
|
||||
endpoint: oss-cn-beijing.aliyuncs.com
|
||||
accessKey: ??
|
||||
secretKey: ??
|
||||
bucketName: jeroos
|
||||
staticDomain: jerodev
|
||||
# ElasticSearch 6设置
|
||||
elasticsearch:
|
||||
cluster-name: jero-ES
|
||||
cluster-nodes: 127.0.0.1:9200
|
||||
check-enabled: false
|
||||
# 表单设计器配置
|
||||
desform:
|
||||
# 主题颜色(仅支持 16进制颜色代码)
|
||||
theme-color: "#1890ff"
|
||||
# 文件、图片上传方式,可选项:qiniu(七牛云)、system(跟随系统配置)
|
||||
upload-type: system
|
||||
# 在线预览文件服务器地址配置
|
||||
file-view-domain: 127.0.0.1:8012
|
||||
# minio文件上传
|
||||
minio:
|
||||
minio_url: http://minio.jero.com
|
||||
minio_name: ??
|
||||
minio_pass: ??
|
||||
bucketName: otatest
|
||||
#大屏报表参数设置
|
||||
jmreport:
|
||||
mode: dev
|
||||
#数据字典是否可以全局看到
|
||||
saas: false
|
||||
#是否需要校验token
|
||||
is_verify_token: false
|
||||
#必须校验方法
|
||||
verify_methods: remove,delete,save,add,update
|
||||
#Wps在线文档
|
||||
wps:
|
||||
domain: https://wwo.wps.cn/office/
|
||||
appid: ??
|
||||
appsecret: ??
|
||||
#xxl-job配置
|
||||
xxljob:
|
||||
enabled: false
|
||||
adminAddresses: http://127.0.0.1:9080/xxl-job-admin
|
||||
appname: ${spring.application.name}
|
||||
accessToken: ''
|
||||
address: 127.0.0.1:30007
|
||||
ip: 127.0.0.1
|
||||
port: 30007
|
||||
logPath: logs/jero/job/jobhandler/
|
||||
logRetentionDays: 30
|
||||
route:
|
||||
config:
|
||||
data-id: jero-gateway-router
|
||||
group: DEFAULT_GROUP
|
||||
#自定义路由配置 yml nacos database
|
||||
data-type: yml
|
||||
#分布式锁配置
|
||||
redisson:
|
||||
address: 121.36.69.172:4780
|
||||
password: 'hzwlsoft.com'
|
||||
type: STANDALONE
|
||||
enabled: true
|
||||
# 文件限制后缀黑名单
|
||||
fileSuffixLimits : 0x00,%00,\\00,.jsp,.exe,.php,.asp,.aspx,.jspx,.xml,.html,.js,.sh,.bin
|
||||
# 跨站白名单
|
||||
whiteUrls:
|
||||
#cas单点登录
|
||||
cas:
|
||||
prefixUrl: http://cas.example.org:8443/cas
|
||||
#Mybatis输出sql日志
|
||||
logging:
|
||||
level:
|
||||
com.jero.modules.system.mapper : info
|
||||
#swagger
|
||||
knife4j:
|
||||
#开启增强配置
|
||||
enable: true
|
||||
#开启生产环境屏蔽
|
||||
production: false
|
||||
basic:
|
||||
enable: false
|
||||
username: jero
|
||||
password: jero.com
|
||||
#第三方登录
|
||||
justauth:
|
||||
enabled: true
|
||||
type:
|
||||
GITHUB:
|
||||
client-id: true
|
||||
client-secret: true
|
||||
redirect-uri: http://sso.test.com:8080/jero-boot/sys/thirdLogin/github/callback
|
||||
WECHAT_ENTERPRISE:
|
||||
client-id: true
|
||||
client-secret: true
|
||||
redirect-uri: http://sso.test.com:8080/jero-boot/sys/thirdLogin/wechat_enterprise/callback
|
||||
agent-id: 1000002
|
||||
DINGTALK:
|
||||
client-id: true
|
||||
client-secret: true
|
||||
redirect-uri: http://sso.test.com:8080/jero-boot/sys/thirdLogin/dingtalk/callback
|
||||
WECHAT_OPEN:
|
||||
client-id: ??
|
||||
client-secret: ??
|
||||
redirect-uri: http://sso.test.com:8080/jero-boot/sys/thirdLogin/wechat_open/callback
|
||||
cache:
|
||||
type: default
|
||||
prefix: 'demo::'
|
||||
timeout: 1h
|
||||
@@ -0,0 +1,58 @@
|
||||
<?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>laws-starter</artifactId>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<version>2.5.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>laws-starter-cloud</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>jero-system-cloud-api</artifactId>
|
||||
</dependency>
|
||||
<!-- Nacos注册中心 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-client</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Nacos配置中心 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-client</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Nacos 2.0客户端 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-client</artifactId>
|
||||
<version>2.0.4</version>
|
||||
</dependency>
|
||||
<!-- feign -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<!-- 服务降级 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
package com.jero.config;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson.support.config.FastJsonConfig;
|
||||
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
|
||||
import com.alibaba.fastjson.support.springfox.SwaggerJsonSerializer;
|
||||
import feign.Feign;
|
||||
import feign.Logger;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import feign.form.spring.SpringFormEncoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.common.config.mqtoken.UserTokenContext;
|
||||
import com.jero.common.constant.CommonConstant;
|
||||
import com.jero.common.util.DateUtils;
|
||||
import com.jero.common.util.PathMatcherUtil;
|
||||
import com.jero.config.sign.interceptor.SignAuthConfiguration;
|
||||
import com.jero.config.sign.util.HttpUtils;
|
||||
import com.jero.config.sign.util.SignUtil;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.SpringDecoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.SortedMap;
|
||||
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class FeignConfig {
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor requestInterceptor() {
|
||||
return requestTemplate -> {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (null != attributes) {
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
log.debug("Feign request: {}", request.getRequestURI());
|
||||
// 将token信息放入header中
|
||||
String token = getToken(request);
|
||||
log.debug("Feign request token: {}", token);
|
||||
requestTemplate.header(CommonConstant.X_ACCESS_TOKEN, token);
|
||||
|
||||
//根据URL地址过滤请求 【字典表参数签名验证】
|
||||
if (PathMatcherUtil.matches(Arrays.asList(SignAuthConfiguration.urlList),requestTemplate.path())) {
|
||||
try {
|
||||
log.info("============================ [begin] fegin starter url ============================");
|
||||
log.info(requestTemplate.path());
|
||||
log.info(requestTemplate.method());
|
||||
String queryLine = requestTemplate.queryLine();
|
||||
queryLine = getQueryLine(queryLine);
|
||||
log.info(queryLine);
|
||||
if(requestTemplate.body()!=null){
|
||||
log.info(new String(requestTemplate.body()));
|
||||
}
|
||||
SortedMap<String, String> allParams = HttpUtils.getAllParams(requestTemplate.path(),queryLine,requestTemplate.body(),requestTemplate.method());
|
||||
String sign = SignUtil.getParamsSign(allParams);
|
||||
log.info(" Feign request params sign: {}",sign);
|
||||
log.info("============================ [end] fegin starter url ============================");
|
||||
requestTemplate.header(CommonConstant.X_SIGN, sign);
|
||||
requestTemplate.header(CommonConstant.X_TIMESTAMP, DateUtils.getCurrentTimestamp().toString());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
String token = UserTokenContext.getToken();
|
||||
log.debug("Feign request token: {}", token);
|
||||
requestTemplate.header(CommonConstant.X_ACCESS_TOKEN, token);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String getQueryLine(String queryLine) {
|
||||
if(queryLine!=null && queryLine.startsWith("?")){
|
||||
queryLine = queryLine.substring(1);
|
||||
}
|
||||
return queryLine;
|
||||
}
|
||||
|
||||
private String getToken(HttpServletRequest request) {
|
||||
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||
if(token==null || "".equals(token)){
|
||||
token = request.getParameter("token");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Feign 客户端的日志记录,默认级别为NONE
|
||||
* Logger.Level 的具体级别如下:
|
||||
* NONE:不记录任何信息
|
||||
* BASIC:仅记录请求方法、URL以及响应状态码和执行时间
|
||||
* HEADERS:除了记录 BASIC级别的信息外,还会记录请求和响应的头信息
|
||||
* FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据
|
||||
*/
|
||||
@Bean
|
||||
Logger.Level feignLoggerLevel() {
|
||||
return Logger.Level.FULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feign支持文件上传
|
||||
* @param messageConverters
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
@Scope("prototype")
|
||||
public Encoder multipartFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
|
||||
return new SpringFormEncoder(new SpringEncoder(messageConverters));
|
||||
}
|
||||
|
||||
// update-begin--Author:sunjianlei Date:20210604 for: 给 Feign 添加 FastJson 的解析支持 ----------
|
||||
@Bean
|
||||
public Encoder feignEncoder() {
|
||||
return new SpringEncoder(feignHttpMessageConverter());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Decoder feignDecoder() {
|
||||
return new SpringDecoder(feignHttpMessageConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置解码器为fastjson
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
|
||||
final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(this.getFastJsonConverter());
|
||||
return () -> httpMessageConverters;
|
||||
}
|
||||
|
||||
private FastJsonHttpMessageConverter getFastJsonConverter() {
|
||||
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
|
||||
|
||||
List<MediaType> supportedMediaTypes = new ArrayList<>();
|
||||
MediaType mediaTypeJson = MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE);
|
||||
supportedMediaTypes.add(mediaTypeJson);
|
||||
converter.setSupportedMediaTypes(supportedMediaTypes);
|
||||
FastJsonConfig config = new FastJsonConfig();
|
||||
config.getSerializeConfig().put(JSON.class, new SwaggerJsonSerializer());
|
||||
config.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
|
||||
converter.setFastJsonConfig(config);
|
||||
|
||||
return converter;
|
||||
}
|
||||
// update-end--Author:sunjianlei Date:20210604 for: 给 Feign 添加 FastJson 的解析支持 ----------
|
||||
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.jero.starter.cloud.config;
|
||||
|
||||
import feign.Client;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;
|
||||
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class PersonBeanConfiguration {
|
||||
|
||||
/**
|
||||
* 创建FeignClient
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
|
||||
SpringClientFactory clientFactory) {
|
||||
return new LoadBalancerFeignClient(new Client.Default(null, null),
|
||||
cachingFactory, clientFactory);
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.jero.starter.cloud.feign;
|
||||
|
||||
public interface IJeroFeignService {
|
||||
|
||||
<T> T newInstance(Class<T> apiType, String name);
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.jero.starter.cloud.feign.impl;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Contract;
|
||||
import feign.Feign;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.common.constant.CommonConstant;
|
||||
import com.jero.starter.cloud.feign.IJeroFeignService;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
@Import(FeignClientsConfiguration.class)
|
||||
public class JeroFeignService implements IJeroFeignService {
|
||||
|
||||
|
||||
//Feign 原生构造器
|
||||
Feign.Builder builder;
|
||||
|
||||
//创建构造器
|
||||
public JeroFeignService(Decoder decoder, Encoder encoder, Client client, Contract contract) {
|
||||
this.builder = Feign.builder()
|
||||
.client(client)
|
||||
.encoder(encoder)
|
||||
.decoder(decoder)
|
||||
.contract(contract);
|
||||
|
||||
builder.requestInterceptor(requestTemplate -> {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (null != attributes) {
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
log.info("Feign request: {}", request.getRequestURI());
|
||||
// 将token信息放入header中
|
||||
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||
if(token==null){
|
||||
token = request.getParameter("token");
|
||||
}
|
||||
log.info("Feign request token: {}", token);
|
||||
requestTemplate.header(CommonConstant.X_ACCESS_TOKEN, token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> T newInstance(Class<T> clientClass, String serviceName) {
|
||||
return builder.target(clientClass, String.format("http://%s/", serviceName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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>laws-starter</artifactId>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<version>2.5.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>laws-starter-job</artifactId>
|
||||
<description>laws-starter-定时任务</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.xuxueli</groupId>
|
||||
<artifactId>xxl-job-core</artifactId>
|
||||
<version>${xxl-job-core.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.jero.boot.starter.job.config;
|
||||
|
||||
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.boot.starter.job.prop.XxlJobProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
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 javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 定时任务配置
|
||||
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(value = XxlJobProperties.class)
|
||||
@ConditionalOnProperty(value = "jero.xxljob.enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class XxlJobConfiguration {
|
||||
|
||||
|
||||
@Resource
|
||||
private XxlJobProperties xxlJobProperties;
|
||||
|
||||
//@Bean(initMethod = "start", destroyMethod = "destroy")
|
||||
@Bean
|
||||
@ConditionalOnClass()
|
||||
public XxlJobSpringExecutor xxlJobExecutor() {
|
||||
log.info(">>>>>>>>>>> xxl-job config init.");
|
||||
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
|
||||
xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdminAddresses());
|
||||
xxlJobSpringExecutor.setAppname(xxlJobProperties.getAppname());
|
||||
// xxlJobSpringExecutor.setIp(xxlJobProperties.getIp());
|
||||
// xxlJobSpringExecutor.setPort(xxlJobProperties.getPort());
|
||||
xxlJobSpringExecutor.setAccessToken(xxlJobProperties.getAccessToken());
|
||||
xxlJobSpringExecutor.setLogPath(xxlJobProperties.getLogPath());
|
||||
xxlJobSpringExecutor.setLogRetentionDays(xxlJobProperties.getLogRetentionDays());
|
||||
return xxlJobSpringExecutor;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.jero.boot.starter.job.prop;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "jero.xxljob")
|
||||
public class XxlJobProperties {
|
||||
|
||||
|
||||
private String adminAddresses;
|
||||
|
||||
|
||||
private String appname;
|
||||
|
||||
|
||||
private String ip;
|
||||
|
||||
|
||||
private int port;
|
||||
|
||||
|
||||
private String accessToken;
|
||||
|
||||
|
||||
private String logPath;
|
||||
|
||||
|
||||
private int logRetentionDays;
|
||||
|
||||
/**
|
||||
* 是否开启xxljob
|
||||
*/
|
||||
private Boolean enable = true;
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.jero.boot.starter.job.config.XxlJobConfiguration
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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>laws-starter</artifactId>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<version>2.5.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>laws-starter-lock</artifactId>
|
||||
<description>laws-starter-分布式锁</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.jero.boot.starter.lock.annotation;
|
||||
|
||||
import com.jero.boot.starter.lock.enums.LockModel;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Redisson分布式锁注解
|
||||
*
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface JLock {
|
||||
|
||||
/**
|
||||
* 锁的模式:如果不设置,自动模式,当参数只有一个.使用 REENTRANT 参数多个 MULTIPLE
|
||||
*/
|
||||
LockModel lockModel() default LockModel.AUTO;
|
||||
|
||||
/**
|
||||
* 如果keys有多个,如果不设置,则使用 联锁
|
||||
* @return
|
||||
*/
|
||||
String[] lockKey() default {};
|
||||
|
||||
/**
|
||||
* key的静态常量:当key的spel的值是LIST,数组时使用+号连接将会被spel认为这个变量是个字符串
|
||||
* @return
|
||||
*/
|
||||
String keyConstant() default "";
|
||||
|
||||
|
||||
/**
|
||||
* 锁超时时间,默认30000毫秒
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
long expireSeconds() default 30000L;
|
||||
|
||||
/**
|
||||
* 等待加锁超时时间,默认10000毫秒 -1 则表示一直等待
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
long waitTime() default 10000L;
|
||||
|
||||
/**
|
||||
* 未取到锁时提示信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String failMsg() default "获取锁失败,请稍后重试";
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.jero.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 "";
|
||||
|
||||
|
||||
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package com.jero.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;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.jero.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<Object> 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);
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
package com.jero.boot.starter.lock.aspect;
|
||||
|
||||
import com.jero.boot.starter.lock.annotation.JLock;
|
||||
import com.jero.boot.starter.lock.enums.LockModel;
|
||||
import lombok.SneakyThrows;
|
||||
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.redisson.RedissonMultiLock;
|
||||
import org.redisson.RedissonRedLock;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
/**
|
||||
* 分布式锁解析器
|
||||
*
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class DistributedLockHandler extends BaseAspect{
|
||||
|
||||
|
||||
@Autowired(required = false)
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
/**
|
||||
* 切面环绕通知
|
||||
*
|
||||
* @param joinPoint
|
||||
* @param jLock
|
||||
* @return Object
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Around("@annotation(jLock)")
|
||||
public Object around(ProceedingJoinPoint joinPoint, JLock jLock) {
|
||||
Object obj = null;
|
||||
RLock rLock = getLock(joinPoint, jLock);
|
||||
boolean res = false;
|
||||
//获取超时时间
|
||||
long expireSeconds = jLock.expireSeconds();
|
||||
//等待多久,n秒内获取不到锁,则直接返回
|
||||
long waitTime = jLock.waitTime();
|
||||
//执行aop
|
||||
if (rLock != null) {
|
||||
try {
|
||||
if (waitTime == -1) {
|
||||
res = true;
|
||||
//一直等待加锁
|
||||
rLock.lock(expireSeconds, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
res = rLock.tryLock(waitTime, expireSeconds, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
if (res) {
|
||||
obj = joinPoint.proceed();
|
||||
} else {
|
||||
log.error("获取锁异常");
|
||||
}
|
||||
} finally {
|
||||
if (res) {
|
||||
rLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("结束RedisLock环绕通知...");
|
||||
return obj;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private RLock getLock(ProceedingJoinPoint joinPoint, JLock jLock) {
|
||||
String[] keys = jLock.lockKey();
|
||||
if (keys.length == 0) {
|
||||
throw new IllegalArgumentException("keys不能为空");
|
||||
}
|
||||
String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
|
||||
Object[] args = joinPoint.getArgs();
|
||||
|
||||
LockModel lockModel = jLock.lockModel();
|
||||
if (!lockModel.equals(LockModel.MULTIPLE) && !lockModel.equals(LockModel.REDLOCK) && keys.length > 1) {
|
||||
throw new IllegalArgumentException("参数有多个,锁模式为->" + lockModel.name() + ".无法锁定");
|
||||
}
|
||||
RLock rLock = null;
|
||||
String keyConstant = jLock.keyConstant();
|
||||
lockModel = getLockModel(keys, lockModel);
|
||||
switch (lockModel) {
|
||||
case FAIR:
|
||||
rLock = redissonClient.getFairLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0));
|
||||
break;
|
||||
case REDLOCK:
|
||||
List<RLock> rLocks = new ArrayList<>();
|
||||
getRedLock(keys, parameterNames, args, keyConstant, rLocks);
|
||||
RLock[] locks = new RLock[rLocks.size()];
|
||||
int index = 0;
|
||||
for (RLock r : rLocks) {
|
||||
locks[index++] = r;
|
||||
}
|
||||
rLock = new RedissonRedLock(locks);
|
||||
break;
|
||||
case MULTIPLE:
|
||||
rLocks = new ArrayList<>();
|
||||
|
||||
getRedLock(keys, parameterNames, args, keyConstant, rLocks);
|
||||
locks = new RLock[rLocks.size()];
|
||||
index = 0;
|
||||
for (RLock r : rLocks) {
|
||||
locks[index++] = r;
|
||||
}
|
||||
rLock = new RedissonMultiLock(locks);
|
||||
break;
|
||||
case REENTRANT:
|
||||
List<String> valueBySpEL = getValueBySpEL(keys[0], parameterNames, args, keyConstant);
|
||||
//如果spel表达式是数组或者LIST 则使用红锁
|
||||
if (valueBySpEL.size() == 1) {
|
||||
rLock = redissonClient.getLock(valueBySpEL.get(0));
|
||||
} else {
|
||||
locks = new RLock[valueBySpEL.size()];
|
||||
index = 0;
|
||||
for (String s : valueBySpEL) {
|
||||
locks[index++] = redissonClient.getLock(s);
|
||||
}
|
||||
rLock = new RedissonRedLock(locks);
|
||||
}
|
||||
break;
|
||||
case READ:
|
||||
rLock = redissonClient.getReadWriteLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0)).readLock();
|
||||
break;
|
||||
case WRITE:
|
||||
rLock = redissonClient.getReadWriteLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0)).writeLock();
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
return rLock;
|
||||
}
|
||||
|
||||
private LockModel getLockModel(String[] keys, LockModel lockModel) {
|
||||
if (lockModel.equals(LockModel.AUTO)) {
|
||||
if (keys.length > 1) {
|
||||
lockModel = LockModel.REDLOCK;
|
||||
} else {
|
||||
lockModel = LockModel.REENTRANT;
|
||||
}
|
||||
}
|
||||
return lockModel;
|
||||
}
|
||||
|
||||
private void getRedLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, List<RLock> rLocks) {
|
||||
for (String key : keys) {
|
||||
List<String> valueBySpEL = getValueBySpEL(key, parameterNames, args, keyConstant);
|
||||
for (String s : valueBySpEL) {
|
||||
rLocks.add(redissonClient.getLock(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.jero.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 com.jero.boot.starter.lock.annotation.JRepeat;
|
||||
import com.jero.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) {
|
||||
// do point
|
||||
}
|
||||
|
||||
/**
|
||||
* 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等
|
||||
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 IllegalArgumentException("请勿重复提交");
|
||||
}
|
||||
} finally {
|
||||
// 如果锁还存在,在方法执行完成后,释放锁
|
||||
if (isLocked) {
|
||||
redissonLockClient.unlock(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package com.jero.boot.starter.lock.client;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 分布式锁实现基于Redisson
|
||||
*
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RedissonLockClient {
|
||||
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* 获取锁
|
||||
*/
|
||||
public RLock getLock(String lockKey) {
|
||||
return redissonClient.getLock(lockKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加锁操作
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean tryLock(String lockName, long expireSeconds) {
|
||||
return tryLock(lockName, 0, expireSeconds);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加锁操作
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean tryLock(String lockName, long waitTime, long expireSeconds) {
|
||||
RLock rLock = getLock(lockName);
|
||||
boolean getLock = false;
|
||||
try {
|
||||
getLock = rLock.tryLock(waitTime, expireSeconds, TimeUnit.SECONDS);
|
||||
if (getLock) {
|
||||
log.info("获取锁成功,lockName={}", lockName);
|
||||
} else {
|
||||
log.info("获取锁失败,lockName={}", lockName);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("获取式锁异常,lockName=" + lockName, e);
|
||||
Thread.currentThread().interrupt();
|
||||
}finally {
|
||||
rLock.unlock();
|
||||
}
|
||||
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();
|
||||
Thread.currentThread().interrupt();
|
||||
}finally {
|
||||
fairLock.unlock();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean existKey(String key) {
|
||||
return redisTemplate.hasKey(key);
|
||||
}
|
||||
/**
|
||||
* 锁lockKey
|
||||
*
|
||||
* @param lockKey
|
||||
* @return
|
||||
*/
|
||||
public RLock lock(String lockKey) {
|
||||
RLock lock = getLock(lockKey);
|
||||
try {
|
||||
lock.lock();
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁lockKey
|
||||
*
|
||||
* @param lockKey
|
||||
* @param leaseTime
|
||||
* @return
|
||||
*/
|
||||
public RLock lock(String lockKey, long leaseTime) {
|
||||
RLock lock = getLock(lockKey);
|
||||
try {
|
||||
lock.lock(leaseTime, TimeUnit.SECONDS);
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return lock;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解锁
|
||||
*
|
||||
* @param lockName 锁名称
|
||||
*/
|
||||
public void unlock(String lockName) {
|
||||
try {
|
||||
redissonClient.getLock(lockName).unlock();
|
||||
} catch (Exception e) {
|
||||
log.error("解锁异常,lockName=" + lockName, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.jero.boot.starter.lock.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.boot.starter.lock.core.RedissonManager;
|
||||
import com.jero.boot.starter.lock.prop.RedissonProperties;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
/**
|
||||
* Redisson自动化配置
|
||||
*
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@ConditionalOnClass(RedissonProperties.class)
|
||||
@EnableConfigurationProperties(RedissonProperties.class)
|
||||
public class RedissonConfiguration {
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(RedissonClient.class)
|
||||
public RedissonClient redissonClient(RedissonProperties redissonProperties) {
|
||||
RedissonManager redissonManager = new RedissonManager(redissonProperties);
|
||||
log.info("RedissonManager初始化完成,当前连接方式:" + redissonProperties.getType() + ",连接地址:" + redissonProperties.getAddress());
|
||||
return redissonManager.getRedisson();
|
||||
}
|
||||
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package com.jero.boot.starter.lock.core;
|
||||
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.boot.starter.lock.core.strategy.RedissonConfigStrategy;
|
||||
import com.jero.boot.starter.lock.prop.RedissonProperties;
|
||||
import com.jero.boot.starter.lock.core.strategy.impl.ClusterRedissonConfigStrategyImpl;
|
||||
import com.jero.boot.starter.lock.core.strategy.impl.MasterslaveRedissonConfigStrategyImpl;
|
||||
import com.jero.boot.starter.lock.core.strategy.impl.SentinelRedissonConfigStrategyImpl;
|
||||
import com.jero.boot.starter.lock.core.strategy.impl.StandaloneRedissonConfigStrategyImpl;
|
||||
import com.jero.boot.starter.lock.enums.RedisConnectionType;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.config.Config;
|
||||
|
||||
|
||||
/**
|
||||
* Redisson配置管理器,用于初始化的redisson实例
|
||||
*
|
||||
* @author zyf
|
||||
* @date 2020-11-12
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedissonManager {
|
||||
|
||||
private Config config = new Config();
|
||||
|
||||
private Redisson redisson = null;
|
||||
|
||||
public RedissonManager() {
|
||||
}
|
||||
|
||||
public RedissonManager(RedissonProperties redissonProperties) {
|
||||
//装配开关
|
||||
Boolean enabled = redissonProperties.getEnabled();
|
||||
if (enabled != null && enabled) {
|
||||
try {
|
||||
config = RedissonConfigFactory.getInstance().createConfig(redissonProperties);
|
||||
redisson = (Redisson) Redisson.create(config);
|
||||
} catch (Exception e) {
|
||||
log.error("Redisson初始化错误", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Redisson getRedisson() {
|
||||
return redisson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redisson连接方式配置工厂
|
||||
* 双重检查锁
|
||||
*/
|
||||
static class RedissonConfigFactory {
|
||||
|
||||
private RedissonConfigFactory() {
|
||||
}
|
||||
|
||||
private static RedissonConfigFactory factory = null;
|
||||
|
||||
public static RedissonConfigFactory getInstance() {
|
||||
synchronized (Object.class) {
|
||||
if (factory == null) {
|
||||
factory = new RedissonConfigFactory();
|
||||
}
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据连接类型創建连接方式的配置
|
||||
*
|
||||
* @param redissonProperties
|
||||
* @return Config
|
||||
*/
|
||||
Config createConfig(RedissonProperties redissonProperties) {
|
||||
Preconditions.checkNotNull(redissonProperties);
|
||||
Preconditions.checkNotNull(redissonProperties.getAddress(), "redis地址未配置");
|
||||
RedisConnectionType connectionType = redissonProperties.getType();
|
||||
// 声明连接方式
|
||||
RedissonConfigStrategy redissonConfigStrategy;
|
||||
if (connectionType.equals(RedisConnectionType.SENTINEL)) {
|
||||
redissonConfigStrategy = new SentinelRedissonConfigStrategyImpl();
|
||||
} else if (connectionType.equals(RedisConnectionType.CLUSTER)) {
|
||||
redissonConfigStrategy = new ClusterRedissonConfigStrategyImpl();
|
||||
} else if (connectionType.equals(RedisConnectionType.MASTERSLAVE)) {
|
||||
redissonConfigStrategy = new MasterslaveRedissonConfigStrategyImpl();
|
||||
} else {
|
||||
redissonConfigStrategy = new StandaloneRedissonConfigStrategyImpl();
|
||||
}
|
||||
Preconditions.checkNotNull(redissonConfigStrategy, "连接方式创建异常");
|
||||
|
||||
return redissonConfigStrategy.createRedissonConfig(redissonProperties);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.jero.boot.starter.lock.core.strategy;
|
||||
|
||||
import com.jero.boot.starter.lock.prop.RedissonProperties;
|
||||
import org.redisson.config.Config;
|
||||
|
||||
/**
|
||||
* Redisson配置构建接口
|
||||
*
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
public interface RedissonConfigStrategy {
|
||||
|
||||
/**
|
||||
* 根据不同的Redis配置策略创建对应的Config
|
||||
*
|
||||
* @param redissonProperties
|
||||
* @return Config
|
||||
*/
|
||||
Config createRedissonConfig(RedissonProperties redissonProperties);
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.jero.boot.starter.lock.core.strategy.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.jero.boot.starter.lock.core.strategy.RedissonConfigStrategy;
|
||||
import com.jero.boot.starter.lock.prop.RedissonProperties;
|
||||
import com.jero.boot.starter.lock.enums.GlobalConstant;
|
||||
import org.redisson.config.Config;
|
||||
|
||||
|
||||
/**
|
||||
* 集群方式Redisson配置
|
||||
* cluster方式至少6个节点(3主3从)
|
||||
* 配置方式:127.0.0.1:6379,127.0.0.1:6380,127.0.0.1:6381,127.0.0.1:6382,127.0.0.1:6383,127.0.0.1:6384
|
||||
*
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
@Slf4j
|
||||
public class ClusterRedissonConfigStrategyImpl implements RedissonConfigStrategy {
|
||||
|
||||
@Override
|
||||
public Config createRedissonConfig(RedissonProperties redissonProperties) {
|
||||
Config config = new Config();
|
||||
try {
|
||||
String address = redissonProperties.getAddress();
|
||||
String password = redissonProperties.getPassword();
|
||||
String[] addrTokens = address.split(",");
|
||||
// 设置集群(cluster)节点的服务IP和端口
|
||||
for (int i = 0; i < addrTokens.length; i++) {
|
||||
config.useClusterServers().addNodeAddress(GlobalConstant.REDIS_CONNECTION_PREFIX + addrTokens[i]);
|
||||
if (StringUtils.isNotBlank(password)) {
|
||||
config.useClusterServers().setPassword(password);
|
||||
}
|
||||
}
|
||||
log.info("初始化集群方式Config,连接地址:" + address);
|
||||
} catch (Exception e) {
|
||||
log.error("集群Redisson初始化错误", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.jero.boot.starter.lock.core.strategy.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.jero.boot.starter.lock.core.strategy.RedissonConfigStrategy;
|
||||
import com.jero.boot.starter.lock.prop.RedissonProperties;
|
||||
import com.jero.boot.starter.lock.enums.GlobalConstant;
|
||||
import org.redisson.config.Config;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 主从方式Redisson配置
|
||||
* <p>配置方式: 127.0.0.1:6379(主),127.0.0.1:6380(子),127.0.0.1:6381(子)</p>
|
||||
*
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
@Slf4j
|
||||
public class MasterslaveRedissonConfigStrategyImpl implements RedissonConfigStrategy {
|
||||
|
||||
@Override
|
||||
public Config createRedissonConfig(RedissonProperties redissonProperties) {
|
||||
Config config = new Config();
|
||||
try {
|
||||
String address = redissonProperties.getAddress();
|
||||
String password = redissonProperties.getPassword();
|
||||
int database = redissonProperties.getDatabase();
|
||||
String[] addrTokens = address.split(",");
|
||||
String masterNodeAddr = addrTokens[0];
|
||||
// 设置主节点ip
|
||||
config.useMasterSlaveServers().setMasterAddress(masterNodeAddr);
|
||||
if (StringUtils.isNotBlank(password)) {
|
||||
config.useMasterSlaveServers().setPassword(password);
|
||||
}
|
||||
config.useMasterSlaveServers().setDatabase(database);
|
||||
// 设置从节点,移除第一个节点,默认第一个为主节点
|
||||
List<String> slaveList = new ArrayList<>();
|
||||
for (String addrToken : addrTokens) {
|
||||
slaveList.add(GlobalConstant.REDIS_CONNECTION_PREFIX + addrToken);
|
||||
}
|
||||
slaveList.remove(0);
|
||||
|
||||
config.useMasterSlaveServers().addSlaveAddress(slaveList.toArray(new String[0]));
|
||||
log.info("初始化主从方式Config,redisAddress:" + address);
|
||||
} catch (Exception e) {
|
||||
log.error("主从Redisson初始化错误", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.jero.boot.starter.lock.core.strategy.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.jero.boot.starter.lock.core.strategy.RedissonConfigStrategy;
|
||||
import com.jero.boot.starter.lock.prop.RedissonProperties;
|
||||
import com.jero.boot.starter.lock.enums.GlobalConstant;
|
||||
import org.redisson.config.Config;
|
||||
|
||||
|
||||
/**
|
||||
* 哨兵方式Redis连接配置
|
||||
* 比如sentinel.conf里配置为sentinel monitor my-sentinel-name 127.0.0.1 6379 2,那么这里就配置my-sentinel-name
|
||||
* 配置方式:my-sentinel-name,127.0.0.1:26379,127.0.0.1:26389,127.0.0.1:26399
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
@Slf4j
|
||||
public class SentinelRedissonConfigStrategyImpl implements RedissonConfigStrategy {
|
||||
|
||||
@Override
|
||||
public Config createRedissonConfig(RedissonProperties redissonProperties) {
|
||||
Config config = new Config();
|
||||
try {
|
||||
String address = redissonProperties.getAddress();
|
||||
String password = redissonProperties.getPassword();
|
||||
int database = redissonProperties.getDatabase();
|
||||
String[] addrTokens = address.split(",");
|
||||
String sentinelAliasName = addrTokens[0];
|
||||
// 设置redis配置文件sentinel.conf配置的sentinel别名
|
||||
config.useSentinelServers().setMasterName(sentinelAliasName);
|
||||
config.useSentinelServers().setDatabase(database);
|
||||
if (StringUtils.isNotBlank(password)) {
|
||||
config.useSentinelServers().setPassword(password);
|
||||
}
|
||||
// 设置哨兵节点的服务IP和端口
|
||||
for (int i = 1; i < addrTokens.length; i++) {
|
||||
config.useSentinelServers().addSentinelAddress(GlobalConstant.REDIS_CONNECTION_PREFIX+ addrTokens[i]);
|
||||
}
|
||||
log.info("初始化哨兵方式Config,redisAddress:" + address);
|
||||
} catch (Exception e) {
|
||||
log.error("哨兵Redisson初始化错误", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.jero.boot.starter.lock.core.strategy.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.jero.boot.starter.lock.core.strategy.RedissonConfigStrategy;
|
||||
import com.jero.boot.starter.lock.prop.RedissonProperties;
|
||||
import com.jero.boot.starter.lock.enums.GlobalConstant;
|
||||
import org.redisson.config.Config;
|
||||
|
||||
|
||||
/**
|
||||
* 单机方式Redisson配置
|
||||
*
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
@Slf4j
|
||||
public class StandaloneRedissonConfigStrategyImpl implements RedissonConfigStrategy {
|
||||
|
||||
@Override
|
||||
public Config createRedissonConfig(RedissonProperties redissonProperties) {
|
||||
Config config = new Config();
|
||||
try {
|
||||
String address = redissonProperties.getAddress();
|
||||
String password = redissonProperties.getPassword();
|
||||
int database = redissonProperties.getDatabase();
|
||||
String redisAddr = GlobalConstant.REDIS_CONNECTION_PREFIX + address;
|
||||
config.useSingleServer().setAddress(redisAddr);
|
||||
config.useSingleServer().setDatabase(database);
|
||||
if (StringUtils.isNotBlank(password)) {
|
||||
config.useSingleServer().setPassword(password);
|
||||
}
|
||||
log.info("初始化Redisson单机配置,连接地址:" + address);
|
||||
} catch (Exception e) {
|
||||
log.error("单机Redisson初始化错误", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.jero.boot.starter.lock.enums;
|
||||
|
||||
/**
|
||||
* 全局常量枚举
|
||||
*
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
|
||||
public class GlobalConstant {
|
||||
|
||||
private GlobalConstant(){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis地址连接前缀
|
||||
*/
|
||||
public static final String REDIS_CONNECTION_PREFIX = "redis://";
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.jero.boot.starter.lock.enums;
|
||||
|
||||
/**
|
||||
* 锁的模式
|
||||
* @author jero
|
||||
*/
|
||||
public enum LockModel {
|
||||
//可重入锁
|
||||
REENTRANT,
|
||||
//公平锁
|
||||
FAIR,
|
||||
//联锁(可以把一组锁当作一个锁来加锁和释放)
|
||||
MULTIPLE,
|
||||
//红锁
|
||||
REDLOCK,
|
||||
//读锁
|
||||
READ,
|
||||
//写锁
|
||||
WRITE,
|
||||
//自动模式,当参数只有一个.使用 REENTRANT 参数多个 REDLOCK
|
||||
AUTO
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.jero.boot.starter.lock.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Redis连接方式
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RedisConnectionType {
|
||||
/**
|
||||
* 单机部署方式(默认)
|
||||
*/
|
||||
STANDALONE("standalone", "单机部署方式"),
|
||||
/**
|
||||
* 哨兵部署方式
|
||||
*/
|
||||
SENTINEL("sentinel", "哨兵部署方式"),
|
||||
/**
|
||||
* 集群部署方式
|
||||
*/
|
||||
CLUSTER("cluster", "集群方式"),
|
||||
/**
|
||||
* 主从部署方式
|
||||
*/
|
||||
MASTERSLAVE("masterslave", "主从部署方式");
|
||||
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
private final String code;
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private final String name;
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.jero.boot.starter.lock.prop;
|
||||
|
||||
import lombok.Data;
|
||||
import com.jero.boot.starter.lock.enums.RedisConnectionType;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Redisson配置映射类
|
||||
*
|
||||
* @author zyf
|
||||
* @date 2020-11-11
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "jero.redisson")
|
||||
public class RedissonProperties {
|
||||
|
||||
/**
|
||||
* redis主机地址,ip:port,多个用逗号(,)分隔
|
||||
*/
|
||||
private String address;
|
||||
/**
|
||||
* 连接类型
|
||||
*/
|
||||
private RedisConnectionType type;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* 数据库(默认0)
|
||||
*/
|
||||
private int database;
|
||||
|
||||
/**
|
||||
* 是否装配redisson配置
|
||||
*/
|
||||
private Boolean enabled = true;
|
||||
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.jero.boot.starter.lock.config.RedissonConfiguration
|
||||
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package org.jero.boot.starter.lock.test;
|
||||
|
||||
import com.jero.boot.starter.lock.annotation.JLock;
|
||||
import com.jero.boot.starter.lock.annotation.JRepeat;
|
||||
import com.jero.boot.starter.lock.annotation.LockConstant;
|
||||
import com.jero.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);
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package org.jero.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;
|
||||
|
||||
import static junit.framework.TestCase.assertNull;
|
||||
|
||||
@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();
|
||||
}
|
||||
}));
|
||||
assertNull(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();
|
||||
}
|
||||
}));
|
||||
assertNull(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();
|
||||
}
|
||||
}));
|
||||
assertNull(executorService.awaitTermination(30, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package org.jero.boot.starter.lock.test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "com.jero")
|
||||
@EnableAspectJAutoProxy
|
||||
public class LockTestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(LockTestApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package org.jero.boot.starter.lock.test;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TestUser {
|
||||
private String userId;
|
||||
private String userName;
|
||||
}
|
||||
+19
@@ -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: hzwlsoft123
|
||||
port: 6379
|
||||
jero :
|
||||
redisson:
|
||||
address: 127.0.0.1:6379
|
||||
password: hzwlsoft123
|
||||
type: STANDALONE
|
||||
enabled: true
|
||||
@@ -0,0 +1,21 @@
|
||||
<?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>laws-starter</artifactId>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<version>2.5.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>laws-starter-rabbitmq</artifactId>
|
||||
<description>laws-starter-消息队列</description>
|
||||
<dependencies>
|
||||
<!-- 消息总线 rabbitmq -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
package com.jero.boot.starter.rabbitmq.client;
|
||||
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.boot.starter.rabbitmq.event.EventObj;
|
||||
import com.jero.boot.starter.rabbitmq.event.JeroRemoteApplicationEvent;
|
||||
import com.jero.boot.starter.rabbitmq.exchange.DelayExchangeBuilder;
|
||||
import com.jero.common.annotation.RabbitComponent;
|
||||
import com.jero.common.base.BaseMap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.bus.BusProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 消息队列客户端
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class RabbitMqClient {
|
||||
|
||||
private final RabbitAdmin rabbitAdmin;
|
||||
|
||||
private final RabbitTemplate rabbitTemplate;
|
||||
|
||||
|
||||
@Resource
|
||||
private SimpleMessageListenerContainer messageListenerContainer;
|
||||
|
||||
@Resource
|
||||
BusProperties busProperties;
|
||||
@Resource
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
|
||||
@Resource
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
|
||||
@Bean
|
||||
public void initQueue() {
|
||||
Map<String, Object> beansWithRqbbitComponentMap = this.applicationContext.getBeansWithAnnotation(RabbitComponent.class);
|
||||
Class<? extends Object> clazz = null;
|
||||
for (Map.Entry<String, Object> entry : beansWithRqbbitComponentMap.entrySet()) {
|
||||
log.info("初始化队列............");
|
||||
//获取到实例对象的class信息
|
||||
clazz = entry.getValue().getClass();
|
||||
Method[] methods = clazz.getMethods();
|
||||
RabbitListener rabbitListener = clazz.getAnnotation(RabbitListener.class);
|
||||
if (ObjectUtil.isNotEmpty(rabbitListener)) {
|
||||
createQueue(rabbitListener);
|
||||
}
|
||||
for (Method method : methods) {
|
||||
RabbitListener methodRabbitListener = method.getAnnotation(RabbitListener.class);
|
||||
if (ObjectUtil.isNotEmpty(methodRabbitListener)) {
|
||||
createQueue(methodRabbitListener);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化队列
|
||||
*
|
||||
* @param rabbitListener
|
||||
*/
|
||||
private void createQueue(RabbitListener rabbitListener) {
|
||||
String[] queues = rabbitListener.queues();
|
||||
DirectExchange directExchange = createExchange(DelayExchangeBuilder.DELAY_EXCHANGE);
|
||||
//创建交换机
|
||||
rabbitAdmin.declareExchange(directExchange);
|
||||
if (ObjectUtil.isNotEmpty(queues)) {
|
||||
for (String queueName : queues) {
|
||||
Properties result = rabbitAdmin.getQueueProperties(queueName);
|
||||
if (ObjectUtil.isEmpty(result)) {
|
||||
Queue queue = new Queue(queueName);
|
||||
addQueue(queue);
|
||||
Binding binding = BindingBuilder.bind(queue).to(directExchange).with(queueName);
|
||||
rabbitAdmin.declareBinding(binding);
|
||||
log.info("创建队列:" + queueName);
|
||||
}else{
|
||||
log.info("已有队列:" + queueName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Map<String,Object> sentObj = new HashMap<>();
|
||||
|
||||
|
||||
@Autowired
|
||||
public RabbitMqClient(RabbitAdmin rabbitAdmin, RabbitTemplate rabbitTemplate) {
|
||||
this.rabbitAdmin = rabbitAdmin;
|
||||
this.rabbitTemplate = rabbitTemplate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送远程事件
|
||||
*
|
||||
* @param handlerName
|
||||
* @param baseMap
|
||||
*/
|
||||
public void publishEvent(String handlerName, BaseMap baseMap) {
|
||||
EventObj eventObj = new EventObj();
|
||||
eventObj.setHandlerName(handlerName);
|
||||
eventObj.setBaseMap(baseMap);
|
||||
publisher.publishEvent(new JeroRemoteApplicationEvent(eventObj, busProperties.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换Message对象
|
||||
*
|
||||
* @param messageType 返回消息类型 MessageProperties类中常量
|
||||
* @param msg
|
||||
* @return
|
||||
*/
|
||||
public Message getMessage(String messageType, Object msg) {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType(messageType);
|
||||
return new Message(msg.toString().getBytes(), messageProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 有绑定Key的Exchange发送
|
||||
*
|
||||
* @param routingKey
|
||||
* @param msg
|
||||
*/
|
||||
public void sendMessageToExchange(TopicExchange topicExchange, String routingKey, Object msg) {
|
||||
Message message = getMessage(MessageProperties.CONTENT_TYPE_JSON, msg);
|
||||
rabbitTemplate.send(topicExchange.getName(), routingKey, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 没有绑定KEY的Exchange发送
|
||||
*
|
||||
* @param exchange
|
||||
* @param msg
|
||||
*/
|
||||
public void sendMessageToExchange(TopicExchange topicExchange, AbstractExchange exchange, String msg) {
|
||||
addExchange(exchange);
|
||||
log.info("RabbitMQ send " + exchange.getName() + "->" + msg);
|
||||
rabbitTemplate.convertAndSend(topicExchange.getName(), msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param queueName 队列名称
|
||||
* @param params 消息内容map
|
||||
*/
|
||||
public void sendMessage(String queueName, Object params) {
|
||||
log.info("发送消息到mq");
|
||||
try {
|
||||
rabbitTemplate.convertAndSend(DelayExchangeBuilder.DELAY_EXCHANGE, queueName, params, message -> message);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param queueName 队列名称
|
||||
*/
|
||||
public void sendMessage(String queueName) {
|
||||
this.send(queueName, this.sentObj, 0);
|
||||
this.sentObj.clear();
|
||||
}
|
||||
|
||||
|
||||
public RabbitMqClient put(String key, Object value) {
|
||||
this.sentObj.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟发送消息
|
||||
*
|
||||
* @param queueName 队列名称
|
||||
* @param params 消息内容params
|
||||
* @param expiration 延迟时间 单位毫秒
|
||||
*/
|
||||
public void sendMessage(String queueName, Object params, Integer expiration) {
|
||||
this.send(queueName, params, expiration);
|
||||
}
|
||||
|
||||
private void send(String queueName, Object params, Integer expiration) {
|
||||
Queue queue = new Queue(queueName);
|
||||
addQueue(queue);
|
||||
CustomExchange customExchange = DelayExchangeBuilder.buildExchange();
|
||||
rabbitAdmin.declareExchange(customExchange);
|
||||
Binding binding = BindingBuilder.bind(queue).to(customExchange).with(queueName).noargs();
|
||||
rabbitAdmin.declareBinding(binding);
|
||||
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
log.debug("发送时间:" + sf.format(new Date()));
|
||||
messageListenerContainer.setQueueNames(queueName);
|
||||
/* messageListenerContainer.setMessageListener(new MqListener<Message>() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) {
|
||||
MqListener messageListener = SpringContextHolder.getHandler(queueName + "Listener", MqListener.class);
|
||||
if (ObjectUtil.isNotEmpty(messageListener)) {
|
||||
messageListener.onMessage(message, channel);
|
||||
}
|
||||
}
|
||||
});*/
|
||||
rabbitTemplate.convertAndSend(DelayExchangeBuilder.DEFAULT_DELAY_EXCHANGE, queueName, params, message -> {
|
||||
if (expiration != null && expiration > 0) {
|
||||
message.getMessageProperties().setHeader("x-delay", expiration);
|
||||
}
|
||||
return message;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 给queue发送消息
|
||||
*
|
||||
* @param queueName
|
||||
*/
|
||||
public String receiveFromQueue(String queueName) {
|
||||
return receiveFromQueue(DirectExchange.DEFAULT, queueName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给direct交换机指定queue发送消息
|
||||
*
|
||||
* @param directExchange
|
||||
* @param queueName
|
||||
*/
|
||||
public String receiveFromQueue(DirectExchange directExchange, String queueName) {
|
||||
Queue queue = new Queue(queueName);
|
||||
addQueue(queue);
|
||||
Binding binding = BindingBuilder.bind(queue).to(directExchange).withQueueName();
|
||||
rabbitAdmin.declareBinding(binding);
|
||||
String messages = (String) rabbitTemplate.receiveAndConvert(queueName);
|
||||
log.info("Receive:" + messages);
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Exchange
|
||||
*
|
||||
* @param exchange
|
||||
*/
|
||||
public void addExchange(AbstractExchange exchange) {
|
||||
rabbitAdmin.declareExchange(exchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个Exchange
|
||||
*
|
||||
* @param exchangeName
|
||||
*/
|
||||
public boolean deleteExchange(String exchangeName) {
|
||||
return rabbitAdmin.deleteExchange(exchangeName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 声明其名称自动命名的队列。它是用exclusive=true、autoDelete=true和 durable = false
|
||||
*
|
||||
* @return Queue
|
||||
*/
|
||||
public Queue addQueue() {
|
||||
return rabbitAdmin.declareQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个指定的Queue
|
||||
*
|
||||
* @param queue
|
||||
* @return queueName
|
||||
*/
|
||||
public String addQueue(Queue queue) {
|
||||
return rabbitAdmin.declareQueue(queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个队列
|
||||
*
|
||||
* @param queueName the name of the queue.
|
||||
* @param unused true if the queue should be deleted only if not in use.
|
||||
* @param empty true if the queue should be deleted only if empty.
|
||||
*/
|
||||
public void deleteQueue(String queueName, boolean unused, boolean empty) {
|
||||
rabbitAdmin.deleteQueue(queueName, unused, empty);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个队列
|
||||
*
|
||||
* @param queueName
|
||||
* @return true if the queue existed and was deleted.
|
||||
*/
|
||||
public boolean deleteQueue(String queueName) {
|
||||
return rabbitAdmin.deleteQueue(queueName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定一个队列到一个匹配型交换器使用一个routingKey
|
||||
*
|
||||
* @param queue
|
||||
* @param exchange
|
||||
* @param routingKey
|
||||
*/
|
||||
public void addBinding(Queue queue, TopicExchange exchange, String routingKey) {
|
||||
Binding binding = BindingBuilder.bind(queue).to(exchange).with(routingKey);
|
||||
rabbitAdmin.declareBinding(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定一个Exchange到一个匹配型Exchange 使用一个routingKey
|
||||
*
|
||||
* @param exchange
|
||||
* @param topicExchange
|
||||
* @param routingKey
|
||||
*/
|
||||
public void addBinding(Exchange exchange, TopicExchange topicExchange, String routingKey) {
|
||||
Binding binding = BindingBuilder.bind(exchange).to(topicExchange).with(routingKey);
|
||||
rabbitAdmin.declareBinding(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 去掉一个binding
|
||||
*
|
||||
* @param binding
|
||||
*/
|
||||
public void removeBinding(Binding binding) {
|
||||
rabbitAdmin.removeBinding(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建交换器
|
||||
*
|
||||
* @param exchangeName
|
||||
* @return
|
||||
*/
|
||||
public DirectExchange createExchange(String exchangeName) {
|
||||
return new DirectExchange(exchangeName, true, false);
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.jero.boot.starter.rabbitmq.config;
|
||||
|
||||
import com.jero.boot.starter.rabbitmq.event.JeroRemoteApplicationEvent;
|
||||
import com.jero.common.config.mqtoken.TransmitUserTokenFilter;
|
||||
import org.springframework.amqp.core.AcknowledgeMode;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.cloud.bus.jackson.RemoteApplicationEventScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 消息队列配置类
|
||||
*
|
||||
* @author zyf
|
||||
*/
|
||||
@Configuration
|
||||
@RemoteApplicationEventScan(basePackageClasses = JeroRemoteApplicationEvent.class)
|
||||
public class RabbitMqConfig {
|
||||
|
||||
|
||||
@Bean
|
||||
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
|
||||
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
//设置忽略声明异常
|
||||
rabbitAdmin.setIgnoreDeclarationExceptions(true);
|
||||
return rabbitAdmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入获取token过滤器
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public TransmitUserTokenFilter transmitUserInfoFromHttpHeader(){
|
||||
return new TransmitUserTokenFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory) {
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
//手动确认
|
||||
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
|
||||
//当前的消费者数量
|
||||
container.setConcurrentConsumers(1);
|
||||
//最大的消费者数量
|
||||
container.setMaxConcurrentConsumers(1);
|
||||
//是否重回队列
|
||||
container.setDefaultRequeueRejected(true);
|
||||
|
||||
//消费端的标签策略
|
||||
/*container.setConsumerTagStrategy(new ConsumerTagStrategy() {
|
||||
@Override
|
||||
public String createConsumerTag(String queue) {
|
||||
return queue + "_" + UUID.randomUUID().toString();
|
||||
}
|
||||
});*/
|
||||
container.setConsumerTagStrategy(queue-> queue + "_" + UUID.randomUUID());
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.jero.boot.starter.rabbitmq.core;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.jero.boot.starter.rabbitmq.listenter.MqListener;
|
||||
import com.jero.common.config.mqtoken.UserTokenContext;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
public class BaseRabbiMqHandler<T> {
|
||||
|
||||
private String token= UserTokenContext.getToken();
|
||||
|
||||
public void onMessage(T t, Long deliveryTag, Channel channel, MqListener<T> mqListener) {
|
||||
try {
|
||||
UserTokenContext.setToken(token);
|
||||
mqListener.handler(t, channel);
|
||||
channel.basicAck(deliveryTag, false);
|
||||
} catch (Exception e) {
|
||||
log.info("接收消息失败,重新放回队列");
|
||||
try {
|
||||
/**
|
||||
* deliveryTag:该消息的index
|
||||
* multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
|
||||
* requeue:被拒绝的是否重新入队列
|
||||
*/
|
||||
channel.basicNack(deliveryTag, false, true);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.jero.boot.starter.rabbitmq.core;
|
||||
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.support.converter.MessageConversionException;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
|
||||
public class MapMessageConverter implements MessageConverter {
|
||||
@Override
|
||||
public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
|
||||
return new Message(object.toString().getBytes(), messageProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromMessage(Message message) throws MessageConversionException {
|
||||
String contentType = message.getMessageProperties().getContentType();
|
||||
if (null != contentType && contentType.contains("text")) {
|
||||
return new String(message.getBody());
|
||||
} else {
|
||||
ObjectInputStream objInt = null;
|
||||
try {
|
||||
ByteArrayInputStream byteInt = new ByteArrayInputStream(message.getBody());
|
||||
objInt = new ObjectInputStream(byteInt);
|
||||
//byte[]转map
|
||||
return objInt.readObject();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.jero.boot.starter.rabbitmq.event;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
|
||||
import com.jero.common.util.SpringContextHolder;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 监听远程事件,并分发消息到业务模块消息处理器
|
||||
*/
|
||||
@Component
|
||||
public class BaseApplicationEvent implements ApplicationListener<JeroRemoteApplicationEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(JeroRemoteApplicationEvent jeroRemoteApplicationEvent) {
|
||||
EventObj eventObj = jeroRemoteApplicationEvent.getEventObj();
|
||||
if (ObjectUtil.isNotEmpty(eventObj)) {
|
||||
//获取业务模块消息处理器
|
||||
JeroBusEventHandler busEventHandler = SpringContextHolder.getHandler(eventObj.getHandlerName(), JeroBusEventHandler.class);
|
||||
if (ObjectUtil.isNotEmpty(busEventHandler)) {
|
||||
//通知业务模块
|
||||
busEventHandler.onMessage(eventObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.jero.boot.starter.rabbitmq.event;
|
||||
|
||||
import lombok.Data;
|
||||
import com.jero.common.base.BaseMap;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 远程事件数据对象
|
||||
*/
|
||||
@Data
|
||||
public class EventObj implements Serializable {
|
||||
/**
|
||||
* 数据对象
|
||||
*/
|
||||
private BaseMap baseMap;
|
||||
/**
|
||||
* 自定义业务模块消息处理器beanName
|
||||
*/
|
||||
private String handlerName;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.jero.boot.starter.rabbitmq.event;
|
||||
|
||||
/**
|
||||
* 业务模块消息处理器接口
|
||||
*/
|
||||
public interface JeroBusEventHandler {
|
||||
void onMessage(EventObj map);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.jero.boot.starter.rabbitmq.event;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
/**
|
||||
* 自定义网关刷新远程事件
|
||||
*
|
||||
* @author : zyf
|
||||
* @date :2020-11-10
|
||||
*/
|
||||
@Data
|
||||
public class JeroRemoteApplicationEvent extends RemoteApplicationEvent {
|
||||
|
||||
private JeroRemoteApplicationEvent() {
|
||||
}
|
||||
|
||||
private EventObj eventObj;
|
||||
|
||||
public JeroRemoteApplicationEvent(EventObj source, String originService, String destinationService) {
|
||||
super(source, originService, destinationService);
|
||||
this.eventObj = source;
|
||||
}
|
||||
|
||||
public JeroRemoteApplicationEvent(EventObj source, String originService) {
|
||||
super(source, originService, null);
|
||||
this.eventObj = source;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.jero.boot.starter.rabbitmq.exchange;
|
||||
|
||||
import org.springframework.amqp.core.CustomExchange;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 延迟交换器构造器
|
||||
* @author: zyf
|
||||
* @date: 2019/3/8 13:31
|
||||
* @description:
|
||||
*/
|
||||
public class DelayExchangeBuilder {
|
||||
/**
|
||||
* 默认延迟消息交换器
|
||||
*/
|
||||
public static final String DEFAULT_DELAY_EXCHANGE = "jero.delayed.exchange";
|
||||
/**
|
||||
* 普通交换器
|
||||
*/
|
||||
public static final String DELAY_EXCHANGE = "jero.direct.exchange";
|
||||
|
||||
private DelayExchangeBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建延迟消息交换器
|
||||
* @return
|
||||
*/
|
||||
public static CustomExchange buildExchange() {
|
||||
Map<String, Object> args = new HashMap<>();
|
||||
args.put("x-delayed-type", "direct");
|
||||
return new CustomExchange(DEFAULT_DELAY_EXCHANGE, "x-delayed-message", true, false, args);
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.jero.boot.starter.rabbitmq.listenter;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
|
||||
public interface MqListener<T> {
|
||||
|
||||
default void handler(T map, Channel channel) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<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>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>byd-structuredplugins</artifactId>
|
||||
<version>2.5.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>laws-starter</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>laws-starter-cloud</module>
|
||||
<module>laws-starter-job</module>
|
||||
<module>laws-starter-lock</module>
|
||||
<module>laws-starter-rabbitmq</module>
|
||||
</modules>
|
||||
<dependencies>
|
||||
<!--jero-tools-->
|
||||
<dependency>
|
||||
<groupId>com.jero.boot</groupId>
|
||||
<artifactId>laws-base-tools</artifactId>
|
||||
</dependency>
|
||||
<!--加载配置信息-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
Reference in New Issue
Block a user