动态视图支持权限配置

动态页面在前端自定义之后,目前是没办法通过权限去控制路由页面的权限的,本篇文章将介绍如何解决这个问题。
阅读本篇文章之前,应已经实现自定义页面渲染动态视图。参考文章:自定义视图内部渲染动态视图


实现思路:

  1. 通过自定义页面里设计的组件api名字,获取配置的路由页面名字。
  2. 解析路由页面包含的所有动作,拼接权限节点。
  3. 将路由页面的权限节点,拼到自定义视图所在菜单的权限管理上。

效果,路由页面的动作出现在自定义视图所在的菜单权限节点上
动态视图支持权限配置

代码示例

package pro.shushi.pamirs.top.core.auth;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.auth.api.entity.node.ActionPermissionNode;
import pro.shushi.pamirs.auth.api.entity.node.PermissionNode;
import pro.shushi.pamirs.auth.api.extend.load.PermissionNodeLoadExtendApi;
import pro.shushi.pamirs.auth.api.loader.visitor.AuthCompileContext;
import pro.shushi.pamirs.auth.api.loader.visitor.AuthCompileVisitor;
import pro.shushi.pamirs.auth.api.loader.visitor.DslParser;
import pro.shushi.pamirs.auth.api.pmodel.AuthResourceAuthorization;
import pro.shushi.pamirs.auth.api.utils.AuthAuthorizationHelper;
import pro.shushi.pamirs.boot.base.model.Menu;
import pro.shushi.pamirs.boot.base.model.View;
import pro.shushi.pamirs.boot.base.model.ViewAction;
import pro.shushi.pamirs.boot.base.ux.model.UIView;
import pro.shushi.pamirs.boot.base.ux.model.UIWidget;
import pro.shushi.pamirs.boot.base.ux.model.view.UIField;
import pro.shushi.pamirs.boot.web.loader.path.AccessResourceInfo;
import pro.shushi.pamirs.boot.web.loader.path.ResourcePath;
import pro.shushi.pamirs.boot.web.manager.MetaCacheManager;
import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j;
import pro.shushi.pamirs.meta.common.spi.SPI;
import pro.shushi.pamirs.top.api.TopModule;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Component
@Order(88)
@SPI.Service
@Slf4j
public class MyTestNodeLoadExtend implements PermissionNodeLoadExtendApi {

    @Autowired
    protected MetaCacheManager metaCacheManager;

    @Override
    public List<PermissionNode> buildNextPermissions(PermissionNode selected, List<PermissionNode> nodes) {
        // 动态页面所属的菜单name
        List<String> menuNames = new ArrayList<>();
        menuNames.add("uiMenu3c094a75bd88461ba0ad780825069b32");
        // 自定义组件api名称
        List<String> apiNames = new ArrayList<>();
        apiNames.add("dynamicView");

        List<ActionPermissionNode> newNodes = new ArrayList<>();
        for (String menuName : menuNames) {
            List<ActionPermissionNode> actionPermissionNodes = buildActionPermissionNodes(selected, menuName, apiNames);
            if (CollectionUtils.isNotEmpty(actionPermissionNodes)) {
                newNodes.addAll(actionPermissionNodes);
            }
        }
        nodes.addAll(newNodes);
        return nodes;
    }

    private List<ActionPermissionNode> buildActionPermissionNodes(PermissionNode selected, String menuName, List<String> apiNames) {
        String path = ResourcePath.generatorPath(TopModule.MODULE_MODULE, menuName);
        if (!path.equals(selected.getPath())) {
            return null;
        }
        Menu menu = metaCacheManager.fetchCloneMenus(TopModule.MODULE_MODULE).stream()
                .filter(v -> v.getName().equals(menuName))
                .findFirst()
                .orElse(null);
        if (menu == null) {
            return null;
        }
        menu.fieldQuery(Menu::getViewAction);
        View mainView = fetchMainView(menu.getViewAction());
        if (mainView == null) {
            return null;
        }
        UIView parser = new UIView();
        try {
            parser = DslParser.parser(mainView);
        } catch (Throwable e) {
            log.error("compile view error. model: {}, name: {}", mainView.getModel(), mainView.getName(), e);
        }
        List<ActionPermissionNode> dyNodes = new ArrayList<>();
        for (String apiName : apiNames) {
            List<ActionPermissionNode> viewPermissionNode = getViewPermissionNode(selected, parser, menu, apiName);
            if (CollectionUtils.isNotEmpty(viewPermissionNode)) {
                dyNodes.addAll(viewPermissionNode);
            }
        }
        return dyNodes;
    }

    private List<ActionPermissionNode> getViewPermissionNode(PermissionNode selected, UIView parser, Menu menu, String apiName) {

        String viewName = findLabelByApiName(parser.getWidgets(), apiName);
        View view = new View().setName(viewName).queryOne();
        if (view == null) {
            log.info("incorrect view name, viewName: {}", viewName);
            return null;
        }
        Long groupId = selected.getGroupId();
        Map<String, AuthResourceAuthorization> authorizationMap = null;
        if (groupId != null) {
            authorizationMap = AuthAuthorizationHelper.fetchActionAuthorizationMap(groupId);
        }
        return compileByMenu(view, menu, menu.getViewAction(), authorizationMap);
    }

    private View fetchMainView(ViewAction viewAction) {
        String resModel = Optional.ofNullable(viewAction.getResModel()).orElse(viewAction.getModel());
        View resView = viewAction.getResView();
        View mainView;
        if (resView == null || resView.getTemplate() == null) {
            mainView = metaCacheManager.fetchView(resModel, viewAction.getResViewName(), viewAction.getViewType(), false);
        } else {
            mainView = resView;
        }
        return mainView;
    }

    private List<ActionPermissionNode> compileByMenu(View mainView, Menu menu, ViewAction viewAction, Map<String, AuthResourceAuthorization> authorizationMap) {
        AccessResourceInfo info = new AccessResourceInfo();
        info.setModule(menu.getModule());
        info.setModel(viewAction.getModel());
        info.setActionName(viewAction.getName());
        info.setMenu(menu.getName());
        info.setViewAction(viewAction);
        return compile(info, mainView, authorizationMap);
    }

    private List<ActionPermissionNode> compile(AccessResourceInfo info, View mainView, Map<String, AuthResourceAuthorization> authorizationMap) {
        if (log.isDebugEnabled()) {
            log.debug("auth compile view. model: {}, name: {}", mainView.getModel(), mainView.getName());
        }
        AuthCompileContext context = new AuthCompileContext(info, authorizationMap);
        AuthCompileVisitor visitor = new AuthCompileVisitor(context);
        try {
            DslParser.visit(mainView, visitor);
        } catch (Throwable e) {
            log.error("compile view error. model: {}, name: {}", mainView.getModel(), mainView.getName(), e);
        }
        return visitor.getNodes();
    }

    private String findLabelByApiName(List<? extends UIWidget> elements, String apiName) {
        if (elements == null || elements.isEmpty()) {
            return null;
        }
        for (UIWidget element : elements) {
            // 判断是否是指定组件。
            if (element instanceof UIField) {
                UIField field = (UIField) element;
                if (apiName.equals(field.getWidget())) {
                // 这里获取该组件的标题作为视图名字的配置,如业务有不同请自行修改。
                    return field.getLabel();
                }
            }

            List<? extends UIWidget> subElements = element.getWidgets();
            if (subElements != null && !subElements.isEmpty()) {
                String label = findLabelByApiName(subElements, apiName);
                if (label != null) {
                    return label;
                }
            }
        }
        return null;
    }

}

Oinone社区 作者:yexiu原创文章,如若转载,请注明出处:https://doc.oinone.top/dai-ma-shi-jian/21714.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(0)
yexiu的头像yexiu数式员工
上一篇 2025年9月8日 am10:45
下一篇 2025年9月18日 pm2:51

相关推荐

  • 集成设计器数据流程、流程设计器可以暴露接口触发

    集成设计器数据流程暴露接口触发 需求:在集成设计器配置的连接器、数据流程链接到外部接口,需要可以有一个管理页面,统一管理这些集成配置。比如对接多个医院的挂号系统,希望可以配置好数据流程之后,能够在已经实现的开放接口上,动态的调用集成平台配置的数据流程。 连接器暴露接口触发 设计连接器资源配置模型。使用业务ID+接口唯一标识+连接器实现连接器资源和业务唯一。 @Model.model(EipConnectorResourceSetting.MODEL_MODEL) @Model(displayName = "连接器资源配置", summary = "连接器资源配置") @Model.Advanced(unique = {"hospitalId, interfaceUnique"}) public class EipConnectorResourceSetting extends IdModel { public static final String MODEL_MODEL = "hr.simple.EipConnectorResourceSetting"; @UxTableSearch.FieldWidget(@UxWidget()) @Field(displayName = "医院", required = true) @Field.Relation(relationFields = {"hospitalId"}, referenceFields = {"id"}) private Hospital hospital; @Field.Integer @Field(displayName = "医院ID", invisible = true) private Long hospitalId; @UxTableSearch.FieldWidget(@UxWidget()) @Field.String @Field(displayName = "接口唯一标识", required = true) private String interfaceUnique; @UxTableSearch.FieldWidget(@UxWidget()) @Field.many2one @Field(displayName = "连接器", required = true) @Field.Relation(relationFields = {"connectorId"}, referenceFields = {"id"}) private EipConnector connector; @Field.Integer @Field(displayName = "连接器ID", invisible = true) private Long connectorId; @UxTableSearch.FieldWidget(@UxWidget()) @Field.many2one @Field(displayName = "连接器接口", required = true) @Field.Relation(relationFields = {"integrationInterfaceId"}, referenceFields = {"id"}, domain = "connectorId==${activeRecord.connector.id}") private EipConnectorResource connectorResource; @Field.Integer @Field(displayName = "连接器接口ID", invisible = true) private Long connectorResourceId; } 开放接口定义,文档参考:https://doc.oinone.top/oio4/9326.html外部平台调用开放接口,实现动态调用连接器资源 @Model.model(HospitalSection.MODEL_MODEL) @Model(displayName = "医院科室", summary = "医院科室") public class HospitalSection extends IdModel { public static final String MODEL_MODEL = "net.example.HospitalSection"; @Field(displayName = "科室Code", required = true) private String deptCode; @Field(displayName = "科室名称", required = true) private String deptName; @Field(displayName = "科室描述") private String deptDesc; } @Slf4j @Fun @Component public class TestOpenService { @Resource private TestCommonService testCommonService;…

    2025年4月21日
    38600
  • 如何把平台功能菜单加到项目中?

    概述 在使用Oinone低代码平台进行项目开发的过程中,会存在把平台默认提供的菜单加到自己的项目中。这篇文章介绍实现方式,以角色管理为例。 1. 低代码情况 即项目是通过后端代码初始化菜单的方式。 通常在 XXXMenu.java类通过@UxMenu注解的方式,代码片段如下: 此种情况与添加项目本地的菜单无区别,具体代码: @UxMenu(value = "账号管理", icon = "icon-yonghuguanli") class AccountManage { @UxMenu("用户管理") @UxRoute(value = CustomerCompanyUserProxy.MODEL_MODEL, title = "用户管理") class CompanyUserManage { } @UxMenu("角色管理") @UxRoute(value = AuthRole.MODEL_MODEL, module = AdminModule.MODULE_MODULE, title = "角色管理") class RoleManage { } @UxMenu("操作日志") @UxRoute(value = OperationLog.MODEL_MODEL, module = AdminModule.MODULE_MODULE/***菜单所挂载的模块**/) class OperateLog { } } 2. 无代码情况 在界面设计器中,新建菜单–>选择绑定已有页面,进行发布即可。

    2024年4月19日
    1.5K00
  • Oinone登录扩展:对接SSO(适应于4.7.8及之后的版本)

    适配版本 4.7.8及其之后的版本 概述 在企业内部,对于已有一套完整的登录系统(SSO)的情况下,通常会要求把所有的系统都对接到SSO中;本文主要讲解用Oinone开发的项目对接SSO的具体实现。 对接步骤 1、项目自定义实现UserCookieLogin,可参考示例说明:pro.shushi.pamirs.user.api.login.UserCookieLoginFree 2、对接SSO示例 package pro.shushi.pamirs.demo.core.sso; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import pro.shushi.pamirs.demo.core.sso.constant.HttpConstant; import pro.shushi.pamirs.demo.core.sso.constant.SessionUserTypeEnum; import pro.shushi.pamirs.demo.core.sso.model.ApiCommonTransient; import pro.shushi.pamirs.demo.core.sso.model.PermissionInfoResp; import pro.shushi.pamirs.demo.core.sso.utils.AuthenticateUtils; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.api.dto.model.PamirsUserDTO; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.common.exception.PamirsException; import pro.shushi.pamirs.meta.common.spring.BeanDefinitionUtils; import pro.shushi.pamirs.resource.api.enmu.UserSignUpType; import pro.shushi.pamirs.user.api.cache.UserCache; import pro.shushi.pamirs.user.api.constants.UserConstant; import pro.shushi.pamirs.user.api.enmu.UserExpEnumerate; import pro.shushi.pamirs.user.api.enmu.UserLoginTypeEnum; import pro.shushi.pamirs.user.api.login.IUserLoginChecker; import pro.shushi.pamirs.user.api.login.UserCookieLogin; import pro.shushi.pamirs.user.api.login.UserCookieLoginSimple; import pro.shushi.pamirs.user.api.model.PamirsUser; import pro.shushi.pamirs.user.api.model.tmodel.PamirsUserTransient; import pro.shushi.pamirs.user.api.service.UserService; import pro.shushi.pamirs.user.api.utils.CookieUtil; import javax.servlet.http.HttpServletResponse; /** * * @author shushi * * 完全自定义login的过程 * 需要实现登陆部分login 以及拦截部分fetchUserIdByReq * 如果fetchUserIdByReq返回值为null的时候 将会被拦截 */ @Slf4j @Order(0) @Component public class DemoUserSSOCookieLogin extends UserCookieLogin<PamirsUser> { //刷新令牌 private static String REFRESH_TOKEN = "refreshToken"; //系统id private static String CLIENT_ID = "client-id"; //访问令牌 private static String AUTHORIZATION = "Authorization"; private IUserLoginChecker checker; @Autowired private UserService userService; @Autowired private RedisTemplate<String, String> redisTemplate; @Override public String type() { return UserLoginTypeEnum.COOKIE.value(); } @Override public PamirsUser resolveAndVerification(PamirsUserTransient user) { if (checker == null) { checker = BeanDefinitionUtils.getBean(IUserLoginChecker.class); } return checker.check4login(user); } /** * 重写登录拦截功能 * 该函数主要作用,通过三方权限校验. * @return */ // 版本升级需要修改 @Override public PamirsUserDTO fetchUserIdByReq() { String sessionId = PamirsSession.getSessionId(); PamirsUserDTO pamirsUserDTO = UserCache.get(sessionId); if (pamirsUserDTO ==null) { //H5-企业微信登录,其他SSO登录。获取标识…

    2024年4月2日
    1.7K00
  • 查询时自定义排序字段和排序规则

    指定字段排序 平台默认排序字段,参考IdModel,按创建时间和ID倒序(ordering = "createDate DESC, id DESC") 方法1:模型指定排序 模型定义增加排序字段。@Model.Advanced(ordering = "xxxxx DESC, yyyy DESC") @Model.model(PetShop.MODEL_MODEL) @Model(displayName = "宠物店铺",summary="宠物店铺",labelFields ={"shopName"}) @Model.Code(sequence = "DATE_ORDERLY_SEQ",prefix = "P",size=6,step=1,initial = 10000,format = "yyyyMMdd") @Model.Advanced(ordering = "createDate DESC") public class PetShop extends AbstractDemoIdModel { public static final String MODEL_MODEL="demo.PetShop"; // ………… } 方法2:Page查询中可以自定排序规则 API参考 pro.shushi.pamirs.meta.api.dto.condition.Pagination#orderBy public <G, R> Pagination<T> orderBy(SortDirectionEnum direction, Getter<G, R> getter) { if (null == getSort()) { setSort(new Sort()); } getSort().addOrder(direction, getter); return this; } 具体示例 @Function.Advanced(type= FunctionTypeEnum.QUERY) @Function.fun(FunctionConstants.queryPage) @Function(openLevel = {FunctionOpenEnum.API}) public Pagination<PetShop> queryPage(Pagination<PetShop> page, IWrapper<PetShop> queryWrapper){ page.orderBy(SortDirectionEnum.DESC, PetShop::getCreateDate); page = new PetShop().queryPage(page, queryWrapper); return page; } 方法3:查询的wapper中指定 API参考:pro.shushi.pamirs.framework.connectors.data.sql.AbstractWrapper#orderBy @Override public Children orderBy(boolean condition, boolean isAsc, R… columns) { if (ArrayUtils.isEmpty(columns)) { return typedThis; } SqlKeyword mode = isAsc ? ASC : DESC; for (R column : columns) { doIt(condition, ORDER_BY, columnToString(column), mode); } return typedThis; } 具体示例 public List<PetShop> queryList(String name) { List<PetShop> petShops = Models.origin().queryListByWrapper( Pops.<PetShop>lambdaQuery().from(PetShop.MODEL_MODEL) .orderBy(true, true, PetShop::getCreateDate) .orderBy(true, true, PetShop::getId) .like(PetShop::getShopName, name)); return petShops; } 设置查询不排序 方法1:关闭平台默认排序字段,设置模型的ordering,改成:ordering = "1=1" 模型定义增加排序字段。@Model.Advanced(ordering = "1=1") @Model.model(PetShop.MODEL_MODEL) @Model(displayName = "宠物店铺",summary="宠物店铺",labelFields ={"shopName"}) @Model.Code(sequence = "DATE_ORDERLY_SEQ",prefix = "P",size=6,step=1,initial = 10000,format = "yyyyMMdd") @Model.Advanced(ordering =…

    2024年5月25日
    1.4K00
  • 工作流:通过业务数据操作工作流程(催办、撤销等)

    一、抽象模型,需要操作流程的模型继承此模型 定义流程相关的一些信息在模型中;如果直接定义在存储模型中,下面这些字段都是显示的指定为非存储字段。更好的建议:1、如果有多个业务模型有这类需要,则可以把这些字段抽取到抽象模型中2、如果仅有一个业务模型需要,则可以放到代理模型中 /** * 定义流程相关的一些信息在模型中 */ @Model.model(DemoBaseAbstractModel.MODEL_MODEL) @Model(displayName = "抽象模型") @Model.Advanced(type= ModelTypeEnum.ABSTRACT) public abstract class DemoBaseAbstractModel extends IdModel { public static final String MODEL_MODEL = "hr.simple.DemoBaseAbstractModel"; // 流程相关 @Field.Integer @Field(displayName = "工作流用户任务ID", summary = "业务数据列表中审核流程使用", invisible = true, store = NullableBoolEnum.FALSE) private Long workflowUserTaskId; @Field.Integer @Field(displayName = "流程实例ID", summary = "业务数据列表中催办使用", invisible = true, store = NullableBoolEnum.FALSE) private Long instanceId; @Field.String @UxForm.FieldWidget(@UxWidget(invisible = "true")) @UxDetail.FieldWidget(@UxWidget(invisible = "true")) @Field(displayName = "当前流程节点", store = NullableBoolEnum.FALSE) private String currentFlowNode; @Field.Boolean @Field(displayName = "能否催办", invisible = true, defaultValue = "false", store = NullableBoolEnum.FALSE) private Boolean canUrge; // 审批状态控制申请单是否可以被发起流程、能否编辑等控制 @Field.Enum @Field(displayName = "审批状态", defaultValue = "NC") @UxForm.FieldWidget(@UxWidget(invisible = "true")) private ApprovalStatusEnum approvalStatus; } @Dict(dictionary = ApprovalStatusEnum.dictionary, displayName = "审批状态") public enum ApprovalStatusEnum implements IEnum<String> { NC("NC", "待提交", "待提交"), PENDING("PENDING", "已提交", "已提交,待审批"), APPROVED("APPROVED", "已批准", "已批准"), REJECTED("REJECTED", "已拒绝", "已拒绝"); public static final String dictionary = "land.enums.LandApprovalStatusEnum"; private final String value; private final String displayName; private final String help; ApprovalStatusEnum(String value, String displayName, String help) { this.value = value; this.displayName = displayName; this.help = help; } public String getValue() { return value; } public String getDisplayName() { return…

    2025年6月26日
    36100

Leave a Reply

登录后才能评论