NodeJS 6.x 蓝图(二)
原文:
zh.annas-archive.org/md5/9B48011577F790A25E05CA5ABA4F9C8B译者:飞龙
第三章:构建多媒体应用程序
在 Node.js 应用程序中最常讨论的话题之一无疑是文件的加载和存储,无论是文本、图像、音频还是视频。也有许多方法可以做到这一点;我们不会深入技术细节,但会简要概述两种最重要的方法。
一种是直接以二进制格式保存文件在数据库中,另一种方式是直接将文件保存在服务器上(服务器的硬盘),或者简单地将文件存储在云中。
在本章中,我们将看到一种非常实用的方式,可以直接将文件上传到硬盘,并将文件名记录在我们的数据库中作为参考。这样,如果需要,我们可以使用可扩展的云存储服务。
在本章中,我们将涵盖以下主题:
-
如何将不同的文件上传到硬盘
-
如何使用 Stream API 读写文件
-
处理多部分表单上传
-
如何配置 Multer 模块将文件存储在本地机器上
-
如何获取文件类型并应用简单的文件验证
-
如何使用动态用户 gravatar 生成器
我们正在构建什么?
我们将构建一个使用 MongoDB 和 Mongoose 进行用户身份验证的上传图像和视频的应用程序;然后我们可以看到这些图像将成为我们工作的最终结果。
在这个例子中,我们将使用另一种方式开始我们的项目;这次我们将从package.json文件开始。
以下截图展示了我们最终应用程序的样子:
| 图像屏幕 | 视频屏幕 |
|---|---|
从 package.json 开始
正如我们在前几章中所解释的,packages.json文件是应用程序的核心。创建必要文件的步骤如下:
-
创建一个名为
chapter-03的文件夹。 -
创建一个名为
package.json的新文件,并将其保存在chapter-03文件夹中,包含以下代码:
{
"name": "chapter-03",
"description": "Build a multimedia Application with Node.js",
"license": "MIT",
"author": {
"name": "Fernando Monteiro",
"url": "https://github.com/newaeonweb/node-6-blueprints"
},
"repository": {
"type": "git",
"url": "https://github.com/newaeonweb/node-6-blueprints.git"
},
"keywords": [
"MVC",
"Express Application",
"Expressjs",
"Expressjs images upload",
"Expressjs video upload"
],
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node app.js"
},
"dependencies": {
"bcrypt-nodejs": "0.0.3",
"body-parser": "~1.13.2",
"connect-flash": "⁰.1.1",
"connect-mongo": "¹.1.0",
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"ejs": "~2.3.3",
"express": "~4.13.1",
"express-session": "¹.13.0",
"gravatar": "¹.4.0",
"mongoose": "⁴.4.5",
"morgan": "~1.6.1",
"multer": "¹.1.0",
"node-sass-middleware": "0.8.0",
"passport": "⁰.3.2",
"passport-local": "¹.0.0",
"serve-favicon": "~2.3.0"
},
"devDependencies": {
"nodemon": "¹.9.1"
}
}
添加基线配置文件
现在让我们向项目添加一些有用的文件:
- 创建一个名为
.editorconfig的文件,并将其保存在chapter-03文件夹中,包含以下代码:
# http://editorconfig.org
root = true
[*]
indent_style = tab
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
- 创建一个名为
.gitignore的文件,保存在chapter-03中,并包含以下代码:
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate
storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-
my-node_modules-folder-into-git-
node_modules
# Debug log from npm
npm-debug.log
提示
请记住,我们正在使用 git 作为源代码控制。虽然这个文件不是运行应用程序所必需的,但我们强烈建议您使用源代码版本控制系统。
- 创建一个名为
app.js的文件。
添加服务器文件夹
要完成应用程序的基本创建,我们现在将创建存储控件、模板和应用程序其他文件的目录:
- 创建一个名为
public的文件夹,并在其中创建以下文件夹:
-
/images -
/javascripts -
/stylesheets -
/uploads -
/视频
- 创建一个名为
server的文件夹,并在其中创建这些文件夹:
-
/config -
/controllers -
/models -
/views
-
此时,我们的项目已经具有了所有基本目录和文件;让我们从
package.json中安装 Node 模块。 -
在项目根目录打开您的终端/ shell,并输入以下命令:
npm install
在执行步骤 1、2 和 3 之后,项目文件夹将具有以下结构:
文件夹结构
让我们开始创建app.js文件内容。
配置 app.js 文件
我们将逐步创建app.js文件;它将与第一章中创建的应用程序有许多相似的部分。但是,在本章中,我们将使用不同的模块和不同的方式来创建应用程序控件。
在Node.js中使用 Express 框架,有不同的方法来实现相同的目标:
- 打开项目根目录下的
app.js文件,并添加以下模块:
// Import basic modules
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
// import multer
var multer = require('multer');
var upload = multer({ dest:'./public/uploads/', limits: {fileSize:
1000000, files:1} });
提示
请注意,我们正在使用 Multer 模块来处理multipart/form-data。您可以在github.com/expressjs/multer找到有关multer的更多信息。
- 在
multer导入之后添加以下行:
// Import home controller
var index = require('./server/controllers/index');
// Import login controller
var auth = require('./server/controllers/auth');
// Import comments controller
var comments = require('./server/controllers/comments');
// Import videos controller
var videos = require('./server/controllers/videos');
// Import images controller
var images = require('./server/controllers/images');
提示
不要担心这些控制文件。在本书的后面,我们会逐个看到它们。此时,我们将专注于创建app.js。
- 在
controller导入器之后添加以下行:
// ODM with Mongoose
var mongoose = require('mongoose');
// Modules to store session
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
// Import Passport and Warning flash modules
var passport = require('passport');
var flash = require('connect-flash');
// start express application in variable app.
var app = express();
上述代码设置了带有消息系统的用户会话,还使用 Passport 进行身份验证。
- 现在让我们设置模板引擎和与应用程序数据库的连接。在
express app变量之后添加以下代码:
// view engine setup
app.set('views', path.join(__dirname, 'server/views/pages'));
app.set('view engine', 'ejs');
// Database configuration
var config = require('./server/config/config.js');
// connect to our database
mongoose.connect(config.url);
// Check if MongoDB is running
mongoose.connection.on('error', function() {
console.error('MongoDB Connection Error. Make sure MongoDB is
running.');
});
// Passport configuration
require('./server/config/passport')(passport);
以下行设置了一些默认中间件并初始化了Passport-local和用户会话。
- 在上一个代码块之后添加以下行:
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(require('node-sass-middleware')({
src: path.join(__dirname, 'public'),
dest: path.join(__dirname, 'public'),
indentedSyntax: true,
sourceMap: true
}));
// Setup public directory
app.use(express.static(path.join(__dirname, 'public')));
// required for passport
// secret for session
app.use(session({
secret: 'sometextgohere',
saveUninitialized: true,
resave: true,
//store session on MongoDB using express-session + connect mongo
store: new MongoStore({
url: config.url,
collection : 'sessions'
})
}));
// Init passport authentication
app.use(passport.initialize());
// persistent login sessions
app.use(passport.session());
// flash messages
app.use(flash());
现在让我们添加所有应用程序路由。我们本可以使用外部文件来存储所有路由,但是我们将其保留在此文件中,因为我们的应用程序中不会有太多路由。
- 在
app.use(flash())之后添加以下代码:
// Application Routes
// Index Route
app.get('/', index.show);
app.get('/login', auth.signin);
app.post('/login', passport.authenticate('local-login', {
//Success go to Profile Page / Fail go to login page
successRedirect : '/profile',
failureRedirect : '/login',
failureFlash : true
}));
app.get('/signup', auth.signup);
app.post('/signup', passport.authenticate('local-signup', {
//Success go to Profile Page / Fail go to Signup page
successRedirect : '/profile',
failureRedirect : '/signup',
failureFlash : true
}));
app.get('/profile', auth.isLoggedIn, auth.profile);
// Logout Page
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
// Setup routes for comments
app.get('/comments', comments.hasAuthorization, comments.list);
app.post('/comments', comments.hasAuthorization, comments.create);
// Setup routes for videos
app.get('/videos', videos.hasAuthorization, videos.show);
app.post('/videos', videos.hasAuthorization, upload.single('video'),
videos.uploadVideo);
// Setup routes for images
app.post('/images', images.hasAuthorization, upload.single('image'),
images.uploadImage);
app.get('/images-gallery', images.hasAuthorization, images.show);
提示
在这里,您可以看到我们正在使用第一章中的示例应用程序相同的路由和函数,使用 MVC 设计模式构建类似 Twitter 的应用程序;我们保留了路由、身份验证和评论,并进行了一些小的更改。
要采取的最后一步是添加error函数并配置我们的应用程序将使用的服务器端口。
- 在上一个代码块之后添加以下代码:
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
app.set('port', process.env.PORT || 3000);
var server = app.listen(app.get('port'), function() {
console.log('Express server listening on port ' +
server.address().port);
});
创建 config.js 文件
在server/config/文件夹内创建一个名为config.js的文件,并添加以下代码:
// Database URL
module.exports = {
'url' : 'mongodb://localhost/mvc-app-multimedia'
};
在server/config/文件夹内创建一个名为passport.js的文件。
在这一步中,我们将使用第一章中使用的Passport模块的相同配置文件,在 Node.js 中使用 MVC 设计模式构建类似 Twitter 的应用程序。您可以从www.packtpub.com或从 GitHub 的官方存储库下载示例代码。
创建控制器文件
现在让我们在server/controllers中创建控制器文件:
- 创建一个名为
auth.js的文件,并添加以下代码:
// get gravatar icon from email
var gravatar = require('gravatar');
var passport = require('passport');
// Signin GET
exports.signin = function(req, res) {
// List all Users and sort by Date
res.render('login', { title: 'Login Page', message:
req.flash('loginMessage') });
};
// Signup GET
exports.signup = function(req, res) {
// List all Users and sort by Date
res.render('signup', { title: 'Signup Page', message:
req.flash('signupMessage') });
};
// Profile GET
exports.profile = function(req, res) {
// List all Users and sort by Date
res.render('profile', { title: 'Profile Page', user : req.user,
avatar:gravatar.url(req.user.email , {s: '100', r: 'x', d: 'retro'},
true) });
};
// Logout function
exports.logout = function () {
req.logout();
res.redirect('/');
};
// check if user is logged in
exports.isLoggedIn = function(req, res, next) {
if (req.isAuthenticated())
return next();
res.redirect('/login');
};
- 创建一个名为
comments.js的文件,并添加以下代码:
// get gravatar icon from email
var gravatar = require('gravatar');
// get Comments model
var Comments = require('../models/comments');
// List Comments
exports.list = function(req, res) {
// List all comments and sort by Date
Comments.find().sort('-created').populate('user',
'local.email').exec(function(error, comments) {
if (error) {
returnres.send(400, {
message: error
});
}
// Render result
res.render('comments', {
title: 'Comments Page',
comments: comments,
gravatar: gravatar.url(comments.email , {s: '80', r: 'x', d:
'retro'}, true)
});
});
};
// Create Comments
exports.create = function(req, res) {
// create a new instance of the Comments model with request body
var comments = new Comments(req.body);
// Set current user (id)
comments.user = req.user;
// save the data received
comments.save(function(error) {
if (error) {
returnres.send(400, {
message: error
});
}
// Redirect to comments
res.redirect('/comments');
});
};
// Comments authorization middleware
exports.hasAuthorization = function(req, res, next) {
if (req.isAuthenticated())
return next();
res.redirect('/login');
};
- 创建一个名为
index.js的文件,并添加以下代码:
// Show home screen
exports.show = function(req, res) {
// Render home screen
res.render('index', {
title: 'Multimedia Application',
callToAction: 'An easy way to upload and manipulate files
with Node.js'
});
};
现在让我们创建控制器来处理图像和视频上传到我们的应用程序。我们将使用 Node.js API 流来读取和写入我们的文件。
提示
您可以在nodejs.org/api/stream.html找到此 API 的完整文档。
-
创建一个名为
images.js的文件。 -
添加以下代码:
// Import modules
var fs = require('fs');
var mime = require('mime');
// get gravatar icon from email
var gravatar = require('gravatar');
var Images = require('../models/images');
// set image file types
var IMAGE_TYPES = ['image/jpeg','image/jpg', 'image/png'];
- 在
importer模块之后添加以下代码:
// Show images gallery
exports.show = function (req, res) {
Images.find().sort('-created').populate('user',
'local.email').exec(function(error, images) {
if (error) {
returnres.status(400).send({
message: error
});
}
// Render galley
res.render('images-gallery', {
title: 'Images Gallery',
images: images,
gravatar: gravatar.url(images.email , {s: '80', r: 'x', d: 'retro'},
true)
});
});
};
现在让我们添加负责将文件保存到临时目录并将其传输到public文件夹中的目录的函数。
- 在上一个代码块之后添加以下行:
// Image upload
exports.uploadImage = function(req, res) {
var src;
var dest;
var targetPath;
var targetName;
var tempPath = req.file.path;
console.log(req.file);
//get the mime type of the file
var type = mime.lookup(req.file.mimetype);
// get file extension
var extension = req.file.path.split(/[. ]+/).pop();
// check support file types
if (IMAGE_TYPES.indexOf(type) == -1) {
returnres.status(415).send('Supported image formats: jpeg, jpg,
jpe, png.');
}
// Set new path to images
targetPath = './public/images/' + req.file.originalname;
// using read stream API to read file
src = fs.createReadStream(tempPath);
// using a write stream API to write file
dest = fs.createWriteStream(targetPath);
src.pipe(dest);
// Show error
src.on('error', function(err) {
if (err) {
returnres.status(500).send({
message: error
});
}
});
// Save file process
src.on('end', function() {
// create a new instance of the Images model with request body
var image = new Images(req.body);
// Set the image file name
image.imageName = req.file.originalname;
// Set current user (id)
image.user = req.user;
// save the data received
image.save(function(error) {
if (error) {
return res.status(400).send({
message: error
});
}
});
// remove from temp folder
fs.unlink(tempPath, function(err) {
if (err) {
return res.status(500).send('Woh, something bad happened here');
}
// Redirect to galley's page
res.redirect('images-gallery');
});
});
};
添加检查用户是否经过身份验证并被授权插入图像的函数。
- 在文件末尾添加以下代码:
// Images authorization middleware
exports.hasAuthorization = function(req, res, next) {
if (req.isAuthenticated())
return next();
res.redirect('/login');
};
- 现在让我们重复这个过程来控制
videos.js,然后创建一个名为videos.js的文件,并添加以下代码:
// Import modules
var fs = require('fs');
var mime = require('mime');
// get gravatar icon from email
var gravatar = require('gravatar');
// get video model
var Videos = require('../models/videos');
// set image file types
var VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/ogg',
'video/ogv'];
// List Videos
exports.show = function(req, res) {
Videos.find().sort('-created').populate('user',
'local.email').exec(function(error, videos) {
if (error) {
return res.status(400).send({
message: error
});
}
// Render result
console.log(videos);
res.render('videos', {
title: 'Videos Page',
videos: videos,
gravatar: gravatar.url(videos.email , {s: '80', r: 'x', d: 'retro'},
true)
});
});
};
// Create Videos
exports.uploadVideo = function(req, res) {
var src;
var dest;
var targetPath;
var targetName;
console.log(req);
var tempPath = req.file.path;
//get the mime type of the file
var type = mime.lookup(req.file.mimetype);
// get file extenstion
var extension = req.file.path.split(/[. ]+/).pop();
// check support file types
if (VIDEO_TYPES.indexOf(type) == -1) {
return res.status(415).send('Supported video formats: mp4, webm, ogg,
ogv');
}
// Set new path to images
targetPath = './public/videos/' + req.file.originalname;
// using read stream API to read file
src = fs.createReadStream(tempPath);
// using a write stream API to write file
dest = fs.createWriteStream(targetPath);
src.pipe(dest);
// Show error
src.on('error', function(error) {
if (error) {
return res.status(500).send({
message: error
});
}
});
// Save file process
src.on('end', function() {
// create a new instance of the Video model with request body
var video = new Videos(req.body);
// Set the video file name
video.videoName = req.file.originalname;
// Set current user (id)
video.user = req.user;
// save the data received
video.save(function(error) {
if (error) {
return res.status(400).send({
message: error
});
}
});
// remove from temp folder
fs.unlink(tempPath, function(err) {
if (err) {
return res.status(500).send({
message: error
});
}
// Redirect to galley's page
res.redirect('videos');
});
});
};
// Videos authorization middleware
exports.hasAuthorization = function(req, res, next) {
if (req.isAuthenticated())
return next();
res.redirect('/login');
};
如您所见,我们使用与图像控制器相同的概念来创建视频控制器。
由于流,Node.js API 可以使用createReadStream()和createWriteStream()函数处理任何类型的文件。
创建模型文件
现在让我们创建应用程序的模板文件。由于我们在第一章中使用了 mongoose 中间件,在 Node.js 中使用 MVC 设计模式构建类似 Twitter 的应用程序,我们将保持相同类型的配置:
- 在
server/models文件夹内创建一个名为comments.js的文件,并添加以下代码:
// load the things we need
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var commentSchema = mongoose.Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
content: {
type: String,
default: '',
trim: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
module.exports = mongoose.model('Comments', commentSchema);
- 在
server/models文件夹内创建一个名为user.js的文件,并添加以下代码:
// Import Mongoose and password Encrypt
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
// define the schema for User model
var userSchema = mongoose.Schema({
// Using local for Local Strategy Passport
local: {
name: String,
email: String,
password: String,
}
});
// Encrypt Password
userSchema.methods.generateHash = function(password) { return
bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
// Verify if password is valid
userSchema.methods.validPassword = function(password) { return
bcrypt.compareSync(password, this.local.password);
};
// create the model for users and expose it to our app
module.exports = mongoose.model('User', userSchema);
- 然后,在
server/models文件夹内创建一个名为images.js的文件,并添加以下代码:
// load the things we need
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var imagesSchema = mongoose.Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
imageName: {
type: String
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
module.exports = mongoose.model('Images', imagesSchema);
- 接下来,在
server/models文件夹内创建一个名为videos.js的文件,并添加以下代码:
// load the things we need
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var videosSchema = mongoose.Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
videoName: {
type: String
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
module.exports = mongoose.model('Videos', videosSchema);
创建视图文件
在本节中,我们将使用与第一章中相同的view文件来处理以下文件:
-
views/partials/javascripts.ejs -
views/partials/stylesheets.ejs -
views/pages/login.ejs -
views/pages/signup.ejs -
views/pages/profile.ejs -
views/pages/index.ejs -
views/pages/comments.js -
views/pages/error.ejs
如前所述,您可以从 Packt 网站或本书的官方 GitHub 存储库下载这些文件。
除了这些文件之外,我们将为照片和视频页面创建views文件,并将这些路由添加到应用程序菜单中:
- 在
views/partials文件夹内创建一个名为footer.ejs的文件,并添加以下代码:
<footer class="footer">
<div class="container">
<span>© 2016\. Node-Express-MVC-Multimedia-App</span>
</div>
</footer>
- 然后在
views/partials文件夹内创建一个名为header.ejs的文件,并添加以下代码:
<!-- Fixed navbar -->
<div class="pos-f-t">
<div class="collapse" id="navbar-header">
<div class="container bg-inverse p-a-1">
<h3>Collapsed content</h3>
<p>Toggleable via the navbar brand.</p>
</div>
</div>
<nav class="navbarnavbar-light navbar-static-top">
<div class="container">
<button class="navbar-toggler hidden-sm-up" type="button"
data-toggle="collapse" data-target="#exCollapsingNavbar2">
Menu
</button>
<div class="collapse navbar-toggleable-xs"
id="exCollapsingNavbar2">
<a class="navbar-brand" href="/">MVC Multimedia App</a>
<ul class="navnavbar-navnavbar-right">
<li class="nav-item">
<a class="nav-link" href="/login">Sign in</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/signup">Sign up</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/profile">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/comments">Comments</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/videos">Videos</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/images-gallery">Photos</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
<!-- Fixed navbar -->
- 在
views/pages文件夹内创建一个名为images-gallery.ejs的文件,并添加以下代码:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<% include ../partials/stylesheet %>
</head>
<body>
<% include ../partials/header %>
<div class="container">
<div class="row">
<div class="col-lg-6">
<h4 class="text-muted">Images</h4>
</div>
<div class="col-lg-6">
<button type="button" class="btnbtn-secondary pull-right"
data-toggle="modal" data-target="#createImage">
Image Upload
</button>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="createImage" tabindex="-1"
role="dialog" aria-labelledby="myModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<form action="/images" method="post"
enctype="multipart/formdata">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="myModalLabel">
Upload a imagefile
</h4>
</div>
<div class="modal-body">
<fieldset class="form-group">
<label for="itle">Title</label>
<input type="text" id="itle" name="title" class="form-
control" placeholder="Image Title" required="">
</fieldset>
<label class="file" style="width: 100%">
<input type="file" id="image" name="image">
<span class="file-custom"></span>
</label>
</div>
<div class="modal-footer">
<button type="button" class="btnbtnsecondary" data
dismiss="modal">Close
</button>
<button type="submit" class="btnbtn-primary">
Savechanges
</button>
</div>
</form>
</div>
</div>
</div>
<hr>
<div class="row">
<% images.forEach(function(images){ %>
<div class="col-lg-4">
<figure class="figure">
<img src="img/<%= images.imageName %>" class="figure-img
img-fluid img-rounded" alt="<%= images.imageName %>">
<figcaption class="figure-caption"><%= images.title%>
</figcaption>
<small>Upload by: <%= images.user.local.email %></small>
</figure>
</div>
<% }); %>
</div>
</div>
<% include ../partials/footer %>
<% include ../partials/javascript %>
</body>
</html>
上述代码设置了一个表单 HTML 标签,使用enctype="multipart/form-data类型,并创建一个循环来显示添加到图库中的所有图像。
- 在
views/pages文件夹内创建一个名为videos.ejs的文件,并添加以下代码:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<% include ../partials/stylesheet %>
</head>
<body>
<% include ../partials/header %>
<div class="container">
<div class="row">
<div class="col-lg-6">
<h4 class="text-muted">Videos</h4>
</div>
<div class="col-lg-6">
<button type="button" class="btn btn-secondary pull-right"
data-toggle="modal" data-target="#createVideo">
Video Upload
</button>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="createVideo" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<form action="/videos" method="post" enctype="multipart/form-data">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="myModalLabel">Upload a video file</h4>
</div>
<div class="modal-body">
<fieldset class="form-group">
<label for="inputitle">Title</label>
<input type="text" id="inputitle" name="title" class="form-control"
placeholder="Video Title" required="">
</fieldset>
<label class="file" style="width: 100%"
onclick="$('input[id=lefile]').click();">
<input type="file" id="video" name="video">
<span class="file-custom"></span>
</label>
</div>
<div class="modal-footer">
<button type="button" class="btnbtn-secondary"
data-dismiss="modal">Close</button>
<button type="submit" class="btnbtn-primary">Save changes</button>
</div>
</form>
</div>
</div>
</div>
<hr>
<div class="row">
<% videos.forEach(function(videos){ %>
<div class="col-lg-4">
<h4 class="list-group-item-heading"><%= videos.title %></h4>
<video width="320" height="240" controls preload="auto"
codecs="avc1.42E01E, mp4a.40.2">
<source src="img/<%= videos.videoName %>" type="video/mp4" />
</video>
<small>Upload by: <%= videos.user.local.email %></small>
</div>
<% }); %>
</div>
</div>
<% include ../partials/footer %>
<% include ../partials/javascript %>
</body>
</html>
创建 public 文件夹内容
在这个阶段,我们已经完成了在server目录中创建文件夹和文件的所有必要步骤,如控制器、模型和视图。
现在我们需要复制在第一章中创建的public文件夹中的内容:
- 复制以下文件夹及其内容,并将它们粘贴到
chapter-03根项目文件夹中:
-
public/images -
public/javascripts -
bootstrap.min.js -
jquery.min.js -
public/stylesheets -
bootstrap.min.css -
style.css -
style.css.map -
style.sass
-
在
public文件夹内创建一个名为uploads的文件夹。 -
然后,在
public文件夹内创建一个名为videos的文件夹。
使用上传表单在应用程序中插入图像
现在是测试应用程序的时候了,需要注意的是,为此您应该启动您的 MongoDB。否则,应用程序在连接时会返回失败错误:
- 在项目根目录打开您的终端/Shell,并输入以下命令:
npm start
- 转到
http://localhost:3000/signup并输入以下数据:
-
姓名:约翰
-
电子邮件:john@doe.com
-
密码:123
- 转到
http://localhost:3000/images-gallery,点击图像上传按钮,填写表单标题并选择图像(请注意,我们设置了图像大小限制为1MB,仅用于示例目的)。您将看到一个模型表单,如下截图所示:
图像上传表单
- 选择图像后,点击保存更改按钮,完成!您将在
http://localhost:3000/images-gallery页面看到以下截图:
图库图像屏幕
使用上传表单在应用程序中插入视频文件
与插入图像到我们的应用程序一样,让我们按照相同的步骤来插入视频:
- 转到
http://localhost:3000/videos,点击视频上传按钮,填写表单标题并选择视频文件(请注意,我们再次将图像大小限制设置为 1MB,视频格式设置为MP4, WEBM,仅用于示例目的)。您将看到一个模型表单,如下截图所示:
视频上传表单
关于图像和视频上传的重要说明
Node.js 为我们提供了一个完整的处理文件的 API(流 API),如图像、视频、pdf 和其他格式。还有几种将文件上传到服务器或存储在云中的方法,正如前面已经提到的。此外,Node.js 生态系统为我们提供了许多模块,用于处理不同类型的文件,并使用带有enctype="multipart/form-data"的表单。
在本章中,我们使用了Multer模块。Multer 是一个完整的中间件,用于处理各种上传和文件存储的方法。
在这个例子中,我们只在 MongoDB 中存储了文件名,并直接将文件发送到服务器文件夹。还有另一种上传方式,我们将文件以二进制格式发送到数据库,尽管重要的是要记住 MongoDB 存储BSON文件的容量为16MB。
如果您选择将文件存储在 MongoDB 数据库中,您可以使用 MongoDB 的 GridFS 功能和 Node.js 模块,如GridFS-stream,作为上传中间件,就像我们使用 Multer 一样。
在本章的示例中,我们将上传大小限制为 1 MB,在下面突出显示的行中可以看到:
var upload = multer({
dest:'./public/uploads/',
limits: {fileSize: 1000000, files:1}
});
提示
您可以在 Multer 的官方文档中找到有关限制的更多信息,网址为github.com/expressjs/multer#limits。
总结
在本章中,我们构建了一个完整的 Node MVC 应用程序,用于上传图像和视频文件;我们还设置了一个用户会话,使用电子邮件和密码进行身份验证,并将加密密码存储在 MongoDB 上。
此外,本章使您能够构建模块化、健壮和可扩展的 MVC 应用程序。
此外,我们还使用了在 Web 应用程序中非常常见的功能,如访问数据库、登录和注册以及文件上传。
在下一章中,我们将看到如何使用云服务操纵和上传图像,并将元数据存储在 MongoDB 中。
第四章:不要拍照,制作它-摄影师的应用程序
在本章中,我们将讨论 Node.js 社区在全球范围内广泛讨论的一个话题-使用 Node.js 架构和云进行图像处理。
正如我们在上一章中看到的,我们有两种方式来存储图像和文件,一种是在服务器上使用硬盘,另一种是直接存储到云中。在第三章中,构建多媒体应用程序,我们使用了直接上传到服务器的方法,将图像存储在硬盘上。
在本章中,我们将使用云中的服务器来存储和处理我们相册应用程序中的图像。
我们将使用一个名为 Cloudinary 的服务来存储和处理图像。
在本章中,我们将涵盖以下主题:
-
如何使用 generator-express 设置 MVC 应用程序
-
如何安装 cloudinary npm 模块
-
实现 Materialize.css 框架
-
如何上传图像到云并在 MongoDB 上保存元数据
-
如何使用点文件中的全局变量
-
设置 Cloudinary 帐户并创建文件夹
-
如何使用 Cloudinary API 上传图像
-
如何使用 Cloudinary API 中的 URL 参数呈现模板
我们正在构建的内容
在本章结束时,我们将创建以下示例应用程序,一个强大而可扩展的相册:
相册应用程序的主屏幕
创建基线应用程序
在本章中,我们将使用稍微修改过的express-generator的版本,这是我们在之前章节中使用的。
这个生成器叫做generator-express;它在express generator的基础上进行了大量的改进。
以下是我们安装的步骤:
- 打开终端/ shell 并输入:
npm install -g generator-express
-
创建一个名为
chapter04的文件夹。 -
在
chapter04文件夹中打开您的终端/ shell,并输入以下命令:
yo express
现在,按照以下顺序填写问题:
-
选择
N,我们已经在步骤 2中创建了项目文件夹 -
选择
MVC作为应用程序类型 -
选择
Swig作为模板引擎 -
选择
None作为 CSS 预处理器 -
选择
None作为数据库(在本章后面,我们将手动设置数据库) -
选择
Gulp进行 LiveReload 和其他内容
提示
不要担心Gulp,如果你从未听说过它。在本书的后面,我们将看到并解释一些构建工具。
在生成器的最后,我们有以下目录结构:
应用程序文件夹结构
更改应用程序结构
与我们在第一章中使用的示例不同,使用 MVC 设计模式构建类似 Twitter 的应用程序,我们不会对当前的结构进行重大更改;我们只会更改views文件夹。
作为示例应用程序,将有一本图片书;我们将在views文件夹中添加一个名为 book 的文件夹:
-
在
app/views文件夹中创建一个名为book的文件夹。 -
现在我们将为 Cloudinary 服务创建一个配置文件。在本章后面,我们将讨论有关 Cloudinary 的所有细节;现在,只需创建一个新文件。
-
在根文件夹中创建一个名为
.env的文件。
现在,我们有必要的基础来继续前进。
添加处理图像和 Cloudinary 云服务的 Node 模块
现在我们将在package.json文件中为我们的应用程序添加必要的模块。
- 将以下突出显示的代码行添加到
package.json文件中:
{
"name": "chapter-04",
"description": "Don't take a photograph, make it - An app for
photographers",
"license": "MIT",
"author": {
"name": "Fernando Monteiro",
"url": "https://github.com/newaeonweb/node-6-blueprints"
},
"repository": {
"type": "git",
"url": "https://github.com/newaeonweb/node-6-blueprints.git"
},
"keywords": [
"MVC",
"Express Application",
"Expressjs",
"Expressjs cloud upload",
"Expressjs cloudinary upload"
],
"version": "0.0.1",
"private": true,
"scripts": {
"start": "gulp"
},
"dependencies": {
"body-parser": "¹.13.3",
"cloudinary": "¹.3.1",
"compression": "¹.5.2",
"connect-multiparty": "².0.0",
"cookie-parser": "¹.3.3",
"dotenv": "².0.0",
"express": "⁴.13.3",
"glob": "⁶.0.4",
"jugglingdb": "².0.0-rc3",
"jugglingdb-mongodb": "⁰.1.1",
"method-override": "².3.0",
"morgan": "¹.6.1",
"serve-favicon": "².3.0",
"swig": "¹.4.2"
},
"devDependencies": {
"gulp": "³.9.0",
"gulp-nodemon": "².0.2",
"gulp-livereload": "³.8.0",
"gulp-plumber": "¹.0.0"
}
}
只需几个模块,我们就可以构建一个非常强大和可扩展的应用程序。让我们描述每一个:
| 模块名称 | 描述 | 更多信息 |
|---|---|---|
cloudinary | 用于存储和管道图像和视频文件的云服务 | www.npmjs.com/package/cloudinary |
connect-multiparty | 用于接受多部分表单上传的中间件 | www.npmjs.com/package/connect-multiparty |
dotenv | 加载环境变量 | www.npmjs.com/package/dotenv |
jugglingdb | 跨数据库 ORM | www.npmjs.com/package/jugglingdb |
jugglingdb-mongodb | MongoDB 连接器 | www.npmjs.com/package/jugglingdb-mongodb |
创建书籍控制器
我们将遵循生成器建议的相同生成器代码模式;使用此生成器的优势之一是我们已经可以使用 MVC 模式。
提示
请记住,您可以从 Packpub 网站或直接从 GitHub 书库下载示例文件。
-
在
controllers文件夹中创建一个名为books.js的文件。 -
将以下代码添加到
book.js文件中:
var express = require('express'),
router = express.Router(),
schema = require('../models/book'),
Picture = schema.models.Picture,
cloudinary = require('cloudinary').v2,
fs = require('fs'),
multipart = require('connect-multiparty'),
multipartMiddleware = multipart();
module.exports = function (app) {
app.use('/', router);
};
// Get pictures list
router.get('/books', function (req, res, next) {
Picture.all().then(function (photos) {
console.log(photos);
res.render('book/books', {
title: 'PhotoBook',
photos: photos,
cloudinary: cloudinary
})
});
});
// Get form upload
router.get('/books/add', function (req, res, next) {
res.render('book/add-photo', {
title: 'Upload Picture'
});
});
// Post to
router.post('/books/add', multipartMiddleware, function (req, res,
next)
{
// Checking the file received
console.log(req.files);
// create a new instance using Picture Model
var photo = new Picture(req.body);
// Get temp file path
var imageFile = req.files.image.path;
// Upload file to Cloudinary
cloudinary.uploader.upload(imageFile, {
tags: 'photobook',
folder: req.body.category + '/',
public_id: req.files.image.originalFilename
// eager: {
// width: 280, height: 200, crop: "fill", gravity: "face"
// }
})
.then(function (image) {
console.log('Picture uploaded to Cloudinary');
// Check the image Json file
console.dir(image);
// Added image informations to picture model
photo.image = image;
// Save photo with image metadata
return photo.save();
})
.then(function (photo) {
console.log('Successfully saved');
// Remove image from local folder
var filePath = req.files.image.path;
fs.unlinkSync(filePath);
})
.finally(function () {
// Show the result with image file
res.render('book/posted-photo', {
title: 'Upload with Success',
photo: photo,
upload: photo.image
});
});
});
让我们解释一些关于前面代码示例的重要点:
- 为了在视图中使用 Cloudinary API,我们需要将
cloudinary变量传递给我们的视图:
res.render('book/books', {
title: 'PhotoBook',
photos: photos,
cloudinary: cloudinary
})
- 在使用
multipartMiddleware时,为了最佳实践,我们需要清理上传到云中的每个文件:
.then(function (photo) {
console.log('Successfully saved');
// Remove image from local folder
var filePath = req.files.image.path;
fs.unlinkSync(filePath);
})
稍后我们将讨论更多关于 Cloudinary API 的内容。
提示
请注意,当您使用多部分连接时,默认情况下会将图像加载到硬盘上的文件夹中,因此您应该始终删除应用程序中加载的所有文件。
创建书籍模型文件
为此应用程序创建模型的过程与我们在前几章中看到的非常相似;几乎每个模块的ORM/ODM都有非常相似的操作。
让我们看看如何为书籍对象创建模型:
- 在
app/models文件夹中创建一个名为book.js的文件,并放入以下代码:
var Schema = require('jugglingdb').Schema;
// Pay attention, we are using MongoDB for this example.
var schema = new Schema('mongodb', {url: 'mongodb://localhost
/photobookapp'});
// Setup Books Schema
var Picture = schema.define('Picture', {
title : { type: String, length: 255 },
description: {type: Schema.Text},
category: {type: String, length: 255 },
image : { type: JSON}
});
module.exports = schema;
提示
请注意,我们使用 MongoDB 来存储书籍模型。还记得在启动应用程序之前必须使本地 MongoDB 运行起来。
向应用程序添加 CSS 框架
在本书的所有章节中,我们将始终使用最新的技术,就像在前几章中使用新的 Bootstrap(Alpha Release)一样。
特别是在本章中,我们将使用一种称为Material Design的设计模式。
提示
您可以在www.google.com/design/spec/material-design/introduction.html上阅读更多关于设计材料的信息。
为此,我们将使用一个名为Materialize.css的简单CSS框架。
提示
您可以在此链接找到有关 Materialize 的更多信息:materializecss.com/。
- 用以下代码替换
app/views/layout.swig文件中的所有内容:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>{{ title }}</title>
<!--Let browser know website is optimized for mobile-->
<meta name="viewport" content="width=device-width, initial-
scale=1.0"/>
<!-- Import Google Material font and icons -->
<link href="https://fonts.googleapis.com/icon?family=
Material+Icons" rel="stylesheet">
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/
ajax/libs/materialize/0.97.6/css/materialize.min.css">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<nav class="orange darken-4" role="navigation">
<div class="nav-wrapper container"><a id="logo-container"
href="/" class="brand-logo">Logo</a>
<ul class="right hide-on-med-and-down">
<li><a href="/books">Books</a></li>
<li><a href="/books/add">Add Picture</a></li>
</ul>
<ul id="nav-mobile" class="side-nav" style="transform:
translateX(-100%);">
<li><a href="/books">Books</a></li>
<li><a href="/books/add">Add Picture</a></li>
</ul>
<a href="#" data-activates="nav-mobile" class="button-
collapse">
<i class="material-icons">menu</i></a>
</div>
</nav>
{% block content %}{% endblock %}
<!-- Footer -->
<footer class="page-footer orange darken-4">
<div class="container">
<div class="row">
<div class="col l6 s12">
<h5 class="white-text">Some Text Example</h5>
<p class="grey-text text-lighten-4">Lorem ipsum
dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et
dolore magnaaliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur.</p>
</div>
<div class="col l3 s12">
<h5 class="white-text">Sample Links</h5>
<ul>
<li><a class="white-text" href="#!">Link 1
</a></li>
<li><a class="white-text" href="#!">Link 2
</a></li>
<li><a class="white-text" href="#!">Link 3
</a></li>
<li><a class="white-text" href="#!">Link 4
</a></li>
</ul>
</div>
<div class="col l3 s12">
<h5 class="white-text">Sample Links</h5>
<ul>
<li><a class="white-text" href="#!">Link 1
</a></li>
<li><a class="white-text" href="#!">Link 2
</a></li>
<li><a class="white-text" href="#!">Link 3
</a></li>
<li><a class="white-text" href="#!">Link 4
</a></li>
</ul>
</div>
</div>
</div>
<div class="footer-copyright">
<div class="container">
MVC Express App for: <a class="white-text text-darken-2"
href="#">Node.js 6 Blueprints Book</a>
</div>
</div>
</footer>
<!-- Place scripts at the bottom page-->
{% if ENV_DEVELOPMENT %}
<script src="img/livereload.js"></script>
{% endif %}
<!--Import jQuery before materialize.js-->
<script type="text/javascript"
src="img/jquery-2.1.1.min.js"></script>
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/
ajax/libs/materialize/0.97.6/js/materialize.min.js"></script>
<!-- Init Rsponsive Sidenav Menu -->
<script>
(function($){
$(function(){
$('.button-collapse').sideNav();
$('.materialboxed').materialbox();
});
})(jQuery);
</script>
</body>
</html>
提示
为了避免 CSS 冲突,请清理您的public/css/style.css文件。
重构视图文件夹
现在我们将对app/views文件夹进行一些小改动并添加一些文件:
- 首先,让我们编辑
app/views/index.js。用以下代码替换原始代码:
{% extends 'layout.swig' %}
{% block content %}
<div class="section no-pad-bot" id="index-banner">
<div class="container">
<br><br>
<h1 class="header center orange-text">{{ title }}</h1>
<div class="row center">
<h5 class="header col s12 light">Welcome to
{{ title }}
</h5>
</div>
<div class="row center">
<a href="books/add" id="download-button" class="btn-large
waves-effect waves-light orange">Create Your Photo
Book
</a>
</div>
<br><br>
</div>
</div>
<div class="container">
<div class="section">
<!-- Icon Section -->
<div class="row">
<div class="col s12 m4">
<div class="icon-block">
<h5 class="center">Animals</h5>
<img src="img/animals"/>
<p class="light">Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut laboreet dolore magna-
aliqua.</p>
</div>
</div>
<div class="col s12 m4">
<div class="icon-block">
<h5 class="center">Cities</h5>
<img src="img/city"/>
<p class="light">Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut laboreet dolore magna-
aliqua.</p>
</div>
</div>
<div class="col s12 m4">
<div class="icon-block">
<h5 class="center">Nature</h5>
<img src="img/nature"/>
<p class="light">Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut laboreet dolore magna-
aliqua..</p>
</div>
</div>
</div>
</div>
<br><br>
<div class="section">
</div>
</div>
{% endblock %}
- 创建一个名为
add-photo.swig的文件,并添加以下代码:
{% extends '../layout.swig' %}
{% block content %}
<div class="section no-pad-bot" id="index-banner">
<div class="container">
<br>
<br>
<h1 class="header center orange-text">{{ title }}</h1>
<div class="row center">
<h5 class="header col s12 light">Welcome to
{{ title }}</h5>
</div>
<div class="photo">
<h2>{{ photo.title }}</h2>
{% if photo.image.url %}
<img src="img/{{ photo.image.url }}" height='200' width='100%'>
</img>
<a href="{{ photo.image.url }}" target="_blank">
{{ cloudinary.image(photo.image.public_id, {width: 150,
height: 150, quality:80,crop:'fill',format:'png',
class:'thumbnail inline'}) }}
</a>
{% endif %}
</div>
<div class="card">
<div class="card-content orange-text">
<form action="/books/add" enctype="multipart/form-data"
method="POST">
<div class="input-field">
<input id="title" name="title" type="text"
value="{{ photo.title }}" class="validate">
<label for="title">Image Title</label>
</div>
<div class="file-field input-field">
<div class="btn orange">
<span>Choose File</span>
<input type="file" name="image">
<input id="photo_image_cache"
name="image_cache" type="hidden" />
</div>
<div class="file-path-wrapper">
<input class="file-path validate"
type="text">
</div>
</div>
<div class="input-field col s12">
<select class="browser-default" id="category"
name="category">
<option value="" disabled selected>Choose a
category</option>
<option value="animals">Animals</option>
<option value="cities">Cities</option>
<option value="nature">Nature</option>
</select>
</div>
<div class="input-field">
<input id="description" name="description"
type="text" value="{{ photo.description }}"
class="validate">
<label for="description">Image Text
Description</label>
</div>
<br>
<br>
<div class="row center">
<button class="btn orange waves-effect waves
light" type="submit" name="action">
Submit
</button>
</div>
</form>
</div>
</div>
<br>
<br>
<br>
</div>
</div>
{% endblock %}
- 然后创建一个名为
books.swig的文件,并添加以下代码:
{% extends '../layout.swig' %}
{% block content %}
<div class="section no-pad-bot" id="index-banner">
<div class="container">
<br><br>
<h1 class="header center orange-text">{{ title }}</h1>
<div class="row center">
< h5 class="header col s12 light">Welcome to {{ title }}
</h5>
</div>
<br><br>
{% if photos.length == 0 %}
<div class="row center">
<div class="card-panel orange lighten-2">No photos yet,
click add picture to upload</div>
</div>
{% endif %}
<div class="row">
{% for item in photos %}
<div class="col s12 m4">
<div class="icon-block">
<h5 class="center">{{ item.title }}</h5>
{{ cloudinary.image(item.image.public_id, {
width:280, height: 200, quality:80,
crop:'fill',format:'png', effect:
'brightness:20', radius: 5, class:
'materialboxed' }) | safe }}
{#
Swig comment tag
<img class="materialboxed" src="
{{ item.image.url }}" height='200'
width='100%' alt="{{ item.title }}"
data-caption="{{item.description}}"></img>
#}
<p class="light">{{ item.description }}</p>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
- 创建一个名为
posted-photo.swig的文件,并添加以下代码:
{% extends '../layout.swig' %}
{% block content %}
<div class="section no-pad-bot" id="index-banner">
<div class="container">
<br><br>
<h1 class="header center orange-text">{{ title }}</h1>
<div class="row center">
<h5 class="header col s12 light">Welcome to
{{ title }}
</h5>
</div>
<div class="photo col s12 m12">
<h2>{{ photo.title }}</h2>
{% if photo.image.url %}
<img src="img/{{ photo.image.url }}" width='100%'></img>
<a href="{{ photo.image.url }}" target="_blank">
{{ cloudinary.image(photo.image.public_id, {width: 150,
height: 150, quality: 80,crop:'fill',format:'png',
class:'thumbnail inline'}) }}
</a>
{% endif %}
</div>
<br>
<br>
<br>
</div>
</div>
{% endblock %}
创建.env.js 文件
在这一点上,我们将创建env.js文件;此文件验证了 Cloudinary 服务的配置。在config文件夹中创建一个名为env.js的文件,并放入以下代码:
// get Env variable / cloudinary
module.exports = function(app, configEnv) {
var dotenv = require('dotenv');
dotenv.load();
var cloudinary = require('cloudinary').v2;
// Log some messages on Terminal
if ( typeof(process.env.CLOUDINARY_URL) == 'undefined' ){
console.log('Cloudinary config file is not defined');
console.log('Setup CLOUDINARY_URL or use dotenv mdule file')
} else {
console.log('Cloudinary config, successfully used:');
console.log(cloudinary.config())
}
}
现在我们有一个完全配置的应用程序;但是,我们仍然需要在 Cloudinary 服务上创建一个帐户。
创建和配置 Cloudinary 账户
Cloudinary 是用于存储和处理图像和视频文件的云服务;您可以在cloudinary.com找到有关 Cloudinary 服务的更多信息:
- 转到
cloudinary.com/users/register/free并注册一个免费帐户。
提示
在注册表单的末尾,您可以为您的云设置一个名称。我们选择了n6b(Node.js 6 蓝图);选择您自己的名称。
- 从您的帐户中复制数据(
环境变量)并将其直接放到仪表板面板上,如下面的屏幕截图所示:
Cloudinary 仪表板面板
- 现在在
.env.js文件中使用您自己的凭据更新以下代码:
PORT=9000
CLOUDINARY_URL=cloudinary://82499XXXXXXXXX:dXXXXXXXXXXX@n6b
Cloudinary 的工作原理
除了我们在 Cloudinary 上存储文件之外,我们还可以使用强大的 API 来操作和转换图像,应用效果,调整大小,以及在我们的机器上不使用任何软件做更多的事情。
让我们回到books.js控制器,查看我们使用了什么。我们从 promises 函数中提取了额外的代码,以便专注于突出显示的代码行:
cloudinary.uploader.upload(imageFile,
{
tags: 'photobook',
folder: req.body.category + '/',
public_id: req.files.image.originalFilename
// eager: {
// width: 280, height: 200, crop: "fill", gravity: "face"
// }
})
.then(function (image) {
...
})
.then(function (photo) {
...
})
.finally(function () {
...
});
在这里,我们设置了一个文件夹,folder: req.body.category,用于存储我们的图像,并覆盖了默认的public_id: req.files.image.originalFilename,以使用图像名称。这是一个很好的做法,因为 API 为我们提供了一个带有随机字符串的public_id——没有错,但非常有用。例如,查看这样的链接:http://res.cloudinary.com/demo/image/upload/mydog.jpg
而不是这个:res.cloudinary.com/demo/image/upload/8jsb1xofxdqamu2rzwt9q.jpg。
注释的eager属性使我们能够转换图像并生成一个带有所有急切选项的新图像。在这种情况下,我们可以保存一个宽度为280px,高度为200px的转换图像,裁剪填充内容,如果图片中有一些脸,缩略图将居中显示在脸上。这是一个非常有用的功能,可以保存图像配置文件。
您可以在上传方法上使用任何转换组合;以下是 API 返回的JSON的示例:
{ title: 'Sample01',
description: 'Example with Transformation',
image:
{ public_id: 'cpl6ghhoiqphyagwvbaj',
version: 1461269043,
signature: '59cbbf3be205d72fbf7bbea77de8e7391d333363',
width: 845,
height: 535,
format: 'bmp',
resource_type: 'image',
created_at: '2016-04-21T20:04:03Z',
tags: [ 'photobook' ],
bytes: 1356814,
type: 'upload',
etag: '639c51691528139ae4f1ef00bc995464',
url: 'http://res.cloudinary.com/n6b/image/upload/v146126904
/cpl6ghhoiqphyagwvbaj.bmp',
secure_url: 'https://res.cloudinary.com/n6b/image/upload
/v1461269043/cpl6ghhoiqphyagwvbaj.bmp',
coordinates: { faces: [ [ 40, 215, 116, 158 ] ] }, original_filename: 'YhCmSuFxm0amW5TFX9FqXt3F', eager:[ { transformation: 'c_thumb,g_face,h_100,w_150',
width: 150, height: 100,url: 'http://res.cloudinary.com
/n6b/image/upload/c_thumb,g_face,h_100,w_150/v1461269043
/cpl6ghhoiqphyagwvbaj.bmp', secure_url:
'https://res.cloudinary.com/n6b/image/upload
/c_thumb,g_face,h_100,w_150/v1461269043
/cpl6ghhoiqphyagwvbaj.bmp' } ] }
注意带有 URL 转换的突出显示的代码:
c_thumb,g_face,h_100,w_150
提示
您可以在以下链接找到有关 Cloudinary 上传 API 的更多信息:cloudinary.com/documentation/node_image_upload。
运行应用程序
现在是时候执行应用程序并上传一些照片了:
- 打开您的终端/Shell 并输入以下命令:
npm start
- 转到
http://localhost:3000/books,您将看到以下屏幕:
书籍屏幕
上传和显示图像
现在让我们插入一些图像,并检查我们应用程序的行为:
- 转到
http://localhost:3000/books/add,并填写表单:
上传表单
添加以下信息:
标题:图像示例 02
文件:选择 sample02.jpg 文件。
类别:城市
描述:Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
让我们检查一下我们的 MongoDB,看看在我们继续之前发生了什么。
-
打开您的
RoboMongo并选择第一个对象:
来自 MongoDB(RoboMongo)的屏幕截图
提示
请注意,您必须从左侧面板菜单中选择正确的数据库。
-
当我们上传一张图片时,API 会返回一个包含与该图片相关的所有信息的 JSON。我们将这个 JSON 存储为我们的书籍模型的图像属性,存储在 MongoDB 中,正如我们在之前的图片中所看到的。
-
通过Sample02至Sample06重复步骤 1。
检查 MongoDb 图片集合
让我们在 MongoDB 上看一下图片集合:
-
打开 RoboMongo 并从左侧面板中选择正确的数据库。
-
打开
collections文件夹,双击图片集合。 -
在面板的右上方,单击
以表格模式查看结果图标。
现在您可以在RoboMongo界面上看到以下屏幕截图:
来自图片集合的屏幕截图
在 Cloudinary 仪表板中创建文件夹
如前所述,我们设置了文件夹(folder: req.body.category)。在这种情况下,文件夹名称将是类别名称。为了更好地在云中组织我们的图像,就像我们以编程方式做的那样,我们需要直接在 Cloudinary 仪表板中创建它们:
-
登录到您的 Cloudinary 帐户。
创建文件夹截图
注意
不要担心 Cloudinary 仪表板上的其他图像;它们是每个帐户中的默认图像。如果您愿意,可以删除它们。
-
点击右侧的输入字段(文件夹名称)并创建一个名为
animals的文件夹。 -
点击右侧的输入字段(文件夹名称)并创建一个名为
cities的文件夹。 -
点击右侧的输入字段(文件夹名称)并创建一个名为
nature的文件夹。
您可以在图像顶部看到所有创建的类别,如下面的截图所示:
类别截图
现在当您选择一个类别时,您只会看到属于该类别的图像,例如animals,如下图所示:
动物文件夹的截图
这是一种更有效的组织所有照片的方式,您可以创建几个相册,例如:
my-vacations/germany/berlin
road-trip/2015/route-66
URL 转换渲染
作为 Cloudinary API 的一部分,我们可以通过使用 URL 参数设置来操纵图像,就像我们在书籍页面上所做的那样:
-
转到
http://localhost:3000/books。 -
打开您的 Web 检查器并检查第一张图像的呈现代码;您将看到以下代码:
<img src="http://res.cloudinary.com/n6b/image/upload
/c_fill,e_brightness:20,h_200,q_80,r_5,w_280/v1/cities/sample01.jpg
.png" class="materialboxed initialized" height="200" width="280">
API 创建img标签,并应用app/views/books.swig中定义的对象属性作为 URL 参数,如下面的代码所示:
{{ cloudinary.image(item.image.public_id, { width: 280, height: 200,
quality: 80,crop: 'fill',format:'png', effect: 'brightness:20',
radius: 5, class:'materialboxed' }) | safe }}
| 对象属性 | URL 参数 |
|---|---|
| width: 280 | w_280 |
| height: 200 | h_200 |
| crop: fill | c_fill |
| quality: 80 | q_80 |
| effect:brightness:20 | e_brightness:20 |
| 半径:5 | r_5 |
花括号和安全过滤器{{... | safe}}是Swig模板引擎的标记,用于在视图上安全地呈现变量。
此外,我们还可以直接使用img标签,如下面的代码所示:
<img class="materialboxed" src="img/{{ item.image.url }}" height='200'
width='100%' alt="{{ item.title }}"
data-caption="{{item.description}}">
</img>
添加原始图像的直接链接
我们还可以使用 API 生成原始图像链接,而不应用任何转换:
- 打开
app/views/books.swig并添加以下突出显示的代码:
<div class="icon-block">
<h5 class="center">{{ item.title }}</h5>
{{ cloudinary.image(item.image.public_id, { width: 280, height:
200, quality: 80,crop: 'fill',format:'png', effect:
'brightness:20', radius:5,class:'materialboxed' }) | safe }}
{#
Swig comment tag
<img class="materialboxed" src="img/{{ item.image.url }}"
height='200' width='100%' alt="{{ item.title }}"
data-caption="{{item.description}}">
</img>
#}
<p class="light">{{ item.description }}</p>
<a href="{{ cloudinary.url(item.image.url) }}" target="_blank">
Link to original image
</a>
</div>
- 现在当我们点击
链接到原始图像时,我们可以在另一个浏览器窗口中看到完整的图像:
带有原始图像链接的书籍页面截图
重要的是要注意,我们还使用了Materialize.css框架中的简单colorbox,因此当我们将鼠标悬停在图像上时,我们可以看到一个图标,显示全尺寸的图像。
总结
我们已经到达了另一章的结尾。通过这一章,我们完成了一系列四章,讨论了使用 Node.js 进行软件开发的 MVC 模式。
在本章中,我们看到了如何构建一个使用云服务上传和操作图像的应用程序,还展示了如何应用效果,如亮度和边框半径。此外,我们还看到了如何使用简单的界面框架Materialize.css构建简单的图像库。
我们探索了一种不同的使用 ORM 模块的方式,并直接以 JSON 格式将所有有关图像的信息保存在 MongoDB 中。
在下一章中,我们将看到如何使用 Node 和 Firebase 云服务构建 Web 应用程序。