From 0ab094719ddd88cbc3c1b79e1712ac04fd6219c0 Mon Sep 17 00:00:00 2001 From: DXF <2794985061@qq.com> Date: Tue, 16 Sep 2025 12:54:01 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat=E5=B0=86=E6=B5=8B=E8=AF=95=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=B8=AD=E7=9A=84=E5=8A=9F=E8=83=BD=E7=A7=BB=E6=A4=8D?= =?UTF-8?q?=E8=BF=87=E6=9D=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- user-service/data.sql | 23 ++ user-service/pom.xml | 5 + user-service/user-service-adapter/pom.xml | 3 + .../user-adapter-in-web/pom.xml | 22 +- .../in/web/controller/UserController.java | 230 +++++++++++++++--- .../persistence/convertor/UserConvertor.java | 30 ++- .../out/persistence/entity/UserEntity.java | 42 +++- .../out/persistence/mapper/UserMapper.java | 7 +- user-service/user-service-application/pom.xml | 39 +++ .../config/BasicSecurityConfig.java | 90 +++++++ .../application/config/PasswordConfig.java | 16 ++ .../service/application/dto/LoginRequest.java | 24 ++ .../application/dto/LoginResponse.java | 16 ++ .../application/dto/RegisterRequest.java | 45 ++++ .../user/service/application/dto/Result.java | 43 ++++ .../service/application/dto/UserInfo.java | 23 ++ .../exception/GlobalExceptionHandler.java | 127 ++++++++++ .../filter/JwtAuthenticationFilter.java | 112 +++++++++ .../service/CustomUserDetailsService.java | 90 +++++++ .../service/TokenBlacklistService.java | 77 ++++++ .../application/service/UserLoginService.java | 9 +- .../service/application/util/JwtUtil.java | 99 ++++++++ .../UserServiceBootstrapApplication.java | 3 +- .../src/main/resources/application.properties | 18 +- user-service/user-service-common/pom.xml | 39 +-- .../example/user/service/common/JwtUtil.java | 78 ------ user-service/user-service-domain/pom.xml | 16 ++ .../service/domain/config/PasswordConfig.java | 14 -- 28 files changed, 1166 insertions(+), 174 deletions(-) create mode 100644 user-service/data.sql create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/config/BasicSecurityConfig.java create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/config/PasswordConfig.java create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/dto/LoginRequest.java create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/dto/LoginResponse.java create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/dto/RegisterRequest.java create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/dto/Result.java create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/dto/UserInfo.java create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/exception/GlobalExceptionHandler.java create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/filter/JwtAuthenticationFilter.java create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/service/CustomUserDetailsService.java create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/service/TokenBlacklistService.java create mode 100644 user-service/user-service-application/src/main/java/com/example/user/service/application/util/JwtUtil.java delete mode 100644 user-service/user-service-common/src/main/java/com/example/user/service/common/JwtUtil.java delete mode 100644 user-service/user-service-domain/src/main/java/com/example/user/service/domain/config/PasswordConfig.java diff --git a/user-service/data.sql b/user-service/data.sql new file mode 100644 index 0000000..662fe4a --- /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 c69afc8..e0f8382 100644 --- a/user-service/pom.xml +++ b/user-service/pom.xml @@ -49,6 +49,7 @@ pom import + com.alibaba.cloud spring-cloud-alibaba-dependencies @@ -104,4 +105,8 @@ + + + + diff --git a/user-service/user-service-adapter/pom.xml b/user-service/user-service-adapter/pom.xml index 3b61e10..a45f19d 100644 --- a/user-service/user-service-adapter/pom.xml +++ b/user-service/user-service-adapter/pom.xml @@ -36,4 +36,7 @@ + + + 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 b673bee..2c26e63 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 @@ -19,7 +19,6 @@ org.springframework.boot spring-boot-starter-web - org.springframework.boot spring-boot-starter-test @@ -34,7 +33,16 @@ org.projectlombok lombok - + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-test + test + com.example user-service-application @@ -46,6 +54,11 @@ knife4j-openapi3-jakarta-spring-boot-starter ${knife4j.version} + + com.example + user-adapter-out-persistence + 0.0.1-SNAPSHOT + @@ -61,7 +74,12 @@ UTF-8 + + + + + 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 96820c0..7226d7f 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,33 @@ 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.dto.*; 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.application.util.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.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; import java.util.List; @Slf4j @RequestMapping("/users") @RestController @RequiredArgsConstructor +@Tag(name = "用户认证和管理", description = "用户登录、注册、信息管理等接口") public class UserController { private final GetUserListUseCase getUserListUseCase; @@ -27,42 +36,192 @@ 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; + @PostMapping("/login") + @Operation(summary = "用户登录", description = "用户名密码登录,返回JWT token") + public Result login(@Valid @RequestBody LoginRequest loginRequest) { + try { + log.info("用户登录请求: {}", loginRequest.getUsername()); - @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; + // 使用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 +231,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 +249,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 +261,11 @@ 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; + } +} \ 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/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 cc56ab5..700add3 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 1455f81..3ee78ba 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 @@ -7,21 +7,51 @@ 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 92125d3..6404c20 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 df05045..0c5b343 100644 --- a/user-service/user-service-application/pom.xml +++ b/user-service/user-service-application/pom.xml @@ -36,6 +36,41 @@ user-service-domain 0.0.1-SNAPSHOT + + com.example + user-adapter-out-persistence + 0.0.1-SNAPSHOT + compile + + + + org.springframework.boot + spring-boot-starter-security + + + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + + org.springframework.boot + spring-boot-starter-validation + @@ -54,4 +89,8 @@ + + + + diff --git a/user-service/user-service-application/src/main/java/com/example/user/service/application/config/BasicSecurityConfig.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/config/BasicSecurityConfig.java new file mode 100644 index 0000000..1a68219 --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/config/BasicSecurityConfig.java @@ -0,0 +1,90 @@ + +package com.example.user.service.application.config; + + +import com.example.user.service.application.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-application/src/main/java/com/example/user/service/application/config/PasswordConfig.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/config/PasswordConfig.java new file mode 100644 index 0000000..f2b8d47 --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/config/PasswordConfig.java @@ -0,0 +1,16 @@ + +package com.example.user.service.application.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-application/src/main/java/com/example/user/service/application/dto/LoginRequest.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/dto/LoginRequest.java new file mode 100644 index 0000000..a3c2104 --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/dto/LoginRequest.java @@ -0,0 +1,24 @@ +package com.example.user.service.application.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-application/src/main/java/com/example/user/service/application/dto/LoginResponse.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/dto/LoginResponse.java new file mode 100644 index 0000000..ca10b4e --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/dto/LoginResponse.java @@ -0,0 +1,16 @@ +package com.example.user.service.application.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-application/src/main/java/com/example/user/service/application/dto/RegisterRequest.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/dto/RegisterRequest.java new file mode 100644 index 0000000..2d70df7 --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/dto/RegisterRequest.java @@ -0,0 +1,45 @@ +package com.example.user.service.application.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-application/src/main/java/com/example/user/service/application/dto/Result.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/dto/Result.java new file mode 100644 index 0000000..deb2679 --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/dto/Result.java @@ -0,0 +1,43 @@ +package com.example.user.service.application.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-application/src/main/java/com/example/user/service/application/dto/UserInfo.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/dto/UserInfo.java new file mode 100644 index 0000000..bdff5e9 --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/dto/UserInfo.java @@ -0,0 +1,23 @@ +package com.example.user.service.application.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-application/src/main/java/com/example/user/service/application/exception/GlobalExceptionHandler.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..0a4f67c --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/exception/GlobalExceptionHandler.java @@ -0,0 +1,127 @@ +package com.example.user.service.application.exception; + +import com.example.user.service.application.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-application/src/main/java/com/example/user/service/application/filter/JwtAuthenticationFilter.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..f5ea7c6 --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/filter/JwtAuthenticationFilter.java @@ -0,0 +1,112 @@ +package com.example.user.service.application.filter; + +import com.example.user.service.application.service.TokenBlacklistService; +import com.example.user.service.application.util.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-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 0000000..ed03811 --- /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 0000000..d74520f --- /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.application.util.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 b10d43e..fbdd79a 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 @@ -2,7 +2,7 @@ package com.example.user.service.application.service; import com.example.user.service.application.command.UserLoginCommand; import com.example.user.service.application.port.in.UserLoginUseCase; -import com.example.user.service.common.JwtUtil; +import com.example.user.service.application.util.JwtUtil; import com.example.user.service.domain.User; import com.example.user.service.domain.port.GetUserByNamePort; import jakarta.annotation.Resource; @@ -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-application/src/main/java/com/example/user/service/application/util/JwtUtil.java b/user-service/user-service-application/src/main/java/com/example/user/service/application/util/JwtUtil.java new file mode 100644 index 0000000..91580f0 --- /dev/null +++ b/user-service/user-service-application/src/main/java/com/example/user/service/application/util/JwtUtil.java @@ -0,0 +1,99 @@ +package com.example.user.service.application.util; + +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; + + @Value("${jwt.expiration}") + private long EXPIRATION_TIME; + + @Value("${jwt.secret:mySecretKeyForJwtTokenGenerationAndValidation123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789}") + private String secret; + // 原有的generateToken方法保持不变 + public String generateToken(Long userId, String username, Boolean isSuper) { + Map claims = new HashMap<>(); + claims.put("id", userId); + claims.put("name", username); + claims.put("is_super", isSuper); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) + .signWith(SignatureAlgorithm.HS256, SECRET_KEY) + .compact(); + } + + // 新增的generateToken方法,用于用户名生成token + public String generateToken(String username) { + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + EXPIRATION_TIME); + + return Jwts.builder() + .setSubject(username) + .setIssuedAt(now) + .setExpiration(expiryDate) + .signWith(SignatureAlgorithm.HS512, SECRET_KEY) + .compact(); + } + + 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 boolean isTokenExpired(String token) { + Date expiration = getExpirationDateFromToken(token); + return expiration.before(new Date()); + } + + 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(); + } + + private SecretKey getSigningKey() { + byte[] keyBytes = secret.getBytes(); + return Keys.hmacShaKeyFor(keyBytes); + } + + 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-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 3ee2044..d72ee5c 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/resources/application.properties b/user-service/user-service-bootstrap/src/main/resources/application.properties index 3ffae5b..5762cb2 100644 --- a/user-service/user-service-bootstrap/src/main/resources/application.properties +++ b/user-service/user-service-bootstrap/src/main/resources/application.properties @@ -2,24 +2,24 @@ server.port=28080 spring.application.name=user-service +# JWT?? +jwt.secret=mySecretKeyForJwtTokenGenerationAndValidation123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +jwt.expiration=86400000 - -# Nacos认证信息 +# Nacos?? spring.cloud.nacos.discovery.username=nacos spring.cloud.nacos.discovery.password=nacos -# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口 spring.cloud.nacos.discovery.server-addr=192.168.168.128:8848 -# 注册到 nacos 的指定 namespace,默认为 public spring.cloud.nacos.discovery.namespace=public -# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html -# Nacos认证信息 spring.cloud.nacos.config.username=nacos spring.cloud.nacos.config.password=nacos spring.cloud.nacos.config.contextPath=/nacos -# 设置配置中心服务端地址 spring.cloud.nacos.config.server-addr=192.168.168.128:8848 -# Nacos 配置中心的namespace。需要注意,如果使用 public 的 namcespace ,请不要填写这个值,直接留空即可 -# 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 20aa5fc..764140e 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 @@ -82,4 +85,8 @@ + + + + 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 deleted file mode 100644 index f655938..0000000 --- a/user-service/user-service-common/src/main/java/com/example/user/service/common/JwtUtil.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.example.user.service.common; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import lombok.extern.slf4j.Slf4j; - -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - - -@Slf4j -public class JwtUtil { - - - private static final String SECRET_KEY = "123456"; - - - private static final long EXPIRATION_TIME = 5 * 60 * 1000; // 5分钟 - - - - public static String generateToken(Long userId, String username, Boolean isSuper) { - Map claims = new HashMap<>(); - claims.put("id", userId); - claims.put("name", username); - claims.put("is_super", isSuper); - - return Jwts.builder() - .setClaims(claims) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) - .signWith(SignatureAlgorithm.HS256, SECRET_KEY) - .compact(); - } - - - public static Claims parseToken(String token) { - return Jwts.parser() - .setSigningKey(SECRET_KEY) - .parseClaimsJws(token) - .getBody(); - } - - - public static Long getUserIdFromToken(String token) { - Claims claims = parseToken(token); - return claims.get("id", Long.class); - } - - - public static String getUsernameFromToken(String token) { - Claims claims = parseToken(token); - return claims.get("name", String.class); - } - - public static Boolean getIsSuperFromToken(String token) { - Claims claims = parseToken(token); - return claims.get("is_super", Boolean.class); - } - - - public static boolean validateToken(String token) { - try { - parseToken(token); - return true; - } catch (Exception e) { - log.error("JWT令牌验证失败: {}", e.getMessage()); - return false; - } - } - - public static boolean isTokenExpired(String token) { - Claims claims = parseToken(token); - return claims.getExpiration().before(new Date()); - } -} \ 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 502cbe0..4a980ee 100644 --- a/user-service/user-service-domain/pom.xml +++ b/user-service/user-service-domain/pom.xml @@ -40,6 +40,18 @@ org.springframework.security spring-security-crypto + + com.example + user-service-application + 0.0.1-SNAPSHOT + compile + + + com.example + user-adapter-in-web + 0.0.1-SNAPSHOT + compile + @@ -67,4 +79,8 @@ + + + + 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 ebb036d..0000000 --- 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(); -// } -//} -- Gitee From 55b53500b39dd165ca932eb4eb7092b033208fb3 Mon Sep 17 00:00:00 2001 From: DXF <2794985061@qq.com> Date: Wed, 17 Sep 2025 23:41:24 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat=E5=B0=86rest-demo=E9=87=8C=E9=9D=A2?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=E7=A7=BB=E6=A4=8D=E5=88=B0qs-sys?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- user-service/pom.xml | 6 +- user-service/user-service-adapter/pom.xml | 3 - .../user-adapter-in-web/pom.xml | 22 +------ .../in/web/controller/UserController.java | 64 ++++++++++++++++++- .../in/web/dto/ChatCompletionRequestDTO.java | 12 ++++ .../in/web/dto/ChatCompletionResponseDTO.java | 13 ++++ .../user/adapter/in/web/dto/Choice.java | 10 +++ .../adapter/in/web}/dto/LoginRequest.java | 2 +- .../adapter/in/web}/dto/LoginResponse.java | 2 +- .../user/adapter/in/web/dto/Message.java | 9 +++ .../adapter/in/web}/dto/RegisterRequest.java | 2 +- .../user/adapter/in/web}/dto/Result.java | 2 +- .../user/adapter/in/web}/dto/UserInfo.java | 2 +- .../exception/GlobalExceptionHandler.java | 4 +- .../web}/filter/JwtAuthenticationFilter.java | 4 +- .../user-adapter-out-persistence/pom.xml | 10 +-- .../out/persistence/entity/UserEntity.java | 1 - user-service/user-service-application/pom.xml | 34 +--------- .../service/TokenBlacklistService.java | 2 +- .../application/service/UserLoginService.java | 2 +- .../config/BasicSecurityConfig.java | 4 +- .../bootstrap/config/MoonShotConfig.java | 14 ++++ .../bootstrap}/config/PasswordConfig.java | 2 +- .../service/bootstrap/config/RestConfig.java | 13 ++++ .../src/main/resources/application.properties | 13 ++++ user-service/user-service-common/pom.xml | 4 -- .../example/user/service/common}/JwtUtil.java | 4 +- user-service/user-service-domain/pom.xml | 32 +++------- 28 files changed, 179 insertions(+), 113 deletions(-) create mode 100644 user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/ChatCompletionRequestDTO.java create mode 100644 user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/ChatCompletionResponseDTO.java create mode 100644 user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Choice.java rename user-service/{user-service-application/src/main/java/com/example/user/service/application => user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web}/dto/LoginRequest.java (92%) rename user-service/{user-service-application/src/main/java/com/example/user/service/application => user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web}/dto/LoginResponse.java (86%) create mode 100644 user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Message.java rename user-service/{user-service-application/src/main/java/com/example/user/service/application => user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web}/dto/RegisterRequest.java (96%) rename user-service/{user-service-application/src/main/java/com/example/user/service/application => user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web}/dto/Result.java (95%) rename user-service/{user-service-application/src/main/java/com/example/user/service/application => user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web}/dto/UserInfo.java (90%) rename user-service/{user-service-application/src/main/java/com/example/user/service/application => user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web}/exception/GlobalExceptionHandler.java (97%) rename user-service/{user-service-application/src/main/java/com/example/user/service/application => user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web}/filter/JwtAuthenticationFilter.java (97%) rename user-service/{user-service-application/src/main/java/com/example/user/service/application => user-service-bootstrap/src/main/java/com/example/user/service/bootstrap}/config/BasicSecurityConfig.java (97%) create mode 100644 user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/MoonShotConfig.java rename user-service/{user-service-application/src/main/java/com/example/user/service/application => user-service-bootstrap/src/main/java/com/example/user/service/bootstrap}/config/PasswordConfig.java (88%) create mode 100644 user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/RestConfig.java rename user-service/{user-service-application/src/main/java/com/example/user/service/application/util => user-service-common/src/main/java/com/example/user/service/common}/JwtUtil.java (96%) diff --git a/user-service/pom.xml b/user-service/pom.xml index e0f8382..535fed0 100644 --- a/user-service/pom.xml +++ b/user-service/pom.xml @@ -49,7 +49,6 @@ pom import - com.alibaba.cloud spring-cloud-alibaba-dependencies @@ -105,8 +104,5 @@ - - - - + diff --git a/user-service/user-service-adapter/pom.xml b/user-service/user-service-adapter/pom.xml index a45f19d..3b61e10 100644 --- a/user-service/user-service-adapter/pom.xml +++ b/user-service/user-service-adapter/pom.xml @@ -36,7 +36,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 2c26e63..b673bee 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 @@ -19,6 +19,7 @@ org.springframework.boot spring-boot-starter-web + org.springframework.boot spring-boot-starter-test @@ -33,16 +34,7 @@ org.projectlombok lombok - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.security - spring-security-test - test - + com.example user-service-application @@ -54,11 +46,6 @@ knife4j-openapi3-jakarta-spring-boot-starter ${knife4j.version} - - com.example - user-adapter-out-persistence - 0.0.1-SNAPSHOT - @@ -74,12 +61,7 @@ UTF-8 - - - - - 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 7226d7f..2ac7e18 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 @@ -3,11 +3,10 @@ package com.example.user.adapter.in.web.controller; 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.dto.*; 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.application.util.JwtUtil; +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; @@ -15,12 +14,19 @@ 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 java.time.LocalDateTime; +import java.util.Arrays; import java.util.List; @Slf4j @@ -39,6 +45,17 @@ public class UserController { private final CustomUserDetailsService userDetailsService; private final JwtUtil jwtUtil; private final TokenBlacklistService tokenBlacklistService; + private final RestTemplate restTemplate; + + // 使用@Value注入配置,避免循环依赖 + @Value("${moonshot.url}") + private String moonshotUrl; + + @Value("${moonshot.key}") + private String moonshotKey; + + @Value("${moonshot.model}") + private String moonshotModel; @PostMapping("/login") @Operation(summary = "用户登录", description = "用户名密码登录,返回JWT token") @@ -268,4 +285,47 @@ public class UserController { } 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()); + } + } } \ 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 0000000..47c56ab --- /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 0000000..5c745bd --- /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 0000000..27cb2ef --- /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-application/src/main/java/com/example/user/service/application/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 similarity index 92% rename from user-service/user-service-application/src/main/java/com/example/user/service/application/dto/LoginRequest.java rename to user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/LoginRequest.java index a3c2104..b7d4478 100644 --- a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 @@ -1,4 +1,4 @@ -package com.example.user.service.application.dto; +package com.example.user.adapter.in.web.dto; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 similarity index 86% rename from user-service/user-service-application/src/main/java/com/example/user/service/application/dto/LoginResponse.java rename to user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/LoginResponse.java index ca10b4e..4c6ec00 100644 --- a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 @@ -1,4 +1,4 @@ -package com.example.user.service.application.dto; +package com.example.user.adapter.in.web.dto; import lombok.Data; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; 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 0000000..5a67d73 --- /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-application/src/main/java/com/example/user/service/application/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 similarity index 96% rename from user-service/user-service-application/src/main/java/com/example/user/service/application/dto/RegisterRequest.java rename to user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/RegisterRequest.java index 2d70df7..d1804a6 100644 --- a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 @@ -1,4 +1,4 @@ -package com.example.user.service.application.dto; +package com.example.user.adapter.in.web.dto; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 similarity index 95% rename from user-service/user-service-application/src/main/java/com/example/user/service/application/dto/Result.java rename to user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/Result.java index deb2679..8fa7856 100644 --- a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 @@ -1,4 +1,4 @@ -package com.example.user.service.application.dto; +package com.example.user.adapter.in.web.dto; import lombok.Data; diff --git a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 similarity index 90% rename from user-service/user-service-application/src/main/java/com/example/user/service/application/dto/UserInfo.java rename to user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/dto/UserInfo.java index bdff5e9..a0b9024 100644 --- a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 @@ -1,4 +1,4 @@ -package com.example.user.service.application.dto; +package com.example.user.adapter.in.web.dto; import lombok.Data; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; diff --git a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 similarity index 97% rename from user-service/user-service-application/src/main/java/com/example/user/service/application/exception/GlobalExceptionHandler.java rename to user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/exception/GlobalExceptionHandler.java index 0a4f67c..6f0c31b 100644 --- a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 @@ -1,6 +1,6 @@ -package com.example.user.service.application.exception; +package com.example.user.adapter.in.web.exception; -import com.example.user.service.application.dto.Result; +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; diff --git a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 similarity index 97% rename from user-service/user-service-application/src/main/java/com/example/user/service/application/filter/JwtAuthenticationFilter.java rename to user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/filter/JwtAuthenticationFilter.java index f5ea7c6..c4ed404 100644 --- a/user-service/user-service-application/src/main/java/com/example/user/service/application/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 @@ -1,7 +1,7 @@ -package com.example.user.service.application.filter; +package com.example.user.adapter.in.web.filter; import com.example.user.service.application.service.TokenBlacklistService; -import com.example.user.service.application.util.JwtUtil; +import com.example.user.service.common.JwtUtil; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; 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 812d59d..7c05375 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/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 3ee78ba..b3324e1 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,7 +3,6 @@ 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; diff --git a/user-service/user-service-application/pom.xml b/user-service/user-service-application/pom.xml index 0c5b343..2a408e1 100644 --- a/user-service/user-service-application/pom.xml +++ b/user-service/user-service-application/pom.xml @@ -42,35 +42,6 @@ 0.0.1-SNAPSHOT compile - - - org.springframework.boot - spring-boot-starter-security - - - - - io.jsonwebtoken - jjwt-api - 0.11.5 - - - io.jsonwebtoken - jjwt-impl - 0.11.5 - runtime - - - io.jsonwebtoken - jjwt-jackson - 0.11.5 - runtime - - - - org.springframework.boot - spring-boot-starter-validation - @@ -89,8 +60,5 @@ - - - - + 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 index d74520f..91459a3 100644 --- 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 @@ -1,5 +1,5 @@ package com.example.user.service.application.service; -import com.example.user.service.application.util.JwtUtil; +import com.example.user.service.common.JwtUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; 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 fbdd79a..c0e55ef 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 @@ -2,7 +2,7 @@ package com.example.user.service.application.service; import com.example.user.service.application.command.UserLoginCommand; import com.example.user.service.application.port.in.UserLoginUseCase; -import com.example.user.service.application.util.JwtUtil; +import com.example.user.service.common.JwtUtil; import com.example.user.service.domain.User; import com.example.user.service.domain.port.GetUserByNamePort; import jakarta.annotation.Resource; diff --git a/user-service/user-service-application/src/main/java/com/example/user/service/application/config/BasicSecurityConfig.java b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/BasicSecurityConfig.java similarity index 97% rename from user-service/user-service-application/src/main/java/com/example/user/service/application/config/BasicSecurityConfig.java rename to user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/BasicSecurityConfig.java index 1a68219..fa5d287 100644 --- a/user-service/user-service-application/src/main/java/com/example/user/service/application/config/BasicSecurityConfig.java +++ b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/BasicSecurityConfig.java @@ -1,8 +1,8 @@ -package com.example.user.service.application.config; +package com.example.user.service.bootstrap.config; -import com.example.user.service.application.filter.JwtAuthenticationFilter; +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; 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 0000000..e5870f9 --- /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-application/src/main/java/com/example/user/service/application/config/PasswordConfig.java b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/PasswordConfig.java similarity index 88% rename from user-service/user-service-application/src/main/java/com/example/user/service/application/config/PasswordConfig.java rename to user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/PasswordConfig.java index f2b8d47..c0391a3 100644 --- a/user-service/user-service-application/src/main/java/com/example/user/service/application/config/PasswordConfig.java +++ b/user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/PasswordConfig.java @@ -1,5 +1,5 @@ -package com.example.user.service.application.config; +package com.example.user.service.bootstrap.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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 0000000..f90f414 --- /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 5762cb2..4bbddc9 100644 --- a/user-service/user-service-bootstrap/src/main/resources/application.properties +++ b/user-service/user-service-bootstrap/src/main/resources/application.properties @@ -6,16 +6,29 @@ spring.application.name=user-service 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 + + # Nacos?? +# Nacos认证信息 spring.cloud.nacos.discovery.username=nacos spring.cloud.nacos.discovery.password=nacos +# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口 spring.cloud.nacos.discovery.server-addr=192.168.168.128:8848 +# 注册到 nacos 的指定 namespace,默认为 public spring.cloud.nacos.discovery.namespace=public +# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html +# Nacos认证信息 spring.cloud.nacos.config.username=nacos spring.cloud.nacos.config.password=nacos spring.cloud.nacos.config.contextPath=/nacos +# 设置配置中心服务端地址 spring.cloud.nacos.config.server-addr=192.168.168.128:8848 +# Nacos 配置中心的namespace。需要注意,如果使用 public 的 namcespace ,请不要填写这个值,直接留空即可 +# spring.cloud.nacos.config.namespace= spring.config.import=nacos:${spring.application.name}.properties?refresh=true # Swagger?? diff --git a/user-service/user-service-common/pom.xml b/user-service/user-service-common/pom.xml index 764140e..27981bd 100644 --- a/user-service/user-service-common/pom.xml +++ b/user-service/user-service-common/pom.xml @@ -85,8 +85,4 @@ - - - - diff --git a/user-service/user-service-application/src/main/java/com/example/user/service/application/util/JwtUtil.java b/user-service/user-service-common/src/main/java/com/example/user/service/common/JwtUtil.java similarity index 96% rename from user-service/user-service-application/src/main/java/com/example/user/service/application/util/JwtUtil.java rename to user-service/user-service-common/src/main/java/com/example/user/service/common/JwtUtil.java index 91580f0..7acf87b 100644 --- a/user-service/user-service-application/src/main/java/com/example/user/service/application/util/JwtUtil.java +++ b/user-service/user-service-common/src/main/java/com/example/user/service/common/JwtUtil.java @@ -1,4 +1,4 @@ -package com.example.user.service.application.util; +package com.example.user.service.common; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -24,7 +24,7 @@ public class JwtUtil { @Value("${jwt.secret:mySecretKeyForJwtTokenGenerationAndValidation123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789}") private String secret; - // 原有的generateToken方法保持不变 + public String generateToken(Long userId, String username, Boolean isSuper) { Map claims = new HashMap<>(); claims.put("id", userId); diff --git a/user-service/user-service-domain/pom.xml b/user-service/user-service-domain/pom.xml index 4a980ee..06e61b3 100644 --- a/user-service/user-service-domain/pom.xml +++ b/user-service/user-service-domain/pom.xml @@ -40,27 +40,15 @@ org.springframework.security spring-security-crypto - - com.example - user-service-application - 0.0.1-SNAPSHOT - compile - - - com.example - user-adapter-in-web - 0.0.1-SNAPSHOT - compile - - - - - - - - - + + + + + + + + @@ -79,8 +67,4 @@ - - - - -- Gitee From 379b4fa5bada97e9eb6f9341e5f951ec477322a5 Mon Sep 17 00:00:00 2001 From: DXF <2794985061@qq.com> Date: Wed, 17 Sep 2025 23:55:28 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat=E5=B0=86mq-demo=E7=9A=84=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=A7=BB=E6=A4=8D=E5=88=B0qs-sys=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- .../user-adapter-in-web/pom.xml | 11 ++++ .../in/web/controller/UserController.java | 59 +++++++++++++++++++ .../in/web/listener/Demo2Listener.java | 16 +++++ .../in/web/listener/Demo3Listener.java | 19 ++++++ .../adapter/in/web/listener/DemoListener.java | 15 +++++ .../bootstrap/config/RabbitConfig.java | 55 +++++++++++++++++ .../src/main/resources/application.properties | 5 ++ .../example/user/service/domain/Student.java | 11 ++++ 8 files changed, 191 insertions(+) create mode 100644 user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/Demo2Listener.java create mode 100644 user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/Demo3Listener.java create mode 100644 user-service/user-service-adapter/user-adapter-in/user-adapter-in-web/src/main/java/com/example/user/adapter/in/web/listener/DemoListener.java create mode 100644 user-service/user-service-bootstrap/src/main/java/com/example/user/service/bootstrap/config/RabbitConfig.java create mode 100644 user-service/user-service-domain/src/main/java/com/example/user/service/domain/Student.java 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 b673bee..90440d6 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 2ac7e18..15f4a91 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 @@ -24,6 +24,9 @@ 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; @@ -46,6 +49,7 @@ public class UserController { private final JwtUtil jwtUtil; private final TokenBlacklistService tokenBlacklistService; private final RestTemplate restTemplate; + private final RabbitTemplate rabbitTemplate; // 使用@Value注入配置,避免循环依赖 @Value("${moonshot.url}") @@ -328,4 +332,59 @@ public class UserController { 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/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 0000000..a44d89c --- /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 0000000..6c96ead --- /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 0000000..9ecfb0b --- /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-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 0000000..0e1acab --- /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/resources/application.properties b/user-service/user-service-bootstrap/src/main/resources/application.properties index 4bbddc9..8362bd4 100644 --- a/user-service/user-service-bootstrap/src/main/resources/application.properties +++ b/user-service/user-service-bootstrap/src/main/resources/application.properties @@ -10,6 +10,11 @@ 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认证信息 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 0000000..0413065 --- /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 -- Gitee