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

发布于:2025-08-30 ⋅ 阅读:(14) ⋅ 点赞:(0)

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

系统架构概述

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

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

第一部分:服务器端配置

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

  1. 安装Nginx

    # Ubuntu/Debian
    sudo apt update && sudo apt install nginx
    
    # CentOS/RHEL
    sudo 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-gateway
    npm init -y
    npm 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 pm2
    pm2 start gateway.js --name edgex-gateway
    pm2 save
    pm2 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的调用频率限制

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


网站公告

今日签到

点亮在社区的每一天
去签到