Browse Source

Merge pull request #204 from jetlinks/fix-bug

同步协议模块代码
老周 2 years ago
parent
commit
1a25e693b9

+ 2 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/reference/DataReferenceManager.java

@@ -24,6 +24,8 @@ public interface DataReferenceManager {
     String TYPE_NETWORK = "network";
     //数据类型:关系配置
     String TYPE_RELATION = "relation";
+    //数据类型:消息协议
+    String TYPE_PROTOCOL = "protocol";
 
     /**
      * 判断指定数据类型的数据是否已经被其他地方所引用

+ 122 - 0
jetlinks-manager/authentication-manager/src/main/java/org/jetlinks/community/auth/initialize/MenuAuthenticationInitializeService.java

@@ -0,0 +1,122 @@
+package org.jetlinks.community.auth.initialize;
+
+import lombok.AllArgsConstructor;
+import org.apache.commons.collections4.CollectionUtils;
+import org.hswebframework.web.api.crud.entity.QueryParamEntity;
+import org.hswebframework.web.authorization.DefaultDimensionType;
+import org.hswebframework.web.authorization.Permission;
+import org.hswebframework.web.authorization.events.AuthorizationInitializeEvent;
+import org.hswebframework.web.authorization.simple.SimpleAuthentication;
+import org.hswebframework.web.authorization.simple.SimplePermission;
+import org.hswebframework.web.system.authorization.api.entity.ActionEntity;
+import org.hswebframework.web.system.authorization.api.entity.PermissionEntity;
+import org.hswebframework.web.system.authorization.defaults.service.DefaultPermissionService;
+import org.jetlinks.community.auth.entity.MenuEntity;
+import org.jetlinks.community.auth.entity.MenuView;
+import org.jetlinks.community.auth.service.DefaultMenuService;
+import org.jetlinks.community.auth.service.request.MenuGrantRequest;
+import org.jetlinks.community.auth.web.request.AuthorizationSettingDetail;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@AllArgsConstructor
+@Component
+public class MenuAuthenticationInitializeService {
+
+    private final DefaultMenuService menuService;
+
+    private final DefaultPermissionService permissionService;
+
+    /**
+     * 根据角色配置的菜单权限来重构权限信息
+     *
+     * @param event 权限初始化事件
+     */
+    @EventListener
+    public void refactorPermission(AuthorizationInitializeEvent event) {
+        if (event.getAuthentication().getDimensions().isEmpty()) {
+            return;
+        }
+        event.async(
+            Mono
+                .zip(
+                    // T1: 权限定义列表
+                    permissionService
+                        .createQuery()
+                        .where(PermissionEntity::getStatus, 1)
+                        .fetch()
+                        .collectMap(PermissionEntity::getId, Function.identity()),
+                    // T2: 菜单定义列表
+                    menuService
+                        .createQuery()
+                        .where(MenuEntity::getStatus, 1)
+                        .fetch()
+                        .collectList(),
+                    // T3: 角色赋予的菜单列表
+                    menuService
+                        .getGrantedMenus(QueryParamEntity.of(), event
+                            .getAuthentication()
+                            .getDimensions())
+                        .collectList()
+                        .filter(CollectionUtils::isNotEmpty)
+                )
+                .<Permission>flatMapIterable(tp3 -> {
+                    Map<String, PermissionEntity> permissions = tp3.getT1();
+                    List<MenuEntity> menus = tp3.getT2();
+                    List<MenuView> grantedMenus = tp3.getT3();
+                    MenuGrantRequest request = new MenuGrantRequest();
+                    request.setTargetType(DefaultDimensionType.role.getId());
+                    request.setTargetId("merge");
+                    request.setMenus(grantedMenus);
+                    AuthorizationSettingDetail detail = request.toAuthorizationSettingDetail(menus);
+                    return detail
+                        .getPermissionList()
+                        .stream()
+                        .map(per -> {
+                            PermissionEntity entity = permissions.get(per.getId());
+                            if (entity == null || per.getActions() == null) {
+                                return null;
+                            }
+
+                            Set<String> actions;
+                            if (CollectionUtils.isEmpty(entity.getActions())) {
+                                actions = new HashSet<>();
+                            } else {
+                                Set<String> defActions = entity
+                                    .getActions()
+                                    .stream()
+                                    .map(ActionEntity::getAction)
+                                    .collect(Collectors.toSet());
+                                actions = new HashSet<>(per.getActions());
+                                actions.retainAll(defActions);
+                            }
+
+                            return SimplePermission
+                                .builder()
+                                .id(entity.getId())
+                                .name(entity.getName())
+                                .options(entity.getProperties())
+                                .actions(actions)
+                                .build();
+                        })
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toList());
+                })
+                .collectList()
+                .filter(CollectionUtils::isNotEmpty)
+                .doOnNext(mapping -> {
+                    SimpleAuthentication authentication = new SimpleAuthentication();
+                    authentication.setUser(event.getAuthentication().getUser());
+                    authentication.setPermissions(mapping);
+                    event.setAuthentication(event.getAuthentication().merge(authentication));
+                })
+        );
+
+    }
+
+}

+ 0 - 30
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/entity/DeviceCategory.java

@@ -1,30 +0,0 @@
-package org.jetlinks.community.device.entity;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Getter;
-import lombok.Setter;
-import org.hswebframework.web.api.crud.entity.GenericTreeSortSupportEntity;
-
-import java.util.List;
-
-@Getter
-@Setter
-public class DeviceCategory extends GenericTreeSortSupportEntity<String> {
-
-    @Schema(description = "ID")
-    private String id;
-
-    @Schema(description = "标识")
-    private String key;
-
-    @Schema(description = "名称")
-    private String name;
-
-    @Schema(description = "父节点标识")
-    private String parentId;
-
-    @Schema(description = "子节点")
-    private List<DeviceCategory> children;
-
-
-}

+ 79 - 0
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/entity/DeviceCategoryEntity.java

@@ -0,0 +1,79 @@
+package org.jetlinks.community.device.entity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType;
+import org.hswebframework.ezorm.rdb.mapping.annotation.Comment;
+import org.hswebframework.ezorm.rdb.mapping.annotation.DefaultValue;
+import org.hswebframework.web.api.crud.entity.GenericTreeSortSupportEntity;
+import org.hswebframework.web.api.crud.entity.RecordCreationEntity;
+import org.hswebframework.web.crud.annotation.EnableEntityEvent;
+import org.hswebframework.web.crud.generator.Generators;
+import org.hswebframework.web.validator.CreateGroup;
+
+import javax.persistence.Column;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import java.sql.JDBCType;
+import java.util.List;
+
+@Getter
+@Setter
+@Table(name = "dev_product_category")
+@Comment("产品分类信息表")
+@EnableEntityEvent
+public class DeviceCategoryEntity extends GenericTreeSortSupportEntity<String> implements RecordCreationEntity {
+
+    @Override
+    @Id
+    @Column(length = 64, updatable = false)
+    @GeneratedValue(generator = Generators.SNOW_FLAKE)
+    @NotBlank(message = "ID不能为空", groups = CreateGroup.class)
+    @Pattern(regexp = "^[0-9a-zA-Z_\\-|]+$", message = "ID只能由数字,字母,下划线和中划线组成", groups = CreateGroup.class)
+    public String getId() {
+        return super.getId();
+    }
+
+    @Schema(description = "标识")
+    @Column(nullable = false,length = 64)
+    @NotBlank(message = "标识不能为空", groups = CreateGroup.class)
+    @GeneratedValue(generator = Generators.SNOW_FLAKE)
+    @Pattern(regexp = "^[0-9a-zA-Z_\\-]+$", message = "分类标识只能由数字,字母,下划线和中划线组成")
+    private String key;
+
+    @Schema(description = "名称")
+    @Column(nullable = false)
+    @NotBlank
+    private String name;
+
+    @Schema(description = "说明")
+    @Column
+    private String description;
+
+    @Schema(description = "子节点")
+    private List<DeviceCategoryEntity> children;
+
+    @Schema(description = "物模型")
+    @Column
+    @ColumnType(javaType = String.class, jdbcType = JDBCType.CLOB)
+    private String metadata;
+
+    @Column(updatable = false)
+    @Schema(
+        description = "创建者ID(只读)"
+        , accessMode = Schema.AccessMode.READ_ONLY
+    )
+    private String creatorId;
+
+    @Column(updatable = false)
+    @DefaultValue(generator = Generators.CURRENT_TIME)
+    @Schema(
+        description = "创建时间(只读)"
+        , accessMode = Schema.AccessMode.READ_ONLY
+    )
+    private Long createTime;
+}

+ 80 - 0
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/service/DeviceCategoryService.java

@@ -0,0 +1,80 @@
+package org.jetlinks.community.device.service;
+
+import com.alibaba.fastjson.JSON;
+import org.hswebframework.web.api.crud.entity.TreeSupportEntity;
+import org.hswebframework.web.crud.service.GenericReactiveTreeSupportCrudService;
+import org.hswebframework.web.id.IDGenerator;
+import org.jetlinks.community.device.entity.DeviceCategoryEntity;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StreamUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+@Service
+public class DeviceCategoryService extends GenericReactiveTreeSupportCrudService<DeviceCategoryEntity, String> implements CommandLineRunner {
+
+    @Override
+    public IDGenerator<String> getIDGenerator() {
+        return IDGenerator.MD5;
+    }
+
+    private static final String category_splitter = "-";
+    @Override
+    public void setChildren(DeviceCategoryEntity entity, List<DeviceCategoryEntity> children) {
+        entity.setChildren(children);
+    }
+
+    @Override
+    public void run(String... args) {
+        this
+            .createQuery()
+            .fetchOne()
+            .switchIfEmpty(initDefaultData().then(Mono.empty()))
+            .subscribe();
+    }
+
+
+    static void rebuild(String parentId, List<DeviceCategoryEntity> children) {
+        if (children == null) {
+            return;
+        }
+        for (DeviceCategoryEntity child : children) {
+            String id = child.getId();
+            child.setId(parentId + category_splitter + id +category_splitter);
+            child.setParentId(parentId +category_splitter);
+            rebuild(parentId + category_splitter + id, child.getChildren());
+        }
+    }
+
+    private Mono<Void> initDefaultData() {
+        return Mono
+            .fromCallable(() -> {
+                ClassPathResource resource = new ClassPathResource("device-category.json");
+
+                try (InputStream stream = resource.getInputStream()) {
+                    String json = StreamUtils.copyToString(stream, StandardCharsets.UTF_8);
+
+                    List<DeviceCategoryEntity> all = JSON.parseArray(json, DeviceCategoryEntity.class);
+
+                    List<DeviceCategoryEntity> root = TreeSupportEntity.list2tree(all, DeviceCategoryEntity::setChildren);
+
+                    for (DeviceCategoryEntity category : root) {
+                        String id = category.getId();
+                        category.setId(category_splitter + id + category_splitter);
+                        category.setParentId(category_splitter + category.getParentId() + category_splitter);
+                        rebuild(category_splitter + id, category.getChildren());
+                    }
+                    return root;
+                }
+
+            })
+            .flatMap(all -> save(Flux.fromIterable(all)))
+            .then();
+    }
+}

+ 29 - 22
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/service/LocalProtocolSupportService.java

@@ -1,48 +1,55 @@
 package org.jetlinks.community.device.service;
 
+import lombok.extern.slf4j.Slf4j;
 import org.hswebframework.web.crud.service.GenericReactiveCrudService;
-import org.hswebframework.web.exception.BusinessException;
 import org.hswebframework.web.exception.NotFoundException;
 import org.jetlinks.community.device.entity.ProtocolSupportEntity;
-import org.jetlinks.supports.protocol.management.ProtocolSupportLoader;
+import org.jetlinks.community.reference.DataReferenceManager;
 import org.jetlinks.supports.protocol.management.ProtocolSupportManager;
+import org.reactivestreams.Publisher;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 @Service
+@Slf4j
 public class LocalProtocolSupportService extends GenericReactiveCrudService<ProtocolSupportEntity, String> {
 
     @Autowired
     private ProtocolSupportManager supportManager;
 
     @Autowired
-    private ProtocolSupportLoader loader;
+    private DataReferenceManager referenceManager;
+
+    @Override
+    public Mono<Integer> deleteById(Publisher<String> idPublisher) {
+        return Flux.from(idPublisher)
+                   .flatMap(id -> supportManager.remove(id).thenReturn(id))
+                   .as(super::deleteById);
+    }
 
     public Mono<Boolean> deploy(String id) {
         return findById(Mono.just(id))
-                .switchIfEmpty(Mono.error(NotFoundException::new))
-                .map(ProtocolSupportEntity::toDeployDefinition)
-                .flatMap(def->loader.load(def).thenReturn(def))
-                .onErrorMap(err->new BusinessException("无法加载协议:"+err.getMessage(),err))
-                .flatMap(supportManager::save)
-                .flatMap(r -> createUpdate()
-                        .set(ProtocolSupportEntity::getState, 1)
-                        .where(ProtocolSupportEntity::getId, id)
-                        .execute())
-                .map(i -> i > 0);
+            .switchIfEmpty(Mono.error(NotFoundException::new))
+            .flatMap(r -> createUpdate()
+                .set(ProtocolSupportEntity::getState, 1)
+                .where(ProtocolSupportEntity::getId, id)
+                .execute())
+            .map(i -> i > 0);
     }
 
     public Mono<Boolean> unDeploy(String id) {
-        return findById(Mono.just(id))
-                .switchIfEmpty(Mono.error(NotFoundException::new))
-                .map(ProtocolSupportEntity::toUnDeployDefinition)
-                .flatMap(supportManager::save)
-                .flatMap(r -> createUpdate()
-                        .set(ProtocolSupportEntity::getState, 0)
-                        .where(ProtocolSupportEntity::getId, id)
-                        .execute())
-                .map(i -> i > 0);
+        // 消息协议被使用时,不能禁用
+        return referenceManager
+            .assertNotReferenced(DataReferenceManager.TYPE_PROTOCOL, id)
+            .then(findById(Mono.just(id)))
+            .switchIfEmpty(Mono.error(NotFoundException::new))
+            .flatMap(r -> createUpdate()
+                .set(ProtocolSupportEntity::getState, 0)
+                .where(ProtocolSupportEntity::getId, id)
+                .execute())
+            .map(i -> i > 0);
     }
 
 }

+ 73 - 0
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/service/ProtocolSupportHandler.java

@@ -0,0 +1,73 @@
+package org.jetlinks.community.device.service;
+
+import lombok.AllArgsConstructor;
+import org.hswebframework.web.crud.events.EntityBeforeDeleteEvent;
+import org.hswebframework.web.crud.events.EntityCreatedEvent;
+import org.hswebframework.web.crud.events.EntityModifyEvent;
+import org.hswebframework.web.crud.events.EntitySavedEvent;
+import org.hswebframework.web.exception.BusinessException;
+import org.jetlinks.community.device.entity.ProtocolSupportEntity;
+import org.jetlinks.community.reference.DataReferenceManager;
+import org.jetlinks.core.ProtocolSupport;
+import org.jetlinks.supports.protocol.management.ProtocolSupportLoader;
+import org.jetlinks.supports.protocol.management.ProtocolSupportManager;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Collection;
+
+/**
+ * 协议事件处理类.
+ *
+ * @author zhangji 2022/4/1
+ */
+@Component
+@AllArgsConstructor
+public class ProtocolSupportHandler {
+    private final DataReferenceManager referenceManager;
+    private       ProtocolSupportLoader  loader;
+    private       ProtocolSupportManager supportManager;
+
+    //禁止删除已有网关使用的协议
+    @EventListener
+    public void handleProtocolDelete(EntityBeforeDeleteEvent<ProtocolSupportEntity> event) {
+        event.async(
+            Flux.fromIterable(event.getEntity())
+                .flatMap(protocol -> referenceManager
+                    .assertNotReferenced(DataReferenceManager.TYPE_PROTOCOL, protocol.getId()))
+        );
+    }
+
+    @EventListener
+    public void handleCreated(EntityCreatedEvent<ProtocolSupportEntity> event) {
+        event.async(reloadProtocol(event.getEntity()));
+    }
+
+    @EventListener
+    public void handleSaved(EntitySavedEvent<ProtocolSupportEntity> event) {
+        event.async(reloadProtocol(event.getEntity()));
+    }
+
+    @EventListener
+    public void handleModify(EntityModifyEvent<ProtocolSupportEntity> event) {
+        event.async(reloadProtocol(event.getAfter()));
+    }
+
+    // 重新加载协议
+    private Mono<Void> reloadProtocol(Collection<ProtocolSupportEntity> protocol) {
+        return Flux
+            .fromIterable(protocol)
+            .filter(entity -> entity.getState() != null)
+            .map(entity -> entity.getState() == 1 ? entity.toDeployDefinition() : entity.toUnDeployDefinition())
+            .flatMap(def -> loader
+                //加载一下检验是否正确,然后就卸载
+                .load(def)
+                .doOnNext(ProtocolSupport::dispose)
+                .thenReturn(def))
+            .onErrorMap(err -> new BusinessException("error.unable_to_load_protocol", 500, err.getMessage()))
+            .flatMap(supportManager::save)
+            .then();
+    }
+}

+ 50 - 63
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/web/DeviceCategoryController.java

@@ -1,85 +1,72 @@
 package org.jetlinks.community.device.web;
 
-import com.alibaba.fastjson.JSON;
-import io.swagger.v3.oas.annotations.Operation;
+
+import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.web.api.crud.entity.QueryNoPagingOperation;
+import org.hswebframework.web.api.crud.entity.QueryParamEntity;
 import org.hswebframework.web.api.crud.entity.TreeSupportEntity;
-import org.jetlinks.community.device.entity.DeviceCategory;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.util.StreamUtils;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.hswebframework.web.authorization.annotation.Authorize;
+import org.hswebframework.web.authorization.annotation.Resource;
+import org.hswebframework.web.crud.service.ReactiveCrudService;
+import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController;
+import org.jetlinks.community.device.entity.DeviceCategoryEntity;
+import org.jetlinks.community.device.service.DeviceCategoryService;
+import org.springframework.web.bind.annotation.*;
 import reactor.core.publisher.Flux;
-
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
+import reactor.core.publisher.Mono;
 
 @RestController
 @RequestMapping("/device/category")
 @Slf4j
-@Tag(name = "设备分类目录")
-public class DeviceCategoryController {
+@Tag(name = "产品分类管理")
+@AllArgsConstructor
+@Resource(id="device-category",name = "产品分类")
+public class DeviceCategoryController implements ReactiveServiceCrudController<DeviceCategoryEntity,String> {
 
 
-    static List<DeviceCategory> statics;
+    private final DeviceCategoryService categoryService;
 
-
-    static void rebuild(String parentId, List<DeviceCategory> children) {
-        if (children == null) {
-            return;
-        }
-        for (DeviceCategory child : children) {
-            String id = child.getId();
-            child.setId(parentId + "|" + id + "|");
-            child.setParentId(parentId + "|");
-            rebuild(parentId + "|" + id, child.getChildren());
-        }
+    @GetMapping
+    @QueryNoPagingOperation(summary = "获取全部分类")
+    @Authorize(merge = false)
+    public Flux<DeviceCategoryEntity> getAllCategory(@Parameter(hidden = true) QueryParamEntity query) {
+        return this
+            .categoryService
+            .createQuery()
+            .setParam(query)
+            .fetch();
     }
 
-    static {
-        try {
-            ClassPathResource resource = new ClassPathResource("device-category.json");
-            String json = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
-
-            List<DeviceCategory> all = JSON.parseArray(json, DeviceCategory.class);
-
-            List<DeviceCategory> root = TreeSupportEntity.list2tree(all, DeviceCategory::setChildren);
-
-            for (DeviceCategory category : root) {
-                String id = category.getId();
-
-                category.setId("|" + id + "|");
-                category.setParentId("|" + category.getParentId() + "|");
-                rebuild("|" + id, category.getChildren());
-            }
-
-            statics = all;
-
-        } catch (Exception e) {
-            statics = new ArrayList<>();
-            DeviceCategoryController.log.error(e.getMessage(), e);
-        }
+    @GetMapping("/_tree")
+    @QueryNoPagingOperation(summary = "获取全部分类(树结构)")
+    @Authorize(merge = false)
+    public Flux<DeviceCategoryEntity> getAllCategoryTree(@Parameter(hidden = true) QueryParamEntity query) {
+        return this
+            .categoryService
+            .createQuery()
+            .setParam(query)
+            .fetch()
+            .collectList()
+            .flatMapMany(all-> Flux.fromIterable(TreeSupportEntity.list2tree(all, DeviceCategoryEntity::setChildren)));
     }
 
-    @GetMapping
-    @Operation(summary = "获取全部分类目录")
-    public Flux<DeviceCategory> getAllCategory() {
-        return Flux.fromIterable(statics);
-    }
 
-    @GetMapping("/_query/no-paging")
-    @Operation(summary = "获取全部分类目录")
-    public Flux<DeviceCategory> getAllCategory2() {
-        return Flux.fromIterable(statics);
+    @PostMapping("/_tree")
+    @QueryNoPagingOperation(summary = "获取全部分类(树结构)")
+    @Authorize(merge = false)
+    public Flux<DeviceCategoryEntity> getAllCategoryTreeByQueryParam(@RequestBody Mono<QueryParamEntity> query) {
+        return this
+            .categoryService
+            .query(query)
+            .collectList()
+            .flatMapMany(all-> Flux.fromIterable(TreeSupportEntity.list2tree(all, DeviceCategoryEntity::setChildren)));
     }
 
-
-    @GetMapping("/_tree")
-    @Operation(summary = "获取全部分类目录(树结构)")
-    public Flux<DeviceCategory> getAllCategoryTree() {
-        return Flux.fromIterable(TreeSupportEntity.list2tree(statics, DeviceCategory::setChildren));
+    @Override
+    public ReactiveCrudService<DeviceCategoryEntity, String> getService() {
+        return categoryService;
     }
 }

+ 59 - 0
jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/web/ProtocolSupportController.java

@@ -7,11 +7,13 @@ import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.Getter;
 import org.hswebframework.utils.StringUtils;
+import org.hswebframework.web.api.crud.entity.QueryParamEntity;
 import org.hswebframework.web.authorization.annotation.Authorize;
 import org.hswebframework.web.authorization.annotation.QueryAction;
 import org.hswebframework.web.authorization.annotation.Resource;
 import org.hswebframework.web.authorization.annotation.SaveAction;
 import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController;
+import org.hswebframework.web.exception.BusinessException;
 import org.jetlinks.community.device.entity.ProtocolSupportEntity;
 import org.jetlinks.community.device.service.LocalProtocolSupportService;
 import org.jetlinks.community.device.web.protocol.ProtocolDetail;
@@ -19,6 +21,7 @@ import org.jetlinks.community.device.web.protocol.ProtocolInfo;
 import org.jetlinks.community.device.web.protocol.TransportInfo;
 import org.jetlinks.community.device.web.request.ProtocolDecodeRequest;
 import org.jetlinks.community.device.web.request.ProtocolEncodeRequest;
+import org.jetlinks.community.protocol.TransportDetail;
 import org.jetlinks.core.ProtocolSupport;
 import org.jetlinks.core.ProtocolSupports;
 import org.jetlinks.core.message.codec.Transport;
@@ -32,7 +35,10 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import reactor.util.function.Tuple2;
+import reactor.util.function.Tuples;
 
+import java.util.Comparator;
 import java.util.List;
 
 @RestController
@@ -174,4 +180,57 @@ public class ProtocolSupportController
     public Flux<ValueUnit> allUnits() {
         return Flux.fromIterable(ValueUnits.getAllUnit());
     }
+
+
+    @GetMapping("/supports/{transport}")
+    @Authorize(merge = false)
+    @Operation(summary = "获取支持指定传输协议的消息协议")
+    public Flux<ProtocolInfo> getSupportTransportProtocols(@PathVariable String transport,
+                                                           @Parameter(hidden = true) QueryParamEntity query) {
+        return protocolSupports
+            .getProtocols()
+            .collectMap(ProtocolSupport::getId)
+            .flatMapMany(protocols -> service.createQuery()
+                                             .setParam(query)
+                                             .fetch()
+                                             .index()
+                                             .flatMap(tp2 -> Mono
+                                                 .justOrEmpty(protocols.get(tp2.getT2().getId()))
+                                                 .filterWhen(support -> support
+                                                     .getSupportedTransport()
+                                                     .filter(t -> t.isSame(transport))
+                                                     .hasElements())
+                                                 .map(ProtocolInfo::of)
+                                                 .map(protocolInfo -> Tuples.of(tp2.getT1(), protocolInfo))))
+            .sort(Comparator.comparingLong(Tuple2::getT1))
+            .map(Tuple2::getT2);
+    }
+
+    @GetMapping("/{id}/transport/{transport}")
+    @Authorize(merge = false)
+    @Operation(summary = "获取消息协议对应的传输协议信息")
+    public Mono<TransportDetail> getTransportDetail(@PathVariable @Parameter(description = "协议ID") String id,
+                                                    @PathVariable @Parameter(description = "传输协议") String transport) {
+        return protocolSupports
+            .getProtocol(id)
+            .onErrorMap(e -> new BusinessException("error.unable_to_load_protocol_by_access_id", 404, id))
+            .flatMapMany(protocol -> protocol
+                .getSupportedTransport()
+                .filter(trans -> trans.isSame(transport))
+                .distinct()
+                .flatMap(_transport -> TransportDetail.of(protocol, _transport)))
+            .singleOrEmpty();
+    }
+
+
+    @PostMapping("/{id}/detail")
+    @QueryAction
+    @Operation(summary = "获取协议详情")
+    public Mono<ProtocolDetail> protocolDetail(@PathVariable String id) {
+        return protocolSupports
+            .getProtocol(id)
+            .onErrorMap(e -> new BusinessException("error.unable_to_load_protocol_by_access_id", 404, id))
+            .flatMap(ProtocolDetail::of);
+    }
+
 }