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

news2024/11/15 6:53:40

前言

        最近又要开始为跳槽做准备了,发现还是写博客学的效率高点,在总结其他技术栈之前准备先把这个专题小完结一波。在这一篇中我又试着添加了一些实际项目中可能会用到的功能点,用来验证这个平台的扩展性,以及总结一些学过的知识。在这一篇中会增加如下功能点:增加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 {

    @Autowired
    private PythonUtil pythonUtil;

    @Override
    public 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;
    }

}

测试

单独测试这个组件:

    @Test
    public void testPythonScript() {
        try {
            // 调用方法并打印结果
            ComponentInfo componentInfo = new ComponentInfo();
            componentInfo.setInputs(new HashMap<>() {{
                String s = """
                        def main(response: str, length: int) -> str:
                            import re
                            response = 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——修改

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

    @Override
    public 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());

            // 设置duration
            flowInfo.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> {
    @Override
    public Object defaultValue(OperatorWrapper<?, ?> param) {
        return new Object();
    }

    @Override
    public Object execute(OperatorWrapper<?, ?> param, ComponentContext context) throws Exception {
        return invokeMethod(param, context);
    }

    @Override
    public void onStart (OperatorWrapper<?, ?> param, ComponentContext context) {
        invokeMethod(param, context, "onStart", null);
    }

    @Override
    public void onSuccess (OperatorWrapper<?, ?> param, ComponentContext context, OperatorResult<Object> result) {
        invokeMethod(param, context, "onSuccess", result);
    }

    @Override
    public 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去掉,正常运行 

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2151461.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ngnix 在windows上的简单使用

安装 下载链接: nginx: download 选择页面中 Stable version 下的windows版本直接下载解压到本地。 运行nginx: 解压到本地后,结构如图: cmd 进入到上图的根目录,运行 start nginx ,即可开启。 打开 http://localhost 进行查看,如果正常打开nginx的测试页面,则说…

Nvidia的高级研究科学家Jim Fan预计在未来两到三年内,机器人技术将取得重大进展

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

pcdn盒子连接方式

连接方式 大部分连接方式如下 光猫拨号 → 路由器 → 盒子 优点&#xff1a;光猫负责拨号&#xff0c;路由器只需做路由转发&#xff0c;性能要求不高缺点&#xff1a;光猫会有一层nat&#xff0c;路由器还有一层nat&#xff0c;两层nat需要在两个设备上都做nat优化注意&…

mysql通过binlog做数据恢复

1 介绍 binlog&#xff08;二进制日志&#xff09;在 MySQL 中具有非常重要的作用。它记录了数据库的所有更改操作&#xff0c;主要用于数据恢复、复制和审计等方面。以下是 binlog 的主要作用&#xff1a; 1.数据恢复 binlog 可以用于恢复数据库中的数据。当数据库发生故障时…

Activiti7《第三式:破刀式》——工作流中的刀锋利刃

冲冲冲&#xff01;开干 这篇文章将分为九个篇章&#xff0c;带你逐步掌握工作流的核心知识。欢迎来到 “破刀式” 篇章&#xff01;在工作流的开发过程中&#xff0c;锋利的利器就是 精湛的设计与代码优化。本篇文章将探讨如何像一把利刃一样&#xff0c;用最直接的方式切入复…

phpword读取word docx文档文本及图片转html格式

最近在做一个PHP读取word文档功能&#xff0c;搜索一圈后决定选择用phpword第三方组件。 composer安装phpWord composer require phpoffice/phpword如果你的文件是doc格式&#xff0c;直接另存为一个docx就行了&#xff1b;如果你的doc文档较多&#xff0c;可以下一个批量转…

ZXing.Net:一个开源条码生成和识别器,支持二维码、条形码等

推荐一个跨平台的非常流行的条码库&#xff0c;方便我们在.Net项目集成条码扫描和生成功能。 01 项目简介 ZXing.Net是ZXing的.Net版本的开源库。支持跨多个平台工作&#xff0c;包括 Windows、Linux 和 macOS&#xff0c;以及在 .NET Core 和 .NET Framework 上运行。 解码…

Qwen 个人笔记

Qwen 个人笔记 Qwen的整体架构与Llama2类似&#xff0c;如下图所示: 1 Qwen2Config 1.1 Model 1.1.1 初始化 设置了模型的两个属性:padding_idx&#xff08;用于指定填充标记的索引&#xff09;&#xff0c;vocab_size&#xff08;词汇表的大小&#xff09;初始化了模型的…

基于MATLAB的安全帽检测系统

课题名称 课题介绍 众所周知&#xff0c;在一些施工工地&#xff0c;必须明确佩戴安全帽。可以对生命安全起到保障作用。该课题为常见的安全帽的识别&#xff0c;主要分为红色&#xff0c;蓝色&#xff0c;黄色三类安全帽。而安全帽的主要是红色&#xff0c;蓝色&…

Dell PowerEdge 网络恢复笔记

我有一台Dell的PowerEdge服务器&#xff0c;之前安装了Ubuntu 20 桌面版。突然有一天不能开机了。 故障排查 Disk Error 首先是看一下机器的正面&#xff0c;有一个非常小的液晶显示器&#xff0c;只能显示一排字。 上面显示Disk Error&#xff0c;然后看挂载的硬盘仓&#…

【Mysql-索引总结】

文章目录 什么是索引索引类型索引的数据结构Hash索引有序数组二叉搜索树平衡二叉树B树B索引 索引使用规则索引失效的情况如何选择正确的列进行索引&#xff1f; 什么是索引 索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构&#xff0c;它是某个表中…

【第34章】Spring Cloud之SkyWalking分布式日志

文章目录 前言一、准备1. 引入依赖 二、日志配置1. 打印追踪ID2. gRPC 导出 三、完整日志配置四、日志展示1. 前端2. 后端 总结 前言 前面已经完成了请求的链路追踪&#xff0c;这里我们通过SkyWalking来处理分布式日志&#xff1b; 场景描述&#xff1a;我们有三个服务消费者…

《JKTECH柔性振动盘:原理与多行业应用》东莞市江坤自动化科技有限公司

一、柔性振动盘的原理 柔性振动盘是一种新型的自动化上料设备&#xff0c;它采用先进的音圈电机技术和柔性振动技术&#xff0c;实现了对各种不规则形状、微小尺寸、易损伤零部件的高效上料和分拣。 其工作原理主要包括以下几个方面&#xff1a; 1. 音圈电机驱动 柔性振动盘内部…

电力施工作业安全行为检测图像数据集

电力施工作业安全行为检测图像数据集&#xff0c;图片总共 2300左右&#xff0c;标注为voc(xml)格式&#xff0c;包含高空抛物&#xff0c;未佩戴安全带&#xff0c;高处作业无人监护等。 电力施工作业安全行为检测图像数据集 数据集描述 这是一个专门用于电力施工作业安全行…

大数据新视界 --大数据大厂之AI 与大数据的融合:开创智能未来的新篇章

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

分发饼干00

题目链接 分发饼干 题目描述 注意点 1 < g[i], s[j] < 2^31 - 1目标是满足尽可能多的孩子&#xff0c;并输出这个最大数值 解答思路 可以先将饼干和孩子的胃口都按升序进行排序&#xff0c;随后根据双指针 贪心&#xff0c;将当前满足孩子胃口的最小饼干分配给该孩…

MySQL篇(存储引擎)(持续更新迭代)

目录 一、简介 二、使用存储引擎 1. 建表时指定存储引擎 2. 查询当前数据库支持的存储引擎 三、三种常见存储引擎 1. InnoDB存储引擎 1.1. 简介 1.2. 特点 1.3. 文件格式 1.4. 逻辑存储结构 表空间 段 区 页 行 2. MyISAM存储引擎 2.1. 简介 2.2. 特点 2.3. …

在Windows环境下设置SSH克隆GitHub仓库

在Windows环境下设置SSH克隆GitHub仓库的步骤如下&#xff1a; 1. 生成SSH密钥 打开 Git Bash&#xff08;如果你已经安装了Git&#xff09;。输入以下命令生成SSH密钥&#xff1a;ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 按 Enter 键接受默认文件名…

大数据处理从零开始————3.Hadoop伪分布式和分布式搭建

1.伪分布式搭建&#xff08;不会用&#xff0c;了解就好不需要搭建&#xff09; 这里接上一节。 1.1 伪分布式集群概述 伪分布式集群就是只有⼀个服务器节点的分布式集群。在这种模式中&#xff0c;我们也是只需要⼀台机器。 但与本地模式不同&#xff0c;伪分布式采⽤了分布式…

计算机的错误计算(九十九)

摘要 讨论 的计算精度问题。 计算机的错误计算&#xff08;五十五&#xff09;、&#xff08;七十八&#xff09;以及&#xff08;九十六&#xff09;分别列出了 IEEE 754-2019[1]中的一些函数与运算。下面再截图给出其另外几个运算。 另外&#xff0c;计算机的错误计算&…