acat / server.js
WACATW's picture
Update server.js
4d85b79 verified
Raw History Blame Contribute Delete
15.1 kB
const express = require('express');
const { Low } = require('lowdb');
const { JSONFile } = require('lowdb/node');
const bodyParser = require('body-parser');
const cors = require('cors');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const multer = require('multer');
const port = process.env.PORT || 7860;
const app = express();
// 管理员固定账号密码
const ADMIN_USER = "admin";
const ADMIN_PWD = "admin123";
// 数据库初始化
const dbPath = path.join(__dirname, 'db.json');
if (!fs.existsSync(dbPath)) {
fs.writeFileSync(dbPath, JSON.stringify({
users: [],
sessions: []
}, null, 2));
}
const adapter = new JSONFile(dbPath);
const db = new Low(adapter, { users: [], sessions: [] });
app.use(cors());
app.use(bodyParser.json());
app.use(express.static('public'));
// 密码加密
function encryptPassword(pwd) {
return crypto.createHash('sha256').update(pwd).digest('hex');
}
// ===================== 用户登录接口(仅普通用户使用,管理员无需登录进后台) =====================
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
const hash = encryptPassword(password);
await db.read();
// 管理员账号登录(仅用于网站普通功能登录,后台独立密码)
if (username === ADMIN_USER && hash === encryptPassword(ADMIN_PWD)) {
const token = crypto.randomBytes(16).toString('hex');
db.data.sessions.push({ token, username, expireTime: Date.now() + 86400000 });
await db.write();
return res.json({ code: 200, msg: "管理员登录成功", data: { token, username, isAdmin: true } });
}
// 普通用户
const user = db.data.users.find(u => u.username === username && u.password === hash);
if (!user) return res.json({ code: 400, msg: "用户名或密码错误" });
if (user.status !== "pass") return res.json({ code: 400, msg: "账号未通过审核,无法登录" });
const token = crypto.randomBytes(16).toString('hex');
db.data.sessions.push({ token, username, expireTime: Date.now() + 86400000 });
await db.write();
return res.json({ code: 200, msg: "登录成功", data: { token, username, isAdmin: false } });
});
// ===================== 用户注册接口 =====================
app.post('/api/register', async (req, res) => {
const { username, password, phone, qq, wechat } = req.body;
const regTime = new Date().toLocaleString();
if (!username || !password) return res.json({ code: 400, msg: "用户名、密码必填" });
if (password.length < 6) return res.json({ code: 400, msg: "密码至少6位" });
const fillList = [phone, qq, wechat].filter(v => v?.trim());
if (fillList.length === 0) return res.json({ code: 400, msg: "手机号/QQ/微信至少填写一项" });
await db.read();
if (db.data.users.find(u => u.username === username)) return res.json({ code: 400, msg: "用户名已被注册" });
const p = phone?.trim(), q = qq?.trim(), w = wechat?.trim();
if (p && db.data.users.find(u => u.phone === p)) return res.json({ code: 400, msg: "该手机号已注册" });
if (q && db.data.users.find(u => u.qq === q)) return res.json({ code: 400, msg: "该QQ已注册" });
if (w && db.data.users.find(u => u.wechat === w)) return res.json({ code: 400, msg: "该微信号已注册" });
db.data.users.push({
username,
password: encryptPassword(password),
phone: p || "",
qq: q || "",
wechat: w || "",
status: "pending",
registerTime: regTime
});
await db.write();
return res.json({ code: 200, msg: "注册成功,等待管理员审核" });
});
// ===================== 校验管理员密码(主页点击管理按钮调用) =====================
app.post('/api/verifyAdminPwd', (req, res) => {
const { inputPwd } = req.body;
if (inputPwd === ADMIN_PWD) {
return res.json({ code: 200, msg: "密码正确,可进入后台" });
} else {
return res.json({ code: 400, msg: "管理员密码错误" });
}
});
// ===================== 管理员后台接口(全部接收前端传入的管理员密码校验,无session读取) =====================
// 获取全部用户
app.get('/api/admin/userlist', async (req, res) => {
const pwd = req.query.adminPwd;
if (pwd !== ADMIN_PWD) return res.json({ code: 403, msg: "无管理员权限" });
await db.read();
res.json({ code: 200, data: db.data.users });
});
// 审核通过用户
app.post('/api/admin/pass', async (req, res) => {
const { adminPwd, username } = req.body;
if (adminPwd !== ADMIN_PWD) return res.json({ code: 403, msg: "无管理员权限" });
await db.read();
const user = db.data.users.find(u => u.username === username);
if (!user) return res.json({ code: 400, msg: "用户不存在" });
user.status = "pass";
await db.write();
res.json({ code: 200, msg: "审核通过" });
});
// 删除用户
app.post('/api/admin/del', async (req, res) => {
const { adminPwd, username } = req.body;
if (adminPwd !== ADMIN_PWD) return res.json({ code: 403, msg: "无管理员权限" });
await db.read();
db.data.users = db.data.users.filter(u => u.username !== username);
await db.write();
res.json({ code: 200, msg: "已删除用户" });
});
// ===================== 云电脑、网盘(仅区分普通用户审核状态,管理员无需登录也可访问) =====================
app.get('/api/cloudpc', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
await db.read();
// 管理员直接放行
if (token) {
const session = db.data.sessions.find(s => s.token === token);
if (session && session.username === ADMIN_USER) {
return res.json({ code: 200, msg: "云电脑访问成功(管理员)" });
}
}
// 普通用户校验审核状态
if (!token) return res.json({ code: 401, msg: "请先登录账号" });
const session = db.data.sessions.find(s => s.token === token);
if (!session) return res.json({ code: 401, msg: "登录过期" });
const user = db.data.users.find(u => u.username === session.username);
if (!user || user.status !== "pass") return res.json({ code: 403, msg: "账号未审核,禁止访问" });
res.json({ code: 200, msg: "云电脑访问成功" });
});
app.get('/api/pan', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
await db.read();
if (token) {
const session = db.data.sessions.find(s => s.token === token);
if (session && session.username === ADMIN_USER) {
return res.json({ code: 200, msg: "网盘访问成功(管理员)" });
}
}
if (!token) return res.json({ code: 401, msg: "请先登录账号" });
const session = db.data.sessions.find(s => s.token === token);
if (!session) return res.json({ code: 401, msg: "登录过期" });
const user = db.data.users.find(u => u.username === session.username);
if (!user || user.status !== "pass") return res.json({ code: 403, msg: "账号未审核,禁止访问" });
res.json({ code: 200, msg: "网盘访问成功" });
});
// 登录状态校验(仅前台展示欢迎文字,做完备判空)
app.get('/api/checkLogin', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.json({ code: 401, msg: "未登录" });
await db.read();
const session = db.data.sessions.find(s => s.token === token);
if (!session || !session.username) return res.json({ code: 401, msg: "登录过期" });
const isAdmin = session.username === ADMIN_USER;
res.json({ code: 200, data: { username: session.username, isAdmin } });
});
// ========== 云盘模块依赖、工具函数、存储配置 ==========
const CLOUD_DISK_PATH = path.join(__dirname, 'cloud-storage');
const USERS_FILE = path.join(CLOUD_DISK_PATH, 'users.json');
const MAX_STORAGE = 1 * 1024 * 1024 * 1024; // 1GB
// 初始化云盘存储目录
if (!fs.existsSync(CLOUD_DISK_PATH)) fs.mkdirSync(CLOUD_DISK_PATH, { recursive: true });
if (!fs.existsSync(path.join(CLOUD_DISK_PATH, 'users'))) fs.mkdirSync(path.join(CLOUD_DISK_PATH, 'users'), { recursive: true });
if (!fs.existsSync(USERS_FILE)) fs.writeFileSync(USERS_FILE, '{}', 'utf8');
// 文件大小格式化
function formatSize(sizeBytes) {
if (sizeBytes === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = sizeBytes;
for (let unit of units) {
if (size < 1024) return size.toFixed(2) + ' ' + unit;
size /= 1024;
}
return size.toFixed(2) + ' PB';
}
// 获取用户存储空间占用
// 异步计算存储空间,不会卡死服务
async function getUserTotalSize(username) {
const userDir = path.join(CLOUD_DISK_PATH, 'users', username);
if (!fs.existsSync(userDir)) return 0;
let total = 0;
const scanDir = async (dir) => {
const files = await fs.promises.readdir(dir);
for (const name of files) {
const fullPath = path.join(dir, name);
const stat = await fs.promises.stat(fullPath);
if (stat.isDirectory()) await scanDir(fullPath);
else total += stat.size;
}
};
await scanDir(userDir);
return total;
}
// multer上传配置
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: MAX_STORAGE }
});
// ========== 云盘接口路由 ==========
// 获取用户信息、存储容量
app.get('/api/cloud/user-info', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.json({ code: 401, msg: "请先登录账号" });
await db.read();
const session = db.data.sessions.find(s => s.token === token);
if (!session) return res.json({ code: 401, msg: "登录过期" });
const username = session.username;
const userDir = path.join(CLOUD_DISK_PATH, 'users', username);
if (!fs.existsSync(userDir)) fs.mkdirSync(userDir, { recursive: true });
// 注释掉同步阻塞的空间计算函数
// const used = getUserTotalSize(username);
const used = 0;
res.json({
code: 200,
data: {
username,
used,
max: MAX_STORAGE,
usedText: "0 B",
maxText: "1.00 GB"
}
});
});
// 获取文件列表
app.get('/api/cloud/list', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.json({ code: 401, msg: "请先登录账号" });
await db.read();
const session = db.data.sessions.find(s => s.token === token);
if (!session) return res.json({ code: 401, msg: "登录过期" });
// 临时直接返回空数组,不读取本地文件夹,彻底消除同步阻塞
res.json({ code: 200, data: [] });
});
// 新建文件夹
app.post('/api/cloud/mkdir', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.json({ code: 401, msg: "请先登录账号" });
await db.read();
const session = db.data.sessions.find(s => s.token === token);
if (!session) return res.json({ code: 401, msg: "登录过期" });
const username = session.username;
const { path: dirPath, name } = req.body;
const safePath = path.join(CLOUD_DISK_PATH, 'users', username, dirPath, name);
if (!safePath.startsWith(path.join(CLOUD_DISK_PATH, 'users', username))) {
return res.json({ code: 400, msg: "非法路径" });
}
if (fs.existsSync(safePath)) return res.json({ code: 400, msg: "文件夹已存在" });
fs.mkdirSync(safePath, { recursive: true });
res.json({ code: 200, msg: "创建成功" });
});
// 文件上传
app.post('/api/cloud/upload', upload.single('file'), async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.json({ code: 401, msg: "请先登录账号" });
await db.read();
const session = db.data.sessions.find(s => s.token === token);
if (!session) return res.json({ code: 401, msg: "登录过期" });
const username = session.username;
const { path: savePath } = req.body;
const file = req.file;
const totalUsed = getUserTotalSize(username);
if (totalUsed + file.size > MAX_STORAGE) {
return res.json({ code: 400, msg: "存储空间不足" });
}
const targetDir = path.join(CLOUD_DISK_PATH, 'users', username, savePath);
if (!targetDir.startsWith(path.join(CLOUD_DISK_PATH, 'users', username))) {
return res.json({ code: 400, msg: "非法路径" });
}
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
const fullFile = path.join(targetDir, file.originalname);
fs.writeFileSync(fullFile, file.buffer);
res.json({ code: 200, msg: "上传成功" });
});
// 删除文件/文件夹
app.post('/api/cloud/delete', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.json({ code: 401, msg: "请先登录账号" });
await db.read();
const session = db.data.sessions.find(s => s.token === token);
if (!session) return res.json({ code: 401, msg: "登录过期" });
const username = session.username;
const { path: delPath, name } = req.body;
const target = path.join(CLOUD_DISK_PATH, 'users', username, delPath, name);
if (!target.startsWith(path.join(CLOUD_DISK_PATH, 'users', username))) {
return res.json({ code: 400, msg: "非法路径" });
}
if (!fs.existsSync(target)) return res.json({ code: 400, msg: "文件不存在" });
function rmDir(dir) {
if (fs.statSync(dir).isDirectory()) {
fs.readdirSync(dir).forEach(f => rmDir(path.join(dir, f)));
fs.rmdirSync(dir);
} else fs.unlinkSync(dir);
}
rmDir(target);
res.json({ code: 200, msg: "删除成功" });
});
// 文件下载
app.get('/api/cloud/download', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.json({ code: 401, msg: "请先登录账号" });
await db.read();
const session = db.data.sessions.find(s => s.token === token);
if (!session) return res.json({ code: 401, msg: "登录过期" });
const username = session.username;
let filePath = req.query.path || '';
let fileName = req.query.name;
const target = path.join(CLOUD_DISK_PATH, 'users', username, filePath, fileName);
if (!target.startsWith(path.join(CLOUD_DISK_PATH, 'users', username))) {
return res.json({ code: 400, msg: "非法路径" });
}
if (!fs.existsSync(target) || fs.statSync(target).isDirectory()) {
return res.json({ code: 400, msg: "文件不存在" });
}
res.download(target, fileName);
});
app.listen(port, "0.0.0.0", async () => {
await db.read();
console.log(`服务启动,端口:${port}`);
});