【问题标题】:How can I run multiple npm scripts in parallel?如何并行运行多个 npm 脚本?
【发布时间】:2015-09-06 03:19:31
【问题描述】:

在我的package.json 我有这两个脚本:

  "scripts": {
    "start-watch": "nodemon run-babel index.js",
    "wp-server": "webpack-dev-server",
  }

每次我开始使用 Node.js 进行开发时,我都必须并行运行这 2 个脚本。我首先想到的是添加这样的第三个脚本:

"dev": "npm run start-watch && npm run wp-server"

...但这将等待start-watch 完成,然后再运行wp-server

如何并行运行这些命令?请记住,我需要查看这些命令的output。另外,如果您的解决方案涉及构建工具,我宁愿使用gulp 而不是grunt,因为我已经在另一个项目中使用它。

【问题讨论】:

  • &&顺序运行您的脚本,而&并行运行它们。
  • 一个快速的方法是npm run start-watch & npm run wp-server。这会将第一个命令作为后台线程运行。当其中一个命令运行时间不长并且以后不需要手动退出时,这非常有效。 concurrently 之类的东西可以让你用 CTRL-C 同时杀死所有线程。
  • @vsync 这适用于 Windows 吗?
  • @vsync 你确定吗?其他 cmets 说这不是它的工作原理,而且它在实践中对我不起作用。
  • @Clonkex,是的 但是 它不可靠,我使用 concurrently npm 包 代替,效果很好,我只使用 Windows跨度>

标签: javascript node.js build


【解决方案1】:

使用名为 concurrently 的包。

npm i concurrently --save-dev

然后设置您的npm run dev 任务:

"dev": "concurrently --kill-others \"npm run start-watch\" \"npm run wp-server\""

【讨论】:

  • node ./node_modules/concurrently/src/main.js 不是必需的。 concurrent 在脚本中可以正常工作,因为该模块将一个 bin 安装到 ./node_modules/.bin/concurrent
  • 还有parallelshell。我实际上建议concurrently 使用多个与控制台输出混淆的流(颜色可能会变得奇怪,光标消失),而parallelshell 没有that issue
  • @StijndeWitt 同时提到的错误现已在2.0.0 release 中修复。您可以使用--raw 模式在输出中保留颜色。
  • @StijndeWitt parallelshell 已被弃用,取而代之的是 npm-run-all github.com/keithamus/…
  • 我们必须有更好的方法来管理 Javascript 构建/运行脚本。这个平台的一切似乎都拼凑在一起。带有转义引号的引号和 npm 构建以调用其他“npm run”构建。这变得非常痛苦。
【解决方案2】:

如果您使用的是类 UNIX 环境,只需使用 & 作为分隔符:

"dev": "npm run start-watch & npm run wp-server"

否则,如果您对跨平台解决方案感兴趣,可以使用npm-run-all 模块:

"dev": "npm-run-all --parallel start-watch wp-server"

【讨论】:

  • 我这样做 - 有时当我“ctrl-c”npm 时,命令一直在后台挂起......有什么想法吗?
  • a && ba 成功完成后启动b,但nodemon 永远不会停止而没有错误,因此无法正常工作。 a & b 启动 a,将其移至后台并立即启动 b。赢! a | ba 的标准输出通过管道连接到b 的标准输入,这需要两者同时运行。尽管这似乎具有预期的效果,但您不应该在这里使用它。
  • @KamilTomšík & 是一个非常糟糕的主意,因为它分离了进程。这意味着npm 将不再是父进程。你最终会得到一个不会被ctrl-c 杀死的僵尸npm run start-watch
  • 只需添加wait 以缓解挂起进程的问题:"dev": "npm run start-watch & npm run wp-server & wait"
  • 它不是僵尸。但是 unix 上的 & 会阻止命令响应 C-c/C-z 并且还会阻止其返回代码在发生故障时传播。
【解决方案3】:

从 windows cmd 你可以使用start:

"dev": "start npm run start-watch && start npm run wp-server"

以这种方式启动的每个命令都从自己的窗口开始。

【讨论】:

  • 完美解决方案!我喜欢它启动新窗口。非常适合 VS2015 package.json 需求
  • 如果您有观察者任务,这将不起作用,因为&& 在开始第二个命令之前等待第一个命令完成,并且观察者任务永远不会完成。
  • @BennyNeugebauer 命令前面带有“start”命令,它为每个命令打开一个新的命令行。一开始我也很困惑,因为我认为“使用 && 运算符不起作用”。这个解决方案非常简单,不需要开发人员额外的包/工作。
  • 这是错误的。命令将按顺序运行。在 Windows 上,您必须使用插件才能同时运行命令。
  • 这也意味着我必须在你的项目上使用 windows 工作。
【解决方案4】:

您应该使用npm-run-all(或concurrentlyparallelshell),因为它对启动和终止命令有更多的控制权。运算符&| 是个坏主意,因为您需要在所有测试完成后手动停止它。

这是一个通过 npm 进行量角器测试的示例:

scripts: {
  "webdriver-start": "./node_modules/protractor/bin/webdriver-manager update && ./node_modules/protractor/bin/webdriver-manager start",
  "protractor": "./node_modules/protractor/bin/protractor ./tests/protractor.conf.js",
  "http-server": "./node_modules/http-server/bin/http-server -a localhost -p 8000",
  "test": "npm-run-all -p -r webdriver-start http-server protractor"
}

-p = 并行运行命令。

-r = 当其中一个命令以 0 退出代码结束时终止所有命令。

运行npm run test 将启动 Selenium 驱动程序,启动 http 服务器(为您提供文件)并运行量角器测试。完成所有测试后,它将关闭 http 服务器和 selenium 驱动程序。

【讨论】:

  • 我想知道这是如何正常运行测试的。虽然 webdriver-start 和 http-server 可以并行运行,但量角器任务应该只在前两者之后运行。
  • @asenovm 用于订单相关任务,为什么不直接使用gulpgulp-sync
【解决方案5】:

您可以使用一个& 并行运行脚本

"dev": "npm run start-watch & npm run wp-server"

Reference link

【讨论】:

  • 这也适用于 Windows 吗?抱歉,我对节点很陌生,我不知道如何验证这一点!
  • @BenisonSam 我在我的 Windows 电脑上试过,即使使用单个“&”它也不会运行第二个命令
  • 4 年前发布了相同的答案,并且获得的支持少于此。此外,已经充分讨论了为什么这种方法是一个坏主意。嗯,为什么这又有这么多的赞成票?
  • @MartinBraun 快速简单
  • tnx - 这就是答案 - 所有其他提到的解决方案都是矫枉过正
【解决方案6】:

更好的解决方案是使用&

"dev": "npm run start-watch & npm run wp-server"

【讨论】:

  • 不,不是更好,因为它不适用于所有平台。
  • 我不知道。哪些平台不支持? @Corey - 用互操作警告更新你的答案,我会支持你
  • & 在 Windows 上工作,但工作方式不同。在 OSX 上,它会同时运行两个命令,但在 Windows 上,它会运行第一个命令,当第一个命令存在后,它会运行第二个命令。
  • 不,不是因为它分离了进程,你将无法以简单的方式杀死它。
  • @ngryman 这也是我的预期。但是,我试过了,当你按下 Ctrl+C 时,它确实杀死了所有三个进程(dev、start-watch 和 wp-server)。
【解决方案7】:

我已经检查了上面几乎所有的解决方案,只有 npm-run-all 我能够解决所有问题。与所有其他解决方案相比,主要优势在于能够run script with arguments

{
  "test:static-server": "cross-env NODE_ENV=test node server/testsServer.js",
  "test:jest": "cross-env NODE_ENV=test jest",
  "test": "run-p test:static-server \"test:jest -- {*}\" --",
  "test:coverage": "npm run test -- --coverage",
  "test:watch": "npm run test -- --watchAll",
}

注意run-pnpm-run-all --parallel 的快捷方式

这允许我使用npm run test:watch -- Something 之类的参数运行命令。

编辑:

对于npm-run-all,还有一个更有用的option

 -r, --race   - - - - - - - Set the flag to kill all tasks when a task
                            finished with zero. This option is valid only
                            with 'parallel' option.

-r 添加到您的npm-run-all 脚本中,以在一个完成代码0 时终止所有进程。这在您运行 HTTP 服务器和另一个使用该服务器的脚本时特别有用。

  "test": "run-p -r test:static-server \"test:jest -- {*}\" --",

【讨论】:

  • 另一个有用的选项是 -l 或 --print-labels - 它将任务名称打印为每行输出的前缀,因此您可以将它们区分开来。色彩也很好。
【解决方案8】:

我有一个没有任何额外模块的跨平台解决方案。我一直在寻找可以在 cmd.exe 和 bash 中使用的 try catch 块之类的东西。

解决方案是command1 || command2,它似乎在两种环境中都适用。所以OP的解决方案是:

"scripts": {
  "start-watch": "nodemon run-babel index.js",
  "wp-server": "webpack-dev-server",
  // first command is for the cmd.exe, second one is for the bash
  "dev": "(start npm run start-watch && start npm run wp-server) || (npm run start-watch & npm run wp-server)",
  "start": "npm run dev"
}

那么简单的npm start(和npm run dev)将适用于所有平台!

【讨论】:

  • || 似乎无法在我的 Windows 10 PowerShell 上运行,但是,即使在 PowerShell 上,单个 | 似乎也能很好运行。我只用了两个命令进行了尝试,只能看到第二部分的输出,而不是第一部分的输出。
【解决方案9】:

如果将双 & 号替换为单 & 号,脚本将同时运行。

【讨论】:

  • 没错,就是简洁优雅,不需要依赖或者其他魔法。
  • @Ginzburg 因为并非所有平台都适用,就像您在其他答案中看到的那样。
【解决方案10】:

分叉怎么样

运行多个节点脚本的另一个选项是使用单个节点脚本,它可以fork 很多其他脚本。分叉在 Node 中是原生支持的,因此它不添加任何依赖项并且是跨平台的。


小例子

这只会按原样运行脚本并假设它们位于父脚本的目录中。

// fork-minimal.js - run with: node fork-minimal.js

const childProcess = require('child_process');

let scripts = ['some-script.js', 'some-other-script.js'];
scripts.forEach(script => childProcess.fork(script));

详细示例

这将运行带有参数并由许多可用选项配置的脚本。

// fork-verbose.js - run with: node fork-verbose.js

const childProcess = require('child_process');

let scripts = [
    {
        path: 'some-script.js',
        args: ['-some_arg', '/some_other_arg'],
        options: {cwd: './', env: {NODE_ENV: 'development'}}
    },    
    {
        path: 'some-other-script.js',
        args: ['-another_arg', '/yet_other_arg'],
        options: {cwd: '/some/where/else', env: {NODE_ENV: 'development'}}
    }
];

let runningScripts= [];

scripts.forEach(script => {
    let runningScript = childProcess.fork(script.path, script.args, script.options);

   // Optionally attach event listeners to the script
   runningScript.on('close', () => console.log('Time to die...'))

    runningScripts.push(runningScript); // Keep a reference to the script for later use
});

与分叉脚本通信

分叉还有一个额外的好处,即父脚本可以从分叉的子进程接收事件以及发回。一个常见的例子是父脚本杀死其分叉的孩子。

 runningScripts.forEach(runningScript => runningScript.kill());

有关更多可用事件和方法,请参阅ChildProcess documentation

【讨论】:

    【解决方案11】:
    npm-run-all --parallel task1 task2
    

    编辑:

    您需要事先安装npm-run-all。另请查看this page 了解其他使用场景。

    【讨论】:

      【解决方案12】:

      快速解决方案

      在这种情况下,我会说最好的选择 如果这个脚本是一个私有模块,只在基于 *nix 的机器上运行,你可以使用fork 进程的控制操作符,如下所示:&

      在部分 package.json 文件中执行此操作的示例:

      {
        "name": "npm-scripts-forking-example",
        "scripts": {
          "bundle": "watchify -vd -p browserify-hmr index.js -o bundle.js",
          "serve":  "http-server -c 1 -a localhost",
          "serve-bundle": "npm run bundle & npm run serve &"
        }
      

      然后您将通过npm run serve-bundle 并行执行它们。您可以增强脚本以将分叉进程的 pid 输出到文件,如下所示:

      "serve-bundle": "npm run bundle & echo \"$!\" > build/bundle.pid && npm run serve & echo \"$!\" > build/serve.pid && npm run open-browser",
      

      Google 类似 bash control operator for fork 以了解更多关于它的工作原理。我还提供了一些关于在以下 Node 项目中利用 Unix 技术的进一步背景:

      更多上下文 RE:Unix 工具和 Node.js

      如果您不在 Windows 上,Unix 工具/技术通常可以很好地使用 Node 脚本实现某些目标,因为:

      1. Node.js 的大部分内容都在模仿 Unix 原则
      2. 您使用的是 *nix(包括 OS X),而 NPM 无论如何都在使用 shell

      Nodeland 中用于系统任务的模块通常也是 Unix 工具的抽象或近似,从 fsstreams

      【讨论】:

      • 不,因为& 运算符在 Windows 上不受支持。
      • @StijndeWitt 我的帖子说“如果你不在 Windows 上......”。在世界上最大的科技公司之一,与我共事的人中有 0% 在 Windows 上运行 Node。很明显,我的帖子对许多开发者来说仍然很有价值。
      • 这是一种循环推理方式,不是吗?如果您像这样编写您的 npm 脚本,您将无法能够使用 Windows,因为它无法工作。所以没有人使用Windows,所以它不起作用也没关系......你最终得到了平台相关的软件。现在,如果需要做的事情很难跨平台做,那么这可能是一个很好的权衡。但是这里的这个问题非常容易使用标准的 npm 脚本来解决,例如 concurrentlyparallelshell
      • @StijndeWitt 我的推理都不是循环的。我做了一个没有推理的事实陈述。我们发布了 Node 开发人员常用的技术,其中许多人在 Linux 服务器上构建和部署。是的,如果它是用户级脚本,它应该可以在 Windows 上运行,但大多数 npm 脚本用于开发和部署——主要是在 *nix 机器上。关于您提到的模块a)同时调用parallelshell“标准”(每天约1500次下载远非NPMland的标准)和b)如果您需要额外的软件来并行处理,您不妨使用吞咽。
      • @StijndeWitt 我很高兴被告知这些模块 - 谢谢
      【解决方案13】:
      npm install npm-run-all --save-dev
      

      package.json:

      "scripts": {
        "start-watch": "...",
        "wp-server": "...",
        "dev": "npm-run-all --parallel start-watch wp-server"
      }
      

      更多信息:https://github.com/mysticatea/npm-run-all/blob/master/docs/npm-run-all.md

      【讨论】:

      • 不,它没有。
      【解决方案14】:

      只需将此 npm 脚本添加到根文件夹中的 package.json 文件即可。

      {
        ...
        "scripts": {
          ...
          "start": "react-scripts start", // or whatever else depends on your project
          "dev": "(cd server && npm run start) & (cd ../client && npm run start)"
        }
      }
      

      【讨论】:

        【解决方案15】:

        ...但是在运行 wp-server 之前会等待 start-watch 完成。

        为此,您必须在命令中使用start。其他人已经说明了,但这就是它的工作方式,您的代码如下:

        "dev": "npm run start-watch && npm run wp-server"

        应该是

        "dev": " start npm run start-watch && start npm run wp-server"

        这样做会为每个命令打开一个单独的实例并同时处理它们,就您最初的问题而言,这应该不是问题。 我为什么这么说?这是因为这些实例都会在您只运行 1 条语句时自动打开,这是您的初始目标。

        【讨论】:

        【解决方案16】:

        我遇到了&| 的问题,它们分别退出状态和抛出错误。

        其他解决方案希望使用给定名称运行任何任务,例如 npm-run-all,这不是我的用例。

        所以我创建了 npm-run-parallel,它异步运行 npm 脚本并在它们完成后报告。

        所以,对于您的脚本,应该是:

        npm-run-parallel wp-server start-watch

        【讨论】:

          【解决方案17】:

          我的解决方案类似于 Piittis 的解决方案,但我在使用 Windows 时遇到了一些问题。所以我必须验证win32。

          const { spawn } = require("child_process");
          
          function logData(data) {
              console.info(`stdout: ${data}`);
          }
          
          function runProcess(target) {
              let command = "npm";
              if (process.platform === "win32") {
                  command = "npm.cmd"; // I shit you not
              }
              const myProcess = spawn(command, ["run", target]); // npm run server
          
              myProcess.stdout.on("data", logData);
              myProcess.stderr.on("data", logData);
          }
          
          (() => {
              runProcess("server"); // package json script
              runProcess("client");
          })();
          

          【讨论】:

            【解决方案18】:

            在父文件夹的 package.json 中:

            "dev": "(cd api && start npm run start) & (cd ../client && start npm run start)"
            

            在 Windows 中工作

            【讨论】:

              【解决方案19】:

              使用 npm 运行多个并行脚本的分步指南。 全局安装 npm-run-all

              npm i -g npm-run-all
              

              现在在你的 package.json 所在的项目中安装并保存这个包

              npm i npm-run-all --save-dev
              

              现在以这种方式修改 package.json 文件中的脚本

              "scripts": {
                  "server": "live-server index.html",
                  "watch": "node-sass scss/style.scss --watch",
                  "all": "npm-run-all --parallel server watch"
              },
              

              现在运行这个命令

              npm run all
              

              在给定链接中有关此软件包的更多详细信息 npm-run-all

              【讨论】:

                【解决方案20】:

                就我而言,我有两个项目,一个是 UI,另一个是 API,它们在各自的 package.json 文件中都有自己的脚本。

                所以,这就是我所做的。

                npm run --prefix react start&  npm run --prefix express start&
                

                【讨论】:

                • 喜欢您的解决方案。还有 UI (node app) 和 API (Angular 在子文件夹 src,猜测是cd src/ng serve),只有第一部分有效.例如node app& cd src& ng serve.
                【解决方案21】:

                简单的节点脚本让您轻松进行。使用 readline 组合输出,这样行就不会被破坏。

                const { spawn } = require('child_process');
                const readline = require('readline');
                
                [
                  spawn('npm', ['run', 'start-watch']),
                  spawn('npm', ['run', 'wp-server'])
                ].forEach(child => {
                    readline.createInterface({
                        input: child.stdout
                    }).on('line', console.log);
                
                    readline.createInterface({
                        input: child.stderr,
                    }).on('line', console.log);
                });
                

                【讨论】:

                  【解决方案22】:

                  我已经使用npm-run-all 有一段时间了,但我一直没有适应它,因为在监视模式下命令的输出不能很好地协同工作。例如,如果我在监视模式下启动 create-react-appjest,我将只能看到我运行的最后一个命令的输出。所以大多数时候,我都是手动运行所有命令...

                  这就是为什么我实现了自己的库run-screen。它仍然是一个非常年轻的项目(从昨天开始 :p )但它可能值得一看,在你的情况下它会是:

                  run-screen "npm run start-watch" "npm run wp-server"
                  

                  然后按数字键1查看wp-server的输出,按0查看start-watch的输出。

                  【讨论】:

                  • 我使用 npm-run-all 并在终端中获得两个进程的输出。
                  • 对,我认为他们在处理输出的方式上做了一些更新,我最近使用npm-run-all,到目前为止似乎工作得很好。
                  【解决方案23】:

                  这对我有用

                  {
                  "start-express": "tsc && nodemon dist/server/server.js",
                  "start-react": "react-scripts start",
                  "start-both": "npm -p -r run start-react && -p -r npm run start-express"
                  }
                  

                  客户端和服务器都是用 typescript 编写的。

                  React 应用是使用 create-react-app 和 typescript 模板创建的,并且位于默认的 src 目录中。

                  express在server目录下,入口文件是server.js

                  typescript代码并转译成js放在dist目录下

                  查看我的项目了解更多信息:https://github.com/nickjohngray/staticbackeditor

                  更新: 调用 npm run dev,开始工作

                  {"server": "tsc-watch --onSuccess \"node ./dist/server/index.js\"",
                  "start-server-dev": "npm run build-server-dev && node src/server/index.js",
                  "client": "webpack-dev-server --mode development --devtool inline-source-map --hot",
                  "dev": "concurrently \"npm run build-server-dev\"  \"npm run server\" \"npm run client\""}
                  

                  【讨论】:

                  • 我已经更新了我的脚本,我认为这是有效的,我在上面发布了我的更新
                  • npm 上的 -p 和 -r 是什么?
                  【解决方案24】:

                  您还可以使用prepost 作为特定脚本的前缀。

                    "scripts": {
                      "predev": "nodemon run-babel index.js &",
                      "dev": "webpack-dev-server"
                    }
                  

                  然后运行: npm run dev

                  【讨论】:

                    【解决方案25】:

                    在 Linux 上仅使用 shell 脚本。

                    "scripts": {
                      "cmd": "{ trap 'trap \" \" TERM; kill 0; wait' INT TERM; } && blocking1 & blocking2 & wait"
                    }
                    

                    npm run cmd 然后 ^C 将杀死孩子并等待干净的退出。

                    【讨论】:

                      【解决方案26】:

                      Windows CMD 的简单原生方式

                      "start /b npm run bg-task1 && start /b npm run bg-task2 && npm run main-task"
                      

                      start /b 表示在后台启动)

                      【讨论】:

                        【解决方案27】:

                        由于您可能需要向此脚本添加越来越多的内容,因此它会变得混乱且难以使用。如果您需要一些条件来检查,需要使用变量怎么办?所以我建议你看看google/zx允许使用js创建脚本。

                        简单用法:

                        1. 安装zx:npm i -g zx
                        2. 添加package.json 命令(可选,您可以将所有内容移至脚本):
                          "scripts": {
                            "dev": "zx ./scripts/dev.mjs", // run script
                            "build:dev": "tsc -w", // compile in watch mode
                            "build": "tsc", // compile
                            "start": "node dist/index.js", // run
                            "start:dev": "nodemon dist/index.js", // run in watch mode
                          },
                        
                        1. 创建dev.mjs脚本文件:
                        #!/usr/bin/env zx
                        
                        await $`yarn build`; // prebuild if dist is empty
                        await Promise.all([$`yarn start:dev`, $`yarn build:dev`]); // run in parallel
                        

                        现在,每次您想启动开发服务器时,只需运行 yarn devnpm run dev

                        它会先编译 ts->js,然后在 watch 模式下并行运行 typescrpt 编译器和服务器。当你改变你的 ts 文件时->它将被 tsc 重新编译->nodemon 将重新启动服务器。


                        高级编程用法

                        加载环境变量,在监视模式下编译 ts 并在更改时从 dist 重新运行服务器(dev.mjs):

                        #!/usr/bin/env zx
                        import nodemon from "nodemon";
                        import dotenv from "dotenv";
                        import path from "path";
                        import { fileURLToPath } from "url";
                        
                        // load env variables
                        loadEnvVariables("../env/.env");
                        
                        await Promise.all([
                          // compile in watch mode (will recompile on changes in .ts files)
                          $`tsc -w`,
                          // wait for tsc to compile for first time and rerun server on any changes (tsc emited .js files)
                          sleep(4000).then(() =>
                            nodemon({
                              script: "dist/index.js",
                            })
                          ),
                        ]);
                        
                        function sleep(ms) {
                          return new Promise((resolve) => {
                            setTimeout(resolve, ms);
                          });
                        }
                        
                        function getDirname() {
                          return path.dirname(fileURLToPath(import.meta.url));
                        }
                        
                        function loadEnvVariables(relativePath) {
                          const { error, parsed } = dotenv.config({
                            path: path.join(getDirname(), relativePath),
                          });
                        
                          if (error) {
                            throw error;
                          }
                        
                          return parsed;
                        }
                        

                        【讨论】:

                          【解决方案28】:

                          一个好的老式Makefile怎么样?

                          这允许您进行大量控制,包括如何管理子 shell、脚本之间的依赖关系等。

                          
                          # run both scripts
                          start: server client
                          
                          # start server and use & to background it
                          server:
                              npm run serve &
                          
                          # start the client
                          client:
                              npm start
                          
                          

                          调用这个Makefile 然后你就可以输入

                          make start 启动一切。因为当你 ctrl-C 时 server 命令实际上是在 start 命令的子进程中运行的,所以 server 命令也将停止——这与你自己在 shell 中将其设置为后台不同。 Make 还为您提供命令行补全,至少在我正在使用的 shell 上。奖励 - 第一个命令将始终运行,因此您实际上只需在此处输入make

                          我总是在我的项目中添加一个 makefile,以便稍后在我在它们之间切换时快速扫描每个项目的所有常用命令和参数。

                          【讨论】:

                            【解决方案29】:

                            我认为最好的方法是使用npm-run-all,如下所示:

                            1- npm install -g npm-run-all 2- npm-run-all --parallel server client

                            【讨论】:

                              猜你喜欢
                              • 2018-06-06
                              • 1970-01-01
                              • 2017-12-11
                              • 2013-04-26
                              • 2023-03-22
                              相关资源
                              最近更新 更多