Merge branch 'test' into test-dsc
* test: 新增根据流程实例ID获取抄送记录的接口,用于待办详情中展示 [+]增加部门推动消息功能 新增忽略公司以及部门数据权限的注解
This commit is contained in:
@@ -8,6 +8,7 @@ import com.zt.plat.framework.common.util.json.JsonUtils;
|
||||
import com.zt.plat.framework.common.util.spring.SpringUtils;
|
||||
import com.zt.plat.framework.security.core.LoginUser;
|
||||
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
|
||||
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
|
||||
import com.zt.plat.framework.web.core.util.WebFrameworkUtils;
|
||||
import com.zt.plat.module.system.api.dept.DeptApi;
|
||||
import com.zt.plat.module.system.api.dept.dto.CompanyDeptInfoRespDTO;
|
||||
@@ -197,6 +198,9 @@ public class BusinessDeptHandleUtil {
|
||||
}
|
||||
CompanyContextHolder.setIgnore(false);
|
||||
CompanyContextHolder.setCompanyId(Long.valueOf(info.getCompanyId()));
|
||||
DeptContextHolder.setIgnore(false);
|
||||
DeptContextHolder.setCompanyId(Long.valueOf(info.getCompanyId()));
|
||||
DeptContextHolder.setDeptId(Long.valueOf(info.getDeptId()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# 数据权限忽略与上下文覆盖说明
|
||||
|
||||
本文说明新增的公司/部门数据权限忽略能力,以及部门上下文对数据权限的覆盖策略。
|
||||
|
||||
## 新增注解
|
||||
|
||||
- `@CompanyDataPermissionIgnore(enable = "true")`
|
||||
- `@DeptDataPermissionIgnore(enable = "true")`
|
||||
|
||||
用法:
|
||||
- 可标记在类或方法上。
|
||||
- `enable` 支持 Spring EL,计算结果为 `true` 时生效,默认开启。
|
||||
- 生效后,在方法执行期间临时设置忽略标记,结束后自动恢复。
|
||||
|
||||
## 忽略生效范围
|
||||
|
||||
- 公司数据权限:切面在进入方法时将 `CompanyContextHolder.setIgnore(true)`,数据权限规则检测到后直接放行。
|
||||
- 部门数据权限:切面在进入方法时将 `DeptContextHolder.setIgnore(true)`,部门数据权限规则检测到后直接放行。
|
||||
|
||||
## 部门上下文覆盖策略
|
||||
|
||||
当未忽略部门数据权限且上下文存在有效部门 ID 时:
|
||||
- 优先使用上下文中的单一部门作为过滤条件,避免因默认的 `ALL` 或“无部门且不可查看自己”导致放行或误判无权。
|
||||
- 上下文部门不会修改原有的数据权限 DTO,仅在当前计算中使用。
|
||||
- 若上下文公司与缓存公司不一致,会记录告警日志,但仍按上下文部门过滤。
|
||||
|
||||
## 典型场景
|
||||
|
||||
1) **任务/全局调用需要暂时关闭数据权限**
|
||||
- 在方法上标记 `@DeptDataPermissionIgnore` 或 `@CompanyDataPermissionIgnore`。
|
||||
|
||||
2) **带部门上下文的接口调用**
|
||||
- 请求预先设置 `DeptContextHolder.setContext(deptId, companyId)`。
|
||||
- 即便数据权限声明为 `all=true`,也会按该部门过滤,避免读出全量。
|
||||
|
||||
3) **无部门权限但指定了上下文部门**
|
||||
- 即使 `deptIds` 为空且 `self=false`,只要上下文提供部门,也会使用该部门过滤,而非直接判定无权。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 忽略标记只作用于当前线程上下文,切面会在 `finally` 中恢复旧值,嵌套调用安全。
|
||||
- 若需要同时忽略公司与部门数据权限,可叠加两个注解或在业务代码中分别设置忽略标记。
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.zt.plat.framework.datapermission.config;
|
||||
|
||||
import com.zt.plat.framework.datapermission.core.aop.CompanyDataPermissionIgnoreAspect;
|
||||
import com.zt.plat.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor;
|
||||
import com.zt.plat.framework.datapermission.core.aop.DeptDataPermissionIgnoreAspect;
|
||||
import com.zt.plat.framework.datapermission.core.db.DataPermissionRuleHandler;
|
||||
import com.zt.plat.framework.datapermission.core.rule.DataPermissionRule;
|
||||
import com.zt.plat.framework.datapermission.core.rule.DataPermissionRuleFactory;
|
||||
@@ -43,4 +45,14 @@ public class ZtDataPermissionAutoConfiguration {
|
||||
return new DataPermissionAnnotationAdvisor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DeptDataPermissionIgnoreAspect deptDataPermissionIgnoreAspect() {
|
||||
return new DeptDataPermissionIgnoreAspect();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CompanyDataPermissionIgnoreAspect companyDataPermissionIgnoreAspect() {
|
||||
return new CompanyDataPermissionIgnoreAspect();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.zt.plat.framework.datapermission.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 忽略公司数据权限的注解。
|
||||
* <p>
|
||||
* 标记在方法或类上时,匹配的调用会临时忽略公司类型的数据权限规则。
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface CompanyDataPermissionIgnore {
|
||||
|
||||
/**
|
||||
* 是否开启忽略,默认开启。
|
||||
* 支持 Spring EL 表达式,返回 true 时生效。
|
||||
*/
|
||||
String enable() default "true";
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.zt.plat.framework.datapermission.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 忽略部门数据权限的注解。
|
||||
* <p>
|
||||
* 标记在方法或类上时,匹配的调用会临时忽略部门类型的数据权限规则。
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface DeptDataPermissionIgnore {
|
||||
|
||||
/**
|
||||
* 是否开启忽略,默认开启。
|
||||
* 支持 Spring EL 表达式,返回 true 时生效。
|
||||
*/
|
||||
String enable() default "true";
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.zt.plat.framework.datapermission.core.aop;
|
||||
|
||||
import com.zt.plat.framework.common.util.spring.SpringExpressionUtils;
|
||||
import com.zt.plat.framework.datapermission.core.annotation.CompanyDataPermissionIgnore;
|
||||
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
/**
|
||||
* 公司数据权限忽略切面,基于 {@link CompanyDataPermissionIgnore} 注解。
|
||||
*/
|
||||
@Aspect
|
||||
@Slf4j
|
||||
public class CompanyDataPermissionIgnoreAspect {
|
||||
|
||||
@Around("@within(companyDataPermissionIgnore) || @annotation(companyDataPermissionIgnore)")
|
||||
public Object around(ProceedingJoinPoint joinPoint, CompanyDataPermissionIgnore companyDataPermissionIgnore) throws Throwable {
|
||||
boolean oldIgnore = CompanyContextHolder.isIgnore();
|
||||
try {
|
||||
Object enable = SpringExpressionUtils.parseExpression(companyDataPermissionIgnore.enable());
|
||||
if (Boolean.TRUE.equals(enable)) {
|
||||
CompanyContextHolder.setIgnore(true);
|
||||
}
|
||||
return joinPoint.proceed();
|
||||
} finally {
|
||||
CompanyContextHolder.setIgnore(oldIgnore);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.zt.plat.framework.datapermission.core.aop;
|
||||
|
||||
import com.zt.plat.framework.common.util.spring.SpringExpressionUtils;
|
||||
import com.zt.plat.framework.datapermission.core.annotation.DeptDataPermissionIgnore;
|
||||
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
/**
|
||||
* 部门数据权限忽略切面,基于 {@link DeptDataPermissionIgnore} 注解。
|
||||
*/
|
||||
@Aspect
|
||||
@Slf4j
|
||||
public class DeptDataPermissionIgnoreAspect {
|
||||
|
||||
@Around("@within(deptDataPermissionIgnore) || @annotation(deptDataPermissionIgnore)")
|
||||
public Object around(ProceedingJoinPoint joinPoint, DeptDataPermissionIgnore deptDataPermissionIgnore) throws Throwable {
|
||||
boolean oldIgnore = DeptContextHolder.shouldIgnore();
|
||||
try {
|
||||
Object enable = SpringExpressionUtils.parseExpression(deptDataPermissionIgnore.enable());
|
||||
if (Boolean.TRUE.equals(enable)) {
|
||||
DeptContextHolder.setIgnore(true);
|
||||
}
|
||||
return joinPoint.proceed();
|
||||
} finally {
|
||||
DeptContextHolder.setIgnore(oldIgnore);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,9 @@ import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.skipPermissionCheck;
|
||||
|
||||
@@ -41,6 +43,7 @@ public class DataPermissionRuleHandler implements MultiDataPermissionHandler {
|
||||
}
|
||||
|
||||
// 生成条件
|
||||
final Set<String> processed = new HashSet<>();
|
||||
Expression allExpression = null;
|
||||
for (DataPermissionRule rule : rules) {
|
||||
// 判断表名是否匹配
|
||||
@@ -49,6 +52,14 @@ public class DataPermissionRuleHandler implements MultiDataPermissionHandler {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 同一张表 + 同一别名 + 同一规则 在一次 SQL 解析内仅处理一次,避免重复拼接条件
|
||||
String aliasName = table.getAlias() == null ? "" : table.getAlias().getName();
|
||||
String key = tableName + "|" + aliasName + "|" + rule.getClass().getName();
|
||||
if (processed.contains(key)) {
|
||||
continue;
|
||||
}
|
||||
processed.add(key);
|
||||
|
||||
// 单条规则的条件
|
||||
Expression oneExpress = rule.getExpression(tableName, table.getAlias());
|
||||
if (oneExpress == null) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.zt.plat.framework.common.util.collection.CollectionUtils;
|
||||
import com.zt.plat.framework.datapermission.core.rule.DataPermissionRule;
|
||||
import com.zt.plat.framework.mybatis.core.util.MyBatisUtils;
|
||||
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.expression.Alias;
|
||||
@@ -49,6 +50,10 @@ public class CompanyDataPermissionRule implements DataPermissionRule {
|
||||
|
||||
@Override
|
||||
public Expression getExpression(String tableName, Alias tableAlias) {
|
||||
// 显式忽略公司数据权限时直接放行
|
||||
if (CompanyContextHolder.isIgnore()) {
|
||||
return null;
|
||||
}
|
||||
// 业务拼接 Company 的条件
|
||||
if (getLoginUserCompanyId() == null) {
|
||||
// 如果没有登录用户的公司编号,则不需要拼接条件
|
||||
|
||||
@@ -156,21 +156,28 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
}
|
||||
}
|
||||
|
||||
// 情况一,如果是 ALL 可查看全部,则无需拼接条件
|
||||
if (deptDataPermission.getAll()) {
|
||||
// 计算有效的部门与自查标记:当存在上下文部门且未被忽略时,强制仅使用该部门,以避免默认全量或空权限分支
|
||||
Set<Long> effectiveDeptIds = deptDataPermission.getDeptIds();
|
||||
Boolean effectiveSelf = deptDataPermission.getSelf();
|
||||
if (!DeptContextHolder.shouldIgnore() && ctxDeptId != null && ctxDeptId > 0L) {
|
||||
effectiveDeptIds = CollUtil.newHashSet(ctxDeptId);
|
||||
}
|
||||
|
||||
// 情况一:仅当不存在上下文部门时,且 ALL 可查看全部,才无需拼接条件;若存在上下文部门则仍需基于该部门过滤
|
||||
if (ctxDeptId == null && deptDataPermission.getAll()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限
|
||||
if (CollUtil.isEmpty(deptDataPermission.getDeptIds())
|
||||
&& Boolean.FALSE.equals(deptDataPermission.getSelf())) {
|
||||
// 情况二:仅在有效部门集合为空且不可查看自己时,才认为无权限;若上下文提供部门,则跳过该兜底
|
||||
if (CollUtil.isEmpty(effectiveDeptIds)
|
||||
&& Boolean.FALSE.equals(effectiveSelf)) {
|
||||
return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空
|
||||
}
|
||||
|
||||
// 情况三,拼接 Dept 和 Company User 的条件,最后组合
|
||||
Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds());
|
||||
Expression deptExpression = buildDeptExpression(tableName, tableAlias, effectiveDeptIds);
|
||||
// Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds());
|
||||
Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
|
||||
Expression userExpression = buildUserExpression(tableName, tableAlias, effectiveSelf, loginUser.getId());
|
||||
if (deptExpression == null && userExpression == null) {
|
||||
// TODO ZT:获得不到条件的时候,暂时不抛出异常,而是不返回数据
|
||||
log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",
|
||||
|
||||
@@ -264,7 +264,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test // 上下文部门存在且公司一致时,清空原集合并覆盖为单一 deptId
|
||||
@Test // 上下文部门存在且公司一致时,表达式按上下文 deptId 生效,但不修改原数据权限集合
|
||||
void testGetExpression_deptContextOverride_companyMatch() {
|
||||
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
|
||||
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class);
|
||||
@@ -292,12 +292,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
|
||||
Expression expression = rule.getExpression(tableName, tableAlias);
|
||||
|
||||
assertEquals("u.dept_id IN (99)", expression.toString());
|
||||
assertEquals(CollUtil.newLinkedHashSet(99L), deptDataPermission.getDeptIds());
|
||||
// 原始权限对象不被修改,只是临时使用上下文 deptId 计算
|
||||
assertEquals(CollUtil.newLinkedHashSet(10L, 20L), deptDataPermission.getDeptIds());
|
||||
assertEquals(1L, deptDataPermission.getCompanyId());
|
||||
}
|
||||
}
|
||||
|
||||
@Test // 上下文部门存在但公司不一致时,记录告警并保持原逻辑(不覆盖)
|
||||
@Test // 上下文部门存在但公司不一致时,仍按上下文 deptId 过滤,原数据权限保持不变
|
||||
void testGetExpression_deptContextOverride_companyMismatch() {
|
||||
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
|
||||
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class);
|
||||
@@ -324,10 +325,72 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
|
||||
|
||||
Expression expression = rule.getExpression(tableName, tableAlias);
|
||||
|
||||
assertEquals("u.dept_id IN (10)", expression.toString());
|
||||
assertEquals(CollUtil.newLinkedHashSet(10L), deptDataPermission.getDeptIds());
|
||||
assertEquals(1L, deptDataPermission.getCompanyId());
|
||||
assertEquals("u.dept_id IN (99)", expression.toString());
|
||||
// 原始权限对象不被修改
|
||||
assertEquals(CollUtil.newLinkedHashSet(10L), deptDataPermission.getDeptIds());
|
||||
assertEquals(1L, deptDataPermission.getCompanyId());
|
||||
}
|
||||
}
|
||||
|
||||
@Test // ALL 权限但存在上下文部门时,仍按上下文部门过滤
|
||||
void testGetExpression_allPermission_withCtxDept() {
|
||||
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
|
||||
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class);
|
||||
MockedStatic<CompanyContextHolder> companyCtxMock = mockStatic(CompanyContextHolder.class)) {
|
||||
|
||||
String tableName = "t_user";
|
||||
Alias tableAlias = new Alias("u");
|
||||
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
|
||||
.setUserType(UserTypeEnum.ADMIN.getValue()));
|
||||
secMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
|
||||
|
||||
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
|
||||
.setAll(true)
|
||||
.setDeptIds(CollUtil.newLinkedHashSet(10L));
|
||||
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(success(deptDataPermission));
|
||||
|
||||
deptCtxMock.when(DeptContextHolder::shouldIgnore).thenReturn(false);
|
||||
deptCtxMock.when(DeptContextHolder::getDeptId).thenReturn(99L);
|
||||
deptCtxMock.when(DeptContextHolder::getCompanyId).thenReturn(1L);
|
||||
companyCtxMock.when(CompanyContextHolder::getCompanyId).thenReturn(1L);
|
||||
companyCtxMock.when(CompanyContextHolder::isIgnore).thenReturn(false);
|
||||
|
||||
rule.addDeptColumn(tableName, "dept_id");
|
||||
|
||||
Expression expression = rule.getExpression(tableName, tableAlias);
|
||||
|
||||
assertEquals("u.dept_id IN (99)", expression.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test // 无部门且不可查看自己,但上下文提供部门时,应使用上下文部门而非判定无权限
|
||||
void testGetExpression_noDeptNoSelf_withCtxDept() {
|
||||
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
|
||||
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class);
|
||||
MockedStatic<CompanyContextHolder> companyCtxMock = mockStatic(CompanyContextHolder.class)) {
|
||||
|
||||
String tableName = "t_user";
|
||||
Alias tableAlias = new Alias("u");
|
||||
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
|
||||
.setUserType(UserTypeEnum.ADMIN.getValue()));
|
||||
secMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
|
||||
|
||||
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
|
||||
.setSelf(false);
|
||||
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(success(deptDataPermission));
|
||||
|
||||
deptCtxMock.when(DeptContextHolder::shouldIgnore).thenReturn(false);
|
||||
deptCtxMock.when(DeptContextHolder::getDeptId).thenReturn(88L);
|
||||
deptCtxMock.when(DeptContextHolder::getCompanyId).thenReturn(1L);
|
||||
companyCtxMock.when(CompanyContextHolder::getCompanyId).thenReturn(1L);
|
||||
companyCtxMock.when(CompanyContextHolder::isIgnore).thenReturn(false);
|
||||
|
||||
rule.addDeptColumn(tableName, "dept_id");
|
||||
|
||||
Expression expression = rule.getExpression(tableName, tableAlias);
|
||||
|
||||
assertEquals("u.dept_id IN (88)", expression.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.zt.plat.module.bpm.controller.admin.task;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.zt.plat.framework.business.core.util.DeptUtil;
|
||||
@@ -11,8 +12,10 @@ import com.zt.plat.module.bpm.controller.admin.task.vo.instance.*;
|
||||
import com.zt.plat.module.bpm.convert.task.BpmProcessInstanceConvert;
|
||||
import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO;
|
||||
import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
|
||||
import com.zt.plat.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
|
||||
import com.zt.plat.module.bpm.service.definition.BpmCategoryService;
|
||||
import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService;
|
||||
import com.zt.plat.module.bpm.service.task.BpmProcessInstanceCopyService;
|
||||
import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService;
|
||||
import com.zt.plat.module.bpm.service.task.BpmTaskService;
|
||||
import com.zt.plat.module.system.api.dept.DeptApi;
|
||||
@@ -24,6 +27,8 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.list.SetUniqueList;
|
||||
import org.flowable.engine.history.HistoricProcessInstance;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.flowable.task.api.Task;
|
||||
@@ -31,6 +36,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -53,6 +60,8 @@ public class BpmProcessInstanceController {
|
||||
private BpmProcessDefinitionService processDefinitionService;
|
||||
@Resource
|
||||
private BpmCategoryService categoryService;
|
||||
@Resource
|
||||
private BpmProcessInstanceCopyService processInstanceCopyService;
|
||||
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@@ -181,6 +190,32 @@ public class BpmProcessInstanceController {
|
||||
return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/copy-list-by-process-instance-id")
|
||||
@Operation(summary = "根据流程实例编号获取抄送列表")
|
||||
@Parameter(name = "id", description = "流程实例的编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
|
||||
public CommonResult<List<BpmProcessInstanceCopyVO>> getCopyListByProcessInstanceId(@RequestParam("processInstanceId") String processInstanceId) {
|
||||
List<BpmProcessInstanceCopyDO> copyDOList = processInstanceCopyService.getByProcessInstanceId(processInstanceId);
|
||||
if (CollectionUtils.isEmpty(copyDOList)) {
|
||||
return success(new ArrayList<>(0));
|
||||
}
|
||||
List<BpmProcessInstanceCopyVO> copyVOList = new ArrayList<>(copyDOList.size());
|
||||
SetUniqueList<Long> userIdList = SetUniqueList.setUniqueList(new ArrayList<>());
|
||||
for (BpmProcessInstanceCopyDO copyDO : copyDOList) {
|
||||
BpmProcessInstanceCopyVO copyVO = new BpmProcessInstanceCopyVO();
|
||||
BeanUtil.copyProperties(copyDO, copyVO);
|
||||
copyVOList.add(copyVO);
|
||||
userIdList.add(copyDO.getStartUserId());
|
||||
userIdList.add(copyDO.getUserId());
|
||||
}
|
||||
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIdList);
|
||||
for (BpmProcessInstanceCopyVO copyVO : copyVOList) {
|
||||
copyVO.setStartUserName(userMap.get(copyVO.getStartUserId()).getNickname());
|
||||
copyVO.setUserName(userMap.get(copyVO.getUserId()).getNickname());
|
||||
}
|
||||
return success(copyVOList);
|
||||
}
|
||||
|
||||
@GetMapping("/get-next-approval-nodes")
|
||||
@Operation(summary = "获取下一个执行的流程节点")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.zt.plat.module.bpm.controller.admin.task.vo.instance;
|
||||
|
||||
import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 流程抄送 VO
|
||||
*
|
||||
* @author kr
|
||||
* @since 2025-12-31
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class BpmProcessInstanceCopyVO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 发起人 Id
|
||||
*/
|
||||
@Schema(description ="发起人 Id")
|
||||
private Long startUserId;
|
||||
|
||||
/**
|
||||
* 发起人 姓名
|
||||
*/
|
||||
@Schema(description ="发起人 姓名")
|
||||
private String startUserName;
|
||||
/**
|
||||
* 流程名
|
||||
*/
|
||||
@Schema(description ="流程名")
|
||||
private String processInstanceName;
|
||||
/**
|
||||
* 流程实例的编号
|
||||
*/
|
||||
@Schema(description ="流程实例的编号")
|
||||
private String processInstanceId;
|
||||
/**
|
||||
* 流程实例的流程定义编号
|
||||
*/
|
||||
@Schema(description ="流程实例的流程定义编号")
|
||||
private String processDefinitionId;
|
||||
/**
|
||||
* 流程分类
|
||||
*/
|
||||
@Schema(description ="流程分类")
|
||||
private String category;
|
||||
/**
|
||||
* 流程活动的编号
|
||||
*/
|
||||
@Schema(description ="流程活动的编号")
|
||||
private String activityId;
|
||||
/**
|
||||
* 流程活动的名字
|
||||
*/
|
||||
@Schema(description ="流程活动的名字")
|
||||
private String activityName;
|
||||
/**
|
||||
* 流程活动的编号
|
||||
*/
|
||||
@Schema(description ="流程活动的编号")
|
||||
private String taskId;
|
||||
|
||||
/**
|
||||
* 用户编号(被抄送的用户编号)
|
||||
*/
|
||||
@Schema(description ="用户编号(被抄送的用户编号)")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户姓名(被抄送的用户姓名)
|
||||
*/
|
||||
@Schema(description ="用户姓名(被抄送的用户姓名)")
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 抄送意见
|
||||
*/
|
||||
@Schema(description ="抄送意见")
|
||||
private String reason;
|
||||
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmProcessInstan
|
||||
import com.zt.plat.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface BpmProcessInstanceCopyMapper extends BaseMapperX<BpmProcessInstanceCopyDO> {
|
||||
|
||||
@@ -22,4 +24,8 @@ public interface BpmProcessInstanceCopyMapper extends BaseMapperX<BpmProcessInst
|
||||
delete(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId);
|
||||
}
|
||||
|
||||
default List<BpmProcessInstanceCopyDO> getByProcessInstanceId(String processInstanceId) {
|
||||
return selectList(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotEmpty;
|
||||
import org.flowable.bpmn.model.FlowNode;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流程抄送 Service 接口
|
||||
@@ -57,4 +58,12 @@ public interface BpmProcessInstanceCopyService {
|
||||
*/
|
||||
void deleteProcessInstanceCopy(String processInstanceId);
|
||||
|
||||
/**
|
||||
* 获得流程的抄送列表
|
||||
*
|
||||
* @param processInstanceId 流程实例 ID
|
||||
* @return 抄送流程列表
|
||||
*/
|
||||
List<BpmProcessInstanceCopyDO> getByProcessInstanceId(String processInstanceId);
|
||||
|
||||
}
|
||||
|
||||
@@ -93,4 +93,9 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
|
||||
processInstanceCopyMapper.deleteByProcessInstanceId(processInstanceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BpmProcessInstanceCopyDO> getByProcessInstanceId(String processInstanceId) {
|
||||
return processInstanceCopyMapper.getByProcessInstanceId(processInstanceId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -73,4 +73,8 @@
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<logger name="com.zt.plat.module.bpm.dal" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</logger>
|
||||
|
||||
</configuration>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.zt.plat.module.system.api.esp;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.framework.common.util.collection.CollectionUtils;
|
||||
import com.zt.plat.module.system.api.dept.dto.*;
|
||||
import com.zt.plat.module.system.enums.ApiConstants;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@FeignClient(name = ApiConstants.NAME)
|
||||
@Tag(name = "RPC 服务 - 部门")
|
||||
public interface EspApi {
|
||||
|
||||
String PREFIX = ApiConstants.PREFIX + "/dept";
|
||||
|
||||
// === 以下为补全的接口方法 ===
|
||||
@PostMapping(PREFIX + "/create")
|
||||
@Operation(summary = "新增部门")
|
||||
CommonResult<Long> createDept(@RequestBody DeptSaveReqDTO createReqVO);
|
||||
|
||||
@PutMapping(PREFIX + "/update")
|
||||
@Operation(summary = "修改部门")
|
||||
CommonResult<Boolean> updateDept(@RequestBody DeptSaveReqDTO updateReqVO);
|
||||
|
||||
@DeleteMapping(PREFIX + "/delete")
|
||||
@Operation(summary = "删除部门")
|
||||
CommonResult<Boolean> deleteDept(@RequestParam("id") Long id);
|
||||
|
||||
@PostMapping(PREFIX + "/list-all")
|
||||
@Operation(summary = "获得部门列表")
|
||||
CommonResult<List<DeptDetailRespDTO>> getDeptList(@RequestBody DeptListReqDTO reqVO);
|
||||
|
||||
@GetMapping(PREFIX + "/simple-list")
|
||||
@Operation(summary = "获得部门精简信息列表")
|
||||
CommonResult<List<DeptSimpleRespDTO>> getSimpleDeptList();
|
||||
|
||||
@GetMapping(PREFIX + "/simple-company-list")
|
||||
@Operation(summary = "获得公司精简信息列表")
|
||||
CommonResult<List<DeptSimpleRespDTO>> getSimpleCompanyList();
|
||||
|
||||
@GetMapping(PREFIX + "/all-company-list")
|
||||
@Operation(summary = "获得所有公司精简信息列表")
|
||||
CommonResult<List<DeptSimpleRespDTO>> getAllCompanyList();
|
||||
|
||||
@GetMapping(PREFIX + "/get")
|
||||
@Operation(summary = "获得部门信息")
|
||||
@Parameter(name = "id", description = "部门编号", example = "1024", required = true)
|
||||
CommonResult<DeptRespDTO> getDept(@RequestParam("id") Long id);
|
||||
|
||||
@GetMapping(PREFIX + "/list")
|
||||
@Operation(summary = "获得部门信息数组")
|
||||
@Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true)
|
||||
CommonResult<List<DeptRespDTO>> getDeptList(@RequestParam("ids") Collection<Long> ids);
|
||||
|
||||
@GetMapping(PREFIX + "/valid")
|
||||
@Operation(summary = "校验部门是否合法")
|
||||
@Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true)
|
||||
CommonResult<Boolean> validateDeptList(@RequestParam("ids") Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得指定编号的部门 Map
|
||||
*
|
||||
* @param ids 部门编号数组
|
||||
* @return 部门 Map
|
||||
*/
|
||||
default Map<Long, DeptRespDTO> getDeptMap(Collection<Long> ids) {
|
||||
List<DeptRespDTO> list = getDeptList(ids).getCheckedData();
|
||||
return CollectionUtils.convertMap(list, DeptRespDTO::getId);
|
||||
}
|
||||
|
||||
@GetMapping(PREFIX + "/list-child")
|
||||
@Operation(summary = "获得指定部门的所有子部门")
|
||||
@Parameter(name = "id", description = "部门编号", example = "1024", required = true)
|
||||
CommonResult<List<DeptRespDTO>> getChildDeptList(@RequestParam("id") Long id);
|
||||
|
||||
@GetMapping(PREFIX + "/company-dept-info")
|
||||
@Operation(summary = "获得指定用户的公司部门信息")
|
||||
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
|
||||
CommonResult<Set<CompanyDeptInfoRespDTO>> getCompanyDeptInfoListByUserId(@RequestParam("userId") Long userId);
|
||||
|
||||
// ========== 数据同步专用接口 ==========
|
||||
|
||||
@PostMapping(PREFIX + "/sync")
|
||||
@Operation(summary = "同步部门")
|
||||
CommonResult<Boolean> syncDept(@RequestBody DeptSaveReqDTO syncReqDTO);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.zt.plat.module.system.api.esp.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "RPC 服务 - 推送外部系统配置信息 Response DTO")
|
||||
@Data
|
||||
public class EspDto {
|
||||
|
||||
@Schema(description = "部门名称,模糊匹配", example = "ZT")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "是否公司", example = "false")
|
||||
private Boolean isCompany;
|
||||
|
||||
@Schema(description = "是否集团", example = "false")
|
||||
private Boolean isGroup;
|
||||
|
||||
@Schema(description = "部门编号集合,支持多部门查询", example = "[\"1001\", \"1002\"]")
|
||||
private List<String> ids;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.zt.plat.module.system.controller.admin.dept;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo;
|
||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
|
||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
|
||||
import com.zt.plat.module.system.service.dept.DeptService;
|
||||
import com.zt.plat.module.system.service.dept.IEspService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
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.stream.Collectors;
|
||||
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 部门推送消息")
|
||||
@RestController
|
||||
@RequestMapping("/system/esp")
|
||||
@Validated
|
||||
public class EspController
|
||||
{
|
||||
|
||||
@Resource
|
||||
private IEspService espService;
|
||||
@Resource
|
||||
private DeptService deptService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建部门推送消息")
|
||||
@PreAuthorize("@ss.hasPermission('system:esp-external-code:create')")
|
||||
public CommonResult<Long> create(@Valid @RequestBody EspSaveRespVo createReqVO) {
|
||||
Long id = espService.createDeptPushMsg(createReqVO);
|
||||
return success(id);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改部门推送消息")
|
||||
@PreAuthorize("@ss.hasPermission('system:esp-external-code:update')")
|
||||
public CommonResult<Boolean> update(@Valid @RequestBody EspSaveRespVo updateReqVO) {
|
||||
espService.updateDeptPushMsg(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除部门推送消息")
|
||||
@PreAuthorize("@ss.hasPermission('system:esp-external-code:delete')")
|
||||
public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
|
||||
espService.deleteDeptPushMsg(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获取部门推送消息详情")
|
||||
@PreAuthorize("@ss.hasPermission('system:esp-external-code:query')")
|
||||
public CommonResult<EspSaveRespVo> get(@RequestParam("id") Long id) {
|
||||
DeptPushMsgDO entity = espService.getDeptPushMsgDetails(id);
|
||||
EspSaveRespVo respVO = BeanUtils.toBean(entity, EspSaveRespVo.class);
|
||||
fillDeptInfo(List.of(respVO));
|
||||
return success(respVO);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询部门推送消息")
|
||||
@PreAuthorize("@ss.hasPermission('system:esp-external-code:query')")
|
||||
public CommonResult<PageResult<EspSaveRespVo>> page(@Valid EspPageReqVO reqVO) {
|
||||
PageResult<DeptPushMsgDO> pageResult = espService.getDeptExternalCodePage(reqVO);
|
||||
PageResult<EspSaveRespVo> result = BeanUtils.toBean(pageResult, EspSaveRespVo.class);
|
||||
fillDeptInfo(result.getList());
|
||||
return success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/list-by-dept")
|
||||
@Operation(summary = "根据部门部门推送消息")
|
||||
@Parameter(name = "deptId", description = "部门编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('system:esp-external-code:query')")
|
||||
public CommonResult<List<EspSaveRespVo>> listByDept(@RequestParam("deptId") Long deptId) {
|
||||
List<DeptPushMsgDO> list = espService.getPushMsgByDeptId(deptId);
|
||||
List<EspSaveRespVo> respList = BeanUtils.toBean(list, EspSaveRespVo.class);
|
||||
fillDeptInfo(respList);
|
||||
return success(respList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void fillDeptInfo(List<EspSaveRespVo> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set<Long> deptIds = list.stream()
|
||||
.map(EspSaveRespVo::getDeptId)
|
||||
.collect(Collectors.toCollection(HashSet::new));
|
||||
if (deptIds == null || deptIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, DeptDO> deptMap = deptService.getDeptList(deptIds).stream()
|
||||
.collect(Collectors.toMap(DeptDO::getId, dept -> dept, (left, right) -> left));
|
||||
list.forEach(item -> {
|
||||
DeptDO dept = deptMap.get(item.getDeptId());
|
||||
if (dept != null) {
|
||||
item.setDeptName(dept.getName());
|
||||
item.setDeptCode(dept.getCode());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Schema(description = "管理后台 - 部门外部组织编码映射分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EspPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "部门编号", example = "1024")
|
||||
private Long deptId;
|
||||
|
||||
@Schema(description = "外部系统标识", example = "ERP")
|
||||
private String systemCode;
|
||||
|
||||
@Schema(description = "外部组织编码", example = "100200")
|
||||
private String externalDeptCode;
|
||||
|
||||
@Schema(description = "状态", example = "0")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 部门外消息推送创建/修改 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EspSaveRespVo extends DeptExternalCodeBaseVO {
|
||||
|
||||
@Schema(description = "映射编号", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "所属部门名称", example = "技术部")
|
||||
private String deptName;
|
||||
|
||||
@Schema(description = "所属部门编码", example = "DEPT_001")
|
||||
private String deptCode;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "最后更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.zt.plat.module.system.dal.dataobject.dept;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
||||
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 部门推送消息 DO
|
||||
*/
|
||||
@TableName("system_dept_push_msg")
|
||||
@KeySequence("system_dept_push_msg_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DeptPushMsgDO extends TenantBaseDO {
|
||||
|
||||
/**
|
||||
* 主键编号
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 本系统部门 ID
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 外部系统标识
|
||||
*/
|
||||
private String systemCode;
|
||||
|
||||
/**
|
||||
* 外部系统组织编码
|
||||
*/
|
||||
private String externalDeptCode;
|
||||
|
||||
/**
|
||||
* 外部系统组织名称
|
||||
*/
|
||||
private String externalDeptName;
|
||||
|
||||
/**
|
||||
* 映射状态
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.zt.plat.module.system.dal.mysql.dept;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo;
|
||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
/**
|
||||
* 部门推送消息接口Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface EspMapper extends BaseMapperX<DeptPushMsgDO> {
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param reqVO 消息推送VO
|
||||
* @return PageResult
|
||||
*/
|
||||
default PageResult<DeptPushMsgDO> selectPage(EspPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<DeptPushMsgDO>()
|
||||
.eqIfPresent(DeptPushMsgDO::getDeptId,reqVO.getDeptId())
|
||||
.eqIfPresent(DeptPushMsgDO::getSystemCode, reqVO.getSystemCode())
|
||||
.likeIfPresent(DeptPushMsgDO::getExternalDeptCode, reqVO.getExternalDeptCode())
|
||||
.eqIfPresent(DeptPushMsgDO::getStatus, reqVO.getStatus())
|
||||
.orderByDesc(DeptPushMsgDO::getId));
|
||||
}
|
||||
|
||||
|
||||
default DeptPushMsgDO selectBySystemCodeAndDeptId(String systemCode, Long deptId) {
|
||||
return selectOne(new LambdaQueryWrapperX<DeptPushMsgDO>()
|
||||
.eq(DeptPushMsgDO::getSystemCode, systemCode)
|
||||
.eq(DeptPushMsgDO::getDeptId, deptId));
|
||||
}
|
||||
|
||||
default DeptPushMsgDO selectBySystemCodeAndExternalCode(String systemCode, String externalDeptCode) {
|
||||
return selectOne(new LambdaQueryWrapperX<DeptPushMsgDO>()
|
||||
.eq(DeptPushMsgDO::getSystemCode, systemCode)
|
||||
.eq(DeptPushMsgDO::getExternalDeptCode, externalDeptCode));
|
||||
}
|
||||
|
||||
default List<DeptPushMsgDO> selectListByDeptId(Long deptId) {
|
||||
return selectList(DeptPushMsgDO::getDeptId, deptId);
|
||||
}
|
||||
|
||||
default int deleteByDeptId(Long deptId) {
|
||||
return delete(DeptPushMsgDO::getDeptId, deptId);
|
||||
}
|
||||
|
||||
default List<DeptPushMsgDO> selectListBySystemCode(String systemCode) {
|
||||
return selectList(DeptPushMsgDO::getSystemCode, systemCode);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.zt.plat.module.system.service.dept;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo;
|
||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
|
||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
|
||||
import com.zt.plat.module.system.dal.mysql.dept.DeptMapper;
|
||||
import com.zt.plat.module.system.dal.mysql.dept.EspMapper;
|
||||
import com.zt.plat.module.system.dal.redis.RedisKeyConstants;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.zt.plat.module.system.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 部门推送消息接口ServiceImpl实现类
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class EspServiceImpl implements IEspService {
|
||||
|
||||
@Resource
|
||||
private EspMapper espMapper;
|
||||
@Resource
|
||||
private DeptMapper deptMapper;
|
||||
@Resource
|
||||
private CacheManager cacheManager;
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST, key = "#createReqVO.deptId", beforeInvocation = false)
|
||||
public Long createDeptPushMsg(EspSaveRespVo createReqVO) {
|
||||
|
||||
//请求校验
|
||||
normalizeRequest(createReqVO);
|
||||
//冲突禁用-映射
|
||||
disableActiveMappingIfConflict(createReqVO.getDeptId(), createReqVO.getSystemCode(), createReqVO.getExternalDeptCode());
|
||||
validateForCreateOrUpdate(null, createReqVO.getDeptId(), createReqVO.getSystemCode(),
|
||||
createReqVO.getExternalDeptCode());
|
||||
|
||||
DeptPushMsgDO entity = BeanUtils.toBean(createReqVO, DeptPushMsgDO.class);
|
||||
if (entity.getStatus() == null) {
|
||||
entity.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
}
|
||||
espMapper.insert(entity);
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDeptPushMsg(EspSaveRespVo updateReqVO) {
|
||||
normalizeRequest(updateReqVO);
|
||||
DeptPushMsgDO exists = validateExists(updateReqVO.getId());
|
||||
disableActiveMappingIfConflict(updateReqVO.getDeptId(), updateReqVO.getSystemCode(), updateReqVO.getExternalDeptCode());
|
||||
validateForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getDeptId(), updateReqVO.getSystemCode(),
|
||||
updateReqVO.getExternalDeptCode());
|
||||
|
||||
DeptPushMsgDO updateObj = BeanUtils.toBean(updateReqVO, DeptPushMsgDO.class);
|
||||
// 保持原有的状态默认值逻辑
|
||||
if (updateObj.getStatus() == null) {
|
||||
updateObj.setStatus(exists.getStatus() == null ? CommonStatusEnum.ENABLE.getStatus() : exists.getStatus());
|
||||
}
|
||||
espMapper.updateById(updateObj);
|
||||
evictCacheSafely(exists.getDeptId());
|
||||
evictCacheSafely(updateObj.getDeptId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDeptPushMsg(Long id) {
|
||||
DeptPushMsgDO exists = validateExists(id);
|
||||
espMapper.deleteById(id);
|
||||
evictCacheSafely(exists.getDeptId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeptPushMsgDO getDeptPushMsgDetails(Long id) {
|
||||
return espMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<DeptPushMsgDO> getDeptExternalCodePage(EspPageReqVO reqVO) {
|
||||
|
||||
return espMapper.selectPage(reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST, key = "#deptId")
|
||||
public List<DeptPushMsgDO> getPushMsgByDeptId(Long deptId) {
|
||||
return espMapper.selectListByDeptId(deptId);
|
||||
}
|
||||
|
||||
/* @Override
|
||||
public DeptExternalCodeDO getBySystemCodeAndExternalCode(String systemCode, String externalDeptCode) {
|
||||
if (StrUtil.hasEmpty(systemCode, externalDeptCode)) {
|
||||
return null;
|
||||
}
|
||||
return espMapper.selectBySystemCodeAndExternalCode(systemCode.trim(), externalDeptCode.trim());
|
||||
}*/
|
||||
|
||||
/* @Override
|
||||
public DeptExternalCodeDO getBySystemCodeAndDeptId(String systemCode, Long deptId) {
|
||||
if (StrUtil.isBlank(systemCode) || deptId == null) {
|
||||
return null;
|
||||
}
|
||||
return espMapper.selectBySystemCodeAndDeptId(systemCode.trim(), deptId);
|
||||
}*/
|
||||
|
||||
/* @Override
|
||||
public Long getDeptPushMsgDetails(Long deptId, String systemCode, String externalDeptCode,
|
||||
String externalDeptName, Integer status) {
|
||||
|
||||
if (StrUtil.hasEmpty(systemCode, externalDeptCode) || deptId == null) {
|
||||
return null;
|
||||
}
|
||||
String normalizedSystemCode = systemCode.trim();
|
||||
String normalizedExternalCode = externalDeptCode.trim();
|
||||
String normalizedExternalName = StrUtil.blankToDefault(StrUtil.trimToNull(externalDeptName), null);
|
||||
|
||||
disableActiveMappingIfConflict(deptId, normalizedSystemCode, normalizedExternalCode);
|
||||
|
||||
// 如果存在则更新,否则创建
|
||||
DeptExternalCodeDO exists = espMapper.selectBySystemCodeAndDeptId(normalizedSystemCode, deptId);
|
||||
if (exists != null) {
|
||||
DeptExternalCodeSaveReqVO updateReqVO = new DeptExternalCodeSaveReqVO();
|
||||
updateReqVO.setId(exists.getId());
|
||||
updateReqVO.setDeptId(deptId);
|
||||
updateReqVO.setSystemCode(normalizedSystemCode);
|
||||
updateReqVO.setExternalDeptCode(normalizedExternalCode);
|
||||
updateReqVO.setExternalDeptName(normalizedExternalName);
|
||||
updateReqVO.setStatus(status == null ? exists.getStatus() : status);
|
||||
|
||||
//TODO
|
||||
//getDeptPushMsgDetails(updateReqVO);
|
||||
return exists.getId();
|
||||
}
|
||||
|
||||
DeptExternalCodeSaveReqVO createReqVO = new DeptExternalCodeSaveReqVO();
|
||||
createReqVO.setDeptId(deptId);
|
||||
createReqVO.setSystemCode(normalizedSystemCode);
|
||||
createReqVO.setExternalDeptCode(normalizedExternalCode);
|
||||
createReqVO.setExternalDeptName(normalizedExternalName);
|
||||
createReqVO.setStatus(status == null ? CommonStatusEnum.ENABLE.getStatus() : status);
|
||||
return getDeptPushMsgDetails(createReqVO);
|
||||
}*/
|
||||
|
||||
/* @Override
|
||||
public void deleteDeptExternalCodesByDeptId(Long deptId) {
|
||||
if (deptId == null) {
|
||||
return;
|
||||
}
|
||||
espMapper.deleteByDeptId(deptId);
|
||||
evictCacheSafely(deptId);
|
||||
}*/
|
||||
|
||||
private DeptPushMsgDO validateExists(Long id) {
|
||||
if (id == null) {
|
||||
throw exception(DEPT_EXTERNAL_RELATION_NOT_EXISTS);
|
||||
}
|
||||
DeptPushMsgDO entity = espMapper.selectById(id);
|
||||
if (entity == null) {
|
||||
throw exception(DEPT_EXTERNAL_RELATION_NOT_EXISTS);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void validateForCreateOrUpdate(Long id, Long deptId, String systemCode, String externalDeptCode) {
|
||||
|
||||
// 校验部门存在
|
||||
DeptDO dept = deptMapper.selectById(deptId);
|
||||
if (dept == null) {
|
||||
throw exception(DEPT_NOT_FOUND);
|
||||
}
|
||||
String normalizedSystemCode = StrUtil.blankToDefault(systemCode, null);
|
||||
String normalizedExternalCode = StrUtil.blankToDefault(externalDeptCode, null);
|
||||
|
||||
// 校验同一系统下部门唯一
|
||||
if (StrUtil.isNotBlank(normalizedSystemCode)) {
|
||||
DeptPushMsgDO sameDept = espMapper
|
||||
.selectBySystemCodeAndDeptId(normalizedSystemCode, deptId);
|
||||
if (sameDept != null && (id == null || !sameDept.getId().equals(id))) {
|
||||
throw exception(DEPT_EXTERNAL_RELATION_EXISTS, normalizedSystemCode);
|
||||
}
|
||||
}
|
||||
// 校验同一系统下外部编码唯一
|
||||
if (StrUtil.isNotBlank(normalizedSystemCode) && StrUtil.isNotBlank(normalizedExternalCode)) {
|
||||
DeptPushMsgDO sameExternal = espMapper
|
||||
.selectBySystemCodeAndExternalCode(normalizedSystemCode, normalizedExternalCode);
|
||||
if (sameExternal != null && (id == null || !sameExternal.getId().equals(id))) {
|
||||
boolean sameDept = Objects.equals(deptId, sameExternal.getDeptId());
|
||||
boolean activeConflict = !sameDept && CommonStatusEnum.isEnable(sameExternal.getStatus());
|
||||
if (activeConflict) {
|
||||
throw exception(DEPT_EXTERNAL_CODE_DUPLICATE, normalizedSystemCode, normalizedExternalCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void normalizeRequest(EspSaveRespVo reqVO) {
|
||||
if (reqVO == null) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isNotBlank(reqVO.getSystemCode())) {
|
||||
reqVO.setSystemCode(reqVO.getSystemCode().trim());
|
||||
}
|
||||
if (StrUtil.isNotBlank(reqVO.getExternalDeptCode())) {
|
||||
reqVO.setExternalDeptCode(reqVO.getExternalDeptCode().trim());
|
||||
}
|
||||
if (reqVO.getStatus() == null) {
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private void disableActiveMappingIfConflict(Long targetDeptId, String systemCode, String externalDeptCode) {
|
||||
String normalizedSystem = StrUtil.trimToNull(systemCode);
|
||||
String normalizedExternal = StrUtil.trimToNull(externalDeptCode);
|
||||
if (StrUtil.hasEmpty(normalizedSystem, normalizedExternal) || targetDeptId == null) {
|
||||
return;
|
||||
}
|
||||
DeptPushMsgDO existing = espMapper.selectBySystemCodeAndExternalCode(normalizedSystem, normalizedExternal);
|
||||
if (existing == null) {
|
||||
return;
|
||||
}
|
||||
if (Objects.equals(existing.getDeptId(), targetDeptId)) {
|
||||
return;
|
||||
}
|
||||
if (CommonStatusEnum.isEnable(existing.getStatus())) {
|
||||
DeptPushMsgDO update = new DeptPushMsgDO();
|
||||
update.setId(existing.getId());
|
||||
update.setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
espMapper.updateById(update);
|
||||
evictCacheSafely(existing.getDeptId());
|
||||
}
|
||||
}
|
||||
|
||||
private void evictCacheSafely(Long deptId) {
|
||||
|
||||
if (deptId == null || cacheManager == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (cacheManager.getCache(RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST) != null) {
|
||||
cacheManager.getCache(RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST).evict(deptId);
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
// 缓存失效失败不影响主流程
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.zt.plat.module.system.service.dept;
|
||||
|
||||
import com.zt.plat.framework.common.pojo.PageResult;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
|
||||
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo;
|
||||
import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 部门推送消息 Service 接口
|
||||
*/
|
||||
public interface IEspService {
|
||||
|
||||
/**
|
||||
* 创建映射关系
|
||||
* @param createReqVO 创建请求
|
||||
* @return 新增记录编号
|
||||
*/
|
||||
Long createDeptPushMsg(EspSaveRespVo createReqVO);
|
||||
|
||||
/**
|
||||
* 更新映射关系
|
||||
* @param updateReqVO 更新请求
|
||||
*/
|
||||
void updateDeptPushMsg(EspSaveRespVo updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除映射关系
|
||||
*
|
||||
* @param id 记录编号
|
||||
*/
|
||||
void deleteDeptPushMsg(Long id);
|
||||
|
||||
/**
|
||||
* 获取映射详情
|
||||
*/
|
||||
DeptPushMsgDO getDeptPushMsgDetails(Long id);
|
||||
|
||||
/**
|
||||
* 分页查询映射
|
||||
*/
|
||||
PageResult<DeptPushMsgDO> getDeptExternalCodePage(EspPageReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 根据部门推送消息
|
||||
*/
|
||||
List<DeptPushMsgDO> getPushMsgByDeptId(Long deptId);
|
||||
|
||||
/**
|
||||
* 根据部门与外部系统保存/更新映射(存在则更新,不存在则创建)
|
||||
* @param deptId 本系统部门 ID
|
||||
* @param systemCode 外部系统标识
|
||||
* @param externalDeptCode 外部系统组织编码
|
||||
* @param externalDeptName 外部系统组织名称(可选)
|
||||
* @param status 状态,默认启用
|
||||
* @return 映射记录 ID
|
||||
*/
|
||||
/* Long getDeptPushMsgDetails(Long deptId, String systemCode, String externalDeptCode, String externalDeptName,
|
||||
Integer status);*/
|
||||
|
||||
/**
|
||||
* 根据部门删除全部外部编码映射
|
||||
*
|
||||
* @param deptId 部门编号
|
||||
*/
|
||||
//void deleteDeptExternalCodesByDeptId(Long deptId);
|
||||
|
||||
/**
|
||||
* 根据外部系统与外部组织编码查询映射
|
||||
*/
|
||||
//DeptExternalCodeDO getBySystemCodeAndExternalCode(String systemCode, String externalDeptCode);
|
||||
|
||||
/**
|
||||
* 根据外部系统与部门编号查询映射
|
||||
*/
|
||||
//DeptExternalCodeDO getBySystemCodeAndDeptId(String systemCode, Long deptId);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user