bugfix1001.2

星引擎Party已发行!
欢迎来到Star Engine 星引擎 WIKI
点击成为魔法少女!

全站通知:

模块:角色一览表

来自星引擎WIKI_BWIKI_哔哩哔哩
跳到导航 跳到搜索

此模块的文档可以在模块:角色一览表/doc创建

local p = {}

-- ===================== 配置常量(统一命名风格+注释) =====================
local CONST = {
    -- 图片相关配置
    IMG_BASE = "File:UT_AccountHeadShot_",
    IMG_SIZE = "30px",
    IMG_PREFIX_DEFAULT = "709",    -- 默认图片前缀
    IMG_PREFIX_SPECIAL = "709",   -- 联动角色(ID以3开头)特殊前缀
    -- 数据分隔符
    DATA_SEPARATOR = ";",
    FIELD_SEPARATOR = "|",
    -- 时期排序优先级(数字越小越靠前)
    PERIOD_ORDER = {
        ["第一期"] = 1,
        ["第二期"] = 2,
        ["第三期"] = 3,
        ["第四期"] = 4,
        ["联动"] = 98,
        ["其他"] = 99
    },
    -- 缓存配置
    CACHE_EXPIRE = 86400,         -- 缓存过期时间(24小时)
    CACHE_VERSION = "v2.1",         -- 缓存版本号(修改逻辑时更新)
    LOCK_EXPIRE = 10,             -- 缓存锁过期时间(10秒)
    -- 文本配置(便于多语言/样式修改)
    TEXT_EMPTY_DATA = "<div class='no-role-data'>暂无角色数据</div>",
    TEXT_LOADING = "<div class='role-table-loading'>数据加载中...</div>",
    TEXT_CACHE_CLEARED = "<div style='color: green;'>角色表格缓存已清除,页面将加载最新数据!</div>",
    TEXT_CACHE_UNAVAILABLE = "<div style='color: red;'>LuaCache扩展未启用,无需清除缓存!</div>"
}

-- ===================== 工具函数(优化+容错) =====================
-- 轻量级字符串拆分(纯字符串查找,无正则开销,过滤空结果)
local function splitStr(str, sep)
    str = tostring(str) -- 确保输入是字符串
    sep = tostring(sep)
    local res = {}
    local start = 1
    while true do
        local pos = string.find(str, sep, start, true)
        if not pos then
            local part = string.sub(str, start)
            if part ~= "" then -- 过滤空元素
                table.insert(res, part)
            end
            break
        end
        local part = string.sub(str, start, pos - 1)
        if part ~= "" then -- 过滤空元素
            table.insert(res, part)
        end
        start = pos + #sep
    end
    return res
end

-- HTML转义函数(增强容错)
local function escapeHtml(str)
    if type(str) ~= "string" then
        str = tostring(str or "")
    end
    str = string.gsub(str, "&", "&amp;")
    str = string.gsub(str, "<", "&lt;")
    str = string.gsub(str, ">", "&gt;")
    str = string.gsub(str, '"', "&quot;")
    str = string.gsub(str, "'", "&#39;")
    return str
end

-- 简单哈希函数(降低冲突概率+明确空值处理)
local HASH_SEED = 5381
local function simpleHash(str)
    local hash = HASH_SEED
    if type(str) ~= "string" then
        str = tostring(str or "empty")
    end
    for i = 1, #str do
        hash = (hash * 33 + string.byte(str, i)) % 2^32 -- 33代替31,分布更均匀
    end
    return tostring(hash)
end

local function getLastTwoDigits(id)
    -- 先转为字符串,避免数字类型问题
    local idStr = tostring(id)
    -- 检查是否为纯数字
    if tonumber(idStr) then
        -- 补零后取最后两位(如 "5" → "05" → "05";"101" → "101" → "01")
        return string.format("%02d", tonumber(idStr)):sub(-2)
    else
        -- 非数字ID返回原字符串(或按需改为 ""/"--" 等)
        return idStr
    end
end
-- ===================== 核心业务函数 =====================
function p.main(frame)
    -- 1. 参数初始化
    local data = frame.args.data or ""
    data = tostring(data)
    local cacheKey = "role_table_" .. CONST.CACHE_VERSION .. "_" .. simpleHash(data)
    local lockKey = cacheKey .. "_lock"
    local hasLock = false -- 原子锁标记,提前定义

    -- 2. 优先读缓存(增加方法存在性判断,避免nil调用)
    local cachedHtml
    if mw.ext and mw.ext.LuaCache and type(mw.ext.LuaCache.get) == "function" then
        cachedHtml = mw.ext.LuaCache.get(cacheKey)
    end
    if cachedHtml then
        return cachedHtml
    end

    -- 3. 兼容式加锁(优先原子add,降级为get+set,增加容错)
    if mw.ext and mw.ext.LuaCache then
        -- 3.1 优先原子add(若存在)
        if type(mw.ext.LuaCache.add) == "function" then
            local ok = mw.ext.LuaCache.add(lockKey, "1", CONST.LOCK_EXPIRE)
            if ok then
                hasLock = true
            else
                return CONST.TEXT_LOADING
            end
        -- 3.2 降级为get+set(无add方法时)
        else
            local lockVal = mw.ext.LuaCache.get(lockKey)
            if not lockVal then
                mw.ext.LuaCache.set(lockKey, "1", CONST.LOCK_EXPIRE)
                hasLock = true
            else
                return CONST.TEXT_LOADING
            end
        end
    end

      -- 4. 数据解析(修复边界漏洞+兼容Lua 5.1,去掉goto)
      local roles = {}
      local roleStrs = splitStr(data, CONST.DATA_SEPARATOR)
      
      for _, roleStr in ipairs(roleStrs) do
          -- 首尾去空格,跳过空条目(用if包裹,无需goto)
          roleStr = roleStr:gsub("^%s+", ""):gsub("%s+$", "")
          if roleStr ~= "" then
              -- 拆分字段并清洗
              local fields = splitStr(roleStr, CONST.FIELD_SEPARATOR)
              local name = escapeHtml(fields[1] or "")
              local id = escapeHtml(fields[2] or "")
              local period = escapeHtml((fields[3] or "其他"):gsub("^%s+", ""):gsub("%s+$", ""))
              
              -- 过滤空名称
              if name ~= "" then
                  -- ========== 新增:处理非联动角色ID ==========
                  local processedId = id -- 默认保留原ID
                  if period ~= "联动" then -- 非联动角色
                      processedId = getLastTwoDigits(id)
                  end
                  
                  -- ID数字转换(用于排序,保留原逻辑)
                  -- 注意:排序用原始ID的数字值,避免处理后的两位ID影响排序
                  local idNum = tonumber(id) or math.huge
                  
                  -- 按时期分组(存储处理后的ID)
                  if not roles[period] then
                      roles[period] = {}
                  end
                  table.insert(roles[period], {
                      name = name,
                      id = processedId, -- 存储处理后的ID
                      originalId = id,  -- 可选:保留原始ID,备用
                      idNum = idNum     -- 排序用原始ID的数值
                  })
              end
          end
      end

    -- 5. 空数据兜底(增加方法存在性判断)
    if next(roles) == nil then
        local emptyHtml = CONST.TEXT_EMPTY_DATA
        if mw.ext and mw.ext.LuaCache and type(mw.ext.LuaCache.set) == "function" then
            mw.ext.LuaCache.set(cacheKey, emptyHtml, CONST.CACHE_EXPIRE)
            if hasLock and type(mw.ext.LuaCache.delete) == "function" then
                mw.ext.LuaCache.delete(lockKey)
            end
        end
        return emptyHtml
    end

    -- 6. 时期排序(优化哈希查询)
    local periods = {}
    for period in pairs(roles) do
        table.insert(periods, period)
    end
    
    -- 预缓存时期优先级,减少重复查询
    local periodPriorityCache = {}
    for _, period in ipairs(periods) do
        periodPriorityCache[period] = CONST.PERIOD_ORDER[period] or 999
    end
    
    -- 排序:先按优先级,再按名称
    table.sort(periods, function(a, b)
        local pA = periodPriorityCache[a] or 999
        local pB = periodPriorityCache[b] or 999
        if pA ~= pB then
            return pA < pB
        else
            return a < b
        end
    end)
    -- 7. HTML拼接(性能最优:预分配索引,无table.insert,修复所有逻辑错误)
    local htmlParts = {"<table class='role-table'>"}
    
    for _, period in ipairs(periods) do
        local roleList = roles[period]
        -- 按ID排序(非数字ID排最后)
        table.sort(roleList, function(a, b)
            return a.idNum < b.idNum
        end)
        
        -- 拼接时期标题
        table.insert(htmlParts, "<tr><th class='role-period-th'>" .. period .. "角色</th><td>")
        
        -- 批量拼接角色内容(预分配索引,避免table.insert的性能损耗)
        local roleHtmlParts = {}
        local roleCount = #roleList
        for i = 1, roleCount do
            local role = roleList[i]
            -- 图片拼接(容错+转义)
            local img = ""
            if role.id ~= "" then -- role.id 已是处理后的后两位(非联动)/完整ID(联动)
                local imgPrefix = CONST.IMG_PREFIX_DEFAULT
                -- 联动角色且ID以3开头,使用特殊前缀
                if period == "联动" and string.sub(role.originalId, 1, 1) == "3" then
                    imgPrefix = CONST.IMG_PREFIX_SPECIAL
                end
                -- 转义链接特殊字符
                local safeLinkName = mw.uri and mw.uri.encode(role.name, "WIKI") or role.name
                img = string.format("[[%s%s%s.png|%s|link=%s]]",
                    CONST.IMG_BASE, imgPrefix, role.id, CONST.IMG_SIZE, safeLinkName)
            end
            
            -- 角色链接
            local link = "[[" .. role.name .. "]]"
            
            -- 预分配索引(性能最优,无table.insert开销)
            local idx = 3 * (i - 1)
            roleHtmlParts[idx + 1] = img
            roleHtmlParts[idx + 2] = link
            roleHtmlParts[idx + 3] = " "
        end
        
        -- 合并角色内容并插入
        table.insert(htmlParts, table.concat(roleHtmlParts))
        table.insert(htmlParts, "</td></tr>")
    end -- 闭合period循环

    table.insert(htmlParts, "</table>")
    local finalHtml = table.concat(htmlParts)

    -- 8. 写入缓存并释放锁(全链路容错,仅自己加的锁才释放)
    if mw.ext and mw.ext.LuaCache then
        if type(mw.ext.LuaCache.set) == "function" then
            mw.ext.LuaCache.set(cacheKey, finalHtml, CONST.CACHE_EXPIRE)
        end
        if hasLock and type(mw.ext.LuaCache.delete) == "function" then
            mw.ext.LuaCache.delete(lockKey)
        end
    end
    
    return finalHtml
end

-- ===================== 缓存清理函数 =====================
function p.clearCache(frame)
    local data = frame.args.data or ""
    local cacheKey = "role_table_" .. CONST.CACHE_VERSION .. "_" .. simpleHash(data)
    local lockKey = cacheKey .. "_lock"
    
    if mw.ext and mw.ext.LuaCache and type(mw.ext.LuaCache.delete) == "function" then
        mw.ext.LuaCache.delete(cacheKey)
        mw.ext.LuaCache.delete(lockKey) -- 同时清理锁
        return CONST.TEXT_CACHE_CLEARED
    end
    return CONST.TEXT_CACHE_UNAVAILABLE
end

return p