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
这个提交包含在:
@@ -0,0 +1,187 @@
|
||||
// BLE commands: writing commands and high-level device operations
|
||||
|
||||
var protocol = require('./protocol')
|
||||
var connection = require('./connection')
|
||||
|
||||
var _cmdSeq = 0
|
||||
var _pendingAcks = {}
|
||||
|
||||
function nextSeq() {
|
||||
_cmdSeq = (_cmdSeq + 1) % 256
|
||||
return _cmdSeq
|
||||
}
|
||||
|
||||
function getPendingAcks() {
|
||||
return _pendingAcks
|
||||
}
|
||||
|
||||
function clearPendingAcks() {
|
||||
_pendingAcks = {}
|
||||
}
|
||||
|
||||
// --- low-level write ---
|
||||
|
||||
function writeCommand(type, payload) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (!connection.isConnected()) {
|
||||
reject({ error_code: 0x0C, error_msg: 'ERR_BLE_DISCONNECTED' })
|
||||
return
|
||||
}
|
||||
var chars = connection.getChars()
|
||||
if (!chars.command) {
|
||||
reject({ error_code: 0xFF, error_msg: 'command characteristic not found' })
|
||||
return
|
||||
}
|
||||
|
||||
var seq = nextSeq()
|
||||
var payloadWithSeq = (payload || []).concat([seq])
|
||||
var buffer = protocol.buildFrame(type, payloadWithSeq)
|
||||
|
||||
_pendingAcks[seq] = { resolve: resolve, reject: reject }
|
||||
|
||||
setTimeout(function () {
|
||||
if (_pendingAcks[seq]) {
|
||||
_pendingAcks[seq].reject({ error_code: 0xFF, error_msg: 'ACK timeout' })
|
||||
delete _pendingAcks[seq]
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
var deviceId = connection.getDeviceId()
|
||||
wx.writeBLECharacteristicValue({
|
||||
deviceId: deviceId,
|
||||
serviceId: chars.command.serviceId,
|
||||
characteristicId: chars.command.uuid,
|
||||
value: buffer,
|
||||
success: function () {},
|
||||
fail: function () {
|
||||
delete _pendingAcks[seq]
|
||||
reject({ error_code: 0x0C, error_msg: 'ERR_BLE_DISCONNECTED' })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function writeCommandWithRetry(type, payload, maxRetries) {
|
||||
if (maxRetries === undefined) maxRetries = 3
|
||||
var attempt = 0
|
||||
function tryOnce() {
|
||||
attempt++
|
||||
return writeCommand(type, payload).catch(function (err) {
|
||||
if (err && err.error_code === 0x0C) {
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (attempt >= maxRetries) {
|
||||
return Promise.reject(err)
|
||||
}
|
||||
return new Promise(function (resolve) {
|
||||
setTimeout(resolve, 500)
|
||||
}).then(function () {
|
||||
return tryOnce()
|
||||
})
|
||||
})
|
||||
}
|
||||
return tryOnce()
|
||||
}
|
||||
|
||||
// --- high-level commands ---
|
||||
|
||||
function readDeviceInfo() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var chars = connection.getChars()
|
||||
var deviceId = connection.getDeviceId()
|
||||
if (!connection.isConnected() || !chars.deviceInfo) {
|
||||
reject({ msg: '设备未连接或特征值未就绪' })
|
||||
return
|
||||
}
|
||||
wx.readBLECharacteristicValue({
|
||||
deviceId: deviceId,
|
||||
serviceId: chars.deviceInfo.serviceId,
|
||||
characteristicId: chars.deviceInfo.uuid,
|
||||
success: function () {},
|
||||
fail: function () { reject({ msg: '读取设备信息失败' }) }
|
||||
})
|
||||
|
||||
var handler = function (res) {
|
||||
if (res.characteristicId.toUpperCase().indexOf(protocol.CHAR.DEVICE_INFO) !== -1) {
|
||||
wx.offBLECharacteristicValueChange(handler)
|
||||
var bytes = protocol.bufferToBytes(res.value)
|
||||
if (bytes.length >= 14) {
|
||||
resolve({
|
||||
hw_version: (bytes[0] << 8) | bytes[1],
|
||||
fw_version: (bytes[2] << 8) | bytes[3],
|
||||
device_type: (bytes[4] << 8) | bytes[5],
|
||||
device_id: protocol.bytesToHex(bytes.slice(6, 14))
|
||||
})
|
||||
} else {
|
||||
reject({ msg: '设备信息格式错误' })
|
||||
}
|
||||
}
|
||||
}
|
||||
wx.onBLECharacteristicValueChange(handler)
|
||||
})
|
||||
}
|
||||
|
||||
function setParams(options) {
|
||||
var regionMask = options.region_mask || protocol.REGION.FULL_FACE
|
||||
var wavelength = options.wavelength || protocol.WAVELENGTH.R
|
||||
var brightness = options.brightness || 200
|
||||
var durationMs = options.duration_ms || 600000
|
||||
var mode = options.mode !== undefined ? options.mode : protocol.TREAT_MODE.NORMAL
|
||||
|
||||
var payload = [
|
||||
regionMask,
|
||||
wavelength,
|
||||
brightness,
|
||||
protocol.uint32ToBytes(durationMs),
|
||||
mode
|
||||
].reduce(function (a, b) {
|
||||
return a.concat(Array.isArray(b) ? b : [b])
|
||||
}, [])
|
||||
|
||||
return writeCommandWithRetry(protocol.CMD.SET_PARAMS, payload)
|
||||
}
|
||||
|
||||
function startTreatment(regionMask) {
|
||||
var mask = regionMask || protocol.REGION.FULL_FACE
|
||||
return writeCommandWithRetry(protocol.CMD.START, [mask])
|
||||
}
|
||||
|
||||
function stopTreatment() {
|
||||
return writeCommandWithRetry(protocol.CMD.STOP, [])
|
||||
}
|
||||
|
||||
function queryStatus() {
|
||||
return writeCommandWithRetry(protocol.CMD.QUERY_STATUS, [])
|
||||
}
|
||||
|
||||
function bindDevice(userId, bindToken) {
|
||||
var userBytes = protocol.hexToBytes(userId)
|
||||
var tokenBytes = protocol.hexToBytes(bindToken)
|
||||
var ts = Math.floor(Date.now() / 1000)
|
||||
var tsBytes = protocol.uint32ToBytes(ts)
|
||||
|
||||
var payload = [0x01].concat(userBytes).concat(tokenBytes).concat(tsBytes)
|
||||
return writeCommandWithRetry(protocol.CMD.BIND, payload)
|
||||
}
|
||||
|
||||
function unbindDevice(userId) {
|
||||
var userBytes = protocol.hexToBytes(userId)
|
||||
var payload = [0x02].concat(userBytes)
|
||||
return writeCommandWithRetry(protocol.CMD.UNBIND, payload)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPendingAcks: getPendingAcks,
|
||||
clearPendingAcks: clearPendingAcks,
|
||||
|
||||
writeCommand: writeCommand,
|
||||
writeCommandWithRetry: writeCommandWithRetry,
|
||||
|
||||
readDeviceInfo: readDeviceInfo,
|
||||
setParams: setParams,
|
||||
startTreatment: startTreatment,
|
||||
stopTreatment: stopTreatment,
|
||||
queryStatus: queryStatus,
|
||||
bindDevice: bindDevice,
|
||||
unbindDevice: unbindDevice
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户