feat(security): 实现基于 Spring WebFlux 的用户认证和权限管理

- 新增 ReactiveUserDetailsService 实现类 FluxUserDetailServiceImpl- 添加 MenuFluxRepository、RoleFluxRepository、UserFluxRepository 和 UserRoleFluxRepository 接口
- 实现 SpringWebFluxSecurityConfig 配置类
- 在 V1ChatController、V1DatasetController、V1ServerController 和 V1WorkflowController 中添加权限控制注解
- 更新 DeptService接口,新增 listFluxTree 方法
This commit is contained in:
zhuangtianxiang 2025-04-28 00:09:31 +08:00
parent 9b72d5deb0
commit 54d4e14761
18 changed files with 360 additions and 37 deletions

View File

@ -119,6 +119,15 @@
<optional>true</optional> <optional>true</optional>
</dependency> </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> <dependency>
<groupId>org.mapstruct</groupId> <groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId> <artifactId>mapstruct</artifactId>

View File

@ -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.repo.*;
import com.zsc.edu.dify.modules.system.service.DeptService; import com.zsc.edu.dify.modules.system.service.DeptService;
import com.zsc.edu.dify.modules.system.service.RoleService; import com.zsc.edu.dify.modules.system.service.RoleService;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;

View File

@ -24,7 +24,6 @@ import javax.sql.DataSource;
/** /**
* @author harry_yao * @author harry_yao
*/ */
//@EnableWebFluxSecurity
@AllArgsConstructor @AllArgsConstructor
@EnableMethodSecurity @EnableMethodSecurity
@Configuration @Configuration

View File

@ -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);
// })
// );
// }
}

View File

@ -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;
}
}

View File

@ -9,6 +9,7 @@ import io.github.guoshiqiufeng.dify.chat.dto.response.MessageConversationsRespon
import io.github.guoshiqiufeng.dify.core.pojo.DifyPageResult; import io.github.guoshiqiufeng.dify.core.pojo.DifyPageResult;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
@ -34,7 +35,8 @@ public class V1ChatController {
* apikey 建议在数据库进行存储前端调用时传智能体 id从数据库查询 * apikey 建议在数据库进行存储前端调用时传智能体 id从数据库查询
*/ */
@PostMapping(value = "/completions/{appId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @PostMapping(value = "/completions/{appId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatMessageSendCompletionResponse> sendChatMessageStream(@RequestBody ChatMessageSendRequest sendRequest,@PathVariable String appId) { @PreAuthorize("hasAuthority('difyChat:query')")
public Flux<ChatMessageSendCompletionResponse> sendChatMessageStream(@RequestBody ChatMessageSendRequest sendRequest, @PathVariable String appId) {
sendRequest.setApiKey(appEntityRepository.selectApiKey(appId)); sendRequest.setApiKey(appEntityRepository.selectApiKey(appId));
return difyChat.sendChatMessageStream(sendRequest); return difyChat.sendChatMessageStream(sendRequest);
} }
@ -46,6 +48,7 @@ public class V1ChatController {
* @return 会话列表 * @return 会话列表
*/ */
@PostMapping("/conversations") @PostMapping("/conversations")
@PreAuthorize("hasAuthority('difyChat:query')")
public DifyPageResult<MessageConversationsResponse> conversations(@RequestBody MessageConversationsRequest request) { public DifyPageResult<MessageConversationsResponse> conversations(@RequestBody MessageConversationsRequest request) {
request.setApiKey("app-mM2UGTE5QVPLCwGvwifnV0g7"); request.setApiKey("app-mM2UGTE5QVPLCwGvwifnV0g7");
return difyChat.conversations(request); return difyChat.conversations(request);
@ -58,6 +61,7 @@ public class V1ChatController {
* @param userId 用户id * @param userId 用户id
*/ */
@PatchMapping("/stopMessagesStream") @PatchMapping("/stopMessagesStream")
@PreAuthorize("hasAuthority('difyChat:update')")
public void stopMessagesStream( String taskId, String userId) { public void stopMessagesStream( String taskId, String userId) {
difyChat.stopMessagesStream("app-mM2UGTE5QVPLCwGvwifnV0g7", taskId, userId); difyChat.stopMessagesStream("app-mM2UGTE5QVPLCwGvwifnV0g7", taskId, userId);
} }
@ -69,6 +73,7 @@ public class V1ChatController {
* @param userId 用户id * @param userId 用户id
*/ */
@DeleteMapping("/messages/suggested") @DeleteMapping("/messages/suggested")
@PreAuthorize("hasAuthority('difyChat:delete')")
public void deleteConversation(String conversationId, String userId) { public void deleteConversation(String conversationId, String userId) {
difyChat.deleteConversation(conversationId, "app-mM2UGTE5QVPLCwGvwifnV0g7", userId); difyChat.deleteConversation(conversationId, "app-mM2UGTE5QVPLCwGvwifnV0g7", userId);
} }

View File

@ -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.request.*;
import io.github.guoshiqiufeng.dify.dataset.dto.response.*; import io.github.guoshiqiufeng.dify.dataset.dto.response.*;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@RestController @RestController
@ -20,6 +21,7 @@ public class V1DatasetController {
* @return * @return
*/ */
@PostMapping("/page") @PostMapping("/page")
@PreAuthorize("hasAuthority('difyDataSet:query')")
public DifyPageResult<DatasetResponse> page(@RequestBody DatasetPageRequest request){ public DifyPageResult<DatasetResponse> page(@RequestBody DatasetPageRequest request){
request.setApiKey("dataset-kN5WTJ8jR877YfN1A34JceVg"); request.setApiKey("dataset-kN5WTJ8jR877YfN1A34JceVg");
return difyDataset.page(request); return difyDataset.page(request);
@ -32,6 +34,7 @@ public class V1DatasetController {
* @return * @return
*/ */
@PostMapping("/createDocumentByFile") @PostMapping("/createDocumentByFile")
@PreAuthorize("hasAuthority('difyDataSet:create')")
public DocumentCreateResponse createDocumentByFile(DocumentCreateByFileRequest request){ public DocumentCreateResponse createDocumentByFile(DocumentCreateByFileRequest request){
return difyDataset.createDocumentByFile(request); return difyDataset.createDocumentByFile(request);
} }
@ -43,6 +46,7 @@ public class V1DatasetController {
* @return * @return
*/ */
@GetMapping("/pageDocument") @GetMapping("/pageDocument")
@PreAuthorize("hasAuthority('difyDataSet:query')")
public DifyPageResult<DocumentInfo> pageDocument(@RequestBody DatasetPageDocumentRequest request){ public DifyPageResult<DocumentInfo> pageDocument(@RequestBody DatasetPageDocumentRequest request){
request.setApiKey("dataset-kN5WTJ8jR877YfN1A34JceVg"); request.setApiKey("dataset-kN5WTJ8jR877YfN1A34JceVg");
return difyDataset.pageDocument(request); return difyDataset.pageDocument(request);
@ -56,6 +60,7 @@ public class V1DatasetController {
* @return * @return
*/ */
@PostMapping("/uploadFileInfo") @PostMapping("/uploadFileInfo")
@PreAuthorize("hasAuthority('difyDataSet:query')")
public UploadFileInfoResponse uploadFileInfo(String datasetId, String documentId){ public UploadFileInfoResponse uploadFileInfo(String datasetId, String documentId){
return difyDataset.uploadFileInfo(datasetId, documentId,"dataset-kN5WTJ8jR877YfN1A34JceVg"); return difyDataset.uploadFileInfo(datasetId, documentId,"dataset-kN5WTJ8jR877YfN1A34JceVg");
} }
@ -68,6 +73,7 @@ public class V1DatasetController {
* @return * @return
*/ */
@DeleteMapping("/deleteDocument") @DeleteMapping("/deleteDocument")
@PreAuthorize("hasAuthority('difyDataSet:delete')")
public DocumentDeleteResponse deleteDocument(String datasetId, String documentId){ public DocumentDeleteResponse deleteDocument(String datasetId, String documentId){
return difyDataset.deleteDocument(datasetId, documentId, "app-mM2UGTE5QVPLCwGvwifnV0g7"); return difyDataset.deleteDocument(datasetId, documentId, "app-mM2UGTE5QVPLCwGvwifnV0g7");
} }
@ -79,6 +85,7 @@ public class V1DatasetController {
* @return * @return
*/ */
@PostMapping("/createSegment") @PostMapping("/createSegment")
@PreAuthorize("hasAuthority('difyDataSet:create')")
public SegmentResponse createSegment(@RequestBody SegmentCreateRequest request){ public SegmentResponse createSegment(@RequestBody SegmentCreateRequest request){
return difyDataset.createSegment(request); return difyDataset.createSegment(request);
} }
@ -90,6 +97,7 @@ public class V1DatasetController {
* @return * @return
*/ */
@PostMapping("/createSegmentChildChunk") @PostMapping("/createSegmentChildChunk")
@PreAuthorize("hasAuthority('difyDataSet:create')")
public SegmentChildChunkCreateResponse createSegmentChildChunk(@RequestBody SegmentChildChunkCreateRequest request){ public SegmentChildChunkCreateResponse createSegmentChildChunk(@RequestBody SegmentChildChunkCreateRequest request){
return difyDataset.createSegmentChildChunk(request); return difyDataset.createSegmentChildChunk(request);
} }
@ -101,6 +109,7 @@ public class V1DatasetController {
* @return * @return
*/ */
@GetMapping("/retrieve") @GetMapping("/retrieve")
@PreAuthorize("hasAuthority('difyDataSet:query')")
public RetrieveResponse retrieve(@RequestBody RetrieveRequest request){ public RetrieveResponse retrieve(@RequestBody RetrieveRequest request){
return difyDataset.retrieve(request); return difyDataset.retrieve(request);
} }

View File

@ -10,6 +10,7 @@ import io.github.guoshiqiufeng.dify.server.dto.response.AppsResponseVO;
import io.github.guoshiqiufeng.dify.server.dto.response.DatasetApiKeyResponseVO; import io.github.guoshiqiufeng.dify.server.dto.response.DatasetApiKeyResponseVO;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
@ -30,6 +31,7 @@ public class V1ServerController {
* @return * @return
*/ */
@GetMapping("/apps") @GetMapping("/apps")
@PreAuthorize("hasAuthority('difyServer:query')")
public List<AppsResponseVO> getApps(String mode, String name) { public List<AppsResponseVO> getApps(String mode, String name) {
return difyServerService.getApps(mode, name); return difyServerService.getApps(mode, name);
} }
@ -40,6 +42,7 @@ public class V1ServerController {
* @return * @return
*/ */
@GetMapping("/{id}") @GetMapping("/{id}")
@PreAuthorize("hasAuthority('difyServer:query')")
public AppsResponseVO getApp(@PathVariable("id") String id) { public AppsResponseVO getApp(@PathVariable("id") String id) {
return difyServer.app(id); return difyServer.app(id);
} }
@ -50,6 +53,7 @@ public class V1ServerController {
* @return * @return
*/ */
@GetMapping("/api-key/{id}") @GetMapping("/api-key/{id}")
@PreAuthorize("hasAuthority('difyServer:query')")
public List<ApiKeyResponseVO> getAppApiKey(@PathVariable("id") String id) { public List<ApiKeyResponseVO> getAppApiKey(@PathVariable("id") String id) {
return difyServer.getAppApiKey(id); return difyServer.getAppApiKey(id);
} }
@ -60,6 +64,7 @@ public class V1ServerController {
* @return * @return
*/ */
@PostMapping("/api-key/init/{id}") @PostMapping("/api-key/init/{id}")
@PreAuthorize("hasAuthority('difyServer:create')")
public List<ApiKeyResponseVO> initAppApiKey(@PathVariable("id") String id) { public List<ApiKeyResponseVO> initAppApiKey(@PathVariable("id") String id) {
return difyServer.initAppApiKey(id); return difyServer.initAppApiKey(id);
} }
@ -69,6 +74,7 @@ public class V1ServerController {
* @return * @return
*/ */
@GetMapping("/api-key/dataset") @GetMapping("/api-key/dataset")
@PreAuthorize("hasAuthority('difyServer:query')")
public List<DatasetApiKeyResponseVO> getDatasetApiKey() { public List<DatasetApiKeyResponseVO> getDatasetApiKey() {
return difyServer.getDatasetApiKey(); return difyServer.getDatasetApiKey();
} }
@ -78,6 +84,7 @@ public class V1ServerController {
* @return * @return
*/ */
@PostMapping("/api-key/dataset/init") @PostMapping("/api-key/dataset/init")
@PreAuthorize("hasAuthority('difyServer:create')")
public List<DatasetApiKeyResponseVO> initDatasetApiKey() { public List<DatasetApiKeyResponseVO> initDatasetApiKey() {
return difyServer.initDatasetApiKey(); return difyServer.initDatasetApiKey();
} }
@ -89,6 +96,7 @@ public class V1ServerController {
* @return * @return
*/ */
@PostMapping("/app/{id}") @PostMapping("/app/{id}")
@PreAuthorize("hasAuthority('difyServer:update')")
public boolean enabledApp(@PathVariable String id) { public boolean enabledApp(@PathVariable String id) {
return difyServerService.enabledApp(id); return difyServerService.enabledApp(id);
} }
@ -98,6 +106,7 @@ public class V1ServerController {
* @return * @return
*/ */
@GetMapping("/apps/able") @GetMapping("/apps/able")
@PreAuthorize("hasAuthority('difyServer:query')")
public List<AppEntity> getAbleApps() { public List<AppEntity> getAbleApps() {
LambdaQueryWrapper<AppEntity> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<AppEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AppEntity::isEnabled, true); queryWrapper.eq(AppEntity::isEnabled, true);

View File

@ -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.request.WorkflowRunRequest;
import io.github.guoshiqiufeng.dify.workflow.dto.response.*; import io.github.guoshiqiufeng.dify.workflow.dto.response.*;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
@ -23,6 +24,7 @@ public class V1WorkflowController {
* @return * @return
*/ */
@PostMapping("/run") @PostMapping("/run")
@PreAuthorize("hasAuthority('difyWorkFlow:query')")
public WorkflowRunResponse runWorkflow(@RequestBody WorkflowRunRequest request) { public WorkflowRunResponse runWorkflow(@RequestBody WorkflowRunRequest request) {
request.setApiKey("app-ZpkQM6yy767oUTfNSBYq65nB"); request.setApiKey("app-ZpkQM6yy767oUTfNSBYq65nB");
return difyWorkflow.runWorkflow(request); return difyWorkflow.runWorkflow(request);
@ -59,6 +61,7 @@ public class V1WorkflowController {
* @return * @return
*/ */
@GetMapping("/info") @GetMapping("/info")
@PreAuthorize("hasAuthority('difyWorkFlow:query')")
public WorkflowInfoResponse info(String workflowRunId) { public WorkflowInfoResponse info(String workflowRunId) {
return difyWorkflow.info(workflowRunId, "app-ZpkQM6yy767oUTfNSBYq65nB"); return difyWorkflow.info(workflowRunId, "app-ZpkQM6yy767oUTfNSBYq65nB");
} }
@ -70,6 +73,7 @@ public class V1WorkflowController {
* @return * @return
*/ */
@PostMapping("/logs") @PostMapping("/logs")
@PreAuthorize("hasAuthority('difyWorkFlow:query')")
public DifyPageResult<WorkflowLogs> logs(@RequestBody WorkflowLogsRequest request) { public DifyPageResult<WorkflowLogs> logs(@RequestBody WorkflowLogsRequest request) {
request.setApiKey("app-ZpkQM6yy767oUTfNSBYq65nB"); request.setApiKey("app-ZpkQM6yy767oUTfNSBYq65nB");
return difyWorkflow.logs(request); return difyWorkflow.logs(request);

View File

@ -1,7 +1,7 @@
package com.zsc.edu.dify.modules.system.entity; package com.zsc.edu.dify.modules.system.entity;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.*;
import java.io.Serializable; import java.io.Serializable;
@ -10,6 +10,7 @@ import java.io.Serializable;
* @author zhuang * @author zhuang
*/ */
@Data @Data
@Getter
@TableName("sys_users_roles") @TableName("sys_users_roles")
public class UserRole implements Serializable { public class UserRole implements Serializable {
/** /**
@ -20,5 +21,7 @@ public class UserRole implements Serializable {
/** /**
* 角色ID * 角色ID
*/ */
@Getter
private Long roleId; private Long roleId;
} }

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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.dto.DeptDto;
import com.zsc.edu.dify.modules.system.entity.Dept; import com.zsc.edu.dify.modules.system.entity.Dept;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import reactor.core.publisher.Mono;
import java.util.List; import java.util.List;
@ -31,8 +32,11 @@ public interface DeptService extends IService<Dept> {
Boolean toggle(Long id); Boolean toggle(Long id);
Mono<?> listFluxTree(Long deptId);
/** /**
* 生成部门树结构 * 生成部门树结构
*
* @param id * @param id
* @return * @return
*/ */

View File

@ -11,6 +11,8 @@ import com.zsc.edu.dify.modules.system.service.DeptService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -57,6 +59,40 @@ public class DeptServiceImpl extends ServiceImpl<DeptRepository, Dept> implement
return updateById(dept); 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 @Override
public List<Dept> listTree(Long deptId) { public List<Dept> listTree(Long deptId) {
List<Dept> deptTrees = baseMapper.selectDeptTree(); List<Dept> deptTrees = baseMapper.selectDeptTree();
@ -84,5 +120,4 @@ public class DeptServiceImpl extends ServiceImpl<DeptRepository, Dept> implement
} }
return deptTree; return deptTree;
} }
} }

View File

@ -11,6 +11,12 @@ mybatis-plus:
map-underscore-to-camel-case: true map-underscore-to-camel-case: true
spring: 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: datasource:
url: jdbc:postgresql://43.139.10.64:15432/dify?ssl=false&TimeZone=Asia/Shanghai url: jdbc:postgresql://43.139.10.64:15432/dify?ssl=false&TimeZone=Asia/Shanghai
username: gitea username: gitea
@ -64,4 +70,5 @@ dify:
email: 2913129173@qq.com # 请替换为实际的 Dify 服务邮箱,若不需要调用 server相关接口可不填 email: 2913129173@qq.com # 请替换为实际的 Dify 服务邮箱,若不需要调用 server相关接口可不填
password: tian14384, # 请替换为实际的 Dify 服务密码,若不需要调用 server相关接口可不填 password: tian14384, # 请替换为实际的 Dify 服务密码,若不需要调用 server相关接口可不填
dataset: dataset:
api-key: dataset-kN5WTJ8jR877YfN1A34JceVg # 请替换为实际的知识库api-key, 若不需要调用知识库可不填 api-key: dataset-kN5WTJ8jR877YfN1A34JceVg # 请替换为实际的知识库api-key, 若不需要调用知识库可不填

View File

@ -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 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 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 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", ""); 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 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 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", ""); 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 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 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 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", ""); 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 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 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", ""); 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 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 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 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 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 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, menuService.saveBatch(List.of(roleCreate, roleDelete, roleUpdate, roleQuery,
deptSave, deptUpdate, deptQuery, deptDelete, deptSave, deptUpdate, deptQuery, deptDelete,
userSave, userUpdate, userQuery, userDelete, userSave, userUpdate, userQuery, userDelete,
menuSave, menuUpdate, menuQuery, menuDelete, menuSave, menuUpdate, menuQuery, menuDelete,
noticeCreate, noticeUpdate, noticeQuery, noticeDelete, noticeCreate, noticeUpdate, noticeQuery, noticeDelete,
bulletinCreate, bulletinUpdate, bulletinQuery, bulletinDelete, bulletinCreate, bulletinUpdate, bulletinQuery, bulletinDelete,
deviceQuery, deviceCreate, deviceUpdate, deviceDelete, operationLogQuery, operationLogDelete,
productQuery, productCreate, productUpdate, productDelete, difyChatQuery, difyChatCreate, difyChatUpdate, difyChatDelete,
eventQuery, eventCreate, eventUpdate, eventDelete, difyServerQuery, difyServerCreate, difyServerUpdate, difyServerDelete,
propertyQuery, propertyCreate, propertyUpdate, propertyDelete, difyDataSetQuery, difyDataSetCreate, difyDataSetUpdate, difyDataSetDelete,
serverQuery, serverCreate, serverUpdate, serverDelete, difyWorkFlowQuery, difyWorkFlowCreate, difyWorkFlowUpdate, difyWorkFlowDelete
operationLogQuery, operationLogDelete
)); ));
} }