Node.js 路径与 URL 模块

章节介绍

在开发 Web 应用时,经常需要处理文件路径和 URL。Node.js 提供了 path 和 url 模块来简化这些操作。path 模块用于处理文件系统路径,url 模块用于处理和解析 URL。本教程将详细介绍这两个模块的使用方法。

核心知识点

path 模块概述

path 模块提供了一系列用于处理和转换文件路径的工具方法。

path 模块功能:

┌─────────────────────────────────────┐
│           path 模块                  │
├─────────────────────────────────────┤
│  路径拼接:path.join()              │
│  路径解析:path.parse()             │
│  路径规范化:path.normalize()       │
│  路径解析为绝对:path.resolve()     │
│  相对路径:path.relative()          │
│  路径信息:basename, dirname, ext   │
└─────────────────────────────────────┘

路径拼接

const path = require('path');

// 基本路径拼接
const joinedPath = path.join('folder', 'subfolder', 'file.txt');
console.log('拼接路径:', joinedPath);
// 输出:folder/subfolder/file.txt (Unix) 或 folder\subfolder\file.txt (Windows)

// 使用多个参数
const multiJoin = path.join('folder', 'subfolder', 'file.txt', '..', 'other.txt');
console.log('多参数拼接:', multiJoin);
// 输出:folder/subfolder/other.txt

// 处理绝对路径
const absoluteJoin = path.join('/absolute', 'path', 'file.txt');
console.log('绝对路径拼接:', absoluteJoin);
// 输出:/absolute/path/file.txt

// 处理相对路径
const relativeJoin = path.join('..', 'folder', 'file.txt');
console.log('相对路径拼接:', relativeJoin);
// 输出:../folder/file.txt

// 实际应用:构建文件路径
const __filename = '/path/to/project/app.js';
const __dirname = path.dirname(__filename);

const configPath = path.join(__dirname, 'config', 'settings.json');
const dataPath = path.join(__dirname, 'data', 'users.json');
const logPath = path.join(__dirname, 'logs', 'app.log');

console.log('配置文件路径:', configPath);
console.log('数据文件路径:', dataPath);
console.log('日志文件路径:', logPath);

路径解析

const path = require('path');

// 解析路径
const parsedPath = path.parse('/path/to/file.txt');
console.log('解析路径:', parsedPath);
// 输出:
// {
//   root: '/',
//   dir: '/path/to',
//   base: 'file.txt',
//   ext: '.txt',
//   name: 'file'
// }

// 获取路径的各个部分
const filePath = '/path/to/file.txt';

console.log('根目录:', path.parse(filePath).root);        // /
console.log('目录:', path.parse(filePath).dir);          // /path/to
console.log('文件名:', path.parse(filePath).base);       // file.txt
console.log('扩展名:', path.parse(filePath).ext);        // .txt
console.log '文件名(不含扩展名):', path.parse(filePath).name); // file

// 使用便捷方法
console.log('目录名:', path.dirname(filePath));          // /path/to
console.log('文件名:', path.basename(filePath));         // file.txt
console.log('扩展名:', path.extname(filePath));          // .txt
console.log('文件名(不含扩展名):', path.basename(filePath, path.extname(filePath))); // file

// 解析 Windows 路径
const windowsPath = 'C:\\Users\\user\\file.txt';
const parsedWindows = path.parse(windowsPath);
console.log('Windows 路径解析:', parsedWindows);
// 输出:
// {
//   root: 'C:\\',
//   dir: 'C:\\Users\\user',
//   base: 'file.txt',
//   ext: '.txt',
//   name: 'file'
// }

路径规范化

const path = require('path');

// 规范化路径
const normalized1 = path.normalize('/path/to/../to/./file.txt');
console.log('规范化路径 1:', normalized1);
// 输出:/path/to/file.txt

const normalized2 = path.normalize('./folder/subfolder/../file.txt');
console.log('规范化路径 2:', normalized2);
// 输出:folder/file.txt

const normalized3 = path.normalize('/path/to/../../file.txt');
console.log('规范化路径 3:', normalized3);
// 输出:/file.txt

// 处理多余的斜杠
const normalized4 = path.normalize('/path//to///file.txt');
console.log('规范化路径 4:', normalized4);
// 输出:/path/to/file.txt

// 处理结尾的斜杠
const normalized5 = path.normalize('/path/to/folder/');
console.log('规范化路径 5:', normalized5);
// 输出:/path/to/folder

// 实际应用:清理用户输入的路径
function cleanPath(inputPath) {
  return path.normalize(inputPath);
}

console.log('清理路径:');
console.log(cleanPath('./folder/../file.txt'));      // file.txt
console.log(cleanPath('/path/to/./file.txt'));       // /path/to/file.txt
console.log(cleanPath('folder//subfolder/file.txt')); // folder/subfolder/file.txt

绝对路径和相对路径

const path = require('path');

// 解析为绝对路径
const absolute1 = path.resolve('folder/file.txt');
console.log('绝对路径 1:', absolute1);
// 输出:/current/working/directory/folder/file.txt

const absolute2 = path.resolve('/path/to', '../folder', 'file.txt');
console.log('绝对路径 2:', absolute2);
// 输出:/path/folder/file.txt

const absolute3 = path.resolve('folder', 'subfolder', '..', 'file.txt');
console.log('绝对路径 3:', absolute3);
// 输出:/current/working/directory/folder/file.txt

// 计算相对路径
const relative1 = path.relative('/path/to/file1.txt', '/path/to/file2.txt');
console.log('相对路径 1:', relative1);
// 输出:file2.txt

const relative2 = path.relative('/path/to/file1.txt', '/path/to/subfolder/file2.txt');
console.log('相对路径 2:', relative2);
// 输出:subfolder/file2.txt

const relative3 = path.relative('/path/to/subfolder/file1.txt', '/path/to/file2.txt');
console.log('相对路径 3:', relative3);
// 输出:../file2.txt

// 实际应用:计算两个文件之间的相对路径
function getRelativePath(from, to) {
  return path.relative(from, to);
}

console.log('相对路径计算:');
console.log(getRelativePath('/project/src/app.js', '/project/src/utils/helper.js'));
// 输出:utils/helper.js

console.log(getRelativePath('/project/src/app.js', '/project/config/settings.json'));
// 输出:../config/settings.json

路径信息获取

const path = require('path');

// 获取文件名
const basename1 = path.basename('/path/to/file.txt');
console.log('文件名 1:', basename1);
// 输出:file.txt

const basename2 = path.basename('/path/to/file.txt', '.txt');
console.log('文件名 2(不含扩展名):', basename2);
// 输出:file

// 获取目录名
const dirname1 = path.dirname('/path/to/file.txt');
console.log('目录名 1:', dirname1);
// 输出:/path/to

const dirname2 = path.dirname('/path/to/folder/');
console.log('目录名 2:', dirname2);
// 输出:/path/to

// 获取扩展名
const extname1 = path.extname('/path/to/file.txt');
console.log('扩展名 1:', extname1);
// 输出:.txt

const extname2 = path.extname('/path/to/file');
console.log('扩展名 2:', extname2);
// 输出:''

const extname3 = path.extname('/path/to/file.tar.gz');
console.log('扩展名 3:', extname3);
// 输出:.gz

// 实际应用:根据扩展名判断文件类型
function getFileType(filePath) {
  const ext = path.extname(filePath).toLowerCase();
  
  const types = {
    '.txt': '文本文件',
    '.js': 'JavaScript 文件',
    '.json': 'JSON 文件',
    '.html': 'HTML 文件',
    '.css': 'CSS 文件',
    '.jpg': '图片文件',
    '.png': '图片文件',
    '.pdf': 'PDF 文件'
  };
  
  return types[ext] || '未知文件类型';
}

console.log('文件类型判断:');
console.log(getFileType('/path/to/file.txt'));   // 文本文件
console.log(getFileType('/path/to/app.js'));    // JavaScript 文件
console.log(getFileType('/path/to/image.jpg')); // 图片文件

路径分隔符

const path = require('path');

// 获取路径分隔符
console.log('路径分隔符:', path.sep);
// 输出:\ (Windows) 或 / (Unix)

// 获取环境变量分隔符
console.log('环境变量分隔符:', path.delimiter);
// 输出:; (Windows) 或 : (Unix)

// 使用分隔符构建路径
const parts = ['folder', 'subfolder', 'file.txt'];
const joinedPath = parts.join(path.sep);
console.log('使用分隔符拼接:', joinedPath);
// 输出:folder\subfolder\file.txt (Windows) 或 folder/subfolder/file.txt (Unix)

// 解析环境变量路径
const envPath = process.env.PATH;
const pathParts = envPath.split(path.delimiter);
console.log('环境变量路径:', pathParts);

// 实际应用:跨平台路径处理
function buildPath(...parts) {
  return parts.join(path.sep);
}

console.log('跨平台路径构建:');
console.log(buildPath('folder', 'subfolder', 'file.txt'));
// 输出:folder\subfolder\file.txt (Windows) 或 folder/subfolder/file.txt (Unix)

url 模块概述

url 模块提供了一系列用于处理和解析 URL 的工具方法。

url 模块功能:

┌─────────────────────────────────────┐
│           url 模块                   │
├─────────────────────────────────────┤
│  URL 解析:url.parse()              │
│  URL 格式化:url.format()           │
│  URL 解析(新):new URL()          │
│  查询字符串:querystring 模块       │
└─────────────────────────────────────┘

URL 解析

const url = require('url');

// 解析 URL
const urlString = 'https://example.com:8080/path/to/resource?query=value#hash';
const parsedUrl = url.parse(urlString);

console.log('解析 URL:', parsedUrl);
// 输出:
// {
//   protocol: 'https:',
//   slashes: true,
//   auth: null,
//   host: 'example.com:8080',
//   port: '8080',
//   hostname: 'example.com',
//   hash: '#hash',
//   search: '?query=value',
//   query: 'query=value',
//   pathname: '/path/to/resource',
//   path: '/path/to/resource?query=value',
//   href: 'https://example.com:8080/path/to/resource?query=value#hash'
// }

// 解析查询字符串
const parsedWithQuery = url.parse(urlString, true);
console.log('解析查询字符串:', parsedWithQuery.query);
// 输出:{ query: 'value' }

// 获取 URL 的各个部分
console.log('协议:', parsedUrl.protocol);   // https:
console.log('主机名:', parsedUrl.hostname);  // example.com
console.log('端口:', parsedUrl.port);        // 8080
console.log('路径:', parsedUrl.pathname);    // /path/to/resource
console.log('查询字符串:', parsedUrl.search);  // ?query=value
console.log('哈希:', parsedUrl.hash);        // #hash

// 解析相对 URL
const relativeUrl = url.parse('/path/to/resource');
console.log('相对 URL:', relativeUrl);
// 输出:{ pathname: '/path/to/resource', path: '/path/to/resource' }

// 实际应用:解析请求 URL
function parseRequestUrl(requestUrl) {
  return url.parse(requestUrl, true);
}

const requestUrl = '/api/users?page=1&limit=10';
const parsedRequest = parseRequestUrl(requestUrl);
console.log('请求 URL 解析:', parsedRequest);

URL 格式化

const url = require('url');

// 格式化 URL
const urlObject = {
  protocol: 'https:',
  hostname: 'example.com',
  port: '8080',
  pathname: '/path/to/resource',
  query: { query: 'value' },
  hash: 'hash'
};

const formattedUrl = url.format(urlObject);
console.log('格式化 URL:', formattedUrl);
// 输出:https://example.com:8080/path/to/resource?query=value#hash

// 格式化相对 URL
const relativeObject = {
  pathname: '/path/to/resource',
  query: { page: '1', limit: '10' }
};

const formattedRelative = url.format(relativeObject);
console.log('格式化相对 URL:', formattedRelative);
// 输出:/path/to/resource?page=1&limit=10

// 实际应用:构建 API URL
function buildApiUrl(baseUrl, endpoint, params) {
  const urlObject = {
    protocol: url.parse(baseUrl).protocol,
    hostname: url.parse(baseUrl).hostname,
    port: url.parse(baseUrl).port,
    pathname: endpoint,
    query: params
  };
  
  return url.format(urlObject);
}

const apiUrl = buildApiUrl('https://api.example.com:8080', '/users', {
  page: '1',
  limit: '10'
});

console.log('API URL:', apiUrl);
// 输出:https://api.example.com:8080/users?page=1&limit=10

新的 URL API

Node.js 提供了新的 URL API,符合 WHATWG URL 标准。

// 创建 URL 对象
const myUrl = new URL('https://example.com:8080/path/to/resource?query=value#hash');

console.log('URL 对象:', myUrl);
// 输出:URL {
//   href: 'https://example.com:8080/path/to/resource?query=value#hash',
//   origin: 'https://example.com:8080',
//   protocol: 'https:',
//   username: '',
//   password: '',
//   host: 'example.com:8080',
//   hostname: 'example.com',
//   port: '8080',
//   pathname: '/path/to/resource',
//   search: '?query=value',
//   searchParams: URLSearchParams { 'query' => 'value' },
//   hash: '#hash'
// }

// 获取 URL 的各个部分
console.log('协议:', myUrl.protocol);    // https:
console.log('主机名:', myUrl.hostname);   // example.com
console.log('端口:', myUrl.port);         // 8080
console.log('路径:', myUrl.pathname);     // /path/to/resource
console.log('查询字符串:', myUrl.search); // ?query=value
console.log('哈希:', myUrl.hash);         // #hash
console.log('源:', myUrl.origin);         // https://example.com:8080

// 使用 URLSearchParams
console.log('查询参数:', myUrl.searchParams.get('query'));  // value

// 添加查询参数
myUrl.searchParams.append('page', '1');
myUrl.searchParams.append('limit', '10');
console.log('添加参数后的 URL:', myUrl.href);
// 输出:https://example.com:8080/path/to/resource?query=value&page=1&limit=10#hash

// 删除查询参数
myUrl.searchParams.delete('query');
console.log('删除参数后的 URL:', myUrl.href);
// 输出:https://example.com:8080/path/to/resource?page=1&limit=10#hash

// 实际应用:构建和修改 URL
function modifyUrl(originalUrl, params) {
  const urlObj = new URL(originalUrl);
  
  Object.entries(params).forEach(([key, value]) => {
    urlObj.searchParams.set(key, value);
  });
  
  return urlObj.href;
}

const modifiedUrl = modifyUrl('https://example.com/api/users', {
  page: '2',
  limit: '20',
  sort: 'name'
});

console.log('修改后的 URL:', modifiedUrl);
// 输出:https://example.com/api/users?page=2&limit=20&sort=name

查询字符串处理

const querystring = require('querystring');

// 解析查询字符串
const queryString = 'name=John&age=30&city=New+York';
const parsed = querystring.parse(queryString);

console.log('解析查询字符串:', parsed);
// 输出:{ name: 'John', age: '30', city: 'New York' }

// 解析带分隔符的查询字符串
const queryString2 = 'name=John;age=30;city=New+York';
const parsed2 = querystring.parse(queryString2, ';', null, { decodeURIComponent: decodeURIComponent });

console.log('解析查询字符串(自定义分隔符):', parsed2);
// 输出:{ name: 'John', age: '30', city: 'New York' }

// 字符串化对象
const obj = { name: 'John', age: '30', city: 'New York' };
const stringified = querystring.stringify(obj);

console.log('字符串化对象:', stringified);
// 输出:name=John&age=30&city=New%20York

// 字符串化对象(自定义分隔符)
const stringified2 = querystring.stringify(obj, ';', '=', { encodeURIComponent: encodeURIComponent });

console.log('字符串化对象(自定义分隔符):', stringified2);
// 输出:name=John;age=30;city=New%20York

// 转义和反转义
const escaped = querystring.escape('Hello World!');
console.log('转义:', escaped);
// 输出:Hello%20World!

const unescaped = querystring.unescape('Hello%20World!');
console.log('反转义:', unescaped);
// 输出:Hello World!

// 实际应用:处理 API 查询参数
function parseQueryParams(queryString) {
  return querystring.parse(queryString);
}

function buildQueryParams(params) {
  return querystring.stringify(params);
}

const queryParams = parseQueryParams('page=1&limit=10&sort=name');
console.log('解析查询参数:', queryParams);
// 输出:{ page: '1', limit: '10', sort: 'name' }

const builtParams = buildQueryParams({ page: '2', limit: '20', sort: 'date' });
console.log('构建查询参数:', builtParams);
// 输出:page=2&limit=20&sort=date

实用案例分析

案例 1:路径工具类

创建一个功能完善的路径工具类。

// path-utils.js
const path = require('path');

class PathUtils {
  // 拼接路径
  static join(...paths) {
    return path.join(...paths);
  }
  
  // 解析路径
  static parse(filePath) {
    return path.parse(filePath);
  }
  
  // 规范化路径
  static normalize(filePath) {
    return path.normalize(filePath);
  }
  
  // 解析为绝对路径
  static resolve(...paths) {
    return path.resolve(...paths);
  }
  
  // 计算相对路径
  static relative(from, to) {
    return path.relative(from, to);
  }
  
  // 获取文件名
  static basename(filePath, ext) {
    return path.basename(filePath, ext);
  }
  
  // 获取目录名
  static dirname(filePath) {
    return path.dirname(filePath);
  }
  
  // 获取扩展名
  static extname(filePath) {
    return path.extname(filePath);
  }
  
  // 获取文件名(不含扩展名)
  static getName(filePath) {
    return path.basename(filePath, path.extname(filePath));
  }
  
  // 判断是否为绝对路径
  static isAbsolute(filePath) {
    return path.isAbsolute(filePath);
  }
  
  // 获取文件类型
  static getFileType(filePath) {
    const ext = this.extname(filePath).toLowerCase();
    
    const types = {
      '.txt': 'text',
      '.js': 'javascript',
      '.json': 'json',
      '.html': 'html',
      '.css': 'css',
      '.jpg': 'image',
      '.jpeg': 'image',
      '.png': 'image',
      '.gif': 'image',
      '.pdf': 'pdf',
      '.doc': 'document',
      '.docx': 'document',
      '.xls': 'spreadsheet',
      '.xlsx': 'spreadsheet'
    };
    
    return types[ext] || 'unknown';
  }
  
  // 路径安全检查
  static isSafe(filePath, basePath) {
    const resolved = this.resolve(basePath, filePath);
    const normalized = this.normalize(resolved);
    const baseNormalized = this.normalize(basePath);
    
    return normalized.startsWith(baseNormalized);
  }
  
  // 构建相对路径
  static buildRelativePath(from, to) {
    return this.relative(from, to);
  }
  
  // 获取路径的父目录
  static getParentDirectory(filePath, levels = 1) {
    let current = filePath;
    
    for (let i = 0; i < levels; i++) {
      current = this.dirname(current);
    }
    
    return current;
  }
}

// 使用示例
console.log('路径工具类使用示例:');

// 拼接路径
const joinedPath = PathUtils.join('folder', 'subfolder', 'file.txt');
console.log('拼接路径:', joinedPath);

// 解析路径
const parsedPath = PathUtils.parse('/path/to/file.txt');
console.log('解析路径:', parsedPath);

// 规范化路径
const normalizedPath = PathUtils.normalize('./folder/../file.txt');
console.log('规范化路径:', normalizedPath);

// 解析为绝对路径
const absolutePath = PathUtils.resolve('folder', 'file.txt');
console.log('绝对路径:', absolutePath);

// 计算相对路径
const relativePath = PathUtils.relative('/path/to/file1.txt', '/path/to/file2.txt');
console.log('相对路径:', relativePath);

// 获取文件信息
const filePath = '/path/to/document.pdf';
console.log('文件名:', PathUtils.basename(filePath));
console.log('目录名:', PathUtils.dirname(filePath));
console.log('扩展名:', PathUtils.extname(filePath));
console.log('文件名(不含扩展名):', PathUtils.getName(filePath));
console.log('文件类型:', PathUtils.getFileType(filePath));

// 路径安全检查
const safePath = PathUtils.isSafe('../etc/passwd', '/safe/directory');
console.log('路径安全:', safePath);

// 获取父目录
const parentDir = PathUtils.getParentDirectory('/path/to/folder/file.txt', 2);
console.log('父目录:', parentDir);

案例 2:URL 工具类

创建一个功能完善的 URL 工具类。

// url-utils.js
const url = require('url');
const querystring = require('querystring');

class UrlUtils {
  // 解析 URL
  static parse(urlString, parseQueryString = false) {
    return url.parse(urlString, parseQueryString);
  }
  
  // 格式化 URL
  static format(urlObject) {
    return url.format(urlObject);
  }
  
  // 创建 URL 对象
  static create(urlString) {
    return new URL(urlString);
  }
  
  // 解析查询字符串
  static parseQueryString(queryString) {
    return querystring.parse(queryString);
  }
  
  // 构建查询字符串
  static buildQueryString(params) {
    return querystring.stringify(params);
  }
  
  // 获取 URL 的各个部分
  static getUrlParts(urlString) {
    const parsed = this.parse(urlString);
    return {
      protocol: parsed.protocol,
      hostname: parsed.hostname,
      port: parsed.port,
      pathname: parsed.pathname,
      search: parsed.search,
      hash: parsed.hash,
      query: parsed.query
    };
  }
  
  // 构建 URL
  static buildUrl(baseUrl, pathname, params = {}) {
    const urlObject = {
      ...this.parse(baseUrl),
      pathname: pathname,
      query: params
    };
    
    return this.format(urlObject);
  }
  
  // 修改 URL 查询参数
  static modifyQueryParams(urlString, params) {
    const urlObj = new URL(urlString);
    
    Object.entries(params).forEach(([key, value]) => {
      urlObj.searchParams.set(key, value);
    });
    
    return urlObj.href;
  }
  
  // 添加查询参数
  static addQueryParams(urlString, params) {
    const urlObj = new URL(urlString);
    
    Object.entries(params).forEach(([key, value]) => {
      urlObj.searchParams.append(key, value);
    });
    
    return urlObj.href;
  }
  
  // 删除查询参数
  static removeQueryParams(urlString, paramNames) {
    const urlObj = new URL(urlString);
    
    paramNames.forEach(name => {
      urlObj.searchParams.delete(name);
    });
    
    return urlObj.href;
  }
  
  // 获取查询参数
  static getQueryParams(urlString) {
    const urlObj = new URL(urlString);
    const params = {};
    
    urlObj.searchParams.forEach((value, key) => {
      params[key] = value;
    });
    
    return params;
  }
  
  // 获取单个查询参数
  static getQueryParam(urlString, paramName) {
    const urlObj = new URL(urlString);
    return urlObj.searchParams.get(paramName);
  }
  
  // 检查 URL 是否包含某个查询参数
  static hasQueryParam(urlString, paramName) {
    const urlObj = new URL(urlString);
    return urlObj.searchParams.has(paramName);
  }
  
  // 规范化 URL
  static normalizeUrl(urlString) {
    const urlObj = new URL(urlString);
    return urlObj.href;
  }
  
  // 比较两个 URL 是否相同
  static compareUrls(url1, url2) {
    const normalized1 = this.normalizeUrl(url1);
    const normalized2 = this.normalizeUrl(url2);
    return normalized1 === normalized2;
  }
}

// 使用示例
console.log('URL 工具类使用示例:');

// 解析 URL
const urlString = 'https://example.com:8080/path/to/resource?query=value#hash';
const parsedUrl = UrlUtils.parse(urlString);
console.log('解析 URL:', parsedUrl);

// 获取 URL 的各个部分
const urlParts = UrlUtils.getUrlParts(urlString);
console.log('URL 各部分:', urlParts);

// 构建查询字符串
const queryParams = { page: '1', limit: '10', sort: 'name' };
const queryString = UrlUtils.buildQueryString(queryParams);
console.log('构建查询字符串:', queryString);

// 修改 URL 查询参数
const modifiedUrl = UrlUtils.modifyQueryParams(urlString, { page: '2', limit: '20' });
console.log('修改后的 URL:', modifiedUrl);

// 添加查询参数
const addedParamsUrl = UrlUtils.addQueryParams(urlString, { filter: 'active' });
console.log('添加参数后的 URL:', addedParamsUrl);

// 删除查询参数
const removedParamsUrl = UrlUtils.removeQueryParams(urlString, ['query']);
console.log('删除参数后的 URL:', removedParamsUrl);

// 获取查询参数
const queryParamsFromUrl = UrlUtils.getQueryParams(urlString);
console.log('查询参数:', queryParamsFromUrl);

// 获取单个查询参数
const singleParam = UrlUtils.getQueryParam(urlString, 'query');
console.log('单个查询参数:', singleParam);

// 检查是否包含查询参数
const hasParam = UrlUtils.hasQueryParam(urlString, 'query');
console.log('是否包含查询参数:', hasParam);

// 构建完整 URL
const fullUrl = UrlUtils.buildUrl('https://api.example.com', '/users', { page: '1', limit: '10' });
console.log('构建的 URL:', fullUrl);

案例 3:路由解析器

创建一个能够解析和匹配 URL 路由的路由解析器。

// router.js
const path = require('path');
const url = require('url');

class Router {
  constructor() {
    this.routes = [];
  }
  
  // 添加路由
  addRoute(method, pathPattern, handler) {
    this.routes.push({
      method: method.toUpperCase(),
      pattern: pathPattern,
      handler
    });
  }
  
  // 解析路径参数
  parsePathParams(pathPattern, pathname) {
    const patternParts = pathPattern.split('/').filter(Boolean);
    const pathParts = pathname.split('/').filter(Boolean);
    
    if (patternParts.length !== pathParts.length) {
      return null;
    }
    
    const params = {};
    
    for (let i = 0; i < patternParts.length; i++) {
      const patternPart = patternParts[i];
      const pathPart = pathParts[i];
      
      if (patternPart.startsWith(':')) {
        const paramName = patternPart.slice(1);
        params[paramName] = pathPart;
      } else if (patternPart !== pathPart) {
        return null;
      }
    }
    
    return params;
  }
  
  // 匹配路由
  matchRoute(method, pathname) {
    const methodUpper = method.toUpperCase();
    
    for (const route of this.routes) {
      if (route.method !== methodUpper) {
        continue;
      }
      
      const params = this.parsePathParams(route.pattern, pathname);
      
      if (params !== null) {
        return {
          handler: route.handler,
          params
        };
      }
    }
    
    return null;
  }
  
  // 处理请求
  handleRequest(method, pathname) {
    const matched = this.matchRoute(method, pathname);
    
    if (matched) {
      return matched.handler(matched.params);
    } else {
      return { error: 'Route not found', status: 404 };
    }
  }
}

// 使用示例
const router = new Router();

// 添加路由
router.addRoute('GET', '/', (params) => {
  return { message: 'Home page', params };
});

router.addRoute('GET', '/users', (params) => {
  return { message: 'User list', params };
});

router.addRoute('GET', '/users/:id', (params) => {
  return { message: 'User detail', params };
});

router.addRoute('GET', '/users/:id/posts', (params) => {
  return { message: 'User posts', params };
});

router.addRoute('POST', '/users', (params) => {
  return { message: 'Create user', params };
});

router.addRoute('PUT', '/users/:id', (params) => {
  return { message: 'Update user', params };
});

router.addRoute('DELETE', '/users/:id', (params) => {
  return { message: 'Delete user', params };
});

// 处理请求
console.log('路由匹配示例:');

console.log(router.handleRequest('GET', '/'));
// 输出:{ message: 'Home page', params: {} }

console.log(router.handleRequest('GET', '/users'));
// 输出:{ message: 'User list', params: {} }

console.log(router.handleRequest('GET', '/users/123'));
// 输出:{ message: 'User detail', params: { id: '123' } }

console.log(router.handleRequest('GET', '/users/123/posts'));
// 输出:{ message: 'User posts', params: { id: '123' } }

console.log(router.handleRequest('POST', '/users'));
// 输出:{ message: 'Create user', params: {} }

console.log(router.handleRequest('PUT', '/users/123'));
// 输出:{ message: 'Update user', params: { id: '123' } }

console.log(router.handleRequest('DELETE', '/users/123'));
// 输出:{ message: 'Delete user', params: { id: '123' } }

console.log(router.handleRequest('GET', '/not-found'));
// 输出:{ error: 'Route not found', status: 404 }

代码示例

示例 1:文件路径解析器

// file-path-parser.js
const path = require('path');

class FilePathParser {
  constructor(filePath) {
    this.filePath = filePath;
    this.parsed = path.parse(filePath);
  }
  
  getExtension() {
    return this.parsed.ext;
  }
  
  getName() {
    return this.parsed.name;
  }
  
  getBasename() {
    return this.parsed.base;
  }
  
  getDirectory() {
    return this.parsed.dir;
  }
  
  getRoot() {
    return this.parsed.root;
  }
  
  isAbsolute() {
    return path.isAbsolute(this.filePath);
  }
  
  toAbsolute() {
    return path.resolve(this.filePath);
  }
  
  toRelative(from) {
    return path.relative(from, this.filePath);
  }
  
  changeExtension(newExt) {
    const newPath = path.join(
      this.parsed.dir,
      this.parsed.name + newExt
    );
    return new FilePathParser(newPath);
  }
  
  changeName(newName) {
    const newPath = path.join(
      this.parsed.dir,
      newName + this.parsed.ext
    );
    return new FilePathParser(newPath);
  }
  
  changeDirectory(newDir) {
    const newPath = path.join(newDir, this.parsed.base);
    return new FilePathParser(newPath);
  }
  
  toString() {
    return this.filePath;
  }
}

// 使用示例
const filePath = new FilePathParser('/path/to/document.pdf');

console.log('文件扩展名:', filePath.getExtension());
console.log('文件名:', filePath.getName());
console.log('完整文件名:', filePath.getBasename());
console.log('目录:', filePath.getDirectory());
console.log('根目录:', filePath.getRoot());
console.log('是否为绝对路径:', filePath.isAbsolute());
console.log('绝对路径:', filePath.toAbsolute());
console.log('相对路径:', filePath.toRelative('/other/path'));

const newFilePath = filePath.changeExtension('.txt');
console.log('更改扩展名:', newFilePath.toString());

const newNamePath = filePath.changeName('new-document');
console.log('更改文件名:', newNamePath.toString());

const newDirPath = filePath.changeDirectory('/new/directory');
console.log('更改目录:', newDirPath.toString());

示例 2:URL 构建器

// url-builder.js
const url = require('url');
const querystring = require('querystring');

class UrlBuilder {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    this.pathname = '';
    this.params = {};
    this.hash = '';
  }
  
  setPathname(pathname) {
    this.pathname = pathname;
    return this;
  }
  
  addParam(key, value) {
    this.params[key] = value;
    return this;
  }
  
  addParams(params) {
    Object.assign(this.params, params);
    return this;
  }
  
  removeParam(key) {
    delete this.params[key];
    return this;
  }
  
  setHash(hash) {
    this.hash = hash;
    return this;
  }
  
  build() {
    const urlObject = {
      ...url.parse(this.baseUrl),
      pathname: this.pathname || url.parse(this.baseUrl).pathname,
      query: this.params
    };
    
    if (this.hash) {
      urlObject.hash = this.hash;
    }
    
    return url.format(urlObject);
  }
  
  toString() {
    return this.build();
  }
}

// 使用示例
const urlBuilder = new UrlBuilder('https://api.example.com');

const builtUrl = urlBuilder
  .setPathname('/users')
  .addParam('page', '1')
  .addParam('limit', '10')
  .addParam('sort', 'name')
  .setHash('top')
  .build();

console.log('构建的 URL:', builtUrl);
// 输出:https://api.example.com/users?page=1&limit=10&sort=name#top

const anotherUrl = new UrlBuilder('https://api.example.com')
  .setPathname('/posts')
  .addParams({
    author: 'John',
    category: 'tech',
    status: 'published'
  })
  .build();

console.log('另一个 URL:', anotherUrl);
// 输出:https://api.example.com/posts?author=John&category=tech&status=published

示例 3:路径验证器

// path-validator.js
const path = require('path');
const fs = require('fs');

class PathValidator {
  // 验证路径是否安全
  static isSafe(inputPath, basePath) {
    const resolved = path.resolve(basePath, inputPath);
    const normalized = path.normalize(resolved);
    const baseNormalized = path.normalize(basePath);
    
    return normalized.startsWith(baseNormalized);
  }
  
  // 验证路径是否存在
  static exists(filePath) {
    return fs.existsSync(filePath);
  }
  
  // 验证是否为文件
  static isFile(filePath) {
    try {
      return fs.statSync(filePath).isFile();
    } catch (error) {
      return false;
    }
  }
  
  // 验证是否为目录
  static isDirectory(dirPath) {
    try {
      return fs.statSync(dirPath).isDirectory();
    } catch (error) {
      return false;
    }
  }
  
  // 验证文件扩展名
  static hasExtension(filePath, extensions) {
    const ext = path.extname(filePath).toLowerCase();
    const extArray = Array.isArray(extensions) ? extensions : [extensions];
    return extArray.some(e => e.toLowerCase() === ext);
  }
  
  // 验证路径可读
  static isReadable(filePath) {
    try {
      fs.accessSync(filePath, fs.constants.R_OK);
      return true;
    } catch (error) {
      return false;
    }
  }
  
  // 验证路径可写
  static isWritable(filePath) {
    try {
      fs.accessSync(filePath, fs.constants.W_OK);
      return true;
    } catch (error) {
      return false;
    }
  }
  
  // 验证路径可执行
  static isExecutable(filePath) {
    try {
      fs.accessSync(filePath, fs.constants.X_OK);
      return true;
    } catch (error) {
      return false;
    }
  }
  
  // 综合验证
  static validate(inputPath, options = {}) {
    const {
      basePath,
      mustExist = false,
      mustBeFile = false,
      mustBeDirectory = false,
      allowedExtensions,
      mustBeReadable = false,
      mustBeWritable = false
    } = options;
    
    // 验证路径安全
    if (basePath && !this.isSafe(inputPath, basePath)) {
      return { valid: false, error: 'Path is not safe' };
    }
    
    // 验证路径存在
    if (mustExist && !this.exists(inputPath)) {
      return { valid: false, error: 'Path does not exist' };
    }
    
    // 验证是否为文件
    if (mustBeFile && !this.isFile(inputPath)) {
      return { valid: false, error: 'Path is not a file' };
    }
    
    // 验证是否为目录
    if (mustBeDirectory && !this.isDirectory(inputPath)) {
      return { valid: false, error: 'Path is not a directory' };
    }
    
    // 验证文件扩展名
    if (allowedExtensions && !this.hasExtension(inputPath, allowedExtensions)) {
      return { valid: false, error: 'File extension not allowed' };
    }
    
    // 验证可读
    if (mustBeReadable && !this.isReadable(inputPath)) {
      return { valid: false, error: 'Path is not readable' };
    }
    
    // 验证可写
    if (mustBeWritable && !this.isWritable(inputPath)) {
      return { valid: false, error: 'Path is not writable' };
    }
    
    return { valid: true };
  }
}

// 使用示例
console.log('路径验证示例:');

// 验证路径安全
const safePath = PathValidator.isSafe('../etc/passwd', '/safe/directory');
console.log('路径安全:', safePath);

// 验证路径存在
const exists = PathValidator.exists('/etc/passwd');
console.log('路径存在:', exists);

// 验证是否为文件
const isFile = PathValidator.isFile('/etc/passwd');
console.log('是否为文件:', isFile);

// 验证文件扩展名
const hasExt = PathValidator.hasExtension('/path/to/file.txt', ['.txt', '.md']);
console.log('文件扩展名:', hasExt);

// 综合验证
const validation = PathValidator.validate('/path/to/file.txt', {
  basePath: '/safe/directory',
  mustExist: true,
  mustBeFile: true,
  allowedExtensions: ['.txt', '.md'],
  mustBeReadable: true
});

console.log('验证结果:', validation);

实现技巧与注意事项

路径处理最佳实践

  1. 使用 path 模块:避免手动拼接路径
  2. 处理跨平台路径:注意不同操作系统的路径分隔符
  3. 验证路径安全性:防止路径遍历攻击
  4. 规范化路径:使用 path.normalize() 清理路径

URL 处理最佳实践

  1. 使用 URL API:优先使用新的 URL API
  2. 验证 URL 格式:确保 URL 格式正确
  3. 处理查询参数:正确解析和构建查询字符串
  4. 编码和解码:正确处理 URL 编码

性能优化建议

  1. 缓存解析结果:避免重复解析相同的路径或 URL
  2. 使用正则表达式:对于复杂的路径匹配,使用正则表达式
  3. 批量处理:对于大量路径或 URL 操作,使用批量处理
  4. 避免频繁转换:减少字符串和对象之间的转换

常见问题与解决方案

问题 1:跨平台路径问题

// 问题代码:硬编码路径分隔符
const filePath = 'folder\\subfolder\\file.txt';  // Windows 特定

// 解决方案:使用 path 模块
const path = require('path');
const filePath = path.join('folder', 'subfolder', 'file.txt');

问题 2:路径遍历攻击

// 问题代码:未验证用户输入的路径
const userPath = req.body.path;
const filePath = path.join(baseDir, userPath);  // 可能导致路径遍历攻击

// 解决方案:验证路径安全性
const userPath = req.body.path;
const resolvedPath = path.resolve(baseDir, userPath);
const normalizedPath = path.normalize(resolvedPath);

if (!normalizedPath.startsWith(path.resolve(baseDir))) {
  throw new Error('非法路径');
}

const filePath = normalizedPath;

问题 3:URL 查询参数解析问题

// 问题代码:未正确处理查询参数
const queryString = 'name=John&age=30';
const params = queryString.split('&').reduce((acc, pair) => {
  const [key, value] = pair.split('=');
  acc[key] = value;
  return acc;
}, {});  // 未处理编码和复杂情况

// 解决方案:使用 querystring 模块
const querystring = require('querystring');
const queryString = 'name=John&age=30';
const params = querystring.parse(queryString);

问题 4:路径和 URL 混淆

// 问题代码:混淆文件路径和 URL
const filePath = 'https://example.com/file.txt';  // 这是 URL,不是文件路径

// 解决方案:明确区分文件路径和 URL
const fileUrl = 'https://example.com/file.txt';
const filePath = '/path/to/file.txt';

// 如果需要从 URL 提取文件路径
const url = require('url');
const parsedUrl = url.parse(fileUrl);
const pathname = parsedUrl.pathname;  // /file.txt

总结

本教程详细介绍了 Node.js 的 path 和 url 模块,包括路径解析、URL 处理、查询字符串解析等重要内容。掌握这些模块对于处理文件路径和 URL 至关重要。

通过本集的学习,您应该能够:

  1. 使用 path 模块处理文件系统路径
  2. 使用 url 模块解析和构建 URL
  3. 处理查询字符串
  4. 创建路径和 URL 工具类
  5. 实现路由解析功能
  6. 避免常见的路径和 URL 处理错误

在下一集中,我们将学习 Node.js 的事件循环机制,这是理解 Node.js 异步编程的核心概念。继续加油,您的 Node.js 技能正在不断提升!

« 上一篇 Node.js 文件系统进阶 下一篇 » Node.js 事件循环机制