feat(security): 实现基于 Spring WebFlux 的用户认证和权限管理
- 新增 ReactiveUserDetailsService 实现类 FluxUserDetailServiceImpl- 添加 MenuFluxRepository、RoleFluxRepository、UserFluxRepository 和 UserRoleFluxRepository 接口 - 实现 SpringWebFluxSecurityConfig 配置类 - 在 V1ChatController、V1DatasetController、V1ServerController 和 V1WorkflowController 中添加权限控制注解 - 更新 DeptService接口,新增 listFluxTree 方法
This commit is contained in:
parent
9b72d5deb0
commit
54d4e14761
9
pom.xml
9
pom.xml
@ -119,6 +119,15 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-postgresql</artifactId>
|
||||
<version>0.8.13.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
|
@ -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;
|
||||
|
||||
|
@ -24,7 +24,6 @@ import javax.sql.DataSource;
|
||||
/**
|
||||
* @author harry_yao
|
||||
*/
|
||||
//@EnableWebFluxSecurity
|
||||
@AllArgsConstructor
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
|
@ -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<UserDetails> 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<Dept> depts = (List<Dept>) tuple.getT2();
|
||||
List<Menu> menus = tuple.getT3();
|
||||
|
||||
user.setRole(role);
|
||||
|
||||
Set<Long> dataScopeDeptIds = depts.stream()
|
||||
.map(Dept::getId)
|
||||
.collect(Collectors.toSet());
|
||||
user.setDataScopeDeptIds(dataScopeDeptIds);
|
||||
|
||||
Set<String> permissions = menus.stream()
|
||||
.map(Menu::getPermissions)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return Mono.just(UserDetailsImpl.from(user, permissions));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// @Override
|
||||
// public Mono<UserDetails> 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<String> permissions = menus.stream().map(Menu::getPermissions).collect(Collectors.toSet());
|
||||
// return UserDetailsImpl.from(user, permissions);
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
|
||||
}
|
@ -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<AuthorizationContext> 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;
|
||||
}
|
||||
}
|
@ -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,6 +35,7 @@ public class V1ChatController {
|
||||
* apikey 建议在数据库进行存储,前端调用时传智能体 id,从数据库查询
|
||||
*/
|
||||
@PostMapping(value = "/completions/{appId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
@PreAuthorize("hasAuthority('difyChat:query')")
|
||||
public Flux<ChatMessageSendCompletionResponse> 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<MessageConversationsResponse> 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);
|
||||
}
|
||||
|
@ -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<DatasetResponse> 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<DocumentInfo> 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);
|
||||
}
|
||||
|
@ -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<AppsResponseVO> 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<ApiKeyResponseVO> 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<ApiKeyResponseVO> 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<DatasetApiKeyResponseVO> getDatasetApiKey() {
|
||||
return difyServer.getDatasetApiKey();
|
||||
}
|
||||
@ -78,6 +84,7 @@ public class V1ServerController {
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/api-key/dataset/init")
|
||||
@PreAuthorize("hasAuthority('difyServer:create')")
|
||||
public List<DatasetApiKeyResponseVO> 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<AppEntity> getAbleApps() {
|
||||
LambdaQueryWrapper<AppEntity> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(AppEntity::isEnabled, true);
|
||||
|
@ -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<WorkflowLogs> logs(@RequestBody WorkflowLogsRequest request) {
|
||||
request.setApiKey("app-ZpkQM6yy767oUTfNSBYq65nB");
|
||||
return difyWorkflow.logs(request);
|
||||
|
@ -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;
|
||||
|
||||
}
|
@ -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<Menu,Long> {
|
||||
|
||||
@Query("select * from sys_menu where id in (select menu_id from sys_roles_menus where role_id = :roleId)")
|
||||
Mono<List<Menu>> findByRoleId(Long roleId);
|
||||
}
|
@ -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<Role,Long> {
|
||||
|
||||
Mono<?> findByDeptId(Long id);
|
||||
}
|
@ -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<User,Long> {
|
||||
|
||||
Mono<User> findByUsername(String username);
|
||||
}
|
@ -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<UserRole,Long> {
|
||||
|
||||
Flux<List<Long>> findByUserId(Long userId);
|
||||
}
|
@ -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<Dept> {
|
||||
|
||||
Boolean toggle(Long id);
|
||||
|
||||
Mono<?> listFluxTree(Long deptId);
|
||||
|
||||
/**
|
||||
* 生成部门树结构
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
|
@ -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<DeptRepository, Dept> implement
|
||||
return updateById(dept);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<List<Dept>> listFluxTree(Long deptId) {
|
||||
// 1. 将同步数据库查询改为响应式查询(假设 baseMapper 支持响应式)
|
||||
return Mono.fromCallable(() -> baseMapper.selectDeptTree())
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.flatMap(deptTrees -> {
|
||||
// 2. 构建部门树(TreeUtil.makeTree 如果是计算密集型,需在弹性线程执行)
|
||||
List<Dept> 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<Dept> 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<Dept> listTree(Long deptId) {
|
||||
List<Dept> deptTrees = baseMapper.selectDeptTree();
|
||||
@ -84,5 +120,4 @@ public class DeptServiceImpl extends ServiceImpl<DeptRepository, Dept> implement
|
||||
}
|
||||
return deptTree;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
@ -65,3 +71,4 @@ dify:
|
||||
password: tian14384, # 请替换为实际的 Dify 服务密码,若不需要调用 server相关接口可不填
|
||||
dataset:
|
||||
api-key: dataset-kN5WTJ8jR877YfN1A34JceVg # 请替换为实际的知识库api-key, 若不需要调用知识库可不填
|
||||
|
||||
|
@ -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
|
||||
));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user