-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
159 lines (141 loc) · 5.31 KB
/
app.js
File metadata and controls
159 lines (141 loc) · 5.31 KB
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
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const app = express();
const PORT = 3000;
app.set('trust proxy', true);
app.use(bodyParser.json());
// ------------------------------
// 日志系统(匹配指定格式+北京时间)
// ------------------------------
const LOG_DIR = path.join(__dirname, 'logs');
if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true });
// 生成指定格式的北京时间字符串:YYYY/M/D HH:mm:ss
function getBeijingTimeString() {
const offset = 8 * 60 * 60 * 1000; // 北京时间偏移8小时
const now = new Date(Date.now() + offset);
const year = now.getUTCFullYear();
const month = now.getUTCMonth() + 1; // 不补零,如1月→1而非01
const day = now.getUTCDate(); // 不补零,如8日→8而非08
const hours = String(now.getUTCHours()).padStart(2, '0');
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
}
// 日志文件名仍按原规则(补零),保证文件命名规范
function getLogFileName(type) {
const offset = 8 * 60 * 60 * 1000;
const now = new Date(Date.now() + offset);
const year = now.getUTCFullYear();
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
const day = String(now.getUTCDate()).padStart(2, '0');
return path.join(LOG_DIR, `${type}-${year}-${month}-${day}.log`);
}
// 写入指定格式的访问日志(包含User-Agent)
function writeAccessLog(req) {
const timeStr = getBeijingTimeString();
const userAgent = req.headers['user-agent'] || ''; // 获取浏览器UA信息
// 严格匹配指定的输出格式
const log = `[${timeStr}] [IP: ${req.ip}] [${req.method}] ${req.originalUrl} - "${userAgent}"\n`;
fs.appendFileSync(getLogFileName('access'), log);
}
app.use((req, res, next) => {
writeAccessLog(req);
next();
});
// 错误处理(不写错误日志)
app.use((err, req, res, next) => {
res.status(500).json({ success: false, message: '服务器错误' });
});
// ------------------------------
// 静态文件
// ------------------------------
app.use(express.static('public'));
// ------------------------------
// 数据文件
// ------------------------------
const DATA_FILE = path.join(__dirname, 'data.json');
if (!fs.existsSync(DATA_FILE)) fs.writeFileSync(DATA_FILE, '[]');
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'admin';
// ------------------------------
// API
// ------------------------------
app.post('/api/verify-password', (req, res) => {
res.json({ success: req.body.password === ADMIN_PASSWORD });
});
app.get('/api/cards', (req, res) => {
try {
const data = fs.readFileSync(DATA_FILE, 'utf8');
res.json(JSON.parse(data));
} catch (err) {
res.json([]);
}
});
app.post('/api/cards', (req, res) => {
try {
fs.writeFileSync(DATA_FILE, JSON.stringify(req.body, null, 2));
res.json({ success: true });
} catch (err) {
res.json({ success: false });
}
});
app.post('/api/sync', (req, res) => {
exec('node sync.js', (err, stdout, stderr) => {
if (err) return res.json({ success: false, message: '同步失败' });
res.json({ success: true, message: '同步成功' });
});
});
// ------------------------------
// 日志可视化 API(含多日期查看功能)
// ------------------------------
// 1. 获取所有访问日志文件名(按日期倒序)
app.get('/api/logs/files', (req, res) => {
try {
// 读取日志目录下所有access-xxx.log文件
const files = fs.readdirSync(LOG_DIR)
.filter(file => file.startsWith('access-') && file.endsWith('.log'))
.sort((a, b) => {
// 按文件名中的日期倒序(最新日期在前)
const dateA = a.replace('access-', '').replace('.log', '');
const dateB = b.replace('access-', '').replace('.log', '');
return dateB.localeCompare(dateA);
});
res.json({ success: true, files });
} catch (err) {
res.json({ success: false, message: '获取日志文件列表失败' });
}
});
// 2. 按文件名加载指定日期日志
app.get('/api/logs/:filename', (req, res) => {
const filename = req.params.filename;
// 验证文件名格式(防止路径穿越攻击)
if (!filename.startsWith('access-') || !filename.endsWith('.log')) {
return res.status(400).send('无效的日志文件名');
}
const file = path.join(LOG_DIR, filename);
fs.readFile(file, 'utf8', (err, data) => {
if (err) return res.status(404).send('日志文件不存在');
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.send(data);
});
});
// 3. 兼容原有接口(加载当日日志)
app.get('/api/logs', (req, res) => {
const file = getLogFileName('access');
fs.readFile(file, 'utf8', (err, data) => {
if (err) return res.status(404).send('日志不存在');
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.send(data);
});
});
app.get('/logs', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'logs', 'index.html'));
});
// ------------------------------
// 启动服务器
// ------------------------------
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server running on port ${PORT} (Beijing Time)`);
});