事件+Magic-API
本页面介绍如何将事件推送与Magic API结合使用,实现完整的二次开发方案。
组合原理
┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐
│ MISEB系统 │ │ Redis │ │ MagicEventProcessor │
│ 业务操作 │ ──→ │ Channel │ ──→ │ 事件处理器 │
└─────────────┘ └─────────────┘ └───────────┬─────────────┘
│
┌───────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────┐
│ Magic API │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 订单同步ERP │ │ 用户同步CRM │ │ 库存同步WMS │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└───────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ ERP系统 │ │ CRM系统 │ │ WMS系统 │
└──────────────┘ └──────────────┘ └──────────────┘工作流程
- 事件产生:系统业务操作(下单、支付等)触发事件
- 事件发布:事件发布到Redis Channel(
magic-api:event) - 事件监听:
MagicEventProcessor接收并解析事件 - 处理器匹配:查询
sys_event_handler表,找到对应的Magic API - 执行脚本:调用配置的Magic API脚本处理事件
- 日志记录:处理结果记录到Elasticsearch
配置步骤
步骤1:创建Magic API接口
在Magic API中创建事件处理接口:
javascript
// 接口路径: /custom/erp/order-sync
// 方法: POST
// 1. 获取事件数据
var event = payload.event;
var eventKey = event.eventKey;
var orderNo = event.payload;
log.info("收到事件: eventKey={}, orderNo={}", eventKey, orderNo);
// 2. 查询订单详情
var order = db.selectOne(`
SELECT o.*, u.phone, u.nickname
FROM eb_store_order o
LEFT JOIN eb_user u ON o.uid = u.uid
WHERE o.order_id = ?
`, orderNo);
if (!order) {
log.error("订单不存在: {}", orderNo);
return { success: false, message: "订单不存在" };
}
// 3. 查询订单商品
var products = db.select(`
SELECT product_id, product_name, sku, cart_num, price
FROM eb_store_order_info
WHERE order_no = ?
`, orderNo);
// 4. 转换为ERP格式并调用
var erpOrder = {
sourceOrderNo: order.order_id,
sourceSystem: "MISEB",
customerId: "MISEB_" + order.uid,
payAmount: order.pay_price / 100,
items: products.map(p => ({
skuCode: "MISEB_" + p.product_id,
productName: p.product_name,
quantity: p.cart_num
}))
};
var result = http.post(env.get('ERP_API_URL') + '/order/create', {
headers: { 'Authorization': 'Bearer ' + env.get('ERP_API_KEY') },
body: erpOrder
});
// 5. 处理结果
if (result.code == 0) {
log.info("ERP同步成功: orderNo={}", orderNo);
return { success: true };
} else {
log.error("ERP同步失败: orderNo={}, error={}", orderNo, result.message);
throw new Error(result.message); // 抛出异常会记录到日志
}步骤2:配置事件处理器
在MISEB平台管理后台配置:
菜单路径:系统设置 → 事件处理器 → 新增
| 配置项 | 值 |
|---|---|
| 事件标识 | ORDER_PAY_SUCCESS |
| 处理器名称 | 订单支付成功-同步ERP |
| Magic API | /custom/erp/order-sync |
| 执行方式 | 异步 |
| 状态 | 启用 |
步骤3:配置环境变量
在Magic API环境变量中配置外部系统信息:
ERP_API_URL = https://erp.company.com/api
ERP_API_KEY = your_erp_api_key完整示例:订单同步ERP
需求说明
订单支付成功后,自动同步到ERP系统,包括:
- 订单基本信息
- 订单商品信息
- 收货地址信息
实现代码
javascript
// 接口路径: /custom/erp/order-sync
// 用于处理 ORDER_PAY_SUCCESS 事件
// ============ 获取事件数据 ============
var event = payload.event;
var orderNo = event.payload;
log.info("处理订单支付成功事件: orderNo={}", orderNo);
// ============ 查询订单详情 ============
var order = db.selectOne(`
SELECT o.*, m.mer_name, u.nickname, u.phone
FROM eb_store_order o
LEFT JOIN eb_merchant m ON o.mer_id = m.id
LEFT JOIN eb_user u ON o.uid = u.uid
WHERE o.order_id = ?
`, orderNo);
if (!order) {
log.error("订单不存在: {}", orderNo);
return { success: false, message: "订单不存在" };
}
// ============ 查询订单商品 ============
var products = db.select(`
SELECT oi.*, p.bar_code
FROM eb_store_order_info oi
LEFT JOIN eb_store_product p ON oi.product_id = p.id
WHERE oi.order_no = ?
`, orderNo);
// ============ 查询收货地址 ============
var address = db.selectOne(`
SELECT * FROM eb_store_order_address WHERE order_no = ?
`, orderNo);
// ============ 转换为ERP格式 ============
var erpOrder = {
// 订单信息
sourceOrderNo: order.order_id,
sourceSystem: "MISEB",
merchantId: "MER_" + order.mer_id,
merchantName: order.mer_name,
// 客户信息
customerId: "MISEB_" + order.uid,
customerName: order.nickname,
customerPhone: order.phone,
// 金额信息
totalAmount: order.total_price / 100,
payAmount: order.pay_price / 100,
freightAmount: order.freight_price / 100,
discountAmount: order.coupon_price / 100,
// 支付信息
payType: order.pay_type,
payTime: order.pay_time,
transactionId: order.transaction_id,
// 收货地址
shippingAddress: {
name: address.real_name,
phone: address.user_phone,
province: address.province,
city: address.city,
district: address.district,
detail: address.detail,
fullAddress: address.province + address.city + address.district + address.detail
},
// 商品列表
items: products.map(p => ({
skuCode: p.bar_code || ("MISEB_" + p.product_id),
productId: p.product_id,
productName: p.product_name,
spec: p.sku,
quantity: p.cart_num,
price: p.price / 100,
amount: (p.price * p.cart_num) / 100
})),
// 备注
remark: order.mark || ""
};
// ============ 调用ERP接口 ============
try {
var result = http.post(env.get('ERP_API_URL') + '/order/create', {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + env.get('ERP_API_KEY')
},
body: erpOrder,
timeout: 15000
});
if (result.code == 0 || result.code == 200) {
// 记录ERP订单号到备注
var erpOrderId = result.data?.orderId || result.data?.order_id;
if (erpOrderId) {
db.update(`
UPDATE eb_store_order
SET mark = CONCAT(IFNULL(mark, ''), ' [ERP:', ?, ']')
WHERE order_id = ?
`, erpOrderId, orderNo);
}
log.info("订单同步ERP成功: orderNo={}, erpOrderId={}", orderNo, erpOrderId);
return { success: true, erpOrderId: erpOrderId };
} else {
log.error("订单同步ERP失败: orderNo={}, error={}", orderNo, result.message);
throw new Error("ERP接口返回错误: " + result.message);
}
} catch (e) {
log.error("订单同步ERP异常: orderNo={}, error={}", orderNo, e.message);
throw e;
}多事件处理
可以为同一个事件配置多个处理器:
| 事件标识 | 处理器名称 | Magic API |
|---|---|---|
| ORDER_PAY_SUCCESS | 同步ERP | /custom/erp/order-sync |
| ORDER_PAY_SUCCESS | 发送通知 | /custom/notify/order-paid |
| ORDER_PAY_SUCCESS | 积分奖励 | /custom/points/order-reward |
每个处理器都会被执行,互不影响。
通过FRP对接内网系统
结合FRP实现对接内网ERP/WMS:
javascript
// 通过FRP访问内网ERP
// FRP配置: localIP=192.168.1.10, localPort=8080, remotePort=16001
var result = http.post('http://FRP服务器IP:16001/api/order/create', {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + env.get('ERP_TOKEN')
},
body: erpOrder
});或者配置Magic API数据源直接操作内网数据库:
yaml
# application.yml
magic-api:
datasource:
erp-db:
url: jdbc:mysql://FRP服务器IP:16003/erp_database
username: readonly_user
password: passwordjavascript
// 直接查询内网ERP数据库
var erpProducts = db['erp-db'].select(`
SELECT sku_code, stock FROM products WHERE sku_code IN (?)
`, skuCodes);事件日志查询
处理结果会自动记录到Elasticsearch:
菜单路径:系统监控 → 事件日志
可以查看:
- 事件标识和消息内容
- 处理器名称
- 执行状态(SUCCESS/FAIL)
- 错误信息
- 处理耗时
最佳实践
1. 异步处理
对于调用外部API等耗时操作,配置为异步执行:
执行方式: 异步2. 错误处理
使用try-catch捕获异常,抛出的异常会记录到日志:
javascript
try {
var result = http.post(url, options);
if (result.code != 0) {
throw new Error("接口返回错误: " + result.message);
}
return { success: true };
} catch (e) {
log.error("处理失败: {}", e.message);
throw e; // 抛出异常,记录到事件日志
}3. 幂等设计
部分事件可能重复触发,做好幂等处理:
javascript
// 检查是否已同步
var exists = db.selectInt(`
SELECT COUNT(*) FROM sync_log
WHERE order_no = ? AND sync_type = 'erp'
`, orderNo);
if (exists > 0) {
log.info("订单已同步,跳过: orderNo={}", orderNo);
return { success: true, message: "已同步" };
}
// 同步处理...
// 记录同步日志
db.insert(`
INSERT INTO sync_log (order_no, sync_type, sync_time)
VALUES (?, 'erp', NOW())
`, orderNo);4. 使用环境变量
敏感配置使用环境变量:
javascript
var apiUrl = env.get('ERP_API_URL');
var apiKey = env.get('ERP_API_KEY');5. 详细日志
记录关键步骤日志,便于问题排查:
javascript
log.info("开始处理: orderNo={}", orderNo);
log.info("查询订单完成: order={}", order);
log.info("调用ERP接口: url={}", apiUrl);
log.info("ERP返回结果: result={}", result);下一步
- 事件类型列表 - 查看所有事件
- Magic-API快速入门 - Magic API入门
- FRP内网穿透 - 对接内网系统
