文件
jw-beauty/server/src/app.js
T
Guoguo 92416261ee feat: production readiness — feature gaps + config hardening
Miniprogram:
- Add BLE reconnect button on home page when disconnected
- Add loading states to index, profile, subscribe-plans pages
- Add profile editing (avatar + nickname) with COS upload
- Enable pull-to-refresh on history page
- Fix auto-scan self.options → self.data inconsistency
- Add console.error to silent catch blocks
- Gate 'Simple Peripheral' BLE scan behind __DEV__ flag
- Add production config comment to env.js

Admin console:
- Add empty state '暂无数据' to all 5 list views
- Replace plain text plan input with select dropdown
- Add type="date" to record date filters
- Add production config comment

Server:
- CORS origin restricted in production (env CORS_ORIGIN)
- DB pool size configurable via DB_POOL_SIZE env var
- .env.example updated with WeChat Pay + production fields
2026-05-15 09:07:41 -07:00

69 行
2.2 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(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()
})
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(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((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'))
})
module.exports = app