Compare commits

...

61 Commits

Author SHA1 Message Date
chenchuchuan bc89e5091b refactor(online): 优化子表列字段类型映射逻辑 2 months ago
chenchuchuan 9d518c2c06 fix(online): 修复在线表单新增字段同步逻辑 2 months ago
chenchuchuan d53982dcda refactor(tenant-admin): 优化应用配置和事件监听器 2 months ago
chenchuchuan 17f06fb86e chore(build): 更新项目依赖版本和仓库配置 2 months ago
chenchuchuan 152b8712df feat(rule): 添加表单规则引擎执行方法并更新控制器权限配置 2 months ago
chenchuchuan 05530f7e7c feat(generator): 添加在线表单规则引擎及脚本事件支持 2 months ago
chenchuchuan 2945a0f428 feat(generator): 添加下拉查询功能支持 2 months ago
chenchuchuan b3f0ef7aa6 feat(widget): 添加规则数组获取功能并优化子表控件字段收集逻辑 2 months ago
chenchuchuan efb60f5f04 fix(flow): 修复审批设置创建监听器中的操作类型映射 2 months ago
chenchuchuan 83d7acdbb6 feat(flow): 优化多实例会签任务处理逻辑 2 months ago
chenchuchuan e12e17b6f5 feat(online): 统一表单字段变量名与数据库字段名的一致性 2 months ago
chenchuchuan 318ea9262b feat(common-online): 添加在线事件插件执行DTO字段 2 months ago
chenchuchuan 75297e6e12 refactor(widget): 优化组件JSON中表格ID回填逻辑 2 months ago
chenchuchuan b15ec4aba9 feat(online): 优化在线表单公式计算和字段同步功能 2 months ago
chenchuchuan 618f14aced refactor(online): 将WidgetJson解析逻辑提取为独立工具类 2 months ago
chenchuchuan 4cce9a9c11 fix(flow): 修复流程任务信息获取中的空指针异常 2 months ago
chenchuchuan a4894b2c25 feat(online): 添加根据表名查询列表表单功能 2 months ago
chenchuchuan 25f7f8f7c7 feat(online): 完善数据权限规则与角色绑定功能 2 months ago
chenchuchuan 9c618961ce feat(flow): 更新在线表单工作流业务数据获取功能 2 months ago
chenchuchuan 2ebdaf82e6 chore(config): 更新项目配置和依赖版本 2 months ago
chenchuchuan cf41dad6cf feat(flow): 添加在线表单工作流标题模板和多实例会签功能 2 months ago
chenchuchuan 91b9907be7 fix(online): 过滤表格字段类型避免误识别为新增字段 2 months ago
chenchuchuan 289ce088c8 fix(filter): 修复过滤器中JSON对象值处理问题 2 months ago
chenchuchuan 39e1d5ce3d feat(common): 支持移动端表单字段同步功能 2 months ago
chenchuchuan 6d4e3bb1e8 fix(button): 解决按钮配置接口权限和空值处理问题 2 months ago
chenchuchuan e8e7cbd588 fix(button): 解决按钮配置接口权限和空值处理问题 2 months ago
chenchuchuan 416c3d1793 refactor(online): 移除在线表单控制器中的废弃方法 2 months ago
chenchuchuan 7f1acccc4b Merge branch 'master' of http://124.222.6.97:3000/sunquan/apelet_tenant into feature/2026-07-ccc-dev 2 months ago
chenchuchuan ee197e24bb Merge branch 'feature/form-permission-control' of http://124.222.6.97:3000/sunquan/apelet_tenant into feature/2026-07-ccc-dev 2 months ago
chenchuchuan cefb7ecf67 feat(common): 添加重置行数据功能和自定义页面跳转支持 2 months ago
chenchuchuan bb30a3454a feat(online): 添加在线字典映射获取功能和重置页面类型 2 months ago
chenchuchuan ee0b0f8295 Merge remote-tracking branch 'refs/remotes/origin/online-form-direct-creation' into feature/2026-07-ccc-dev 2 months ago
chenchuchuan 1bae360e2a refactor(permission): 将按钮权限检查器和相关服务迁移至公共模块 2 months ago
chenchuchuan f9ecb2aaef refactor(permission): 将权限数据规则方案控制器迁移至公共模块 2 months ago
chenchuchuan bc20c40671 refactor(orm): 优化数据权限规则和部门管理功能 2 months ago
chenchuchuan 7045cdea35 fix(controller): 修复权限数据规则模式控制器中的参数绑定和空指针问题 2 months ago
chenchuchuan 9ec9758bd5 refactor(upms): 重构角色组织关联逻辑并优化用户角色分配 2 months ago
chenchuchuan 88d393d1f7 feat(role): 角色模块支持组织多对多关联 3 months ago
chenchuchuan b2dc6af80f refactor(tenant): 重构角色表单按钮权限配置服务 3 months ago
chenchuchuan 8d491ff216 docs(common): 更新在线表单控制器逻辑并添加项目总览文档 3 months ago
chenchuchuan 446eeba6be docs(common): 更新在线表单控制器逻辑并添加项目总览文档 3 months ago
chenchuchuan b6e8b1914e refactor(tenant): 重构按钮权限配置服务以支持多角色合并 3 months ago
chenchuchuan d26d2b14a5 feat(online): 添加从表关联字段同步功能和表单类型查询接口 3 months ago
chenchuchuan ed03e2a488 Merge branch 'master' of http://124.222.6.97:3000/sunquan/apelet_tenant into online-form-direct-creation 3 months ago
chenchuchuan f3aa6ab636 Merge branch 'online-form-direct-creation' of http://124.222.6.97:3000/sunquan/apelet_tenant into feature/form-permission-control 3 months ago
chenchuchuan 65f2f7938d Merge branch 'master' of http://124.222.6.97:3000/sunquan/apelet_tenant into feature/form-permission-control 3 months ago
chenchuchuan 1f96a131a6 refactor(permission): 将权限数据规则方案相关代码迁移到common-online模块 3 months ago
chenchuchuan b8a4b5727f refactor(permission): 将权限数据规则方案相关代码迁移到common-online模块 3 months ago
chenchuchuan cb1b2dd9c3 feat(role): 添加角色按钮权限配置功能 3 months ago
chenchuchuan eadce13994 feat(upms): 添加部门组织类型支持及角色用户关联功能 3 months ago
chenchuchuan 35ee89c750 feat(tenant): 添加在线表单按钮权限控制功能 3 months ago
chenchuchuan 9f5efe49e3 feat(tenant): 添加权限数据规则方案管理功能 3 months ago
chenchuchuan 3104f1b026 refactor(common-generator): 优化在线表单头服务实现 3 months ago
chenchuchuan 6fc400ab77 feat(online): 表单字段保存时,修改字段备注取值逻辑 3 months ago
chenchuchuan e9c3b36512 feat(online): 添加数据表字段保存功能 3 months ago
chenchuchuan 7dcb3bb5c9 refactor(online): 使用常量替换硬编码的元数据源字符串 3 months ago
chenchuchuan f405113552 fix(controller): 修正在线页面控制器返回类型 3 months ago
sunquan 15dd345744 fix:补在线表单控件和数据库字段类型 3 months ago
sunquan 1d42dfc702 fix:去掉nacos配置 app-config、t-admin-dev 3 months ago
sunquan c665cde835 fix:去掉nacos配置 3 months ago
sunquan 610a4d10ed feature-online-form-direct-creation:收到请求后自动创建数据库物理表 + 元数据记录 + 数据源配置 3 months ago
  1. 6
      application-tenant/tenant-admin/pom.xml
  2. 2
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/TenantAdminApplication.java
  3. 23
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/LoginController.java
  4. 8
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/SysDeptController.java
  5. 97
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/SysRoleController.java
  6. 91
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/SysUserController.java
  7. 23
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/dto/RoleDeptItemDto.java
  8. 6
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/dto/SysDeptDto.java
  9. 1
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/model/SysDept.java
  10. 6
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/model/SysUserRole.java
  11. 15
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/SysRoleService.java
  12. 32
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/SysUserService.java
  13. 15
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/impl/SysDeptServiceImpl.java
  14. 49
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/impl/SysRoleServiceImpl.java
  15. 107
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/impl/SysUserServiceImpl.java
  16. 6
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/vo/SysDeptVo.java
  17. 7
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/vo/SysRoleVo.java
  18. 161
      application-tenant/tenant-admin/src/main/resources/application-config.yml
  19. 23
      application-tenant/tenant-admin/src/main/resources/bootstrap.yml
  20. 82
      application-tenant/tenant-admin/src/main/resources/logback-spring.xml
  21. 355
      application-tenant/tenant-admin/src/main/resources/tenant-admin-dev.yml
  22. 2
      application-tenant/tenant-sync/pom.xml
  23. 4
      application/upms/upms-service/pom.xml
  24. 8
      common/common-flow-online/pom.xml
  25. 118
      common/common-flow-online/src/main/java/apelet/common/flow/online/controller/FlowOnlineOperationController.java
  26. 2
      common/common-flow-online/src/main/java/apelet/common/flow/online/listener/ApprovalSettingsByCreateListener.java
  27. 11
      common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowEntryOnlineServiceImpl.java
  28. 66
      common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineBusinessServiceImpl.java
  29. 2
      common/common-flow/pom.xml
  30. 22
      common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java
  31. 5
      common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java
  32. 19
      common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java
  33. 74
      common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java
  34. 5
      common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml
  35. 55
      common/common-flow/src/main/java/apelet/common/flow/dto/FlowTaskDto.java
  36. 76
      common/common-flow/src/main/java/apelet/common/flow/listener/MultiInstanceAssigneeListener.java
  37. 4
      common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java
  38. 6
      common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java
  39. 14
      common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java
  40. 15
      common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java
  41. 194
      common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java
  42. 14
      common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java
  43. 32
      common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java
  44. 22
      common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java
  45. 18
      common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java
  46. 6
      common/common-flow/src/main/java/apelet/common/flow/vo/TaskInfoVo.java
  47. 4
      common/common-generator/pom.xml
  48. 7
      common/common-generator/src/main/java/apelet/common/generator/controller/OnlFormHeadController.java
  49. 6
      common/common-generator/src/main/java/apelet/common/generator/model/OnlCgreportItem.java
  50. 2
      common/common-generator/src/main/java/apelet/common/generator/model/OnlFormHead.java
  51. 11
      common/common-generator/src/main/java/apelet/common/generator/service/IOnlFormHeadService.java
  52. 18
      common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlCgreportHeadServiceImpl.java
  53. 48
      common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java
  54. 9
      common/common-online/pom.xml
  55. 28
      common/common-online/src/main/java/apelet/common/online/abstractplugin/ExecutePluginParent.java
  56. 112
      common/common-online/src/main/java/apelet/common/online/abstractplugin/ListDataOrmService.java
  57. 12
      common/common-online/src/main/java/apelet/common/online/abstractplugin/ListDataService.java
  58. 282
      common/common-online/src/main/java/apelet/common/online/abstractplugin/MyInterfaceRulesExecute.java
  59. 1
      common/common-online/src/main/java/apelet/common/online/abstractplugin/model/ListColumn.java
  60. 648
      common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java
  61. 103
      common/common-online/src/main/java/apelet/common/online/controller/OnlineOperationController.java
  62. 91
      common/common-online/src/main/java/apelet/common/online/controller/OnlinePageController.java
  63. 186
      common/common-online/src/main/java/apelet/common/online/controller/PermissionDataRuleSchemaController.java
  64. 119
      common/common-online/src/main/java/apelet/common/online/controller/RoleFormButtonController.java
  65. 8
      common/common-online/src/main/java/apelet/common/online/dao/OnlineDictMapper.java
  66. 27
      common/common-online/src/main/java/apelet/common/online/dao/PermissionDataRuleSchemaMapper.java
  67. 23
      common/common-online/src/main/java/apelet/common/online/dao/RoleFormButtonMapper.java
  68. 13
      common/common-online/src/main/java/apelet/common/online/dao/RolePermissionSchemaMapper.java
  69. 4
      common/common-online/src/main/java/apelet/common/online/dao/mapper/OnlineDictMapper.xml
  70. 14
      common/common-online/src/main/java/apelet/common/online/dao/mapper/OnlineFormMapper.xml
  71. 27
      common/common-online/src/main/java/apelet/common/online/dao/mapper/PermissionDataRuleSchemaMapper.xml
  72. 21
      common/common-online/src/main/java/apelet/common/online/dao/mapper/RoleFormButtonMapper.xml
  73. 7
      common/common-online/src/main/java/apelet/common/online/dto/OnlineEventPluginExecuteDto.java
  74. 14
      common/common-online/src/main/java/apelet/common/online/dto/OnlinePageDto.java
  75. 35
      common/common-online/src/main/java/apelet/common/online/dto/RoleFormButtonDto.java
  76. 14
      common/common-online/src/main/java/apelet/common/online/model/OnlineForm.java
  77. 152
      common/common-online/src/main/java/apelet/common/online/model/PermissionDataRuleSchema.java
  78. 69
      common/common-online/src/main/java/apelet/common/online/model/RoleFormButton.java
  79. 28
      common/common-online/src/main/java/apelet/common/online/model/RolePermissionSchema.java
  80. 1
      common/common-online/src/main/java/apelet/common/online/model/constant/AttributeEnum.java
  81. 35
      common/common-online/src/main/java/apelet/common/online/model/constant/DataRuleType.java
  82. 1
      common/common-online/src/main/java/apelet/common/online/model/constant/EventEnum.java
  83. 1
      common/common-online/src/main/java/apelet/common/online/model/constant/ShowTypeEnum.java
  84. 143
      common/common-online/src/main/java/apelet/common/online/rule/FormRuleEngine.java
  85. 39
      common/common-online/src/main/java/apelet/common/online/rule/RuleActionExecutorRegistry.java
  86. 105
      common/common-online/src/main/java/apelet/common/online/rule/action/AbstractRuleActionExecutor.java
  87. 21
      common/common-online/src/main/java/apelet/common/online/rule/action/RuleActionExecutor.java
  88. 105
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/BaseDataCarryExecutor.java
  89. 97
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/FieldAssignExecutor.java
  90. 97
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/FieldClearExecutor.java
  91. 162
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/FormulaAssignExecutor.java
  92. 27
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetHideExecutor.java
  93. 51
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetLockExecutor.java
  94. 27
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetShowExecutor.java
  95. 27
      common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetUnlockExecutor.java
  96. 121
      common/common-online/src/main/java/apelet/common/online/rule/condition/ConditionEvaluator.java
  97. 67
      common/common-online/src/main/java/apelet/common/online/rule/condition/FunctionExecutor.java
  98. 26
      common/common-online/src/main/java/apelet/common/online/rule/enums/EventTypeEnum.java
  99. 44
      common/common-online/src/main/java/apelet/common/online/rule/enums/RuleActionTypeEnum.java
  100. 23
      common/common-online/src/main/java/apelet/common/online/rule/enums/RuleTypeEnum.java
  101. Some files were not shown because too many files have changed in this diff Show More

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

@ -58,12 +58,12 @@ @@ -58,12 +58,12 @@
<dependency>
<groupId>apelet</groupId>
<artifactId>common-flow</artifactId>
<version>1.0.0</version>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>apelet</groupId>
<artifactId>common-flow-online</artifactId>
<version>1.0.0</version>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>apelet</groupId>
@ -99,7 +99,7 @@ @@ -99,7 +99,7 @@
<dependency>
<groupId>apelet</groupId>
<artifactId>common-generator</artifactId>
<version>1.0.0</version>
<version>1.0.4</version>
</dependency>
<dependency>

2
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/TenantAdminApplication.java

@ -24,4 +24,6 @@ public class TenantAdminApplication { @@ -24,4 +24,6 @@ public class TenantAdminApplication {
public static void main(String[] args) {
SpringApplication.run(TenantAdminApplication.class, args);
}
}

23
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/LoginController.java

@ -8,7 +8,10 @@ import apelet.common.core.annotation.NoAuthInterface; @@ -8,7 +8,10 @@ import apelet.common.core.annotation.NoAuthInterface;
import apelet.common.core.constant.ApplicationConstant;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.object.*;
import apelet.common.core.upload.*;
import apelet.common.core.upload.BaseUpDownloader;
import apelet.common.core.upload.UpDownloaderFactory;
import apelet.common.core.upload.UploadResponseInfo;
import apelet.common.core.upload.UploadStoreInfo;
import apelet.common.core.util.*;
import apelet.common.datafilter.config.DataFilterProperties;
import apelet.common.eas.util.EasConfig;
@ -21,13 +24,19 @@ import apelet.common.mobile.model.MobileEntry; @@ -21,13 +24,19 @@ import apelet.common.mobile.model.MobileEntry;
import apelet.common.mobile.object.MobileEntryExtraData;
import apelet.common.mobile.service.MobileEntryService;
import apelet.common.online.service.OnlineOperationService;
import apelet.common.orm.impl.*;
import apelet.common.orm.impl.Filter;
import apelet.common.orm.impl.FilterItem;
import apelet.common.orm.impl.Selector;
import apelet.common.orm.impl.SelectorItem;
import apelet.common.redis.cache.SessionCacheHelper;
import apelet.common.report.service.ReportOperationService;
import apelet.tenantadmin.config.ApplicationConfig;
import apelet.tenantadmin.upms.bo.SysMenuPerm;
import apelet.tenantadmin.upms.model.*;
import apelet.tenantadmin.upms.model.constant.*;
import apelet.tenantadmin.upms.model.constant.SysMenuType;
import apelet.tenantadmin.upms.model.constant.SysOnlineMenuPermType;
import apelet.tenantadmin.upms.model.constant.SysUserStatus;
import apelet.tenantadmin.upms.model.constant.SysUserType;
import apelet.tenantadmin.upms.service.*;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
@ -69,7 +78,7 @@ import java.nio.charset.StandardCharsets; @@ -69,7 +78,7 @@ import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import apelet.common.eas.util.EasUtil;
/**
* 登录接口控制器类。
*
@ -1251,9 +1260,10 @@ public class LoginController { @@ -1251,9 +1260,10 @@ public class LoginController {
Set<Long> postIdSet = userPostList.stream().map(SysUserPost::getPostId).collect(Collectors.toSet());
tokenData.setPostIds(StrUtil.join(",", postIdSet));
}
// 2026-07-28 角色用户关联表,增加组织,导致角色id 可能为空
List<SysUserRole> userRoleList = sysRoleService.getSysUserRoleListByUserId(user.getUserId());
if (CollUtil.isNotEmpty(userRoleList)) {
Set<Long> userRoleIdSet = userRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toSet());
Set<Long> userRoleIdSet = userRoleList.stream().map(SysUserRole::getRoleId).filter(Objects::nonNull).collect(Collectors.toSet());
tokenData.setRoleIds(StrUtil.join(",", userRoleIdSet));
}
return tokenData;
@ -1281,9 +1291,10 @@ public class LoginController { @@ -1281,9 +1291,10 @@ public class LoginController {
Set<Long> postIdSet = userPostList.stream().map(SysUserPost::getPostId).collect(Collectors.toSet());
tokenData.setPostIds(StrUtil.join(",", postIdSet));
}
// 2026-07-28 角色用户关联表,增加组织,导致角色id 可能为空
List<SysUserRole> userRoleList = sysRoleService.getSysUserRoleListByUserId(user.getUserId());
if (CollUtil.isNotEmpty(userRoleList)) {
Set<Long> userRoleIdSet = userRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toSet());
Set<Long> userRoleIdSet = userRoleList.stream().map(SysUserRole::getRoleId).filter(Objects::nonNull).collect(Collectors.toSet());
tokenData.setRoleIds(StrUtil.join(",", userRoleIdSet));
}
return tokenData;

8
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/SysDeptController.java

@ -29,7 +29,10 @@ import org.springdoc.api.annotations.ParameterObject; @@ -29,7 +29,10 @@ import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
@ -145,6 +148,9 @@ public class SysDeptController { @@ -145,6 +148,9 @@ public class SysDeptController {
}
SysDept sysDeptFilter = MyModelUtil.copyTo(sysDeptDtoFilter, SysDept.class);
String orderBy = MyOrderParam.buildOrderBy(orderParam, SysDept.class);
if (sysDeptFilter == null){
sysDeptFilter = new SysDept();
}
List<SysDept> sysDeptList = sysDeptService.getSysDeptListWithRelation(sysDeptFilter, orderBy);
return ResponseResult.success(MyPageUtil.makeResponseData(sysDeptList, SysDept.INSTANCE));
}

97
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/SysRoleController.java

@ -9,6 +9,10 @@ import apelet.common.core.util.MyPageUtil; @@ -9,6 +9,10 @@ import apelet.common.core.util.MyPageUtil;
import apelet.common.core.validator.UpdateGroup;
import apelet.common.log.annotation.OperationLog;
import apelet.common.log.model.constant.SysOperationLogType;
import apelet.common.online.dto.RoleFormButtonDto;
import apelet.common.online.model.RolePermissionSchema;
import apelet.common.online.service.IRoleFormButtonService;
import apelet.common.online.service.IRolePermissionSchemaService;
import apelet.tenantadmin.upms.dto.SysRoleDto;
import apelet.tenantadmin.upms.dto.SysUserDto;
import apelet.tenantadmin.upms.model.SysRole;
@ -18,6 +22,7 @@ import apelet.tenantadmin.upms.service.SysRoleService; @@ -18,6 +22,7 @@ import apelet.tenantadmin.upms.service.SysRoleService;
import apelet.tenantadmin.upms.service.SysUserService;
import apelet.tenantadmin.upms.vo.SysRoleVo;
import apelet.tenantadmin.upms.vo.SysUserVo;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.TypeReference;
import com.github.pagehelper.Page;
import com.github.pagehelper.page.PageMethod;
@ -48,19 +53,28 @@ public class SysRoleController { @@ -48,19 +53,28 @@ public class SysRoleController {
private SysRoleService sysRoleService;
@Autowired
private SysUserService sysUserService;
@Autowired
private IRoleFormButtonService roleFormButtonService;
@Autowired
private IRolePermissionSchemaService rolePermissionSchemaService;
/**
* 新增角色操作。
*
* @param sysRoleDto 新增角色对象。
* @param menuIdListString 与当前角色Id绑定的menuId列表,多个menuId之间逗号分隔。
* @param sysRoleDto 新增角色对象。
* @param menuIdListString 与当前角色Id绑定的menuId列表,多个menuId之间逗号分隔。
* @param buttonConfigList 按钮权限配置列表(可选,不传不影响原有逻辑)。
* @param permissionDataRuleIdList 绑定的数据规则方案ID列表(可选,有值则校验并写入关联表)。
* @return 应答结果对象,包含新增角色的主键Id。
*/
@ApiOperationSupport(ignoreParameters = {"sysRoleDto.roleId", "sysRoleDto.createTimeStart", "sysRoleDto.createTimeEnd"})
@OperationLog(type = SysOperationLogType.ADD)
@PostMapping("/add")
public ResponseResult<Long> add(
@MyRequestBody SysRoleDto sysRoleDto, @MyRequestBody String menuIdListString) {
@MyRequestBody SysRoleDto sysRoleDto,
@MyRequestBody String menuIdListString,
@MyRequestBody List<RoleFormButtonDto> buttonConfigList,
@MyRequestBody List<Long> permissionDataRuleIdList) {
String errorMessage = MyCommonUtil.getModelValidationError(sysRoleDto);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
@ -75,20 +89,37 @@ public class SysRoleController { @@ -75,20 +89,37 @@ public class SysRoleController {
menuIdSet = result.getData().getObject("menuIdSet", new TypeReference<Set<Long>>(){});
}
sysRoleService.saveNew(sysRole, menuIdSet);
// 按钮权限配置(可选,不传不影响原有逻辑)
if (buttonConfigList != null && !buttonConfigList.isEmpty()) {
buttonConfigList.forEach(dto -> dto.setRoleId(sysRole.getRoleId()));
roleFormButtonService.batchSaveButtonConfig(buttonConfigList);
}
// 数据规则绑定(可选,不传不影响原有逻辑)
if (CollectionUtil.isNotEmpty(permissionDataRuleIdList)) {
CallResult bindResult = rolePermissionSchemaService.bindRoleSchemas(sysRole.getRoleId(), permissionDataRuleIdList);
if (!bindResult.isSuccess()) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, bindResult.getErrorMessage());
}
}
return ResponseResult.success(sysRole.getRoleId());
}
/**
* 更新角色操作。
*
* @param sysRoleDto 更新角色对象。
* @param menuIdListString 与当前角色Id绑定的menuId列表,多个menuId之间逗号分隔。
* @param sysRoleDto 更新角色对象。
* @param menuIdListString 与当前角色Id绑定的menuId列表,多个menuId之间逗号分隔。
* @param buttonConfigList 按钮权限配置列表(可选,不传不影响原有逻辑)。
* @param permissionDataRuleIdList 绑定的数据规则方案ID列表(可选,有值则校验后先删后增)。
* @return 应答结果对象。
*/
@OperationLog(type = SysOperationLogType.UPDATE)
@PostMapping("/update")
public ResponseResult<Void> update(
@MyRequestBody SysRoleDto sysRoleDto, @MyRequestBody String menuIdListString) {
@MyRequestBody SysRoleDto sysRoleDto,
@MyRequestBody String menuIdListString,
@MyRequestBody List<RoleFormButtonDto> buttonConfigList,
@MyRequestBody List<Long> permissionDataRuleIdList) {
String errorMessage = MyCommonUtil.getModelValidationError(sysRoleDto, Default.class, UpdateGroup.class);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
@ -111,6 +142,18 @@ public class SysRoleController { @@ -111,6 +142,18 @@ public class SysRoleController {
errorMessage = "更新失败,数据不存在,请刷新后重试!";
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
}
// 按钮权限配置(可选,不传不影响原有逻辑)
if (buttonConfigList != null && !buttonConfigList.isEmpty()) {
buttonConfigList.forEach(dto -> dto.setRoleId(sysRole.getRoleId()));
roleFormButtonService.batchSaveButtonConfig(buttonConfigList);
}
// 数据规则绑定(可选,不传不影响原有逻辑;传空列表则清空绑定)
if (permissionDataRuleIdList != null) {
CallResult bindResult = rolePermissionSchemaService.bindRoleSchemas(sysRole.getRoleId(), permissionDataRuleIdList);
if (!bindResult.isSuccess()) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, bindResult.getErrorMessage());
}
}
return ResponseResult.success();
}
@ -173,9 +216,14 @@ public class SysRoleController { @@ -173,9 +216,14 @@ public class SysRoleController {
}
SysRole sysRole = sysRoleService.getByIdWithRelation(roleId, MyRelationParam.full());
if (sysRole == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
SysRoleVo sysRoleVo = MyModelUtil.copyTo(sysRole, SysRoleVo.class);
// 返回角色绑定的数据规则方案关联关系
List<RolePermissionSchema> roleSchemaList = rolePermissionSchemaService.listByRoleId(roleId);
if (CollectionUtil.isNotEmpty(roleSchemaList)) {
sysRoleVo.setPermissionDataRuleIdList(roleSchemaList.stream().map(RolePermissionSchema::getSchemaId).collect(Collectors.toList()));
}
return ResponseResult.success(sysRoleVo);
}
@ -349,4 +397,39 @@ public class SysRoleController { @@ -349,4 +397,39 @@ public class SysRoleController {
}
return ResponseResult.success(sysRoleService.getSysPermCodeListWithDetail(roleId, permCode));
}
// /**
// * 查询角色关联的组织列表(用于编辑页回显,多对多)。
// *
// * @param roleId 角色Id。
// * @return 应答结果对象,包含角色关联的组织信息列表。
// */
// @GetMapping("/getRoleDept")
// public ResponseResult<List<SysDeptVo>> getRoleDept(@RequestParam Long roleId) {
// if (MyCommonUtil.existBlankArgument(roleId)) {
// return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
// }
// List<SysDeptVo> deptVoList = sysRoleService.getRoleDept(roleId);
// return ResponseResult.success(deptVoList);
// }
/**
* 根据组织ID分页查询关联角色(运行时使用)。
*
* @param deptId 组织Id。
* @param orderParam 排序参数。
* @param pageParam 分页参数。
* @return 应答结果对象,包含分页的角色列表。
*/
@PostMapping("/listRoleByDeptId")
public ResponseResult<MyPageData<SysRoleVo>> listRoleByDeptId(
@MyRequestBody Long deptId,
@MyRequestBody MyOrderParam orderParam,
@MyRequestBody MyPageParam pageParam) {
if (MyCommonUtil.existBlankArgument(deptId)) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
MyPageData<SysRoleVo> pageData = sysRoleService.listRoleByDeptId(deptId, orderParam, pageParam);
return ResponseResult.success(pageData);
}
}

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

@ -4,45 +4,41 @@ import apelet.common.core.annotation.MyRequestBody; @@ -4,45 +4,41 @@ import apelet.common.core.annotation.MyRequestBody;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.exception.MyRuntimeException;
import apelet.common.core.object.*;
import apelet.common.core.upload.*;
import apelet.common.core.upload.BaseUpDownloader;
import apelet.common.core.upload.UpDownloaderFactory;
import apelet.common.core.upload.UploadResponseInfo;
import apelet.common.core.upload.UploadStoreInfo;
import apelet.common.core.util.MyCommonUtil;
import apelet.common.core.util.MyModelUtil;
import apelet.common.core.util.MyPageUtil;
import apelet.common.eas.service.impl.EASSyncService;
import apelet.common.generator.utils.OrmGenDataSourceUtil;
import apelet.common.log.annotation.OperationLog;
import apelet.common.log.model.constant.SysOperationLogType;
import apelet.common.redis.cache.SessionCacheHelper;
import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import apelet.tenantadmin.config.ApplicationConfig;
import apelet.tenantadmin.upms.dto.RoleDeptItemDto;
import apelet.tenantadmin.upms.dto.SysUserDto;
import apelet.tenantadmin.upms.model.SysUser;
import apelet.tenantadmin.upms.service.SysUserService;
import apelet.tenantadmin.upms.vo.SysUserVo;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.fastjson.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.alibaba.fastjson.TypeReference;
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 用户管理操作控制器类。
@ -97,12 +93,9 @@ public class SysUserController { @@ -97,12 +93,9 @@ public class SysUserController {
if (!result.isSuccess()) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, result.getErrorMessage());
}
Set<Long> deptPostIdSet = result.getData().getObject("deptPostIdSet", new TypeReference<Set<Long>>() {
});
Set<Long> roleIdSet = result.getData().getObject("roleIdSet", new TypeReference<Set<Long>>() {
});
Set<Long> dataPermIdSet = result.getData().getObject("dataPermIdSet", new TypeReference<Set<Long>>() {
});
Set<Long> deptPostIdSet = result.getData().getObject("deptPostIdSet", new TypeReference<Set<Long>>(){});
Set<Long> roleIdSet = result.getData().getObject("roleIdSet", new TypeReference<Set<Long>>(){});
Set<Long> dataPermIdSet = result.getData().getObject("dataPermIdSet", new TypeReference<Set<Long>>(){});
sysUserService.saveNew(sysUser, roleIdSet, deptPostIdSet, dataPermIdSet);
return ResponseResult.success(sysUser.getUserId());
}
@ -131,12 +124,9 @@ public class SysUserController { @@ -131,12 +124,9 @@ public class SysUserController {
if (!result.isSuccess()) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, result.getErrorMessage());
}
Set<Long> deptPostIdSet = result.getData().getObject("deptPostIdSet", new TypeReference<Set<Long>>() {
});
Set<Long> roleIdSet = result.getData().getObject("roleIdSet", new TypeReference<Set<Long>>() {
});
Set<Long> dataPermIdSet = result.getData().getObject("dataPermIdSet", new TypeReference<Set<Long>>() {
});
Set<Long> deptPostIdSet = result.getData().getObject("deptPostIdSet", new TypeReference<Set<Long>>(){});
Set<Long> roleIdSet = result.getData().getObject("roleIdSet", new TypeReference<Set<Long>>(){});
Set<Long> dataPermIdSet = result.getData().getObject("dataPermIdSet", new TypeReference<Set<Long>>(){});
SysUser sysUserO = sysUserService.saveNew(sysUser, roleIdSet, deptPostIdSet, dataPermIdSet);
try {
@ -190,12 +180,9 @@ public class SysUserController { @@ -190,12 +180,9 @@ public class SysUserController {
if (!result.isSuccess()) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, result.getErrorMessage());
}
Set<Long> roleIdSet = result.getData().getObject("roleIdSet", new TypeReference<Set<Long>>() {
});
Set<Long> deptPostIdSet = result.getData().getObject("deptPostIdSet", new TypeReference<Set<Long>>() {
});
Set<Long> dataPermIdSet = result.getData().getObject("dataPermIdSet", new TypeReference<Set<Long>>() {
});
Set<Long> roleIdSet = result.getData().getObject("roleIdSet", new TypeReference<Set<Long>>(){});
Set<Long> deptPostIdSet = result.getData().getObject("deptPostIdSet", new TypeReference<Set<Long>>(){});
Set<Long> dataPermIdSet = result.getData().getObject("dataPermIdSet", new TypeReference<Set<Long>>(){});
if (!sysUserService.update(sysUser, originalUser, roleIdSet, deptPostIdSet, dataPermIdSet)) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
@ -203,6 +190,31 @@ public class SysUserController { @@ -203,6 +190,31 @@ public class SysUserController {
}
/**
* 分配用户组织角色。
* 先删除用户原有的角色关联(xy_sys_user_role),再按 roleDeptList 逐条写入。
*
* @param userId 用户Id。
* @param roleDeptList 角色-组织关联列表,每项含 roleId/deptId(均可为空,roleId 为空则跳过)。
* @return 应答结果对象。
*/
@OperationLog(type = SysOperationLogType.UPDATE)
@PostMapping("/assignRoleDept")
public ResponseResult<Void> assignRoleDept(@MyRequestBody Long userId, @MyRequestBody List<RoleDeptItemDto> roleDeptList) {
if (MyCommonUtil.existBlankArgument(userId)) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
if (!sysUserService.existId(userId)) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, "用户不存在,请刷新后重试!");
}
CallResult result = sysUserService.verifyRoleDeptMap(roleDeptList);
if (!result.isSuccess()) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, result.getErrorMessage());
}
sysUserService.assignRoleDept(userId, roleDeptList);
return ResponseResult.success();
}
/**
* 删除用户管理数据。
*
* @param userId 删除对象主键Id。
@ -482,6 +494,21 @@ public class SysUserController { @@ -482,6 +494,21 @@ public class SysUserController {
return ResponseResult.success(resultList);
}
/**
* 查询所有组织及其下角色列表,并标记用户是否拥有该角色。
*
* @param userId 用户Id。
* @return 应答结果对象,包含组织-角色树及用户勾选状态。
*/
@GetMapping("/listUserDept")
public ResponseResult<List<Map<String, Object>>> listUserDept(@RequestParam Long userId) {
if (MyCommonUtil.existBlankArgument(userId)) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
List<Map<String, Object>> result = sysUserService.listUserDept(userId);
return ResponseResult.success(result);
}
private ResponseResult<Void> doDelete(Long userId) {
String errorMessage;
// 验证关联Id的数据合法性

23
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/dto/RoleDeptItemDto.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
package apelet.tenantadmin.upms.dto;
import lombok.Data;
/**
* 用户-角色-组织关联参数对象,用于 assignRoleDept 接口接收前端传值。
*
* @author chenchuchuan
* @date 2026-07-28
*/
@Data
public class RoleDeptItemDto {
/**
* 角色Id(可为空,为空时跳过该条记录)。
*/
private String roleId;
/**
* 组织Id(可为空,表示不限定组织)。
*/
private String deptId;
}

6
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/dto/SysDeptDto.java

@ -43,4 +43,10 @@ public class SysDeptDto { @@ -43,4 +43,10 @@ public class SysDeptDto {
*/
@Schema(description = "父部门Id")
private Long parentId;
/**
* 类型:0-部门,1-组织。
*/
@Schema(description = "类型:0-部门,1-组织")
private Integer type;
}

1
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/model/SysDept.java

@ -41,7 +41,6 @@ public class SysDept { @@ -41,7 +41,6 @@ public class SysDept {
* 父部门Id。
*/
private Long parentId;
/**
* 逻辑删除标记字段(1: 正常 -1: 已删除)。
*/

6
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/model/SysUserRole.java

@ -25,4 +25,10 @@ public class SysUserRole { @@ -25,4 +25,10 @@ public class SysUserRole {
*/
@TableField(value = "role_id")
private Long roleId;
/**
* 组织Id(可为空,表示不限定组织)。
*/
@TableField(value = "dept_id")
private Long deptId;
}

15
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/SysRoleService.java

@ -2,8 +2,12 @@ package apelet.tenantadmin.upms.service; @@ -2,8 +2,12 @@ package apelet.tenantadmin.upms.service;
import apelet.common.core.base.service.IBaseService;
import apelet.common.core.object.CallResult;
import apelet.common.core.object.MyOrderParam;
import apelet.common.core.object.MyPageData;
import apelet.common.core.object.MyPageParam;
import apelet.tenantadmin.upms.model.SysRole;
import apelet.tenantadmin.upms.model.SysUserRole;
import apelet.tenantadmin.upms.vo.SysRoleVo;
import java.util.List;
import java.util.Map;
@ -104,4 +108,15 @@ public interface SysRoleService extends IBaseService<SysRole, Long> { @@ -104,4 +108,15 @@ public interface SysRoleService extends IBaseService<SysRole, Long> {
* @return 包含从角色到权限字的权限分配路径信息的查询结果列表。
*/
List<Map<String, Object>> getSysPermCodeListWithDetail(Long roleId, String permCode);
/**
* 根据组织ID分页查询关联角色。
*
* @param deptId 组织Id。
* @param orderParam 排序参数。
* @param pageParam 分页参数。
* @return 分页的角色列表。
*/
MyPageData<SysRoleVo> listRoleByDeptId(Long deptId, MyOrderParam orderParam, MyPageParam pageParam);
}

32
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/SysUserService.java

@ -3,6 +3,7 @@ package apelet.tenantadmin.upms.service; @@ -3,6 +3,7 @@ package apelet.tenantadmin.upms.service;
import apelet.common.core.base.service.IBaseService;
import apelet.common.core.object.CallResult;
import apelet.common.core.object.ResponseResult;
import apelet.tenantadmin.upms.dto.RoleDeptItemDto;
import apelet.tenantadmin.upms.model.SysUser;
import java.util.List;
@ -31,7 +32,7 @@ public interface SysUserService extends IBaseService<SysUser, Long> { @@ -31,7 +32,7 @@ public interface SysUserService extends IBaseService<SysUser, Long> {
* @param user 新增的用户对象。
* @param roleIdSet 用户角色Id集合。
* @param deptPostIdSet 部门岗位Id集合。
* @param dataPermIdSet 数据权限Id集合。
* @param deptIdSet 组织Id集合(写入SysUserRole.deptId)。
* @return 新增后的用户对象。
*/
SysUser saveNew(SysUser user, Set<Long> roleIdSet, Set<Long> deptPostIdSet, Set<Long> dataPermIdSet);
@ -228,8 +229,7 @@ public interface SysUserService extends IBaseService<SysUser, Long> { @@ -228,8 +229,7 @@ public interface SysUserService extends IBaseService<SysUser, Long> {
* @param dataPermIds 逗号分隔的数据权限Id列表字符串。
* @return 验证结果。
*/
CallResult verifyRelatedData(
SysUser sysUser, SysUser originalSysUser, String roleIds, String deptPostIds, String dataPermIds);
CallResult verifyRelatedData(SysUser sysUser, SysUser originalSysUser, String roleIds, String deptPostIds, String dataPermIds);
/**
* 根据云之家openId获取当前用户信息
@ -253,4 +253,30 @@ public interface SysUserService extends IBaseService<SysUser, Long> { @@ -253,4 +253,30 @@ public interface SysUserService extends IBaseService<SysUser, Long> {
boolean myUpdateBatch(List<SysUser> sysUserList);
ResponseResult<?> syncEasUSer();
/**
* 查询所有组织及其下角色列表,并标记用户是否拥有该角色。
*
* @param userId 用户Id。
* @return 组织-角色树列表,包含用户勾选状态。
*/
List<Map<String, Object>> listUserDept(Long userId);
/**
* 验证角色-组织映射数据的合法性。
*
* @param roleDeptList 角色-组织关联列表。
* @return 验证结果。
*/
CallResult verifyRoleDeptMap(List<RoleDeptItemDto> roleDeptList);
/**
* 分配用户组织角色。
* 先删除用户原有的角色关联,再按 roleDeptList 逐条写入 xy_sys_user_role。
* roleId 为空则跳过该条;deptId 为空则不限组织。
*
* @param userId 用户Id。
* @param roleDeptList 角色-组织关联列表。
*/
void assignRoleDept(Long userId, List<RoleDeptItemDto> roleDeptList);
}

15
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/impl/SysDeptServiceImpl.java

@ -3,14 +3,20 @@ package apelet.tenantadmin.upms.service.impl; @@ -3,14 +3,20 @@ package apelet.tenantadmin.upms.service.impl;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.core.base.service.BaseService;
import apelet.common.core.constant.GlobalDeletedFlag;
import apelet.common.core.object.*;
import apelet.common.core.object.MyOrderParam;
import apelet.common.core.object.MyPageData;
import apelet.common.core.object.MyPageParam;
import apelet.common.core.object.MyRelationParam;
import apelet.common.core.util.MyModelUtil;
import apelet.common.core.util.MyPageUtil;
import apelet.common.ext.base.BizWidgetDatasource;
import apelet.common.ext.constant.BizWidgetDatasourceType;
import apelet.common.ext.util.BizWidgetDatasourceExtHelper;
import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import apelet.tenantadmin.upms.dao.*;
import apelet.tenantadmin.upms.dao.SysDataPermDeptMapper;
import apelet.tenantadmin.upms.dao.SysDeptMapper;
import apelet.tenantadmin.upms.dao.SysDeptPostMapper;
import apelet.tenantadmin.upms.dao.SysDeptRelationMapper;
import apelet.tenantadmin.upms.model.*;
import apelet.tenantadmin.upms.service.SysDeptService;
import apelet.tenantadmin.upms.service.SysUserService;
@ -29,7 +35,10 @@ import org.springframework.stereotype.Service; @@ -29,7 +35,10 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**

49
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/impl/SysRoleServiceImpl.java

@ -4,14 +4,20 @@ import apelet.common.core.annotation.MyDataSource; @@ -4,14 +4,20 @@ import apelet.common.core.annotation.MyDataSource;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.core.base.service.BaseService;
import apelet.common.core.constant.GlobalDeletedFlag;
import apelet.common.core.object.*;
import apelet.common.core.object.CallResult;
import apelet.common.core.object.MyOrderParam;
import apelet.common.core.object.MyPageData;
import apelet.common.core.object.MyPageParam;
import apelet.common.core.util.MyModelUtil;
import apelet.common.core.util.MyPageUtil;
import apelet.common.ext.base.BizWidgetDatasource;
import apelet.common.ext.constant.BizWidgetDatasourceType;
import apelet.common.ext.util.BizWidgetDatasourceExtHelper;
import apelet.common.online.service.IRoleFormButtonService;
import apelet.common.online.service.IRolePermissionSchemaService;
import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import apelet.tenantadmin.config.DataSourceType;
import apelet.tenantadmin.upms.dao.SysDeptMapper;
import apelet.tenantadmin.upms.dao.SysRoleMapper;
import apelet.tenantadmin.upms.dao.SysRoleMenuMapper;
import apelet.tenantadmin.upms.dao.SysUserRoleMapper;
@ -20,10 +26,14 @@ import apelet.tenantadmin.upms.model.SysRoleMenu; @@ -20,10 +26,14 @@ import apelet.tenantadmin.upms.model.SysRoleMenu;
import apelet.tenantadmin.upms.model.SysUserRole;
import apelet.tenantadmin.upms.service.SysMenuService;
import apelet.tenantadmin.upms.service.SysRoleService;
import apelet.tenantadmin.upms.vo.SysRoleVo;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.page.PageMethod;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -55,9 +65,15 @@ public class SysRoleServiceImpl extends BaseService<SysRole, Long> implements Sy @@ -55,9 +65,15 @@ public class SysRoleServiceImpl extends BaseService<SysRole, Long> implements Sy
@Autowired
private SysMenuService sysMenuService;
@Autowired
private SysDeptMapper sysDeptMapper;
@Autowired
private IdGeneratorWrapper idGenerator;
@Autowired
private BizWidgetDatasourceExtHelper bizWidgetDatasourceExtHelper;
@Autowired
private IRoleFormButtonService roleFormButtonService;
@Autowired
private IRolePermissionSchemaService rolePermissionSchemaService;
@PostConstruct
private void registerBizWidgetDatasource() {
@ -169,6 +185,8 @@ public class SysRoleServiceImpl extends BaseService<SysRole, Long> implements Sy @@ -169,6 +185,8 @@ public class SysRoleServiceImpl extends BaseService<SysRole, Long> implements Sy
SysUserRole userRole = new SysUserRole();
userRole.setRoleId(roleId);
sysUserRoleMapper.delete(new QueryWrapper<>(userRole));
roleFormButtonService.removeByRoleId(roleId);
rolePermissionSchemaService.removeByRoleId(roleId);
return true;
}
@ -266,4 +284,33 @@ public class SysRoleServiceImpl extends BaseService<SysRole, Long> implements Sy @@ -266,4 +284,33 @@ public class SysRoleServiceImpl extends BaseService<SysRole, Long> implements Sy
public List<Map<String, Object>> getSysPermCodeListWithDetail(Long roleId, String permCode) {
return sysRoleMapper.getSysPermCodeListWithDetail(roleId, permCode);
}
@Override
public MyPageData<SysRoleVo> listRoleByDeptId(Long deptId, MyOrderParam orderParam, MyPageParam pageParam) {
LambdaQueryWrapper<SysUserRole> roleQuery = new LambdaQueryWrapper<>();
roleQuery.eq(SysUserRole::getDeptId, deptId);
List<SysUserRole> userRoleList = sysUserRoleMapper.selectList(roleQuery);
if (CollUtil.isEmpty(userRoleList)) {
return new MyPageData<>(Collections.emptyList(), 0L);
}
Set<Long> roleIdList = userRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toSet());
// 分页查询角色,使用IN条件
if (pageParam != null) {
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
}
String orderBy = MyOrderParam.buildOrderBy(orderParam, SysRole.class);
LambdaQueryWrapper<SysRole> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(SysRole::getRoleId, roleIdList);
if (StrUtil.isNotBlank(orderBy)) {
queryWrapper.last("ORDER BY " + orderBy);
}
List<SysRole> roleList = sysRoleMapper.selectList(queryWrapper);
List<SysRoleVo> roleVoList = MyModelUtil.copyCollectionTo(roleList, SysRoleVo.class);
long totalCount = 0L;
if (roleList instanceof Page) {
totalCount = ((Page<SysRole>) roleList).getTotal();
}
return MyPageUtil.makeResponseData(roleVoList, totalCount);
}
}

107
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/impl/SysUserServiceImpl.java

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
package apelet.tenantadmin.upms.service.impl;
import apelet.common.botp.db.util.DbUtil;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.core.base.service.BaseService;
import apelet.common.core.constant.GlobalDeletedFlag;
@ -13,10 +12,9 @@ import apelet.common.eas.service.impl.EASSyncService; @@ -13,10 +12,9 @@ import apelet.common.eas.service.impl.EASSyncService;
import apelet.common.ext.base.BizWidgetDatasource;
import apelet.common.ext.constant.BizWidgetDatasourceType;
import apelet.common.ext.util.BizWidgetDatasourceExtHelper;
import apelet.common.generator.utils.OrmGenDataSourceUtil;
import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import apelet.tenantadmin.upms.controller.SysUserController;
import apelet.tenantadmin.upms.dao.*;
import apelet.tenantadmin.upms.dto.RoleDeptItemDto;
import apelet.tenantadmin.upms.model.*;
import apelet.tenantadmin.upms.model.constant.SysUserStatus;
import apelet.tenantadmin.upms.service.*;
@ -32,6 +30,7 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; @@ -32,6 +30,7 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.page.PageMethod;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
@ -73,6 +72,10 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy @@ -73,6 +72,10 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy
@Autowired
private SysDeptService sysDeptService;
@Autowired
private SysDeptMapper sysDeptMapper;
@Autowired
private SysRoleMapper sysRoleMapper;
@Autowired
private IdGeneratorWrapper idGenerator;
@Autowired
private PasswordEncoder passwordEncoder;
@ -341,6 +344,45 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy @@ -341,6 +344,45 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy
return true;
}
@Transactional(rollbackFor = Exception.class)
@Override
public CallResult verifyRoleDeptMap(List<RoleDeptItemDto> roleDeptList) {
if (CollUtil.isEmpty(roleDeptList)) {
return CallResult.ok();
}
Set<Long> roleIdSet = roleDeptList.stream().filter(i -> StrUtil.isNotBlank(i.getRoleId())).map(i -> Long.valueOf(i.getRoleId())).collect(Collectors.toSet());
if (CollUtil.isNotEmpty(roleIdSet) && !sysRoleService.existAllPrimaryKeys(roleIdSet)) {
return CallResult.error("数据验证失败,存在不合法的角色,请刷新后重试!");
}
Set<Long> deptIdSet = roleDeptList.stream().filter(i -> StrUtil.isNotBlank(i.getDeptId())).map(i -> Long.valueOf(i.getDeptId())).collect(Collectors.toSet());
if (CollUtil.isNotEmpty(deptIdSet) && !sysDeptService.existAllPrimaryKeys(deptIdSet)) {
return CallResult.error("数据验证失败,存在不合法的组织,请刷新后重试!");
}
return CallResult.ok();
}
@Transactional(rollbackFor = Exception.class)
@Override
public void assignRoleDept(Long userId, List<RoleDeptItemDto> roleDeptList) {
// 先删除用户原有的角色关联
SysUserRole deletedUserRole = new SysUserRole();
deletedUserRole.setUserId(userId);
sysUserRoleMapper.delete(new QueryWrapper<>(deletedUserRole));
// 再按 roleDeptList 逐条写入,roleId 为空则跳过
if (CollUtil.isNotEmpty(roleDeptList)) {
for (RoleDeptItemDto item : roleDeptList) {
if (StringUtils.isAllBlank(item.getRoleId(), item.getDeptId())) {
continue;
}
SysUserRole userRole = new SysUserRole();
userRole.setUserId(userId);
userRole.setRoleId(StringUtils.isNotBlank(item.getRoleId()) ? Long.valueOf(item.getRoleId()) : null);
userRole.setDeptId(StrUtil.isNotBlank(item.getDeptId()) ? Long.valueOf(item.getDeptId()) : null);
sysUserRoleMapper.insert(userRole);
}
}
}
/**
* 获取单表查询结果。由于没有关联数据查询,因此在仅仅获取单表数据的场景下,效率更高。
* 如果需要同时获取关联数据,请移步(getSysUserListWithRelation)方法。
@ -525,7 +567,6 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy @@ -525,7 +567,6 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy
* @param originalSysUser 原有对象。
* @param roleIds 逗号分隔的角色Id列表字符串。
* @param deptPostIds 逗号分隔的部门岗位Id列表字符串。
* @param dataPermIds 逗号分隔的数据权限Id列表字符串。
* @return 验证结果。
*/
@Override
@ -756,5 +797,63 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy @@ -756,5 +797,63 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy
}
}
@Override
public List<Map<String, Object>> listUserDept(Long userId) {
// 1. 查询所有组织(不再区分type,部门和组织共用一张表)
List<SysDept> allDepts = sysDeptMapper.selectList(new LambdaQueryWrapper<>());
if (CollUtil.isEmpty(allDepts)) {
return Collections.emptyList();
}
// 2. 查询当前用户的角色记录(含deptId)
List<SysUserRole> userRoleList = sysUserRoleMapper.selectList(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getUserId, userId));
Set<Long> userRoleIdSet = userRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toSet());
Set<Long> userDeptIdSet = userRoleList.stream().map(SysUserRole::getDeptId).filter(Objects::nonNull).collect(Collectors.toSet());
// 3. 批量查询所有组织的角色关联(通过SysUserRole.deptId)
List<Long> allDeptIds = allDepts.stream().map(SysDept::getDeptId).collect(Collectors.toList());
LambdaQueryWrapper<SysUserRole> roleDeptQuery = new LambdaQueryWrapper<>();
roleDeptQuery.in(SysUserRole::getDeptId, allDeptIds);
List<SysUserRole> allRoleDeptList = sysUserRoleMapper.selectList(roleDeptQuery);
Map<Long, List<Long>> deptRoleMap = new HashMap<>();
Set<Long> allRoleIdSet = new HashSet<>();
if (CollUtil.isNotEmpty(allRoleDeptList)) {
for (SysUserRole ur : allRoleDeptList) {
deptRoleMap.computeIfAbsent(ur.getDeptId(), k -> new ArrayList<>()).add(ur.getRoleId());
allRoleIdSet.add(ur.getRoleId());
}
}
// 4. 批量查询所有角色
Map<Long, SysRole> roleMap = Collections.emptyMap();
if (CollUtil.isNotEmpty(allRoleIdSet)) {
List<SysRole> allRoleList = sysRoleMapper.selectBatchIds(allRoleIdSet);
roleMap = allRoleList.stream().collect(Collectors.toMap(SysRole::getRoleId, r -> r));
}
// 5. 组装结果
List<Map<String, Object>> result = new ArrayList<>();
for (SysDept dept : allDepts) {
Map<String, Object> item = new HashMap<>();
Map<String, Object> deptInfo = new HashMap<>();
deptInfo.put("deptId", dept.getDeptId());
deptInfo.put("deptName", dept.getDeptName());
item.put("deptinfo", deptInfo);
List<Long> roleIds = deptRoleMap.getOrDefault(dept.getDeptId(), Collections.emptyList());
boolean deptCheck = userDeptIdSet.contains(dept.getDeptId()) || roleIds.stream().anyMatch(userRoleIdSet::contains);
deptInfo.put("check", deptCheck);
List<Map<String, Object>> roleInfoList = new ArrayList<>();
for (Long roleId : roleIds) {
SysRole role = roleMap.get(roleId);
if (role != null) {
Map<String, Object> roleInfo = new HashMap<>();
roleInfo.put("roleId", role.getRoleId());
roleInfo.put("roleName", role.getRoleName());
roleInfo.put("check", userRoleIdSet.contains(roleId));
roleInfoList.add(roleInfo);
}
}
item.put("roleInfoList", roleInfoList);
result.add(item);
}
return result;
}
}

6
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/vo/SysDeptVo.java

@ -40,6 +40,12 @@ public class SysDeptVo { @@ -40,6 +40,12 @@ public class SysDeptVo {
private Long parentId;
/**
* 类型:0-部门,1-组织。
*/
@Schema(description = "类型:0-部门,1-组织")
private Integer type;
/**
* 创建者Id。
*/
@Schema(description = "创建者Id")

7
application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/vo/SysRoleVo.java

@ -36,4 +36,11 @@ public class SysRoleVo extends BaseVo { @@ -36,4 +36,11 @@ public class SysRoleVo extends BaseVo {
*/
@Schema(description = "角色与菜单关联对象列表")
private List<Map<String, Object>> sysRoleMenuList;
/**
* 角色绑定的数据规则方案ID列表。
*/
@Schema(description = "角色绑定的数据规则方案ID列表")
private List<Long> permissionDataRuleIdList;
}

161
application-tenant/tenant-admin/src/main/resources/application-config.yml

@ -0,0 +1,161 @@ @@ -0,0 +1,161 @@
server:
tomcat:
uri-encoding: UTF-8
threads:
max: 100
min-spare: 10
servlet:
encoding:
force: true
charset: UTF-8
enabled: true
logging:
config: classpath:logback-spring.xml
spring:
servlet:
multipart:
max-file-size: 50MB
max-request-size: 50MB
mvc:
converters:
preferred-json-mapper: fastjson
freemarker:
template-loader-path: classpath:/template/
cache: false
charset: UTF-8
check-template-location: true
content-type: text/html
expose-request-attributes: false
expose-session-attributes: false
request-context-attribute: request
suffix: .ftl
cloud:
sentinel:
transport:
# sentinel控制台地址。
# 由于8080端口容易与其他服务端口冲突,可以在启动sentinel控制台时动态修改,如:
# java -Dserver.port=8858 -jar sentinel-dashboard-$VERSION.jar
# 改为8858后,下面的配置端口也需要改为:localhost:8858。
dashboard: localhost:8858
# feign 配置
feign:
sentinel:
enabled: false
httpclient:
enabled: true
max-connections: 200
max-connections-per-route: 50
client:
config:
default:
# 输出feignclient的日志,缺省值为none,可选值为none/basic/headers/full
# 注意:需要把feignclient类所在包的日志级别设置为debug时才生效。如:
# logging:
# level:
# com.demo.multi: info
# com.demo.multi.coursepaperinterface.client: debug
loggerLevel: full
connectTimeout: 5000
readTimeout: 10000
compression:
request:
enabled: true
response:
enabled: true
common-core:
# 可选值为 mysql / postgresql / oracle / dm8 / kingbase
databaseType: mysql
swagger:
# 当enabled为false的时候,则可禁用swagger。
enabled: true
# 工程的基础包名。
basePackage: apelet
# 工程服务的基础包名。
serviceBasePackage:
title: 小猿微服务工程
description: 小猿微服务工程详情
version: 1.0
knife4j:
# 打开knife4j增强模式。
enable: true
aliyun:
oss:
enabled: false
expireSeconds: 1000
# 下面几项均需在申请阿里云OSS后,根据自己的实际情况进行配置。
endpoint:
accessKey:
secretKey:
bucketName:
minio:
enabled: false
endpoint:
accessKey:
secretKey:
bucketName:
# 暴露监控端点
management:
metrics:
tags:
application: ${spring.application.name}
endpoints:
web:
exposure:
include: '*'
jmx:
exposure:
include: '*'
endpoint:
# 与中间件相关的健康详情也会被展示
health:
show-details: always
configprops:
# 在/actuator/configprops中,所有包含password的配置,将用 * 隐藏。
# 如果不想隐藏任何配置项的值,可以直接使用如下被注释的空值。
# keys-to-sanitize:
keys-to-sanitize: password
server:
base-path: "/"
# 存储session数据的Redis,所有服务均需要,因此放到公共配置中。
# 根据实际情况,该Redis也可以用于存储其他数据。
common-redis:
# redisson的配置。每个服务可以自己的配置文件中覆盖此选项。
redisson:
# 如果该值为false,系统将不会创建RedissionClient的bean。
enabled: true
# mode的可用值为,single/cluster/sentinel/master-slave
mode: single
# single: 单机模式
# address: redis://localhost:6379
# cluster: 集群模式
# 每个节点逗号分隔,同时每个节点前必须以redis://开头。
# address: redis://localhost:6379,redis://localhost:6378,...
# sentinel:
# 每个节点逗号分隔,同时每个节点前必须以redis://开头。
# address: redis://localhost:6379,redis://localhost:6378,...
# master-slave:
# 每个节点逗号分隔,第一个为主节点,其余为从节点。同时每个节点前必须以redis://开头。
# address: redis://localhost:6379,redis://localhost:6378,...
address: redis://192.168.100.25:6379
# 链接超时,单位毫秒。
timeout: 6000
# 单位毫秒。分布式锁的超时检测时长。
# 如果一次锁内操作超该毫秒数,或在释放锁之前异常退出,Redis会在该时长之后主动删除该锁使用的key。
lockWatchdogTimeout: 60000
# redis 密码,空可以不填。
password:
database: 0
pool:
# 连接池数量。
poolSize: 20
# 连接池中最小空闲数量。
minIdle: 5

23
application-tenant/tenant-admin/src/main/resources/bootstrap.yml

@ -3,20 +3,15 @@ spring: @@ -3,20 +3,15 @@ spring:
name: tenant-admin
profiles:
active: @profile.name@
cloud:
nacos:
discovery:
server-addr: @discovery.server-addr@
config:
server-addr: @config.server-addr@
group: @config.group@
namespace: @config.namespace@
file-extension: yaml
# 共享配置文件,排序越高后,优先级越高。
shared-configs:
- data-id: application-${spring.profiles.active}.yaml
group: @config.group@
refresh: true
main:
allow-bean-definition-overriding: true
allow-circular-references: true
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
config:
import:
- classpath:tenant-admin-dev.yml
- classpath:application-config.yml # 引入共享配置

82
application-tenant/tenant-admin/src/main/resources/logback-spring.xml

@ -16,6 +16,7 @@ debug:当此属性设置为true时,将打印出logback内部日志信息, @@ -16,6 +16,7 @@ debug:当此属性设置为true时,将打印出logback内部日志信息,
<property name="LOG_PATTERN" value="[%-5level] [%d{yyyy-MM-dd HH:mm:ss}] [%thread] ==> [%tid] %msg%n" />
<property name="LOG_PATTERN_EX" value="[%-5level] [%d{yyyy-MM-dd HH:mm:ss}] T:[%X{traceId}] S:[%X{sessionId}] U:[%X{userId}] [%thread] ==> [%tid] %msg%n" />
<!--
<appender name="kafka_log" class="com.github.danielwegener.logback.kafka.KafkaAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
@ -27,6 +28,7 @@ debug:当此属性设置为true时,将打印出logback内部日志信息, @@ -27,6 +28,7 @@ debug:当此属性设置为true时,将打印出logback内部日志信息,
<deliveryStrategy class="com.github.danielwegener.logback.kafka.delivery.AsynchronousDeliveryStrategy" />
<producerConfig>bootstrap.servers=@kafka.log-server-addr@</producerConfig>
</appender>
-->
<!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
@ -89,48 +91,48 @@ debug:当此属性设置为true时,将打印出logback内部日志信息, @@ -89,48 +91,48 @@ debug:当此属性设置为true时,将打印出logback内部日志信息,
</logger>
<logger name="apelet" additivity="false" level="info">
<appender-ref ref="console" />
<appender-ref ref="kafka_log"/>
<appender-ref ref="file_log" />
</logger>
<!-- 这里将dao的日志级别设置为DEBUG,是为了SQL语句的输出 -->
<!-- <appender-ref ref="kafka_log"/> -->
<appender-ref ref="file_log" />
</logger>
<!-- 这里将dao的日志级别设置为DEBUG,是为了SQL语句的输出 -->
<logger name="apelet.tenantadmin.tenant.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<appender-ref ref="kafka_log"/>
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.tenantadmin.upms.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<appender-ref ref="kafka_log"/>
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.tenantadmin.app.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<appender-ref ref="kafka_log"/>
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.common.mobile.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<appender-ref ref="kafka_log"/>
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.common.online.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<appender-ref ref="kafka_log"/>
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.common.flow.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<appender-ref ref="kafka_log"/>
<appender-ref ref="file_log" />
</logger>
<logger name="org.flowable" additivity="false" level="info">
<appender-ref ref="console" />
<appender-ref ref="kafka_log"/>
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.common.report.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<appender-ref ref="kafka_log"/>
<!-- <appender-ref ref="kafka_log"/>-->
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.tenantadmin.upms.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<!-- <appender-ref ref="kafka_log"/>-->
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.tenantadmin.app.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<!-- <appender-ref ref="kafka_log"/>-->
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.common.mobile.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<!-- <appender-ref ref="kafka_log"/>-->
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.common.online.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<!-- <appender-ref ref="kafka_log"/>-->
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.common.flow.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<!-- <appender-ref ref="kafka_log"/>-->
<appender-ref ref="file_log" />
</logger>
<logger name="org.flowable" additivity="false" level="info">
<appender-ref ref="console" />
<!-- <appender-ref ref="kafka_log"/>-->
<appender-ref ref="file_log" />
</logger>
<logger name="apelet.common.report.dao" additivity="false" level="debug">
<appender-ref ref="console" />
<!-- <appender-ref ref="kafka_log"/>-->
<appender-ref ref="file_log" />
</logger>
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>

355
application-tenant/tenant-admin/src/main/resources/tenant-admin-dev.yml

@ -0,0 +1,355 @@ @@ -0,0 +1,355 @@
logging:
level:
# 这里设置的日志级别优先于logback-spring.xml文件Loggers中的日志级别。
apelet: info
config: classpath:logback-spring.xml
server:
port: 8075
application:
# Jwt令牌加密的签名值。该值的长度要超过10个字符(过短会报错)。
tokenSigningKey: tenantadmin-signing-key-signing-key
# Jwt令牌在Http Header中的键名称。
tokenHeaderKey: Authorization
# Jwt令牌刷新后在Http Header中的键名称。
refreshedTokenHeaderKey: RefreshedToken
# Jwt令牌过期时间(毫秒)。
expiration: 72000000
# 初始化密码。
defaultUserPassword: 123456
# 缺省的文件上传根目录。
uploadFileBaseDir: ./zz-resource/upload-files/tenant-admin
# 跨域的IP(http://192.168.10.10:8086)白名单列表,多个IP之间逗号分隔(* 表示全部信任,空白表示禁用跨域信任)。
credentialIpList: "*"
# 同步租户公共管理数据到消息队列的主题,再由同步消费者服务将数据同步到目标数据库中。
tenantSyncTopic: TenantSync
# Session会话和用户和权限在Redis中的过期时间(秒)。
sessionExpiredSeconds: 86400
# Session的数据权限信息在Redis中的过期时间(秒)。
dataPermExpiredSeconds: 86460
# 是否排他登录。
excludeLogin: false
common-eas:
urlPrefix: /tenantadmin/eas
common-ext:
urlPrefix: /tenantadmin/commonext
common-mobile:
# 注意不要以反斜杠(/)结尾。
urlPrefix: /tenantadmin/mobile
# 在线表单业务数据上传资源路径
uploadFileBaseDir: ./zz-resource/upload-files/mobile
common-online:
# 注意不要以反斜杠(/)结尾。
urlPrefix: /tenantadmin/online
# 打印接口的路径,不要以反斜杠(/)结尾。
printUrlPath: /tenantadmin/report/reportPrint/print
# 在线表单业务数据上传资源路径
uploadFileBaseDir: ./zz-resource/upload-files/online
# 如果为false,在线表单模块中所有Controller接口将不能使用。
operationEnabled: true
# 1: minio 2: aliyun-oss 3: qcloud-cos。
distributeStoreType: 2
# 调用render接口时候,是否打开一级缓存加速。
enableRenderCache: false
# 脱敏字段的掩码字符,只能为单个字符。
maskChar: '*'
# 下面的url列表,请保持反斜杠(/)结尾。
viewUrlList:
- ${common-online.urlPrefix}/onlineOperation/viewByDatasourceId/
- ${common-online.urlPrefix}/onlineOperation/viewByOneToManyRelationId/
- ${common-online.urlPrefix}/onlineOperation/listByDatasourceId/
- ${common-online.urlPrefix}/onlineOperation/listByOneToManyRelationId/
- ${common-online.urlPrefix}/onlineOperation/exportByDatasourceId/
- ${common-online.urlPrefix}/onlineOperation/exportByOneToManyRelationId/
- ${common-online.urlPrefix}/onlineOperation/downloadDatasource/
- ${common-online.urlPrefix}/onlineOperation/downloadOneToManyRelation/
- ${common-online.urlPrefix}/onlineOperation/print/
editUrlList:
- ${common-online.urlPrefix}/onlineOperation/addDatasource/
- ${common-online.urlPrefix}/onlineOperation/addOneToManyRelation/
- ${common-online.urlPrefix}/onlineOperation/updateDatasource/
- ${common-online.urlPrefix}/onlineOperation/updateOneToManyRelation/
- ${common-online.urlPrefix}/onlineOperation/deleteDatasource/
- ${common-online.urlPrefix}/onlineOperation/deleteOneToManyRelation/
- ${common-online.urlPrefix}/onlineOperation/deleteBatchDatasource/
- ${common-online.urlPrefix}/onlineOperation/deleteBatchOneToManyRelation/
- ${common-online.urlPrefix}/onlineOperation/uploadDatasource/
- ${common-online.urlPrefix}/onlineOperation/uploadOneToManyRelation/
common-flow:
# 请慎重修改urlPrefix的缺省配置,注意不要以反斜杠(/)结尾。如必须修改其他路径,请同步修改数据库脚本。
urlPrefix: /tenantadmin/flow
# 如果为false,流程模块的所有Controller中的接口将不能使用。
operationEnabled: true
common-report:
# 注意不要以反斜杠(/)结尾。
urlPrefix: /tenantadmin/report
# 如果为false,报表模块的所有Controller中的接口将不能使用。
operationEnabled: true
# 该配置项仅当打印模板中,打印图片字段时,才会需要。
# 这里的url配置只是一个示例,并不能保证开箱即用,代码示例和说明可参考common-report模块
# example包内的ReportExampleController中的代码和详细说明。
imageDownloadUrl: "http://localhost:8083/tenantadmin/report/example/downloadDirectly"
# 该配置用于报表部分的数据权限过滤功能。
# 当前数据权限需要获取指定部门Ids的所有下级子部门Ids的时候,会调用该接口。
dataPermAllChildrenDeptIdUrl: "http://localhost:8083/tenantadmin/upms/sysDept/listAllChildDeptIdByParentIds"
# 当前服务是否为可视化后台服务。
isVisualization: false
# 下面的url列表,请保持反斜杠(/)结尾。
viewUrlList:
- ${common-report.urlPrefix}/reportOperation/listData/
datasync:
producer:
# upms服务作为生产者,需要打开该选项。
enabled: true
# Redisson 分布式锁的名称。
# 当存在多个发送者实例时,需要分布式锁控制只有一个实例可以执行消息流水读取并发送到MQ的操作。
distributeLockName: tenant-datasync
# 每次读取生产者消息流水的等待时间间隔,单位分钟。
fetchTransIntervalMins: 2
# 每次读取生产者消息流水的记录数量。
fetchCount: 10
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: apelet_saas
datafilter:
tenant:
enabled: false
dataperm:
enabled: true
# 在拼接数据权限过滤的SQL时,我们会用到sys_dept_relation表,该表的前缀由此配置项指定。
# 如果没有前缀,请使用 "" 。
deptRelationTablePrefix: xy_
# 是否在每次执行数据权限查询过滤时,都要进行菜单Id和URL之间的越权验证。
enableMenuPermVerify: true
sequence:
# 是否使用基于美团Leaf的分布式Id生成器。
advanceIdGenerator: true
# 多个zk服务之间逗号分隔。
zkAddress: localhost:2181
# 与本机的ip一起构成zk中标识不同服务实例的key值。
idPort: 18083
# zk中生成WorkNode的路径。不同的业务可以使用不同的路径,以免冲突。
zkPath: apelet
common-log:
# 操作日志配置,对应配置文件common-log/OperationLogProperties.java
operation-log:
enabled: false
kafkaTopic: SysOperationLog-TenantAdmin
mybatis-plus:
mapper-locations: classpath:apelet/tenantadmin/tenant/dao/mapper/*Mapper.xml,apelet/tenantadmin/upms/dao/mapper/*Mapper.xml,apelet/common/report/dao/mapper/*Mapper.xml,apelet/common/datasync/dao/mapper/*Mapper.xml,apelet/common/log/dao/mapper/*Mapper.xml,apelet/common/flow/dao/mapper/*Mapper.xml,apelet/common/online/dao/mapper/*Mapper.xml,apelet/common/generator/dao/mapper/*Mapper.xml,apelet/common/botp/dao/mapper/*Mapper.xml,apelet/tenantadmin/app/dao/mapper/*Mapper.xml,apelet/common/mobile/dao/mapper/*Mapper.xml,apelet/common/eas/dao/mapper/*Mapper.xml,classpath:mapper/*Mapper.xml
type-aliases-package: apelet.tenantadmin.*.model,apelet.common.report.model,apelet.common.datasync.model,apelet.common.log.model,apelet.common.flow.model,apelet.common.online.model,apelet.common.mobile.model,apelet.common.eas.model
global-config:
db-config:
logic-delete-value: -1
logic-not-delete-value: 1
# 自动分页的配置
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: false
params: count=countSql
aj:
captcha:
# 缓存local/redis..
cache-type: redis
# 验证码类型default两种都实例化。
type: default
# 右下角水印文字(我的水印)
water-mark: 我的水印
#点选字体样式 默认Font.BOLD
font-style: 1
#点选字体字体大小
font-size: 25
# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/jigsaw
jigsaw: classpath:images/jigsaw
# 支持项目路径,以classpath:开头,取resource目录下路径,例:classpath:images/pic-click
pic-click: classpath:images/pic-click
history-data-clear-enable: false
# 接口请求次数一分钟限制是否开启 true|false
req-frequency-limit-enable: false
# 验证失败5次,get接口锁定
req-get-lock-limit: 5
# 验证失败后,锁定时间间隔,s
req-get-lock-seconds: 360
# get接口一分钟内请求数限制
req-get-minute-limit: 30
# check接口一分钟内请求数限制
req-check-minute-limit: 30
# verify接口一分钟内请求数限制
req-verify-minute-limit: 60
# 校验滑动拼图允许误差偏移量(默认5像素)
slip-offset: 5
# aes加密坐标开启或者禁用(true|false)
aes-status: true
# 滑动干扰项(0/1/2)
interference-options: 2
# 开发数据库相关配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 租户管理数据源。
tenant-admin:
url: jdbc:mysql://192.168.100.35:3306/apelet_admin_xk?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: X7&9p8L2@6z4K7!8
# 租户业务平台的通用业务数据,如字典、在线表单、流程、报表的定义等数据。
tenant-common:
url: jdbc:mysql://192.168.100.35:3306/apelet_admin_xk?characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
username: root
password: X7&9p8L2@6z4K7!8
# 默认生成的操作日志数据源配置。
operation-log:
url: jdbc:mysql://192.168.100.35:3306/apelet_admin_xk?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: X7&9p8L2@6z4K7!8
driverClassName: com.mysql.cj.jdbc.Driver
name: tenant-admin
initialSize: 10
minIdle: 10
maxActive: 50
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
maxOpenPreparedStatements: 20
validationQuery: SELECT 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
filters: stat,wall
useGlobalDataSourceStat: true
web-stat-filter:
enabled: true
url-pattern: "/*"
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*,/actuator/*,/ai/*"
stat-view-servlet:
enabled: true
urlPattern: /druid/*
resetEnable: true
eas:
config:
easurl:
userName:
password: 1
dcName:
dbType: 2
ltpaToken: C:\work\company\project\plat\orm\apelet_saas\common\common-eas\src\main\resources\LtpaToken.properties
#easurl: https://ehr.jztweb.com
#userName: SSQadmin
#password: SSQadmin
#dcName: EHR_001
#dbType: 2
#ltpaToken: C:\work\company\project\plat\orm\apelet_saas\common\common-eas\src\main\resources\LtpaToken_zs.properties
hubCloud:
#测试
#secret: RXmdRE0lf5ngGfLJSSj4
#appId: 501019058
secret:
appId:
dingTalk:
secret: 95gAMy_7nWGqRwFbt_kLzRpI9BqKhB7LM3SPgpcvV8Q7s0Az_PMei_yRxWE6LmhV
project:
server:
url: https://jgzy-api.nbjgcloud.com
sync:
url: http://10.22.98.37:9999
loginUrl: /sys/login
loginName: nbjg
loginPassword: NBjj123456
userUrl:
projectUrl:
ai:
dify:
baseUrl:
datasetKey:
appKey:
appUrl:
pandoc:
panurl: pandoc
genurl:
templateFileName: sample-orgin-change.docx
luaFileName: parse-html.lua
preUrl:
mineru:
workspace:
batchfile:
timeout: 10000
baseurl: http://192.168.77.6:8000
pdfurl: /file_parse
mdSaveDir: /app/work/pdf
cqurl:
config:
url:
gys:
#client_id: xy_mobile
#client_secret: asdasdnsandlkadnlkas!@#@!3WWW
#username: sunquan
client_id:
client_secret:
username:
accountId:
msg:
notice:
# 是否启用定时消息提醒(true启用,false禁用)
enabled: true
# 定时任务cron表达式,控制提醒执行频率
# 默认值:0 0 * * * ? (每小时整点执行)
# 常用示例:
# 0 0 * * * ? - 每小时执行一次
# 0 0 */2 * * ? - 每2小时执行一次
# 0 0 9 * * ? - 每天上午9点执行
# 0 0 9,18 * * ? - 每天上午9点和下午6点执行
# 0 */30 * * * ? - 每30分钟执行一次
cron: "0 0 * * * ?"
# 默认提醒消息内容
default-message: hello
# ====================================
# 短信通知配置
# ====================================
sms:
# 是否启用短信通知
enabled: false
# 短信API地址(根据实际短信服务商配置)
api-url: https://sms.example.com/send
# 短信API密钥
api-key: your-sms-api-key
# 短信签名
sign: "【您的签名】"
# ====================================
# 邮箱通知配置
# ====================================
email:
# 是否启用邮箱通知
enabled: true
# SMTP服务器地址
smtp-host: smtp.qq.com
# SMTP端口(25或465或587)
smtp-port: 465
# 发件人邮箱地址
from-email: 287962685@qq.com
# 发件人密码或授权码
password: evccxezdmdnpbhbh
# 是否启用SSL(如果使用465端口,建议启用SSL)
ssl-enabled: true

2
application-tenant/tenant-sync/pom.xml

@ -38,7 +38,7 @@ @@ -38,7 +38,7 @@
<dependency>
<groupId>apelet</groupId>
<artifactId>common-online</artifactId>
<version>1.0.0</version>
<version>1.0.4</version>
</dependency>
</dependencies>

4
application/upms/upms-service/pom.xml

@ -53,12 +53,12 @@ @@ -53,12 +53,12 @@
<dependency>
<groupId>apelet</groupId>
<artifactId>common-online</artifactId>
<version>1.0.0</version>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>apelet</groupId>
<artifactId>common-flow-online</artifactId>
<version>1.0.0</version>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>apelet</groupId>

8
common/common-flow-online/pom.xml

@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>common-flow-online</artifactId>
<version>1.0.0</version>
<version>1.0.2</version>
<name>common-flow-online</name>
<packaging>jar</packaging>
@ -18,17 +18,17 @@ @@ -18,17 +18,17 @@
<dependency>
<groupId>apelet</groupId>
<artifactId>common-flow</artifactId>
<version>1.0.0</version>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>apelet</groupId>
<artifactId>common-online</artifactId>
<version>1.0.0</version>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>apelet</groupId>
<artifactId>common-orm</artifactId>
<version>1.0.0</version>
<version>1.0.2</version>
</dependency>
</dependencies>
</project>

118
common/common-flow-online/src/main/java/apelet/common/flow/online/controller/FlowOnlineOperationController.java

@ -4,9 +4,15 @@ import apelet.common.core.annotation.DisableDataFilter; @@ -4,9 +4,15 @@ import apelet.common.core.annotation.DisableDataFilter;
import apelet.common.core.annotation.MyRequestBody;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.object.*;
import apelet.common.core.util.*;
import apelet.common.flow.constant.*;
import apelet.common.core.util.MyCommonUtil;
import apelet.common.core.util.MyModelUtil;
import apelet.common.core.util.MyPageUtil;
import apelet.common.flow.constant.FlowApprovalType;
import apelet.common.flow.constant.FlowConstant;
import apelet.common.flow.constant.FlowTaskStatus;
import apelet.common.flow.constant.FlowTaskType;
import apelet.common.flow.dto.FlowTaskCommentDto;
import apelet.common.flow.dto.FlowTaskDto;
import apelet.common.flow.dto.FlowWorkOrderDto;
import apelet.common.flow.exception.FlowOperationException;
import apelet.common.flow.model.*;
@ -15,7 +21,10 @@ import apelet.common.flow.online.service.FlowEntryOnlineService; @@ -15,7 +21,10 @@ import apelet.common.flow.online.service.FlowEntryOnlineService;
import apelet.common.flow.online.service.FlowOnlineOperationService;
import apelet.common.flow.service.*;
import apelet.common.flow.util.FlowOperationHelper;
import apelet.common.flow.vo.*;
import apelet.common.flow.vo.FlowEntryVo;
import apelet.common.flow.vo.FlowTaskVo;
import apelet.common.flow.vo.FlowWorkOrderVo;
import apelet.common.flow.vo.TaskInfoVo;
import apelet.common.generator.utils.OrmGenDataSourceUtil;
import apelet.common.log.annotation.OperationLog;
import apelet.common.log.model.constant.SysOperationLogType;
@ -44,8 +53,8 @@ import com.github.pagehelper.page.PageMethod; @@ -44,8 +53,8 @@ import com.github.pagehelper.page.PageMethod;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
@ -1434,4 +1443,105 @@ public class FlowOnlineOperationController { @@ -1434,4 +1443,105 @@ public class FlowOnlineOperationController {
}
return null;
}
/**
* 批量提交流程的用户任务。
* 该接口无需数据权限过滤,因此用DisableDataFilter注解标注。如果当前系统没有支持数据权限过滤,该注解不会有任何影响。
*
* @param flowTaskDtos 批量提交统一请求DTO,承载全部请求参数
* @param flowTaskCommentDto 流程审批数据。
* @return 应答结果对象。
*/
@DisableDataFilter
@OperationLog(type = SysOperationLogType.SUBMIT_TASK)
@PostMapping("/submitBatch")
public ResponseResult<Void> submitBatch(
@MyRequestBody(required = true) List<FlowTaskDto> flowTaskDtos,
@MyRequestBody(required = true) FlowTaskCommentDto flowTaskCommentDto) {
FlowTaskComment flowTaskComment = BeanUtil.copyProperties(flowTaskCommentDto, FlowTaskComment.class);
// 循环处理每一条流程任务
for (FlowTaskDto flowTaskDto : flowTaskDtos) {
String processInstanceId = flowTaskDto.getProcessInstanceId();
String taskId = flowTaskDto.getTaskId();
String errorMessage;
// 验证流程任务的合法性。
Task task;
try {
task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId);
} catch (Exception e) {
log.error("批量提交-获取任务失败 processInstanceId:{}, taskId:{}", processInstanceId, taskId, e);
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED,
"流程实例[" + processInstanceId + "]任务[" + taskId + "]不存在或已结束");
}
ResponseResult<TaskInfoVo> taskInfoResult = flowOperationHelper.verifyAndGetRuntimeTaskInfo(task);
if (!taskInfoResult.isSuccess()) {
return ResponseResult.errorFrom(taskInfoResult);
}
TaskInfoVo taskInfo = taskInfoResult.getData();
// 验证在线表单及其关联数据源的合法性。
ResponseResult<OnlineDatasource> datasourceResult = this.verifyAndGetOnlineDatasource(taskInfo.getFormId());
if (!datasourceResult.isSuccess()) {
return ResponseResult.errorFrom(datasourceResult);
}
CallResult assigneeVerifyResult = flowApiService.verifyAssigneeOrCandidateAndClaim(task);
if (!assigneeVerifyResult.isSuccess()) {
return ResponseResult.errorFrom(assigneeVerifyResult);
}
OnlineDatasource datasource = datasourceResult.getData();
ProcessInstance instance = flowApiService.getProcessInstance(processInstanceId);
String dataId = instance.getBusinessKey();
// 从当前循环FlowTaskDto获取本条任务独立数据 ==========
JSONObject itemTaskVar = flowTaskDto.getTaskVariableData();
JSONObject itemMasterData = flowTaskDto.getMasterData();
JSONObject itemSlaveData = flowTaskDto.getSlaveData();
JSONObject itemCopyData = flowTaskDto.getCopyData();
// 拷贝变量,防止多条任务互相污染变量对象
JSONObject currentTaskVariableData = itemTaskVar == null ? new JSONObject() : new JSONObject(itemTaskVar);
// 这里把传阅数据放到任务变量中
if (MapUtil.isNotEmpty(itemCopyData)) {
currentTaskVariableData.put(FlowConstant.COPY_DATA_KEY, itemCopyData);
}
if (StrUtil.isBlank(dataId)) {
ResponseResult<Void> submitNewTaskResult = this.submitNewTask(processInstanceId, taskId,
flowTaskComment, currentTaskVariableData, datasource, itemMasterData, itemSlaveData);
if (!submitNewTaskResult.isSuccess()) {
return submitNewTaskResult;
}
continue;
}
try {
if (StrUtil.equals(flowTaskComment.getApprovalType(), FlowApprovalType.TRANSFER)
&& StrUtil.isBlank(flowTaskComment.getDelegateAssignee())) {
errorMessage = "数据验证失败,加签或转办任务指派人不能为空!!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
// 如果本次请求中包含从表数据,则一同插入。
ResponseResult<Map<OnlineDatasourceRelation, List<JSONObject>>> slaveDataListResult =
onlineOperationHelper.buildSlaveDataList(datasource.getDatasourceId(), itemSlaveData);
if (!slaveDataListResult.isSuccess()) {
return ResponseResult.errorFrom(slaveDataListResult);
}
flowOnlineOperationService.updateAndTakeTask(
task, flowTaskComment, currentTaskVariableData, datasource, itemMasterData, dataId, slaveDataListResult.getData());
} catch (FlowOperationException e) {
log.error("批量提交-执行任务失败 processInstanceId:{}, taskId:{}", processInstanceId, taskId, e);
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED,
"流程实例[" + processInstanceId + "]任务[" + taskId + "]处理失败:" + e.getMessage());
}
}
// 全部任务处理完成
return ResponseResult.success();
}
}

2
common/common-flow-online/src/main/java/apelet/common/flow/online/listener/ApprovalSettingsByCreateListener.java

@ -3,7 +3,6 @@ package apelet.common.flow.online.listener; @@ -3,7 +3,6 @@ package apelet.common.flow.online.listener;
import apelet.common.core.util.ApplicationContextHolder;
import apelet.common.flow.model.FlowEntry;
import apelet.common.flow.model.FlowNodeTasks;
import apelet.common.flow.model.FlowWorkOrder;
import apelet.common.flow.service.FlowEntryService;
import apelet.common.flow.service.FlowNodeTasksService;
import apelet.common.flow.service.FlowWorkOrderService;
@ -55,6 +54,7 @@ public class ApprovalSettingsByCreateListener implements TaskListener { @@ -55,6 +54,7 @@ public class ApprovalSettingsByCreateListener implements TaskListener {
}else if(operationType.equals("reject")) {
setList.add(6);
}else if(operationType.equals("refuse")){
setList.add(8);
}
}
setList.add(3);

11
common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowEntryOnlineServiceImpl.java

@ -12,7 +12,10 @@ import apelet.common.flow.model.constant.FlowEntryStatus; @@ -12,7 +12,10 @@ import apelet.common.flow.model.constant.FlowEntryStatus;
import apelet.common.flow.object.*;
import apelet.common.flow.online.listener.ApprovalSettingsByCreateListener;
import apelet.common.flow.online.service.FlowEntryOnlineService;
import apelet.common.flow.service.*;
import apelet.common.flow.service.FlowApiService;
import apelet.common.flow.service.FlowCategoryService;
import apelet.common.flow.service.FlowEntryVariableService;
import apelet.common.flow.service.FlowTaskExtService;
import apelet.common.flow.util.BaseFlowIdentityExtHelper;
import apelet.common.flow.util.FlowCustomExtFactory;
import apelet.common.flow.util.FlowRedisKeyUtil;
@ -258,6 +261,12 @@ public class FlowEntryOnlineServiceImpl implements FlowEntryOnlineService { @@ -258,6 +261,12 @@ public class FlowEntryOnlineServiceImpl implements FlowEntryOnlineService {
FlowTaskPostCandidateGroup.buildCandidateGroupList(groupDataList);
userTask.setCandidateGroups(candidateGroupList);
}
// 多实例会签节点:如果没有通过前置"会签"按钮写入 assigneeList,
// 则自动从 zz_flow_task_ext.candidate_usernames 中读取审批人列表。
if (userTask.hasMultiInstanceLoopCharacteristics()) {
flowApiService.addExecutionListener(userTask, MultiInstanceAssigneeListener.class, "start", null);
}
this.processFlowTaskExtListener(userTask, t);
}
}

66
common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineBusinessServiceImpl.java

@ -3,22 +3,29 @@ package apelet.common.flow.online.service.impl; @@ -3,22 +3,29 @@ package apelet.common.flow.online.service.impl;
import apelet.common.core.annotation.MyDataSource;
import apelet.common.core.constant.ApplicationConstant;
import apelet.common.flow.base.service.BaseFlowOnlineService;
import apelet.common.flow.model.FlowEntry;
import apelet.common.flow.model.FlowWorkOrder;
import apelet.common.flow.online.object.TransactionalFlowBusinessData;
import apelet.common.flow.service.FlowEntryService;
import apelet.common.flow.util.FlowCustomExtFactory;
import apelet.common.generator.model.OnlFormHead;
import apelet.common.generator.service.IOnlFormHeadService;
import apelet.common.online.exception.OnlineRuntimeException;
import apelet.common.online.model.*;
import apelet.common.online.model.constant.FieldKind;
import apelet.common.online.service.*;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
/**
* 在线表单和流程监听器进行数据对接时的服务实现类。
@ -34,13 +41,20 @@ public class FlowOnlineBusinessServiceImpl implements BaseFlowOnlineService { @@ -34,13 +41,20 @@ public class FlowOnlineBusinessServiceImpl implements BaseFlowOnlineService {
@Autowired
private FlowCustomExtFactory flowCustomExtFactory;
@Autowired
private FlowEntryService flowEntryService;
@Autowired
private OnlineTableService onlineTableService;
@Autowired
private OnlinePageService onlinePageService;
@Autowired
private OnlineDatasourceService onlineDatasourceService;
@Autowired
private OnlineDatasourceRelationService onlineDatasourceRelationService;
@Autowired
private OnlineOperationService onlineOperationService;
@Autowired
private IOnlFormHeadService onlFormHeadService;
@PostConstruct
public void doRegister() {
@ -92,6 +106,58 @@ public class FlowOnlineBusinessServiceImpl implements BaseFlowOnlineService { @@ -92,6 +106,58 @@ public class FlowOnlineBusinessServiceImpl implements BaseFlowOnlineService {
onlineOperationService.delete(onlineTable, relationList, dataId);
}
@Override
public String getMasterTableName(Long pageId) {
OnlinePage page = onlinePageService.getById(pageId);
if (page == null || page.getMasterTableId() == null) {
return null;
}
OnlFormHead onlFormHead = onlFormHeadService.getById(page.getMasterTableId());
return onlFormHead == null || StringUtils.isBlank(onlFormHead.getTableTxt()) ? null : onlFormHead.getTableTxt();
}
@Override
public String getBusinessData(FlowWorkOrder workOrder, FlowEntry flowEntry) {
// 优先使用工单上的 onlineTableId;部分在线表单启动路径不会回填该字段,此时通过流程绑定的在线表单 pageId 解析主表。
OnlineTable onlineTable = this.resolveMasterTable(workOrder, flowEntry);
if (onlineTable == null || workOrder == null || workOrder.getBusinessKey() == null) {
return null;
}
Map<String, Object> dataMap = onlineOperationService.getMasterData(onlineTable, null, null, workOrder.getBusinessKey());
return dataMap == null ? null : JSON.toJSONString(dataMap);
}
/**
* 解析在线表单工作流的主表。优先取工单上的 onlineTableId,为空时通过 FlowEntry.pageId → 页面关联数据源的主表Id 兜底。
* 注意:OnlinePage.masterTableId 是表单元数据(OnlFormHead)的Id,不能直接作为 OnlineTable 的tableId 使用。
*
* @param workOrder 流程工单对象。
* @return 在线表单主表,无法解析返回null。
*/
private OnlineTable resolveMasterTable(FlowWorkOrder workOrder, FlowEntry flowEntry) {
if (workOrder == null && flowEntry == null) {
return null;
}
if (workOrder != null) {
if (workOrder.getOnlineTableId() != null) {
return onlineTableService.getOnlineTableFromCache(workOrder.getOnlineTableId());
}
if (StringUtils.isBlank(workOrder.getProcessDefinitionKey())) {
return null;
}
flowEntry = flowEntryService.getFlowEntryFromCache(workOrder.getProcessDefinitionKey());
}
if (flowEntry == null || flowEntry.getPageId() == null) {
return null;
}
// 参考 OnlineOperationController#getEntryListByDatasourceId:通过页面关联的数据源解析主表。
List<OnlineDatasource> datasourceList = onlineDatasourceService.getOnlineDatasourceListByPageId(flowEntry.getPageId(), null, null);
if (CollUtil.isEmpty(datasourceList) || datasourceList.get(0).getMasterTableId() == null) {
return null;
}
return onlineTableService.getOnlineTableFromCache(datasourceList.get(0).getMasterTableId());
}
private void handleTransactionalFlowBusinessData(FlowWorkOrder workOrder, String desc) {
TransactionalFlowBusinessData eventData = TransactionalFlowBusinessData.getFromRequestAttribute();
if (eventData != null) {

2
common/common-flow/pom.xml

@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>common-flow</artifactId>
<version>1.0.0</version>
<version>1.0.2</version>
<name>common-flow</name>
<packaging>jar</packaging>

22
common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
package apelet.common.flow.base.service;
import apelet.common.flow.model.FlowEntry;
import apelet.common.flow.model.FlowWorkOrder;
/**
@ -23,4 +24,25 @@ public interface BaseFlowOnlineService { @@ -23,4 +24,25 @@ public interface BaseFlowOnlineService {
* @param workOrder 工单对象。
*/
void deleteBusinessData(FlowWorkOrder workOrder);
/**
* 根据在线表单页面Id,获取在线表单主表的表名。
*
* @param pageId 在线表单页面Id。
* @return 主表表名,如果无法获取返回null。
*/
default String getMasterTableName(Long pageId) {
return null;
}
/**
* 获取在线表单工作流的业务主表数据。
*
* @param workOrder 工单对象。
* @param flowEntry 流程节点对象。
* @return JSON格式的业务主表数据,如果无法获取返回null。
*/
default String getBusinessData(FlowWorkOrder workOrder, FlowEntry flowEntry) {
return null;
}
}

5
common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java

@ -209,6 +209,11 @@ public class FlowConstant { @@ -209,6 +209,11 @@ public class FlowConstant {
public static final String USER_TASK_REJECT_TYPE_KEY = "rejectType";
/**
* 流程用户任务标题模板的Key。
*/
public static final String USER_TASK_TITLE_TEMPLATE_KEY = "title";
/**
* 驳回时携带的变量数据。
*/
public static final String REJECT_TO_SOURCE_DATA_VAR = "rejectData";

19
common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java

@ -7,6 +7,7 @@ import apelet.common.core.util.MyCommonUtil; @@ -7,6 +7,7 @@ import apelet.common.core.util.MyCommonUtil;
import apelet.common.core.util.MyModelUtil;
import apelet.common.core.util.MyPageUtil;
import apelet.common.core.validator.UpdateGroup;
import apelet.common.flow.constant.FlowConstant;
import apelet.common.flow.constant.FlowTaskType;
import apelet.common.flow.dto.FlowEntryDto;
import apelet.common.flow.model.FlowEntry;
@ -24,12 +25,14 @@ import cn.hutool.core.util.ObjectUtil; @@ -24,12 +25,14 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.github.pagehelper.page.PageMethod;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.tags.Tag;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -37,7 +40,10 @@ import org.springframework.web.bind.annotation.*; @@ -37,7 +40,10 @@ import org.springframework.web.bind.annotation.*;
import javax.validation.groups.Default;
import javax.xml.stream.XMLStreamException;
import java.util.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 工作流流程定义接口。
@ -443,6 +449,15 @@ public class FlowEntryController { @@ -443,6 +449,15 @@ public class FlowEntryController {
taskInfoVo.setAssignee(userTask.getAssignee());
taskInfoVo.setTaskKey(userTask.getId());
taskInfoVo.setTaskType(FlowTaskType.USER_TYPE);
// 返回初始标题(标题模板,运行时再解析变量)。
Map<String, String> attributeMap = JSON.parseObject(userTask.getFormKey(), new TypeReference<Map<String, String>>() {
});
if (MapUtil.isNotEmpty(attributeMap)) {
java.lang.String value = attributeMap.get(FlowConstant.USER_TASK_TITLE_TEMPLATE_KEY);
if (StringUtil.isNotEmpty(value)) {
taskInfoVo.setTitle(value);
}
}
Map<String, List<ExtensionElement>> extensionMap = userTask.getExtensionElements();
if (MapUtil.isNotEmpty(extensionMap)) {
taskInfoVo.setOperationList(flowTaskExtService.buildOperationListExtensionElement(extensionMap));

74
common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java

@ -5,7 +5,10 @@ import apelet.common.core.annotation.MyRequestBody; @@ -5,7 +5,10 @@ import apelet.common.core.annotation.MyRequestBody;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.object.*;
import apelet.common.core.util.MyPageUtil;
import apelet.common.flow.constant.*;
import apelet.common.flow.constant.FlowApprovalType;
import apelet.common.flow.constant.FlowBackType;
import apelet.common.flow.constant.FlowConstant;
import apelet.common.flow.constant.FlowTaskStatus;
import apelet.common.flow.exception.FlowOperationException;
import apelet.common.flow.model.*;
import apelet.common.flow.model.constant.FlowMessageType;
@ -13,7 +16,10 @@ import apelet.common.flow.object.FlowEntryExtensionData; @@ -13,7 +16,10 @@ import apelet.common.flow.object.FlowEntryExtensionData;
import apelet.common.flow.service.*;
import apelet.common.flow.util.FlowCustomExtFactory;
import apelet.common.flow.util.FlowOperationHelper;
import apelet.common.flow.vo.*;
import apelet.common.flow.vo.FlowTaskCommentVo;
import apelet.common.flow.vo.FlowTaskVo;
import apelet.common.flow.vo.FlowUserInfoVo;
import apelet.common.flow.vo.TaskInfoVo;
import apelet.common.log.annotation.OperationLog;
import apelet.common.log.model.constant.SysOperationLogType;
import cn.hutool.core.bean.BeanUtil;
@ -211,6 +217,9 @@ public class FlowOperationController { @@ -211,6 +217,9 @@ public class FlowOperationController {
if (StrUtil.isNotBlank(flowTaskExt.getVariableListJson())) {
taskInfoVo.setVariableList(JSON.parseArray(flowTaskExt.getVariableListJson(), JSONObject.class));
}
FlowWorkOrder workOrder = flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(processInstanceId);
FlowEntry flowEntry = workOrder == null ? new FlowEntry() : flowEntryService.getFlowEntryFromCache(workOrder.getProcessDefinitionKey());
taskInfoVo.setTitle(flowApiService.resolveTitle(flowTaskExt.getTitleTemplate(), processInstanceId, workOrder, flowEntry));
}
return ResponseResult.success(taskInfoVo);
}
@ -831,6 +840,18 @@ public class FlowOperationController { @@ -831,6 +840,18 @@ public class FlowOperationController {
flowWorkOrderService.getInList(INSTANCE_ID, instanceIdSet);
Map<String, FlowWorkOrder> workOrderMap =
workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c));
Set<String> processDefinitionIdSet = taskInstanceList.stream()
.map(HistoricTaskInstance::getProcessDefinitionId).collect(Collectors.toSet());
List<FlowEntryPublish> flowEntryPublishList =
flowEntryService.getFlowEntryPublishList(processDefinitionIdSet);
Map<String, FlowEntryPublish> flowEntryPublishMap =
flowEntryPublishList.stream().collect(Collectors.toMap(FlowEntryPublish::getProcessDefinitionId, c -> c));
Set<Long> entryIdSet = flowEntryPublishList.stream()
.map(FlowEntryPublish::getEntryId).filter(Objects::nonNull).collect(Collectors.toSet());
Map<Long, FlowEntry> flowEntryMap = flowEntryService.listByIds(entryIdSet).stream()
.collect(Collectors.toMap(FlowEntry::getEntryId, e -> e));
Map<String, FlowTaskExt> flowTaskExtMap = flowTaskExtService.getByProcessDefinitionId(processDefinitionIdSet).stream()
.collect(Collectors.toMap(t -> t.getProcessDefinitionId() + ":" + t.getTaskId(), t -> t, (k1, k2) -> k1));
resultList.forEach(result -> {
String instanceId = result.get(INSTANCE_ID).toString();
HistoricProcessInstance instance = instanceMap.get(instanceId);
@ -844,6 +865,17 @@ public class FlowOperationController { @@ -844,6 +865,17 @@ public class FlowOperationController {
FlowWorkOrder flowWorkOrder = workOrderMap.get(instanceId);
if (flowWorkOrder != null) {
result.put("workOrderCode", flowWorkOrder.getWorkOrderCode());
String processDefinitionId = result.get("processDefinitionId").toString();
FlowEntryPublish flowEntryPublish = flowEntryPublishMap.get(processDefinitionId);
FlowTaskExt flowTaskExt = flowTaskExtMap.get(
processDefinitionId + ":" + result.get("taskDefinitionKey"));
if (flowEntryPublish != null && flowTaskExt != null) {
FlowEntry flowEntry = flowEntryMap.get(flowEntryPublish.getEntryId());
if (flowEntry != null) {
result.put("title", flowApiService.resolveTitle(
flowTaskExt.getTitleTemplate(), instanceId, flowWorkOrder, flowEntry));
}
}
}
});
Set<String> taskIdSet =
@ -896,6 +928,34 @@ public class FlowOperationController { @@ -896,6 +928,34 @@ public class FlowOperationController {
flowWorkOrderService.getInList(INSTANCE_ID, instanceIdSet);
Map<String, FlowWorkOrder> workOrderMap =
workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c));
Set<String> processDefinitionIdSet = pageData.getDataList().stream()
.map(HistoricProcessInstance::getProcessDefinitionId).collect(Collectors.toSet());
Map<String, FlowEntryPublish> flowEntryPublishMap = new HashMap<>();
Map<Long, FlowEntry> flowEntryMap = new HashMap<>();
Map<String, FlowTaskExt> flowTaskExtMap = new HashMap<>();
// 流程实例列表使用起始节点的标题模板。
Map<String, String> initTaskKeyMap = new HashMap<>();
if (CollUtil.isNotEmpty(processDefinitionIdSet)) {
List<FlowEntryPublish> flowEntryPublishList =
flowEntryService.getFlowEntryPublishList(processDefinitionIdSet);
flowEntryPublishMap.putAll(flowEntryPublishList.stream()
.collect(Collectors.toMap(FlowEntryPublish::getProcessDefinitionId, c -> c)));
Set<Long> entryIdSet = flowEntryPublishList.stream()
.map(FlowEntryPublish::getEntryId).filter(Objects::nonNull).collect(Collectors.toSet());
if (CollUtil.isNotEmpty(entryIdSet)) {
flowEntryMap.putAll(flowEntryService.listByIds(entryIdSet).stream()
.collect(Collectors.toMap(FlowEntry::getEntryId, e -> e)));
}
for (FlowEntryPublish flowEntryPublish : flowEntryPublishList) {
TaskInfoVo initTaskInfo = StrUtil.isBlank(flowEntryPublish.getInitTaskInfo())
? null : JSON.parseObject(flowEntryPublish.getInitTaskInfo(), TaskInfoVo.class);
if (initTaskInfo != null && StrUtil.isNotBlank(initTaskInfo.getTaskKey())) {
initTaskKeyMap.put(flowEntryPublish.getProcessDefinitionId(), initTaskInfo.getTaskKey());
}
}
flowTaskExtMap.putAll(flowTaskExtService.getByProcessDefinitionId(processDefinitionIdSet).stream()
.collect(Collectors.toMap(t -> t.getProcessDefinitionId() + ":" + t.getTaskId(), t -> t, (k1, k2) -> k1)));
}
List<Map<String, Object>> resultList = new LinkedList<>();
pageData.getDataList().forEach(instance -> {
Map<String, Object> data = BeanUtil.beanToMap(instance);
@ -908,6 +968,16 @@ public class FlowOperationController { @@ -908,6 +968,16 @@ public class FlowOperationController {
if (workOrder != null) {
data.put("workOrderCode", workOrder.getWorkOrderCode());
data.put("flowStatus", workOrder.getFlowStatus());
FlowEntryPublish flowEntryPublish = flowEntryPublishMap.get(instance.getProcessDefinitionId());
FlowTaskExt flowTaskExt = flowTaskExtMap.get(
instance.getProcessDefinitionId() + ":" + initTaskKeyMap.get(instance.getProcessDefinitionId()));
if (flowEntryPublish != null && flowTaskExt != null) {
FlowEntry flowEntry = flowEntryMap.get(flowEntryPublish.getEntryId());
if (flowEntry != null) {
data.put("title", flowApiService.resolveTitle(
flowTaskExt.getTitleTemplate(), instance.getId(), workOrder, flowEntry));
}
}
}
resultList.add(data);
});

5
common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml

@ -14,6 +14,7 @@ @@ -14,6 +14,7 @@
<result column="candidate_usernames" jdbcType="VARCHAR" property="candidateUsernames"/>
<result column="copy_list_json" jdbcType="VARCHAR" property="copyListJson"/>
<result column="extra_data_json" jdbcType="VARCHAR" property="extraDataJson"/>
<result column="title_template" jdbcType="VARCHAR" property="titleTemplate"/>
</resultMap>
<insert id="insertList">
@ -30,7 +31,9 @@ @@ -30,7 +31,9 @@
#{item.deptIds},
#{item.candidateUsernames},
#{item.copyListJson},
#{item.extraDataJson})
#{item.extraDataJson},
#{item.titleTemplate}
)
</foreach>
</insert>
</mapper>

55
common/common-flow/src/main/java/apelet/common/flow/dto/FlowTaskDto.java

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
package apelet.common.flow.dto;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 批量提交流程任务
*
* @author guifc
* @date 2023-08-04
*/
@Schema(description = "批量提交流程任务")
@Data
public class FlowTaskDto {
/**
* 流程标识
*/
@Schema(description = "流程标识")
@NotNull(message = "数据验证失败,任务的流程标识不能为空!")
private String processInstanceId;
/**
* 流程任务ID
*/
@Schema(description = "流程任务ID")
@NotBlank(message = "数据验证失败,任务ID不能为空!")
private String taskId;
/**
* 流程任务公共变量数据
*/
@Schema(description = "流程任务公共变量数据")
private JSONObject taskVariableData;
/**
* 流程审批相关公共主表数据
*/
@Schema(description = "流程审批相关公共主表数据")
private JSONObject masterData;
/**
* 流程审批相关公共从表数据
*/
@Schema(description = "流程审批相关公共从表数据")
private JSONObject slaveData;
/**
* 传阅公共数据,格式为type和id,type的值参考FlowConstant中的常量值
*/
@Schema(description = "传阅公共数据,格式为type和id,type的值参考FlowConstant中的常量值")
private JSONObject copyData;
}

76
common/common-flow/src/main/java/apelet/common/flow/listener/MultiInstanceAssigneeListener.java

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
package apelet.common.flow.listener;
import apelet.common.core.util.ApplicationContextHolder;
import apelet.common.flow.model.FlowTaskExt;
import apelet.common.flow.service.FlowTaskExtService;
import apelet.common.flow.util.BaseFlowIdentityExtHelper;
import apelet.common.flow.util.FlowCustomExtFactory;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* 多实例会签节点审批人监听器。
* 当多实例节点没有通过前置"会签"按钮写入 assigneeList 时,
* 自动从 zz_flow_task_ext.candidate_usernames 中读取审批人列表。
*
* @author guifc
* @date 2025-07-29
*/
@Slf4j
public class MultiInstanceAssigneeListener implements ExecutionListener {
private final transient FlowTaskExtService flowTaskExtService = ApplicationContextHolder.getBean(FlowTaskExtService.class);
private final transient FlowCustomExtFactory flowCustomExtFactory = ApplicationContextHolder.getBean(FlowCustomExtFactory.class);
@Override
public void notify(DelegateExecution execution) {
// 如果 assigneeList 已经存在(由前置节点的会签按钮写入),则不覆盖
if (execution.hasVariable("assigneeList")) {
return;
}
FlowTaskExt flowTaskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId(
execution.getProcessDefinitionId(), execution.getCurrentActivityId());
if (flowTaskExt == null) {
return;
}
List<String> candidateUsernames = new ArrayList<>();
if (StrUtil.isNotBlank(flowTaskExt.getCandidateUsernames())) {
candidateUsernames.addAll(StrUtil.split(flowTaskExt.getCandidateUsernames(), ","));
} else {
BaseFlowIdentityExtHelper extHelper = flowCustomExtFactory.getFlowIdentityExtHelper();
if (extHelper == null) {
return;
}
// 先看部门是否存在,通过部门id 去取用户登录名
if (StrUtil.isNotBlank(flowTaskExt.getDeptIds())) {
Set<String> deptUsernameSet = extHelper.getUsernameListByDeptIds(CollUtil.newHashSet(StrUtil.split(flowTaskExt.getDeptIds(), ",")));
if (CollUtil.isNotEmpty(deptUsernameSet)) {
candidateUsernames.addAll(deptUsernameSet);
}
}
// 再看角色是否存在,通过角色id 去取用户登录名
if (StrUtil.isNotBlank(flowTaskExt.getRoleIds())) {
Set<String> roleUsernameSet = extHelper.getUsernameListByRoleIds(CollUtil.newHashSet(StrUtil.split(flowTaskExt.getRoleIds(), ",")));
if (CollUtil.isNotEmpty(roleUsernameSet)) {
candidateUsernames.addAll(roleUsernameSet);
}
}
}
if (CollUtil.isEmpty(candidateUsernames)) {
return;
}
execution.setVariable("assigneeList", candidateUsernames);
log.info("MultiInstanceAssigneeListener: set assigneeList from candidate_usernames = {}", candidateUsernames);
}
}

4
common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java

@ -2,6 +2,8 @@ package apelet.common.flow.model; @@ -2,6 +2,8 @@ package apelet.common.flow.model;
import apelet.common.core.annotation.RelationOneToOne;
import apelet.common.core.base.mapper.BaseModelMapper;
import apelet.common.flow.model.constant.FlowBindFormType;
import apelet.common.flow.model.constant.FlowEntryStatus;
import apelet.common.flow.vo.FlowEntryVo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@ -73,6 +75,7 @@ public class FlowEntry { @@ -73,6 +75,7 @@ public class FlowEntry {
/**
* 流程状态。
* @see FlowEntryStatus
*/
@TableField(value = "status")
private Integer status;
@ -91,6 +94,7 @@ public class FlowEntry { @@ -91,6 +94,7 @@ public class FlowEntry {
/**
* 绑定表单类型。
* @see FlowBindFormType
*/
@TableField(value = "bind_form_type")
private Integer bindFormType;

6
common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java

@ -85,4 +85,10 @@ public class FlowTaskExt { @@ -85,4 +85,10 @@ public class FlowTaskExt {
*/
@TableField(value = "extra_data_json")
private String extraDataJson;
/**
* 节点标题模板,如 ${startUserName}发起的报销单,单号${orderNo}。
*/
@TableField(value = "title_template")
private String titleTemplate;
}

14
common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java

@ -1,8 +1,10 @@ @@ -1,8 +1,10 @@
package apelet.common.flow.service;
import apelet.common.core.object.*;
import apelet.common.flow.model.FlowEntry;
import apelet.common.flow.model.FlowTaskComment;
import apelet.common.flow.model.FlowTaskExt;
import apelet.common.flow.model.FlowWorkOrder;
import apelet.common.flow.object.AnalyzedNode;
import apelet.common.flow.object.FlowApiOption;
import apelet.common.flow.vo.FlowTaskVo;
@ -634,4 +636,16 @@ public interface FlowApiService { @@ -634,4 +636,16 @@ public interface FlowApiService {
* @return 用户任务。
*/
UserTask getUserTask(String processDefinitionId, String taskKey);
/**
* 解析用户任务节点的标题。未配置标题模板时,使用默认标题,默认标题中的表名取自在线表单主表。
* 非在线表单(路由表单)工作流不返回标题。
*
* @param titleTemplate 节点配置的标题模板,为空时使用默认标题。
* @param processInstanceId 流程实例Id。
* @param workOrder 流程工单对象。
* @param flowEntry 流程对象,用于判断是否为在线表单(bindFormType=0)。
* @return 解析后的标题,非在线表单工作流返回null。
*/
String resolveTitle(String titleTemplate, String processInstanceId, FlowWorkOrder workOrder, FlowEntry flowEntry);
}

15
common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java

@ -5,11 +5,15 @@ import apelet.common.flow.model.FlowTaskExt; @@ -5,11 +5,15 @@ import apelet.common.flow.model.FlowTaskExt;
import apelet.common.flow.object.FlowElementExtProperty;
import apelet.common.flow.vo.FlowUserInfoVo;
import com.alibaba.fastjson.JSONObject;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.task.api.TaskInfo;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 流程任务扩展数据操作服务接口。
@ -44,6 +48,15 @@ public interface FlowTaskExtService extends IBaseService<FlowTaskExt, String> { @@ -44,6 +48,15 @@ public interface FlowTaskExtService extends IBaseService<FlowTaskExt, String> {
List<FlowTaskExt> getByProcessDefinitionId(String processDefinitionId);
/**
* 查询指定的流程定义的任务扩展对象。
*
* @param processDefinitionIdList 流程引擎的定义Id。
* @return 查询结果。
*/
List<FlowTaskExt> getByProcessDefinitionId(Set<String> processDefinitionIdList);
/**
* 获取任务扩展信息中的候选人用户信息列表。
*
* @param processInstanceId 流程引擎的实例Id。

194
common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java

@ -10,9 +10,13 @@ import apelet.common.core.util.DefaultDataSourceResolver; @@ -10,9 +10,13 @@ import apelet.common.core.util.DefaultDataSourceResolver;
import apelet.common.core.util.MyCommonUtil;
import apelet.common.core.util.MyDateUtil;
import apelet.common.flow.cmd.RestartProcessInstanceCmd;
import apelet.common.flow.constant.*;
import apelet.common.flow.constant.FlowApprovalType;
import apelet.common.flow.constant.FlowBackType;
import apelet.common.flow.constant.FlowConstant;
import apelet.common.flow.constant.FlowTaskStatus;
import apelet.common.flow.exception.FlowOperationException;
import apelet.common.flow.model.*;
import apelet.common.flow.model.constant.FlowBindFormType;
import apelet.common.flow.object.*;
import apelet.common.flow.service.*;
import apelet.common.flow.util.BaseFlowIdentityExtHelper;
@ -25,6 +29,7 @@ import cn.hutool.core.lang.Assert; @@ -25,6 +29,7 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
@ -34,8 +39,8 @@ import lombok.Cleanup; @@ -34,8 +39,8 @@ import lombok.Cleanup;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.impl.de.odysseus.el.ExpressionFactoryImpl;
import org.flowable.common.engine.impl.de.odysseus.el.util.SimpleContext;
import org.flowable.common.engine.impl.identity.Authentication;
@ -49,7 +54,10 @@ import org.flowable.engine.history.HistoricProcessInstance; @@ -49,7 +54,10 @@ import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.*;
import org.flowable.engine.runtime.ChangeActivityStateBuilder;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceBuilder;
import org.flowable.identitylink.api.IdentityLink;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
@ -691,10 +699,12 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -691,10 +699,12 @@ public class FlowApiServiceImpl implements FlowApiService {
query.taskNameLike("%" + taskName + "%");
}
this.buildCandidateCondition(query, username);
long totalCount = query.count();
query.orderByTaskCreateTime().desc();
Map<String, Boolean> multiInstanceTaskMap = new HashMap<>();
long totalCount = this.countOwnedTaskList(query, username, multiInstanceTaskMap);
int firstResult = (pageParam.getPageNum() - 1) * pageParam.getPageSize();
List<Task> taskList = query.listPage(firstResult, pageParam.getPageSize());
List<Task> taskList = this.listOwnedTaskPage(
query, username, firstResult, pageParam.getPageSize(), multiInstanceTaskMap);
return new MyPageData<>(taskList, totalCount);
}
@ -702,7 +712,102 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -702,7 +712,102 @@ public class FlowApiServiceImpl implements FlowApiService {
public long getTaskCountByUserName(String username) {
TaskQuery query = this.createQuery();
this.buildCandidateCondition(query, username);
return query.count();
return this.countOwnedTaskList(query, username, new HashMap<>());
}
/**
* 统计当前用户待办任务数。
* 多实例会签节点在流程图中将全部会签人同时配置为候选者,导致每个实例对所有人可见。
* 这里只保留会签实例归属人自己的那一条,其他会签人的实例不参与统计。
*/
private long countOwnedTaskList(TaskQuery query, String username, Map<String, Boolean> multiInstanceTaskMap) {
long totalCount = 0;
int cursor = 0;
while (true) {
List<Task> batch = query.listPage(cursor, 500);
if (CollUtil.isEmpty(batch)) {
break;
}
cursor += batch.size();
for (Task task : batch) {
if (this.isTaskOwnedByUser(task, username, multiInstanceTaskMap)) {
totalCount++;
}
}
}
return totalCount;
}
/**
* 分页获取当前用户待办任务。
* 多实例会签节点的实例对候选者全体可见,分页时跳过不属于当前用户的其他会签实例。
*/
private List<Task> listOwnedTaskPage(
TaskQuery query, String username, int firstResult, int pageSize, Map<String, Boolean> multiInstanceTaskMap) {
List<Task> resultList = new LinkedList<>();
int skipCount = firstResult;
int cursor = 0;
while (resultList.size() < pageSize) {
List<Task> batch = query.listPage(cursor, Math.max(pageSize, 500));
if (CollUtil.isEmpty(batch)) {
break;
}
cursor += batch.size();
for (Task task : batch) {
if (!this.isTaskOwnedByUser(task, username, multiInstanceTaskMap)) {
continue;
}
if (skipCount > 0) {
skipCount--;
continue;
}
resultList.add(task);
if (resultList.size() >= pageSize) {
break;
}
}
}
return resultList;
}
/**
* 判断任务是否属于当前用户。
* 多实例会签实例的元素变量 assignee 即该实例真正的归属人。
*/
private boolean isTaskOwnedByUser(Task task, String username, Map<String, Boolean> multiInstanceTaskMap) {
String taskKey = task.getProcessDefinitionId() + ":" + task.getTaskDefinitionKey();
Boolean isMultiInstance = multiInstanceTaskMap.get(taskKey);
if (isMultiInstance == null) {
isMultiInstance = this.isMultiInstanceTask(task.getProcessDefinitionId(), task.getTaskDefinitionKey());
multiInstanceTaskMap.put(taskKey, isMultiInstance);
}
if (!isMultiInstance) {
return true;
}
Object assignee = taskService.getVariable(task.getId(), "assignee");
return assignee == null || StrUtil.equals(assignee.toString(), username);
}
/**
* 获取任务的审批人。
* 多实例会签实例的指派人保存在元素变量 assignee 中,普通任务取任务的指派人。
*/
private String getTaskAssignee(Task task, Map<String, Boolean> multiInstanceTaskMap) {
String assignee = task.getAssignee();
if (StrUtil.isNotBlank(assignee)) {
return assignee;
}
String taskKey = task.getProcessDefinitionId() + ":" + task.getTaskDefinitionKey();
Boolean isMultiInstance = multiInstanceTaskMap.get(taskKey);
if (isMultiInstance == null) {
isMultiInstance = this.isMultiInstanceTask(task.getProcessDefinitionId(), task.getTaskDefinitionKey());
multiInstanceTaskMap.put(taskKey, isMultiInstance);
}
if (!isMultiInstance) {
return null;
}
Object assigneeVar = taskService.getVariable(task.getId(), "assignee");
return assigneeVar == null ? null : assigneeVar.toString();
}
@Override
@ -782,6 +887,11 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -782,6 +887,11 @@ public class FlowApiServiceImpl implements FlowApiService {
flowEntryService.getFlowEntryPublishList(processDefinitionIdSet);
Map<String, FlowEntryPublish> flowEntryPublishMap =
flowEntryPublishList.stream().collect(Collectors.toMap(FlowEntryPublish::getProcessDefinitionId, c -> c));
// 批量加载流程对象,用于判断在线表单(bindFormType=0)
Set<Long> entryIdSet = flowEntryPublishList.stream()
.map(FlowEntryPublish::getEntryId).filter(Objects::nonNull).collect(Collectors.toSet());
Map<Long, FlowEntry> flowEntryMap = flowEntryService.listByIds(entryIdSet).stream()
.collect(Collectors.toMap(FlowEntry::getEntryId, e -> e));
List<ProcessInstance> instanceList = this.getProcessInstanceList(procInstanceIdSet);
Map<String, ProcessInstance> instanceMap =
instanceList.stream().collect(Collectors.toMap(ProcessInstance::getId, c -> c));
@ -792,6 +902,9 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -792,6 +902,9 @@ public class FlowApiServiceImpl implements FlowApiService {
flowWorkOrderService.getInList("processInstanceId", procInstanceIdSet);
Map<String, FlowWorkOrder> workOrderMap =
workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c));
List<FlowTaskExt> allFlowTaskExtList = flowTaskExtService.getByProcessDefinitionId(processDefinitionIdSet);
Map<String, FlowTaskExt> flowTaskExtMap = allFlowTaskExtList.stream().collect(Collectors.toMap(t -> t.getProcessDefinitionId() + ":" + t.getTaskId(), t -> t, (k1, k2) -> k1));
Map<String, Boolean> multiInstanceTaskMap = new HashMap<>();
for (Task task : taskList) {
FlowTaskVo flowTaskVo = new FlowTaskVo();
flowTaskVo.setTaskId(task.getId());
@ -799,6 +912,7 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -799,6 +912,7 @@ public class FlowApiServiceImpl implements FlowApiService {
flowTaskVo.setTaskKey(task.getTaskDefinitionKey());
flowTaskVo.setTaskFormKey(task.getFormKey());
flowTaskVo.setTaskStartTime(task.getCreateTime());
flowTaskVo.setAssignee(this.getTaskAssignee(task, multiInstanceTaskMap));
flowTaskVo.setEntryId(flowEntryPublishMap.get(task.getProcessDefinitionId()).getEntryId());
ProcessDefinition processDefinition = definitionMap.get(task.getProcessDefinitionId());
flowTaskVo.setProcessDefinitionId(processDefinition.getId());
@ -817,10 +931,17 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -817,10 +931,17 @@ public class FlowApiServiceImpl implements FlowApiService {
flowTaskVo.setIsDraft(flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.DRAFT));
flowTaskVo.setWorkOrderCode(flowWorkOrder.getWorkOrderCode());
}
FlowTaskExt flowTaskExt = flowTaskExtMap.get(task.getProcessDefinitionId() + ":" + task.getTaskDefinitionKey());
FlowEntry flowEntry = flowEntryMap.get(flowEntryPublishMap.get(task.getProcessDefinitionId()).getEntryId());
if (flowTaskExt != null && flowEntry != null) {
flowTaskVo.setTitle(this.resolveTitle(flowTaskExt.getTitleTemplate(), task.getProcessInstanceId(), flowWorkOrder, flowEntry));
}
flowTaskVoList.add(flowTaskVo);
}
Set<String> loginNameSet = flowTaskVoList.stream()
.map(FlowTaskVo::getProcessInstanceInitiator).collect(Collectors.toSet());
loginNameSet.addAll(flowTaskVoList.stream()
.map(FlowTaskVo::getAssignee).filter(StrUtil::isNotBlank).collect(Collectors.toSet()));
List<FlowUserInfoVo> flowUserInfos = flowCustomExtFactory
.getFlowIdentityExtHelper().getUserInfoListByUsernameSet(loginNameSet);
Map<String, FlowUserInfoVo> userInfoMap =
@ -829,6 +950,10 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -829,6 +950,10 @@ public class FlowApiServiceImpl implements FlowApiService {
FlowUserInfoVo userInfo = userInfoMap.get(flowTaskVo.getProcessInstanceInitiator());
flowTaskVo.setShowName(userInfo.getShowName());
flowTaskVo.setHeadImageUrl(userInfo.getHeadImageUrl());
FlowUserInfoVo assigneeInfo = userInfoMap.get(flowTaskVo.getAssignee());
if (assigneeInfo != null) {
flowTaskVo.setAssigneeShowName(assigneeInfo.getShowName());
}
}
return flowTaskVoList;
}
@ -2441,4 +2566,61 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -2441,4 +2566,61 @@ public class FlowApiServiceImpl implements FlowApiService {
}
return null;
}
@Override
public String resolveTitle(String titleTemplate, String processInstanceId, FlowWorkOrder workOrder, FlowEntry flowEntry) {
// 在线表单判断以流程绑定的表单类型为准(bindFormType=0),不能依赖工单上的 onlineTableId(部分在线表单启动路径不会回填该字段)。
if (!ObjectUtil.equal(flowEntry.getBindFormType(), FlowBindFormType.ONLINE_FORM)) {
return null;
}
if (StrUtil.isBlank(titleTemplate)) {
titleTemplate = this.buildDefaultTitle(flowEntry);
}
String result = titleTemplate;
// 1. Flowable 持久化变量: ${initiator}, ${startUserName}, ${submitUser}
List<String> varNames = ReUtil.findAll("\\$\\{(\\w+)\\}", result, 1);
for (String varName : varNames) {
String value = null;
if (StrUtil.isNotBlank(processInstanceId)) {
Object v = null;
try {
v = runtimeService.getVariable(processInstanceId, varName);
} catch (Exception e) {
// 流程实例已结束,运行时变量已被清理,回退到历史变量查询。
log.warn("查询流程实例运行时变量失败,回退到历史变量查询。processInstanceId [{}], varName [{}]",
processInstanceId, varName);
}
if (v == null) {
v = this.getHistoricProcessInstanceVariable(processInstanceId, varName);
}
if (v != null) {
value = v.toString();
}
}
if (value == null) {
// 2. 业务数据: ${number}, ${amount} 等在线表单字段。
String bizData = flowCustomExtFactory.getOnlineBusinessDataExtHelper().getBusinessData(workOrder, flowEntry);
if (StrUtil.isNotBlank(bizData)) {
JSONObject biz = JSON.parseObject(bizData);
value = biz.getString(varName);
}
}
if (value != null) {
result = result.replace("${" + varName + "}", value);
}
}
return result;
}
private String buildDefaultTitle(FlowEntry flowEntry) {
String tableName = null;
if (flowEntry != null && flowEntry.getPageId() != null) {
// 在线表单:FlowEntry.pageId → OnlinePage.masterTableName
tableName = flowCustomExtFactory.getOnlineBusinessDataExtHelper().getTableTxt(flowEntry.getPageId());
}
if (StrUtil.isBlank(tableName)) {
tableName = "业务";
}
return tableName + "单据,单据编号为${number}";
}
}

14
common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java

@ -364,6 +364,15 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement @@ -364,6 +364,15 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
startUserNameVariable.setBuiltin(true);
startUserNameVariable.setCreateTime(now);
flowEntryVariableService.saveNew(startUserNameVariable);
FlowEntryVariable assigneeListVariable = new FlowEntryVariable();
assigneeListVariable.setVariableId(idGenerator.nextLongId());
assigneeListVariable.setEntryId(entryId);
assigneeListVariable.setVariableName("assigneeList");
assigneeListVariable.setShowName("会签人");
assigneeListVariable.setVariableType(FlowVariableType.TASK);
assigneeListVariable.setBuiltin(true);
assigneeListVariable.setCreateTime(now);
flowEntryVariableService.saveNew(assigneeListVariable);
}
private void insertEntryPublishVariables(List<FlowEntryVariable> entryVariableList, Long entryPublishId) {
@ -482,6 +491,11 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement @@ -482,6 +491,11 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
FlowTaskPostCandidateGroup.buildCandidateGroupList(groupDataList);
userTask.setCandidateGroups(candidateGroupList);
}
// 多实例会签节点:如果没有通过前置"会签"按钮写入 assigneeList,
// 则自动从 zz_flow_task_ext.candidate_usernames 中读取审批人列表。
if (userTask.hasMultiInstanceLoopCharacteristics()) {
flowApiService.addExecutionListener(userTask, MultiInstanceAssigneeListener.class, "start", null);
}
this.processFlowTaskExtListener(userTask, t);
}
}

32
common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java

@ -10,7 +10,10 @@ import apelet.common.core.util.DefaultDataSourceResolver; @@ -10,7 +10,10 @@ import apelet.common.core.util.DefaultDataSourceResolver;
import apelet.common.flow.constant.FlowApprovalType;
import apelet.common.flow.constant.FlowConstant;
import apelet.common.flow.dao.FlowTaskExtMapper;
import apelet.common.flow.model.*;
import apelet.common.flow.model.FlowEntryVariable;
import apelet.common.flow.model.FlowMultiInstanceTrans;
import apelet.common.flow.model.FlowTaskComment;
import apelet.common.flow.model.FlowTaskExt;
import apelet.common.flow.object.FlowElementExtProperty;
import apelet.common.flow.object.FlowTaskMultiSignAssign;
import apelet.common.flow.object.FlowUserTaskExtData;
@ -25,10 +28,13 @@ import cn.hutool.core.util.StrUtil; @@ -25,10 +28,13 @@ import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.task.api.TaskInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -90,11 +96,18 @@ public class FlowTaskExtServiceImpl extends BaseService<FlowTaskExt, String> imp @@ -90,11 +96,18 @@ public class FlowTaskExtServiceImpl extends BaseService<FlowTaskExt, String> imp
@Override
public List<FlowTaskExt> getByProcessDefinitionId(String processDefinitionId) {
FlowTaskExt filter = new FlowTaskExt();
filter.setProcessDefinitionId(processDefinitionId);
return flowTaskExtMapper.selectList(new QueryWrapper<>(filter));
return this.getByProcessDefinitionId(Collections.singleton(processDefinitionId));
}
@Override
public List<FlowTaskExt> getByProcessDefinitionId(Set<String> processDefinitionIdList) {
if (CollUtil.isEmpty(processDefinitionIdList)) {
return Collections.emptyList();
}
return flowTaskExtMapper.selectList(new LambdaQueryWrapper<FlowTaskExt>().in(FlowTaskExt::getProcessDefinitionId, processDefinitionIdList));
}
@Override
public List<FlowUserInfoVo> getCandidateUserInfoList(
String processInstanceId,
@ -265,6 +278,15 @@ public class FlowTaskExtServiceImpl extends BaseService<FlowTaskExt, String> imp @@ -265,6 +278,15 @@ public class FlowTaskExtServiceImpl extends BaseService<FlowTaskExt, String> imp
if (extraDataJson != null) {
flowTaskExt.setExtraDataJson(extraDataJson.toJSONString());
}
// 解析 titleTemplate 扩展属性。
Map<String, String> attributeMap = JSON.parseObject(userTask.getFormKey(), new TypeReference<Map<String, String>>() {
});
if (MapUtil.isNotEmpty(attributeMap)) {
java.lang.String value = attributeMap.get(FlowConstant.USER_TASK_TITLE_TEMPLATE_KEY);
if (StringUtil.isNotEmpty(value)) {
flowTaskExt.setTitleTemplate(value);
}
}
Map<String, List<ExtensionElement>> extensionMap = userTask.getExtensionElements();
if (MapUtil.isEmpty(extensionMap)) {
return flowTaskExt;

22
common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
package apelet.common.flow.util;
import apelet.common.flow.base.service.BaseFlowOnlineService;
import apelet.common.flow.model.FlowEntry;
import apelet.common.flow.model.FlowWorkOrder;
import cn.hutool.core.lang.Assert;
import lombok.extern.slf4j.Slf4j;
@ -48,4 +49,25 @@ public class BaseOnlineBusinessDataExtHelper { @@ -48,4 +49,25 @@ public class BaseOnlineBusinessDataExtHelper {
onlineBusinessService.deleteBusinessData(workOrder);
}
}
/**
* 根据在线表单页面Id,获取在线表单主表的表名。
*
* @param pageId 在线表单页面Id。
* @return 主表表名,如果无法获取返回null。
*/
public String getTableTxt(Long pageId) {
return this.onlineBusinessService == null ? null : onlineBusinessService.getMasterTableName(pageId);
}
/**
* 获取在线表单工作流的业务主表数据。
*
* @param workOrder 工单对象。
* @param flowEntry 流程节点对象。
* @return JSON格式的业务主表数据,如果无法获取返回null。
*/
public String getBusinessData(FlowWorkOrder workOrder, FlowEntry flowEntry) {
return this.onlineBusinessService == null ? null : onlineBusinessService.getBusinessData(workOrder, flowEntry);
}
}

18
common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java

@ -122,4 +122,22 @@ public class FlowTaskVo { @@ -122,4 +122,22 @@ public class FlowTaskVo {
*/
@Schema(description = "是否为草稿状态")
private Boolean isDraft;
/**
* 解析后的任务标题。
*/
@Schema(description = "解析后的任务标题")
private String title;
/**
* 任务指派人(审批人)。
*/
@Schema(description = "任务指派人(审批人)")
private String assignee;
/**
* 任务指派人(审批人)显示名。
*/
@Schema(description = "任务指派人(审批人)显示名")
private String assigneeShowName;
}

6
common/common-flow/src/main/java/apelet/common/flow/vo/TaskInfoVo.java

@ -76,4 +76,10 @@ public class TaskInfoVo { @@ -76,4 +76,10 @@ public class TaskInfoVo {
*/
@Schema(description = "任务节点的自定义变量列表")
List<JSONObject> variableList;
/**
* 解析后的任务标题。
*/
@Schema(description = "解析后的任务标题")
private String title;
}

4
common/common-generator/pom.xml

@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
<artifactId>common-generator</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<version>1.0.4</version>
<dependencies>
<dependency>
@ -59,7 +59,7 @@ @@ -59,7 +59,7 @@
<dependency>
<groupId>apelet</groupId>
<artifactId>common-orm</artifactId>
<version>1.0.0</version>
<version>1.0.2</version>
<scope>compile</scope>
</dependency>
<dependency>

7
common/common-generator/src/main/java/apelet/common/generator/controller/OnlFormHeadController.java

@ -301,11 +301,12 @@ public class OnlFormHeadController { @@ -301,11 +301,12 @@ public class OnlFormHeadController {
*
* @param name 查询对象name
* @param type 是否查询字段关联的元数据 1:是 0:否
* @param isChild 是否查询子表 1:是 0:否
* @return
*/
@GetMapping(value = "/selectOnlFrom")
@Schema(description = "查询对象的所有元数据")
public ResponseResult<?> selectOnlFrom(@RequestParam String name, @RequestParam String type) {
public ResponseResult<?> selectOnlFrom(@RequestParam String name, @RequestParam String type, @RequestParam(required = false) Integer isChild) {
ArrayList<Object> arrayList = new ArrayList<>();
OnlFormHead onlFormHead = onlFormHeadService.getHeadTableNameCache(name);
if (onlFormHead == null) {
@ -321,8 +322,8 @@ public class OnlFormHeadController { @@ -321,8 +322,8 @@ public class OnlFormHeadController {
List list2 = selectRefOnl(list1, type, "1");
head.put("child", list2);
arrayList.add(head);
//关联附表
if (StringUtils.isNotEmpty(onlFormHead.getChildName())) {
//关联附表 并且是否需要附表 为 1 则返回
if (StringUtils.isNotEmpty(onlFormHead.getChildName()) && (isChild == null || isChild == 1)) {
List<String> list = Arrays.asList(onlFormHead.getChildName().split(","));
list.forEach(f -> {
OnlFormHead childOnlForm = onlFormHeadService.getHeadTableNameCache(f);

6
common/common-generator/src/main/java/apelet/common/generator/model/OnlCgreportItem.java

@ -56,6 +56,12 @@ public class OnlCgreportItem extends BaseModel implements Serializable { @@ -56,6 +56,12 @@ public class OnlCgreportItem extends BaseModel implements Serializable {
@Schema(description = "是否显示 0否,1显示")
private Integer isShow;
@Schema(description = "是否下拉显示 0否,1显示")
private Integer isSelectShow;
@Schema(description = "是否下拉查询 0否,1是")
private Integer isSelectQuery;
@Schema(description = "排序")
private Integer orderNum;

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

@ -26,7 +26,7 @@ public class OnlFormHead extends BaseModel implements Serializable { @@ -26,7 +26,7 @@ public class OnlFormHead extends BaseModel implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id")
private Long id;
// 0 附表,1 主表,2单表
@Schema(description = "表类型")
private String tableType;

11
common/common-generator/src/main/java/apelet/common/generator/service/IOnlFormHeadService.java

@ -119,4 +119,15 @@ public interface IOnlFormHeadService extends IService<OnlFormHead> { @@ -119,4 +119,15 @@ public interface IOnlFormHeadService extends IService<OnlFormHead> {
* @return
*/
OnlFormHead getHeadTableNameCache(String tableName);
/**
* 创建元数据(含默认字段)并同步数据库. 返回创建后的 OnlFormHead.
*
* @param tableName 表名
* @param tableTxt 表描述
* @param metaType 表类型: "0"=附表(分录), "1"=主表(业务单据), "2"=单表(基础资料)
* @param sortFiled 关联字段(附表时传主表表名)
* @return OnlFormHead
*/
OnlFormHead createMetaAndSync(String tableName, String tableTxt, String metaType, String sortFiled);
}

18
common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlCgreportHeadServiceImpl.java

@ -9,11 +9,15 @@ import apelet.common.eas.service.impl.EASSyncService; @@ -9,11 +9,15 @@ import apelet.common.eas.service.impl.EASSyncService;
import apelet.common.generator.dao.OnlCgreportHeadMapper;
import apelet.common.generator.dto.OnlCgreportHeadDto;
import apelet.common.generator.dto.SQLParseDto;
import apelet.common.generator.model.*;
import apelet.common.generator.service.*;
import apelet.common.generator.model.OnlCgreportHead;
import apelet.common.generator.model.OnlCgreportItem;
import apelet.common.generator.model.OnlCgreportParam;
import apelet.common.generator.service.IOnlCgreportHeadService;
import apelet.common.generator.service.IOnlCgreportItemService;
import apelet.common.generator.service.IOnlCgreportParamService;
import apelet.common.generator.service.IOnlFormHeadService;
import apelet.common.generator.utils.CompareType;
import apelet.common.generator.vo.OnlFormHeadVo2;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.druid.sql.SQLUtils;
@ -424,7 +428,13 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe @@ -424,7 +428,13 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe
JSONObject f7Params = jsonObject.getJSONObject("self__f7Params");
jsonObject.remove("self__f7Params");//移除
if (jsonObject.getBooleanValue("isSelectQuery") && jsonObject.containsKey("searchKeyword")) {
List<OnlCgreportItem> selectQuery = all.stream().filter(f -> f.getIsSelectQuery().equals(1)).collect(Collectors.toList());
selectQuery.forEach(f -> {
where.put(f.getFieldName(), jsonObject.get("searchKeyword"));
});
}
jsonObject.remove("isSelectQuery");//移除
if (null != jsonObject) {
for (OnlCgreportItem item : queryList) {
String fieldName = item.getFieldName();

48
common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java

@ -781,6 +781,15 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -781,6 +781,15 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
parentid.setIsEmpty(1);
parentid.setIsDefault(1);
list.add(parentid);
OnlFormField seq = new OnlFormField();
seq.setFieldName("seq");
seq.setFieldRemark("排序顺序");
seq.setFieldType(CustomUtil.FieldType_int);
seq.setRowDisabled(1);
seq.setIsEmpty(1);
seq.setIsDefault(1);
list.add(seq);
}
// //流程表单需要添加的参数
@ -1660,4 +1669,43 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -1660,4 +1669,43 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
public void saveEasDict(List<OnlineFromDict> list) {
onlFormHeadMapper.saveEasDict(list);
}
@Override
@Transactional(rollbackFor = Exception.class)
public OnlFormHead createMetaAndSync(String tableName, String tableTxt, String metaType, String sortFiled) {
OnlFormHead head = new OnlFormHead();
head.setId(idGenerator.nextLongId());
head.setTableName(tableName);
head.setTableTxt(tableTxt);
head.setTableType(metaType);
head.setStatus("0"); // 未同步状态
head.setLine(CustomUtil.MetaType_Entry.equals(metaType) ? 1 : 0);
if (StringUtils.isNotEmpty(sortFiled)) {
head.setSort(sortFiled);
head.setSortFiled("parent_id");
}
// 初始化审计字段(createUserId/updateUserId/createTime/updateTime/deletedFlag)
addOrUpdateBaseData(head, head.getId());
// Build DTO
OnlFormHeadDto dto = new OnlFormHeadDto();
BeanUtil.copyProperties(head, dto);
// Generate default fields
List<OnlFormField> defaultFields = new ArrayList<>();
defaultData(defaultFields, null, null, metaType);
dto.setOnlFormFieldList(defaultFields);
// Save
this.addAll(dto);
// Reload to get full info
OnlFormHead saved = this.getById(head.getId());
// Sync database (type=0: CREATE TABLE or ALTER TABLE ADD COLUMN)
this.syncDB(saved, 0L);
return saved;
}
}

9
common/common-online/pom.xml

@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>common-online</artifactId>
<version>1.0.0</version>
<version>1.0.4</version>
<name>common-online</name>
<packaging>jar</packaging>
@ -65,19 +65,18 @@ @@ -65,19 +65,18 @@
<dependency>
<groupId>apelet</groupId>
<artifactId>common-generator</artifactId>
<version>1.0.0</version>
<version>1.0.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>apelet</groupId>
<artifactId>common-flow</artifactId>
<version>1.0.0</version>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>apelet</groupId>
<artifactId>common-orm</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
<version>1.0.2</version>
</dependency>
</dependencies>

28
common/common-online/src/main/java/apelet/common/online/abstractplugin/ExecutePluginParent.java

@ -1,25 +1,25 @@ @@ -1,25 +1,25 @@
package apelet.common.online.abstractplugin;
import apelet.common.core.object.Model;
import apelet.common.core.object.ObjectValue;
import apelet.common.core.util.ApplicationContextHolder;
import apelet.common.generator.utils.OrmGenDataSourceUtil;
import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import apelet.common.online.model.*;
import apelet.common.online.model.FilterInfo;
import apelet.common.online.model.ShowParameter;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.service.OnlineDatasourceService;
import apelet.common.online.service.OnlineFormService;
import apelet.common.online.vo.OnlineEventPluginExecuteEntryVo;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import apelet.common.orm.impl.OrmDataSourceUtil;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 插件执行抽象类父类
@ -128,6 +128,16 @@ public abstract class ExecutePluginParent { @@ -128,6 +128,16 @@ public abstract class ExecutePluginParent {
public void change(String widgetVariableName, ObjectValue objectValue) {
}
/**
* 脚本事件
*
* @param widgetVariableName 控件标识
* @param objectValue 表单数据
*/
public void scriptEvent(String widgetVariableName, ObjectValue objectValue) {
}
public void returnData(String widgetVariableName, ObjectValue objectValue) {
}
@ -569,4 +579,10 @@ public abstract class ExecutePluginParent { @@ -569,4 +579,10 @@ public abstract class ExecutePluginParent {
return getDto().getModel().getCellField();
}
public void childPageCallback(String widgetVariableName, ObjectValue objectValue) {
}
}

112
common/common-online/src/main/java/apelet/common/online/abstractplugin/ListDataOrmService.java

@ -3,16 +3,20 @@ package apelet.common.online.abstractplugin; @@ -3,16 +3,20 @@ package apelet.common.online.abstractplugin;
import apelet.common.core.object.MyOrderParam;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ResponseResult;
import apelet.common.core.object.TokenData;
import apelet.common.online.abstractplugin.model.GridData;
import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import apelet.common.online.dto.OnlineFilterDto;
import apelet.common.online.model.OnlineColumn;
import apelet.common.online.model.OnlineDatasourceRelation;
import apelet.common.online.model.OnlineForm;
import apelet.common.online.model.OnlineTable;
import apelet.common.online.model.*;
import apelet.common.online.model.constant.DataRuleType;
import apelet.common.online.model.constant.FieldFilterType;
import apelet.common.online.model.constant.RelationType;
import apelet.common.orm.impl.*;
import apelet.common.orm.parser.FilterParser;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@ -36,14 +40,20 @@ public class ListDataOrmService extends ListDataService { @@ -36,14 +40,20 @@ public class ListDataOrmService extends ListDataService {
protected Filter getFilter() {
Filter f = super.getFilter();
for (OnlineFilterDto onlineFilterDto : filterDtoList) {
Object columnValue = onlineFilterDto.getColumnValue();
if (columnValue instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) columnValue;
columnValue = jsonObject.get("id");
}
if (onlineFilterDto.getFilterType() == FieldFilterType.EQUAL_FILTER) {
f.add(new FilterItem(onlineFilterDto.getColumnName(), FilterItem.equals, onlineFilterDto.getColumnValue()));
f.add(new FilterItem(onlineFilterDto.getColumnName(), FilterItem.equals, columnValue));
} else if (onlineFilterDto.getFilterType() == FieldFilterType.EXSITS) {
f.add(new FilterItem(null, FilterItem.EXISTS, onlineFilterDto.getColumnValue()));
f.add(new FilterItem(null, FilterItem.EXISTS, columnValue));
} else if (onlineFilterDto.getFilterType() == FieldFilterType.IN_LIST_FILTER) {
f.add(new FilterItem(onlineFilterDto.getColumnName(), FilterItem.INNER, onlineFilterDto.getColumnValue()));
f.add(new FilterItem(onlineFilterDto.getColumnName(), FilterItem.INNER, columnValue));
} else if (onlineFilterDto.getFilterType() == FieldFilterType.LIKE_FILTER) {
f.add(new FilterItem(onlineFilterDto.getColumnName(), FilterItem.like, "%" + onlineFilterDto.getColumnValue() + "%"));
f.add(new FilterItem(onlineFilterDto.getColumnName(), FilterItem.like, "%" + columnValue + "%"));
} else if (onlineFilterDto.getFilterType() == FieldFilterType.RANGE_FILTER) {
if (onlineFilterDto.getColumnValueStart() != null) {
f.add(new FilterItem(onlineFilterDto.getColumnName(), FilterItem.large_equals, onlineFilterDto.getColumnValueStart()));
@ -61,11 +71,34 @@ public class ListDataOrmService extends ListDataService { @@ -61,11 +71,34 @@ public class ListDataOrmService extends ListDataService {
List<Map<String, Object>> resList = new ArrayList<>();
OnlineTable masterTable = datasourceResult.getData().getMasterTable();
OnlineForm onlineForm = onlineFormService.getOnlineFormFromCache(dto.getFormId());
// 根据当前用户角色 + 当前表单匹配权限规则:非空→基础数据权限,为空→列表权限
List<PermissionDataRuleSchema> ruleSchemaList = this.getRuleSchemaListByCurrentRole();
String conditionExpression = null;
if (CollUtil.isNotEmpty(ruleSchemaList)) {
List<PermissionDataRuleSchema> filtered = new ArrayList<>();
if (StrUtil.isNotBlank(metaCode)) {
filtered = ruleSchemaList.stream().filter(s -> DataRuleType.BASIC_DATA == s.getRuleType() && metaCode.equals(s.getMetaCode())).collect(Collectors.toList());
}
if (CollUtil.isEmpty(filtered)) {
filtered = ruleSchemaList.stream().filter(s -> DataRuleType.LIST == s.getRuleType()).collect(Collectors.toList());
}
if (CollUtil.isNotEmpty(filtered)) {
conditionExpression = filtered.stream().map(PermissionDataRuleSchema::getConditionExpression).collect(Collectors.joining(") AND (", "(", ")"));
}
}
JSONObject widgetJson = JSON.parseObject(onlineForm.getWidgetJson());
JSONObject widgetList = widgetJson.getJSONObject("pc").getJSONObject("tableWidget");
Set<String> selectorSet = new TreeSet<>();
getOrmSelector(widgetList, selectorSet, masterTable, new HashMap<>());
Filter filter = getFilter();
if (StrUtil.isNotBlank(conditionExpression)) {
TokenData tokenData = TokenData.takeFromRequest();
// 解析 conditionExpression 中的 ${字段名} 占位符,从 tokenData 中取值填充
conditionExpression = resolvePlaceholders(conditionExpression, tokenData);
Filter parsed = FilterParser.parse(conditionExpression);
filter = filter.and(parsed);
}
Sorter sorter = new Sorter();
if (orderParam != null) {
@ -77,7 +110,6 @@ public class ListDataOrmService extends ListDataService { @@ -77,7 +110,6 @@ public class ListDataOrmService extends ListDataService {
}
if (!sorter.startsWithKey("create_time")) {
SorterItem sorterItem = new SorterItem("create_time");
;
sorterItem.setSortType(SortType._DESC);
sorter.add(sorterItem);
}
@ -100,6 +132,68 @@ public class ListDataOrmService extends ListDataService { @@ -100,6 +132,68 @@ public class ListDataOrmService extends ListDataService {
}
/**
* 解析 conditionExpression 中的 ${字段名} 占位符,从 tokenData 中取值填充。
* 如 ${loginName} → 当前登录用户名,${deptId} → 当前用户部门ID。
*/
private String resolvePlaceholders(String expression, TokenData tokenData) {
List<String> varNames = ReUtil.findAll("\\$\\{(\\w+)\\}", expression, 1);
for (String fieldName : varNames) {
Object value = BeanUtil.getProperty(tokenData, fieldName);
if (value != null) {
expression = expression.replace("${" + fieldName + "}", value.toString());
}
}
return expression;
}
/**
* 根据当前登录用户角色查询启用的数据规则,并按当前表单匹配。
* 兜底:角色未绑定任何数据规则时返回空列表,不做数据权限过滤。
*/
private List<PermissionDataRuleSchema> getRuleSchemaListByCurrentRole() {
// 1. 当前用户角色ID集合
TokenData tokenData = TokenData.takeFromRequest();
if (tokenData == null || StrUtil.isBlank(tokenData.getRoleIds())) {
return Collections.emptyList();
}
Set<Long> roleIds = StrUtil.split(tokenData.getRoleIds(), ',').stream()
.map(Long::valueOf).collect(Collectors.toSet());
// 2. 查询角色绑定的数据规则方案
List<RolePermissionSchema> roleSchemaList = rolePermissionSchemaService.listByRoleIds(roleIds);
if (CollUtil.isEmpty(roleSchemaList)) {
return Collections.emptyList();
}
Set<Long> schemaIdSet = roleSchemaList.stream().map(RolePermissionSchema::getSchemaId).collect(Collectors.toSet());
List<PermissionDataRuleSchema> ruleList = permissionDataRuleSchemaService.listEnabledByIds(schemaIdSet);
if (CollUtil.isEmpty(ruleList)) {
return Collections.emptyList();
}
// 3. 按当前表单过滤:先按 form_head_id 匹配,命中后再按 form_id_list 筛选
return ruleList.stream().filter(this::matchRuleByForm).collect(Collectors.toList());
}
/**
* 判断数据规则是否匹配当前在线表单。
* 先按 form_head_id 匹配规则所属主表,命中后再按 form_id_list 筛选;
* form_id_list 未配置则全部生效(原先逻辑),已配置时需包含当前表单id。
*/
private boolean matchRuleByForm(PermissionDataRuleSchema rule) {
// 1. 先按 form_head_id 匹配(规则作用于其所属主表)
if (!Objects.equals(rule.getFormHeadId(), onlFormHead.getId())) {
return false;
}
// 2. 再按 form_id_list 筛选
if (StrUtil.isBlank(rule.getFormIdList())) {
return true;
}
Long currentFormId = dto.getFormId();
if (currentFormId == null) {
return false;
}
return StrUtil.split(rule.getFormIdList(), ',').stream().map(Long::valueOf).collect(Collectors.toSet()).contains(currentFormId);
}
public void getOrmSelector(JSONObject jsonObject, Set<String> selectorSet, OnlineTable masterTable, Map<String, Object> entryIdMap) {
Map<Long, OnlineColumn> columnMap = masterTable.getColumnMap();
JSONArray array = jsonObject.getJSONArray("childWidgetList");

12
common/common-online/src/main/java/apelet/common/online/abstractplugin/ListDataService.java

@ -11,15 +11,11 @@ import apelet.common.online.abstractplugin.model.GridData; @@ -11,15 +11,11 @@ import apelet.common.online.abstractplugin.model.GridData;
import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import apelet.common.online.dto.OnlineFilterDto;
import apelet.common.online.model.OnlineDatasource;
import apelet.common.online.service.OnlineDictService;
import apelet.common.online.service.OnlineFormService;
import apelet.common.online.service.OnlineOperationService;
import apelet.common.online.service.OnlineTableService;
import apelet.common.online.service.*;
import apelet.common.online.util.OnlineOperationHelper;
import apelet.common.orm.impl.Filter;
import apelet.common.orm.impl.OrmDataSourceUtil;
import apelet.common.orm.impl.Sorter;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@ -41,6 +37,7 @@ public abstract class ListDataService { @@ -41,6 +37,7 @@ public abstract class ListDataService {
protected MyPageParam pageParam;
protected TraceParamDto traceParamDto;
protected OnlFormHead onlFormHead;
protected String metaCode;
protected OrmDataSourceUtil ormDataSourceUtil;
@ -51,6 +48,8 @@ public abstract class ListDataService { @@ -51,6 +48,8 @@ public abstract class ListDataService {
protected OnlineOperationHelper onlineOperationHelper;
protected OnlineOperationService onlineOperationService;
protected OnlineTableService onlineTableService;
protected IPermissionDataRuleSchemaService permissionDataRuleSchemaService;
protected IRolePermissionSchemaService rolePermissionSchemaService;
protected ResponseResult<OnlineDatasource> datasourceResult;
@ -64,6 +63,7 @@ public abstract class ListDataService { @@ -64,6 +63,7 @@ public abstract class ListDataService {
onlFormHead = (OnlFormHead) listParams.get("onlFormHead");
datasourceResult =
(ResponseResult<OnlineDatasource>) listParams.get("datasourceResult");
metaCode = (String) listParams.get("metaCode");
onlineFormService = ApplicationContextHolder.getBean("onlineFormService");
onlFormFieldService = ApplicationContextHolder.getBean("onlFormFieldServiceImpl");
@ -72,6 +72,8 @@ public abstract class ListDataService { @@ -72,6 +72,8 @@ public abstract class ListDataService {
onlineOperationService = ApplicationContextHolder.getBean("onlineOperationService");
onlineTableService = ApplicationContextHolder.getBean("onlineTableService");
ormDataSourceUtil = ApplicationContextHolder.getBean("ormGenDataSourceUtil");
permissionDataRuleSchemaService = ApplicationContextHolder.getBean("permissionDataRuleSchemaService");
rolePermissionSchemaService = ApplicationContextHolder.getBean("rolePermissionSchemaService");
}
@Setter

282
common/common-online/src/main/java/apelet/common/online/abstractplugin/MyInterfaceRulesExecute.java

@ -1,282 +0,0 @@ @@ -1,282 +0,0 @@
package apelet.common.online.abstractplugin;
import apelet.common.core.constant.AppDeviceType;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.core.util.ApplicationContextHolder;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.service.impl.OnlineFormServiceImpl;
import apelet.common.online.util.Jexl3Util;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
public class MyInterfaceRulesExecute extends ExecutePluginParent {
private OnlineFormServiceImpl onlineFormService;
private Jexl3Util jexl3Util;
public MyInterfaceRulesExecute() {
onlineFormService = ApplicationContextHolder.getBean("onlineFormService");
jexl3Util = ApplicationContextHolder.getBean("jexl3Util");
}
@Override
public void formCreated(String widgetVariableName, ObjectValue objectValue) {
Map values = objectValue.getValues();
List<Map<String, Object>> conditional = (List) values.get("conditional");
conditional("加载", widgetVariableName, conditional, objectValue);
List<Map<String, Object>> business = (List) values.get("business");
conditional("加载", widgetVariableName, business, objectValue);
}
@Override
public void change(String widgetVariableName, ObjectValue objectValue) {
Map values = objectValue.getValues();
List<Map<String, Object>> conditional = (List) values.get("conditional");
conditional("值改变", widgetVariableName, conditional, objectValue);
List<Map<String, Object>> business = (List) values.get("business");
conditional("值改变", widgetVariableName, business, objectValue);
}
void conditional(String type, String widgetVariableName, List<Map<String, Object>> isEstablish, ObjectValue objectValue) {
if (isEstablish == null || objectValue == null) {
return;
}
for (Map<String, Object> obj : isEstablish) {
List<JSONObject> allList = (List<JSONObject>) obj.get("allList");
List<JSONObject> condition;
if ((boolean) obj.get("establish")) {
condition = allList.stream().filter(f -> "1".equals(f.get("condition").toString())).collect(Collectors.toList());
} else {
condition = allList.stream().filter(f -> "2".equals(f.get("condition").toString())).collect(Collectors.toList());
}
//业务规则
d(type, widgetVariableName, condition, objectValue);
// 条件规则
b(type, widgetVariableName, condition);
}
}
void d(String pluginType, String widgetVariableName, List<JSONObject> jsonArray, ObjectValue objectValue) {
for (JSONObject obj : jsonArray) {
String checkList = obj.getJSONArray("checkList").toString();
if (checkList.contains(pluginType)) {
String type = obj.get("type").toString();
JSONArray treeList = obj.getJSONArray("treeList");
switch (type) {
case "1": { //计算定义公式的值并填写到指定列
String expressionTransl = obj.getString("expressionTranslation");
List<String> split = Arrays.asList(expressionTransl.split("="));
String fun = "";
for (String s : split) {
s = s.trim();
if (s.contains("$")) {
fun = s;
}
}
if (StringUtils.isEmpty(fun)) {
String tag = split.get(0);
String src = split.get(1);
if (src.contains(objectValue.getTableName())) {
List<String> list = Arrays.asList(tag.split(" "));
list.forEach(f -> {
});
} else {
List<String> list = Arrays.asList(tag.split("\\."));
super.setWidgetAttribute(list.get(list.size() - 1), AttributeEnum.VALUE_CHANGE, src);
}
} else {
fun = jexl3Util.getMySplit("\\$(.*?)\\$", fun);
List<String> funParam = Arrays.asList(jexl3Util.getMySplit("\\((.*?)\\)", fun).split(","));
Object[] objects = jexl3Util.getFunParam(funParam, objectValue);
Object o = onlineFormService.executeFunction(obj, fun, objects);
List<String> list = Arrays.asList(split.get(0).split("\\."));
super.setWidgetAttribute(list.get(list.size() - 1).trim(), AttributeEnum.VALUE_CHANGE, o);
}
break;
}
case "2": { //携带基础资料属性到指定列
JSONArray array = obj.getJSONArray("specifyJiList");
JSONObject keyNameObj = (JSONObject) objectValue.get("keyNameObj");
for (Object o : array) {
JSONObject o2 = (JSONObject) o;
String[] sourceFields = o2.get("sourceFields").toString().split("\\.");
String[] targetFields = o2.get("targetFields").toString().split("\\.");
if (sourceFields[0].equals(sourceFields[1])) {
ObjectValue o1 = (ObjectValue) objectValue.get(sourceFields[2]);
Object o11 = o1.get("id");
if (o11 != null) {
ObjectValue next1 = jexl3Util.getObjectValue(o1.get("id"), o1.getTableName());
Object values = next1.get(sourceFields[3]);
super.setWidgetAttribute(StrUtil.toCamelCase(targetFields[1]), AttributeEnum.VALUE_CHANGE, values);
}
} else {
ObjectCollection o3 = (ObjectCollection) objectValue.get(sourceFields[1]);
Iterator iterator = o3.iterator();
String rowKeySubset = getDto().getRowKeySubset();
Set<String> targetSet = new HashSet<String>();
if (rowKeySubset != null && !rowKeySubset.equals("")) {
String[] rowKeySubsetAry = rowKeySubset.split(",");
Collections.addAll(targetSet, rowKeySubsetAry);
}
while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next();
String deviceType = AppDeviceType.getDeviceType();
if (
(StringUtils.isEmpty(rowKeySubset) && next.get("rowkey") != null) || //分录弹出界面新增时,rowkey 为空,所以为空时,为当前编辑数据
(next.get("rowkey") != null && !targetSet.contains(next.get("rowkey"))) //rowKey相等时,为当前编辑数据
) {
continue;
}
ObjectValue o4 = (ObjectValue) next.get(sourceFields[2]);
if (o4 == null) {
return;
}
o4 = jexl3Util.getObjectValue(o4.get("id"), o4.getTableName());
Object values = o4.get(sourceFields[3]);
Map rowChangeMap = (Map) objectValue.get("rowChange");
if (rowChangeMap == null) {//为空是弹出界面编辑,不为空行内编辑
super.setWidgetAttribute(StrUtil.toCamelCase(targetFields[1]), AttributeEnum.VALUE_CHANGE, values);
} else {
super.setWidgetAttributeByEntry(widgetVariableName, targetFields[1], AttributeEnum.VALUE_CHANGE, values,
next.getString("rowkey"));
}
}
}
}
break;
}
case "3": { //设置当前编辑字段值到指定字段
JSONArray array = obj.getJSONArray("specifyList");
Object v = "";
for (Object o : array) {
JSONObject o2 = (JSONObject) o;
String sourceFields = (String) o2.get("sourceFields");
List<String> list = Arrays.asList(sourceFields.split("\\."));
String filed = list.get(list.size() - 1);
String table = list.get(list.size() - 2);
if (table.equals(objectValue.getTableName())) {
v = objectValue.get(filed);
} else {
ObjectCollection o1 = (ObjectCollection) objectValue.get(table);
Iterator iterator = o1.iterator();
while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next();
v = next.get(String.valueOf(filed));
}
}
String targetFields = (String) o2.get("targetFields");
List<String> list1 = Arrays.asList(targetFields.split("\\."));
super.setWidgetAttribute(list1.get(list1.size() - 1), AttributeEnum.VALUE_CHANGE, v);
}
break;
}
case "4": { //清除指定字段值
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
if (jsonObject.containsKey("zxName")) {
String[] split = jsonObject.getString("zxName").split("\\.");
if (split[0].equals(split[1])) {
String name = jsonObject.get("label").toString();
super.setWidgetAttribute(name, AttributeEnum.VALUE_CHANGE, "");
} else {
String tab = jexl3Util.getMySplit("\\((.*?)\\)", split[1]);
JSONObject keyNameObj = (JSONObject) objectValue.get("keyNameObj");
String tabId = (String) keyNameObj.getJSONArray(tab).get(0);
ObjectCollection collection = (ObjectCollection) objectValue.get(tab);
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next();
super.setWidgetAttributeByEntry(tabId, jsonObject.get("label").toString(), AttributeEnum.VALUE_CHANGE, "", next.getString("rowkey"));
}
}
}
}
break;
}
}
}
}
}
void b(String pluginType, String widgetVariableName, List<JSONObject> jsonArray) {
for (JSONObject obj : jsonArray) {
String checkList = obj.getJSONArray("checkList").toString();
if (checkList.contains(pluginType)) {
String type = obj.get("type").toString();
JSONArray treeList = obj.getJSONArray("treeList");
JSONArray objects = new JSONArray();
switch (type) {
case "5": { //锁定控件
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
String name = jsonObject.get("variableName").toString();
if (jsonObject.containsKey("btnId")) {
String showName = jsonObject.get("showName").toString();
String parentVariableName = jsonObject.get("parentVariableName").toString();
JSONObject json = new JSONObject();
json.put("name", showName);
json.put("disable", true);
objects.add(json);
super.setWidgetAttribute(parentVariableName, AttributeEnum.CHANGEOPERATE, objects);
} else {
super.setWidgetAttribute(name, AttributeEnum.DISABLED, true);
}
}
break;
}
case "6": { //解锁控件
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
String name = jsonObject.get("variableName").toString();
super.setWidgetAttribute(name, AttributeEnum.DISABLED, false);
}
break;
}
case "7": { //显示控件
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
String name = jsonObject.get("variableName").toString();
super.setWidgetAttribute(name, AttributeEnum.SHOW, true);
}
break;
}
case "8": { //隐藏控件
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
String name = jsonObject.get("variableName").toString();
super.setWidgetAttribute(name, AttributeEnum.SHOW, false);
}
break;
}
}
}
}
}
@Override
public void beforeSelector(String widgetVariableName, ObjectValue objectValue) {
super.beforeSelector(widgetVariableName, objectValue);
}
@Override
public void entryEditComplete(String widgetVariableName, ObjectValue objectValue) {
super.entryEditComplete(widgetVariableName, objectValue);
}
public String removeSymbols(String input) {
// 使用正则表达式替换所有非字母数字字符为空字符串
return input.replaceAll("_", "").toLowerCase();
}
}

1
common/common-online/src/main/java/apelet/common/online/abstractplugin/model/ListColumn.java

@ -27,5 +27,6 @@ public class ListColumn { @@ -27,5 +27,6 @@ public class ListColumn {
private boolean uploadFile;//是否是附件
private boolean treeLine;//是否树形结构
private Map<String, Object> column = null; //Html字段设置 "column:"{ "fieldKind" : 34}
private boolean customize;//是否跳转自定义页面
//
}

648
common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java

@ -3,8 +3,10 @@ package apelet.common.online.controller; @@ -3,8 +3,10 @@ package apelet.common.online.controller;
import apelet.common.core.annotation.MyRequestBody;
import apelet.common.core.annotation.NoAuthInterface;
import apelet.common.core.cache.CacheConfig;
import apelet.common.core.cache.CacheKey;
import apelet.common.core.constant.AppDeviceType;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.constant.GlobalDeletedFlag;
import apelet.common.core.object.*;
import apelet.common.core.util.MyCommonUtil;
import apelet.common.core.util.MyModelUtil;
@ -12,19 +14,25 @@ import apelet.common.core.util.MyPageUtil; @@ -12,19 +14,25 @@ import apelet.common.core.util.MyPageUtil;
import apelet.common.core.validator.UpdateGroup;
import apelet.common.dbutil.object.SqlTable;
import apelet.common.dbutil.object.SqlTableColumn;
import apelet.common.generator.model.OnlFormField;
import apelet.common.generator.model.OnlFormHead;
import apelet.common.generator.service.IOnlFormFieldService;
import apelet.common.generator.service.IOnlFormHeadService;
import apelet.common.generator.utils.CustomUtil;
import apelet.common.generator.utils.cache.MetaCacheService;
import apelet.common.log.annotation.OperationLog;
import apelet.common.log.model.constant.SysOperationLogType;
import apelet.common.online.config.OnlineProperties;
import apelet.common.online.dto.*;
import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import apelet.common.online.dto.OnlineFormDto;
import apelet.common.online.model.*;
import apelet.common.online.model.constant.FormType;
import apelet.common.online.service.*;
import apelet.common.online.util.OnlineRedisKeyUtil;
import apelet.common.online.util.WidgetFieldTypeMapping;
import apelet.common.online.util.WidgetJsonUtil;
import apelet.common.online.vo.OnlineFormVo;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@ -34,6 +42,7 @@ import com.alibaba.fastjson.JSONObject; @@ -34,6 +42,7 @@ import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.page.PageMethod;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.google.common.collect.Lists;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -47,8 +56,9 @@ import org.springframework.web.bind.annotation.*; @@ -47,8 +56,9 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.groups.Default;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
@ -83,12 +93,42 @@ public class OnlineFormController { @@ -83,12 +93,42 @@ public class OnlineFormController {
@Autowired
IOnlFormHeadService onlFormHeadService;
@Autowired
IOnlFormFieldService onlFormFieldService;
@Autowired
OnlinePageService onlinePageService;
@Autowired
private IOnlineFormFieldSyncService onlineFormFieldSyncService;
@Autowired
private OnlineProperties properties;
@Resource(name = "caffeineCacheManager")
private CacheManager cacheManager;
@Autowired
private MetaCacheService metaCacheService;
@Autowired(required = false)
private FormButtonPermissionChecker buttonPermissionChecker;
/**
* 自动同步字段到历史数据:给所有缺该字段的 onl_form_head 表补字段(可按表类型过滤)。
* 已同步的表补字段+同步数据库+回写 zz_online_column;未同步的表只补字段。
*
* @param fieldName 字段名
* @param type 字段类型(数据库类型名如 varchar/bigint/decimal,或 onl_fieldType 字典码 0~5)
* @param length 字段长度(varchar 默认 255;decimal 为精度、小数位默认 2)
* @param description 字段描述
* @param tableType 表类型(0 附表 / 1 主表 / 2 单表,不传则全部)
*/
@GetMapping(value = "/syncFieldToHistory")
@NoAuthInterface
public ResponseResult<?> syncFieldToHistory(@RequestParam String fieldName,
@RequestParam String type,
@RequestParam(required = false) Integer length,
@RequestParam(required = false) String description,
@RequestParam(required = false) String tableType) {
return ResponseResult.success(onlineFormFieldSyncService.syncFieldToHistory(fieldName, type, length, description, tableType));
}
/**
* 新增在线表单数据。
*
@ -212,120 +252,19 @@ public class OnlineFormController { @@ -212,120 +252,19 @@ public class OnlineFormController {
.eq(OnlineDatasourceRelation::getRelationType,0)
.eq(OnlineDatasourceRelation::getDatasourceId,onlineFormDto.getDatasourceIdList().get(0)));
}
// for (int i = 0; i < jsonArray.size(); i++) {
// JSONObject object = jsonArray.getJSONObject(i);
// Integer widgetType = object.getInteger("widgetType");
// //外键id
// String columnId = object.getJSONObject("bindData").getString("columnId");
// //主表id
// String tableId = object.getJSONObject("bindData").getString("tableId");
// OnlineTable onlineTable = onlineTableService.getById(tableId);
// //从表名称
// String slaveTableName = object.getJSONObject("bindData").getString("slaveTableName");
// if (402 == widgetType){
// if (object.getJSONObject("props") != null && object.getJSONObject("props").getJSONObject("relativeTable") != null){
// String relationId = object.getJSONObject("props").getJSONObject("relativeTable").getString("relationId");//关联id
// if(StringUtils.isNotEmpty(relationId)){
// OnlineDatasourceRelation onlineDatasourceRelation = onlineDatasourceRelationService.getById(Long.valueOf(relationId));
// if(onlineDatasourceRelation != null){
// OnlineTable sTable = onlineTableService.getById(onlineDatasourceRelation.getSlaveTableId());
// if( ! (onlineDatasourceRelation.getMasterColumnId() == Long.valueOf(columnId)
// && onlineDatasourceRelation.getRelationType() == 0
// && onlineDatasourceRelation.getDatasourceId() == onlineFormDto.getDatasourceIdList().get(0)
// && sTable.getTableName().equals(slaveTableName) )
// ){//已存在关联关系,不符合规则
// onlineDatasourceRelationService.remove(onlineDatasourceRelation.getRelationId());
// }
// }
// }
// }
// OnlineDatasourceRelation onr = onlineDatasourceRelationService.getOne(new LambdaQueryWrapper<OnlineDatasourceRelation>()
// .eq(OnlineDatasourceRelation::getMasterColumnId,columnId)
// .eq(OnlineDatasourceRelation::getRelationType,0)
// .eq(OnlineDatasourceRelation::getDatasourceId,onlineFormDto.getDatasourceIdList().get(0)));
// if(slaveTableName == null){
// errorMessage = "关联控件校验失败,字段绑定表";
// return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
// }
// if(onr != null){
// OnlineTable sTable = onlineTableService.getById(onr.getSlaveTableId());
// if(onr != null && !slaveTableName.equals(sTable.getTableName())){
// errorMessage = "关联控件校验失败,字段关联多张表";
// return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
// }
// if(onr != null && slaveTableName.equals(sTable.getTableName())) {
// if (object.getJSONObject("props") != null && object.getJSONObject("props").getJSONObject("relativeTable") != null) {
// if (object.getJSONObject("props").getJSONObject("relativeTable").getString("relativeFormType").equals("2")) {//关联查询数据源\
// OnlineDatasource datasource = onlineDatasourceService.getById(onlineFormDto.getDatasourceIdList().get(0));
// object.getJSONObject("props").getJSONObject("relativeTable").put("datasourceId", onlineFormDto.getDatasourceIdList().get(0));
// object.getJSONObject("props").getJSONObject("relativeTable").put("variableName",datasource.getVariableName() );
// object.getJSONObject("props").getJSONObject("relativeTable").put("relationId",onr.getRelationId());
// object.getJSONObject("props").getJSONObject("relativeTable").put("relativeTableName",slaveTableName);
// }
// }
// continue;
// }
// }
// //先删除对应关联关系
// onlineDatasourceRelationService.remove(new LambdaQueryWrapper<OnlineDatasourceRelation>()
// .eq(OnlineDatasourceRelation::getMasterColumnId,columnId)
// .eq(OnlineDatasourceRelation::getRelationType,0)
// .eq(OnlineDatasourceRelation::getDatasourceId,onlineFormDto.getDatasourceIdList().get(0)));
// OnlineDatasourceRelationDto onlineDatasourceRelationDto = new OnlineDatasourceRelationDto();
// onlineDatasourceRelationDto.setDatasourceId(onlineFormDto.getDatasourceIdList().get(0));
// //关联名称
// onlineDatasourceRelationDto.setRelationName(object.getString("showName"));
// //关联标识-组件标识
// onlineDatasourceRelationDto.setVariableName(onlineTable.getTableName()+"_"+object.getString("variableName")+"_"+slaveTableName);
// //关联类型
// onlineDatasourceRelationDto.setRelationType(0);
// //主键字段id
// onlineDatasourceRelationDto.setMasterColumnId(Long.valueOf(columnId));
// //从表名称
// onlineDatasourceRelationDto.setSlaveTableName(slaveTableName);
// //从表字段名称
// onlineDatasourceRelationDto.setSlaveColumnName("id");
// //是否级联删除
// onlineDatasourceRelationDto.setCascadeDelete(false);
// //是否左连接查询
// onlineDatasourceRelationDto.setLeftJoin(true);
// OnlineDatasourceRelation onlineDatasourceRelation =
// MyModelUtil.copyTo(onlineDatasourceRelationDto, OnlineDatasourceRelation.class);
// SqlTable slaveTable = onlineDblinkService.getDblinkTable(
// onlineDblink, onlineDatasourceRelationDto.getSlaveTableName());
// if (slaveTable == null) {
// errorMessage = "关联控件建立一对一关系失败:数据验证失败,指定的数据表不存在!";
// return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
// }
// SqlTableColumn slaveColumn = null;
// for (SqlTableColumn column : slaveTable.getColumnList()) {
// if (column.getColumnName().equals(onlineDatasourceRelationDto.getSlaveColumnName())) {
// slaveColumn = column;
// break;
// }
// }
// if (slaveColumn == null) {
// errorMessage = "关联控件建立一对一关系失败:数据验证失败,指定的数据表字段 [" + onlineDatasourceRelationDto.getSlaveColumnName() + "] 不存在!";
// return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
// }
// onlineDatasourceRelation = onlineDatasourceRelationService.saveNew(onlineDatasourceRelation, slaveTable, slaveColumn);
// if (object.getJSONObject("props") != null && object.getJSONObject("props").getJSONObject("relativeTable") != null){
// if (object.getJSONObject("props").getJSONObject("relativeTable").getString("relativeFormType").equals("2")) {//关联查询数据源\
// OnlineDatasource datasource = onlineDatasourceService.getById(onlineFormDto.getDatasourceIdList().get(0));
// object.getJSONObject("props").getJSONObject("relativeTable").put("datasourceId", onlineFormDto.getDatasourceIdList().get(0));
// object.getJSONObject("props").getJSONObject("relativeTable").put("variableName",datasource.getVariableName() );
// object.getJSONObject("props").getJSONObject("relativeTable").put("relativeTableName",slaveTableName);
// }
// object.getJSONObject("props").getJSONObject("relativeTable").put("relationId",onlineDatasourceRelation.getRelationId());
// }
//
//
// }
// }
onlineForm.setWidgetJson(jsonObject.toJSONString());
}
// ★ 新增: 处理新增字段和子表(仅编辑表单:5=编辑 / 10=移动端编辑)
if (onlineFormDto.getFormType() == FormType.EDIT_FORM) {
JSONObject updatedJson = syncNewFieldsAndSubTables(onlineForm, onlineFormDto, JSON.parseObject(onlineForm.getWidgetJson()));
if (updatedJson != null) {
onlineForm.setWidgetJson(updatedJson.toJSONString());
onlineFormDto.setWidgetJson(updatedJson.toJSONString());
}
}
if (!onlineFormService.update(onlineForm, originalOnlineForm, datasourceIdSet)) {
redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineFormKey(onlineForm.getFormId())).delete();
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
@ -495,7 +434,7 @@ public class OnlineFormController { @@ -495,7 +434,7 @@ public class OnlineFormController {
jsonObject.put("onlineVirtualColumnList", virtualColumnList);
Set<Long> dictIdSet = onlineColumnList.stream()
.filter(c -> c.getDictId() != null).map(OnlineColumn::getDictId).collect(Collectors.toSet());
Set<Long> widgetDictIdSet = this.extractDictIdSetFromWidgetJson(onlineForm.getWidgetJson());
Set<Long> widgetDictIdSet = WidgetJsonUtil.extractDictIdSet(onlineForm.getWidgetJson());
CollUtil.addAll(dictIdSet, widgetDictIdSet);
if (CollUtil.isNotEmpty(dictIdSet)) {
List<OnlineDict> onlineDictList = onlineDictService.getOnlineDictListFromCache(dictIdSet);
@ -574,6 +513,19 @@ public class OnlineFormController { @@ -574,6 +513,19 @@ public class OnlineFormController {
@PostMapping("/eventPluginExecute")
public ResponseResult<List<Object>> eventPluginExecute(
@MyRequestBody OnlineEventPluginExecuteDto onlineEventPluginExecuteDto) {
// // 按钮权限校验
// if (buttonPermissionChecker != null) {
// Long formId = onlineEventPluginExecuteDto.getFormId();
// String buttonName = onlineEventPluginExecuteDto.getEventWidgetVariableName();
// if (formId != null && buttonName != null) {
// if (!buttonPermissionChecker.hasPermission(formId, buttonName)) {
// return ResponseResult.error(ErrorCodeEnum.NO_OPERATION_PERMISSION,
// "当前角色无此按钮操作权限!");
// }
// }
// }
List<Object> eventPluginExecuteVoList =
onlineFormService.eventPluginExecute(onlineEventPluginExecuteDto);
return ResponseResult.success(eventPluginExecuteVoList);
@ -623,46 +575,6 @@ public class OnlineFormController { @@ -623,46 +575,6 @@ public class OnlineFormController {
return ResponseResult.success(datasourceIdSet);
}
private Set<Long> extractDictIdSetFromWidgetJson(String widgetJson) {
Set<Long> dictIdSet = new HashSet<>();
if (StrUtil.isBlank(widgetJson)) {
return dictIdSet;
}
JSONObject allData = JSON.parseObject(widgetJson);
JSONObject pcData = allData.getJSONObject(AppDeviceType.getDeviceType());
if (MapUtil.isEmpty(pcData)) {
return dictIdSet;
}
JSONArray widgetListArray = pcData.getJSONArray("widgetList");
if (CollUtil.isEmpty(widgetListArray)) {
return dictIdSet;
}
for (int i = 0; i < widgetListArray.size(); i++) {
this.recursiveExtractDictId(widgetListArray.getJSONObject(i), dictIdSet);
}
return dictIdSet;
}
private void recursiveExtractDictId(JSONObject widgetData, Set<Long> dictIdSet) {
JSONObject propsData = widgetData.getJSONObject("props");
if (MapUtil.isNotEmpty(propsData)) {
JSONObject dictInfoData = propsData.getJSONObject("dictInfo");
if (MapUtil.isNotEmpty(dictInfoData)) {
Long dictId = dictInfoData.getLong("dictId");
if (dictId != null) {
dictIdSet.add(dictId);
}
}
}
JSONArray childWidgetArray = widgetData.getJSONArray("childWidgetList");
if (CollUtil.isNotEmpty(childWidgetArray)) {
for (int i = 0; i < childWidgetArray.size(); i++) {
this.recursiveExtractDictId(childWidgetArray.getJSONObject(i), dictIdSet);
}
}
}
/**
* 获取流程对应的在线表单
* @param bosType
@ -685,4 +597,432 @@ public class OnlineFormController { @@ -685,4 +597,432 @@ public class OnlineFormController {
}
return ResponseResult.success(onlineFormList);
}
/**
* 根据表单类型查询表单列表
*
* @param formType 表单类型
* @return 表单列表
* @see apelet.common.online.model.constant.FormType
*/
@PostMapping(value = "/queryFormByType")
public ResponseResult<List<OnlineForm>> queryFormByType(@MyRequestBody Integer formType) {
if (formType == null) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
return ResponseResult.success(onlineFormService.queryListByFormType(Lists.newArrayList(formType)));
}
// ============ 字段diff+同步 相关私有方法 ============
/**
* 对比 widgetJson 与现有 OnlFormField / OnlFormHead 识别新增字段和子表,
* 创建 OnlFormField 记录、同步数据库、创建 OnlineColumn、回填 columnId/relationId。
*
* @return 修改后的 widgetJson (含回填的 columnId/relationId),无变更则返回 null
*/
private JSONObject syncNewFieldsAndSubTables(OnlineForm form, OnlineFormDto dto, JSONObject widgetJson) {
// 1. 获取数据源 → 主表 → 主表 head
OnlineDatasource datasource = onlineDatasourceService.getById(dto.getDatasourceIdList().get(0));
OnlineTable masterTable = onlineTableService.getById(datasource.getMasterTableId());
OnlFormHead masterHead = onlFormHeadService.getOne(new LambdaQueryWrapper<OnlFormHead>().eq(OnlFormHead::getTableName, masterTable.getTableName()));
if (masterHead == null) return null;
boolean changed = false;
// === 统一取值:pc/mobile 所有"表-字段"绑定关系(表标识 → 字段名 → 控件) ===
Map<String, Map<String, JSONObject>> tableFieldMap = WidgetJsonUtil.buildTableFieldMap(widgetJson);
// === 新增表:widgetType=100 且无 tableId(只有 tableName)的表统一先建表 ===
Set<String> newTableNames = WidgetJsonUtil.collectNewSubTableNames(widgetJson);
if (!newTableNames.isEmpty()) changed |= createNewSubTables(widgetJson, masterHead, datasource, newTableNames);
// === 逐表 diff 新增字段 ===
for (Map.Entry<String, Map<String, JSONObject>> entry : tableFieldMap.entrySet()) {
String tableKey = entry.getKey();
Map<String, JSONObject> columnWidgetMap = entry.getValue();
if (columnWidgetMap.isEmpty()) continue;
// 解析表:纯数字 → 已有表(主表/单表/附表统一处理);字符串 → 刚创建的新表
OnlineTable table = tableKey.matches("\\d+") ? onlineTableService.getById(Long.valueOf(tableKey)) : onlineTableService.getOne(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, tableKey.toLowerCase()));
if (table == null) continue;
OnlFormHead head = onlFormHeadService.getOne(new LambdaQueryWrapper<OnlFormHead>().eq(OnlFormHead::getTableName, table.getTableName()));
if (head == null) continue;
List<OnlFormField> existingFields = onlFormFieldService.list(new LambdaQueryWrapper<OnlFormField>().eq(OnlFormField::getHeadId, head.getId()));
Map<String, OnlFormField> fieldByColumnName = existingFields.stream().collect(Collectors.toMap(OnlFormField::getFieldName, Function.identity(), (a, b) -> a));
List<String> newColumnNames = new ArrayList<>();
for (Map.Entry<String, JSONObject> columnEntry : columnWidgetMap.entrySet()) {
String columnName = columnEntry.getKey();
JSONObject widget = columnEntry.getValue();
OnlFormField existingField = fieldByColumnName.get(columnName);
if (existingField != null) {
// 已有字段:带 slaveTableId 则更新 refPropert(子表列仅关联列 comType=402 生效)
boolean isTableColumn = !widget.containsKey("bindData");
Integer comType = widget.getInteger("comType");
boolean isAssociation = !isTableColumn || (comType != null && comType == 402);
String slaveTableId = extractSlaveTableId(widget);
if (isAssociation && StrUtil.isNotEmpty(slaveTableId)) {
existingField.setRefPropert(Long.valueOf(slaveTableId));
onlFormFieldService.updateById(existingField);
changed = true;
}
continue;
}
// 新增字段:识别"字段控件"还是"子表列",非数据控件跳过
boolean isTableColumn = !widget.containsKey("bindData");
Integer widgetType = widget.getInteger("widgetType");
if (!isTableColumn && (widgetType == null || !WidgetFieldTypeMapping.isDataWidget(widgetType))) continue;
if (isTableColumn) {
// 子表列:按 comType 从映射表取字段类型,未映射则回退 varchar(750)
Integer comType = widget.getInteger("comType");
this.saveField(WidgetFieldTypeMapping.isDataWidget(comType) ? comType : 750, widget.getString("showName"), null, head, columnName, extractSlaveTableId(widget));
// 子表列:已有 variableName 标签才转驼峰反写字段名,没有则不动
if (widget.containsKey("variableName")) {
widget.put("variableName", StrUtil.toCamelCase(columnName));
}
} else {
JSONObject bindData = widget.getJSONObject("bindData");
String ref = bindData.containsKey("slaveTableId") ? bindData.getString("slaveTableId") : null;
this.saveField(widgetType, bindData.getString("columnComment"), widget.getString("showName"), head, columnName, ref);
// 新增字段:将字段名转驼峰后反写到 variableName,保证表单变量名与数据库字段一致
widget.put("variableName", StrUtil.toCamelCase(columnName));
}
newColumnNames.add(columnName);
changed = true;
}
if (!newColumnNames.isEmpty()) {
// ALTER TABLE ADD COLUMN
onlFormHeadService.syncDB(head, 0L);
// 批量获取列信息,避免 N+1
OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId());
List<SqlTableColumn> allColumns = onlineDblinkService.getDblinkTableColumnList(dblink, table.getTableName());
Map<String, SqlTableColumn> columnMap = new HashMap<>();
if (CollUtil.isNotEmpty(allColumns)) {
for (SqlTableColumn col : allColumns) {
if (col.getColumnName() != null) columnMap.put(col.getColumnName().toLowerCase(), col);
}
}
// 增量创建 OnlineColumn + 回填 columnId 到所有匹配控件
for (String columnName : newColumnNames) {
SqlTableColumn sqlCol = columnMap.get(columnName.toLowerCase());
if (sqlCol == null) continue;
long columnId = onlineColumnService.saveBySqlTable(sqlCol, table.getTableId(), columnWidgetMap.get(columnName).getString("showName"));
WidgetJsonUtil.backfillColumnId(widgetJson, columnName, columnId, table.getTableId(), table.getTableName());
}
}
// 删除缓存
redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineTableKey(table.getTableId())).delete();
redissonClient.getBucket(CacheKey.makeTableKey(table.getTableName())).delete();
redissonClient.getBucket(CacheKey.makeChildFieldListKey(head.getId())).delete();
metaCacheService.removeData(table.getTableName());
}
// === mobile 端子表(widgetType=102)专用同步,保持原有逻辑 ===
changed |= syncMobileSubTables(widgetJson, masterHead, datasource);
return changed ? widgetJson : null;
}
/**
* 创建不存在的附表:新建 OnlFormHead/OnlineTable、绑定数据源关系、回填 relationId 并追加 childName。
* 若 newSubTableNames 为空(tableIdAndPropMap 走 tableId 取值没取到),兜底通过 tableName 收集附表。
*
* @param widgetJson 表单 JSON
* @param onlFormHead 主表
* @param datasource 数据源
* @param newSubTableNames 附表名集合(可为空)
* @return 是否有变更
*/
private boolean createNewSubTables(JSONObject widgetJson, OnlFormHead onlFormHead, OnlineDatasource datasource, Set<String> newSubTableNames) {
if (newSubTableNames.isEmpty()) {
// 如果他为空,说明我们走tableId取值没取到,我们就去一下 tableName;
newSubTableNames = WidgetJsonUtil.collectSubTableNames(widgetJson);
}
if (newSubTableNames.isEmpty()) {
return false;
}
for (String subTableName : newSubTableNames) {
// 因为是附表,所以要加上
subTableName = "t_" + subTableName + "_entry";
OnlFormHead subHead = onlFormHeadService.createMetaAndSync(subTableName, onlFormHead.getTableTxt() + "附表", CustomUtil.MetaType_Entry, onlFormHead.getTableName());
// 获取子表的 OnlineTable
OnlineTable subOnlineTable = onlineTableService.getOne(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, subTableName.toLowerCase()));
if (subOnlineTable == null) {
// 从DB获取表结构并创建 OnlineTable
SqlTable subSqlTable = onlineDblinkService.getDblinkTable(onlineDblinkService.getById(datasource.getDblinkId()), subTableName);
if (subSqlTable != null) {
subOnlineTable = onlineTableService.saveNewFromSqlTable(subSqlTable);
}
}
OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId());
onlineDatasourceRelationService.addOnlineDatasourceRelation(onlFormHead, datasource, null, dblink);
if (subOnlineTable != null) {
WidgetJsonUtil.backfillTableId(widgetJson, subTableName, subOnlineTable.getTableId());
}
}
//这里要将 所有的附表名称 加到 apelet.common.generator.model.OnlFormHead.childName 后面
String newSubTableName = String.join(",", newSubTableNames);
if (StringUtils.isNotBlank(onlFormHead.getChildName())) {
onlFormHead.setChildName(onlFormHead.getChildName() + "," + newSubTableName);
} else {
onlFormHead.setChildName(newSubTableName);
}
onlFormHeadService.updateById(onlFormHead);
return true;
}
/**
* 提取控件/子表列绑定的从表 id(slaveTableId)。
* 字段控件在 bindData 下;子表列对象直接挂在顶层。
*/
private String extractSlaveTableId(JSONObject widget) {
if (widget == null) {
return null;
}
JSONObject bindData = widget.getJSONObject("bindData");
if (bindData != null) {
return bindData.containsKey("slaveTableId") ? bindData.getString("slaveTableId") : null;
}
return widget.getString("slaveTableId");
}
private void saveField(Integer widgetType, String columnComment, String showName, OnlFormHead onlFormHead, String fieldName, String refPropert) {
OnlFormField field = new OnlFormField();
field.setHeadId(onlFormHead.getId());
field.setFieldName(fieldName);
field.setFieldType(WidgetFieldTypeMapping.getFieldTypeId(widgetType));
Integer length = WidgetFieldTypeMapping.getDefaultLength(widgetType);
if (length != null) {
field.setFieldLength(length);
}
if (StringUtils.isNotBlank(refPropert)) {
field.setRefPropert(Long.valueOf(refPropert));
}
field.setIsEmpty(1);
field.setIsDefault(0);
field.setRowDisabled(0);
field.setSyncFlag(0);
// 逻辑删除标记:默认正常(1),onl_form_field 未配置 @TableLogic,需显式写入
field.setDeletedFlag(GlobalDeletedFlag.NORMAL);
field.setCreateTime(new Date());
field.setUpdateTime(new Date());
field.setCreateUserId(TokenData.takeFromRequest().getUserId());
field.setUpdateUserId(TokenData.takeFromRequest().getUserId());
// 从 widget 的 bindData.columnComment 设置字段备注
field.setFieldRemark(columnComment);
if (StringUtils.isNotBlank(showName) && (StringUtils.isBlank(field.getFieldRemark()) || StringUtils.equals("null", field.getFieldRemark()))) {
field.setFieldRemark(showName);
}
onlFormFieldService.save(field);
}
// ============ mobile 端子表同步相关方法 ============
/**
* 递归遍历 mobile 控件树(childWidgetList),收集所有 widgetType=102 的子表组件。
* 102 可嵌套在分组/卡片等容器内。
*/
private void walkMobileSubTableWidgets(JSONArray widgetList, Consumer<JSONObject> consumer) {
if (CollUtil.isEmpty(widgetList)) return;
for (int i = 0; i < widgetList.size(); i++) {
JSONObject widget = widgetList.getJSONObject(i);
if (widget == null) continue;
Integer widgetType = widget.getInteger("widgetType");
if (widgetType != null && widgetType == 102) {
consumer.accept(widget);
continue;
}
JSONArray childWidgetList = widget.getJSONArray("childWidgetList");
if (CollUtil.isNotEmpty(childWidgetList)) walkMobileSubTableWidgets(childWidgetList, consumer);
}
}
/**
* 同步 mobile 端的子表(新增子表 + 已有子表的新增字段)。
* 规则:bindData 有 tableId → 已有表,向下找字段;只有 tableName → 新增表,不找字段,建表后回写 tableId。
* 子表: mobile -> widgetList -> [widgetType=102] -> bindData -> tableName/tableId
* 字段: mobile -> widgetList -> childWidgetList -> childWidgetList -> bindData -> columnName
*/
private boolean syncMobileSubTables(JSONObject widgetJson, OnlFormHead onlFormHead, OnlineDatasource datasource) {
JSONObject mobile = widgetJson.getJSONObject("mobile");
if (mobile == null) return false;
JSONArray mobileWidgetList = mobile.getJSONArray("widgetList");
if (mobileWidgetList == null) return false;
boolean changed = false;
Set<String> newSubTableNames = new LinkedHashSet<>();
Set<Long> existingSubTableIds = new LinkedHashSet<>();
walkMobileSubTableWidgets(mobileWidgetList, widget -> {
JSONObject bindData = widget.getJSONObject("bindData");
if (bindData == null) return;
String tableName = bindData.getString("tableName");
String tableIdStr = bindData.getString("tableId");
if (StrUtil.isNotEmpty(tableName) && StrUtil.isEmpty(tableIdStr)) {
newSubTableNames.add(tableName);
} else if (StrUtil.isNotEmpty(tableIdStr)) {
existingSubTableIds.add(Long.valueOf(tableIdStr));
}
});
// 处理新子表: 创建后回写 tableId + masterColumnName
if (!newSubTableNames.isEmpty()) {
changed |= createNewSubTables(widgetJson, onlFormHead, datasource, newSubTableNames);
backfillMobileSubTableId(widgetJson, newSubTableNames);
}
// 处理已有子表的新增字段
if (!existingSubTableIds.isEmpty()) {
changed |= syncMobileSubTableNewFields(widgetJson, datasource, existingSubTableIds);
}
return changed;
}
/**
* 回写 mobile 端子表的 tableId 和 masterColumnName。
* 路径: mobile -> widgetList -> [widgetType=102] -> bindData
*/
private void backfillMobileSubTableId(JSONObject widgetJson, Set<String> newSubTableNames) {
JSONObject mobile = widgetJson.getJSONObject("mobile");
if (mobile == null) return;
JSONArray mobileWidgetList = mobile.getJSONArray("widgetList");
if (mobileWidgetList == null) return;
walkMobileSubTableWidgets(mobileWidgetList, widget -> {
JSONObject bindData = widget.getJSONObject("bindData");
if (bindData == null) return;
String tableName = bindData.getString("tableName");
if (StrUtil.isEmpty(tableName) || !newSubTableNames.contains(tableName)) return;
// createNewSubTables 只回写了 100 组件的 tableId,102 组件的在这里补
if (!bindData.containsKey("tableId")) {
OnlineTable subTable = onlineTableService.getOne(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, tableName.toLowerCase()));
if (subTable != null) bindData.put("tableId", subTable.getTableId());
}
bindData.put("masterColumnName", "id");
});
}
/**
* 同步 mobile 端已有子表的新增字段。
* 字段路径: mobile -> widgetList -> [widgetType=102] -> childWidgetList -> childWidgetList -> bindData -> columnName
*/
private boolean syncMobileSubTableNewFields(JSONObject widgetJson, OnlineDatasource datasource, Set<Long> existingSubTableIds) {
List<OnlineTable> subTables = onlineTableService.listByIds(existingSubTableIds);
if (CollUtil.isEmpty(subTables)) return false;
Map<Long, String> tableIdToName = subTables.stream().collect(Collectors.toMap(OnlineTable::getTableId, OnlineTable::getTableName));
List<String> tableNames = subTables.stream().map(OnlineTable::getTableName).collect(Collectors.toList());
List<OnlFormHead> subHeads = onlFormHeadService.list(new LambdaQueryWrapper<OnlFormHead>().in(OnlFormHead::getTableName, tableNames));
Map<String, OnlFormHead> tableNameToHead = subHeads.stream().collect(Collectors.toMap(OnlFormHead::getTableName, Function.identity()));
OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId());
JSONObject mobile = widgetJson.getJSONObject("mobile");
if (mobile == null) return false;
JSONArray mobileWidgetList = mobile.getJSONArray("widgetList");
if (mobileWidgetList == null) return false;
boolean[] changed = {false};
walkMobileSubTableWidgets(mobileWidgetList, widget -> {
if (syncMobileSubTableNewFieldForWidget(widget, tableIdToName, tableNameToHead, dblink)) changed[0] = true;
});
return changed[0];
}
/**
* 处理单个已有子表(widgetType=102)下的新增字段,返回是否有变更。
*/
private boolean syncMobileSubTableNewFieldForWidget(JSONObject widget, Map<Long, String> tableIdToName, Map<String, OnlFormHead> tableNameToHead, OnlineDblink dblink) {
JSONObject bindData = widget.getJSONObject("bindData");
if (bindData == null) return false;
String tableIdStr = bindData.getString("tableId");
if (StrUtil.isEmpty(tableIdStr)) return false;
Long tableId = Long.valueOf(tableIdStr);
String tableName = tableIdToName.get(tableId);
if (tableName == null) return false;
OnlFormHead subHead = tableNameToHead.get(tableName);
if (subHead == null) return false;
// 搜集该子表下无 columnId 的字段
List<JSONObject> newFieldWidgets = new ArrayList<>();
JSONArray cardList = widget.getJSONArray("childWidgetList");
if (cardList != null) {
for (int j = 0; j < cardList.size(); j++) {
JSONObject card = cardList.getJSONObject(j);
JSONArray fieldList = card.getJSONArray("childWidgetList");
if (fieldList == null) continue;
for (int k = 0; k < fieldList.size(); k++) {
JSONObject field = fieldList.getJSONObject(k);
JSONObject fieldBd = field.getJSONObject("bindData");
if (fieldBd != null && fieldBd.containsKey("columnName") && !fieldBd.containsKey("columnId")) {
newFieldWidgets.add(field);
}
}
}
}
if (newFieldWidgets.isEmpty()) return false;
// 创建字段记录
for (JSONObject fieldWidget : newFieldWidgets) {
JSONObject fieldBd = fieldWidget.getJSONObject("bindData");
String columnName = fieldBd.getString("columnName");
String showName = fieldWidget.getString("showName");
this.saveField(fieldWidget.getInteger("widgetType"), fieldBd.getString("columnComment"), showName, subHead, columnName, null);
// 小程序端新增字段:已有 variableName 标签则转驼峰反写字段名,没有则不动
if (fieldWidget.containsKey("variableName")) {
fieldWidget.put("variableName", StrUtil.toCamelCase(columnName));
}
}
// ALTER TABLE ADD COLUMN
onlFormHeadService.syncDB(subHead, 0L);
// 批量获取列信息
List<SqlTableColumn> allColumns = onlineDblinkService.getDblinkTableColumnList(dblink, tableName);
Map<String, SqlTableColumn> columnMap = new HashMap<>();
if (CollUtil.isNotEmpty(allColumns)) {
for (SqlTableColumn col : allColumns) {
if (col.getColumnName() != null) columnMap.put(col.getColumnName().toLowerCase(), col);
}
}
// 创建 OnlineColumn + 回写
for (JSONObject fieldWidget : newFieldWidgets) {
JSONObject fieldBd = fieldWidget.getJSONObject("bindData");
String columnName = fieldBd.getString("columnName");
SqlTableColumn sqlCol = columnMap.get(columnName.toLowerCase());
if (sqlCol != null) {
String showName = fieldWidget.getString("showName");
long columnId = onlineColumnService.saveBySqlTable(sqlCol, tableId, showName);
fieldBd.put("columnId", columnId);
fieldBd.put("tableId", tableId);
fieldBd.put("columnFieldName", columnName);
}
}
// 删除缓存
redissonClient.getBucket(OnlineRedisKeyUtil.makeOnlineTableKey(tableId)).delete();
redissonClient.getBucket(CacheKey.makeTableKey(tableName)).delete();
redissonClient.getBucket(CacheKey.makeChildFieldListKey(subHead.getId())).delete();
metaCacheService.removeData(tableName);
return true;
}
/**
* 查询指定表名的列表表单
*
* @param tableName 表名
* @return 表单信息
*/
@PostMapping("/queryListFormByTableName")
public ResponseResult<List<OnlineForm>> queryListFormByTableName(@MyRequestBody String tableName) {
if (tableName == null) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
List<OnlineForm> formList = onlineFormService.queryByTableName(tableName, FormType.QUERY);
if (CollUtil.isEmpty(formList)) {
return ResponseResult.success(new ArrayList<>());
}
formList.forEach(item -> {
item.setWidgetJson(null);
item.setParamsJson(null);
item.setCreateTime(null);
item.setUpdateTime(null);
item.setCreateUserId(null);
item.setUpdateUserId(null);
});
return ResponseResult.success(formList);
}
}

103
common/common-online/src/main/java/apelet/common/online/controller/OnlineOperationController.java

@ -1,54 +1,60 @@ @@ -1,54 +1,60 @@
package apelet.common.online.controller;
import apelet.common.core.upload.LocalUpDownloader;
import apelet.common.eas.service.impl.EASSyncService;
import apelet.common.generator.model.OnlFormField;
import apelet.common.generator.model.OnlFormHead;
import apelet.common.generator.service.IOnlFormFieldService;
import apelet.common.generator.service.IOnlFormHeadService;
import apelet.common.generator.utils.OrmGenDataSourceUtil;
import apelet.common.online.abstractplugin.ListDataEasService;
import apelet.common.online.abstractplugin.ListDataOrmService;
import apelet.common.online.abstractplugin.ListDataService;
import apelet.common.online.abstractplugin.model.ExportData;
import apelet.common.online.abstractplugin.model.GridData;
import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import apelet.common.online.dto.OnlinePluginExecuteDto;
import apelet.common.online.model.constant.*;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import apelet.common.orm.impl.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.*;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import apelet.common.core.annotation.MyRequestBody;
import apelet.common.core.constant.DictType;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.constant.ObjectFieldType;
import apelet.common.core.exception.MyRuntimeException;
import apelet.common.core.object.*;
import apelet.common.core.upload.LocalUpDownloader;
import apelet.common.core.util.*;
import apelet.common.dict.model.GlobalDictItem;
import apelet.common.dict.model.TenantGlobalDict;
import apelet.common.dict.model.TenantGlobalDictItem;
import apelet.common.dict.service.GlobalDictService;
import apelet.common.dict.service.TenantGlobalDictService;
import apelet.common.eas.service.impl.EASSyncService;
import apelet.common.generator.model.OnlFormField;
import apelet.common.generator.model.OnlFormHead;
import apelet.common.generator.service.IOnlFormFieldService;
import apelet.common.generator.service.IOnlFormHeadService;
import apelet.common.generator.utils.OrmGenDataSourceUtil;
import apelet.common.log.annotation.OperationLog;
import apelet.common.log.model.constant.SysOperationLogType;
import apelet.common.online.abstractplugin.ListDataEasService;
import apelet.common.online.abstractplugin.ListDataOrmService;
import apelet.common.online.abstractplugin.ListDataService;
import apelet.common.online.abstractplugin.model.ExportData;
import apelet.common.online.abstractplugin.model.GridData;
import apelet.common.online.config.OnlineProperties;
import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import apelet.common.online.dto.OnlineFilterDto;
import apelet.common.online.dto.OnlinePluginExecuteDto;
import apelet.common.online.exception.OnlineRuntimeException;
import apelet.common.online.model.*;
import apelet.common.online.model.constant.EventEnum;
import apelet.common.online.model.constant.FieldFilterType;
import apelet.common.online.model.constant.FieldKind;
import apelet.common.online.model.constant.RelationType;
import apelet.common.online.service.*;
import apelet.common.online.util.OnlineConstant;
import apelet.common.online.util.OnlineOperationHelper;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import apelet.common.orm.impl.*;
import apelet.common.redis.cache.SessionCacheHelper;
import apelet.common.redis.util.CommonRedisUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -59,7 +65,8 @@ import org.springframework.web.bind.annotation.*; @@ -59,7 +65,8 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
@ -113,6 +120,9 @@ public class OnlineOperationController { @@ -113,6 +120,9 @@ public class OnlineOperationController {
@Autowired
private OnlineDblinkService onlineDblinkService;
@Autowired(required = false)
private FormButtonPermissionChecker buttonPermissionChecker;
/**
* 执行功能插件
*
@ -127,6 +137,18 @@ public class OnlineOperationController { @@ -127,6 +137,18 @@ public class OnlineOperationController {
@MyRequestBody JSONObject masterData,
@MyRequestBody JSONObject slaveData) {
// //todo 暂时不开启 按钮权限校验
// if (buttonPermissionChecker != null) {
// Long formId = onlinePluginExecuteDto.getFormId();
// String buttonName = onlinePluginExecuteDto.getButtonName();
// if (formId != null && buttonName != null) {
// if (!buttonPermissionChecker.hasPermission(formId, buttonName)) {
// return ResponseResult.error(ErrorCodeEnum.NO_OPERATION_PERMISSION,
// "当前角色无此按钮操作权限!");
// }
// }
// }
return ResponseResult.success(onlineOperationService.executePlugin(onlinePluginExecuteDto, masterData, slaveData));
}
@ -795,7 +817,8 @@ public class OnlineOperationController { @@ -795,7 +817,8 @@ public class OnlineOperationController {
@MyRequestBody MyPageParam pageParam,
@MyRequestBody String ignoreMaskFields,
@MyRequestBody TraceParamDto traceParamDto,
@MyRequestBody OnlineEventPluginExecuteDto onlineEventPluginExecuteDto
@MyRequestBody OnlineEventPluginExecuteDto onlineEventPluginExecuteDto,
@MyRequestBody String metaCode
) {
// 1. 验证数据源及其关联
ResponseResult<OnlineDatasource> datasourceResult =
@ -807,6 +830,16 @@ public class OnlineOperationController { @@ -807,6 +830,16 @@ public class OnlineOperationController {
if (!filterDtoListResult.isSuccess()) {
return ResponseResult.errorFrom(filterDtoListResult);
}
// 透传 metaCode 到 ListDataOrmService,用于权限规则类型过滤
if (onlineEventPluginExecuteDto == null) {
onlineEventPluginExecuteDto = new OnlineEventPluginExecuteDto();
}
Map<String, Object> listParams = onlineEventPluginExecuteDto.getListParams();
if (listParams == null) {
listParams = new HashMap<>();
onlineEventPluginExecuteDto.setListParams(listParams);
}
listParams.put("metaCode", metaCode);
OnlFormHead onlFormHead = onlFormHeadService.getFormHeadByDatasource(datasourceId);
GridData pluginData = this.exeListPlugin(formId,filterDtoList,orderParam,pageParam,
traceParamDto,onlFormHead,datasourceResult,onlineEventPluginExecuteDto
@ -840,7 +873,10 @@ public class OnlineOperationController { @@ -840,7 +873,10 @@ public class OnlineOperationController {
}
onlineEventPluginExecuteDto.setFormId(formId);
onlineEventPluginExecuteDto.setEventCode(EventEnum.LOADLISTDATA.getCode());
Map<String,Object> listParams = new HashMap<>();
Map<String,Object> listParams = onlineEventPluginExecuteDto.getListParams();
if (listParams == null) {
listParams = new HashMap<>();
}
listParams.put("filterDtoList",filterDtoList);
listParams.put("pageParam",pageParam);
listParams.put("orderParam",orderParam);
@ -873,7 +909,6 @@ public class OnlineOperationController { @@ -873,7 +909,6 @@ public class OnlineOperationController {
return gridData;
}
private void normalizeFilterList(OnlineTable table
, List<OnlineFilterDto> filterList) {
OnlineDblink dblink = onlineDblinkService.getById(table.getDblinkId());
@ -936,7 +971,7 @@ public class OnlineOperationController { @@ -936,7 +971,7 @@ public class OnlineOperationController {
HashMap<String, Object> rowData = new HashMap<>();
HashMap<String, Object> listData = new HashMap<>();
ObjectCollection collection = ormGenDataSourceUtil.query(masterTable.getTableName(), filter, selector);
if(collection.size() == 0){
if (collection.isEmpty()) {
JSONObject jsonObject = new JSONObject();
rowData.put("id", id);
jsonObject.put("rowData", rowData);
@ -956,6 +991,12 @@ public class OnlineOperationController { @@ -956,6 +991,12 @@ public class OnlineOperationController {
ObjectCollection entryCollection = (ObjectCollection) map.get(k);
List<Map<String, Object>> entryList = new ArrayList<>();
onlineOperationService.collectionToJson(entryCollection, entryList);
String sort = "seq";
entryList = entryList.stream().sorted(
Comparator.comparing(m -> m.containsKey(sort) && m.get(sort) != null && StringUtils.isNotBlank(m.get(sort).toString())
? Double.valueOf(m.get(sort).toString()) : null,
Comparator.nullsLast(Double::compareTo)))
.collect(Collectors.toList());
listData.put(String.valueOf(entryIdMap.get(k)), entryList);
} else {
rowData.put(k, map.get(k));

91
common/common-online/src/main/java/apelet/common/online/controller/OnlinePageController.java

@ -16,7 +16,10 @@ import apelet.common.generator.service.IOnlFormHeadService; @@ -16,7 +16,10 @@ import apelet.common.generator.service.IOnlFormHeadService;
import apelet.common.generator.utils.CustomUtil;
import apelet.common.log.annotation.OperationLog;
import apelet.common.log.model.constant.SysOperationLogType;
import apelet.common.online.dto.*;
import apelet.common.online.dto.OnlineDatasourceDto;
import apelet.common.online.dto.OnlineDatasourceRelationDto;
import apelet.common.online.dto.OnlinePageDatasourceDto;
import apelet.common.online.dto.OnlinePageDto;
import apelet.common.online.model.*;
import apelet.common.online.model.constant.PageStatus;
import apelet.common.online.service.*;
@ -36,6 +39,7 @@ import lombok.extern.slf4j.Slf4j; @@ -36,6 +39,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.validation.groups.Default;
@ -74,6 +78,10 @@ public class OnlinePageController { @@ -74,6 +78,10 @@ public class OnlinePageController {
@Autowired
private IOnlFormFieldService onlFormFieldService;
//new=新建元数据,exist=现有元数据
private static final String META_SOURCE_NEW = "new";
private static final String META_SOURCE_EXIST = "exist";
/**
* 新增在线表单页面数据。
*
@ -82,41 +90,82 @@ public class OnlinePageController { @@ -82,41 +90,82 @@ public class OnlinePageController {
*/
@ApiOperationSupport(ignoreParameters = {"onlinePageDto.pageId"})
@OperationLog(type = SysOperationLogType.ADD)
@Transactional(rollbackFor = Exception.class)
@PostMapping("/add")
public ResponseResult<Long> add(@MyRequestBody OnlinePageDto onlinePageDto) {
public ResponseResult<OnlinePage> add(@MyRequestBody OnlinePageDto onlinePageDto) {
String errorMessage = MyCommonUtil.getModelValidationError(onlinePageDto);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
// Validate based on metaSource
String metaSource = onlinePageDto.getMetaSource();
if (META_SOURCE_NEW.equals(metaSource)) {
// New metadata flow: metaType and pageCode are required
if (StrUtil.isEmpty(onlinePageDto.getMetaType())) {
errorMessage = "数据验证失败,新建元数据时表类型(metaType)不能为空!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
if (StrUtil.isEmpty(onlinePageDto.getPageCode())) {
errorMessage = "数据验证失败,新建元数据时页面编码不能为空!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
} else {
// Existing metadata flow: masterTableId is required
if (StrUtil.isEmpty(onlinePageDto.getMasterTableId())) {
errorMessage = "数据验证失败,表单元数据不能为空!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
}
OnlinePage onlinePage = MyModelUtil.copyTo(onlinePageDto, OnlinePage.class);
if (onlinePageService.existByPageCode(onlinePage.getPageCode())) {
errorMessage = "数据验证失败,页面编码已经存在!";
return ResponseResult.error(ErrorCodeEnum.DUPLICATED_UNIQUE_KEY, errorMessage);
}
OnlFormHead onlFormHead;
if (META_SOURCE_NEW.equals(metaSource)) {
// === New branch: create fresh metadata ===
String metaType = onlinePageDto.getMetaType();
String tableName = "t_" + onlinePageDto.getPageCode().toLowerCase();
String tableTxt = onlinePageDto.getPageName();
onlFormHead = iOnlFormHeadService.createMetaAndSync(tableName, tableTxt, metaType, null);
Long masterTableId = onlFormHead.getId();
onlinePage.setMasterTableId(String.valueOf(masterTableId));
onlinePage.setMasterTableName(onlFormHead.getTableName());
// For business documents (metaType="1"): auto-create sub-table
if (CustomUtil.MetaType_Main.equals(metaType)) {
String entryTableName = tableName + "_entry";
iOnlFormHeadService.createMetaAndSync(entryTableName, tableTxt + "附表", CustomUtil.MetaType_Entry, tableName);
}
} else {
// === Existing branch: use existing metadata ===
String formMetadata = onlinePageDto.getMasterTableId();
onlFormHead = iOnlFormHeadService.getById(formMetadata);
}
try {
onlinePage = onlinePageService.saveNew(onlinePage);
} catch (DuplicateKeyException e) {
errorMessage = "数据验证失败,当前应用的页面编码已经存在!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
//根据所传表单元数据id查出对应表单信息
String formMetadata = onlinePageDto.getMasterTableId();
//在线表单主表对象
OnlFormHead onlFormHead = iOnlFormHeadService.getById(formMetadata);
//准备创建数据模型所需的数据
// 准备创建数据模型所需的数据
OnlineDatasourceDto onlineDatasourceDto = new OnlineDatasourceDto();
//数据源标识-取表名驼峰
// 数据源标识-取表名驼峰
onlineDatasourceDto.setVariableName(CustomUtil.UnderlineToCamel(onlFormHead.getTableName()));
//数据主表标识
// 数据主表标识
onlineDatasourceDto.setMasterTableName(onlFormHead.getTableName());
//数据源名称-取表名
// 数据源名称-取表名
onlineDatasourceDto.setDatasourceName((onlFormHead.getTableName()));
//目前系统只有一个数据源,所以默认1740180645717610505
// 目前系统只有一个数据源,所以默认1740180645717610505
OnlineDblink onlineDblink = onlineDblinkService.getById(1740180645717610505L);
onlineDatasourceDto.setDblinkId(1740180645717610505L);
//创建数据模型
// 创建数据模型
SqlTable sqlTable = onlineDblinkService.getDblinkTable(onlineDblink, onlineDatasourceDto.getMasterTableName());
if (sqlTable == null) {
errorMessage = "数据验证失败,指定的数据表名不存在!";
@ -130,22 +179,20 @@ public class OnlinePageController { @@ -130,22 +179,20 @@ public class OnlinePageController {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
/**
*创建数据表关联关系 先查出改元数据表的所有附表关系
* 创建数据表关联关系 先查出改元数据表的所有附表关系
* 一对多
*
*/
addOnlineDatasourceRelation(onlFormHead, onlineDatasource, errorMessage, onlineDblink);
return ResponseResult.success(onlinePage.getPageId());
onlineDatasourceRelationService.addOnlineDatasourceRelation(onlFormHead, onlineDatasource, errorMessage, onlineDblink);
return ResponseResult.success(onlinePage);
}
/**
*创建数据表关联关系 先查出改元数据表的所有附表关系
*
* 方法迁移☞ onlineDatasourceRelationService.addOnlineDatasourceRelation
*/
@Deprecated
public ResponseResult<Long> addOnlineDatasourceRelation(OnlFormHead onlFormHead, OnlineDatasource onlineDatasource, String errorMessage, OnlineDblink onlineDblink) {
//历史数据 --做比较新数据写入,已存在的不用管,不存在的删除
OnlineDatasourceRelation onlineDatasourceRelations = new OnlineDatasourceRelation();
@ -399,7 +446,7 @@ public class OnlinePageController { @@ -399,7 +446,7 @@ public class OnlinePageController {
onlineColumnService.saveNewList(sqlTableColumnList, onlineDatasourceList.get(0).getMasterTableId());
//写表关联关系 --一对多,一对一
onlineDatasourceService.getOnlineDatasourceListByPageId(onlinePageDto.getPageId(), null, null);
addOnlineDatasourceRelation(onlFormHeadList.get(0), onlineDatasourceList.get(0), errorMessage, onlineDblink);
onlineDatasourceRelationService.addOnlineDatasourceRelation(onlFormHeadList.get(0), onlineDatasourceList.get(0), errorMessage, onlineDblink);
ArrayList<OnlineColumn> onlineColumns = new ArrayList<>();
List<OnlFormField> filedByHeadIdCache = onlFormFieldService.getFiledByHeadIdCache(onlFormHeadList.get(0).getId());

186
common/common-online/src/main/java/apelet/common/online/controller/PermissionDataRuleSchemaController.java

@ -0,0 +1,186 @@ @@ -0,0 +1,186 @@
package apelet.common.online.controller;
import apelet.common.core.annotation.MyRequestBody;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.object.CallResult;
import apelet.common.core.object.MyOrderParam;
import apelet.common.core.object.MyPageParam;
import apelet.common.core.object.ResponseResult;
import apelet.common.core.util.MyCommonUtil;
import apelet.common.core.util.MyPageUtil;
import apelet.common.core.validator.UpdateGroup;
import apelet.common.log.annotation.OperationLog;
import apelet.common.log.model.constant.SysOperationLogType;
import apelet.common.online.model.PermissionDataRuleSchema;
import apelet.common.online.service.IPermissionDataRuleSchemaService;
import com.github.pagehelper.page.PageMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.groups.Default;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* 权限数据规则方案表 控制器。
*
* @author chenchuchuan
* @date 2026-07-15
*/
@RestController
@RequestMapping("${common-online.urlPrefix}/permissionDataRuleSchema")
public class PermissionDataRuleSchemaController {
@Autowired
private IPermissionDataRuleSchemaService service;
// ========== 新增 ==========
/**
* 新增数据规则方案。
*
* @param schema 新增的规则方案对象。
* @return 新增后的规则方案对象。
*/
@OperationLog(type = SysOperationLogType.ADD)
@PostMapping("/add")
public ResponseResult<?> add(@RequestBody PermissionDataRuleSchema schema) {
schema.setCreateTime(new Date());
// 1. 基础字段校验(@NotBlank / @NotNull)
String errorMessage = MyCommonUtil.getModelValidationError(schema);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
// 2. 业务校验:formId → 主表/单表,并填充 tableType
CallResult result = service.verifyRelatedData(schema);
if (!result.isSuccess()) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, result.getErrorMessage());
}
// 3. 保存
service.saveNew(schema);
return ResponseResult.success(schema);
}
// ========== 编辑(含启用/禁用) ==========
/**
* 更新数据规则方案(含启用/禁用)。
*
* @param schema 更新的规则方案对象。
* @return 应答结果。
*/
@OperationLog(type = SysOperationLogType.UPDATE)
@PostMapping("/update")
public ResponseResult<?> update(@RequestBody PermissionDataRuleSchema schema) {
// 1. 基础校验(需校验 id 非空 + 其他必填字段)
String errorMessage = MyCommonUtil.getModelValidationError(schema, Default.class, UpdateGroup.class);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
// 2. 检查数据是否存在
PermissionDataRuleSchema original = service.getById(schema.getId());
if (original == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST,
"数据验证失败,当前规则方案不存在,请刷新后重试!");
}
// 3. 业务校验:formId → 主表/单表,填充 tableType
CallResult result = service.verifyRelatedData(schema);
if (!result.isSuccess()) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, result.getErrorMessage());
}
// 4. 更新
schema.setUpdateTime(new Date());
if (!service.updateById(schema)) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
return ResponseResult.success();
}
// ========== 删除(启用状态禁止删) ==========
/**
* 删除数据规则方案(已启用的规则不允许删除)。
*
* @param id 主键ID。
* @return 应答结果。
*/
@OperationLog(type = SysOperationLogType.DELETE)
@PostMapping("/delete")
public ResponseResult<?> delete(@MyRequestBody Long id) {
if (MyCommonUtil.existBlankArgument(id)) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
PermissionDataRuleSchema schema = service.getById(id);
if (schema == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
// 启用状态不允许删除
if (Objects.equals(schema.getStatus(), 1)) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED,
"该规则方案已启用,请先禁用后再删除!");
}
if (!service.removeById(id)) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
return ResponseResult.success();
}
// ========== 分页列表 ==========
/**
* 分页查询数据规则方案列表。
*
* @param filter 过滤条件(支持 ruleCode/ruleName/status)。
* @param orderParam 排序参数。
* @param pageParam 分页参数。
* @return 分页结果。
*/
@PostMapping("/list")
public ResponseResult<?> list(
@MyRequestBody PermissionDataRuleSchema filter,
@MyRequestBody MyOrderParam orderParam,
@MyRequestBody MyPageParam pageParam) {
if (pageParam != null) {
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
}
if (filter == null){
filter = new PermissionDataRuleSchema();
}
List<PermissionDataRuleSchema> list = service.getListByFilter(filter, MyOrderParam.buildOrderBy(orderParam, PermissionDataRuleSchema.class));
return ResponseResult.success(MyPageUtil.makeResponseData(list, PermissionDataRuleSchema.INSTANCE));
}
// ========== 根据 formId 查询启用的规则(运行时用) ==========
/**
* 根据 formId 查询启用状态的数据规则方案(运行时用)。
*
* @param formId 在线表ID。
* @return 启用状态的规则方案列表。
*/
@GetMapping("/listByFormId")
public ResponseResult<List<PermissionDataRuleSchema>> listByFormId(@RequestParam Long formId) {
if (MyCommonUtil.existBlankArgument(formId)) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
List<PermissionDataRuleSchema> list = service.listByFormId(formId);
return ResponseResult.success(list);
}
// ========== 查看详情 ==========
/**
* 查看数据规则方案详情。
*
* @param id 主键ID。
* @return 规则方案详情。
*/
@GetMapping("/view")
public ResponseResult<PermissionDataRuleSchema> view(@RequestParam Long id) {
if (MyCommonUtil.existBlankArgument(id)) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
PermissionDataRuleSchema schema = service.getById(id);
if (schema == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
// 根据配置的多个 formId 查询 zz_online_form,返回 formName
service.fillFormNameList(schema);
return ResponseResult.success(schema);
}
}

119
common/common-online/src/main/java/apelet/common/online/controller/RoleFormButtonController.java

@ -0,0 +1,119 @@ @@ -0,0 +1,119 @@
package apelet.common.online.controller;
import apelet.common.core.annotation.MyRequestBody;
import apelet.common.core.annotation.NoAuthInterface;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.object.*;
import apelet.common.core.util.MyCommonUtil;
import apelet.common.core.util.MyPageUtil;
import apelet.common.log.annotation.OperationLog;
import apelet.common.log.model.constant.SysOperationLogType;
import apelet.common.online.dto.RoleFormButtonDto;
import apelet.common.online.model.RoleFormButton;
import apelet.common.online.service.IRoleFormButtonService;
import com.github.pagehelper.page.PageMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 角色-表单按钮权限控制器。
*
* @author chenchuchuan
* @date 2026-07-17
*/
@RestController
@RequestMapping("${common-online.urlPrefix}/roleFormButton")
public class RoleFormButtonController {
@Autowired
private IRoleFormButtonService service;
/**
* 查询当前登录用户对指定表单的按钮权限(合并所有角色)。
* 根据当前登录用户的所有角色,查询各角色在该表单的按钮配置,取并集返回。
*
* @param formId 在线表单ID(必填)。
* @return 包含 formId, buttonList[{name, checked}]。
*/
@GetMapping("/getButtonConfig")
@NoAuthInterface
public ResponseResult<Map<String, Set<String>>> getButtonConfig(@RequestParam(required = false) Long formId) {
return ResponseResult.success(service.getButtonConfigVo(formId));
}
/**
* 批量保存角色+表单的按钮权限配置(新增或更新)。
*
* @param dtoList 列表,每个元素包含 roleId(必填), formId(必填), id(编辑时传), buttonList。
* @return 应答结果。
*/
@OperationLog(type = SysOperationLogType.ADD)
@PostMapping("/saveButtonConfig")
public ResponseResult<Void> saveButtonConfig(
@MyRequestBody List<RoleFormButtonDto> dtoList) {
if (dtoList == null || dtoList.isEmpty()) {
return ResponseResult.error(
ErrorCodeEnum.ARGUMENT_NULL_EXIST, "数据验证失败,按钮配置列表不能为空!");
}
CallResult result = service.batchSaveButtonConfig(dtoList);
if (!result.isSuccess()) {
return ResponseResult.error(
ErrorCodeEnum.DATA_VALIDATED_FAILED, result.getErrorMessage());
}
return ResponseResult.success();
}
/**
* 根据角色ID查询该角色下所有绑定的按钮权限配置列表。
*
* @param roleId 角色ID。
* @return 按钮权限配置列表。
*/
@GetMapping("/listByRoleId")
public ResponseResult<List<RoleFormButton>> listByRoleId(
@RequestParam Long roleId) {
if (MyCommonUtil.existBlankArgument(roleId)) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
return ResponseResult.success(service.listByRoleId(roleId));
}
/**
* 分页查询所有在线表单下的所有按钮。
* 遍历全部在线表单,从每个表单的 widgetJson 中提取已开启的按钮。
* 如果传了 roleId,则只返回该角色已配置的表单,并合并已保存的勾选状态。
*
* @param roleId 角色ID(可选,用于过滤已配置的表单)。
* @param orderParam 排序参数。
* @param pageParam 分页参数。
* @return 分页结果,每个元素包含 formId, formName, buttonList[{name, checked}]。
*/
@PostMapping("/listAllFormButtons")
public ResponseResult<MyPageData<Map<String, Object>>> listAllFormButtons(
@MyRequestBody Long roleId,
@MyRequestBody MyOrderParam orderParam,
@MyRequestBody MyPageParam pageParam) {
return ResponseResult.success(service.listAllFormButtons(roleId, orderParam, pageParam));
}
/**
* 分页查询按钮权限配置列表。
*/
@PostMapping("/list")
public ResponseResult<MyPageData<?>> list(
@MyRequestBody RoleFormButton filter,
@MyRequestBody MyOrderParam orderParam,
@MyRequestBody MyPageParam pageParam) {
if (pageParam != null) {
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
}
List<RoleFormButton> list = service.getListByFilter(
filter, MyOrderParam.buildOrderBy(orderParam, RoleFormButton.class));
return ResponseResult.success(
MyPageUtil.makeResponseData(list, RoleFormButton.INSTANCE));
}
}

8
common/common-online/src/main/java/apelet/common/online/dao/OnlineDictMapper.java

@ -23,4 +23,12 @@ public interface OnlineDictMapper extends BaseDaoMapper<OnlineDict> { @@ -23,4 +23,12 @@ public interface OnlineDictMapper extends BaseDaoMapper<OnlineDict> {
*/
List<OnlineDict> getOnlineDictList(
@Param("onlineDictFilter") OnlineDict onlineDictFilter, @Param("orderBy") String orderBy);
/**
* 根据字典名称获取字典信息
*
* @param dictName
* @return
*/
List<OnlineDict> queryByDictName(@Param("dictName") String dictName);
}

27
common/common-online/src/main/java/apelet/common/online/dao/PermissionDataRuleSchemaMapper.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
package apelet.common.online.dao;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.online.model.PermissionDataRuleSchema;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 权限数据规则方案表 Mapper 接口。
*
* @author chenchuchuan
* @date 2026-07-15
*/
public interface PermissionDataRuleSchemaMapper extends BaseDaoMapper<PermissionDataRuleSchema> {
/**
* 分页条件查询(支持 rule_code/rule_name/status 过滤 + 排序)。
*
* @param filter 过滤条件。
* @param orderBy 排序语句。
* @return 规则方案列表。
*/
List<PermissionDataRuleSchema> getListByFilter(
@Param("filter") PermissionDataRuleSchema filter,
@Param("orderBy") String orderBy);
}

23
common/common-online/src/main/java/apelet/common/online/dao/RoleFormButtonMapper.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
package apelet.common.online.dao;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.online.model.RoleFormButton;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 角色-表单按钮权限配置数据访问操作接口。
*
* @author chenchuchuan
* @date 2026-07-17
*/
public interface RoleFormButtonMapper extends BaseDaoMapper<RoleFormButton> {
/**
* 分页条件查询。
*/
List<RoleFormButton> getListByFilter(
@Param("filter") RoleFormButton filter,
@Param("orderBy") String orderBy);
}

13
common/common-online/src/main/java/apelet/common/online/dao/RolePermissionSchemaMapper.java

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
package apelet.common.online.dao;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.online.model.RolePermissionSchema;
/**
* 角色-数据规则方案关联表 Mapper 接口。
*
* @author chenchuchuan
* @date 2026-08-05
*/
public interface RolePermissionSchemaMapper extends BaseDaoMapper<RolePermissionSchema> {
}

4
common/common-online/src/main/java/apelet/common/online/dao/mapper/OnlineDictMapper.xml

@ -62,4 +62,8 @@ @@ -62,4 +62,8 @@
ORDER BY ${orderBy}
</if>
</select>
<select id="queryByDictName" resultMap="BaseResultMap" parameterType="apelet.common.online.model.OnlineDict">
SELECT * FROM zz_online_dict where dict_name=#{dictName}
</select>
</mapper>

14
common/common-online/src/main/java/apelet/common/online/dao/mapper/OnlineFormMapper.xml

@ -47,7 +47,7 @@ @@ -47,7 +47,7 @@
AND zz_online_form.form_code = #{onlineFormFilter.formCode}
</if>
<if test="onlineFormFilter.formName != null and onlineFormFilter.formName != ''">
<bind name= "safeFormName" value= "'%' + onlineFormFilter.formName + '%'"/>
<bind name="safeFormName" value="'%' + onlineFormFilter.formName + '%'"/>
AND zz_online_form.form_name LIKE #{safeFormName}
</if>
<if test="onlineFormFilter.formType != null">
@ -56,6 +56,18 @@ @@ -56,6 +56,18 @@
<if test="onlineFormFilter.masterTableId != null">
AND zz_online_form.master_table_id = #{onlineFormFilter.masterTableId}
</if>
<if test="onlineFormFilter.pageIdSet != null">
AND zz_online_form.page_id IN
<foreach collection="onlineFormFilter.pageIdSet" item="item" separator="," open="(" close=")">
#{item}
</foreach>
</if>
<if test="onlineFormFilter.formIdSet != null">
AND zz_online_form.form_id IN
<foreach collection="onlineFormFilter.formIdSet" item="item" separator="," open="(" close=")">
#{item}
</foreach>
</if>
</if>
</sql>

27
common/common-online/src/main/java/apelet/common/online/dao/mapper/PermissionDataRuleSchemaMapper.xml

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="apelet.common.online.dao.PermissionDataRuleSchemaMapper">
<select id="getListByFilter"
resultType="apelet.common.online.model.PermissionDataRuleSchema">
SELECT * FROM zz_permission_data_rule_schema
WHERE 1=1
<if test="filter.ruleCode != null and filter.ruleCode != ''">
AND rule_code LIKE CONCAT('%', #{filter.ruleCode}, '%')
</if>
<if test="filter.ruleName != null and filter.ruleName != ''">
AND rule_name LIKE CONCAT('%', #{filter.ruleName}, '%')
</if>
<if test="filter.status != null">
AND status = #{filter.status}
</if>
<if test="filter.ruleType != null">
AND rule_type = #{filter.ruleType}
</if>
<if test="orderBy != null and orderBy != ''">
ORDER BY ${orderBy}
</if>
</select>
</mapper>

21
common/common-online/src/main/java/apelet/common/online/dao/mapper/RoleFormButtonMapper.xml

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="apelet.common.online.dao.RoleFormButtonMapper">
<select id="getListByFilter"
resultType="apelet.common.online.model.RoleFormButton">
SELECT * FROM xy_sys_role_form_button
WHERE 1=1
<if test="filter.roleId != null">
AND role_id = #{filter.roleId}
</if>
<if test="filter.formId != null">
AND form_id = #{filter.formId}
</if>
<if test="orderBy != null and orderBy != ''">
ORDER BY ${orderBy}
</if>
</select>
</mapper>

7
common/common-online/src/main/java/apelet/common/online/dto/OnlineEventPluginExecuteDto.java

@ -7,6 +7,7 @@ import lombok.Data; @@ -7,6 +7,7 @@ import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
/**
@ -61,4 +62,10 @@ public class OnlineEventPluginExecuteDto { @@ -61,4 +62,10 @@ public class OnlineEventPluginExecuteDto {
@Schema(description = "嵌入在线表单,父子页面参数传递")
private Map parentParams;
@Schema(description="页面跳转参数传递")
private List<Map<String, Object>> jumpParams;
@Schema(description="编辑字段")
private String tableField;
}

14
common/common-online/src/main/java/apelet/common/online/dto/OnlinePageDto.java

@ -60,13 +60,23 @@ public class OnlinePageDto { @@ -60,13 +60,23 @@ public class OnlinePageDto {
* 表单元数据。
*/
@Schema(description = "表单元数据")
@NotNull(message = "数据验证失败,表单元数据不能为空!")
private String masterTableId;
/**
* 表单元数据。
*/
@Schema(description = "表单元数据表名")
// @NotNull(message = "数据验证失败,表单元数据不能为空!")
private String masterTableName;
/**
* 数据来源: "new"=新建元数据, "existing"=使用已有元数据。
*/
@Schema(description = "数据来源: new=新建元数据, existing=使用已有元数据")
private String metaSource;
/**
* 表类型: "2"=基础资料(单表), "1"=业务单据(主表+附表)。仅在metaSource="new"时有效。
*/
@Schema(description = "表类型: 2=基础资料(单表), 1=业务单据(主表+附表)。仅在metaSource=new时有效")
private String metaType;
}

35
common/common-online/src/main/java/apelet/common/online/dto/RoleFormButtonDto.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
package apelet.common.online.dto;
import lombok.Data;
import java.util.List;
/**
* 角色表单按钮权限保存入参。
*
* @author chenchuchuan
* @date 2026-07-17
*/
@Data
public class RoleFormButtonDto {
/** 角色ID(必填) */
private Long roleId;
/** 表单ID(必填) */
private Long formId;
/** 主键ID(编辑时传,新增时不传) */
private Long id;
/** 按钮勾选列表 */
private List<ButtonItem> buttonList;
@Data
public static class ButtonItem {
/** 按钮名称 */
private String name;
/** 是否勾选 */
private Boolean checked;
}
}

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

@ -16,6 +16,7 @@ import org.mapstruct.factory.Mappers; @@ -16,6 +16,7 @@ import org.mapstruct.factory.Mappers;
import java.util.Date;
import java.util.Map;
import java.util.Set;
/**
* 在线表单实体对象。
@ -71,6 +72,7 @@ public class OnlineForm { @@ -71,6 +72,7 @@ public class OnlineForm {
/**
* 表单类型。
* @see FormType
*/
@TableField(value = "form_type")
private Integer formType;
@ -139,6 +141,18 @@ public class OnlineForm { @@ -139,6 +141,18 @@ public class OnlineForm {
@TableField(exist = false)
private Map<String, Object> formTypeDictMap;
/**
* 页面id集合。
*/
@TableField(exist = false)
private Set<Long> pageIdSet;
/**
* 表单id集合。
*/
@TableField(exist = false)
private Set<Long> formIdSet;
@Mapper
public interface OnlineFormModelMapper extends BaseModelMapper<OnlineFormVo, OnlineForm> {
/**

152
common/common-online/src/main/java/apelet/common/online/model/PermissionDataRuleSchema.java

@ -0,0 +1,152 @@ @@ -0,0 +1,152 @@
package apelet.common.online.model;
import apelet.common.core.base.mapper.BaseModelMapper;
import apelet.common.core.base.model.BaseModel;
import apelet.common.core.validator.UpdateGroup;
import apelet.common.online.vo.PermissionDataRuleSchemaVo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 权限数据规则方案表 实体对象。
*
* @author chenchuchuan
* @date 2026-07-15
*/
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("zz_permission_data_rule_schema")
public class PermissionDataRuleSchema extends BaseModel implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID。
*/
@TableId(value = "id")
@NotNull(message = "数据验证失败,id不能为空!", groups = {UpdateGroup.class})
private Long id;
/**
* onl_form_head 表 Id(数据规则所属主表)。
*/
@TableField(value = "form_head_id")
@NotNull(message = "数据验证失败,form_head_id不能为空!")
private Long formHeadId;
/**
* 在线表单id集合(zz_online_form.form_id),多个逗号分隔,为空表示对绑定的全部在线表单生效。
*/
@TableField(value = "form_id_list")
private String formIdList;
/**
* 表单元数据编码。
*/
@TableField(value = "meta_code")
// @NotBlank(message = "数据验证失败,表单元数据编码不能为空!")
private String metaCode;
/**
* 主表类型:1-主表,2-单表(由后端自动填充,前端无需传入)。
*/
@TableField(value = "table_type")
private Integer tableType;
/**
* 规则编码。
*/
@TableField(value = "rule_code")
@NotBlank(message = "数据验证失败,规则编码不能为空!")
private String ruleCode;
/**
* 规则方案名称。
*/
@TableField(value = "rule_name")
@NotBlank(message = "数据验证失败,规则方案名称不能为空!")
private String ruleName;
/**
* 条件表达式(JSON)。
*/
@TableField(value = "condition_expression")
@NotBlank(message = "数据验证失败,条件表达式不能为空!")
private String conditionExpression;
/**
* 元数据展示名称
*/
@TableField(value = "meta_show_name")
private String metaShowName;
/**
* 规则类型:1-列表权限,2-基础数据权限。
* @see apelet.common.online.model.constant.DataRuleType
*/
@TableField(value = "rule_type")
private Integer ruleType;
/**
* 状态:1-启用,0-禁用。
*/
@TableField(value = "status")
private Integer status;
/**
* 备注。
*/
@TableField(value = "remark")
private String remark;
/**
* 创建时间。
*/
@TableField(value = "create_time")
private Date createTime;
/**
* 创建者。
*/
@TableField(value = "create_user_id")
private Long createUserId;
/**
* 更新时间。
*/
@TableField(value = "update_time")
private Date updateTime;
/**
* 更新者。
*/
@TableField(value = "update_user_id")
private Long updateUserId;
/**
* 绑定的在线表单列表(formId + formName),详情接口返回用,非持久化。
*/
@TableField(exist = false)
private List<Map<String, Object>> formList;
// ========== MapStruct ==========
@Mapper
public interface PermissionDataRuleSchemaModelMapper
extends BaseModelMapper<PermissionDataRuleSchemaVo, PermissionDataRuleSchema> {
}
public static final PermissionDataRuleSchemaModelMapper INSTANCE =
Mappers.getMapper(PermissionDataRuleSchemaModelMapper.class);
}

69
common/common-online/src/main/java/apelet/common/online/model/RoleFormButton.java

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
package apelet.common.online.model;
import apelet.common.core.base.mapper.BaseModelMapper;
import apelet.common.core.base.model.BaseModel;
import apelet.common.core.validator.UpdateGroup;
import apelet.common.online.vo.RoleFormButtonVo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
/**
* 角色-表单按钮权限配置实体对象。
*
* @author chenchuchuan
* @date 2026-07-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("xy_sys_role_form_button")
public class RoleFormButton extends BaseModel implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id")
@NotNull(message = "数据验证失败,id不能为空!", groups = {UpdateGroup.class})
private Long id;
/** 角色ID */
@TableField(value = "role_id")
@NotNull(message = "数据验证失败,角色ID不能为空!")
private Long roleId;
/** 在线表单ID */
@TableField(value = "form_id")
@NotNull(message = "数据验证失败,表单ID不能为空!")
private Long formId;
/** 按钮配置JSON */
@TableField(value = "button_config")
private String buttonConfig;
@TableField(value = "create_time")
private Date createTime;
@TableField(value = "create_user_id")
private Long createUserId;
@TableField(value = "update_time")
private Date updateTime;
@TableField(value = "update_user_id")
private Long updateUserId;
@Mapper
public interface RoleFormButtonModelMapper
extends BaseModelMapper<RoleFormButtonVo, RoleFormButton> {
}
public static final RoleFormButtonModelMapper INSTANCE =
Mappers.getMapper(RoleFormButtonModelMapper.class);
}

28
common/common-online/src/main/java/apelet/common/online/model/RolePermissionSchema.java

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
package apelet.common.online.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 角色-数据规则方案关联实体对象(role_id + schema_id 联合主键)。
*
* @author chenchuchuan
* @date 2026-08-05
*/
@Data
@TableName(value = "zz_role_permission_schema")
public class RolePermissionSchema {
/**
* 角色ID。
*/
@TableField(value = "role_id")
private Long roleId;
/**
* 规则方案ID(关联 zz_permission_data_rule_schema.id)。
*/
@TableField(value = "schema_id")
private Long schemaId;
}

1
common/common-online/src/main/java/apelet/common/online/model/constant/AttributeEnum.java

@ -57,6 +57,7 @@ public enum AttributeEnum { @@ -57,6 +57,7 @@ public enum AttributeEnum {
BACK_PAGE("backPage", "返回上一页"),
OPEN_URL_PAGE("openUrlPage", "打开前端页面"),
LISTEN_TIMER_ELAPSED("listentimerElapsed", "前端定时发送请求,触发此界面事件。"),
RESETROWS("resetrows", "重置行数据"),
;

35
common/common-online/src/main/java/apelet/common/online/model/constant/DataRuleType.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
package apelet.common.online.model.constant;
import java.util.HashMap;
import java.util.Map;
/**
* 数据权限规则类型常量。
*
* @author chenchuchuan
* @date 2026-07-21
*/
public final class DataRuleType {
/**
* 列表权限。
*/
public static final int LIST = 1;
/**
* 基础数据权限。
*/
public static final int BASIC_DATA = 2;
private static final Map<Object, String> DICT_MAP = new HashMap<>(2);
static {
DICT_MAP.put(LIST, "列表权限");
DICT_MAP.put(BASIC_DATA, "基础数据权限");
}
public static boolean isValid(Integer value) {
return value != null && DICT_MAP.containsKey(value);
}
private DataRuleType() {
}
}

1
common/common-online/src/main/java/apelet/common/online/model/constant/EventEnum.java

@ -42,6 +42,7 @@ public enum EventEnum { @@ -42,6 +42,7 @@ public enum EventEnum {
MENU_CLICK("menuClick", "菜单点击事件"),
OPERATION_AFTER("operationAfter", "保存后"),
TIMER_ELAPSED("timerElapsed", "前端定时发送请求,触发此界面事件。"),
SCRIPT_EVENT("scriptEvent", "脚本事件"),
;

1
common/common-online/src/main/java/apelet/common/online/model/constant/ShowTypeEnum.java

@ -13,6 +13,7 @@ import java.util.stream.Collectors; @@ -13,6 +13,7 @@ import java.util.stream.Collectors;
public enum ShowTypeEnum {
OPEN_ONLINE_MODAL("openOnlineModal", "弹窗"),
RESET_ONLINE_PAGE("resetOnlinePage", "重置跳转页面"),
OPEN_ONLINE_PAGE("openOnlinePage", "跳转页面");
@Getter

143
common/common-online/src/main/java/apelet/common/online/rule/FormRuleEngine.java

@ -0,0 +1,143 @@ @@ -0,0 +1,143 @@
package apelet.common.online.rule;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.dto.OnlineEventPluginExecuteDto;
import apelet.common.online.model.OnlineForm;
import apelet.common.online.rule.action.RuleActionExecutor;
import apelet.common.online.rule.condition.ConditionEvaluator;
import apelet.common.online.rule.enums.RuleTypeEnum;
import apelet.common.online.rule.model.FormRule;
import apelet.common.online.rule.model.RuleAction;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.rule.model.RuleEvalContext;
import apelet.common.online.rule.parser.FormRuleParser;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* 规则引擎入口:解析 → 规则级一次求值 → 按 condition 选动作 → 事件匹配 → 路由执行器。
*/
@Component
public class FormRuleEngine {
@Resource
private FormRuleParser parser;
@Resource
private ConditionEvaluator conditionEvaluator;
@Resource
private RuleActionExecutorRegistry registry;
public List<Object> execute(OnlineForm form, ObjectValue ov, String eventCode, OnlineEventPluginExecuteDto dto) {
List<Object> result = new ArrayList<>();
if (form == null || form.getWidgetJson() == null) {
return result;
}
for (FormRule rule : parser.parse(form.getWidgetJson())) {
executeRule(rule, form, ov, eventCode, dto, result);
}
return result;
}
/**
* 通用动作执行入口:给定单条规则的 JSON 串、字段值、规则来源类型,
* 走 解析 → 条件求值 → 按 condition 选动作 → 路由执行器 → 产出 Vo 全流程。
* 通用入口不按事件过滤(eventCode 为 null,checkList 只作展示,不拦截执行)。
*
* @param ruleJson 单条规则 JSON 串(businessRulesList / conditionTypeList 数组元素:expressionTranslation、allList、Enable 等)
* @param values 字段取值(key 为字段名或完整 表.表.字段 / 表.关联对象.字段)
* @param ruleType 规则来源类型(界面规则 / 业务规则)
*/
public List<Object> executeAction(String ruleJson, Map<String, Object> values, RuleTypeEnum ruleType) {
if (StringUtils.isBlank(ruleJson)) {
return null;
}
FormRule rule = parser.parseSingle(JSONObject.parseObject(ruleJson), ruleType);
if (rule == null) {
return null;
}
return executeAction(rule, buildObjectValue(rule.getCondition().getExpression(), values));
}
public List<Object> executeAction(String ruleJson, ObjectValue values, RuleTypeEnum ruleType) {
if (StringUtils.isBlank(ruleJson)) {
return null;
}
FormRule rule = parser.parseSingle(JSONObject.parseObject(ruleJson), ruleType);
if (rule == null) {
return null;
}
return executeAction(rule, values);
}
public List<Object> executeAction(FormRule rule, ObjectValue values) {
if (rule == null) {
return null;
}
List<Object> result = new ArrayList<>();
executeRule(rule, null, values, null, new OnlineEventPluginExecuteDto(), result);
return result;
}
private void executeRule(FormRule rule, OnlineForm form, ObjectValue ov, String eventCode,
OnlineEventPluginExecuteDto dto, List<Object> result) {
if (!rule.isEnable()) {
return;
}
// 规则级:表达式只求值一次
boolean establish = conditionEvaluator.evaluate(rule.getCondition(), new RuleEvalContext(ov, dto));
// 规则内动作按 sort 升序执行(稳定排序,缺失/相同的保持数组顺序)
rule.getActions().sort(Comparator.comparingInt(RuleAction::getSort));
for (RuleAction action : rule.getActions()) {
// 成立 → condition=1;不成立 → condition=2
if ((establish && action.getCondition() != 1) || (!establish && action.getCondition() != 2)) {
continue;
}
// executeAction 通用入口 eventCode 为 null:不按事件过滤,全部执行
if (eventCode != null && !action.matchEvent(eventCode)) {
continue;
}
RuleActionExecutor executor = registry.get(action.getActionType());
RuleActionContext ctx = new RuleActionContext(form, ov, dto, action, eventCode,
dto == null ? null : dto.getEventWidgetVariableName(), rule.getCondition().getFunctionForm());
Object value = executor.resolveValue(ctx);
result.addAll(executor.buildResult(ctx, value));
}
}
private ObjectValue buildObjectValue(String expression, Map<String, Object> values) {
ObjectValue objectValue = new ObjectValue(extractTableName(expression));
if (values != null) {
for (Map.Entry<String, Object> entry : values.entrySet()) {
objectValue.put(lastSegment(entry.getKey()), entry.getValue());
}
}
return objectValue;
}
/**
* 从表达式里取主表名:如 "t_flowtest001.t_flowtest001.amount = ..." → "t_flowtest001"
*/
private String extractTableName(String expressionTranslation) {
if (StringUtils.isBlank(expressionTranslation)) return "";
int eq = expressionTranslation.indexOf('=');
String tag = (eq < 0 ? expressionTranslation : expressionTranslation.substring(0, eq)).trim();
int dot = tag.indexOf('.');
return dot < 0 ? tag : tag.substring(0, dot);
}
/**
* 取 key 末段:完整 表.表.字段 或字段名 → 字段名
*/
private String lastSegment(String key) {
int dot = key.lastIndexOf('.');
return dot < 0 ? key : key.substring(dot + 1);
}
}

39
common/common-online/src/main/java/apelet/common/online/rule/RuleActionExecutorRegistry.java

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
package apelet.common.online.rule;
import apelet.common.online.rule.action.RuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 动作类型 → 执行器 路由选择器。
* 常量 map 在构造器注入时一次性初始化,运行期只读,无需反复构建。
* 加新动作类型 = 加一个执行器,Spring 注入后自动注册,无需改这里。
*/
@Slf4j
@Component
public class RuleActionExecutorRegistry {
/** 动作类型 → 执行器 常量映射,构造器初始化,运行期只读 */
private final Map<RuleActionTypeEnum, RuleActionExecutor> executors = new HashMap<>();
public RuleActionExecutorRegistry(List<RuleActionExecutor> executorList) {
for (RuleActionExecutor executor : executorList) {
executors.put(executor.getActionType(), executor);
}
log.info("规则执行器初始化完成,共注册 {} 个动作类型:{}", executors.size(), executors.keySet());
}
/** 按动作类型路由执行器 */
public RuleActionExecutor get(RuleActionTypeEnum actionType) {
RuleActionExecutor executor = executors.get(actionType);
if (executor == null) {
throw new IllegalArgumentException("未注册的动作类型: " + actionType);
}
return executor;
}
}

105
common/common-online/src/main/java/apelet/common/online/rule/action/AbstractRuleActionExecutor.java

@ -0,0 +1,105 @@ @@ -0,0 +1,105 @@
package apelet.common.online.rule.action;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.OnlineForm;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.util.WidgetJsonUtil;
import apelet.common.online.vo.OnlineEventPluginExecuteEntryVo;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 执行器基类:自带 Vo 产出助手(不复用 ExecutePluginParent,插件机制独立)。
*/
public abstract class AbstractRuleActionExecutor implements RuleActionExecutor {
/** 默认取值:整表单(type5~8 无重活,直接用默认) */
@Override
public Object resolveValue(RuleActionContext ctx) {
return ctx.getObjectValue();
}
protected OnlineEventPluginExecuteVo buildVo(String widgetVariableName, AttributeEnum type, Object value) {
OnlineEventPluginExecuteVo vo = new OnlineEventPluginExecuteVo();
vo.setWidgetVariableName(widgetVariableName);
vo.setWidgetOperateType(type.getCode());
vo.setWidgetOperateContent(value instanceof ObjectValue ? ((ObjectValue) value).getValues() : value);
return vo;
}
protected OnlineEventPluginExecuteEntryVo buildEntryVo(String name, String col, AttributeEnum type, Object value, String rowNum) {
OnlineEventPluginExecuteEntryVo vo = new OnlineEventPluginExecuteEntryVo();
vo.setWidgetVariableName(name);
vo.setEntriesTableColumnName(col);
vo.setWidgetOperateType(type.getCode());
vo.setWidgetOperateContent(value);
vo.setRowNum(rowNum);
return vo;
}
/**
* 列名 → 控件 variableName 列表:从表单 widgetJson 的 pc/mobile.widgetList 按 bindData.columnFieldName 反查。
* 未配置映射的列名原样返回自身;一个列名对应多个控件(多个控件绑定同一列)时返回多个 variableName。
*/
protected List<String> resolveWidgetVariableNames(RuleActionContext ctx, String column) {
OnlineForm form = ctx.getForm();
if (form != null && StringUtils.isNotBlank(form.getWidgetJson())) {
Map<String, List<String>> map = WidgetJsonUtil.collectColumnVariableNames(JSONObject.parseObject(form.getWidgetJson()));
List<String> names = map.get(column);
if (names != null && !names.isEmpty()) {
return names;
}
}
return Collections.singletonList(column);
}
/** treeList 控件目标 → 一组普通指令(type 6/7/8 用) */
protected List<OnlineEventPluginExecuteVo> buildFromTreeList(RuleActionContext ctx, AttributeEnum type, Object attrValue) {
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
JSONArray treeList = ctx.getAction().getSource().getJSONArray("treeList");
if (treeList == null) return out;
for (Object o : treeList) {
JSONObject json = (JSONObject) o;
out.add(buildVo(json.get("variableName").toString(), type, attrValue));
}
return out;
}
/** 分录 tabId:keyNameObj 反查(主表/已登记分录),缺失时兜底用事件触发控件名(分录内触发即分录表控件),均无则返回 null 不产出分录 Vo */
protected String resolveEntryTabId(ObjectValue objectValue, String tableName, String eventWidgetVariableName) {
JSONObject keyNameObj = (JSONObject) objectValue.get("keyNameObj");
if (keyNameObj != null) {
JSONArray tabIds = keyNameObj.getJSONArray(tableName);
if (tabIds != null && !tabIds.isEmpty()) {
return tabIds.get(0).toString();
}
}
return eventWidgetVariableName;
}
/**
* 从目标/源字段路径识别附表名:目标 主表.分录表.字段 / 分录表.字段 优先,源 主表.分录表.字段 兜底(附表内字段影响附表字段时目标随源);
* 主表目标(表.字段 / 表.表.字段)返回 null。
*/
protected String resolveEntryTable(ObjectValue objectValue, String[] targetParts, String[] sourceParts) {
if (targetParts.length >= 3 && !targetParts[0].equals(targetParts[1]) && objectValue.get(targetParts[1]) instanceof ObjectCollection) {
return targetParts[1];
}
if (targetParts.length == 2 && objectValue.get(targetParts[0]) instanceof ObjectCollection) {
return targetParts[0];
}
if (sourceParts.length >= 3 && !sourceParts[0].equals(sourceParts[1]) && objectValue.get(sourceParts[1]) instanceof ObjectCollection) {
return sourceParts[1];
}
return null;
}
}

21
common/common-online/src/main/java/apelet/common/online/rule/action/RuleActionExecutor.java

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
package apelet.common.online.rule.action;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import java.util.List;
/**
* 规则动作执行器:一个动作类型一个执行器,三方法契约。
*/
public interface RuleActionExecutor {
/** ① 路由标识:本执行器负责的动作类型,Registry 靠它路由 */
RuleActionTypeEnum getActionType();
/** ② 取值:读取执行所需的字段/数据(各动作类型差异最大) */
Object resolveValue(RuleActionContext ctx);
/** ③ 返回结果:产出前端指令 */
List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value);
}

105
common/common-online/src/main/java/apelet/common/online/rule/action/impl/BaseDataCarryExecutor.java

@ -0,0 +1,105 @@ @@ -0,0 +1,105 @@
package apelet.common.online.rule.action.impl;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.util.Jexl3Util;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
/**
* type 2 携带基础资料:主表直接带出,分录按 rowKeySubset 只带当前编辑行(行内编辑带 rowNum)。
*/
@Component
public class BaseDataCarryExecutor extends AbstractRuleActionExecutor {
@Resource
private Jexl3Util jexl3Util;
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.BASE_DATA_CARRY;
}
@Override
public Object resolveValue(RuleActionContext ctx) {
List<CarryValue> values = new ArrayList<>();
ObjectValue objectValue = ctx.getObjectValue();
JSONArray array = ctx.getAction().getSource().getJSONArray("specifyJiList");
if (array == null) {
return values;
}
String rowKeySubset = ctx.getDto().getRowKeySubset();
String rowKey = ctx.getDto().getRowKey();
Set<String> targetSet = new HashSet<>();
if (StringUtils.isNotEmpty(rowKeySubset)) {
Collections.addAll(targetSet, rowKeySubset.split(","));
}
Map rowChangeMap = (Map) objectValue.get("rowChange");
for (Object o : array) {
JSONObject o2 = (JSONObject) o;
String[] parts = o2.get("sourceFields").toString().split("\\.");
String[] targetParts = o2.get("targetFields").toString().split("\\.");
String targetField = targetParts[targetParts.length - 1];
Object first = objectValue.get(parts[1]);
if (!parts[0].equals(parts[1]) && first instanceof ObjectCollection) { // 分录:主表.分录表.关联对象.字段
Iterator iterator = ((ObjectCollection) first).iterator();
while (iterator.hasNext()) {
ObjectValue next = (ObjectValue) iterator.next();
String k = next.getString("rowkey");
if (StringUtils.isNotEmpty(rowKeySubset)) { // 行内编辑:命中 subset 的行
if (k == null || !targetSet.contains(k)) continue;
} else if (StringUtils.isNotEmpty(rowKey)) { // 分录编辑完成:命中当前行 rowKey
if (!rowKey.equals(k)) continue;
} else { // 无过滤:跳过已保存行,只处理新增行
if (k != null) continue;
}
String rowNum = rowChangeMap == null ? null : next.getString("rowkey"); // 行内编辑才带 rowNum
Object value = jexl3Util.resolveFieldChain(next, parts, 2);
next.put(targetField, value); // 写回分录行,供后续规则条件求值
values.add(new CarryValue(targetField, value, rowNum));
}
} else { // 主表:表.关联对象.子字段 / 表.表.字段
Object value = jexl3Util.resolveFieldChain(objectValue, parts, parts[0].equals(parts[1]) ? 2 : 1);
objectValue.put(targetField, value); // 写回主表,供后续规则条件求值
values.add(new CarryValue(targetField, value, null));
}
}
return values;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
for (CarryValue v : (List<CarryValue>) value) {
if (v.rowNum == null) {
for (String name : resolveWidgetVariableNames(ctx, v.targetField)) {
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, v.value));
}
} else {
out.add(buildEntryVo(ctx.getEventWidgetVariableName(), v.targetField, AttributeEnum.VALUE_CHANGE, v.value, v.rowNum));
}
}
return out;
}
private static class CarryValue {
final String targetField;
final Object value;
final String rowNum;
CarryValue(String targetField, Object value, String rowNum) {
this.targetField = targetField;
this.value = value;
this.rowNum = rowNum;
}
}
}

97
common/common-online/src/main/java/apelet/common/online/rule/action/impl/FieldAssignExecutor.java

@ -0,0 +1,97 @@ @@ -0,0 +1,97 @@
package apelet.common.online.rule.action.impl;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.util.Jexl3Util;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* type 3 设置字段值:sourceFields 灵活解析(主表 表.字段 / 表.关联对象.子字段,分录取当前编辑行);
* 目标字段归属分录表时写当前编辑行并产分录 Vo,主表目标写主表对象。
*/
@Component
public class FieldAssignExecutor extends AbstractRuleActionExecutor {
@Resource
private Jexl3Util jexl3Util;
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.FIELD_ASSIGN;
}
@Override
public Object resolveValue(RuleActionContext ctx) {
List<AssignValue> values = new ArrayList<>();
ObjectValue objectValue = ctx.getObjectValue();
JSONArray array = ctx.getAction().getSource().getJSONArray("specifyList");
if (array == null) {
return values;
}
String rowKeySubset = ctx.getDto().getRowKeySubset();
for (Object o : array) {
JSONObject o2 = (JSONObject) o;
String sourceFields = (String) o2.get("sourceFields");
String targetFields = (String) o2.get("targetFields");
String[] sourceParts = sourceFields.split("\\.");
String[] targetParts = targetFields.split("\\.");
String targetField = targetParts[targetParts.length - 1];
Object v = jexl3Util.resolveRefValue(sourceFields, objectValue, rowKeySubset);
String entryTable = resolveEntryTable(objectValue, targetParts, sourceParts);
ObjectValue row = entryTable == null ? null : jexl3Util.currentRow((ObjectCollection) objectValue.get(entryTable), rowKeySubset, ctx.getDto().getRowKey());
if (entryTable != null && row == null) {
continue; // 附表目标但表为空,跳过,避免误写主表
}
if (row != null) {
row.put(targetField, v); // 附表目标:写回当前编辑行
String tabId = resolveEntryTabId(objectValue, entryTable, ctx.getEventWidgetVariableName());
if (tabId != null) {
values.add(new AssignValue(targetField, v, tabId, row.getString("rowkey")));
}
} else {
objectValue.put(targetField, v); // 主表目标:写回表单对象,供后续规则条件求值
values.add(new AssignValue(targetField, v, null, null));
}
}
return values;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
for (AssignValue v : (List<AssignValue>) value) {
if (v.rowNum == null) {
for (String name : resolveWidgetVariableNames(ctx, v.targetField)) {
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, v.value));
}
} else {
out.add(buildEntryVo(v.tabId, v.targetField, AttributeEnum.VALUE_CHANGE, v.value, v.rowNum));
}
}
return out;
}
private static class AssignValue {
final String targetField;
final Object value;
final String tabId;
final String rowNum;
AssignValue(String targetField, Object value, String tabId, String rowNum) {
this.targetField = targetField;
this.value = value;
this.tabId = tabId;
this.rowNum = rowNum;
}
}
}

97
common/common-online/src/main/java/apelet/common/online/rule/action/impl/FieldClearExecutor.java

@ -0,0 +1,97 @@ @@ -0,0 +1,97 @@
package apelet.common.online.rule.action.impl;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.util.Jexl3Util;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* type 4 清除字段值:主表直接清,分录从主表对象取集合后逐行清(空表跳过,tabId 经 keyNameObj 反查、缺失兜底事件控件)。
*/
@Component
public class FieldClearExecutor extends AbstractRuleActionExecutor {
@Resource
private Jexl3Util jexl3Util;
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.FIELD_CLEAR;
}
@Override
public Object resolveValue(RuleActionContext ctx) {
List<ClearValue> values = new ArrayList<>();
ObjectValue objectValue = ctx.getObjectValue();
JSONArray treeList = ctx.getAction().getSource().getJSONArray("treeList");
if (treeList == null) {
return values;
}
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
if (jsonObject.containsKey("zxName")) {
String[] split = jsonObject.getString("zxName").split("\\.");
if (split[0].equals(split[1])) { // 主表字段
String label = jsonObject.get("label").toString();
objectValue.put(label, ""); // 写回清空,供后续规则条件求值
values.add(new ClearValue(label, null, null));
} else { // 分录字段:主表对象里取分录集合,清当前编辑行
String tab = jexl3Util.getMySplit("\\((.*?)\\)", split[1]);
ObjectCollection collection = (ObjectCollection) objectValue.get(tab); // 从主表对象取分录集合(新增空分录表时为空)
if (collection == null || collection.isEmpty()) {
continue; // 附表为空,无行可清
}
ObjectValue current = jexl3Util.currentRow(collection, ctx.getDto().getRowKeySubset(), ctx.getDto().getRowKey());
if (current == null) {
continue; // 无当前编辑行(表单加载等),跳过不清,避免误清其他行
}
String label = jsonObject.get("label").toString();
String tabId = resolveEntryTabId(objectValue, tab, ctx.getEventWidgetVariableName());
current.put(label, ""); // 只清当前编辑行,避免连带清空其他行
if (tabId != null) {
values.add(new ClearValue(label, tabId, current.getString("rowkey")));
}
}
}
}
return values;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
for (ClearValue v : (List<ClearValue>) value) {
if (v.rowNum == null) {
for (String name : resolveWidgetVariableNames(ctx, v.field)) {
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, ""));
}
} else {
out.add(buildEntryVo(v.tabId, v.field, AttributeEnum.VALUE_CHANGE, "", v.rowNum));
}
}
return out;
}
private static class ClearValue {
final String field;
final String tabId;
final String rowNum;
ClearValue(String field, String tabId, String rowNum) {
this.field = field;
this.tabId = tabId;
this.rowNum = rowNum;
}
}
}

162
common/common-online/src/main/java/apelet/common/online/rule/action/impl/FormulaAssignExecutor.java

@ -0,0 +1,162 @@ @@ -0,0 +1,162 @@
package apelet.common.online.rule.action.impl;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.condition.FunctionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.util.Jexl3Util;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
/**
* type 1 计算并赋值:拆 expressionTranslation,公式引用字段→算值、字面量直接赋、$fun$→函数;
* 目标字段归属分录表时写当前编辑行并产分录 Vo,主表目标写主表对象。
*/
@Component
public class FormulaAssignExecutor extends AbstractRuleActionExecutor {
@Resource
private Jexl3Util jexl3Util;
@Resource
private FunctionExecutor functionExecutor;
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.FORMULA_ASSIGN;
}
@Override
public Object resolveValue(RuleActionContext ctx) {
String expressionTranslation = ctx.getAction().getSource().getString("expressionTranslation");
if (expressionTranslation == null || !expressionTranslation.contains("=")) {
return null;
}
String[] split = expressionTranslation.split("=");
String tag = split[0].trim();
String src = split[1].trim();
ObjectValue objectValue = ctx.getObjectValue();
String targetField = lastSegment(tag);
String rowKeySubset = ctx.getDto().getRowKeySubset();
Object result;
if (src.contains("$")) { // 分支 C:$fun$ 函数赋值
String fun = jexl3Util.getMySplit("\\$(.*?)\\$", src);
result = functionExecutor.execute(ctx.getFunctionForm(), fun, objectValue);
} else if (!src.contains(objectValue.getTableName())) { // 分支 B:字面量直接赋值
result = parseLiteral(src);
} else { // 分支 A:公式引用字段,替换为实际值后求值(空值补 0)
Map<String, Object> context = new HashMap<>();
for (String ref : extractFieldRefs(src)) {
if (ref.contains(objectValue.getTableName()) && !context.containsKey(ref)) {
context.put(ref, resolveFormulaField(ref, objectValue, rowKeySubset));
}
}
result = jexl3Util.expression(src, context);
}
String[] tagParts = tag.split("\\.");
List<String> srcRefs = extractFieldRefs(src);
String[] srcParts = srcRefs.isEmpty() ? new String[0] : srcRefs.get(0).split("\\.");
String entryTable = resolveEntryTable(objectValue, tagParts, srcParts);
ObjectValue row = entryTable == null ? null : jexl3Util.currentRow((ObjectCollection) objectValue.get(entryTable), rowKeySubset, ctx.getDto().getRowKey());
if (entryTable != null && row == null) {
return null; // 附表目标但表为空,跳过,避免误写主表
}
if (row != null) {
row.put(targetField, result); // 附表目标:写回当前编辑行
String tabId = resolveEntryTabId(objectValue, entryTable, ctx.getEventWidgetVariableName());
return tabId == null ? null : new FormulaValue(targetField, result, tabId, row.getString("rowkey"));
}
objectValue.put(targetField, result); // 主表目标:写回表单对象,供后续规则条件求值
return new FormulaValue(targetField, result, null, null);
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
FormulaValue fv = (FormulaValue) value;
if (fv == null) {
return Collections.emptyList();
}
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
if (fv.rowNum == null) {
for (String name : resolveWidgetVariableNames(ctx, fv.targetField)) {
out.add(buildVo(name, AttributeEnum.VALUE_CHANGE, fv.value));
}
} else {
out.add(buildEntryVo(fv.tabId, fv.targetField, AttributeEnum.VALUE_CHANGE, fv.value, fv.rowNum));
}
return out;
}
private String lastSegment(String tag) {
String[] parts = tag.split("\\.");
return parts[parts.length - 1].trim();
}
/** 解析公式中 表.表.字段 / 表.字段 引用的实际值:主表直接取/下钻关联对象,分录取当前编辑行;空值补 0,数字字符串转数值(避免字符串相加) */
private Object resolveFormulaField(String ref, ObjectValue objectValue, String rowKeySubset) {
String[] parts = ref.split("\\.");
Object value;
Object first = objectValue.get(parts[1]);
if (!parts[0].equals(parts[1]) && first instanceof ObjectCollection) { // 分录:表.分录表.字段
ObjectValue row = jexl3Util.currentRow((ObjectCollection) first, rowKeySubset, (String) objectValue.get("rowKey"));
value = row == null ? null : row.get(parts[2]);
} else { // 主表:表.字段 / 表.表.字段 / 表.关联对象.子字段
value = jexl3Util.resolveFieldChain(objectValue, parts, parts[0].equals(parts[1]) ? 2 : 1);
}
if (value == null || value.toString().isEmpty()) {
return 0;
}
return value instanceof String ? toNumber((String) value) : value;
}
/** 解析字面量赋值:去首尾引号,'' → 空串,数字字符串转数值,其余原样返回 */
private Object parseLiteral(String src) {
String trim = src.trim();
if (trim.length() >= 2
&& ((trim.startsWith("'") && trim.endsWith("'")) || (trim.startsWith("\"") && trim.endsWith("\"")))) {
trim = trim.substring(1, trim.length() - 1);
}
return toNumber(trim);
}
/** 数字字符串转数值:正则判数字 → 整数转 Long,小数转 BigDecimal;非数字原样返回 */
private Object toNumber(String s) {
String trim = s.trim();
if (!trim.matches("-?\\d+(\\.\\d+)?")) {
return s;
}
if (trim.contains(".")) {
return new BigDecimal(trim);
}
return Long.valueOf(trim);
}
private List<String> extractFieldRefs(String src) {
List<String> refs = new ArrayList<>();
for (String token : src.split("[^a-zA-Z0-9_.]+")) {
if (!token.isEmpty() && token.split("\\.").length >= 2 && !refs.contains(token)) {
refs.add(token);
}
}
return refs;
}
private static class FormulaValue {
final String targetField;
final Object value;
final String tabId;
final String rowNum;
FormulaValue(String targetField, Object value, String tabId, String rowNum) {
this.targetField = targetField;
this.value = value;
this.tabId = tabId;
this.rowNum = rowNum;
}
}
}

27
common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetHideExecutor.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
package apelet.common.online.rule.action.impl;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* type 8 隐藏控件 → SHOW=false。
*/
@Component
public class WidgetHideExecutor extends AbstractRuleActionExecutor {
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.WIDGET_HIDE;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
return buildFromTreeList(ctx, AttributeEnum.SHOW, false);
}
}

51
common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetLockExecutor.java

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
package apelet.common.online.rule.action.impl;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* type 5 锁定控件:普通控件 → DISABLED;btnId 特例(锁父容器内按钮)→ CHANGEOPERATE 数组。
*/
@Component
public class WidgetLockExecutor extends AbstractRuleActionExecutor {
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.WIDGET_LOCK;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
List<OnlineEventPluginExecuteVo> out = new ArrayList<>();
JSONArray treeList = ctx.getAction().getSource().getJSONArray("treeList");
if (treeList == null) {
return out;
}
for (Object o : treeList) {
JSONObject jsonObject = (JSONObject) o;
String name = jsonObject.get("variableName").toString();
if (jsonObject.containsKey("btnId")) { // 特例:锁定父容器内按钮
String showName = jsonObject.get("showName").toString();
String parentVariableName = jsonObject.get("parentVariableName").toString();
JSONArray objects = new JSONArray();
JSONObject json = new JSONObject();
json.put("name", showName);
json.put("disable", true);
objects.add(json);
out.add(buildVo(parentVariableName, AttributeEnum.CHANGEOPERATE, objects));
} else { // 普通控件锁定
out.add(buildVo(name, AttributeEnum.DISABLED, true));
}
}
return out;
}
}

27
common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetShowExecutor.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
package apelet.common.online.rule.action.impl;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* type 7 显示控件 → SHOW=true。
*/
@Component
public class WidgetShowExecutor extends AbstractRuleActionExecutor {
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.WIDGET_SHOW;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
return buildFromTreeList(ctx, AttributeEnum.SHOW, true);
}
}

27
common/common-online/src/main/java/apelet/common/online/rule/action/impl/WidgetUnlockExecutor.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
package apelet.common.online.rule.action.impl;
import apelet.common.online.model.constant.AttributeEnum;
import apelet.common.online.rule.action.AbstractRuleActionExecutor;
import apelet.common.online.rule.enums.RuleActionTypeEnum;
import apelet.common.online.rule.model.RuleActionContext;
import apelet.common.online.vo.OnlineEventPluginExecuteVo;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* type 6 解锁控件 → DISABLED=false。
*/
@Component
public class WidgetUnlockExecutor extends AbstractRuleActionExecutor {
@Override
public RuleActionTypeEnum getActionType() {
return RuleActionTypeEnum.WIDGET_UNLOCK;
}
@Override
public List<OnlineEventPluginExecuteVo> buildResult(RuleActionContext ctx, Object value) {
return buildFromTreeList(ctx, AttributeEnum.DISABLED, false);
}
}

121
common/common-online/src/main/java/apelet/common/online/rule/condition/ConditionEvaluator.java

@ -0,0 +1,121 @@ @@ -0,0 +1,121 @@
package apelet.common.online.rule.condition;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.rule.model.RuleCondition;
import apelet.common.online.rule.model.RuleEvalContext;
import apelet.common.online.util.Jexl3Util;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 规则级条件求值:一条规则只求值一次,用结果选 condition=1/2 的动作。
* 加载表达式里【所有】表.表.字段 / 表.字段 token 进 context(修复:旧 getExpressionValue 只取第一个字段)。
*/
@Component
public class ConditionEvaluator {
private static final Pattern FIELD_REF = Pattern.compile("[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)+");
@Resource
private Jexl3Util jexl3Util;
@Resource
private FunctionExecutor functionExecutor;
public boolean evaluate(RuleCondition condition, RuleEvalContext ctx) {
String expression = condition.getExpression();
if (StringUtils.isEmpty(expression)) return false;
boolean result = true;
for (String expr : expression.split("&&")) {
expr = expr.trim();
if (expr.isEmpty()) continue;
if (expr.startsWith("$")) {
String fun = jexl3Util.getMySplit("\\$(.*?)\\$", expr);
Object value = functionExecutor.execute(condition.getFunctionForm(), fun, ctx.getObjectValue());
result = jexl3Util.judgmentFunc(expr, value) && result;
} else {
result = evaluateSimple(expr, ctx.getObjectValue()) && result;
}
}
return result;
}
private boolean evaluateSimple(String expr, ObjectValue objectValue) {
List<String> refs = extractFieldRefs(expr);
Map<String, Object> context = new HashMap<>();
String entryTable = null;
for (String ref : refs) {
String[] parts = ref.split("\\.");
if (parts.length < 2) continue;
// 分录:起始段在 objectValue 里是分录集合;其余都是主表字段(兼容 表.表.字段 / 表.字段 两种格式)
if (!parts[0].equals(parts[1]) && objectValue.get(parts[1]) instanceof ObjectCollection) {
if (entryTable == null) entryTable = parts[1];
} else {
context.put(ref, resolveMasterField(ref, objectValue));
}
}
if (entryTable == null) {
Object val = jexl3Util.expression(expr, context);
return val instanceof Boolean && (Boolean) val;
}
return evaluateEntryRows(expr, refs, entryTable, context, objectValue);
}
/** 分录表达式:命中当前编辑行逐行求值(rowKeySubset 行内编辑 / rowKey 分录编辑完成 / 无过滤取新增行),任一行为 true 即成立 */
private boolean evaluateEntryRows(String expr, List<String> refs, String entryTable,
Map<String, Object> baseContext, ObjectValue objectValue) {
ObjectCollection rows = (ObjectCollection) objectValue.get(entryTable);
if (rows == null || rows.isEmpty()) return false;
String rowKeySubset = (String) objectValue.get("rowKeySubset");
String rowKey = (String) objectValue.get("rowKey");
Set<String> targetSet = new HashSet<>();
if (StringUtils.isNotEmpty(rowKeySubset)) Collections.addAll(targetSet, rowKeySubset.split(","));
Iterator iterator = rows.iterator();
while (iterator.hasNext()) {
ObjectValue row = (ObjectValue) iterator.next();
if (row.getValues() == null || row.getValues().isEmpty()) continue;
String k = row.getString("rowkey");
if (StringUtils.isNotEmpty(rowKeySubset)) { // 行内编辑:命中 subset 的行
if (k == null || !targetSet.contains(k)) continue;
} else if (StringUtils.isNotEmpty(rowKey)) { // 分录编辑完成:命中当前行 rowKey
if (!rowKey.equals(k)) continue;
} else { // 无过滤:跳过已保存行,只评估新增行
if (k != null) continue;
}
Map<String, Object> context = new HashMap<>(baseContext);
for (String ref : refs) {
String[] parts = ref.split("\\.");
if (parts.length >= 2 && entryTable.equals(parts[1])) {
context.put(ref, resolveRowField(row, parts));
}
}
Object val = jexl3Util.expression(expr, context);
if (val instanceof Boolean && (Boolean) val) return true;
}
return false;
}
private Object resolveMasterField(String ref, ObjectValue objectValue) {
String[] parts = ref.split("\\.");
return jexl3Util.resolveFieldChain(objectValue, parts, parts[0].equals(parts[1]) ? 2 : 1);
}
private Object resolveRowField(ObjectValue row, String[] parts) {
return jexl3Util.resolveFieldChain(row, parts, 2);
}
private List<String> extractFieldRefs(String expr) {
List<String> refs = new ArrayList<>();
Matcher matcher = FIELD_REF.matcher(expr);
while (matcher.find()) {
String ref = matcher.group();
if (!refs.contains(ref)) refs.add(ref);
}
return refs;
}
}

67
common/common-online/src/main/java/apelet/common/online/rule/condition/FunctionExecutor.java

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
package apelet.common.online.rule.condition;
import apelet.common.core.exception.MyRuntimeException;
import apelet.common.core.object.ObjectValue;
import apelet.common.online.model.SysFunction;
import apelet.common.online.service.ISysFunctionService;
import apelet.common.online.util.Jexl3Util;
import apelet.common.online.vo.SysFunctionVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* 封装 $fun$ 动态编译函数调用(javassist),条件求值与 type1 动作赋值共用。
*/
@Component
public class FunctionExecutor {
@Resource
private ISysFunctionService sysFunctionService;
@Resource
private Jexl3Util jexl3Util;
/** 规则引擎路径:提取函数参数后执行 */
public Object execute(Map<String, Long> functionForm, String fun, ObjectValue objectValue) {
List<String> funParam = Arrays.asList(jexl3Util.getMySplit("\\((.*?)\\)", fun).split(","));
Object[] objects = jexl3Util.getFunParam(funParam, objectValue);
return executeWithParams(functionForm, fun, objects);
}
/** 核心调用:给定已提取的参数,查函数并动态编译执行(Save/Audit 插件经 OnlineFormServiceImpl.executeFunction 也走这里) */
public Object executeWithParams(Map<String, Long> functionForm, String fun, Object[] objects) {
if (fun == null) {
return null;
}
String funName = fun.substring(0, fun.indexOf('(')).trim();
SysFunction sysFunction = resolveSysFunction(functionForm, funName);
SysFunctionVo sysFunctionVo = new SysFunctionVo();
BeanUtils.copyProperties(sysFunction, sysFunctionVo);
sysFunctionVo.setParameter(objects);
return sysFunctionService.onLineFunction(sysFunctionVo);
}
/**
* 解析 $fun$ 引用的函数:functionForm 映射的 ID 优先 getById;ID 缺失/查不到时按函数名或编码(function_name/number)查询
* (兼容 functionName 字段后续改为 number 字段);名称或编码匹配到 0 个或多个视为配置错误,直接失效。
*/
private SysFunction resolveSysFunction(Map<String, Long> functionForm, String funName) {
Long funId = functionForm == null ? null : functionForm.get(funName);
if (funId != null) {
SysFunction f = sysFunctionService.getById(funId);
if (f != null) return f;
}
List<SysFunction> list = sysFunctionService.list(new LambdaQueryWrapper<SysFunction>()
.eq(SysFunction::getFunctionName, funName)
.or().eq(SysFunction::getNumber, funName));
if (list == null || list.size() != 1) {
throw new MyRuntimeException("函数 [" + funName + "] 不存在或匹配到多个,无法执行 $fun$");
}
return list.get(0);
}
}

26
common/common-online/src/main/java/apelet/common/online/rule/enums/EventTypeEnum.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
package apelet.common.online.rule.enums;
import lombok.Getter;
/**
* 归一化后的事件类型,由动作 checkList 归一化而来:
* "加载" → FORM_CREATED;"值更新"/"值改变" → CHANGE;空数组 → ALL(监听全部事件)。
*
* 触发匹配:eventCode = formCreated 命中 FORM_CREATED,其余命中 CHANGE;ALL 恒命中。
*/
public enum EventTypeEnum {
/** 全部事件:checkList 为空数组,动作在所有事件下都触发(修复空数组「永不执行」行为反转点) */
ALL("全部事件(checkList 为空)"),
/** 页面加载:checkList 含「加载」,对应 eventCode = formCreated */
FORM_CREATED("页面加载(checkList 含「加载」,formCreated)"),
/** 值改变:checkList 含「值更新」/「值改变」,对应 eventCode = change */
CHANGE("值改变(checkList 含「值更新」/「值改变」,change)");
@Getter
private final String description;
EventTypeEnum(String description) {
this.description = description;
}
}

44
common/common-online/src/main/java/apelet/common/online/rule/enums/RuleActionTypeEnum.java

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
package apelet.common.online.rule.enums;
import lombok.Getter;
import java.util.Arrays;
/**
* 规则动作类型(1~8),对应规则 JSON allList[].type,Registry 按它路由到 RuleActionExecutor 实现。
*
* 1~4 为数据类动作(业务规则 businessRulesList),5~8 为控件状态类动作(界面规则 conditionTypeList):
* <pre>
* 1 计算并赋值 → FormulaAssignExecutor 金额 = 数量 * 单价
* 2 携带基础资料 → BaseDataCarryExecutor 创建人.fname → 提交人
* 3 设置字段值 → FieldAssignExecutor 源字段值 → 目标字段
* 4 清除字段值 → FieldClearExecutor 清空字段
* 5~8 锁定/解锁/显示/隐藏控件(WidgetLock/Unlock/Show/HideExecutor)
* </pre>
*/
@Getter
public enum RuleActionTypeEnum {
FORMULA_ASSIGN(1, "计算并赋值"),
BASE_DATA_CARRY(2, "携带基础资料"),
FIELD_ASSIGN(3, "设置字段值"),
FIELD_CLEAR(4, "清除字段值"),
WIDGET_LOCK(5, "锁定控件"),
WIDGET_UNLOCK(6, "解锁控件"),
WIDGET_SHOW(7, "显示控件"),
WIDGET_HIDE(8, "隐藏控件");
private final int code;
private final String description;
RuleActionTypeEnum(int code, String description) {
this.code = code;
this.description = description;
}
/** 由 widgetJson 动作里的 type 值(1~8)转枚举,未识别抛异常 */
public static RuleActionTypeEnum of(int code) {
return Arrays.stream(values()).filter(t -> t.code == code).findFirst()
.orElseThrow(() -> new IllegalArgumentException("未识别的动作类型: " + code));
}
}

23
common/common-online/src/main/java/apelet/common/online/rule/enums/RuleTypeEnum.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
package apelet.common.online.rule.enums;
import lombok.Getter;
/**
* 规则来源类型:界面规则 / 业务规则。
* 对应 widgetJson 两个数组:conditionTypeList(界面规则) / businessRulesList(业务规则)。
* 两个数组在代码里一视同仁处理(同一管线),ruleType 仅作来源标记,供前端差异化展示或排查。
*/
public enum RuleTypeEnum {
/** 界面规则:widgetJson 的 conditionTypeList 数组(锁定/解锁/显隐等控件状态类动作) */
INTERFACE("界面规则(conditionTypeList)"),
/** 业务规则:widgetJson 的 businessRulesList 数组(赋值/携带基础资料等数据类动作) */
BUSINESS("业务规则(businessRulesList)");
@Getter
private final String description;
RuleTypeEnum(String description) {
this.description = description;
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save