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,272 @@
|
||||
// BLE protocol: frame encoding/decoding, checksum, constants
|
||||
|
||||
var SERVICE = {
|
||||
DEVICE_INFO: 'FFE0',
|
||||
DATA_COMM: 'FFE1',
|
||||
OTA: 'FFE2'
|
||||
}
|
||||
|
||||
var CHAR = {
|
||||
DEVICE_INFO: 'FFE3',
|
||||
COMMAND: 'FFE4',
|
||||
STATUS: 'FFE5',
|
||||
BOND_INFO: 'FFE6',
|
||||
OTA_CONTROL: 'FFE7',
|
||||
OTA_DATA: 'FFE8',
|
||||
OTA_STATUS: 'FFE9'
|
||||
}
|
||||
|
||||
var CMD = {
|
||||
SET_PARAMS: 0x01,
|
||||
START: 0x02,
|
||||
STOP: 0x03,
|
||||
QUERY_STATUS: 0x04,
|
||||
BIND: 0x05,
|
||||
UNBIND: 0x06
|
||||
}
|
||||
|
||||
var NOTIFY = {
|
||||
STATUS_REPORT: 0x21,
|
||||
ACK: 0x22,
|
||||
TREATMENT_COMPLETE: 0x31,
|
||||
EXCEPTION: 0x32,
|
||||
BIND_SUCCESS: 0x33
|
||||
}
|
||||
|
||||
var MODE_STATE = {
|
||||
IDLE: 0x00,
|
||||
SCANNING: 0x01,
|
||||
ACTIVE: 0x02,
|
||||
PAUSED: 0x03,
|
||||
COMPLETED: 0x04,
|
||||
ERROR: 0x05,
|
||||
OTA: 0x06
|
||||
}
|
||||
|
||||
var WAVELENGTH = {
|
||||
IR: 1,
|
||||
R: 2,
|
||||
UV: 3,
|
||||
Y: 4
|
||||
}
|
||||
|
||||
var TREAT_MODE = {
|
||||
NORMAL: 0,
|
||||
SMART: 1
|
||||
}
|
||||
|
||||
var REGION = {
|
||||
LEFT_CHEEK: 0x01,
|
||||
RIGHT_CHEEK: 0x02,
|
||||
FOREHEAD: 0x04,
|
||||
CHIN: 0x08,
|
||||
NOSE: 0x10,
|
||||
LEFT_EYE: 0x20,
|
||||
RIGHT_EYE: 0x40,
|
||||
FULL_FACE: 0x7F
|
||||
}
|
||||
|
||||
var REGION_NAMES = ['left_cheek', 'right_cheek', 'forehead', 'chin', 'nose', 'left_eye', 'right_eye']
|
||||
|
||||
var DEVICE_ERR = {
|
||||
0x00: 'SUCCESS',
|
||||
0x01: 'ERR_REGION_INVALID',
|
||||
0x02: 'ERR_REGION_EMPTY',
|
||||
0x03: 'ERR_BRIGHTNESS_INVALID',
|
||||
0x04: 'ERR_DURATION_INVALID',
|
||||
0x05: 'ERR_NOT_BOUND',
|
||||
0x06: 'ERR_NO_SUBSCRIPTION',
|
||||
0x07: 'ERR_TEMP_HIGH',
|
||||
0x08: 'ERR_BATTERY_LOW',
|
||||
0x09: 'ERR_ALREADY_RUNNING',
|
||||
0x0A: 'ERR_NOT_RUNNING',
|
||||
0x0B: 'ERR_OTA_FAILED',
|
||||
0x0C: 'ERR_BLE_DISCONNECTED'
|
||||
}
|
||||
|
||||
// --- byte utilities ---
|
||||
|
||||
function bufferToBytes(buffer) {
|
||||
var arr = new Uint8Array(buffer)
|
||||
var bytes = []
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
bytes.push(arr[i])
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
function bytesToBuffer(bytes) {
|
||||
var buffer = new ArrayBuffer(bytes.length)
|
||||
var view = new Uint8Array(buffer)
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
view[i] = bytes[i]
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
|
||||
function xorChecksum(bytes) {
|
||||
var result = 0
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
result ^= bytes[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function uint32ToBytes(value) {
|
||||
return [
|
||||
(value >> 24) & 0xFF,
|
||||
(value >> 16) & 0xFF,
|
||||
(value >> 8) & 0xFF,
|
||||
value & 0xFF
|
||||
]
|
||||
}
|
||||
|
||||
function bytesToUint32(bytes, offset) {
|
||||
return (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]
|
||||
}
|
||||
|
||||
function hexToBytes(hex) {
|
||||
var bytes = []
|
||||
for (var i = 0; i < hex.length; i += 2) {
|
||||
bytes.push(parseInt(hex.substr(i, 2), 16))
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
function bytesToHex(bytes) {
|
||||
var hex = ''
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
hex += ('0' + bytes[i].toString(16)).slice(-2)
|
||||
}
|
||||
return hex.toUpperCase()
|
||||
}
|
||||
|
||||
// --- frame encoding/decoding ---
|
||||
|
||||
function buildFrame(type, payload) {
|
||||
var len = payload ? payload.length : 0
|
||||
var frame = [0xAA, 0x55, len, type]
|
||||
if (payload && payload.length > 0) {
|
||||
frame = frame.concat(payload)
|
||||
}
|
||||
var checkBytes = frame.slice(0)
|
||||
frame.push(xorChecksum(checkBytes))
|
||||
return bytesToBuffer(frame)
|
||||
}
|
||||
|
||||
function parseFrame(buffer) {
|
||||
var bytes = bufferToBytes(buffer)
|
||||
if (bytes.length < 5) return null
|
||||
if (bytes[0] !== 0xAA || bytes[1] !== 0x55) return null
|
||||
var len = bytes[2]
|
||||
if (bytes.length < 5 + len) return null
|
||||
var type = bytes[3]
|
||||
var payload = bytes.slice(4, 4 + len)
|
||||
var checksum = bytes[4 + len]
|
||||
var expected = xorChecksum(bytes.slice(0, 4 + len))
|
||||
if (checksum !== expected) return null
|
||||
return { type: type, payload: payload, seq: payload.length > 0 ? payload[payload.length - 1] : 0 }
|
||||
}
|
||||
|
||||
// --- notification parsing ---
|
||||
|
||||
function parseStatusReport(payload) {
|
||||
if (payload.length < 14) return null
|
||||
return {
|
||||
mode_state: payload[0],
|
||||
region_mask: payload[1],
|
||||
wavelength: payload[2],
|
||||
brightness: payload[3],
|
||||
remaining_ms: bytesToUint32(payload, 4),
|
||||
error_code: payload[8],
|
||||
command_seq: payload[9],
|
||||
battery: payload[10],
|
||||
temperature: payload[11],
|
||||
bind_status: payload[12],
|
||||
subscription: payload[13]
|
||||
}
|
||||
}
|
||||
|
||||
function parseAck(payload) {
|
||||
return {
|
||||
seq: payload[0],
|
||||
error_code: payload.length > 1 ? payload[1] : 0,
|
||||
error_msg: DEVICE_ERR[payload.length > 1 ? payload[1] : 0] || 'UNKNOWN'
|
||||
}
|
||||
}
|
||||
|
||||
function parseTreatmentComplete(payload) {
|
||||
return {
|
||||
session_id: bytesToHex(payload.slice(0, 8)),
|
||||
regions: payload[8],
|
||||
total_duration_ms: bytesToUint32(payload, 9),
|
||||
avg_pd: payload[13]
|
||||
}
|
||||
}
|
||||
|
||||
function parseException(payload) {
|
||||
return {
|
||||
error_code: payload[0],
|
||||
error_msg: DEVICE_ERR[payload[0]] || 'UNKNOWN',
|
||||
temperature: payload.length > 1 ? payload[1] : 0
|
||||
}
|
||||
}
|
||||
|
||||
// --- display helpers ---
|
||||
|
||||
function getRegionName(mask) {
|
||||
var names = []
|
||||
var bits = [
|
||||
[0x01, '左脸颊'], [0x02, '右脸颊'], [0x04, '额头'],
|
||||
[0x08, '下巴'], [0x10, '鼻部'], [0x20, '左眼周'], [0x40, '右眼周']
|
||||
]
|
||||
for (var i = 0; i < bits.length; i++) {
|
||||
if (mask & bits[i][0]) names.push(bits[i][1])
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
function getWavelengthName(code) {
|
||||
var map = { 1: '红外 850nm', 2: '红光 630nm', 3: '紫光 405nm', 4: '黄光 590nm' }
|
||||
return map[code] || '未知'
|
||||
}
|
||||
|
||||
function getModeStateName(code) {
|
||||
var map = {
|
||||
0x00: '空闲', 0x01: '扫描中', 0x02: '护理中',
|
||||
0x03: '已暂停', 0x04: '已完成', 0x05: '异常', 0x06: 'OTA升级中'
|
||||
}
|
||||
return map[code] || '未知'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SERVICE: SERVICE,
|
||||
CHAR: CHAR,
|
||||
CMD: CMD,
|
||||
NOTIFY: NOTIFY,
|
||||
MODE_STATE: MODE_STATE,
|
||||
WAVELENGTH: WAVELENGTH,
|
||||
TREAT_MODE: TREAT_MODE,
|
||||
REGION: REGION,
|
||||
REGION_NAMES: REGION_NAMES,
|
||||
DEVICE_ERR: DEVICE_ERR,
|
||||
|
||||
bufferToBytes: bufferToBytes,
|
||||
bytesToBuffer: bytesToBuffer,
|
||||
xorChecksum: xorChecksum,
|
||||
uint32ToBytes: uint32ToBytes,
|
||||
bytesToUint32: bytesToUint32,
|
||||
hexToBytes: hexToBytes,
|
||||
bytesToHex: bytesToHex,
|
||||
|
||||
buildFrame: buildFrame,
|
||||
parseFrame: parseFrame,
|
||||
parseStatusReport: parseStatusReport,
|
||||
parseAck: parseAck,
|
||||
parseTreatmentComplete: parseTreatmentComplete,
|
||||
parseException: parseException,
|
||||
|
||||
getRegionName: getRegionName,
|
||||
getWavelengthName: getWavelengthName,
|
||||
getModeStateName: getModeStateName
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户