diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/core/aspect/QmsPermissionAspect.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/core/aspect/QmsPermissionAspect.java index 37c1bcab..cc876d80 100644 --- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/core/aspect/QmsPermissionAspect.java +++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/core/aspect/QmsPermissionAspect.java @@ -61,7 +61,8 @@ public class QmsPermissionAspect { QmsPermission annotation = getAnnotationByJoinPoint(joinPoint); if(annotation == null) return; - QMSPermissionContextHolder.setContext(true, annotation.deptDataRoleCodes(), annotation.moduleDataRoleCodes(), annotation.deptIdColumn(), annotation.userIdColumn(), annotation.custom()); + QMSPermissionContextHolder.setContext(true, annotation.deptDataRoleCodes(), annotation.moduleDataRoleCodes(), + annotation.deptIdColumn(), annotation.userIdColumn(), annotation.self(), annotation.custom()); } private QmsPermission getAnnotationByJoinPoint(JoinPoint joinPoint) { diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/core/aspect/annotation/QmsPermission.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/core/aspect/annotation/QmsPermission.java index 29379bc2..5ef381ca 100644 --- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/core/aspect/annotation/QmsPermission.java +++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/core/aspect/annotation/QmsPermission.java @@ -21,6 +21,8 @@ public @interface QmsPermission { String userIdColumn() default "CREATOR"; //人员id列 + boolean self() default false; //是否可查看自己创建的数据 + //todo 考虑支持模块自定义扩展。参数传入表达式,通过表达式计算权限 String custom() default ""; diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/framework/datapermission/QMSMultiDataPermissionHandler.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/framework/datapermission/QMSMultiDataPermissionHandler.java index 3cfc3d7b..c8fe6e7f 100644 --- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/framework/datapermission/QMSMultiDataPermissionHandler.java +++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/framework/datapermission/QMSMultiDataPermissionHandler.java @@ -3,7 +3,6 @@ package com.zt.plat.module.qms.framework.datapermission; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler; import com.zt.plat.framework.common.biz.system.permission.PermissionCommonApi; import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; @@ -15,11 +14,9 @@ import com.zt.plat.framework.security.core.LoginUser; import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; //import com.zt.plat.module.qms.core.aspect.annotation.QmsPermission; import com.zt.plat.framework.tenant.core.context.DeptContextHolder; +import com.zt.plat.module.qms.core.aspect.annotation.QmsPermission; import lombok.extern.slf4j.Slf4j; -import net.sf.jsqlparser.expression.Alias; -import net.sf.jsqlparser.expression.Expression; -import net.sf.jsqlparser.expression.LongValue; -import net.sf.jsqlparser.expression.NullValue; +import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.conditional.OrExpression; import net.sf.jsqlparser.expression.operators.relational.*; @@ -31,6 +28,9 @@ import net.sf.jsqlparser.statement.select.SelectItem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -63,13 +63,21 @@ public class QMSMultiDataPermissionHandler implements MultiDataPermissionHandler @Override public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) { - log.error("QMSMultiDataPermissionHandler: Expression={}", where); + log.info("QMSMultiDataPermissionHandler - table={}, Expression={}, mappedStatementId={}", + table.getName(), where, mappedStatementId); + + // 判断是否应该对该表应用权限(只处理主表) + boolean applyPermission = shouldApplyPermission(table, mappedStatementId); + if (!applyPermission) { + return null; + } //获取注解 if(!QMSPermissionContextHolder.shouldExecute()) return null; try{ Expression expression = buildExpression(table, where); + log.info("QMSMultiDataPermissionHandler 权限表达式:{}", expression); return expression; }catch (Exception e){ log.error("QMSMultiDataPermissionHandler: Expression={}", where); @@ -79,6 +87,88 @@ public class QMSMultiDataPermissionHandler implements MultiDataPermissionHandler return EXPRESSION_NULL; } + /** + * 判断是否应该对当前表应用权限控制(只针对主表) + * 通过解析 mappedStatementId 找到对应的 Mapper 接口和实体类,然后判断当前表是否是主表 + * + */ + private boolean shouldApplyPermission(Table table, String mappedStatementId) { + try{ + // mappedStatementId 格式:com.zt.plat.module.qms.resource.material.dal.mapper.MaterialBatchMapper.selectPageWithPdtInfo + int lastDotIndex = mappedStatementId.lastIndexOf('.'); + if (lastDotIndex == -1) { + return true; + } + + String mapperClassName = mappedStatementId.substring(0, lastDotIndex); + Class mapperClass = Class.forName(mapperClassName); + // 获取方法上的@QmsPermission 注解 + Method[] methods = mapperClass.getMethods(); + boolean applyPermission = false; + for (Method method : methods) { + if (method.isAnnotationPresent(QmsPermission.class)) { + // 获取主表的表名(通过实体类) + String mainTableName = getMainTableName(mapperClass); + String currentTableName = MyBatisUtils.getTableName(table); + Alias tableAlias = table.getAlias(); + String aliasName = tableAlias != null ? tableAlias.getName() : null; + // 只处理主表,且别名为 t 的表(避免自关联或重复应用) + if (mainTableName != null && mainTableName.equalsIgnoreCase(currentTableName)) { + // 如果有别名,检查别名是否为 "t" + if (aliasName != null) { + applyPermission = "t".equals(aliasName); + } else { + applyPermission = true; + } + } + } + } + return applyPermission; + } catch (Exception e) { + log.warn("QMSMultiDataPermissionHandler 解析 mappedStatementId 失败:{}", mappedStatementId, e); + } + + return false; + } + + /** + * 获取主表的表名 + * 通过 Mapper 接口的泛型参数找到对应的实体类,然后从@TableName 注解中获取表名 + * + */ + private String getMainTableName(Class mapperClass) { + try{ + // 1. 获取泛型接口列表 + Type[] genericInterfaces = mapperClass.getGenericInterfaces(); + if (genericInterfaces.length == 0) return null; + // 2. 遍历泛型接口 + for (Type type : genericInterfaces) { + // 3. 检查是否是参数化类型 + if (!(type instanceof ParameterizedType parameterizedType)) continue; + // 4. 获取泛型参数数组 + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments.length == 0) continue; + // 5. 获取第一个泛型参数(实体类) + Type firstTypeArg = actualTypeArguments[0]; + if (!(firstTypeArg instanceof Class entityClass)) continue; + // 6. 检查是否有 @TableName 注解 + com.baomidou.mybatisplus.annotation.TableName tableNameAnnotation = + entityClass.getAnnotation(com.baomidou.mybatisplus.annotation.TableName.class); + + if (tableNameAnnotation == null) continue; + + // 7. 返回表名 + return tableNameAnnotation.value(); + } + // 8. 未找到符合条件的表名 + log.debug("[getMainTableName] {} 未找到带@TableName 的实体类", mapperClass.getSimpleName()); + return null; + + } catch (Exception e) { + log.warn("[getMainTableName] 获取主表名失败:{}", mapperClass.getName(), e); + return null; + } + } /* * 构建权限sql*/ private Expression buildExpression(Table table, Expression where) { @@ -92,6 +182,7 @@ public class QMSMultiDataPermissionHandler implements MultiDataPermissionHandler } String deptIdCol = QMSPermissionContextHolder.getDeptIdColumn(); //部门id列 String userIdCol = QMSPermissionContextHolder.getUserIdColumn(); //人员id列 + Boolean self = QMSPermissionContextHolder.getSelf(); String deptDataRoleCodes = QMSPermissionContextHolder.getDeptDataRoleCode(); //部门数据权限角色。具有该角色,然后按角色的权限属性判断。从而获得人员允许查看的部门数据 String moduleDataRoleCodes = QMSPermissionContextHolder.getModuleDataRoleCodes(); //模块数据权限角色。 String custom = QMSPermissionContextHolder.getCustom(); //todo 自定义权限 @@ -149,7 +240,11 @@ public class QMSMultiDataPermissionHandler implements MultiDataPermissionHandler // 情况三,拼接 Dept 和 Company User 的条件,最后组合 Expression deptExpression = buildDeptExpression(tableName, deptIdCol, tableAlias, effectiveDeptIds); - Expression userExpression = buildUserExpression(tableName, userIdCol, tableAlias, effectiveSelf, loginUser.getId()); + // 只有当 userIdCol 不为空且 self 为 true 时,才构建用户表达式 + Expression userExpression = null; + if (StrUtil.isNotEmpty(userIdCol) && Boolean.TRUE.equals(self)) { + userExpression = buildUserExpression(tableName, userIdCol, tableAlias, effectiveSelf, loginUser.getId()); + } if (deptExpression == null && userExpression == null) { // TODO:获得不到条件的时候,暂时不抛出异常,而是不返回数据 log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]", @@ -165,7 +260,7 @@ public class QMSMultiDataPermissionHandler implements MultiDataPermissionHandler return deptExpression; } // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?) - return new ParenthesedExpressionList(new OrExpression(deptExpression, userExpression)); + return new ParenthesedExpressionList<>(new OrExpression(deptExpression, userExpression)); } private Expression buildDeptExpression(String tableName, String columnName, Alias tableAlias, Set deptIds) { @@ -205,10 +300,26 @@ public class QMSMultiDataPermissionHandler implements MultiDataPermissionHandler if (CollUtil.isEmpty(deptIds)) { return null; } - // 拼接条件 + /*// 拼接条件 return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), // Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号 - new ParenthesedExpressionList(new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)))); + new ParenthesedExpressionList( + new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new))));*/ + // 构建:(dept_id IS NULL OR dept_id IN (?, ?, ?)) + // 含义:部门 ID 为空的数据允许所有人查看,部门 ID 不为空的数据只允许指定部门查看 + Column column = MyBatisUtils.buildColumn(tableName, tableAlias, columnName); + + // 构建 IS NULL 条件(允许部门 ID 为空的数据) + IsNullExpression isNullExpr = new IsNullExpression(column); + + // 构建 IN 条件(限制部门 ID 不为空的数据) + InExpression inExpression = new InExpression(column, + new ParenthesedExpressionList<>( + new ExpressionList<>(CollectionUtils.convertList(deptIds, LongValue::new)))); + + // 组合:IS NULL OR IN (...) + OrExpression orExpression = new OrExpression(isNullExpr, inExpression); + return new ParenthesedExpressionList<>(orExpression); } private Expression buildUserExpression(String tableName, String columnName, Alias tableAlias, Boolean self, Long userId) { diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/framework/datapermission/QMSPermissionContextHolder.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/framework/datapermission/QMSPermissionContextHolder.java index 192bae82..ed663811 100644 --- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/framework/datapermission/QMSPermissionContextHolder.java +++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/framework/datapermission/QMSPermissionContextHolder.java @@ -11,6 +11,7 @@ public class QMSPermissionContextHolder { private static final ThreadLocal moduleDataRoleCodes = new TransmittableThreadLocal<>(); //模块数据权限 private static final ThreadLocal deptIdColumn = new TransmittableThreadLocal<>(); //部门id列 private static final ThreadLocal userIdColumn = new TransmittableThreadLocal<>(); //人员id列 + private static final ThreadLocal self = new TransmittableThreadLocal<>(); private static final ThreadLocal custom = new TransmittableThreadLocal<>(); //人员id列 public static void setEnable(Boolean ignore) { @@ -21,12 +22,14 @@ public class QMSPermissionContextHolder { return Boolean.TRUE.equals(enable.get()); } - public static void setContext(boolean enable, String deptDataRoleCode, String moduleDataRoleCode, String deptIdColumn, String userIdColumn, String custom){ + public static void setContext(boolean enable, String deptDataRoleCode, String moduleDataRoleCode, + String deptIdColumn, String userIdColumn, Boolean self, String custom){ QMSPermissionContextHolder.setEnable(enable); QMSPermissionContextHolder.deptDataRoleCodes.set(deptDataRoleCode); QMSPermissionContextHolder.moduleDataRoleCodes.set(moduleDataRoleCode); QMSPermissionContextHolder.deptIdColumn.set(deptIdColumn); QMSPermissionContextHolder.userIdColumn.set(userIdColumn); + QMSPermissionContextHolder.self.set(self); QMSPermissionContextHolder.custom.set(custom); } @@ -62,6 +65,15 @@ public class QMSPermissionContextHolder { return userIdColumn.get(); } + // 新增 self 的 getter 和 setter + public static void setSelf(Boolean self) { + QMSPermissionContextHolder.self.set(self); + } + + public static Boolean getSelf() { + return self.get(); + } + public static void setCustom(String custom) { QMSPermissionContextHolder.custom.set(custom); } diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/resource/material/dal/mapper/MaterialBatchMapper.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/resource/material/dal/mapper/MaterialBatchMapper.java index 72b581d5..b215d9fb 100644 --- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/resource/material/dal/mapper/MaterialBatchMapper.java +++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/resource/material/dal/mapper/MaterialBatchMapper.java @@ -48,7 +48,7 @@ public interface MaterialBatchMapper extends BaseMapperX { .orderByDesc(MaterialBatchDO::getId)); } - // @QmsPermission(deptDataRoleCodes = "ytjyDeptAndSub", moduleDataRoleCodes = "qms_material_manager") + @QmsPermission(moduleDataRoleCodes = "ytjyAdmin", deptIdColumn = "ASN_DEPT_ID") default PageResult selectPageWithPdtInfo(MaterialBatchPageReqVO reqVO, List pdtIds) { MPJLambdaWrapper wrapper = new MPJLambdaWrapperX() diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/resource/material/service/MaterialBatchServiceImpl.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/resource/material/service/MaterialBatchServiceImpl.java index 376afb53..8a846903 100644 --- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/resource/material/service/MaterialBatchServiceImpl.java +++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/resource/material/service/MaterialBatchServiceImpl.java @@ -392,21 +392,7 @@ public class MaterialBatchServiceImpl implements MaterialBatchService { pageResult = materialBatchMapper.selectPageWithPdtInfo(pageReqVO, List.of()); } - // 查全部 - if (StrUtil.isEmpty(pageReqVO.getDataType())) { - List respVOS = pageResult.getList(); - if (CollUtil.isNotEmpty(respVOS)) { - List batIds = respVOS.stream().map(MaterialBatchRespVO::getId).toList(); - List gongs = materialBatchMapper.selectList(Wrappers.lambdaQuery(MaterialBatchDO.class) - .in(MaterialBatchDO::getParentId, batIds)); - if (CollUtil.isNotEmpty(gongs)) { - List gongRespVOs = gongs.stream().map(gong -> BeanUtils.toBean(gong, MaterialBatchRespVO.class)).toList(); - respVOS.addAll(gongRespVOs); - pageResult.setList(respVOS); - } - } - } - + // 给批次设置顶级分类的json配置 if (StrUtil.isEmpty(pageReqVO.getDataType()) || MaterialBatchGongType.batch.name().equals(pageReqVO.getDataType())) { List batches = pageResult.getList(); if (CollUtil.isNotEmpty(batches)) { @@ -428,6 +414,21 @@ public class MaterialBatchServiceImpl implements MaterialBatchService { } } + // 查全部 + if (StrUtil.isEmpty(pageReqVO.getDataType())) { + List respVOS = pageResult.getList(); + if (CollUtil.isNotEmpty(respVOS)) { + List batIds = respVOS.stream().map(MaterialBatchRespVO::getId).toList(); + List gongs = materialBatchMapper.selectList(Wrappers.lambdaQuery(MaterialBatchDO.class) + .in(MaterialBatchDO::getParentId, batIds)); + if (CollUtil.isNotEmpty(gongs)) { + List gongRespVOs = gongs.stream().map(gong -> BeanUtils.toBean(gong, MaterialBatchRespVO.class)).toList(); + respVOS.addAll(gongRespVOs); + pageResult.setList(respVOS); + } + } + } + // 需要组装children if (!MaterialBatchGongType.gong.name().equals(pageReqVO.getDataType()) && pageReqVO.getChildren()) { List voList = pageResult.getList();