Compare commits

...

1 Commits

  1. 5
      application-tenant/tenant-admin/pom.xml
  2. 58
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/controller/OnlineFormTenantSyncController.java
  3. 10
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/OnlineFormTenantSyncService.java
  4. 181
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/impl/OnlineFormTenantSyncServiceImpl.java
  5. 110
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/impl/SysTenantRoleServiceImpl.java
  6. 2
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/SysUserController.java
  7. 4
      application-tenant/tenant-sync/src/main/java/apelet/tenantsync/consumer/TenantSyncConsumer.java
  8. 95
      application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlCgreporHeadServiceImpl.java
  9. 139
      application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlFormHeadServiceImpl.java
  10. 88
      application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlineFormUpdateServiceImpl.java
  11. 909
      application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/utils/FormWidgetMergeUtils.java
  12. 3
      common/common-generator/src/main/java/apelet/common/generator/model/OnlCgreportHead.java
  13. 3
      common/common-generator/src/main/java/apelet/common/generator/model/OnlFormHead.java
  14. 3
      common/common-online/src/main/java/apelet/common/online/model/OnlineForm.java
  15. 14
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineFormServiceImpl.java
  16. 22
      common/common-tenant/src/main/java/apelet/common/tenant/annotation/TenantOnlFormHeadSuffix.java
  17. 97
      common/common-tenant/src/main/java/apelet/common/tenant/aop/TenantOnlFormHeadSuffixHandlerAspect.java
  18. 25
      common/common-tenant/src/main/java/apelet/common/tenant/constant/TenantConstant.java
  19. 5940
      zzlogs/tenant-admin/tenant-admin-2026-03-25-0.log
  20. 10142
      zzlogs/tenant-admin/tenant-admin.log

5
application-tenant/tenant-admin/pom.xml

@ -154,6 +154,11 @@ @@ -154,6 +154,11 @@
<artifactId>quartz</artifactId>
<version>2.3.2</version> <!-- 确保使用最新的稳定版本 -->
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.8.0</version>
</dependency>
</dependencies>
<build>

58
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/controller/OnlineFormTenantSyncController.java

@ -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));
}
}

10
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/OnlineFormTenantSyncService.java

@ -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);
}

181
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/impl/OnlineFormTenantSyncServiceImpl.java

@ -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;
}
}

110
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/service/impl/SysTenantRoleServiceImpl.java

@ -1,5 +1,10 @@ @@ -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.*; @@ -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<SysTenantRole, Long> i @@ -65,6 +74,34 @@ public class SysTenantRoleServiceImpl extends BaseService<SysTenantRole, Long> 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<SysTenantRole, Long> i @@ -489,9 +526,82 @@ public class SysTenantRoleServiceImpl extends BaseService<SysTenantRole, Long> i
TenantConstant.INSERT_TENANT_ONLINE_PAGE_COMMAND,
messageJsonData.toJSONString(),
TenantConstant.MESSAGE_QUEUE_SELECTOR_KEY);
//表单元数据与查询数据源新增同步
List<OnlineForm> onlineFormListByPageIds = onlineFormService.getOnlineFormListByPageIds((Set<Long>) pageIds);
for (OnlineForm onlineForm : onlineFormListByPageIds) {
// 根据表单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 : 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<OnlFormHead> headList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(OnlFormHead.class), pageIds);
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 : 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<Long> 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<Long> otherRoleIdSet) {
List<Long> pageIdSet = this.getSysTenantRoleOnlinePageIdList(tenantRoleId);
Set<Long> resultOnlinePageIdSet = this.filterTenantOnlinePageIdSet(new HashSet<>(pageIdSet), otherRoleIdSet);

2
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/SysUserController.java

@ -27,7 +27,7 @@ import com.github.pagehelper.page.PageMethod; @@ -27,7 +27,7 @@ import com.github.pagehelper.page.PageMethod;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.xxl.job.core.handler.annotation.XxlJob;
import io.swagger.v3.oas.annotations.tags.Tag;
import javafx.concurrent.Task;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

4
application-tenant/tenant-sync/src/main/java/apelet/tenantsync/consumer/TenantSyncConsumer.java

@ -25,6 +25,8 @@ public class TenantSyncConsumer extends BaseDataSyncRocketMqConsumer { @@ -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);
}
}
}

95
application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlCgreporHeadServiceImpl.java

@ -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;
}
}

139
application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlFormHeadServiceImpl.java

@ -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;
}
}

88
application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/service/SysTenantOnlineFormUpdateServiceImpl.java

@ -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;
}
}

909
application-tenant/tenant-sync/src/main/java/apelet/tenantsync/online/utils/FormWidgetMergeUtils.java

@ -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() {
}
/**
* 三路合并在保留 basebranch1 修改的基础上合并 basebranch2 的修改
* <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);
}
}

3
common/common-generator/src/main/java/apelet/common/generator/model/OnlCgreportHead.java

@ -50,4 +50,7 @@ public class OnlCgreportHead extends BaseModel implements Serializable { @@ -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;
}

3
common/common-generator/src/main/java/apelet/common/generator/model/OnlFormHead.java

@ -114,6 +114,9 @@ public class OnlFormHead extends BaseModel implements Serializable { @@ -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<OnlFormField> onlFormFieldList;

3
common/common-online/src/main/java/apelet/common/online/model/OnlineForm.java

@ -25,8 +25,9 @@ import java.util.Map; @@ -25,8 +25,9 @@ import java.util.Map;
*/
@Data
@TableName(value = "zz_online_form")
public class OnlineForm {
public class OnlineForm implements java.io.Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键Id
*/

14
common/common-online/src/main/java/apelet/common/online/service/impl/OnlineFormServiceImpl.java

@ -35,7 +35,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; @@ -35,7 +35,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.github.pagehelper.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
@ -125,6 +124,8 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme @@ -125,6 +124,8 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> 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();
@ -174,6 +175,8 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme @@ -174,6 +175,8 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme
onlineFormDatasourceMapper.insert(onlineFormDatasource);
}
}
// 缓存上次更新的在线表单数据,新增则更新新增的表单数据
cacheLastUpdateOnlineForm(originalOnlineForm);
return true;
}
@ -194,6 +197,7 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme @@ -194,6 +197,7 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme
OnlineFormDatasource formDatasourceFilter = new OnlineFormDatasource();
formDatasourceFilter.setFormId(formId);
onlineFormDatasourceMapper.delete(new QueryWrapper<>(formDatasourceFilter));
redissonClient.getBucket("lastUpdateOnlineForm:" + formId).delete();
return true;
}
@ -216,6 +220,7 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme @@ -216,6 +220,7 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> 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));
@ -572,4 +577,11 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme @@ -572,4 +577,11 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme
}
public OnlineForm cacheLastUpdateOnlineForm(OnlineForm onlineForm){
RBucket<Object> bucket = redissonClient.getBucket("lastUpdateOnlineForm:" + onlineForm.getFormId());
bucket.set(onlineForm);
return onlineForm;
}
}

22
common/common-tenant/src/main/java/apelet/common/tenant/annotation/TenantOnlFormHeadSuffix.java

@ -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;
}

97
common/common-tenant/src/main/java/apelet/common/tenant/aop/TenantOnlFormHeadSuffixHandlerAspect.java

@ -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);
}
}

25
common/common-tenant/src/main/java/apelet/common/tenant/constant/TenantConstant.java

@ -40,6 +40,31 @@ public final class TenantConstant { @@ -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";
/**
* 新增租户和流程定义多对多关系的消息同步命令
*/

5940
zzlogs/tenant-admin/tenant-admin-2026-03-25-0.log

File diff suppressed because one or more lines are too long

10142
zzlogs/tenant-admin/tenant-admin.log

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save