# 一.配置
# 1.配置文件
SpringBoot 使用一个全局的配置文件,配置文件名称固定
- application.properties
- application.yml
配置文件加载顺序:
不同文件类型,同目录:
yml–>yaml–>properties 后加载会覆盖前面加载的
# 2.tomcat 配置
server:
port: 8081
error:
path: /error
servlet:
session:
timeout: 30m
context-path: /start
tomcat:
uri-encoding: utf-8
threads:
max: 500
basedir: /home/tmp
2
3
4
5
6
7
8
9
10
11
12
13
- server.port 配直了 Web 器的端口号。
- error.path 配直了当项目出错时跳转去的页面
- session .timeout 配置了 session 失效时间 30m 表示 30 分钟,如果不写单位 默认单位是秒,由于 Tomcat 中配直 session 过期时间以分钟为单位,因此这里单位如果是秒的话,该时间会被转换为一个不超过所配置秒数的大分钟数, 例如这里配置了 119 ,默认单位为秒,则实际 session 过期时间为 1 分钟
- context-path 表示项目名称,不配置时默认为/。如果配置了,就要在访问路径中加上配置的路径。
- uri-encoding 表示配置 Tomcat 请求编码。
- max threads 表示 Tomcat 最大线程数。
- basedir 是一个存放 Tomcat 运行日志和临时文件的目录,若不配置,则默认使用系统的临时目录
# 3.打包配置
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2
3
4
5
6
7
8
# 4.读取配置内容
读取配置的内容有三种方式,
@Value
Environment
@ConfigurationProperties
@Value
配置文件的名称要和表达式中的值一样, 表达式中有层级关系用.
表示下一层
Environment
注入一个org.springframework.core.env.Environment
的对象, 然后通过 env 对象来获取对象的属性
输入的参数方式和 value 的一样
@ConfigurationProperties
创建一个配置类, 将配置文件中的值全部注入到配置类中,在配置类中加上
Component
,标识这是一个 spring 的 bean
ConfigurationProperties
, 标识这是一个配置类
上面报红的是没有开启处理注解的依赖, 点击文档添加依赖的配置后,在写配置文件的时候会有对应的提示
但是使用时需要注意, ConfigurationProperties 需要指定前缀,不然无法读取到 person 类, 而是将整个 application.yml 文件当成 Person 类,按上面的运行只能读取到 name 是 abc。
# 5.profIle
我们在开发 Spring Boot 应用时,通常同一套程序会被安装到不同环境,比如:开发、测试、生产等。其中数据库地址、服务器端口等等配置都不同,如果每次打包时,都要修改配置文件,那么非常麻烦。profile 功能就是来进行动态配置切换,编写多个配置文件, 启动时用spring.profiles.active
来激活对应的配置文件
profile 激活方式
profile 激活的方式有 3 种,第一种方式是静态的已经在代码中写死, 第 2 3 种在程序启动时给指定具体的参数
- 方式 1,配置文件
配置文件的方式, 就是上面那种在配置文件中指定
- 方式 2, 虚拟机参数
在 VM options 指定:-Dspring-profiles.active:=dev
- 方式 3, 命令行参数
java -jar xxx.jar --spring.profiles.active=dev
- 配置文件: 再配置文件中配置:spring.profiles.active=dev
- 虚拟机参数:在 VM options 指定:-Dspring.profiles.active=dev
- 命令行参数:java –jar xxx.jar --spring.profiles.active=dev
# 6.监控
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2
3
4
http://localhost:8080/acruator
http://localhost:8080/actuator/info
info.name=lucy
info.age=99
2
http://localhost:8080/actuator/health
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=*
2
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"health-component-instance": {
"href": "http://localhost:8080/actuator/health/{component}/{instance}",
"templated": true
},
"health-component": {
"href": "http://localhost:8080/actuator/health/{component}",
"templated": true
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 二.基础配置
# 1.application 配置
- spring.application.admin.enabled 是否启用 admin 特性,默认为: false
- spring.application.admin.jmx-name 指定 admin MBean 的名称,默认为: org.springframework.boot:type=Admin,name=SpringApplication
# 2.aop 配置
- spring.aop.auto 是否支持@EnableAspectJAutoProxy,默认为: true
- spring.aop.proxy-target-class true 为使用 CGLIB 代理,false 为 JDK 代理,默认为 false
# 3.autoconfig 配置
- spring.autoconfigure.exclude 配置要排除的 Auto-configuration classes.
# 4.server 通用配置
- server.port 设定 http 监听端口。此配置项最为常见。
- server.compression.enabled 是否开启压缩,默认为 false.
- server.display-name 设定应用的展示名称,默认: application
# 5.multipart 文件上传配置
- multipart.enabled 是否开启文件上传支持,默认为 true
- multipart.file-size-threshold 设定文件写入磁盘的阈值,单位为 MB 或 KB,默认为 0
- multipart.location 指定文件上传路径.
- multipart.max-file-size 指定文件大小最大值,默认 1MB
- multipart.max-request-size 指定每次请求的最大值,默认为 10MB
# 6.banner 生成网站
- 第一种 (opens new window)
- 第二种 (opens new window)
- 第三种 (opens new window)
- 第四种 (opens new window)
- 第五种 (opens new window)
# 7.banner 配置
- 首先我们需要在项目的 resource 文件夹下新建新文件,文件名命名为 banner.txt,我们需要自定义的图形就放在该文件里面。
- 我们可以在该网站生成需要的图案 (opens new window)
- 复制网站生成的相关字体信息到 banner.txt 文件中。
- 修改 SpringApplication 启动类
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(StarBootApplication.class);
springApplication.setBannerMode(Banner.Mode.CONSOLE);
springApplication.run(args);
}
2
3
4
5
# 8.修改 banner 里面的版本号
在 SringBoot 启动的时候会打印 SpringBoot 的版本号,这个同样是在 banner.txt 文件中设置,在 banner.txt 文件中加入以下代码,即可显示自己项目的版本号和 Springboot 项目的版本号。AnsiColor 主要是设置颜色。
${AnsiColor.BRIGHT_GREEN}
Project Version: ${project-name.version} ${project-name.formatted-version}
Spring Boot Version: ${spring-boot.version}${spring-boot.formatted-version}
${AnsiColor.BLACK}
2
3
4
其中${project-name.version} ${project-name.formatted-version}是在 yml 文件中设置的,设置如下:配置里的 version 取的是 pom 文件中的 version 标签。
project-name:
version: @version@
formatted-version: (v@version@)
2
3
# 三.yaml
# 1.语法格式
说明:语法要求严格!
1、空格不能省略
2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
3、属性和值的大小写都是十分敏感的。
# 2.字面量
字面量:普通的值 [ 数字,布尔值,字符串 ]
k: v
# 3.对象和 Map
对象、Map(键值对)
#对象、Map格式
k:
v1:
v2:
2
3
4
student:
name: qinjiang
age: 3
2
3
student: { name: qinjiang, age: 3 }
# 4.数组
数组( List、set )
pets:
- cat
- dog
- pig
2
3
4
pets: [cat, dog, pig]
# 5.应用
@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
2
3
4
5
6
7
8
9
10
11
person:
name: qinjiang
age: 3
happy: false
birth: 2000/01/01
maps: { k1: v1, k2: v2 }
lists:
- code
- girl
- music
dog:
name: 旺财
age: 1
2
3
4
5
6
7
8
9
10
11
12
13
# 6.自动提示
<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
2
3
4
5
6
# 7.数组实操
csdn:
self_comment: 大帅b1号; 大帅b2号;大帅b3号
2
@Value("#{'${csdn.self_comment}'.split(';')}")
private String[] selfComment;
2
# 四.常见问题
# 1.生成代码
#官网
https://start.spring.io/
#阿里云提供脚手架
https://start.aliyun.com/bootstrap.html
2
3
4
5
或者使用阿里云提供的 spring 脚手架, 功能更加强大基本上覆盖前者所有,同时有更多阿里开源组件的选择
# 2.cros 跨域支持
@Configuration
public class MyConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 3.自定义过滤器
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
}
2
3
4
5
6
7
8
9
# 4.重定向
@GetMapping("/redirect/{id}")
public void redirect(@PathVariable("id") String id, HttpServletResponse resp) throws IOException {
String redirectUri = "http://www.baidu.com";
resp.sendRedirect(redirectUri);
}
2
3
4
5
# 5.时间格式化
@JsonFormat(shape =JsonFormat.Shape.STRING,pattern ="yyyy-MM-dd HH:mm:ss",timezone ="GMT+8")
private LocalDate createTime;
2
# 6.启动参数
--spring.profiles.active=dev --server.port=8090
# 7.@Scheduled 动态配置 cron
1.application.yml
scheduled:
cron:
test: "*/10 * * * * ?"
2
3
2.启动类
@SpringBootApplication
@EnableScheduling
public class AlgApplication {
public static void main(String[] args) {
SpringApplication.run(AlgApplication.class, args);
}
}
2
3
4
5
6
7
3.测试
@Component
@Slf4j
public class TimeTask {
@Scheduled(cron = "${scheduled.cron.test}")
public void task() {
log.info("开始定时任务");
}
}
2
3
4
5
6
7
8
4.结果
# 8.关闭定时任务
直接注释@EnableScheduling 注解即可
@EnableSwagger2
@EnableScheduling
@SpringBootApplication
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
System.out.println("------------------vue-springboot-kwan应用启动成功--------------------");
}
}
2
3
4
5
6
7
8
9
10
# 9.定时任务
启动类添加@EnableScheduling 注解
@Slf4j
@Component
public class ScheduleCacheTagInfo {
@Autowired
private RedisCacheService redisCacheService;
/**
* 每天的9点,12点,16点执行一次
*/
@Scheduled(cron = "0 0 9,12,16 * * ?")
public void execute() {
log.info("缓存数据 start ");
StopWatch stopWatch = new StopWatch();
stopWatch.start("首页tag数据缓存到redis中");
redisCacheService.oneDay(BrandDetailNoEnum.BS.getCode(), DateTimeUtils.date2DateStr(DateUtil.yesterday()));
stopWatch.stop();
log.info("缓存数据累计耗时信息={}", JSON.toJSONString(stopWatch.getTaskInfo()));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 10.StopWatch
public void execute() {
log.info("缓存数据 start ");
StopWatch stopWatch = new StopWatch();
stopWatch.start("首页tag数据缓存到redis中");
redisCacheService.oneDay(BrandDetailNoEnum.BS.getCode(), DateTimeUtils.date2DateStr(DateUtil.yesterday()));
stopWatch.stop();
log.info("缓存数据累计耗时信息={}", JSON.toJSONString(stopWatch.getTaskInfo()));
}
2
3
4
5
6
7
8
# 11.工具类
HttpMessageConverter
是 Spring 框架中的一个组件,用于处理 HTTP 请求和响应的消息转换。它的作用是将 Java 对象与 HTTP 请求或响应的内容进行相互转换,常见的转换包括 JSON、XML、Form 表单等。
在 Spring MVC 中,当处理请求时,会根据请求的内容类型(Content-Type)和方法参数的类型来确定使用哪个 HttpMessageConverter
进行消息转换。如果请求的内容类型是 JSON,并且方法参数的类型是一个 Java 对象,则通常会使用 Jackson 库进行 JSON 的序列化和反序列化。
具体来说,当请求到达 Spring MVC 控制器的方法时,Spring 会根据配置的 HttpMessageConverter
列表进行匹配,找到能够处理请求内容类型的合适转换器。如果找到了支持 JSON 的转换器,且方法参数的类型是一个 Java 对象,那么就会使用 Jackson 进行序列化或反序列化操作。
需要注意的是,要使用 Jackson 进行 JSON 的序列化和反序列化,需要在项目中添加 Jackson 的相关依赖,并正确配置 HttpMessageConverter
。另外,如果你使用的是 Spring Boot,它会自动配置 Jackson 的 HttpMessageConverter
,无需额外的配置。
总结起来,HttpMessageConverter
在处理请求时,会根据请求的内容类型和方法参数的类型来确定是否使用 Jackson 进行序列化。如果请求的内容类型是 JSON,并且方法参数的类型是一个 Java 对象,则会使用 Jackson 进行序列化操作。
- 工具类的使用
- getColor 会给前端加一个 color 字段
public interface SkuSizeColorData {
/**
* 获取当天尺码库存
*
* @return
*/
Integer getInvQty();
/**
* 获取当天尺码销量
*
* @return
*/
Integer getSalQty();
/**
* 获取当前尺码累计销量
*
* @return
*/
Integer getTotalSalQty();
/**
* 获取当天sku尺码累计库存
*
* @return
*/
Integer getSkuInvQty();
/**
* 进销存标识-颜色标识-0:灰色(无库存/未铺) 1:绿色(有库存) 2:红色(缺货) 3:黄色(有销售)
*
* @return
*/
default Integer getColor() {
final Integer salQty = this.getSalQty();
if (salQty > 0) return FlagEnum.YELLOW.getCode();
final Integer invQty = this.getInvQty();
if (invQty > 0) return FlagEnum.GREEN.getCode();
final Integer skuInvQty = this.getSkuInvQty();
if (skuInvQty <= 0) return FlagEnum.GREY.getCode();
final Integer totalSalQty = this.getTotalSalQty();
if (totalSalQty > 0) return FlagEnum.RED.getCode();
return FlagEnum.GREY.getCode();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Jackson 序列化的时候会去扫 getter 和 setter 方法 get 方法只是起因,并没有触发把 color 加到 dto 上,因为没有调用 setter 方法,也没有 color 的 setter 方法,也就是说使用 get 方法是获取不到 color 值的,jackson 扫描的时候,扫到了接口中的 default 修饰的 getColor 方法,把 color 当做一个字段填充到 json 字符串中了,不是填充到 dto 中了
# 12.如何打日志?
- 你想要知道什么?
- 你如何清晰的知道什么?
- 如何简洁的知道大量的信息?
# 13.设置超时时间
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS) // 设置读取超时时间为30秒
.build();
2
3
# 五.敏感词过滤
# 1.引入工具包
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-word</artifactId>
<version>0.2.1</version>
</dependency>
2
3
4
5
# 2.配置类
@Configuration
public class SensitiveConfig {
// 配置默认敏感词 + 自定义敏感词
IWordDeny wordDeny = WordDenys.chains(WordDenys.system(), new MyWordDeny());
// 配置默认非敏感词 + 自定义非敏感词
IWordAllow wordAllow = WordAllows.chains(WordAllows.system(), new MyWordAllow());
@Bean
public SensitiveWordBs sensitiveWordBs() {
return SensitiveWordBs.newInstance()
// 忽略大小写
.ignoreCase(true)
// 忽略半角圆角
.ignoreWidth(true)
// 忽略数字的写法
.ignoreNumStyle(true)
// 忽略中文的书写格式:简繁体
.ignoreChineseStyle(true)
// 忽略英文的书写格式
.ignoreEnglishStyle(true)
// 忽略重复词
.ignoreRepeat(false)
// 是否启用数字检测
.enableNumCheck(true)
// 是否启用邮箱检测
.enableEmailCheck(true)
// 是否启用链接检测
.enableUrlCheck(true)
// 数字检测,自定义指定长度
.numCheckLen(8)
// 配置自定义敏感词
.wordDeny(wordDeny)
// 配置非自定义敏感词
.wordAllow(wordAllow)
.init();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 3.自定义
public class MySensitiveWordReplace implements ISensitiveWordReplace {
@Override
public String replace(ISensitiveWordReplaceContext context) {
String sensitiveWord = context.sensitiveWord();
// 自定义不同的敏感词替换策略,可以从数据库等地方读取
if ("五星红旗".equals(sensitiveWord)) {
return "国家旗帜";
}
if ("毛主席".equals(sensitiveWord)) {
return "教员";
}
// 其他默认使用 * 代替
int wordLength = context.wordLength();
return CharUtil.repeat('*', wordLength);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
public class MyWordAllow implements IWordAllow {
@Override
public List<String> allow() {
List<String> list = new ArrayList<>();
;
try {
Resource myAllowWords = new ClassPathResource("myNotSensitiveWords.txt");
Path myAllowWordsPath = Paths.get(myAllowWords.getFile().getPath());
list = Files.readAllLines(myAllowWordsPath, StandardCharsets.UTF_8);
} catch (IOException ioException) {
log.error("读取非敏感词文件错误!" + ioException.getMessage());
}
return list;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Slf4j
public class MyWordDeny implements IWordDeny {
@Override
public List<String> deny() {
List<String> list = new ArrayList<>();
try {
Resource mySensitiveWords = new ClassPathResource("mySensitiveWords.txt");
Path mySensitiveWordsPath = Paths.get(mySensitiveWords.getFile().getPath());
list = Files.readAllLines(mySensitiveWordsPath, StandardCharsets.UTF_8);
} catch (IOException ioException) {
log.error("读取敏感词文件错误!" + ioException.getMessage());
}
return list;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 4.在 resources 下添加文件
文件以及文件内容如下
非敏感: myNotSensitiveWords.txt
小码哥
敏感: mySensitiveWords.txt
国家
马化腾
2
# 5.工具类
@Component
public class SensitiveWordUtil {
@Autowired
private SensitiveWordBs sensitiveWordBs;
// 刷新敏感词库与非敏感词库缓存
public void refresh() {
sensitiveWordBs.init();
}
// 判断是否含有敏感词
public boolean contains(String text) {
return sensitiveWordBs.contains(text);
}
// 指定替换符进行替换敏感词
public String replace(String text, char replaceChar) {
return sensitiveWordBs.replace(text, replaceChar);
}
// 使用默认替换符 * 进行替换敏感词
public String replace(String text) {
return sensitiveWordBs.replace(text);
}
// 返回所有敏感词
public List<String> findAll(String text) {
return sensitiveWordBs.findAll(text);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 6.测试
import com.kwan.springbootkwan.utils.SensitiveWordUtil;
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.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = SpringBootKwanApplication.class)
public class SensitiveTest {
@Autowired
private SensitiveWordUtil sensitiveWordUtil;
@Test
public void test() {
String result = sensitiveWordUtil.replace("国家 哇 nnd 复活 马化腾");
System.out.println(result);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 7.结果
# 六.新项目配置
# 1.启动配置文件
application.yml
spring:
profiles:
active: local
2
3
application-local.yml
server:
servlet:
context-path: /bfp-o2o
#Spring 相关配置
spring:
autoconfigure:
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
datasource:
dynamic:
#设置默认的数据源或者数据源组,默认值即为master
primary: master
strict: false
datasource:
master:
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://10.239.183.66:3306/o2o?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false
# url: jdbc:mysql://10.201.1.222:3306/o2o?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false
username: root
password: cw68_sit@crv
o2o:
jd:
file:
remotePath: /home/ftpuser/jd/
remoteFileName: jd-back.csv
localPath: ./data/jd/
localFileName: jd-back.csv
mt:
file:
remotePath: /home/ftpuser/meituan/
remoteFileName: mt.csv
localPath: ./data/meituan/
localFileName: mt.csv
eleme:
file:
remotePath: /home/ftpuser/eleme/
remoteFileName: eleme.csv
localPath: ./data/eleme/
localFileName: eleme.csv
shop_ids_url: https://o2o-api.crv.com.cn/open_api/v1/dc-shop
suguo_url: https://wei-test.vipxiaoqu.com/wty-test
sftp:
name: ftpuser
passwd: ftpP#ssw0rd
ipAddr: 10.201.1.223
port: 22
#mybatis-plus配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
call-setters-on-nulls: true
auto-mapping-behavior: full
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:mapper/**/*Mapper.xml
global-config:
banner: false
# 逻辑删除配置
db-config:
id-type: AUTO
logic-delete-field: delFlag
logic-delete-value: 1
logic-not-delete-value: 0
table-underline: true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
bootstrap.yml
spring:
application:
name: o2o-service
cloud:
nacos:
config:
file-extension: yaml
2
3
4
5
6
7
启动参数
-Dspring.cloud.nacos.discovery.serverAddr=10.239.165.190:80 -Dspring.cloud.nacos.discovery.namespace=bfp-sit -Dspring.cloud.nacos.config.serverAddr=10.239.165.190:80 -Dspring.cloud.nacos.config.namespace=bfp-sit
# 2..gitignore 文件
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 3.logback.xml 日志文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<!--定义日志文件的存储地址 -->
<property name="APPDIR" value="o2o-api"/>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>log/${APPDIR}.out</File>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 添加.gz 历史日志会启用压缩 大大缩小日志文件所占空间 -->
<fileNamePattern>log/daily/${APPDIR}.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>7</maxHistory><!-- 保留7天日志 -->
</rollingPolicy>
</appender>
<!--myibatis log configure -->
<logger name="com.apache.ibatis" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
<logger name="com.wanjia.o2o" level="INFO"/>
<!-- 日志输出级别 -->
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 4.Swagger2Config
package org.jeecg.config;
import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.shiro.vo.DefContants;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.Parameter;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @Author scott
*/
@Slf4j
@Configuration
@EnableSwagger2
@EnableSwaggerBootstrapUI
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public class Swagger2Config implements WebMvcConfigurer {
/**
* 显示swagger-ui.html文档展示页,还必须注入swagger资源:
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
Predicate<RequestHandler> selector1 = RequestHandlerSelectors.basePackage("org.jeecg.modules");
Predicate<RequestHandler> selector2 = RequestHandlerSelectors.basePackage("com.eastroc");
/**
* swagger2的配置文件,这里可以配置swagger2的一些基本的内容,比如扫描的包等等
*
* @return Docket
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo()).groupName("预约系统")
.select()
//此包路径下的类,才生成接口文档
// .apis(RequestHandlerSelectors.basePackage("org.jeecg.modules"))
//加了ApiOperation注解的类,才生成接口文档
.apis(Predicates.or(selector1, selector2))
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(securityScheme()))
.globalOperationParameters(setHeaderToken());
}
@Bean
public Docket createVrsRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo()).groupName("ARRANGE模块")
.select()
//此包路径下的类,才生成接口文档
.apis(RequestHandlerSelectors.basePackage("com.eastroc"))
//加了ApiOperation注解的类,才生成接口文档
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(securityScheme()));
// .globalOperationParameters(setHeaderToken());
}
/***
* oauth2配置
* 需要增加swagger授权回调地址
* http://localhost:8888/webjars/springfox-swagger-ui/o2c.html
* @return
*/
@Bean
SecurityScheme securityScheme() {
return new ApiKey(DefContants.X_ACCESS_TOKEN, DefContants.X_ACCESS_TOKEN, "header");
}
/**
* JWT token
*
* @return
*/
private List<Parameter> setHeaderToken() {
ParameterBuilder tokenPar = new ParameterBuilder();
List<Parameter> pars = new ArrayList<>();
tokenPar.name(DefContants.X_ACCESS_TOKEN).description("token").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
pars.add(tokenPar.build());
return pars;
}
/**
* api文档的详细信息函数,注意这里的注解引用的是哪个
*
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
// //大标题
.title("Jeecg-Boot 后台服务API接口文档")
// 版本号
.version("1.0")
// .termsOfServiceUrl("NO terms of service")
// 描述
.description("后台API接口")
// 作者
.contact("JEECG团队")
.license("The Apache License, Version 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.build();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* @author : qinyingjie
* @date : 2021/10/26
* @desc : http://localhost:8080/ant/doc.html#/home
*/
@Configuration
@EnableOpenApi //开启 Swagger3 ,可不写
@EnableKnife4j //开启 knife4j ,可不写
public class Knife4jConfig {
@Bean
public Docket createRestApi() {
// Swagger 2 使用的是:DocumentationType.SWAGGER_2
// Swagger 3 使用的是:DocumentationType.OAS_30
return new Docket(DocumentationType.OAS_30)
// 定义是否开启swagger,false为关闭,可以通过变量控制
.enable(true)
// 将api的元信息设置为包含在json ResourceListing响应中。
.apiInfo(new ApiInfoBuilder()
.title("ANT接口文档")
// 描述
.description("平台服务管理api")
.contact(new Contact("秦英杰", "深圳", "327782001@qq.com"))
.version("1.0.0")
.build())
// 分组名称
.groupName("1.0")
// 选择哪些接口作为swagger的doc发布
.select()
// 要扫描的API(Controller)基础包
.apis(RequestHandlerSelectors.basePackage("com.xiaofei"))
// .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 5.WebConfigurer
package com.eastroc.login;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.eastroc.common.enums.RoleEnum;
import com.eastroc.common.service.ISystemFixService;
import com.eastroc.common.utils.LoginUtils;
import com.eastroc.mine.entity.SystemFix;
import com.eastroc.mine.entity.UserWechat;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.exception.RoleException;
import org.jeecg.common.exception.SystemMaintenanceException;
import org.jeecg.common.exception.UserException;
import org.jeecg.common.util.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@Slf4j
@Component
public class XcxRoleInterceptor extends HandlerInterceptorAdapter {
@Autowired
private ISystemFixService systemFixService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
List<SystemFix> systemFixes = systemFixService.list();
if (CollectionUtils.isNotEmpty(systemFixes)) {
SystemFix systemFix = systemFixes.get(0);
//系统维护通知
long startInterval = DateUtil.between(systemFix.getFixStart(), new Date(), DateUnit.MS, false);
long endInterval = DateUtil.between(new Date(), systemFix.getFixEnd(), DateUnit.MS, false);
if (startInterval > 0 && endInterval > 0) {
String endDate = DateUtils.date2Str(systemFix.getFixEnd(), DateUtils.datetimeFormat.get());
throw new SystemMaintenanceException("系统维护中,维护时间至:" + endDate);
}
}
UserWechat user = LoginUtils.getCurrentUser(request);
if (Objects.isNull(user)) {
throw new UserException("登录信息没有" + request.getRequestURL());
}
if (Objects.isNull(user.getRoleId())) {
throw new UserException("登录角色没有" + request.getRequestURL() + "////" + user.toString());
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
XcxRole role = method.getAnnotation(XcxRole.class);
if (Objects.isNull(role)) {
return true;
}
if (role.roles().length > 0) {
Integer currentRole = user.getRoleId();
for (RoleEnum annotationRole : role.roles()) {
if (currentRole.equals(annotationRole.getType())) {
return true;
}
}
}
throw new RoleException("用户越权访问接口");
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# 6.Result
package org.jeecg.common.api.vo;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.jeecg.common.constant.CommonConstant;
import lombok.Data;
/**
* 接口返回数据格式
* @author scott
* @email jeecgos@163.com
* @date 2019年1月19日
*/
@Data
@ApiModel(value="接口返回对象", description="接口返回对象")
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 成功标志
*/
@ApiModelProperty(value = "成功标志")
private boolean success = true;
/**
* 返回处理消息
*/
@ApiModelProperty(value = "返回处理消息")
private String message = "操作成功!";
/**
* 返回代码
*/
@ApiModelProperty(value = "返回代码")
private Integer code = 0;
/**
* 返回数据对象 data
*/
@ApiModelProperty(value = "返回数据对象")
private T result;
/**
* 时间戳
*/
@ApiModelProperty(value = "时间戳")
private long timestamp = System.currentTimeMillis();
public Result() {
}
public Result<T> success(String message) {
this.message = message;
this.code = CommonConstant.SC_OK_200;
this.success = true;
return this;
}
public Result<T> good(T t) {
this.setResult(t);
this.code = CommonConstant.SC_OK_200;
this.success = true;
return this;
}
public Result<T> good() {
this.code = CommonConstant.SC_OK_200;
this.success = true;
this.setMessage("成功");
return this;
}
public Result<T> fail(String msg) {
this.setCode(CommonConstant.SC_INTERNAL_SERVER_ERROR_500);
this.setMessage(msg);
this.setSuccess(false);
return this;
}
public static Result<Object> ok() {
Result<Object> r = new Result<Object>();
r.setSuccess(true);
r.setCode(CommonConstant.SC_OK_200);
r.setMessage("成功");
return r;
}
public static Result<Object> ok(String msg) {
Result<Object> r = new Result<Object>();
r.setSuccess(true);
r.setCode(CommonConstant.SC_OK_200);
r.setMessage(msg);
return r;
}
public static Result<Object> ok(Object data) {
Result<Object> r = new Result<Object>();
r.setSuccess(true);
r.setCode(CommonConstant.SC_OK_200);
r.setResult(data);
return r;
}
public static Result<Object> error(String msg) {
return error(CommonConstant.SC_INTERNAL_SERVER_ERROR_500, msg);
}
public static Result<Object> error(int code, String msg) {
Result<Object> r = new Result<Object>();
r.setCode(code);
r.setMessage(msg);
r.setSuccess(false);
return r;
}
public Result<T> error500(String message) {
this.message = message;
this.code = CommonConstant.SC_INTERNAL_SERVER_ERROR_500;
this.success = false;
return this;
}
/**
* 无权限访问返回结果
*/
public static Result<Object> noauth(String msg) {
return error(CommonConstant.SC_JEECG_NO_AUTHZ, msg);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# 7.JeecgBootException
package org.jeecg.common.exception;
public class JeecgBootException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code;
public JeecgBootException(String message) {
super(message);
}
public JeecgBootException(Integer code, String message) {
super(message);
this.code = code;
}
public JeecgBootException(Throwable cause) {
super(cause);
}
public JeecgBootException(String message, Throwable cause) {
super(message, cause);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 8.JeecgBootExceptionHandler
package org.jeecg.common.exception;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.jeecg.common.api.vo.Result;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.connection.PoolException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;
import java.util.Arrays;
/**
* 异常处理器
*
* @Author scott
* @Date 2019
*/
@Slf4j
@RestControllerAdvice
public class JeecgBootExceptionHandler {
@Value("${profileCurrentValue}")
private String profileCurrentValue;
/**
* 处理自定义异常
*/
@ExceptionHandler(JeecgBootException.class)
public Result<?> handleRRException(JeecgBootException e) {
log.error(e.getMessage(), e);
return Result.error(e.getMessage());
}
@ExceptionHandler(RoleException.class)
public Result<?> handleRoleException(RoleException e) {
log.error(e.getMessage(), e);
return Result.error("用户角色异常");
}
@ExceptionHandler(UserException.class)
public Result<?> handleUserException(UserException e) {
log.error(e.getMessage(), e);
return Result.error("用户登录异常");
}
@ExceptionHandler(SystemMaintenanceException.class)
public Result<?> handleSystemMaintenanceException(SystemMaintenanceException e) {
log.error(e.getMessage(), e);
return Result.error(e.getMessage());
}
@ExceptionHandler(NoHandlerFoundException.class)
public Result<?> handlerNoFoundException(Exception e) {
log.error(e.getMessage(), e);
return Result.error(404, "路径不存在,请检查路径是否正确");
}
@ExceptionHandler(DuplicateKeyException.class)
public Result<?> handleDuplicateKeyException(DuplicateKeyException e) {
log.error(e.getMessage(), e);
return Result.error("数据库中已存在该记录");
}
@ExceptionHandler({UnauthorizedException.class, AuthorizationException.class})
public Result<?> handleAuthorizationException(AuthorizationException e) {
log.error(e.getMessage(), e);
return Result.noauth("没有权限,请联系管理员授权");
}
@ExceptionHandler({Exception.class})
public Result<?> handleException(Exception e) {
log.error(e.getMessage(), e);
String env = "小程序";
if ("dev".equals(profileCurrentValue)) {
return Result.error("操作失败," + e.getMessage());
// env = "开发环境";
} else if ("test".equals(profileCurrentValue)) {
env += "测试环境";
} else if ("prod".equals(profileCurrentValue)) {
env += "生产环境";
}
HttpRequest.post("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=8315d403-91d3-4bc2-bc72-91bd92a38a51")
.header(Header.CONTENT_TYPE, "application/json")
.body(" {\n" +
" \"msgtype\": \"text\",\n" +
" \"text\": {\n" +
" \"content\": \"" + env + "\n" + e.toString() + "\n" + Arrays.toString(e.getStackTrace()).substring(0, 500) + "\"\n" +
" }\n" +
" }")
.execute();
return Result.error("操作失败," + e.getMessage());
}
/**
* 自定义系统交互异常
*/
@ExceptionHandler(SystemException.class)
public Result<?> handleSystemException(SystemException e) {
log.error(e.getMessage(), e);
String env = "小程序";
if ("dev".equals(profileCurrentValue)) {
// return Result.error("操作失败," + e.getMessage());
env = "开发环境";
} else if ("test".equals(profileCurrentValue)) {
env += "测试环境";
} else if ("prod".equals(profileCurrentValue)) {
env += "生产环境";
}
env = env + "--系统交互异常";
HttpRequest.post("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=8315d403-91d3-4bc2-bc72-91bd92a38a51")
.header(Header.CONTENT_TYPE, "application/json")
.body(" {\n" +
" \"msgtype\": \"text\",\n" +
" \"text\": {\n" +
" \"content\": \"" + env + "\n" + e.toString() + "\n" + e.getMessage() + "\"\n" +
" }\n" +
" }")
.execute();
return Result.error(e.getMessage());
}
/**
* @param e
* @return
* @Author 政辉
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result<?> HttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
StringBuffer sb = new StringBuffer();
sb.append("不支持");
sb.append(e.getMethod());
sb.append("请求方法,");
sb.append("支持以下");
String[] methods = e.getSupportedMethods();
if (methods != null) {
for (String str : methods) {
sb.append(str);
sb.append("、");
}
}
log.error(sb.toString(), e);
//return Result.error("没有权限,请联系管理员授权");
return Result.error(405, sb.toString());
}
/**
* spring默认上传大小100MB 超出大小捕获异常MaxUploadSizeExceededException
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
public Result<?> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
log.error(e.getMessage(), e);
return Result.error("文件大小超出10MB限制, 请压缩或降低文件质量! ");
}
@ExceptionHandler(DataIntegrityViolationException.class)
public Result<?> handleDataIntegrityViolationException(DataIntegrityViolationException e) {
log.error(e.getMessage(), e);
return Result.error("字段太长,超出数据库字段的长度");
}
@ExceptionHandler(PoolException.class)
public Result<?> handlePoolException(PoolException e) {
log.error(e.getMessage(), e);
return Result.error("Redis 连接异常!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package com.xiaofei.common.constant;
public class CommonConstant {
/**
* 成功
*/
public static final Integer SC_OK_200 = 200;
/**
* 服务器错误
*/
public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
/**
* 未认证
*/
public static final int SC_JEECG_NO_AUTHZ = 401;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 9.MybatisPlusConfig
package org.jeecg.config;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.jeecg.config.mybatis.MetaHandler;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.aop.interceptor.PerformanceMonitorInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
* 单数据源配置(jeecg.datasource.open = false时生效)
*
* @Author zhoujf
*/
@Configuration
@MapperScan(value = {"org.jeecg.modules.**.mapper*", "com.eastroc.**.mapper*"})
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
// 设置sql的limit为无限制,默认是500
return new PaginationInterceptor().setLimit(-1);
}
/**
* 自动填充功能
*
* @return
*/
@Bean
public GlobalConfig globalConfig() {
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setMetaObjectHandler(new MetaHandler());
return globalConfig;
}
@Bean
@Profile({"dev", "test"}) // 在dev和test环境中开启 SQL执行效率插件【生产环境可以关闭】
public PerformanceMonitorInterceptor performanceMonitorInterceptor() {
return new PerformanceMonitorInterceptor();
}
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.xiaofei.common.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.function.Supplier;
public class MetaHandler implements MetaObjectHandler {
private static String CREATE_TIME = "createTime";
private static String UPDATE_TIME = "updateTime";
/**
* 插入使用
*/
@Override
public void insertFill(MetaObject metaObject) {
fillValue(metaObject, CREATE_TIME, () -> getDateValue(metaObject.getSetterType(CREATE_TIME)));
fillValue(metaObject, UPDATE_TIME, () -> getDateValue(metaObject.getSetterType(UPDATE_TIME)));
}
/**
* 更新使用
*/
@Override
public void updateFill(MetaObject metaObject) {
fillValue(metaObject, "et." + UPDATE_TIME, () -> getDateValue(metaObject.getSetterType("et." + UPDATE_TIME)));
}
private void fillValue(MetaObject metaObject, String fieldName, Supplier<Object> valueSupplier) {
if (!metaObject.hasGetter(fieldName)) {
return;
}
Object sidObj = metaObject.getValue(fieldName);
if (sidObj == null && metaObject.hasSetter(fieldName) && valueSupplier != null) {
setFieldValByName(fieldName, valueSupplier.get(), metaObject);
}
}
private Object getDateValue(Class<?> setterType) {
if (Date.class.equals(setterType)) {
return new Date();
} else if (LocalDateTime.class.equals(setterType)) {
return LocalDateTime.now();
} else if (Long.class.equals(setterType)) {
return System.currentTimeMillis();
}
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 10.RedisLock
package org.jeecg.common.aspect.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
/**
* 获取方法上的参数值生成key,spEl表达式:例如'#{data}'或者 '#{data.value}'或者 '#{data.value1}&#{data.value2}'
*/
String key() default "";
/**
* 自定义后缀,字符串,加在key的后面
*/
String suffix() default "";
/**
* key过期时间,毫秒,默认10秒
*/
String expireTime() default "10000";
/**
* 是否包含token值,需要请求头里包含token
*/
boolean includeToken() default false;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@RedisLock(key = "#data")
@AutoLog(value = "供应链新增订单到预约系统")
@ApiOperation(value = "供应链新增订单到预约系统", notes = "供应链新增订单到预约系统")
@PostMapping(value = "/expressOrder")
public Map<String, Object> expressOrder(@RequestBody String data) {}
2
3
4
5
package org.jeecg.modules.system.aspect;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
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.common.aspect.annotation.RedisLock;
import org.jeecg.common.util.redis.JedisClient;
import org.jeecg.common.util.redis.RedisTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.UUID;
@Slf4j
@Aspect
@Configuration
public class RedisLockAspect {
@Autowired
private JedisClient jedisClient;
@Around(value = "@annotation(org.jeecg.common.aspect.annotation.RedisLock)")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
RedisLock redisLock = method.getAnnotation(RedisLock.class);
Object[] arguments = pjp.getArgs();
//解析SpEL表达式
String key = String.valueOf(parseSpel(redisLock.key(), method, arguments));
//解析UserId表达式
String token = parseToken(redisLock.includeToken());
Object result;
String methodName = pjp.getSignature().getName();
//唯一标识
String requestId = UUID.randomUUID().toString();
String lockKey = "vrs_" + methodName + "_" + token + key;
//后缀
if (StringUtils.isNotBlank(redisLock.suffix())) {
lockKey = lockKey + "_" + redisLock.suffix();
}
try {
//添加分布式锁
boolean res = RedisTool.tryGetDistributedLock(jedisClient.getJedis(), lockKey, requestId, Integer.parseInt(redisLock.expireTime()));
if (res) {
//执行
result = pjp.proceed();
} else {
log.info("重复提交的请求: " + lockKey);
throw new Exception("重复提交的请求");
}
} catch (Exception e) {
log.error(e.getMessage(), e);
throw e;
} finally {
//移除分布式锁
RedisTool.releaseDistributedLock(jedisClient.getJedis(), lockKey, requestId);
log.info("释放分布式锁成功: " + lockKey);
}
return result;
}
private String parseToken(boolean includeToken) {
if (includeToken) {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
if (sra != null && sra.getRequest() != null) {
HttpServletRequest request = sra.getRequest();
Object token = request.getHeader("token_manage");
return token == null ? "" : token.toString();
}
}
return "";
}
/**
* 解析SpEL表达式
* @param key SpEL表达式
* @param method 反射得到的方法
* @param args 反射得到的方法参数
* @return 解析后SpEL表达式对应的值
*/
private Object parseSpel(String key, Method method, Object[] args) {
if (StringUtils.isNotBlank(key)) {
// 创建解析器
ExpressionParser parser = new SpelExpressionParser();
// 通过Spring的LocalVariableTableParameterNameDiscoverer获取方法参数名列表
LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
// 构造上下文
EvaluationContext context = new StandardEvaluationContext();
if (args.length == parameterNames.length) {
for (int i = 0, len = args.length; i < len; i++) {
// 使用setVariable方法来注册自定义变量
context.setVariable(parameterNames[i], args[i]);
}
}
String[] keys = key.split("&");
StringBuilder sb = new StringBuilder();
if (keys.length > 0) {
for (String k : keys) {
sb.append(parser.parseExpression(k).getValue(context));
}
}
return sb.toString();
}
return "";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# 11.redis
package org.jeecg.common.util.redis;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import java.util.Collections;
@Component
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX"; // NX 代表只在键不存在时,才对键进行设置操作
private static final String SET_WITH_EXPIRE_TIME = "PX"; // PX 5000 设置键的过期时间为5000毫秒
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
/**
* 1、把key、value set到redis中,隐含覆盖,默认的ttl是-1(永不过期)
*
* 2、根据第三个参数,把key、value set到redis中
* nx : not exists, 只有key 不存在时才把key value set 到redis
* xx : is exists ,只有 key 存在是,才把key value set 到redis
*
* 3、4 和2 就相同,只是多加了个过期时间
* expx参数有两个值可选 :
* ex : seconds 秒
* px : milliseconds 毫秒
* 使用其他值,抛出 异常 : redis.clients.jedis.exceptions.JedisDataException : ERR syntax error
*/
try {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
}finally {
if (jedis != null) {
jedis.close();
}
}
return false;
}
private static final Long RELEASE_SUCCESS = 1L;
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
try {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
}finally {
if (jedis != null) {
jedis.close();
}
}
return false;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package org.jeecg.common.util.redis;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
public class JedisConfig {
private String host;
private String password;
private String port;
private String timeout;
private String maxTotal;
private String maxIdle;
private String minIdle;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.jeecg.common.util.redis;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class JedisClient {
@Autowired
private JedisConfig jedisConfig;
//等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
private static int MAX_WAIT = 15 * 1000;
//超时时间
private static int TIMEOUT = 10 * 1000;
private JedisPool jedisPool = null;
public JedisPool getJedisPool() {
return jedisPool;
}
/**
* Jedis实例获取返回码
*
* @author jqlin
*/
public static class JedisStatus {
/**
* Jedis实例获取失败
*/
public static final long FAIL_LONG = -5L;
/**
* Jedis实例获取失败
*/
public static final int FAIL_INT = -5;
/**
* Jedis实例获取失败
*/
public static final String FAIL_STRING = "-5";
}
@PostConstruct
private void initialPool() {
//Redis服务器IP
String HOST = jedisConfig.getHost();
//Redis的端口号
int PORT = NumberUtils.toInt(jedisConfig.getPort(), 6379);
//访问密码
String AUTH = jedisConfig.getPassword();
try {
JedisPoolConfig config = new JedisPoolConfig();
//最大连接数,如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
config.setMaxTotal(NumberUtils.toInt(jedisConfig.getMaxTotal(), 400));
//最大空闲数,控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
config.setMaxIdle(NumberUtils.toInt(jedisConfig.getMaxIdle(), 50));
//最小空闲数
config.setMinIdle(NumberUtils.toInt(jedisConfig.getMinIdle(), 10));
//是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
config.setTestOnBorrow(false);
//在return给pool时,是否提前进行validate操作
config.setTestOnReturn(false);
//在空闲时检查有效性,默认false
config.setTestWhileIdle(true);
//表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;
//这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义
config.setMinEvictableIdleTimeMillis(30000);
//表示idle object evitor两次扫描之间要sleep的毫秒数
config.setTimeBetweenEvictionRunsMillis(60000);
//表示idle object evitor每次扫描的最多的对象数
config.setNumTestsPerEvictionRun(1000);
//等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
config.setMaxWaitMillis(MAX_WAIT);
if (StringUtils.isNotBlank(AUTH)) {
jedisPool = new JedisPool(config, HOST, PORT, TIMEOUT, AUTH);
} else {
jedisPool = new JedisPool(config, HOST, PORT, TIMEOUT);
}
} catch (Exception e) {
if (jedisPool != null) {
jedisPool.close();
}
log.error("初始化Redis连接池失败", e);
}
}
/**
* 在多线程环境同步初始化
*/
private synchronized void poolInit() {
if (jedisPool == null) {
initialPool();
}
}
/**
* 同步获取Jedis实例
*
* @return Jedis
*/
public Jedis getJedis() {
if (jedisPool == null) {
poolInit();
}
Jedis jedis = null;
try {
if (jedisPool != null) {
jedis = jedisPool.getResource();
}
} catch (Exception e) {
log.error("同步获取Jedis实例失败" + e.getMessage(), e);
jedis.close();
}
return jedis;
}
/**
* 设置值
*
* @param key
* @param value
* @return -5:Jedis实例获取失败<br/>OK:操作成功<br/>null:操作失败
* @author jqlin
*/
public String set(String key, String value) {
try (
Jedis jedis = getJedis();
) {
if (jedis == null) {
return JedisStatus.FAIL_STRING;
}
return jedis.set(key, value);
} catch (Exception e) {
log.error("[redis error] -> 设置值失败");
throw new RuntimeException(e);
}
}
/**
* 设置值
*
* @param key
* @param value
* @param expire 过期时间,单位:秒
* @return -5:Jedis实例获取失败<br/>OK:操作成功<br/>null:操作失败
* @author jqlin
*/
public String set(String key, String value, int expire, TimeUnit timeUnit) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
return JedisStatus.FAIL_STRING;
}
if (TimeUnit.MILLISECONDS.equals(timeUnit)) {
expire = expire / 1000;
} else if (!TimeUnit.SECONDS.equals(timeUnit)) {
throw new RuntimeException("TimeUnit时间单位设置错误:请设置milliseconds或seconds");
}
return jedis.setex(key, expire, value);
} catch (Exception e) {
log.error("[redis error] -> 设置值失败");
throw new RuntimeException(e);
}
}
/**
* 获取值
*
* @param key
* @return
* @author jqlin
*/
public String get(String key) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
return JedisStatus.FAIL_STRING;
}
return jedis.get(key);
} catch (Exception e) {
log.error("[redis error] -> 设置值失败");
throw new RuntimeException(e);
}
}
/**
* 设置key的过期时间
*
* @param key
* @param -5:Jedis实例获取失败,1:成功,0:失败
* @return
* @author jqlin
*/
public long expire(String key, int seconds) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
return JedisStatus.FAIL_LONG;
}
return jedis.expire(key, seconds);
} catch (Exception e) {
log.error(String.format("设置key=%s的过期时间失败:" + e.getMessage(), key));
throw new RuntimeException(e);
}
}
/**
* 判断key是否存在
*
* @param key
* @return
* @author jqlin
*/
public boolean exists(String key) {
try (
Jedis jedis = getJedis();
) {
if (jedis == null) {
log.warn("Jedis实例获取为空");
return false;
}
return jedis.exists(key);
} catch (Exception e) {
log.error(String.format("判断key=%s是否存在失败:" + e.getMessage(), key));
throw new RuntimeException(e);
}
}
/**
* 删除key
*
* @param keys
* @return -5:Jedis实例获取失败,1:成功,0:失败
* @author jqlin
*/
public long del(String... keys) {
try (
Jedis jedis = getJedis();
) {
if (jedis == null) {
return JedisStatus.FAIL_LONG;
}
return jedis.del(keys);
} catch (Exception e) {
log.error(String.format("删除key=%s失败:" + e.getMessage(), keys));
throw new RuntimeException(e);
}
}
/**
* set if not exists,若key已存在,则setnx不做任何操作
*
* @param key
* @param value key已存在,1:key赋值成功
* @return
* @author jqlin
*/
public long setnx(String key, String value) {
try (
Jedis jedis = getJedis();
) {
if (jedis == null) {
return JedisStatus.FAIL_LONG;
}
return jedis.setnx(key, value);
} catch (Exception e) {
log.error("设置值失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* set if not exists,若key已存在,则setnx不做任何操作
*
* @param key
* @param value key已存在,1:key赋值成功
* @param expire 过期时间,单位:秒
* @return
* @author jqlin
*/
public long setnx(String key, String value, int expire) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
return JedisStatus.FAIL_LONG;
}
Long result = jedis.setnx(key, value);
expire(key, expire);
return result;
} catch (Exception e) {
log.error("设置值失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 在列表key的头部插入元素
*
* @param key
* @param values -5:Jedis实例获取失败,>0:返回操作成功的条数,0:失败
* @return
* @author jqlin
*/
public long lpush(String key, String... values) {
try (
Jedis jedis = getJedis();
) {
if (jedis == null) {
return JedisStatus.FAIL_LONG;
}
return jedis.lpush(key, values);
} catch (Exception e) {
log.error("在列表key的头部插入元素失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 在列表key的尾部插入元素
*
* @param key
* @param values -5:Jedis实例获取失败,>0:返回操作成功的条数,0:失败
* @return
* @author jqlin
*/
public long rpush(String key, String... values) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
return JedisStatus.FAIL_LONG;
}
return jedis.rpush(key, values);
} catch (Exception e) {
log.error("在列表key的尾部插入元素失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 返回存储在key列表的特定元素
*
* @param key
* @param start 开始索引,索引从0开始,0表示第一个元素,1表示第二个元素
* @param end 结束索引,-1表示最后一个元素,-2表示倒数第二个元素
* @return redis client获取失败返回null
* @author jqlin
*/
public List<String> lrange(String key, long start, long end) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
return null;
}
return jedis.lrange(key, start, end);
} catch (Exception e) {
log.error("查询列表元素失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 获取List缓存对象
*
* @param key
* @param start
* @param end
* @return List<T> 返回类型
* @author jqlin
*/
public <T> List<T> range(String key, long start, long end, Class<T> clazz) {
List<String> dataList = lrange(key, start, end);
if (CollectionUtils.isEmpty(dataList)) {
return new ArrayList<>();
}
return JSON.parseArray(JSON.toJSONString(dataList), clazz);
}
/**
* 获取列表长度
*
* @param key -5:Jedis实例获取失败
* @return
* @author jqlin
*/
public long llen(String key) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
return JedisStatus.FAIL_LONG;
}
return jedis.llen(key);
} catch (Exception e) {
log.error("获取列表长度失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 移除等于value的元素<br/><br/>
* 当count>0时,从表头开始查找,移除count个;<br/>
* 当count=0时,从表头开始查找,移除所有等于value的;<br/>
* 当count<0时,从表尾开始查找,移除count个
*
* @param key
* @param count
* @param value
* @return -5:Jedis实例获取失败
* @author jqlin
*/
public long lrem(String key, long count, String value) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
return JedisStatus.FAIL_LONG;
}
return jedis.lrem(key, count, value);
} catch (Exception e) {
log.error("获取列表长度失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 对列表进行修剪
*
* @param key
* @param start
* @param end
* @return -5:Jedis实例获取失败,OK:命令执行成功
* @author jqlin
*/
public String ltrim(String key, long start, long end) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
return JedisStatus.FAIL_STRING;
}
return jedis.ltrim(key, start, end);
} catch (Exception e) {
log.error("获取列表长度失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 设置对象
*
* @param key
* @param obj
* @return
* @author jqlin
*/
public <T> String setObject(String key, T obj) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
return JedisStatus.FAIL_STRING;
}
byte[] data = JSON.toJSONBytes(obj);
return jedis.set(key.getBytes(), data);
} catch (Exception e) {
log.error("设置对象失败:" + e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* 获取对象
*
* @param key
* @return
* @author jqlin
*/
public <T> T getObject(String key) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
return null;
}
T result = null;
byte[] data = jedis.get(key.getBytes());
if (data != null && data.length > 0) {
result = (T) JSON.parse(data);
}
return result;
} catch (Exception e) {
log.error("获取对象失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 缓存Map赋值
*
* @param key
* @param field
* @param value
* @return -5:Jedis实例获取失败
* @author jqlin
*/
public long hset(String key, String field, String value) {
try (
Jedis jedis = getJedis();
) {
if (jedis == null) {
return JedisStatus.FAIL_LONG;
}
return jedis.hset(key, field, value);
} catch (Exception e) {
log.error("缓存Map赋值失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 获取缓存的Map值
*
* @param key
* @return
*/
public String hget(String key, String field) {
try (
Jedis jedis = getJedis();
) {
if (jedis == null) {
return null;
}
return jedis.hget(key, field);
} catch (Exception e) {
log.error("获取缓存的Map值失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 缓存Map
*
* @param key
* @param map
* @return
*/
public String hmset(String key, Map<String, String> map) {
try (
Jedis jedis = getJedis();
) {
if (jedis == null) {
return JedisStatus.FAIL_STRING;
}
return jedis.hmset(key, map);
} catch (Exception e) {
log.error("缓存Map赋值失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 获取map所有的字段和值
*
* @param key
* @return
* @author jqlin
*/
public Map<String, String> hgetAll(String key) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
log.warn("Jedis实例获取为空");
return new HashMap<>();
}
return jedis.hgetAll(key);
} catch (Exception e) {
log.error("获取map所有的字段和值失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 查看哈希表 key 中,指定的field字段是否存在。
*
* @param key
* @param field
* @return
* @author jqlin
*/
public Boolean hexists(String key, String field) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
log.warn("Jedis实例获取为空");
return null;
}
return jedis.hexists(key, field);
} catch (Exception e) {
log.error("查看哈希表field字段是否存在失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 获取所有哈希表中的字段
*
* @param key
* @return
* @author jqlin
*/
public Set<String> hkeys(String key) {
try (
Jedis jedis = getJedis();
) {
if (jedis == null) {
log.warn("Jedis实例获取为空");
return new HashSet<>();
}
return jedis.hkeys(key);
} catch (Exception e) {
log.error("获取所有哈希表中的字段失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 获取所有哈希表中的值
*
* @param key
* @return
* @author jqlin
*/
public List<String> hvals(String key) {
try (
Jedis jedis = getJedis();
) {
if (jedis == null) {
log.warn("Jedis实例获取为空");
return new ArrayList<>();
}
return jedis.hvals(key);
} catch (Exception e) {
log.error("获取所有哈希表中的值失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 从哈希表 key 中删除指定的field
*
* @param key
* @param
* @return
* @author jqlin
*/
public long hdel(String key, String... fields) {
try (
Jedis jedis = getJedis();
) {
if (jedis == null) {
log.warn("Jedis实例获取为空");
return JedisStatus.FAIL_LONG;
}
return jedis.hdel(key, fields);
} catch (Exception e) {
log.error("map删除指定的field失败:" + e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 获取正则匹配的key (慎用)
*
* @param pattern
* @return
*/
public Set<String> keys(String pattern) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
log.warn("Jedis实例获取为空");
return new HashSet<>();
}
return jedis.keys(pattern);
} catch (Exception e) {
log.error("操作keys失败:" + e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* Redis Incrby 命令将 key 中储存的数字加上指定的增量值;
* 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令;
* 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
* 本操作的值限制在 64 位(bit)有符号数字表示之内。
*
* @return
*/
public Long incrBy(String key, long integer) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
log.warn("Jedis实例获取为空");
return 0L;
}
return jedis.incrBy(key, integer);
} catch (Exception e) {
log.error("操作 Incrby 失败:" + e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* Redis Incr 命令将 key 中储存的数字值增一。
* 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
* 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
* 本操作的值限制在 64 位(bit)有符号数字表示之内
*/
public Long incr(String key) {
try (
Jedis jedis = getJedis()
) {
if (jedis == null) {
log.warn("Jedis实例获取为空");
return 0L;
}
return jedis.incr(key);
} catch (Exception e) {
log.error("操作 Incr 失败:" + e.getMessage(), e);
throw new RuntimeException(e);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
# 12.RabbitMqConfig
package org.jeecg.config;
import com.eastroc.common.consts.ArrangeConstant;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RequiredArgsConstructor
public class RabbitMqConfig {
private final SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer;
private final CachingConnectionFactory connectionFactory;
@Bean(name = "syncshipmentOrderReportServiceQueue")
public Queue syncshipmentOrderReportServiceQueue() {
return QueueBuilder.durable(ArrangeConstant.Q_SYNC_SHIPMENT_ORDER_TO_REPORT_ONE).build();
}
@Bean
public Binding syncShipmentOrderReportServiceBinding(Queue syncshipmentOrderReportServiceQueue, DirectExchange shipmentOrderReportExchange) {
return BindingBuilder.bind(syncshipmentOrderReportServiceQueue).to(shipmentOrderReportExchange).with(ArrangeConstant.K_SHIPMENT_ORDER_REPORT_ONE);
}
@Bean(name = "shipmentOrderReportContainer")
public SimpleRabbitListenerContainerFactory shipmentOrderReportContainer() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factoryConfigurer.configure(factory, connectionFactory);
factory.setAcknowledgeMode(AcknowledgeMode.NONE);
factory.setPrefetchCount(10);
return factory;
}
@Bean
public DirectExchange shipmentOrderReportExchange() {
return (DirectExchange) ExchangeBuilder.directExchange(ArrangeConstant.E_EXCHANGE).durable(true).build();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.eastroc.common.consumer;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.eastroc.common.consts.ArrangeConstant;
import com.eastroc.order.entity.*;
import com.eastroc.order.entity.StoreBase;
import com.eastroc.common.mapper.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
@Slf4j
@Component
@RabbitListener(queues = ArrangeConstant.Q_SYNC_SHIPMENT_ORDER_TO_REPORT_ONE, containerFactory = "shipmentOrderReportContainer")
public class ShipmentOrderReportConsumer {
@Resource
private StoreBaseMapper storeBaseMapper;
@Resource
private ShipmentOrderMapper shipmentOrderMapper;
@Resource
private ShipmentOrderDetailMapper shipmentOrderDetailMapper;
@Resource
private ShipmentOrderReportMapper shipmentOrderReportMapper;
@Resource
private ShipmentOrderRelateDriverMapper shipmentOrderRelateDriverMapper;
@Resource
private AccessControlSystemCarInfoMapper accessControlSystemCarInfoMapper;
@Resource
private ShipmentOrderDetailGoodsInfoMapper shipmentOrderDetailGoodsInfoMapper;
@RabbitHandler
public void syncReport(String shipmentOrderId) {
log.info("syncReport shipmentOrderId={}", shipmentOrderId);
ShipmentOrder shipmentOrder = shipmentOrderMapper.selectOne(Wrappers.<ShipmentOrder>lambdaQuery().eq(ShipmentOrder::getId, shipmentOrderId));
List<ShipmentOrderDetail> shipmentOrderDetails = shipmentOrderDetailMapper.selectList(Wrappers.<ShipmentOrderDetail>lambdaQuery()
.eq(ShipmentOrderDetail::getShipmentOrderId, shipmentOrderId));
if (CollectionUtils.isNotEmpty(shipmentOrderDetails)) {
shipmentOrderDetails.forEach(item -> {
String shipmentDetailId = item.getId();
ShipmentOrderRelateDriver shipmentOrderRelateDriver = shipmentOrderRelateDriverMapper.selectOne(Wrappers.<ShipmentOrderRelateDriver>lambdaQuery()
.eq(ShipmentOrderRelateDriver::getShipmentOrderId, shipmentOrderId)
);
List<ShipmentOrderDetailGoodsInfo> shipmentOrderDetailGoodsInfos = shipmentOrderDetailGoodsInfoMapper.selectList(Wrappers.<ShipmentOrderDetailGoodsInfo>lambdaQuery()
.eq(ShipmentOrderDetailGoodsInfo::getShipmentOrderDetailId, shipmentDetailId)
);
if (CollectionUtils.isNotEmpty(shipmentOrderDetailGoodsInfos)) {
shipmentOrderDetailGoodsInfos.forEach(goodsInfo -> {
ShipmentOrderReport shipmentOrderReport = shipmentOrderReportMapper.selectOne(Wrappers.<ShipmentOrderReport>lambdaQuery()
.eq(ShipmentOrderReport::getTrainNumber, shipmentOrder.getTrainNumber())
.eq(ShipmentOrderReport::getOrderNumber, item.getOrderNumber())
.eq(ShipmentOrderReport::getGoodsType, goodsInfo.getGoodsType())
.eq(ShipmentOrderReport::getProductCode, goodsInfo.getProductCode()));
if (shipmentOrderReport == null) {
shipmentOrderReport = new ShipmentOrderReport();
this.buildShipmentOrderReport(shipmentOrder, item, shipmentOrderRelateDriver, goodsInfo, shipmentOrderReport);
shipmentOrderReportMapper.insert(shipmentOrderReport);
} else {
this.buildShipmentOrderReport(shipmentOrder, item, shipmentOrderRelateDriver, goodsInfo, shipmentOrderReport);
shipmentOrderReportMapper.updateById(shipmentOrderReport);
}
});
}
if (Objects.isNull(shipmentOrderRelateDriver)) {
shipmentOrderReportMapper.update(new ShipmentOrderReport(), Wrappers.<ShipmentOrderReport>lambdaUpdate()
.set(ShipmentOrderReport::getPeriodDay, null)
.set(ShipmentOrderReport::getDriverName, null)
.set(ShipmentOrderReport::getDriverPhone, null)
.set(ShipmentOrderReport::getCarNumber, null)
.eq(ShipmentOrderReport::getTrainNumber, shipmentOrder.getTrainNumber())
);
}
});
}
}
/**
* 构建报表信息
*
* @param shipmentOrder
* @param item
* @param shipmentOrderRelateDriver
* @param goodsInfo
* @param shipmentOrderReport
*/
private void buildShipmentOrderReport(ShipmentOrder shipmentOrder, ShipmentOrderDetail item, ShipmentOrderRelateDriver shipmentOrderRelateDriver
, ShipmentOrderDetailGoodsInfo goodsInfo, ShipmentOrderReport shipmentOrderReport) {
//构建基础信息
this.buildBaseInfo(shipmentOrder, shipmentOrderReport);
//构建详细信息
this.buildDetailInfo(item, shipmentOrderReport);
//构建约车信息
this.buildDriverInfo(shipmentOrderRelateDriver, shipmentOrderReport);
//构建产品信息
this.buildGoodsInfo(goodsInfo, shipmentOrderReport);
}
/**
* 构建产品信息
*
* @param goodsInfo
* @param shipmentOrderReport
*/
private void buildGoodsInfo(ShipmentOrderDetailGoodsInfo goodsInfo, ShipmentOrderReport shipmentOrderReport) {
shipmentOrderReport.setGoodsType(goodsInfo.getGoodsType());
shipmentOrderReport.setProductCode(goodsInfo.getProductCode());
shipmentOrderReport.setProductName(goodsInfo.getProductName());
shipmentOrderReport.setProductType(goodsInfo.getProductType());
shipmentOrderReport.setProductWeight(goodsInfo.getProductWeight());
shipmentOrderReport.setProductSize(goodsInfo.getProductSize());
shipmentOrderReport.setProductNumber(goodsInfo.getProductNumber());
shipmentOrderReport.setProductUnit(goodsInfo.getProductUnit());
shipmentOrderReport.setDelFlag(goodsInfo.getDelFlag());
shipmentOrderReport.setCreateTime(goodsInfo.getCreateTime());
shipmentOrderReport.setUpdateTime(goodsInfo.getUpdateTime());
shipmentOrderReport.setCreateBy(goodsInfo.getCreateBy());
shipmentOrderReport.setUpdateBy(goodsInfo.getUpdateBy());
}
/**
* 构建预约信息
*
* @param shipmentOrderRelateDriver
* @param shipmentOrderReport
*/
private void buildDriverInfo(ShipmentOrderRelateDriver shipmentOrderRelateDriver, ShipmentOrderReport shipmentOrderReport) {
if (Objects.nonNull(shipmentOrderRelateDriver)) {
shipmentOrderReport.setDriverPhone(shipmentOrderRelateDriver.getDriverPhone());
shipmentOrderReport.setDriverName(shipmentOrderRelateDriver.getDriverName());
shipmentOrderReport.setPeriodDay(shipmentOrderRelateDriver.getPeriodDay());
shipmentOrderReport.setCarNumber(shipmentOrderRelateDriver.getCarNumber());
}
}
/**
* 构建订单详情
*
* @param item
* @param shipmentOrderReport
*/
private void buildDetailInfo(ShipmentOrderDetail item, ShipmentOrderReport shipmentOrderReport) {
shipmentOrderReport.setAddress(item.getAddress());
shipmentOrderReport.setCardBoard(item.getCardBoard());
shipmentOrderReport.setOrderNumber(item.getOrderNumber());
shipmentOrderReport.setDetailId(item.getId());
shipmentOrderReport.setConsigneeName(item.getConsigneeName());
shipmentOrderReport.setConsigneePhone(item.getConsigneePhone());
shipmentOrderReport.setRealReceiveNumber(item.getRealReceiveNumber());
shipmentOrderReport.setDealerNumber(item.getDealerNumber());
shipmentOrderReport.setDealerName(item.getDealerName());
}
/**
* 构建车次基础信息
*
* @param shipmentOrder
* @param shipmentOrderReport
*/
private void buildBaseInfo(ShipmentOrder shipmentOrder, ShipmentOrderReport shipmentOrderReport) {
shipmentOrderReport.setTrainNumber(shipmentOrder.getTrainNumber());
shipmentOrderReport.setOrderStatus(shipmentOrder.getOrderStatus());
shipmentOrderReport.setOutTime(shipmentOrder.getOutTime());
shipmentOrderReport.setCompany(shipmentOrder.getCompany());
shipmentOrderReport.setOutType(shipmentOrder.getOutType());
shipmentOrderReport.setDeliveryMode(shipmentOrder.getDeliveryMode());
shipmentOrderReport.setDispatchDate(shipmentOrder.getCreateTime());
String storeBaseId = shipmentOrder.getStoreBaseId();
shipmentOrderReport.setStoreBaseId(storeBaseId);
shipmentOrderReport.setStoreBaseName(shipmentOrder.getStoreBaseName());
if (StringUtils.isNotBlank(storeBaseId)) {
StoreBase storeBase = storeBaseMapper.selectById(storeBaseId);
shipmentOrderReport.setStoreBaseCode(storeBase.getBaseCode());
}
shipmentOrderReport.setModes(shipmentOrder.getModes());
AccessControlSystemCarInfo accessControlSystemCarInfo = accessControlSystemCarInfoMapper.selectOne(Wrappers.<AccessControlSystemCarInfo>lambdaQuery()
.eq(AccessControlSystemCarInfo::getTrainNumber, shipmentOrder.getTrainNumber())
.orderByDesc(AccessControlSystemCarInfo::getCreateTime)
.last("limit 1")
);
if (accessControlSystemCarInfo != null) {
shipmentOrderReport.setInTime(accessControlSystemCarInfo.getInTime());
shipmentOrderReport.setSecondWeightTime(accessControlSystemCarInfo.getSecondWeightTime());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package com.eastroc.common.utils;
import com.eastroc.common.consts.ArrangeConstant;
import org.jeecg.common.util.ExecutorServiceFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.concurrent.CompletableFuture;
@Component
public class TransactionSynchronizationUtil {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* mq同步到历史订单报表
*
* @param shipmentOrderId
*/
public void syncReportTransaction(String shipmentOrderId) {
try {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
CompletableFuture.runAsync(() -> {
rabbitTemplate.convertAndSend(ArrangeConstant.E_EXCHANGE, ArrangeConstant.K_SHIPMENT_ORDER_REPORT_ONE, shipmentOrderId);
}, ExecutorServiceFactory.getInstance());
}
});
} catch (
Exception e) {
e.printStackTrace();
}
}
/**
* mq同步到历史订单报表
*
* @param shipmentOrderId
*/
public void syncReport(String shipmentOrderId) {
CompletableFuture.runAsync(() -> {
rabbitTemplate.convertAndSend(ArrangeConstant.E_EXCHANGE, ArrangeConstant.K_SHIPMENT_ORDER_REPORT_ONE, shipmentOrderId);
}, ExecutorServiceFactory.getInstance());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50