当前位置: 首页 > article >正文

SpringBoot整合Flowable【08】- 前后端如何交互

引子

在第02篇中,我通过 Flowable-UI 绘制了一个简单的绩效流程,并在后续章节中基于这个流程演示了 Flowable 的各种API调用。然而,在实际业务场景中,如果要求前端将用户绘制的流程文件发送给后端再进行解析处理,这种方式显得繁琐且不够优雅。那么,有没有更简便的方法,让前端通过常规的参数传递方式就能实现流程创建呢?

答案是肯定的。Flowable 提供了强大的 BpmnModel API,它允许我们以编程方式动态构建流程定义,无需依赖XML文件。

什么是 BpmnModel ?

BpmnModelFlowable 提供的一个核心类,它允许开发者以编程方式构建完整的 BPMN 2.0 流程模型。与通过 Flowable-UI 设计流程并导出XML文件的方式不同,BpmnModel 让我们可以直接在代码中定义流程的各个元素。

所以,我们可以通过 BpmnModel 对象来动态创建流程定义,前端只需通过接口传递必要的流程参数即可,无需传递完整的 XML 文件。这里需要明确区分两个概念:一是后端如何接收参数并构建流程模型,二是前端如何提供流程设计的交互界面。对于后者,可以基于 bpmn.js 开发自定义设计器,也可以采用现有的开源方案,这些选项在第01篇中已有详细介绍。

代码编写

接下来,我将完全抛弃 Flowable UI 可视化建模方式,转而通过纯代码方式使用 BpmnModel 对象构建先前设计的绩效流程。让我们深入编码环节,一步步实现这一转换。

一、构建传参对象

1.流程定义实体

流程定义是创建流程的传参对象,包含流程的基本信息和节点结构。

import lombok.Data;
import java.io.Serializable;/*** 流程定义.*/
@Data
public class ProcessDef implements Serializable {/*** 流程定义id.*/private String processDefKey;/*** 流程定义名称.*/private String processName;/*** 流程定义描述.*/private String description;/*** 创建人id.*/private Long creatorId;/*** 流程定义节点.*/private ProcessNode processNode;}

2. 流程节点实体

流程节点定义了流程中的各个环节,包括节点类型、表单属性以及子节点关系等。

import lombok.Data;
import java.io.Serializable;
import java.util.List;/*** 流程节点.*/
@Data
public class ProcessNode implements Serializable {/*** 节点名称.*/private String name;/*** 节点类型.*/private NodeCategory category;/*** 是否启用 0否 1是.*/private Integer enable;/*** 办理人属性.*/private AssigneeProps assigneeProps;/*** 表单列表.*/private List<FormProp> formProps;/*** 子节点.*/private ProcessNode children;}

3. 相关枚举和属性类

为了更好地定义流程节点的各种属性,我们最好还是定义相关枚举和辅助类。(Ps:在实际开发中,个人建议如果类型比较多的情况下,尽量使用枚举,避免魔法字段。)

节点类型枚举
/*** 节点类型.*/
public enum NodeCategory {/*** 自评.*/SELF_EVALUATION,/*** 上级评.*/LEADER_EVALUATION,/*** 隔级评.*/AUDIT,/*** 绩效确认.*/CONFIRMATION;
}
办理人属性
import java.io.Serializable;
import lombok.Data;/*** 办理人Props.*/
@Data
public class AssigneeProps implements Serializable {/*** 办理人类型.*/private AssigneeType assigneeType;/*** 候选办理人类型.*/private AssigneeType candidateType;
}
办理人类型枚举
/*** 办理人类型.*/
public enum AssigneeType {/*** 节点处理人类型, 用户本人,直接上级,上级部门管理员,隔级上级,隔级部门管理员.*/USER_ASSESSED, LEADER, DEPT_MANAGER, SUPERIOR_LEADER, SUPERIOR_DEPT_MANAGER
}
表单属性
import java.io.Serializable;
import lombok.Data;/*** FormProps 表单属性.*/
@Data
public class FormProp implements Serializable {/*** id.*/private String id;/*** 属性名称.*/private String name;/*** 属性变量.*/private String variable;/*** 变量类型.*/private String type;/*** 值.*/private String value;/*** 表单配置.*/private FormConfig config;public FormConfig getConfig() {return config;}
}
表单配置
import java.io.Serializable;
import lombok.Data;/*** FormConfig 表单配置.*/
@Data
public class FormConfig implements Serializable {/*** 分组名称.*/private String group;/*** 分组权重.*/private Double weight;/*** 分类:评分SCORE、评语COMMENT.*/private FormCategory category;}
表单类别枚举
/*** 表单类别.*/
public enum FormCategory {/*** 评分.*/SCORE,/*** 评语.*/COMMENT;
}

二、创建控制器

import com.pitayafruit.base.BaseResponse;
import com.pitayafruit.rest.vo.ProcessDef;
import com.pitayafruit.service.ProcessDefService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 流程Controller.*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/process-def/v1")
public class ProcessDefControllerV1 {private final ProcessDefService processDefService;/*** 创建流程模型并部署.** @param processDef 流程定义* @return 流程key*/@PostMappingpublic ResponseEntity<Object> createProcessDef(@RequestBody ProcessDef processDef) {//1,创建并部署流程模型String processDefKey = processDefService.createProcessDefApi(processDef);//2.返回流程模型keyreturn new ResponseEntity<>(new BaseResponse<>(processDefKey), HttpStatus.OK);}}

三、实现服务层主方法

这个负责将前端传递的数据结构转换为 Flowable 的 BpmnModel 模型,并进行部署。

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.pitayafruit.rest.vo.AssigneeProps;
import com.pitayafruit.rest.vo.ProcessDef;
import com.pitayafruit.rest.vo.ProcessNode;
import com.pitayafruit.service.ProcessDefService;
import com.pitayafruit.utils.UUIDUtil;
import lombok.RequiredArgsConstructor;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.validation.ValidationError;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;/*** 流程定义ServiceImpl.*/
@Service
@RequiredArgsConstructor
public class ProcessDefServiceImpl implements ProcessDefService {private final RepositoryService repositoryService;/*** 创建流程模型并部署.** @param processDef 流程定义* @return 流程key*/@Overridepublic String createProcessDefApi(ProcessDef processDef) {//1.设置流程定义KeyprocessDef.setProcessDefKey("processDef" + UUIDUtil.getUUID());//2.设置流程定义创建人idprocessDef.setCreatorId(1L);//3.设置流程定义名称processDef.setProcessName("绩效流程");//4.创建流程模型BpmnModel bpmnModel = toBpmn(processDef);//5.部署流程模型repositoryService.createDeployment()//5-1.流程定义key.key(processDef.getProcessDefKey())//5-2.流程定义名称.name(processDef.getProcessName())//5-3.添加流程模型.addBpmnModel(processDef.getProcessDefKey() + ".bpmn", bpmnModel)//5-4.部署.deploy();//6.返回流程定义keyreturn processDef.getProcessDefKey();}

四、流程模型转换方法

类型转换涉及到的逻辑比较多,所以把这个方法单独抽取出来。首先创建一个流程对象,设置流程的基本属性,添加开始事件,然后通过调用 buildTask 方法递归构建流程中的各个节点,最后生成并验证 BpmnModel。

/*** 将流程定义转换为BpmnModel.** @param processDef 流程定义* @return Bpmn模型*/
private BpmnModel toBpmn(ProcessDef processDef) {//1.创建流程//1-1.声明流程对象Process process = new Process();//1-2.设置流程idprocess.setId(processDef.getProcessDefKey());//1-3.设置流程名称process.setName(processDef.getProcessName());//2.创建开始事件//2-1.声明开始事件对象StartEvent startEvent = new StartEvent();//2-2.设置开始事件idstartEvent.setId("startEvent" + UUIDUtil.getUUID());//2-3.设置开始事件名称startEvent.setName("开始");//2-4.将开始事件添加到流程中process.addFlowElement(startEvent);//创建用户节点ProcessNode processNode = processDef.getProcessNode();buildTask(startEvent, processNode, process);//创建流程模型BpmnModel bpmnModel = new BpmnModel();bpmnModel.addProcess(process);// 验证BPMN模型List<ValidationError> validationErrors = repositoryService.validateProcess(bpmnModel);if (ObjectUtil.isNotEmpty(validationErrors)) {//打印失败日志validationErrors.forEach(validationError -> System.out.println(validationError.toString()));throw new IllegalArgumentException("验证失败");}return bpmnModel;
}

五、任务节点构建方法

这个方法负责创建用户任务节点。根据传入的流程节点数据,创建一个用户任务对象,设置其属性,添加自定义属性和任务监听器,然后将任务添加到流程中,并构建与前一个节点的连线。如果当前节点未启用,则直接处理下一个节点。

/*** 创建用户任务节点.** @param parentTask  父节点* @param processNode 流程节点* @param process     流程定义*/
private void buildTask(FlowNode parentTask, ProcessNode processNode, Process process) {//如果节点启用,处理当前节点if (ObjectUtil.isNotNull(processNode.getEnable()) && processNode.getEnable() == 1) {UserTask userTask = new UserTask();userTask.setId("userTask" + UUIDUtil.getUUID());userTask.setName(processNode.getName());//设置节点类型userTask.setCategory(processNode.getCategory().toString());List<CustomProperty> customProperties = new ArrayList<>();//办理人属性AssigneeProps assigneeProps = processNode.getAssigneeProps();//设置办理人类型customProperties.add(buildCustomProperty("assigneeType", assigneeProps.getAssigneeType().toString()));//设置候选办理人类型if (ObjectUtil.isNotNull(assigneeProps.getCandidateType())) {customProperties.add(buildCustomProperty("candidateType", assigneeProps.getCandidateType().toString()));}//绑定表单userTask.setFormProperties(buildFormProperty(processNode));//表单列表添加到节点扩展属性customProperties.add(buildCustomProperty("formProps", JSON.toJSONString(processNode.getFormProps())));//设置自定义属性,包括办理人类型和表单分组和权重配置userTask.setCustomProperties(customProperties);//监听器列表List<FlowableListener> taskListeners = new ArrayList<>();//任务创建时添加监听器,用于动态指定任务的办理人和设置变量taskListeners.add(buildTaskListener(TaskListener.EVENTNAME_CREATE, "taskCreateListener"));//任务完成后添加监听器,用于计算分数和保存表单//如果是手动填写流程,添加手动填写的监听器taskListeners.add(buildTaskListener(TaskListener.EVENTNAME_COMPLETE, "taskCompleteListener"));//设置任务监听器userTask.setTaskListeners(taskListeners);// 将用户任务节点添加到流程定义process.addFlowElement(userTask);//添加流程连线process.addFlowElement(new SequenceFlow(parentTask.getId(), userTask.getId()));//解析下一个节点,参数为当前任务,当前流程节点,流程定义,流程类型buildChildren(userTask, processNode, process);} else {//当前节点关闭的情况下,直接解析下一个节点,参数为父任务,当前流程节点,流程定义,流程类型buildChildren(parentTask, processNode, process);}
}

六、子节点处理方法

这个方法用于处理流程节点的子节点。它检查当前节点是否有子节点,如果有则递归调用 buildTask 方法构建子节点,如果没有则创建结束事件,表示流程的结束。

/*** 解析子节点.** @param parentTask  下一个节点的父任务* @param processNode 流程节点* @param process     流程定义*/
private void buildChildren(FlowNode parentTask, ProcessNode processNode, Process process) {ProcessNode childrenNode = processNode.getChildren();if (ObjectUtil.isNotNull(childrenNode)) {//创建子节点buildTask(parentTask, childrenNode, process);} else {//创建结束事件EndEvent endEvent = new EndEvent();endEvent.setId("endEvent" + UUIDUtil.getUUID());endEvent.setName("结束");process.addFlowElement(endEvent);//添加流程连线process.addFlowElement(new SequenceFlow(parentTask.getId(), endEvent.getId()));}
}

七、任务监听器创建方法

这个方法用于创建任务监听器。在Flowable中,任务监听器可以在任务的不同生命周期阶段触发特定逻辑,例如在任务创建时动态分配任务办理人,或在任务完成时处理表单数据。这里使用了委托表达式方式,将监听器的实现委托给Spring容器中的Bean。

/*** 创建任务监听器,用于动态分配任务的办理人.** @param event    事件* @param beanName 类名* @return 任务监听器*/
private FlowableListener buildTaskListener(String event, String beanName) {FlowableListener flowableListener = new FlowableListener();flowableListener.setEvent(event);flowableListener.setImplementationType("delegateExpression");flowableListener.setImplementation("${" + beanName + "}");return flowableListener;
}

八、表单属性创建方法

这个方法用于创建表单属性。它遍历流程节点中的表单属性列表,为每个表单属性创建一个Flowable的FormProperty对象,设置其ID、名称、变量名等属性,并将原始表单属性的ID和变量名更新为实际使用的值,以便在后续处理中使用。

/*** 创建表单属性.** @param processNode 流程节点*/
private List<FormProperty> buildFormProperty(ProcessNode processNode) {List<FormProperty> formProperties = new ArrayList<>();if (ObjectUtil.isNull(processNode.getFormProps())) {return formProperties;}processNode.getFormProps().forEach(prop -> {//新建表单属性对象FormProperty formProperty = new FormProperty();//设置表单属性idString id = "formProperty" + UUIDUtil.getUUID();formProperty.setId(id);//设置表单名称formProperty.setName(prop.getName());//设置表单变量名为表单idformProperty.setVariable(id);//设置表单变量类型formProperty.setType(prop.getType());//设置表单是否必填formProperty.setRequired(true);formProperties.add(formProperty);//设置表单属性idprop.setId(id);prop.setVariable(id);});return formProperties;
}

九、自定义属性创建方法

这个方法用于创建自定义属性。在 Flowable 中,自定义属性可以用于存储不属于标准BPMN规范但业务上需要的信息,通常用来传递我们的业务数据。

/*** 创建自定义属性.** @param key  键* @param value 值* @return 自定义属性*/
private CustomProperty buildCustomProperty(String key, String value) {CustomProperty customProperty = new CustomProperty();//自定义属性名称customProperty.setName(key);//自定义属性值customProperty.setSimpleValue(value);return customProperty;
}

接口测试

我把请求转换成了curl命令,方便直接在命令行中测试。创建的这个流程与前面篇章中介绍的结构一致,包含三个核心节点:员工自评、上级评价和隔级评价,每个节点都包含分数、评语等表单项,并且每个节点都设置了不同的权重。

为什么要加权重?这是因为在实际业务场景中,不同评价环节的重要性往往不同。

curl -X POST 'http://localhost:8080/api/process-def/v1' \
-H 'Content-Type: application/json' \
-d '{"processName": "通过bpmn model创建的绩效流程","description": "用于员工季度绩效评估","processNode": {"name": "员工自评","category": "SELF_EVALUATION","enable": 1,"assigneeProps": {"assigneeType": "USER_ASSESSED"},"formProps": [{"name": "自评分数","type": "string","value": "0","config": {"group": "绩效评分","weight": 0.3,"category": "SCORE"}},{"name": "自我评价","type": "string","config": {"group": "评语","weight": 0.3,"category": "COMMENT"}}],"children": {"name": "上级评价","category": "LEADER_EVALUATION","enable": 1,"assigneeProps": {"assigneeType": "LEADER"},"formProps": [{"name": "上级评分","type": "string","value": "0","config": {"group": "绩效评分","weight": 0.5,"category": "SCORE"}},{"name": "上级评语","type": "string","config": {"group": "评语","weight": 0.5,"category": "COMMENT"}}],"children": {"name": "隔级评价","category": "AUDIT","enable": 1,"assigneeProps": {"assigneeType": "SUPERIOR_LEADER"},"formProps": [{"name": "隔级评分","type": "string","value": "0","config": {"group": "绩效评分","weight": 0.2,"category": "SCORE"}},{"name": "隔级评语","type": "string","config": {"group": "评语","weight": 0.2,"category": "COMMENT"}}],"children": null}}}
}'

按照接口的定义,部署成功后会返回流程模型key。
在这里插入图片描述

同时查看部署表和流程运行表,可以看到这个流程已经启动了。

在这里插入图片描述
在这里插入图片描述

小结

通过本章的实践,我们可以明显看到利用 BpmnModel API 构建流程虽然灵活强大,但即使是构建一个相对简单的线性流程,也需要编写大量代码来处理各种细节。这种方法的复杂性在面对更复杂的流程结构(如并行网关、排他网关或子流程等)时会进一步增加。

因此,在实际项目中,我建议大家可以花点时间封装,比如可以将创建开始事件、任务节点、结束事件、网关等常见元素的代码封装成独立方法。通过这些封装,在写业务的时候就能像搭积木一样轻松组合各种流程元素,提高开发效率和代码可维护性。

相关文章:

SpringBoot整合Flowable【08】- 前后端如何交互

引子 在第02篇中&#xff0c;我通过 Flowable-UI 绘制了一个简单的绩效流程&#xff0c;并在后续章节中基于这个流程演示了 Flowable 的各种API调用。然而&#xff0c;在实际业务场景中&#xff0c;如果要求前端将用户绘制的流程文件发送给后端再进行解析处理&#xff0c;这种…...

DM达梦数据库开启SQL日志记录功能

DM达梦数据库开启SQL日志记录功能 配置SQL日志&#xff08;非必须的配置步骤&#xff0c;与主备集群配置无关&#xff0c;如果没有需求可以跳过配置SQL日志&#xff09; sqllog.ini 配置文件用于SQL日志的配置&#xff0c;当且仅当 INI&#xff08;dm.ini&#xff09; 参数 SV…...

00 QEMU源码分析中文注释与架构讲解(v8.2.4版本)

QEMU-v8.2.4源码中文注释与架构讲解 文档会不定期更新 注释作者将狼才鲸创建日期2025-05-30更新日期2025-06-02 CSDN阅读地址&#xff1a;QEMU源码中文注释与架构讲解Gitee源码仓库地址&#xff1a;才鲸嵌入式/qemu 一、前言 其它参考教程的网址&#xff1a; QEMU 源码目录…...

【五模型时间序列预测对比】Transformer-LSTM、Transformer、CNN-LSTM、LSTM、CNN

【五模型时间序列预测对比】Transformer-LSTM、Transformer、CNN-LSTM、LSTM、CNN 目录 【五模型时间序列预测对比】Transformer-LSTM、Transformer、CNN-LSTM、LSTM、CNN预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Transformer-LSTM、Transformer、CNN-LSTM、LSTM、…...

深入了解MCP基础与架构

一、引言 在人工智能技术以指数级速度渗透各行业领域的今天&#xff0c;我们正站在一个关键的技术拐点。当ChatGPT月活突破亿级、Gemini Pro实现多模态实时交互、Claude 3.5 Sonnet突破百万上下文长度&#xff0c;这些里程碑事件背后&#xff0c;一个崭新的大门逐步打开&#…...

实验设计与分析(第6版,Montgomery)第5章析因设计引导5.7节思考题5.13 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第5章析因设计引导5.7节思考题5.13 R语言解题。主要涉及方差分析&#xff0c;正态假设检验&#xff0c;残差分析&#xff0c;交互作用图。 dataframe<-data.frame( yc(36,18,30,39,20…...

怎么选择合适的高防IP

选择合适的高防IP需要综合考虑业务需求、防护能力、服务稳定性、成本效益等多方面因素。以下是从多个权威来源整理的关键要点&#xff0c;帮助您做出科学决策&#xff1a; 一、明确业务需求 业务类型与规模 网站/应用类&#xff1a;需支持HTTP/HTTPS协议&#xff0c;并配置域名…...

【java面试】MySQL篇

MySQL篇 一、总体结构二、优化&#xff08;一&#xff09;定位慢查询1.1 开源工具1.2Mysql自带的慢日志查询1.3 总结 &#xff08;二&#xff09;定位后优化2.1 优化2.2 总结 &#xff08;三&#xff09;索引3.1 索引3.2 索引底层数据结构——B树3.3 总结 &#xff08;四&#…...

贪心算法应用:欧拉路径(Fleury算法)详解

Java中的贪心算法应用&#xff1a;欧拉路径&#xff08;Fleury算法&#xff09;详解 一、欧拉路径与欧拉回路基础 1.1 基本概念 欧拉路径&#xff08;Eulerian Path&#xff09;是指在一个图中&#xff0c;经过图中每一条边且每一条边只经过一次的路径。如果这条路径的起点和…...

【算法设计与分析】实验——二维0-1背包问题(算法分析题:算法思路),独立任务最优调度问题(算法实现题:实验过程,描述,小结)

说明&#xff1a;博主是大学生&#xff0c;有一门课是算法设计与分析&#xff0c;这是博主记录课程实验报告的内容&#xff0c;题目是老师给的&#xff0c;其他内容和代码均为原创&#xff0c;可以参考学习&#xff0c;转载和搬运需评论吱声并注明出处哦。 要求&#xff1a;3-…...

P12592题解

题目传送门 思路 由于题目中说了可以任意交换两个字符的位置&#xff0c;我们只需要判断这个字符串是否满足回文串的条件即可。 代码&#xff1a; #include<bits/stdc.h> using namespace std; int a[30]; int main(){int T;cin>>T;while(T--){fill(a,a29,0);/…...

ffmpeg命令(二):分解与复用命令

分解&#xff08;Demuxing&#xff09; 提取视频流&#xff08;不含音频&#xff09; ffmpeg -i input.mp4 -an -vcodec copy video.h264-an&#xff1a;去掉音频 -vcodec copy&#xff1a;拷贝视频码流&#xff0c;不重新编码 提取音频流&#xff08;不含视频&#xff09…...

【Git】View Submitted Updates——diff、show、log

在 Git 中查看更新的内容&#xff08;即工作区、暂存区或提交之间的差异&#xff09;是日常开发中的常见操作。以下是常用的命令和场景说明&#xff1a; 文章目录 1、查看工作区与暂存区的差异2、查看提交历史中的差异3、查看工作区与最新提交的差异4、查看两个提交之间的差异5…...

deepseek原理和项目实战笔记2 -- deepseek核心架构

混合专家&#xff08;MoE&#xff09; ​​混合专家&#xff08;Mixture of Experts, MoE&#xff09;​​ 是一种机器学习模型架构&#xff0c;其核心思想是通过组合多个“专家”子模型&#xff08;通常为小型神经网络&#xff09;来处理不同输入&#xff0c;从而提高模型的容…...

在 MATLAB 2015a 中如何调用 Python

在 MATLAB 2015a 中调用 Python 可通过系统命令调用、.NET 交互层包装、MEX 接口间接桥接、环境变量配置四种方式&#xff0c;但因该版本对 Python 支持有限&#xff0c;主要依赖的是系统命令调用与间接脚本交互。其中&#xff0c;通过 system() 函数调用 Python 脚本是最简单且…...

房屋租赁系统 Java+Vue.js+SpringBoot,包括房屋类型、房屋信息、预约看房、合同信息、房屋报修、房屋评价、房主管理模块

房屋租赁系统 JavaVue.jsSpringBoot&#xff0c;包括房屋类型、房屋信息、预约看房、合同信息、房屋报修、房屋评价、房主管理模块 百度云盘链接&#xff1a;https://pan.baidu.com/s/1KmwOFzN9qogyaLQei3b6qw 密码&#xff1a;l2yn 摘 要 社会的发展和科学技术的进步&#xf…...

华为OD机试真题——生成哈夫曼树(2025B卷:100分)Java/python/JavaScript/C/C++/GO六种最佳实现

2025 B卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 本文收录于专栏:《2025华为OD真题目录+全流程解析/备考攻略/经验分享》 华为OD机试真题《生成…...

react与vue的渲染原理

vue&#xff1a;响应式驱动模板编译 &#xff08;1&#xff09;模板编译 将模板&#xff08;.vue 文件或 HTML 模板&#xff09;编译为 渲染函数&#xff08;Render Function&#xff09;&#xff1b; &#xff08;2&#xff09;响应式依赖收集 初始化时&#xff0c;通过 Ob…...

我提出结构学习的思路,意图用结构学习代替机器学习

我提出结构学习的思路&#xff0c;意图用结构学习代替机器学习 1.机器学习的本质和缺点 机器学习的规律是设计算法、用数据训练算法、让算法学会产生正确的数据回答问题&#xff0c;其缺点在于&#xff0c;需要大规模训练数据和巨大算力还其次&#xff0c;机器学习不能产生智…...

Outbox模式:确保微服务间数据可靠交换的设计方案

https://debezium.io/blog/2019/02/19/reliable-microservices-data-exchange-with-the-outbox-pattern/ Outbox模式是一种在微服务架构中确保数据更改和消息/事件发布之间可靠性的设计模式。它解决了在更新数据库和发送消息这两个独立操作中可能出现的不一致问题&#xff08;…...

数据可视化的定义和类型

数据可视化是一种将数据转换为图形或视觉表示的方法。想象一下&#xff0c;你面前有一堆数字和表格&#xff0c;看着这些&#xff0c;可能会让人头大。数据可视化就像是给这些枯燥的数字画上一幅画。它用图表、地图和各种有趣的图形&#xff0c;帮我们把难懂的数字变得容易看懂…...

sqlite-vec:谁说SQLite不是向量数据库?

sqlite-vec 是一个 SQLite 向量搜索插件&#xff0c;具有以零依赖、轻量级、跨平台和高效 KNN 搜索等优势&#xff0c;是本地化向量检索&#xff08;例如 RAG&#xff09;、轻量级 AI 应用以及边缘计算等场景的理想工具。 sqlite-vec 使用纯 C 语言实现&#xff0c;零外部依赖…...

Redis最佳实践——性能优化技巧之监控与告警详解

Redis 在电商应用的性能优化技巧之监控与告警全面详解 一、监控体系构建 1. 核心监控指标矩阵 指标类别关键指标计算方式/说明健康阈值&#xff08;参考值&#xff09;内存相关used_memoryINFO Memory 获取不超过 maxmemory 的 80%mem_fragmentation_ratio内存碎片率 used_m…...

R3GAN训练自己的数据集

简介 简介&#xff1a;这篇论文挑战了"GANs难以训练"的广泛观点&#xff0c;通过提出一个更稳定的损失函数和现代化的网络架构&#xff0c;构建了一个简洁而高效的GAN基线模型R3GAN。作者证明了通过合适的理论基础和架构设计&#xff0c;GANs可以稳定训练并达到优异…...

MATLAB实战:Arduino硬件交互项目方案

以下是一个使用MATLAB与Arduino进行硬件交互的项目方案&#xff0c;涵盖传感器数据采集和执行器控制。本方案使用MATLAB的Arduino硬件支持包&#xff0c;无需额外编写Arduino固件。 系统组成 硬件&#xff1a; Arduino Uno 温度传感器&#xff08;如LM35&#xff09; 光敏电…...

bert扩充或者缩小词表

在BERT模型中添加自己的词汇&#xff08;pytorch版&#xff09; - 知乎 输入 1. 扩充词表 替换bert词表中的【unused】 2. 缩小词表 因为要使用预训练的模型&#xff0c;词id不能变&#xff0c;词向量矩阵大小不变 要做的是将减少的那一部分词全部对应为unk&#xff0c;即可…...

什么是 TOML?

&#x1f6e0; Rust 配置文件实战&#xff1a;TOML 语法详解与结构体映射&#xff08; 在 Rust 中&#xff0c;Cargo.toml 是每个项目的心脏。它不仅定义了项目的名称、版本和依赖项&#xff0c;还使用了一种轻巧易读的配置语言&#xff1a;TOML。 本文将深入解析 TOML 的语法…...

git怎么合并两个分支

git怎么合并分支代码 注意: 第一步你得把当前分支合到远程分支去才能有下面的操作 另外我是将develop分支代码合并到release分支去 git 命令 查看本地所有分支 git branch切换分支 例如切换到release分支 git checkout release拉取代码 git pull up release 合并分支 …...

1.文件操作相关的库

一、filesystem(C17) 和 fstream 1.std::filesystem::path - cppreference.cn - C参考手册 std::filesystem::path 表示路径 构造函数&#xff1a; path( string_type&& source, format fmt auto_format ); 可以用string进行构造&#xff0c;也可以用string进行隐式类…...

Pytorch中一些重要的经典操作和简单讲解

Pytorch中一些重要的经典操作和简单讲解&#xff1a; 形状变换操作 reshape() / view() import torchx torch.randn(2, 3, 4) print(f"原始形状: {x.shape}")# reshape可以处理非连续张量 y x.reshape(6, 4) print(f"reshape后: {y.shape}")# view要求…...