基于nodejs express框架设计的 相册上传和查看 以及代码分析

前段时间忙项目没时间学node,前两天负责人提出要做直播,考虑使用websocket,马上我就想到node的socket.io了,于是打算多花点时间赶紧把node搞熟。express 作为http协议的web开发框架用起来还是蛮顺手的,感觉比较直白,而且毕竟是js,函数式编程和Java有很大的不同。今天来体验一把。

入手demo就写一个 个人相册 好了,相册涉及了文件上传,查看全部相册,查看单张照片。

我先把我最初的设计写出来,然后写我完善后的。

1.express等环境搭建

首先安装express, 安装express真是没把我坑死,找的教程说安装完express后使用express命令可快速搭建express工程,然而卵用没有。。。后来发现那个教程讲的是express3.x 我用的是express4.x,所以还请大家务必注意,express4.x的安装和使用和3有一些差异,命令如下

npm install -express -save

npm install -express-generator --save

第二条命令式为了安装express构建工具的,他能帮你快速构建一个express项目,有一点编程经验的人还是不要自己一个个建文件夹和配置文件,太慢了,用工具建好后看看目录结构和配置文件,了解一下就好,特别是用过maven, grandle的看起来so easy;细节的不明白的日后慢慢理解也好。

如果用的webstorm的话,要记得以依赖下node的global库。

安装完express后,最好把express加到你的环境变量,找到express.cmd后放到你的环境变量(环境变量配置 同 Java配置方法)。

然后这时候你使用express -e demo 就能看到一个demo 项目出来了,当然这里我的名字叫 photo。下面是我构建完的工程:

134837_I7Qn_2320871.png

2.初始工作

app.js就是你webapp工程入口,运行一下看看,可能会报错,因为也许你的依赖没有添加完,比如 body-parse,cookie-parse等 库,你运行一下看看报什么错,就添加什么库好了,我大概加了四个后就没问题了。当然这里还要额外加俩库,就是multer(文件上传相关),mongoose(操作mongodb)和ejs(模板库)

views里面都是模板试图,routes是路由,类比controller filter. public一般是设置静态资源的,前端js,css,html等放在这里面;node_modules你不用管,你每添加一个依赖就会在里面多一个模块;lib一般是你自己写的,包括业务逻辑,data access等等,其实我感觉可以划分的细一些,比如 server,db,util。

列一下看看我们都依赖了什么库:

var express = require("express");
var path = require("path");
var bodyParser = require("body-parser");
var multer = require("multer");

打开app.js看看里面的有什么?你会看到有很多require被添加进来,其实就类似C/C++的include,java的import。再就是express方法,调了很多use()方法,如下:

app.use(express.static("public"));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(multer({dest: '/tmp/'}).array('image'));

use方法的设计思路感觉和jquery的css等函数一样,链式编程,有点像挂载,你把很多组件写好了,use一下,这个功能就被挂载进来了。这种写法Java C/C++中不曾见过,其实jquery中也是这样,你给一个控件添加样式就是不断地把样式挂载到控件实例上。以上代码的解读就是 给express添加 静态资源,设置为public路径下的都是静态资源;添加表单上传编码“application/x-www-form-urlencoded ;添加cookie识别转换,添加文件上传功能(这里设置了临时文件位置)。

为了要使用ejs视图,还是要设置一下试图引擎:

 

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', "ejs");

这里set方法采用键值对,设置视图路径和视图引擎,一般set功能都放在服务器启动时设置启动参数 比如端口,ip,运行环境什么的,可以用get获取。这个方法可能用的比较少,毕竟set是在设置webapp的启动配置参数,你看看Java的spring配置文件,会设置很多东西,在启动时会被加载,也就是说如果你在开发有关express插件的话,可能用的比较多吧?不知道我这么说对不对。

 

3.业务逻辑之----- 文件上传

首先你需要一个页面用来上传文件,这个好说就一个表单,其次你需要一个页面来展示你数据库中存储的全部图片,然后还有个页面是显示单张图片;这里,显示单张和显示多张我放在一起。需要俩页面,分别叫index.ejs(照片上传) show.ejs(显示照片).

在app.js代码基础上我们开始写逻辑了。先写照片上传。上传请求为post方法请求,所以先写一个路由来接受post请求

 

var router = express.Router();

router就是一个路由对象,router对象有post方法,就是用来接收post请求。

 

router.post("/upload", function (req,res) {
    ......
}

回调函数在上传文件请求接收到后执行。具体细节应该就是文件上传,并把详细信息存入数据库了。

文件信息上传后通过req.file[0]对象来接受,该对象就是表单中file控件选中的文件,属性包括originalname,

path等等,原名好理解,path大概是临时路径,我们用了multer插件,设置过临时路径。

我们的文件是不可能放在临时文件内的,而且临时文件的命名方式很BT。。。 所以我们要把他移动到我们服务器默认的路径下,我们默认在public/images下好了,那么先拼出来这个路径

path.join(__dirname,"./public/images/") + req.files[0].originalname

path也是node中的工具,需要 require("path)才能使用,这个工具就是为了方便我们拼接路径的。

下面我们有了临时路径和指定路径了,就要做文件转移了。需要使用fs模块的rename方法。rename默认是异步发的方法,所以我们需要把数据库操作放在这个回调中,才能保证文件在上传完后把信息存到数据库。

fs.(req.[].image_file(errdata) {
  (err){ err}
  {
    data= {
      :path.(req.[].):req.[].}

这样就完成了文件的转移,数据库里面我们自然是只存文件路径了,mongodb键值对结构,所以我们定一个photo这样一个json字符串,包括文件名和文件路径。

 nodejs原本有一套mongodb的库来操作数据库,这里我使用mongoose这个库来开发,相对简单方便。

和传统关系型数据库一样,我们需要先把服务开开,windows下 在 开始 中输入 service 找到mongodb的服务并打开它。

代码中,第一步必然是打开数据库连接了。

.((err) {
    (err) {err}
    {
        console.()}
})

 指定数据库。回调中打好log.

接下来就增删改查直接写吗? 当然不是,为了方便数据操作和使用,mongoose推荐我们建立schema,schema理解为一个标准,也就是我们要定一个pojo用来描述我们要插入的对象及其类型。当我们定义好类型后,我们可以经这个类型赋值使其成为一个实例,真正的拥有数据。这一点可以类比Java中对象和类的概念,类就好比一个schema,对象就是一个具体的schema了。定义格式如下:

var photos = new mongoose.Schema({
    name:String,
    path:String
});
var Photo = mongoose.model("photo",photos)

这样Photo就是一个pojo.注意mongoose挺奇葩的,model中的photo我本以为是mongodb中定义的数据表名字,结果发现他去我表中创建了一个photos ,注意多了个 s .。也就是说你模板中起的名字对应你数据表名字都要加一个s,这个问题我试了很多遍确实是这样,不知道为何要如此设计。photos表示传入的数据,只有传入一个数据才能是一个可用的有数据的对象。

接下来操作mongodb无非就是CRUD了,上传文件的话就先写 增。

photo.save(function (err) {
    if (err){console.log(err);}
    else {console.log("data saved! ");fn()}
});

 data就是上面写的那个json,里面就是请求里面的文件信息。

photo就是Photo类的实例,photo这个对象是mongoose.model这个方法生成的模板类,也就带有数据库的curd方法。这一点和传统编程语言相差甚远,我们Java或C#中定义的pojo是不包括curd方法的,curd需要用dao或者DBUtil的insert update等等方法;而这里的pojo就是自带pojo的,其实自带的curd也是来自model方法,直接pojo.insert pojo.find即可。所以这里你会看到photo.save()这个方法。回调函数表示数据库存储完毕。

写一个get请求接受“/upload”显示上传界面。

router.get("/upload", function (req,res) {
  res.render("index");
});

render方法类似于java的springmvc框架 controller中方法的返回值,能映射到配置好的试图,这里写index,他就会去views路径下找名字为 index.ejs的文件,前面的引擎如果为html,那就回去找index.html  直截了当不多说。

末尾,app.listen(3000)开个端口试试。上传界面自己随便写写,访问localhost:3000/upload.

 

 4.业务逻辑之--------图片查看

既然上传会了,查看也就不难了,因为我们添加了静态资源路径,所以前台只需要读取/images/****.jpg就能看到了,但是为了看到所有的照片,还是要从数据库去除所有连接,反映到视图层遍历出来。那么这块就需要用到ejs模板技术了。ejs可以理解为 和jsp,asp 差不多的,方便视图层写业务逻辑的模板。

前面express已经设置了试图引擎为ejs,那么在views中建立一个文件叫show.ejs。

下面写一下模板中核心逻辑

 

<% photo.((data) {%>
<%=data.%><%=data.%><%=data.%><%})%>

photo是服务器传递到模板的对象,不管数据库有几个照片信息,我们都视为list,并在页面中遍历他,显示图片名和图片,并且将图片名做成超链接,点击后查看单张。

这里有一个问题了,photo是如何传递进来的呢?用jsp的时候我们都知道可以把数据放到session或者request中模板直接调用内置函数或对象获得数据,这里photo的传递方式稍微不一样,前面我们访问“index.ejs”的时候调用了render方法去视图层查找这个文件,其实这个方法不止一个参数,还有个参数就是用来设置对象的,这样视图层就能调用你传递的这个参数对象。

 

res.render("show",{photo:data});

如上,如果data是一个集合,而且json中格式遵循了path,name,就能被模板使用。

这里我们开一个get方法用来接收“/”根请求,每个人访问localhost:3000的时候都能看到相册中的相片。

这里也就需要用到photo对象(前面新建的)的find方法了

 

Photo.Photo.find({}, function (err,photos) {
    if(err){console.log(err);return {}}
    console.log("method find : "+photos);
    fn(photos);
});

这个时候我们是直接用了Photo对象,并没有新建实例,大概因为我们不需要调用者是一个实例,需要调用者调用这个方法找出一个实例而已。find的第一个参数是查询条件,这里为空,表示查出所有,大概接触过mongodb的人会知道db.find里面放的也是一个json表示查询方法。,回调函数的photos是查询结果,查询完小勇render找到视图层并把结果传导视图层。

这样我们访问以下localhost:3000就能看到所有图片了。

5.业务逻辑之--------查看单张

查看单张逻辑没啥特殊,就是需要在find中加条件而已,大家可以尝试findOne或者findById,其中findById应该是只需要传递id进去就好了,其他的要穿json表示你要查询什么。特别之处在于如果你是按照_id查询,你需要多做一步。因为mongodb中的_id类型不是String而是ObjectId,是用正则表达式判断这个id的格式的,正则如下:

/^[0-9a-fA-F]{24}$/

你还可以用一个插件 叫 objcetid 这个插件内置了这个正则,而且做了额外包装,能够让你判断一个字付出是不是这个格式的数据。因为在用id查询的时候,如果你的格式是错的,node直接异常抛出来了,所以你用这个插件先判断是不是objectid格式的,不是就肯定查不出数据,也省了走数据库的开销。

 

var objectid = require("objectid");

添加完依赖,然后看看怎么用的:

 

var _id = objectid.isValid(id)?id:objectid();

判断是不是objectid,不是随便生成一个objectid,mongodb既然用objectid做主键,说明基本不会重复,不考虑大并发的话,我们先这么用。

其他的操作和find的一样的就不重复了,需要强调的是,这里采用restful风格的请求,我们开一个get请求来查看某个照片:

 

router.get("/photo/:id", function (req, res) {....}

看到:id了吗?不奇怪,Springmvc中经常用,就是个占位符罢了,使用req.params.id就能获取到了。

 

5.业务逻辑之--------优化

照片上传查看到这里就结束了,剩下一点内容我想写的就是优化。因为长期受Java编程习惯的影响,我总是喜欢把操作分门别类,比如router.post("/upload") 我视其为控制器(node中成为路由),Photo类的建立我成为pojo或者model. photo.save这样的叫dao(或者db),文件存储和转移我成为util

那么我们就要把app.js这个文件一分为四了,app.js保留,作为webapp的入口,所有路由部分我们单独放到routers文件夹中,当道route.js内,照片对象和数据库访问和文件上传转移我们放到lib下,分别叫photo.js,db.js,util.js

先从简单的来,定义Photo的schema 如下:photo.js

/**
 * Created by Administrator on 2015/10/29.
 */
var mongoose = require("mongoose");
var photos = new mongoose.Schema({
    name:String,
    path:String
});
exports.Photo = mongoose.model("photo",photos);

这个类导出Phot对象,外部想用的时候,调用require的对象然后new就行,当然还要传一个json,

然后写一下db部分,看看他们如何依赖:

 

/**
 * Created by Administrator on 2015/10/31.
 */
var Photo =require("../lib/photo");
var objectid = require("objectid");
var mongoose = require("mongoose");
mongoose.connect("mongodb://127.0.0.1:27017/tonghang", function (err) {
    if (err) {throw err;}
    else {
        console.log("mongoDB connected ");
    }
});
exports.insert = function (data,fn) {
    var photo = new Photo.Photo(data);
    photo.save(function (err) {
        if (err){console.log(err);}
        else {console.log("data saved! ");fn()}
    });
};
exports.findById = function (id,fn) {
    var _id = objectid.isValid(id)?id:objectid();
    Photo.Photo.find({_id:_id}, function (err,photo) {
        if(err){console.log(err);}
        fn(photo);
    });
};
exports.find = function (fn) {
    Photo.Photo.find({}, function (err,photos) {
        if(err){console.log(err);return {}}
        console.log("method find : "+photos);
        fn(photos);
    });
}

正如大家所见,Photo对象是Photo模块中exports的副本,在db模块的save中需要先new出来才能存储。

这里我用了nodejs的回调机制(有点函数编程的感觉),导出的find函数需要一个回调函数,在查找完成之后才会执行回调。如果这里用回调,仅仅是导出函数的返回值,那么你肯定查不出数据,因为代码执行速度肯定比你访问数据库快,还没查出来你就render去了。。。。

和db直接相关的就是路由,路由将请求分发到不同的db操作,当然在大项目中还需要有个service来处理若干业务逻辑比如数据的拼接啊,计算啊等等,这里就先不写了:

var express = require('express');
var path = require("path");
var fs = require("fs");
var futil = require("../lib/futil");
var router = express.Router();
var Db = require("../lib/db");
/* GET home page. */
router.get("/", function (req,res,next) {
  Db.find(function (data) {
    res.render("show",{photo:data});
});
console.log("mongodb 查询");
});
router.get("/photo/:id", function (req, res) {
  Db.findById(req.params.id, function (photo) {
    res.render("show",{photo:photo});
  });
  console.log("mongodb 查询条件:"+req.params.id);
})
router.get("/upload", function (req,res) {
  res.render("index");
});
    /*POST Method*/
router.post("/upload", function (req,res) {
    var image_file = path.join(__dirname,"../public/images/") + req.files[0].originalname;
    console.log("images_file: "+image_file);
    futil.fileUpload(req.files[0].path,image_file,function () {
      var photo = {
        path:path.join("images/",req.files[0].originalname),
        name:req.files[0].originalname
      };
      Db.insert(photo, function (err) {
        if(err){
          res.render("error");
        }else{
          res.redirect("/");
        }
      });
    })
});
module.exports = router;

路由中集合了路由功能和业务逻辑处理,所以看起来很多,大家在正规开发中还是分一个service出来比较好,路由还是要遵循以简单为主,只用来接收数据或者将数据做简单的调整。一切db  util都在service中处理,service就好比是集线器一样,这样以后项目在扩展接口的时候看起来就会比较轻松了。

router模块再和文件上传工具耦合(其实还是要交给service).下面是futil.js:

/**
 * Created by Administrator on 2015/10/31.
 */
var path = require("path");
var fs = require("fs");
exports.fileUpload = function (originpath,newpath,fn) {
    fs.rename(originpath,newpath, function (err, data) {
        if(err){ throw err; }
        else{
            fn();
        }
    });
}

当然路由模块要导出Router实例,干什么用的?当然是挂载到app.js上,想想最开始讲的挂载,起作用就是将一个webapp中具备的功能(中间件这个词看似比较专业,但是初学者感觉不好理解,我的理解就是具有某些功能的模块)全部添加到express上(老实说express内部的细节我是确实不了解,建议大家学完express后看看connect,他是在connect的基础上包装的) ,我们的webapp需要支持/upload  /  等请求,所以将这个功能挂载到express上。  不多说,直接看代码:

exvar express = require("express");
var path = require("path");
var bodyParser = require("body-parser");
var multer = require("multer");
var router = require("./routes/router")
var cookieParser = require('cookie-parser');
var fs = require("fs");
var app = express();
app.use(express.static("public"));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(multer({dest: '/tmp/'}).array('image'));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', "ejs");
app.use(router);
app.listen(3000);
module.exports = app;

 着重看加粗的就是我上面说的挂载router了,这里的outer不再是express中的Router模块了,而是我们自己写的“个性化”的router了(个性化这个词我觉得不错)。

好了,运行下看看吧!我把我运行的截图发来:

162232_VA39_2320871.png

162233_kRkQ_2320871.png

162237_SF3M_2320871.png

分层完了以后的项目目录看起来还是蛮不错的:

162404_EEfD_2320871.png

 

转载于:https://my.oschina.net/rpgmakervx/blog/524341

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值