Skip to content

Commit 6470004

Browse files
authored
feature: add user login and signup functions (#244)
* feature: add user login and signup functions * fix: change the collection template "OBS归集模板" to "S3存储归集模板" * refactor: refactor the generate_datx_config function
1 parent cc1d68c commit 6470004

34 files changed

Lines changed: 629 additions & 606 deletions

File tree

backend/api-gateway/pom.xml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,31 @@
2020
<properties>
2121
<spring-boot.version>3.5.6</spring-boot.version>
2222
<spring-cloud.version>2025.0.0</spring-cloud.version>
23+
<jjwt.version>0.11.5</jjwt.version>
2324
</properties>
2425

2526
<dependencies>
27+
<dependency>
28+
<groupId>com.datamate</groupId>
29+
<artifactId>domain-common</artifactId>
30+
<version>${project.version}</version>
31+
<exclusions>
32+
<exclusion>
33+
<artifactId>spring-boot-starter-web</artifactId>
34+
<groupId>org.springframework.boot</groupId>
35+
</exclusion>
36+
</exclusions>
37+
</dependency>
2638
<dependency>
2739
<groupId>org.springframework.cloud</groupId>
2840
<artifactId>spring-cloud-starter-gateway</artifactId>
2941
</dependency>
42+
43+
<!-- 响应式安全 -->
44+
<dependency>
45+
<groupId>org.springframework.boot</groupId>
46+
<artifactId>spring-boot-starter-security</artifactId>
47+
</dependency>
3048
<!-- Log4j2 API -->
3149
<dependency>
3250
<groupId>org.springframework.boot</groupId>
@@ -36,6 +54,38 @@
3654
<groupId>com.alibaba.fastjson2</groupId>
3755
<artifactId>fastjson2</artifactId>
3856
</dependency>
57+
<dependency>
58+
<groupId>com.baomidou</groupId>
59+
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
60+
</dependency>
61+
<dependency>
62+
<groupId>com.baomidou</groupId>
63+
<artifactId>mybatis-plus-jsqlparser</artifactId>
64+
</dependency>
65+
<!-- Database -->
66+
<dependency>
67+
<groupId>org.postgresql</groupId>
68+
<artifactId>postgresql</artifactId>
69+
</dependency>
70+
71+
<!-- 使用新版本的 JJWT -->
72+
<dependency>
73+
<groupId>io.jsonwebtoken</groupId>
74+
<artifactId>jjwt-api</artifactId>
75+
<version>${jjwt.version}</version>
76+
</dependency>
77+
<dependency>
78+
<groupId>io.jsonwebtoken</groupId>
79+
<artifactId>jjwt-impl</artifactId>
80+
<version>${jjwt.version}</version>
81+
<scope>runtime</scope>
82+
</dependency>
83+
<dependency>
84+
<groupId>io.jsonwebtoken</groupId>
85+
<artifactId>jjwt-jackson</artifactId> <!-- 或 jjwt-gson -->
86+
<version>${jjwt.version}</version>
87+
<scope>runtime</scope>
88+
</dependency>
3989
</dependencies>
4090

4191
<build>

backend/api-gateway/src/main/java/com/datamate/gateway/ApiGatewayApplication.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
package com.datamate.gateway;
22

3+
import org.mybatis.spring.annotation.MapperScan;
34
import org.springframework.boot.SpringApplication;
45
import org.springframework.boot.autoconfigure.SpringBootApplication;
56
import org.springframework.cloud.gateway.route.RouteLocator;
67
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
78
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.ComponentScan;
810

911
/**
1012
* API Gateway & Auth Service Application
1113
* 统一的API网关和认证授权微服务
1214
* 提供路由、鉴权、限流等功能
1315
*/
1416
@SpringBootApplication
17+
@ComponentScan(basePackages = {"com.datamate"})
18+
@MapperScan(basePackages = {"com.datamate.**.mapper"})
1519
public class ApiGatewayApplication {
1620

1721
public static void main(String[] args) {
@@ -47,6 +51,10 @@ public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
4751
.filters(f -> f.stripPrefix(1).prefixPath("/api"))
4852
.uri("http://deer-flow-backend:8000"))
4953

54+
// 网关服务(用户)
55+
.route("gateway", r -> r.path("/api/user/**")
56+
.uri("http://localhost:8080"))
57+
5058
// 其他后端服务
5159
.route("default", r -> r.path("/api/**")
5260
.uri("http://datamate-backend:8080"))
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.datamate.gateway.application;
2+
3+
import com.datamate.gateway.domain.entity.User;
4+
import com.datamate.gateway.domain.service.UserService;
5+
import com.datamate.gateway.interfaces.dto.LoginRequest;
6+
import com.datamate.gateway.interfaces.dto.LoginResponse;
7+
import com.datamate.gateway.interfaces.dto.RegisterRequest;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.stereotype.Service;
11+
import org.springframework.transaction.annotation.Transactional;
12+
13+
import java.util.Optional;
14+
15+
/**
16+
* UserApplicationServices
17+
*
18+
* @since 2026/1/14
19+
*/
20+
@Slf4j
21+
@Service
22+
@Transactional
23+
@RequiredArgsConstructor
24+
public class UserApplicationService {
25+
private final UserService userService;
26+
27+
public Optional<LoginResponse> login(LoginRequest loginRequest) {
28+
User user = new User();
29+
user.setUsername(loginRequest.getUsername());
30+
user.setPassword(loginRequest.getPassword());
31+
32+
Optional<User> authenticatedUser = userService.authenticate(user);
33+
if (authenticatedUser.isPresent()) {
34+
User userEntity = authenticatedUser.get();
35+
return Optional.of(convertToLoginResponse(userEntity));
36+
}
37+
return Optional.empty();
38+
}
39+
40+
/**
41+
* Register a new user
42+
*
43+
* @param registerRequest registration request
44+
* @return LoginResponse with user details and token if registration successful, empty otherwise
45+
*/
46+
public Optional<LoginResponse> register(RegisterRequest registerRequest) {
47+
return userService.register(registerRequest)
48+
.map(this::convertToLoginResponse);
49+
}
50+
51+
private LoginResponse convertToLoginResponse(User user) {
52+
return LoginResponse.builder()
53+
.id(user.getId())
54+
.username(user.getUsername())
55+
.email(user.getEmail())
56+
.token(user.getToken())
57+
.build();
58+
}
59+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.datamate.gateway.common.config;
2+
3+
import jakarta.annotation.PostConstruct;
4+
import lombok.Getter;
5+
import lombok.Setter;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.boot.context.properties.ConfigurationProperties;
8+
import org.springframework.context.annotation.Configuration;
9+
10+
/**
11+
* JwtConfig
12+
*
13+
* @since 2026/1/14
14+
*/
15+
@Getter
16+
@Setter
17+
@Slf4j
18+
@Configuration
19+
@ConfigurationProperties(prefix = "datamate.jwt")
20+
public class JwtConfig {
21+
private String secret;
22+
23+
@PostConstruct
24+
public void validate() {
25+
if (secret == null || secret.trim().isEmpty()) {
26+
throw new IllegalStateException(
27+
"""
28+
JWT secret is required. Please configure datamate.jwt.secret
29+
Options:
30+
1. Add to application.yml:
31+
datamate:
32+
jwt:
33+
secret: your-strong-secret-key-here
34+
2. Set environment variable:
35+
export JWT_SECRET=your-strong-secret-key-here
36+
3. Run with system property:
37+
-Ddatamate.jwt.secret=your-strong-secret-key-here"""
38+
);
39+
}
40+
41+
// 额外验证
42+
if (secret.length() < 32) {
43+
log.warn("\n⚠️ JWT secret is only {} characters. For security, use at least 32 characters.\n", secret.length());
44+
}
45+
}
46+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.datamate.gateway.common.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
6+
import org.springframework.security.config.web.server.ServerHttpSecurity;
7+
import org.springframework.security.web.server.SecurityWebFilterChain;
8+
9+
/**
10+
* 安全配置 - 暂时禁用所有认证
11+
*/
12+
@Configuration
13+
@EnableWebFluxSecurity
14+
public class SecurityConfig {
15+
16+
@Bean
17+
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) throws Exception {
18+
http.csrf(ServerHttpSecurity.CsrfSpec::disable)
19+
.authorizeExchange(exchange ->
20+
exchange.pathMatchers("**").permitAll() // 允许所有请求无需认证
21+
);
22+
23+
return http.build();
24+
}
25+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.datamate.gateway.common.filter;
2+
3+
import com.datamate.common.infrastructure.common.Response;
4+
import com.datamate.common.infrastructure.exception.CommonErrorCode;
5+
import com.datamate.gateway.domain.service.UserService;
6+
import com.fasterxml.jackson.core.JsonProcessingException;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.beans.factory.annotation.Value;
11+
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
12+
import org.springframework.cloud.gateway.filter.GlobalFilter;
13+
import org.springframework.core.io.buffer.DataBuffer;
14+
import org.springframework.http.HttpStatus;
15+
import org.springframework.http.MediaType;
16+
import org.springframework.http.server.reactive.ServerHttpRequest;
17+
import org.springframework.http.server.reactive.ServerHttpResponse;
18+
import org.springframework.stereotype.Component;
19+
import org.springframework.web.server.ServerWebExchange;
20+
import reactor.core.publisher.Mono;
21+
22+
import java.nio.charset.StandardCharsets;
23+
24+
/**
25+
* 用户信息过滤器
26+
*
27+
*/
28+
@Slf4j
29+
@Component
30+
@RequiredArgsConstructor
31+
public class UserContextFilter implements GlobalFilter {
32+
private static final String AUTH_HEADER = "Authorization";
33+
34+
private static final String TOKEN_PREFIX = "Bearer ";
35+
36+
private final UserService userService;
37+
38+
@Value("${datamate.jwt.enable:false}")
39+
private Boolean jwtEnable;
40+
41+
@Override
42+
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
43+
ServerHttpRequest request = exchange.getRequest();
44+
String path = request.getURI().getPath();
45+
if (path.equals("/api/user/login") || path.equals("/api/user/signup")) {
46+
return chain.filter(exchange);
47+
}
48+
try {
49+
if (!jwtEnable) {
50+
return chain.filter(exchange);
51+
}
52+
// Get token from Authorization header
53+
String authHeader = request.getHeaders().getFirst(AUTH_HEADER);
54+
if (authHeader == null || !authHeader.startsWith(TOKEN_PREFIX)) {
55+
return sendUnauthorizedResponse(exchange);
56+
}
57+
String token = authHeader.substring(TOKEN_PREFIX.length());
58+
if (!userService.validateToken(token)) {
59+
return sendUnauthorizedResponse(exchange);
60+
}
61+
return chain.filter(exchange);
62+
} catch (Exception e) {
63+
log.error("get current user info error", e);
64+
return sendUnauthorizedResponse(exchange);
65+
}
66+
}
67+
68+
private Mono<Void> sendUnauthorizedResponse(ServerWebExchange exchange) {
69+
ServerHttpResponse response = exchange.getResponse();
70+
response.setStatusCode(HttpStatus.UNAUTHORIZED);
71+
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
72+
ObjectMapper objectMapper = new ObjectMapper();
73+
byte[] bytes;
74+
try {
75+
bytes = objectMapper.writeValueAsString(Response.error(CommonErrorCode.UNAUTHORIZED)).getBytes(StandardCharsets.UTF_8);
76+
} catch (JsonProcessingException e) {
77+
String responseBody = "{\"code\":401,\"message\":\"登录失败:用户名或密码错误\",\"data\":null}";
78+
bytes = responseBody.getBytes(StandardCharsets.UTF_8);
79+
}
80+
DataBuffer buffer = response.bufferFactory().wrap(bytes);
81+
return response.writeWith(Mono.just(buffer));
82+
}
83+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.datamate.gateway.domain.entity;
2+
3+
import com.baomidou.mybatisplus.annotation.TableField;
4+
import com.baomidou.mybatisplus.annotation.TableName;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
import lombok.Setter;
9+
10+
import java.time.LocalDateTime;
11+
12+
/**
13+
* 用户
14+
*
15+
* @since 2026/1/12
16+
*/
17+
@Getter
18+
@Setter
19+
@NoArgsConstructor
20+
@AllArgsConstructor
21+
@TableName(value = "users", autoResultMap = true)
22+
public class User {
23+
private Long id;
24+
private String username;
25+
private String email;
26+
private String passwordHash;
27+
private String fullName;
28+
private String role;
29+
private boolean enabled;
30+
private LocalDateTime lastLoginAt;
31+
32+
@TableField(exist = false)
33+
private String password;
34+
35+
@TableField(exist = false)
36+
private String token;
37+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.datamate.gateway.domain.repository;
2+
3+
import com.baomidou.mybatisplus.extension.repository.IRepository;
4+
import com.datamate.gateway.domain.entity.User;
5+
6+
/**
7+
* UserRepository
8+
*
9+
* @since 2026/1/12
10+
*/
11+
public interface UserRepository extends IRepository<User> {
12+
}

0 commit comments

Comments
 (0)