Nodejs 文件 与 路径 相关用法实例解析

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
全局流量管理 GTM,标准版 1个月
简介: Nodejs 文件 与 路径 相关用法实例解析

Nodejs 文件 与 路径 相关用法


1.路径

(1)引入path模块

const path = require('path');

(2)当前文件所在目录的绝对路径:__dirname

console.log("__dirname =",__dirname);
__dirname = g:\NodeJs\fs

(3)路径连接:path.join()

path.join() 方法使用特定于平台的分隔符作为定界符将所有给定的 path 片段连接在一起,然后规范化生成的路径。

  • 零长度的 path 片段被忽略;
  • 如果连接的路径字符串是零长度字符串,则将返回 ‘.’,表示当前工作目录。
const joined_path = path.join(__dirname, 'NodeJs', 'textFile.txt')
console.log(joined_path);
g:\NodeJs\fs\NodeJs\textFile.txt

(4)获取文件名:path.basename()

const filename = path.basename('g:\\NodeJs\\fs\\NodeJs\\textFile.txt')
console.log(filename);
textFile.txt

(5)获取文件扩展名:path.extname()

const surfix = path.extname('g:\\NodeJs\\fs\\NodeJs\\textFile.txt')
console.log(surfix);
.txt

(6)分解绝对路径:path.parse()

  • root: 根目录/ 磁盘名
  • dir: 文件或文件夹所在文件夹完整绝对路径
  • base: 文件/文件夹名加扩展(非绝对路径)
  • ext: 扩展名
  • name: 文件/文件夹名
const parserst = path.parse('g:\\NodeJs\\fs\\NodeJs\\textFile.txt')
console.log(parserst);console.log();
{
  root: 'g:\\',
  dir: 'g:\\NodeJs\\fs\\NodeJs',
  base: 'textFile.txt',
  ext: '.txt',
  name: 'textFile'
}

(7)从对象返回路径字符串:path.format()

path.parse()功能相反

const pt = path.format({
  root: 'g:\\',
  dir: 'g:\\NodeJs\\fs\\NodeJs',
  base: 'textFile.txt'
});
console.log(pt);
g:\NodeJs\fs\NodeJs\textFile.txt

(8)判断是否是绝对路径:path.isAbsolute()

const isAbs1 = path.isAbsolute('g:\\NodeJs\\fs\\NodeJs\\textFile.txt');
const isAbs2 = path.isAbsolute('textFile.txt');
console.log('isAbs1是绝对路径吗:',isAbs1);
console.log('isAbs2是绝对路径吗:',isAbs2);
isAbs1是绝对路径吗: true
isAbs2是绝对路径吗: false

(9)返回两端路径之间的相对路径表示:path.relative()

const path1 = '/data/orandea/test/aaa';
const path2 = '/data/orandea/impl/bbb';
const relativepath = path.relative(path1,path2)
console.log('path2在path1的相对表示为:',relativepath);console.log();
path2在path1的相对表示为: ..\..\impl\bbb

(10)将路径或路径片段的序列解析为绝对路径:path.resolve()

// 给定的路径序列从右到左处理,每个后续的 path 会被追加到前面,直到构建绝对路径。
console.log(path.resolve('/foo/bar', '/tmp/file/'));
console.log(path.resolve('NodeJs', 'fs/NodeJs/', 'textFile'));
G:\tmp\file
G:\NodeJs\NodeJs\fs\NodeJs\textFile

(11)将路径分割为数组:path.sep

(注意与不同系统的路径分隔符有关)

console.log('foo\\bar\\baz'.split(path.sep));console.log();
[ 'foo', 'bar', 'baz' ]

注意区分不同系统上的路径分隔符不同。

(12)获取当前操作系统上的路径定界符:path.delimiter

console.log('路径定界符为:',path.delimiter);
路径定界符为: ;

应用

const processEnvPATH = process.env.PATH
console.log(processEnvPATH);
console.log(processEnvPATH.split(path.delimiter));console.log();
C:\Program Files\PowerShell\7;C:\Program Files\AdoptOpenJDK\jdk-11.0.8.10-hotspot\bin;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Program Files\Common Files\Oracle\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\PowerShell\7\;D:\Program Files\Dart\dart-sdk\bin;C:\Program Files\PuTTY\;D:\Program Files\nodejs\;C:\ProgramData\chocolatey\bin;C:\Program Files (x86)\Yarn\bin\;C:\Program Files\Docker\Docker\resources\bin;C:\ProgramData\DockerDesktop\version-bin;C:\Program Files\dotnet\;C:\Program Files\Git\cmd;C:\Program Files\MySQL\MySQL Shell 8.0\bin\;C:\Users\a2911\AppData\Local\Programs\Python\Python39\Scripts\;C:\Users\a2911\AppData\Local\Programs\Python\Python39\;C:\Users\a2911\AppData\Local\Microsoft\WindowsApps;D:\flutter;C:\Users\a2911\AppData\Local\GitHubDesktop\bin;C:\Users\a2911\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\a2911\AppData\Local\Yarn\bin;D:\flutter\bin;C:\Users\a2911\.dotnet\tools;D:\Program Files\Nox\bin;C:\Users\a2911\AppData\Roaming\npm;
[
  'C:\\Program Files\\PowerShell\\7',
  'C:\\Program Files\\AdoptOpenJDK\\jdk-11.0.8.10-hotspot\\bin',
  'C:\\Program Files (x86)\\Common Files\\Oracle\\Java\\javapath',
  'C:\\Program Files\\Common Files\\Oracle\\Java\\javapath',
  'C:\\WINDOWS\\system32',
  'C:\\WINDOWS',
  'C:\\WINDOWS\\System32\\Wbem',
  'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\',
  'C:\\WINDOWS\\System32\\OpenSSH\\',
  'C:\\Program Files\\PowerShell\\7\\',
  'D:\\Program Files\\Dart\\dart-sdk\\bin',
  'C:\\Program Files\\PuTTY\\',
  'D:\\Program Files\\nodejs\\',
  'C:\\ProgramData\\chocolatey\\bin',
  'C:\\Program Files (x86)\\Yarn\\bin\\',
  'C:\\Program Files\\Docker\\Docker\\resources\\bin',
  'C:\\ProgramData\\DockerDesktop\\version-bin',
  'C:\\Program Files\\dotnet\\',
  'C:\\Program Files\\Git\\cmd',
  'C:\\Program Files\\MySQL\\MySQL Shell 8.0\\bin\\',
  'C:\\Users\\a2911\\AppData\\Local\\Programs\\Python\\Python39\\Scripts\\',
  'C:\\Users\\a2911\\AppData\\Local\\Programs\\Python\\Python39\\',
  'C:\\Users\\a2911\\AppData\\Local\\Microsoft\\WindowsApps',
  'D:\\flutter',
  'C:\\Users\\a2911\\AppData\\Local\\GitHubDesktop\\bin',
  'C:\\Users\\a2911\\AppData\\Local\\Programs\\Microsoft VS Code\\bin',
  'C:\\Users\\a2911\\AppData\\Local\\Yarn\\bin',
  'D:\\flutter\\bin',
  'C:\\Users\\a2911\\.dotnet\\tools',
  'D:\\Program Files\\Nox\\bin',
  'C:\\Users\\a2911\\AppData\\Roaming\\npm',
  ''
]

(13)返回所在文件夹绝对路径:path.dirname()

const dir = path.dirname('g:\\NodeJs\\fs\\NodeJs\\textFile.txt');
console.log(dir);
g:\NodeJs\fs\NodeJs

2. 文件

(1)引入相关模块

const path = require('path');

(2)异步地获取指定文件的相关信息:access()

const file = 'g:\\NodeJs\\textFile.txt';
// 检查当前目录中是否存在该文件。
fs.access(file, fs.constants.F_OK, (err) => {
  console.log(`${file} ${err ? '不存在' : '存在'}`);
});
// 检查文件是否可读。
fs.access(file, fs.constants.R_OK, (err) => {
  console.log(`${file} ${err ? '不可读' : '可读'}`);
});
// 检查文件是否可写。
fs.access(file, fs.constants.W_OK, (err) => {
  console.log(`${file} ${err ? '不可写' : '可写'}`);
});
// 检查当前目录中是否存在文件,是否可写。
fs.access(file, fs.constants.F_OK | fs.constants.W_OK, (err) => {
  if (err) {
    console.error(
      `${file} ${err.code === 'ENOENT' ? '不存在' : '是 只读文件'}`);
  } else {
    console.log(`${file} 存在, 并且它是可写的`);
  }
});
g:\NodeJs\textFile.txt 存在
g:\NodeJs\textFile.txt 可读
g:\NodeJs\textFile.txt 可写
g:\NodeJs\textFile.txt 存在, 并且它是可写的

(3)复制文件(自动创建或覆盖目标):copyFile()

let sourceFile = 'g:\\NodeJs\\textfile.txt';
let destinationFile = 'g:\\NodeJs\\textfile.txt';
// 定义复制操作后执行的回调函数
function callBack(err) {
  if (err) {throw err}
  else{
    console.log('文件顺利复制完成');
  };
}
fs.copyFile(sourceFile, destinationFile, callBack);
文件顺利复制完成

(4)复制文件(文件存在则报错):fs.constants.COPYFILE_EXCL

let sourceFile = 'g:\\NodeJs\\textfile.txt';
let destinationFile = 'g:\\NodeJs\\textfile.txt';
function callBack(err) {
  if (err) throw err;
  console.log('文件顺利复制完成');
}
fs.copyFile(sourceFile, destinationFile, fs.constants.COPYFILE_EXCL, callBack);
[Error: EEXIST: file already exists, copyfile 'g:\NodeJs\textfile.txt' -> 'g:\NodeJs\textfile.txt'] {
  errno: -4075,
  code: 'EEXIST',
  syscall: 'copyfile',
  path: 'g:\\NodeJs\\textfile.txt',
  dest: 'g:\\NodeJs\\textfile.txt'
}

(5)打开文件:open()

let target = 'G:\\NodeJs\\fs\\a.txt'
let a =  fs.open(target, 'r', (err,fd)=>{
  if(err){throw err}
  else{
    console.log("文件已在内存中加载");
  }
})

注:

fs.open(path[, flags[, mode]], callback)

  • path:文件路径
  • flags:文件打开方式;
  • mode(可选):文件权限模式,默认为可读写;
  • Callback:回调函数,带有err和fd两个参数。

其中回调函数callback(err,fd)的两个参数中:

  • err:打开时的异常;
  • fd文件描述符
    一个文件打开以后,需要在操作完后对其关闭(见关闭文件:close()部分)以释放内存资源,并确认其正确关闭,这是需要用到这些曾今打开过的文件的文件描述符。也就是说该参数是用来跟踪一个已经打开的文件的,他在读取方法read()和关闭方法close()中都需要用到。
flag 含义
r 以只读模式打开文件,文件不存在时抛出异常
r+ 以读写模式打开文件,文件不存在时抛出异常
rs 以同步方式读取文件
rs+ 以同步方式读写文件
w 以只写模式打开文件,文件不存在时自动将其创建
wx 以只写模式打开文件,如果文件路径存在则写入失败
w+ 以读写模式打开文件,文件不存在时自动将其创建
wx+ 以读写模式打开文件,如果文件路径存在则写入失败
a 以追加模式打开文件,文件不存在时自动创建
ax 以追加模式打开文件,文件存在时将导致追加失败

(6)关闭文件:close()

该方法时open()的逆过程,需要接收open()方法返回的fd(文件描述符)参数以跟踪一个打开的文件进行关闭。

let target = 'G:\\NodeJs\\fs\\a.txt'
let a =  fs.open(target, 'r', (err,fd)=>{
  if(err){throw err}
  else{
    console.log("文件已在内存中加载");
    fs.close(fd, (error)=>{
      if(error){console.log(error)}
      else{
        console.log("文件关闭成功");
      }
    })
  }
})
文件已在内存中加载
文件关闭成功

(7)写入文件:writeFile()

let file = 'message.txt';
const data = new Uint8Array(Buffer.from('这是一些用于写入的文字'));
fs.writeFile(file, data, (err) => {
  if (err) throw err;
  console.log('文件已被写入');
});
文件已被写入

可以看到生成了一个文本文件:

(8)异步地将数据追加到文件:appendFile()

如果该文件尚不存在,则将自动创建该文件

// 将数据追加到文件,如果该文件尚不存在,则创建该文件
let file_path = 'g:\\NodeJs\\newfile.txt';
let datas = 'some text to append...'
fs.appendFile(file_path, datas, (err) => {
  if (err) throw err;
  console.log('The ' + datas + ', was appended to file!');
});

(9)读取文件:readFile()

假设我们在path指定处有一个文本,其内容为文本:这里有一些文字

直接读取这个文本文件:

let path = './fs/a.txt';
fs.readFile(path , (err, data) => {
  if (err) {throw err}
  else{console.log(data)}
});
<Buffer e8 bf 99 e9 87 8c e6 9c 89 e4 b8 80 e4 ba 9b e6 96 87 e5 ad 97>

可以看到由于未指定编码,则返回原始缓冲区数据。由于此处文本是先前使用utf-8格式保存的,我们可以这样读取:

fs.readFile('./fs/a.txt', 'utf8', (err, data) => {
  if (err) {throw err}
  else{console.log(data)}
});
这里有一些文字

注:这里这API都是异步的,且有相应的同步API,函数名相差不过多了一个Sync,比如我们也可以使用同步API读取文件:

let uts8Text = fs.readFileSync('./fs/a.txt',"utf8",'r+');
console.log(uts8Text)
这里有一些文字

(10)读取文件:read()

该方法需要提供以open()方法打开所返回的fd(文件资源定位符)参数作为参数。其格式为:

fs.read(fd, [options,] callback)

  • fd:通过open()方法返回的文件描述符;
  • buffer(可选):数据写入的缓冲区;
  • offset(可选):写入缓冲区时的写入偏移量;
  • length(可选):要从文件中读取的字节数;
  • position(可选):文件读取的起始位置,当为null时,就会从当前文件指针的位置进行读取。
let target = 'G:\\NodeJs\\fs\\a.txt'
fs.open(target, 'r', (err,fd)=>{
  if(err){throw err}
  else{
    console.log("文件已加载\n");
    let readUf = Buffer.alloc(1024); 
    let offset = 0;
    let length = readUf.length;
    let position = null;
    fs.read(fd, readUf, offset, length, position, (ery, bytesRead)=>{
      if(ery){throw err}
      else{
        console.log('读取文件大小',bytesRead +'bytes\n');
        let rst = readUf.slice(0,bytesRead);
        console.log('Buffer形式:',rst,'\n');
        console.log('以UTF-8编码转为字符串:',rst.toString('utf8'),'\n');
        fs.close(fd, (error)=>{
          if(error){console.log(error)}
          else{
            console.log("文件关闭成功");
          }
        })
      }
    })
  }
})
文件已加载
读取文件大小 36bytes
Buffer形式: <Buffer e8 bf 99 e9 87 8c e6 9c 89 e4 b8 80 e4 ba 9b e7 94 a8 e4 ba 8e e8 af bb e5 8f 96 e7 9a 84 e6 96 87 e5 ad 97> 
以UTF-8编码转为字符串: 这里有一些用于读取的文字
文件关闭成功

(11)异步地创建文件夹:mkdir()

fs.mkdir('./newFolder', { recursive: true }, (err) => {
  if (err) {throw err}
  else{
    console.log('新文件夹创建成功');
  };
});
新文件夹创建成功

如果成功,则将在指定目录下看到一个新的名为newFolder的文件夹。

(12)异步地删除文件和目录:rm()

function callBack(err) {
  if (err) throw err;
  console.log('文件删除完成');
}
fs.rm('a.text', {force: false , maxRetries: 0, recursive: false, retryDelay: 100,}, callBack)

如果文件不存在:

[Error: ENOENT: no such file or directory, stat 'xxxxxx'] {
  errno: -4058,
  code: 'ENOENT',
  syscall: 'stat',
  path: 'xxxxxx'
}

(13)异步地删除目录:fs.rmdir

在文件(而不是目录)上使用 fs.rmdir(),则在 Windows 上会导致 ENOENT 错误,在 POSIX 上会导致 ENOTDIR 错误。

function callBack(err) {
  if (err) throw err;
  console.log('文件夹删除完成');
}
fs.rm('folder', { maxRetries: 0, recursive: false, retryDelay: 100,}, callBack)

(14)读取文件夹(中的所有项目的名字):fs.readdir()

读取目录,其读取的内容既包括文件名也包括目录名。(不是绝对路径表示)

let dirName = 'G:\\mobile-flutter\\jcstdio\\assets\\coder\\statics\\css';
fs.readdir(dirName,{encoding: 'utf8', withFileTypes: false },(err:any, data:string[]) =>{
  if (err) {throw err
  }else{console.log(data);
  }
});
[
  'app.6605356c.css',
  'chunk-vendors.574a9ade.css',
  'login.6d2c5898.css'
]

获取给定文件夹中所有内容(文件和文件夹)的绝对路径

没有直接的方法,不过也很简单:

let dirName = 'G:\\mobile-flutter\\jcstdio\\assets\\coder\\statics\\css';
fs.readdir(dirName,{encoding: 'utf8', withFileTypes: false },(err:any, data:string[]) =>{
  if (err) {throw err}
  else{
    for(let i =0; i<data.length;i++){
      data[i] = path.join(dirName,data[i]);
    };
    console.log(data);
  }
});

如果withFileTypes参数为true,则返回项目中将带有文件类型信息,如:

[
  Dirent { name: 'app.6605356c.css', [Symbol(type)]: 1 },
  Dirent { name: 'chunk-vendors.574a9ade.css', [Symbol(type)]: 1 },
  Dirent { name: 'login.6d2c5898.css', [Symbol(type)]: 1 }
]

注:其相应的同步函数为readFileSync(),用法相同,如:

let dirName = 'G:\\mobile-flutter\\jcstdio\\assets\\coder\\statics\\css';
let ary = fs.readdirSync(dirName, 'utf8');
console.log(ary);
[
  'app.6605356c.css',
  'chunk-vendors.574a9ade.css',
  'login.6d2c5898.css'
]

另外,有对应的同步API为readdirSync(),其用法基本用法基本相似:

let dirName = 'G:\\mobile-flutter\\jcstdio\\assets\\coder\\statics\\css';
let a = fs.readdirSync(dirName, 'utf8', false)
console.log(a);
[
  'app.6605356c.css',
  'chunk-vendors.574a9ade.css',
  'login.6d2c5898.css'
]

如果要返回文件夹中所有文件的完整路径,可以和之前用同样的方法:

function getDirItemsAbsPath(dirName:string):string[]{
  let absPaths:string[] = [];
  fs.readdirSync(dirName, 'utf8', false).forEach((elem:string) => {
    absPaths.push(path.join(dirName,elem))
  });
  return absPaths
}
let dirName = 'G:\\mobile-flutter\\jcstdio\\assets\\coder\\statics\\css';
let dirItemsAbsPath = getDirItemsAbsPath(dirName)
console.log(dirItemsAbsPath);
[
  'G:\\mobile-flutter\\jcstdio\\assets\\coder\\statics\\css\\app.6605356c.css',
  'G:\\mobile-flutter\\jcstdio\\assets\\coder\\statics\\css\\chunk-vendors.574a9ade.css',
  'G:\\mobile-flutter\\jcstdio\\assets\\coder\\statics\\css\\login.6d2c5898.css'
]

(15)创建名为 path 指向 target 的链接:symlink()

function callBack(err) {
  if (err) throw err;
}
let target = 'G:\\NodeJs\\fs\\a.txt'
let path = 'C:\\Users\\a2911\\Desktop\\a_link'
fs.symlink(target, path, callBack);

成功执行后,可以看到path指定处产生了一个链接名为a_link(Windows中也称快捷方式)

该函数也有其对应的同步函数,为:symlinkSync()

用法基本类似。

目录
相关文章
|
28天前
|
存储 Java 计算机视觉
Java二维数组的使用技巧与实例解析
本文详细介绍了Java中二维数组的使用方法
44 15
|
26天前
|
存储 运维 资源调度
阿里云服务器经济型e实例解析:性能、稳定性与兼顾成本
阿里云经济型e云服务器以其高性价比、稳定可靠的性能以及灵活多样的配置选项,成为了众多企业在搭建官网时的首选。那么,阿里云经济型e云服务器究竟怎么样?它是否能够满足企业官网的搭建需求?本文将从性能表现、稳定性与可靠性、成本考虑等多个方面对阿里云经济型e云服务器进行深入剖析,以供大家参考选择。
106 37
|
2月前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
218 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
1月前
|
Serverless 对象存储 人工智能
智能文件解析:体验阿里云多模态信息提取解决方案
在当今数据驱动的时代,信息的获取和处理效率直接影响着企业决策的速度和质量。然而,面对日益多样化的文件格式(文本、图像、音频、视频),传统的处理方法显然已经无法满足需求。
93 4
智能文件解析:体验阿里云多模态信息提取解决方案
|
2月前
|
数据挖掘 vr&ar C++
让UE自动运行Python脚本:实现与实例解析
本文介绍如何配置Unreal Engine(UE)以自动运行Python脚本,提高开发效率。通过安装Python、配置UE环境及使用第三方插件,实现Python与UE的集成。结合蓝图和C++示例,展示自动化任务处理、关卡生成及数据分析等应用场景。
172 5
|
2月前
|
存储 网络协议 算法
【C语言】进制转换无难事:二进制、十进制、八进制与十六进制的全解析与实例
进制转换是计算机编程中常见的操作。在C语言中,了解如何在不同进制之间转换数据对于处理和显示数据非常重要。本文将详细介绍如何在二进制、十进制、八进制和十六进制之间进行转换。
93 5
|
3月前
|
存储 机器学习/深度学习 编解码
阿里云服务器计算型c8i实例解析:实例规格性能及使用场景和最新价格参考
计算型c8i实例作为阿里云服务器家族中的重要成员,以其卓越的计算性能、稳定的算力输出、强劲的I/O引擎以及芯片级的安全加固,广泛适用于机器学习推理、数据分析、批量计算、视频编码、游戏服务器前端、高性能科学和工程应用以及Web前端服务器等多种场景。本文将全面介绍阿里云服务器计算型c8i实例,从规格族特性、适用场景、详细规格指标、性能优势、实际应用案例,到最新的活动价格,以供大家参考。
|
6天前
|
弹性计算 JavaScript 前端开发
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
Node.js 是一种高效的 JavaScript 运行环境,基于 Chrome V8 引擎,支持在服务器端运行 JavaScript 代码。本文介绍如何在阿里云上一键部署 Node.js 环境,无需繁琐配置,轻松上手。前提条件包括 ECS 实例运行中且操作系统为 CentOS、Ubuntu 等。功能特点为一键安装和稳定性好,支持常用 LTS 版本。安装步骤简单:登录阿里云控制台,选择扩展程序管理页面,安装 Node.js 扩展,选择实例和版本,等待创建完成并验证安装成功。通过阿里云的公共扩展,初学者和经验丰富的开发者都能快速进入开发状态,开启高效开发之旅。
|
8天前
|
弹性计算 JavaScript 前端开发
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
|
1月前
|
JavaScript
nodejs安装之npm ERR! code CERT_HAS_EXPIREDnpm ERR! errno CERT_HAS_EXPIRED reason: certificate has expired-证书错误通用问题解决方案-优雅草央千澈
nodejs安装之npm ERR! code CERT_HAS_EXPIREDnpm ERR! errno CERT_HAS_EXPIRED reason: certificate has expired-证书错误通用问题解决方案-优雅草央千澈
139 26

热门文章

最新文章

推荐镜像

更多