Oinone协同开发源码分析

前提

源码分析版本是 5.1.x版本

什么是协同开发模式

协同开发模式解决的是不同开发,在开发同一个模型时,不会相互影响,也不会影响到测试环境
详见:Oinone协同开发使用手册

协同开发原理

在协同模式下,本地开发的元数据,配置pamirs.data.distribution.session.ownSign参数后,元数据前缀加ownSign值,然后只存在redis缓存,不落库。其它环境无法直接访问到该数据。
测试环境,或其它环境访问,需要在url上加ownSign等于设置的,则读redis数据时,除了加载通用数据,也会合并ownSign前缀的redis数据,显示出来

注意事项

  1. 协同开发仅支持界面设计器,其他设计器均不支持
  2. 不支持权限配置
  3. 不支持工作流触发

版本支持

完整支持5.1.0及以上

功能详解

启动时操作

  1. 做元数据保护检查
  2. 配置ownSign,则key拼接为 ownSign + ':' + key
  3. 清除掉ownSign的redis缓存数据;非ownSign不用清理
  4. 计算差量数据
  5. 有差量数据,放入ownSign标识数据,并清理本地标识
  6. dubbo注册服务,group拼接group + ownSign 后进行注册

读取时操作

读本地

  1. 组装key: ownSign + ':' + key
  2. 本地缓存有数据,更新缓存本地数据,返回
  3. 本地没有数据,读redis,并插入本地缓存

读远程

dubbo注册消费者,group拼接group + ownSign 后进行泛化调用

元数据保护检查

开启数据保护模式,在启动参数里加
-PmetaProtected=pamirs

会在启动时,往redis里写入数据

private static final String META_PROTECTED_KEY = "pamirs:check:meta-protected";
private void writeMetaProtected(String metaProtected) {
    stringRedisTemplate.opsForValue().set(META_PROTECTED_KEY, metaProtected);
}

如果同时又设置 pamirs.data.distribution.session.ownSign
则会报错 在使用元数据保护模式下,不允许设置 [pamirs.distribution.session.ownSign]

处理逻辑如下

  1. 看redis是否启用保护标识的值
  2. 获取pamirs.distribution.session.ownSign配置
  3. 没有启动参数 且redis没有值,则retrun
  4. 如果有启动参数且配置了ownSign,报错 在使用元数据保护模式下,不允许设置 [pamirs.distribution.session.ownSign]
  5. 如果有启动参数且 redis没有值或启动参数设置 -P metaForceProtected,则写入redis
  6. 如果有启动参数, 且启动参数跟redis值不同,则报错[公共环境开启了元数据保护模式,本地开发环境需配置[pamirs.distribution.session.ownSign]]
  7. 如果没有启动参数且redis有值,但没有配置ownSign 报错[公共环境开启了元数据保护模式,本地开发环境需配置[pamirs.distribution.session.ownSign]]

核心代码如下
MetadataProtectedChecker

public void process(AppLifecycleCommand command, Set<String> runModules,
                        List<ModuleDefinition> installModules, List<ModuleDefinition> upgradeModules, List<ModuleDefinition> reloadModules) {
        String currentMetaProtected = stringRedisTemplate.opsForValue().get(META_PROTECTED_KEY);
        String metaProtected = getMetaProtected();
        boolean hasCurrentMetaProtected = StringUtils.isNotBlank(currentMetaProtected);
        boolean hasMetaProtected = StringUtils.isNotBlank(metaProtected);
        if (!hasCurrentMetaProtected && !hasMetaProtected) {
            return;
        }
        if (hasMetaProtected) {
            if (Spider.getDefaultExtension(SessionFillOwnSignApi.class).handleOwnSign()) {
            // 如果有启动参数且配置了ownSign
                throw new UnsupportedOperationException("在使用元数据保护模式下,不允许设置 [pamirs.distribution.session.ownSign]");
            }
            if (!hasCurrentMetaProtected || isForceProtected()) {
                writeMetaProtected(metaProtected);
            } else if (!metaProtected.equals(currentMetaProtected)) {
            // 如果有启动参数, 且启动参数跟redis值不同
                throw unsupportedLocalOperation();
            }
        } else {
            if (Spider.getDefaultExtension(SessionFillOwnSignApi.class).handleOwnSign()) {
                return;
            }
            // 没有启动参数且redis有值,但没有配置ownSign 报错
            throw unsupportedLocalOperation();
        }
    }

取ownSign方式

  1. 看header是否有ownSign这个标识
  2. header没有,则从配置里取,并放到header里
    ownSign的获取核心代码
    CdDistributionSessionFillOwnSignApi

    @Override
    public String getCdOwnSign() {
        String cdOwnSign = null;
        // 看header是否有ownSign这个标识
        Map<String, String> headers = PamirsSession.getRequestVariables().getHeaders();
        if (MapUtils.isNotEmpty(headers)) {
            cdOwnSign = headers.get(OWN_SIGN);
        }
        // 页面没有ownSign标识,则从配置里取,并放到header里
        if (StringUtils.isBlank(cdOwnSign)) {
            cdOwnSign = getConfigCdOwnSign();
            if (headers != null) {
                headers.put(OWN_SIGN, cdOwnSign);
            }
        }
        return cdOwnSign;
    }

清理redis缓存

ownSign的rediskey组装形式,会在原有key前加入ownSign值 + ":"
ownSign + ':' + key
CdDistributionSessionCacheApi

    SEPARATOR_COLON = ":";
    default String keyOwnSign(String key) {
        String cdOwnSign = SessionFillOwnSignApiHolder.get().getCdOwnSign();
        if (StringUtils.isNotBlank(cdOwnSign)) {
            key = cdOwnSign + SEPARATOR_COLON + key;
        }
        return key;
    }
  1. 如果配置ownSign设置,则进行清理工作
  2. 组装Key: ownSign + ':' + key
  3. 执行redis删除
    核心代码 CdModelL2Cache

    public void clear() {
    
        if (handleOwnSign()) {
            SessionRedisUtils.deletePattern(keyOwnSign(super.getModelDefPattern()));
            SessionRedisUtils.deletePattern(keyOwnSign(super.getModelNamePattern()));
        }
    }

数据写入缓存

以模型为例
插入缓存入口,updateMetaData

public <T extends MetaBaseModel> void updateMetaData(String model, List<T> dataList) {
        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }

        // 初始化. Init要放在allMetaRefresh的判断前面
        this.init();
        if (allMetaRefresh) {
            return;
        }

        switch (model) {
            case ModuleDefinition.MODEL_MODEL:
            case UeModule.MODEL_MODEL:
                asyncExecution(model, dataList.size(), MetaUpgradeCheckApi.UPDATE_OPERATOR, () -> fillSessionModule((List<ModuleDefinition>) dataList));
                break;
            ....
        }
    }

每个model轮询处理
    private boolean fillSessionModel(List<ModelDefinition> dataList, Boolean needFunctions) {
        ....
        return reliableExecution(() -> {
            for (ModelDefinition modelDefinition : dataList) {
                doOneModel(modelDefinition, needFunctions);
            }
        }, "模型元数据[变更]");
    }

    private void doOneModel(ModelDefinition modelDefinition, Boolean needFunctions) {
        // 构造模型配置
        ModelConfig modelConfig = SessionsHelper.fetchModelConfig(modelDefinition);
        String modelModel = modelConfig.getModel();
        String modelUniqueKey = modelModel + ":" + Optional.ofNullable(modelDefinition.getModelFields()).map(List::size).orElse(0);
        try {
            if (ObjectRepeatHelper.isNotRepeat(DistributionDataSupplier.getModelRepeats(), modelUniqueKey)) {
            // 放入缓存
                modelCache.put(modelModel, modelConfig);
            }
        } catch (Exception e) {
            ....
        }

        // 全量情况下metaData.getStandAloneFunctionList()的Function不全;需要把模型的Function也进行缓存
        if (needFunctions) {
            fillSessionModelFunction(modelConfig.getFunctionList());
        }
    }

放入缓存
CdModelL2Cache

    private void cdPut(String key, ModelConfig value) {
        String modelDefCacheKey = cdModelDefCacheKey(key);
        String modelNameCacheKey = cdModelNameCacheKey(value.getName());
        byte[] bytes = MetadataSerializationHelper.serialize(value.getModelDefinition());
        SessionRedisUtils.set(modelDefCacheKey, bytes);
        SessionRedisUtils.set(modelNameCacheKey, bytes);
        //从模型设计器保存后,需要清除掉本地空的标识。后面再get的时候会从redis中获取
DistributionMetaDataLocalCache.removeModelDef(modelDefCacheKey);
        DistributionMetaDataLocalCache.removeModelDef(modelNameCacheKey);
    }

其它处理ownSign数据对象有:

CdFunctionByNameL2Cache
CdFunctionL2Cache
CdActionL2Cache
CdViewL2Cache
....

写入dubbo服务

写入dubbo链路看文章Oinone远程调用链路源码分析

  1. 在group加后缀 group = group + ownSign
  2. 调用dubbo泛化调用

核心代码DefaultRemoteRegistry

    // 泛化调用
    public void registryService(Function function) {
        String interfaceName = RegistryUtils.getRegistryInterface(function);
        String group = getGroup(function, true);
        String version = getVersion(function);
        Integer timeout = getTimeout(function);
        Integer retries = function.getRetries();
        if (log.isDebugEnabled()) {
            log.debug("register service. interfaceName: {}, group: {}, version: {}, timeout: {}, retries: {}", interfaceName, group, version, timeout, retries);
        }
        remoteRegistryComponent.registryGenericService(interfaceName, group, version, timeout, retries);
}

// group加前缀
private String getGroup(Function function, boolean usingOwnSign) {
        String group = function.getGroup();
...
        if (usingOwnSign) {
            String ownSign = Spider.getDefaultExtension(SessionFillOwnSignApi.class).getCdOwnSign();
            if (StringUtils.isNotBlank(ownSign)) {
                group = group + ownSign;
            }
        }
        return group;
    }

读缓存

  1. 如果配置ownSign参数,则走ownSign处理逻辑
  2. 组装key: cdOwnSign + SEPARATOR_COLON + key
  3. 本地有缓存,更新本地缓存,并返回
  4. 本地没有缓存,读取redis数据,后放入本地缓存,返回

CdModelL2Cache

public ModelConfig get(String key) {
        ModelConfig modelConfig;
        if (handleOwnSign()) {
            modelConfig = cdGet(key);
            if (modelConfig == null) {
                modelConfig = super.get(key);
            }
        } else {
            modelConfig = super.get(key);
        }
        return modelConfig;
    }

private ModelConfig cdGet(String key) {
        if (DistributionBaseKeyer.MODEL_MODEL_BASE_KEYS.contains(key)) {
            return CommonApiFactory.getApi(DistributionL1CacheProxy.class).getModelL1Cache().get(key);
        }
        String modelDefCacheKey = cdModelDefCacheKey(key);
        ModelDefinition modelDef = DistributionMetaDataLocalCache.getModelDef(modelDefCacheKey);
        // 本地有数据,返回,没有则放入本地缓存,再返回
        if (modelDef != null || DistributionMetaDataLocalCache.keyModelDef(modelDefCacheKey)) {
            return super.covertModelConfig(modelDef);
        }
        // 读取redis数据
        byte[] bytes = SessionRedisUtils.get(modelDefCacheKey);
        modelDef = KryoSerializer.deserialize(bytes);
        if (modelDef != null) {
            // 放入本地缓存DistributionMetaDataLocalCache.setModelDef(modelDefCacheKey, modelDef);
        }
        return super.covertModelConfig(modelDef);
    }

读dubbo服务

读取dubbo链路看文章Oinone远程调用链路源码分析
RemoteComputer.invoke()

  1. 在group加后缀 group = group + ownSign
  2. 调用dubbo泛化调用

核心代码DefaultRemoteRegistry

    public GenericService registryConsumer(Function function) {
        String interfaceName = RegistryUtils.getRegistryInterface(function);
        // 
        String group = getGroup(function, true);
        String version = getVersion(function);
        Integer timeout = getTimeout(function);
        Integer retries = function.getRetries();
        if (log.isDebugEnabled()) {
            log.debug("register consumer. interfaceName: {}, group: {}, version: {}, timeout: {}, retries: {}", interfaceName, group, version, timeout, retries);
        }
        return remoteRegistryComponent.registryGenericConsumer(interfaceName, group, version, timeout, retries);
    }

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

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

(0)
oinone的头像oinone
上一篇 2024年9月11日 am9:57
下一篇 2024年9月12日 pm6:12

相关推荐

  • 低无一体使用 (后端)

    低无一体使用 (后端) 低无一体应用 打开低无一体应用。 选择应用模块 在选择模块选择框中,下拉选择需要使用低无一体的应用模块。 生成SDK 点击生成SDK, 生成当前选择应用模块的低无一体SDK。 点击之后的系统消息 提示"生成SDK成功",表示操作完成。 生成扩展工程 点击下载扩展工程模板, 生成当前选择应用模块的低无一体SDK。 点击之后的系统消息 提示"下载扩展工程模板成功",表示操作完成。 之后刷新页面 下载扩展工程 使用系统消息中的链接或者详情页中的下载地址下载扩展工程 扩展工程结构概览 自定义Action示例 import org.springframework.stereotype.Component; import pro.shushi.oinone.stand.testExt.model.Model0000000001; import pro.shushi.pamirs.meta.annotation.Action; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.api.dto.condition.Pagination; import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper; import pro.shushi.pamirs.meta.constant.FunctionConstants; import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum; import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum; /** * Model0000000001Action * * @author yakir on 2025/01/20 14:59. */ @Component @Model.model(Model0000000001.MODEL_MODEL) public class Model0000000001Action { @Function.Advanced(type = FunctionTypeEnum.QUERY) @Function.fun(FunctionConstants.queryPage) @Function(openLevel = {FunctionOpenEnum.API}) public Pagination<Model0000000001> queryPage(Pagination<Model0000000001> page, IWrapper<Model0000000001> queryWrapper) { return new Model0000000001().queryPage(page, queryWrapper); } @Action(displayName = "sayHello") @Action.Advanced(type = FunctionTypeEnum.QUERY) public Model0000000001 sayHello(Model0000000001 query) { query.setName(query.getName() + System.currentTimeMillis()); return query; } } 注意事项 ⚠️⚠️⚠️ Oinone底层依赖版本与设计器和业务应用一致 (参考 版本更新日志 ) 扩展工程如需独立启动, 手动修改application.yml中安装模块和pom.xml中模块jar的依赖配置

    2025年2月17日
    87500
  • 导入设计数据时dubbo超时导入失败

    问题描述 在本地启动导入设计数据的工程时,会出现dubbo调用超时导致设计数据无法完整导入的问题。 org.apache.dubbo.remoting.TimeoutException 产生原因 pom中的包依赖出现问题,导致没有使用正确的远程服务。 本地可能出现的异常报错堆栈信息如下: xception in thread "fixed-1-thread-10" PamirsException level: ERROR, code: 10100025, type: SYSTEM_ERROR, msg: 函数执行错误, extra:, extend: null at pro.shushi.pamirs.meta.common.exception.PamirsException$Builder.errThrow(PamirsException.java:190) at pro.shushi.pamirs.framework.faas.fun.manage.ManagementAspect.around(ManagementAspect.java:118) at sun.reflect.GeneratedMethodAccessor498.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633) at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) at pro.shushi.pamirs.framework.orm.DefaultWriteApi$$EnhancerBySpringCGLIB$$b4cea2b4.createOrUpdateBatchWithResult(<generated>) at pro.shushi.pamirs.meta.base.manager.data.OriginDataManager.createOrUpdateBatchWithResult(OriginDataManager.java:161) at pro.shushi.pamirs.meta.base.manager.data.OriginDataManager.createOrUpdateBatch(OriginDataManager.java:152) at pro.shushi.pamirs.ui.designer.service.installer.UiDesignerInstaller.lambda$install$0(UiDesignerInstaller.java:42) at pro.shushi.pamirs.core.common.function.AroundRunnable.run(AroundRunnable.java:26) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: org.apache.dubbo.rpc.RpcException: Failed to invoke the method createOrUpdateBatchWithResult in the service org.apache.dubbo.rpc.service.GenericService. Tried 1 times of the providers [192.168.0.123:20880] (1/1) from the registry 127.0.0.1:2181 on the consumer 192.168.0.123 using the dubbo version 2.7.22. Last error is: Invoke remote method timeout. method: $invoke, provider: dubbo://192.168.0.123:20880/ui.designer.UiDesignerViewLayout.oio.defaultWriteApi?anyhost=true&application=pamirs-demo&application.version=1.0.0&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=true&group=pamirs&interface=ui.designer.UiDesignerViewLayout.oio.defaultWriteApi&metadata-type=remote&methods=*&payload=104857600&pid=69748&qos.enable=false&register.ip=192.168.0.123&release=2.7.15&remote.application=pamirs-test&retries=0&serialization=pamirs&service.name=ServiceBean:pamirs/ui.designer.UiDesignerViewLayout.oio.defaultWriteApi:1.0.0&side=consumer&sticky=false&timeout=5000&timestamp=1701136088893&version=1.0.0, cause: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout by scan timer. start time: 2023-11-28 10:23:05.835, end time: 2023-11-28 10:23:10.856, client elapsed: 695 ms, server elapsed: 4326 ms, timeout: 5000 ms, request: Request [id=0, version=2.0.2, twoway=true, event=false, broken=false, data=null], channel: /192.168.0.123:49449 -> /192.168.0.123:20880 at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:110) at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:265) at org.apache.dubbo.rpc.cluster.interceptor.ClusterInterceptor.intercept(ClusterInterceptor.java:47) at org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster$InterceptorInvokerNode.invoke(AbstractCluster.java:92) at org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:98) at org.apache.dubbo.registry.client.migration.MigrationInvoker.invoke(MigrationInvoker.java:170) at org.apache.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:96) at org.apache.dubbo.common.bytecode.proxy0.$invoke(proxy0.java) at pro.shushi.pamirs.framework.faas.distribution.computer.RemoteComputer.compute(RemoteComputer.java:124) at pro.shushi.pamirs.framework.faas.FunEngine.run(FunEngine.java:80) at pro.shushi.pamirs.distribution.faas.remote.spi.service.RemoteFunctionHelper.run(RemoteFunctionHelper.java:68) at pro.shushi.pamirs.framework.faas.fun.manage.ManagementAspect.around(ManagementAspect.java:109) … 20 more Caused…

    2023年11月28日
    1.0K00
  • OSS(CDN)配置和文件系统的一些操作

    目前Oinone支持的OSS类型 类型 服务 OSS 阿里云OSS UPYUN 又拍云 MINIO MinIO HUAWEI_OBS 华为云OBS LOCAL 本地NGINX文件存储 TENCENT_COS 腾讯云COS OSS通用yaml配置 cdn: oss: name: # 名称 type: # 类型 bucket: uploadUrl: # 上传URL downloadUrl: # 下载URL accessKeyId: accessKeySecret: mainDir: # 主目录 validTime: 3600000 timeout: 600000 active: true referer: localFolderUrl: others: [key]: name: # 名称 type: # 类型 bucket: uploadUrl: # 上传URL downloadUrl: # 下载URL accessKeyId: accessKeySecret: mainDir: # 主目录 validTime: 3600000 timeout: 600000 active: true referer: localFolderUrl: PS:others中使用自定义key来指定OSS服务进行文件上传/下载功能。上传/下载必须匹配,否则无法正常使用。 OSS配置示例 阿里云OSS cdn: oss: name: 阿里云 type: OSS bucket: pamirs(根据实际情况修改) uploadUrl: oss-cn-hangzhou.aliyuncs.com downloadUrl: oss-cn-hangzhou.aliyuncs.com accessKeyId: 你的accessKeyId accessKeySecret: 你的accessKeySecret # 根据实际情况修改 mainDir: upload/ validTime: 3600000 timeout: 600000 active: true imageResizeParameter: referer: 华为云OBS cdn: oss: name: 华为云 type: HUAWEI_OBS bucket: pamirs(根据实际情况修改) uploadUrl: obs.cn-east-2.myhuaweicloud.com downloadUrl: obs.cn-east-2.myhuaweicloud.com accessKeyId: 你的accessKeyId accessKeySecret: 你的accessKeySecret # 根据实际情况修改 mainDir: upload/ validTime: 3600000 timeout: 600000 active: true allowedOrigin: http://192.168.95.31:8888,https://xxxx.xxxxx.com referer: 华为云OBS需要在启动工程增加以下依赖 <okhttp3.version>4.9.3</okhttp3.version> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>${okhttp3.version}</version> </dependency> 注意事项华为云OBS的防盗链配置,仅允许携带特定referer的才可以,而excel导入后端处理的逻辑匿名读的时候是不带referer的,所以会被拒绝 MINIO 文件系统,mino的配置: cdn: oss: name: minio type: MINIO bucket: pamirs(根据实际情况修改) uploadUrl: http://192.168.243.6:32190(根据实际情况修改) downloadUrl: http://192.168.243.6:9000(根据实际情况修改) accessKeyId: 你的accessKeyId accessKeySecret: 你的accessKeySecret # 根据实际情况修改 mainDir: upload/ validTime: 3600000 timeout: 600000 active: true referer: localFolderUrl: MINIO无公网访问地址下OSS的配置方式: https://doc.oinone.top/yun-wei-shi-jian/7112.html 又拍云 cdn: oss: name: 又拍云 type: UPYUN…

    后端 2023年11月1日
    1.6K00
  • 自定义RSQL占位符(placeholder)及在权限中使用

    1 自定义RSQL占位符常用场景 统一的数据权限配置 查询表达式的上下文变量扩展 2 自定义RSQL的模板 /** * 演示Placeholder占位符基本定义 * * @author Adamancy Zhang at 13:53 on 2024-03-24 */ @Component public class DemoPlaceHolder extends AbstractPlaceHolderParser { private static final String PLACEHOLDER_KEY = "${thisPlaceholder}"; /** * 占位符 * * @return placeholder */ @Override public String namespace() { return PLACEHOLDER_KEY; } /** * 占位符替换值 * * @return the placeholder replace to the value */ @Override protected String value() { return PamirsSession.getUserId().toString(); } /** * 优先级 * * @return execution order of placeholders, ascending order. */ @Override public Integer priority() { return 0; } /** * 是否激活 * * @return the placeholder is activated */ @Override public Boolean active() { return true; } } 注意事项 在一些旧版本中,priority和active可能不起作用,为保证升级时不受影响,请保证该属性配置正确。 PLACEHOLDER_KEY变量表示自定义占位符使用的关键字,需按照所需业务场景的具体功能并根据上下文语义正确定义。 为保证占位符可以被正确替换并执行,所有占位符都不应该出现重复,尤其是不能与系统内置的重复。 3 占位符使用时的优先级问题 多个占位符在进行替换时,会根据优先级按升序顺序执行,如需要指定替换顺序,可使用Spring的Order注解对其进行排序。 import org.springframework.core.annotation.Order; @Order(0) 4 Oinone平台内置的占位符 占位符 数据类型 含义 备注 ${currentUser} String 当前用户ID 未登录时无法使用 ${currentRoles} Set<String> 当前用户的角色ID集合 未登录时无法使用 5 如何覆盖平台内置的占位符? 通过指定占位符的优先级,并定义相同的namespace可优先替换。 6 如何定义会话级别的上下文变量? 在上述模板中,我们使用的是Oinone平台内置的上下文变量进行演示,通常情况下,我们需要根据实际业务场景增加上下文变量,以此来实现所需功能。 下面,我们将根据当前用户获取当前员工ID定义该上下文变量进行演示。 /** * 员工Session * * @author Adamancy Zhang at 14:33 on 2024-03-24 */ @Component public class EmployeeSession implements HookBefore { private static final String SESSION_KEY = "CUSTOM_EMPLOYEE_ID"; @Autowired private DemoEmployeeService demoEmployeeService; public static String getEmployeeId() { return PamirsSession.getTransmittableExtend().get(SESSION_KEY);…

    2024年3月24日
    1.5K00
  • 如何重写获取首页的方法

    介绍 用户登录成功后或者访问网页不带任何路由参数的时候前端会请求全局的首页的视图动作viewAction配置,然后跳转到该视图动作viewAction 方案 我们可以通过在该方法的后置hook自定义获取首页的逻辑,下面以按角色跳转不同首页的需求示例 package pro.shushi.pamirs.demo.core.hook; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import pro.shushi.pamirs.auth.api.model.AuthRole; import pro.shushi.pamirs.boot.base.enmu.BaseExpEnumerate; import pro.shushi.pamirs.boot.base.model.ViewAction; import pro.shushi.pamirs.boot.web.loader.PageLoadAction; import pro.shushi.pamirs.demo.api.model.DemoItemCategory; import pro.shushi.pamirs.demo.api.model.DemoItemLabel; import pro.shushi.pamirs.meta.annotation.Hook; import pro.shushi.pamirs.meta.api.CommonApiFactory; import pro.shushi.pamirs.meta.api.core.faas.HookAfter; import pro.shushi.pamirs.meta.api.dto.fun.Function; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.common.exception.PamirsException; import pro.shushi.pamirs.user.api.model.PamirsUser; import java.util.List; import java.util.stream.Collectors; @Component public class DemoHomepageHook implements HookAfter { private static final String TEST_ROLE_CODE_01 = "ROLE_1211"; private static final String TEST_ROLE_CODE_02 = "ROLE_1211_1"; @Override @Hook(module = {"base"}, model = {ViewAction.MODEL_MODEL}, fun = {"homepage"}) public Object run(Function function, Object ret) { if (ret == null) { return null; } ViewAction viewAction = getViewActionByCurrentRole(); if (viewAction != null) { ViewAction retNew = CommonApiFactory.getApi(PageLoadAction.class).load(viewAction); ViewAction viewActionRet = (ViewAction) ((Object[]) ret)[0]; viewActionRet.set_d(retNew.get_d()); } return ret; } protected ViewAction getViewActionByCurrentRole() { try { PamirsUser user = new PamirsUser(); user.setId(PamirsSession.getUserId()); user.fieldQuery(PamirsUser::getRoles); List<AuthRole> roles = user.getRoles(); if (CollectionUtils.isNotEmpty(roles)) { List<String> roleCodes = roles.stream().map(AuthRole::getCode).collect(Collectors.toList()); if (roleCodes.contains(TEST_ROLE_CODE_01)) { return new ViewAction().setModel(DemoItemCategory.MODEL_MODEL).setName("DemoMenus_ItemPMenu_DemoItemAndCateMenu_DemoItemCategoryMenu").queryOne(); } else if (roleCodes.contains(TEST_ROLE_CODE_02)) { return new ViewAction().setModel(DemoItemLabel.MODEL_MODEL).setName("DemoMenus_ItemPMenu_DemoItemAndCateMenu_DemoItemLabelMenu").queryOne(); } } } catch (PamirsException exception) { if (PamirsSession.getUserId() == null) { throw PamirsException.construct(BaseExpEnumerate.BASE_USER_NOT_LOGIN_ERROR, exception.getCause()).errThrow(); } else { throw exception; } } return null; } }

    2024年7月6日
    2.3K00

Leave a Reply

登录后才能评论