-- 服务端消息与后端HTTP API之间的转发模块
local DataForwarder = {}
DataForwarder.__cname = "DataForwarder"
DataForwarder.NAME = DataForwarder.__cname
-- 全局配置:后端API根地址与已注册的消息配置
local g_config = {
backend_endpoint = "",
registered_msg_configs = {},
}
--[[
初始化配置
config.backend_endpoint: 后端API根地址
config.msg_configs: 需要监听的消息配置列表
]]
function DataForwarder:init(config)
if config and config.backend_endpoint then
g_config.backend_endpoint = config.backend_endpoint
end
if config and config.msg_configs then
for _, msgConfig in ipairs(config.msg_configs) do
local msgID = msgConfig.id
local recvType = msgConfig.recv_type or msgConfig.type or "lua"
local sendType = msgConfig.send_type or msgConfig.type or "lua"
self:registerMsgListener(msgID, recvType, sendType)
end
end
end
--[[
注册消息监听器
msgID: 消息ID
recvType: 接收消息类型,"lua" 或 "txt"
sendType: 返回消息类型,"lua" 或 "txt"
]]
function DataForwarder:registerMsgListener(msgID, recvType, sendType)
recvType = recvType or "lua"
sendType = sendType or "lua"
if g_config.registered_msg_configs[msgID] then
return
end
-- 消息回调:解析引擎原始参数,标准化为 msgData 后交给 handleMessage
local function networkCB(msgID, ...)
local params = {...}
local msgData = {}
-- sendluamsg 格式:多个前置数值参数 + 末尾字符串载荷(可能为JSON)
if #params >= 4 then
local lastParam = params[#params]
if type(lastParam) == "string" and #lastParam > 0 then
-- 首字符为 { 或 [,尝试按JSON解码
if string.sub(lastParam, 1, 1) == "{" or string.sub(lastParam, 1, 1) == "[" then
local success, result = pcall(SL.JsonDecode, SL, lastParam)
if success and result then
msgData = result
msgData.format = "json"
msgData.params = {}
for j = 1, #params - 1 do
table.insert(msgData.params, params[j])
end
else
msgData = {
message = lastParam,
format = "sendluamsg",
params = {}
}
for j = 1, #params - 1 do
table.insert(msgData.params, params[j])
end
end
else
msgData = {
message = lastParam,
format = "sendluamsg",
params = {}
}
for j = 1, #params - 1 do
table.insert(msgData.params, params[j])
end
end
else
msgData = {
format = "unknown",
params = params
}
end
else
-- TXT格式:通常只传递单个参数
if #params == 1 then
local param = params[1]
if type(param) == "string" and #param > 0 and (string.sub(param, 1, 1) == "{" or string.sub(param, 1, 1) == "[") then
local success, result = pcall(SL.JsonDecode, SL, param)
if success and result then
msgData = result
msgData.format = "txt_json"
msgData.msgid = msgID
else
msgData = {
format = "txt_unknown",
params = params
}
end
elseif type(param) == "table" then
msgData = param
msgData.format = "txt_json"
msgData.msgid = msgID
else
msgData = {
format = "txt_unknown",
params = params
}
end
else
msgData = {
format = "unknown",
params = params
}
end
end
if not msgData or not next(msgData) then
msgData = {
format = "unknown",
params = params
}
end
self:handleMessage(msgID, msgData)
end
if recvType == "txt" then
SL:RegisterNetMsg(msgID, networkCB)
else
SL:RegisterLuaNetMsg(msgID, networkCB)
end
g_config.registered_msg_configs[msgID] = {recv_type = recvType, send_type = sendType}
end
--[[
处理服务端消息:校验数据并规范化HTTP方法后转发
msgID: 消息ID
msgData: 解析后的消息数据
]]
function DataForwarder:handleMessage(msgID, msgData)
if not msgData or type(msgData) ~= "table" then
return
end
local method
if msgData.method then
method = string.upper(tostring(msgData.method))
local supportedMethods = {"GET", "POST", "PUT", "DELETE", "PATCH"}
local isStandardMethod = false
for _, supportedMethod in ipairs(supportedMethods) do
if method == supportedMethod then
isStandardMethod = true
break
end
end
if not isStandardMethod then
method = "POST"
end
else
method = "POST"
end
self:forwardDataToBackend(msgData, msgID, method)
end
--[[
转发数据到后端API(Django DRF 风格路由)
data: 消息数据,支持 resource/action/pk/method/token 等字段
msgID: 消息ID
method: HTTP方法,默认POST
]]
function DataForwarder:forwardDataToBackend(data, msgID, method)
if not data or type(data) ~= "table" then
return
end
if type(data.backend_endpoint) == "string" and data.backend_endpoint ~= "" then
local endpoint = data.backend_endpoint
if string.sub(endpoint, -1) ~= "/" then
endpoint = endpoint .. "/"
end
g_config.backend_endpoint = endpoint
data.backend_endpoint = nil
end
if not g_config.backend_endpoint or g_config.backend_endpoint == "" then
return
end
local method = method or data.method or "POST"
method = string.upper(method)
local token = data.token or data.Token or (data.data and data.data.token) or (data.data and data.data.Token)
local headers = {
["Content-Type"] = "application/json",
}
-- 引擎仅提供GET/POST接口,其余方法通过覆盖头标识
if method ~= "GET" and method ~= "POST" then
headers["X-HTTP-Method-Override"] = method
end
local url = g_config.backend_endpoint
local postDataString = nil
if data.resource then
url = url .. data.resource .. "/"
end
if data.action then
url = url .. data.action .. "/"
end
-- 详情语义(非POST):pk 作为路径参数拼入 URL
if method == "GET" or method == "PUT" or method == "DELETE" or method == "PATCH" then
if data.pk then
url = url .. data.pk .. "/"
end
end
-- GET 无法携带请求头,token 走查询参数;其余方法放入 Authorization 头
if token then
if method == "GET" then
local tokenParam = "token=" .. token
if string.find(url, "?") then
url = url .. "&" .. tokenParam
else
url = url .. "?" .. tokenParam
end
else
headers["Authorization"] = "Bearer " .. token
end
end
if method == "GET" then
if not data.pk then
-- URL编码(支持UTF-8中文字符)
local function urlEncode(str)
if type(str) ~= "string" then
return tostring(str)
end
local result = string.gsub(str, "([^%w _%-%.~])", function(c)
local bytes = {string.byte(c, 1, -1)}
local encoded = ""
for _, byte in ipairs(bytes) do
encoded = encoded .. string.format("%%%02X", byte)
end
return encoded
end)
result = string.gsub(result, " ", "+")
return result
end
local params = {}
for k, v in pairs(data) do
if k ~= "resource" and k ~= "method" and k ~= "filter" and k ~= "ordering" and k ~= "page" and k ~= "pagesize" and k ~= "data" and k ~= "format" and k ~= "msgid" and k ~= "token" and k ~= "params" then
table.insert(params, tostring(k) .. "=" .. urlEncode(tostring(v)))
end
end
if data.filter and type(data.filter) == "table" then
for field, value in pairs(data.filter) do
table.insert(params, "filter[" .. tostring(field) .. "]=" .. urlEncode(tostring(value)))
end
end
if data.ordering then
table.insert(params, "ordering=" .. urlEncode(tostring(data.ordering)))
end
if data.page then
table.insert(params, "page=" .. urlEncode(tostring(data.page)))
end
if data.pagesize then
table.insert(params, "pagesize=" .. urlEncode(tostring(data.pagesize)))
end
if #params > 0 then
if string.find(url, "?") then
url = url .. "&" .. table.concat(params, "&")
else
url = url .. "?" .. table.concat(params, "&")
end
end
end
else
postDataString = SL:JsonEncode(data)
end
local function httpsCB(success, response)
local responseData
local returnData
if success then
responseData = response and SL:JsonDecode(response) or nil
else
local error_msg = "连接服务器失败"
if response then
if string.find(response, "timeout") then
error_msg = "请求超时,请检查网络连接"
elseif string.find(response, "refused") then
error_msg = "服务器拒绝连接,请稍后再试"
elseif string.find(response, "dns") or string.find(response, "lookup") then
error_msg = "DNS解析失败,请检查网络设置"
elseif string.find(response, "certificate") or string.find(response, "ssl") then
error_msg = "SSL证书错误,连接被拒绝"
else
error_msg = "网络错误:" .. tostring(response)
end
end
responseData = {
success = false,
msg = error_msg,
action = data.action,
token = data.token
}
end
if not responseData then
responseData = {
success = false,
msg = "响应解析失败",
action = data.action,
token = data.token
}
end
-- 标准化响应:补充 action,将 success 映射为 status
if not responseData.action and data.action then
responseData.action = data.action
end
if responseData.status == nil then
responseData.status = (responseData.success == true) and "success" or "error"
end
returnData = SL:JsonEncode(responseData)
-- 按注册的发送类型将响应回传给服务端
local msgConfig = g_config.registered_msg_configs[msgID]
if msgConfig then
if msgConfig.send_type == "txt" then
SL:SendNetMsg(msgID, 1, 2, 3, returnData)
else
SL:SendLuaNetMsg(msgID, 1, 2, 3, returnData)
end
end
end
if method == "GET" then
SL:HTTPRequestGet(url, httpsCB)
elseif method == "POST" then
SL:HTTPRequestPost(url, httpsCB, postDataString, headers, true)
elseif method == "PUT" then
SL:HTTPRequestPost(url, httpsCB, postDataString, headers, true)
elseif method == "DELETE" then
SL:HTTPRequestPost(url, httpsCB, postDataString, headers, true)
else
SL:HTTPRequestPost(url, httpsCB, postDataString, headers, true)
end
end
return DataForwarder