template.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. require("fwutils.webapi")
  2. -- CREATE TABLE `fw_template` (
  3. -- `id` int NOT NULL AUTO_INCREMENT,
  4. -- `role_id` int DEFAULT NULL,
  5. -- `key` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  6. -- `value` varchar(2048) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  7. -- `enable` tinyint NOT NULL COMMENT '是否启用',
  8. -- PRIMARY KEY (`id`),
  9. -- UNIQUE KEY `role_id` (`role_id`,`key`)
  10. -- ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Fastweb-关键词模板';
  11. local M = {
  12. cfg = nil,
  13. template_engine_bc_this_role = nil
  14. }
  15. -- 允许的扩展名
  16. local allowed_extensions = {
  17. "shtml",
  18. "html",
  19. "js"
  20. }
  21. function M.get_by_id(id,conn)
  22. local select = conn:select()
  23. select:table("fw_template LEFT JOIN fw_role ON fw_template.role_id = fw_role.id")
  24. select:field({
  25. "fw_template.id",
  26. "fw_template.role_id",
  27. "fw_template.`key`",
  28. "fw_template.value",
  29. "fw_template.enable",
  30. "fw_role.title as role_title",
  31. })
  32. select:where_i32("fw_template.id","=",id)
  33. select:limit(0,1)
  34. local result = select:query()
  35. if result:row_count() == 0 then
  36. return nil
  37. end
  38. local d = result:table()[1]
  39. return d
  40. end
  41. function M.add(data,conn)
  42. local insert = conn:insert()
  43. insert:table("fw_template")
  44. if data.role_id ~= nil and data.role_id ~= cjson.null then
  45. insert:set_i32("role_id",data.role_id)
  46. end
  47. if data.key ~= nil then
  48. insert:set_str("`key`",data.key)
  49. end
  50. if data.value ~= nil then
  51. insert:set_str("value",data.value)
  52. end
  53. if data.enable ~= nil then
  54. insert:set_i32("enable",data.enable)
  55. end
  56. local d = insert:exec()
  57. return d == 1
  58. end
  59. function M.update(data,conn)
  60. local update = conn:update()
  61. update:table("fw_template")
  62. if data.role_id ~= nil then
  63. update:set_i32("role_id",data.role_id)
  64. end
  65. if data.key ~= nil then
  66. update:set_str("`key`",data.key)
  67. end
  68. if data.value ~= nil then
  69. update:set_str("value",data.value)
  70. end
  71. if data.enable ~= nil then
  72. update:set_i32("enable",data.enable)
  73. end
  74. update:where_i32("id","=",data.id)
  75. local d = update:exec()
  76. return d == 1
  77. end
  78. function M.delete(id,conn)
  79. local del = conn:delete()
  80. del:table("fw_template")
  81. del:where_i32("id","=",id)
  82. local d = del:exec()
  83. return d == 1
  84. end
  85. function M.list(search,limit,conn)
  86. return query_model_ex(conn,[=[
  87. fw_template LEFT JOIN fw_role ON fw_template.role_id = fw_role.id
  88. ]=],{
  89. "fw_template.id",
  90. "fw_template.role_id",
  91. "fw_template.`key`",
  92. "fw_template.value",
  93. "fw_template.enable",
  94. "fw_role.title as role_title",
  95. "fw_role.id as role_id",
  96. },limit.start,limit.length,function(sel)
  97. if search.role_id ~= nil and search.role_id ~= -1 then
  98. sel:where_i32("fw_template.role_id","=",search.role_id)
  99. end
  100. end,function(sel_data)
  101. sel_data:orderby("fw_template.enable DESC")
  102. end)
  103. end
  104. -- 更新
  105. M.make_bytecode = function(conn)
  106. -- 查询权限表
  107. local select = conn:select()
  108. select:table("fw_template")
  109. select:where_i32("enable","=",1)
  110. local result = select:query()
  111. local bc = {
  112. public = {}
  113. }
  114. while result:next() do
  115. local id = result:get("id")
  116. local role_id = string.format("%d",result:get("role_id"))
  117. local key = result:get("key")
  118. local value = result:get("value")
  119. if bc[role_id] == nil then
  120. bc[role_id] = {}
  121. end
  122. if role_id == "0" then
  123. bc["public"][key] = value
  124. else
  125. bc[role_id][key] = value
  126. end
  127. end
  128. local code = "return " .. require("serpent").serialize(bc, {comment = false})
  129. utils.save_file(fw.website_dir().."/"..(fwutils_config.path.luabytecode:gsub("%.", "/")).."/template_engine_bc.lua",code)
  130. return true
  131. end
  132. -- 处理
  133. -- @param static_content 静态内容
  134. -- @param cfg 配置
  135. -- @return 是否替换(TRUE则不需要继续处理,FALSE则继续处理)
  136. M.handle = function(__cfg,functions)
  137. local function has_ext(ext)
  138. for _, v in ipairs(allowed_extensions) do
  139. if v == ext then
  140. return true
  141. end
  142. end
  143. return false
  144. end
  145. M.cfg = __cfg
  146. M.template_engine_bc_this_role = {
  147. template = {
  148. private = {},
  149. public = {}
  150. },
  151. }
  152. if not utils.exists_file(fw.website_dir()..M.cfg.filepath()) then
  153. return false
  154. end
  155. local ext = utils.ext(M.cfg.filepath())
  156. local last_modified = fw.file_last_modified_time(fw.website_dir()..M.cfg.filepath())
  157. local static_content = nil
  158. if ext ~= nil then
  159. if not has_ext(ext) then
  160. return false
  161. end
  162. -- 读取资源文件
  163. static_content = utils.read_file(fw.website_dir()..M.cfg.filepath())
  164. if static_content == nil or static_content == "" then
  165. return false
  166. end
  167. else
  168. -- 无需替换的扩展名
  169. return false
  170. end
  171. -- 验证缓存
  172. local if_modified_since = request.header("If-Modified-Since")
  173. if if_modified_since ~= nil and if_modified_since ~= "" then
  174. if if_modified_since == last_modified then
  175. response.sendex("",304,"Not Modified")
  176. return true
  177. end
  178. end
  179. local template_engine_bc = require(fwutils_config.path.luabytecode..".template_engine_bc")
  180. M.template_engine_bc_this_role["template"]["private"] = template_engine_bc[string.format("%d",M.cfg.role_id())]
  181. M.template_engine_bc_this_role["template"]["public"] = template_engine_bc["public"]
  182. local replaced,content = M.replace(static_content)
  183. if replaced then
  184. static_content = content
  185. end
  186. -- 执行函数
  187. static_content, n = static_content:gsub("%${<<<%s*(.-)%s*>>>}", function(code)
  188. -- 尝试编译代码(Lua 5.2+ 使用 load;Lua 5.1 可用 loadstring)
  189. local env = {}
  190. _G["engine"] = M
  191. local env = require("fwutils.funs").regist(env)
  192. if functions ~= nil then
  193. env = functions.regist(env)
  194. end
  195. env["_G"] = _G
  196. local chunk, errmsg = load(code,nil,nil,env)
  197. if not chunk then
  198. fw.throw_string(errmsg)
  199. end
  200. -- 使用 pcall 安全执行代码块
  201. local status, result = pcall(chunk)
  202. if not status then
  203. fw.throw_string(result)
  204. end
  205. -- 如果代码没有返回值,则替换为空字符串,否则转换成字符串返回
  206. return tostring(result)
  207. end)
  208. if n > 0 then
  209. replaced = true
  210. end
  211. if replaced then
  212. if ext == "shtml" or ext == "html" then
  213. response.header("Content-Type","text/html")
  214. elseif ext == "js" then
  215. response.header("Content-Type","application/javascript")
  216. end
  217. -- print("Last-Modified:", last_modified)
  218. response.header("Last-Modified", last_modified)
  219. response.header("Cache-Control", "no-cache")
  220. response.send(static_content)
  221. return true
  222. end
  223. return false
  224. end
  225. M.replace = function (content)
  226. if content == nil or content == "" then
  227. return false
  228. end
  229. -- 支持多级kvs替换,如 ${people.age}
  230. local function flatten_kvs(tbl, prefix, out)
  231. if tbl == nil then
  232. return {}
  233. end
  234. out = out or {}
  235. prefix = prefix or ""
  236. for k, v in pairs(tbl) do
  237. local key = prefix ~= "" and (prefix .. "." .. k) or k
  238. if type(v) == "table" then
  239. flatten_kvs(v, key, out)
  240. elseif type(v) == "string" then
  241. out[key] = v
  242. elseif type(v) == "number" then
  243. if math.type and math.type(v) == "integer" then
  244. out[key] = string.format("%d", v)
  245. elseif math.floor(v) == v then
  246. out[key] = string.format("%d", v)
  247. else
  248. out[key] = tostring(v)
  249. end
  250. else
  251. out[key] = tostring(v)
  252. end
  253. end
  254. return out
  255. end
  256. -- 先提取 content 中所有需要替换的占位符
  257. -- 需要正确处理 ${<<<...>>>} 块:块内部的 ${...} 需要替换,但块本身不替换
  258. local placeholders = {}
  259. -- 第一步:提取所有 ${<<<...>>>} 块,临时替换它们,并收集所有占位符
  260. local blocks = {}
  261. local block_index = 0
  262. local temp_content = content:gsub("%${<<<%s*(.-)%s*>>>}", function(block_content)
  263. block_index = block_index + 1
  264. local placeholder = "${__TEMP_BLOCK_" .. block_index .. "__}"
  265. -- 收集块内部的 ${...} 占位符
  266. for ph in string.gmatch(block_content, "%${([^}]+)}") do
  267. placeholders[ph] = true
  268. end
  269. blocks[block_index] = {
  270. placeholder = placeholder,
  271. content = block_content
  272. }
  273. return placeholder
  274. end)
  275. -- 第二步:提取外部(不在 ${<<<...>>>} 块中)的 ${...} 占位符
  276. for placeholder in string.gmatch(temp_content, "%${([^}]+)}") do
  277. -- 忽略临时占位符
  278. if not placeholder:match("^__TEMP_BLOCK_%d+__$") then
  279. placeholders[placeholder] = true
  280. end
  281. end
  282. -- print("PLACEHOLDERS:",cjson.encode(placeholders))
  283. -- 如果没有任何占位符,直接返回
  284. if next(placeholders) == nil then
  285. return false
  286. end
  287. -- 合并所有数据源到一个查找表中(只 flatten 一次)
  288. local value_map = {}
  289. -- PUBLIC
  290. local flat_kvs = flatten_kvs(M.template_engine_bc_this_role["template"]["public"])
  291. for k, v in pairs(flat_kvs) do
  292. value_map[k] = v
  293. end
  294. -- PRIVATE
  295. flat_kvs = flatten_kvs(M.template_engine_bc_this_role["template"]["private"])
  296. for k, v in pairs(flat_kvs) do
  297. value_map[k] = v
  298. end
  299. -- REQUEST
  300. flat_kvs = flatten_kvs(request.gets(), "request")
  301. for k, v in pairs(flat_kvs) do
  302. value_map[k] = v
  303. end
  304. -- TOKEN
  305. flat_kvs = flatten_kvs(M.cfg.user_data(), "token")
  306. for k, v in pairs(flat_kvs) do
  307. value_map[k] = v
  308. end
  309. -- 转义函数:将 Lua 模式特殊字符转义为字面匹配
  310. local function escape_pattern(str)
  311. return str:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
  312. end
  313. -- 第三步:先处理 ${<<<...>>>} 块内部的占位符
  314. local replaced = false
  315. for i, block in ipairs(blocks) do
  316. local block_content = block.content
  317. for placeholder, _ in pairs(placeholders) do
  318. if value_map[placeholder] ~= nil then
  319. local escaped_placeholder = escape_pattern(placeholder)
  320. local new_content, count = string.gsub(block_content, "%${" .. escaped_placeholder .. "}", tostring(value_map[placeholder]))
  321. if count > 0 then
  322. block_content = new_content
  323. replaced = true
  324. end
  325. end
  326. end
  327. blocks[i].processed_content = block_content
  328. end
  329. -- 第四步:替换外部的 ${...} 占位符(在临时内容中,此时块已被替换为临时占位符)
  330. local n = 0
  331. for placeholder, _ in pairs(placeholders) do
  332. if value_map[placeholder] ~= nil then
  333. local escaped_placeholder = escape_pattern(placeholder)
  334. temp_content, n = string.gsub(temp_content, "%${" .. escaped_placeholder .. "}", tostring(value_map[placeholder]))
  335. if n > 0 then
  336. replaced = true
  337. end
  338. end
  339. end
  340. -- 第五步:恢复 ${<<<...>>>} 块(使用处理后的内容)
  341. for i, block in ipairs(blocks) do
  342. local processed_block = "${<<<" .. blocks[i].processed_content .. ">>>}"
  343. temp_content = temp_content:gsub(block.placeholder:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1"), processed_block)
  344. end
  345. content = temp_content
  346. if replaced then
  347. return true, content
  348. end
  349. return false
  350. end
  351. -- 检查内容中是否有TOKEN变量
  352. -- @return boolean
  353. M.hasToken = function()
  354. if M.static_content == nil or M.static_content == "" then
  355. return false
  356. end
  357. return M.static_content:match("%${token%.") ~= nil
  358. end
  359. return M