> 技术文档 > 微信小程序对接EdgeX Foundry详细指南

微信小程序对接EdgeX Foundry详细指南


微信小程序对接EdgeX Foundry详细指南

系统架构概述

微信小程序 → 服务器API网关/反向代理 → EdgeX Foundry (部署在服务器)

由于微信小程序要求所有请求必须使用HTTPS且域名需要备案,小程序无法直接访问EdgeX的API,需要通过服务器端做中转或反向代理。

第一部分:服务器端配置

方法一:使用Nginx反向代理(推荐)

  1. 安装Nginx

    # Ubuntu/Debiansudo apt update && sudo apt install nginx# CentOS/RHELsudo yum install epel-release && sudo yum install nginx
  2. 配置Nginx反向代理
    创建配置文件 /etc/nginx/conf.d/edgex.conf

    server { listen 443 ssl; server_name your-domain.com; # 替换为已备案的域名 # SSL证书配置 ssl_certificate /path/to/your/certificate.crt; ssl_certificate_key /path/to/your/private.key; # 核心数据API代理 location /edgex/core-data/ { proxy_pass http://localhost:59880/api/v2/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 添加CORS头部 add_header \'Access-Control-Allow-Origin\' \'*\' always; add_header \'Access-Control-Allow-Methods\' \'GET, POST, OPTIONS\' always; add_header \'Access-Control-Allow-Headers\' \'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization\' always; # 处理预检请求 if ($request_method = \'OPTIONS\') { add_header \'Access-Control-Allow-Origin\' \'*\'; add_header \'Access-Control-Allow-Methods\' \'GET, POST, OPTIONS\'; add_header \'Access-Control-Allow-Headers\' \'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization\'; add_header \'Access-Control-Max-Age\' 1728000; add_header \'Content-Type\' \'text/plain; charset=utf-8\'; add_header \'Content-Length\' 0; return 204; } } # 设备服务API代理 location /edgex/device-service/ { proxy_pass http://localhost:59881/api/v2/; # ... 类似上面的配置 }}
  3. 重启Nginx

    sudo nginx -t && sudo systemctl restart nginx

方法二:使用Node.js编写API网关(更灵活)

  1. 创建项目目录

    mkdir edgex-gateway && cd edgex-gatewaynpm init -ynpm install express cors axios
  2. 创建网关服务器文件 gateway.js

    const express = require(\'express\');const cors = require(\'cors\');const axios = require(\'axios\');const app = express();const port = 3000;// 中间件app.use(cors());app.use(express.json());// EdgeX服务地址配置const EDGEX_CONFIG = { coreData: \'http://localhost:59880\', deviceService: \'http://localhost:59881\', command: \'http://localhost:59882\'};// 身份验证中间件(可选)const authenticate = (req, res, next) => { // 这里可以添加JWT验证逻辑 const token = req.header(\'Authorization\'); if (!token) { return res.status(401).json({ error: \'访问被拒绝,缺少令牌\' }); } // 验证token逻辑... next();};// 获取设备数据app.get(\'/api/devices/:deviceName/events\', authenticate, async (req, res) => { try { const { deviceName } = req.params; const { limit = 10 } = req.query; const response = await axios.get( `${EDGEX_CONFIG.coreData}/api/v2/event/device/name/${deviceName}?limit=${limit}` ); res.json({ success: true, data: response.data }); } catch (error) { res.status(500).json({ success: false, message: \'获取设备数据失败\', error: error.message }); }});// 发送命令到设备app.post(\'/api/devices/:deviceName/command\', authenticate, async (req, res) => { try { const { deviceName } = req.params; const { command, params } = req.body; const response = await axios.post( `${EDGEX_CONFIG.command}/api/v2/device/name/${deviceName}/${command}`, params ); res.json({ success: true, data: response.data }); } catch (error) { res.status(500).json({ success: false, message: \'发送命令失败\', error: error.message }); }});// 获取设备列表app.get(\'/api/devices\', authenticate, async (req, res) => { try { const response = await axios.get( `${EDGEX_CONFIG.coreData}/api/v2/device/all?limit=100` ); res.json({ success: true, data: response.data }); } catch (error) { res.status(500).json({ success: false, message: \'获取设备列表失败\', error: error.message }); }});app.listen(port, () => { console.log(`EdgeX网关服务器运行在端口 ${port}`);});
  3. 使用PM2管理进程

    npm install -g pm2pm2 start gateway.js --name edgex-gatewaypm2 savepm2 startup

第二部分:微信小程序开发

1. 小程序网络请求封装

创建 utils/api.js 文件:

const BASE_URL = \'https://your-domain.com\'; // 替换为您的域名class ApiClient { constructor() { this.token = wx.getStorageSync(\'token\'); } // 统一请求方法 request(url, method = \'GET\', data = {}) { return new Promise((resolve, reject) => { const header = { \'Content-Type\': \'application/json\' }; if (this.token) { header[\'Authorization\'] = `Bearer ${this.token}`; } wx.request({ url: BASE_URL + url, method: method, data: data, header: header, success: (res) => { if (res.statusCode === 200) { resolve(res.data); } else { reject(res.data); } }, fail: (error) => { reject(error); } }); }); } // 获取设备事件数据 getDeviceEvents(deviceName, limit = 10) { return this.request(`/api/devices/${deviceName}/events?limit=${limit}`); } // 发送设备命令 sendDeviceCommand(deviceName, command, params) { return this.request(`/api/devices/${deviceName}/command`, \'POST\', { command, params }); } // 获取设备列表 getDeviceList() { return this.request(\'/api/devices\'); }}export default new ApiClient();

2. 小程序页面示例

创建 pages/device/device.js

const api = require(\'../../utils/api.js\');Page({ data: { deviceList: [], currentDevice: null, events: [], loading: false }, onLoad() { this.loadDevices(); }, // 加载设备列表 async loadDevices() { this.setData({ loading: true }); try { const result = await api.getDeviceList(); this.setData({ deviceList: result.data.devices, loading: false }); } catch (error) { wx.showToast({ title: \'加载设备失败\', icon: \'none\' }); this.setData({ loading: false }); } }, // 选择设备 onSelectDevice(e) { const device = e.currentTarget.dataset.device; this.setData({ currentDevice: device }); this.loadDeviceEvents(device.name); }, // 加载设备事件 async loadDeviceEvents(deviceName) { this.setData({ loading: true }); try { const result = await api.getDeviceEvents(deviceName, 20); this.setData({ events: result.data.events, loading: false }); } catch (error) { wx.showToast({ title: \'加载数据失败\', icon: \'none\' }); this.setData({ loading: false }); } }, // 发送命令 async sendCommand() { if (!this.data.currentDevice) { wx.showToast({ title: \'请先选择设备\', icon: \'none\' }); return; } try { const result = await api.sendDeviceCommand( this.data.currentDevice.name, \'switch\', { value: \'on\' } ); wx.showToast({ title: \'命令发送成功\', icon: \'success\' }); } catch (error) { wx.showToast({ title: \'命令发送失败\', icon: \'none\' }); } }});

创建 pages/device/device.wxml

<view class=\"container\">  <view class=\"section\"> <text class=\"section-title\">选择设备</text> <scroll-view scroll-x class=\"device-scroll\"> <view wx:for=\"{{deviceList}}\" wx:key=\"id\" class=\"device-item {{currentDevice && currentDevice.id === item.id ? \'active\' : \'\'}}\" bindtap=\"onSelectDevice\" data-device=\"{{item}}\" > <text>{{item.name}}</text> </view> </scroll-view> </view>  <view class=\"section\" wx:if=\"{{currentDevice}}\"> <text class=\"section-title\">设备数据: {{currentDevice.name}}</text> <view class=\"data-card\"> <view class=\"data-item\" wx:for=\"{{events}}\" wx:key=\"id\"> <text class=\"data-label\">{{item.readings[0].resourceName}}:</text> <text class=\"data-value\">{{item.readings[0].value}}</text> <text class=\"data-time\">{{item.origin}}</text> </view> </view> <button type=\"primary\" bindtap=\"sendCommand\" loading=\"{{loading}}\"> 发送开启命令 </button> </view>  <view wx:if=\"{{loading}}\" class=\"loading\"> <text>加载中...</text> </view></view>

创建 pages/device/device.wxss

.container { padding: 20rpx;}.section { margin-bottom: 40rpx;}.section-title { font-size: 32rpx; font-weight: bold; display: block; margin-bottom: 20rpx;}.device-scroll { white-space: nowrap; width: 100%;}.device-item { display: inline-block; padding: 20rpx 40rpx; margin-right: 20rpx; background-color: #f5f5f5; border-radius: 10rpx;}.device-item.active { background-color: #007aff; color: white;}.data-card { background-color: #fff; border-radius: 10rpx; padding: 20rpx; margin-bottom: 20rpx; box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);}.data-item { padding: 20rpx 0; border-bottom: 1rpx solid #eee;}.data-item:last-child { border-bottom: none;}.data-label { font-weight: bold; margin-right: 20rpx;}.data-value { color: #007aff;}.data-time { display: block; font-size: 24rpx; color: #999; margin-top: 10rpx;}.loading { text-align: center; padding: 40rpx; color: #999;}

3. 小程序配置文件

app.json 中添加页面配置:

{ \"pages\": [ \"pages/device/device\" ], \"window\": { \"navigationBarTitleText\": \"EdgeX设备监控\", \"navigationBarBackgroundColor\": \"#007aff\", \"navigationBarTextStyle\": \"white\" }, \"networkTimeout\": { \"request\": 10000 }}

第三部分:安全增强建议

  1. API访问控制

    // 在网关服务器中添加身份验证const jwt = require(\'jsonwebtoken\');// 登录接口app.post(\'/api/login\', async (req, res) => { const { username, password } = req.body; // 验证用户 credentials (简化示例) if (username === \'admin\' && password === \'password\') { const token = jwt.sign({ userId: 1 }, \'your-secret-key\', { expiresIn: \'24h\' }); res.json({ success: true, token }); } else { res.status(401).json({ success: false, message: \'认证失败\' }); }});
  2. 请求频率限制

    const rateLimit = require(\'express-rate-limit\');const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: 100 // 限制每个IP每15分钟最多100次请求});app.use(\'/api/\', limiter);

部署和测试步骤

  1. 部署EdgeX Foundry到您的服务器
  2. 配置Nginx反向代理或部署Node.js网关
  3. 申请HTTPS证书并配置到Nginx
  4. 微信小程序后台配置服务器域名
  5. 开发并上传小程序
  6. 测试设备数据读取和命令发送

常见问题解决

  1. 跨域问题:确保Nginx配置了正确的CORS头
  2. HTTPS问题:小程序要求所有请求使用HTTPS
  3. 域名备案:小程序要求的域名必须已完成ICP备案
  4. API限制:微信小程序有网络请求API的调用频率限制

这个方案提供了从服务器配置到小程序开发的完整实现,可以根据实际需求进行调整和扩展。