新增user服务和gateway服务

This commit is contained in:
jiangh277
2025-08-12 19:01:26 +08:00
parent 957462b60b
commit aa42f35254
29 changed files with 1047 additions and 1 deletions

View File

@@ -0,0 +1,79 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.timeline</groupId>
<artifactId>timeline</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>timeline-gateway-service</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Cloud LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- JWT支持 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- 公共模块 -->
<dependency>
<groupId>com.timeline</groupId>
<artifactId>timeline-component-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,14 @@
// TimelineGatewayApplication.java
package com.timeline.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class TimelineGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(TimelineGatewayApplication.class, args);
}
}

View File

@@ -0,0 +1,25 @@
// GatewayConfig.java
package com.timeline.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class GatewayConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}

View File

@@ -0,0 +1,103 @@
package com.timeline.gateway.filter;
import com.timeline.common.response.ResponseEntity;
import com.timeline.common.response.ResponseEnum;
import com.timeline.gateway.util.JwtUtils;
import io.jsonwebtoken.Claims;
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.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
@Component
@Slf4j
public class AuthenticationFilter implements GlobalFilter, Ordered {
@Autowired
private JwtUtils jwtUtils;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 白名单路径不需要认证
if (isWhitelisted(path)) {
return chain.filter(exchange);
}
// 从请求头中获取token
String token = extractToken(request);
if (token == null || token.isEmpty()) {
return handleUnauthorized(exchange, ResponseEnum.UNAUTHORIZED.getMessage());
}
// 验证token
if (!jwtUtils.validateToken(token)) {
return handleUnauthorized(exchange, "无效的Token");
}
Claims claims = jwtUtils.getClaimsFromToken(token);
if (claims == null || jwtUtils.isTokenExpired(claims)) {
return handleUnauthorized(exchange, "Token已过期");
}
// 将用户信息添加到请求头中传递给下游服务
String userId = jwtUtils.getUserIdFromClaims(claims);
String username = jwtUtils.getUsernameFromClaims(claims);
ServerHttpRequest mutatedRequest = request.mutate()
.header("X-User-Id", userId)
.header("X-Username", username)
.build();
ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
return chain.filter(mutatedExchange);
}
@Override
public int getOrder() {
return -100; // 设置过滤器优先级,确保在其他过滤器之前执行
}
private boolean isWhitelisted(String path) {
// 白名单路径不需要认证
return path.startsWith("/auth/login") ||
path.startsWith("/auth/register") ||
path.startsWith("/ping") ||
path.startsWith("/actuator");
}
private String extractToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
private Mono<Void> handleUnauthorized(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
ResponseEntity<String> responseEntity = ResponseEntity.error(ResponseEnum.UNAUTHORIZED.getCode(), message);
String responseBody = com.alibaba.fastjson.JSON.toJSONString(responseEntity);
byte[] bytes = responseBody.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
}
}

View File

@@ -0,0 +1,56 @@
// JwtUtils.java
package com.timeline.gateway.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class JwtUtils {
@Value("${jwt.secret:timelineSecretKey}")
private String secret;
public Claims getClaimsFromToken(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(secret)
.build()
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
return null;
}
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(secret).build().parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
public boolean isTokenExpired(Claims claims) {
if (claims == null) {
return true;
}
return claims.getExpiration().before(new java.util.Date());
}
public String getUserIdFromClaims(Claims claims) {
if (claims == null) {
return null;
}
return claims.get("userId", String.class);
}
public String getUsernameFromClaims(Claims claims) {
if (claims == null) {
return null;
}
return claims.getSubject();
}
}

View File

@@ -0,0 +1,27 @@
# application.properties
spring.application.name=timeline-gateway
server.port=30000
# ????
spring.cloud.gateway.routes[0].id=story-service
spring.cloud.gateway.routes[0].uri=lb://timeline.story
spring.cloud.gateway.routes[0].predicates[0]=Path=/story/**
spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1
spring.cloud.gateway.routes[1].id=file-service
spring.cloud.gateway.routes[1].uri=lb://timeline.file
spring.cloud.gateway.routes[1].predicates[0]=Path=/file/**
spring.cloud.gateway.routes[1].filters[0]=StripPrefix=1
# JWT??
jwt.secret=timelineSecretKey
jwt.expiration=86400
# Actuator??
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
# ????
logging.level.org.springframework.cloud.gateway=DEBUG
logging.level.com.timeline.gateway=DEBUG