action 和 function 有什么区别

Oinone(开源低代码 / 企业应用开发平台) 里,ActionFunction 都是“可被调用的逻辑单元”,但它们的定位和使用场景不同。可以简单理解为:

  • 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️⃣ 一句话理解

Action = 用户触发的业务操作
Function = 可复用的业务逻辑函数


💡 如果你在 Oinone 做模块开发,我可以再给你讲一个更实用的:

“什么时候写 Action,什么时候写 Function” 的最佳实践(很多人会写反)。

他们在权限上有什么区别

Oinone 里,Action 和 Function 在权限控制上的区别其实很明显:

Action 是有权限控制的业务入口,而 Function 通常没有独立权限控制。

可以从几个层面理解。


1️⃣ Action:有权限控制(面向用户)

Action 通常直接参与权限体系。

因为 Action 往往对应:

  • 页面按钮
  • 菜单操作
  • API接口
  • 工作流节点动作

所以平台会对 Action 做权限控制

常见控制方式

Action 可以被:

  • 角色权限控制
  • 菜单权限控制
  • 数据权限控制
  • 按钮可见性控制

例如:

角色:销售
   ✔ 提交订单 Action
   ✔ 查看订单
   ✘ 删除订单 Action

如果用户没有权限:

  • 按钮不会显示
  • 或调用 API 时被拒绝

📌 Action 是权限体系的主要控制点。


2️⃣ Function:通常没有独立权限

Function 只是逻辑方法,不直接暴露给用户。

它通常:

  • 只能被 Action / Service / Workflow 调用
  • 不直接作为 UI 操作入口

因此 Function 一般不做权限控制

例如:

submitOrderAction
    ↓
calcPriceFunction
checkInventoryFunction
createOrderFunction

权限只在:

submitOrderAction

检查一次即可。


3️⃣ 为什么设计成这样

这是典型的 业务入口权限模型

用户
 ↓
Action(权限控制)
 ↓
Function(内部逻辑)
 ↓
数据

好处:

1️⃣ 权限集中
2️⃣ 避免重复校验
3️⃣ Function 可复用

否则:

10个 Action
调用同一个 Function

每个 Function 再做权限判断会非常混乱。


4️⃣ 一个真实场景

删除订单

deleteOrderAction

权限:

角色:管理员 ✔
角色:销售 ✘

Action:

deleteOrderAction
   ↓
deleteOrderFunction

如果销售没有权限:

  • 按钮不显示
  • API 不允许调用

deleteOrderFunction 仍然存在,只是不会被调用。


5️⃣ 特殊情况(高级开发)

有些团队会在 Function 内再做安全校验,例如:

  • 防止被错误调用
  • 防止内部接口滥用

例如:

checkPermissionFunction()

但这属于 业务安全设计,不是 Oinone 的默认权限模型。


6️⃣ 一句话总结

对比 Action Function
是否有权限控制 ✅ 有 ❌ 通常没有
是否直接暴露给用户 ✅ 是 ❌ 否
是否是权限入口 ✅ 是 ❌ 不是
是否可被 UI 按钮触发 ✅ 可以 ❌ 不可以

核心理解:

权限控制在 Action 层,Function 是内部逻辑。


如果你在 Oinone 做企业系统开发,还有一个非常关键的点很多人会搞错:

Action 的权限 ≠ 数据权限(Data Permission)

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

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

(0)
nation的头像nation数式员工
上一篇 2026年2月10日 pm3:38
下一篇 2024年9月12日 am11:23

相关推荐

  • 表格字段配置Switch开关

    在业务开发中,我们经常会遇到表格中有个Switch开关组件: 那么如何通过界面设计器配置这个组件呢,下面让我们一起来学习下吧。 设计布尔类型的组件 1: 首先在界面设计器的组件区域添加一个组件。 2: 我们给这个组件添加一个元件,元件的配置必须跟下面的一致 3: 给元件添加属性配置 拖拽一个单行文本字段, 字段编码必须是truthyAction,代表的是该字段为true的时候要执行的动作 再拖拽一个单行文本字段, 字段编码必须是falsyAction,代表的是该字段为false的时候要执行的动作 4: 发布元件,然后再去对应的界面设计器页面,将对应的字段切换成刚刚设计的组件 5: 发布界面设计器,染红我们可以在运行时的页面看到效果 当switch切换的时候,会执行对应的action

    2023年11月21日
    1.8K00
  • 表单页如何在服务端动作点击后让整个表单都处于loading状态

    介绍 在业务场景中,有时候由于提交的数据很多,导致服务端动作耗时较长,为了保证这个过程中表单内的字段不再能被编辑,我们可以通过自定义能力将整个表单区域处于loading状态 自定义动作组件代码 import { ActionType, ActionWidget, BaseElementViewWidget, BaseView, ClickResult, ServerActionWidget, SPI, Widget } from '@kunlun/dependencies'; @SPI.ClassFactory(ActionWidget.Token({ actionType: ActionType.Server })) class LoadingServerActionWidget extends ServerActionWidget { protected async clickAction(): Promise<ClickResult> { const baseView = Widget.select(this.rootHandle) as unknown as BaseView; if (!baseView) { return super.clickAction(); } const baseViewWidget = baseView.getChildrenInstance().find((a) => a instanceof BaseElementViewWidget) as unknown as BaseElementViewWidget; if (!baseViewWidget) { return super.clickAction(); } return new Promise((resolve, reject) => { try { baseViewWidget.load(async () => { const res = await super.clickAction(); resolve(res); }); } catch (e) { reject(false); } }); } } 本案例知识点 BaseElementWidget提供了load方法将继承了该class的元素渲染的区域做整体loading交互,等入参的函数处理完成后恢复正常状态,其实所有继承了ActionWidget的组件也提供了这个能力让按钮在执行函数中的时候处于loading状态, 每个组件都有一个全局唯一的handle值,所在根视图的rootHandle,组件可以用this.rootHandle通过Widget.Select方法查找到所在的根视图组件,从视图的实例化子元素里可以查找到具体的业务类型视图组件,如详情页的DetailWidget、表单页的FormWidget、表格页的TableWidget,拿到这些实例后就可以操作里面的属性和方法了

    2024年5月29日
    1.3K00
  • 界面设计器 扩展字段的查询上下文

    默认情况下oinone平台对于查询条件,只提供的当前登录用户这一个配置,但是允许开发者扩展 开发者可以在前端代码的main.ts进行扩展 import { SessionContextOptions, ModelFieldType } from '@kunlun/dependencies'; const currentDeptOption = { ttype: ModelFieldType.String, value: '$#{currentDept}', displayName: '当前登录部门', label: '当前登录部门' }; SessionContextOptions.push(currentDeptOption as any); 加上上面的代码,然后再去界面设计器,我们就会发现,多了一个配置

    2023年11月8日
    1.7K00
  • GraphQL请求详解(v4)

    阅读之前 什么是GraphQL? Oinone官方解读GraphQL入门 可视化请求工具 insomnia下载 概述 (以下内容简称GQL) 众所周知,平台的所有功能都是通过一系列元数据定义来驱动的,而GQL作为服务端和客户端交互协议,其重要性不言而喻。下面会从以下几个方面介绍GQL在平台中是如何运作的: 服务端定义元数据生成GQL对应的schema 通过HttpClient发起一个GQL请求 通过window.open使用GET方式发起一个GQL请求 客户端泛化调用任意API服务 客户端通过运行时上下文RuntimeContext发起GQL请求 准备工作 在开始介绍GQL之前,我们需要定义一个可以被GQL请求的服务端函数,以此来介绍我们的相关内容。(对服务端不感兴趣的同学可以跳过代码部分) DemoModel.java @Model.model(DemoModel.MODEL_MODEL) @Model(displayName = "演示模型", labelFields = {"name"}) public class DemoModel extends IdModel { private static final long serialVersionUID = -7211802945795866154L; public static final String MODEL_MODEL = "demo.DemoModel"; @Field(displayName = "名称") private String name; @Field(displayName = "是否启用") private Boolean isEnabled; } DemoModelAction.java @Model.model(DemoModel.MODEL_MODEL) public class DemoModelAction { @Action(displayName = "启用") public DemoModel enable(DemoModel data) { data.setIsEnabled(true); data.updateById(); return data; } @Action(displayName = "禁用") public DemoModel disable(DemoModel data) { data.setIsEnabled(false); data.updateById(); return data; } } 上面的java代码定义了演示模型的字段和动作: 字段 field id:ID 整数 Integer name:名称 字符串 String isEnabled:是否启用 布尔 Boolean 动作 action enable:启用 提交动作 ServerAction disable:禁用 提交动作 ServerAction 服务端定义元数据生成GQL对应的schema 模型和字段 type DemoModel { id: Long name: String isEnabled: Boolean } 动作 type DemoModelInput { id: Long name: String isEnabled: Boolean } type DemoModelQuery { queryOne(query: DemoModelInput): DemoModel …… } type DemoModelMutation { enable(data: DemoModelInput): DemoModel disable(data: DemoModelInput): DemoModel …… } PS:平台内置了多种Query和Mutation定义,通过模型继承关系将自动生成,无需手动定义。比如Query定义包括queryOne、queryPage等;Mutation定义包括create、update等。特殊情况下,默认逻辑无法满足时,服务端通常采用函数重载的方式进行替换,客户端则无需关心。 生成规则 type DemoModel:通过模型编码demo.DemoModel取.分隔后的最后一位,并转换为大驼峰格式。字段与声明类型一致。 type DemoModelInput:动作入参定义,未做特殊声明的情况下与模型定义一致。 type DemoModelQuery和type DemoModelMutation:Query和Mutation为固定后缀,分别生成动作相关类型。当函数类型为QUERY时,使用Query后缀,其他情况使用Mutation后缀。 (此处仅做简单解释,详细生成规则过于复杂,客户端无需关心) Query类型的GQL示例 query { demoModelQuery { queryOne(query: { id: ${id} }) { id name isEnabled } } } Mutation类型的GQL示例 mutation…

    2023年11月1日
    2.3K00
  • 国际化-语言和时区设置

    国际化-翻译 1、引入翻译的包 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-translate</artifactId> </dependency> 2、默认逻辑:在系统的右上角,切换【系统语言后】,用户所选择的语言会保存到对应的用户信息中,后续所有的请求都会拿这个「语言」的值,并将其放入到PamirsSession#Lang中。3、实际项目可以通过自定义Session逻辑,根据实际业务覆盖掉默认方式,并将其设置在PamirsSession中: PamirsSession.setLang(langCode); 构建自定义Session参考:https://shushi.yuque.com/yoxz76/oio3/kg2sgr 构建自定义Session的逻辑中,根据业务逻辑把获取到的langCode设置都PamirsSession 4、目标语言编码说明语言编码必须符合ISO标准,即语言ISO代码。国际化-语言代码表-Language Codes参考下面的链接:https://blog.csdn.net/qq827245563/article/details/131552695 国际化-时区 1、引入时区的包 <dependency> <groupId>pro.shushi.pamirs.core</groupId> <artifactId>pamirs-timezone</artifactId> </dependency> 2、时区设置类似语言(langCode)3、在自定义Session(与设置语言共同的Session自定义)中,根据实际业务覆盖掉默认方式,并将其设置在TimezoneSession中: pro.shushi.pamirs.timezone.session.TimezoneSession#setTimezone(TimeZone timezone) 其他说明 PamirsSession 和 TimezoneSession 都是请求级别的;即每次请求都会自动销毁; 因此在自定义Session中覆盖这两个属性的默认值的时候特别注意一下性能。

    2023年12月4日
    1.2K00

Leave a Reply

登录后才能评论