diff --git a/gateway/.drone.yml b/gateway/.drone.yml
new file mode 100644
index 0000000..94bcee9
--- /dev/null
+++ b/gateway/.drone.yml
@@ -0,0 +1,119 @@
+clone:
+ image: registry.t-aaron.com/drone/git:latest
+
+kind: pipeline
+type: kubernetes
+name: gateway
+
+volumes:
+ - name: maven-cache
+ host:
+ path: /opt/maven-cache-default
+ - name: sonar-cache
+ host:
+ path: /opt/sonar-cache-default
+
+steps:
+ - name: download-dependencies
+ image: registry.t-aaron.com/maven:3.8.6-openjdk-11-slim
+ volumes:
+ - name: maven-cache
+ path: /root/.m2
+ commands:
+ - echo "配置 Maven 镜像源..."
+ - mkdir -p /root/.m2
+ - cp settings.xml /root/.m2/settings.xml
+ - echo "开始下载 Maven 依赖..."
+ - mvn dependency:go-offline -B
+ - echo "依赖下载完成!"
+ - echo "将本地 Maven 缓存同步到工作区用于后续构建..."
+ - mkdir -p /drone/src/.m2
+ - cp -a /root/.m2/. /drone/src/.m2/
+
+ - name: package
+ image: registry.t-aaron.com/maven:3.8.6-openjdk-11-slim
+ volumes:
+ - name: maven-cache
+ path: /root/.m2
+ commands:
+ - echo "配置 Maven 镜像源..."
+ - mkdir -p /root/.m2
+ - cp settings.xml /root/.m2/settings.xml
+ - echo "开始构建 JAR 包..."
+ - mvn clean package -DskipTests -B
+ - echo "JAR 包构建完成!"
+ - ls -la target/*.jar
+ when:
+ event: [ push, pull_request ]
+ depends_on:
+ - download-dependencies
+
+ - name: sonar-scan
+ image: registry.t-aaron.com/maven:3.8.6-openjdk-11-slim
+ volumes:
+ - name: maven-cache
+ path: /root/.m2
+ - name: sonar-cache
+ path: /root/.sonar/cache
+ commands:
+ - echo "配置 Maven 镜像源..."
+ - mkdir -p /root/.m2
+ - cp settings.xml /root/.m2/settings.xml
+ - echo "开始 SonarQube 代码质量检查..."
+ - echo "清理之前的构建文件..."
+ - rm -rf target/ .mvn/ .classpath .project .settings/
+ - echo "编译代码..."
+ - mvn clean compile
+ - echo "执行 SonarQube 扫描..."
+ - mvn sonar:sonar -Dsonar.projectKey=gateway -Dsonar.host.url=https://sonar-ops.t-aaron.com/sonar -Dsonar.login=$SONAR_TOKEN -Dsonar.projectName="Gateway" -Dsonar.projectVersion=${DRONE_COMMIT_SHA:0:8} -Dsonar.sources=src/main/java -Dsonar.java.binaries=target/classes
+ - echo "SonarQube 代码质量检查完成!"
+ environment:
+ SONAR_TOKEN:
+ from_secret: SONAR_TOKEN
+ when:
+ event: [ push, pull_request ]
+ depends_on:
+ - download-dependencies
+
+ - name: build-and-push
+ image: registry.t-aaron.com/plugins/kaniko
+ settings:
+ registry: registry.t-aaron.com
+ repo: registry.t-aaron.com/tuoheng/gateway
+ cache: true
+ cache_repo: registry.t-aaron.com/kaniko/cache-gateway
+ build_args:
+ - MAVEN_MIRROR_URL=https://maven.aliyun.com/repository/public
+ username:
+ from_secret: REGISTRY_USERNAME
+ password:
+ from_secret: REGISTRY_PASSWORD
+ tags:
+ - latest
+ - ${DRONE_COMMIT_SHA:0:8}
+ dockerfile: Dockerfile
+ context: .
+ when:
+ event: [ push, tag ]
+ depends_on:
+ - package
+
+ - name: deploy-to-k8s
+ image: registry.t-aaron.com/alpine/k8s:1.25.9
+ commands:
+ - echo "部署/更新 gateway 到 default 命名空间"
+ - |
+ kubectl create deployment gateway \
+ --image=registry.t-aaron.com/tuoheng/gateway:${DRONE_COMMIT_SHA:0:8} \
+ --port=8080 -n default --dry-run=client -o yaml | kubectl apply -f -
+ - kubectl set image deployment/gateway gateway=registry.t-aaron.com/tuoheng/gateway:${DRONE_COMMIT_SHA:0:8} -n default --record=true || true
+ - kubectl create service clusterip gateway --tcp=8080:8080 -n default --dry-run=client -o yaml | kubectl apply -f -
+ - echo "等待 Deployment 就绪..."
+ - kubectl rollout status deployment/gateway -n default --timeout=300s
+ - echo "查看服务与Pod状态"
+ - kubectl get deploy,svc -n default | grep -i gateway || true
+ - kubectl get pods -n default -l app=gateway || kubectl get pods -n default | grep gateway || true
+ when:
+ event: [ push ]
+ depends_on:
+ - build-and-push
\ No newline at end of file
diff --git a/gateway/.gitignore b/gateway/.gitignore
new file mode 100644
index 0000000..2f7896d
--- /dev/null
+++ b/gateway/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/gateway/Dockerfile b/gateway/Dockerfile
new file mode 100644
index 0000000..184a471
--- /dev/null
+++ b/gateway/Dockerfile
@@ -0,0 +1,27 @@
+# 生产阶段 - 仅复制预构建的 JAR 文件
+FROM registry.t-aaron.com/openjdk:11-jre-slim
+
+# 创建应用用户
+RUN groupadd -r appuser && useradd -r -g appuser appuser
+
+# 设置工作目录
+WORKDIR /app
+
+# 复制预构建的 JAR 文件
+COPY target/*.jar app.jar
+
+# 更改文件所有者
+RUN chown -R appuser:appuser /app
+
+# 切换到应用用户
+USER appuser
+
+# 暴露端口
+EXPOSE 8080
+
+# 健康检查
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD netstat -an | grep :8080 | grep LISTEN || exit 1
+
+# 启动应用
+ENTRYPOINT ["java", "-jar", "app.jar"]
diff --git a/gateway/README.md b/gateway/README.md
new file mode 100644
index 0000000..13a2d63
--- /dev/null
+++ b/gateway/README.md
@@ -0,0 +1,48 @@
+# Test pipeline trigger
+# Test pipeline with secrets permission
+# Test with image mirroring
+# Test with local images - drone/git and drone/placeholder
+# Test with updated RBAC permissions
+# Test with uploaded alpine image
+# Test host volume mount
+# Test host volume mount again
+# Test after server config update
+# Test with emptyDir volume
+Trigger new build
+Trigger Kaniko build
+Retrigger build with correct image
+Trigger build test
+Test build
+Test registry mirror
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+xx
+a
+a
+a
+xx
+xx
+xx
diff --git a/gateway/pom.xml b/gateway/pom.xml
new file mode 100644
index 0000000..0caeac4
--- /dev/null
+++ b/gateway/pom.xml
@@ -0,0 +1,122 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.18
+
+
+ com.tuoheng
+ gateway
+ 0.0.1-SNAPSHOT
+ gateway
+ Spring Boot 2.7.x Servlet Gateway
+
+ 11
+ 2021.0.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+ 2021.0.5.0
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+ 2021.0.5.0
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+ 3.1.5
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-loadbalancer
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.8
+
+
+
+ prepare-agent
+
+
+
+ report
+ compile
+
+ report
+
+
+
+
+
+
+
+ org.sonarsource.scanner.maven
+ sonar-maven-plugin
+ 3.9.1.2184
+
+
+
+
\ No newline at end of file
diff --git a/gateway/settings.xml b/gateway/settings.xml
new file mode 100644
index 0000000..7749838
--- /dev/null
+++ b/gateway/settings.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ aliyun-all
+ Aliyun Maven (all)
+ https://maven.aliyun.com/repository/public
+ *
+
+
+
diff --git a/gateway/src/main/java/com/tuoheng/gateway/GatewayApplication.java b/gateway/src/main/java/com/tuoheng/gateway/GatewayApplication.java
new file mode 100644
index 0000000..186d9ac
--- /dev/null
+++ b/gateway/src/main/java/com/tuoheng/gateway/GatewayApplication.java
@@ -0,0 +1,13 @@
+package com.tuoheng.gateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+@SpringBootApplication
+@EnableDiscoveryClient
+public class GatewayApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(GatewayApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/gateway/src/main/java/com/tuoheng/gateway/config/SecurityConfig.java b/gateway/src/main/java/com/tuoheng/gateway/config/SecurityConfig.java
new file mode 100644
index 0000000..b65652c
--- /dev/null
+++ b/gateway/src/main/java/com/tuoheng/gateway/config/SecurityConfig.java
@@ -0,0 +1,22 @@
+package com.tuoheng.gateway.config;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+
+@Configuration
+@EnableWebFluxSecurity
+public class SecurityConfig {
+ @Bean
+ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
+ http
+ .authorizeExchange(exchanges -> exchanges
+ .pathMatchers("/a/**").authenticated()
+ .pathMatchers("/b/**").authenticated()
+ .anyExchange().permitAll()
+ )
+ .oauth2ResourceServer(oauth2 -> oauth2.jwt());
+ return http.build();
+ }
+}
\ No newline at end of file
diff --git a/gateway/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java b/gateway/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java
new file mode 100644
index 0000000..ff17242
--- /dev/null
+++ b/gateway/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java
@@ -0,0 +1,77 @@
+package com.tuoheng.gateway.filter;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.security.core.context.ReactiveSecurityContextHolder;
+
+@Component
+public class JwtPermissionFilter implements GlobalFilter, Ordered {
+
+
+ /**
+ * 这边能获取到Token里面的值
+ * @param exchange
+ * @param chain
+ * @return
+ */
+ @Override
+ public Mono filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) {
+
+ // 获取完整的请求URL
+ String fullUrl = exchange.getRequest().getURI().toString();
+ System.out.println("用户访问的完整URL: " + fullUrl);
+
+ // 获取请求的path
+ String path = exchange.getRequest().getPath().toString();
+ System.out.println("用户访问的path: " + path);
+
+ // 获取请求的host
+ String host = exchange.getRequest().getHeaders().getHost().toString();
+ System.out.println("用户访问的host: " + host);
+
+ String hostName = exchange.getRequest().getHeaders().getHost().getHostName();
+ System.out.println("用户访问的域名: " + hostName);
+
+ // 获取Referer(如果有)
+ String referer = exchange.getRequest().getHeaders().getFirst("Referer");
+ System.out.println("Referer: " + referer);
+// 从Spring Security上下文获取JWT
+ return ReactiveSecurityContextHolder.getContext()
+ .flatMap(securityContext -> {
+ if (securityContext.getAuthentication() instanceof JwtAuthenticationToken) {
+ JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) securityContext.getAuthentication();
+ Jwt jwt = jwtAuth.getToken();
+ String username = jwt.getClaimAsString("username");
+ String clientId = jwt.getClaimAsString("client_id");
+ String tenantCode = jwt.getClaimAsString("tenant_code");
+ String authorities = jwt.getClaimAsString("clientIds");
+
+ // 你可以在这里做权限判断
+ System.out.println("网关解析到token字段:");
+ System.out.println("用户名: " + username);
+ System.out.println("客户端ID: " + clientId);
+ System.out.println("租户代码: " + tenantCode);
+ System.out.println("用户权限: " + authorities);
+
+ //该域名没有权限
+ if(!authorities.contains(hostName)){
+ exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.FORBIDDEN);
+ return exchange.getResponse().setComplete();
+ }
+ }
+ return chain.filter(exchange);
+ });
+
+ }
+
+ @Override
+ public int getOrder() {
+ return -1; // 优先级高
+ }
+}
\ No newline at end of file
diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties
new file mode 100644
index 0000000..b5475db
--- /dev/null
+++ b/gateway/src/main/resources/application.properties
@@ -0,0 +1,26 @@
+server.port=8080
+
+# 应用名称
+spring.application.name=gateway
+
+# Nacos 服务发现配置
+spring.cloud.nacos.discovery.server-addr=nacos:8848
+spring.cloud.nacos.discovery.namespace=public
+spring.cloud.nacos.discovery.group=DEFAULT_GROUP
+spring.cloud.nacos.discovery.enabled=true
+
+# Gateway 路由配置 - 使用服务发现
+spring.cloud.gateway.routes[0].id=resource-server-a
+spring.cloud.gateway.routes[0].uri=lb://aserver
+spring.cloud.gateway.routes[0].predicates[0]=Path=/a/**
+spring.cloud.gateway.routes[0].filters[0]=RewritePath=/a/(?.*), /api/${segment}
+spring.cloud.gateway.routes[0].filters[1]=TokenRelay
+
+spring.cloud.gateway.routes[1].id=resource-server-b
+spring.cloud.gateway.routes[1].uri=lb://bserver
+spring.cloud.gateway.routes[1].predicates[0]=Path=/b/**
+spring.cloud.gateway.routes[1].filters[0]=RewritePath=/b/(?.*), /api/${segment}
+spring.cloud.gateway.routes[1].filters[1]=TokenRelay
+
+# OAuth2 配置
+spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://oidc:8080/oauth2/jwks
\ No newline at end of file
diff --git a/gateway/src/main/resources/bootstrap.properties b/gateway/src/main/resources/bootstrap.properties
new file mode 100644
index 0000000..4bc2f0c
--- /dev/null
+++ b/gateway/src/main/resources/bootstrap.properties
@@ -0,0 +1,9 @@
+# Bootstrap configuration for Nacos config center
+spring.application.name=gateway
+
+# Nacos config center configuration
+spring.cloud.nacos.config.server-addr=nacos:8848
+spring.cloud.nacos.config.namespace=public
+spring.cloud.nacos.config.group=DEFAULT_GROUP
+spring.cloud.nacos.config.file-extension=properties
+spring.cloud.nacos.config.enabled=true
diff --git a/thingsboard-gateway-ws-demo b/thingsboard-gateway-ws-demo
index 18c739a..9bab576 160000
--- a/thingsboard-gateway-ws-demo
+++ b/thingsboard-gateway-ws-demo
@@ -1 +1 @@
-Subproject commit 18c739a10b45f3ea3365a561181f92df40dd9cbb
+Subproject commit 9bab576c5c96627aae9205a83282136d7ff00ab4