require 根据工程目录的相对路径-require新文件实现简单的热更新

设计亮点:使用 paths 统一管理路径,避免直接写 ../ 等相对路径,提升可维护性。

新增文件 paths.js

复制代码
// src/paths.js
const path = require('path');
const { common } = require('protobufjs');
// 工程根目录:src/ 的上级目录(your-project/)
const projectRoot = path.resolve(__dirname, '../'); 
module.exports = {
    root: projectRoot,
    src: path.resolve(projectRoot, 'src'),
    common: path.resolve(projectRoot, 'src/common'),
    data: path.resolve(projectRoot, 'src/data'),
    html: path.resolve(projectRoot, 'src/html'),
    lib: path.resolve(projectRoot, 'src/lib'),
    logic: path.resolve(projectRoot, 'src/logic'),
    logicHot: path.resolve(projectRoot, 'src/logicHot'),
    manager: path.resolve(projectRoot, 'src/manager'),
};

这段代码是 Node.js 服务器的命令处理器模块,主要用于游戏 / 服务端场景中,将客户端 / 外部发送的命令(CMD)映射到对应的业务逻辑处理函数,并实现了定时热修复(HotFix)机制,核心目标是:

命令 - 逻辑解耦:通过映射表关联命令常量与业务处理函数,便于扩展和维护;

动态热更新:定时检测热修复目录的文件,无需重启服务即可替换业务逻辑,快速修复线上问题;

模块化拆分:将业务逻辑拆分为多个独立模块(登录、道具、战斗等),降低耦合。

commandManager.js

复制代码
const path = require('path');
const paths = require('../paths'); 
const fs = require('fs').promises;
const gCmd = require(path.join(paths.common, 'constant')).gCmd
const log2file = require(path.join(paths.common, 'log2file'))

const logicObject={
    ["loginLogic"]:require(path.join(paths.logic, 'loginLogic')),
    ["itemLogic"]:require(path.join(paths.logic, 'itemLogic')),
    ["battleLogic"]:require(path.join(paths.logic, 'battleLogic')),
    ["accountLogic"]:require(path.join(paths.logic, 'accountLogic')),
    ["mailLogic"]:require(path.join(paths.logic, 'mailLogic')),
    ["rewardLogic"]:require(path.join(paths.logic, 'rewardLogic')),
}

const commandHandlers = {
    [gCmd.CMD_SERVER_REGISTER_LOGIN]: (client, data, obj, clientObject) => logicObject["loginLogic"].loginHandler1(client, data, obj, clientObject),

    [gCmd.CMD_SERVER_USE_ITEM]: (client, data, obj) => logicObject["itemLogic"].itemHandler1(client, data, obj),
    
    [gCmd.CMD_SERVER_BATTLE_BEGIN]: (client, data, obj) => logicObject["battleLogic"].battleHandler1(client, data, obj),
    [gCmd.CMD_SERVER_BATTLE_USE_ITEM]: (client, data, obj) => logicObject["battleLogic"].battleHandler2(client, data, obj),
    [gCmd.CMD_SERVER_BATTLE_DROP_ITEM]: (client, data, obj) => logicObject["battleLogic"].battleHandler3(client, data, obj),
    [gCmd.CMD_SERVER_BATTLE_ADD_ITEM]: (client, data, obj) => logicObject["battleLogic"].battleHandler4(client, data, obj),

    [gCmd.CMD_SERVER_BIND_ACCOUNT]: (client, data, obj) => logicObject["accountLogic"].accountHandler1(client, data, obj),
    [gCmd.CMD_SERVER_UNBIND_ACCOUNT]: (client, data, obj) => logicObject["accountLogic"].accountHandler2(client, data, obj),

    // [gCmd.CMD_SERVER_BATTLE_UPDATE_ENDTIME]: (client, data, obj) => logicObject["battleLogic"].battleHandler5(client, data, obj),
    [gCmd.CMD_SERVER_BATTLE_SET_RESULT]: (client, data, obj) => logicObject["battleLogic"].battleHandler6(client, data, obj),
    [gCmd.CMD_SERVER_BATTLE_RECEIVE_REWARD]: (client, data, obj) => logicObject["battleLogic"].battleHandler7(client, data, obj),
    [gCmd.CMD_SERVER_BATTLE_RECEIVE_REWARD_2]: (client, data, obj) => logicObject["battleLogic"].battleHandler8(client, data, obj),
    [gCmd.CMD_SERVER_BATTLE_DROP_GOLD]: (client, data, obj) => logicObject["battleLogic"].battleHandler9(client, data, obj),

    [gCmd.CMD_SERVER_MAIL_READ]: (client, data, obj) => logicObject["mailLogic"].mailHandler1(client, data, obj),
    [gCmd.CMD_SERVER_MAIL_RECEIVE]: (client, data, obj) => logicObject["mailLogic"].mailHandler2(client, data, obj),
    [gCmd.CMD_SERVER_MAIL_DELETE]: (client, data, obj) => logicObject["mailLogic"].mailHandler3(client, data, obj),
    [gCmd.CMD_SERVER_MODIFY_NICKNAME]: (client, data, obj) => logicObject["accountLogic"].accountHandler3(client, data, obj),
    [gCmd.CMD_SERVER_MODIFY_SIGNATURE]: (client, data, obj) => logicObject["accountLogic"].accountHandler4(client, data, obj),
    [gCmd.CMD_SERVER_BASE_SETTING]: (client, data, obj) => logicObject["accountLogic"].accountHandler5(client, data, obj),
    [gCmd.CMD_SERVER_REDEMPTION_CODE]: (client, data, obj) => logicObject["rewardLogic"].rewardHandler1(client, data, obj),
};

async function checkFileExists(filePath) {
    try {
        await fs.access(filePath);
        return true;
    } catch {
        return false;
    }
}

let isChecking = false;
async function checkHotFix() {
    if (isChecking) {
        log2file.logger.warn('上一次热修复检测未完成,跳过本次');
        return;
    }
    isChecking = true;
    try {
        for(let key in logicObject){
            const fileInfo='../logicHot/'+key
            const filePath = path.join(__dirname, fileInfo+".js");
            let isExist=await checkFileExists(filePath)
            if(isExist){
                logicObject[key] = require(path.join(paths.logicHot, key))
                log2file.logger.info('checkHotFix filePath=', filePath);
            }
        }
    } finally {
        isChecking = false; // 无论成功失败,重置标志位
    }
}

// 启动定时器
setInterval(checkHotFix, 60 * 1000);

module.exports = {
    commandHandlers: commandHandlers,
};

核心作用:定时检测 logicHot 目录下的热修复文件,若存在则动态替换 logicObject 中的对应模块,实现不重启服务的热更新;

设计亮点:使用异步文件检查(fs.promises),避免阻塞 Node.js 事件循环。

相关推荐
Flynt4 分钟前
我把热榜那篇"禁用 axios"逐条实测了一遍:结论大体对,坑在它没讲的地方
前端·javascript·axios
Csvn39 分钟前
bundle优化
前端
slongzhang_1 小时前
linux 递归删除源码文件|递归删除目录下 *.js,但排除 *.min.js
linux·开发语言·javascript
北冥有鱼1 小时前
发版后动态路由点击报 `Failed to fetch dynamically imported module`」问题上的完整排查与修复过程
前端·vue.js
静默回滚1 小时前
视频压到 25 MB 以内,码率要算、音轨要扣、余量要留
前端
叼烟扛炮1 小时前
Linux 学习笔记(三十三):线程互斥——互斥锁(Mutex)与死锁
linux·运维·服务器·笔记·学习·1024程序员节
用户37203749260181 小时前
把 10MB 中文语义模型塞进浏览器:STSB 做到教师模型的 93%,但线上 top1 只有 42%
前端
光锥智能1 小时前
海康机器人举行2026移动机器人新品发布会:箱式存拣升级,「明智」系列发布
java·前端·机器人
计算机魔术师1 小时前
Anthropic 上市文件最吓人的一幕,不是亏损 807 亿
前端
无足鸟ICT1 小时前
【RHCA+】who命令(查看已登陆用户)
linux·运维·服务器