feat: 小菜记账 v1.0 - 完整功能实现
核心功能: - 记账CRUD(支出/收入/分类/备注/日期) - 统计分析(概览/分类占比/每日趋势) - 预算管理(按月设置/进度条/超支提醒) - 数据导出CSV 安全与认证: - HMAC-SHA256签名token认证 - 用户数据隔离 - 输入验证与错误处理 - CORS配置 前端优化: - 骨架屏加载 - 账单按日期分组 - 预算页面重构(快捷预设+Numpad) - SvgIcon组件(H5+微信双端适配) - 下拉刷新 后端优化: - 共享日期工具函数 - 数据库连接池优化 - 健康检查端点 - 优雅关闭处理 技术栈: - 前端:Uni-app (Vue 3 + Pinia) - 后端:Node.js + Express + MySQL
This commit is contained in:
74
server/src/index.ts
Normal file
74
server/src/index.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import 'dotenv/config'
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { authMiddleware } from './middleware/auth'
|
||||
import { initDatabase } from './db/init'
|
||||
import pool from './db/connection'
|
||||
import authRoutes from './routes/auth'
|
||||
import transactionRoutes from './routes/transaction'
|
||||
import statsRoutes from './routes/stats'
|
||||
import budgetRoutes from './routes/budget'
|
||||
import categoryRoutes from './routes/category'
|
||||
|
||||
// Warn if token secret is using default fallback
|
||||
if (!process.env.TOKEN_SECRET) {
|
||||
console.warn('[Security] TOKEN_SECRET not set — using default fallback. Set TOKEN_SECRET in .env for production!')
|
||||
}
|
||||
|
||||
const app = express()
|
||||
const PORT = process.env.PORT || 3000
|
||||
const ALLOWED_ORIGINS = (process.env.CORS_ORIGINS || '').split(',').filter(Boolean)
|
||||
|
||||
app.use(cors({
|
||||
origin: (origin, callback) => {
|
||||
// No origin = WeChat mini-program request (always allow)
|
||||
if (!origin) return callback(null, true)
|
||||
// If no CORS_ORIGINS configured, allow all (dev mode)
|
||||
if (ALLOWED_ORIGINS.length === 0) return callback(null, true)
|
||||
// Check whitelist
|
||||
if (ALLOWED_ORIGINS.includes(origin)) return callback(null, true)
|
||||
callback(null, false)
|
||||
},
|
||||
credentials: true,
|
||||
}))
|
||||
app.use(express.json({ limit: '10kb' }))
|
||||
app.use(authMiddleware)
|
||||
|
||||
app.use('/api/auth', authRoutes)
|
||||
app.use('/api/transactions', transactionRoutes)
|
||||
app.use('/api/stats', statsRoutes)
|
||||
app.use('/api/budget', budgetRoutes)
|
||||
app.use('/api/categories', categoryRoutes)
|
||||
|
||||
app.get('/api/health', async (_req, res) => {
|
||||
try {
|
||||
await pool.query('SELECT 1')
|
||||
res.json({ status: 'ok', db: 'connected', time: new Date().toISOString() })
|
||||
} catch {
|
||||
res.status(503).json({ status: 'error', db: 'disconnected', time: new Date().toISOString() })
|
||||
}
|
||||
})
|
||||
|
||||
let server: any
|
||||
|
||||
initDatabase().then(() => {
|
||||
server = app.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`)
|
||||
})
|
||||
})
|
||||
|
||||
// Graceful shutdown
|
||||
function shutdown(signal: string) {
|
||||
console.log(`[Server] ${signal} received, shutting down gracefully...`)
|
||||
server?.close(() => {
|
||||
pool.end().then(() => {
|
||||
console.log('[Server] All connections closed')
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
// Force exit after 10s
|
||||
setTimeout(() => process.exit(1), 10000)
|
||||
}
|
||||
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'))
|
||||
process.on('SIGINT', () => shutdown('SIGINT'))
|
||||
Reference in New Issue
Block a user