2026-07-16 22:51:02 +08:00
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const path = require('path');
|
2026-07-16 23:13:56 +08:00
|
|
|
|
const os = require('os');
|
2026-07-16 22:51:02 +08:00
|
|
|
|
const multer = require('multer');
|
|
|
|
|
|
const initSqlJs = require('sql.js');
|
|
|
|
|
|
const { v4: uuidv4 } = require('uuid');
|
|
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
const ADMIN_PORT = 16888;
|
|
|
|
|
|
const FRONT_PORT = 80;
|
2026-07-16 22:51:02 +08:00
|
|
|
|
|
|
|
|
|
|
let db;
|
|
|
|
|
|
|
2026-07-16 23:13:56 +08:00
|
|
|
|
const DB_PATH = path.join(os.homedir(), '.showpic.db');
|
|
|
|
|
|
|
2026-07-16 22:51:02 +08:00
|
|
|
|
async function initDB() {
|
|
|
|
|
|
const SQL = await initSqlJs();
|
2026-07-16 23:13:56 +08:00
|
|
|
|
if (fs.existsSync(DB_PATH)) {
|
|
|
|
|
|
db = new SQL.Database(fs.readFileSync(DB_PATH));
|
2026-07-16 22:51:02 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
db = new SQL.Database();
|
|
|
|
|
|
}
|
2026-07-16 22:58:46 +08:00
|
|
|
|
db.run(fs.readFileSync(path.join(__dirname, 'database', 'schema.sql'), 'utf8'));
|
|
|
|
|
|
|
|
|
|
|
|
const defaults = {
|
|
|
|
|
|
'feature_enabled': 'false',
|
|
|
|
|
|
'current_group_index': '0',
|
|
|
|
|
|
'current_group_start_time': '0'
|
|
|
|
|
|
};
|
|
|
|
|
|
for (const [k, v] of Object.entries(defaults)) {
|
|
|
|
|
|
const r = runQuery("SELECT value FROM config WHERE key=?", [k]);
|
|
|
|
|
|
if (r.length === 0) db.run("INSERT INTO config (key,value) VALUES (?,?)", [k, v]);
|
|
|
|
|
|
}
|
2026-07-16 22:51:02 +08:00
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
// 确保有默认管理员账号
|
|
|
|
|
|
const userCheck = runQuery("SELECT COUNT(*) as cnt FROM users WHERE username=?", ['admin']);
|
|
|
|
|
|
if (userCheck.length === 0 || userCheck[0].cnt === 0) {
|
|
|
|
|
|
db.run("INSERT INTO users (username, password) VALUES (?,?)", ['admin', 'admin123']);
|
2026-07-16 22:51:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
saveDB();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function saveDB() {
|
|
|
|
|
|
const data = db.export();
|
2026-07-16 23:13:56 +08:00
|
|
|
|
fs.writeFileSync(DB_PATH, Buffer.from(data));
|
2026-07-16 22:51:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function runQuery(sql, params) {
|
|
|
|
|
|
const stmt = db.prepare(sql);
|
2026-07-16 22:58:46 +08:00
|
|
|
|
if (params && params.length > 0) stmt.bind(params);
|
2026-07-16 22:51:02 +08:00
|
|
|
|
const results = [];
|
2026-07-16 22:58:46 +08:00
|
|
|
|
while (stmt.step()) results.push(stmt.getAsObject());
|
2026-07-16 22:51:02 +08:00
|
|
|
|
stmt.free();
|
|
|
|
|
|
return results;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function runExec(sql, params) {
|
|
|
|
|
|
db.run(sql, params || []);
|
2026-07-16 22:58:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getConfig(key) {
|
|
|
|
|
|
const r = runQuery("SELECT value FROM config WHERE key=?", [key]);
|
|
|
|
|
|
return r.length > 0 ? r[0].value : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function setConfig(key, value) {
|
|
|
|
|
|
runExec("INSERT OR REPLACE INTO config (key,value) VALUES (?,?)", [key, String(value)]);
|
2026-07-16 22:51:02 +08:00
|
|
|
|
saveDB();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
function getEnabledGroups() {
|
|
|
|
|
|
return runQuery(`
|
|
|
|
|
|
SELECT g.*, (SELECT COUNT(*) FROM images WHERE group_id=g.id AND enabled=1) as img_count
|
|
|
|
|
|
FROM groups g WHERE g.enabled=1 ORDER BY g.id ASC
|
|
|
|
|
|
`).filter(g => g.img_count > 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 轮播逻辑 ==========
|
|
|
|
|
|
|
|
|
|
|
|
function getCurrentState() {
|
|
|
|
|
|
const featureEnabled = getConfig('feature_enabled') === 'true';
|
|
|
|
|
|
if (!featureEnabled) return { type: 'state', playing: false, groups: [] };
|
|
|
|
|
|
|
|
|
|
|
|
const groups = getEnabledGroups();
|
|
|
|
|
|
if (groups.length === 0) return { type: 'state', playing: false, groups: [] };
|
|
|
|
|
|
|
|
|
|
|
|
const idx = parseInt(getConfig('current_group_index') || '0') % groups.length;
|
|
|
|
|
|
const startTime = parseInt(getConfig('current_group_start_time') || '0');
|
|
|
|
|
|
const now = Math.floor(Date.now() / 1000);
|
|
|
|
|
|
const currentGroup = groups[idx];
|
|
|
|
|
|
|
|
|
|
|
|
let remaining = 0;
|
|
|
|
|
|
if (currentGroup.display_seconds === 0) {
|
|
|
|
|
|
remaining = -1;
|
|
|
|
|
|
} else if (startTime > 0) {
|
|
|
|
|
|
remaining = Math.max(0, startTime + currentGroup.display_seconds - now);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const images = runQuery('SELECT * FROM images WHERE group_id=? AND enabled=1', [currentGroup.id]);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
type: 'state',
|
|
|
|
|
|
playing: true,
|
|
|
|
|
|
current_index: idx,
|
|
|
|
|
|
total: groups.length,
|
|
|
|
|
|
group_name: currentGroup.name,
|
|
|
|
|
|
group_id: currentGroup.id,
|
|
|
|
|
|
display_seconds: currentGroup.display_seconds,
|
|
|
|
|
|
remaining: remaining,
|
|
|
|
|
|
images: images
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-17 09:36:50 +08:00
|
|
|
|
// broadcastState 已移除,前端通过 /api/state 主动拉取
|
2026-07-16 22:58:46 +08:00
|
|
|
|
|
|
|
|
|
|
function startCarouselTimer() {
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
|
if (getConfig('feature_enabled') !== 'true') return;
|
|
|
|
|
|
|
|
|
|
|
|
const groups = getEnabledGroups();
|
|
|
|
|
|
if (groups.length === 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
const idx = parseInt(getConfig('current_group_index') || '0') % groups.length;
|
|
|
|
|
|
const startTime = parseInt(getConfig('current_group_start_time') || '0');
|
|
|
|
|
|
const currentGroup = groups[idx];
|
|
|
|
|
|
|
2026-07-17 09:05:55 +08:00
|
|
|
|
// 无限时分组,不需要切换
|
|
|
|
|
|
if (currentGroup.display_seconds === 0) return;
|
2026-07-16 22:58:46 +08:00
|
|
|
|
|
2026-07-17 09:05:55 +08:00
|
|
|
|
// 首次启动,记录开始时间并广播
|
2026-07-16 22:58:46 +08:00
|
|
|
|
if (startTime === 0) {
|
|
|
|
|
|
setConfig('current_group_start_time', Math.floor(Date.now() / 1000));
|
2026-07-17 09:36:50 +08:00
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const now = Math.floor(Date.now() / 1000);
|
|
|
|
|
|
const elapsed = now - startTime;
|
|
|
|
|
|
|
2026-07-17 09:05:55 +08:00
|
|
|
|
// 时间到了,切换分组并广播
|
2026-07-16 22:58:46 +08:00
|
|
|
|
if (elapsed >= currentGroup.display_seconds) {
|
|
|
|
|
|
const nextIdx = findNextGroupIndex(groups, idx);
|
|
|
|
|
|
setConfig('current_group_index', nextIdx);
|
|
|
|
|
|
setConfig('current_group_start_time', Math.floor(Date.now() / 1000));
|
2026-07-17 09:36:50 +08:00
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function findNextGroupIndex(groups, currentIdx) {
|
|
|
|
|
|
for (let i = 1; i <= groups.length; i++) {
|
|
|
|
|
|
const nextIdx = (currentIdx + i) % groups.length;
|
|
|
|
|
|
if (groups[nextIdx].display_seconds > 0) return nextIdx;
|
2026-07-16 22:51:02 +08:00
|
|
|
|
}
|
2026-07-16 22:58:46 +08:00
|
|
|
|
return currentIdx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resetCarousel() {
|
|
|
|
|
|
setConfig('current_group_index', 0);
|
|
|
|
|
|
setConfig('current_group_start_time', Math.floor(Date.now() / 1000));
|
2026-07-17 09:36:50 +08:00
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========== Multer ==========
|
|
|
|
|
|
const storage = multer.diskStorage({
|
|
|
|
|
|
destination: (req, file, cb) => cb(null, path.join(__dirname, 'uploads')),
|
|
|
|
|
|
filename: (req, file, cb) => cb(null, uuidv4() + path.extname(file.originalname))
|
|
|
|
|
|
});
|
|
|
|
|
|
const upload = multer({ storage });
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 前台服务 (端口 80) ==========
|
|
|
|
|
|
const frontApp = express();
|
|
|
|
|
|
frontApp.use(express.static(path.join(__dirname, 'public')));
|
|
|
|
|
|
frontApp.use('/uploads', express.static(path.join(__dirname, 'uploads')));
|
|
|
|
|
|
|
2026-07-17 09:36:50 +08:00
|
|
|
|
// 首页API,返回当前状态
|
|
|
|
|
|
frontApp.get('/api/state', (req, res) => {
|
|
|
|
|
|
res.json(getCurrentState());
|
2026-07-16 22:58:46 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const frontServer = frontApp.listen(FRONT_PORT, '0.0.0.0', () => {
|
|
|
|
|
|
console.log(`前台运行在 http://0.0.0.0:${FRONT_PORT}`);
|
2026-07-16 22:51:02 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-17 09:36:50 +08:00
|
|
|
|
// ========== 后台服务 (端口 16888) ==========
|
2026-07-16 22:58:46 +08:00
|
|
|
|
const adminApp = express();
|
|
|
|
|
|
adminApp.use(express.json());
|
|
|
|
|
|
adminApp.use(express.urlencoded({ extended: true }));
|
|
|
|
|
|
adminApp.use('/uploads', express.static(path.join(__dirname, 'uploads')));
|
2026-07-16 22:51:02 +08:00
|
|
|
|
|
2026-07-16 23:01:43 +08:00
|
|
|
|
// 路由 - 后台直接在根路径,未登录跳转登录页
|
|
|
|
|
|
adminApp.get('/login', (req, res) => res.sendFile(path.join(__dirname, 'public', 'login.html')));
|
|
|
|
|
|
adminApp.get('/', (req, res) => res.sendFile(path.join(__dirname, 'public', 'admin.html')));
|
|
|
|
|
|
|
|
|
|
|
|
// 静态文件(不自动serve index.html)
|
|
|
|
|
|
adminApp.use(express.static(path.join(__dirname, 'public'), { index: false }));
|
|
|
|
|
|
|
2026-07-16 22:51:02 +08:00
|
|
|
|
const authMiddleware = (req, res, next) => {
|
|
|
|
|
|
const token = req.headers.authorization?.split(' ')[1];
|
2026-07-16 22:58:46 +08:00
|
|
|
|
if (!token || token !== 'admin-token') return res.status(401).json({ error: '未授权' });
|
2026-07-16 22:51:02 +08:00
|
|
|
|
next();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
// 登录
|
|
|
|
|
|
adminApp.post('/api/login', (req, res) => {
|
2026-07-16 22:51:02 +08:00
|
|
|
|
const { username, password } = req.body;
|
2026-07-16 22:58:46 +08:00
|
|
|
|
const users = runQuery('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);
|
|
|
|
|
|
if (users.length > 0) {
|
2026-07-16 22:51:02 +08:00
|
|
|
|
res.json({ success: true, token: 'admin-token' });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
res.status(401).json({ success: false, error: '用户名或密码错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
// 修改密码
|
|
|
|
|
|
adminApp.put('/api/change-password', authMiddleware, (req, res) => {
|
|
|
|
|
|
const { oldPassword, newPassword } = req.body;
|
|
|
|
|
|
if (!oldPassword || !newPassword) {
|
|
|
|
|
|
return res.status(400).json({ error: '请输入旧密码和新密码' });
|
|
|
|
|
|
}
|
|
|
|
|
|
const users = runQuery('SELECT * FROM users WHERE username = ? AND password = ?', ['admin', oldPassword]);
|
|
|
|
|
|
if (users.length === 0) {
|
|
|
|
|
|
return res.status(400).json({ error: '旧密码错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
runExec('UPDATE users SET password = ? WHERE username = ?', [newPassword, 'admin']);
|
|
|
|
|
|
saveDB();
|
|
|
|
|
|
res.json({ success: true });
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 分组
|
|
|
|
|
|
adminApp.get('/api/groups', (req, res) => {
|
|
|
|
|
|
res.json(runQuery('SELECT * FROM groups ORDER BY id ASC'));
|
2026-07-16 22:51:02 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
adminApp.post('/api/groups', authMiddleware, (req, res) => {
|
2026-07-16 22:51:02 +08:00
|
|
|
|
const { name, display_seconds } = req.body;
|
2026-07-16 22:58:46 +08:00
|
|
|
|
runExec('INSERT INTO groups (name, display_seconds) VALUES (?, ?)', [name || '新分组', display_seconds || 30]);
|
|
|
|
|
|
saveDB();
|
|
|
|
|
|
const id = db.exec("SELECT last_insert_rowid()")[0].values[0][0];
|
2026-07-17 09:36:50 +08:00
|
|
|
|
|
2026-07-16 22:51:02 +08:00
|
|
|
|
res.json({ id });
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
adminApp.put('/api/groups/:id', authMiddleware, (req, res) => {
|
2026-07-16 22:51:02 +08:00
|
|
|
|
const { name, display_seconds, enabled } = req.body;
|
2026-07-16 22:58:46 +08:00
|
|
|
|
runExec('UPDATE groups SET name=?, display_seconds=?, enabled=? WHERE id=?',
|
|
|
|
|
|
[name, display_seconds, enabled, parseInt(req.params.id)]);
|
|
|
|
|
|
saveDB();
|
2026-07-17 09:36:50 +08:00
|
|
|
|
|
2026-07-16 22:51:02 +08:00
|
|
|
|
res.json({ success: true });
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
adminApp.put('/api/groups/:id/settime', authMiddleware, (req, res) => {
|
2026-07-16 22:51:02 +08:00
|
|
|
|
const { display_seconds } = req.body;
|
2026-07-16 22:58:46 +08:00
|
|
|
|
runExec('UPDATE groups SET display_seconds=? WHERE id=?', [display_seconds, parseInt(req.params.id)]);
|
|
|
|
|
|
saveDB();
|
2026-07-17 09:36:50 +08:00
|
|
|
|
|
2026-07-16 22:51:02 +08:00
|
|
|
|
res.json({ success: true });
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
adminApp.delete('/api/groups/:id', authMiddleware, (req, res) => {
|
2026-07-16 22:51:02 +08:00
|
|
|
|
const id = parseInt(req.params.id);
|
2026-07-16 22:58:46 +08:00
|
|
|
|
const imgs = runQuery('SELECT filename FROM images WHERE group_id=?', [id]);
|
|
|
|
|
|
imgs.forEach(img => {
|
|
|
|
|
|
const fp = path.join(__dirname, 'uploads', img.filename);
|
|
|
|
|
|
if (fs.existsSync(fp)) fs.unlinkSync(fp);
|
2026-07-16 22:51:02 +08:00
|
|
|
|
});
|
2026-07-16 22:58:46 +08:00
|
|
|
|
runExec('DELETE FROM images WHERE group_id=?', [id]);
|
|
|
|
|
|
runExec('DELETE FROM groups WHERE id=?', [id]);
|
|
|
|
|
|
saveDB();
|
|
|
|
|
|
resetCarousel();
|
2026-07-16 22:51:02 +08:00
|
|
|
|
res.json({ success: true });
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
// 图片
|
|
|
|
|
|
adminApp.get('/api/images', (req, res) => {
|
|
|
|
|
|
const gid = parseInt(req.query.group_id);
|
|
|
|
|
|
if (gid) {
|
|
|
|
|
|
res.json(runQuery('SELECT * FROM images WHERE group_id=? ORDER BY id DESC', [gid]));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
res.json(runQuery('SELECT * FROM images ORDER BY id DESC'));
|
2026-07-16 22:51:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
adminApp.post('/api/upload', authMiddleware, upload.single('image'), (req, res) => {
|
|
|
|
|
|
if (!req.file) return res.status(400).json({ error: '没有上传文件' });
|
|
|
|
|
|
const group_id = parseInt(req.body.group_id);
|
|
|
|
|
|
if (!group_id) return res.status(400).json({ error: '缺少分组ID' });
|
|
|
|
|
|
runExec('INSERT INTO images (group_id, filename, original_name, size) VALUES (?,?,?,?)',
|
|
|
|
|
|
[group_id, req.file.filename, req.file.originalname, req.file.size || 0]);
|
|
|
|
|
|
saveDB();
|
|
|
|
|
|
const id = db.exec("SELECT last_insert_rowid()")[0].values[0][0];
|
2026-07-17 09:36:50 +08:00
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
res.json({ id, filename: req.file.filename });
|
2026-07-16 22:51:02 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
adminApp.put('/api/images/:id/enable', authMiddleware, (req, res) => {
|
|
|
|
|
|
runExec('UPDATE images SET enabled=1 WHERE id=?', [parseInt(req.params.id)]);
|
2026-07-17 09:36:50 +08:00
|
|
|
|
saveDB(); res.json({ success: true });
|
2026-07-16 22:51:02 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
adminApp.put('/api/images/:id/disable', authMiddleware, (req, res) => {
|
|
|
|
|
|
runExec('UPDATE images SET enabled=0 WHERE id=?', [parseInt(req.params.id)]);
|
2026-07-17 09:36:50 +08:00
|
|
|
|
saveDB(); res.json({ success: true });
|
2026-07-16 22:51:02 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
adminApp.delete('/api/images/:id', authMiddleware, (req, res) => {
|
2026-07-16 22:51:02 +08:00
|
|
|
|
const id = parseInt(req.params.id);
|
2026-07-16 22:58:46 +08:00
|
|
|
|
const imgs = runQuery('SELECT filename FROM images WHERE id=?', [id]);
|
|
|
|
|
|
if (imgs.length > 0) {
|
|
|
|
|
|
const fp = path.join(__dirname, 'uploads', imgs[0].filename);
|
|
|
|
|
|
if (fs.existsSync(fp)) fs.unlinkSync(fp);
|
2026-07-16 22:51:02 +08:00
|
|
|
|
}
|
2026-07-16 22:58:46 +08:00
|
|
|
|
runExec('DELETE FROM images WHERE id=?', [id]);
|
2026-07-17 09:36:50 +08:00
|
|
|
|
saveDB(); res.json({ success: true });
|
2026-07-16 22:51:02 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
// 配置
|
|
|
|
|
|
adminApp.get('/api/config', (req, res) => {
|
2026-07-16 22:51:02 +08:00
|
|
|
|
const rows = runQuery('SELECT * FROM config');
|
|
|
|
|
|
const config = {};
|
2026-07-16 22:58:46 +08:00
|
|
|
|
rows.forEach(r => config[r.key] = r.value);
|
2026-07-16 22:51:02 +08:00
|
|
|
|
res.json(config);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
adminApp.put('/api/config', authMiddleware, (req, res) => {
|
|
|
|
|
|
setConfig(req.body.key, req.body.value);
|
2026-07-17 09:36:50 +08:00
|
|
|
|
|
2026-07-16 22:51:02 +08:00
|
|
|
|
res.json({ success: true });
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
// 重置轮播
|
|
|
|
|
|
adminApp.post('/api/carousel/reset', authMiddleware, (req, res) => {
|
|
|
|
|
|
resetCarousel();
|
|
|
|
|
|
res.json({ success: true });
|
2026-07-16 22:51:02 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
// 路由 - 后台直接在根路径,未登录跳转登录页
|
|
|
|
|
|
adminApp.get('/login', (req, res) => res.sendFile(path.join(__dirname, 'public', 'login.html')));
|
|
|
|
|
|
adminApp.get('/', (req, res) => res.sendFile(path.join(__dirname, 'public', 'admin.html')));
|
2026-07-16 22:51:02 +08:00
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
adminApp.listen(ADMIN_PORT, '0.0.0.0', () => {
|
|
|
|
|
|
console.log(`后台运行在 http://0.0.0.0:${ADMIN_PORT}`);
|
2026-07-16 22:51:02 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-16 22:58:46 +08:00
|
|
|
|
// ========== 启动 ==========
|
2026-07-16 22:51:02 +08:00
|
|
|
|
initDB().then(() => {
|
2026-07-16 22:58:46 +08:00
|
|
|
|
startCarouselTimer();
|
|
|
|
|
|
console.log('数据库初始化完成');
|
2026-07-16 22:51:02 +08:00
|
|
|
|
}).catch(err => {
|
2026-07-16 22:58:46 +08:00
|
|
|
|
console.error('启动失败:', err);
|
2026-07-16 22:51:02 +08:00
|
|
|
|
process.exit(1);
|
|
|
|
|
|
});
|