diff --git a/pom.xml b/pom.xml index 6cae0c1..ef39dfe 100644 --- a/pom.xml +++ b/pom.xml @@ -119,6 +119,15 @@ true + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.r2dbc + r2dbc-postgresql + 0.8.13.RELEASE + org.mapstruct mapstruct 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 0570adb..a3adedb 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 @@ -6,10 +6,12 @@ import com.zsc.edu.dify.modules.system.entity.*; import com.zsc.edu.dify.modules.system.repo.*; import com.zsc.edu.dify.modules.system.service.DeptService; import com.zsc.edu.dify.modules.system.service.RoleService; +import jakarta.annotation.Resource; import lombok.AllArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; 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 ea6d687..dcb04a9 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 @@ -24,7 +24,6 @@ import javax.sql.DataSource; /** * @author harry_yao */ -//@EnableWebFluxSecurity @AllArgsConstructor @EnableMethodSecurity @Configuration diff --git a/src/main/java/com/zsc/edu/dify/framework/security/flux/FluxUserDetailServiceImpl.java b/src/main/java/com/zsc/edu/dify/framework/security/flux/FluxUserDetailServiceImpl.java new file mode 100644 index 0000000..338964f --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/flux/FluxUserDetailServiceImpl.java @@ -0,0 +1,83 @@ +package com.zsc.edu.dify.framework.security.flux; +import com.zsc.edu.dify.common.util.TreeUtil; +import com.zsc.edu.dify.exception.StateException; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.zsc.edu.dify.modules.system.entity.Menu; +import com.zsc.edu.dify.modules.system.entity.Role; +import com.zsc.edu.dify.modules.system.entity.UserRole; +import com.zsc.edu.dify.modules.system.repo.flux.*; +import com.zsc.edu.dify.modules.system.service.DeptService; +import lombok.AllArgsConstructor; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author zhuang + */ +@AllArgsConstructor +@Service +public class FluxUserDetailServiceImpl implements ReactiveUserDetailsService { + + private final UserFluxRepository userRepo; + private final MenuFluxRepository menuRepository; + private final RoleFluxRepository roleFluxRepository; + private final UserRoleFluxRepository userRoleFluxRepository; + private final DeptService deptService; + @Override + public Mono findByUsername(String username) { + return userRepo.findByUsername(username) + .flatMap(user -> { + if (!user.getEnableState()) { + return Mono.error(new StateException("用户 '" + username + "' 已被禁用!请联系管理员")); + } + // 先获取UserRole列表,并从中提取roleIds + return userRoleFluxRepository.findByUserId(user.getId()).collectList() + .flatMap(userRoles -> Mono.zip( + roleFluxRepository.findByDeptId(user.getDeptId()), + deptService.listFluxTree(user.getDeptId()), + menuRepository.findByRoleId(user.getRoleId()) + )) + .flatMap(tuple -> { + Role role = (Role) tuple.getT1(); + List depts = (List) tuple.getT2(); + List menus = tuple.getT3(); + + user.setRole(role); + + Set dataScopeDeptIds = depts.stream() + .map(Dept::getId) + .collect(Collectors.toSet()); + user.setDataScopeDeptIds(dataScopeDeptIds); + + Set permissions = menus.stream() + .map(Menu::getPermissions) + .collect(Collectors.toSet()); + + return Mono.just(UserDetailsImpl.from(user, permissions)); + }); + }); + } + + + +// @Override +// public Mono findByUsername(String username) { +// return userRepo.findByUsername(username) +// .flatMap(user -> menuRepository.findByRoleId(user.getRoleId()) +// .map(ids-> userRoleFluxRepository.findByUserId(user.getId())) +// .map(roles-> roleFluxRepository.findByIdIn(ids)) +// .map(menus -> { +// Set permissions = menus.stream().map(Menu::getPermissions).collect(Collectors.toSet()); +// return UserDetailsImpl.from(user, permissions); +// }) +// ); +// } + +} diff --git a/src/main/java/com/zsc/edu/dify/framework/security/flux/SpringWebFluxSecurityConfig.java b/src/main/java/com/zsc/edu/dify/framework/security/flux/SpringWebFluxSecurityConfig.java new file mode 100644 index 0000000..dadcd64 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/flux/SpringWebFluxSecurityConfig.java @@ -0,0 +1,102 @@ +package com.zsc.edu.dify.framework.security.flux; + +import com.zsc.edu.dify.framework.security.*; +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.ReactiveAuthenticationManager; +import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.ReactiveAuthorizationManager; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.SecurityWebFiltersOrder; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.security.web.server.ServerAuthenticationEntryPoint; +import org.springframework.security.web.server.authentication.AuthenticationWebFilter; +import org.springframework.security.web.server.authentication.ServerAuthenticationEntryPointFailureHandler; +import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; +import org.springframework.security.web.server.authorization.AuthorizationContext; +import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler; +import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.reactive.CorsConfigurationSource; +import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; +import reactor.core.publisher.Mono; + +import javax.sql.DataSource; + +@EnableWebFluxSecurity +@AllArgsConstructor +@Configuration +public class SpringWebFluxSecurityConfig { + + private final ReactiveUserDetailsService userDetailsService; + private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; + private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; + private final CustomAccessDeniedHandler customAccessDeniedHandler; + private final SecurityBeanConfig securityBeanConfig; + + + @Bean + public ReactiveAuthenticationManager reactiveAuthenticationManager() { + UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = + new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService); + authenticationManager.setPasswordEncoder(securityBeanConfig.passwordEncoder()); + return authenticationManager; + } + + @Bean + public ReactiveAuthorizationManager reactiveAuthorizationManager() { + return (authenticationMono, context) -> authenticationMono + .flatMap(authentication -> + userDetailsService.findByUsername(authentication.getName()) + .map(userDetails -> { + SecurityUtil.setUserInfo((UserDetailsImpl) userDetails); + return new AuthorizationDecision(true); + }) + ) + .defaultIfEmpty(new AuthorizationDecision(false)); + } + + @Bean + public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { + // 配置认证过滤器 + AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(reactiveAuthenticationManager()); + authenticationFilter.setAuthenticationSuccessHandler((ServerAuthenticationSuccessHandler) customAuthenticationSuccessHandler); + authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler((ServerAuthenticationEntryPoint) customAuthenticationEntryPoint)); + authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository()); + + return http + .authorizeExchange(exchanges -> exchanges + .pathMatchers("/v1/flux/**").access(reactiveAuthorizationManager()) // 通过自定义权限管理器进行权限校验 + .anyExchange().authenticated() + ) + .addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION) + .exceptionHandling(handling -> handling + .authenticationEntryPoint((ServerAuthenticationEntryPoint) customAuthenticationEntryPoint) + .accessDeniedHandler((ServerAccessDeniedHandler) customAccessDeniedHandler) + ) + .csrf(ServerHttpSecurity.CsrfSpec::disable) + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) + .build(); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.addAllowedOrigin("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + config.setAllowCredentials(true); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/v1/flux/**", config); + return source; + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1ChatController.java b/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1ChatController.java index a150897..5843d8e 100644 --- a/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1ChatController.java +++ b/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1ChatController.java @@ -9,6 +9,7 @@ import io.github.guoshiqiufeng.dify.chat.dto.response.MessageConversationsRespon import io.github.guoshiqiufeng.dify.core.pojo.DifyPageResult; import jakarta.annotation.Resource; import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; @@ -34,7 +35,8 @@ public class V1ChatController { * apikey 建议在数据库进行存储,前端调用时传智能体 id,从数据库查询 */ @PostMapping(value = "/completions/{appId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - public Flux sendChatMessageStream(@RequestBody ChatMessageSendRequest sendRequest,@PathVariable String appId) { + @PreAuthorize("hasAuthority('difyChat:query')") + public Flux sendChatMessageStream(@RequestBody ChatMessageSendRequest sendRequest, @PathVariable String appId) { sendRequest.setApiKey(appEntityRepository.selectApiKey(appId)); return difyChat.sendChatMessageStream(sendRequest); } @@ -46,6 +48,7 @@ public class V1ChatController { * @return 会话列表 */ @PostMapping("/conversations") + @PreAuthorize("hasAuthority('difyChat:query')") public DifyPageResult conversations(@RequestBody MessageConversationsRequest request) { request.setApiKey("app-mM2UGTE5QVPLCwGvwifnV0g7"); return difyChat.conversations(request); @@ -58,6 +61,7 @@ public class V1ChatController { * @param userId 用户id */ @PatchMapping("/stopMessagesStream") + @PreAuthorize("hasAuthority('difyChat:update')") public void stopMessagesStream( String taskId, String userId) { difyChat.stopMessagesStream("app-mM2UGTE5QVPLCwGvwifnV0g7", taskId, userId); } @@ -69,6 +73,7 @@ public class V1ChatController { * @param userId 用户id */ @DeleteMapping("/messages/suggested") + @PreAuthorize("hasAuthority('difyChat:delete')") public void deleteConversation(String conversationId, String userId) { difyChat.deleteConversation(conversationId, "app-mM2UGTE5QVPLCwGvwifnV0g7", userId); } diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1DatasetController.java b/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1DatasetController.java index 8954bbc..dba1bc3 100644 --- a/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1DatasetController.java +++ b/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1DatasetController.java @@ -5,6 +5,7 @@ import io.github.guoshiqiufeng.dify.dataset.DifyDataset; import io.github.guoshiqiufeng.dify.dataset.dto.request.*; import io.github.guoshiqiufeng.dify.dataset.dto.response.*; import jakarta.annotation.Resource; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @@ -20,6 +21,7 @@ public class V1DatasetController { * @return */ @PostMapping("/page") + @PreAuthorize("hasAuthority('difyDataSet:query')") public DifyPageResult page(@RequestBody DatasetPageRequest request){ request.setApiKey("dataset-kN5WTJ8jR877YfN1A34JceVg"); return difyDataset.page(request); @@ -32,6 +34,7 @@ public class V1DatasetController { * @return */ @PostMapping("/createDocumentByFile") + @PreAuthorize("hasAuthority('difyDataSet:create')") public DocumentCreateResponse createDocumentByFile(DocumentCreateByFileRequest request){ return difyDataset.createDocumentByFile(request); } @@ -43,6 +46,7 @@ public class V1DatasetController { * @return */ @GetMapping("/pageDocument") + @PreAuthorize("hasAuthority('difyDataSet:query')") public DifyPageResult pageDocument(@RequestBody DatasetPageDocumentRequest request){ request.setApiKey("dataset-kN5WTJ8jR877YfN1A34JceVg"); return difyDataset.pageDocument(request); @@ -56,6 +60,7 @@ public class V1DatasetController { * @return */ @PostMapping("/uploadFileInfo") + @PreAuthorize("hasAuthority('difyDataSet:query')") public UploadFileInfoResponse uploadFileInfo(String datasetId, String documentId){ return difyDataset.uploadFileInfo(datasetId, documentId,"dataset-kN5WTJ8jR877YfN1A34JceVg"); } @@ -68,6 +73,7 @@ public class V1DatasetController { * @return */ @DeleteMapping("/deleteDocument") + @PreAuthorize("hasAuthority('difyDataSet:delete')") public DocumentDeleteResponse deleteDocument(String datasetId, String documentId){ return difyDataset.deleteDocument(datasetId, documentId, "app-mM2UGTE5QVPLCwGvwifnV0g7"); } @@ -79,6 +85,7 @@ public class V1DatasetController { * @return */ @PostMapping("/createSegment") + @PreAuthorize("hasAuthority('difyDataSet:create')") public SegmentResponse createSegment(@RequestBody SegmentCreateRequest request){ return difyDataset.createSegment(request); } @@ -90,6 +97,7 @@ public class V1DatasetController { * @return */ @PostMapping("/createSegmentChildChunk") + @PreAuthorize("hasAuthority('difyDataSet:create')") public SegmentChildChunkCreateResponse createSegmentChildChunk(@RequestBody SegmentChildChunkCreateRequest request){ return difyDataset.createSegmentChildChunk(request); } @@ -101,6 +109,7 @@ public class V1DatasetController { * @return */ @GetMapping("/retrieve") + @PreAuthorize("hasAuthority('difyDataSet:query')") public RetrieveResponse retrieve(@RequestBody RetrieveRequest request){ return difyDataset.retrieve(request); } 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 3a641d9..cf4d2ba 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 @@ -10,6 +10,7 @@ import io.github.guoshiqiufeng.dify.server.dto.response.AppsResponseVO; import io.github.guoshiqiufeng.dify.server.dto.response.DatasetApiKeyResponseVO; import jakarta.annotation.Resource; import org.apache.ibatis.annotations.Param; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -30,6 +31,7 @@ public class V1ServerController { * @return */ @GetMapping("/apps") + @PreAuthorize("hasAuthority('difyServer:query')") public List getApps(String mode, String name) { return difyServerService.getApps(mode, name); } @@ -40,6 +42,7 @@ public class V1ServerController { * @return */ @GetMapping("/{id}") + @PreAuthorize("hasAuthority('difyServer:query')") public AppsResponseVO getApp(@PathVariable("id") String id) { return difyServer.app(id); } @@ -50,6 +53,7 @@ public class V1ServerController { * @return */ @GetMapping("/api-key/{id}") + @PreAuthorize("hasAuthority('difyServer:query')") public List getAppApiKey(@PathVariable("id") String id) { return difyServer.getAppApiKey(id); } @@ -60,6 +64,7 @@ public class V1ServerController { * @return */ @PostMapping("/api-key/init/{id}") + @PreAuthorize("hasAuthority('difyServer:create')") public List initAppApiKey(@PathVariable("id") String id) { return difyServer.initAppApiKey(id); } @@ -69,6 +74,7 @@ public class V1ServerController { * @return */ @GetMapping("/api-key/dataset") + @PreAuthorize("hasAuthority('difyServer:query')") public List getDatasetApiKey() { return difyServer.getDatasetApiKey(); } @@ -78,6 +84,7 @@ public class V1ServerController { * @return */ @PostMapping("/api-key/dataset/init") + @PreAuthorize("hasAuthority('difyServer:create')") public List initDatasetApiKey() { return difyServer.initDatasetApiKey(); } @@ -89,6 +96,7 @@ public class V1ServerController { * @return */ @PostMapping("/app/{id}") + @PreAuthorize("hasAuthority('difyServer:update')") public boolean enabledApp(@PathVariable String id) { return difyServerService.enabledApp(id); } @@ -98,6 +106,7 @@ public class V1ServerController { * @return */ @GetMapping("/apps/able") + @PreAuthorize("hasAuthority('difyServer:query')") public List getAbleApps() { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(AppEntity::isEnabled, true); diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1WorkflowController.java b/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1WorkflowController.java index a73b3a4..752bcf1 100644 --- a/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1WorkflowController.java +++ b/src/main/java/com/zsc/edu/dify/modules/dify/controller/V1WorkflowController.java @@ -6,6 +6,7 @@ import io.github.guoshiqiufeng.dify.workflow.dto.request.WorkflowLogsRequest; import io.github.guoshiqiufeng.dify.workflow.dto.request.WorkflowRunRequest; import io.github.guoshiqiufeng.dify.workflow.dto.response.*; import jakarta.annotation.Resource; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; @@ -23,6 +24,7 @@ public class V1WorkflowController { * @return */ @PostMapping("/run") + @PreAuthorize("hasAuthority('difyWorkFlow:query')") public WorkflowRunResponse runWorkflow(@RequestBody WorkflowRunRequest request) { request.setApiKey("app-ZpkQM6yy767oUTfNSBYq65nB"); return difyWorkflow.runWorkflow(request); @@ -59,6 +61,7 @@ public class V1WorkflowController { * @return */ @GetMapping("/info") + @PreAuthorize("hasAuthority('difyWorkFlow:query')") public WorkflowInfoResponse info(String workflowRunId) { return difyWorkflow.info(workflowRunId, "app-ZpkQM6yy767oUTfNSBYq65nB"); } @@ -70,6 +73,7 @@ public class V1WorkflowController { * @return */ @PostMapping("/logs") + @PreAuthorize("hasAuthority('difyWorkFlow:query')") public DifyPageResult logs(@RequestBody WorkflowLogsRequest request) { request.setApiKey("app-ZpkQM6yy767oUTfNSBYq65nB"); return difyWorkflow.logs(request); diff --git a/src/main/java/com/zsc/edu/dify/modules/system/entity/UserRole.java b/src/main/java/com/zsc/edu/dify/modules/system/entity/UserRole.java index fdc0b5a..f2399d7 100644 --- a/src/main/java/com/zsc/edu/dify/modules/system/entity/UserRole.java +++ b/src/main/java/com/zsc/edu/dify/modules/system/entity/UserRole.java @@ -1,7 +1,7 @@ package com.zsc.edu.dify.modules.system.entity; import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; +import lombok.*; import java.io.Serializable; @@ -10,6 +10,7 @@ import java.io.Serializable; * @author zhuang */ @Data +@Getter @TableName("sys_users_roles") public class UserRole implements Serializable { /** @@ -20,5 +21,7 @@ public class UserRole implements Serializable { /** * 角色ID */ + @Getter private Long roleId; + } \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/MenuFluxRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/MenuFluxRepository.java new file mode 100644 index 0000000..af33517 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/MenuFluxRepository.java @@ -0,0 +1,16 @@ +package com.zsc.edu.dify.modules.system.repo.flux; + +import com.zsc.edu.dify.modules.system.entity.Menu; +import org.springframework.data.r2dbc.repository.Query; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; +@Repository +public interface MenuFluxRepository extends ReactiveCrudRepository { + + @Query("select * from sys_menu where id in (select menu_id from sys_roles_menus where role_id = :roleId)") + Mono> findByRoleId(Long roleId); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/RoleFluxRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/RoleFluxRepository.java new file mode 100644 index 0000000..6723273 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/RoleFluxRepository.java @@ -0,0 +1,15 @@ +package com.zsc.edu.dify.modules.system.repo.flux; + +import com.zsc.edu.dify.modules.system.entity.Role; +import org.mapstruct.Mapper; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +import java.util.List; + +@Repository +public interface RoleFluxRepository extends ReactiveCrudRepository { + + Mono findByDeptId(Long id); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/UserFluxRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/UserFluxRepository.java new file mode 100644 index 0000000..4a0f208 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/UserFluxRepository.java @@ -0,0 +1,12 @@ +package com.zsc.edu.dify.modules.system.repo.flux; + +import com.zsc.edu.dify.modules.system.entity.User; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +@Repository +public interface UserFluxRepository extends ReactiveCrudRepository { + + Mono findByUsername(String username); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/UserRoleFluxRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/UserRoleFluxRepository.java new file mode 100644 index 0000000..b6e86a8 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/flux/UserRoleFluxRepository.java @@ -0,0 +1,16 @@ +package com.zsc.edu.dify.modules.system.repo.flux; + +import com.zsc.edu.dify.modules.system.entity.UserRole; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; +@Repository +public interface UserRoleFluxRepository extends ReactiveCrudRepository { + + Flux> findByUserId(Long userId); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/DeptService.java b/src/main/java/com/zsc/edu/dify/modules/system/service/DeptService.java index afc8704..ba85e60 100644 --- a/src/main/java/com/zsc/edu/dify/modules/system/service/DeptService.java +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/DeptService.java @@ -3,6 +3,7 @@ package com.zsc.edu.dify.modules.system.service; import com.zsc.edu.dify.modules.system.dto.DeptDto; import com.zsc.edu.dify.modules.system.entity.Dept; import com.baomidou.mybatisplus.extension.service.IService; +import reactor.core.publisher.Mono; import java.util.List; @@ -31,8 +32,11 @@ public interface DeptService extends IService { Boolean toggle(Long id); + Mono listFluxTree(Long deptId); + /** * 生成部门树结构 + * * @param id * @return */ diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/impl/DeptServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/DeptServiceImpl.java index cebc5bb..6458bc6 100644 --- a/src/main/java/com/zsc/edu/dify/modules/system/service/impl/DeptServiceImpl.java +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/DeptServiceImpl.java @@ -11,6 +11,8 @@ import com.zsc.edu.dify.modules.system.service.DeptService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; import java.util.ArrayList; import java.util.List; @@ -57,6 +59,40 @@ public class DeptServiceImpl extends ServiceImpl implement return updateById(dept); } + @Override + public Mono> listFluxTree(Long deptId) { + // 1. 将同步数据库查询改为响应式查询(假设 baseMapper 支持响应式) + return Mono.fromCallable(() -> baseMapper.selectDeptTree()) + .subscribeOn(Schedulers.boundedElastic()) + .flatMap(deptTrees -> { + // 2. 构建部门树(TreeUtil.makeTree 如果是计算密集型,需在弹性线程执行) + List deptTree = TreeUtil.makeTree( + deptTrees, + department -> department.getPid() == null || department.getPid() == -1L, + (parent, child) -> parent.getId().equals(child.getPid()), + Dept::setChildren + ); + + // 3. 根据 deptId 条件分支处理 + if (deptId == null) { + return Mono.just(deptTree); + } else if (deptId == 0L) { + return Mono.just(deptTree); + } else { + // 4. 使用响应式方式过滤指定部门及其子部门 + return Mono.fromCallable(() -> { + List deptChildrenTree = new ArrayList<>(); + TreeUtil.forLevelOrder(deptTree, node -> { + if (node.getId().equals(deptId)) { + deptChildrenTree.add(node); + } + }, Dept::getChildren); + return deptChildrenTree.isEmpty() ? deptTree : deptChildrenTree; + }).subscribeOn(Schedulers.boundedElastic()); + } + }); + } + @Override public List listTree(Long deptId) { List deptTrees = baseMapper.selectDeptTree(); @@ -84,5 +120,4 @@ public class DeptServiceImpl extends ServiceImpl implement } return deptTree; } - } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index ecbe385..69a2e1c 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -11,6 +11,12 @@ mybatis-plus: map-underscore-to-camel-case: true spring: + main: + allow-bean-definition-overriding: true + r2dbc: + url: r2dbc:postgresql://43.139.10.64:15432/dify?ssl=false&TimeZone=Asia/Shanghai + username: gitea + password: gitea datasource: url: jdbc:postgresql://43.139.10.64:15432/dify?ssl=false&TimeZone=Asia/Shanghai username: gitea @@ -64,4 +70,5 @@ dify: email: 2913129173@qq.com # 请替换为实际的 Dify 服务邮箱,若不需要调用 server相关接口可不填 password: tian14384, # 请替换为实际的 Dify 服务密码,若不需要调用 server相关接口可不填 dataset: - api-key: dataset-kN5WTJ8jR877YfN1A34JceVg # 请替换为实际的知识库api-key, 若不需要调用知识库可不填 \ No newline at end of file + api-key: dataset-kN5WTJ8jR877YfN1A34JceVg # 请替换为实际的知识库api-key, 若不需要调用知识库可不填 + diff --git a/src/test/java/com/zsc/edu/dify/modules/system/service/impl/MenuServiceImplTest.java b/src/test/java/com/zsc/edu/dify/modules/system/service/impl/MenuServiceImplTest.java index 50946cd..b7b29fd 100644 --- a/src/test/java/com/zsc/edu/dify/modules/system/service/impl/MenuServiceImplTest.java +++ b/src/test/java/com/zsc/edu/dify/modules/system/service/impl/MenuServiceImplTest.java @@ -31,9 +31,9 @@ class MenuServiceImplTest { Menu system = new Menu(null, Menu.Type.PAGE, "System", "/system", "menu.system", "icon-computer", true, false, 2, "system", ""); Menu user = new Menu(null, Menu.Type.PAGE, "User", "/user", "menu.user", "icon-user", true, false, 3, "user", ""); Menu message = new Menu(null, Menu.Type.PAGE, "Message", "/message", "消息管理", "icon-message", true, false, 4, "message", ""); - Menu iot = new Menu(null, Menu.Type.PAGE, "Iot", "/iot", "物联网管理", "icon-iot", true, false, 5, "iot", ""); Menu operationLog = new Menu(null, Menu.Type.PAGE, "OperationLog", "/operationLog", "操作日志", "icon-log", true, false, 6, "operationLog", ""); - menuService.saveBatch(List.of(dashboard, system, user, message, iot, operationLog)); + Menu difyProxyBackend = new Menu(null, Menu.Type.PAGE, "DiFyProxyBackend", "/diFyProxyBackend", "dify", "icon-dify", true, false, 5, "dify", ""); + menuService.saveBatch(List.of(dashboard, system, user, message, operationLog,difyProxyBackend)); Menu workplace = new Menu(dashboard.getId(), Menu.Type.PAGE, "Workplace", "workplace", "工作台", "icon-dashboard", true, false, 1, "dashboard:workplace", ""); Menu role = new Menu(system.getId(), Menu.Type.PAGE, "Role", "role", "角色管理", null, true, false, 1, "system:role", ""); Menu dept = new Menu(system.getId(), Menu.Type.PAGE, "Dept", "dept", "部门管理", null, true, false, 2, "system:dept", ""); @@ -42,11 +42,9 @@ class MenuServiceImplTest { Menu menu = new Menu(system.getId(), Menu.Type.PAGE, "Menu", "menu", "菜单管理", null, true, false, 5, "system:menu", ""); Menu notice = new Menu(message.getId(), Menu.Type.PAGE, "Notice", "notice", "通知管理", null, true, false, 1, "message:notice", ""); Menu bulletin = new Menu(message.getId(), Menu.Type.PAGE, "Bulletin", "bulletin", "公告管理", null, true, false, 2, "message:bulletin", ""); - Menu device = new Menu(iot.getId(), Menu.Type.PAGE, "Device", "device", "设备管理", null, true, false, 1, "iot:device", ""); - Menu product = new Menu(iot.getId(), Menu.Type.PAGE, "Product", "product", "产品管理", null, true, false, 2, "iot:product", ""); - Menu tsl = new Menu(product.getId(), Menu.Type.PAGE, "Tsl", "tsl", "物模型管理", null, true, true, 3, "iot:tsl", ""); Menu log = new Menu(operationLog.getId(), Menu.Type.PAGE, "OperationLog", "operationLog", "操作日志", null, true, false, 1, "operationLog", ""); - menuService.saveBatch(List.of(workplace, dept, users, authority, menu, notice, bulletin, device, product, tsl, role, log)); + Menu dify = new Menu(difyProxyBackend.getId(), Menu.Type.PAGE, "Dify", "dify", "dify", null, true, false, 1, "dify", ""); + menuService.saveBatch(List.of(workplace, dept, users, authority, menu, notice, bulletin,role, log, dify)); Menu roleCreate = new Menu(role.getId(), Menu.Type.OPERATION, "roleCreate", null, "角色新增", null, true, false, 1, "system:role:create", ""); Menu roleDelete = new Menu(role.getId(), Menu.Type.OPERATION, "roleDelete", null, "角色删除", null, true, false, 1, "system:role:delete", ""); Menu roleUpdate = new Menu(role.getId(), Menu.Type.OPERATION, "roleUpdate", null, "角色修改", null, true, false, 1, "system:role:update", ""); @@ -71,40 +69,35 @@ class MenuServiceImplTest { Menu bulletinUpdate = new Menu(bulletin.getId(), Menu.Type.OPERATION, "bulletinUpdate", null, "公告修改", null, true, false, 1, "message:bulletin:update", ""); Menu bulletinQuery = new Menu(bulletin.getId(), Menu.Type.OPERATION, "bulletinQuery", null, "公告查询", null, true, false, 1, "message:bulletin:query", ""); Menu bulletinDelete = new Menu(bulletin.getId(), Menu.Type.OPERATION, "bulletinDelete", null, "公告删除", null, true, false, 1, "message:bulletin:delete", ""); - Menu deviceQuery = new Menu(device.getId(), Menu.Type.OPERATION, "deviceQuery", null, "设备查询", null, true, false, 1, "iot:device:query", ""); - Menu deviceCreate = new Menu(device.getId(), Menu.Type.OPERATION, "deviceCreate", null, "设备新增", null, true, false, 1, "iot:device:create", ""); - Menu deviceUpdate = new Menu(device.getId(), Menu.Type.OPERATION, "deviceUpdate", null, "设备修改", null, true, false, 1, "iot:device:update", ""); - Menu deviceDelete = new Menu(device.getId(), Menu.Type.OPERATION, "deviceDelete", null, "设备删除", null, true, false, 1, "iot:device:delete", ""); - Menu productQuery = new Menu(product.getId(), Menu.Type.OPERATION, "productQuery", null, "产品查询", null, true, false, 1, "iot:product:query", ""); - Menu productCreate = new Menu(product.getId(), Menu.Type.OPERATION, "productCreate", null, "产品新增", null, true, false, 1, "iot:product:create", ""); - Menu productUpdate = new Menu(product.getId(), Menu.Type.OPERATION, "productUpdate", null, "产品修改", null, true, false, 1, "iot:product:update", ""); - Menu productDelete = new Menu(product.getId(), Menu.Type.OPERATION, "productDelete", null, "产品删除", null, true, false, 1, "iot:product:delete", ""); - Menu eventQuery = new Menu(tsl.getId(), Menu.Type.OPERATION, "eventQuery", null, "事件查询", null, true, false, 1, "iot:event:query", ""); - Menu eventCreate = new Menu(tsl.getId(), Menu.Type.OPERATION, "eventCreate", null, "事件新增", null, true, false, 1, "iot:event:create", ""); - Menu eventUpdate = new Menu(tsl.getId(), Menu.Type.OPERATION, "eventUpdate", null, "事件修改", null, true, false, 1, "iot:event:update", ""); - Menu eventDelete = new Menu(tsl.getId(), Menu.Type.OPERATION, "eventDelete", null, "事件删除", null, true, false, 1, "iot:event:delete", ""); - Menu propertyQuery = new Menu(tsl.getId(), Menu.Type.OPERATION, "propertyQuery", null, "属性查询", null, true, false, 1, "iot:property:query", ""); - Menu propertyCreate = new Menu(tsl.getId(), Menu.Type.OPERATION, "propertyCreate", null, "属性新增", null, true, false, 1, "iot:property:create", ""); - Menu propertyUpdate = new Menu(tsl.getId(), Menu.Type.OPERATION, "propertyUpdate", null, "属性修改", null, true, false, 1, "iot:property:update", ""); - Menu propertyDelete = new Menu(tsl.getId(), Menu.Type.OPERATION, "propertyDelete", null, "属性删除", null, true, false, 1, "iot:property:delete", ""); - Menu serverQuery = new Menu(tsl.getId(), Menu.Type.OPERATION, "serverQuery", null, "服务查询", null, true, false, 1, "iot:server:query", ""); - Menu serverCreate = new Menu(tsl.getId(), Menu.Type.OPERATION, "serverCreate", null, "服务新增", null, true, false, 1, "iot:server:create", ""); - Menu serverUpdate = new Menu(tsl.getId(), Menu.Type.OPERATION, "serverUpdate", null, "服务修改", null, true, false, 1, "iot:server:update", ""); - Menu serverDelete = new Menu(tsl.getId(), Menu.Type.OPERATION, "serverDelete", null, "服务删除", null, true, false, 1, "iot:server:delete", ""); Menu operationLogQuery = new Menu(operationLog.getId(), Menu.Type.OPERATION, "operationLogQuery", null, "操作日志查询", null, true, false, 1, "operationLog:query", ""); Menu operationLogDelete = new Menu(operationLog.getId(), Menu.Type.OPERATION, "operationLogDelete", null, "操作日志删除", null, true, false, 1, "operationLog:delete", ""); + Menu difyChatQuery = new Menu(dify.getId(), Menu.Type.OPERATION, "difyChatQuery", null, "difyChat查询", null, true, false, 1, "difyChat:query", ""); + Menu difyChatCreate = new Menu(dify.getId(), Menu.Type.OPERATION, "difyChatCreate", null, "difyChat新增", null, true, false, 1, "difyChat:create", ""); + Menu difyChatUpdate = new Menu(dify.getId(), Menu.Type.OPERATION, "difyChatUpdate", null, "difyChat修改", null, true, false, 1, "difyChat:update", ""); + Menu difyChatDelete = new Menu(dify.getId(), Menu.Type.OPERATION, "difyChatDelete", null, "difyChat删除", null, true, false, 1, "difyChat:delete", ""); + Menu difyServerQuery = new Menu(dify.getId(), Menu.Type.OPERATION, "difyServerQuery", null, "difyServer查询", null, true, false, 1, "difyServer:query", ""); + Menu difyServerCreate = new Menu(dify.getId(), Menu.Type.OPERATION, "difyServerCreate", null, "difyServer新增", null, true, false, 1, "difyServer:create", ""); + Menu difyServerUpdate = new Menu(dify.getId(), Menu.Type.OPERATION, "difyServerUpdate", null, "difyServer修改", null, true, false, 1, "difyServer:update", ""); + Menu difyServerDelete = new Menu(dify.getId(), Menu.Type.OPERATION, "difyServerDelete", null, "difyServer删除", null, true, false, 1, "difyServer:delete", ""); + Menu difyDataSetQuery = new Menu(dify.getId(), Menu.Type.OPERATION, "difyDataSetQuery", null, "difyDataSet查询", null, true, false, 1, "difyDataSet:query", ""); + Menu difyDataSetCreate = new Menu(dify.getId(), Menu.Type.OPERATION, "difyDataSetCreate", null, "difyDataSet新增", null, true, false, 1, "difyDataSet:create", ""); + Menu difyDataSetUpdate = new Menu(dify.getId(), Menu.Type.OPERATION, "difyDataSetUpdate", null, "difyDataSet修改", null, true, false, 1, "difyDataSet:update", ""); + Menu difyDataSetDelete = new Menu(dify.getId(), Menu.Type.OPERATION, "difyDataSetDelete", null, "difyDataSet删除", null, true, false, 1, "difyDataSet:delete", ""); + Menu difyWorkFlowQuery = new Menu(dify.getId(), Menu.Type.OPERATION, "difyWorkFlowQuery", null, "difyWorkFlow查询", null, true, false, 1, "difyWorkFlow:query", ""); + Menu difyWorkFlowCreate = new Menu(dify.getId(), Menu.Type.OPERATION, "difyWorkFlowCreate", null, "difyWorkFlow新增", null, true, false, 1, "difyWorkFlow:create", ""); + Menu difyWorkFlowUpdate = new Menu(dify.getId(), Menu.Type.OPERATION, "difyWorkFlowUpdate", null, "difyWorkFlow修改", null, true, false, 1, "difyWorkFlow:update", ""); + Menu difyWorkFlowDelete = new Menu(dify.getId(), Menu.Type.OPERATION, "difyWorkFlowDelete", null, "difyWorkFlow删除", null, true, false, 1, "difyWorkFlow:delete", ""); menuService.saveBatch(List.of(roleCreate, roleDelete, roleUpdate, roleQuery, deptSave, deptUpdate, deptQuery, deptDelete, userSave, userUpdate, userQuery, userDelete, menuSave, menuUpdate, menuQuery, menuDelete, noticeCreate, noticeUpdate, noticeQuery, noticeDelete, bulletinCreate, bulletinUpdate, bulletinQuery, bulletinDelete, - deviceQuery, deviceCreate, deviceUpdate, deviceDelete, - productQuery, productCreate, productUpdate, productDelete, - eventQuery, eventCreate, eventUpdate, eventDelete, - propertyQuery, propertyCreate, propertyUpdate, propertyDelete, - serverQuery, serverCreate, serverUpdate, serverDelete, - operationLogQuery, operationLogDelete + operationLogQuery, operationLogDelete, + difyChatQuery, difyChatCreate, difyChatUpdate, difyChatDelete, + difyServerQuery, difyServerCreate, difyServerUpdate, difyServerDelete, + difyDataSetQuery, difyDataSetCreate, difyDataSetUpdate, difyDataSetDelete, + difyWorkFlowQuery, difyWorkFlowCreate, difyWorkFlowUpdate, difyWorkFlowDelete )); }