436 lines
15 KiB
JavaScript
436 lines
15 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const path = require('path');
|
|
const db = require('./db');
|
|
|
|
// 导入路由模块
|
|
const financeRouter = require('./finance-api');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 5000;
|
|
|
|
// 中间件
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
// 静态文件服务 - 前端应用
|
|
app.use('/app', express.static(path.join(__dirname, '../frontend/dist')));
|
|
|
|
// 健康检查
|
|
app.get('/health', async (req, res) => {
|
|
try {
|
|
// 测试数据库连接
|
|
await db.query('SELECT 1');
|
|
|
|
res.json({
|
|
status: 'healthy',
|
|
service: 'company-finance-system',
|
|
timestamp: new Date().toISOString(),
|
|
version: '1.0.0',
|
|
database: 'connected',
|
|
endpoints: {
|
|
frontend: '/app/index.html',
|
|
api: '/api/*',
|
|
finance: '/api/finance/*',
|
|
test: '/test'
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
status: 'unhealthy',
|
|
service: 'company-finance-system',
|
|
timestamp: new Date().toISOString(),
|
|
database: 'disconnected',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// API路由
|
|
app.use('/api/finance', financeRouter);
|
|
|
|
// 客户管理API
|
|
app.get('/api/customers', async (req, res) => {
|
|
try {
|
|
const result = await db.query(`
|
|
SELECT
|
|
c.*,
|
|
COUNT(ct.contact_id) as contact_count
|
|
FROM customers c
|
|
LEFT JOIN contacts ct ON c.customer_id = ct.customer_id
|
|
GROUP BY c.customer_id
|
|
ORDER BY c.created_at DESC
|
|
`);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.rows,
|
|
count: result.rows.length
|
|
});
|
|
} catch (error) {
|
|
console.error('获取客户失败:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取客户失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// 供应商管理API
|
|
app.get('/api/suppliers', async (req, res) => {
|
|
try {
|
|
const result = await db.query(`
|
|
SELECT
|
|
s.*,
|
|
COUNT(ct.contact_id) as contact_count
|
|
FROM suppliers s
|
|
LEFT JOIN contacts ct ON s.supplier_id = ct.supplier_id
|
|
GROUP BY s.supplier_id
|
|
ORDER BY s.created_at DESC
|
|
`);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.rows,
|
|
count: result.rows.length
|
|
});
|
|
} catch (error) {
|
|
console.error('获取供应商失败:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取供应商失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// 项目管理API
|
|
app.get('/api/projects', async (req, res) => {
|
|
try {
|
|
const result = await db.query(`
|
|
SELECT
|
|
p.*,
|
|
c.company_name as customer_name,
|
|
s.company_name as supplier_name,
|
|
COUNT(pn.node_id) as payment_node_count,
|
|
SUM(pn.amount) as total_amount
|
|
FROM projects p
|
|
LEFT JOIN customers c ON p.customer_id = c.customer_id
|
|
LEFT JOIN suppliers s ON p.supplier_id = s.supplier_id
|
|
LEFT JOIN payment_nodes pn ON p.project_id = pn.project_id
|
|
GROUP BY p.project_id, c.company_name, s.company_name
|
|
ORDER BY p.created_at DESC
|
|
`);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.rows,
|
|
count: result.rows.length
|
|
});
|
|
} catch (error) {
|
|
console.error('获取项目失败:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '获取项目失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// 测试数据API(用于演示)
|
|
app.get('/api/test-data', async (req, res) => {
|
|
try {
|
|
// 获取各种统计数据
|
|
const [customers, suppliers, projects, paymentNodes, paymentRecords] = await Promise.all([
|
|
db.query('SELECT COUNT(*) as count FROM customers'),
|
|
db.query('SELECT COUNT(*) as count FROM suppliers'),
|
|
db.query('SELECT COUNT(*) as count FROM projects'),
|
|
db.query('SELECT COUNT(*) as count FROM payment_nodes'),
|
|
db.query('SELECT COUNT(*) as count FROM payment_records')
|
|
]);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
customers: customers.rows[0].count,
|
|
suppliers: suppliers.rows[0].count,
|
|
projects: projects.rows[0].count,
|
|
paymentNodes: paymentNodes.rows[0].count,
|
|
paymentRecords: paymentRecords.rows[0].count,
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.json({
|
|
success: false,
|
|
message: '获取测试数据失败',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// 测试页面
|
|
app.get('/test', (req, res) => {
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>公司财务管理系统 - 生产环境测试</title>
|
|
<meta charset="utf-8">
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }
|
|
.container { max-width: 1200px; margin: 0 auto; padding: 40px 20px; }
|
|
.header { text-align: center; margin-bottom: 40px; color: white; }
|
|
h1 { font-size: 48px; margin-bottom: 10px; text-shadow: 0 2px 10px rgba(0,0,0,0.2); }
|
|
.subtitle { font-size: 18px; opacity: 0.9; }
|
|
.dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 40px; }
|
|
.card { background: white; border-radius: 15px; padding: 30px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); transition: transform 0.3s; }
|
|
.card:hover { transform: translateY(-5px); }
|
|
.card h2 { color: #333; margin-bottom: 20px; border-bottom: 2px solid #667eea; padding-bottom: 10px; }
|
|
.btn { display: inline-block; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 24px; border-radius: 8px; text-decoration: none; margin: 5px; border: none; cursor: pointer; font-weight: 500; }
|
|
.btn:hover { opacity: 0.9; }
|
|
.btn-secondary { background: #4CAF50; }
|
|
.status { padding: 10px; border-radius: 8px; margin: 10px 0; }
|
|
.status-ok { background: #e8f5e9; border: 1px solid #4CAF50; color: #2e7d32; }
|
|
.status-error { background: #ffebee; border: 1px solid #f44336; color: #c62828; }
|
|
.system-info { background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 15px; padding: 30px; margin-top: 40px; color: white; }
|
|
.info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-top: 20px; }
|
|
.info-item { padding: 15px; background: rgba(255,255,255,0.1); border-radius: 8px; }
|
|
.quick-links { display: flex; flex-wrap: wrap; gap: 10px; margin: 20px 0; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>🏢 公司财务管理系统</h1>
|
|
<div class="subtitle">生产环境 v1.0.0 | 服务器: 43.161.248.209:${PORT}</div>
|
|
</div>
|
|
|
|
<div class="dashboard">
|
|
<div class="card">
|
|
<h2>🚀 立即使用</h2>
|
|
<p>访问完整的前端应用程序,开始管理您的财务。</p>
|
|
<div class="quick-links">
|
|
<a href="/app/index.html" class="btn">进入系统</a>
|
|
<a href="/health" class="btn btn-secondary">健康检查</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>🔧 系统测试</h2>
|
|
<p>测试各个组件是否正常工作。</p>
|
|
<div class="quick-links">
|
|
<button class="btn" onclick="testAPI()">测试API</button>
|
|
<button class="btn" onclick="testDatabase()">测试数据库</button>
|
|
<button class="btn" onclick="testFrontend()">测试前端</button>
|
|
</div>
|
|
<div id="test-results"></div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>📊 系统状态</h2>
|
|
<div id="system-status">检查中...</div>
|
|
<div class="quick-links">
|
|
<a href="/api/test-data" class="btn" target="_blank">查看数据统计</a>
|
|
<a href="/api/customers" class="btn" target="_blank">客户API</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="system-info">
|
|
<h2>📋 系统信息</h2>
|
|
<div class="info-grid">
|
|
<div class="info-item">
|
|
<div style="font-size: 12px; opacity: 0.8;">服务器IP</div>
|
|
<div style="font-size: 18px; font-weight: bold;">43.161.248.209</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div style="font-size: 12px; opacity: 0.8;">服务端口</div>
|
|
<div style="font-size: 18px; font-weight: bold;">${PORT}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div style="font-size: 12px; opacity: 0.8;">数据库</div>
|
|
<div style="font-size: 18px; font-weight: bold;">PostgreSQL</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div style="font-size: 12px; opacity: 0.8;">前端技术</div>
|
|
<div style="font-size: 18px; font-weight: bold;">React + Ant Design</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-top: 30px;">
|
|
<h3>🔗 快速链接</h3>
|
|
<div class="quick-links">
|
|
<a href="/api/finance/payment-nodes" class="btn" target="_blank">付款节点API</a>
|
|
<a href="/api/finance/payment-records" class="btn" target="_blank">付款记录API</a>
|
|
<a href="/api/finance/exchange-rates" class="btn" target="_blank">汇率API</a>
|
|
<a href="/api/finance/finance-stats" class="btn" target="_blank">财务统计</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 测试函数
|
|
async function testAPI() {
|
|
const results = document.getElementById('test-results');
|
|
results.innerHTML = '<div class="status">测试API连接...</div>';
|
|
|
|
try {
|
|
const response = await fetch('/health');
|
|
const data = await response.json();
|
|
results.innerHTML = \`
|
|
<div class="status status-ok">
|
|
✅ API正常<br>
|
|
服务: \${data.service}<br>
|
|
数据库: \${data.database}<br>
|
|
时间: \${new Date(data.timestamp).toLocaleString()}
|
|
</div>
|
|
\`;
|
|
} catch (error) {
|
|
results.innerHTML = \`<div class="status status-error">❌ API连接失败: \${error.message}</div>\`;
|
|
}
|
|
}
|
|
|
|
async function testDatabase() {
|
|
const results = document.getElementById('test-results');
|
|
results.innerHTML = '<div class="status">测试数据库连接...</div>';
|
|
|
|
try {
|
|
const response = await fetch('/api/test-data');
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
results.innerHTML = \`
|
|
<div class="status status-ok">
|
|
✅ 数据库连接正常<br>
|
|
客户数: \${data.data.customers}<br>
|
|
供应商数: \${data.data.suppliers}<br>
|
|
项目数: \${data.data.projects}
|
|
</div>
|
|
\`;
|
|
} else {
|
|
results.innerHTML = \`<div class="status status-error">⚠️ 数据库连接可能有问题</div>\`;
|
|
}
|
|
} catch (error) {
|
|
results.innerHTML = \`<div class="status status-error">❌ 数据库测试失败: \${error.message}</div>\`;
|
|
}
|
|
}
|
|
|
|
async function testFrontend() {
|
|
const results = document.getElementById('test-results');
|
|
results.innerHTML = '<div class="status">测试前端访问...</div>';
|
|
|
|
try {
|
|
const response = await fetch('/app/index.html');
|
|
if (response.ok) {
|
|
results.innerHTML = '<div class="status status-ok">✅ 前端页面可正常访问</div>';
|
|
} else {
|
|
results.innerHTML = '<div class="status status-error">❌ 前端页面访问失败</div>';
|
|
}
|
|
} catch (error) {
|
|
results.innerHTML = \`<div class="status status-error">❌ 前端测试失败: \${error.message}</div>\`;
|
|
}
|
|
}
|
|
|
|
// 检查系统状态
|
|
async function checkSystemStatus() {
|
|
const statusEl = document.getElementById('system-status');
|
|
|
|
try {
|
|
const response = await fetch('/health');
|
|
const data = await response.json();
|
|
|
|
if (data.status === 'healthy') {
|
|
statusEl.innerHTML = \`
|
|
<div class="status status-ok">
|
|
✅ 系统运行正常<br>
|
|
🔗 <a href="/app/index.html" style="color: #2e7d32;">点击进入系统</a>
|
|
</div>
|
|
\`;
|
|
} else {
|
|
statusEl.innerHTML = \`<div class="status status-error">⚠️ 系统状态异常: \${data.status}</div>\`;
|
|
}
|
|
} catch (error) {
|
|
statusEl.innerHTML = \`<div class="status status-error">❌ 无法获取系统状态: \${error.message}</div>\`;
|
|
}
|
|
}
|
|
|
|
// 页面加载时自动检查
|
|
window.onload = function() {
|
|
checkSystemStatus();
|
|
testAPI();
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`);
|
|
});
|
|
|
|
// 默认路由重定向到前端
|
|
app.get('/', (req, res) => {
|
|
res.redirect('/app/index.html');
|
|
});
|
|
|
|
// 404处理
|
|
app.use((req, res) => {
|
|
res.status(404).send(`
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><title>页面未找到</title><meta charset="utf-8"></head>
|
|
<body style="font-family: Arial; text-align: center; padding: 50px;">
|
|
<h1>404 - 页面未找到</h1>
|
|
<p>您访问的页面不存在。</p>
|
|
<p><a href="/app/index.html">返回首页</a> | <a href="/test">测试页面</a></p>
|
|
</body>
|
|
</html>
|
|
`);
|
|
});
|
|
|
|
// 错误处理中间件
|
|
app.use((err, req, res, next) => {
|
|
console.error(err.stack);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '服务器内部错误',
|
|
error: process.env.NODE_ENV === 'development' ? err.message : undefined
|
|
});
|
|
});
|
|
|
|
// 启动服务器
|
|
const server = app.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`
|
|
🚀 公司财务管理系统 - 生产服务器
|
|
====================================
|
|
📍 服务器地址: http://0.0.0.0:${PORT}
|
|
🌐 外部访问: http://43.161.248.209:${PORT}
|
|
|
|
🔗 重要链接:
|
|
- 前端应用: http://43.161.248.209:${PORT}/app/index.html
|
|
- 测试页面: http://43.161.248.209:${PORT}/test
|
|
- 健康检查: http://43.161.248.209:${PORT}/health
|
|
- API文档: http://43.161.248.209:${PORT}/api/*
|
|
|
|
📊 已启用的模块:
|
|
✅ 财务模块 (付款节点、付款记录、汇率)
|
|
✅ 客户管理
|
|
✅ 供应商管理
|
|
✅ 项目管理
|
|
✅ 静态文件服务
|
|
|
|
⏰ 启动时间: ${new Date().toISOString()}
|
|
====================================
|
|
`);
|
|
});
|
|
|
|
// 优雅关闭
|
|
process.on('SIGTERM', () => {
|
|
console.log('收到SIGTERM信号,正在关闭服务器...');
|
|
server.close(() => {
|
|
console.log('服务器已关闭');
|
|
process.exit(0);
|
|
});
|
|
}); |