6.2 集成平台(改)

企业在数字化转型过程中内外部集成是一个必然需求、也是趋势

集成的诉求主要来自两个方面:1.企业的数字化改造是由外而内逐步进行的(内部异构集成)、2.企业数字化方向是朝越来越开放的方向发展(外部平台、工具集成)。总的来说企业在数字化转型过程中内外部集成是一个必然需求、也是趋势。所以我们不能简单地去理解做个API对接就结束了,而是要统一规划构建成企业的集成门户对API定义,安全、控制、记录等做全方位管理。oinone在下个版本规则中也纳入了基于集成平台之上做产品化配置的需求

概述

pamirs-eip为平台提供企业集成门户的相关功能,如请求外部接口使用的【集成接口】和对外开放被其他系统请求调用的【开放接口】功能。在请求外部接口时,还支持了多个接口调用(路由定义)、分页控制(paging)、增量控制(incremental)等功能。

准备工作

Step1 POM与模块依赖

pamirs-demo-api 和 pamirs-second-api 的pom文件中引入pamirs-eip2-api包依赖

<dependency>
    <groupId>pro.shushi.pamirs.core</groupId>
    <artifactId>pamirs-eip2-api</artifactId>
</dependency>

DemoModule和SecondModule 增加对EipModule的依赖

@Module(dependencies = {EipModule.MODULE_MODULE})

pamirs-demo-boot和pamirs-second-boot工程的pom文件中引入pamirs-eip2-core包依赖

<dependency>
    <groupId>pro.shushi.pamirs.core</groupId>
    <artifactId>pamirs-eip2-core</artifactId>
</dependency>

Step2 yaml配置文件参考

pamirs-demo-boot和pamirs-second-boot工程的application-dev.yml文件中增加配置pamirs.boot.modules增加eip,即在启动模块中增加eip模块

pamirs:
    boot:
    modules:
        - eip

pamirs-demo-boot和pamirs-second-boot工程的application-dev.yml文件中增加eip模块的数据源与路由配置

pamirs:
    framework:
    data:
        ds-map:
        eip: eip
    datasource:
    eip:
      driverClassName: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
      url: jdbc:mysql://127.0.0.1:3306/eip_v3?useSSL=false&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true
      username: root
      password: oinone
      initialSize: 5
      maxActive: 200
      minIdle: 5
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      asyncInit: true

pamirs-demo-boot工程的application-dev.yml文件中修改eip的配置

pamirs:
    eip:
    open-api:
      enabled: false

pamirs-second-boot工程的application-dev.yml文件中修改eip的配置

pamirs:
  eip:
    enabled: true
    open-api:
      enabled: true
      route:
        host: 127.0.0.1
        port: 8094
        aes-key: Nj5Thnxz4rV8Yy1FLGA2hUym3RepB8MKgafEaYC4GKo=

注:

hosts配置在远程调用时不能使用127.0.0.1,可配置为0.0.0.0进行自动识别。若自动识别仍无法访问,请准确配置其他已知的可访问IP地址。

aes-key:用下面代码生成

附录:AES Key生成

pro.shushi.pamirs.core.common.EncryptHelper加解密帮助类,默认支持AES、RSA类型的数据加解密方法,也可自定义其他类型的加解密方法。

System.out.println(EncryptHelper.getKey(EncryptHelper.getAESKey()));

Step3 在pamirs-second-api新建一个SessionTenantApi实现类

只要在我们公共的jar包中构建类似DemoSessionTenant类就可以了,之所以要构建SessionTenantApi实现类是因为EIP是以租户信息做路由的。所以这里我们写死返回一个“pamirs”租户就好了。

记得要重新mvn install second工程,再刷新demo工程

package pro.shushi.pamirs.second.api.tenant;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.framework.session.tenant.api.SessionTenantApi;
import pro.shushi.pamirs.meta.api.core.session.SessionClearApi;
import pro.shushi.pamirs.meta.common.spi.SPI;

@Order(99)
@Component
@SPI.Service
public class DemoSessionTenant implements SessionTenantApi, SessionClearApi {
    public String getTenant() {
        return "pamirs";
    }

    public void setTenant(String tenant) {
    }

    public void clear() {
    }
}

开放接口(举例)

Step1 用于演示的模型定义

package pro.shushi.pamirs.second.api.model;

import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.base.IdModel;

@Model.model(TestOpenApiModel.MODEL_MODEL)
@Model(displayName = "演示开放接口模型")
public class TestOpenApiModel extends IdModel {

    public static final String MODEL_MODEL = "demo.second.TestOpenApiModel";

    @Field.String
    @Field(displayName = "名称")
    private String name;

    @Field.Integer
    @Field(displayName = "年龄")
    private Integer age;
}
package pro.shushi.pamirs.second.api.tmodel;

import pro.shushi.pamirs.meta.annotation.fun.Data;

import java.io.Serializable;

@Data
public class TestOpenApiResponse implements Serializable {

    private Long id;
    private String name;
    private Integer age;

}

Step2 用于演示的接口定义

  1. TestOpenApiModelService接口定义了内部访问函数queryById

  2. TestOpenApiModelServiceImpl 实现TestOpenApiModelService接口,并定义了两个开放接口

a. queryById4Open函数用@Open进行注解,请求路径是:openapi/pamirs/方法名?tenant=pamirs,返回对象结构为函数返回值类型的JSON格式

b. queryById4OpenError函数用@Open(config = TestEipConfig.class,path = "error")进行注解,请求路径为:openapi/pamirs/error?tenant=pamirs,返回对象结构为函数返回值类型的JSON格式

c. config = TestEipConfig.class 设置通用配置类,可以在TestEipConfig增加@Open.Advanced。优先级低于方法上的注解。TestEipConfig见集成接口举例的集成配置模型定义。配置模型在开放接口定义中非必须的,在集成接口定义中时是必须。

d更多注解配置见Eip注解说明

package pro.shushi.pamirs.second.api.service;

import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.second.api.model.TestOpenApiModel;

@Fun(TestOpenApiModelService.FUN_NAMESPACE)
public interface TestOpenApiModelService {

    String FUN_NAMESPACE ="demo.second.TestOpenApiModelService";
    @Function
    TestOpenApiModel queryById(Long id);

}
package pro.shushi.pamirs.second.core.service;

import org.apache.camel.ExtendedExchange;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.core.common.SuperMap;
import pro.shushi.pamirs.eip.api.IEipContext;
import pro.shushi.pamirs.eip.api.annotation.Open;
import pro.shushi.pamirs.eip.api.enmu.EipExpEnumerate;
import pro.shushi.pamirs.eip.api.entity.openapi.OpenEipResult;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.common.exception.PamirsException;
import pro.shushi.pamirs.second.api.model.TestOpenApiModel;
import pro.shushi.pamirs.second.api.service.TestOpenApiModelService;
import pro.shushi.pamirs.second.api.tmodel.TestOpenApiResponse;

import java.util.Optional;

@Fun(TestOpenApiModelService.FUN_NAMESPACE)
@Component
public class TestOpenApiModelServiceImpl implements TestOpenApiModelService {

    @Override
    @Function
    public TestOpenApiModel queryById(Long id) {
        return new TestOpenApiModel().queryById(id);
    }

    @Function
    @Open
    public OpenEipResult<TestOpenApiResponse> queryById4Open(IEipContext<SuperMap> context , ExtendedExchange exchange) {
        String id = Optional.ofNullable(String.valueOf(context.getInterfaceContext().getIteration("id"))).orElse("");
        TestOpenApiModel temp  = queryById(Long.valueOf(id));
        TestOpenApiResponse response = new TestOpenApiResponse();
        if(temp != null ) {
            response.setAge(temp.getAge());
            response.setId(temp.getId());
            response.setName(temp.getName());
        }else{
            response.setAge(1);
            response.setId(1L);
            response.setName("oinone eip test");
        }
        OpenEipResult<TestOpenApiResponse> result = new OpenEipResult<TestOpenApiResponse>(response);
        return result;
    }

    @Function
    // @Open(config = TestEipConfig.class,path = "error")
    @Open(path = "error")
    @Open.Advanced(
            httpMethod = "post"
    )
    public OpenEipResult<TestOpenApiResponse> queryById4OpenError(IEipContext<SuperMap> context , ExtendedExchange exchange) {
        throw PamirsException.construct(EipExpEnumerate.SYSTEM_ERROR).appendMsg("测试异常").errThrow();
    }

}

Step3 用于演示的请求协议

请求方法:POST

{
  "id":111
}

请求参数格式:

{
  "success":true,
  "errorCode":0,
  "result":{
    "age":1,
    "id":1,
    "name":"oinone eip test"
  }
}

成功响应格式:

{
  "success":true,
  "errorCode":0,
  "result":{
    "age":1,
    "id":1,
    "name":"oinone eip test"
  }
}

异常响应格式:

{
  "success":false,
    "errorCode":"20140000",
  "errorMsg":"系统异常, 测试异常"
}

Step4 初始化EIP

构建SecondModuleBizInit 实现InstallDataInit, UpgradeDataInit, ReloadDataInit接口,调用Eip注解模式的初始化方法:EipResolver.resolver(SecondModule.MODULE_MODULE,null)

package pro.shushi.pamirs.second.core.init;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.boot.common.api.command.AppLifecycleCommand;
import pro.shushi.pamirs.boot.common.api.init.InstallDataInit;
import pro.shushi.pamirs.boot.common.api.init.ReloadDataInit;
import pro.shushi.pamirs.boot.common.api.init.UpgradeDataInit;
import pro.shushi.pamirs.eip.api.annotation.EipResolver;
import pro.shushi.pamirs.second.api.SecondModule;

import java.util.Collections;
import java.util.List;

@Component
public class SecondModuleBizInit implements InstallDataInit, UpgradeDataInit, ReloadDataInit {

    @Override
    public boolean init(AppLifecycleCommand command, String version) {
        initEip();
        return Boolean.TRUE;
    }

    @Override
    public boolean reload(AppLifecycleCommand command, String version) {
        initEip();
        return Boolean.TRUE;
    }

    @Override
    public boolean upgrade(AppLifecycleCommand command, String version, String existVersion) {
        initEip();
        return Boolean.TRUE;
    }

    @Override
    public List<String> modules() {
        return Collections.singletonList(SecondModule.MODULE_MODULE);
    }

    @Override
    public int priority() {
        return 0;
    }

    private void initEip() {
        EipResolver.resolver(SecondModule.MODULE_MODULE,null);
    }
}

Step5 重启看效果

Second模块重新 mvn install

重启pamris-second-boot工程

请求地址一:http://localhost:8094/openapi/pamirs/queryById4Open?tenant=pamirs

curl --location --request POST 'http://localhost:8094/openapi/pamirs/queryById4Open?tenant=pamirs' \
--header 'Content-Type: application/json' \
--data-raw '{
    "id": "111",
}'

返回结果:

{"errorCode":0,"result":{"age":1,"id":1,"name":"oinone eip test"},"success":true}

请求地址二:http://localhost:8094/openapi/pamirs/error?tenant=pamirs

curl --location --request POST 'http://localhost:8094/openapi/pamirs/error?tenant=pamirs' \
--header 'Content-Type: application/json' \
--data-raw '{
    "id": "111",
}'

返回结果:

{"success":false,"errorCode":"20140000","errorMsg":"系统异常, 测试异常"}

集成接口(举例)

下面的例子我们将用集成接口调用开放接口,集成接口配置在Demo模块

Step1 用于演示的集成配置模型定义

  1. 配置模型必须继承IEipAnnotationSingletonConfig接口

  2. 自定义construct函数,达到修改配置时可以自动刷新对应的集成接口和开放接口

  3. host为服务端:域名+端口,如www.oinone.top:80

  4. shcema为请求协议:http或https

  5. 类上可以增加@Open.Advanced 和 @Integrate.Advanced 配置达到通用配置的作用,优先级低于方法上的注解

package pro.shushi.pamirs.demo.api.model;

import pro.shushi.pamirs.eip.api.annotation.IEipAnnotationSingletonConfig;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.base.IdModel;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;

@Model.model(TestEipConfig.MODEL_MODEL)
@Model(displayName = "演示集成接口配置模型")
public class TestEipConfig extends IdModel implements IEipAnnotationSingletonConfig<TestEipConfig> {

    public static final String MODEL_MODEL = "demo.TestEipConfig";

    @Field(displayName = "服务端域名")
    private String host;
    @Field(displayName = "请求协议Http或Https")
    private String schema;

    @Function(openLevel = FunctionOpenEnum.API,summary = "演示集成接口配置模型")
    @Function.Advanced(type = FunctionTypeEnum.QUERY)
    public TestEipConfig construct(TestEipConfig config){
        TestEipConfig config1 = config.singletonModel();
        if(config1!=null){
            return config1;
        }
        return config.construct();
    }
}

Step2 用于演示的接口定义

  1. TestIntegrateService接口定义了内部访问函数callQueryById、callQueryByData、callQueryByIdError

  2. TestIntegrateServiceImpl 实现TestIntegrateService接口,并把方法定义为集成接口

a. config = TestEipConfig.class 设置通用配置类,可以在TestEipConfig增加@Integrate.Advanced。优先级低于方法上的注解。它在集成接口定义中时是必须。

b. @Integrate.Advanced(path=""),集成接口最终请求地址:TestEipConfig.schema+"://"+TestEipConfig.host+path

c. 方法体返回空就可以,实际执行时候该方法被拦截并不会执行

d. @Integrate.ConvertParam

ⅰ. 请求从方法入参数会以参数名为key放在上下文中,@Integrate.ConvertParam(inParam="data.id",outParam="id")代表把参数名为data的id属性值,放到上下文key为id上。

ⅱ. url和head的参数转化参见paramConverter的特殊转换

e. finalResultKey是指最终的请求参数key,会从上下文中找对应的key值作为请求参数

f. 更多注解配置见Eip注解说明

package pro.shushi.pamirs.demo.api.service;

import pro.shushi.pamirs.core.common.SuperMap;
import pro.shushi.pamirs.eip.api.entity.EipResult;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.second.api.model.TestOpenApiModel;

@Fun(TestIntegrateService.FUN_NAMESPACE)
public interface TestIntegrateService {
    String FUN_NAMESPACE ="demo.TestIntegrateService";
    @Function
    EipResult<SuperMap> callQueryById(String id);
    @Function
    EipResult<SuperMap> callQueryByData(TestOpenApiModel data);
    @Function
    EipResult<SuperMap> callQueryByIdError(TestOpenApiModel data);

}
package pro.shushi.pamirs.demo.core.service;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.core.common.SuperMap;
import pro.shushi.pamirs.demo.api.model.TestEipConfig;
import pro.shushi.pamirs.demo.api.service.TestIntegrateService;
import pro.shushi.pamirs.eip.api.annotation.Integrate;
import pro.shushi.pamirs.eip.api.entity.EipResult;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.second.api.model.TestOpenApiModel;

@Fun(TestIntegrateService.FUN_NAMESPACE)
@Component
public class TestIntegrateServiceImpl implements TestIntegrateService {

    @Override
    @Function
    @Integrate(config = TestEipConfig.class)
    @Integrate.Advanced(path = "/openapi/pamirs/queryById4Open?tenant=pamirs")
    public EipResult<SuperMap> callQueryById(String id) {
        return null;
    }

    @Override
    @Function
    @Integrate(config = TestEipConfig.class)
    @Integrate.Advanced(path = "/openapi/pamirs/queryById4Open?tenant=pamirs")
    @Integrate.RequestProcessor(
            convertParams={
                    @Integrate.ConvertParam(inParam="data.id",outParam="id")
            }
    )
    public EipResult<SuperMap> callQueryByData(TestOpenApiModel data) {
        return null;
    }

    @Function
    @Integrate(config = TestEipConfig.class)
    @Integrate.Advanced(path = "/openapi/pamirs/error?tenant=pamirs")
    @Integrate.RequestProcessor(
            finalResultKey = "out",
            convertParams={
                    @Integrate.ConvertParam(inParam="data.id",outParam="out.id")
            }
    )
    public EipResult<SuperMap> callQueryByIdError(TestOpenApiModel data) {
        return null;
    }

}

Step3 新建TestEipAction

package pro.shushi.pamirs.demo.core.action;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.core.common.SuperMap;
import pro.shushi.pamirs.demo.api.model.TestEipConfig;
import pro.shushi.pamirs.demo.api.service.TestIntegrateService;
import pro.shushi.pamirs.eip.api.entity.EipResult;
import pro.shushi.pamirs.meta.annotation.Action;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j;
import pro.shushi.pamirs.meta.api.session.PamirsSession;
import pro.shushi.pamirs.meta.enmu.ActionContextTypeEnum;
import pro.shushi.pamirs.meta.util.JsonUtils;
import pro.shushi.pamirs.second.api.model.TestOpenApiModel;

@Model.model(TestEipConfig.MODEL_MODEL)
@Component
@Slf4j
public class TestEipAction {

    @Autowired
    private TestIntegrateService testIntegrateService;

    @Action(displayName = "调用集成接口callQueryById",contextType = ActionContextTypeEnum.CONTEXT_FREE)
    public TestEipConfig callQueryById(TestEipConfig data){
        EipResult<SuperMap> eipResult = testIntegrateService.callQueryById("111");
        PamirsSession.getMessageHub().error(JsonUtils.toJSONString(eipResult));
        return data;
    }

    @Action(displayName = "调用集成接口callQueryByIdError",contextType = ActionContextTypeEnum.CONTEXT_FREE)
    public TestEipConfig callQueryByIdError(TestEipConfig data){
        EipResult<SuperMap> eipResult = testIntegrateService.callQueryByIdError((TestOpenApiModel)new TestOpenApiModel().setId(111L));
        PamirsSession.getMessageHub().error(JsonUtils.toJSONString(eipResult));
        return data;
    }

    @Action(displayName = "调用集成接口callQueryByData",contextType = ActionContextTypeEnum.CONTEXT_FREE)
    public TestEipConfig callQueryByIdSuccess(TestEipConfig data){
        EipResult<SuperMap> eipResult = testIntegrateService.callQueryByData((TestOpenApiModel)new TestOpenApiModel().setId(111L));
        PamirsSession.getMessageHub().error(JsonUtils.toJSONString(eipResult));
        return data;
    }
}

Step4 构建测试入口

@UxMenu("集成测试")@UxRoute(TestEipConfig.MODEL_MODEL) class TestEipConfigMenu{}

Step5 初始化Eip

在DemoModuleBizInit 类中增加私有方法initEip,并在init 、reload 、upgrade 中调用该私有化

private void initEip() {
    EipResolver.resolver(DemoModule.MODULE_MODULE,null);
}

Step6 重启看效果

  1. 点击【集成测试】菜单,点击【新增】按钮,配置集成接口信息

image.png

  1. 点击【集成测试】菜单,依次点击三个方法

image.png

  1. 模块切换进入【集成接口】,点击【日志】菜单。比对请求不同点

a. 设置finalResultKey的表达从接口上下文中取最终对应的value作为请求参数,不然会传全部接口上下文过去

b. callQueryByIdError,集成接口和对应的开放接口对结果认知差别

ⅰ. 开放接口返回异常信息

ⅱ. 集成接口没有对返回信息作判定,直接当成功。集成接口如何判定对方返还的是错误,请见自定义异常判定

自定义异常判定(举例)

Step1 新建异常判定函数

新建异常判定处理类TestExceptionPredictFunction,实现IEipExceptionPredict接口。从context.getInterfaceContextValue(DEFAULT_ERROR_CODE_KEY)中获取成功与否的值,并进行判断

package pro.shushi.pamirs.demo.core.service;

import pro.shushi.pamirs.core.common.StringHelper;
import pro.shushi.pamirs.core.common.SuperMap;
import pro.shushi.pamirs.eip.api.IEipContext;
import pro.shushi.pamirs.eip.api.IEipExceptionPredict;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;

import static pro.shushi.pamirs.eip.api.IEipContext.DEFAULT_ERROR_CODE_KEY;

@Fun(TestExceptionPredictFunction.FUN_NAMESPACE)
public class TestExceptionPredictFunction implements IEipExceptionPredict<SuperMap> {

    public static final String FUN_NAMESPACE ="demo.TestExceptionPredictFunction";
    public static final String FUN ="testFunction";

    @Override
    public boolean test(IEipContext<SuperMap> context) {
        return !Boolean.TRUE.toString().equals(StringHelper.valueOf(context.getExecutorContextValue(DEFAULT_ERROR_CODE_KEY)));
    }

    @Function
    @Function.fun(FUN)
    public Boolean testFunction(IEipContext<SuperMap> context) {
        return test(context);
    }
}

Step2 修改TestIntegrateServiceImpl类

修改TestIntegrateServiceImpl的callQueryByIdError函数的注解,增加@Integrate.ExceptionProcessor注解

  1. errorCode ="success", 从返回结果中拿键值为“success”的值,作为接口上下文对应键值为IEipContext.DEFAULT_ERROR_CODE_KEY的值

  2. exceptionPredictFun 指定函数名

  3. exceptionPredictNamespace 指定函数命名空间

    
    …… 依赖类引用

@Fun(TestIntegrateService.FUN_NAMESPACE)
@Component
public class TestIntegrateServiceImpl implements TestIntegrateService {

……其他代码

@Function
@Integrate(config = TestEipConfig.class)
@Integrate.Advanced(path = "/openapi/pamirs/error?tenant=pamirs")
@Integrate.RequestProcessor(
        finalResultKey = "out",
        convertParams={
                @Integrate.ConvertParam(inParam="data.id",outParam="out.id")
        }
)
@Integrate.ExceptionProcessor(
        errorCode ="success",
        exceptionPredictFun = TestExceptionPredictFunction.FUN,
        exceptionPredictNamespace = TestExceptionPredictFunction.FUN_NAMESPACE
)
public EipResult<SuperMap> callQueryByIdError(TestOpenApiModel data) {
    return null;
}

}

### Step3 重启看效果

1. 点击【集成测试】菜单,点击【调用集成接口callQueryByIdError 】

2. 模块切换进入【集成接口】,点击【日志】菜单。集成接口是否成功调用判定为false

![image.png](https://doc.oinone.top/wp-content/uploads/2024/05/1665041696328-76259be4-72ed-47c2-b3ea-4f7f3c9e6417.png)

## 自定义安全策略(AccessToken)

开发接口提供默认三种实现方式,例子采用DEFAULT_NO_ENCRYPT_AUTHENTICATION_PROCESSOR_FUN

 认证模式,最简单的安全模式:是有accessToken

1. DEFAULT_AUTHENTICATION_PROCESSOR_FUN:根据EipApplication配置的加密类型进行加解密处理的认证

2. DEFAULT_NO_ENCRYPT_AUTHENTICATION_PROCESSOR_FUN:不进行加解密处理的认证。

3. DEFAULT_MD5_SIGNATURE_AUTHENTICATION_PROCESSOR_FUN:使用MD5简单验签的认证。

### Step1 给开放接口加上认证处理配置
```java
……依赖类引用

@Fun(TestOpenApiModelService.FUN_NAMESPACE)
@Component
public class TestOpenApiModelServiceImpl implements TestOpenApiModelService {

    @Override
    @Function
    public TestOpenApiModel queryById(Long id) {
        return new TestOpenApiModel().queryById(id);
    }

    @Function
    @Open
    @Open.Advanced(
        authenticationProcessorFun= EipFunctionConstant.DEFAULT_NO_ENCRYPT_AUTHENTICATION_PROCESSOR_FUN,
        authenticationProcessorNamespace = EipFunctionConstant.FUNCTION_NAMESPACE
    )
    public OpenEipResult<TestOpenApiResponse> queryById4Open(IEipContext<SuperMap> context , ExtendedExchange exchange) {
        String id = Optional.ofNullable(String.valueOf(context.getInterfaceContext().getIteration("id"))).orElse("");
        TestOpenApiModel temp  = queryById(Long.valueOf(id));
        TestOpenApiResponse response = new TestOpenApiResponse();
        if(temp != null ) {
            response.setAge(temp.getAge());
            response.setId(temp.getId());
            response.setName(temp.getName());
        }else{
            response.setAge(1);
            response.setId(1L);
            response.setName("oinone eip test");
        }
        OpenEipResult<TestOpenApiResponse> result = new OpenEipResult<TestOpenApiResponse>(response);
        return result;
    }

    @Function
    @Open(path = "error")
    @Open.Advanced(
        httpMethod = "post",
        authenticationProcessorFun= EipFunctionConstant.DEFAULT_NO_ENCRYPT_AUTHENTICATION_PROCESSOR_FUN,
        authenticationProcessorNamespace = EipFunctionConstant.FUNCTION_NAMESPACE
    )
    public OpenEipResult<TestOpenApiResponse> queryById4OpenError(IEipContext<SuperMap> context , ExtendedExchange exchange) {
        throw PamirsException.construct(EipExpEnumerate.SYSTEM_ERROR).appendMsg("测试异常").errThrow();
    }

}
</code></pre>
<h3>Step2 修改TestEipAction,增加返回结果判断</h3>
<pre><code class="language-java">@Model.model(TestEipConfig.MODEL_MODEL)
@Component
@Slf4j
public class TestEipAction {

    @Autowired
    private TestIntegrateService testIntegrateService;

    @Action(displayName = "调用集成接口callQueryById",contextType = ActionContextTypeEnum.CONTEXT_FREE)
    public TestEipConfig callQueryById(TestEipConfig data){
        EipResult<SuperMap> eipResult = testIntegrateService.callQueryById("111");
        if(!eipResult.getSuccess()){
            throw PamirsException.construct(EipExpEnumerate.SYSTEM_ERROR).appendMsg("调用集成接口callQueryById 失败").errThrow();
        }
        PamirsSession.getMessageHub().error(JsonUtils.toJSONString(eipResult));-
        return data;
    }

    @Action(displayName = "调用集成接口callQueryByIdError",contextType = ActionContextTypeEnum.CONTEXT_FREE)
    public TestEipConfig callQueryByIdError(TestEipConfig data){
        EipResult<SuperMap> eipResult = testIntegrateService.callQueryByIdError((TestOpenApiModel)new TestOpenApiModel().setId(111L));
        if(!eipResult.getSuccess()){
            throw PamirsException.construct(EipExpEnumerate.SYSTEM_ERROR).appendMsg("调用集成接口callQueryByIdError 失败").errThrow();
        }
        PamirsSession.getMessageHub().error(JsonUtils.toJSONString(eipResult));
        return data;
    }

    @Action(displayName = "调用集成接口callQueryByData",contextType = ActionContextTypeEnum.CONTEXT_FREE)
    public TestEipConfig callQueryByIdSuccess(TestEipConfig data){
        EipResult<SuperMap> eipResult = testIntegrateService.callQueryByData((TestOpenApiModel)new TestOpenApiModel().setId(111L));
        if(!eipResult.getSuccess()){
            throw PamirsException.construct(EipExpEnumerate.SYSTEM_ERROR).appendMsg("调用集成接口callQueryByData 失败").errThrow();
        }
        PamirsSession.getMessageHub().error(JsonUtils.toJSONString(eipResult));
        return data;
    }
}</code></pre>
<h3>Step3 重启看效果</h3>
<p>期待效果,因为开放接口增加了安全相关校验,集成接口这边并没有调整,所以都会抛错</p>
<p><img src="https://doc.oinone.top/wp-content/uploads/2024/05/1665028192254-4f96bda0-d2c2-4b23-a7be-268c84a920f1.png" alt="image.png" /></p>
<h3>Step4 为集成接口定义auth处理类</h3>
<h3>Step4_1 修改 TestEipConfig</h3>
<p>增加了appkey和appSecret两个字段,用于存储开放接口的appkey和appSecret。大家注意我们是用集成接口去访问开放接口,大家可以要想象成两个应用。</p>
<p>站在开放接口角度,但凡需要访问开放接口,就需要先申请appkey和appSecret,并通过appkey和appSecret来换取accessToken,再用accessToken来请求。</p>
<p>站在集成接口角度,我们需要访问对方的接口,必须遵循对方接口安全规范。</p>
<pre><code class="language-java">package pro.shushi.pamirs.demo.api.model;

import pro.shushi.pamirs.eip.api.annotation.IEipAnnotationSingletonConfig;
import pro.shushi.pamirs.eip.api.annotation.Integrate;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.base.IdModel;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;

@Model.model(TestEipConfig.MODEL_MODEL)
@Model(displayName = "演示集成接口配置模型")
@Integrate.ExceptionProcessor(
        errorCode ="success",
        exceptionPredictFun = "testFunction",
        exceptionPredictNamespace = "demo.TestExceptionPredictFunction"
)
public class TestEipConfig extends IdModel implements IEipAnnotationSingletonConfig<TestEipConfig> {

    public static final String MODEL_MODEL = "demo.TestEipConfig";

    @Field(displayName = "服务端域名")
    private String host;
    @Field(displayName = "请求协议Http或Https")
    private String schema;

    @Field(displayName = "appkey")
    private String appKey;

    @Field(displayName = "appSecret")
    @Field.String(size = 4096)
    private String appSecret;

    @Function(openLevel = FunctionOpenEnum.API,summary = "演示集成接口配置模型")
    @Function.Advanced(type = FunctionTypeEnum.QUERY)
    public TestEipConfig construct(TestEipConfig config){
        TestEipConfig config1 = config.singletonModel();
        if(config1!=null){
            return config1;
        }
        return config.construct();
    }
}
</code></pre>
<h3>Step4_2 修改TestIntegrateService和TestIntegrateServiceImpl</h3>
<ol>
<li>
<p>定义和实现fetchOinoneAccessToken</p>
</li>
<li>
<p>修改集成注解增加auth处理器</p>
</li>
</ol>
<h3>Step4_3 新建TestAuthFunction</h3>
<ol>
<li>
<p>获取TestEipConfig的配置信息</p>
</li>
<li>
<p>先从缓存中拿accessToken</p>
<pre><code class="language-java">
@Function
EipResult<SuperMap> fetchOinoneAccessToken(String appkey,String appSecret) ;</code></pre>
</li>
</ol>
<pre><code>
```java

@Override
@Function
@Integrate(config = TestEipConfig.class)
@Integrate.Advanced(path = "/openapi/pamirs/queryById4Open?tenant=pamirs")
@Integrate.RequestProcessor(
        finalResultKey = "out",
        convertParams={
                @Integrate.ConvertParam(inParam="id",outParam="out.id")
        },
        authenticationProcessorNamespace = TestAuthFunction.FUN_NAMESPACE,
        authenticationProcessorFun =  TestAuthFunction.FUN
)
public EipResult<SuperMap> callQueryById(String id) {
    return null;
}

@Override
@Function
@Integrate(config = TestEipConfig.class)
@Integrate.Advanced(path = "/openapi/pamirs/queryById4Open?tenant=pamirs")
@Integrate.RequestProcessor(
        finalResultKey = "out",
        convertParams={
                @Integrate.ConvertParam(inParam="data.id",outParam="out.id")
        },
        authenticationProcessorNamespace = TestAuthFunction.FUN_NAMESPACE,
        authenticationProcessorFun =  TestAuthFunction.FUN
)
public EipResult<SuperMap> callQueryByData(TestOpenApiModel data) {
    return null;
}

@Function
@Integrate(config = TestEipConfig.class)
@Integrate.Advanced(path = "/openapi/pamirs/error?tenant=pamirs")
@Integrate.RequestProcessor(
        finalResultKey = "out",
        convertParams={
                @Integrate.ConvertParam(inParam="data.id",outParam="out.id")
        },
        authenticationProcessorNamespace = TestAuthFunction.FUN_NAMESPACE,
        authenticationProcessorFun =  TestAuthFunction.FUN

)
public EipResult<SuperMap> callQueryByIdError(TestOpenApiModel data) {
    return null;
}

@Function
@Integrate(config = TestEipConfig.class)
@Integrate.Advanced(path = "/openapi/get/access-token?tenant=pamirs")
@Integrate.RequestProcessor(
        convertParams={
                @Integrate.ConvertParam(inParam="appkey",outParam= IEipContext.HEADER_PARAMS_KEY+".appkey"),
                @Integrate.ConvertParam(inParam="appSecret",outParam=IEipContext.HEADER_PARAMS_KEY+".appSecret")
        }
)
@Integrate.ResponseProcessor(
        convertParams={
                @Integrate.ConvertParam(inParam="access_token",outParam="accessToken"),
        }
)
public EipResult<SuperMap> fetchOinoneAccessToken(String appkey,String appSecret) {
    return null;
}

a. 拿到直接放到请求头中

b. 拿不到调用对方接口获取accessToken(这里对方接口也就是我们自己的开放平台的接口)获取accessToken

package pro.shushi.pamirs.demo.core.service;

import org.apache.camel.ExtendedExchange;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.core.common.SuperMap;
import pro.shushi.pamirs.demo.api.model.TestEipConfig;
import pro.shushi.pamirs.demo.api.service.TestIntegrateService;
import pro.shushi.pamirs.eip.api.IEipAuthenticationProcessor;
import pro.shushi.pamirs.eip.api.IEipContext;
import pro.shushi.pamirs.eip.api.entity.EipResult;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.common.spring.BeanDefinitionUtils;

import java.util.concurrent.TimeUnit;

@Fun(TestAuthFunction.FUN_NAMESPACE)
@Component
public class TestAuthFunction implements IEipAuthenticationProcessor<SuperMap> {

    public static final String FUN_NAMESPACE ="demo.TestAuthFunction";
    public static final String FUN ="testAuthentication";
    @Function
    @Function.fun(FUN)
    public Boolean testAuthentication(IEipContext<SuperMap> context,ExtendedExchange exchange) {
        return authentication(context,exchange);
    }

    @Override
    public boolean authentication(IEipContext<SuperMap> context, ExtendedExchange exchange) {
        //获取第三方配置
        TestEipConfig testEipConfig = new TestEipConfig().singletonModel();
        TestIntegrateService testIntegrateService =  BeanDefinitionUtils.getBean(TestIntegrateService.class);
        RedisTemplate redisTemplate = (RedisTemplate)BeanDefinitionUtils.getBean("redisTemplate");
        String accessToken = (String)redisTemplate.opsForValue().get(testEipConfig.getAppKey());
        if(StringUtils.isNotBlank(accessToken)){
            context.putInterfaceContextValue(IEipContext.HEADER_PARAMS_KEY+".accessToken",accessToken);
        }else {
            //调用获取accessToken接口
            EipResult<SuperMap> eipResult = testIntegrateService.fetchOinoneAccessToken(testEipConfig.getAppKey(), testEipConfig.getAppSecret());
            if (eipResult.getSuccess()) {
                accessToken = String.valueOf(eipResult.getContext().getInterfaceContextValue("accessToken"));
                if (StringUtils.isBlank(accessToken)) {
                    return Boolean.FALSE;
                }
                context.putInterfaceContextValue(IEipContext.HEADER_PARAMS_KEY + ".accessToken", accessToken);
                //oinone的accessToken默认失效时间为15分钟
                redisTemplate.opsForValue().set(testEipConfig.getAppKey(), accessToken,13*60, TimeUnit.SECONDS);
            } else {
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;
    }
}

Step4_4 修改TestEipAction

借用TestEipConfig 模型的列表页提供一个新增EipApplication的入口

……引用依赖类
@Model.model(TestEipConfig.MODEL_MODEL)
@Component
@Slf4j
public class TestEipAction {
    ……其他代码

    @Action(displayName = "创建一个集成应用",contextType = ActionContextTypeEnum.CONTEXT_FREE)
    public TestEipConfig createEipApplication(TestEipConfig data){

        EipApplication eipApplication = new EipApplication().setName("测试的集成应用").queryOne();
        if(eipApplication ==null){
            eipApplication = new EipApplication().setName("测试的集成应用");
            eipApplication.create();
        }else {
            eipApplication.updateById();
        }
        return data;
    }
}

Step5 重启看效果

  1. 点击【集成测试】菜单,点击【创建一个集成应用】按钮,数据库查看创建出来的EipApplication记录和EipAuthentication记录

image.png

image.png

  1. 点击【集成测试】菜单,编辑记录,填写appkey和appSecret

image.png

image.png

  1. 点击【集成测试】菜单,依次点击恢复原有的效果,只有【调用集成接口callQueryByIdError】报错其他不再报错了,也可以进入集成接口模块的日志菜单查看请求日志

image.png

自定义安全策略(AccessToken+加密)

例子采用DEFAULT_AUTHENTICATION_PROCESSOR_FUN安全策略,根据EipApplication配置的加密类型进行加解密处理的认证。安全模式:accessToken+加密

当开放接口采用DEFAULT_AUTHENTICATION_PROCESSOR_FUN的策略,需要外部调用方请求参数为{result:"加密后的请求体内容"}的结构,result的值为加密后的请求体内容。

Step1 更改开放接口认证方式

认证方式换成DEFAULT_AUTHENTICATION_PROCESSOR_FUN,@Open.Advanced(authenticationProcessorFun= EipFunctionConstant.DEFAULT_AUTHENTICATION_PROCESSOR_FUN)

小伙伴们可以把@Open.Advanced(authenticationProcessorFun)的配置挪到TestEipConfig中去,这样就不用为每个接口配置了,自己动手吧。

//…… 包引用
@Fun(TestOpenApiModelService.FUN_NAMESPACE)
@Component
public class TestOpenApiModelServiceImpl implements TestOpenApiModelService {

//…… 其他代码

    @Function
    @Open
    @Open.Advanced(
            authenticationProcessorFun= EipFunctionConstant.DEFAULT_AUTHENTICATION_PROCESSOR_FUN,
            authenticationProcessorNamespace = EipFunctionConstant.FUNCTION_NAMESPACE
    )
    public OpenEipResult<TestOpenApiResponse> queryById4Open(IEipContext<SuperMap> context , ExtendedExchange exchange) {
       //…… 其他代码
    }

    @Function
    @Open(path = "error")
    @Open.Advanced(
            httpMethod = "post",
            authenticationProcessorFun= EipFunctionConstant.DEFAULT_AUTHENTICATION_PROCESSOR_FUN,
            authenticationProcessorNamespace = EipFunctionConstant.FUNCTION_NAMESPACE
    )
    public OpenEipResult<TestOpenApiResponse> queryById4OpenError(IEipContext<SuperMap> context , ExtendedExchange exchange) {
        throw PamirsException.construct(EipExpEnumerate.SYSTEM_ERROR).appendMsg("测试异常").errThrow();
    }

}

Step2 重启看效果

期待效果,因为开放接口修改了安全相关校验,集成接口这边并没有调整,所以都会抛错

image.png

Step3 为集成接口定义新的auth处理类

Step3_1 修改 TestEipConfig

增加两个字段encryptType和publicKey,这里加密类型为RSA,我们为集成接口申请的EipApplication没有指定加密类型,默认RSA。oinone的开放接口总共支持两种:RSA、AES


    @Field(displayName = "encryptType")
    private EncryptTypeEnum encryptType;

    @Field(displayName = "publicKey")
    @Field.String(size = 4096)
    private String publicKey;

Step3_2 新建TestInOutConvertFunction

新建TestInOutConvertFunction实现IEipInOutConverter接口

package pro.shushi.pamirs.demo.core.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import org.apache.camel.ExtendedExchange;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.core.common.EncryptHelper;
import pro.shushi.pamirs.demo.api.model.TestEipConfig;
import pro.shushi.pamirs.eip.api.IEipInOutConverter;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.util.JsonUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

@Fun(TestInOutConvertFunction.FUN_NAMESPACE)
@Component
public class TestInOutConvertFunction implements IEipInOutConverter {

    public static final String FUN_NAMESPACE ="demo.third.TestInOutConvertFunction";
    public static final String FUN ="exchangeObject";

    @Function
    @Function.fun(FUN)
    @Override
    public Object exchangeObject(ExtendedExchange exchange, Object inObject) throws Exception {
        //获取第三方配置
        TestEipConfig testEipConfig = new TestEipConfig().singletonModel();
        //数据加密
        String data;
        if (inObject instanceof InputStream) {
            data = (String) JSON.parseObject((InputStream)inObject, String.class, new Feature[0]);
        } else if (inObject instanceof String) {
            data = (String)inObject;
        } else {
            data = JsonUtils.toJSONString(inObject);
        }
        switch (testEipConfig.getEncryptType()) {
            case RSA:
                try {
                    data = EncryptHelper.encryptByKey(EncryptHelper.getPublicKey(testEipConfig.getEncryptType().value(), testEipConfig.getPublicKey()), data);
                } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException | InvalidKeySpecException e) {
                    e.printStackTrace();
                    data = null;
                }
                break;
            case AES:
                try {
                    data = EncryptHelper.encryptByKey(EncryptHelper.getSecretKeySpec(testEipConfig.getEncryptType().value(), testEipConfig.getPublicKey()), data);
                } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
                    e.printStackTrace();
                    data = null;
                }
                break;
            default:
                throw new IllegalStateException("Unexpected value: " + testEipConfig.getEncryptType());
        }
        //放到指定的路径中,因为集成接口的finalResultKey为"out",这样刚好构建{result:""}的请求结构,根据不同公司不一样
        return "{result:\""+data+"\"}";
    }
}

Step3_3 修改TestIntegrateServiceImpl

为集成方法增加inOutConverter的注解

@Override
@Function
@Integrate(config = TestEipConfig.class)
@Integrate.Advanced(path = "/openapi/pamirs/queryById4Open?tenant=pamirs")
@Integrate.RequestProcessor(
        finalResultKey = "out",
        convertParams={
                @Integrate.ConvertParam(inParam="id",outParam="out.id")
        },
        authenticationProcessorNamespace = TestAuthFunction.FUN_NAMESPACE,
        authenticationProcessorFun =  TestAuthFunction.FUN,
        inOutConverterFun = TestInOutConvertFunction.FUN,
        inOutConverterNamespace = TestInOutConvertFunction.FUN_NAMESPACE
)
public EipResult<SuperMap> callQueryById(String id) {
    return null;
}

@Override
@Function
@Integrate(config = TestEipConfig.class)
@Integrate.Advanced(path = "/openapi/pamirs/queryById4Open?tenant=pamirs")
@Integrate.RequestProcessor(
        finalResultKey = "out",
        convertParams={
                @Integrate.ConvertParam(inParam="data.id",outParam="out.id")
        },
        authenticationProcessorNamespace = TestAuthFunction.FUN_NAMESPACE,
        authenticationProcessorFun =  TestAuthFunction.FUN,
        inOutConverterFun = TestInOutConvertFunction.FUN,
        inOutConverterNamespace = TestInOutConvertFunction.FUN_NAMESPACE
)
public EipResult<SuperMap> callQueryByData(TestOpenApiModel data) {
    return null;
}

@Function
@Integrate(config = TestEipConfig.class)
@Integrate.Advanced(path = "/openapi/pamirs/error?tenant=pamirs")
@Integrate.RequestProcessor(
        finalResultKey = "out",
        convertParams={
                @Integrate.ConvertParam(inParam="data.id",outParam="out.id")
        },
        authenticationProcessorNamespace = TestAuthFunction.FUN_NAMESPACE,
        authenticationProcessorFun =  TestAuthFunction.FUN,
        inOutConverterFun = TestInOutConvertFunction.FUN,
        inOutConverterNamespace = TestInOutConvertFunction.FUN_NAMESPACE
)
public EipResult<SuperMap> callQueryByIdError(TestOpenApiModel data) {
    return null;
}

Step4 重启看效果

  1. 点击【集成测试】菜单,点击【创建一个集成应用】按钮,数据库查看创建出来的EipApplication记录和EipAuthentication记录,为记录填写publicKey

image.png

image.png

  1. 再次点击【集成测试】菜单,依次点击恢复原有的效果,只有【调用集成接口callQueryByIdError】报错其他不再报错了,也可以进入集成接口模块的日志菜单查看请求日志

image.png

自定义安全策略(AccessToken+签名)

例子采用DEFAULT_MD5_SIGNATURE_AUTHENTICATION_PROCESSOR_FUN

安全策略,安全模式:accessToken+签名

签名支持:md5和hmac

签名方式和签名摘要:请求方指定,在请求体中设置signatureMethod、signature

Step1 更改开放接口认证方式

认证方式换成DEFAULT_MD5_SIGNATURE_AUTHENTICATION_PROCESSOR_FUN,@Open.Advanced(authenticationProcessorFun= EipFunctionConstant.DEFAULT_MD5_SIGNATURE_AUTHENTICATION_PROCESSOR_FUN)

//…… 包引用

@Fun(TestOpenApiModelService.FUN_NAMESPACE)
@Component
public class TestOpenApiModelServiceImpl implements TestOpenApiModelService {
//…… 其他代码

    @Function
    @Open
    @Open.Advanced(
        authenticationProcessorFun= EipFunctionConstant.DEFAULT_MD5_SIGNATURE_AUTHENTICATION_PROCESSOR_FUN,
        authenticationProcessorNamespace = EipFunctionConstant.FUNCTION_NAMESPACE
    )
    public OpenEipResult<TestOpenApiResponse> queryById4Open(IEipContext<SuperMap> context , ExtendedExchange exchange) {
        //…… 其他代码
    }

    @Function
    @Open(config = TestEipConfig.class,path = "error")
    @Open.Advanced(
        httpMethod = "post",
        authenticationProcessorFun= EipFunctionConstant.DEFAULT_MD5_SIGNATURE_AUTHENTICATION_PROCESSOR_FUN,
        authenticationProcessorNamespace = EipFunctionConstant.FUNCTION_NAMESPACE
    )
    public OpenEipResult<TestOpenApiResponse> queryById4OpenError(IEipContext<SuperMap> context , ExtendedExchange exchange) {
        throw PamirsException.construct(EipExpEnumerate.SYSTEM_ERROR).appendMsg("测试异常").errThrow();
    }

}

Step2 重启看效果

期待效果,因为开放接口修改了安全相关校验,集成接口这边并没有调整,所以都会抛错

image.png

Step3 为集成接口定义新的auth处理类

Step3_1 新建SignUtil

新建SignUtil签名工具类

package pro.shushi.pamirs.demo.core.util;

import org.apache.commons.lang3.StringUtils;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;

public class SignUtil {

    public static String signTopRequest(Map<String, String> params, String secret, String signMethod) throws IOException {
        // 第一步:检查参数是否已经排序
        String[] keys = params.keySet().toArray(new String[0]);
        Arrays.sort(keys);

        // 第二步:把所有参数名和参数值串在一起
        StringBuilder query = new StringBuilder();
        if ("md5".equals(signMethod)) { //签名的摘要算法,可选值为:hmac,md5,hmac-sha256
            query.append(secret);
        }
        for (String key : keys) {
            String value = params.get(key);
            if (StringUtils.isAllBlank(new CharSequence[]{key, value})) {
                query.append(key).append(value);
            }
        }

        // 第三步:使用MD5/HMAC加密
        byte[] bytes;
        if ("hmac".equals(signMethod)) {
            bytes = encryptHMAC(query.toString(), secret);
        } else {
            query.append(secret);
            bytes = encryptMD5(query.toString());
        }

        // 第四步:把二进制转化为大写的十六进制(正确签名应该为32大写字符串,此方法需要时使用)
        return byte2hex(bytes);
    }

    public static byte[] encryptHMAC(String data, String secret) throws IOException {
        byte[] bytes = null;
        try {
            SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacMD5");
            Mac mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
            bytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        } catch (GeneralSecurityException gse) {
            throw new IOException(gse.toString());
        }
        return bytes;
    }

    private static byte[] encryptMD5(String data) throws IOException {
        return encryptMD5(data.getBytes(StandardCharsets.UTF_8));
    }

    private static byte[] encryptMD5(byte[] data) throws IOException {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            return md.digest(data);
        } catch (NoSuchAlgorithmException var3) {
            throw new IOException(var3.toString(), var3);
        }
    }

    public static String byte2hex(byte[] bytes) {
        StringBuilder sign = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toUpperCase());
        }
        return sign.toString();
    }
}

Step3_2 新建TestMd5InOutConvertFunction

  1. 跟加密一样,必须在所有内容都处理完成后进行处理,要不然参数个数对不上的。

  2. 例子中新增一种设置Header的方法,exchange.getMessage().setHeader前面,在前面我们学习AccessToken的时候用的方法是:context.putInterfaceContextValue(IEipContext.HEADER_PARAMS_KEY+".accessToken"

    
    package pro.shushi.pamirs.demo.core.service;

import org.apache.camel.ExtendedExchange;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.demo.api.model.TestEipConfig;
import pro.shushi.pamirs.demo.core.util.SignUtil;
import pro.shushi.pamirs.eip.api.IEipInOutConverter;
import pro.shushi.pamirs.eip.api.converter.DefaultInOutConverter;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Fun(TestMd5InOutConvertFunction.FUN_NAMESPACE)
@Component
public class TestMd5InOutConvertFunction extends DefaultInOutConverter implements IEipInOutConverter {

public static final String FUN_NAMESPACE ="demo.third.TestMd5InOutConvertFunction";
public static final String FUN ="exchangeObject";

@Function
@Function.fun(FUN)
public Object exchangeObject(ExtendedExchange exchange, Object inObject) throws Exception {
    if (inObject instanceof Map) {
        Map<String, Object> map = (Map)inObject;
        if (MapUtils.isNotEmpty(map)) {

            TestEipConfig testEipConfig = new TestEipConfig().singletonModel();
            String signatureMethod ="md5";
            Map<String, String> params = new HashMap(map.size());
            for(Map.Entry<String ,Object> entry: map.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                params.put(key, String.valueOf(value));
            }
            try {
                String signature = SignUtil.signTopRequest(params, testEipConfig.getAppKey(), signatureMethod);
                exchange.getMessage().setHeader("signatureMethod",signatureMethod);
                exchange.getMessage().setHeader("signature",signature);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return super.exchangeObject(exchange, inObject);
}

}


### Step3_3 修改TestIntegrateServiceImpl

更换inOutConverter的注解为TestMd5InOutConvertFunction
```JAVA
@Override
@Function
@Integrate(config = TestEipConfig.class)
@Integrate.Advanced(path = "/openapi/pamirs/queryById4Open?tenant=pamirs")
@Integrate.RequestProcessor(
        finalResultKey = "out",
        convertParams={
                @Integrate.ConvertParam(inParam="id",outParam="out.id")
        },
        authenticationProcessorNamespace = TestAuthFunction.FUN_NAMESPACE,
        authenticationProcessorFun =  TestAuthFunction.FUN,
        inOutConverterFun = TestMd5InOutConvertFunction.FUN,
        inOutConverterNamespace = TestMd5InOutConvertFunction.FUN_NAMESPACE
)
public EipResult<SuperMap> callQueryById(String id) {
    return null;
}

@Override
@Function
@Integrate(config = TestEipConfig.class)
@Integrate.Advanced(path = "/openapi/pamirs/queryById4Open?tenant=pamirs")
@Integrate.RequestProcessor(
        finalResultKey = "out",
        convertParams={
                @Integrate.ConvertParam(inParam="data.id",outParam="out.id")
        },
        authenticationProcessorNamespace = TestAuthFunction.FUN_NAMESPACE,
        authenticationProcessorFun =  TestAuthFunction.FUN,
        inOutConverterFun = TestMd5InOutConvertFunction.FUN,
        inOutConverterNamespace = TestMd5InOutConvertFunction.FUN_NAMESPACE
)
public EipResult<SuperMap> callQueryByData(TestOpenApiModel data) {
    return null;
}

@Function
@Integrate(config = TestEipConfig.class)
@Integrate.Advanced(path = "/openapi/pamirs/error?tenant=pamirs")
@Integrate.RequestProcessor(
        finalResultKey = "out",
        convertParams={
                @Integrate.ConvertParam(inParam="data.id",outParam="out.id")
        },
        authenticationProcessorNamespace = TestAuthFunction.FUN_NAMESPACE,
        authenticationProcessorFun =  TestAuthFunction.FUN,
        inOutConverterFun = TestMd5InOutConvertFunction.FUN,
        inOutConverterNamespace = TestMd5InOutConvertFunction.FUN_NAMESPACE

)
public EipResult<SuperMap> callQueryByIdError(TestOpenApiModel data) {
    return null;
}

Step4 重启看效果

点击【集成测试】菜单,依次点击恢复原有的效果,只有【调用集成接口callQueryByIdError】报错其他不再报错了,也可以进入集成接口模块的日志菜单查看请求日志

image.png

自定义序列化方式

Step1 新增一个xml的开放接口

为TestOpenApiModelServiceImpl新增一个方法queryById4ReturnXml,使用@Open注解,路径配置为xml。同时记得重启second应用,发布这个openApi

@Function
@Open(path = "xml")
@Open.Advanced(
        httpMethod = "post",
        authenticationProcessorFun= EipFunctionConstant.DEFAULT_MD5_SIGNATURE_AUTHENTICATION_PROCESSOR_FUN,
        authenticationProcessorNamespace = EipFunctionConstant.FUNCTION_NAMESPACE
)
public String queryById4ReturnXml(IEipContext<SuperMap> context , ExtendedExchange exchange) {
    return "<result><id>12</id><success>true</success></result>";
}

Step2 Demo工程引入dom4j的依赖包

在总工程中修改pom.xml增加对dom4j的依赖管理

<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.1</version>
</dependency>

在pamirs-demo-core工程中修改pom.xml增加对dom4j的依赖

<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
</dependency>

Step3 自定义序列化函数TestSerializableFunction

新建TestSerializableFunction实现 IEipSerializable<SuperMap>接口

package pro.shushi.pamirs.demo.core.service;

import com.alibaba.fastjson.JSONArray;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.core.common.SuperMap;
import pro.shushi.pamirs.eip.api.IEipSerializable;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;

@Fun(TestSerializableFunction.FUN_NAMESPACE)
@Component
public class TestSerializableFunction implements IEipSerializable<SuperMap> {

    public static final String FUN_NAMESPACE ="demo.third.TestSerializableFunction";
    public static final String FUN ="serializable";

    @Override
    @Function.Advanced(displayName = "自定义xml序列化方式")
    @Function.fun(FUN)
    public SuperMap serializable(Object inObject) {
        if (inObject == null) {
            return new SuperMap();
        } else {
            SuperMap result;
            if (inObject instanceof String) {
                String inObjectString = (String)inObject;
                if (StringUtils.isNotBlank(inObjectString)) {
                    result = this.stringToSuperMap(inObjectString);
                } else {
                    result = new SuperMap();
                }
            } else if (inObject instanceof InputStream) {
                result = this.inputStreamToString((InputStream)inObject);
            } else if (inObject instanceof SuperMap) {
                result = (SuperMap) inObject;
            }
            else{
                result = new SuperMap();
            }
            return result;
        }
    }

    protected SuperMap inputStreamToString(InputStream inputStream) {
        StringBuilder sb = new StringBuilder();
        String line;
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }
            return serializable(sb.toString());
        } catch (IOException e) {
            return new SuperMap();
        }
    }

    protected SuperMap stringToSuperMap(String s) {
        SuperMap result = new SuperMap();
        try {
            Document document = DocumentHelper.parseText(s);
            Element root = document.getRootElement();
            iterateNodes(root, result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public static void iterateNodes(Element node, SuperMap superMap){
        //获取当前元素的名称
        String nodeName = node.getName();
        if(superMap.containsKey(nodeName)){
            //该元素在同级下有多个
            Object object = superMap.getIteration(nodeName);
            List<Object> list = Lists.newArrayList();
            if(object instanceof JSONArray){
                list = (List) object;
            }else {
                list = Lists.newArrayList();
                list.add(object);
            }
            //获取该元素下所有子元素
            List<Element> listElement = node.elements();
            if(listElement.isEmpty()){
                //该元素无子元素,获取元素的值
                String nodeValue = node.getTextTrim();
                list.add(nodeValue);
                superMap.putIteration(nodeName, list);
                return;
            }
            //有子元素
            SuperMap subMap = new SuperMap();
            //遍历所有子元素
            for(Element e:listElement){
                //递归
                iterateNodes(e, subMap);
            }
            list.add(subMap);
            subMap.putIteration(nodeName, list);
            return;
        }
        List<Element> listElement = node.elements();
        if(listElement.isEmpty()){
            //该元素无子元素,获取元素的值
            String nodeValue = node.getTextTrim();
            superMap.putIteration(nodeName, nodeValue);
            return;
        }
        //有子节点,新建一个JSONObject来存储该节点下子节点的值
        SuperMap subMap = new SuperMap();
        //遍历所有一级子节点
        for(Element e:listElement){
            //递归
            iterateNodes(e, subMap);
        }
        superMap.putIteration(nodeName, subMap);
    }

}

Step4 新增一个异常判定类

新增TestXmlExceptionPredictFunction异常判定类,因为原来的异常判断类配置在了TestEipConfig,所以所有方法都会默认使用TestEipConfig类上定义的异常判断类。

package pro.shushi.pamirs.demo.core.service;

import pro.shushi.pamirs.core.common.SuperMap;
import pro.shushi.pamirs.eip.api.IEipContext;
import pro.shushi.pamirs.eip.api.IEipExceptionPredict;
import pro.shushi.pamirs.meta.annotation.Fun;
import pro.shushi.pamirs.meta.annotation.Function;

@Fun(TestXmlExceptionPredictFunction.FUN_NAMESPACE)
public class TestXmlExceptionPredictFunction implements IEipExceptionPredict<SuperMap> {

    public static final String FUN_NAMESPACE ="demo.TestXmlExceptionPredictFunction";
    public static final String FUN ="testFunction";

    @Override
    public boolean test(IEipContext<SuperMap> context) {
        return Boolean.FALSE;
    }

    @Function
    @Function.fun(FUN)
    public Boolean testFunction(IEipContext<SuperMap> context) {
        return test(context);
    }
}

Step5 修改TestIntegrateService和TestIntegrateServiceImpl

TestIntegrateService增加方法定义

@Function
EipResult<SuperMap> callQueryReturnXml(TestOpenApiModel data) ;

TestIntegrateServiceImpl实现callQueryReturnXml 并定义为集成接口,同时配置自定义序列化函数

……引用依赖类

@Fun(TestIntegrateService.FUN_NAMESPACE)
@Component
public class TestIntegrateServiceImpl implements TestIntegrateService {
        ……其他代码

    @Function
    @Integrate(config = TestEipConfig.class)
    @Integrate.Advanced(
            path = "/openapi/pamirs/xml?tenant=pamirs",
            httpMethod = "post"
    )
    @Integrate.RequestProcessor(
            finalResultKey = "out",
            convertParams={
                    @Integrate.ConvertParam(inParam="data.id",outParam="out.id")
            },
            authenticationProcessorNamespace = TestAuthFunction.FUN_NAMESPACE,
            authenticationProcessorFun =  TestAuthFunction.FUN,
            inOutConverterFun = TestMd5InOutConvertFunction.FUN,
            inOutConverterNamespace = TestMd5InOutConvertFunction.FUN_NAMESPACE

    )
    @Integrate.ResponseProcessor(
            finalResultKey = "result",
            serializableFun = TestSerializableFunction.FUN,
            serializableNamespace = TestSerializableFunction.FUN_NAMESPACE
    )
    @Integrate.ExceptionProcessor(
            errorCode ="success",
            exceptionPredictFun = TestXmlExceptionPredictFunction.FUN,
            exceptionPredictNamespace = TestXmlExceptionPredictFunction.FUN_NAMESPACE
    )
    public EipResult<SuperMap> callQueryReturnXml(TestOpenApiModel data) {
        return null;
    }

}

Step6 增加测试入口

修改TestEipAction 类增加一个callQueryByXml的Action定义,调用testIntegrateService集成接口的callQueryReturnXml方法

@Action(displayName = "调用集成接口callQueryReturnXml",contextType = ActionContextTypeEnum.CONTEXT_FREE)
public TestEipConfig callQueryByXml(TestEipConfig data){
    EipResult<SuperMap> eipResult = testIntegrateService.callQueryReturnXml((TestOpenApiModel)new TestOpenApiModel().setName("cpc").setId(111L));
    if(!eipResult.getSuccess()){
        throw PamirsException.construct(EipExpEnumerate.SYSTEM_ERROR).appendMsg("调用集成接口callQueryReturnXml 失败").errThrow();
    }
    PamirsSession.getMessageHub().error(JsonUtils.toJSONString(eipResult));
    return data;
}

Step7 重启看效果

image.png

Eip注解说明

开放接口注解

Open
├── name 显示名称
├── config 配置类
├── path 路径
├── Advanced 更多配置
│ ├── httpMethod 请求方法,默认:post
│ ├── inOutConverterFun 输入输出转换器函数名称
│ ├── inOutConverterNamespace 输入输出转换器函数命名空间
│ ├── authenticationProcessorFun 认证处理器函数名称
│ ├── authenticationProcessorNamespace 认证处理器函数命名空间
│ ├── serializableFun 序列化函数名称
│ ├── serializableNamespace 序列化函数命名空间
│ ├── deserializationFun 反序列化函数名称
│ └── deserializationNamespace 反序列化函数命名空间

集成接口注解

Integrate
├── name 显示名称
├── config 配置类
├── Advanced 更多配置
│ ├── host 请求域名+端口
│ ├── path 请求路径 以“/”开头
│ ├── schema 请求协议 http或者https
│ └── httpMethod 请求方法,默认post
├── ExceptionProcessor 异常配置
│ ├── exceptionPredictFun 异常判定函数名
│ ├── exceptionPredictNamespace 异常判定函数命名空间
│ ├── errorMsg 异常判定Msg的键值
│ └── errorCode 异常判定errorCode的键值
├── RequestProcessor 请求处理配置
│ ├── finalResultKey 请求的最终结果键值
│ ├── inOutConverterFun 输入输出转换器函数名称
│ ├── inOutConverterNamespace 输入输出转换器函数命名空间
│ ├── paramConverterCallbackFun 参数转换回调函数名称
│ ├── paramConverterCallbackNamespace 参数转换回调函数命名空间
│ ├── authenticationProcessorFun 认证处理器函数名称
│ ├── authenticationProcessorNamespace 认证处理器函数命名空间
│ ├── serializableFun 序列化函数名称
│ ├── serializableNamespace 序列化函数命名空间
│ ├── deserializationFun 反序列化函数名称
│ ├── deserializationNamespace 反序列化函数命名空间
│ └── convertParams 参数转化集合
│ └── ConvertParam 参数转化
│ ├── inParam 输入参数的键值
│ └── outParam输出参数的键值
├── ResponseProcessor 请求处理配置
│ ├── finalResultKey 响应的最终结果键值
│ ├── inOutConverterFun 输入输出转换器函数名称
│ ├── inOutConverterNamespace 输入输出转换器函数命名空间
│ ├── paramConverterCallbackFun 参数转换回调函数名称
│ ├── paramConverterCallbackNamespace 参数转换回调函数命名空间
│ ├── authenticationProcessorFun 认证处理器函数名称
│ ├── authenticationProcessorNamespace 认证处理器函数命名空间
│ ├── serializableFun 序列化函数名称
│ ├── serializableNamespace 序列化函数命名空间
│ ├── deserializationFun 反序列化函数名称
│ ├── deserializationNamespace 反序列化函数命名空间
│ └── convertParams 参数转化集合
│ └── ConvertParam 参数转化
│ ├── inParam 输入参数的键值
│ └── outParam 输出参数的键值

Eip处理函数说明

Eip处理函数 作用 需实现接口 说明
inOutConverter 输入输出转换器函数 IEipInOutConverter inOutConverter 核心处理请求体的构造或响应体的构造
paramConverter 参数转换回调函数 IEipParamConverter 如果自定义paramConverter,会让默认ConvertParam配置的(inParam,outParam)转化失效了,需要自行处理
authenticationProcessor 认证处理器函数 IEipAuthenticationProcessor<SuperMap>
serializable 序列化函数 IEipSerializable<SuperMap> 序列化方法将任意对象转换为上下文承载对象
deserialization 反序列化函数 IEipDeserialization<SuperMap> 目前这个意义不大,因为如果设置了finalResultKey,就会不走反序列化。现在都是通过inoutconverter处理的。
exceptionProcessor 异常判定函数 IEipExceptionPredict<SuperMap>

paramConverter的特殊转换

paramConverter的特殊转换
URL_QUERY_PARAMS_KEY IEipContext.URL_QUERY_PARAMS_KEY.xxx是请求参数,会拼接在url的问号后面 convertParams={@Integrate.ConvertParam(inParam="id",outParam=IEipContext.URL_QUERY_PARAMS_KEY+".id") }
HEADER_PARAMS_KEY IEipContext.HEADER_PARAMS_KEY.xxx是http请求头参数 convertParams={@Integrate.ConvertParam(inParam="id",outParam=IEipContext.HEADER_PARAMS_KEY+".id") }

调用流程

请求调用流程

6.2 集成平台(改)

响应调用流程

6.2 集成平台(改)

Oinone社区 作者:史, 昂原创文章,如若转载,请注明出处:https://doc.oinone.top/oio4/9326.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(0)
史, 昂的头像史, 昂数式管理员
上一篇 2024年5月23日 am8:12
下一篇 2024年5月23日 am8:14

相关推荐

  • 第2章 Oinone的技术独特性

    本章的主要目的是通过分析企业商业支撑软件的项目特性和关注点,找到企业软件发展的另一个本质变化——新技术流派的产生。在对“互联网架构做为最佳实践为何失效”的思考基础上,我们分析互联网中台架构的发展历史以及企业实际现状,找出其水土不服的原因。进而引出Oinone的低代码开发平台如何结合互联网架构并完成创新,以满足企业数字化转型的需求。 具体而言,本章包括以下内容: 企业软件发展的另一个本质变化:新技术流派的产生; 最佳实践为何失效?Oinone如何打造具有企业特色的互联网架构; Oinone独特性之源:元数据与设计原则; Oinone独特性之单体与分布式的灵活切换; Oinone独特性之每一个需求都是一个模块; Oinone独特性之低无一体。

    Oinone 7天入门到精通 2024年5月23日
    1.4K00
  • 4.2.1 组件之生命周期

    组件生命周期的意义所在:比如动态创建了「视图、字段」,等它们初始化完成或者发生了修改后要执行业务逻辑,这个时候只能去自定义当前字段或者视图,体验极差,平台应该提供一些列的生命周期,允许其他人调用生命周期的api去执行对应的逻辑。 一、实现原理 图4-2-1-1 实现原理 当用户通过内部API去监听某个生命周期的时候,内部会动态的去创建该生命周期,每个生命周期都有「唯一标识」,内部会根据「唯一标识」去创建对应的「Effect」,Effect会根据生命周期的「唯一标识」实例化一个「lifeCycle」,「lifeCycle」创建完成后,会被存放到「Heart」中,「Heart」是整个生命周期的心脏,当心脏每次跳动的时候(生命周期被监听触发)都会触发对应的生命周期 二、生命周期API API 描述 返回值 View LifeCycle onViewBeforeCreated 视图创建前 ViewWidget onViewCreated 视图创建后 ViewWidget onViewBeforeMount 视图挂载前 ViewWidget onViewMounted 视图挂载后 ViewWidget onViewBeforeUpdate 视图数据发生修改前 ViewWidget onViewUpdated 视图数据修改后 ViewWidget onViewBeforeUnmount 视图销毁前 ViewWidget onViewUnmounted 视图销毁 ViewWidget onViewSubmit 提交数据 ViewWidget onViewSubmitStart 数据开始提交 ViewWidget onViewSubmitSuccess 数据提交成功 ViewWidget onViewSubmitFailed 数据提交失败 ViewWidget onViewSubmitEnd 数据提交结束 ViewWidget onViewValidateStart 视图字段校验 ViewWidget onViewValidateSuccess 校验成功 ViewWidget onViewValidateFailed 校验失败 ViewWidget onViewValidateEnd 校验结束 ViewWidget Field LifeCycle onFieldBeforeCreated 字段创建前 FieldWidget onFieldCreated 字段创建后 FieldWidget onFieldBeforeMount 字段挂载前 FieldWidget onFieldMounted 字段挂载后 FieldWidget onFieldBeforeUpdate 字段数据发生修改前 FieldWidget onFieldUpdated 字段数据修改后 FieldWidget onFieldBeforeUnmount 字段销毁前 FieldWidget onFieldUnmounted 字段销毁 FieldWidget onFieldFocus 字段聚焦 FieldWidget onFieldChange 字段的值发生了变化 FieldWidget onFieldBlur 字段失焦 FieldWidget onFieldValidateStart 字段开始校验 FieldWidget onFieldValidateSuccess 校验成功 FieldWidget onFieldValidateFailed 校验失败 FieldWidget onFieldValidateEnd 校验结束 FieldWidget 表4-2-1-1 生命周期API 上面列出的分别是「视图、字段」的生命周期,目前Action的生命周期还没有,后续再补充。 三、第一个View组件生命周期的监听(举例) Step1 新建registryLifeCycle.ts 新建registryLifeCycle.ts,监听宠物达人的列表页。’宠物达人table_demo_core’为视图名,您需要找后端配合 import { onViewCreated } from '@kunlun/dependencies' function registryLifeCycle(){ onViewCreated('宠物达人table_demo_core', (viewWidget) => { console.log('宠物达人table_demo_core'); console.log(viewWidget); }); } export {registryLifeCycle} 图4-2-1-2 新建registryLifeCycle.ts Step2 修改main.ts 全局注册lifeCycle import { registryLifeCycle } from './registryLifeCycle'; registryLifeCycle(); 图4-2-1-3 修改main.ts Step3 看效果 图4-2-1-4 示例效果 四、第一个Filed组件生命周期的监听(举例) Step1 修改registryLifeCycle.ts 通过onFieldValueChange增加宠物达人搜索视图的name(达人)字段的值变化进行监听。 宠物达人search:name 代表 视图名:字段名 import { onViewCreated , onFieldValueChange} from '@kunlun/dependencies' function registryLifeCycle(){ onViewCreated('宠物达人table_demo_core', (viewWidget) => { console.log('宠物达人table_demo_core'); console.log(viewWidget); }); onFieldValueChange('宠物达人search:name', (filedWidget) => { console.log('宠物达人search:name');…

  • 3.5.7.3 自定义布局

    布局是什么 在系统中,布局决定了母版内的页面元素,一个页面可以由多个组件进行组装,布局可以根据视图类型来替换。 默认布局范围: 图3-5-7-36 默认布局范围 作用场景 系统内置了多个布局组件,这些组件适用于全局、某个应用或某个页面,提供了灵活的布局定制选项。这些组件根据不同类型的视图进行了默认的组装,这也是选择了视图类型后,页面能够呈现的原因。当然,这些默认的组装是可以被覆盖、新增和添加新组件的。 使用registerLayout进行自定义布局 开发者在使用这些布局组件时,应该遵循公司的规范进行统一的调整。自定义布局组件的使用可通过 registerLayout 实现。registerLayout 的第一个参数是代表布局的 XML,第二个参数是不同的选项维度,默认包含以下维度: viewType: 视图类型 module: 视图模型所在模块 moduleName: 视图模型所在模块名称 model: 视图模型编码 modelName: 视图模型名称 viewName: 视图名称 actionName: 动作名称 inline: 是否内嵌视图(子视图特有) ttype: 模型字段类型(子视图特有) relatedTtype: 关联模型字段类型(子视图特有) field: 字段(子视图特有) 需要注意的是,动作可以是A模块下的a模型,这个动作可以打开B模块下的b模型的视图,module、moduleName、model、modelName应该填b模型对应的值,只不过大部分场景我们都是本模型的动作打开本模型的视图,所以这些场景拿动作所在模型填这些值也可以这些纬度也可以通过查看TS的定义查看 全局 在系统中,我们可以通过指定视图类型来决定某一类视图的全局布局。以表格为例,当第二个入参为 { viewType: ViewType.Table } 时,代表了替换了系统内所有表格的布局样式。 示例工程目录 以下是需关注的工程目录示例,main.ts更新导入./layout,layout/index.ts更新导出./tableLayout: 图3-5-7-37 全局纬度注册布局工程目录示例 示例代码 import {registerLayout, ViewType} from '@kunlun/dependencies' /** * 把系统内所有表格类型视图的全局动作放入搜索区域 * * 移动actionBar布局至外层 * <element widget="actionBar" slot="actionBar" slotSupport="action"> * <xslot name="actions" slotSupport="action" /> * </element> * */ const registerGlobalTableLayout = () => { return registerLayout(`<view type="TABLE"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" slotSupport="field" /> </view> </pack> <pack widget="group" slot="tableGroup"> <element widget="table" slot="table" slotSupport="field"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" slotSupport="field" /> <element widget="rowActions" slot="rowActions" slotSupport="action" /> </element> </pack> </view>`, { viewType: ViewType.Table }) } registerGlobalTableLayout() 图3-5-7-38 全局纬度注册布局代码示例 效果 图3-5-7-39 全局纬度注册布局效果示例 应用 在系统中,我们可以通过指定视图类型和模块名称来替换某一类视图在特定模块下的全局布局。以表格为例,当第二个入参为 { viewType: ViewType.Table, moduleName: ‘resource’ } 时,代表了替换了资源应用下所有表格的布局样式,而其他应用仍使用默认布局 import {registerLayout, ViewType} from '@kunlun/dependencies' const registerModuleTableLayout = () => { return registerLayout(`<view type="TABLE"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" slotSupport="field" /> </view> </pack> <pack…

    2024年5月23日
    1.1K00
  • 4.1.12 函数之内置函数与表达式

    本文意在列全所有内置函数与表达式,方便大家查阅。 一、内置函数 内置函数是系统预先定义好的函数,并且提供表达式调用支持。 通用函数 数学函数 表达式 名称 说明 ABS 绝对值 函数场景: 表达式函数示例: ABS(number)函数说明: 获取number的绝对值 FLOOR 向下取整 函数场景: 表达式函数示例: FLOOR(number)函数说明: 对number向下取整 CEIL 向上取整 函数场景: 表达式函数示例: CEIL(number)函数说明: 对number向上取整 ROUND 四舍五入 函数场景: 表达式函数示例: ROUND(number)函数说明: 对number四舍五入 MOD 取余 函数场景: 表达式函数示例: MOD(A,B)函数说明: A对B取余 SQRT 平方根 函数场景: 表达式函数示例: SQRT(number) 函数说明: 对number平方根 SIN 正弦 函数场景: 表达式函数示例: SIN(number)函数说明: 对number取正弦 COS 余弦 函数场景: 表达式函数示例: COS(number)函数说明: 对number取余弦 PI 圆周率 函数场景: 表达式函数示例: PI() 函数说明: 圆周率 ADD 相加 函数场景: 表达式函数示例: ADD(A,B)函数说明: A与B相加 SUBTRACT 相减 函数场景: 表达式函数示例: SUBTRACT(A,B)函数说明: A与B相减 MULTIPLY 乘积 函数场景: 表达式函数示例: MULTIPLY(A,B)函数说明: A与B相乘 DIVIDE 相除 函数场景: 表达式函数示例: DIVIDE(A,B)函数说明: A与B相除 MAX 取最大值 函数场景: 表达式函数示例: MAX(collection) 函数说明: 返回集合中的最大值,参数collection为集合或数组 MIN 取最小值 函数场景: 表达式函数示例: MIN(collection) 函数说明: 返回集合中的最小值,参数collection为集合或数组 SUM 求和 函数场景: 表达式函数示例: SUM(collection)函数说明: 返回对集合的求和,参数collection为集合或数组 AVG 取平均值 函数场景: 表达式函数示例: AVG(collection)函数说明: 返回集合的平均值,参数collection为集合或数组 COUNT 计数 函数场景: 表达式函数示例: COUNT(collection)函数说明: 返回集合的总数,参数collection为集合或数组 UPPER_MONEY 大写金额 函数场景: 表达式函数示例: UPPER_MONEY(number)函数说明: 返回金额的大写,参数number为数值或数值类型的字符串 表4-1-12-1 数学函数 文本函数 表达式 名称 说明 TRIM 空字符串过滤 函数场景: 表达式函数示例: TRIM(text)函数说明: 去掉文本字符串text中的首尾空格,文本为空时,返回空字符串 IS_BLANK 是否为空字符串 函数场景: 表达式函数示例: IS_BLANK(text)函数说明: 判断文本字符串text是否为空 STARTS_WITH 是否以指定字符串开始 函数场景: 表达式函数示例: STARTS_WITH(text,start)函数说明: 判断文本字符串text是否以文本字符串start开始,文本为空时,按照空字符串处理 ENDS_WITH 是否以指定字符串结束 函数场景: 表达式函数示例: ENDS_WITH(text,start)函数说明: 判断文本字符串text是否以文本字符串end结束,文本为空时,按照空字符串处理 CONTAINS 包含 函数场景: 表达式函数示例: CONTAINS(text,subtext)函数说明: 判断文本字符串text是否包含文本字符串subtext,文本text为空时,按照空字符串处理 LOWER 小写 函数场景: 表达式函数示例: LOWER(text)函数说明: 小写文本字符串text,文本为空时,按照空字符串处理 UPPER 大写 函数场景: 表达式函数示例: UPPER(text)函数说明: 大写文本字符串text,文本为空时,按照空字符串处理 REPLACE 替换字符串 函数场景: 表达式函数示例: REPLACE(text,oldtext,newtext)函数说明: 使用文本字符串newtext替换文本字符串text中的文本字符串oldtext…

    Oinone 7天入门到精通 2024年5月23日
    1.1K00

Leave a Reply

登录后才能评论