1 登录注册项目(form请求)
-
项目文件:
- MVC:
- models:创建db.js,用于引入封装的数据库方法;创建md5.js,用于引入加密函数,对密码加密;
- views:创建login.ejs和reg.ejs文件,用于呈现登录和注册页面;
- controllers:管理者,创建router.js,作为管理器;
- mongodb:数据库;
- form-app.js:服务器;
- setting.js:作为db.js中引入,用于设置数据库地址url和数据库名字dbName;
- 下载模块:express ejs mongodb formidable express-session
- package.json:用于记录信息;
- MVC:
-
思路:
- 服务器中设置请求:
- get:"/login" => 用于渲染页面login.ejs;
- post:"/dologin" => 用于提交登录数据;
- get:"/reg" => 用于渲染页面reg.ejs;
- post:"/doreg" => 用于提交注册数据;
- 渲染页面:res.render();
- 获取表单post提交的数据:formidable;
- 对密码进行加密处理;crypto md5;
- 通过db.js中封装的数据库方法;对数据进行查找和插入;
- db.find():用于查找数据;
- db.insertOne():用于插入数据;
- 登录成功和注册成功时,均需要设置session;
- 服务器中设置请求:
-
知识点:
- md5加密
- 模块:node.js中的系统模块crypto;如:
const crypto=require("crypto");
- 创建md5格式的hash;如:
const hash=crypto.createHash("md5");
- 更新和转化数据str;必须为字符串;
- 代码:
var res=hash.update(str).digest("base64")
- res为32为的乱码字符串;
- 可进行重复加密,不易解密;如
password=crypto.md5(crypto.md5(password)+"guomushan");
- 代码:
- md5封装函数代码:
exports.md5=function(str){ //引入node系统模块 const crypto=require("crypto"); //创建hash const hash=crypto.createHash("md5");//创建md5格式; //加密文件 return hash.update(str).digest("base64");//加密成64为的字符串; };
- 模块:node.js中的系统模块crypto;如:
- get请求用来渲染页面,post请求用来提交数据
- 用formidable模块,获取表单post请求提交的参数;
- 使用express模块创建服务器;
- 使用express-session模块设置session;
- 注意:引入中间件;获取和设置均用req;
- 使用ejs模板渲染页面;需要设置模板引擎;
- 数据库方法的封装中需要通过settings设置url和dbName;
- md5加密
-
实例代码
- 服务器form-app.js代码:
//引入node系统模块 //引入第三方模块 const express=require("express"); const session=require("express-session"); //引入自定义模块 const router=require("./controller"); //创建服务器 const app=express(); //设置模板引擎 app.set("view engine","ejs"); //引入session中间件 app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: true, cookie: { secure: false }//设置为false; })); //1.发送请求get:"/login" => 渲染登录页面 app.get("/login",router.showlogin); //1.1 发送请求post:"/dologin" => 提交数据 app.post("/dologin",router.dologin); //2.发送请求get:"/reg" => 渲染注册页面 app.get("/reg",router.showreg); //2.1 发送请求post:"/reg" => 提交数据 app.post("/doreg",router.doreg); //监听 app.listen(8080);
- 管理器router.js代码:
//引入第三方模块 const formidable=require("formidable"); //引入自定义模块 const db=require("../models/db"); const crypto=require("../models/md5"); //导出 //渲染登录页面; exports.showlogin=function (req, res, next) { res.render("login"); }; //提交登录数据 exports.dologin=function (req, res, next){ const form=new formidable.IncomingForm(); form.parse(req,function (err, fields, files) { var username=fields.username; var password=fields.password;//在页面中拿到的数据都是字符串; //密码加密处理 password=crypto.md5(crypto.md5(password)+"guomushan");//双重加密 db.find("users",{username},function (err, result) { if(err){ res.send("服务器错误"); return; } if(result.length){ if(result[0].password===password){ //设置session req.session.login="true"; req.session.username=username; //登录成功 res.send({"bok":true,"msg":"恭喜你,登录成功!!!"}) }else{ res.send({"bok":false,"msg":"密码不正确,请重新输入"}); } }else{ res.send({"bok":false,"msg":"用户名不存在,请注册!!!"}); } }) }) }; //渲染注册页面 exports.showreg=function (req, res, next) { res.render("reg"); }; //提交注册数据 exports.doreg=function (req, res, next) { //通过formidable模块获取数据 const form=new formidable.IncomingForm(); form.parse(req,function (err, fields, files) { if(err){ res.send("获取数据失败"); return; } //拿到数据后需要对密码加密 var username=fields.username; var password=fields.password;//在页面中拿到的数据都是字符串; //密码加密处理 password=crypto.md5(crypto.md5(password)+"guomushan");//双重加密 //连接数据库,查找数据,若无,则插入数据 db.find("users",{username},function (err, result) { if(err){ res.send("服务器错误"); return; } if(result.length){ //长度不为0,则代表数据存在; res.send({"bok":false,"msg":"注册失败,该用户名已被注册!!!"}); return; }else{ //长度为0,就证明用户名不存在,插入数据到数据库中; db.insertOne("users",{username,password},function (err, result) { if(err){ res.send("服务器错误"); return; } //设置session req.session.login="true"; req.session.username=username; //注册成功 res.send({"bok":true,"msg":"注册成功,请登录!!!"}); }) } }) }) };
- 页面login.ejs代码:
登录页面 登录页面
2 登录注册项目(ajax请求)
- 请求方式使用ajax请求配合表单
- ajax请求中主要是需要获取表单中的键值对,通过
$("form").serialize()
;表单序列化拿到{key:val,key:val}
格式的数据;与form的action以及method均无关; - 与form表单的区别:将submit按钮去掉,换成input(button)按钮,给其添加点击事件;然后触发事件,发送ajax请求;
- 注意:不能换成
- 先决条件:必须引入jQuery.js文件,所以需要设置静态资源目录,然后利用相对路径引入js文件;
- ajax请求中主要是需要获取表单中的键值对,通过
- login-ajax.ejs文件
- form表单用于提供键值对数据,通过
$("form").serialize()
表单序列化来拿到键值对参数; - 设置按钮
;添加点击事件,进行ajax请求;
- 引入jQuery.js文件;
- 代码:
登录页面 登录页面
- form表单用于提供键值对数据,通过
- ajax-app.js服务器文件
- 与form-app.js的区别在于,设置静态资源目录,用于引入jQuery.js文件;
- 在渲染页面时,渲染login-ajax.ejs和reg-ajax.ejs文件;
- 静态资源设置代码:
app.use(express.static("./public"))
3 知识点:form表单submit提交按钮发送请求与ajax发送请求
3.1 form表单和ajax发送get请求
- form表单和ajax提交get请求中如何设置数据参数;
- form表单中get请求和post请求,区别就在于method设置不同,其余的设置没有区别,设置参数,都是通过name和value值;
- form表单提交get请求;会默认将参数用问号拼接在url后面,显示在地址栏中;
- ajax发送get请求如何设置数据参数;
- ajax发送get请求时,type属性设置为"get",参数以键值对(key=val&key=val)的形式通过问号拼接在域名地址后面;设置url属性中;
- 代码:
//点击span发送post请求; $("span").click(function () { $.ajax({ url:"/test?meiaho=djfj&tiankong=djid", type:"get", success:function (val) { alert(val); } }) })
- 获取form表单submit提交按钮发送请求与ajax发送请求的参数
- 在express中用
req.query
获取参数,为一个对象;
- 在express中用
3.2 form表单和ajax发送post请求
- 注意:
- ajax设置data属性可以是
key=val&key=val
键值对;也可以是:{key:val,key:val}
对象; - ajax中发送get请求和post请求,都可以设置data属性;
- ajax设置data属性可以是
- form表单和ajax提交post请求中如何设置数据参数;
- form表单中设置post请求提交的参数:在每个文本框中设置name值和value值;通过submit提交自动形成
{key:val,key:val}
对象形式;参数不会呈现在地址栏中; - ajax设置post请求提交的参数:设置data属性,属性值格式为:
{key:val,key:val}
对象
- form表单中设置post请求提交的参数:在每个文本框中设置name值和value值;通过submit提交自动形成
- form表单submit提交按钮-发送post请求
- 表单结构:
- form标签中设置action,method,enctype
- action:指的是上传的服务器地址,包括完整的路由,如:
http://localhost:8080/dologin
;当地址栏中已经是在http://localhost:8080
环境下,此时就只写/dologin
即可; - method:指的是请求样式,一般为get和post请求;
- ectype:指的是在post请求中根据上传数据的大小来设置;
- 小数据:
application/x-www-form-urlencoded
;为默认值;可以省略; - 大数据:
multipart/form-data
;一般用于file文件上传和textarea文本上传;
- 小数据:
- action:指的是上传的服务器地址,包括完整的路由,如:
- 在表单input中必须设置name值,此值作为上传格式
{key:val,key:val}
中的key值;表单中输入的内容作value值; - 设置input类型为submit;点击默认提交数据;
- form标签中设置action,method,enctype
- 重要关键:表单发送请求,必须要设置submit类型,点击可提交请求;其他类型都不行;如何没有它,数据根本不能提交;
- 代码:
//action设置的前提条件是在"http://localhost:8080"环境下提交,才能省略;否则必须写全;
- 表单结构:
- ajax提交请求
- 前提条件:必须引入jQuery.js文件或自己封装的ajax.js文件;能够正常使用ajax;
- 在项目中使用时,
- 新建public文件夹,存放静态文件,如js等文件;
- 在服务器中设置静态资源目录为public目录下,如
app.use(express.static("./public"));
- 在html或ejs文件中,通过script引入jQuery.js文件,src设置相对路径在public目录下,才能引入成功;
- 设置ajax属性参数;
- url:指的是上传的服务器地址,包括完整的路由,如:
http://localhost:8080/dologin
;当地址栏中已经是在http://localhost:8080
环境下,此时就只写/dologin
即可;与form表单中的action设置等同; - type:设置"post",指post请求;
- data:设置参数,格式为
{key:val,key:val}
; - dataType:设置接收数据的类型,一般为json格式;
- success:匿名函数接收服务器中res.send()或res.end()响应回来的数据;
- url:指的是上传的服务器地址,包括完整的路由,如:
- 代码:
- login1.ejs文件代码:
ajax发送post请求 提交- 服务器代码:
//设置静态资源目录,用于引用css或js文件; app.use(express.static("./public")); //设置get请求"/main",渲染login1.ejs页面 app.get("/main",function (req, res) { res.render("login1"); }); //发送post请求"/href",提交数据,此地址为ajax中url属性设置; app.post("/href",function (req, res) { const formidable=require("formidable"); const form=new formidable.IncomingForm(); form.parse(req,function (err, fields, files) { if (err) { res.send("获取数据失败"); return; } console.log(fields); res.send("111");//success接收的数据111 }) });
- 获取form表单submit提交按钮发送请求与ajax发送请求的参数
- 使用formidable模块获取post请求提交的参数;
- 引入模块
const formidable=require("formidable");
- 新建form对象
const form=new formidable.IncomingForm();
- 利用
form.parse(req,function(err,fields,files){})
中的fields获取数据,为一个对象;
- 引入模块
- 总结:不管通过什么形式发送post请求,formidable都能获取到发送请求中的参数;
- 使用formidable模块获取post请求提交的参数;
4 留言板项目
- 项目效果:
- 通过地址栏输入地址,来获取留言页面;数据更新显示
- 输入留言内容,点击提交按钮后,页面刷新,数据更新;
- 刚刚提交的数据在第一页显示;
- 页面进行分页显示;默认第一页显示;
- 点击页面删除按钮,数据删除,页面更新;
- 思路:
- get:"/liuyan" => 渲染页面,展现留言面板;
- post:"/doliuyan" => 提交数据,利用ajax提交,提交成功后需要刷新页面,所以不能使用表单提交;
- 页面在渲染打开后,直接发送get请求,向数据库获取数据;
- get: "/getliuyan" => 向数据库获取数据,通过DOM操作插入到页面中;
- 所有数据都显示在页面中;
- 实现排序和分页;
- 在获取数据时,传入实参sort,来控制降序还是升序,此时用的是降序,通过time属性排序;
- 是在获取数据时,传入实参page和pageamount,来设置分页;
- 在第三步发送get请求"/getliuyan"时,需要给ajax设置data属性,传入page和pageamount属性;来获取分页数据;
- 所以需要在获取数据之前先获取数据的总个数,然后计算出page页码个数;在拿数据,pageamount设置定值就好;
- 设置ajax请求,get:"/getcount" => 获取所有数据的总个数;通过计算得到page页码个数;
- 根据页码个数,插入DOM中,显示页码;
- 页面加载后,默认在第一页显示,显示第一页的数据;所以需要设置page为0;$Li中第一个添加active类名;
- 页码与数据进行同步设置
- 给每个页码添加点击事件;然后拿到页码的索引值;
- 设置点击谁,给谁添加active类名,将其点亮,其他的兄弟元素删除active类型;
- 通过索引值来设置page值,进而再次请求数据,拿到数据,插入DOM;所以需要将获取数据的ajax封装在一个函数中;然后调用函数,传入实参,为索引值;
- 点击页面上的删除按钮,删除数据,重新刷新页面;
- 通过数据库中每条数据中的唯一的"_id"值,来进行查找;
- 发送请求:通过a标签中的href来发送get请求,请求的地址为"/delete/id值";
- 在服务器中通过设置get请求地址为正则表达式的地址"/delete/:xx";这样id的值为任意值,通过req.params.xx就能拿到请求时的id值;
- 在数据库中数据为一个对象,对象中的属性为"_id",属性值为:
ObjectId("5c16fef27b4f0c2374efd5ae")
;所以在调用deleteMany方法时,设置的json格式也为此格式; - 所以需要用到mongdb中的模块ObjectID,则需要引入模块;即
const ObjectID = require('mongodb').ObjectID;
- 查找数据时的json为:
{"_id":ObjectID(id)}
; - 在删除数据成功后,需要跳转到原来页面,也就是再发送一次"/liuyan"的请求;用到的知识点为:
res.redirect("/liuyan");
;即,跳转到某页面; - 如果想要对删除成功后做出响应,可以使用ajax请求;通过success函数内操作;
- 注意:
- 重点:
- 在此项目中需要用到jQuery.js和bootstrap.js等文件;需要在页面中引用;所以必须设置静态资源目录;
- 引入css和js文件时,在静态资源目录相对路径下设置;
- 此项目中,指对页面进行了三次刷新;
- 第一次:地址栏输入"/liuyan";打开留言页面;
- 第二次:提交数据完成后,前端刷新页面;用到的代码:
window.location.href="/liuyan"
; - 第三次:在删除数据后,服务器端通过
res.redirect()
跳转页面;即再次发送请求;代码:res.redirect("/liuyan");
- 注意:在页面刷新后,就会默认设置第一页数据显示,所以在分页点击时,不能刷新页面;必须重新调用findData函数,获取数据,重新插入DOM;
- 此项目均使用ajax提交请求,不用表单的原因:ajax提交请求,可以通过success拿到响应结果,对其进行操作,而表单不能对响应结果进行操作;
- 项目中ajax请求默认是异步操作的,所以想让其同步运行,必须设置async属性为false;
- 项目中ajax请求的两种形式都使用了;
- post请求提交数据,get获取数据;
- 二者的参数都是通过data属性进行设置,一般设置为对象,也可以是键值对格式的字符串;
- ajax请求中,get请求提交的参数,在服务器端通过req.query来拿,拿到一个对象;
- ajax请求中,post请求提交的参数,在服务器端通过formidable模块来拿;拿到的也是一个对象;
- 此项目中,从数据库中获取数据,封装为一个函数;总共调用了两次
- 第一次:默认设置页面为第一页,显示第一页的数据,调用findData()函数,传入实参为0;即设置page值为0;进而获取第一页的数据;
- 第二次:分页设置点击事件中,当点击某一页时,获取此元素的索引,作为调用findData()函数的实参,即设置page的值,从而拿到对应页码的数据,然后插入DOM显示;
- 时间的设置,通过第三方模块silly-datetime,设置成固定格式,然后插入到数据库;
- 重点:
- 代码:
- levmes.ejs文件代码:
留言板 我的留言板
- levmes-app.js服务器代码:
const express=require("express"); const router=require("./controller/router2"); const app=express(); app.listen(8080); //设置模板引擎 app.set("view engine","ejs"); //设置静态资源目录 app.use(express.static("./public")); //1.渲染页面 app.get("/liuyan",router.showIndex); //2.post提交数据,ajax请求的数据 app.post("/doliuyan",router.doliuyan); //3.get请求,拿到数据库中的数据,ajax发送get请求 app.get("/getliuyan",router.getliuyan); //4.get请求,拿到数据库中的数据总个数,ajax发送get请求; app.get("/getcount",router.getcount); //5.get请求,删除数据,更新页面,a标签中href提交的get请求; app.get("/delete/:id",router.deleteData);
- router2.js管理器代码:
//引入模块 const db=require("../models/db"); const formidable=require("formidable"); const ObjectID = require('mongodb').ObjectID; const sd = require('silly-datetime'); //导出 //渲染留言板页面 exports.showIndex=function (req, res, next) { res.render("levmes"); }; //提交数据到数据库 exports.doliuyan=function (req, res, next) { //拿到前端ajax提交过来的数据; var form=new formidable.IncomingForm(); form.parse(req,function (err, fields, files) { //插入时间数据 fields.time=sd.format(new Date(),"YYYY-MM-DD HH:mm:ss"); db.insertOne("liuyan",fields,function (err, result) { if(err){ res.send({"bok":false,"msg":"提交数据失败"}); return; } res.send({"bok":true,"msg":"留言成功!!!"}) }) }) }; //获取数据库中数据,响应会前端 exports.getliuyan=function (req, res, next) { //获取前端传来的参数 var page=req.query.page; var pageamount=req.query.pageamount; //向数据库获取数据find db.find("liuyan",{},{"sort":{"time":-1},page,pageamount},function (err, result) { if(err){ res.send({"bok":false,"msg":"获取数据库数据失败"}); return; } res.send({"bok":true,"msg":result}); }) }; //获取数据库中的总个数 exports.getcount=function (req, res, next) { db.count("liuyan",{},function (err, result) { if(err){ res.send({"bok":false,"msg":"获取数据库数据总个数失败"}); return; } res.send({"bok":true,"msg":result}); }) }; //删除数据 exports.deleteData=function (req, res, next) { var id=req.params.id;//通过params拿到冒号后面的数据; //拿到数据库每个数据的id值;然后通过ObjectID拼接id处理查找数据; db.deleteMany("liuyan",{"_id":ObjectID(id)},function (err, result) { if(err){ res.send("删除数据失败"); return; } //数据删除成功之后,通过res.redirect()来跳转会原页面; res.redirect("/liuyan"); }); };
- db.js数据库封装方法代码:
//引入mongodb模块 const MongoClient=require("mongodb").MongoClient; //引入自定义模块setting const setting=require("../setting-liuyan"); //数据库地址 const url=setting.url; //数据库名 const dbname=setting.dbName; //连接数据库 function mongoConnect(callback) { MongoClient.connect(url,{ useNewUrlParser: true },function (err, client) { if(err){ console.log("连接数据库失败"); return; } //将client通过callback回调函数传出; callback(client); }); } //1.在数据库中指定集合插入一条数据; //参数:集合, 插入数据,callback导出信息 exports.insertOne=function (collectionName, json, callback) { mongoConnect(function (client) { //获取数据库db,注:3.0以上版本获取数据库会有差异; var db=client.db(dbname); var col=db.collection(collectionName); col.insertOne(json,function (err, result) { callback(err,result); client.close();//关闭与数据库的连接 }) }) }; //2.查找数据库中集合下的数据 //参数:集合 待查找的数据 分页和排序参数 callback导出信息 //其中第三个分页和排序参数可传可不传; exports.find=function (collectionName,json1,json2,callback) { if(arguments.length===3){ //说明json2没有传;第三个参数传的是回调; callback=json2; json2={}; } var sort=json2.sort || {};//默认值为空对象,即乱序; var limit=Number(json2.pageamount) || 0;//默认值为0,必须为数字 var skip=Number(json2.page) || 0;//默认跳过0; mongoConnect(function (client) { var db=client.db(dbname); var col=db.collection(collectionName); //用toArray将获取的数据以数组的形式传出; col.find(json1).sort(sort).limit(limit).skip(skip*limit).toArray(function (err, doc) { callback(err,doc); client.close();//关闭与数据库的链接 }) }) }; //3 修改更新数据 //参数:集合 待修改数据 修改后的数据 callback导出信息 exports.updateMany=function (collectionName, json1, json2, callback) { mongoConnect(function (client) { var db=client.db(dbname); var col=db.collection(collectionName); col.updateMany(json1,json2,function (err, result) { callback(err,result); client.close(); }) }) }; //4 删除 //参数:集合 待删除 callback导出信息 exports.deleteMany=function (collectionName, json, callback) { mongoConnect(function (client) { var db=client.db(dbname); var col=db.collection(collectionName); col.deleteMany(json,function (err, result) { callback(err,result); client.close(); }) }) }; //5 获取集合下满足条件的总个数 //参数:集合 数据 callback导出信息 exports.count=function (collectionName, json, callback) { mongoConnect(function (client) { var db=client.db(dbname); var col=db.collection(collectionName); col.countDocuments(json,function (err, count) { callback(err,count); client.close(); }) }) };
- setting-liuyan.js设置数据库地址和名称文件代码:
//设置数据库地址 exports.url="mongodb://localhost:27017"; //设置数据库名称 exports.dbName="liuyankuang";
5 留言板项目复习
- 思路:
- 通过
res.render()
来向ejs模板发送参数,前端通过获取的参数渲染DOM页面; - 进行分页显示时,可以通过拿到page参数进行跳转设置,get请求:
/liuyan?page=2
;
- 通过
- 代码
- guestbook.ejs代码:
留言板实例 留言板
-
<%for(var i=0; i
-
【姓名】:<%= datas[i].username%>
【留言】:<%= datas[i].msg%>
【时间】:<%= datas[i].curtime%>
<%}%>
- app.js代码:
//引入模块 const express=require("express"); //引入自定义模块 const router=require("./controllers/router"); //创建服务器 const app=express(); //设置模板引擎 app.set("view engine","ejs"); //设置静态资源目录 app.use(express.static("./public")); //路由设置 //get:"/liuyan" 渲染留言板页面,并获取指定页码内的数据 app.get("/liuyan",router.showIndex); //post:"/doliuyan" 提交留言数据 app.post("/doliuyan",router.toLiuyan); //get:"/:id" 删除数据 app.get("/delete/:id",router.deleteData); //端口号监听 app.listen(5555,function () { console.log("5555 server is running"); });
- router.js代码:
const formidable=require("formidable"); const sd=require("silly-datetime"); const db=require("../models/guestdb"); const ObjectID=require("mongodb").ObjectID; module.exports={ //渲染留言板页面,并获取指定页码内的数据 showIndex:function (req, res, next) { //前端获取参数,如:"/liuyan?page=2" var pageobj=req.query; //创建page,pageamount var page=pageobj.page-1 || 0; var pageamount=3; //获取所有的数据 db.find("liuyan",{},{sort:{"curtime":-1},page,pageamount},function (err, docs) { if(err){ res.send(err); } //获取所有数据的总个数 db.count("liuyan",{},function (err, count) { if(err){ res.send(err); } res.render("guestbook",{ datas:docs, count:count/pageamount, page:Number(page) }); }); }); }, //提交留言数据到数据库 toLiuyan:function (req, res, next) { var form=new formidable.IncomingForm(); form.parse(req,function (err, fields) { if(err){ res.send({"bok":false,"curr":false,"msg":err}); } if(fields.user.length && fields.msg.length){ var time=sd.format(new Date(),"YYYY-MM-DD hh:mm:ss"); //插入到数据库 db.insertOne("liuyan",{"username":fields.user,"msg":fields.msg,curtime:time},function (err, doc) { if(err){ res.send({"bok":false,"curr":false,"msg":err}); } res.send({"bok":true,"curr":true,"msg":"提交成功"}); }); }else{ res.send({"bok":false,"curr":true,"msg":"输入框内不能为空,请输入内容"}); } }) }, //删除数据 deleteData:function (req, res, next) { db.deleteOne("liuyan",{_id:ObjectID(req.params.id)},function (err, doc) { if(err){ res.send(err); } res.redirect("/liuyan"); }) } };