Files
tongjiweb/server/app.js
T
2026-06-04 14:25:39 +08:00

114 lines
4.4 KiB
JavaScript

const express = require('express');
const cors = require('cors');
const path = require('path');
const { initDB } = require('./db');
const { authenticateToken } = require('./middleware/auth');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ limit: '50mb', extended: true }));
// Static files (for serving built frontend)
app.use(express.static(path.join(__dirname, 'public')));
async function startServer() {
// Initialize database
const db = await initDB();
// Make db available to routes
app.locals.db = db;
// Auth routes (login is public)
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { JWT_SECRET } = require('./middleware/auth');
app.post('/api/auth/login', (req, res) => {
const { username, password } = req.body;
if (!username || !password) return res.status(400).json({ code: 400, message: '用户名和密码不能为空' });
const user = db.get('SELECT * FROM users WHERE username = ?', [username]);
if (!user) return res.status(401).json({ code: 401, message: '用户名或密码错误' });
if (user.status !== 1) return res.status(403).json({ code: 403, message: '账号已被禁用' });
if (user.role === 'sales') return res.status(403).json({ code: 403, message: '销售人员无需登录系统' });
if (!bcrypt.compareSync(password, user.password)) return res.status(401).json({ code: 401, message: '用户名或密码错误' });
const token = jwt.sign({ id: user.id, username: user.username, role: user.role, realname: user.realname }, JWT_SECRET, { expiresIn: '24h' });
res.json({ code: 200, message: '登录成功', data: { token, user: { id: user.id, username: user.username, realname: user.realname, role: user.role } } });
});
app.get('/api/auth/me', authenticateToken, (req, res) => {
const user = db.get('SELECT id, username, realname, role, status FROM users WHERE id = ?', [req.user.id]);
if (!user) return res.status(404).json({ code: 404, message: '用户不存在' });
res.json({ code: 200, data: user });
});
// Module routes
const adminRoutes = require('./routes/admin');
const rdRoutes = require('./routes/rd');
app.use('/api/admin', authenticateToken, adminRoutes);
app.use('/api/rd', authenticateToken, rdRoutes);
// Health check
app.get('/api/health', (req, res) => {
res.json({ code: 200, message: 'OK', time: new Date().toISOString() });
});
// Fallback: serve index.html for SPA
app.get('*', (req, res) => {
const indexPath = path.join(__dirname, 'public', 'index.html');
const fs = require('fs');
if (fs.existsSync(indexPath)) {
res.sendFile(indexPath);
} else {
res.status(404).json({ code: 404, message: 'Not found' });
}
});
app.listen(PORT, () => {
console.log(`🚀 WorkTime Server running at http://localhost:${PORT}`);
});
// ===== Auto-backup cron: daily at 03:00 =====
const cron = require('node-cron');
const fsBackup = require('fs');
const { buildBackupJson, BACKUP_DIR, ensureBackupDir } = adminRoutes;
function runAutoBackup() {
try {
ensureBackupDir();
const json = buildBackupJson(db);
const date = new Date().toISOString().slice(0, 10);
const filePath = `${BACKUP_DIR}/backup-${date}.json`;
fsBackup.writeFileSync(filePath, json, 'utf8');
console.log(`[AutoBackup] Saved: ${filePath}`);
// Purge backups older than 30 days
const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
fsBackup.readdirSync(BACKUP_DIR).forEach(f => {
if (!f.endsWith('.json')) return;
const fp = `${BACKUP_DIR}/${f}`;
if (fsBackup.statSync(fp).mtimeMs < cutoff) {
fsBackup.unlinkSync(fp);
console.log(`[AutoBackup] Purged old backup: ${f}`);
}
});
} catch (e) {
console.error('[AutoBackup] Error:', e.message);
}
}
cron.schedule('0 3 * * *', runAutoBackup);
console.log('[AutoBackup] Scheduled daily at 03:00');
}
startServer().catch(err => {
console.error('Failed to start server:', err);
process.exit(1);
});
module.exports = app;