spring无侵入自动生成接口文档

2023-10-26

背景

spring cloud多个微服务开发了很多接口,紧急对接前端,需要快速提供一批接口的文档,且不同微服务的接口由多位同事开发且注释非常的少各有不同,现在需要不修改代码不添加注释的情况下能自动的扫描接口并生成文档。本文将详细介绍实现此需求的技术方案。

技术方案

在通过网络搜索后,最终定位到了JApiDocs开源代码,感谢大神开源,此代码基本实现了我想要的,但是需要对源码做些改动。

  • JApiDocs源码:https://github.com/YeDaxia/JApiDocs

  • JApiDocs详细简介:https://japidocs.agilestudio.cn/#/zh-cn/

  • 源码弊端

    • 只扫描class中的接口,不生成interface中的接口
    • 只扫描@xxxxController注解的java文件,我还需要扫描自己实现注解@PaaFeignClient的java文件
    • 接口参数使用的方法注释@param中的,我的很多接口没有注释
    • 接口名使用的方法名,不够直观
    • @RequestParam中未添加is_required的场景spring默认为true,这里有bug
    • 不支持参数中default的支持
    • 参数名未优先使用@RequestParma中的name或value
    • 获取接口type存在bug
  • 我想要的

    • 扫描java文件规则
      • 扫描带有@ResetController或@PaasFeignClient的class或interface文件
    • 扫描要生成的接口规则
      • 扫描带有@RequestMapping注解的方法
      • 接口名使用@RequestMapping的name或value
      • url使用类上@RequestMapping的name或value 和 方法上@RequestMapping的name或value的拼装,在最前面自动拼装了微服务名
      • 接口注释采用接口方法注释中的description内容
    • 扫描接口参数规则
      • 扫描带有@RequstParam参数
      • 参数名使用@RequstParam的name或value
      • 参数是否必须采用@RequstParam的is_required,没有跟spring保持一致默认必须
      • 参数默认值采用@RequstParam的default值,没有为空
      • 参数注释采用接口方法注释@param中的description
      • 参数作者采用类注释中的@author
    • 接口返回值
      • 自动解析返回值PaasResult,如果PaasResult声明返回格式,会自动解析对象,所以最好是声明返回格式

JApiDocs使用方法

  • POM加入依赖
<dependency>
            <groupId>io.github.yedaxia</groupId>
            <artifactId>japidocs</artifactId>
            <version>1.4.3</version>
</dependency>
  • 可以在test里添加生成doc代码,这样每次构建都会自动生成接口文档
@Test
    public void jApiDocTest() throws FileNotFoundException {
        String projectPath = new File(ResourceUtils.getURL("../").getPath()).getAbsolutePath();
        String docPath = new File(ResourceUtils.getURL("../APP-META/doc/all").getPath()).getAbsolutePath();
        System.out.println("projectPath=" + projectPath);
        System.out.println("docPath=" + docPath);
        DocsConfig config = new DocsConfig();
        config.setProjectPath(projectPath); // 项目根目录
        config.setProjectName("paas"); // 项目名称
        config.setApiVersion("V1.0");       // 声明该API的版本
        config.setDocsPath(docPath); // 生成API 文档所在目录
        config.setMvcFramework("spring");
        //config.addJavaSrcPath("E:\\github\\workspace\\paas\\paas-app-ops\\src\\");
        config.setAutoGenerate(Boolean.TRUE);  // 配置自动生成
        config.addPlugin(new MarkdownDocPlugin());
        Docs.buildHtmlDocs(config); // 执行生成文档
    }
  • 正如JApiDocs文档所言,到这里你几乎就可以生成文档了

源码改动

针对上面所说的弊端或者与我需求不同的点,下面是我对源码的改动
实现源码改动不需要下载源码,只需要在自己module中重新实现要改动的相关文件就好了

  • DocContext.java 添加对其他注解的类支持
case SPRING:
                    controllerParser = new SpringControllerParser();
                    Utils.wideSearchFile(javaSrcDir, (f, name) -> f.getName().endsWith(".java") && ParseUtils.compilationUnit(f)
                                                 .getChildNodesByType(ClassOrInterfaceDeclaration.class)
                                                 .stream()
                                                 .anyMatch(cd -> (cd.getAnnotationByName("Controller").isPresent()
                                                         || cd.getAnnotationByName("RestController").isPresent()
                                                         || cd.getAnnotationByName("PaasFeignClient").isPresent())
                                                         && !cd.getAnnotationByName(Ignore.class.getSimpleName()).isPresent())
                            , result, false);
                    controllerFiles.addAll(result);
                    break;
  • RequestNode.java 添加属性 interfaceName
  • ParamNode.java添加属性defaultStr
  • AbsControllerParser.java
    • parse中添加对interface的支持
    public ControllerNode parse(File javaFile) {
    
        this.javaFile = javaFile;
        this.compilationUnit = ParseUtils.compilationUnit(javaFile);
        this.controllerNode = new ControllerNode();
    
        String controllerName = Utils.getJavaFileName(javaFile);
        controllerNode.setClassName(controllerName);
        compilationUnit.getClassByName(controllerName)
                .ifPresent(c -> {
                    beforeHandleController(controllerNode, c);
                    parseClassDoc(c);
                    parseMethodDocs(c);
                    afterHandleController(controllerNode, c);
                });
        if (controllerName.contains("Interface")) {
            compilationUnit.getInterfaceByName(controllerName)
                    .ifPresent(c -> {
                        beforeHandleController(controllerNode, c);
                        parseClassDoc(c);
                        parseMethodDocs(c);
                        afterHandleController(controllerNode, c);
                    });
        }
    
    
        return controllerNode;
    }
    
    • parseMethodDocs 方法 添加requestNode新属性
    private void parseMethodDocs(ClassOrInterfaceDeclaration c) {
        c.findAll(MethodDeclaration.class).stream()
                //.filter(m -> m.getModifiers().contains(Modifier.PUBLIC))
                .forEach(m -> {
    
                    boolean existsApiDoc = m.getAnnotationByName(ApiDoc.class.getSimpleName()).isPresent();
                    if (!existsApiDoc && !controllerNode.getGenerateDocs() && !DocContext.getDocsConfig().getAutoGenerate()) {
                        return;
                    }
    
                    if(shouldIgnoreMethod(m)){
                        return;
                    }
    
                    RequestNode requestNode = new RequestNode();
                    requestNode.setControllerNode(controllerNode);
                    requestNode.setAuthor(controllerNode.getAuthor());
                    requestNode.setMethodName(m.getNameAsString());
                    requestNode.setUrl(requestNode.getMethodName());
                    requestNode.setDescription(requestNode.getMethodName());
                    requestNode.setInterfaceName(requestNode.getMethodName());
    
                    m.getAnnotationByClass(Deprecated.class).ifPresent(f -> {
                        requestNode.setDeprecated(true);
                    });
                    m.getParameters().forEach(p -> {
                    /*
                        p.getAnnotationByName("RequestParam").ifPresent(f -> {
                           ParamNode paramNode = new ParamNode();
                            //  @RequestParam("email") String email
                            if (f instanceof SingleMemberAnnotationExpr) {
                                paramNode.setName(((StringLiteralExpr) ((SingleMemberAnnotationExpr) f).getMemberValue()).getValue());
                                return;
                            }
    
                           // @RequestParam(name = "email", required = true)
                            if (f instanceof NormalAnnotationExpr) {
                                ((NormalAnnotationExpr) f).getPairs().forEach(pair -> {
                                    String exprName = pair.getNameAsString();
                                    if ("value".equals(exprName) || "name".equals(exprName)) {
                                       String exprValue = ((StringLiteralExpr) pair.getValue()).getValue();
                                        paramNode.setName(exprValue);
                                    }
                                });
                            }
    
                            requestNode.addParamNode(paramNode);
                        });*/
                        ParamNode paramNode = new ParamNode();
                        paramNode.setName(p.getNameAsString());
                        paramNode.setDefaultStr(" ");
                        requestNode.addParamNode(paramNode);
                    });
    
                    m.getJavadoc().ifPresent(d -> {
                        String description = d.getDescription().toText();
                        requestNode.setDescription(description);
    
                        List<JavadocBlockTag> blockTagList = d.getBlockTags();
                        for (JavadocBlockTag blockTag : blockTagList) {
                            if (blockTag.getTagName().equalsIgnoreCase("param")) {
                                ParamNode paramNode = requestNode.getParamNodeByName(blockTag.getName().get());
    
                                if (paramNode != null) {
                                    paramNode.setDescription(blockTag.getContent().toText());
                                    //requestNode.addParamNode(paramNode);
                                }
                            } else if (blockTag.getTagName().equalsIgnoreCase("author")) {
                                requestNode.setAuthor(blockTag.getContent().toText());
                            } else if(blockTag.getTagName().equalsIgnoreCase("description")){
                                requestNode.setSupplement(blockTag.getContent().toText());
                            }
                        }
                    });
    
                    m.getParameters().forEach(p -> {
                        String paraName = p.getName().asString();
                        ParamNode paramNode = requestNode.getParamNodeByName(paraName);
    
                        if (paramNode != null && ParseUtils.isExcludeParam(p)) {
                            requestNode.getParamNodes().remove(paramNode);
                            return;
                        }
    
                        if (paramNode != null) {
                            Type pType = p.getType();
                            boolean isList = false;
                            if(pType instanceof ArrayType){
                                isList = true;
                                pType = ((ArrayType) pType).getComponentType();
                            }else if(ParseUtils.isCollectionType(pType.asString())){
                                List<ClassOrInterfaceType> collectionTypes = pType.getChildNodesByType(ClassOrInterfaceType.class);
                                isList = true;
                                if(!collectionTypes.isEmpty()){
                                    pType = collectionTypes.get(0);
                                }else{
                                    paramNode.setType("Object[]");
                                }
                            }else{
                                pType = p.getType();
                            }
                            if(paramNode.getType() == null){
                                if(ParseUtils.isEnum(getControllerFile(), pType.asString())){
                                    paramNode.setType(isList ? "enum[]": "enum");
                                }else{
                                    final String pUnifyType = ParseUtils.unifyType(pType.asString());
                                    paramNode.setType(isList ? pUnifyType + "[]": pUnifyType);
                                }
                            }
                        }
                    });
    
                    com.github.javaparser.ast.type.Type resultClassType = null;
                    String stringResult = null;
                    if (existsApiDoc) {
                        AnnotationExpr an = m.getAnnotationByName("ApiDoc").get();
                        if (an instanceof SingleMemberAnnotationExpr) {
                            resultClassType = ((ClassExpr) ((SingleMemberAnnotationExpr) an).getMemberValue()).getType();
                        } else if (an instanceof NormalAnnotationExpr) {
                            for (MemberValuePair pair : ((NormalAnnotationExpr) an).getPairs()) {
                                final String pairName = pair.getNameAsString();
                                if ("result".equals(pairName) || "value".equals(pairName)) {
                                    resultClassType = ((ClassExpr) pair.getValue()).getType();
                                } else if (pairName.equals("url")) {
                                    requestNode.setUrl(((StringLiteralExpr) pair.getValue()).getValue());
                                } else if (pairName.equals("method")) {
                                    requestNode.addMethod(((StringLiteralExpr) pair.getValue()).getValue());
                                } else if("stringResult".equals(pairName)){
                                    stringResult = ((StringLiteralExpr)pair.getValue()).getValue();
                                }
                            }
                        }
                    }
    
                    afterHandleMethod(requestNode, m);
    
                    if (resultClassType == null) {
                        if (m.getType() == null) {
                            return;
                        }
                        resultClassType = m.getType();
                    }
    
                    ResponseNode responseNode = new ResponseNode();
                    responseNode.setRequestNode(requestNode);
                    if(stringResult != null){
                        responseNode.setStringResult(stringResult);
                    }else{
                        handleResponseNode(responseNode, resultClassType.getElementType(), javaFile);
                    }
                    requestNode.setResponseNode(responseNode);
                    setRequestNodeChangeFlag(requestNode);
                    controllerNode.addRequestNode(requestNode);
                });
    }
    
  • SpringControllerParser.java
    • afterHandleMethod主要是对注解的解析
    protected void afterHandleMethod(RequestNode requestNode, MethodDeclaration md) {
        md.getAnnotations().forEach(an -> {
            String name = an.getNameAsString();
            if (Arrays.asList(MAPPING_ANNOTATIONS).contains(name)) {
                String method = Utils.getClassName(name).toUpperCase().replace("MAPPING", "");
                if (!"REQUEST".equals(method)) {
                    requestNode.addMethod(RequestMethod.valueOf(method).name());
                }
    
                if (an instanceof NormalAnnotationExpr) {
                    ((NormalAnnotationExpr) an).getPairs().forEach(p -> {
                        String key = p.getNameAsString();
                        if (isUrlPathKey(key)) {
                            requestNode.setUrl(Utils.removeQuotations(p.getValue().toString()));
                            requestNode.setInterfaceName(Utils.removeQuotations(p.getValue().toString()));
                        }
    
                        if ("headers".equals(key)) {
                            Expression methodAttr = p.getValue();
                            if (methodAttr instanceof ArrayInitializerExpr) {
                                NodeList<Expression> values = ((ArrayInitializerExpr) methodAttr).getValues();
                                for (Node n : values) {
                                    String[] h = n.toString().split("=");
                                    requestNode.addHeaderNode(new HeaderNode(h[0], h[1]));
                                }
                            } else {
                                String[] h = p.getValue().toString().split("=");
                                requestNode.addHeaderNode(new HeaderNode(h[0], h[1]));
                            }
                        }
    
                        if ("method".equals(key)) {
                            Expression methodAttr = p.getValue();
                            if (methodAttr instanceof ArrayInitializerExpr) {
                                NodeList<Expression> values = ((ArrayInitializerExpr) methodAttr).getValues();
                                for (Node n : values) {
                                    requestNode.addMethod(RequestMethod.valueOf(Utils.getClassName(n.toString())).name());
                                }
                            } else {
                                requestNode.addMethod(RequestMethod.valueOf(Utils.getClassName(p.getValue().toString())).name());
                            }
                        }
                    });
                }
    
                if (an instanceof SingleMemberAnnotationExpr) {
                    String url = ((SingleMemberAnnotationExpr) an).getMemberValue().toString();
                    requestNode.setInterfaceName(Utils.removeQuotations(url));
                    requestNode.setUrl(Utils.removeQuotations(url));
                    requestNode.addMethod("GET");
                }
                // add service name
                String packageName = getControllerNode().getPackageName();
                String preFix = "";
                if (packageName.contains("meta")) {
                    preFix = "/meta/";
                } else if (packageName.contains("rm")) {
                    preFix = "/rm/";
                } else if (packageName.contains("ops")) {
                    preFix = "/ops/";
                }
                requestNode.setUrl(Utils.getActionUrl(getControllerNode().getBaseUrl(), preFix + requestNode.getUrl()));
            }
        });
    
        md.getParameters().forEach(p -> {
            String paraName = p.getName().asString();
            ParamNode paramNode = requestNode.getParamNodeByName(paraName);
            if (paramNode != null) {
    
                p.getAnnotations().forEach(an -> {
                    String name = an.getNameAsString();
    
                    // @NotNull, @NotBlank, @NotEmpty
                    if (ParseUtils.isNotNullAnnotation(name)) {
                        paramNode.setRequired(true);
                        return;
                    }
    
                    if (!"RequestParam".equals(name) && !"RequestBody".equals(name) && !"PathVariable".equals(name)) {
                        return;
                    }
    
                    if ("RequestBody".equals(name)) {
                        setRequestBody(paramNode, p.getType());
                    }
    
                    // @RequestParam String name
                    if (an instanceof MarkerAnnotationExpr) {
                        paramNode.setRequired(true);
                        return;
                    }
    
    
                    //  @RequestParam("email") String email
                    if (an instanceof SingleMemberAnnotationExpr) {
                        paramNode.setRequired(Boolean.TRUE);
                        paramNode.setName(((StringLiteralExpr) ((SingleMemberAnnotationExpr) an).getMemberValue()).getValue());
                        return;
                    }
    
                    // @RequestParam(name = "email", required = true)
                    if (an instanceof NormalAnnotationExpr) {
                        boolean required_flag = false;
                        for (MemberValuePair pair : ((NormalAnnotationExpr) an).getPairs()) {
                            String exprName = pair.getNameAsString();
                            if ("required".equals(exprName)) {
                                required_flag = true;
                                Boolean exprValue = ((BooleanLiteralExpr) pair.getValue()).getValue();
                                paramNode.setRequired(Boolean.valueOf(exprValue));
                            } else if ("value".equals(exprName) || "name".equals(exprName)) {
                                String exprValue = ((StringLiteralExpr) pair.getValue()).getValue();
                                paramNode.setName(exprValue);
                            } else if ("defaultValue".equals(exprName)) {
                                String exprValue = ((StringLiteralExpr) pair.getValue()).getValue();
                                paramNode.setDefaultStr(exprValue);
                            }
                        }
                        // @RequestParam(name = "email") 省略require的场景
                        if (!required_flag) {
                            paramNode.setRequired(Boolean.TRUE);
                        }
                    }
                });
    
                //如果参数是个对象
                if (!paramNode.isJsonBody() && ParseUtils.isModelType(paramNode.getType())) {
                    ClassNode classNode = new ClassNode();
                    ParseUtils.parseClassNodeByType(getControllerFile(), classNode, p.getType());
                    List<ParamNode> paramNodeList = new ArrayList<>();
                    toParamNodeList(paramNodeList, classNode, "");
                    requestNode.getParamNodes().remove(paramNode);
                    requestNode.getParamNodes().addAll(paramNodeList);
                }
            }
        });
        // add action param
        ParamNode paramNode = new ParamNode();
        paramNode.setName("Action");
        paramNode.setType("string");
        paramNode.setRequired(true);
        paramNode.setDescription("固定值:" + requestNode.getInterfaceName());
        paramNode.setDefaultStr(" ");
        requestNode.addParamNode(0, paramNode);
    }
    

配置文件

效果

  • md文件输出如下
    在这里插入图片描述

  • html文件输出如下
    在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

spring无侵入自动生成接口文档 的相关文章

  • Hibernate中通过ID获取对象

    我注意到我们的高级开发人员使用以下代码通过 ID 检索实体 Override public Source get Long id Session session getSession if session null session sess
  • 使用 ActionBar 选项卡进行导航时菜单会折叠

    我已经使用支持库中的 ActionBar 来将我的应用程序构建为选项卡式导航栏 我的应用程序中有两个选项卡 这两个片段都有菜单 并且有一个菜单项 我想将其显示为操作栏中的一项操作 但由于某种原因 显示了溢出图标 而不是分配给这些项目的图标
  • maven依赖插件忽略依赖版本? [复制]

    这个问题在这里已经有答案了 在我看来 maven 依赖插件在计算依赖列表时行为不当 假设这 3 个项目 base1
  • 如何在java中使用模式匹配器?

    假设字符串是我想提取xyz从字符串中出来 我用了 Pattern titlePattern Pattern compile lttitle gt s s lt title gt Matcher titleMatcher titlePatte
  • 使用 Spring 通过构造函数自动装配集合

    我有一个看似简单的问题 如标题所示 这是我的课程类型 public class Foo Autowired public Foo Qualifier bar Set
  • 根本原因 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver

    我有这个小代码用于将我的 jsp 连接到我的 mysql 数据库 String driver com mysql jdbc Driver Class forName driver String url jdbc mysql localhos
  • Java util Logger 未写入文件

    我为我的应用程序编写了一个愚蠢的记录器 它将信息 警告和严重消息写入日志文件 前一段时间它工作得很好 但突然间 它无缘无故地停止工作了 如果日志文件不存在 它会继续创建该文件 但不写入任何内容 我找不到它现在不起作用的原因 我会感谢一些帮助
  • 通过 jclouds 使用 AWS (S3) - 如何承担角色

    使用普通身份验证凭据时 我可以执行以下操作 ContextBuilder newBuilder aws s3 credentials keyId key buildView BlobStoreContext class 访问 S3 的 Bl
  • Java + JNA:找不到指定的过程

    我正在尝试使用 Visual Studio 创建一个 dll 文件并在 java 项目中使用 访问它 该库似乎已加载 但总是抛出相同的异常 线程 main 中出现异常 java lang UnsatisfiedLinkError 查找函数
  • Hibernate + Oracle IN 子句限制,如何解决?

    我知道这个问题已经发了很多次了 但我想问一下细节 使用 Oracle 您不能向 IN 子句传递超过 1000 个参数 因此将 hibernate 与 Oracle 一起使用可能有一些解决此问题的方案 例如 1 对于每个 1000 个参数列表
  • 从已排序的 ArrayList 中删除重复项,同时保留重复项中的某些元素

    好吧 一开始我以为这会很简单 但我想不出有效的方法来解决这个问题 我想出了一种蛮力的方法来解决这个问题 但这不是很优雅 我有一个数组列表 Contacts 是一个 VO 类 有多个成员 名称 区域 id ArrayList中存在重复项 因为
  • 如何使用java.util.concurrent包实现后台线程?

    这是我首先使用的代码 但在最新的 Android 版本中AsyncTask类已被弃用并且 因此它没有响应 然后我使用了Thread类 但该类也不起作用 我想要与我得到的结果相同的结果AsyncTask班级 我知道我必须使用 java uti
  • java.lang.NoSuchFieldError:APPLICATION_CONTEXT_ID_PREFIX

    我在运行项目时收到此错误 最终结果为 404 该项目是在Spring框架上进行的 我读了很多帖子 发现要么是混合了罐子 要么是多余的罐子 接下来我尝试整理我的罐子 以下列表是我的构建路径中的内容 antlr 2 7 6 jar asm ja
  • CoreNLP 如何识别小写的命名实体,例如 kobe bryant?

    我遇到一个问题 CoreNLP 只能识别以大写字符开头的命名实体 例如科比 布莱恩特 Kobe Bryant 但无法识别科比 布莱恩特 kobe bryant 作为一个人 那么CoreNLP如何识别以小写字符开头的命名实体 赞赏它 首先 您
  • 如何使用 Spring 状态机在状态转换期间引发异常

    我试图了解状态转换期间操作如何抛出异常 我配置了这个简单的状态机 transitions withExternal source State A1 target State A2 event Event E1 action executeA
  • 如何正确关闭资源

    当我清理一些代码时 FindBugs 向我指出了一些使用 Connection CallableStatement 和 ResultSet 对象的 JDBC 代码 这是该代码的一个片段 CallableStatement cStmt get
  • 如何在 Mulesoft 中将睡眠设置为流程而不丢失消息负载

    我想插入脚本来延迟 Mulesoft 中的处理流程 我尝试在 groovy 中插入脚本 但丢失了消息有效负载 因此当我必须获取消息有效负载时 收到了空指针 我怎样才能不丢失消息有效负载 Thanks 如果您正在使用Groovy流程中的组件
  • Fragment中有类似setResult()的方法吗?

    我正在使用一个片段 我收到错误onResult 方法 我需要一个替代方法setResult RESULT OK data 我可以在我的片段中使用它 请帮忙 日历片段 package app pal study samplestudy imp
  • 使用 Android API 发布推文

    我一直在寻找一种使用 Android 应用程序发布推文的方法 但我发现的所有方法都不起作用 我不得不承认 Twitter 的 API 并不是那么容易理解 但是我的代码并不长 而且我看不出我的错误在哪里 这是我的代码 public class
  • Java Webstart 和 URLConnection 缓存 API

    的描述URLConnection 缓存 API http docs oracle com javase 6 docs technotes guides net http cache html最后一句指出 Java 2 标准版中没有 URLC

随机推荐

  • Mysql进阶索引篇02——InnoDB存储引擎的数据存储结构

    前言 前面我们已经剖析了mysql中InnoDB与MyISAM索引的数据结构 了解了B 树的设计思想 原理 并且介绍了B 树与Hash结构 平衡二叉树 AVL树 B树等的区别和实际应用场景 页和页之间并不一定在物理上相连 只是在逻辑上使用双
  • java内存

    在java视频中 一直强调java内存的重要性 如果真正理解 了java内存的分配情况和程序运行时的java内存 那么你会对 java编程的思想 会更加深刻 Java内存分配与管理是Java的核心技术之一 Java的内存分配有三种 一 静态
  • 使用WPD API操作MTP设备一些总结

    使用WPD API操作MTP设备总结 本文分为两部分 1 WPD基本架构和概念的理解 2 使用WPD API操作MTP 拷贝 删除 设备 1 WPD基本架构和概念 1 1 WPD架构 原文 https docs microsoft com
  • Vue脚手架的安装配置以及使用

    安装Vue脚手架 1 需要安装nodejs支持 去nodejs官网下载对应版本的nodejs 可以使用installer 选择安装目录点击安装 也可以使用binary文件 直接选择文件夹解压缩 安装完成后如上图所示 然后配置环境变量 1 添
  • C++中指针和应用有哪些区别?

    a 指针是一个新的变量 存储了另一个变量的地址 我们可以通过访问这个地址来修改另一个变量 引用只是一个别名 还是变量本身 对引用的任何操作就是对变量本身进行操作 以达到修改变量的目的 b 引用只有一级 而指针可以有多级 c 指针传参的时候
  • show processlist 命令执行结果解释

    show full processlist show processlist 显示哪些线程正在运行 也可以通过 INFORMATION SCHEMA PROCESSLIST 表或 mysqladmin processlit 获取这些信息 如
  • 设计模式-状态模式(State)

    文章目录 前言 状态模式的核心概念 状态模式的用途 示例 状态模式的Java实现 状态模式优缺点 总结 前言 当我们需要在对象的生命周期中管理不同状态时 状态模式 State Pattern 是一种有用的设计模式 在这篇博客中 我们将介绍状
  • 免费的 PPT 模版资源

    1 第一 PPT 第一PPT站内资源以免费下载为基础 本着开放的共享为原则 服务于国内广大国内PPT爱好者 目前第一PPT站内的所有PowerPoint资源 PPT模板 PPT背景 PPT 素材 PPT教程 PPT软件 均是免费下载 所以请
  • openVPN服务端搭建

    搭建步骤 云服务器 Ubuntu 20 04 1 LTS 搭建服务端 公网IP 47 215 测试客户端 部门内部成员的windows10 或者windows11 及mac电脑 还有现场linux环境 最后目标是实现所有客户端之间能够互联
  • Electron桌面程序开发入门

    1 Electron结合vue项目配置 Electron是利用web前端技术进行桌面应用开发的一套框架 是由 github 开发的开源框架 允许开发者使用 Web 技术构建跨平台的桌面应用 它的基本结构 Electron Chromium
  • Vuluhub靶场-breach1

    网络设置和准备 该靶场的ip 192 168 110 140 我们要设置为仅主机模式 在虚拟机中将仅主机模式的ip地址范围包含靶机的ip 除了网络设置 还要准备两台kali 一台连接外网 一台和靶机一样要仅主机模式 信息收集 Nmap扫描
  • lvgl 自定义控制表格行高、颜色和外框样式

    lvgl 自定义控制表格行高 颜色和外框样式 lvgl版本 8 3 7 lvgl自带表格控件能够指定列宽 但是表格行高是根据内容动态渲染的 表格自带样式如图 带有蓝色的外框和白底 如果想要手动控制表格行高 颜色和外框等属性 需要监听表格绘制
  • 国产加密实际运用:使用SM3加盐存储密码,并且使用SM2进行登录认证

    目录 1 简要 2 开发环境及工具 3 后台密码加密部分 3 1加密代码 3 2 SM3加密类 Sm3crypto 3 3国密SM3工具类 Sm3Utils 3 4国密相关依赖包 4 登录认证部分 4 1前端部分关键代码 4 2后端logi
  • 查看tensorflow是否支持GPU,以及测试程序

    测试程序 Python import tensorflow as tf hello tf constant Hello TensorFlow sess tf Session print sess run hello 是否支持GPU impo
  • 【新手入门篇】React+ant design

    本篇着重讲解如何使用官方的demo 至于React及antd的安装及配置在本文末尾会给出相应的参考链接 创建一个React项目之后 create react app 你的项目名 在新建的项目目录下引入antd组件库 yarn add ant
  • Ubuntu 23.10 支持基于 TPM 的全磁盘加密

    将于下个月发布的 Ubuntu 23 10 增加了一项实验性功能 初步支持基于 TPM 的全磁盘加密 该功能利用系统的可信平台模块 TPM 缺点是这种额外的安全性依赖于 Snaps 包括内核和 GRUB 引导加载器 Ubuntu 开发商 C
  • 输出该单链表的中间结点的值,如果链表长度为偶数,则输出中间靠右的结点

    输出该单链表的中间结点的值 如果链表长度为偶数 则输出中间靠右的结点 题目要求 输入数据创建一个单链表 实现一种算法 输出该单链表的中间结点的值 如果链表长度为偶数 则输出 中间靠右 的结点 如果链表只有一个元素 则输出唯一的元素 算法思路
  • 【华为机试真题 JAVA】水果搬运问题-200

    题目描述 一组工人搬运一批水果 用一维数组存储工人编号和水果名称以及搬运重量 要求先按水果分组 然后按搬运重量排序输出 输入描述 第一行包括一个整数 N 1 N 100 代表工人的个数 接下来的 N 行每行包括两个整数 p 和 q 分别代表
  • 关于STM32的SPI使用DAM首发的回调问题

    本人第一次使用HAL库 然后用SPI操作FLAH 担心数据量大 于是打算使用DMA 之前是用的LL库 然后发现了一个问题 SPI怎么都接收不到数据 想了一下应该是片选引脚的问题 我应该在DMA传输结束时关闭引脚 但是之前都是用LL库 判断标
  • spring无侵入自动生成接口文档

    背景 spring cloud多个微服务开发了很多接口 紧急对接前端 需要快速提供一批接口的文档 且不同微服务的接口由多位同事开发且注释非常的少各有不同 现在需要不修改代码不添加注释的情况下能自动的扫描接口并生成文档 本文将详细介绍实现此需