File size: 15,099 Bytes
e4f4df1
 
 
 
 
 
 
 
792eb60
0f86d0f
e4f4df1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4281bdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7ce3dfa
 
4281bdd
 
 
7ce3dfa
 
 
 
 
 
4281bdd
 
7ce3dfa
 
4281bdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d4b698
 
 
4281bdd
 
 
 
 
 
1d4b698
 
4281bdd
 
 
 
 
 
 
 
 
 
 
4d85b79
f02aec3
4281bdd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e4f4df1
5e331fd
e4f4df1
5e331fd
e4f4df1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
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}`);
});