为期一个半月的学习成果,记录的一些笔记
Day1
Node.js是什么
1.Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
node.js是一个js运行环境
node.js可以解释和执行js代码
浏览器中的JavaScript分为3部分:
1)EcmaScript(基本的语法,如if、var、function等)
2)BOM
3)DOM
node.js中的JavaScript:
1)EcmaScript
2)为JavaScript提供了一些服务器级别的操作API(文件读写、网络通信、http服务器等)
2.Node.js uses an event-driven,non-blocking I/O model that makes it lightweight and efficient
关键字:事件驱动,NIO,轻量高效
3.Node.js’s package ecosystem,npm,is the largest ecosystem of open source libraries in the world
拥有包的生态系统,npm,类似于java后台中使用的maven
Node.js能做什么
- Web服务器后台
- 命令行工具
Node.js读写文件的操作
var fs = require('fs');
fs.readFile('文件路径',function(error,data){
//error是错误对象,data是读取到的文件的内容
});
fs.writeFile('要写文件到哪个路径','要写的内容',function(error){
//error是错误对象
});
node.js简单的http服务
var http = require('http');
var server = http.createServer();
server.on('request',funciton(request,response){
});
serve.listen(端口号,function(){
//绑定端口号之后的回调
})
引用自定义的js文件模块
现有a.js文件与b.js文件在同一目录下,需要在a中引用b,则在a中写:
var b = require('./b.js');
注意一定要带上路径定位符号./
不然会会将b.js识别成系统模块而报错;末尾的.js可以省略
需要在a中使用b中的数据,需要将b中的数据使用一个名为exports的参数进行挂载:
在b中:exports.param='hello'
;
在a中:使用var b = require('./b.js');
引入b.js
使用b.param
则可以得到hello
同理还可以在b中挂载方法到exports上:
exports.add = function(a,b){ return a+b; }
获取请求方的IP地址和端口号
- 引入http模块,在server.on的回调中使用request参数获取请求方的IP地址和端口号
- console.log(“请求方的地址是:”+req.socket.remoteAddress+“端口号是:”+req.socket.remotePort);
Content-Type
为了解决响应中文乱码的问题,课程中简单介绍了一下content-type
输出之前设置字符集编码utf-8
res.setHeader('Content-type','text/plain;charset=utf-8');
关于content-type的详细类型可参考http://www.runoob.com/http/http-content-type.html
示例:使用'text/plain'
的类型,res.end('<p>hello node</p>')
,会在页面上输出<p>hello node</p>
使用'text/html'
的类型,res.end('<p>hello node</p>')
,会在页面上输出hello node
(不设置content-type
时,浏览器按照text/html对输出进行解析
Day2
Js代码中的分号问题
js代码中的分号一般都可以省略,除了以下情况:
当一行代码是
以[开头的、
以(开头的、
以`开头的
使用省略分号的书写风格时,建议在上述三种情况下,代码开头加一个分号,示例:
;['苹果','橘子','香蕉'].forEach(function(item){console.log(item)})<br>
初步实现apache的功能
实现将一个服务端文件夹中的所有文件的文件名,以表格的形式展示在页面上
由一个node.js文件和一个html文件实现,两个文件放在同一文件夹下,html页面放一个占位符号,node读取html中的页面字符串、文件夹中的文件名,使用文件名数组拼接成html代码替换占位符,输出合成处理后的页面:
node.js文件
var fs = require('fs')
var http = require('http')
var server = http.createServer();
server.on('request',function(req,res){
//文件夹路径
var basePath = 'D:/workspace/nodetest'
var files = []
var mainHtml = ''
var contentStr = ''
fs.readdir(basePath,function(err,data){
if(err){
console.log('文件夹不存在')
}else{
files = data
files.forEach(function(item){
//循环拼接表格中的html代码
contentStr += '<tr><td><a href="#">'+item+'</a></td></tr>'
})
fs.readFile('./demo.html',function(err,data){
mainHtml = data.toString()
res.setHeader('Content-Type','text/html;charset=utf-8')
mainHtml = mainHtml.replace('{{}}',contentStr)
res.end(mainHtml)
})
}
})
})
server.listen('3000',function(){
console.log('服务器在3000端口启动了')
})
用来做页面模板的demo.html文件
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<h1>Demo</h1>
<table>{{}}</table>
</body>
</html>
`的使用(键盘上esc下方那个按键)
`是ECMAScript6中的语法,与’的区别
var res = '123
456
789
'
console.log(res)
结果输出123456789
var res = `123
456
789`
console.log(res)
结果输出`123
456
789`
另,``中可以直接渲染模板,如:
var content = ''
;['苹果','橘子','香蕉'].forEach(function(item){
content += `
<a>${item}</a>
`
})
console.log(content)
结果为:
<a>苹果</a>
<a>橘子</a>
<a>香蕉</a>
模板引擎art-template在html中的用法
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<!--引入art-template的相关js文件 -->
<script src="node_modules/art-template/lib/template-web.js"></script>
<script type="text/template" id="tp1">
hello {{name}}
</script>
<script>
var para = template('tp1',{
name:'Jack'
})
console.log(para)
</script>
</body>
</html>
在node中使用模板引擎art-template
- 安装:npm install art-template
- 在需要使用的文档中加载art-template:
require(‘art-template’) - 使用render函数进行渲染
实例:
var template = require('art-template')
var ret = template.render('hello{{name}}',{
name:'Jack Ma'
})
var ret1 = template.render('hello{{each files}}文件有:{{$value}}{{/each}}',{
files:['Jack Ma','Chen','Wang']
})
console.log(ret) //helloJack Ma
console.log(ret1) //hello文件有:Jack Ma文件有:Chen文件有:Wang
服务端渲染和客户端渲染的区别
服务端渲染(同步,在后台使用模板引擎):可以被爬虫抓取到,可以进行SEO
客户端渲染(异步):难以被爬虫抓取到;不利于SEO搜索引擎优化
如:京东的商品列表,为了SEO采用的服务端渲染
京东的评论列表为了用户体验,而不需要SEO,采用的客户端渲染
处理网站中的静态资源
html页面引入静态资源,如:
<link rel="stylesheet" href="/public/css/main.css" >`
<script type="text/javascript" src="/public/js/main.js">
href和src中直接写文件的相对路径如./css/main.css
是访问不到的
正确的写法示例:
node.js文件:
var http = require('http')
var fs = require('fs')
http
.createServer(function(req,res){
var url = req.url
if(url === '/'){
fs.readFile('./views/index.html',function(err,data){
if(err){
res.end('404 Not Found')
}else{
res.setHeader('Content-Type','text/html;charset=utf-8')
res.end(data)
}
})
}else if(url.indexOf('/public/')===0||url.indexOf('/views/')===0){
fs.readFile('.'+url,function(err,data){
if(err){
res.end('404 Not Found')
}else{
res.end(data)
}
})
}
})
.listen('3000',function(){
console.log('running');
})
主要关注
else if
这块的写法,每个静态资源的加载都会重新向服务器发送一次新的请求,对每一次请求都要进行处理,示例中的写法将项目中的public
文件夹和views
文件夹中的文件变成可被请求访问到的
在html页面中写src
或href
写成/public/css/main.css
(以/public
开头),就可以了
注意/
不可省略,在这里是url根路径的意思
url.parse的用法
了解此方法,有助于进行请求的处理
var url = require('url')
var parseObj = url.parse('127.0.0.1:8888/index?name=张三&gender=male')
//parseObj的值为
{
protocol: '127.0.0.1:',
slashes: null,
auth: null,
host: '8888',
port: null,
hostname: '8888',
hash: null,
search: '?name=张三&gender=male',
query: 'name=张三&gender=male',
pathname: '/index',
path: '/index?name=张三&gender=male',
href: '127.0.0.1:8888/index?name=张三&gender=male'
}
var url = require('url',true)
var parseObj = url.parse('127.0.0.1:8888/index?name=张三&gender=male')
//parseObj的值为
{
protocol: '127.0.0.1:',
slashes: null,
auth: null,
host: '8888',
port: null,
hostname: '8888',
hash: null,
search: '?name=张三&gender=male',
query: { name: '张三', gender: 'male' },
pathname: '/index',
path: '/index?name=张三&gender=male',
href: '127.0.0.1:8888/index?name=张三&gender=male'
}
该方法加上第二个参数true时,返回值中的query会被解析成json格式
url.parse
得到parseObj
对象,parseObj.pathname
为请求路径,parseObj.query
为url中带的参数
如何通过服务器让客户端重定向
- 状态码设置为 302 临时重定向
res.statusCode=302 - 在响应头中通过Location告诉客户端往哪重定向
res.setHeader(‘Location’,‘要重定向的路径’)
如果客户端收到的响应状态码是302,就会自动去响应头中找Location,然后对该地址发起新的请求
Day3
各种each
1. art-template中的each
{{each 数组}}
{{$value}}
{{each}}
2. jQuery中的each
$.each(数组,function(index,item){})
$('div').each(function)
一般用于jQuery选择器选择到的伪数组实例对象
3. forEach
array.forEach(function(item,index){})
是js原生的遍历方法,是EcmaScript5中的一个数组遍历函数,不支持ie8
301状态码和302状态码
- 301是永久重定向:客户端访问a,被重定向到b,之后再请求访问,不会再经过a,而是直接访问b(浏览器缓存)
- 302是临时重定向:客户端访问a,被重定向到b,之后再请求访问,还是先经过a,再被重定向到b
CommonJS模块规范
模块引用、模块定义和模块标识三部分
在Node中的JavaScript还有一个很重要的概念:模块系统
- 模块作用域
- 使用require方法来导入模块
- 使用exports接口对象来导出模块中的成员
导出多个成员
exports.a=‘hello’
exports.b=function(){}
导出单个成员
module.exports=‘hello’
exports原理解析
b.js文件被导入
- 默认有一个module对象:
module=
{
exports:{}
}
-
require(’./b.js’)默认返回值是module.exports对象
-
默认
exports=module.exports
,所以exports.a
与module.exports.a
等效
因此exports.a='123’会导致:
exports={a:'123'}
module={
exports:{
a:'123'
}
}
但是如果直接使用exports='123'
就会改变exports
的指向(直接对module.exports
赋值也同样会破坏两者之间的等效的关系),不再与module.exports
指向同一个地址,再对exports
进行赋值操作都不会影响返回结果
模块加载规则
- 优先从缓存中加载
如果main.js中
requrie('./a.js')
requrie('./b.js')
a.js中
console.log('a.js被加载了')
requrie('./b.js')
b.js中
console.log('b.js被加载了')
那么运行main.js,结果为
a.js被加载了
b.js被加载了
b.js只会被加载一次main.js中对b.js的引用是直接从缓存中去读取
2. 自定义模块
以路径的形式引用
3. 核心模块
被编译成了二进制文件放在了lib中,直接require(‘包名’)进行引用
4. 第三方模块
也是使用require(‘包名’)的形式进行引用
加载过程:
1)先找到当前文件所处目录中的node_modules目录
2)node_modules/第三方模块名/package.json文件
3)node_modules/第三方模块名/package.json文件中的main属性
4)main属性记录了该模块的入口模块
5)最终加载到模块(如果main中的属性值为空,则默认去加载package.json同级的index.js文件)
如果按照上述步骤没有加载到,则去上一级目录下的node_modules目录中去查找;若上级也没有找到,则去上上级目录下的node_modules目录去查找,如此循环,直至当前磁盘根目录
package.json
包描述文件
- 使用
npm init
指令,并按照指令提示输入对应参数后可以在当前目录下生成一个package.json文件,该文件记录了一些项目的基本信息:如项目名、版本号、项目描述、项目入口等
- 在使用npm指令安装第三方包时,加上在包名前/后加上
--save
,在下载第三方包的同时会将该包的信息添加到package.json
的dependencies
中 - 使用
npm install
会根据package.json
中的dependencies
中的信息下载对应的依赖包
npm常用指令
npm install
安装所有package.json中dependencies下的包
npm install 包名
只下载,不在dependencies中添加依赖
npm install 包名 -save
下载并保存依赖信息
npm uninstall 包名
只删除包的物理文件,不在dependencies中删除依赖
npm uninstall 包名 --save
删除包的物理文件,同时删除dependencies中的依赖
npm config list
查看npm配置信息
使用淘宝镜像
1.安装淘宝镜像
npm install --global cnpm
--global表示安装到全局而非当前目录
2.使用淘宝镜像
安装之后使用cnpm install 包名
下载包
3.若想免安装直接使用,可以每次下载时使用:
npm install 包名-register=https://register.npm.taobao.org
或者通过
npm config set register https://register.npm.taobao.org
修改register的地址,以后直接使用npm默认就是使用淘宝镜像了
文件操作路径和模块标识路径问题
- 文件操作时的相对路径标识
./
可以省略 - 加载模块时,相对路径中的
./
标识不能省略
Day4
Express
express的安装
npm install express
express的helloworld
var express = require('express')
var app = express()
app.get('/',function(req,res){
res.send('hello express')
})
app.listen('3000',function(){
console.log('app在300端口启动了')
})
express开放静态资源访问
var express = require('express')
var app = express()
app.use('/public/',express.static('./public/'))
//public下的文件可以被直接访问到了
//use方法的第一个参数可以省略,省略时效果与第一个参数为'/'效果相同
修改完代码自动重启
使用第三方工具nodemon
- 安装npm install --global nodemon
- 使用
nodemon 文件名
启动
在Express中使用art-template
- 安装
npm install express-art-template - 引用
var express = requires(‘express’)
var app = express()
app.engine(‘html’,require(‘express-art-template’))
该方法的第一个参数表示要渲染的页面的后缀 - 渲染
app.render(‘index.html’,{name:‘jack’,gender:‘male’})
express-art-template会默认去渲染views目录下的文件,render方法的第一个参数为views下的文件名(可以通过app.set('views',指定其他路径)
来修改)
使用post进行表单提交
需要使用第三方插件body-parser
1.安装npm install body-parser
官方示例
var express = require('express')
var bodyParser = require('body-parser')
var app = express()
// create application/json parser
var jsonParser = bodyParser.json()
// create application/x-www-form-urlencoded parser
var urlencodedParser = bodyParser.urlencoded({ extended: false })
// POST /login gets urlencoded bodies
app.post('/login', urlencodedParser, function (req, res) {
if (!req.body) return res.sendStatus(400)
res.send('welcome, ' + req.body.username)
})
// POST /api/users gets JSON bodies
app.post('/api/users', jsonParser, function (req, res) {
if (!req.body) return res.sendStatus(400)
// create user in req.body
})
使用了插件之后,req.body和使用get提交时的req.query是相同的效果
提取路由模块
项目中一般专门使用一个router.js文件来作为路由模块
项目启动的入口还是app.js:
var express = require('express')
var app = express()
var bodyParser = require('body-parser')
var router = require('./router')
app.use('/node_modules',express.static('./node_modules/'))
app.use('/public',express.static('./public/'))
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.engine('html',require('express-art-template'))
//将router挂载到app上
app.use(router)
app.listen('3000',function(){
console.log('running 3000')
})
路由router.js
var express = require('express')
//创建一个路由容器
var router = express.Router()
//把路由都挂载到路由容器中
router
.get('/student',function(req,res){
})
.get('/student/create',function(req,res){
})
.post('/student/create',function(req,res){
})
.get('/student/update',function(req,res){
})
.post('/student/update',function(req,res){
})
.get('/student/delete',function(req,res){
})
module.exports = router
node中使用回调函数
需求是:先执行异步函数fn1,再执行fn2,fn1的返回值作为fn2的参数。由于fn1是异步的,使用顺序逻辑就不行,因此引入回调函数。
fn2是回调函数,作为参数传入fn1方法中,fn1中部分方法的执行结果作为参数再传到fn2中
node.js中大多数操作如文件读写,都是异步操作的,如
var fs = require('fs')
var fn1 = function(){
fs.readFile(path,function(err,data){
var para = data
})
return para
}
console.log(fn1())
使用顺序逻辑的写法,打印的结果是undefined
想要正确的获取文件读取结果data的数据,就需要使用回调函数
var fs = require('fs')
var fn1 = function(callback){
fs.readFile(path,function(err,data){
callback(data)
})
}
var fn2 = callback(data){
console.log(data)
}
这样就能正确的获取到文件中的内容了
Array.find
var para = [
{'id':1,'name':'jack'},
{'id':2,'name':'mary'},
{'id':3,'name':'jerry'}
]
var p = para.find(function(item){
return item.id===2
})
console.log(p)
//{'id':2,'name':'mary'}
Array.findIndex
var para = [
{'id':1,'name':'jack'},
{'id':3,'name':'mary'},
{'id':5,'name':'jerry'}
]
var p = para.findIndex(function(item){
return item.id===5
})
console.log(p)
//2
Array.splice
var para = ['1','3','5']
para.splice(1,2)
console.log(para)
//['1','3']
Day5
package-lock.json文件的作用
保存了node_modules中所有包的信息
可以提升npm install的速度
可以用来锁定版本,重新nmp install时,如果没有package-lock.json文件,package.json中的依赖会被升级
404页面的改写
app.use(function(res,req){
})
MongoDB
- 安装
- 添加环境变量
在环境变量的path中添加mongoDB安装后的bin目录路径 - 启动
在cmd窗口输入mongod
指令进行启动,注意mongodb会将输入mongod指令的目录的根目录下的/data/db作为自己的数据存储目录(如在C:/abc/def下输入mongod,会将C:/data/db作为存储目录) - 连接mongodb
再打开一个窗口输入mongo
- 常用指令
层次结构:数据库->集合(类似表名)->文档(数据)
show dbs
显示所有数据库
use 数据库名
切换数据库
db
查看当前使用的数据库名称
db.集合名.insertOne(data)
插入数据
show collections
查看当前数据库下所有集合名
db.集合名.find()
查看当前集合下所有数据
在node中操纵MongoDB
使用第三方包mongoose
增删改查的demo
var mongoose = require('mongoose')
var Schema = mongoose.Schema
//1.连接数据库
mongoose.connect('mongodb://localhost/myitem');
//2.设计集合结构(表结构)
var userSchema = new Schema({
userName:{
type:String,
required:true
},
password:{
type:String,
required:true
},
email:{
type:String
}
})
//3.将文档结构发布为模型
//这里model方法的第一个参数要写大写单数形式的单词,mongoose会自动将其转成小写复数,作为集合名称
var User = mongoose.model('User',userSchema)
//4.进行增删改查的操作
//新增数据
var item1 = new User({
userName:'admin',
password:'123456',
email:'admin@admin.com'
})
item1.save(function(err,data){
if(err){
console.log(err)
}else{
console.log('添加成功')
console.log(data)
}
})
//查询数据
//查询所有
User.find(function(err,data){
if(err){
console.log(err)
}else{
console.log(data)
}
})
//按条件查询,第一个参数为查询条件,返回结果为满足条件的数组
User.find({
userName:'admin'
},function(err,data){
if(err){
console.log(err)
}else{
console.log(data)
}
})
//查询一条,返回结果为满足条件的第一条数据
User.findOne({
userName:'admin'
},function(err,data){
if(err){
console.log(err)
}else{
console.log(data)
}
})
//删除,第一个参数为删除条件
User.remove({
userName:'admin'
},function(err,data){
if(err){
console.log(err)
}else{
console.log('删除成功')
}
})
//更新数据
User.findByIdAndUpdate('id',{userName:'admin'},function(err,data){
if(err){
console.log(err)
}else{
console.log('删除成功')
}
})
使用Node操纵MySQL
安装npm install mysql
官网demo
var mysql = require('mysql');
//创建数据库连接
var connection = mysql.createConnection({
host : 'localhost',
user : 'me',
password : 'secret',
database : 'my_db'
});
//连接数据库
connection.connect();
//进行操作
connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results[0].solution);
});
//关闭连接
connection.end();
Promise
异步编程的传统写法
按顺序读取path1,path2,path3路径下的文件内容
fs.readFile('path1',function(err,data){
console.log(data)
fs.readFile('path2',function(err,data){
console.log(data)
fs.readFile('path3',function(err,data){
console.log(data)
})
})
})
为了异步编程中回调嵌套过多的问题,ES6中新增了一个api:Promise
Promise容器,开始状态为pending,执行成功之后变为resolved,执行失败之后变成rejected
pending->resolved
->rejected
使用了promise
之后的写法,将异步方法放到promise
容器中,使用then
方法进行调用。
创建容器时的resolve
方法就是调用时then
方法参数中的第一个函数,reject
方法就是调用时then
方法参数中的第二个函数。
如果第一个then
函数的返回值是一个promise
容器,则第二个then
函数的第一个参数是返回的promise
容器中的resolve
方法,第二个参数是返回的promise
容器中的reject
方法
var fs = require('fs')
var p1 = new Promise(function(resolve,reject){
fs.readFile('./a.js','utf8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
var p2 = new Promise(function(resolve,reject){
fs.readFile('./b.js','utf8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
var p3 = new Promise(function(resolve,reject){
fs.readFile('./c.js','utf8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
p1
.then(function(data){
console.log(data)
return p2
},function(err){
console.log(err)
})
.then(function(data){
console.log(data)
return p3
},function(err){
console.log(err)
})
.then(function(data){
console.log(data)
})
Promise封装readFile的api
var fs = require('fs')
function pReadFile(filePath){
return new Promise(function(resolve,reject){
fs.readFile(filePath,'utf8',function(err,data){
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
pReadFile('./a.js')
.then(function(data){
console.log(data)
return pReadFile('./b.js')
})
.then(function(data){
console.log(data)
return pReadFile('./c.js')
})
.then(function(data){
console.log(data)
})
Promise操纵数据库
完成一个业务场景:在数据库中按条件查询一条记录,若查询到了,则打印"用户已存在",若为查询到,则向数据库中插入一条记录
var mongoose = require('mongoose')
var Schema = mongoose.Schema
mongoose.connect('mongodb://localhost/myitem');
var userSchema = new Schema({
userName:{
type:String,
required:true
},
password:{
type:String,
required:true
},
email:{
type:String
}
})
var User = mongoose.model('User',userSchema)
var item1 = new User({
userName:'admin',
password:'123456',
email:'admin@admin.com'
})
//
User.findOne({userName:'admin'})
.then(function(user){
if(user){
console.log('用户已存在')
}else{
return item1.save();
}
})
.then(function(ret){
if(ret){
console.log('保存成功')
console.log(ret)
}
})
Day6
Node中的非模块成员
除了require、exports等模块相关的api,还有两个特殊成员
__dirname
动态获取当前文件模块所属目录的绝对路径__filename
动态获取当前文件的绝对路径
如在D:\workspace\nodetest有app.js文件
console.log(__dirname)
console.log(__filename)
// D:\workspace\nodetest
// D:\workspace\nodetest\app.js
Node中文件操作时的路径问题
现在在D:\workspace路径下有app.js和index.html两个文件
app.js中的内容为
var fs = require('fs')
fs.readFile('./index.html',function(err,data){
if(err){
throw err
}else{
console.log(data)
}
})
在D:\workspace路径下打开dos窗口,输入node app.js,可以正确读取文件内容
但是如果在D盘根目录打开dos窗口,输入node workspace\app.js,执行app.js文件,会在读取文件时因为路径错误进入if(err)的逻辑中
因为相对路径使用的./并不是指当前执行文件所处目录
而是执行node命令所处的终端路径
此时使用__dirname
可以解决这个问题,写成
var fs = require('fs')
var path = require('path')
fs.readFile(path.join(__dirname,'./index.html'),function(err,data){
if(err){
throw err
}else{
console.log(data)
}
})
path.join用来辅助路径拼接
art-template中的include和extend
express中的session
使用第三方包express-session
- 安装
npm install express-session - 配置(需要在挂载路由之前)
app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: true })) //secret配置加密字符串,它会在原有加密基础上和这个字符串拼接起来去加密 //saveUninitialized为true时,未使用session也会被分配
- 使用
当把这个插件配置好之后,我们就可以通过req.session来访问和设置session成员了
添加session数据:req.session.foo = ‘bar’
访问session数据:req.session.foo
默认session数据是内存存储的,服务器一旦重启就会丢失,真正的生产环境中会将session持久化
登录把用户user信息放在req.session中,登出时直接把session中的user置为null