分类
技术

欢迎欢迎

欢迎来到我的博客,我是 Meathill,想了解我可以点 关于我

我工作日晚上 9:00~10:00 会在 B 站直播,https://live.bilibili.com/5126601,大部分都是全栈开发相关,感兴趣的同学可以关注下。

08-02 ~ 08-06 直播计划。

日期上半场下半场
08-02重构插件BB酱
08-03重构插件BB酱
08-04重构插件BB酱
08-05重构插件BB酱
08-06重构插件BB酱
直播计划

欢迎留言点播各种内容。


近期作品

分类
js

聊聊 NPM 里的版本号和依赖

好像一直没有写过版本号和依赖相关的内容,偶尔会有同学问,所以写一篇总结一下。

0. Semver

我们目前使用的版本规范通常基于 Semver,语义化版本。官方网站:语义化版本 2.0.0 | Semantic Versioning (semver.org)。按照其规则,版本号的结构应该是:

主(大)版本号.次(小)版本号.修正(补丁版本)号

其中,

  • 主版本号一般包含架构和 API 的变化。如果 API 出现重大变化,使得依赖它的软件要重构,那么就要体现在大版本号里。不过现在的代码仓库很少有破坏式重构,API 一般能够在至少 2、3 个大版本里保持稳定。所以主版本号变化一般出现在大型重构时,仓库内部的代码架构和组织形式出现重大变化,或者基于不同系统,就需要升级主版本了。
  • 次版本号一般表达功能变化。架构没有变化,原有 API 也基本维持不变,只是新增了功能。这个时候就会上调次版本号。
  • 修订号一般表示修复 bug。

0.1 年代版本号

另一种流行的版本号规范是年代版本号,比如 Ubuntu,每年会发布两个版本,目前是 21.04,10月份会发布 21.10。偶数年的 .04 版本会最终成为 LTS,长期维护版本;奇数年的版本和 .10 版本则只维护半年,里面包含各种最新的软件组件,方便喜欢尝新的用户。

前端常见的软件和库,包括 node.js、Angular、Electron 也用这种方式确定版本号。

1. 依赖中指定版本号

我们在项目中可能会使用大量开源代码,这些开源代码通常都会使用包管理工具(比如 NPM,node package manager)安装和管理。

在 package.json 里,我们可以使用几个运算符告诉 NPM 我们希望怎么使用这些依赖:

  • 不写,repo: '1.0.0'。要求使用 1.0.0 版本的 repo,必须完全一致。
  • ~repo: ~1.0.0。要求大版本为 1,小版本为 0,修订版本不限制,比 0 高就可以。
  • ^repo: ^1.0.0。要求大版本为 1,小版本高于 0 就可以。

一般来说,使用 npm i repo 安装的依赖,默认规则是 ^;使用 npm i repo@version 安装的依赖,默认规则是写死。

2. 升级依赖

这个世界上不存在没有 Bug 的代码,也没有功能完善的代码。使用开源仓库,我们就要考虑升级依赖。一方面可以使用新功能,一方面可以解决 bug。

通常来说,直接使用 npm update 就能升级项目依赖,NPM 会按照(1)里设定的规则更新依赖。

3. 升级依赖的大版本

只使用 npm update 无法升级大版本。原因如上文所述,大版本可能包含破坏性的 API 更新,很容易导致 dev/build 失败,作为工具无法妥善处理,必须交给开发者手动完成。

很多同学因此不愿意升级大版本。但我建议大家还是要找机会做升级,尤其工具链,比如 webpack、babel。这些工具很多时候有时效性,长期不升级会导致各种问题。而且,升级工具链本身这也是我们偿还技术债务的好机会。

其实升级工具链的大版本并不复杂,大多数半天就搞定了;如果没有用到偏门功能,甚至可能直接升级就能跑。——通常来说,开源软件的作者面对使用频率高的功能,会比较保守;没人用的功能改动就会大刀阔斧一些。

升级依赖需要指定大版本,比如从 webpack 4 升级到 webpack 5,可以使用 npm i webpack@5,这样会安装大版本是 v5 的最新版本。这里有个小建议,公司的生产级别的项目,最好不要着急升级,等到 X.2 X.3 这样基本稳定的次版本发布后再升级,可以避免踩很多坑。

一般来说,开源仓库的官方也会提供迁移指南,比如 webpack v4 to v5。只要你有 v4 的配置经验,照着指南操作,大多数时候都能顺利完成。

4. 解决 npm audit 问题

开源仓库的安全问题日趋严重,GitHub 和 NPM 都会帮我们检查依赖,并且根据已知的安全问题列表发出警告。所以在安装依赖时,我们经常能看到类似下面这种警告信息:

found 5 moderate severity vulnerabilities
  run `npm audit fix` to fix them, or `npm audit` for details

这个时候,我们应该执行 npm audit 查看所有的审计结果,可能得到如下的报告:

┌───────────────┬──────────────────────────────────────────────────────────────┐
│ Moderate      │ Regular expression denial of service                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ glob-parent                                                  │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ >=5.1.2                                                      │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ @vue/cli-service [dev]                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ @vue/cli-service > webpack-dev-server > chokidar >           │
│               │ glob-parent                                                  │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://npmjs.com/advisories/1751                            │
└───────────────┴──────────────────────────────────────────────────────────────┘

这个报告说明有问题的包是 glob-parent,它由 webpack-dev-server 引入,又因为 @vue/cli-service 而最终成为项目的依赖。很明显,它是 @vue/cli 的组成部分,是前端工具链的一环。那么通常来说,它的危害层级很低,多半不会影响到项目整体安全性。

如果是 node.js 项目,就要小心了,服务器和后端都是安全重灾区,后果可能会很严重。这个时候,你可以往上翻,找到这些报告的最前面,如果可以修复,NPM 会告诉你应该运行什么命令更新问题依赖。如果不能修复,就要自己想办法了。

比如,你可以移除出问题的依赖,不过多半不可行。或者,我比较常用的方案是,在 GitHub 上找到依赖仓库,自己 fork 一份,然后升级其中的依赖,然后发布一个我个人的版本,接下来就用我自己的版本。等到官方修复后,再用回官方版本。

5. 总结

最后按惯例总结一下。依赖是我们软件产品的重要组成部分,它们的版本事关重大,必须给予足够的关注。至少,每个月要检查一次依赖的安全审计问题,如有需要,就升级依赖。

我们自己写代码的时候,也要遵守 Semver 的规定,适时上调版本号,让版本号能表示代码的发展情况。

分类
生活

郑州挺住,郑州加油

我是郑州人,暂时出不了力,只能贡献精神力量了。

郑州挺住,郑州加油!

感谢为我们祈祷的朋友,感谢抢险救灾的英雄,感谢一起做出贡献的人。

分类
css

正确使用 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 世界》呢,都送完了,还得再买……)


参考阅读:

分类
ffmpeg

解决 FFMPEG 合并视频没有声音的问题

FFMPEG 是个非常强大的视频工具,操作音视频必备。

使用 FFMPEG 合并音视频很简单,只需要把待合并的文件名都放在列表文件里,然后使用 -f concat 连接即可。

列表文件是纯文本,格式如下:

# 可以用 # 写注释
file '/path/to/file1.mp4'
file '/path/to/file2.mp4'
....

然后调用命令,其中,files.txt 就是列表文件:

ffmpeg -f concat -safe 0 -i files.txt -c copy -y output.mp4

解决没有声音的问题

这些只是基础,实际应用要复杂的多。比如今天遇到一个问题,合并后的视频没有声音。

第一步,很快搜到 Concatenating videos with ffmpeg produces silent video when the first video has no audio track 这个答案。按照里面的说法,因为第一个视频(即封面视频)没有声音,所以后面就都没有声音。解决方案是,给第一个视频添加一条空白音轨,这样就不影响其它视频的声音了。

答案里的方法是:

ffmpeg -i INPUT -f lavfi -i aevalsrc=0 -shortest -y OUTPUT

试了一下不好使,而且合并时大量报错:

[aac @ 0x55e495967e80] decode_band_types: Input buffer exhausted before END element found
Error while decoding stream #0:1: Invalid data found when processing input

仔细阅读答案的评论,有位同学说:

For the silent audio make sure to match the channel layout and sample rate of the other inputs. For example, 44.1kHz stereo audio: -i anullsrc=cl=stereo:r=44100

生成空白音轨的时候,要确保声道和采样率与其它输入视频一致。比如,44.1KHz 立体声:-i anullsrc=cl=stereo:r=44100

似乎有点关系。我立刻使用 ffprobe 查看源视频的信息:

ffprobe -i input.mp4 -show_streams -select_streams a -loglevel error

得到:

sample_rate=24000
channels=2
channel_layout=stereo

于是,我修改前面的空白音轨生成方式,这次终于成功了:

ffmpeg -i input.mp4 -f lavfi -i anullsrc=cl=stereo:r=24000 -shortest -y input-new.mp4

一点推测

  • FFMPEG 会以第一个视频为基底,往上合并其它视频,所以第一个视频的音轨就很重要
  • 合并视频前最好先进行转码,然后 -c copy 就好了。

其它 FFMPEG 操作笔记在:FFMPEG 笔记

参考资料:

分类
puppeteer

在 Puppeteer 里使用代理服务器科学上网

使用 Puppeteer 录制视频的时候,如果服务器在国内,可能会有一些网站打不开。这个时候,我们可以要求 Puppeteer 使用代理服务器。

0. 配置科学上网

参考两篇旧文,其实原理一样,只是用的软件不一样:

配置完成之后,通过浏览器应该可以正常访问。

1. 使用代理服务器

启动 Puppeteer 的时候,可以传入参数 args,进行各种调整,完整的列表请参考:List of Chromium Command Line Switches

关于代理服务器,有若干个选项,我们要用的是 --proxy-server,方法是:

puppeteer.launch({
  args: [
    '--proxy-server=socks5://127.0.0.1:1080',
  ],
});

2. 使用 PAC 文件

但是这样所有流量都会走代理服务器,也不符合我们的期待,所以最好使用 PAC 文件。参数名称是:--proxy-pac-url,但请注意,因为 Chromium 的 bug,这个参数只在 headless: false,即有界面的时候才会生效。好在我们是为了录视频,所以本来就要打开界面。

所以最终的启动代码就是:

puppeteer.launch({
  headless: false,
  args: [
    '--proxy-server=socks5://127.0.0.1:1080',
    '--proxy-pac-url=http://localhost/autoproxy.pac',
  ],
});

分类
js

用 express.js 实现流式输出 HTTP 响应

0. 前言

我们先来总结一下客户端与服务器端的数据交互方式:

  1. ajax,即 XMLHttpRequest,最常见的数据交互方式,适用于较轻量级的一次性请求,比如用户登录、CRUD 等。
  2. SSE,服务器端事件,适用于有大量服务器端推送、且不需要从客户端发送数据的需求,比如盯股票行情
  3. WebSocket,适用于上下行数据都很多、都很频繁的情况,比如在线聊天、网络游戏等

单从技术角度来看,大概是这几个。根据需求变化,我也可以组合出更多方案。比如,类似 Showman 生成视频这种需求,耗时很长,对服务器资源占用很高,就比较适合:

  1. 发起请求,产生一条任务记录,进入队列
  2. 服务器上运行独立的进程,从队列中取任务出来执行,并记录日志
  3. 客户端不断检查任务进度

1. 需求

今天的需求,介于轻量一次性,与需要服务器长时间执行之间,即 FastTest 中的发布功能。这里,我们有以下需求:

  1. 管理员点击 Publish 按钮,开始发布静态网站
  2. 服务器需要先保存数据,然后调用 webpack 发布所有语言版本
  3. 只有少数管理员会使用此功能,服务器压力不大
  4. 管理员希望能了解发布进度,及每一步的状态

所以,提交任务后等待服务器慢慢跑就不合适;等待请求完成一次性看到所有结果也不合适;最合适的,就是基于长链接,不断获取返回信息,并在前端实时看到进度。也即是:流式输出 HTTP 响应。

2. 实现

2.1 后端

后端实现我选择 node.js + express.js,在后端领域,这两个我比较熟悉。express.js 是在 node.js 网络模块上进行封装得来,使用起来很简单,也支持原生 node.js 方法。

首先我们创建一个服务器:

const express = require('express');
const app = express();
const port = 3100;

app.listen(port, () => {
  console.log('FastTest Admin API at: ', port);
});

然后,我们在需要流式返回响应的接口里设置相应头 Content-type: application/octet-stream。接下来,只要我们不断向输出流写入内容就可以了。哦,对了,结束的时候,我们还要关闭输出流。

app.post('/data', async(req, res, next) => {
  res.setHeader('Content-type', 'application/octet-stream');
  
  // 第一次返回
  res.write('Local data saved. Start to build dist。files.\n');

  // 数次返回
  for (const item of items) {
    await doSomething(item);
    res.write(`${item} done successfully.\n`);
  }

  // 最后,全部完成
  res.write('All done.');
  // 关闭输出流
  res.end();
});

2.2 axios

axios 是很流行的 ajax 库,它进行了 Promise 封装,用起来很方便。这里我们要用 onDownloadProgress(即 axios 对 XMLHttpRequest.progress 的封装)获取下载进度,它会在每次服务器返回响应字符串的时候更新,我们只需要截取上次响应之后,这次响应新增的内容,即可。

function publish(data, onDownloadProgress) {
  return axios.post('/data', data, {
    onDownloadProgress,
  });
}

async function doPublish() {
  if (isLoading.value) {
    return;
  }

  isPublishing.value = true;
  message.value = status.value = null;

  try {
    const { cases, lang } = store.state;
    let offset = 0;
    await publish({ cases, lang }, ({ target: xhr }) => {
      // responseText 包含了从一开始到此刻的全部响应内容,所以我们需要从上次结束的位置截取,获得新增的内容
      const { responseText } = xhr;
      const chunk = responseText.substring(offset);
      // 记录这一次的结束位置
      offset = responseText.length;
      currentStatus.value = chunk;
    });
    status.value = true;
    currentStatus.value = '';
    message.value = 'Published successfully.';
  } catch (e) {
    message.value = 'Failed to publish. ' + e.message;
  }
  isPublishing.value = false;
}

2.3 Vue3

相对来说,Vue3 的部分最容易。这里我用了 animate.css 的 flash 效果,让信息更显眼。除此之外,就是简单的赋值。

<template lang="pug">
.alert.alert-info.mb-0.me-2.py-1.px-3.animated.flash.infinite.slower(
  v-if="currentStatus",
) {{currentStatus}}
</template>

3. 效果演示

流式输出效果演示

4. 部署

一般来说,我们很少会直接用 node.js 当服务器,多半会启动 node.js 服务,然后用 nginx 反向代理给用户访问。这里需要注意,nginx 默认会将响应内容存入缓冲区,然后批量返回给客户端。这会导致流式输出无效,变成常规的执行完毕后一次性输出。

所以我们必须修改 nginx 的配置:

# 仅展示有关配置
location /data {
        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://localhost:3100;
        # 关闭代理缓冲区,所有响应实时输出
        proxy_buffering off;
}

5. 总结 & 扩展阅读

前些天我帮朋友做了个小项目,叫 FastTest。项目很简单,不过我主动扩展了技术选型,使用了非常多的技术,都是浅显入门的程度,非常适合用来做基础入门学习。本文也是从这个项目中提炼出来的。建议大家有空的时候看看,拉到本地,跑起来试试看效果。

文中的几段代码,分别位于:

项目的详细介绍请见:超全面全栈入门项目:fastest

扩展阅读

分类
js

检查超宽元素的脚本

有时候我们制作页面,搞着搞着发现超宽,出现横向滚动条。于是我们就要想办法调整样式,但是往往超宽的只有那么一两个元素,并不是很好找,所以我就写了下面一个脚本,在页面里跑一下就能找到超宽的元素,然后针对性调整一下样式就可以了:

// 这里的 375 主要针对基于 iPhone 6 开发移动端页面时
function traverse(parent) {
  let target;
  for (const elem of parent.children) {
    const rect = elem.getBoundingClientRect();
    const {left, width} = rect;
    if (left + width > 375) {
      target = elem;
      break;
    }
    target = traverse(elem);
    if (target) {
      return target;
    }
  }
  if (target) {
    return target;
  }
}

使用的时候,打开页面的开发者工具,将这段代码复制到 console 里面,然后执行 traverse(document.body) 就可以找到超宽的元素,然后想办法调整它即可。

当然我们也可以继续用这个函数探索可疑元素,找到更具体的超宽元素;或者找到其它超宽元素。这些就留给大家自行探索吧。

分类
作品

超全面全栈入门项目:fastest

TL;DR.

前些天做了个覆盖面很广的项目:FastTest。涉及到静态网站、node.js 后端 API、响应式、多语言、Vue3 全家桶、前端工具链、nginx 配置等一系列技术,都是很基础的应用,相信对大家入门很有帮助。

GitHub:https://github.com/meathill/fasttest

欢迎阅读学习,有任何问题均可提问或提 issue。


前些日子有个朋友找到我,想请我帮他做个项目。我看了一眼需求,不太想接:这个项目太小,要多了不合适;要少了感觉又不值当——有那时间还不如砍两把胖虎。

后来我仔细一想,感觉这个项目很适合拿来做教程,而且是非常全面的全栈入门教程。

先来看需求。他的需求很简单:开发一个测速应用。用户打开页面,点击按钮,然后下载一些东西,下载完成之后,告诉他他的网速是多少。与之前的测速产品不同,这个应用不下载大文件,而是从各种 CDN 下载 JS 库和 SDK,因为大部分 web 应用都依赖这些资源,所以可能更接近真实体验。

关于需求我们不深入讨论(我觉得挺有道理,但也未必有道理到哪儿去……),只看实现。为了满足这个需求,我们需要开发这些东西:

  1. 静态页,让用户能够测速。这就需要:
    1. HTML+CSS+JS,静态资源
    2. 响应式,支持桌面和移动端
    3. 多语言支持
  2. 后台,能调整待测资源、修改翻译、控制广告、查看数据。这就需要:
    1. 友好的 Admin panel:vue 全家桶
    2. 后端 API:express.js + node.js server
    3. 自动发布静态页(webpack 前端工具链)
  3. 部署到服务器:
    1. 静态服务供给用户端页面和资源
    2. SPA 服务,提供后台 Admin Panel 给管理员
    3. 反向代理 express.js API
    4. 配置 CDN
  4. 其它,比如版本管理、前端预处理工具,等等

这些东西可以说是非常全栈,除了没有移动 App、没有数据库操作,其它 Web 开发的东西都用到了。而且涉及到技术也都很基础,没有特别深入的东西。再加上,这个项目会上线,会迭代,是个真实产品。所以,很适合拿来做教程,大部分观众,不管是什么背景,都可以拿来入门。

于是我就答应了,然后断断续续在直播的时间把它做了出来。当然还有一些问题,不过大部分功能都就绪了。

项目放在 GitHub 上:https://github.com/meathill/fasttest,有需要的同学请自由取用。对其中任何技术点有问题都可以提问或者开 issue。

网站地址在 https://afasttest.com

视频还需要一些时间来整理,将来慢慢放出来吧。着急且有时间的同学可以自行从百度网盘下载:https://pan.baidu.com/s/1KPuCM-9gPd0hQsr5Df_PnA 提取码: w8f2。

分类
服务器端

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}
分类
linux

在 Fedora 34 上启动 VNC DISPLAY

大家可以先阅读 使用 Node.js 驱动 FFmpeg 在 Linux + vncserver 下完成视频录制 了解产品目标和技术选型。

前两天在系统更新里看到 Fedora 34 发布,作为更新党,我当然迫不及待就升级了。升级过程蛮顺利的,升级后,系统里的“在线账户”也能正常走 VPN 了,感觉还蛮好的。

然后,前两天需要调试录视频的程序,发现新系统的 tigerVNC 有一个巨大的变化:不再支持用 vncserver 命令创建虚拟显示器,必须用 systemctl start service,目的是方便绑定系统启动,因为很多服务器的运维需要自动化。

不过这可苦了我。我是系统运维菜鸡,基本只能照抄文章,搞了半天也没搞好。不过感谢开源,在 GitHub issue 里讨论的只言片语让我知道了其实 vncserver 是个脚本,它调用的其实是 Xvnc 这个命令。

那就好办了,我开始按图索骥,寻找 vncserverXvnc 之间的关联。最终找到解决方法如下:

  1. 修改 /etc/X11/Xwrapper.config,加入 allowed_users=anybody。这样才能直接使用 Xvnc 创建虚拟显示器,不然会报告只有 console 用户才能创建的错误。
  2. 使用 vncpasswd 命令创建密码文件,创建后的密码文件位于 ~/.vnc/passwd
  3. 然后用 Xvnc :5 -geometry 1280x720 -PasswordFile ~/.vnc/passwd 创建显示器,跟之前的命令很类似,不过需要 -PasswordFile 选项指定密码
  4. 使用 VNC viewer 登录 VNC,输入密码。(我不知道这一步是否必须)
  5. 可以继续使用 DISPLAY=:5

不过问题并没有完美解决,虽然我的 puppeteer JS 能跑,FFmpeg 也能录。但是 DISPLAY=:5 firefox https://cn.bing.com 只会在当前屏幕打开窗口,不知道为什么。留待以后解决吧。

最后吐槽下,这种稳定版里换大版本的行为真的要不得,开源团队也不能滥用自己的地位。


参考阅读