Merge remote-tracking branch 'origin/master' into feature/update-dify

This commit is contained in:
vertoryao 2025-06-20 17:10:54 +08:00
commit 95f80df743
7 changed files with 43 additions and 14 deletions

16
pom.xml
View File

@ -132,6 +132,22 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-data-r2dbc</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>pro.chenggang</groupId>-->
<!-- <artifactId>mybatis-r2dbc-spring</artifactId>-->
<!-- <version>3.0.6.RELEASE</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>pro.chenggang</groupId>-->
<!-- <artifactId>mybatis-r2dbc</artifactId>-->
<!-- <version>3.0.6.RELEASE</version>-->
<!-- </dependency>-->
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>

View File

@ -6,6 +6,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.authorization.AuthorizationDeniedException;
@ -38,11 +39,10 @@ public class CustomAccessDeniedHandler implements AccessDeniedHandler {
response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setStatus(HttpStatus.UNAUTHORIZED.value());
result = new ExceptionResult("凭证已过期,请重新登录", HttpStatus.UNAUTHORIZED.value(), result = new ExceptionResult("凭证已过期,请重新登录", HttpStatus.UNAUTHORIZED.value(),
LocalDateTime.now()); LocalDateTime.now());
} else if (ex instanceof AuthorizationDeniedException) { // } else if (ex instanceof AuthorizationDeniedException) {
// 会话已存在禁止重复登录返回401 // response.setStatus(HttpStatus.FORBIDDEN.value());
response.setStatus(HttpStatus.UNAUTHORIZED.value()); // result = new ExceptionResult("权限不足", HttpStatus.FORBIDDEN.value(),
result = new ExceptionResult("当前账号已在其他设备登录,请先退出再尝试登录", HttpStatus.UNAUTHORIZED.value(), // LocalDateTime.now());
LocalDateTime.now());
} else { } else {
// 403 // 403
response.setStatus(HttpStatus.FORBIDDEN.value()); response.setStatus(HttpStatus.FORBIDDEN.value());

View File

@ -9,6 +9,7 @@ import org.springframework.stereotype.Component;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map; import java.util.Map;
/** /**
@ -26,8 +27,9 @@ public class CustomSessionInformationExpiredStrategy implements SessionInformati
response.getWriter().print(objectMapper.writeValueAsString(Map.of( response.getWriter().print(objectMapper.writeValueAsString(Map.of(
"msg", "会话已过期(有可能是您同时登录了太多的客户端)", "msg", "会话已过期(有可能是您同时登录了太多的客户端)",
"code", HttpStatus.UNAUTHORIZED.value(), "code", HttpStatus.UNAUTHORIZED.value(),
"timestamp", LocalDateTime.now() "timestamp", new Date()
))); )));
response.flushBuffer(); response.flushBuffer();
} }
} }

View File

@ -10,6 +10,8 @@ import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 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.builders.HttpSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
@ -20,11 +22,13 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy; import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.session.HttpSessionEventPublisher; import org.springframework.security.web.session.HttpSessionEventPublisher;
import javax.sql.DataSource; import javax.sql.DataSource;
import static org.springframework.security.config.Customizer.withDefaults;
/** /**
* @author harry_yao * @author harry_yao
*/ */
@ -39,7 +43,6 @@ public class SpringSecurityConfig {
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
private final CustomAccessDeniedHandler customAccessDeniedHandler; private final CustomAccessDeniedHandler customAccessDeniedHandler;
private final CustomSessionInformationExpiredStrategy customSessionInformationExpiredStrategy; private final CustomSessionInformationExpiredStrategy customSessionInformationExpiredStrategy;
// private final SessionRegistry sessionRegistry;
@Resource @Resource
private final DataSource dataSource; private final DataSource dataSource;
@ -63,7 +66,7 @@ public class SpringSecurityConfig {
public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() { public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() {
ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry()); ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
concurrentSessionControlAuthenticationStrategy.setMaximumSessions(1); concurrentSessionControlAuthenticationStrategy.setMaximumSessions(1);
concurrentSessionControlAuthenticationStrategy.setExceptionIfMaximumExceeded(true); concurrentSessionControlAuthenticationStrategy.setExceptionIfMaximumExceeded(false);
return concurrentSessionControlAuthenticationStrategy; return concurrentSessionControlAuthenticationStrategy;
} }
@ -126,7 +129,7 @@ public class SpringSecurityConfig {
.ignoringRequestMatchers("v1/**","/api/internal/**", "/api/rest/user/logout","/api/rest/user/register")) .ignoringRequestMatchers("v1/**","/api/internal/**", "/api/rest/user/logout","/api/rest/user/register"))
.sessionManagement(session -> session .sessionManagement(session -> session
.maximumSessions(1) .maximumSessions(1)
.maxSessionsPreventsLogin(true) .maxSessionsPreventsLogin(false)
.sessionRegistry(sessionRegistry()) .sessionRegistry(sessionRegistry())
.expiredSessionStrategy(customSessionInformationExpiredStrategy) .expiredSessionStrategy(customSessionInformationExpiredStrategy)
).build(); ).build();

View File

@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author yanghq * @author yanghq
@ -45,6 +46,7 @@ public class V1ChatController {
* apikey 建议在数据库进行存储前端调用时传智能体 id从数据库查询 * apikey 建议在数据库进行存储前端调用时传智能体 id从数据库查询
*/ */
@PostMapping("/completions/{appId}") @PostMapping("/completions/{appId}")
@PreAuthorize("hasAuthority('difyChat:query')")
@OperationLogAnnotation(content = "'dify对话'", operationType = "发送") @OperationLogAnnotation(content = "'dify对话'", operationType = "发送")
public ChatMessageSendResponse sendChatMessage( public ChatMessageSendResponse sendChatMessage(
@RequestBody ChatMessageSendRequest sendRequest, @RequestBody ChatMessageSendRequest sendRequest,
@ -125,11 +127,12 @@ public class V1ChatController {
*/ */
@DeleteMapping("/conversation") @DeleteMapping("/conversation")
@OperationLogAnnotation(content = "'dify对话'", operationType = "删除") @OperationLogAnnotation(content = "'dify对话'", operationType = "删除")
public void deleteConversation(@RequestParam String conversationId, @RequestParam String appId) { public Map<String, String> deleteConversation(@RequestParam String conversationId, @RequestParam String appId) {
String apiKey = appEntityService.getApikey(appId); String apiKey = appEntityService.getApikey(appId);
String userId = SecurityUtil.getUserInfo().id.toString(); String userId = SecurityUtil.getUserInfo().id.toString();
try{ try{
difyChat.deleteConversation(conversationId, apiKey, userId); difyChat.deleteConversation(conversationId, apiKey, userId);
return Map.of("message", "删除成功");
}catch (RuntimeException e){ }catch (RuntimeException e){
throw new ApiException("删除会话失败"+e.getMessage()); throw new ApiException("删除会话失败"+e.getMessage());
} }

View File

@ -139,4 +139,9 @@ public class V1ServerController {
ResponseEntity.ok("关联成功") : ResponseEntity.ok("关联成功") :
ResponseEntity.status(HttpStatus.BAD_REQUEST).body("关联失败"); ResponseEntity.status(HttpStatus.BAD_REQUEST).body("关联失败");
} }
@GetMapping("/link/{workflowId}")
public List<WorkflowDept> linked(@PathVariable String workflowId) {
return workflowDeptService.lambdaQuery().eq(WorkflowDept::getWorkflowId, workflowId).list();
}
} }

View File

@ -24,7 +24,7 @@ public class OperationController {
* 获取操作日志详情 * 获取操作日志详情
*/ */
@GetMapping("/{id}") @GetMapping("/{id}")
@PreAuthorize("hasAuthority('operationLog:query')") @PreAuthorize("hasAuthority('system:operationLog:query')")
public OperationLog crate(@PathVariable("id") Long id) { public OperationLog crate(@PathVariable("id") Long id) {
return repo.selectById(id); return repo.selectById(id);
} }
@ -33,7 +33,7 @@ public class OperationController {
* 获取操作日志分页 * 获取操作日志分页
*/ */
@GetMapping("") @GetMapping("")
@PreAuthorize("hasAuthority('operationLog:query')") @PreAuthorize("hasAuthority('system:operationLog:query')")
public Page<OperationLog> query(Page<OperationLog> page, OperationLogQuery query) { public Page<OperationLog> query(Page<OperationLog> page, OperationLogQuery query) {
return repo.selectPage(page, query.wrapper()); return repo.selectPage(page, query.wrapper());
} }
@ -42,7 +42,7 @@ public class OperationController {
* 批量删除操作日志 * 批量删除操作日志
*/ */
@DeleteMapping("/batch") @DeleteMapping("/batch")
@PreAuthorize("hasAuthority('operationLog:delete')") @PreAuthorize("hasAuthority('system:operationLog:delete')")
public int deleteBatch(@RequestBody List<Long> ids) { public int deleteBatch(@RequestBody List<Long> ids) {
LambdaQueryWrapper<OperationLog> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<OperationLog> wrapper = new LambdaQueryWrapper<>();
wrapper.in(OperationLog::getId, ids); wrapper.in(OperationLog::getId, ids);