diff --git a/src/main/java/com/zsc/edu/dify/framework/security/CustomAccessDeniedHandler.java b/src/main/java/com/zsc/edu/dify/framework/security/CustomAccessDeniedHandler.java index 92da44e..983d8d9 100644 --- a/src/main/java/com/zsc/edu/dify/framework/security/CustomAccessDeniedHandler.java +++ b/src/main/java/com/zsc/edu/dify/framework/security/CustomAccessDeniedHandler.java @@ -29,7 +29,7 @@ public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) throws IOException, ServletException { -// ex.printStackTrace(); + ex.printStackTrace(); response.setContentType("application/json;charset=utf-8"); ExceptionResult result; if (ex instanceof MissingCsrfTokenException) { @@ -39,9 +39,9 @@ public class CustomAccessDeniedHandler implements AccessDeniedHandler { result = new ExceptionResult("凭证已过期,请重新登录", HttpStatus.UNAUTHORIZED.value(), LocalDateTime.now()); } else if (ex instanceof AuthorizationDeniedException) { - // 403 - response.setStatus(HttpStatus.FORBIDDEN.value()); - result = new ExceptionResult("当前账号已在其他设备登录,请先退出再尝试登录", HttpStatus.FORBIDDEN.value(), + // 会话已存在,禁止重复登录,返回401 + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + result = new ExceptionResult("当前账号已在其他设备登录,请先退出再尝试登录", HttpStatus.UNAUTHORIZED.value(), LocalDateTime.now()); } else { // 403 diff --git a/src/main/java/com/zsc/edu/dify/framework/security/JpaUserDetailsServiceImpl.java b/src/main/java/com/zsc/edu/dify/framework/security/JpaUserDetailsServiceImpl.java index a3adedb..59bbbf9 100644 --- a/src/main/java/com/zsc/edu/dify/framework/security/JpaUserDetailsServiceImpl.java +++ b/src/main/java/com/zsc/edu/dify/framework/security/JpaUserDetailsServiceImpl.java @@ -55,3 +55,4 @@ public class JpaUserDetailsServiceImpl implements UserDetailsService { return UserDetailsImpl.from(user, permissions); } } + diff --git a/src/main/java/com/zsc/edu/dify/framework/security/SpringSecurityConfig.java b/src/main/java/com/zsc/edu/dify/framework/security/SpringSecurityConfig.java index 617698a..7c09f2d 100644 --- a/src/main/java/com/zsc/edu/dify/framework/security/SpringSecurityConfig.java +++ b/src/main/java/com/zsc/edu/dify/framework/security/SpringSecurityConfig.java @@ -10,7 +10,6 @@ import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 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.reactive.EnableWebFluxSecurity; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.core.userdetails.UserDetailsService; @@ -39,6 +38,7 @@ public class SpringSecurityConfig { private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; private final CustomAccessDeniedHandler customAccessDeniedHandler; + private final CustomSessionInformationExpiredStrategy customSessionInformationExpiredStrategy; // private final SessionRegistry sessionRegistry; @Resource @@ -59,6 +59,14 @@ public class SpringSecurityConfig { return new HttpSessionEventPublisher(); } + @Bean + public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() { + ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry()); + concurrentSessionControlAuthenticationStrategy.setMaximumSessions(1); + concurrentSessionControlAuthenticationStrategy.setExceptionIfMaximumExceeded(true); + return concurrentSessionControlAuthenticationStrategy; + } + @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); @@ -75,7 +83,7 @@ public class SpringSecurityConfig { } @Bean - public JsonAuthenticationFilter jsonAuthenticationFilter() throws Exception { + public JsonAuthenticationFilter jsonAuthenticationFilter() { JsonAuthenticationFilter filter = new JsonAuthenticationFilter(); filter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler); filter.setAuthenticationFailureHandler(customAuthenticationFailureHandler); @@ -89,21 +97,19 @@ public class SpringSecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - return http .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.GET, "/api/rest/user/menu","/api/rest/user/register","/api/rest/user/send-email").permitAll() .requestMatchers(HttpMethod.POST, "/api/rest/user/login","/api/rest/user/register").permitAll() .requestMatchers("/api/rest/user/me").permitAll() - .requestMatchers("/v1/**").authenticated() .requestMatchers("/api/**").authenticated() ) .addFilterAt(jsonAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .formLogin(form -> form - .loginPage("/user/login") - .loginProcessingUrl("/api/rest/user/login") - .successHandler(customAuthenticationSuccessHandler) - .failureHandler(customAuthenticationFailureHandler)) + .loginPage("/user/login") + .loginProcessingUrl("/api/rest/user/login") + .successHandler(customAuthenticationSuccessHandler) + .failureHandler(customAuthenticationFailureHandler)) .logout(logout -> logout .logoutUrl("/api/rest/user/logout") .logoutSuccessHandler((request, response, authentication) -> {})) @@ -115,13 +121,14 @@ public class SpringSecurityConfig { .rememberMe(rememberMe -> rememberMe .userDetailsService(userDetailsService) .tokenRepository(persistentTokenRepository())) - .csrf(csrf -> - csrf.ignoringRequestMatchers("v1/**","/api/internal/**", "/api/rest/user/logout","/api/rest/user/register")) + .csrf(csrf -> csrf + .sessionAuthenticationStrategy(concurrentSessionControlAuthenticationStrategy()) + .ignoringRequestMatchers("v1/**","/api/internal/**", "/api/rest/user/logout","/api/rest/user/register")) .sessionManagement(session -> session .maximumSessions(1) .maxSessionsPreventsLogin(true) .sessionRegistry(sessionRegistry()) -// .expiredSessionStrategy(customSessionInformationExpiredStrategy) + .expiredSessionStrategy(customSessionInformationExpiredStrategy) ).build(); } } diff --git a/src/main/java/com/zsc/edu/dify/framework/security/UserDetailsImpl.java b/src/main/java/com/zsc/edu/dify/framework/security/UserDetailsImpl.java index 13c9d57..849632e 100644 --- a/src/main/java/com/zsc/edu/dify/framework/security/UserDetailsImpl.java +++ b/src/main/java/com/zsc/edu/dify/framework/security/UserDetailsImpl.java @@ -130,3 +130,5 @@ public class UserDetailsImpl implements UserDetails { '}'; } } + + diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1ServerController.java b/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1ServerController.java index b8f8aa3..bf144c4 100644 --- a/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1ServerController.java +++ b/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1ServerController.java @@ -2,28 +2,37 @@ package com.zsc.edu.dify.modules.dify.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.zsc.edu.dify.framework.mybatisplus.DataPermission; +import com.zsc.edu.dify.modules.dify.dto.WorkflowDeptDto; import com.zsc.edu.dify.modules.dify.entity.AppEntity; +import com.zsc.edu.dify.modules.dify.entity.WorkflowDept; import com.zsc.edu.dify.modules.dify.service.AppEntityService; +import com.zsc.edu.dify.modules.dify.service.WorkflowDeptService; import com.zsc.edu.dify.modules.operationLog.entity.OperationLogAnnotation; import io.github.guoshiqiufeng.dify.server.DifyServer; import io.github.guoshiqiufeng.dify.server.dto.response.ApiKeyResponseVO; import io.github.guoshiqiufeng.dify.server.dto.response.AppsResponseVO; import io.github.guoshiqiufeng.dify.server.dto.response.DatasetApiKeyResponseVO; import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; import java.util.List; +@AllArgsConstructor @RestController @RequestMapping("/api/v1/server") public class V1ServerController { - @Resource private DifyServer difyServer; - @Resource + private AppEntityService appEntityService; + private final WorkflowDeptService workflowDeptService; + /** * 获取应用列表 * @param mode 模式 chat\agent-chat\completion\advanced-chat\workflow @@ -31,9 +40,8 @@ public class V1ServerController { * @return */ @GetMapping("/apps") - @DataPermission - public List getApps(String mode, String name) { - return appEntityService.getApps(mode, name); + public List getApps(String mode, String name, Integer type) { + return appEntityService.getApps(mode, name, type); } /** @@ -114,8 +122,21 @@ public class V1ServerController { * @return */ @GetMapping("/apps/type") - @DataPermission +// @DataPermission public List getAppsByAppType(Integer appType){ return appEntityService.selectByAppType(appType); } + + @PostMapping("/link") + public ResponseEntity link(@RequestBody WorkflowDeptDto workflowDeptDto) { + List workflowDepts = new ArrayList<>(); + for (Long deptId: workflowDeptDto.getDeptIds()) { + workflowDepts.add(new WorkflowDept(workflowDeptDto.getWorkflowId(), deptId)); + } + // 删除旧的关联关系 + workflowDeptService.remove(new LambdaQueryWrapper().eq(WorkflowDept::getWorkflowId, workflowDeptDto.getWorkflowId())); + return workflowDeptService.saveBatch(workflowDepts) ? + ResponseEntity.ok("关联成功") : + ResponseEntity.status(HttpStatus.BAD_REQUEST).body("关联失败"); + } } diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/dto/WorkflowDeptDto.java b/src/main/java/com/zsc/edu/dify/modules/dify/dto/WorkflowDeptDto.java new file mode 100644 index 0000000..afe46f1 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/dify/dto/WorkflowDeptDto.java @@ -0,0 +1,12 @@ +package com.zsc.edu.dify.modules.dify.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class WorkflowDeptDto { + @NotNull + private String workflowId; + @NotNull + private Long[] deptIds; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/entity/AppEntity.java b/src/main/java/com/zsc/edu/dify/modules/dify/entity/AppEntity.java index cbaedbb..1229426 100644 --- a/src/main/java/com/zsc/edu/dify/modules/dify/entity/AppEntity.java +++ b/src/main/java/com/zsc/edu/dify/modules/dify/entity/AppEntity.java @@ -19,7 +19,6 @@ import java.util.Map; @EqualsAndHashCode(callSuper = true) @Getter @Setter -@Data @TableName("apps_entity") public class AppEntity extends AppsResponseVO { @@ -70,4 +69,9 @@ public class AppEntity extends AppsResponseVO { return this.value; } } + + public AppEntity(String id, Long deptId) { + this.setId(id); + this.deptId = deptId; + } } diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/entity/WorkflowData.java b/src/main/java/com/zsc/edu/dify/modules/dify/entity/WorkflowData.java index 28dedcd..04f0b01 100644 --- a/src/main/java/com/zsc/edu/dify/modules/dify/entity/WorkflowData.java +++ b/src/main/java/com/zsc/edu/dify/modules/dify/entity/WorkflowData.java @@ -14,7 +14,6 @@ import lombok.Setter; @Getter @Setter -@Data @TableName("workflow_data") public class WorkflowData{ @TableId diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/entity/WorkflowDept.java b/src/main/java/com/zsc/edu/dify/modules/dify/entity/WorkflowDept.java new file mode 100644 index 0000000..eb85823 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/dify/entity/WorkflowDept.java @@ -0,0 +1,13 @@ +package com.zsc.edu.dify.modules.dify.entity; + +import jakarta.validation.constraints.NotNull; +import lombok.*; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class WorkflowDept { + private String workflowId; + private Long deptId; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/repo/AppEntityRepository.java b/src/main/java/com/zsc/edu/dify/modules/dify/repo/AppEntityRepository.java index 4cb1182..dbd2172 100644 --- a/src/main/java/com/zsc/edu/dify/modules/dify/repo/AppEntityRepository.java +++ b/src/main/java/com/zsc/edu/dify/modules/dify/repo/AppEntityRepository.java @@ -2,10 +2,15 @@ package com.zsc.edu.dify.modules.dify.repo; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.zsc.edu.dify.modules.dify.entity.AppEntity; +import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; +import java.util.List; + public interface AppEntityRepository extends BaseMapper { @Select("select api_key from apps_entity where id = #{appId}") String selectApiKey(String appId); + + List selectByAppType(@Param("deptId") Long deptId, @Param("appType") Integer appType); } diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/repo/WorkflowDeptRepository.java b/src/main/java/com/zsc/edu/dify/modules/dify/repo/WorkflowDeptRepository.java new file mode 100644 index 0000000..b9a758c --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/dify/repo/WorkflowDeptRepository.java @@ -0,0 +1,7 @@ +package com.zsc.edu.dify.modules.dify.repo; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zsc.edu.dify.modules.dify.entity.WorkflowDept; + +public interface WorkflowDeptRepository extends BaseMapper { +} diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/service/AppEntityService.java b/src/main/java/com/zsc/edu/dify/modules/dify/service/AppEntityService.java index e2c9399..96923e6 100644 --- a/src/main/java/com/zsc/edu/dify/modules/dify/service/AppEntityService.java +++ b/src/main/java/com/zsc/edu/dify/modules/dify/service/AppEntityService.java @@ -2,12 +2,13 @@ package com.zsc.edu.dify.modules.dify.service; import com.baomidou.mybatisplus.extension.service.IService; import com.zsc.edu.dify.modules.dify.entity.AppEntity; +import com.zsc.edu.dify.modules.dify.entity.WorkflowDept; import io.github.guoshiqiufeng.dify.server.dto.response.AppsResponseVO; import java.util.List; public interface AppEntityService extends IService { - List getApps(String mode, String name); + List getApps(String mode, String name, Integer appType); boolean enabledApp(String id); diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/service/Impl/AppEntityServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/dify/service/Impl/AppEntityServiceImpl.java index 2cdc6a2..2449d6a 100644 --- a/src/main/java/com/zsc/edu/dify/modules/dify/service/Impl/AppEntityServiceImpl.java +++ b/src/main/java/com/zsc/edu/dify/modules/dify/service/Impl/AppEntityServiceImpl.java @@ -3,9 +3,12 @@ package com.zsc.edu.dify.modules.dify.service.Impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.framework.security.SecurityUtil; import com.zsc.edu.dify.modules.dify.entity.AppEntity; +import com.zsc.edu.dify.modules.dify.entity.WorkflowDept; import com.zsc.edu.dify.modules.dify.mapper.AppEntityMapper; import com.zsc.edu.dify.modules.dify.repo.AppEntityRepository; +import com.zsc.edu.dify.modules.dify.repo.WorkflowDeptRepository; import com.zsc.edu.dify.modules.dify.service.AppEntityService; import io.github.guoshiqiufeng.dify.server.DifyServer; import io.github.guoshiqiufeng.dify.server.dto.response.ApiKeyResponseVO; @@ -14,6 +17,7 @@ import jakarta.annotation.Resource; import lombok.AllArgsConstructor; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import java.util.*; @@ -23,16 +27,23 @@ import java.util.*; @AllArgsConstructor @Service public class AppEntityServiceImpl extends ServiceImpl implements AppEntityService { - @Resource - private AppEntityMapper appEntityMapper; - @Resource - private DifyServer difyServer; - @Resource - private AppEntityRepository appEntityRepository; + + private final AppEntityMapper appEntityMapper; + + private final DifyServer difyServer; + + private final AppEntityRepository appEntityRepository; + + private final WorkflowDeptRepository workflowDeptRepository; + @Override - public List getApps(String mode, String name) { - return addApps(difyServer.apps(mode, name)); + public List getApps(String mode, String name, Integer type) { + return this.lambdaQuery() + .eq(StringUtils.hasText(mode), AppEntity::getMode, mode) + .eq(StringUtils.hasText(name), AppEntity::getName, name) + .eq(Objects.nonNull(type), AppEntity::getAppType, type) + .list(); } /** @@ -97,9 +108,8 @@ public class AppEntityServiceImpl extends ServiceImpl selectByAppType(Integer appType) { - return this.lambdaQuery() - .eq(AppEntity::getAppType, appType) - .list(); + Long deptId = SecurityUtil.getUserInfo().getDept().getId(); + return baseMapper.selectByAppType(deptId, appType); } } diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/service/Impl/WorkflowDeptServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/dify/service/Impl/WorkflowDeptServiceImpl.java new file mode 100644 index 0000000..ab34f5e --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/dify/service/Impl/WorkflowDeptServiceImpl.java @@ -0,0 +1,11 @@ +package com.zsc.edu.dify.modules.dify.service.Impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zsc.edu.dify.modules.dify.entity.WorkflowDept; +import com.zsc.edu.dify.modules.dify.repo.WorkflowDeptRepository; +import com.zsc.edu.dify.modules.dify.service.WorkflowDeptService; +import org.springframework.stereotype.Service; + +@Service +public class WorkflowDeptServiceImpl extends ServiceImpl implements WorkflowDeptService { +} diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/service/WorkflowDeptService.java b/src/main/java/com/zsc/edu/dify/modules/dify/service/WorkflowDeptService.java new file mode 100644 index 0000000..115f5c5 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/dify/service/WorkflowDeptService.java @@ -0,0 +1,7 @@ +package com.zsc.edu.dify.modules.dify.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zsc.edu.dify.modules.dify.entity.WorkflowDept; + +public interface WorkflowDeptService extends IService { +} diff --git a/src/main/resources/mappers/dify/AppEntityMapper.xml b/src/main/resources/mappers/dify/AppEntityMapper.xml new file mode 100644 index 0000000..e93db1d --- /dev/null +++ b/src/main/resources/mappers/dify/AppEntityMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/zsc/edu/dify/modules/dify/repo/AppEntityRepositoryTest.java b/src/test/java/com/zsc/edu/dify/modules/dify/repo/AppEntityRepositoryTest.java new file mode 100644 index 0000000..8fe78a1 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/modules/dify/repo/AppEntityRepositoryTest.java @@ -0,0 +1,19 @@ +package com.zsc.edu.dify.modules.dify.repo; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class AppEntityRepositoryTest { + + @Autowired + private AppEntityRepository appEntityRepository; + + @Test + void selectByAppType() { + appEntityRepository.selectByAppType(1L,1); + } +} \ No newline at end of file