secubox-openwrt/docs-zh/luci-development-reference.md
CyberMind-FR ccfb58124c docs: Add trilingual documentation (French and Chinese translations)
Add complete French (fr) and Chinese (zh) translations for all documentation:

- Root files: README, CHANGELOG, SECURITY, BETA-RELEASE
- docs/: All 16 core documentation files
- DOCS/: All 19 deep-dive documents including embedded/ and archive/
- package/secubox/: All 123+ package READMEs
- Misc: secubox-tools/, scripts/, EXAMPLES/, config-backups/, streamlit-apps/

Total: 346 translation files created

Each file includes language switcher links for easy navigation between
English, French, and Chinese versions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-20 10:00:18 +01:00

1194 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LuCI 开发参考指南
> **语言:** [English](../docs/luci-development-reference.md) | [Francais](../docs-fr/luci-development-reference.md) | 中文
**版本:** 1.0.0
**最后更新:** 2025-12-28
**状态:** 活跃
**基于:** luci-app-secubox 和 luci-app-system-hub 实现
**目标受众:** Claude.ai 和从事 OpenWrt LuCI 应用开发的开发人员
---
## 另请参阅
- **设计与标准:** [DEVELOPMENT-GUIDELINES.md](development-guidelines.md)
- **快速命令:** [QUICK-START.md](quick-start.md)
- **自动化简报:** [CODEX.md](codex.md)
- **代码模板:** [CODE-TEMPLATES.md](code-templates.md)
本文档记录了在开发 SecuBox LuCI 应用程序过程中发现的关键模式、最佳实践和常见陷阱。请将此作为所有未来 LuCI 应用开发的验证参考。
---
## 目录
1. [ubus 和 RPC 基础](#ubus-和-rpc-基础)
2. [RPCD 后端模式](#rpcd-后端模式)
3. [LuCI API 模块模式](#luci-api-模块模式)
4. [LuCI 视图导入模式](#luci-视图导入模式)
5. [ACL 权限结构](#acl-权限结构)
6. [数据结构约定](#数据结构约定)
7. [常见错误和解决方案](#常见错误和解决方案)
8. [验证清单](#验证清单)
9. [测试和部署](#测试和部署)
---
## ubus 和 RPC 基础
### 什么是 ubus
**ubus**OpenWrt 微总线架构)是 OpenWrt 的进程间通信IPC系统。它支持
- 进程间的 RPC远程过程调用
- Web 界面LuCI与后端服务的通信
- 通过 `ubus call` 进行命令行交互
### ubus 对象命名约定
**关键规则**:所有 LuCI 应用程序的 ubus 对象必须使用 `luci.` 前缀。
```javascript
// ✅ 正确
object: 'luci.system-hub'
object: 'luci.cdn-cache'
object: 'luci.wireguard-dashboard'
// ❌ 错误
object: 'system-hub'
object: 'systemhub'
object: 'cdn-cache'
```
**为什么?** LuCI 期望 Web 应用程序的对象在 `luci.*` 命名空间下。如果没有此前缀:
- ACL 权限将不匹配
- RPCD 将无法正确路由调用
- 浏览器控制台显示:`RPC call to system-hub/status failed with error -32000: Object not found`
### RPCD 脚本名称必须与 ubus 对象匹配
RPCD 脚本文件名必须与 ubus 对象名称完全匹配:
```bash
# 如果 JavaScript 声明:
# object: 'luci.system-hub'
# 那么 RPCD 脚本必须命名为:
/usr/libexec/rpcd/luci.system-hub
# 而不是:
/usr/libexec/rpcd/system-hub
/usr/libexec/rpcd/luci-system-hub
```
**验证命令**
```bash
# 检查 JavaScript 文件中的 ubus 对象名称
grep -r "object:" luci-app-*/htdocs --include="*.js"
# 验证 RPCD 脚本是否存在且名称匹配
ls luci-app-*/root/usr/libexec/rpcd/
```
### ubus 调用类型
**读操作**(类似 GET
- `status` - 获取当前状态
- `get_*` - 检索数据(例如 `get_health`、`get_settings`
- `list_*` - 枚举项目(例如 `list_services`
**写操作**(类似 POST
- `save_*` - 持久化配置(例如 `save_settings`
- `*_action` - 执行操作(例如 `service_action`
- `backup`、`restore`、`reboot` - 系统修改
**ACL 映射**
- 读操作 → ACL 中的 `"read"` 部分
- 写操作 → ACL 中的 `"write"` 部分
---
## RPCD 后端模式
### Shell 脚本结构
RPCD 后端是可执行的 shell 脚本,它们:
1. 解析 `$1` 获取操作(`list` 或 `call`
2. 解析 `$2` 获取方法名称(如果是 `call`
3. 从 stdin 读取 JSON 输入(用于带参数的方法)
4. 将 JSON 输出到 stdout
5. 成功时退出状态为 0失败时为非零
### 标准模板
```bash
#!/bin/sh
# RPCD 后端luci.system-hub
# 版本0.1.0
# 加载 JSON shell 助手
. /usr/share/libubox/jshn.sh
case "$1" in
list)
# 列出所有可用方法及其参数
echo '{
"status": {},
"get_health": {},
"service_action": { "service": "string", "action": "string" },
"save_settings": {
"auto_refresh": 0,
"health_check": 0,
"refresh_interval": 0
}
}'
;;
call)
case "$2" in
status)
status
;;
get_health)
get_health
;;
service_action)
# 从 stdin 读取 JSON 输入
read -r input
json_load "$input"
json_get_var service service
json_get_var action action
service_action "$service" "$action"
;;
save_settings)
read -r input
json_load "$input"
json_get_var auto_refresh auto_refresh
json_get_var health_check health_check
json_get_var refresh_interval refresh_interval
save_settings "$auto_refresh" "$health_check" "$refresh_interval"
;;
*)
echo '{"error": "Method not found"}'
exit 1
;;
esac
;;
esac
```
### 使用 jshn.sh 输出 JSON
**jshn.sh** 提供了用于 JSON 操作的 shell 函数:
```bash
# 初始化 JSON 对象
json_init
# 添加简单值
json_add_string "hostname" "openwrt"
json_add_int "uptime" 86400
json_add_boolean "running" 1
# 添加嵌套对象
json_add_object "cpu"
json_add_int "usage" 25
json_add_string "status" "ok"
json_close_object
# 添加数组
json_add_array "services"
json_add_string "" "network"
json_add_string "" "firewall"
json_close_array
# 将 JSON 输出到 stdout
json_dump
```
**常用函数**
- `json_init` - 开始新的 JSON 对象
- `json_add_string "key" "value"` - 添加字符串
- `json_add_int "key" 123` - 添加整数
- `json_add_boolean "key" 1` - 添加布尔值0 或 1
- `json_add_object "key"` - 开始嵌套对象
- `json_close_object` - 结束嵌套对象
- `json_add_array "key"` - 开始数组
- `json_close_array` - 结束数组
- `json_dump` - 将 JSON 输出到 stdout
### 错误处理
始终验证输入并返回有意义的错误:
```bash
service_action() {
local service="$1"
local action="$2"
# 验证服务名称
if [ -z "$service" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "Service name is required"
json_dump
return 1
fi
# 验证操作
case "$action" in
start|stop|restart|enable|disable)
;;
*)
json_init
json_add_boolean "success" 0
json_add_string "error" "Invalid action: $action"
json_dump
return 1
;;
esac
# 执行操作
/etc/init.d/"$service" "$action" >/dev/null 2>&1
if [ $? -eq 0 ]; then
json_init
json_add_boolean "success" 1
json_add_string "message" "Service $service $action successful"
json_dump
else
json_init
json_add_boolean "success" 0
json_add_string "error" "Service $service $action failed"
json_dump
return 1
fi
}
```
### UCI 集成
对于持久化配置,使用 UCI统一配置接口
```bash
save_settings() {
local auto_refresh="$1"
local health_check="$2"
local refresh_interval="$3"
# 创建/更新 UCI 配置
uci set system-hub.general=general
uci set system-hub.general.auto_refresh="$auto_refresh"
uci set system-hub.general.health_check="$health_check"
uci set system-hub.general.refresh_interval="$refresh_interval"
uci commit system-hub
json_init
json_add_boolean "success" 1
json_add_string "message" "Settings saved successfully"
json_dump
}
get_settings() {
# 加载 UCI 配置
if [ -f "/etc/config/system-hub" ]; then
. /lib/functions.sh
config_load system-hub
fi
json_init
json_add_object "general"
# 获取值或使用默认值
config_get auto_refresh general auto_refresh "1"
json_add_boolean "auto_refresh" "${auto_refresh:-1}"
config_get refresh_interval general refresh_interval "30"
json_add_int "refresh_interval" "${refresh_interval:-30}"
json_close_object
json_dump
}
```
### 性能提示
1. **缓存昂贵的操作**:不要多次重复读取 `/proc` 文件
2. **高效使用命令替换**
```bash
# 好
uptime=$(cat /proc/uptime | cut -d' ' -f1)
# 更好
read uptime _ < /proc/uptime
uptime=${uptime%.*}
```
3. **尽可能避免外部命令**
```bash
# 慢
count=$(ls /etc/init.d | wc -l)
# 快
count=0
for file in /etc/init.d/*; do
[ -f "$file" ] && count=$((count + 1))
done
```
---
## LuCI API 模块模式
### 关键:使用 baseclass.extend()
**规则**LuCI API 模块必须使用 `baseclass.extend()` 模式。
```javascript
'use strict';
'require baseclass';
'require rpc';
// 声明 RPC 方法
var callStatus = rpc.declare({
object: 'luci.system-hub',
method: 'status',
expect: {}
});
var callGetHealth = rpc.declare({
object: 'luci.system-hub',
method: 'get_health',
expect: {}
});
var callSaveSettings = rpc.declare({
object: 'luci.system-hub',
method: 'save_settings',
params: ['auto_refresh', 'health_check', 'refresh_interval'],
expect: {}
});
// ✅ 正确:使用 baseclass.extend()
return baseclass.extend({
getStatus: callStatus,
getHealth: callGetHealth,
saveSettings: callSaveSettings
});
// ❌ 错误:不要使用这些模式
return baseclass.singleton({...}); // 会破坏一切!
return {...}; // 普通对象不起作用
```
**为什么使用 baseclass.extend()**
- LuCI 的模块系统期望基于类的模块
- 视图使用 `'require module/api as API'` 导入,会自动实例化
- `baseclass.extend()` 创建正确的类构造函数
- `baseclass.singleton()` 会破坏实例化机制
- 普通对象不支持 LuCI 的模块生命周期
### rpc.declare() 参数
```javascript
var callMethodName = rpc.declare({
object: 'luci.module-name', // ubus 对象名称(必须以 luci. 开头)
method: 'method_name', // RPCD 方法名称
params: ['param1', 'param2'], // 可选:参数名称(顺序很重要!)
expect: {} // 期望的返回结构(或 { key: [] } 用于数组)
});
```
**参数顺序很重要**
```javascript
// RPCD 期望参数按此确切顺序
var callSaveSettings = rpc.declare({
object: 'luci.system-hub',
method: 'save_settings',
params: ['auto_refresh', 'health_check', 'debug_mode', 'refresh_interval'],
expect: {}
});
// JavaScript 调用必须按相同顺序传递参数
API.saveSettings(1, 1, 0, 30); // auto_refresh=1, health_check=1, debug_mode=0, refresh_interval=30
```
### expect 参数模式
```javascript
// 方法返回单个对象
expect: {}
// 方法在顶层返回数组
expect: { services: [] }
// 方法返回特定结构
expect: {
services: [],
count: 0
}
```
### API 模块中的错误处理
API 方法返回 Promise。在视图中处理错误
```javascript
return API.getHealth().then(function(data) {
if (!data || typeof data !== 'object') {
console.error('Invalid health data:', data);
return null;
}
return data;
}).catch(function(err) {
console.error('Failed to load health data:', err);
ui.addNotification(null, E('p', {}, 'Failed to load health data'), 'error');
return null;
});
```
---
## LuCI 视图导入模式
### 关键:为 API 使用 'require ... as VAR'
**规则**:导入 API 模块时,在文件顶部使用 `'require ... as VAR'` 模式。
```javascript
// ✅ 正确:自动实例化类
'require system-hub/api as API';
return L.view.extend({
load: function() {
return API.getHealth(); // API 已经实例化
}
});
// ❌ 错误:返回类构造函数,而不是实例
var api = L.require('system-hub.api');
api.getHealth(); // 错误api.getHealth is not a function
```
**为什么?**
- `'require module/path as VAR'`(使用斜杠)自动实例化类
- `L.require('module.path')`(使用点)返回原始类构造函数
- API 模块扩展 `baseclass`,需要实例化
- 使用 `as VAR` 模式时LuCI 的模块加载器处理实例化
### 标准视图结构
```javascript
'use strict';
'require view';
'require form';
'require ui';
'require system-hub/api as API';
return L.view.extend({
load: function() {
// 加载渲染所需的数据
return Promise.all([
API.getHealth(),
API.getStatus()
]);
},
render: function(data) {
var health = data[0];
var status = data[1];
// 创建 UI 元素
var container = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, 'Dashboard'),
// ... 更多元素
]);
return container;
},
handleSave: null, // 禁用保存按钮
handleSaveApply: null, // 禁用保存并应用按钮
handleReset: null // 禁用重置按钮
});
```
### 导入模式总结
```javascript
// 核心 LuCI 模块(始终使用引号)
'require view';
'require form';
'require ui';
'require rpc';
'require baseclass';
// 自定义 API 模块(使用 'as VAR' 进行自动实例化)
'require system-hub/api as API';
'require cdn-cache/api as CdnAPI';
// 访问全局 L 对象(无需 require
L.resolveDefault(...)
L.Poll.add(...)
L.ui.addNotification(...)
```
---
## ACL 权限结构
### 文件位置
ACL 文件位于:
```
/usr/share/rpcd/acl.d/luci-app-<模块名称>.json
```
在源代码树中:
```
luci-app-<模块名称>/root/usr/share/rpcd/acl.d/luci-app-<模块名称>.json
```
### 标准 ACL 模板
```json
{
"luci-app-module-name": {
"description": "Module Name - Description",
"read": {
"ubus": {
"luci.module-name": [
"status",
"get_system_info",
"get_health",
"list_services",
"get_logs",
"get_storage",
"get_settings"
]
}
},
"write": {
"ubus": {
"luci.module-name": [
"service_action",
"backup_config",
"restore_config",
"reboot",
"save_settings"
]
}
}
}
}
```
### 读取与写入分类
**读操作**(不修改系统):
- `status` - 获取当前状态
- `get_*` - 检索数据(系统信息、健康状况、设置、日志、存储)
- `list_*` - 枚举项目(服务、接口等)
**写操作**(修改系统状态):
- `*_action` - 执行操作(启动/停止服务等)
- `save_*` - 持久化配置更改
- `backup`、`restore` - 系统备份/恢复
- `reboot`、`shutdown` - 系统控制
### 常见 ACL 错误
**错误**`Access denied` 或 RPC 错误 `-32002`
**原因**:方法未在 ACL 文件中列出,或列在错误的部分(读取与写入)
**解决方案**
1. 确定方法是读操作还是写操作
2. 将方法名称添加到 ACL 的正确部分
3. 重启 RPCD`/etc/init.d/rpcd restart`
**验证**
```bash
# 检查 ACL 文件是否为有效 JSON
jsonlint /usr/share/rpcd/acl.d/luci-app-system-hub.json
# 列出所有 ubus 对象和方法
ubus list luci.system-hub
# 使用 ubus call 测试方法
ubus call luci.system-hub get_health
```
---
## 数据结构约定
### 健康指标结构system-hub v0.1.0
基于大量迭代,此结构提供清晰性和一致性:
```json
{
"cpu": {
"usage": 25,
"status": "ok",
"load_1m": "0.25",
"load_5m": "0.30",
"load_15m": "0.28",
"cores": 4
},
"memory": {
"total_kb": 4096000,
"free_kb": 2048000,
"available_kb": 3072000,
"used_kb": 1024000,
"buffers_kb": 512000,
"cached_kb": 1536000,
"usage": 25,
"status": "ok"
},
"disk": {
"total_kb": 30408704,
"used_kb": 5447680,
"free_kb": 24961024,
"usage": 19,
"status": "ok"
},
"temperature": {
"value": 45,
"status": "ok"
},
"network": {
"wan_up": true,
"status": "ok"
},
"services": {
"running": 35,
"failed": 2
},
"score": 92,
"timestamp": "2025-12-26 10:30:00",
"recommendations": [
"2 service(s) enabled but not running. Check service status."
]
}
```
**关键原则**
1. **嵌套对象**用于相关指标cpu、memory、disk 等)
2. **一致的结构**:每个指标都有 `usage`(百分比)和 `status`ok/warning/critical
3. **原始值 + 计算值**:同时提供(例如 `used_kb``usage` 百分比)
4. **状态阈值**ok< warning)、warningwarning-critical)、critical>= critical
5. **总体评分**:仪表板的单一 0-100 健康评分
6. **动态建议**:基于阈值的可操作警报数组
### 状态值
对所有指标使用一致的状态字符串:
- `"ok"` - 正常运行(绿色)
- `"warning"` - 接近阈值(橙色)
- `"critical"` - 超过阈值(红色)
- `"error"` - 无法检索指标
- `"unknown"` - 指标不可用
### 时间戳格式
使用 ISO 8601 或一致的本地格式:
```bash
timestamp="$(date '+%Y-%m-%d %H:%M:%S')" # 2025-12-26 10:30:00
```
### JSON 中的布尔值
在使用 jshn.sh 的 shell 脚本中:
```bash
json_add_boolean "wan_up" 1 # true
json_add_boolean "wan_up" 0 # false
```
在 JavaScript 中:
```javascript
if (health.network.wan_up) {
// WAN 已连接
}
```
### 数组与单值
**对以下情况使用数组**
- 相同类型的多个项目(服务、接口、挂载点)
- 可变长度数据
**对以下情况使用单值**
- 系统范围的指标CPU、内存、磁盘
- 主要/聚合值(总体温度、总运行时间)
**示例 - 存储**
```json
// 多个挂载点 - 使用数组
"storage": [
{
"mount": "/",
"total_kb": 30408704,
"used_kb": 5447680,
"usage": 19
},
{
"mount": "/mnt/usb",
"total_kb": 128000000,
"used_kb": 64000000,
"usage": 50
}
]
// 仅根文件系统 - 使用对象
"disk": {
"total_kb": 30408704,
"used_kb": 5447680,
"usage": 19,
"status": "ok"
}
```
---
## 常见错误和解决方案
### 1. RPC 错误:"Object not found" (-32000)
**错误消息**
```
RPC call to system-hub/status failed with error -32000: Object not found
```
**原因**RPCD 脚本名称与 JavaScript 中的 ubus 对象名称不匹配
**解决方案**
1. 检查 JavaScript 中的对象名称:
```bash
grep -r "object:" luci-app-system-hub/htdocs --include="*.js"
```
输出:`object: 'luci.system-hub'`
2. 重命名 RPCD 脚本以完全匹配:
```bash
mv root/usr/libexec/rpcd/system-hub root/usr/libexec/rpcd/luci.system-hub
```
3. 确保脚本可执行:
```bash
chmod +x root/usr/libexec/rpcd/luci.system-hub
```
4. 重启 RPCD
```bash
/etc/init.d/rpcd restart
```
### 2. JavaScript 错误:"api.methodName is not a function"
**错误消息**
```
Uncaught TypeError: api.getHealth is not a function
at view.load (health.js:12)
```
**原因**:错误的导入模式 - 导入了类构造函数而不是实例
**解决方案**
从:
```javascript
var api = L.require('system-hub.api'); // ❌ 错误
```
改为:
```javascript
'require system-hub/api as API'; // ✅ 正确
```
**为什么**`L.require('module.path')` 返回原始类,`'require module/path as VAR'` 自动实例化。
### 3. RPC 错误:"Access denied" (-32002)
**错误消息**
```
RPC call to luci.system-hub/get_settings failed with error -32002: Access denied
```
**原因**:方法未在 ACL 文件中列出,或在错误的部分(读取与写入)
**解决方案**
1. 打开 ACL 文件:`root/usr/share/rpcd/acl.d/luci-app-system-hub.json`
2. 将方法添加到适当的部分:
```json
"read": {
"ubus": {
"luci.system-hub": [
"get_settings"
]
}
}
```
3. 部署并重启 RPCD
```bash
scp luci-app-system-hub/root/usr/share/rpcd/acl.d/*.json router:/usr/share/rpcd/acl.d/
ssh router "/etc/init.d/rpcd restart"
```
### 4. 显示错误:"NaN%" 或未定义值
**错误**:仪表板显示 "NaN%"、"undefined" 或空值
**原因**:前端使用了错误的数据结构键(后端更改后已过时)
**解决方案**
1. 检查后端输出:
```bash
ubus call luci.system-hub get_health
```
2. 更新前端以匹配结构:
```javascript
// ❌ 旧结构
var cpuPercent = health.load / health.cores * 100;
var memPercent = health.memory.percent;
// ✅ 新结构
var cpuPercent = health.cpu ? health.cpu.usage : 0;
var memPercent = health.memory ? health.memory.usage : 0;
```
3. 添加 null/undefined 检查:
```javascript
var temp = health.temperature?.value || 0;
var loadAvg = health.cpu?.load_1m || '0.00';
```
### 5. HTTP 404找不到视图文件
**错误消息**
```
HTTP error 404 while loading class file '/luci-static/resources/view/netifyd/overview.js'
```
**原因**:菜单路径与实际视图文件位置不匹配
**解决方案**
1. 检查菜单 JSON
```bash
cat root/usr/share/luci/menu.d/luci-app-netifyd-dashboard.json
```
查找:`"path": "netifyd/overview"`
2. 检查实际文件位置:
```bash
ls htdocs/luci-static/resources/view/
```
文件位于:`view/netifyd-dashboard/overview.js`
3. 修复菜单路径或文件位置:
```json
// 选项 1更新菜单路径以匹配文件
"path": "netifyd-dashboard/overview"
// 选项 2移动文件以匹配菜单
mv view/netifyd-dashboard/ view/netifyd/
```
### 6. 构建错误:"factory yields invalid constructor"
**错误消息**
```
/luci-static/resources/system-hub/api.js: factory yields invalid constructor
```
**原因**:在 API 模块中使用了错误的模式singleton、普通对象等
**解决方案**
始终使用 `baseclass.extend()`
```javascript
return baseclass.extend({
getStatus: callStatus,
getHealth: callGetHealth,
// ... 更多方法
});
```
不要使用:
- `baseclass.singleton({...})`
- 普通对象:`return {...}`
- `baseclass.prototype`
### 7. 更改后 RPCD 无响应
**症状**:对 RPCD 脚本的更改不生效
**解决方案**
1. 验证脚本已部署:
```bash
ssh router "ls -la /usr/libexec/rpcd/"
```
2. 检查脚本是否可执行:
```bash
ssh router "chmod +x /usr/libexec/rpcd/luci.system-hub"
```
3. 重启 RPCD
```bash
ssh router "/etc/init.d/rpcd restart"
```
4. 清除浏览器缓存Ctrl+Shift+R
5. 检查 RPCD 日志:
```bash
ssh router "logread | grep rpcd"
```
---
## 验证清单
在部署前使用此清单:
### 文件结构
- [ ] RPCD 脚本存在:`/usr/libexec/rpcd/luci.<模块名称>`
- [ ] RPCD 脚本可执行:`chmod +x`
- [ ] 菜单 JSON 存在:`/usr/share/luci/menu.d/luci-app-<模块>.json`
- [ ] ACL JSON 存在:`/usr/share/rpcd/acl.d/luci-app-<模块>.json`
- [ ] API 模块存在:`htdocs/luci-static/resources/<模块>/api.js`
- [ ] 视图存在:`htdocs/luci-static/resources/view/<模块>/*.js`
### 命名约定
- [ ] RPCD 脚本名称与 JavaScript 中的 ubus 对象匹配(包括 `luci.` 前缀)
- [ ] 菜单路径与视图文件目录结构匹配
- [ ] 所有 ubus 对象以 `luci.` 开头
- [ ] ACL 键与包名称匹配:`"luci-app-<模块>"`
### 代码验证
- [ ] API 模块使用 `baseclass.extend()` 模式
- [ ] 视图使用 `'require <模块>/api as API'` 模式导入 API
- [ ] 所有 rpc.declare() 调用包含正确的 `object`、`method`、`params`、`expect`
- [ ] RPCD 脚本输出有效 JSON使用 `ubus call` 测试)
- [ ] 菜单 JSON 有效(使用 `jsonlint` 测试)
- [ ] ACL JSON 有效(使用 `jsonlint` 测试)
### 权限
- [ ] 所有读方法在 ACL `"read"` 部分
- [ ] 所有写方法在 ACL `"write"` 部分
- [ ] ACL 中的方法与 RPCD 脚本方法名称完全匹配
### 测试
- [ ] 运行验证脚本:`./secubox-tools/validate-modules.sh`
- [ ] 通过 ubus 测试每个方法:`ubus call luci.<模块> <方法>`
- [ ] 在浏览器中测试前端(检查控制台错误)
- [ ] 部署后清除浏览器缓存
- [ ] 验证 RPCD 重启:`/etc/init.d/rpcd restart`
### 自动化验证命令
```bash
# 运行全面验证
./secubox-tools/validate-modules.sh
# 验证特定模块
./secubox-tools/validate-module-generation.sh luci-app-system-hub
# 检查 JSON 语法
find luci-app-system-hub -name "*.json" -exec jsonlint {} \;
# 检查 shell 脚本
shellcheck luci-app-system-hub/root/usr/libexec/rpcd/*
```
---
## 测试和部署
### 使用 ubus 进行本地测试
在部署到路由器之前,本地测试 RPCD 脚本:
```bash
# 将 RPCD 脚本复制到本地 /tmp
cp luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub /tmp/
# 设置可执行权限
chmod +x /tmp/luci.system-hub
# 测试 'list' 操作
/tmp/luci.system-hub list
# 使用方法测试 'call' 操作
/tmp/luci.system-hub call status
# 测试带参数的方法
echo '{"service":"network","action":"restart"}' | /tmp/luci.system-hub call service_action
```
### 部署脚本
使用部署脚本进行快速迭代:
```bash
#!/bin/bash
# deploy-system-hub.sh
ROUTER="root@192.168.8.191"
echo "正在将 system-hub 部署到 $ROUTER"
# 部署 API 模块
scp luci-app-system-hub/htdocs/luci-static/resources/system-hub/api.js \
"$ROUTER:/www/luci-static/resources/system-hub/"
# 部署视图
scp luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/*.js \
"$ROUTER:/www/luci-static/resources/view/system-hub/"
# 部署 RPCD 后端
scp luci-app-system-hub/root/usr/libexec/rpcd/luci.system-hub \
"$ROUTER:/usr/libexec/rpcd/"
# 部署 ACL
scp luci-app-system-hub/root/usr/share/rpcd/acl.d/luci-app-system-hub.json \
"$ROUTER:/usr/share/rpcd/acl.d/"
# 设置权限并重启
ssh "$ROUTER" "chmod +x /usr/libexec/rpcd/luci.system-hub && /etc/init.d/rpcd restart"
echo "部署完成请清除浏览器缓存Ctrl+Shift+R"
```
### 浏览器测试
1. 打开浏览器控制台F12
2. 导航到模块页面
3. 检查错误:
- RPC 错误object not found、method not found、access denied
- JavaScript 错误api.method is not a function
- 404 错误(找不到视图文件)
4. 测试功能:
- 数据加载正确显示
- 操作有效(启动/停止服务、保存设置)
- 没有 "NaN"、"undefined" 或空值
### 远程 ubus 测试
在路由器上测试 RPCD 方法:
```bash
# 列出所有方法
ssh router "ubus list luci.system-hub"
# 调用无参数方法
ssh router "ubus call luci.system-hub status"
# 调用带参数方法
ssh router "ubus call luci.system-hub service_action '{\"service\":\"network\",\"action\":\"restart\"}'"
# 格式化 JSON 输出
ssh router "ubus call luci.system-hub get_health | jsonlint"
```
### 调试技巧
**启用 RPCD 调试日志**
```bash
# 编辑 /etc/init.d/rpcd
# 在 procd_set_param command 中添加 -v 标志
procd_set_param command "$PROG" -v
# 重启 RPCD
/etc/init.d/rpcd restart
# 监视日志
logread -f | grep rpcd
```
**启用 JavaScript 控制台日志**
```javascript
// 添加到 api.js
console.log('API v0.1.0 已加载于', new Date().toISOString());
// 添加到视图
console.log('正在加载健康数据...');
API.getHealth().then(function(data) {
console.log('健康数据:', data);
});
```
**测试 JSON 输出**
```bash
# 在路由器上
/usr/libexec/rpcd/luci.system-hub call get_health | jsonlint
# 检查常见错误
# - 缺少逗号
# - 尾随逗号
# - 未加引号的键
# - 无效的转义序列
```
---
## 最佳实践总结
### 应该做的:
- 对所有 ubus 对象使用 `luci.` 前缀
- RPCD 脚本命名与 ubus 对象完全匹配
- 对 API 模块使用 `baseclass.extend()`
- 使用 `'require module/api as API'` 模式导入 API
- 在前端添加 null/undefined 检查:`health.cpu?.usage || 0`
- 部署前使用 `jsonlint` 验证 JSON
- 浏览器测试前先使用 `ubus call` 测试
- 后端更改后重启 RPCD
- 前端更改后清除浏览器缓存
- 提交前运行 `./secubox-tools/validate-modules.sh`
### 不应该做的:
- 使用没有 `luci.` 前缀的 ubus 对象名称
- 对 API 模块使用 `baseclass.singleton()` 或普通对象
- 使用 `L.require('module.path')` 导入 API返回类而不是实例
- 忘记将方法添加到 ACL 文件
- 在 ACL 部分混淆读/写方法
- 从 RPCD 脚本输出非 JSON
- 后端和前端之间使用不一致的数据结构
- 不先本地测试就部署
- 假设数据存在 - 始终检查 null/undefined
- 忘记使 RPCD 脚本可执行(`chmod +x`
---
## 版本历史
**v1.0**2025-12-26
- 初始参考指南
- 基于 luci-app-secubox v1.0.0 和 luci-app-system-hub v0.1.0
- 记录了所有关键模式和常见错误
- 针对实际实现挑战进行了验证
---
## 参考资料
- **OpenWrt 文档**https://openwrt.org/docs/guide-developer/start
- **LuCI 文档**https://github.com/openwrt/luci/wiki
- **ubus 文档**https://openwrt.org/docs/techref/ubus
- **UCI 文档**https://openwrt.org/docs/guide-user/base-system/uci
- **jshn.sh 库**OpenWrt 上的 `/usr/share/libubox/jshn.sh`
---
## 联系方式
如有问题或想为本参考指南做贡献:
- **作者**CyberMind <contact@cybermind.fr>
- **项目**SecuBox OpenWrt
- **仓库**https://github.com/cybermind-fr/secubox-openwrt
---
**参考指南结束**