MVC
- 指的是:models views controller;
- models:用于后台数据的查找;
- views:视图,用于存储浏览器页面的文件;
- controller:管理者,控制者;用于二者之间的控制;
myAlbum项目开发
- 创建文件夹:命令:mkdir
- models:提供数据;
- views:模板文件;
- controller:控制器;
- uploads:上传的文件
- public:静态资源
- 创建文件:命令:touch ; window命令行中使用
type nul>
- app.js:入口文件,创建服务器;
- package.json文件:记录当前项目依赖;命令
npm init -y
; - 安装项目模块框架:命令
npm install --save-dev express ejs formidable
- controller文件夹下创建router.js文件;作为一个路由的控制器;
- 知识点:在app.js中引入自定义模块,必须添加相对路径;如:
const router=require("./controller/router")
; - 在router.js中设置代码输出参数;
exports.showIndex=function (req, res) { res.render("index") };
- 在app.js中直接引入:router.showIndex获取匿名函数;
- 知识点:在app.js中引入自定义模块,必须添加相对路径;如:
- 静态资源文件加载
- 设置静态资源在public文件下:
app.use(express.static("./public"));
;即相对路径都在public目录下; - 在ejs文件中,通过link引入css文件和通过script引入js文件;都会发送对应的请求;请求地址为href和src中的路径地址;
- 在通过get请求,打开index.ejs文件后,会在页面中继续发送请求;页面想要有css样式和js动效,必须保证css文件和js文件能够正常引入;如何在页面中渲染打开css文件;css的引入地址是很重要的;
- 代码:
app.use(express.static("./public"));
中将静态资源的目录设置在public目录下,即所引入的css文件和js文件必须在此目录下添加相对路径,然后找到文件才能打开; - 所以,将css文件和js文件放在public文件夹下,在引入时添加相对路径;如:
<link href="/css/bootstrap.css" rel="stylesheet">
和<script src="/js/bootstrap.js"></script>
;注意:css前斜杠最好添加上,若不添加,会被拼接在地址的后面,会出错,所以必须添加; - 在地址栏中输入地址
localhost:8080/css/bootstrap.css
也可以打开引入的css文件;
- 设置静态资源在public文件下:
- 静态资源设置的含义
- 代码:
app.use(express.static("./public"))
- 含义:在express中,设置静态资源的请求路径为public目录下;
- 验证:在地址栏中输入
localhost:8080/index.html
地址,指的含义是查找到public目录下的index.html,打开并渲染到页面上; - 在index.html文件中有link引入的css文件;如:
<link href="/css/bootstrap.css">
;其中link的href会发送请求;相当于在地址栏中输入localhost:8080/css/bootstrap.css
地址;即,在public目录下查找css文件夹下的bootstrap.css文件,打开并渲染到页面中; - 结论:
- 设置了静态资源的路径;则在地址栏中寻找的文件都是在public目录下查找,如果查找到,则渲染,查找不到,则会查看其它的请求是否存在此地址;
- 打开html或ejs文件中,通过link或script或img中的href或src等引入文件,都会向服务器发送请求;会默认在public目录下查找;
- 代码:
- 简单的实现index.ejs文件的页面打开渲染:
- index.ejs文件,通过设置get请求:
app.get("/",function(req,res){ res.render("index")};);
即:在地址栏中输入"/",后可执行匿名函数;在匿名函数中通过res.render来默认打开views目录下的index.ejs文件; - 打开index.ejs文件后,里面引入的href和src再次发送多个请求;在设置的静态资源目录public文件夹下查找文件;然后打开渲染;
- 代码:
- index.ejs文件代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>模板</title> <link href="css/bootstrap.css" rel="stylesheet"> </head> <body> <h1>你好,世界!</h1> <script src="js/jquery.js"></script> <script src="js/bootstrap.js"></script> </body> </html>
- app.js服务器代码:
const express=require("express"); const app=express(); const router=require("./controller/router");//引入自定义模块,必须添加相对路径; app.listen(8080); //发送请求,静态渲染页面 app.use(express.static("./public")); //设置模板引擎 app.set("view engine","ejs"); //通过"/"来发送请求,打开views下的index.ejs文件 app.get("/",router.showIndex);
- router.js管理器代码:
exports.showIndex=function (req, res) { res.render("index") };
- index.ejs文件,通过设置get请求:
项目开发
- 搭建静态页面index.ejs;利用bootstrap模板来创建页面布局;
- 页面中相册的个数取决于uploads目录下文件夹的个数;
- 在页面中利用ejs模板创建for循环,在服务器中通过render()传送参数;即在router.js管理器中获取参数;router.js再去models中获取数据;然后最终传送给views中ejs页面;
- 获取磁盘中uploads目录下文件夹的个数;
- 在models中files.js中获取数据,导出数据;利用fs文件系统中的readdir()来获取文件夹下所有的文件,再通过fs.stat()获取每个文件的状态stats,判断stats.isDirectory()是否为true;如果文件是文件夹,则返回true;
- 通过自执行函数和递归函数,将异步强制变成同步;然后待遍历完,输出数组;
- 在匿名函数中如何输出文件:通过callback参数;注意传出的数据包括err错误信息;
- 在router.js中引入自定义模块files.js,添加相对路径;通过匿名函数中的参数拿到文件夹信息;然后通过res.render()传送给ejs文件使用;
- 注意点:
- 在使用fs.readdir(path,()=>{})时,path路径为"./uploads",其中"./"拿到的是根目录;不是"../"拿到根目录;
- 数据的输出通过回调函数,将异步操作中的结果输出,但要注意输出不同时刻的err信息;在成功输出数据时,赋值为null,这样在if(err)条件语句中,就不会走语句;
- 代码:
- ejs代码:
<div class="container"> <div class="row"> <% for(var i=0; i<albums.length; i++){%> <div class="col-xs-6 col-md-3"> <a href="/" class="thumbnail"> <img src="/img/01.jpg" alt="文件夹图片"> </a> <h5 style="text-align: center;"><%= albums[i]%></h5> </div> <% }%> </div> </div>
- app.js代码:
app.get("/",router.showIndex);
- router.js代码:
//引入models中的files.js模块; const modFiles=require("../models/files"); //显示首页 exports.showIndex=function (req, res) { modFiles.showAllAlbums(function(err,albums){ if(err){ res.send(err); return;//阻断程序执行; } res.render("index",{ albums }) }); };
- files.js代码:
//用于生产数据,然后输给router.js使用; const fs=require("fs"); exports.showAllAlbums=function (callback) {//数据以回调函数的形式输出; fs.readdir("./uploads",(err,files)=>{//地址为根目录下的地址; if(err){ callback(err,null); return; } var ary=[]; //把异步操作强制变成了同步操作; (function interator(i){ if(i>=files.length){ callback(null,ary);//null代表不会走if(err)条件; return;//阻断程序执行; } fs.stat("./uploads/"+files[i],(err,stats)=>{ if(err){ callback(err,null); return; } if(stats.isDirectory()){ ary.push(files[i]); } interator(++i); }) })(0); }); };
- 点击文件夹后发送请求,打开文件夹下的所有文件;
- 发送请求:在地址栏中输入文件夹名字后,即发送了get请求,使其获的数据并打开渲染;在index.ejs中每一个文件夹都在一个a标签中,设置href为
<a href="/<%= albums[i]%>" class="thumbnail">
,即根据文件夹的名字不同,发送不同的请求;跟地址栏中输入localhost:8080/大学
是一样的效果;从而实现页面的跳转; - 打开页面;需要新建一个showImg.ejs文件,里面是新的内容;在发送
/:albumsImg
请求后,在files.js中打开该文件夹,获取文件夹下所有的文件,通过fs.isFile()筛选出不是文件夹的文件;通过匿名函数传出;然后渲染到页面上,展示出相应个数和名字的图集; - 在页面渲染中打开img图片;在files.js中只能拿到文件夹下的文件个数和文件名称;无法打开渲染;res.render的作用是,打开showImg.ejs文件,并且给其传数据;只能渲染页面,不能打开img图片;
- 呈现img图片在页面中,需要img标签中的src再次发送强求,服务器接受进行响应;所以,需要再设置静态资源到
./uploads
目录下,指的是,发送来的请求闲杂public目录下找文件,如果找不到,就到uploads目录下查找,找到文件,渲染打开文件; - 注意1:img中src的路径需要注意;当showImg.ejs被渲染到页面上的时候,src才开始发送请求;包括css和js也会发送请求;
- img的src设置时有两种方式;1)
<img src="/<%= albumsImg%>/<%= albums[i]%>" alt="文件夹图片">
,在地址中添加文件夹路径;2)<img src="<%= albums[i]%>" alt="文件夹图片">
,在地址中不添加文件夹路径但是不能添加"/",可能的原因是在地址栏中已经存在http://localhost:8080/大学/
; - css和js文件src中必须添加"/",不能省略;
- img的src设置时有两种方式;1)
- 注意2:使用req.params.albumsImg获取请求地址时,除了文件夹地址,还会得到favicon.ico,所以需要在获取文件夹下文件的函数中,阻止favicon.ico运行;
- 代码:
- showImg.ejs代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>展示图片</title> <link href="/css/bootstrap.css" rel="stylesheet"> </head> <body> <% include include/header.ejs%> <section class="container"> <ol class="breadcrumb"> <li><a href="/">全部相册</a></li> <li class="active"><%= albumsImg%></li> </ol> </section> <div class="container"> <div class="row"> <% for(var i=0; i<albums.length; i++){%> <div class="col-xs-6 col-md-3"> <span class="thumbnail"> <img src="/<%= albumsImg%>/<%= albums[i]%>" alt="文件夹图片"> </span> <h5 style="text-align: center;"><%= albumsName[i]%></h5> </div> <% }%> </div> </div> <script src="/js/jquery.js"></script> <script src="/js/bootstrap.js"></script> </body> </html>
- app.js代码:
const express=require("express"); const app=express(); const router=require("./controller");//引入自定义模块,必须添加相对路径;因为controller创建了package.json文件,在里面设置了main属性,设置默认的入口文件为router.js文件; app.listen(8080); //发送请求,静态渲染页面 //1.设置静态资源路径在public目录下,用于渲染引入的css文件和js文件; app.use(express.static("./public")); //2.设置静态资源路径在uploads目录下,用于渲染img图片; app.use(express.static("./uploads")); //设置模板引擎 app.set("view engine","ejs"); //通过"/"来发送请求,打开views下的index.ejs文件 app.get("/",router.showIndex); //发送get请求:地址:"/大学",打开views下的showImg.ejs文件; app.get("/:albumsImg",router.showImg);
- router.js代码:
//显示相册文件夹中的图片 exports.showImg=function (req, res) { var albumsImg=req.params.albumsImg; modFiles.showAlbumsImgs(albumsImg,function (err,albums,albumsName) { //如果有错误,则显示在页面上,并阻断程序执行; if(err){ res.send(err);//send后面可以写return; return;//必须阻断程序执行; } res.render("showImg",{ albumsImg, albums, albumsName }) }); };
- files.js代码:
//获取地址栏中发送的地址文件夹下的所有图片文件;生成数组导出; exports.showAlbumsImgs=function (albumsImg, callback) { //albumsImg是地址栏中的文件夹名称;callback用来导出数据; //读取文件夹下的文件 fs.readdir("./uploads/"+albumsImg,function (err, files) { if(err){ callback("读取文件夹失败",null,null); return; } var albums=[]; var albumsName=[]; (function interator(i) { if(i>=files.length){ callback(null,albums,albumsName); return; } fs.stat("./uploads/"+albumsImg+"/"+files[i],function (err, stats) { if(err){ callback("读取文件夹下的文件失败",null,null); return; } if(stats.isFile()){ //成立,则证明不是文件夹 albums.push(files[i]); albumsName.push(path.parse(files[i]).name); } interator(++i); }) })(0); }) };
- 发送请求:在地址栏中输入文件夹名字后,即发送了get请求,使其获的数据并打开渲染;在index.ejs中每一个文件夹都在一个a标签中,设置href为
- 页面的操作:
- 提炼出两个ejs页面中的公共的代码,放在views下的include文件夹下的header.ejs文件中;这样在文件中引入此文件即可;
- 优点:当页面公共部分的代码,发生变化,只需在header.ejs文件中修改即可,否则,还需分别再两个文件中修改;
- 引入include.ejs文件代码:
<% include include/header.ejs%>
,其中include/header.ejs
为相对路径,无需加引号;
- 图片上传功能
- 思路:
- 创建form.ejs表单页面;通过form表单来提交数据;
- 发送get请求,通过header.ejs中的上传a标签href设置地址,点击发送请求;打开form.ejs页面;传送数据;用于select表单中获取文件夹数据;
- 发送post请求,通过表单中submit提交按钮,设置form中的action和method;点击发送post请求;
- 在router.js中通过formidable模块来获取上传的数据;
- 新建form对象;
var form= new formidable.IncomingForm()
; - 设置上传地址;
form.uploadDir="./uploads/"
; - 通过form.parse()拿数据;=》fields:表单上传参数组成的对象;files:表单file中name组成的对象;用于获取上传图片的路径名字;以及上传之前的名字;
- 通过
files.tupian.size
,拿到上传文件的大小,然后进行判断,如果大于某值,则用fs.unlink()
删除已经上传的文件;注意,在条件判断中设置return;阻断程序向下运行; - 通过fs.rename()将oldpath改为newpath,修改原来文件名字并移动该文件到新的路径下;
- 新建form对象;
- 修改成功后,打开对应文件夹的图集;调用
modFiles.showAlbumsImgs(albumsImg,function (err,albums,albumsName){})
; - 修改成功后,可以通过
res.redirect("/中学")
来进行重定向页面跳转;
- 注意点:
- 设置get请求"/upload"时,必须放在
app.get("/:albumsImg",router.showImg);
前面,否则,不会走该请求;必须将严格的请求放在不严格的请求的前面; - form表单中,seclect和file必须设置name值,否则无法获取数据;seclect的name值用来获取放在哪个文件夹下,file的name值是用来作为files对象中属性名来获取上传文件的数据的;
- form表单中的action设置请求地址,method设置post请求,enctype设置大文件下的类型;
- 利用formidable模块获取files对象后,path属性拿到的是路径加文件名;作为oldpath;newpath包含相对路径和文件名;相对路径设置为uploads下的对应文件夹中;
- 在修改名字后,原来上传的文件会被移动到新的路径下;
- 在设置上传地址时地址后面必须加"/",否则,无法使用;如
form.uploadDir="./uploads/";
- 注意异步和同步问题;
- 设置get请求"/upload"时,必须放在
- 代码:
- form-upload.ejs代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>表单上传文件</title> <link href="/css/bootstrap.css" rel="stylesheet"> </head> <body> <% include include/header.ejs%> <section class="container"> <form action="/doupload" method="post" enctype="multipart/form-data"> <div class="form-group"> <label for="wenjian">相册目录</label> <select class="form-control" name="wenjianjia" id="wenjian"> <% for(var i=0; i<albums.length; i++){%> <option><%= albums[i]%></option> <%}%> </select> </div> <div class="form-group"> <label for="InputFile">上传文件路径</label> <input type="file" id="InputFile" name="tupian"> </div> <button type="submit" class="btn btn-primary">上传</button> </form> </section> <script src="/js/jquery.js"></script> <script src="/js/bootstrap.js"></script> </body> </html>
- app.js代码:
/发送get请求:地址:"/upload",打开views下的form.ejs文件; app.get("/upload",router.upload); //发送post请求,地址:"/doupload",上传文件; app.post("/doupload",router.doUpload); //以上代码必须放在下面代码前面; //发送get请求:地址:"/大学",打开views下的showImg.ejs文件; app.get("/:albumsImg",router.showImg);
- router.js代码:
//上传文件获取数据 exports.doUpload=function (req, res) { //新建一个form对象 var form= new formidable.IncomingForm(); //设置上传地址 form.uploadDir="./uploads/"; //解析文件 form.parse(req,(err,fields,files)=>{ //修改文件名,放在指定文件夹下 var oldpath=files.tupian.path; var newpath=form.uploadDir+fields.wenjianjia+"/"+files.tupian.name; fs.rename(oldpath,newpath,function (err) { if(err){ res.send("修改名字失败"); return; }else{ //res.send("上传成功");//注意异步问题;所以此代码不能写在外面; //当上传成功后,跳转到相应的文件夹中打开; var albumsImg=fields.wenjianjia; modFiles.showAlbumsImgs(albumsImg,function (err,albums,albumsName) { //如果有错误,则显示在页面上,并阻断程序执行; if(err){ res.send(err);//send后面可以写return; return;//必须阻断程序执行; } res.render("showImg",{ albumsImg, albums, albumsName }) }); } }) }); };
- 思路:
- 接盘侠
- 在最后设置use请求,请求地址为"/",即所有的地址都可以进入请求;渲染打开404.ejs文件,显示404页面;
- 代码:
//设置404页面,用来接底 app.use("/",function (req, res) { res.render("404");//打开404.ejs文件; });
- 利用middleware中间件来设置错误流,当进入一个请求后,如果出现err,则通过next(),来向下接着请求,最终到达use请求,渲染404页面;
- 在router.js文件中所有请求的匿名函数;均要设置next,当err情况下,设置next(),并阻断程序执行;
- files.js文件中err不需要设置next(),因为通过callback已经将err返回到router.js中;
- 这样设置,也可以解决地址具体地址和广泛地址的影响;
- 注意:send()后面不能设置任何东西;可以写return;
项目开发中的架构格局
- 文件分类及作用
- MVC结构:
- M:models:用来获取后台的数据库,通过exports输出给controller管理器;
- 创建files.js文件,作为后台数据的模块;通过exports来传出数据,在router.js管理器中引入此模块,用于获取传出数据;
- V:views: 目录下存在ejs文件,作为浏览器视图的渲染文件;
- 存储ejs文件,用于页面的打开渲染;
- 创建include文件夹,里面创建header.ejs文件,作为ejs文件的公共部分代码;通过
<% include include/header.ejs%>
引入到其他文件中;
- C:controller:管理器;用来连接models和views;
- 作用:作为一个管理者,在views中通过render来打开ejs文件,在数据库或models中拿数据;
- 创建router.js文件,作为数据的管理器;通过exports来传出数据;在app.js中引入此模块来获取数据;
- 创建package.json文件,通过设置里面的main属性,来设置默认入口文件router.js;则在app.js服务器文件中引入模块时,可以直接引入controller文件夹,如
const router=require("./controller")
;
- 三者之间的关系:
- 当服务器中接受到浏览器发送的请求后,向controller管理器请求;如代码:
app.get("/",router.showIndex);
;router.js中设置匿名函数;渲染页面; - 当浏览器中需要uploads文件夹下的文件夹个数时,向controller管理器要数据;controller再向models后台数据库中查找获取;models数据也通过exports输出数据;
- 当服务器中接受到浏览器发送的请求后,向controller管理器请求;如代码:
- M:models:用来获取后台的数据库,通过exports输出给controller管理器;
- public文件夹:用于存储静态文件;如css,js文件;通过在服务器中设置静态渲染来引用文件;
- app.js:为入口文件,用于创建服务器来前后台交互;
- package.json:用于记录项目依赖的文件;
- readme.md:读者读的文档,用于解读项目;
- MVC结构:
- 请求及目的
- get请求,是为了渲染打开页面;
- get-"/"=>打开index.ejs;即,打开图片首页,根据uploads下文件夹的个数,呈现出相应个数的图集;
- get-"/:albumsImg" => 打开showImg.ejs;即呈现出每个albumsImg文件夹下的图片;
- get-"/upload" => 打开form上传文件表单;用于上传图片文件;
- post请求,是真正做一些功能的,如上传文件;
- post-"/doupload" => 通过表单中的submit提交按钮,来发送post请求,进行图片文件的上传;
- use请求:请求地址设置为"/",可以省略,作为接盘侠;
- use-"/" => 打开404.ejs文件,呈现404页面,配合middleware中间件使用;
- get请求,是为了渲染打开页面;
项目开发中的知识点
- 在controller文件夹中创建package.json文件,里面设置main属性,来设置默认的入口文件为router.js,这样在app.js服务器中引入模块时,只引入文件夹controller;即:
const router=require("./controller")
; - 在项目开发中如果匿名函数中存在err错误代码,必须设置相应的条件语句,然后传出错误信息;后面一定要加return,阻断程序执行;
- 使用req.params.albumsImg获取请求地址时,除了文件夹地址,还会得到favicon.ico,所以需要在获取文件夹下文件的函数中,阻止favicon.ico运行;
- 1M等于1024K,1K等于1024字节,文件上传时,通过
files.tupian.size
来获取上传文件的大小,单位为字节; - 接盘use请求和middleware的设置
- use请求地址为"/",可以省略;当所有请求都不成立的时候,最后请求use的匿名函数,打开404页面文件;
- 前提是在每个请求的匿名函数中设置middleware中间件;即next();当err时设置next(),并阻断程序执行;