From 9c04f81f2ddba9c56c56a3ce0ff331caeb00f55c Mon Sep 17 00:00:00 2001 From: promise_cyf <15731060594@163.com> Date: Fri, 9 Oct 2026 15:58:45 +0800 Subject: [PATCH] =?UTF-8?q?build:=20=E6=B7=BB=E5=8A=A0=20Maven=20=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E9=85=8D=E7=BD=AE=E5=B9=B6=E5=BF=BD=E7=95=A5=20IDE=20?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E3=80=82=20=E9=9B=86=E6=88=90=E8=81=9A?= =?UTF-8?q?=E5=90=88=E6=95=B0=E6=8D=AE=E7=9A=84=20=E6=AF=8F=E6=97=A5?= =?UTF-8?q?=E5=A4=A9=E6=B0=94=E3=80=81=E6=AF=8F=E6=97=A5=E4=B8=80=E5=8F=A5?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .mvn/wrapper/maven-wrapper.properties | 3 + pom.xml | 96 ++++++++++++++ .../cyf/devToolBox/DevToolBoxApplication.java | 15 +++ .../common/GlobalExceptionHandler.java | 44 +++++++ .../com/cyf/devToolBox/common/Result.java | 62 +++++++++ .../devToolBox/controller/JuheController.java | 80 ++++++++++++ .../devToolBox/juhe/AbstractJuheClient.java | 82 ++++++++++++ .../cyf/devToolBox/juhe/JuheClientConfig.java | 26 ++++ .../cyf/devToolBox/juhe/JuheProperties.java | 72 ++++++++++ .../com/cyf/devToolBox/juhe/JuheResponse.java | 23 ++++ .../juhe/exception/JuheApiException.java | 21 +++ .../devToolBox/juhe/soup/JuheSoupClient.java | 44 +++++++ .../juhe/soup/model/JuheSoupResponse.java | 45 +++++++ .../juhe/weather/JuheWeatherClient.java | 69 ++++++++++ .../weather/model/JuheWeatherResponse.java | 123 ++++++++++++++++++ src/main/resources/application.yml | 66 ++++++++++ 17 files changed, 873 insertions(+) create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 pom.xml create mode 100644 src/main/java/com/cyf/devToolBox/DevToolBoxApplication.java create mode 100644 src/main/java/com/cyf/devToolBox/common/GlobalExceptionHandler.java create mode 100644 src/main/java/com/cyf/devToolBox/common/Result.java create mode 100644 src/main/java/com/cyf/devToolBox/controller/JuheController.java create mode 100644 src/main/java/com/cyf/devToolBox/juhe/AbstractJuheClient.java create mode 100644 src/main/java/com/cyf/devToolBox/juhe/JuheClientConfig.java create mode 100644 src/main/java/com/cyf/devToolBox/juhe/JuheProperties.java create mode 100644 src/main/java/com/cyf/devToolBox/juhe/JuheResponse.java create mode 100644 src/main/java/com/cyf/devToolBox/juhe/exception/JuheApiException.java create mode 100644 src/main/java/com/cyf/devToolBox/juhe/soup/JuheSoupClient.java create mode 100644 src/main/java/com/cyf/devToolBox/juhe/soup/model/JuheSoupResponse.java create mode 100644 src/main/java/com/cyf/devToolBox/juhe/weather/JuheWeatherClient.java create mode 100644 src/main/java/com/cyf/devToolBox/juhe/weather/model/JuheWeatherResponse.java create mode 100644 src/main/resources/application.yml diff --git a/.gitignore b/.gitignore index a7e4258..44db056 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ buildNumber.properties # JDT-specific (Eclipse Java Development Tools) .classpath +/.codebuddy +/.idea \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..216df05 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c74f9fb --- /dev/null +++ b/pom.xml @@ -0,0 +1,96 @@ + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.3.5 + + + + com.cyf + devToolBox + 1.0-SNAPSHOT + jar + + devToolBox + 在线开发工具箱 + + + 21 + UTF-8 + 3.5.9 + 2.2.22 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + + + com.mysql + mysql-connector-j + runtime + + + + + com.h2database + h2 + runtime + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter-validation + + + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-v3.version} + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/src/main/java/com/cyf/devToolBox/DevToolBoxApplication.java b/src/main/java/com/cyf/devToolBox/DevToolBoxApplication.java new file mode 100644 index 0000000..f6b5e00 --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/DevToolBoxApplication.java @@ -0,0 +1,15 @@ +package com.cyf.devToolBox; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@MapperScan("com.cyf.devToolBox.mapper") +public class DevToolBoxApplication { + + public static void main(String[] args) { + SpringApplication.run(DevToolBoxApplication.class, args); + } + +} diff --git a/src/main/java/com/cyf/devToolBox/common/GlobalExceptionHandler.java b/src/main/java/com/cyf/devToolBox/common/GlobalExceptionHandler.java new file mode 100644 index 0000000..7463f4e --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/common/GlobalExceptionHandler.java @@ -0,0 +1,44 @@ +package com.cyf.devToolBox.common; + +import com.cyf.devToolBox.juhe.exception.JuheApiException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理,保证异常时也返回统一的 {@link Result} 结构。 + * + * @author cyf + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** 聚合数据接口业务错误(error_code != 0) */ + @ExceptionHandler(JuheApiException.class) + public Result handleJuheApiException(JuheApiException e) { + log.warn("聚合数据接口调用失败:{}", e.getMessage()); + return Result.error(e.getErrorCode(), e.getMessage()); + } + + /** 参数错误 */ + @ExceptionHandler(IllegalArgumentException.class) + public Result handleIllegalArgumentException(IllegalArgumentException e) { + log.warn("参数校验失败:{}", e.getMessage()); + return Result.error(400, e.getMessage()); + } + + /** 状态错误(如未配置 Key) */ + @ExceptionHandler(IllegalStateException.class) + public Result handleIllegalStateException(IllegalStateException e) { + log.warn("状态校验失败:{}", e.getMessage()); + return Result.error(500, e.getMessage()); + } + + /** 兜底 */ + @ExceptionHandler(Exception.class) + public Result handleException(Exception e) { + log.error("系统异常", e); + return Result.error(500, "系统异常:" + e.getMessage()); + } +} diff --git a/src/main/java/com/cyf/devToolBox/common/Result.java b/src/main/java/com/cyf/devToolBox/common/Result.java new file mode 100644 index 0000000..e9f166e --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/common/Result.java @@ -0,0 +1,62 @@ +package com.cyf.devToolBox.common; + +import lombok.Data; + +/** + * 统一 API 返回结构,所有对外接口都返回该格式。 + * + *
+ * {
+ *   "code": 0,
+ *   "message": "success",
+ *   "data": { ... }
+ * }
+ * 
+ * + * @param 业务数据类型 + * @author cyf + */ +@Data +public class Result { + + /** 业务状态码,0 表示成功,非 0 表示失败 */ + private int code; + + /** 提示信息 */ + private String message; + + /** 业务数据 */ + private T data; + + /** + * 成功(带数据)。 + */ + public static Result success(T data) { + return build(0, "success", data); + } + + /** + * 成功(无数据)。 + */ + public static Result success() { + return build(0, "success", null); + } + + /** + * 失败。 + * + * @param code 业务状态码 + * @param message 错误信息 + */ + public static Result error(int code, String message) { + return build(code, message, null); + } + + private static Result build(int code, String message, T data) { + Result result = new Result<>(); + result.setCode(code); + result.setMessage(message); + result.setData(data); + return result; + } +} diff --git a/src/main/java/com/cyf/devToolBox/controller/JuheController.java b/src/main/java/com/cyf/devToolBox/controller/JuheController.java new file mode 100644 index 0000000..cd9cf60 --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/controller/JuheController.java @@ -0,0 +1,80 @@ +package com.cyf.devToolBox.controller; + +import com.cyf.devToolBox.juhe.soup.JuheSoupClient; +import com.cyf.devToolBox.juhe.soup.model.JuheSoupResponse; +import com.cyf.devToolBox.juhe.weather.JuheWeatherClient; +import com.cyf.devToolBox.juhe.weather.model.JuheWeatherResponse; +import com.cyf.devToolBox.common.Result; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 聚合数据接口,统一返回 {@link Result} 结构。 + * + *
+ * GET /api/juhe/weather?city=丰台              查询实时天气(未来天数取配置默认值)
+ * GET /api/juhe/weather?city=北京&futureNum=3  指定未来天气天数
+ * GET /api/juhe/weather/raw?city=北京          返回原始 JSON,便于排查
+ * GET /api/juhe/soup                           查询每日一句
+ * GET /api/juhe/soup/raw                       返回原始 JSON,便于排查
+ * 
+ * + * @author cyf + */ +@RestController +@RequestMapping("/api/juhe") +public class JuheController { + + private final JuheWeatherClient juheWeatherClient; + private final JuheSoupClient juheSoupClient; + + public JuheController(JuheWeatherClient juheWeatherClient, JuheSoupClient juheSoupClient) { + this.juheWeatherClient = juheWeatherClient; + this.juheSoupClient = juheSoupClient; + } + + /** + * 查询指定城市的天气。 + * + * @param city 城市名称(中文),如:北京、丰台 + * @param futureNum 未来天气天数(1~7),不传则使用配置中的默认值 + * @return 统一返回结构,data 为天气数据 + */ + @GetMapping("/weather") + public Result weather(@RequestParam String city, + @RequestParam(required = false) Integer futureNum) { + JuheWeatherResponse.Result data = futureNum == null + ? juheWeatherClient.query(city) + : juheWeatherClient.query(city, futureNum); + return Result.success(data); + } + + /** + * 天气接口原始 JSON,便于排查问题。 + */ + @GetMapping("/weather/raw") + public Result weatherRaw(@RequestParam String city, + @RequestParam(required = false) Integer futureNum) { + return Result.success(juheWeatherClient.queryRaw(city, futureNum)); + } + + /** + * 查询每日一句。 + * + * @return 统一返回结构,data 为每日一句数据 + */ + @GetMapping("/soup") + public Result soup() { + return Result.success(juheSoupClient.query()); + } + + /** + * 每日一句接口原始 JSON,便于排查问题。 + */ + @GetMapping("/soup/raw") + public Result soupRaw() { + return Result.success(juheSoupClient.queryRaw()); + } +} diff --git a/src/main/java/com/cyf/devToolBox/juhe/AbstractJuheClient.java b/src/main/java/com/cyf/devToolBox/juhe/AbstractJuheClient.java new file mode 100644 index 0000000..d0af5a0 --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/juhe/AbstractJuheClient.java @@ -0,0 +1,82 @@ +package com.cyf.devToolBox.juhe; + +import com.cyf.devToolBox.juhe.exception.JuheApiException; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClient; +import org.springframework.web.util.UriBuilder; + +import java.net.URI; +import java.util.function.Function; + +/** + * 聚合数据客户端抽象基类,抽离各接口的公共能力: + *
    + *
  • 持有配置与 {@link RestClient}
  • + *
  • 统一执行请求并校验 {@code error_code}
  • + *
  • 统一校验接口 key 是否已配置
  • + *
+ * 子类只需关心「拼装参数」与「提取 result」。 + * + * @author cyf + */ +public abstract class AbstractJuheClient { + + protected final JuheProperties properties; + + protected final RestClient restClient; + + protected AbstractJuheClient(JuheProperties properties, ClientHttpRequestFactory requestFactory, String baseUrl) { + this.properties = properties; + this.restClient = RestClient.builder() + .baseUrl(baseUrl) + .requestFactory(requestFactory) + .build(); + } + + /** + * 统一执行 GET 请求、校验 error_code,成功后返回响应对象。 + * + * @param uri 请求地址构造器 + * @param responseType 响应类型 + * @throws JuheApiException 接口返回内容为空或 error_code != 0 + */ + protected T execute(Function uri, Class responseType) { + T response = restClient.get() + .uri(uri) + .retrieve() + .body(responseType); + + if (response == null) { + throw new JuheApiException(-1, "接口返回内容为空"); + } + if (!response.isSuccess()) { + throw new JuheApiException( + response.getErrorCode() == null ? -1 : response.getErrorCode(), + response.getReason()); + } + return response; + } + + /** + * 统一执行 GET 请求并返回原始 JSON,便于排查问题。 + */ + protected String executeRaw(Function uri) { + return restClient.get() + .uri(uri) + .retrieve() + .body(String.class); + } + + /** + * 校验接口 key 是否已配置。 + * + * @param key 实际的 key 值 + * @param configKey 配置项名称,用于错误提示 + */ + protected void checkKeyConfigured(String key, String configKey) { + if (!StringUtils.hasText(key)) { + throw new IllegalStateException("未配置聚合数据 API Key(" + configKey + ")"); + } + } +} diff --git a/src/main/java/com/cyf/devToolBox/juhe/JuheClientConfig.java b/src/main/java/com/cyf/devToolBox/juhe/JuheClientConfig.java new file mode 100644 index 0000000..846fad9 --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/juhe/JuheClientConfig.java @@ -0,0 +1,26 @@ +package com.cyf.devToolBox.juhe; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; + +/** + * 聚合数据客户端公共配置。 + * + * @author cyf + */ +@Configuration +public class JuheClientConfig { + + /** + * 所有聚合数据客户端共用的请求工厂(统一连接/读取超时)。 + */ + @Bean + public ClientHttpRequestFactory juheRequestFactory(JuheProperties properties) { + SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + requestFactory.setConnectTimeout((int) properties.getConnectTimeout().toMillis()); + requestFactory.setReadTimeout((int) properties.getReadTimeout().toMillis()); + return requestFactory; + } +} diff --git a/src/main/java/com/cyf/devToolBox/juhe/JuheProperties.java b/src/main/java/com/cyf/devToolBox/juhe/JuheProperties.java new file mode 100644 index 0000000..8da4e36 --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/juhe/JuheProperties.java @@ -0,0 +1,72 @@ +package com.cyf.devToolBox.juhe; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +/** + * 聚合数据(Juhe)相关配置,对应 application.yml 中的 juhe.* 配置项。 + * + *
+ * juhe:
+ *   connect-timeout: 3s
+ *   read-timeout: 5s
+ *   weather:
+ *     base-url: https://apis.juhe.cn/simpleWeather/query
+ *     key: 天气接口的 key
+ *     future-num: 5
+ *   soup:
+ *     base-url: https://apis.juhe.cn/fapig/soup/query
+ *     key: 每日一句接口的 key
+ * 
+ * + * @author cyf + */ +@Data +@Component +@ConfigurationProperties(prefix = "juhe") +public class JuheProperties { + + /** 连接超时时间 */ + private Duration connectTimeout = Duration.ofSeconds(3); + + /** 读取超时时间 */ + private Duration readTimeout = Duration.ofSeconds(5); + + /** 天气查询配置 */ + private Weather weather = new Weather(); + + /** 每日一句配置 */ + private Soup soup = new Soup(); + + /** + * 天气接口配置。 + */ + @Data + public static class Weather { + + /** 接口地址 */ + private String baseUrl = "https://apis.juhe.cn/simpleWeather/query"; + + /** 天气接口的 API Key */ + private String key; + + /** 默认查询的未来天气天数(1~7),为空表示只查询实时天气 */ + private Integer futureNum; + } + + /** + * 每日一句接口配置。 + */ + @Data + public static class Soup { + + /** 接口地址 */ + private String baseUrl = "https://apis.juhe.cn/fapig/soup/query"; + + /** 每日一句接口的 API Key */ + private String key; + } +} diff --git a/src/main/java/com/cyf/devToolBox/juhe/JuheResponse.java b/src/main/java/com/cyf/devToolBox/juhe/JuheResponse.java new file mode 100644 index 0000000..2ca8bf0 --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/juhe/JuheResponse.java @@ -0,0 +1,23 @@ +package com.cyf.devToolBox.juhe; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * 聚合数据接口响应的统一抽象:所有响应都包含 {@code reason} 和 {@code error_code}。 + * + * @author cyf + */ +public interface JuheResponse { + + /** 返回说明,如:success、查询成功 */ + String getReason(); + + /** 返回码,0 表示成功 */ + Integer getErrorCode(); + + /** 是否请求成功(error_code == 0) */ + @JsonIgnore + default boolean isSuccess() { + return getErrorCode() != null && getErrorCode() == 0; + } +} diff --git a/src/main/java/com/cyf/devToolBox/juhe/exception/JuheApiException.java b/src/main/java/com/cyf/devToolBox/juhe/exception/JuheApiException.java new file mode 100644 index 0000000..cdbc9d9 --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/juhe/exception/JuheApiException.java @@ -0,0 +1,21 @@ +package com.cyf.devToolBox.juhe.exception; + +/** + * 聚合数据接口调用异常(业务层错误,error_code != 0)。 + * + * @author cyf + */ +public class JuheApiException extends RuntimeException { + + /** 聚合数据返回的 error_code */ + private final int errorCode; + + public JuheApiException(int errorCode, String reason) { + super("聚合数据接口调用失败 [error_code=" + errorCode + "]: " + reason); + this.errorCode = errorCode; + } + + public int getErrorCode() { + return errorCode; + } +} diff --git a/src/main/java/com/cyf/devToolBox/juhe/soup/JuheSoupClient.java b/src/main/java/com/cyf/devToolBox/juhe/soup/JuheSoupClient.java new file mode 100644 index 0000000..d3664dc --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/juhe/soup/JuheSoupClient.java @@ -0,0 +1,44 @@ +package com.cyf.devToolBox.juhe.soup; + +import com.cyf.devToolBox.juhe.AbstractJuheClient; +import com.cyf.devToolBox.juhe.JuheProperties; +import com.cyf.devToolBox.juhe.soup.model.JuheSoupResponse; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriBuilder; + +import java.net.URI; +import java.util.function.Function; + +/** + * 聚合数据「每日一句」客户端。 + * + * @author cyf + */ +@Component +public class JuheSoupClient extends AbstractJuheClient { + + public JuheSoupClient(JuheProperties properties, ClientHttpRequestFactory juheRequestFactory) { + super(properties, juheRequestFactory, properties.getSoup().getBaseUrl()); + } + + /** + * 查询每日一句。 + */ + public JuheSoupResponse.Result query() { + checkKeyConfigured(properties.getSoup().getKey(), "juhe.soup.key"); + return execute(uri(), JuheSoupResponse.class).getResult(); + } + + /** + * 每日一句接口的原始 JSON,方便排查问题。 + */ + public String queryRaw() { + checkKeyConfigured(properties.getSoup().getKey(), "juhe.soup.key"); + return executeRaw(uri()); + } + + private Function uri() { + return uriBuilder -> uriBuilder.queryParam("key", properties.getSoup().getKey()).build(); + } +} diff --git a/src/main/java/com/cyf/devToolBox/juhe/soup/model/JuheSoupResponse.java b/src/main/java/com/cyf/devToolBox/juhe/soup/model/JuheSoupResponse.java new file mode 100644 index 0000000..56fc0c7 --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/juhe/soup/model/JuheSoupResponse.java @@ -0,0 +1,45 @@ +package com.cyf.devToolBox.juhe.soup.model; + +import com.cyf.devToolBox.juhe.JuheResponse; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 聚合数据「每日一句」接口响应模型。 + * + *
+ * {
+ *   "reason": "success",
+ *   "result": { "text": "无论有多困难,都坚强地抬头挺胸..." },
+ *   "error_code": 0
+ * }
+ * 
+ * + * @author cyf + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class JuheSoupResponse implements JuheResponse { + + /** 返回说明,如:success */ + private String reason; + + /** 返回结果集 */ + private Result result; + + /** 返回码,0 表示成功 */ + @JsonProperty("error_code") + private Integer errorCode; + + /** + * 每日一句数据主体。 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Result { + + /** 每日一句内容 */ + private String text; + } +} diff --git a/src/main/java/com/cyf/devToolBox/juhe/weather/JuheWeatherClient.java b/src/main/java/com/cyf/devToolBox/juhe/weather/JuheWeatherClient.java new file mode 100644 index 0000000..1ce796d --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/juhe/weather/JuheWeatherClient.java @@ -0,0 +1,69 @@ +package com.cyf.devToolBox.juhe.weather; + +import com.cyf.devToolBox.juhe.AbstractJuheClient; +import com.cyf.devToolBox.juhe.JuheProperties; +import com.cyf.devToolBox.juhe.weather.model.JuheWeatherResponse; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriBuilder; + +import java.net.URI; +import java.util.function.Function; + +/** + * 聚合数据「天气查询」客户端。 + * + * @author cyf + */ +@Component +public class JuheWeatherClient extends AbstractJuheClient { + + public JuheWeatherClient(JuheProperties properties, ClientHttpRequestFactory juheRequestFactory) { + super(properties, juheRequestFactory, properties.getWeather().getBaseUrl()); + } + + /** + * 使用配置中的默认 {@code future-num} 查询指定城市的天气。 + */ + public JuheWeatherResponse.Result query(String city) { + return query(city, properties.getWeather().getFutureNum()); + } + + /** + * 查询指定城市的天气。 + * + * @param city 城市名称(中文),如:北京、丰台 + * @param futureNum 需要查询的未来天气天数(1~7),为 null 或不大于 0 时不查询 + */ + public JuheWeatherResponse.Result query(String city, Integer futureNum) { + validate(city); + return execute(uri(city, futureNum), JuheWeatherResponse.class).getResult(); + } + + /** + * 天气接口的原始 JSON,方便排查问题。 + */ + public String queryRaw(String city, Integer futureNum) { + validate(city); + return executeRaw(uri(city, futureNum)); + } + + private void validate(String city) { + if (!StringUtils.hasText(city)) { + throw new IllegalArgumentException("城市名称不能为空"); + } + checkKeyConfigured(properties.getWeather().getKey(), "juhe.weather.key"); + } + + private Function uri(String city, Integer futureNum) { + return uriBuilder -> { + uriBuilder.queryParam("key", properties.getWeather().getKey()); + uriBuilder.queryParam("city", city); + if (futureNum != null && futureNum > 0) { + uriBuilder.queryParam("future_num", futureNum); + } + return uriBuilder.build(); + }; + } +} diff --git a/src/main/java/com/cyf/devToolBox/juhe/weather/model/JuheWeatherResponse.java b/src/main/java/com/cyf/devToolBox/juhe/weather/model/JuheWeatherResponse.java new file mode 100644 index 0000000..bbaee24 --- /dev/null +++ b/src/main/java/com/cyf/devToolBox/juhe/weather/model/JuheWeatherResponse.java @@ -0,0 +1,123 @@ +package com.cyf.devToolBox.juhe.weather.model; + +import com.cyf.devToolBox.juhe.JuheResponse; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 聚合数据天气接口的原始响应模型。 + * + *
+ * {
+ *   "reason": "查询成功",
+ *   "result": {
+ *     "city": "苏州",
+ *     "realtime": { ... },
+ *     "future": [ ... ]
+ *   },
+ *   "error_code": 0
+ * }
+ * 
+ * + * @author cyf + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class JuheWeatherResponse implements JuheResponse { + + /** 返回说明,如:查询成功 */ + private String reason; + + /** 返回结果集 */ + private Result result; + + /** 返回码,0 表示成功 */ + @JsonProperty("error_code") + private Integer errorCode; + + /** + * 天气数据主体。 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Result { + + /** 城市名 */ + private String city; + + /** 实时天气 */ + private Realtime realtime; + + /** 未来天气,未查询 future_num 时可能为空 */ + private List future; + } + + /** + * 实时天气。 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Realtime { + + /** 温度(℃) */ + private String temperature; + + /** 湿度(%) */ + private String humidity; + + /** 天气现象,如:阴 */ + private String info; + + /** 天气标识,注意此处是字符串,如:"02" */ + private String wid; + + /** 风向,如:西北风 */ + private String direct; + + /** 风力,如:3级 */ + private String power; + + /** 空气质量指数 */ + private String aqi; + } + + /** + * 未来某天的天气预报。 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Future { + + /** 日期,如:2019-02-22 */ + private String date; + + /** 温度区间,如:1/7℃ */ + private String temperature; + + /** 天气,如:小雨转多云 */ + private String weather; + + /** 天气标识,注意此处是对象(day / night) */ + private Wid wid; + + /** 风向,如:北风转西北风 */ + private String direct; + } + + /** + * 未来天气的 wid 结构,仅 future 中是对象,与 realtime.wid 的字符串类型不同。 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Wid { + + /** 白天天气标识 */ + private String day; + + /** 夜间天气标识 */ + private String night; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..117c19b --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,66 @@ +server: + port: 8077 + +spring: + application: + name: dev-toolbox + + # 默认使用 H2 内存库,本地无需任何外部依赖即可启动 + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:dev_toolbox;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=TRUE + username: sa + password: "" + + h2: + console: + enabled: true + path: /h2-console + + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + +mybatis-plus: + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + banner: false + db-config: + id-type: auto + +# 聚合数据(每个接口使用各自的 key) +# 天气文档:https://www.juhe.cn/docs/api/id/73 +# 每日一句文档:https://www.juhe.cn/docs/api/id/86 +juhe: + connect-timeout: 3s + read-timeout: 5s + # 天气查询 + weather: + base-url: https://apis.juhe.cn/simpleWeather/query + # 建议改用环境变量 JUHE_WEATHER_KEY 覆盖,避免明文提交到仓库 + key: ${JUHE_WEATHER_KEY:ca38eda68ea7242a3601e4e98afa8499} + # 查询未来天气天数(1~7),不填写则只返回实时天气 + future-num: 5 + # 每日一句 + soup: + base-url: https://apis.juhe.cn/fapig/soup/query + # 每日一句接口的 key,请填入并建议用环境变量 JUHE_SOUP_KEY 覆盖 + key: ${JUHE_SOUP_KEY:cf8695f97027c22b346a468697ba11db} + +--- +# 切换为 MySQL 时使用该 profile:mvn spring-boot:run -Dspring-boot.run.profiles=mysql +spring: + config: + activate: + on-profile: mysql + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/dev_toolbox?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true + username: root + password: root + +logging: + level: + com.cyf.devToolBox: debug