- closeStaleOrders: marks payment orders older than 24h as 'closed' - closeExpiredPending: marks expired binding requests (bind_status=3) as cancelled - Both run on app startup (SCF cold start), no cron needed - countPendingByUser already filters to 2h window (previous commit)
92 行
3.1 KiB
JavaScript
92 行
3.1 KiB
JavaScript
const express = require('express')
|
|
const rateLimit = require('express-rate-limit')
|
|
const config = require('./config')
|
|
const { ok, fail } = require('./lib/response')
|
|
const { authMiddleware } = require('./middleware/auth')
|
|
|
|
const app = express()
|
|
|
|
// Rate limiting for auth-sensitive endpoints
|
|
const userLoginLimiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 10,
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
message: { code: 2001, message: 'too_many_attempts' }
|
|
})
|
|
const adminLoginLimiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000,
|
|
max: 5,
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
message: { code: 2001, message: 'too_many_attempts' }
|
|
})
|
|
const uploadLimiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000,
|
|
max: 20,
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
message: { code: 2001, message: 'too_many_attempts' }
|
|
})
|
|
|
|
app.use('/api/v1/payment/wechat/notify', express.raw({ type: 'application/json' }))
|
|
app.use(express.json())
|
|
app.use((req, res, next) => {
|
|
const origin = config.nodeEnv === 'production'
|
|
? (process.env.CORS_ORIGIN || 'https://admin.vsai.net.cn')
|
|
: '*'
|
|
res.header('Access-Control-Allow-Origin', origin)
|
|
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Device-Id, X-App-Version, X-Platform')
|
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
|
|
if (req.method === 'OPTIONS') return res.sendStatus(204)
|
|
next()
|
|
})
|
|
|
|
const paymentLimiter = rateLimit({
|
|
windowMs: 60 * 1000,
|
|
max: 5,
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
message: { code: 2001, message: 'too_many_attempts' }
|
|
})
|
|
|
|
app.use('/api/v1/auth/login', userLoginLimiter)
|
|
app.use('/api/v1/admin/login', adminLoginLimiter)
|
|
app.use('/api/v1/user/avatar', uploadLimiter)
|
|
app.use('/api/v1/user/phone', uploadLimiter)
|
|
app.use('/api/v1/subscription/purchase', paymentLimiter)
|
|
app.use('/api/v1/payment/orders', paymentLimiter)
|
|
|
|
// WeChat Pay callback — must be before authMiddleware (no JWT)
|
|
app.post('/api/v1/payment/wechat/notify', require('./routes/payment-notify'))
|
|
|
|
app.use(authMiddleware)
|
|
|
|
app.get('/health', (req, res) => res.json(ok({ status: 'ok' })))
|
|
|
|
app.use('/api/v1', require('./routes/auth'))
|
|
app.use('/api/v1', require('./routes/user'))
|
|
app.use('/api/v1', require('./routes/device'))
|
|
app.use('/api/v1', require('./routes/subscription'))
|
|
app.use('/api/v1', require('./routes/treatment'))
|
|
app.use('/api/v1/admin', require('./routes/admin'))
|
|
app.use('/api/v1', require('./routes/firmware'))
|
|
app.use('/api/v1', require('./routes/payment'))
|
|
|
|
app.use((req, res) => res.status(404).json(fail(404, 'not_found')))
|
|
|
|
app.use((err, req, res, _next) => {
|
|
console.error('[ERROR]', req.method, req.path, err.code || '', err.sqlMessage || err.message, err.stack)
|
|
res.status(500).json(fail(3001, 'server_error'))
|
|
})
|
|
|
|
// Clean up stale records on startup (SCF cold start)
|
|
require('./dao/payment-order.dao').closeStaleOrders().catch(err => {
|
|
console.error('[STARTUP] closeStaleOrders failed:', err.message)
|
|
})
|
|
require('./dao/binding.dao').closeExpiredPending().catch(err => {
|
|
console.error('[STARTUP] closeExpiredPending failed:', err.message)
|
|
})
|
|
|
|
module.exports = app
|