1. 首先创建Express项目
# 安装express-generator
npm install -g express-generator
# 创建Express项目
express my-image-upload-app --view=ejs
# 进入项目目录
cd my-image-upload-app
# 安装依赖
npm install
# 安装额外的依赖
npm install multer mongoose dotenv
2. 项目结构
my-image-upload-app/
├── models/
│ └── Image.js # 图片模型
├── routes/
│ ├── index.js
│ └── upload.js # 上传路由
├── public/
│ ├── images/ # 上传的图片存储在这里
│ └── uploads/ # 也可以放在这里
├── uploads/ # 上传目录(可选,在根目录)
├── .env # 环境变量
├── app.js
└── package.json
3. 创建数据库模型
models/Image.js
const mongoose = require('mongoose');
const imageSchema = new mongoose.Schema({
filename: {
type: String,
required: true
},
originalName: {
type: String,
required: true
},
path: {
type: String,
required: true
},
url: {
type: String,
required: true
},
mimetype: {
type: String,
required: true
},
size: {
type: Number,
required: true
},
uploadDate: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Image', imageSchema);
4. 创建上传配置
config/upload.js
const multer = require('multer');
const path = require('path');
const fs = require('fs');
// 确保上传目录存在
const uploadDir = 'public/uploads/images';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
// 配置存储
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, uploadDir);
},
filename: function (req, file, cb) {
// 生成唯一文件名:时间戳 + 随机数 + 扩展名
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
}
});
// 文件过滤器
const fileFilter = (req, file, cb) => {
// 允许的图片类型
const allowedTypes = /jpeg|jpg|png|gif|webp/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = allowedTypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb(new Error('只允许上传图片文件 (jpeg, jpg, png, gif, webp)'));
}
};
// 创建multer实例
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 5 * 1024 * 1024 // 限制5MB
}
});
module.exports = upload;
5. 创建上传路由
routes/upload.js
const express = require('express');
const router = express.Router();
const upload = require('../config/upload');
const Image = require('../models/Image');
const path = require('path');
// 单文件上传
router.post('/single', upload.single('image'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({
success: false,
message: '请选择要上传的图片'
});
}
// 构建访问URL
const imageUrl = `/uploads/images/${req.file.filename}`;
const fullPath = path.join(req.file.destination, req.file.filename);
// 保存到数据库
const newImage = new Image({
filename: req.file.filename,
originalName: req.file.originalname,
path: fullPath,
url: imageUrl,
mimetype: req.file.mimetype,
size: req.file.size
});
await newImage.save();
res.status(200).json({
success: true,
message: '图片上传成功',
data: {
id: newImage._id,
filename: newImage.filename,
originalName: newImage.originalName,
url: newImage.url,
path: newImage.path,
mimetype: newImage.mimetype,
size: newImage.size,
uploadDate: newImage.uploadDate
}
});
} catch (error) {
console.error('上传错误:', error);
res.status(500).json({
success: false,
message: '上传失败',
error: error.message
});
}
});
// 多文件上传
router.post('/multiple', upload.array('images', 10), async (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({
success: false,
message: '请选择要上传的图片'
});
}
const uploadedImages = [];
for (const file of req.files) {
const imageUrl = `/uploads/images/${file.filename}`;
const fullPath = path.join(file.destination, file.filename);
const newImage = new Image({
filename: file.filename,
originalName: file.originalname,
path: fullPath,
url: imageUrl,
mimetype: file.mimetype,
size: file.size
});
await newImage.save();
uploadedImages.push(newImage);
}
res.status(200).json({
success: true,
message: `成功上传 ${uploadedImages.length} 张图片`,
data: uploadedImages.map(img => ({
id: img._id,
filename: img.filename,
originalName: img.originalName,
url: img.url,
path: img.path
}))
});
} catch (error) {
console.error('批量上传错误:', error);
res.status(500).json({
success: false,
message: '上传失败',
error: error.message
});
}
});
// 获取所有图片
router.get('/images', async (req, res) => {
try {
const images = await Image.find().sort({ uploadDate: -1 });
res.status(200).json({
success: true,
count: images.length,
data: images
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取图片列表失败',
error: error.message
});
}
});
// 获取单个图片信息
router.get('/images/:id', async (req, res) => {
try {
const image = await Image.findById(req.params.id);
if (!image) {
return res.status(404).json({
success: false,
message: '图片不存在'
});
}
res.status(200).json({
success: true,
data: image
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取图片信息失败',
error: error.message
});
}
});
module.exports = router;
6. 配置主应用文件
app.js (修改后)
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const mongoose = require('mongoose');
require('dotenv').config();
var indexRouter = require('./routes/index');
var uploadRouter = require('./routes/upload');
var app = express();
// 连接MongoDB
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/image-upload')
.then(() => console.log('MongoDB连接成功'))
.catch(err => console.error('MongoDB连接失败:', err));
// 视图引擎设置
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
// 静态文件服务 - 允许访问uploads目录
app.use('/uploads', express.static(path.join(__dirname, 'public/uploads')));
app.use(express.static(path.join(__dirname, 'public')));
// 路由
app.use('/', indexRouter);
app.use('/api/upload', uploadRouter);
// 错误处理
app.use(function(req, res, next) {
next(createError(404));
});
app.use(function(err, req, res, next) {
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.json({
success: false,
message: err.message,
error: req.app.get('env') === 'development' ? err.stack : {}
});
});
module.exports = app;
7. 创建环境变量文件
.env
MONGODB_URI=mongodb://localhost:27017/image-upload
PORT=3000
NODE_ENV=development
8. 修改默认路由
routes/index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: '图片上传API' });
});
module.exports = router;
9. 测试
单文件上传:
-
URL :
POST http://localhost:3000/api/upload/single -
Body: form-data
-
Key :
image(类型选择File) -
Value: 选择图片文件
多文件上传:
-
URL :
POST http://localhost:3000/api/upload/multiple -
Body: form-data
-
Key :
images(类型选择File) -
Value: 选择多个图片文件
1. multer - 文件上传中间件
主要作用:
-
处理 multipart/form-data 类型的数据(专门用于文件上传)
-
解析客户端通过表单上传的文件
-
提供存储配置、文件过滤、大小限制等功能
核心功能:
const multer = require('multer');
// 配置存储
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/') // 指定存储目录
},
filename: (req, file, cb) => {
cb(null, Date.now() + '-' + file.originalname) // 自定义文件名
}
});
const upload = multer({ storage: storage });
// 使用方式
app.post('/upload', upload.single('avatar'), (req, res) => {
// req.file 包含上传的文件信息
console.log(req.file);
});
常见用法:
-
upload.single('fieldname')- 单个文件 -
upload.array('fieldname', maxCount)- 多个文件 -
upload.fields([{name: 'avatar', maxCount: 1}, {name: 'gallery', maxCount: 8}])- 多个字段
2. mongoose - MongoDB ODM(对象文档映射)
主要作用:
-
连接和操作 MongoDB 数据库
-
提供 Schema(模式)定义数据结构
-
数据验证、中间件、查询构建等高级功能
核心功能示例:
const mongoose = require('mongoose');
// 1. 连接数据库
mongoose.connect('mongodb://localhost:27017/myapp');
// 2. 定义Schema(数据结构)
const userSchema = new mongoose.Schema({
name: String,
email: { type: String, required: true, unique: true },
age: { type: Number, min: 0 },
createdAt: { type: Date, default: Date.now }
});
// 3. 创建模型(相当于数据库表)
const User = mongoose.model('User', userSchema);
// 4. 使用模型操作数据
const newUser = new User({ name: '张三', email: 'zhangsan@example.com' });
await newUser.save(); // 保存到数据库
// 查询数据
const users = await User.find({ age: { $gt: 18 } });
主要特性:
-
Schema 验证:数据类型、必填字段、默认值等
-
中间件:pre/post save/find 等钩子函数
-
查询链 :
User.find().where().sort().limit() -
索引:性能优化
-
虚拟属性:不存数据库的派生字段
3. dotenv - 环境变量管理
主要作用:
-
从 .env 文件加载环境变量到 process.env
-
保护敏感信息(密码、API密钥等)
-
区分不同环境(开发、测试、生产)
使用方法:
// 1. 在项目根目录创建 .env 文件
// .env 文件内容:
DB_HOST=localhost
DB_PORT=27017
DB_NAME=mydatabase
JWT_SECRET=mysecretkey123
API_KEY=abc123xyz
// 2. 在应用入口文件(app.js)中加载
require('dotenv').config();
// 3. 使用环境变量
const mongoose = require('mongoose');
mongoose.connect(
`mongodb://${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`
);
// 使用其他变量
const jwtSecret = process.env.JWT_SECRET;
const apiKey = process.env.API_KEY;
为什么要用 dotenv?
-
安全性:敏感信息不进入代码库
-
可配置性:不同环境不同配置
-
方便部署:在服务器上只需修改 .env 文件