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)

区别到这就介绍完了,使用规范参考:Function、Action函数使用规范

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

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

(0)
nation的头像nation数式员工
上一篇 2026年2月10日 pm3:38
下一篇 2天前

相关推荐

  • 【前端】登录页面扩展点

    登录页面扩展点 场景 1: 登录之前需要二次确认框2: 前端默认的错误提示允许被修改3: 后端返回的错误提示允许被修改4: 登录后跳转到自定义的页面 方案 前端默认错误可枚举 errorMessages: { loginEmpty: '用户名不能为空', passwordEmpty: '密码不能为空', picCodeEmpty: '图形验证码不能为空', phoneEmpty: '手机号不能为空', verificationCodeEmpty: '验证码不能为空', picCodeError: '图形验证码错误', inputVerificationCodeAlign: '请重新输入验证码' } 登录按钮添加拓展点beforeClick、afterClick 代码 新增一个ts文件,继承平台默认的LoginPageWidget @SPI.ClassFactory(RouterWidget.Token({ widget: 'Login' })) export class CustomLoginPageWidget extends LoginPageWidget { constructor() { super(); // 修改前端默认的错误文案 this.errorMessages.loginEmpty = '登录用户名不能为空'; } /** * 用来处理点击「登录」之前的事件,可以做二次确定或者其他的逻辑 * 只有return true,才会继续往下执行 */ public beforeClick(): Promise<Boolean | null | undefined> { return new Promise((resolve) => { Modal.confirm({ title: '提示', content: '是否登录?', onOk: () => { resolve(true); } }); }); } /** * * @param result 后端接口返回的数据 * * 用来处理「登录」接口调用后的逻辑,可以修改后端返回的错误文案,也可以自定义 * * 只有return true,才会执行默认的跳转事件 */ public afterClick(result): Promise<any | null | undefined> { // if(result.redirect) { // 自定义跳转 //return false //} if (result.errorCode === 20060023) { result.errorMsg = '手机号不对,请联系管理员'; } return result; } }

    2023年11月1日
    1.2K00
  • 如何自定义SQL(Mapper)语句

    场景描述 在实际业务场景中,存在复杂SQL的情况,具体表现为: 单表单SQL满足不了的情况下 有复杂的Join关系或者子查询 复杂SQL的逻辑通过程序逻辑难以实现或实现代价较大 在此情况下,通过原生的mybatis/mybatis-plus, 自定义Mapper的方式实现业务功能 1、编写所需的Mapper SQL Mapper写法无限制,与使用原生的mybaits/mybaits-plus用法一样; Mapper(DAO)和SQL可以写在一个文件中,也分开写在两个文件中。 package pro.shushi.pamirs.demo.core.map; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; import java.util.Map; @Mapper public interface DemoItemMapper { @Select("<script>select sum(item_price) as itemPrice,sum(inventory_quantity) as inventoryQuantity,categoryId from ${demoItemTable} as core_demo_item ${where} group by category_id</script>") List<Map<String, Object>> groupByCategoryId(@Param("demoItemTable") String pamirsUserTable, @Param("where") String where); } 2.调用mapper 调用Mapper代码示例 package pro.shushi.pamirs.demo.core.map; import com.google.api.client.util.Lists; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.model.DemoItem; import pro.shushi.pamirs.framework.connectors.data.api.datasource.DsHintApi; import pro.shushi.pamirs.meta.api.core.orm.convert.DataConverter; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.common.spring.BeanDefinitionUtils; import java.util.List; import java.util.Map; @Component public class DemoItemDAO { public List<DemoItem> customSqlDemoItem(){ try (DsHintApi dsHint = DsHintApi.model(DemoItem.MODEL_MODEL)) { String demoItemTable = PamirsSession.getContext().getModelCache().get(DemoItem.MODEL_MODEL).getTable(); DemoItemMapper demoItemMapper = BeanDefinitionUtils.getBean(DemoItemMapper.class); String where = " where status = 'ACTIVE'"; List<Map<String, Object>> dataList = demoItemMapper.groupByCategoryId(demoItemTable,where); DataConverter persistenceDataConverter = BeanDefinitionUtils.getBean(DataConverter.class); return persistenceDataConverter.out(DemoItem.MODEL_MODEL, dataList); } return Lists.newArrayList(); } } 调用Mapper一些说明 启动类需要配置扫描包MapperScan @MapperScan(value = "pro.shushi", annotationClass = Mapper.class) @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, FreeMarkerAutoConfiguration.class}) public class DemoApplication { 调用Mapper接口的时候,需要指定数据源;即上述示例代码中的 DsHintApi dsHint = DsHintApi.model(DemoItem.MODEL_MODEL), 实际代码中使用 try-with-resources语法。 从Mapper返回的结果中获取数据 如果SQL Mapper中已定义了resultMap,调用Mapper(DAO)返回的就是Java对象 如果Mapper返回的是Map<String, Object>,则通过 DataConverter.out进行转化,参考上面的示例 其他参考:Oinone连接外部数据源方案:https://doc.oinone.top/backend/4562.html

    2023年11月27日
    1.6K00
  • 表格字段配置Switch开关

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

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

    国际化-翻译 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
  • OSS(CDN)配置和文件系统的一些操作

    目前Oinone支持的OSS类型 类型 服务 OSS 阿里云OSS UPYUN 又拍云 MINIO MinIO HUAWEI_OBS 华为云OBS LOCAL 本地NGINX文件存储 TENCENT_COS 腾讯云COS CTYUN_ZOS 天翼云ZOS OSS通用yaml配置 cdn: oss: name: # 名称 type: # 类型 bucket: uploadUrl: # 上传URL downloadUrl: # 下载URL accessKeyId: accessKeySecret: mainDir: # 主目录 validTime: 3600000 timeout: 600000 active: true referer: localFolderUrl: others: [key]: name: # 名称 type: # 类型 bucket: uploadUrl: # 上传URL downloadUrl: # 下载URL accessKeyId: accessKeySecret: mainDir: # 主目录 validTime: 3600000 timeout: 600000 active: true referer: localFolderUrl: PS:others中使用自定义key来指定OSS服务进行文件上传/下载功能。上传/下载必须匹配,否则无法正常使用。 OSS配置示例 阿里云OSS cdn: oss: name: 阿里云 type: OSS bucket: pamirs(根据实际情况修改) uploadUrl: oss-cn-hangzhou.aliyuncs.com downloadUrl: oss-cn-hangzhou.aliyuncs.com accessKeyId: 你的accessKeyId accessKeySecret: 你的accessKeySecret # 根据实际情况修改 mainDir: upload/ validTime: 3600000 timeout: 600000 active: true imageResizeParameter: referer: 华为云OBS cdn: oss: name: 华为云 type: HUAWEI_OBS bucket: pamirs(根据实际情况修改) uploadUrl: obs.cn-east-2.myhuaweicloud.com downloadUrl: obs.cn-east-2.myhuaweicloud.com accessKeyId: 你的accessKeyId accessKeySecret: 你的accessKeySecret # 根据实际情况修改 mainDir: upload/ validTime: 3600000 timeout: 600000 active: true allowedOrigin: http://192.168.95.31:8888,https://xxxx.xxxxx.com referer: 华为云OBS需要在启动工程增加以下依赖 <okhttp3.version>4.9.3</okhttp3.version> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>${okhttp3.version}</version> </dependency> 注意事项华为云OBS的防盗链配置,仅允许携带特定referer的才可以,而excel导入后端处理的逻辑匿名读的时候是不带referer的,所以会被拒绝 MINIO 文件系统,mino的配置: cdn: oss: name: minio type: MINIO bucket: pamirs(根据实际情况修改) uploadUrl: http://192.168.243.6:32190(根据实际情况修改) downloadUrl: http://192.168.243.6:9000(根据实际情况修改) accessKeyId: 你的accessKeyId accessKeySecret: 你的accessKeySecret # 根据实际情况修改 mainDir: upload/ validTime: 3600000 timeout: 600000 active: true referer: localFolderUrl: MINIO无公网访问地址下OSS的配置方式: https://doc.oinone.top/yun-wei-shi-jian/7112.html 又拍云 cdn: oss: name: 又拍云…

    后端 2023年11月1日
    1.8K00

Leave a Reply

登录后才能评论