一、前序说明

javaFX的界面框架有很多,实际业务中我们常常要将界面框架与整体业务框架融为一体,这里介绍一下如何将javaFX的界面框架与springboot相结合,并实现jar包加密、数据库加密、安装时在线校验序列号等一系列的过程。本文以一个图书管理模块为例,界面框架采用了atlantafx,数据库采用了h2。下面上几个界面图,如下图所示:

闪屏界面

登录界面

列表界面

黑色主题的列表界面

系统设置界面

主题设置界面

1. JavaFX 20 ,

openjfx下载地址

2. SpringBoot 2.7.5 这个就不给链接了,在pom.xml中。

3. JDK17 windows的下载地址:

JDK17 windows下载地址

4. Launch4j 3.5.0 用于制作和生成各平台可执行文件,windows下生成exe文件。链接地址:

launch4j下载地址

5. JVMTI 制作dll,用于对Jar包加解密,防破解。这个自行编译出dll即可。我在框架下载地址中也放了我自己编译的dll。

6. Inno Setup 6.22 用于制作安装程序,同时在安装过程中提示输入序列号。

inno setup下载地址

7.(可选)Python 3+ Python,用于执行的build.py来构建安装程序。

python下载地址

8. (可选) jad-ui,可通过该工具查看加密后的jar包中的类是否可反编译。链接地址:

下载最新版jad

9. 数据库,在本项目中我采用H2数据库,且数据库实现加密。

说明:我以windows平台为例,其他平台类似。示例中会以一个图书管理系统作为示例。在本文中,我会附带上所有需要的软件的链接。

二、项目结构说明

在本文中给出了两个项目,如下所示:

其中,bookemanager是图书管理系统的实现,jniencrypt是加解密dll源代码。

三、SpringBoot2.75+JavaFX20的图书管理系统实现说明

1. 组件说明:

lombok -- 没啥可说的,都懂

openjfx, controls/fxml/swing/media/web/base/

springboot 2.7.5+ 

springboot-javafx-support springboot对javafx的支持库

界面库:atlantafx,目前感觉这个还是挺不错的,而且支持java17以上版本。

图标库:ikonli

fasjson:json处理库

dao:mybatis-2.1.2

分页:google-pagehelper

数据库:h2 druid

sass编译组件:sass-cli-maven-plugin

2. pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.5</version>
	</parent>
	<groupId>huxg</groupId>
	<artifactId>bookmanager</artifactId>
	<version>1.0.0</version>
	<properties>
		<app.launcher>huxg.bookmanager.BookManagerApplication</app.launcher>
		<java.version>17</java.version>
		<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<maven.compiler.source>17</maven.compiler.source>
		<maven.compiler.target>17</maven.compiler.target>
		<openjfx.version>20</openjfx.version>
		<lib.cssfx.version>11.5.1</lib.cssfx.version>
		<lib.ikonli.version>12.3.1</lib.ikonli.version>
		<lib.datafaker.version>1.3.0</lib.datafaker.version>
		<lib.jetbrains-annotations.version>23.0.0</lib.jetbrains-annotations.version>
		<atlantafx.version>2.0.1</atlantafx.version>
	</properties>

	<profiles>
		<profile>
			<id>win64</id>
			<activation>
				<os>
					<family>windows</family>
				</os>
			</activation>
			<properties>
				<app.icon>
					${project.build.directory}\resources\icons\app-icon.ico</app.icon>
			</properties>
		</profile>
	</profiles>

	<dependencies>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
		</dependency>
		<dependency>
			<groupId>de.roskenet</groupId>
			<artifactId>springboot-javafx-support</artifactId>
			<version>2.1.6</version>
		</dependency>
		<dependency>
			<groupId>io.github.mkpaz</groupId>
			<artifactId>atlantafx-base</artifactId>
			<version>2.0.1</version>
		</dependency>
		<dependency>
			<groupId>org.openjfx</groupId>
			<artifactId>javafx-controls</artifactId>
			<version>${openjfx.version}</version>
		</dependency>
		<dependency>
			<groupId>org.openjfx</groupId>
			<artifactId>javafx-fxml</artifactId>
			<version>${openjfx.version}</version>
		</dependency>
		<dependency>
			<groupId>org.openjfx</groupId>
			<artifactId>javafx-swing</artifactId>
			<version>${openjfx.version}</version>
		</dependency>
		<dependency>
			<groupId>org.openjfx</groupId>
			<artifactId>javafx-media</artifactId>
			<version>${openjfx.version}</version>
		</dependency>
		<dependency>
			<groupId>org.openjfx</groupId>
			<artifactId>javafx-web</artifactId>
			<version>${openjfx.version}</version>
		</dependency>
		<dependency>
			<groupId>io.github.mkpaz</groupId>
			<artifactId>atlantafx-base</artifactId>
			<version>${atlantafx.version}</version>
		</dependency>
		<dependency>
			<groupId>org.kordamp.ikonli</groupId>
			<artifactId>ikonli-javafx</artifactId>
			<version>${lib.ikonli.version}</version>
		</dependency>
		<dependency>
			<groupId>org.kordamp.ikonli</groupId>
			<artifactId>ikonli-feather-pack</artifactId>
			<version>${lib.ikonli.version}</version>
		</dependency>
		<dependency>
			<groupId>org.kordamp.ikonli</groupId>
			<artifactId>ikonli-material2-pack</artifactId>
			<version>${lib.ikonli.version}</version>
		</dependency>
		<dependency>
			<groupId>org.jetbrains</groupId>
			<artifactId>annotations</artifactId>
			<version>${lib.jetbrains-annotations.version}</version>
		</dependency>
		<dependency>
			<groupId>fr.brouillard.oss</groupId>
			<artifactId>cssfx</artifactId>
			<version>${lib.cssfx.version}</version>
		</dependency>
		<dependency>
			<groupId>net.datafaker</groupId>
			<artifactId>datafaker</artifactId>
			<version>${lib.datafaker.version}</version>
		</dependency>
		<dependency>
			<groupId>commons-beanutils</groupId>
			<artifactId>commons-beanutils</artifactId>
			<version>1.9.3</version>
		</dependency>
		<dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-collections4</artifactId>
			<version>4.2</version>
		</dependency>
		<dependency>
			<groupId>commons-httpclient</groupId>
			<artifactId>commons-httpclient</artifactId>
			<version>3.1</version>
		</dependency>
		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>2.5</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.12.0</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.4</version>
			<exclusions>
				<exclusion>
					<groupId>com.alibaba</groupId>
					<artifactId>jconsole</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.alibaba</groupId>
					<artifactId>tools</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.62</version>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<version>2.2.224</version>
		</dependency>
		<dependency>
			<groupId>org.reflections</groupId>
			<artifactId>reflections</artifactId>
			<version>0.10.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpmime</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient-cache</artifactId>
		</dependency>
		<dependency>
			<groupId>com.github.pagehelper</groupId>
			<artifactId>pagehelper-spring-boot-starter</artifactId>
			<version>1.2.12</version>
		</dependency>
		<!-- 通用mapper -->
		<dependency>
			<groupId>tk.mybatis</groupId>
			<artifactId>mapper-spring-boot-starter</artifactId>
			<version>2.1.2</version>
			<exclusions>
				<exclusion>
					<groupId>org.mybatis</groupId>
					<artifactId>mybatis</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>net.java.dev.jna</groupId>
			<artifactId>jna</artifactId>
			<version>5.3.1</version>
		</dependency>
		<dependency>
			<groupId>net.java.dev.jna</groupId>
			<artifactId>jna-platform</artifactId>
			<version>5.3.1</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>${project.artifactId}-${project.version}</finalName>

		<plugins>
			<!-- compile SCSS -->
			<plugin>
				<groupId>us.hebi.sass</groupId>
				<artifactId>sass-cli-maven-plugin</artifactId>
				<configuration>
					<args>
						<arg>
							${project.basedir}/src/main/resources/styles/scss/index.scss:${project.build.directory}/classes/styles/index.css
						</arg>
						<arg>--no-source-map</arg>
					</args>
				</configuration>
				<executions>
					<execution>
						<id>run-sass</id>
						<phase>generate-resources</phase>
						<goals>
							<goal>run</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<!-- check code style before compilation -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-checkstyle-plugin</artifactId>
				<executions>
					<execution>
						<id>validate</id>
						<phase>verify</phase>
						<goals>
							<goal>check</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<!-- Generate springboot compile and jar file -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<configuration>
					<archive>
						<manifest>
							<mainClass>${app.launcher}</mainClass>
						</manifest>
					</archive>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
				<dependencies>
					<dependency>
						<groupId>org.springframework.boot</groupId>
						<artifactId>spring-boot-maven-plugin</artifactId>
						<version>2.7.5</version>
					</dependency>
				</dependencies>
				<configuration>
					<createDependencyReducedPom>false</createDependencyReducedPom>
				</configuration>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>shade</goal>
						</goals>
					</execution>
				</executions>

			</plugin>

			<plugin>
				<artifactId>maven-resources-plugin</artifactId>
				<executions>
					<!-- 复制jre -->
					<execution>
						<id>copy-jre</id>
						<phase>validate</phase>
						<goals>
							<goal>copy-resources</goal>
						</goals>
						<configuration>
							<outputDirectory>
								${project.basedir}/target/pre-deploy/jre/app</outputDirectory>
							<resources>
								<resource>
									<directory>
										${project.basedir}/../jre</directory>
									<filtering>false</filtering>
								</resource>
							</resources>
						</configuration>
					</execution>

					<!-- 复制图标 -->
					<execution>
						<id>copy-resources</id>
						<phase>validate</phase>
						<goals>
							<goal>copy-resources</goal>
						</goals>
						<configuration>
							<outputDirectory>
								${project.basedir}/target/pre-deploy/</outputDirectory>
							<resources>
								<resource>
									<directory>
										${project.basedir}/src/main/installer</directory>
									<filtering>true</filtering>
									<includes>
										<include>${project.artifactId}.ico</include>
										<include>license.txt</include>
									</includes>
								</resource>
							</resources>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

3. h2数据库的加密

h2数据支持数据文件加密,下面给出一个示例代码。真实案例中,数据库的用户名、密码可以通过特定的加密算法来生成。示例代码如下:

@Bean
	@Order(1)
	public DataSource createDataSource() throws Exception {
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName("org.h2.Driver");
		ds.setUrl("jdbc:h2:./bookmanager;CIPHER=AES;TRACE_LEVEL_FILE=0;TRACE_LEVEL_SYSTEM_OUT=0");
		ds.setUsername("F74032E");
		ds.setPassword(
				"2BAAFB04E44ACAB6CA010EAEC7654C4C3BEB382BFC6814BB1CB2C3E0B3A130FBF8D00642E8CF2DAF1CB2C3E0B3A130FBF8D00642E8CF2DAF1CB2C3E0B3A130FBF8D00642E8CF2DAF1CB2C3E0B3A130FBF8D00642E8CF2DAF1CB2C3E0B3A130FBF8D00642E8CF2DAF1CB2C3E0B3A130FBF8D00642E8CF2DAF1CB2C3E0B3A130FBF8D00642E8CF2DAF1CB2C3E0B3A130FBF8D00642E8CF2DAF1CB2C3E0B3A130FBF8D00642E8CF2DAF1CB2C3E0B3A130FBF8D00642E8CF2DAF 6DC0D393029EFF18A9C1BAF74032E56FA9C1BAF74032E56FA9C1BAF74032E56FA9C1BAF74032E56FA9C1BAF74032E56FA9C1BAF74032E56FA9C1BAF74032E56FA9C1BAF74032E56FA9C1BAF74032E56FA9C1BAF74032E56F");
		ds.setInitialSize(4);
		ds.setMinIdle(4);
		ds.setMaxActive(20);
		ds.setMaxWait(60000);
		ds.setTimeBetweenEvictionRunsMillis(60000);
		ds.setMinEvictableIdleTimeMillis(300000);
		ds.setTestWhileIdle(true);
		ds.setTestOnBorrow(true);
		ds.setTestOnReturn(true);
		ds.setPoolPreparedStatements(true);
		ds.setMaxPoolPreparedStatementPerConnectionSize(30);
		ds.setRemoveAbandoned(true);
		ds.setRemoveAbandonedTimeout(18000);
		ds.setLogAbandoned(false);
		return ds;
	}

上面的代码只是个例子,这里我去掉了动态生成账号和密码的过程,仅将账号密码写死了在代码中。这段代码在AppConfiguration.class这个类中,可参加这个类的实现。

4. 机器码的生成

机器码是在安装程序时自动生成的,参见install-win64.iss中,具体代码如下:

function GenMachineID(SerialNum: LongInt): String;
var
 SN: LongInt;
 MID, tmp1, tmp2: string;
begin
  if SerialNum > 567890123 then
    SN:= SerialNum-567890123
  else
    SN:= 567890123-SerialNum;
  tmp1:= IntToStr(SN);
  while length(tmp1)>3 do
    begin
    tmp2:= copy(tmp1,1,3);
    delete(tmp1,1,3);
    MID:= MID+Chr(65+StrToInt(tmp2)*26/999)+tmp2;
    end;
  MID:= MID+Chr(65+StrToInt(tmp1)*26/999)+tmp1;
  tmp1:= MID;
  MID:= '';
  while length(tmp1)>3 do
    begin
    tmp2:= copy(tmp1,1,3);
    delete(tmp1,1,3);
    MID:= MID+tmp2+'-';
    end;
  MID:= MID+tmp1;
  If SerialNum > 567890123 then
    MID:='317-'+MID
  else
    MID:='V10-'+MID;
result:= MID;
end;

生成的机器经过加密存储到注册表中,可在install-win64.iss中查看具体实现。

5. 编译及运行

执行mvn clean package 即可。

四、jar包的加密

这里通过JVMTI的agentlib来对jar包进行加密和解密。具体相关概念可以百度一下,这里不再赘述了。将源代码下载下来,然后进入Visual Studio中,根据情况修改加解密算法。要注意一点,解密过程是需要判定包名的。特别注意一点:因为本项目是基于SpringBoot的,基于CGLIB动态代理的类这里不能加密,否则运行时会出错,所以要将其过滤掉,如下图所示:

我在示例项目中已经放了一个dll,仅供参考。

示例项目中,在build.py中对编译出来的jar包进行加密,并生成了一个“bookmanager-1.0.0_encrypted.jar”的加密后的jar包。后续生成exe时将使用这个jar。

五、生成EXE

首先需要制作一个launch4j的xml配置文件,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<launch4jConfig>
  <dontWrapJar>false</dontWrapJar>
  <headerType>gui</headerType>
  <jar>../../../target/bookmanager-1.0.0_encrypted.jar</jar>
  <outfile>../../../target/pre-deploy/jre/app/bookmanager.exe</outfile>
  <errTitle>图书管理系统</errTitle>
  <cmdLine></cmdLine>
  <chdir>.</chdir>
  <priority>normal</priority>
  <downloadUrl></downloadUrl>
  <supportUrl></supportUrl>
  <stayAlive>false</stayAlive>
  <restartOnCrash>false</restartOnCrash>
  <manifest></manifest>
  <icon>../../../src/main/resources/icons/app-icon.ico</icon>
  <singleInstance>
    <mutexName>图书管理系统</mutexName>
    <windowTitle>图书管理系统</windowTitle>
  </singleInstance>
  <classPath>
    <mainClass>huxg.bookmanager.BookManagerApplication</mainClass>
  </classPath>
  <jre>
    <path>./</path>
    <requiresJdk>false</requiresJdk>
    <requires64Bit>true</requires64Bit>
    <minVersion>17</minVersion>
    <maxVersion>19</maxVersion>
    <opt>-Xrunjarencrypt -Xms1024M -Xmx6144M -XX:+UseG1GC -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m </opt>
  </jre>
</launch4jConfig>

这里着重说明几点:

1. jvm的参数配置写在<opt></option>中,但是这里并不能添加-agentlib参数,必须使用-X参数,所以使用-Xrunhuxgd参数指定我们的解密dll

2. jre的路径,这里使用的是“./” 可以看出,exe将会放入到jre的目录下。当然你可以将exe放在与jre平级目录中,但我自己更偏向将exe放入到jre目录中,这样后面安装完毕后,整个jre目录都会被展开,看起来更好一些。

3. 生成的exe并不携带jre,仅仅是对jar包加了一层壳。从这一点来说,如果我们要破解和反编译,那么也是可以从其中取出来的。这也是为什么我们需要采用jvmti来对jar包进行加密的原因,要彻底防止关键代码被反编译。

生成exe的命令如下:

launch4jc.exe ./bookmanager/src/main/installer/launch4j-config-win64.xml

六、生成安装程序

安装程序的生成是通过Inno Setup来生成的。所以这里我们需要制作一个安装程序配置脚本文件,我这里将其命名为:installer-win64.iss,具体代码如下所示:

; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "ͼ�����ϵͳ"
#define MyAppVersion "V1.0.0"
#define MyAppPublisher "ͼ�����ϵͳ"
#define MyAppExeName "bookmanager.exe"

[Setup]
UserInfoPage=yes
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{DDA4F3A1-1886-4742-8E7F-BF4AD486F0BD}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
DefaultDirName={autopf}\bookmanager
DisableProgramGroupPage=yes
LicenseFile=.\license.rtf
PrivilegesRequiredOverridesAllowed=commandline
OutputDir=..\..\..\..\output\release\win64
OutputBaseFilename=ͼ�����ϵͳ_installer
SetupIconFile=..\resources\icons\app-icon.ico
Compression=lzma
SolidCompression=yes
WizardStyle=modern

[Languages]
Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked

[Files]
Source: "..\..\..\target\pre-deploy\jre\app\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs

[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon

[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

[UninstallDelete]
Type: filesandordirs; Name: "{app}";

[Code]

function CheckSerial(Serial: String): Boolean;
begin
  if Serial <> '' then
  Result:=True;
end;

function getStringValue(str: String): longint;
var 
  sum : longint;
  i: longint;
begin
  sum :=0;
  for i:=1 to length(str) do
    sum := sum + ord(str[i]);

  Result := sum;
end;

function GenMachineID(SerialNum: LongInt): String;
var
 SN: LongInt;
 MID, tmp1, tmp2: string;
begin
// ���ɻ�����
  if SerialNum > 567890123 then
    SN:= SerialNum-567890123
  else
    SN:= 567890123-SerialNum;
  tmp1:= IntToStr(SN);
  while length(tmp1)>3 do
    begin
    tmp2:= copy(tmp1,1,3);
    delete(tmp1,1,3);
    MID:= MID+Chr(65+StrToInt(tmp2)*26/999)+tmp2;
    end;
  MID:= MID+Chr(65+StrToInt(tmp1)*26/999)+tmp1;
  tmp1:= MID;
  MID:= '';
  while length(tmp1)>3 do
    begin
    tmp2:= copy(tmp1,1,3);
    delete(tmp1,1,3);
    MID:= MID+tmp2+'-';
    end;
  MID:= MID+tmp1;
  If SerialNum > 567890123 then
    MID:='317-'+MID
  else
    MID:='V10-'+MID;
result:= MID;
end;

function NextButtonClick(CurPageID: Integer): Boolean;
var
  WinHttpReq: Variant;
  Url: string;
  machineCode: longint;
  UID: string;
  regRet: string; 
begin
  Result := True; 
  if CurPageID = wpUserInfo then
  begin
    // ��װ�����д���ע�����ļ�
    machineCode := getStringValue(WizardForm.UserInfoSerialEdit.Text);
    UID := GenMachineID(machineCode);
    
    if WizardForm.UserInfoSerialEdit.Text <> '123456' then
		begin
			MsgBox('�������ע��������������������룡', mbError, MB_OK);
			Result := False;
		end
	else
	    begin
	        // д��ע���
	        RegWriteStringValue(HKEY_CURRENT_USER, 'Software\bookmanager', 'uid',   UID);
	    end;
          
	// ͨ���ӿ���֤��Ȩ���Ƿ���ȷ
    // WinHttpReq := CreateOleObject('WinHttp.WinHttpRequest.5.1');
    // Url := 'http://xxxxxx.com/...?serialNo=' + WizardForm.UserInfoSerialEdit.Text + '&muid=' + UID;
    // WinHttpReq.Open('GET', Url, False);
    // WinHttpReq.Send('');
    // if WinHttpReq.Status = 200 then
    //   begin
    //     if WinHttpReq.ResponseText = 'false' then
    //       begin
    //         MsgBox('�������ע��������������������룡', mbError, MB_OK);
    //         Result := False;
    //       end
    //     else
    //       begin
    //         // д��ע���
    //         RegWriteStringValue(HKEY_CURRENT_USER, 'Software\bookmanager', 'uid',   UID);
    //       end;
    //    end
    // else
    //   begin
    //     Result := False;  
    //   end;
  end;
end;



这里着重说明几点:

1. 语言,简体中文的语言文件我在上面已经放了链接,可参见本文开头处。

2. 安装序列号:在安装过程中,会提示输入序列号,用户输入序列号后,会用该序列号去服务器端匹配。示例程序中正确的序列号为“123”输入这个即可。后面的一大堆Pascal程序都是在验证序列号是否正确的。这里WinHttpRequest对象请求服务器。

3. 生成机器码:pascal程序中生成了机器码,同时将其传递给了后端。

4. 生成注册表项:在验证序列号通过之后,将唯一的机器码保存到了注册表中,在Java程序中会检查此注册表项是否存在,同时使用该码进行各种加解密。

上面的示例中,我将提交至服务器这段代码注释掉了,只是通过字符串来进行判定。采用api接口进行验证时可以将注释打开,填入自己的api地址即可。

生成安装程序的命令如下:

iscc ./bookmanager/src/main/installer/installer-win64.iss

七、Jlink生成JRE

jdk9以上已经没有了jre,所以这里我们要生成一份jre运行环境。其实这一步是在(六)之前要做的。生成jre的命令如下:

jlink --module-path D:\Program\Java\javafx-sdk-17.0.9\lib;D:\Program\Java\jdk-17\jmods --add-modules java.base,jdk.unsupported,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.management.rmi,java.net.http,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.sql.rowset,java.transaction.xa,java.xml,java.xml.crypto,javafx.controls,javafx.fxml,javafx.swing,javafx.web --compress=2 --strip-debug --output jre

因为是javafx程序,需要javafx的相应模块,所以生成的jre环境同样要包含javafx的相关模块。这里一定要特别特别的注意,以上生成的jre环境是无法运行我们的jar包的,还需要将jfx-sdk下的bin目录复制到生成的jre中,这一点在其他的技术文章中都没有提及。

生成完jre后,通过(六)中的安装程序即可将exe与jre一同打包了。

八、JarEncrytpor.dll的编译

安装Visual Studio ,我贴上我自己的Visual Studio版本,如下托所示:

打开Visual Studio Installer,安装C++桌面开发选项,如下图所示:

安装完毕后,双击jni_jarencrypt\jarencrypt_dll.sln,打开Visual Studio,如下图所示:

注意:将顶部编译版本修改为release,如下图所示:

加、解密都在bookmanager_JarEncryptor.cpp中,其中encrypt为加密函数,decrypt为解密函数。我只是给了一个示例的加密过程,你们可以定义自己的加密过程。

特别要注意一点,如下图所示:

一切处理完毕后,右键点击项目-》生成,即可生成jarEncrypt.dll了。生成的dll在output目录下。

九、运行项目

将生成dll直接放入到jre\bin目录下,然后执行命令:

jre\bin\java -Xrunjarencrypt target\bookmanager-1.0.0_encrypt.jar

十、一键编译

本项目中提供了build.py脚本,该脚本需要python3环境,python3的下载地址我在上面已经提供了。同时还要安装其他几个软件,我在上面也提供了相应的链接。全部安装完毕,并设置好环境变量后,直接执行:

python build.py

输入命令并回车后,如下图所示:

1. Build Book Manager -- 编译图书管理系统,并生成安装程序

2. Generate JRE -- 生成jre环境

3. Clean All -- 清除所有临时目录

特别说明:在运行(1)之前,需要先执行(2)来生成jre环境。这是一个前置过程。一旦生成jre环境,下次直接执行(1)即可。

完整的图书管理系统全部代码可以在这里下载。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐