diff --git a/iot-gateway/pom.xml b/iot-gateway/pom.xml
index 9516122..6e514d6 100644
--- a/iot-gateway/pom.xml
+++ b/iot-gateway/pom.xml
@@ -49,7 +49,17 @@
org.springframework.boot
spring-boot-starter-actuator
-
+
+ org.projectlombok
+ lombok
+
+
+ com.alibaba
+ fastjson
+ 1.2.83
+ compile
+
+
diff --git a/iot-gateway/src/main/java/com/qiuguo/iot/gateway/config/GlobalCorsConfiguration.java b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/config/GlobalCorsConfiguration.java
new file mode 100644
index 0000000..7810bbd
--- /dev/null
+++ b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/config/GlobalCorsConfiguration.java
@@ -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);
+ }
+}
diff --git a/iot-gateway/src/main/java/com/qiuguo/iot/gateway/config/properties/CorsProperties.java b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/config/properties/CorsProperties.java
new file mode 100644
index 0000000..a0ef19d
--- /dev/null
+++ b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/config/properties/CorsProperties.java
@@ -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 allowedOriginPatterns;
+
+ /**
+ * 允许跨域访问的方法.
+ */
+ private List allowedMethods;
+
+ /**
+ * 允许跨域访问的请求头.
+ */
+ private List allowedHeaders;
+
+ /**
+ * 暴露的响应头.
+ */
+ private List exposedHeaders;
+
+ /**
+ * 是否允许跨域发送cookie.
+ */
+ private Boolean allowCredentials = true;
+
+ /**
+ * 跨域访问有效期.
+ */
+ private Long maxAge = 1800L;
+}
diff --git a/iot-gateway/src/main/java/com/qiuguo/iot/gateway/config/properties/XssProperties.java b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/config/properties/XssProperties.java
new file mode 100644
index 0000000..622adf3
--- /dev/null
+++ b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/config/properties/XssProperties.java
@@ -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 excludeUrls = new ArrayList<>();
+}
diff --git a/iot-gateway/src/main/java/com/qiuguo/iot/gateway/filter/GlobalCacheRequestFilter.java b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/filter/GlobalCacheRequestFilter.java
new file mode 100644
index 0000000..93132e8
--- /dev/null
+++ b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/filter/GlobalCacheRequestFilter.java
@@ -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 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;
+ }
+
+}
diff --git a/iot-gateway/src/main/java/com/qiuguo/iot/gateway/handler/GatewayExceptionHandler.java b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/handler/GatewayExceptionHandler.java
new file mode 100644
index 0000000..9b3840c
--- /dev/null
+++ b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/handler/GatewayExceptionHandler.java
@@ -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 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);
+ }
+
+}
diff --git a/iot-gateway/src/main/java/com/qiuguo/iot/gateway/util/WebFluxUtils.java b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/util/WebFluxUtils.java
new file mode 100644
index 0000000..5e5eef9
--- /dev/null
+++ b/iot-gateway/src/main/java/com/qiuguo/iot/gateway/util/WebFluxUtils.java
@@ -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 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 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 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 webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status,
+ Object value, int code) {
+ response.setStatusCode(status);
+ response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);
+ HashMap 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));
+ }
+}