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 = { cfg = nil, template_engine_bc_this_role = nil } -- 允许的扩展名 local allowed_extensions = { "shtml", "html", "js" } 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 -- 更新 M.make_bytecode = function(conn) -- 查询权限表 local select = conn:select() select:table("fw_template") select:where_i32("enable","=",1) local result = select:query() local bc = { public = {} } while result:next() do local id = result:get("id") local role_id = string.format("%d",result:get("role_id")) local key = result:get("key") local value = result:get("value") if bc[role_id] == nil then bc[role_id] = {} end if role_id == "0" then bc["public"][key] = value else bc[role_id][key] = value end end local code = "return " .. require("serpent").serialize(bc, {comment = false}) 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,functions) local function has_ext(ext) for _, v in ipairs(allowed_extensions) do if v == ext then return true end end return false end M.cfg = __cfg M.template_engine_bc_this_role = { template = { private = {}, public = {} }, } 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()..M.cfg.filepath()) if static_content == nil or static_content == "" then return false end else -- 无需替换的扩展名 return false end 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) if replaced then static_content = content end -- 执行函数 static_content, n = static_content:gsub("%${<<<%s*(.-)%s*>>>}", function(code) -- 尝试编译代码(Lua 5.2+ 使用 load;Lua 5.1 可用 loadstring) local env = {} _G["engine"] = M local env = require("fwutils.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 -- 使用 pcall 安全执行代码块 local status, result = pcall(chunk) if not status then fw.throw_string(result) end -- 如果代码没有返回值,则替换为空字符串,否则转换成字符串返回 return tostring(result) end) if n > 0 then replaced = true end if replaced then if ext == "shtml" or ext == "html" then response.header("Content-Type","text/html") elseif ext == "js" then response.header("Content-Type","application/javascript") end response.send(static_content) return true end return false end M.replace = function (content) if content == nil or content == "" then return false end -- 支持多级kvs替换,如 ${people.age} local function flatten_kvs(tbl, prefix, out) if tbl == nil then return {} end out = out or {} prefix = prefix or "" for k, v in pairs(tbl) do local key = prefix ~= "" and (prefix .. "." .. k) or k if type(v) == "table" then flatten_kvs(v, key, out) elseif type(v) == "string" then out[key] = v elseif type(v) == "number" then if math.type and math.type(v) == "integer" then out[key] = string.format("%d", v) elseif math.floor(v) == v then out[key] = string.format("%d", v) else out[key] = tostring(v) end else out[key] = tostring(v) end end return out end -- 先提取 content 中所有需要替换的占位符 -- 需要正确处理 ${<<<...>>>} 块:块内部的 ${...} 需要替换,但块本身不替换 local placeholders = {} -- 第一步:提取所有 ${<<<...>>>} 块,临时替换它们,并收集所有占位符 local blocks = {} local block_index = 0 local temp_content = content:gsub("%${<<<%s*(.-)%s*>>>}", function(block_content) block_index = block_index + 1 local placeholder = "${__TEMP_BLOCK_" .. block_index .. "__}" -- 收集块内部的 ${...} 占位符 for ph in string.gmatch(block_content, "%${([^}]+)}") do placeholders[ph] = true end blocks[block_index] = { placeholder = placeholder, content = block_content } return placeholder end) -- 第二步:提取外部(不在 ${<<<...>>>} 块中)的 ${...} 占位符 for placeholder in string.gmatch(temp_content, "%${([^}]+)}") do -- 忽略临时占位符 if not placeholder:match("^__TEMP_BLOCK_%d+__$") then placeholders[placeholder] = true end end -- print("PLACEHOLDERS:",cjson.encode(placeholders)) -- 如果没有任何占位符,直接返回 if next(placeholders) == nil then return false end -- 合并所有数据源到一个查找表中(只 flatten 一次) local value_map = {} -- 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(M.template_engine_bc_this_role["template"]["private"]) for k, v in pairs(flat_kvs) do value_map[k] = v end -- REQUEST flat_kvs = flatten_kvs(request.gets(), "request") for k, v in pairs(flat_kvs) do value_map[k] = v end -- TOKEN flat_kvs = flatten_kvs(M.cfg.user_data(), "token") for k, v in pairs(flat_kvs) do value_map[k] = v end -- 转义函数:将 Lua 模式特殊字符转义为字面匹配 local function escape_pattern(str) return str:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") end -- 第三步:先处理 ${<<<...>>>} 块内部的占位符 local replaced = false for i, block in ipairs(blocks) do local block_content = block.content for placeholder, _ in pairs(placeholders) do if value_map[placeholder] ~= nil then local escaped_placeholder = escape_pattern(placeholder) local new_content, count = string.gsub(block_content, "%${" .. escaped_placeholder .. "}", tostring(value_map[placeholder])) if count > 0 then block_content = new_content replaced = true end end end blocks[i].processed_content = block_content end -- 第四步:替换外部的 ${...} 占位符(在临时内容中,此时块已被替换为临时占位符) local n = 0 for placeholder, _ in pairs(placeholders) do if value_map[placeholder] ~= nil then local escaped_placeholder = escape_pattern(placeholder) temp_content, n = string.gsub(temp_content, "%${" .. escaped_placeholder .. "}", tostring(value_map[placeholder])) if n > 0 then replaced = true end end end -- 第五步:恢复 ${<<<...>>>} 块(使用处理后的内容) for i, block in ipairs(blocks) do local processed_block = "${<<<" .. blocks[i].processed_content .. ">>>}" temp_content = temp_content:gsub(block.placeholder:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1"), processed_block) end content = temp_content if replaced then return true, content end return false end -- 检查内容中是否有TOKEN变量 -- @return boolean M.hasToken = function() if M.static_content == nil or M.static_content == "" then return false end return M.static_content:match("%${token%.") ~= nil end return M