也是用来 解决 http 无状态协议的问题(无法区分多次请求是否发送自同一客户端)
npm install express-session
npm install connect-mongo
- 基本使用
-
const session = require('express-session'); const MongoStore = require('connect-mongo')(session);
-
app.use(express.session({ secret: 'keyboard cat', // 加密字符串,参与 sessionid 加密 saveUninitialized: false, // 在存储某东东之前,不会创建 session 对象 resave: false, // 如果没有修改 session 对象,就不会重新保存 store: new MongoStore({ url: 'mongodb://localhost: 27017/test-app', // 连接数据库的地址 touchAfter: 24 * 3600 // 24 小时更新一次 }) })); ... // 设置 session 会在数据库中创建 session 对象 // 保存 userId=findRet.id 到数据库 request.session.userId = findRet.id; ... // 解析 cookie 中的 session 去数据库中找对应 sessionId 的数据 // 返回一个 cookie const {userId} = request.session; ...
- session 优势
读写二合一
存储数据近乎无限大,取决于 服务器 的存储容量
传输流量小(数据传输过程中 cookie 更小更少)
- 私有变量 _name
- 一定要在最开始 激活 session
app.use(session({... ...});
登录/注册 实例
package.json
-
{ "name": "node_express", "version": "1.0.0", "main": "index.js", "license": "MIT", "dependencies": { "connect-mongo": "^2.0.3", "cookie-parser": "^1.4.3", "ejs": "^2.6.1", "express": "^4.16.4", "express-session": "^1.15.6", "mongoose": "^5.4.0", "sha1": "^1.1.1" } }
public/login.html
-
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>用户登录</title> <link rel="stylesheet" type="text/css" href="css/index.css"/> </head> <body> <div id="outer_box" class="login"> <h2>用户登录</h2> <form action="http://localhost:3000/login" method="post"> <div class="clothes"> <label for="input_name">用 户 名</label> <input id="input_name" type="text" name="user_name" placeholder="请输入用户名" /> </div> <div class="clothes"> <label for="input_pwd">密 码</label> <input id="input_pwd" type="password" name="user_pwd" placeholder="请输入密码" /> </div> <div class="clothes"> <a class="btn" href="http://localhost:3000/register"> <button type="button">注册</button> </a> <button class="login btn" type="submit">登录</button> </div> </form> </div> </body> </html>
public/register.html
-
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>用户注册</title> <link rel="stylesheet" type="text/css" href="css/index.css"/> </head> <body> <div id="outer_box" class="register"> <h2>用户注册</h2> <form action="http://localhost:3000/register" method="post"> <div class="clothes"> <label for="input_name">用 户 名</label> <input id="input_name" type="text" name="user_name" placeholder="请输入用户名" /> </div> <div class="clothes"> <label for="input_pwd">密 码</label> <input id="input_pwd" type="password" name="user_pwd" placeholder="请输入密码" /> </div> <div class="clothes"> <label for="input_repeat_pwd">确认密码</label> <input id="input_repeat_pwd" type="password" name="user_repeat_pwd" placeholder="请再次输入密码" /> </div> <div class="clothes"> <label for="input_email">注册邮箱</label> <input id="input_email" type="text" name="user_email" placeholder="请输入邮箱地址" /> </div> <div class="clothes"> <button class="register btn" type="submit">注册</button> <a class="btn" href="http://localhost:3000/login"> <button type="button">登录</button> </a> </div> </form> </div> </body> </html>
public/user_center.html
-
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>用户中心</title> <link rel="stylesheet" type="text/css" href="css/index.css"/> </head> <body> <div id="outer_box" class="login"> <h2>个人空间</h2> </div> </body> </html>
public/index.css
-
body { 100%; height: 100%; color: #000; background: #b9c2a4; background-size: cover; /* 指定背景图片大小 */ } /*************************************************/ #outer_box { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #1a45c3; } #outer_box.login { color: #9e098b; } #outer_box.register { color: #1a45c3; } #outer_box>h2{ padding-bottom: 40px; margin-left: -50px; } .clothes { position: relative; 260px; display: flex; justify-content: space-between; margin: 20px 0; font-size: 18px; line-height: 32px; } .tips { position: absolute; top: 0; right: -100%; margin-left: -50%; margin-top: 2px; 252px; height: 32px; text-align: center; color: #f00; } .clothes>label{ 80px; text-align: center; } .clothes>input{ 170px; height: 32px; } button { 100%; height: 100%; font-size: 16px; background-color: #c4ceda; cursor: pointer; } .clothes .btn{ 64px; height: 32px; margin: 0 20px; } .clothes button.register{ background-color: #1a45c3; color: #fff; } .clothes button.login{ background-color: #9e098b; color: #fff; } #outer_box>h2 { position: relative; } #server_info { display: block; 139px; height: 20px; border-radius: 10px; position: absolute; top: 0; right: 0; color: red; font-size: 14px; text-align: center; line-height: 20px; background-color: #cecece; }
views/login.ejs
-
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>用户登录</title> <link rel="stylesheet" type="text/css" href="css/index.css"/> </head> <body> <div id="outer_box" class="login"> <h2> 用户登录 <div id="server_info"> <%= serverInfo.tips %> </div> </h2> <form action="http://localhost:3000/login" method="post"> <div class="clothes"> <label for="input_name">用 户 名</label> <input id="input_name" type="text" name="user_name" value="<%= serverInfo.uName %>" placeholder="请输入用户名" /> </div> <div class="clothes"> <label for="input_pwd">密 码</label> <input id="input_pwd" type="password" name="user_pwd" placeholder="请输入密码" /> </div> <div class="clothes"> <a class="btn" href="http://localhost:3000/register"> <button type="button">注册</button> </a> <button class="login btn" type="submit">登录</button> </div> </form> </div> </body> </html>
views/register.ejs
-
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>用户注册</title> <link rel="stylesheet" type="text/css" href="css/index.css"/> </head> <body> <div id="outer_box" class="register"> <h2>用户注册</h2> <form action="http://localhost:3000/register" method="post"> <div class="clothes"> <label for="input_name">用 户 名</label> <input id="input_name" type="text" name="user_name" value="<%= serverInfo.uName %>" placeholder="请输入用户名" /> <div class="tips"><%= serverInfo.nameErr %></div> </div> <div class="clothes"> <label for="input_pwd">密 码</label> <input id="input_pwd" type="password" name="user_pwd" placeholder="请输入密码" /> <div class="tips"><%= serverInfo.passwordErr %></div> </div> <div class="clothes"> <label for="input_repeat_pwd">确认密码</label> <input id="input_repeat_pwd" type="password" name="user_repeat_pwd" placeholder="请再次输入密码" /> <div class="tips"><%= serverInfo.repeatErr %></div> </div> <div class="clothes"> <label for="input_email">注册邮箱</label> <input id="input_email" type="text" name="user_email" value="<%= serverInfo.uEmail %>" placeholder="请输入邮箱地址" /> <div class="tips"><%= serverInfo.emailErr %></div> </div> <div class="clothes"> <button class="register btn" type="submit">注册</button> <a class="btn" href="http://localhost:3000/login"> <button type="button">登录</button> </a> </div> </form> </div> </body> </html>
views/user_center.ejs
-
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>用户中心</title> <link rel="stylesheet" type="text/css" href="css/index.css"/> </head> <body> <div id="outer_box" class="login"> <h2><%= serverInfo.uName%> - 个人空间</h2> </div> </body> </html>
db/connectDB.js
-
const mongoose = require('mongoose'); module.exports = new Promise((resolve, reject)=>{ mongoose.connect('mongodb://localhost:27017/user_database', {useNewUrlParser:true}) mongoose.connection.once('open', err=>{ if(err){ console.log(err); reject(err); }else{ resolve('数据库已连接'); }; }); });
models/userModel.js
-
const mongoose = require('mongoose'); const Schema = mongoose.Schema; const fieldSchema = new Schema({ "userName": { "type": String, "unique": true, "required": true }, "userPassword": { "type": String, "required": true }, "userEmail": { "type": String, "unique": true, "required": true }, "createTime": { "type": Date, "default": Date.now() } }); module.exports = mongoose.model("user_info", fieldSchema);
routers/get/index_router.js
-
const express = require('express'); const promiseConnect = require('../../db/connectDB.js'); const userInfoModel = require('../../models/userModel.js'); const {resolve} = require('path'); const cookieParser = require('cookie-parser'); const indexRouter = new express.Router(); indexRouter.use(cookieParser()); /************************ get ***********************/ indexRouter.get('/', (request, response)=>{ response.sendFile(resolve(__dirname, '../../public/login.html')); }); indexRouter.get('/login', (request, response)=>{ response.sendFile(resolve(__dirname, '../../public/login.html')); }); indexRouter.get('/register', (request, response)=>{ response.sendFile(resolve(__dirname, '../../public/register.html')); }); promiseConnect.then(result=>{ console.log("index_router.js - "+result); indexRouter.get('/user_center',async (request, response)=>{ // s4: 解析 cookie 中的 session 去数据库中找对应 sessionId 的数据 const {userId} = request.session; // 返回一个 Cookie 对象 if(userId){ // 用户已经登录 const findRet = await userInfoModel.findOne({"_id":userId}); if(findRet){ let serverInfo = {uName:findRet.userName}; response.render('user_center.ejs', {serverInfo}); }else{ // 恶意 Cookie response.clearCookie("userId"); response.redirect('/login'); }; }else{ // 用户未登录 response.redirect('/login'); }; }); }).catch(err=>console.log(err)); module.exports = indexRouter;
routers/post/form_router.js
-
const express = require('express'); const sha1 = require('sha1'); const promiseConnect = require('../../db/connectDB.js'); const userInfoModel = require('../../models/userModel.js'); const formRouter = new express.Router(); /************************ post ***********************/ let logged = false ; promiseConnect.then(result=>{ console.log("form_router.js - "+result); formRouter.post('/register', async (request, response)=>{ const { user_name:uName, user_pwd:uPwd, user_repeat_pwd:urePwd, user_email:uEmail, } = request.body; /**** 解构赋值 ****/ userInfo = { "userName": uName, "userPassword": uPwd, "userEmail": uEmail }; let serverInfo = {uName, uEmail}; if(urePwd !== uPwd){ serverInfo.repeatErr = '密码两次输入不一致'; }; if(!(/^[a-zA-Z][a-zA-Z0-9_]{5,20}$/.test(uName))){ serverInfo.nameErr = '用户名不合法'; }; if(!(/^[a-zA-Z0-9_]{6,20}$/.test(uPwd))){ serverInfo.passwordErr = '密码不合法'; }; if(!(/^w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*$/.test(uEmail))){ serverInfo.emailErr = '邮箱不合法'; }; const fond = await userInfoModel.findOne({"userName": uName}); if(fond){ serverInfo.nameErr = '用户名已被注册'; }; const badEmail = await userInfoModel.findOne({"userEmail": uEmail}); if(badEmail){ serverInfo.emailErr = '邮箱已被注册'; }; if(serverInfo.repeatErr || serverInfo.nameErr || serverInfo.passwordErr || serverInfo.emailErr){ response.render('register.ejs', {serverInfo}); // 渲染错误信息 return; }else{ userInfo.userPassword = sha1(userInfo.userPassword); await userInfoModel.create(userInfo); response.redirect('/login'); // 跳转到登录 }; }); formRouter.post('/login',async (request, response)=>{ logged = false; let uName = request.body['user_name']; let uPwd = request.body['user_pwd']; userInfo = { "userName": uName, "userPassword": uPwd }; if(!(/^[a-zA-Z][a-zA-Z0-9_]{5,20}$/.test(uName))){ logged = false; }else if(!(/^[a-zA-Z0-9_]{6,20}$/.test(uPwd))){ logged = false; }; try{ const findName = await userInfoModel.findOne({"userName": uName}); if(findName && (findName.userPassword===sha1(uPwd)) ){ logged = true; }; let serverInfo = {uName}; if(logged){ // s3: 设置 session 会在数据库中创建 session 对象 // 保存 userId=findRet.id 到数据库 request.session.userId = findName.id; response.redirect('/user_center'); // 跳转到用户 个人空间 页面 }else{ serverInfo.tips='用户名或密码错误'; response.render('login.ejs', {serverInfo}); // 渲染错误信息 }; }catch(err) { console.log(err); } }); }).catch(err=>console.log(err)); module.exports = formRouter;
index.js
-
const express = require('express'); // s1: 导入 session 模块 const session = require('express-session'); const mongoSession = require('connect-mongo')(session); const app = express(); const indexRouter = require('./routers/get/index_router.js'); const formRouter = require('./routers/post/form_router.js'); app.set('views', 'views'); // 1. 配置模板路径 app.set('view engine', 'ejs'); // 2. 配置模板引擎 /*********************** 中间件 **********************/ // s2: 通过中间件,激活 session app.use(session({ secret: 'myMongoSession', saveUninitialized: false, resave: false, store: new mongoSession({ url: 'mongodb://localhost:27017/user_database', touchAfter: 1000*3600*24 }) })); // 暴露路由 login.html register.html app.use(express.static('public')); // 默认调用 next(); // 将 用户输入的数据 挂载到 请求体 request.body 上 app.use(express.urlencoded({extended: true})); // 默认调用 next(); app.use(indexRouter); app.use(formRouter); /**************** 端口号 3000, 启动服务器 ***************/ app.listen(3000, err=>console.log(err?err:' 服务器已启动: http://localhost:3000 Hunting Happy!'));