OpenWrt WebUI 交互架构深度解析

在 OpenWrt 的开发中,HTML/JS (前端)、Lua (后端逻辑) 和 UCI/ubus (系统底层) 共同构成了一个完整的数据闭环。

1. 宏观架构图:数据是如何流动的?

想象一下,用户在网页上点击了"保存 Wi-Fi 密码"按钮,数据会经历以下旅程:

复制代码
graph TD
    User[用户 (HTML/CSS)] -->|1. 点击保存| JS[JavaScript (前端逻辑)]
    JS -->|2. 发送 HTTP 请求 (JSON)| Server[uhttpd (Web 服务器)]
    Server -->|3. 调用 CGI 脚本| Lua[Lua 脚本 (LuCI Controller)]
    
    subgraph "路由器内部 (后端 & 底层)"
    Lua -->|4. uci:set (修改配置)| UCI[UCI 配置文件 (/etc/config/)]
    Lua -->|4. ubus call (获取状态)| Ubus[ubus 总线 (系统状态)]
    UCI -->|5. commit & reload| System[系统内核/服务]
    end
    
    Lua -->|6. 返回结果 (JSON)| JS
    JS -->|7. 更新界面 DOM| User

2. 交互细节与数据格式

交互 A:前端 -> 后端 (JavaScript 调用 Lua)

这是最关键的一步,打通了浏览器和路由器。

  • 场景:用户填好表单,点击提交;或者页面自动刷新获取流量数据。

  • 协议:HTTP / HTTPS

  • 数据格式JSON (现代推荐) 或 application/x-www-form-urlencoded (传统 CBI)。

代码示例 (现代 JSON 方式)

1. JavaScript (发送方):

复制代码
// 前端将数据打包成 JSON
const payload = {
    ssid: "Quectel_WiFi",
    password: "new_password_123"
};

// 发送 POST 请求给 Lua 定义的 API 接口
fetch('/cgi-bin/luci/admin/api/update_wifi', {
    method: 'POST',
    body: JSON.stringify(payload) // 格式化为 JSON 字符串
})
.then(res => res.json())
.then(data => console.log("Lua 返回结果:", data));

2. Lua (接收方 - Controller):

复制代码
-- /usr/lib/lua/luci/controller/api.lua
function index()
    -- 注册路由,对应前端 fetch 的 URL
    entry({"admin", "api", "update_wifi"}, call("action_update_wifi"))
end

function action_update_wifi()
    -- 读取 HTTP 请求体
    local json_str = luci.http.content()
    -- 将 JSON 字符串解析为 Lua Table (核心转换)
    local data = luci.jsonc.parse(json_str) 
    
    -- 此时 data 就是 { ssid = "Quectel_WiFi", password = "..." }
    -- ... 后续处理 ...
end

交互 B:后端 -> 底层 (Lua 操作 UCI/ubus)

Lua 拿到数据后,通过 OpenWrt 提供的标准库与系统进行交互。

  • 场景:将 Wi-Fi 密码写入配置文件;读取当前 CPU 负载。

  • 接口:luci.model.uci (配置) 和 luci.sys / ubus (状态)。

  • 数据格式Lua Table

代码示例 1:Lua 操作 UCI (存配置)

UCI 是 OpenWrt 的统一配置接口,Lua 通过 Cursor (游标) 来操作它。

复制代码
local uci = require "luci.model.uci".cursor()

-- 1. 读取 (Get)
-- 相当于命令: uci get wireless.@wifi-iface[0].ssid
local current_ssid = uci:get("wireless", "@wifi-iface[0]", "ssid")

-- 2. 写入 (Set)
-- 相当于命令: uci set wireless.@wifi-iface[0].ssid='NewName'
uci:set("wireless", "@wifi-iface[0]", "ssid", data.ssid)
uci:set("wireless", "@wifi-iface[0]", "key", data.password)

-- 3. 提交 (Commit) - 必须提交才生效
-- 相当于命令: uci commit wireless
uci:commit("wireless")
代码示例 2:Lua 调用 ubus (读状态)

ubus 是 OpenWrt 的系统总线,用于进程间通信(例如获取网络接口的实时流量)。

复制代码
local ubus = require "ubus"
local conn = ubus.connect()

if conn then
    -- 调用 network.interface dump 方法
    -- 相当于命令: ubus call network.interface dump
    local status = conn:call("network.interface", "dump", {})
    
    -- status 是一个巨大的 Lua Table,包含所有接口的 IP、Mac、流量等信息
    local wan_ip = status.interface[1].ipv4_address[1].address
end

交互 C:后端 -> 前端 (Lua 返回数据给 JS)

Lua 处理完业务逻辑后,需要将结果告诉前端。

  • 场景:告诉用户"保存成功";返回最新的网速数据。

  • 数据格式JSON

代码示例

Lua (发送方):

复制代码
-- 准备返回的数据 (Lua Table)
local result = {
    code = 0,
    msg = "保存成功",
    timestamp = os.time()
}

-- 设置 HTTP 头
luci.http.prepare_content("application/json")
-- 将 Lua Table 编码回 JSON 字符串并输出
luci.http.write_json(result)

JavaScript (接收方):

复制代码
// 前端 fetch 的 .then(data => ...) 接收到的就是上面的 result 对象
if (data.code === 0) {
    alert(data.msg); // 弹出 "保存成功"
}

3. 两种开发模式的区别

根据您的项目(React),您主要使用的是 模式二

模式一:传统 LuCI CBI (Server-Side Rendering)

  • 特点:Lua 代码中直接定义表单结构(Map, Section, Value)。

  • 渲染:Lua 运行并在服务器端生成完整的 HTML 发给浏览器。

  • 交互:配置定义非常快,但界面风格死板,很难做成您视频中那样炫酷的效果。

  • 核心文件:/usr/lib/lua/luci/model/cbi/xxx.lua

模式二:现代前后端分离 (Client-Side Rendering) - 推荐

  • 特点:HTML/JS 是静态资源,React 负责渲染。

  • 渲染:浏览器运行 JS 生成界面。

  • 交互JS 通过 JSON API 与 Lua 通信

  • 优势:可以完全自定义 UI(如华为风格、Quectel 风格),用户体验极其流畅。

  • 核心:JS 负责美貌,Lua 负责干活,中间用 JSON 传话。

4. 总结:交互字典

|------------|---------------|--------------|---------------|----------------------------------|
| 交互动作 | 发起方 | 接收方 | 传输格式 | 关键函数/库 |
| 保存配置 | JavaScript | Lua | JSON | fetch(POST) -> luci.jsonc.parse |
| 获取状态 | JavaScript | ubus (或 Lua) | JSON-RPC | ubus call (JS库) 或 fetch(GET) |
| 修改系统配置 | Lua | UCI | Lua Table | uci:set, uci:commit |
| 读取系统状态 | Lua | ubus | Lua Table | ubus_conn:call |
| 页面渲染 | React/Browser | DOM | HTML/CSS | ReactDOM.render |

相关推荐
这是个栗子4 小时前
【Vue代码分析】前端动态路由传参与可选参数标记:实现“添加/查看”模式的灵活路由配置
前端·javascript·vue.js
刘一说4 小时前
Vue 动态路由参数丢失问题详解:为什么 `:id` 拿不到值?
前端·javascript·vue.js
方也_arkling5 小时前
elementPlus按需导入配置
前端·javascript·vue.js
爱吃大芒果5 小时前
Flutter for OpenHarmony 实战: mango_shop 资源文件管理与鸿蒙适配
javascript·flutter·harmonyos
David凉宸5 小时前
vue2与vue3的差异在哪里?
前端·javascript·vue.js
Irene19915 小时前
JavaScript字符串转数字方法总结
javascript·隐式转换
星空露珠6 小时前
速算24点检测生成核心lua
开发语言·数据库·算法·游戏·lua
css趣多多6 小时前
this.$watch
前端·javascript·vue.js
Code小翊6 小时前
JS语法速查手册,一遍过JS
javascript
子春一6 小时前
Flutter for OpenHarmony:构建一个 Flutter 天气卡片组件,深入解析动态 UI、响应式布局与语义化设计
javascript·flutter·ui