GoWind CMS has a built-in Lua script engine that supports dynamically extending business logic through scripts, with no compilation or deployment required. This tutorial explains the application scenarios, API interfaces, and practical cases of Lua scripts in the CMS.
| Comparison | Admin | CMS |
|---|
| Event types | Users/roles/system | Content/comments/translations/media |
| API extension | System administration | Content management + frontend API |
| Scheduled tasks | System maintenance | Content sync + data analysis |
eventbus.subscribe("post.published", function(event)
local post_id = event.PostId
local title = event.Title
local author = event.AuthorName
local post = api.call("GET", "/admin/v1/posts/" .. post_id)
local summary = http.post("https://api.ai-service.com/summarize", {
headers = { ["Authorization"] = "Bearer your-key" },
body = json.encode({ text = post.content })
})
api.call("PUT", "/admin/v1/posts/" .. post_id, {
data = { summary = summary }
})
log.info("已自动生成摘要: postId=" .. post_id)
end)
eventbus.subscribe("comment.created", function(event)
local content = event.Content
local comment_id = event.CommentId
local sensitive_words = {"广告", "垃圾", "恶意"}
for _, word in ipairs(sensitive_words) do
if string.find(content, word) then
api.call("PUT", "/admin/v1/comments/" .. comment_id .. "/reject", {
data = { reason = "包含敏感内容: " .. word }
})
log.warn("评论被自动拒绝: commentId=" .. comment_id)
return
end
end
api.call("PUT", "/admin/v1/comments/" .. comment_id .. "/approve")
end)
api.register("GET", "/app/v1/posts/popular", function(ctx)
local limit = tonumber(ctx.query.limit) or 10
local days = tonumber(ctx.query.days) or 7
local start_time = os.time() - (days * 24 * 3600)
local posts = db.query([[
SELECT p.id, p.title, p.slug, COUNT(v.id) as view_count
FROM posts p
JOIN post_views v ON v.post_id = p.id
WHERE p.status = 1 AND v.created_at >= ?
GROUP BY p.id
ORDER BY view_count DESC
LIMIT ?
]], start_time, limit)
return json.encode({ items = posts })
end)
api.register("GET", "/admin/v1/dashboard/stats", function(ctx)
local stats = {
post_count = db.scalar("SELECT COUNT(*) FROM posts WHERE status = 1"),
comment_count = db.scalar("SELECT COUNT(*) FROM comments WHERE status = 1"),
user_count = db.scalar("SELECT COUNT(*) FROM users WHERE status = 1"),
today_views = db.scalar([[
SELECT COUNT(*) FROM post_views
WHERE created_at >= CURRENT_DATE
]]),
}
return json.encode(stats)
end)
cron.register("0 2 * * *", function()
log.info("开始生成内容热力图...")
local daily_stats = db.query([[
SELECT DATE(created_at) as date, COUNT(*) as count
FROM posts
WHERE status = 1 AND created_at >= NOW() - INTERVAL '30 days'
GROUP BY DATE(created_at)
ORDER BY date
]])
redis.set("stats:content_heatmap", json.encode(daily_stats))
log.info("热力图数据已更新")
end)
cron.register("0 3 1 * *", function()
local threshold = os.time() - (90 * 24 * 3600)
local expired = db.query([[
SELECT id FROM posts
WHERE status = 2 AND updated_at < ?
]], threshold)
for _, post in ipairs(expired) do
db.execute("INSERT INTO posts_archive SELECT * FROM posts WHERE id = ?", post.id)
db.execute("DELETE FROM posts WHERE id = ?", post.id)
log.info("已归档文章: " .. post.id)
end
end)
eventbus.subscribe("post.created", function(event)
local post_id = event.PostId
local post = api.call("GET", "/admin/v1/posts/" .. post_id)
if not post or not post.content then return end
if post.content_format == "markdown" then
local html = http.post("https://api.markdown.com/render", {
body = json.encode({ markdown = post.content })
})
api.call("PUT", "/admin/v1/posts/" .. post_id, {
data = {
content_html = html,
content_format = "html"
}
})
end
end)
eventbus.subscribe("post.published", function(event)
local post = api.call("GET", "/admin/v1/posts/" .. event.PostId)
if not post.sections then return end
for _, section in ipairs(post.sections) do
if section.type == "SECTION_TYPE_IMAGE" then
local url = section.data.image.url
local file_info = oss.stat(url)
if file_info.size > 2 * 1024 * 1024 then
local compressed_url = image.compress(url, {
quality = 85,
maxWidth = 1920,
format = "webp"
})
section.data.image.url = compressed_url
log.info("已压缩图片: " .. url .. " → " .. compressed_url)
end
end
end
api.call("PUT", "/admin/v1/posts/" .. event.PostId, {
data = { sections = post.sections }
})
end)
| Object | Description |
|---|
api | CMS API calls |
db | Database queries |
redis | Redis operations |
http | HTTP client |
oss | Object storage operations |
image | Image processing |
log | Logging |
json | JSON encoding/decoding |
eventbus | Event subscription |
cron | Scheduled task registration |
os | System functions |
string | String operations |
api.call(method, path, options)
api.register(method, path, handler)
db.query(sql, params)
db.scalar(sql, params)
db.execute(sql, params)
redis.get(key)
redis.set(key, value, ttl)
redis.del(key)
redis.incr(key)
http.get(url, options)
http.post(url, options)
log.info(msg)
log.warn(msg)
log.error(msg)
| Check Item | Description |
|---|
| Lua engine initialization | Scripts loaded at startup |
| Event subscription | Content/comment event handling |
| Custom APIs | Route registration |
| Scheduled tasks | cron registration |
| Security sandbox | Dangerous operations restricted |
| Error recovery | Panics do not affect the main flow |