1.前置准备工具如下:
- nodejs
- express(nodejs mvc框架)
- body-parser(express middleware)
- gm(nodejs中用来处理图片的)
- uuid(nodejs中用于生成uuid)
- underscore(nodejs数据处理库)
- ImageMagick(gm会调用该程序处理图片)
2.搭建项目
使用express来快速搭建项目,图片服务器的功能相对简单,步骤:1、获取图片资源 2、上传图片,因此对应express只需要使用到bodyParser这样的基本组件,代码大致如下:
//app.js var app = require('express')(); process.app = app;//方便在其他地方使用app获取配置 require('./config')(__dirname, app);//所有配置 var mode = app.get('mode'); require('./controller/' + mode + 'Controller.js'); var config = app.get(mode); require('http').createServer(app).listen(config.port, function () { console.log('%s已经启动,正监听:%s', config.name, config.port); }); //config.js var path = require('path'); var OPTIONS = { targetDir: function (app) { return path.join(app.get('rootDir'), 'img'); }, read: { name: '<<图片服务器(读取)>>', port: 80, 'default': 'default.jpg', sizeReg: /(\w+)-(\d+)-(\d+)\.(\w+)$/ }, save: { name: '<<图片服务器(保存)>>', port: 9990 }, mode: 'read', contentType: { 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'gif': 'image/gif', 'png': 'image/png' } }; module.exports = function (rootDir, app) { app.set('rootDir', rootDir); var $ = require('underscore'); $.each(OPTIONS, function (v, k) { app.set(k, typeof v === 'function' ? v(app) : v); }); app.use(require('body-parser')()) };
3.获取图片
用户根据url获取图片服务器内存储的图片,当后台接收这个请求后,首先判断图片上会否存在,如果存在则返回对应的图片否则返回默认的图片,大致代码如下:
var path = require('path'); var fs = require('fs'); var gm = require('gm').subClass({ imageMagick: true });//默认的情况下 gm使用的是另外一个图片处理程序 var app = process.app; var config = app.get('read'); var targetDir = app.get('targetDir'); app.get('/:filename', function (req, res) { var filePath = path.join(targetDir, req.params.filename); fs.exists(filePath, function (exists) { res.sendfile(exists ? filePath : path.join(targetDir, config.default)); }); });
使用以上的代码便可以对图片进行读取了,但是只能对targetDir目录下的文件进行读取且没有对文件类型进行判断,对文件类型的判断比较简单只要在方法执行之前对文件扩展名进行判断即可,至于增加了文件夹结构的话,跟直接从targetDir目录下读取是差不多的流程,稍微调整一下代码:
var contentTypes = app.get('contentType'); app.get('/:filename', function (req, res) { sendFile([], req.params.filename, res); }); app.get('/:folder/:filename', function (req, res) { sendFile([req.params.folder], req.params.filename, res); }); app.get('/:folder1/:folder2/:name', function (req, res) { sendFile([req.params.folder1, req.params.folder2], req.params.filename, res); }); function sendFile(folders, filename, res) { var ext = path.extname(filename).substr(1); if (!contentTypes[ext]) return res.sendfile(getFilePath()); folders.push(filename); var filePath = getFilePath(path.join.apply(path, folders)); fs.exists(filePath, function (exists) { res.sendfile(exists ? filePath : getFilePath()); }); } function getFilePath(filename) { return path.join(app.get('targetDir'), filename || config.default); }
接下来假设一个场景,如果上传了一张800*600的图片,但是在页面上显示的时候,也许我只想显示一张200*150的缩略图,这时候gm就该登场了,我们可以使用gm来为800*600的图片临时生成一张200*150的图片,并以buffer的形式回传给客户端,大致代码如下:
//判断请求图片是否存在 if (exists) return res.sendfile(filePath); var groups = filename.match(config.sizeReg); if (!groups) return res.sendfile(getFilePath()); folders[len] = groups[1] + "." + groups[4]; filePath = getFilePath(path.join.apply(path, folders)); var width = parseInt(groups[2]); var height = parseInt(groups[3]); //判断原始图是否存在 fs.exists(filePath, function (exists) { if (!exists) filePath = getFilePath(); var gm = gm(filePath); if (width > 0 && height > 0) gm.resize(width, height); gm.toBuffer(function (err, buffer) { if (err) return res.sendfile(getFilePath()); res.set('Content-Type', contentTypes[ext]); res.send(buffer); }); });
4.上传图片
由于图片服务器只提供最简单的功能,支持文件上传(制作水印以及其他的处理在下一篇中讲述),也不会将此功能开放到外网,因此图片上传服务器是在内网的,只能从web服务器那边处理图片以后上传到图片服务器,图片服务器对其进行存储并返回存储后的图片路径,大致代码如下:
var buf = require('buffer'); var fs = require('fs'); var path = require('path'); var util = require('util'); var gm = require('gm').subClass({ imageMagick: true }); var uuid = require('uuid'); var app = process.app; var contentTypes = app.get('contentType'); /* 请求包含如下参数: @ext 图片扩展名 @buffer 图片buffer数据 @folder 文件夹,格式:/aa/bb */ app.post('/', function (req, res) { var ext = req.body.ext; var buffer = req.body.buffer; if (!(ext && buffer && contentTypes[ext])) return res.json({ success: false }); var pathArgs = req.body.folder.replace(/\n/g, ''); if (pathArgs) pathArgs = pathArgs.substr(1).split('/'); else pathArgs = ['']; createDir(pathArgs); pathArgs.push(''); var filePath = createPath(pathArgs, ext); gm(new buf.Buffer(JSON.parse(buffer))).write(filePath, function (err) { if (err) return res.json({ success: false }); res.json({ success: true, data: util.format('\\%s.%s', pathArgs.join('\\'), ext) }); }); }); function createDir(pathArgs) { if (pathArgs[0]) { var dir = path.join(app.get('targetDir'), path.join.apply(path, pathArgs)); var exists = fs.existsSync(dir); if (!exists) fs.mkdirSync(dir); } } function createPath(pathArgs, ext) { var args = [app.get('targetDir')]; pathArgs[pathArgs.length - 1] = uuid.v1().replace(/-/g, ''); args.push.apply(args, pathArgs); var filePath = path.join.apply(path, args) + '.' + ext; return fs.existsSync(filePath) ? createPath(pathArgs, ext) : filePath; }