Bladeren bron

主要修改开发模块

a158 8 maanden geleden
bovenliggende
commit
39546d14ac

+ 101 - 0
target/aliyun/oss.lua

@@ -0,0 +1,101 @@
+local aliyun_oss = require("aliyunoss")
+local fw = require("fastweb")
+local utils = require("utils")
+local M = {}
+-- 文件扩展名与目录的映射
+M.ext_dirpath = {
+    images = {
+        "jpg",
+        "jpeg",
+        "png",
+        "gif",
+        "bmp",
+        "webp",
+        "svg",
+        "ico",
+        "tif",
+        "tiff",
+    },
+    videos = {
+        "mp4",
+        "avi",
+        "mov",
+        "wmv",
+        "flv"
+    },
+    audio = {
+        "mp3",
+        "wav",
+        "ogg",
+        "aac",
+        "m4a",
+        "wma"
+    },
+    documents = {
+        "pdf",
+        "doc",
+        "docx",
+        "xls",
+        "xlsx",
+        "ppt",
+        "pptx"
+    }
+}
+
+
+function M.upload_file(config,remote_dir,local_filepath,auto_remove)
+
+    if not utils.exists_file(local_filepath) then
+        return false,"文件不存在,local_filepath: "..local_filepath
+    end
+    local ext = utils.ext(local_filepath)
+    local filename = fw.make_software_guid().."."..ext
+
+    local dirpath = ""
+    
+    for dir, ext_list in pairs(M.ext_dirpath) do
+        for _, ext_type_ext in ipairs(ext_list) do
+            if ext_type_ext == ext then
+                dirpath = dir
+                break
+            end
+        end
+        if dirpath ~= "" then
+            break
+        end
+    end
+    if dirpath == "" then
+        return false,"不支持的文件格式,ext: "..ext
+    end
+
+    if remote_dir == nil or remote_dir == "" then
+        remote_dir = ""
+    else
+        remote_dir = remote_dir.."/"
+    end
+
+    local cos_filepath = remote_dir .. dirpath.."/"..filename
+    local oss = aliyun_oss.new()
+
+
+    local result = oss:upfile(
+        config.endpoint,
+        config.access_key_id,
+        config.access_key_secret,
+        config.bucket_name,
+        cos_filepath,
+        local_filepath,
+        ""
+    )
+    if result == "" then
+        if auto_remove then
+            utils.delete_file(local_filepath)
+        end
+        return true,cos_filepath
+    end
+    return false,result
+end
+
+
+
+return M

+ 0 - 117
target/fwutils/acl.lua

@@ -1,117 +0,0 @@
-local utils = require("utils")
-local fw = require("fastweb")
-local config = require("fwutils.config")
-local M = {}
-
-
--- 更新
-M.update = function(role_id,conn)
-    -- 查询权限表
-    local select = conn:select()
-    select:table("fw_role_permissions")
-    select:where_expression("AND delete_time IS NULL")
-    if role_id ~= nil then
-        select:where_i32("role_id", "=", role_id)
-    end
-    local result = select:query()
-
-
-    local bc = {}
-
-    while result:next() do
-        local id = result:get("id")
-        local path = result:get("path")
-        local role_id = tostring(result:get("role_id"))
-        local action = result:get("action")
-        local desc = result:get("desc")
-        local create_time = result:get("create_time")
-        local update_time = result:get("update_time")
-        local delete_time = result:get("delete_time")
-        -- local public = result:get("public")
-
-        if bc[role_id] == nil then
-            bc[role_id] = {}
-        end
-        if bc[role_id]["public"] == nil then
-            bc[role_id]["public"] = {}
-        end
-        if bc[role_id]["private"] == nil then
-            bc[role_id]["private"] = {}
-        end
-        -- 处理 action 字段,将其切分为表或空表
-        local actions_tbl = {}
-        if action and action ~= "" then
-            for act in string.gmatch(action, "([^,]+)") do
-                table.insert(actions_tbl, act)
-            end
-        end
-
-        local item = {
-            create_time = create_time,
-            update_time = update_time,
-            delete_time = delete_time,
-            action = actions_tbl,
-            desc = desc,
-        }
-        -- if public == 1 then
-        --     bc[role_id]["public"][path] = item
-        -- else
-        --     bc[role_id]["private"][path] = item
-        -- end
-        bc[role_id][path] = item
-    end
-    local code = "return " .. require("serpent").serialize(bc, {comment = false})
-    utils.save_file(fw.website_dir().."/"..(config.path.luabytecode:gsub("%.", "/")).."/acl_bc.lua",code)
-    return true
-end
--- 匹配
-M.match = function(cfg)
-
-    local function match_path(path, patterns)
-
-        -- print("[match_path] path:",path)
-        for pattern, v in pairs(patterns) do
-           
-            -- 如果是正则(以^开头),用string.match,否则精确匹配
-            if string.sub(pattern, 1, 1) == "^" then
-                
-                if string.match(path, pattern) then
-                    -- print("[TRUE] pattern:",pattern,",path:",path)
-                    return true, v 
-                -- else
-                    -- print("[FALSE] pattern:",pattern,",path:",path)
-                end
-            else
-                if path == pattern then
-                    return true, v
-                end
-            end
-        end
-        return false, nil
-    end
-    -- 检查action
-    local function check_action(actions,action)
-        if actions == nil or #actions == 0 then 
-            return true
-        end
-    
-        for _,v in pairs(actions) do
-            if v == action then
-                return true
-            end
-        end
-        return false, "action not match"
-    end
-    local role_id_str = string.format("%d",cfg.role_id())
-    local acl_bc = require(config.path.luabytecode..".acl_bc")
-    if acl_bc[role_id_str] == nil then
-        return false,"role id("..role_id_str..") acl not found"
-    end
-    local result, item = match_path(cfg.filepath(), acl_bc[role_id_str])
-    if result then
-        return check_action(item.action,cfg.action())
-    end 
-    return false,"path("..cfg.filepath()..") acl not found"
-end
-
-return M

+ 5 - 2
target/fwutils/config.lua

@@ -1,9 +1,12 @@
 return {
     -- 服务器ID,如果REDIS或MYSQL与其它服务公用必须更改此参数,要求唯一
-    server_id = "TEACHER_1234567890",
+    server_id = "TEACHER_1",
     
     path = {
-        luabytecode = "cache.fw.luabytecode"
+        -- 字节码路径
+        luabytecode = "cache.fw.luabytecode",
+        -- 开发接口路径
+        develop_api = "/app/controll/develop/"
     },
     token = {
         -- 超时时间

+ 56 - 0
target/fwutils/develop/controll/menu.lua

@@ -0,0 +1,56 @@
+local fwutils_config = require("fwutils.config")
+local develop_menu = require("fwutils.develop.function.menu")
+
+local function get_by_id()
+
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    succ(develop_menu.get_by_id(pint("id"),conn))
+end
+
+local function add()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    if develop_menu.add(data,conn) then
+        succ({})
+    else
+        error("")
+    end
+end
+local function update()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    if develop_menu.update(data,conn) then
+        succ({}) 
+    else
+        error("")
+    end
+end
+local function delete()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    if develop_menu.delete(data.id,conn) then
+        succ({})
+    else
+        error("")
+    end
+end
+local function list()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    local d = develop_menu.list(data.search,data.limit,conn)
+    succ(d)
+end
+local function refresh()
+    local module_menu = require("fwutils.develop.function.menu")
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    module_menu.make_bytecode(nil,conn)
+    succ({})
+end
+exec({
+    get_by_id = get_by_id,
+    list = list,
+    add = add,
+    update = update,
+    delete = delete,
+    refresh = refresh,
+})

+ 13 - 0
target/fwutils/develop/controll/role.lua

@@ -0,0 +1,13 @@
+require("app.app")
+
+local fwutils_config = require("fwutils.config")
+local develop_role = require("fwutils.develop.function.role")
+local function list()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    local d = develop_role.list(data.search,data.limit,conn)
+    succ(d)
+end
+exec({
+    list = list,
+})

+ 57 - 0
target/fwutils/develop/controll/role_permissions.lua

@@ -0,0 +1,57 @@
+require("app.app")
+
+local fwutils_config = require("fwutils.config")
+local develop_role_permissions = require("fwutils.develop.function.role_permissions")
+
+local function get_by_id()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    succ(develop_role_permissions.get_by_id(pint("id"),conn))
+end
+
+local function add()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    if develop_role_permissions.add(data,conn) then
+        succ({})
+    else
+        error("")
+    end
+end
+local function update()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    if develop_role_permissions.update(data,conn) then
+        succ({}) 
+    else
+        error("")
+    end
+end
+local function delete()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    if develop_role_permissions.delete(data.id,conn) then
+        succ({})
+    else
+        error("")
+    end
+end
+local function list()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    local d = develop_role_permissions.list(data.search,data.limit,conn)
+    succ(d)
+end
+local function refresh()
+    local module_acl = require("fwutils.develop.function.role_permissions")
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    module_acl.make_bytecode(nil,conn)
+    succ({})
+end
+exec({
+    get_by_id = get_by_id,
+    list = list,
+    add = add,
+    update = update,
+    delete = delete,
+    refresh = refresh,
+})

+ 57 - 0
target/fwutils/develop/controll/template.lua

@@ -0,0 +1,57 @@
+require("app.app")
+
+local fwutils_config = require("fwutils.config")
+local develop_template = require("fwutils.develop.function.template")
+
+local function get_by_id()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    succ(develop_template.get_by_id(pint("id"),conn))
+end
+
+local function add()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    if develop_template.add(data,conn) then
+        succ({})
+    else
+        error("")
+    end
+end
+local function update()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    if develop_template.update(data,conn) then
+        succ({}) 
+    else
+        error("")
+    end
+end
+local function delete()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    if develop_template.delete(data.id,conn) then
+        succ({})
+    else
+        error("")
+    end
+end
+local function list()
+    local data = pjson()
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    local d = develop_template.list(data.search,data.limit,conn)
+    succ(d)
+end
+local function refresh()
+    local module_template = require("fwutils.template_engine")
+    local conn = mysql.new(_G[fwutils_config.db.mysql_pool_name]):get()
+    module_template.update(conn)
+    succ()
+end
+exec({
+    get_by_id = get_by_id,
+    list = list,
+    add = add,
+    update = update,
+    delete = delete,
+    refresh = refresh,
+})

+ 214 - 0
target/fwutils/develop/function/menu.lua

@@ -0,0 +1,214 @@
+require("fwutils.webapi")
+-- CREATE TABLE `fw_menu` (
+--   `id` int NOT NULL AUTO_INCREMENT,
+--   `role_id` int DEFAULT NULL,
+--   `title` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+--   `path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+--   `icon` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+--   `sort` int DEFAULT NULL,
+--   `parent_id` int DEFAULT NULL,
+--   `create_time` datetime DEFAULT NULL,
+--   `update_time` datetime DEFAULT NULL,
+--   `delete_time` datetime DEFAULT NULL,
+--   PRIMARY KEY (`id`)
+-- ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+local M = {}
+
+function M.get_by_id(id,conn)
+    local select = conn:select()
+    select:table("fw_menu LEFT JOIN fw_role ON fw_menu.role_id = fw_role.id")
+    select:field({
+        "fw_menu.id",
+        "fw_menu.title",
+        "fw_menu.path",
+        "fw_menu.icon",
+        "fw_menu.sort",
+        "fw_menu.parent_id",
+        "fw_menu.create_time",
+        "fw_menu.update_time",
+        "fw_menu.delete_time",
+        "fw_role.title as role_title",
+        "fw_role.id as role_id",
+    })
+    select:where_i32("fw_menu.id","=",id)
+    select:where_expression("AND fw_menu.delete_time IS NULL")
+    select:limit(0,1)
+    local result = select:query()
+    if result:row_count() == 0 then
+        return nil
+    end
+    local d = result:table()[1]
+    return d
+end
+function M.add(data,conn)
+    local insert = conn:insert()
+    insert:table("fw_menu")
+    insert:set_str("title",data.title)
+    insert:set_str("path",data.path)
+    insert:set_i32("role_id",data.role_id)
+    if data.icon ~= nil then
+        insert:set_str("icon",data.icon)
+    end
+    if data.sort ~= nil then
+        insert:set_i32("sort",data.sort)
+    end
+    if data.parent_id ~= nil and data.parent_id ~= 0 then
+        insert:set_i32("parent_id",data.parent_id)
+    end
+    insert:set_not_ppst("create_time","NOW()")
+    local d = insert:exec()
+    return d == 1
+end
+function M.update(data,conn)
+    local update = conn:update()
+    update:table("fw_menu")
+    if data.title ~= nil then
+        update:set_str("title",data.title)
+    end
+    if data.path ~= nil then
+        update:set_str("path",data.path)
+    end
+    if data.role_id ~= nil then
+        update:set_i32("role_id",data.role_id)
+    end
+    if data.icon ~= nil then
+        update:set_str("icon",data.icon)
+    end
+    if data.sort ~= nil then
+        update:set_i32("sort",data.sort)
+    end
+    if data.parent_id ~= nil then
+        update:set_i32("parent_id",data.parent_id)
+    end
+    update:set("update_time=NOW()")
+    update:where_i32("id","=",data.id)
+    local d = update:exec()
+    return d == 1
+end
+function M.delete(id,conn)
+    local update = conn:update()
+    update:table("fw_menu")
+    update:set("delete_time=NOW()")
+    update:where_i32("id","=",id)
+    local d = update:exec()
+    return d == 1
+end
+function M.list(search,limit,conn)
+    if search.role_id == -1 then
+        return {
+            count = 0,
+            data = {}
+        }
+    end
+    return query_model_ex(conn,[=[
+        fw_menu LEFT JOIN fw_role ON fw_menu.role_id = fw_role.id
+    ]=],{
+        "fw_menu.id",
+        "fw_menu.title",
+        "fw_menu.path",
+        "fw_menu.icon",
+        "fw_menu.sort",
+        "fw_menu.parent_id",
+        "fw_menu.create_time",
+        "fw_menu.update_time",
+        "fw_menu.delete_time",
+        "fw_role.title as role_title",
+        "fw_role.id as role_id",
+    },limit.start,limit.length,function(sel)
+
+        if search.role_id ~= nil and search.role_id ~= 0 then
+            sel:where_i32("fw_menu.role_id","=",search.role_id)
+        end
+        sel:where_expression("AND fw_menu.delete_time IS NULL")
+    end,function(sel_data)
+        sel_data:orderby("fw_menu.sort DESC")
+    end)
+end
+M.make_bytecode = function(role_id,conn)
+    -- 查询菜单表
+    local select = conn:select()
+    select:table("fw_menu")
+    select:where_expression("AND delete_time IS NULL")
+    if role_id ~= nil then
+        select:where_i32("role_id", "=", role_id)
+    end
+    local result = select:query()
+
+    local bc = {}
+    local items_by_id = {}  -- 通过id快速查找菜单项:{id = {role_id, title, item, parent_id}}
+    local items_with_parent = {}  -- 存储有父级的菜单项
+
+    -- 第一遍:读取所有菜单项并存储
+    while result:next() do
+        local id = result:get("id")
+        local role_id = tostring(result:get("role_id"))
+        local title = result:get("title")
+        local path = result:get("path")
+        local icon = result:get("icon")
+        local sort = result:get("sort")
+        local parent_id = result:get("parent_id")
+
+        if not title or title == "" then
+            goto continue
+        end
+
+        if bc[role_id] == nil then
+            bc[role_id] = {}
+        end
+
+        local item = {
+            path = path,
+            icon = icon,
+            sort = sort,
+        }
+
+        -- 存储所有菜单项信息
+        items_by_id[id] = {
+            role_id = role_id,
+            title = title,
+            item = item,
+            parent_id = parent_id
+        }
+
+        -- 如果parent_id为空,则作为顶层菜单项
+        if parent_id == nil or parent_id == 0 then
+            bc[role_id][title] = item
+        else
+            -- 有父级,记录下来稍后处理
+            table.insert(items_with_parent, {
+                id = id,
+                role_id = role_id,
+                title = title,
+                item = item,
+                parent_id = parent_id
+            })
+        end
+
+        ::continue::
+    end
+
+    -- 第二遍:处理有父级的菜单项,构建children结构
+    for _, menu_item in ipairs(items_with_parent) do
+        local parent_info = items_by_id[menu_item.parent_id]
+        if parent_info then
+            local parent_item = parent_info.item
+
+            -- 如果父项还没有children表,创建它
+            if not parent_item.children then
+                parent_item.children = {}
+            end
+
+            -- 将子项添加到父项的children中
+            parent_item.children[menu_item.title] = menu_item.item
+        end
+    end
+
+    local code = "return " .. require("serpent").serialize(bc, {comment = false})
+    utils.save_file(fw.website_dir().."/"..(fwutils_config.path.luabytecode:gsub("%.", "/")).."/menu_bc.lua",code)
+    return true
+end
+M.bytecode = function(role_id)
+    local menu_bc = require(fwutils_config.path.luabytecode..".menu_bc")
+    return menu_bc[string.format("%d",role_id)]
+end
+return M

+ 22 - 0
target/fwutils/develop/function/role.lua

@@ -0,0 +1,22 @@
+require("fwutils.webapi")
+-- CREATE TABLE `fw_role` (
+--   `id` int NOT NULL AUTO_INCREMENT,
+--   `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
+--   `sort` int DEFAULT NULL COMMENT '排序',
+--   `desc` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
+--   `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+--   `default_filepath` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '默认页面',
+--   PRIMARY KEY (`id`)
+-- ) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+local M = {}
+
+
+
+function M.list(search,limit,conn)
+    return query_model_ex(conn,"fw_role",{},limit.start,limit.length,function(sel)
+    end,function(sel_data)
+        sel_data:orderby("`sort` DESC")
+    end)
+end
+
+return M

+ 224 - 0
target/fwutils/develop/function/role_permissions.lua

@@ -0,0 +1,224 @@
+require("fwutils.webapi")
+-- CREATE TABLE `fw_role_permissions` (
+--   `id` int NOT NULL AUTO_INCREMENT,
+--   `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '路径',
+--   `role_id` int DEFAULT NULL COMMENT '角色',
+--   `action` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '动作',
+--   `desc` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
+--   `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+--   `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+--   `delete_time` datetime DEFAULT NULL COMMENT '删除时间',
+--   PRIMARY KEY (`id`),
+--   UNIQUE KEY `path` (`path`,`role_id`)
+-- ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Fastweb-角色访问权限';
+local M = {}
+
+M.get_by_id = function(id,conn)
+    local select = conn:select()
+    select:table("fw_role_permissions LEFT JOIN fw_role ON fw_role_permissions.role_id = fw_role.id")
+    select:field({
+        "fw_role_permissions.id",
+        "fw_role_permissions.path",
+        "fw_role_permissions.role_id",
+        "fw_role_permissions.action",
+        "fw_role_permissions.`desc`",
+        "fw_role_permissions.create_time",
+        "fw_role_permissions.update_time",
+        "fw_role_permissions.delete_time",
+        "fw_role.title as role_title",
+        "fw_role.id as role_id",
+    })
+    select:where_i32("fw_role_permissions.id","=",id)
+    select:where_expression("AND fw_role_permissions.delete_time IS NULL")
+    select:limit(0,1)
+    local result = select:query()
+    if result:row_count() == 0 then
+        return nil
+    end
+    local d = result:table()[1]
+    return d
+end
+M.add = function(data,conn)
+    local insert = conn:insert()
+    insert:table("fw_role_permissions")
+    insert:set_str("path",data.path)
+    insert:set_i32("role_id",data.role_id)
+    if data.action ~= nil then
+        insert:set_str("action",data.action)
+    end
+    if data.desc ~= nil then
+        insert:set_str("`desc`",data.desc)
+    end
+    insert:set_not_ppst("create_time","NOW()")
+    local d = insert:exec()
+    return d == 1
+end
+M.update = function(data,conn)
+    local update = conn:update()
+    update:table("fw_role_permissions")
+    if data.path ~= nil then
+        update:set_str("path",data.path)
+    end
+    if data.action ~= nil then
+        update:set_str("action",data.action)
+    end
+    if data.desc ~= nil then
+        update:set_str("`desc`",data.desc)
+    end
+    if data.role_id ~= nil then
+        update:set_i32("role_id",data.role_id)
+    end
+    update:set("update_time=NOW()")
+    update:where_i32("id","=",data.id)
+    local d = update:exec()
+    return d == 1
+end
+M.delete = function(id,conn)
+    local update = conn:update()
+    update:table("fw_role_permissions")
+    update:set("delete_time=NOW()")
+    update:where_i32("id","=",id)
+    local d = update:exec()
+    return d == 1
+end
+M.list = function(search,limit,conn)
+    if search.role_id == -1 then
+        return {
+            count = 0,
+            data = {}
+        }
+    end
+    return query_model_ex(conn,[=[
+        fw_role_permissions LEFT JOIN fw_role ON fw_role_permissions.role_id = fw_role.id
+    ]=],{
+        "fw_role_permissions.id",
+        "fw_role_permissions.path",
+        "fw_role_permissions.role_id",
+        "fw_role_permissions.action",
+        "fw_role_permissions.`desc`",
+        "fw_role_permissions.create_time",
+        "fw_role_permissions.update_time",
+        "fw_role_permissions.delete_time",
+        "fw_role.title as role_title",
+        "fw_role.id as role_id",
+    },limit.start,limit.length,function(sel)
+
+        if search.role_id ~= nil and search.role_id ~= 0 then
+            sel:where_i32("fw_role_permissions.role_id","=",search.role_id)
+        end
+        sel:where_expression("AND fw_role_permissions.delete_time IS NULL")
+    end,function(sel_data)
+        sel_data:orderby("fw_role_permissions.create_time DESC")
+    end)
+end
+-- 更新
+M.make_bytecode = function(role_id,conn)
+    -- 查询权限表
+    local select = conn:select()
+    select:table("fw_role_permissions")
+    select:where_expression("AND delete_time IS NULL")
+    if role_id ~= nil then
+        select:where_i32("role_id", "=", role_id)
+    end
+    local result = select:query()
+
+
+    local bc = {}
+
+    while result:next() do
+        local id = result:get("id")
+        local path = result:get("path")
+        local role_id = tostring(result:get("role_id"))
+        local action = result:get("action")
+        local desc = result:get("desc")
+        local create_time = result:get("create_time")
+        local update_time = result:get("update_time")
+        local delete_time = result:get("delete_time")
+        -- local public = result:get("public")
+
+        if bc[role_id] == nil then
+            bc[role_id] = {}
+        end
+        if bc[role_id]["public"] == nil then
+            bc[role_id]["public"] = {}
+        end
+        if bc[role_id]["private"] == nil then
+            bc[role_id]["private"] = {}
+        end
+        -- 处理 action 字段,将其切分为表或空表
+        local actions_tbl = {}
+        if action and action ~= "" then
+            for act in string.gmatch(action, "([^,]+)") do
+                table.insert(actions_tbl, act)
+            end
+        end
+
+        local item = {
+            create_time = create_time,
+            update_time = update_time,
+            delete_time = delete_time,
+            action = actions_tbl,
+            desc = desc,
+        }
+        -- if public == 1 then
+        --     bc[role_id]["public"][path] = item
+        -- else
+        --     bc[role_id]["private"][path] = item
+        -- end
+        bc[role_id][path] = item
+    end
+    local code = "return " .. require("serpent").serialize(bc, {comment = false})
+    utils.save_file(fw.website_dir().."/"..(fwutils_config.path.luabytecode:gsub("%.", "/")).."/acl_bc.lua",code)
+    return true
+end
+-- 匹配
+M.match = function(cfg)
+
+    local function match_path(path, patterns)
+
+        -- print("[match_path] path:",path)
+        for pattern, v in pairs(patterns) do
+           
+            -- 如果是正则(以^开头),用string.match,否则精确匹配
+            if string.sub(pattern, 1, 1) == "^" then
+                
+                if string.match(path, pattern) then
+                    -- print("[TRUE] pattern:",pattern,",path:",path)
+                    return true, v 
+                -- else
+                    -- print("[FALSE] pattern:",pattern,",path:",path)
+                end
+            else
+                if path == pattern then
+                    return true, v
+                end
+            end
+        end
+        return false, nil
+    end
+    -- 检查action
+    local function check_action(actions,action)
+        if actions == nil or #actions == 0 then 
+            return true
+        end
+    
+        for _,v in pairs(actions) do
+            if v == action then
+                return true
+            end
+        end
+        return false, "action not match"
+    end
+    local role_id_str = string.format("%d",cfg.role_id())
+    local acl_bc = require(fwutils_config.path.luabytecode..".acl_bc")
+    if acl_bc[role_id_str] == nil then
+        return false,"role id("..role_id_str..") acl not found"
+    end
+    local result, item = match_path(cfg.filepath(), acl_bc[role_id_str])
+    if result then
+        return check_action(item.action,cfg.action())
+    end 
+    return false,"path("..cfg.filepath()..") acl not found"
+end
+
+return M

+ 102 - 0
target/fwutils/develop/function/template.lua

@@ -0,0 +1,102 @@
+require("fwutils.webapi")
+-- CREATE TABLE `fw_template` (
+--   `id` int NOT NULL AUTO_INCREMENT,
+--   `role_id` int DEFAULT NULL,
+--   `key` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+--   `value` varchar(2048) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+--   `enable` tinyint NOT NULL COMMENT '是否启用',
+--   PRIMARY KEY (`id`),
+--   UNIQUE KEY `role_id` (`role_id`,`key`)
+-- ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Fastweb-关键词模板';
+local M = {}
+
+function M.get_by_id(id,conn)
+    local select = conn:select()
+    select:table("fw_template LEFT JOIN fw_role ON fw_template.role_id = fw_role.id")
+    select:field({
+        "fw_template.id",
+        "fw_template.role_id",
+        "fw_template.`key`",
+        "fw_template.value",
+        "fw_template.enable",
+        "fw_role.title as role_title",
+    })
+    select:where_i32("fw_template.id","=",id)
+
+    select:limit(0,1)
+    local result = select:query()
+    if result:row_count() == 0 then
+        return nil
+    end
+    local d = result:table()[1]
+    return d
+end
+function M.add(data,conn)
+    local insert = conn:insert()
+    insert:table("fw_template")
+
+    if data.role_id ~= nil and data.role_id ~= cjson.null then
+        insert:set_i32("role_id",data.role_id)
+    end
+    if data.key ~= nil then
+        insert:set_str("`key`",data.key)
+    end
+    if data.value ~= nil then
+        insert:set_str("value",data.value)
+    end
+    if data.enable ~= nil then
+        insert:set_i32("enable",data.enable)
+    end
+    local d = insert:exec()
+    return d == 1
+end
+function M.update(data,conn)
+    local update = conn:update()
+    update:table("fw_template")
+
+    if data.role_id ~= nil then
+        update:set_i32("role_id",data.role_id)
+    end
+    if data.key ~= nil then
+        update:set_str("`key`",data.key)
+    end
+    if data.value ~= nil then
+        update:set_str("value",data.value)
+    end
+    if data.enable ~= nil then
+        update:set_i32("enable",data.enable)
+    end
+    update:where_i32("id","=",data.id)
+    local d = update:exec()
+    return d == 1
+end
+function M.delete(id,conn)
+    local del = conn:delete()
+    del:table("fw_template")
+    del:where_i32("id","=",id)
+    local d = del:exec()
+    return d == 1
+end
+function M.list(search,limit,conn)
+    
+    return query_model_ex(conn,[=[
+        fw_template LEFT JOIN fw_role ON fw_template.role_id = fw_role.id
+    ]=],{
+        "fw_template.id",
+        "fw_template.role_id",
+        "fw_template.`key`",
+        "fw_template.value",
+        "fw_template.enable",
+        "fw_role.title as role_title",
+        "fw_role.id as role_id",
+    },limit.start,limit.length,function(sel)
+
+        if search.role_id ~= nil and search.role_id ~= -1 then
+            sel:where_i32("fw_template.role_id","=",search.role_id)
+        end
+    end,function(sel_data)
+        sel_data:orderby("fw_template.enable DESC")
+    end)
+end
+
+return M

+ 154 - 0
target/fwutils/funs.lua

@@ -0,0 +1,154 @@
+local M = {}
+
+
+M.file_get_contents = function(filepath)
+    
+    local file, errmsg = io.open(fw.website_dir()..filepath, "r")
+    if not file then
+        fw.throw_string(errmsg)
+    end
+    local content = file:read("*a")
+    if content == nil or content == "" then
+        print("file_get_contents error: ",filepath)
+        return ""
+    end
+    local replaced,c2 = engine.replace(content)
+    if replaced then
+        return c2
+    end
+    return content
+end
+M.menu_top = function()
+    -- 当前请求路径
+    local request_path = request.filepath() or ""
+    -- 取配置
+    local menuData = require("fwutils.develop.function.menu").bytecode(string.format("%d",engine.cfg.role_id()))
+    if not menuData then
+        return "<!-- no menu config -->"
+    end
+
+    -- 提取并排序主菜单
+    local menuItems = {}
+    for name, item in pairs(menuData) do
+        table.insert(menuItems, {
+            name = name,
+            item = item,
+            sort = item.sort or 0
+        })
+    end
+    table.sort(menuItems, function(a, b)
+        return a.sort > b.sort
+    end)
+
+    local html = [[
+
+  <ul class="navbar-nav me-auto mb-2 mb-lg-0">
+  ]]
+
+    -- 渲染主菜单
+    for _, entry in ipairs(menuItems) do
+        local name = entry.name
+        local item = entry.item
+        local icon = item.icon or ""
+        local path = item.path or "#"
+        local activeClass = ""
+
+        if item.children then
+            -- 有子菜单
+            local isMegamenu = item.megamenu and " dropdown-megamenu" or ""
+
+            -- 检查子菜单是否有激活
+            local hasActiveChild = false
+            local children = {}
+            for childName, childItem in pairs(item.children) do
+                table.insert(children, {
+                    name = childName,
+                    item = childItem,
+                    sort = childItem.sort or 0
+                })
+                if not hasActiveChild and (request_path == (childItem.path or "")) then
+                    hasActiveChild = true
+                end
+            end
+            table.sort(children, function(a, b)
+                return a.sort > b.sort
+            end)
+
+            -- 父菜单激活:当前页面是父菜单path,或是任一子菜单path
+            if request_path == path or hasActiveChild then
+                activeClass = "active-link"
+            end
+
+            html = html .. [[
+    <li class="nav-item dropdown ]] .. activeClass .. [[">
+      <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
+        <i class="]] .. icon .. [["></i> ]] .. name .. [[
+      </a>
+      <ul class="dropdown-menu]] .. isMegamenu .. [[">
+  ]]
+
+            -- 渲染子菜单
+            for _, child in ipairs(children) do
+                local childName = child.name
+                local childItem = child.item
+                local childPath = childItem.path or "#"
+
+                -- 子菜单不设置激活样式
+                html = html .. [[
+        <li>
+          <a class="dropdown-item" href="]] .. childPath .. [[">
+            <span>]] .. childName .. [[</span>
+          </a>
+        </li>
+  ]]
+            end
+
+            html = html .. [[
+      </ul>
+    </li>
+  ]]
+        else
+            -- 没有子菜单
+            -- 简单的“当前页面是否激活”检查
+            if request_path == path then
+                activeClass = "active-link"
+            end
+            html = html .. [[
+    <li class="nav-item ]] .. activeClass .. [[">
+      <a class="nav-link" href="]] .. path .. [[">
+        <i class="]] .. icon .. [["></i> ]] .. name .. [[
+      </a>
+    </li>
+  ]]
+        end
+    end
+
+    html = html .. [[
+  </ul>
+  ]]
+
+    return [=[
+    <nav class="navbar navbar-expand-lg">
+          <div class="container">
+            <div class="offcanvas offcanvas-end" id="MobileMenu">
+              <div class="offcanvas-header">
+                <h5 class="offcanvas-title semibold">Navigation</h5>
+                <button type="button" class="btn btn-danger btn-sm ms-auto" data-bs-dismiss="offcanvas">
+                  <i class="icon-clear"></i>
+                </button>
+              </div>
+    ]=]..html..[[
+            </div>
+          </div>
+        </nav>
+    ]]
+end
+M.regist = function(env)
+    for key, value in pairs(M) do
+        if type(value) == "function" and key ~= "functions" and key ~= "get_all_functions" then
+            env[key] = value
+        end
+    end
+    return env
+end
+return M

+ 4 - 2
target/fwutils/init.lua

@@ -16,7 +16,9 @@ M.initialization = function(conn)
     return true
 end
 M.__get_guest_role_id = function(mysql_conn)
-    local ppst = mysql_conn:setsql("SELECT * FROM fw_role WHERE guest = 1")
+    local config = require("fwutils.config")
+    local ppst = mysql_conn:setsql("SELECT * FROM fw_role WHERE guest = 1 AND server_id = ?")
+    ppst:set_str(1,config.server_id)
     local result = ppst:query()
     if result:row_count() ~= 1 then
         return false,"role guest not found"
@@ -26,7 +28,7 @@ M.__get_guest_role_id = function(mysql_conn)
     local bc = {
         GUEST_ROLE_ID = result:get("id"),
     }
-    local config = require("fwutils.config")
+    
     local code = "return " .. require("serpent").serialize(bc, {comment = false})
     utils.save_file(fw.website_dir().."/"..(config.path.luabytecode:gsub("%.", "/")).."/fwcache.lua",code)
     return true

+ 0 - 104
target/fwutils/menu.lua

@@ -1,104 +0,0 @@
-local fw = require("fastweb")
-local config = require("fwutils.config")
-local M = {}
--- CREATE TABLE `fw_menu` (
---   `id` int NOT NULL AUTO_INCREMENT,
---   `role_id` int DEFAULT NULL,
---   `title` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
---   `path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
---   `icon` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
---   `sort` int DEFAULT NULL,
---   `parent_id` int DEFAULT NULL,
---   PRIMARY KEY (`id`)
--- ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-
--- 更新
-M.update = function(role_id,conn)
-    -- 查询菜单表
-    local select = conn:select()
-    select:table("fw_menu")
-    select:where_expression("AND delete_time IS NULL")
-    if role_id ~= nil then
-        select:where_i32("role_id", "=", role_id)
-    end
-    local result = select:query()
-
-    local bc = {}
-    local items_by_id = {}  -- 通过id快速查找菜单项:{id = {role_id, title, item, parent_id}}
-    local items_with_parent = {}  -- 存储有父级的菜单项
-
-    -- 第一遍:读取所有菜单项并存储
-    while result:next() do
-        local id = result:get("id")
-        local role_id = tostring(result:get("role_id"))
-        local title = result:get("title")
-        local path = result:get("path")
-        local icon = result:get("icon")
-        local sort = result:get("sort")
-        local parent_id = result:get("parent_id")
-
-        if not title or title == "" then
-            goto continue
-        end
-
-        if bc[role_id] == nil then
-            bc[role_id] = {}
-        end
-
-        local item = {
-            path = path,
-            icon = icon,
-            sort = sort,
-        }
-
-        -- 存储所有菜单项信息
-        items_by_id[id] = {
-            role_id = role_id,
-            title = title,
-            item = item,
-            parent_id = parent_id
-        }
-
-        -- 如果parent_id为空,则作为顶层菜单项
-        if parent_id == nil or parent_id == 0 then
-            bc[role_id][title] = item
-        else
-            -- 有父级,记录下来稍后处理
-            table.insert(items_with_parent, {
-                id = id,
-                role_id = role_id,
-                title = title,
-                item = item,
-                parent_id = parent_id
-            })
-        end
-
-        ::continue::
-    end
-
-    -- 第二遍:处理有父级的菜单项,构建children结构
-    for _, menu_item in ipairs(items_with_parent) do
-        local parent_info = items_by_id[menu_item.parent_id]
-        if parent_info then
-            local parent_item = parent_info.item
-
-            -- 如果父项还没有children表,创建它
-            if not parent_item.children then
-                parent_item.children = {}
-            end
-
-            -- 将子项添加到父项的children中
-            parent_item.children[menu_item.title] = menu_item.item
-        end
-    end
-
-    local code = "return " .. require("serpent").serialize(bc, {comment = false})
-    utils.save_file(fw.website_dir().."/"..(config.path.luabytecode:gsub("%.", "/")).."/menu_bc.lua",code)
-    return true
-end
-M.get = function(role_id)
-    local menu_bc = require(config.path.luabytecode..".menu_bc")
-    return menu_bc[string.format("%d",role_id)]
-end
-
-return M

+ 25 - 186
target/fwutils/template_engine.lua

@@ -4,186 +4,18 @@ local config = require("fwutils.config")
 local request = require("fastweb.request")
 local response = require("fastweb.response")
 local cjson = require("cjson")
+local funs = require("fwutils.funs")
 -- 允许的扩展名
 local allowed_extensions = {
     "shtml",
     "html",
     "js"
 }
-local template_engine_bc_this_role = nil
-local cfg = nil
-
-local M = {}
-
-function file_get_contents(filepath)
-    
-    local file, errmsg = io.open(fw.website_dir()..filepath, "r")
-    if not file then
-        err.server(errmsg)
-    end
-    local content = file:read("*a")
-    if content == nil or content == "" then
-        print("file_get_contents error: ",filepath)
-        return ""
-    end
-    local replaced,c2 = M.replace(content)
-    if replaced then
-        return c2
-    end
-    return content
-end
-function menu_top()
-    -- 当前请求路径
-    local request_path = request.filepath() or ""
-    -- 取配置
-    local menuData = require("fwutils.menu").get(string.format("%d",cfg.role_id()))
-    if not menuData then
-        return "<!-- no menu config -->"
-    end
-
-    -- 提取并排序主菜单
-    local menuItems = {}
-    for name, item in pairs(menuData) do
-        table.insert(menuItems, {
-            name = name,
-            item = item,
-            sort = item.sort or 0
-        })
-    end
-    table.sort(menuItems, function(a, b)
-        return a.sort > b.sort
-    end)
-
-    local html = [[
-
-  <ul class="navbar-nav me-auto mb-2 mb-lg-0">
-  ]]
-
-    -- 渲染主菜单
-    for _, entry in ipairs(menuItems) do
-        local name = entry.name
-        local item = entry.item
-        local icon = item.icon or ""
-        local path = item.path or "#"
-        local activeClass = ""
-
-        if item.children then
-            -- 有子菜单
-            local isMegamenu = item.megamenu and " dropdown-megamenu" or ""
-
-            -- 检查子菜单是否有激活
-            local hasActiveChild = false
-            local children = {}
-            for childName, childItem in pairs(item.children) do
-                table.insert(children, {
-                    name = childName,
-                    item = childItem,
-                    sort = childItem.sort or 0
-                })
-                if not hasActiveChild and (request_path == (childItem.path or "")) then
-                    hasActiveChild = true
-                end
-            end
-            table.sort(children, function(a, b)
-                return a.sort > b.sort
-            end)
-
-            -- 父菜单激活:当前页面是父菜单path,或是任一子菜单path
-            if request_path == path or hasActiveChild then
-                activeClass = "active-link"
-            end
-
-            html = html .. [[
-    <li class="nav-item dropdown ]] .. activeClass .. [[">
-      <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
-        <i class="]] .. icon .. [["></i> ]] .. name .. [[
-      </a>
-      <ul class="dropdown-menu]] .. isMegamenu .. [[">
-  ]]
-
-            -- 渲染子菜单
-            for _, child in ipairs(children) do
-                local childName = child.name
-                local childItem = child.item
-                local childPath = childItem.path or "#"
-
-                -- 子菜单不设置激活样式
-                html = html .. [[
-        <li>
-          <a class="dropdown-item" href="]] .. childPath .. [[">
-            <span>]] .. childName .. [[</span>
-          </a>
-        </li>
-  ]]
-            end
-
-            html = html .. [[
-      </ul>
-    </li>
-  ]]
-        else
-            -- 没有子菜单
-            -- 简单的“当前页面是否激活”检查
-            if request_path == path then
-                activeClass = "active-link"
-            end
-            html = html .. [[
-    <li class="nav-item ]] .. activeClass .. [[">
-      <a class="nav-link" href="]] .. path .. [[">
-        <i class="]] .. icon .. [["></i> ]] .. name .. [[
-      </a>
-    </li>
-  ]]
-        end
-    end
-
-    html = html .. [[
-  </ul>
-  ]]
-
-    return [=[
-    <nav class="navbar navbar-expand-lg">
-          <div class="container">
-            <div class="offcanvas offcanvas-end" id="MobileMenu">
-              <div class="offcanvas-header">
-                <h5 class="offcanvas-title semibold">Navigation</h5>
-                <button type="button" class="btn btn-danger btn-sm ms-auto" data-bs-dismiss="offcanvas">
-                  <i class="icon-clear"></i>
-                </button>
-              </div>
-    ]=]..html..[[
-            </div>
-          </div>
-        </nav>
-    ]]
-end
-function template(filepath)
-    local path = "/public/user/template/"..config.agent[user_agent_id()].template.."/"..filepath
-    return file_get_contents(path) 
-end
+local M = {
+    cfg = nil,
+    template_engine_bc_this_role = nil
+}
 
-function teacher_photos()
-    require("app.app")
-    local agent_id = user_agent_id()
-    local teacher = require("app.function.teacher")
-    local teacher_info = teacher.get_by_id(pint("id"))
-    if teacher_info == nil then
-        return ""
-    end
-    local photos = cjson.decode(teacher_info.photo)
-    local content = "<div><div class='home-img'><img src='".. teacher_info.avatar.."' class='img-fluid bg-img' alt=''></div></div>"
-    for i,v in ipairs(photos) do
-        if type(v) == "string" then
-            content = content..[[<div>
-                <div class="home-img">
-                    <img src="]]..v..[[" class="img-fluid bg-img" alt="">
-                </div>
-            </div>
-            ]] 
-        end
-    end
-    return content
-end
 -- 更新
 M.update = function(conn)
     -- 查询权限表
@@ -211,14 +43,14 @@ M.update = function(conn)
         end
     end
     local code = "return " .. require("serpent").serialize(bc, {comment = false})
-    utils.save_file(fw.website_dir().."/"..(config.path.luabytecode:gsub("%.", "/")).."/template_engine_bc.lua",code)
+    utils.save_file(fw.website_dir().."/"..(fwutils_config.path.luabytecode:gsub("%.", "/")).."/template_engine_bc.lua",code)
     return true
 end
 -- 处理
 -- @param static_content 静态内容
 -- @param cfg 配置
 -- @return 是否替换(TRUE则不需要继续处理,FALSE则继续处理)
-M.handle = function(__cfg)
+M.handle = function(__cfg,functions)
 
     local function has_ext(ext)
         for _, v in ipairs(allowed_extensions) do
@@ -230,8 +62,8 @@ M.handle = function(__cfg)
     end
 
 
-    cfg = __cfg
-    template_engine_bc_this_role = {
+    M.cfg = __cfg
+    M.template_engine_bc_this_role = {
         template = {
             private = {},
             public = {}
@@ -239,14 +71,14 @@ M.handle = function(__cfg)
     }
 
 
-    local ext = utils.ext(cfg.filepath())
+    local ext = utils.ext(M.cfg.filepath())
     local static_content = nil
     if ext ~= nil  then
         if not has_ext(ext) then
             return false
         end
         -- 读取资源文件
-        static_content = utils.read_file(fw.website_dir()..cfg.filepath())
+        static_content = utils.read_file(fw.website_dir()..M.cfg.filepath())
         if static_content == nil or static_content == "" then
             return false
         end
@@ -254,9 +86,9 @@ M.handle = function(__cfg)
         -- 无需替换的扩展名
         return false
     end 
-    local template_engine_bc = require(config.path.luabytecode..".template_engine_bc")
-    template_engine_bc_this_role["template"]["private"] = template_engine_bc[string.format("%d",cfg.role_id())]
-    template_engine_bc_this_role["template"]["public"] = template_engine_bc["public"]
+    local template_engine_bc = require(fwutils_config.path.luabytecode..".template_engine_bc")
+    M.template_engine_bc_this_role["template"]["private"] = template_engine_bc[string.format("%d",M.cfg.role_id())]
+    M.template_engine_bc_this_role["template"]["public"] = template_engine_bc["public"]
     
 
     local replaced,content = M.replace(static_content)
@@ -266,7 +98,14 @@ M.handle = function(__cfg)
     -- 执行函数
     static_content, n = static_content:gsub("%${<<<%s*(.-)%s*>>>}", function(code)
         -- 尝试编译代码(Lua 5.2+ 使用 load;Lua 5.1 可用 loadstring)
-        local chunk, errmsg = load(code)
+        local env = {}
+        _G["engine"] = M
+        local env = funs.regist(env)
+        if functions ~= nil then
+            env = functions.regist(env)
+        end
+        env["_G"] = _G
+        local chunk, errmsg = load(code,nil,nil,env)
         if not chunk then
             fw.throw_string(errmsg)
         end
@@ -356,12 +195,12 @@ M.replace = function (content)
     local value_map = {}
     
     -- PUBLIC
-    local flat_kvs = flatten_kvs(template_engine_bc_this_role["template"]["public"])
+    local flat_kvs = flatten_kvs(M.template_engine_bc_this_role["template"]["public"])
     for k, v in pairs(flat_kvs) do
         value_map[k] = v
     end
     -- PRIVATE
-    flat_kvs = flatten_kvs(template_engine_bc_this_role["template"]["private"])
+    flat_kvs = flatten_kvs(M.template_engine_bc_this_role["template"]["private"])
     for k, v in pairs(flat_kvs) do
         value_map[k] = v
     end
@@ -371,7 +210,7 @@ M.replace = function (content)
         value_map[k] = v
     end
     -- TOKEN
-    flat_kvs = flatten_kvs(cfg.user_data(), "token")
+    flat_kvs = flatten_kvs(M.cfg.user_data(), "token")
     for k, v in pairs(flat_kvs) do
         value_map[k] = v
     end

+ 117 - 0
target/fwutils/webapi.lua

@@ -0,0 +1,117 @@
+cjson = require("cjson")
+fw = require("fastweb")
+request = require("fastweb.request")
+response = require("fastweb.response")
+utils = require("utils")
+mysql = require("mysql.pool")
+fwutils_config = require("fwutils.config")
+-- 执行
+function exec(map)
+    local action = param("action")
+    if action == nil or action == "" then
+        fw.throw_string("action is required")
+        return
+    end
+    if map[action] == nil then
+        fw.throw_string("action `"..action.."()` not found")
+        return
+    end
+    map[action]() 
+end
+-- 标准回复
+function reply(code,msg,data)
+    local body = cjson.encode({
+        code = code,
+        msg = msg,
+        data = data
+    })
+    response.header("Content-Type","application/json")
+    response.send(body)
+end
+-- 回复JSON
+function reply_json(data)
+    response.header("Content-Type","application/json")
+    response.send(cjson.encode(data))
+end
+
+-- 快速回复-错误
+function error(msg)
+    reply(201,msg)
+end
+-- 快速回复-成功
+function succ(data)
+    reply(200,"",data)
+end
+-- 取请求参数(无参抛异常)
+function param_trw(name)
+    return request.param(name,true)
+end
+-- 取请求参数
+function param(name)
+    return request.param(name,false)
+end
+
+
+function pint(name)
+    local result = utils.tointeger(param_trw(name))
+    if result == nil then
+        fw.throw_string("Parameters require integers,name:"..name..",value:"..param_trw(name))
+    else
+        return result
+    end
+end
+function pnum(name)
+    local result = tonumber(param_trw(name))
+    if result == nil then
+        fw.throw_string("Parameters require number,name:"..name..",value:"..param_trw(name))
+    else
+        return result
+    end
+end
+function pstr(name)
+    return param_trw(name)
+end
+function pjson()
+    return cjson.decode(request.body())
+end
+-- 查询模型
+function query_model_ex(conn,table,field,start,length,callback_gen,callback_data,callback_count,distinct)
+    local sel_data = conn:select()
+    local sel_count = conn:select()
+
+    sel_data:table(table)
+    sel_data:field(field)
+    if start ~= nil and length ~= nil then
+        sel_data:limit(start,length)
+    end
+
+    sel_count:table(table)
+
+
+    if distinct == nil then
+        sel_count:field({"COUNT(1)"})
+    else
+        sel_count:field(distinct)
+    end
+    
+    if callback_gen ~= nil then
+        callback_gen(sel_data)
+        callback_gen(sel_count)
+    end
+    if callback_data ~= nil then
+        callback_data(sel_data)
+    end
+    if callback_count ~= nil then
+        callback_count(sel_count)
+    end
+
+
+    local result_count = sel_count:query()
+    result_count:next()
+ 
+    local data = {
+        count = result_count:get(1),
+        data = sel_data:query():table()
+    }
+    return data
+end

+ 11 - 2
target/utils.lua

@@ -75,8 +75,17 @@ M.copy_file = function(src, dst, replace)
     return true
 end
 -- 取路径文件名(带扩展名)
-M.filename = function(filepath)
-    return string.match(filepath,"[^/]+$")
+M.filename = function(filepath, have_ext)
+    local filename = string.match(filepath, "[^/]+$")
+    if have_ext == false then
+        local name = string.match(filename, "^(.*)%.") -- get name before last dot
+        if name ~= nil then
+            return name
+        else
+            return filename -- no extension
+        end
+    end
+    return filename
 end
 -- 删除文件
 M.delete_file = function(filepath)