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

低代码平台后端搭建-阶段完结

前言

        最近又要开始为跳槽做准备了,发现还是写博客学的效率高点,在总结其他技术栈之前准备先把这个专题小完结一波。在这一篇中我又试着添加了一些实际项目中可能会用到的功能点,用来验证这个平台的扩展性,以及总结一些学过的知识。在这一篇中会增加如下功能点:增加Python执行组件、支持断点调试组件流、展示每个组件的详细运行信息。

Python组件

实现过程

        在实际的应用中,有些复杂的需求可能没办法用现有的组件去实现,比如希望对组件A的结果进行函数计算、数据格式转换等,此时可以考虑引入一个Python组件,在这个组件的入参中直接写Python代码进行需要的操作。具体代码用gpt即可搞定,示例如下:

lowcode.application.properties——修改

python.interpreter.path=/Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10

在配置文件中引入当前机器的Python环境的位置

com.example.lowcode.util.PythonUtil——新增

然后创建一个类用于解析Python代码

package com.example.lowcode.util;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.concurrent.TimeUnit;@Service
public class PythonUtil {@Value("${python.interpreter.path}")private String pythonInterpreterPath;public String executePythonCode(String code, Map<String,Object> params) throws IOException, InterruptedException {String fullCode = buildFullPythonCode(code, params);ProcessBuilder processBuilder = new ProcessBuilder(pythonInterpreterPath, "-c", fullCode);Process process = processBuilder.start();// Handle the process's output stream (Python's stdout)String output = readFromStream(process.getInputStream());// Handle the process's error stream (Python's stderr)String errorOutput = readFromStream(process.getErrorStream());boolean finished = process.waitFor(30, TimeUnit.SECONDS);if (!finished) {throw new RuntimeException("Python process did not finish within the timeout period.");}if (process.exitValue() != 0) {throw new RuntimeException("Python execution error: " + errorOutput);}return output.replaceAll("\\n$", "");}private String buildFullPythonCode(String code, Map<String, Object> params) {// 构建参数传递的代码StringBuilder arguments = new StringBuilder();for (Map.Entry<String, Object> entry : params.entrySet()) {String key = entry.getKey();Object value = entry.getValue();if (value instanceof String) {// 字符串参数需要加引号arguments.append(String.format("%s = '%s'", key, value));} else {// 非字符串参数直接转换为字符串arguments.append(String.format("%s = %s", key, value));}// 在参数之间添加换行符arguments.append(System.lineSeparator());}return arguments + code;}private String readFromStream(InputStream inputStream) throws IOException {StringBuilder output = new StringBuilder();try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {String line;while ((line = reader.readLine()) != null) {output.append(line).append(System.lineSeparator());}}return output.toString();}
}

com.example.lowcode.component.PythonScript——新增

最后再创建Python组件即可

package com.example.lowcode.component;import com.example.lowcode.core.dto.ComponentInfo;
import com.example.lowcode.core.framework.AbstractComponent;
import com.example.lowcode.core.framework.ComponentContext;
import com.example.lowcode.core.model.*;
import com.example.lowcode.util.PythonUtil;
import com.google.common.collect.Maps;
import org.springframework.beans.factory.annotation.Autowired;import java.util.HashMap;
import java.util.Map;@ComponentDefinition(name = "PythonScript", type = ComponentTypeEnum.SERVICE_CALL, desc = "python组件")
@InputParamDefinition({@Param(name = "code", desc = "python函数模板", type = ParamTypeEnum.STRING, required = true),@Param(name = "params", desc = "函数入参", type = ParamTypeEnum.MAP, required = false)
})
@OutputParamDefinition({@Param(name = "result", desc = "http接口返回结果", required = true)})
public class PythonScript extends AbstractComponent {@Autowiredprivate PythonUtil pythonUtil;@Overridepublic Map<String, Object> execute(ComponentContext context, ComponentInfo componentInfo) throws Exception {String code = (String) parseInputParam("code", context, componentInfo);Map<String, Object> paramMap = (Map<String, Object>) parseInputParam("params", context, componentInfo);String output = parseOutputParam("result",componentInfo);HashMap<String, Object> result = Maps.newHashMap();String pythonResult = pythonUtil.executePythonCode(code,paramMap);result.put(output, pythonResult);return result;}}

测试

单独测试这个组件:

    @Testpublic void testPythonScript() {try {// 调用方法并打印结果ComponentInfo componentInfo = new ComponentInfo();componentInfo.setInputs(new HashMap<>() {{String s = """def main(response: str, length: int) -> str:import reresponse = response[:length]match = re.search(r'[ABCDEFGH]', response)if match:return match.group()else:return 'other result'""";String s1 = """def main(content1, content2):return content1 + "" + content2""";String mainDef = """result = main(response, length)print(result, end='')""";put("code", new ComponentParam().setName("code").setValue(s+mainDef));HashMap<Object, Object> map = new HashMap<>();
//                map.put("content1","Hello World!");
//                map.put("content2","hehe");map.put("response","Hello World!");map.put("length",20);put("params", new ComponentParam().setName("params").setValue(map));}});componentInfo.setOutputs(new HashMap<>() {{put("result", new ComponentParam().setName("result").setValue("result"));}});Map<String, Object> execute = pythonScript.execute(new ComponentContext(), componentInfo);System.out.println(execute);} catch (Exception e) {e.printStackTrace();}}

 运行结果:

断点调试组件流

实现过程

        在使用低代码平台编辑组件流时,可能会遇到后面几个组件执行有问题或执行很慢的情况,可以考虑增加断点执行的能力,制定中间的某个组件为结束节点。

      

        如上图所示,比如在调试的时候不想调用HttpClient组件,那就可以把PageFilter组件指定为结束节点,最下面的组件因为入参不够也会不执行。

        实现的思路很简单,因为之前2.0版本的代码会根据组件之间的线去解析关联关系,只需要找到新的结束节点依赖的所有节点,把他们放到执行引擎中,不被依赖的节点自然就被剪掉了。

com.example.lowcode.core.dto2.FlowEngineBuilder——修改

剪枝部分的代码:

public DagEngine<O> buildDebug(String instanceName) {check();DagEngine<O> engineWithOpConfig = getEngineWithOpConfig(flow, instanceName);clear();return engineWithOpConfig;}private DagEngine<O> getEngineWithOpConfig(Flow flow, String instanceName) {DagEngine<O> engine = new DagEngine<>(executor);List<OperatorWrapper<?, ?>> operatorWrappers = getWrappersWithOpConfig(flow, engine);// 单节点执行逻辑,根据当前节点解析依赖节点Set<String> dependNode = new HashSet<>();resolveDependenciesForCut(flow, operatorWrappers, instanceName, dependNode);// 遍历wrapperMap,保留debug节点的所有依赖节点Map<String, OperatorWrapper<?, ?>> debugWrapperMap = new HashMap<>();engine.getWrapperMap().forEach((k, v) -> {if (dependNode.contains(k)) {debugWrapperMap.put(k, v);}});engine.setWrapperMap(debugWrapperMap);return engine;}private void resolveDependenciesForCut(Flow flow, List<OperatorWrapper<?, ?>> operatorWrappers, String instanceName, Set<String> dependNode) {final Map<String, OperatorWrapper<?, ?>> wrapperMap = operatorWrappers.stream().collect(Collectors.toMap(OperatorWrapper::getInstanceName, e -> e));final Map<String, List<Edge>> groupBySource = flow.getEdgeInstances().stream().collect(Collectors.groupingBy(Edge::getSourceName));groupBySource.forEach((id, followings) -> {for (Edge following : followings) {final OperatorWrapper<?, ?> targetOp = wrapperMap.get(following.getTargetName());targetOp.depend(id);}});Map<String, List<String>> sourceNameMap = new HashMap<>();groupBySource.forEach((k, v) -> {List<String> collect = v.stream().map(Edge::getTargetName).collect(Collectors.toList());sourceNameMap.put(k, collect);});dependNode.add(instanceName);// 查找当前节点的依赖节点findDependNode(instanceName, dependNode, sourceNameMap);}private void findDependNode(String start, Set<String> dependNode, Map<String, List<String>> sourceNameMap) {List<String> list = new ArrayList<>();list.add(start);while (!list.isEmpty()) {String node = list.remove(0);for (Map.Entry<String, List<String>> entry : sourceNameMap.entrySet()) {if (entry.getValue().contains(node)) {dependNode.add(entry.getKey());list.add(entry.getKey());}}}}

        另外指定新的结束节点需要一个组件标志,可以随意选择只要保证唯一即可,这里为了方便演示选择用nodeName(组件的自定义名称)作为组件标志,同时ComponentInfo类也需要加上private String instanceName;属性。

       

         然后引擎类DagEngine也需要加上set方法。

public void setWrapperMap(Map<String, OperatorWrapper<?, ?>> wrapperMap){this.wrapperMap = wrapperMap;}

       

         最后修改接口层,把构建引擎的方法替换为新建的:

测试

详细运行信息

实现过程

        实现这个功能需要小改动一下架构,之前的上下文类是用于存放每个组件的变量->变量值,是整个流层面的对象;现在需要保存每个节点的运行信息,且因为是并行需要线程安全。这里我加上原开源框架的DagContext类,来保存每个组件的运行信息。

        作为示例,本篇实现展示每个组件的组件名、输入输出、耗时、异常报错,经过分析,组件名、输入、异常报错可以直接从flowNode中获取,而输出和耗时需要在执行组件时添加。

com.example.lowcode.core.framework2.DagContext——新增

引擎上下文,和ComponentContext不同,后者是整个流用一个ComponentContext对象,这个类是用于记录多线程环境每个组件的执行过程。

package com.example.lowcode.core.framework2;import com.example.lowcode.core.dto2.OperatorResult;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** DAG执行引擎上下文* 上下文的生命周期是引擎执行期间,即从开始节点到结束节点之间*/
public class DagContext<O> {/*** 保存每个节点返回的结果* key: 节点id* value: result*/private Map<String, OperatorResult> operatorResultMap = new ConcurrentHashMap<>();private OperatorResult<O> output;public void putOperatorResult(String wrapperId, OperatorResult<?> operatorResult) {operatorResultMap.put(wrapperId, operatorResult);}public OperatorResult getOperatorResult(String wrapperId) {return operatorResultMap.get(wrapperId);}public synchronized void setOutput(OperatorResult<O> endResult) {this.output = endResult;}public OperatorResult<O> getOutput () {return output;}public Map<String, OperatorResult> getOperatorResultMap() {return operatorResultMap;}
}

com.example.lowcode.core.framework2.DagContextHolder——新增

包装DagContext,线程安全

package com.example.lowcode.core.framework2;import com.alibaba.ttl.TransmittableThreadLocal;
import com.example.lowcode.core.dto2.OperatorResult;/*** 获取DagContext上下文的工具类*/
public class DagContextHolder {private static ThreadLocal<DagContext> holder = new TransmittableThreadLocal<>();protected static void set(DagContext dagContext) {holder.set(dagContext);}public static DagContext get() {return holder.get();}protected static void remove() {holder.remove();}public static void putOperatorResult(String instanceName, OperatorResult<?> operatorResult) {holder.get().putOperatorResult(instanceName, operatorResult);}public static OperatorResult getOperatorResult(String instanceName) {return holder.get().getOperatorResult(instanceName);}
}
com.example.lowcode.core.framework2.DagEngine——修改

然后修改引擎类的代码

↑初始化DagContext

↑在getRunningTask方法中更新上下文的运行结果

↑最后在执行run组件的前后记录耗时,OperatorResult类也需要加上duration属性。这个实现方式很不好,在下文会修改这段实现。 

com.example.lowcode.core.service.RunServiceImpl——修改

下一步是在实现类中新写一个接口,把组件的运行信息都取出来:

    @Overridepublic Map<String, Map<String, Object>> runFlowDebug(long flowId, Map<String, ComponentInfo> inputParams, String instanceName) {FlowSnapshot flowSnapshot = FlowSnapshotDO.selectByFlowId(flowId);assert flowSnapshot != null;Flow flow = JSON.parseObject(flowSnapshot.getJsonParam(), new TypeReference<>() {});DagEngine<Map<String, Object>> engine = new FlowEngineBuilder<Map<String, Object>>().setFlow(flow).setInputParams(inputParams).setExecutor(THREAD_POOL_EXECUTOR).buildDebug(instanceName);engine.runAndWait();
//        if (engine.getEx() != null) {
//            throw new FlowExecutionException(String.format("【%s:%s】执行异常,原因:%s", flow.getId(), flow.getName(), engine.getEx().getMessage()), engine.getEx());
//        }Map<String, Map<String, Object>> flowResult = new HashMap<>();// 遍历存放每个组件的信息for(FlowNode node : flow.getNodeInstances()) {Map<String, Object> flowInfo = Maps.newHashMap();flowResult.put(node.getNodeName(), flowInfo);flowInfo.put("nodeName", node.getNodeName());flowInfo.put("componentName", node.getComponentName());OperatorWrapper<?, ?> operatorWrapper = engine.getWrapperMap().get(node.getNodeName());// 当且仅当node执行才设置详细信息if(operatorWrapper == null || operatorWrapper.getOperatorResult() == null|| Objects.equals(ResultState.DEFAULT, operatorWrapper.getOperatorResult().getResultState())) {continue;}// 设置input信息Map<String, Object> inputMap = Maps.newHashMap();inputMap.putAll(node.getComponentInfo().getInputs());flowInfo.put("input", inputMap);// 设置output信息OperatorResult operatorResult = engine.getDagContext().getOperatorResultMap().get(node.getNodeName());flowInfo.put("output", operatorResult.getResult());// 设置durationflowInfo.put("duration", operatorWrapper.getOperatorResult().getDuration());// 设置log信息Map<String, Object> logMap = Maps.newHashMap();if(operatorResult.getEx() != null) {logMap.put("stderr", operatorResult.getEx().getStackTrace());}flowInfo.put("log", logMap);}return flowResult;}

上面我把异常判断注释了,当发现组件流有异常时不再抛异常,而是返回结果。这里遍历的是flowNode,里面有写node可能没有被执行(断点执行)。

测试

修改测试代码 

可以看到result中展示了每个组件的运行结果。

之后我手动造了一个异常,用于测试报错信息的展示↓

虽然在这里报错了,却没有执行到下面这块↓,也就没有记录异常信息。

原因是我之前写切面的时候把异常捕获了还没有抛出↓

把这块删掉后,再次运行即可看到报错信息:

补充

        上面记录耗时的写法并不好,如果未来要进行一些复杂操作,会写的比较杂乱,不过目前来看其实不需要重构,可以当做参考看看。原开源代码中提供了两种思路:类似抽象类的execute方法,再抽象出start方法、success方法、error方法,分别对应组件的执行前、执行完成、执行异常,调用方式和执行的方法execute类似;另一个思路是用回调方法来实现。这里我用前者来扩展实现:

实现过程

com.example.lowcode.core.framework.ComponentInterface——修改

package com.example.lowcode.core.framework;import com.example.lowcode.core.dto.ComponentInfo;
import com.example.lowcode.core.dto2.OperatorResult;
import com.example.lowcode.core.framework2.OperatorWrapper;import java.util.Map;/*** @author llxzdmd* @version IComponent.java, v 0.1 2024年01月02日 19:00 llxzdmd*/
public interface ComponentInterface {default Object defaultValue(ComponentContext context, ComponentInfo componentInfo) {return null;}Map<String, Object> execute(ComponentContext context, ComponentInfo componentInfo) throws Exception;default void onStart(ComponentContext context, ComponentInfo componentInfo){};default void onSuccess(ComponentContext context, ComponentInfo componentInfo, OperatorResult<Object> result){};default void onError(ComponentContext context, ComponentInfo componentInfo, OperatorResult<Object> result){};
}

首先在接口和抽象类中增加三个准备监听阶段的方法。这几个方法是组件去执行时的调用,可以在里面写额外逻辑,但目前需求不需要,直接置空即可。

com.example.lowcode.core.framework2.IOperator——修改

package com.example.lowcode.core.framework2;import com.example.lowcode.core.dto2.OperatorResult;
import com.example.lowcode.core.framework.ComponentContext;/*** Operator接口** @author llxzdmd* @version IOperator.java, 2024年02月18日 16:13 llxzdmd*/
public interface IOperator<P, V> {/*** 自定义OP的默认返回值*/default V defaultValue(P param) {return null;}/*** 该方法实现OP的具体处理逻辑*/V execute(P param, ComponentContext context) throws Exception;void onStart(OperatorWrapper<?, ?> param, ComponentContext context);void onSuccess(OperatorWrapper<?, ?> param, ComponentContext context, OperatorResult<Object> result);void onError(OperatorWrapper<?, ?> param, ComponentContext context, OperatorResult<Object> result);
}

因为引擎在执行的过程中无法获取到组件对象去执行对应的方法,需要获取到封装的IOperator类,由这个类再去执行接口的方法,因此在此处也定义几个阶段。之后需要修改IOperator接口的实现类。

com.example.lowcode.core.framework2.DefaultInvokeMethodComponent——修改

package com.example.lowcode.core.framework2;import com.example.lowcode.core.dto2.OperatorResult;
import com.example.lowcode.core.exception.FlowConfigException;
import com.example.lowcode.core.framework.AbstractComponent;
import com.example.lowcode.core.framework.ComponentContext;
import com.example.lowcode.core.framework.SpringUtil;
import org.springframework.stereotype.Component;import java.util.Map;/*** @author llxzdmd* @version DefaultInvokeMethodComponent.java, 2024年02月18日 19:26 llxzdmd*/
@Component
public class DefaultInvokeMethodComponent implements IOperator<OperatorWrapper<?, ?>, Object> {@Overridepublic Object defaultValue(OperatorWrapper<?, ?> param) {return new Object();}@Overridepublic Object execute(OperatorWrapper<?, ?> param, ComponentContext context) throws Exception {return invokeMethod(param, context);}@Overridepublic void onStart (OperatorWrapper<?, ?> param, ComponentContext context) {invokeMethod(param, context, "onStart", null);}@Overridepublic void onSuccess (OperatorWrapper<?, ?> param, ComponentContext context, OperatorResult<Object> result) {invokeMethod(param, context, "onSuccess", result);}@Overridepublic void onError (OperatorWrapper<?, ?> param, ComponentContext context, OperatorResult<Object> result) {invokeMethod(param, context, "onError", result);}private Object invokeMethod(OperatorWrapper<?, ?> param, ComponentContext context) {OpConfig opConfig = param.getOpConfig();try {Class<?> aClass = Class.forName(opConfig.getClassName());AbstractComponent abstractComponent = (AbstractComponent) SpringUtil.getBean(aClass);return abstractComponent.execute(context, opConfig.getComponentInfo());} catch (Exception e) {throw new RuntimeException(e);}}private void invokeMethod(OperatorWrapper<?, ?> param, ComponentContext context, String methodName, OperatorResult<Object> result){OpConfig opConfig = param.getOpConfig();try {Class<?> aClass = Class.forName(opConfig.getClassName());AbstractComponent abstractComponent = (AbstractComponent) SpringUtil.getBean(aClass);switch (methodName) {case "onStart" -> abstractComponent.onStart(context, opConfig.getComponentInfo());case "onSuccess" -> abstractComponent.onSuccess(context, opConfig.getComponentInfo(), result);case "onError" -> abstractComponent.onError(context, opConfig.getComponentInfo(), result);default -> abstractComponent.defaultValue(context, opConfig.getComponentInfo());}} catch (Exception e) {throw new RuntimeException(e);}}
}

这里新写了一个多一个参数的invokeMethod方法,因为需要得到onSuccess和onError时的运行结果。invokeMethod方法也可以再抽象一层,用反射来执行对应的方法,尝试了一下由于需要获取到每个方法的入参类型,用枚举的话和上面的写法类似;否则需要再定义一个记录需要执行的方法的入参类型、入参值,再在此处解析,成本太大,就不继续抽象了。

com.example.lowcode.core.framework2.DagEngine——修改

在引擎类的对应位置让执行节点调用对应的方法,节点就会调用到组件的对应方法。

 

 之后可以在切面中监听到组件执行这几个方法的动作,进行相应的处理。

之后再把这两行注释掉,就准备就绪可以测试了。

测试

效果符合预期

↑把之前制造的bug去掉,正常运行 

总结

        需要博客源码可私信免费获取,看到就会回复。

相关文章:

低代码平台后端搭建-阶段完结

前言 最近又要开始为跳槽做准备了&#xff0c;发现还是写博客学的效率高点&#xff0c;在总结其他技术栈之前准备先把这个专题小完结一波。在这一篇中我又试着添加了一些实际项目中可能会用到的功能点&#xff0c;用来验证这个平台的扩展性&#xff0c;以及总结一些学过的知识。…...

暑假考研集训营游记

文章目录 摘要&#xff1a;1.对各大辅导机构考研封闭集训营的一些个人看法&#xff1a;2.对于考研原因一些感想&#xff1a;结语 摘要&#xff1a; Ashy在暑假的时候参加了所在辅导班的为期一个月的考研封闭集训营&#xff0c;有了一些全新的感悟&#xff0c;略作记录。 1.对…...

C#中的报文(Message)

在C#中&#xff0c;报文&#xff08;Message&#xff09;通常是指在网络通信中交换的数据单元。报文可以由多种不同的组成部分构成&#xff0c;具体取决于通信协议和应用场景。 以下是一些常见的报文组成部分&#xff1a; 头部&#xff08;Header&#xff09;&#xff1a;包含…...

Python知识点:如何使用Python与Java进行互操作(Jython)

开篇&#xff0c;先说一个好消息&#xff0c;截止到2025年1月1日前&#xff0c;翻到文末找到我&#xff0c;赠送定制版的开题报告和任务书&#xff0c;先到先得&#xff01;过期不候&#xff01; Jython 是一种完全兼容 Java 的 Python 实现&#xff0c;它将 Python 代码编译成…...

ffmpeg解封装解码

文章目录 封装和解封装封装解封装 相关接口解封装的流程图关于AVPacket的解释如何区分不同的码流&#xff0c;视频流&#xff0c;音频流&#xff1f;第一种方式av_find_best_stream第二种方式 通过遍历流 代码 封装和解封装 封装 是把音频流 &#xff0c;视频流&#xff0c;字…...

golang学习笔记10-循环结构

注&#xff1a;本人已有C&#xff0c;C,Python基础&#xff0c;只写本人认为的重点。 go的循环只有for循环&#xff0c;但有多个语法&#xff0c;可以实现C/C中的while和do while。当然&#xff0c;for循环也有break和continue&#xff0c;这点和C/C相同。 语法1&#xff1a; f…...

Java高级编程——泛型(泛型类、泛型接口、泛型方法,完成详解,并附有案例+代码)

文章目录 泛型21.1 概述21.2 泛型类21.3 泛型方法21.4 泛型接口 泛型 21.1 概述 JDK5中引入的特性&#xff0c;在编译阶段约束操作的数据类型&#xff0c;并进行检查 泛型格式&#xff1a;<数据类型> 泛型只能支持引用数据类型&#xff0c;如果写基本数据类型需要写对…...

GPU硬件如何实现光栅化?

版权声明 本文为“优梦创客”原创文章&#xff0c;您可以自由转载&#xff0c;但必须加入完整的版权声明文章内容不得删减、修改、演绎本文视频版本&#xff1a;见文末 引言 大家好&#xff0c;我是老雷&#xff0c;今天我想从GPU硬件原理出发&#xff0c;给大家分享在图形渲…...

Python写入文件内容:从入门到精通

在日常编程工作中&#xff0c;我们常常会遇到需要将数据保存至磁盘的需求。无论是日志记录、配置文件管理还是数据持久化&#xff0c;掌握如何有效地使用Python来写入文件内容都是必不可少的一项技能。本文将从基础语法开始&#xff0c;逐步深入探讨Python中写入文件内容的各种…...

相亲交易系统源码详解与开发指南

随着互联网技术的发展&#xff0c;越来越多的传统行业开始寻求线上转型&#xff0c;其中就包括婚恋服务。传统的相亲方式已经不能满足现代人快节奏的生活需求&#xff0c;因此&#xff0c;开发一款基于Web的相亲交易系统显得尤为重要开发者h17711347205。本文将详细介绍如何使用…...

Golang | Leetcode Golang题解之第413题等差数列划分

题目&#xff1a; 题解&#xff1a; func numberOfArithmeticSlices(nums []int) (ans int) {n : len(nums)if n 1 {return}d, t : nums[0]-nums[1], 0// 因为等差数列的长度至少为 3&#xff0c;所以可以从 i2 开始枚举for i : 2; i < n; i {if nums[i-1]-nums[i] d {t}…...

汽车总线之----FlexRay总线

Introduction 随着汽车智能化发展&#xff0c;车辆开发的ECU数量不断增加&#xff0c;人们对汽车系统的各个性能方面提出了更高的需求&#xff0c;比如更多的数据交互&#xff0c;更高的传输带宽等。现如今人们广泛接受电子功能来提高驾驶安全性&#xff0c;像ABS防抱死系统&a…...

前端代替后端做分页操作

如果后端没有分页api&#xff0c;前端如何做分页一、使用computed 这个变量应该是计算之后的值&#xff0c;是一个状态管理变量&#xff0c;跟onMounted类似import {computed} from vue // 定义ref储存rolelist&#xff0c;这里是原始数据 const roleList ref([])// 定义页码…...

L3 逻辑回归

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 在周将使用 LogisticRegression 函数对经典的鸢尾花 (Iris) 数据集进行分类。将详细介绍逻辑回归的数学原理。 1. 逻辑回归的数学原理 逻辑回归是一种线性分…...

Flink系列知识之:Checkpoint原理

Flink系列知识之&#xff1a;Checkpoint原理 在介绍checkpoint的执行流程之前&#xff0c;需要先明白Flink中状态的存储机制&#xff0c;因为状态对于检查点的持续备份至关重要。 State Backends分类 下图显示了Flink中三个内置的状态存储种类。MemoryStateBackend和FsState…...

智算中心动环监控:构建高效、安全的数字基础设施@卓振思众

在当今快速发展的数字经济时代&#xff0c;智算中心作为人工智能和大数据技术的核心支撑设施&#xff0c;正日益成为各行业实现智能化转型的重要基石。为了确保这些高性能计算环境的安全与稳定&#xff0c;卓振思众动环监控应运而生&#xff0c;成为智算中心管理的重要组成部分…...

PyTorch VGG16手写数字识别教程

手写数字识别教程&#xff1a;使用PyTorch和VGG16 1. 环境准备 确保你已安装以下库&#xff1a; pip install torch torchvision2. 导入必要的库 import torch import torch.nn as nn import torch.optim as optim import torchvision.transforms as transforms import tor…...

安卓13删除下拉栏中的设置按钮 android13删除设置按钮

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.编译6.彩蛋1.前言 顶部导航栏下拉可以看到,底部这里有个设置按钮,点击可以进入设备的设置页面,这里我们将更改为删除,不同用户通过这个地方进入设置。也就是下面这个按钮。 2.问题分析…...

FDA辅料数据库在线免费查询-药用辅料

在药物制剂的研制过程中&#xff0c;需要确定这些药用辅料的安全用量。而美国食品药品监督管理局&#xff08;FDA&#xff09;的辅料数据库&#xff08;IID&#xff09;提供了其制剂研发中的关键参考资源&#xff0c;使得更多的医药研发相关人员及企业单位节省试验环节及时间成…...

git pull 报错 refusing to merge unrelated histories

这个对我来说非常常见&#xff0c;因为我都是先由本地项目&#xff0c;再想着传到github上去。 在本地项目中执行 git init git add . git commit -m “xxx” 在github上创建项目&#xff0c;添加了 README.md 文件。 git remote add origin https://github.com/raoxiaoya/x…...

STM32G431RBT6(蓝桥杯)串口(发送)

一、基础配置 (1) PA9和PA10就是串口对应在单片机上的端口 注意&#xff1a;一定要先选择PA9的TX和PA10的RX&#xff0c;再去打开异步的模式 (2) 二、查看单片机的端口连接至电脑的哪里 &#xff08;1&#xff09;此电脑->右击属性 &#xff08;2&#xff09;找到端…...

使用 typed-rest-client 进行 REST API 调用

typed-rest-client 是一个用于 Node.js 的库&#xff0c;它提供了一种类型安全的方式来与 RESTful API 进行交互。其主要功能包括&#xff1a; 安装 typed-rest-client 要使用 typed-rest-client&#xff0c;首先需要安装它&#xff0c;可以通过 npm 来安装&#xff1a; $ n…...

在Ubuntu 14.04上安装Solr的方法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 简介 Solr 是基于 Apache Lucene 的搜索引擎平台。它用 Java 编写&#xff0c;并使用 Lucene 库来实现索引。可以通过各种 REST API&am…...

LabVIEW提高开发效率技巧----使用LabVIEW工具

LabVIEW为开发者提供了多种工具和功能&#xff0c;不仅提高工作效率&#xff0c;还能确保项目的质量和可维护性。以下详细介绍几种关键工具&#xff0c;并结合实际案例说明它们的应用。 1. VI Analyzer&#xff1a;自动检查代码质量 VI Analyzer 是LabVIEW提供的一款强大的工…...

Pyspark dataframe基本内置方法(4)

文章目录 Pyspark sql DataFrame相关文章RDDrepartition 重新分区replace 替换sameSemantics dataframe是否相等sample 采样sampleBy 分层采样schema 显示dataframe结构select 查询selectExpr 查询semanticHash 获取哈希值show 展示dataframesort 排序sortWithinPartitions 分区…...

配置win10开电脑时显示可登录账号策略

有1台公用的windows10电脑&#xff0c;电脑上有N多用户&#xff0c;使用人员登录时选择相应的账号登录即可。但在某次使用脚本加固后&#xff0c;发现之前显示的用户都不能显示了。检查加固脚本&#xff0c;是脚本启用了“交互式登录&#xff1a;不显示上次登录”策略。因此&am…...

01-Mac OS系统如何下载安装Python解释器

目录 Mac安装Python的教程 mac下载并安装python解释器 如何下载和安装最新的python解释器 访问python.org&#xff08;受国内网速的影响&#xff0c;访问速度会比较慢&#xff0c;不过也可以去我博客的资源下载&#xff09; 打开历史发布版本页面 进入下载页 鼠标拖到页面…...

24 C 语言常用的字符串处理函数详解:strlen、strcat、strcpy、strcmp、strchr、strrchr、strstr、strtok

目录 1 strlen 1.1 函数原型 1.2 功能说明 1.3 案例演示 1.4 注意事项 2 strcat 2.1 函数原型 2.2 功能说明 2.3 案例演示 2.4 注意事项 3 strcpy 3.1 函数原型 3.2 功能说明 3.3 案例演示 3.4 注意事项 4 strcmp 4.1 函数原型 4.2 功能说明 4.3 案例演示 …...

数据驱动农业——农业中的大数据

橙蜂智能公司致力于提供先进的人工智能和物联网解决方案&#xff0c;帮助企业优化运营并实现技术潜能。公司主要服务包括AI数字人、AI翻译、埃域知识库、大模型服务等。其核心价值观为创新、客户至上、质量、合作和可持续发展。 橙蜂智农的智慧农业产品涵盖了多方面的功能&…...

学习《分布式》必须清楚的《CAP理论》

分布式的理论基础CAP理论 当学习分布式的redis、mq等中间件时&#xff0c;都会看到有提到CAP。 CAP理论是学习分布式必备的一个概念知识点。 CAP理论由三个特性组成&#xff0c;分别是一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#xff0…...