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.
这个提交包含在:
@@ -96,16 +96,13 @@ Page({
|
|||||||
this.startBleConnect()
|
this.startBleConnect()
|
||||||
},
|
},
|
||||||
|
|
||||||
_mockConfirmBind: function () {
|
onMockConnect: function () {
|
||||||
var self = this
|
var self = this
|
||||||
|
clearTimeout(this._scanTimer)
|
||||||
|
ble.stopScan()
|
||||||
self.setData({ state: 'binding' })
|
self.setData({ state: 'binding' })
|
||||||
http.post('/api/v1/device/bind', {
|
http.post('/api/v1/device/mock-bind', {
|
||||||
device_id: self.data.deviceId
|
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 () {
|
}).then(function () {
|
||||||
self.setData({ state: 'done' })
|
self.setData({ state: 'done' })
|
||||||
wx.redirectTo({
|
wx.redirectTo({
|
||||||
@@ -116,15 +113,7 @@ Page({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onMockConnect: function () {
|
|
||||||
clearTimeout(this._scanTimer)
|
|
||||||
ble.stopScan()
|
|
||||||
this._mockConfirmBind()
|
|
||||||
},
|
|
||||||
|
|
||||||
onMockBindSuccess: function () {
|
onMockBindSuccess: function () {
|
||||||
clearTimeout(this._scanTimer)
|
this.onMockConnect()
|
||||||
ble.stopScan()
|
|
||||||
this._mockConfirmBind()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -150,18 +150,14 @@ Page({
|
|||||||
|
|
||||||
onMockBind: function () {
|
onMockBind: function () {
|
||||||
var self = this
|
var self = this
|
||||||
var mockDeviceId = 'MOCK_' + Date.now().toString(36).toUpperCase()
|
|
||||||
wx.showModal({
|
wx.showModal({
|
||||||
title: '模拟绑定',
|
title: '模拟绑定',
|
||||||
content: '将创建一个模拟设备 ' + mockDeviceId + ' 并绑定到当前账号',
|
editable: true,
|
||||||
|
placeholderText: '输入设备编号(需先在后台创建)',
|
||||||
success: function (res) {
|
success: function (res) {
|
||||||
if (res.confirm) {
|
if (res.confirm && res.content) {
|
||||||
http.post('/api/v1/device/bind', { device_id: mockDeviceId }).then(function (data) {
|
var deviceId = res.content.trim()
|
||||||
return http.post('/api/v1/device/bind/confirm', {
|
http.post('/api/v1/device/mock-bind', { device_id: deviceId }).then(function () {
|
||||||
device_id: data.device_id,
|
|
||||||
bind_token: data.bind_token
|
|
||||||
})
|
|
||||||
}).then(function () {
|
|
||||||
wx.showToast({ title: '模拟绑定成功', icon: 'success' })
|
wx.showToast({ title: '模拟绑定成功', icon: 'success' })
|
||||||
self.checkState()
|
self.checkState()
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
|
|||||||
@@ -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 } })
|
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 => {
|
router.post('/api/v1/device/unbind', async ctx => {
|
||||||
const user = await requireUser(ctx)
|
const user = await requireUser(ctx)
|
||||||
if (!user) return fail(1001, 'invalid_token')
|
if (!user) return fail(1001, 'invalid_token')
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户