Marco Nie - 2021年1月
https://blog.niekun.net/2021/01/
you are the company you keep...
-
创建新文件 in Linux
https://blog.niekun.net/archives/2086.html
2021-01-28T08:17:25+08:00
下面介绍几种常用的命令行创建新文件的方法。touch:touch test1.txt
>:> test2.txt
cat:cat /dev/null > test3.txt
echo:echo > test4.txt
vim:vim test5.txt
:wq
使用中选择最简单的方式即可。
-
node.js 入门教程之三 -- npm
https://blog.niekun.net/archives/2085.html
2021-01-26T15:45:00+08:00
调用 node.js 文件node.js 有内建的 module system 模块系统,一个node.js 文件可以导入其他 node.js 文件内定义的功能。语法如下:const library = require('./libs')和导入其他第三方模块类似使用 require 指令,需要指定 node.js 文件路径。以上示例表示导入当前目录下的 libs.js 文件。js 文件中默认定义的 objects 等元素是私有的,不能够被外部使用。在被导入的文件中,需要在引用前 expose 暴漏出需要被外部使用的功能。需要使用到 module system 的 module.exports API 来实现。当给一个 object 或 function 赋予 exports 属性时,就表明这些元素可以暴漏给外部。有两种方法来实现。第一种方法是将某个 object 直接赋值给 module.exports,module.exports 是 module system 的一个 object 用来将某个 object 暴漏出来,且只能在一个文件中赋值给某一个元素,下面是一个示例:libs.js 文件:const person = {
name: 'marco',
age: 20
}
module.exports = person;main.js 文件:const libs = require('./libs');
console.log(libs.age);
//OUTPUT:
//20上面示例中,我们将 libs.js 文件的 person object 赋值给了 module.exports,在 main.js 中调用 libs.js 文件后,main.js 中的 libs object 就相当于 person object。我们可以发现,这种定义方法可以将某个 node.js 文件中的一个 object 暴漏给外部使用。第二种方法是将需要暴漏的元素定义为 exports 的 properties,这种方式可以将多个 objects 或 function 暴漏给外部。libs.js 文件:const person = {
name: 'marco',
age: 20
}
exports.person = person;
exports.car = {
brand: 'bmw',
color: 'red'
}main.js 文件:const libs = require('./libs');
console.log(libs.person.age);
console.log(libs.car.brand);
//OUTPUT:
//20
//bmw以上示例中,我们先在 libs.js 中定义了两个 object,并都作为 exports 的一个 property,可以看到有两种方式定义。在 main.js 中调用后,通过调用 libs object 对应的 properties 名称即可调用对应暴露的元素。npm 包管理器npm 是 node.js 的标准包管理器。一开始,npm 作为下载和管理 node.js 包的依赖的工具,现在也成为了 JavaScript 前端开发工具。下载包如果项目中有 package.json 文件,可以通过下面指令自动安装所有在文件中定义的模块,安装路径为 node_modules 文件夹内:npm install
安装某一个模块包:npm install <package-name>
要安装某个包的特定版本需要加上 @ 标记:npm install cowsay@1.2.0
在项目中安装的包会自动添加条目到 package.json 文件中。也可以在安装时使用 --save 选项来添加到文件。更新包更新项目中所有包:npm update
更新某一个包:npm update <package-name>
更新 npm 本身到最新版:npm install -g npm@latest
执行任务package.json 文件内支持定义指定的命令行指令,通过下面的语法来执行:npm run <task-name>
指令定义在 scripts 块内,例如:{
"scripts": {
"start": "uname -r"
}
}此时我们在终端执行下面命令:$ npm run start
Debugger attached.
> start
> uname -r
5.8.0-38-generic
Waiting for the debugger to disconnect...可以看到通过 run 定义命令的名称就可以执行对应的命令。国内源由于默认 npm 的包安装地址使用的是国外服务器所以国内下载速度很慢,推荐替换为国内镜像地址,使用下面命令替换为国内腾讯云镜像:npm config set registry http://mirrors.cloud.tencent.com/npm/
验证当前使用的地址:npm config get registry
如果返回https://registry.npm.taobao.org,说明镜像配置成功。包安装路径当使用 npm 安装包时,可以定义两种安装模式:local install 逻辑安装global install 全局安装默认情况下当我们使用下面指令安装包:npm install chalk
包会安装到当前项目路径下的 node_modules 文件夹内。此时 npm 会将对应包的信息写入 package.json 文件内的 dependencies 块内,如:{
"dependencies": {
"chalk": "^4.1.0",
"inquirer": "^7.3.3",
"minimist": "^1.2.5",
"progress": "^2.0.3"
}
}全局安装通过 -g 标记实现:npm install -g chalk
使用全局安装模式时,npm 不会将包安装到项目路径下,而是 global location 路径下。通过 npm root -g 命令可以返回 golbal 路径的地址,Linux 默认地址为:/usr/local/lib/node_modules,Windows 默认地址为:C:\Users\YOU\AppData\Roaming\npm\node_modules。可执行程序当安装一个包后,我们通过 require 关键词来调用模块:const inquirer = require('inquirer')
当安装的包是可执行程序的时候呢?当安装的包含有可执行程序时,程序会放在 node_modules/.bin/ 文件夹内,关于如何运行这些可执行程序,我们通过 cowsay 包来演示。首先安装 cowsay,这里安装到 global 路径下:npm install -g cowsay
我的 global 路径地址为:/opt/node-v15.5.1-linux-x64/lib/node_modules,可执行文件被安装在 /opt/node-v15.5.1-linux-x64/bin/ 目录下:cowsay -> ../lib/node_modules/cowsay/cli.js查看属性可以看到此可执行文件是指向 cli.js 的一个链接。通过 npx 命令可以方便的执行此程序,不需要提供程序所在路径:$ npx cowsay wow
_____
< wow >
-----
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||package.json 文件package.json 文件是其项目的一个声明,它可以用来做一些完全没有关联的事情。例如,它是配置工具的仓库,也存储着 npm 安装的包的版本信息。下面是最简单的文件形式:{}
文件需要遵守 json 格式,否则无法被程序读取其定义的内容。没有什么内容是必须的,所以可以是一个简单的大括号。如果你要做一个通过 npm 分享的 node.js 包,那么 package.json 文件需要定义一些必须的属性以供了解这个包的信息,后面会做详细介绍。下面是另一个示例:{
"name": "test-node"
}以上定义了一个 name property,定义了这个 app 或 package 的名称。文件和项目文件在同一文件夹下。下面是一个更加复杂的示例:{
"name": "test-project",
"version": "1.0.0",
"description": "A Vue.js project",
"main": "src/main.js",
"private": true,
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"unit": "jest --config test/unit/jest.conf.js --coverage",
"test": "npm run unit",
"lint": "eslint --ext .js,.vue src test/unit",
"build": "node build/build.js"
},
"dependencies": {
"vue": "^2.5.2"
},
"devDependencies": {
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1",
"babel-eslint": "^8.2.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-jest": "^21.0.2",
"babel-loader": "^7.1.1",
"babel-plugin-dynamic-import-node": "^1.2.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.5.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"chalk": "^2.0.1",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.0",
"eslint": "^4.15.0",
"eslint-config-airbnb-base": "^11.3.0",
"eslint-friendly-formatter": "^3.0.0",
"eslint-import-resolver-webpack": "^0.8.3",
"eslint-loader": "^1.7.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-vue": "^4.0.0",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.6.1",
"html-webpack-plugin": "^2.30.1",
"jest": "^22.0.4",
"jest-serializer-vue": "^0.3.0",
"node-notifier": "^5.1.2",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"portfinder": "^1.0.13",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"rimraf": "^2.6.0",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^0.5.8",
"vue-jest": "^1.0.2",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2",
"webpack": "^3.6.0",
"webpack-bundle-analyzer": "^2.9.0",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": ["> 1%", "last 2 versions", "not ie <= 8"]
}包含了很多的属性设置:version 表示当前版本号name 设置包名称description 对 app 简要的描述main 设置 app 的入口文件private 如果设置为 true 可以防止包被无意的发布到 npmscripts 定义可以运行的脚本命令dependencies npm 依赖包列表devDependencies 定义开发使用的 npm 包列表engines 设置 app 运行的 node 版本browserslist 指定支持的浏览器版本以上所有的属性都可以被 npm 或其他工具使用。常用属性下面介绍一些常规使用的属性,包括你在本地开发的项目也可以使用。name 定义app 名称:"name": "test-project"
name 必须小于 214 个字符,不能含有空格,只能包含小写字母,横杠- 或下划线_。如果将包发布到 npm,会生成一个独有的链接。author 定义作者信息:"author": "marco <me@niekun.net> (https://niekun.net)"
或者: "author": {
"name": "marco",
"email": "me@niekun.net",
"url": "https://niekun.net"
}contributors 定义代码贡献者信息,可以包含多人: "contributors": [
{
"name": "marco",
"email": "me@niekun.net",
"url": "https://niekun.net"
}
]bugs 定义反馈 bug 的链接,一般是 GitHub issue 页面:"bugs": "https://github.com/whatever/package/issues"
homepage 定义包的主页链接:"homepage": "https://whatever.com/package"
version 定义当前包的版本:"version": "1.0.0"
此属性遵守 semantic versioning (semver) notation 语义版本标记语法。也就是由三个数字表示:x.x.xlicense 定义包的授权信息:"license": "MIT"
keywords 定义关于这个包实现功能的关键词,是一个数组: "keywords": [
"learning",
"nodejs"
]使用 keywords 可以帮助别人找到你的包,或者在 https://www.npmjs.com/ 网站上搜索关键词。description 定义一个对此 app 的简短描述:"description": "a node.js beginner guide"
repository 定义项目仓库地址:"repository": "github:whatever/node-project"
注意如果不是使用的 GitHub 也可以定义其他工具:"repository": "gitlab:whatever/node-project"
也可以明确的定义版本控制系统信息: "repository": {
"type": "git",
"url": "https://github.com/whatever/testing.git"
}main 设置包的入口:"main": "./main.js"
private 如果设置为 true,可以防止包被无意的发布到 npm:"private": "true"
scripts 定义可执行的 node 脚本: "scripts": {
"start": "uname -r",
"test": "echo abcd"
}定义的脚本是命令行程序,通过 npm run xxx 执行,如:npm run start
dependencies 定义 app 需要安装的 npm 依赖包: "dependencies": {
"chalk": "^4.1.0",
"inquirer": "^7.3.3",
"minimist": "^1.2.5",
"progress": "^2.0.3"
}当通过 npm install <PACKAGENAME> 安装包,会自动将包写入 package.json 文件的 dependencies 块。devDependencies 定义 app 开发需要的 npm 依赖包:"devDependencies": {
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1"
}和 dependencies 的区别是这里定义的包只是在开发设备上安装,而不需要在执行设备上安装。开发包通过以下命令安装:npm install --save-dev <PACKAGENAME>
engines 设置此包运行的 node 版本和其他命令版本: "engines": {
"node": ">= 6.0.0",
"npm": ">=3.0.0"
}browserslist 定义 app 支持的浏览器版本:"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]以上定义的意义是:支持所有类型浏览器的最近两个版本且需要至少有 1% 的占有率,不包括小于等于 IE8 版本的浏览器。浏览器占有率信息来自于 https://caniuse.com/ 统计。依赖包版本信息在 dependencies 和 devDependencies 中定义了依赖包列表,每个包都定义了版本信息,如:^0.13.0 或 ~3.0.0,通过版本号前的符号可以们定义接受那些版本的升级,下面介绍版本标记的用法:无标记,如 1.0.0 定义只安装此版本的包latest 定义安装最新版本的包^ 只能升级到不改变最左边第一个非零数字的版本,如定义 ^0.13.0,通过 npm update 可以升级到 0.13.1 或 0.13.2,但不能升级到 0.14.0,定义 ^1.13.0,能够升级到 1.13.1 或 1.14.0,不能升级到 2.0.0~ 只能升级最后一位数字变化的版本,如 定义 ~1.13.0,能够升级到 1.13.1,但不能升级到 1.14.0> 接受大于定义的版本号的更新>= 接受大于等于定义的版本号的更新< 接受小于定义的版本号的更新<= 接受小于等于定义的版本号的更新- 设置一个版本范围,如:1.0.0 - 2.0.0|| 组合设置,如:< 2.0.0 || > 3.0.0更加详细的定义方法参考:https://nodejs.dev/learn/semantic-versioning-using-npmpackage-lock.json 文件在 npm 5 版本中 npm 引入了 package-lock.json 文件。前面介绍了 package.json 文件,它是一个通用的被广泛使用的配置文件。package-lock.json 文件的目的是更加确切的追踪安装的依赖包的版本,以用来 100% 复制开发者的安装环境到其他安装者设备上,即使某些包已经够了更新版本。这解决了一个 package.json 遗留的问题,在 package.json 中你可以定义某个包可接受的版本升级范围,版本格式遵守 semantic versioning (semver) notation 语义版本标记语法,例如:~1.13.1 可以升级到 1.13.2,但不可以升级到 1.14.0^1.13.1 可以升级到 1.14.0,但不能升级到 2.0.01.13.1 只能安装 1.13.1 版本,不能升级到其他任何版本你发布的包中并不包含开发环境中的 node_modules 文件夹因为它占用空间很大。当在其他设备中通过 npm install 命令安装依赖包时,竟会遵循上面定义的版本范围安装支持的最新版本到设备中,这就会导致用户安装的版本和你开发环境的版本不一致,可能导致 bug 的出现。package-lock.json 中定义了你当前开发环境安装的各种依赖包的准确版本,这样通过 npm install 命令安装的就是其中定义的准确版本。这一概念并不是 npm 独创的,其他编程语言也在使用类似的模式,如 php 的 Composer。package-lock.json 文件需要打包在仓库中,以供其他人使用。当使用 npm update 更新包时,package-lock.json 中的定义也会同步更新。下面是一个 package-lock.json 文件的示例,当我们执行 npm install cowsay 时,会创建以下内容:{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.
0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"cowsay": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/cowsay/-/cowsay-1.3.1.tgz"
,
"integrity": "sha512-3PVFe6FePVtPj1HTeLin9v8WyLl+VmM1l1H/5P+BTTDkM
Ajufp+0F9eLjzRnOHzVAYeIYFF5po5NjRrgefnRMQ==",
"requires": {
"get-stdin": "^5.0.1",
"optimist": "~0.6.1",
"string-width": "~2.1.1",
"strip-eof": "^1.0.0"
}
},
"get-stdin": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.
1.tgz",
"integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/
is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"minimist": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10
.tgz",
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
},
"optimist": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
"requires": {
"minimist": "~0.0.1",
"wordwrap": "~0.0.2"
}
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "^3.0.0"
}
},
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
},
"wordwrap": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
}
}
}我们安装的 cowsay 有如下依赖,它们会被自动安装:get-stdinoptimiststring-widthstrip-eof以上的依赖包可能也会有它们各自的其他依赖包,会自动安装相关其他包,每个包的依赖包在 requires 属性中定义。所有的包以字母排序顺序在文件中。每个包都有 version 属性定义了各自当前安装的版本,resolved 定义了包地址,integrity 字符串用来验证包。包版本管理及安装下面的指令查询当前安装的所有包及其依赖包:npm list
输出信息示例:❯ npm list
/Users/joe/dev/node/cowsay
└─┬ cowsay@1.3.1
├── get-stdin@5.0.1
├─┬ optimist@0.6.1
│ ├── minimist@0.0.10
│ └── wordwrap@0.0.3
├─┬ string-width@2.1.1
│ ├── is-fullwidth-code-point@2.0.0
│ └─┬ strip-ansi@4.0.0
│ └── ansi-regex@3.0.0
└── strip-eof@1.0.0当然也可以直接打开 package-lock.json 文件查看,但这样不太方便观察结构。npm list -g 用来查看 global 安装的包。如果只想查询顶层的包,也就是你主动通过 npm install 安装的那些包,可通过 npm list --depth=0 查询:❯ npm list --depth=0
/Users/joe/dev/node/cowsay
└── cowsay@1.3.1也可以单独查询某个包:❯ npm list cowsay
/Users/joe/dev/node/cowsay
└── cowsay@1.3.1使用下面命令查询某个包当前发布的最新版本:❯ npm view cowsay version
1.4.0要安装某个包的特定版本需要加上 @ 标记:npm install cowsay@1.2.0
查看某个包的所有历史版本列表:$ npm view cowsay versions
[
'1.0.0', '1.0.1', '1.0.2',
'1.0.3', '1.1.0', '1.1.1',
'1.1.2', '1.1.3', '1.1.4',
'1.1.5', '1.1.6', '1.1.7',
'1.1.8', '1.1.9', '1.2.0',
'1.2.1', '1.3.0', '1.3.1',
'1.4.0'
]通过 npm update 可以更新所有已安装的包,更新版本规则遵循 package.json 定义的版本升级范围,同时会更新 package-lock.json 文件内的当前包版本信息。想要查询当前有哪些包有更新版本时,可以执行 npm outdated 命令:其中有些更新是大版本更新, npm update 并不会更新到这些大版本,因为这些更新可能包含有重大的变化。如果要强制更新到最新的版本包括大版本更新,可以按顺序执行下面的命令:npm install -g npm-check-updates
ncu -u
npm update使用下面命令卸载某个包:npm uninstall <package-name>
添加 -S 或 --save 标记删除 package.json 中的相关信息。如果要删除的包是一个开发包,在 devDependencies 中定义过,则需要添加 -D 或 --save-dev 标记来删除相关信息:npm uninstall -S <package-name>
npm uninstall -D <package-name>如果包是 global 安装的,则卸载需要添加 -g 标记:npm uninstall -g <package-name>
npm local 和 global 安装本地包和全局包的区别是本地包通过 npm install 安装到当前项目下的 node_modules 文件夹内,全局包通过 npm install -g 安装到系统路径下,路径地址可通过 npm root -g 查询。在程序中,只能 require 本地包:require('package-name')
通常情况下推荐所有的包都以本地模式安装。这可以让不同的程序使用各自不同版本的包。更新一个全局包会同时影响所有使用这个包的 app。当一个包含有可执行程序时,应该安装为 global 全局包。这样所有的项目都可以调用执行。npm dependencies 和 devDependencies当通过 npm intall 安装包时,表示将包作为 dependency。包信息会自动写入 package.json 文件的 dependencies 块内。当安装时添加 -D 标记,表示将包作为 devdependency 安装。包信息会自动写入 devDependencies 块内。开发包的目的是用来开发程序时使用,作为产品运行时是不需要的。执行 npm install 会默认安装这些开发包。如果不需要安装开发包需要加上 --production 标记:npm install --production xxx
npx 包运行器npx 可以用来运行 node.js 程序。node.js 开发者通常将大部分含有可执行程序的包作为 global 包发布,这样就使得可执行程序在系统 PATH 路径下可以被直接执行。但这样的弊端就是不能安装不同版本的包。使用 npx 命令可以自动寻找 node_modules 文件夹内的对应的名称的可执行程序,而不需要知道程序具体的路径,也不需要以 global 方式安装包。npx 另一个很好的功能是可以直接运行某些命令而不需要提前安装它们,还可以通过 @ 标记来执行不同版本的同一命令。cowsay 是一个很好的示例来说明 npx 的使用,如果我们以 global 模式安装 cowsay,可以通过下面方式执行:cowsay "wow"
以上命令只有通过 global 方式安装才能够正常执行,否则会报错提示命令不存在。如果以 local 方式安装 cowsay,可以通过 npx 执行:npx cowsay "Hello"
通过 @ 标记来执行不同版本的命令:npx node@10 -v #v10.18.1
npx node@12 -v #v12.14.1npm 也可以直接执行来自 url 的任意代码片段,而不仅限于 npm 官方渠道发布的包。下面示例是执行部署在 glist 的包:npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32
可执行程序路径定义在 package.json 文件的 bin 属性内。glist 包含内容如下:当然执行不受控制网络的代码需要多加注意。
-
服务器部署 WebDAV 服务
https://blog.niekun.net/archives/2074.html
2021-01-22T11:16:00+08:00
WebDAV(Web-based Distributed Authoring and Versioning) 一种基于 HTTP 1.1协议的通信协议。它扩展了HTTP 1.1在 GET、POST、HEAD等几个HTTP标准方法以外添加了一些新的方法,使应用程序可对Web Server直接读写,并支持写文件锁定(Locking)及解锁(Unlock),还可以支持文件的版本控制。简单说 webdav 就像一个网盘,可以远程访问他的目录名对其文件进行读写操作。WebDAV 允许客户端进行下列操作:处理服务器上 WebDAV 发布目录中的资源具有正确权限的用户可以在 WebDAV目录中复制和移动文件修改与某些资源相关联的属性。例如,用户可写入并检索文件的属性信息锁定并解锁资源以便多个用户可同时读取一个文件。但每次只能有一个人修改文件搜索 WebDAV 目录中的文件的内容和属性下面介绍如何在服务器上部署 WebDAV 服务。这里通过 nginx 来代理。编译 nginx我们通过 nginx 来代理 webdav 服务,nginx 自带有 ngx_http_dav_module 模块,但是其不支持一些 webdav 的 method 如:PROPFIND, OPTIONS, LOCK, UNLOCK。可以通过第三方模块来完整支持 webdav 的特性。下载以下两个第三方模块:nginx-dav-ext-module:https://github.com/arut/nginx-dav-ext-moduleheaders-more-nginx-module:https://github.com/openresty/headers-more-nginx-module以上两个模块需要在编译时通过 --add-module 参数来引入模块,同时需要包含 --with-http_dav_module 模块,否则编译会报错。从源码编译 nginx 参考我之前的教程:https://blog.niekun.net/archives/30.html我使用的完整的编译参数如下:./configure --prefix=/opt/nginx-1.19.6 \
--user=nginx --group=nginx \
--with-compat --with-file-aio --with-threads \
--with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module \
--with-mail --with-mail_ssl_module \
--with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module \
--add-module=../echo-nginx-module \
--add-module=../ngx-fancyindex \
--add-module=../headers-more-nginx-module \
--add-module=../nginx-dav-ext-module配置文件nginx 编译安装完成后,需要配置 conf 文件来使 webdav 生效。我提前已经设置了一个单独的子域名来访问 webdav 服务,且使用 ssl 加密。首先建立 webdav 文件夹并设置正确的权限,否则在读写时会提示权限不足:mkdir /home/www/webdav
chown -R www-data:www-data /home/www/webdav如果想要限制用户访问,可以使用 ngx_http_auth_basic_module 模块来建立账号访问,具体参考:https://blog.niekun.net/archives/730.html完整配置文件如下:dav_ext_lock_zone zone=foo:10m;
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name webdav.xxx.xxx;
include my-server/ssl;
# 限制访问
auth_basic "Restricted Access";
auth_basic_user_file ../users/.adminpasswd;
# webdav 目录
root /home/www/webdav;
client_body_temp_path /opt/nginx/client_body_temp;
# webdav 设置
dav_access user:rw group:rw all:r;
dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND OPTIONS LOCK UNLOCK;
dav_ext_lock zone=foo;
create_full_put_path on;
# 优化大文件上传
send_timeout 3600;
client_body_timeout 3600;
keepalive_timeout 3600;
lingering_timeout 3600;
client_max_body_size 2G;
location / {
# 创建文件夹操作时结尾添加斜杠
if ($request_method = MKCOL) {
rewrite ^(.*[^/])$ $1/ break;
}
# 移动文件夹操作时结尾添加斜杠
if (-d $request_filename) {
rewrite ^(.*[^/])$ $1/;
set $md /;
}
set $x $http_destination$request_method;
if ($x ~ [^/]MOVE) {
more_set_input_headers -r "Destination: ${http_destination}${md}";
}
}
# 拒绝 Windows 或 macos 多余文件上传到 webdav 路径
location ~ \.(_.*|DS_Store|Spotlight-V100|TemporaryItems|Trashes|hidden|localized)$ {
access_log off;
error_log off;
if ($request_method = PUT) {
return 403;
}
return 404;
}
location ~ \.metadata_never_index$ {
return 200 "Don't index this drive, Finder!";
}
}注意第一句 dav_ext_lock_zone 要放在 http 块内。否则会报错。配置文件修改好后,使用下面指令测试配置是否正确:nginx -t
如果返回 ok 重启服务即可:systemctl restart nginx
客户端连接nginx 配置好 webdav 模块并启动后,可以尝试在客户端访问。Windows 的 file explorer 和 macos 的 finder 都可以直接连接 webdav。Windows 端在 file explorer 中点击 home - easy access - map as drive:在弹出窗口中点击 connect to a web site:点击 next 在地址栏输入服务器 nginx 定义的 webdav 访问地址:点击 next 后如果设置了 auth_basic 会提示要求输入账户和密码,输入账户密码后就进入了 webdav 目录了,下面就可以测试新建文件,修改文件等操作。注意 Windows 中默认只有 https 方式访问的地址才可以设置 auth,否则不会弹出输入账户和密码的提示框,而是直接提示无法访问此地址。如果想要开放 http 方式的 auth 验证,需要修改注册表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters\BasicAuthLevel 的值为 2,然后重启系统即可,此键值定义为:0 - Basic authentication disabled1 - Basic authentication enabled for SSL shares only2 or greater - Basic authentication enabled for SSL shares and for non-SSL sharesmacos 端参考链接:Making Native WebDAV Actually Work on nginx with Finder and ExplorerNginx repair of WebDAV functionalityModule ngx_http_dav_moduleUsing Basic Authentication with Windows 7 and Windows Vista WebDAV Client
-
修复 ubuntu vmware 虚拟机无法访问主机共享目录
https://blog.niekun.net/archives/2071.html
2021-01-15T16:32:00+08:00
今天发现在 Ubuntu 20.04 虚拟机内无法访问设置的共享文件夹,在 /mnt/hgfs 目录下是空的,检查虚拟机设置并没有什么问题。最后发现是虚拟机没有自动挂载共享目录,命令行下进行挂载即可。首先查看当前设置的共享目录有哪些:$ vmware-hgfsclient
Development
Downloads
InstallationPackage挂载主机共享路径到虚拟机对应路径下,设置所有用户可访问:$ sudo vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other
执行以上命令后,发现共享文件夹出现了:$ ls /mnt/hgfs/
Development Downloads InstallationPackage以上就是解决 VMware Linux 虚拟机没有自动挂载共享目录的方法。
-
c++ 类型转换
https://blog.niekun.net/archives/2066.html
2021-01-14T11:58:00+08:00
Implicit conversion 隐式转换当一个数据复制为兼容格式的类型时,隐式转换可以自动完成。请看下面示例:double a = 100.1;
int b;
b = a;
cout << b << endl;
//output:
//100以上示例中,我们将 double 类型的数据复制给 int 类型的变量,不会引起语法报错,这就是 Implicit 隐式类型转换,也叫 standard conversion 标准转换。标准转换对一些基本的数据类型有效,能够对一些 numerical 数值类型的数据间进行转换,如:double to int,int to float,short to double,int to bool 等,也可以在 pointer 指针类型间转换。从小一些的整型如 short 转换到 int 类型,或者从 float 类型转换到 double 类型,这种转换过程叫做 promotion 晋升操作。这种转换可以确保原始数据完整的复制到目标数据类型中。其他数学运算类型间的转换可能不一定会完整的保留原始数据,下面是几种情况举例:一个负整数转换为 unsigned 类型,结果为 unsigned 类型数据所能表达的最大值数据转换为 bool 类型的数据时,对于数值类型原始数据为 0 时,对于指针类型指针数据为 null pointer 时,对应转换为 false。原始数据为所有其他情况时,转换结果为 true。浮点型数据转换为整型数据,数据会被截取整数部分,如果数据结果超出目标数据类型所能表达的最大值,会得到 undefined我们可以看到,隐式转换可能会带来数据精度的丢失,编译器此时会提示一条 warning 警告。可以通过使用 explicit conversion 显式转换来避免警告信息。class 的隐式转换class 中的隐式转换通过以下三个 function 控制:单参数的 constructors 允许从一个特定类型的数据隐式转换构造为一个 object通过等号操作符 Assignment operator 复用来隐式转换类型传播符 Type-cast 来隐式转换为特定的类型下面示例解释各种方式的含义:class A {};
class B {
public:
B(A a) {} //constructor: conversion from A
B operator=(A a) {return *this;} // assignment operator: conversion from A
operator A() {return A();} // type-cast: conversion to A
};
int main()
{
A a; // instance a
B b = a; // constructor b from a
B c(a); // constructor c from a
b = a; // assignment a to b
a = b; // convert b to A type and assignment to a
return 0;
}以上实例分别介绍了三种隐式转换的方式。构造器的一个传入参数为 A 类型数据,也就是等同于可以将 A 类型因素转换为 B 类型通过操作符复用将等号= 重新定义,等号右边的为 A 类型数据时,以A类型数据作为构造器参数返回 B 类型 object 指针当执行 B 类型数据赋值给 A 类型时,会通过类型传播符定义的 function 返回 A 类型 object构造器初始化 object时,当只有一个参数时,就相当于把传入数据转换为对应 object 类型了。上面示例中可以看到,有两种方法来构造 object:B b = a; // constructor b from a
B c(a); // constructor c from a以上两种方式都是将 a 作为初始化参数构造 B 类型的 object。编写了等号操作符复用后,对某个指定类型的外部 object 进行等号操作时就会将其转换为当前 object 类型,而不会报错。通过类型传播符可以将 object 转换为其他指定类型的 object。关于操作符复用的语法参考我之前的教程:https://blog.niekun.net/archives/1920.htmlexplicit 关键词当调用一个 function 时,对于其传递参数 c++ 允许进行隐式转换,这在一些情况下会引起一些问题,因为我们并不是所有情况下都希望自动进行转换的。在以上示例中加入 function:void function(B b) {}
此 function 有一个 B 类型的参数,但是在实际调用中,由于 B 中定义了 A 的隐式转换相关模块,所以我们在这里可以将 A 类型数据作为传入数据:fun(a);
实际中我们可能并不需要这种转换,我们希望的是这里只能将 B 类型数据作为传入参数。通过关键词 explicit 来定义 constructor 可以实现这个需求,修改 B 的 constructor:explicit B(A a) {}
再次执行程序,会发现以下几个指令会报错:B b = a;
fun(a);通过 关键词 explicit 定义 constructor 的 class 不能通过 assignment 赋值符来初始化 object,也不能对 function 的传入参数进行隐式转换。explicit conversion 显式转换c++ 是一种严格区分数据类型的语言,对于那些会影响数据本身的转换,需要进行 explicit conversion 显式转换也叫做 type-casting。在过去的语法中有两种常规的 type-casting 方式:double e = 10.11;
int f = int(e);
int g = (int)e;第一种叫做 function 样式,第二种叫做 c-like C语言模式。对于那些基础数据类型的数据,这种转换模式没有什么问题,但这种语法对于 class 和 pointer 类型数据也会不加判断的进行转换,从而导致运行时的 runtime error。为了控制这些在 class 间进行转换的过程,新版 c++ 提供了 4 种 casting operators 传播符来供不同场景下使用:dynamic_cast <new_type> (expression)reinterpret_cast <new_type> (expression)static_cast <new_type> (expression)const_cast <new_type> (expression)dynamic_castdynamic_cast 只能用于某个 class 或(void*) 的指针。他的作用是确保转换后的目标类型指针指向的是一个完整有效的 object,而不是空 object。例如,从 derived class 指针转换为 base class 指针。但是对于多态化的 polymorphic class(包含 virtual 元素的 class),当且仅当指向的 object 是一个完整有效的目标 object 类型,使用 dynamic_cast 就可以从 base class 指针转换为 derived class 指针,请看如下示例:class Base { virtual void dummy() {} };
class Derived: public Base {int a;};
int main()
{
try {
Base *b1 = new Base;
Base *b2 = new Derived;
Derived *d;
d = dynamic_cast<Derived*>(b1);
if (d == 0)
cout << "null pointer on first type cast" << endl;
d = dynamic_cast<Derived*>(b2);
if (d == 0)
cout << "null pointer on second type cast" << endl;
} catch(exception e) {
cout << e.what() << endl;
}
return 0;
}
//output:
//null pointer on first type cast以上示例中,我们建立了 Base class 和 Derived class,其中 Base 含有一个 virtual function。然后我们创建两个 Base 类型指针,两个指针分别预分配类型为 Base 和 Derived。在之前的 c++ 教程中提到过可以新建 base 类型的变量然后使用 derived 类型数据,需要了解的可以查看:https://blog.niekun.net/archives/1927.html。然后我们创建一个 Derived 类型指针,使用 dynamic_cast 分别将上面建立的两个 Base 类型指针转换为 Derived 类型。由于 b2 虽然是 Base 类型指针,但是我们预分配内存类型为 Derived 类型,所以它其实包含了 Derived object 所有属性。这样转换后的类型为完整的 Derived 类型指针。所以 d 指针不为空。而 b1 完全是 Base 类型指针,所以转换后的类型是不完整的 Derived 类型指针,所以赋值后结果为空。dynamic_cast 可以将任意指针转换为 void* 类型指针。static_caststatic_cast 可以转换任意相关联 class 类型的指针。不仅仅从 derived 到 base,也可以从 base 到 derived。不会判断是否转换到目标指针是完整的数据类型,所以完全由编程人员判断转换操作是否是安全的。相比于 dynamic_cast 有更大适用范围。以下示例语法不会报错:class Base {};
class Derived: public Base {};
Base * a = new Base;
Derived * b = static_cast<Derived*>(a);b 指针会得到一个不完整的 Derived 类型数据,运行时可能报错。因此,使用 static_cast 不仅可以转换那些直接支持隐式转换的 class 指针,也可以在那些不支持转换的 class 间进行转换。我们测试在其他数据将进行转换:double a = 12.23;
int b = static_cast<int>(a);
cout << b << endl;
//OUTPUT:
//12以上,我们将 double 类型的数据转换为 int 类型,这种转换可以直接通过隐式转换完成,但是使用显式转换语法实现更加明确清晰。但前提是原类型和目标类型必须是 related 有关联的 object 类型。reinterpret_castreinterpret_cast 可以将任意类型指针转换为其他任意类型指针,甚至是完全没有关联的两个类型。转换的过程就是将源数据的二进制数据复制到新指针地址。相比于 static_cast 有更大适用范围。以下代码可以正常执行:class A { /* ... */ };
class B { /* ... */ };
A * a = new A;
B * b = reinterpret_cast<B*>(a);此时 b 指针指向的是一个和 B 类型完全不相干的数据,此时访问 b 指向的数据是不安全的。const_castconst_cast 可以操作 const 类型的指针数据,例如当一个 function 需要非 const 类型传入数据时,可以通过 const_cast 进行转换。请看下面示例:void test(int a) {
cout << a << endl;
}
const int a = 10;
test(a);以上示例中,调用 test function 会报错,因为传入参数需要是非 const 类型的数据。修改以上代码:const int a = 10;
int *b = const_cast<int*>(&a);
test(*b);
//output:
//10通过 const_cast 将 const int 转换为 int,这样就可以在 function 中使用了。参考链接Type conversions
-
VMware 虚拟机 NAT 网络下配置端口转发到主机
https://blog.niekun.net/archives/2061.html
2021-01-12T11:06:00+08:00
今天安装了 Ubuntu 虚拟机用来做 node.js 开发,在选择网络模式时我选择了 NAT 模式,因为这样可以在主机处于不同网络环境下使虚拟机都有着同一个 IP 地址,方便管理。但是在此时中发现,虚拟机可以正常访问主机及外网,但是主机无法通过分配的 NAT 地址来 ssh 访问虚拟机。查询后发现这是正常现象。可以通过设置端口转发来将虚拟机端口映射到主机端口来实现对虚拟机的访问。首先给虚拟机设置一个静态 ip 地址:然后打开虚拟网络编辑器:选中 NAT 对应的网卡,这里是 vnet 8,点击 NAT 设置:在端口转发栏点击添加:设置要转发到的主机端口及对应虚拟机 IP 和端口等信息,这里我需要转发 ssh 的 22 端口到主机的 2222 端口:点击确定保存设置即可。设置好后就可以通过访问主机本地 2222 端口实现 ssh 访问虚拟机了。可以根据需要设置多个端口转发。
-
vs code 通过 ssh 远程连接服务器调试
https://blog.niekun.net/archives/2053.html
2021-01-12T09:01:00+08:00
安装扩展插件: Remote - SSH可以实现远程文件访问终端调试远程端口转发到本地https://code.visualstudio.com/docs/remote/ssh-tutorial
-
node.js 入门教程之二 -- Getting Started
https://blog.niekun.net/archives/2051.html
2021-01-09T15:48:00+08:00
V8 JavaScript EngineV8 是 chrome 浏览器的 JavaScript Engine 名称,它是用来在 chrome 中获取和执行 JavaScript 代码的工具。V8 提供了一个 JavaScript 的运行环境,DOM 及其他 web API 是由 browser 提供的。JavaScript Engine 是独立于其所寄生的浏览器的,这一特性才使 node.js 得以诞生。node.js 选择了 V8 Engine 且随着 node.js 的不断成长,大量服务端通过 JavaScript 编写的程序使用 v8 Engine。基于 V8 同样可以创造桌面应用,例如 Electron 项目。不同的浏览器有着各自的 Engine:Firefox has SpiderMonkeySafari has JavaScriptCore (also called Nitro)Edge was originally based on Chakra but has more recently been rebuilt using Chromium and the V8 engine.所有的 Engine 都遵守 ECMAScript 标准。在最初时候 JavaScript 被作为一种 interpreted language 解释型语言。但是现代 JavaScript Engine 不在单单 interpret 解释代码,而是要 compile 编译代码。从 2009 年起,Firefox 将 SpiderMonkey 编译器集成到浏览器中,从此大家都开始引入这一方案。JavaScript 代码被 V8 通过内部的 just-in-time (JIT) 编译器编译来提高执行效率。命令行运行 node.js 代码一般情况下在安装好 node.js 后,可以通过 node 命令执行 node.js 代码,需要传递被执行文件的路径:node app.js
触发信号的使用当在终端中执行 node.js 程序时,可以通过快捷键 ctrl C 退出程序。当我们下面介绍如果通过更加程序化的方式实现退出动作。首先介绍第一种比较直接粗暴的方式:process.exit()
通过调用 precess 模块的 exit function ,进程会立即被强行终止。这意味着任何它当前挂起的任务,正在传输的数据,对文件的访问和读写等都会被以一种不友好的方式终止掉。process 不需要通过 require 的方式引入,他是默认被包含的。你可以传递一个整型数字作为 exit code 退出码,不写的话默认为 0:process.exit(1)
不同的退出码有不同的意义,你可以使用不同的退出码来和其他程序响应。各种退出码的意义:https://nodejs.org/api/process.html#process_exit_codes你也可以提前设置退出码:process.exitCode = 1
在后续执行到 process.exit 时会返回此处设置的值。下面介绍更加友好的退出方式。在上一节中,我们介绍了通过 http 模块搭建一个简单的 web 服务器,使用 express 模块也可以搭建 web 服务器,默认没有安装此模块,首先需要通过 npm 进行安装,模块会安装到当前路径下:npm install express
新建 js 文件,内容如下:const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.send('Hi!')
})
const server = app.listen(3000, () => console.log('Server ready'))打开浏览器访问本地 3000 端口即可看到输出。这个 web 服务会一直进行下去,如果调用了 process.exit(),当前的响应会立刻被终止,这是显然不太好的。我们可以通过发送一个 SIGTERM 信号,然后处理对这个信号的响应。响应的动作可以是关闭这个 web 服务,上面的示例增加如下代码:process.on('SIGTERM', () => {
server.close(() => console.log('process termimated'));
});这里我们监控一个 SIGTERM signal 作为一个 event 事件,然后响应关闭服务器的动作。process.on 叫做 signal handler 信号响应器,用来定义接受的信号及对应的响应动作。这里定义当 SIGTERM 信号被触发后,执行关闭 server 动作及输出 log。SIGTERM 是告诉系统将进程友好终止的信号。此信号被触发后,首先会执行 signal handler 信号响应器所定义的响应动作,也就是关闭 web 服务器,最后再终止对应进程。SIGTERM 信号一般是由进程管理器,如:upstart,supervisord 发出。这里我们在程序内部触发此信号:setTimeout(() => {
process.kill(process.pid, 'SIGTERM')
}, 3000);process.kill() method 用来将 signal 信号发送给对应 pid 进程。语法如下:process.kill(pid, signal)
pid 进程 IDsignal | 要发送的信号, 大写字母的字符串或数字. 默认为: 'SIGTERM'当 pid 对应进程不存在时将会 throw 一个 error。process.pid 返回值为当前程序的进程 ID。虽然 process.kill 名称看起来是要终止一个进程,但是他的确仅仅是一个 signal sender 信号发送器。具体会有什么样的结果完全取决于发送的是什么信号,以及对应信号的响应。kill method 的文档:https://nodejs.org/api/process.html#process_process_kill_pid_signal以上示例中,加入对 SIGTERM 信号的触发和响应后,效果为:首先开启一个 web 服务器,3 秒后会尝试关闭此服务器并输出 log,此处会等待服务器完全结束当前 request 才会结束,最后终止进程:$ node express.js
Server ready
process termimated注意 Windows 下 signal handler 信号响应器会无效,以上示例如果在 Windows 下执行不会输出最后一行 log 信息。常见的信号:SIGTERM 告诉系统需要友好的终止某个进程SIGKILL 立即结束对应进程,类似于 process.exit() 的效果beforeExit 进程退出前会触发此信号exit 进程退出时会触发此信号下面是一个对部分信号触发时间的测试:process.on("beforeExit", () =>
console.log("before exit"));
process.on("exit", () =>
console.log("exit"));
console.log("running");以上程序执行后输出信息如下:$ node process.js
running
before exit
exit更多信号的意义参考:signal(7) — Linux manual page更多使用方法参考:Signal events环境变量的读取node.js 的核心模块 process 提供了 env properties 寄存了当前程序开始时的所有 environment variables 环境变量。执行以下命令可以输出所有变量:console.log(process.env);
查看某一个变量:console.log(process.env.HOME);
//-> /home/marco也可以设置自定义的环境变量并给其赋值:console.log(process.env.TEST);
process.env.TEST = "dev";
console.log(process.env.TEST);
// output:
// undefined
// devnode.js REPL通常情况下我们将代码写在 js 文件中,然后通过 node 命令执行:node app.js
如果不指定需要执行的 js 文件,则会进入 REPL (Read Evaluate Print Loop) 编程环境,可以通过直接输入指令,逐行执行代码。进入 REPL:$ node
Welcome to Node.js v15.5.1.
Type ".help" for more information.
> 尝试输出 log 信息:> console.log('test')
test
undefined
> 可以发现首先会输出指定的字符串内容,然后输出 undefined,它是 console.log function 的返回值。同样在 REPL 中也可以使用 tab 键来自动补全指令名称。输入 JavaScript object 名称和符号点.,然后点击 tab 键会输出指定 object 的所有 properties。例如 Array:> Array.
Array.__defineGetter__ Array.__defineSetter__ Array.__lookupGetter__ Array.__lookupSetter__
Array.__proto__ Array.hasOwnProperty Array.isPrototypeOf Array.propertyIsEnumerable
Array.toLocaleString Array.valueOf
Array.apply Array.arguments Array.bind Array.call
Array.caller Array.constructor Array.toString
Array.from Array.isArray Array.length Array.name
Array.of Array.prototype
> Array.使用这种方式输出 global objects 全局可用的所有 objects:> global.
global.__defineGetter__ global.__defineSetter__ global.__lookupGetter__ global.__lookupSetter__
global.__proto__ global.hasOwnProperty global.isPrototypeOf global.propertyIsEnumerable
global.toLocaleString global.toString global.valueOf
global.constructor
global.AbortController global.AbortSignal global.AggregateError global.Array
global.ArrayBuffer global.Atomics global.BigInt global.BigInt64Array
global.BigUint64Array global.Boolean global.Buffer global.DataView
global.Date global.Error global.EvalError global.Event
global.EventTarget global.FinalizationRegistry global.Float32Array global.Float64Array
global.Function global.Infinity global.Int16Array global.Int32Array
global.Int8Array global.Intl global.JSON global.Map
global.Math global.MessageChannel global.MessageEvent global.MessagePort
global.NaN global.Number global.Object global.Promise
global.Proxy global.RangeError global.ReferenceError global.Reflect
global.RegExp global.Set global.SharedArrayBuffer global.String
global.Symbol global.SyntaxError global.TextDecoder global.TextEncoder
global.TypeError global.URIError global.URL global.URLSearchParams
global.Uint16Array global.Uint32Array global.Uint8Array global.Uint8ClampedArray
global.WeakMap global.WeakRef global.WeakSet global.WebAssembly
global._ global._error global.assert global.async_hooks
global.buffer global.child_process global.clearImmediate global.clearInterval
global.clearTimeout global.cluster global.console global.constants
global.crypto global.decodeURI global.decodeURIComponent global.dgram
global.diagnostics_channel global.dns global.domain global.encodeURI
global.encodeURIComponent global.escape global.eval global.events
global.fs global.global global.globalThis global.http
global.http2 global.https global.inspector global.isFinite
global.isNaN global.module global.net global.os
global.parseFloat global.parseInt global.path global.perf_hooks
global.process global.punycode global.querystring global.queueMicrotask
global.readline global.repl global.require global.setImmediate
global.setInterval global.setTimeout global.stream global.string_decoder
global.sys global.timers global.tls global.trace_events
global.tty global.undefined global.unescape global.url
global.util global.v8 global.vm global.wasi
global.worker_threads global.zlib
> global.REPL 有一些特殊指令,以 . 开始:.help: shows the dot commands help.editor: enables editor mode, to write multiline JavaScript code with ease. Once you are in this mode, enter ctrl-D to run the code you wrote..break: when inputting a multi-line expression, entering the .break command will abort further input. Same as pressing ctrl-C..clear: resets the REPL context to an empty object and clears any multi-line expression currently being input..load: loads a JavaScript file, relative to the current working directory.save: saves all you entered in the REPL session to a file (specify the filename).exit: exits the repl (same as pressing ctrl-C two times)来自命令行的传递参数在执行某个 js 文件时,你可以传入任意个数的数据作为传入参数供程序内使用。参数可以是独立的,也可以是以 key/value 形式:node app.js jon
node appljs name=jon age=20如果使用第二种方式,则需要对其进行解析,后面会介绍。通过访问 process 模块的 argv property 可以 retrieve 这些传入参数,argv 是一个数组,它的首个元素是 node 可执行文件路径,第二个元素是当前执行的 js 文件路径,其他元素就是传入参数数据。我们新建 argv.js 文件内容如下:process.argv.forEach((val, index) => {
console.log(`${index}: ${val}`);
});执行以下指令:$ node argv.js ABC
0: /opt/node-v15.5.1-linux-x64/bin/node
1: /mnt/hgfs/Development/node.js/argv.js
2: ABC可以看到第三个元素是我们传入的数据。可以通过 array 的 slice method 将所有传入参数单独提取出来:const val = process.argv.slice(2);
slice 语法如下:array.slice(start, end)
将指定的数组范围内的元素生成一个新数组并返回,start 参数为起始元素索引,end 为终止元素索引,end 默认值为原数组最后一个元素。如果传入参数没有定义索引 key:node app.js jon
使用以下方法调用:const val = process.argv.slice(2);
console.log(val[0]);如果传入参数定义了索引 key:node app.js name=jon
此时 args[0] 的值为 name=joe,需要对其进行解析操作,最简单的是使用 minimist 库实现(先通过 npm 安装):const args = require('minimist')(process.argv.slice(2));
console.log(args['name']);
console.log(args['age']);此时我们需要在传入参数的索引 key 前加双横杠--:$ node argv.js --name=jon --age=20
jon
20命令行输出node.js 提供了一个 console 模块提供了很多有用的方法来在命令行下交互信息。它同浏览器下的 console object 类似。最基础的就是 console.log method,可以将传入数据输出为字符串到终端。如果传入一个 object,会将其渲染为 string。你可以输出多个数据,例如:let a = 1;
let b = 2;
console.log(a, b);
//output:
//1 2可以使用连接符来组合字符串和变量:console.log('num1 is ' + a + ', num2 is ' + b)
通过传入变量和一个对应的 format specifier 格式占位符来使格式解析更加明晰:console.log('num1 is %s, num2 is %s', a, b)
%s 格式化一个变量成字符串%d 格式化一个变量成数字%i 格式化一个变量成整数%o 格式化一个变量为 object调用 clear method 可以清空当前终端的信息:console.clear();
count method 可以实现对一个输出字符串的累计计数,输出字符串的同时会显示此字符串已经输出的次数,执行以下代码:console.count('count1')
console.count('count2')
console.count('count1')
console.count('count1')输出结果为:count1: 1
count2: 1
count1: 2
count1: 3结合 time 和 timeEnd method 可以计算执行一段代码花费的时间:const doSomeThing = () => console.log('test');
const measureTime = () => {
console.time();
doSomeThing();
console.timeEnd();
}
measureTime()
//output:
//test
//default: 5.937ms以上示例中,调用 console.time() 开始计时,调用 console.timeEnd() 终止计时并返回总时间。通过 console.log 等输出到终端的信息称之为 standard 标准输出:stdout,通过 console.error 会输出到 stderr stream 流,不会显示到终端而是输出到了 error log。可以通过 escape sequences 转义序列来给输出信息添加颜色,执行下面示例:console.log('\x1b[31m%s, \x1b[33m%s', 'hello', 'world')
输出如下:\x1b[ 后跟对应颜色的数字编号即可:30m Black31m Red32m Green33m Yellow34m Blue35m Purple36m Cyan37m White但这种方法比较麻烦,输入也不太友好。最好的方式是通过 Chalk 库来实现这个功能,它不仅可以修改颜色,还可以设置粗体,下划线,斜体等效果。需要通过 npm install chalk 安装库。使用方法如下:const chalk = require('chalk')
console.log(chalk.yellow('hello world'))这种方法使代码更加清晰可读。更多 chalk 使用方法参考:https://github.com/chalk/chalk创建进度条Progress 模块可以用来在终端创建进度条效果,通过 npm install progress 安装。下面示例创建一个 10 级的进度条,每 100 ms 进一格,完成后取消定时器:const ProgressBar = require('progress')
const bar = new ProgressBar(':bar', { total: 10 })
const timer = setInterval(() => {
bar.tick();
if (bar.complete) {
clearInterval(timer);
}
}, 100);每调用一次 bar.tick() 就会前进一格,可以在程序中添加进度条来提示进度。命令行输入下面介绍如何在命令行下进行输入输出交互。node.js 从 version 7 开始提供了 readline module 可以在程序运行期间获取一个可读取的 stream 流的数据,如 process.stdin stream,一次读取一行数据。请看下面示例:const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
readline.question("what is your name\n", name => {
console.log(`Hi, ${name}`);
readline.close();
});
//output:
//what is your name
//marco
//Hi, marcoquestion() method 显示第一个参数数据到命令行,然后等待用户输入数据,当用户输入数据并敲回车后会自动调用定义的 function,并将输入数据作为传入数据。readline 的更多用法参考:https://nodejs.org/api/readline.html但需要输入一个密码时,我们希望在输入时可以隐藏输入的信息,我们可以通过 Inquirer.js 模块来实现需求。Inquirer.js 项目地址:https://github.com/SBoudrias/Inquirer.js通过 npm 安装:npm install inquirer
inquirer 可以实现很多输入交互方式,如:常规输入,密码,列表,选项框等,下面举例说明:const inquirer = require('inquirer')
var questions = [
{
type: 'input',
name: 'name',
message: 'what is your name\n',
default: function () {
return 'Doe';
}
},
{
type: 'input',
name: 'age',
message: 'how old are you?\n'
},
{
type: 'input',
name: 'phone number',
message: 'please input your phone number\n',
validate: function (value) {
var pass = value.match(
/^1[3|4|5|6|7|8|9][0-9]{9}$/
);
if (pass)
return true;
else
return 'Please enter a valid phone number';
}
},
{
type: 'password',
name: 'password',
message: 'your password\n'
}
];
inquirer.prompt(questions)
.then(answers => {
console.log(`Hi, ${answers['name']}, you age is ${answers['age']}, your phone number is ${answers['phone number']}`);
});questions 数组定义了需要输入的内容,可以定义多项输入信息,每个输入内容都是一个独立的 question object 用来定义这个 question 具体的模式,下面介绍最常用的几个 properties:type 提示框的类型,默认为 input,可选项有: input, number, confirm, list, rawlist, expand, checkbox, password, editorname 定义了此 question 的名称,用来在 answers 中读取数据时使用message 定义一个打印输出的字符串提示信息default 如果没有输入内容,则使用此处定义的默认值,可以是变量或 functionvalidate 用来判断输入信息是否满足一定条件,如果满足则返回 true,不满足返回预定一个提示信息并停留在当前 question 等待用户修改输入内容更多使用 question object 的属性参考:https://github.com/SBoudrias/Inquirer.js#question在上面的示例中,通过 validate function 判断电话号码是否有效,通过 string 的 match method 使用正则表达式来匹配信息。match method 语法如下:string.match(regexp)
注意 regexp 是一个字符串,需要使用斜杠/来包围,如:var str = "abcdefab";
if (str.match(/ab/))
console.log(true);
else
console.log(false);
//output:
//true还可以使用修饰符来设置正则匹配限制,可用的修饰符有 g 和 i,放在正则表达式结尾斜杠/后面。g 修饰符表示 global 全局查找,当使用 g 时,所有匹配结果将会返回,当不使用 g 时,只有第一个匹配的结果会返回,且包含其 groups,index 等信息:var str = "abcdefab";
console.log(str.match(/ab/));
console.log(str.match(/ab/g));
//OUTPUT:
//[ 'ab', index: 0, input: 'abcdefab', groups: undefined ]
//[ 'ab', 'ab' ]i 修饰符用来设置忽略大小写:var str = "abcdefabAB";
console.log(str.match(/ab/g));
console.log(str.match(/ab/gi));
//OUTPUT:
//[ 'ab', 'ab' ]
//[ 'ab', 'ab', 'AB' ]当 question object 的 type 定义为 password 类型时,输入时信息会被隐藏。prompt method 用来显示提示框,传入参数就是预定义的 question 数组,其返回值为 promise 类型,所以可以通过 then method 来实现异步响应。promise 的用法参考:https://blog.niekun.net/archives/2011.html这里通过 then method 定义了 success 的 function,传入参数就是用户输入的数据构成的 object,通过 question object 中 name property 定义的名称来索引到具体的某个输入数据。更多 inquirer 示例:https://github.com/SBoudrias/Inquirer.js/tree/master/packages/inquirer/examples
-
node.js 入门教程之一 -- 介绍
https://blog.niekun.net/archives/2043.html
2021-01-08T09:28:00+08:00
Node.js 是一个开源跨平台的 JavaScript 运行环境,它是时下最流行的工具,能够应用于几乎所有的项目。Node.js 运行 V8 JavaScript engine。它是 chrome 的核心,这让 Node.js 可以脱离浏览器运行 JavaScript 代码。一个 Node.js app 运行在一个线程中,不会给每个 request 建立一个进程。Node.js 在其标准库中提供了一个 asynchronous I/O primitives 异步 IO 原生语法库来防止阻塞,同时 Node.js 中的库使用一种 non-blocking paradigms 无阻塞范式,将阻塞作为一种 exception 来处理。当 Node.js 执行 IO 动作,例如读取文件,访问数据库等,不同于等待进程,占用 CPU 资源,Node.js 会在收到 request 响应后再去处理后续操作。这将使 Node.js 可以在一个 server 上同时处理上千条链接而不会引入大量的进程而导致 bug 出现。Node.js 的另一大优势是使用 JavaScript 的前端开发者可以同时编写 server 端的代码而不用学习新的语言。最新的 ECMAScript 标准可以在 Node.js 中使用,你不需要等待用户去更新浏览器。通过使用不同版本的 Node.js 来切换不同的 ECMAScript 标准。Node.js 使用 JavaScript 语言,如果你还不太了解 js 可以参考我之前的 8 篇 JavaScript 教程:JavaScript 入门教程之一 -- 总览海量的第三方库通过 npm 简单的结构帮助 Node.js 生态系统快速的增长。目前 npm 注册超过 1,000,000 个开源库可供免费使用。hello worldweb server 是最常见的 hello world 示例。新建一个 js 文件,内容如下:const http = require('http')
const hostname = '127.0.0.1'
const port = 8080
const server = http.createServer((req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('Hello World!\n')
})
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`)
})以上代码首先引入 http 模块。Node.js 使用 CommonJS module 系统,使用内建的 require 关键词来引入其他文件写好的 module 模块。Node.js 有一个很好的标准库:https://nodejs.org/api/,包含有对 network 的直接支持。createServer method 创建一个新的 http server 并返回它。定义了一个 requestListener function,会被自动添加到 request event。listen method 定义特定的监听地址和端口。当 server 运行起来后, listen method 调用的 function 会被执行,这里我们显示 server 已启动的信息。当收到一个 request,会自动调用 request event,并传入两个 object:一个 request (an http.IncomingMessage object) 和一个 response (an http.ServerResponse object)。这两个 object 是处理 http 访问的核心。request object 提供了 request 请求的信息,在以上示例中没有使用到这个,但是我们可读取 request header 和 data 数据。response object 用来返回数据给发起请求者,在这里通过:res.statusCode = 200
返回一个 200 状态码,表示一次成功的响应。然后我们设置一个 header:res.setHeader('Content-Type', 'text/plain')
最后我们关闭 response 响应,并将响应内容作为 end method 的参数传入:res.end('Hello World\n')
Node.js Frameworks and ToolsNode.js 是一个底层平台,为了让开发更加容易,海量的基于 Node.js 的第三方库通过社区建立。它们中的很多已经非常流行,以下是一些常见的第三方库:AdonisJs: A full-stack framework highly focused on developer ergonomics, stability, and confidence. Adonis is one of the fastest Node.js web frameworks.Express: It provides one of the most simple yet powerful ways to create a web server. Its minimalist approach, unopinionated, focused on the core features of a server, is key to its success.Fastify: A web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture. Fastify is one of the fastest Node.js web frameworks.Gatsby: A React-based, GraphQL powered, static site generator with a very rich ecosystem of plugins and starters.hapi: A rich framework for building applications and services that enables developers to focus on writing reusable application logic instead of spending time building infrastructure.koa: It is built by the same team behind Express, aims to be even simpler and smaller, building on top of years of knowledge. The new project born out of the need to create incompatible changes without disrupting the existing community.Loopback.io: Makes it easy to build modern applications that require complex integrations.Meteor: An incredibly powerful full-stack framework, powering you with an isomorphic approach to building apps with JavaScript, sharing code on the client and the server. Once an off-the-shelf tool that provided everything, now integrates with frontend libs React, Vue, and Angular. Can be used to create mobile apps as well.Micro: It provides a very lightweight server to create asynchronous HTTP microservices.NestJS: A TypeScript based progressive Node.js framework for building enterprise-grade efficient, reliable and scalable server-side applications.Next.js: React framework that gives you the best developer experience with all the features you need for production: hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more.Nx: A toolkit for full-stack monorepo development using NestJS, Express, React, Angular, and more! Nx helps scale your development from one team building one application to many teams collaborating on multiple applications!Sapper: Sapper is a framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem-based routing. Offers SSR and more!Socket.io: A real-time communication engine to build network applications.Strapi: Strapi is a flexible, open-source Headless CMS that gives developers the freedom to choose their favorite tools and frameworks while also allowing editors to easily manage and distribute their content. By making the admin panel and API extensible through a plugin system, Strapi enables the world's largest companies to accelerate content delivery while building beautiful digital experiences.安装在官方下载页面下载对应系统的安装包:https://nodejs.org/en/download/macOS 可以通过 brew 来安装:brew install node
node.js 和浏览器端的区别浏览器端和 node.js 都是使用 JavaScript 作为编程语言,但他们是完全不同的。node.js 可以同时开发前端和后端 application,这样使用一种语言就可以完成所有的开发。浏览器中使用 DOM 结构,或者其他 web 平台的 API 如:cookies,这些在 node.js 中都是没有的,document 或 window 等这些 object 是没有的。使用 node.js 由你来控制 environment,你知道 application 所使用的 node.js 是哪个版本的,相比较于浏览器,你并不知道用户使用了什么浏览器。所以你可以使用更加现代的 ES 标准来编写程序。另一个区别是 node.js 使用 CommonJS 模块系统,浏览器端我们已经可以使用 ES 的标准命令来导入模块了。也就是说 node.js 中使用 require() 而浏览器端使用 import。参考链接https://nodejs.dev/learn/introduction-to-nodejs
-
修复 parallels desktop 16 网络无法连接问题
https://blog.niekun.net/archives/2040.html
2021-01-03T22:40:00+08:00
随着升级到 macOS Big Sur 后,虚拟机 parallels desktop 也更新到了 16 版本。我当然是继续安装破解版啦。但是安装好后,启动虚拟机会有一个报错:Network Initialization Failed,启动后会发现虚拟机无法联网,网络设置中无法正常在各种模式中切换。在寻找解决方案期间,了解到了这可能是由于破解的原因,正版用户没有这个问题。网上有人提供了一个方法,执行一条命令就启动虚拟机就可以正常联网了。但是会导致虚拟机无法访问主机的共享目录及无法访问 USB 设备。而且一旦重启 parallels desktop 后又回恢复之前的状态,我也就没有去做。这两天看到网上终于有人找到的解决方法,测试后的确可用,所以介绍给大家参考。解决的方法都是通过修改相关配置文件实现的。首先需要彻底退出 parallels desktop。网络问题修改 /Library/Preferences/Parallels/network.desktop.xml 文件。在终端或者 vs code 中打开这个文件,找到 <UseKextless> 标签,将其中的内容改为 0:<UseKextless>0</UseKextless>
注意如果你的这个文件内没有这个标签,则需要在 <ParallelsNetworkConfig> 根标签内手动创建它即可。修改完成后保存文件,需要输入账户密码。USB 问题修改 /Library/Preferences/Parallels/dispatcher.desktop.xml 文件。在终端或者 vs code 中打开这个文件,找到 <Usb> 标签,将其中的内容改为 1:<Usb>1</Usb>
修改完成后保存文件,需要输入账户密码。修改完以上两个文件后,重启 parallels desktop 就会发现一切都正常了。