https://nodejs.org
const puppeteer = require('puppeteer');
const url = 'https://movie.douban.com/subject/26794435';
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);
const film = await page.evaluate(() => {
const title = $('h1 > span:first-child').text();
const poster = $('#mainpic img').attr('src');
const desc = $('span[property="v:summary"]').text().trim();
return {title, poster, desc};
});
console.log(JSON.stringify(film, null, ' '));
await browser.close();
})();
// index.js
console.log('Hello World');
Hello World
➜ ~ node index.js
const fs = require('fs');
fs.readFile('test.txt', (err, data) => {
console.log(data);
});
console.log('read file content');
const fs = require('fs');
fs.readFile('a.text', (err, buffer) => {
console.log(buffer);
})
const {readFile} = require('fs');
readFile('a.txt', (err, buffer) => {
console.log(buffer);
})
// app.js
var circle = require('./circle.js');
console.log('半径为4的圆面积是:' + circle.area(4));
// circle.js
const pi = Math.PI;
exports.area = function (r) {
return pi * r * r;
};
exports.circumference = function (r) {
return 2 * pi * r;
};
// 加载绝对路径文件
require('/foo/bar/a.js');
// 加载相对路径文件
require('../a.js');
// 加载无后缀的文件
require('../a');
// 加载外部模块
require('pkg-name');
// app.js
const circle = require('./circle.js');
require 并不是全局变量
定义的变量 circle 会污染其他文件么?
通过 fs.readFileSync 同步拿到文件内容
对内容进行包装
(function (exports, require, module, __filename, __dirname) {
var circle = require('./circle.js');
console.log('The area is ' + circle.area(4));
});
通过 vm.runInThisContext 执行
获取 module 对象的值作为模块的返回值
npm config set init.author.name
等命令修改初始化时的默认值➜ star-plan npm init -y
Wrote to /Users/lizheming/star-plan/package.json:
{
"name": "star-plan",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
"dependencies": {
"accepts": "^1.2.2",
"content-disposition": "~0.5.0",
"cookies": "~0.7.0",
"debug": "*",
"delegates": "^1.0.0",
"escape-html": "~1.0.1",
"fresh": "^0.5.2",
"only": "0.0.2",
"parseurl": "^1.3.0",
"statuses": "^1.2.0",
"type-is": "^1.5.5",
"vary": "^1.0.0"
},
1.0.0 Must match version exactly
1.0.0 Must be greater than version
=1.0.0 <1.0.0 <=1.0.0
~1.0.0 “Approximately equivalent to version”
^1.0.0 “Compatible with version”
1.2.x 1.2.0, 1.2.1, etc., but not 1.3.0
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Hello World');
});
server.listen(3000);
const Koa = require('koa');
const app = new Koa();
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
module.exports = class Application extends Emitter {
...
listen() {
debug('listen');
const server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
}
use(fn) {
this.middleware.push(fn);
return this;
}
callback() {
const fn = compose(this.middleware);
if (!this.listeners('error').length) this.on('error', this.onerror);
const handleRequest = (req, res) => {
res.statusCode = 404;
const ctx = this.createContext(req, res);
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fn(ctx).then(handleResponse).catch(onerror);
};
return handleRequest;
}
createContext(req, res) {
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
context.originalUrl = request.originalUrl = req.url;
context.cookies = new Cookies(req, res, {
keys: this.keys,
secure: request.secure
});
request.ip = request.ips[0] || req.socket.remoteAddress || '';
context.accept = request.accept = accepts(req);
context.state = {};
return context;
}
}
Koa 无规范约束,不利于团队开发
中间件繁多,质量参差不齐,选择困难
逻辑分层
路由处理
数据解析、校验
权限校验
Session、Cache
数据库、Redis
安全
…
TODO List 项目实战
https://github.com/lizheming/simple-todo
功能列表
TODO List 的页面
API
获取 TOO 列表
增加 TODO
删除 TODO
更新 TODO 状态
数据表设计
CREATE TABLE `todo` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`desc` varchar(255) NOT NULL DEFAULT '',
`status` tinyint(11) NOT NULL DEFAULT '0' COMMENT '0 是未完成,1是已完成',
`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
npm install -g think-cli
➜ ~ thinkjs --version
2.2.8
创建项目
$ thinkjs new todo
$ cd todo
$ npm install
启动项目
➜ simple-todo npm start
> simple-todo@1.0.0 start /Users/lizheming/Desktop/star-plan/simple-todo
> node development.js
[2019-04-21T21:58:16.197] [7077] [INFO] - Server running at http://127.0.0.1:8360
[2019-04-21T21:58:16.201] [7077] [INFO] - ThinkJS version: 3.2.10
[2019-04-21T21:58:16.201] [7077] [INFO] - Environment: development
[2019-04-21T21:58:16.201] [7077] [INFO] - Workers: 1
模板渲染
// src/controller/index.js
const Base = require('./base.js');
module.exports = class extends Base {
indexAction() {
return this.display();
}
};
API 开发
使用 RESTful API
RESTful 接口规范
每个 API 都对应一种资源或资源集合
使用 HTTP Method 来表示对资源的动作
使用 HTTP Status Code 来表示资源操作结果
RESTful API
GET /ticket 获取 ticket 列表
GET /ticket/:id 查看某个具体的 ticket
POST /ticket 新建一个 ticket
PUT /ticket/:id 更新 id 为 12 的 ticket
DELETE /ticket/:id 删除 id 为 12 的 ticekt
创建 API 文件
➜ simple-todo thinkjs controller -r ticket
think-cli · Create: src/controller/rest.js
think-cli · Create: src/controller/ticket.js
think-cli · Create: src/logic/api/ticket.js
配置路由
// src/config/router.js
module.exports = [
['/ticket/:id?', 'rest'], // 配置 RESTful API 路由
]
路由解析
GET /api/todo 获取 TODO 列表,执行 getAction
GET /api/todo/:id 获取某个TODO的详细信息,执行 getAction
POST /api/todo 添加一个 TODO,执行 postAction
PUT /api/todo/:id 更新一个 TODO,执行 putAction
DELETE /api/todo/:id 删除一个 TODO,执行 deleteAction
getAction
// src/controller/rest.js
async getAction() {
let data;
if (this.id) {
const pk = this.modelInstance.pk;
data = await this.modelInstance.where({ [pk]: this.id }).find();
return this.success(data);
}
data = await this.modelInstance.select();
return this.success(data);
}
postAction
async postAction() {
const pk = this.modelInstance.pk;
const data = this.post();
delete data[pk];
if (think.isEmpty(data)) {
return this.fail('data is empty');
}
const insertId = await this.modelInstance.add(data);
return this.success({ id: insertId });
}
deleteAction
async deleteAction() {
if (!this.id) {
return this.fail('params error');
}
const pk = this.modelInstance.pk;
const rows = await this.modelInstance.where({ [pk]: this.id }).delete();
return this.success({ affectedRows: rows });
putAction
async putAction() {
if (!this.id) {
return this.fail('params error');
}
const pk = this.modelInstance.pk;
const data = this.post();
delete data[pk];
if (think.isEmpty(data)) {
return this.fail('data is empty');
}
const rows = await this.modelInstance.where({ [pk]: this.id }).update(data);
return this.success({ affectedRows: rows });
}
数据库配置
// src/config/adapter.js
exports.model = {
type: 'mysql',
common: {
logConnect: isDev,
logSql: isDev,
logger: msg => think.logger.info(msg)
},
mysql: {
handle: mysql,
database: 'todo',
prefix: '',
encoding: 'utf8',
host: '127.0.0.1',
port: '',
user: 'root',
password: 'root',
dateStrings: true
}
};
http://127.0.0.1:8360/ticket
{
"errno": 0,
"errmsg": "",
"data": [{
"id": 1,
"desc": "八点打扫房间",
"status": 0,
"createdAt": "2018-07-08 17:12:59",
"updatedAt": "2018-07-08 17:13:14"
}]
}
数据校验
提供了 Logic 机制转门用来支持数据校验
文件和 Action 与 Controller 一一对应
数据校验
// src/logic/ticket.js
module.exports = class extends think.Logic {
getAction() {
this.rules = {
id: {
int: true
}
};
}
deleteAction() {
this.rules = {
id: {
int: true,
required: true,
method: 'get'
}
};
}
putAction() {
this.rules = {
id: {
int: true,
required: true,
method: 'get'
},
status: {
int: true,
required: true
},
desc: {
required: true
}
};
}
postAction() {
this.rules = {
desc: {
required: true
}
};
}
};
DELETE http://127.0.0.1:8360/ticket
{
"errno": 1001,
"errmsg": {
"id": "id can not be blank"
}
}
数据库操作
封装了 think.Model 类
提供增删改查等操作
支持关联模型查询
自动分析数据表字段类型
自动数据安全过滤
控制器中操作模型
const model = this.model(modeName);
根据模型名查找 src/model 下的模型文件
文件存在,实例化对应的模型类
文件不存在,实例化 think.Model 类
定义模型类
// src/model/todo.js
module.exports = class TodoModel extends think.Model {
getList () {
// get list
}
}
模型的好处
简化代码、提高效率
不用太懂 SQL 语句也能操作数据库
避免手写 SQL 语句的安全风险
NodeJS调试
https://zhuanlan.zhihu.com/p/41315709
日志调试
断点调试
node --inspect
vscode
ndb
NodeJS 6.3+ 使用 node --inspect
参数启动可以在 Chrome 浏览器中调试,在 chrome://inspect
中可以发现你的项目并启动 devtool
Node 开发角色转换
前端
跟浏览器打交道,兼容性问题
组件化
加载速度、JS 执行性能、渲染性能
错误监控
XSS、CSRF 等安全漏洞
服务端
数据库、Redis 等周边服务
性能、内存泄露、CPU、机器管理
服务监控、错误监控、流量监控、报警
SQL注入、目录遍历等安全漏洞