From 775112130da99d9459c98cbc0831fa52aa030849 Mon Sep 17 00:00:00 2001 From: Guoguo Date: Wed, 29 Apr 2026 05:10:12 -0700 Subject: [PATCH] feat: add mock-bind endpoint to bypass token flow for testing The bind + confirm two-step flow has timezone issues between Node.js (UTC on SCF) and MySQL (timezone +08:00) that cause bind_token to fail validation. Add POST /api/v1/device/mock-bind that does bind + confirm in one atomic step, skipping the token entirely. Only available when NODE_ENV != production. Update ble-connect and index page mock buttons to use mock-bind. --- miniprogram/pages/ble-connect/ble-connect.js | 21 ++++------------ miniprogram/pages/index/index.js | 14 ++++------- server/src/routes/device.js | 25 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/miniprogram/pages/ble-connect/ble-connect.js b/miniprogram/pages/ble-connect/ble-connect.js index c805b16..50a9aaa 100644 --- a/miniprogram/pages/ble-connect/ble-connect.js +++ b/miniprogram/pages/ble-connect/ble-connect.js @@ -96,16 +96,13 @@ Page({ this.startBleConnect() }, - _mockConfirmBind: function () { + onMockConnect: function () { var self = this + clearTimeout(this._scanTimer) + ble.stopScan() self.setData({ state: 'binding' }) - http.post('/api/v1/device/bind', { + http.post('/api/v1/device/mock-bind', { device_id: self.data.deviceId - }).then(function (data) { - return http.post('/api/v1/device/bind/confirm', { - device_id: data.device_id, - bind_token: data.bind_token - }) }).then(function () { self.setData({ state: 'done' }) wx.redirectTo({ @@ -116,15 +113,7 @@ Page({ }) }, - onMockConnect: function () { - clearTimeout(this._scanTimer) - ble.stopScan() - this._mockConfirmBind() - }, - onMockBindSuccess: function () { - clearTimeout(this._scanTimer) - ble.stopScan() - this._mockConfirmBind() + this.onMockConnect() } }) diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js index 6be4fee..fe65b30 100644 --- a/miniprogram/pages/index/index.js +++ b/miniprogram/pages/index/index.js @@ -150,18 +150,14 @@ Page({ onMockBind: function () { var self = this - var mockDeviceId = 'MOCK_' + Date.now().toString(36).toUpperCase() wx.showModal({ title: '模拟绑定', - content: '将创建一个模拟设备 ' + mockDeviceId + ' 并绑定到当前账号', + editable: true, + placeholderText: '输入设备编号(需先在后台创建)', success: function (res) { - if (res.confirm) { - http.post('/api/v1/device/bind', { device_id: mockDeviceId }).then(function (data) { - return http.post('/api/v1/device/bind/confirm', { - device_id: data.device_id, - bind_token: data.bind_token - }) - }).then(function () { + if (res.confirm && res.content) { + var deviceId = res.content.trim() + http.post('/api/v1/device/mock-bind', { device_id: deviceId }).then(function () { wx.showToast({ title: '模拟绑定成功', icon: 'success' }) self.checkState() }).catch(function (err) { diff --git a/server/src/routes/device.js b/server/src/routes/device.js index ec38b71..e98e344 100644 --- a/server/src/routes/device.js +++ b/server/src/routes/device.js @@ -62,6 +62,31 @@ function register(router) { return ok({ message: 'success', subscription: sub ? { plan: sub.plan, remaining_days: sub.remaining_days } : { plan: 'none', remaining_days: 0 } }) }) + router.post('/api/v1/device/mock-bind', async ctx => { + const config = require('../config') + if (config.nodeEnv === 'production') return fail(2001, 'not available in production') + 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 } + await conn.execute('UPDATE bindings SET bind_status = 2 WHERE user_id = ? AND bind_status = 3', [user.user_id]) + await conn.execute('INSERT INTO bindings (user_id, device_id, bind_token, bind_expires, bind_status, bind_time) VALUES (?, ?, ?, NOW(), 1, NOW())', [user.user_id, deviceId, 'mock']) + await ensureTrial(conn, user.user_id) + return { success: true } + }) + + 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_confirm', detail: '模拟绑定设备: ' + deviceId, ip: ctx.ip }) + return ok({ message: 'success', device_id: deviceId }) + }) + router.post('/api/v1/device/unbind', async ctx => { const user = await requireUser(ctx) if (!user) return fail(1001, 'invalid_token')