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);实现技巧与注意事项
路径处理最佳实践
- 使用 path 模块:避免手动拼接路径
- 处理跨平台路径:注意不同操作系统的路径分隔符
- 验证路径安全性:防止路径遍历攻击
- 规范化路径:使用 path.normalize() 清理路径
URL 处理最佳实践
- 使用 URL API:优先使用新的 URL API
- 验证 URL 格式:确保 URL 格式正确
- 处理查询参数:正确解析和构建查询字符串
- 编码和解码:正确处理 URL 编码
性能优化建议
- 缓存解析结果:避免重复解析相同的路径或 URL
- 使用正则表达式:对于复杂的路径匹配,使用正则表达式
- 批量处理:对于大量路径或 URL 操作,使用批量处理
- 避免频繁转换:减少字符串和对象之间的转换
常见问题与解决方案
问题 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 至关重要。
通过本集的学习,您应该能够:
- 使用 path 模块处理文件系统路径
- 使用 url 模块解析和构建 URL
- 处理查询字符串
- 创建路径和 URL 工具类
- 实现路由解析功能
- 避免常见的路径和 URL 处理错误
在下一集中,我们将学习 Node.js 的事件循环机制,这是理解 Node.js 异步编程的核心概念。继续加油,您的 Node.js 技能正在不断提升!