背景
当我们开发一个 web 项目的时候, 为了将图片管理与 web 服务分离开, 通常都会搭建一个图片服务器.
之所以选择 nodejs 是因为使用 nodejs 来搭建 web 项目相当简单而且快速, 虽然这个图片服务器很简单, 也有很多人会认为使用 nodejs 来当图片服务器不合适, 但是当我们的应用没有达到非常大的程度的情况下, 其实 nodejs 还是够用的.
会使用到的工具如下:
- http://www.nodejs.org/
- express(nodejs mvc 框架) http://expressjs.com/
- body-parser(express middleware) https://github.com/expressjs/body-parser
- gm(nodejs 中用来处理图片的) https://github.com/aheckmann/gm
- uuid(nodejs 中用于生成 uuid) https://github.com/pnegri/uuid-js
- underscore(nodejs 数据处理库) http://underscorejs.org/
- ImageMagick(gm 会调用该程序处理图片) http://www.imagemagick.org/
那么接下来就来尝试实现这个简易的图片服务器吧, ^_^
搭建项目
首先要使用 express 来搭建项目, 由于图片服务器的功能相对简单, 只有 2 个功能: 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')())
- };
上面独立出了 config.js 是为了将所有的配置放在一起方便维护, 其次配置中的 mode 是为了区分当前是一个读取还是存储服务器.
获取图片
这是图片服务器的其中一个功能, 用户根据 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);
- });
- });
上传图片
由于图片服务器只提供最简单的功能, 支持文件上传, 但是并不会对上传的文件制作水印以及其他的处理, 也不会将此功能开放到外网, 因此图片上传服务器是在内网的, 只能从 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;
- }
结尾
到这里我们就完成了一个 nodejs 版本的图片服务器了, 因为尽可能将不需要的功能剥离到了其他的项目中去, 因此图片服务器的功能就变得很简单, 单一了.
对于图片获取服务器生成临时大小的文件, 可以先缓存起来, 并结合 leasing 模式进行管理, 频繁使用的情况下可以翻倍增加它的租约, 到期则直接释放.
而图片存储服务器, 如果对于某些应用上传的图片都会生成固定系列大小的情况下, 可以使用 gm 对原始图进行二次处理, 根据规则批量生成各种大小的图, 至于图片的名字可以采用一些规则, 这样返回的话, 依然是存储后的原始图, 而且格式则可以通过获取服务器获取, 无需再进行缓存了.
由于大部分的代码已经提供了, 请不要再跟我要什么源码之类的哦, 大家可以自己尝试搭建一下, 今天就到这里了, 如果有什么问题请告知我, 谢谢各位.
来源: https://www.cnblogs.com/ahl5esoft/p/3769781.html