fix: resolve critical audit issues across all modules

Server: add filter/search/pagination to admin list endpoints, enrich
user/device queries with JOINs and subqueries, prevent duplicate active
subscriptions on creation.

Admin console: fix record page TypeError on numeric record_id, correct
mode comparison (integer vs string), fix device detail field names and
command opcode, remove hardcoded login credentials, wire up dead buttons
(unbind, view logs, export), fix user/subscription field mappings.

Miniprogram: fix subscription status string/number mismatches across
index/treatment-setup/profile pages, fix device name field reference,
fix treatment-done null device_id by capturing at onLoad.
这个提交包含在:
Guoguo
2026-04-28 18:46:03 -07:00
父节点 c5f6033ccf
当前提交 6201b97fcd
修改 15 个文件,包含 132 行新增43 行删除
+86 -10
查看文件
@@ -76,7 +76,9 @@ function register(router) {
if (!admin) return fail(1002, '未授权,请重新登录')
const device = await one('SELECT d.*, b.user_id AS bound_user, b.bind_time AS activated_at FROM devices d LEFT JOIN bindings b ON b.device_id = d.device_id AND b.bind_status = 1 WHERE d.device_id = :device_id', { device_id: ctx.params.device_id })
if (!device) return fail(1005, 'DEVICE_NOT_FOUND')
return ok(device)
const bindingHistory = await query('SELECT b.*, u.nickname FROM bindings b LEFT JOIN users u ON u.user_id = b.user_id WHERE b.device_id = :device_id ORDER BY b.bind_time DESC', { device_id: ctx.params.device_id })
const recentTreatments = await query('SELECT r.*, u.nickname FROM treatment_records r LEFT JOIN users u ON u.user_id = r.user_id WHERE r.device_id = :device_id ORDER BY r.created_at DESC LIMIT 5', { device_id: ctx.params.device_id })
return ok(Object.assign({}, device, { binding_history: bindingHistory, recent_treatments: recentTreatments }))
})
router.post('/api/v1/admin/devices/:device_id/unbind', async ctx => {
@@ -116,8 +118,23 @@ function register(router) {
const admin = await requireAdmin(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const p = pageParams(ctx)
const total = await query('SELECT COUNT(*) AS total FROM users', {})
const records = await query('SELECT * FROM users ORDER BY created_at DESC' + limitClause(p.pageSize, p.offset), {})
const keyword = (ctx.query.keyword || '').trim()
let where = ''
const params = {}
if (keyword) {
where = ' WHERE u.nickname LIKE :kw OR u.phone LIKE :kw OR u.user_id = :keyword'
params.kw = '%' + keyword + '%'
params.keyword = keyword
}
const total = await query('SELECT COUNT(*) AS total FROM users u' + where, params)
const records = await query(
'SELECT u.*,' +
' (SELECT COUNT(*) FROM bindings WHERE user_id = u.user_id AND bind_status = 1) AS device_count,' +
' (SELECT COUNT(*) FROM treatment_records WHERE user_id = u.user_id) AS treatment_count,' +
' COALESCE((SELECT status FROM subscriptions WHERE user_id = u.user_id AND status = 1 AND expire_time > NOW() ORDER BY expire_time DESC LIMIT 1), 0) AS subscription_status' +
' FROM users u' + where + ' ORDER BY u.created_at DESC' + limitClause(p.pageSize, p.offset),
params
)
return ok({ records, total: total[0].total })
})
@@ -128,15 +145,39 @@ function register(router) {
if (!user) return fail(1004, 'USER_NOT_FOUND')
const devices = await query('SELECT d.device_id, d.device_name FROM bindings b JOIN devices d ON d.device_id = b.device_id WHERE b.user_id = :user_id AND b.bind_status = 1', { user_id: user.user_id })
const treatments = await query('SELECT * FROM treatment_records WHERE user_id = :user_id ORDER BY created_at DESC LIMIT 5', { user_id: user.user_id })
return ok(Object.assign({}, user, { devices, recent_treatments: treatments }))
const subscription = await one('SELECT plan, status, start_time, expire_time FROM subscriptions WHERE user_id = :user_id AND status = 1 AND expire_time > NOW() ORDER BY expire_time DESC LIMIT 1', { user_id: user.user_id })
const stats = await one('SELECT COUNT(*) AS treatment_count, COALESCE(SUM(total_duration_ms), 0) AS total_duration FROM treatment_records WHERE user_id = :user_id', { user_id: user.user_id })
return ok(Object.assign({}, user, {
devices,
recent_treatments: treatments,
subscription_status: subscription ? subscription.status : 0,
subscription_type: subscription ? subscription.plan : null,
subscription_expire: subscription ? subscription.expire_time : null,
treatment_count: stats ? stats.treatment_count : 0,
total_duration: stats ? stats.total_duration : 0
}))
})
router.get('/api/v1/admin/subscriptions', async ctx => {
const admin = await requireAdmin(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const p = pageParams(ctx)
const total = await query('SELECT COUNT(*) AS total FROM subscriptions', {})
const records = await query('SELECT * FROM subscriptions ORDER BY created_at DESC' + limitClause(p.pageSize, p.offset), {})
const tab = (ctx.query.tab || '').trim()
let where = ''
const params = {}
if (tab && tab !== 'all') {
if (tab === 'expired') {
where = ' WHERE s.status = 2'
} else {
where = ' WHERE s.plan = :plan'
params.plan = tab
}
}
const total = await query('SELECT COUNT(*) AS total FROM subscriptions s' + where, params)
const records = await query(
'SELECT s.*, u.nickname FROM subscriptions s LEFT JOIN users u ON u.user_id = s.user_id' + where + ' ORDER BY s.created_at DESC' + limitClause(p.pageSize, p.offset),
params
)
return ok({ records, total: total[0].total })
})
@@ -145,6 +186,7 @@ function register(router) {
if (!admin) return fail(1002, '未授权,请重新登录')
const targetUser = await one('SELECT user_id FROM users WHERE user_id = :user_id', { user_id: ctx.body.user_id })
if (!targetUser) return fail(1004, 'user_not_found')
await query('UPDATE subscriptions SET status = 2 WHERE user_id = :user_id AND status = 1', { user_id: ctx.body.user_id })
await query('INSERT INTO subscriptions (user_id, plan, status, amount, order_id, start_time, expire_time) VALUES (:user_id, :plan, 1, :amount, :order_id, NOW(), DATE_ADD(NOW(), INTERVAL :days DAY))', {
user_id: ctx.body.user_id,
plan: ctx.body.plan || 'monthly',
@@ -159,8 +201,29 @@ function register(router) {
const admin = await requireAdmin(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const p = pageParams(ctx)
const total = await query('SELECT COUNT(*) AS total FROM treatment_records', {})
const records = await query('SELECT * FROM treatment_records ORDER BY created_at DESC' + limitClause(p.pageSize, p.offset), {})
const keyword = (ctx.query.keyword || '').trim()
const dateFrom = (ctx.query.date_from || '').trim()
const dateTo = (ctx.query.date_to || '').trim()
const conditions = []
const params = {}
if (keyword) {
conditions.push('u.nickname LIKE :kw')
params.kw = '%' + keyword + '%'
}
if (dateFrom) {
conditions.push('r.created_at >= :date_from')
params.date_from = dateFrom
}
if (dateTo) {
conditions.push('r.created_at <= :date_to')
params.date_to = dateTo
}
const where = conditions.length ? ' WHERE ' + conditions.join(' AND ') : ''
const total = await query('SELECT COUNT(*) AS total FROM treatment_records r LEFT JOIN users u ON u.user_id = r.user_id' + where, params)
const records = await query(
'SELECT r.*, u.nickname FROM treatment_records r LEFT JOIN users u ON u.user_id = r.user_id' + where + ' ORDER BY r.created_at DESC' + limitClause(p.pageSize, p.offset),
params
)
return ok({ records, total: total[0].total })
})
@@ -168,8 +231,21 @@ function register(router) {
const admin = await requireAdmin(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const p = pageParams(ctx)
const total = await query('SELECT COUNT(*) AS total FROM operation_logs', {})
const records = await query('SELECT * FROM operation_logs ORDER BY created_at DESC' + limitClause(p.pageSize, p.offset), {})
const type = (ctx.query.type || '').trim()
const deviceId = (ctx.query.device_id || '').trim()
const conditions = []
const params = {}
if (type) {
conditions.push('action LIKE :type')
params.type = '%' + type + '%'
}
if (deviceId) {
conditions.push('detail LIKE :device_id')
params.device_id = '%' + deviceId + '%'
}
const where = conditions.length ? ' WHERE ' + conditions.join(' AND ') : ''
const total = await query('SELECT COUNT(*) AS total FROM operation_logs' + where, params)
const records = await query('SELECT * FROM operation_logs' + where + ' ORDER BY created_at DESC' + limitClause(p.pageSize, p.offset), params)
return ok({ records, total: total[0].total })
})