# 数据范围定义对接

原始文档链接：[数据范围定义对接](https://weapp.eteams.cn/build/techdoc/wdoc/index.html#/public/repository/b90d212e-0cab-40a6-9845-d9476f717caf/doc/958006160789774336)
目录分组：对接方案

[http://10.10.10.40:4999/web/#/672271398/287941162](http://10.10.10.40:4999/web/#/672271398/287941162)

# 一、功能简述

![](http://10.10.10.40:4999/server/index.php?s=/api/attachment/visitFile&sign=dbca74cc7f6add3c0a07c17dc1d160c3)
 当前表单对接了数据范围定义组件，目前数据范围定义也主要是在表单类场景中，以下都以表单举例，如果有其它需求，可以单独找公共组件评审。
 数据范围定义的功能定位是在不同的场景选择某一个浏览按钮时，可以针对性的配置相关过滤条件在这个场景下的浏览按钮中生效
 组件基本页面如下：
 ![](http://10.10.10.40:4999/server/index.php?s=/api/attachment/visitFile&sign=7ec12dc0c88bcc44fe652abc9cef6e98)
 一般分为通用和表单字段两种：
 通用：即条件的直接过滤选择
 表单字段：条件基于表单已选的数据做过滤，根据表单已填数据的上下文获取变量数据过滤

# 二、业务模块目前建议的对接方案

对接前请先提交需求
 对接分为三步
 1、在表单的浏览数据定义tab页面开启设置按钮，可以找表单的陈子龙协助开启
 2、前端个性化开发数据范围定义弹框组件内容，可以找蒋贝贝协助
 3、后端统一根据配置的值，统一进行过滤

## 2.1、前端页面开发

请看此文档，不清楚的找蒋贝贝协调一下，前端可以随意开发，**主要是后端接口还是要统一走功能接口，可以参照部门或者会议等的页面以及接口**
 [https://weapp.eteams.cn/sp/techdoc/view/711118517086429185/788388273019543552](https://weapp.eteams.cn/sp/techdoc/view/711118517086429185/788388273019543552)

![](http://10.10.10.40:4999/server/index.php?s=/api/attachment/visitFile&sign=91abd62faef4b396bf299df88023078b)  一条记录基本为下面的格式

```json
{
  "businessId": "809672120889688065", // 业务id，表单传递
  "businessModule": "biaoge", // 业务模块，表单传递
  "controlId": "809672155041693699",// 控件id，表单传递
  "browserType": "hrm_department", // 浏览按钮类型，表单传递
  "formId": "809672120889688065", // 表单id
  "module": "biaoge",// 模块
  "scopeInfoRender": "{\"scopeInfoRender\":{\"department\":{\"datas\":[{\"id\":\"8513938757467575536\",\"name\":\"人事部\",\"content\":\"人事部\",\"title\":\"人事部|组件发版测试Test很长很很长很长很长很长很长长很长很长测试测试长很长长很长很长测试测试1111长很长长很长很长测试测试1111111111/一级部门/一级部门的下级部门\",\"isLeaf\":true,\"icon\":\"Icon-department03\",\"leaf\":true,\"stopDepartment\":false}],\"ismain\":true},\"departmentAttr\":{\"datas\":\"ownDepartment\",\"ismain\":false}},\"type\":\"normal\"}", // 数据范围的info渲染，主要是协助前端渲染字段
  "scopeType": "targetDepartment", // 模块自己定义，如这一条表示指定部门
  "scopeInfo": "8513938757467575536:ownDepartment"// 模块自己定义,格式也可以自己定义，如这一条过滤表示条件为自己的部门为8513938757467575536的数据
}
```

主要的其实就是**scopeInfo、scopeType、scopeInfoRender**这几个字段，这几个字段可以任意定义，只要回显的时候做好处理就好了（操作日志目前暂时不考虑），后端也是由模块自己来处理这些数据，所以只要模块能够理解数据代表的含义就可以了

## 2.2、后端开发

前端将数据保存后，数据会统一存到eteams.browser_data_scope表中，后端则是要让这个设置生效，需要调整的主要是浏览按钮实现类的getBrowserData、browserAutoComplete、getTotal方法，分别对应前端接口的/data、/complete、/count接口，以下以data接口为例，阐明整个后端过滤流程

### 2.2.1、通用设置的处理

![](http://10.10.10.40:4999/server/index.php?s=/api/attachment/visitFile&sign=d840a5864ad9af939895f08c64d9c07e)  由于通用设置没有变量相关的数据需要转换，所以直接获取eteams.browser_data_scope的数据解析后过滤就可以了，组件在调用浏览按钮自身实现的方法前，会统一查询BrowserDataScope,并将其放到请求参数中，下方是一个/data简化的查询示例

```java
public Map<String,Object> getBrowserData(Map<String,Object> params) throws Exception{
	// 获取数据范围定义配置数据
    List<BrowserDataScope> browserDataScopes = (List<BrowserDataScope>) Optional.of(params.get("scopeConfig")).orElse(Lists.newArrayList());
    QueryDto queryDto = new QueryDto();
    if(CollectionUtil.isNotEmpty(browserDataScopes)){
		// 解析数据并拼装查询条件
        for (BrowserDataScope browserDataScope : browserDataScopes) {
            if(browserDataScope.getScopeType().equals("targetUser")){
                String userId = browserDataScope.getScopeInfo();
                queryDto.setUserId(userId);
            }
        }
    }
	// 查询数据
    return queryByDto(queryDto);
}
```

### 2.2.2、表单字段数据的处理

由于表单数据是动态的，所以需要从前端获取并将配置中的数据替换为动态值再做处理，以部门为例，下图中为【部门选择】字段配置了变量字段为【部门选择2】  ![](http://10.10.10.40:4999/server/index.php?s=/api/attachment/visitFile&sign=8d8525d7760c268c4924d320a7aa3282)  保存的数据如下：scopeInfo中的881212690996862976实际是部门选择2的控件id

```json
{
  "id": "881212793907765248",
  "createTime": 1688373343000,
  "updateTime": 1688373343000,
  "creator": "5112451412429675880",
  "tenantKey": "tf288yacf3",
  "businessId": "809672120889688065",
  "controlId": "809672155041693699",
  "scopeType": "department",
  "scopeInfo": "881212690996862976:ownDepartment",
  "scopeInfoRender": "{\"scopeInfoRender\":{\"department\":{\"datas\":[{\"name\":\"部门选择2\",\"location\":\"主表\",\"id\":\"881212690996862976\",\"content\":\"部门选择2\"}],\"ismain\":true},\"departmentAttr\":{\"datas\":\"ownDepartment\",\"items\":{\"itemType\":\"SELECT\",\"data\":[{\"id\":\"ownDepartment\",\"content\":\"本部门\"},{\"id\":\"ownOrSubDepartment\",\"content\":\"本部门及下级部门\"},{\"id\":\"ownOrSupDepartment\",\"content\":\"本部门及上级部门\"}]}}},\"type\":\"form\"}",
  "browserType": "hrm_department",
  "businessModule": "biaoge"
}
```

### 运行时

可以看到在【部门选择二】选择了数据后，部门选择会会根据选择的数据过滤  ![](http://10.10.10.40:4999/server/index.php?s=/api/attachment/visitFile&sign=fe87ac16c78a0f081dc28f69b7ba786e)  实现的机制是表单前端会给浏览按钮添加动态的参数，后端将动态的参数替换为实际值再过滤  ![](http://10.10.10.40:4999/server/index.php?s=/api/attachment/visitFile&sign=889a540048c71d3e94b4eb7194d7abf4)

主要看variableMap这个参数

```json
{
  "881212690996862976": [
    {
      "id": "3575208094180500698",
      "name": "分部部门01"
    }
  ]
}
```

881212690996862976就是保存配置881212690996862976:ownDepartment的控件id，后端将其替换为3575208094180500698就可以正常过滤了，下面是一个示意代码

```java
public Map<String,Object> getBrowserData(Map<String,Object> params) throws Exception{
    List<BrowserDataScope> browserDataScopes = (List<BrowserDataScope>) Optional.of(params.get("scopeConfig")).orElse(Lists.newArrayList());
    Map<String,Object> variableMap = (Map<String, Object>) Optional.of(params.get("variableMap")).orElse(Lists.newArrayList());
    QueryDto queryDto = new QueryDto();
    if(CollectionUtil.isNotEmpty(browserDataScopes)){
        for (BrowserDataScope browserDataScope : browserDataScopes) {
            if(browserDataScope.getScopeType().equals("targetUserWithVariableKey")){
                String controlId = browserDataScope.getScopeInfo();
                String actualUserId = parseVariable(controlId,variableMap);
                queryDto.setUserId(actualUserId);
            }
        }
    }
    return queryByDto(queryDto);
}
```

### 表单对接

由于表单需要提前知道那些控件是配置了变量字段的，所以还需要模块对接一个RPC接口，示例如下，找师源对接

```java
public class DemoDataScopeHandler extends AbstractDataScopeHandler {


    @ApiOperation("表示该实现类处理的浏览按钮类型,对应browser_data_scope表中的browser_type字段")
    @Override
    public List<String> determineBrowserType() {
        return Arrays.asList("frontProjects","demoBrowser");
    }

    @ApiOperation("通过browserDataScope来构建controlId变量映射，该变量映射会由表单前端获取，在实际使用浏览按钮时，根据映射会往浏览按钮请求参数中添加VariableMap参数")
    @Override
    public Map<String, List<String>> acquireVariableKeys(List<BrowserDataScope> browserDataScopeList) {
        if(CollectionUtil.isNotEmpty(browserDataScopeList)){
            List<BrowserDataScope> demoBrowserDataScopeList = browserDataScopeList.stream().filter(a -> "demoBrowser".equals(a.getBrowserType())).collect(Collectors.toList());
            if(CollectionUtil.isNotEmpty(demoBrowserDataScopeList)){
             return demoBrowserAssemble(demoBrowserDataScopeList);
            }
        }
        return Maps.newHashMap();
    }

    @ApiOperation("实际变量的组装，需要模块自己实现，这些代码只是一个示例，具体实现可以自由发挥")
    private Map<String, List<String>> demoBrowserAssemble(List<BrowserDataScope> demoBrowserDataScopeList) {
        return null;
    }
}
```

# 三、条件设计器形式的对接方案

## 3.1、 前提  

数据范围定义想要展示为条件设计器的形式，需要找赵俊飞开通一下  

## 3.2、 原理  

### 3.2.1、浏览按钮标准条件设计器布局接口  

/api/{module}/common/browser/getScopeConditionLayout/{type}  ![](http://10.10.10.40:4999/server/index.php?s=/api/attachment/visitFile&sign=551de7fa357b6737356b46e4108c2c88) 

 条件设计器形式默认打开后会直接调用上述接口，根据浏览按钮的module、type传递不同的参数，下面开始分开介绍两种情况  

1、浏览按钮的数据不支持数据源条件设计器查询  这种是大部分业务浏览按钮的情况，目前公共做了统一实现，直接将高级搜索布局转换为条件设计器的布局形式，如果返回结果不符合预期可以通过后置接口微调，在下文介绍  

2、浏览按钮的数据支持数据源条件设计器查询  如果浏览按钮底层用的数据源，本身支持条件设计器的查询，那最好是直接用后置接口强行将接口的返回数据调整为数据源的fields接口返回格式  

### 3.2.2、后置接口  

从上图中的getHook方法看到，布局接口支持二次修改，下面给出一个修改示例，示例为自定义浏览按钮相关的布局接口实现方式，由于自定义浏览按钮直接支持数据源条件设计器查询，所以直接重写了相关数据

```
@Component
@Slf4j
public class CustomBrowserScopeLayoutHook extends AbstractBrowserScopeConditionLayoutHook {

    @Override
    public BrowserScopeConditionSet rebuildScopeConditionLayout(BrowserScopeConditionSet browserScopeConditionSet) {
        try {
            CustomBrowserInfo currentBrowserInfo = BaseCustomBrowserContext.getCurrentBrowserInfo();
            List<Map<String, Object>> fieldsMap = DataSourceUtil.getFields(currentBrowserInfo.getSourceId(), currentBrowserInfo.getSourceType(), currentBrowserInfo.getSourceGroup(), false);
            Map<String, Object> map = fieldsMap.get(0);
            return JSON.parseObject(JSON.toJSONString(map), BrowserScopeConditionSet.class);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return browserScopeConditionSet;
    }

    @Override
    protected String getBrowserSign() {
        return "bcw_customBrowser";
    }
}
```

### 3.2.3、配置界面

![](http://10.10.10.40:4999/server/index.php?s=/api/attachment/visitFile&sign=c7f3d7e36b8133de96526ce07e3eb34c)
 使用数据范围定义的条件设计器和普通的条件设计器没有太多区别，只是重写了部分界面逻辑使得设计器可以选到表单的变量字段

#### 3.2.4、运行时

在实际运行的时候，业务模块需要接受数据范围定义的特殊参数，变量相关不用担心，组件会统一替换处理
 按需要，有两种参数可以接收，相关参数会直接补充到浏览按钮实现方法的param里
 ![](http://10.10.10.40:4999/server/index.php?s=/api/attachment/visitFile&sign=d36b994c1a192649d0186d3c472d4721)
 1、scopeSql
 条件设计器转换后的条件sql，接受后请直接拼到相关的查询语句中使用
 2、customTreeDto
 为数据源直接转换后的实体，数据类型为ConditionTreeDto，目前只为自定义浏览按钮提供，如需特殊提供，需要提交师源申请
