vivo 互联网自研代码评审 VCR 落地实践

作者:vivo 互联网效能平台团队- Chi Wei

本文介绍了vivo工程效能团队基于 Gitlab、Gerrit等开源工具搭建的VCR平台,代码评审idea插件开发及开发过程中遇到的挑战、困难,并分享了相应的应对策略和优化方案。

代码评审是软件质量保证一种活动,由一个或者多个人对一个程序的部分或者全部源代码进阅读理解。一般来说分为作者和评审者两种角色,作者方提供代码逻辑的介绍和代码,评审者则对提供的代码基于设计,功能性和非功能性等方面认知进行阅读并提出问题。常见的评审组织形式是有同行评审(Peer Review)和小组检查 (Team Inspection)两种方式。

在代码评审中,评审的目的在通过代码的评审发现潜在的问题,同时分享和表达是代码评审的重要收获,我们知道人相同在不同的文化下生产力是不同的,代码评审是一个工具,工具受文化的影响的同时也影响着文化,最终朝着我们希望的责任共担、持续改进的方向发展。

一、代码评审演进

图片

随着互联网的发展,开发人员也越来越重视代码评审带来的代码的代码质量提高以及代码评审间接带来的分享及人员备份效果,已经不满足于只是简单的发现当前问题解决问题记录问题,需要满足从评审基本跟进、评论管理、评审报告以及评审方式多样化、评审与研发流程相结合等需求。

① 代码评审检查表:手工定义要检查项,检查完进行打卡标记结果。

② 插件快速评审导入导出:快速在插件上进行评论,并将评论结果导出给被评审人,被评审人导入评审结果查看,评审表不可复用,一旦代码变更则无法准确定位、也无法再次跟踪评审修改结果。

③ 在线代码评审:在线插件或网页评审,提供提交前提交后评审,可多人评审策略管控、代码评审与需求/缺陷关联管理。

④ 自动化代码评审:结合现有的Sonar扫描、安全扫描进行对提交的代码进行自动化检查,使代码在人工评审之前已经经历一轮自动评审,代码评审通过之后可自动触发构建、部署等。

⑤ 智能化代码评审:根据AI大模型,可对提交代码进行综合评价(编码标准、可用性、可读性、可维护性、安全性、高性能、异常控制、设计原则、可扩展性、代码复杂度等等)并给出相关测试建议等,未来大模型对代码评审还有更大的空间。

二、代码评审解决的需求和痛点是什么?

vivo当前已经有EasyCR评审工具,那为什么我们还需要继续开发调研代码评审工具呢?

我们先看看下面通过内部调研获取的信息,看看用户希望的代码评审工具需求和痛点是什么?

图片

针对当前vivo代码评审工具我们继续升级补充场景:

  1. 增加评审方式:对原自由评审方式(主要是提交后进行代码评审)增加评审控制方式(提交代码至仓库前进行代码评审、合并时提交代码评审)。

  2. 支持网页/插件:增加网页端评审功能,满足不同角色进行评审及用户体验上的优化,增强插件版评审功能。

  3. 支持研发流程控制:上线过程中可作为人工卡点一项检查项(可通过代码是否评审、代码评分、代码问题解决情况等进行判断),通过线上管理,提高上线质量。

  4. 支持自动化检查:代码提交前,提交后可进行代码自动化检查,对代码进行自动评审。

  5. 增加用户定制化需求:如评审权限、评审通知方式、评审策略多人评审管理、评审报告订阅等。

当前市场上有很多优秀的代码评审工具,但是很少有评审工具能满足所有的场景,角色不同,需要的能力不同,同一个角色不同团队使用的方式不同,我们需要一款解决用户痛痒爽的代码评审工具。

三、vivo代码评审系统架构

图片

四、vivo代码评审工具使用流程

在代码评审中,CR可以是一次Commit,也可以是一次MergeCommit,那么针对一次CR我们可以随时对已经提交的commit进行评审,也可以在CRpush至代码库之前拦截,同时也可以在一次合并之前进行代码评审。

代码评审模式:

1. 提交前评审(Pre-push Code Review)

2. 提交后评审(Post-push Code Review)

    ① 合并评审

    ② 自由评审

图片

提交前评审:VCR基于VCR在提交push至Gitlab代码仓库之前,对代码进行拦截,并进行评审,支持一次评审请求作为一次评审,可对一次一次评审请求查看所有变更记录并进行评审追踪。利用开源工具Gerrit,将评审请求推送至Gerrit中,评审通过后,将代码从Gerrit同步至Gitlab仓库

提交后评审

①合并评审:VCR基于Gitlab 在一次MR的基础上进行代码评审。

②自由评审:针对用户当前代码库当前分支信息或历史commit进行评审。

五、vivo代码评审工具实施

5.1 确认技术架构

提交仓库前进行代码评审,我们使用当前成熟的代码评审Gerrit,实施过程中最大的问题是用户如何低成本切换及简单评审的问题,对于当前Gerrit评审工具遇到的问题如何解决呢?

1) 我们知道Gerrit评审工具需要提供给用户Gerrit代码库地址,并进行下载使用,当前用户使用的代码库习惯不能更改,也是不愿意修改的,那么我们如何解决呢?

给插件加持,提供用户黑盒切换至评审代码库,或执行一键下载代码库功能,底层使用Gerrit与代码托管库同步机制解决代码一致性问题,用户在使用代码库时同原使用方式一致。

图片

2) Git代码提交,CR为最小单位,CR可作为一次评审,但还有很多用户使用的习惯是一次push作为一次评审,如何解决用户一次push为一次评审呢?

a)需要对代码关系链需要进行整理,识别出一次push作为一次评审记录,用户多次追加提交记录至评审请求,需要重新识别出关系链作为原push请求的评审记录,Git原生对代码变更的情况比较多,我们对一些场景进行分析再特殊处理,不穷举。

图片

b)可对最小粒度CR的评审,也同时提供一次push请求内容进行评审,更方便快捷。

用户不管是提交前评审、合并时评审,都可能会产生一次push,多次commit,用户需要对最小粒度CR评审,也需要对最新变更所有内容进行评审。

5.2 插件改造实施

根据我们对用户的调研过程中,用户对代码评审插件网页同时兼容的要求比较高,针对idea插件我们如何改造代码评审,这里我们着重对Gerrit插件改造展开说明。

步骤1:了解插件框架、配置、打包、运行

1)插件框架整体介绍

图片

(图片来源于网络)

  • 开发方式:在官网的描述中,创建IDEA插件工程的方式有两种分别是使用DevKit(IntelliJ Platform Plugin 模版创建)和Gradle构建方式,这两种方式在构建项目和打包发布上有所区别,同时官方提供了将Devkit迁移至Gradle的方式。参考:Developing a Plugin | IntelliJ Platform Plugin SDK

  • 框架入口:一个 IDEA 插件开发完,要考虑把它嵌入到哪,比如是从 IDEA 窗体的 Edit、Tools 等进入配置还是把窗体嵌入到左、右工具条还是IDEA窗体下的对话框。

  • UI:思考的是窗体需要用到什么语言开发,没错,用的就是 Swing、Awt 的技术能力。

  • API:在 IDEA 插件开发中,一般都是围绕工程进行的,那么基本要从通过 IDEA 插件 JDK 开发能力中获取到工程信息、类信息、文件信息等。

  • 外部功能:这一个是用于把插件能力与外部系统结合,比如你是需要把拿到的接口上传到服务器,还是从远程下载文件等等。

 2)Gradle创建

新版通过 New-> Project->IDE Plugin进行创建,旧版通过New Project->Gradle->IntelliJ Platform Plugin进行创建。

项目结构如下:

图片

3)配置介绍

  • plugin.xml

<!DOCTYPE idea-plugin PUBLIC "Plugin/DTD" "http://xxxx">
<idea-plugin>
  <!-- 插件唯一id,不能和其他插件项目重复,所以推荐使用包名+插件名com.xxx.xxx的格式
       插件不同版本之间不能更改,若没有指定,则与name相同 -->
    <id> com.your.company.unique.plugin.id </id>
<!-- 插件名称,别人在官方插件库搜索你的插件时使用的名称 -->
    <name> Plugin display name here </name>
<!-- 插件版本,格式:BRANCH.BUILD.FIX (MAJOR.MINOR.FIX) -->vs
    <version>1.0.0</version>
<!-- 供应商主页和email(不能使用默认值,必须修改成自己的)-->
   <vendor email="support@yourcompany.com" url="https://www.yourcompany.com">YourCompany</vendor>

  <!-- 插件的描述 (不能使用默认值,必须修改成自己的。并且需要大于40个字符)-->
  <description><![CDATA[
  Enter short description for your plugin here.<br>
  <em>most HTML tags may be used</em>
]]></description> 
    <!-- 插件版本变更信息,使用<![CDATA[  ]]> 来支持HTML格式;
       将展示在 settings | Plugins 对话框和插件仓库的Web页面 -->
    <change-notes><![CDATA[
      <p>
          <li>1.0.0</li>
          <ul>
            <li>
             1.新增xxx功能 <br/>
             2.优化xxx功能 <br/>
            </li>
          </ul>
      </p>
     ]]>
    </change-notes>

    <!-- please see http://confluence.jetbrains.net/display/IDEADEV/Build+Number+Ranges for description -->
   <!-- 插件兼容构建的IDE版本, until-build可以不写,默认到最新版 -->
   <idea-version since-build="203.4818.26" until-build="211"/>


    <!-- please see http://confluence.jetbrains.net/display/IDEADEV/Plugin+Compatibility+with+IntelliJ+Platform+Products
         on how to target different products -->
  <!-- 插件依赖,可以依赖模块或插件 -->
    <depends>com.intellij.modules.lang</depends>
    <depends>Git4Idea</depends>
    <depends optional="true" config-file="plugin-maven.xml">org.jetbrains.idea.maven</depends>

<!—idea第一次打开, 实际上就是订阅了应用程序打开的事件-->
    <application-components>
        <component>
            <implementation-class>xxxxx</implementation-class>
        </component>
    </application-components>
<!—打开项目 -->
    <project-components>
        <component>
            <implementation-class>
                xxxxx
            </implementation-class>
        </component>
    </project-components>
<!-- 插件定义的扩展点,以供其他插件扩展该插件,类似Java的抽象类的功能 
     如何在https://plugins.jetbrains.com/docs/intellij/plugin-extensions.html -->
   <extensionPoints>
   </extensionPoints>


<!-- 声明该插件对IDEA core或其他插件的扩展,Ns是NameSpace的缩写 -->
    <extensions defaultExtensionNs="com.intellij">
        <toolWindow id="代码评审" icon="/icons/xx_13x13.png" anchor="bottom" factoryClass="xxx" />
    </extensions>
    <!-- 编写插件动作 https://plugins.jetbrains.com/docs/intellij/plugin-actions.html-->
    <actions>
     <action id="com.xx.xx.AddCommentAction"
        class="com.xx.xx.actions.AddCommentAction"
        text="添加评论"
        description="为选中的代码添加评论意见"
        icon="AllIcons.Actions.StartDebugger"> 
    <!—编辑器右键弹出菜单--!>
    <add-to-group group-id="EditorPopupMenu" anchor="first"/>
    <!--快捷方式--!>
    <keyboard-shortcut first-keystroke="alt X" keymap="$default"/> </action>
    </action>
    </actions>
</idea-plugin>

4)插件运行调试打包安装

Gradle构建方式进行调试打包安装

  • 运行/调试:runIde 可以选择Debug模式或者是Run模式

图片

  • 打包

图片

  • 安装:可以将打的包发布市场(本地idea配置插件仓库),从Marketplace搜索插件或者是直接从Settings->plugins->Install->Install Plugin from Disk安装

图片

步骤2:研究Gerrit插件源码,搞清楚整理开发流程和模块

图片

步骤3:基于Gerrit插件规划VCR插件模块,增加clone、branch、mergeRequest、VCR模块,并对各组件增强

图片

步骤4:定制原有流程模块push,自动化关联工作项

图片

图片

在使用Git依赖插件之前,先了解一下插件的扩展以及扩展点(Extensions、Extension Points)。

Intellij 平台提供了允许一个插件与其他插件或者 IDE 交互的 extensions 以及 extension points 的概念。

  • Extension Points:如果你想要你的插件可以被其他插件使用,那么你必须在你的插件内声明一个或多个扩展点(extension points)。每个扩展点定义了允许访问这个点的类或者接口。

  • Extensions:如果你想要你的插件扩展其他插件或者 Intellij 平台,你必须声明一个或多个 extensions。

可以在 plugin.xml 中的和块中定义 extensions 以及 extension points。

图片

  • plugin.xml

<!--依赖插件包--!>
<depends>Git4Idea</depends>
<!—idea第一次打开, 实际上就是订阅了应用程序打开的事件-->
<application-components>
<component>
<implementation-class>com.demo.intellij.plugin.vcr.push.VcrPushExtension$Proxy</implementation-class>
</component>
</application-components>

上述我们看到依赖的Git4Idea 包,如果我们想修改原生的的Git,先看下push依赖包中如何实现的。

  • Git4Idea(plugin.xml)

<extensions defaultExtensionNs="com.intellij">

<pushSupport implementation="git4idea.push.GitPushSupport"/>
...
</extensions>
  • intellij-dvcs.jar(plugin.xml)
<extensionPoints>
  <extensionPoint name="pushSupport"
                  interface="com.intellij.dvcs.push.PushSupport"
                  area="IDEA_PROJECT"
                  dynamic="true"/>
....
</extensionPoints>

从上述可看到,Git4Idea 的GitPushSupport扩展实现push的功能点,接下来我们主要对GitPushSupport进行javassist字节码修改以达到扩展git push组件能力。

扩展使用GitPushSupport之前,需要将需要的类进行装载至GitPlugin中,然后再对GitPushSupport进行字节码改造,至此对git Push原生插件页进行改造。

图片

步骤5:使用树状列表模式,展示一次push请求VCR提交内容及多个CR情况

主要是实现JTreeTable,对VCR与CR进行管理。

一次评审请求VCR包含所有CR的提交变更记录,可针对该变更记录进行代码评审,单个CR也可以进行评审。

图片

步骤6:展示变更文件视图及定制评论展示模块,精准定位代码

代码评审主要根据编辑器获取代码行及位置,评论可精准定位到代码行。

图片

1)changeBrowser变更视图展示VCR变更文件信息

图片

2)双击文件,diff视图展示inline和side-by-side两种代码差异

声明扩展,针对扩展类进行定制化改造。

  • plugin.xml

<diff.DiffTool implementation="com.demo.intellij.plugin.vcr.ui.diff.VcrCommentsDiffTool$Proxy"/>

图片

3)添加代码块评论,定位代码块

  • AddCommentAction.java

public class AddCommentAction extends AnAction implements DumbAware {

public AddCommentAction(String label,
                        Icon icon,
                        CommentsDiffTool commentsDiffTool,                 
                        Editor editor, 
                        List<CommentInfo> fileComments
                        ....
                        ) {
    super(label, null, icon);
}
private CommentInput createComment() {
//获取用户选择代码位置位置

//行的情况下,默认是开头和行结束  得到光标的位置caretModel.getOffset();
/*取到插字光标模式对象 CaretModel caretModel = editor.getCaretModel();
得到光标的位置int caretOffset = caretModel.getOffset();
//得到一行开始和结束的地方
int lineNum = document.getLineNumber(caretOffset);
int lineStartOffset = document.getLineStartOffset(lineNum);
int lineEndOffset = document.getLineEndOffset(lineNum);
获取一行内容String lineContent = document.getText(new TextRange(lineStartOffset, lineEndOffset));
*/
Document document = editor.getDocument();
int lineNum  = document.getLineNumber(editor.getCaretModel().getOffset()) ;
int lineStartOffset = document.getLineStartOffset(lineNum);
int lineEndOffset = document.getLineEndOffset(lineNum);
String lineContent = document.getText(new TextRange(lineStartOffset, lineEndOffset));
.....
}
}

所有评论展示列表如何精准定位代码

  • SafeHtmlHistoryComments.java

public class SafeHtmlHistoryComments extends JPanel {
    private Iterable<CommentInfo> fileComments;
    private List<CommentInfo> commentInfos = new ArrayList<>();
    private CommentInfo currentCommentInfo;
    private SelectedComment selectedComment;
    private SelectedComment operatorSelectedComment;
    private Editor editor;
    public SafeHtmlHistoryComments(Editor editor,Iterable<CommentInfo> fileComments, Comment selectedComment) {
        super(new BorderLayout());
        ....
      HistoryCommentListPanel historyCommentListPanel = new HistoryCommentListPanel(fileComments);
       
        //双击table某行触发代码定位
        historyCommentListPanel.addTableMouseDoubleHit(new Consumer<CommentInfo>() {
            @Override
            public void consume(CommentInfo commentInfo) { 
                codeTextHit(editor,commentInfo);
            }
        });
    }

    /**
     * 定位代码
     * @param editor
     * @param commentInfo
     */
    private static void codeTextHit(Editor editor, CommentInfo commentInfo) {
        SelectionModel selectionModel = editor.getSelectionModel();
        // 优化:如果文件修改过了,则不进行选中操作,换为提示
        if (null != commentInfo.startIndex && null != commentInfo.endIndex && commentInfo.startIndex != 0 && commentInfo.endIndex != 0) {
            editor.getCaretModel().moveToOffset(commentInfo.endIndex);
            selectionModel.setSelection(commentInfo.startIndex, commentInfo.endIndex);
        } else if (null != commentInfo.line && commentInfo.line != 0) {
            int lineNum = commentInfo.line - 1;
            editor.getCaretModel().moveToOffset(lineNum);
            CharSequence charsSequence = editor.getMarkupModel().getDocument().getCharsSequence();
            if(null!=commentInfo.range) {
                RangeUtils.Offset offset = RangeUtils.rangeToTextOffset(charsSequence, commentInfo.range);
                selectionModel.setSelection(offset.start, offset.end);
            }else{
                Document document = editor.getDocument();
                int lineStartOffset = document.getLineStartOffset(lineNum);
                int lineEndOffset = document.getLineEndOffset(lineNum);
                selectionModel.setSelection(lineStartOffset, lineEndOffset);
            }
        }
        editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
    }
....
}

六、未来展望

6.1 自动化代码评审

  1. 代码提交评审或代码合并之前,先自动化检查(Sonar/安全扫描)快速发现并纠正潜在问题,检查成功后提交评审。

  2. 代码评审通过之后,结合流水线,自定义部署构建策略,实现快速迭代。

  3. 自动汇聚测试报告,根据评审问题类型进行分类,不断改进Sonar检查规则,从而形成良性循环。

6.2智能化代码评审

  1. 提交代码评审之后,通过AI大模型对代码进行综合评价,并给出建议。

  2. 通过智能代码评审,产生评审报告,并进行智能化分析。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

5年成为10亿级大单品,海天如何策划黄豆酱的极致产品力?

在当今的调味品市场中&#xff0c;酱料作为一个重要的细分品类&#xff0c;正迅速成为消费者日常饮食中不可或缺的一部分。随着人们对烹饪品质和口味多样化的追求不断提升&#xff0c;酱料市场的前景愈发广阔。而在这片广阔的市场中&#xff0c;海天味业&#xff0c;作为中国调…

ElasticSearch-Windows系统ElasticSearch(ES)的下载及安装

前言 下载ElasticSearch 可以进入ElasticSearch官方下载地址&#xff0c;选择与电脑系统相对应的版本&#xff1b;博主已经上传资源&#xff0c;或者点此直接免费下载&#xff0c;本次演示版本为8.14.1。 注意&#xff1a; Elasticsearch 5 需要 Java 8 以上版本&#xff1b;…

Excel 宏录制与VBA编程 ——VBA编程技巧篇一 (Union方法、Resize方法、Cells方法、UseSelect方法、With用法)

Uniom方法 使用Union方法可以将多个非连续区域连接起来成为一个区域&#xff0c;从而可以实现对多个非连续区域一起进行操作。 Resize方法 使用Range对象的Resize属性调整指定区域的大小&#xff0c;并返回调整大小后的单元格区域。 Cells方法 Cells属性返回一个Range对象。 Us…

基于Java的大学生租房系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot框架&#xff0c;MVC模式 工具&#xff1a;Vscode&#xff0c;MySQL&#xff0c;B/S架构…

计算机网络之OSI七层体系结构

目录 1.物理层 1.1物理层组成 1.2物理层功能 1.3物理层服务 1.4物理层标准 1.5物理层接口 2.数据链路层 2.1基于物理层的问题 2.2数据链路层功能 2.3数据链路层服务 2.4数据链路层协议 3.网络层 3.1基于DL层的问题 3.2网络层功能 3.3网络层服务 3.4网络层协议 …

根据肥胖类型选择减调方向收获窈窕身材

我们生活中胖子很多&#xff0c;从胖到瘦的人也不少&#xff0c;但瘦了后对自己身材满意的人却是不多的&#xff0c;很多人瘦了也只是减掉了身上的赘肉而已&#xff0c;大体的身形却是没有变化的&#xff0c;因此&#xff0c;并不感到满意。因为他们本身的形体是固定的&#xf…

拼多多面试总结

文章目录 一面自我介绍提问算法反问结果 二面提问算法反问结果 主管面主管面试准备算法题其他个人提问准备 提问数据库普通索引和覆盖索引的区别索引是什么&#xff1f;索引怎么加快数据库查询的&#xff1f;索引具体怎么实现的&#xff1f;以B树为例&#xff0c;节点放了什么&…

java生成excel,uniapp微信小程序接收excel并打开

java引包&#xff0c;引的是apache.poi <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.3</version></dependency> 写一个测试类&#xff0c;把excel输出到指定路径 public s…

leetcode494. 目标和

1.思想方法 2.代码 class Solution { public int findTargetSumWays(int[] nums, int target) {int sum 0;for(int num : nums)sum num;if(sum < Math.abs(target) || (targetsum)%2 ! 0)return 0;int x (targetsum) / 2,n nums.length;//基于滚动数组的方法int[] dp…

JavaWeb-day28_HTML

今日内容 零、 复习昨日 一、HTML 零、 复习昨日 一、Web开发 前端三大件 HTML ,页面展现CSS , 样式JS (JavaScript) , 动起来 二、HTML 2.1 HTML概念 ​ 网页&#xff0c;是网站中的一个页面&#xff0c;通常是网页是构成网站的基本元素&#xff0c;是承载各种网站应用的平台…

MySQL高级-SQL优化- count 优化 - 尽量使用count(*)

文章目录 1、count 优化2、count的几种用法3、count(*)4、count(id)5、count(profession)6、count(null)7、 count(1) 1、count 优化 MyISAM引擎把一个表的总行数存在了磁盘上&#xff0c;因此执行count&#xff08;*&#xff09;的时候会直接返回这个数&#xff0c;效率很高&a…

阿里云nginx更新证书后依旧显示旧证书

尝试的解决办法 重启nginx服务删除服务器上的旧证书清除浏览器缓存检查是否使用CDN服务 最后的解决办法 云服务器开启了WAF服务&#xff0c;在WAF服务中配置证书

MySQL学习(5):SQL语句之数据查询语言:DQL

1.DQL语法 select 字段列表 from 表名列表 #DQL是可以进行多表查询的 where 条件列表 group by 分组字段列表 having 分组后条件列表 order by 排序字段列表 limit 分页参数 2.基本查询&#xff08;select&#xff09; 2.1查询多字段 select 字段1,字段2,字段3,......fro…

重要通知:据最新TEMU要求所有欧区车灯都需要能效标签(eu energy lable)

重要通知&#xff1a; 据最新TEMU要求&#xff0c;所有“欧区车灯”都需要能效标签&#xff08;eu energy lable&#xff09;&#xff0c;目前已下架欧区站点&#xff0c;上传成功后可恢复。 灯具类欧盟EU ENERGY LABEL 近日有不少欧洲站卖家收到TEMU平台商品要求卖家们发布的…

SHELL脚本学习(十二)sed进阶

一、多行命令 概述 sed 编辑器的基础命令都是对一行文本进行操作。如果要处理的数据分布在多行中&#xff0c;sed基础命令是没办法处理的。 幸运的是&#xff0c;sed编辑器的设计人员已经考虑了这个问题的解决方案。sed编辑器提供了3个处理多行文本的特殊命令。 命令描述N加…

大数据学习之分布式数据采集系统Flume学习

分布式数据采集系统Flume学习 一、Flume架构 1.1 Hadoop业务开发流程 1.2 Flume概述 flume是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的系统。 支持在日志系统中定制各类数据发送方&#xff0c;用于收集数据; 同时&#xff0c;Flume提供对数据进行简单处理&…

如何在720漫游中设置付费观看?

1. 进入720漫游作品编辑器&#xff0c;点击 「全局设置-营销设置-付费观看设置」 即可打开付费观看设置栏&#xff1b; 720漫游编辑器-全局设置 2. 开启付费观看功能后&#xff0c;设置「付费金额」&#xff0c;选择「付费场景」即可 720漫游编辑器-全局设置-付费观看设置 3.…

CentOS 生命周期结束指南

2019 年 9 月&#xff0c;Red Hat 宣布打算废止 CentOS&#xff0c;并将其替换为 CentOS Stream。 CentOS 7 和 8 是 CentOS Linux 的最终版本。CentOS 7 和 8 的生命周期结束日期为&#xff1a; CentOS 8 - 2021 年 12 月 31 日 CentOS 7 - 2024 年 6 月 30 日 相关内容推荐 点…

【人工智能学习之图像操作(三)】

【人工智能学习之图像操作&#xff08;三&#xff09;】 图像滤波滤波概念卷积平滑算子均值滤波高斯滤波中值滤波双边滤波锐化算子USM锐化梯度算子 傅里叶变换直方图直方图直方图均衡化自适应均衡化2D 直方图直方图反向投影 图像滤波 滤波概念 滤波过程就是把不需要的信号频率…

【D3.js in Action 3 精译】1.1.2 D3.js 的适用场景

译注 上一节中我们了解了 D3 诞生的技术背景——为了满足 Web 可访问数据的可视化需求。本节再来看看 D3.js 的适用场景是怎样的、在什么时候会考虑使用 D3.js。 1.1.2 D3.js 的适用场景 数据可视化领域正蓬勃发展&#xff0c;且备受青睐。过去十年间用于生成数据驱动图形的工…