Compare commits

..

2 Commits

  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. 64
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/SysRoleController.java
  4. 119
      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. 23
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/SysRoleService.java
  10. 36
      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. 62
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/service/impl/SysRoleServiceImpl.java
  13. 120
      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. 6
      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. 121
      common/common-core/pom.xml
  21. 31
      common/common-core/src/main/java/apelet/common/core/advice/MyControllerAdvice.java
  22. 167
      common/common-core/src/main/java/apelet/common/core/advice/MyExceptionHandler.java
  23. 16
      common/common-core/src/main/java/apelet/common/core/annotation/DeptFilterColumn.java
  24. 17
      common/common-core/src/main/java/apelet/common/core/annotation/DisableDataFilter.java
  25. 28
      common/common-core/src/main/java/apelet/common/core/annotation/DisableTenantFilter.java
  26. 35
      common/common-core/src/main/java/apelet/common/core/annotation/EnableDataPerm.java
  27. 16
      common/common-core/src/main/java/apelet/common/core/annotation/FlowLatestApprovalStatusColumn.java
  28. 16
      common/common-core/src/main/java/apelet/common/core/annotation/FlowStatusColumn.java
  29. 16
      common/common-core/src/main/java/apelet/common/core/annotation/JobUpdateTimeColumn.java
  30. 50
      common/common-core/src/main/java/apelet/common/core/annotation/MaskField.java
  31. 18
      common/common-core/src/main/java/apelet/common/core/annotation/MultiDatabaseWriteMethod.java
  32. 21
      common/common-core/src/main/java/apelet/common/core/annotation/MyDataSource.java
  33. 35
      common/common-core/src/main/java/apelet/common/core/annotation/MyDataSourceResolver.java
  34. 23
      common/common-core/src/main/java/apelet/common/core/annotation/MyRequestBody.java
  35. 15
      common/common-core/src/main/java/apelet/common/core/annotation/NoAuthInterface.java
  36. 29
      common/common-core/src/main/java/apelet/common/core/annotation/RelationConstDict.java
  37. 77
      common/common-core/src/main/java/apelet/common/core/annotation/RelationDict.java
  38. 29
      common/common-core/src/main/java/apelet/common/core/annotation/RelationGlobalDict.java
  39. 39
      common/common-core/src/main/java/apelet/common/core/annotation/RelationManyToMany.java
  40. 102
      common/common-core/src/main/java/apelet/common/core/annotation/RelationManyToManyAggregation.java
  41. 53
      common/common-core/src/main/java/apelet/common/core/annotation/RelationOneToMany.java
  42. 74
      common/common-core/src/main/java/apelet/common/core/annotation/RelationOneToManyAggregation.java
  43. 67
      common/common-core/src/main/java/apelet/common/core/annotation/RelationOneToOne.java
  44. 16
      common/common-core/src/main/java/apelet/common/core/annotation/TenantFilterColumn.java
  45. 24
      common/common-core/src/main/java/apelet/common/core/annotation/UploadFlagColumn.java
  46. 16
      common/common-core/src/main/java/apelet/common/core/annotation/UserFilterColumn.java
  47. 48
      common/common-core/src/main/java/apelet/common/core/aop/DataSourceAspect.java
  48. 59
      common/common-core/src/main/java/apelet/common/core/aop/DataSourceResolveAspect.java
  49. 188
      common/common-core/src/main/java/apelet/common/core/base/client/BaseClient.java
  50. 110
      common/common-core/src/main/java/apelet/common/core/base/client/BaseFallbackFactory.java
  51. 520
      common/common-core/src/main/java/apelet/common/core/base/controller/BaseController.java
  52. 87
      common/common-core/src/main/java/apelet/common/core/base/dao/BaseDaoMapper.java
  53. 126
      common/common-core/src/main/java/apelet/common/core/base/mapper/BaseModelMapper.java
  54. 58
      common/common-core/src/main/java/apelet/common/core/base/mapper/DummyModelMapper.java
  55. 40
      common/common-core/src/main/java/apelet/common/core/base/model/BaseModel.java
  56. 230
      common/common-core/src/main/java/apelet/common/core/base/service/BaseDictService.java
  57. 3243
      common/common-core/src/main/java/apelet/common/core/base/service/BaseService.java
  58. 69
      common/common-core/src/main/java/apelet/common/core/base/service/IBaseDictService.java
  59. 614
      common/common-core/src/main/java/apelet/common/core/base/service/IBaseService.java
  60. 35
      common/common-core/src/main/java/apelet/common/core/base/vo/BaseVo.java
  61. 111
      common/common-core/src/main/java/apelet/common/core/cache/CacheConfig.java
  62. 60
      common/common-core/src/main/java/apelet/common/core/cache/CacheKey.java
  63. 89
      common/common-core/src/main/java/apelet/common/core/cache/DictionaryCache.java
  64. 200
      common/common-core/src/main/java/apelet/common/core/cache/MapDictionaryCache.java
  65. 138
      common/common-core/src/main/java/apelet/common/core/cache/MapTreeDictionaryCache.java
  66. 60
      common/common-core/src/main/java/apelet/common/core/config/BaseMultiDataSourceConfig.java
  67. 87
      common/common-core/src/main/java/apelet/common/core/config/CommonWebMvcConfig.java
  68. 83
      common/common-core/src/main/java/apelet/common/core/config/CoreProperties.java
  69. 52
      common/common-core/src/main/java/apelet/common/core/config/DataSourceContextHolder.java
  70. 41
      common/common-core/src/main/java/apelet/common/core/config/DataSourceInfo.java
  71. 170
      common/common-core/src/main/java/apelet/common/core/config/DynamicDataSource.java
  72. 20
      common/common-core/src/main/java/apelet/common/core/config/EncryptConfig.java
  73. 69
      common/common-core/src/main/java/apelet/common/core/config/FeignConfig.java
  74. 21
      common/common-core/src/main/java/apelet/common/core/config/MybatisPlusKeyGeneratorConfig.java
  75. 64
      common/common-core/src/main/java/apelet/common/core/config/RestTemplateConfig.java
  76. 39
      common/common-core/src/main/java/apelet/common/core/config/TomcatConfig.java
  77. 36
      common/common-core/src/main/java/apelet/common/core/constant/AggregationKind.java
  78. 81
      common/common-core/src/main/java/apelet/common/core/constant/AggregationType.java
  79. 68
      common/common-core/src/main/java/apelet/common/core/constant/AppDeviceType.java
  80. 153
      common/common-core/src/main/java/apelet/common/core/constant/ApplicationConstant.java
  81. 40
      common/common-core/src/main/java/apelet/common/core/constant/BooleanEnum.java
  82. 81
      common/common-core/src/main/java/apelet/common/core/constant/DataPermRuleType.java
  83. 64
      common/common-core/src/main/java/apelet/common/core/constant/DictType.java
  84. 92
      common/common-core/src/main/java/apelet/common/core/constant/ErrorCodeEnum.java
  85. 127
      common/common-core/src/main/java/apelet/common/core/constant/FieldFilterType.java
  86. 54
      common/common-core/src/main/java/apelet/common/core/constant/FilterParamType.java
  87. 25
      common/common-core/src/main/java/apelet/common/core/constant/GlobalDeletedFlag.java
  88. 47
      common/common-core/src/main/java/apelet/common/core/constant/MaskFieldTypeEnum.java
  89. 26
      common/common-core/src/main/java/apelet/common/core/constant/ObjectFieldType.java
  90. 22
      common/common-core/src/main/java/apelet/common/core/constant/UserFilterGroup.java
  91. 26
      common/common-core/src/main/java/apelet/common/core/exception/DataValidationException.java
  92. 19
      common/common-core/src/main/java/apelet/common/core/exception/IllegalConversionException.java
  93. 30
      common/common-core/src/main/java/apelet/common/core/exception/InvalidClassFieldException.java
  94. 30
      common/common-core/src/main/java/apelet/common/core/exception/InvalidDataFieldException.java
  95. 27
      common/common-core/src/main/java/apelet/common/core/exception/InvalidDataModelException.java
  96. 24
      common/common-core/src/main/java/apelet/common/core/exception/InvalidDblinkTypeException.java
  97. 27
      common/common-core/src/main/java/apelet/common/core/exception/InvalidRedisModeException.java
  98. 20
      common/common-core/src/main/java/apelet/common/core/exception/MapCacheAccessException.java
  99. 46
      common/common-core/src/main/java/apelet/common/core/exception/MyRuntimeException.java
  100. 26
      common/common-core/src/main/java/apelet/common/core/exception/NoDataAffectException.java
  101. Some files were not shown because too many files have changed in this diff Show More

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

@ -8,10 +8,7 @@ import apelet.common.core.annotation.NoAuthInterface; @@ -8,10 +8,7 @@ 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.BaseUpDownloader;
import apelet.common.core.upload.UpDownloaderFactory;
import apelet.common.core.upload.UploadResponseInfo;
import apelet.common.core.upload.UploadStoreInfo;
import apelet.common.core.upload.*;
import apelet.common.core.util.*;
import apelet.common.datafilter.config.DataFilterProperties;
import apelet.common.eas.util.EasConfig;
@ -24,19 +21,13 @@ import apelet.common.mobile.model.MobileEntry; @@ -24,19 +21,13 @@ 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.Filter;
import apelet.common.orm.impl.FilterItem;
import apelet.common.orm.impl.Selector;
import apelet.common.orm.impl.SelectorItem;
import apelet.common.orm.impl.*;
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.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.model.constant.*;
import apelet.tenantadmin.upms.service.*;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
@ -78,7 +69,7 @@ import java.nio.charset.StandardCharsets; @@ -78,7 +69,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;
/**
* 登录接口控制器类
*
@ -1260,10 +1251,9 @@ public class LoginController { @@ -1260,10 +1251,9 @@ 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).filter(Objects::nonNull).collect(Collectors.toSet());
Set<Long> userRoleIdSet = userRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toSet());
tokenData.setRoleIds(StrUtil.join(",", userRoleIdSet));
}
return tokenData;
@ -1291,10 +1281,9 @@ public class LoginController { @@ -1291,10 +1281,9 @@ 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).filter(Objects::nonNull).collect(Collectors.toSet());
Set<Long> userRoleIdSet = userRoleList.stream().map(SysUserRole::getRoleId).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,10 +29,7 @@ import org.springdoc.api.annotations.ParameterObject; @@ -29,10 +29,7 @@ import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -148,9 +145,6 @@ public class SysDeptController { @@ -148,9 +145,6 @@ 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));
}

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

@ -9,8 +9,6 @@ import apelet.common.core.util.MyPageUtil; @@ -9,8 +9,6 @@ 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.service.IRoleFormButtonService;
import apelet.tenantadmin.upms.dto.SysRoleDto;
import apelet.tenantadmin.upms.dto.SysUserDto;
import apelet.tenantadmin.upms.model.SysRole;
@ -18,7 +16,6 @@ import apelet.tenantadmin.upms.model.SysUser; @@ -18,7 +16,6 @@ import apelet.tenantadmin.upms.model.SysUser;
import apelet.tenantadmin.upms.model.SysUserRole;
import apelet.tenantadmin.upms.service.SysRoleService;
import apelet.tenantadmin.upms.service.SysUserService;
import apelet.tenantadmin.upms.vo.SysDeptVo;
import apelet.tenantadmin.upms.vo.SysRoleVo;
import apelet.tenantadmin.upms.vo.SysUserVo;
import com.alibaba.fastjson.TypeReference;
@ -51,24 +48,19 @@ public class SysRoleController { @@ -51,24 +48,19 @@ public class SysRoleController {
private SysRoleService sysRoleService;
@Autowired
private SysUserService sysUserService;
@Autowired
private IRoleFormButtonService roleFormButtonService;
/**
* 新增角色操作
*
* @param sysRoleDto 新增角色对象
* @param menuIdListString 与当前角色Id绑定的menuId列表多个menuId之间逗号分隔
* @param buttonConfigList 按钮权限配置列表可选不传不影响原有逻辑
* @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 List<RoleFormButtonDto> buttonConfigList) {
@MyRequestBody SysRoleDto sysRoleDto, @MyRequestBody String menuIdListString) {
String errorMessage = MyCommonUtil.getModelValidationError(sysRoleDto);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
@ -83,11 +75,6 @@ public class SysRoleController { @@ -83,11 +75,6 @@ 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);
}
return ResponseResult.success(sysRole.getRoleId());
}
@ -96,15 +83,12 @@ public class SysRoleController { @@ -96,15 +83,12 @@ public class SysRoleController {
*
* @param sysRoleDto 更新角色对象
* @param menuIdListString 与当前角色Id绑定的menuId列表多个menuId之间逗号分隔
* @param buttonConfigList 按钮权限配置列表可选不传不影响原有逻辑
* @return 应答结果对象
*/
@OperationLog(type = SysOperationLogType.UPDATE)
@PostMapping("/update")
public ResponseResult<Void> update(
@MyRequestBody SysRoleDto sysRoleDto,
@MyRequestBody String menuIdListString,
@MyRequestBody List<RoleFormButtonDto> buttonConfigList) {
@MyRequestBody SysRoleDto sysRoleDto, @MyRequestBody String menuIdListString) {
String errorMessage = MyCommonUtil.getModelValidationError(sysRoleDto, Default.class, UpdateGroup.class);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
@ -127,11 +111,6 @@ public class SysRoleController { @@ -127,11 +111,6 @@ 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);
}
return ResponseResult.success();
}
@ -194,11 +173,9 @@ public class SysRoleController { @@ -194,11 +173,9 @@ 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);
// 填充关联的组织列表(多对多,仅需要deptId和deptName)
sysRoleVo.setDeptList(sysRoleService.getRoleDept(roleId));
return ResponseResult.success(sysRoleVo);
}
@ -372,39 +349,4 @@ public class SysRoleController { @@ -372,39 +349,4 @@ 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);
}
}

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

@ -4,41 +4,45 @@ import apelet.common.core.annotation.MyRequestBody; @@ -4,41 +4,45 @@ 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.BaseUpDownloader;
import apelet.common.core.upload.UpDownloaderFactory;
import apelet.common.core.upload.UploadResponseInfo;
import apelet.common.core.upload.UploadStoreInfo;
import apelet.common.core.upload.*;
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.TypeReference;
import com.alibaba.fastjson.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 用户管理操作控制器类
@ -70,7 +74,6 @@ public class SysUserController { @@ -70,7 +74,6 @@ public class SysUserController {
* @param deptPostIdListString 逗号分隔的部门岗位Id列表
* @param dataPermIdListString 逗号分隔的数据权限Id列表
* @param roleIdListString 逗号分隔的角色Id列表
* @param deptIdListString 逗号分隔的组织Id列表可选写入SysUserRole.deptId
* @return 应答结果对象包含新增用户的主键Id
*/
@ApiOperationSupport(ignoreParameters = {
@ -83,26 +86,24 @@ public class SysUserController { @@ -83,26 +86,24 @@ public class SysUserController {
@MyRequestBody SysUserDto sysUserDto,
@MyRequestBody String deptPostIdListString,
@MyRequestBody String dataPermIdListString,
@MyRequestBody String roleIdListString,
@MyRequestBody String deptIdListString) {
@MyRequestBody String roleIdListString) {
String errorMessage = MyCommonUtil.getModelValidationError(sysUserDto);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
}
SysUser sysUser = MyModelUtil.copyTo(sysUserDto, SysUser.class);
CallResult result = sysUserService.verifyRelatedData(
sysUser, null, roleIdListString, deptPostIdListString, dataPermIdListString, deptIdListString);
sysUser, null, roleIdListString, deptPostIdListString, dataPermIdListString);
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> deptIdSet = null;
if (result.getData().containsKey("deptIdSet")) {
deptIdSet = result.getData().getObject("deptIdSet", new TypeReference<Set<Long>>(){});
}
sysUserService.saveNew(sysUser, roleIdSet, deptPostIdSet, dataPermIdSet, deptIdSet);
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());
}
@ -126,14 +127,17 @@ public class SysUserController { @@ -126,14 +127,17 @@ public class SysUserController {
}
SysUser sysUser = MyModelUtil.copyTo(sysUserDto, SysUser.class);
CallResult result = sysUserService.verifyRelatedData(
sysUser, null, "1801273688285270000", "1821796396504723500", "1821788867364139000", null);
sysUser, null, "1801273688285270000", "1821796396504723500", "1821788867364139000");
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>>(){});
SysUser sysUserO = sysUserService.saveNew(sysUser, roleIdSet, deptPostIdSet, dataPermIdSet, null);
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 {
ObjectValue userInfoObj = new ObjectValue("sys_user_info");
@ -160,7 +164,6 @@ public class SysUserController { @@ -160,7 +164,6 @@ public class SysUserController {
* @param deptPostIdListString 逗号分隔的部门岗位Id列表
* @param dataPermIdListString 逗号分隔的数据权限Id列表
* @param roleIdListString 逗号分隔的角色Id列表
* @param deptIdListString 逗号分隔的组织Id列表可选写入SysUserRole.deptId
* @return 应答结果对象
*/
@ApiOperationSupport(ignoreParameters = {
@ -172,8 +175,7 @@ public class SysUserController { @@ -172,8 +175,7 @@ public class SysUserController {
@MyRequestBody SysUserDto sysUserDto,
@MyRequestBody String deptPostIdListString,
@MyRequestBody String dataPermIdListString,
@MyRequestBody String roleIdListString,
@MyRequestBody String deptIdListString) {
@MyRequestBody String roleIdListString) {
String errorMessage = MyCommonUtil.getModelValidationError(sysUserDto, true);
if (errorMessage != null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
@ -184,49 +186,23 @@ public class SysUserController { @@ -184,49 +186,23 @@ public class SysUserController {
}
SysUser sysUser = MyModelUtil.copyTo(sysUserDto, SysUser.class);
CallResult result = sysUserService.verifyRelatedData(
sysUser, originalUser, roleIdListString, deptPostIdListString, dataPermIdListString, deptIdListString);
sysUser, originalUser, roleIdListString, deptPostIdListString, dataPermIdListString);
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> deptIdSet = null;
if (result.getData().containsKey("deptIdSet")) {
deptIdSet = result.getData().getObject("deptIdSet", new TypeReference<Set<Long>>(){});
}
if (!sysUserService.update(sysUser, originalUser, roleIdSet, deptPostIdSet, dataPermIdSet, deptIdSet)) {
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);
}
return ResponseResult.success();
}
/**
* 分配用户组织角色
* 先删除用户原有的角色关联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
@ -506,21 +482,6 @@ public class SysUserController { @@ -506,21 +482,6 @@ 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

@ -1,23 +0,0 @@ @@ -1,23 +0,0 @@
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,10 +43,4 @@ public class SysDeptDto { @@ -43,10 +43,4 @@ 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,6 +41,7 @@ public class SysDept { @@ -41,6 +41,7 @@ public class SysDept {
* 父部门Id
*/
private Long parentId;
/**
* 逻辑删除标记字段(1: 正常 -1: 已删除)
*/

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

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

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

@ -2,13 +2,8 @@ package apelet.tenantadmin.upms.service; @@ -2,13 +2,8 @@ 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.SysDeptVo;
import apelet.tenantadmin.upms.vo.SysRoleVo;
import java.util.List;
import java.util.Map;
@ -109,22 +104,4 @@ public interface SysRoleService extends IBaseService<SysRole, Long> { @@ -109,22 +104,4 @@ public interface SysRoleService extends IBaseService<SysRole, Long> {
* @return 包含从角色到权限字的权限分配路径信息的查询结果列表
*/
List<Map<String, Object>> getSysPermCodeListWithDetail(Long roleId, String permCode);
/**
* 查询角色关联的组织列表用于编辑页回显多对多
*
* @param roleId 角色Id
* @return 角色关联的组织Vo列表如果不存在返回空列表
*/
List<SysDeptVo> getRoleDept(Long roleId);
/**
* 根据组织ID分页查询关联角色
*
* @param deptId 组织Id
* @param orderParam 排序参数
* @param pageParam 分页参数
* @return 分页的角色列表
*/
MyPageData<SysRoleVo> listRoleByDeptId(Long deptId, MyOrderParam orderParam, MyPageParam pageParam);
}

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

@ -3,7 +3,6 @@ package apelet.tenantadmin.upms.service; @@ -3,7 +3,6 @@ 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;
@ -33,10 +32,9 @@ public interface SysUserService extends IBaseService<SysUser, Long> { @@ -33,10 +32,9 @@ public interface SysUserService extends IBaseService<SysUser, Long> {
* @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, Set<Long> deptIdSet);
SysUser saveNew(SysUser user, Set<Long> roleIdSet, Set<Long> deptPostIdSet, Set<Long> dataPermIdSet);
/**
* 更新用户对象
@ -46,10 +44,9 @@ public interface SysUserService extends IBaseService<SysUser, Long> { @@ -46,10 +44,9 @@ public interface SysUserService extends IBaseService<SysUser, Long> {
* @param roleIdSet 用户角色Id列表
* @param deptPostIdSet 部门岗位Id集合
* @param dataPermIdSet 数据权限Id集合
* @param deptIdSet 组织Id集合写入SysUserRole.deptId
* @return 更新成功返回true否则false
*/
boolean update(SysUser user, SysUser originalUser, Set<Long> roleIdSet, Set<Long> deptPostIdSet, Set<Long> dataPermIdSet, Set<Long> deptIdSet);
boolean update(SysUser user, SysUser originalUser, Set<Long> roleIdSet, Set<Long> deptPostIdSet, Set<Long> dataPermIdSet);
/**
* 修改用户密码
@ -229,11 +226,10 @@ public interface SysUserService extends IBaseService<SysUser, Long> { @@ -229,11 +226,10 @@ public interface SysUserService extends IBaseService<SysUser, Long> {
* @param roleIds 逗号分隔的角色Id列表字符串
* @param deptPostIds 逗号分隔的部门岗位Id列表字符串
* @param dataPermIds 逗号分隔的数据权限Id列表字符串
* @param deptIds 逗号分隔的组织Id列表字符串可选写入SysUserRole.deptId
* @return 验证结果
*/
CallResult verifyRelatedData(
SysUser sysUser, SysUser originalSysUser, String roleIds, String deptPostIds, String dataPermIds, String deptIds);
SysUser sysUser, SysUser originalSysUser, String roleIds, String deptPostIds, String dataPermIds);
/**
* 根据云之家openId获取当前用户信息
@ -257,30 +253,4 @@ public interface SysUserService extends IBaseService<SysUser, Long> { @@ -257,30 +253,4 @@ 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,20 +3,14 @@ package apelet.tenantadmin.upms.service.impl; @@ -3,20 +3,14 @@ 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.MyOrderParam;
import apelet.common.core.object.MyPageData;
import apelet.common.core.object.MyPageParam;
import apelet.common.core.object.MyRelationParam;
import apelet.common.core.object.*;
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.SysDataPermDeptMapper;
import apelet.tenantadmin.upms.dao.SysDeptMapper;
import apelet.tenantadmin.upms.dao.SysDeptPostMapper;
import apelet.tenantadmin.upms.dao.SysDeptRelationMapper;
import apelet.tenantadmin.upms.dao.*;
import apelet.tenantadmin.upms.model.*;
import apelet.tenantadmin.upms.service.SysDeptService;
import apelet.tenantadmin.upms.service.SysUserService;
@ -35,10 +29,7 @@ import org.springframework.stereotype.Service; @@ -35,10 +29,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
/**

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

@ -4,37 +4,26 @@ import apelet.common.core.annotation.MyDataSource; @@ -4,37 +4,26 @@ 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.CallResult;
import apelet.common.core.object.MyOrderParam;
import apelet.common.core.object.MyPageData;
import apelet.common.core.object.MyPageParam;
import apelet.common.core.object.*;
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.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;
import apelet.tenantadmin.upms.model.SysDept;
import apelet.tenantadmin.upms.model.SysRole;
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.SysDeptVo;
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;
@ -66,13 +55,9 @@ public class SysRoleServiceImpl extends BaseService<SysRole, Long> implements Sy @@ -66,13 +55,9 @@ 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;
@PostConstruct
private void registerBizWidgetDatasource() {
@ -184,7 +169,6 @@ public class SysRoleServiceImpl extends BaseService<SysRole, Long> implements Sy @@ -184,7 +169,6 @@ public class SysRoleServiceImpl extends BaseService<SysRole, Long> implements Sy
SysUserRole userRole = new SysUserRole();
userRole.setRoleId(roleId);
sysUserRoleMapper.delete(new QueryWrapper<>(userRole));
roleFormButtonService.removeByRoleId(roleId);
return true;
}
@ -282,48 +266,4 @@ public class SysRoleServiceImpl extends BaseService<SysRole, Long> implements Sy @@ -282,48 +266,4 @@ 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 List<SysDeptVo> getRoleDept(Long roleId) {
LambdaQueryWrapper<SysUserRole> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUserRole::getRoleId, roleId).isNotNull(SysUserRole::getDeptId);
List<SysUserRole> userRoleList = sysUserRoleMapper.selectList(queryWrapper);
if (CollUtil.isEmpty(userRoleList)) {
return Collections.emptyList();
}
Set<Long> deptIdSet = userRoleList.stream().map(SysUserRole::getDeptId).collect(Collectors.toSet());
List<SysDept> deptList = sysDeptMapper.selectBatchIds(deptIdSet);
if (CollUtil.isEmpty(deptList)) {
return Collections.emptyList();
}
return MyModelUtil.copyCollectionTo(deptList, SysDeptVo.class);
}
@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);
}
}

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

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
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;
@ -12,9 +13,10 @@ import apelet.common.eas.service.impl.EASSyncService; @@ -12,9 +13,10 @@ 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.*;
@ -30,7 +32,6 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; @@ -30,7 +32,6 @@ 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;
@ -72,10 +73,6 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy @@ -72,10 +73,6 @@ 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;
@ -198,7 +195,7 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy @@ -198,7 +195,7 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy
*/
@Transactional(rollbackFor = Exception.class)
@Override
public SysUser saveNew(SysUser user, Set<Long> roleIdSet, Set<Long> deptPostIdSet, Set<Long> dataPermIdSet, Set<Long> deptIdSet) {
public SysUser saveNew(SysUser user, Set<Long> roleIdSet, Set<Long> deptPostIdSet, Set<Long> dataPermIdSet) {
user.setUserId(idGenerator.nextLongId());
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setUserStatus(SysUserStatus.STATUS_NORMAL);
@ -246,7 +243,7 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy @@ -246,7 +243,7 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy
*/
@Transactional(rollbackFor = Exception.class)
@Override
public boolean update(SysUser user, SysUser originalUser, Set<Long> roleIdSet, Set<Long> deptPostIdSet, Set<Long> dataPermIdSet, Set<Long> deptIdSet) {
public boolean update(SysUser user, SysUser originalUser, Set<Long> roleIdSet, Set<Long> deptPostIdSet, Set<Long> dataPermIdSet) {
user.setLoginName(originalUser.getLoginName());
user.setPassword(originalUser.getPassword());
MyModelUtil.fillCommonsForUpdate(user, originalUser);
@ -344,45 +341,6 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy @@ -344,45 +341,6 @@ 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)方法
@ -572,7 +530,7 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy @@ -572,7 +530,7 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy
*/
@Override
public CallResult verifyRelatedData(
SysUser sysUser, SysUser originalSysUser, String roleIds, String deptPostIds, String dataPermIds, String deptIds) {
SysUser sysUser, SysUser originalSysUser, String roleIds, String deptPostIds, String dataPermIds) {
JSONObject jsonObject = new JSONObject();
if (StrUtil.isBlank(deptPostIds)) {
return CallResult.error("数据验证失败,用户的部门岗位数据不能为空!");
@ -605,14 +563,6 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy @@ -605,14 +563,6 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy
&& !sysDeptService.existId(sysUser.getDeptId())) {
return CallResult.error("数据验证失败,关联的用户部门Id并不存在,请刷新后重试!");
}
// 验证组织ID(可选,写入SysUserRole.deptId)
if (StrUtil.isNotBlank(deptIds)) {
Set<Long> deptIdSet = Arrays.stream(deptIds.split(",")).map(Long::valueOf).collect(Collectors.toSet());
if (!sysDeptService.existAllPrimaryKeys(deptIdSet)) {
return CallResult.error("数据验证失败,存在不合法的组织数据,请刷新后重试!");
}
jsonObject.put("deptIdSet", deptIdSet);
}
return CallResult.ok(jsonObject);
}
@ -806,63 +756,5 @@ public class SysUserServiceImpl extends BaseService<SysUser, Long> implements Sy @@ -806,63 +756,5 @@ 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,12 +40,6 @@ public class SysDeptVo { @@ -40,12 +40,6 @@ public class SysDeptVo {
private Long parentId;
/**
* 类型0-部门1-组织
*/
@Schema(description = "类型:0-部门,1-组织")
private Integer type;
/**
* 创建者Id
*/
@Schema(description = "创建者Id")

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

@ -36,10 +36,4 @@ public class SysRoleVo extends BaseVo { @@ -36,10 +36,4 @@ public class SysRoleVo extends BaseVo {
*/
@Schema(description = "角色与菜单关联对象列表")
private List<Map<String, Object>> sysRoleMenuList;
/**
* 关联的组织列表仅返回deptId和deptName多对多
*/
@Schema(description = "关联的组织列表")
private List<SysDeptVo> deptList;
}

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

@ -1,161 +0,0 @@ @@ -1,161 +0,0 @@
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,14 +3,20 @@ spring: @@ -3,14 +3,20 @@ 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,7 +16,6 @@ debug:当此属性设置为true时,将打印出logback内部日志信息, @@ -16,7 +16,6 @@ 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">
@ -28,7 +27,6 @@ debug:当此属性设置为true时,将打印出logback内部日志信息, @@ -28,7 +27,6 @@ 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">
@ -91,48 +89,48 @@ debug:当此属性设置为true时,将打印出logback内部日志信息, @@ -91,48 +89,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

@ -1,355 +0,0 @@ @@ -1,355 +0,0 @@
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

121
common/common-core/pom.xml

@ -0,0 +1,121 @@ @@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>apelet</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-core</artifactId>
<version>1.0.0</version>
<name>common-core</name>
<packaging>jar</packaging>
<dependencies>
<!-- spring cloud gateway 网关不需要该starter。因此放到parent pom中就没法排除了 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 常用工具 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons-collections4.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>${common-csv.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<dependency>
<groupId>cn.jimmyshi</groupId>
<artifactId>bean-query</artifactId>
<version>${bean.query.version}</version>
</dependency>
<!-- poi相关工具包 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi-ooxml.version}</version>
</dependency>
<!-- 数据库访问层相关工具 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
<exclusions>
<exclusion>
<groupId>com.sun</groupId>
<artifactId>jconsole</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>4.4.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

31
common/common-core/src/main/java/apelet/common/core/advice/MyControllerAdvice.java

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
package apelet.common.core.advice;
import apelet.common.core.util.MyDateUtil;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Controller的环绕拦截类
*
* @author guifc
* @date 2023-08-04
*/
@ControllerAdvice
public class MyControllerAdvice {
/**
* 转换前端传入的日期变量参数为指定格式
*
* @param binder 数据绑定参数
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class,
new CustomDateEditor(new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT), false));
}
}

167
common/common-core/src/main/java/apelet/common/core/advice/MyExceptionHandler.java

@ -0,0 +1,167 @@ @@ -0,0 +1,167 @@
package apelet.common.core.advice;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.exception.*;
import apelet.common.core.object.ResponseResult;
import apelet.common.core.util.ContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.PersistenceException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.PermissionDeniedDataAccessException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeoutException;
/**
* 业务层的异常处理类这里只是给出最通用的Exception的捕捉今后可以根据业务需要
* 用不同的函数处理不同类型的异常
*
* @author guifc
* @date 2023-08-04
*/
@Slf4j
@RestControllerAdvice("apelet")
public class MyExceptionHandler {
/**
* 通用异常处理方法
*
* @param ex 异常对象
* @param request http请求
* @return 应答对象
*/
@ExceptionHandler(value = Exception.class)
public ResponseResult<Void> exceptionHandle(Exception ex, HttpServletRequest request) {
log.error("Unhandled exception from URL [" + request.getRequestURI() + "]", ex);
ContextUtil.getHttpResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return ResponseResult.error(ErrorCodeEnum.UNHANDLED_EXCEPTION, ex.getMessage());
}
/**
* 无效的实体对象异常
*
* @param ex 异常对象
* @param request http请求
* @return 应答对象
*/
@ExceptionHandler(value = InvalidDataModelException.class)
public ResponseResult<Void> invalidDataModelExceptionHandle(Exception ex, HttpServletRequest request) {
log.error("InvalidDataModelException exception from URL [" + request.getRequestURI() + "]", ex);
return ResponseResult.error(ErrorCodeEnum.INVALID_DATA_MODEL);
}
/**
* 无效的实体对象字段异常
*
* @param ex 异常对象
* @param request http请求
* @return 应答对象
*/
@ExceptionHandler(value = InvalidDataFieldException.class)
public ResponseResult<Void> invalidDataFieldExceptionHandle(Exception ex, HttpServletRequest request) {
log.error("InvalidDataFieldException exception from URL [" + request.getRequestURI() + "]", ex);
return ResponseResult.error(ErrorCodeEnum.INVALID_DATA_FIELD);
}
/**
* 无效类字段异常
*
* @param ex 异常对象
* @param request http请求
* @return 应答对象
*/
@ExceptionHandler(value = InvalidClassFieldException.class)
public ResponseResult<Void> invalidClassFieldExceptionHandle(Exception ex, HttpServletRequest request) {
log.error("InvalidClassFieldException exception from URL [" + request.getRequestURI() + "]", ex);
return ResponseResult.error(ErrorCodeEnum.INVALID_CLASS_FIELD);
}
/**
* 重复键异常处理方法
*
* @param ex 异常对象
* @param request http请求
* @return 应答对象
*/
@ExceptionHandler(value = DuplicateKeyException.class)
public ResponseResult<Void> duplicateKeyExceptionHandle(Exception ex, HttpServletRequest request) {
log.error("DuplicateKeyException exception from URL [" + request.getRequestURI() + "]", ex);
return ResponseResult.error(ErrorCodeEnum.DUPLICATED_UNIQUE_KEY);
}
/**
* 数据访问失败异常处理方法
*
* @param ex 异常对象
* @param request http请求
* @return 应答对象
*/
@ExceptionHandler(value = DataAccessException.class)
public ResponseResult<Void> dataAccessExceptionHandle(Exception ex, HttpServletRequest request) {
log.error("DataAccessException exception from URL [" + request.getRequestURI() + "]", ex);
if (ex.getCause() instanceof PersistenceException
&& ex.getCause().getCause() instanceof PermissionDeniedDataAccessException) {
return ResponseResult.error(ErrorCodeEnum.DATA_PERM_ACCESS_FAILED);
}
return ResponseResult.error(ErrorCodeEnum.DATA_ACCESS_FAILED);
}
/**
* 操作不存在或已逻辑删除数据的异常处理方法
*
* @param ex 异常对象
* @param request http请求
* @return 应答对象
*/
@ExceptionHandler(value = NoDataAffectException.class)
public ResponseResult<Void> noDataEffectExceptionHandle(Exception ex, HttpServletRequest request) {
log.error("NoDataAffectException exception from URL [" + request.getRequestURI() + "]", ex);
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
/**
* 数据权限异常
*
* @param ex 异常对象
* @param request http请求
* @return 应答对象
*/
@ExceptionHandler(value = NoDataPermException.class)
public ResponseResult<Void> noDataPermExceptionHandle(Exception ex, HttpServletRequest request) {
log.error("NoDataPermException exception from URL [" + request.getRequestURI() + "]", ex);
return ResponseResult.error(ErrorCodeEnum.DATA_PERM_ACCESS_FAILED, ex.getMessage());
}
/**
* 自定义运行时异常
*
* @param ex 异常对象
* @param request http请求
* @return 应答对象
*/
@ExceptionHandler(value = MyRuntimeException.class)
public ResponseResult<Void> myRuntimeExceptionHandle(Exception ex, HttpServletRequest request) {
log.error("MyRuntimeException exception from URL [" + request.getRequestURI() + "]", ex);
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, ex.getMessage());
}
/**
* Redis缓存访问异常处理方法
*
* @param ex 异常对象
* @param request http请求
* @return 应答对象
*/
@ExceptionHandler(value = RedisCacheAccessException.class)
public ResponseResult<Void> redisCacheAccessExceptionHandle(Exception ex, HttpServletRequest request) {
log.error("RedisCacheAccessException exception from URL [" + request.getRequestURI() + "]", ex);
if (ex.getCause() instanceof TimeoutException) {
return ResponseResult.error(ErrorCodeEnum.REDIS_CACHE_ACCESS_TIMEOUT);
}
return ResponseResult.error(ErrorCodeEnum.REDIS_CACHE_ACCESS_STATE_ERROR);
}
}

16
common/common-core/src/main/java/apelet/common/core/annotation/DeptFilterColumn.java

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 主要用于标记数据权限中基于DeptId进行过滤的字段
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DeptFilterColumn {
}

17
common/common-core/src/main/java/apelet/common/core/annotation/DisableDataFilter.java

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 作为DisableDataFilterAspect的切点
* 该注解标记的方法内所有的查询语句均不会被Mybatis拦截器过滤数据
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DisableDataFilter {
}

28
common/common-core/src/main/java/apelet/common/core/annotation/DisableTenantFilter.java

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 仅用于微服务的多租户项目
* 用于注解DAO层Mapper对象的租户过滤规则被包含的方法将不会进行租户Id的过滤
* 对于tk mapper和mybatis plus中的内置方法可以直接指定方法名即可selectOne
* 需要说明的是在大多数场景下只要在实体对象中指定了租户Id字段基于该主表的绝大部分增删改操作
* 都需要经过租户Id过滤仅当查询非常复杂或者主表不在SQL语句之中的时候可以通过该注解禁用该SQL
* 并根据需求通过手动的方式实现租户过滤
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DisableTenantFilter {
/**
* 包含的方法名称数组该值不能为空因为如想取消所有方法的租户过滤
* 可以通过在实体对象中不指定租户Id字段注解的方式实现
*
* @return 被包括的方法名称数组
*/
String[] includeMethodName();
}

35
common/common-core/src/main/java/apelet/common/core/annotation/EnableDataPerm.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 用于注解DAO层Mapper对象的数据权限规则
* 由于框架使用了tk.mapper所以并非所有的Mapper接口均在当前Mapper对象中定义有一部分被tk.mapper封装如selectAll等
* 如果需要排除tk.mapper中的方法可以直接使用tk.mapper基类所声明的方法名称即可
* 另外比较特殊的场景是因为tk.mapper是通用框架所以同样的selectAll方法可以获取不同的数据集合因此在service中如果
* 出现两个不同的方法调用Mapper的selectAll方法但是一个需要参与过滤另外一个不需要参与那么就需要修改当前类的Mapper方法
* 将其中一个方法重新定义一个具体的接口方法并重新设定其是否参与数据过滤
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableDataPerm {
/**
* 排除的方法名称数组如果为空所有的方法均会被Mybaits拦截注入权限过滤条件
*
* @return 被排序的方法名称数据
*/
String[] excluseMethodName() default {};
/**
* 必须包含能看用户自己数据的数据过滤条件如果当前用户的数据过滤中没有DataPermRuleType.TYPE_USER_ONLY
* 在进行数据权限过滤时会自动包含该权限
*
* @return 是否必须包含DataPermRuleType.TYPE_USER_ONLY类型的数据权限
*/
boolean mustIncludeUserRule() default false;
}

16
common/common-core/src/main/java/apelet/common/core/annotation/FlowLatestApprovalStatusColumn.java

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 业务表中记录流程最后审批状态标记的字段
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FlowLatestApprovalStatusColumn {
}

16
common/common-core/src/main/java/apelet/common/core/annotation/FlowStatusColumn.java

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 业务表中记录流程实例结束标记的字段
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FlowStatusColumn {
}

16
common/common-core/src/main/java/apelet/common/core/annotation/JobUpdateTimeColumn.java

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 主要用于标记Job实体对象的更新时间字段
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JobUpdateTimeColumn {
}

50
common/common-core/src/main/java/apelet/common/core/annotation/MaskField.java

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
package apelet.common.core.annotation;
import apelet.common.core.constant.MaskFieldTypeEnum;
import apelet.common.core.util.MaskFieldHandler;
import java.lang.annotation.*;
/**
* 脱敏字段注解
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MaskField {
/**
* 脱敏类型
*
* @return 脱敏类型
*/
MaskFieldTypeEnum maskType();
/**
* 掩码符号
*
* @return 掩码符号
*/
char maskChar() default '*';
/**
* 前面noMaskPrefix数量的字符不被掩码
* 掩码类型为MaskFieldTypeEnum.ID_CARD时可用
*
* @return 从1开始计算前面不被掩码的字符数
*/
int noMaskPrefix() default 1;
/**
* 末尾noMaskSuffix数量的字符不被掩码
* 掩码类型为MaskFieldTypeEnum.ID_CARD时可用
*
* @return 从1开始计算末尾不被掩码的字符数
*/
int noMaskSuffix() default 1;
/**
* 自定义脱敏处理器接口的Class
* @return 自定义脱敏处理器接口的Class
*/
Class<? extends MaskFieldHandler> handler() default MaskFieldHandler.class;
}

18
common/common-core/src/main/java/apelet/common/core/annotation/MultiDatabaseWriteMethod.java

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 该注解通常标记于Service中的事务方法并且会和@Transactional注解同时存在
* 被注解标注的方法内代码通常通过mybatis并在同一个事务内访问数据库与此同时还会存在基于
* JDBC的跨库操作
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiDatabaseWriteMethod {
}

21
common/common-core/src/main/java/apelet/common/core/annotation/MyDataSource.java

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 主要用于标记Service所依赖的数据源类型
*
* @author guifc
* @date 2023-08-04
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataSource {
/**
* 标注的数据源类型
* @return 当前标注的数据源类型
*/
int value();
}

35
common/common-core/src/main/java/apelet/common/core/annotation/MyDataSourceResolver.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
package apelet.common.core.annotation;
import apelet.common.core.util.DataSourceResolver;
import java.lang.annotation.*;
/**
* 基于自定义解析规则的多数据源注解主要用于标注Service的实现类
*
* @author guifc
* @date 2023-08-04
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataSourceResolver {
/**
* 多数据源路由键解析接口的Class
* @return 多数据源路由键解析接口的Class
*/
Class<? extends DataSourceResolver> resolver();
/**
* DataSourceResolver.resovle方法的入参
* @return DataSourceResolver.resovle方法的入参
*/
String arg() default "";
/**
* 数值型参数
* @return DataSourceResolver.resovle方法的入参
*/
int intArg() default -1;
}

23
common/common-core/src/main/java/apelet/common/core/annotation/MyRequestBody.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 标记Controller中的方法参数参数解析器会根据该注解将请求中的JSON数据映射到参数中的绑定字段
*
* @author guifc
* @date 2023-08-04
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestBody {
/**
* 是否必须出现的参数
*/
boolean required() default false;
/**
* 解析时用到的JSON的key
*/
String value() default "";
}

15
common/common-core/src/main/java/apelet/common/core/annotation/NoAuthInterface.java

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 主要用于标记无需Token验证的接口
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoAuthInterface {
}

29
common/common-core/src/main/java/apelet/common/core/annotation/RelationConstDict.java

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 标识Model和常量字典之间的关联关系
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RelationConstDict {
/**
* 当前对象的关联Id字段名称
*
* @return 当前对象的关联Id字段名称
*/
String masterIdField();
/**
* 被关联的常量字典的Class对象
*
* @return 关联的常量字典的Class对象
*/
Class<?> constantDictClass();
}

77
common/common-core/src/main/java/apelet/common/core/annotation/RelationDict.java

@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
package apelet.common.core.annotation;
import apelet.common.core.object.DummyClass;
import java.lang.annotation.*;
/**
* 标识Model之间的字典关联关系
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RelationDict {
/**
* 当前对象的关联Id字段名称
*
* @return 当前对象的关联Id字段名称
*/
String masterIdField();
/**
* 被关联Model对象的Class对象
*
* @return 被关联Model对象的Class对象
*/
Class<?> slaveModelClass();
/**
* 被关联Model对象的关联Id字段名称
*
* @return 被关联Model对象的关联Id字段名称
*/
String slaveIdField();
/**
* 被关联Model对象的关联Name字段名称
*
* @return 被关联Model对象的关联Name字段名称
*/
String slaveNameField();
/**
* 被关联的远程调用对象的Class对象
*
* @return 被关联远程调用对象的Class对象
*/
Class<?> slaveClientClass() default DummyClass.class;
/**
* 被关联的本地Service对象名称
* 该参数的优先级高于 slaveService()如果定义了该值会优先使用加载service的bean对象
*
* @return 被关联的本地Service对象名称
*/
String slaveServiceName() default "";
/**
* 被关联的本地Service对象CLass类型
*
* @return 被关联的本地Service对象CLass类型
*/
Class<?> slaveServiceClass() default DummyClass.class;
/**
* 在同一个实体对象中如果有一对一关联和字典关联都是基于相同的主表字段并关联到
* 相同关联表的同一关联字段时可以在字典关联的注解中引用被一对一注解标准的对象属性
* 从而在数据整合时当前字典的数据可以直接取自"equalOneToOneRelationField"指定
* 的字段从而避免一次没必要的数据库查询操作提升了加载显示的效率
*
* @return 与该字典字段引用关系完全相同的一对一关联属性名称
*/
String equalOneToOneRelationField() default "";
}

29
common/common-core/src/main/java/apelet/common/core/annotation/RelationGlobalDict.java

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 全局字典关联
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RelationGlobalDict {
/**
* 当前对象的关联Id字段名称
*
* @return 当前对象的关联Id字段名称
*/
String masterIdField();
/**
* 全局字典编码
*
* @return 全局字典编码空表示为不使用全局字典
*/
String dictCode();
}

39
common/common-core/src/main/java/apelet/common/core/annotation/RelationManyToMany.java

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 标注多对多的Model关系
* 重要提示由于多对多关联表数据很多时候都不需要跟随主表数据返回所以该注解不会在
* 生成的时候自动添加到实体类字段上需要的时候用户可自行手动添加
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RelationManyToMany {
/**
* 多对多中间表的Mapper对象名称
* 如果是空字符串BaseService会自动拼接为 relationModelClass().getSimpleName() + "Mapper"
*
* @return 被关联的本地Service对象名称
*/
String relationMapperName() default "";
/**
* 多对多关联表Model对象的Class对象
*
* @return 被关联Model对象的Class对象
*/
Class<?> relationModelClass();
/**
* 多对多关联表Model对象中与主表关联的Id字段名称
*
* @return 被关联Model对象的关联Id字段名称
*/
String relationMasterIdField();
}

102
common/common-core/src/main/java/apelet/common/core/annotation/RelationManyToManyAggregation.java

@ -0,0 +1,102 @@ @@ -0,0 +1,102 @@
package apelet.common.core.annotation;
import apelet.common.core.object.DummyClass;
import java.lang.annotation.*;
/**
* 主要用于多对多的Model关系标注通过从表关联字段或者关联表关联字段计算主表聚合计算字段的规则
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RelationManyToManyAggregation {
/**
* 当前对象的关联Id字段名称
*
* @return 当前对象的关联Id字段名称
*/
String masterIdField();
/**
* 被关联的本地Service对象名称
* 该参数的优先级高于 slaveService()如果定义了该值会优先使用加载service的bean对象
*
* @return 被关联的本地Service对象名称
*/
String slaveServiceName() default "";
/**
* 被关联的本地Service对象CLass类型
*
* @return 被关联的本地Service对象CLass类型
*/
Class<?> slaveServiceClass() default DummyClass.class;
/**
* 多对多从表Model对象的Class对象
*
* @return 被关联Model对象的Class对象
*/
Class<?> slaveModelClass();
/**
* 多对多从表Model对象的关联Id字段名称
*
* @return 被关联Model对象的关联Id字段名称
*/
String slaveIdField();
/**
* 被关联远程调用对象的Class对象如果为DummyClass.class通常表示是本地关联
*
* @return 被关联远程调用对象的Class对象
*/
Class<?> slaveClientClass() default DummyClass.class;
/**
* 多对多关联表Model对象的Class对象
*
* @return 被关联Model对象的Class对象
*/
Class<?> relationModelClass();
/**
* 多对多关联表Model对象中与主表关联的Id字段名称
*
* @return 被关联Model对象的关联Id字段名称
*/
String relationMasterIdField();
/**
* 多对多关联表Model对象中与从表关联的Id字段名称
*
* @return 被关联Model对象的关联Id字段名称
*/
String relationSlaveIdField();
/**
* 聚合计算所在的Model
*
* @return 聚合计算所在Model的Class
*/
Class<?> aggregationModelClass();
/**
* 聚合类型具体数值参考AggregationType对象
*
* @return 聚合类型
*/
int aggregationType();
/**
* 聚合计算所在Model的字段名称
*
* @return 聚合计算所在Model的字段名称
*/
String aggregationField();
}

53
common/common-core/src/main/java/apelet/common/core/annotation/RelationOneToMany.java

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
package apelet.common.core.annotation;
import apelet.common.core.object.DummyClass;
import java.lang.annotation.*;
/**
* 标识Model之间的一对多关联关系
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RelationOneToMany {
/**
* 当前对象的关联Id字段名称
*
* @return 当前对象的关联Id字段名称
*/
String masterIdField();
/**
* 被关联Model对象的Class对象
*
* @return 被关联Model对象的Class对象
*/
Class<?> slaveModelClass();
/**
* 被关联Model对象的关联Id字段名称
*
* @return 被关联Model对象的关联Id字段名称
*/
String slaveIdField();
/**
* 被关联的本地Service对象名称
* 该参数的优先级高于 slaveService()如果定义了该值会优先使用加载service的bean对象
*
* @return 被关联的本地Service对象名称
*/
String slaveServiceName() default "";
/**
* 被关联的本地Service对象CLass类型
*
* @return 被关联的本地Service对象CLass类型
*/
Class<?> slaveServiceClass() default DummyClass.class;
}

74
common/common-core/src/main/java/apelet/common/core/annotation/RelationOneToManyAggregation.java

@ -0,0 +1,74 @@ @@ -0,0 +1,74 @@
package apelet.common.core.annotation;
import apelet.common.core.object.DummyClass;
import java.lang.annotation.*;
/**
* 主要用于一对多的Model关系标注通过从表关联字段计算主表聚合计算字段的规则
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RelationOneToManyAggregation {
/**
* 当前对象的关联Id字段名称
*
* @return 当前对象的关联Id字段名称
*/
String masterIdField();
/**
* 被关联的本地Service对象名称
* 该参数的优先级高于 slaveService()如果定义了该值会优先使用加载service的bean对象
*
* @return 被关联的本地Service对象名称
*/
String slaveServiceName() default "";
/**
* 被关联的本地Service对象CLass类型
*
* @return 被关联的本地Service对象CLass类型
*/
Class<?> slaveServiceClass() default DummyClass.class;
/**
* 被关联Model对象的Class对象
*
* @return 被关联Model对象的Class对象
*/
Class<?> slaveModelClass();
/**
* 被关联Model对象的关联Id字段名称
*
* @return 被关联Model对象的关联Id字段名称
*/
String slaveIdField();
/**
* 被关联远程调用对象的Class对象如果为DummyClass.class通常表示是本地关联
*
* @return 被关联远程调用对象的Class对象
*/
Class<?> slaveClientClass() default DummyClass.class;
/**
* 被关联Model对象中参与计算的聚合类型具体数值参考AggregationType对象
*
* @return 被关联Model对象中参与计算的聚合类型
*/
int aggregationType();
/**
* 被关联Model对象中参与聚合计算的字段名称
*
* @return 被关联Model对象中参与计算字段的名称
*/
String aggregationField();
}

67
common/common-core/src/main/java/apelet/common/core/annotation/RelationOneToOne.java

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
package apelet.common.core.annotation;
import apelet.common.core.object.DummyClass;
import java.lang.annotation.*;
/**
* 标识Model之间的一对一关联关系
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RelationOneToOne {
/**
* 当前对象的关联Id字段名称
*
* @return 当前对象的关联Id字段名称
*/
String masterIdField();
/**
* 被关联Model对象的Class对象
*
* @return 被关联Model对象的Class对象
*/
Class<?> slaveModelClass();
/**
* 被关联Model对象的关联Id字段名称
*
* @return 被关联Model对象的关联Id字段名称
*/
String slaveIdField();
/**
* 被关联远程调用对象的Class对象
*
* @return 被关联远程调用对象的Class对象
*/
Class<?> slaveClientClass() default DummyClass.class;
/**
* 被关联的本地Service对象名称
* 该参数的优先级高于 slaveService()如果定义了该值会优先使用加载service的bean对象
*
* @return 被关联的本地Service对象名称
*/
String slaveServiceName() default "";
/**
* 被关联的本地Service对象CLass类型
*
* @return 被关联的本地Service对象CLass类型
*/
Class<?> slaveServiceClass() default DummyClass.class;
/**
* 在一对一关联时是否加载从表的字典关联
*
* @return 是否加载从表的字典关联true关联false则只返回从表自身数据
*/
boolean loadSlaveDict() default true;
}

16
common/common-core/src/main/java/apelet/common/core/annotation/TenantFilterColumn.java

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 主要用于标记通过租户Id进行过滤的字段
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TenantFilterColumn {
}

24
common/common-core/src/main/java/apelet/common/core/annotation/UploadFlagColumn.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
package apelet.common.core.annotation;
import apelet.common.core.upload.UploadStoreTypeEnum;
import java.lang.annotation.*;
/**
* 用于标记支持数据上传和下载的字段
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UploadFlagColumn {
/**
* 上传数据存储类型
*
* @return 上传数据存储类型
*/
UploadStoreTypeEnum storeType();
}

16
common/common-core/src/main/java/apelet/common/core/annotation/UserFilterColumn.java

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
package apelet.common.core.annotation;
import java.lang.annotation.*;
/**
* 主要用于标记数据权限中基于UserId进行过滤的字段
*
* @author guifc
* @date 2023-08-04
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserFilterColumn {
}

48
common/common-core/src/main/java/apelet/common/core/aop/DataSourceAspect.java

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
package apelet.common.core.aop;
import apelet.common.core.annotation.MyDataSource;
import apelet.common.core.config.DataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 多数据源AOP切面处理类
*
* @author guifc
* @date 2023-08-04
*/
@Aspect
@Component
@Order(1)
@Slf4j
public class DataSourceAspect {
/**
* 所有配置MyDataSource注解的Service实现类
*/
@Pointcut("execution(public * apelet..service..*(..)) " +
"&& @target(apelet.common.core.annotation.MyDataSource)")
public void datasourcePointCut() {
// 空注释,避免sonar警告
}
@Around("datasourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Class<?> clazz = point.getTarget().getClass();
MyDataSource ds = clazz.getAnnotation(MyDataSource.class);
// 通过判断 DataSource 中的值来判断当前方法应用哪个数据源
Integer originalType = DataSourceContextHolder.setDataSourceType(ds.value());
log.debug("set datasource is " + ds.value());
try {
return point.proceed();
} finally {
DataSourceContextHolder.unset(originalType);
log.debug("unset datasource is " + originalType);
}
}
}

59
common/common-core/src/main/java/apelet/common/core/aop/DataSourceResolveAspect.java

@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
package apelet.common.core.aop;
import apelet.common.core.annotation.MyDataSourceResolver;
import apelet.common.core.config.DataSourceContextHolder;
import apelet.common.core.util.ApplicationContextHolder;
import apelet.common.core.util.DataSourceResolver;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 基于自定义解析规则的多数据源AOP切面处理类
*
* @author guifc
* @date 2023-08-04
*/
@Aspect
@Component
@Order(1)
@Slf4j
public class DataSourceResolveAspect {
private final Map<Class<? extends DataSourceResolver>, DataSourceResolver> resolverMap = new ConcurrentHashMap<>();
/**
* 所有配置MyDataSourceResovler注解的Service实现类
*/
@Pointcut("execution(public * apelet..service..*(..)) " +
"&& @target(apelet.common.core.annotation.MyDataSourceResolver)")
public void datasourceResolverPointCut() {
// 空注释,避免sonar警告
}
@Around("datasourceResolverPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Class<?> clazz = point.getTarget().getClass();
MyDataSourceResolver dsr = clazz.getAnnotation(MyDataSourceResolver.class);
Class<? extends DataSourceResolver> resolverClass = dsr.resolver();
DataSourceResolver resolver =
resolverMap.computeIfAbsent(resolverClass, ApplicationContextHolder::getBean);
int type = resolver.resolve(dsr.arg(), dsr.intArg(), point.getArgs());
// 通过判断 DataSource 中的值来判断当前方法应用哪个数据源
Integer originalType = DataSourceContextHolder.setDataSourceType(type);
log.debug("set datasource is " + type);
try {
return point.proceed();
} finally {
DataSourceContextHolder.unset(originalType);
log.debug("unset datasource is " + originalType);
}
}
}

188
common/common-core/src/main/java/apelet/common/core/base/client/BaseClient.java

@ -0,0 +1,188 @@ @@ -0,0 +1,188 @@
package apelet.common.core.base.client;
import apelet.common.core.object.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 远程调用接口
*
* @param <D> 主DomainDto域数据对象类型
* @param <V> 主DomainVo域数据对象类型
* @param <K> 主键类型
* @author guifc
* @date 2023-08-04
*/
public interface BaseClient<D, V, K> {
/**
* 基于主键的(in list)获取远程数据接口
*
* @param filterIds 主键Id集合
* @param withDict 是否包含字典关联
* @return 应答结果对象包含主对象集合
*/
ResponseResult<List<V>> listByIds(Set<K> filterIds, Boolean withDict);
/**
* 基于主键Id获取远程对象
*
* @param id 主键Id
* @param withDict 是否包含字典关联
* @return 应答结果对象包含主对象数据
*/
ResponseResult<V> getById(K id, Boolean withDict);
/**
* 判断参数列表中指定的主键Id是否全部存在
*
* @param filterIds 主键Id集合
* @return 应答结果对象包含true全部存在否则false
*/
ResponseResult<Boolean> existIds(Set<K> filterIds);
/**
* 给定主键Id是否存在
*
* @param id 主键Id
* @return 应答结果对象包含true表示存在否则false
*/
ResponseResult<Boolean> existId(K id);
/**
* 保存或更新数据
*
* @param data 主键Id为null时表示新增数据否则更新数据
* @return 应答结果对象主键Id
*/
default ResponseResult<V> saveNewOrUpdate(D data) {
throw new UnsupportedOperationException();
}
/**
* 批量新增或保存数据列表
*
* @param dataList 数据列表主键Id为null时表示新增数据否则更新数据
* @return 应答结果对象
*/
default ResponseResult<Void> saveNewOrUpdateBatch(List<D> dataList) {
throw new UnsupportedOperationException();
}
/**
* 验证指定数据的关联Id数据是否存合法
*
* @param data 数据对象
* @return 应答结果对象
*/
default ResponseResult<Void> verifyRelatedData(D data) {
throw new UnsupportedOperationException();
}
/**
* 验证指定数据列表的关联Id数据是否存合法
*
* @param dataList 数据对象列表
* @return 应答结果对象
*/
default ResponseResult<Void> verifyRelatedDataList(List<D> dataList) {
throw new UnsupportedOperationException();
}
/**
* 删除主键Id关联的对象
*
* @param id 主键Id
* @return 应答结果对象
*/
default ResponseResult<Integer> deleteById(K id) {
throw new UnsupportedOperationException();
}
/**
* 删除符合过滤条件的数据
*
* @param filter 过滤对象
* @return 应答结果对象包含删除数量
*/
default ResponseResult<Integer> deleteBy(D filter) {
throw new UnsupportedOperationException();
}
/**
* 获取远程主对象中符合查询条件的数据列表
* 缺省实现是因为字典类型的远程调用客户端中不需要实现该方法因此尽早抛出异常用户可自行修改
*
* @param queryParam 查询参数
* @return 分页数据集合对象如MyQueryParam参数的分页属性为空则不会执行分页操作只是基于MyPageData对象返回数据结果
*/
default ResponseResult<MyPageData<V>> listBy(MyQueryParam queryParam) {
throw new UnsupportedOperationException();
}
/**
* 获取远程主对象中符合查询条件的单条数据对象
* 缺省实现是因为字典类型的远程调用客户端中不需要实现该方法因此尽早抛出异常用户可自行修改
*
* @param queryParam 查询参数
* @return 应答结果对象包含主对象集合
*/
default ResponseResult<V> getBy(MyQueryParam queryParam) {
throw new UnsupportedOperationException();
}
/**
* 获取远程主对象中符合查询条件的数据列表
* 缺省实现是因为字典类型的远程调用客户端中不需要实现该方法因此尽早抛出异常用户可自行修改
*
* @param queryParam 查询参数
* @return 分页数据集合对象如MyQueryParam参数的分页属性为空则不会执行分页操作只是基于MyPageData对象返回数据结果
*/
default ResponseResult<MyPageData<Map<String, Object>>> listMapBy(MyQueryParam queryParam) {
throw new UnsupportedOperationException();
}
/**
* 获取远程主对象中符合查询条件的数据数量
* 缺省实现是因为字典类型的远程调用客户端中不需要实现该方法因此尽早抛出异常用户可自行修改
*
* @param queryParam 查询参数
* @return 应答结果对象包含结果数量
*/
default ResponseResult<Integer> countBy(MyQueryParam queryParam) {
throw new UnsupportedOperationException();
}
/**
* 获取远程主对象中符合过滤条件的分组聚合数据
* 缺省实现是因为字典类型的远程调用客户端中不需要实现该方法因此尽早抛出异常用户可自行修改
*
* @param aggregationParam 聚合参数
* @return 应答结果对象包含聚合计算后的数据列表
*/
default ResponseResult<List<Map<String, Object>>> aggregateBy(MyAggregationParam aggregationParam) {
throw new UnsupportedOperationException();
}
/**
* 根据主键Id及其列表数据(not in list)进行过滤返回给定的数据返回的对象数据中仅仅包含实体对象自己的数据以及配置的字典关联数据
*
* @param queryParam 查询参数
* @return 应答结果对象包含分页查询数据列表
*/
default ResponseResult<MyPageData<V>> listByNotInList(MyQueryParam queryParam) {
throw new UnsupportedOperationException();
}
/**
* 根据过滤字段和过滤集合返回不存在的数据
*
* @param queryParam 查询参数
* @return filterSet中在从表中不存在的数据集合
*/
default ResponseResult<List<?>> notExist(MyQueryParam queryParam) {
throw new UnsupportedOperationException();
}
}

110
common/common-core/src/main/java/apelet/common/core/base/client/BaseFallbackFactory.java

@ -0,0 +1,110 @@ @@ -0,0 +1,110 @@
package apelet.common.core.base.client;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.object.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* FeignClient 熔断降级处理对象
*
* @param <D> 主DomainDto域数据对象类型
* @param <V> 主DomainVo域数据对象类型
* @param <K> 主键类型
* @param <T> Feign客户端对象类型
* @author guifc
* @date 2023-08-04
*/
@Slf4j
public abstract class BaseFallbackFactory<D, V, K, T extends BaseClient<D, V, K>>
implements FallbackFactory<T>, BaseClient<D, V, K> {
@Override
public ResponseResult<List<V>> listByIds(Set<K> idSet, Boolean withDict) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<V> getById(K id, Boolean withDict) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<Boolean> existIds(Set<K> idSet) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<Boolean> existId(K id) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<V> saveNewOrUpdate(D data) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<Void> saveNewOrUpdateBatch(List<D> dataList) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<Void> verifyRelatedData(D data) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<Void> verifyRelatedDataList(List<D> dataList) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<Integer> deleteById(K id) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<Integer> deleteBy(D filter) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<MyPageData<V>> listBy(MyQueryParam queryParam) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<V> getBy(MyQueryParam queryParam) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<MyPageData<Map<String, Object>>> listMapBy(MyQueryParam queryParam) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<Integer> countBy(MyQueryParam queryParam) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<List<Map<String, Object>>> aggregateBy(MyAggregationParam aggregationParam) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<MyPageData<V>> listByNotInList(MyQueryParam queryParam) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
@Override
public ResponseResult<List<?>> notExist(MyQueryParam queryParam) {
return ResponseResult.error(ErrorCodeEnum.RPC_DATA_ACCESS_FAILED);
}
}

520
common/common-core/src/main/java/apelet/common/core/base/controller/BaseController.java

@ -0,0 +1,520 @@ @@ -0,0 +1,520 @@
package apelet.common.core.base.controller;
import apelet.common.core.base.mapper.BaseModelMapper;
import apelet.common.core.base.service.IBaseService;
import apelet.common.core.config.CoreProperties;
import apelet.common.core.constant.AggregationKind;
import apelet.common.core.constant.AggregationType;
import apelet.common.core.constant.ErrorCodeEnum;
import apelet.common.core.exception.RemoteDataBuildException;
import apelet.common.core.object.*;
import apelet.common.core.util.MyCommonUtil;
import apelet.common.core.util.MyModelUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableId;
import com.github.pagehelper.Page;
import com.github.pagehelper.page.PageMethod;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 控制器Controller的基类
*
* @param <M> 主Model实体对象类型
* @param <V> 主Model的DomainVO域对象类型
* @param <K> 主键类型
* @author guifc
* @date 2023-08-04
*/
@Slf4j
public abstract class BaseController<M, V, K extends Serializable> {
@Autowired
private CoreProperties coreProperties;
/**
* 当前Service关联的主Model实体对象的Class
*/
private final Class<M> modelClass;
/**
* 当前Service关联的主model的VO对象的Class
*/
private final Class<V> domainVoClass;
/**
* 当前Service关联的主Model对象主键字段名称
*/
private String idFieldName;
/**
* 获取子类中注入的IBaseService接口
*
* @return 子类中注入的BaseService类
*/
protected abstract IBaseService<M, K> service();
/**
* 构造函数
*/
@SuppressWarnings("unchecked")
protected BaseController() {
modelClass = (Class<M>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
domainVoClass = (Class<V>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
Field[] fields = ReflectUtil.getFields(modelClass);
for (Field field : fields) {
if (null != field.getAnnotation(TableId.class)) {
idFieldName = field.getName();
break;
}
}
}
/**
* 根据主键Id集合获取数据对象集合仅限于微服务间远程接口调用
*
* @param filterIds 主键Id集合
* @param withDict 是否包含字典关联
* @param modelMapper 对象映射函数对象如果为空则使用MyModelUtil中的缺省转换函数
* @return 应答结果对象包含主对象集合
* @throws RemoteDataBuildException buildRelationForDataList会抛出该异常
*/
public ResponseResult<List<V>> baseListByIds(
Set<K> filterIds, Boolean withDict, BaseModelMapper<V, M> modelMapper) {
if (MyCommonUtil.existBlankArgument(filterIds, withDict)) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
List<M> resultList = service().getInList(idFieldName, filterIds);
List<V> resultVoList = null;
if (CollUtil.isEmpty(resultList)) {
return ResponseResult.success(resultVoList);
}
if (Boolean.TRUE.equals(withDict)) {
service().buildRelationForDataList(resultList, MyRelationParam.dictOnly());
}
resultVoList = convertToVoList(resultList, modelMapper);
return ResponseResult.success(resultVoList);
}
/**
* 根据主键Id获取数据对象仅限于微服务间远程接口调用
*
* @param id 主键Id
* @param withDict 是否包含字典关联
* @param modelMapper 对象映射函数对象如果为空则使用MyModelUtil中的缺省转换函数
* @return 应答结果对象包含主对象数据
* @throws RemoteDataBuildException buildRelationForData会抛出此异常
*/
public ResponseResult<V> baseGetById(K id, Boolean withDict, BaseModelMapper<V, M> modelMapper) {
if (MyCommonUtil.existBlankArgument(id, withDict)) {
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
}
M resultObject = service().getById(id);
V resultVoObject = null;
if (resultObject == null) {
return ResponseResult.success(resultVoObject);
}
if (Boolean.TRUE.equals(withDict)) {
service().buildRelationForData(resultObject, MyRelationParam.dictOnly());
}
resultVoObject = this.convertToVo(resultObject, modelMapper);
return ResponseResult.success(resultVoObject);
}
/**
* 判断参数列表中指定的主键Id集合是否全部存在仅限于微服务间远程接口调用
*
* @param filterIds 主键Id集合
* @return 应答结果对象包含true全部存在否则false
*/
public ResponseResult<Boolean> baseExistIds(Set<K> filterIds) {
return ResponseResult.success(CollUtil.isNotEmpty(filterIds)
&& service().existUniqueKeyList(idFieldName, filterIds));
}
/**
* 判断参数列表中指定的主键Id集合是否全部存在仅限于微服务间远程接口调用
*
* @param id 主键Id
* @return 应答结果对象包含true全部存在否则false
*/
public ResponseResult<Boolean> baseExistId(K id) {
return ResponseResult.success(
!MyCommonUtil.existBlankArgument(id) && service().getById(id) != null);
}
/**
* 根据最新对象列表和原有对象的数据对比判断关联的字典数据和多对一主表数据是否都是合法数据仅限于微服务间远程接口调用
*
* @param data 数据对象
* 主键有值是视为更新操作的数据比对因此仅当关联Id变化时才会验证
* 主键为空视为新增操作的数据比对所有关联Id都会被验证
* @param idGetter 获取主键值的函数对象
* @return 应答结果对象
*/
public ResponseResult<Void> baseVerifyRelatedData(M data, Function<M, K> idGetter) {
CallResult result;
K id = idGetter.apply(data);
if (id == null) {
result = service().verifyRelatedData(data, null);
} else {
M originalData = service().getById(id);
if (originalData == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
result = service().verifyRelatedData(data, originalData);
}
return !result.isSuccess() ? ResponseResult.errorFrom(result) : ResponseResult.success();
}
/**
* 根据最新对象列表和原有对象列表的数据对比判断关联的字典数据和多对一主表数据是否都是合法数据
*
* @param dataList 数据对象列表
* @param idGetter 获取主键值的函数对象
* @return 应答结果对象
*/
public ResponseResult<Void> baseVerifyRelatedDataList(List<M> dataList, Function<M, K> idGetter) {
if (CollUtil.isEmpty(dataList)) {
return ResponseResult.success();
}
// 1. 先过滤出数据列表中的主键Id集合。
Set<K> idList = dataList.stream()
.filter(c -> idGetter.apply(c) != null).map(idGetter).collect(Collectors.toSet());
// 2. 列表中,我们目前仅支持全部是更新数据,或全部新增数据,不能混着。如果有主键值,说明当前全是更新数据。
if (CollUtil.isNotEmpty(idList)) {
// 3. 这里是批量读取的优化,用一个主键值得in list查询,一步获取全部原有数据。然后再在内存中基于Map排序。
List<M> originalList = service().getInList(idList);
Map<K, M> originalMap = originalList.stream().collect(Collectors.toMap(idGetter, c2 -> c2));
// 迭代列表,传入当前最新数据和更新前数据进行比对,如果关联数据变化了,就对新数据进行合法性验证。
for (M data : dataList) {
CallResult result = service().verifyRelatedData(data, originalMap.get(idGetter.apply(data)));
if (!result.isSuccess()) {
return ResponseResult.errorFrom(result);
}
}
} else {
// 4. 迭代列表,传入当前最新数据,对关联数据进行合法性验证。
for (M model : dataList) {
CallResult result = service().verifyRelatedData(model, null);
if (!result.isSuccess()) {
return ResponseResult.errorFrom(result);
}
}
}
return ResponseResult.success();
}
/**
* 删除符合过滤条件的数据
*
* @param filter 过滤对象
* @return 删除数量
*/
public ResponseResult<Integer> baseDeleteBy(M filter) {
return ResponseResult.success(service().removeBy(filter));
}
/**
* 自定义过滤条件显示字段和排序字段的单表查询主要用于微服务间远程过程调用
* NOTE: 和baseListMapBy方法的差别只是返回的数据形式不同该接口以对象列表的形式返回数据
*
* @param queryParam 查询参数
* @param modelMapper 对象映射函数对象如果为空则使用MyModelUtil中的缺省转换函数
* @return 分页数据集合对象如MyQueryParam参数的分页属性为空则不会执行分页操作只是基于MyPageData对象返回数据结果
* @throws RemoteDataBuildException buildRelationForDataList会抛出此异常
*/
public ResponseResult<MyPageData<V>> baseListBy(MyQueryParam queryParam, BaseModelMapper<V, M> modelMapper) {
boolean dataFilterEnabled = GlobalThreadLocal.setDataFilter(queryParam.getUseDataFilter());
if (CollUtil.isNotEmpty(queryParam.getSelectFieldList())) {
for (String fieldName : queryParam.getSelectFieldList()) {
String columnName = MyModelUtil.mapToColumnName(fieldName, modelClass);
if (columnName == null) {
String errorMessage = "数据验证失败,实体对象 ["
+ modelClass.getSimpleName() + "] 中不存在字段 [" + fieldName + "]!";
return ResponseResult.error(ErrorCodeEnum.INVALID_DATA_FIELD, errorMessage);
}
}
}
M filter = queryParam.getFilterDto(modelClass);
if (StrUtil.isNotBlank(queryParam.getInFilterField())
&& CollUtil.isNotEmpty(queryParam.getInFilterValues())) {
if (queryParam.getCriteriaList() == null) {
queryParam.setCriteriaList(new LinkedList<>());
}
MyWhereCriteria whereCriteria = new MyWhereCriteria();
whereCriteria.setFieldName(queryParam.getInFilterField());
whereCriteria.setOperatorType(MyWhereCriteria.OPERATOR_IN);
whereCriteria.setValue(queryParam.getInFilterValues());
queryParam.getCriteriaList().add(whereCriteria);
}
String whereClause = MyWhereCriteria.makeCriteriaString(queryParam.getCriteriaList(), modelClass);
whereClause = this.makeupSearchCriteria(queryParam, whereClause);
String orderBy = MyOrderParam.buildOrderBy(queryParam.getOrderParam(), modelClass);
MyPageParam pageParam = queryParam.getPageParam();
if (pageParam != null) {
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
}
List<M> resultList = service().getListByCondition(
queryParam.getSelectFieldList(), filter, whereClause, orderBy);
if (CollUtil.isEmpty(resultList)) {
return ResponseResult.success(MyPageData.emptyPageData());
}
long totalCount;
if (resultList instanceof Page) {
totalCount = ((Page<M>) resultList).getTotal();
} else {
totalCount = resultList.size();
}
if (BooleanUtil.isTrue(queryParam.getWithDict())) {
service().buildRelationForDataList(resultList, MyRelationParam.dictOnly());
}
service().maskFieldDataList(resultList, queryParam.getIgnoreMaskFieldSet());
List<V> resultVoList = convertToVoList(resultList, modelMapper);
GlobalThreadLocal.setDataFilter(dataFilterEnabled);
return ResponseResult.success(new MyPageData<>(resultVoList, totalCount));
}
/**
* 自定义过滤条件显示字段和排序字段的单表查询主要用于微服务间远程过程调用
* NOTE: 和baseListBy方法的差别只是返回的数据形式不同该接口以Map列表的形式返回数据
*
* @param queryParam 查询参数
* @param modelMapper 对象映射函数对象如果为空则使用MyModelUtil中的缺省转换函数
* @return 分页数据集合对象如MyQueryParam参数的分页属性为空则不会执行分页操作只是基于MyPageData对象返回数据结果
*/
public ResponseResult<MyPageData<Map<String, Object>>> baseListMapBy(
MyQueryParam queryParam, BaseModelMapper<V, M> modelMapper) {
ResponseResult<MyPageData<V>> result = this.baseListBy(queryParam, modelMapper);
if (!result.isSuccess()) {
return ResponseResult.errorFrom(result);
}
List<Map<String, Object>> resultMapList =
result.getData().getDataList().stream().map(BeanUtil::beanToMap).collect(Collectors.toList());
return ResponseResult.success(new MyPageData<>(resultMapList, result.getData().getTotalCount()));
}
/**
* 自定义过滤条件显示字段并返回单条记录主要用于微服务间远程过程调用
*
* @param queryParam 查询参数
* @param modelMapper 对象映射函数对象如果为空则使用MyModelUtil中的缺省转换函数
* @return 应答结果对象包含符合查询过滤条件的单条实体对象
*/
public ResponseResult<V> baseGetBy(MyQueryParam queryParam, BaseModelMapper<V, M> modelMapper) {
ResponseResult<MyPageData<V>> result = baseListBy(queryParam, modelMapper);
if (!result.isSuccess()) {
return ResponseResult.errorFrom(result);
}
List<V> dataList = result.getData().getDataList();
V data = null;
if (CollUtil.isNotEmpty(dataList)) {
data = dataList.get(0);
}
return ResponseResult.success(data);
}
/**
* 自定义过滤条件的记录数量统计主要用于微服务间远程过程调用
*
* @param queryParam 查询参数
* @return 应答结果对象包含符合查询过滤条件的记录数量
*/
public ResponseResult<Integer> baseCountBy(MyQueryParam queryParam) {
boolean dataFilterEnabled = GlobalThreadLocal.setDataFilter(queryParam.getUseDataFilter());
String whereClause = MyWhereCriteria.makeCriteriaString(queryParam.getCriteriaList(), modelClass);
Integer count = service().getCountByCondition(whereClause);
GlobalThreadLocal.setDataFilter(dataFilterEnabled);
return ResponseResult.success(count);
}
/**
* 获取远程对象中符合查询条件的分组聚合计算Map列表
*
* @param param 聚合参数
* @return 应该结果对象包含聚合计算后的分组Map列表
*/
public ResponseResult<List<Map<String, Object>>> baseAggregateBy(MyAggregationParam param) {
boolean dataFilterEnabled = GlobalThreadLocal.setDataFilter(param.getUseDataFilter());
// 完成一些共同性规则的验证。
VerifyAggregationInfo verifyInfo = this.verifyAndParseAggregationParam(param);
if (!verifyInfo.isSuccess) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, verifyInfo.errorMsg);
}
// 构建SelectList
StringBuilder selectList = new StringBuilder(64);
// 一对多场景相对比较简单,直接基于从表的关联键进行group,同时对聚合字段进行聚合计算即可。
// SQL返回的数据,可直接返回给调用端服务。
if (param.getAggregationKind() == AggregationKind.ONE_TO_MANY) {
selectList.append(verifyInfo.groupColumn).append(" ").append(MyAggregationParam.KEY_NAME).append(", ");
}
selectList.append(AggregationType.getAggregationFunction(verifyInfo.aggregationType))
.append("(")
.append(verifyInfo.aggregationColumn)
.append(") ")
.append(MyAggregationParam.VALUE_NAME);
String whereClause = MyWhereCriteria.makeCriteriaString(param.getWhereCriteriaList(), modelClass);
List<Map<String, Object>> resultMapList = null;
// 一对多场景直接返回分组查询计算结果即可。
if (param.getAggregationKind() == AggregationKind.ONE_TO_MANY) {
resultMapList = service().getGroupedListByCondition(
selectList.toString(), whereClause, verifyInfo.groupColumn);
} else if (param.getAggregationKind() == AggregationKind.MANY_TO_MANY) {
boolean stringKey = true;
if (param.getGroupedInFilterValues().entrySet().iterator().next().getKey() instanceof Number) {
stringKey = false;
}
resultMapList = new LinkedList<>();
// 迭代分组map,通过多次查询的方式进行数据分组。通过该方式可以避免多次rpc调用,以提升性能。
for (Map.Entry<Object, Set<Object>> entry : param.getGroupedInFilterValues().entrySet()) {
StringBuilder groupedSelectList = new StringBuilder(64);
if (stringKey) {
groupedSelectList.append("'").append(entry.getKey()).append("' as ");
} else {
groupedSelectList.append(entry.getKey()).append(" as ");
}
groupedSelectList.append(MyAggregationParam.KEY_NAME).append(", ").append(selectList);
MyWhereCriteria criteria = new MyWhereCriteria();
criteria.setModelClazz(modelClass);
criteria.setCriteria(param.getInFilterField(), MyWhereCriteria.OPERATOR_IN, entry.getValue());
StringBuilder groupedClause = new StringBuilder(128);
groupedClause.append(criteria.makeCriteriaString());
if (StrUtil.isNotBlank(whereClause)) {
groupedClause.append(" AND ").append(whereClause);
}
List<Map<String, Object>> subResultMapList = service().getGroupedListByCondition(
groupedSelectList.toString(), groupedClause.toString(), null);
resultMapList.addAll(subResultMapList);
}
}
GlobalThreadLocal.setDataFilter(dataFilterEnabled);
return ResponseResult.success(resultMapList);
}
/**
* 将Model实体对象的集合转换为DomainVO域对象的集合
* 如果Model存在该实体的ModelMapper就用该ModelMapper转换否则使用缺省的基于字段反射的copy
*
* @param modelList 实体对象列表
* @param modelMapper 从实体对象到VO对象的映射对象
* @return 转换后的VO域对象列表
*/
protected List<V> convertToVoList(List<M> modelList, BaseModelMapper<V, M> modelMapper) {
List<V> resultVoList;
if (modelMapper != null) {
resultVoList = modelMapper.fromModelList(modelList);
} else {
resultVoList = MyModelUtil.copyCollectionTo(modelList, domainVoClass);
}
return resultVoList;
}
/**
* 将Model实体对象转换为DomainVO域对象
* 如果Model存在该实体的ModelMapper就用该ModelMapper转换否则使用缺省的基于字段反射的copy
*
* @param model 实体对象
* @param modelMapper 从实体对象到VO对象的映射对象
* @return 转换后的VO域对象
*/
protected V convertToVo(M model, BaseModelMapper<V, M> modelMapper) {
V resultVo;
if (modelMapper != null) {
resultVo = modelMapper.fromModel(model);
} else {
resultVo = MyModelUtil.copyTo(model, domainVoClass);
}
return resultVo;
}
private String makeupSearchCriteria(MyQueryParam queryParam, String whereClause) {
if (CollUtil.isEmpty(queryParam.getSearchStringFieldList())
|| StrUtil.isBlank(queryParam.getSearchStringValue())) {
return whereClause;
}
String tableName = MyModelUtil.mapToTableName(modelClass);
StringBuilder sb = new StringBuilder(128);
if (StrUtil.isNotBlank(whereClause)) {
sb.append(" AND ");
}
sb.append(" CONCAT(");
for (int i = 0; i < queryParam.getSearchStringFieldList().size(); i++) {
String fieldName = queryParam.getSearchStringFieldList().get(i);
String columnName = MyModelUtil.mapToColumnInfo(fieldName, modelClass).getFirst();
if (coreProperties.isMySql() || coreProperties.isDm()) {
sb.append("IFNULL(");
} else if (coreProperties.isPostgresql() || coreProperties.isOpenGauss()) {
sb.append("COALESCE(");
} else if (coreProperties.isOracle() || coreProperties.isKingbase()) {
sb.append("NVL(");
}
sb.append(tableName).append(".").append(columnName).append(", '')");
if (i != queryParam.getSearchStringFieldList().size() - 1) {
sb.append(", ");
}
}
sb.append(") LIKE ").append("'").append(queryParam.getSearchStringValue()).append("'");
if (StrUtil.isNotBlank(whereClause)) {
whereClause = whereClause + sb.toString();
}
return whereClause;
}
private VerifyAggregationInfo verifyAndParseAggregationParam(MyAggregationParam param) {
VerifyAggregationInfo verifyInfo = new VerifyAggregationInfo();
if (!AggregationKind.isValid(param.getAggregationKind())) {
verifyInfo.errorMsg = "参数验证失败,聚合类别 [MyAggregationParam.AggregationKind] 数值无效!";
return verifyInfo;
}
Integer aggregationType = param.getAggregationType();
if (!AggregationType.isValid(aggregationType)) {
verifyInfo.errorMsg = "参数验证失败,聚合类型 [MyAggregationParam.AggregationType] 数值无效!";
return verifyInfo;
}
String aggregationColumn = MyModelUtil.mapToColumnName(param.getAggregationField(), modelClass);
if (StrUtil.isBlank(aggregationColumn)) {
verifyInfo.errorMsg = "参数验证失败,聚合字段 [MyAggregationParam.AggregationField] 为非法值!";
return verifyInfo;
}
// 一对多场景相对比较简单,直接基于从表的关联键进行group,同时对聚合字段进行聚合计算即可。
// SQL返回的数据,可直接返回给调用端服务。
if (param.getAggregationKind() == AggregationKind.ONE_TO_MANY) {
verifyInfo.groupColumn = MyModelUtil.mapToColumnName(param.getGroupField(), modelClass);
if (StrUtil.isBlank(verifyInfo.groupColumn)) {
verifyInfo.errorMsg = "参数验证失败,一对多聚合 [MyAggregationParam.GroupField] 分组字段为非法值!";
return verifyInfo;
}
} else {
String inFilterColumn = MyModelUtil.mapToColumnName(param.getInFilterField(), modelClass);
if (StrUtil.isBlank(inFilterColumn)) {
verifyInfo.errorMsg = "参数验证失败,多对多 [MyAggregationParam.InfilterField] 过滤字段为非法值!";
return verifyInfo;
}
if (MapUtil.isEmpty(param.getGroupedInFilterValues())) {
verifyInfo.errorMsg = "参数验证失败,多对多 [MyAggregationParam.GroupedInFilterValues] 数据集合不能为空!";
return verifyInfo;
}
}
verifyInfo.isSuccess = true;
verifyInfo.aggregationType = aggregationType;
verifyInfo.aggregationColumn = aggregationColumn;
return verifyInfo;
}
private static final class VerifyAggregationInfo {
private boolean isSuccess = false;
private String errorMsg;
private Integer aggregationType;
private String aggregationColumn;
private String groupColumn;
}
}

87
common/common-core/src/main/java/apelet/common/core/base/dao/BaseDaoMapper.java

@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
package apelet.common.core.base.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
/**
* 数据访问对象的基类
*
* @param <M> 主Model实体对象
* @author guifc
* @date 2023-08-04
*/
public interface BaseDaoMapper<M> extends BaseMapper<M> {
/**
* 根据指定的表名显示字段列表过滤条件字符串和分组字段返回聚合计算后的查询结果
*
* @param selectTable 表名称
* @param selectFields 返回字段列表逗号分隔
* @param whereClause SQL常量形式的条件从句
* @param groupBy 分组字段列表逗号分隔
* @return 对象可选字段Map列表
*/
@Select("<script>"
+ "SELECT ${selectFields} FROM ${selectTable}"
+ "<where>"
+ " <if test=\"whereClause != null and whereClause != ''\">"
+ " AND ${whereClause}"
+ " </if>"
+ "</where>"
+ "<if test=\"groupBy != null and groupBy != ''\">"
+ " GROUP BY ${groupBy}"
+ "</if>"
+ "</script>")
List<Map<String, Object>> getGroupedListByCondition(
@Param("selectTable") String selectTable,
@Param("selectFields") String selectFields,
@Param("whereClause") String whereClause,
@Param("groupBy") String groupBy);
/**
* 根据指定的表名显示字段列表过滤条件字符串和排序字符串返回查询结果
*
* @param selectTable 表名称
* @param selectFields 选择的字段列表
* @param whereClause 过滤字符串
* @param orderBy 排序字符串
* @return 查询结果
*/
@Select("<script>"
+ "SELECT ${selectFields} FROM ${selectTable}"
+ "<where>"
+ " <if test=\"whereClause != null and whereClause != ''\">"
+ " AND ${whereClause}"
+ " </if>"
+ "</where>"
+ "<if test=\"orderBy != null and orderBy != ''\">"
+ " ORDER BY ${orderBy}"
+ "</if>"
+ "</script>")
List<Map<String, Object>> getListByCondition(
@Param("selectTable") String selectTable,
@Param("selectFields") String selectFields,
@Param("whereClause") String whereClause,
@Param("orderBy") String orderBy);
/**
* 用指定过滤条件计算记录数量
*
* @param selectTable 表名称
* @param whereClause 过滤字符串
* @return 返回过滤后的数据数量
*/
@Select("<script>"
+ "SELECT COUNT(1) FROM ${selectTable}"
+ "<where>"
+ " <if test=\"whereClause != null and whereClause != ''\">"
+ " AND ${whereClause}"
+ " </if>"
+ "</where>"
+ "</script>")
int getCountByCondition(@Param("selectTable") String selectTable, @Param("whereClause") String whereClause);
}

126
common/common-core/src/main/java/apelet/common/core/base/mapper/BaseModelMapper.java

@ -0,0 +1,126 @@ @@ -0,0 +1,126 @@
package apelet.common.core.base.mapper;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Model对象到Domain类型对象的相互转换实现类通常声明在Model实体类中
*
* @param <D> Domain域对象类型
* @param <M> Model实体对象类型
* @author guifc
* @date 2023-08-04
*/
public interface BaseModelMapper<D, M> {
/**
* 转换Model实体对象到Domain域对象
*
* @param model Model实体对象
* @return Domain域对象
*/
D fromModel(M model);
/**
* 转换Model实体对象列表到Domain域对象列表
*
* @param modelList Model实体对象列表
* @return Domain域对象列表
*/
List<D> fromModelList(List<M> modelList);
/**
* 转换Domain域对象到Model实体对象
*
* @param domain Domain域对象
* @return Model实体对象
*/
M toModel(D domain);
/**
* 转换Domain域对象列表到Model实体对象列表
*
* @param domainList Domain域对象列表
* @return Model实体对象列表
*/
List<M> toModelList(List<D> domainList);
/**
* 转换bean到map
*
* @param bean bean对象
* @param ignoreNullValue 值为null的字段是否转换到Map
* @param <T> bean类型
* @return 转换后的map对象
*/
default <T> Map<String, Object> beanToMap(T bean, boolean ignoreNullValue) {
return BeanUtil.beanToMap(bean, false, ignoreNullValue);
}
/**
* 转换bean集合到map集合
*
* @param dataList bean对象集合
* @param ignoreNullValue 值为null的字段是否转换到Map
* @param <T> bean类型
* @return 转换后的map对象集合
*/
default <T> List<Map<String, Object>> beanToMap(List<T> dataList, boolean ignoreNullValue) {
if (CollUtil.isEmpty(dataList)) {
return new LinkedList<>();
}
return dataList.stream()
.map(o -> BeanUtil.beanToMap(o, false, ignoreNullValue))
.collect(Collectors.toList());
}
/**
* 转换map到bean
*
* @param map map对象
* @param beanClazz bean的Class对象
* @param <T> bean类型
* @return 转换后的bean对象
*/
default <T> T mapToBean(Map<String, Object> map, Class<T> beanClazz) {
return BeanUtil.toBeanIgnoreError(map, beanClazz);
}
/**
* 转换map集合到bean集合
*
* @param mapList map对象集合
* @param beanClazz bean的Class对象
* @param <T> bean类型
* @return 转换后的bean对象集合
*/
default <T> List<T> mapToBean(List<Map<String, Object>> mapList, Class<T> beanClazz) {
if (CollUtil.isEmpty(mapList)) {
return new LinkedList<>();
}
return mapList.stream()
.map(m -> BeanUtil.toBeanIgnoreError(m, beanClazz))
.collect(Collectors.toList());
}
/**
* 对于Map字段到Map字段的映射场景MapStruct会根据方法签名自动选择该函数
* 作为对象copy的函数由于该函数是直接返回的因此没有对象copy效率更高
* 如果没有该函数MapStruct会生成如下代码
* Map<String, Object> map = courseDto.getTeacherIdDictMap();
* if ( map != null ) {
* course.setTeacherIdDictMap( new HashMap<String, Object>( map ) );
* }
*
* @param map map对象
* @return 直接返回的map
*/
default Map<String, Object> mapToMap(Map<String, Object> map) {
return map;
}
}

58
common/common-core/src/main/java/apelet/common/core/base/mapper/DummyModelMapper.java

@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
package apelet.common.core.base.mapper;
import java.util.List;
/**
* 哑元占位对象Model实体对象和Domain域对象相同的场景下使用
* 由于没有实际的数据转换因此同时保证了代码统一和执行效率
*
* @param <M> 数据类型
* @author guifc
* @date 2023-08-04
*/
public class DummyModelMapper<M> implements BaseModelMapper<M, M> {
/**
* 不转换直接返回
*
* @param model Model实体对象
* @return Domain域对象
*/
@Override
public M fromModel(M model) {
return model;
}
/**
* 不转换直接返回
*
* @param modelList Model实体对象列表
* @return Domain域对象列表
*/
@Override
public List<M> fromModelList(List<M> modelList) {
return modelList;
}
/**
* 不转换直接返回
*
* @param domain Domain域对象
* @return Model实体对象
*/
@Override
public M toModel(M domain) {
return domain;
}
/**
* 不转换直接返回
*
* @param domainList Domain域对象列表
* @return Model实体对象列表
*/
@Override
public List<M> toModelList(List<M> domainList) {
return domainList;
}
}

40
common/common-core/src/main/java/apelet/common/core/base/model/BaseModel.java

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
package apelet.common.core.base.model;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.util.Date;
/**
* 实体对象的公共基类所有子类均必须包含基类定义的数据表字段和实体对象字段
*
* @author guifc
* @date 2023-08-04
*/
@Data
public class BaseModel {
/**
* 创建者Id
*/
@TableField(value = "create_user_id")
private Long createUserId;
/**
* 创建时间
*/
@TableField(value = "create_time")
private Date createTime;
/**
* 更新者Id
*/
@TableField(value = "update_user_id")
private Long updateUserId;
/**
* 更新时间
*/
@TableField(value = "update_time")
private Date updateTime;
}

230
common/common-core/src/main/java/apelet/common/core/base/service/BaseDictService.java

@ -0,0 +1,230 @@ @@ -0,0 +1,230 @@
package apelet.common.core.base.service;
import apelet.common.core.cache.DictionaryCache;
import apelet.common.core.constant.GlobalDeletedFlag;
import apelet.common.core.object.TokenData;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
/**
* 带有缓存功能的字典Service基类需要留意的是由于缓存基于Key/Value方式存储
* 目前仅支持基于主键字段的缓存查找其他条件的查找仍然从数据源获取
*
* @param <M> Model实体对象的类型
* @param <K> Model对象主键的类型
* @author guifc
* @date 2023-08-04
*/
@Slf4j
public abstract class BaseDictService<M, K extends Serializable>
extends BaseService<M, K> implements IBaseDictService<M, K> {
/**
* 缓存池对象
*/
protected DictionaryCache<K, M> dictionaryCache;
/**
* 构造函数使用缺省缓存池对象
*/
protected BaseDictService() {
super();
}
/**
* 重新加载数据库中所有当前表数据到系统内存
*
* @param force true则强制刷新如果false当缓存中存在数据时不刷新
*/
@Override
public void reloadCachedData(boolean force) {
// 在非强制刷新情况下。
// 先行判断缓存中是否存在数据,如果有就不加载了。
if (!force && dictionaryCache.getCount() > 0) {
return;
}
List<M> allList = super.getAllList();
dictionaryCache.reload(allList, force);
}
/**
* 保存新增对象
*
* @param data 新增对象
* @return 返回新增对象
*/
@Transactional(rollbackFor = Exception.class)
@Override
public M saveNew(M data) {
// 清空全部缓存
dictionaryCache.invalidateAll();
if (deletedFlagFieldName != null) {
ReflectUtil.setFieldValue(data, deletedFlagFieldName, GlobalDeletedFlag.NORMAL);
}
if (tenantIdField != null) {
ReflectUtil.setFieldValue(data, tenantIdField, TokenData.takeFromRequest().getTenantId());
}
mapper().insert(data);
return data;
}
/**
* 更新数据对象
*
* @param data 更新的对象
* @param originalData 原有数据对象
* @return 成功返回true否则false
*/
@Transactional(rollbackFor = Exception.class)
@Override
public boolean update(M data, M originalData) {
dictionaryCache.invalidateAll();
if (tenantIdField != null) {
ReflectUtil.setFieldValue(data, tenantIdField, TokenData.takeFromRequest().getTenantId());
}
return mapper().updateById(data) == 1;
}
/**
* 删除指定数据
*
* @param id 主键Id
* @return 成功返回true否则false
*/
@Transactional(rollbackFor = Exception.class)
@Override
public boolean remove(K id) {
dictionaryCache.invalidateAll();
return mapper().deleteById(id) == 1;
}
/**
* 直接从缓存池中获取主键Id关联的数据如果缓存中不存在再从数据库中取出并回写到缓存
*
* @param id 主键Id
* @return 主键关联的数据不存在返回null
*/
@SuppressWarnings("unchecked")
@Override
public M getById(Serializable id) {
M data = dictionaryCache.get((K) id);
if (data != null) {
return data;
}
if (dictionaryCache.getCount() != 0) {
return data;
}
this.reloadCachedData(true);
return dictionaryCache.get((K) id);
}
/**
* 直接从缓存池中获取所有数据
*
* @return 返回所有数据
*/
@Override
public List<M> getAllListFromCache() {
List<M> resultList = dictionaryCache.getAll();
if (CollUtil.isNotEmpty(resultList)) {
return resultList;
}
this.reloadCachedData(true);
return dictionaryCache.getAll();
}
/**
* 直接从缓存池中返回符合主键 in (idValues) 条件的所有数据
* 对于缓存中不存在的数据从数据库中获取并回写入缓存
*
* @param idValues 主键值列表
* @return 检索后的数据列表
*/
@Override
public List<M> getInList(Set<K> idValues) {
List<M> resultList = dictionaryCache.getInList(idValues);
// 如果从缓存中获取与请求的id完全相同就直接返回。
if (resultList.size() == idValues.size()) {
return resultList;
}
// 如果此时缓存中存在数据,说明有部分id是不存在的。也可以直接返回了。
if (dictionaryCache.getCount() != 0) {
return resultList;
}
// 执行到这里,说明缓存是空的,所有需要重新加载并再次从缓存中读取并返回。
this.reloadCachedData(true);
return dictionaryCache.getInList(idValues);
}
@Override
public List<M> getListByParentId(K parentId) {
List<M> resultList = dictionaryCache.getListByParentId(parentId);
// 如果包含数据就直接返回了
if (CollUtil.isNotEmpty(resultList)) {
return resultList;
}
// 如果缓存中存在该字典数据,说明该parentId下子对象列表为空,也可以直接返回了。
if (this.getCachedCount() != 0) {
return resultList;
}
// 执行到这里就需要重新加载全部缓存了。
this.reloadCachedData(true);
return dictionaryCache.getListByParentId(parentId);
}
/**
* 返回符合 inFilterField in (inFilterValues) 条件的所有数据属性property是主键则从缓存中读取
*
* @param inFilterField 参与(In-list)过滤的Java字段
* @param inFilterValues 参与(In-list)过滤的Java字段值集合
* @return 检索后的数据列表
*/
@SuppressWarnings("unchecked")
@Override
public <T> List<M> getInList(String inFilterField, Set<T> inFilterValues) {
if (inFilterField.equals(this.idFieldName)) {
return this.getInList((Set<K>) inFilterValues);
}
return super.getInList(inFilterField, inFilterValues);
}
/**
* 判断参数值列表中的所有数据是否全部存在另外keyName字段在数据表中必须是唯一键值否则返回结果会出现误判
*
* @param inFilterField 待校验的数据字段这里使用Java对象中的属性如courseId而不是数据字段名course_id
* @param inFilterValues 数据值集合
* @return 全部存在返回true否则false
*/
@SuppressWarnings("unchecked")
@Override
public <T> boolean existUniqueKeyList(String inFilterField, Set<T> inFilterValues) {
if (CollUtil.isEmpty(inFilterValues)) {
return true;
}
if (inFilterField.equals(this.idFieldName)) {
List<M> dataList = this.getInList((Set<K>) inFilterValues);
return dataList.size() == inFilterValues.size();
}
String columnName = this.safeMapToColumnName(inFilterField);
QueryWrapper<M> queryWrapper = new QueryWrapper<>();
queryWrapper.in(columnName, inFilterValues);
return mapper().selectCount(queryWrapper) == inFilterValues.size();
}
/**
* 获取缓存中的数据数量
*
* @return 缓存中的数据总量
*/
@Override
public int getCachedCount() {
return dictionaryCache.getCount();
}
}

3243
common/common-core/src/main/java/apelet/common/core/base/service/BaseService.java

File diff suppressed because it is too large Load Diff

69
common/common-core/src/main/java/apelet/common/core/base/service/IBaseDictService.java

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
package apelet.common.core.base.service;
import java.io.Serializable;
import java.util.List;
/**
* 带有缓存功能的字典Service接口
*
* @param <M> Model实体对象的类型
* @param <K> Model对象主键的类型
* @author guifc
* @date 2023-08-04
*/
public interface IBaseDictService<M, K extends Serializable> extends IBaseService<M, K> {
/**
* 重新加载数据库中所有当前表数据到系统内存
*
* @param force true则强制刷新如果false当缓存中存在数据时不刷新
*/
void reloadCachedData(boolean force);
/**
* 保存新增对象
*
* @param data 新增对象
* @return 返回新增对象
*/
M saveNew(M data);
/**
* 更新数据对象
*
* @param data 更新的对象
* @param originalData 原有数据对象
* @return 成功返回true否则false
*/
boolean update(M data, M originalData);
/**
* 删除指定数据
*
* @param id 主键Id
* @return 成功返回true否则false
*/
boolean remove(K id);
/**
* 直接从缓存池中获取所有数据
*
* @return 返回所有数据
*/
List<M> getAllListFromCache();
/**
* 根据父主键Id获取子对象列表
*
* @param parentId 上级行政区划Id
* @return 下级行政区划列表
*/
List<M> getListByParentId(K parentId);
/**
* 获取缓存中的数据数量
*
* @return 缓存中的数据总量
*/
int getCachedCount();
}

614
common/common-core/src/main/java/apelet/common/core/base/service/IBaseService.java

@ -0,0 +1,614 @@ @@ -0,0 +1,614 @@
package apelet.common.core.base.service;
import apelet.common.core.object.CallResult;
import apelet.common.core.object.MyRelationParam;
import apelet.common.core.object.TableModelInfo;
import com.baomidou.mybatisplus.extension.service.IService;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* 所有Service的接口
*
* @param <M> Model对象的类型
* @param <K> Model对象主键的类型
* @author guifc
* @date 2023-08-04
*/
public interface IBaseService<M, K extends Serializable> extends IService<M> {
/**
* 如果主键存在则更新否则新增保存实体对象
*
* @param data 实体对象数据
* @param saveNew 新增实体对象方法
* @param update 更新实体对象方法
*/
void saveNewOrUpdate(M data, Consumer<M> saveNew, BiConsumer<M, M> update);
/**
* 如果主键存在的则更新否则批量新增保存实体对象
*
* @param dataList 实体对象数据列表
* @param saveNewBatch 批量新增实体对象方法
* @param update 更新实体对象方法
*/
void saveNewOrUpdateBatch(List<M> dataList, Consumer<List<M>> saveNewBatch, BiConsumer<M, M> update);
/**
* 根据过滤条件删除数据
*
* @param filter 过滤对象
* @return 删除数量
*/
Integer removeBy(M filter);
/**
* 基于主从表之间的关联字段批量改更新一对多从表数据
* 该操作会覆盖增改三个操作具体如下
* 1. 先删除从表中relationFieldName字段的值为relationFieldValue, 同时主键Id不在dataList中的
* 2. 再批量插入遍历dataList中没有主键Id的对象视为新对象批量插入
* 3. 最后逐条更新遍历dataList中有主键Id的对象视为已存在对象并逐条更新
* 4. 如果更新时间和更新用户Id为空我们将视当前记录为变化数据因此使用当前时间和用户分别填充这两个字段
*
* @param relationFieldName 主从表关联中从表的Java字段名
* @param relationFieldValue 主从表关联中与从表关联的主表字段值该值会被赋值给从表关联字段
* @param updateUserIdFieldName 一对多从表的更新用户Id字段名
* @param updateTimeFieldName 一对多从表的更新时间字段名
* @param dataList 批量更新的从表数据列表
* @param batchInserter 从表批量插入方法
*/
void updateBatchOneToManyRelation(
String relationFieldName,
Object relationFieldValue,
String updateUserIdFieldName,
String updateTimeFieldName,
List<M> dataList,
Consumer<List<M>> batchInserter);
/**
* 判断指定字段的数据是否存在且仅仅存在一条记录
* 如果是基于主键的过滤会直接调用existId过滤函数提升性能在有缓存的场景下也可以利用缓存
*
* @param fieldName 待过滤的字段名(Java 字段)
* @param fieldValue 字段值
* @return 存在且仅存在一条返回true否则false
*/
boolean existOne(String fieldName, Object fieldValue);
/**
* 判断主键Id关联的数据是否存在
*
* @param id 主键Id
* @return 存在返回true否则false
*/
boolean existId(K id);
/**
* 返回符合过滤条件的一条数据
*
* @param filter 过滤的Java对象
* @return 查询后的数据对象
*/
M getOne(M filter);
/**
* 返回符合 filterField = filterValue 条件的一条数据
*
* @param filterField 过滤的Java字段
* @param filterValue 过滤的Java字段值
* @return 查询后的数据对象
*/
M getOne(String filterField, Object filterValue);
/**
* 获取主表的查询结果以及主表关联的字典数据和一对一从表数据以及一对一从表的字典数据
*
* @param id 主表主键Id
* @param relationParam 实体对象数据组装的参数构建器
* @return 查询结果对象
*/
M getByIdWithRelation(K id, MyRelationParam relationParam);
/**
* 获取所有数据
*
* @return 返回所有数据
*/
List<M> getAllList();
/**
* 获取排序后所有数据
*
* @param orderByProperties 需要排序的字段属性这里使用Java对象中的属性名而不是数据库字段名
* @return 返回排序后所有数据
*/
List<M> getAllListByOrder(String... orderByProperties);
/**
* 判断参数值主键集合中的所有数据是否全部存在
*
* @param idSet 待校验的主键集合
* @return 全部存在返回true否则false
*/
boolean existAllPrimaryKeys(Set<K> idSet);
/**
* 判断参数值列表中的所有数据是否全部存在另外keyName字段在数据表中必须是唯一键值否则返回结果会出现误判
*
* @param inFilterField 待校验的数据字段这里使用Java对象中的属性如courseId而不是数据字段名course_id
* @param inFilterValues 数据值列表
* @return 全部存在返回true否则false
*/
<T> boolean existUniqueKeyList(String inFilterField, Set<T> inFilterValues);
/**
* 根据过滤字段和过滤集合返回不存在的数据
*
* @param filterField 过滤的Java字段
* @param filterSet 过滤字段数据集合
* @param findFirst 是否找到第一个就返回
* @param <R> 过滤字段类型
* @return filterSet中在从表中不存在的数据集合
*/
<R> List<R> notExist(String filterField, Set<R> filterSet, boolean findFirst);
/**
* 返回符合主键 IN (idValues) 条件的所有数据
*
* @param idValues 主键值集合
* @return 检索后的数据列表
*/
List<M> getInList(Set<K> idValues);
/**
* 返回符合 inFilterField IN (inFilterValues) 条件的所有数据
*
* @param inFilterField 参与(IN-list)过滤的Java字段
* @param inFilterValues 参与(IN-list)过滤的Java字段值集合
* @return 检索后的数据列表
*/
<T> List<M> getInList(String inFilterField, Set<T> inFilterValues);
/**
* 返回符合 inFilterField IN (inFilterValues) 条件的所有数据并根据orderBy字段排序
*
* @param inFilterField 参与(IN-list)过滤的Java字段
* @param inFilterValues 参与(IN-list)过滤的Java字段值集合
* @param orderBy 排序字段
* @return 检索后的数据列表
*/
<T> List<M> getInList(String inFilterField, Set<T> inFilterValues, String orderBy);
/**
* 返回符合主键 IN (idValues) 条件的所有数据同时返回关联数据
*
* @param idValues 主键值集合
* @param relationParam 实体对象数据组装的参数构建器
* @return 检索后的数据列表
*/
List<M> getInListWithRelation(Set<K> idValues, MyRelationParam relationParam);
/**
* 返回符合 inFilterField IN (inFilterValues) 条件的所有数据同时返回关联数据
*
* @param inFilterField 参与(IN-list)过滤的Java字段
* @param inFilterValues 参与(IN-list)过滤的Java字段值集合
* @param relationParam 实体对象数据组装的参数构建器
* @return 检索后的数据列表
*/
<T> List<M> getInListWithRelation(String inFilterField, Set<T> inFilterValues, MyRelationParam relationParam);
/**
* 返回符合 inFilterField IN (inFilterValues) 条件的所有数据并根据orderBy字段排序同时返回关联数据
*
* @param inFilterField 参与(IN-list)过滤的Java字段
* @param inFilterValues 参与(IN-list)过滤的Java字段值集合
* @param orderBy 排序字段
* @param relationParam 实体对象数据组装的参数构建器
* @return 检索后的数据列表
*/
<T> List<M> getInListWithRelation(
String inFilterField, Set<T> inFilterValues, String orderBy, MyRelationParam relationParam);
/**
* 返回符合主键 NOT IN (idValues) 条件的所有数据
*
* @param idValues 主键值集合
* @return 检索后的数据列表
*/
List<M> getNotInList(Set<K> idValues);
/**
* 返回符合 inFilterField NOT IN (inFilterValues) 条件的所有数据
*
* @param inFilterField 参与(NOT IN-list)过滤的Java字段
* @param inFilterValues 参与(NOT IN-list)过滤的Java字段值集合
* @return 检索后的数据列表
*/
<T> List<M> getNotInList(String inFilterField, Set<T> inFilterValues);
/**
* 返回符合 inFilterField NOT IN (inFilterValues) 条件的所有数据并根据orderBy字段排序
*
* @param inFilterField 参与(NOT IN-list)过滤的Java字段
* @param inFilterValues 参与(NOT IN-list)过滤的Java字段值集合
* @param orderBy 排序字段
* @return 检索后的数据列表
*/
<T> List<M> getNotInList(String inFilterField, Set<T> inFilterValues, String orderBy);
/**
* 返回符合主键 NOT IN (idValues) 条件的所有数据同时返回关联数据
*
* @param idValues 主键值集合
* @param relationParam 实体对象数据组装的参数构建器
* @return 检索后的数据列表
*/
List<M> getNotInListWithRelation(Set<K> idValues, MyRelationParam relationParam);
/**
* 返回符合 inFilterField NOT IN (inFilterValues) 条件的所有数据同时返回关联数据
*
* @param inFilterField 参与(NOT IN-list)过滤的Java字段
* @param inFilterValues 参与(NOT IN-list)过滤的Java字段值集合
* @param relationParam 实体对象数据组装的参数构建器
* @return 检索后的数据列表
*/
<T> List<M> getNotInListWithRelation(String inFilterField, Set<T> inFilterValues, MyRelationParam relationParam);
/**
* 返回符合 inFilterField NOT IN (inFilterValues) 条件的所有数据并根据orderBy字段排序同时返回关联数据
*
* @param inFilterField 参与(NOT IN-list)过滤的Java字段
* @param inFilterValues 参与(NOT IN-list)过滤的Java字段值集合
* @param orderBy 排序字段
* @param relationParam 实体对象数据组装的参数构建器
* @return 检索后的数据列表
*/
<T> List<M> getNotInListWithRelation(
String inFilterField, Set<T> inFilterValues, String orderBy, MyRelationParam relationParam);
/**
* 用参数对象作为过滤条件获取数据数量
*
* @param filter 过滤对象中只有被赋值的字段才会成为where中的条件
* @return 返回过滤后的数据数量
*/
long getCountByFilter(M filter);
/**
* 用参数对象作为过滤条件判断是否存在过滤数据
*
* @param filter 过滤对象中只有被赋值的字段才会成为where中的条件
* @return 存在返回true否则false
*/
boolean existByFilter(M filter);
/**
* 用参数对象作为过滤条件获取查询结果
*
* @param filter 过滤对象中只有被赋值的字段才会成为where中的条件如果参数为null则返回全部数据
* @return 返回过滤后的数据
*/
List<M> getListByFilter(M filter);
/**
* 用参数对象作为过滤条件获取查询结果同时查询并绑定关联数据
*
* @param filter 该方法基于mybatis的通用mapper如果参数为null则返回全部数据
* @param orderBy 排序字段
* @param relationParam 实体对象数据组装的参数构建器
* @return 返回过滤后的数据
*/
List<M> getListWithRelationByFilter(M filter, String orderBy, MyRelationParam relationParam);
/**
* 获取父主键Id下的所有子数据列表
*
* @param parentIdFieldName 父主键字段名字"courseId"
* @param parentId 父主键的值
* @return 父主键Id下的所有子数据列表
*/
List<M> getListByParentId(String parentIdFieldName, K parentId);
/**
* 根据指定的显示字段列表过滤条件字符串和分组字符串返回聚合计算后的查询结果(基本是内部框架使用不建议外部接口直接使用)
*
* @param selectFields 选择的字段列表多个字段逗号分隔
* NOTE: 如果数据表字段和Java对象字段名字不同Java对象字段应该以别名的形式出现
* : table_column_name modelFieldName否则无法被反射回Bean对象
* @param whereClause SQL常量形式的条件从句
* @param groupBy SQL常量形式分组字段列表逗号分隔
* @return 聚合计算后的数据结果集
*/
List<Map<String, Object>> getGroupedListByCondition(String selectFields, String whereClause, String groupBy);
/**
* 根据指定的显示字段列表过滤条件字符串和排序字符串返回查询结果(基本是内部框架使用不建议外部接口直接使用)
*
* @param selectList 选择的Java字段列表如果为空表示返回全部字段
* @param filter 过滤对象
* @param whereClause SQL常量形式的条件从句
* @param orderBy SQL常量形式排序字段列表逗号分隔
* @return 查询结果
*/
List<M> getListByCondition(List<String> selectList, M filter, String whereClause, String orderBy);
/**
* 用指定过滤条件计算记录数量(基本是内部框架使用不建议外部接口直接使用)
*
* @param whereClause SQL常量形式的条件从句
* @return 返回过滤后的数据数量
*/
Integer getCountByCondition(String whereClause);
/**
* 仅对标记MaskField注解的字段数据进行脱敏
*
* @param data 实体对象
* @param ignoreFieldSet 忽略字段集合如果为null则对所有标记MaskField注解的字段数据进行脱敏处理
*/
void maskFieldData(M data, Set<String> ignoreFieldSet);
/**
* 仅对标记MaskField注解的字段数据进行脱敏
*
* @param dataList 实体对象列表
* @param ignoreFieldSet 忽略字段集合如果为null则对所有标记MaskField注解的字段数据进行脱敏处理
*/
void maskFieldDataList(List<M> dataList, Set<String> ignoreFieldSet);
/**
* 比较并处理脱敏字段的数据变化
* 如果data对象中的脱敏字段值和originalData字段的脱敏后值相同表示当前data对象的脱敏字段数据没有变化
* 因此需要使用数据库中的原有字段值覆盖当前实体对象中的该字段值以保证数据库表字段中始终存储的是未脱敏数据
*
* @param data 当前数据对象
* @param originalData 原数据对象
*/
void compareAndSetMaskFieldData(M data, M originalData);
/**
* 对标记MaskField注解的脱敏字段进行判断字段数据中不能包含脱敏掩码字符
*
* @param data 实体对象
*/
void verifyMaskFieldData(M data);
/**
* 根据最新对象和原有对象的数据对比判断关联的字典数据和多对一主表数据是否都是合法数据
* NOTE: BaseService中会给出返回CallResult.ok()的缺省实现每个业务服务实现类在需要的时候可以重载该方法
*
* @param data 数据对象
* @param originalData 原有数据对象null表示data为新增对象
* @return 数据全部正确返回true否则false同时返回具体的错误信息
*/
CallResult verifyRelatedData(M data, M originalData);
/**
* 根据最新对象和原有对象的数据对比判断关联的字典数据和多对一主表数据是否都是合法数据
* 如果data对象中包含主键值方法内部会获取原有对象值并进行更新方式的关联数据比对否则视为新增数据关联对象比对
*
* @param data 数据对象
* @return 应答结果对象
*/
CallResult verifyRelatedData(M data);
/**
* 根据最新对象列表和原有对象列表的数据对比判断关联的字典数据和多对一主表数据是否都是合法数据
* 如果dataList列表中的对象包含主键值方法内部会获取原有对象值并进行更新方式的关联数据比对否则视为新增数据关联对象比对
*
* @param dataList 数据对象列表
* @return 数据全部正确返回true否则false同时返回具体的错误信息
*/
CallResult verifyRelatedData(List<M> dataList);
/**
* 根据最新对象和原有对象的数据对比判断关联的远程字典数据和多对一主表数据是否都是合法数据
* NOTE: BaseService中会给出返回CallResult.ok()的缺省实现每个业务服务实现类在需要的时候可以重载该方法
*
* @param data 数据对象
* @param originalData 原有数据对象null表示data为新增对象
* @return 数据全部正确返回true否则false同时返回具体的错误信息
*/
CallResult verifyRemoteRelatedData(M data, M originalData);
/**
* 根据最新对象列表和原有对象列表的数据对比判断关联的远程字典数据和多对一主表数据是否都是合法数据
* NOTE: BaseService中会给出返回CallResult.ok()的缺省实现每个业务服务实现类在需要的时候可以重载该方法
*
* @param dataList 数据对象列表
* @return 数据全部正确返回true否则false同时返回具体的错误信息
*/
CallResult verifyRemoteRelatedData(List<M> dataList);
/**
* 根据最新对象和原有对象的数据对比判断关联的本地和远程字典数据和多对一主表数据是否都是合法数据
* NOTE: BaseService中给出了缺省实现
*
* @param data 数据对象
* @param originalData 原有数据对象null表示data为新增对象
* @return 数据全部正确返回true否则false同时返回具体的错误信息
*/
CallResult verifyAllRelatedData(M data, M originalData);
/**
* 根据最新对象和原有对象的数据对比判断关联的本地和远程字典数据和多对一主表数据是否都是合法数据
* NOTE: BaseService中给出了缺省实现
* 如果data对象中包含主键值方法内部会获取原有对象值并进行更新方式的关联数据比对否则视为新增数据关联对象比对
*
* @param data 数据对象
* @return 数据全部正确返回true否则false同时返回具体的错误信息
*/
CallResult verifyAllRelatedData(M data);
/**
* 根据最新对象列表和原有对象列表的数据对比判断关联的本地和远程字典数据和多对一主表数据是否都是合法数据
* NOTE: BaseService中给出了缺省实现
*
* @param dataList 数据对象列表
* @return 数据全部正确返回true否则false同时返回具体的错误信息
*/
CallResult verifyAllRelatedData(List<M> dataList);
/**
* 批量导入数据列表对依赖全局字典的数据进行验证
*
* @param dataList 批量导入数据列表
* @param fieldName 业务主表中依赖全局字典的字段名包含RelationGlobalDict注解的字段
* @param idGetter 获取业务主表中依赖全局字典字段值的Function对象
* @param <R> 业务主表中依全局字典的字段类型
* @return 验证结果如果失败在data中包含具体的错误对象
*/
<R> CallResult verifyImportForGlobalDict(List<M> dataList, String fieldName, Function<M, R> idGetter);
/**
* 批量导入数据列表对依赖常量字典的数据进行验证
*
* @param dataList 批量导入数据列表
* @param fieldName 业务主表中依赖常量字典的字段名包含RelationConstDict注解的字段
* @param idGetter 获取业务主表中依赖常量字典字段值的Function对象
* @param <R> 业务主表中依赖常量字典的字段类型
* @return 验证结果如果失败在data中包含具体的错误对象
*/
<R> CallResult verifyImportForConstDict(List<M> dataList, String fieldName, Function<M, R> idGetter);
/**
* 批量导入数据列表对依赖字典表字典的数据进行验证
*
* @param dataList 批量导入数据列表
* @param fieldName 业务主表中依赖字典表字典的字段名包含RelationDict注解的字段
* @param idGetter 获取业务主表中依赖字典表字典字段值的Function对象
* @param <R> 业务主表中依赖字典表字典的字段类型
* @return 验证结果如果失败在data中包含具体的错误对象
*/
<R> CallResult verifyImportForDict(List<M> dataList, String fieldName, Function<M, R> idGetter);
/**
* 批量导入数据列表对依赖数据源字典的数据进行验证
*
* @param dataList 批量导入数据列表
* @param fieldName 业务主表中依赖数据源字典的字段名包含RelationDict注解的字段的数据源字典
* @param idGetter 获取业务主表中依赖数据源字典字段值的Function对象
* @param <R> 业务主表中依赖数据源字典的字段类型
* @return 验证结果如果失败在data中包含具体的错误对象
*/
<R> CallResult verifyImportForDatasourceDict(List<M> dataList, String fieldName, Function<M, R> idGetter);
/**
* 批量导入数据列表对存在一对一关联的数据进行验证
*
* @param dataList 批量导入数据列表
* @param fieldName 业务主表中存在一对一关联的字段名包含RelationOneToOne注解的字段
* @param idGetter 获取业务主表中一对一关联字段值的Function对象
* @param <R> 业务主表中存在一对一关联的字段类型
* @return 验证结果如果失败在data中包含具体的错误对象
*/
<R> CallResult verifyImportForOneToOneRelation(List<M> dataList, String fieldName, Function<M, R> idGetter);
/**
* 集成所有与主表实体对象相关的关联数据列表包括一对一字典一对多和多对多聚合运算等
* 也可以根据实际需求单独调用该函数所包含的各个数据集成函数
* NOTE: 该方法内执行的SQL将禁用数据权限过滤
*
* @param resultList 主表实体对象列表数据集成将直接作用于该对象列表
* @param relationParam 实体对象数据组装的参数构建器
*/
void buildRelationForDataList(List<M> resultList, MyRelationParam relationParam);
/**
* 集成所有与主表实体对象相关的关联数据列表包括本地和远程服务的一对一字典一对多和多对多聚合运算等
* 也可以根据实际需求单独调用该函数所包含的各个数据集成函数
* NOTE: 该方法内执行的SQL将禁用数据权限过滤
*
* @param resultList 主表实体对象列表数据集成将直接作用于该对象列表
* @param relationParam 实体对象数据组装的参数构建器
* @param ignoreFields 该集合中的字段即便包含注解也不会在当前调用中进行数据组装
*/
void buildRelationForDataList(List<M> resultList, MyRelationParam relationParam, Set<String> ignoreFields);
/**
* 该函数主要用于对查询结果的批量导出不同于支持分页的列表查询批量导出没有分页机制
* 因此在导出数据量较大的情况下很容易给数据库的内存CPU和IO带来较大的压力而通过
* 我们的分批处理可以极大的规避该问题的出现几率调整batchSize的大小也可以有效的
* 改善运行效率
* 我们目前的处理机制是先从主表取出所有符合条件的主表数据这样可以避免分批处理时
* 后面几批数据因为skip过多而带来的效率问题因为是单表过滤不会给数据库带来过大的压力
* 之后再在主表结果集数据上进行分批级联处理
* 集成所有与主表实体对象相关的关联数据列表包括一对一字典一对多和多对多聚合运算等
* 也可以根据实际需求单独调用该函数所包含的各个数据集成函数
* NOTE: 该方法内执行的SQL将禁用数据权限过滤
*
* @param resultList 主表实体对象列表数据集成将直接作用于该对象列表
* @param relationParam 实体对象数据组装的参数构建器
* @param batchSize 每批集成的记录数量小于等于0时将不做分批处理
*/
void buildRelationForDataList(List<M> resultList, MyRelationParam relationParam, int batchSize);
/**
* 该函数主要用于对查询结果的批量导出不同于支持分页的列表查询批量导出没有分页机制
* 因此在导出数据量较大的情况下很容易给数据库的内存CPU和IO带来较大的压力而通过
* 我们的分批处理可以极大的规避该问题的出现几率调整batchSize的大小也可以有效的
* 改善运行效率
* 我们目前的处理机制是先从主表取出所有符合条件的主表数据这样可以避免分批处理时
* 后面几批数据因为skip过多而带来的效率问题因为是单表过滤不会给数据库带来过大的压力
* 之后再在主表结果集数据上进行分批级联处理
* 集成所有与主表实体对象相关的关联数据列表包括一对一字典一对多和多对多聚合运算等
* 也可以根据实际需求单独调用该函数所包含的各个数据集成函数
* NOTE: 该方法内执行的SQL将禁用数据权限过滤
*
* @param resultList 主表实体对象列表数据集成将直接作用于该对象列表
* @param relationParam 实体对象数据组装的参数构建器
* @param batchSize 每批集成的记录数量小于等于0时将不做分批处理
* @param ignoreFields 该集合中的字段即便包含注解也不会在当前调用中进行数据组装
*/
void buildRelationForDataList(
List<M> resultList, MyRelationParam relationParam, int batchSize, Set<String> ignoreFields);
/**
* 集成所有与主表实体对象相关的关联数据对象包括一对一字典一对多和多对多聚合运算等
* 也可以根据实际需求单独调用该函数所包含的各个数据集成函数
* NOTE: 该方法内执行的SQL将禁用数据权限过滤
*
* @param dataObject 主表实体对象数据集成将直接作用于该对象
* @param relationParam 实体对象数据组装的参数构建器
* @param <T> 实体对象类型
*/
<T extends M> void buildRelationForData(T dataObject, MyRelationParam relationParam);
/**
* 集成所有与主表实体对象相关的关联数据对象包括本地和远程服务的一对一字典一对多和多对多聚合运算等
* 也可以根据实际需求单独调用该函数所包含的各个数据集成函数
* NOTE: 该方法内执行的SQL将禁用数据权限过滤
*
* @param dataObject 主表实体对象数据集成将直接作用于该对象
* @param relationParam 实体对象数据组装的参数构建器
* @param ignoreFields 该集合中的字段即便包含注解也不会在当前调用中进行数据组装
* @param <T> 实体对象类型
*/
<T extends M> void buildRelationForData(T dataObject, MyRelationParam relationParam, Set<String> ignoreFields);
/**
* 仅仅在spring boot 启动后的监听器事件中调用缓存所有远程调用Client的关联关系加速后续的数据绑定效率
*/
void loadRemoteRelationStruct();
/**
* 仅仅在spring boot 启动后的监听器事件中调用缓存所有service的关联关系加速后续的数据绑定效率
*/
void loadLocalRelationStruct();
/**
* 获取当前服务引用的实体对象及表信息
*
* @return 实体对象及表信息
*/
TableModelInfo getTableModelInfo();
}

35
common/common-core/src/main/java/apelet/common/core/base/vo/BaseVo.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
package apelet.common.core.base.vo;
import lombok.Data;
import java.util.Date;
/**
* VO对象的公共基类
*
* @author guifc
* @date 2023-08-04
*/
@Data
public class BaseVo {
/**
* 创建者Id
*/
private Long createUserId;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者Id
*/
private Long updateUserId;
/**
* 更新时间
*/
private Date updateTime;
}

111
common/common-core/src/main/java/apelet/common/core/cache/CacheConfig.java vendored

@ -0,0 +1,111 @@ @@ -0,0 +1,111 @@
package apelet.common.core.cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
/**
* 使用Caffeine作为本地缓存库
*
* @author guifc
* @date 2023-08-04
*/
@Configuration
@EnableCaching
public class CacheConfig {
private static final int DEFAULT_MAXSIZE = 10000;
private static final int DEFAULT_TTL = 3600;
/**
* 定义cache名称超时时长秒最大个数
* 每个cache缺省3600秒过期最大个数1000
*/
public enum CacheEnum {
/**
* 专门存储用户权限的缓存(600秒)
*/
USER_PERMISSION_CACHE(600, 10000),
/**
* 专门存储用户数据权限的缓存(600秒)
*/
DATA_PERMISSION_CACHE(600, 10000),
/**
* 专门存储用户菜单关联权限的缓存(600秒)
*/
MENU_PERM_CACHE(600, 10000),
/**
* 存储指定部门Id集合的所有子部门Id结合(600秒)
*/
CHILDREN_DEPT_ID_CACHE(1800, 10000),
/**
* 在线表单组件渲染数据缓存
*/
ONLINE_FORM_RENDER_CACCHE(600, 100),
/**
* 报表表单组件渲染数据缓存
*/
REPORT_FORM_RENDER_CACCHE(600, 100),
/**
* 缺省全局缓存(时间是24小时)
*/
GLOBAL_CACHE(86400, 20000),
/**
* 元数据缓存
*/
METAS_CACHE(86400, 2000);
CacheEnum() {
}
CacheEnum(int ttl, int maxSize) {
this.ttl = ttl;
this.maxSize = maxSize;
}
/**
* 缓存的最大数量
*/
private int maxSize = DEFAULT_MAXSIZE;
/**
* 缓存的时长(单位)
*/
private int ttl = DEFAULT_TTL;
public int getMaxSize() {
return maxSize;
}
public int getTtl() {
return ttl;
}
}
/**
* 初始化缓存配置这里为了有别于Redisson的缓存
*/
@Bean("caffeineCacheManager")
public CacheManager cacheManager() {
SimpleCacheManager manager = new SimpleCacheManager();
// 把各个cache注册到cacheManager中,CaffeineCache实现了org.springframework.cache.Cache接口
ArrayList<CaffeineCache> caches = new ArrayList<>();
for (CacheEnum c : CacheEnum.values()) {
caches.add(new CaffeineCache(c.name(),
Caffeine.newBuilder().recordStats()
.expireAfterWrite(c.getTtl(), TimeUnit.SECONDS)
.maximumSize(c.getMaxSize())
.build())
);
}
manager.setCaches(caches);
return manager;
}
}

60
common/common-core/src/main/java/apelet/common/core/cache/CacheKey.java vendored

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
package apelet.common.core.cache;
/**
* 在线表单缓存
* @author Soon
* @version 1.0
* Create by 2024/10/29 15:13
*/
public class CacheKey {
/**
* ORM 封装元数据 + 表名
*/
public final static String EntityTable = "EntityTable:";
/**
*表单元数据字段集合 + headId 表单元数据id
*/
public final static String FORM_FIELD_BY_HEAD_Id = "FORM_FIELD_BY_HEAD_Id:";
public static String makeEntityTableKey(String datasourceVariableName) {
return "EntityTable:" + datasourceVariableName;
}
public static String makeTableKey(Long id) {
return "FORM_HEAD_TABLE:" + id;
}
public static String makeFormHeadByTableId(Long id) {
return "FORM_HEAD_BY_TABLE_ID:" + id;
}
public static String makeTableKey(String tableName) {
return "FORM_HEAD_TABLE_NAME:" + tableName;
}
public static String makeChildFieldListKey(Long id) {
return "FORM_CHILD_FIELD:" + id;
}
public static String makeOneToManyTable(String tableName) {
return "FORM_ONETOMANY_TABLE:" + tableName;
}
public static String getFormFieldByHeadId(long headId) {
return FORM_FIELD_BY_HEAD_Id + headId;
}
}

89
common/common-core/src/main/java/apelet/common/core/cache/DictionaryCache.java vendored

@ -0,0 +1,89 @@ @@ -0,0 +1,89 @@
package apelet.common.core.cache;
import java.util.List;
import java.util.Set;
/**
* 主要用于完整缓存字典表数据的接口对象
*
* @param <K> 字典表主键类型
* @param <V> 字典表对象类型
* @author guifc
* @date 2023-08-04
*/
public interface DictionaryCache<K, V> {
/**
* 按照数据插入的顺序返回全部字典对象的列表
*
* @return 全部字段数据列表
*/
List<V> getAll();
/**
* 获取缓存中与键列表对应的对象列表
*
* @param keys 主键集合
* @return 对象列表
*/
List<V> getInList(Set<K> keys);
/**
* 重新加载如果数据列表为空则会清空原有缓存数据
*
* @param dataList 待缓存的数据列表
* @param force true则强制刷新如果false当缓存中存在数据时不刷新
*/
void reload(List<V> dataList, boolean force);
/**
* 从缓存中获取指定的数据
*
* @param key 数据的key
* @return 获取到的数据如果没有返回null
*/
V get(K key);
/**
* 将数据存入缓存
*
* @param key 通常为字典数据的主键
* @param object 字典数据对象
*/
void put(K key, V object);
/**
* 获取缓存中数据条目的数量
*
* @return 返回缓存的数据数量
*/
int getCount();
/**
* 删除缓存中指定的键
*
* @param key 待删除数据的主键
* @return 返回被删除的对象如果主键不存在返回null
*/
V invalidate(K key);
/**
* 删除缓存中参数列表中包含的键
*
* @param keys 待删除数据的主键集合
*/
void invalidateSet(Set<K> keys);
/**
* 清空缓存
*/
void invalidateAll();
/**
* 根据父主键Id获取所有子对象的列表
*
* @param parentId 父主键Id如果parentId为null则返回所有一级节点数据
* @return 所有子对象的列表
*/
default List<V> getListByParentId(K parentId) { throw new UnsupportedOperationException(); }
}

200
common/common-core/src/main/java/apelet/common/core/cache/MapDictionaryCache.java vendored

@ -0,0 +1,200 @@ @@ -0,0 +1,200 @@
package apelet.common.core.cache;
import apelet.common.core.exception.MapCacheAccessException;
import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* 字典数据内存缓存对象
*
* @param <K> 字典表主键类型
* @param <V> 字典表对象类型
* @author guifc
* @date 2023-08-04
*/
@Slf4j
public class MapDictionaryCache<K, V> implements DictionaryCache<K, V> {
/**
* 存储字典数据的Map
*/
protected final LinkedHashMap<K, V> dataMap = new LinkedHashMap<>();
/**
* 获取字典主键数据的函数对象
*/
protected final Function<V, K> idGetter;
/**
* 由于大部分场景是读取操作所以使用读写锁提高并发的伸缩性
*/
protected final ReadWriteLock lock = new ReentrantReadWriteLock();
/**
* 超时时长单位毫秒
*/
protected static final long TIMEOUT = 2000L;
/**
* 当前对象的构造器函数
*
* @param idGetter 获取当前类主键字段值的函数对象
* @param <K> 字典主键类型
* @param <V> 字典对象类型
* @return 实例化后的字典内存缓存对象
*/
public static <K, V> MapDictionaryCache<K, V> create(Function<V, K> idGetter) {
if (idGetter == null) {
throw new IllegalArgumentException("IdGetter can't be NULL.");
}
return new MapDictionaryCache<>(idGetter);
}
/**
* 构造函数
*
* @param idGetter 主键Id的获取函数对象
*/
public MapDictionaryCache(Function<V, K> idGetter) {
this.idGetter = idGetter;
}
@Override
public List<V> getAll() {
return this.safeRead("getAll", () -> {
List<V> resultList = new LinkedList<>();
if (MapUtil.isNotEmpty(dataMap)) {
resultList.addAll(dataMap.values());
}
return resultList;
});
}
@Override
public List<V> getInList(Set<K> keys) {
return this.safeRead("getInList", () -> {
List<V> resultList = new LinkedList<>();
keys.forEach(key -> {
V object = dataMap.get(key);
if (object != null) {
resultList.add(object);
}
});
return resultList;
});
}
@Override
public V get(K id) {
if (id == null) {
return null;
}
return this.safeRead("get", () -> dataMap.get(id));
}
@Override
public void reload(List<V> dataList, boolean force) {
if (!force && this.getCount() > 0) {
return;
}
this.safeWrite("reload", () -> {
dataMap.clear();
dataList.forEach(dataObj -> {
K id = idGetter.apply(dataObj);
dataMap.put(id, dataObj);
});
return null;
});
}
@Override
public void put(K id, V object) {
this.safeWrite("put", () -> dataMap.put(id, object));
}
@Override
public int getCount() {
return dataMap.size();
}
@Override
public V invalidate(K id) {
if (id == null) {
return null;
}
return this.safeWrite("invalidate", () -> dataMap.remove(id));
}
@Override
public void invalidateSet(Set<K> keys) {
this.safeWrite("invalidateSet", () -> {
keys.forEach(id -> {
if (id != null) {
dataMap.remove(id);
}
});
return null;
});
}
@Override
public void invalidateAll() {
this.safeWrite("invalidateAll", () -> {
dataMap.clear();
return null;
});
}
protected <T> T safeRead(String functionName, Supplier<T> supplier) {
String exceptionMessage;
try {
if (lock.readLock().tryLock(TIMEOUT, TimeUnit.MILLISECONDS)) {
try {
return supplier.get();
} finally {
lock.readLock().unlock();
}
} else {
throw new TimeoutException();
}
} catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
exceptionMessage = String.format(
"LOCK Operation of [MapDictionaryCache::%s] encountered EXCEPTION [%s] for DICT.",
functionName, e.getClass().getSimpleName());
log.warn(exceptionMessage);
throw new MapCacheAccessException(exceptionMessage, e);
}
}
protected <T> T safeWrite(String functionName, Supplier<T> supplier) {
String exceptionMessage;
try {
if (lock.writeLock().tryLock(TIMEOUT, TimeUnit.MILLISECONDS)) {
try {
return supplier.get();
} finally {
lock.writeLock().unlock();
}
} else {
throw new TimeoutException();
}
} catch (Exception e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
exceptionMessage = String.format(
"LOCK Operation of [MapDictionaryCache::%s] encountered EXCEPTION [%s] for DICT.",
functionName, e.getClass().getSimpleName());
log.warn(exceptionMessage);
throw new MapCacheAccessException(exceptionMessage, e);
}
}
}

138
common/common-core/src/main/java/apelet/common/core/cache/MapTreeDictionaryCache.java vendored

@ -0,0 +1,138 @@ @@ -0,0 +1,138 @@
package apelet.common.core.cache;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.function.Function;
/**
* 树形字典数据内存缓存对象
*
* @param <K> 字典表主键类型
* @param <V> 字典表对象类型
* @author guifc
* @date 2023-08-04
*/
@Slf4j
public class MapTreeDictionaryCache<K, V> extends MapDictionaryCache<K, V> {
/**
* 树形数据存储对象
*/
private final Multimap<K, V> allTreeMap = LinkedHashMultimap.create();
/**
* 获取字典父主键数据的函数对象
*/
protected final Function<V, K> parentIdGetter;
/**
* 当前对象的构造器函数
*
* @param idGetter 获取当前类主键字段值的函数对象
* @param parentIdGetter 获取当前类父主键字段值的函数对象
* @param <K> 字典主键类型
* @param <V> 字典对象类型
* @return 实例化后的树形字典内存缓存对象
*/
public static <K, V> MapTreeDictionaryCache<K, V> create(Function<V, K> idGetter, Function<V, K> parentIdGetter) {
if (idGetter == null) {
throw new IllegalArgumentException("IdGetter can't be NULL.");
}
if (parentIdGetter == null) {
throw new IllegalArgumentException("ParentIdGetter can't be NULL.");
}
return new MapTreeDictionaryCache<>(idGetter, parentIdGetter);
}
/**
* 构造函数
*
* @param idGetter 获取当前类主键字段值的函数对象
* @param parentIdGetter 获取当前类父主键字段值的函数对象
*/
public MapTreeDictionaryCache(Function<V, K> idGetter, Function<V, K> parentIdGetter) {
super(idGetter);
this.parentIdGetter = parentIdGetter;
}
@Override
public void reload(List<V> dataList, boolean force) {
if (!force && this.getCount() > 0) {
return;
}
this.safeWrite("reload", () -> {
dataMap.clear();
allTreeMap.clear();
dataList.forEach(data -> {
K id = idGetter.apply(data);
dataMap.put(id, data);
K parentId = parentIdGetter.apply(data);
allTreeMap.put(parentId, data);
});
return null;
});
}
@Override
public List<V> getListByParentId(K parentId) {
return this.safeRead("getListByParentId", () -> {
List<V> resultList = new LinkedList<>();
Collection<V> children = allTreeMap.get(parentId);
if (CollUtil.isNotEmpty(children)) {
resultList.addAll(children);
}
return resultList;
});
}
@Override
public void put(K id, V data) {
this.safeWrite("put", () -> {
dataMap.put(id, data);
K parentId = parentIdGetter.apply(data);
allTreeMap.remove(parentId, data);
allTreeMap.put(parentId, data);
return null;
});
}
@Override
public V invalidate(K id) {
return this.safeWrite("invalidate", () -> {
V v = dataMap.remove(id);
if (v != null) {
K parentId = parentIdGetter.apply(v);
allTreeMap.remove(parentId, v);
}
return v;
});
}
@Override
public void invalidateSet(Set<K> keys) {
this.safeWrite("invalidateSet", () -> {
keys.forEach(id -> {
if (id != null) {
V data = dataMap.remove(id);
if (data != null) {
K parentId = parentIdGetter.apply(data);
allTreeMap.remove(parentId, data);
}
}
});
return null;
});
}
@Override
public void invalidateAll() {
this.safeWrite("invalidateAll", () -> {
dataMap.clear();
allTreeMap.clear();
return null;
});
}
}

60
common/common-core/src/main/java/apelet/common/core/config/BaseMultiDataSourceConfig.java

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
package apelet.common.core.config;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 基于Druid的数据源配置的基类
*
* @author guifc
* @date 2023-08-04
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.druid")
public class BaseMultiDataSourceConfig {
private String driverClassName;
private String name;
private Integer initialSize;
private Integer minIdle;
private Integer maxActive;
private Integer maxWait;
private Integer timeBetweenEvictionRunsMillis;
private Integer minEvictableIdleTimeMillis;
private Boolean poolPreparedStatements;
private Integer maxPoolPreparedStatementPerConnectionSize;
private Integer maxOpenPreparedStatements;
private String validationQuery;
private Boolean testWhileIdle;
private Boolean testOnBorrow;
private Boolean testOnReturn;
/**
* 将连接池的通用配置应用到数据源对象上
*
* @param druidDataSource Druid的数据源
* @return 应用后的Druid数据源
*/
protected DruidDataSource applyCommonProps(DruidDataSource druidDataSource) {
druidDataSource.setConnectionErrorRetryAttempts(5);
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setName(name);
druidDataSource.setInitialSize(initialSize);
druidDataSource.setMinIdle(minIdle);
druidDataSource.setMaxActive(maxActive);
druidDataSource.setMaxWait(maxWait);
druidDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
druidDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
druidDataSource.setPoolPreparedStatements(poolPreparedStatements);
druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
druidDataSource.setMaxOpenPreparedStatements(maxOpenPreparedStatements);
druidDataSource.setValidationQuery(validationQuery);
druidDataSource.setTestWhileIdle(testWhileIdle);
druidDataSource.setTestOnBorrow(testOnBorrow);
druidDataSource.setTestOnReturn(testOnReturn);
return druidDataSource;
}
}

87
common/common-core/src/main/java/apelet/common/core/config/CommonWebMvcConfig.java

@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
package apelet.common.core.config;
import apelet.common.core.interceptor.MyRequestArgumentResolver;
import apelet.common.core.util.ContextUtil;
import apelet.common.core.util.MyDateUtil;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* 所有的项目拦截器参数解析器消息对象转换器都在这里集中配置
*
* @author guifc
* @date 2023-08-04
*/
@Configuration
public class CommonWebMvcConfig implements WebMvcConfigurer {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
// 添加MyRequestBody参数解析器
argumentResolvers.add(new MyRequestArgumentResolver());
}
private HttpMessageConverter<String> responseBodyConverter() {
return new StringHttpMessageConverter(StandardCharsets.UTF_8);
}
@Bean
public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter fastConverter = new MyFastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
fastConverter.setSupportedMediaTypes(supportedMediaTypes);
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
SerializerFeature.PrettyFormat,
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.IgnoreNonFieldGetter);
fastJsonConfig.setDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT);
fastConverter.setFastJsonConfig(fastJsonConfig);
return fastConverter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(responseBodyConverter());
converters.add(new ByteArrayHttpMessageConverter());
converters.add(fastJsonHttpMessageConverter());
}
public static class MyFastJsonHttpMessageConverter extends FastJsonHttpMessageConverter {
@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
HttpServletRequest request = ContextUtil.getHttpRequest();
if (request == null) {
return super.canWrite(type, clazz, mediaType);
}
if (request.getRequestURI().contains("/v3/api-docs")) {
return false;
}
return super.canWrite(type, clazz, mediaType);
}
}
}

83
common/common-core/src/main/java/apelet/common/core/config/CoreProperties.java

@ -0,0 +1,83 @@ @@ -0,0 +1,83 @@
package apelet.common.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* common-core的配置属性类
*
* @author guifc
* @date 2023-08-04
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "common-core")
public class CoreProperties {
public static final String MYSQL_TYPE = "mysql";
public static final String POSTGRESQL_TYPE = "postgresql";
public static final String ORACLE_TYPE = "oracle";
public static final String DM_TYPE = "dm8";
public static final String KINGBASE_TYPE = "kingbase";
public static final String OPENGAUSS_TYPE = "opengauss";
/**
* 数据库类型
*/
private String databaseType = MYSQL_TYPE;
/**
* 是否为MySQL
*
* @return 是返回true否则false
*/
public boolean isMySql() {
return this.databaseType.equals(MYSQL_TYPE);
}
/**
* 是否为PostgreSQl
*
* @return 是返回true否则false
*/
public boolean isPostgresql() {
return this.databaseType.equals(POSTGRESQL_TYPE);
}
/**
* 是否为Oracle
*
* @return 是返回true否则false
*/
public boolean isOracle() {
return this.databaseType.equals(ORACLE_TYPE);
}
/**
* 是否为达梦8
*
* @return 是返回true否则false
*/
public boolean isDm() {
return this.databaseType.equals(DM_TYPE);
}
/**
* 是否为人大金仓
*
* @return 是返回true否则false
*/
public boolean isKingbase() {
return this.databaseType.equals(KINGBASE_TYPE);
}
/**
* 是否为华为高斯
*
* @return 是返回true否则false
*/
public boolean isOpenGauss() {
return this.databaseType.equals(OPENGAUSS_TYPE);
}
}

52
common/common-core/src/main/java/apelet/common/core/config/DataSourceContextHolder.java

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
package apelet.common.core.config;
/**
* 通过线程本地存储的方式保存当前数据库操作所需的数据源类型动态数据源会根据该值进行动态切换
*
* @author guifc
* @date 2023-08-04
*/
public class DataSourceContextHolder {
private static final ThreadLocal<Integer> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源类型
*
* @param type 数据源类型
* @return 原有数据源类型如果第一次设置则返回null
*/
public static Integer setDataSourceType(Integer type) {
Integer datasourceType = CONTEXT_HOLDER.get();
CONTEXT_HOLDER.set(type);
return datasourceType;
}
/**
* 获取当前数据库操作执行线程的数据源类型同时由动态数据源的路由函数调用
*
* @return 数据源类型
*/
public static Integer getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清除线程本地变量以免内存泄漏
* @param originalType 原有的数据源类型如果该值为null则情况本地化变量
*/
public static void unset(Integer originalType) {
if (originalType == null) {
CONTEXT_HOLDER.remove();
} else {
CONTEXT_HOLDER.set(originalType);
}
}
/**
* 私有构造函数明确标识该常量类的作用
*/
private DataSourceContextHolder() {
}
}

41
common/common-core/src/main/java/apelet/common/core/config/DataSourceInfo.java

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
package apelet.common.core.config;
import lombok.Data;
/**
* 主要用户动态多数据源使用的配置数据
*
* @author guifc
* @date 2023-08-04
*/
@Data
public class DataSourceInfo {
/**
* 用于多数据源切换的数据源类型
*/
private Integer datasourceType;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 数据库主机
*/
private String databaseHost;
/**
* 端口号
*/
private Integer port;
/**
* 模式名
*/
private String schemaName;
/**
* 数据库名称
*/
private String databaseName;
}

170
common/common-core/src/main/java/apelet/common/core/config/DynamicDataSource.java

@ -0,0 +1,170 @@ @@ -0,0 +1,170 @@
package apelet.common.core.config;
import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.Assert;
import java.util.*;
/**
* 动态数据源对象当存在多个数据连接时使用
*
* @author guifc
* @date 2023-08-04
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Autowired
private BaseMultiDataSourceConfig baseMultiDataSourceConfig;
@Autowired
private CoreProperties properties;
private Set<Integer> dynamicDatasourceTypeSet = new HashSet<>();
private static final String ASSERT_MSG = "defaultTargetDatasource can't be null.";
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
/**
* 重新加载动态添加的数据源既清空之前动态添加的数据源同时添加参数中的新数据源列表
*
* @param dataSourceInfoList 新动态数据源列表
*/
public synchronized void reloadAll(List<DataSourceInfo> dataSourceInfoList) {
Map<Object, Object> dataSourceMap = new HashMap<>(this.getResolvedDataSources());
dynamicDatasourceTypeSet.forEach(dataSourceMap::remove);
dynamicDatasourceTypeSet.clear();
for (DataSourceInfo dataSourceInfo : dataSourceInfoList) {
dynamicDatasourceTypeSet.add(dataSourceInfo.getDatasourceType());
DruidDataSource dataSource = this.doConvert(dataSourceInfo);
baseMultiDataSourceConfig.applyCommonProps(dataSource);
dataSourceMap.put(dataSourceInfo.getDatasourceType(), dataSource);
}
Object defaultTargetDatasource = this.getResolvedDefaultDataSource();
Assert.notNull(defaultTargetDatasource, ASSERT_MSG);
this.setTargetDataSources(dataSourceMap);
this.setDefaultTargetDataSource(defaultTargetDatasource);
super.afterPropertiesSet();
}
/**
* 添加动态添加数据源
*
* 动态添加数据源
*/
public synchronized void addDataSource(DataSourceInfo dataSourceInfo) {
if (dynamicDatasourceTypeSet.contains(dataSourceInfo.getDatasourceType())) {
return;
}
dynamicDatasourceTypeSet.add(dataSourceInfo.getDatasourceType());
Map<Object, Object> dataSourceMap = new HashMap<>(this.getResolvedDataSources());
DruidDataSource dataSource = this.doConvert(dataSourceInfo);
baseMultiDataSourceConfig.applyCommonProps(dataSource);
dataSourceMap.put(dataSourceInfo.getDatasourceType(), dataSource);
Object defaultTargetDatasource = this.getResolvedDefaultDataSource();
Assert.notNull(defaultTargetDatasource, ASSERT_MSG);
this.setTargetDataSources(dataSourceMap);
this.setDefaultTargetDataSource(defaultTargetDatasource);
super.afterPropertiesSet();
}
/**
* 添加动态添加数据源列表
*
* @param dataSourceInfoList 数据源信息列表
*/
public synchronized void addDataSources(List<DataSourceInfo> dataSourceInfoList) {
Map<Object, Object> dataSourceMap = new HashMap<>(this.getResolvedDataSources());
for (DataSourceInfo dataSourceInfo : dataSourceInfoList) {
if (!dynamicDatasourceTypeSet.contains(dataSourceInfo.getDatasourceType())) {
dynamicDatasourceTypeSet.add(dataSourceInfo.getDatasourceType());
DruidDataSource dataSource = this.doConvert(dataSourceInfo);
baseMultiDataSourceConfig.applyCommonProps(dataSource);
dataSourceMap.put(dataSourceInfo.getDatasourceType(), dataSource);
}
}
Object defaultTargetDatasource = this.getResolvedDefaultDataSource();
Assert.notNull(defaultTargetDatasource, ASSERT_MSG);
this.setTargetDataSources(dataSourceMap);
this.setDefaultTargetDataSource(defaultTargetDatasource);
super.afterPropertiesSet();
}
/**
* 动态移除数据源
*
* @param datasourceType 数据源类型
*/
public synchronized void removeDataSource(int datasourceType) {
if (!dynamicDatasourceTypeSet.remove(datasourceType)) {
return;
}
Map<Object, Object> dataSourceMap = new HashMap<>(this.getResolvedDataSources());
dataSourceMap.remove(datasourceType);
Object defaultTargetDatasource = this.getResolvedDefaultDataSource();
Assert.notNull(defaultTargetDatasource, ASSERT_MSG);
this.setTargetDataSources(dataSourceMap);
this.setDefaultTargetDataSource(defaultTargetDatasource);
super.afterPropertiesSet();
}
private DruidDataSource doConvert(DataSourceInfo dataSourceInfo) {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
dataSource.setUsername(dataSourceInfo.getUsername());
dataSource.setPassword(dataSourceInfo.getPassword());
StringBuilder urlBuilder = new StringBuilder(256);
String hostAndPort = dataSourceInfo.getDatabaseHost() + ":" + dataSourceInfo.getPort();
if (properties.isMySql()) {
urlBuilder.append("jdbc:mysql://")
.append(hostAndPort)
.append("/")
.append(dataSourceInfo.getDatabaseName())
.append("?characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai");
} else if (properties.isOracle()) {
urlBuilder.append("jdbc:oracle:thin:@")
.append(hostAndPort)
.append(":")
.append(dataSourceInfo.getDatabaseName());
} else if (properties.isPostgresql()) {
urlBuilder.append("jdbc:postgresql://")
.append(hostAndPort)
.append("/")
.append(dataSourceInfo.getDatabaseName());
if (StrUtil.isBlank(dataSourceInfo.getSchemaName())) {
urlBuilder.append("?currentSchema=public");
} else {
urlBuilder.append("?currentSchema=").append(dataSourceInfo.getSchemaName());
}
urlBuilder.append("&TimeZone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8");
} else if (properties.isDm()) {
urlBuilder.append("jdbc:dm://")
.append(hostAndPort)
.append("?schema=")
.append(dataSourceInfo.getDatabaseName())
.append("&useJDBCCompliantTimezoneShift=true&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8");
} else if (properties.isKingbase()) {
urlBuilder.append("jdbc:kingbase8://")
.append(hostAndPort)
.append("/")
.append(dataSourceInfo.getDatabaseName())
.append("?useJDBCCompliantTimezoneShift=true&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8");
} else if (properties.isOpenGauss()) {
urlBuilder.append("jdbc:opengauss://")
.append(hostAndPort)
.append("/")
.append(dataSourceInfo.getDatabaseName());
if (StrUtil.isBlank(dataSourceInfo.getSchemaName())) {
urlBuilder.append("?currentSchema=public");
} else {
urlBuilder.append("?currentSchema=").append(dataSourceInfo.getSchemaName());
}
}
dataSource.setUrl(urlBuilder.toString());
return dataSource;
}
}

20
common/common-core/src/main/java/apelet/common/core/config/EncryptConfig.java

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
package apelet.common.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* 目前用于用户密码加密UAA接入应用客户端的client_secret加密
*
* @author guifc
* @date 2023-08-04
*/
@Configuration
public class EncryptConfig {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

69
common/common-core/src/main/java/apelet/common/core/config/FeignConfig.java

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
package apelet.common.core.config;
import apelet.common.core.constant.ApplicationConstant;
import apelet.common.core.object.TokenData;
import apelet.common.core.util.ContextUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* FeignClient的配置对象
*
* @author guifc
* @date 2023-08-04
*/
@Configuration
public class FeignConfig implements RequestInterceptor {
@SneakyThrows
@Override
public void apply(RequestTemplate requestTemplate) {
// 对于非servlet请求发起的远程调用,由于无法获取到标识用户身份的TokenData,因此需要略过下面的HEADER注入。
// 如:由消息队列consumer发起的远程调用请求。
if (!ContextUtil.hasRequestContext()) {
return;
}
HttpServletRequest request = ContextUtil.getHttpRequest();
String tokenData = request.getHeader(TokenData.REQUEST_ATTRIBUTE_NAME);
if (StrUtil.isBlank(tokenData)) {
Object token = request.getAttribute(TokenData.REQUEST_ATTRIBUTE_NAME);
if (token != null) {
tokenData = JSON.toJSONString(token);
}
}
if (StrUtil.isNotBlank(tokenData)) {
String encodedTokenData = tokenData;
if (StrUtil.isBlank(request.getHeader(TokenData.REQUEST_ENCODED_TOKEN))) {
encodedTokenData = URLEncoder.encode(tokenData, StandardCharsets.UTF_8.name());
}
requestTemplate.header(TokenData.REQUEST_ATTRIBUTE_NAME, encodedTokenData);
requestTemplate.header(TokenData.REQUEST_ENCODED_TOKEN, Boolean.TRUE.toString());
}
String traceId = request.getHeader(ApplicationConstant.HTTP_HEADER_TRACE_ID);
if (StrUtil.isBlank(traceId)) {
traceId = (String) request.getAttribute(ApplicationConstant.HTTP_HEADER_TRACE_ID);
}
if (StrUtil.isNotBlank(traceId)) {
requestTemplate.header(ApplicationConstant.HTTP_HEADER_TRACE_ID, traceId);
}
String originalUrl = request.getHeader(ApplicationConstant.HTTP_HEADER_ORIGINAL_REQUEST_URL);
if (StrUtil.isNotBlank(originalUrl)) {
requestTemplate.header(ApplicationConstant.HTTP_HEADER_ORIGINAL_REQUEST_URL, originalUrl);
}
String menuId = request.getHeader(ApplicationConstant.HTTP_HEADER_MENU_ID);
if (menuId == null) {
menuId = request.getParameter(ApplicationConstant.HTTP_HEADER_MENU_ID);
}
if (StrUtil.isNotBlank(menuId)) {
requestTemplate.header(ApplicationConstant.HTTP_HEADER_MENU_ID, menuId);
}
}
}

21
common/common-core/src/main/java/apelet/common/core/config/MybatisPlusKeyGeneratorConfig.java

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
package apelet.common.core.config;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.extension.incrementer.OracleKeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 仅仅用于Oracle基于Sequence计算自增字段值的生成器
*
* @author guifc
* @date 2023-08-04
*/
@Configuration
public class MybatisPlusKeyGeneratorConfig {
@Bean
public IKeyGenerator keyGenerator() {
return new OracleKeyGenerator();
}
}

64
common/common-core/src/main/java/apelet/common/core/config/RestTemplateConfig.java

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
package apelet.common.core.config;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* RestTemplate连接池配置对象
*
* @author guifc
* @date 2023-08-04
*/
@Configuration
public class RestTemplateConfig {
private static final int MAX_TOTAL_CONNECTION = 50;
private static final int MAX_CONNECTION_PER_ROUTE = 20;
private static final int CONNECTION_TIMEOUT = 20000;
private static final int READ_TIMEOUT = 30000;
@Bean
@ConditionalOnMissingBean({RestOperations.class, RestTemplate.class})
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate(createFactory());
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
messageConverters.removeIf(
c -> c instanceof StringHttpMessageConverter || c instanceof MappingJackson2HttpMessageConverter);
messageConverters.add(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
messageConverters.add(new FastJsonHttpMessageConverter());
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
// 防止400+和500等错误被直接抛出异常,这里避开了缺省处理方式,所有的错误均交给业务代码处理。
}
});
return restTemplate;
}
private ClientHttpRequestFactory createFactory() {
HttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(MAX_TOTAL_CONNECTION)
.setMaxConnPerRoute(MAX_CONNECTION_PER_ROUTE)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setReadTimeout(READ_TIMEOUT);
factory.setConnectTimeout(CONNECTION_TIMEOUT);
return factory;
}
}

39
common/common-core/src/main/java/apelet/common/core/config/TomcatConfig.java

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
package apelet.common.core.config;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* tomcat配置对象当前配置禁用了PUT和DELETE方法防止渗透攻击
*
* @author guifc
* @date 2023-08-04
*/
@Configuration
public class TomcatConfig {
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addContextCustomizers(context -> {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
collection.addMethod("HEAD");
collection.addMethod("PUT");
collection.addMethod("PATCH");
collection.addMethod("DELETE");
collection.addMethod("TRACE");
collection.addMethod("COPY");
collection.addMethod("SEARCH");
collection.addMethod("PROPFIND");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
});
return factory;
}
}

36
common/common-core/src/main/java/apelet/common/core/constant/AggregationKind.java

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
package apelet.common.core.constant;
/**
* 在跨服务调用时需要传递的聚合分类常量对象
*
* @author guifc
* @date 2023-08-04
*/
public final class AggregationKind {
/**
* 一对多关联中聚合从表中指定字段将多条记录汇总到主表的指定字段上
*/
public static final int ONE_TO_MANY = 1;
/**
* 多对多关联中聚合从表中指定字段将多条记录汇总到主表的指定字段上
*/
public static final int MANY_TO_MANY = 2;
/**
* 判断参数是否为当前常量字典的合法值
*
* @param aggregationKind 待验证的参数值
* @return 合法返回true否则false
*/
public static boolean isValid(Integer aggregationKind) {
return aggregationKind != null &&
(aggregationKind == ONE_TO_MANY || aggregationKind == MANY_TO_MANY);
}
/**
* 私有构造函数明确标识该常量类的作用
*/
private AggregationKind() {
}
}

81
common/common-core/src/main/java/apelet/common/core/constant/AggregationType.java

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
package apelet.common.core.constant;
import java.util.HashMap;
import java.util.Map;
/**
* 聚合计算的常量类型对象
*
* @author guifc
* @date 2023-08-04
*/
public final class AggregationType {
/**
* sum 计数
*/
public static final int SUM = 0;
/**
* count 汇总
*/
public static final int COUNT = 1;
/**
* average 平均值
*/
public static final int AVG = 2;
/**
* min 最小值
*/
public static final int MIN = 3;
/**
* max 最大值
*/
public static final int MAX = 4;
private static final Map<Object, String> DICT_MAP = new HashMap<>(5);
static {
DICT_MAP.put(SUM, "累计总和");
DICT_MAP.put(COUNT, "数量总和");
DICT_MAP.put(AVG, "平均值");
DICT_MAP.put(MIN, "最小值");
DICT_MAP.put(MAX, "最大值");
}
/**
* 判断参数是否为当前常量字典的合法值
*
* @param value 待验证的参数值
* @return 合法返回true否则false
*/
public static boolean isValid(Integer value) {
return value != null && DICT_MAP.containsKey(value);
}
/**
* 获取与SQL对应的聚合函数字符串名称
*
* @return 聚合函数名称
*/
public static String getAggregationFunction(Integer aggregationType) {
switch (aggregationType) {
case COUNT:
return "COUNT";
case AVG:
return "AVG";
case SUM:
return "SUM";
case MAX:
return "MAX";
case MIN:
return "MIN";
default:
throw new IllegalArgumentException("无效的聚合类型!");
}
}
/**
* 私有构造函数明确标识该常量类的作用
*/
private AggregationType() {
}
}

68
common/common-core/src/main/java/apelet/common/core/constant/AppDeviceType.java

@ -0,0 +1,68 @@ @@ -0,0 +1,68 @@
package apelet.common.core.constant;
import apelet.common.core.object.TokenData;
import java.util.HashMap;
import java.util.Map;
/**
* App 登录的设备类型
*
* @author guifc
* @date 2023-08-04
*/
public final class AppDeviceType {
/**
* 移动端 (如果不考虑区分android或ios的可以使用该值)
*/
public static final int MOBILE = 0;
/**
* android
*/
public static final int ANDROID = 1;
/**
* iOS
*/
public static final int IOS = 2;
/**
* 微信公众号和小程序
*/
public static final int WEIXIN = 3;
/**
* PC WEB
*/
public static final int WEB = 4;
private static final Map<Object, String> DICT_MAP = new HashMap<>(5);
static {
DICT_MAP.put(MOBILE, "移动端");
DICT_MAP.put(ANDROID, "Android");
DICT_MAP.put(IOS, "iOS");
DICT_MAP.put(WEIXIN, "微信");
DICT_MAP.put(WEB, "PC WEB");
}
public static String getDeviceType(){
if(TokenData.takeFromRequest().getDeviceType() == 4){
return "pc";
}
return "mobile";
}
/**
* 判断参数是否为当前常量字典的合法值
*
* @param value 待验证的参数值
* @return 合法返回true否则false
*/
public static boolean isValid(Integer value) {
return value != null && DICT_MAP.containsKey(value);
}
/**
* 私有构造函数明确标识该常量类的作用
*/
private AppDeviceType() {
}
}

153
common/common-core/src/main/java/apelet/common/core/constant/ApplicationConstant.java

@ -0,0 +1,153 @@ @@ -0,0 +1,153 @@
package apelet.common.core.constant;
import java.util.regex.Pattern;
/**
* 应用程序的常量声明对象
*
* @author guifc
* @date 2023-08-04
*/
public final class ApplicationConstant {
/**
* 适用于所有类型的字典格式数据该常量为字典的键字段
*/
public static final String DICT_ID = "id";
/**
* 适用于所有类型的字典格式数据该常量为字典的名称字段
*/
public static final String DICT_NAME = "name";
/**
* 适用于所有类型的字典格式数据该常量为字典的键父字段
*/
public static final String PARENT_ID = "parentId";
/**
* 数据同步使用的缺省消息队列主题名称
*/
public static final String DEFAULT_DATA_SYNC_TOPIC = "apelet_saas";
/**
* 全量数据同步中新增数据对象的键名称
*/
public static final String DEFAULT_FULL_SYNC_DATA_KEY = "data";
/**
* 全量数据同步中原有数据对象的键名称
*/
public static final String DEFAULT_FULL_SYNC_OLD_DATA_KEY = "oldData";
/**
* 全量数据同步中数据对象主键的键名称
*/
public static final String DEFAULT_FULL_SYNC_ID_KEY = "id";
/**
* 为字典表数据缓存时缓存名称的固定后缀
*/
public static final String DICT_CACHE_NAME_SUFFIX = "-DICT";
/**
* 为树形字典表数据缓存时缓存名称的固定后缀
*/
public static final String TREE_DICT_CACHE_NAME_SUFFIX = "-TREE-DICT";
/**
* 图片文件上传的父目录
*/
public static final String UPLOAD_IMAGE_PARENT_PATH = "image";
/**
* 附件文件上传的父目录
*/
public static final String UPLOAD_ATTACHMENT_PARENT_PATH = "attachment";
/**
* CSV文件扩展名
*/
public static final String CSV_EXT = "csv";
/**
* XLSX文件扩展名
*/
public static final String XLSX_EXT = "xlsx";
/**
* 统计分类计算时按天聚合计算的常量值(前端在MyOrderParam和MyGroupParam中传给后台)
*/
public static final String DAY_AGGREGATION = "day";
/**
* 统计分类计算时按月聚合计算的常量值(前端在MyOrderParam和MyGroupParam中传给后台)
*/
public static final String MONTH_AGGREGATION = "month";
/**
* 统计分类计算时按年聚合计算的常量值(前端在MyOrderParam和MyGroupParam中传给后台)
*/
public static final String YEAR_AGGREGATION = "year";
/**
* 请求头跟踪id名
*/
public static final String HTTP_HEADER_TRACE_ID = "traceId";
/**
* 请求头菜单Id
*/
public static final String HTTP_HEADER_MENU_ID = "MenuId";
/**
* 数据权限中标记所有菜单的Id值
*/
public static final String DATA_PERM_ALL_MENU_ID = "AllMenuId";
/**
* 请求头中记录的原始请求URL
*/
public static final String HTTP_HEADER_ORIGINAL_REQUEST_URL = "MY_ORIGINAL_REQUEST_URL";
/**
* 系统服务内部调用时可使用该HEAD以便和外部调用加以区分便于监控和流量分析
*/
public static final String HTTP_HEADER_INTERNAL_TOKEN = "INTERNAL_AUTH_TOKEN";
/**
* 操作日志的数据源类型
*/
public static final int OPERATION_LOG_DATASOURCE_TYPE = 1000;
/**
* 工作流和在线表单的数据源类型
*/
public static final int COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE = 1010;
/**
* 报表模块的数据源类型
*/
public static final int COMMON_REPORT_DATASOURCE_TYPE = 1020;
/**
* 全局编码字典的数据源类型
*/
public static final int COMMON_GLOBAL_DICT_TYPE = 1050;
/**
* 租户管理所对应的数据源常量值
*/
public static final int TENANT_ADMIN_DATASOURCE_TYPE = 1100;
/**
* 租户业务默认数据库(系统搭建时的第一个租户数据库)所对应的数据源常量值
*/
public static final int TENANT_BUSINESS_DATASOURCE_TYPE = 1120;
/**
* 租户通用数据所对应的数据源常量值如全局编码字典在线表单流程和报表等内置表数据
*/
public static final int TENANT_COMMON_DATASOURCE_TYPE = 1130;
/**
* 租户动态数据源主题(Redis)
*/
public static final String TENANT_DYNAMIC_DATASOURCE_TOPIC = "TenantDynamicDatasoruce";
/**
* 租户基础数据同步(RocketMQ)如upms全局编码字典在线表单流程报表等
*/
public static final String TENANT_DATASYNC_TOPIC = "TenantSync";
/**
* 重要说明该值为项目生成后的缺省密钥仅为使用户可以快速上手并跑通流程
* 在实际的应用中一定要为不同的项目或服务自行生成公钥和私钥并将 PRIVATE_KEY 的引用改为服务的配置项
* 密钥的生成方式可通过执行common.core.util.RsaUtil类的main函数动态生成
*/
public static final String PRIVATE_KEY =
"MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKkLhAydtOtA4WuIkkIIUVaGWu4ElOEAQF9GTulHHWOwCHI1UvcKolvS1G+mdsKcmGtEAQ92AUde/kDRGu8Wn7kLDtCgUfo72soHz7Qfv5pVB4ohMxQd/9cxeKjKbDoirhB9Z3xGF20zUozp4ZPLxpTtI7azr0xzUtd5+D/HfLDrAgMBAAECgYEApESZhDz4YyeAJiPnpJ06lS8oS2VOWzsIUs0av5uoloeoHXtt7Lx7u2kroHeNrl3Hy2yg7ypH4dgQkGHin3VHrVAgjG3TxhgBXIqqntzzk2AGJKBeIIkRX86uTvtKZyp3flUgcwcGmpepAHS1V1DPY3aVYvbcqAmoL6DX6VYN0NECQQDQUitMdC76lEtAr5/ywS0nrZJDo6U7eQ7ywx/eiJ+YmrSye8oorlAj1VBWG+Cl6jdHOHtTQyYv/tu71fjzQiJTAkEAz7wb47/vcSUpNWQxItFpXz0o6rbJh71xmShn1AKP7XptOVZGlW9QRYEzHabV9m/DHqI00cMGhHrWZAhCiTkUCQJAFsJjaJ7o4weAkTieyO7B+CvGZw1h5/V55Jvcx3s1tH5yb22G0Jr6tm9/r2isSnQkReutzZLwgR3e886UvD7lcQJAAUcD2OOuQkDbPwPNtYwaHMbQgJj9JkOI9kskUE5vuiMdltOr/XFAyhygRtdmy2wmhAK1VnDfkmL6/IR8fEGImQJABOB0KCalb0M8CPnqqHzozrD8gPObnIIr4aVvLIPATN2g7MM2N6F7JbI4RZFiKa92LV6bhQCY8OvHi5K2cgFpbw==";
/**
* SQL注入检测的正则对象
*/
@SuppressWarnings("all")
public static final Pattern SQL_INJECT_PATTERN =
Pattern.compile("(.*\\=.*\\-\\-.*)|(.*(\\+).*)|(.*\\w+(%|\\$|#|&)\\w+.*)|(.*\\|\\|.*)|(.*\\s+(and|or)\\s+.*)" +
"|(.*\\b(select|update|union|and|or|delete|insert|trancate|char|substr|ascii|declare|exec|count|master|into|drop|execute|sleep|extractvalue|updatexml|substring|database|concat|rand)\\b.*)");
/**
* 私有构造函数明确标识该常量类的作用
*/
private ApplicationConstant() {
}
}

40
common/common-core/src/main/java/apelet/common/core/constant/BooleanEnum.java

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
package apelet.common.core.constant;
import lombok.Getter;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* boolean 枚举
*/
@Getter
public enum BooleanEnum {
OK("true", "是"),
NO("false", "否"),
ONE("1", "true"),
TOW("0", "false"),
;
private final String type;
private final String name;
BooleanEnum(String type, String name) {
this.type = type;
this.name = name;
}
private static Map<String, BooleanEnum> ENUM_MAP;
static {
ENUM_MAP = Arrays.stream(values()).collect(Collectors.toMap(BooleanEnum :: getType, e -> e));
}
public static BooleanEnum of(String type) {
return Optional.ofNullable(ENUM_MAP.get(type)).orElseThrow(() -> new IllegalArgumentException("Could not get cube auth type by type " + type));
}
}

81
common/common-core/src/main/java/apelet/common/core/constant/DataPermRuleType.java

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
package apelet.common.core.constant;
import java.util.HashMap;
import java.util.Map;
/**
* 数据权限规则类型常量类
*
* @author guifc
* @date 2023-08-04
*/
public final class DataPermRuleType {
/**
* 查看全部
*/
public static final int TYPE_ALL = 0;
/**
* 仅查看当前用户
*/
public static final int TYPE_USER_ONLY = 1;
/**
* 仅查看当前部门
*/
public static final int TYPE_DEPT_ONLY = 2;
/**
* 所在部门及子部门
*/
public static final int TYPE_DEPT_AND_CHILD_DEPT = 3;
/**
* 多部门及子部门
*/
public static final int TYPE_MULTI_DEPT_AND_CHILD_DEPT = 4;
/**
* 自定义部门列表
*/
public static final int TYPE_CUSTOM_DEPT_LIST = 5;
/**
* 本部门所有用户
*/
public static final int TYPE_DEPT_USERS = 6;
/**
* 本部门及子部门所有用户
*/
public static final int TYPE_DEPT_AND_CHILD_DEPT_USERS = 7;
private static final Map<Object, String> DICT_MAP = new HashMap<>(6);
static {
DICT_MAP.put(TYPE_ALL, "查看全部");
DICT_MAP.put(TYPE_USER_ONLY, "仅查看当前用户");
DICT_MAP.put(TYPE_DEPT_ONLY, "仅查看所在部门");
DICT_MAP.put(TYPE_DEPT_AND_CHILD_DEPT, "所在部门及子部门");
DICT_MAP.put(TYPE_MULTI_DEPT_AND_CHILD_DEPT, "多部门及子部门");
DICT_MAP.put(TYPE_CUSTOM_DEPT_LIST, "自定义部门列表");
DICT_MAP.put(TYPE_DEPT_USERS, "本部门所有用户");
DICT_MAP.put(TYPE_DEPT_AND_CHILD_DEPT_USERS, "本部门及子部门所有用户");
}
/**
* 判断参数是否为当前常量字典的合法取值范围
*
* @param value 待验证的参数值
* @return 合法返回true否则false
*/
public static boolean isValid(Integer value) {
return value != null && DICT_MAP.containsKey(value);
}
/**
* 私有构造函数明确标识该常量类的作用
*/
private DataPermRuleType() {
}
}

64
common/common-core/src/main/java/apelet/common/core/constant/DictType.java

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
package apelet.common.core.constant;
import java.util.HashMap;
import java.util.Map;
/**
* 字典类型常量字典对象
*
* @author guifc
* @date 2023-08-04
*/
public final class DictType {
/**
* 数据表字典
*/
public static final int TABLE = 1;
/**
* URL字典
*/
public static final int URL = 5;
/**
* 常量字典
*/
public static final int CONST = 10;
/**
* 自定义字典
*/
public static final int CUSTOM = 15;
/**
* 全局编码字典
*/
public static final int GLOBAL_DICT = 20;
/**
* EAS编码字典
*/
public static final int EAS_DICT = 25;
private static final Map<Object, String> DICT_MAP = new HashMap<>(2);
static {
DICT_MAP.put(TABLE, "数据表字典");
DICT_MAP.put(URL, "URL字典");
DICT_MAP.put(CONST, "静态字典");
DICT_MAP.put(CUSTOM, "自定义字典");
DICT_MAP.put(GLOBAL_DICT, "全局编码字典");
DICT_MAP.put(EAS_DICT, "EAS字典");
}
/**
* 判断参数是否为当前常量字典的合法值
*
* @param value 待验证的参数值
* @return 合法返回true否则false
*/
public static boolean isValid(Integer value) {
return value != null && DICT_MAP.containsKey(value);
}
/**
* 私有构造函数明确标识该常量类的作用
*/
private DictType() {
}
}

92
common/common-core/src/main/java/apelet/common/core/constant/ErrorCodeEnum.java

@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
package apelet.common.core.constant;
/**
* 返回应答中的错误代码和错误信息
*
* @author guifc
* @date 2023-08-04
*/
public enum ErrorCodeEnum {
/**
* 没有错误
*/
NO_ERROR("没有错误"),
/**
* 未处理的异常
*/
UNHANDLED_EXCEPTION("未处理的异常!"),
ARGUMENT_NULL_EXIST("数据验证失败,接口调用参数存在空值,请核对!"),
ARGUMENT_PK_ID_NULL("数据验证失败,接口调用主键Id参数为空,请核对!"),
INVALID_ARGUMENT_FORMAT("数据验证失败,不合法的参数格式,请核对!"),
INVALID_STATUS_ARGUMENT("数据验证失败,无效的状态参数值,请核对!"),
UPLOAD_FAILED("数据验证失败,数据上传失败!"),
INVALID_UPLOAD_FIELD("数据验证失败,该字段不支持数据上传!"),
INVALID_UPLOAD_STORE_TYPE("数据验证失败,并不支持上传存储类型!"),
INVALID_UPLOAD_FILE_ARGUMENT("数据验证失败,上传文件参数错误,请核对!"),
INVALID_UPLOAD_FILE_FORMAT("无效的上传文件格式!"),
INVALID_UPLOAD_FILE_IOERROR("上传文件写入失败,请联系管理员!"),
UNAUTHORIZED_LOGIN("当前用户尚未登录或登录已超时,请重新登录!"),
UNAUTHORIZED_USER_PERMISSION("权限验证失败,当前用户不能访问该接口,请核对!"),
NO_ACCESS_PERMISSION("当前用户没有访问权限,请核对!"),
NO_OPERATION_PERMISSION("当前用户没有操作权限,请核对!"),
PASSWORD_ERR("密码错误,请重试!"),
INVALID_USERNAME_PASSWORD("用户名或密码错误,请重试!"),
INVALID_ACCESS_TOKEN("无效的用户访问令牌!"),
INVALID_USER_STATUS("用户状态错误,请刷新后重试!"),
INVALID_TENANT_CODE("指定的租户编码并不存在,请刷新后重试!"),
INVALID_TENANT_STATUS("当前租户为不可用状态,请刷新后重试!"),
INVALID_USER_TENANT("当前用户并不属于当前租户,请刷新后重试!"),
HAS_CHILDREN_DATA("数据验证失败,子数据存在,请刷新后重试!"),
DATA_VALIDATED_FAILED("数据验证失败,请核对!"),
UPLOAD_FILE_FAILED("文件上传失败,请联系管理员!"),
DATA_SAVE_FAILED("数据保存失败,请联系管理员!"),
DATA_ACCESS_FAILED("数据访问失败,请联系管理员!"),
DATA_PERM_ACCESS_FAILED("数据访问失败,您没有该页面的数据访问权限!"),
DUPLICATED_UNIQUE_KEY("数据保存失败,存在重复数据,请核对!"),
DATA_NOT_EXIST("数据不存在,请刷新后重试!"),
DATA_PARENT_LEVEL_ID_NOT_EXIST("数据验证失败,父级别关联Id不存在,请刷新后重试!"),
DATA_PARENT_ID_NOT_EXIST("数据验证失败,ParentId不存在,请核对!"),
RPC_DATA_ACCESS_FAILED("远程调用数据访问失败,请联系管理员!"),
INVALID_RELATED_RECORD_ID("数据验证失败,关联数据并不存在,请刷新后重试!"),
INVALID_DATA_MODEL("数据验证失败,无效的数据实体对象!"),
INVALID_DATA_FIELD("数据验证失败,无效的数据实体对象字段!"),
INVALID_CLASS_FIELD("数据验证失败,无效的类对象字段!"),
SERVER_INTERNAL_ERROR("服务器内部错误,请联系管理员!"),
REDIS_CACHE_ACCESS_TIMEOUT("Redis缓存数据访问超时,请刷新后重试!"),
REDIS_CACHE_ACCESS_STATE_ERROR("Redis缓存数据访问状态错误,请刷新后重试!"),
FAILED_TO_INVOKE_THIRDPARTY_URL("调用第三方接口失败!"),
MESSAGE_SEND_FAIL("消息发送失败!"),
TRANSACTION_MESSAGE_LOCAL_STATUS_ROLLBACK("本地数据操作失败,请联系管理员!"),
TRANSACTION_MESSAGE_LOCAL_STATUS_UNKNOW("本地数据操作结果未知,请刷新后重试!"),
FLOW_WORK_ORDER_EXIST("该业务数据Id存在尚未完成审批的流程实例,同一业务数据主键不能同时重复提交审批!");
// 下面的枚举值为特定枚举值,即开发者可以根据自己的项目需求定义更多的非通用枚举值
/**
* 构造函数
*
* @param errorMessage 错误消息
*/
ErrorCodeEnum(String errorMessage) {
this.errorMessage = errorMessage;
}
/**
* 错误信息
*/
private final String errorMessage;
/**
* 获取错误信息
*
* @return 错误信息
*/
public String getErrorMessage() {
return errorMessage;
}
}

127
common/common-core/src/main/java/apelet/common/core/constant/FieldFilterType.java

@ -0,0 +1,127 @@ @@ -0,0 +1,127 @@
package apelet.common.core.constant;
import java.util.HashMap;
import java.util.Map;
/**
* 字段过滤类型常量字典对象
*
* @author guifc
* @date 2023-08-04
*/
public final class FieldFilterType {
/**
* 等于过滤
*/
public static final int EQUAL = 0;
/**
* 不等于过滤
*/
public static final int NOT_EQUAL = 1;
/**
* 大于等于
*/
public static final int GE = 2;
/**
* 大于
*/
public static final int GT = 3;
/**
* 小于等于
*/
public static final int LE = 4;
/**
* 小于
*/
public static final int LT = 5;
/**
* 模糊查询
*/
public static final int LIKE = 6;
/**
* IN列表过滤
*/
public static final int IN = 7;
/**
* NOT IN列表过滤
*/
public static final int NOT_IN = 8;
/**
* 范围过滤
*/
public static final int BETWEEN = 9;
/**
* 不为空
*/
public static final int IS_NOT_NULL = 100;
/**
* 为空
*/
public static final int IS_NULL = 101;
private static final Map<Object, String> DICT_MAP = new HashMap<>(9);
static {
DICT_MAP.put(EQUAL, " = ");
DICT_MAP.put(NOT_EQUAL, " <> ");
DICT_MAP.put(GE, " >= ");
DICT_MAP.put(GT, " > ");
DICT_MAP.put(LE, " <= ");
DICT_MAP.put(LT, " < ");
DICT_MAP.put(LIKE, " LIKE ");
DICT_MAP.put(IN, " IN ");
DICT_MAP.put(NOT_IN, " NOT IN ");
DICT_MAP.put(BETWEEN, " BETWEEN ");
DICT_MAP.put(IS_NOT_NULL, " IS NOT NULL ");
DICT_MAP.put(IS_NULL, " IS NULL ");
}
/**
* 判断参数是否为当前常量字典的合法值
*
* @param value 待验证的参数值
* @return 合法返回true否则false
*/
public static boolean isValid(Integer value) {
return value != null && DICT_MAP.containsKey(value);
}
/**
* 获取显示名
* @param value 常量值
* @return 常量值对应的显示名
*/
public static String getName(Integer value) {
return DICT_MAP.get(value);
}
/**
* 不支持日期型字段的过滤类型
*
* @param filterType 过滤类型
* @return 不支持返回true否则false
*/
public static boolean unsupportDateFilterType(int filterType) {
return filterType == FieldFilterType.IN
|| filterType == FieldFilterType.NOT_IN
|| filterType == FieldFilterType.NOT_EQUAL
|| filterType == FieldFilterType.LIKE;
}
/**
* 支持多过滤值的过滤类型
*
* @param filterType 过滤类型
* @return 支持返回true否则false
*/
public static boolean supportMultiValueFilterType(int filterType) {
return filterType == FieldFilterType.IN
|| filterType == FieldFilterType.NOT_IN
|| filterType == FieldFilterType.BETWEEN;
}
/**
* 私有构造函数明确标识该常量类的作用
*/
private FieldFilterType() {
}
}

54
common/common-core/src/main/java/apelet/common/core/constant/FilterParamType.java

@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
package apelet.common.core.constant;
import java.util.HashMap;
import java.util.Map;
/**
* 字段过滤参数类型常量字典对象
*
* @author guifc
* @date 2023-08-04
*/
public final class FilterParamType {
/**
* 整数数值型
*/
public static final int LONG = 0;
/**
* 浮点型
*/
public static final int FLOAT = 1;
/**
* 字符型
*/
public static final int STRING = 2;
/**
* 日期型
*/
public static final int DATE = 3;
private static final Map<Object, String> DICT_MAP = new HashMap<>(9);
static {
DICT_MAP.put(LONG, "整数数值型");
DICT_MAP.put(FLOAT, "浮点型");
DICT_MAP.put(STRING, "字符型");
DICT_MAP.put(DATE, "日期型");
}
/**
* 判断参数是否为当前常量字典的合法值
*
* @param value 待验证的参数值
* @return 合法返回true否则false
*/
public static boolean isValid(Integer value) {
return value != null && DICT_MAP.containsKey(value);
}
/**
* 私有构造函数明确标识该常量类的作用
*/
private FilterParamType() {
}
}

25
common/common-core/src/main/java/apelet/common/core/constant/GlobalDeletedFlag.java

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
package apelet.common.core.constant;
/**
* 数据记录逻辑删除标记常量
*
* @author guifc
* @date 2023-08-04
*/
public final class GlobalDeletedFlag {
/**
* 表示数据表记录已经删除
*/
public static final int DELETED = -1;
/**
* 数据记录正常
*/
public static final int NORMAL = 1;
/**
* 私有构造函数明确标识该常量类的作用
*/
private GlobalDeletedFlag() {
}
}

47
common/common-core/src/main/java/apelet/common/core/constant/MaskFieldTypeEnum.java

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
package apelet.common.core.constant;
/**
* 字段脱敏类型枚举
*
* @author guifc
* @date 2023-08-04
*/
public enum MaskFieldTypeEnum {
/**
* 自定义实现
*/
CUSTOM,
/**
* 姓名
*/
NAME,
/**
* 移动电话
*/
MOBILE_PHONE,
/**
* 座机电话
*/
FIXED_PHONE,
/**
* 身份证
*/
ID_CARD,
/**
* 银行卡号
*/
BANK_CARD,
/**
* 汽车牌照号
*/
CAR_LICENSE,
/**
* 邮件
*/
EMAIL,
/**
* 固定长度的前缀和后缀不被掩码
*/
NO_MASK_PREFIX_SUFFIX,
}

26
common/common-core/src/main/java/apelet/common/core/constant/ObjectFieldType.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
package apelet.common.core.constant;
/**
* 对应于数据表字段中的类型我们需要统一映射到Java实体对象字段的类型
* 该类是描述Java实体对象字段类型的常量类
*
* @author guifc
* @date 2023-08-04
*/
public final class ObjectFieldType {
public static final String LONG = "Long";
public static final String INTEGER = "Integer";
public static final String DOUBLE = "Double";
public static final String BIG_DECIMAL = "BigDecimal";
public static final String BOOLEAN = "Boolean";
public static final String STRING = "String";
public static final String DATE = "Date";
public static final String BYTE_ARRAY = "byte[]";
/**
* 私有构造函数明确标识该常量类的作用
*/
private ObjectFieldType() {
}
}

22
common/common-core/src/main/java/apelet/common/core/constant/UserFilterGroup.java

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
package apelet.common.core.constant;
/**
* 用户分组过滤常量
*
* @author guifc
* @date 2023-08-04
*/
public class UserFilterGroup {
public static final String USER = "USER_GROUP";
public static final String ROLE = "ROLE_GROUP";
public static final String DEPT = "DEPT_GROUP";
public static final String POST = "POST_GROUP";
public static final String DEPT_POST = "DEPT_POST_GROUP";
/**
* 私有构造函数明确标识该常量类的作用
*/
private UserFilterGroup() {
}
}

26
common/common-core/src/main/java/apelet/common/core/exception/DataValidationException.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
package apelet.common.core.exception;
/**
* 数据验证失败的自定义异常
*
* @author guifc
* @date 2023-08-04
*/
public class DataValidationException extends RuntimeException {
/**
* 构造函数
*/
public DataValidationException() {
}
/**
* 构造函数
*
* @param msg 错误信息
*/
public DataValidationException(String msg) {
super(msg);
}
}

19
common/common-core/src/main/java/apelet/common/core/exception/IllegalConversionException.java

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
package apelet.common.core.exception;
/**
* @author Soon
* @version 1.0
* Create by 2024/4/10 16:11
*/
public class IllegalConversionException extends RuntimeException{
public IllegalConversionException()
{
this("非法转换");
}
public IllegalConversionException(String s)
{
super("[" + s + "]");
}
}

30
common/common-core/src/main/java/apelet/common/core/exception/InvalidClassFieldException.java

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
package apelet.common.core.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 无效的类对象字段的自定义异常
*
* @author guifc
* @date 2023-08-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class InvalidClassFieldException extends RuntimeException {
private final String className;
private final String fieldName;
/**
* 构造函数
*
* @param className 对象名
* @param fieldName 字段名
*/
public InvalidClassFieldException(String className, String fieldName) {
super("Invalid FieldName [" + fieldName + "] in Class [" + className + "].");
this.className = className;
this.fieldName = fieldName;
}
}

30
common/common-core/src/main/java/apelet/common/core/exception/InvalidDataFieldException.java

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
package apelet.common.core.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 无效的实体对象字段的自定义异常
*
* @author guifc
* @date 2023-08-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class InvalidDataFieldException extends RuntimeException {
private final String modelName;
private final String fieldName;
/**
* 构造函数
*
* @param modelName 实体对象名
* @param fieldName 字段名
*/
public InvalidDataFieldException(String modelName, String fieldName) {
super("Invalid FieldName [" + fieldName + "] in Model Class [" + modelName + "].");
this.modelName = modelName;
this.fieldName = fieldName;
}
}

27
common/common-core/src/main/java/apelet/common/core/exception/InvalidDataModelException.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
package apelet.common.core.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 无效的实体对象的自定义异常
*
* @author guifc
* @date 2023-08-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class InvalidDataModelException extends RuntimeException {
private final String modelName;
/**
* 构造函数
*
* @param modelName 实体对象名
*/
public InvalidDataModelException(String modelName) {
super("Invalid Model Class [" + modelName + "].");
this.modelName = modelName;
}
}

24
common/common-core/src/main/java/apelet/common/core/exception/InvalidDblinkTypeException.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
package apelet.common.core.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 无效的数据库链接类型自定义异常
*
* @author guifc
* @date 2023-08-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class InvalidDblinkTypeException extends RuntimeException {
/**
* 构造函数
*
* @param dblinkType 数据库链接类型
*/
public InvalidDblinkTypeException(int dblinkType) {
super("Invalid Dblink Type [" + dblinkType + "].");
}
}

27
common/common-core/src/main/java/apelet/common/core/exception/InvalidRedisModeException.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
package apelet.common.core.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 无效的Redis模式的自定义异常
*
* @author guifc
* @date 2023-08-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class InvalidRedisModeException extends RuntimeException {
private final String mode;
/**
* 构造函数
*
* @param mode 错误的模式
*/
public InvalidRedisModeException(String mode) {
super("Invalid Redis Mode [" + mode + "], only supports [single/cluster/sentinel/master_slave]");
this.mode = mode;
}
}

20
common/common-core/src/main/java/apelet/common/core/exception/MapCacheAccessException.java

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
package apelet.common.core.exception;
/**
* 内存缓存访问失败比如获取分布式数据锁超时等待线程中断等
*
* @author guifc
* @date 2023-08-04
*/
public class MapCacheAccessException extends RuntimeException {
/**
* 构造函数
*
* @param msg 错误信息
* @param cause 原始异常
*/
public MapCacheAccessException(String msg, Throwable cause) {
super(msg, cause);
}
}

46
common/common-core/src/main/java/apelet/common/core/exception/MyRuntimeException.java

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
package apelet.common.core.exception;
/**
* 自定义的运行时异常在需要抛出运行时异常时可使用该异常
* NOTE主要是为了避免SonarQube进行代码质量扫描时给出警告
*
* @author guifc
* @date 2023-08-04
*/
public class MyRuntimeException extends RuntimeException {
/**
* 构造函数
*/
public MyRuntimeException() {
}
/**
* 构造函数
*
* @param throwable 引发异常对象
*/
public MyRuntimeException(Throwable throwable) {
super(throwable);
}
/**
* 构造函数
*
* @param msg 错误信息
*/
public MyRuntimeException(String msg) {
super(msg);
}
/**
* 构造函数
*
* @param msg 错误信息
* @param throwable 引发异常对象
*/
public MyRuntimeException(String msg, Throwable throwable) {
super(msg, throwable);
}
}

26
common/common-core/src/main/java/apelet/common/core/exception/NoDataAffectException.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
package apelet.common.core.exception;
/**
* 没有数据被修改的自定义异常
*
* @author guifc
* @date 2023-08-04
*/
public class NoDataAffectException extends RuntimeException {
/**
* 构造函数
*/
public NoDataAffectException() {
}
/**
* 构造函数
*
* @param msg 错误信息
*/
public NoDataAffectException(String msg) {
super(msg);
}
}

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

Loading…
Cancel
Save