From aa42f352544964e5c3143c3ca195c621a20e8a29 Mon Sep 17 00:00:00 2001 From: jiangh277 Date: Tue, 12 Aug 2025 19:01:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Euser=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=92=8Cgateway=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 +- .../common/constants/CommonConstants.java | 4 + .../common/utils/UserContextUtils.java | 26 ++++ timeline-gateway-service/pom.xml | 79 ++++++++++ .../gateway/TimelineGatewayApplication.java | 14 ++ .../gateway/config/GatewayConfig.java | 25 +++ .../gateway/filter/AuthenticationFilter.java | 103 +++++++++++++ .../com/timeline/gateway/utils/JwtUtils.java | 56 +++++++ .../src/main/resources/application.properties | 27 ++++ timeline-user-service/pom.xml | 113 ++++++++++++++ .../user/TimelineUserServiceApplication.java | 14 ++ .../com/timeline/user/config/JwtConfig.java | 28 ++++ .../timeline/user/config/SecurityConfig.java | 15 ++ .../timeline/user/config/WebMvcConfig.java | 21 +++ .../user/controller/AuthController.java | 34 +++++ .../user/controller/PermissionController.java | 25 +++ .../user/controller/UserController.java | 43 ++++++ .../com/timeline/user/dao/UserMapper.java | 14 ++ .../com/timeline/user/dto/LoginRequest.java | 9 ++ .../com/timeline/user/dto/LoginResponse.java | 12 ++ .../timeline/user/dto/RegisterRequest.java | 12 ++ .../java/com/timeline/user/entity/User.java | 22 +++ .../interceptor/UserContextInterceptor.java | 42 ++++++ .../timeline/user/service/UserService.java | 15 ++ .../user/service/impl/UserServiceImpl.java | 142 ++++++++++++++++++ .../com/timeline/user/utils/JwtUtils.java | 63 ++++++++ .../com/timeline/user/utils/UserContext.java | 29 ++++ .../src/main/resources/application.properties | 20 +++ .../com/timeline/user/dao/UserMapper.xml | 37 +++++ 29 files changed, 1047 insertions(+), 1 deletion(-) create mode 100644 timeline-component-common/src/main/java/com/timeline/common/utils/UserContextUtils.java create mode 100644 timeline-gateway-service/pom.xml create mode 100644 timeline-gateway-service/src/main/java/com/timeline/gateway/TimelineGatewayApplication.java create mode 100644 timeline-gateway-service/src/main/java/com/timeline/gateway/config/GatewayConfig.java create mode 100644 timeline-gateway-service/src/main/java/com/timeline/gateway/filter/AuthenticationFilter.java create mode 100644 timeline-gateway-service/src/main/java/com/timeline/gateway/utils/JwtUtils.java create mode 100644 timeline-gateway-service/src/main/resources/application.properties create mode 100644 timeline-user-service/pom.xml create mode 100644 timeline-user-service/src/main/java/com/timeline/user/TimelineUserServiceApplication.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/config/JwtConfig.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/config/SecurityConfig.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/config/WebMvcConfig.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/controller/AuthController.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/controller/PermissionController.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/controller/UserController.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/dao/UserMapper.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/dto/LoginRequest.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/dto/LoginResponse.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/dto/RegisterRequest.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/entity/User.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/interceptor/UserContextInterceptor.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/service/UserService.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/service/impl/UserServiceImpl.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/utils/JwtUtils.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/utils/UserContext.java create mode 100644 timeline-user-service/src/main/resources/application.properties create mode 100644 timeline-user-service/src/main/resources/com/timeline/user/dao/UserMapper.xml diff --git a/pom.xml b/pom.xml index 877bba6..6fd1f5f 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,9 @@ timeline-file-service timeline-component-common timeline-story-service - + timeline-user-service + timeline-gateway-service + diff --git a/timeline-component-common/src/main/java/com/timeline/common/constants/CommonConstants.java b/timeline-component-common/src/main/java/com/timeline/common/constants/CommonConstants.java index 7396bab..3b78ca3 100644 --- a/timeline-component-common/src/main/java/com/timeline/common/constants/CommonConstants.java +++ b/timeline-component-common/src/main/java/com/timeline/common/constants/CommonConstants.java @@ -14,4 +14,8 @@ public class CommonConstants { public static final int DELETED = 1; public static final int NOT_DELETED = 0; public static final String LOW_RESOLUTION_PREFIX = "low_res_"; + + // 用户状态 + public static final int USER_STATUS_NORMAL = 0; + public static final int USER_STATUS_DISABLED = 1; } diff --git a/timeline-component-common/src/main/java/com/timeline/common/utils/UserContextUtils.java b/timeline-component-common/src/main/java/com/timeline/common/utils/UserContextUtils.java new file mode 100644 index 0000000..3e77542 --- /dev/null +++ b/timeline-component-common/src/main/java/com/timeline/common/utils/UserContextUtils.java @@ -0,0 +1,26 @@ +package com.timeline.common.utils; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +public class UserContextUtils { + + public static String getCurrentUserId() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + HttpServletRequest request = attributes.getRequest(); + return request.getHeader("X-User-Id"); + } + return null; + } + + public static String getCurrentUsername() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + HttpServletRequest request = attributes.getRequest(); + return request.getHeader("X-Username"); + } + return null; + } +} \ No newline at end of file diff --git a/timeline-gateway-service/pom.xml b/timeline-gateway-service/pom.xml new file mode 100644 index 0000000..0527bd6 --- /dev/null +++ b/timeline-gateway-service/pom.xml @@ -0,0 +1,79 @@ + + + 4.0.0 + + com.timeline + timeline + 0.0.1-SNAPSHOT + + + timeline-gateway-service + + + 21 + 21 + UTF-8 + + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + + + com.timeline + timeline-component-common + 0.0.1-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.projectlombok + lombok + provided + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/timeline-gateway-service/src/main/java/com/timeline/gateway/TimelineGatewayApplication.java b/timeline-gateway-service/src/main/java/com/timeline/gateway/TimelineGatewayApplication.java new file mode 100644 index 0000000..d5170bc --- /dev/null +++ b/timeline-gateway-service/src/main/java/com/timeline/gateway/TimelineGatewayApplication.java @@ -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); + } +} diff --git a/timeline-gateway-service/src/main/java/com/timeline/gateway/config/GatewayConfig.java b/timeline-gateway-service/src/main/java/com/timeline/gateway/config/GatewayConfig.java new file mode 100644 index 0000000..f674a1b --- /dev/null +++ b/timeline-gateway-service/src/main/java/com/timeline/gateway/config/GatewayConfig.java @@ -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); + } +} diff --git a/timeline-gateway-service/src/main/java/com/timeline/gateway/filter/AuthenticationFilter.java b/timeline-gateway-service/src/main/java/com/timeline/gateway/filter/AuthenticationFilter.java new file mode 100644 index 0000000..65f0437 --- /dev/null +++ b/timeline-gateway-service/src/main/java/com/timeline/gateway/filter/AuthenticationFilter.java @@ -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 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 handleUnauthorized(ServerWebExchange exchange, String message) { + ServerHttpResponse response = exchange.getResponse(); + response.setStatusCode(HttpStatus.UNAUTHORIZED); + response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); + + ResponseEntity 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)); + } +} diff --git a/timeline-gateway-service/src/main/java/com/timeline/gateway/utils/JwtUtils.java b/timeline-gateway-service/src/main/java/com/timeline/gateway/utils/JwtUtils.java new file mode 100644 index 0000000..aa56910 --- /dev/null +++ b/timeline-gateway-service/src/main/java/com/timeline/gateway/utils/JwtUtils.java @@ -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(); + } +} diff --git a/timeline-gateway-service/src/main/resources/application.properties b/timeline-gateway-service/src/main/resources/application.properties new file mode 100644 index 0000000..e5ff041 --- /dev/null +++ b/timeline-gateway-service/src/main/resources/application.properties @@ -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 diff --git a/timeline-user-service/pom.xml b/timeline-user-service/pom.xml new file mode 100644 index 0000000..033cddb --- /dev/null +++ b/timeline-user-service/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + com.timeline + timeline + 0.0.1-SNAPSHOT + + + timeline-user-service + timeline-user-service + User management service for timeline system + + + 21 + 21 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + + + com.timeline + timeline-component-common + 0.0.1-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 3.0.4 + + + com.mysql + mysql-connector-j + 8.2.0 + runtime + + + + + org.projectlombok + lombok + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + diff --git a/timeline-user-service/src/main/java/com/timeline/user/TimelineUserServiceApplication.java b/timeline-user-service/src/main/java/com/timeline/user/TimelineUserServiceApplication.java new file mode 100644 index 0000000..5d3d154 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/TimelineUserServiceApplication.java @@ -0,0 +1,14 @@ +package com.timeline.user; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan({"com.timeline", "com.timeline.user"}) +public class TimelineUserServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(TimelineUserServiceApplication.class, args); + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/config/JwtConfig.java b/timeline-user-service/src/main/java/com/timeline/user/config/JwtConfig.java new file mode 100644 index 0000000..959108f --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/config/JwtConfig.java @@ -0,0 +1,28 @@ +package com.timeline.user.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "jwt") +public class JwtConfig { + private String secret = "timelineSecretKey"; + private Long expiration = 86400L; // 24小时 + + // Getters and setters + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public Long getExpiration() { + return expiration; + } + + public void setExpiration(Long expiration) { + this.expiration = expiration; + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/config/SecurityConfig.java b/timeline-user-service/src/main/java/com/timeline/user/config/SecurityConfig.java new file mode 100644 index 0000000..469fc29 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/config/SecurityConfig.java @@ -0,0 +1,15 @@ +package com.timeline.user.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class SecurityConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/config/WebMvcConfig.java b/timeline-user-service/src/main/java/com/timeline/user/config/WebMvcConfig.java new file mode 100644 index 0000000..6ee9461 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/config/WebMvcConfig.java @@ -0,0 +1,21 @@ +package com.timeline.user.config; + +import com.timeline.user.interceptor.UserContextInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Autowired + private UserContextInterceptor userContextInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(userContextInterceptor) + .addPathPatterns("/api/**") + .excludePathPatterns("/api/auth/login", "/api/auth/register"); + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/controller/AuthController.java b/timeline-user-service/src/main/java/com/timeline/user/controller/AuthController.java new file mode 100644 index 0000000..06d5872 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/controller/AuthController.java @@ -0,0 +1,34 @@ +package com.timeline.user.controller; + +import com.timeline.common.response.ResponseEntity; +import com.timeline.user.dto.LoginRequest; +import com.timeline.user.dto.LoginResponse; +import com.timeline.user.dto.RegisterRequest; +import com.timeline.user.entity.User; +import com.timeline.user.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequestMapping("/api/auth") +public class AuthController { + + @Autowired + private UserService userService; + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + log.info("用户登录请求: {}", loginRequest.getUsername()); + LoginResponse response = userService.login(loginRequest); + return ResponseEntity.success(response); + } + + @PostMapping("/register") + public ResponseEntity register(@RequestBody RegisterRequest registerRequest) { + log.info("用户注册请求: {}", registerRequest.getUsername()); + User user = userService.register(registerRequest); + return ResponseEntity.success(user); + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/controller/PermissionController.java b/timeline-user-service/src/main/java/com/timeline/user/controller/PermissionController.java new file mode 100644 index 0000000..4fa11dd --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/controller/PermissionController.java @@ -0,0 +1,25 @@ +package com.timeline.user.controller; + +import com.timeline.common.response.ResponseEntity; +import com.timeline.user.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequestMapping("/api/permission") +public class PermissionController { + + @Autowired + private UserService userService; + + @GetMapping("/check") + public ResponseEntity checkUserPermission( + @RequestParam String userId, + @RequestParam String requiredPermission) { + log.info("检查用户权限: userId={}, requiredPermission={}", userId, requiredPermission); + boolean hasPermission = userService.checkUserPermission(userId, requiredPermission); + return ResponseEntity.success(hasPermission); + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/controller/UserController.java b/timeline-user-service/src/main/java/com/timeline/user/controller/UserController.java new file mode 100644 index 0000000..50f4e02 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/controller/UserController.java @@ -0,0 +1,43 @@ +package com.timeline.user.controller; + +import com.timeline.common.response.ResponseEntity; +import com.timeline.user.dto.RegisterRequest; +import com.timeline.user.entity.User; +import com.timeline.user.service.UserService; +import com.timeline.user.utils.UserContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequestMapping("/api/user") +public class UserController { + + @Autowired + private UserService userService; + + @GetMapping("/info") + public ResponseEntity getCurrentUserInfo() { + String userId = UserContext.getCurrentUserId(); + log.info("获取当前用户信息: {}", userId); + User user = userService.getUserByUserId(userId); + return ResponseEntity.success(user); + } + + @PutMapping("/info") + public ResponseEntity updateUserInfo(@RequestBody RegisterRequest updateRequest) { + String userId = UserContext.getCurrentUserId(); + log.info("更新用户信息: {}", userId); + User user = userService.updateUserInfo(userId, updateRequest); + return ResponseEntity.success(user); + } + + @DeleteMapping + public ResponseEntity deleteUser() { + String userId = UserContext.getCurrentUserId(); + log.info("删除用户: {}", userId); + userService.deleteUser(userId); + return ResponseEntity.success("用户删除成功"); + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/dao/UserMapper.java b/timeline-user-service/src/main/java/com/timeline/user/dao/UserMapper.java new file mode 100644 index 0000000..5e5e284 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/dao/UserMapper.java @@ -0,0 +1,14 @@ +package com.timeline.user.dao; + +import com.timeline.user.entity.User; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface UserMapper { + void insert(User user); + User selectById(Long id); + User selectByUserId(String userId); + User selectByUsername(String username); + void update(User user); + void deleteByUserId(String userId); +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/dto/LoginRequest.java b/timeline-user-service/src/main/java/com/timeline/user/dto/LoginRequest.java new file mode 100644 index 0000000..725a20d --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/dto/LoginRequest.java @@ -0,0 +1,9 @@ +package com.timeline.user.dto; + +import lombok.Data; + +@Data +public class LoginRequest { + private String username; + private String password; +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/dto/LoginResponse.java b/timeline-user-service/src/main/java/com/timeline/user/dto/LoginResponse.java new file mode 100644 index 0000000..bf10838 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/dto/LoginResponse.java @@ -0,0 +1,12 @@ +package com.timeline.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class LoginResponse { + private String token; + private String userId; + private String username; +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/dto/RegisterRequest.java b/timeline-user-service/src/main/java/com/timeline/user/dto/RegisterRequest.java new file mode 100644 index 0000000..8cd0cb5 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/dto/RegisterRequest.java @@ -0,0 +1,12 @@ +package com.timeline.user.dto; + +import lombok.Data; + +@Data +public class RegisterRequest { + private String username; + private String nickname; + private String password; + private String email; + private String phone; +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/entity/User.java b/timeline-user-service/src/main/java/com/timeline/user/entity/User.java new file mode 100644 index 0000000..8791885 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/entity/User.java @@ -0,0 +1,22 @@ +package com.timeline.user.entity; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class User { + private Long id; + private String userId; + private String username; + private String password; + private String nickname; + private String email; + private String phone; + private Integer status; // 0-正常,1-禁用 + private Integer isDeleted; // 0-未删除,1-已删除 + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/interceptor/UserContextInterceptor.java b/timeline-user-service/src/main/java/com/timeline/user/interceptor/UserContextInterceptor.java new file mode 100644 index 0000000..d6b2cb3 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/interceptor/UserContextInterceptor.java @@ -0,0 +1,42 @@ +package com.timeline.user.interceptor; + +import com.timeline.user.entity.User; +import com.timeline.user.service.UserService; +import com.timeline.user.utils.JwtUtils; +import com.timeline.user.utils.UserContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class UserContextInterceptor implements HandlerInterceptor { + + @Autowired + private JwtUtils jwtUtils; + + @Autowired + private UserService userService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String token = request.getHeader("Authorization"); + if (token != null && token.startsWith("Bearer ")) { + token = token.substring(7); + if (jwtUtils.validateToken(token) && !jwtUtils.isTokenExpired(token)) { + String userId = jwtUtils.getUserIdFromToken(token); + User user = userService.getUserByUserId(userId); + if (user != null) { + UserContext.setUser(user); + } + } + } + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + UserContext.clear(); + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/service/UserService.java b/timeline-user-service/src/main/java/com/timeline/user/service/UserService.java new file mode 100644 index 0000000..14cc5bb --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/service/UserService.java @@ -0,0 +1,15 @@ +package com.timeline.user.service; + +import com.timeline.user.entity.User; +import com.timeline.user.dto.LoginRequest; +import com.timeline.user.dto.RegisterRequest; +import com.timeline.user.dto.LoginResponse; + +public interface UserService { + LoginResponse login(LoginRequest loginRequest); + User register(RegisterRequest registerRequest); + User getUserByUserId(String userId); + User updateUserInfo(String userId, RegisterRequest updateRequest); + void deleteUser(String userId); + boolean checkUserPermission(String userId, String requiredPermission); +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/service/impl/UserServiceImpl.java b/timeline-user-service/src/main/java/com/timeline/user/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..3d5192a --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/service/impl/UserServiceImpl.java @@ -0,0 +1,142 @@ +package com.timeline.user.service.impl; + +import com.timeline.common.constants.CommonConstants; +import com.timeline.common.exception.CustomException; +import com.timeline.common.response.ResponseEnum; +import com.timeline.common.utils.IdUtils; +import com.timeline.user.dao.UserMapper; +import com.timeline.user.entity.User; +import com.timeline.user.dto.LoginRequest; +import com.timeline.user.dto.RegisterRequest; +import com.timeline.user.dto.LoginResponse; +import com.timeline.user.service.UserService; +import com.timeline.user.utils.JwtUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Slf4j +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UserMapper userMapper; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private JwtUtils jwtUtils; + + @Override + public LoginResponse login(LoginRequest loginRequest) { + try { + User user = userMapper.selectByUsername(loginRequest.getUsername()); + if (user == null) { + throw new CustomException(ResponseEnum.UNAUTHORIZED, "用户名或密码错误"); + } + + if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) { + throw new CustomException(ResponseEnum.UNAUTHORIZED, "用户名或密码错误"); + } + + if (user.getStatus() == 1) { + throw new CustomException(ResponseEnum.FORBIDDEN, "用户已被禁用"); + } + + String token = jwtUtils.generateToken(user.getUserId(), user.getUsername()); + return new LoginResponse(token, user.getUserId(), user.getUsername()); + } catch (CustomException e) { + throw e; + } catch (Exception e) { + log.error("用户登录失败", e); + throw new CustomException(ResponseEnum.INTERNAL_SERVER_ERROR, "登录失败"); + } + } + + @Override + public User register(RegisterRequest registerRequest) { + try { + // 检查用户名是否已存在 + User existingUser = userMapper.selectByUsername(registerRequest.getUsername()); + if (existingUser != null) { + throw new CustomException(ResponseEnum.BAD_REQUEST, "用户名已存在"); + } + + User user = new User(); + user.setUserId(IdUtils.randomUuidUpper()); + user.setUsername(registerRequest.getUsername()); + user.setNickname(registerRequest.getNickname()); + user.setPassword(passwordEncoder.encode(registerRequest.getPassword())); + user.setEmail(registerRequest.getEmail()); + user.setPhone(registerRequest.getPhone()); + user.setStatus(CommonConstants.USER_STATUS_NORMAL); // 正常状态 + user.setIsDeleted(CommonConstants.NOT_DELETED); + user.setCreateTime(LocalDateTime.now()); + user.setUpdateTime(LocalDateTime.now()); + + userMapper.insert(user); + return user; + } catch (CustomException e) { + throw e; + } catch (Exception e) { + log.error("用户注册失败", e); + throw new CustomException(ResponseEnum.INTERNAL_SERVER_ERROR, "注册失败"); + } + } + + @Override + public User getUserByUserId(String userId) { + return userMapper.selectByUserId(userId); + } + + @Override + public User updateUserInfo(String userId, RegisterRequest updateRequest) { + try { + User user = userMapper.selectByUserId(userId); + if (user == null) { + throw new CustomException(ResponseEnum.NOT_FOUND, "用户不存在"); + } + + user.setEmail(updateRequest.getEmail()); + user.setPhone(updateRequest.getPhone()); + user.setUpdateTime(LocalDateTime.now()); + + userMapper.update(user); + return user; + } catch (CustomException e) { + throw e; + } catch (Exception e) { + log.error("更新用户信息失败", e); + throw new CustomException(ResponseEnum.INTERNAL_SERVER_ERROR, "更新用户信息失败"); + } + } + + @Override + public void deleteUser(String userId) { + try { + User user = userMapper.selectByUserId(userId); + if (user == null) { + throw new CustomException(ResponseEnum.NOT_FOUND, "用户不存在"); + } + + userMapper.deleteByUserId(userId); + } catch (CustomException e) { + throw e; + } catch (Exception e) { + log.error("删除用户失败", e); + throw new CustomException(ResponseEnum.INTERNAL_SERVER_ERROR, "删除用户失败"); + } + } + + @Override + public boolean checkUserPermission(String userId, String requiredPermission) { + // 这里可以实现更复杂的权限检查逻辑 + // 简单示例:检查用户是否存在 + User user = userMapper.selectByUserId(userId); + return user != null && user.getStatus() == 0; // 用户存在且未被禁用 + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/utils/JwtUtils.java b/timeline-user-service/src/main/java/com/timeline/user/utils/JwtUtils.java new file mode 100644 index 0000000..f0d4402 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/utils/JwtUtils.java @@ -0,0 +1,63 @@ +package com.timeline.user.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Component +public class JwtUtils { + + @Value("${jwt.secret:timelineSecretKey}") + private String secret; + + @Value("${jwt.expiration:86400}") + private Long expiration; + + public String generateToken(String userId, String username) { + Map claims = new HashMap<>(); + claims.put("userId", userId); + claims.put("username", username); + return Jwts.builder() + .setClaims(claims) + .setSubject(username) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) + .signWith(SignatureAlgorithm.HS512, secret) + .compact(); + } + + public Claims getClaimsFromToken(String token) { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } + + public String getUserIdFromToken(String token) { + return getClaimsFromToken(token).get("userId", String.class); + } + + public String getUsernameFromToken(String token) { + return getClaimsFromToken(token).getSubject(); + } + + public boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(secret).parseClaimsJws(token); + return true; + } catch (Exception e) { + return false; + } + } + + public boolean isTokenExpired(String token) { + Date expiration = getClaimsFromToken(token).getExpiration(); + return expiration.before(new Date()); + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/utils/UserContext.java b/timeline-user-service/src/main/java/com/timeline/user/utils/UserContext.java new file mode 100644 index 0000000..09c83de --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/utils/UserContext.java @@ -0,0 +1,29 @@ +package com.timeline.user.utils; + +import com.timeline.user.entity.User; + +public class UserContext { + private static final ThreadLocal userHolder = new ThreadLocal<>(); + + public static void setUser(User user) { + userHolder.set(user); + } + + public static User getCurrentUser() { + return userHolder.get(); + } + + public static String getCurrentUserId() { + User user = getCurrentUser(); + return user != null ? user.getUserId() : null; + } + + public static String getCurrentUsername() { + User user = getCurrentUser(); + return user != null ? user.getUsername() : null; + } + + public static void clear() { + userHolder.remove(); + } +} diff --git a/timeline-user-service/src/main/resources/application.properties b/timeline-user-service/src/main/resources/application.properties new file mode 100644 index 0000000..eaf7c18 --- /dev/null +++ b/timeline-user-service/src/main/resources/application.properties @@ -0,0 +1,20 @@ +spring.application.name=timeline.user +server.port=30003 + +# ????? +spring.datasource.url=jdbc:mysql://8.137.148.196:33306/timeline?serverTimezone=UTC&allowPublicKeyRetrieval=true +spring.datasource.username=root +spring.datasource.password=your_password +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +# MyBatis ?? +mybatis.mapper-locations=classpath:mapper/*.xml +mybatis.type-aliases-package=com.timeline.user.entity +mybatis.configuration.mapUnderscoreToCamelCase=true + +# JWT ?? +jwt.secret=timelineSecretKey +jwt.expiration=86400 + +# ???? +logging.level.com.timeline.user=DEBUG diff --git a/timeline-user-service/src/main/resources/com/timeline/user/dao/UserMapper.xml b/timeline-user-service/src/main/resources/com/timeline/user/dao/UserMapper.xml new file mode 100644 index 0000000..1f501aa --- /dev/null +++ b/timeline-user-service/src/main/resources/com/timeline/user/dao/UserMapper.xml @@ -0,0 +1,37 @@ + + + + + + + INSERT INTO user (user_id, username, password, nickname, email, phone, status, is_deleted, create_time, update_time) + VALUES (#{userId}, #{username}, #{password}, #{nickname}, #{email}, #{phone}, #{status}, #{isDeleted}, #{createTime}, #{updateTime}) + + + + + + + + + + UPDATE user + SET username = #{username}, + email = #{email}, + phone = #{phone}, + status = #{status}, + update_time = #{updateTime} + WHERE user_id = #{userId} AND is_deleted = 0 + + + + UPDATE user SET is_deleted = 1, update_time = NOW() WHERE user_id = #{userId} + +