从源码分析openwrt Luci 构成
一. luci 简介
LuCI是OpenWrt上的Web管理界面,LuCI采用了MVC三层架构。
MVC(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
- (控制器Controller)- 负责转发请求,对请求进行处理。
- (视图View) - 界面设计人员进行图形界面设计。
- (模型Model) - 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
二. 源码分析
1. LUCI 在文件系统中的主要目录结构
/www/index.html
#引导页面
/cgi-bin/luci
#脚本跳转
/luci-static/*/*.css、*.js、*.gif
#静态页面css js gif 等目录
/usr/lib/lua/nixio.so、uci.so
#库文件,调用接口用
/usr/lib/lua/luci/
#mvc 层需要的脚本及库文件存放目录
/usr/lib/lua/luci/controller/*.lua
# lua 文件里,定义了菜单的显示和html 以及业务处理路径,每个文件对应一个菜单项。
# ./admin/index.lua 这个文件定义了node ,最外面的节点,最上层菜单的显示。
/usr/lib/lua/luci/model/*.lua
#对应controller 里面调用的脚本文件,下文会解析, 这里不再赘述
/usr/lib/lua/luci/view/*.lua
#对应controller 里面调用的静态页面文件
2. 从源码分析如何创建一个模块
从MVC结构可见, LUCI工作是从CONTROLLER中的脚本开始运行, 我从luci的目录结构中选用常用的系统页面来举例说明。
2.1 菜单栏入口创建
如下
module("luci.controller.admin.system", package.seeall)
function index()
local fs = require "nixio.fs"
entry({"admin", "system"}, alias("admin", "system", "system"), _("System"), 30).index = true
entry({"admin", "system", "system"}, cbi("admin_system/system"), _("System"), 1)
entry({"admin", "system", "clock_status"}, post_on({ set = true }, "action_clock_status"))
entry({"admin", "system", "admin"}, cbi("admin_system/admin"), _("Administration"), 2)
.......
end
entry({"admin", "system"}, alias("admin", "system", "system"), _("System"), 30).index = true
-- 定义system的最外层的菜单栏 -- alias是指向别的entry的别名
entry({"admin", "system", "system"}, cbi("admin_system/system"), _("System"), 1)
在luci里面entry的全定义entry(path, target, title=nil, order=nil)
path:对应的就是菜单路径 {“admin”, “system”}或者{“admin”, “system”, “system”}
target:调用目标分为三种,分别是执行指定方法Action、访问指定页面Views以及调用CBI Module
- 第一种可以直接调用指定的函数,直接写为call(“function_name”),然后在lua文件下编写名为function_name的函数就可以调用了。
- 第二种可以访问指定的页面,template(“admin/system”)就可以调用/usr/lib/lua/luci/view/admin/system.htm文件。
- 第三种为调用lua 脚本cbi(“admin/system”)就可以调用/usr/lib/lua/luci/model/cbi/admin/system.lua文件。
title:菜单栏显示名,_(“string”), 多语时调用这个来翻译对应语言
order: 同级菜单下,此菜单项的位置,从小到大,表现为从左到右,从上到下
2.2 菜单栏入口创建
2.1 当中提到过代码创建管理权菜单中,调用了cbi(“admin_system/admin”)
-- 菜单调用项
entry({"admin", "system", "admin"}, cbi("admin_system/admin"), _("Administration"), 2)
-- /usr/lib/lua/luci/model/cbi 目录下有 admin_system/admin.lua
对应实际页面中
CBI模型是Lua文件描述UCI配置文件的结构和由此产生的HTML表单来评估CBI解析器,所有CBI luci.cbi.Map类型的模型文件必须返回一个map对象,在cbi模块中定义各种控件,Luci系统会自动执行大部分处理工作。
m = Map("system", translate("Router Password"),translate("Changes the administrator password for accessing the device"))
-- 第一个参数,代表cbi 基于配置文件system建立Map, 这里的system 实际路径 /etc/config/system
-- 第二个参数, 映射页面的标题,第三个参数, 功能简介
-- 一个配置文件可以map多个文件页面
根据配置文件生成生成对应的section
s = m:section(TypedSection, "_dummy", "")
--表示获取所有类型为空的section并生成html
-- _dummy 这里差不多和空值等价,因为密码不需要填充值
s.addremove = false
-- 设定不允许增加或删除Section
s.anonymous = true
-- 设定不显示Section的名称
pw1 = s:option(Value, "pw1", translate("Password"))
-- 生成输入框
pw1.password = true
-- 设置输入框属性
pw2 = s:option(Value, "pw2", translate("Confirmation"))
pw2.password = true
section 函数根据配置
TypedSection表示根据类型获取section
NamedSection表示根据名字获取section
生成如下, 所示的页面
function s.cfgsections()
return { "_pass" }
end
-- 上面显示section名函数
function m.parse(map)
local v1 = pw1:formvalue("_pass")
local v2 = pw2:formvalue("_pass")
if v1 and v2 and #v1 > 0 and #v2 > 0 then
if v1 == v2 then
if luci.sys.user.setpasswd(luci.dispatcher.context.authuser, v1) == 0 then
--设置密码接口luci.sys.user.setpasswd, 查询接口见下面。
m.message = translate("Password successfully changed!")
else
m.message = translate("Unknown Error, password not changed!")
end
else
m.message = translate("Given password confirmation did not match, password not changed!")
end
end
Map.parse(map)
end
-- 判断设置密码函数
参考文献
[1] WIKI
[2] 部分前辈的博客