初学nodejs express小案例——小小相册(不涉及数据库,非常详细)
业务简介:
显示文件夹
点击显示相册
上传相册
一、在主页显示文件夹
首先,我们要建立以上的文件夹,其中views用于放模板ejs,uploads里放的是相册文件夹,public是网页所需要的css,js等,node_modules放的是开发要用到的包,models是为数据库而建立的(本次用不到数据库)里面的函数是最底层的,tempup只是用于图片上传时的中转站(之后会懂的),controller文件夹里就是真正需要实现业务的函数。
1.在app.js里使用express
var express = require("express"); var app = express(); //控制器 var router = require("./controller"); //设置模板引擎 app.set("view engine","ejs"); //路由中间件 //静态页面 //app.use("/static",express.static("./public"));//所有/static/是从public下找 app.use(express.static("./public")); app.use(express.static("./uploads")); app.get("/",router.showIndex);//函数的引用
app.listen(3000);
这一句表示当开启网页 http://localhost:3000/ 时,将调用router里的showIndex
app.get("/",router.showIndex);//函数的引用
2. 在router里需要写showIndex函数,函数中,通过调用file,js里的getAllAlbums函数获得allAlbums数组,再将数组给allAlbums,同时渲染前端页面index.ejs,其中ejs可不写
var file = require("../models/file") //用于文件操作 var fs = require("fs"); //首页 exports.showIndex = function (req,res,next) { //传统的思维,错误的 /*res.render("index",{ //由于异步不能这么写,还没return就已经赋值了 "albums":file.getAllAlbums() });*/ //这就是Node.js的编程思维,就是所有东西都是异步的 //所以,内侧函数,不是return回来东西,而是调用高层函数 //提供的回调函数,把数据当作回调函数的参数来使用。 file.getAllAlbums(function (err,allAlbums) { if(err){ next();//交给下面适合它的中间件 //res.render("err"); return; } res.render("index",{ "albums":allAlbums }) }) }
3.接着我们在models文件下建立file.js,在里面写getAllAlbums函数,用于获取uploads文件夹下的所有文件夹,借用迭代器组成一个数组allAlbums返回。
var fs = require("fs"); //这个函数的callback中含有两个参数,一个是err //另一个是所有文件夹名字的array exports.getAllAlbums = function (callback) { fs.readdir("./uploads",function (err,files) { if(err){ callback("没有找到uploads文件夹",null); } var allAlbums = []; //console.log(files);//[ '小狗', '军犬' ] //迭代器 异步 (function iterator(i) { if(i == files.length){ //console.log(allAlbums); //return allAlbums;//遍历结束 callback(null,allAlbums); return; } fs.stat("./uploads/"+files[i],function (err,stats) { if(err){ callback("找不到文件"+files,null); } if(stats.isDirectory()){ allAlbums.push(files[i]); } iterator(i +1); }) })(0); }); }
4.最后,要写模板函数index.ejs,在views下新建一个index.ejs,利用bootstrap写模板,这里时关键。因为我们已将public静态了,也就是public里的东西都公开了。所以这里直接images/图片即可
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <title>小小相册</title> <!-- Bootstrap --> <link href="/css/bootstrap.min.css" rel="stylesheet"> <style type="text/css"> .row h4{ text-align: center; } </style> </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">小小相册</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="/">全部相册 <span class="sr-only"></span></a></li> <li><a href="/up">上传</a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container"> <div class="row"> <% for(var i = 0 ; i < albums.length ; i++){%> <div class="col-xs-6 col-md-3"> <a href="<%= albums[i]%>" class="thumbnail"> <img src="images/wjj.jpg" alt="..."> </a> <h4><%= albums[i]%></h4> </div> <%}%> </div> </div> <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) --> <script src="js/jquery.min.js"></script> <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 --> <script src="js/bootstrap.min.js"></script> </body> </html>
二、404页面的制作
1.在views下新建一个err.ejs
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <title>小小相册</title> <!-- Bootstrap --> <link href="/css/bootstrap.min.css" rel="stylesheet"> <style type="text/css"> .row h4{ text-align: center; } </style> </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">小小相册</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li><a href="/">全部相册 <span class="sr-only"></span></a></li> <li><a href="/up">上传</a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container"> <img src="/images/404.gif"/> </div> <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) --> <script src="/js/jquery.min.js"></script> <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 --> <script src="/js/bootstrap.min.js"></script> </body> </html>
2.再在app.js下配置路由
三、点击相册文件夹,显示所有图片
1.先配置路由
app.get("/:albumName",router.showAlbum);
2.在router.js里写函数showAlbum,要通过向file.js里写函数getAllImagesByAlbumName传相册名获得该相册的所有图片路径,再传给前端album.ejs
//相册页 exports.showAlbum = function (req,res,next) { //遍历相册页的所有图片 var albumName = req.params.albumName; //具体业务交给model //调用函数得到图片 file.getAllImagesByAlbumName(albumName,function(err,imagesArray){ //返回得到imagesArray if(err){ next();//交给下面适合它的中间件 //res.render("err"); return; } //渲染album.ejs页面,把albumname赋值albumName传到页面 res.render("album",{ "albumname":albumName, "images":imagesArray }); }); }
3.在models里的file.js里写函数getAllImagesByAlbumName,利用router里传来的相册名,获取所有图片路径
//通过文件名,得到所有图片 exports.getAllImagesByAlbumName = function (albumName,callback) { fs.readdir("./uploads/"+albumName,function (err,files) { if(err){ callback("没有找到uploads文件夹",null); return; } var allImages = []; //console.log(files);//[ '小狗', '军犬' ] //迭代器 异步 (function iterator(i) { if(i == files.length){ //console.log(allImages); //return allAlbums;//遍历结束 callback(null,allImages); return; } fs.stat("./uploads/"+albumName+"/"+files[i],function (err,stats) { if(err){ callback("找不到文件"+files,null); return; } if(stats.isFile()){ allImages.push(files[i]); } iterator(i +1); }) })(0); }) }
4.写album.ejs模板
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <title>小小相册</title> <!-- Bootstrap --> <link href="/css/bootstrap.min.css" rel="stylesheet"> <style type="text/css"> .row h4{ text-align: center; } .thumbnail img{ width:auto; height: auto; } </style> </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">小小相册</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li ><a href="/">全部相册 <span class="sr-only"></span></a></li> <li><a href="/up">上传</a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container"> <ol class="breadcrumb"> <li><a href="/">全部相册</a></li> <li class="active"><%=albumname%></li> </ol> <div class="row"> <% for(var i = 0 ; i < images.length ; i++){%> <div class="col-xs-6 col-md-3"> <a href="#" class="thumbnail"> <img src="<%=images[i]%>" alt="..."> </a> </div> <%}%> </div> </div> <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) --> <script src="/js/jquery.min.js"></script> <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 --> <script src="/js/bootstrap.min.js"></script> </body> </html>
5.记得最后把超链接都补补全
四、上传相册
1.先配置路由,做一个上传的界面
app.js全部代码:
var express = require("express"); var app = express(); //控制器 var router = require("./controller"); //设置模板引擎 app.set("view engine","ejs"); //路由中间件 //静态页面 //app.use("/static",express.static("./public"));//所有/static/是从public下找 app.use(express.static("./public")); app.use(express.static("./uploads")); app.get("/",router.showIndex);//函数的引用 app.get("/:albumName",router.showAlbum); app.get("/up",router.showUp); app.post("/up",router.doPost);//点击表单提交后 //最后的中间件404 app.use(function (req,res) { res.render("err") }) app.listen(3000);
2.在router里写showUp和doPost函数
showUp比较简单,就是跳转到up.ejs页面,同时该页面有个下拉框,需要显示所有相册文件夹的名字
doPost比较复杂,它先将上传的文件放到了tempup文件夹里,然后利用fs自带函数rename改名,新名字使用了上传的时间戳。改名的同时,可以更改文件路径。再将文件上传的过程中,先判断图片的大小有没有超限,超的话使用fs自带的unlink函数删除。
router.js全部代码
var file = require("../models/file") //npm install silly-datetime 用于上传使用 var formidable = require('formidable'); var path = require("path"); //用于文件操作 var fs = require("fs"); //npm install silly-datetime 用于获取日期 var sd = require("silly-datetime"); //首页 exports.showIndex = function (req,res,next) { //传统的思维,错误的 /*res.render("index",{ //由于异步不能这么写,还没return就已经赋值了 "albums":file.getAllAlbums() });*/ //这就是Node.js的编程思维,就是所有东西都是异步的 //所以,内侧函数,不是return回来东西,而是调用高层函数 //提供的回调函数,把数据当作回调函数的参数来使用。 file.getAllAlbums(function (err,allAlbums) { if(err){ next();//交给下面适合它的中间件 //res.render("err"); return; } res.render("index",{ "albums":allAlbums }) }) } //相册页 exports.showAlbum = function (req,res,next) { //遍历相册页的所有图片 var albumName = req.params.albumName; //具体业务交给model //调用函数得到图片 file.getAllImagesByAlbumName(albumName,function(err,imagesArray){ //返回得到imagesArray if(err){ next();//交给下面适合它的中间件 //res.render("err"); return; } //渲染album.ejs页面,把albumname赋值albumName传到页面 res.render("album",{ "albumname":albumName, "images":imagesArray }); }); } exports.showUp = function (req,res) { //调用file的getAllAlbums函数,得到文件夹名字之后的事情卸载回调函数里 file.getAllAlbums(function (err,allAlbums) { if(err){ next();//交给下面适合它的中间件 //res.render("err"); return; } res.render("up",{ "albums":allAlbums }) }) } //上传表单 exports.doPost = function (req,res) { var form = new formidable.IncomingForm(); form.uploadDir = path.normalize(__dirname + "/../tempup/"); console.log(__dirname + "/../temup/") form.parse(req,function (err,fields,files) { console.log(fields); console.log(files); /*res.writeHead(200,{'content-type':'text/plain'}); res.write('received upload:\n\n'); res.end(util.inspect({fields: fields,files:files}));*/ //改名 if(err){ next(); //这个中间件不受理这个请求了,往下走 return; } //判断文件尺寸 var size = parseInt(files.tupian.size); if(size>102400){ //console.log("图片尺寸应该小于100M"); res.send("图片尺寸应该小于100M"); //删除图片 fs.unlink(files.tupian.path,function(){});//新版本要加function(){} return; } var ttt = sd.format(new Date(),"YYYYMMDDHHmmss"); var ran = parseInt(Math.random() * 89999 + 10000); var extname = path.extname(files.tupian.name); var wenjianjia = fields.wenjianjia; var oldpath = files.tupian.path; var newpath = path.normalize(__dirname + "/../uploads/"+ wenjianjia + "/" + ttt + ran + extname); fs.rename(oldpath,newpath,function (err) { if(err){ res.send("改名失败"); //console.log("改名失败!") return; } res.send("成功"); }); }); }
3.写up.ejs,在views里新建up.ejs
up.ejs全部代码
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <title>小小相册</title> <!-- Bootstrap --> <link href="/css/bootstrap.min.css" rel="stylesheet"> <style type="text/css"> .row h4{ text-align: center; } </style> </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">小小相册</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li><a href="/">全部相册 <span class="sr-only"></span></a></li> <li class="active"><a href="/up">上传</a></li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container"> <div class="row"> <form style="width:40%;" method="post" action="#" enctype="multipart/form-data"> <div class="form-group"> <label for="exampleInputEmail1">选择文件夹</label> <select class="form-control" name="wenjianjia"> <%for(var i = 0; i < albums.length; i++){%> <option><%=albums[i]%></option> <%}%> </select> </div> <div class="form-group"> <label for="exampleInputFile">选择图片</label> <input type="file" id="exampleInputFile" name="tupian"> </div> <button type="submit" class="btn btn-default">上传</button> </form> </div> </div> <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) --> <script src="js/jquery.min.js"></script> <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 --> <script src="js/bootstrap.min.js"></script> </body> </html>
4.最终实现功能