build: 添加 Maven 项目配置并忽略 IDE 目录。
集成聚合数据的 每日天气、每日一句功能
This commit is contained in:
@@ -43,3 +43,5 @@ buildNumber.properties
|
|||||||
# JDT-specific (Eclipse Java Development Tools)
|
# JDT-specific (Eclipse Java Development Tools)
|
||||||
.classpath
|
.classpath
|
||||||
|
|
||||||
|
/.codebuddy
|
||||||
|
/.idea
|
||||||
+3
@@ -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
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<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.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.3.5</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.cyf</groupId>
|
||||||
|
<artifactId>devToolBox</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>devToolBox</name>
|
||||||
|
<description>在线开发工具箱</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<mybatis-plus.version>3.5.9</mybatis-plus.version>
|
||||||
|
<swagger-annotations-v3.version>2.2.22</swagger-annotations-v3.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis-Plus -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MySQL Connector -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 本地开发用内存数据库,无需依赖外部 MySQL 即可启动 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Validation -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.swagger.core.v3</groupId>
|
||||||
|
<artifactId>swagger-annotations</artifactId>
|
||||||
|
<version>${swagger-annotations-v3.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Void> handleJuheApiException(JuheApiException e) {
|
||||||
|
log.warn("聚合数据接口调用失败:{}", e.getMessage());
|
||||||
|
return Result.error(e.getErrorCode(), e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 参数错误 */
|
||||||
|
@ExceptionHandler(IllegalArgumentException.class)
|
||||||
|
public Result<Void> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||||
|
log.warn("参数校验失败:{}", e.getMessage());
|
||||||
|
return Result.error(400, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 状态错误(如未配置 Key) */
|
||||||
|
@ExceptionHandler(IllegalStateException.class)
|
||||||
|
public Result<Void> handleIllegalStateException(IllegalStateException e) {
|
||||||
|
log.warn("状态校验失败:{}", e.getMessage());
|
||||||
|
return Result.error(500, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 兜底 */
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public Result<Void> handleException(Exception e) {
|
||||||
|
log.error("系统异常", e);
|
||||||
|
return Result.error(500, "系统异常:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.cyf.devToolBox.common;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一 API 返回结构,所有对外接口都返回该格式。
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "code": 0,
|
||||||
|
* "message": "success",
|
||||||
|
* "data": { ... }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param <T> 业务数据类型
|
||||||
|
* @author cyf
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class Result<T> {
|
||||||
|
|
||||||
|
/** 业务状态码,0 表示成功,非 0 表示失败 */
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
/** 提示信息 */
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/** 业务数据 */
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功(带数据)。
|
||||||
|
*/
|
||||||
|
public static <T> Result<T> success(T data) {
|
||||||
|
return build(0, "success", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功(无数据)。
|
||||||
|
*/
|
||||||
|
public static <T> Result<T> success() {
|
||||||
|
return build(0, "success", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败。
|
||||||
|
*
|
||||||
|
* @param code 业务状态码
|
||||||
|
* @param message 错误信息
|
||||||
|
*/
|
||||||
|
public static <T> Result<T> error(int code, String message) {
|
||||||
|
return build(code, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> Result<T> build(int code, String message, T data) {
|
||||||
|
Result<T> result = new Result<>();
|
||||||
|
result.setCode(code);
|
||||||
|
result.setMessage(message);
|
||||||
|
result.setData(data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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} 结构。
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 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,便于排查
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @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<JuheWeatherResponse.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<String> weatherRaw(@RequestParam String city,
|
||||||
|
@RequestParam(required = false) Integer futureNum) {
|
||||||
|
return Result.success(juheWeatherClient.queryRaw(city, futureNum));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询每日一句。
|
||||||
|
*
|
||||||
|
* @return 统一返回结构,data 为每日一句数据
|
||||||
|
*/
|
||||||
|
@GetMapping("/soup")
|
||||||
|
public Result<JuheSoupResponse.Result> soup() {
|
||||||
|
return Result.success(juheSoupClient.query());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每日一句接口原始 JSON,便于排查问题。
|
||||||
|
*/
|
||||||
|
@GetMapping("/soup/raw")
|
||||||
|
public Result<String> soupRaw() {
|
||||||
|
return Result.success(juheSoupClient.queryRaw());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聚合数据客户端抽象基类,抽离各接口的公共能力:
|
||||||
|
* <ul>
|
||||||
|
* <li>持有配置与 {@link RestClient}</li>
|
||||||
|
* <li>统一执行请求并校验 {@code error_code}</li>
|
||||||
|
* <li>统一校验接口 key 是否已配置</li>
|
||||||
|
* </ul>
|
||||||
|
* 子类只需关心「拼装参数」与「提取 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 extends JuheResponse> T execute(Function<UriBuilder, URI> uri, Class<T> 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<UriBuilder, URI> 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 + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.* 配置项。
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 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
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<UriBuilder, URI> uri() {
|
||||||
|
return uriBuilder -> uriBuilder.queryParam("key", properties.getSoup().getKey()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聚合数据「每日一句」接口响应模型。
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "reason": "success",
|
||||||
|
* "result": { "text": "无论有多困难,都坚强地抬头挺胸..." },
|
||||||
|
* "error_code": 0
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<UriBuilder, URI> 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();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聚合数据天气接口的原始响应模型。
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "reason": "查询成功",
|
||||||
|
* "result": {
|
||||||
|
* "city": "苏州",
|
||||||
|
* "realtime": { ... },
|
||||||
|
* "future": [ ... ]
|
||||||
|
* },
|
||||||
|
* "error_code": 0
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @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> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user