解决冲突

This commit is contained in:
wulin 2023-09-21 19:08:46 +08:00
commit c0d5b22535
41 changed files with 1384 additions and 35 deletions

View File

@ -1,18 +0,0 @@
package com.qiuguo.iot.base.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 强制登陆
*
* @author weiyachao
* @since 2023/9/20 15:55
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
}

View File

@ -0,0 +1,44 @@
package com.qiuguo.iot.data.request.third;
import lombok.Data;
import javax.annotation.Nullable;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* @author simon
* @date 2023/9/21
* @description 天气查询参数
**/
@Data
public class ThirdWeatherInfoRequest {
/*
* 1: 按小时查询 2:按天查询
*/
@NotNull
private Integer type;
/*
* 查询条数
*/
@NotNull
private Integer size;
/*
* 客户端ip
*/
private String ip;
/*
* 纬度
*/
private String lat;
/*
* 经度
*/
private String lng;
}

View File

@ -0,0 +1,26 @@
package com.qiuguo.iot.data.resp.third;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
* @author simon
* @date 2023/9/21
* @description
**/
@Data
public class ThirdIpInfoResp {
private Long id;
private String ip;
private Float lat;
private Float lng;
private String nation;
private String province;
private String city;
private String district;
private Integer adcode;
}

View File

@ -0,0 +1,22 @@
package com.qiuguo.iot.data.resp.third;
import lombok.Data;
import org.springframework.core.ParameterizedTypeReference;
/**
* @author simon
* @date 2023/9/21
* @description
**/
@Data
public class ThirdRpcResp <T>{
private Integer code;
private String msg;
private Boolean success;
private Boolean error;
private T data;
public static ParameterizedTypeReference<ThirdRpcResp<ThirdIpInfoResp>> getResponseIpType() {
return new ParameterizedTypeReference<ThirdRpcResp<ThirdIpInfoResp>>() {};
}
}

View File

@ -0,0 +1,11 @@
package com.qiuguo.iot.data.resp.third.weather;
import lombok.Data;
import java.util.List;
@Data
public class AirQuality {
private List<WeatherTimeValue> aqi;
private List<WeatherTimeInt> pm25;
}

View File

@ -0,0 +1,30 @@
package com.qiuguo.iot.data.resp.third.weather;
import lombok.Data;
import java.util.List;
/**
* @author simon
* @date 2023/9/21
* @description
**/
@Data
public class Daily {
private String status;
private List<Temperature> precipitation_08h_20h;
private List<Temperature> precipitation_20h_32h;
private List<Temperature> precipitation;
private List<Temperature> temperature;
private List<Temperature> temperature_08h_20h;
private List<Temperature> temperature_20h_32h;
private List<Wind> wind;
private List<Wind> wind_08h_20h;
private List<Wind> wind_20h_32h;
private List<Temperature> humidity;
private List<Temperature> cloudrate;
private List<Temperature> pressure;
private List<Temperature> visibility;
private List<Temperature> dswrf;
private LifeIndex life_index;
}

View File

@ -0,0 +1,22 @@
package com.qiuguo.iot.data.resp.third.weather;
import lombok.Data;
import java.util.List;
@Data
public class Hourly {
private String status;
private String description;
private List<Precipitation> precipitation;
private List<WeatherTimeInt> temperature;
private List<WeatherTimeDouble> apparent_temperature;
private List<Wind> wind;
private List<WeatherTimeDouble> humidity;
private List<WeatherTimeInt> cloudrate;
private List<WeatherTimeString> skycon;
private List<WeatherTimeDouble> pressure;
private List<WeatherTimeDouble> visibility;
private List<WeatherTimeDouble> dswrf;
private AirQuality air_quality;
}

View File

@ -0,0 +1,20 @@
package com.qiuguo.iot.data.resp.third.weather;
import lombok.Data;
import java.util.List;
/**
* @author simon
* @date 2023/9/21
* @description
**/
@Data
public class LifeIndex {
private List<LifeIndexDesc> ultraviolet;
private List<LifeIndexDesc> carWashing;
private List<LifeIndexDesc> dressing;
private List<LifeIndexDesc> comfort;
private List<LifeIndexDesc> coldRisk;
}

View File

@ -0,0 +1,19 @@
package com.qiuguo.iot.data.resp.third.weather;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* @author simon
* @date 2023/9/21
* @description
**/
@Data
public class LifeIndexDesc {
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm", timezone = "GMT+8")
private Date date;
private String index;
private String desc;
}

View File

@ -0,0 +1,18 @@
package com.qiuguo.iot.data.resp.third.weather;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
public class Precipitation {
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm", timezone = "GMT+8")
private Date datetime;
private Integer value;
private Integer probability;
private Integer max;
private Integer min;
private Double avg;
}

View File

@ -0,0 +1,11 @@
package com.qiuguo.iot.data.resp.third.weather;
import lombok.Data;
@Data
public class Result {
private Hourly hourly;
private Daily daily;
private Integer primary;
private String forecast_keypoint;
}

View File

@ -0,0 +1,22 @@
package com.qiuguo.iot.data.resp.third.weather;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* @author simon
* @date 2023/9/21
* @description
**/
@Data
public class Temperature {
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm", timezone = "GMT+8")
private Date datetime;
//天气
private Double max;
private Double min;
private Double avg;
private Double probability;
}

View File

@ -0,0 +1,9 @@
package com.qiuguo.iot.data.resp.third.weather;
import lombok.Data;
@Data
public class Value {
private Integer chn;
private Integer usa;
}

View File

@ -0,0 +1,14 @@
package com.qiuguo.iot.data.resp.third.weather;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
public class Visibility {
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm", timezone = "GMT+8")
private Date datetime;
private double value;
}

View File

@ -0,0 +1,19 @@
package com.qiuguo.iot.data.resp.third.weather;
import lombok.Data;
import java.util.List;
@Data
public class WeatherResp {
private String status;
private String api_version;
private String api_status;
private String lang;
private String unit;
private Integer tzshift;
private String timezone;
private Long server_time;
private List<Double> location;
private Result result;
}

View File

@ -0,0 +1,14 @@
package com.qiuguo.iot.data.resp.third.weather;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
public class WeatherTimeDouble {
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm", timezone = "GMT+8")
private Date datetime;
private Double value;
}

View File

@ -0,0 +1,18 @@
package com.qiuguo.iot.data.resp.third.weather;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
public class WeatherTimeInt {
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm", timezone = "GMT+8")
private Date datetime;
private Integer value;
//天气
private Double max;
private Double min;
private Double avg;
}

View File

@ -0,0 +1,14 @@
package com.qiuguo.iot.data.resp.third.weather;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
public class WeatherTimeString {
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm", timezone = "GMT+8")
private Date datetime;
private String value;
}

View File

@ -0,0 +1,14 @@
package com.qiuguo.iot.data.resp.third.weather;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
public class WeatherTimeValue {
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm", timezone = "GMT+8")
private Date datetime;
private Value value;
}

View File

@ -0,0 +1,19 @@
package com.qiuguo.iot.data.resp.third.weather;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
public class Wind {
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm", timezone = "GMT+8")
private Date datetime;
private Double speed;
private Double direction;
private WindSpeed max;
private WindSpeed min;
private WindSpeed avg;
}

View File

@ -0,0 +1,14 @@
package com.qiuguo.iot.data.resp.third.weather;
import lombok.Data;
/**
* @author simon
* @date 2023/9/21
* @description
**/
@Data
public class WindSpeed {
private Double speed;
private Double direction;
}

33
iot-common/iot-third/.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -0,0 +1,68 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.qiuguo.iot</groupId>
<artifactId>iot-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>iot-third</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>iot-third</name>
<description>iot-third</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.qiuguo.iot</groupId>
<artifactId>iot-base</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.qiuguo.iot</groupId>
<artifactId>iot-data</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.maven.plugin.version}</version>
<configuration>
<!--跳过对项目中main方法的查找-->
<skip>true</skip>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,30 @@
package com.qiuguo.iot.third.enums;
/**
* @author simon
* @create 2023-09-21 10:57
*/
public enum WeatherEnum {
//退款申请状态 0:待申请, 1:申请成功, 2:退款成功
QUERY_TYPE_1(1, "按小时查询"),
QUERY_TYPE_2(2, "按天查询"),
QUERY_MIN_SIZE(1, "1"),
QUERY_HOUR_MAX_SIZE(360, "360小时"),
QUERY_DAY_MAX_SIZE(15, "15天"),
;
public final String name;
public final Integer code;
WeatherEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
public Integer getCode() {
return this.code;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,32 @@
package com.qiuguo.iot.third.service;
import com.qiuguo.iot.data.resp.third.ThirdIpInfoResp;
import com.qiuguo.iot.data.resp.third.ThirdRpcResp;
import org.springframework.beans.factory.annotation.Value;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
/**
* @author simon
* @date 2023/9/21
* @description ip查询
**/
@Service
@Slf4j
public class IpService {
@Value("https://qiuguo-app.qiuguojihua.com/prod-api/third/ip/info")
public String thirdIpInfoUrl;
/**
* 查询ip信息
* @param ip
* @return
*/
public Mono<ThirdRpcResp<ThirdIpInfoResp>> getIpInfo(String ip) {
WebClient webClient = WebClient.builder().build();
return webClient.get().uri(thirdIpInfoUrl + "?ip=" + ip).retrieve().bodyToMono(ThirdRpcResp.getResponseIpType());
}
}

View File

@ -0,0 +1,108 @@
package com.qiuguo.iot.third.service;
import cn.hutool.json.JSONObject;
import com.qiuguo.iot.data.request.third.ThirdWeatherInfoRequest;
import com.qiuguo.iot.data.resp.third.ThirdIpInfoResp;
import com.qiuguo.iot.data.resp.third.ThirdRpcResp;
import com.qiuguo.iot.data.resp.third.weather.WeatherResp;
import com.qiuguo.iot.third.enums.WeatherEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.CoreSubscriber;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.annotation.Nullable;
import javax.annotation.Resource;
import java.util.regex.Pattern;
/**
* @author simon
* @date 2023/9/21
* @description 天气服务
**/
@Service
@Slf4j
@Validated
public class WeatherService {
//101.6656,39.2072/hourly?hourlysteps=1
//101.6656,39.2072/daily?dailysteps=1
@Value("https://api.caiyunapp.com/v2.6/ilUeAnf1vNkphxYS/")
private String queryWeatherUrl;
@Resource
private IpService ipService;
/**
*
* @return
*/
public Mono<WeatherResp> queryWeather(ThirdWeatherInfoRequest req) {
if (req.getType() < WeatherEnum.QUERY_TYPE_1.getCode() || req.getType() > WeatherEnum.QUERY_TYPE_2.getCode()) {
req.setType(WeatherEnum.QUERY_TYPE_1.getCode());
}
if (req.getSize() < WeatherEnum.QUERY_MIN_SIZE.getCode()) {
req.setSize(WeatherEnum.QUERY_MIN_SIZE.getCode());
}
//小时查询最大360小时
if (req.getType().equals(WeatherEnum.QUERY_TYPE_1.getCode()) && req.getSize() > WeatherEnum.QUERY_HOUR_MAX_SIZE.getCode()) {
req.setSize(WeatherEnum.QUERY_HOUR_MAX_SIZE.getCode());
}
//按天查询最多15天
if (req.getType().equals(WeatherEnum.QUERY_TYPE_2.getCode()) && req.getSize() > WeatherEnum.QUERY_DAY_MAX_SIZE.getCode()) {
req.setSize(WeatherEnum.QUERY_DAY_MAX_SIZE.getCode());
}
Mono<ThirdWeatherInfoRequest> lngLatMono = Mono.empty();
//如果经纬度为空通过ip获取经纬度
if (ObjectUtils.isEmpty(req.getLng()) || ObjectUtils.isEmpty(req.getLat())) {
if (ObjectUtils.isEmpty(req.getIp())) {
throw new RuntimeException("ip经纬度不能同时为空");
}
boolean matches = Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$", req.getIp());
if (!matches) {
throw new RuntimeException("ip格式不正确");
}
lngLatMono = ipService.getIpInfo(req.getIp()).flatMap(resp -> {
if (!resp.getCode().equals(HttpStatus.OK.value())) {
return Mono.error(new RuntimeException("ip服务查询失败:" + resp.getMsg() + " " + resp.getCode()));
}
if (ObjectUtils.isEmpty(resp.getData().getLng()) || ObjectUtils.isEmpty(resp.getData().getLat())) {
return Mono.error(new RuntimeException("当前ip查询失败:" + req.getIp()));
}
req.setLng(resp.getData().getLng().toString());
req.setLat(resp.getData().getLat().toString());
return Mono.just(req);
});
} else {
lngLatMono = Mono.just(req);
}
return thirdQueryWeather(lngLatMono, req.getType(), req.getSize());
}
public Mono<WeatherResp> thirdQueryWeather(Mono<ThirdWeatherInfoRequest> lngLatMono, Integer queryType, Integer querySize) {
WebClient webClient = WebClient.builder().build();
return lngLatMono.flatMap(r -> {
if (queryType.equals(WeatherEnum.QUERY_TYPE_1.getCode())) {
queryWeatherUrl = queryWeatherUrl + r.getLng().toString() + "," + r.getLat().toString() + "/hourly?hourlysteps=" + querySize;
} else {
queryWeatherUrl = queryWeatherUrl + r.getLng().toString() + "," + r.getLat().toString() + "/daily?dailysteps=" + querySize;
}
return webClient.get().uri(queryWeatherUrl).retrieve().bodyToMono(WeatherResp.class);
});
}
}

View File

@ -0,0 +1,16 @@
package com.qiuguo.iot.third;
import com.qiuguo.iot.data.resp.third.ThirdIpInfoResp;
import com.qiuguo.iot.data.resp.third.ThirdRpcResp;
import com.qiuguo.iot.third.service.IpService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.concurrent.atomic.AtomicReference;
@SpringBootTest()
class IotThirdApplicationTests {
}

View File

@ -0,0 +1,46 @@
package com.qiuguo.iot.third.service;
import com.qiuguo.iot.data.resp.third.ThirdIpInfoResp;
import com.qiuguo.iot.data.resp.third.ThirdRpcResp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.test.context.SpringBootTest;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import javax.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author simon
* @date 2023/9/21
* @description
**/
@SpringBootTest(classes = IpService.class)
class IpServiceTest {
@Resource
private IpService ipService;
@Test
void contextLoads() throws InterruptedException {
Mono<ThirdRpcResp<ThirdIpInfoResp>> ipInfo = ipService.getIpInfo("60.186.105.204");
System.out.println("contextLoads");
Mono<ThirdRpcResp<ThirdIpInfoResp>> thirdRpcRespMono = ipInfo.flatMap(resp -> {
System.out.println(resp.getData().getCity());
return Mono.just(resp);
});
thirdRpcRespMono.subscribe(System.out::println);
Thread.sleep(20000);
// StepVerifier.create(ipInfo)
// //.expectNext("Hello, World!") // 验证预期的输出值
// //.expectNext()
// .expectComplete() // 验证是否正常完成
// .verify(); // 执行验证
System.out.println("contextLoads");
}
}

View File

@ -0,0 +1,47 @@
package com.qiuguo.iot.third.service;
import cn.hutool.json.JSONObject;
import com.qiuguo.iot.data.request.third.ThirdWeatherInfoRequest;
import com.qiuguo.iot.data.resp.third.weather.WeatherResp;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
/**
* @author simon
* @date 2023/9/21
* @description
**/
@SpringBootTest(classes = {WeatherService.class, IpService.class})
class WeatherServiceTest {
@Resource
private WeatherService weatherService;
@Test
void queryWeather() throws InterruptedException {
ThirdWeatherInfoRequest thirdWeatherInfoRequest = new ThirdWeatherInfoRequest();
thirdWeatherInfoRequest.setType(2);
thirdWeatherInfoRequest.setSize(1);
thirdWeatherInfoRequest.setIp("60.186.105.204");
System.out.println(thirdWeatherInfoRequest.getIp());
Mono<WeatherResp> jsonObjectMono = weatherService.queryWeather(thirdWeatherInfoRequest);
jsonObjectMono.flatMap(r -> {
System.out.println("flatMap");
System.out.println(r.getApi_version());
return Mono.just(r);
}).subscribe(System.out::println);
Thread.sleep(20000);
System.out.println(thirdWeatherInfoRequest.getIp());
}
@Test
void thirdQueryWeather() {
}
}

View File

@ -49,7 +49,27 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>com.qiuguo.iot</groupId>
<artifactId>iot-base</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,45 @@
package com.qiuguo.iot.gateway.config;
import com.qiuguo.iot.gateway.config.properties.CorsProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* 全局跨域配置.
*
*/
// @AutoConfiguration
// @EnableConfigurationProperties(CorsProperties.class)
public class GlobalCorsConfiguration {
/**
* 允许跨域调用的过滤器.
*
* @param corsProperties 跨域配置
*/
// @Bean
// @ConditionalOnMissingBean(CorsFilter.class)
public CorsFilter corsFilter(CorsProperties corsProperties) {
CorsConfiguration config = new CorsConfiguration();
// 设置允许跨域访问的域名
config.setAllowedOriginPatterns(corsProperties.getAllowedOriginPatterns());
// 设置允许跨域访问的方法
config.setAllowedMethods(corsProperties.getAllowedMethods());
// 设置允许跨域访问的请求头
config.setAllowedHeaders(corsProperties.getAllowedHeaders());
config.setExposedHeaders(corsProperties.getExposedHeaders());
// 允许跨越发送cookie
config.setAllowCredentials(corsProperties.getAllowCredentials());
config.setMaxAge(corsProperties.getMaxAge());
// 对接口配置跨域设置
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

View File

@ -0,0 +1,48 @@
package com.qiuguo.iot.gateway.config.properties;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 跨域配置.
*
* @author yangning
* @since 2022/11/7 14:45
*/
@Getter
@Setter
// @ConfigurationProperties(prefix = "application.cors")
public class CorsProperties {
/**
* 允许跨域访问的域名.
*/
private List<String> allowedOriginPatterns;
/**
* 允许跨域访问的方法.
*/
private List<String> allowedMethods;
/**
* 允许跨域访问的请求头.
*/
private List<String> allowedHeaders;
/**
* 暴露的响应头.
*/
private List<String> exposedHeaders;
/**
* 是否允许跨域发送cookie.
*/
private Boolean allowCredentials = true;
/**
* 跨域访问有效期.
*/
private Long maxAge = 1800L;
}

View File

@ -0,0 +1,31 @@
package com.qiuguo.iot.gateway.config.properties;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
/**
* Xss配置.
*
* @author yangning
* @since 2023/4/7 18:31
*/
@Data
@Component
@RefreshScope
@ConfigurationProperties(prefix = "security.xss")
public class XssProperties {
/**
* Xss开关.
*/
private Boolean enabled;
/**
* 排除路径.
*/
private List<String> excludeUrls = new ArrayList<>();
}

View File

@ -0,0 +1,69 @@
package com.qiuguo.iot.gateway.filter;
import com.qiuguo.iot.base.constans.RedisConstans;
import com.qiuguo.iot.base.constans.UserAuthContains;
import com.qiuguo.iot.gateway.config.properties.XssProperties;
import java.time.Duration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* XXX
*
* @author weiyachao
* @since 2023/9/21 17:56
*/
@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {
@Autowired
private ReactiveStringRedisTemplate reactiveRedisTemplate;
@Autowired
private XssProperties xssProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String url = request.getURI().toString();
if (xssProperties.getExcludeUrls().contains(url)) {
return chain.filter(exchange);
}
String api_token = exchange.getRequest().getHeaders().getFirst(UserAuthContains.API_TOKEN);
String api_type = exchange.getRequest().getHeaders().getFirst(UserAuthContains.API_TYPE);
if (ObjectUtils.isEmpty(api_token) || ObjectUtils.isEmpty(api_type)) {
return Mono.error(new RuntimeException("未登录"));
}
String key = RedisConstans.IOT_TOKEN.concat(api_token);
return reactiveRedisTemplate.getExpire(key).map(Duration::getSeconds).flatMap(ttl -> {
if (ttl == -1) {
// 用户没登陆
return Mono.error(new RuntimeException("未登录"));
} else if (ttl <= 3600) {
// token 将要失效
return reactiveRedisTemplate.expire(key, Duration.ofDays(7)).then(chain.filter(exchange));
} else {
// 正常登录
return chain.filter(exchange);
}
});
}
@Override
public int getOrder() {
return -1;
}
}

View File

@ -0,0 +1,40 @@
package com.qiuguo.iot.gateway.filter;
import com.qiuguo.iot.gateway.util.WebFluxUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 全局缓存获取body请求数据解决流不能重复读取问题.
*
* @author yangning
* @since 2022/12/14 16:07
*/
@Component
public class GlobalCacheRequestFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 只缓存json类型请求
// if (!WebFluxUtils.isJsonRequest(exchange)) {
// return chain.filter(exchange);
// }
return ServerWebExchangeUtils.cacheRequestBody(exchange, (serverHttpRequest) -> {
if (serverHttpRequest == exchange.getRequest()) {
return chain.filter(exchange);
}
return chain.filter(exchange.mutate().request(serverHttpRequest).build());
});
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}

View File

@ -0,0 +1,47 @@
package com.qiuguo.iot.gateway.handler;
import com.qiuguo.iot.gateway.util.WebFluxUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 网关统一异常处理.
*
*/
@Slf4j
@Order(-1)
@Configuration
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
final ServerHttpResponse response = exchange.getResponse();
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
String msg;
if (ex instanceof NotFoundException) {
msg = "服务未找到";
} else if (ex instanceof ResponseStatusException) {
ResponseStatusException responseStatusException = (ResponseStatusException) ex;
msg = responseStatusException.getMessage();
} else {
msg = "内部服务器错误";
}
log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
return WebFluxUtils.webFluxResponseWriter(response, msg);
}
}

View File

@ -0,0 +1,88 @@
package com.qiuguo.iot.gateway.util;
import com.alibaba.fastjson.JSON;
import java.util.HashMap;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* WebFlux 工具类.
*/
public class WebFluxUtils {
/**
* 是否是Json请求.
*
* @param exchange HTTP请求
*/
public static boolean isJsonRequest(ServerWebExchange exchange) {
String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
}
/**
* 设置webflux模型响应.
*
* @param response ServerHttpResponse
* @param value 响应内容
* @return Mono
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value) {
return webFluxResponseWriter(response, HttpStatus.OK, value, HttpStatus.INTERNAL_SERVER_ERROR.value());
}
/**
* 设置webflux模型响应.
*
* @param response ServerHttpResponse
* @param code 响应状态码
* @param value 响应内容
* @return Mono
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value, int code) {
return webFluxResponseWriter(response, HttpStatus.OK, value, code);
}
/**
* 设置webflux模型响应.
*
* @param response ServerHttpResponse
* @param status http状态码
* @param code 响应状态码
* @param value 响应内容
* @return Mono
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value,
int code) {
return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code);
}
/**
* 设置webflux模型响应.
*
* @param response ServerHttpResponse
* @param contentType content-type
* @param status http状态码
* @param code 响应状态码
* @param value 响应内容
* @return Mono
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status,
Object value, int code) {
response.setStatusCode(status);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);
HashMap<String, Object> map = new HashMap<>();
map.put("message", value.toString());
map.put("status", code);
map.put("code", "error");
map.put("timestamp", System.currentTimeMillis());
DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(map).getBytes());
return response.writeWith(Mono.just(dataBuffer));
}
}

View File

@ -19,4 +19,35 @@ spring:
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
gateway:
discovery:
locator:
# 开启服务发现
enabled: true
# 忽略注册中心服务的大小写
lower-case-service-id: true
globalcors:
corsConfigurations:
'[/**]':
# 允许携带认证信息
allow-credentials: true
# 允许跨域的源(网站域名/ip),设置*为全部
allowedOriginPatterns: "*"
# 允许跨域的method 默认为GET和OPTIONS设置*为全部
allowedMethods: "*"
# 允许跨域请求里的head字段设置*为全部
allowedHeaders: "*"
routes:
# 安全配置
security:
# 防止XSS攻击
xss:
enabled: true
# 排除的路径
exclude-urls:
- /ehs-audit/web/audit-content
application:
cors:
allowed-crigin-patterns:

View File

@ -1,9 +1,16 @@
package com.qiuguo.iot.user.api.controller.user;
import com.alibaba.fastjson.JSONObject;
import com.qiuguo.iot.base.constans.RedisConstans;
import com.qiuguo.iot.base.constans.UserAuthContains;
import java.time.Duration;
import java.util.Objects;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@ -31,7 +38,7 @@ import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VAL
public class UserController {
private final WebClient webClient = WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE,APPLICATION_FORM_URLENCODED_VALUE)
.defaultHeader("Api-Type","web")
.defaultHeader("Api-Type","iot")
.build();
@Value("${userUrl.baseUrl}")
@ -64,12 +71,15 @@ public class UserController {
@Value("${userUrl.editUserInfoUrl}")
private String editUserInfoUrl;
@Resource
private ReactiveStringRedisTemplate reactiveStringRedisTemplate;
/**
* 修改登录密码-auth
*/
@PostMapping("/change")
public Mono<JSONObject> change(@RequestBody JSONObject jsonObject, @RequestHeader("Api-Token") String token,
@RequestHeader("Api-Type") String type) {
public Mono<JSONObject> change(@RequestBody JSONObject jsonObject, @RequestHeader(UserAuthContains.API_TOKEN) String token,
@RequestHeader(UserAuthContains.API_TYPE) String type) {
WebClient authWebClient = getAuthWebClient(token, type);
return authWebClient.post().uri(baseUrl + changeUrl).bodyValue(getMultiValueMap(jsonObject)).retrieve()
.bodyToMono(JSONObject.class).doOnNext(res -> {
@ -83,8 +93,8 @@ public class UserController {
* 账号注销-auth
*/
@PostMapping("/userCance")
public Mono<JSONObject> userCance(@RequestBody JSONObject jsonObject, @RequestHeader("Api-Token") String token,
@RequestHeader("Api-Type") String type) {
public Mono<JSONObject> userCance(@RequestBody JSONObject jsonObject, @RequestHeader(UserAuthContains.API_TOKEN) String token,
@RequestHeader(UserAuthContains.API_TYPE) String type) {
return getAuthWebClient(token, type).post().uri(baseUrl + userCancelUrl)
.bodyValue(getMultiValueMap(jsonObject))
.retrieve()
@ -99,8 +109,8 @@ public class UserController {
* 修改用户信息-auth
*/
@PostMapping("/edit/userInfo")
public Mono<JSONObject> editUserInfo(@RequestBody JSONObject jsonObject, @RequestHeader("Api-Token") String token,
@RequestHeader("Api-Type") String type) {
public Mono<JSONObject> editUserInfo(@RequestBody JSONObject jsonObject, @RequestHeader(UserAuthContains.API_TOKEN) String token,
@RequestHeader(UserAuthContains.API_TYPE) String type) {
return webClient.mutate()
.defaultHeader("Api-Token", token)
.defaultHeader("Api-Type", type).build().post().uri(editUserInfoUrl)
@ -117,8 +127,8 @@ public class UserController {
* 个人信息管理-auth
*/
@GetMapping("/userInfo")
public Mono<JSONObject> getUserInfo(@RequestHeader("Api-Token") String token,
@RequestHeader("Api-Type") String type) {
public Mono<JSONObject> getUserInfo(@RequestHeader(UserAuthContains.API_TOKEN) String token,
@RequestHeader(UserAuthContains.API_TYPE) String type) {
return getAuthWebClient(token, type).get().uri(userInfoUrl).retrieve()
.bodyToMono(JSONObject.class).doOnNext(res -> {
if (!Objects.equals(res.getInteger("code"), 200)) {
@ -131,8 +141,8 @@ public class UserController {
* 是否设置登录密码-auth
*/
@GetMapping("/first/password")
public Mono<JSONObject> firstPassword(@RequestHeader("Api-Token") String token,
@RequestHeader("Api-Type") String type) {
public Mono<JSONObject> firstPassword(@RequestHeader(UserAuthContains.API_TOKEN) String token,
@RequestHeader(UserAuthContains.API_TYPE) String type) {
return getAuthWebClient(token, type).get().uri(firstPasswordUrl).retrieve()
.bodyToMono(JSONObject.class).doOnNext(res -> {
if (!Objects.equals(res.getInteger("code"), 200)) {
@ -166,7 +176,9 @@ public class UserController {
.retrieve().bodyToMono(JSONObject.class).flatMap(res -> {
if (Objects.equals(res.getInteger("code"), 1) && !res.getString("info")
.contains("该手机号还没有注册哦")) {
return Mono.just(res);
String token = res.getJSONObject("data").getJSONObject("token").getString("token");
return reactiveStringRedisTemplate.opsForValue()
.set(RedisConstans.IOT_TOKEN.concat(token), token, Duration.ofDays(7)).then(Mono.just(res));
} else if(!res.getString("info").contains("该手机号还没有注册哦")){
return Mono.error(new RuntimeException(res.getString("info")));
}else {
@ -186,10 +198,14 @@ public class UserController {
object.add("phone", jsonObject.getString("phone"));
object.add("verify", jsonObject.getString("verify"));
return webClient.post().uri(baseUrl + smsUrl).bodyValue(object).retrieve()
.bodyToMono(JSONObject.class).doOnNext(twoRes -> {
.bodyToMono(JSONObject.class).flatMap(twoRes -> {
if (!Objects.equals(twoRes.getInteger("code"), 1)) {
throw new RuntimeException(twoRes.getString("info"));
return Mono.error(new RuntimeException(twoRes.getString("info")));
}
String token = res.getJSONObject("data").getJSONObject("token").getString("token");
return reactiveStringRedisTemplate.opsForValue()
.set(RedisConstans.IOT_TOKEN.concat(token), token, Duration.ofDays(7)).then(Mono.just(res));
});
}
});
@ -205,11 +221,15 @@ public class UserController {
log.info("UserController[]loginByPwd[]jsonObject:{}", jsonObject);
return webClient.post().uri(baseUrl + pwdUrl).bodyValue(getMultiValueMap(jsonObject)).retrieve()
.bodyToMono(JSONObject.class)
.doOnNext(res -> {
.flatMap(res -> {
if (!Objects.equals(res.getInteger("code"), 1)) {
throw new RuntimeException(res.getString("info"));
return Mono.error(new RuntimeException(res.getString("info")));
}
String token = res.getJSONObject("data").getJSONObject("token").getString("token");
return reactiveStringRedisTemplate.opsForValue()
.set(RedisConstans.IOT_TOKEN.concat(token), token, Duration.ofDays(7)).then(Mono.just(res));
});
}
private MultiValueMap<String,String> getMultiValueMap(JSONObject jsonObject) {

View File

@ -0,0 +1,58 @@
package com.qiuguo.iot.user.api.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
// import org.springframework.web.servlet.HandlerMapping;
import reactor.core.publisher.Mono;
/**
* XXX
*
* @author weiyachao
* @since 2023/9/20 16:06
*/
@Configuration
@Slf4j
@Order(-1)
public class AuthFilter implements WebFilter {
// @Autowired
private ReactiveStringRedisTemplate reactiveRedisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// HandlerMethod handlerMethod = exchange.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
Object attribute = exchange.getAttribute("org.springframework.web.server.ServerWebExchange.LOG_ID");
System.out.println("attribute = " + attribute);
// if (handlerMethod != null && handlerMethod.getMethod().isAnnotationPresent(Auth.class)) {
// // 如果请求方法上有 Auth 注解执行登录验证逻辑
// String api_token = exchange.getRequest().getHeaders().getFirst(UserAuthContains.API_TOKEN);
// String api_type = exchange.getRequest().getHeaders().getFirst(UserAuthContains.API_TYPE);
// if (ObjectUtils.isEmpty(api_token) || ObjectUtils.isEmpty(api_type)) {
// return Mono.error(new RuntimeException("未登录"));
// }
// String key = RedisConstans.IOT_TOKEN.concat(api_token);
// return reactiveRedisTemplate.getExpire(key).map(Duration::getSeconds).flatMap(ttl -> {
// if (ttl == -1) {
// // 用户没登陆
// return Mono.error(new RuntimeException("未登录"));
// } else if (ttl <= 3600) {
// // token 将要失效
// return reactiveRedisTemplate.expire(key, Duration.ofDays(7))
// .then(chain.filter(exchange));
// } else {
// // 正常登录
// return chain.filter(exchange);
// }
// });
// }
return chain.filter(exchange);
}
}

View File

@ -0,0 +1,110 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.qiuguo.iot.user.api.IotBoxUserApiApplication;
import com.qiuguo.iot.user.api.service.TuyaDeviceConnector;
import java.util.Arrays;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* XXX
*
* @author weiyachao
* @since 2023/9/5 16:03
*/
@SpringBootTest(classes = IotBoxUserApiApplication.class)
@Slf4j
public class UserTest {
public String deviceId = "6cae26f5512eee7c12aqd9";
public String spaceId = "163257138";
@Autowired
private TuyaDeviceConnector tuyaDeviceConnector;
@Test
public void 查询所有分类() {
JSONArray aa = tuyaDeviceConnector.categories();
aa.forEach(System.out::println);
}
@Test
public void 转移设备() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("space_id", "163257138");
Boolean aBoolean = tuyaDeviceConnector.transferDevice(deviceId, jsonObject);
System.out.println(aBoolean);
}
@Test
public void 获取设备支持的指令集() {
Object functions = tuyaDeviceConnector.getFunctions(deviceId);
System.out.println(functions);
}
@Test
public void 修改空间() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "testname");
Boolean jsonObject1 = tuyaDeviceConnector.updateSpace(spaceId, jsonObject);
System.out.println(jsonObject1);
}
@Test
public void 查询空间() {
//40001900
JSONObject spaceInfo = tuyaDeviceConnector.getSpaceInfo("163257138");
System.out.println(spaceInfo);
}
@Test
public void 删除空间() {
//40001900
Boolean jsonObject = tuyaDeviceConnector.deleteSpace("163258893");
System.out.println(jsonObject);
}
@Test
public void 创建空间() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "space1_2");
jsonObject.put("description", "space1_2的空间描述");
Long jsonObject1 = tuyaDeviceConnector.creatSpace(jsonObject);
System.out.println(jsonObject1);
}
@Test
public void 控制设备动作() {
JSONObject commands = new JSONObject();
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "switch_led");
jsonObject.put("value", true);
JSONObject js2 = new JSONObject();
js2.put("code", "work_mode");
js2.put("value", "colour");
JSONObject js3 = new JSONObject();
js3.put("code", "bright_value_v2");
js3.put("value", 10);
commands.put("commands", Arrays.asList(jsonObject));
Object controlDevice = tuyaDeviceConnector.controlDevice(deviceId,commands);
System.out.println(controlDevice);
}
@Test
public void 查询空间下设备列表() {
JSONArray devicesBySpaceIds = tuyaDeviceConnector.getDevicesBySpaceIds("163257138", 20);
System.out.println(devicesBySpaceIds);
}
@Test
public void 查询设备信息() {
JSONArray byid = tuyaDeviceConnector.getByid(deviceId);
System.out.println(byid);
}
}