简介:NSSM是一款将Spring Boot应用封装为Windows后台服务的轻量级工具,其最大特点是无需复杂配置、操作直观,特别适合需要让Java程序常驻运行、开机自启的中级开发者与运维人员。这份nssm-2.24.zip压缩包共包含35个文件,既包含可直接运行的win32与win64两个平台下的nssm.exe可执行程序,也有完整的C++源码(13个.h与12个.cpp)、Visual Studio工程文件(sln/vcproj)、版本脚本与资源描述文件(rc/mc/cmd/ico),以及ChangeLog、README等说明文档,既能开箱即用,又能深入阅读源码了解Windows服务封装原理。整个压缩包仅有344KB,体积小巧却功能完备。目前已有268位用户学习使用,使用时可仅通过指定Java执行文件、jar包路径与工作目录等几个关键步骤,便为Spring Boot应用生成可靠的后台服务;NSSM还内置日志记录、崩溃自动重启与恢复机制,让服务运行状态透明、排障高效,显著提升Windows服务器上Java应用部署的效率与稳定性。
1. 先把话说清楚:nssm-2.24.zip 是 Windows 服务化绕不开的那个小工具
nssm-2.24.zip 里的 NSSM(Non-Sucking Service Manager)2.24,是把普通 exe、bat、python 脚本注册成 Windows 服务的第三方小工具。长期跑批处理、后台程序、内网工具和监控脚本的运维与开发,遇到「开机自启 + 崩溃拉起 + 无人值守」这类需求时,Windows 自带的 sc 命令和任务计划程序往往不够用,而 NSSM 用一条命令就能把任意程序托管成真正的 Windows 服务。这个 zip 包解决的是从业者最常见的一类问题:怎么让一个没有服务实现的可执行程序,以服务形态稳定常驻,并能在异常退出后自动重启。适合所有需要把进程「服务化」的 Windows 场景,从个人开发机到生产服务器都适用。
2. NSSM 2.24 凭什么还在被大量使用:先把它放进 Windows 服务生态里看
2.1 为什么是 NSSM,而不是 sc create、任务计划或 instsrv
很多人在第一次接触「把 exe 注册成服务」时,第一反应是 sc create。sc create 能把可执行文件挂到服务管理器的名下,Service Control Manager(SCM)也认它,但 sc create 不会替你处理工作目录、不会传参数、不会在崩溃后拉起进程,也不会把 stdout/stderr 重定向到文件。换句话说,sc create 只管「注册」这一步,运行期策略几乎是裸的。
任务计划程序(Task Scheduler)是另一条路。它能设置开机触发、按账号运行,但任务计划里跑的进程没有服务身份,SCM 不管理它,状态查询、依赖关系、失败重启策略和真正服务的语义相差很大。很多程序判断自己是否被信任运行,会检查 Session 0 和 SCM 句柄,任务计划跑出来的环境会表现异常。
NSSM 2.24 的定位正是补这两块空白。它作为服务进程的「外壳」,由 SCM 拉起,然后 NSSM 再去拉起真正的目标程序,并承担三件 sc 和任务计划都不愿意做的事:监视子进程状态、退出后按策略重启、把目标程序的输出接入日志文件。这个「服务外壳 + 业务子进程」的双进程模型,是 NSSM 稳定性的根本来源。
2.2 解压 nssm-2.24.zip 后先认清包里的结构
拿到 nssm-2.24.zip 之后,先别急着双击。这个版本包内部一般按平台分目录组织,常见的命名是 win32 和 win64 各放一份 nssm.exe,外加源代码和文档。我拿到压缩包后的固定顺序是:先右键查看属性里的数字签名,确认文件完整;再解压到固定目录(如 C:\tools\nssm-2.24),并且全程用管理员权限操作。
一个容易被忽略的事实是:NSSM 服务的位宽必须和要托管的程序匹配。64 位系统上跑 32 位程序,服务外壳建议用 32 位 nssm.exe;反过来要托管的程序是 64 位,外壳也尽量用 64 位。虽然 NSSM 在多数场景下能跨位宽工作,但一旦目标程序依赖特定位数下的 DLL 或重定向,外壳选错会让注册出来的服务行为变得很诡异。
用命令行校验解压结果,我一般会这样做:
cd C:\tools\nssm-2.24 # 查看 nssm 版本,确认可执行文件能正常加载 .\win64\nssm.exe version # 列出注册表中 NSSM 已经登记过的服务 .\win64\nssm.exe status all第一行输出 NSSM 的版本号,如果提示缺少 DLL 或无法运行,说明系统缺运行库或文件拷贝不完整。第二行会列出当前机器上所有已经被 NSSM 接管的服务及其运行状态,在动手安装新服务之前先看一眼,能避免后面出现服务名冲突。我之所以坚持多跑这一条,是因为有些机器上之前装过别的 NSSM 服务,重名时 nssm install 会直接覆盖旧服务的配置,这个误操作非常难排查。
自检这一遍只需要几秒钟,却能把「位宽选错」「文件损坏」「服务名已存在」这三类最常见的问题挡在最前面。很多老手推荐直接看文档目录里的 usage 部分也是同样的道理,先建立对参数的心智模型,后面配置时才不会漏。
2.3 32 位与 64 位 nssm.exe 的混用陷阱
NSSM 2.24 的 zip 包在 win32 和 win64 目录下各放了一个 nssm.exe,文件大小不同,行为也有细微差别。64 位系统上最容易犯的错,是把 win32 目录里的 exe 当成「通用版」来用,然后发现服务日志里出现路径被改写、环境变量丢失的现象。这是因为 32 位进程运行在 WOW64 兼容层里,访问 Program Files、System32 等路径时会被系统重定向到 SysWOW64,目标程序如果依赖原生 64 位路径,自然就找不到文件。
我能给出的选择建议很简单:先确认目标程序是几位的,任务管理器里查一下它的 Image 类型是 32 位还是 64 位;然后让 NSSM 外壳和它对齐。如果目标程序是脚本解释器(比如 python.exe、node.exe),直接看解释器本身的位宽即可,而不是看脚本文件的后缀名。选定之后,把对应目录下的 nssm.exe 单独复制到部署目录里使用,不要两套混着用,避免维护时搞不清哪次注册用的是哪个外壳。
另外还有一个细节:NSSM 注册服务时会把当前用的 nssm.exe 的完整路径写进注册表,之后运行和卸载都依赖这个路径。所以如果先把 nssm.exe 放在临时目录里测试,服务注册成功后再把 exe 挪走或删除,服务就会因为找不到外壳文件而启动失败。正确做法是先把 nssm.exe 放到最终要落地的固定目录,再执行注册命令,这样路径永远有效。这个「先落位、再注册」的习惯,几乎能规避掉一半的 NSSM 求助帖。
3. 把普通 exe 注册成 Windows 服务:最小可复现的两条路径
3.1 图形界面路径:nssm install 之后填五个字段
如果只是想快速把某个程序托起来,不打算写部署脚本,GUI 是最省事的入口。在管理员命令行里执行 nssm install <服务名>,会弹出 NSSM 的配置窗口。这个窗口的第一页只涉及五个必填项:Application 的完整路径、Startup directory(工作目录)、Arguments(启动参数)、Service name(服务显示名)。服务名在安装前就定好了,窗口里还会让你确认一次。
我在第一次接触这个工具时就走 GUI,因为每个字段旁边都有提示,尤其是 Startup directory 这一项,GUI 会明确提示「要填目标程序实际运行时所在的目录,而不是 exe 所在的目录」。这两者在大多数情况下相同,但遇到程序需要在固定目录下读取配置文件的场景,工作目录一旦填错,程序能启动但立刻报「找不到配置文件」,表现形式和崩溃非常像。
GUI 里我一般还会顺手把 Log 目录填上。标准做法是在配置窗口的 I/O 标签页里,把 Output(stdout)、Error(stderr)分别指到两个独立的日志文件路径,比如 C:\logs\mysvc.out.log 和 C:\logs\mysvc.err.log。这样注册完成后,程序打出的所有内容都会落盘,排查问题时不需要重新挂调试器。
GUI 操作看起来零门槛,但有一点要注意:配置窗口点完 Install service 按钮后,服务只是被创建,并没有立即启动。如果此时去 services.msc 里看,服务状态可能是已停止。必须回到命令行手动 net start <服务名>,或者右键选择启动。这一步经常被新手漏掉,造成「我明明装了为什么没跑起来」的错觉。
3.2 命令行路径:一条命令完成注册并配置核心参数
生产环境里我不太想依赖 GUI,因为交付脚本要被反复执行,GUI 无法回放。NSSM 2.24 的命令行能力足够完成整个过程,常见做法是先把 install 把服务登记进 SCM,再用一组 nssm set 命令写运行参数。
# 假设要把 C:\apps\myapp\run.bat 注册为服务(管理员身份执行) cd C:\tools\nssm-2.24\win64 # 1. 注册服务,服务名为 MyAppSvc .\nssm.exe install MyAppSvc "C:\apps\myapp\run.bat" # 2. 设置工作目录为程序所在目录 .\nssm.exe set MyAppSvc AppDirectory "C:\apps\myapp" # 3. 设置进程异常退出时的重启延迟(单位毫秒),这里设 10 秒 .\nssm.exe set MyAppSvc AppRestartDelay 10000 # 4. 把标准输出和标准错误分别写入日志文件 .\nssm.exe set MyAppSvc AppStdout "C:\logs\MyAppSvc.out.log" .\nssm.exe set MyAppSvc AppStderr "C:\logs\MyAppSvc.err.log" # 5. 启动服务 .\nssm.exe start MyAppSvc逐条拆开看:install 命令把 run.bat 登记为服务,这一步不要求目标程序本身是常驻进程,批处理里的程序只要不退出,NSSM 就会认为服务活着。AppDirectory 决定子进程启动时的当前目录,很多程序读配置用的是相对路径,这一项错了服务能启动但逻辑跑偏。AppRestartDelay 后面跟的是毫秒值,10000 就是 10 秒,设置成 0 或过短会导致崩溃后立刻重启、连续崩溃时 CPU 被反复占满。AppStdout 和 AppStderr 是 NSSM 最实用的能力之一,把子进程的输出直接落盘,省掉自己写日志重定向的功夫。
这套命令里最容易出的问题是把 install 写成了 nssm install MyAppSvc "C:\apps\myapp\run.bat" extra_args,而 NSSM 2.24 对 install 命令的语法要求是服务名跟在 install 后面,可执行文件路径和参数要分开写。如果在同一个引号字符串里混入参数,NSSM 会把整串当成程序路径,服务注册出来执行时必然失败。正确写法是先 install,再用 AppParameters 单独设置参数,下面这一节会展开。
3.3 服务注册成功的验证方法
服务注册完不能只看 services.msc 里的状态列,那只是 SCM 视角下的一个快照。我更习惯用命令验证三个层面:服务是否在 SCM 里、进程是否真的起来、子进程是否处于预期状态。
# 查服务状态:STATE 是 RUNNING 才算真正起来 sc query MyAppSvc # 查进程:确认 run.bat 对应的解释器进程确实在跑 tasklist /fi "IMAGENAME eq cmd.exe" # 查 NSSM 视角下的状态:会显示 Exit code / AppPID 等细节 .\nssm.exe status MyAppSvc # 服务启动后立刻停止的话,看一眼系统事件日志里的错误码 Get-EventLog -LogName System -Source "Service Control Manager" -Newest 5 | Format-Listsc query 返回的 STATE 为 RUNNING 只代表 NSSM 这个外壳被 SCM 拉起来了,不代表业务程序本身还活着。任务管理器里查 cmd.exe 的进程数,能确认批处理解释器是否被 NSSM 作为子进程创建。nssm status 输出里的 AppPID 字段是 NSSM 托管的实际子进程 PID,这个值比 tasklist 更精确。事件日志里如果出现 1053、1058 这类错误码,则说明外壳在启动阶段就超时或失败,要回到 2.2 节的位宽和路径检查上。
4. 让 NSSM 2.24 服务长期稳定跑:四个必调的参数
4.1 AppDirectory 与 AppParameters:决定服务「能不能正常干活」的一对组合
很多人把 AppDirectory 当成可有可无的配置,但生产环境里它几乎是第一排查项。Windows 服务由 SCM 启动时,默认工作目录是 C:\Windows\System32,而不是你的程序所在目录。如果程序用相对路径打开数据文件或配置文件,就会在 System32 下找不到文件而异常退出。所以我在注册每个服务前都会确认一件事:这个程序依赖哪个目录下的相对路径,然后把 AppDirectory 指到那里。
AppParameters 则解决传参问题。install 命令里可以直接附参数,但那种写法在后续修改时很不方便。更稳妥的做法是在 install 之后再单独设置:
.\nssm.exe set MyAppSvc AppParameters "--config=C:\apps\myapp\conf.ini --daemon"设置之后可以用 nssm get 回读确认。AppParameters 里的引号处理是 NSSM 2.24 版本里容易踩的地方,如果参数里本身含有空格,要给整个参数值套上英文引号,NSSM 会把引号内的内容当作单一参数传给子进程。若参数里还嵌套引号(比如路径里带空格又用引号括起来),建议直接使用命令行转义写法,或者先把程序路径改成不包含空格的短路径,能省掉很多烦恼。
这两个参数合起来解决的是同一件事:让子进程在服务环境下,看到的目录和参数与在命令行手工运行时完全一样。只要这两项和手工运行时一致,后面 NSSM 的重启和日志功能才有意义,否则服务注册得再漂亮,程序本身也起不来。
4.2 AppExit 与 AppRestartDelay:崩溃重启的策略要显式写
NSSM 对子进程退出有一套自己的处理逻辑,默认情况下子进程正常退出(退出码 0)后服务也会跟着停止,子进程非正常退出时 NSSM 会按 AppRestartDelay 的间隔重启它。这个默认行为对很多「希望常驻」的服务来说不够用,因为它无法区分「程序自己主动退出」和「崩溃退出」,而很多后台程序会把异常错误用非零退出码返回。
我的习惯是显式配置退出码策略。AppExit 的语法是 nssm set <服务名> AppExit <退出码> <动作>,动作可以是 Restart、Exit、Ignore 中的一个。常见做法是让所有非零退出码都触发重启:
# 对退出码 1 到 3 设置重启动作 .\nssm.exe set MyAppSvc AppExit 1 Restart .\nssm.exe set MyAppSvc AppExit 2 Restart .\nssm.exe set MyAppSvc AppExit 3 Restart # 退出码 0 时也可以重启(适合常驻服务,任何退出都拉起) .\nssm.exe set MyAppSvc AppExit 0 Restart # 重启间隔设保守一些:30 秒,避免崩溃抖动 .\nssm.exe set MyAppSvc AppRestartDelay 30000要特别注意 AppRestartDelay 的单位是毫秒,写成 30 表示 30 毫秒,这会让崩溃循环变得非常可怕。我一般最小设到 10 秒,生产环境常设在 30 秒到 60 秒。原因是程序频繁崩溃时,快速重启不但没有意义,还会把日志文件刷得很大,甚至拖垮 CPU。遇到需要快速恢复的业务,可以把间隔调小,但建议同时配一个外部监控来接管多次拉起仍失败的服务,而不是只依赖 NSSM 自己的重启。
还容易踩的一个坑是:把 AppExit 0 也设为 Restart 后,如果程序是「收到停止信号就正常退出」的类型,服务将永远无法被手动停止,因为停服务时 NSSM 会给子进程发终止信号,子进程以 0 退出后又被立即重启。这种情况需要先把 AppExit 0 改回 Exit,再停止服务。遇到这类服务,我建议在 AppExit 策略里保留 0 的 Exit 动作,除非确认程序退出码 0 一定代表异常。
4.3 AppStdout 与 AppStderr:日志重定向的三种写法
NSSM 2.24 日志功能很实用,但有一些边界要说清楚。它会接管子进程的控制台输出,并追加写入指定文件。配置方式和前面命令行示例一致,但关于日志有三个细节值得单独拿出来讲。
第一个是文件路径的目录必须存在。NSSM 不会自动创建日志目录,AppStdout 指向的文件夹如果不存在,服务启动时 NSSM 会尝试打开文件并失败,然后导致服务无法启动。常见做法是先手动新建日志目录,并给目录设好写权限,服务账户要有对该目录的写入权限。
第二个是日志文件默认无限追加,不会自动切割。NSSM 2.24 提供按大小滚动日志的选项,配置项是 AppRotateFiles、AppRotateBytes 等。我一般在日志量大的服务上把滚动打开,按 10MB 或 50MB 切分,避免单个文件膨胀到几 GB 后无法打开。
第三个是编码问题。如果目标程序输出的是 UTF-8 文本,而 NSSM 默认按系统 ANSI 代码页写入,中文日志会变成乱码。解决方法是让目标程序输出前明确使用 UTF-8,或者在 NSSM 侧配置合适的代码页。这个坑在纯英文程序上不存在,但只要涉及中文日志几乎都会遇到,建议注册服务时就把日志格式统一成 UTF-8,并在验证阶段查看一次日志内容。
另外,如果目标程序自己不往 stdout 写任何内容,AppStdout 配了也看不到东西。有些 GUI 程序把日志写到自己的文件里,NSSM 的日志重定向对它就无效,这时候不需要把 AppStdout 指到那个文件,因为 NSSM 只抓控制台句柄,不抓程序内部的磁盘日志。
4.4 服务账户与 Session 0 隔离:GUI 程序被「静默」的真正原因
Windows 服务默认在 Session 0 中运行,这是一个不加载交互桌面的会话。如果直接把一个带界面的 GUI 程序注册成 NSSM 服务,程序可能启动正常,但你看到的桌面不会弹任何窗口,因为窗口被隔离在 Session 0 里。这是系统设计,不是 NSSM 的问题,但 NSSM 2.24 默认不帮你绕过。
如果业务上确实需要服务进程能被当前登录用户看到界面,常见做法是:第一,把服务账户设成某个具体的本地账户或有交互权限的账户;第二,在服务的属性里勾选「允许服务与桌面交互」;第三,在 NSSM 的配置里设置合适的账户。但说实话,即使这样配置,现代 Windows 对 Session 0 的隔离仍然很严格,跨会话显示窗口经常失败,所以最可靠的选择还是把程序改成不需要界面的后台模式。
账户选择上还有另一层考虑:服务默认以 LocalSystem 运行,权限很大,但如果目标程序需要访问某个网络共享或特定用户的配置文件,LocalSystem 反而会因为缺少用户上下文而失败。我会在注册前先用目标账户手工运行一次程序,确认权限足够,再在 NSSM 的配置里填入账号密码。填写账户密码后,密码更改时服务会失效,这也是一个容易被忽略的运维维护项。
5. NSSM 2.24 避坑:从安装到卸载的 5 个常见翻车瞬间
5.1 现象:服务启动后秒退,状态一直停在 Stopped,事件日志报 1053
服务管理器里点启动,状态从 Starting 变回 Stopped,系统事件日志里出现错误 1053(服务没有及时响应启动请求)。先别急着怀疑 NSSM 有问题。这个错误的常见原因有三个:注册时写进 AppDirectory 的路径不存在,导致子进程创建失败;AppStdout 指向的日志目录不存在,NSSM 打开日志文件失败;或者目标程序是交互式 GUI,在 Session 0 里没有可用的显示环境而退出。
按顺序排查:先确认 AppDirectory 和日志目录都存在且有权限;再把目标程序放到命令行前台跑一次,看它是否依赖交互界面;最后用 nssm get 把刚刚设置的所有参数导出来核对。大部分 1053 都能在这一步解决。
5.2 现象:服务状态是 RUNNING,但端口没监听,进程列表里也找不到目标进程
这是 NSSM 典型的「外壳起来了,子进程没起来」状态。sc query 显示 RUNNING 指的是 NSSM 服务外壳在跑,它可能一直处于等待状态。原因多半是 AppParameters 里的参数被 NSSM 拆分错误,导致目标程序启动即退出;或者目标程序是被 NSSM 启动后立刻 fork 出子进程,然后主进程主动退出,NSSM 误认为程序已结束。
这种情况下的处理方法是先看 nssm status <服务名> 里的 AppPID 是否为 0,为 0 说明 NSSM 根本没有持有存活子进程。再去检查程序自身的日志,看它是真的退出还是已经以守护进程模式脱离。如果是 fork 型程序,建议让 NSSM 托管的不是主程序而是最外层的守护启动脚本,让 NSSM 一直持有那个不会退出的父进程。
5.3 现象:卸载服务后 exe 被占用,程序文件删不掉
nssm remove <服务名> 确认后,服务从 SCM 里消失,但原程序的 exe 文件仍然显示被占用。这通常不是 NSSM 的问题,而是目标程序自身还有进程没退出,或者是它加载的 DLL 句柄没有被释放。NSSM 移除服务时不会去 kill 业务进程,它只把外壳的登记信息清掉。
解决方法是先到任务管理器里结束残留的进程,再删除文件。如果进程反复重启,检查是否还有另一个 NSSM 服务在托管同一个程序,或者任务计划里还有同名任务。在卸载前先 nssm stop <服务名>,等几秒再 remove,能减少这种残留。
5.4 现象:日志文件一直不产生,或产生了但全是乱码
日志文件不产生的第一嫌疑是 AppStdout 路径的目录不存在;第二嫌疑是服务账户没有那个目录的写权限,NSSM 打开文件失败后不会报错,服务照样启动。我习惯在注册时就把日志目录权限发给服务账户,避免用 LocalSystem 测试通过、换具体账户后失效的情况。
乱码问题基本是编码不一致,前面 4.3 已经说了,这里补一句:如果程序本身能配置输出编码,优先让程序输出与 NSSM 预期一致;如果程序无法配置,只能让 NSSM 侧去适配。日志乱码不会影响服务运行,但会严重影响排查效率,属于那种不致命却非常恼人的问题。
5.5 现象:x64 系统上注册的服务偶尔访问到 SysWOW64 下的文件
有些服务运行中读配置文件,路径明明是 C:\Windows\System32\drivers\etc\hosts,结果读到的却是重定向后的内容,因为外壳是 32 位进程,文件系统重定向把它指向了 SysWOW64。这就是 2.3 节说的位宽混用问题。一旦出现,把 nssm.exe 换成 64 位版本,重新注册服务即可。判断是不是这个原因,可以用 Process Explorer 看服务进程的 Image 类型,或者观察程序读取的关键路径是否被改写。
说到底,NSSM 2.24 本身不是黑匣子,它的行为基本都在文档和配置项里。这五个翻车场景有一个共同点:都是把服务外壳和业务进程混为一谈导致的。只要时刻记住「NSSM 只负责托管,业务进程自己的生命周期和行为还是它自己的」,排查方向就不会跑偏。血泪经验是:动手前先花三分钟把目标程序放到命令行前台跑一遍,这一步能过滤掉一半以上问题。
6. 真正值得长期保留的习惯:注册前先做一次前台冒烟验证
我发现很多人拿到 nssm-2.24.zip 之后直接注册服务,出了问题再去查日志,这是把顺序反了。Windows 服务和手工运行最大的区别在于运行环境,而不是程序本身的逻辑。所以我的固定流程是:在注册之前,先打开一个普通命令行,用与 NSSM 完全相同的参数组合把程序跑一次,观察三件事——它会不会退出、它依赖哪些相对路径、它往 stdout 写什么。
# 前台冒烟,模拟 NSSM 将要用到的目录和参数 cd C:\apps\myapp C:\apps\myapp\run.bat --config=C:\apps\myapp\conf.ini --daemon如果前台跑不了,直接别注册服务,先解决程序自身的问题。前台能跑后,再核对四个关键配置:AppDirectory 要等于 cd 到的目录,AppParameters 要等于 --config 后面的内容,AppStdout 指向的日志目录要存在,服务账户对程序目录和日志目录都要有权限。这三项对齐之后,注册出来的服务成功率接近十成。
服务上线后,我还会把 nssm dump 的输出保存一份。nssm dump <服务名> 会把该服务的全部配置导出为标准键值格式,相当于给服务留了一份后悔药。之后不管是迁移机器还是重建服务,直接对照这份 dump 逐项 nssm set 回去即可,不用靠记忆重建配置。这也是一种很轻量的配置版本管理方式。
另一个值得培养的习惯是:不要在一个 nssm.exe 副本上注册多个服务后,随手把它删掉。我在一次环境清理里,把 nssm.exe 放在部署临时目录注册了四个服务,之后清临时文件时把整个目录删了,结果这四个服务全部启动失败,因为注册表里记录的外壳路径已经不存在。后来我养成了习惯:先把 nssm.exe 复制到 C:\tools\nssm-2.24\win64 这样的独立目录,再开始注册,卸载时也最后再动这个目录。
NSSM 2.24 本身已经没有在频繁更新,但稳定性和参数设计让它到今天仍是 Windows 服务化最实用的方案之一。它不负责替你写好业务程序,只是给你一个可重复、可脚本化、可观测的托管层。把这层用明白,你的 Windows 服务运维会顺手很多。这是我在多台机器上反复折腾后最想留给你的一句话。希望帮到你。
本文还有配套的精品资源,点击获取