local utils = require("utils")
local fw = require("fastweb")
local config = require("fwutils.config")
local request = require("fastweb.request")
local response = require("fastweb.response")
local cjson = require("cjson")
-- 允许的扩展名
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 ""
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 = [[
]]
-- 渲染主菜单
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 .. [[
]]
-- 渲染子菜单
for _, child in ipairs(children) do
local childName = child.name
local childItem = child.item
local childPath = childItem.path or "#"
-- 子菜单不设置激活样式
html = html .. [[
]]
return [=[
]]
end
function template(filepath)
local path = "/public/user/template/"..config.agent[user_agent_id()].template.."/"..filepath
return file_get_contents(path)
end
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 = "
"
for i,v in ipairs(photos) do
if type(v) == "string" then
content = content..[[
]]
end
end
return content
end
-- 更新
M.update = 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().."/"..(config.path.luabytecode:gsub("%.", "/")).."/template_engine_bc.lua",code)
return true
end
-- 处理
-- @param static_content 静态内容
-- @param cfg 配置
-- @return 是否替换(TRUE则不需要继续处理,FALSE则继续处理)
M.handle = function(__cfg)
local function has_ext(ext)
for _, v in ipairs(allowed_extensions) do
if v == ext then
return true
end
end
return false
end
cfg = __cfg
template_engine_bc_this_role = {
template = {
private = {},
public = {}
},
}
local ext = utils.ext(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())
if static_content == nil or static_content == "" then
return false
end
else
-- 无需替换的扩展名
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 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 chunk, errmsg = load(code)
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)
else
out[key] = 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(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"])
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(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