diff --git a/user-service/data.sql b/user-service/data.sql new file mode 100644 index 0000000000000000000000000000000000000000..662fe4a72170f80b41d77088e33cc95fab88d6c6 --- /dev/null +++ b/user-service/data.sql @@ -0,0 +1,23 @@ +CREATE TABLE sys_user +( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID', + username VARCHAR(255) NOT NULL COMMENT '用户名', + password VARCHAR(255) NOT NULL COMMENT '密码', + email VARCHAR(255) COMMENT '邮箱', + phone VARCHAR(20) COMMENT '手机号', + real_name VARCHAR(100) COMMENT '真实姓名', + status INT NOT NULL DEFAULT 1 COMMENT '用户状态:0-禁用,1-启用', + role VARCHAR(50) NOT NULL DEFAULT 'USER' COMMENT '角色:ADMIN-管理员,USER-普通用户', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + last_login_time DATETIME COMMENT '最后登录时间' +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='用户表'; +-- 初始化用户数据 +-- 密码使用BCrypt加密,原始密码为 'password' +-- BCrypt哈希: $2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2uheWG/igi. +INSERT INTO sys_user (id, username, password, email, phone, real_name, status, create_time, update_time) +VALUES (1, 'admin', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2uheWG/igi.', 'admin@example.com', '13800138000', + '管理员', 1, NOW(), NOW()), + (2, 'user', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2uheWG/igi.', 'user@example.com', '13800138001', + '普通用户', 1, NOW(), NOW()); \ No newline at end of file diff --git a/user-service/pom.xml b/user-service/pom.xml index c69afc83c25b3353caf5990a6177cba309dcb683..535fed04cd4fd74be4adb8b9cf966c1dcbc7c055 100644 --- a/user-service/pom.xml +++ b/user-service/pom.xml @@ -105,3 +105,4 @@ + diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/pom.xml b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/pom.xml index b673beeab279b6a48e1682bc66bcab53fa002d75..90440d62dd1dfd79a0665aa0dc3f5dfdc66774dd 100644 --- a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/pom.xml +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/pom.xml @@ -46,7 +46,18 @@ knife4j-openapi3-jakarta-spring-boot-starter ${knife4j.version} + + + org.springframework.boot + spring-boot-starter-amqp + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.58 + diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/controller/UserController.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/controller/UserController.java index 96820c00d7cf18e1f698ddc65b3e279601b5d576..15f4a912b0076a9fb020db1fcae516ad9653013f 100644 --- a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/controller/UserController.java +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/controller/UserController.java @@ -1,24 +1,42 @@ package com.example.user.adapter.in.web.controller; -import com.example.user.adapter.in.web.dto.CreateUserRequestDTO; -import com.example.user.adapter.in.web.dto.UpdateUserRequestDTO; -import com.example.user.adapter.in.web.dto.UserLoginRequestDTO; -import com.example.user.adapter.in.web.dto.UserResponseDTO; +import com.example.user.adapter.in.web.dto.*; import com.example.user.service.application.command.CreateUserCommand; import com.example.user.service.application.command.UpdateUserCommand; -import com.example.user.service.application.command.UserLoginCommand; import com.example.user.service.application.port.in.*; +import com.example.user.service.application.service.CustomUserDetailsService; +import com.example.user.service.application.service.TokenBlacklistService; +import com.example.user.service.common.JwtUtil; import com.example.user.service.domain.User; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; +import org.springframework.web.client.RestTemplate; +import com.alibaba.fastjson2.JSON; +import com.example.user.service.domain.Student; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import java.time.LocalDateTime; +import java.util.Arrays; import java.util.List; @Slf4j @RequestMapping("/users") @RestController @RequiredArgsConstructor +@Tag(name = "用户认证和管理", description = "用户登录、注册、信息管理等接口") public class UserController { private final GetUserListUseCase getUserListUseCase; @@ -27,42 +45,204 @@ public class UserController { private final UpdateUserUseCase updateUserUseCase; private final GetUserByIdUseCase getUserByIdUseCase; private final UserLoginUseCase userLoginUseCase; + private final CustomUserDetailsService userDetailsService; + private final JwtUtil jwtUtil; + private final TokenBlacklistService tokenBlacklistService; + private final RestTemplate restTemplate; + private final RabbitTemplate rabbitTemplate; + // 使用@Value注入配置,避免循环依赖 + @Value("${moonshot.url}") + private String moonshotUrl; - @PostMapping("login") - public String login(@RequestBody UserLoginRequestDTO userLoginRequestDTO){ - log.info("UserLoginRequestDTO:{}",userLoginRequestDTO); - UserLoginCommand command=UserLoginCommand.builder() - .name(userLoginRequestDTO.getName()) - .password(userLoginRequestDTO.getPassword()) - .build(); - String token = userLoginUseCase.login(command); - return token; + @Value("${moonshot.key}") + private String moonshotKey; + + @Value("${moonshot.model}") + private String moonshotModel; + + @PostMapping("/login") + @Operation(summary = "用户登录", description = "用户名密码登录,返回JWT token") + public Result login(@Valid @RequestBody LoginRequest loginRequest) { + try { + log.info("用户登录请求: {}", loginRequest.getUsername()); + + // 使用CustomUserDetailsService进行认证 + com.example.user.adapter.out.persistence.entity.UserEntity userEntity = + userDetailsService.authenticate(loginRequest.getUsername(), loginRequest.getPassword()); + + if (userEntity == null) { + return Result.error("登录失败:用户名或密码错误"); + } + + // 生成JWT Token - 使用现有的载荷结构 + String token = jwtUtil.generateToken( + userEntity.getId(), + userEntity.getName(), + "ROLE_ADMIN".equals(userEntity.getRole()) // 简化处理,实际应根据角色判断 + ); + + // 构建用户信息对象 + UserInfo userInfo = UserInfo.builder() + .id(userEntity.getId()) + .username(userEntity.getName()) + .email(userEntity.getEmail()) + .phone(userEntity.getPhone()) + .realName(userEntity.getRealName()) + .status(userEntity.getStatus()) + .role(userEntity.getRole()) + .createTime(userEntity.getCreateTime()) + .lastLoginTime(LocalDateTime.now()) + .build(); + + // 构建登录响应对象 + LoginResponse loginResponse = LoginResponse.builder() + .accessToken(token) + .tokenType("Bearer") + .userInfo(userInfo) + .expiresIn(System.currentTimeMillis() + 86400000L) + .build(); + + log.info("用户登录成功: {}", userEntity.getName()); + return Result.success("登录成功", loginResponse); + + } catch (Exception e) { + log.error("用户登录失败: {}", e.getMessage()); + return Result.error("登录失败: " + e.getMessage()); + } } + @PostMapping("/register") + @Operation(summary = "用户注册", description = "用户注册接口") + public Result register(@Valid @RequestBody RegisterRequest registerRequest) { + try { + log.info("用户注册请求: username={}, email={}", registerRequest.getUsername(), registerRequest.getEmail()); + + // 检查用户名是否已存在 + com.example.user.adapter.out.persistence.entity.UserEntity existingUser = + userDetailsService.getUserByUsername(registerRequest.getUsername()); + if (existingUser != null) { + log.warn("注册失败: 用户名已存在 - {}", registerRequest.getUsername()); + return Result.error("用户名已存在,请选择其他用户名"); + } + + // 验证两次密码是否一致 + if (!registerRequest.getPassword().equals(registerRequest.getConfirmPassword())) { + log.warn("注册失败: 两次密码不一致 - {}", registerRequest.getUsername()); + return Result.error("两次输入的密码不一致"); + } + + // 创建新用户对象 + com.example.user.adapter.out.persistence.entity.UserEntity newUser = + new com.example.user.adapter.out.persistence.entity.UserEntity(); + newUser.setName(registerRequest.getUsername()); + + // 对密码进行加密 + String encodedPassword = userDetailsService.generatePasswordHash(registerRequest.getPassword()); + newUser.setPassword(encodedPassword); + + // 设置其他用户信息 + newUser.setEmail(registerRequest.getEmail()); + newUser.setPhone(registerRequest.getPhone()); + newUser.setRealName(registerRequest.getRealName()); + newUser.setRole("USER"); // 默认角色为普通用户 + newUser.setStatus(1); // 默认状态为启用 + + // 保存用户到数据库 + int result = userDetailsService.saveUser(newUser); + + if (result > 0) { + log.info("用户注册成功: username={}, userId={}", registerRequest.getUsername(), newUser.getId()); + return Result.success("注册成功,请使用用户名和密码登录"); + } else { + log.error("用户注册失败: 数据库插入失败 - {}", registerRequest.getUsername()); + return Result.error("注册失败,请稍后重试"); + } + + } catch (Exception e) { + log.error("用户注册异常: username={}, error={}", registerRequest.getUsername(), e.getMessage(), e); + return Result.error("系统异常,请稍后重试"); + } + } + + @GetMapping("/info") + @Operation(summary = "获取用户信息", description = "获取当前登录用户的详细信息") + public Result getUserInfo() { + try { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || !authentication.isAuthenticated()) { + return Result.unauthorized("用户未登录"); + } + + String username = authentication.getName(); + com.example.user.adapter.out.persistence.entity.UserEntity user = + userDetailsService.getUserByUsername(username); + + if (user == null) { + return Result.error("用户不存在"); + } + + UserInfo userInfo = UserInfo.builder() + .id(user.getId()) + .username(user.getName()) + .email(user.getEmail()) + .phone(user.getPhone()) + .realName(user.getRealName()) + .status(user.getStatus()) + .role(user.getRole()) + .createTime(user.getCreateTime()) + .lastLoginTime(user.getLastLoginTime()) + .build(); + + return Result.success(userInfo); + + } catch (Exception e) { + log.error("获取用户信息失败: {}", e.getMessage()); + return Result.error("获取用户信息失败: " + e.getMessage()); + } + } + + @PostMapping("/logout") + @Operation(summary = "用户登出", description = "用户登出,清除认证信息并将token加入黑名单") + public Result logout(HttpServletRequest request) { + try { + String token = getJwtFromRequest(request); + + if (token != null) { + tokenBlacklistService.addToBlacklist(token); + log.info("Token已加入黑名单: {}", token.substring(0, Math.min(token.length(), 20)) + "..."); + } + SecurityContextHolder.clearContext(); + log.info("用户登出成功"); + return Result.success("登出成功,token已失效"); + } catch (Exception e) { + log.error("用户登出失败: {}", e.getMessage()); + return Result.error("登出失败: " + e.getMessage()); + } + } + + @GetMapping("/test") + @Operation(summary = "测试接口", description = "需要JWT认证的测试接口") + public Result test() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authentication.getName(); + return Result.success("Hello, " + username + "! JWT认证成功!"); + } + // 原有的方法保持不变 @GetMapping("") public List getUsers() { log.info("getUsers"); return getUserListUseCase.getUsers(); } - /** - * 创建新用户 - * 功能:接收用户注册信息,验证密码一致性,创建新用户账户 - * @author dongxuanfeng - * @param createUserRequestDTO - * @return User - 成功创建的新用户 - * @throws IllegalArgumentException 当密码与确认密码不匹配时抛出此异常 - */ @PostMapping() - public User createUser(@RequestBody CreateUserRequestDTO createUserRequestDTO){ - + public User createUser(@RequestBody CreateUserRequestDTO createUserRequestDTO) { if (!createUserRequestDTO.isPasswordValid()) { throw new IllegalArgumentException("密码和确认密码不匹配"); } - CreateUserCommand command=CreateUserCommand.builder() + CreateUserCommand command = CreateUserCommand.builder() .name(createUserRequestDTO.name()) .age(createUserRequestDTO.age()) .email(createUserRequestDTO.email()) @@ -72,17 +252,15 @@ public class UserController { return createUserUseCase.createUser(command); } - @DeleteMapping("{id}") - public String deleteUser(@PathVariable("id") Long id){ + public String deleteUser(@PathVariable("id") Long id) { deleteUserUseCase.deleteUser(id); return "success"; } - @PutMapping("") - public User updateUser(@RequestBody UpdateUserRequestDTO updateUserRequestDTO){ - UpdateUserCommand command=UpdateUserCommand.builder() + public User updateUser(@RequestBody UpdateUserRequestDTO updateUserRequestDTO) { + UpdateUserCommand command = UpdateUserCommand.builder() .id(updateUserRequestDTO.id()) .name(updateUserRequestDTO.name()) .age(updateUserRequestDTO.age()) @@ -92,10 +270,8 @@ public class UserController { return user; } - - @GetMapping("{id}") - public UserResponseDTO getUserById(@PathVariable("id") Long id){ + public UserResponseDTO getUserById(@PathVariable("id") Long id) { User user = getUserByIdUseCase.getUserById(id); UserResponseDTO userResponseDTO = new UserResponseDTO( user.getId().id(), @@ -106,4 +282,109 @@ public class UserController { return userResponseDTO; } -} + private String getJwtFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } + // 新增Kimi聊天接口 + @Operation(summary = "与Kimi聊天", description = "调用Moonshot AI的Kimi模型进行聊天") + @GetMapping("/chat") + public Result> chat(@RequestParam("question") String question) { + try { + log.info("用户Kimi聊天请求: {}", question); + + // 创建请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + moonshotKey); + + // 创建请求体 + ChatCompletionRequestDTO request = new ChatCompletionRequestDTO(); + request.setModel(moonshotModel); + request.setTemperature(0.6); + + // 创建消息列表 + Message systemMessage = new Message(); + systemMessage.setRole("system"); + systemMessage.setContent("你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一切涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。"); + + Message userMessage = new Message(); + userMessage.setRole("user"); + userMessage.setContent(question); + + request.setMessages(Arrays.asList(systemMessage, userMessage)); + + // 创建Http实体 + HttpEntity response = restTemplate.exchange( + moonshotUrl, + HttpMethod.POST, + new HttpEntity<>(request, headers), + ChatCompletionResponseDTO.class + ); + + log.info("Kimi聊天成功"); + return Result.success(response.getBody().getChoices()); + } catch (Exception e) { + log.error("Kimi聊天失败: {}", e.getMessage()); + return Result.error("聊天失败: " + e.getMessage()); + } + } + + // 新增RabbitMQ相关方法 + @Operation(summary = "发送简单消息", description = "向RabbitMQ发送简单文本消息") + @GetMapping("/send") + public Result send() { + try { + // 把消息交给MQ处理 + rabbitTemplate.convertAndSend("test", "hello world"); + log.info("简单消息发送成功"); + return Result.success("消息发送成功"); + } catch (Exception e) { + log.error("消息发送失败: {}", e.getMessage()); + return Result.error("消息发送失败: " + e.getMessage()); + } + } + + @Operation(summary = "发送对象消息", description = "向RabbitMQ发送Student对象消息") + @GetMapping("/sendObject") + public Result sendObject() { + try { + // 创建Student对象 + Student stu = new Student(); + stu.setId(1L); + stu.setName("xiaohei"); + stu.setAge(18); + + // 把消息交给MQ处理 + rabbitTemplate.convertAndSend("test2", stu); + log.info("对象消息发送成功"); + return Result.success("对象消息发送成功"); + } catch (Exception e) { + log.error("对象消息发送失败: {}", e.getMessage()); + return Result.error("对象消息发送失败: " + e.getMessage()); + } + } + + @Operation(summary = "发送JSON消息", description = "向RabbitMQ发送JSON格式消息") + @GetMapping("/sendJson") + public Result sendJson() { + try { + // 创建Student对象 + Student stu = new Student(); + stu.setId(1L); + stu.setName("xiaohei"); + stu.setAge(18); + + // 把消息交给MQ处理 + rabbitTemplate.convertAndSend("test3", JSON.toJSONString(stu)); + log.info("JSON消息发送成功"); + return Result.success("JSON消息发送成功"); + } catch (Exception e) { + log.error("JSON消息发送失败: {}", e.getMessage()); + return Result.error("JSON消息发送失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/ChatCompletionRequestDTO.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/ChatCompletionRequestDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..47c56abde9f3676f5e3b1e322f1a0f619338bfd7 --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/ChatCompletionRequestDTO.java @@ -0,0 +1,12 @@ +// ChatCompletionRequestDTO.java +package com.example.user.adapter.in.web.dto; + +import lombok.Data; +import java.util.List; + +@Data +public class ChatCompletionRequestDTO { + private String model; + private List messages; + private double temperature; +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/ChatCompletionResponseDTO.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/ChatCompletionResponseDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..5c745bdeb40bfb82422e37dae1fad3d996d85c89 --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/ChatCompletionResponseDTO.java @@ -0,0 +1,13 @@ +package com.example.user.adapter.in.web.dto; + +import lombok.Data; +import java.util.List; + +@Data +public class ChatCompletionResponseDTO { + private String id; + private String object; + private long created; + private String model; + private List choices; +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Choice.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Choice.java new file mode 100644 index 0000000000000000000000000000000000000000..27cb2ef8673becf3da2fcfbc713eedec6a77cb7a --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Choice.java @@ -0,0 +1,10 @@ +package com.example.user.adapter.in.web.dto; + +import lombok.Data; + +@Data +public class Choice { + private int index; + private Message message; + private String finish_reason; +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/LoginRequest.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/LoginRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..b7d44784e387f88d7eae060ee2d2a0b7f76422e8 --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/LoginRequest.java @@ -0,0 +1,24 @@ +package com.example.user.adapter.in.web.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginRequest { + + + @NotBlank(message = "用户名不能为空") + @Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间") + private String username; + + + @NotBlank(message = "密码不能为空") + @Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间") + private String password; +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/LoginResponse.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/LoginResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..4c6ec001c741ed052deddcc31e017eb1f78f5d0d --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/LoginResponse.java @@ -0,0 +1,16 @@ +package com.example.user.adapter.in.web.dto; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.Builder; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LoginResponse { + private String accessToken; + private String tokenType = "Bearer"; + private UserInfo userInfo; + private Long expiresIn; +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Message.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Message.java new file mode 100644 index 0000000000000000000000000000000000000000..5a67d73b5ac2e7aafb332162867783cc22838bbb --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Message.java @@ -0,0 +1,9 @@ +package com.example.user.adapter.in.web.dto; + +import lombok.Data; + +@Data +public class Message { + private String role; + private String content; +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/RegisterRequest.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/RegisterRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..d1804a6432872973a1df37618ab7ed76e567fb59 --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/RegisterRequest.java @@ -0,0 +1,45 @@ +package com.example.user.adapter.in.web.dto; + + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +@Data +@Schema(description = "用户注册请求") +public class RegisterRequest { + + @NotBlank(message = "用户名不能为空") + @Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间") + @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线") + @Schema(description = "用户名", example = "testuser") + private String username; + + @NotBlank(message = "密码不能为空") + @Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间") + @Schema(description = "密码", example = "123456") + private String password; + + + @NotBlank(message = "确认密码不能为空") + @Schema(description = "确认密码", example = "123456") + private String confirmPassword; + + + @Email(message = "邮箱格式不正确") + @Schema(description = "邮箱", example = "test@example.com") + private String email; + + + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + @Schema(description = "手机号", example = "13800138000") + private String phone; + + + @Schema(description = "真实姓名", example = "张三") + private String realName; +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Result.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Result.java new file mode 100644 index 0000000000000000000000000000000000000000..8fa78565f282599d83e3261d5279a3f42febc040 --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Result.java @@ -0,0 +1,43 @@ +package com.example.user.adapter.in.web.dto; + + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Result { + private Integer code; + private String message; + private T data; + + public static Result success(T data) { + return new Result<>(200, "操作成功", data); + } + + public static Result success() { + return new Result<>(200, "操作成功", null); + } + + public static Result success(String message, T data) { + return new Result<>(200, message, data); + } + + public static Result error(String message) { + return new Result<>(500, message, null); + } + + public static Result error(Integer code, String message) { + return new Result<>(code, message, null); + } + + public static Result unauthorized(String message) { + return new Result<>(401, message, null); + } + + public static Result forbidden(String message) { + return new Result<>(403, message, null); + } +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/UserInfo.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/UserInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..a0b902407630459d72ee9b98cd645e4317b7864b --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/UserInfo.java @@ -0,0 +1,23 @@ +package com.example.user.adapter.in.web.dto; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.Builder; + +import java.time.LocalDateTime; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserInfo { + private Long id; + private String username; + private String email; + private String phone; + private String realName; + private Integer status; + private String role; + private LocalDateTime createTime; + private LocalDateTime lastLoginTime; +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/exception/GlobalExceptionHandler.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..6f0c31b0756f160d9d6d3a743c023793b0fe5504 --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,127 @@ +package com.example.user.adapter.in.web.exception; + +import com.example.user.adapter.in.web.dto.Result; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import java.util.stream.Collectors; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(AuthenticationException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public Result handleAuthenticationException(AuthenticationException e) { + log.error("认证异常: {}", e.getMessage()); + return Result.unauthorized("认证失败: " + e.getMessage()); + } + + @ExceptionHandler(BadCredentialsException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public Result handleBadCredentialsException(BadCredentialsException e) { + log.error("凭据错误: {}", e.getMessage()); + return Result.unauthorized("用户名或密码错误"); + } + + @ExceptionHandler(AccessDeniedException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public Result handleAccessDeniedException(AccessDeniedException e) { + log.error("访问拒绝: {}", e.getMessage()); + return Result.forbidden("访问被拒绝,权限不足"); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + String errorMessage = e.getBindingResult().getFieldErrors().stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining(", ")); + log.error("参数校验失败: {}", errorMessage); + return Result.error("参数校验失败: " + errorMessage); + } + + @ExceptionHandler(BindException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleBindException(BindException e) { + String errorMessage = e.getFieldErrors().stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining(", ")); + log.error("参数绑定失败: {}", errorMessage); + return Result.error("参数绑定失败: " + errorMessage); + } + + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleConstraintViolationException(ConstraintViolationException e) { + String errorMessage = e.getConstraintViolations().stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.joining(", ")); + log.error("约束违反: {}", errorMessage); + return Result.error("参数校验失败: " + errorMessage); + } + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleIllegalArgumentException(IllegalArgumentException e) { + log.error("非法参数: {}", e.getMessage()); + return Result.error("参数错误: " + e.getMessage()); + } + + @ExceptionHandler(NullPointerException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Result handleNullPointerException(NullPointerException e) { + log.error("空指针异常", e); + return Result.error("系统内部错误,请联系管理员"); + } + + @ExceptionHandler(RuntimeException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Result handleRuntimeException(RuntimeException e) { + log.error("运行时异常: {}", e.getMessage(), e); + return Result.error("系统异常: " + e.getMessage()); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Result handleException(Exception e) { + log.error("未知异常: {}", e.getMessage(), e); + return Result.error("系统内部错误,请联系管理员"); + } + + public static class BusinessException extends RuntimeException { + private final int code; + + public BusinessException(String message) { + super(message); + this.code = 500; + } + + public BusinessException(int code, String message) { + super(message); + this.code = code; + } + + public int getCode() { + return code; + } + } + + @ExceptionHandler(BusinessException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Result handleBusinessException(BusinessException e) { + log.error("业务异常: {}", e.getMessage()); + return Result.error(e.getCode(), e.getMessage()); + } +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/filter/JwtAuthenticationFilter.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..c4ed4049fac246fd763405b8cf7a67b7abd84234 --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/filter/JwtAuthenticationFilter.java @@ -0,0 +1,112 @@ +package com.example.user.adapter.in.web.filter; + +import com.example.user.service.application.service.TokenBlacklistService; +import com.example.user.service.common.JwtUtil; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Slf4j +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private TokenBlacklistService tokenBlacklistService; + + @Autowired + private ApplicationContext applicationContext; + + private UserDetailsService userDetailsService; + + private UserDetailsService getUserDetailsService() { + if (userDetailsService == null) { + userDetailsService = applicationContext.getBean(UserDetailsService.class); + } + return userDetailsService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + try { + String jwt = getJwtFromRequest(request); + + if (StringUtils.hasText(jwt)) { + if (tokenBlacklistService.isBlacklisted(jwt)) { + log.warn("Token is blacklisted (user has logged out): {}", + jwt.substring(0, Math.min(20, jwt.length())) + "..."); + SecurityContextHolder.clearContext(); + filterChain.doFilter(request, response); + return; + } + + String username = jwtUtil.getUsernameFromToken(jwt); + + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = getUserDetailsService().loadUserByUsername(username); + + if (jwtUtil.validateToken(jwt, userDetails.getUsername())) { + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + log.debug("JWT authentication successful for user: {}", username); + } else { + log.warn("JWT token validation failed for user: {}", username); + } + } + } + } catch (Exception e) { + log.error("Cannot set user authentication: {}", e.getMessage()); + SecurityContextHolder.clearContext(); + } + + filterChain.doFilter(request, response); + } + + private String getJwtFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + return jwtUtil.extractTokenFromHeader(bearerToken); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + String path = request.getRequestURI(); + + boolean isBusinessWhitelist = path.startsWith("/users/login") || + path.startsWith("/users/register"); + + boolean isDocWhitelist = path.startsWith("/doc.html") || + path.startsWith("/swagger-ui") || + path.startsWith("/v3/api-docs") || + path.startsWith("/webjars"); + + return isBusinessWhitelist || isDocWhitelist; + } +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/Demo2Listener.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/Demo2Listener.java new file mode 100644 index 0000000000000000000000000000000000000000..a44d89c72f6aa20217ef24e6dfafd37f87948bb2 --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/Demo2Listener.java @@ -0,0 +1,16 @@ +package com.example.user.adapter.in.web.listener; + +import com.example.user.service.domain.Student; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +@Component +@RabbitListener(queues = "test2") +public class Demo2Listener { + + @RabbitHandler + public void handler(Student stu){ + System.out.println("收到的消息是:" + stu); + } +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/Demo3Listener.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/Demo3Listener.java new file mode 100644 index 0000000000000000000000000000000000000000..6c96ead32d78cd034913335b2d12a2f5cb34f5ba --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/Demo3Listener.java @@ -0,0 +1,19 @@ +package com.example.user.adapter.in.web.listener; + +import com.alibaba.fastjson2.JSON; +import com.example.user.service.domain.Student; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +@Component +@RabbitListener(queues = "test3") +public class Demo3Listener { + + @RabbitHandler + public void handler(String stu){ + // JSON字符串还原成对象 + Student student = JSON.parseObject(stu, Student.class); + System.out.println("收到的消息是:" + student); + } +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/DemoListener.java b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/DemoListener.java new file mode 100644 index 0000000000000000000000000000000000000000..9ecfb0b886de441e1611fa256b21766d5753a4b4 --- /dev/null +++ b/user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/DemoListener.java @@ -0,0 +1,15 @@ +package com.example.user.adapter.in.web.listener; + +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +@Component +@RabbitListener(queues = "test") +public class DemoListener { + + @RabbitHandler + public void handler(String msg){ + System.out.println("收到的消息是:" + msg); + } +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/pom.xml b/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/pom.xml index 812d59d239543ac421905880a779976e3b0c980f..7c05375bd394081a29652aaed393e9117eafbbfd 100644 --- a/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/pom.xml +++ b/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/pom.xml @@ -7,11 +7,11 @@ 0.0.1-SNAPSHOT user-adapter-out-persistence user-adapter-out-persistence - - com.example - user-adapter-out - 0.0.1-SNAPSHOT - + + com.example + user-adapter-out + 0.0.1-SNAPSHOT + diff --git a/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/convertor/UserConvertor.java b/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/convertor/UserConvertor.java index cc56ab567d7dd43b0fd158c989ea8b8069c882fb..700add37ec8842d0ee2e3ceeb18bf442fb565b48 100644 --- a/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/convertor/UserConvertor.java +++ b/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/convertor/UserConvertor.java @@ -4,6 +4,9 @@ import com.example.user.adapter.out.persistence.entity.UserEntity; import com.example.user.service.domain.User; import com.example.user.service.domain.valueobject.*; +import java.time.LocalDateTime; + + public class UserConvertor { /** * 将持久化实体转换为领域对象 @@ -28,14 +31,21 @@ public class UserConvertor { * @param user 用户领域对象,包含用户的所有业务属性和行为 * @return UserEntity 数据库用户实体,包含用户的所有持久化数据 */ - public static UserEntity toEntity(User user) { - return new UserEntity( - user.getId().id(), - user.getName().username(), - user.getAge().age(), - user.getEmail().email(), - user.getPassword().encryptedValue(), - user.getIsSuper().value() ? 1 : 0 - ); - } + public static UserEntity toEntity(User user) { + return new UserEntity( + user.getId().id(), + user.getName().username(), + user.getAge().age(), + user.getEmail().email(), + user.getPassword().encryptedValue(), + user.getIsSuper().value() ? 1 : 0, + "USER", // 默认角色 + 1, // 默认状态(启用) + LocalDateTime.now(), // 默认创建时间 + LocalDateTime.now(), // 默认更新时间 + null, // 最后登录时间 + null, // 电话(默认为空) + null // 真实姓名(默认为空) + ); + } } diff --git a/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/entity/UserEntity.java b/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/entity/UserEntity.java index 1455f81d5b6a2f987fc9344007f8013df1f4c4fa..b3324e10c7e6f4ae71ea14a5f0556e24b4308dfe 100644 --- a/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/entity/UserEntity.java +++ b/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/entity/UserEntity.java @@ -3,25 +3,54 @@ package com.example.user.adapter.out.persistence.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; + @Data -@AllArgsConstructor + @NoArgsConstructor @TableName("user") public class UserEntity { - @TableId(type= IdType.ASSIGN_ID) + @TableId(type = IdType.ASSIGN_ID) private long id; private String name; private Integer age; private String email; private String password; private Integer isSuper; - public UserEntity(long value, String value1, int value2, String value3) { - } + private String role; + private Integer status; + private LocalDateTime createTime; + private LocalDateTime updateTime; + private LocalDateTime lastLoginTime; + private String phone; // 新增字段 + private String realName; // 新增字段 + + // 简化构造方法 public UserEntity(long id, String name, Integer age, String email, String password) { - this(id, name, age, email, password, 0); // 默认isSuper为0 + this(id, name, age, email, password, 0, "USER", 1, + LocalDateTime.now(), LocalDateTime.now(), null, null, null); + } + + // 完整构造方法 + public UserEntity(long id, String name, Integer age, String email, String password, + Integer isSuper, String role, Integer status, LocalDateTime createTime, + LocalDateTime updateTime, LocalDateTime lastLoginTime, + String phone, String realName) { + this.id = id; + this.name = name; + this.age = age; + this.email = email; + this.password = password; + this.isSuper = isSuper; + this.role = role; + this.status = status; + this.createTime = createTime; + this.updateTime = updateTime; + this.lastLoginTime = lastLoginTime; + this.phone = phone; + this.realName = realName; } -} +} \ No newline at end of file diff --git a/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/mapper/UserMapper.java b/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/mapper/UserMapper.java index 92125d3cc1d3386d1f0ddd3c2fae1953dd77a5f4..6404c2086144dac68fb2a2d83bcf3612b96e413d 100644 --- a/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/mapper/UserMapper.java +++ b/user-service/user-service-adapter/user-adapter-out/user-adapter-out-persistence/src/main/java/com/example/user/adapter/out/persistence/mapper/UserMapper.java @@ -2,6 +2,11 @@ package com.example.user.adapter.out.persistence.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.user.adapter.out.persistence.entity.UserEntity; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; public interface UserMapper extends BaseMapper { -} + + @Select("SELECT * FROM user WHERE name = #{username}") + UserEntity selectByUsername(@Param("username") String username); +} \ No newline at end of file diff --git a/user-service/user-service-application/pom.xml b/user-service/user-service-application/pom.xml index df05045dc789c7f49514ce9c6c7c90a6a664231f..2a408e19d2270116dc5f7459b127ce38fdb4110a 100644 --- a/user-service/user-service-application/pom.xml +++ b/user-service/user-service-application/pom.xml @@ -36,6 +36,12 @@ user-service-domain 0.0.1-SNAPSHOT + + com.example + user-adapter-out-persistence + 0.0.1-SNAPSHOT + compile + @@ -55,3 +61,4 @@ + diff --git a/user-service/user-service-application/src/main/java/com/example/user/service/application/service/CustomUserDetailsService.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/service/CustomUserDetailsService.java new file mode 100644 index 0000000000000000000000000000000000000000..ed0381156b763f258ef0c885d0c8bc531a6820a9 --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/service/CustomUserDetailsService.java @@ -0,0 +1,90 @@ +package com.example.user.service.application.service; +import com.example.user.adapter.out.persistence.entity.UserEntity; +import com.example.user.adapter.out.persistence.mapper.UserMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.Collections; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final PasswordEncoder passwordEncoder; + + private final UserMapper userMapper; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + log.debug("Loading user by username: {}", username); + + UserEntity user = userMapper.selectByUsername(username); + if (user == null) { + log.warn("User not found: {}", username); + throw new UsernameNotFoundException("用户不存在: " + username); + } + + if (user.getStatus() == 0) { + log.warn("User is disabled: {}", username); + throw new UsernameNotFoundException("用户已被禁用: " + username); + } + + return org.springframework.security.core.userdetails.User.builder() + .username(user.getName()) + .password(user.getPassword()) + .authorities(Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole()))) + .accountExpired(false) + .accountLocked(false) + .credentialsExpired(false) + .disabled(user.getStatus() == 0) + .build(); + } + + public UserEntity getUserByUsername(String username) { + return userMapper.selectByUsername(username); + } + + public UserEntity authenticate(String username, String password) { + UserEntity user = userMapper.selectByUsername(username); + if (user == null) { + log.warn("用户不存在: {}", username); + return null; + } + + if (!passwordEncoder.matches(password, user.getPassword())) { + log.warn("用户登录失败: 密码错误 - {}", username); + return null; + } + + log.info("用户登录成功: {}", username); + return user; + } + + public String generatePasswordHash(String password) { + return passwordEncoder.encode(password); + } + + public int saveUser(UserEntity user) { + try { + int result = userMapper.insert(user); + + if (result > 0) { + log.info("用户保存成功: username={}, userId={}", user.getName(), user.getId()); + } else { + log.warn("用户保存失败: username={}", user.getName()); + } + + return result; + } catch (Exception e) { + log.error("保存用户异常: username={}, error={}", user.getName(), e.getMessage(), e); + return 0; + } + } +} \ No newline at end of file diff --git a/user-service/user-service-application/src/main/java/com/example/user/service/application/service/TokenBlacklistService.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/service/TokenBlacklistService.java new file mode 100644 index 0000000000000000000000000000000000000000..91459a3c5bf6bc9996763e5424d7aadc9398b093 --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/service/TokenBlacklistService.java @@ -0,0 +1,77 @@ +package com.example.user.service.application.service; +import com.example.user.service.common.JwtUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Service +@RequiredArgsConstructor +public class TokenBlacklistService { + + private final JwtUtil jwtUtil; + + private final ConcurrentHashMap blacklistedTokens = new ConcurrentHashMap<>(); + + public void addToBlacklist(String token) { + try { + Date expirationTime = jwtUtil.getExpirationDateFromToken(token); + + blacklistedTokens.put(token, expirationTime); + + log.info("令牌已添加到黑名单,过期时间: {}, 令牌前缀: {}...", + expirationTime, + token.substring(0, Math.min(token.length(), 10))); + + } catch (Exception e) { + log.warn("添加令牌到黑名单时发生错误: {}, 令牌前缀: {}...", + e.getMessage(), + token.substring(0, Math.min(token.length(), 10))); + } + } + + public boolean isBlacklisted(String token) { + boolean isBlacklisted = blacklistedTokens.containsKey(token); + + if (isBlacklisted) { + log.debug("检测到黑名单令牌访问尝试,令牌前缀: {}...", + token.substring(0, Math.min(token.length(), 10))); + } + + return isBlacklisted; + } + + @Scheduled(fixedRate = 3600000) + public void cleanupExpiredTokens() { + Date now = new Date(); + int initialSize = blacklistedTokens.size(); + + blacklistedTokens.entrySet().removeIf(entry -> { + Date expirationTime = entry.getValue(); + return expirationTime != null && now.after(expirationTime); + }); + + int finalSize = blacklistedTokens.size(); + int cleanedCount = initialSize - finalSize; + + if (cleanedCount > 0) { + log.info("黑名单清理完成,清理了 {} 个过期令牌,当前黑名单大小: {}", cleanedCount, finalSize); + } else { + log.debug("黑名单清理完成,无过期令牌需要清理,当前黑名单大小: {}", finalSize); + } + } + + public int getBlacklistSize() { + return blacklistedTokens.size(); + } + + public void clearBlacklist() { + int size = blacklistedTokens.size(); + blacklistedTokens.clear(); + log.warn("黑名单已被清空,共清理了 {} 个令牌", size); + } +} \ No newline at end of file diff --git a/user-service/user-service-application/src/main/java/com/example/user/service/application/service/UserLoginService.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/service/UserLoginService.java index b10d43e0179a76381f25cb3e62b9319077c76549..c0e55ef5ae2eefb987cf6b42e3162ca3216cd366 100644 --- a/user-service/user-service-application/src/main/java/com/example/user/service/application/service/UserLoginService.java +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/service/UserLoginService.java @@ -16,6 +16,9 @@ public class UserLoginService implements UserLoginUseCase { @Resource private GetUserByNamePort getUserByNamePort; + @Resource // 添加 JwtUtil 的注入 + private JwtUtil jwtUtil; + @Override public String login(UserLoginCommand userLoginCommand) { //验证用户 @@ -28,8 +31,8 @@ public class UserLoginService implements UserLoginUseCase { if(!user.validatePassword(userLoginCommand.password())){ throw new RuntimeException("密码错误"); } - // 签发token - String token = JwtUtil.generateToken( + // 使用注入的 jwtUtil 实例签发token + String token = jwtUtil.generateToken( // 改为实例调用 user.getId().id(), user.getName().username(), user.getIsSuper().value() diff --git a/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/UserServiceBootstrapApplication.java b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/UserServiceBootstrapApplication.java index 3ee204481e77b6984cfb2991e65757ef820bf6c7..d72ee5c719ef275f2d84959ba3550401fb4b4ab0 100644 --- a/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/UserServiceBootstrapApplication.java +++ b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/UserServiceBootstrapApplication.java @@ -3,13 +3,14 @@ package com.example.user.service.bootstrap; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication(scanBasePackages = "com.example") @MapperScan("com.example.user.adapter.out.persistence.mapper") +@EnableScheduling public class UserServiceBootstrapApplication { public static void main(String[] args) { SpringApplication.run(UserServiceBootstrapApplication.class, args); } - } diff --git a/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/BasicSecurityConfig.java b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/BasicSecurityConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..fa5d2875c6364b6a3dc944d2c6961b36e07ca835 --- /dev/null +++ b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/BasicSecurityConfig.java @@ -0,0 +1,90 @@ + +package com.example.user.service.bootstrap.config; + + +import com.example.user.adapter.in.web.filter.JwtAuthenticationFilter; +import com.example.user.service.application.service.CustomUserDetailsService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.Arrays; +import java.util.List; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true) +@RequiredArgsConstructor +public class BasicSecurityConfig { + + private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final CustomUserDetailsService userDetailsService; + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOriginPatterns(List.of("*")); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")); + configuration.setAllowedHeaders(List.of("*")); + configuration.setAllowCredentials(true); + configuration.setMaxAge(3600L); + configuration.setExposedHeaders(Arrays.asList("Authorization", "Content-Type")); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) + .authorizeHttpRequests(authz -> authz + .requestMatchers("/users/login", "/users/register").permitAll() + .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**").permitAll() + .requestMatchers("/doc.html", "/webjars/**", "/favicon.ico").permitAll() + .requestMatchers("/actuator/**").permitAll() + .requestMatchers("/error").permitAll() + .anyRequest().authenticated() + ) + .formLogin(AbstractHttpConfigurer::disable) + .logout(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .userDetailsService(userDetailsService) + .exceptionHandling(exceptions -> exceptions + .authenticationEntryPoint((request, response, authException) -> { + response.setStatus(401); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"code\":401,\"message\":\"未授权访问,请先登录\",\"data\":null}"); + }) + .accessDeniedHandler((request, response, accessDeniedException) -> { + response.setStatus(403); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"code\":403,\"message\":\"访问被拒绝,权限不足\",\"data\":null}"); + }) + ); + + return http.build(); + } +} \ No newline at end of file diff --git a/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/MoonShotConfig.java b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/MoonShotConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..e5870f90a108ae8d342c589aa8713ba45e60177f --- /dev/null +++ b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/MoonShotConfig.java @@ -0,0 +1,14 @@ +package com.example.user.service.bootstrap.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "moonshot") +public class MoonShotConfig { + private String url; + private String key; + private String model; +} \ No newline at end of file diff --git a/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/PasswordConfig.java b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/PasswordConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..c0391a3ee86a19e3ccb2bcacc4da09f635bb1512 --- /dev/null +++ b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/PasswordConfig.java @@ -0,0 +1,16 @@ + +package com.example.user.service.bootstrap.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 PasswordConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} \ No newline at end of file diff --git a/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/RabbitConfig.java b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/RabbitConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..0e1acabfa845f759d72c61df67315d8cd779d920 --- /dev/null +++ b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/RabbitConfig.java @@ -0,0 +1,55 @@ +package com.example.user.service.bootstrap.config; + +import com.example.user.service.domain.Student; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class RabbitConfig { + + @Bean + public Queue queue() { + return new Queue("test"); + } + + @Bean + public Queue queue2() { + return new Queue("test2"); + } + + @Bean + public Queue queue3() { + return new Queue("test3"); + } + + @Bean + public MessageConverter messageConverter() { + Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(); + // 配置类型映射器以支持 Student 类的反序列化 + DefaultJackson2JavaTypeMapper typeMapper = new DefaultJackson2JavaTypeMapper(); + Map> idClassMapping = new HashMap<>(); + // 添加 Student 类的映射 + idClassMapping.put("com.example.user.service.domain.Student", Student.class); + typeMapper.setIdClassMapping(idClassMapping); + typeMapper.setTrustedPackages("com.example.user.service.domain"); + converter.setJavaTypeMapper(typeMapper); + + return converter; + } + + @Bean + public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { + RabbitTemplate template = new RabbitTemplate(connectionFactory); + template.setMessageConverter(messageConverter()); + return template; + } +} \ No newline at end of file diff --git a/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/RestConfig.java b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/RestConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..f90f414f59065397e187c9732727e4acb30d6b2b --- /dev/null +++ b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/RestConfig.java @@ -0,0 +1,13 @@ +package com.example.user.service.bootstrap.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestConfig { + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} \ No newline at end of file diff --git a/user-service/user-service-bootstrap/src/main/resources/application.properties b/user-service/user-service-bootstrap/src/main/resources/application.properties index 3ffae5ba70d1950bb4d5530c8b2e561a15ee9e5e..8362bd4bc4c00682706a427ca52a65c804e3cd97 100644 --- a/user-service/user-service-bootstrap/src/main/resources/application.properties +++ b/user-service/user-service-bootstrap/src/main/resources/application.properties @@ -2,8 +2,21 @@ server.port=28080 spring.application.name=user-service +# JWT?? +jwt.secret=mySecretKeyForJwtTokenGenerationAndValidation123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +jwt.expiration=86400000 +moonshot.url=https://api.moonshot.cn/v1/chat/completions +moonshot.key=sk-lvNNBKhSo69MEhY16RXUJI0IreeiO9lw65JvqeKF1YxlRswQ +moonshot.model=kimi-k2-0905-preview +spring.rabbitmq.addresses=192.168.168.128 +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest + +spring.amqp.deserialization.trust.all = true + +# Nacos?? # Nacos认证信息 spring.cloud.nacos.discovery.username=nacos spring.cloud.nacos.discovery.password=nacos @@ -23,3 +36,8 @@ spring.cloud.nacos.config.server-addr=192.168.168.128:8848 # spring.cloud.nacos.config.namespace= spring.config.import=nacos:${spring.application.name}.properties?refresh=true +# Swagger?? +springdoc.api-docs.enabled=true +springdoc.swagger-ui.enabled=true +springdoc.swagger-ui.path=/swagger-ui.html + diff --git a/user-service/user-service-common/pom.xml b/user-service/user-service-common/pom.xml index 20aa5fc4a8e02f4fce861aab3cca87288a00c8c4..27981bda0c4104c1fded4ccca62c176c5310e89d 100644 --- a/user-service/user-service-common/pom.xml +++ b/user-service/user-service-common/pom.xml @@ -18,31 +18,34 @@ org.springframework.boot spring-boot-starter + - io.jsonwebtoken - jjwt - 0.9.1 + org.springframework.boot + spring-boot-starter-security - + + - javax.xml.bind - jaxb-api - 2.3.1 + io.jsonwebtoken + jjwt-api + 0.11.5 - com.sun.xml.bind - jaxb-core - 2.3.0.1 + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime - com.sun.xml.bind - jaxb-impl - 2.3.3 + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + - javax.activation - activation - 1.1.1 + org.springframework.boot + spring-boot-starter-validation org.springframework.boot diff --git a/user-service/user-service-common/src/main/java/com/example/user/service/common/JwtUtil.java b/user-service/user-service-common/src/main/java/com/example/user/service/common/JwtUtil.java index f65593845868a5b959ccd7990d64a4675e1cf2bb..7acf87b90be0511c1c3d46b4faa003e6f49c30c0 100644 --- a/user-service/user-service-common/src/main/java/com/example/user/service/common/JwtUtil.java +++ b/user-service/user-service-common/src/main/java/com/example/user/service/common/JwtUtil.java @@ -3,25 +3,29 @@ package com.example.user.service.common; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import javax.crypto.SecretKey; import java.util.Date; import java.util.HashMap; import java.util.Map; - - @Slf4j +@Component public class JwtUtil { + @Value("${jwt.secret}") + private String SECRET_KEY; - private static final String SECRET_KEY = "123456"; - + @Value("${jwt.expiration}") + private long EXPIRATION_TIME; - private static final long EXPIRATION_TIME = 5 * 60 * 1000; // 5分钟 + @Value("${jwt.secret:mySecretKeyForJwtTokenGenerationAndValidation123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789}") + private String secret; - - - public static String generateToken(Long userId, String username, Boolean isSuper) { + public String generateToken(Long userId, String username, Boolean isSuper) { Map claims = new HashMap<>(); claims.put("id", userId); claims.put("name", username); @@ -35,44 +39,61 @@ public class JwtUtil { .compact(); } + // 新增的generateToken方法,用于用户名生成token + public String generateToken(String username) { + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + EXPIRATION_TIME); - public static Claims parseToken(String token) { - return Jwts.parser() - .setSigningKey(SECRET_KEY) - .parseClaimsJws(token) - .getBody(); + return Jwts.builder() + .setSubject(username) + .setIssuedAt(now) + .setExpiration(expiryDate) + .signWith(SignatureAlgorithm.HS512, SECRET_KEY) + .compact(); } - - public static Long getUserIdFromToken(String token) { - Claims claims = parseToken(token); - return claims.get("id", Long.class); + public String getUsernameFromToken(String token) { + Claims claims = getClaimsFromToken(token); + return claims.getSubject(); } + public boolean validateToken(String token, String username) { + try { + String tokenUsername = getUsernameFromToken(token); + return (username.equals(tokenUsername) && !isTokenExpired(token)); + } catch (Exception e) { + log.error("Token validation failed: {}", e.getMessage()); + return false; + } + } - public static String getUsernameFromToken(String token) { - Claims claims = parseToken(token); - return claims.get("name", String.class); + public boolean isTokenExpired(String token) { + Date expiration = getExpirationDateFromToken(token); + return expiration.before(new Date()); } - public static Boolean getIsSuperFromToken(String token) { - Claims claims = parseToken(token); - return claims.get("is_super", Boolean.class); + public Date getExpirationDateFromToken(String token) { + Claims claims = getClaimsFromToken(token); + return claims.getExpiration(); } + private Claims getClaimsFromToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } - public static boolean validateToken(String token) { - try { - parseToken(token); - return true; - } catch (Exception e) { - log.error("JWT令牌验证失败: {}", e.getMessage()); - return false; - } + private SecretKey getSigningKey() { + byte[] keyBytes = secret.getBytes(); + return Keys.hmacShaKeyFor(keyBytes); } - public static boolean isTokenExpired(String token) { - Claims claims = parseToken(token); - return claims.getExpiration().before(new Date()); + public String extractTokenFromHeader(String authHeader) { + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + return null; } } \ No newline at end of file diff --git a/user-service/user-service-domain/pom.xml b/user-service/user-service-domain/pom.xml index 502cbe0cdd4a7f789e429f56d87b2162bb6f9b42..06e61b3b19d8d065426f379380db1016df90f065 100644 --- a/user-service/user-service-domain/pom.xml +++ b/user-service/user-service-domain/pom.xml @@ -41,14 +41,14 @@ spring-security-crypto - - - - - - - - + + + + + + + + diff --git a/user-service/user-service-domain/src/main/java/com/example/user/service/domain/Student.java b/user-service/user-service-domain/src/main/java/com/example/user/service/domain/Student.java new file mode 100644 index 0000000000000000000000000000000000000000..04130658d9f7a846b55f6db25d3a957305fcaa68 --- /dev/null +++ b/user-service/user-service-domain/src/main/java/com/example/user/service/domain/Student.java @@ -0,0 +1,11 @@ +package com.example.user.service.domain; + +import lombok.Data; +import java.io.Serializable; + +@Data +public class Student implements Serializable { + private Long id; + private String name; + private Integer age; +} \ No newline at end of file diff --git a/user-service/user-service-domain/src/main/java/com/example/user/service/domain/config/PasswordConfig.java b/user-service/user-service-domain/src/main/java/com/example/user/service/domain/config/PasswordConfig.java deleted file mode 100644 index ebb036dc573ecf94a8af1b3a7b23bdf713906c9d..0000000000000000000000000000000000000000 --- a/user-service/user-service-domain/src/main/java/com/example/user/service/domain/config/PasswordConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -//package com.example.user.service.domain.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 PasswordConfig { -// @Bean -// public PasswordEncoder passwordEncoder() { -// return new BCryptPasswordEncoder(); -// } -//}