1 简介
优点:
- nodejs的对象,语法和 js 一样
- 性能相对于一些后台语言(PHP)高
- 前后台配合方便
缺点:
没有像 java 那样有丰富的库的支持
用处:
- 小型后台系统、中间层
- 基于 node 的工具 :测试、构建(gulp、WebPack、grunt)、抓取
2 数据交互
服务器与页面进行数据交互
Node接收GET数据、POST数据、文件数据
文件数据优化:流操作、zlib压缩
2.1 get数据
获取数据主要要得到 路径pathname, 参数query
const http = require('http');
const urllib = require('url');
http.createServer((req, res) => {
let {pathname, query} = urllib.parse(req.url, true);
console.log(pathname, query);
res.end();
}).listen(8080);
2.2 post数据
post数据较大,不能一次性传输
需要将一个大包切成若干个小包
const http = require('http');
const urllib = require('url');
const querystring = require('querystring');
http.createServer((req, res) => {
let arr = [];
req.on('data', buffer => {
arr.push(buffer); // ?
});
req.on('end', () => {
// 将数组连接成buffer字符串
let buffer = Buffer.concat(arr);
// querystring => 解析buffer字符串数据
let post = querystring.parse(buffer.toString());
console.log(buffer);
console.log(post);
});
res.end();
}).listen(8080);
2.3 文件post数据
解析二进制文件数据
步骤一:分析
------WebKitFormBoundaryYcAqX5KAq2SdHuVG // 分隔符
Content-Disposition: form-data; name="user" // 数据描述(文件型有两行数据描述)
// 每个换行都有/r/n
dwadwd
------WebKitFormBoundaryYcAqX5KAq2SdHuVG
Content-Disposition: form-data; name="pass"
wdawd
------WebKitFormBoundaryYcAqX5KAq2SdHuVG
Content-Disposition: form-data; name="f1"; filename="wda.txt"
Content-Type: text/plain
wdawdwwdwd
------WebKitFormBoundaryYcAqX5KAq2SdHuVG--
步骤二:简化
<分隔符>\r\n数据描述\r\n\r\n数据值\r\n
<分隔符>\r\n数据描述\r\n\r\n数据值\r\n
<分隔符>\r\n数据描述1\r\n数据描述2\r\n\r\n<文件内容>\r\n
<分隔符>--
步骤四:用"<分隔符>"切开数据
[
空,
\r\n数据描述\r\n\r\n数据值\r\n,
\r\n数据描述\r\n\r\n数据值\r\n,
\r\n数据描述1\r\n数据描述2\r\n\r\n<文件内容>\r\n,
-\-\r\n
]
步骤五:丢弃头尾元素
[
\r\n数据描述\r\n\r\n数据值\r\n,
\r\n数据描述\r\n\r\n数据值\r\n,
\r\n数据描述1\r\n数据描述2\r\n\r\n<文件内容>\r\n,
]
步骤六:丢弃每一项的头尾\r\n
[
数据描述\r\n\r\n数据值,
数据描述\r\n\r\n数据值,
数据描述1\r\n数据描述2\r\n\r\n<文件内容>,
]
步骤七:用第一次出现的"\r\n\r\n"切分
普通数据:[数据描述, 数据值]
或
文件数据:[数据描述1\r\n数据描述2, <文件内容>]
步骤八:判断描述的里面有没有"\r\n"
有\r\n——文件数据:[数据描述1\r\n数据描述2, <文件内容>]
没有\r\n——普通数据:[数据描述, 数据值]
步骤九:分析"数据描述"
2.3.1 代码实现
const http=require('http');
const common=require('./libs/common');
const fs=require('fs');
const uuid=require('uuid/v4');
let server=http.createServer((req, res)=>{
let arr=[];
req.on('data', data=>{
arr.push(data);
});
req.on('end', ()=>{
let data=Buffer.concat(arr);
//data
//解析二进制文件上传数据
let post={};
let files={};
if(req.headers['content-type']){
let str=req.headers['content-type'].split('; ')[1];
if(str){
let boundary='--'+str.split('=')[1];
//1.用"分隔符切分整个数据"
let arr=data.split(boundary);
//2.丢弃头尾两个数据
arr.shift();
arr.pop();
//3.丢弃掉每个数据头尾的"\r\n"
arr=arr.map(buffer=>buffer.slice(2,buffer.length-2));
//4.每个数据在第一个"\r\n\r\n"处切成两半
arr.forEach(buffer=>{
let n=buffer.indexOf('\r\n\r\n');
let disposition=buffer.slice(0, n);
let content=buffer.slice(n+4);
disposition=disposition.toString();
if(disposition.indexOf('\r\n')==-1){
//普通数据
//Content-Disposition: form-data; name="user"
content=content.toString();
let name=disposition.split('; ')[1].split('=')[1];
name=name.substring(1, name.length-1);
post[name]=content;
}else{
//文件数据
/*Content-Disposition: form-data; name="f1"; filename="a.txt"\r\n
Content-Type: text/plain*/
let [line1, line2]=disposition.split('\r\n');
let [,name,filename]=line1.split('; ');
let type=line2.split(': ')[1];
name=name.split('=')[1];
name=name.substring(1,name.length-1);
filename=filename.split('=')[1];
filename=filename.substring(1,filename.length-1);
// 将文件内容存入upload文件夹中,文件名由uuid生成
let path=`upload/${uuid().replace(/\-/g, '')}`;
fs.writeFile(path, content, err=>{
if(err){
console.log('文件写入失败', err);
}else{
files[name]={filename, path, type};
console.log(files);
}
});
}
});
//5.完成
console.log(post);
}
}
res.end();
});
});
server.listen(8080);
2.3.2 buffer操作
- 查找 indexOf()
- 截取 slice(s, e) [s, …, e)
- 切分 split(需要自己实现)
// split切分实现
// this —— 需要切分的字符串(buffer型)
// separator—— 分隔符
// abc---wer---qwe
Buffer.prototype.split = Buffer.prototype.split || function(separator) {
let arr = [];
let current = 0;
let n = 0;
while((n=this.indexOf(separator, current)) > 0) {
arr.push(this.slice(current, n));
current = n + separator.length;
};
arr.push(this.slice(current));
return arr;
};
3 mysql使用
数据库:navicat
3.1 引入mysql模块
cnpm init
cnpm i mysql -D
3.2 使用mysql
const mysql = require('mysql');
let db = mysql.createPool({host: 'localhost', user: 'root', password: '', port: 3306, database: '20180127'});
3.3 SQL增删改查
1.增 INSERT
INSERT INTO 表 (字段列表) VALUES(值列表)
INSERT INTO user_table (ID, name, gender, chinese, math, english) VALUES(0, 'blue', '男', 35, 18, 29);
2.删 DELETE
DELETE FROM 表 WHERE 条件
DELETE FROM user_table WHERE ID=3;
3.改 UPDATE
UPDATE 表 SET 字段=值, 字段2=值2, ... WHERE 条件
UPDATE user_table SET chinese=100 WHERE ID=2;
4.查 SELECT
SELECT 字段列表 FROM 表 WHERE 条件
SELECT * FROM user_table WHERE user='${user}';
SQL注入
概念:将SQL语句通过表单提交,对数据库执行恶意SQL命令
防范:充分校验
3.4 例子
// 1. 连接数据库(config配置文件)
// 2. query命令增删改查
// 3. crypto二次加密
const mysql=require('mysql');
const config=require('./config');
const crypto=require('crypto');
let db=mysql.createPool({
host: config.db_host,
port: config.db_port,
user: config.db_user,
password: config.db_pass,
database: config.db_name,
});
let md5=crypto.createHash('md5');
let username='skylake';
let password='123456';
md5.update(password+config.md5_key);
password=md5.digest('hex');
db.query(`INSERT INTO user_table (name,password) VALUES('${username}', '${password}')`, (err, rows)=>{
if(err){
console.log(err);
}else{
console.log(rows);
}
});
4 express框架
入门简单、做东西极其麻烦——回调函数
写法
const express = require('express');
let server = express();
server.listen(8080);
server.get('/xxx', ()=>{});
server.post('/xxx', ()=>{});
server.use('/xxx', ()=>{}); //use = get + post
server.get(()=>{});
server.post(()=>{});
server.use(()=>{});
4.1 数据交互
4.1.1 GET数据
数据在 req.query 中
server.get('/路径名', (req, res) => {
console.log(req.query);
})
4.1.2 普通POST
通过引入中间件 body-parser 来处理
cnpm i body-parser -D
const express=require('express');
const body=require('body-parser');
let server=express();
server.listen(8080);
server.use(body.urlencoded({extended: false}));
server.post('/路径名', (req, res)=>{
console.log(req.body);
});
4.1.3 文件POST
通过引入中间件 multer 来处理
cnpm i multer -D
普通数据:req.body
文件数据:req.files
const express=require('express');
const multer=require('multer');
let server=express();
server.listen(8080);
server.use(multer({dest: 'upload/'}).any());
server.post('/upload', (req, res)=>{
console.log(req.body);
console.log(req.files);
});
4.1.4 cookie(签名)
通过引入中间件 cookie-parser 来处理
cnpm i cookie-parser -D
const express=require('express');
const cookieParser=require('cookie-parser');
let server=express();
server.listen(8080);
server.use(cookieParser({}));
server.get('/a', (req, res)=>{
console.log(req.cookies); // 接受
res.cookie(名字, 值, 选项); // 发送——express自带
res.send('aaa');
});
cookie签名
// 生成带签名的cookie
server.use(cookieParser('任意字符串'));
...
res.cookie('c', 11, {signed: true});
// 获取cookie
req.signedCookie;
4.2 命令
4.2.1 res.send()
res.send() 是 res.write() 升级版,可以向页面写任意形式的数据
不需要 res.end()
4.2.2 res.sendFile()
发送文件
必须用绝对路径:path.reslove(’./xxx’);
4.2.3 res.sendStatus()
发送状态码
= writeHeader + write + end
4.2.4 res.redirect()
重定向
res.redirect(网址) // 等于
res.setHeader('location', '网址');
res.writeHeader(302);
res.end();
4.2.5 中间件
流水线:
- 利于重用
- 利于分工
- 要用next
- 顺序执行
中间件 | 功能 | 写法 |
---|---|---|
express.static(‘路径’)(自带) | 加载静态文件 | server.use(express.static(‘路径’)) |
body-parser | 处理普通post数据 | server.use(body.urlencoded({extended: false})); |
multer | 处理文件post数据 | server.use(multer({dest: ‘/path’}).any()); |
cookie-parser | 处理cookie数据 | server.use(cookieParser(‘加密字符串’)); |
cookie-session | 处理session出局 | server.use(cookieSession({keys: [’’, ‘’…]c})); |
4.3 路由router
- 创建
let router=express.Router(); - 给路由填东西
router.get(‘地址’, ()=>{});
router.post(‘地址’, ()=>{});
router.use(‘地址’, ()=>{}); - 添加到父级
server.use(‘路径’, router);
parentRouter.use(‘路径’, router);
5 koa框架
入门麻烦、做东西舒服
generator/async
koa@1 generator
koa@2 generator+async 过渡版
koa@3 async
使用
const koa=require('koa');
const router=require('koa-router');
let server=new koa();
server.listen(8080);
let r1=router();
server.use(r1.routes());
r1.get('/aaa', async (ctx, next) => {
ctx.request
ctx,response
}
5.1 路由
koa强依赖router(必须使用router)
const router=require('koa-router');
let r1=router();
server.use(r1.routes());
r1.get('xxx', async);
r1.post('xxx', async);
r1.use('xxx', async);
r1.put('xxx', async);
r1.delete('xxx', async);
...
5.2 请求/响应
ctx.request
ctx.request.method
ctx.request.url
ctx.request.header/headers
ctx.response
ctx.response.set('名字', '值') 设置Response Headers
ctx.response.status=xxx 状态码
ctx.response.body=xxx 向页面输出内容
5.3 加载静态文件
const koa=require('koa');
const staticCache=require('koa-static-cache');
const pathlib=require('path');
let server=new koa();
server.listen(8080);
server.use(staticCache(pathlib.resolve('www'))); // 推荐绝对路径
5.4 数据
- GET : ctx.request.query
- 路由参数 : ctx.params
- POST/文件
server.use(convert(betterBody({ //koa-convert——转换老版插件
uploadDir: '路径',
keepExtensions: bool
})));
ctx.request.fields 数据+文件信息
ctx.request.files 文件信息(全)
- cookie:
获取:ctx.cookies.get(name)
设置:ctx.cookies.set(name, val, options) - session:
使用:server.use(session({}, server))
获取/设置:ctx.session
5.5 mysql-pro
5.5.1 transaction事务
要么都发生、要么不发生
事务的四个特性:ACID
- A 原子性:要么都发生、要么都不发生
- C 持久性:只要事务提交了,他的作用就是永久
- I 隔离性:各个事务之间是独立
- D 一致性:事务前后的状态是一致的
// 使用事务
const koa=require('koa');
const router=require('koa-router');
const Mysql=require('mysql-pro');
const db=new Mysql({
mysql: {host: 'localhost',user: 'root',password: '',database: '20180412'}
});
const server=new koa();
server.listen(8080);
let r1=router();
server.use(r1.routes());
r1.get('/user', async ctx=>{
await db.startTransaction();
// 强制绑定事物
let data=await db.executeTransaction("SELECT * FROM user_table");
await db.stopTransaction();
console.log(data);
ctx.response.body=data;
});
5.5.2 防止注入
? 占位符
db.executeTransaction('SELECT * FRO user_table WHERE ID=? AND age=?', [id, 18])
5.5 中间件
中间件 | 功能 |
---|---|
koa-convert | 转换老版插件 |
path | 相对路径 -> 绝对路径 |
koa-better-body | 解析post数据 |
koa-static-cache | 加载静态资源(+压缩) |
koa-router | 路由 |
koa-session | session数据 |
koa-mysql | |
koa-pug | |
koa-ejs | |
mysql-pro |
待完善↓
回调地狱
模块机制
作用:可以减少模块耦合
实现:把一个功能单独写在一个文件里,用 exports 或 module.exports 暴露接口
- exports.xxx 用来导出属性和方法
- module.exports = function Fn {};——可以直接new实例对象