去年接手了一个电子外设销售平台的项目,客户要求实现从商品展示到硬件调试的全流程功能。这个系统最特别的地方在于,它不仅是个普通电商平台,还需要直接与Arduino、树莓派等开发板进行交互。经过技术选型,最终采用Vue 3作为前端框架,Node.js+Express作为后端服务,配合串口通信模块实现了硬件控制功能。整个开发周期约3个月,期间踩过不少硬件兼容性的坑,后面会详细说明解决方案。
选择Vue 3而非React的主要考虑因素:
javascript复制// vite.config.js 优化配置示例
export default defineConfig({
plugins: [vue()],
build: {
chunkSizeWarningLimit: 1600,
rollupOptions: {
output: {
manualChunks: {
'hardware-control': [
'./src/components/HardwareController.vue',
'./src/libs/serial-helper.js'
]
}
}
}
}
})
在Express和Koa之间最终选择了Express,主要因为:
数据库选型时做了AB测试:
电子外设通常有型号、接口类型等多个维度规格。采用组合算法生成SKU:
javascript复制// SKU生成算法
function generateSKU(attributes) {
const combinations = attributes.reduce((acc, curr) => {
return acc.flatMap(x =>
curr.values.map(v => [...x, `${curr.name}:${v}`])
)
}, [[]])
return combinations.map(combo => ({
id: hash(combo.join('|')),
specs: combo
}))
}
针对电子元件类商品的特点:
Node.js层使用serialport库的TypeScript版本:
typescript复制import { SerialPort } from 'serialport'
const port = new SerialPort({
path: '/dev/ttyUSB0',
baudRate: 115200,
dataBits: 8,
parity: 'none'
})
// 硬件指令队列
const commandQueue = new Map()
port.on('data', (data) => {
const response = data.toString()
const [cmdId, result] = response.split(':')
if(commandQueue.has(cmdId)) {
commandQueue.get(cmdId)(result)
commandQueue.delete(cmdId)
}
})
export async function sendCommand(command: string): Promise<string> {
const cmdId = generateUUID()
return new Promise((resolve) => {
commandQueue.set(cmdId, resolve)
port.write(`${cmdId}:${command}\n`)
})
}
通过WebUSB API实现的设备配对流程:
javascript复制// WebUSB设备连接示例
async function connectDevice() {
const device = await navigator.usb.requestDevice({
filters: [{ vendorId: 0x2341 }] // Arduino的厂商ID
})
await device.open()
await device.selectConfiguration(1)
await device.claimInterface(2)
return device
}
不同操作系统下的串口权限问题:
解决方案:
bash复制# Linux下的udev规则示例
SUBSYSTEM=="tty", ATTRS{idVendor}=="2341", MODE="0666", GROUP="plugdev"
PCI DSS合规要点:
支付流程优化:
javascript复制const HardwarePanel = defineAsyncComponent(() =>
import('./components/HardwarePanel.vue')
)
javascript复制// axios响应缓存拦截器
axios.interceptors.response.use(response => {
if(response.config.method === 'get') {
const cacheKey = hash(response.config.url + JSON.stringify(response.config.params))
cacheStore.set(cacheKey, response.data, 300000) // 5分钟缓存
}
return response
})
针对订单查询的索引优化:
sql复制-- 原查询(执行时间约1200ms)
SELECT * FROM orders WHERE user_id = ? AND status = ?;
-- 优化后(添加复合索引后执行时间约80ms)
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
硬件网关部署拓扑:
code复制[前端CDN] -> [负载均衡] -> [API集群]
-> [硬件网关节点]
↓
[本地USB设备]
使用PM2的监控方案:
bash复制# 安装监控模块
pm2 install pm2-logrotate
# 监控配置
module.exports = {
apps: [{
name: 'hardware-gateway',
script: './server.js',
watch: true,
env: {
NODE_ENV: 'production',
SERIAL_PORT: '/dev/ttyACM0'
},
log_date_format: 'YYYY-MM-DD HH:mm Z',
error_file: '/var/log/hardware/err.log',
out_file: '/var/log/hardware/out.log'
}]
}
硬件类电商系统开发有几个特别需要注意的点:
javascript复制// 监听USB设备插拔事件
navigator.usb.addEventListener('connect', handleDeviceConnect)
navigator.usb.addEventListener('disconnect', handleDeviceDisconnect)
javascript复制async function sendWithRetry(cmd, maxRetry = 3) {
let retry = 0
while(retry < maxRetry) {
try {
return await sendCommand(cmd)
} catch(err) {
retry++
await sleep(500 * retry)
}
}
throw new Error(`Command ${cmd} failed after ${maxRetry} retries`)
}
javascript复制const ws = new WebSocket('wss://api.example.com/device-status')
ws.onmessage = (event) => {
const data = JSON.parse(event.data)
store.commit('updateDeviceState', data)
}