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 |

相关推荐
程序员Sunday8 小时前
JavaScript 事件循环面试题,宏任务与微任务怎么执行|Sunday面试指南
开发语言·javascript·面试·校招·事件循环·程序员sunday
默_笙9 小时前
🌊 向量库和 ES 都查不出"关系",我只好给奶茶建了一张人脉网
前端·javascript
思无邪6610 小时前
用 AI 做 JS 逆向:从抓包到复现的完整方法论
开发语言·javascript·人工智能
福兮说12 小时前
浏览器里把 iPhone 的 HEIC 转成 JPG:Chrome 解不开、转完大了七成、拍摄时间全丢,六个坑实测
前端·javascript·图像处理·chrome·ios·iphone·heic
可乐鸡翅yeah_13 小时前
video.js 集成 hls.js 开发 M3U8 播放器,新手高频踩坑
开发语言·前端·javascript·后端·ecmascript·m3u8·音视频在线播放
用户1179536547814 小时前
TRAE vs Claude Code 性能对比:从代码生成、多文件重构到底层吞吐,谁更适合你的日常开发?
javascript
kill52215 小时前
useRef
前端·javascript·react.js
米多科技15 小时前
我做了一个 3D 虚拟世界基底,在上面再搭 3D 世界或元宇宙,事半功倍
javascript·人工智能·github
霍营_托尼16 小时前
纯前端播 40GB 本地视频?我把浏览器改造成了「磁盘流式」播放器
前端·javascript·音视频开发
西柚小萌新16 小时前
【LLM&&AI应用开发 八股文】--4.2.Agent智能体(中)
前端·javascript·react.js