在 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 |