分类目录归档:技术

black and white penguin toy

LCTT 从Travis CI迁移到 GitHub Action 实践

LCTT 的 CI 已经在 Travis CI 上运转了多年,一致保持着良好的使用体验。自 2019 年 Github 推出了自家的 CI 工具 Github Action 后,我们就在考虑将 CI 从 Travis-CI 迁移到 Github,以降低维护和沟通的成本,并借助于 GitHub Action Marketplace 实现更强的功能。

项目首页
项目首页

最近,因为 TravisCI 屡屡部署出错,而我们的账户因为使用的较多,已经超出了免费使用的限制,以此为契机,将 CI 从 Travis CI 迁移到 GitHub Action。

Travis CI 的提醒
Travis CI 的提醒

项目介绍

Translate Project 是 LCTT 翻译组的主要协作项目,几百位译者通过 GitHub 进行围绕开源、Linux、软件工程等领域的文章翻译,从 2013 年来,累计了大量的 Commit,致使项目下有非常多的文件。

Transalte Project 借助于 CI 帮助译者进行基本的文章格式进行检查;并定时执行命令,以进行所有的文章检查,对于超时未完成翻译的工作进行回收;对于文章的状态进行标记,生成相应的 Badge。

生成 badge
生成 badge

迁移思路

Travis CI 和 Github Action 在使用方面,其实总体差异不会太大,都是基于 YAML 文件格式来编写配置文件。不过,和 Travis CI 不同的是,Github Action 支持多个不同的配置文件,因此,你可以根据不同的场景,设定不同的配置文件,降低单个配置的文件的复杂度。

此外,由于我们的脚本中依赖了一些 Travis CI 的环境变量,也需要将其替换为 Github Action 中的相应环境变量,从而确保脚本可以运转。

改造实践

1. 分析之前的 CI 流程

我们在 TravisCI 上的 CI 配置文件如图

配置文件
配置文件

总体可以分为三块:

  1. 命令区:说明了安装阶段和执行阶段的操作有哪些
  2. 条件区:指定了这个配置文件在哪些条件下会生效
  3. 部署区:写明了构建产物如何进行部署

在命令区中,有预置的安装过程和后续的执行过程。在安装过程中,安装了一些依赖,并将当前的 pages 资源克隆到本地,以继承上一次构建生成的资料。

在条件区则指明了仅作用于 master 分支

在部署区便是将前面命令区的执行结果进行部署。

基本流程
基本流程

在实际的执行过程中,还会根据环境变量不同,决定是否要执行特定的命令,这部分在后续的改造过程中,就可以调整部署,拆分到不同的文件中。

构建流程
构建流程

2. 直译配置文件

在完成了基本的分析后,就可以建立新的 Action 配置文件了。由于基本的语法很类似,对于其中的不少内容可以进行直译。

比如,我们的配置文件在直译后,结果如下

直译后的结果
直译后的结果

直译的文件已经可以直接运行,不过,这里有很多不满足需要的地方,所以需要做一些修改。

3. 恢复 Travis CI 的环境变量

由于我们使用的 Badge 等生成脚本并非我所编写,所以在这一次的迁移中,并不打算对齐进行调整,以避免出现故障。而脚本中依赖了一些变量,需要将其重新设置出来。

Github Action 提供了一些方法,可以让你手动设置环境变量。你可以在你的构建的 step 中,加入如下代码,从而在构建环境中设定 TRAVIS_BRANCH 和 TRAVIS_EVENT_TYPE 环境变量,确保你可以使用这个环境变量。

 - <strong>name</strong>: Set ENV variables
        run: |
          echo "::set-env name=TRAVIS_BRANCH::${TRAVIS_BRANCH:-$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')}"
          echo "::set-env name=TRAVIS_EVENT_TYPE::$(if [ "schedule" == "$" ]; then echo "cron"; else echo "$"; fi)"

Code language: JavaScript (javascript)

此外,由于 set-env 这个方法相对比较危险,你还需要在环境变量中,开启危险函数的执行。

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      ACTIONS_ALLOW_UNSECURE_COMMANDS: true

Code language: JavaScript (javascript)

4. 拆分配置文件

Github Action 和 TravisCI 不同的一点是你可以将你的 CI文件拆分成多个文件,从而降低每一个单独的配置文件的复杂度。

根据前面对于流程的分析,可以将我们的 CI 流程拆分成三个部分:

  1. 生成 badge 文件,应当跟随每一次 commit 进行
  2. 生成 status 文件,执行时间较长,可以定期执行
  3. 根据 Pull Request 内容进行整理,做核验

则将我们的配置文件拆分成三个不同的文件

也得益于拆分开,则在 checker 中就可以免于安装一些必要的依赖,从而精简 CI 流程,提升 CI 的执行时间。

5. 测试 CI 的运行情况

在完成了配置文件的编写和拆分后,就可以进行本地的执行测试。Github Action 写完了,难免要执行一下,确保整个流程是正常的。

这个时候你可以安装工具(https://github.com/nektos/act),来在本地执行 action ,从而确认你的代码执行是正确的。

bkpl8

如果你是 macOS ,只需要执行 brew install act 就可以安装 act 工具,来完成 act 的安装。

安装完成 act ,就可以通过执行 act 命令来在本地执行 action ,比如,执行 act pull_request来触发 GitHub 的 Pull Request 事件

mt4ca

通过本地测试后,再将你的配置文件推送到 GitHub 上,进行生产环境的测试即可。

6. 移除 Travis-CI

通过上述的一些步骤,我们完成了从 Travis CI 到 GitHub Action 的迁移,此时,就可以移除项目根目录中的 .travis.yml 文件,彻底关闭 travis-ci。

7. 修改 GitHub 的 Branch 保护条件

为了确保修改符合标准,我们限制了新的 Pull Request 必须通过 CI 检查,才能合并进入 master,因此,也需要修改相应的分支保护规则,确保设定相应的保护。

b5cwa

一些注意事项

1. 环境变量不同

如果你依赖了环境变量,则需要进行相应的修改。或者可以像我一样,先通过 set-env 来让本地拥有临时的环境变量,后续再逐步进行迁移。

2. Action 运行依赖要注意安全

Action 执行过程中会有两个部分。action 本身流程依赖于 master,但执行过程中调用的脚本是根据 source 决定的,因此,从安全角度来看,你应当尽可能将所有的流程放在 Action 中,而不是放在你的源码目录中。

总结

通过对 TravisCI 的流程整理、代码修改等流程,我们将之前的 Travis-CI 迁移至速度更快、性能更好的 GitHub Action ,一方面可以优化我们的工作流,另一方面,也让我们的代码更加简洁明了。

对于还在使用 Travis CI 的项目来说,也可以考虑迁移到 GitHub Action 中,来优化自己的工作流。

参考阅读

  • https://mauricius.dev/run-and-debug-github-actions-locally/
  • https://jeffrafter.com/working-with-github-actions/
  • https://developer.okta.com/blog/2020/05/18/travis-ci-to-github-actions
code 1076536 640

拯救老旧 Discuz 的 Flash 上传

Discuz 目前依旧是一个非常重要的建站程序,不少老的站点依旧会使用 Discuz 上运转。
由于 Discuz 是一个多年的程序,所以在系统的设计上,有很多地方充满了对老旧系统的兼容。

比如,Discuz 的上传使用的是 Flash 上传,但在 2021 年,Flash 已经彻底停止使用了,这个时候你就需要使用自己的方式来进行上传。

这里我以 Linux 中国进行的相关改造为例来介绍。

Linux 中国的文章发布系统就是基于 Discuz 实现的,编辑器层面使用的是 Discuz 提供的 TinyMCE,

erf5f

Discuz 提供了一个基于 Flash 的上传组件,但在2021 年,现在这个 Flash 按钮已经完全显示不出来了。

上传组件

这个时候,你就需要自己实现一套上传系统。

由于 Linux 中国的很多底层实现都是基于 Discuz 进行的,因此,此次改造希望继续沿用 Discuz 的系统。

实现路线

在对 Discuz 默认的 Flash 插件进行抓包后,很轻松的就找到了 Discuz 的 Flash 上传组件的接口:/misc.php?mod=swfupload&action=swfupload&operation=album

这个接口接受 Form 表单作为参数,并在表单中接受一个 Hash 作为身份校验,用来判断请求的合法性。

因此,只需要在 TinyMCE 自带的上传功能中加入相应的 Hash 和表单提交的能力,就可以将 Discuz 实现的上传功能改为用 TinyMCE 的原生组件实现。

而 Hash 可以通过分析得出,其算法为 md5(substr(md5($_G['config']['security']['authkey']), 8).$_G['uid'])

因此,需要做的便是,在页面中注入 Hash ,并在 TinyMCE 中调用此值,实现上传功能即可。

样例代码

html/template/default/portal/portalcp_article.htm 文件顶部加入如下代码,从而在文章发布页面注入 hash

<span class="hljs-comment"><!--{eval$swf_hash =  md5(substr(md5($_G['config']['security']['authkey']), 8).$_G['uid']); }--></span><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span>></span><span class="language-javascript"><span class="hljs-keyword">var</span> <span class="hljs-variable constant_">FORMHASH</span> = <span class="hljs-string">"{$swf_hash}"</span></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
Code language: JavaScript (javascript)

html/static/js/editor_tinymce.js 中的 tinyMCE 配置中添加如下配置项目,即可调用 HASH 来上传文件。从而实现文件的上传。

        images_upload_url: "/misc.php?mod=swfupload&action=swfupload&operation=album",
        images_upload_credentials: true,
        images_upload_handler:function(blobInfo, success, failure, progress) {
            var xhr, formData;
            xhr = new XMLHttpRequest();
            xhr.withCredentials = true;
            xhr.open('POST', '/misc.php?mod=swfupload&action=swfupload&operation=album');
            xhr.upload.onprogress = function(e) {
                progress(e.loaded / e.total * 100);
            };
            xhr.onload = function() {
                var json;
                if (xhr.status === 403) {
                    failure('HTTP Error: ' + xhr.status, {
                        remove: true
                    });
                    return;
                }
                if (xhr.status < 200 || xhr.status >= 300) {
                    failure('HTTP Error: ' + xhr.status);
                    return;
                }
                json = JSON.parse(xhr.responseText);
                if (!json || typeof json.bigimg != 'string') {
                    failure('Invalid JSON: ' + xhr.responseText);
                    return;
                }
                success(json.bigimg);
            };
            xhr.onerror = function() {
                failure('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
            };
            filenameArray = blobInfo.filename().split(".");
            formData = new FormData();
            formData.append('Filedata', blobInfo.blob(), blobInfo.filename());
            formData.append('Filename',blobInfo.filename() );
            formData.append('type','image');
            formData.append('Upload','Submit Query');
            formData.append('uid',discuz_uid);
            formData.append('filetype',"." +filenameArray[filenameArray.length-1]);
            formData.append('hash',FORMHASH);
            xhr.send(formData);
        },
        file_picker_callback: function(cb, value, meta) {
            var input = document.createElement('input');
            input.setAttribute('type', 'file');
            input.setAttribute('accept', 'image/*');
            input.onchange = function() {
                var file = this.files[0];
                var reader = new FileReader();
                reader.onload = function() {
                    var id = 'blobid' + (new Date()).getTime();
                    var blobCache = tinymce.activeEditor.editorUpload.blobCache;
                    var base64 = reader.result.split(',')[1];
                    var blobInfo = blobCache.create(id, file, base64);
                    blobCache.add(blobInfo);
                    cb(blobInfo.blobUri(), {
                        title: file.name
                    });
                };
                reader.readAsDataURL(file);
            };
            input.click();
        },
Code language: JavaScript (javascript)

总结

老旧系统的升级改造不可怕,只要你用心,从业务的底层抽丝剥茧,就能找到要解决的问题。剩下,不过是代码层间的问题罢了。

black and white penguin toy

被 GitHub Action 坑了半天...

我这几天在忙着搞 LCTT 的 Travis-CI 迁移到 Github Action 。整体的 CI 的流程都已经迁移完成了,但是一个特定的检查脚本死活无法正常运行。

在这个过程中我尝试了多种办法,比如修改代码、调整里面的参数、打印其中的参数,似乎都是工作运行正常的。

将项目代码 clone 到本地后,发现代码在本地运转也是正常的。

我一直不知道问题出在哪里,百思不得其解。直到我在跑了几百次 Action 构建后,我发现,Github 默认的 checkout 插件是有问题的。

和标准的 git clone 不同, Github 默认的 Checkout 插件在实际 clone 项目是使用的不是 git clone 命令,而是采用先在本地 init 一个目录,并添加相应的 remote ,并 fetch 代码下来。

github action
GitHub Action 的 Clone

这样的好处是在处理的时候,只会 fetch 到特定的分支到本地,而不会将默认的其他分支一同 clone
到本地。但坏处就是你在执行的时候,只能针对特定的分支进行操作。

而检查脚本则是基于 git 本身的命令进行执行的,因此是需要比如 master 这样的分支的,这就导致在使用了默认的 checkout 插件的时候,检查脚本无法使用。

Travis-CI 的表现
Travis CI 的 Clone

与之对比的,是 Travis CI 在执行 Clone 的时候,采用的是全量 Clone ,再单独 Fetch 某个特定分支。从这个角度上来看,我可以理解 Github Action 为什么会 Clone 的更快一些。不过,这种 Clone 的方式确实给一些 CI 在 Check 时留下隐患。

总结

如果你在 Github 中使用默认的 Checkout 插件获取项目以后,执行 Git 操作出现了问题,很有可能是插件自己的问题,而不是你的问题。你可以选择自己构建 clone 命令,避免这个问题。

black and silver laptop computer

在 macOS 下创建启动 U 盘

因为要重装 Mac mini ,所以研究了一下怎么配置启动 U 盘。

依赖

想要给 U 盘制作一个 macOS 的启动盘,首先,你需要有一个 macOS 的系统,并且有相应的安装软件(Install macOS Catalina 之类的)。此外,还需要有相应的容量 U 盘。

根据 macOS 的系统大小,我比较建议你使用 8G 或 16G 以上的 U 盘。

获取安装软件

由于安装软件比较大,所以一般情况下我们也不会保留这个软件,但当我们需要的时候,就要去安装对应的软件了。

你可以访问 Apple 的官网,找到相应的软件下载地址

系统下载地址:https://support.apple.com/zh-cn/HT211683

如果你已经升级了 Big Sur ,却希望制作 Catalina 的启动盘,那么你需要看看 这篇文章

选择你需要使用的系统

image

会自动打开下载界面

g645c

你只需要点击其中的获取,就可以下载相应的系统镜像。等软件自动下载并安装完成后,就可以进行安装操作了。

查看 U 盘挂载路径

想要制作启动 U 盘,自然要说明对应的路径,这个时候你需要先找到你自己的 U 盘。

你可以在终端中执行 df -h ,在其中找到你自己的 U 盘,比如我这里的是 /Volumes/install

制作启动盘

准备好软件和U盘后,剩下的比较简单,直接执行命令即可。

以 Catalina 为例,只需要执行如下命令

sudo /Applications/Install\ macOS\ Catalina.app/Contents/Resources/createinstallmedia --volume /Volumes/MyVolume

其他版本的系统可以参考 https://support.apple.com/zh-cn/HT201372

black and silver laptop computer

如何从 macOS 系统中启动到恢复模式

macOS 可以在开机的情况下通过按 Control + R 启动到 恢复模式,那是否有不按 Control + R 就能进入到 Recovery 的方式呢?

答案是,有的

你在 macOS 的 Terminal 中输入如下命令,即可进入到恢复模式中。

sudo nvram "recovery-boot-mode=unused"
sudo reboot
Code language: JavaScript (javascript)

在操作完成后,你可以执行如下命令来移除添加的 Flag,重新进入到正常操作的系统中。

nvram -d recovery-boot-mode

来源:https://apple.stackexchange.com/questions/367336/can-i-initiate-a-macos-restart-to-recovery-mode-solely-from-the-command-line

text

对 Discuz 进行手动搬迁

由于服务器托管方的维护,需要对托管在机房的 Linux.cn 服务器进行搬迁,因此,多年不碰 Discuz 的我又要进行一次搬迁。

刚好,记录下来,方便后续查用。

流程图

下方流程图中,绿色为原服务器操作,黄色为新的备份服务器操作

d9uas

具体流程介绍

1. 导出数据库

想要搬迁,首先要处理的是数据库的导出,你可以选择你的站点流量最小的时候,使用 MySQL dump 命令来完成 SQL 文件的导出,导出的命令也非常简单。

mysqldump -uroot -p database >/tmp/db.sql
Code language: JavaScript (javascript)

你可以将上方的 root 调整为合适的用户名;将 database 调整为合适的数据库名,以及将 /tmp/db/sql 调整为合适的文件名

执行命令后,会要求你输入 MySQL 对应用户的密码,输入密码, 稍等片刻,数据库就完成导出,你就可以在 /tmp/db.sql 找到数据库文件。

2. 压缩数据库文件

在进行数据库文件后续的传输时,如果文件太大,可能会导致传输速度较慢,这个时候你可以选择使用 gzip、zip、7zip 之类的进行传递。

我一般习惯用 gzip 进行压缩,并使用 tar 进行打包。

tar -zcvf db.sql.tar.gz /tmp/db.sql

打包后,会获得一个 db.sql.tar.gz 文件,这个文件基于导出的 SQL 进行了一定的压缩,可以确保传输的时候,不需要传输那么大的文件。在实际测试时,可以将 1.1G 的数据库压缩到 188M,效果还是十分明显的。

3. 压缩网站文件

需要传递到新的服务器中的,除了 MySQL 数据库,还需要传递网站的代码文件到新的服务器中,因此,为了方便传输,同样需要进行压缩。

tar -zcvf website.tar.gz /data/website/website.com

命令执行完成后,你就会获得一个 website.tar.gz ,这个文件就可以在后续传递到你的新服务器中。

4. 配置新的网站运行环境

在你备份的同时,你可以在新的服务器上进行环境配置。

一般而言,在搬迁的同时,不会采用新的版本的软件,以避免出现问题。

你可以通过 php -vmysql --version 来查看 PHP 和 MySQL 的版本。

Nginx 的版本倒是不需要太过介怀,他只是一个反向代理,问题不大。

MySQL 的版本则在进行迁移的时候,不建议做版本升级,尽量保持同版本升级;如果跨版本,则需要考虑相应的回滚措施。

5. 进行文件传输

在新的服务器中配置旧服务器的公钥,从而可以直接通过 scp,在两个服务器之间传递文件,简单方便。

scp source root@host:/data/xxx 
Code language: JavaScript (javascript)

执行上面的命令就可以直接在两个服务器之间传输文件,简单方便快捷。

6. 进行文件恢复

完成文件搬迁后,就可以在新的服务器上配置环境,这时可以根据你的配置,将文件迁移至对应的目录中。

涉及到压缩包,可以进行一下解压操作。

7. 配置应用服务器

在我们的系统运行时,会依赖很多应用服务器,比如数据库 MySQL 、反向代理 Nginx 等等。在迁移时,比较稳妥的方案是在当前版本的基础之上进行配置。

这时你需要在新的服务器上配置和旧服务器完全一致的运行环境,从而确保迁移后业务不会出问题。

8. 导入数据库和站点文件

在完成了数据文件的迁移和,就可以进行数据库的导入,并迁移网站文件。

你可以使用 mysql 命令行中的 source 命令,来加载 mysql 的dump 文件。

对于网站文件,只需要根据你的配置进行调整即可。

9. 修复权限

在文件进行迁移的时候,可能会由于迁移前后的用户等问题出现权限问题。因此,如果你发现出现了项目的权限有问题,则需要根据实际情况,调整项目的文件和目录的权限。

10. 修改配置

Discuz 的配置文件会在多个地方重复使用,因此,在实际的使用时,如果你调整了数据库信息,则需要修改以下几个文件中的配置项目。

  • config/config_global.php
  • config/config_ucenter.php
  • uc_server/data/config.inc.php

修改其中的数据库名,从而确保系统中的各模块都可以正常工作。

green and black digital device

如何隐藏 oh-my-zsh 的 Last Login?

oh-my-zsh 是我目前配置新的 Mac 必然会装的。不过,oh-my-zsh 一直有一个我不喜欢的就是它会自动一个 Last Login 的 Hello Message。

jb2ia

这个 Hello Message 倒是不占位置,但是我觉得它让我的命令行不那么极简

因此,我希望将这个提醒删除掉。

删除的方法不复杂,只需要在用户的根目录创建一个空白的~/.hushlogin 文件即可

touch ~/.hushlogin
15bd774f997dc7fae5f8faae791cca7d

几个可以简化 rails 开发命令的函数

在 rails 的 bin 目录下,有一些可执行文件,你在开发过程中使用这些可执行文件来操作,从而使用项目自带的可执行文件

但是默认的 rails 命令使用的是全局的 rails ,如果我希望使用项目中的可执行文件,就需要执行 bin/rails, 略微繁琐,所以有没有一种可以更加简单的方式呢?答案是肯定的。你可以通过在你的命令行中添加一个新的命令来实现这个效果。

具体代码如下:

funciton rx(){
	if test -f "bin/rails"
	then
	  bin/rails $*
	  exit
	else
	  rails $*
	fi
}
funciton yx(){
	if test -f "bin/yarn"
	then
	  bin/yarn $*
	  exit
	else
	  yarn  $*
	fi
}
function bx(){
if test -f "bin/bundle"
	then
	  bin/bundle $*
	  exit
	else
	  bundle  $*
	fi
}
Code language: PHP (php)

你可以将这段代码粘贴在你的 .bashrc.zshrc  文件中,从而使其在命令行启动时可用。

这段代码很简单, 定义了三个新的函数,后续我们在命令行输入 rxyxbx 的时候,会自动调用当前目录或全局的 railsyarnbundle 目录。这样你可以在任何一个目录下使用 rx 命令来操作。

三个函数的结构都是一样的,首先检测当前目录下的子目录是否存在 rails 可执行文件,如果存在,就调用本地的函数,并将参数传递。如果不存在,就调用全局的函数,将参数传递。

总结

我们可以通过定义简单的一些命令,简化项目的开发。而这样的思路,你可以应用在任何一个项目中,而不仅仅是 rails 项目中。

red and white heart illustration

如何实现子域名应用?

多域名应用的心思是从 WordPress.com 的时候起来的,当时一直在想,如何实现这种多域名的应用?

最近看到了一个实现,算是解了心头的疑惑。

如果不涉及 SSL ,子域名应用的实现并不算复杂,可以简单的通过将用户请求进行 rewirte 转发的方式,来实现对请求的转发。

举个例子,比如将 x.example.com 转发到 www.example.com/pages/x 中,这样在应用中就无需单独对于多域名编写代码,只需要从 Path 中提取前缀,并进行数据库查询,将数据结果返回回去就好。

如果涉及到 SSL,子域名应用的实现相对复杂一些,涉及到了 SSL 证书的管理。不过也有实现简单的方式,那就是购买一个泛域名证书,这样 Nginx 会通过泛域名证书提供服务,因此,应用程序在处理上和上面的逻辑一样,只要将请求转发就好了。

如果涉及到子域名绑定,则相对麻烦一些,需要能够编程式的操作 Nginx 的代码文件,不过也还好,你可以在应用的目录下动态的生成 Nginx 的配置文件,并在默认的配置文件中 include 动态生成的目录,从而避免文件生成后的管理成本太高。此外,需要借助 execl ,执行命令对 Nginx 执行检查和重启的操作。

此外,也可以考虑直接使用 default_server ,从而直接将所有未识别的情况转发到某个特定的路径。不过需要在应用中添加请求判断,对于无法绑定的域名,提供一个特定的反馈,引导用户进行绑定。

总结

如果你的应用不涉及到 SSL,也不涉及到域名绑定,则十分简单,直接使用 Nginx 转发请求即可;但如果涉及到了 SSL,则需要考虑泛域名证书,从而降低编程的成本;如果涉及到了域名绑定,则需要为应用程序新增对 Nginx 操作的能力,从而降低应用的管理和研发成本。

继续阅读
pile of assorted-title books

几本超融合的书

这几本是我前段时间在研究超融合时找到的,这里分享给大家。因为这些书都是公开放在网上(厂商提供下载)的,所以整理起来一并发布。

这四本书值得发一个很有意思的点是这四本书都是 For Dummies 出版社为这四家企业定制发行的电子书。官网上没有,但使用的都是 For Dummies 的标示,所以这看起来也是一个出版社营收的事情。

这四本书提供了四个不同的厂商对于超融合的看法,对于在研究超融合的你来说,是一个不错的选择。