diff --git a/application-tenant/tenant-admin/pom.xml b/application-tenant/tenant-admin/pom.xml index d556531..f81b749 100644 --- a/application-tenant/tenant-admin/pom.xml +++ b/application-tenant/tenant-admin/pom.xml @@ -163,6 +163,11 @@ quartz 2.3.2 + + com.jayway.jsonpath + json-path + 2.8.0 + diff --git a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/controller/OnlineFormTenantSyncController.java b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/controller/OnlineFormTenantSyncController.java new file mode 100644 index 0000000..66ff3f7 --- /dev/null +++ b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/controller/OnlineFormTenantSyncController.java @@ -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 syncUpdateTenant( + @MyRequestBody Long pageId, + @MyRequestBody List tenantIdList) { + return ResponseResult.success(onlineFormTenantSyncService.syncUpdateTenant(pageId, tenantIdList)); + } + +} diff --git a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/OnlineFormTenantSyncService.java b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/OnlineFormTenantSyncService.java new file mode 100644 index 0000000..039b42d --- /dev/null +++ b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/OnlineFormTenantSyncService.java @@ -0,0 +1,10 @@ +package apelet.tenantadmin.tenant.service; + + +import java.util.List; + +public interface OnlineFormTenantSyncService { + + Boolean syncUpdateTenant(Long pageId, List tenantIdSet); + +} diff --git a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/impl/OnlineFormTenantSyncServiceImpl.java b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/impl/OnlineFormTenantSyncServiceImpl.java new file mode 100644 index 0000000..c680395 --- /dev/null +++ b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/impl/OnlineFormTenantSyncServiceImpl.java @@ -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 tenantIdSet) { + List onlineFormListByPageIds = onlineFormService.getOnlineFormListByPageIds(Collections.singleton(pageId)); + for (OnlineForm onlineForm : onlineFormListByPageIds) { + List datasourceList = onlineDatasourceService.getOnlineDatasourceListByFormIds(Collections.singleton(onlineForm.getFormId())); + Set datasourceIdSet = datasourceList.stream() + .map(OnlineDatasource::getDatasourceId) + .collect(Collectors.toSet()); + RBucket bucket = redissonClient.getBucket("lastUpdateOnlineForm:" + onlineForm.getFormId()); + OnlineForm lastUpdateOnlineForm; + if (null != bucket) { + lastUpdateOnlineForm = bucket.get(); + } else { + lastUpdateOnlineForm = null; + } + // 根据表单json中的relativeFormCode获取到对应的查询数据源数据 + String widgetJson = onlineForm.getWidgetJson(); + List relativeFormCodeList = JsonPath.read(widgetJson, "$..relativeFormCode"); + List onlCgreportHeadList = iOnlCgreportHeadService.list(new LambdaQueryWrapper() + .in(OnlCgreportHead::getCode, relativeFormCodeList)); + List onlCgreportHeadIdList = onlCgreportHeadList.stream().map(OnlCgreportHead::getId).collect(Collectors.toList()); + List onlCgreportItemList = iOnlCgreportItemService.list(new LambdaQueryWrapper() + .in(OnlCgreportItem::getCgrheadId, onlCgreportHeadIdList)); + List onlCgreportParamList = iOnlCgreportParamService.list(new LambdaQueryWrapper() + .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 headList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(OnlFormHead.class), Collections.singleton(pageId)); + List findList = onlFormFindService.list(new LambdaQueryWrapper().in(OnlFormFind::getHeadId, headList)); + List fieldList = onlFormFieldService.list(new LambdaQueryWrapper().in(OnlFormField::getHeadId, headList)); + List checkList = onlFormCheckService.list(new LambdaQueryWrapper().in(OnlFormCheck::getHeadId, headList)); + List indexList = onlFormIndexService.list(new LambdaQueryWrapper().in(OnlFormIndex::getHeadId, headList)); + List keyList = onlFormKeyService.list(new LambdaQueryWrapper().in(OnlFormKey::getHeadId, headList)); + List pageList = onlFormPageService.list(new LambdaQueryWrapper().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; + } +} diff --git a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/impl/SysTenantRoleServiceImpl.java b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/impl/SysTenantRoleServiceImpl.java index 48a94da..9f89235 100644 --- a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/impl/SysTenantRoleServiceImpl.java +++ b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/impl/SysTenantRoleServiceImpl.java @@ -1,5 +1,10 @@ package apelet.tenantadmin.tenant.service.impl; +import apelet.common.generator.model.*; +import apelet.common.generator.service.*; +import apelet.common.online.model.OnlineForm; +import apelet.common.online.service.OnlineFormService; +import apelet.tenantadmin.tenant.service.OnlineFormTenantSyncService; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; @@ -22,11 +27,15 @@ import apelet.tenantadmin.tenant.model.*; import apelet.tenantadmin.tenant.service.SysTenantRoleService; import apelet.tenantadmin.tenant.service.SysTenantService; import apelet.tenantadmin.config.DataSourceType; +import com.jayway.jsonpath.JsonPath; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; 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; @@ -65,6 +74,34 @@ public class SysTenantRoleServiceImpl extends BaseService i @Autowired private ApplicationConfig applicationConfig; + @Autowired + private OnlineFormService onlineFormService; + + @Autowired + private IOnlCgreportHeadService iOnlCgreportHeadService; + + @Autowired + private IOnlCgreportItemService iOnlCgreportItemService; + + @Autowired + private IOnlCgreportParamService iOnlCgreportParamService; + + @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; + private static final String TENANT_ID_KEY = "tenantId"; private static final String TENANT_ID_SET_KEY = "tenantIdSet"; private static final String TENANT_LIST_KEY = "tenantList"; @@ -489,9 +526,82 @@ public class SysTenantRoleServiceImpl extends BaseService i TenantConstant.INSERT_TENANT_ONLINE_PAGE_COMMAND, messageJsonData.toJSONString(), TenantConstant.MESSAGE_QUEUE_SELECTOR_KEY); + + //表单元数据与查询数据源新增同步 + List onlineFormListByPageIds = onlineFormService.getOnlineFormListByPageIds((Set) pageIds); + for (OnlineForm onlineForm : onlineFormListByPageIds) { + // 根据表单json中的relativeFormCode获取到对应的查询数据源数据 + String widgetJson = onlineForm.getWidgetJson(); + List relativeFormCodeList = JsonPath.read(widgetJson, "$..relativeFormCode"); + List onlCgreportHeadList = iOnlCgreportHeadService.list(new LambdaQueryWrapper() + .in(OnlCgreportHead::getCode, relativeFormCodeList)); + List onlCgreportHeadIdList = onlCgreportHeadList.stream().map(OnlCgreportHead::getId).collect(Collectors.toList()); + List onlCgreportItemList = iOnlCgreportItemService.list(new LambdaQueryWrapper() + .in(OnlCgreportItem::getCgrheadId, onlCgreportHeadIdList)); + List onlCgreportParamList = iOnlCgreportParamService.list(new LambdaQueryWrapper() + .in(OnlCgreportParam::getCgrheadId, onlCgreportHeadIdList)); + for (Long tenantId : tenantIds) { + //表单数据源更新同步新增 + 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(pageIds); + List headList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(OnlFormHead.class), pageIds); + List findList = onlFormFindService.list(new LambdaQueryWrapper().in(OnlFormFind::getHeadId, headList)); + List fieldList = onlFormFieldService.list(new LambdaQueryWrapper().in(OnlFormField::getHeadId, headList)); + List checkList = onlFormCheckService.list(new LambdaQueryWrapper().in(OnlFormCheck::getHeadId, headList)); + List indexList = onlFormIndexService.list(new LambdaQueryWrapper().in(OnlFormIndex::getHeadId, headList)); + List keyList = onlFormKeyService.list(new LambdaQueryWrapper().in(OnlFormKey::getHeadId, headList)); + List pageList = onlFormPageService.list(new LambdaQueryWrapper().in(OnlFormPage::getHeadId, headList)); + // 表单元数据通知更新新增 + for(Long tenantId : tenantIds){ + JSONObject messageJsonData1 = new JSONObject(); + messageJsonData1.put("onlFormHeadList", headList); + messageJsonData1.put("onlFormFindList", findList); + messageJsonData1.put("onlFormFieldList", fieldList); + messageJsonData1.put("onlFormCheckList", checkList); + messageJsonData1.put("onlFormIndexList", indexList); + messageJsonData1.put("onlFormKeyList", keyList); + messageJsonData1.put("onlFormPageList", pageList); + dataSyncProducer.sendOrderly( + tenantId, + applicationConfig.getTenantSyncTopic(), + OnlFormHead.class.getSimpleName(), + TenantConstant.INSERT_TENANT_ONL_FORM_HEAD_COMMAND, + messageJsonData1.toJSONString(), + TenantConstant.MESSAGE_QUEUE_SELECTOR_KEY + ); + } } } + private static @NotNull String getOnlFormHeadByPageIdSQL(Collection pageIdList) { + String placeholders = String.join(",", Collections.nCopies(pageIdList.size(), "?")); + 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; + } + + + private void processRemoveTenantOnlinePageAndSync(Long tenantRoleId, Long tenantId, Set otherRoleIdSet) { List pageIdSet = this.getSysTenantRoleOnlinePageIdList(tenantRoleId); Set resultOnlinePageIdSet = this.filterTenantOnlinePageIdSet(new HashSet<>(pageIdSet), otherRoleIdSet); diff --git a/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/consumer/TenantSyncConsumer.java b/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/consumer/TenantSyncConsumer.java index a154a71..7a24c67 100644 --- a/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/consumer/TenantSyncConsumer.java +++ b/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/consumer/TenantSyncConsumer.java @@ -25,6 +25,8 @@ public class TenantSyncConsumer extends BaseDataSyncRocketMqConsumer { @Override protected void handleMessage(String transId, String messageType, String messageCommand, JSONObject messageJsonData) { BaseDataSyncConsumerService service = serviceMap.get(messageType); - service.doHandle(transId, messageCommand, messageJsonData); + if (service != null) { + service.doHandle(transId, messageCommand, messageJsonData); + } } } diff --git a/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlCgreporHeadServiceImpl.java b/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlCgreporHeadServiceImpl.java new file mode 100644 index 0000000..3e94b41 --- /dev/null +++ b/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlCgreporHeadServiceImpl.java @@ -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 onlCgreportHeadList = messageJsonData.getJSONArray("onlCgreportHeadList").toJavaList(OnlCgreportHead.class); + List onlCgreportItemList = messageJsonData.getJSONArray("onlCgreportItemList").toJavaList(OnlCgreportItem.class); + List 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() + .eq(OnlCgreportHead::getCode, onlCgreportHead.getCode()) + .eq(OnlCgreportHead::getTenantId, transId)); + if (one == null) { + // 把onlCgreportItemList、onlCgreportParamList中headId和onlCgreportHead的id匹配的数据过滤出来 + List matchedItemList = onlCgreportItemList.stream() + .filter(item -> item.getCgrheadId() != null && item.getCgrheadId().equals(onlCgreportHead.getId())) + .collect(Collectors.toList()); + List 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 handleConsumerClass() { + return TenantSyncConsumer.class; + } +} diff --git a/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlFormHeadServiceImpl.java b/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlFormHeadServiceImpl.java new file mode 100644 index 0000000..4baa80b --- /dev/null +++ b/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlFormHeadServiceImpl.java @@ -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 onlFormHeadList = messageJsonData.getJSONArray("onlFormHeadList").toJavaList(OnlFormHead.class); + List onlFormFindList = messageJsonData.getJSONArray("onlFormFindList").toJavaList(OnlFormFind.class); + List onlFormFieldList = messageJsonData.getJSONArray("onlFormFieldList").toJavaList(OnlFormField.class); + List onlFormCheckList = messageJsonData.getJSONArray("onlFormCheckList").toJavaList(OnlFormCheck.class); + List onlFormIndexList = messageJsonData.getJSONArray("onlFormIndexList").toJavaList(OnlFormIndex.class); + List onlFormKeyList = messageJsonData.getJSONArray("onlFormKeyList").toJavaList(OnlFormKey.class); + List 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() + .eq(OnlFormHead::getTableName, onlFormHead.getTableName())); + if (one == null) { + // 把onlFormFindList、onlFormFieldList、onlFormCheckList、onlFormIndexList、onlFormKeyList、onlFormPageList + // 中headId和onlFormHead的id匹配的数据过滤出来 + List matchedFindList = onlFormFindList.stream() + .filter(find -> find.getHeadId() != null && find.getHeadId().equals(onlFormHead.getId())) + .collect(Collectors.toList()); + List matchedFieldList = onlFormFieldList.stream() + .filter(field -> field.getHeadId() != null && field.getHeadId().equals(onlFormHead.getId())) + .collect(Collectors.toList()); + List matchedCheckList = onlFormCheckList.stream() + .filter(check -> check.getHeadId() != null && check.getHeadId().equals(onlFormHead.getId())) + .collect(Collectors.toList()); + List matchedIndexList = onlFormIndexList.stream() + .filter(index -> index.getHeadId() != null && index.getHeadId().equals(onlFormHead.getId())) + .collect(Collectors.toList()); + List matchedKeyList = onlFormKeyList.stream() + .filter(key -> key.getHeadId() != null && key.getHeadId().equals(onlFormHead.getId())) + .collect(Collectors.toList()); + List 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 handleConsumerClass() { + return TenantSyncConsumer.class; + } +} diff --git a/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlineFormUpdateServiceImpl.java b/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlineFormUpdateServiceImpl.java new file mode 100644 index 0000000..c90c482 --- /dev/null +++ b/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlineFormUpdateServiceImpl.java @@ -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 datasourceIdSet = messageJsonData.getJSONArray("datasourceIdSet").toJavaList(Long.class); + if (originalOnlineForm == null) { + originalOnlineForm = onlineForm; + } + //获取租户数据库中所有对应form_code的表单数据 + List dbOnlineFormList = onlineFormMapper.selectList(new QueryWrapper().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().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 handleConsumerClass() { + return TenantSyncConsumer.class; + } +} diff --git a/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/utils/FormWidgetMergeUtils.java b/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/utils/FormWidgetMergeUtils.java new file mode 100644 index 0000000..aaf1f4c --- /dev/null +++ b/application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/utils/FormWidgetMergeUtils.java @@ -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 深度合并工具 + *

+ * 合并策略: + *

    + *
  • 对象(JSONObject):递归深度合并,source 覆盖 target 的同名键
  • + *
  • 数组(JSONArray):按业务键去重合并,具体逻辑由数组合并策略决定
  • + *
  • 标量值:source 直接覆盖 target
  • + *
+ *

+ */ +public class FormWidgetMergeUtils { + + /** + * 数组合并策略枚举 + */ + public enum ArrayMergeStrategy { + /** 按业务键去重合并:相同键的元素由 source 覆盖 target,新增元素追加 */ + MERGE_BY_KEY, + /** 直接追加:source 数组元素全部追加到 target 数组末尾 */ + APPEND + } + + /** + * 数组路径 → 业务键 映射配置 + *

+ * key 为 JSON 路径中数组字段的名称,value 为该数组中元素的业务主键字段名 + * 例如 "operationList" → "type" 表示 operationList 数组按 type 字段去重 + */ + private static final Map ARRAY_BUSINESS_KEY_MAP = new LinkedHashMap<>(); + + /** + * 数组路径 → 合并策略 映射配置 + *

+ * 未在此映射中配置的数组字段,默认使用 APPEND 策略 + */ + private static final Map 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 的修改 + *

+ * 合并逻辑: + * 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 的差异 + *

+ * 返回一个包含所有新增和修改字段的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 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 baseKeyIndexMap = buildKeyIndexMap(baseArray, businessKey); + Map sourceKeyIndexMap = buildKeyIndexMap(sourceArray, businessKey); + + // 检查 source 中新增或修改的元素 + for (Map.Entry 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 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 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; + } + + /** + * 应用差异但不覆盖已修改的部分 + *

+ * 这是三路合并的核心逻辑:确保 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 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 targetKeyIndexMap = buildKeyIndexMap(result, businessKey); + // 构建 diff1 的业务键到元素的映射(而不是索引映射) + Map 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 buildKeyElementMap(JSONArray array, String businessKey) { + Map 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 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); + } + } + + /** + * 按业务键去重合并数组 + *

+ * 逻辑: + * 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 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 元素的 业务键值 → 索引 映射 + *

+ * 仅处理 JSONObject 类型的元素;标量元素跳过。 + * 若同一业务键值出现多次,后出现的索引覆盖先前的(保留最后一个)。 + * + * @param array JSON数组 + * @param businessKey 业务主键字段名 + * @return 业务键值字符串 → 在数组中的索引位置 + */ + private static Map buildKeyIndexMap(JSONArray array, String businessKey) { + Map 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 中指定业务键字段的值,统一转为字符串用于比较 + *

+ * 支持的值类型: + *

    + *
  • String → 直接返回
  • + *
  • Number → 返回其字符串表示(如 1 → "1", 10 → "10")
  • + *
  • Boolean → 返回 "true" / "false"
  • + *
  • null 或字段不存在 → 返回 null
  • + *
+ * + * @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 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); + } +} diff --git a/common/common-generator/src/main/java/apelet/common/generator/model/OnlCgreportHead.java b/common/common-generator/src/main/java/apelet/common/generator/model/OnlCgreportHead.java index bb3eedd..e3b929f 100644 --- a/common/common-generator/src/main/java/apelet/common/generator/model/OnlCgreportHead.java +++ b/common/common-generator/src/main/java/apelet/common/generator/model/OnlCgreportHead.java @@ -50,4 +50,7 @@ public class OnlCgreportHead extends BaseModel implements Serializable { @Schema(description = "逻辑删除标记(1: 正常 -1: 已删除)") // @TableLogic private Integer deletedFlag; + + @Schema(description = "租户id") + private String tenantId; } diff --git a/common/common-generator/src/main/java/apelet/common/generator/model/OnlFormHead.java b/common/common-generator/src/main/java/apelet/common/generator/model/OnlFormHead.java index 8c3f2c9..258b5d1 100644 --- a/common/common-generator/src/main/java/apelet/common/generator/model/OnlFormHead.java +++ b/common/common-generator/src/main/java/apelet/common/generator/model/OnlFormHead.java @@ -114,6 +114,9 @@ public class OnlFormHead extends BaseModel implements Serializable { @Schema(description = "继承表") private String metaTable; + @Schema(description = "租户id") + private String tenantId; + @Schema(description = "数据库属性分录") @TableField(exist = false) private List onlFormFieldList; diff --git a/common/common-online/src/main/java/apelet/common/online/model/OnlineForm.java b/common/common-online/src/main/java/apelet/common/online/model/OnlineForm.java index 54dd673..51f1596 100644 --- a/common/common-online/src/main/java/apelet/common/online/model/OnlineForm.java +++ b/common/common-online/src/main/java/apelet/common/online/model/OnlineForm.java @@ -26,8 +26,9 @@ import java.util.Set; */ @Data @TableName(value = "zz_online_form") -public class OnlineForm { +public class OnlineForm implements java.io.Serializable { + private static final long serialVersionUID = 1L; /** * 主键Id。 */ diff --git a/common/common-online/src/main/java/apelet/common/online/service/impl/OnlineFormServiceImpl.java b/common/common-online/src/main/java/apelet/common/online/service/impl/OnlineFormServiceImpl.java index 702bcf1..657736b 100644 --- a/common/common-online/src/main/java/apelet/common/online/service/impl/OnlineFormServiceImpl.java +++ b/common/common-online/src/main/java/apelet/common/online/service/impl/OnlineFormServiceImpl.java @@ -129,6 +129,8 @@ public class OnlineFormServiceImpl extends BaseService impleme onlineForm.setCreateUserId(tokenData.getUserId()); onlineForm.setUpdateUserId(tokenData.getUserId()); onlineFormMapper.insert(onlineForm); + // 缓存上次更新的在线表单数据,新增则更新新增的表单数据 + cacheLastUpdateOnlineForm(onlineForm); if (CollUtil.isNotEmpty(datasourceIdSet)) { for (Long datasourceId : datasourceIdSet) { OnlineFormDatasource onlineFormDatasource = new OnlineFormDatasource(); @@ -178,6 +180,8 @@ public class OnlineFormServiceImpl extends BaseService impleme onlineFormDatasourceMapper.insert(onlineFormDatasource); } } + // 缓存上次更新的在线表单数据,新增则更新新增的表单数据 + cacheLastUpdateOnlineForm(originalOnlineForm); return true; } @@ -198,6 +202,7 @@ public class OnlineFormServiceImpl extends BaseService impleme OnlineFormDatasource formDatasourceFilter = new OnlineFormDatasource(); formDatasourceFilter.setFormId(formId); onlineFormDatasourceMapper.delete(new QueryWrapper<>(formDatasourceFilter)); + redissonClient.getBucket("lastUpdateOnlineForm:" + formId).delete(); return true; } @@ -220,6 +225,7 @@ public class OnlineFormServiceImpl extends BaseService impleme for (Long formId : formIdSet) { commonRedisUtil.evictFormCache(OnlineRedisKeyUtil.makeOnlineFormKey(formId)); commonRedisUtil.evictFormCache(OnlineRedisKeyUtil.makeOnlineFormDatasourceKey(formId)); + redissonClient.getBucket("lastUpdateOnlineForm:" + formId).delete(); } } return onlineFormMapper.delete(new QueryWrapper<>(filter)); @@ -624,4 +630,9 @@ public class OnlineFormServiceImpl extends BaseService impleme } } + public OnlineForm cacheLastUpdateOnlineForm(OnlineForm onlineForm) { + RBucket bucket = redissonClient.getBucket("lastUpdateOnlineForm:" + onlineForm.getFormId()); + bucket.set(onlineForm); + return onlineForm; + } } diff --git a/common/common-tenant/src/main/java/apelet/common/tenant/annotation/TenantOnlFormHeadSuffix.java b/common/common-tenant/src/main/java/apelet/common/tenant/annotation/TenantOnlFormHeadSuffix.java new file mode 100644 index 0000000..e7b1649 --- /dev/null +++ b/common/common-tenant/src/main/java/apelet/common/tenant/annotation/TenantOnlFormHeadSuffix.java @@ -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; +} diff --git a/common/common-tenant/src/main/java/apelet/common/tenant/aop/TenantOnlFormHeadSuffixHandlerAspect.java b/common/common-tenant/src/main/java/apelet/common/tenant/aop/TenantOnlFormHeadSuffixHandlerAspect.java new file mode 100644 index 0000000..31c1204 --- /dev/null +++ b/common/common-tenant/src/main/java/apelet/common/tenant/aop/TenantOnlFormHeadSuffixHandlerAspect.java @@ -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); + } +} diff --git a/common/common-tenant/src/main/java/apelet/common/tenant/constant/TenantConstant.java b/common/common-tenant/src/main/java/apelet/common/tenant/constant/TenantConstant.java index 1715902..3653a30 100644 --- a/common/common-tenant/src/main/java/apelet/common/tenant/constant/TenantConstant.java +++ b/common/common-tenant/src/main/java/apelet/common/tenant/constant/TenantConstant.java @@ -40,6 +40,31 @@ public final class TenantConstant { * 移除租户和在线表单页面多对多关系的消息同步命令。 */ public static final String DELETE_TENANT_ONLINE_PAGE_COMMAND = "DELETE_TENANT_ONLINE_PAGE"; + + /** + * 新增租户和表单元数据多对多关系的消息同步命令。 + */ + public static final String INSERT_TENANT_ONL_FORM_HEAD_COMMAND = "INSERT_TENANT_ONL_FORM_HEAD"; + /** + * 移除租户和表单元数据多对多关系的消息同步命令。 + */ + public static final String DELETE_TENANT_ONL_FORM_HEAD_COMMAND = "DELETE_TENANT_ONL_FORM_HEAD"; + + /** + * 新增租户和表单元数据多对多关系的消息同步命令。 + */ + public static final String INSERT_TENANT_ONL_CGREPORT_HEAD_COMMAND = "INSERT_TENANT_ONL_CGREPORT_HEAD"; + /** + * 移除租户和表单元数据多对多关系的消息同步命令。 + */ + public static final String DELETE_TENANT_ONL_CGREPORT_HEAD_COMMAND = "DELETE_TENANT_ONL_CGREPORT_HEAD"; + + + /** + * 更新租户和在线表单页面多对多关系的消息同步命令。 + */ + public static final String UPDATE_TENANT_ONLINE_FORM_COMMAND = "UPDATE_TENANT_ONLINE_FORM"; + /** * 新增租户和流程定义多对多关系的消息同步命令。 */