Springboot项目启动前,使用GUI做初始化配置页面并将Log4j2的日志实时显示在GUI上
效果预览
Mac Os效果图
Windows 10 效果图
需求分析
做这样的一个功能并不适用于所有系统,主要用于交付给用户的产品,这样方便客户自行维护。传统的服务一般都是集群部署在Linux,有专业的运维工程师去维护,对于局域网内单机部署在Windows的情况,此功能特别实用。客户并不具备用专业的技术去部署去配置,所以用一套傻瓜式的安装、配置、启动等显得尤为重要。
代码实现
主面板
/*** 主面板* @author YoungJ*/
@Slf4j
public class MainPanel {public static JPanel mainPanel;public static EpilepsyDbConfig epilepsyDbConfig = readEpilepsyDbConfigFromProperties();public static GdDbConfig gdDbConfig = readGdDbConfigFromProperties();public static SysConfig sysConfig = readSysConfigFromProperties();/*** 左侧配置面板宽度*/public static final int CONFIG_PANEL_WIDTH = 300;/*** 日志面板宽度*/public static final int LOG_PANEL_WIDTH = 850;/*** 系统数据库面板起点坐标*/public static final int[] EPILEPSDB_PANEL_START_COOR = {5, 0};/*** 系统数据库面板高度*/public static final int EPILEPSDB_PANEL_HEIGHT = 190;/*** 光电数据库面板起点坐标*/public static final int[] GDDB_PANEL_START_COOR = {5, 200};/*** 光电数据库面板高度*/public static final int GDDB_PANEL_HEIGHT = 150;/*** 证书面板起点坐标*/public static final int[] CERT_PANEL_START_COOR = {5, 360};/*** 证书面板高度*/public static final int CERT_PANEL_HEIGHT = 160;/*** 服务面板起点坐标*/public static final int[] SERVER_PANEL_START_COOR = {5, 530};/*** 服务面板高度*/public static final int SERVER_PANEL_HEIGHT = 140;/*** 日志面板起点坐标*/public static final int[] LOG_PANEL_START_COOR = {310, 0};/*** 日志面板高度*/public static final int LOG_PANEL_HEIGHT = 670;public MainPanel() {mainPanel = new JPanel();mainPanel.setLayout(null);EpilepsysDbPanel.buildJpanel(mainPanel);GdDbPanel.buildJpanel(mainPanel);CertPanel.buildJpanel(mainPanel);ServerPanel.buildJpanel(mainPanel);LogPanel.buildJpanel(mainPanel);buildFrame(mainPanel);}protected static JButton buildJButton(String name, int x, int y, int width, int height) {JButton button = new JButton(name);button.setBounds(x, y, width, height);return button;}protected static JRadioButton buildJRadioButton(String name, int x, int y, int width, int height) {JRadioButton button = new JRadioButton(name);button.setBounds(x, y, width, height);return button;}// 文本框protected static JTextField buildJTextField(String value, String name, int columns, int x, int y, int width, int height) {JTextField jtf = new JTextField(columns);jtf.setText(value);jtf.setName(name);jtf.setBounds(x, y, width, height);return jtf;}// 密码框protected static JPasswordField buildJPasswordField(String value, String name, int columns, int x, int y, int width, int height) {JPasswordField jtf = new JPasswordField(columns);jtf.setText(value);jtf.setName(name);jtf.setBounds(x, y, width, height);return jtf;}protected static JLabel buildJLabel(String name, int x, int y, int width, int height) {JLabel label = new JLabel(name);label.setBounds(x, y, width, height);return label;}protected static JLabel buildJBorder(String name, int x, int y, int width, int height) {JLabel label = new JLabel();label.setBounds(x, y, width, height);label.setBorder(BorderFactory.createTitledBorder(name));return label;}private static void buildFrame(JComponent component) {JFrame frame = new JFrame("癫痫数据管理系统");component.setBounds(0, 0, 1055, 730);frame.add(component);frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);frame.getContentPane().setLayout(new BorderLayout());frame.getContentPane().setLayout(null);frame.getContentPane().add(BorderLayout.CENTER, component);// 设置窗口最小尺寸frame.setMinimumSize(new Dimension(1065, 730));frame.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {int result = JOptionPane.showConfirmDialog(frame, "关闭窗口服务将停止运行,确定要关闭吗?", "关闭确认", JOptionPane.YES_NO_OPTION);if (result == JOptionPane.YES_OPTION) {// 如果用户点击是,执行关闭操作frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 关闭时同时停止 Spring BootServerPanel.taskStop();saveConfigToFile();}}});frame.pack();frame.setLocationRelativeTo(null);frame.setVisible(true);
// frame.setResizable(false);}private static EpilepsyDbConfig readEpilepsyDbConfigFromProperties() {EpilepsyDbConfig config = new EpilepsyDbConfig();Properties properties = loadProperties();config.setDatabase(properties.getProperty("epilepsysys.db.database"));config.setUrl(properties.getProperty("epilepsysys.db.url"));config.setPort(Integer.parseInt(properties.getProperty("epilepsysys.db.port")));config.setUsername(properties.getProperty("epilepsysys.db.username"));config.setPassword(properties.getProperty("epilepsysys.db.password"));return config;}private static GdDbConfig readGdDbConfigFromProperties() {GdDbConfig config = new GdDbConfig();Properties properties = loadProperties();config.setDatabase(properties.getProperty("gd.db.database"));config.setUrl(properties.getProperty("gd.db.url"));config.setUsername(properties.getProperty("gd.db.username"));config.setPassword(properties.getProperty("gd.db.password"));return config;}private static SysConfig readSysConfigFromProperties() {SysConfig config = new SysConfig();Properties properties = loadProperties();config.setCertPath(properties.getProperty("spring.config.import"));config.setAutoStart(properties.getProperty("sys.auto.start"));config.setZipName(properties.getProperty("sys.license.name"));return config;}private static Properties loadProperties() {Properties properties = new Properties();String filePath = System.getProperty("user.dir") + File.separator + "config/application.properties";try (InputStream input = new BufferedInputStream(Files.newInputStream(Paths.get(filePath)));) {properties.load(input);} catch (IOException e) {e.printStackTrace();}return properties;}public static void saveConfigToFile() {try {EpilepsysDbPanel.updateConfigFromGui();GdDbPanel.updateConfigFromGui();String filePath = System.getProperty("user.dir") + File.separator + "config/application.properties";Properties properties = new Properties();properties.setProperty("epilepsysys.db.database", epilepsyDbConfig.getDatabase());properties.setProperty("epilepsysys.db.url", epilepsyDbConfig.getUrl());properties.setProperty("epilepsysys.db.port", String.valueOf(epilepsyDbConfig.getPort()));properties.setProperty("epilepsysys.db.username", epilepsyDbConfig.getUsername());properties.setProperty("epilepsysys.db.password", epilepsyDbConfig.getPassword());properties.setProperty("gd.db.database", gdDbConfig.getDatabase());properties.setProperty("gd.db.url", gdDbConfig.getUrl());properties.setProperty("gd.db.username", gdDbConfig.getUsername());properties.setProperty("gd.db.password", gdDbConfig.getPassword());properties.setProperty("sys.auto.start", sysConfig.getAutoStart() == null ? "false" : sysConfig.getAutoStart());properties.setProperty("spring.config.import", sysConfig.getCertPath() == null ? "" : sysConfig.getCertPath());properties.setProperty("sys.license.name", sysConfig.getZipName() == null ? "" : sysConfig.getZipName());try (OutputStream output = Files.newOutputStream(Paths.get(filePath))) {properties.store(output, "Updated configuration");}} catch (IOException e) {Toolkit.getDefaultToolkit().beep();}}public static void disableConfigFields() {EpilepsysDbPanel.disableConfigFields();GdDbPanel.disableConfigFields();CertPanel.disableConfigFields();ServerPanel.disableConfigFields();}public static void enableConfigFields() {EpilepsysDbPanel.enableConfigFields();GdDbPanel.enableConfigFields();CertPanel.enableConfigFields();ServerPanel.enableConfigFields();}
}
系统数据库配置面板
/*** 系统数据库配置面板* @author YoungJ*/
@Slf4j
public class EpilepsysDbPanel {private static JTextField dbNameTextField;private static JTextField urlTextField;private static JTextField portTextField;private static JTextField usernameTextField;private static JPasswordField passwordTextField;private static final int y0 = MainPanel.EPILEPSDB_PANEL_START_COOR[1];private static final int x0 = MainPanel.EPILEPSDB_PANEL_START_COOR[0];private static EpilepsyDbConfig epilepsyDbConfig = MainPanel.epilepsyDbConfig;public static void buildJpanel(JPanel panel) {String dbName = epilepsyDbConfig.getDatabase();String url = epilepsyDbConfig.getUrl();String username = epilepsyDbConfig.getUsername();Integer port = epilepsyDbConfig.getPort();String password = epilepsyDbConfig.getPassword();panel.add(MainPanel.buildJBorder("系统数据库配置", x0, y0 + 10, MainPanel.CONFIG_PANEL_WIDTH, MainPanel.EPILEPSDB_PANEL_HEIGHT));// 数据库panel.add(MainPanel.buildJLabel("数据库:", x0 + 15, y0 + 40, 80, 25));dbNameTextField = MainPanel.buildJTextField(dbName, "database", 20, 100, y0 + 40, 165, 25);panel.add(dbNameTextField);// URLpanel.add(MainPanel.buildJLabel("URL:", x0 + 15, y0 + 70, 80, 25));urlTextField = MainPanel.buildJTextField(url, "url", 20, 100, y0 + 70, 165, 25);panel.add(urlTextField);// 端口panel.add(MainPanel.buildJLabel("端口:", x0 + 15, y0 + 100, 80, 25));portTextField = MainPanel.buildJTextField(port + "", "port", 20, 100, y0 + 100, 165, 25);panel.add(portTextField);// 用户名panel.add(MainPanel.buildJLabel("用户名:", x0 + 15, y0 + 130, 80, 25));usernameTextField = MainPanel.buildJTextField(username, "username", 20, 100, y0 + 130, 165, 25);panel.add(usernameTextField);// 密码panel.add(MainPanel.buildJLabel("密码:", x0 + 15, y0 + 160, 80, 25));passwordTextField = MainPanel.buildJPasswordField(password, "password", 20, 100, y0 + 160, 165, 25);panel.add(passwordTextField);}// 为按钮绑定监听private static void addActionListener(JButton saveButton) {saveButton.addActionListener(e -> updateConfigFromGui());}public static void updateConfigFromGui() {epilepsyDbConfig.setDatabase(dbNameTextField.getText());epilepsyDbConfig.setUrl(urlTextField.getText());epilepsyDbConfig.setPort(Integer.parseInt(portTextField.getText()));epilepsyDbConfig.setUsername(usernameTextField.getText());epilepsyDbConfig.setPassword(new String(passwordTextField.getPassword()));}public static void disableConfigFields() {dbNameTextField.setEnabled(false);urlTextField.setEnabled(false);portTextField.setEnabled(false);usernameTextField.setEnabled(false);passwordTextField.setEnabled(false);}public static void enableConfigFields() {dbNameTextField.setEnabled(true);urlTextField.setEnabled(true);portTextField.setEnabled(true);usernameTextField.setEnabled(true);passwordTextField.setEnabled(true);}
}
其他数据库配置面板
/*** 光电数据库配置面板* @author YoungJ*/
public class GdDbPanel {private static JTextField gdDbNameTextField;private static JTextField gdUrlTextField;private static JTextField gdUserNameTextField;private static JPasswordField gdPasswordTextField;private static final int y0 = MainPanel.GDDB_PANEL_START_COOR[1];private static final int x0 = MainPanel.GDDB_PANEL_START_COOR[0];private static GdDbConfig gdDbConfig = MainPanel.gdDbConfig;public static void buildJpanel(JPanel panel) {String dbName = gdDbConfig.getDatabase();String url = gdDbConfig.getUrl();String username = gdDbConfig.getUsername();String password = gdDbConfig.getPassword();//添加服务器配置区panel.add(MainPanel.buildJBorder("光电数据库配置", x0, y0 + 10, MainPanel.CONFIG_PANEL_WIDTH, MainPanel.GDDB_PANEL_HEIGHT));//数据库panel.add(MainPanel.buildJLabel("数据库:", x0 + 15, y0 + 30, 80, 25));gdDbNameTextField = MainPanel.buildJTextField(dbName, "database", 20, 100, y0 + 30, 165, 25);panel.add(gdDbNameTextField);//URLpanel.add(MainPanel.buildJLabel("URL:", x0 + 15, y0 + 60, 80, 25));gdUrlTextField = MainPanel.buildJTextField(url, "url", 20, 100, y0 + 60, 165, 25);panel.add(gdUrlTextField);//用户名panel.add(MainPanel.buildJLabel("用户名:", x0 + 15, y0 + 90, 80, 25));gdUserNameTextField = MainPanel.buildJTextField(username, "username", 20, 100, y0 + 90, 165, 25);panel.add(gdUserNameTextField);//密码panel.add(MainPanel.buildJLabel("密码:", x0 + 15, y0 + 120, 80, 25));gdPasswordTextField = MainPanel.buildJPasswordField(password, "password", 20, 100, y0 + 120, 165, 25);panel.add(gdPasswordTextField);}// 为按钮绑定监听private static void addActionListener(JButton saveButton) {saveButton.addActionListener(e -> {if (Objects.equals(saveButton.getText(), "保存")) {updateConfigFromGui();}});}// save eventpublic static void updateConfigFromGui() {gdDbConfig.setDatabase(gdDbNameTextField.getText());gdDbConfig.setUrl(gdUrlTextField.getText());gdDbConfig.setUsername(gdUserNameTextField.getText());gdDbConfig.setPassword(new String(gdPasswordTextField.getPassword()));}public static void disableConfigFields() {gdDbNameTextField.setEnabled(false);gdUrlTextField.setEnabled(false);gdUserNameTextField.setEnabled(false);gdPasswordTextField.setEnabled(false);}public static void enableConfigFields() {gdDbNameTextField.setEnabled(true);gdUrlTextField.setEnabled(true);gdUserNameTextField.setEnabled(true);gdPasswordTextField.setEnabled(true);}
}
证书配置面板
/*** 证书配置面板** @author YoungJ*/
@Slf4j
public class CertPanel {private static ImageIcon insertedIcon;private static ImageIcon notInsertedIcon;private static JLabel statusLabel;private static JLabel licenseLabel;private static JLabel licenseInfoLable;private static JLabel licenseDescLabel;private static JButton chooseButton;private static JLabel validLabel;private static JLabel failLabel;private static final int y0 = MainPanel.CERT_PANEL_START_COOR[1];private static final int x0 = MainPanel.CERT_PANEL_START_COOR[0];private static final String basePath = System.getProperty("user.dir");public static void buildJpanel(JPanel panel) {// 配置区panel.add(MainPanel.buildJBorder("证书配置", x0, y0 + 10, MainPanel.CONFIG_PANEL_WIDTH, MainPanel.CERT_PANEL_HEIGHT));insertedIcon = createStatusIcon(true);notInsertedIcon = createStatusIcon(false);panel.add(MainPanel.buildJLabel("加密锁:", x0 + 15, y0 + 30, 80, 25));// 证书按钮panel.add(MainPanel.buildJLabel("选择证书:", x0 + 15, y0 + 60, 80, 25));chooseButton = MainPanel.buildJButton("选择…", x0 + 100, y0 + 60, 80, 25);addActionListener(chooseButton, panel);panel.add(chooseButton);String certPath = MainPanel.sysConfig.getCertPath();if (StringUtils.isNotBlank(certPath)) {// 新增状态显示组件JPanel licensePanel = new JPanel();licensePanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0));licenseLabel = new JLabel(insertedIcon); // 图标licenseLabel.setText("已安装"); // 文本licensePanel.add(licenseLabel);licensePanel.setBounds(x0 + 220, y0 + 60, 80, 20);panel.add(licensePanel);if (checkFileExist("license.properties")&& checkFileExist("license.lic")&& checkFileExist("publicCerts.keystore")) {chooseButton.setText("更换…");panel.add(licenseDescLabel = MainPanel.buildJLabel("证书详情:", x0 + 15, y0 + 100, 80, 20));panel.add(licenseInfoLable = MainPanel.buildJLabel(MainPanel.sysConfig.getZipName(), x0 + 100, y0 + 100, 165, 20));}} else {JPanel licensePanel = new JPanel();licensePanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0));licenseLabel = new JLabel(notInsertedIcon); // 图标licenseLabel.setText("未安装"); // 文本licensePanel.add(licenseLabel);licensePanel.setBounds(x0 + 220, y0 + 60, 80, 20);panel.add(licensePanel);}// 新增状态显示组件JPanel statusPanel = new JPanel();statusPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0));statusLabel = new JLabel(notInsertedIcon); // 图标statusLabel.setText("未检测"); // 文本setDongleStatus(DongleManager.isLicenseInserted());statusPanel.add(statusLabel);statusPanel.setBounds(x0 + 220, y0 + 30, 80, 20);panel.add(statusPanel);// 初始化 USB 检查定时器,每隔一段时间检查一次状态Timer dongleCheckTimer = new Timer(30000, e -> setDongleStatus(DongleManager.isLicenseInserted()));dongleCheckTimer.start();}// 为按钮绑定监听private static void addActionListener(JButton button, JPanel panel) {button.addActionListener(e -> {JFileChooser fileChooser = new JFileChooser();fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);fileChooser.setFileFilter(new FileFilter() {@Overridepublic boolean accept(File f) {return f.getName().toLowerCase().endsWith(".zip") || f.isDirectory();}@Overridepublic String getDescription() {return "ZIP Files (*.zip)";}});int result = fileChooser.showOpenDialog(null);if (result == JFileChooser.APPROVE_OPTION) {File selectedFile = fileChooser.getSelectedFile();extractZip(selectedFile, panel);}});}private static void extractZip(File zipFile, JPanel panel) {try (ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(zipFile.toPath()))) {ZipEntry entry;if (licenseDescLabel == null) {panel.add(licenseDescLabel = MainPanel.buildJLabel("证书详情", x0 + 15, y0 + 100, 80, 25));}while ((entry = zipInputStream.getNextEntry()) != null) {if (!entry.isDirectory()) {Path outputPath = Paths.get(basePath, entry.getName());Files.copy(zipInputStream, outputPath, StandardCopyOption.REPLACE_EXISTING);}}if (licenseInfoLable == null) {panel.add(licenseInfoLable = MainPanel.buildJLabel(zipFile.getName(), x0 + 100, y0 + 100, 80, 25));} else {licenseInfoLable.setText(zipFile.getName());}chooseButton.setText("更换…");panel.updateUI();MainPanel.sysConfig.setCertPath("file:" + basePath + File.separator + "license.properties");MainPanel.sysConfig.setZipName(zipFile.getName());setLicenseStatus(true);MainPanel.saveConfigToFile();JOptionPane.showMessageDialog(null, "安装成功!");} catch (IOException e) {log.error(e.getMessage());JOptionPane.showMessageDialog(null, "安装失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);}}private static boolean checkFileExist(String fileName) {Path filePath = Paths.get(basePath, fileName);if (Files.exists(filePath)) {return true;} else {return false;}}public static void addCertLabel() {if (LicenseInfo.VERIFY_SUCCESS) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String notBefore = sdf.format(LicenseInfo.notBefore);String notAfter = sdf.format(LicenseInfo.notAfter);MainPanel.mainPanel.add(validLabel = MainPanel.buildJLabel("有效期至: " + notAfter, x0 + 15, y0 + 130, 250, 20));validLabel.setToolTipText(notBefore + " 至 " + notAfter);} else {MainPanel.mainPanel.add(validLabel = MainPanel.buildJLabel("失败原因: " + String.join(",", LicenseInfo.FAIL_MSG), x0 + 15, y0 + 130, 250, 20));validLabel.setToolTipText("失败原因:" + String.join(",", LicenseInfo.FAIL_MSG));}MainPanel.mainPanel.updateUI();}public static void removeCertLabel() {if (validLabel != null) {MainPanel.mainPanel.remove(validLabel);}if (failLabel != null) {MainPanel.mainPanel.remove(failLabel);}MainPanel.mainPanel.updateUI();}public static void disableConfigFields() {chooseButton.setEnabled(false);}public static void enableConfigFields() {chooseButton.setEnabled(true);}// 用于设置插入状态private static void setDongleStatus(boolean status) {if (status) {statusLabel.setIcon(insertedIcon);statusLabel.setText("已插入");} else {statusLabel.setIcon(notInsertedIcon);statusLabel.setText("未插入");}}// 用于设置证书安装状态private static void setLicenseStatus(boolean status) {if (status) {licenseLabel.setIcon(insertedIcon);licenseLabel.setText("已安装");} else {licenseLabel.setIcon(notInsertedIcon);licenseLabel.setText("未安装");}}// 新增方法用于创建状态图标private static ImageIcon createStatusIcon(boolean status) {int size = 12; // 调整图标大小ImageIcon icon = new ImageIcon(new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB));Graphics g = icon.getImage().getGraphics();if (status) {g.setColor(Color.GREEN);} else {g.setColor(Color.GRAY);}g.fillOval(0, 0, size, size);g.dispose();return icon;}}
服务管理面板
/*** 服务面板* @author YoungJ*/
public class ServerPanel {private static JButton stopBtn;private static JButton startBtn;private static JRadioButton yesRadioBtn;private static JRadioButton noRadioBtn;private static PrintStream consolePrintStream;private static JLabel statusLabel;// 使用单线程的 ExecutorServicepublic static ExecutorService executorService = Executors.newSingleThreadExecutor();private static final int y0 = MainPanel.SERVER_PANEL_START_COOR[1];private static final int x0 = MainPanel.SERVER_PANEL_START_COOR[0];private enum StartupStatus {STARTING, STARTED, NOT_STARTED}private static ImageIcon startingIcon;private static ImageIcon startedIcon;private static ImageIcon notStartedIcon;public static JPanel buildJpanel(JPanel panel) {panel.add(MainPanel.buildJBorder("服务管理", x0, y0 + 10, MainPanel.CONFIG_PANEL_WIDTH, MainPanel.SERVER_PANEL_HEIGHT));// 初始化图标startingIcon = createStatusIcon(StartupStatus.STARTING);startedIcon = createStatusIcon(StartupStatus.STARTED);notStartedIcon = createStatusIcon(StartupStatus.NOT_STARTED);// 新增状态显示组件JPanel statusPanel = new JPanel(); // 创建容器用于放置图标和文本statusPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0)); // 设置容器布局statusLabel = new JLabel(notStartedIcon); // 图标statusLabel.setText("未启动"); // 文本statusPanel.add(statusLabel);statusPanel.setBounds(x0 + 220, y0 + 30, 80, 20);panel.add(statusPanel);String osName = System.getProperty("os.name");String osArch = System.getProperty("os.arch");panel.add(MainPanel.buildJLabel(osName + " " + osArch, x0 + 10, y0 + 30, 200, 20));panel.add(MainPanel.buildJLabel("自动启动:", x0 + 10, y0 + 60, 80, 20));panel.add(yesRadioBtn = MainPanel.buildJRadioButton("是", x0 + 90, y0 + 60, 50, 20));panel.add(noRadioBtn = MainPanel.buildJRadioButton("否", x0 + 150, y0 + 60, 50, 20));ButtonGroup buttonGroup = new ButtonGroup();buttonGroup.add(yesRadioBtn);buttonGroup.add(noRadioBtn);if ("true".equals(MainPanel.sysConfig.getAutoStart())) {buttonGroup.setSelected(yesRadioBtn.getModel(), true);taskStart();} else {buttonGroup.setSelected(noRadioBtn.getModel(), true);}addActionListener(yesRadioBtn);addActionListener(noRadioBtn);// 开始按钮startBtn = MainPanel.buildJButton("启动", x0 + 50, y0 + 100, 80, 25);addActionListener(startBtn);panel.add(startBtn);//添加停止上报按钮stopBtn = MainPanel.buildJButton("停止", x0 + 150, y0 + 100, 80, 25);stopBtn.setEnabled(false);addActionListener(stopBtn);panel.add(stopBtn);return panel;}// 为按钮绑定监听private static void addActionListener(JButton button) {button.addActionListener(e -> {if (Objects.equals(button.getText(), "启动")) {taskStart();} else if (Objects.equals(button.getText(), "停止")) {taskStop();}});}// 为按钮绑定监听private static void addActionListener(JRadioButton button) {button.addActionListener(e -> {if (Objects.equals(button.getText(), "是")) {MainPanel.sysConfig.setAutoStart("true");} else {MainPanel.sysConfig.setAutoStart("false");}});}// 开始private static void taskStart() {SwingUtilities.invokeLater(() -> {startBtn.setEnabled(false);setStartupStatus(StartupStatus.STARTING);MainPanel.disableConfigFields();MainPanel.saveConfigToFile();// 创建一个新的 PrintStream,用于重定向 System.outconsolePrintStream = new PrintStream(new LogPanel.LogOutputStream());// 将 System.out 重定向到新的 PrintStreamSystem.setOut(consolePrintStream);System.setErr(consolePrintStream);// 使用新线程去启动,否则会阻塞其他线程executorService.execute(() -> {CertPanel.removeCertLabel();try {ManageApplication.startSpringBoot(MainPanel.epilepsyDbConfig, MainPanel.gdDbConfig);} catch (Exception e) {MainPanel.enableConfigFields();stopBtn.setEnabled(false);startBtn.setEnabled(true);setStartupStatus(StartupStatus.NOT_STARTED);return;}stopBtn.setEnabled(true);startBtn.setEnabled(false);setStartupStatus(StartupStatus.STARTED);CertPanel.addCertLabel();if (LicenseInfo.VERIFY_SUCCESS) {openBrowser("http://localhost:9999");}});});}public static void main(String[] args) {try {System.out.println("Local IP Addresses:");Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();while (networkInterfaces.hasMoreElements()) {NetworkInterface networkInterface = networkInterfaces.nextElement();Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();while (inetAddresses.hasMoreElements()) {InetAddress inetAddress = inetAddresses.nextElement();if (!inetAddress.isLoopbackAddress() && !inetAddress.getHostAddress().contains(":")) {System.out.println(" " + networkInterface.getName() + ": " + inetAddress.getHostAddress());}}}} catch (Exception e) {e.printStackTrace();}}public static void openBrowser(String url) {try {if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {Desktop.getDesktop().browse(new URI(url));} else {System.err.println("Desktop or Browser is not supported on this platform");}} catch (Exception e) {e.printStackTrace();}}// 停止服务public static void taskStop() {MainPanel.enableConfigFields();// 开始执行任务SwingUtilities.invokeLater(ManageApplication::stopSpringBoot);stopBtn.setEnabled(false);startBtn.setEnabled(true);setStartupStatus(StartupStatus.NOT_STARTED);}// 新增方法用于设置启动状态private static void setStartupStatus(StartupStatus status) {switch (status) {case STARTING:statusLabel.setIcon(startingIcon);statusLabel.setText("启动中");break;case STARTED:statusLabel.setIcon(startedIcon);statusLabel.setText("已启动");break;case NOT_STARTED:statusLabel.setIcon(notStartedIcon);statusLabel.setText("未启动");break;}}// 新增方法用于创建状态图标private static ImageIcon createStatusIcon(StartupStatus status) {int size = 12; // 调整图标大小ImageIcon icon = new ImageIcon(new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB));Graphics g = icon.getImage().getGraphics();switch (status) {case STARTING:g.setColor(Color.ORANGE);break;case STARTED:g.setColor(Color.GREEN);break;case NOT_STARTED:g.setColor(Color.GRAY);break;}g.fillOval(0, 0, size, size);g.dispose();return icon;}public static void disableConfigFields() {yesRadioBtn.setEnabled(false);noRadioBtn.setEnabled(false);}public static void enableConfigFields() {yesRadioBtn.setEnabled(true);noRadioBtn.setEnabled(true);}}
日志区域面板
/*** 日志面板** @author YoungJ*/
public class LogPanel {private static final int MAX_LOG_LINES = 1000; // 限制最大日志行数public static JTextArea logTextArea;private static final int y0 = MainPanel.LOG_PANEL_START_COOR[1];private static final int x0 = MainPanel.LOG_PANEL_START_COOR[0];private static JButton clearBtn;public static void buildJpanel(JPanel panel) {panel.add(MainPanel.buildJBorder("服务日志", x0, y0 + 10, MainPanel.LOG_PANEL_WIDTH, MainPanel.LOG_PANEL_HEIGHT));panel.add(clearBtn = MainPanel.buildJButton("清空", x0 + 10, y0 + 28, 60, 20));addActionListener(clearBtn);logTextArea = new JTextArea();logTextArea.setEditable(false);panel.add(logTextArea);JScrollPane logScrollPane = new JScrollPane();logScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);logScrollPane.setBounds(x0 + 10, y0 + 50, MainPanel.LOG_PANEL_WIDTH - 10, MainPanel.LOG_PANEL_HEIGHT - 50);logScrollPane.getViewport().add(logTextArea);panel.add(logScrollPane);LogPanelAppender.setTextArea(logTextArea);}public static void print(String message) {SwingUtilities.invokeLater(() -> {logTextArea.append(message);int lineCount = logTextArea.getLineCount();if (lineCount > MAX_LOG_LINES) {try {int startOffset = logTextArea.getLineStartOffset(0);int endOffset = logTextArea.getLineEndOffset(lineCount - MAX_LOG_LINES);logTextArea.replaceRange("", startOffset, endOffset);} catch (Exception e) {e.printStackTrace();}}});logTextArea.setCaretPosition(logTextArea.getDocument().getLength());}// 自定义 OutputStream,用于重定向输出流到 LogPanelstatic class LogOutputStream extends OutputStream {@Overridepublic void write(int b) {}@Overridepublic void write(byte[] b, int off, int len) {SwingUtilities.invokeLater(() -> print(new String(b, off, len)));}}private static void addActionListener(JButton button) {button.addActionListener(e -> {if (Objects.equals(button.getText(), "清空")) {logTextArea.setText("");}});}}
将Log4j2的日志输出到GUI上
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.layout.PatternLayout;import javax.swing.*;
import java.io.Serializable;/*** 日志Appender,将log4j2的日志输出到GUI* @author zhaoyajie*/
public class LogPanelAppender extends AbstractAppender {private static JTextArea textArea;// 静态方法用于设置 textAreapublic static void setTextArea(JTextArea logTextArea) {textArea = logTextArea;}private LogPanelAppender(String name, Layout<? extends Serializable> layout, Filter filter, boolean ignoreExceptions) {super(name, filter, layout, ignoreExceptions);}@Overridepublic void append(LogEvent event) {if (event != null && textArea != null) {String message = new String(getLayout().toByteArray(event));SwingUtilities.invokeLater(() -> textArea.append(message + "\n"));}}// 需要提供一个工厂方法,Log4j2 才能正确初始化 Appenderpublic static Appender createAppender(String name, JTextArea area) {textArea = area;Layout<? extends Serializable> layout = PatternLayout.createDefaultLayout();return new LogPanelAppender(name, layout, null, false);}
}
log4j2配置文件增加输出配置
# 添加 LogPanelAppender
appender.logPanel.type = com.xxx.gui.LogPanelAppender
appender.logPanel.name = LogPanelAppender
appender.logPanel.layout.type = PatternLayout
appender.logPanel.layout.pattern = %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%nrootLogger.appenderRefs = stdout,I,E,logPanelrootLogger.appenderRef.logPanel.ref = LogPanelAppender
Springboot启动类
@SpringBootApplication
@Slf4j
public class ManageApplication {private static boolean isSpringBootRunning = false;private static ConfigurableApplicationContext context;private static MainPanel mainPanel;public static void main(String[] args) {SwingUtilities.invokeLater(() -> mainPanel = new MainPanel());}public static void startSpringBoot(EpilepsyDbConfig epilepsyDbConfig, GdDbConfig gdDbConfig) {if (!isSpringBootRunning) {// 设置Spring Boot启动参数SpringApplication app = new SpringApplication(ManageApplication.class);Map<String, Object> properties = new HashMap<>();properties.put("spring.profiles.active", "prod");properties.put("epilepsysys.db.database", epilepsyDbConfig.getDatabase());properties.put("epilepsysys.db.url", epilepsyDbConfig.getUrl());properties.put("epilepsysys.db.port", String.valueOf(epilepsyDbConfig.getPort()));properties.put("epilepsysys.db.username", epilepsyDbConfig.getUsername());properties.put("epilepsysys.db.password", epilepsyDbConfig.getPassword());properties.put("gd.db.database", gdDbConfig.getDatabase());properties.put("gd.db.url", gdDbConfig.getUrl());properties.put("gd.db.username", gdDbConfig.getUsername());properties.put("gd.db.password", gdDbConfig.getPassword());if (StringUtils.isNotBlank(MainPanel.sysConfig.getCertPath())) {properties.put("spring.config.import", MainPanel.sysConfig.getCertPath());}app.setDefaultProperties(properties);context = app.run();isSpringBootRunning = true;}}public static void stopSpringBoot() {if (isSpringBootRunning) {SpringApplication.exit(context);isSpringBootRunning = false;}}}
关注公众号“呲花是朵花”,查看更多的实用技术分享