0%

Azure Functions 是微软云提供的部署Api服务的一种功能,利用Azure Functions部署些Api,无需创建/配置服务器环境。即无需先创建 VM 或发布 Web 应用程序。
Azure Function项目自包含运行环境,不依赖外部服务、框架等

安装Azure Functions Core Tools 2.x

  • 安装 .Net Core 2.x SDK

    用于 Windows 的 .NET Core 2.x SDK
  • 使用npm安装Core Tools包
    1
    npm install -g azure-functions-core-tools
  • VS Code Azure Functions 扩展

    搜索 azure functions,或者在 Visual Studio Code 中打开此链接,安装该扩展。

    命令行调试

    安装Azure Functions .NET Worker
    1
    func host start --dotnet-isolated-debug

    创建Functions

  1. 按 F1 键打开命令面板。 在命令面板中,搜索并选择 Azure Functions: Create new project…。
  2. 按照提示对项目进行预设

    Prompt value description
    Select a language for your function app project C# or JavaScript This article supports C# and JavaScript. For Python, see this Python article, and for PowerShell, see this PowerShell article
    Select a template for your project’s first function HTTP trigger Create an HTTP triggered function in the new function app.
    Provide a function name HttpTrigger Press Enter to use the default name.
    Authorization level Function Requires a function key to call the function’s HTTP endpoint.
    Select how you would like to open your project Add to workspace Creates the function app in the current workspace.
  3. 本地调试

    开启F5 / 停止shift+F5
    local.settings.json 文件存储应用设置、连接字符串和本地开发工具使用的设置。 只有在本地运行项目时,才会使用 local.settings.json 文件中的设置。

    关于调用参数

    上面提到本地调试的参数可存于 local.settings.json,取这些参数的方法同取环境变量方法:

  • System.Environment.GetEnvironmentVariable 或 ConfigurationManager.AppSettings (C#)
  • process.env. Settings (js)

    在 Azure Functions 中,没有所有环境通用的基线配置, 即并非所有Azure Function实现都是从本地开发版本部署的,最初是在云中编辑独立解决方案,而不是部署多个版本具有不同的配置。探索 .NET Core 配置系列二

    发布Functions到Azure

    发布功能需要开启Azure Functions: Advanced Creation,在VS Code settings中可以设置,或者发布失败时根据弹窗提示更新设置

  1. 在 Visual Studio Code 中,按 F1 键打开命令面板。 在命令面板中,搜索并选择 Azure Functions: Deploy to function app…。
  2. Create New Function App in Azure
  3. Enter a global unique name for the new function app
  4. select an OS
  5. select a hosting plan

    托管计划consumption plan 和 app service plan等,参考

使用visual studio发布

连接存储队列

“NoSQL” database

非关系型数据库,不适用SQL作为查询语言,不使用数据表格存放数据。

优势:

  • Scalability 可扩展性: by default, non-relational databases are split (or “shared”) across many systems instead of only one. This makes it easier to improve performance at a lower cost.
  • Flexibility 灵活性: new datasets and properties can be added to a document without the need to make a new table for that data.
  • Replication 备用性: copies of the database run in parallel so if one goes down, one of the copies becomes the new primary data source.

MongoDB Atlas

a cloud MongoDB service

1
mongodb+srv://qqs:<password>@clusteraws-vcbnj.mongodb.net/test?retryWrites=true&w=majority

Project with MongoDB

package:

  • mongodb
  • mongoose
    1
    2
    var mongoose = require('mongoose');
    mongoose.connect(process.env.MONGO_URI);
    Mongoose Docs
  • Schema: 一种以文件形式存储的数据库模型骨架,不具备数据库的操作能力
  • Model: 由Schema发布生成的模型,具有抽象属性和行为的数据库操作对
  • Entity : 由Model创建的实体,他的操作也会影响数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mongoose.connect(process.env.MONGO_URI);           //数据库连接

var Schema = mongoose.Schema;
var personSchema = new Schema({ //定义Schema
name: String,
age: Number,
favoriteFoods: Array
})

var Person = mongoose.model('Person',personSchema); //发布Model

var document = new Person({ //创建Entity
name:'David',
age:18,
favoriteFoods:'ice cream',
})

document.save(function(error,data){ //调用实例save方法
console.log('Person document saved') //以回调函数作为最后一个参数
})

如果是Entity,使用save方法,如果是Model,使用create方法,参数是json + 回调函数

1
2
3
4
5
6
7
Person.create({
name:'Ellar',
age:18,
favoriteFoods:'banana'
},callback)

Person.create(arrayOfPeople,callback); //批量创建

Model.find()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// example find all documents whose name is David
Person.find({name:'David'},function(error,data){
if(!error){
console.log(data)
}else{
console.log(error)
}
})

// find方法在非完全匹配情况下的应用,会复杂很多,需要借助不同的方法及其选项,这些方法以$开头
// example find whose name contains 'White'
Person.find(
{
name:
{
'$regex':'White',
'$options':'i'
}
},findcallback
)
// example find whose favoriteFood Array contains 'ice creame'
Person.find(
{
favoriteFood:{
$in:['ice cream']
},
},findcallback
)

// update
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var findAndUpdate = function(personName, done) {
var ageToSet = 20;
Person.findOneAndUpdate(
{name:personName}, /* find filter*/
{$set:{age:ageToSet}}, /* set options*/
{new:true}, /* return option*/
(error,data)=>{
if(error)
done(error);
done(null,data)
}
)
//done(null/*, data*/);
};

// findByIdAndRemove
1
Person.findByIdAndRemove('5d4a500e994a2154010dc67f',function(){})

实现自增长字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var WebSiteSchema = mongoose.Schema({
"original_url":String,
"short_url":Number
})
// model
var Website = mongoose.model('Website', CounterSchema)
// counter
var CounterSchema = Schema({
_id: {type: String, required: true},
seq: { type: Number, default: 0 }
});
var counter = mongoose.model('counter', CounterSchema);

WebSiteSchema.pre('save', function(){
var self = this;
counter.findByIdAndUpdate(
{_id: 'entityId'},
{$inc:{req:1}},
function(err,data){
if(err){
return next(err)
}
self.short_url = data.seq;
next();
}
)
})

pre是前置中间件操作,相当于其他语境的拦截器,在保存WebSite实例前调用计数器counter的findByIdAndUpdate。pre作用在Schema级别上,因此要在使用Schema生成model前定义才会生效。

区间条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* collection{
* userId:String,
* from:Date,
* to:Date,
* limit:Number
*}
*/
var getLog = function(collection,callback){
let queryCondition = {userId:collection.userId}
if(collection.from){
queryCondition.date = (queryCondition.date || {});
queryCondition.date['$gte'] = collection.from;
}
if(collection.to){
queryCondition.date = (queryCondition.date || {});
queryCondition.date['$lt'] = collection.to;
}
console.log('query conditions:',queryCondition)
let query = Exercise.find(queryCondition)
if(collection.limit){
query = query.sort({'date': -1}).limit(collection.limit)
}
query.exec(callback)
}

关联查询 population (存目)

Please keep learning —> Linux工具快速教程

测试网络连接

linux 上一般是不装ping的

1
2
3
4
telnet 8.8.8.8 80
curl 8.8.8.8:80
ssh -v -p qqs@8.8.8.8
wget 8.8.8.8:80

拷贝目录

将工作空间目录下的test文件夹拷到labhome下面

1
cp -ri /home/Workspace/test /var/labhome/

  • -r 递归
  • -i 询问是否覆盖

    查找文件

    find [path] -name [filename]
    1
    find / -name filename

    tail

    1
    tail -f filename
    tail命令读取文件内容到标准输出,-f 循环读取,上述命令查找文件中最尾部的内容显示在屏幕上,并且不断刷新,使你看到最新的文件内容

    filename 可以是多个文件 以空格分隔即可

tar

tar 通常是GNU tar,而libarchive库集成了bsdtar,不知道为什么我的windows默认的tar.exe是后者执行下述脚本

1
tar -c * | gzip > Agt.tgz

报异常:tar.exe: Failed to open ‘\.\tape0’
该路径是缺省的设备起始位置(?),因为bsdtar要求不能缺省输出参数 应为
1
tar -cf - * | gzip > Agt.tgz

后台运行

后台运行命令

1
anycommand $

将前台命令放到挂起(stopped)

Ctrl + Z
查看后台工作项目
1
jobs -l

操作后台工作项目

  • 终止后台工作项目 kill jobNumber
  • 调至前台继续运行 fg jobNumber
  • 在后台执行挂起的工作项目 bg jobNumber

    vi查找

    在命令模式下敲斜杆( / )这时在状态栏(也就是屏幕左下脚)就出现了 “/” 然后输入你要查找的关键字敲回车就可以了。
    如果你要继续查找此关键字,敲字符 n 就可以继续查找了。
    敲字符N(大写N)就会向前查询;

    重命名rename

    个人理解: 语法其一 rename 原字符串 新值 范围

    将run.sh重命名为run_bak.sh
    1
    rename run.sh run_bak.sh run.sh
    语法其二 rename ‘option/原字符串模式/新字符串模式/‘ 搜索范围

    统一删除.bak后缀名:
    1
    rename 's/\.bak$//' *.bak
    其中/ /之间是正则表达式,后缀名中的.需要转义

    批量修改文件名为全部小写
    1
    rename 'y/A-Z/a-z/' *

    软件

    1
    2
    3
    sudo npm cache clean -f #----- 先清除 npm cache
    sudo apt-get update #------ 更新源
    sudo apt-get upgrade #------ 更新已安装的包
    对于nodejs npm需要使用n模块升级到最新稳定版本
    1
    2
    3
    sudo apt-get install nodejs npm
    npm install -g n
    sudo n stable
    Nov 25th 遭遇 issue 27711 需将nodejs从v12降至v10
    1
    sudo n 10.16.0
    RPM
    1
    2
    3
    4
    wget https://example.com/file.rpm
    sudo yum localinstall file.rpm
    sudo rpm –ivh file.rpm
    sudo rpm –ivh https://example.com/file.rpm

    查看系统版本

    1
    2
    3
    cat /etc/issue 查看发行版
    cat /etc/redhat-release 查看CentOS版本
    uname -r 查看内核版本

    查看硬件信息

    1
    2
    3
    4
    5
    cat /proc/cpuinfo |more
    cat /proc/meminfo |more
    // 磁盘
    cat /proc/partitions
    fdisk -l

    查看服务列表

    1
    service --status-all

    异常:键入命令不显示(回显关闭)

    1
    2
    3
    4
    // 回显关闭
    stty -echo
    // 回显打开
    stty echo

    定时任务

    查看服务状态
    1
    service cron status
    crontab
    1
    2
    3
    4
    // 列出该用户的计时器设置
    crontab -l
    // 编辑该用户的计时器设置
    crontab -u username -e
    不指定username 则以root用户身份执行

    计划任务分为系统任务调度用户任务调度两类

    系统任务调度:系统周期性所要执行的工作,比如写缓存数据到硬盘、日志清理等 见/etc/crontab时日周月计划。

    用户任务调度:用户可以使用 crontab 工具来定制自己的计划任务。所有用户定义的crontab文件都被保存在/var/spool/cron目录中。其文件名与用户名一致

实际上,crontab文件被设计为不允许(存疑)用户直接编辑,而是通过crontab -e管理
计划的格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed

  • * 表示匹配该域的任意值。假如在Minutes域使用*, 即表示每分钟都会触发事件。
  • / 表示起始时间开始触发,然后每隔固定时间触发一次。例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.
  • - 表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
  • , 表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。

  • ? 只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 ?, 其中最后一位只能用?,而不能使用,如果使用*表示不管星期几都会触发,实际上并不是这样。

  • L 表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。

  • W 表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 。

  • LW 这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。

  • # 用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。

例子:

20 6 pwd 每天的 6:20 执行pwd命令

20 6 8 6
pwd 每年的6月8日6:20执行pwd命令

20 6 0 pwd 每星期日的6:20执行pwd命令

20 3 10,20 pwd 每月10号及20号的3:20执行pwd命令

25 8-10 pwd 每天8-10点的第25分钟执行pwd命令
/15 pwd 每15分钟执行一次pwd命令

20 6 /10 * pwd 每个月中,每隔10天6:20执行一次pwd命令

开启系统cron日志

sudo vim /etc/rsyslog.d/50-default.conf 取消注释

1
cron.log

即可在/var/log/syslog中看到关于CRON的log

ISSUE: No MTA installed, discarding output

cron默认将console output用邮件发送,task有控制台输出但未配置邮件则记如上日志,可以将console output重定向到文件

1
echo "" > /home/QQs/corn.log

若task执行中间过程有报错,报错信息仍会以Email形式发送,非安装邮件服务器不能解决
安装postfix

注意,crontab task 若以root权限执行(syslog中可以看到),root若无重定向日志文件的写权限,仍然会报 “No MTA installed, discarding output”

重载CRON服务

1
sudo service cron reload

启动/关闭
1
sudo service cron start/stop

Ubuntu timezone

显示时间:

1
date -R

某年月日,测试scheduled tasks,总是无法在期望时间触发,经查系统时间为‘Universal Time’(即格林威治时间时间)
输入
1
tzselect 

选择本地时间(shanghai)

永久覆盖本地时间设置
1
cp /usr/share/zoneinfo/Asia/Shanghai  /etc/localtime

设置时间
1
date -s '2020/3/1 18:20:30'

Linux 以一定时间间隔更新内核时间,做上述调整后立即重启,可以将设置写入内核,否则会被更新回来(QQs尚未验证重启操作)

异常:刚启动正常,很短时间后,所有端口无法连接

事实证明,Linux与Windows发生IP冲突,Linux无法竞争过Windows,(反之未验证),现象是刚刚启动时连接正常,很短时间后无法连接。
再次遭遇此问题,Linux虚拟机IP被抢,virtualbox桥接,宿主计算机访问虚拟机无碍,其他计算机可以ping通,telnet不通(目标ip实际上是windows机器)

user

1
su -l USERNAME

chmod

Linux/Unix 的文件调用权限分为三级 : 文件所有者(Owner)、用户组(Group)、其它用户(Other Users)。

例如 owner - RWX, group - RX, other - R_

如上,u 表示该文件的拥有者,g 表示与该文件的拥有者属于同一个群体(group)者,o 表示其他以外的人,a 表示这三者皆是。

1
chmod [ugoa...][[+-=][rwxX]

栗子
1
2
chmod ugo+r file1.txt //将文件 file1.txt 设为所有人皆可读
chmod a+r file1.txt // 同上

其他参数

  • -R 对目录递归变更权限

    查看端口使用

    1
    sudo lsof -i:8080

    curl

    即client url tool curl 的用法指南

dos2unix

windows&linux交叉编译环境下脚本结尾标记可能出错

Bash syntax error: unexpected end of file

使用dos2unix工具转换脚本文件

1
dos2unix task.sh

LoopBack 是一个可扩展的开源Node.js 框架。它可以让我们

  • 无需写任何代码(或少量的代码)来创建REST API
  • 访问任意数据库中的数据甚至是外部的REST API
  • 可以在API上定义关系型数据模型和访问限制(ACL)
  • 在移动APP中使用地理位置,文件访问和推送消息
  • 提供 Android, iOS 和 JavaScript SDKs快速创建有数据支持的应用程序
  • 方便的应用部署,无论在云上还是自己的服务器
1
npm install -g @loopback/cli
1
lb4 app

昨天法国人发我一资料,前置工作参照一教程安装nodejs环境,使用apt-get install完了,node -v 一看,是4.x.x的版本

实际上,这种情况下应该先更新软件的源

1
2
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs

dpkg was interrupted, you must manually run ‘sudo dpkg —configure -a’ to correct the problem

之前调用apt-get install时出现上述异常
需要

1
2
3
cd /var/lib/dpkg/updates
sudo rm *
sudo apt-get update

这里提一下update和upgrade的区别:

update 是更新软件库列表

upgrade 是在上述基础上将本地软件安装升级

处理控制台进程无响应

Ctrl C无法终结,按Ctrl Z将进程转到后台执行,然后ps -ef查看进程列表,kill无响应的进程

ubuntu lts 的IP设置

1
sudo nano /etc/netplan/01-xxxx.yaml

这个配置文件内容如下例

1
2
3
4
5
6
7
8
9
10
network:
version: 2
renderer: NetworkManager
ethernets:
enp0s3:
addresses:
- 192.168.1.100/24
gateway4: 192.168.1.1
nameservers:
addresses: [8.8.8.8, 4.4.4.4]

enp0s3为配置的ethernet(以太网)网络接口 用 ip link show 命令显示网络接口列表
1
2
3
# netplan try 若配置正确 这个命令会应用上面的配置 并提示是否退回之前的设置
sudo netplan apply
# sudo netplan --debug apply

查看发行版本号

1
cat /etc/issue

一切可以用JavaScript实现的,终将用Javascript来实现

Node.js

官方定义

Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js’ package ecosystem, npm, is the largest ecosystem of open source libraries in the world.

Node 是js的一种新的运行环境,基于Chrome V8 js引擎开发,以事件驱动和无阻塞IO模型实现轻量和高效. npm是Node包管理生态系统,目前是世界最大的开源库。

关于CommonJS

CommonJS规范————阮一峰
CommonJS规范是旨在解决Javascript的作用域问题,其规定每个文件就是一个模块,有其自己的作用域,一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见(例外的文件之间分享使用global全局变量)模块必须通过 module.exports 导出对外的变量或接口,通过 require() 来导入其他模块的输出到当前模块作用域中。

require是同步执行的,只有加载完成,才能执行后面的操作 浏览器端一般遵循异步模块定义(asynchronous module definition, AMD)协议

module

上文所述,每个文件就是一个模块,在每个模块/文件内部,都有一个module对象,该对象存在以下属性

  • module.id 模块的识别符,通常是带有绝对路径的模块文件名。
  • module.filename 模块的文件名,带有绝对路径。
  • module.loaded 返回一个布尔值,表示模块是否已经完成加载。
  • module.parent 返回一个对象,表示调用该模块的模块。(可以判断是否为应用入口)
  • module.children 返回一个数组,表示该模块要用到的其他模块。
  • module.exports 表示模块对外输出的值。

Express.js

Express 是一个简洁而灵活的 node.js Web 应用程序框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。

server.js

1
2
3
4
5
6
7
8
9
10
const express = require('express');
const app = express();

app.get('/',function(req, res){
res.send('hello express')
});

const listener = app.listen(8080, function(){
console.log('express app is running on port '+listener.address().port)
})

插一句,require和import

vscode 建议我将上面第一行代码改为‘import express from ‘express’’

Require是CommonJS的语法,CommonJS的模块是对象,输入时必须查找对象属性。
1
2
3
4
declare module.fs{
function stat(){}
//...
}

1
2
3
4
5
6
7
let { stat, exists, readFile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

ES6模块不是对象,而是通过export命令显示指定输出代码,再通过import输入。import的可以是对象定义或表达式等
express封装了http method 和 router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/** A first working Express Server */
app.get('/',function(req, res){
res.send('Hello Express')
})

/** Serve an HTML file */
app.get('/views/index.html',function(req,res){
let absolutePath = __dirname + '/views/index.html'
res.sendFile(absolutePath)
})

/** Serve static assets */
app.use('/public', express.static( __dirname + '/public'))
// 内置中间件函数,访问静态资源文件

/** params add a '?' if the parameter is omissible */
app.get("/api/timestamp/:date_string/:addr_string?",function(req,res){
res.json(req.params.date_string)
})
/** Request Headers */
app.get("/api/whoami", function (req, res) {
var ip = req.header('x-forwarded-for') || req.connection.remoteAddress;
var lang = req.header('Accept-Language');
var software = req.header('User-Agent');
console.log({"ip":ip,"language":lang,"software":software})
res.json({"ip":ip,"language":lang,"software":software});
});

请求参数的获取方式

  • path中的变量,形如/api/user/:userId, 用req.params.userId
  • url参数如?org=dw001&type=1,将直接结构化为req.query对象
  • post请求的RequestBody,使用bodyParser中间件,添加到req.body中
  • req.param(parameterName)方法

中间件middleware

Express是一个自身功能极简,完全是路由和中间件构成一个web开发框架:从本质上来说,一个Express应用就是在调用各种中间件。

1
2
3
4
5
6
7
8
app.get('/now', function(req, res, next){
let now = new Date().toString();
req.time = now;
next();
},
function(req, res){
res.json({time: req.time})
})

大致是express().[method]([path],[middleware],(req,res)=>{…})

可以引用第三方中间件函数

body-parse

将post body内容编码并放入req.body

1
2
3
4
5
6
7
8
var bodyParser = require('body-parser');

app.use(bodyParser.urlencoded({ extended: false }));

/** Get data form POST */
app.post('/name',function(req, res){
res.json({name: req.body.first + ' ' + req.body.last})
})

1
2
3
4
5
6
7
const cookieparser = require('cookie-parser')
const util =require('util')

app.use(cookieparser())
app.get('/getcookie',function(req, res){
res.send(util.inspect(req.cookies))
});

util.inspect类似于JSON.stringify将json对象属性以{key}={value};的字符串格式输出

multer

文件上传

Multer 会添加一个 body 对象 以及 file 或 files 对象 到 express 的 request 对象中。 body 对象包含表单的文本域信息,file 或 files 对象包含对象表单上传的文件信息。 GitHub:multer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var _fs = require('fs') 
var multer = require('multer')

const upload = multer({dest:'/tmp'})
app.post('/files/upload', upload.single('file'), function(req,res){ // image是input [type='file'] 的name属性 或 formdata的field名
console.log(req.files[0])
var des_file = __dirname + '/tmp/' +req.files[0].originalname;
_fs.readFile(req.files[0].path, function(err, data){
_fs.writeFile(des_file, data, function(err){
var response={}
if(err){
console.log(err)
}else{
response={
message:'File uploaded successfully',
filename:req.files[0].originalname
}
}
console.log(response);
res.end(JSON.stringify(response))
})
})
})

Multer 接受一个 options 对象,其中最基本的是 dest 属性,这将告诉 Multer 将上传文件保存在哪。如果你省略 options 对象,这些文件将保存在内存中,永远不会写入磁盘。 关于options

环境变量

服务端口号变量控制

1
2
3
4
// listen for requests
const listener = app.listen(process.env.PORT, function() {
console.log('Your app is listening on port ' + listener.address().port);
});

unix shell prompt:
1
2
3
4
export PORT=1234
echo env|PORT

unset PORT

windows CMD
1
2
3
4
# 设置
set PORT=1234
# 移除
set PORT=

windows powershell
1
2
3
$env:PORT = 1234

del env:PORT

cross-env

从package.json获取版本作为环境变量

1
cross-env REACT_APP_VERSION=$(node -p 'require(\"./package.json\").version')

关于Node.js的系统学习

Node.js的实现的学习才应该是你要学的Node.js本身,而不是无尽的工具和第三方库。

参考官方文档

外部服务访问静态文件也会有跨域问题, 解决方法:

1
2
3
4
5
6
let options = {
setHeaders: function (res, path, stat) {
res.set('Access-Control-Allow-Origin', '*')
}
}
app.use(express.static('public', options))

application performance

使用chrome devtoolProfile和Memory
Easy-Monitor
[阿里Node.js性能平台](https://cn.aliyun.com/product/nodejs)

to be continued…

Tips

path.resolve vs path.join

express-async-errors

Limit repeat requests

AES加密算法 CSC模式:通过密钥和salt(起扰乱作用)按固定算法(md5)产生key和iv。然后用key和iv(初始向量,加密第一块明文)加密(明文)和解密(密文)。

对称加密 Symmetric Encryption

共享密钥加密 私钥加密算法。加密和解密时使用相同密钥,或是使用两个可以简单相互推算的密钥。事实上,这组密钥成为双方或多个成员之间的共同秘密,以便维持专属的通讯联系。

要求雙方取得相同的密鑰是對稱密鑰加密的主要缺點之一
对称加密的速度比公钥加密快很多,在很多场合都需要对称加密。

進階加密标准(Advanced Encryption Standard,AES),对称加密中最流行的算法之一。

非对称加密

公开密钥密码,公钥加密算法。
要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密,另一个则用作解密。使用其中一个密钥拿明文加密后所得密文,只能用相对应个另一个密钥才能解密得到原本明文;甚至连最初用来加密个密钥也不能解。

RSA是最具影响力的非对称加密算法,三个字母是三位创始人的姓氏首字母。RSA算法基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。

阮一峰的文章

RSA算法原理1

RSA算法原理2

非对称加密另有一个用途是数字签名

Digital Signature
网站对敏感内容使用私钥生成摘要信息,该信息使用公钥解密后可以证实为由私钥所生成(?),且反映内容是否已被篡改,故可作为数字签名

为避免不怀好意的第三方冒充网站,提供公钥并发送密文给客户,应运而生”证书中心”(certificate authority,CA)。CA使用其私钥为官方公钥及其他信息加密,生成数字证书(Digital Certificate)。客户使用CA提供的公钥从数字证书中获取真实的网站公钥。

https协议

客户端向服务器发出加密请求。

服务器用自己的私钥加密网页以后,连同本身的数字证书,一起发送给客户端。

客户端(浏览器)的”证书管理器”,有”受信任的根证书颁发机构”列表。客户端会根据这张列表,查看解开数字证书的公钥是否在列表之内。

如果数字证书记载的网址,与你正在浏览的网址不一致,就说明这张证书可能被冒用,浏览器会发出警告”此网站的安全证书有问题 单击此处关闭该网页 继续浏览此网站(不推荐)”。

author

description

license

keywords

dependencies 和 devDependencies, peerDependencies, bundledDependencies, optionalDependencies

  • npm install 命令 不加 —save 不会修改package.json
  • npm install 命令 —save参数将package记入dependencies,—save-dev参数将package记入devdependencies
  • 开发工具如glup,angular脚手架自动安装的jasmine、karma、tslint、chai等,以致于@angular/cli自身,@angular/compiler-cli等工具,以及用于编译器做类型识别的各种@type/XXX 应该放devdependencies
  • 运行环境如Express,前端框架@angular/core以及可选包forms、animation等,弥补浏览器内核、版本等差异的腻子如core.js,应为dependencies 参考Angular Doc: npm packages
  • 封装node.js api第三方工具如操作xml、mail、excel等,是业务不可或缺的,应属dependencies
  • 重新编译electron的如node-ffi私以为其实并不会进入构建结果,应为dependencies

    当有人准备在自己的程序中下载使用你的模块时,估计他们不想下载构建你使用的外部测试或者文档框架,为此宜将这些依赖项放在devDependencies里 npm doc

peerDependencies: peer意为同等地位的人,同龄人。peerDependencies将该模块的树形的依赖关系摊平到宿主的依赖环境中

bundledDependencies: 将依赖的包与当前模块绑定在一起, 如发布一模块做如下配置,npm pack该模块将获得包含”renderized”和”super-streams”的awesome-web-framework-1.0.0.tgz,package version另需在dependencies中指定

1
2
3
4
5
6
7
{
"name": "awesome-web-framework",
"version": "1.0.0",
"bundledDependencies": [
"renderized", "super-streams"
]
}

optionalDependencies 可选的依赖,有的依赖模块依赖于特定的运行环境,因此optionalDependencies的依赖项在安装失败的情况下不会影响整个安装结果

语义版本控制(Semantic Versioning)

MAJOR version when you make incompatible API changes,

MINOR version when you add functionality in a backwards-compatible manner, and

PATCH version when you make backwards-compatible bug fixes.

Use the tilde-character (~) to prefix the version of moment in your dependencies and allow npm to update it to any new PATCH release(补丁).

Use the caret-character (^) to prefix the version of moment in your dependencies and allow npm to update it to any new MINOR release(小版本).

本地依赖

1
"qqsmodule": "file:../CustomModules/qqsmodule"

package-lock

Node Docs: package-lock

  • 记录整个依赖树的具体版本, 即包括了依赖的依赖,
  • 须提交package-lock 在npm install时安装指定的版本
  • 每次npm update时依据package.json中的升级版本设置修改package-lock.json

version lens

VS Code extension 鼠标悬停在dependency的项上,可查看最新版本和依赖项目链接

QQs:TS相比ES————静态类型,代码的可读性和可维护性
TS相比ES不只是静态类型,还有Class Interface Generics(泛型) Enum等
辩证地看,也有它地缺点如学习成本,搭框架地额外成本,与js库的兼容性,额外的编译过程等
官方Doc
在线编译器

调试

方法一

npm install typescript

add tsconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": true,
"outDir": "./dist",
"sourceMap": true
},
"include": [
"src/**/*"
]
}

add .vscode/tasks.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "typescript",
"tsconfig": "tsconfig.json",
"problemMatcher": [
"$tsc"
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

Terminal—Run Task—Choose tsconfig.json

add .vscode/launch.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"version": "0.2.0",
"configurations": [
{
"name": "launch",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/dist/main.js",
"args": [],
"cwd": "${workspaceRoot}",
"protocol": "inspector"
}
]
}

Run Debugging(Choose ‘launch’, the name definited in the launch.json)
方法二

npm i typescript node-ts

add tsconfig.js
1
2
3
4
5
6
7
8
9
10
11
12
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": true,
"outDir": "./dist",
"sourceMap": true
},
"include": [
"src/**/*"
]
}

add .vscode/launch.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"version": "0.2.0",
"configurations": [
{
"name": "Current TS File",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/node_modules/ts-node/dist/_bin.js",
"args": [
"${relativeFile}"
],
"cwd": "${workspaceRoot}",
"protocol": "inspector"
}
]
}

基本类型

  1. number
  2. boolean
  3. string
  4. []
  5. enum
  6. any
  7. void
  8. null 和 undefined
  9. never

关于枚举
定义一组常量

1
2
3
4
5
6
enum Direction {
Up = "↑",
Down = "↓",
Left = "←",
Right = "→",
}

类似map的用法
1
2
3
4
5
6
switch(key){
case Direaction.Up:
console.log('direction is up');
break;
...
}

类似interface的用法, 如 function Foo(direct: Direaction)

“类型谓词”

1
2
3
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}

定义类型保护函数isFish用以区分一个联合类型(Fish | Bird)的变量,依据是Fish类型存在swim属性
其意义无非就是把下列代码
1
2
3
4
5
6
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
}
else {
(<Bird>pet).fly();
}

改为
1
2
3
4
5
6
if (isFish(pet)) {
pet.swim();
}
else {
pet.fly();
}

多数情况下还是用 typeof 和 instanceof

let 和 const 同es6

let const 声明的变量只在当前代码块中有效

1
2
for (var i = 0; i < 10; i++) {}
console.log(i); //10
1
2
for(let j = 0; j < 10; j++) {}
console.log(j);// Error: j is not define

以前需要立即执行表达式(IIFE)解决的问题

1
2
3
4
5
6
7
for (var k = 0; k < 5; k++) {
(function (k) {
setTimeout(function () {
console.log(k); //输出0,1,2,3,4
},0);
})(k);
}

1
2
3
4
5
for (let j = 0; j < 5; j++) {
setTimeout(function () {
console.log(j); //输出0,1,2,3,4
},0);
}

不存在变量提升
1
2
3
4
5
console.log(foo); // 输出undefined
console.log(bar); // 报错ReferenceError

var foo = 2;
let bar = 2;

不允许重复声明

暂时性死区

即不允许在声明位置之前调用该变量

const 声明引用值不允许修改,然而const的对象内部状态是可以修改的。区分声明只读类型关键字readonly

1
readonly GIPX = 0x1a;

解构

1

接口

鸭子类型

1
2
3
4
5
6
7
8
9
10
interface LabelledValue	
{
label: string;
}
function printLabel(labelledObj:LabelledValue)
{
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

可见,参数对象并非实现接口,只需对外表现接口的特性

另外,接口里的属性不全都是必需的

1
2
3
4
interface SquareConfig { 
color?: string;
width?: number;
}

定义可索引的类型

1
2
3
4
5
6
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];

如上,是一个接口的定义,符合该接口的属性可以number类型为索引。

另,索引亦可为string类型。注意当同时使用两种类型的索引,数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number 来索引时,JavaScript会将它转换成 string 然后再去索引对象。 就是说用 100 (一个 number )去索引等同于使用 “100” (一个 string )去 索引,因此两者需要保持一致。

实现接口..

es6是没有constructor的,可以再琢磨下js原型篇。
ts中,类是具有两个类型:静态部分的类型和实例的类型。静态部分是类定义本身,实例部分就是生成的类的对象
constructor 存在于类的静态部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

这个是官方示例代码。为了实现定义一个符合ClockInterface接口规范的createClock方法。
而且应将符合ClockConstructor接口规范的类型作为返回值得类型

泛型

1
2
3
4
function identity<T>(arg: T): T 
{
return arg;
}

infer

1
type ParamType<T> = T extends (param: infer P) => any ? P : T;

infer表示P是待推断的参数类型,如T

never

不会返回结果的类型,一直在while(true)的函数,或者一定会抛出异常的函数

模块

export 和 import

namespace(存目)

从js迁移

参考TS Doc

元组

与数组的唯一区别是依次逐项指定了类型
适用于特定的数据元结构,比较现实的场景如csv文件的row

1
const newRow:[number,string,boolean] = [1,'老王',true]

装饰器 Decorator

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息作为参数传入。

1
2
3
4
5
6
7
8
9
10
11
import "reflect-metadata";

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

这里用到了反射

1
2
3
4
5
6
7
8
9
10
11
12
class Greeter {
@format("Hello, %s")
greeting: string;

constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}

typescript-eslint

Typescript-ESLint

面试必备

typescript的特点

  • 提供面向对面编程(OOP)的特性 如 类,接口,模块
  • 静态类型检查
  • ES6特性 箭头函数 变量声明等
  • 可选参数
  • 内置类型

优点和技巧

  • 静态类型
  • 扩展名为.d.ts的Definition文件提供对现有JavaScript库(如Jquery,D3.js等)的支持。