文件
jw-beauty/server/src/routes/admin.js
T
Guoguo bb4b80f867 refactor: restructure entire project for human maintainability
Server:
- Add Express framework, replace custom router/request parser
- Create DAO layer (12 files) centralizing all 73 SQL queries
- Rewrite 7 route files as thin Express controllers calling DAOs
- Add SCF-to-Express adapter (lib/serverless.js)
- Add auth middleware (middleware/auth.js)
- Remove dead code from lib/auth.js

Admin console:
- Extract DataTable component (table + pagination)
- Extract ConfirmModal component (modal + form styles)
- Create listMixin for paginated list pages
- Move form styles to common.css for slot compatibility
- Refactor device + subscription pages as examples

Miniprogram:
- Split 734-line BLE monolith into 4 focused modules
  (protocol, connection, commands, barrel index)
- Create API module (utils/api.js) with named functions
- Create page utilities (utils/page.js)
- Refactor index + profile pages to use API module
2026-04-29 05:58:20 -07:00

236 行
9.3 KiB
JavaScript

const router = require('express').Router()
const { ok, fail } = require('../lib/response')
const { hashPassword, hashPasswordLegacy, verifyPassword, signAdmin } = require('../lib/auth')
const { requireAdmin } = require('../middleware/auth')
const adminDao = require('../dao/admin.dao')
const deviceDao = require('../dao/device.dao')
const bindingDao = require('../dao/binding.dao')
const commandDao = require('../dao/command.dao')
const userDao = require('../dao/user.dao')
const subscriptionDao = require('../dao/subscription.dao')
const treatmentDao = require('../dao/treatment.dao')
const logDao = require('../dao/log.dao')
const settingsDao = require('../dao/settings.dao')
const wrap = fn => (req, res, next) => fn(req, res, next).catch(next)
function pageParams(query) {
const page = Math.max(1, parseInt(query.page, 10) || 1)
const pageSize = Math.min(Math.max(1, parseInt(query.page_size, 10) || 20), 100)
return { page, pageSize, offset: (page - 1) * pageSize }
}
// --- Auth ---
router.post('/login', wrap(async (req, res) => {
const username = req.body.username || ''
const password = req.body.password || ''
const admin = await adminDao.findByUsername(username)
if (!admin) return res.json(fail(1001, '用户名或密码错误'))
let matched = verifyPassword(password, admin.password_hash)
if (!matched) {
if (admin.password_salt && hashPasswordLegacy(password, admin.password_salt) === admin.password_hash) {
const newHash = hashPassword(password)
await adminDao.updatePassword(admin.admin_id, newHash)
matched = true
}
}
if (!matched) return res.json(fail(1001, '用户名或密码错误'))
const token = signAdmin(admin)
await logDao.write({ admin_id: admin.admin_id, action: 'admin_login', detail: '管理员登录: ' + username, ip: req.ip })
res.json(ok({ token, admin_id: String(admin.admin_id), username: admin.username, real_name: admin.real_name, role: admin.role }))
}))
router.post('/password', requireAdmin, wrap(async (req, res) => {
const oldPassword = req.body.old_password || ''
const newPassword = req.body.new_password || ''
if (newPassword.length < 6) return res.json(fail(2001, 'password too short'))
const current = await adminDao.findById(req.admin.admin_id)
if (!current) return res.json(fail(1002, '未授权,请重新登录'))
let matched = verifyPassword(oldPassword, current.password_hash)
if (!matched && current.password_salt && hashPasswordLegacy(oldPassword, current.password_salt) === current.password_hash) {
matched = true
}
if (!matched) return res.json(fail(1001, '原密码错误'))
const newHash = hashPassword(newPassword)
await adminDao.updatePassword(req.admin.admin_id, newHash)
await logDao.write({ admin_id: req.admin.admin_id, action: 'admin_change_password', detail: '管理员修改密码', ip: req.ip })
res.json(ok({ message: 'success' }))
}))
// --- Dashboard ---
router.get('/dashboard', requireAdmin, wrap(async (req, res) => {
const counts = await adminDao.getDashboardCounts()
const subStats = await adminDao.getSubscriptionStats()
res.json(ok({
device_count: counts.device_count,
user_count: counts.user_count,
treatment_count: counts.treatment_count,
subscription_count: counts.subscription_count,
sub_stats: subStats
}))
}))
// --- Devices ---
router.get('/devices', requireAdmin, wrap(async (req, res) => {
const { page, pageSize, offset } = pageParams(req.query)
const { records, total } = await deviceDao.list({
keyword: req.query.keyword,
pageSize,
offset
})
res.json(ok({ records, total }))
}))
router.post('/devices', requireAdmin, wrap(async (req, res) => {
const deviceId = String(req.body.device_id || '').trim()
if (!deviceId) return res.json(fail(2001, 'device_id required'))
await deviceDao.create({
device_id: deviceId,
product_id: req.body.product_id || 'HOX_LIGHT_MASK',
device_secret: req.body.device_secret || '',
device_name: req.body.device_name || '光子美容仪',
firmware_version: req.body.firmware_version || '1.0.0'
})
await logDao.write({ admin_id: req.admin.admin_id, action: 'admin_device_create', detail: '预生成产品码: ' + deviceId, ip: req.ip })
res.json(ok({ device_id: deviceId }))
}))
router.post('/devices/batch', requireAdmin, wrap(async (req, res) => {
const deviceIds = req.body.device_ids
if (!Array.isArray(deviceIds) || deviceIds.length === 0 || deviceIds.length > 500) {
return res.json(fail(2001, 'device_ids must be an array with 1-500 items'))
}
const { created, failed } = await deviceDao.createBatch(deviceIds)
await logDao.write({ admin_id: req.admin.admin_id, action: 'admin_device_batch_create', detail: '批量预生成产品码: ' + created + '/' + deviceIds.length, ip: req.ip })
res.json(ok({ created, failed }))
}))
router.get('/devices/:device_id', requireAdmin, wrap(async (req, res) => {
const result = await deviceDao.findByIdWithHistory(req.params.device_id)
if (!result) return res.json(fail(1005, 'DEVICE_NOT_FOUND'))
res.json(ok(result))
}))
router.post('/devices/:device_id/unbind', requireAdmin, wrap(async (req, res) => {
await deviceDao.unbind(req.params.device_id)
await logDao.write({ admin_id: req.admin.admin_id, action: 'admin_device_unbind', detail: '后台解绑设备: ' + req.params.device_id, ip: req.ip })
res.json(ok({ message: 'success' }))
}))
router.post('/devices/:device_id/command', requireAdmin, wrap(async (req, res) => {
const opcode = parseInt(req.body.opcode, 10)
if (!opcode) return res.json(fail(2001, 'opcode required'))
await commandDao.create(req.params.device_id, req.admin.admin_id, opcode, req.body)
await logDao.write({ admin_id: req.admin.admin_id, action: 'admin_device_command', detail: '记录远程指令: ' + req.params.device_id, ip: req.ip })
res.json(ok({ message: 'queued', command: req.body }))
}))
router.get('/devices/:device_id/commands', requireAdmin, wrap(async (req, res) => {
const { page, pageSize, offset } = pageParams(req.query)
const { records, total } = await commandDao.listByDevice(req.params.device_id, { pageSize, offset })
res.json(ok({ records, total }))
}))
// --- Users ---
router.get('/users', requireAdmin, wrap(async (req, res) => {
const { page, pageSize, offset } = pageParams(req.query)
const { records, total } = await userDao.listAdmin({
keyword: req.query.keyword,
pageSize,
offset
})
res.json(ok({ records, total }))
}))
router.get('/users/:user_id', requireAdmin, wrap(async (req, res) => {
const result = await userDao.findByIdAdmin(req.params.user_id)
if (!result) return res.json(fail(1004, 'USER_NOT_FOUND'))
res.json(ok(result))
}))
// --- Subscriptions ---
router.get('/subscriptions', requireAdmin, wrap(async (req, res) => {
const { page, pageSize, offset } = pageParams(req.query)
const { records, total } = await subscriptionDao.list({
tab: req.query.tab,
pageSize,
offset
})
const stats = await subscriptionDao.getStats()
res.json(ok({ records, total, stats }))
}))
router.post('/subscriptions', requireAdmin, wrap(async (req, res) => {
const userId = req.body.user_id
if (!userId) return res.json(fail(2001, 'user_id required'))
const targetUser = await userDao.findById(userId)
if (!targetUser) return res.json(fail(1004, 'user_not_found'))
await subscriptionDao.adminCreate(
userId,
req.body.plan || 'monthly',
req.body.amount || 0,
req.body.order_id || 'ADMIN' + Date.now(),
req.body.days || 30
)
res.json(ok({ message: 'success' }))
}))
router.post('/subscriptions/cancel', requireAdmin, wrap(async (req, res) => {
const subscriptionId = req.body.subscription_id
if (!subscriptionId) return res.json(fail(2001, 'subscription_id required'))
const result = await subscriptionDao.cancel(subscriptionId)
if (result.affectedRows === 0) return res.json(fail(2001, '未找到有效订阅'))
await logDao.write({ admin_id: req.admin.admin_id, action: 'subscription_cancel', detail: '取消订阅 #' + subscriptionId, ip: req.ip })
res.json(ok({ message: 'success' }))
}))
// --- Treatment Records ---
router.get('/records', requireAdmin, wrap(async (req, res) => {
const { page, pageSize, offset } = pageParams(req.query)
const { records, total } = await treatmentDao.listAdmin({
keyword: req.query.keyword,
dateFrom: req.query.date_from,
dateTo: req.query.date_to,
pageSize,
offset
})
res.json(ok({ records, total }))
}))
// --- Logs ---
router.get('/logs', requireAdmin, wrap(async (req, res) => {
const { page, pageSize, offset } = pageParams(req.query)
const { records, total } = await logDao.list({
type: req.query.type,
deviceId: req.query.device_id,
pageSize,
offset
})
res.json(ok({ records, total }))
}))
// --- Settings ---
router.get('/settings', requireAdmin, wrap(async (req, res) => {
const settings = await settingsDao.getAll()
res.json(ok(settings))
}))
router.post('/settings', requireAdmin, wrap(async (req, res) => {
const ALLOWED_KEYS = ['system_name', 'admin_email', 'timezone', 'monthly_price', 'yearly_price', 'trial_days', 'enable_register', 'enable_binding', 'enable_free_mode', 'enable_smart_mode', 'maintenance_mode']
for (const key of Object.keys(req.body || {})) {
if (!ALLOWED_KEYS.includes(key)) continue
await settingsDao.update(key, req.body[key])
}
res.json(ok({ message: 'success' }))
}))
module.exports = router