Ver código fonte

Implements user authentication feature

Adds login and registration endpoints
Updates user model to include password hashing
Integrates JWT for secure token generation

Fixes #789
zrh1922741520 1 semana atrás
pai
commit
0a8bd8e739
42 arquivos alterados com 443 adições e 208 exclusões
  1. 0 0
      src/main/java/ai/huisuan/back/config/CustomAccessDeniedHandler.java
  2. 0 0
      src/main/java/ai/huisuan/back/config/CustomAuthenticationEntryPoint.java
  3. 0 31
      src/main/java/ai/huisuan/back/config/SecurityConfig.java
  4. 0 41
      src/main/java/ai/huisuan/back/controller/ChatController.java
  5. 0 37
      src/main/java/ai/huisuan/back/controller/LoginController.java
  6. 0 16
      src/main/java/ai/huisuan/back/model/History.java
  7. 1 2
      src/main/java/ai/java/back/BackApplication.java
  8. 26 0
      src/main/java/ai/java/back/config/CustomAccessDeniedHandler.java
  9. 26 0
      src/main/java/ai/java/back/config/CustomAuthenticationEntryPoint.java
  10. 0 0
      src/main/java/ai/java/back/config/CustomSseEmitter.java
  11. 33 0
      src/main/java/ai/java/back/config/JwtAuthenticationFilter.java
  12. 27 0
      src/main/java/ai/java/back/config/JwtAuthenticationToken.java
  13. 80 0
      src/main/java/ai/java/back/config/SecurityConfig.java
  14. 42 0
      src/main/java/ai/java/back/controller/ChatController.java
  15. 39 0
      src/main/java/ai/java/back/controller/LoginController.java
  16. 2 1
      src/main/java/ai/java/back/entity/DialogueSessionInfo.java
  17. 2 1
      src/main/java/ai/java/back/entity/HistoryRecordInfo.java
  18. 1 1
      src/main/java/ai/java/back/entity/UserInfo.java
  19. 1 1
      src/main/java/ai/java/back/entity/UserLoginInfo.java
  20. 2 3
      src/main/java/ai/java/back/model/ChatRequest.java
  21. 1 1
      src/main/java/ai/java/back/model/ChatResponse.java
  22. 1 1
      src/main/java/ai/java/back/model/ChatStreamResponse.java
  23. 16 0
      src/main/java/ai/java/back/model/History.java
  24. 1 1
      src/main/java/ai/java/back/model/LoginRequest.java
  25. 3 2
      src/main/java/ai/java/back/model/LoginResponseModel.java
  26. 1 2
      src/main/java/ai/java/back/model/Message.java
  27. 1 1
      src/main/java/ai/java/back/model/OllamaChatRequest.java
  28. 1 1
      src/main/java/ai/java/back/model/OllamaChatResponse.java
  29. 1 1
      src/main/java/ai/java/back/model/OllamaMessage.java
  30. 1 1
      src/main/java/ai/java/back/model/RegisterRequest.java
  31. 0 0
      src/main/java/ai/java/back/model/ResponseModel.java
  32. 2 2
      src/main/java/ai/java/back/repository/DialogueSessionsRepository.java
  33. 2 2
      src/main/java/ai/java/back/repository/HistoryRecordsRepository.java
  34. 2 2
      src/main/java/ai/java/back/repository/UserLoginInfoRepository.java
  35. 2 2
      src/main/java/ai/java/back/repository/UserRepository.java
  36. 4 6
      src/main/java/ai/java/back/service/ChatService.java
  37. 1 1
      src/main/java/ai/java/back/service/LoginService.java
  38. 82 42
      src/main/java/ai/java/back/service/impl/ChatServiceImpl.java
  39. 6 6
      src/main/java/ai/java/back/service/impl/LoginServiceImpl.java
  40. 31 0
      src/main/java/ai/java/back/util/JwtUtil.java
  41. 2 0
      src/main/resources/application.properties
  42. 0 0
      src/test/java/ai/huisuan/back/service/impl/LoginServiceImplTest.java

+ 0 - 0
src/main/java/ai/huisuan/back/config/CustomAccessDeniedHandler.java


+ 0 - 0
src/main/java/ai/huisuan/back/config/CustomAuthenticationEntryPoint.java


+ 0 - 31
src/main/java/ai/huisuan/back/config/SecurityConfig.java

@@ -1,31 +0,0 @@
-package ai.huisuan.back.config;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.web.SecurityFilterChain;
-
-@Configuration
-public class SecurityConfig {
-
-    @Bean
-    public PasswordEncoder passwordEncoder() {
-        return new BCryptPasswordEncoder();
-    }
-
-    @Bean
-    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
-        http
-            .csrf(csrf -> csrf.disable())
-            .authorizeHttpRequests(auth -> auth
-                .requestMatchers("/v1/auth/**").permitAll() // 允许访问登录和注册接口
-                .anyRequest().authenticated())
-            .formLogin(form -> form.disable()) // 禁用默认登录页面
-            .logout(logout -> logout
-                .logoutUrl("/logout")
-                .logoutSuccessUrl("/login-page")); // 确保与 loginPage 一致
-        return http.build();
-    }
-}

+ 0 - 41
src/main/java/ai/huisuan/back/controller/ChatController.java

@@ -1,41 +0,0 @@
-package ai.huisuan.back.controller;
-
-import ai.huisuan.back.model.ChatRequest;
-import ai.huisuan.back.model.ChatResponse;
-import ai.huisuan.back.service.ChatService;
-import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
-
-import java.io.IOException;
-
-/**
- * @author 对话接口
- */
-@RestController
-@RequestMapping("/v1/chat")
-public class ChatController {
-
-
-    private final ChatService chatService;
-
-    public ChatController(ChatService chatService) {
-        this.chatService = chatService;
-    }
-
-
-    //直接输出
-    @PostMapping(value = "/completions", produces = MediaType.APPLICATION_JSON_VALUE)
-    public ChatResponse completions(@RequestBody ChatRequest chatRequest) throws IOException {
-            return chatService.completions(chatRequest);
-    }
-    //流式输出
-    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
-    public SseEmitter streamChat(@RequestBody ChatRequest request) {
-        return chatService.streamChat(request);
-    }
-
-}

+ 0 - 37
src/main/java/ai/huisuan/back/controller/LoginController.java

@@ -1,37 +0,0 @@
-package ai.huisuan.back.controller;
-
-import ai.huisuan.back.model.LoginRequest;
-import ai.huisuan.back.model.RegisterRequest;
-import ai.huisuan.back.model.ResponseModel;
-import ai.huisuan.back.service.LoginService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.*;
-
-@RestController
-@RequestMapping("/v1/auth")
-public class LoginController {
-
-    @Autowired
-    private LoginService loginService;
-
-    @PostMapping("/login")
-    public ResponseEntity<ResponseModel> login(@RequestBody LoginRequest loginRequest) {
-        boolean success = loginService.login(loginRequest.getUsername(), loginRequest.getPassword(), loginRequest.getIp());
-        if (success) {
-            return ResponseEntity.ok(new ResponseModel("登录成功", 200));
-        } else {
-            return ResponseEntity.status(401).body(new ResponseModel("用户名或密码错误", 401));
-        }
-    }
-
-    @PostMapping("/register")
-    public ResponseEntity<ResponseModel> register(@RequestBody RegisterRequest registerRequest) {
-        boolean success = loginService.registerUser(registerRequest.getUsername(), registerRequest.getPassword(), registerRequest.getPhone());
-        if (success) {
-            return ResponseEntity.ok(new ResponseModel("注册成功", 200));
-        } else {
-            return ResponseEntity.status(400).body(new ResponseModel("用户名已存在", 400));
-        }
-    }
-}

+ 0 - 16
src/main/java/ai/huisuan/back/model/History.java

@@ -1,16 +0,0 @@
-package ai.huisuan.back.model;
-
-
-import lombok.Builder;
-import lombok.Data;
-
-import java.util.List;
-
-@Data
-@Builder
-public class History {
-    // 信息列表
-    private List<Message> messages;
-    // 会话ID
-    private Integer sessionId;
-}

+ 1 - 2
src/main/java/ai/huisuan/back/BackApplication.java → src/main/java/ai/java/back/BackApplication.java

@@ -1,5 +1,4 @@
-package ai.huisuan.back;
-
+package ai.java.back;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.context.annotation.PropertySource;

+ 26 - 0
src/main/java/ai/java/back/config/CustomAccessDeniedHandler.java

@@ -0,0 +1,26 @@
+package ai.java.back.config;
+
+import org.springframework.stereotype.Component;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.security.access.AccessDeniedException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.ServletException;
+import java.io.IOException;
+
+@Component
+public class CustomAccessDeniedHandler implements AccessDeniedHandler {
+    @Override
+    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
+        if (response.isCommitted()) {
+            return;
+        }
+        String accept = request.getHeader("Accept");
+        if (accept != null && accept.contains("text/event-stream")) {
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+        response.getWriter().write("Access Denied: " + accessDeniedException.getMessage());
+    }
+}

+ 26 - 0
src/main/java/ai/java/back/config/CustomAuthenticationEntryPoint.java

@@ -0,0 +1,26 @@
+package ai.java.back.config;
+
+import org.springframework.stereotype.Component;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.core.AuthenticationException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.ServletException;
+import java.io.IOException;
+
+@Component
+public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
+        if (response.isCommitted()) {
+            return;
+        }
+        String accept = request.getHeader("Accept");
+        if (accept != null && accept.contains("text/event-stream")) {
+            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
+        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        response.getWriter().write("Unauthorized: " + authException.getMessage());
+    }
+}

+ 0 - 0
src/main/java/ai/java/back/config/CustomSseEmitter.java


+ 33 - 0
src/main/java/ai/java/back/config/JwtAuthenticationFilter.java

@@ -0,0 +1,33 @@
+package ai.java.back.config;
+
+import org.springframework.stereotype.Component;
+import ai.java.back.util.JwtUtil;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.lang.NonNull;
+import java.io.IOException;
+
+@Component
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+    @Override
+    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain)
+            throws ServletException, IOException {
+        String authorizationHeader = request.getHeader("Authorization");
+        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
+            String token = authorizationHeader.substring(7);
+            String username = JwtUtil.validateToken(token); // 静态调用
+            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+                JwtAuthenticationToken authentication = new JwtAuthenticationToken(username, null);
+                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+                SecurityContextHolder.getContext().setAuthentication(authentication);
+            }
+        }
+        filterChain.doFilter(request, response);
+    }
+}

+ 27 - 0
src/main/java/ai/java/back/config/JwtAuthenticationToken.java

@@ -0,0 +1,27 @@
+package ai.java.back.config;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.util.Collection;
+
+public class JwtAuthenticationToken extends AbstractAuthenticationToken {
+
+    private final Object principal;
+
+    public JwtAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
+        super(authorities);
+        this.principal = principal;
+        setAuthenticated(true);
+    }
+
+    @Override
+    public Object getCredentials() {
+        return null;
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return principal;
+    }
+}

+ 80 - 0
src/main/java/ai/java/back/config/SecurityConfig.java

@@ -0,0 +1,80 @@
+package ai.java.back.config;
+
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+    @Autowired
+    private JwtAuthenticationFilter jwtAuthenticationFilter;
+    @Autowired
+    private CustomAccessDeniedHandler customAccessDeniedHandler;
+    @Autowired
+    private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
+
+    @Bean
+    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+        http
+            .csrf(AbstractHttpConfigurer::disable)
+            .cors(Customizer.withDefaults())
+            .authorizeHttpRequests(auth -> auth
+                .requestMatchers("/v1/auth/**").permitAll()
+                .anyRequest().authenticated()
+            )
+            .sessionManagement(session -> session
+                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+            )
+            .securityContext(context -> context
+                .requireExplicitSave(false)
+            )
+            .exceptionHandling(ex -> ex
+                .accessDeniedHandler(customAccessDeniedHandler)
+                .authenticationEntryPoint(customAuthenticationEntryPoint)
+            )
+            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+        return http.build();
+    }
+
+    @Bean("securityContextExecutor")
+    public ExecutorService securityContextExecutor() {
+        ExecutorService executor = Executors.newCachedThreadPool();
+        return new DelegatingSecurityContextExecutorService(executor);
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    @Bean
+    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
+        return config.getAuthenticationManager();
+    }
+
+    // 已移除 JwtUtil Bean,无需注入
+
+    @PostConstruct
+    public void enableSecurityContextInheritance() {
+        SecurityContextHolder.setStrategyName(
+            SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
+        );
+    }
+}

+ 42 - 0
src/main/java/ai/java/back/controller/ChatController.java

@@ -0,0 +1,42 @@
+package ai.java.back.controller;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import ai.java.back.model.ChatRequest;
+import ai.java.back.model.ChatResponse;
+import ai.java.back.service.ChatService;
+
+import java.io.IOException;
+
+/**
+ * @author 对话接口
+ */
+@RestController
+@RequestMapping("/v1/chat")
+public class ChatController {
+
+
+    private final ChatService chatService;
+
+    public ChatController(ChatService chatService) {
+        this.chatService = chatService;
+    }
+
+
+    //直接输出
+    @PostMapping(value = "/completions", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ChatResponse completions(@RequestBody ChatRequest chatRequest) throws IOException {
+            return chatService.completions(chatRequest);
+    }
+    //流式输出
+    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    public SseEmitter streamChat(@RequestBody ChatRequest request) {
+        return chatService.streamChat(request);
+    }
+
+}

+ 39 - 0
src/main/java/ai/java/back/controller/LoginController.java

@@ -0,0 +1,39 @@
+package ai.java.back.controller;
+
+import ai.java.back.model.LoginRequest;
+import ai.java.back.model.RegisterRequest;
+import ai.java.back.model.LoginResponseModel;
+import ai.java.back.service.LoginService;
+import ai.java.back.util.JwtUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/v1/auth")
+public class LoginController {
+
+    @Autowired
+    private LoginService loginService;
+
+    @PostMapping("/login")
+    public ResponseEntity<LoginResponseModel> login(@RequestBody LoginRequest loginRequest) {
+        boolean success = loginService.login(loginRequest.getUsername(), loginRequest.getPassword(), loginRequest.getIp());
+        if (success) {
+            String token = JwtUtil.generateToken(loginRequest.getUsername());
+            return ResponseEntity.ok(new LoginResponseModel("登录成功" , 200, token));
+        } else {
+            return ResponseEntity.status(401).body(new LoginResponseModel("用户名或密码错误", 401, null));
+        }
+    }
+
+    @PostMapping("/register")
+    public ResponseEntity<LoginResponseModel> register(@RequestBody RegisterRequest registerRequest) {
+        boolean success = loginService.registerUser(registerRequest.getUsername(), registerRequest.getPassword(), registerRequest.getPhone());
+        if (success) {
+            return ResponseEntity.ok(new LoginResponseModel("注册成功", 200, null));
+        } else {
+            return ResponseEntity.status(400).body(new LoginResponseModel("用户名已存在", 400,null));
+        }
+    }
+}

+ 2 - 1
src/main/java/ai/huisuan/back/entity/DialogueSessionInfo.java → src/main/java/ai/java/back/entity/DialogueSessionInfo.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.entity;
+package ai.java.back.entity;
 
 import jakarta.persistence.*;
 import lombok.Getter;
@@ -12,6 +12,7 @@ import java.time.Instant;
 @Table(name = "dialogue_session_info")
 public class DialogueSessionInfo {
     @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
     @Column(name = "session_id", nullable = false)
     private Integer id;
 

+ 2 - 1
src/main/java/ai/huisuan/back/entity/HistoryRecordInfo.java → src/main/java/ai/java/back/entity/HistoryRecordInfo.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.entity;
+package ai.java.back.entity;
 
 import jakarta.persistence.*;
 import jakarta.validation.constraints.NotNull;
@@ -15,6 +15,7 @@ import java.time.Instant;
 @Table(name = "history_record_info")
 public class HistoryRecordInfo {
     @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
     @Column(name = "history_record_id", nullable = false)
     private Integer id;
 

+ 1 - 1
src/main/java/ai/huisuan/back/entity/UserInfo.java → src/main/java/ai/java/back/entity/UserInfo.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.entity;
+package ai.java.back.entity;
 
 import jakarta.persistence.*;
 import jakarta.validation.constraints.Size;

+ 1 - 1
src/main/java/ai/huisuan/back/entity/UserLoginInfo.java → src/main/java/ai/java/back/entity/UserLoginInfo.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.entity;
+package ai.java.back.entity;
 
 import jakarta.persistence.*;
 import jakarta.validation.constraints.NotNull;

+ 2 - 3
src/main/java/ai/huisuan/back/model/ChatRequest.java → src/main/java/ai/java/back/model/ChatRequest.java

@@ -1,5 +1,4 @@
-package ai.huisuan.back.model;
-
+package ai.java.back.model;
 
 import lombok.Data;
 
@@ -14,7 +13,7 @@ public class ChatRequest {
     // 会话id
     private Integer sessionId;
     // 历史记录
-    private History history;
+    private History history; // 允许 history 为 null
     // 问题信息
     private Message question;
 

+ 1 - 1
src/main/java/ai/huisuan/back/model/ChatResponse.java → src/main/java/ai/java/back/model/ChatResponse.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.model;
+package ai.java.back.model;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;

+ 1 - 1
src/main/java/ai/huisuan/back/model/ChatStreamResponse.java → src/main/java/ai/java/back/model/ChatStreamResponse.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.model;
+package ai.java.back.model;
 
 import lombok.AllArgsConstructor;
 import lombok.Builder;

+ 16 - 0
src/main/java/ai/java/back/model/History.java

@@ -0,0 +1,16 @@
+package ai.java.back.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.ArrayList;
+
+@Data
+@NoArgsConstructor
+public class History {
+    // 信息列表
+    private List<Message> messages = new ArrayList<>();
+    // 会话ID
+    private Integer sessionId;
+}

+ 1 - 1
src/main/java/ai/huisuan/back/model/LoginRequest.java → src/main/java/ai/java/back/model/LoginRequest.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.model;
+package ai.java.back.model;
 
 import lombok.Data;
 

+ 3 - 2
src/main/java/ai/huisuan/back/model/ResponseModel.java → src/main/java/ai/java/back/model/LoginResponseModel.java

@@ -1,11 +1,12 @@
-package ai.huisuan.back.model;
+package ai.java.back.model;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;
 
 @AllArgsConstructor
 @Data
-public class ResponseModel {
+public class LoginResponseModel {
     private String message;
     private int status;
+    private String token;
 }

+ 1 - 2
src/main/java/ai/huisuan/back/model/Message.java → src/main/java/ai/java/back/model/Message.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.model;
+package ai.java.back.model;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -9,7 +9,6 @@ import java.time.Instant;
 @NoArgsConstructor
 @AllArgsConstructor
 public @Data class Message {
-    private Integer messageId;
     // 角色
     private String role;
     // 信息内容

+ 1 - 1
src/main/java/ai/huisuan/back/model/OllamaChatRequest.java → src/main/java/ai/java/back/model/OllamaChatRequest.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.model;
+package ai.java.back.model;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;

+ 1 - 1
src/main/java/ai/huisuan/back/model/OllamaChatResponse.java → src/main/java/ai/java/back/model/OllamaChatResponse.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.model;
+package ai.java.back.model;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import lombok.Data;

+ 1 - 1
src/main/java/ai/huisuan/back/model/OllamaMessage.java → src/main/java/ai/java/back/model/OllamaMessage.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.model;
+package ai.java.back.model;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;

+ 1 - 1
src/main/java/ai/huisuan/back/model/RegisterRequest.java → src/main/java/ai/java/back/model/RegisterRequest.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.model;
+package ai.java.back.model;
 
 import lombok.Data;
 

+ 0 - 0
src/main/java/ai/java/back/model/ResponseModel.java


+ 2 - 2
src/main/java/ai/huisuan/back/repository/DialogueSessionsRepository.java → src/main/java/ai/java/back/repository/DialogueSessionsRepository.java

@@ -1,7 +1,7 @@
-package ai.huisuan.back.repository;
+package ai.java.back.repository;
 
 
-import ai.huisuan.back.entity.DialogueSessionInfo;
+import ai.java.back.entity.DialogueSessionInfo;
 import org.springframework.data.jpa.repository.JpaRepository;
 
 public interface DialogueSessionsRepository extends JpaRepository<DialogueSessionInfo, Integer> {

+ 2 - 2
src/main/java/ai/huisuan/back/repository/HistoryRecordsRepository.java → src/main/java/ai/java/back/repository/HistoryRecordsRepository.java

@@ -1,7 +1,7 @@
-package ai.huisuan.back.repository;
+package ai.java.back.repository;
 
 
-import ai.huisuan.back.entity.HistoryRecordInfo;
+import ai.java.back.entity.HistoryRecordInfo;
 import org.springframework.data.jpa.repository.JpaRepository;
 
 import java.util.List;

+ 2 - 2
src/main/java/ai/huisuan/back/repository/UserLoginInfoRepository.java → src/main/java/ai/java/back/repository/UserLoginInfoRepository.java

@@ -1,6 +1,6 @@
-package ai.huisuan.back.repository;
+package ai.java.back.repository;
 
-import ai.huisuan.back.entity.UserLoginInfo;
+import ai.java.back.entity.UserLoginInfo;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 

+ 2 - 2
src/main/java/ai/huisuan/back/repository/UserRepository.java → src/main/java/ai/java/back/repository/UserRepository.java

@@ -1,6 +1,6 @@
-package ai.huisuan.back.repository;
+package ai.java.back.repository;
 
-import ai.huisuan.back.entity.UserInfo;
+import ai.java.back.entity.UserInfo;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 

+ 4 - 6
src/main/java/ai/huisuan/back/service/ChatService.java → src/main/java/ai/java/back/service/ChatService.java

@@ -1,11 +1,9 @@
-package ai.huisuan.back.service;
+package ai.java.back.service;
 
-// ChatService.java (Service Layer Interface)
-
-
-import ai.huisuan.back.model.ChatRequest;
-import ai.huisuan.back.model.ChatResponse;
 import com.fasterxml.jackson.core.JsonProcessingException;
+
+import ai.java.back.model.ChatRequest;
+import ai.java.back.model.ChatResponse;
 import okhttp3.Request;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 

+ 1 - 1
src/main/java/ai/huisuan/back/service/LoginService.java → src/main/java/ai/java/back/service/LoginService.java

@@ -1,4 +1,4 @@
-package ai.huisuan.back.service;
+package ai.java.back.service;
 
 public interface LoginService {
     boolean login(String username, String password, String ip);

+ 82 - 42
src/main/java/ai/huisuan/back/service/impl/ChatServiceImpl.java → src/main/java/ai/java/back/service/impl/ChatServiceImpl.java

@@ -1,13 +1,14 @@
-package ai.huisuan.back.service.impl;
-
-import ai.huisuan.back.entity.DialogueSessionInfo;
-import ai.huisuan.back.entity.HistoryRecordInfo;
-import ai.huisuan.back.model.*;
-import ai.huisuan.back.repository.DialogueSessionsRepository;
-import ai.huisuan.back.repository.HistoryRecordsRepository;
-import ai.huisuan.back.service.ChatService;
+package ai.java.back.service.impl;
+
+import ai.java.back.entity.DialogueSessionInfo;
+import ai.java.back.entity.HistoryRecordInfo;
+import ai.java.back.model.*;
+import ai.java.back.repository.DialogueSessionsRepository;
+import ai.java.back.repository.HistoryRecordsRepository;
+import ai.java.back.service.ChatService;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.JsonNode;
 
 import okhttp3.*;
 import org.jetbrains.annotations.NotNull;
@@ -72,7 +73,7 @@ public class ChatServiceImpl implements ChatService {
         // 构建请求体
         List<OllamaMessage> ollamaMessages = new ArrayList<>();
 
-        List<Message> messages = requestDTO.getHistory().getMessages();
+        List<Message> messages = requestDTO.getHistory() != null ? requestDTO.getHistory().getMessages() : new ArrayList<>();
         for (Message message : messages) {
             OllamaMessage ollamaMessage = new OllamaMessage(message.getRole(), message.getContent());
             ollamaMessages.add(ollamaMessage);
@@ -99,40 +100,65 @@ public class ChatServiceImpl implements ChatService {
 
 
     @Override
-    public ChatResponse parseResponse(String responseBody,ChatRequest chatRequest) {
-
-            // id之后设置
-            Message message= new Message(1,"model",responseBody, Instant.now());
-            History history =chatRequest.getHistory();
-            history.getMessages().add(chatRequest.getQuestion());
-            history.getMessages().add(new Message());
-            ChatResponse responseDTO = new ChatResponse();
-            responseDTO.setAnswer(message);
-            responseDTO.setModel(message.getRole());
-            responseDTO.setCreatedAt(message.getTimestamp());
-            responseDTO.setHistory(history);
-
-            // 保存数据到数据库
-            HistoryRecordInfo historyRecordInfo = new HistoryRecordInfo();
-            historyRecordInfo.setContent(responseBody);
-            historyRecordInfo.setRole(responseDTO.getAnswer().getRole());
-            historyRecordInfo.setTimestamp(Instant.now());
-            DialogueSessionInfo dialogueSessionInfo = dialogueSessionsRepository.findById(chatRequest.getSessionId()).orElse(null);
-            historyRecordInfo.setSession(dialogueSessionInfo);
-            historyRecordsRepository.save(historyRecordInfo);
-
-            // 返回结果
-            return responseDTO;
-    }
+    public ChatResponse parseResponse(String responseBody, ChatRequest chatRequest) {
+        // 直接将响应内容作为普通字符串处理
+        Message message = new Message( "model", responseBody, Instant.now());
 
+        // 确保 history 不为 null
+        if (chatRequest.getHistory() == null) {
+            chatRequest.setHistory(new History());
+        }
+        History history = chatRequest.getHistory();
+
+        // 构建 ChatResponse
+        ChatResponse responseDTO = new ChatResponse();
+        responseDTO.setAnswer(message);
+        responseDTO.setModel(message.getRole());
+        responseDTO.setSessionId(chatRequest.getSessionId());
+        responseDTO.setCreatedAt(message.getTimestamp());
+        history.getMessages().add(chatRequest.getQuestion());
+        history.setSessionId(chatRequest.getSessionId());
+        responseDTO.setHistory(history);
+
+        return responseDTO;
+    }
 
 
     @Override
     public ChatResponse completions(ChatRequest requestDTO) throws IOException {
-            Request request = buildOllamaRequest(requestDTO,false);
-            Response response = client.newCall(request).execute();
-            System.out.println(response);
-            return parseResponse(response.body().string(),requestDTO);
+        Request request = buildOllamaRequest(requestDTO, false);
+        Response response = client.newCall(request).execute();
+        System.out.println(response);
+
+        // 解析响应内容并提取嵌套的 content 字段
+        String responseBody = response.body().string();
+        String extractedContent;
+        try {
+            JsonNode rootNode = objectMapper.readTree(responseBody);
+            extractedContent = rootNode.path("message").path("content").asText();
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException("Failed to parse response body", e);
+        }
+
+        Message message = new Message("model", extractedContent, Instant.now());
+
+        // 确保 history 不为 null
+        if (requestDTO.getHistory() == null) {
+            requestDTO.setHistory(new History());
+        }
+        History history = requestDTO.getHistory();
+
+        // 构建 ChatResponse
+        ChatResponse responseDTO = new ChatResponse();
+        responseDTO.setAnswer(message);
+        responseDTO.setModel(message.getRole());
+        responseDTO.setSessionId(requestDTO.getSessionId());
+        responseDTO.setCreatedAt(message.getTimestamp());
+        history.getMessages().add(requestDTO.getQuestion());
+        history.setSessionId(requestDTO.getSessionId());
+        responseDTO.setHistory(history);
+
+        return responseDTO;
     }
 
     @Override
@@ -159,18 +185,32 @@ public class ChatServiceImpl implements ChatService {
 
                 BufferedReader reader = new BufferedReader(new InputStreamReader(body.byteStream()));
 
-                StringBuilder fullResponse = new StringBuilder();
+                // 在流式输出中累积内容
+                StringBuilder accumulatedContent = new StringBuilder();
+
+                // 在 while 循环中累积 content
                 String line;
-                while ((line=reader.readLine()) != null) {
+                while ((line = reader.readLine()) != null) {
                     OllamaChatResponse chunk = objectMapper.readValue(line, OllamaChatResponse.class);
                     System.out.println(chunk);
-                    fullResponse.append(chunk.getMessage().getContent());
+                    accumulatedContent.append(chunk.getMessage().getContent());
                     processChunk(emitter, chunk);
                     if (chunk.isDone()) {
                         break;
                     }
                 }
-                ChatResponse chatResponse  = parseResponse(fullResponse.toString(),requestDTO);
+
+                // 在流结束后保存累积的内容到数据库
+                String finalContent = accumulatedContent.toString();
+                HistoryRecordInfo historyRecordInfo = new HistoryRecordInfo();
+                historyRecordInfo.setContent(finalContent);
+                historyRecordInfo.setRole("model");
+                historyRecordInfo.setTimestamp(Instant.now());
+                DialogueSessionInfo dialogueSessionInfo = dialogueSessionsRepository.findById(requestDTO.getSessionId()).orElse(null);
+                historyRecordInfo.setSession(dialogueSessionInfo);
+                historyRecordsRepository.save(historyRecordInfo);
+
+                ChatResponse chatResponse  = parseResponse(finalContent,requestDTO);
                 emitter.send(chatResponse);
                 emitter.complete();
 

+ 6 - 6
src/main/java/ai/huisuan/back/service/impl/LoginServiceImpl.java → src/main/java/ai/java/back/service/impl/LoginServiceImpl.java

@@ -1,10 +1,10 @@
-package ai.huisuan.back.service.impl;
+package ai.java.back.service.impl;
 
-import ai.huisuan.back.entity.UserLoginInfo;
-import ai.huisuan.back.entity.UserInfo;
-import ai.huisuan.back.repository.UserRepository;
-import ai.huisuan.back.repository.UserLoginInfoRepository;
-import ai.huisuan.back.service.LoginService;
+import ai.java.back.entity.UserLoginInfo;
+import ai.java.back.entity.UserInfo;
+import ai.java.back.repository.UserRepository;
+import ai.java.back.repository.UserLoginInfoRepository;
+import ai.java.back.service.LoginService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;

+ 31 - 0
src/main/java/ai/java/back/util/JwtUtil.java

@@ -0,0 +1,31 @@
+package ai.java.back.util;
+
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.Keys;
+
+import java.security.Key;
+import java.util.Date;
+
+public class JwtUtil {
+
+    private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
+    private static final long EXPIRATION_TIME = 24 * 60 * 60 * 1000; // 24小时
+
+    public static String generateToken(String username) {
+        return Jwts.builder()
+                .setSubject(username)
+                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
+                .signWith(key)
+                .compact();
+    }
+
+    public static String validateToken(String token) {
+        return Jwts.parserBuilder()
+                .setSigningKey(key)
+                .build()
+                .parseClaimsJws(token)
+                .getBody()
+                .getSubject();
+    }
+}

+ 2 - 0
src/main/resources/application.properties

@@ -16,3 +16,5 @@ spring.servlet.multipart.max-file-size=10MB
 spring.servlet.multipart.max-request-size=10MB
 
 management.threads.virtual.enabled=true
+
+spring.security.strategy=MODE_INHERITABLETHREADLOCAL

+ 0 - 0
src/test/java/ai/huisuan/back/service/impl/LoginServiceImplTest.java