正在显示
14 个修改的文件
包含
1195 行增加
和
0 行删除
pom.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
3 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
4 | + <modelVersion>4.0.0</modelVersion> | ||
5 | + <parent> | ||
6 | + <groupId>org.springframework.boot</groupId> | ||
7 | + <artifactId>spring-boot-starter-parent</artifactId> | ||
8 | + <version>2.2.7.RELEASE</version> | ||
9 | + <relativePath/> <!-- lookup parent from repository --> | ||
10 | + </parent> | ||
11 | + <groupId>com.aukey.example</groupId> | ||
12 | + <artifactId>dw-example</artifactId> | ||
13 | + <version>0.0.1</version> | ||
14 | + <name>dw-example</name> | ||
15 | + <description>dw客户端demo项目</description> | ||
16 | + | ||
17 | + <properties> | ||
18 | + <java.version>1.8</java.version> | ||
19 | + </properties> | ||
20 | + | ||
21 | + <dependencies> | ||
22 | + <dependency> | ||
23 | + <groupId>org.springframework.boot</groupId> | ||
24 | + <artifactId>spring-boot-starter-web</artifactId> | ||
25 | + </dependency> | ||
26 | + | ||
27 | + <!-- 在线文档,实际使用不需要依赖 !--> | ||
28 | + <dependency> | ||
29 | + <groupId>io.springfox</groupId> | ||
30 | + <artifactId>springfox-swagger2</artifactId> | ||
31 | + <version>2.7.0</version> | ||
32 | + </dependency> | ||
33 | + | ||
34 | + <dependency> | ||
35 | + <groupId>io.springfox</groupId> | ||
36 | + <artifactId>springfox-swagger-ui</artifactId> | ||
37 | + <version>2.7.0</version> | ||
38 | + </dependency> | ||
39 | + <!-- 在线文档end !--> | ||
40 | + | ||
41 | + <dependency> | ||
42 | + <groupId>com.alibaba</groupId> | ||
43 | + <artifactId>fastjson</artifactId> | ||
44 | + <version>1.2.68</version> | ||
45 | + </dependency> | ||
46 | + | ||
47 | + <dependency> | ||
48 | + <groupId>com.squareup.okhttp3</groupId> | ||
49 | + <artifactId>okhttp</artifactId> | ||
50 | + <version>3.14.1</version> | ||
51 | + </dependency> | ||
52 | + | ||
53 | + <dependency> | ||
54 | + <groupId>org.apache.commons</groupId> | ||
55 | + <artifactId>commons-lang3</artifactId> | ||
56 | + <version>3.4</version> | ||
57 | + </dependency> | ||
58 | + | ||
59 | + <dependency> | ||
60 | + <groupId>org.springframework.boot</groupId> | ||
61 | + <artifactId>spring-boot-starter-test</artifactId> | ||
62 | + <scope>test</scope> | ||
63 | + </dependency> | ||
64 | + </dependencies> | ||
65 | + | ||
66 | + <build> | ||
67 | + <plugins> | ||
68 | + <plugin> | ||
69 | + <groupId>org.springframework.boot</groupId> | ||
70 | + <artifactId>spring-boot-maven-plugin</artifactId> | ||
71 | + </plugin> | ||
72 | + </plugins> | ||
73 | + </build> | ||
74 | + | ||
75 | +</project> |
readme.md
0 → 100644
1 | +### dw example 使用手册 | ||
2 | + | ||
3 | +修改配置文件 | ||
4 | +--- | ||
5 | +修改 application.yml 的 `dwAppId`、 `dwAppSecret` 配置 | ||
6 | + | ||
7 | +- `dwAppId`:傲基数仓 APP 模块的 AppId。 | ||
8 | + | ||
9 | +- `dwAppSecret`:傲基数仓 APP 模块的 应用秘钥。 | ||
10 | + | ||
11 | +提供三种数据请求方案 | ||
12 | +--- | ||
13 | + | ||
14 | +- 全量请求:一次http请求获取所有数据,有数量限制,最大1000条。适合小批量数据同步。 | ||
15 | + | ||
16 | +- 分页请求:多次http请求获取所有数据,分页参数`pageNumber`, `pageSize(取值范围[1,3000])`. 适合中批量数据同步。 | ||
17 | + | ||
18 | +- 流式请求:一次http请求获取所有数据,没有数量限制,要求网络稳定,最好是内网环境。适合大批量数据同步。 | ||
19 | + | ||
20 | +token更新 | ||
21 | +--- | ||
22 | + | ||
23 | +`TokenJob` 为tokne更新定时任务 | ||
24 | + | ||
25 | +`TokenController` token更新回调地址 | ||
26 | + | ||
27 | +定时任务定点调用dw服务刷新token api,然后dw将最新token作为参数调用数仓平台上对应的APP配置 | ||
28 | +的回调地址,将token传递给客户端。 | ||
29 | + | ||
30 | +如何使用 | ||
31 | +--- | ||
32 | +1. 申请API | ||
33 | + | ||
34 | +2. 启动项目 | ||
35 | + | ||
36 | +3. 访问 http://localhost:8099/swagger-ui.html | ||
37 | + | ||
38 | +4. 源码 demo 在 `TestApiController` |
1 | +package com.aukey.example; | ||
2 | + | ||
3 | +import org.springframework.boot.SpringApplication; | ||
4 | +import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
5 | +import org.springframework.context.annotation.Bean; | ||
6 | +import org.springframework.scheduling.annotation.EnableScheduling; | ||
7 | +import springfox.documentation.builders.ApiInfoBuilder; | ||
8 | +import springfox.documentation.builders.PathSelectors; | ||
9 | +import springfox.documentation.builders.RequestHandlerSelectors; | ||
10 | +import springfox.documentation.service.ApiInfo; | ||
11 | +import springfox.documentation.spi.DocumentationType; | ||
12 | +import springfox.documentation.spring.web.plugins.Docket; | ||
13 | +import springfox.documentation.swagger2.annotations.EnableSwagger2; | ||
14 | + | ||
15 | +@EnableSwagger2 | ||
16 | +@EnableScheduling | ||
17 | +@SpringBootApplication | ||
18 | +public class DwExampleApplication { | ||
19 | + | ||
20 | + public static void main(String[] args) { | ||
21 | + SpringApplication.run(DwExampleApplication.class, args); | ||
22 | + } | ||
23 | + | ||
24 | + | ||
25 | + @Bean | ||
26 | + public Docket createRestApi() { | ||
27 | + Docket docket = new Docket(DocumentationType.SWAGGER_2) | ||
28 | + .apiInfo(apiInfo()) | ||
29 | + .select() | ||
30 | + //controller所在包 | ||
31 | + .apis(RequestHandlerSelectors.basePackage("com.aukey.example.web")) | ||
32 | + .paths(PathSelectors.any()) | ||
33 | + .build(); | ||
34 | + | ||
35 | + return docket; | ||
36 | + } | ||
37 | + | ||
38 | + //构建 api文档的详细信息函数 | ||
39 | + private ApiInfo apiInfo() { | ||
40 | + return new ApiInfoBuilder() | ||
41 | + .title("dw example") | ||
42 | + .description("") | ||
43 | + .termsOfServiceUrl("") | ||
44 | + .version("1.0.0") | ||
45 | + .build(); | ||
46 | + } | ||
47 | +} |
1 | +package com.aukey.example.conf; | ||
2 | + | ||
3 | +import com.alibaba.fastjson.JSON; | ||
4 | +import com.alibaba.fastjson.JSONException; | ||
5 | +import com.alibaba.fastjson.JSONReader; | ||
6 | +import okhttp3.OkHttpClient; | ||
7 | +import okhttp3.Request; | ||
8 | +import okhttp3.Response; | ||
9 | +import org.slf4j.Logger; | ||
10 | +import org.slf4j.LoggerFactory; | ||
11 | + | ||
12 | +import java.io.InputStream; | ||
13 | +import java.io.InputStreamReader; | ||
14 | +import java.io.Reader; | ||
15 | +import java.net.URLEncoder; | ||
16 | +import java.util.ArrayList; | ||
17 | +import java.util.List; | ||
18 | +import java.util.Map; | ||
19 | +import java.util.Objects; | ||
20 | +import java.util.concurrent.TimeUnit; | ||
21 | +import java.util.function.Consumer; | ||
22 | +import java.util.function.Supplier; | ||
23 | + | ||
24 | +/** | ||
25 | + * 描述:流式读取帮助类 | ||
26 | + * 创建者: wgf | ||
27 | + * 创建时间:2020年5月20日 10:03:15 | ||
28 | + **/ | ||
29 | +public class StreamReaderHandler { | ||
30 | + | ||
31 | + private static Logger log = LoggerFactory.getLogger(StreamReaderHandler.class); | ||
32 | + | ||
33 | + private StreamReaderHandler() { | ||
34 | + } | ||
35 | + | ||
36 | + /** | ||
37 | + * @param dwDataApi dw数据请求接口 | ||
38 | + * @param api 平台申请的api | ||
39 | + * @param paramMap 参数 | ||
40 | + * @param callback 业务回调 | ||
41 | + * @param batchSize 业务回调数据量大小,参考值 [500,5000] | ||
42 | + * @param constructor 实体的构造函数 | ||
43 | + * @throws Exception | ||
44 | + */ | ||
45 | + public static <T> void reader(String dwDataApi, String api, | ||
46 | + Map<String, Object> paramMap, | ||
47 | + Consumer<List<T>> callback, | ||
48 | + int batchSize, | ||
49 | + Supplier<T> constructor) throws Exception { | ||
50 | + | ||
51 | + dwDataApi = dwDataApi + api; | ||
52 | + Response response = null; | ||
53 | + InputStream is = null; | ||
54 | + Reader reader = null; | ||
55 | + JSONReader jsonReader = null; | ||
56 | + | ||
57 | + try { | ||
58 | + response = executeHttpPostRequest(dwDataApi, paramMap); | ||
59 | + | ||
60 | + if (response.code() == 200) { | ||
61 | + List<T> container = new ArrayList<>(); | ||
62 | + int total = 0; | ||
63 | + | ||
64 | + // 从响应中获取流 | ||
65 | + is = response.body().byteStream(); | ||
66 | + reader = new InputStreamReader(is); | ||
67 | + jsonReader = new JSONReader(reader); | ||
68 | + // 开始读取 | ||
69 | + jsonReader.startArray(); | ||
70 | + | ||
71 | + while (jsonReader.hasNext()) { | ||
72 | + try { | ||
73 | + T t = constructor.get(); | ||
74 | + jsonReader.readObject(t); | ||
75 | + container.add(t); | ||
76 | + } catch (JSONException ex) { | ||
77 | + Object o = jsonReader.readObject(); | ||
78 | + log.info("json数据转换异常:{}", o.toString()); | ||
79 | + } | ||
80 | + | ||
81 | + if (container.size() == batchSize) { | ||
82 | + callback.accept(container); | ||
83 | + total += batchSize; | ||
84 | + log.info("{} 流式读取已读条数:{}", dwDataApi, total); | ||
85 | + container.clear(); | ||
86 | + } | ||
87 | + } | ||
88 | + | ||
89 | + // 处理最后一批不足 batchSize 的数据 | ||
90 | + if (container.size() > 0) { | ||
91 | + callback.accept(container); | ||
92 | + total += container.size(); | ||
93 | + log.info("{} 流式读取已读条数:{}", dwDataApi, total); | ||
94 | + container.clear(); | ||
95 | + } | ||
96 | + | ||
97 | + //结束读取 | ||
98 | + jsonReader.endArray(); | ||
99 | + } else { | ||
100 | + log.info("流式读取异常 [ url:{} ] [ code:{} ]", dwDataApi, response.code()); | ||
101 | + } | ||
102 | + | ||
103 | + } finally { | ||
104 | + if (Objects.nonNull(jsonReader)) { | ||
105 | + jsonReader.close(); | ||
106 | + } | ||
107 | + | ||
108 | + if (Objects.nonNull(reader)) { | ||
109 | + reader.close(); | ||
110 | + } | ||
111 | + | ||
112 | + if (Objects.nonNull(is)) { | ||
113 | + is.close(); | ||
114 | + } | ||
115 | + | ||
116 | + if (Objects.nonNull(response)) { | ||
117 | + response.close(); | ||
118 | + } | ||
119 | + } | ||
120 | + } | ||
121 | + | ||
122 | + protected static Response executeHttpPostRequest(String url, Map<String, Object> paramMap) throws Exception { | ||
123 | + OkHttpClient client = new OkHttpClient.Builder() | ||
124 | + .connectTimeout(60 * 30 * 2, TimeUnit.SECONDS) | ||
125 | + .readTimeout(60 * 1, TimeUnit.SECONDS) | ||
126 | + .build(); | ||
127 | + | ||
128 | + StringBuilder sb = new StringBuilder(url); | ||
129 | + if (paramMap != null && !paramMap.isEmpty()) { | ||
130 | + boolean isFirst = true; | ||
131 | + | ||
132 | + for (Map.Entry<String, Object> entry : paramMap.entrySet()) { | ||
133 | + | ||
134 | + if (Objects.isNull(entry.getValue())) { | ||
135 | + continue; | ||
136 | + } | ||
137 | + | ||
138 | + if (isFirst) { | ||
139 | + sb.append("?"); | ||
140 | + isFirst = false; | ||
141 | + } else { | ||
142 | + sb.append("&"); | ||
143 | + } | ||
144 | + | ||
145 | + // 兼容参数特殊符号 | ||
146 | + String value = String.valueOf(entry.getValue()); | ||
147 | + value = URLEncoder.encode(value, "UTF-8"); | ||
148 | + value = value.replaceAll("\\+", "%20"); | ||
149 | + | ||
150 | + sb.append(entry.getKey()) | ||
151 | + .append("=") | ||
152 | + .append(value); | ||
153 | + } | ||
154 | + url = sb.toString(); | ||
155 | + } | ||
156 | + | ||
157 | + Request request = new Request.Builder().url(url).build(); | ||
158 | + Response response = client.newCall(request).execute(); | ||
159 | + return response; | ||
160 | + } | ||
161 | +} |
1 | +package com.aukey.example.conf; | ||
2 | + | ||
3 | +import org.springframework.context.annotation.Bean; | ||
4 | +import org.springframework.context.annotation.Configuration; | ||
5 | +import org.springframework.web.context.request.async.TimeoutCallableProcessingInterceptor; | ||
6 | +import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; | ||
7 | +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||
8 | + | ||
9 | +/** | ||
10 | + * @author: wgf | ||
11 | + * @create: 2020-05-19 12:56 | ||
12 | + * @description: | ||
13 | + **/ | ||
14 | +//@Configuration | ||
15 | +public class WebMvcConfig implements WebMvcConfigurer { | ||
16 | + @Override | ||
17 | + public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { | ||
18 | + configurer.setDefaultTimeout(30 * 60 * 1000); | ||
19 | + configurer.registerCallableInterceptors(timeoutInterceptor()); | ||
20 | + } | ||
21 | + | ||
22 | + @Bean | ||
23 | + public TimeoutCallableProcessingInterceptor timeoutInterceptor() { | ||
24 | + return new TimeoutCallableProcessingInterceptor(); | ||
25 | + } | ||
26 | +} |
1 | +package com.aukey.example.entity; | ||
2 | + | ||
3 | +import java.math.BigDecimal; | ||
4 | +import java.util.Date; | ||
5 | + | ||
6 | +/** | ||
7 | + * @author: wgf | ||
8 | + * @create: 2020-05-13 14:38 | ||
9 | + * @description: 汇率实体 | ||
10 | + **/ | ||
11 | +public class CurrencySet { | ||
12 | + private Integer currencyId; | ||
13 | + private String currencyCode; | ||
14 | + private String currencyName; | ||
15 | + private BigDecimal currencyRate; | ||
16 | + private String baseCurrency; | ||
17 | + private Integer createBy; | ||
18 | + private Date createTime; | ||
19 | + private String updateBy; | ||
20 | + private Date updateTime; | ||
21 | + private String dataStatus; | ||
22 | + private Integer auditBy; | ||
23 | + private String auditStatus; | ||
24 | + private Date auditTime; | ||
25 | + private String currencySymbol; | ||
26 | + | ||
27 | + public Integer getCurrencyId() { | ||
28 | + return currencyId; | ||
29 | + } | ||
30 | + | ||
31 | + public void setCurrencyId(Integer currencyId) { | ||
32 | + this.currencyId = currencyId; | ||
33 | + } | ||
34 | + | ||
35 | + public String getCurrencyCode() { | ||
36 | + return currencyCode; | ||
37 | + } | ||
38 | + | ||
39 | + public void setCurrencyCode(String currencyCode) { | ||
40 | + this.currencyCode = currencyCode; | ||
41 | + } | ||
42 | + | ||
43 | + public String getCurrencyName() { | ||
44 | + return currencyName; | ||
45 | + } | ||
46 | + | ||
47 | + public void setCurrencyName(String currencyName) { | ||
48 | + this.currencyName = currencyName; | ||
49 | + } | ||
50 | + | ||
51 | + public BigDecimal getCurrencyRate() { | ||
52 | + return currencyRate; | ||
53 | + } | ||
54 | + | ||
55 | + public void setCurrencyRate(BigDecimal currencyRate) { | ||
56 | + this.currencyRate = currencyRate; | ||
57 | + } | ||
58 | + | ||
59 | + public String getBaseCurrency() { | ||
60 | + return baseCurrency; | ||
61 | + } | ||
62 | + | ||
63 | + public void setBaseCurrency(String baseCurrency) { | ||
64 | + this.baseCurrency = baseCurrency; | ||
65 | + } | ||
66 | + | ||
67 | + public Integer getCreateBy() { | ||
68 | + return createBy; | ||
69 | + } | ||
70 | + | ||
71 | + public void setCreateBy(Integer createBy) { | ||
72 | + this.createBy = createBy; | ||
73 | + } | ||
74 | + | ||
75 | + public Date getCreateTime() { | ||
76 | + return createTime; | ||
77 | + } | ||
78 | + | ||
79 | + public void setCreateTime(Date createTime) { | ||
80 | + this.createTime = createTime; | ||
81 | + } | ||
82 | + | ||
83 | + public String getUpdateBy() { | ||
84 | + return updateBy; | ||
85 | + } | ||
86 | + | ||
87 | + public void setUpdateBy(String updateBy) { | ||
88 | + this.updateBy = updateBy; | ||
89 | + } | ||
90 | + | ||
91 | + public Date getUpdateTime() { | ||
92 | + return updateTime; | ||
93 | + } | ||
94 | + | ||
95 | + public void setUpdateTime(Date updateTime) { | ||
96 | + this.updateTime = updateTime; | ||
97 | + } | ||
98 | + | ||
99 | + public String getDataStatus() { | ||
100 | + return dataStatus; | ||
101 | + } | ||
102 | + | ||
103 | + public void setDataStatus(String dataStatus) { | ||
104 | + this.dataStatus = dataStatus; | ||
105 | + } | ||
106 | + | ||
107 | + public Integer getAuditBy() { | ||
108 | + return auditBy; | ||
109 | + } | ||
110 | + | ||
111 | + public void setAuditBy(Integer auditBy) { | ||
112 | + this.auditBy = auditBy; | ||
113 | + } | ||
114 | + | ||
115 | + public String getAuditStatus() { | ||
116 | + return auditStatus; | ||
117 | + } | ||
118 | + | ||
119 | + public void setAuditStatus(String auditStatus) { | ||
120 | + this.auditStatus = auditStatus; | ||
121 | + } | ||
122 | + | ||
123 | + public Date getAuditTime() { | ||
124 | + return auditTime; | ||
125 | + } | ||
126 | + | ||
127 | + public void setAuditTime(Date auditTime) { | ||
128 | + this.auditTime = auditTime; | ||
129 | + } | ||
130 | + | ||
131 | + public String getCurrencySymbol() { | ||
132 | + return currencySymbol; | ||
133 | + } | ||
134 | + | ||
135 | + public void setCurrencySymbol(String currencySymbol) { | ||
136 | + this.currencySymbol = currencySymbol; | ||
137 | + } | ||
138 | +} |
1 | +package com.aukey.example.entity; | ||
2 | + | ||
3 | +/** | ||
4 | + * @author: wgf | ||
5 | + * @create: 2020-05-19 17:18 | ||
6 | + * @description: | ||
7 | + **/ | ||
8 | +public class FbaFulfillmentCurrentInventoryView { | ||
9 | + private String corporationName; | ||
10 | + private String groupCode; | ||
11 | + private String groupName; | ||
12 | + private String deptCode; | ||
13 | + private String deptName; | ||
14 | + private String companySku; | ||
15 | + private String amazonSku; | ||
16 | + private String fnsku; | ||
17 | + | ||
18 | + /** | ||
19 | + * ...... | ||
20 | + * more field | ||
21 | + */ | ||
22 | + | ||
23 | + public String getCorporationName() { | ||
24 | + return corporationName; | ||
25 | + } | ||
26 | + | ||
27 | + public void setCorporationName(String corporationName) { | ||
28 | + this.corporationName = corporationName; | ||
29 | + } | ||
30 | + | ||
31 | + public String getGroupCode() { | ||
32 | + return groupCode; | ||
33 | + } | ||
34 | + | ||
35 | + public void setGroupCode(String groupCode) { | ||
36 | + this.groupCode = groupCode; | ||
37 | + } | ||
38 | + | ||
39 | + public String getGroupName() { | ||
40 | + return groupName; | ||
41 | + } | ||
42 | + | ||
43 | + public void setGroupName(String groupName) { | ||
44 | + this.groupName = groupName; | ||
45 | + } | ||
46 | + | ||
47 | + public String getDeptCode() { | ||
48 | + return deptCode; | ||
49 | + } | ||
50 | + | ||
51 | + public void setDeptCode(String deptCode) { | ||
52 | + this.deptCode = deptCode; | ||
53 | + } | ||
54 | + | ||
55 | + public String getDeptName() { | ||
56 | + return deptName; | ||
57 | + } | ||
58 | + | ||
59 | + public void setDeptName(String deptName) { | ||
60 | + this.deptName = deptName; | ||
61 | + } | ||
62 | + | ||
63 | + public String getCompanySku() { | ||
64 | + return companySku; | ||
65 | + } | ||
66 | + | ||
67 | + public void setCompanySku(String companySku) { | ||
68 | + this.companySku = companySku; | ||
69 | + } | ||
70 | + | ||
71 | + public String getAmazonSku() { | ||
72 | + return amazonSku; | ||
73 | + } | ||
74 | + | ||
75 | + public void setAmazonSku(String amazonSku) { | ||
76 | + this.amazonSku = amazonSku; | ||
77 | + } | ||
78 | + | ||
79 | + public String getFnsku() { | ||
80 | + return fnsku; | ||
81 | + } | ||
82 | + | ||
83 | + public void setFnsku(String fnsku) { | ||
84 | + this.fnsku = fnsku; | ||
85 | + } | ||
86 | +} |
1 | +package com.aukey.example.job; | ||
2 | + | ||
3 | +import com.aukey.example.util.DwUtil; | ||
4 | +import org.slf4j.Logger; | ||
5 | +import org.slf4j.LoggerFactory; | ||
6 | +import org.springframework.beans.factory.annotation.Value; | ||
7 | +import org.springframework.scheduling.annotation.Scheduled; | ||
8 | +import org.springframework.stereotype.Component; | ||
9 | + | ||
10 | +import java.util.HashMap; | ||
11 | +import java.util.Map; | ||
12 | + | ||
13 | +/** | ||
14 | + * @author: wgf | ||
15 | + * @create: 2020-05-13 10:54 | ||
16 | + * @description: 获取token定时任务 | ||
17 | + **/ | ||
18 | +@Component | ||
19 | +public class TokenJob { | ||
20 | + private Logger log = LoggerFactory.getLogger(TokenJob.class); | ||
21 | + | ||
22 | + @Value("${dwTokenApi:null}") | ||
23 | + private String dwTokenApi;/* DW获取token的接口 */ | ||
24 | + | ||
25 | + @Value("${dwAppId:null}")/* 数据仓库AppId */ | ||
26 | + private String appId; | ||
27 | + | ||
28 | + @Value("${dwAppSecret:null}")/* 数据仓库应用秘钥 */ | ||
29 | + private String appSecret; | ||
30 | + | ||
31 | + /** | ||
32 | + * 3分钟刷新获取一次token | ||
33 | + */ | ||
34 | + @Scheduled(fixedDelay = 180 * 1000l) | ||
35 | + public synchronized void fetchToken() { | ||
36 | + if ("null".equals(this.appSecret)) { | ||
37 | + log.info("未配置appSecret,不访问远程DW服务!"); | ||
38 | + return; | ||
39 | + } | ||
40 | + | ||
41 | + if ("null".equals(this.appId)) { | ||
42 | + log.info("未配置appId,不访问远程DW服务!"); | ||
43 | + return; | ||
44 | + } | ||
45 | + | ||
46 | + Map<String, Object> params = new HashMap<>(); | ||
47 | + params.put("appSecret", this.appSecret); | ||
48 | + DwUtil.doGet(dwTokenApi, params); | ||
49 | + } | ||
50 | +} |
1 | +package com.aukey.example.util; | ||
2 | + | ||
3 | +import org.springframework.util.StringUtils; | ||
4 | + | ||
5 | +import java.io.BufferedReader; | ||
6 | +import java.io.IOException; | ||
7 | +import java.io.InputStream; | ||
8 | +import java.io.InputStreamReader; | ||
9 | +import java.net.HttpURLConnection; | ||
10 | +import java.net.URL; | ||
11 | +import java.net.URLEncoder; | ||
12 | +import java.nio.charset.StandardCharsets; | ||
13 | +import java.util.Map; | ||
14 | +import java.util.Objects; | ||
15 | + | ||
16 | +/** | ||
17 | + * @author: wgf | ||
18 | + * @create: 2020-05-13 11:21 | ||
19 | + * @description: | ||
20 | + * 关于 java url 编码问题 | ||
21 | + * Java官方的URLEncoder.encode 实际上是为了post请求的content-type为x-www-form-urlencoded来设计的 | ||
22 | + * 在进行特殊参数转义的时候会将空格转为 + | ||
23 | + * 但是在 RFC1738、RFC2396协议中规定,GET请求的空格转为为 %20 | ||
24 | + * 所以在发送GET请求的特殊参数中存在空格必须 先URLEncoder.encode 然后再用 %20替换掉所有+ | ||
25 | + **/ | ||
26 | +public class DwUtil { | ||
27 | + private DwUtil() { | ||
28 | + } | ||
29 | + | ||
30 | + /** | ||
31 | + * GET请求 | ||
32 | + * | ||
33 | + * @param urlStr 请求url | ||
34 | + * @param params 请求参数 | ||
35 | + * @return | ||
36 | + */ | ||
37 | + public static String doGet(String urlStr, Map<String, Object> params) { | ||
38 | + | ||
39 | + if (StringUtils.isEmpty(urlStr)) { | ||
40 | + return null; | ||
41 | + } | ||
42 | + | ||
43 | + HttpURLConnection connection = null; | ||
44 | + InputStream is = null; | ||
45 | + BufferedReader br = null; | ||
46 | + String result = null; | ||
47 | + | ||
48 | + try { | ||
49 | + StringBuilder sb = new StringBuilder(urlStr); | ||
50 | + if (params != null && !params.isEmpty()) { | ||
51 | + boolean isFirst = true; | ||
52 | + | ||
53 | + for (Map.Entry<String, Object> entry : params.entrySet()) { | ||
54 | + | ||
55 | + if (Objects.isNull(entry.getValue())) { | ||
56 | + continue; | ||
57 | + } | ||
58 | + | ||
59 | + if (isFirst) { | ||
60 | + sb.append("?"); | ||
61 | + isFirst = false; | ||
62 | + } else { | ||
63 | + sb.append("&"); | ||
64 | + } | ||
65 | + | ||
66 | + // 兼容参数特殊符号 | ||
67 | + String value = String.valueOf(entry.getValue()); | ||
68 | + value = URLEncoder.encode(value, "UTF-8"); | ||
69 | + value = value.replaceAll("\\+", "%20"); | ||
70 | + | ||
71 | + sb.append(entry.getKey()) | ||
72 | + .append("=") | ||
73 | + .append(value); | ||
74 | + } | ||
75 | + | ||
76 | + urlStr = sb.toString(); | ||
77 | + } | ||
78 | + | ||
79 | + URL url = new URL(urlStr); | ||
80 | + connection = (HttpURLConnection) url.openConnection(); | ||
81 | + connection.setRequestMethod("GET"); | ||
82 | + connection.setConnectTimeout(15000); | ||
83 | + connection.setReadTimeout(60000); | ||
84 | + connection.connect(); | ||
85 | + int responseCode = connection.getResponseCode(); | ||
86 | + if (responseCode == 200) { | ||
87 | + is = connection.getInputStream(); | ||
88 | + br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); | ||
89 | + StringBuilder sbf = new StringBuilder(); | ||
90 | + String temp; | ||
91 | + while ((temp = br.readLine()) != null) { | ||
92 | + sbf.append(temp); | ||
93 | + sbf.append("\r\n"); | ||
94 | + } | ||
95 | + result = sbf.toString(); | ||
96 | + } | ||
97 | + } catch (Exception e) { | ||
98 | + e.printStackTrace(); | ||
99 | + } finally { | ||
100 | + if (null != br) { | ||
101 | + try { | ||
102 | + br.close(); | ||
103 | + } catch (IOException e) { | ||
104 | + e.printStackTrace(); | ||
105 | + } | ||
106 | + } | ||
107 | + | ||
108 | + if (null != is) { | ||
109 | + try { | ||
110 | + is.close(); | ||
111 | + } catch (IOException e) { | ||
112 | + e.printStackTrace(); | ||
113 | + } | ||
114 | + } | ||
115 | + | ||
116 | + if (connection != null) { | ||
117 | + connection.disconnect();// 关闭远程连接 | ||
118 | + } | ||
119 | + } | ||
120 | + | ||
121 | + return result; | ||
122 | + } | ||
123 | + | ||
124 | + | ||
125 | + /** | ||
126 | + * @param dwDoMain 数据仓库域名 | ||
127 | + * @param api 数据仓库申请的API | ||
128 | + * @param params 参数 | ||
129 | + * @return | ||
130 | + */ | ||
131 | + public static String doGet(String dwDoMain, String api, Map<String, Object> params) { | ||
132 | + return doGet(dwDoMain + api, params); | ||
133 | + } | ||
134 | +} |
1 | +package com.aukey.example.vo; | ||
2 | + | ||
3 | +import org.springframework.cglib.beans.BeanMap; | ||
4 | + | ||
5 | +import java.util.Map; | ||
6 | + | ||
7 | +/** | ||
8 | + * @author: wgf | ||
9 | + * @create: 2020-05-13 12:01 | ||
10 | + * @description: | ||
11 | + **/ | ||
12 | +public class DwParamVo { | ||
13 | + public DwParamVo() { | ||
14 | + } | ||
15 | + | ||
16 | + public DwParamVo(String appId, String token) { | ||
17 | + this.appId = appId; | ||
18 | + this.token = token; | ||
19 | + } | ||
20 | + | ||
21 | + /** | ||
22 | + * 应用id | ||
23 | + */ | ||
24 | + private String appId; | ||
25 | + | ||
26 | + /** | ||
27 | + * token | ||
28 | + */ | ||
29 | + private String token; | ||
30 | + | ||
31 | + /** | ||
32 | + * 查询条件(可不传) | ||
33 | + */ | ||
34 | + private String queryCondition; | ||
35 | + | ||
36 | + /** | ||
37 | + * 字段选择(可不传) | ||
38 | + */ | ||
39 | + private String multiFields; | ||
40 | + | ||
41 | + /** | ||
42 | + * 偏移量(可不传 如果传递offset则必传limit) | ||
43 | + */ | ||
44 | + private Integer offset; | ||
45 | + | ||
46 | + /** | ||
47 | + * 限制条数(可不传 如果传递limit则必传offset) | ||
48 | + */ | ||
49 | + private Integer limit; | ||
50 | + | ||
51 | + /** | ||
52 | + * 是否流式读写(Y/N) | ||
53 | + */ | ||
54 | + private String stream; | ||
55 | + | ||
56 | + public String getAppId() { | ||
57 | + return appId; | ||
58 | + } | ||
59 | + | ||
60 | + public void setAppId(String appId) { | ||
61 | + this.appId = appId; | ||
62 | + } | ||
63 | + | ||
64 | + public String getToken() { | ||
65 | + return token; | ||
66 | + } | ||
67 | + | ||
68 | + public void setToken(String token) { | ||
69 | + this.token = token; | ||
70 | + } | ||
71 | + | ||
72 | + public String getQueryCondition() { | ||
73 | + return queryCondition; | ||
74 | + } | ||
75 | + | ||
76 | + public void setQueryCondition(String queryCondition) { | ||
77 | + this.queryCondition = queryCondition; | ||
78 | + } | ||
79 | + | ||
80 | + public String getMultiFields() { | ||
81 | + return multiFields; | ||
82 | + } | ||
83 | + | ||
84 | + public void setMultiFields(String multiFields) { | ||
85 | + this.multiFields = multiFields; | ||
86 | + } | ||
87 | + | ||
88 | + public Integer getOffset() { | ||
89 | + return offset; | ||
90 | + } | ||
91 | + | ||
92 | + public void setOffset(Integer offset) { | ||
93 | + this.offset = offset; | ||
94 | + } | ||
95 | + | ||
96 | + public Integer getLimit() { | ||
97 | + return limit; | ||
98 | + } | ||
99 | + | ||
100 | + public void setLimit(Integer limit) { | ||
101 | + this.limit = limit; | ||
102 | + } | ||
103 | + | ||
104 | + public String getStream() { | ||
105 | + return stream; | ||
106 | + } | ||
107 | + | ||
108 | + public void setStream(String stream) { | ||
109 | + this.stream = stream; | ||
110 | + } | ||
111 | + | ||
112 | + public Map<String, Object> toMap() { | ||
113 | + return BeanMap.create(this); | ||
114 | + } | ||
115 | +} |
1 | +package com.aukey.example.vo; | ||
2 | + | ||
3 | +import java.util.List; | ||
4 | + | ||
5 | +/** | ||
6 | + * @author: wgf | ||
7 | + * @create: 2020-05-13 14:22 | ||
8 | + * @description: 数据仓库结果集返回vo | ||
9 | + **/ | ||
10 | +public class DwResultVo<T> { | ||
11 | + | ||
12 | + /** | ||
13 | + * API是否调用成功 | ||
14 | + */ | ||
15 | + private boolean success; | ||
16 | + | ||
17 | + /** | ||
18 | + * API返回的异常消息 | ||
19 | + */ | ||
20 | + private String message; | ||
21 | + | ||
22 | + /** | ||
23 | + * 错误编码 200为正常 | ||
24 | + */ | ||
25 | + private Integer code; | ||
26 | + | ||
27 | + /** | ||
28 | + * 返回数据 | ||
29 | + */ | ||
30 | + private List<T> data; | ||
31 | + | ||
32 | + public boolean isSuccess() { | ||
33 | + return success; | ||
34 | + } | ||
35 | + | ||
36 | + public void setSuccess(boolean success) { | ||
37 | + this.success = success; | ||
38 | + } | ||
39 | + | ||
40 | + public String getMessage() { | ||
41 | + return message; | ||
42 | + } | ||
43 | + | ||
44 | + public void setMessage(String message) { | ||
45 | + this.message = message; | ||
46 | + } | ||
47 | + | ||
48 | + public Integer getCode() { | ||
49 | + return code; | ||
50 | + } | ||
51 | + | ||
52 | + public void setCode(Integer code) { | ||
53 | + this.code = code; | ||
54 | + } | ||
55 | + | ||
56 | + public List<T> getData() { | ||
57 | + return data; | ||
58 | + } | ||
59 | + | ||
60 | + public void setData(List<T> data) { | ||
61 | + this.data = data; | ||
62 | + } | ||
63 | +} |
1 | +package com.aukey.example.web; | ||
2 | + | ||
3 | +import com.alibaba.fastjson.JSON; | ||
4 | +import com.alibaba.fastjson.TypeReference; | ||
5 | +import com.aukey.example.conf.StreamReaderHandler; | ||
6 | +import com.aukey.example.entity.CurrencySet; | ||
7 | +import com.aukey.example.entity.FbaFulfillmentCurrentInventoryView; | ||
8 | +import com.aukey.example.util.DwUtil; | ||
9 | +import com.aukey.example.vo.DwParamVo; | ||
10 | +import com.aukey.example.vo.DwResultVo; | ||
11 | +import io.swagger.annotations.Api; | ||
12 | +import io.swagger.annotations.ApiOperation; | ||
13 | +import org.slf4j.Logger; | ||
14 | +import org.slf4j.LoggerFactory; | ||
15 | +import org.springframework.beans.factory.annotation.Value; | ||
16 | +import org.springframework.util.CollectionUtils; | ||
17 | +import org.springframework.util.StringUtils; | ||
18 | +import org.springframework.web.bind.annotation.GetMapping; | ||
19 | +import org.springframework.web.bind.annotation.RestController; | ||
20 | + | ||
21 | +import java.util.List; | ||
22 | +import java.util.function.Consumer; | ||
23 | +import java.util.function.Supplier; | ||
24 | + | ||
25 | +/** | ||
26 | + * @author: wgf | ||
27 | + * @create: 2020-05-13 16:34 | ||
28 | + * @description: API调用测试 | ||
29 | + * <p> | ||
30 | + * **************************************************************************************** | ||
31 | + * * * | ||
32 | + * * 这里的TEST代码写在controller是方便使用swagger2本地调试,正常业务应该是在项目的定时任务里 * | ||
33 | + * * * | ||
34 | + * **************************************************************************************** | ||
35 | + * <p> | ||
36 | + * Demo 提供三种数据拉取方式, | ||
37 | + * 第一种:直接调用API适合[1, 100000]数据读取 | ||
38 | + * 第二种:分页适合[1, 1000000]数据读取,数据基数过大深层分页会影响查询性能,并且客户端需要不断发起请求 | ||
39 | + * 第三种:流式读取适合[1, 10000000]数据读取,不存在深层分页性能影响,并且客户端只需要发起一次请求 | ||
40 | + **/ | ||
41 | +@RestController | ||
42 | +@Api(tags = "API调用DEMO") | ||
43 | +public class TestApiController { | ||
44 | + private Logger log = LoggerFactory.getLogger(TestApiController.class); | ||
45 | + | ||
46 | + // API需要在傲基数仓申请授权 | ||
47 | + public static final String CURRENCY_SET_API = "/base/currency_set"; | ||
48 | + public static final String FBA_FULFILLMENT_CURRENT_INVENTORY_VIEW_API = "/stock/fba_fulfillment_current_inventory_view"; | ||
49 | + | ||
50 | + @Value("${dwDataApi:null}") | ||
51 | + private String dwDataApi; | ||
52 | + | ||
53 | + @Value("${dwAppId:null}") | ||
54 | + private String appId; | ||
55 | + | ||
56 | + @ApiOperation(value = "获取前100条汇率 DEMO") | ||
57 | + @GetMapping("/query_all") | ||
58 | + public void queryAll() { | ||
59 | + | ||
60 | + // 构造请求参数 | ||
61 | + DwParamVo paramVo = new DwParamVo(this.appId, TokenController.getCurrentToken()); | ||
62 | + | ||
63 | + // 添加查询条件 | ||
64 | + paramVo.setQueryCondition("WHERE currency_code = 'USD'"); | ||
65 | + | ||
66 | + // 指定获取条数 | ||
67 | + paramVo.setOffset(0); | ||
68 | + paramVo.setLimit(100); | ||
69 | + | ||
70 | + // 调用API | ||
71 | + String result = DwUtil.doGet(dwDataApi, CURRENCY_SET_API, paramVo.toMap()); | ||
72 | + | ||
73 | + if (StringUtils.isEmpty(result)) { | ||
74 | + throw new RuntimeException(String.format("API %s 调用失败", CURRENCY_SET_API)); | ||
75 | + } | ||
76 | + // json解析为对象 | ||
77 | + DwResultVo<CurrencySet> resultVo = JSON.parseObject(result, new TypeReference<DwResultVo<CurrencySet>>() { | ||
78 | + }); | ||
79 | + | ||
80 | + log.info(""); | ||
81 | + log.info("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓"); | ||
82 | + log.info("API请求状态:{}", resultVo.getMessage()); | ||
83 | + log.info("API TOP100 数据:{}", JSON.toJSONString(resultVo.getData())); | ||
84 | + log.info("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"); | ||
85 | + log.info(""); | ||
86 | + } | ||
87 | + | ||
88 | + | ||
89 | + @ApiOperation(value = "分页查询 DEMO") | ||
90 | + @GetMapping("/query_page") | ||
91 | + public void queryPage() { | ||
92 | + | ||
93 | + // 构造请求参数 | ||
94 | + DwParamVo paramVo = new DwParamVo(this.appId, TokenController.getCurrentToken()); | ||
95 | + | ||
96 | + // 页数 | ||
97 | + int pageNum = 0; | ||
98 | + // 每页数据大小 | ||
99 | + final int pageSize = 1000; | ||
100 | + // 当前页数据 | ||
101 | + int currentPageSize; | ||
102 | + | ||
103 | + // 分页请求 | ||
104 | + do { | ||
105 | + paramVo.setToken(TokenController.getCurrentToken()); | ||
106 | + paramVo.setOffset(pageNum * pageSize); | ||
107 | + paramVo.setLimit(pageSize); | ||
108 | + // TODO 自定义查询条件 | ||
109 | + | ||
110 | + String result = DwUtil.doGet(dwDataApi, CURRENCY_SET_API, paramVo.toMap()); | ||
111 | + if (StringUtils.isEmpty(result)) { | ||
112 | + throw new RuntimeException(String.format("API %s 调用失败", CURRENCY_SET_API)); | ||
113 | + } | ||
114 | + // json解析为对象 | ||
115 | + DwResultVo<CurrencySet> resultVo = JSON.parseObject(result, new TypeReference<DwResultVo<CurrencySet>>() { | ||
116 | + }); | ||
117 | + | ||
118 | + currentPageSize = CollectionUtils.isEmpty(resultVo.getData()) ? 0 : resultVo.getData().size(); | ||
119 | + pageNum++; | ||
120 | + log.info("========== 获取API:{} 第:{}页数据 数据条数:{} ==========", CURRENCY_SET_API, pageNum + 1, currentPageSize); | ||
121 | + // TODO 自定义实现持久化逻辑 | ||
122 | + | ||
123 | + } while (currentPageSize == pageSize); | ||
124 | + } | ||
125 | + | ||
126 | + | ||
127 | + /** | ||
128 | + * 流式读取适合百/千万级别的内网数据同步,本机测试 | ||
129 | + * <p> | ||
130 | + * 网络环境:局域网 | ||
131 | + * 数据源 :华为dws | ||
132 | + * cpu : 4核3.2GHz | ||
133 | + * 内存 :16GB DDR3 | ||
134 | + * 测试数据:1000W条 | ||
135 | + * 回调函数不做持久化操作 | ||
136 | + * <p> | ||
137 | + * <p> | ||
138 | + * 测试报表 | ||
139 | + * ************************************** | ||
140 | + * * | ||
141 | + * * QPS :1800 | ||
142 | + * * 耗时 :100W / 5分钟 | ||
143 | + * * CPU毛刺 :1% - 5% 波动 | ||
144 | + * * 内存毛刺:50Mb - 150Mb 波动 | ||
145 | + * * | ||
146 | + * ************************************** | ||
147 | + */ | ||
148 | + @ApiOperation(value = "流式读取 DEMO") | ||
149 | + @GetMapping("/query_stream") | ||
150 | + public void queryStream() { | ||
151 | + long stratTime = System.currentTimeMillis(); | ||
152 | + | ||
153 | + try { | ||
154 | + // 构造请求参数 | ||
155 | + DwParamVo paramVo = new DwParamVo(this.appId, TokenController.getCurrentToken()); | ||
156 | + // 设置为流式读取 | ||
157 | + paramVo.setStream("Y"); | ||
158 | + // TODO 自定义查询条件 | ||
159 | + paramVo.setOffset(3000000); | ||
160 | + | ||
161 | + // 定义实体构造函数 | ||
162 | + Supplier<FbaFulfillmentCurrentInventoryView> constructor = FbaFulfillmentCurrentInventoryView::new; | ||
163 | + | ||
164 | + // 流式读取回调函数 | ||
165 | + Consumer<List<FbaFulfillmentCurrentInventoryView>> callBack = (List<FbaFulfillmentCurrentInventoryView> list) -> { | ||
166 | + // TODO 自定义实现持久化逻辑 | ||
167 | + // ... more | ||
168 | + // mapper.insertList(list) | ||
169 | + }; | ||
170 | + | ||
171 | + // 指定回调函数数据大小,取值[500, 5000]. | ||
172 | + // 取值越大,数据读取占用的堆内存越高 | ||
173 | + int batchSize = 5000; | ||
174 | + | ||
175 | + // 使用 StreamAPI | ||
176 | + StreamReaderHandler.reader( | ||
177 | + dwDataApi, | ||
178 | + FBA_FULFILLMENT_CURRENT_INVENTORY_VIEW_API, | ||
179 | + paramVo.toMap(), | ||
180 | + callBack, | ||
181 | + batchSize, | ||
182 | + constructor); | ||
183 | + | ||
184 | + } catch (Exception e) { | ||
185 | + log.info("流式读取异常", e); | ||
186 | + } finally { | ||
187 | + log.info("流式读取使用时间 {} 秒", (System.currentTimeMillis() - stratTime) / 1000.0f); | ||
188 | + } | ||
189 | + } | ||
190 | +} |
1 | +package com.aukey.example.web; | ||
2 | + | ||
3 | +import org.slf4j.Logger; | ||
4 | +import org.slf4j.LoggerFactory; | ||
5 | +import org.springframework.util.StringUtils; | ||
6 | +import org.springframework.web.bind.annotation.PostMapping; | ||
7 | +import org.springframework.web.bind.annotation.RequestMapping; | ||
8 | +import org.springframework.web.bind.annotation.RestController; | ||
9 | +import springfox.documentation.annotations.ApiIgnore; | ||
10 | + | ||
11 | +import java.util.concurrent.TimeUnit; | ||
12 | + | ||
13 | +/** | ||
14 | + * @author: wgf | ||
15 | + * @create: 2020-05-13 10:11 | ||
16 | + * @description: 获取token回调controller | ||
17 | + * 当向数据仓库平台发起 http://dw.aukeyit.com/api/authorize?appSecret=应用秘钥 请求时, | ||
18 | + * 数据仓库验证应用秘钥后会生成token,并通过调用APP回调地址将token作为参数返回到客户端 | ||
19 | + **/ | ||
20 | + | ||
21 | + | ||
22 | +@RestController | ||
23 | +@RequestMapping("/token") | ||
24 | +@ApiIgnore | ||
25 | +public class TokenController { | ||
26 | + private Logger log = LoggerFactory.getLogger(TokenController.class); | ||
27 | + | ||
28 | + /** | ||
29 | + * 当前token | ||
30 | + */ | ||
31 | + private static String currentToken = ""; | ||
32 | + | ||
33 | + | ||
34 | + /********************************************************************* | ||
35 | + * 这里的controller path 需要和数据仓库中APP设置的回调地址保持一致。 * | ||
36 | + * 例如APP里回调地址为 http://localhost:8099/token/receive * | ||
37 | + * TokenJob定时任务定时请求数据仓库获取授权API后 * | ||
38 | + * 数据仓库回调 http://localhost:8099/token/receive,并且传递token参数 * | ||
39 | + * 获取token后,可以放在 mysql,redis等。这里DEMO取巧放在内存中。 * | ||
40 | + ********************************************************************/ | ||
41 | + @PostMapping("/receive") | ||
42 | + public void receive(String token) { | ||
43 | + log.info("获取到远程DW服务回调请求,token:{}", token); | ||
44 | + currentToken = token; | ||
45 | + } | ||
46 | + | ||
47 | + public static String getCurrentToken() { | ||
48 | + for (int i = 0; i < 5; i++) { | ||
49 | + if (StringUtils.isEmpty(currentToken)) { | ||
50 | + try { | ||
51 | + TimeUnit.SECONDS.sleep(2); | ||
52 | + } catch (InterruptedException e) { | ||
53 | + e.printStackTrace(); | ||
54 | + } | ||
55 | + } else { | ||
56 | + return currentToken; | ||
57 | + } | ||
58 | + } | ||
59 | + | ||
60 | + return null; | ||
61 | + } | ||
62 | +} |
src/main/resources/application.yml
0 → 100644
-
请 注册 或 登录 后发表评论