自定义用户中心菜单

使用扩展点实现用户中心菜单替换

1. 工程中引起pamirs-user-api

<dependency>
    <groupId>pro.shushi.pamirs.core</groupId>
    <artifactId>pamirs-user-api</artifactId>
</dependency>

2. 实现TopBarUserBlockAction的后置扩展

  • 实现HookAfter后置扩展接口
  • @Hook(model = {TopBarUserBlock.MODEL_MODEL}, fun = {"construct"})添加Hook注解注明是TopBarUserBlock模型的construct函数的后置扩展。

增加用户中心菜单

@Component
@Order(1)
@SPI.Service
public class MyTopBarActionExt implements TopBarActionExtendApi {

    public void edit(List<TopBarAction> list) {
        list.add(new TopBarAction("top_demo", "top.Teacher", "uiView9a0caf1d574a42c9847a057a0c4a4ad1", ActionTypeEnum.VIEW, 1)
                .setDisplayName("商品管理")
                .setIcon("oinone-gongzuotai")
        );
    }

}

实现效果

自定义用户中心菜单

替换原有的用户中心菜单

  1. 替换原有的菜单跳转

    @Component
    public class DemoTopBarUserBlockDataHookAfter implements HookAfter {
    
    @Override
    @Hook(model = {TopBarUserBlock.MODEL_MODEL}, fun = {"construct"})
    public Object run(Function function, Object ret) {
        if (ret == null) {
            return null;
        }
        TopBarUserBlock result = null;
        if (ret instanceof Object[]) {
            Object[] rets = (Object[]) ((Object[]) ret);
            if (rets.length == 1) {
                result = (TopBarUserBlock) rets[0];
              //例:替换用户中心:修改密码菜单
              //使用name和model查询出模型的ViewAction替换修改密码ViewAction
                ViewAction demoViewAction = PamirsSession.getContext().getExtendCache(ActionCacheApi.class).get(Dog.MODEL_MODEL, "changePassword");
                //设置菜单的icon
                Map<String, Object> attributes = Optional.ofNullable(demoViewAction.getAttributes()).orElse(new HashMap<>());
                attributes.put("icon", "oinone-xiugaimima");
                demoViewAction.setAttributes(attributes);
                //UserViewAction第0个是修改密码ViewAction,使用自定义的ViewAction就可以实现替换
                result.getUserViewAction().set(0, demoViewAction);
            }
        } else {
            result = (TopBarUserBlock) ret;
        }
        return result;
    }
    }
  2. 使用@UxRouteButton方式新增ViewAction,更多新增Action方式详见:Action的类型

    @Model.model(Dog.MODEL_MODEL)
    @Component
    @UxRouteButton(
        action = @UxAction(name = "changePassword", displayName = "修改密码"),
        value = @UxRoute(model = Dog.MODEL_MODEL, openType = ActionTargetEnum.DIALOG))
    public class DogAction {
    }

替换原有的个人设置头像跳转

  1. 修改点击头像绑定的跳转逻辑

    @Order(10)
    @Component
    @SPI.Service
    public class DemoTopBarUserBlockDataApi implements TopBarUserBlockDataApi {
    
    @Override
    public TopBarUserBlock extendData(TopBarUserBlock data) {
        //例如增加一个菜单, PamirsDemo.MODEL_MODEL: 模型。 MenuuiMenu31f22466735a4abe8e0544b428ed88ac:viewAction的name。
        Action demoViewAction = PamirsSession.getContext().getExtendCache(ActionCacheApi.class).get(PamirsDemo.MODEL_MODEL, "MenuuiMenu31f22466735a4abe8e0544b428ed88ac");
        if (demoViewAction != null){
            AccessResourceInfo info = PageLoadHelper.generatorAccessResourceInfo(TopModule.MODULE_MODULE, demoViewAction);
            AccessResourceInfoSession.setInfo(info);
            String path = ResourcePath.generatorPath(demoViewAction.getModel(), demoViewAction.getName());
            demoViewAction.setSessionPath(path);
            data.setUserAvatarAction(demoViewAction);
        }
        return data;
    }
    }
  2. 添加权限节点,用于控制权限。

    @Component
    @Order(88)
    @SPI.Service
    public class MyTestNodeLoadExtend implements PermissionNodeLoadExtendApi {
    
    @Override
    public List<PermissionNode> buildRootPermissions(PermissionLoadContext loadContext, List<PermissionNode> nodes) {
        PermissionNode root = AuthNodeHelper.createNodeWithTranslate("CustomNode", "自定义节点");
        List<PermissionNode> newNodes = new ArrayList<>();
        newNodes.add(root);
        Action demoViewAction = PamirsSession.getContext().getExtendCache(ActionCacheApi.class).get(PamirsDemo.MODEL_MODEL, "MenuuiMenu31f22466735a4abe8e0544b428ed88ac");
    
        if (demoViewAction != null) {
            //将该Action放入权限树
            //权限鉴权的path路径是根据【cacheAction.getModel() + cacheAction.getName()】拼接的。和MODULE没有关系,这里MODULE可以自定义。
            AuthNodeHelper.addNode(newNodes, root, AuthNodeHelper.createActionNode(TopModule.MODULE_MODULE, demoViewAction, root));
        }
        nodes.add(0, root);
        return newNodes;
    }
    }
  3. 管理中心控制个人设置头像权限
    自定义用户中心菜单

Oinone社区 作者:yexiu原创文章,如若转载,请注明出处:https://doc.oinone.top/backend/14399.html

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

(0)
yexiu的头像yexiu数式员工
上一篇 2024年7月2日 pm7:42
下一篇 2024年7月4日 pm10:10

相关推荐

  • 如何扩展自有的文件存储系统

    介绍 数式Oinone默认提供了阿里云、腾讯云、华为云、又拍云、Minio和本地文件存储这几种文件存储系统,如果我们有其他的文件存储系统需要对接,或者是扩展现有的文件系统,可以通过SPI继承AbstractFileClient注册新的文件存储系统。 代码示例 这里以扩展自有的本地文件系统为例 继承了内置的本地文件存储LocalFileClient,将其中上传文件的方法重写 package pro.shushi.pamirs.demo.core.file; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; import pro.shushi.pamirs.framework.connectors.cdn.client.LocalFileClient; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.common.spi.SPI; import javax.servlet.http.HttpServletRequest; @Slf4j @Component // 注册新的文件存储系统类型 @SPI.Service(DemoLocalFileClient.TYPE) @RestController @RequestMapping("/demo_file") public class DemoLocalFileClient extends LocalFileClient { public static final String TYPE = "DEMO_LOCAL"; @Override public CdnFileForm getFormData(String fileName) { CdnConfig cdnConfig = getCdnConfig(); CdnFileForm fileForm = new CdnFileForm(); String uniqueFileName = Spider.getDefaultExtension(CdnFileNameApi.class).getNewFilename(fileName); String fileKey = getFileKey(cdnConfig.getMainDir(), uniqueFileName); //前端获取uploadUrl,上传文件到该地址 fileForm.setUploadUrl(cdnConfig.getUploadUrl() + "/demo_file/upload"); //上传后,前端将downloadUrl返回给后端 fileForm.setDownloadUrl(getDownloadUrl(fileKey)); fileForm.setFileName(uniqueFileName); Map<String, Object> formDataJson = new HashMap<>(); formDataJson.put("uniqueFileName", uniqueFileName); formDataJson.put("key", fileKey); fileForm.setFormDataJson(JSON.toJSONString(formDataJson)); return fileForm; } @ResponseBody @RequestMapping(value = "/upload", produces = "multipart/form-data;charset=UTF-8",method = RequestMethod.POST) public String uploadFileToLocal(HttpServletRequest request) { MultipartFile file = ((StandardMultipartHttpServletRequest) request).getFile("file"); // 例如可以根据file文件类型判断哪些文件是否可以上传 return super.uploadFileToLocal(request); } } 在application.yml内配置 cdn: oss: name: 本地文件系统 # 这里的type与代码中定义的文件存储系统类型对应 type: DEMO_LOCAL bucket: pamirs uploadUrl: http://127.0.0.1:8190 downloadUrl: http://127.0.0.1:6800 validTime: 3600000 timeout: 600000 active: true referer: localFolderUrl: /Users/demo/workspace/static

    2024年10月24日
    86100
  • Oinone连接外部数据源方案

    场景描述 在实际业务场景中,有是有这样的需求:链接外部数据进行数据的获取;通常的做法:1、【推荐】通过集成平台的数据连接器,链接外部数据源进行数据操作;2、项目代码中链接数据源,即通过程序代码操作外部数据源的数据; 本篇文章只介绍通过程序代码操作外部数据源的方式. 整体方案 Oinone管理外部数据源,即yml中配置外部数据源; 后端通过Mapper的方式进行数据操作(增/删/查/改); 调用Mapper接口的时候,指定到外部数据源; 详细步骤 1、数据源配置(application.yml), 与正常的数据源配置一样 out_ds_name(外部数据源别名): driverClassName: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # local环境配置调整 url: jdbc:mysql://ip(host):端口/数据库Schema?useSSL=false&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true username: 用户名 password: 命名 initialSize: 5 maxActive: 200 minIdle: 5 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true asyncInit: true 2、外部数据源其他配置外部数据源限制创建表结构的执行,可以通过配置指定【不创建DB,不创建数据表】 persistence: global: auto-create-database: true auto-create-table: true ds: out_ds_name(外部数据源别名): # 不创建DB auto-create-database: false # 不创建数据表 auto-create-table: false 3、后端写Mapper SQL Mapper跟使用原生mybaits/mybaits-plus写法一样,无特殊限制; Mapper和SQL写到一起,或者分开两个文件都可以 4、Mapper被Service或者Action调用1)启动的Application中@MapperScan需要扫描到对应的包。2)调用是与普通bean一样(即调用方式跟传统的方式样),唯一的区别就是加上DsHintApi,即指定Mapper所使用的数据源。 @Autowired private ScheduleItemMapper scheduleItemMapper; public saveData(Object data) { ScheduleQuery scheduleQuery = new ScheduleQuery(); //scheduleQuery.setActionName(); try (DsHintApi dsHint = DsHintApi.use(“外部数据源名称”)) { List<ScheduleItem> scheduleItems = scheduleItemMapper.selectListForSerial(scheduleQuery); // 具体业务逻辑 } } 其他参考:如何自定义sql语句:https://doc.oinone.top/backend/4759.html

    2024年5月17日
    1.9K00
  • Oinone引入搜索引擎(增强模型)

    场景描述 在碰到大数据量并且需要全文检索的场景,我们在分布式架构中基本会架设ElasticSearch来作为一个常规解决方案。在oinone体系中增强模型就是应对这类场景,其背后也是整合了ElasticSearch; 使用前你应该 了解ElasticSearch,包括不限于:Index(索引)、分词、Node(节点)、Document(文档)、Shards(分片) & Replicas(副本)。参考官方网站:https://www.elastic.co/cn/ 有一个可用的ElasticSearch环境(本地项目能引用到) 前置约束 增强模型增量依赖数据变更实时消息,因此确保项目的event是开启的,mq配置正确。 项目引入搜索步骤 1、boot工程加入相关依赖包 boot工程需要指定ES客户端包版本,不指定版本会隐性依赖顶层spring-boot依赖管理指定的低版本 boot工程加入pamris-channel的工程依赖 <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>8.4.1</version> </dependency> <dependency> <groupId>jakarta.json</groupId> <artifactId>jakarta.json-api</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-sql-record-core</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-channel-core</artifactId> </dependency> 2、api工程加入相关依赖包 在XXX-api中增加入pamirs-channel-api的依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-channel-api</artifactId> </dependency> 3、yml文件配置 在pamirs-demo-boot的application-dev.yml文件中增加配置pamirs.boot.modules增加channel,即在启动模块中增加channel模块。同时注意es的配置,是否跟es的服务一致 pamirs: record: sql: #改成自己本地路径(或服务器路径) store: /Users/oinone/record boot: modules: – channel ## 确保也安装了sql_record – sql_record channel: packages: # 增强模型扫描包配置 – com.xxx.xxx elastic: url: 127.0.0.1:9200 4、项目的模块增加模块依赖 XXXModule增加对ChannelModule的依赖 @Module(dependencies = {ChannelModule.MODULE_MODULE}) 5、增加增强模型(举例) package pro.shushi.pamirs.demo.api.enhance; import pro.shushi.pamirs.channel.enmu.IncrementEnum; import pro.shushi.pamirs.channel.meta.Enhance; import pro.shushi.pamirs.channel.meta.EnhanceModel; import pro.shushi.pamirs.demo.api.model.ShardingModel; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.enmu.ModelTypeEnum; @Model(displayName = "测试EnhanceModel") @Model.model(ShardingModelEnhance.MODEL_MODEL) @Model.Advanced(type = ModelTypeEnum.PROXY, inherited = {EnhanceModel.MODEL_MODEL}) @Enhance(shards = "3", replicas = "1", reAlias = true,increment= IncrementEnum.OPEN) public class ShardingModelEnhance extends ShardingModel { public static final String MODEL_MODEL="demo.ShardingModelEnhance"; } 6、重启系统看效果 1、进入【传输增强模型】应用,访问增强模型列表我们会发现一条记录,并点击【全量同步】初始化ES,并全量dump数据 2、再次回到Demo应用,进入增强模型页面,可以正常访问并进增删改查操作 个性化dump逻辑 通常dump逻辑是有个性化需求,那么我们可以重写模型的synchronize方法,函数重写特性在“面向对象-继承与多态”部分中已经有详细介绍。 重写ShardingModelEnhance模型的synchronize方法 重写后,如果针对老数据记录需要把新增的字段都自动填充,可以进入【传输增强模型】应用,访问增强模型列表,找到对应的记录并点击【全量同步】 package pro.shushi.pamirs.demo.api.enhance; import pro.shushi.pamirs.channel.enmu.IncrementEnum; import pro.shushi.pamirs.channel.meta.Enhance; import pro.shushi.pamirs.channel.meta.EnhanceModel; import pro.shushi.pamirs.demo.api.model.ShardingModel; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum; import pro.shushi.pamirs.meta.enmu.ModelTypeEnum; import java.util.List; @Model(displayName = "测试EnhanceModel") @Model.model(ShardingModelEnhance.MODEL_MODEL) @Model.Advanced(type = ModelTypeEnum.PROXY, inherited = {EnhanceModel.MODEL_MODEL}) @Enhance(shards = "3", replicas = "1", reAlias = true,increment= IncrementEnum.OPEN) public class ShardingModelEnhance extends ShardingModel { public static final String MODEL_MODEL="demo.ShardingModelEnhance"; @Field(displayName = "nick") private String nick;…

    2024年5月14日
    2.2K00
  • action 和 function 有什么区别

    在 Oinone(开源低代码 / 企业应用开发平台) 里,Action 和 Function 都是“可被调用的逻辑单元”,但它们的定位和使用场景不同。可以简单理解为: Function = 纯逻辑函数(偏后端能力) Action = 面向业务操作的动作(偏应用行为 / UI触发) 下面给你详细对比一下。 1️⃣ Function:函数(逻辑能力) Function 更像是一个 可复用的服务方法。 特点 通常是 纯逻辑处理 不直接绑定 UI 可以被 Action / Service / 其他 Function 调用 用来封装 业务计算或工具逻辑 常见用途 比如: 价格计算 数据校验 数据转换 调用第三方 API 复杂业务规则 示例 @Function(openLevel = FunctionOpenEnum.API) @Function.Advanced(type = FunctionTypeEnum.QUERY) public TradeOrder computePrice(TradeOrder data) { return data; } 用途: 订单金额计算逻辑 然后可能被多个地方调用: Action -> 调用 Function Service -> 调用 Function Workflow -> 调用 Function 📌 核心:可复用业务逻辑 2️⃣ Action:动作(业务操作) Action 是一个 业务动作,通常是 用户触发的行为。 特点 通常绑定 UI 可以在 按钮 / 菜单 / API / 工作流 中触发 通常操作 模型数据 可以调用 Function 常见用途 例如: 创建订单 提交审批 发布文章 批量删除 导入数据 示例 @Action public void submitOrder(Order order){ order.setStatus("SUBMITTED"); } UI 可能是: 订单详情页 [提交订单] 按钮 点击按钮 → 调用 Action。 📌 核心:业务行为入口 3️⃣ 核心区别总结 维度 Action Function 定位 业务动作 逻辑函数 是否绑定 UI 通常是 否 是否直接给用户操作 是 否 是否可复用 一般 很高 是否操作模型 常见 不一定 调用关系 可调用 Function 不调用 Action 4️⃣ 调用关系(典型架构) 通常推荐的结构: UI按钮 ↓ Action(业务入口) ↓ Function(业务逻辑) ↓ DAO / Repository 例如: 提交订单按钮 ↓ submitOrderAction ↓ checkInventoryFunction calcPriceFunction createOrderFunction 这样: Action 只负责 流程 Function 负责 逻辑 代码会更清晰。 5️⃣…

    2026年3月12日
    15700
  • 函数之触发与定时配置和示例

    异步任务总体介绍 函数的触发和定时在很多场景中会用到,也是一个oinone的基础能力。比如我们的流程产品中在定义流程触发时就会让用户选择模型触发还是时间触发,就是用到了函数的触发与定时能力。 触发任务TriggerTaskAction 触发任务的创建,使用sql-record模块监听mysql的binlog事件,通过rocketmq发送变更数据消息,收到MQ消息后,创建TriggerAutoTask。 触发任务的执行,使用TBSchedule拉取触发任务后,执行相应函数。 项目中引入依赖 1、项目的API工程引入依赖pamirs-core-trigger模块 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-trigger-api</artifactId> </dependency> 2、DemoModule在模块依赖定义中增加@Module(dependencies={TriggerModule.MODULE_MODULE}) @Component @Module( name = DemoModule.MODULE_NAME, displayName = "oinoneDemo工程", version = "1.0.0", dependencies = {ModuleConstants.MODULE_BASE, CommonModule.MODULE_MODULE, UserModule.MODULE_MODULE, TriggerModule.MODULE_MODULE} ) @Module.module(DemoModule.MODULE_MODULE) @Module.Advanced(selfBuilt = true, application = true) @UxHomepage(PetShopProxy.MODEL_MODEL) public class DemoModule implements PamirsModule { ……其他代码 } 3、项目的boot工程引入依赖 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-trigger-core</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-trigger-bridge-tbschedule</artifactId> </dependency> <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-sql-record-core</artifactId> </dependency> yml文件修改(applcation-xxx.yml) a. 修改pamris.event.enabled和pamris.event.schedule.enabled为trueb. pamirs_boot_modules增加启动模块:trigger、sql_record pamirs: record: sql: #改成自己路径 store: /opt/pamirs/logs … event: enabled: true schedule: enabled: true rocket-mq: namesrv-addr: 127.0.0.1:9876 boot: init: true sync: true modules: – base -…… – trigger – sql_record -…… 新建触发任务 新建PetTalentTrigger类,当PetTalent模型的数据记录被新建时触发系统做一些事情 package pro.shushi.pamirs.demo.core.trigger; import pro.shushi.pamirs.demo.api.model.PetTalent; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.trigger.annotation.Trigger; import pro.shushi.pamirs.trigger.enmu.TriggerConditionEnum; @Fun(PetTalent.MODEL_MODEL) @Slf4j public class PetTalentTrigger { @Function @Trigger(displayName = “PetTalent创建时触发”,name = “PetTalent#Trigger#onCreate”,condition = TriggerConditionEnum.ON_CREATE) public PetTalent onCreate(PetTalent data){ log.info(data.getName() + “,被创建”); //可以增加逻辑 return data; } } 定时任务 定时任务是一种非常常见的模式,这里就不介绍概念了,直接进入示例环节 新建PetTalentAutoTask实现ScheduleAction getInterfaceName()需要跟taskAction.setExecuteNamespace定义保持一致,都是函数的命名空间 taskAction.setExecuteFun("execute");跟执行函数名“execute”一致 TaskType需配置为CYCLE_SCHEDULE_NO_TRANSACTION_TASK,把定时任务的schedule线程分开,要不然有一个时间长的任务会导致普通异步或触发任务全部延时。 package pro.shushi.pamirs.demo.core.task; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import pro.shushi.pamirs.core.common.enmu.TimeUnitEnum; import pro.shushi.pamirs.demo.api.model.PetTalent; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.domain.fun.FunctionDefinition; import pro.shushi.pamirs.middleware.schedule.api.ScheduleAction; import pro.shushi.pamirs.middleware.schedule.common.Result; import pro.shushi.pamirs.middleware.schedule.domain.ScheduleItem; import pro.shushi.pamirs.middleware.schedule.eunmeration.TaskType; import pro.shushi.pamirs.trigger.enmu.TriggerTimeAnchorEnum; import pro.shushi.pamirs.trigger.model.ScheduleTaskAction; import pro.shushi.pamirs.trigger.service.ScheduleTaskActionService; @Slf4j @Component @Fun(PetTalent.MODEL_MODEL) public class PetTalentAutoTask implements…

    2024年5月25日
    1.5K00

Leave a Reply

登录后才能评论