Initial commit: 全友米项目完整代码

This commit is contained in:
Yvan
2026-04-09 09:40:45 +08:00
commit 89960b1cb8
54 changed files with 14574 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
PORT=3000
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your-db-password
DB_NAME=quanyoumi
JWT_SECRET=your-secret-key-change-in-production
# MinIO 配置
MINIO_ENDPOINT=localhost # 后端连接 MinIO 的地址
MINIO_PORT=9000
MINIO_USE_SSL=false
MINIO_ACCESS_KEY=jFywUsaCb4zNr2LV7hTP
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=quanyoumi
# MINIO_PUBLIC_URL(可选):前端访问文件的公开地址,不填则自动用 MINIO_ENDPOINT 拼接
# 生产环境如果用了反向代理(如 Nginx),填写外网地址,例如:https://cdn.yourdomain.com
# MINIO_PUBLIC_URL=http://localhost:9000
+27
View File
@@ -0,0 +1,27 @@
// 初始化管理员账号
require('dotenv').config();
const bcrypt = require('bcryptjs');
const { Admin, sequelize } = require('./models');
async function initAdmin() {
try {
await sequelize.sync({ force: false });
const hashedPassword = bcrypt.hashSync('admin123', 10);
await Admin.create({
username: 'admin',
password: hashedPassword,
name: '管理员'
});
console.log('管理员账号创建成功');
console.log('用户名: admin');
console.log('密码: admin123');
process.exit(0);
} catch (error) {
console.error('创建失败:', error.message);
process.exit(1);
}
}
initAdmin();
+19
View File
@@ -0,0 +1,19 @@
const jwt = require('jsonwebtoken');
const auth = (req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: '未授权访问' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.adminId = decoded.id;
next();
} catch (error) {
res.status(401).json({ error: '无效的token' });
}
};
module.exports = auth;
+30
View File
@@ -0,0 +1,30 @@
const multer = require('multer');
const path = require('path');
const { v4: uuidv4 } = require('uuid');
const { uploadBuffer } = require('../utils/minio');
// 使用内存存储,文件不落盘,不限制大小
const upload = multer({
storage: multer.memoryStorage(),
fileFilter: (req, file, cb) => {
const extname = /jpeg|jpg|png|gif|mp4|mov|avi/.test(
path.extname(file.originalname).toLowerCase()
);
const mimetype = /image|video/.test(file.mimetype);
if (mimetype && extname) return cb(null, true);
cb(new Error('只允许上传图片或视频文件'));
}
});
// 将 req.files 或 req.file 上传到 MinIO,返回 url 数组或单个 url
const uploadFilesToMinio = async (files) => {
const list = Array.isArray(files) ? files : [files];
return Promise.all(list.map(async (file) => {
const ext = path.extname(file.originalname).toLowerCase();
const filename = `${Date.now()}-${uuidv4()}${ext}`;
const url = await uploadBuffer(file.buffer, filename, file.mimetype);
return { ...file, filename, url };
}));
};
module.exports = { upload, uploadFilesToMinio };
+44
View File
@@ -0,0 +1,44 @@
const { DataTypes } = require('sequelize');
const sequelize = require('./db');
const Activity = sequelize.define('Activity', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
title: {
type: DataTypes.STRING,
allowNull: false
},
description: DataTypes.TEXT,
location: DataTypes.STRING,
locationLat: DataTypes.DECIMAL(10, 7),
locationLng: DataTypes.DECIMAL(10, 7),
startTime: DataTypes.DATE,
endTime: DataTypes.DATE,
registrationDeadline: DataTypes.DATE,
maxParticipants: DataTypes.INTEGER,
status: {
type: DataTypes.ENUM('upcoming', 'ongoing', 'completed'),
defaultValue: 'upcoming'
},
registrationEnabled: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: '报名开关'
},
coverImage: DataTypes.STRING,
price: {
type: DataTypes.DECIMAL(10, 2),
defaultValue: 0,
comment: '人均费用'
},
budget: {
type: DataTypes.DECIMAL(10, 2),
defaultValue: 0,
comment: '活动预算'
}
});
module.exports = Activity;
+22
View File
@@ -0,0 +1,22 @@
const { DataTypes } = require('sequelize');
const sequelize = require('./db');
const Admin = sequelize.define('Admin', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
name: DataTypes.STRING
});
module.exports = Admin;
+32
View File
@@ -0,0 +1,32 @@
const { DataTypes } = require('sequelize');
const sequelize = require('./db');
const Expense = sequelize.define('Expense', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
activityId: {
type: DataTypes.INTEGER,
allowNull: false
},
item: {
type: DataTypes.STRING,
allowNull: false
},
amount: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
category: DataTypes.STRING,
receipt: DataTypes.STRING,
note: DataTypes.TEXT,
media: {
type: DataTypes.JSON,
defaultValue: [],
comment: '经费相关照片/视频,最多5个'
}
});
module.exports = Expense;
+30
View File
@@ -0,0 +1,30 @@
const { DataTypes } = require('sequelize');
const sequelize = require('./db');
const Photo = sequelize.define('Photo', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
activityId: {
type: DataTypes.INTEGER,
allowNull: false
},
url: {
type: DataTypes.STRING,
allowNull: false
},
type: {
type: DataTypes.ENUM('image', 'video'),
defaultValue: 'image'
},
description: DataTypes.STRING,
uploadedBy: DataTypes.STRING,
status: {
type: DataTypes.ENUM('pending', 'approved', 'rejected'),
defaultValue: 'approved'
}
});
module.exports = Photo;
+40
View File
@@ -0,0 +1,40 @@
const { DataTypes } = require('sequelize');
const sequelize = require('./db');
const Registration = sequelize.define('Registration', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
activityId: {
type: DataTypes.INTEGER,
allowNull: false
},
name: {
type: DataTypes.STRING,
allowNull: false
},
phone: {
type: DataTypes.STRING,
allowNull: false
},
carModel: DataTypes.STRING,
carNumber: DataTypes.STRING,
peopleCount: {
type: DataTypes.INTEGER,
defaultValue: 1
},
status: {
type: DataTypes.ENUM('pending', 'approved', 'rejected'),
defaultValue: 'pending'
},
note: DataTypes.TEXT,
checkInStatus: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
checkInTime: DataTypes.DATE
});
module.exports = Registration;
+26
View File
@@ -0,0 +1,26 @@
const { DataTypes } = require('sequelize');
const sequelize = require('./db');
const Sponsor = sequelize.define('Sponsor', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
activityId: {
type: DataTypes.INTEGER,
allowNull: false
},
name: {
type: DataTypes.STRING,
allowNull: false
},
logo: DataTypes.STRING,
type: {
type: DataTypes.ENUM('title', 'sponsor'),
defaultValue: 'sponsor'
},
link: DataTypes.STRING
});
module.exports = Sponsor;
+14
View File
@@ -0,0 +1,14 @@
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize(
process.env.DB_NAME || 'quanyoumi',
process.env.DB_USER || 'root',
process.env.DB_PASSWORD || '',
{
host: process.env.DB_HOST || 'localhost',
dialect: 'mysql',
logging: false
}
);
module.exports = sequelize;
+22
View File
@@ -0,0 +1,22 @@
const sequelize = require('./db');
const Activity = require('./Activity');
const Registration = require('./Registration');
const Expense = require('./Expense');
const Photo = require('./Photo');
const Sponsor = require('./Sponsor');
const Admin = require('./Admin');
// 同步数据库
sequelize.sync({ alter: true }).then(() => {
console.log('数据库同步完成');
});
module.exports = {
sequelize,
Activity,
Registration,
Expense,
Photo,
Sponsor,
Admin
};
+2140
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
{
"name": "quanyoumi-backend",
"version": "1.0.0",
"description": "泉友米车友会管理系统后端",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"minio": "^8.0.7",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.6.0",
"sequelize": "^6.33.0",
"uuid": "^9.0.1"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
+113
View File
@@ -0,0 +1,113 @@
const express = require('express');
const router = express.Router();
const { Activity, Photo, Sponsor, Expense, Registration } = require('../models');
const { upload, uploadFilesToMinio } = require('../middleware/upload');
// 获取活动列表
router.get('/activities', async (req, res) => {
try {
const { status } = req.query;
const where = status ? { status } : {};
const activities = await Activity.findAll({ where, order: [['startTime', 'DESC']] });
res.json(activities);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取活动详情
router.get('/activities/:id', async (req, res) => {
try {
const activity = await Activity.findByPk(req.params.id);
// 只返回已审核通过的照片
const photos = await Photo.findAll({
where: { activityId: req.params.id, status: 'approved' }
});
const sponsors = await Sponsor.findAll({ where: { activityId: req.params.id } });
const expenses = await Expense.findAll({ where: { activityId: req.params.id } });
res.json({ activity, photos, sponsors, expenses });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取活动经费(公开接口)
router.get('/activities/:id/expenses', async (req, res) => {
try {
const expenses = await Expense.findAll({ where: { activityId: req.params.id } });
res.json(expenses);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 用户上传照片(需要审核)
router.post('/activities/:id/photos', upload.array('photos', 10), async (req, res) => {
try {
const { uploadedBy } = req.body;
const uploaded = await uploadFilesToMinio(req.files);
const photos = uploaded.map(file => ({
activityId: req.params.id,
url: file.url,
type: /\.(mp4|mov|avi)$/i.test(file.filename) ? 'video' : 'image',
uploadedBy,
status: 'pending'
}));
const created = await Photo.bulkCreate(photos);
res.json(created);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 用户验证登录
router.post('/auth/verify/:activityId', async (req, res) => {
try {
const { phone, carNumber } = req.body;
const { activityId } = req.params;
console.log('验证请求:', { phone, carNumber, activityId });
if (!phone || !carNumber) {
return res.status(400).json({ error: '请输入手机号和车牌号' });
}
const registration = await Registration.findOne({
where: {
activityId,
phone,
carNumber
}
});
console.log('查询结果:', registration ? '找到' : '未找到');
if (!registration) {
// 调试:查询该活动下该手机号的所有报名
const allRegs = await Registration.findAll({
where: { activityId, phone }
});
console.log('该手机号的所有报名:', allRegs.map(r => ({ id: r.id, carNumber: r.carNumber })));
return res.status(404).json({ error: '未找到报名信息,请检查车牌号是否正确' });
}
res.json({
registration: {
id: registration.id,
name: registration.name,
phone: registration.phone,
carNumber: registration.carNumber,
carModel: registration.carModel,
status: registration.status,
activityId: registration.activityId
}
});
} catch (error) {
console.error('验证错误:', error);
res.status(500).json({ error: error.message });
}
});
module.exports = router;
+294
View File
@@ -0,0 +1,294 @@
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const auth = require('../middleware/auth');
const { upload, uploadFilesToMinio } = require('../middleware/upload');
const { deleteFile } = require('../utils/minio');
const { Admin, Activity, Registration, Expense, Photo, Sponsor } = require('../models');
// 管理员登录
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
const admin = await Admin.findOne({ where: { username } });
if (!admin || !bcrypt.compareSync(password, admin.password)) {
return res.status(401).json({ error: '用户名或密码错误' });
}
const token = jwt.sign({ id: admin.id }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.json({ token, admin: { id: admin.id, username: admin.username, name: admin.name } });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 创建活动
router.post('/activities', auth, async (req, res) => {
try {
const activity = await Activity.create(req.body);
res.json(activity);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 编辑活动
router.put('/activities/:id', auth, async (req, res) => {
try {
await Activity.update(req.body, { where: { id: req.params.id } });
const activity = await Activity.findByPk(req.params.id);
res.json(activity);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 审核报名
router.put('/registrations/:id', auth, async (req, res) => {
try {
const { status, peopleCount } = req.body;
const updateData = {};
if (status) updateData.status = status;
if (peopleCount !== undefined) updateData.peopleCount = peopleCount;
await Registration.update(updateData, { where: { id: req.params.id } });
const registration = await Registration.findByPk(req.params.id);
res.json(registration);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 签到/取消签到
router.put('/registrations/:id/checkin', auth, async (req, res) => {
try {
const registration = await Registration.findByPk(req.params.id);
if (!registration) {
return res.status(404).json({ error: '报名信息不存在' });
}
const newStatus = !registration.checkInStatus;
await Registration.update({
checkInStatus: newStatus,
checkInTime: newStatus ? new Date() : null
}, { where: { id: req.params.id } });
const updated = await Registration.findByPk(req.params.id);
res.json(updated);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取所有报名(含待审核)
router.get('/registrations', auth, async (req, res) => {
try {
const { activityId } = req.query;
const where = activityId ? { activityId } : {};
const registrations = await Registration.findAll({ where, order: [['createdAt', 'DESC']] });
res.json(registrations);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 上传经费明细
router.post('/expenses', auth, upload.array('media', 5), async (req, res) => {
try {
const { activityId, item, amount, category, note } = req.body;
let receipt = null;
let media = [];
if (req.files && req.files.length > 0) {
const uploaded = await uploadFilesToMinio(req.files);
media = uploaded.map(file => ({
url: file.url,
type: file.mimetype.startsWith('video/') ? 'video' : 'image'
}));
}
const expense = await Expense.create({
activityId,
item,
amount,
category,
receipt,
note,
media
});
res.json(expense);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取经费列表
router.get('/expenses', auth, async (req, res) => {
try {
const { activityId } = req.query;
const where = activityId ? { activityId } : {};
const expenses = await Expense.findAll({ where });
res.json(expenses);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 更新经费
router.put('/expenses/:id', auth, upload.array('media', 5), async (req, res) => {
try {
const { item, amount, category, note, existingMedia } = req.body;
const updateData = { item, amount, category, note };
// 处理现有媒体
let media = [];
if (existingMedia) {
try {
media = JSON.parse(existingMedia);
} catch (e) {
media = [];
}
}
// 处理新上传的媒体
if (req.files && req.files.length > 0) {
const uploaded = await uploadFilesToMinio(req.files);
const newMedia = uploaded.map(file => ({
url: file.url,
type: file.mimetype.startsWith('video/') ? 'video' : 'image'
}));
media = [...media, ...newMedia].slice(0, 5); // 限制最多5个
}
updateData.media = media;
await Expense.update(updateData, { where: { id: req.params.id } });
const expense = await Expense.findByPk(req.params.id);
res.json(expense);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 删除经费
router.delete('/expenses/:id', auth, async (req, res) => {
try {
await Expense.destroy({ where: { id: req.params.id } });
res.json({ message: '删除成功' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 上传活动照片
router.post('/photos', auth, upload.array('photos', 10), async (req, res) => {
try {
const { activityId } = req.body;
const uploaded = await uploadFilesToMinio(req.files);
const photos = uploaded.map(file => ({
activityId,
url: file.url,
type: /\.(mp4|mov|avi)$/i.test(file.filename) ? 'video' : 'image',
status: 'approved'
}));
const created = await Photo.bulkCreate(photos);
res.json(created);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取待审核照片
router.get('/photos/pending', auth, async (req, res) => {
try {
const photos = await Photo.findAll({
where: { status: 'pending' },
order: [['createdAt', 'DESC']]
});
res.json(photos);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 审核照片
router.put('/photos/:id', auth, async (req, res) => {
try {
const { status } = req.body;
await Photo.update({ status }, { where: { id: req.params.id } });
const photo = await Photo.findByPk(req.params.id);
res.json(photo);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 删除照片
router.delete('/photos/:id', auth, async (req, res) => {
try {
const photo = await Photo.findByPk(req.params.id);
if (!photo) {
return res.status(404).json({ error: '照片不存在' });
}
// 从URL提取文件名
const filename = photo.url.split('/').pop();
// 从MinIO删除文件
await deleteFile(filename);
// 从数据库删除记录
await photo.destroy();
res.json({ message: '删除成功' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 添加赞助商
router.post('/sponsors', auth, upload.single('logo'), async (req, res) => {
try {
const { activityId, name, type, link } = req.body;
let logo = null;
if (req.file) {
const [uploaded] = await uploadFilesToMinio(req.file);
logo = uploaded.url;
}
const sponsor = await Sponsor.create({ activityId, name, logo, type, link });
res.json(sponsor);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 更新赞助商
router.put('/sponsors/:id', auth, upload.single('logo'), async (req, res) => {
try {
const { name, type, link } = req.body;
const updateData = { name, type, link };
if (req.file) {
const [uploaded] = await uploadFilesToMinio(req.file);
updateData.logo = uploaded.url;
}
await Sponsor.update(updateData, { where: { id: req.params.id } });
const sponsor = await Sponsor.findByPk(req.params.id);
res.json(sponsor);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 删除赞助商
router.delete('/sponsors/:id', auth, async (req, res) => {
try {
await Sponsor.destroy({ where: { id: req.params.id } });
res.json({ message: '删除成功' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
+47
View File
@@ -0,0 +1,47 @@
const express = require('express');
const router = express.Router();
const { Registration, Activity } = require('../models');
// 提交报名
router.post('/registrations', async (req, res) => {
try {
const { activityId, name, phone, carModel, carNumber, note } = req.body;
const activity = await Activity.findByPk(activityId);
if (!activity) {
return res.status(404).json({ error: '活动不存在' });
}
const registration = await Registration.create({
activityId, name, phone, carModel, carNumber, note
});
res.json(registration);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 查询报名状态
router.get('/registrations/:id', async (req, res) => {
try {
const registration = await Registration.findByPk(req.params.id);
res.json(registration);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取活动报名列表(已通过)
router.get('/activities/:id/registrations', async (req, res) => {
try {
const registrations = await Registration.findAll({
where: { activityId: req.params.id, status: 'approved' }
});
res.json(registrations);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
+36
View File
@@ -0,0 +1,36 @@
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const path = require('path');
const app = express();
// 中间件
app.use(cors());
app.use(express.json());
// API 路由
app.use('/api', require('./routes/activity'));
app.use('/api', require('./routes/registration'));
app.use('/api/admin', require('./routes/admin'));
// 生产环境下提供前端静态文件
if (process.env.NODE_ENV === 'production') {
// 静态文件目录
app.use(express.static(path.join(__dirname, 'public')));
// 所有其他请求返回 index.html(支持前端路由)
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
}
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
if (process.env.NODE_ENV === 'production') {
console.log('运行模式: 生产环境(前后端一体)');
} else {
console.log('运行模式: 开发环境(仅后端API)');
}
});
Binary file not shown.
+88
View File
@@ -0,0 +1,88 @@
const Minio = require('minio');
const config = {
endPoint: process.env.MINIO_ENDPOINT || 'localhost',
port: parseInt(process.env.MINIO_PORT) || 9000,
useSSL: process.env.MINIO_USE_SSL === 'true',
accessKey: process.env.MINIO_ACCESS_KEY || 'minioadmin',
secretKey: process.env.MINIO_SECRET_KEY || 'minioadmin'
};
console.log('MinIO 配置:', {
...config,
secretKey: '***' // 隐藏密钥
});
const client = new Minio.Client(config);
const BUCKET = process.env.MINIO_BUCKET || 'quanyoumi';
// 确保 bucket 存在
const ensureBucket = async () => {
try {
console.log(`检查 bucket: ${BUCKET}`);
const exists = await client.bucketExists(BUCKET);
console.log(`Bucket ${BUCKET} 存在:`, exists);
if (!exists) {
console.log(`创建 bucket: ${BUCKET}`);
await client.makeBucket(BUCKET);
// 设置公开读取策略
const policy = JSON.stringify({
Version: '2012-10-17',
Statement: [{
Effect: 'Allow',
Principal: { AWS: ['*'] },
Action: ['s3:GetObject'],
Resource: [`arn:aws:s3:::${BUCKET}/*`]
}]
});
await client.setBucketPolicy(BUCKET, policy);
console.log(`Bucket ${BUCKET} 创建成功并设置为公开读取`);
}
} catch (error) {
console.error('ensureBucket 错误:', error);
throw error;
}
};
// 上传 buffer 到 MinIO,返回公开访问 URL
const uploadBuffer = async (buffer, filename, mimetype) => {
try {
console.log(`开始上传文件: ${filename}, 大小: ${buffer.length} bytes`);
await ensureBucket();
await client.putObject(BUCKET, filename, buffer, buffer.length, { 'Content-Type': mimetype });
console.log(`文件上传成功: ${filename}`);
// 如果配置了公开 URL 就用,否则用 endpoint 拼接
const publicUrl = process.env.MINIO_PUBLIC_URL;
if (publicUrl) {
return `${publicUrl}/${BUCKET}/${filename}`;
}
const protocol = process.env.MINIO_USE_SSL === 'true' ? 'https' : 'http';
const endpoint = process.env.MINIO_ENDPOINT || 'localhost';
const port = process.env.MINIO_PORT || 9000;
return `${protocol}://${endpoint}:${port}/${BUCKET}/${filename}`;
} catch (error) {
console.error('uploadBuffer 错误:', error);
throw error;
}
};
// 从 MinIO 删除文件
const deleteFile = async (filename) => {
try {
console.log(`开始删除文件: ${filename}`);
await client.removeObject(BUCKET, filename);
console.log(`文件删除成功: ${filename}`);
return true;
} catch (error) {
console.error('deleteFile 错误:', error);
throw error;
}
};
module.exports = { client, BUCKET, uploadBuffer, deleteFile };