|
@@ -0,0 +1,224 @@
|
|
|
+package com.sikey.wa04.business.infrastructure.adapter.weather;
|
|
|
+
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
|
|
+import com.sikey.wa04.business.domain.entity.Weather;
|
|
|
+import com.sikey.wa04.business.infrastructure.dto.model.MapWeatherModel;
|
|
|
+import com.sikey.wa04.business.infrastructure.repository.WeatherRepository;
|
|
|
+import com.sikey.wa04.common.enums.WeatherPhenomenon;
|
|
|
+import jakarta.annotation.Resource;
|
|
|
+import lombok.Setter;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import okhttp3.OkHttpClient;
|
|
|
+import okhttp3.Request;
|
|
|
+import okhttp3.Response;
|
|
|
+import okhttp3.ResponseBody;
|
|
|
+import org.apache.http.client.utils.URIBuilder;
|
|
|
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.net.URISyntaxException;
|
|
|
+import java.time.Instant;
|
|
|
+import java.time.temporal.ChronoUnit;
|
|
|
+import java.util.List;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 百度天气
|
|
|
+ */
|
|
|
+@Setter
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+public class BaiduWeatherService {
|
|
|
+ /**
|
|
|
+ * 百度获取填写的接口地址
|
|
|
+ */
|
|
|
+ private final String weatherUrl = "https://api.map.baidu.com/weather/v1/?";
|
|
|
+
|
|
|
+ // ak API控制台申请得到的ak
|
|
|
+ private final String ak = "suBnrGHXb5K7iPUtipyLPDBzPHlnSMqc";
|
|
|
+
|
|
|
+ private final String output = "json";
|
|
|
+ private final String dataType = "all";
|
|
|
+
|
|
|
+ private volatile OkHttpClient httpClient;
|
|
|
+
|
|
|
+ private OkHttpClient getHttpClient() {
|
|
|
+ if (httpClient == null) {
|
|
|
+ synchronized (this) {
|
|
|
+ if (httpClient == null) {
|
|
|
+ httpClient = new OkHttpClient.Builder() // 使用 Builder 模式创建 OkHttpClient
|
|
|
+ .connectTimeout(60, TimeUnit.SECONDS) // 设置连接超时时间
|
|
|
+ .readTimeout(60, TimeUnit.SECONDS) // 设置读取超时时间
|
|
|
+ .writeTimeout(60, TimeUnit.SECONDS) // 设置写入超时时间
|
|
|
+ .build(); // 构建 OkHttpClient 实例
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return httpClient;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final ObjectMapper objectMapper;
|
|
|
+
|
|
|
+ static {
|
|
|
+ objectMapper = new Jackson2ObjectMapperBuilder().build();
|
|
|
+ objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private WeatherRepository weatherRepository;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 百度天气
|
|
|
+ *
|
|
|
+ * @param cityId 城市id
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public Weather getWeather(String cityId) {
|
|
|
+ //查询当前数据库是否有对应城市Id的天气
|
|
|
+ MapWeatherModel weatherModel = weatherRepository.findByCityId(cityId);
|
|
|
+ Instant curTime = Instant.now();
|
|
|
+ if (weatherModel != null) {
|
|
|
+ //该数据创建时间加4小时后早于当前时间(已超过4小时)
|
|
|
+ if (isOlderThan4Hours(weatherModel.getUpdatedAt())) {
|
|
|
+ log.info("[百度天气]该数据创建时间加4小时后早于当前时间(已超过4小时),需重新获取;cityId:{}", cityId);
|
|
|
+ //获取新天气
|
|
|
+ WeatherData weatherData = requestBaiDuWeather(cityId);
|
|
|
+
|
|
|
+ //组装天气数据存入数据库
|
|
|
+ WeatherDataResultNow now = weatherData.result().now();
|
|
|
+ WeatherDataResultForecasts forecast = weatherData.result().forecasts().getFirst();
|
|
|
+ //更新新天气
|
|
|
+ weatherModel.setTextCode(WeatherPhenomenon.valueOfName(forecast.text_day).getBaiduCode());
|
|
|
+ weatherModel.setNow(now.temp());
|
|
|
+ weatherModel.setDate(forecast.date);
|
|
|
+ weatherModel.setHigh(forecast.high);
|
|
|
+ weatherModel.setLow(forecast.low);
|
|
|
+ weatherModel.setUpdatedAt(curTime);
|
|
|
+ weatherRepository.save(weatherModel);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ //不存在则查询百度天气获取
|
|
|
+ WeatherData weatherData = requestBaiDuWeather(cityId);
|
|
|
+ weatherModel = new MapWeatherModel();
|
|
|
+
|
|
|
+ WeatherDataResultNow now = weatherData.result().now();
|
|
|
+ WeatherDataResultForecasts forecast = weatherData.result().forecasts().getFirst();
|
|
|
+
|
|
|
+ //更新新天气
|
|
|
+ weatherModel.setCityId(cityId);
|
|
|
+ weatherModel.setTextCode(WeatherPhenomenon.valueOfName(forecast.text_day).getBaiduCode());
|
|
|
+ weatherModel.setNow(now.temp());
|
|
|
+ weatherModel.setDate(forecast.date);
|
|
|
+ weatherModel.setHigh(forecast.high);
|
|
|
+ weatherModel.setLow(forecast.low);
|
|
|
+ weatherModel.setCreatedAt(curTime);
|
|
|
+ weatherModel.setUpdatedAt(curTime);
|
|
|
+ //保存新天气
|
|
|
+ weatherRepository.save(weatherModel);
|
|
|
+ }
|
|
|
+ log.info("[百度天气]天气查询结果;cityId:{}; weatherModel:{}", cityId, JSONUtil.toJsonPrettyStr(weatherModel));
|
|
|
+ //获取自定义的天气code
|
|
|
+ String myTextCode = WeatherPhenomenon.valueOfBaiduCode(weatherModel.getTextCode()).getMyCode();
|
|
|
+ //返回天气
|
|
|
+ return new Weather()
|
|
|
+ .setCityId(weatherModel.getCityId())
|
|
|
+ .setTextCode(weatherModel.getTextCode())
|
|
|
+ .setMyTextCode(myTextCode)
|
|
|
+ .setHigh(weatherModel.getHigh())
|
|
|
+ .setLow(weatherModel.getLow())
|
|
|
+ .setNow(weatherModel.getNow());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发起request请求百度天气获取最新天气信息
|
|
|
+ *
|
|
|
+ * @param cityId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public WeatherData requestBaiDuWeather(String cityId) {
|
|
|
+ //组装请求地址和参数
|
|
|
+ String reqUrl = buildWeatherUrl(cityId);
|
|
|
+
|
|
|
+ OkHttpClient client = getHttpClient();
|
|
|
+ Request request = new Request.Builder()
|
|
|
+ .url(reqUrl) // OkHttp直接接受完整URL
|
|
|
+ .build();
|
|
|
+
|
|
|
+ // 使用newCall替换send
|
|
|
+ try (Response response = client.newCall(request).execute()) { // 同步请求
|
|
|
+ if (!response.isSuccessful()) {
|
|
|
+ throw new RuntimeException("[获取百度天气] Request failed with code: " + response.code());
|
|
|
+ }
|
|
|
+
|
|
|
+ ResponseBody body = response.body();
|
|
|
+ if (body == null) {
|
|
|
+ throw new RuntimeException("[获取百度天气] Response body is null");
|
|
|
+ }
|
|
|
+
|
|
|
+ String bodyString = body.string();
|
|
|
+ WeatherData weatherData = objectMapper.readValue(bodyString, WeatherData.class);
|
|
|
+ if (weatherData.status != 0){
|
|
|
+ log.info("[获取百度天气]status 不等于 0 ;message:{}",weatherData.message);
|
|
|
+ }
|
|
|
+ return weatherData;
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new RuntimeException("[获取百度天气]失败eror city: " + cityId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用Instant检查数据是否超过4小时
|
|
|
+ * @param createTime 数据对象
|
|
|
+ * @return true如果创建时间+4小时早于当前时间
|
|
|
+ */
|
|
|
+ public static boolean isOlderThan4Hours(Instant createTime) {
|
|
|
+ // 当前时间
|
|
|
+ Instant now = Instant.now();
|
|
|
+
|
|
|
+ // 创建时间加4小时
|
|
|
+ Instant createPlus4Hours = createTime.plus(4, ChronoUnit.HOURS);
|
|
|
+
|
|
|
+ // 比较
|
|
|
+ return createPlus4Hours.isBefore(now);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装请求地址和参数
|
|
|
+ * @param cityId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public String buildWeatherUrl(String cityId) {
|
|
|
+ try {
|
|
|
+ return new URIBuilder(weatherUrl)
|
|
|
+ .addParameter("district_id", cityId)
|
|
|
+ .addParameter("data_type", dataType)
|
|
|
+ .addParameter("output", output)
|
|
|
+ .addParameter("ak", ak)
|
|
|
+ .build()
|
|
|
+ .toString();
|
|
|
+ } catch (URISyntaxException e) {
|
|
|
+ throw new IllegalArgumentException("[获取百度天气]buildWeatherUrl URL Invalid", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ record WeatherData(Integer status, WeatherDataResult result, String message) {
|
|
|
+ }
|
|
|
+
|
|
|
+ private record WeatherDataResult(WeatherDataResultLocation location, WeatherDataResultNow now, List<WeatherDataResultForecasts> forecasts) {
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private record WeatherDataResultLocation(String country, String province, String city, String name, String id) {
|
|
|
+ }
|
|
|
+
|
|
|
+ private record WeatherDataResultNow(String text, Integer temp, Integer feels_like, Integer rh, String wind_class, String wind_dir, String uptime) {
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private record WeatherDataResultForecasts(String text_day, String text_night, Integer high, Integer low, String wc_day, String wd_day, String wc_night, String wd_night, String date, String week) {
|
|
|
+ }
|
|
|
+
|
|
|
+}
|