0.0.3: 增加https ssl白名单,跳过认证;增加查询高度层的接口,

download
shiyi 2 months ago
parent b92795b619
commit 6a5be20d82

@ -134,6 +134,9 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.htfp</groupId>
<artifactId>weather-service</artifactId>
<version>0.0.1</version>
<version>0.0.3</version>
<name>weather-service</name>
<description>weather-service</description>
<packaging>jar</packaging>
@ -146,6 +146,14 @@
<!-- 指定该Main Class为全局的唯一入口 -->
<mainClass>com.htfp.weather.WeatherServiceApplication</mainClass>
<layout>ZIP</layout>
<!--打包排除所有jar包-->
<includes>
<include>
<groupId>nothing</groupId>
<artifactId>nothing</artifactId>
</include>
</includes>
<!--打包排除所有jar包-->
</configuration>
<executions>
<execution>

@ -20,7 +20,6 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import sun.nio.ch.DirectBuffer;
import ucar.ma2.*;
import ucar.nc2.NetcdfFile;
import ucar.nc2.NetcdfFiles;

@ -1,5 +1,8 @@
package com.htfp.weather.utils;
import com.htfp.weather.utils.ssl.HostInterceptor;
import com.htfp.weather.utils.ssl.SelectiveHostnameVerifier;
import com.htfp.weather.utils.ssl.SelectiveTrustManager;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@ -16,9 +19,17 @@ import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.*;
import javax.net.ssl.SSLContext;
import java.security.SecureRandom;
/**
* @Author : shiyi
* @Date : 2024/1/23 16:19
@ -26,13 +37,36 @@ import java.util.concurrent.TimeUnit;
*/
@Slf4j
public class HttpClientUtils {
private static final List<String> TRUSTED_DOMAINS = Arrays.asList("nomads.ncep.noaa.gov");
private static final OkHttpClient OKHTTP_CLIENT = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build();
private static final OkHttpClient DONLOAD_OKHTTP_CLIENT = new OkHttpClient.Builder()
.build();
private static final OkHttpClient DOWNLOAD_OKHTTP_CLIENT = buildOKHttpClient(TRUSTED_DOMAINS).build();
public static OkHttpClient.Builder buildOKHttpClient(List<String> trustedDomains) {
try {
// 1. 创建自定义 TrustManager
SelectiveTrustManager trustManager = new SelectiveTrustManager(trustedDomains);
// 2. 初始化 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslSocketFactory, trustManager);
builder.addInterceptor(new HostInterceptor());
builder.hostnameVerifier(new SelectiveHostnameVerifier(trustedDomains));
return builder;
} catch (NoSuchAlgorithmException | KeyManagementException e) {
e.printStackTrace();
return new OkHttpClient.Builder();
} catch (Exception e) {
e.printStackTrace();
return new OkHttpClient.Builder();
}
}
// public static <T> T sendGet(String url, Map<String, String> params, Class<T> responseClass) {
// MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
// if (params != null) {
@ -55,6 +89,7 @@ public class HttpClientUtils {
String jsonStr = sendGet(url, params);
return JSONUtils.json2obj(jsonStr, responseClass);
}
public static String sendGet(String url, Map<String, String> params) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
if (!CollectionUtils.isEmpty(params)) {
@ -98,7 +133,7 @@ public class HttpClientUtils {
.url(sourceUrl)
.build();
try (Response response = DONLOAD_OKHTTP_CLIENT.newCall(request).execute()) {
try (Response response = DOWNLOAD_OKHTTP_CLIENT.newCall(request).execute()) {
if (response.code() == 200) {
assert response.body() != null;
InputStream stream = response.body().byteStream();

@ -0,0 +1,22 @@
package com.htfp.weather.utils.ssl;
import okhttp3.Interceptor;
import okhttp3.Response;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
public class HostInterceptor implements Interceptor {
private static final ThreadLocal<String> hostnameHolder = new ThreadLocal<>();
@NotNull
@Override
public Response intercept(Chain chain) throws IOException {
hostnameHolder.set(chain.request().url().host());
return chain.proceed(chain.request());
}
public static String getCurrentHostname() {
return hostnameHolder.get();
}
}

@ -0,0 +1,31 @@
package com.htfp.weather.utils.ssl;
import org.springframework.util.CollectionUtils;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import java.util.Arrays;
import java.util.List;
public class SelectiveHostnameVerifier implements HostnameVerifier {
private List<String> trustedDomains;
private final HostnameVerifier defaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
public SelectiveHostnameVerifier(){}
public SelectiveHostnameVerifier(List<String> trustedDomains) {
this.trustedDomains = trustedDomains;
}
@Override
public boolean verify(String hostname, SSLSession session) {
if (!CollectionUtils.isEmpty(trustedDomains) && trustedDomains.contains(hostname)) {
// 白名单域名:跳过主机名验证
return true;
} else {
// 其他域名:使用默认验证
return defaultVerifier.verify(hostname, session);
}
}
}

@ -0,0 +1,79 @@
package com.htfp.weather.utils.ssl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
@Slf4j
public class SelectiveTrustManager implements X509TrustManager {
// 目标域名白名单example.com
private final List<String> trustedDomains;
private final X509TrustManager defaultTrustManager;
private final X509TrustManager fakeTrustManager;
public SelectiveTrustManager(List<String> trustedDomains) throws Exception {
this.trustedDomains = trustedDomains;
// 获取默认 TrustManager
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
// 创建一个“信任所有证书”的 TrustManager仅用于白名单域名
fakeTrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// 客户端验证交给默认逻辑
try {
defaultTrustManager.checkClientTrusted(chain, authType);
} catch (CertificateException e) {
throw new RuntimeException(e);
}
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// 获取当前请求的域名
String hostname = getCurrentHostname(); // 需要额外逻辑获取当前域名
if (!CollectionUtils.isEmpty(trustedDomains) && trustedDomains.contains(hostname)) {
// 白名单域名:跳过证书验证
log.debug("{} is in trusted domains, skipping certificate verification", hostname);
fakeTrustManager.checkServerTrusted(chain, authType);
} else {
// 非白名单域名:使用默认验证
defaultTrustManager.checkServerTrusted(chain, authType);
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return defaultTrustManager.getAcceptedIssuers();
}
// 需要结合 OkHttp 拦截器或反射获取当前请求域名(见后续优化)
private String getCurrentHostname() {
return HostInterceptor.getCurrentHostname();
}
}

@ -17,7 +17,7 @@ import com.htfp.weather.web.param.response.DownloadResult;
import com.htfp.weather.web.param.response.ImportResult;
import com.htfp.weather.web.param.response.Result;
import com.htfp.weather.web.service.FileService;
import com.htfp.weather.web.service.GfsUpperDataServiceImpl;
import com.htfp.weather.web.service.GfsDataServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.util.CollectionUtils;
@ -52,7 +52,7 @@ public class ConfigController {
@Resource
FileService fileService;
@Resource
GfsUpperDataServiceImpl gfsDataService;
GfsDataServiceImpl gfsDataService;
@Resource
SurfaceWeatherController surfaceWeatherController;

@ -8,7 +8,7 @@ import com.htfp.weather.web.param.response.NowWeatherStatus;
import com.htfp.weather.web.param.response.Result;
import com.htfp.weather.web.param.response.SurfaceWeatherWarning;
import com.htfp.weather.web.param.response.TimeSeriesDataset;
import com.htfp.weather.web.service.GfsUpperDataServiceImpl;
import com.htfp.weather.web.service.GfsDataServiceImpl;
import com.htfp.weather.web.service.surfaceapi.CaiYunServiceImpl;
import com.htfp.weather.web.service.surfaceapi.CmaServiceImpl;
import com.htfp.weather.web.service.surfaceapi.HeFengServiceImpl;
@ -38,7 +38,7 @@ public class SurfaceWeatherController {
@Resource
CaiYunServiceImpl caiYunService;
@Resource
GfsUpperDataServiceImpl gfsDataService;
GfsDataServiceImpl gfsDataService;
ISurfaceDataService service;
@PostConstruct
public void init() {

@ -6,7 +6,7 @@ import com.htfp.weather.web.exception.AppException;
import com.htfp.weather.web.exception.ErrorCode;
import com.htfp.weather.web.param.request.*;
import com.htfp.weather.web.param.response.*;
import com.htfp.weather.web.service.GfsUpperDataServiceImpl;
import com.htfp.weather.web.service.GfsDataServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
@ -29,7 +29,7 @@ import java.util.List;
public class UpperWeatherController {
@Resource(name = "tablestore-gfs")
GfsUpperDataServiceImpl gfsDataService;
GfsDataServiceImpl gfsDataService;
@PostMapping("/queryUpperNowWeather")
@ControllerLog(info = "高空实时气象信息查询")
@ -50,7 +50,7 @@ public class UpperWeatherController {
List<Double> longitudeList = new ArrayList<>(size);
checkLatitudeLongitude(latitudeList, longitudeList, pointList);
int level = multiPointsRequest.getLevel();
List<NowWeatherStatus> nowWeatherByMultiPoint = gfsDataService.getNowWeatherByMultiPoint(latitudeList, longitudeList, level);
List<NowWeatherStatus> nowWeatherByMultiPoint = gfsDataService.getNowWeatherByMultiPoint(level, latitudeList, longitudeList);
return Result.success(nowWeatherByMultiPoint);
}
@ -73,7 +73,7 @@ public class UpperWeatherController {
List<Double> longitudeList = new ArrayList<>(size);
checkLatitudeLongitude(latitudeList, longitudeList, pointList);
int level = multiPointsRequest.getLevel();
List<TimeSeriesDataset> nowWeatherByMultiPoint = gfsDataService.getForecastSeriesByMultiPoint(latitudeList, longitudeList, level);
List<TimeSeriesDataset> nowWeatherByMultiPoint = gfsDataService.getForecastSeriesByMultiPoint(level, latitudeList, longitudeList);
return Result.success(nowWeatherByMultiPoint);
}

@ -0,0 +1,18 @@
package com.htfp.weather.web.param.response;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data @NoArgsConstructor @AllArgsConstructor
public class LevelsAvailableResponse {
List<LevelsAvailable> levelsAvailableList;
@Data @NoArgsConstructor @AllArgsConstructor
public static class LevelsAvailable {
String dataSource;
List<Integer> pressureLevels;
List<Integer> heightLevels;
}
}

@ -42,7 +42,7 @@ import java.util.stream.Collectors;
*/
@Service("tablestore-gfs")
@Slf4j
public class GfsUpperDataServiceImpl implements IUpperDataService, ISurfaceDataService {
public class GfsDataServiceImpl implements IUpperDataService, ISurfaceDataService {
@Resource
GfsDataFetcher gfsDataFetcher;
@Resource
@ -163,7 +163,7 @@ public class GfsUpperDataServiceImpl implements IUpperDataService, ISurfaceDataS
* @return
*/
@Override
public List<NowWeatherStatus> getNowWeatherByMultiPoint(List<Double> latitude, List<Double> longitude, int level) {
public List<NowWeatherStatus> getNowWeatherByMultiPoint(int level, List<Double> latitude, List<Double> longitude) {
if (latitude.size() != longitude.size()) {
throw new AppException(ErrorCode.INDEX_SIZE_ERROR, ": 经纬度数组大小不一致");
}
@ -233,7 +233,7 @@ public class GfsUpperDataServiceImpl implements IUpperDataService, ISurfaceDataS
* @return
*/
@Override
public List<TimeSeriesDataset> getForecastSeriesByMultiPoint(List<Double> latitudeList, List<Double> longitudeList, int level) {
public List<TimeSeriesDataset> getForecastSeriesByMultiPoint(int level, List<Double> latitudeList, List<Double> longitudeList) {
if (latitudeList.size() != longitudeList.size()) {
throw new AppException(ErrorCode.INDEX_SIZE_ERROR, ": 经纬度数组大小不一致");
}

@ -16,11 +16,11 @@ import java.util.List;
public interface IUpperDataService {
NowWeatherStatus getNowWeather(int level, double latitude, double longitude);
List<NowWeatherStatus> getNowWeatherByMultiPoint(List<Double> latitude, List<Double> longitude, int level);
List<NowWeatherStatus> getNowWeatherByMultiPoint(int level, List<Double> latitude, List<Double> longitude);
TimeSeriesDataset getForecastSeries(int level, double latitude, double longitude);
List<TimeSeriesDataset> getForecastSeriesByMultiPoint(List<Double> latitudeList, List<Double> longitudeList, int level);
List<TimeSeriesDataset> getForecastSeriesByMultiPoint(int level, List<Double> latitudeList, List<Double> longitudeList);
ProfileDataset getProfileByPressure(OffsetDateTime targetTime, double latitude, double longitude);

@ -29,3 +29,7 @@ mail:
send:
from: shiyi@htsdfp.com
to: shiyi@htsdfp.com
logging:
level:
com.htfp.weather: debug
Loading…
Cancel
Save