做Java桌面应用或者内部系统交付的时候我猜不少人都被“部署环境”折磨过。客户电脑上没有JDK要么自己手动下一个要么让客户装MySQL更麻烦安装包一步步点root密码、字符集、端口稍不注意就装崩。实际上把MySQL、JDK和你的JAR包一起打进一个exe双击之后自动部署、自动建库、自动启动这条路完全走得通。我这套方案就是用Inno Setup把这三样东西做成一个一键安装包从MySQL免安装版到JDK内置版再到Spring Boot的可执行JAR全部揉进一个安装程序里。整个过程我已经在好几台裸机上反复验证过今天把完整思路、脚本和踩坑记录原样整理出来希望能帮你少走点弯路。这个方案适合谁主要三类人一是交付桌面型Java应用的开发人员客户不懂环境配置二是企业内部工具维护者需要在几十台电脑上快速部署统一环境三是想学习Inno Setup高级用法的打包爱好者。本文不假设你有Inno Setup基础但默认你熟悉Java开发、知道Spring Boot的JAR包怎么启动MySQL的基本操作也得会一点。1. 整体设计方案与选型思路1.1 为什么选Inno Setup而不是其他打包工具先回答一个最常被问到的问题打包工具那么多为什么非要用Inno SetupWindows平台上的安装包制作工具大致分几类。InstallShield是商业大厂货功能强大但配置复杂学习曲线陡一个小型安装包愣是能写几百个配置项。NSIS走脚本路线能做很小巧的安装包压缩率也不错但它的脚本语法偏底层想实现“安装过程中自动改环境变量、等待MySQL初始化完成”这种操作写起来很绕。WiX Toolset基于XML和MSI标准适合企业级软件分发但它是面向MSI的调试起来非常痛苦一个XML标签写错就是编译失败。Inno Setup的好处在于脚本语法接近Pascal可读性强文档齐全社区案例多内置Pascal脚本引擎可以在安装过程中写自定义逻辑比如检测MySQL服务是否已存在、修改系统环境变量、执行命令行并等待结果压缩率足够高用LZMA2算法能把几百MB的运行时压到很小最关键的是它对“静默安装”/SILENT参数支持非常好方便后面做无人值守部署。还有一个实际考量Inno Setup完全免费商业使用也没有授权问题。对于内部工具打包来说成本为零而且生成的exe体积并不比NSIS大多少。综合下来它是“功能、学习成本、可控性”三者最平衡的选择。1.2 三个核心部件的处理策略这套方案的核心是解决三个问题MySQL怎么随包安装JDK怎么分发和配置JAR包怎么启动和连接数据库MySQL的处理思路是不下载官方的exe安装程序而是使用ZIP压缩版。官方exe安装程序面向人工点击操作每一步都有向导很难做到静默可控。ZIP版则完全相反解压到目录执行mysqld --initialize-insecure初始化数据目录再通过mysqld --install注册为Windows服务然后启动服务、改root密码、导入业务SQL。整条链路每一步都是命令行天然适合脚本化和自动化的场景。JDK的处理思路是直接在安装包里带上一个可用的JDK目录。很多人有疑问为什么不让用户自己装JDK因为一键安装的核心目标就是“免去用户一切手动操作”。如果还要用户先装JDK、配JAVA_HOME那这个“一键”就名存实亡了。更何况业务方很可能同时运行多个Java应用各应用依赖的JDK版本很可能不一样全局装一个JDK反而容易互相干扰。内置JDK之后每个应用用自己目录下的JRE互不干扰卸载的时候连环境变量一起删掉不会给客户机器留垃圾。JAR包的处理思路是Spring Boot的可执行JAR直接作为文件放进安装目录通过一个启动脚本启动。启动脚本内部设置好JAVA_HOME和PATH再执行java -jar。这里有个细节值得注意不要在启动脚本里依赖全局环境变量而是用相对路径比如set JAVA_HOME%CD%\jdk。这样即使安装目录挪了位置或者做绿色版拷贝到U盘上用启动脚本一样能跑。1.3 安装目录规划与卸载设计目录规划直接影响后续脚本的复杂度。我建议安装目录采用如下结构C:\Program Files\MyApp\ ├── app\ │ ├── myapp.jar │ ├── application.yml │ └── start.bat ├── jdk\ │ └── (JDK文件) ├── mysql\ │ ├── bin\ │ ├── data\ │ └── my.ini └── db\ └── init.sql把JAR应用、JDK、MySQL、初始化SQL分目录存放好处是后续升级时可以只替换某一个子目录。尤其是业务频繁迭代的时候只需把新的JAR包覆盖进去重启应用运维成本极低。卸载设计同样重要。Inno Setup默认的卸载只会删除文件但MySQL注册的Windows服务不会自己消失环境变量也不会自动清理。必须在卸载脚本里加上停止服务、删除服务、删除数据目录、删除环境变量。否则客户卸载后重装新的MySQL服务会因为服务名重复而安装失败。2. 物料准备与环境初装2.1 MySQL免安装版的获取与瘦身先去MySQL官网的社区版下载页面选ZIP Archive版本Windows (x86, 64-bit)ZIP Archive。我建议用MySQL 5.7或者8.0系列5.7兼容性最好8.0性能更好看你们JAR包用的JDBC驱动版本决定。如果用的是mysql-connector-java 8.x那就上MySQL 8.0。下载后解压先把目录改名成简单的mysql。接下来是瘦身将mysql\bin下用不到的工具删掉比如mysqldump.exe保留备份用mysql.exe一定保留mysqladmin.exe保留但mysql_ssl_rsa_setup.exe这类极少用到的可以删。另外根目录下的lib目录里libmysql.dll保留调试用的静态库可以删。整个MySQL目录瘦身后大约在400MB左右压缩之后会小很多。MySQL目录里需要手动创建一个my.ini内容按自己的实际路径生成。注意这里面有一个关键技巧不要写死路径因为安装目录是用户可选的。我的做法是在Inno Setup的Pascal脚本里动态生成my.ini把basedir和datadir替换成当前安装路径。后面讲到脚本的时候会给出完整代码。2.2 JDK内置版的选择与精简技巧JDK版本一定要跟你的JAR编译目标匹配。如果你的项目是Java 8就带JDK 8如果是Java 17就带JDK 17。带高版本的JDK去跑低版本编译的JAR通常没问题反过来不行所以尽量按项目实际JDK版本来。JDK下载直接去Oracle官网或AdoptiumEclipse Temurin。从Oracle下载的JDK是安装版exe无法直接解压使用。有两个办法一是在一台装有JDK的机器上找到安装目录整体拷贝出来二是下载Adoptium的压缩版它直接提供ZIP格式解压即用。我建议用第二种省事。JDK目录瘦身空间很大。删掉jdk\src.zip这个压缩包只是源码运行时用不到删掉jdk\lib\src.zip删掉jre目录JDK 8及之前版本自带现在很多发行版已经去掉了删掉demo、sample等示例代码目录。整个JDK从300MB能瘦到150MB左右。注意一点JDK 9以上是模块化结构如果你对体积特别敏感可以研究jlink生成自定义精简运行时但不建议在这条路上走太深因为jlink配置复杂还容易踩模块依赖坑。常规用户删掉演示和源码即可。2.3 Spring Boot JAR包的配置外置化如果项目连数据库的账号密码是硬编码在application.yml里的那安装到客户机器上就会出问题——不同客户机器的root密码未必一样端口也可能被占用。所以正式打包前务必将配置外置化。Spring Boot天然支持外部配置。默认会读取JAR包同目录下的config/application.yml或者直接读取同目录下的application.yml。所以我在安装目录的app文件夹里放了application.yml里面这样写spring: datasource: url: jdbc:mysql://127.0.0.1:3306/myapp?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/Shanghai username: myapp_user password: MyApp2024这里的账号密码不是root而是安装时创建的业务账号。这样即使MySQL的root密码被用户修改JAR包依然能用独立账号连接安全性和稳定性都更好。2.4 Inno Setup 6环境与中文语言包Inno Setup的安装没什么好说官网下载安装即可。目前稳定版本是6.3.x我用的6.x界面和脚本语法与5.x差别不大但强烈建议用6.x因为支持Unicode中文路径和中文内容处理起来没有乱码问题。默认安装的Inno Setup只有英文。中文界面需要额外放语言文件。到Inno Setup官网或者GitHub仓库下载ChineseSimplified.isl扔进Inno Setup安装目录下的Languages文件夹。惯用版本是6.0.3的ChineseSimplified.isl放到6.3里也能用只是个别新增界面字符串会是英文影响不大。Inno Setup安装后会在开始菜单出现两个程序Inno Setup Compiler编译器IDE和Inno Setup QuickStart向导。我们要用的是Compiler它可以直接编辑和编译.iss脚本。3. Inno Setup脚本编写与静默安装实现3.1 核心脚本骨架与目录映射打开Inno Setup Compiler新建文件或者直接用文本编辑器写.iss脚本。下面是我整理的核心骨架省去了重复的格式说明[Setup] AppNameMyApp 一键安装 AppVersion1.0.0 DefaultDirName{autopf}\MyApp DefaultGroupNameMyApp PrivilegesRequiredadmin OutputDir.\Output OutputBaseFilenameMyApp_Setup_1.0.0 Compressionlzma2/max SolidCompressionyes ArchitecturesAllowedx64compatible ArchitecturesInstallIn64BitModex64compatible UninstallDisplayIcon{app}\app\myapp.ico [Languages] Name: chinesesimplified; MessagesFile: compiler:Languages\ChineseSimplified.isl [Files] Source: C:\pack\mysql\*; DestDir: {app}\mysql; Flags: recursesubdirs createallsubdirs Source: C:\pack\jdk\*; DestDir: {app}\jdk; Flags: recursesubdirs createallsubdirs Source: C:\pack\app\*; DestDir: {app}\app; Flags: recursesubdirs createallsubdirs Source: C:\pack\db\init.sql; DestDir: {app}\db这一段的作用是把三个目录原封不动拷到安装目录。Flags里的recursesubdirs和createallsubdirs必须带上否则子目录不会创建文件全部堆在根目录MySQL会直接起不来的。DefaultDirName我用了{autopf}常量代表Program Files目录。如果不想装到Program Files也可以用{localappdata}装到用户目录但内部工具一般是给管理员装Program Files更符合习惯。PrivilegesRequiredadmin必不可少因为后面要创建Windows服务、改注册表环境变量没有管理员权限这些操作都会失败。3.2 MySQL初始化与服务的静默安装接下来是核心中的核心MySQL的静默安装。这里推荐用Inno Setup的Pascal脚本在安装后期执行命令行而不是放到[Run]段因为Pascal脚本可以用Exec函数等待命令执行完并检查退出码可控性更强。在脚本底部写[Code]段procedure InstallMySQL(); var ResultCode: Integer; CmdLine: string; MyIniPath: string; begin MyIniPath : ExpandConstant({app}\mysql\my.ini); // 动态生成my.ini SaveStringToFile(MyIniPath, [mysqld] #13#10 basedir ExpandConstant({app}\mysql) #13#10 datadir ExpandConstant({app}\mysql\data) #13#10 port3306 #13#10 character-set-serverutf8mb4 #13#10 default-storage-engineINNODB #13#10 skip-grant-tables0 #13#10, False); // 初始化数据目录注意insecure表示root初始密码为空 CmdLine : ExpandConstant({app}\mysql\bin\mysqld.exe --initialize-insecure); if Exec(cmd.exe, /c CmdLine, , SW_HIDE, ewWaitUntilTerminated, ResultCode) then begin if ResultCode 0 then MsgBox(MySQL初始化失败退出码 IntToStr(ResultCode), mbError, MB_OK); end; // 注册为Windows服务 CmdLine : ExpandConstant({app}\mysql\bin\mysqld.exe --install MySQL_MyApp); Exec(cmd.exe, /c CmdLine, , SW_HIDE, ewWaitUntilTerminated, ResultCode); // 启动服务 CmdLine : net start MySQL_MyApp; Exec(cmd.exe, /c CmdLine, , SW_HIDE, ewWaitUntilTerminated, ResultCode); end;这里有几个关键点必须讲清楚。第一--initialize-insecure的含义是初始化数据目录时root用户的密码为空。注意它不是不设密码而是“临时为空”服务启动后你随时可以改。对自动化安装来说这是最顺滑的路径因为它省去了读取初始化随机密码的麻烦。初始化过程中data目录会自动生成如果该目录已存在且非空mysqld会报错退出所以安装包检测到已有数据目录时要么跳过初始化要么先删除再重建。第二服务名用了MySQL_MyApp而不是默认的MySQL。原因很简单这台机器上可能已经装过别的MySQL实例默认服务名会冲突。起一个带应用前缀的名字可以避免跟已有MySQL服务打架。代价是后续连接数据库时启动脚本里也得用这个名字保持一致即可。第三Exec函数里用cmd.exe /c包一层是因为直接Exec mysqld.exe时某些MySQL版本的路径或参数转义会有莫名其妙的问题尤其参数里带引号时。通过cmd执行路径引号解析更可靠。ewWaitUntilTerminated表示安装程序会一直等这个命令跑完。MySQL初始化在机械硬盘上可能需要一两分钟期间安装界面可能会像是卡住了这是正常的不要误判为死机。3.3 数据库账号与业务SQL的导入MySQL服务启动后还需要完成三件事情修改root临时密码、创建业务数据库、创建业务账号并授权、导入初始化SQL。procedure InitDatabase(); var ResultCode: Integer; AppPath, DbPath, CmdLine: string; begin AppPath : ExpandConstant({app}); DbPath : AppPath \db\init.sql; CmdLine : Format(mysql -h127.0.0.1 -uroot --skip-password -e ALTER USER rootlocalhost IDENTIFIED BY Root2024;, []); Exec(cmd.exe, /c CmdLine, , SW_HIDE, ewWaitUntilTerminated, ResultCode); CmdLine : Format(mysql -h127.0.0.1 -uroot -pRoot2024 -e CREATE DATABASE IF NOT EXISTS myapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;, []); Exec(cmd.exe, /c CmdLine, , SW_HIDE, ewWaitUntilTerminated, ResultCode); CmdLine : Format(mysql -h127.0.0.1 -uroot -pRoot2024 -e CREATE USER myapp_user%% IDENTIFIED BY MyApp2024; GRANT ALL PRIVILEGES ON myapp.* TO myapp_user%%; FLUSH PRIVILEGES;, []); Exec(cmd.exe, /c CmdLine, , SW_HIDE, ewWaitUntilTerminated, ResultCode); CmdLine : Format(mysql -h127.0.0.1 -uroot -pRoot2024 myapp %s, [DbPath]); Exec(cmd.exe, /c CmdLine, , SW_HIDE, ewWaitUntilTerminated, ResultCode); end;这段脚本里有个容易踩的坑在Pascal字符串中写SQL单引号必须写成两个单引号转义否则编译时字符串会断掉。上面的写法里我用rootlocalhost来表示SQL中的rootlocalhost这是Inno Setup Pascal脚本语法的基础知识不熟悉的人第一次写基本都会在这报错。再提一个更稳的做法把建库、建账号、授权这些SQL写进init.sql的头部然后只用一条命令完成导入。-- init.sql ALTER USER rootlocalhost IDENTIFIED BY Root2024; CREATE DATABASE IF NOT EXISTS myapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER IF NOT EXISTS myapp_user% IDENTIFIED BY MyApp2024; GRANT ALL PRIVILEGES ON myapp.* TO myapp_user%; FLUSH PRIVILEGES; USE myapp; -- 以下接业务表结构 DROP TABLE IF EXISTS t_user; CREATE TABLE t_user (...); INSERT INTO t_user (...) VALUES (...);这样一来Pascal脚本里的SQL拼接大幅减少只用一条命令导入整个文件出错概率更低。初始化SQL务必保证幂等性——也就是可以重复执行不报错。这点很现实因为安装失败后修复再跑一次的情况很常见如果SQL不是幂等的重跑会插入重复数据或直接报错。3.4 环境变量的注册与Pascal脚本处理JDK环境变量的配置是一键安装里争议最多的环节。这里我给出两种方案你们按实际需要选。方案A不写全局环境变量只在启动脚本里指定JAVA_HOME。这是最推荐的做法干净、不污染系统卸载时也不需要清理环境变量。start.bat内容如下echo off setlocal cd /d %~dp0 set JAVA_HOME%cd%\..\jdk set PATH%JAVA_HOME%\bin;%PATH% java -jar myapp.jar方案B确实需要全局JDK比如这台机器上还有别的Java工具依赖JAVA_HOME。那就用Inno Setup的[Registry]段直接操作注册表环境变量[Registry] Root: HKLM; Subkey: SYSTEM\CurrentControlSet\Control\Session Manager\Environment; \ ValueType: string; ValueName: JAVA_HOME; \ ValueData: {app}\jdk; Flags: uninsdeletevalue Root: HKLM; Subkey: SYSTEM\CurrentControlSet\Control\Session Manager\Environment; \ ValueType: expandsz; ValueName: Path; \ ValueData: {olddata};{app}\jdk\bin; Check: NeedsAddPath({app}\jdk\bin)NeedsAddPath是一个需要自己写的Check函数作用是检测PATH里是否已经有这个路径避免重复追加function NeedsAddPath(Param: string): Boolean; var OrigPath: string; begin if not RegQueryStringValue(HKEY_LOCAL_MACHINE, SYSTEM\CurrentControlSet\Control\Session Manager\Environment, Path, OrigPath) then begin Result : True; Exit; end; Result : Pos(LowerCase(Param), LowerCase(OrigPath)) 0; end;修改注册表环境变量有一个副作用不会立即生效除非广播WM_SETTINGCHANGE消息让系统刷新。Inno Setup里可以通过调用系统API实现但代码比较绕。更简单的做法是安装完成后提示用户重启或注销。不过对多数内部工具场景我个人强烈建议走方案A别碰全局环境变量省心太多。3.5 安装完成后的启动处理安装流程的最后是启动业务应用。这里有两个选择。一个是安装完成后立刻启动应用另一个是创建桌面快捷方式让用户手动启动。我建议安装完成后立即启动因为“一键安装”的体验闭环应该是双击安装包一路下一步最后安装程序自动把应用跑起来。用户感知就是“装好了能用了”。在[Run]段加一行[Run] Filename: {app}\app\start.bat; Description: 运行MyApp; Flags: nowait postinstall skipifsilent注意Flags里用了nowait因为start.bat是持续运行的Java进程安装程序不能等它退出。postinstall表示安装完成后用户可以在完成界面勾选运行skipifsilent表示静默安装模式下不弹这个勾选。另外卸载前必须停止服务并删除MySQL服务否则重装的坑已经埋下了。在[Code]里加procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); var ResultCode: Integer; begin if CurUninstallStep usUninstall then begin Exec(cmd.exe, /c net stop MySQL_MyApp, , SW_HIDE, ewWaitUntilTerminated, ResultCode); Exec(cmd.exe, /c sc delete MySQL_MyApp, , SW_HIDE, ewWaitUntilTerminated, ResultCode); end; end;sc delete如果服务已经停了就只删注册信息很快。net stop如果服务本来就没启动返回错误码但不会弹出错误界面因为Exec的返回值没做判断。这里故意不判断目的是“尽力清理清理不了就算了”不让卸载流程卡住。4. 编译测试与常见问题排查4.1 编译与安装验证流程写完.iss脚本直接按F9编译。Inno Setup Compiler会先做语法检查然后生成安装包exe输出到Output目录。编译过程中如果报错绝大多数是[Files]段指向的源路径不存在或者Pascal脚本语法错误按提示修正即可。生成exe后我建议的验证流程分成三层。第一层在开发机或装有Java开发环境的机器上跑一遍安装。这时候主要验证文件有没有全部拷贝到目标目录安装流程有没有报错。由于本机已经有JDK和MySQL即使安装过程有细微问题也不一定能暴露所以这一层只能算热身。第二层找一台干净的虚拟机或备用电脑最好是没有任何Java和MySQL的裸机。这一层才是真正考验安装包的地方。跑一遍安装重点观察MySQL初始化是否成功、服务有没有起来、数据库账号是否创建、业务SQL有没有导入、JAR包能不能连上数据库、启动脚本能不能拉起应用。任何一个环节失败看安装日志定位。第三层卸载再安装测试。这个很多人会忽略但它同样重要。卸载后检查MySQL服务是否删除、安装目录是否清空、重装是否成功。这层测试通过了才能应付客户“装完不好用删掉重装”的常见场景。4.2 高频问题与解决方案速查我在实操中积累了一些高频问题整理成一张速查表希望能帮大家节省现场排查的时间。现象原因解决方案安装时报“MySQL初始化失败退出码非0”数据目录已存在或路径含中文/空格确认安装包不覆盖已有数据初始化前删除残留data目录MySQL服务启动失败错误1067my.ini配置路径错误检查my.ini的basedir/datadir是否为绝对路径字符集配置是否被写成utf8mb4注意MySQL 5.7用utf8mb4没问题5.5则不支持mysql命令行报“Cant connect to MySQL server”服务没起来或端口被占用先net start MySQL_MyApp再netstat -ano导入SQL时中文乱码客户端字符集与库字符集不一致初始化SQL文件用UTF-8编码保存在SQL文件开头使用SET NAMES utf8mb4;start.bat启动后Java进程闪退JAR包依赖的JDK版本与内置JDK不匹配检查JAR的编译版本用javap -verbose或java -jar报错信息换成对应版本JDK安装包被杀毒软件拦截内置MySQL和JDK被误报在杀毒软件中加白名单或者用代码签名证书对exe签名其中“MySQL初始化失败”是我遇到最多的一个问题。大部分原因是之前安装失败后data目录没有清理干净。MySQL没有那么智能它不会检测数据目录是否完整只要目录存在且非空它就会拒绝初始化。处理方法也很简单在[Code]里初始化前先删除data目录DelTree(ExpandConstant({app}\mysql\data), False, True, False);这句话的意思是强制删除data目录下所有文件即使文件是只读或系统属性。删完再执行mysqld --initialize-insecure就不会有残留数据导致初始化失败的问题。4.3 安装包的体积与性能优化很多人第一次打包完会惊讶安装包怎么这么大这是正常的。MySQL加JDK原始体积在500MB以上LZMA2压缩后通常剩200-300MB如果再带上8.0的MySQL可能会到400MB。如果你的安装包过大有几个优化手段。MySQL瘦身可以做彻底一点。mysql\bin下那些不常用的工具如mysqlpump.exe、mysqlslap.exe、mydumper.exe等全部可以删掉只留mysqld.exe、mysql.exe、mysqladmin.exe、mysqldump.exe、mysqlshow.exe。lib目录里的静态库文件整个删掉只留libmysql.dll。另外mysql\share目录里有大量字符集定义如果不是特别需求可以只保留charsets子目录和errmsg.sys但一般我建议整个保留因为删多了容易出乱码问题。JDK瘦身之前讲过去掉src.zip和示例代码即可。如果项目是纯Java应用不涉及JNI可以去Adoptium找jre版本的压缩包体积更小。实在不行再上jlink但普通项目不推荐。脚本里还有个优化点Inno Setup默认的压缩级别是LZMA2级别参数可以设为lzma2/max压缩率最高但编译时间最长。如果你觉得编译太慢可以调成lzma2/normal体积可能会多出二三十MB但编译速度提升明显。这个看个人取舍。4.4 静默安装与命令行部署一键安装的另一个重要场景是批量部署。几十台电脑一台台双击安装包太累Inno Setup原生支持静默安装只需要在命令行加参数。MyApp_Setup_1.0.0.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART/VERYSILENT表示不显示安装界面和进度条完全后台安装。/SUPPRESSMSGBOXES表示抑制所有消息框装完了直接结束。/NORESTART表示不触发重启。这样签名就可以配合批量部署工具比如组策略、计划任务、或者其他自动化分发平台了。需要注意如果脚本里有MsgBox调用静默模式下可能出现对话框卡住安装进程。我的建议是所有安装逻辑尽量自动化不弹任何确认框任何失败都写日志文件而不是弹窗。上面给出的Pascal脚本里还有一个MsgBox在正式打包时建议删掉或改成写日志。写日志的方法很简单Inno Setup内置了Log函数但只在/LOG参数下才输出。我习惯自己写一个日志函数把关键步骤输出到{app}\install.logprocedure WriteLog(Msg: string); var LogPath: string; begin LogPath : ExpandConstant({app}\install.log); SaveStringToFile(LogPath, Msg #13#10, True); end;这样即使安装失败看日志也能迅速定位失败在哪一步。同时在安装完成界面上可以提示用户如果遇到问题把install.log发给管理员。5. 我的一点补充心得这套方案从第一次跑通到现在也迭代过好几个版本。最初我用的是[Run]段依次跑命令但发现有个问题[Run]段命令默认是异步执行等MySQL初始化完再去导入SQL服务还没起来SQL导入就会失败。后来改成Pascal脚本的Exec ewWaitUntilTerminated之后整个流程稳妥多了。建议看到这里的朋友直接按Pascal脚本的方式走不要在[Run]段浪费时间去调参数。另外数据库密码的管理也要提前想清楚。上面的脚本里root密码是写死在脚本里的这在内部工具场景没什么问题但如果是面向外部客户交付建议在安装时生成随机root密码然后把连接信息写进application.yml。Inno Setup的Pascal脚本里可以用GetRandomNumber生成数字再拼上固定字符构成密码虽然不算高强度的随机但对这类场景足够了。最后建议整个安装包里嵌入一份安装说明.txt把软件安装路径、服务名、日志位置、启动方式写清楚。别高估客户看文档的意愿但真出问题的时候这份文件能省很多沟通成本。