From 73325667d0bba834e1404f1f98be929bf4afd5dd Mon Sep 17 00:00:00 2001 From: uname <2986773479@qq.com> Date: Wed, 14 Jan 2026 17:28:23 +0800 Subject: [PATCH 1/3] feature: add user login and signup functions --- backend/api-gateway/pom.xml | 50 +++++++ .../gateway/ApiGatewayApplication.java | 8 ++ .../application/UserApplicationService.java | 59 ++++++++ .../gateway/common/config/JwtConfig.java | 46 +++++++ .../gateway/common/config/SecurityConfig.java | 25 ++++ .../common/filter/UserContextFilter.java | 83 ++++++++++++ .../datamate/gateway/domain/entity/User.java | 37 +++++ .../domain/repository/UserRepository.java | 12 ++ .../gateway/domain/service/UserService.java | 126 ++++++++++++++++++ .../gateway/filter/UserContextFilter.java | 34 ----- .../infrastructure/mapper/UserMapper.java | 14 ++ .../repository/UserRepositoryImpl.java | 18 +++ .../gateway/interfaces/dto/LoginRequest.java | 16 +++ .../gateway/interfaces/dto/LoginResponse.java | 20 +++ .../interfaces/dto/RegisterRequest.java | 24 ++++ .../interfaces/rest/UserController.java | 51 +++++++ .../src/main/resources/application.yml | 16 +++ backend/pom.xml | 4 + .../services/data-annotation-service/pom.xml | 96 ------------- .../services/data-evaluation-service/pom.xml | 87 ------------ .../services/data-synthesis-service/pom.xml | 87 ------------ .../services/execution-engine-service/pom.xml | 91 ------------- backend/services/main-application/pom.xml | 30 ----- .../pipeline-orchestration-service/pom.xml | 91 ------------- backend/services/pom.xml | 10 -- backend/services/rag-query-service/pom.xml | 63 --------- .../exception/CommonErrorCode.java | 4 +- deployment/docker/datamate/docker-compose.yml | 2 + deployment/helm/datamate/values.yaml | 3 + scripts/db/data-management-init.sql | 3 +- scripts/images/backend/Dockerfile | 2 +- scripts/images/gateway/Dockerfile | 2 +- 32 files changed, 620 insertions(+), 594 deletions(-) create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/application/UserApplicationService.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/common/config/JwtConfig.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/common/config/SecurityConfig.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/UserContextFilter.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/domain/entity/User.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/domain/repository/UserRepository.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/domain/service/UserService.java delete mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/filter/UserContextFilter.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/mapper/UserMapper.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/repository/UserRepositoryImpl.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/LoginRequest.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/LoginResponse.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/RegisterRequest.java create mode 100644 backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/rest/UserController.java delete mode 100644 backend/services/data-annotation-service/pom.xml delete mode 100644 backend/services/data-evaluation-service/pom.xml delete mode 100644 backend/services/data-synthesis-service/pom.xml delete mode 100644 backend/services/execution-engine-service/pom.xml delete mode 100644 backend/services/pipeline-orchestration-service/pom.xml delete mode 100644 backend/services/rag-query-service/pom.xml diff --git a/backend/api-gateway/pom.xml b/backend/api-gateway/pom.xml index 067e6af2f..98a8066e6 100644 --- a/backend/api-gateway/pom.xml +++ b/backend/api-gateway/pom.xml @@ -20,13 +20,31 @@ 3.5.6 2025.0.0 + 0.11.5 + + com.datamate + domain-common + ${project.version} + + + spring-boot-starter-web + org.springframework.boot + + + org.springframework.cloud spring-cloud-starter-gateway + + + + org.springframework.boot + spring-boot-starter-security + org.springframework.boot @@ -36,6 +54,38 @@ com.alibaba.fastjson2 fastjson2 + + com.baomidou + mybatis-plus-spring-boot3-starter + + + com.baomidou + mybatis-plus-jsqlparser + + + + org.postgresql + postgresql + + + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + runtime + diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/ApiGatewayApplication.java b/backend/api-gateway/src/main/java/com/datamate/gateway/ApiGatewayApplication.java index caac81e74..722f83a0a 100644 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/ApiGatewayApplication.java +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/ApiGatewayApplication.java @@ -1,10 +1,12 @@ package com.datamate.gateway; +import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; /** * API Gateway & Auth Service Application @@ -12,6 +14,8 @@ * 提供路由、鉴权、限流等功能 */ @SpringBootApplication +@ComponentScan(basePackages = {"com.datamate"}) +@MapperScan(basePackages = {"com.datamate.**.mapper"}) public class ApiGatewayApplication { public static void main(String[] args) { @@ -47,6 +51,10 @@ public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { .filters(f -> f.stripPrefix(1).prefixPath("/api")) .uri("http://deer-flow-backend:8000")) + // 网关服务(用户) + .route("gateway", r -> r.path("/api/user/**") + .uri("http://localhost:8080")) + // 其他后端服务 .route("default", r -> r.path("/api/**") .uri("http://datamate-backend:8080")) diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/application/UserApplicationService.java b/backend/api-gateway/src/main/java/com/datamate/gateway/application/UserApplicationService.java new file mode 100644 index 000000000..b02c18945 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/application/UserApplicationService.java @@ -0,0 +1,59 @@ +package com.datamate.gateway.application; + +import com.datamate.gateway.domain.entity.User; +import com.datamate.gateway.domain.service.UserService; +import com.datamate.gateway.interfaces.dto.LoginRequest; +import com.datamate.gateway.interfaces.dto.LoginResponse; +import com.datamate.gateway.interfaces.dto.RegisterRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +/** + * UserApplicationServices + * + * @since 2026/1/14 + */ +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class UserApplicationService { + private final UserService userService; + + public Optional login(LoginRequest loginRequest) { + User user = new User(); + user.setUsername(loginRequest.getUsername()); + user.setPassword(loginRequest.getPassword()); + + Optional authenticatedUser = userService.authenticate(user); + if (authenticatedUser.isPresent()) { + User userEntity = authenticatedUser.get(); + return Optional.of(convertToLoginResponse(userEntity)); + } + return Optional.empty(); + } + + /** + * Register a new user + * + * @param registerRequest registration request + * @return LoginResponse with user details and token if registration successful, empty otherwise + */ + public Optional register(RegisterRequest registerRequest) { + return userService.register(registerRequest) + .map(this::convertToLoginResponse); + } + + private LoginResponse convertToLoginResponse(User user) { + return LoginResponse.builder() + .id(user.getId()) + .username(user.getUsername()) + .email(user.getEmail()) + .token(user.getToken()) + .build(); + } +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/config/JwtConfig.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/config/JwtConfig.java new file mode 100644 index 000000000..c57ecc7af --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/config/JwtConfig.java @@ -0,0 +1,46 @@ +package com.datamate.gateway.common.config; + +import jakarta.annotation.PostConstruct; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * JwtConfig + * + * @since 2026/1/14 + */ +@Getter +@Setter +@Slf4j +@Configuration +@ConfigurationProperties(prefix = "datamate.jwt") +public class JwtConfig { + private String secret; + + @PostConstruct + public void validate() { + if (secret == null || secret.trim().isEmpty()) { + throw new IllegalStateException( + """ + JWT secret is required. Please configure datamate.jwt.secret + Options: + 1. Add to application.yml: + datamate: + jwt: + secret: your-strong-secret-key-here + 2. Set environment variable: + export JWT_SECRET=your-strong-secret-key-here + 3. Run with system property: + -Ddatamate.jwt.secret=your-strong-secret-key-here""" + ); + } + + // 额外验证 + if (secret.length() < 32) { + log.warn("\n⚠️ JWT secret is only {} characters. For security, use at least 32 characters.\n", secret.length()); + } + } +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/config/SecurityConfig.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/config/SecurityConfig.java new file mode 100644 index 000000000..ab1bac242 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/config/SecurityConfig.java @@ -0,0 +1,25 @@ +package com.datamate.gateway.common.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 filterChain(ServerHttpSecurity http) throws Exception { + http.csrf(ServerHttpSecurity.CsrfSpec::disable) + .authorizeExchange(exchange -> + exchange.pathMatchers("**").permitAll() // 允许所有请求无需认证 + ); + + return http.build(); + } +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/UserContextFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/UserContextFilter.java new file mode 100644 index 000000000..950dcb0ec --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/common/filter/UserContextFilter.java @@ -0,0 +1,83 @@ +package com.datamate.gateway.common.filter; + +import com.datamate.common.infrastructure.common.Response; +import com.datamate.common.infrastructure.exception.CommonErrorCode; +import com.datamate.gateway.domain.service.UserService; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +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; + +/** + * 用户信息过滤器 + * + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class UserContextFilter implements GlobalFilter { + private static final String AUTH_HEADER = "Authorization"; + + private static final String TOKEN_PREFIX = "Bearer "; + + private final UserService userService; + + @Value("${datamate.jwt.enable:false}") + private Boolean jwtEnable; + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + String path = request.getURI().getPath(); + if (path.equals("/api/user/login") || path.equals("/api/user/signup")) { + return chain.filter(exchange); + } + try { + if (!jwtEnable) { + return chain.filter(exchange); + } + // Get token from Authorization header + String authHeader = request.getHeaders().getFirst(AUTH_HEADER); + if (authHeader == null || !authHeader.startsWith(TOKEN_PREFIX)) { + return sendUnauthorizedResponse(exchange); + } + String token = authHeader.substring(TOKEN_PREFIX.length()); + if (!userService.validateToken(token)) { + return sendUnauthorizedResponse(exchange); + } + return chain.filter(exchange); + } catch (Exception e) { + log.error("get current user info error", e); + return sendUnauthorizedResponse(exchange); + } + } + + private Mono sendUnauthorizedResponse(ServerWebExchange exchange) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + response.getHeaders().setContentType(MediaType.APPLICATION_JSON); + ObjectMapper objectMapper = new ObjectMapper(); + byte[] bytes; + try { + bytes = objectMapper.writeValueAsString(Response.error(CommonErrorCode.UNAUTHORIZED)).getBytes(StandardCharsets.UTF_8); + } catch (JsonProcessingException e) { + String responseBody = "{\"code\":401,\"message\":\"登录失败:用户名或密码错误\",\"data\":null}"; + bytes = responseBody.getBytes(StandardCharsets.UTF_8); + } + DataBuffer buffer = response.bufferFactory().wrap(bytes); + return response.writeWith(Mono.just(buffer)); + } +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/domain/entity/User.java b/backend/api-gateway/src/main/java/com/datamate/gateway/domain/entity/User.java new file mode 100644 index 000000000..cdc282a40 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/domain/entity/User.java @@ -0,0 +1,37 @@ +package com.datamate.gateway.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +/** + * 用户 + * + * @since 2026/1/12 + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@TableName(value = "users", autoResultMap = true) +public class User { + private Long id; + private String username; + private String email; + private String passwordHash; + private String fullName; + private String role; + private boolean enabled; + private LocalDateTime lastLoginAt; + + @TableField(exist = false) + private String password; + + @TableField(exist = false) + private String token; +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/domain/repository/UserRepository.java b/backend/api-gateway/src/main/java/com/datamate/gateway/domain/repository/UserRepository.java new file mode 100644 index 000000000..0254d1fb5 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/domain/repository/UserRepository.java @@ -0,0 +1,12 @@ +package com.datamate.gateway.domain.repository; + +import com.baomidou.mybatisplus.extension.repository.IRepository; +import com.datamate.gateway.domain.entity.User; + +/** + * UserRepository + * + * @since 2026/1/12 + */ +public interface UserRepository extends IRepository { +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/domain/service/UserService.java b/backend/api-gateway/src/main/java/com/datamate/gateway/domain/service/UserService.java new file mode 100644 index 000000000..938dfa575 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/domain/service/UserService.java @@ -0,0 +1,126 @@ +package com.datamate.gateway.domain.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.datamate.gateway.domain.entity.User; +import com.datamate.gateway.domain.repository.UserRepository; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.Optional; + +import com.datamate.gateway.interfaces.dto.RegisterRequest; + +/** + * UserService + * + * @since 2026/1/12 + */ +@Service +@RequiredArgsConstructor +public class UserService { + private final UserRepository userRepository; + + @Value("${datamate.jwt.expiration-seconds:3600}") + private long expirationSeconds; + + @Value("${datamate.jwt.secret}") + private String secret; + + /** + * Authenticate user with username and password + * + * @param user user to authenticate + * @return authenticated user with token if successful, empty otherwise + */ + public Optional authenticate(User user) { + LambdaQueryWrapper userWrapper = new LambdaQueryWrapper<>(); + userWrapper.eq(User::getUsername, user.getUsername()); + User userInDB = userRepository.getOne(userWrapper); + + if (userInDB != null && validPassword(user, userInDB)) { + String token = generateToken(userInDB); + userInDB.setToken(token); + return Optional.of(userInDB); + } + return Optional.empty(); + } + + private boolean validPassword(User user, User userInDB) { + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10); // cost=10 + return encoder.matches(user.getPassword(), userInDB.getPasswordHash()); + } + + private String generateToken(User user) { + long now = System.currentTimeMillis(); + return Jwts.builder() + .setSubject(user.getUsername()) + .claim("uid", user.getId()) + .claim("role", user.getRole()) + .setIssuedAt(new Date(now)) + .setExpiration(new Date(now + expirationSeconds * 1000)) + .signWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256) + .compact(); + } + + public boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(secret.getBytes()).parseClaimsJws(token); + return true; + } catch (JwtException | IllegalArgumentException ex) { + return false; + } + } + + /** + * Register a new user + * + * @param registerRequest registration request containing user details + * @return registered user with token if successful, empty if username or email already exists + */ + public Optional register(RegisterRequest registerRequest) { + // Check if username already exists + LambdaQueryWrapper usernameQuery = new LambdaQueryWrapper<>(); + usernameQuery.eq(User::getUsername, registerRequest.getUsername()); + if (userRepository.getOne(usernameQuery) != null) { + return Optional.empty(); + } + + // Check if email already exists + LambdaQueryWrapper emailQuery = new LambdaQueryWrapper<>(); + emailQuery.eq(User::getEmail, registerRequest.getEmail()); + if (userRepository.getOne(emailQuery) != null) { + return Optional.empty(); + } + + // Create new user + User user = new User(); + user.setUsername(registerRequest.getUsername()); + user.setEmail(registerRequest.getEmail()); + user.setFullName(registerRequest.getUsername()); + + // Encode password + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10); + user.setPasswordHash(encoder.encode(registerRequest.getPassword())); + + // Set default role and enabled status + user.setRole("USER"); + user.setEnabled(true); + + // Save user + userRepository.save(user); + + // Generate token + String token = generateToken(user); + user.setToken(token); + + return Optional.of(user); + } +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/filter/UserContextFilter.java b/backend/api-gateway/src/main/java/com/datamate/gateway/filter/UserContextFilter.java deleted file mode 100644 index d549a3999..000000000 --- a/backend/api-gateway/src/main/java/com/datamate/gateway/filter/UserContextFilter.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.datamate.gateway.filter; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -/** - * 用户信息过滤器 - * - */ -@Slf4j -@Component -public class UserContextFilter implements GlobalFilter { - @Value("${commercial.switch:false}") - private boolean isCommercial; - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - if (!isCommercial) { - return chain.filter(exchange); - } - try { - - } catch (Exception e) { - log.error("get current user info error", e); - return chain.filter(exchange); - } - return chain.filter(exchange); - } -} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/mapper/UserMapper.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/mapper/UserMapper.java new file mode 100644 index 000000000..bb548c0ff --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/mapper/UserMapper.java @@ -0,0 +1,14 @@ +package com.datamate.gateway.infrastructure.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.datamate.gateway.domain.entity.User; +import org.apache.ibatis.annotations.Mapper; + +/** + * UserMapper + * + * @since 2026/1/12 + */ +@Mapper +public interface UserMapper extends BaseMapper { +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/repository/UserRepositoryImpl.java b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/repository/UserRepositoryImpl.java new file mode 100644 index 000000000..6bd0c00fe --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/infrastructure/repository/UserRepositoryImpl.java @@ -0,0 +1,18 @@ +package com.datamate.gateway.infrastructure.repository; + +import com.baomidou.mybatisplus.extension.repository.CrudRepository; +import com.datamate.gateway.domain.entity.User; +import com.datamate.gateway.domain.repository.UserRepository; +import com.datamate.gateway.infrastructure.mapper.UserMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +/** + * UserRepositoryImpl + * + * @since 2026/1/12 + */ +@Repository +@RequiredArgsConstructor +public class UserRepositoryImpl extends CrudRepository implements UserRepository { +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/LoginRequest.java b/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/LoginRequest.java new file mode 100644 index 000000000..0b26c21f5 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/LoginRequest.java @@ -0,0 +1,16 @@ +package com.datamate.gateway.interfaces.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * Login Request DTO + */ +@Data +public class LoginRequest { + @NotBlank(message = "Username cannot be empty") + private String username; + + @NotBlank(message = "Password cannot be empty") + private String password; +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/LoginResponse.java b/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/LoginResponse.java new file mode 100644 index 000000000..7f088bd43 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/LoginResponse.java @@ -0,0 +1,20 @@ +package com.datamate.gateway.interfaces.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Login Response DTO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LoginResponse { + private Long id; + private String username; + private String email; + private String token; +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/RegisterRequest.java b/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/RegisterRequest.java new file mode 100644 index 000000000..6f47e5c2a --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/dto/RegisterRequest.java @@ -0,0 +1,24 @@ +package com.datamate.gateway.interfaces.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * Register Request DTO + */ +@Data +public class RegisterRequest { + @NotBlank(message = "Username cannot be empty") + @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") + private String username; + + @NotBlank(message = "Email cannot be empty") + @Email(message = "Email should be valid") + private String email; + + @NotBlank(message = "Password cannot be empty") + @Size(min = 6, max = 100, message = "Password must be between 6 and 100 characters") + private String password; +} diff --git a/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/rest/UserController.java b/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/rest/UserController.java new file mode 100644 index 000000000..b755be065 --- /dev/null +++ b/backend/api-gateway/src/main/java/com/datamate/gateway/interfaces/rest/UserController.java @@ -0,0 +1,51 @@ +package com.datamate.gateway.interfaces.rest; + +import com.datamate.common.infrastructure.common.IgnoreResponseWrap; +import com.datamate.common.infrastructure.common.Response; +import com.datamate.common.infrastructure.exception.CommonErrorCode; +import com.datamate.gateway.application.UserApplicationService; +import com.datamate.gateway.interfaces.dto.LoginRequest; +import com.datamate.gateway.interfaces.dto.LoginResponse; +import com.datamate.gateway.interfaces.dto.RegisterRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * UserController + * + * @since 2026/1/14 + */ +@Slf4j +@Validated +@RestController +@RequestMapping("/api/user") +@RequiredArgsConstructor +public class UserController { + private final UserApplicationService userApplicationService; + + @PostMapping("/login") + @IgnoreResponseWrap + public ResponseEntity> login(@Valid @RequestBody LoginRequest loginRequest) { + return userApplicationService.login(loginRequest) + .map(response -> ResponseEntity.ok(Response.ok(response))) + .orElseGet(() -> ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(Response.error(CommonErrorCode.UNAUTHORIZED))); + } + + @PostMapping("/signup") + @IgnoreResponseWrap + public ResponseEntity> register(@Valid @RequestBody RegisterRequest registerRequest) { + return userApplicationService.register(registerRequest) + .map(response -> ResponseEntity.ok(Response.ok(response))) + .orElseGet(() -> ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(Response.error(CommonErrorCode.SIGNUP_ERROR))); + } +} diff --git a/backend/api-gateway/src/main/resources/application.yml b/backend/api-gateway/src/main/resources/application.yml index 46908fe8d..a6603b6c9 100644 --- a/backend/api-gateway/src/main/resources/application.yml +++ b/backend/api-gateway/src/main/resources/application.yml @@ -17,6 +17,22 @@ spring: secure: true cluster-name: DEFAULT + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://datamate-database:5432/datamate?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: ${DB_USERNAME:postgres} + password: ${DB_PASSWORD:password} + hikari: + maximum-pool-size: 20 + minimum-idle: 5 + connection-timeout: 30000 + idle-timeout: 600000 + max-lifetime: 1800000 + +datamate: + jwt: + secret: ${JWT_SECRET} + expiration-seconds: 3600 # 服务器端口配置 server: port: 8080 # 必须有这个配置 \ No newline at end of file diff --git a/backend/pom.xml b/backend/pom.xml index f71a3b644..b5626074a 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -42,6 +42,10 @@ + + ./shared/domain-common + ./shared/security-common + services diff --git a/backend/services/data-annotation-service/pom.xml b/backend/services/data-annotation-service/pom.xml deleted file mode 100644 index 9652adbc1..000000000 --- a/backend/services/data-annotation-service/pom.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - 4.0.0 - - - com.datamate - services - 1.0.0-SNAPSHOT - ../pom.xml - - - data-annotation-service - Data Annotation Service - 数据标注服务 - - - - com.datamate - domain-common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-websocket - - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.0.4 - - - org.openapitools - jackson-databind-nullable - 0.2.6 - - - jakarta.validation - jakarta.validation-api - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.openapitools - openapi-generator-maven-plugin - 6.6.0 - - - - generate - - - ${project.basedir}/../../openapi/specs/data-annotation.yaml - spring - ${project.build.directory}/generated-sources/openapi - com.datamate.annotation.interfaces.api - com.datamate.annotation.interfaces.dto - - true - true - true - true - true - java8 - true - true - true - springdoc - - - - - - - - - diff --git a/backend/services/data-evaluation-service/pom.xml b/backend/services/data-evaluation-service/pom.xml deleted file mode 100644 index 718afb0af..000000000 --- a/backend/services/data-evaluation-service/pom.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - 4.0.0 - - - com.datamate - services - 1.0.0-SNAPSHOT - ../pom.xml - - - data-evaluation-service - Data Evaluation Service - 数据评估服务 - - - - com.datamate - domain-common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - - - org.openapitools - jackson-databind-nullable - - - jakarta.validation - jakarta.validation-api - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.openapitools - openapi-generator-maven-plugin - 6.6.0 - - - - generate - - - ${project.basedir}/../../openapi/specs/data-evaluation.yaml - spring - ${project.build.directory}/generated-sources/openapi - com.datamate.evaluation.interfaces.api - com.datamate.evaluation.interfaces.dto - - true - true - true - springdoc - - - - - - - - - - diff --git a/backend/services/data-synthesis-service/pom.xml b/backend/services/data-synthesis-service/pom.xml deleted file mode 100644 index c4e34007c..000000000 --- a/backend/services/data-synthesis-service/pom.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - 4.0.0 - - - com.datamate - services - 1.0.0-SNAPSHOT - ../pom.xml - - - data-synthesis-service - Data Synthesis Service - 数据合成服务 - - - - com.datamate - domain-common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - - - org.openapitools - jackson-databind-nullable - - - jakarta.validation - jakarta.validation-api - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.openapitools - openapi-generator-maven-plugin - 6.6.0 - - - - generate - - - ${project.basedir}/../../openapi/specs/data-synthesis.yaml - spring - ${project.build.directory}/generated-sources/openapi - com.datamate.synthesis.interfaces.api - com.datamate.synthesis.interfaces.dto - - true - true - true - springdoc - - - - - - - - - - diff --git a/backend/services/execution-engine-service/pom.xml b/backend/services/execution-engine-service/pom.xml deleted file mode 100644 index aeda8d56f..000000000 --- a/backend/services/execution-engine-service/pom.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - 4.0.0 - - - com.datamate - services - 1.0.0-SNAPSHOT - ../pom.xml - - - execution-engine-service - Execution Engine Service - 执行引擎服务 - - - - com.datamate - domain-common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-data-redis - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - - - org.openapitools - jackson-databind-nullable - - - jakarta.validation - jakarta.validation-api - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.openapitools - openapi-generator-maven-plugin - 6.6.0 - - - - generate - - - ${project.basedir}/../../openapi/specs/execution-engine.yaml - spring - ${project.build.directory}/generated-sources/openapi - com.datamate.execution.interfaces.api - com.datamate.execution.interfaces.dto - - true - true - true - springdoc - - - - - - - - - - diff --git a/backend/services/main-application/pom.xml b/backend/services/main-application/pom.xml index e7f871972..d49f05b04 100644 --- a/backend/services/main-application/pom.xml +++ b/backend/services/main-application/pom.xml @@ -66,31 +66,6 @@ data-cleaning-service ${project.version} - - com.datamate - data-synthesis-service - ${project.version} - - - com.datamate - data-annotation-service - ${project.version} - - - com.datamate - data-evaluation-service - ${project.version} - - - com.datamate - pipeline-orchestration-service - ${project.version} - - - com.datamate - execution-engine-service - ${project.version} - @@ -98,11 +73,6 @@ rag-indexer-service ${project.version} - - com.datamate - rag-query-service - ${project.version} - diff --git a/backend/services/pipeline-orchestration-service/pom.xml b/backend/services/pipeline-orchestration-service/pom.xml deleted file mode 100644 index 53ca81006..000000000 --- a/backend/services/pipeline-orchestration-service/pom.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - 4.0.0 - - - com.datamate - services - 1.0.0-SNAPSHOT - ../pom.xml - - - pipeline-orchestration-service - Pipeline Orchestration Service - 流程编排服务 - - - - com.datamate - domain-common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-data-redis - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - - - org.openapitools - jackson-databind-nullable - - - jakarta.validation - jakarta.validation-api - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.openapitools - openapi-generator-maven-plugin - 6.6.0 - - - - generate - - - ${project.basedir}/../../openapi/specs/pipeline-orchestration.yaml - spring - ${project.build.directory}/generated-sources/openapi - com.datamate.pipeline.interfaces.api - com.datamate.pipeline.interfaces.dto - - true - true - true - springdoc - - - - - - - - - - diff --git a/backend/services/pom.xml b/backend/services/pom.xml index 912227210..f103535b9 100644 --- a/backend/services/pom.xml +++ b/backend/services/pom.xml @@ -18,23 +18,13 @@ Services - - ../shared/domain-common - ../shared/security-common - data-management-service operator-market-service data-cleaning-service - data-synthesis-service - data-annotation-service - data-evaluation-service - pipeline-orchestration-service - execution-engine-service rag-indexer-service - rag-query-service main-application diff --git a/backend/services/rag-query-service/pom.xml b/backend/services/rag-query-service/pom.xml deleted file mode 100644 index b8e0df4a6..000000000 --- a/backend/services/rag-query-service/pom.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - 4.0.0 - - - com.datamate - services - 1.0.0-SNAPSHOT - ../pom.xml - - - rag-query-service - RAG Query Service - RAG查询服务 - - - - com.datamate - domain-common - ${project.version} - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.cloud - spring-cloud-starter-openfeign - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - - - org.openapitools - jackson-databind-nullable - - - jakarta.validation - jakarta.validation-api - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - - - diff --git a/backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/exception/CommonErrorCode.java b/backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/exception/CommonErrorCode.java index 0e5e98426..05bad840a 100644 --- a/backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/exception/CommonErrorCode.java +++ b/backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/exception/CommonErrorCode.java @@ -12,7 +12,9 @@ @AllArgsConstructor public enum CommonErrorCode implements ErrorCode{ PARAM_ERROR("common.0001", "参数错误"), - PRE_UPLOAD_REQUEST_NOT_EXIST("common.0101", "预上传请求不存在"); + PRE_UPLOAD_REQUEST_NOT_EXIST("common.0101", "预上传请求不存在"), + SIGNUP_ERROR("common.0400", "用户名或者邮箱已经存在"), + UNAUTHORIZED("common.0401", "认证失败"); private final String code; private final String message; } diff --git a/deployment/docker/datamate/docker-compose.yml b/deployment/docker/datamate/docker-compose.yml index 2b4ee2b95..5b42b9376 100644 --- a/deployment/docker/datamate/docker-compose.yml +++ b/deployment/docker/datamate/docker-compose.yml @@ -38,6 +38,8 @@ services: image: ${REGISTRY:-}datamate-gateway restart: on-failure privileged: true + environment: + - JWT_SECRET=default-insecure-key-change-in-production networks: [ datamate ] datamate-frontend: diff --git a/deployment/helm/datamate/values.yaml b/deployment/helm/datamate/values.yaml index fc9051e5e..4276518a3 100644 --- a/deployment/helm/datamate/values.yaml +++ b/deployment/helm/datamate/values.yaml @@ -121,6 +121,9 @@ backend-python: mountPath: /var/log/datamate gateway: + env: + - name: JWT_SECRET + value: &JWT_SECRET "default-insecure-key-change-in-production" volumes: - *logVolume volumeMounts: diff --git a/scripts/db/data-management-init.sql b/scripts/db/data-management-init.sql index 549721fdd..f606a149e 100644 --- a/scripts/db/data-management-init.sql +++ b/scripts/db/data-management-init.sql @@ -290,8 +290,7 @@ CREATE TRIGGER update_users_updated_at -- 插入初始数据 INSERT INTO users (username, email, password_hash, full_name, role, organization) VALUES -('admin', 'admin@datamate.com', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7q7U3.XUO', '系统管理员', 'ADMIN', 'DataMate'), -('knowledge_user', 'knowledge@datamate.com', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7q7U3.XUO', '知识库用户', 'USER', '三甲医院') + ('admin', 'admin@datamate.com', '$2a$10$/esawo436yxg2eodpl/JJ.3Xu6y9m91/ihXRHie9al.LUoNQR5fF.', '系统管理员', 'ADMIN', 'DataMate') ON CONFLICT (username) DO NOTHING; -- 创建视图:数据集统计摘要 diff --git a/scripts/images/backend/Dockerfile b/scripts/images/backend/Dockerfile index 5d8015b6e..f75e9200b 100644 --- a/scripts/images/backend/Dockerfile +++ b/scripts/images/backend/Dockerfile @@ -1,7 +1,7 @@ FROM maven:3-eclipse-temurin-21 AS builder COPY backend/ /opt/backend -RUN cd /opt/backend/services && \ +RUN cd /opt/backend/ && \ mvn -U clean package -Dmaven.test.skip=true diff --git a/scripts/images/gateway/Dockerfile b/scripts/images/gateway/Dockerfile index 06e642847..8974a39d0 100644 --- a/scripts/images/gateway/Dockerfile +++ b/scripts/images/gateway/Dockerfile @@ -2,7 +2,7 @@ FROM maven:3-eclipse-temurin-21 AS builder COPY backend/ /opt/gateway -RUN cd /opt/gateway/api-gateway && \ +RUN cd /opt/gateway/ && \ mvn -U clean package -Dmaven.test.skip=true From 666b932ba57e49b8a72db6a52736f22e36df7cc1 Mon Sep 17 00:00:00 2001 From: uname <2986773479@qq.com> Date: Wed, 14 Jan 2026 17:34:27 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20change=20the=20collection=20template?= =?UTF-8?q?=20"OBS=E5=BD=92=E9=9B=86=E6=A8=A1=E6=9D=BF"=20to=20"S3?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=BD=92=E9=9B=86=E6=A8=A1=E6=9D=BF"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/db/data-collection-init.sql | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/db/data-collection-init.sql b/scripts/db/data-collection-init.sql index caa38d121..362af9227 100644 --- a/scripts/db/data-collection-init.sql +++ b/scripts/db/data-collection-init.sql @@ -173,13 +173,13 @@ INSERT INTO t_dc_collection_templates ( ), ( '2', - 'OBS归集模板', - '将OBS存储上的文件归集到DataMate平台上。', - 'obsreader', - 'obsreader', - 'obswriter', - 'obswriter', - '{"parameter": {"endpoint": {"name": "服务地址","description": "OBS的服务地址。","type": "input", "required": true, "index": 1},"bucket": {"name": "存储桶名称","description": "OBS存储桶名称。","type": "input", "required": true, "index": 2},"accessKey": {"name": "AK","description": "OBS访问密钥。","type": "input", "required": true, "index": 3},"secretKey": {"name": "SK","description": "OBS密钥。","type": "password", "required": true, "index": 4},"prefix": {"name": "匹配前缀","description": "按照匹配前缀去选中OBS中的文件进行归集。","type": "input", "required": true, "index": 5}}, "reader": {}, "writer": {}}', + 'S3存储归集模板', + '将S3对象存储(如OBS、MinIO、Ceph等)上的文件归集到DataMate平台上。', + 's3reader', + 's3reader', + 's3writer', + 's3writer', + '{"parameter": {"endpoint": {"name": "服务地址","description": "S3兼容存储的服务地址(如http://minio.example.com:9000)。","type": "input", "required": true, "index": 1}, "bucket": {"name": "存储桶名称","description": "S3存储桶名称。","type": "input", "required": true, "index": 2}, "accessKey": {"name": "Access Key","description": "S3访问密钥(Access Key ID)。","type": "input", "required": true, "index": 3}, "secretKey": {"name": "Secret Key","description": "S3密钥(Secret Access Key)。","type": "password", "required": true, "index": 4}, "prefix": {"name": "匹配前缀","description": "按照匹配前缀选中S3中的文件进行归集。","type": "input", "required": false, "index": 5}, "region": {"name": "区域","description": "S3区域(默认us-east-1)。","type": "input", "required": false, "index": 6}}, "reader": {}, "writer": {}}', TRUE, 'system', 'system' From 78882d4a03fd0a4fb8fffd9485209f58e4f914ac Mon Sep 17 00:00:00 2001 From: uname <2986773479@qq.com> Date: Wed, 14 Jan 2026 17:45:46 +0800 Subject: [PATCH 3/3] refactor: refactor the generate_datx_config function --- .../app/module/collection/client/datax_client.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/runtime/datamate-python/app/module/collection/client/datax_client.py b/runtime/datamate-python/app/module/collection/client/datax_client.py index 01bddfef7..94891d81f 100644 --- a/runtime/datamate-python/app/module/collection/client/datax_client.py +++ b/runtime/datamate-python/app/module/collection/client/datax_client.py @@ -50,6 +50,7 @@ def validate_json_string(self) -> Dict[str, Any]: @staticmethod def generate_datx_config(task_config: CollectionConfig, template: CollectionTemplate, target_path: str): # 校验参数 + dest_path_target = {"nfswriter", "s3writer", "glusterfswriter"} reader_parameter = { **(task_config.parameter if task_config.parameter else {}), **(task_config.reader if task_config.reader else {}) @@ -61,11 +62,7 @@ def generate_datx_config(task_config: CollectionConfig, template: CollectionTemp "fileName": "collection_result", "writeMode": "truncate" } - elif template.target_type == "nfswriter": - dest_parameter = { - "destPath": target_path - } - elif template.target_type == "s3writer" or template.target_type == "glusterfswriter" or template.target_type == "localwriter": + elif dest_path_target.__contains__(template.target_type): dest_parameter = { "destPath": target_path }