20 changed files with 11816 additions and 6034 deletions
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
package apelet.tenantadmin.tenant.controller; |
||||
|
||||
import apelet.common.core.annotation.MyRequestBody; |
||||
import apelet.common.core.constant.ErrorCodeEnum; |
||||
import apelet.common.core.object.ResponseResult; |
||||
import apelet.common.core.object.TokenData; |
||||
import apelet.common.log.annotation.OperationLog; |
||||
import apelet.common.log.model.constant.SysOperationLogType; |
||||
import apelet.common.online.model.OnlineDatasource; |
||||
import apelet.common.online.model.OnlineForm; |
||||
import apelet.common.online.service.OnlineDatasourceService; |
||||
import apelet.common.online.service.OnlineFormService; |
||||
import apelet.tenantadmin.tenant.service.OnlineFormTenantSyncService; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.redisson.api.RBucket; |
||||
import org.redisson.api.RedissonClient; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* 在线表单租户同步接口控制器类。 |
||||
* |
||||
* @author guifc |
||||
* @date 2023-08-04 |
||||
*/ |
||||
@Slf4j |
||||
@RestController |
||||
@RequestMapping("/tenantadmin/tenant/onlineFormTenantSync") |
||||
public class OnlineFormTenantSyncController { |
||||
|
||||
@Autowired |
||||
private OnlineFormTenantSyncService onlineFormTenantSyncService; |
||||
|
||||
/** |
||||
* 同步更新租户的在线表单数据。 |
||||
* |
||||
* @param pageId 在线表单页面传输对象id |
||||
* @param tenantIdList 租户ID列表 |
||||
* @return 应答结果对象,包含同步操作是否成功的布尔值 |
||||
*/ |
||||
@OperationLog(type = SysOperationLogType.UPDATE) |
||||
@PostMapping("/syncUpdateTenant") |
||||
public ResponseResult<Boolean> syncUpdateTenant( |
||||
@MyRequestBody Long pageId, |
||||
@MyRequestBody List<Long> tenantIdList) { |
||||
return ResponseResult.success(onlineFormTenantSyncService.syncUpdateTenant(pageId, tenantIdList)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
package apelet.tenantadmin.tenant.service; |
||||
|
||||
|
||||
import java.util.List; |
||||
|
||||
public interface OnlineFormTenantSyncService { |
||||
|
||||
Boolean syncUpdateTenant(Long pageId, List<Long> tenantIdSet); |
||||
|
||||
} |
||||
@ -0,0 +1,181 @@
@@ -0,0 +1,181 @@
|
||||
package apelet.tenantadmin.tenant.service.impl; |
||||
|
||||
import apelet.common.core.annotation.MyDataSource; |
||||
import apelet.common.datasync.producer.DataSyncRocketMqProducer; |
||||
import apelet.common.generator.model.*; |
||||
import apelet.common.generator.service.*; |
||||
import apelet.common.online.model.OnlineDatasource; |
||||
import apelet.common.online.model.OnlineForm; |
||||
import apelet.common.online.service.OnlineDatasourceService; |
||||
import apelet.common.online.service.OnlineFormService; |
||||
import apelet.common.tenant.constant.TenantConstant; |
||||
import apelet.tenantadmin.config.ApplicationConfig; |
||||
import apelet.tenantadmin.config.DataSourceType; |
||||
import apelet.tenantadmin.tenant.service.OnlineFormTenantSyncService; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
import com.jayway.jsonpath.JsonPath; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.jetbrains.annotations.NotNull; |
||||
import org.redisson.api.RBucket; |
||||
import org.redisson.api.RedissonClient; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper; |
||||
import org.springframework.jdbc.core.JdbcTemplate; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* 租户权限资源数据服务实现类。 |
||||
* |
||||
* @author guifc |
||||
* @date 2023-08-04 |
||||
*/ |
||||
@Slf4j |
||||
@MyDataSource(DataSourceType.TENANT_ADMIN) |
||||
@Service("onlineFormTenantSyncService") |
||||
public class OnlineFormTenantSyncServiceImpl implements OnlineFormTenantSyncService { |
||||
|
||||
@Autowired |
||||
private OnlineFormService onlineFormService; |
||||
|
||||
@Autowired |
||||
private RedissonClient redissonClient; |
||||
|
||||
@Autowired |
||||
private OnlineDatasourceService onlineDatasourceService; |
||||
|
||||
@Autowired |
||||
private IOnlCgreportHeadService iOnlCgreportHeadService; |
||||
|
||||
@Autowired |
||||
private IOnlCgreportItemService iOnlCgreportItemService; |
||||
|
||||
@Autowired |
||||
private IOnlCgreportParamService iOnlCgreportParamService; |
||||
|
||||
@Autowired |
||||
private DataSyncRocketMqProducer dataSyncProducer; |
||||
@Autowired |
||||
private ApplicationConfig applicationConfig; |
||||
|
||||
@Autowired |
||||
private JdbcTemplate jdbcTemplate; |
||||
|
||||
@Autowired |
||||
private IOnlFormFindService onlFormFindService; |
||||
@Autowired |
||||
private IOnlFormFieldService onlFormFieldService; |
||||
@Autowired |
||||
private IOnlFormCheckService onlFormCheckService; |
||||
@Autowired |
||||
private IOnlFormIndexService onlFormIndexService; |
||||
@Autowired |
||||
private IOnlFormKeyService onlFormKeyService; |
||||
@Autowired |
||||
private IOnlFormPageService onlFormPageService; |
||||
|
||||
@Override |
||||
@Transactional(rollbackFor = Exception.class) |
||||
public Boolean syncUpdateTenant(Long pageId, List<Long> tenantIdSet) { |
||||
List<OnlineForm> onlineFormListByPageIds = onlineFormService.getOnlineFormListByPageIds(Collections.singleton(pageId)); |
||||
for (OnlineForm onlineForm : onlineFormListByPageIds) { |
||||
List<OnlineDatasource> datasourceList = onlineDatasourceService.getOnlineDatasourceListByFormIds(Collections.singleton(onlineForm.getFormId())); |
||||
Set<Long> datasourceIdSet = datasourceList.stream() |
||||
.map(OnlineDatasource::getDatasourceId) |
||||
.collect(Collectors.toSet()); |
||||
RBucket<OnlineForm> bucket = redissonClient.getBucket("lastUpdateOnlineForm:" + onlineForm.getFormId()); |
||||
OnlineForm lastUpdateOnlineForm; |
||||
if (null != bucket) { |
||||
lastUpdateOnlineForm = bucket.get(); |
||||
} else { |
||||
lastUpdateOnlineForm = null; |
||||
} |
||||
// 根据表单json中的relativeFormCode获取到对应的查询数据源数据
|
||||
String widgetJson = onlineForm.getWidgetJson(); |
||||
List<String> relativeFormCodeList = JsonPath.read(widgetJson, "$..relativeFormCode"); |
||||
List<OnlCgreportHead> onlCgreportHeadList = iOnlCgreportHeadService.list(new LambdaQueryWrapper<OnlCgreportHead>() |
||||
.in(OnlCgreportHead::getCode, relativeFormCodeList)); |
||||
List<String> onlCgreportHeadIdList = onlCgreportHeadList.stream().map(OnlCgreportHead::getId).collect(Collectors.toList()); |
||||
List<OnlCgreportItem> onlCgreportItemList = iOnlCgreportItemService.list(new LambdaQueryWrapper<OnlCgreportItem>() |
||||
.in(OnlCgreportItem::getCgrheadId, onlCgreportHeadIdList)); |
||||
List<OnlCgreportParam> onlCgreportParamList = iOnlCgreportParamService.list(new LambdaQueryWrapper<OnlCgreportParam>() |
||||
.in(OnlCgreportParam::getCgrheadId, onlCgreportHeadIdList)); |
||||
for (Long tenantId : tenantIdSet) { |
||||
// 表单更新同步通知
|
||||
JSONObject formMsgJsonData = new JSONObject(); |
||||
formMsgJsonData.put("adminOnlineForm", onlineForm); |
||||
formMsgJsonData.put("originalOnlineForm", lastUpdateOnlineForm); |
||||
formMsgJsonData.put("datasourceIdSet", datasourceIdSet); |
||||
dataSyncProducer.sendOrderly( |
||||
tenantId, |
||||
applicationConfig.getTenantSyncTopic(), |
||||
OnlineForm.class.getSimpleName(), |
||||
TenantConstant.UPDATE_TENANT_ONLINE_FORM_COMMAND, |
||||
formMsgJsonData.toJSONString(), |
||||
TenantConstant.MESSAGE_QUEUE_SELECTOR_KEY); |
||||
//表单数据源更新同步新增
|
||||
JSONObject onlCgreportHeadMsgJsonData = new JSONObject(); |
||||
onlCgreportHeadMsgJsonData.put("onlCgreportHeadList", onlCgreportHeadList); |
||||
onlCgreportHeadMsgJsonData.put("onlCgreportItemList", onlCgreportItemList); |
||||
onlCgreportHeadMsgJsonData.put("onlCgreportParamList", onlCgreportParamList); |
||||
dataSyncProducer.sendOrderly( |
||||
tenantId, |
||||
applicationConfig.getTenantSyncTopic(), |
||||
OnlCgreportHead.class.getSimpleName(), |
||||
TenantConstant.INSERT_TENANT_ONL_CGREPORT_HEAD_COMMAND, |
||||
onlCgreportHeadMsgJsonData.toJSONString(), |
||||
TenantConstant.MESSAGE_QUEUE_SELECTOR_KEY); |
||||
} |
||||
} |
||||
//根据页面ID获取表单元数据列表
|
||||
String sql = getOnlFormHeadByPageIdSQL(pageId); |
||||
List<OnlFormHead> headList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(OnlFormHead.class), Collections.singleton(pageId)); |
||||
List<OnlFormFind> findList = onlFormFindService.list(new LambdaQueryWrapper<OnlFormFind>().in(OnlFormFind::getHeadId, headList)); |
||||
List<OnlFormField> fieldList = onlFormFieldService.list(new LambdaQueryWrapper<OnlFormField>().in(OnlFormField::getHeadId, headList)); |
||||
List<OnlFormCheck> checkList = onlFormCheckService.list(new LambdaQueryWrapper<OnlFormCheck>().in(OnlFormCheck::getHeadId, headList)); |
||||
List<OnlFormIndex> indexList = onlFormIndexService.list(new LambdaQueryWrapper<OnlFormIndex>().in(OnlFormIndex::getHeadId, headList)); |
||||
List<OnlFormKey> keyList = onlFormKeyService.list(new LambdaQueryWrapper<OnlFormKey>().in(OnlFormKey::getHeadId, headList)); |
||||
List<OnlFormPage> pageList = onlFormPageService.list(new LambdaQueryWrapper<OnlFormPage>().in(OnlFormPage::getHeadId, headList)); |
||||
// 表单元数据通知更新新增
|
||||
for(Long tenantId : tenantIdSet){ |
||||
JSONObject messageJsonData = new JSONObject(); |
||||
messageJsonData.put("onlFormHeadList", headList); |
||||
messageJsonData.put("onlFormFindList", findList); |
||||
messageJsonData.put("onlFormFieldList", fieldList); |
||||
messageJsonData.put("onlFormCheckList", checkList); |
||||
messageJsonData.put("onlFormIndexList", indexList); |
||||
messageJsonData.put("onlFormKeyList", keyList); |
||||
messageJsonData.put("onlFormPageList", pageList); |
||||
dataSyncProducer.sendOrderly( |
||||
tenantId, |
||||
applicationConfig.getTenantSyncTopic(), |
||||
OnlFormHead.class.getSimpleName(), |
||||
TenantConstant.INSERT_TENANT_ONL_FORM_HEAD_COMMAND, |
||||
messageJsonData.toJSONString(), |
||||
TenantConstant.MESSAGE_QUEUE_SELECTOR_KEY |
||||
); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
private static @NotNull String getOnlFormHeadByPageIdSQL(Long pageId) { |
||||
String placeholders = pageId.toString(); |
||||
String sql = "WITH RECURSIVE form_tree AS (" + |
||||
"SELECT fh.* FROM onl_form_head fh " + |
||||
"LEFT JOIN zz_online_page p ON p.master_table_id = fh.id " + |
||||
"WHERE p.page_id IN (" + placeholders + ") " + |
||||
"UNION ALL " + |
||||
"SELECT child.* FROM onl_form_head child " + |
||||
"INNER JOIN form_tree parent ON child.sort = parent.table_name " + |
||||
"WHERE child.sort IS NOT NULL AND child.sort != ''" + |
||||
") SELECT DISTINCT * FROM form_tree"; |
||||
return sql; |
||||
} |
||||
} |
||||
@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
|
||||
package apelet.tenantsync.online.service; |
||||
|
||||
import apelet.common.datasync.base.service.BaseDataSyncConsumerService; |
||||
import apelet.common.datasync.consumer.BaseDataSyncRocketMqConsumer; |
||||
import apelet.common.generator.model.*; |
||||
import apelet.common.generator.service.IOnlCgreportHeadService; |
||||
import apelet.common.generator.service.IOnlCgreportItemService; |
||||
import apelet.common.generator.service.IOnlCgreportParamService; |
||||
import apelet.common.tenant.constant.TenantConstant; |
||||
import apelet.tenantsync.consumer.TenantSyncConsumer; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.List; |
||||
import java.util.stream.Collectors; |
||||
|
||||
@Slf4j |
||||
@Service |
||||
public class SysTenantOnlCgreporHeadServiceImpl implements BaseDataSyncConsumerService { |
||||
|
||||
@Autowired |
||||
private IOnlCgreportHeadService iOnlCgreportHeadService; |
||||
|
||||
@Autowired |
||||
private IOnlCgreportItemService iOnlCgreportItemService; |
||||
|
||||
@Autowired |
||||
private IOnlCgreportParamService iOnlCgreportParamService; |
||||
|
||||
@Override |
||||
public void doHandle(String transId, String messageCommand, JSONObject messageJsonData) { |
||||
if (StrUtil.equals(messageCommand, TenantConstant.INSERT_TENANT_ONL_FORM_HEAD_COMMAND)) { |
||||
List<OnlCgreportHead> onlCgreportHeadList = messageJsonData.getJSONArray("onlCgreportHeadList").toJavaList(OnlCgreportHead.class); |
||||
List<OnlCgreportItem> onlCgreportItemList = messageJsonData.getJSONArray("onlCgreportItemList").toJavaList(OnlCgreportItem.class); |
||||
List<OnlCgreportParam> onlCgreportParamList = messageJsonData.getJSONArray("onlCgreportParamList").toJavaList(OnlCgreportParam.class); |
||||
//对需要同步的查询数据源数据进行多租户处理
|
||||
onlCgreportHeadList.stream().map(onlCgreportHead -> { |
||||
onlCgreportHead.setTenantId(transId); |
||||
return onlCgreportHead; |
||||
}).collect(Collectors.toList()); |
||||
for (OnlCgreportHead onlCgreportHead : onlCgreportHeadList) { |
||||
//判断对应租户是否已经存在相同名称的查询数据源
|
||||
OnlCgreportHead one = iOnlCgreportHeadService.getOne(new LambdaQueryWrapper<OnlCgreportHead>() |
||||
.eq(OnlCgreportHead::getCode, onlCgreportHead.getCode()) |
||||
.eq(OnlCgreportHead::getTenantId, transId)); |
||||
if (one == null) { |
||||
// 把onlCgreportItemList、onlCgreportParamList中headId和onlCgreportHead的id匹配的数据过滤出来
|
||||
List<OnlCgreportItem> matchedItemList = onlCgreportItemList.stream() |
||||
.filter(item -> item.getCgrheadId() != null && item.getCgrheadId().equals(onlCgreportHead.getId())) |
||||
.collect(Collectors.toList()); |
||||
List<OnlCgreportParam> matchedParamList = onlCgreportParamList.stream() |
||||
.filter(param -> param.getCgrheadId() != null && param.getCgrheadId().equals(onlCgreportHead.getId())) |
||||
.collect(Collectors.toList()); |
||||
// 把onlFormHead保存进数据库,并获取保存后的id
|
||||
onlCgreportHead.setId(null); |
||||
iOnlCgreportHeadService.save(onlCgreportHead); |
||||
String newId = onlCgreportHead.getId(); |
||||
// 将获取到的onlCgreportHead的id赋值到过滤匹配出来的数据的cgrheadId上,并将数据保存进数据库
|
||||
for (OnlCgreportItem onlCgreportItem : matchedItemList) { |
||||
onlCgreportItem.setId(null); |
||||
onlCgreportItem.setCgrheadId(newId); |
||||
} |
||||
iOnlCgreportItemService.saveBatch(matchedItemList); |
||||
for (OnlCgreportParam onlCgreportParam : matchedParamList) { |
||||
onlCgreportParam.setId(null); |
||||
onlCgreportParam.setCgrheadId(newId); |
||||
} |
||||
iOnlCgreportParamService.saveBatch(matchedParamList); |
||||
} |
||||
} |
||||
} else if (StrUtil.equals(messageCommand, TenantConstant.DELETE_TENANT_ONL_FORM_HEAD_COMMAND)) { |
||||
|
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String handleMessageType() { |
||||
return OnlCgreportHead.class.getSimpleName(); |
||||
} |
||||
|
||||
/** |
||||
* 返回关联的RocketMQ消费者服务。 |
||||
* 该服务的实现类,将只是处理来自该消费者的消息。 |
||||
* |
||||
* @return 关联的RocketMQ消费者服务。 |
||||
*/ |
||||
@Override |
||||
public Class<? extends BaseDataSyncRocketMqConsumer> handleConsumerClass() { |
||||
return TenantSyncConsumer.class; |
||||
} |
||||
} |
||||
@ -0,0 +1,139 @@
@@ -0,0 +1,139 @@
|
||||
package apelet.tenantsync.online.service; |
||||
|
||||
import apelet.common.datasync.base.service.BaseDataSyncConsumerService; |
||||
import apelet.common.datasync.consumer.BaseDataSyncRocketMqConsumer; |
||||
import apelet.common.generator.model.*; |
||||
import apelet.common.generator.service.*; |
||||
import apelet.common.tenant.constant.TenantConstant; |
||||
import apelet.tenantsync.consumer.TenantSyncConsumer; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
import org.w3c.dom.stylesheets.LinkStyle; |
||||
|
||||
import java.util.List; |
||||
import java.util.stream.Collectors; |
||||
|
||||
@Slf4j |
||||
@Service |
||||
public class SysTenantOnlFormHeadServiceImpl implements BaseDataSyncConsumerService { |
||||
|
||||
@Autowired |
||||
private IOnlFormHeadService onlFormHeadService; |
||||
@Autowired |
||||
private IOnlFormFindService onlFormFindService; |
||||
@Autowired |
||||
private IOnlFormFieldService onlFormFieldService; |
||||
@Autowired |
||||
private IOnlFormCheckService onlFormCheckService; |
||||
@Autowired |
||||
private IOnlFormIndexService onlFormIndexService; |
||||
@Autowired |
||||
private IOnlFormKeyService onlFormKeyService; |
||||
@Autowired |
||||
private IOnlFormPageService onlFormPageService; |
||||
|
||||
@Override |
||||
public void doHandle(String transId, String messageCommand, JSONObject messageJsonData) { |
||||
if (StrUtil.equals(messageCommand, TenantConstant.INSERT_TENANT_ONL_FORM_HEAD_COMMAND)) { |
||||
List<OnlFormHead> onlFormHeadList = messageJsonData.getJSONArray("onlFormHeadList").toJavaList(OnlFormHead.class); |
||||
List<OnlFormFind> onlFormFindList = messageJsonData.getJSONArray("onlFormFindList").toJavaList(OnlFormFind.class); |
||||
List<OnlFormField> onlFormFieldList = messageJsonData.getJSONArray("onlFormFieldList").toJavaList(OnlFormField.class); |
||||
List<OnlFormCheck> onlFormCheckList = messageJsonData.getJSONArray("onlFormCheckList").toJavaList(OnlFormCheck.class); |
||||
List<OnlFormIndex> onlFormIndexList = messageJsonData.getJSONArray("onlFormIndexList").toJavaList(OnlFormIndex.class); |
||||
List<OnlFormKey> onlFormKeyList = messageJsonData.getJSONArray("onlFormKeyList").toJavaList(OnlFormKey.class); |
||||
List<OnlFormPage> onlFormPageList = messageJsonData.getJSONArray("onlFormPageList").toJavaList(OnlFormPage.class); |
||||
//对需要同步的表单元数据进行多租户处理
|
||||
onlFormHeadList.stream().map(onlFormHead -> { |
||||
onlFormHead.setTenantId(transId); |
||||
onlFormHead.setTableName(onlFormHead.getTableName() + "_" + transId); |
||||
return onlFormHead; |
||||
}).collect(Collectors.toList()); |
||||
for (OnlFormHead onlFormHead : onlFormHeadList) { |
||||
//判断对应租户是否已经存在相同名称的表单元数据
|
||||
OnlFormHead one = onlFormHeadService.getOne(new LambdaQueryWrapper<OnlFormHead>() |
||||
.eq(OnlFormHead::getTableName, onlFormHead.getTableName())); |
||||
if (one == null) { |
||||
// 把onlFormFindList、onlFormFieldList、onlFormCheckList、onlFormIndexList、onlFormKeyList、onlFormPageList
|
||||
// 中headId和onlFormHead的id匹配的数据过滤出来
|
||||
List<OnlFormFind> matchedFindList = onlFormFindList.stream() |
||||
.filter(find -> find.getHeadId() != null && find.getHeadId().equals(onlFormHead.getId())) |
||||
.collect(Collectors.toList()); |
||||
List<OnlFormField> matchedFieldList = onlFormFieldList.stream() |
||||
.filter(field -> field.getHeadId() != null && field.getHeadId().equals(onlFormHead.getId())) |
||||
.collect(Collectors.toList()); |
||||
List<OnlFormCheck> matchedCheckList = onlFormCheckList.stream() |
||||
.filter(check -> check.getHeadId() != null && check.getHeadId().equals(onlFormHead.getId())) |
||||
.collect(Collectors.toList()); |
||||
List<OnlFormIndex> matchedIndexList = onlFormIndexList.stream() |
||||
.filter(index -> index.getHeadId() != null && index.getHeadId().equals(onlFormHead.getId())) |
||||
.collect(Collectors.toList()); |
||||
List<OnlFormKey> matchedKeyList = onlFormKeyList.stream() |
||||
.filter(key -> key.getHeadId() != null && key.getHeadId().equals(onlFormHead.getId())) |
||||
.collect(Collectors.toList()); |
||||
List<OnlFormPage> matchedPageList = onlFormPageList.stream() |
||||
.filter(page -> page.getHeadId() != null && page.getHeadId().equals(onlFormHead.getId())) |
||||
.collect(Collectors.toList()); |
||||
// 把onlFormHead保存进数据库,并获取保存后的id
|
||||
onlFormHead.setId(null); |
||||
onlFormHeadService.save(onlFormHead); |
||||
Long newId = onlFormHead.getId(); |
||||
// 将获取到的onlFormHead的id赋值到过滤匹配出来的数据的headId上,并将数据保存进数据库
|
||||
for (OnlFormFind onlFormFind : matchedFindList) { |
||||
onlFormFind.setId(null); |
||||
onlFormFind.setHeadId(newId); |
||||
} |
||||
onlFormFindService.saveBatch(matchedFindList); |
||||
for (OnlFormField onlFormField : matchedFieldList) { |
||||
onlFormField.setId(null); |
||||
onlFormField.setHeadId(newId); |
||||
} |
||||
onlFormFieldService.saveBatch(matchedFieldList); |
||||
for (OnlFormCheck onlFormCheck : matchedCheckList) { |
||||
onlFormCheck.setId(null); |
||||
onlFormCheck.setHeadId(newId); |
||||
} |
||||
onlFormCheckService.saveBatch(matchedCheckList); |
||||
for (OnlFormIndex onlFormIndex : matchedIndexList) { |
||||
onlFormIndex.setId(null); |
||||
onlFormIndex.setHeadId(newId); |
||||
} |
||||
onlFormIndexService.saveBatch(matchedIndexList); |
||||
for (OnlFormKey onlFormKey : matchedKeyList) { |
||||
onlFormKey.setId(null); |
||||
onlFormKey.setHeadId(newId); |
||||
} |
||||
onlFormKeyService.saveBatch(matchedKeyList); |
||||
for (OnlFormPage onlFormPage : matchedPageList) { |
||||
onlFormPage.setId(null); |
||||
onlFormPage.setHeadId(newId); |
||||
} |
||||
onlFormPageService.saveBatch(matchedPageList); |
||||
//表单元数据同步
|
||||
onlFormHeadService.syncDB(onlFormHead, 0L); |
||||
} |
||||
} |
||||
} else if (StrUtil.equals(messageCommand, TenantConstant.DELETE_TENANT_ONL_FORM_HEAD_COMMAND)) { |
||||
|
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String handleMessageType() { |
||||
return OnlFormHead.class.getSimpleName(); |
||||
} |
||||
|
||||
/** |
||||
* 返回关联的RocketMQ消费者服务。 |
||||
* 该服务的实现类,将只是处理来自该消费者的消息。 |
||||
* |
||||
* @return 关联的RocketMQ消费者服务。 |
||||
*/ |
||||
@Override |
||||
public Class<? extends BaseDataSyncRocketMqConsumer> handleConsumerClass() { |
||||
return TenantSyncConsumer.class; |
||||
} |
||||
} |
||||
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
package apelet.tenantsync.online.service; |
||||
|
||||
import apelet.common.datasync.base.service.BaseDataSyncConsumerService; |
||||
import apelet.common.datasync.consumer.BaseDataSyncRocketMqConsumer; |
||||
import apelet.common.online.dao.OnlineFormDatasourceMapper; |
||||
import apelet.common.online.dao.OnlineFormMapper; |
||||
import apelet.common.online.model.OnlineForm; |
||||
import apelet.common.online.model.OnlineFormDatasource; |
||||
import apelet.common.sequence.wrapper.IdGeneratorWrapper; |
||||
import apelet.common.tenant.constant.TenantConstant; |
||||
import apelet.tenantsync.consumer.TenantSyncConsumer; |
||||
import apelet.tenantsync.online.utils.FormWidgetMergeUtils; |
||||
import cn.hutool.core.collection.CollUtil; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import com.alibaba.fastjson.JSON; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.List; |
||||
|
||||
@Slf4j |
||||
@Service |
||||
public class SysTenantOnlineFormUpdateServiceImpl implements BaseDataSyncConsumerService { |
||||
|
||||
@Autowired |
||||
private IdGeneratorWrapper idGenerator; |
||||
@Autowired |
||||
private OnlineFormMapper onlineFormMapper; |
||||
@Autowired |
||||
private OnlineFormDatasourceMapper onlineFormDatasourceMapper; |
||||
|
||||
@Override |
||||
public void doHandle(String transId, String messageCommand, JSONObject messageJsonData) { |
||||
if (StrUtil.equals(messageCommand, TenantConstant.UPDATE_TENANT_ONLINE_FORM_COMMAND)) { |
||||
OnlineForm onlineForm = messageJsonData.getJSONObject("adminOnlineForm").toJavaObject(OnlineForm.class); |
||||
OnlineForm originalOnlineForm = messageJsonData.getJSONObject("originalOnlineForm").toJavaObject(OnlineForm.class); |
||||
List<Long> datasourceIdSet = messageJsonData.getJSONArray("datasourceIdSet").toJavaList(Long.class); |
||||
if (originalOnlineForm == null) { |
||||
originalOnlineForm = onlineForm; |
||||
} |
||||
//获取租户数据库中所有对应form_code的表单数据
|
||||
List<OnlineForm> dbOnlineFormList = onlineFormMapper.selectList(new QueryWrapper<OnlineForm>().eq("form_code", onlineForm.getFormCode())); |
||||
for (OnlineForm dbOnlineForm : dbOnlineFormList) { |
||||
//三路合并表单json
|
||||
String mergeFormJson = FormWidgetMergeUtils.threeWayMerge( |
||||
JSON.parseObject(originalOnlineForm.getWidgetJson()) |
||||
, JSON.parseObject(dbOnlineForm.getWidgetJson()) |
||||
, JSON.parseObject(onlineForm.getWidgetJson())).toJSONString(); |
||||
dbOnlineForm.setWidgetJson(mergeFormJson); |
||||
//更新租户数据库中的表单json数据
|
||||
onlineFormMapper.update(dbOnlineForm, new QueryWrapper<OnlineForm>().eq("form_id", dbOnlineForm.getFormId())); |
||||
} |
||||
//按照同步过来的datasourceIdSet更新数据
|
||||
OnlineFormDatasource formDatasourceFilter = new OnlineFormDatasource(); |
||||
formDatasourceFilter.setFormId(onlineForm.getFormId()); |
||||
onlineFormDatasourceMapper.delete(new QueryWrapper<>(formDatasourceFilter)); |
||||
if (CollUtil.isNotEmpty(datasourceIdSet)) { |
||||
for (Long datasourceId : datasourceIdSet) { |
||||
OnlineFormDatasource onlineFormDatasource = new OnlineFormDatasource(); |
||||
onlineFormDatasource.setId(idGenerator.nextLongId()); |
||||
onlineFormDatasource.setFormId(onlineForm.getFormId()); |
||||
onlineFormDatasource.setDatasourceId(datasourceId); |
||||
onlineFormDatasourceMapper.insert(onlineFormDatasource); |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String handleMessageType() { |
||||
return OnlineForm.class.getSimpleName(); |
||||
} |
||||
|
||||
/** |
||||
* 返回关联的RocketMQ消费者服务。 |
||||
* 该服务的实现类,将只是处理来自该消费者的消息。 |
||||
* |
||||
* @return 关联的RocketMQ消费者服务。 |
||||
*/ |
||||
@Override |
||||
public Class<? extends BaseDataSyncRocketMqConsumer> handleConsumerClass() { |
||||
return TenantSyncConsumer.class; |
||||
} |
||||
} |
||||
@ -0,0 +1,909 @@
@@ -0,0 +1,909 @@
|
||||
package apelet.tenantsync.online.utils; |
||||
|
||||
import com.alibaba.fastjson.JSON; |
||||
import com.alibaba.fastjson.JSONArray; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
|
||||
import java.io.InputStream; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.ArrayList; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* form_widget.json 深度合并工具 |
||||
* <p> |
||||
* 合并策略: |
||||
* <ul> |
||||
* <li>对象(JSONObject):递归深度合并,source 覆盖 target 的同名键</li> |
||||
* <li>数组(JSONArray):按业务键去重合并,具体逻辑由数组合并策略决定</li> |
||||
* <li>标量值:source 直接覆盖 target</li> |
||||
* </ul> |
||||
* <p> |
||||
*/ |
||||
public class FormWidgetMergeUtils { |
||||
|
||||
/** |
||||
* 数组合并策略枚举 |
||||
*/ |
||||
public enum ArrayMergeStrategy { |
||||
/** 按业务键去重合并:相同键的元素由 source 覆盖 target,新增元素追加 */ |
||||
MERGE_BY_KEY, |
||||
/** 直接追加:source 数组元素全部追加到 target 数组末尾 */ |
||||
APPEND |
||||
} |
||||
|
||||
/** |
||||
* 数组路径 → 业务键 映射配置 |
||||
* <p> |
||||
* key 为 JSON 路径中数组字段的名称,value 为该数组中元素的业务主键字段名 |
||||
* 例如 "operationList" → "type" 表示 operationList 数组按 type 字段去重 |
||||
*/ |
||||
private static final Map<String, String> ARRAY_BUSINESS_KEY_MAP = new LinkedHashMap<>(); |
||||
|
||||
/** |
||||
* 数组路径 → 合并策略 映射配置 |
||||
* <p> |
||||
* 未在此映射中配置的数组字段,默认使用 APPEND 策略 |
||||
*/ |
||||
private static final Map<String, ArrayMergeStrategy> ARRAY_STRATEGY_MAP = new LinkedHashMap<>(); |
||||
|
||||
static { |
||||
// ====== 数组业务键配置 ======
|
||||
// operationList 按 type 去重(如两个JSON都有 type=0 的"新建"按钮,则后者覆盖前者)
|
||||
ARRAY_BUSINESS_KEY_MAP.put("operationList", "type"); |
||||
// childWidgetList 按 variableName 去重(如两个JSON都有 billno 字段组件)
|
||||
ARRAY_BUSINESS_KEY_MAP.put("childWidgetList", "variableName"); |
||||
// pluginList 按 pluginType 去重
|
||||
ARRAY_BUSINESS_KEY_MAP.put("pluginList", "pluginType"); |
||||
// tableColumnList 按 columnFieldName 去重
|
||||
ARRAY_BUSINESS_KEY_MAP.put("tableColumnList", "id"); |
||||
// widgetList 按 variableName 去重
|
||||
ARRAY_BUSINESS_KEY_MAP.put("widgetList", "variableName"); |
||||
// otherWidgetList 按 variableName 去重
|
||||
ARRAY_BUSINESS_KEY_MAP.put("otherWidgetList", "variableName"); |
||||
|
||||
// ARRAY_BUSINESS_KEY_MAP.put("formEventList", "variableName");
|
||||
// ARRAY_BUSINESS_KEY_MAP.put("eventList", "variableName");
|
||||
// ARRAY_BUSINESS_KEY_MAP.put("paramList", "variableName");
|
||||
// ARRAY_BUSINESS_KEY_MAP.put("printParamList", "variableName");
|
||||
// ARRAY_BUSINESS_KEY_MAP.put("customFieldList", "variableName");
|
||||
// ARRAY_BUSINESS_KEY_MAP.put("exportColumnList", "variableName");
|
||||
|
||||
|
||||
|
||||
// ====== 数组合并策略配置 ======
|
||||
// 以上配置了业务键的数组默认使用 MERGE_BY_KEY 策略
|
||||
ARRAY_STRATEGY_MAP.put("operationList", ArrayMergeStrategy.MERGE_BY_KEY); |
||||
ARRAY_STRATEGY_MAP.put("childWidgetList", ArrayMergeStrategy.MERGE_BY_KEY); |
||||
ARRAY_STRATEGY_MAP.put("pluginList", ArrayMergeStrategy.MERGE_BY_KEY); |
||||
ARRAY_STRATEGY_MAP.put("tableColumnList", ArrayMergeStrategy.MERGE_BY_KEY); |
||||
ARRAY_STRATEGY_MAP.put("widgetList", ArrayMergeStrategy.MERGE_BY_KEY); |
||||
ARRAY_STRATEGY_MAP.put("otherWidgetList", ArrayMergeStrategy.MERGE_BY_KEY); |
||||
} |
||||
|
||||
private FormWidgetMergeUtils() { |
||||
} |
||||
|
||||
/** |
||||
* 三路合并:在保留 base→branch1 修改的基础上,合并 base→branch2 的修改 |
||||
* <p> |
||||
* 合并逻辑: |
||||
* 1. 计算 branch1 相对于 base 的差异(diff1) |
||||
* 2. 计算 branch2 相对于 base 的差异(diff2) |
||||
* 3. 将 diff1 应用到 base 上 |
||||
* 4. 将 diff2 应用到结果上(不覆盖 diff1 已修改的部分) |
||||
* |
||||
* @param base 基础版本JSON(如 1.0) |
||||
* @param branch1 第一个分支JSON(如 1.5) |
||||
* @param branch2 第二个分支JSON(如 2.0) |
||||
* @return 三路合并后的JSON对象 |
||||
*/ |
||||
public static JSONObject threeWayMerge(JSONObject base, JSONObject branch1, JSONObject branch2) { |
||||
if (base == null) { |
||||
// 如果没有基础版本,直接合并 branch1 和 branch2, 以 branch1 为准
|
||||
return merge(branch2, branch1); |
||||
} |
||||
if (branch1 == null && branch2 == null) { |
||||
return deepCopy(base); |
||||
} |
||||
if (branch1 == null) { |
||||
return deepCopy(branch2); |
||||
} |
||||
if (branch2 == null) { |
||||
return deepCopy(branch1); |
||||
} |
||||
|
||||
// 步骤1:计算 branch1 相对于 base 的差异
|
||||
JSONObject diff1 = computeDiff(base, branch1, ""); |
||||
|
||||
// 步骤2:计算 branch2 相对于 base 的差异
|
||||
JSONObject diff2 = computeDiff(base, branch2, ""); |
||||
|
||||
// 步骤3:先将 branch1 的差异应用到 base
|
||||
JSONObject result = deepCopy(base); |
||||
deepMergeObject(result, diff1, ""); |
||||
|
||||
// 步骤4:将 branch2 的差异应用到结果,但不覆盖 diff1 已修改的部分
|
||||
applyDiffWithoutOverride(result, diff2, diff1, ""); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 计算 source 相对于 base 的差异 |
||||
* <p> |
||||
* 返回一个包含所有新增和修改字段的JSONObject,删除的字段也会被标记 |
||||
* |
||||
* @param base 基础版本 |
||||
* @param source 目标版本 |
||||
* @param path 当前路径 |
||||
* @return 差异对象 |
||||
*/ |
||||
private static JSONObject computeDiff(JSONObject base, JSONObject source, String path) { |
||||
JSONObject diff = new JSONObject(); |
||||
|
||||
// 检查 source 中新增或修改的字段
|
||||
for (Map.Entry<String, Object> entry : source.entrySet()) { |
||||
String key = entry.getKey(); |
||||
Object sourceValue = entry.getValue(); |
||||
String currentPath = buildPath(path, key); |
||||
|
||||
if (!base.containsKey(key)) { |
||||
// 新增字段
|
||||
diff.put(key, deepCopyValue(sourceValue)); |
||||
} else { |
||||
// 字段已存在,检查是否有变化
|
||||
Object baseValue = base.get(key); |
||||
if (!valuesEqual(baseValue, sourceValue)) { |
||||
// 值发生变化
|
||||
if (baseValue instanceof JSONObject && sourceValue instanceof JSONObject) { |
||||
// 对象类型,递归计算差异
|
||||
JSONObject subDiff = computeDiff((JSONObject) baseValue, (JSONObject) sourceValue, currentPath); |
||||
if (!subDiff.isEmpty()) { |
||||
diff.put(key, subDiff); |
||||
} |
||||
} else if (baseValue instanceof JSONArray && sourceValue instanceof JSONArray) { |
||||
// 数组类型,计算数组差异
|
||||
JSONArray arrayDiff = computeArrayDiff((JSONArray) baseValue, (JSONArray) sourceValue, currentPath); |
||||
if (arrayDiff != null && !arrayDiff.isEmpty()) { |
||||
diff.put(key, arrayDiff); |
||||
} |
||||
} else { |
||||
// 标量值变化,直接记录
|
||||
diff.put(key, deepCopyValue(sourceValue)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 检查 base 中有但 source 中删除的字段
|
||||
for (String key : base.keySet()) { |
||||
if (!source.containsKey(key)) { |
||||
// 使用特殊标记表示删除(使用 null 值)
|
||||
diff.put(key, null); |
||||
} |
||||
} |
||||
|
||||
return diff; |
||||
} |
||||
|
||||
/** |
||||
* 计算两个数组的差异 |
||||
* |
||||
* @param baseArray 基础数组 |
||||
* @param sourceArray 目标数组 |
||||
* @param currentPath 当前路径 |
||||
* @return 差异数组,如果无差异返回null |
||||
*/ |
||||
private static JSONArray computeArrayDiff(JSONArray baseArray, JSONArray sourceArray, String currentPath) { |
||||
String arrayFieldName = extractFieldName(currentPath); |
||||
ArrayMergeStrategy strategy = ARRAY_STRATEGY_MAP.getOrDefault(arrayFieldName, ArrayMergeStrategy.APPEND); |
||||
|
||||
if (strategy == ArrayMergeStrategy.MERGE_BY_KEY) { |
||||
return computeArrayDiffByKey(baseArray, sourceArray, arrayFieldName); |
||||
} else { |
||||
// 对于 APPEND 和 REPLACE 策略,如果数组不同则返回整个 source 数组
|
||||
if (!arraysEqual(baseArray, sourceArray)) { |
||||
return deepCopy(sourceArray); |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 按业务键计算数组差异 |
||||
*/ |
||||
private static JSONArray computeArrayDiffByKey(JSONArray baseArray, JSONArray sourceArray, String arrayFieldName) { |
||||
String businessKey = ARRAY_BUSINESS_KEY_MAP.get(arrayFieldName); |
||||
if (businessKey == null) { |
||||
if (!arraysEqual(baseArray, sourceArray)) { |
||||
return deepCopy(sourceArray); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
JSONArray diff = new JSONArray(); |
||||
Map<String, Integer> baseKeyIndexMap = buildKeyIndexMap(baseArray, businessKey); |
||||
Map<String, Integer> sourceKeyIndexMap = buildKeyIndexMap(sourceArray, businessKey); |
||||
|
||||
// 检查 source 中新增或修改的元素
|
||||
for (Map.Entry<String, Integer> entry : sourceKeyIndexMap.entrySet()) { |
||||
String keyValue = entry.getKey(); |
||||
int sourceIndex = entry.getValue(); |
||||
Object sourceElement = sourceArray.get(sourceIndex); |
||||
|
||||
Integer baseIndex = baseKeyIndexMap.get(keyValue); |
||||
if (baseIndex == null) { |
||||
// 新增元素
|
||||
diff.add(deepCopyValue(sourceElement)); |
||||
} else { |
||||
// 元素已存在,检查是否有变化
|
||||
Object baseElement = baseArray.get(baseIndex); |
||||
if (!valuesEqual(baseElement, sourceElement)) { |
||||
if (baseElement instanceof JSONObject && sourceElement instanceof JSONObject) { |
||||
JSONObject subDiff = computeDiff((JSONObject) baseElement, (JSONObject) sourceElement, |
||||
arrayFieldName + "[" + keyValue + "]"); |
||||
if (!subDiff.isEmpty()) { |
||||
// 将差异包装成包含业务键的对象
|
||||
JSONObject wrappedDiff = new JSONObject(); |
||||
wrappedDiff.put(businessKey, keyValue); |
||||
for (Map.Entry<String, Object> diffEntry : subDiff.entrySet()) { |
||||
if (!diffEntry.getKey().equals(businessKey)) { |
||||
wrappedDiff.put(diffEntry.getKey(), diffEntry.getValue()); |
||||
} |
||||
} |
||||
diff.add(wrappedDiff); |
||||
} |
||||
} else { |
||||
// 非对象元素变化
|
||||
diff.add(deepCopyValue(sourceElement)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 检查 base 中删除的元素
|
||||
for (Map.Entry<String, Integer> entry : baseKeyIndexMap.entrySet()) { |
||||
String keyValue = entry.getKey(); |
||||
if (!sourceKeyIndexMap.containsKey(keyValue)) { |
||||
// 删除的元素,用只包含业务键的对象标记
|
||||
JSONObject deleteMarker = new JSONObject(); |
||||
deleteMarker.put(businessKey, keyValue); |
||||
deleteMarker.put("__deleted__", true); |
||||
diff.add(deleteMarker); |
||||
} |
||||
} |
||||
|
||||
return diff.isEmpty() ? null : diff; |
||||
} |
||||
|
||||
/** |
||||
* 应用差异但不覆盖已修改的部分 |
||||
* <p> |
||||
* 这是三路合并的核心逻辑:确保 branch2 的差异不会覆盖 branch1 已修改的内容 |
||||
* |
||||
* @param target 目标对象(会被修改) |
||||
* @param diff 要应用的差异 |
||||
* @param diff1 branch1 的差异(用于检测冲突) |
||||
* @param path 当前路径 |
||||
*/ |
||||
private static void applyDiffWithoutOverride(JSONObject target, JSONObject diff, JSONObject diff1, String path) { |
||||
for (Map.Entry<String, Object> entry : diff.entrySet()) { |
||||
String key = entry.getKey(); |
||||
Object diffValue = entry.getValue(); |
||||
String currentPath = buildPath(path, key); |
||||
|
||||
// 检查这个字段是否在 diff1 中也被修改了(冲突检测)
|
||||
boolean conflictInDiff1 = diff1 != null && diff1.containsKey(key); |
||||
|
||||
if (diffValue == null) { |
||||
// 删除操作:仅在 diff1 中没有修改此字段时才执行删除
|
||||
if (!conflictInDiff1) { |
||||
target.remove(key); |
||||
} |
||||
// 如果 diff1 也修改了此字段,保留 diff1 的修改
|
||||
} else if (diffValue instanceof JSONObject) { |
||||
// 对象差异
|
||||
if (!target.containsKey(key)) { |
||||
target.put(key, deepCopyValue(diffValue)); |
||||
} else { |
||||
Object targetValue = target.get(key); |
||||
if (targetValue instanceof JSONObject) { |
||||
JSONObject targetObj = (JSONObject) targetValue; |
||||
JSONObject diff1Sub = conflictInDiff1 && diff1.get(key) instanceof JSONObject |
||||
? (JSONObject) diff1.get(key) : null; |
||||
applyDiffWithoutOverride(targetObj, (JSONObject) diffValue, diff1Sub, currentPath); |
||||
} else { |
||||
// 类型不匹配,直接覆盖
|
||||
target.put(key, deepCopyValue(diffValue)); |
||||
} |
||||
} |
||||
} else if (diffValue instanceof JSONArray) { |
||||
// 数组差异
|
||||
if (!target.containsKey(key)) { |
||||
target.put(key, deepCopyValue(diffValue)); |
||||
} else { |
||||
Object targetValue = target.get(key); |
||||
if (targetValue instanceof JSONArray) { |
||||
// 合并数组差异
|
||||
JSONArray mergedArray = mergeArrayDiff((JSONArray) targetValue, (JSONArray) diffValue, |
||||
conflictInDiff1 && diff1.get(key) instanceof JSONArray |
||||
? (JSONArray) diff1.get(key) : null, |
||||
currentPath); |
||||
target.put(key, mergedArray); |
||||
} else { |
||||
// 类型不匹配,直接覆盖
|
||||
target.put(key, deepCopyValue(diffValue)); |
||||
} |
||||
} |
||||
} else { |
||||
// 标量值差异:仅在 diff1 中没有修改此字段时才应用
|
||||
if (!conflictInDiff1) { |
||||
target.put(key, deepCopyValue(diffValue)); |
||||
} |
||||
// 如果 diff1 也修改了此字段,保留 diff1 的修改(不覆盖)
|
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 合并数组差异 |
||||
*/ |
||||
private static JSONArray mergeArrayDiff(JSONArray targetArray, JSONArray diff, JSONArray diff1, String currentPath) { |
||||
String arrayFieldName = extractFieldName(currentPath); |
||||
ArrayMergeStrategy strategy = ARRAY_STRATEGY_MAP.getOrDefault(arrayFieldName, ArrayMergeStrategy.APPEND); |
||||
|
||||
if (strategy == ArrayMergeStrategy.MERGE_BY_KEY) { |
||||
return mergeArrayDiffByKey(targetArray, diff, diff1, arrayFieldName); |
||||
} else { |
||||
// 对于简单策略,直接应用 diff
|
||||
JSONArray result = deepCopy(targetArray); |
||||
result.addAll(deepCopy(diff)); |
||||
return result; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 按业务键合并数组差异 |
||||
*/ |
||||
private static JSONArray mergeArrayDiffByKey(JSONArray targetArray, JSONArray diff, JSONArray diff1, |
||||
String arrayFieldName) { |
||||
String businessKey = ARRAY_BUSINESS_KEY_MAP.get(arrayFieldName); |
||||
if (businessKey == null) { |
||||
JSONArray result = deepCopy(targetArray); |
||||
result.addAll(deepCopy(diff)); |
||||
return result; |
||||
} |
||||
|
||||
JSONArray result = deepCopy(targetArray); |
||||
Map<String, Integer> targetKeyIndexMap = buildKeyIndexMap(result, businessKey); |
||||
// 构建 diff1 的业务键到元素的映射(而不是索引映射)
|
||||
Map<String, JSONObject> diff1ElementMap = diff1 != null ? buildKeyElementMap(diff1, businessKey) : null; |
||||
|
||||
for (int i = 0; i < diff.size(); i++) { |
||||
Object diffElement = diff.get(i); |
||||
|
||||
if (!(diffElement instanceof JSONObject)) { |
||||
result.add(deepCopyValue(diffElement)); |
||||
continue; |
||||
} |
||||
|
||||
JSONObject diffObj = (JSONObject) diffElement; |
||||
|
||||
// 检查是否是删除标记
|
||||
if (Boolean.TRUE.equals(diffObj.getBoolean("__deleted__"))) { |
||||
String keyValue = extractBusinessKeyValue(diffObj, businessKey); |
||||
if (keyValue != null) { |
||||
// 仅在 diff1 中没有修改此元素时才执行删除
|
||||
if (diff1ElementMap == null || !diff1ElementMap.containsKey(keyValue)) { |
||||
Integer targetIndex = targetKeyIndexMap.get(keyValue); |
||||
if (targetIndex != null) { |
||||
result.remove(targetIndex.intValue()); |
||||
// 重建索引映射
|
||||
targetKeyIndexMap = buildKeyIndexMap(result, businessKey); |
||||
} |
||||
} |
||||
} |
||||
continue; |
||||
} |
||||
|
||||
String keyValue = extractBusinessKeyValue(diffObj, businessKey); |
||||
if (keyValue == null) { |
||||
result.add(deepCopyValue(diffElement)); |
||||
continue; |
||||
} |
||||
|
||||
Integer targetIndex = targetKeyIndexMap.get(keyValue); |
||||
if (targetIndex != null) { |
||||
// 元素已存在,递归合并
|
||||
Object targetElement = result.get(targetIndex); |
||||
if (targetElement instanceof JSONObject) { |
||||
JSONObject targetObj = (JSONObject) targetElement; |
||||
// 检查 diff1 中是否也修改了此元素(获取完整的元素对象)
|
||||
JSONObject diff1Element = diff1ElementMap != null ? diff1ElementMap.get(keyValue) : null; |
||||
applyDiffWithoutOverride(targetObj, diffObj, diff1Element, arrayFieldName + "[" + keyValue + "]"); |
||||
} |
||||
} else { |
||||
// 新增元素
|
||||
result.add(deepCopyValue(diffElement)); |
||||
targetKeyIndexMap.put(keyValue, result.size() - 1); |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 构建业务键到元素对象的映射(用于三路合并冲突检测) |
||||
*/ |
||||
private static Map<String, JSONObject> buildKeyElementMap(JSONArray array, String businessKey) { |
||||
Map<String, JSONObject> map = new LinkedHashMap<>(); |
||||
for (int i = 0; i < array.size(); i++) { |
||||
Object element = array.get(i); |
||||
if (!(element instanceof JSONObject)) { |
||||
continue; |
||||
} |
||||
JSONObject obj = (JSONObject) element; |
||||
String keyValue = extractBusinessKeyValue(obj, businessKey); |
||||
if (keyValue != null) { |
||||
map.put(keyValue, obj); |
||||
} |
||||
} |
||||
return map; |
||||
} |
||||
|
||||
/** |
||||
* 比较两个值是否相等 |
||||
*/ |
||||
private static boolean valuesEqual(Object value1, Object value2) { |
||||
if (value1 == null && value2 == null) { |
||||
return true; |
||||
} |
||||
if (value1 == null || value2 == null) { |
||||
return false; |
||||
} |
||||
if (value1 instanceof JSONObject && value2 instanceof JSONObject) { |
||||
return ((JSONObject) value1).toJSONString().equals(((JSONObject) value2).toJSONString()); |
||||
} |
||||
if (value1 instanceof JSONArray && value2 instanceof JSONArray) { |
||||
return ((JSONArray) value1).toJSONString().equals(((JSONArray) value2).toJSONString()); |
||||
} |
||||
return value1.equals(value2); |
||||
} |
||||
|
||||
/** |
||||
* 比较两个数组是否相等 |
||||
*/ |
||||
private static boolean arraysEqual(JSONArray arr1, JSONArray arr2) { |
||||
if (arr1 == null && arr2 == null) { |
||||
return true; |
||||
} |
||||
if (arr1 == null || arr2 == null) { |
||||
return false; |
||||
} |
||||
return arr1.toJSONString().equals(arr2.toJSONString()); |
||||
} |
||||
|
||||
// ==================== 公开入口方法 ====================
|
||||
|
||||
/** |
||||
* 合并两个 form_widget JSON 对象(source 覆盖 target) |
||||
* |
||||
* @param target 基础JSON(被覆盖方) |
||||
* @param source 覆盖JSON(覆盖方) |
||||
* @return 合并后的JSON对象(新对象,不修改入参) |
||||
*/ |
||||
public static JSONObject merge(JSONObject target, JSONObject source) { |
||||
if (target == null) { |
||||
return source != null ? deepCopy(source) : new JSONObject(); |
||||
} |
||||
if (source == null) { |
||||
return deepCopy(target); |
||||
} |
||||
JSONObject result = deepCopy(target); |
||||
deepMergeObject(result, source, ""); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 从 InputStream 读取并合并两个 JSON |
||||
* |
||||
* @param baseStream 基础JSON输入流 |
||||
* @param overrideStream 覆盖JSON输入流 |
||||
* @return 合并后的JSON对象 |
||||
*/ |
||||
public static JSONObject merge(InputStream baseStream, InputStream overrideStream) { |
||||
JSONObject target = readJsonFromStream(baseStream); |
||||
JSONObject source = readJsonFromStream(overrideStream); |
||||
return merge(target, source); |
||||
} |
||||
|
||||
/** |
||||
* 合并两个 JSON 字符串 |
||||
* |
||||
* @param baseJson 基础JSON字符串 |
||||
* @param overrideJson 覆盖JSON字符串 |
||||
* @return 合并后的JSON对象 |
||||
*/ |
||||
public static JSONObject merge(String baseJson, String overrideJson) { |
||||
JSONObject target = JSON.parseObject(baseJson); |
||||
JSONObject source = JSON.parseObject(overrideJson); |
||||
return merge(target, source); |
||||
} |
||||
|
||||
/** |
||||
* 仅合并指定端(pc 或 mobile)的配置 |
||||
* |
||||
* @param target 基础JSON |
||||
* @param source 覆盖JSON |
||||
* @param endpoint 端标识:"pc" 或 "mobile" |
||||
* @return 合并后的JSON对象 |
||||
*/ |
||||
public static JSONObject mergeEndpoint(JSONObject target, JSONObject source, String endpoint) { |
||||
if (endpoint == null || (!"pc".equals(endpoint) && !"mobile".equals(endpoint))) { |
||||
throw new IllegalArgumentException("endpoint 必须为 'pc' 或 'mobile'"); |
||||
} |
||||
JSONObject result = deepCopy(target); |
||||
if (!source.containsKey(endpoint)) { |
||||
return result; |
||||
} |
||||
JSONObject sourceEndpoint = source.getJSONObject(endpoint); |
||||
if (sourceEndpoint == null) { |
||||
return result; |
||||
} |
||||
JSONObject targetEndpoint = result.getJSONObject(endpoint); |
||||
if (targetEndpoint == null) { |
||||
result.put(endpoint, deepCopy(sourceEndpoint)); |
||||
} else { |
||||
deepMergeObject(targetEndpoint, sourceEndpoint, endpoint); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
// ==================== 核心递归合并逻辑 ====================
|
||||
|
||||
/** |
||||
* 递归深度合并:将 source 的内容合并到 target 中(target 会被直接修改) |
||||
* |
||||
* @param target 目标对象(会被修改) |
||||
* @param source 源对象(覆盖方) |
||||
* @param path 当前JSON路径,用于定位上下文和策略选择 |
||||
*/ |
||||
private static void deepMergeObject(JSONObject target, JSONObject source, String path) { |
||||
for (Map.Entry<String, Object> entry : source.entrySet()) { |
||||
String key = entry.getKey(); |
||||
Object sourceValue = entry.getValue(); |
||||
String currentPath = buildPath(path, key); |
||||
|
||||
if (!target.containsKey(key)) { |
||||
// target 中不存在该键,直接放入(深拷贝)
|
||||
target.put(key, deepCopyValue(sourceValue)); |
||||
} else { |
||||
// target 中已存在该键,按类型决定合并方式
|
||||
Object targetValue = target.get(key); |
||||
target.put(key, mergeValues(targetValue, sourceValue, currentPath)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 合并两个值(根据类型分发到不同的合并策略) |
||||
* |
||||
* @param targetValue 目标值 |
||||
* @param sourceValue 源值 |
||||
* @param currentPath 当前路径 |
||||
* @return 合并后的值 |
||||
*/ |
||||
private static Object mergeValues(Object targetValue, Object sourceValue, String currentPath) { |
||||
// 两者都是 JSONObject → 递归合并
|
||||
if (targetValue instanceof JSONObject && sourceValue instanceof JSONObject) { |
||||
JSONObject mergedObj = deepCopy((JSONObject) targetValue); |
||||
deepMergeObject(mergedObj, (JSONObject) sourceValue, currentPath); |
||||
return mergedObj; |
||||
} |
||||
|
||||
// 两者都是 JSONArray → 按策略合并
|
||||
if (targetValue instanceof JSONArray && sourceValue instanceof JSONArray) { |
||||
return mergeArray((JSONArray) targetValue, (JSONArray) sourceValue, currentPath); |
||||
} |
||||
|
||||
// 类型不一致 或 标量值 → source 覆盖 target
|
||||
return deepCopyValue(sourceValue); |
||||
} |
||||
|
||||
// ==================== 数组合并逻辑 ====================
|
||||
|
||||
/** |
||||
* 合并两个 JSONArray(根据当前路径对应的策略决定合并方式) |
||||
* |
||||
* @param targetArray 目标数组 |
||||
* @param sourceArray 源数组 |
||||
* @param currentPath 当前JSON路径(用于匹配策略) |
||||
* @return 合并后的数组 |
||||
*/ |
||||
private static JSONArray mergeArray(JSONArray targetArray, JSONArray sourceArray, String currentPath) { |
||||
// 从路径中提取数组字段名(取最后一段)
|
||||
String arrayFieldName = extractFieldName(currentPath); |
||||
ArrayMergeStrategy strategy = ARRAY_STRATEGY_MAP.getOrDefault(arrayFieldName, ArrayMergeStrategy.APPEND); |
||||
|
||||
switch (strategy) { |
||||
case MERGE_BY_KEY: |
||||
return mergeArrayByKey(targetArray, sourceArray, arrayFieldName); |
||||
case APPEND: |
||||
return mergeArrayAppend(targetArray, sourceArray); |
||||
default: |
||||
return mergeArrayAppend(targetArray, sourceArray); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 按业务键去重合并数组 |
||||
* <p> |
||||
* 逻辑: |
||||
* 1. 从 ARRAY_BUSINESS_KEY_MAP 中获取该数组字段对应的业务主键名 |
||||
* 2. 遍历 targetArray,建立 业务键 → 索引 的映射 |
||||
* 3. 遍历 sourceArray: |
||||
* - 若业务键已存在于 target 中,递归合并对应元素 |
||||
* - 若业务键不存在,追加到结果数组末尾 |
||||
* |
||||
* @param targetArray 目标数组 |
||||
* @param sourceArray 源数组 |
||||
* @param arrayFieldName 数组字段名(用于查找业务键配置) |
||||
* @return 合并后的数组 |
||||
*/ |
||||
private static JSONArray mergeArrayByKey(JSONArray targetArray, JSONArray sourceArray, String arrayFieldName) { |
||||
String businessKey = ARRAY_BUSINESS_KEY_MAP.get(arrayFieldName); |
||||
if (businessKey == null) { |
||||
// 未配置业务键,降级为追加
|
||||
return mergeArrayAppend(targetArray, sourceArray); |
||||
} |
||||
|
||||
// 步骤1:深拷贝 targetArray 作为结果容器
|
||||
JSONArray result = deepCopy(targetArray); |
||||
|
||||
// 步骤2:构建 targetArray 的 业务键值 → 索引 映射
|
||||
// - 仅处理 JSONObject 类型的元素(跳过标量元素)
|
||||
// - 使用 String 业务键值作为 map key,支持 Number/Boolean 等类型自动转为字符串比较
|
||||
Map<String, Integer> targetKeyIndexMap = buildKeyIndexMap(result, businessKey); |
||||
|
||||
// 步骤3:遍历 sourceArray,按业务键匹配合并或追加
|
||||
for (int i = 0; i < sourceArray.size(); i++) { |
||||
Object sourceElement = sourceArray.get(i); |
||||
|
||||
// 非 JSONObject 元素:直接追加(无法按业务键匹配)
|
||||
if (!(sourceElement instanceof JSONObject)) { |
||||
result.add(deepCopyValue(sourceElement)); |
||||
continue; |
||||
} |
||||
|
||||
JSONObject sourceObj = (JSONObject) sourceElement; |
||||
String keyValue = extractBusinessKeyValue(sourceObj, businessKey); |
||||
|
||||
if (keyValue == null) { |
||||
// 源元素不含业务键字段,无法匹配,直接追加
|
||||
result.add(deepCopyValue(sourceElement)); |
||||
continue; |
||||
} |
||||
|
||||
Integer targetIndex = targetKeyIndexMap.get(keyValue); |
||||
if (targetIndex != null) { |
||||
// 业务键匹配到:递归合并 target 中对应索引的元素
|
||||
Object targetElement = result.get(targetIndex); |
||||
if (targetElement instanceof JSONObject) { |
||||
// 对象级递归合并,保留 target 中的独有字段,source 覆盖同名字段
|
||||
deepMergeObject((JSONObject) targetElement, sourceObj, |
||||
arrayFieldName + "[" + keyValue + "]"); |
||||
} else { |
||||
// target 中对应元素不是对象(理论上不会发生),直接替换
|
||||
result.set(targetIndex, deepCopyValue(sourceElement)); |
||||
} |
||||
} else { |
||||
// 业务键未匹配:追加新元素
|
||||
result.add(deepCopyValue(sourceElement)); |
||||
// 同步更新索引映射,防止 source 内部出现重复业务键时后者覆盖前者
|
||||
targetKeyIndexMap.put(keyValue, result.size() - 1); |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* 构建 JSONArray 中各 JSONObject 元素的 业务键值 → 索引 映射 |
||||
* <p> |
||||
* 仅处理 JSONObject 类型的元素;标量元素跳过。 |
||||
* 若同一业务键值出现多次,后出现的索引覆盖先前的(保留最后一个)。 |
||||
* |
||||
* @param array JSON数组 |
||||
* @param businessKey 业务主键字段名 |
||||
* @return 业务键值字符串 → 在数组中的索引位置 |
||||
*/ |
||||
private static Map<String, Integer> buildKeyIndexMap(JSONArray array, String businessKey) { |
||||
Map<String, Integer> map = new LinkedHashMap<>(); |
||||
for (int i = 0; i < array.size(); i++) { |
||||
Object element = array.get(i); |
||||
if (!(element instanceof JSONObject)) { |
||||
continue; |
||||
} |
||||
String keyValue = extractBusinessKeyValue((JSONObject) element, businessKey); |
||||
if (keyValue != null) { |
||||
map.put(keyValue, i); |
||||
} |
||||
} |
||||
return map; |
||||
} |
||||
|
||||
/** |
||||
* 提取 JSONObject 中指定业务键字段的值,统一转为字符串用于比较 |
||||
* <p> |
||||
* 支持的值类型: |
||||
* <ul> |
||||
* <li>String → 直接返回</li> |
||||
* <li>Number → 返回其字符串表示(如 1 → "1", 10 → "10")</li> |
||||
* <li>Boolean → 返回 "true" / "false"</li> |
||||
* <li>null 或字段不存在 → 返回 null</li> |
||||
* </ul> |
||||
* |
||||
* @param obj JSON对象 |
||||
* @param businessKey 业务主键字段名 |
||||
* @return 业务键值的字符串表示,字段不存在或值为null时返回null |
||||
*/ |
||||
private static String extractBusinessKeyValue(JSONObject obj, String businessKey) { |
||||
Object value = obj.get(businessKey); |
||||
if (value == null) { |
||||
return null; |
||||
} |
||||
// Number 类型需特殊处理:确保数值比较一致
|
||||
// 例如 type=1 和 type="1" 应视为相同业务键
|
||||
if (value instanceof Number) { |
||||
return String.valueOf(((Number) value).longValue()); |
||||
} |
||||
return value.toString(); |
||||
} |
||||
|
||||
/** |
||||
* 追加合并:source 数组元素全部追加到 target 数组末尾 |
||||
*/ |
||||
private static JSONArray mergeArrayAppend(JSONArray targetArray, JSONArray sourceArray) { |
||||
JSONArray result = deepCopy(targetArray); |
||||
result.addAll(deepCopy(sourceArray)); |
||||
return result; |
||||
} |
||||
|
||||
// ==================== 工具方法 ====================
|
||||
|
||||
/** |
||||
* 构建JSON路径(类似 $.pc.tableWidget.operationList) |
||||
*/ |
||||
private static String buildPath(String parentPath, String key) { |
||||
if (parentPath == null || parentPath.isEmpty()) { |
||||
return key; |
||||
} |
||||
return parentPath + "." + key; |
||||
} |
||||
|
||||
/** |
||||
* 从路径中提取数组字段名 |
||||
* 例如 "pc.tableWidget.operationList" → "operationList" |
||||
*/ |
||||
private static String extractFieldName(String path) { |
||||
if (path == null || path.isEmpty()) { |
||||
return ""; |
||||
} |
||||
int lastDot = path.lastIndexOf('.'); |
||||
return lastDot >= 0 ? path.substring(lastDot + 1) : path; |
||||
} |
||||
|
||||
/** |
||||
* 深拷贝 JSONObject |
||||
*/ |
||||
private static JSONObject deepCopy(JSONObject obj) { |
||||
if (obj == null) { |
||||
return null; |
||||
} |
||||
return JSON.parseObject(obj.toJSONString()); |
||||
} |
||||
|
||||
/** |
||||
* 深拷贝 JSONArray |
||||
*/ |
||||
private static JSONArray deepCopy(JSONArray arr) { |
||||
if (arr == null) { |
||||
return null; |
||||
} |
||||
return JSON.parseArray(arr.toJSONString()); |
||||
} |
||||
|
||||
/** |
||||
* 深拷贝任意 JSON 值 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
private static Object deepCopyValue(Object value) { |
||||
if (value == null) { |
||||
return null; |
||||
} |
||||
if (value instanceof JSONObject) { |
||||
return deepCopy((JSONObject) value); |
||||
} |
||||
if (value instanceof JSONArray) { |
||||
return deepCopy((JSONArray) value); |
||||
} |
||||
// 标量值(String, Number, Boolean)不可变,直接返回
|
||||
return value; |
||||
} |
||||
|
||||
/** |
||||
* 从输入流读取 JSON |
||||
*/ |
||||
private static JSONObject readJsonFromStream(InputStream stream) { |
||||
try { |
||||
String content = new String(readAllBytes(stream), StandardCharsets.UTF_8); |
||||
return JSON.parseObject(content); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException("读取JSON流失败", e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 读取输入流全部字节(兼容 Java 8,不使用 InputStream.readAllBytes) |
||||
*/ |
||||
private static byte[] readAllBytes(InputStream stream) throws Exception { |
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
||||
byte[] data = new byte[4096]; |
||||
int nRead; |
||||
while ((nRead = stream.read(data, 0, data.length)) != -1) { |
||||
buffer.write(data, 0, nRead); |
||||
} |
||||
return buffer.toByteArray(); |
||||
} |
||||
|
||||
/** |
||||
* 简易 ByteArrayOutputStream(避免额外依赖) |
||||
*/ |
||||
private static class ByteArrayOutputStream { |
||||
private final List<byte[]> buffers = new ArrayList<>(); |
||||
private int count = 0; |
||||
private static final int BLOCK_SIZE = 4096; |
||||
|
||||
void write(byte[] b, int off, int len) { |
||||
byte[] block = new byte[len]; |
||||
System.arraycopy(b, off, block, 0, len); |
||||
buffers.add(block); |
||||
count += len; |
||||
} |
||||
|
||||
byte[] toByteArray() { |
||||
byte[] result = new byte[count]; |
||||
int pos = 0; |
||||
for (byte[] block : buffers) { |
||||
System.arraycopy(block, 0, result, pos, block.length); |
||||
pos += block.length; |
||||
} |
||||
return result; |
||||
} |
||||
} |
||||
|
||||
// ==================== 策略配置方法(供外部动态调整) ====================
|
||||
|
||||
/** |
||||
* 注册数组的业务键配置 |
||||
* |
||||
* @param arrayFieldName 数组字段名 |
||||
* @param businessKey 业务主键字段名 |
||||
*/ |
||||
public static void registerArrayBusinessKey(String arrayFieldName, String businessKey) { |
||||
ARRAY_BUSINESS_KEY_MAP.put(arrayFieldName, businessKey); |
||||
} |
||||
|
||||
/** |
||||
* 注册数组的合并策略 |
||||
* |
||||
* @param arrayFieldName 数组字段名 |
||||
* @param strategy 合并策略 |
||||
*/ |
||||
public static void registerArrayStrategy(String arrayFieldName, ArrayMergeStrategy strategy) { |
||||
ARRAY_STRATEGY_MAP.put(arrayFieldName, strategy); |
||||
} |
||||
} |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
package apelet.common.tenant.annotation; |
||||
|
||||
import java.lang.annotation.*; |
||||
|
||||
/** |
||||
* 用于标记需要在 datasourceVariableName 参数后拼接租户ID后缀的注解。 |
||||
* |
||||
* @author guifc |
||||
* @date 2026-05-19 |
||||
*/ |
||||
@Target({ElementType.TYPE, ElementType.METHOD}) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Documented |
||||
public @interface TenantOnlFormHeadSuffix { |
||||
|
||||
/** |
||||
* 是否启用租户ID后缀拼接。 |
||||
* |
||||
* @return 是否启用,默认启用。 |
||||
*/ |
||||
boolean enabled() default true; |
||||
} |
||||
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
package apelet.common.tenant.aop; |
||||
|
||||
import apelet.common.core.object.TokenData; |
||||
import apelet.common.tenant.annotation.TenantOnlFormHeadSuffix; |
||||
import cn.hutool.core.util.StrUtil; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.aspectj.lang.JoinPoint; |
||||
import org.aspectj.lang.ProceedingJoinPoint; |
||||
import org.aspectj.lang.Signature; |
||||
import org.aspectj.lang.annotation.Around; |
||||
import org.aspectj.lang.annotation.Aspect; |
||||
import org.aspectj.lang.annotation.Pointcut; |
||||
import org.aspectj.lang.reflect.MethodSignature; |
||||
import org.springframework.core.annotation.Order; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import java.lang.reflect.Method; |
||||
|
||||
/** |
||||
* 织入TenantOnlFormHeadSuffix注解,处理接口中datasourceVariableName参数的租户ID后缀拼接。 |
||||
* |
||||
* @author guifc |
||||
* @date 2026-05-19 |
||||
*/ |
||||
@Aspect |
||||
@Component |
||||
@Order(2) |
||||
@Slf4j |
||||
public class TenantOnlFormHeadSuffixHandlerAspect { |
||||
|
||||
@Pointcut("@within(apelet.common.tenant.annotation.TenantOnlFormHeadSuffix) " + |
||||
"|| @annotation(apelet.common.tenant.annotation.TenantOnlFormHeadSuffix)") |
||||
public void tenantOnlFormHeadSuffixHandlerPointCut() { |
||||
// 空注释,避免sonar警告
|
||||
} |
||||
|
||||
@Around("tenantOnlFormHeadSuffixHandlerPointCut()") |
||||
public Object around(ProceedingJoinPoint point) throws Throwable { |
||||
TenantOnlFormHeadSuffix annotation = this.getAnnotation(point); |
||||
if (annotation == null || !annotation.enabled()) { |
||||
return point.proceed(); |
||||
} |
||||
|
||||
// 获取方法签名
|
||||
MethodSignature signature = (MethodSignature) point.getSignature(); |
||||
Method method = signature.getMethod(); |
||||
String[] parameterNames = signature.getParameterNames(); |
||||
Object[] args = point.getArgs(); |
||||
|
||||
// 从token中获取租户ID
|
||||
TokenData tokenData = TokenData.takeFromRequest(); |
||||
if (tokenData == null || tokenData.getTenantId() == null) { |
||||
log.warn("TenantId is null in TokenData, skip datasourceVariableName suffix handling."); |
||||
return point.proceed(); |
||||
} |
||||
|
||||
Long tenantId = tokenData.getTenantId(); |
||||
String suffix = "_" + tenantId; |
||||
|
||||
// 遍历参数,查找并修改datasourceVariableName参数
|
||||
for (int i = 0; i < parameterNames.length; i++) { |
||||
if ("datasourceVariableName".equals(parameterNames[i]) && args[i] instanceof String) { |
||||
String originalValue = (String) args[i]; |
||||
if (StrUtil.isNotBlank(originalValue) && !originalValue.endsWith(suffix)) { |
||||
String newValue = originalValue + suffix; |
||||
args[i] = newValue; |
||||
log.debug("Modified datasourceVariableName from [{}] to [{}] for tenant [{}]", |
||||
originalValue, newValue, tenantId); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
try { |
||||
return point.proceed(args); |
||||
} catch (Exception e) { |
||||
log.error("Error in TenantOnlFormHeadSuffixHandlerAspect: {}", e.getMessage(), e); |
||||
throw e; |
||||
} |
||||
} |
||||
|
||||
private TenantOnlFormHeadSuffix getAnnotation(JoinPoint joinPoint) { |
||||
Signature signature = joinPoint.getSignature(); |
||||
MethodSignature methodSignature = (MethodSignature) signature; |
||||
|
||||
// 先尝试从方法上获取注解
|
||||
TenantOnlFormHeadSuffix anno = methodSignature.getMethod() |
||||
.getAnnotation(TenantOnlFormHeadSuffix.class); |
||||
if (anno != null) { |
||||
return anno; |
||||
} |
||||
|
||||
// 再从类上获取注解
|
||||
Class<?> declaringType = methodSignature.getDeclaringType(); |
||||
return declaringType.getAnnotation(TenantOnlFormHeadSuffix.class); |
||||
} |
||||
} |
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue