微信小程序对接EdgeX Foundry详细指南
系统架构概述
微信小程序 → 服务器API网关/反向代理 → EdgeX Foundry (部署在服务器)
由于微信小程序要求所有请求必须使用HTTPS且域名需要备案,小程序无法直接访问EdgeX的API,需要通过服务器端做中转或反向代理。
第一部分:服务器端配置
方法一:使用Nginx反向代理(推荐)
-
安装Nginx
bash# Ubuntu/Debian sudo apt update && sudo apt install nginx # CentOS/RHEL sudo yum install epel-release && sudo yum install nginx
-
配置Nginx反向代理
创建配置文件
/etc/nginx/conf.d/edgex.conf
:nginxserver { 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/; # ... 类似上面的配置 } }
-
重启Nginx
bashsudo nginx -t && sudo systemctl restart nginx
方法二:使用Node.js编写API网关(更灵活)
-
创建项目目录
bashmkdir edgex-gateway && cd edgex-gateway npm init -y npm install express cors axios
-
创建网关服务器文件
gateway.js
javascriptconst 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}`); });
-
使用PM2管理进程
bashnpm install -g pm2 pm2 start gateway.js --name edgex-gateway pm2 save pm2 startup
第二部分:微信小程序开发
1. 小程序网络请求封装
创建 utils/api.js
文件:
javascript
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
:
javascript
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
:
html
<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
:
css
.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
中添加页面配置:
json
{
"pages": [
"pages/device/device"
],
"window": {
"navigationBarTitleText": "EdgeX设备监控",
"navigationBarBackgroundColor": "#007aff",
"navigationBarTextStyle": "white"
},
"networkTimeout": {
"request": 10000
}
}
第三部分:安全增强建议
-
API访问控制
javascript// 在网关服务器中添加身份验证 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: '认证失败' }); } });
-
请求频率限制
javascriptconst rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: 100 // 限制每个IP每15分钟最多100次请求 }); app.use('/api/', limiter);
部署和测试步骤
- 部署EdgeX Foundry到您的服务器
- 配置Nginx反向代理或部署Node.js网关
- 申请HTTPS证书并配置到Nginx
- 微信小程序后台配置服务器域名
- 开发并上传小程序
- 测试设备数据读取和命令发送
常见问题解决
- 跨域问题:确保Nginx配置了正确的CORS头
- HTTPS问题:小程序要求所有请求使用HTTPS
- 域名备案:小程序要求的域名必须已完成ICP备案
- API限制:微信小程序有网络请求API的调用频率限制
这个方案提供了从服务器配置到小程序开发的完整实现,可以根据实际需求进行调整和扩展。