From 686fe55892de7bf8d206cddbead77a5fbdb0e091 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期日, 08 三月 2026 19:59:29 +0800
Subject: [PATCH] #

---
 src/main/java/com/zy/system/controller/LicenseCreatorController.java      |   98 ++-
 src/main/java/com/zy/system/entity/license/LicenseUtils.java              |  104 ++++
 src/main/java/com/zy/system/entity/license/LicenseCheckListener.java      |   69 ++
 src/main/java/com/zy/system/entity/LicenseInfos.java                      |    4 
 src/main/webapp/views/config/config.html                                  |    3 
 src/main/java/com/zy/system/entity/license/LicenseNodeCheck.java          |   22 
 src/main/java/com/zy/system/config/LicenseSchemaInitializer.java          |   49 ++
 src/main/java/com/zy/system/entity/license/CustomLicenseManager.java      |   31 
 src/main/java/com/zy/system/entity/license/LicenseUploadParam.java        |    9 
 src/main/java/com/zy/system/entity/license/LicenseVerify.java             |   36 +
 src/main/resources/mapper/LicenseInfosMapper.xml                          |    5 
 src/main/java/com/zy/system/mapper/LicenseInfosMapper.java                |    3 
 src/main/resources/sql/20260307_add_request_code_to_sys_license_infos.sql |   42 +
 src/main/java/com/zy/system/entity/license/LicenseBindModel.java          |   21 
 src/main/java/com/zy/system/entity/license/LicenseBindingSupport.java     |  196 ++++++++
 src/main/java/com/zy/system/entity/license/LicenseRequestCode.java        |   19 
 src/main/webapp/views/index.html                                          |  227 +++++++--
 src/main/java/com/zy/system/service/LicenseInfosService.java              |    2 
 src/main/webapp/static/js/config/config.js                                |  111 ----
 src/main/java/com/zy/system/timer/LicenseTimer.java                       |   82 ++
 src/main/webapp/views/login.html                                          |  254 ++++++----
 src/main/resources/application.yml                                        |    3 
 src/main/java/com/zy/system/service/impl/LicenseInfosServiceImpl.java     |    5 
 23 files changed, 1,052 insertions(+), 343 deletions(-)

diff --git a/src/main/java/com/zy/system/config/LicenseSchemaInitializer.java b/src/main/java/com/zy/system/config/LicenseSchemaInitializer.java
new file mode 100644
index 0000000..98e52a1
--- /dev/null
+++ b/src/main/java/com/zy/system/config/LicenseSchemaInitializer.java
@@ -0,0 +1,49 @@
+package com.zy.system.config;
+
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+@Component
+public class LicenseSchemaInitializer {
+
+    private final DataSource dataSource;
+
+    public LicenseSchemaInitializer(DataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    @PostConstruct
+    public void init() {
+        ensureColumn("sys_license_infos", "request_code", "VARCHAR(2048)");
+    }
+
+    private void ensureColumn(String tableName, String columnName, String columnDefinition) {
+        try (Connection connection = dataSource.getConnection()) {
+            if (hasColumn(connection, tableName, columnName)) {
+                return;
+            }
+            try (Statement statement = connection.createStatement()) {
+                statement.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnName + " " + columnDefinition);
+            }
+        } catch (Exception ignored) {
+        }
+    }
+
+    private boolean hasColumn(Connection connection, String tableName, String columnName) throws Exception {
+        DatabaseMetaData metaData = connection.getMetaData();
+        try (ResultSet resultSet = metaData.getColumns(connection.getCatalog(), null, tableName, null)) {
+            while (resultSet.next()) {
+                if (columnName.equalsIgnoreCase(resultSet.getString("COLUMN_NAME"))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/com/zy/system/controller/LicenseCreatorController.java b/src/main/java/com/zy/system/controller/LicenseCreatorController.java
index f40ee5a..a908113 100644
--- a/src/main/java/com/zy/system/controller/LicenseCreatorController.java
+++ b/src/main/java/com/zy/system/controller/LicenseCreatorController.java
@@ -1,18 +1,21 @@
 package com.zy.system.controller;
 
 import com.core.common.R;
+import com.zy.system.entity.LicenseInfos;
 import com.zy.system.entity.license.*;
+import com.zy.system.service.LicenseInfosService;
 import com.zy.system.timer.LicenseTimer;
+import de.schlichtherle.license.LicenseContent;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
 
-import java.io.File;
-import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 
 /**
  *
@@ -24,12 +27,20 @@
 
     @Value("${license.subject}")
     private String licenseSubject;
+    @Value("${license.publicAlias}")
+    private String publicAlias;
+    @Value("${license.storePass}")
+    private String storePass;
     @Value("${license.licensePath}")
     private String licensePath;
+    @Value("${license.publicKeysStorePath}")
+    private String publicKeysStorePath;
     @Autowired
     private LicenseCheckListener licenseCheckListener;
     @Autowired
     private LicenseTimer licenseTimer;
+    @Autowired
+    private LicenseInfosService licenseInfosService;
     /**
      * 鑾峰彇鏈嶅姟鍣ㄧ‖浠朵俊鎭�
      * @param osName 鎿嶄綔绯荤粺绫诲瀷锛屽鏋滀负绌哄垯鑷姩鍒ゆ柇
@@ -37,6 +48,14 @@
     @RequestMapping(value = "/getServerInfos",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
     public LicenseCheck getServerInfos(@RequestParam(value = "osName",required = false) String osName) {
         return LicenseUtils.getServerInfos();
+    }
+
+    /**
+     * 鑾峰彇璇锋眰鐮併��
+     */
+    @RequestMapping(value = "/getRequestCode")
+    public R getRequestCode() {
+        return R.ok(LicenseUtils.buildRequestCode(licenseSubject));
     }
 
     /**
@@ -51,42 +70,46 @@
         return R.ok().add(licenseDays);
     }
 
-    @RequestMapping(value = "/updateLicense")
-    public R updateLicense(@RequestParam("file") MultipartFile[] files){
-        MultipartFile file = files[0];
-
-        String licensePathFileName = this.getClass().getClassLoader().getResource(licensePath).getPath();
-        File licensePathFile = new File(licensePathFileName);
-        //鏈嶅姟鍣ㄧ淇濆瓨鐨勬枃浠跺璞�
-        File serverFile = new File(licensePathFile.getPath());
-        if (serverFile.exists()) {
-            try {
-                serverFile.delete();//瀛樺湪鏂囦欢锛屽垹闄�
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
+    @RequestMapping(value = "/updateLicense", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
+    public R updateLicense(@RequestBody LicenseUploadParam param){
+        if (param == null || param.getLicense() == null || param.getLicense().trim().isEmpty()) {
+            return R.error("璁稿彲璇佸唴瀹逛笉鑳戒负绌�");
         }
 
-        try {
-            //鍒涘缓鏂囦欢
-            serverFile.createNewFile();
-            //灏嗕笂浼犵殑鏂囦欢鍐欏叆鍒版湇鍔″櫒绔枃浠跺唴
-            file.transferTo(serverFile);
-        } catch (IOException e) {
-            e.printStackTrace();
+        String licenseBase64 = param.getLicense().trim();
+        LicenseVerifyParam verifyParam = buildVerifyParam();
+        LicenseVerify licenseVerify = new LicenseVerify();
+        LicenseContent install = licenseVerify.install(verifyParam, licenseBase64);
+        if (install == null) {
+            return R.error("璁稿彲璇佸唴瀹规棤鏁�");
         }
 
-        //閲嶆柊鍔犺浇璁稿彲璇�
-        boolean loadedLicense = licenseCheckListener.loadLicense();
-        if (loadedLicense) {
-            return R.ok();
+        LicenseInfos licenseInfos = new LicenseInfos();
+        licenseInfos.setLicense(licenseBase64);
+        licenseInfos.setLicenseTime(formatLicenseTime(install));
+        licenseInfos.setRequestCode(LicenseUtils.buildRequestCode(licenseSubject));
+        licenseInfos.setCreateTime(new Date());
+        if (!licenseInfosService.insert(licenseInfos)) {
+            return R.error("璁稿彲璇佷繚瀛樺け璐�");
         }
-        return R.error("璁稿彲璇佹洿鏂板け璐�");
+
+        boolean loadedLicense = licenseCheckListener.loadLicense(false);
+        if (!loadedLicense) {
+            return R.error("璁稿彲璇佹縺娲诲け璐�");
+        }
+        licenseTimer.verify();
+        if (!licenseTimer.getSystemSupport()) {
+            return R.error("璁稿彲璇佹牎楠屽け璐�");
+        }
+        return R.ok();
     }
 
     @RequestMapping(value = "/activate")
     public R activate() {
         licenseTimer.timer();
+        if (!licenseTimer.getSystemSupport()) {
+            return R.error("璁稿彲璇佹縺娲诲け璐�");
+        }
         return R.ok();
     }
 
@@ -95,4 +118,19 @@
         return R.ok(licenseSubject);
     }
 
-}
\ No newline at end of file
+    private LicenseVerifyParam buildVerifyParam() {
+        LicenseVerifyParam param = new LicenseVerifyParam();
+        param.setSubject(licenseSubject);
+        param.setPublicAlias(publicAlias);
+        param.setStorePass(storePass);
+        param.setLicensePath(licensePath);
+        param.setPublicKeysStorePath(publicKeysStorePath);
+        return param;
+    }
+
+    private String formatLicenseTime(LicenseContent install) {
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        return format.format(install.getNotBefore()) + "  -  " + format.format(install.getNotAfter());
+    }
+
+}
diff --git a/src/main/java/com/zy/system/entity/LicenseInfos.java b/src/main/java/com/zy/system/entity/LicenseInfos.java
index dafec6d..7c41d93 100644
--- a/src/main/java/com/zy/system/entity/LicenseInfos.java
+++ b/src/main/java/com/zy/system/entity/LicenseInfos.java
@@ -29,6 +29,10 @@
     private String licenseTime;
 
     @ApiModelProperty(value= "")
+    @TableField("request_code")
+    private String requestCode;
+
+    @ApiModelProperty(value= "")
     @TableField("create_time")
     @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
     private Date createTime;
diff --git a/src/main/java/com/zy/system/entity/license/CustomLicenseManager.java b/src/main/java/com/zy/system/entity/license/CustomLicenseManager.java
index 223706d..0096748 100644
--- a/src/main/java/com/zy/system/entity/license/CustomLicenseManager.java
+++ b/src/main/java/com/zy/system/entity/license/CustomLicenseManager.java
@@ -1,6 +1,5 @@
 package com.zy.system.entity.license;
 
-import com.core.common.Cools;
 import de.schlichtherle.license.*;
 import de.schlichtherle.xml.GenericCertificate;
 import org.apache.logging.log4j.LogManager;
@@ -120,13 +119,23 @@
         //1. 棣栧厛璋冪敤鐖剁被鐨剉alidate鏂规硶
         super.validate(content);
 
-        //2. 鐒跺悗鏍¢獙鑷畾涔夌殑License鍙傛暟
-        //License涓彲琚厑璁哥殑鍙傛暟淇℃伅
-        LicenseCheck expectedCheckModel = (LicenseCheck) content.getExtra();
-        //褰撳墠鏈嶅姟鍣ㄧ湡瀹炵殑鍙傛暟淇℃伅
+        Object extra = content.getExtra();
         LicenseCheck serverCheckModel = LicenseUtils.getServerInfos();
 
-        if(expectedCheckModel != null && serverCheckModel != null){
+        if (serverCheckModel == null) {
+            throw new LicenseContentException("涓嶈兘鑾峰彇鏈嶅姟鍣ㄧ‖浠朵俊鎭�");
+        }
+
+        if (LicenseBindingSupport.isV2Extra(extra)) {
+            LicenseBindModel bindModel = LicenseBindingSupport.parseBindModel(extra);
+            if (!LicenseBindingSupport.matches(bindModel, serverCheckModel)) {
+                throw new LicenseContentException("褰撳墠鏈嶅姟鍣ㄤ笉鍦ㄦ巿鏉冭妭鐐硅寖鍥村唴");
+            }
+            return;
+        }
+
+        LicenseCheck expectedCheckModel = LicenseBindingSupport.parseLegacyLicenseCheck(extra);
+        if(expectedCheckModel != null){
             //鏍¢獙IP鍦板潃
             if(!checkIpAddress(expectedCheckModel.getIpAddress(),serverCheckModel.getIpAddress())){
                 throw new LicenseContentException("褰撳墠鏈嶅姟鍣ㄧ殑IP娌″湪鎺堟潈鑼冨洿鍐�");
@@ -210,8 +219,8 @@
      * 鏍¢獙褰撳墠鏈嶅姟鍣ㄧ‖浠讹紙涓绘澘銆丆PU绛夛級搴忓垪鍙锋槸鍚﹀湪鍙厑璁歌寖鍥村唴
      */
     private boolean checkSerial(String expectedSerial,String serverSerial){
-        if(!Cools.isEmpty(expectedSerial)){
-            if(!Cools.isEmpty(serverSerial)){
+        if(!isBlank(expectedSerial)){
+            if(!isBlank(serverSerial)){
                 if(expectedSerial.equals(serverSerial)){
                     return true;
                 }
@@ -222,4 +231,8 @@
         }
     }
 
-}
\ No newline at end of file
+    private boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+
+}
diff --git a/src/main/java/com/zy/system/entity/license/LicenseBindModel.java b/src/main/java/com/zy/system/entity/license/LicenseBindModel.java
new file mode 100644
index 0000000..cfe4d5a
--- /dev/null
+++ b/src/main/java/com/zy/system/entity/license/LicenseBindModel.java
@@ -0,0 +1,21 @@
+package com.zy.system.entity.license;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class LicenseBindModel implements Serializable {
+
+    private static final long serialVersionUID = 7064744215406459726L;
+
+    private Integer version = 2;
+
+    private String bindMode = "MULTI_NODE";
+
+    private String matchMode = "ANY";
+
+    private List<LicenseNodeCheck> nodes = new ArrayList<>();
+}
diff --git a/src/main/java/com/zy/system/entity/license/LicenseBindingSupport.java b/src/main/java/com/zy/system/entity/license/LicenseBindingSupport.java
new file mode 100644
index 0000000..c354074
--- /dev/null
+++ b/src/main/java/com/zy/system/entity/license/LicenseBindingSupport.java
@@ -0,0 +1,196 @@
+package com.zy.system.entity.license;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class LicenseBindingSupport {
+
+    private LicenseBindingSupport() {
+    }
+
+    public static boolean isV2Extra(Object extra) {
+        if (extra == null) {
+            return false;
+        }
+        if (extra instanceof LicenseBindModel) {
+            return true;
+        }
+        if (extra instanceof JSONObject) {
+            return isV2Json((JSONObject) extra);
+        }
+        if (extra instanceof String) {
+            String text = ((String) extra).trim();
+            if (isBlank(text) || !text.startsWith("{")) {
+                return false;
+            }
+            return isV2Json(JSON.parseObject(text));
+        }
+        return false;
+    }
+
+    public static LicenseBindModel parseBindModel(Object extra) {
+        if (extra == null) {
+            return null;
+        }
+        if (extra instanceof LicenseBindModel) {
+            return normalizeBindModel((LicenseBindModel) extra);
+        }
+        if (extra instanceof JSONObject) {
+            return normalizeBindModel(((JSONObject) extra).toJavaObject(LicenseBindModel.class));
+        }
+        if (extra instanceof String) {
+            String text = ((String) extra).trim();
+            if (isBlank(text)) {
+                return null;
+            }
+            return normalizeBindModel(JSON.parseObject(text, LicenseBindModel.class));
+        }
+        return normalizeBindModel(JSON.parseObject(JSON.toJSONString(extra), LicenseBindModel.class));
+    }
+
+    public static LicenseCheck parseLegacyLicenseCheck(Object extra) {
+        if (extra == null) {
+            return null;
+        }
+        if (extra instanceof LicenseCheck) {
+            return (LicenseCheck) extra;
+        }
+        if (extra instanceof JSONObject) {
+            return ((JSONObject) extra).toJavaObject(LicenseCheck.class);
+        }
+        if (extra instanceof String) {
+            String text = ((String) extra).trim();
+            if (isBlank(text)) {
+                return null;
+            }
+            return JSON.parseObject(text, LicenseCheck.class);
+        }
+        return JSON.parseObject(JSON.toJSONString(extra), LicenseCheck.class);
+    }
+
+    public static boolean matches(LicenseBindModel licenseBind, LicenseCheck serverCheck) {
+        if (licenseBind == null) {
+            return false;
+        }
+        if ("UNLIMITED".equalsIgnoreCase(trimToEmpty(licenseBind.getBindMode()))) {
+            return true;
+        }
+        List<LicenseNodeCheck> nodes = licenseBind.getNodes();
+        if (nodes == null || nodes.isEmpty()) {
+            return true;
+        }
+        for (LicenseNodeCheck node : nodes) {
+            if (matchesNode(node, serverCheck)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isV2Json(JSONObject jsonObject) {
+        if (jsonObject == null) {
+            return false;
+        }
+        return jsonObject.containsKey("nodes")
+                || jsonObject.containsKey("bindMode")
+                || jsonObject.containsKey("matchMode")
+                || Integer.valueOf(2).equals(jsonObject.getInteger("version"));
+    }
+
+    private static LicenseBindModel normalizeBindModel(LicenseBindModel source) {
+        if (source == null) {
+            return null;
+        }
+        LicenseBindModel target = new LicenseBindModel();
+        target.setVersion(source.getVersion() == null ? 2 : source.getVersion());
+        target.setBindMode(isBlank(source.getBindMode()) ? "MULTI_NODE" : source.getBindMode().trim());
+        target.setMatchMode(isBlank(source.getMatchMode()) ? "ANY" : source.getMatchMode().trim());
+
+        List<LicenseNodeCheck> normalizedNodes = new ArrayList<>();
+        if (source.getNodes() != null) {
+            for (LicenseNodeCheck node : source.getNodes()) {
+                if (node == null) {
+                    continue;
+                }
+                LicenseNodeCheck normalizedNode = new LicenseNodeCheck();
+                normalizedNode.setNodeId(trimToEmpty(node.getNodeId()));
+                normalizedNode.setIpAddress(normalizeList(node.getIpAddress(), false));
+                normalizedNode.setMacAddress(normalizeList(node.getMacAddress(), true));
+                normalizedNode.setCpuSerial(trimToEmpty(node.getCpuSerial()));
+                normalizedNode.setMainBoardSerial(trimToEmpty(node.getMainBoardSerial()));
+                normalizedNodes.add(normalizedNode);
+            }
+        }
+        target.setNodes(normalizedNodes);
+        return target;
+    }
+
+    private static boolean matchesNode(LicenseNodeCheck node, LicenseCheck serverCheck) {
+        if (node == null || serverCheck == null) {
+            return false;
+        }
+        if (!checkList(node.getIpAddress(), serverCheck.getIpAddress())) {
+            return false;
+        }
+        if (!checkList(node.getMacAddress(), serverCheck.getMacAddress())) {
+            return false;
+        }
+        if (!checkSerial(node.getMainBoardSerial(), serverCheck.getMainBoardSerial())) {
+            return false;
+        }
+        return checkSerial(node.getCpuSerial(), serverCheck.getCpuSerial());
+    }
+
+    private static boolean checkList(List<String> expectedList, List<String> actualList) {
+        if (expectedList == null || expectedList.isEmpty()) {
+            return true;
+        }
+        if (actualList == null || actualList.isEmpty()) {
+            return false;
+        }
+        for (String expected : expectedList) {
+            if (!isBlank(expected) && actualList.contains(expected.trim())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean checkSerial(String expectedSerial, String actualSerial) {
+        if (isBlank(expectedSerial)) {
+            return true;
+        }
+        return !isBlank(actualSerial) && expectedSerial.equals(actualSerial);
+    }
+
+    private static List<String> normalizeList(List<String> values, boolean upperCase) {
+        List<String> result = new ArrayList<>();
+        if (values == null || values.isEmpty()) {
+            return result;
+        }
+        for (String value : values) {
+            if (isBlank(value)) {
+                continue;
+            }
+            String normalized = value.trim();
+            normalized = upperCase ? normalized.toUpperCase() : normalized.toLowerCase();
+            if (!result.contains(normalized)) {
+                result.add(normalized);
+            }
+        }
+        Collections.sort(result);
+        return result;
+    }
+
+    private static boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+
+    private static String trimToEmpty(String value) {
+        return value == null ? "" : value.trim();
+    }
+}
diff --git a/src/main/java/com/zy/system/entity/license/LicenseCheckListener.java b/src/main/java/com/zy/system/entity/license/LicenseCheckListener.java
index 7721c02..c45b77e 100644
--- a/src/main/java/com/zy/system/entity/license/LicenseCheckListener.java
+++ b/src/main/java/com/zy/system/entity/license/LicenseCheckListener.java
@@ -16,6 +16,9 @@
 import org.springframework.stereotype.Component;
 
 import java.io.File;
+import java.nio.file.Files;
+import java.text.SimpleDateFormat;
+import java.util.Base64;
 import java.util.Date;
 
 /**
@@ -72,12 +75,18 @@
 
     //鍔犺浇璇佷功
     public boolean loadLicense() {
+        return loadLicense(true);
+    }
+
+    public boolean loadLicense(boolean fetchRemote) {
         if(!Cools.isEmpty(licensePath)){
             logger.info("++++++++ 寮�濮嬪姞杞借鍙瘉 ++++++++");
 
-            try {
-                licenseTimer.getRemoteLicense();
-            } catch (Exception e) {
+            if (fetchRemote) {
+                try {
+                    licenseTimer.getRemoteLicense();
+                } catch (Exception e) {
+                }
             }
 
             try {
@@ -89,15 +98,23 @@
                 param.setPublicKeysStorePath(publicKeysStorePath);
 
                 LicenseVerify licenseVerify = new LicenseVerify();
+                String requestCode = LicenseUtils.buildRequestCode(subject);
+                LicenseInfos latestLicense = licenseInfosService.getLatestLicenseByRequestCode(requestCode);
 
-                LicenseInfos latestLicense = licenseInfosService.getLatestLicense();
-                if (latestLicense == null) {
+                LicenseContent install = null;
+                if (latestLicense != null) {
+                    install = licenseVerify.install(param, latestLicense.getLicense());
+                }
+                if (install == null) {
+                    install = licenseVerify.install(param);
+                    if (install != null) {
+                        cacheLocalLicense(requestCode, install);
+                    }
+                }
+                if (install == null) {
                     logger.info("璁稿彲璇佷笉瀛樺湪");
                     return false;
                 }
-
-                //瀹夎璇佷功
-                LicenseContent install = licenseVerify.install(param, latestLicense.getLicense());
 
                 logger.info("++++++++ 璁稿彲璇佸姞杞界粨鏉� ++++++++");
 
@@ -127,4 +144,38 @@
         licenseTimer.setSystemSupport(false);
         return false;
     }
-}
\ No newline at end of file
+
+    private void cacheLocalLicense(String requestCode, LicenseContent install) {
+        try {
+            File licenseFile = resolveLicenseFile();
+            if (licenseFile == null || !licenseFile.exists()) {
+                return;
+            }
+            LicenseInfos licenseInfos = new LicenseInfos();
+            licenseInfos.setLicense(Base64.getEncoder().encodeToString(Files.readAllBytes(licenseFile.toPath())));
+            licenseInfos.setLicenseTime(formatLicenseTime(install));
+            licenseInfos.setRequestCode(requestCode);
+            licenseInfos.setCreateTime(new Date());
+            licenseInfosService.insert(licenseInfos);
+        } catch (Exception ignored) {
+        }
+    }
+
+    private File resolveLicenseFile() {
+        try {
+            if (this.getClass().getClassLoader().getResource(licensePath) != null) {
+                return new File(this.getClass().getClassLoader().getResource(licensePath).toURI());
+            }
+        } catch (Exception ignored) {
+        }
+        return new File(licensePath);
+    }
+
+    private String formatLicenseTime(LicenseContent install) {
+        if (install == null) {
+            return "";
+        }
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        return format.format(install.getNotBefore()) + "  -  " + format.format(install.getNotAfter());
+    }
+}
diff --git a/src/main/java/com/zy/system/entity/license/LicenseNodeCheck.java b/src/main/java/com/zy/system/entity/license/LicenseNodeCheck.java
new file mode 100644
index 0000000..a0f6857
--- /dev/null
+++ b/src/main/java/com/zy/system/entity/license/LicenseNodeCheck.java
@@ -0,0 +1,22 @@
+package com.zy.system.entity.license;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+public class LicenseNodeCheck implements Serializable {
+
+    private static final long serialVersionUID = 3629488116939928951L;
+
+    private String nodeId;
+
+    private List<String> ipAddress;
+
+    private List<String> macAddress;
+
+    private String cpuSerial;
+
+    private String mainBoardSerial;
+}
diff --git a/src/main/java/com/zy/system/entity/license/LicenseRequestCode.java b/src/main/java/com/zy/system/entity/license/LicenseRequestCode.java
new file mode 100644
index 0000000..897bb37
--- /dev/null
+++ b/src/main/java/com/zy/system/entity/license/LicenseRequestCode.java
@@ -0,0 +1,19 @@
+package com.zy.system.entity.license;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class LicenseRequestCode implements Serializable {
+
+    private static final long serialVersionUID = -6632394365188004836L;
+
+    private Integer version = 2;
+
+    private String subject;
+
+    private String requestTime;
+
+    private LicenseBindModel licenseBind;
+}
diff --git a/src/main/java/com/zy/system/entity/license/LicenseUploadParam.java b/src/main/java/com/zy/system/entity/license/LicenseUploadParam.java
new file mode 100644
index 0000000..ffd9963
--- /dev/null
+++ b/src/main/java/com/zy/system/entity/license/LicenseUploadParam.java
@@ -0,0 +1,9 @@
+package com.zy.system.entity.license;
+
+import lombok.Data;
+
+@Data
+public class LicenseUploadParam {
+
+    private String license;
+}
diff --git a/src/main/java/com/zy/system/entity/license/LicenseUtils.java b/src/main/java/com/zy/system/entity/license/LicenseUtils.java
index d0eec17..35cb92e 100644
--- a/src/main/java/com/zy/system/entity/license/LicenseUtils.java
+++ b/src/main/java/com/zy/system/entity/license/LicenseUtils.java
@@ -1,5 +1,15 @@
 package com.zy.system.entity.license;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.List;
+
 public class LicenseUtils {
 
     /**
@@ -23,4 +33,98 @@
         return abstractServerInfos.getServerInfos();
     }
 
+    /**
+     * 鐢熸垚璇锋眰鐮侊紝璇锋眰鐮佷腑鍖呭惈璁稿彲璇佸悕绉板拰褰撳墠鏈嶅姟鍣ㄧ殑纭欢淇℃伅銆�
+     */
+    public static String buildRequestCode(String subject) {
+        return buildRequestCode(subject, getServerInfos());
+    }
+
+    public static String buildRequestCode(String subject, LicenseCheck licenseCheck) {
+        if (isBlank(subject)) {
+            throw new IllegalArgumentException("璁稿彲璇佸悕绉颁笉鑳戒负绌�");
+        }
+
+        LicenseCheck normalized = normalizeLicenseCheck(licenseCheck == null ? new LicenseCheck() : licenseCheck);
+        JSONObject payload = new JSONObject(true);
+        payload.put("version", 2);
+        payload.put("subject", subject);
+        payload.put("licenseBind", buildBindModel(normalized));
+        return Base64.getEncoder().encodeToString(JSON.toJSONString(payload).getBytes(StandardCharsets.UTF_8));
+    }
+
+    private static JSONObject buildBindModel(LicenseCheck licenseCheck) {
+        JSONObject bindModel = new JSONObject(true);
+        bindModel.put("version", 2);
+        bindModel.put("bindMode", "MULTI_NODE");
+        bindModel.put("matchMode", "ANY");
+
+        JSONObject node = new JSONObject(true);
+        node.put("nodeId", getNodeId());
+        node.put("ipAddress", toJsonArray(licenseCheck.getIpAddress()));
+        node.put("macAddress", toJsonArray(licenseCheck.getMacAddress()));
+        node.put("cpuSerial", trimToEmpty(licenseCheck.getCpuSerial()));
+        node.put("mainBoardSerial", trimToEmpty(licenseCheck.getMainBoardSerial()));
+
+        JSONArray nodes = new JSONArray();
+        nodes.add(node);
+        bindModel.put("nodes", nodes);
+        return bindModel;
+    }
+
+    private static LicenseCheck normalizeLicenseCheck(LicenseCheck source) {
+        LicenseCheck target = new LicenseCheck();
+        target.setIpAddress(normalizeList(source.getIpAddress(), false));
+        target.setMacAddress(normalizeList(source.getMacAddress(), true));
+        target.setCpuSerial(trimToEmpty(source.getCpuSerial()));
+        target.setMainBoardSerial(trimToEmpty(source.getMainBoardSerial()));
+        return target;
+    }
+
+    private static List<String> normalizeList(List<String> values, boolean upperCase) {
+        List<String> result = new ArrayList<>();
+        if (values == null || values.isEmpty()) {
+            return result;
+        }
+        for (String value : values) {
+            if (isBlank(value)) {
+                continue;
+            }
+            String normalized = value.trim();
+            normalized = upperCase ? normalized.toUpperCase() : normalized.toLowerCase();
+            if (!result.contains(normalized)) {
+                result.add(normalized);
+            }
+        }
+        Collections.sort(result);
+        return result;
+    }
+
+    private static JSONArray toJsonArray(List<String> values) {
+        JSONArray array = new JSONArray();
+        if (values != null && !values.isEmpty()) {
+            array.addAll(values);
+        }
+        return array;
+    }
+
+    private static String getNodeId() {
+        try {
+            String hostName = InetAddress.getLocalHost().getHostName();
+            if (!isBlank(hostName)) {
+                return hostName.trim();
+            }
+        } catch (Exception ignored) {
+        }
+        return "node-1";
+    }
+
+    private static boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+
+    private static String trimToEmpty(String value) {
+        return value == null ? "" : value.trim();
+    }
+
 }
diff --git a/src/main/java/com/zy/system/entity/license/LicenseVerify.java b/src/main/java/com/zy/system/entity/license/LicenseVerify.java
index da239c0..22e287b 100644
--- a/src/main/java/com/zy/system/entity/license/LicenseVerify.java
+++ b/src/main/java/com/zy/system/entity/license/LicenseVerify.java
@@ -6,6 +6,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.text.DateFormat;
@@ -35,6 +36,28 @@
 
             File tempFileFromBase64 = createTempFileFromBase64(license);
             result = licenseManager.install(tempFileFromBase64);
+            logger.info(MessageFormat.format("璁稿彲璇佸姞杞芥垚鍔燂紝璁稿彲璇佹湁鏁堟湡锛歿0} - {1}", format.format(result.getNotBefore()), format.format(result.getNotAfter())));
+        } catch (Exception e) {
+            logger.error("璁稿彲璇佸姞杞藉け璐ワ紒", e);
+        }
+
+        return result;
+    }
+
+    public synchronized LicenseContent install(LicenseVerifyParam param) {
+        LicenseContent result = null;
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+        try {
+            LicenseParam licenseParam = initLicenseParam(param);
+            LicenseManager licenseManager = LicenseManagerHolder.getInstance(licenseParam);
+            licenseManager.uninstall();
+
+            File licenseFile = resolveLicenseFile(param.getLicensePath());
+            if (licenseFile == null || !licenseFile.exists()) {
+                return null;
+            }
+            result = licenseManager.install(licenseFile);
             logger.info(MessageFormat.format("璁稿彲璇佸姞杞芥垚鍔燂紝璁稿彲璇佹湁鏁堟湡锛歿0} - {1}", format.format(result.getNotBefore()), format.format(result.getNotAfter())));
         } catch (Exception e) {
             logger.error("璁稿彲璇佸姞杞藉け璐ワ紒", e);
@@ -123,4 +146,15 @@
         return base64ToTempFile(base64Data, "temp_license_", ".bin");
     }
 
-}
\ No newline at end of file
+    private File resolveLicenseFile(String licensePath) {
+        try {
+            URL url = this.getClass().getClassLoader().getResource(licensePath);
+            if (url != null) {
+                return new File(url.toURI());
+            }
+        } catch (Exception ignored) {
+        }
+        return new File(licensePath);
+    }
+
+}
diff --git a/src/main/java/com/zy/system/mapper/LicenseInfosMapper.java b/src/main/java/com/zy/system/mapper/LicenseInfosMapper.java
index 2e51ff1..25be922 100644
--- a/src/main/java/com/zy/system/mapper/LicenseInfosMapper.java
+++ b/src/main/java/com/zy/system/mapper/LicenseInfosMapper.java
@@ -2,6 +2,7 @@
 
 import com.zy.system.entity.LicenseInfos;
 import com.baomidou.mybatisplus.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Mapper;
 import org.springframework.stereotype.Repository;
 
@@ -11,4 +12,6 @@
 
     LicenseInfos getLatestLicense();
 
+    LicenseInfos getLatestLicenseByRequestCode(@Param("requestCode") String requestCode);
+
 }
diff --git a/src/main/java/com/zy/system/service/LicenseInfosService.java b/src/main/java/com/zy/system/service/LicenseInfosService.java
index dc3dc04..b8a21a9 100644
--- a/src/main/java/com/zy/system/service/LicenseInfosService.java
+++ b/src/main/java/com/zy/system/service/LicenseInfosService.java
@@ -7,4 +7,6 @@
 
     LicenseInfos getLatestLicense();
 
+    LicenseInfos getLatestLicenseByRequestCode(String requestCode);
+
 }
diff --git a/src/main/java/com/zy/system/service/impl/LicenseInfosServiceImpl.java b/src/main/java/com/zy/system/service/impl/LicenseInfosServiceImpl.java
index 2a6b9b3..e738c0c 100644
--- a/src/main/java/com/zy/system/service/impl/LicenseInfosServiceImpl.java
+++ b/src/main/java/com/zy/system/service/impl/LicenseInfosServiceImpl.java
@@ -13,4 +13,9 @@
     public LicenseInfos getLatestLicense() {
         return this.baseMapper.getLatestLicense();
     }
+
+    @Override
+    public LicenseInfos getLatestLicenseByRequestCode(String requestCode) {
+        return this.baseMapper.getLatestLicenseByRequestCode(requestCode);
+    }
 }
diff --git a/src/main/java/com/zy/system/timer/LicenseTimer.java b/src/main/java/com/zy/system/timer/LicenseTimer.java
index 758d92d..235a53c 100644
--- a/src/main/java/com/zy/system/timer/LicenseTimer.java
+++ b/src/main/java/com/zy/system/timer/LicenseTimer.java
@@ -52,6 +52,12 @@
     @Value("${license.publicKeysStorePath}")
     private String publicKeysStorePath;
 
+    /**
+     * 璁稿彲璇佹湇鍔$鍦板潃銆�
+     */
+    @Value("${license.remoteServerUrl:http://net.zoneyung.net:9999/license}")
+    private String remoteServerUrl;
+
     @Autowired
     private LicenseInfosService licenseInfosService;
 
@@ -73,24 +79,18 @@
 
     public void getRemoteLicense() {
         try {
+            String requestCode = LicenseUtils.buildRequestCode(subject);
             LicenseCheck serverInfos = LicenseUtils.getServerInfos();
-
-            HashMap<String, Object> map = new HashMap<>();
-            map.put("subject", subject);
-            map.put("licenseCheck", serverInfos);
-
-            String response = new HttpHandler.Builder()
-                    .setUri("http://net.zoneyung.net:9999/license")
-                    .setPath("/remoteQueryLicense")
-                    .setJson(JSON.toJSONString(map))
-                    .build()
-                    .doPost();
-            JSONObject jsonObject = JSON.parseObject(response);
-            if (jsonObject.getString("result").equals("ok")) {
+            JSONObject response = requestRemoteLicense(buildRequestCodePayload(requestCode));
+            if (!isSuccess(response)) {
+                response = requestRemoteLicense(buildLegacyPayload(serverInfos));
+            }
+            if (isSuccess(response)) {
                 LicenseInfos licenseInfos = new LicenseInfos();
-                licenseInfos.setLicense(jsonObject.getString("data"));
+                licenseInfos.setLicense(response.getString("data"));
                 licenseInfos.setCreateTime(new Date());
-                licenseInfos.setLicenseTime(jsonObject.getString("licenseTime"));
+                licenseInfos.setLicenseTime(response.getString("licenseTime"));
+                licenseInfos.setRequestCode(requestCode);
                 licenseInfosService.insert(licenseInfos);
             }
         } catch (Exception e) {
@@ -98,14 +98,42 @@
         }
     }
 
-    public void verify() {
-        LicenseInfos latestLicense = licenseInfosService.getLatestLicense();
-        if (latestLicense == null) {
-            setLicenseDays(0);
-            setSystemSupport(false);
-            return;
+    private JSONObject requestRemoteLicense(String json) {
+        try {
+            String response = new HttpHandler.Builder()
+                    .setUri(remoteServerUrl)
+                    .setPath("/remoteQueryLicense")
+                    .setJson(json)
+                    .build()
+                    .doPost();
+            if (response == null || response.trim().isEmpty()) {
+                return null;
+            }
+            return JSON.parseObject(response);
+        } catch (Exception e) {
+            return null;
         }
+    }
 
+    private String buildRequestCodePayload(String requestCode) {
+        HashMap<String, Object> map = new HashMap<>();
+        map.put("subject", subject);
+        map.put("requestCode", requestCode);
+        return JSON.toJSONString(map);
+    }
+
+    private String buildLegacyPayload(LicenseCheck serverInfos) {
+        HashMap<String, Object> map = new HashMap<>();
+        map.put("subject", subject);
+        map.put("licenseCheck", serverInfos);
+        return JSON.toJSONString(map);
+    }
+
+    private boolean isSuccess(JSONObject jsonObject) {
+        return jsonObject != null && "ok".equalsIgnoreCase(jsonObject.getString("result"));
+    }
+
+    public void verify() {
         LicenseVerifyParam param = new LicenseVerifyParam();
         param.setSubject(subject);
         param.setPublicAlias(publicAlias);
@@ -113,10 +141,18 @@
         param.setLicensePath(licensePath);
         param.setPublicKeysStorePath(publicKeysStorePath);
 
+        String requestCode = LicenseUtils.buildRequestCode(subject);
+        LicenseInfos latestLicense = licenseInfosService.getLatestLicenseByRequestCode(requestCode);
+
         // 楠岃瘉璁稿彲璇佹槸鍚︽湁鏁�
         LicenseVerify licenseVerify = new LicenseVerify();
-        // 瀹夎璇佷功
-        LicenseContent install = licenseVerify.install(param, latestLicense.getLicense());
+        LicenseContent install = null;
+        if (latestLicense != null) {
+            install = licenseVerify.install(param, latestLicense.getLicense());
+        }
+        if (install == null) {
+            install = licenseVerify.install(param);
+        }
 
         if (install != null) {
             Date start = new Date();
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 87e4b79..a521e25 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,6 +1,6 @@
 # 绯荤粺鐗堟湰淇℃伅
 app:
-  version: 1.0.5.1
+  version: 1.0.5.2
   version-type: dev  # prd 鎴� dev
 
 server:
@@ -66,6 +66,7 @@
   storePass: public_zhongyang_123456789
   licensePath: license.lic
   publicKeysStorePath: publicCerts.keystore
+  remoteServerUrl: http://net.zoneyung.net:9999/license
 
 deviceExecuteConfig:
   # 姣忎釜绾跨▼绠℃帶璁惧鎵ц鏁伴噺
diff --git a/src/main/resources/mapper/LicenseInfosMapper.xml b/src/main/resources/mapper/LicenseInfosMapper.xml
index 3d78813..d167984 100644
--- a/src/main/resources/mapper/LicenseInfosMapper.xml
+++ b/src/main/resources/mapper/LicenseInfosMapper.xml
@@ -7,6 +7,7 @@
         <id column="id" property="id" />
         <result column="license" property="license" />
         <result column="license_time" property="licenseTime" />
+        <result column="request_code" property="requestCode" />
         <result column="create_time" property="createTime" />
 
     </resultMap>
@@ -15,4 +16,8 @@
         select * from sys_license_infos order by create_time desc limit 0,1
     </select>
 
+    <select id="getLatestLicenseByRequestCode" resultMap="BaseResultMap">
+        select * from sys_license_infos where request_code = #{requestCode} order by create_time desc limit 0,1
+    </select>
+
 </mapper>
diff --git a/src/main/resources/sql/20260307_add_request_code_to_sys_license_infos.sql b/src/main/resources/sql/20260307_add_request_code_to_sys_license_infos.sql
new file mode 100644
index 0000000..3e547f1
--- /dev/null
+++ b/src/main/resources/sql/20260307_add_request_code_to_sys_license_infos.sql
@@ -0,0 +1,42 @@
+-- sys_license_infos 澧炲姞 request_code 瀛楁
+-- 鐢ㄩ�旓細鎸夊綋鍓嶆満鍣ㄨ姹傜爜绛涢�夊苟婵�娲诲搴旇鍙瘉
+-- 閫傜敤鏁版嵁搴擄細MySQL
+
+SET @current_db := DATABASE();
+
+SET @column_exists := (
+  SELECT COUNT(1)
+  FROM information_schema.COLUMNS
+  WHERE TABLE_SCHEMA = @current_db
+    AND TABLE_NAME = 'sys_license_infos'
+    AND COLUMN_NAME = 'request_code'
+);
+
+SET @add_column_sql := IF(
+  @column_exists = 0,
+  'ALTER TABLE sys_license_infos ADD COLUMN request_code VARCHAR(2048) NULL COMMENT ''璁稿彲璇佽姹傜爜'' AFTER license_time',
+  'SELECT ''column request_code already exists'' '
+);
+PREPARE stmt_add_column FROM @add_column_sql;
+EXECUTE stmt_add_column;
+DEALLOCATE PREPARE stmt_add_column;
+
+SET @index_exists := (
+  SELECT COUNT(1)
+  FROM information_schema.STATISTICS
+  WHERE TABLE_SCHEMA = @current_db
+    AND TABLE_NAME = 'sys_license_infos'
+    AND INDEX_NAME = 'idx_sys_license_infos_request_code_create_time'
+);
+
+SET @add_index_sql := IF(
+  @index_exists = 0,
+  'ALTER TABLE sys_license_infos ADD INDEX idx_sys_license_infos_request_code_create_time (request_code(191), create_time)',
+  'SELECT ''index idx_sys_license_infos_request_code_create_time already exists'' '
+);
+PREPARE stmt_add_index FROM @add_index_sql;
+EXECUTE stmt_add_index;
+DEALLOCATE PREPARE stmt_add_index;
+
+SHOW COLUMNS FROM sys_license_infos LIKE 'request_code';
+SHOW INDEX FROM sys_license_infos WHERE Key_name = 'idx_sys_license_infos_request_code_create_time';
diff --git a/src/main/webapp/static/js/config/config.js b/src/main/webapp/static/js/config/config.js
index 7478474..2be0919 100644
--- a/src/main/webapp/static/js/config/config.js
+++ b/src/main/webapp/static/js/config/config.js
@@ -187,117 +187,6 @@
                     });
                 });
                 break;
-            case 'serverInfo':
-                $.ajax({
-                    url: baseUrl + "/license/getServerInfos",
-                    headers: { 'token': localStorage.getItem('token') },
-                    method: 'GET',
-                    success: function (res) {
-                        var pretty = '';
-                        try {
-                            pretty = JSON.stringify(res, null, 2);
-                        } catch (e) {
-                            pretty = res;
-                        }
-                        var html = ''
-                            + '<div style="padding:15px 20px 5px 20px;">'
-                            + '<div style="font-weight:600;margin-bottom:8px;">绯荤粺閰嶇疆淇℃伅</div>'
-                            + '<pre id="server-info-pre" style="background:#f7f7f7;border:1px solid #e6e6e6;border-radius:6px;padding:12px;white-space:pre-wrap;word-wrap:break-word;max-height:360px;overflow:auto;">'
-                            + pretty
-                            + '</pre>'
-                            + '<div class="layui-btn-container" style="text-align:right;margin-top:6px;">'
-                            + '<button class="layui-btn layui-btn-primary" id="copy-server-info">澶嶅埗</button>'
-                            + '</div>'
-                            + '</div>';
-                        layer.open({
-                            type: 1,
-                            title: '鑾峰彇绯荤粺閰嶇疆',
-                            area: ['640px', '480px'],
-                            shadeClose: true,
-                            content: html,
-                            success: function (layero, index) {
-                                layero.find('#copy-server-info').on('click', function () {
-                                    var text = layero.find('#server-info-pre').text();
-                                    if (navigator.clipboard && navigator.clipboard.writeText) {
-                                        navigator.clipboard.writeText(text).then(function () {
-                                            layer.msg('宸插鍒跺埌鍓创鏉�');
-                                        }).catch(function () {
-                                            try {
-                                                var textarea = document.createElement('textarea');
-                                                textarea.value = text;
-                                                textarea.style.position = 'fixed';
-                                                textarea.style.opacity = '0';
-                                                document.body.appendChild(textarea);
-                                                textarea.select();
-                                                document.execCommand('copy');
-                                                document.body.removeChild(textarea);
-                                                layer.msg('宸插鍒跺埌鍓创鏉�');
-                                            } catch (err) {
-                                                layer.msg('澶嶅埗澶辫触');
-                                            }
-                                        });
-                                    } else {
-                                        try {
-                                            var textarea = document.createElement('textarea');
-                                            textarea.value = text;
-                                            textarea.style.position = 'fixed';
-                                            textarea.style.opacity = '0';
-                                            document.body.appendChild(textarea);
-                                            textarea.select();
-                                            document.execCommand('copy');
-                                            document.body.removeChild(textarea);
-                                            layer.msg('宸插鍒跺埌鍓创鏉�');
-                                        } catch (err) {
-                                            layer.msg('澶嶅埗澶辫触');
-                                        }
-                                    }
-                                });
-                            }
-                        });
-                    },
-                    error: function () {
-                        layer.msg('鑾峰彇绯荤粺閰嶇疆淇℃伅澶辫触');
-                    }
-                });
-                break;
-            case 'activate':
-                layer.confirm('纭畾鎵ц涓�閿縺娲诲悧', function () {
-                    $.ajax({
-                        url: baseUrl + "/license/activate",
-                        headers: { 'token': localStorage.getItem('token') },
-                        method: 'POST',
-                        success: function (res) {
-                            if (res.code === 200) {
-                                layer.msg('婵�娲绘垚鍔�');
-                            } else if (res.code === 403) {
-                                top.location.href = baseUrl + "/";
-                            } else {
-                                layer.msg(res.msg)
-                            }
-                        },
-                        error: function () {
-                            layer.msg('婵�娲诲け璐�');
-                        }
-                    });
-                });
-                break;
-            case 'projectName':
-                $.ajax({
-                    url: baseUrl + "/license/getProjectName",
-                    headers: { 'token': localStorage.getItem('token') },
-                    method: 'GET',
-                    success: function (res) {
-                        if (res.code === 200) {
-                            layer.alert(res.msg);
-                        } else {
-                            layer.msg(res.msg)
-                        }
-                    },
-                    error: function () {
-                        layer.msg('鑾峰彇椤圭洰鍚嶇О澶辫触');
-                    }
-                });
-                break;
         }
     });
 
diff --git a/src/main/webapp/views/config/config.html b/src/main/webapp/views/config/config.html
index a0d92fb..3b94ea2 100644
--- a/src/main/webapp/views/config/config.html
+++ b/src/main/webapp/views/config/config.html
@@ -46,9 +46,6 @@
         <button class="layui-btn layui-btn-sm" id="btn-delete" lay-event="deleteData">鍒犻櫎</button>
         <button class="layui-btn layui-btn-primary layui-btn-sm" id="btn-export" lay-event="exportData">瀵煎嚭</button>
         <button class="layui-btn layui-btn-warm layui-btn-sm" id="btn-refresh-cache" lay-event="refreshCache">鍒锋柊缂撳瓨</button>
-        <button class="layui-btn layui-btn-normal layui-btn-sm" id="btn-project-name" lay-event="projectName">鑾峰彇椤圭洰鍚嶇О</button>
-        <button class="layui-btn layui-btn-normal layui-btn-sm" id="btn-server-info" lay-event="serverInfo">鑾峰彇绯荤粺閰嶇疆</button>
-        <button class="layui-btn layui-btn-normal layui-btn-sm" id="btn-activate" lay-event="activate">涓�閿縺娲�</button>
     </div>
 </script>
 
diff --git a/src/main/webapp/views/index.html b/src/main/webapp/views/index.html
index b9bf30f..37d2110 100644
--- a/src/main/webapp/views/index.html
+++ b/src/main/webapp/views/index.html
@@ -228,6 +228,44 @@
       color: rgba(255, 255, 255, 0.58);
     }
 
+    .aside-loading {
+      padding: 4px 12px 12px;
+      box-sizing: border-box;
+    }
+
+    .aside-skeleton-item {
+      position: relative;
+      height: 46px;
+      margin: 0 10px 6px;
+      overflow: hidden;
+      border-radius: 10px;
+      background: rgba(255, 255, 255, 0.08);
+    }
+
+    .aside-skeleton-item::after {
+      content: "";
+      position: absolute;
+      inset: 0;
+      transform: translateX(-100%);
+      background: linear-gradient(90deg,
+      rgba(255, 255, 255, 0) 0%,
+      rgba(255, 255, 255, 0.16) 48%,
+      rgba(255, 255, 255, 0) 100%);
+      animation: asideSkeletonShimmer 1.25s ease-in-out infinite;
+    }
+
+    .aside-skeleton-item:nth-child(3n) {
+      width: calc(100% - 28px);
+    }
+
+    .aside-skeleton-item:nth-child(4n + 2) {
+      width: calc(100% - 44px);
+    }
+
+    .aside-skeleton-item:nth-child(5n) {
+      width: calc(100% - 36px);
+    }
+
     .aside-footer {
       padding: 14px 16px 16px;
       border-top: 1px solid rgba(255, 255, 255, 0.08);
@@ -450,6 +488,12 @@
       }
     }
 
+    @keyframes asideSkeletonShimmer {
+      100% {
+        transform: translateX(100%);
+      }
+    }
+
     .ai-drawer-layer {
       box-shadow: -8px 0 24px rgba(0, 0, 0, 0.15) !important;
       border-radius: 8px 0 0 8px !important;
@@ -500,42 +544,52 @@
         </el-input>
       </div>
 
-      <el-scrollbar class="aside-scroll" v-loading="menuLoading">
-        <el-menu
-            ref="sideMenu"
-            class="side-menu"
-            :default-active="activeMenuKey"
-            :collapse="isCollapse"
-            :collapse-transition="false"
-            :default-openeds="defaultOpeneds"
-            unique-opened
-            background-color="transparent"
-            text-color="#c6d1df"
-            active-text-color="#ffffff">
-          <el-submenu
-              v-for="group in filteredMenus"
-              :key="'group-' + group.menuId"
-              :index="'group-' + group.menuId">
-            <template slot="title">
-              <i :class="resolveMenuIcon(group.menuCode)"></i>
-              <span>{{ group.menu }}</span>
-            </template>
-            <el-menu-item
-                v-for="item in group.subMenu"
-                :key="item.tabKey"
-                :index="item.tabKey"
-                @click="handleMenuSelect(group, item)">
-              {{ item.name }}
-            </el-menu-item>
-          </el-submenu>
-        </el-menu>
-
-        <div class="aside-empty" v-if="!menuLoading && filteredMenus.length === 0">
-          <el-empty
-              :image-size="80"
-              :description="menuKeyword ? '娌℃湁鍖归厤鑿滃崟' : '褰撳墠璐﹀彿娌℃湁鍙敤鑿滃崟'">
-          </el-empty>
+      <el-scrollbar class="aside-scroll">
+        <div v-if="menuLoading" class="aside-loading" aria-hidden="true">
+          <div
+              v-for="n in 8"
+              :key="'menu-skeleton-' + n"
+              class="aside-skeleton-item">
+          </div>
         </div>
+
+        <template v-else>
+          <el-menu
+              ref="sideMenu"
+              class="side-menu"
+              :default-active="activeMenuKey"
+              :collapse="isCollapse"
+              :collapse-transition="false"
+              :default-openeds="defaultOpeneds"
+              unique-opened
+              background-color="transparent"
+              text-color="#c6d1df"
+              active-text-color="#ffffff">
+            <el-submenu
+                v-for="group in filteredMenus"
+                :key="'group-' + group.menuId"
+                :index="'group-' + group.menuId">
+              <template slot="title">
+                <i :class="resolveMenuIcon(group.menuCode)"></i>
+                <span>{{ group.menu }}</span>
+              </template>
+              <el-menu-item
+                  v-for="item in group.subMenu"
+                  :key="item.tabKey"
+                  :index="item.tabKey"
+                  @click="handleMenuSelect(group, item)">
+                {{ item.name }}
+              </el-menu-item>
+            </el-submenu>
+          </el-menu>
+
+          <div class="aside-empty" v-if="filteredMenus.length === 0">
+            <el-empty
+                :image-size="80"
+                :description="menuKeyword ? '娌℃湁鍖归厤鑿滃崟' : '褰撳墠璐﹀彿娌℃湁鍙敤鑿滃崟'">
+            </el-empty>
+          </div>
+        </template>
       </el-scrollbar>
 
       <div class="aside-footer" v-show="!isCollapse">
@@ -606,7 +660,6 @@
             class="page-tabs"
             v-model="activeTab"
             type="card"
-            @tab-click="handleTabClick"
             @tab-remove="removeTab">
           <el-tab-pane
               v-for="tab in tabs"
@@ -695,7 +748,7 @@
     data: function () {
       return {
         isCollapse: false,
-        menuLoading: false,
+        menuLoading: true,
         pageLoading: true,
         loadingText: "姝e湪鍔犺浇椤甸潰...",
         menuKeyword: "",
@@ -712,6 +765,8 @@
         fakeVisible: false,
         fakeRunning: false,
         fakeStatusInterval: null,
+        menuSyncVersion: 0,
+        menuSyncTimer: null,
         userName: localStorage.getItem(USER_STORAGE_KEY) || "绠$悊鍛�",
         aiLayerIndex: null,
         aiTipIndex: null
@@ -812,7 +867,7 @@
     watch: {
       activeTab: function () {
         var tab = this.getTabByName(this.activeTab);
-        this.activeMenuKey = tab ? (tab.menuKey || this.findMenuKeyByUrl(tab.url)) : "";
+        this.syncMenuStateByUrl(tab ? tab.url : HOME_TAB_CONFIG.url);
         this.pageLoading = !!(tab && !tab.loaded);
         if (this.pageLoading) {
           this.loadingText = "姝e湪鍔犺浇 鈥�" + tab.title + "鈥� ...";
@@ -847,6 +902,10 @@
       if (this.userSyncTimer) {
         clearInterval(this.userSyncTimer);
         this.userSyncTimer = null;
+      }
+      if (this.menuSyncTimer) {
+        clearTimeout(this.menuSyncTimer);
+        this.menuSyncTimer = null;
       }
       if (this.aiTipIndex) {
         layer.close(this.aiTipIndex);
@@ -961,6 +1020,9 @@
       hasTab: function (name) {
         return !!this.getTabByName(name);
       },
+      isHomeTabUrl: function (url) {
+        return this.resolveViewSrc(url || HOME_TAB_CONFIG.url) === this.resolveViewSrc(HOME_TAB_CONFIG.url);
+      },
       getTabByName: function (name) {
         var i;
         for (i = 0; i < this.tabs.length; i++) {
@@ -995,7 +1057,7 @@
         this.loadingText = "姝e湪鍔犺浇 鈥�" + tab.title + "鈥� ...";
         this.pageLoading = !tab.loaded;
         this.activeTab = tab.name;
-        this.activeMenuKey = tab.menuKey || this.findMenuKeyByUrl(tab.url);
+        this.syncMenuStateByUrl(tab.url);
       },
       openHomeTab: function () {
         this.addOrActivateTab(HOME_TAB_CONFIG);
@@ -1006,7 +1068,7 @@
       closeAllTabs: function () {
         this.tabs = [this.createTab(HOME_TAB_CONFIG)];
         this.activeTab = HOME_TAB_CONFIG.url;
-        this.activeMenuKey = "";
+        this.syncMenuStateByUrl(HOME_TAB_CONFIG.url);
         this.pageLoading = true;
         this.loadingText = "姝e湪鍔犺浇 鈥滄帶鍒朵腑蹇冣�� ...";
         this.persistTabs();
@@ -1048,9 +1110,6 @@
 
         this.activeTab = nextTabName;
         this.persistTabs();
-      },
-      handleTabClick: function () {
-        this.activeMenuKey = this.findMenuKeyByUrl(this.activeTabUrl);
       },
       handleFrameLoad: function (name) {
         var tab = this.getTabByName(name);
@@ -1099,6 +1158,24 @@
             item = group.subMenu[j];
             if (item.url === normalized) {
               return item.tabKey;
+            }
+          }
+        }
+        return "";
+      },
+      findMenuGroupIndexByUrl: function (url) {
+        var normalized = this.resolveViewSrc(url);
+        var i;
+        var j;
+        var group;
+        var item;
+
+        for (i = 0; i < this.menus.length; i++) {
+          group = this.menus[i];
+          for (j = 0; j < group.subMenu.length; j++) {
+            item = group.subMenu[j];
+            if (item.url === normalized) {
+              return "group-" + group.menuId;
             }
           }
         }
@@ -1188,6 +1265,62 @@
           menu.close(openedMenus[i]);
         }
       },
+      syncMenuStateByUrl: function (url) {
+        var targetUrl = this.resolveViewSrc(url || HOME_TAB_CONFIG.url);
+        var activeMenuKey = "";
+        var groupIndex = "";
+        var that = this;
+        var syncVersion;
+        var applyMenuState;
+
+        if (!this.isHomeTabUrl(targetUrl)) {
+          activeMenuKey = this.findMenuKeyByUrl(targetUrl);
+          if (activeMenuKey) {
+            groupIndex = this.findMenuGroupIndexByUrl(targetUrl);
+          }
+        }
+
+        this.activeMenuKey = activeMenuKey;
+        this.defaultOpeneds = groupIndex ? [groupIndex] : [];
+        this.menuSyncVersion += 1;
+        syncVersion = this.menuSyncVersion;
+        applyMenuState = function () {
+          var menu = that.$refs.sideMenu;
+          var openedMenus;
+          if (syncVersion !== that.menuSyncVersion) {
+            return;
+          }
+          if (!menu) {
+            return;
+          }
+          openedMenus = menu.openedMenus ? menu.openedMenus.slice() : [];
+          if (!groupIndex) {
+            if (openedMenus.length) {
+              that.collapseAllMenus();
+            }
+            return;
+          }
+          if (openedMenus.indexOf(groupIndex) > -1) {
+            return;
+          }
+          if (openedMenus.length) {
+            that.collapseAllMenus();
+          }
+          menu.open(groupIndex);
+        };
+
+        this.$nextTick(function () {
+          applyMenuState();
+          if (that.menuSyncTimer) {
+            clearTimeout(that.menuSyncTimer);
+            that.menuSyncTimer = null;
+          }
+          that.menuSyncTimer = setTimeout(function () {
+            applyMenuState();
+            that.menuSyncTimer = null;
+          }, 160);
+        });
+      },
       loadMenu: function () {
         var that = this;
         this.menuLoading = true;
@@ -1199,11 +1332,7 @@
             that.menuLoading = false;
             if (res.code === 200) {
               that.menus = that.normalizeMenuData(res.data || []);
-              that.defaultOpeneds = [];
-              that.activeMenuKey = that.findMenuKeyByUrl(that.activeTabUrl);
-              that.$nextTick(function () {
-                that.collapseAllMenus();
-              });
+              that.syncMenuStateByUrl(that.activeTabUrl);
             } else if (res.code === 403) {
               top.location.href = baseUrl + "/login";
             } else {
@@ -1448,7 +1577,7 @@
         };
         window.admin.changeTheme = window.admin.changeTheme || function () {};
         window.admin.activeNav = function (url) {
-          that.activeMenuKey = that.findMenuKeyByUrl(that.resolveViewSrc(url));
+          that.syncMenuStateByUrl(that.resolveViewSrc(url));
         };
 
         window.index = window.index || {};
diff --git a/src/main/webapp/views/login.html b/src/main/webapp/views/login.html
index d82ce9a..5094c39 100644
--- a/src/main/webapp/views/login.html
+++ b/src/main/webapp/views/login.html
@@ -88,11 +88,6 @@
 <div id="login-wrapper" class="animate__animated animate__bounceInDown">
     <header>
         <h2 id="login-title" style="cursor: pointer; user-select: none;">WCS绯荤粺V3.0</h2>
-        <div id="system-btns" style="display: none; margin-bottom: 20px;">
-            <button class="layui-btn layui-btn-normal layui-btn-sm" id="btn-project-name">鑾峰彇椤圭洰鍚嶇О</button>
-            <button class="layui-btn layui-btn-normal layui-btn-sm" id="btn-server-info">鑾峰彇绯荤粺閰嶇疆</button>
-            <button class="layui-btn layui-btn-normal layui-btn-sm" id="btn-activate">涓�閿縺娲�</button>
-        </div>
     </header>
     <div class="layui-form layadmin-user-login-body">
         <div class="layui-form-item">
@@ -103,18 +98,27 @@
             <label class="layui-icon layui-icon-password layadmin-user-login-icon"></label>
             <input id="password" class="layui-input" type="password" name="password" lay-verify="password" placeholder="瀵嗙爜">
         </div>
-<!--        <div id="code-box" class="layui-form-item" style="">-->
-<!--            <label id="code-label" class="layui-icon layui-icon-vercode layadmin-user-login-icon"></label>-->
-<!--            <input id="code" class="layui-input" type="text" name="password" lay-verify="code" placeholder="楠岃瘉鐮�">-->
-<!--            <img id="codeImg" title="鐪嬩笉娓咃紵鐐瑰嚮鎹竴寮犮��">-->
-<!--        </div>-->
-        <!--<div class="layui-form-item">-->
-            <!--<input id="rememberPwd" style="vertical-align: middle" type="checkbox" lay-filter="remPwd" lay-skin="switch" lay-text="寮�鍚瘄鍏抽棴" title="璁颁綇瀵嗙爜" checked="checked">-->
-            <!--<span style="vertical-align: middle;font-size: 15px">璁颁綇瀵嗙爜</span>-->
-        <!--</div>-->
     </div>
     <div class="layui-form-item login-submit">
         <button id="login-button" class="layui-btn layui-btn-fluid layui-btn-normal" lay-submit="" lay-filter="login">鐧� &nbsp  &nbsp 褰�</button>
+    </div>
+</div>
+<div id="system-tools-panel" style="display: none; padding: 20px;">
+    <div style="margin-bottom: 18px;">
+        <div style="margin-bottom: 10px; color: #666; font-weight: 600;">鎺ㄨ崘鎿嶄綔</div>
+        <div style="display: flex; flex-wrap: wrap; gap: 12px;">
+            <button class="layui-btn layui-btn-normal layui-btn-sm" id="btn-request-code">鑾峰彇璇锋眰鐮�</button>
+            <button class="layui-btn layui-btn-normal layui-btn-sm" id="btn-activate">涓�閿縺娲�</button>
+        </div>
+        <div style="margin-top: 8px; color: #999; font-size: 12px;">浼樺厛浣跨敤鈥滆幏鍙栬姹傜爜鈥濆拰鈥滀竴閿縺娲烩�濆畬鎴愯鍙瘉鐢宠涓庢縺娲汇��</div>
+    </div>
+    <div>
+        <div style="margin-bottom: 10px; color: #666; font-weight: 600;">鍏朵粬宸ュ叿</div>
+        <div style="display: flex; flex-wrap: wrap; gap: 12px;">
+            <button class="layui-btn layui-btn-primary layui-btn-sm" id="btn-project-name">鑾峰彇椤圭洰鍚嶇О</button>
+            <button class="layui-btn layui-btn-primary layui-btn-sm" id="btn-server-info">鑾峰彇绯荤粺閰嶇疆</button>
+            <button class="layui-btn layui-btn-primary layui-btn-sm" id="btn-upload-license">褰曞叆璁稿彲璇�</button>
+        </div>
     </div>
 </div>
 
@@ -122,40 +126,6 @@
 <script type="text/javascript" src="../static/js/jquery/jquery-3.3.1.min.js"></script>
 <script type="text/javascript" src="../static/js/tools/md5.js"></script>
 <script type="text/javascript">
-
-    // // 楠岃瘉鐮佸紑鍏�
-    // var codeSwitch = 'Y';
-    // $.ajax({
-    //     url: baseUrl+"/code/switch.action",
-    //     async: false,
-    //     success: function (res) {
-    //         if (res.data === 'N'){
-    //             codeSwitch = res.data;
-    //             $('#code-box').css("display", "none");
-    //         }
-    //     }
-    // });
-
-    // // 鍒濆鍖栭獙璇佺爜
-    // initCode();
-    // $('#codeImg').click(function () {
-    //     initCode();
-    // });
-    // function initCode() {
-    //     var random = Math.random();
-    //     $('#codeImg').attr("src", baseUrl+"/code.action?sd="+random);
-    //     setTimeout(function () {
-    //         $.ajax({
-    //             url: baseUrl+"/code.do",
-    //             data: {sd: random},
-    //             method: 'POST',
-    //             async: false,
-    //             success: function (code) {
-    //                 sessionStorage.setItem("code", code);
-    //             }
-    //         });
-    //     }, 100);
-    // }
 
     layui.use(['form','layer'],function () {
         var form = layui.form,
@@ -171,8 +141,8 @@
                 clearTimeout(titleClickTimer);
             }
             if (titleClickCount >= 3) {
-                $('#system-btns').show();
                 titleClickCount = 0;
+                openSystemToolsDialog();
             } else {
                 titleClickTimer = setTimeout(function() {
                     titleClickCount = 0;
@@ -180,56 +150,18 @@
             }
         });
 
-        // 鑾峰彇绯荤粺閰嶇疆
-        $('#btn-server-info').click(function() {
-            $.ajax({
-                url: baseUrl + "/license/getServerInfos",
-                method: 'GET',
-                success: function (res) {
-                    var pretty = '';
-                    try {
-                        pretty = JSON.stringify(res, null, 2);
-                    } catch (e) {
-                        pretty = res;
-                    }
-                    var html = ''
-                        + '<div style="padding:15px 20px 5px 20px;">'
-                        +   '<div style="font-weight:600;margin-bottom:8px;">绯荤粺閰嶇疆淇℃伅</div>'
-                        +   '<pre id="server-info-pre" style="background:#f7f7f7;border:1px solid #e6e6e6;border-radius:6px;padding:12px;white-space:pre-wrap;word-wrap:break-word;max-height:360px;overflow:auto;">'
-                        +       pretty
-                        +   '</pre>'
-                        +   '<div class="layui-btn-container" style="text-align:right;margin-top:6px;">'
-                        +       '<button class="layui-btn layui-btn-primary" id="copy-server-info">澶嶅埗</button>'
-                        +   '</div>'
-                        + '</div>';
-                    layer.open({
-                        type: 1,
-                        title: '鑾峰彇绯荤粺閰嶇疆',
-                        area: ['640px','480px'],
-                        shadeClose: true,
-                        content: html,
-                        success: function (layero, index) {
-                            layero.find('#copy-server-info').on('click', function () {
-                                var text = layero.find('#server-info-pre').text();
-                                if (navigator.clipboard && navigator.clipboard.writeText) {
-                                    navigator.clipboard.writeText(text).then(function () {
-                                        layer.msg('宸插鍒跺埌鍓创鏉�');
-                                    }).catch(function () {
-                                        fallbackCopy(text);
-                                    });
-                                } else {
-                                    fallbackCopy(text);
-                                }
-                            });
-                        }
-                    });
-                },
-                error: function () {
-                    layer.msg('鑾峰彇绯荤粺閰嶇疆淇℃伅澶辫触');
+        function openSystemToolsDialog() {
+            layer.open({
+                type: 1,
+                title: '绯荤粺宸ュ叿',
+                area: ['560px', '300px'],
+                shadeClose: true,
+                content: $('#system-tools-panel'),
+                end: function () {
+                    $('#system-tools-panel').hide();
                 }
             });
-            return false;
-        });
+        }
 
         function fallbackCopy(text) {
             try {
@@ -246,6 +178,123 @@
                 layer.msg('澶嶅埗澶辫触');
             }
         }
+
+        function openTextDialog(title, label, text, tip) {
+            var html = ''
+                + '<div style="padding:15px 20px 5px 20px;">'
+                +   '<div style="font-weight:600;margin-bottom:8px;">' + label + '</div>'
+                +   (tip ? '<div style="color:#999;margin-bottom:8px;">' + tip + '</div>' : '')
+                +   '<textarea id="dialog-text" readonly style="width:100%;min-height:220px;background:#f7f7f7;border:1px solid #e6e6e6;border-radius:6px;padding:12px;line-height:1.6;resize:none;">'
+                +       text
+                +   '</textarea>'
+                +   '<div class="layui-btn-container" style="text-align:right;margin-top:10px;">'
+                +       '<button class="layui-btn layui-btn-primary" id="copy-dialog-text">澶嶅埗</button>'
+                +   '</div>'
+                + '</div>';
+            layer.open({
+                type: 1,
+                title: title,
+                area: ['720px','460px'],
+                shadeClose: true,
+                content: html,
+                success: function (layero) {
+                    layero.find('#copy-dialog-text').on('click', function () {
+                        var value = layero.find('#dialog-text').val();
+                        if (navigator.clipboard && navigator.clipboard.writeText) {
+                            navigator.clipboard.writeText(value).then(function () {
+                                layer.msg('宸插鍒跺埌鍓创鏉�');
+                            }).catch(function () {
+                                fallbackCopy(value);
+                            });
+                        } else {
+                            fallbackCopy(value);
+                        }
+                    });
+                }
+            });
+        }
+
+        // 鑾峰彇璇锋眰鐮�
+        $('#btn-request-code').click(function() {
+            $.ajax({
+                url: baseUrl + "/license/getRequestCode",
+                method: 'GET',
+                success: function (res) {
+                    if (res.code === 200){
+                        openTextDialog('鑾峰彇璇锋眰鐮�', '璇锋眰鐮�', res.msg || '', '璇锋眰鐮佷腑宸插寘鍚」鐩悕绉帮紝鐩存帴鍙戠粰璁稿彲璇佹湇鍔$鍗冲彲銆�');
+                    } else {
+                        layer.msg(res.msg || '鑾峰彇璇锋眰鐮佸け璐�');
+                    }
+                },
+                error: function () {
+                    layer.msg('鑾峰彇璇锋眰鐮佸け璐�');
+                }
+            });
+            return false;
+        });
+
+        // 鑾峰彇绯荤粺閰嶇疆
+        $('#btn-server-info').click(function() {
+            $.ajax({
+                url: baseUrl + "/license/getServerInfos",
+                method: 'GET',
+                success: function (res) {
+                    var pretty = '';
+                    try {
+                        pretty = JSON.stringify(res, null, 2);
+                    } catch (e) {
+                        pretty = res;
+                    }
+                    openTextDialog('鑾峰彇绯荤粺閰嶇疆', '绯荤粺閰嶇疆淇℃伅', pretty, '鑰侀」鐩粛鍙户缁娇鐢ㄨ繖浠界‖浠朵俊鎭� JSON 鐢宠璁稿彲璇併��');
+                },
+                error: function () {
+                    layer.msg('鑾峰彇绯荤粺閰嶇疆淇℃伅澶辫触');
+                }
+            });
+            return false;
+        });
+
+        // 褰曞叆璁稿彲璇�
+        $('#btn-upload-license').click(function () {
+            layer.open({
+                type: 1,
+                title: '褰曞叆璁稿彲璇�',
+                area: ['760px', '420px'],
+                shadeClose: true,
+                content: ''
+                    + '<div style="padding:15px 20px 5px 20px;">'
+                    +   '<div style="font-weight:600;margin-bottom:8px;">璁稿彲璇� Base64</div>'
+                    +   '<div style="color:#999;margin-bottom:8px;">灏嗚鍙瘉鏈嶅姟绔繑鍥炵殑 license 瀛楁瀹屾暣绮樿创鍒拌繖閲屻��</div>'
+                    +   '<textarea id="license-base64-input" style="width:100%;min-height:240px;background:#fff;border:1px solid #e6e6e6;border-radius:6px;padding:12px;line-height:1.6;resize:none;"></textarea>'
+                    + '</div>',
+                btn: ['鎻愪氦', '鍙栨秷'],
+                yes: function (index, layero) {
+                    var license = $.trim(layero.find('#license-base64-input').val());
+                    if (!license) {
+                        layer.msg('璁稿彲璇佸唴瀹逛笉鑳戒负绌�');
+                        return;
+                    }
+                    $.ajax({
+                        url: baseUrl + '/license/updateLicense',
+                        method: 'POST',
+                        contentType: 'application/json;charset=UTF-8',
+                        data: JSON.stringify({license: license}),
+                        success: function (res) {
+                            if (res.code === 200) {
+                                layer.close(index);
+                                layer.msg('璁稿彲璇佹洿鏂版垚鍔�');
+                            } else {
+                                layer.msg(res.msg || '璁稿彲璇佹洿鏂板け璐�');
+                            }
+                        },
+                        error: function () {
+                            layer.msg('璁稿彲璇佸綍鍏ュけ璐�');
+                        }
+                    });
+                }
+            });
+            return false;
+        });
 
         // 涓�閿縺娲�
         $('#btn-activate').click(function() {
@@ -299,15 +348,6 @@
                 layer.msg("璇疯緭鍏ュ瘑鐮�", {offset: '150px'});
                 return;
             }
-            // var code = $("#code").val();
-            // if (code === "" && codeSwitch === 'Y') {
-            //     layer.msg("璇疯緭鍏ラ獙璇佺爜", {offset: '150px'});
-            //     return;
-            // }
-            // if (sessionStorage.getItem("code").toUpperCase() !== code.toUpperCase()&&codeSwitch==='Y'){
-            //     layer.msg("楠岃瘉鐮侀敊璇�", {offset: '150px'});
-            //     return;
-            // }
 
             var user = {
                 mobile: mobile,
@@ -339,4 +379,4 @@
     });
 </script>
 <script type></script>
-</html>
\ No newline at end of file
+</html>

--
Gitblit v1.9.1