Merge remote-tracking branch 'origin/master' into feature/payment-apply-bfi-sync
# Conflicts: # blade-common/src/main/java/org/springblade/common/config/BladeCommonConfiguration.java # blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClient.java # blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClientFallback.java # blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java # blade-service/blade-transport/pom.xml # blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CustomerArchiveMapper.xml # blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java # blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/PaymentApplicationWrapper.java # blade-service/blade-transport/src/main/resources/application-dev.yml # blade-third-party-api/pom.xml # doc/nacos/blade-dev.yaml # doc/nacos/blade-prod.yaml # doc/nacos/third-party-api.yaml # pom.xml
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-third-party-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>blade-lbs-api</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-tool</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.thirdparty.lbs.config;
|
||||
|
||||
import feign.Logger;
|
||||
import feign.Request;
|
||||
import org.springblade.thirdparty.lbs.interceptor.LbsRequestInterceptor;
|
||||
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* LBS Feign 客户端配置。
|
||||
* <p>
|
||||
* 关闭父上下文继承,避免全局 BladeFeignRequestInterceptor 透传登录态请求头导致 gwzh 400。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class LbsFeignClientConfig {
|
||||
|
||||
@Bean
|
||||
public FeignClientConfigurer feignClientConfigurer() {
|
||||
return new FeignClientConfigurer() {
|
||||
@Override
|
||||
public boolean inheritParentConfiguration() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LbsRequestInterceptor requestInterceptor(LbsProperties lbsProperties) {
|
||||
return new LbsRequestInterceptor(lbsProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Logger.Level feignLoggerLevel() {
|
||||
return Logger.Level.FULL;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Request.Options options() {
|
||||
return new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true);
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.thirdparty.lbs.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* LBS 配置
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "third-party.lbs")
|
||||
public class LbsProperties {
|
||||
|
||||
/**
|
||||
* LBS 网关基础地址
|
||||
*/
|
||||
private String baseUrl;
|
||||
|
||||
/**
|
||||
* 车辆实时定位路径
|
||||
*/
|
||||
private String locateUrl = "/gwzh/LBS/LBS_LOCATE";
|
||||
|
||||
/**
|
||||
* 车辆历史轨迹路径
|
||||
*/
|
||||
private String trackUrl = "/gwzh/LBS/LBS_TRACK";
|
||||
|
||||
/**
|
||||
* gwzh 网关 Authorization(Basic),与 OA 人员接口一致
|
||||
*/
|
||||
private String authorization;
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.thirdparty.lbs.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 第三方 LBS 自动配置
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@EnableConfigurationProperties(LbsProperties.class)
|
||||
@Configuration
|
||||
public class ThirdPartyLbsAutoConfiguration {
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.thirdparty.lbs.constant;
|
||||
|
||||
/**
|
||||
* LBS 常量
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public final class LbsConstant {
|
||||
|
||||
private LbsConstant() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应码(文档标注)
|
||||
*/
|
||||
public static final int SUCCESS_STATUS = 200;
|
||||
|
||||
/**
|
||||
* 成功响应码(字符串)
|
||||
*/
|
||||
public static final String SUCCESS_STATUS_TEXT = "200";
|
||||
|
||||
/**
|
||||
* gwzh 常见成功码(与 OA 一致)
|
||||
*/
|
||||
public static final String SUCCESS_CODE = "1";
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.thirdparty.lbs.feign;
|
||||
|
||||
import org.springblade.thirdparty.lbs.config.LbsFeignClientConfig;
|
||||
import org.springblade.thirdparty.lbs.pojo.dto.LbsLocateRequest;
|
||||
import org.springblade.thirdparty.lbs.pojo.vo.LbsLocateResponse;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
/**
|
||||
* LBS 接口
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@FeignClient(name = "LBS", url = "${thirdParty.lbs.baseUrl}", configuration = LbsFeignClientConfig.class)
|
||||
public interface ILbsClient {
|
||||
|
||||
/**
|
||||
* 车辆实时定位
|
||||
*
|
||||
* @param request 请求(cph=车牌号)
|
||||
* @return 定位结果
|
||||
*/
|
||||
@PostMapping("${thirdParty.lbs.locateUrl:/gwzh/LBS/LBS_LOCATE}")
|
||||
LbsLocateResponse locate(@RequestBody LbsLocateRequest request);
|
||||
|
||||
/**
|
||||
* 车辆历史轨迹
|
||||
*
|
||||
* @param request 请求(cph、start_date、end_date)
|
||||
* @return 轨迹结果
|
||||
*/
|
||||
@PostMapping("${thirdParty.lbs.trackUrl:/gwzh/LBS/LBS_TRACK}")
|
||||
LbsLocateResponse track(@RequestBody LbsLocateRequest request);
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.thirdparty.lbs.interceptor;
|
||||
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.thirdparty.lbs.config.LbsProperties;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* LBS Feign 请求拦截器:仅发送 Authorization + Content-Type,与 OA 人员接口一致。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class LbsRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static final String BASIC_PREFIX = "Basic ";
|
||||
private static final String BEARER_PREFIX = "Bearer ";
|
||||
private static final Set<String> KEEP_HEADERS = Set.of(
|
||||
HttpHeaders.AUTHORIZATION.toLowerCase(Locale.ROOT),
|
||||
HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ROOT)
|
||||
);
|
||||
|
||||
private final LbsProperties lbsProperties;
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
stripUnwantedHeaders(template);
|
||||
template.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
String authorization = normalizeAuthorization(lbsProperties.getAuthorization());
|
||||
if (StringUtil.isNotBlank(authorization)) {
|
||||
template.header(HttpHeaders.AUTHORIZATION, authorization);
|
||||
} else {
|
||||
log.warn("LBS Feign 未配置 third-party.lbs.authorization,gwzh 网关可能拒绝请求");
|
||||
}
|
||||
|
||||
logRequest(template);
|
||||
}
|
||||
|
||||
private void logRequest(RequestTemplate template) {
|
||||
String bodyText = "";
|
||||
byte[] body = template.body();
|
||||
if (body != null && body.length > 0) {
|
||||
Charset charset = template.requestCharset() == null ? StandardCharsets.UTF_8 : template.requestCharset();
|
||||
bodyText = new String(body, charset);
|
||||
}
|
||||
log.info("LBS Feign 请求 method={}, url={}{}{}, headers={}, body={}",
|
||||
template.method(),
|
||||
template.feignTarget() == null ? "" : template.feignTarget().url(),
|
||||
template.path(),
|
||||
template.queryLine() == null ? "" : template.queryLine(),
|
||||
template.headers(),
|
||||
bodyText);
|
||||
}
|
||||
|
||||
private void stripUnwantedHeaders(RequestTemplate template) {
|
||||
Map<String, Collection<String>> headers = template.headers();
|
||||
List<String> headerNames = new ArrayList<>(headers.keySet());
|
||||
for (String headerName : headerNames) {
|
||||
if (!KEEP_HEADERS.contains(headerName.toLowerCase(Locale.ROOT))) {
|
||||
template.removeHeader(headerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeAuthorization(String authorization) {
|
||||
if (StringUtil.isBlank(authorization)) {
|
||||
return authorization;
|
||||
}
|
||||
if (StringUtil.startsWithIgnoreCase(authorization, BASIC_PREFIX)
|
||||
|| StringUtil.startsWithIgnoreCase(authorization, BEARER_PREFIX)) {
|
||||
return authorization;
|
||||
}
|
||||
return BASIC_PREFIX + authorization;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.thirdparty.lbs.pojo.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* LBS 定位/历史轨迹请求
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class LbsLocateRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 车牌号
|
||||
*/
|
||||
private String cph;
|
||||
|
||||
/**
|
||||
* 开始日期,格式 YYYY-MM-DD(历史轨迹)
|
||||
*/
|
||||
@JsonProperty("start_date")
|
||||
private String startDate;
|
||||
|
||||
/**
|
||||
* 结束日期,格式 YYYY-MM-DD(历史轨迹)
|
||||
*/
|
||||
@JsonProperty("end_date")
|
||||
private String endDate;
|
||||
|
||||
public LbsLocateRequest(String cph) {
|
||||
this.cph = cph;
|
||||
}
|
||||
|
||||
public LbsLocateRequest(String cph, String startDate, String endDate) {
|
||||
this.cph = cph;
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.thirdparty.lbs.pojo.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Data;
|
||||
import org.springblade.thirdparty.lbs.constant.LbsConstant;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* LBS 定位/历史轨迹响应。
|
||||
* <p>
|
||||
* 实际网关返回示例:
|
||||
* {@code {"code":200,"obj":{...},"list":null,"msg":"OK"}}
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class LbsLocateResponse implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 响应代码,200 为正确
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 响应代码(网关返回 200 或 "1")
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 提示信息
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 提示信息(兼容 message)
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 单点定位对象
|
||||
*/
|
||||
private JsonNode obj;
|
||||
|
||||
/**
|
||||
* 历史轨迹点列表
|
||||
*/
|
||||
private JsonNode list;
|
||||
|
||||
/**
|
||||
* 兼容旧字段 data
|
||||
*/
|
||||
private JsonNode data;
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
if (status != null && (status == LbsConstant.SUCCESS_STATUS || status == 1)) {
|
||||
return true;
|
||||
}
|
||||
if (code == null) {
|
||||
return false;
|
||||
}
|
||||
String normalized = code.trim();
|
||||
return LbsConstant.SUCCESS_STATUS_TEXT.equals(normalized)
|
||||
|| LbsConstant.SUCCESS_CODE.equals(normalized);
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
public String errorMessage() {
|
||||
if (msg != null && !msg.isBlank() && !"OK".equalsIgnoreCase(msg) && !"Success".equalsIgnoreCase(msg)) {
|
||||
return msg;
|
||||
}
|
||||
if (message != null && !message.isBlank()
|
||||
&& !"OK".equalsIgnoreCase(message) && !"Success".equalsIgnoreCase(message)) {
|
||||
return message;
|
||||
}
|
||||
return "LBS接口调用失败";
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.springblade.thirdparty.lbs.config.ThirdPartyLbsAutoConfiguration
|
||||
Vendored
+9
@@ -65,6 +65,15 @@ public interface IMKClient {
|
||||
@PostMapping("${thirdParty.mk.getCurrentNodesUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getCurNodesInfo}")
|
||||
MKResultVO<List<MKNodeVO>> getCurrentNodes(@RequestBody MKCurrentNodesDTO param, @RequestParam("access_token") String accessToken);
|
||||
|
||||
/**
|
||||
* 获取流程实例详情
|
||||
* @param param
|
||||
* @param accessToken
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("${thirdParty.mk.getProcessInfoUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getProcessInfo}")
|
||||
MKResultVO<Object> getProcessInfo(@RequestBody MKCurrentNodesDTO param, @RequestParam("access_token") String accessToken);
|
||||
|
||||
/**
|
||||
* 获取节点处理人信息
|
||||
* @param param
|
||||
|
||||
+6
@@ -1,6 +1,7 @@
|
||||
package org.springblade.thirdparty.mk.pojo.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
@@ -44,4 +45,9 @@ public class MKProcessCreateDTO implements Serializable {
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private Object tempVarData;
|
||||
/**
|
||||
* 业务类型,仅 ERP 内部使用,不传给 MK
|
||||
*/
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
private String bizType;
|
||||
}
|
||||
|
||||
+8
@@ -66,6 +66,14 @@ public interface IMKService {
|
||||
*/
|
||||
List<MKNodeVO> getCurrentNodes(String processInstanceId, String loginName);
|
||||
|
||||
/**
|
||||
* 获取流程实例详情
|
||||
* @param processInstanceId
|
||||
* @param loginName
|
||||
* @return
|
||||
*/
|
||||
Object getProcessInfo(String processInstanceId, String loginName);
|
||||
|
||||
/**
|
||||
* 获取节点处理人信息
|
||||
* @param processInstanceId
|
||||
|
||||
+15
@@ -246,6 +246,21 @@ public class MKServiceImpl implements IMKService {
|
||||
return nodesResult.getData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProcessInfo(String processInstanceId, String loginName) {
|
||||
String accessToken = getToken();
|
||||
MKCurrentNodesDTO param = new MKCurrentNodesDTO();
|
||||
param.setProcessInstanceId(processInstanceId);
|
||||
param.setLoginName(loginName);
|
||||
MKResultVO<Object> processInfoResult = client.getProcessInfo(param, accessToken);
|
||||
log.info("调用mk接口 获取流程实例详情 :{}", JSONUtil.toJsonStr(processInfoResult));
|
||||
if (processInfoResult == null || !processInfoResult.isSuccess()) {
|
||||
log.error("调用mk接口 获取流程实例详情异常:{}", JSONUtil.toJsonStr(processInfoResult));
|
||||
return null;
|
||||
}
|
||||
return processInfoResult.getData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MKAllHandlerVO getNodeHandlers(String processInstanceId, String loginName, String nodeId) {
|
||||
String accessToken = getToken();
|
||||
|
||||
+36
-12
@@ -1,23 +1,47 @@
|
||||
package org.springblade.thirdparty.oa.config;
|
||||
|
||||
import feign.RequestInterceptor;
|
||||
import jakarta.annotation.Resource;
|
||||
import feign.Logger;
|
||||
import feign.Request;
|
||||
import org.springblade.thirdparty.oa.interceptor.OARequestInterceptor;
|
||||
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* OA Feign 客户端配置。
|
||||
* <p>
|
||||
* 必须关闭父上下文继承,否则全局 {@code BladeFeignRequestInterceptor}
|
||||
* 会把当前登录请求的 Host、Content-Length、Blade-Auth 等头再次写入,
|
||||
* 导致 gwzh nginx 返回 400。
|
||||
*
|
||||
* @author bfhuange
|
||||
* @since 2024/12/18
|
||||
*/
|
||||
public class OAFeignClientConfig {
|
||||
@Resource
|
||||
OAProperties oaProperties;
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor requestInterceptor() {
|
||||
return template -> {
|
||||
// 空实现屏蔽 全局拦截器 BladeFeignRequestInterceptor
|
||||
String authorization=oaProperties.getAuthorization();
|
||||
template.header("Authorization",authorization);
|
||||
};
|
||||
}
|
||||
@Bean
|
||||
public FeignClientConfigurer feignClientConfigurer() {
|
||||
return new FeignClientConfigurer() {
|
||||
@Override
|
||||
public boolean inheritParentConfiguration() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OARequestInterceptor requestInterceptor(OAProperties oaProperties) {
|
||||
return new OARequestInterceptor(oaProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Logger.Level feignLoggerLevel() {
|
||||
return Logger.Level.FULL;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Request.Options options() {
|
||||
return new Request.Options(10, TimeUnit.SECONDS, 120, TimeUnit.SECONDS, true);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ public class OAProperties {
|
||||
private String baseUrl;
|
||||
|
||||
/**
|
||||
* authorization
|
||||
* gwzh 网关 Authorization(Basic),与可用 curl 一致
|
||||
*/
|
||||
private String authorization;
|
||||
}
|
||||
|
||||
+4
@@ -30,4 +30,8 @@ public class OAConvertConstant {
|
||||
* 根公司父id
|
||||
*/
|
||||
public static final Long ROOT_PARENT_ID = 0L;
|
||||
/**
|
||||
* 同步人员时挂载的顶级组织名称
|
||||
*/
|
||||
public static final String ROOT_COMPANY_NAME = "桂物物流集团";
|
||||
}
|
||||
|
||||
Vendored
+3
-3
@@ -26,7 +26,7 @@ public interface IOAClient {
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("${thirdParty.oa.queryCompanyPageUrl:/api/hrm/resful/getHrmsubcompanyWithPage}")
|
||||
@PostMapping("${thirdParty.oa.queryCompanyPageUrl:/gwzh/OA/OA_GET_COMPANY_LIST}")
|
||||
OAResponse<OACompanyResponse> queryCompanyPage(@RequestBody OASearch<OACompanySearch> param);
|
||||
|
||||
/**
|
||||
@@ -34,7 +34,7 @@ public interface IOAClient {
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("${thirdParty.oa.queryDepartmentPage:/api/hrm/resful/getHrmdepartmentWithPage}")
|
||||
@PostMapping("${thirdParty.oa.queryDepartmentPageUrl:/gwzh/OA/OA_GET_DEPARTMENT_LIST}")
|
||||
OAResponse<OADepartmentResponse> queryDepartmentPage(@RequestBody OASearch<OADepartmentSearch> param);
|
||||
|
||||
/**
|
||||
@@ -42,6 +42,6 @@ public interface IOAClient {
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("${thirdParty.oa.queryPersonPageUrl:/api/hrm/resful/getHrmUserInfoWithPage}")
|
||||
@PostMapping("${thirdParty.oa.queryPersonPageUrl:/gwzh/OA/OA_GET_USER_LIST}")
|
||||
OAResponse<OAPersonResponse> queryPersonPage(@RequestBody OASearch<OAPersonSearch> param);
|
||||
}
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.thirdparty.oa.interceptor;
|
||||
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.thirdparty.oa.config.OAProperties;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* OA Feign 请求拦截器。
|
||||
* <p>
|
||||
* 对齐可用 curl:仅发送 Authorization + Content-Type。
|
||||
* 配合 {@code FeignClientConfigurer#inheritParentConfiguration()=false},
|
||||
* 避免全局 BladeFeignRequestInterceptor 透传登录请求头。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class OARequestInterceptor implements RequestInterceptor {
|
||||
|
||||
private static final String BASIC_PREFIX = "Basic ";
|
||||
private static final String BEARER_PREFIX = "Bearer ";
|
||||
private static final Set<String> KEEP_HEADERS = Set.of(
|
||||
HttpHeaders.AUTHORIZATION.toLowerCase(Locale.ROOT),
|
||||
HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ROOT)
|
||||
);
|
||||
|
||||
private final OAProperties oaProperties;
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
stripUnwantedHeaders(template);
|
||||
template.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
String authorization = normalizeAuthorization(oaProperties.getAuthorization());
|
||||
if (StringUtil.isNotBlank(authorization)) {
|
||||
template.header(HttpHeaders.AUTHORIZATION, authorization);
|
||||
} else {
|
||||
log.warn("OA Feign 未配置 third-party.oa.authorization,gwzh 网关可能拒绝请求");
|
||||
}
|
||||
|
||||
logRequest(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印 OA Feign 最终发出的请求头与 body,便于对照 curl。
|
||||
*/
|
||||
private void logRequest(RequestTemplate template) {
|
||||
String bodyText = "";
|
||||
byte[] body = template.body();
|
||||
if (body != null && body.length > 0) {
|
||||
Charset charset = template.requestCharset() == null ? StandardCharsets.UTF_8 : template.requestCharset();
|
||||
bodyText = new String(body, charset);
|
||||
}
|
||||
log.info("OA Feign 请求 method={}, url={}{}{}, headers={}, body={}",
|
||||
template.method(),
|
||||
template.feignTarget() == null ? "" : template.feignTarget().url(),
|
||||
template.path(),
|
||||
template.queryLine() == null ? "" : template.queryLine(),
|
||||
template.headers(),
|
||||
bodyText);
|
||||
}
|
||||
|
||||
/**
|
||||
* 只保留 Authorization / Content-Type,其余全部移除。
|
||||
*/
|
||||
private void stripUnwantedHeaders(RequestTemplate template) {
|
||||
Map<String, Collection<String>> headers = template.headers();
|
||||
List<String> headerNames = new ArrayList<>(headers.keySet());
|
||||
for (String headerName : headerNames) {
|
||||
if (!KEEP_HEADERS.contains(headerName.toLowerCase(Locale.ROOT))) {
|
||||
template.removeHeader(headerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeAuthorization(String authorization) {
|
||||
if (StringUtil.isBlank(authorization)) {
|
||||
return authorization;
|
||||
}
|
||||
if (StringUtil.startsWithIgnoreCase(authorization, BASIC_PREFIX)
|
||||
|| StringUtil.startsWithIgnoreCase(authorization, BEARER_PREFIX)) {
|
||||
return authorization;
|
||||
}
|
||||
return BASIC_PREFIX + authorization;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-third-party-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>blade-wechat-api</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>微信小程序:code2session / 手机号 / openid</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-tool</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package org.springblade.thirdparty.wechat.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 微信小程序第三方能力自动配置
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan("org.springblade.thirdparty.wechat")
|
||||
@EnableConfigurationProperties(WechatMiniProperties.class)
|
||||
public class ThirdPartyWechatAutoConfiguration {
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package org.springblade.thirdparty.wechat.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 微信小程序配置(Nacos:thirdParty.wechat.mini)
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "third-party.wechat.mini")
|
||||
public class WechatMiniProperties {
|
||||
|
||||
/**
|
||||
* 小程序 AppId
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 小程序 AppSecret
|
||||
*/
|
||||
private String appSecret;
|
||||
|
||||
/**
|
||||
* 微信 API 根地址
|
||||
*/
|
||||
private String apiBase = "https://api.weixin.qq.com";
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package org.springblade.thirdparty.wechat.constant;
|
||||
|
||||
/**
|
||||
* 微信小程序常量
|
||||
*/
|
||||
public interface WechatMiniConstant {
|
||||
|
||||
/** blade_user_oauth.source */
|
||||
String SOURCE = "WECHAT_MINI";
|
||||
|
||||
/**
|
||||
* OAuth2 grant_type(对齐 BladeX OAuth2GranterConstant.WECHAT_APPLET)
|
||||
*/
|
||||
String GRANT_TYPE = "wechat_applet";
|
||||
|
||||
String JSCODE2SESSION_PATH = "/sns/jscode2session";
|
||||
String ACCESS_TOKEN_PATH = "/cgi-bin/token";
|
||||
String GET_PHONE_NUMBER_PATH = "/wxa/business/getuserphonenumber";
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.springblade.thirdparty.wechat.exception;
|
||||
|
||||
/**
|
||||
* 微信小程序调用异常
|
||||
*/
|
||||
public class WechatMiniException extends RuntimeException {
|
||||
|
||||
public WechatMiniException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public WechatMiniException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.springblade.thirdparty.wechat.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 微信手机号结果
|
||||
*/
|
||||
@Data
|
||||
public class WechatPhoneVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 不带区号的手机号 */
|
||||
private String phoneNumber;
|
||||
|
||||
/** 带区号手机号 */
|
||||
private String purePhoneNumber;
|
||||
|
||||
private String countryCode;
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.springblade.thirdparty.wechat.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* jscode2session 结果
|
||||
*/
|
||||
@Data
|
||||
public class WechatSessionVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String openid;
|
||||
private String sessionKey;
|
||||
private String unionid;
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.springblade.thirdparty.wechat.service;
|
||||
|
||||
import org.springblade.thirdparty.wechat.pojo.vo.WechatPhoneVO;
|
||||
import org.springblade.thirdparty.wechat.pojo.vo.WechatSessionVO;
|
||||
|
||||
/**
|
||||
* 微信小程序能力:openid / 手机号
|
||||
*/
|
||||
public interface IWechatMiniService {
|
||||
|
||||
/**
|
||||
* wx.login code → openid / session_key
|
||||
*/
|
||||
WechatSessionVO code2Session(String loginCode);
|
||||
|
||||
/**
|
||||
* getPhoneNumber 返回的 code → 手机号
|
||||
*/
|
||||
WechatPhoneVO getPhoneNumber(String phoneCode);
|
||||
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
package org.springblade.thirdparty.wechat.service.impl;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.thirdparty.wechat.config.WechatMiniProperties;
|
||||
import org.springblade.thirdparty.wechat.constant.WechatMiniConstant;
|
||||
import org.springblade.thirdparty.wechat.exception.WechatMiniException;
|
||||
import org.springblade.thirdparty.wechat.pojo.vo.WechatPhoneVO;
|
||||
import org.springblade.thirdparty.wechat.pojo.vo.WechatSessionVO;
|
||||
import org.springblade.thirdparty.wechat.service.IWechatMiniService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 微信小程序:code2session / getuserphonenumber
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatMiniServiceImpl implements IWechatMiniService {
|
||||
|
||||
/** access_token 提前 200 秒刷新 */
|
||||
private static final long TOKEN_REFRESH_AHEAD_MS = 200_000L;
|
||||
|
||||
private final WechatMiniProperties properties;
|
||||
|
||||
private volatile String cachedAccessToken;
|
||||
private volatile long accessTokenExpireAt;
|
||||
|
||||
@Override
|
||||
public WechatSessionVO code2Session(String loginCode) {
|
||||
assertConfigured();
|
||||
if (StringUtil.isBlank(loginCode)) {
|
||||
throw new WechatMiniException("微信登录 code 不能为空");
|
||||
}
|
||||
String url = properties.getApiBase() + WechatMiniConstant.JSCODE2SESSION_PATH
|
||||
+ "?appid=" + properties.getAppId()
|
||||
+ "&secret=" + properties.getAppSecret()
|
||||
+ "&js_code=" + loginCode
|
||||
+ "&grant_type=authorization_code";
|
||||
String body = HttpUtil.get(url, 8000);
|
||||
JSONObject json = parseJson(body, "code2session");
|
||||
assertWxOk(json, "获取 openid 失败");
|
||||
String openid = json.getStr("openid");
|
||||
if (StringUtil.isBlank(openid)) {
|
||||
throw new WechatMiniException("微信未返回 openid");
|
||||
}
|
||||
WechatSessionVO vo = new WechatSessionVO();
|
||||
vo.setOpenid(openid);
|
||||
vo.setSessionKey(json.getStr("session_key"));
|
||||
vo.setUnionid(json.getStr("unionid"));
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WechatPhoneVO getPhoneNumber(String phoneCode) {
|
||||
assertConfigured();
|
||||
if (StringUtil.isBlank(phoneCode)) {
|
||||
throw new WechatMiniException("微信手机号 code 不能为空");
|
||||
}
|
||||
String accessToken = getAccessToken();
|
||||
String url = properties.getApiBase() + WechatMiniConstant.GET_PHONE_NUMBER_PATH
|
||||
+ "?access_token=" + accessToken;
|
||||
Map<String, Object> payload = new HashMap<>(2);
|
||||
payload.put("code", phoneCode);
|
||||
String body = HttpRequest.post(url)
|
||||
.body(JSONUtil.toJsonStr(payload))
|
||||
.timeout(8000)
|
||||
.execute()
|
||||
.body();
|
||||
JSONObject json = parseJson(body, "getuserphonenumber");
|
||||
assertWxOk(json, "获取手机号失败");
|
||||
JSONObject phoneInfo = json.getJSONObject("phone_info");
|
||||
if (phoneInfo == null) {
|
||||
throw new WechatMiniException("微信未返回手机号信息");
|
||||
}
|
||||
WechatPhoneVO vo = new WechatPhoneVO();
|
||||
vo.setPhoneNumber(phoneInfo.getStr("phoneNumber"));
|
||||
vo.setPurePhoneNumber(phoneInfo.getStr("purePhoneNumber"));
|
||||
vo.setCountryCode(phoneInfo.getStr("countryCode"));
|
||||
if (StringUtil.isBlank(vo.getPurePhoneNumber()) && StringUtil.isBlank(vo.getPhoneNumber())) {
|
||||
throw new WechatMiniException("微信未返回有效手机号");
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
private String getAccessToken() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (StringUtil.isNotBlank(cachedAccessToken) && now < accessTokenExpireAt) {
|
||||
return cachedAccessToken;
|
||||
}
|
||||
synchronized (this) {
|
||||
now = System.currentTimeMillis();
|
||||
if (StringUtil.isNotBlank(cachedAccessToken) && now < accessTokenExpireAt) {
|
||||
return cachedAccessToken;
|
||||
}
|
||||
String url = properties.getApiBase() + WechatMiniConstant.ACCESS_TOKEN_PATH
|
||||
+ "?grant_type=client_credential"
|
||||
+ "&appid=" + properties.getAppId()
|
||||
+ "&secret=" + properties.getAppSecret();
|
||||
String body = HttpUtil.get(url, 8000);
|
||||
JSONObject json = parseJson(body, "getAccessToken");
|
||||
assertWxOk(json, "获取 access_token 失败");
|
||||
String token = json.getStr("access_token");
|
||||
Integer expiresIn = json.getInt("expires_in", 7200);
|
||||
if (StringUtil.isBlank(token)) {
|
||||
throw new WechatMiniException("微信未返回 access_token");
|
||||
}
|
||||
cachedAccessToken = token;
|
||||
accessTokenExpireAt = System.currentTimeMillis() + Math.max(60, expiresIn) * 1000L - TOKEN_REFRESH_AHEAD_MS;
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertConfigured() {
|
||||
if (StringUtil.isBlank(properties.getAppId()) || StringUtil.isBlank(properties.getAppSecret())) {
|
||||
throw new WechatMiniException("未配置微信小程序 appId/appSecret(Nacos: thirdParty.wechat.mini)");
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject parseJson(String body, String action) {
|
||||
if (StringUtil.isBlank(body)) {
|
||||
throw new WechatMiniException("微信接口无响应: " + action);
|
||||
}
|
||||
try {
|
||||
return JSONUtil.parseObj(body);
|
||||
} catch (Exception e) {
|
||||
log.error("解析微信响应失败 action={} body={}", action, body, e);
|
||||
throw new WechatMiniException("解析微信响应失败: " + action, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertWxOk(JSONObject json, String fallbackMsg) {
|
||||
Integer errcode = json.getInt("errcode");
|
||||
if (errcode != null && errcode != 0) {
|
||||
String errmsg = json.getStr("errmsg", fallbackMsg);
|
||||
throw new WechatMiniException(fallbackMsg + ":" + errmsg + "(" + errcode + ")");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.springblade.thirdparty.wechat.config.ThirdPartyWechatAutoConfiguration
|
||||
@@ -13,10 +13,12 @@
|
||||
<name>${project.artifactId}</name>
|
||||
<modules>
|
||||
<module>blade-oa-api</module>
|
||||
<module>blade-lbs-api</module>
|
||||
<module>blade-mk-api</module>
|
||||
<module>blade-wps-api</module>
|
||||
<module>blade-ocr-api</module>
|
||||
<module>blade-track-api</module>
|
||||
<module>blade-wechat-api</module>
|
||||
<module>blade-bfi-api</module>
|
||||
</modules>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
Reference in New Issue
Block a user