大前端:开始学习nodejs
Day 1:nodejs入门
模块(module),包(package),commonJS
内置模块
- http模块 (get, post, jsonp, cors, 爬虫)
- url模块
- querystring模块 api: parse, stringify 字符串编码规则escape
- event模块
- fs文件
- stream流模块
- zlib模块
- crypto模块
- 中间层转发
- 垃圾回收机制
路由
- 基础
- 参数接收
- Async
- 静态资源
顶级对象process
Day2:express框架
- 基本路由
- 静态资源
- 中间件 use()
- 获取请求参数
- 服务端渲染(SSR)和客户端渲染(SPA)
- 生成器 express-generator express project --view
Day3:
- MangoDB 数据库
安装与启动
命令行操作
可视化操作
nodejs操作
- 登录鉴权(auth)
- Cookie与Session
- JWT
- 文件上传
- 接口文档(APIDOC)
- koa
- 对比express
- 路由
- 静态资源
- 获取参数
- ejs模版
- cookie&session
- JWT
- 文件上传
- 操作mangoDB
- MySQL
- spl语句
- nodejs操作
- Socket
- ws模块
- socketio模块
Day4:
- Mocha
- 编写测试
- 异步测试
- http测试
- 钩子函数
Day5:后台管理项目node端

优势:进行系统级别的操作
- 文件读写(File System)
- 网络通信(http / https)
- 进程管理(process management)
模块化开发

包管理工具:npm pnpm yarn
const http = require('http');
//创建本地服务器来从其接收数据
const server = http.createserver((req,res)=>{
res.writeHead(200,{'content-Type':'application/json'}),
res.end(JSON.stringify({
data: 'He11o world!'
}));
});
server.listen(8000); //监听本地端口api:createserver 接收两个参数 (req, res) -request, response
req.url 路由path
插件 nodemon node-dev
url模块
pathname = url.parse(req.url).pathnameurl.parse url.format resolve 拼接
样例代码

输出示例

新版URL实例
const url = new URL(req.url, 'http://127.0.0.1:3000')
迭代器searchParams


http模块:jsonp
const http = require('http')
const url= require('url')
const app =http.createserver((req,res)=>{
let urlobj= url.parse(req.url, true)
switch(urlobj.pathname){
case '/api/user':
res.end(`${urlobj.query.cb}({"name": "gp145"})`) //jsonp 关键
break
default:
res.end('404.');
break;
})
app.listen(8080,()=>{console.1og('1ocalhost:8080')})返回callback 执行callback接收到data,这时就没有跨域问题。这个过程需要将response接收到动态创建的script之后取到数据添加到document。
const http = require('http')
const url= require('url')
const querystring =require( 'querystring')
const app = http.createserver((req,res)=>{
let data = ''
let urlobj = url.parse(req.url, true)
res.writeHead(200,{
'content-type':'application/json;charset=utf-8',
'Access-control-A1low-0rigin':'*'
})
req.on('data',(chunk)=>{
data += chunk
})
req.on('end',()=>{
responseResult(querystring.parse(data))
function responseResult(data){
switch(urobj.pathname){
case '/api/login':
res.end(isoN.stringify({
message: data
})
}))CORS问题 'Access-control-A1low-0rigin':'*'


爬虫功能:抓取数据

事件的监听和触发 订阅发布模式
const EventEmitter = require( 'events' )
class MyEventEmitter extends EventEmitter {}
const event = new MyEventEmitter()
event.on('play',(movie)=> {
console.1og(movie)
})
event.emit('play','我和我的祖国')
event.emit('play','中国机长')fs模块文件操作
const fs = require('fs')
// 创建文件夹
fs.mkdir('./logs',(err)=>{
console.1og('done.')
})
// 文件夹改名
fs.rename('./logs','./log',()=> {
console.1og('done')
})
// 删除文件夹
fs.rmdir('./1og',()=>{
console.1og('done.')
})
//写内容到文件里(有文件重置写入,无文件则创建并写入)
fs.writeFile('./logs/1og1.txt','hello',(err)=>{ // 错误优先的回调函数
if(err){
console.log(err.message)
}
})
//追加写入到文件
fs.appendFile('./logs/1og1.txt','\n hello',(err)=>{ // 错误优先的回调函数
if(err){
console.log(err.message)
}
})
//读文件
fs.readFile('./logs/log1.txt', (err, data)=>{
if(!err){
console.log(data.toString('utf-8'))
}
})
fs.readFile('./logs/log1.txt','utf-8', (err, data)=>{
if(!err){
console.log(data)
}
})
//删文件
fs.unlink('.logs/log1.txt', err=>{
console.log(err)
})
//读文件夹
fs.readdir('./avatar', (err, data)=>{
if(!err){
console.log(data)
}
})
// 返回一个布尔值,检查文件是否存在
fs.stat('./avatar', (err, data)=>{
})
// 读取文件/目录信息
fs.readdir('./', (err, data)=>{
data.forEach((value, index)=>{
fs.stat(`./${value}`, (err, stats)=>{
console.log(value + ' is' + (stats.isDirectory() ? 'directory' : 'file'))
})
})
})解决同步异步编程问题 (async)
//同步创建,阻塞后面代码执行
fs.mkdirSync('.avatar')
//一定要进行错误处理
try {
fs.mkdirSync('.avatar')
} catch(err){
console.log(err)
}封装一个文件操作方法:删除文件夹及其文件
// 同步读取
fs.readdir('./avatar', (err, data)=>{
data.forEach(item=>{
fs.unlinkSync(`./avatar/${item}`)
})
fs.rmdir('./avatar', (err)=>{
console.log(err)
})
})
//promises异步
const fs = require('fs').promises
fs.readdir('./avatar').then(async (data)=>{
let arr = []
data.forEach(item=>{
arr.push(fs.unlink(`./avatar/${item}`))
})
await Promise.all(arr)
await fs.rmdir('./avatar')
})
fs.readdir('./avatar').then(async (data)=>{
await Promise.all(data.map(item=>{fs.unlink(`./avatar/${item}`)}))
await fs.rmdir('./avatar')
})流(stream):监听文件流事件 管道(pipe):
var fs = require('fs')
var rs = fs.createReadStream('sample.txt', 'utf-8')
rs.on('data', (chunk)=>{
})
rs.on('end', ()=>{
})
rs.on('error', (err)=>{
})
const ws = fs.createWriteStream('sample.txt', 'utf-8')
ws.write('')
ws.end()
rs.pipe(ws) // 将ws pipe到rs 完成文件的复制Zlib 压缩
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
const gzip = zlib.createGzip()
const readstream = fs.createReadStream('./node.txt')
const writestream = fs.createWriteStream('./node2.txt')
readstream
.pipe(gzip)
.pipe(writestream)
http.createServer((req, res)=>{
res.writeHead(200, {
"Content-Type": "application/x-javascript"; "charset-utf-8",
"Content-Encoding": "gzip"
})
}).listen(3000, ()=>{
})MD5密码加密算法:不可逆只可以单向加密
const crypto = require('crypto')
const hash = crypto.createHash('md5') // 'sha1'算法
hash.update('Hello World!')
console.log(hash.digest('hex'))
// Hmac算法
const hmac = crypto.createHmac('sha256', 'secret-key')AES 对称加密算法
const crypto = require('crypto')
function encrypt(key, iv, data){
let decipher = crypto.createCipheriv('aes-128-cbc', key, iv)
return decipher.update(data, 'binary', 'hex') + decipher.final('hex')
}
function decrypt(key, iv, crypted){
crypted = Buffer.from(crypted, 'hex').toString('binary')
let decipher = crypto.createDecipheriv('aes-128-cbc', key, iv)
return decipher.update(crypted, 'binary', 'utf-8') + decipher.final('utf-8')
}实战:构建路由系统

基本路由原理




路由的接口封装 use和server和route模块化
接收参数 --静态资源管理
// 接收参数
function render(res, pathname, type=''){
res.writeHead(200, {"Content-Type": `${type ? type : 'text/html' };` + 'charset=utf8';})
res.write(fs.readFileSync(pathname), 'utf-8')
}
// 静态资源管理
function readStaticFile(req, res){
// 获取路径
const myURL = new URL(req.url, 'http://127.0.0.1:3000')
// 引入path模块根据系统拼接文件绝对路径
const pathname = path.join(__dirname, '/static', myURL.pathname)
if(fs.existsSync(pathname)){
//使用mime接收数据类型
render(res, pathname, mime.getType(myURL.pathname.split('.')[1]))
return true
}else{
return false
}
}第一个express实例
const express = require('express')
const app = express()
app.get('/', (req, res)=>{
res.send('hello world')
})
app.listen(3000, ()=>{
console.log('server start')
})回调函数数组 --中间件
app.get('', (req, res, next)=>{
//作一下前置判断
next()
}, (req, res)=>{
res.send() //成功发送
})
// 控制整一个异步过程的流程
function cb1(){}
function cb2(){}
function cb3(){}
app.get('', [cb1, cb2, cb3])
Express 是一个完全由路由和中间件构成的web开发框架,从本质上来说,一个Express应用就是在调用各种中间件
中间件是一个函数,它可以访问请求对象,响应对象,和web中处于请求-响应循环流程中的中间件。
它可以:
- 执行任何代码
- 修改请求和响应对象
- 终结请求-响应循环
- 调用堆栈中的下一个中间件
在Express应用中有如下几种中间件:
- 应用级中间件
- 路由级中间件
- 错误处理中间件
- 内置中间件
- 第三方中间件
// 应用级中间件:使用app.use()挂到app
app.use(func) //在语句后使用app实例都要走这个中间件
app.use('/home', func)
app.use('/', 路由模块)
const express = require('express')
const router = express.Router()
router.get('', func)
//错误信息级中间件
app.use((req, res)=>{
res.status(404).send()
})
//内置中间件
app.use(express.static('public'))
app.use(express.static('assect'))
第三方中间件

**怎么拿到request传过来的参数? ** ---req.query
拿到参数后的后续?
// get请求的参数
app.get('', (req, res)=>{
console.log(req.query)
})
//post的参数处理
app.use(express.urlencoded({extended: false})) //接收from表单编码数据
app.use(express.json()) // 接收json数据的中间件
app.post('', (req, res)=>{
console.log(req.body)
const { username, password } = req.body //后续
if(username === 'jiah' && password === '123456'){
res.send('success')
}else{
res.send('fail')
}
})利用Express托管静态文件
// 传入静态资源目录 public和assect
app.use(express.static('public'))
app.use(express.static('assect'))
服务端渲染(SSR)和客户端渲染(SPA)
模板引擎 (ejs模板)
- 服务端渲染,后端嵌套模版,SSR(后端把页面组装)
a. 做好静态页面,动态效果。
b. 把前端代码提供给后端,后端要把静态html以及里面的假数据给删掉,通过模版进行动态生成 html的内容
- 前后端分离,BSR (在前端中组装页面)
app.set('views', './views')
app.set('view engine', 'ejs')
app.get('/', (req, res)=>{
res.render('login') //找views文件夹下的login.ejs
})


<%- variable%> //可以被解析
<%# 注释%>
<%- include('./header', {isShow: true})%>// 配置模板引擎
app.set('views', './views')
app.set('view engine', 'html')
app.engine('html', require('ejs').renderFile) //支持直接渲染html文件mangoDB数据库



连接express 实现数据库的增删改查






mongoose模块





RESTful架构

MVC架构




Cookie&Session

// express-session
const express = require('express')
const session = require('express-session')
const MongoStore = require('connect-mongo')
app = express()
app.use(session({
name: '', // 命名空间
secret: '', // 服务器生成session的签名
resave: true, // 访问自动重新计时
saveUninitialized: true, // 强制为初始化的session存储
cookie: {
maxAge: 1000 * 60 * 10, // 过期时间
secure: false //https
},
rolling: true, // 为true表示超时前刷新,cookie会重新计时; 反之则按照第一次刷新开始计时
store: MongoStore.create({
mongoUrl: 'mongodb://127.0.0.1:27017/kerwin_session',
ttl: 1000 * 60 * 10
})
}))
app.use((req, res, next)=>{
if(req.url.includes('login')){
next()
return
}
if(req.session.user){ // login请求时候修改session
req.session.mydate = Date.now()
next()
}else{
res.redirect('/login')
}
})
router.get('logout', (req, res)=>{
req.session.destroy(()=>{
res.send()
})
})JSON Web Token

Cookie 存储着有效信息,容易被伪造。

//jsonwebtoken 封装
const jsonwebtoken = require("jsonwebtoken")
const secret ="kerwin'
const JwT={
generate(value,exprires){
return jsonwebtoken.sign(value,secret,{expiresIn:exprires})
},
verify(token){
try{
return jsonwebtoken.verify(token,secret)}catch(e){
return false
}
}
}
module.exports = JWT文件上传
- formdata
const upload = multer({ dest: 'public/uploads/' })
router.post('.user', upload.single('avatar'), UserController.addUser)
// 在接口中处理请求头发来的图片信息
const avatar = req.file ? `/uploads/${req.file.filename}` : '/images/default.png'APIDOC - API文档生成工具
npm install -g apidocapidoc的特征:
- 跨平台
- 支持语言广泛,即使不支持,也方便扩展
- 支持多个不同语言的多个项目生成一份文档
- 输出模版可自定义
- 根据文档生成mock数据
使用vscode扩展:apidoc-snippter
apidoc -i src/ -o doc/
// apidoc.json
{
"name"
"version"
"decription"
"title"
}koa框架
初始化
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next)=>{
ctx.response.body
ctx.body
ctx.require.path
})
app.listen(3000)
对比express
不同的异步流程控制 -- callback VS async/await
不同的中间件模型 -- connect 线性模型 洋葱模型


如何理解洋葱模型? -- 控制权会回溯到中间件上

koa的路由模块
// 基本路由配置
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router()
router.post('list', (ctx, next)=>{
})
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)配置静态资源
app.use(static(path.join(__dirname, 'public')))获取请求参数
//get参数
ctx.query ctx.querystring
//post参数
const bodyParser = require('koa-bodyparser')
app.use(bodyParser())
ctx.request.body使用模板引擎
//加载模板引擎
const views = require('koa-view') //koa模块
app.use(views(path.join(__dirname, './views'), {
extension: 'ejs'
}))
app.use(async (ctx)=>{
let title = 'hello koa2'
await ctx.render('index', {
title,
})
})cookie&&session
ctx.cookies.get(name, {options}) //读取上下文请求中的cookie
ctx.cookies.set(name, value, {options}) //在上下文中写入cookie
// session中间件
const session = require('koa-session-minimal')
app.use(session({
key:'SESSION_ID',
cookie: {
maxAge: 1000 * 60
}
}))
app.use(async (ctx, next)=>{
// 排除login相关的路由和接口
if(ctx.url.includes('login')){
await next()
return
}
if(ctx.session.user){
//重新设置一下session
ctx.session.mydate = Date.now()
await next()
}else{
ctx.redirect('/login')
}
})JWT
// 相关模块引入操作同express
// 前后端分离的相关设置
app.use(async (ctx, next)=>{
// 排login
if(ctx.url.includes('login')){
await next()
return
}
const token = ctx.headers['authorization']?.split(' ')[1]
if(token){
const payload = JWT.verify(token) // 解析token
if(payload){
//重新计算token过期时间
const newToken = JWT.generate({
_id: payload._id,
username: payload.username
}, '10s')
ctx.set("Authorization", newToken)
await next()
}else{
ctx.status = 401
ctx.body = {errCode:-1,errInfo:'token过期'}
}
}else{
await next()
}
})文件上传
npm install --save @koa/multer multer
const multer = require('@koa/multer')
const upload = multer({dest: 'public.uploads/'})
router.post('/', upload.single('avatar'), (ctx, next)=>{
console.log(ctx.request.body, ctx.file)
ctx.body = {
ok: 1,
info: 'success'
}
})连接到mongoDB

MySQL
关系型数据库的概念

Sql语句

//插入
INSERT INTO `students` (`id`, `name`, `score`, `gender`) VALUES (null, 'fool', 100, 1)
// 更新
UPDATE `students` SET `name`='money', `score`=20, `gender`=0 WHERE id=2;
// 删除
DELETE FROM `students` WHERE id=2
//查询
// 查所有数据的所有字段
SELECT * FROM `students` WHERE 1;
//查所有数据的某个字段
SELECT `id`, `name`, `score`, `gender` FROM `students` WHERE 1;
// 条件查询
SELECT * FROM `students` WHERE score>=80;
SELECT * FROM `students` where score>=80 AND gender=1
// 模糊查询
SELECT * FROM `students` where name like '%k%'
//排序
SELECT id, name, gender, score FROM students ORDER BY score;
SELECT id, name, gender, score FROM students ORDER BY score DESC;
// 分页查询
SELECT id, name, gender, score FROM students LIMIT 50 OFFSET 0
// 记录条数
SELECT COUNT(*) FROM students;
SELECT COUNT(*) foolnum FROM students;
// 多表查询
SELECT * FROM students, classes; (这种多表查询又称笛卡尔查询,使用笛卡尔查询时要非常小心,由于结果集是目标表的行数乘积,对两个各有100行记录的表进行笛卡尔查询将返回1万条记录,对两个各有1万行记录的表进行笛卡尔查询将返回1亿条记录)
// 设置别名
SELECT
students.id sid,
students.name,
classes.id cid,
classer.name
FROM students, classes;
//联表查询
SELECT s.id, s.name, s.class_id, c.name class_name, s.gender, s.score
FROM students s
INNER JOIN classes c
ON s.class_id = c.id;
(连接查询对多个表进行JOIN运算,简单地说,就是先确定一个主表作为结果集,然后,把其他表的行有选择性地“连接”在主表结果集上)
LEFT JOIN
RIGHT JOIN
FULL JOIN
使用nodejs 操作数据库
const express = require('express')
const app = express()
const mysql2 = require('mysql2')
const port = 9000
app.get('/', async (req, res)=>{
const config = getDBconfig()
const promisePool = mysql2.createPool(config).promise()
//查
let user = await promisePool.query('select * from students')
let user = await promisePool.query('select * from students where name=? and gender=? order by score desc limit 2 offset 0', [name, 100])
//增
let user = await promisePool.query('insert into students (name, score, gender, class_id) values (?,?,?,?)',['kerwin', 100, 1, 3])
//改
let user = await promisePool.query('update students set name=? ,score=? where id=?', [name,99,9])
//删
let user await promisePool.query('delete from students where id=?', [9])
if(user[0].length){
res.send(user[0])
}else{
res.send({
code: -2,
msg: 'user not exsit'
})
}
})
app.listen(port)
function getDBConfig(){
return {
host: '127.0.0.1',
user: 'root',
port: 3306,
password: '',
database: 'test'
connectionLimit: 1 //创建一个连接池
}
}websocket 通信方式
应用场景:
- 弹幕
- 媒体聊天
- 协同编辑
- 基于位置的应用
- 体育实况更新
- 实时更新


该响应代码 101表示本次连接的HTTP协议即将被更改,更改后的协议就是upgrade:websocket指定的Websocket协议。 版本号和子协议规定了双方能理解的数据格式,以及是否支持压缩等等。如果仅使用WebSocket的API,就不需要关心这些。 现在,一个WebSocket连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。消息有两种,一种是文本,一种是二进制数据。通常,我们可以发送ISON格式的文本,这样,在浏览器处理起来就十分容易。 为什么WebSocket连接可以实现全双工通信而HTTP连接不行呢?
实际上HTTP协议是建立在TCP协议之上的,TCP协议本身就实现了全双工通信,但是HTTP协议的请求-应答机制限制了全双工通信。Websocket连接建立以后,其实只是简单规定了一下:接下来,咱们通信就不使用HTTP协议了,直接互相发数据吧 安全的WebSocket连接机制和HTTPS类似。首先,浏览器用wss://xxx创建WebSocket连接时,会先通过HTTPS创建安全的连接,然后,该HTTPS连接升级为WebSocket连接,底层通信走的仍然是安全的SSL/TLS协议。
Ws模块
服务器:
const webSocket = require('ws')
webSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({port: 8080})
wss.on('connection', function connection(ws, req){ //connection的另一个参数就是req请求体
ws.on('message', function message(data){
wss.clients.forEach(function each(client){
if(client !== ws && client.readyState === WebSocket.OPEN){
client.send(data, {binary: isBinary})
}
})
})
ws.send('欢迎加入聊天室')
})客户端:
const ws = new WebSocket('ws://localhost:8080')
ws.onopen = ()=>{
}
ws.onmessage = ()=>{
}
ws.onerror = ()=>{
}加入JWT鉴权

ws.user = payload //存储用户信息
const types = {
Error: 0,
GroupList: 1,
GroupChat: 2,
SingleChat: 3
}
case: WebSocketType.SingleChat:
if(client.user.username === msgObj.to){
}
function createMessage(type, user, data, to){
return JSON.stringify({
type,
user,
data,
to
})
}
ws.on('close', ()=>{
wss.clients.delete(es.user)
sendALl()
})
function sendAll(){
wss.clients.forEach(function each(client){
if(client.readyState === WebSocket.OPEN){
ws.send(createMessage(WebSocket.GroupList, null, JSON.stringify(Array.from(wss.clients).map(item=>item.user)))) //set结构作对应的处理
}
})
}
//前端对于ws通信
switch(msgObj.type){
case WebSocketType.Error:
localStorage.removeItem('token')
location.href = '/login'
break;
case WebSocketType.GroupList:
console.log(JSON.parse(msgObj.data))
const onlineList = JSON.parse(msgObj.data)
select.innerHTML = ''
select.innerHTML = '<option value='all'>all</option>'
+
onlineList.map(item=>`<option value=${item.username}>${item.username}</option>`).join('')
break;
case WebSocketType.GroupChat:
var title = msgObj.user ? msgObj.user.username : '广播'
console.log(title + ' : ' + msgObj.data)
break;
case WebSocketType.SingleChat:
break;
}Socket.io模块
socket.on(WebSocketType.SingleChat, (msgObj)=>{
Array.from(io.sockets.sockets).forEach(item=>{
if(item[1].user.username === msgObj.to){
item[1].emit(WebSocketType.SingleChat, createMessage(socket.user, msgObj.data))
}
})
})
function sendAll(io){
io.sockets.emit(WebSocketType.GroupList.createMessage(null, Array.from(io.sockets.sockets).map(item=>item[1].user).filter(item=>item)))
}
socket.on('disconnect', ()=>{
})单元测试
