随着物联网技术在零售行业的深度应用,传统门店面临以下核心痛点:
本文将展示如何基于Next.js构建高性能IoT数据可视化平台,实现以下技术突破: ✅ 多源IoT设备数据实时聚合 ✅ 3D空间热力图与时间轴联动分析 ✅ 边缘计算与云端协同的混合架构 ✅ 零配置可复用的数据看板系统
图表
代码
组件 | 方案 | 优势 |
---|---|---|
前端框架 | Next.js 14 | 支持SSR/ISR的React框架 |
可视化库 | ECharts + Deck.gl | 支持3D地理可视化 |
实时通信 | Socket.io | 兼容HTTP/WebSocket |
状态管理 | Zustand | 轻量级状态解决方案 |
地理处理 | Turf.js | 空间数据分析库 |
typescript
// pages/api/ingest.ts
import mqtt from 'mqtt'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const client = mqtt.connect('mqtt://iot-gateway')
client.on('connect', () => {
client.subscribe('sensors/#', (err) => {
if (!err) {
res.status(200).json({ status: 'connected' })
}
})
})
client.on('message', (topic, message) => {
const payload = JSON.parse(message.toString())
// 数据标准化处理
normalizePayload(payload).then(data => {
// 写入Redis时间序列数据库
redis.xadd(`iot:${payload.deviceId}`, '*', 'data', JSON.stringify(data))
// 广播到WebSocket频道
io.emit(`update:${payload.deviceType}`, data)
})
})
}
typescript
// pages/api/dashboard/[storeId].ts
export async function getServerSideProps(context) {
const { storeId } = context.params!
// 获取近1小时聚合数据(SSR预取)
const historical = await prisma.$queryRaw`
SELECT
time_bucket('5 minutes', timestamp) as bucket,
avg(temperature) as temp,
avg(humidity) as humidity
FROM sensor_data
WHERE store_id = ${storeId}
AND timestamp > NOW() - INTERVAL '1 hour'
GROUP BY bucket
`
// 获取实时数据快照
const realtime = await redis.mget(
`realtime:${storeId}:temperature`,
`realtime:${storeId}:humidity`,
`realtime:${storeId}:footfall`
)
return {
props: {
initialData: {
historical: JSON.parse(JSON.stringify(historical)),
realtime: {
temperature: parseFloat(realtime[0]),
humidity: parseFloat(realtime[1]),
footfall: parseInt(realtime[2])share.kqiuzb.mobi
}
}
}
}
}
tsx
// components/HeatMap3D.tsx
import DeckGL from '@deck.gl/react'
import { HeatmapLayer } from '@deck.gl/aggregation-layers'
export default function HeatMap3D({ data }) {
const layers = [
new HeatmapLayer({
id: 'heatmap-layer',
data,
getPosition: d => [d.longitude, d.latitude],
getWeight: d => d.intensity,
radiusPixels: 30,
intensity: 0.5,
threshold: 0.1
})
]
return (
<DeckGL
initialViewState={{
longitude: data[0].longitude,
latitude: data[0].latitude,
zoom: 16,
pitch: 60
}}
controller={true}
layers={layers}
>
<Map reuseMaps mapStyle="mapbox://styles/mapbox/dark-v11" />
</DeckGL>blog.kqiuzb.mobi
)
}
tsx
// components/MetricsDashboard.tsx
import { useSocket } from '@/lib/socket'
export default function Dashboard({ initialData }) {
const [metrics, setMetrics] = useState(initialData.realtime)
const { socket } = useSocket()
useEffect(() => {
socket.on('update:sensors', (data) => {
setMetrics(prev => ({
...prev,
[data.type]: data.value
}))
})
return () => socket.off('update:sensors')
}, [])
return (
<div className="grid grid-cols-3 gap-4">
<MetricCard
title="温度"
value={`${metrics.temperature}°C`}
delta={calculateDelta('temperature')}
/>
<MetricCard
title="湿度"
value={`${metrics.humidity}%`}
delta={calculateDelta('humidity')}
/>
<MetricCard
title="实时客流量"
value={metrics.footfall}
trend={getTrend('footfall')}
/>
</div>
)
}
typescript
// pages/stores/[id].tsx
export async function getStaticProps(context) {
const storeData = await fetchStoreBasics(context.params.id)
return {
props: { storeData },
revalidate: 3600 // 每小时重新生成
}
}
export async function getStaticPaths() {
const stores = await prisma.store.findMany({
select: { id: true }
})
return {
paths: stores.map(store => ({ params: { id: store.id } })),
fallback: 'blocking'
}
}
typescript
// lib/socket.ts
let socketInstance: SocketIOClient.Socket | null = null
export const useSocket = () => {
const [socket, setSocket] = useState<SocketIOClient.Socket | null>(null)
useEffect(() => {
if (!socketInstance) {
socketInstance = io(process.env.NEXT_PUBLIC_WS_URL!, {
reconnectionAttempts: 5,
timeout: 3000,
transports: ['websocket']
})
}
setSocket(socketInstance)
return () => {
// 不关闭连接,供多组件复用
}
}, [])
return { socket }uou.kqiuzb.mobi
}
dockerfile
# Dockerfile.edge
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["npm", "run", "start:edge"]
# 启动命令添加--experimental-https
# 用于门店本地HTTPS证书自动更新
yaml
# next.config.js 自定义配置
module.exports = {
experimental: {
instrumentationHook: true
},
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-Edge-Location',
value: process.env.EDGE_LOCATION || 'cloud'
}
]
}
]
}
}
指标 | 实施前 | 实施后 |
---|---|---|
异常响应速度 | 45分钟 | 2.3分钟 |
能耗节约 | - | 18% |
客单价提升 | - | 12% |
部署成本 | 高(定制硬件) | 低(通用设备) |
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。