Nodejs


Egg.js

https://eggjs.org/zh-cn/intro/

阿里开源的Node.js框架egg.js:为企业级框架和应用而生!

  1. 简介

    阿里Egg.js 为企业级框架和应用而生

    https://www.toutiao.com/a6491178538987684365/

    官方文档网址:https://eggjs.org

    image

  2. 特性

  • 提供基于 Egg 定制上层框架的能力
  • 高度可扩展的插件机制
  • 内置多进程管理
  • 基于 Koa 开发,性能优异
  • 框架稳定,测试覆盖率高
  • 渐进式开发
  1. 设计原则

Egg 的插件机制有很高的可扩展性,一个插件只做一件事(比如 Nunjucks 模板封装成了 egg-view-nunjucks、MySQL 数据库封装成了 egg-mysql)

Egg 奉行『约定优于配置』,按照一套统一的约定进行应用开发。没有约定的团队,沟通成本是非常高的,比如有人会按目录分栈而其他人按目录分功能,开发者认知不一致很容易犯错。

  1. 异步编程模型

callback hell: 最臭名昭著的 callback 嵌套问题。

release zalgo: 异步函数中可能同步调用 callback 返回数据,带来不一致性。

async function

1
2
3
4
5
6
const fn = async function() {
const user = await getUser();
const posts = await fetchPosts(user.id);
return { user, posts };
};
fn().then(res => console.log(res)).catch(err => console.error(err.stack));
  1. Koa
  • 编写中间件

中间件洋葱图

中间件执行顺序图

所有的请求经过一个中间件的时候都会执行两次,对比 Express 形式的中间件,Koa 的模型可以非常方便的实现后置处理逻辑。

假设有个需求:我们的新闻站点,禁止百度爬虫访问

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
// app/middleware/robot.js
// options === app.config.robot
module.exports = (options, app) => {
return async function robotMiddleware(ctx, next) {
const source = ctx.get('user-agent') || '';
const match = options.ua.some(ua => ua.test(source));
if (match) {
ctx.status = 403;
ctx.message = 'Go away, robot.';
} else {
await next();
}
}
};

// config/config.default.js
// add middleware robot
exports.middleware = [
'robot'
];
// robot's configurations
exports.robot = {
ua: [
/Baiduspider/i,
]
};
  • Context

Koa 增加了一个 Context 的对象,作为这次请求的上下文对象(在 Koa 1 中为中间件的 this,在 Koa 2 中作为中间件的第一个参数传入)。我们可以将一次请求相关的上下文都挂载到这个对象上。

  • 异常处理

使用 try catch 就可以将按照规范编写的代码中的所有错误都捕获到。这样我们可以很便捷的编写一个自定义的错误处理中间件。

只需要将这个中间件放在其他中间件之前,就可以捕获它们所有的同步或者异步代码中抛出的异常了。

1
2
3
4
5
6
7
8
9
async function onerror(ctx, next) {
try {
await next();
} catch (err) {
ctx.app.emit('error', err);
ctx.body = 'server error';
ctx.status = err.status || 500;
}
}
  • 扩展

可以通过定义 app/extend/{application,context,request,response}.js 来扩展 Koa 中对应的四个对象的原型,通过这个功能,我们可以快速的增加更多的辅助方法,例如我们在 app/extend/context.js 中写入下列代码:

1
2
3
4
5
6
7
// app/extend/context.js
module.exports = {
get isIOS() {
const iosReg = /iphone|ipad|ipod/i;
return iosReg.test(this.get('user-agent'));
},
};

在 Controller 中,我们就可以使用到刚才定义的这个便捷属性了:

1
2
3
4
5
6
// app/controller/home.js
exports.handler = ctx => {
ctx.body = ctx.isIOS
? 'Your operating system is iOS.'
: 'Your operating system is not iOS.';
};
  • 插件

经常会引入许许多多的中间件来提供各种各样的功能,例如引入 koa-session 提供 Session 的支持,引入 koa-bodyparser 来解析请求 body。

一个插件可以包含

1
2
3
extend:扩展基础对象的上下文,提供各种工具类、属性。
middleware:增加一个或多个中间件,提供请求的前置、后置处理逻辑。
config:配置各个环境下插件自身的默认配置项。

安装对应的插件

1
npm i egg-view-nunjucks --save

开启插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// config/plugin.js
exports.nunjucks = {
enable: true,
package: 'egg-view-nunjucks'
};
// config/config.default.js
exports.keys = <此处改为你自己的 Cookie 安全字符串>;
// 添加 view 配置
exports.view = {
defaultViewEngine: 'nunjucks',
mapping: {
'.tpl': 'nunjucks',
},
};

提示:开发期默认开启了 development 插件,修改后端代码后,会自动重启 Worker 进程。

  • 静态资源

Egg 内置了 static 插件,线上环境建议部署到 CDN,无需该插件。

static 插件默认映射 /public/ -> app/public/ 目录

此处,我们把静态资源都放到 app/public 目录即可:

  • 单元测试

测试文件应该放在项目根目录下的 test 目录下,并以 test.js 为后缀名,即 {app_root}/test/*/.test.js

1
2
3
4
5
6
7
8
9
10
11
// test/app/middleware/robot.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');

describe('test/app/middleware/robot.test.js', () => {
it('should block robot', () => {
return app.httpRequest()
.get('/')
.set('User-Agent', "Baiduspider")
.expect(403);
});
});

然后配置依赖和 npm scripts:

1
2
3
4
5
6
7
8
{
"scripts": {
"test": "egg-bin test",
"cov": "egg-bin cov"
}
}

npm i egg-mock --save-dev

执行测试:

1
npm test
  1. 渐进式开发

==初始-雏形-插件-框架==

  • 最初始的状态

假设我们有一段分析 UA 的代码,实现以下功能:

1
ctx.isIOS

目录结构:

1
2
3
4
5
6
7
8
example-app
├── app
│ ├── extend
│ │ └── context.js
│ └── router.js
├── test
│ └── index.test.js
└── package.json

核心代码:

1
2
3
4
5
6
7
// app/extend/context.js
module.exports = {
get isIOS() {
const iosReg = /iphone|ipad|ipod/i;
return iosReg.test(this.get('user-agent'));
},
};
  • 插件的雏形

这段逻辑是具备通用性的,可以写成插件。

但一开始的时候,功能还没完善,直接独立插件,维护起来比较麻烦。

此时,我们可以把代码写成插件的形式,但并不独立出去。

新的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
example-app
├── app
│ └── router.js
├── config
│ └── plugin.js
├── lib
│ └── plugin
│ └── egg-ua
│ ├── app
│ │ └── extend
│ │ └── context.js
│ └── package.json
├── test
│ └── index.test.js
└── package.json

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app/extend/context.js 移动到 lib/plugin/egg-ua/app/extend/context.js。

lib/plugin/egg-ua/package.json 声明插件。

{
"eggPlugin": {
"name": "ua"
}
}
config/plugin.js 中通过 path 来挂载插件。
// config/plugin.js
const path = require('path');
exports.ua = {
enable: true,
path: path.join(__dirname, '../lib/plugin/egg-ua'),
};
  • 抽成独立插件

经过一段时间开发后,该模块的功能成熟,此时可以考虑抽出来成为独立的插件。

目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
egg-ua
├── app
│ └── extend
│ └── context.js
├── test
│ ├── fixtures
│ │ └── test-app
│ │ ├── app
│ │ │ └── router.js
│ │ └── package.json
│ └── ua.test.js
└── package.json

移除 lib/plugin/egg-ua 目录。
package.json 中声明对 egg-ua 的依赖。
config/plugin.js 中修改依赖声明为 package 方式。

// config/plugin.js
exports.ua = {
enable: true,
package: 'egg-ua',
};

注意:在插件还没发布前,可以通过 npm link 的方式进行本地测试,具体参见 npm-link。

1
2
3
4
$ cd example-app
$ npm link ../egg-ua
$ npm i
$ npm test
  • 沉淀到框架

目录结构:

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
29
example-framework
├── config
│ ├── config.default.js
│ └── plugin.js
├── lib
│ ├── agent.js
│ └── application.js
├── test
│ ├── fixtures
│ │ └── test-app
│ └── framework.test.js
├── README.md
├── index.js
└── package.json

移除 config/plugin.js 中对 egg-ua 的依赖。
package.json 中移除对 egg-ua 的依赖。
package.json 中声明对 example-framework 的依赖,并配置 egg.framework。
{
"name": "progressive",
"version": "1.0.0",
"private": true,
"egg": {
"framework": "example-framework"
},
"dependencies": {
"example-framework": "*"
}
}

注意:在框架还没发布前,可以通过 npm link 的方式进行本地测试,具体参见 npm-link。

1
2
3
4
$ cd example-app
$ npm link ../egg-framework
$ npm i
$ npm test
  1. 断点调试
  • WebStorm 下进行断点

Run - Edit Configuration - npn - 点+加号添加

1
2
3
4
Name:输入egg项目名称,方便进行区分
package.json:下拉选择项目根目录的package.json文件
Command:run
Scripts:!!!!!下拉选择带有"egg-bin dev"的Scripts,即可进行断点调试!!!!!
  1. csrf问题

egg安全默认机制,保护post等方法,可以关闭先

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
// config/config.default.js
module.exports = {
const config = {};

// 关闭csrf
config.security = {
csrf: {
enable: false
},
};

config.middleware = [ 'errorHandler' ];

config.mongoose = {
client: {
url: 'mongodb://192.168.1.123:27017/xxx',
options: {
db: {
safe: true
}
}
},
};

return config;
};
  1. REST API
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。
HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

例子:
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

过滤信息(Filtering):
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件

状态码(Status Codes):
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。




index
GET
http://127.0.0.1:7001/api/v2/users

show
GET
http://127.0.0.1:7001/api/v2/users/34ec7139-e4bb-4f2d-b6e6-78e0cb4444d0

create
POST
http://127.0.0.1:7001/api/v2/users
Content-Type: application/x-www-form-urlencoded
name=aaaaa&pass=bbbbb

update
PUT
http://127.0.0.1:7001/api/v2/users/34ec7139-e4bb-4f2d-b6e6-78e0cb4444d0
Content-Type: application/x-www-form-urlencoded
name=aaaaa&pass=bbbbb
  1. 生产环境

配置安装 egg-scripts

package.json

1
2
3
4
5
6
7
8
9
{
"dependencies": {
"egg-scripts": "^2.6.0",
},
"scripts": {
"start": "egg-scripts start --daemon --title=servicename",
"stop": "egg-scripts stop --title=servicename",
}
}

启动停止服务

1
2
npm start
npm stop
  1. 单元测试
  • 测试框架

我们选择和推荐大家使用 Mocha,功能非常丰富,支持运行在 Node.js 和浏览器中, 对异步测试支持非常友好。

  • 断言库

直到我们发现 power-assert, 因为『No API is the best API』, 最终我们重新回归原始的 assert 作为默认的断言库。

简单地说,它的优点是:

1
2
3
4
5
没有 API 就是最好的 API,不需要任何记忆,只需 assert 即可。
强大的错误信息反馈
强大的错误信息反馈
强大的错误信息反馈
报错信息实在太美太详细,让人有种想看错误报告的欲望
  • 测试执行顺序

Mocha 使用 before/after/beforeEach/afterEach 来处理前置后置任务,基本能处理所有问题。 每个用例会按 before -> beforeEach -> it -> afterEach -> after 的顺序执行,而且可以定义多个。

  • Controller 测试

app.httpRequest() 是 egg-mock 封装的 SuperTest 请求实例。

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
29
30
const { app, mock, assert } = require('egg-mock/bootstrap');

describe('test/controller/home.test.js', () => {
describe('GET /', () => {
it('should status 200 and get the body', () => {
// 对 app 发起 `GET /` 请求
return app.httpRequest()
.get('/')
.expect(200) // 期望返回 status 200
.expect('hello world'); // 期望 body 是 hello world
});

it('should send multi requests', async () => {
// 使用 generator function 方式写测试用例,可以在一个用例中串行发起多次请求
await app.httpRequest()
.get('/')
.expect(200) // 期望返回 status 200
.expect('hello world'); // 期望 body 是 hello world

// 再请求一次
const result = await app.httpRequest()
.get('/')
.expect(200)
.expect('hello world');

// 也可以这样验证
assert(result.status === 200);
});
});
});
  • Service 测试

只需要先创建一个 ctx,然后通过 ctx.service.${serviceName} 拿到 Service 实例, 然后调用 Service 方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
describe('get()', () => {
it('should get exists user', async () => {
// 创建 ctx
const ctx = app.mockContext();
// 通过 ctx 访问到 service.user
const user = await ctx.service.user.get('fengmk2');
assert(user);
assert(user.name === 'fengmk2');
});

it('should get null when user not exists', async () => {
const ctx = app.mockContext();
const user = await ctx.service.user.get('fengmk1');
assert(!user);
});
});
  • Mock Service

Service 作为框架标准的内置对象,我们提供了便捷的 app.mockService(service, methodName, fn) 模拟 Service 方法返回值。

例如,模拟 app/service/user 中的 get(name) 方法,让它返回一个本来不存在的用户数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
it('should mock fengmk1 exists', () => {
app.mockService('user', 'get', () => {
return {
name: 'fengmk1',
};
});

return app.httpRequest()
.get('/user?name=fengmk1')
.expect(200)
// 返回了原本不存在的用户信息
.expect({
name: 'fengmk1',
});
});

通过 app.mockServiceError(service, methodName, error) 可以模拟 Service 调用异常。

例如,模拟 app/service/user 中的 get(name) 方法调用异常:

1
2
3
4
5
6
7
8
it('should mock service error', () => {
app.mockServiceError('user', 'get', 'mock user service error');
return app.httpRequest()
.get('/user?name=fengmk2')
// service 异常,触发 500 响应
.expect(500)
.expect(/mock user service error/);
});

egg问题汇总

  1. egg-socket.io 生产环境启动 –daemon 必须有–sticky 否则导致无法连接

==socket.io 协议实现需要 sticky 特性支持,否则在多进程模式下无法正常工作==

1
2
3
4
5
// 这种方式启动,客户端会无法连接上socket.io
"start": "egg-scripts start --daemon"
"start": "egg-scripts start"
// 可以使用如下方式,--sticky
"start": "egg-scripts start --sticky --daemon"

基础

NodeJS和C++的性能比较

  1. 测试1E9次的空循环在NodeJS和C++中的执行用时
1
2
3
4
5
C++:
2136
1281(开启最大优化)
Node:
1516
  1. 长度为1E6的整型数组排序
1
2
3
4
C++:
186(开启最大优化)
Node:
384
  1. 结论

C++是比NodeJS快,但性能比较通常是用耗时的数量级来衡量的,这两个结果取对数之后基本没什么差异,C++真心没比NodeJS快多少。

所以,比起其它一大堆Web编程语言,NodeJS的性能还是非常有优势的。

概念

  1. 错误优先的回调函数

    第一个参数始终应该是一个错误对象, 用于检查程序是否发生了错误。其余的参数用于传递数据。

    fs.readFile(filePath, function(err, data) {
        if (err) {
            //handle the error
    
        }
        // use the data object
    
    });
    
  2. 避免回调地狱

    模块化:将回调函数分割为独立的函数

    使用Promises

    使用yield来计算生成器或Promise

  3. 事件循环

    Node.js借助libuv来实现多线程

    Libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个事件循环,以异步的方式将任务的执行结果返回给V8引擎。

  4. 哪些工具可以用来保证一致性的代码风格

    JSLint

    JSHint

    ESLint

    JSCS - 推荐

    能够帮助团队开发者强制执行规定的风格指南, 还能够通过静态分析捕获常见的错误。

  5. 运算错误与程序员错误的区别

    运算错误并不是bug,这是和系统相关的问题,例如请求超时或者硬件故障。

    而程序员错误就是所谓的bug。

  6. 单元测试

    Stub是完全模拟一个外部依赖

    而Mock常用来判断测试通过还是失败。

    测试金字塔指的是: 当我们在编写测试用例时,底层的单元测试应该远比上层的端到端测试要多。

七天学会NodeJS(开源书籍)

  1. 模块

    require

    require函数用于在当前模块中加载和使用别的模块,传入一个模块名,返回一个模块导出对象。模块名可使用相对路径(以./开头),或者是绝对路径(以/或C:之类的盘符开头)。另外,模块名中的.js扩展名可以省略。以下是一个例子。

    var foo1 = require('./foo');
    var foo2 = require('./foo.js');
    var foo3 = require('/home/user/foo');
    var foo4 = require('/home/user/foo.js');
    

    可以使用以下方式加载和使用一个JSON文件。

    var data = require('./data.json');
    
**exports**

exports对象是当前模块的导出对象,用于导出模块公有方法和属性。别的模块通过require函数使用当前模块时得到的就是当前模块的exports对象。以下例子中导出了一个公有方法。

    exports.hello = function () {
        console.log('Hello World!');
    };

**module**

通过module对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块的导出对象。例如模块导出对象默认是一个普通对象,如果想改成一个函数的话,可以使用以下方式。

    module.exports = function () {
        console.log('Hello World!');
    };

**模块初始化**

一个模块中的JS代码仅在模块第一次被使用时执行一次,并在执行过程中初始化模块的导出对象。之后,缓存起来的导出对象被重复利用。

**主模块**

通过命令行参数传递给NodeJS以启动程序的模块被称为主模块。主模块负责调度组成整个程序的其它模块完成工作。例如通过以下命令启动程序时,main.js就是主模块。

    node main.js
  1. 完整示例

    例如有以下目录。

    - /home/user/hello/
        - util/
            counter.js
        main.js
    

    其中counter.js内容如下:

    var i = 0;
    
    function count() {
        return ++i;
    }
    
    exports.count = count;
    

    该模块内部定义了一个私有变量i,并在exports对象导出了一个公有方法count。

    主模块main.js内容如下:

    var counter1 = require('./util/counter');
    var counter2 = require('./util/counter');
    
    console.log(counter1.count());
    console.log(counter2.count());
    console.log(counter2.count());
    

    运行该程序的结果如下:

    $ node main.js
    1
    2
    3
    

    可以看到,counter.js并没有因为被require了两次而初始化两次。

  2. 二进制模块

    虽然一般我们使用JS编写模块,但NodeJS也支持使用C/C++编写二进制模块。编译好的二进制模块除了文件扩展名是.node外,和JS模块的使用方式相同。二进制模块能使用操作系统提供的所有功能,拥有无限的潜能。

  3. 模块路径解析规则

    依次按照以下规则解析路径,直到找到模块位置

    a. 内置模块

    如果传递给require函数的是NodeJS内置模块名称,不做路径解析,直接返回内部模块的导出对象,例如require(‘fs’)。

    b. node_modules目录

    NodeJS定义了一个特殊的node_modules目录用于存放模块。例如某个模块的绝对路径是/home/user/hello.js,在该模块中使用require(‘foo/bar’)方式加载模块时,则NodeJS依次尝试使用以下路径。

    /home/user/node_modules/foo/bar
    /home/node_modules/foo/bar
    /node_modules/foo/bar
    

    c. NODE_PATH环境变量

    与PATH环境变量类似,NodeJS允许通过NODE_PATH环境变量来指定额外的模块搜索路径。NODE_PATH环境变量中包含一到多个目录路径,路径之间在Linux下使用:分隔,在Windows下使用;分隔。例如定义了以下NODE_PATH环境变量:

    NODE_PATH=/home/user/lib:/home/lib
    

    当使用require(‘foo/bar’)的方式加载模块时,则NodeJS依次尝试以下路径。

    /home/user/lib/foo/bar
    /home/lib/foo/bar
    
  4. 包(package)

    我们已经知道了JS模块的基本单位是单个JS文件,但复杂些的模块往往由多个子模块组成。为了便于管理和使用,我们可以把由多个子模块组成的大模块称做包,并把所有子模块放在同一个目录里。

    在组成一个包的所有子模块中,需要有一个入口模块,入口模块的导出对象被作为包的导出对象。例如有以下目录结构。

    - /home/user/lib/
        - cat/
            head.js
            body.js
            main.js
    

    其中cat目录定义了一个包,其中包含了3个子模块。main.js作为入口模块,其内容如下:

    var head = require('./head');
    var body = require('./body');
    
    exports.create = function (name) {
        return {
            name: name,
            head: head.create(),
            body: body.create()
        };
    };
    

    在其它模块里使用包的时候,需要加载包的入口模块。接着上例,使用require(‘/home/user/lib/cat/main’)能达到目的,但是入口模块名称出现在路径里看上去不是个好主意。因此我们需要做点额外的工作,让包使用起来更像是单个模块。

    index.js

    当模块的文件名是index.js,加载模块时可以使用模块所在目录的路径代替模块文件路径,因此接着上例,以下两条语句等价。

    var cat = require('/home/user/lib/cat');
    var cat = require('/home/user/lib/cat/index');
    

    这样处理后,就只需要把包目录路径传递给require函数,感觉上整个目录被当作单个模块使用,更有整体感。

    package.json

    如果想自定义入口模块的文件名和存放位置,就需要在包目录下包含一个package.json文件,并在其中指定入口模块的路径。上例中的cat模块可以重构如下。

    - /home/user/lib/
        - cat/
            + doc/
            - lib/
                head.js
                body.js
                main.js
            + tests/
            package.json
    

    其中package.json内容如下。

    {
        "name": "cat",
        "main": "./lib/main.js"
    }
    

    如此一来,就同样可以使用require(‘/home/user/lib/cat’)的方式加载模块。NodeJS会根据包目录下的package.json找到入口模块所在位置。

  5. 命令行程序

    例如我们用NodeJS写了个程序,可以把命令行参数原样打印出来。该程序很简单,在主模块内实现了所有功能。并且写好后,我们把该程序部署在/home/user/bin/node-echo.js这个位置。为了在任何目录下都能运行该程序,我们需要使用以下终端命令。

    node /home/user/bin/node-echo.js Hello World
    Hello World
    

    这种使用方式看起来不怎么像是一个命令行程序,下边的才是我们期望的方式。

    node-echo Hello World
    

    Linux

    在Linux系统下,我们可以把JS文件当作shell脚本来运行,从而达到上述目的,具体步骤如下:

    a. 在shell脚本中,可以通过#!注释来指定当前脚本使用的解析器。所以我们首先在node-echo.js文件顶部增加以下一行注释,表明当前脚本使用NodeJS解析。
    
     #! /usr/bin/env node
    
    NodeJS会忽略掉位于JS模块首行的#!注释,不必担心这行注释是非法语句。
    
    b. 然后,我们使用以下命令赋予node-echo.js文件执行权限。
    
     chmod +x /home/user/bin/node-echo.js
    
    c. 最后,我们在PATH环境变量中指定的某个目录下,例如在/usr/local/bin下边创建一个软链文件,文件名与我们希望使用的终端命令同名,命令如下:
    
     sudo ln -s /home/user/bin/node-echo.js /usr/local/bin/node-echo
    

    这样处理后,我们就可以在任何目录下使用node-echo命令了。

  6. 工程目录

    了解了以上知识后,现在我们可以来完整地规划一个工程目录了。以编写一个命令行程序为例,一般我们会同时提供命令行模式和API模式两种使用方式,并且我们会借助三方包来编写代码。除了代码外,一个完整的程序也应该有自己的文档和测试用例。因此,一个标准的工程目录都看起来像下边这样。

    - /home/user/workspace/node-echo/   # 工程目录
        - bin/                          # 存放命令行相关代码
            node-echo
        + doc/                          # 存放文档
        - lib/                          # 存放API相关代码
            echo.js
        - node_modules/                 # 存放三方包
            + argv/
        + tests/                        # 存放测试用例
        package.json                    # 元数据文件
        README.md                       # 说明文件
    

    其中部分文件内容如下:

    /* bin/node-echo */
    var argv = require('argv'),
        echo = require('../lib/echo');
    console.log(echo(argv.join(' ')));
    
    /* lib/echo.js */
    module.exports = function (message) {
        return message;
    };
    
    /* package.json */
    {
        "name": "node-echo",
        "main": "./lib/echo.js"
    }
    

    以上例子中分类存放了不同类型的文件,并通过node_moudles目录直接使用三方包名加载模块。此外,定义了package.json之后,node-echo目录也可被当作一个包来使用。

  7. NPM

    NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种:

    a. 允许用户从NPM服务器下载别人编写的三方包到本地使用。

    npm install argv
    

    下载好之后,argv包就放在了工程目录下的node_modules目录中,因此在代码中只需要通过require(‘argv’)的方式就好,无需指定三方包路径。

    以上命令默认下载最新版三方包,如果想要下载指定版本的话,可以在包名后边加上@version,例如通过以下命令可下载0.0.1版的argv。

    npm install argv@0.0.1
    

    如果使用到的三方包比较多,在终端下一个包一条命令地安装未免太人肉了。因此NPM对package.json的字段做了扩展,允许在其中申明三方包依赖。因此,上边例子中的package.json可以改写如下:

    {
        "name": "node-echo",
        "main": "./lib/echo.js",
        "dependencies": {
            "argv": "0.0.2"
        }
    }
    

    这样处理后,在工程目录下就可以使用npm install命令批量安装三方包了。更重要的是,当以后node-echo也上传到了NPM服务器,别人下载这个包时,NPM会根据包中申明的三方包依赖自动下载进一步依赖的三方包。例如,使用npm install node-echo命令时,NPM会自动创建以下目录结构。

    - project/
        - node_modules/
            - node-echo/
                - node_modules/
                    + argv/
                ...
        ...
    

    如此一来,用户只需关心自己直接使用的三方包,不需要自己去解决所有包的依赖关系。

**b. 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。**

从NPM服务上下载安装一个命令行程序的方法与三方包类似。例如上例中的node-echo提供了命令行使用方式,只要node-echo自己配置好了相关的package.json字段,对于用户而言,只需要使用以下命令安装程序。

    npm install node-echo -g

参数中的-g表示全局安装,因此node-echo会默认安装到以下位置,并且NPM会自动创建好Linux系统下需要的软链文件或Windows系统下需要的.cmd文件。

    - /usr/local/               # Linux系统下
        - lib/node_modules/
            + node-echo/
            ...
        - bin/
            node-echo
            ...
        ...

    - %APPDATA%\npm\            # Windows系统下
        - node_modules\
            + node-echo\
            ...
        node-echo.cmd
        ...




**c. 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。**

第一次使用NPM发布代码前需要注册一个账号。终端下运行npm adduser,之后按照提示做即可。账号搞定后,接着我们需要编辑package.json文件,加入NPM必需的字段。接着上边node-echo的例子,package.json里必要的字段如下。

    {
        "name": "node-echo",           # 包名,在NPM服务器上须要保持唯一
        "version": "1.0.0",            # 当前版本号
        "dependencies": {              # 三方包依赖,需要指定包名和版本号
            "argv": "0.0.2"
          },
        "main": "./lib/echo.js",       # 入口模块位置
        "bin" : {
            "node-echo": "./bin/node-echo"      # 命令行程序名和主模块位置
        }
    }

之后,我们就可以在package.json所在目录下运行npm publish发布代码了。

**版本号**

使用NPM下载和发布代码时都会接触到版本号。NPM使用语义版本号来管理代码,这里简单介绍一下。

语义版本号分为X.Y.Z三位,分别代表主版本号、次版本号和补丁版本号。当代码变更时,版本号按以下原则更新。

    + 如果只是修复bug,需要更新Z位。

    + 如果是新增了功能,但是向下兼容,需要更新Y位。

    + 如果有大变动,向下不兼容,需要更新X位。

版本号有了这个保证后,在申明三方包依赖时,除了可依赖于一个固定版本号外,还可依赖于某个范围的版本号。例如"argv": "0.0.x"表示依赖于0.0.x系列的最新版argv。

**灵机一点**

除了本章介绍的部分外,NPM还提供了很多功能,package.json里也有很多其它有用的字段。除了可以在npmjs.org/doc/查看官方文档外,这里再介绍一些NPM常用命令。

    NPM提供了很多命令,例如install和publish,使用npm help可查看所有命令。

    使用npm help <command>可查看某条命令的详细帮助,例如npm help install。

    在package.json所在目录下使用npm install . -g可先在本地安装当前命令行程序,可用于发布前的本地测试。

    使用npm update <package>可以把当前目录下node_modules子目录里边的对应模块更新至最新版本。

    使用npm update <package> -g可以把全局安装的对应命令行程序更新至最新版。

    使用npm cache clear可以清空NPM本地缓存,用于对付使用相同版本号发布新版本代码的人。

    使用npm unpublish <package>@<version>可以撤销发布自己发布过的某个版本代码。
  1. 文件操作

    小文件拷贝

    我们使用NodeJS内置的fs模块简单实现这个程序如下。

    var fs = require('fs');
    
    function copy(src, dst) {
        fs.writeFileSync(dst, fs.readFileSync(src));
    }
    
    function main(argv) {
        copy(argv[0], argv[1]);
    }
    
    main(process.argv.slice(2));
    

    以上程序使用fs.readFileSync从源路径读取文件内容,并使用fs.writeFileSync将文件内容写入目标路径。

    process是一个全局变量,可通过process.argv获得命令行参数。由于argv[0]固定等于NodeJS执行程序的绝对路径,argv[1]固定等于主模块的绝对路径,因此第一个命令行参数从argv[2]这个位置开始。

    大文件拷贝

    function copy(src, dst) {
        fs.createReadStream(src).pipe(fs.createWriteStream(dst));
    }
    

    *API走马观花**

  • Buffer(数据块) 提供对二进制数据的操作

    直接构造

    var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);
    bin[0]; // => 0x68;
    var str = bin.toString('utf-8'); // => "hello" 使用指定编码将二进制数据转化为字符串
    var bin = new Buffer('hello', 'utf-8'); // => <Buffer 68 65 6c 6c 6f> 将字符串转换为指定编码下的二进制数据
    

    拷贝一份Buffer,得首先创建一个新的Buffer,并通过.copy方法把原Buffer中的数据复制过去

    var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);
    var dup = new Buffer(bin.length);
    
    bin.copy(dup);
    dup[0] = 0x48;
    console.log(bin); // => <Buffer 68 65 6c 6c 6f>
    console.log(dup); // => <Buffer 48 65 65 6c 6f>
    
  • Stream(数据流)

    Stream基于事件机制工作,所有Stream的实例都继承于NodeJS提供的EventEmitter。

    var rs = fs.createReadStream(src);
    
    rs.on('data', function (chunk) {
        rs.pause(); // 可以在处理数据前暂停数据读取,并在处理数据后继续读取数据
        doSomething(chunk, function () {
            rs.resume();
        });
    });
    
    rs.on('end', function () {
        cleanUp();
    });
    
  • File System(文件系统)

    文件属性读写。

    其中常用的有fs.stat、fs.chmod、fs.chown等等。
    

    文件内容读写。

    其中常用的有fs.readFile、fs.readdir、fs.writeFile、fs.mkdir等等。
    

    底层文件操作。

    其中常用的有fs.open、fs.read、fs.write、fs.close等等。
    
  • Path(路径)

    path.normalize 将传入的路径转换为标准路径

    path.normalize('foo//baz//../bar'); // "foo/bar"
    // 标准化之后的路径里的斜杠在Windows系统下是\,而在Linux系统下是/。如果想保证任何系统下都使用/作为路径分隔符的话,需要用.replace(/\\/g, '/')再替换一下标准路径
    

    path.extname 根据不同文件扩展名做不同操作

    path.extname('foo/bar.js'); // => ".js"
    
  • 递归算法

    function factorial(n) {
        if (n === 1) {
            return 1;
        } else {
            return n * factorial(n - 1);
        }
    }
    

    !!!!!使用递归算法编写的代码虽然简洁,但由于每递归一次就产生一次函数调用,在需要优先考虑性能时,需要把递归算法转换为循环算法,以减少函数调用次数。

  • 遍历目录

    同步遍历

    function travel(dir, callback) {
        fs.readdirSync(dir).forEach(function (file) {
            var pathname = path.join(dir, file);
    
            if (fs.statSync(pathname).isDirectory()) {
                travel(pathname, callback);
            } else {
                callback(pathname);
            }
        });
    }
    

    异步遍历

    // 我们可以看到异步编程还是蛮复杂的。
    function travel(dir, callback, finish) {
        fs.readdir(dir, function (err, files) {
            (function next(i) {
                if (i < files.length) {
                    var pathname = path.join(dir, files[i]);
    
                    fs.stat(pathname, function (err, stats) {
                        if (stats.isDirectory()) {
                            travel(pathname, callback, function () {
                                next(i + 1);
                            });
                        } else {
                            callback(pathname, function () {
                                next(i + 1);
                            });
                        }
                    });
                } else {
                    finish && finish();
                }
            }(0));
        });
    }
    
  • 文本编码

    BOM的移除

    BOM用于标记一个文本文件使用Unicode编码,其本身是一个Unicode字符("\uFEFF"),位于文本文件头部。在不同的Unicode编码下,BOM字符对应的二进制字节如下:
    
        Bytes      Encoding
    ----------------------------
        FE FF       UTF16BE
        FF FE       UTF16LE
        EF BB BF    UTF8
    
    使用NodeJS读取文本文件时,一般需要去掉BOM。BOM字符虽然起到了标记文件编码的作用,其本身却不属于文件内容的一部分,如果读取文本文件时不去掉BOM,在某些使用场景下就会有问题。例如我们把几个JS文件合并成一个文件后,如果文件中间含有BOM字符,就会导致浏览器JS语法错误
    
    function readText(pathname) {
        var bin = fs.readFileSync(pathname);
    
        if (bin[0] === 0xEF && bin[1] === 0xBB && bin[2] === 0xBF) {
            bin = bin.slice(3);
        }
    
        return bin.toString('utf-8');
    }
    

    GBK转UTF8

    GBK编码不在NodeJS自身支持范围内。因此,一般我们借助iconv-lite这个三方包来转换编码。
    
    var iconv = require('iconv-lite');
    
    function readGBKText(pathname) {
        var bin = fs.readFileSync(pathname);
    
        return iconv.decode(bin, 'gbk');
    }
    

    单字节编码

    这里的诀窍在于,不管大于0xEF的单个字节在单字节编码下被解析成什么乱码字符,使用同样的单字节编码保存这些乱码字符时,背后对应的字节保持不变。
    
    1. GBK编码源文件内容:
        var foo = '中文';
    2. 对应字节:
        76 61 72 20 66 6F 6F 20 3D 20 27 D6 D0 CE C4 27 3B
    3. 使用单字节编码读取后得到的内容:
        var foo = '{乱码}{乱码}{乱码}{乱码}';
    4. 替换内容:
        var bar = '{乱码}{乱码}{乱码}{乱码}';
    5. 使用单字节编码保存后对应字节:
        76 61 72 20 62 61 72 20 3D 20 27 D6 D0 CE C4 27 3B
    6. 使用GBK编码读取后得到内容:
        var bar = '中文';
    
    function replace(pathname) {
        var str = fs.readFileSync(pathname, 'binary');
        str = str.replace('foo', 'bar');
        fs.writeFileSync(pathname, str, 'binary');
    }
    
  1. 网络操作

    开门红

    var http = require('http');
    
    http.createServer(function (request, response) {
        response.writeHead(200, { 'Content-Type': 'text-plain' });
        response.end('Hello World\n');
    }).listen(8124);
    

    !!!!在Linux系统下,监听1024以下端口需要root权限。因此,如果想监听80或443端口的话,需要使用sudo命令启动程序。

    **API走马观花*

  • HTTP

    模块提供两种使用方式:

    作为服务端使用时,创建一个HTTP服务器,监听HTTP客户端请求并返回响应。
    
    作为客户端使用时,发起一个HTTP客户端请求,获取服务端响应。
    

    HTTP请求本质上是一个数据流,由请求头(headers)和请求体(body)组成。例如以下是一个完整的HTTP请求数据内容。

    空行之上是请求头,之下是请求体
    
    POST / HTTP/1.1
    User-Agent: curl/7.26.0
    Host: localhost
    Accept: */*
    Content-Length: 11
    Content-Type: application/x-www-form-urlencoded
    
    Hello World
    

    客户端模式下如何工作

    var options = {
            hostname: 'www.example.com',
            port: 80,
            path: '/upload',
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        };
    
    var request = http.request(options, function (response) {});
    
    request.write('Hello World');
    request.end();
    

    由于HTTP请求中GET请求是最常见的一种,并且不需要请求体,因此http模块也提供了以下便捷API。

    http.get('http://www.example.com/', function (response) {});
    
  • HTTPS

    在服务端模式下,创建一个HTTPS服务器的示例如下

    var options = {
            key: fs.readFileSync('./ssl/default.key'), // 私钥和公钥
            cert: fs.readFileSync('./ssl/default.cer')
        };
    
    var server = https.createServer(options, function (request, response) {
            // ...
        });
    

    根据HTTPS客户端请求使用的域名动态使用不同的证书

    server.addContext('foo.com', {
        key: fs.readFileSync('./ssl/foo.com.key'),
        cert: fs.readFileSync('./ssl/foo.com.cer')
    });
    
    server.addContext('bar.com', {
        key: fs.readFileSync('./ssl/bar.com.key'),
        cert: fs.readFileSync('./ssl/bar.com.cer')
    });
    

    在客户端模式下,发起一个HTTPS客户端请求与http模块几乎相同

    var options = {
            hostname: 'www.example.com',
            port: 443,
            path: '/',
            method: 'GET'
        };
    
    var request = https.request(options, function (response) {});
    
    request.end();
    
  • URL 该模块允许解析URL、生成URL,以及拼接URL

    完整的URL的各组成部分

                                       href
     -----------------------------------------------------------------
                                host              path
                          --------------- ----------------------------
     http: // user:pass @ host.com : 8080 /p/a/t/h ?query=string #hash
     -----    ---------   --------   ---- -------- ------------- -----
    protocol     auth     hostname   port pathname     search     hash
                                                    ------------
                                                       query
    

    使用.parse方法来将一个URL字符串转换为URL对象,示例如下

    url.parse('http://user:pass@host.com:8080/p/a/t/h?query=string#hash');
    /* =>
    { protocol: 'http:',
      auth: 'user:pass',
      host: 'host.com:8080',
      port: '8080',
      hostname: 'host.com',
      hash: '#hash',
      search: '?query=string',
      query: 'query=string',
      pathname: '/p/a/t/h',
      path: '/p/a/t/h?query=string',
      href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' }
    */
    

    !!!!!!.parse方法还支持第二个和第三个布尔类型可选参数。第二个参数等于true时,该方法返回的URL对象中,query字段不再是一个字符串,而是一个经过querystring模块转换后的参数对象。第三个参数等于true时,该方法可以正确解析不带协议头的URL,例如//www.example.com/foo/bar

    format方法允许将一个URL对象转换为URL字符串,示例如下。

    url.format({
        protocol: 'http:',
        host: 'www.example.com',
        pathname: '/p/a/t/h',
        search: 'query=string'
    });
    /* =>
    'http://www.example.com/p/a/t/h?query=string'
    */
    

    .resolve方法可以用于拼接URL,示例如下。

    url.resolve('http://www.example.com/foo/bar', '../baz');
    /* =>
    http://www.example.com/baz
    */
    
  • Query String

  1. child_process - 子进程 直接调用系统命令或执行shell命令
1
2
3
4
5
6
7
8
9
10
11
12
13
const util = require('util');
const exec = util.promisify(require('child_process').exec);

async function lsExample() {
const { stdout, stderr } = await exec('ls');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
}
lsExample();

实战:
var socketCount = await exec('netstat -nat|grep ESTABLISHED|grep -i "38080"|wc -l');
socketCount = socketCount.stdout.replace('\\n','');

被误解的Node.js

https://www.ibm.com/developerworks/cn/web/1201_wangqf_nodejs/
  1. 网络应用的性能瓶颈

    瓶颈之一在于 I/O 处理上

    访问磁盘及网络数据所花费的 CPU 时间是访问内存时的数十万倍

I/O CPU Cycle
L1-cache 3
L2-cache 14
RAM 250
Disk 41000000
Network 240000000
  1. 传统的处理方式

    • 单线程:客户端发起一个 I/O 请求,然后等待服务器端返回 I/O 结果,结果返回后再对其进行操作
    • 多线程:服务器为每个请求分配一个线程,所有任务均在该线程内执行;程序员需考虑死锁,数据不一致等问题,多线程的程序极难调试和测试
    • 事件驱动:使用一个线程执行,客户发起 I/O 请求的同时传入一个函数,该函数会在 I/O 结果返回后被自动调用,而且该请求不会阻塞后续操作
  2. 为什么选用 JavaScript

    作者 Ryan Dahl 并没有选择 JavaScript,他尝试过 C、Lua,皆因其欠缺一些高级语言的特性,如闭包、函数式编程,致使程序复杂,难以维护。

    加之 Google 提供的 V8 引擎,使 JavaScript 语言的执行速度大大提高。最终呈现在我们面前的就成了 Node.js,而不是 Node.c,Node.lua 或其他语言的实现。

4.

实战

swagger 接口文档

  1. swagger-editor 编辑器

    下载最新版

    https://github.com/swagger-api/swagger-editor/releases

    解压到目录,进入目录执行下面命令启动编辑器

    npm start
    

    浏览器访问打开编辑器

    http://localhost:3001/
    
  2. swagger-ui

    安装swagger-ui-dist

    npm install swagger-ui-dist
    

    将swagger-editor编辑的文件导出json拷贝到api目录

    启动

    const express = require('express')
    const pathToSwaggerUi = require('swagger-ui-dist').absolutePath()
    
    const app = express()
    
    app.use(express.static(pathToSwaggerUi))
    
    app.use('/api', express.static('public'));
    
    app.listen(3000)
    

    浏览器访问打开编辑器

    http://localhost:3000/
    

    输入json文件目录到swagger输入框中

    http://192.168.1.186:3000/api/swagger.json
    
  3. swagger语法

    例子:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
swagger: '2.0'
info:
version: 1.0.0
title: XXXX
host: 192.168.1.168:1000
basePath: /
schemes:
- http
paths:
/babelSelect:
post:
tags:
- XX塔
operationId: babelSelect
consumes:
- application/x-www-form-urlencoded
produces:
- application/json
parameters:
- name: body
in: "body"
description: 参数
required: true
schema:
$ref: '#/definitions/babelParameters'
responses:
'200':
description: 成功
schema:
$ref: '#/definitions/babelSelectResponse'
definitions:
babelParameters:
type: object
properties:
id:
type: string
description: ID
babelSelectResponse:
type: object
properties:
name:
type: string
enum:
- "Babel"
default: 'Babel'
value:
type: "array"
items:
$ref: "#/definitions/babelData"
babelData:
type: object
properties:
id:
type: integer
ticket:
type: integer
type:
type: integer
level:
type: integer
endTime:
type: integer
name:
type: string
babelId:
type: string
reward:
type: string
rewardDesc:
type: string
icon:
type: string

使用Typescript开发node.js项目

https://segmentfault.com/a/1190000007574276

nodejs mongoose bluebird

Nodejs开发的一款完整的,带前台和后台的企业网站demo !!!!!!!!!!!!!!!

https://github.com/iteming/nodejs-website-demo

Nodejs 安装

  1. 安装

    yum -y install gcc make gcc-c++ openssl-devel wget
    
    wget https://npm.taobao.org/mirrors/node/v9.2.1/node-v9.2.1.tar.gz
    
    tar -zvxf node-v9.2.1.tar.gz
    cd node-v9.2.1
    ./configure
    make && make install
    

Nodejs 升级

  1. 安装n模块:

    node有一个模块叫n(这名字可够短的。。。),是专门用来管理node.js的版本的。

    npm install -g n
    
  2. 升级node.js到最新稳定版

    n stable
    

    n后面也可以跟随版本号比如:

    n v0.10.26
    

NodeJS 与 npm 在Windows下升级

  1. nodejs

    Node 版本更新,下载指定版本 .msi 文件,安装到历史安装目录,即完成版本更新。

  2. npm

    npm 是随 NodeJS 一起发布的包管理工具,默认采用的并不一定是最新版本,如需升级使用以下命令:

    npm -g install npm ( 最新稳定版 )
    或
    npm -g install npm@2.9.1 ( 指定版本 )
    

npm

  1. install

    npm install [<@scope>/] [-S|–save|-D|–save-dev|-O|–save-optional]:

    -S, --save: Package will appear in your dependencies.
    
    -D, --save-dev: Package will appear in your devDependencies.
    
    -O, --save-optional: Package will appear in your optionalDependencies.
    
  1. 淘宝镜像

    http://npm.taobao.org/

    npm install -g cnpm --registry=https://registry.npm.taobao.org
    
    npm config set registry https://registry.npm.taobao.org
    

pm2

  1. 安装/升级

    npm install -g pm2
    
  2. 启动nodejs程序

    pm2 start app.js --name critz -o logs/critz.out -e logs/critz.err
    
  1. 查询启动中的程序

    pm2 list
    
  2. 关闭pm2

    pm2 kill
    
  3. 日志

    pm2 logs
    
  1. 性能测试

    nohup ./webbench -c 5000 -t 86400 http://localhost:9999/lottery/commit_score?score=10000 &
    
  2. 其他命令

    pm2 stop all
    
    pm2 reset all
    
    pm2 restart all
    
    pm2 start app.js --name my-api # 命名进程
    pm2 list               # 显示所有进程状态
    pm2 monit              # 监视所有进程监视每个node进程的CPU和内存的使用情况
    pm2 logs               #  显示所有进程日志
    pm2 stop all           # 停止所有进程
    pm2 restart all        # 重启所有进程
    pm2 reload all         # 0秒停机重载进程 (用于 NETWORKED 进程)
    pm2 stop 0             # 停止指定的进程
    pm2 restart 0          # 重启指定的进程
    pm2 startup            # 产生 init 脚本 保持进程活着
    pm2 web                # 运行健壮的 computer API endpoint (http://localhost:9615)
    pm2 delete 0           # 杀死指定的进程
    pm2 delete all         # 杀死全部进程
    

    运行进程的不同方式:

    $ pm2 start app.js -i max  # 根据有效CPU数目启动最大进程数目
    $ pm2 start app.js -i 3      # 启动3个进程
    $ pm2 start app.js -x        #用fork模式启动 app.js 而不是使用 cluster
    $ pm2 start app.js -x -- -a 23   # 用fork模式启动 app.js 并且传递参数 (-a 23)
    $ pm2 start app.js --name serverone  # 启动一个进程并把它命名为 serverone
    $ pm2 stop serverone       # 停止 serverone 进程
    $ pm2 start app.json        # 启动进程, 在 app.json里设置选项
    $ pm2 start app.js -i max -- -a 23                   #在--之后给 app.js 传递参数
    $ pm2 start app.js -i max -e err.log -o out.log  # 启动 并 生成一个配
    
  3. 初始化相关组件

    npm install
    // 初始化ejs
    npm install ejs
    // 初始化express组件
    npm install express
    // 初始化mongdb组合的组件
    npm install mongoose
    npm install express-mongoose
    // node 默认启动后修改文件,页面不会立即体现,通过该组件可以使修改的文件实时在页面体现出来
    npm install supervisor
    
  4. pm2 start 添加参数

参数为 prod

1
pm2 start index.js -- prod

注意:–后面有空格,并且后面全部都是参数

例如下面 38080为新端口号,当参数传入

1
pm2 start app.js --name xxxxx38080 -e logs/xxxxx38080.err -o logs/xxxxx38080.out -- 38080

  1. nodejs命令行执行时带参数
1
2
3
4
5
6
7
//node app.js arg1 arg2 arg3, 想取得这三个参数
//即可以程序中用:
var args = process.argv.splice(2)
//process是一个全局对象,argv返回的是一组包含命令行参数的数组
// args[0] 为arg1
// args[1] 为arg2
// args[2] 为arg3

代码覆盖率

npm install -g istanbul --registry=http://registry.npm.taobao.org

npm install --save --registry=http://registry.npm.taobao.org


//test
npm test

GraphicsMagick for node.js

牛逼的图片处理

调试nodejs

借用Chrome浏览器的JavaScript调试器来调试
  1. npm来安装node-inspector

    npm install -g node-inspector  // -g 导入安装路径到环境变量
    

    node-inspector是通过websocket方式来转向debug输入输出的。

  2. 启动node-inspector来监听Nodejs的debug调试端口

    node-inspector &
    

    默认情况下node-inspector的端口是8080,可以通过参数–web-port=[port]来设置端口。

  3. 在启动node-inpspector之后,我们可以通过–debug或–debug-brk来启动nodejs程序。

    通过在chrome浏览器输入http://[ip address]:8080/debug?port=5858,我们会得到调试窗口。

  4. WebStorm安装调试js

基于Nodejs的网络开源东东

Nodejs开发的一款企业网站demo,包含前台后台管理

https://cnodejs.org/topic/58d4be37b3e60b982d089b3a

koa的框架

  1. koa的框架web开发简介

    https://www.toutiao.com/a6477702957415531022/

    koa 致力于成为一个更小、更富有表现力、更健壮的 Web 框架。

  2. 新一代Node.js的Web开发框架Koa2

    https://www.toutiao.com/a6481426813737239054/

    koa2-demo2的项目目录结构

    bin, 存放启动项目的脚本文件
    node_modules, 存放所有的项目依赖库。
    public,静态文件(css,js,img)
    routes,路由文件(MVC中的C,controller)
    views,页面文件(pug模板)
    package.json,项目依赖配置及开发者信息
    app.js,应用核心配置文件
    package.json,node项目配置文件
    package-lock.json,node项目锁定的配置文件
    

    app.js文件,我们可以分割为X个部分进行解读:

    依赖包加载
    错误处理
    中间件加载
    web界面渲染模板、
    自定义日志
    自己定义路由
    外部调用接口
    

Raneto 支持Markdown的开源知识库

www.raneto.com

配置

  1. 限制浏览用户名和密码

config.default.js这个配置文件

authentication : true,
credentials    : {
  username : ‘你的用户名‘,
  password : ‘你的密码‘
},

Ghost 基于Node.js的开源博客系统

十分吃内存,不建议使用,可以用hexo

  1. 下载

    https://ghost.org/developers/

  2. 安装

    注意:!!!!!!node版本必须是6!!!!!!

    新建目录,并解压下载文件

    unzip Ghost-1.17.1.zip
    

    修改配置文件 core/server/config/env/config.development.json

    "url": "http://192.168.1.132:2368",
    "server": {
        "host": "192.168.1.132",
        "port": 2368
    },
    

    安装 sqlite3 环境

    npm install -g knex-migrator
    
    knex-migrator init
    

    安装其他环境

    npm install
    

    启动调试

    node index.js
    

Hexo

https://hexo.io/zh-cn/
  1. 解决 Hexo 进程守护的问题

    Hexo下新建一个app.js,写入下面代码:

    var spawn = require('child_process').spawn;
    free = spawn('hexo', ['server', '-p 4000']);/* 其实就是等于执行hexo server -p 4000*/
    
    free.stdout.on('data', function (data) {
        console.log('standard output:\n' + data);
    });
    
    free.stderr.on('data', function (data) {
        console.log('standard error output:\n' + data);
    });
    
    free.on('exit', function (code, signal) {
        console.log('child process eixt ,exit:' + code);
    });
    
  2. 主题

    https://hexo.io/themes/

    找到你想要的主题。在github中搜索你要的主题名称

    我选的是hueman,看起来挺不错

    https://github.com/ppoffice/hexo-theme-hueman/wiki

    搜索插件安装

    https://github.com/ppoffice/hexo-theme-hueman/wiki/Search

  3. 分类方法

    categories:
    - Diary
    - Life
    

    分类Life成为Diary的子分类

leanote

https://github.com/leanote/leanote

Leanote, 不只是笔记!

  • 特性

    高效笔记:Leanote 有易操作的界面, 包含一款富文本编辑器和Markdown编辑器,让您的笔记记录更轻松和高效。对高阶用户,我们还提供Vim 和Emacs 编辑模式,助推你的写作速度更上层楼。
    知识管理: Leanote 灵活而强大的“笔记本-笔记-标签”系统,让它成为你个人知识管理的利器。
    分享: 你可以通过Leanote同好友分享知识、想法和经历, 邀请好友加入你的笔记簿,通过云端交流信息。
    协作: Leanote协助你与同事之间相互协作,激荡新思路,随时随地头脑风暴。
    博客: Leanote也可以作为你的个人博客, 把你的知识传播的更远!
    
  • 其它特性

    支持Markdown编辑
    写作模式
    Vim 及 Emacs 编辑模式
    支持PDF导出
    支持批量操作
    博客自定义主题, 实现高度定制化
    

log4js

  1. 基本使用
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var log4js = require('log4js');

log4js.configure({
appenders: {
console: {type: 'console'},
receiveFile: {
type: 'dateFile',
filename: 'logs/socket-',
pattern: 'yyyy-MM-dd.log', // 滚动日志文件
maxLogSize: 10 * 1000 * 1000,
numBackups: 3,
alwaysIncludePattern: true,
layout: { // 自定义日志格式
type: 'pattern',
pattern: '[%d] [%p] recv <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< %m%n'
}
},
sendFile: {
type: 'dateFile',
filename: 'logs/socket-',
pattern: 'yyyy-MM-dd.log',
maxLogSize: 10 * 1000 * 1000,
numBackups: 3,
alwaysIncludePattern: true,
layout: {
type: 'pattern',
pattern: '[%d] [%p] send >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> %m%n'
}
}
},
categories: {
default: {appenders: ['console'], level: 'info'},
receive: {appenders: ['console','receiveFile'], level: 'info'},
send: {appenders: ['console','sendFile'], level: 'info'}
}
});

var receiveLogger = log4js.getLogger('receive');
var sendLogger = log4js.getLogger('send');

// 使用
receiveLogger.info(msg);

websocket

  1. 框架
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
var ws = require('ws');
var server = new ws.Server({port: 3838});

server.on('connection', function (wsconnection, req) {
wsconnection.isAlive = true; // 用于标识连接是否可用
var ip = req.connection.remoteAddress; // 获取对方ip
// 接收消息
wsconnection.on('message', async function (data){
try {
// 解决乱码回车特殊字符等问题
data = data.replace(/(\\|\n|\r|\t)/g, "");
var dataObj = JSON.parse(data);
}catch (exp){
log.error('data:' + data + ' error:' + exp);
}
});

// 心跳响应
wsconnection.on('pong', function (data) {
try{
wsconnection.isAlive = true;
}catch (exp){
log.error('pong error:' + exp);
}
});

// 关闭连接
wsconnection.on('close', async function (code, reason) {
try {

}catch (exp){
log.error('close code:' + code + ' reason:' + reason + ' error:' + exp);
}
});

// 连接错误,必须捕获,否则nodejs进程会在报错时崩溃重启
wsconnection.on('error', async function (err) {
try{

}catch (exp){
log.error('error data:' + err + ' error:' + exp);
}
});

// 失败响应
wsconnection.on('unexpected-response', function (request, response) {
try{

}catch (exp){
log.error('unexpected-response error:' + exp);
}
});

});

function noop() {}

// 定时扫描所有连接,进行心跳检测,如果连接不可用,则关闭连接
const interval = setInterval(function ping() {
server.clients.forEach(function each(wsconnection) {
if (wsconnection.isAlive === false) return wsconnection.terminate();
wsconnection.isAlive = false;
wsconnection.ping(noop);
});
}, 30000);

console.log('websocket-server running on 3838');

module.exports = server;

node-http-proxy

  1. 代理原理

访问http://localhost:8000,会被代理到访问http://localhost:9000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var http = require('http'),
httpProxy = require('http-proxy');
//
// Create your proxy server and set the target in the options.
//
httpProxy.createProxyServer({target:'http://localhost:9000'}).listen(8000); // See (†)

//
// Create your target server
//
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('request successfully proxied!' + '\n' + JSON.stringify(req.headers, true, 2));
res.end();
}).listen(9000);

比如可以通过访问 http://localhost:9000 ,代理到访问 http://www.google.com

实战

高并发Nodejs参数调整

关闭v8 空时通知机制

1
--nouse-idle-notification

修改http.Agent

1
2
3
4
5
6
官网说明:
agent.maxSockets
By default set to 5. Determines how many concurrent sockets the agent can have open per host.
(为了http请求能复用connection连接,Nodejs在http.Agent创建了一个默认大小为5的连接池)
修改后如下:
require("http").globalAgent.maxSockets = Infinity;

修改–max-old-space-size

1
2
3
4
--max-old-space-size=2048(根据自己情况,可以调大,单位是M)
说明:v8 在64位操作系统默认使用的max-old-space-size是1.7G,大家可以通过:node --v8-options 查看V8参数

pm2 start app.js --node-args="--nouse-idle-notification --gc_global --max-old-space-size=8168" --name xxxxx -e logs/xxxxx.err -o logs/xxxxx.out

小实验

  1. 读取tab分隔的文件,转换成json文件

读取文件如下:

1
2
3
1 英勇青铜III
2 英勇青铜II
3 英勇青铜I

得出json文件如下:

1
2
3
4
5
6
7
8
9
10
11
{
"1": {
"name": "英勇青铜III"
},
"2": {
"name": "英勇青铜II"
},
"3": {
"name": "英勇青铜I"
}
}

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
var fs=require("fs");
var data=fs.readFileSync("data/GroupsData.txt","utf-8");
var lines = data.split('\n');
var objs = {};
for(var i = 0; i < lines.length ;i++){
var line = lines[i];
var steps = line.split('\t');
objs[steps[0]] = {
name:steps[1]
};
}
fs.writeFileSync("data/GroupsData.json",JSON.stringify(objs));
  1. 连接使用redis

连接rediser.js

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
"use strict";

const redis = require('redis');

const config = require('../config'),
log = require('../utils/logger.js');

const redisObj = {
client: null,
connect: function () {
this.client = redis.createClient(config.redis);
this.client.on('error', function (err) {
log.error('redisCache Error ' + err);
});
this.client.on('ready', function () {
log.info('redisCache connection succeed');
});
},
init: function () {
this.connect(); // 创建连接
var instance = this.client;
// 主要重写了一下三个方法。可以根据需要定义。
const get = instance.get;
const getObj = instance.getObj;
const set = instance.set;
const setObj = instance.setObj;
const setex = instance.setex;
const setexObj = instance.setexObj;

instance.get = function (key) {
return new Promise((resolve, reject) =>{
get.call(instance, key, function (err, res) {
if(err){
reject(err);
}
resolve(res);
});
});
};
instance.getObj = function (key) {
return new Promise((resolve, reject) =>{
get.call(instance, key, function (err, res) {
if(err){
reject(err);
}
resolve(JSON.parse(res));
});
});
};
instance.set = function (key, value) {
if (value != undefined) {
return new Promise((resolve, reject) =>{
set.call(instance, key, value, function (err, res) {
if(err){
reject(err);
}
resolve(res);
});
});
}
return null;
};
instance.setObj = function (key, value) {
if (value != undefined) {
return new Promise((resolve, reject) =>{
set.call(instance, key, JSON.stringify(value), function (err, res) {
if(err){
reject(err);
}
resolve(res);
});
});
}
return null;
};
// 可以不用传递expires参数。在config文件里进行配置。
instance.setex = function (key, value) {
if (value !== undefined) {
return new Promise((resolve, reject) =>{
setex.call(instance, key, 36000, value, function (err, res) {
if(err){
reject(err);
}
resolve(res);
});
});
}
};
instance.setexObj = function (key, value) {
if (value !== undefined) {
return new Promise((resolve, reject) =>{
setex.call(instance, key, 36000, JSON.stringify(value), function (err, res) {
if(err){
reject(err);
}
resolve(res);
});
});
}
};
return instance;
},
};
// 返回的是一个redis.client的实例
exports.client = redisObj.init();

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var rediser = require('../utils/rediser.js'),
config = require('../app.js'),
log = require('../utils/logger.js');

var setFriendInvite = async (userId, roomCode, mailId) => {
try{
var value = await rediser.client.getObj('friendInvite');
if(!value) value = {};
value[userId+roomCode] = mailId;
await rediser.client.setObj('friendInvite',value);
log.info('setFriendInvite succeed value:' + JSON.stringify(value));
}catch(exp){
log.error('setFriendInvite error:' + exp);
}
}
  1. 使用redis的发布订阅实现跨服通信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const redis = require('redis');

const config = require('../config'),
log = require('../utils/logger.js');

const sub = redis.createClient(config.redis);
exports.sub = sub;
const pub = redis.createClient(config.redis);
exports.pub = pub;

// 监听其他服务器发送的消息
sub.on('message', function(cmd, message) {
log.info('cmd:' + cmd + ' message:' + message);
if (cmd == 'chalengFriend')
{
var data = JSON.parse(message);
}
});

// 订阅消息
sub.subscribe('chalengFriend');

// 发送消息
pub.publish('chalengFriend', JSON.stringify({value:1}));

心得

  1. async/await

    async/await是es7的新标准,并且在node7.0中已经得到支持

    async函数定义如下

    async function fn(){
        return 0;
    }
    

    使用async关键字修饰function即可,async函数的特征在于调用return返回的并不是一个普通的值,而是一个Promise对象

    如果正常return了,则返回Promise.resolve(返回值),如果throw一个异常了,则返回Promise.reject(异常)

    也就是说async函数的返回值一定是一个promise,只是你写出来是一个普通的值,这仅仅是一个语法糖

    await关键字只能在async函数中才能使用。await关键字后跟一个promise对象,函数执行到await后会退出该函数,直到事件轮询检查到Promise有了状态resolve或reject才重新执行这个函数后面的内容。

    例如:

    var a = await A.findOne({id:id});
    if(!a){
        a = await newA(id); // 必须加await,否则newA返回值是promise不是A对象!!!!!!!!!!!!!
    }
    
    async function newA(id){
        var a = new A();
        a = await a.save();
        return a;
    }
    
  1. log4js使用

    var log4js = require('log4js');
    log4js.configure({
        appenders: { critz2payment: { type: 'file', filename: 'logs/critz2payment.log' } },
        categories: { default: { appenders: ['critz2payment'], level: 'info' } }
    });
    const logger = log4js.getLogger('critz2payment');
    
    logger.info('lxpaymentCallback:' + JSON.stringify(reqParm));
    
  2. 一次性密码 NodeJS 实现TOTP算法(Google Authenticator)

    http://www.blogjava.net/baicker/archive/2013/09/11/403712.html

    NodeJS下,先安装notp库

    npm install notp
    

    然后脚本测试如下:

    var notp = require('notp');
    var token = 'LFLFMU2SGVCUIUCZKBMEKRKLIQ';
    var key = notp.totp.gen(token, {});
    console.log(key);
    

性能调优

1百万个链接的并发连接数

http://blog.caustik.com/2012/08/19/node-js-w1m-concurrent-connections/

The new tweaks, placed in /etc/sysctl.conf (CentOS) and then reloaded with “sysctl -p” :

1
2
3
4
5
6
7
8
9
10
net.core.rmem_max = 33554432
net.core.wmem_max = 33554432
net.ipv4.tcp_rmem = 4096 16384 33554432
net.ipv4.tcp_wmem = 4096 16384 33554432
net.ipv4.tcp_mem = 786432 1048576 26777216
net.ipv4.tcp_max_tw_buckets = 360000
net.core.netdev_max_backlog = 2500
vm.min_free_kbytes = 65536
vm.swappiness = 0
net.ipv4.ip_local_port_range = 1024 65535

错误汇总

  1. error: #error This version of node/NAN/v8 requires a C++11 compiler

    安装devtoolset-3

    rpm -ivh https://www.softwarecollections.org/en/scls/rhscl/devtoolset-3/epel-6-x86_64/download/rhscl-devtoolset-3-epel-6-x86_64.noarch.rpm
    yum install devtoolset-3-gcc-c++
    

    安装完毕后使用命令,临时覆盖系统原先的gcc引用

    scl enable devtoolset-3 bash
    

    若想永久覆盖,可在.bashrc中添加

    source /opt/rh/devtoolset-3/enable
    

    然后再继续npm install,就能够正常安装以前无法编译通过的module了

  2. 安装 node-inspector 时报告网络错误

    npm install -g node-inspector
    
    npm http GET https://registry.npmjs.org/node-inspector
    npm http GET https://registry.npmjs.org/node-inspector
    npm http GET https://registry.npmjs.org/node-inspector
    npm ERR! network getaddrinfo ENOTFOUND
    npm ERR! network This is most likely not a problem with npm itself
    npm ERR! network and is related to network connectivity.
    npm ERR! network In most cases you are behind a proxy or have bad network settin
    gs.
    npm ERR! network
    npm ERR! network If you are behind a proxy, please make sure that the
    npm ERR! network ‘proxy’ config is set properly. See: ‘npm help config’
    
    npm ERR! System Windows_NT 6.2.9200
    npm ERR! command “C:\Program Files\nodejs\\node.exe” “C:\Program Files\nod
    ejs\node_modules\npm\bin\npm-cli.js” “install” “-g” "node-inspector"
    npm ERR! cwd D:\sec4
    npm ERR! node -v v0.10.26
    npm ERR! npm -v 1.4.3
    npm ERR! syscall getaddrinfo
    npm ERR! code ENOTFOUND
    npm ERR! errno ENOTFOUND
    npm ERR!
    

    解决方法

    npm --registry=https://registry.npm.taobao.org install -g node-inspector
    

    如果还有问题,可能是域名解析问题 ping registry.npm.taobao.org 看看是否通

  3. Express Request entity too large

    解决方法

    var bodyParser = require('body-parser');
    app.use(bodyParser.json({limit: '50mb'}));
    app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
    
  4. user “root” does not have permission to access the dev dir

    解决方案

    npm install time -g --unsafe-perm
    
    增加了--unsafe-perm
    
  5. node用n升级后看version版本没变

    node的安装目录和 n 默认的路径不一样导致的

    which node
    // 发现在/usr/bin/node, n是在目录/usr/local/bin/node
    

    编辑环境配置文件:

    vim ~/.bash_profile
    

    将下面代码插入到文件末尾:

    export PATH=/usr/local/bin:$PATH
    

    执行source使修改生效

    source ~/.bash_profile
    

    确认一下环境变量是否生效:

    echo $PATH
    

    再查看当前 node 版本:

    node -v
    
  6. npm的任何命令都报错:SyntaxError: Error parsing /usr/local/lib/node_modules/npm/node_modules/npmlog/node_modules/gauge/node_modules/strip-ansi/package.json: Unexpected end of JSON input

    npm本身的关联包出问题了

    根据报错信息在其他机器上安装缺失的包,并用好的替换报错的文件
    如strip-ansi/package.json
    npm install strip-ansi
    拷贝package.json到目录/usr/local/lib/node_modules/npm/node_modules/npmlog/node_modules/gauge/node_modules/strip-ansi/
    

    然后升级npm

    npm install -g npm
    
  7. 升级node后报错

1
2
3
4
5
Error: The module 'node-sass'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 51. This version of Node.js requires
NODE_MODULE_VERSION 55. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
NODE_MODULE_VERSION 是每一个 Node.js 版本内人为设定的数值,意思为 ABI 的版本号。一旦这个号码与已经编译好的二进制模块的号码不符,便判断为 ABI 不兼容,需要用户重新编译。

解决方案:

    备份原项目的node_modules
    npm install
  1. 升级node后mongoose连接不上

    mongoose的版本太低了,修改package.js文件,把限制mongoose的版本号去掉

    "mongoose": "",
    "mongoose-auto-increment": "",
    

    重新安装

    npm uninstall mongoose
    npm uninstall mongoose-auto-increment
    
    npm install mongoose
    npm install mongoose-auto-increment
    
  2. Cannot find module ‘internal/fs’的错

    是graceful-fs 这个模块出现了问题!

    可能原因有以下几点:

    1. 没装
    2. npm的graceful-fs 出现了多个版本
    

    解决办法:

    首先查看: npm list -g graceful-fs
    
    如发现:npm ERR! invalid: graceful-fs@3.0.8 /usr/local/lib/node_modules/npm/node_modules/cmd-shim/node_modules/graceful-fs
    或类似:
    │ ├─┬ cmd-shim@2.0.2
    │ │ └── graceful-fs@3.0.8  invalid
    
    则表示版本有问题
    
    其次全局安装 graceful-fs  接着 在对应环境再安装一遍
    
    如果不行根据目录在其他地方拷贝graceful-fs到目录中,例如:
    /usr/local/lib/node_modules/npm/node_modules/cmd-shim/node_modules/graceful-fs
    
  1. mongoose表里id自增问题

    尽量抛弃使用int的自增id,使用uuid更合适

    删除一条数据

    var result = await mails.remove({mailId:req.params.id});
    if (result != null && result.result.ok > 0) {
        res.json({status: 1, msg: '删除成功!'});
        return;
    }
    
  1. sudo npm install 报错找不到npm命令
1
2
3
4
sudo ln -s /usr/local/bin/node /usr/bin/node
sudo ln -s /usr/local/lib/node /usr/lib/node
sudo ln -s /usr/local/bin/npm /usr/bin/npm
sudo ln -s /usr/local/bin/node-waf /usr/bin/node-waf
  1. nodejs 用socket.io 发送gzip加密后数据是经过base64_encode的,否则emit不能发送二进制数据,只能发送string类型数据,c++进行gzip解密需要先使用base64_decode一下

  2. 安装canvas

http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.24/gtk+-bundle_2.24.10-20120208_win32.zip
http://downloads.sourceforge.net/gladewin32/gtk-dev-2.12.9-win32-2.exe

  1. ==返回给客户端500错误码,会导致服务器端对客户端的请求丢失响应一段时间==

尽量避免返回500错误码,争取都是200

a