Compare commits

...

49 Commits

Author SHA1 Message Date
chenchuchuan 75297e6e12 refactor(widget): 优化组件JSON中表格ID回填逻辑 2 days ago
chenchuchuan b15ec4aba9 feat(online): 优化在线表单公式计算和字段同步功能 2 days ago
chenchuchuan 618f14aced refactor(online): 将WidgetJson解析逻辑提取为独立工具类 3 days ago
chenchuchuan 4cce9a9c11 fix(flow): 修复流程任务信息获取中的空指针异常 4 days ago
chenchuchuan a4894b2c25 feat(online): 添加根据表名查询列表表单功能 4 days ago
chenchuchuan 25f7f8f7c7 feat(online): 完善数据权限规则与角色绑定功能 4 days ago
chenchuchuan 9c618961ce feat(flow): 更新在线表单工作流业务数据获取功能 4 days ago
chenchuchuan 2ebdaf82e6 chore(config): 更新项目配置和依赖版本 5 days ago
chenchuchuan cf41dad6cf feat(flow): 添加在线表单工作流标题模板和多实例会签功能 5 days ago
chenchuchuan 91b9907be7 fix(online): 过滤表格字段类型避免误识别为新增字段 5 days ago
chenchuchuan 289ce088c8 fix(filter): 修复过滤器中JSON对象值处理问题 5 days ago
chenchuchuan 39e1d5ce3d feat(common): 支持移动端表单字段同步功能 1 week ago
chenchuchuan 6d4e3bb1e8 fix(button): 解决按钮配置接口权限和空值处理问题 1 week ago
chenchuchuan e8e7cbd588 fix(button): 解决按钮配置接口权限和空值处理问题 1 week ago
chenchuchuan 416c3d1793 refactor(online): 移除在线表单控制器中的废弃方法 1 week ago
chenchuchuan 7f1acccc4b Merge branch 'master' of http://124.222.6.97:3000/sunquan/apelet_tenant into feature/2026-07-ccc-dev 1 week 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 1 week ago
chenchuchuan cefb7ecf67 feat(common): 添加重置行数据功能和自定义页面跳转支持 1 week ago
chenchuchuan bb30a3454a feat(online): 添加在线字典映射获取功能和重置页面类型 1 week ago
chenchuchuan ee0b0f8295 Merge remote-tracking branch 'refs/remotes/origin/online-form-direct-creation' into feature/2026-07-ccc-dev 2 weeks ago
chenchuchuan 1bae360e2a refactor(permission): 将按钮权限检查器和相关服务迁移至公共模块 2 weeks ago
chenchuchuan f9ecb2aaef refactor(permission): 将权限数据规则方案控制器迁移至公共模块 2 weeks ago
chenchuchuan bc20c40671 refactor(orm): 优化数据权限规则和部门管理功能 2 weeks ago
chenchuchuan 7045cdea35 fix(controller): 修复权限数据规则模式控制器中的参数绑定和空指针问题 2 weeks ago
chenchuchuan 9ec9758bd5 refactor(upms): 重构角色组织关联逻辑并优化用户角色分配 2 weeks ago
chenchuchuan 88d393d1f7 feat(role): 角色模块支持组织多对多关联 2 weeks ago
chenchuchuan b2dc6af80f refactor(tenant): 重构角色表单按钮权限配置服务 2 weeks ago
chenchuchuan 8d491ff216 docs(common): 更新在线表单控制器逻辑并添加项目总览文档 2 weeks ago
chenchuchuan 446eeba6be docs(common): 更新在线表单控制器逻辑并添加项目总览文档 2 weeks ago
chenchuchuan b6e8b1914e refactor(tenant): 重构按钮权限配置服务以支持多角色合并 2 weeks ago
chenchuchuan d26d2b14a5 feat(online): 添加从表关联字段同步功能和表单类型查询接口 2 weeks ago
chenchuchuan ed03e2a488 Merge branch 'master' of http://124.222.6.97:3000/sunquan/apelet_tenant into online-form-direct-creation 2 weeks 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 weeks ago
chenchuchuan 65f2f7938d Merge branch 'master' of http://124.222.6.97:3000/sunquan/apelet_tenant into feature/form-permission-control 3 weeks ago
chenchuchuan 1f96a131a6 refactor(permission): 将权限数据规则方案相关代码迁移到common-online模块 3 weeks ago
chenchuchuan b8a4b5727f refactor(permission): 将权限数据规则方案相关代码迁移到common-online模块 3 weeks ago
chenchuchuan cb1b2dd9c3 feat(role): 添加角色按钮权限配置功能 3 weeks ago
chenchuchuan eadce13994 feat(upms): 添加部门组织类型支持及角色用户关联功能 3 weeks ago
chenchuchuan 35ee89c750 feat(tenant): 添加在线表单按钮权限控制功能 3 weeks ago
chenchuchuan 9f5efe49e3 feat(tenant): 添加权限数据规则方案管理功能 3 weeks ago
chenchuchuan 3104f1b026 refactor(common-generator): 优化在线表单头服务实现 1 month ago
chenchuchuan 6fc400ab77 feat(online): 表单字段保存时,修改字段备注取值逻辑 1 month ago
chenchuchuan e9c3b36512 feat(online): 添加数据表字段保存功能 1 month ago
chenchuchuan 7dcb3bb5c9 refactor(online): 使用常量替换硬编码的元数据源字符串 1 month ago
chenchuchuan f405113552 fix(controller): 修正在线页面控制器返回类型 1 month ago
sunquan 15dd345744 fix:补在线表单控件和数据库字段类型 1 month ago
sunquan 1d42dfc702 fix:去掉nacos配置 app-config、t-admin-dev 1 month ago
sunquan c665cde835 fix:去掉nacos配置 1 month ago
sunquan 610a4d10ed feature-online-form-direct-creation:收到请求后自动创建数据库物理表 + 元数据记录 + 数据源配置 1 month ago
  1. 23
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/LoginController.java
  2. 8
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/SysDeptController.java
  3. 97
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/SysRoleController.java
  4. 91
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/SysUserController.java
  5. 23
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/dto/RoleDeptItemDto.java
  6. 6
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/dto/SysDeptDto.java
  7. 1
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/model/SysDept.java
  8. 6
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/model/SysUserRole.java
  9. 15
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/SysRoleService.java
  10. 32
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/SysUserService.java
  11. 15
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/impl/SysDeptServiceImpl.java
  12. 49
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/impl/SysRoleServiceImpl.java
  13. 107
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/impl/SysUserServiceImpl.java
  14. 6
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/vo/SysDeptVo.java
  15. 7
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/vo/SysRoleVo.java
  16. 161
      application-tenant/tenant-admin/src/main/resources/application-config.yml
  17. 22
      application-tenant/tenant-admin/src/main/resources/bootstrap.yml
  18. 82
      application-tenant/tenant-admin/src/main/resources/logback-spring.xml
  19. 355
      application-tenant/tenant-admin/src/main/resources/tenant-admin-dev.yml
  20. 11
      common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowEntryOnlineServiceImpl.java
  21. 66
      common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineBusinessServiceImpl.java
  22. 22
      common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java
  23. 5
      common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java
  24. 19
      common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java
  25. 74
      common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java
  26. 5
      common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml
  27. 76
      common/common-flow/src/main/java/apelet/common/flow/listener/MultiInstanceAssigneeListener.java
  28. 4
      common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java
  29. 6
      common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java
  30. 14
      common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java
  31. 15
      common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java
  32. 83
      common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java
  33. 14
      common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java
  34. 32
      common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java
  35. 22
      common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java
  36. 6
      common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java
  37. 6
      common/common-flow/src/main/java/apelet/common/flow/vo/TaskInfoVo.java
  38. 7
      common/common-generator/src/main/java/apelet/common/generator/controller/OnlFormHeadController.java
  39. 3
      common/common-generator/src/main/java/apelet/common/generator/model/OnlCgreportItem.java
  40. 2
      common/common-generator/src/main/java/apelet/common/generator/model/OnlFormHead.java
  41. 11
      common/common-generator/src/main/java/apelet/common/generator/service/IOnlFormHeadService.java
  42. 39
      common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java
  43. 18
      common/common-online/src/main/java/apelet/common/online/abstractplugin/ExecutePluginParent.java
  44. 112
      common/common-online/src/main/java/apelet/common/online/abstractplugin/ListDataOrmService.java
  45. 12
      common/common-online/src/main/java/apelet/common/online/abstractplugin/ListDataService.java
  46. 45
      common/common-online/src/main/java/apelet/common/online/abstractplugin/MyInterfaceRulesExecute.java
  47. 1
      common/common-online/src/main/java/apelet/common/online/abstractplugin/model/ListColumn.java
  48. 611
      common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java
  49. 95
      common/common-online/src/main/java/apelet/common/online/controller/OnlineOperationController.java
  50. 91
      common/common-online/src/main/java/apelet/common/online/controller/OnlinePageController.java
  51. 186
      common/common-online/src/main/java/apelet/common/online/controller/PermissionDataRuleSchemaController.java
  52. 119
      common/common-online/src/main/java/apelet/common/online/controller/RoleFormButtonController.java
  53. 8
      common/common-online/src/main/java/apelet/common/online/dao/OnlineDictMapper.java
  54. 27
      common/common-online/src/main/java/apelet/common/online/dao/PermissionDataRuleSchemaMapper.java
  55. 23
      common/common-online/src/main/java/apelet/common/online/dao/RoleFormButtonMapper.java
  56. 13
      common/common-online/src/main/java/apelet/common/online/dao/RolePermissionSchemaMapper.java
  57. 4
      common/common-online/src/main/java/apelet/common/online/dao/mapper/OnlineDictMapper.xml
  58. 14
      common/common-online/src/main/java/apelet/common/online/dao/mapper/OnlineFormMapper.xml
  59. 27
      common/common-online/src/main/java/apelet/common/online/dao/mapper/PermissionDataRuleSchemaMapper.xml
  60. 21
      common/common-online/src/main/java/apelet/common/online/dao/mapper/RoleFormButtonMapper.xml
  61. 14
      common/common-online/src/main/java/apelet/common/online/dto/OnlinePageDto.java
  62. 35
      common/common-online/src/main/java/apelet/common/online/dto/RoleFormButtonDto.java
  63. 14
      common/common-online/src/main/java/apelet/common/online/model/OnlineForm.java
  64. 152
      common/common-online/src/main/java/apelet/common/online/model/PermissionDataRuleSchema.java
  65. 69
      common/common-online/src/main/java/apelet/common/online/model/RoleFormButton.java
  66. 28
      common/common-online/src/main/java/apelet/common/online/model/RolePermissionSchema.java
  67. 1
      common/common-online/src/main/java/apelet/common/online/model/constant/AttributeEnum.java
  68. 35
      common/common-online/src/main/java/apelet/common/online/model/constant/DataRuleType.java
  69. 1
      common/common-online/src/main/java/apelet/common/online/model/constant/ShowTypeEnum.java
  70. 20
      common/common-online/src/main/java/apelet/common/online/service/FormButtonPermissionChecker.java
  71. 65
      common/common-online/src/main/java/apelet/common/online/service/IPermissionDataRuleSchemaService.java
  72. 84
      common/common-online/src/main/java/apelet/common/online/service/IRoleFormButtonService.java
  73. 49
      common/common-online/src/main/java/apelet/common/online/service/IRolePermissionSchemaService.java
  74. 10
      common/common-online/src/main/java/apelet/common/online/service/OnlineColumnService.java
  75. 17
      common/common-online/src/main/java/apelet/common/online/service/OnlineDatasourceRelationService.java
  76. 3
      common/common-online/src/main/java/apelet/common/online/service/OnlineDictService.java
  77. 18
      common/common-online/src/main/java/apelet/common/online/service/OnlineFormService.java
  78. 7
      common/common-online/src/main/java/apelet/common/online/service/OnlinePageService.java
  79. 69
      common/common-online/src/main/java/apelet/common/online/service/impl/FormButtonPermissionCheckerImpl.java
  80. 29
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineColumnServiceImpl.java
  81. 218
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineDatasourceRelationServiceImpl.java
  82. 6
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineDatasourceServiceImpl.java
  83. 20
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineDictServiceImpl.java
  84. 31
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineFormServiceImpl.java
  85. 5
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlinePageServiceImpl.java
  86. 140
      common/common-online/src/main/java/apelet/common/online/service/impl/PermissionDataRuleSchemaServiceImpl.java
  87. 305
      common/common-online/src/main/java/apelet/common/online/service/impl/RoleFormButtonServiceImpl.java
  88. 89
      common/common-online/src/main/java/apelet/common/online/service/impl/RolePermissionSchemaServiceImpl.java
  89. 87
      common/common-online/src/main/java/apelet/common/online/util/WidgetFieldTypeMapping.java
  90. 93
      common/common-online/src/main/java/apelet/common/online/util/WidgetJsonButtonParser.java
  91. 410
      common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java
  92. 19
      common/common-online/src/main/java/apelet/common/online/vo/PermissionDataRuleSchemaVo.java
  93. 17
      common/common-online/src/main/java/apelet/common/online/vo/RoleFormButtonVo.java
  94. 3
      common/common-tenant-online/src/main/java/apelet/common/tenant/online/controller/TenantOnlinePageController.java

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

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

@ -3,20 +3,14 @@ spring: @@ -3,20 +3,14 @@ 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

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) {

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>

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

83
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;
@ -782,6 +790,11 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -782,6 +790,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 +805,8 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -792,6 +805,8 @@ 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));
for (Task task : taskList) {
FlowTaskVo flowTaskVo = new FlowTaskVo();
flowTaskVo.setTaskId(task.getId());
@ -817,6 +832,11 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -817,6 +832,11 @@ 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()
@ -2441,4 +2461,61 @@ public class FlowApiServiceImpl implements FlowApiService { @@ -2441,4 +2461,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);
}
}

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

@ -122,4 +122,10 @@ public class FlowTaskVo { @@ -122,4 +122,10 @@ public class FlowTaskVo {
*/
@Schema(description = "是否为草稿状态")
private Boolean isDraft;
/**
* 解析后的任务标题
*/
@Schema(description = "解析后的任务标题")
private String title;
}

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

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

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

@ -56,6 +56,9 @@ public class OnlCgreportItem extends BaseModel implements Serializable { @@ -56,6 +56,9 @@ public class OnlCgreportItem extends BaseModel implements Serializable {
@Schema(description = "是否显示 0否,1显示")
private Integer isShow;
@Schema(description = "是否下拉显示 0否,1显示")
private Integer isSelectShow;
@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);
}

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

@ -1660,4 +1660,43 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -1660,4 +1660,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;
}
}

18
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;
/**
* 插件执行抽象类父类
@ -569,4 +569,10 @@ public abstract class ExecutePluginParent { @@ -569,4 +569,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

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

@ -40,9 +40,9 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent { @@ -40,9 +40,9 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent {
public void change(String widgetVariableName, ObjectValue objectValue) {
Map values = objectValue.getValues();
List<Map<String, Object>> conditional = (List) values.get("conditional");
conditional("值改变", widgetVariableName, conditional, objectValue);
conditional("值更新", widgetVariableName, conditional, objectValue);
List<Map<String, Object>> business = (List) values.get("business");
conditional("值改变", widgetVariableName, business, objectValue);
conditional("值更新", widgetVariableName, business, objectValue);
}
void conditional(String type, String widgetVariableName, List<Map<String, Object>> isEstablish, ObjectValue objectValue) {
@ -86,13 +86,20 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent { @@ -86,13 +86,20 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent {
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 -> {
});
// 公式引用表单字段(如 amount = price * qty):把 表.表.字段 引用替换为实际值后求值,再赋给目标字段
Map<String, Object> context = new HashMap<>();
for (String ref : src.split("[^a-zA-Z0-9_.]+")) {
if (!ref.contains(objectValue.getTableName()) || context.containsKey(ref)) {
continue;
}
context.put(ref, this.resolveFormulaField(ref, objectValue));
}
Object computed = jexl3Util.expression(src, context);
List<String> list = Arrays.asList(tag.split("\\."));
super.setWidgetAttribute(list.get(list.size() - 1).trim(), AttributeEnum.DATA_VALUE_CHANGE, computed);
} else {
List<String> list = Arrays.asList(tag.split("\\."));
super.setWidgetAttribute(list.get(list.size() - 1), AttributeEnum.VALUE_CHANGE, src);
super.setWidgetAttribute(list.get(list.size() - 1), AttributeEnum.DATA_VALUE_CHANGE, src);
}
} else {
fun = jexl3Util.getMySplit("\\$(.*?)\\$", fun);
@ -100,7 +107,7 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent { @@ -100,7 +107,7 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent {
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);
super.setWidgetAttribute(list.get(list.size() - 1).trim(), AttributeEnum.DATA_VALUE_CHANGE, o);
}
break;
}
@ -279,4 +286,26 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent { @@ -279,4 +286,26 @@ public class MyInterfaceRulesExecute extends ExecutePluginParent {
// 使用正则表达式替换所有非字母数字字符为空字符串
return input.replaceAll("_", "").toLowerCase();
}
/**
* 解析公式中 ..字段 引用的实际值
* 主表字段直接取值关联字段..关联列.子字段取子对象属性分录字段取第一行值空值统一补 0
*/
private Object resolveFormulaField(String ref, ObjectValue objectValue) {
String[] parts = ref.split("\\.");
Object value;
if (parts[0].equals(parts[1])) {
if (parts.length > 3) {
Object linked = objectValue.get(parts[2]);
value = linked instanceof ObjectValue ? ((ObjectValue) linked).get(parts[3]) : null;
} else {
value = objectValue.get(parts[2]);
}
} else {
ObjectCollection collection = (ObjectCollection) objectValue.get(parts[1]);
value = (collection == null || collection.isEmpty())
? null : ((ObjectValue) collection.iterator().next()).get(parts[2]);
}
return value == null || value.toString().isEmpty() ? 0 : value;
}
}

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;//是否跳转自定义页面
//
}

611
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,20 @@ public class OnlineFormController { @@ -83,12 +93,20 @@ public class OnlineFormController {
@Autowired
IOnlFormHeadService onlFormHeadService;
@Autowired
IOnlFormFieldService onlFormFieldService;
@Autowired
OnlinePageService onlinePageService;
@Autowired
private OnlineProperties properties;
@Resource(name = "caffeineCacheManager")
private CacheManager cacheManager;
@Autowired
private MetaCacheService metaCacheService;
@Autowired(required = false)
private FormButtonPermissionChecker buttonPermissionChecker;
/**
* 新增在线表单数据
*
@ -212,120 +230,17 @@ public class OnlineFormController { @@ -212,120 +230,17 @@ 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());
}
// ★ 新增: 处理新增字段和子表
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 +410,7 @@ public class OnlineFormController { @@ -495,7 +410,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 +489,19 @@ public class OnlineFormController { @@ -574,6 +489,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 +551,6 @@ public class OnlineFormController { @@ -623,46 +551,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 +573,419 @@ public class OnlineFormController { @@ -685,4 +573,419 @@ 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) {
// 子表列:字段类型沿用现状,硬编码 750
this.saveField(750, widget.getString("showName"), null, head, columnName, extractSlaveTableId(widget));
} 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);
}
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) {
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;
}
/**
* 提取控件/子表列绑定的从表 idslaveTableId
* 字段控件在 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);
}
// 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);
}
}

95
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());

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>

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/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

20
common/common-online/src/main/java/apelet/common/online/service/FormButtonPermissionChecker.java

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
package apelet.common.online.service;
/**
* 在线表单按钮权限检查器接口
* tenant-admin 模块实现并注册为 Spring Bean
*
* @author chenchuchuan
* @date 2026-07-17
*/
public interface FormButtonPermissionChecker {
/**
* 检查当前用户是否有指定表单上指定按钮的操作权限
*
* @param formId 在线表单ID (zz_online_form.form_id)
* @param buttonName 按钮名称对应 operationList 中的 name 字段
* @return true-有权限false-无权限
*/
boolean hasPermission(Long formId, String buttonName);
}

65
common/common-online/src/main/java/apelet/common/online/service/IPermissionDataRuleSchemaService.java

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
package apelet.common.online.service;
import apelet.common.core.base.service.IBaseService;
import apelet.common.core.object.CallResult;
import apelet.common.online.model.PermissionDataRuleSchema;
import java.util.Collection;
import java.util.List;
/**
* 权限数据规则方案表 服务接口
*
* @author chenchuchuan
* @date 2026-07-15
*/
public interface IPermissionDataRuleSchemaService extends IBaseService<PermissionDataRuleSchema, Long> {
/**
* 分页条件查询规则方案列表
*
* @param filter 过滤条件
* @param orderBy 排序语句
* @return 规则方案列表
*/
List<PermissionDataRuleSchema> getListByFilter(PermissionDataRuleSchema filter, String orderBy);
/**
* 根据 formId 查询启用状态的规则运行时用
*
* @param formId 在线表ID
* @return 启用状态的规则列表
*/
List<PermissionDataRuleSchema> listByFormId(Long formId);
/**
* 校验关联数据验证 formId 对应的是主表或单表并自动填充 tableType
*
* @param schema 待校验的规则方案对象
* @return 校验结果
*/
CallResult verifyRelatedData(PermissionDataRuleSchema schema);
/**
* 保存新增 ID生成 + 默认状态设置
*
* @param schema 新增的规则方案对象
* @return 新增后的规则方案对象
*/
PermissionDataRuleSchema saveNew(PermissionDataRuleSchema schema);
/**
* 根据规则方案ID集合查询启用状态的规则运行时用
*
* @param idSet 规则方案ID集合
* @return 启用状态的规则列表
*/
List<PermissionDataRuleSchema> listEnabledByIds(Collection<Long> idSet);
/**
* 填充绑定的在线表单列表formId + formName用于详情接口回显
*
* @param schema 规则方案对象
*/
void fillFormNameList(PermissionDataRuleSchema schema);
}

84
common/common-online/src/main/java/apelet/common/online/service/IRoleFormButtonService.java

@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
package apelet.common.online.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.common.online.dto.RoleFormButtonDto;
import apelet.common.online.model.RoleFormButton;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 角色-表单按钮权限配置服务接口
*
* @author chenchuchuan
* @date 2026-07-17
*/
public interface IRoleFormButtonService
extends IBaseService<RoleFormButton, Long> {
/**
* 分页条件查询
*/
List<RoleFormButton> getListByFilter(RoleFormButton filter, String orderBy);
// ========== 核心业务接口 ==========
/**
* 查询当前登录用户对指定表单的按钮权限合并所有角色的并集
*
* @param formId 在线表单ID
* @return 包含 formId, buttonList(含name+checked的列表)
*/
Map<String, Set<String>> getButtonConfigVo(Long formId);
/**
* 批量保存按钮权限配置同一事务内全部成功或全部回滚
*
* @param dtoList 保存入参列表
* @return 保存结果
*/
CallResult batchSaveButtonConfig(List<RoleFormButtonDto> dtoList);
/**
* 保存单条按钮权限配置仅供内部批量方法调用不做事务注解
*
* @param dto 保存入参
* @return 保存结果
*/
CallResult saveButtonConfig(RoleFormButtonDto dto);
/**
* 根据角色ID查询该角色下所有绑定的按钮权限配置
*
* @param roleId 角色ID
* @return 按钮权限配置列表
*/
List<RoleFormButton> listByRoleId(Long roleId);
/**
* 根据角色ID和表单ID查询唯一配置记录
*/
RoleFormButton getByRoleIdAndFormId(Long roleId, Long formId);
/**
* 删除角色下绑定的所有按钮权限配置角色删除时调用
*/
void removeByRoleId(Long roleId);
/**
* 分页查询所有在线表单下的所有按钮
* 遍历全部在线表单从每个表单的 widgetJson 中提取已开启的按钮
* 如果传了 roleId则只返回该角色已配置的表单并合并已保存的勾选状态
*
* @param roleId 角色ID可选用于过滤已配置的表单
* @param orderParam 排序参数
* @param pageParam 分页参数
* @return 分页结果每个元素包含 formId, formName, buttonList[{name, checked}]
*/
MyPageData<Map<String, Object>> listAllFormButtons(Long roleId, MyOrderParam orderParam, MyPageParam pageParam);
}

49
common/common-online/src/main/java/apelet/common/online/service/IRolePermissionSchemaService.java

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
package apelet.common.online.service;
import apelet.common.core.base.service.IBaseService;
import apelet.common.core.object.CallResult;
import apelet.common.online.model.RolePermissionSchema;
import java.util.Collection;
import java.util.List;
/**
* 角色-数据规则方案关联服务接口
*
* @author chenchuchuan
* @date 2026-08-05
*/
public interface IRolePermissionSchemaService extends IBaseService<RolePermissionSchema, Long> {
/**
* 查询指定角色绑定的所有数据规则方案
*
* @param roleId 角色ID
* @return 关联关系列表
*/
List<RolePermissionSchema> listByRoleId(Long roleId);
/**
* 根据多个角色ID查询绑定的数据规则方案运行时用
*
* @param roleIds 角色ID集合
* @return 关联关系列表
*/
List<RolePermissionSchema> listByRoleIds(Collection<Long> roleIds);
/**
* 绑定角色的数据规则方案先删后插schemaIdList 为空表示清空该角色的全部绑定
*
* @param roleId 角色ID
* @param schemaIdList 数据规则方案ID列表
* @return 绑定结果校验失败时返回错误信息
*/
CallResult bindRoleSchemas(Long roleId, List<Long> schemaIdList);
/**
* 删除角色下绑定的所有数据规则方案角色删除时调用
*
* @param roleId 角色ID
*/
void removeByRoleId(Long roleId);
}

10
common/common-online/src/main/java/apelet/common/online/service/OnlineColumnService.java

@ -27,6 +27,16 @@ public interface OnlineColumnService extends IBaseService<OnlineColumn, Long> { @@ -27,6 +27,16 @@ public interface OnlineColumnService extends IBaseService<OnlineColumn, Long> {
List<OnlineColumn> saveNewList(List<SqlTableColumn> columnList, Long onlineTableId);
/**
* 保存新增数据表字段
*
* @param sqlTableColumn 新增数据表字段对象
* @param tableId 在线表对象的主键Id
* @param columnComment 字段名
* @return 插入的在线表字段数据
*/
long saveBySqlTable(SqlTableColumn sqlTableColumn, Long tableId, String columnComment);
/**
* 更新数据对象
*
* @param onlineColumn 更新的对象

17
common/common-online/src/main/java/apelet/common/online/service/OnlineDatasourceRelationService.java

@ -1,9 +1,13 @@ @@ -1,9 +1,13 @@
package apelet.common.online.service;
import apelet.common.core.base.service.IBaseService;
import apelet.common.core.object.ResponseResult;
import apelet.common.dbutil.object.SqlTable;
import apelet.common.dbutil.object.SqlTableColumn;
import apelet.common.generator.model.OnlFormHead;
import apelet.common.online.model.OnlineDatasource;
import apelet.common.online.model.OnlineDatasourceRelation;
import apelet.common.online.model.OnlineDblink;
import java.util.List;
import java.util.Set;
@ -82,4 +86,17 @@ public interface OnlineDatasourceRelationService extends IBaseService<OnlineData @@ -82,4 +86,17 @@ public interface OnlineDatasourceRelationService extends IBaseService<OnlineData
*/
List<OnlineDatasourceRelation> getOnlineDatasourceRelationListWithRelation(
OnlineDatasourceRelation filter, String orderBy);
/***
* 功能: 迁移 OnlinePageController#addOnlineDatasourceRelation(OnlFormHead, OnlineDatasource, String, OnlineDblink)
* 方法创建数据表关联关系 先查出改元数据表的所有附表关系
*
* @author ${chenchuchuan}
* @param onlFormHead 主表信息
* @param onlineDatasource 数据库链接配置
* @param errorMessage 错误描述
* @param onlineDblink 数据库实际链接
* @return ResponseResult<Long>
*/
ResponseResult<Long> addOnlineDatasourceRelation(OnlFormHead onlFormHead, OnlineDatasource onlineDatasource, String errorMessage, OnlineDblink onlineDblink);
}

3
common/common-online/src/main/java/apelet/common/online/service/OnlineDictService.java

@ -4,6 +4,7 @@ import apelet.common.core.base.service.IBaseService; @@ -4,6 +4,7 @@ import apelet.common.core.base.service.IBaseService;
import apelet.common.online.model.OnlineDict;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@ -75,4 +76,6 @@ public interface OnlineDictService extends IBaseService<OnlineDict, Long> { @@ -75,4 +76,6 @@ public interface OnlineDictService extends IBaseService<OnlineDict, Long> {
* @return 在线字典对象集合
*/
List<OnlineDict> getOnlineDictListFromCache(Set<Long> dictIdSet);
Map<String, String> getDictMap(Long dictId);
}

18
common/common-online/src/main/java/apelet/common/online/service/OnlineFormService.java

@ -139,4 +139,22 @@ public interface OnlineFormService extends IBaseService<OnlineForm, Long> { @@ -139,4 +139,22 @@ public interface OnlineFormService extends IBaseService<OnlineForm, Long> {
* @return
*/
ObjectValue getObjectValue(Map<String, Object> masterData, Map<String, Object> savedata, String tableName);
/**
* 根据表单类型查询表单列表
*
* @param formType 表单类型
* @return 表单列表
*/
List<OnlineForm> queryListByFormType(List<Integer> formType);
/**
* 根据表名查询表单
*
* @param formType 表单类型
* @param tableName 表名
* @return 表单
* @see apelet.common.online.model.constant.FormType
*/
List<OnlineForm> queryByTableName(String tableName, int formType);
}

7
common/common-online/src/main/java/apelet/common/online/service/OnlinePageService.java

@ -135,4 +135,11 @@ public interface OnlinePageService extends IBaseService<OnlinePage, Long> { @@ -135,4 +135,11 @@ public interface OnlinePageService extends IBaseService<OnlinePage, Long> {
* @return 在线表单页面列表
*/
List<OnlinePage> getInListWithNonTenant(List<Long> pageIds, String orderBy);
/**
* 查询启用的在线表单页面列表
*
* @return 在线表单页面列表
*/
List<OnlinePage> queryByEnableList();
}

69
common/common-online/src/main/java/apelet/common/online/service/impl/FormButtonPermissionCheckerImpl.java

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
package apelet.common.online.service.impl;
import apelet.common.core.object.TokenData;
import apelet.common.online.model.RoleFormButton;
import apelet.common.online.service.FormButtonPermissionChecker;
import apelet.common.online.service.IRoleFormButtonService;
import apelet.common.online.util.WidgetJsonButtonParser;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
/**
* 按钮权限检查器实现
* 通过当前用户的所有角色查询 xy_sys_role_form_button
* 判断是否有指定表单-按钮的权限
*
* @author chenchuchuan
* @date 2026-07-17
*/
@Slf4j
@Component
public class FormButtonPermissionCheckerImpl implements FormButtonPermissionChecker {
@Autowired
private IRoleFormButtonService roleFormButtonService;
@Override
public boolean hasPermission(Long formId, String buttonName) {
try {
// 1. 获取当前登录用户信息
TokenData tokenData = TokenData.takeFromRequest();
if (tokenData == null) {
log.warn("TokenData 为空,无法校验按钮权限");
return false;
}
Long userId = tokenData.getUserId();
if (userId == null) {
return false;
}
// 2. 从 TokenData 获取用户的所有角色ID
if (StrUtil.isBlank(tokenData.getRoleIds())) {
return false;
}
List<Long> roleIds = StrUtil.split(tokenData.getRoleIds(), ',').stream().map(Long::valueOf).collect(Collectors.toList());
// 3. 遍历角色,检查是否有任一角色授权了该表单的该按钮
for (Long roleId : roleIds) {
RoleFormButton config = roleFormButtonService.getByRoleIdAndFormId(roleId, formId);
if (config != null && config.getButtonConfig() != null) {
List<WidgetJsonButtonParser.ButtonInfo> buttons = JSON.parseArray(config.getButtonConfig(), WidgetJsonButtonParser.ButtonInfo.class);
for (WidgetJsonButtonParser.ButtonInfo btn : buttons) {
if (buttonName.equals(btn.getName()) && Boolean.TRUE.equals(btn.getEnabled())) {
return true;
}
}
}
}
} catch (Exception e) {
log.error("校验按钮权限时发生异常", e);
}
return false;
}
}

29
common/common-online/src/main/java/apelet/common/online/service/impl/OnlineColumnServiceImpl.java

@ -31,13 +31,17 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; @@ -31,13 +31,17 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.github.pagehelper.Page;
import com.google.common.base.CaseFormat;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* 字段数据数据操作服务类
@ -106,6 +110,29 @@ public class OnlineColumnServiceImpl extends BaseService<OnlineColumn, Long> imp @@ -106,6 +110,29 @@ public class OnlineColumnServiceImpl extends BaseService<OnlineColumn, Long> imp
return onlineColumnList;
}
@Override
public long saveBySqlTable(SqlTableColumn sqlTableColumn, Long tableId, String columnComment) {
OnlineColumn onlineColumn = new OnlineColumn();
BeanUtil.copyProperties(sqlTableColumn, onlineColumn, false);
onlineColumn.setColumnId(idGenerator.nextLongId());
onlineColumn.setTableId(tableId);
// 传了描述,并且不等于原先的描述,并且原先的为空或者为"null",就赋值
if (StringUtil.isNotBlank(columnComment) && !StringUtil.equals(columnComment, onlineColumn.getColumnComment())
&& (StringUtil.isBlank(onlineColumn.getColumnComment()) || StringUtil.equals("null", onlineColumn.getColumnComment()))) {
onlineColumn.setColumnComment(columnComment);
}
if (StringUtil.isBlank(onlineColumn.getColumnComment())) {
onlineColumn.setColumnComment(onlineColumn.getColumnName());
}
this.setDefault(sqlTableColumn, onlineColumn);
if (super.save(onlineColumn)) {
sqlTableColumn.setColumnName(onlineColumn.getColumnName().toLowerCase());
return onlineColumn.getColumnId();
}
return 0;
}
/**
* 更新数据对象
*

218
common/common-online/src/main/java/apelet/common/online/service/impl/OnlineDatasourceRelationServiceImpl.java

@ -4,21 +4,31 @@ import apelet.common.core.annotation.MyDataSourceResolver; @@ -4,21 +4,31 @@ import apelet.common.core.annotation.MyDataSourceResolver;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.core.base.service.BaseService;
import apelet.common.core.constant.ApplicationConstant;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.object.CallResult;
import apelet.common.core.object.MyRelationParam;
import apelet.common.core.object.ResponseResult;
import apelet.common.core.object.TokenData;
import apelet.common.core.util.DefaultDataSourceResolver;
import apelet.common.core.util.MyModelUtil;
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.online.dao.OnlineDatasourceRelationMapper;
import apelet.common.online.dao.OnlineDatasourceTableMapper;
import apelet.common.online.dto.OnlineDatasourceRelationDto;
import apelet.common.online.model.*;
import apelet.common.online.service.*;
import apelet.common.online.util.OnlineRedisKeyUtil;
import apelet.common.redis.util.CommonRedisUtil;
import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.github.pagehelper.Page;
@ -30,6 +40,7 @@ import org.springframework.stereotype.Service; @@ -30,6 +40,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
/**
* 数据源关联数据操作服务类
@ -61,6 +72,14 @@ public class OnlineDatasourceRelationServiceImpl @@ -61,6 +72,14 @@ public class OnlineDatasourceRelationServiceImpl
private RedissonClient redissonClient;
@Autowired
private CommonRedisUtil commonRedisUtil;
@Autowired
private OnlineDblinkService onlineDblinkService;
@Autowired
private IOnlFormHeadService iOnlFormHeadService;
@Autowired
private OnlineDatasourceRelationService onlineDatasourceRelationService;
@Autowired
private IOnlFormFieldService onlFormFieldService;
/**
* 返回当前Service的主表Mapper对象
@ -289,4 +308,203 @@ public class OnlineDatasourceRelationServiceImpl @@ -289,4 +308,203 @@ public class OnlineDatasourceRelationServiceImpl
}
return CallResult.ok();
}
@Override
public ResponseResult<Long> addOnlineDatasourceRelation(OnlFormHead onlFormHead, OnlineDatasource onlineDatasource, String errorMessage, OnlineDblink onlineDblink) {
//历史数据 --做比较新数据写入,已存在的不用管,不存在的删除
OnlineDatasourceRelation onlineDatasourceRelations = new OnlineDatasourceRelation();
onlineDatasourceRelations.setDatasourceId(onlineDatasource.getDatasourceId());
List<OnlineDatasourceRelation> onlineDatasourceRelationList =
onlineDatasourceRelationService.getOnlineDatasourceRelationListWithRelation(onlineDatasourceRelations, null);
Map<String, OnlineDatasourceRelation> oldmap01 =
onlineDatasourceRelationList.stream().collect(Collectors.toMap(obj -> obj.getMasterColumnId() + "_" + obj.getRelationType() + "_" + obj.getSlaveTableId(), obj -> obj,
(key1, key2) -> key1
));
//查出表单元数据的所有附表关系
//主表id
List<OnlineTable> onlineTableList = onlineTableService.list(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, onlFormHead.getTableName().toLowerCase()));
//创建一对多关系:1
List<OnlFormHead> oneToMoreObjsList = iOnlFormHeadService.list(new LambdaQueryWrapper<OnlFormHead>().eq(OnlFormHead::getLine, 1).eq(OnlFormHead::getSort, onlFormHead.getTableName()));
if (!oneToMoreObjsList.isEmpty()) {
List<Long> tableIdList = onlineTableList.stream().map(OnlineTable::getTableId).collect(Collectors.toList());
// //主表列id对象
OnlineColumn onlineColumn = onlineColumnService.getOne(new LambdaQueryWrapper<OnlineColumn>()
.eq(OnlineColumn::getColumnName, "id")
.in(OnlineColumn::getTableId, tableIdList));
for (OnlFormHead formHead : oneToMoreObjsList) {
List<OnlineTable> onlineTableListfu = onlineTableService.list(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, formHead.getTableName().toLowerCase()));
//附表关联的主表字段+附表id
if ((onlineTableListfu == null || onlineTableListfu.size() < 1) || !oldmap01.containsKey(onlineColumn.getColumnId() + "_1_" + onlineTableListfu.get(0).getTableId())) {
OnlineDatasourceRelationDto onlineDatasourceRelationDto = new OnlineDatasourceRelationDto();
onlineDatasourceRelationDto.setDatasourceId(onlineDatasource.getDatasourceId());
//关联名称
onlineDatasourceRelationDto.setRelationName(onlFormHead.getTableName() + "_" + formHead.getTableName());
//关联标识
onlineDatasourceRelationDto.setVariableName(onlFormHead.getTableName() + "_" + formHead.getTableName());
//关联类型
onlineDatasourceRelationDto.setRelationType(1);
//主键字段id
onlineDatasourceRelationDto.setMasterColumnId(onlineColumn.getColumnId());
//从表名称
onlineDatasourceRelationDto.setSlaveTableName(formHead.getTableName());
//从表字段名称
onlineDatasourceRelationDto.setSlaveColumnName(formHead.getSortFiled());
//是否级联删除
onlineDatasourceRelationDto.setCascadeDelete(true);
//是否左连接查询
onlineDatasourceRelationDto.setLeftJoin(false);
OnlineDatasourceRelation onlineDatasourceRelation =
MyModelUtil.copyTo(onlineDatasourceRelationDto, OnlineDatasourceRelation.class);
String appCode = TokenData.takeFromRequest().getAppCode();
if (!StrUtil.equals(onlineDatasource.getAppCode(), appCode)) {
errorMessage = "数据验证失败,当前应用并不包含该数据源Id!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
// OnlineDblink onlineDblink = onlineDblinkService.getById(onlineDatasource.getDblinkId());
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);
}
// 验证关联Id的数据合法性
CallResult callResult =
onlineDatasourceRelationService.verifyRelatedData(onlineDatasourceRelation, null);
if (!callResult.isSuccess()) {
errorMessage = callResult.getErrorMessage();
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
onlineDatasourceRelation = onlineDatasourceRelationService.saveNew(onlineDatasourceRelation, slaveTable, slaveColumn);
} else {
//更新表字段
SqlTable slaveTable = onlineDblinkService.getDblinkTable(
onlineDblink, formHead.getTableName());
if (slaveTable == null) {
errorMessage = "数据验证失败,指定的数据表不存在!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
List<SqlTableColumn> sqlTableColumnList = slaveTable.getColumnList();
Set<Long> tableids = new HashSet<>();
tableids.add(onlineTableListfu.get(0).getTableId());
List<OnlineColumn> listColumn = onlineColumnService.getOnlineColumnListByTableIds(tableids);
List<String> columnNames = listColumn.stream().map(OnlineColumn::getColumnName).collect(Collectors.toList());
sqlTableColumnList = sqlTableColumnList.stream().filter(x -> !columnNames.contains(x.getColumnName().toLowerCase())).collect(Collectors.toList());
onlineColumnService.saveNewList(sqlTableColumnList, onlineTableListfu.get(0).getTableId());
}
}
}
//创建一对一关系:0
List<OnlFormField> onlFormFieldList = onlFormFieldService.list(new LambdaQueryWrapper<OnlFormField>()
.eq(OnlFormField::getHeadId, onlFormHead.getId()).isNotNull(OnlFormField::getRefPropert));
//List<OnlFormHead> oneToMoreObjsList = iOnlFormHeadService.list(new LambdaQueryWrapper<OnlFormHead>().eq(OnlFormHead::getLine, 1).eq(OnlFormHead::getSort, onlFormHead.getTableName()));
if (!onlFormFieldList.isEmpty()) {
Set<String> updatedTable = new HashSet<>();
//附表
for (OnlFormField onlFormField : onlFormFieldList) {
//附表
List<OnlFormHead> onlFormHeadList = iOnlFormHeadService.list(new LambdaQueryWrapper<OnlFormHead>().eq(OnlFormHead::getId, onlFormField.getRefPropert()));
if (onlFormHeadList.isEmpty()) {
continue;
}
OnlFormHead formHead = onlFormHeadList.get(0);
//根据表名去找在线附表
List<OnlineTable> fuonlineTableList = onlineTableService.list(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, onlFormHeadList.get(0).getTableName()));
//主表id对象
OnlineColumn onlineColumn = onlineColumnService.getOne(new LambdaQueryWrapper<OnlineColumn>()
.eq(OnlineColumn::getColumnName, onlFormField.getFieldName())
.in(OnlineColumn::getTableId, onlineTableList.get(0).getTableId()));
if (onlineColumn == null) {
onlineColumn = onlineColumnService.getOne(new LambdaQueryWrapper<OnlineColumn>()
.eq(OnlineColumn::getColumnName, onlFormField.getFieldName().toLowerCase())
.in(OnlineColumn::getTableId, onlineTableList.get(0).getTableId()));
}
//附表关联的主表字段+附表id
if (fuonlineTableList == null || fuonlineTableList.isEmpty() || (!oldmap01.containsKey(onlineColumn.getColumnId() + "_0_" + fuonlineTableList.get(0).getTableId()))) {
OnlineDatasourceRelationDto onlineDatasourceRelationDto = new OnlineDatasourceRelationDto();
onlineDatasourceRelationDto.setDatasourceId(onlineDatasource.getDatasourceId());
//关联名称
onlineDatasourceRelationDto.setRelationName(onlFormHead.getTableName() + "_" + onlFormField.getFieldName() + "_" + formHead.getTableName());
//关联标识
onlineDatasourceRelationDto.setVariableName(onlFormHead.getTableName() + "_" + onlFormField.getFieldName() + "_" + formHead.getTableName());
//关联类型
onlineDatasourceRelationDto.setRelationType(0);
//主键字段id
onlineDatasourceRelationDto.setMasterColumnId(onlineColumn.getColumnId());
//从表名称
onlineDatasourceRelationDto.setSlaveTableName(formHead.getTableName());
//从表字段名称
onlineDatasourceRelationDto.setSlaveColumnName("id");
//是否级联删除
onlineDatasourceRelationDto.setCascadeDelete(true);
//是否左连接查询
onlineDatasourceRelationDto.setLeftJoin(false);
OnlineDatasourceRelation onlineDatasourceRelation =
MyModelUtil.copyTo(onlineDatasourceRelationDto, OnlineDatasourceRelation.class);
String appCode = TokenData.takeFromRequest().getAppCode();
if (!StrUtil.equals(onlineDatasource.getAppCode(), appCode)) {
errorMessage = "数据验证失败,当前应用并不包含该数据源Id!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
// OnlineDblink onlineDblink = onlineDblinkService.getById(onlineDatasource.getDblinkId());
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);
}
// 验证关联Id的数据合法性
CallResult callResult =
onlineDatasourceRelationService.verifyRelatedData(onlineDatasourceRelation, null);
if (!callResult.isSuccess()) {
errorMessage = callResult.getErrorMessage();
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
onlineDatasourceRelation = onlineDatasourceRelationService.saveNew(onlineDatasourceRelation, slaveTable, slaveColumn);
} else {
if (updatedTable.contains(formHead.getTableName())) {
continue;
}
updatedTable.add(formHead.getTableName());
//更新表字段
SqlTable slaveTable = onlineDblinkService.getDblinkTable(
onlineDblink, formHead.getTableName());
if (slaveTable == null) {
errorMessage = "数据验证失败,指定的数据表不存在!";
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
List<SqlTableColumn> sqlTableColumnList = slaveTable.getColumnList();
Set<Long> tableids = new HashSet<>();
tableids.add(fuonlineTableList.get(0).getTableId());
List<OnlineColumn> listColumn = onlineColumnService.getOnlineColumnListByTableIds(tableids);
List<String> columnNames = listColumn.stream().map(OnlineColumn::getColumnName).collect(Collectors.toList());
sqlTableColumnList = sqlTableColumnList.stream().filter(x -> !columnNames.contains(x.getColumnName().toLowerCase())).collect(Collectors.toList());
onlineColumnService.saveNewList(sqlTableColumnList, fuonlineTableList.get(0).getTableId());
}
}
}
return null;
}
}

6
common/common-online/src/main/java/apelet/common/online/service/impl/OnlineDatasourceServiceImpl.java

@ -11,7 +11,10 @@ import apelet.common.dbutil.object.SqlTable; @@ -11,7 +11,10 @@ import apelet.common.dbutil.object.SqlTable;
import apelet.common.online.dao.OnlineDatasourceMapper;
import apelet.common.online.dao.OnlineDatasourceTableMapper;
import apelet.common.online.dao.OnlinePageDatasourceMapper;
import apelet.common.online.model.*;
import apelet.common.online.model.OnlineDatasource;
import apelet.common.online.model.OnlineDatasourceTable;
import apelet.common.online.model.OnlinePageDatasource;
import apelet.common.online.model.OnlineTable;
import apelet.common.online.service.OnlineDatasourceRelationService;
import apelet.common.online.service.OnlineDatasourceService;
import apelet.common.online.service.OnlineTableService;
@ -83,6 +86,7 @@ public class OnlineDatasourceServiceImpl extends BaseService<OnlineDatasource, L @@ -83,6 +86,7 @@ public class OnlineDatasourceServiceImpl extends BaseService<OnlineDatasource, L
TokenData tokenData = TokenData.takeFromRequest();
OnlineTable onlineTable = onlineTableService.saveNewFromSqlTable(sqlTable);
onlineDatasource.setDatasourceId(idGenerator.nextLongId());
onlineDatasource.setDatasourceName(sqlTable.getTableName());
onlineDatasource.setAppCode(tokenData.getAppCode());
onlineDatasource.setMasterTableId(onlineTable.getTableId());
Date now = new Date();

20
common/common-online/src/main/java/apelet/common/online/service/impl/OnlineDictServiceImpl.java

@ -15,6 +15,9 @@ import apelet.common.online.service.OnlineDictService; @@ -15,6 +15,9 @@ import apelet.common.online.service.OnlineDictService;
import apelet.common.online.util.OnlineRedisKeyUtil;
import apelet.common.redis.util.CommonRedisUtil;
import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.github.pagehelper.Page;
import lombok.extern.slf4j.Slf4j;
@ -184,4 +187,21 @@ public class OnlineDictServiceImpl extends BaseService<OnlineDict, Long> impleme @@ -184,4 +187,21 @@ public class OnlineDictServiceImpl extends BaseService<OnlineDict, Long> impleme
}
return CallResult.ok();
}
@Override
public Map<String, String> getDictMap(Long dictId) {
Map<String, String> map = new HashMap<>();
OnlineDict onlineDict = this.getOnlineDictFromCache(dictId);
if (onlineDict == null) {
return map;
}
JSONObject dictJson = JSON.parseObject(onlineDict.getDictDataJson());
JSONArray jsonArray = dictJson.getJSONArray("dictData");
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
map.put(jsonObject.getString("id"), jsonObject.getString("name"));
}
return map;
}
}

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

@ -26,6 +26,7 @@ import apelet.common.orm.impl.LinkEntityColumn; @@ -26,6 +26,7 @@ import apelet.common.orm.impl.LinkEntityColumn;
import apelet.common.redis.util.CommonRedisUtil;
import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
@ -35,7 +36,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; @@ -35,7 +36,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.github.pagehelper.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
@ -460,6 +460,35 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme @@ -460,6 +460,35 @@ public class OnlineFormServiceImpl extends BaseService<OnlineForm, Long> impleme
return objectValue;
}
@Override
public List<OnlineForm> queryListByFormType(List<Integer> formType) {
// 查询已发布的 page
List<OnlinePage> pageList = onlinePageService.queryByEnableList();
if (CollectionUtil.isEmpty(pageList)) {
return Collections.emptyList();
}
Set<Long> pageIdSet = pageList.stream().map(OnlinePage::getPageId).collect(Collectors.toSet());
List<OnlineForm> formList = onlineFormMapper.selectList(new LambdaQueryWrapper<OnlineForm>().in(OnlineForm::getFormType, formType).in(OnlineForm::getPageId, pageIdSet));
if (CollectionUtil.isNotEmpty(formList)) {
formList.forEach(item -> {
item.setWidgetJson(null);
});
}
return formList;
}
@Override
public List<OnlineForm> queryByTableName(String tableName, int formType) {
List<OnlineTable> onlineTableList = onlineTableService.list(new LambdaQueryWrapper<OnlineTable>().eq(OnlineTable::getTableName, tableName));
if (CollectionUtil.isNotEmpty(onlineTableList)) {
Set<Long> tableIdSet = onlineTableList.stream().map(OnlineTable::getTableId).collect(Collectors.toSet());
return onlineFormMapper.selectList(new LambdaQueryWrapper<OnlineForm>()
.in(OnlineForm::getMasterTableId, tableIdSet)
.eq(OnlineForm::getFormType, formType));
}
return Collections.emptyList();
}
/**
* 设置关联表字段转换为对象
*

5
common/common-online/src/main/java/apelet/common/online/service/impl/OnlinePageServiceImpl.java

@ -296,4 +296,9 @@ public class OnlinePageServiceImpl extends BaseService<OnlinePage, Long> impleme @@ -296,4 +296,9 @@ public class OnlinePageServiceImpl extends BaseService<OnlinePage, Long> impleme
}
return onlinePageMapper.selectList(queryWrapper);
}
@Override
public List<OnlinePage> queryByEnableList() {
return onlinePageMapper.selectList(new LambdaQueryWrapper<OnlinePage>().eq(OnlinePage::getPublished, true));
}
}

140
common/common-online/src/main/java/apelet/common/online/service/impl/PermissionDataRuleSchemaServiceImpl.java

@ -0,0 +1,140 @@ @@ -0,0 +1,140 @@
package apelet.common.online.service.impl;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.core.base.service.BaseService;
import apelet.common.core.object.CallResult;
import apelet.common.generator.model.OnlFormHead;
import apelet.common.generator.service.IOnlFormHeadService;
import apelet.common.online.dao.PermissionDataRuleSchemaMapper;
import apelet.common.online.model.OnlineForm;
import apelet.common.online.model.PermissionDataRuleSchema;
import apelet.common.online.model.constant.DataRuleType;
import apelet.common.online.service.IPermissionDataRuleSchemaService;
import apelet.common.online.service.OnlineFormService;
import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
/**
* 权限数据规则方案表 服务实现类
*
* @author chenchuchuan
* @date 2026-07-15
*/
@Service("permissionDataRuleSchemaService")
@Slf4j
public class PermissionDataRuleSchemaServiceImpl extends BaseService<PermissionDataRuleSchema, Long> implements IPermissionDataRuleSchemaService {
@Autowired
PermissionDataRuleSchemaMapper mapper;
@Autowired
private IdGeneratorWrapper idGenerator;
@Autowired
private IOnlFormHeadService onlFormHeadService;
@Autowired
private OnlineFormService onlineFormService;
// ========== BaseService 要求重写 ==========
@Override
protected BaseDaoMapper<PermissionDataRuleSchema> mapper() {
return mapper;
}
// ========== 分页查询 ==========
@Override
public List<PermissionDataRuleSchema> getListByFilter(PermissionDataRuleSchema filter, String orderBy) {
return mapper.getListByFilter(filter, orderBy);
}
// ========== 按 formHeadId 查询启用状态的规则 ==========
@Override
public List<PermissionDataRuleSchema> listByFormId(Long formId) {
LambdaQueryWrapper<PermissionDataRuleSchema> qw = new LambdaQueryWrapper<>();
qw.eq(PermissionDataRuleSchema::getFormHeadId, formId)
.eq(PermissionDataRuleSchema::getStatus, 1);
return this.list(qw);
}
// ========== 新增(含 ID生成 + 默认状态) ==========
@Override
public PermissionDataRuleSchema saveNew(PermissionDataRuleSchema schema) {
if (schema.getStatus() == null) {
schema.setStatus(1);
}
if (schema.getRuleType() == null) {
schema.setRuleType(DataRuleType.LIST);
}
this.save(schema);
return schema;
}
// ========== 根据规则方案ID集合查询启用状态的规则(运行时用) ==========
@Override
public List<PermissionDataRuleSchema> listEnabledByIds(Collection<Long> idSet) {
if (CollUtil.isEmpty(idSet)) {
return Collections.emptyList();
}
LambdaQueryWrapper<PermissionDataRuleSchema> qw = new LambdaQueryWrapper<>();
qw.in(PermissionDataRuleSchema::getId, idSet)
.eq(PermissionDataRuleSchema::getStatus, 1);
return this.list(qw);
}
// ========== 填充绑定的在线表单列表(formId + formName),详情接口回显用 ==========
@Override
public void fillFormNameList(PermissionDataRuleSchema schema) {
if (schema == null ){
return;
}
List<Map<String, Object>> formList = new ArrayList<>();
if (StrUtil.isNotBlank(schema.getFormIdList())) {
List<Long> formIds = StrUtil.split(schema.getFormIdList(), ',')
.stream().filter(StrUtil::isNotBlank).map(Long::valueOf).collect(Collectors.toList());
if (CollUtil.isNotEmpty(formIds)) {
OnlineForm filter = new OnlineForm();
filter.setFormIdSet(new HashSet<>(formIds));
List<OnlineForm> formListData = onlineFormService.getOnlineFormList(filter, null);
if (CollUtil.isNotEmpty(formListData)) {
for (OnlineForm form : formListData) {
Map<String, Object> item = new HashMap<>();
item.put("formId", form.getFormId());
item.put("formName", form.getFormName());
formList.add(item);
}
}
}
}
schema.setFormList(formList);
}
// ========== 核心校验: formHeadId → 主表/单表,自动填充 tableType ==========
@Override
public CallResult verifyRelatedData(PermissionDataRuleSchema schema) {
// 1. 通过 formHeadId 查询 onl_form_head 获取 tableType
OnlFormHead formHead = onlFormHeadService.getById(schema.getFormHeadId());
if (formHead == null) {
return CallResult.error("数据验证失败,无法获取表类型信息,请检查表单配置!");
}
// 2. tableType 校验: 0=附表拒绝, 1=主表, 2=单表
String tableType = formHead.getTableType();
if (!"1".equals(tableType) && !"2".equals(tableType)) {
return CallResult.error("数据验证失败,仅主表和单表支持配置数据规则,当前为附表!");
}
// 3. 自动填充 tableType
schema.setTableType(Integer.valueOf(tableType));
return CallResult.ok();
}
}

305
common/common-online/src/main/java/apelet/common/online/service/impl/RoleFormButtonServiceImpl.java

@ -0,0 +1,305 @@ @@ -0,0 +1,305 @@
package apelet.common.online.service.impl;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.core.base.service.BaseService;
import apelet.common.core.object.*;
import apelet.common.online.dao.RoleFormButtonMapper;
import apelet.common.online.dto.RoleFormButtonDto;
import apelet.common.online.model.OnlineForm;
import apelet.common.online.model.OnlinePage;
import apelet.common.online.model.RoleFormButton;
import apelet.common.online.service.IRoleFormButtonService;
import apelet.common.online.service.OnlineFormService;
import apelet.common.online.service.OnlinePageService;
import apelet.common.online.util.WidgetJsonButtonParser;
import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.page.PageMethod;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 角色-表单按钮权限配置服务实现
*
* @author chenchuchuan
* @date 2026-07-17
*/
@Service
@Slf4j
public class RoleFormButtonServiceImpl extends BaseService<RoleFormButton, Long> implements IRoleFormButtonService {
@Autowired
private RoleFormButtonMapper mapper;
@Autowired
private IdGeneratorWrapper idGenerator;
@Autowired
private OnlineFormService onlineFormService;
@Autowired
private OnlinePageService onlinePageService;
@Override
protected BaseDaoMapper<RoleFormButton> mapper() {
return mapper;
}
@Override
public List<RoleFormButton> getListByFilter(
RoleFormButton filter, String orderBy) {
return mapper.getListByFilter(filter, orderBy);
}
// ========== 核心:查询按钮配置VO(合并当前用户所有角色) ==========
@Override
public Map<String, Set<String>> getButtonConfigVo(Long formId) {
try {
// 1. 获取当前登录用户的所有角色
Set<Long> roleIds = getCurrentUserRoleIds();
// Set<Long> roleIds = CollectionUtil.newHashSet(2080534691714174976L, 1694263314487447643L);
if (CollectionUtil.isEmpty(roleIds)) {
return buildEmptyResultForAllForms();
}
List<RoleFormButton> roleFormButtonList = mapper.selectList(new LambdaQueryWrapper<RoleFormButton>().in(RoleFormButton::getRoleId, roleIds));
if (CollectionUtil.isEmpty(roleFormButtonList)) {
return buildEmptyResultForAllForms();
}
// 2. 遍历所有角色,合并各角色在该表单的勾选按钮(取并集)
Map<String, Set<String>> roleCheckedNamesMap = new HashMap<>();
for (RoleFormButton roleFormButton : roleFormButtonList) {
if (StringUtils.isBlank(roleFormButton.getButtonConfig())) {
continue;
}
List<WidgetJsonButtonParser.ButtonInfo> buttonInfoList = JSON.parseArray(roleFormButton.getButtonConfig(), WidgetJsonButtonParser.ButtonInfo.class);
Set<String> checkedNames = buttonInfoList.stream().filter(btn -> btn.getEnabled() != null && btn.getEnabled()).map(WidgetJsonButtonParser.ButtonInfo::getName).collect(Collectors.toSet());
roleCheckedNamesMap.computeIfAbsent(Long.toString(roleFormButton.getFormId()), k -> new HashSet<>()).addAll(checkedNames);
}
return roleCheckedNamesMap;
} catch (Exception e) {
log.error("获取按钮配置失败", e);
}
return buildEmptyResultForAllForms();
}
/**
* 获取当前登录用户的所有角色ID集合 TokenData 中解析
*/
private Set<Long> getCurrentUserRoleIds() {
TokenData tokenData = TokenData.takeFromRequest();
if (tokenData == null || StrUtil.isBlank(tokenData.getRoleIds())) {
return Collections.emptySet();
}
return StrUtil.split(tokenData.getRoleIds(), ',').stream().map(Long::valueOf).collect(Collectors.toSet());
}
/**
* 构建空结果 Map查询所有已发布在线表单中有按钮的 formIdvalue 均为空 Set
* 确保前端在没有角色按钮权限配置时仍能拿到完整的 formId key 列表
*/
private Map<String, Set<String>> buildEmptyResultForAllForms() {
Map<String, Set<String>> result = new HashMap<>();
List<OnlinePage> pageList = onlinePageService.queryByEnableList();
if (CollectionUtil.isEmpty(pageList)) {
return result;
}
Set<Long> pageIdSet = pageList.stream().map(OnlinePage::getPageId).collect(Collectors.toSet());
OnlineForm filter = new OnlineForm();
filter.setPageIdSet(pageIdSet);
List<OnlineForm> formList = onlineFormService.getOnlineFormList(filter, null);
for (OnlineForm form : formList) {
result.put(form.getFormId().toString(), Collections.emptySet());
}
return result;
}
// ========== 核心:批量保存按钮配置(事务) ==========
@Override
@Transactional(rollbackFor = Exception.class)
public CallResult batchSaveButtonConfig(List<RoleFormButtonDto> dtoList) {
for (RoleFormButtonDto dto : dtoList) {
CallResult result = this.saveButtonConfig(dto);
if (!result.isSuccess()) {
return result;
}
}
return CallResult.ok();
}
// ========== 核心:保存单条按钮配置(无独立事务,由外层统一管理) ==========
@Override
public CallResult saveButtonConfig(RoleFormButtonDto dto) {
// 1. 必填校验
if (dto.getRoleId() == null) {
return CallResult.error("数据验证失败,角色ID不能为空!");
}
if (dto.getFormId() == null) {
return CallResult.error("数据验证失败,表单ID不能为空!");
}
// 2. 构建 button_config JSON
List<Map<String, Object>> configList = new ArrayList<>();
if (dto.getButtonList() != null) {
for (RoleFormButtonDto.ButtonItem item : dto.getButtonList()) {
Map<String, Object> configItem = new HashMap<>();
configItem.put("name", item.getName());
configItem.put("enabled", Objects.nonNull(item.getChecked()) && item.getChecked());
configList.add(configItem);
}
}
String buttonConfigJson = JSON.toJSONString(configList);
// 3. 查询是否已存在配置(role_id + form_id 唯一)
RoleFormButton existing =
getByRoleIdAndFormId(dto.getRoleId(), dto.getFormId());
if (existing != null) {
// 更新
existing.setButtonConfig(buttonConfigJson);
existing.setUpdateTime(new Date());
this.updateById(existing);
} else {
// 新增
RoleFormButton entity = new RoleFormButton();
entity.setRoleId(dto.getRoleId());
entity.setFormId(dto.getFormId());
entity.setButtonConfig(buttonConfigJson);
entity.setId(idGenerator.nextLongId());
this.save(entity);
}
return CallResult.ok();
}
@Override
public List<RoleFormButton> listByRoleId(Long roleId) {
LambdaQueryWrapper<RoleFormButton> qw = new LambdaQueryWrapper<>();
qw.eq(RoleFormButton::getRoleId, roleId);
return this.list(qw);
}
@Override
public RoleFormButton getByRoleIdAndFormId(Long roleId, Long formId) {
LambdaQueryWrapper<RoleFormButton> qw = new LambdaQueryWrapper<>();
qw.eq(RoleFormButton::getRoleId, roleId)
.eq(RoleFormButton::getFormId, formId);
return this.getOne(qw);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void removeByRoleId(Long roleId) {
LambdaQueryWrapper<RoleFormButton> qw = new LambdaQueryWrapper<>();
qw.eq(RoleFormButton::getRoleId, roleId);
this.remove(qw);
}
private static @NotNull Map<String, Object> getButtonResult(OnlineForm form, List<WidgetJsonButtonParser.ButtonInfo> buttons) {
Map<String, Object> item = new HashMap<>();
item.put("formId", form.getFormId().toString());
item.put("formName", form.getFormName());
List<Map<String, Object>> buttonList = new ArrayList<>();
if (!CollectionUtils.isEmpty(buttons)) {
for (WidgetJsonButtonParser.ButtonInfo btn : buttons) {
Map<String, Object> btnMap = new HashMap<>();
btnMap.put("name", btn.getName());
btnMap.put("checked", false);
buttonList.add(btnMap);
}
}
item.put("buttonList", buttonList);
return item;
}
@Override
public MyPageData<Map<String, Object>> listAllFormButtons(Long roleId, MyOrderParam orderParam, MyPageParam pageParam) {
// 1. 查询已发布的 page(不分页,获取全量)
List<OnlinePage> pageList = onlinePageService.queryByEnableList();
if (CollectionUtil.isEmpty(pageList)) {
return new MyPageData<>(Collections.emptyList(), 0L);
}
Set<Long> pageIdSet = pageList.stream().map(OnlinePage::getPageId).collect(Collectors.toSet());
// 2. 分页查询 OnlineForm
if (pageParam != null) {
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
}
String orderBy = MyOrderParam.buildOrderBy(orderParam, OnlineForm.class);
OnlineForm onlineForm = new OnlineForm();
onlineForm.setPageIdSet(pageIdSet);
List<OnlineForm> formList = onlineFormService.getOnlineFormList(onlineForm, orderBy);
long total = formList instanceof Page ? ((Page) formList).getTotal() : formList.size();
// 3. 如果传了 roleId,查询 RoleFormButton 获取勾选状态
Map<Long, Set<String>> formCheckedMap = Collections.emptyMap();
if (roleId != null) {
List<RoleFormButton> configList = this.listByRoleId(roleId);
if (CollectionUtil.isNotEmpty(configList)) {
formCheckedMap = new HashMap<>(configList.size());
for (RoleFormButton config : configList) {
formCheckedMap.put(config.getFormId(), extractCheckedNames(config));
}
}
}
// 4. 构建结果
List<Map<String, Object>> result = new ArrayList<>();
for (OnlineForm form : formList) {
List<WidgetJsonButtonParser.ButtonInfo> buttons =
WidgetJsonButtonParser.extractEnabledButtons(form.getWidgetJson());
Map<String, Object> item = getButtonResult(form, buttons);
if (roleId != null) {
Set<String> checkedNames = formCheckedMap.get(form.getFormId());
if (CollectionUtil.isNotEmpty(checkedNames)) {
applyCheckedStatus(item, checkedNames);
}
}
result.add(item);
}
return new MyPageData<>(result, total);
}
/**
* RoleFormButton.buttonConfig 中提取已勾选的按钮名称集合
*/
private Set<String> extractCheckedNames(RoleFormButton config) {
if (config.getButtonConfig() == null) {
return Collections.emptySet();
}
List<WidgetJsonButtonParser.ButtonInfo> savedButtons =
JSON.parseArray(config.getButtonConfig(), WidgetJsonButtonParser.ButtonInfo.class);
if (CollectionUtils.isEmpty(savedButtons)) {
return Collections.emptySet();
}
return savedButtons.stream()
.filter(b -> Boolean.TRUE.equals(b.getEnabled()))
.map(WidgetJsonButtonParser.ButtonInfo::getName)
.collect(Collectors.toSet());
}
/**
* 将已勾选的按钮名称集合应用到返回结果中
*/
private void applyCheckedStatus(Map<String, Object> item, Set<String> checkedNames) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> buttonList =
(List<Map<String, Object>>) item.get("buttonList");
if (buttonList != null) {
for (Map<String, Object> btn : buttonList) {
btn.put("checked", checkedNames.contains(btn.get("name")));
}
}
}
}

89
common/common-online/src/main/java/apelet/common/online/service/impl/RolePermissionSchemaServiceImpl.java

@ -0,0 +1,89 @@ @@ -0,0 +1,89 @@
package apelet.common.online.service.impl;
import apelet.common.core.base.dao.BaseDaoMapper;
import apelet.common.core.base.service.BaseService;
import apelet.common.core.object.CallResult;
import apelet.common.online.dao.RolePermissionSchemaMapper;
import apelet.common.online.model.RolePermissionSchema;
import apelet.common.online.service.IPermissionDataRuleSchemaService;
import apelet.common.online.service.IRolePermissionSchemaService;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
/**
* 角色-数据规则方案关联服务实现类
*
* @author chenchuchuan
* @date 2026-08-05
*/
@Service("rolePermissionSchemaService")
@Slf4j
public class RolePermissionSchemaServiceImpl extends BaseService<RolePermissionSchema, Long> implements IRolePermissionSchemaService {
@Autowired
private RolePermissionSchemaMapper mapper;
@Autowired
private IPermissionDataRuleSchemaService permissionDataRuleSchemaService;
@Override
protected BaseDaoMapper<RolePermissionSchema> mapper() {
return mapper;
}
@Override
public List<RolePermissionSchema> listByRoleId(Long roleId) {
LambdaQueryWrapper<RolePermissionSchema> qw = new LambdaQueryWrapper<>();
qw.eq(RolePermissionSchema::getRoleId, roleId);
return this.list(qw);
}
@Override
public List<RolePermissionSchema> listByRoleIds(Collection<Long> roleIds) {
if (CollUtil.isEmpty(roleIds)) {
return Collections.emptyList();
}
LambdaQueryWrapper<RolePermissionSchema> qw = new LambdaQueryWrapper<>();
qw.in(RolePermissionSchema::getRoleId, roleIds);
return this.list(qw);
}
@Transactional(rollbackFor = Exception.class)
@Override
public CallResult bindRoleSchemas(Long roleId, List<Long> schemaIdList) {
// 1. 校验数据规则方案是否存在
if (CollUtil.isNotEmpty(schemaIdList)) {
if (!permissionDataRuleSchemaService.existAllPrimaryKeys(new HashSet<>(schemaIdList))) {
return CallResult.error("数据验证失败,存在不合法的数据规则方案,请刷新后重试!");
}
}
// 2. 先删除该角色原有的绑定
this.removeByRoleId(roleId);
// 3. 再插入新的绑定
if (CollUtil.isNotEmpty(schemaIdList)) {
for (Long schemaId : schemaIdList) {
RolePermissionSchema roleSchema = new RolePermissionSchema();
roleSchema.setRoleId(roleId);
roleSchema.setSchemaId(schemaId);
this.save(roleSchema);
}
}
return CallResult.ok();
}
@Override
public void removeByRoleId(Long roleId) {
LambdaQueryWrapper<RolePermissionSchema> qw = new LambdaQueryWrapper<>();
qw.eq(RolePermissionSchema::getRoleId, roleId);
this.remove(qw);
}
}

87
common/common-online/src/main/java/apelet/common/online/util/WidgetFieldTypeMapping.java

@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
package apelet.common.online.util;
import java.util.HashMap;
import java.util.Map;
public class WidgetFieldTypeMapping {
private WidgetFieldTypeMapping() {
}
private static final Map<Integer, String> WIDGET_TO_FIELD_TYPE = new HashMap<>();
private static final Map<Integer, Integer> DEFAULT_LENGTH = new HashMap<>();
static {
// 文本输入框 → varchar
WIDGET_TO_FIELD_TYPE.put(1, "0");
DEFAULT_LENGTH.put(1, 255);
// 数字输入框 → int
WIDGET_TO_FIELD_TYPE.put(3, "1");
// 开关组件 → int
WIDGET_TO_FIELD_TYPE.put(5, "1");
// 滑块组件 → decimal
WIDGET_TO_FIELD_TYPE.put(6, "4");
// 单选组件 → varchar
WIDGET_TO_FIELD_TYPE.put(7, "0");
DEFAULT_LENGTH.put(7, 255);
// 复选框 → varchar
WIDGET_TO_FIELD_TYPE.put(8, "0");
DEFAULT_LENGTH.put(8, 255);
// 下拉选择框 → varchar
WIDGET_TO_FIELD_TYPE.put(10, "0");
DEFAULT_LENGTH.put(10, 255);
// 级联选择框 → varchar
WIDGET_TO_FIELD_TYPE.put(12, "0");
DEFAULT_LENGTH.put(12, 255);
// 树形选择 → varchar
WIDGET_TO_FIELD_TYPE.put(13, "0");
DEFAULT_LENGTH.put(13, 255);
// 评分组件 → int
WIDGET_TO_FIELD_TYPE.put(14, "1");
DEFAULT_LENGTH.put(14, 5);
// 日期选择框 → datetime
WIDGET_TO_FIELD_TYPE.put(20, "3");
// 颜色选择 → varchar
WIDGET_TO_FIELD_TYPE.put(30, "0");
DEFAULT_LENGTH.put(30, 50);
// 附件上传 → varchar
WIDGET_TO_FIELD_TYPE.put(31, "0");
DEFAULT_LENGTH.put(31, 255);
// 图片上传 → varchar
WIDGET_TO_FIELD_TYPE.put(653, "0");
DEFAULT_LENGTH.put(653, 255);
// 富文本编辑 → varchar(2000)
WIDGET_TO_FIELD_TYPE.put(32, "0");
DEFAULT_LENGTH.put(32, 2000);
// MD编辑器 → varchar(2000)
WIDGET_TO_FIELD_TYPE.put(760, "0");
DEFAULT_LENGTH.put(760, 2000);
// 用户选择 → bigint
WIDGET_TO_FIELD_TYPE.put(400, "2");
// 部门选择 → bigint
WIDGET_TO_FIELD_TYPE.put(401, "2");
// 关联选择 → bigint
WIDGET_TO_FIELD_TYPE.put(402, "2");
// 时间选择器 → datetime
WIDGET_TO_FIELD_TYPE.put(652, "3");
// 文件上传 → varchar
WIDGET_TO_FIELD_TYPE.put(750, "0");
DEFAULT_LENGTH.put(750, 255);
}
public static String getFieldTypeId(Integer widgetType) {
String type = WIDGET_TO_FIELD_TYPE.get(widgetType);
if (type == null) {
throw new IllegalArgumentException("不支持的控件类型: " + widgetType);
}
return type;
}
public static Integer getDefaultLength(Integer widgetType) {
return DEFAULT_LENGTH.get(widgetType);
}
public static boolean isDataWidget(Integer widgetType) {
return WIDGET_TO_FIELD_TYPE.containsKey(widgetType);
}
}

93
common/common-online/src/main/java/apelet/common/online/util/WidgetJsonButtonParser.java

@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
package apelet.common.online.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* widgetJson 按钮解析工具类
* 从在线表单的 widgetJson 中提取所有 operationList 下的按钮
*
* @author chenchuchuan
* @date 2026-07-17
*/
@Slf4j
public class WidgetJsonButtonParser {
/**
* 按钮信息DTO内部类轻量
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ButtonInfo {
/** 按钮名称 */
private String name;
/** 是否在设计器中开启 */
private Boolean enabled;
}
/**
* widgetJson 中提取所有开启的按钮去重按name
*
* @param widgetJson 在线表单的 widget_json 字段值
* @return 按钮列表只包含 enabled=true 的按钮 name 去重
*/
public static List<ButtonInfo> extractEnabledButtons(String widgetJson) {
List<ButtonInfo> allButtons = extractAllButtons(widgetJson);
List<ButtonInfo> result = new ArrayList<>();
Set<String> seen = new HashSet<>();
for (ButtonInfo btn : allButtons) {
if (Boolean.TRUE.equals(btn.getEnabled()) && seen.add(btn.getName())) {
result.add(btn);
}
}
return result;
}
/**
* widgetJson 中提取所有按钮不去重不过滤
* 仅提取 pc.operationList mobile.operationList 两个顶层数组
* 不递归 tableWidgetwidgetListchildWidgetList 等子节点下的 operationList
*
* @param widgetJson 在线表单的 widget_json 字段值
* @return 全部按钮列表
*/
public static List<ButtonInfo> extractAllButtons(String widgetJson) {
List<ButtonInfo> result = new ArrayList<>();
if (widgetJson == null || widgetJson.isEmpty()) {
return result;
}
try {
JSONObject root = JSON.parseObject(widgetJson);
// 仅取 pc 和 mobile 两端的顶层 operationList
for (String device : new String[]{"pc", "mobile"}) {
JSONObject deviceJson = root.getJSONObject(device);
if (deviceJson == null) continue;
JSONArray operationList = deviceJson.getJSONArray("operationList");
if (operationList != null) {
for (int i = 0; i < operationList.size(); i++) {
JSONObject btn = operationList.getJSONObject(i);
String name = btn.getString("name");
Boolean enabled = btn.getBoolean("enabled");
if (name != null) {
result.add(new ButtonInfo(name, enabled != null ? enabled : false));
}
}
}
}
} catch (Exception e) {
log.error("解析 widgetJson 失败", e);
}
return result;
}
}

410
common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java

@ -0,0 +1,410 @@ @@ -0,0 +1,410 @@
package apelet.common.online.util;
import apelet.common.core.constant.AppDeviceType;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
/**
* widgetJson 数据操作工具类
* 提供从在线表单 widgetJson 中提取字典绑定字段子表控件等信息的纯数据操作方法
*
* @author chenchuchuan
* @date 2026-08-06
*/
public class WidgetJsonUtil {
private WidgetJsonUtil() {
}
/**
* widgetJson 中提取所有控件绑定的字典Id
* 仅遍历当前设备端(widgetList childWidgetList)控件树中 props.dictInfo.dictId
*
* @param widgetJson 在线表单的 widgetJson
* @return 字典Id集合
*/
public static Set<Long> extractDictIdSet(String widgetJson) {
Set<Long> dictIdSet = new HashSet<>();
if (StrUtil.isBlank(widgetJson)) {
return dictIdSet;
}
JSONObject deviceData = JSON.parseObject(widgetJson).getJSONObject(AppDeviceType.getDeviceType());
if (MapUtil.isEmpty(deviceData)) {
return dictIdSet;
}
JSONArray widgetListArray = deviceData.getJSONArray("widgetList");
if (CollUtil.isEmpty(widgetListArray)) {
return dictIdSet;
}
for (int i = 0; i < widgetListArray.size(); i++) {
extractDictIdRecursively(widgetListArray.getJSONObject(i), dictIdSet);
}
return dictIdSet;
}
/**
* 递归提取单个控件及其子控件中的字典Id
*/
private static void extractDictIdRecursively(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++) {
extractDictIdRecursively(childWidgetArray.getJSONObject(i), dictIdSet);
}
}
}
/**
* 收集子表控件(widgetType=100) bindData.tableName 的名称集合
*
* @param widgetJson 在线表单的 widgetJson
* @return 子表名称集合
*/
public static Set<String> collectSubTableNames(JSONObject widgetJson) {
Set<String> tableNameSet = new HashSet<>();
walkAllModes(widgetJson, widget -> {
JSONArray widgetList = widget.getJSONArray("widgetList");
if (widgetList == null) {
return;
}
for (int i = 0; i < widgetList.size(); i++) {
JSONObject childWidget = widgetList.getJSONObject(i);
// 只处理 widgetType=100 的子表控件
Integer widgetType = childWidget.getInteger("widgetType");
if (widgetType == null || widgetType != 100) {
continue;
}
JSONObject bindData = childWidget.getJSONObject("bindData");
if (bindData == null) {
continue;
}
if (bindData.containsKey("tableName")) {
tableNameSet.add(bindData.getString("tableName"));
}
}
});
return tableNameSet;
}
/**
* 收集需要新增的子表名称widgetType=100 bindData 只有 tableName tableId的表
* 新增表下面通常不存在需要处理的字段故不注册进字段 map collectTableColumns
* 仅在此单独收集用于建表递归遍历 childWidgetList buildTableFieldMap 取值保持一致
*
* @param widgetJson 在线表单的 widgetJson
* @return 新增子表名称集合
*/
public static Set<String> collectNewSubTableNames(JSONObject widgetJson) {
Set<String> tableNameSet = new HashSet<>();
if (widgetJson == null) return tableNameSet;
for (String mode : new String[]{"pc", "mobile"}) {
JSONObject modeObj = widgetJson.getJSONObject(mode);
if (modeObj == null) continue;
collectNewSubTableNames(modeObj.getJSONArray("widgetList"), tableNameSet);
JSONObject tableWidget = modeObj.getJSONObject("tableWidget");
if (tableWidget != null) collectNewSubTableNames(tableWidget.getJSONArray("childWidgetList"), tableNameSet);
}
return tableNameSet;
}
private static void collectNewSubTableNames(JSONArray widgetList, Set<String> tableNameSet) {
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 == 100) {
JSONObject bindData = widget.getJSONObject("bindData");
if (bindData != null && StrUtil.isEmpty(bindData.getString("tableId"))) {
String tableName = bindData.getString("tableName");
if (StrUtil.isNotEmpty(tableName)) tableNameSet.add(tableName);
}
continue;
}
JSONArray childWidgetList = widget.getJSONArray("childWidgetList");
if (CollUtil.isNotEmpty(childWidgetList)) collectNewSubTableNames(childWidgetList, tableNameSet);
}
}
/**
* 统一收集 pc/mobile 控件树中所有"表-字段"绑定关系
* <p>
* 返回嵌套 Map外层 key = 表标识bindData.tableId缺省用 bindData.tableName用于尚未创建的新表
* 内层 key = 字段名value = 对应的控件对象叶子字段控件 子表列对象
* <p>
* 收集规则
* <ul>
* <li>widgetType=100子表组件 props.tableColumnList 各列字段名取 showFieldName兜底 showName/columnId跳过 fieldType=1 的序号/合计列</li>
* <li>widgetType=102移动端子表组件跳过由移动端子表专用逻辑处理避免重复</li>
* <li>容器组件childWidgetList 非空递归收集</li>
* <li>叶子控件 bindData.columnName bindData.tableId/tableName</li>
* </ul>
* mobile 兼容两种结构widgetList tableWidget childWidgetList
*
* @param widgetJson 在线表单的 widgetJson
* @return 表标识 (字段名 控件对象) 的嵌套映射
*/
public static Map<String, Map<String, JSONObject>> buildTableFieldMap(JSONObject widgetJson) {
Map<String, Map<String, JSONObject>> tableFieldMap = new HashMap<>();
if (widgetJson == null) return tableFieldMap;
for (String mode : new String[]{"pc", "mobile"}) {
JSONObject modeObj = widgetJson.getJSONObject(mode);
if (modeObj == null) continue;
// 新结构:widgetList(递归收集)
collectTableFields(modeObj.getJSONArray("widgetList"), tableFieldMap);
// mobile 老结构兜底:tableWidget -> childWidgetList -> childWidgetList
JSONObject tableWidget = modeObj.getJSONObject("tableWidget");
if (tableWidget != null) collectTableFields(tableWidget.getJSONArray("childWidgetList"), tableFieldMap);
}
return tableFieldMap;
}
/**
* 递归收集控件树中的"表-字段"绑定关系
*/
private static void collectTableFields(JSONArray widgetList, Map<String, Map<String, JSONObject>> tableFieldMap) {
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 == 100) {
collectTableColumns(widget, tableFieldMap);
continue;
}
if (widgetType != null && widgetType == 102) continue;
JSONArray childWidgetList = widget.getJSONArray("childWidgetList");
if (CollUtil.isNotEmpty(childWidgetList)) {
collectTableFields(childWidgetList, tableFieldMap);
continue;
}
JSONObject bindData = widget.getJSONObject("bindData");
if (bindData == null) continue;
// 字段名优先取 bindData.columnName,兼容仅绑定 columnFieldName 的移动端字段
String columnName = bindData.getString("columnName");
if (StrUtil.isEmpty(columnName)) columnName = bindData.getString("columnFieldName");
String tableKey = resolveTableKey(bindData);
if (StrUtil.isEmpty(columnName) || StrUtil.isEmpty(tableKey)) continue;
tableFieldMap.computeIfAbsent(tableKey, k -> new HashMap<>()).put(columnName, widget);
}
}
/**
* 提取子表组件(widgetType=100) props.tableColumnList 中各列
*/
private static void collectTableColumns(JSONObject tableWidget, Map<String, Map<String, JSONObject>> tableFieldMap) {
JSONObject bindData = tableWidget.getJSONObject("bindData");
if (bindData == null) {
return;
}
String tableKey = resolveTableKey(bindData);
// 新增表(无 tableId、只有 tableName)下面不会有需要处理的字段,不注册进字段 map,由建表逻辑单独收集
if (StrUtil.isEmpty(tableKey) || !tableKey.matches("\\d+")) {
return;
}
Map<String, JSONObject> columnMap = tableFieldMap.computeIfAbsent(tableKey, k -> new HashMap<>());
JSONObject props = tableWidget.getJSONObject("props");
if (props == null) {
return;
}
JSONArray tableColumnList = props.getJSONArray("tableColumnList");
if (CollUtil.isEmpty(tableColumnList)) {
return;
}
for (int i = 0; i < tableColumnList.size(); i++) {
JSONObject tableColumn = tableColumnList.getJSONObject(i);
Integer fieldType = tableColumn.getInteger("fieldType");
if (fieldType != null && fieldType == 1) continue;
String columnIdStr = tableColumn.getString("columnId");
String columnName;
if (StrUtil.isNotEmpty(columnIdStr) && !columnIdStr.matches("\\d+")) {
// 新增列:字段名存在 columnId 里(非数字),描述在 showName
columnName = columnIdStr;
} else {
// 已有列:字段名在 showFieldName,兜底 showName
columnName = tableColumn.getString("showFieldName");
if (StrUtil.isEmpty(columnName)) columnName = tableColumn.getString("showName");
}
if (StrUtil.isEmpty(columnName)) continue;
columnMap.put(columnName, tableColumn);
}
}
/**
* 解析控件所属表标识优先 bindData.tableId缺省用 bindData.tableName
*/
private static String resolveTableKey(JSONObject bindData) {
String tableId = bindData.getString("tableId");
if (StrUtil.isNotEmpty(tableId)) {
return tableId;
}
return bindData.getString("tableName");
}
/**
* 回填新列 columnId widgetJson 中所有归属该表且字段名匹配的控件
* <p>
* 覆盖两类控件叶子字段bindData.columnName/columnFieldName == columnName
* 子表列widgetType=100 组件 props.tableColumnList showFieldName/showName == columnName
* 归属表用 tableId / tableName 双重校验避免同名列误回填
*
* @param widgetJson widgetJson 根对象
* @param columnName 字段名
* @param columnId 新列 id
* @param tableId 所属表 id
* @param tableName 所属表名
*/
public static void backfillColumnId(JSONObject widgetJson, String columnName, long columnId, Long tableId, String tableName) {
if (widgetJson == null) return;
for (String mode : new String[]{"pc", "mobile"}) {
JSONObject modeObj = widgetJson.getJSONObject(mode);
if (modeObj == null) continue;
backfillInWidgets(modeObj.getJSONArray("widgetList"), columnName, columnId, tableId, tableName);
JSONObject tableWidget = modeObj.getJSONObject("tableWidget");
if (tableWidget != null) backfillInWidgets(tableWidget.getJSONArray("childWidgetList"), columnName, columnId, tableId, tableName);
}
}
private static void backfillInWidgets(JSONArray widgetList, String columnName, long columnId, Long tableId, String tableName) {
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 == 100) {
backfillTableColumns(widget, columnName, columnId, tableId, tableName);
continue;
}
JSONArray childWidgetList = widget.getJSONArray("childWidgetList");
if (CollUtil.isNotEmpty(childWidgetList)) backfillInWidgets(childWidgetList, columnName, columnId, tableId, tableName);
JSONObject bindData = widget.getJSONObject("bindData");
if (bindData == null) continue;
String boundColumnName = bindData.getString("columnName");
if (StrUtil.isEmpty(boundColumnName)) boundColumnName = bindData.getString("columnFieldName");
if (!columnName.equals(boundColumnName)) continue;
String widgetTableKey = resolveTableKey(bindData);
if (!tableMatched(widgetTableKey, tableId, tableName)) continue;
bindData.put("columnId", columnId);
bindData.put("tableId", tableId);
bindData.put("columnFieldName", columnName);
}
}
private static void backfillTableColumns(JSONObject tableWidget, String columnName, long columnId, Long tableId, String tableName) {
JSONObject bindData = tableWidget.getJSONObject("bindData");
JSONObject props = tableWidget.getJSONObject("props");
if (bindData == null || props == null) return;
String widgetTableKey = resolveTableKey(bindData);
if (!tableMatched(widgetTableKey, tableId, tableName)) return;
JSONArray tableColumnList = props.getJSONArray("tableColumnList");
if (CollUtil.isEmpty(tableColumnList)) return;
for (int i = 0; i < tableColumnList.size(); i++) {
JSONObject tableColumn = tableColumnList.getJSONObject(i);
String colIdStr = tableColumn.getString("columnId");
String boundColumnName;
if (StrUtil.isNotEmpty(colIdStr) && !colIdStr.matches("\\d+")) {
// 新增列:字段名存在 columnId 里(非数字)
boundColumnName = colIdStr;
} else {
boundColumnName = tableColumn.getString("showFieldName");
if (StrUtil.isEmpty(boundColumnName)) boundColumnName = tableColumn.getString("showName");
}
if (columnName.equals(boundColumnName)) {
// 回写:columnId 写真实 id,原 columnId 值(字段名)移到 showFieldName
tableColumn.put("columnId", columnId);
tableColumn.put("showFieldName", columnName);
}
}
}
private static boolean tableMatched(String widgetTableKey, Long tableId, String tableName) {
if (StrUtil.isEmpty(widgetTableKey)) return false;
if (widgetTableKey.equals(String.valueOf(tableId))) return true;
return StrUtil.isNotEmpty(tableName) && widgetTableKey.equalsIgnoreCase(tableName);
}
/**
* widgetJson 中查找匹配 tableName 的子表控件(widgetType=100)回填 bindData.tableId
*
* @param widgetJson 在线表单的 widgetJson
* @param tableName 子表名称
* @param tableId 子表Id
*/
public static void backfillTableId(JSONObject widgetJson, String tableName, Long tableId) {
if (widgetJson == null) return;
for (String mode : new String[]{"pc", "mobile"}) {
JSONObject modeObj = widgetJson.getJSONObject(mode);
if (modeObj == null) continue;
backfillTableIdInWidgets(modeObj.getJSONArray("widgetList"), tableName, tableId);
JSONObject tableWidget = modeObj.getJSONObject("tableWidget");
if (tableWidget != null) {
backfillTableIdInWidgets(tableWidget.getJSONArray("childWidgetList"), tableName, tableId);
}
}
}
private static void backfillTableIdInWidgets(JSONArray widgetList, String tableName, Long tableId) {
if (CollUtil.isEmpty(widgetList)) return;
for (int i = 0; i < widgetList.size(); i++) {
JSONObject widget = widgetList.getJSONObject(i);
if (widget == null) continue;
if (widget.getInteger("widgetType") != null && 100 == widget.getInteger("widgetType")) {
JSONObject bindData = widget.getJSONObject("bindData");
if (bindData != null && tableName.equals(bindData.getString("tableName"))) {
bindData.put("tableId", tableId);
}
continue;
}
JSONArray childWidgetList = widget.getJSONArray("childWidgetList");
if (CollUtil.isNotEmpty(childWidgetList)) backfillTableIdInWidgets(childWidgetList, tableName, tableId);
}
}
/**
* 遍历 "pc" "mobile" 两个模式的控件树
*/
private static void walkAllModes(JSONObject widgetJson, Consumer<JSONObject> visitor) {
for (String mode : new String[]{"pc", "mobile"}) {
JSONObject modeObj = widgetJson.getJSONObject(mode);
if (modeObj != null) {
walkWidgets(modeObj, visitor);
}
}
}
/**
* 递归遍历控件树访问每个控件及其子控件(widgetList)
*/
private static void walkWidgets(JSONObject node, Consumer<JSONObject> visitor) {
if (node == null) {
return;
}
visitor.accept(node);
JSONArray children = node.getJSONArray("widgetList");
if (children != null) {
for (int i = 0; i < children.size(); i++) {
walkWidgets(children.getJSONObject(i), visitor);
}
}
}
}

19
common/common-online/src/main/java/apelet/common/online/vo/PermissionDataRuleSchemaVo.java

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
package apelet.common.online.vo;
import apelet.common.online.model.PermissionDataRuleSchema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* 权限数据规则方案表 VO
*
* @author chenchuchuan
* @date 2026-07-15
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class PermissionDataRuleSchemaVo extends PermissionDataRuleSchema implements Serializable {
private static final long serialVersionUID = 1L;
}

17
common/common-online/src/main/java/apelet/common/online/vo/RoleFormButtonVo.java

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
package apelet.common.online.vo;
import apelet.common.online.model.RoleFormButton;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 角色-表单按钮权限配置VO对象
*
* @author chenchuchuan
* @date 2026-07-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RoleFormButtonVo extends RoleFormButton {
private static final long serialVersionUID = 1L;
}

3
common/common-tenant-online/src/main/java/apelet/common/tenant/online/controller/TenantOnlinePageController.java

@ -8,6 +8,7 @@ import apelet.common.online.controller.OnlinePageController; @@ -8,6 +8,7 @@ import apelet.common.online.controller.OnlinePageController;
import apelet.common.online.dto.OnlineDatasourceDto;
import apelet.common.online.dto.OnlinePageDatasourceDto;
import apelet.common.online.dto.OnlinePageDto;
import apelet.common.online.model.OnlinePage;
import apelet.common.online.vo.OnlineDatasourceVo;
import apelet.common.online.vo.OnlinePageDatasourceVo;
import apelet.common.online.vo.OnlinePageVo;
@ -43,7 +44,7 @@ public class TenantOnlinePageController extends OnlinePageController { @@ -43,7 +44,7 @@ public class TenantOnlinePageController extends OnlinePageController {
@OperationLog(type = SysOperationLogType.ADD)
@PostMapping("/add")
@Override
public ResponseResult<Long> add(@MyRequestBody OnlinePageDto onlinePageDto) {
public ResponseResult<OnlinePage> add(@MyRequestBody OnlinePageDto onlinePageDto) {
return super.add(onlinePageDto);
}

Loading…
Cancel
Save