- 前端: HTML, CSS, JavaScript (使用原生 JS,以更好地理解原理)
- 后端: Node.js + Express.js (轻量级、易于上手)
- 数据库: MongoDB + Mongoose (灵活,适合存储文件元数据)
- 文件存储: Cloudinary (推荐,专业云存储服务,自带图片处理功能) 或本地存储
第一步:规划网站功能与结构
在开始编码之前,先明确你的网站需要什么功能。

核心功能:
- 上传图片: 用户可以选择本地图片文件并提交。
- 显示图片列表: 上传成功后,将图片展示在页面上。
- 图片预览: 点击图片可以放大查看。
进阶功能 (可选,但强烈推荐):
- 图片重命名: 为上传的图片修改文件名。
- 图片删除: 删除不需要的图片。
- 图片信息展示: 显示图片的尺寸、大小、上传时间等。
- 图片处理: 调整大小、裁剪、应用滤镜等 (可以使用 Cloudinary 的 API 轻松实现)。
- 用户认证: 只有登录用户才能上传/管理自己的图片。
技术结构:
my-image-website/
├── public/ # 存放静态文件 (HTML, CSS, JS, 图片)
│ ├── index.html
│ ├── style.css
│ └── script.js
├── uploads/ # 存放上传的图片 (如果使用本地存储)
├── routes/ # 存放后端路由文件
│ └── imageRoutes.js
├── models/ # 存放数据库模型
│ └── Image.js
├── .env # 环境变量配置文件
├── .gitignore # Git 忽略文件
├── package.json # 项目依赖
└── server.js # 服务器入口文件
第二步:搭建后端环境
后端是网站的大脑,负责处理业务逻辑。

-
安装 Node.js 和 npm: 如果你还没有,请从 Node.js 官网 下载并安装。
-
初始化项目:
mkdir my-image-website cd my-image-website npm init -y
-
安装依赖:
# 后端框架 npm install express mongoose # 用于处理文件上传 npm install multer # 用于环境变量管理 npm install dotenv # 开发依赖,用于自动重启服务器 npm install --save-dev nodemon
-
创建服务器入口文件
server.js:// server.js require('dotenv').config(); // 加载环境变量 const express = require('express'); const mongoose = require('mongoose'); const imageRoutes = require('./routes/imageRoutes'); const app = express(); const PORT = process.env.PORT || 3000; // 连接 MongoDB 数据库 mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }) .then(() => console.log('MongoDB connected!')) .catch(err => console.error('MongoDB connection error:', err)); // 中间件 app.use(express.json()); // 解析 JSON 请求体 app.use(express.urlencoded({ extended: true })); // 解析 URL 编码的请求体 app.use(express.static('public')); // 托管 public 目录下的静态文件 // 路由 app.use('/api/images', imageRoutes); app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); }); -
创建环境变量文件
.env: 在项目根目录创建.env文件,注意不要把这个文件上传到 Git。# .env PORT=3000 MONGO_URI=你的mongodb连接字符串 # 如果你使用 Cloudinary,也需要在这里配置 CLOUDINARY_CLOUD_NAME=你的cloudinary云名称 CLOUDINARY_API_KEY=你的cloudinary API密钥 CLOUDINARY_API_SECRET=你的cloudinary API密钥 -
创建
.gitignore文件: 在项目根目录创建.gitignore文件,防止node_modules和.env等文件被提交到 Git。# .gitignore node_modules .env uploads/
第三步:创建数据库模型
我们需要一个模型来存储图片的元数据,比如文件名、URL、上传者等。
-
创建
models/Image.js:// 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, }, size: { type: Number, required: true, }, uploadDate: { type: Date, default: Date.now, }, }); module.exports = mongoose.model('Image', ImageSchema);
第四步:创建后端路由和上传逻辑
这是处理图片上传和请求的核心部分。
-
创建
routes/imageRoutes.js:// routes/imageRoutes.js const express = require('express'); const router = express.Router(); const multer = require('multer'); const Image = require('../models/Image'); const cloudinary = require('cloudinary').v2; // 如果使用 Cloudinary // 配置 multer (用于处理文件上传) // 这里我们先使用本地存储 const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, 'uploads/'); // 上传的文件存放在 uploads 目录 }, filename: function (req, file, cb) { // 生成唯一文件名,避免重名 cb(null, Date.now() + '-' + file.originalname); } }); const upload = multer({ storage: storage }); // POST /api/images/upload - 上传图片 router.post('/upload', upload.single('image'), async (req, res) => { try { if (!req.file) { return res.status(400).send('No file uploaded.'); } const newImage = new Image({ filename: req.file.filename, originalName: req.file.originalname, path: req.file.path, size: req.file.size, }); await newImage.save(); res.status(201).json({ message: 'File uploaded successfully!', image: newImage, }); } catch (error) { res.status(500).send('Error uploading file: ' + error.message); } }); // GET /api/images - 获取所有图片列表 router.get('/', async (req, res) => { try { const images = await Image.find().sort({ uploadDate: -1 }); // 按上传时间倒序 res.json(images); } catch (error) { res.status(500).send('Error fetching images: ' + error.message); } }); // DELETE /api/images/:id - 删除图片 router.delete('/:id', async (req, res) => { try { const image = await Image.findById(req.params.id); if (!image) { return res.status(404).send('Image not found.'); } // TODO: 删除服务器上的实际文件 (fs.unlink) await Image.findByIdAndDelete(req.params.id); res.send('Image deleted successfully.'); } catch (error) { res.status(500).send('Error deleting image: ' + error.message); } }); module.exports = router;
第五步:创建前端界面
用户交互发生在这里。
-
创建
public/index.html:<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>图片上传网站</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="container"> <h1>图片上传</h1> <div class="upload-section"> <input type="file" id="fileInput" accept="image/*"> <button id="uploadBtn">上传图片</button> </div> <div class="gallery" id="gallery"> <!-- 图片列表将在这里动态生成 --> </div> </div> <script src="script.js"></script> </body> </html> -
创建
public/style.css(添加一些基本样式):body { font-family: Arial, sans-serif; background-color: #f4f4f4; } .container { max-width: 800px; margin: 20px auto; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .upload-section { margin-bottom: 20px; } #fileInput { margin-right: 10px; padding: 8px; } #uploadBtn { padding: 8px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } #uploadBtn:hover { background-color: #0056b3; } .gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; } .gallery-item { position: relative; border: 1px solid #ddd; border-radius: 4px; overflow: hidden; } .gallery-item img { width: 100%; height: 150px; object-fit: cover; cursor: pointer; } .gallery-item-info { padding: 10px; font-size: 0.9em; } .gallery-item-delete { position: absolute; top: 5px; right: 5px; background: rgba(255,0,0,0.7); color: white; border: none; border-radius: 50%; width: 25px; height: 25px; cursor: pointer; } -
创建
public/script.js(处理前端逻辑):document.addEventListener('DOMContentLoaded', () => { const fileInput = document.getElementById('fileInput'); const uploadBtn = document.getElementById('uploadBtn'); const gallery = document.getElementById('gallery'); // 加载图片列表 fetchImages(); // 上传按钮点击事件 uploadBtn.addEventListener('click', () => { const file = fileInput.files[0]; if (!file) { alert('请选择一个文件'); return; } const formData = new FormData(); formData.append('image', file); fetch('/api/images/upload', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.message) { alert(data.message); fileInput.value = ''; // 清空文件输入 fetchImages(); // 重新加载图片列表 } else { alert('上传失败: ' + data.error); } }) .catch(error => { console.error('Error:', error); alert('上传失败,请检查控制台'); }); }); // 获取并显示所有图片 function fetchImages() { fetch('/api/images') .then(response => response.json()) .then(images => { gallery.innerHTML = ''; // 清空现有画廊 images.forEach(image => { const item = document.createElement('div'); item.className = 'gallery-item'; item.innerHTML = ` <img src="/${image.path}" alt="${image.originalName}" onclick="viewImage('${image.path}')"> <div class="gallery-item-info"> <p>${image.originalName}</p> <p>大小: ${(image.size / 1024).toFixed(2)} KB</p> </div> <button class="gallery-item-delete" onclick="deleteImage('${image._id}')">X</button> `; gallery.appendChild(item); }); }) .catch(error => console.error('Error fetching images:', error)); } // 删除图片 window.deleteImage = function(imageId) { if (confirm('确定要删除这张图片吗?')) { fetch(`/api/images/${imageId}`, { method: 'DELETE' }) .then(response => { if (response.ok) { alert('图片已删除'); fetchImages(); // 重新加载列表 } else { alert('删除失败'); } }) .catch(error => console.error('Error deleting image:', error)); } } // 预览图片 (简单实现) window.viewImage = function(imagePath) { const newWindow = window.open('', '_blank'); newWindow.document.write(`<img src="/${imagePath}" style="max-width:100%; max-height:100%;">`); } });
第六步:启动和测试
-
修改
package.json的scripts部分,方便启动:"scripts": { "start": "node server.js", "dev": "nodemon server.js" }, -
启动服务器:
npm run dev
-
访问网站: 打开浏览器,访问
http://localhost:3000,现在你应该可以上传图片、查看列表和删除图片了。
进阶与优化
-
使用 Cloudinary 替代本地存储:
- 注册一个 Cloudinary 账户,获取 API 密钥。
- 在
imageRoutes.js中修改上传逻辑,使用cloudinary.uploader.upload()。 - 上传成功后,将 Cloudinary 返回的图片 URL 存储到 MongoDB 中。
- 好处: 自动处理 CDN、图片压缩、格式转换、裁剪等,无需自己管理服务器存储。
-
用户认证:
- 使用
Passport.js或JWT (JSON Web Tokens)实现用户登录注册功能。 - 在
Image模型中添加一个userId字段,关联到用户。 - 在路由中添加中间件,检查用户是否登录,并确保用户只能操作自己的图片。
- 使用
-
更高级的前端框架:
- 使用 React, Vue.js 或 Svelte 来构建更动态、更现代化的用户界面。
-
部署:
- 前端: 可以部署在 Vercel, Netlify 或 GitHub Pages 上。
- 后端: 可以部署在 Heroku, Render, AWS (EC2/Elastic Beanstalk) 或 DigitalOcean App Platform 上。
- 数据库: 使用 MongoDB Atlas (云数据库)。
这个指南为你提供了一个坚实的基础,你可以在此基础上不断添加新功能,将其打造成一个功能完善的图片托管网站,祝你编码愉快!
