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