| New file |
| | |
| | | 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; |
| | | } |
| | | } |
| | |
| | | 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; |
| | | |
| | | /** |
| | | * |
| | |
| | | |
| | | @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 操作系统类型,如果为空则自动判断 |
| | |
| | | @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)); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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) { |
| | | 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("许可证保存失败"); |
| | | } |
| | | |
| | | boolean loadedLicense = licenseCheckListener.loadLicense(false); |
| | | if (!loadedLicense) { |
| | | return R.error("许可证激活失败"); |
| | | } |
| | | licenseTimer.verify(); |
| | | if (!licenseTimer.getSystemSupport()) { |
| | | return R.error("许可证校验失败"); |
| | | } |
| | | return R.ok(); |
| | | } |
| | | return R.error("许可证更新失败"); |
| | | } |
| | | |
| | | @RequestMapping(value = "/activate") |
| | | public R activate() { |
| | | licenseTimer.timer(); |
| | | if (!licenseTimer.getSystemSupport()) { |
| | | return R.error("许可证激活失败"); |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | |
| | | return R.ok(licenseSubject); |
| | | } |
| | | |
| | | 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()); |
| | | } |
| | | |
| | | } |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | //1. 首先调用父类的validate方法 |
| | | 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没在授权范围内"); |
| | |
| | | * 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内 |
| | | */ |
| | | 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; |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | private boolean isBlank(String value) { |
| | | return value == null || value.trim().isEmpty(); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | 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<>(); |
| | | } |
| New file |
| | |
| | | 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(); |
| | | } |
| | | } |
| | |
| | | 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; |
| | | |
| | | /** |
| | |
| | | |
| | | //加载证书 |
| | | public boolean loadLicense() { |
| | | return loadLicense(true); |
| | | } |
| | | |
| | | public boolean loadLicense(boolean fetchRemote) { |
| | | if(!Cools.isEmpty(licensePath)){ |
| | | logger.info("++++++++ 开始加载许可证 ++++++++"); |
| | | |
| | | if (fetchRemote) { |
| | | try { |
| | | licenseTimer.getRemoteLicense(); |
| | | } catch (Exception e) { |
| | | } |
| | | } |
| | | |
| | | try { |
| | |
| | | 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("++++++++ 许可证加载结束 ++++++++"); |
| | | |
| | |
| | | licenseTimer.setSystemSupport(false); |
| | | return false; |
| | | } |
| | | |
| | | 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()); |
| | | } |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| New file |
| | |
| | | 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; |
| | | } |
| New file |
| | |
| | | package com.zy.system.entity.license; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class LicenseUploadParam { |
| | | |
| | | private String license; |
| | | } |
| | |
| | | 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 { |
| | | |
| | | /** |
| | |
| | | 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(); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | 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; |
| | |
| | | |
| | | 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); |
| | |
| | | return base64ToTempFile(base64Data, "temp_license_", ".bin"); |
| | | } |
| | | |
| | | 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); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | 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; |
| | | |
| | |
| | | |
| | | LicenseInfos getLatestLicense(); |
| | | |
| | | LicenseInfos getLatestLicenseByRequestCode(@Param("requestCode") String requestCode); |
| | | |
| | | } |
| | |
| | | |
| | | LicenseInfos getLatestLicense(); |
| | | |
| | | LicenseInfos getLatestLicenseByRequestCode(String requestCode); |
| | | |
| | | } |
| | |
| | | public LicenseInfos getLatestLicense() { |
| | | return this.baseMapper.getLatestLicense(); |
| | | } |
| | | |
| | | @Override |
| | | public LicenseInfos getLatestLicenseByRequestCode(String requestCode) { |
| | | return this.baseMapper.getLatestLicenseByRequestCode(requestCode); |
| | | } |
| | | } |
| | |
| | | @Value("${license.publicKeysStorePath}") |
| | | private String publicKeysStorePath; |
| | | |
| | | /** |
| | | * 许可证服务端地址。 |
| | | */ |
| | | @Value("${license.remoteServerUrl:http://net.zoneyung.net:9999/license}") |
| | | private String remoteServerUrl; |
| | | |
| | | @Autowired |
| | | private LicenseInfosService licenseInfosService; |
| | | |
| | |
| | | |
| | | 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) { |
| | |
| | | } |
| | | } |
| | | |
| | | 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); |
| | |
| | | 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(); |
| | |
| | | # 系统版本信息 |
| | | app: |
| | | version: 1.0.5.1 |
| | | version: 1.0.5.2 |
| | | version-type: dev # prd 或 dev |
| | | |
| | | server: |
| | |
| | | storePass: public_zhongyang_123456789 |
| | | licensePath: license.lic |
| | | publicKeysStorePath: publicCerts.keystore |
| | | remoteServerUrl: http://net.zoneyung.net:9999/license |
| | | |
| | | deviceExecuteConfig: |
| | | # 每个线程管控设备执行数量 |
| | |
| | | <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> |
| | |
| | | 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> |
| New file |
| | |
| | | -- 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'; |
| | |
| | | }); |
| | | }); |
| | | 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; |
| | | } |
| | | }); |
| | | |
| | |
| | | <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> |
| | | |
| | |
| | | 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); |
| | |
| | | } |
| | | } |
| | | |
| | | @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; |
| | |
| | | </el-input> |
| | | </div> |
| | | |
| | | <el-scrollbar class="aside-scroll" v-loading="menuLoading"> |
| | | <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" |
| | |
| | | </el-submenu> |
| | | </el-menu> |
| | | |
| | | <div class="aside-empty" v-if="!menuLoading && filteredMenus.length === 0"> |
| | | <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"> |
| | |
| | | class="page-tabs" |
| | | v-model="activeTab" |
| | | type="card" |
| | | @tab-click="handleTabClick" |
| | | @tab-remove="removeTab"> |
| | | <el-tab-pane |
| | | v-for="tab in tabs" |
| | |
| | | data: function () { |
| | | return { |
| | | isCollapse: false, |
| | | menuLoading: false, |
| | | menuLoading: true, |
| | | pageLoading: true, |
| | | loadingText: "正在加载页面...", |
| | | menuKeyword: "", |
| | |
| | | fakeVisible: false, |
| | | fakeRunning: false, |
| | | fakeStatusInterval: null, |
| | | menuSyncVersion: 0, |
| | | menuSyncTimer: null, |
| | | userName: localStorage.getItem(USER_STORAGE_KEY) || "管理员", |
| | | aiLayerIndex: null, |
| | | aiTipIndex: null |
| | |
| | | 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 = "正在加载 “" + tab.title + "” ..."; |
| | |
| | | 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); |
| | |
| | | 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++) { |
| | |
| | | this.loadingText = "正在加载 “" + 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); |
| | |
| | | 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 = "正在加载 “控制中心” ..."; |
| | | this.persistTabs(); |
| | |
| | | |
| | | this.activeTab = nextTabName; |
| | | this.persistTabs(); |
| | | }, |
| | | handleTabClick: function () { |
| | | this.activeMenuKey = this.findMenuKeyByUrl(this.activeTabUrl); |
| | | }, |
| | | handleFrameLoad: function (name) { |
| | | var tab = this.getTabByName(name); |
| | |
| | | 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; |
| | | } |
| | | } |
| | | } |
| | |
| | | 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; |
| | |
| | | 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 { |
| | |
| | | }; |
| | | 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 || {}; |
| | |
| | | <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"> |
| | |
| | | <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">登     录</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> |
| | | |
| | |
| | | <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, |
| | |
| | | clearTimeout(titleClickTimer); |
| | | } |
| | | if (titleClickCount >= 3) { |
| | | $('#system-btns').show(); |
| | | titleClickCount = 0; |
| | | openSystemToolsDialog(); |
| | | } else { |
| | | titleClickTimer = setTimeout(function() { |
| | | titleClickCount = 0; |
| | |
| | | } |
| | | }); |
| | | |
| | | // 获取系统配置 |
| | | $('#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>'; |
| | | function openSystemToolsDialog() { |
| | | layer.open({ |
| | | type: 1, |
| | | title: '获取系统配置', |
| | | area: ['640px','480px'], |
| | | title: '系统工具', |
| | | area: ['560px', '300px'], |
| | | 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); |
| | | content: $('#system-tools-panel'), |
| | | end: function () { |
| | | $('#system-tools-panel').hide(); |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | error: function () { |
| | | layer.msg('获取系统配置信息失败'); |
| | | } |
| | | }); |
| | | return false; |
| | | }); |
| | | |
| | | function fallbackCopy(text) { |
| | | try { |
| | |
| | | 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() { |
| | |
| | | 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, |