3 Commits

Author SHA1 Message Date
AprilWind
5c9721cfac update 优化工作流权限按钮获取,若需要扩展更多按钮权限,只需在 sources 中新增对应的枚举类或字典类型 2025-04-11 16:01:03 +08:00
疯狂的狮子Li
31502dccc7 update satoken 1.40.0 => 1.42.0 适配所有升级项(改动较多)
SaLoginModel -> SaLoginParameter
device -> deviceType
satoken BCrypt -> hutool BCrypt(satoken不维护了)
SaTokenDao -> SaTokenDaoBySessionFollowObject(satoken做了重构封装)
sse 适配新satoken版本拦截器变化
2025-04-11 15:36:38 +08:00
疯狂的狮子Li
538aa8d908 update 优化 统一流程demo 权限人分隔符 2025-04-11 15:07:18 +08:00
27 changed files with 202 additions and 99 deletions

View File

@@ -23,7 +23,7 @@
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<easyexcel.version>4.0.3</easyexcel.version>
<velocity.version>2.3</velocity.version>
<satoken.version>1.40.0</satoken.version>
<satoken.version>1.42.0</satoken.version>
<mybatis-plus.version>3.5.11</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.35</hutool.version>

View File

@@ -1,8 +1,8 @@
package org.dromara.web.listener;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.convert.Convert;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
@@ -40,7 +40,7 @@ public class UserActionListener implements SaTokenListener {
* 每次登录时触发
*/
@Override
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP();
UserOnlineDTO dto = new UserOnlineDTO();
@@ -50,17 +50,17 @@ public class UserActionListener implements SaTokenListener {
dto.setOs(userAgent.getOs().getName());
dto.setLoginTime(System.currentTimeMillis());
dto.setTokenId(tokenValue);
String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY);
String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
dto.setUserName(username);
dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
dto.setDeviceType(loginModel.getDevice());
dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
dto.setDeviceType(loginParameter.getDeviceType());
dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
TenantHelper.dynamic(tenantId, () -> {
if(loginModel.getTimeout() == -1) {
if(loginParameter.getTimeout() == -1) {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
} else {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginModel.getTimeout()));
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout()));
}
});
// 记录登录日志
@@ -72,7 +72,7 @@ public class UserActionListener implements SaTokenListener {
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
// 更新登录信息
loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
loginService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
}

View File

@@ -1,6 +1,6 @@
package org.dromara.web.service;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.Constants;

View File

@@ -1,7 +1,7 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
@@ -58,8 +58,8 @@ public class EmailAuthStrategy implements IAuthStrategy {
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());

View File

@@ -1,9 +1,9 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -70,8 +70,8 @@ public class PasswordAuthStrategy implements IAuthStrategy {
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());

View File

@@ -1,7 +1,7 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
@@ -58,8 +58,8 @@ public class SmsAuthStrategy implements IAuthStrategy {
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());

View File

@@ -1,7 +1,7 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -99,8 +99,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());

View File

@@ -1,7 +1,7 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -76,8 +76,8 @@ public class XcxAuthStrategy implements IAuthStrategy {
loginUser.setDeviceType(client.getDeviceType());
loginUser.setOpenid(openid);
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
SaLoginParameter model = new SaLoginParameter();
model.setDeviceType(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());

View File

@@ -1,6 +1,6 @@
package org.dromara.common.satoken.core.dao;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.auto.SaTokenDaoBySessionFollowObject;
import cn.dev33.satoken.util.SaFoxUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
@@ -16,10 +16,12 @@ import java.util.concurrent.TimeUnit;
* Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
* <p>
* 采用 caffeine + redis 多级缓存 优化并发查询效率
* <p>
* SaTokenDaoBySessionFollowObject 是 SaTokenDao 子集简化了session方法处理
*
* @author Lion Li
*/
public class PlusSaTokenDao implements SaTokenDao {
public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期
@@ -107,6 +109,19 @@ public class PlusSaTokenDao implements SaTokenDao {
return o;
}
/**
* 获取 Object (指定反序列化类型),如无返空
*
* @param key 键名称
* @return object
*/
@SuppressWarnings("unchecked cast")
@Override
public <T> T getObject(String key, Class<T> classType) {
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
return (T) o;
}
/**
* 写入Object并设定存活时间 (单位: 秒)
*/
@@ -165,7 +180,6 @@ public class PlusSaTokenDao implements SaTokenDao {
RedisUtils.expire(key, Duration.ofSeconds(timeout));
}
/**
* 搜索数据
*/

View File

@@ -1,8 +1,8 @@
package org.dromara.common.satoken.utils;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
@@ -47,8 +47,8 @@ public class LoginHelper {
* @param loginUser 登录用户信息
* @param model 配置参数
*/
public static void login(LoginUser loginUser, SaLoginModel model) {
model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
public static void login(LoginUser loginUser, SaLoginParameter model) {
model = ObjectUtil.defaultIfNull(model, new SaLoginParameter());
StpUtil.login(loginUser.getLoginId(),
model.setExtra(TENANT_KEY, loginUser.getTenantId())
.setExtra(USER_KEY, loginUser.getUserId())

View File

@@ -11,13 +11,13 @@ import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.SseException;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.security.config.properties.SecurityProperties;
import org.dromara.common.security.handler.AllUrlHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -37,6 +37,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class SecurityConfig implements WebMvcConfigurer {
private final SecurityProperties securityProperties;
@Value("${sse.path}")
private String ssePath;
/**
* 注册sa-token的拦截器
@@ -54,15 +56,7 @@ public class SecurityConfig implements WebMvcConfigurer {
.check(() -> {
HttpServletRequest request = ServletUtils.getRequest();
// 检查是否登录 是否有token
try {
StpUtil.checkLogin();
} catch (NotLoginException e) {
if (request.getRequestURI().contains("sse")) {
throw new SseException(e.getMessage(), e.getCode());
} else {
throw e;
}
}
StpUtil.checkLogin();
// 检查 header 与 param 里的 clientid 与 token 里的是否一致
String headerCid = request.getHeader(LoginHelper.CLIENT_KEY);
@@ -84,7 +78,8 @@ public class SecurityConfig implements WebMvcConfigurer {
});
})).addPathPatterns("/**")
// 排除不需要拦截的路径
.excludePathPatterns(securityProperties.getExcludes());
.excludePathPatterns(securityProperties.getExcludes())
.excludePathPatterns(ssePath);
}
/**

View File

@@ -33,6 +33,7 @@ public class SseController implements DisposableBean {
*/
@GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter connect() {
StpUtil.checkLogin();
String tokenValue = StpUtil.getTokenValue();
Long userId = LoginHelper.getUserId();
return sseEmitterManager.connect(userId, tokenValue);

View File

@@ -81,6 +81,17 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
}
/**
* 获取 Object (指定反序列化类型),如无返空
*
* @param key 键名称
* @return object
*/
@Override
public <T> T getObject(String key, Class<T> classType) {
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key, classType);
}
/**
* 写入Object并设定存活时间 (单位: 秒)
*/
@@ -137,7 +148,6 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
}
/**
* 搜索数据
*/

View File

@@ -1,8 +1,8 @@
package org.dromara.system.controller.system;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.digest.BCrypt;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils;

View File

@@ -1,10 +1,10 @@
package org.dromara.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;

View File

@@ -1,11 +1,11 @@
package org.dromara.system.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

View File

@@ -12,18 +12,18 @@ import java.io.Serializable;
* @date 2025-02-28
*/
@Data
public class ButtonPermission implements Serializable {
public class ButtonPermissionVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 枚举路径
* 唯一编码
*/
private String code;
/**
* 按钮编码
* 选项值
*/
private String value;
@@ -31,4 +31,13 @@ public class ButtonPermission implements Serializable {
* 是否显示
*/
private boolean show;
public ButtonPermissionVo() {
}
public ButtonPermissionVo(String code, boolean show) {
this.code = code;
this.show = show;
}
}

View File

@@ -1,20 +1,16 @@
package org.dromara.workflow.domain.vo;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.warm.flow.core.entity.User;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.common.enums.ButtonPermissionEnum;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import java.util.Date;
import java.util.List;
/**
* 任务视图
@@ -186,30 +182,6 @@ public class FlowTaskVo implements Serializable {
/**
* 按钮权限
*/
private List<ButtonPermission> buttonList;
private List<ButtonPermissionVo> buttonList;
public List<ButtonPermission> getButtonList(String ext) {
List<ButtonPermission> buttonPermissions = Arrays.stream(ButtonPermissionEnum.values())
.map(value -> {
ButtonPermission buttonPermission = new ButtonPermission();
buttonPermission.setCode(value.getValue());
buttonPermission.setShow(false);
return buttonPermission;
})
.collect(Collectors.toList());
if (StringUtils.isNotBlank(ext)) {
List<ButtonPermission> buttonCodeList = JSONUtil.toList(JSONUtil.parseArray(ext), ButtonPermission.class);
if (CollUtil.isNotEmpty(buttonCodeList)) {
Optional<ButtonPermission> firstPermission = buttonCodeList.stream().findFirst();
firstPermission.ifPresent(permission -> {
Set<String> codeSet = Arrays.stream(permission.getValue().split(","))
.map(String::trim)
.filter(code -> !code.isEmpty())
.collect(Collectors.toSet());
buttonPermissions.forEach(bp -> bp.setShow(codeSet.contains(bp.getCode())));
});
}
}
return buttonPermissions;
}
}

View File

@@ -0,0 +1,22 @@
package org.dromara.workflow.service;
import org.dromara.workflow.domain.vo.ButtonPermissionVo;
import java.util.List;
/**
* 流程节点扩展属性 服务层
*
* @author AprilWind
*/
public interface IFlwNodeExtService {
/**
* 从扩展属性构建按钮权限列表:根据 ext 中记录的权限值,标记每个按钮是否勾选
*
* @param ext 扩展属性 JSON 字符串
* @return 按钮权限 VO 列表
*/
List<ButtonPermissionVo> buildButtonPermissionsFromExt(String ext);
}

View File

@@ -191,7 +191,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectByIds(StreamUtils.toList(flowHisTasks, FlowHisTask::getDefinitionId));
if (CollUtil.isNotEmpty(flowDefinitions)) {
String join = StreamUtils.join(flowDefinitions, FlowDefinition::getFlowCode);
log.error("流程定义【{}】已被使用不可被删除!", join);
log.info("流程定义【{}】已被使用不可被删除!", join);
throw new ServiceException("流程定义【" + join + "】已被使用不可被删除!");
}
}

View File

@@ -2,18 +2,25 @@ package org.dromara.workflow.service.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.DictTypeDTO;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.warm.flow.ui.service.NodeExtService;
import org.dromara.warm.flow.ui.vo.NodeExt;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.enums.ButtonPermissionEnum;
import org.dromara.workflow.common.enums.NodeExtEnum;
import org.dromara.workflow.domain.vo.ButtonPermissionVo;
import org.dromara.workflow.service.IFlwNodeExtService;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 流程设计器-节点扩展属性
@@ -24,14 +31,15 @@ import java.util.*;
@Slf4j
@RequiredArgsConstructor
@Service
public class FlwNodeExtServiceImpl implements NodeExtService {
public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService {
/**
* 存储不同 dictType 对应的配置信息
*/
private static final Map<String, ButtonPermission> CHILD_NODE_MAP = new HashMap<>();
record ButtonPermission(String label, Integer type, Boolean must, Boolean multiple) {}
record ButtonPermission(String label, Integer type, Boolean must, Boolean multiple) {
}
static {
CHILD_NODE_MAP.put(ButtonPermissionEnum.class.getSimpleName(),
@@ -162,4 +170,74 @@ public class FlwNodeExtServiceImpl implements NodeExtService {
return childNode;
}
/**
* 从扩展属性构建按钮权限列表:根据 ext 中记录的权限值,标记每个按钮是否勾选
*
* @param ext 扩展属性 JSON 字符串
* @return 按钮权限 VO 列表
*/
@Override
public List<ButtonPermissionVo> buildButtonPermissionsFromExt(String ext) {
// 解析 ext 为 Map<code, Set<value>>,用于标记权限
Map<String, Set<String>> permissionMap = JsonUtils.parseArray(ext, ButtonPermissionVo.class)
.stream()
.collect(Collectors.toMap(
ButtonPermissionVo::getCode,
item -> StringUtils.splitList(item.getValue()).stream()
.map(String::trim)
.filter(StrUtil::isNotBlank)
.collect(Collectors.toSet()),
(a, b) -> b,
HashMap::new
));
// 构建按钮权限列表,标记哪些按钮在 permissionMap 中出现(表示已勾选)
return buildPermissionsFromSources(permissionMap, List.of(ButtonPermissionEnum.class));
}
/**
* 将权限映射与按钮权限来源(枚举类或字典类型)进行匹配,生成权限视图列表
* <p>
* 使用说明:
* - sources 支持传入多个来源类型,支持 NodeExtEnum 枚举类 或 字典类型字符串dictType
* - 若需要扩展更多按钮权限,只需在 sources 中新增对应的枚举类或字典类型
* <p>
* 示例:
* buildPermissionsFromSources(permissionMap, List.of(ButtonPermissionEnum.class, "custom_button_dict"));
*
* @param permissionMap 权限映射
* @param sources 枚举类或字典类型列表
* @return 按钮权限视图对象列表
*/
@SuppressWarnings("unchecked cast")
private List<ButtonPermissionVo> buildPermissionsFromSources(Map<String, Set<String>> permissionMap, List<Object> sources) {
return sources.stream()
.flatMap(source -> {
if (source instanceof Class<?> clazz && NodeExtEnum.class.isAssignableFrom(clazz)) {
Set<String> selectedSet = permissionMap.getOrDefault(clazz.getSimpleName(), Collections.emptySet());
return extractDictItems(this.buildChildNode((Class<? extends NodeExtEnum>) clazz), selectedSet).stream();
} else if (source instanceof String dictType) {
Set<String> selectedSet = permissionMap.getOrDefault(dictType, Collections.emptySet());
return extractDictItems(this.buildChildNode(dictType), selectedSet).stream();
}
return Stream.empty();
}).toList();
}
/**
* 从节点子项中提取字典项,并构建按钮权限视图对象列表
*
* @param childNode 子节点
* @param selectedSet 已选中的值集
* @return 按钮权限视图对象列表
*/
private List<ButtonPermissionVo> extractDictItems(NodeExt.ChildNode childNode, Set<String> selectedSet) {
return Optional.ofNullable(childNode)
.map(NodeExt.ChildNode::getDict)
.orElse(List.of())
.stream()
.map(dict -> new ButtonPermissionVo(dict.getValue(), selectedSet.contains(dict.getValue())))
.toList();
}
}

View File

@@ -48,6 +48,7 @@ import org.dromara.workflow.handler.WorkflowPermissionHandler;
import org.dromara.workflow.mapper.FlwCategoryMapper;
import org.dromara.workflow.mapper.FlwTaskMapper;
import org.dromara.workflow.service.IFlwCommonService;
import org.dromara.workflow.service.IFlwNodeExtService;
import org.dromara.workflow.service.IFlwTaskAssigneeService;
import org.dromara.workflow.service.IFlwTaskService;
import org.springframework.stereotype.Service;
@@ -86,6 +87,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
private final FlowNodeMapper flowNodeMapper;
private final IFlwTaskAssigneeService flwTaskAssigneeService;
private final IFlwCommonService flwCommonService;
private final IFlwNodeExtService flwNodeExtService;
/**
* 启动任务
@@ -561,12 +563,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
flowTaskVo.setFlowCode(definition.getFlowCode());
flowTaskVo.setFlowName(definition.getFlowName());
flowTaskVo.setBusinessId(instance.getBusinessId());
//设置按钮权限
FlowNode flowNode = getByNodeCode(flowTaskVo.getNodeCode(), instance.getDefinitionId());
FlowNode flowNode = this.getByNodeCode(flowTaskVo.getNodeCode(), instance.getDefinitionId());
if (ObjectUtil.isNull(flowNode)) {
throw new NullPointerException("当前【" + flowTaskVo.getNodeCode() + "】节点编码不存在");
}
flowTaskVo.setButtonList(flowTaskVo.getButtonList(flowNode.getExt()));
//设置按钮权限
flowTaskVo.setButtonList(flwNodeExtService.buildButtonPermissionsFromExt(flowNode.getExt()));
flowTaskVo.setNodeRatio(flowNode.getNodeRatio());
flowTaskVo.setApplyNode(flowNode.getNodeCode().equals(flwCommonService.applyNodeCode(task.getDefinitionId())));
return flowTaskVo;

View File

@@ -52,7 +52,7 @@
"nodeType" : 1,
"nodeCode" : "a8abf15f-b83e-428a-86cc-033555ea9bbe",
"nodeName" : "部门主管",
"permissionFlag" : "role:3,role:4",
"permissionFlag" : "role:3@@role:4",
"nodeRatio" : 0.000,
"coordinate" : "720,200|720,200",
"formCustom" : "N",
@@ -72,4 +72,4 @@
"formCustom" : "N",
"ext" : "[]"
} ]
}
}

View File

@@ -58,7 +58,7 @@
"nodeType" : 1,
"nodeCode" : "b3528155-dcb7-4445-bbdf-3d00e3499e86",
"nodeName" : "组长",
"permissionFlag" : "3,4",
"permissionFlag" : "3@@4",
"nodeRatio" : 0.000,
"coordinate" : "720,320|720,320",
"formCustom" : "N",
@@ -108,4 +108,4 @@
"coordinate" : "770,160;860,160;860,200"
} ]
} ]
}
}

View File

@@ -106,7 +106,7 @@
"nodeType" : 1,
"nodeCode" : "762cb975-37d8-4276-b6db-79a4c3606394",
"nodeName" : "综合部",
"permissionFlag" : "role:3,role:4",
"permissionFlag" : "role:3@@role:4",
"nodeRatio" : 0.000,
"coordinate" : "800,300|800,300",
"formCustom" : "N",
@@ -118,4 +118,4 @@
"coordinate" : "850,300;920,300;920,245"
} ]
} ]
}
}

View File

@@ -52,7 +52,7 @@
"nodeType" : 1,
"nodeCode" : "2f9f2e21-9bcf-42a3-a07c-13037aad22d1",
"nodeName" : "全部审批通过",
"permissionFlag" : "role:1,role:3",
"permissionFlag" : "role:1@@role:3",
"nodeRatio" : 100.000,
"coordinate" : "820,240|820,240",
"formCustom" : "N",
@@ -87,4 +87,4 @@
"formCustom" : "N",
"ext" : "[]"
} ]
}
}

View File

@@ -55,7 +55,7 @@
"nodeType" : 1,
"nodeCode" : "c80f273e-1f17-4bd8-9ad1-04a4a94ea862",
"nodeName" : "会签",
"permissionFlag" : "role:1,role:3",
"permissionFlag" : "role:1@@role:3",
"nodeRatio" : 100.000,
"coordinate" : "700,320|700,320",
"formCustom" : "N",
@@ -118,4 +118,4 @@
"coordinate" : "750,120;860,120;860,195"
} ]
} ]
}
}