分类: 技术

各种开发心得,包括语言、软件工程、开发工具等

  • 为什么要投资开源项目

    为什么要投资开源项目

    在群里聊起开源项目,说到现在投资者很喜欢投做开源软硬件公司,有同学问:“投资开源项目会有哪些形式获得回报呢?”我觉得一句两句说不清楚,所以写一篇博客作答。

    开源软件的优势

    先说软件。开源软件因其免费、开源的特性,越来越受到各个级别的公司、团队、开发者的欢迎。因为免费,我可以试试,好用就继续用,不好用就换一家;因为开源,如果使用过程中发现一些小问题,我可以自己修、自己适配,大大提升开发效率;同时,由于开源,用户可以更方便地对代码进行安全审计,安全性也会大大提升。

    所以,相比于传统付费商业软件,开源软件的用户量一般要大很多,而且往往成长非常快。当使用人数上升到一定级别的时候,开源软件又具备了一些新的优势。

    开源软件的用户量很大,使得开源软件的问题更容易被发现,也更容易被修复

    会有很多用户贡献使用文档,也会有很多用户在各种问答平台贡献问题解决方案,也会有很多用户写各种教程。如今是视频年代,自然视频领域也有很多用户主动贡献

    开源软件的初用成本很低,使得开源软件可以进入更多行业,获得更多使用场景,接触到更多的设备,尤其是新设备。比如,当苹果 iOS 取得成功之后,Google 立刻基于 Linux 开发了 Android 系统,然后理所当然的击败了闭源的 Symbian 和 Windows,成为移动双雄之一,并在移动互联网领域彻底接管了微软的系统软件地位。物联网方面,更是 Linux 各种发行版一枝独秀。

    开源软件的用户可能比作者更厉害,使得开源软件可以实现超越式发展。比如当年的 Backbone,它最初的成功得益于 API 设计,但是本身代码质量一般。后面有了大量用户贡献生产实例和改进代码,慢慢在代码质量上也远超一般项目。

    开源软件的版权属于全世界,很多厂商都愿意投资开源软件,因为不用担心被竞争对手用专利限制;从国家层面更是如此,开源就不怕卡脖子。《“十四五”软件和信息技术服务业发展规划》解读 中重点强调了开源生态。

    开源硬件也差不多,现在不光是开发板,机械臂什么的也都有开源项目,动手能力强者照着完全可以 DIY 出来。对于我国这样有完整工业体系的制造业大国来说非常有优势。

    投资开源产业的价值

    上面简单梳理了开源软硬件对比传统收费软件行业的优势,接下来深入分析一下投资者,包括投身开源软件的创业者能从中获得哪些特有价值。

    获得事实标准

    前面说过,开源软件发展的更快,触及行业更广。所以开源软件更容易形成事实标准。比如,现在服务器上装的,几乎都是 Linux;当年 RIA(Rich Internet Application,指功能更强大效果更出彩的网络应用)的开创者、商业软件 Flash,遭遇几乎全开源的 HTML5 挑战,如今坟头草一人多高,很多新晋前端开发者听都没听说……

    取得行业标准之后,再想让行业朝着自己喜欢的方向发展,就容易很多。比如 Chrome,如果你对比一下 MV2 阶段的 Browser Extension API,会发现,它基本就是照搬自 Chrome Extension API。结果就是,Safari、Firefox 的扩展也必须兼容 Chrome 规范;Chrome Extension MV3 推出后,其它浏览器开发厂商都要迅速跟进。

    如果你需要某个功能,而规范还不支持,那也没关系。你完全可以调整产品线的优先级,让你的需求优先被满足。

    掌握制定规范的权力

    有时候,事实标准没有那么好拿到,但是只要掌握一定程度的市场,就可以参与规范的制定。能够参与制定规范,就能提前布局产品的设计和生产,从而在后面的竞争中处于优势。相信大家都记得当年的 4G、5G 规范之争吧?

    开源不挣钱,但开源软件公司可以挣钱

    开源并不是做慈善,做开源软件的公司当然可以赚钱。事实上,目前成功案例很多。比如红帽,比如 Automatic(WordPress 就是他们家的产品,本博客就是用 WordPress 搭的),还有 MySQL、PingCap、F5 等等,不胜枚举。

    这些公司的商业模式大约是:

    • 打造一款开源软件,利用开源软件的优势赢下市场
    • 提供基于自家开源软件的服务,比如技术支持、定制开发、咨询培训等等
    • 除了开源免费的社区版,还有给付费用户的高级定制版,一般来说功能更丰富、性能更好

    所以投资开源软硬件企业,完全不用担心投资回报。

    不惧巨头垄断,突破行业壁垒

    开源软件挑战巨头取得胜利的例子更是举不胜举。跟众多“如果 BAT 要做,你怎么办?”的行业比起来,开源软硬件天然不怕专利限制,也不担心巨头在同领域竞争。开源软件即使失败,也是因为自身质量不好,或者瞄准的领域营养不足,或者败于其它开源项目。

    国家政策扶持

    前面说了,作为 IT 行业的后来者,我们国家要想追上行业壁垒森严的先行者,开源是我们的第一选择。所以未来很长一段时间,开源都会是国家关注且支持的领域。

    总结

    时代不同了,以前看起来只能用爱发电的开源软硬件也可以一边造福全人类一边造福自己家;开源从业者也可以不靠捐款过活。当然开源也有不少问题,限于篇幅,我就不在本文里讨论了。

    上面都是我从一个行业爱好者的角度,做的民科式总结。纰漏错误都有可能,欢迎指出、讨论。

  • 职业生涯的新转折点,2021 技术总结

    职业生涯的新转折点,2021 技术总结

    昨天整体总结了 2021 的职业、生活、副业等方面。今天重点总结一下去年技术方面的学习、分享,再规划下 2022 年计划。

    (更多…)
  • 今天打篮球(二)

    时间过得真快,一晃一周过去,今天又是篮球活动日。

    (更多…)
  • Git 操作特定分支的小技巧

    Git 操作特定分支的小技巧

    加入新的开发团队之后,难免有一些东西不适应。比如,我团队要求把所有代码,不分前后端放到一个仓库里;而且,还都不删分支,于是现在代码仓库有 3k+ 分支……

    我很受不了,clone 一次代码大几个 G,我感觉我的固态硬盘在呻吟……

    于是简单研究了一下,只 clone 特定分支的技巧,记录如下。

    clone 特定分支最近一次提交

    git clone -b 分支名 --single-branch --depth=1 仓库地址

    上面这段命令可以只 clone 一个分支、最近一次提交,速度非常快。缺点是会损失代码提交记录,如果你要回顾历史代码,可能会比较麻烦。不过具体到我负责的这个项目,它的历史记录乱成一张蜘蛛网,git blame 出来全是各种 merged branch,实在没啥看头。

    fetch 特定分支最近一次提交

    只 clone 一个分支可能不太够,比如我要看别人的提交,就要把别人的分支拉下来。这个时候,如果你用上面的方式 clone 代码,再执行 git fetch 是拉不到其它分支的,因为本地 git 配置里就不存在别的分支。

    所以这时候要:

    git fetch --depth 1 origin 远程分支:本地分支
    git checkout 本地分支

    修改 .git/config

    除了上面的方法,你还可以修改 .git/config。打开 .git/config,你会看到这样的配置:

    [remote "origin"]
            url = git@github.com:username/repo.git
            fetch = +refs/heads/master:refs/remotes/origin/master

    如果直接 git clone 不加参数得到的仓库,“master”那里会是 *。此时,你可以把想要增加的分支列在后面,然后照常 git fetchgit checkout 即可。修改后的配置文件大约是:

    [remote "origin"]
            url = git@github.com:username/repo.git
            fetch = +refs/heads/master:refs/remotes/origin/master
            fetch = +refs/heads/bugfix:refs/remotes/origin/bugfix
            fetch = +refs/heads/release:refs/remotes/origin/release

    此时你就可以只操作 masterbugfixrelease 三个分支了,其它分支不会干扰你。

  • 教程:搭建 SonarQube 服务并测试自己的项目

    教程:搭建 SonarQube 服务并测试自己的项目

    之前分享过 SonarQube 的使用体验,今天写篇教程介绍下如何部署自己的服务器实例,及测试自己的项目。

    官方文档写得很详细,推荐大家阅读学习:Install the Server。这篇博文我主要分享实操的经验教训。

    0. 安装 Docker

    2021年即将过去,建议大家,部署各种服务能用 Docker 最好都用 Docker。省时省力,集中精力写业务代码。

    Windows、macOS 用户推荐使用 Docker hub,Linux 用户可以命令行安装 Docker Engine——你都用 Linux 了,应该不需要我详细解释,参考 官方文档 即可。

    1. 部署 SonarQube

    1.1 保存配置文件

    有了 docker 之后,部署就很简单。先找个地方,将下面的内容保存成配置文件 sonarqube.yml

    version: "3"
    
    services:
      sonarqube:
        image: sonarqube:community
        depends_on:
          - db
        environment:
          SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
          SONAR_JDBC_USERNAME: sonar
          SONAR_JDBC_PASSWORD: sonar
        volumes:
          - sonarqube_data:/opt/sonarqube/data
          - sonarqube_extensions:/opt/sonarqube/extensions
          - sonarqube_logs:/opt/sonarqube/logs
        ports:
          - "9000:9000"
      db:
        image: postgres:12
        environment:
          POSTGRES_USER: sonar
          POSTGRES_PASSWORD: sonar
        volumes:
          - postgresql:/var/lib/postgresql
          - postgresql_data:/var/lib/postgresql/data
    
    volumes:
      sonarqube_data:
      sonarqube_extensions:
      sonarqube_logs:
      postgresql:
      postgresql_data:

    注意第5行,“sonarqube:community”,是镜像的注册名称,默认是最新版本。如果你想使用长期支持(LTS)版,可以将其改成 sonarqube:lts-community。一般来说,LTS 版本新功能会落后于最新版本,但是会更稳定,安全系数也更高。目前这个时间点,如果你想部署中文版本,必须选择 LTS,也就是 8.9.x 版本,因为中文翻译插件最高只支持 9.1,在最新版 9.2 下会报错。

    1.2 拉取镜像:

    docker-compose -f sonarqube.yml pull

    1.3 启动容器:

    docker-compose -f sonarqube.yml up

    第一次启动建议只用 up,方便看日志,判断状态。我的经验,不管是 Windows + WSL,还是云上小水管,都会报告:max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] 错误。这是虚拟机内存配置不够导致的,所以调高。

    Linux

    1. 永久修改配置:编辑 /etc/sysctl,增加 vm.max_map_count = 262144
    2. 修改当前环境:执行 sysctl -w vm.max_map_count=262144
    3. 重启 docker 服务:systemctl restart docker

    WSL

    先进入 docker 环境:

    wsl -d docker-desktop

    然后

    sysctl -w vm.max_map_count=262144

    1.4 启动为服务

    第一次启动会自动创建 Volumes(相当于磁盘),安装软件、部署各种功能、执行初始化操作等,会比较久。最后停下来时,如果 DB 服务和 SonarQube 服务都成功启动,就算完成。

    默认服务端口是 9000,启动浏览器访问 localhost:9000,此时应该可以看到下图所示的登录界面。

    默认用户名密码是 admin/admin,登录后会被要求修改。如果要对公网提供服务,建议尽量改得复杂一些。

    如果刚才启动 SonarQube 的时候只用了 up,建议先停掉,改为 up -d 再次启动为后台服务。

    2.0 配置 nginx 反向代理

    Docker 启动的服务只能内部访问,所以我们通常需要把它开放到外面,这里还是用 nginx 做反向代理:

    server {
        server_name sonarqube.meathill.com;
        root /var/www/sonarqube;
    
        location / {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $http_host;
            proxy_pass http://127.0.0.1:9000;
    
        }
    }

    3.0 安装中文包

    SonarQube 所有语言包都是以插件形式存在的,所以要汉化 SonarQube 只需要安装中文插件即可。

    登录 SonarQube 后,进入 Administration > Marketplace,搜索 Chinese,安装简体中文包即可。安装之后需要重启,请耐心等待界面变化,按照提示一步一步走。

    SonarQube 会根据浏览器语言配置自动选择语言,没有留出显式选项。

    4.0 分析 FastTest

    全部配置完成之后,可以开始第一次分析了。

    首先我们新增项目(Add Project),选择手动(Manually)即可。当然,如果你是团队内部使用,也可以配置为以 GitHub、GitLab 为代码仓库。

    输入 token 名称,创建一个 token,将 token 复制下来。

    接下来你可以选择使用打包好的二进制文件,那就点击“继续”(Continue)然后按照提示下载二进制文件,再输入命令执行即可。我比较喜欢用 Docker,所以推荐用下面的命令,会自动拉取镜像并且执行:

    docker run \
        --rm \
        -e SONAR_HOST_URL="http://${SONARQUBE_URL}" \
        -e SONAR_LOGIN="${myAuthenticationToken}" \
        -v "/path/to/repo:/usr/src" \
        sonarsource/sonar-scanner-cli

    上面的参数中:

    • SONAR_HOST_URL 即部署后提供服务的地址
    • SONAR_LOGIN 即上一步中复制的 token
    • /path/to/repo 需要是绝对路径,如果是 Windows 系统,正常使用 \ 即可,比如 C:\works\fasttest,不需要转义
    • 如果你的 SonarQube 跑在 docker 里,又使用 docker 启动分析,那么需要增加 --network=host 参数,不然两个 container 之间无法互相访问

    最终得到的分析结果大约是这样:

    报告内容我这次就不详细解析了,回头慢慢分享。

    总结

    好,SonarQube 的部署和分析过程就分享完了,建议大家都学习使用一下,能学到很多安全性、可维护性的知识,能很好的提升团队的代码质量。如果绑定到 GitHub、GitLab 等代码仓库,还可以自动分析 PR、MR,成为工作流程的一部分。

    感兴趣又不想自己动手(或没条件)的同学可以联系我,我可以在我的服务器上给你开通账号试用。

    注意:不要上传公司的业务代码!

    注意:不要上传公司的业务代码!

    注意:不要上传公司的业务代码!

  • Chrome Extension Manifest V3 升级笔记

    Chrome Extension Manifest V3 升级笔记

    关于 MV3 的知识,可以看这篇前文:

    《Chrome 扩展大升级 Manifest V3:变化》

    近期尝试把一个浏览器扩展升级到 MV3,然后发现了很多问题,写篇博客记录一下。

    与文档不符

    • 很多号称返回 Promise 实例的方法实际上不会返回结果,或者在没有 callback 的时候会报错

    文档未说明

    • service worker 里无法使用 chrome.i18n.getMessage 或者 chrome.i18n.getUILanguage(【v100 已修复】)。于是目前右键菜单都无法 i18n 了。
    • chrome.contextMenus.create 无法向 browser_action 添加菜单项(MV3 不再支持)

    未实现 Promise 或者有 bug

    • chrome.storage.local.get(keys) Promise 结果固定返回 undefined,不可用,需用回 callback (【v97 已修复】)
    • 【未测试】chrome.permissions.request 未 Promise 化,无返回值
    • 【未测试】必须传入 callback,不然报错
      • chrome.permissions.contains
      • chrome.contextMenus.remove

    文档写了,但我没看见(>_<!)

    • 在 service worker 里使用 chrome.contextMenus.create 时,必须传入 id,且不能使用 onclick 函数。只能侦听 chrome.contextMenus.onClicked.addListener,然后做进一步判断。

    MV2 存在的功能,但无法迁移至 MV3

    (有些可能是暂时的)

    因:不允许使用 remote JS

    MV3 不支持使用外部 JS,所以 Google Analytics 和 Facebook 分享按钮之类的 SDK 都无法使用。也即是说,现在扩展不能使用 GA,也不能嵌入 Facebook Like,也不能使用其它需要引用外部 SDK 才能实现的功能。

    因:Service worker 取代 background page/JS

    无法获取系统 light/dark mode

    Service worker 没有 window 对象,也就没有那些 window 对象上才有的方法,比如 matchMedia()。所以我们就无法在 service worker 里检查系统的 dark mode,实现动态修改 icon 的功能。

    无法将 SVG 解析成 ImageData

    因为 createImageBitmap 无法解析 SVG blob,所以原则上,所有 SVG 的操作均无法执行。

    外传信息比较麻烦

    无法使用 alert()prompt(),也无法手动弹出 popup。所以如果我们想从 service worker 里向外传出内容,只有三个选择:

    1. 使用通知 notification API
    2. 直接打开页面
    3. 使用 chrome.scripting.executeScript() 在目标页执行脚本

    (1)(3)需要不同的需要权限,(2)的体验不好。怎么用就自己选择吧。

  • 使用 SVG 制作扇形

    使用 SVG 制作扇形

    有时候我们需要制作扇形,比如图形化数据生成饼图的时候。使用 HTML + CSS 做不到,必须借助 SVG 帮助。经过一些摸索,大概方式如下:

    0. 创建 SVG

    我们需要一个 SVG,然后在里面画一个园:

    <svg xmlns="http://www.w3.org/2000/svg" height="600" width="600" viewBox="0 0 20 20">
      <circle r="5" cx="10" cy="10" />
    </svg>

    这里,我创建了一个 SVG,并且以 10,10 位圆心,画了一个半径为 5 的圆。SVG 的视窗只需要显示这个圆,所以是 0 0 20 20 的正方形。widthheight 用来定义网页中 SVG 的尺寸,SVG 是矢量图形,可以实现内容的无损缩放,所以即使显示尺寸比图形尺寸大很多,也不用担心出现锯齿。

    1. 用边框画圆形

    接下来,我们用给圆加边框的方式来做圆形。

    <circle
      r="5"
      cx="10"
      cy="10"
      fill="transparent"
      stroke="tomato"
      stroke-linecap="butt"
      stroke-width="10"
    ></circle>

    首先,我们用 fill="transparent" 清理掉圆形内部的颜色,然后用 stroke="tomato" 给边框加上橙色。接下来,我们通过 stroke-width="10" 设置边框宽度为 10,这也是矩形半径。

    此时,屏幕上会出现一个橙色的正圆。

    2. 画扇形

    画扇形的方式有很多,比如画两条半径然后画弧形再填充颜色。但是利用边框画扇形最简单。

    用边框画扇形说白了,其实是结合圆环和虚线,需要有扇形的地方,就填充颜色;不需要扇形的地方,就用虚线的空白。这里要用到 stroke-dasharray 属性,它的规则很简单,奇数为实偶数为虚,所以我们只要计算扇形所需的弧形长度,然后剩下的填充周长即可。

    在我们这里,就是 stroke-dasharray=”calc(10 * 3.1415926 * 1/6) 31.415926",即取绘制一个 1/6 大小的扇形。

    3. 修改位置

    修改位置需要使用 stroke-dashoffset 属性,它会把图形从原来的位置移动若干距离,正的就往起点移动,负的就往终点移动。

    在我们这里,就是 stroke-dashoffset="calc(-10 * 3.1415926 * 1/6)",将第二个扇形移到第一个扇形的旁边。

    4. 其它+已知问题+扩展阅读

    最终效果:https://codepen.io/meathill/pen/yLMQqBQ?editors=1000

    这些属性,也可以使用 CSS 样式替换,效果一样。

    Safari 问题比较多,首先半圆就不是半圆,其次偏移也不对,不知道是否只支持 CSS。

  • 正确使用 height: 100% 和 flex: 1

    正确使用 height: 100% 和 flex: 1

    HTML+CSS 实现页面布局的时候,盒模型是很重要的概念。早期没有明确布局概念的时候,HTML 元素主要分两大类:行(inline)元素与块(block)元素。默认情况下,块元素会占据父元素的一整行矩形区域,宽度是 100%,高度由内容决定。如果我们希望子元素跟父元素一样高,可以设置 height:100%

    接下来我们有了弹性盒模型, display: flex。弹性盒模型是一种主动布局,即我们先决定怎么布局,浏览器则负责填充内容和渲染。所以直觉上,我以为:

    1. 父元素 height: 100%; display: flex; flex-direction: column
    2. 某个子元素 flex: 1
    3. 子元素的子元素 height: 100%,应该能自动填充剩下的高度
    4. 如果子子元素同时 overflow: auto,那么应该可以自动出滚动条。

    结果不行。

    问题一

    如上图,我厂的 Showman 产品。它的高度自适应屏幕高度,顶部通栏、导航、动作按钮栏高度固定,编辑器和日志输出窗口填满剩下的空间。我希望用户可以调节编辑器和日志输出窗口的比例,以适应开发与调试不同的场景。于是保存日志输出窗口的高度,编辑器的高度自适应(flex:1)。

    因为 Vue2 组件要求有唯一的根元素,且整个应用有多个不同的路由,所以编辑器和日志输出窗口只能作为子子元素存在,大概的结构是这样的:

    <div id="app">
      <header id="main-nav">
      <!-- <router-view> -->
      <div id="main-body">
        <header id="second-nav">....</header>
        <div id="action-bar">....</div>
        <div class="editor-output">
          <div id="editor">....</div>
          <div class="drag-splitter"></div>
          <div id="output">....</div>
        </div>
      </div>
    </div>

    CSS 大概如此:

    html, body, #app {
      height: 100%;
    }
    #app {
      display: flex;
      flex-direction: column;
    }
    #main-nav, #second-nav, #action-bar {
      height: 40px;
    }
    #main-body, .editor-output {
      flex: 1;
      display: flex;
      flex-direction: column;
    }
    #editor {
      flex: 1;
    }
    #output {
      height: 100px;
    }

    这样的结果是,执行时,大量日志输出,就把界面顶开了。而不是预期中那样,出现滚动条,多余的部分被隐藏。

    经过一番 google,发现问题在高度计算。虽然定义了 height: 100%display: flex,但是浏览器在计算高度的时候,并不会从外往里一层一层算,而是按照规范:

    百分比
    指定一个百分比的高度。这个百分比是相对于父元素的盒子的高度计算的。如果父元素没有明确指定高度,并且该元素不是绝对定位的,该值将计算为 “自动”。

    auto
    由其它属性决定。

    于是因为上图中的界面存在嵌套关系,所以在需要计算高度的时候,子元素的高度虽然应该是 100%,但是父元素并没有被明确指定,所以就变成了 auto,继而被子元素撑开。解决方案就是沿着你需要 height: 100% 的元素往上,添加明确的 height,可以是百分比,也可以是绝对数值。

    于是,我在 #main-body 上添加 height: calc(100% - 49px),问题解决。

    😓 等下,不是每级都要加么?为啥只加一个具体高度就可以了?这个问题,我还要再研究一下。目前猜测,因为这个元素是竖直方向排列的(flex-direction: column)。

    问题二

    后来,在编辑器和日志输出窗口的右侧,增加了资源缩略图侧边栏。于是又遇到第二个问题:我以为 #main-nav 的高度确定,那么作为 display:flex,默认 align-items: stretch,它的子元素的高度应该都等于它的高度。所以给子元素设置 overflow: auto 就应该可以限制高度,出现滚动条。结果又失败了。

    然后我想起来 BFC。虽然直觉上 BFC 应该跟 display:flex 应该没什么关系,不过因为测试起来比较简单,可以先试试。

    于是我就在 div.d-flex 上添加了 .overflow-hidden 样式,果然问题就解决了。因为没找到明确的文档解释,所以我只能猜测:

    1. 类似 BFC 的逻辑在 display:flex 元素上依然存在。
    2. 父元素 display:flex;flex:N,根据上下文它应该有个确定的高度
    3. 但如果子元素高度超过它的高度,默认会撑开
    4. 如果父元素 overflow: hidden,会触发某个 xFC,于是整体高度就被限制了
    5. 于是子元素的滚动条就出来了

    总结

    行文至此,其实两个问题我都没找到具体的文档或者规范,只能说是摸索着解决了,然后再自己猜测原理。希望日后能找到具体的解释和规范吧。(要不要去翻翻张鑫旭的《CSS 世界》呢,都送完了,还得再买……)


    参考阅读:

  • nginx 笔记

    nginx 笔记

    基础配置

    # daemon on;
    # worker_processes 1;
    error_log logs/travis.error.log error;
    pid logs/travis.nginx.pid;
    
    events {
        accept_mutex off;
    }
    
    http {
    
        server {
            listen 9000;
    
            include mime.types;
    
            location / {
                rewrite ^ /static/edge/index.html last;
            }
    
            location /admin-api/ {
                proxy_pass https://admin-dev.openresty.com.cn;
                proxy_set_header Host admin-dev.openresty.com.cn;
                proxy_ssl_name "admin-dev.openresty.com.cn";
                proxy_ssl_server_name on;
            }
    
            location /static/ {
                alias fe/dist/static/;
            }
        }
    }

    启动 nginx

    nginx -p $PWD -c conf/travis.conf

    其中,-p $PWD 指定当前目录为工作目录。-c 指定配置文件。

    reload

    找到配置中的 pid 文件,从里面找到 pid

    kill -s HUP ${pid}

    域名 A 返回 a 文件,域名 B 返回 b 文件

    如果同一个项目下,我们有两个 robots.txt 文件,希望根据域名输出不同的文件,可以用条件判断 + rewrite

    注意,nginx 不支持 else,只能纯 if

    server {
        location /robots.txt {
            if ($host = mywordle.org) {
                rewrite ^ /robots.mywordle.org.txt break;
            }
            if ($host = mywordgame.com) {
                rewrite ^ /robots.mywordgame.com.txt break;
            }
            try_files $uri =404;
        }
    }
  • 使用 Node.js 驱动 FFmpeg 在 Linux + vncserver 下完成视频录制

    使用 Node.js 驱动 FFmpeg 在 Linux + vncserver 下完成视频录制

    自动化录制屏幕有很多用途,比如生成教学视频、生成产品文档,等等。对比人工,自动化有很多好处:

    1. 避免创作者的设备和环境问题(比如邻居装修、麦克风不好等)
    2. 避免创作者的语言、发音问题(比如普通话不标准、不会说某种语言)
    3. 录制环境出现变化,可以方便的重录(比如换个背景图,界面有升级)
    4. 就像写博客一样,任何时候,拿出电脑或者手机都能编辑一段

    所以目前研究这方面应用的很多,我厂也是。我近期就投入大量时间在这项工作上面,现在终于有所成果,写篇博客分享一下。

    0. 准备环境

    首先推荐大家使用 Linux。Linux 开源,有很多开源免费的工具可以完成各种操作,不仅可以录屏,还可以很容易地模拟各种用户操作,给我们留下大量开发空间。

    建议选择有图形界面的 Linux 发行版,我尝试过 fedora 33 和 Debian 10 树莓派版,都很容易配置。如果使用纯命令行版本,然后自己完成安装图形界面,比如 gnome,再完成剩下来的配置,会很麻烦。

    然后记得把系统更新到最新版,以规避可能遇到的问题。

    1. 配置 vncserver

    如果只能在主屏录制,这个产品的实用性就会大打折扣。所以我们选择用 vncserver 创建虚拟屏幕,然后在虚拟屏幕上完成录制。如果需要的话,也可以随时用 vnc viewer 之类的软件连上 VNC 实时查看效果,非常方便。

    有些系统自带 vncserver,比如 Debian 10 树莓派,那就不用安装。我们选用 fedora 33,需要手动安装,这里推荐 TigerVNC,安装使用都很方便:

    sudo dnf install tigervnc

    安装完成后,使用:

    vncserver :5 -geometry 1280x720

    就可以创建虚拟显示器了。其中,:5 是显示器 id,可以顺延,比如 :6:7、甚至 :99,至于上限在哪里我暂时不知道。-geometry 1280x720 是设定显示器分辨率为 1280×720。

    另外,还可以使用下面的命令查看和关闭显示器:

     vncserver -list
     vncserver -kill :5 

    1.1 测试

    配置完成之后,可以用 Firefox 测试一下效果。

    # 安装 firefox
    sudo dnf install firefox
    
    # 在指定虚拟显示器打开 firefox
    DISPLAY=:5 firefox https://cn.bing.com
    
    # 截图,可能需要安装 xwd
    xwd -root -display :5 > screen.xwd
    
    # 转换成 png,可能需要安装 ImageMagick
    convert screen.xwd screen.png

    然后把图片下载到本地,或者启动一个 http 服务器就能看到了。

    1.2 关闭桌面

    默认的 VNC server 会启动桌面,此时可能会要求我们登录什么的。我们在这套系统当中并不需要桌面,只要有显示器即可,所以可以修改 ~/.vnc/xstartup 禁用:

    #!/bin/sh
    
    unset SESSION_MANAGER
    unset DBUS_SESSION_BUS_ADDRESS
    # 把下面这行注释掉 
    # exec /etc/X11/xinit/xinitr

    2. 使用 FFmpeg 捕获屏幕内容

    使用 FFmpeg 录屏比较简单,将输入源设置为指定显示器即可,formatx11grab,命令大体如下:

    ffmpeg -y -f x11grab -video_size 1280x720 -framerate 30 -i :5.0+0,0 -c:v h264 a.mp4

    其中,

    • -y 意思是自动覆盖前面生成的视频
    • :5.0+0,0 是使用刚才创建的 :5 显示器,用它的 0 号桌面,启动位置是 0,0 即左上角
    • -f x11grab 是使用 X server 抓取格式,Linux 下的图形界面一版是基于这个系统

    注意,上面这条命令里参数的顺序很重要,否则可能遇到 Protocol not found 等错误。

    3. 使用 node.js 驱动

    最后只要用 node.js 的 child_process.spawn() 功能调用上面的命令即可。这段代码属于公司,我就不贴了,主要分享几点经验教训:

    1. 要用 spawn,因为录制过程中我们需要用输出来判断录制状态,exec 这种只能在结束时提供输出的没法用
    2. FFmpeg 会把输出输出到 stderr,理由不明,不过记得要用 stderr 来检查
    3. 录像完成,如果在命令行,按 q 或者 ctrl+C 都可以停止录像,并开始封装视频文件。在 node.js 里,我们可以调用 cp.kill('SIGINT') 。注意,调用之后,FFmpeg 子进程并没有立刻结束,它要把前面的录像进行封装,这个过程也是需要时间的,所以如果你接下来还要对视频文件进行操作,应该等待子进程彻底结束
    4. 判断录像开始的依据,我目前用的是:输出里包含 Output #0, mp4, to 'a.mp4'
    5. 判断录像结束,视频已经生成的依据,我用的是 cp.on('exit', onExit),然后在 onExit 里处理。注意,其它情况导致 ffpmeg 子进程退出时也会触发这个函数,所以我们必须检查 code。此时,在我的机器上,code 是 255,表示它是用户手动中止的,可以当作判断依据。

    总结

    剩下来的内容基本就是怎么驱动图形界面程序运行了。一般来说用 puppeteer 比较好,可以很容易的跟 node.js 联动,我厂的 showman 也是基于这个方案来实现的,最后贴一段视频,大家看下效果:

    一段用上述技术生成的视频

    参考阅读