前提
源码分析版本是 5.1.x版本
什么是协同开发模式
协同开发模式解决的是不同开发,在开发同一个模型时,不会相互影响,也不会影响到测试环境
详见:Oinone协同开发使用手册
协同开发原理
在协同模式下,本地开发的元数据,配置pamirs.data.distribution.session.ownSign
参数后,元数据前缀加ownSign值,然后只存在redis缓存,不落库。其它环境无法直接访问到该数据。
测试环境,或其它环境访问,需要在url上加ownSign等于设置的,则读redis数据时,除了加载通用数据,也会合并ownSign前缀的redis数据,显示出来
注意事项
- 协同开发仅支持界面设计器,其他设计器均不支持
- 不支持权限配置
- 不支持工作流触发
版本支持
完整支持5.1.0及以上
功能详解
启动时操作
- 做元数据保护检查
- 配置ownSign,则key拼接为
ownSign + ':' + key
- 清除掉ownSign的redis缓存数据;非ownSign不用清理
- 计算差量数据
- 有差量数据,放入ownSign标识数据,并清理本地标识
- dubbo注册服务,group拼接
group + ownSign
后进行注册
读取时操作
读本地
- 组装key:
ownSign + ':' + key
- 本地缓存有数据,更新缓存本地数据,返回
- 本地没有数据,读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]
处理逻辑如下
- 看redis是否启用保护标识的值
- 获取pamirs.distribution.session.ownSign配置
- 没有启动参数 且redis没有值,则retrun
- 如果有启动参数且配置了ownSign,报错
在使用元数据保护模式下,不允许设置 [pamirs.distribution.session.ownSign]
- 如果有启动参数且 redis没有值或启动参数设置 -P metaForceProtected,则写入redis
- 如果有启动参数, 且启动参数跟redis值不同,则报错
[公共环境开启了元数据保护模式,本地开发环境需配置[pamirs.distribution.session.ownSign]]
- 如果没有启动参数且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方式
- 看header是否有ownSign这个标识
- 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;
}
- 如果配置ownSign设置,则进行清理工作
- 组装Key:
ownSign + ':' + key
- 执行redis删除
核心代码 CdModelL2Cachepublic 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远程调用链路源码分析
- 在group加后缀 group = group + ownSign
- 调用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;
}
读缓存
- 如果配置ownSign参数,则走ownSign处理逻辑
- 组装key:
cdOwnSign + SEPARATOR_COLON + key
- 本地有缓存,更新本地缓存,并返回
- 本地没有缓存,读取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()
- 在group加后缀 group = group + ownSign
- 调用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社区 作者:利江原创文章,如若转载,请注明出处:https://doc.oinone.top/backend/17209.html
访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验