template_engine.lua 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. local utils = require("utils")
  2. local fw = require("fastweb")
  3. local config = require("fwutils.config")
  4. local request = require("fastweb.request")
  5. local response = require("fastweb.response")
  6. local cjson = require("cjson")
  7. local funs = require("fwutils.funs")
  8. -- 允许的扩展名
  9. local allowed_extensions = {
  10. "shtml",
  11. "html",
  12. "js"
  13. }
  14. local M = {
  15. cfg = nil,
  16. template_engine_bc_this_role = nil
  17. }
  18. -- 更新
  19. M.update = function(conn)
  20. -- 查询权限表
  21. local select = conn:select()
  22. select:table("fw_template")
  23. select:where_i32("enable","=",1)
  24. local result = select:query()
  25. local bc = {
  26. public = {}
  27. }
  28. while result:next() do
  29. local id = result:get("id")
  30. local role_id = string.format("%d",result:get("role_id"))
  31. local key = result:get("key")
  32. local value = result:get("value")
  33. if bc[role_id] == nil then
  34. bc[role_id] = {}
  35. end
  36. if role_id == "0" then
  37. bc["public"][key] = value
  38. else
  39. bc[role_id][key] = value
  40. end
  41. end
  42. local code = "return " .. require("serpent").serialize(bc, {comment = false})
  43. utils.save_file(fw.website_dir().."/"..(fwutils_config.path.luabytecode:gsub("%.", "/")).."/template_engine_bc.lua",code)
  44. return true
  45. end
  46. -- 处理
  47. -- @param static_content 静态内容
  48. -- @param cfg 配置
  49. -- @return 是否替换(TRUE则不需要继续处理,FALSE则继续处理)
  50. M.handle = function(__cfg,functions)
  51. local function has_ext(ext)
  52. for _, v in ipairs(allowed_extensions) do
  53. if v == ext then
  54. return true
  55. end
  56. end
  57. return false
  58. end
  59. M.cfg = __cfg
  60. M.template_engine_bc_this_role = {
  61. template = {
  62. private = {},
  63. public = {}
  64. },
  65. }
  66. local ext = utils.ext(M.cfg.filepath())
  67. local static_content = nil
  68. if ext ~= nil then
  69. if not has_ext(ext) then
  70. return false
  71. end
  72. -- 读取资源文件
  73. static_content = utils.read_file(fw.website_dir()..M.cfg.filepath())
  74. if static_content == nil or static_content == "" then
  75. return false
  76. end
  77. else
  78. -- 无需替换的扩展名
  79. return false
  80. end
  81. local template_engine_bc = require(fwutils_config.path.luabytecode..".template_engine_bc")
  82. M.template_engine_bc_this_role["template"]["private"] = template_engine_bc[string.format("%d",M.cfg.role_id())]
  83. M.template_engine_bc_this_role["template"]["public"] = template_engine_bc["public"]
  84. local replaced,content = M.replace(static_content)
  85. if replaced then
  86. static_content = content
  87. end
  88. -- 执行函数
  89. static_content, n = static_content:gsub("%${<<<%s*(.-)%s*>>>}", function(code)
  90. -- 尝试编译代码(Lua 5.2+ 使用 load;Lua 5.1 可用 loadstring)
  91. local env = {}
  92. _G["engine"] = M
  93. local env = funs.regist(env)
  94. if functions ~= nil then
  95. env = functions.regist(env)
  96. end
  97. env["_G"] = _G
  98. local chunk, errmsg = load(code,nil,nil,env)
  99. if not chunk then
  100. fw.throw_string(errmsg)
  101. end
  102. -- 使用 pcall 安全执行代码块
  103. local status, result = pcall(chunk)
  104. if not status then
  105. fw.throw_string(result)
  106. end
  107. -- 如果代码没有返回值,则替换为空字符串,否则转换成字符串返回
  108. return tostring(result)
  109. end)
  110. if n > 0 then
  111. replaced = true
  112. end
  113. if replaced then
  114. if ext == "shtml" or ext == "html" then
  115. response.header("Content-Type","text/html")
  116. elseif ext == "js" then
  117. response.header("Content-Type","application/javascript")
  118. end
  119. response.send(static_content)
  120. return true
  121. end
  122. return false
  123. end
  124. M.replace = function (content)
  125. if content == nil or content == "" then
  126. return false
  127. end
  128. -- 支持多级kvs替换,如 ${people.age}
  129. local function flatten_kvs(tbl, prefix, out)
  130. if tbl == nil then
  131. return {}
  132. end
  133. out = out or {}
  134. prefix = prefix or ""
  135. for k, v in pairs(tbl) do
  136. local key = prefix ~= "" and (prefix .. "." .. k) or k
  137. if type(v) == "table" then
  138. flatten_kvs(v, key, out)
  139. else
  140. out[key] = v
  141. end
  142. end
  143. return out
  144. end
  145. -- 先提取 content 中所有需要替换的占位符
  146. -- 需要正确处理 ${<<<...>>>} 块:块内部的 ${...} 需要替换,但块本身不替换
  147. local placeholders = {}
  148. -- 第一步:提取所有 ${<<<...>>>} 块,临时替换它们,并收集所有占位符
  149. local blocks = {}
  150. local block_index = 0
  151. local temp_content = content:gsub("%${<<<%s*(.-)%s*>>>}", function(block_content)
  152. block_index = block_index + 1
  153. local placeholder = "${__TEMP_BLOCK_" .. block_index .. "__}"
  154. -- 收集块内部的 ${...} 占位符
  155. for ph in string.gmatch(block_content, "%${([^}]+)}") do
  156. placeholders[ph] = true
  157. end
  158. blocks[block_index] = {
  159. placeholder = placeholder,
  160. content = block_content
  161. }
  162. return placeholder
  163. end)
  164. -- 第二步:提取外部(不在 ${<<<...>>>} 块中)的 ${...} 占位符
  165. for placeholder in string.gmatch(temp_content, "%${([^}]+)}") do
  166. -- 忽略临时占位符
  167. if not placeholder:match("^__TEMP_BLOCK_%d+__$") then
  168. placeholders[placeholder] = true
  169. end
  170. end
  171. -- print("PLACEHOLDERS:",cjson.encode(placeholders))
  172. -- 如果没有任何占位符,直接返回
  173. if next(placeholders) == nil then
  174. return false
  175. end
  176. -- 合并所有数据源到一个查找表中(只 flatten 一次)
  177. local value_map = {}
  178. -- PUBLIC
  179. local flat_kvs = flatten_kvs(M.template_engine_bc_this_role["template"]["public"])
  180. for k, v in pairs(flat_kvs) do
  181. value_map[k] = v
  182. end
  183. -- PRIVATE
  184. flat_kvs = flatten_kvs(M.template_engine_bc_this_role["template"]["private"])
  185. for k, v in pairs(flat_kvs) do
  186. value_map[k] = v
  187. end
  188. -- REQUEST
  189. flat_kvs = flatten_kvs(request.gets(), "request")
  190. for k, v in pairs(flat_kvs) do
  191. value_map[k] = v
  192. end
  193. -- TOKEN
  194. flat_kvs = flatten_kvs(M.cfg.user_data(), "token")
  195. for k, v in pairs(flat_kvs) do
  196. value_map[k] = v
  197. end
  198. -- 转义函数:将 Lua 模式特殊字符转义为字面匹配
  199. local function escape_pattern(str)
  200. return str:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
  201. end
  202. -- 第三步:先处理 ${<<<...>>>} 块内部的占位符
  203. local replaced = false
  204. for i, block in ipairs(blocks) do
  205. local block_content = block.content
  206. for placeholder, _ in pairs(placeholders) do
  207. if value_map[placeholder] ~= nil then
  208. local escaped_placeholder = escape_pattern(placeholder)
  209. local new_content, count = string.gsub(block_content, "%${" .. escaped_placeholder .. "}", tostring(value_map[placeholder]))
  210. if count > 0 then
  211. block_content = new_content
  212. replaced = true
  213. end
  214. end
  215. end
  216. blocks[i].processed_content = block_content
  217. end
  218. -- 第四步:替换外部的 ${...} 占位符(在临时内容中,此时块已被替换为临时占位符)
  219. local n = 0
  220. for placeholder, _ in pairs(placeholders) do
  221. if value_map[placeholder] ~= nil then
  222. local escaped_placeholder = escape_pattern(placeholder)
  223. temp_content, n = string.gsub(temp_content, "%${" .. escaped_placeholder .. "}", tostring(value_map[placeholder]))
  224. if n > 0 then
  225. replaced = true
  226. end
  227. end
  228. end
  229. -- 第五步:恢复 ${<<<...>>>} 块(使用处理后的内容)
  230. for i, block in ipairs(blocks) do
  231. local processed_block = "${<<<" .. blocks[i].processed_content .. ">>>}"
  232. temp_content = temp_content:gsub(block.placeholder:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1"), processed_block)
  233. end
  234. content = temp_content
  235. if replaced then
  236. return true, content
  237. end
  238. return false
  239. end
  240. -- 检查内容中是否有TOKEN变量
  241. -- @return boolean
  242. M.hasToken = function()
  243. if M.static_content == nil or M.static_content == "" then
  244. return false
  245. end
  246. return M.static_content:match("%${token%.") ~= nil
  247. end
  248. return M