做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 Setup?
Windows平台上的安装包制作工具大致分几类。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官网或Adoptium(Eclipse 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?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai username: myapp_user password: MyApp@2024这里的账号密码不是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] AppName=MyApp 一键安装 AppVersion=1.0.0 DefaultDirName={autopf}\MyApp DefaultGroupName=MyApp PrivilegesRequired=admin OutputDir=.\Output OutputBaseFilename=MyApp_Setup_1.0.0 Compression=lzma2/max SolidCompression=yes ArchitecturesAllowed=x64compatible ArchitecturesInstallIn64BitMode=x64compatible 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更符合习惯。PrivilegesRequired=admin必不可少,因为后面要创建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 + 'port=3306' + #13#10 + 'character-set-server=utf8mb4' + #13#10 + 'default-storage-engine=INNODB' + #13#10 + 'skip-grant-tables=0' + #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 ''root''@''localhost'' IDENTIFIED BY ''Root@2024'';"', []); Exec('cmd.exe', '/c ' + CmdLine, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); CmdLine := Format('mysql -h127.0.0.1 -uroot -pRoot@2024 -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 -pRoot@2024 -e "CREATE USER ''myapp_user''@''%%'' IDENTIFIED BY ''MyApp@2024''; 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 -pRoot@2024 myapp < "%s"', [DbPath]); Exec('cmd.exe', '/c ' + CmdLine, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end;这段脚本里有个容易踩的坑:在Pascal字符串中写SQL,单引号必须写成两个单引号转义,否则编译时字符串会断掉。上面的写法里我用''root''@''localhost''来表示SQL中的'root'@'localhost',这是Inno Setup Pascal脚本语法的基础知识,不熟悉的人第一次写基本都会在这报错。
再提一个更稳的做法:把建库、建账号、授权这些SQL写进init.sql的头部,然后只用一条命令完成导入。
-- init.sql ALTER USER 'root'@'localhost' IDENTIFIED BY 'Root@2024'; CREATE DATABASE IF NOT EXISTS myapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER IF NOT EXISTS 'myapp_user'@'%' IDENTIFIED BY 'MyApp@2024'; 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服务启动失败,错误1067 | my.ini配置路径错误 | 检查my.ini的basedir/datadir是否为绝对路径,字符集配置是否被写成utf8mb4(注意MySQL 5.7用utf8mb4没问题,5.5则不支持) |
| mysql命令行报“Can't 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.log:
procedure 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,把软件安装路径、服务名、日志位置、启动方式写清楚。别高估客户看文档的意愿,但真出问题的时候,这份文件能省很多沟通成本。