refactor: migrate to Tencent Cloud backend

这个提交包含在:
Guoguo
2026-04-28 22:56:47 +08:00
父节点 267c75718b
当前提交 444c91c0b0
修改 102 个文件,包含 17927 行新增1997 行删除
+193
查看文件
@@ -0,0 +1,193 @@
const { one, query } = require('../lib/db')
const { ok, fail } = require('../lib/response')
const { hashPassword, signAdmin, requireAdmin } = require('../lib/auth')
const { writeLog } = require('../lib/log')
function pageParams(ctx) {
const page = Math.max(1, parseInt(ctx.query.page || ctx.body.page, 10) || 1)
const pageSize = Math.min(Math.max(1, parseInt(ctx.query.page_size || ctx.body.page_size, 10) || 20), 100)
return { page, pageSize, offset: (page - 1) * pageSize }
}
function limitClause(p) {
return ' LIMIT ' + Number(p.pageSize) + ' OFFSET ' + Number(p.offset)
}
async function adminOnly(ctx) {
const admin = await requireAdmin(ctx)
return admin
}
function register(router) {
router.post('/api/v1/admin/login', async ctx => {
const username = ctx.body.username || ''
const password = ctx.body.password || ''
const admin = await one('SELECT * FROM admin_accounts WHERE username = :username AND status = 1', { username })
if (!admin || hashPassword(password, admin.password_salt) !== admin.password_hash) return fail(1001, '用户名或密码错误')
const token = signAdmin(admin)
await writeLog({ admin_id: admin.admin_id, action: 'admin_login', detail: '管理员登录: ' + username, ip: ctx.ip })
return ok({ token, admin_id: String(admin.admin_id), username: admin.username, real_name: admin.real_name, role: admin.role })
})
router.get('/api/v1/admin/dashboard', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const rows = await Promise.all([
query('SELECT COUNT(*) AS total FROM devices', {}),
query('SELECT COUNT(*) AS total FROM users', {}),
query('SELECT COUNT(*) AS total FROM treatment_records', {}),
query('SELECT COUNT(*) AS total FROM subscriptions WHERE status = 1 AND expire_time > NOW()', {})
])
return ok({ device_count: rows[0][0].total, user_count: rows[1][0].total, treatment_count: rows[2][0].total, subscription_count: rows[3][0].total })
})
router.get('/api/v1/admin/devices', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const p = pageParams(ctx)
const total = await query('SELECT COUNT(*) AS total FROM devices', {})
const records = await query('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 ORDER BY d.created_at DESC' + limitClause(p), {})
return ok({ records, total: total[0].total })
})
router.post('/api/v1/admin/devices', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const deviceId = String(ctx.body.device_id || '').trim()
if (!deviceId) return fail(2001, 'device_id required')
await query(
'INSERT INTO devices (device_id, product_id, device_secret, device_name, firmware_version, status) VALUES (:device_id, :product_id, :device_secret, :device_name, :firmware_version, 1) ON DUPLICATE KEY UPDATE product_id = VALUES(product_id), device_secret = VALUES(device_secret), device_name = VALUES(device_name), firmware_version = VALUES(firmware_version), status = 1',
{
device_id: deviceId,
product_id: ctx.body.product_id || 'HOX_LIGHT_MASK',
device_secret: ctx.body.device_secret || '',
device_name: ctx.body.device_name || '光子美容仪',
firmware_version: ctx.body.firmware_version || '1.0.0'
}
)
await writeLog({ admin_id: admin.admin_id, action: 'admin_device_create', detail: '预生成产品码: ' + deviceId, ip: ctx.ip })
return ok({ device_id: deviceId })
})
router.get('/api/v1/admin/devices/:device_id', async ctx => {
const admin = await adminOnly(ctx)
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)
})
router.post('/api/v1/admin/devices/:device_id/unbind', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
await query('UPDATE bindings SET bind_status = 2, unbind_time = NOW() WHERE device_id = :device_id AND bind_status = 1', { device_id: ctx.params.device_id })
await writeLog({ admin_id: admin.admin_id, action: 'admin_device_unbind', detail: '后台解绑设备: ' + ctx.params.device_id, ip: ctx.ip })
return ok({ message: 'success' })
})
router.post('/api/v1/admin/devices/:device_id/command', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const opcode = parseInt(ctx.body.opcode, 10)
if (!opcode) return fail(2001, 'opcode required')
await query(
'INSERT INTO device_commands (device_id, admin_id, opcode, payload_json, status) VALUES (:device_id, :admin_id, :opcode, :payload_json, 1)',
{ device_id: ctx.params.device_id, admin_id: admin.admin_id, opcode, payload_json: JSON.stringify(ctx.body) }
)
await writeLog({ admin_id: admin.admin_id, action: 'admin_device_command', detail: '记录远程指令: ' + ctx.params.device_id, ip: ctx.ip })
return ok({ message: 'queued', command: ctx.body })
})
router.get('/api/v1/admin/devices/:device_id/commands', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const p = pageParams(ctx)
const total = await query('SELECT COUNT(*) AS total FROM device_commands WHERE device_id = :device_id', { device_id: ctx.params.device_id })
const records = await query(
'SELECT command_id, device_id, admin_id, opcode, payload_json, status, created_at, pulled_at, finished_at, result_json FROM device_commands WHERE device_id = :device_id ORDER BY created_at DESC' + limitClause(p),
{ device_id: ctx.params.device_id }
)
return ok({ records, total: total[0].total })
})
router.get('/api/v1/admin/users', async ctx => {
const admin = await adminOnly(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), {})
return ok({ records, total: total[0].total })
})
router.get('/api/v1/admin/users/:user_id', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const user = await one('SELECT * FROM users WHERE user_id = :user_id', { user_id: ctx.params.user_id })
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 }))
})
router.get('/api/v1/admin/subscriptions', async ctx => {
const admin = await adminOnly(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), {})
return ok({ records, total: total[0].total })
})
router.post('/api/v1/admin/subscriptions', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
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',
amount: ctx.body.amount || 0,
order_id: ctx.body.order_id || 'ADMIN' + Date.now(),
days: ctx.body.days || 30
})
return ok({ message: 'success' })
})
router.get('/api/v1/admin/records', async ctx => {
const admin = await adminOnly(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), {})
return ok({ records, total: total[0].total })
})
router.get('/api/v1/admin/logs', async ctx => {
const admin = await adminOnly(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), {})
return ok({ records, total: total[0].total })
})
router.get('/api/v1/admin/settings', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const rows = await query('SELECT setting_key, setting_value FROM system_settings', {})
const settings = {}
rows.forEach(row => {
settings[row.setting_key] = typeof row.setting_value === 'string' ? JSON.parse(row.setting_value) : row.setting_value
})
return ok(settings)
})
router.post('/api/v1/admin/settings', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
for (const key of Object.keys(ctx.body || {})) {
await query('REPLACE INTO system_settings (setting_key, setting_value) VALUES (:setting_key, :setting_value)', { setting_key: key, setting_value: JSON.stringify(ctx.body[key]) })
}
return ok({ message: 'success' })
})
}
module.exports = register
+40
查看文件
@@ -0,0 +1,40 @@
const { one, query } = require('../lib/db')
const { ok, fail } = require('../lib/response')
const { signUser } = require('../lib/auth')
const { code2Session } = require('../lib/wechat')
const { writeLog } = require('../lib/log')
function register(router) {
router.post('/api/v1/auth/login', async ctx => {
const session = await code2Session(ctx.body.code || '')
let user = await one('SELECT * FROM users WHERE openid = :openid', { openid: session.openid })
if (!user) {
const result = await query(
'INSERT INTO users (openid, nickname, avatar, status) VALUES (:openid, :nickname, :avatar, 1)',
{ openid: session.openid, nickname: '', avatar: '' }
)
user = await one('SELECT * FROM users WHERE user_id = :user_id', { user_id: result.insertId })
await writeLog({ user_id: user.user_id, action: 'user_register', detail: '新用户注册', ip: ctx.ip })
}
const token = signUser(user)
await writeLog({ user_id: user.user_id, action: 'user_login', detail: '用户登录', ip: ctx.ip })
return ok({
token,
user_id: String(user.user_id),
user_info: {
user_id: String(user.user_id),
nickname: user.nickname || '用户' + String(user.user_id),
avatar: user.avatar || '',
phone: user.phone || '',
gender: user.gender || 0
},
expires_in: 604800
})
})
router.post('/api/v1/auth/refresh', async ctx => {
return fail(2001, 'refresh_token 暂未启用,请重新登录')
})
}
module.exports = register
+150
查看文件
@@ -0,0 +1,150 @@
const { one, query, transaction } = require('../lib/db')
const { ok, fail } = require('../lib/response')
const { requireUser, randomHex } = require('../lib/auth')
const { writeLog } = require('../lib/log')
function formatDate(date) {
return date.toISOString().slice(0, 19).replace('T', ' ')
}
async function ensureTrial(conn, userId) {
const [subs] = await conn.execute('SELECT subscription_id FROM subscriptions WHERE user_id = ? AND status = 1 AND expire_time > NOW() LIMIT 1', [userId])
if (subs.length > 0) return
const expire = formatDate(new Date(Date.now() + 7 * 24 * 3600 * 1000))
await conn.execute('INSERT INTO subscriptions (user_id, plan, status, amount, start_time, expire_time) VALUES (?, ?, 1, 0, NOW(), ?)', [userId, 'trial', expire])
}
function register(router) {
router.post('/api/v1/device/bind', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const deviceId = String(ctx.body.device_id || '').trim()
if (!deviceId) return fail(2001, 'device_id required')
const result = await transaction(async conn => {
const [active] = await conn.execute('SELECT device_id FROM bindings WHERE user_id = ? AND bind_status = 1 LIMIT 1', [user.user_id])
if (active.length > 0) return { duplicated: true, device_id: active[0].device_id }
const [devices] = await conn.execute('SELECT * FROM devices WHERE device_id = ? AND status <> 4 LIMIT 1', [deviceId])
if (devices.length === 0) return { invalid: true }
const bindToken = randomHex(8)
const bindExpires = formatDate(new Date(Date.now() + 10 * 60 * 1000))
await conn.execute(
'INSERT INTO bindings (user_id, device_id, bind_token, bind_expires, bind_status, bind_time) VALUES (?, ?, ?, ?, 3, NOW())',
[user.user_id, deviceId, bindToken, bindExpires]
)
return { device_id: deviceId, bind_token: bindToken, bind_expires: bindExpires }
})
if (result.invalid) return fail(1005, 'DEVICE_NOT_FOUND')
if (result.duplicated) return fail(2001, '已绑定设备', { device_id: result.device_id })
await writeLog({ user_id: user.user_id, action: 'device_bind_request', detail: '申请绑定设备: ' + deviceId, ip: ctx.ip })
return ok(Object.assign(result, { subscription: { plan: 'trial', remaining_days: 7 } }))
})
router.post('/api/v1/device/bind/confirm', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const deviceId = String(ctx.body.device_id || '').trim()
const bindToken = String(ctx.body.bind_token || '').trim()
if (!deviceId || !bindToken) return fail(2001, 'device_id and bind_token required')
const updated = await transaction(async conn => {
const [rows] = await conn.execute(
'SELECT binding_id FROM bindings WHERE user_id = ? AND device_id = ? AND bind_token = ? AND bind_status = 3 AND bind_expires > NOW() LIMIT 1',
[user.user_id, deviceId, bindToken]
)
if (rows.length === 0) return false
await conn.execute('UPDATE bindings SET bind_status = 1, bind_time = NOW() WHERE binding_id = ?', [rows[0].binding_id])
await ensureTrial(conn, user.user_id)
return true
})
if (!updated) return fail(2001, 'bind_token invalid or expired')
await writeLog({ user_id: user.user_id, action: 'device_bind_confirm', detail: '确认绑定设备: ' + deviceId, ip: ctx.ip })
return ok({ message: 'success', subscription: { plan: 'trial', remaining_days: 7 } })
})
router.post('/api/v1/device/unbind', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const deviceId = ctx.body.device_id || null
await query(
'UPDATE bindings SET bind_status = 2, unbind_time = NOW() WHERE user_id = :user_id AND bind_status = 1 AND (:device_id IS NULL OR device_id = :device_id)',
{ user_id: user.user_id, device_id: deviceId }
)
await writeLog({ user_id: user.user_id, action: 'device_unbind', detail: '解绑设备: ' + (deviceId || 'current'), ip: ctx.ip })
return ok({ message: 'success' })
})
router.get('/api/v1/device/list', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const devices = await query(
'SELECT d.device_id, d.device_name, d.status, d.battery, d.firmware_version, d.last_online_at, b.bind_time FROM bindings b JOIN devices d ON d.device_id = b.device_id WHERE b.user_id = :user_id AND b.bind_status = 1 ORDER BY b.bind_time DESC',
{ user_id: user.user_id }
)
return ok({ devices, total: devices.length })
})
router.get('/api/v1/device/command/pending', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const deviceId = String(ctx.query.device_id || '').trim()
if (!deviceId) return fail(2001, 'device_id required')
const bound = await one('SELECT binding_id FROM bindings WHERE user_id = :user_id AND device_id = :device_id AND bind_status = 1', { user_id: user.user_id, device_id: deviceId })
if (!bound) return fail(1006, 'DEVICE_NOT_BOUND')
const commands = await query('SELECT command_id, opcode, payload_json FROM device_commands WHERE device_id = :device_id AND status = 1 ORDER BY created_at ASC LIMIT 10', { device_id: deviceId })
if (commands.length > 0) {
await query('UPDATE device_commands SET status = 2, pulled_at = NOW() WHERE command_id IN (' + commands.map(c => Number(c.command_id)).join(',') + ')', {})
}
return ok({ commands: commands.map(c => ({ seq: c.command_id, opcode: c.opcode, payload: typeof c.payload_json === 'string' ? JSON.parse(c.payload_json) : c.payload_json || {} })) })
})
router.post('/api/v1/device/command/result', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const commandId = parseInt(ctx.body.command_id || ctx.body.seq, 10)
const success = ctx.body.success !== false
if (!commandId) return fail(2001, 'command_id required')
await query('UPDATE device_commands SET status = :status, finished_at = NOW(), result_json = :result_json WHERE command_id = :command_id', {
command_id: commandId,
status: success ? 3 : 4,
result_json: JSON.stringify(ctx.body)
})
return ok({ message: 'success' })
})
router.get('/api/v1/device/:device_id', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const device = await one(
'SELECT d.device_id, d.device_name, d.status, d.battery, d.temperature, d.firmware_version, d.last_online_at, b.bind_time FROM bindings b JOIN devices d ON d.device_id = b.device_id WHERE b.user_id = :user_id AND b.bind_status = 1 AND b.device_id = :device_id',
{ user_id: user.user_id, device_id: ctx.params.device_id }
)
if (!device) return fail(1006, 'DEVICE_NOT_BOUND')
return ok(device)
})
router.post('/api/v1/device/event', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const deviceId = String(ctx.body.device_id || '').trim()
if (!deviceId) return fail(2001, 'device_id required')
await query(
'INSERT INTO device_events (device_id, user_id, event_type, error_code, temperature, payload_json) VALUES (:device_id, :user_id, :event_type, :error_code, :temperature, :payload_json)',
{
device_id: deviceId,
user_id: user.user_id,
event_type: ctx.body.event_type || 'device_error',
error_code: ctx.body.error_code || null,
temperature: ctx.body.temperature || null,
payload_json: JSON.stringify(ctx.body)
}
)
await writeLog({ user_id: user.user_id, action: 'device_event', detail: '设备事件: ' + deviceId, ip: ctx.ip })
return ok({ message: 'ok' })
})
}
module.exports = register
+66
查看文件
@@ -0,0 +1,66 @@
const { one, query } = require('../lib/db')
const { ok, fail } = require('../lib/response')
const { requireUser, requireAdmin } = require('../lib/auth')
const { getObjectUrl } = require('../lib/cos')
const { writeLog } = require('../lib/log')
function adminOnly(ctx) {
return requireAdmin(ctx)
}
function register(router) {
router.get('/api/v1/admin/firmware', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const rows = await query('SELECT firmware_id, version, device_type, cos_key, size_bytes, sha256, status, created_at FROM firmware_files ORDER BY created_at DESC', {})
return ok({ records: rows, total: rows.length })
})
router.post('/api/v1/admin/firmware', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const version = String(ctx.body.version || '').trim()
const cosKey = String(ctx.body.cos_key || '').trim()
if (!version || !cosKey) return fail(2001, 'version and cos_key required')
const result = await query(
'INSERT INTO firmware_files (version, device_type, cos_key, size_bytes, sha256, status) VALUES (:version, :device_type, :cos_key, :size_bytes, :sha256, :status)',
{
version,
device_type: ctx.body.device_type || '',
cos_key: cosKey,
size_bytes: Number(ctx.body.size_bytes || 0),
sha256: ctx.body.sha256 || '',
status: ctx.body.status === 0 ? 0 : 1
}
)
await writeLog({ admin_id: admin.admin_id, action: 'admin_firmware_create', detail: '登记固件: ' + version, ip: ctx.ip })
return ok({ firmware_id: result.insertId })
})
router.post('/api/v1/admin/firmware/:firmware_id/status', async ctx => {
const admin = await adminOnly(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const firmwareId = parseInt(ctx.params.firmware_id, 10)
const status = Number(ctx.body.status) === 1 ? 1 : 0
if (!firmwareId) return fail(2001, 'firmware_id required')
await query('UPDATE firmware_files SET status = :status WHERE firmware_id = :firmware_id', { status, firmware_id: firmwareId })
await writeLog({ admin_id: admin.admin_id, action: 'admin_firmware_status', detail: '更新固件状态: ' + firmwareId + ' -> ' + status, ip: ctx.ip })
return ok({ message: 'success' })
})
router.get('/api/v1/firmware/latest', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const firmware = await one('SELECT * FROM firmware_files WHERE status = 1 ORDER BY created_at DESC LIMIT 1', {})
if (!firmware) return ok({ has_update: false })
return ok({
has_update: true,
version: firmware.version,
size_bytes: firmware.size_bytes,
sha256: firmware.sha256,
download_url: getObjectUrl(firmware.cos_key, 600)
})
})
}
module.exports = register
+48
查看文件
@@ -0,0 +1,48 @@
const { one, query } = require('../lib/db')
const { ok, fail } = require('../lib/response')
const { requireUser } = require('../lib/auth')
const { writeLog } = require('../lib/log')
const PLANS = {
monthly: { amount: 99, days: 30 },
yearly: { amount: 899, days: 365 }
}
function register(router) {
router.get('/api/v1/subscription', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const sub = await one('SELECT *, GREATEST(DATEDIFF(expire_time, NOW()), 0) AS remaining_days FROM subscriptions WHERE user_id = :user_id AND status = 1 ORDER BY expire_time DESC LIMIT 1', { user_id: user.user_id })
if (!sub) return ok({ status: 'inactive', plan: 'none', remaining_days: 0 })
return ok({ status: sub.remaining_days > 0 ? 'active' : 'expired', plan: sub.plan, start_time: sub.start_time, expire_time: sub.expire_time, remaining_days: sub.remaining_days })
})
router.post('/api/v1/subscription/purchase', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const plan = ctx.body.plan || ctx.body.plan_type
if (!PLANS[plan]) return fail(2001, 'invalid plan')
const orderId = 'ORD' + Date.now()
return ok({ order_id: orderId, payment_params: {}, plan, amount: PLANS[plan].amount })
})
router.post('/api/v1/subscription/verify', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const plan = ctx.body.plan || ctx.body.plan_type || 'monthly'
if (!PLANS[plan]) return fail(2001, 'invalid plan')
const p = PLANS[plan]
await query('UPDATE subscriptions SET status = 2 WHERE user_id = :user_id AND status = 1', { user_id: user.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: user.user_id,
plan,
amount: p.amount,
order_id: ctx.body.order_id || 'ORD' + Date.now(),
days: p.days
})
await writeLog({ user_id: user.user_id, action: 'subscription_verify', detail: '订阅生效: ' + plan, ip: ctx.ip })
return ok({ status: 'active', plan, remaining_days: p.days })
})
}
module.exports = register
+67
查看文件
@@ -0,0 +1,67 @@
const { query } = require('../lib/db')
const { ok, fail } = require('../lib/response')
const { requireUser } = require('../lib/auth')
const { writeLog } = require('../lib/log')
function toMysqlDate(value) {
if (!value) return null
const d = new Date(value)
if (Number.isNaN(d.getTime())) return null
return d.toISOString().slice(0, 19).replace('T', ' ')
}
function register(router) {
function limitClause(pageSize, offset) {
return ' LIMIT ' + Number(pageSize) + ' OFFSET ' + Number(offset)
}
router.get('/api/v1/treatment/history', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const page = Math.max(1, parseInt(ctx.query.page, 10) || 1)
const pageSize = Math.min(Math.max(1, parseInt(ctx.query.page_size, 10) || 20), 100)
const offset = (page - 1) * pageSize
const total = await query('SELECT COUNT(*) AS total FROM treatment_records WHERE user_id = :user_id', { user_id: user.user_id })
const records = await query('SELECT * FROM treatment_records WHERE user_id = :user_id ORDER BY created_at DESC' + limitClause(pageSize, offset), { user_id: user.user_id })
return ok({ total: total[0].total, page, page_size: pageSize, records })
})
router.post('/api/v1/treatment/sync', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const d = ctx.body || {}
if (!d.device_id) return fail(2001, 'device_id required')
const sessionId = d.session_id || 'SESS' + Date.now()
await query(
`INSERT INTO treatment_records
(session_id, device_id, user_id, start_time, end_time, regions, total_duration_ms, mode, avg_pd, battery, temperature, wavelength, brightness, pd_json)
VALUES (:session_id, :device_id, :user_id, :start_time, :end_time, :regions, :total_duration_ms, :mode, :avg_pd, :battery, :temperature, :wavelength, :brightness, :pd_json)
ON DUPLICATE KEY UPDATE end_time = VALUES(end_time), total_duration_ms = VALUES(total_duration_ms), avg_pd = VALUES(avg_pd), battery = VALUES(battery), temperature = VALUES(temperature), pd_json = VALUES(pd_json)`,
{
session_id: sessionId,
device_id: d.device_id,
user_id: user.user_id,
start_time: toMysqlDate(d.start_time),
end_time: toMysqlDate(d.end_time),
regions: Array.isArray(d.regions) ? d.regions.join(',') : String(d.regions || ''),
total_duration_ms: parseInt(d.total_duration_ms, 10) || 0,
mode: parseInt(d.mode, 10) || 0,
avg_pd: Number(d.avg_pd) || 0,
battery: d.battery == null ? null : parseInt(d.battery, 10),
temperature: d.temperature == null ? null : parseInt(d.temperature, 10),
wavelength: d.wavelength == null ? null : parseInt(d.wavelength, 10),
brightness: d.brightness == null ? null : parseInt(d.brightness, 10),
pd_json: JSON.stringify(d.pd_values || {})
}
)
await query('UPDATE devices SET battery = COALESCE(:battery, battery), temperature = COALESCE(:temperature, temperature), last_online_at = NOW() WHERE device_id = :device_id', {
device_id: d.device_id,
battery: d.battery == null ? null : parseInt(d.battery, 10),
temperature: d.temperature == null ? null : parseInt(d.temperature, 10)
})
await writeLog({ user_id: user.user_id, action: 'treatment_sync', detail: '同步护理记录: ' + sessionId, ip: ctx.ip })
return ok({ record_id: sessionId })
})
}
module.exports = register
+67
查看文件
@@ -0,0 +1,67 @@
const { query } = require('../lib/db')
const { ok, fail } = require('../lib/response')
const { requireUser } = require('../lib/auth')
const { writeLog } = require('../lib/log')
const { getPhoneNumber } = require('../lib/wechat')
const { getPutObjectUrl, getObjectUrl } = require('../lib/cos')
function register(router) {
router.get('/api/v1/user/profile', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const binds = await query('SELECT COUNT(*) AS total FROM bindings WHERE user_id = :user_id AND bind_status = 1', { user_id: user.user_id })
return ok({
user_id: String(user.user_id),
nickname: user.nickname || '用户' + String(user.user_id),
avatar: user.avatar || '',
phone: user.phone || '',
gender: user.gender || 0,
bind_time: null,
device_count: binds[0].total
})
})
router.put('/api/v1/user/profile', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
await query('UPDATE users SET nickname = COALESCE(:nickname, nickname), avatar = COALESCE(:avatar, avatar), gender = COALESCE(:gender, gender) WHERE user_id = :user_id', {
user_id: user.user_id,
nickname: ctx.body.nickname || null,
avatar: ctx.body.avatar || ctx.body.avatar_url || null,
gender: ctx.body.gender === undefined ? null : ctx.body.gender
})
await writeLog({ user_id: user.user_id, action: 'user_update', detail: '更新用户资料', ip: ctx.ip })
return ok({ message: 'success' })
})
router.post('/api/v1/user/avatar/upload-url', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const ext = String(ctx.body.ext || 'jpg').replace(/[^a-zA-Z0-9]/g, '').toLowerCase() || 'jpg'
const contentType = ctx.body.content_type || (ext === 'png' ? 'image/png' : 'image/jpeg')
const key = 'avatars/' + user.user_id + '/' + Date.now() + '.' + ext
return ok({
key,
upload_url: getPutObjectUrl(key, contentType, 600),
public_url: getObjectUrl(key, 7 * 24 * 3600),
content_type: contentType
})
})
router.post('/api/v1/user/phone', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const code = String(ctx.body.code || '').trim()
if (!code) return fail(2001, 'phone code required')
const phoneInfo = await getPhoneNumber(code)
if (!phoneInfo || !phoneInfo.phoneNumber) return fail(2001, 'phone authorization failed')
await query('UPDATE users SET phone = :phone WHERE user_id = :user_id', {
user_id: user.user_id,
phone: phoneInfo.phoneNumber
})
await writeLog({ user_id: user.user_id, action: 'user_phone_bind', detail: '授权手机号', ip: ctx.ip })
return ok({ phone: phoneInfo.phoneNumber, pure_phone_number: phoneInfo.purePhoneNumber || '', country_code: phoneInfo.countryCode || '' })
})
}
module.exports = register