dubin
6 天以前 c1c38d89b4d1b6576b7be0115a73a35b695ad375
初始化
7个文件已添加
15个文件已修改
1006 ■■■■ 已修改文件
pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/controller/LicenseCreatorController.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/LicenseInfos.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/AbstractServerInfos.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/CustomKeyStoreParam.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/CustomLicenseManager.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseCheckListener.java 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LicenseVerify.java 75 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/entity/license/LinuxServerInfos.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/mapper/LicenseInfosMapper.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/service/LicenseInfosService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/service/impl/LicenseInfosServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/timer/LicenseTimer.java 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-prod.yml 309 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/LicenseInfosMapper.xml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/wcs/js/common.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/wcs/js/console.map.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/wcs/js/console1.map.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/wcs/js/console2.map.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/wcs/js/console3.map.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/index.html 148 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -200,7 +200,7 @@
    </dependencies>
    <build>
        <finalName>scwcs</finalName>
        <finalName>wcs</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
src/main/java/com/zy/system/controller/LicenseCreatorController.java
@@ -3,7 +3,7 @@
import com.core.common.Cools;
import com.core.common.R;
import com.zy.system.entity.license.*;
import de.schlichtherle.license.LicenseContent;
import com.zy.system.timer.LicenseTimer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
@@ -14,7 +14,6 @@
import java.io.File;
import java.io.IOException;
import java.util.Date;
/**
 *
@@ -26,10 +25,10 @@
    @Value("${license.licensePath}")
    private String licensePath;
    @Autowired
    private LicenseCheckListener licenseCheckListener;
    @Autowired
    private LicenseTimer licenseTimer;
    /**
     * 获取服务器硬件信息
     * @param osName 操作系统类型,如果为空则自动判断
@@ -48,7 +47,7 @@
        if (osName.startsWith("windows")) {
            abstractServerInfos = new WindowsServerInfos();
        } else if (osName.startsWith("linux")) {
//            abstractServerInfos = new LinuxServerInfos();
            abstractServerInfos = new LinuxServerInfos();
        }else{//其他服务器类型
            abstractServerInfos = new WindowsServerInfos();
        }
@@ -61,19 +60,7 @@
     */
    @RequestMapping(value = "/getLicenseDays")
    public R getLicenseDays() {
        LicenseVerify licenseVerify = new LicenseVerify();
        LicenseContent verifyInfo = licenseVerify.getVerifyInfo();
        if (verifyInfo == null) {
            return R.error();
        }
        Date start = new Date();
        Date end = verifyInfo.getNotAfter();
        Long starTime = start.getTime();
        Long endTime = end.getTime();
        Long num = endTime - starTime;//时间戳相差的毫秒数
        int day = (int) (num / 24 / 60 / 60 / 1000);
        return R.ok().add(day);
        return R.ok(licenseTimer.getLicenseDays());
    }
    @RequestMapping(value = "/updateLicense")
@@ -109,4 +96,10 @@
        return R.error("许可证更新失败");
    }
    @RequestMapping(value = "/activate")
    public R activate() {
        licenseTimer.timer();
        return R.ok();
    }
}
src/main/java/com/zy/system/entity/LicenseInfos.java
New file
@@ -0,0 +1,56 @@
package com.zy.system.entity;
import com.core.common.Cools;import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.enums.IdType;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.baomidou.mybatisplus.annotations.TableField;
import org.springframework.format.annotation.DateTimeFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import com.baomidou.mybatisplus.annotations.TableName;
import java.io.Serializable;
@Data
@TableName("sys_license_infos")
public class LicenseInfos implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value= "")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty(value= "")
    private String license;
    @ApiModelProperty(value= "")
    private String licenseTime;
    @ApiModelProperty(value= "")
    @TableField("create_time")
    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    public LicenseInfos() {}
    public LicenseInfos(String license,Date createTime) {
        this.license = license;
        this.createTime = createTime;
    }
//    LicenseInfos licenseInfos = new LicenseInfos(
//            null,    //
//            null    //
//    );
    public String getCreateTime$(){
        if (Cools.isEmpty(this.createTime)){
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
    }
}
src/main/java/com/zy/system/entity/license/AbstractServerInfos.java
@@ -23,7 +23,7 @@
        LicenseCheck result = new LicenseCheck();
        try {
            result.setIpAddress(this.getIpAddress());
//            result.setIpAddress(this.getIpAddress());
            result.setMacAddress(this.getMacAddress());
            result.setCpuSerial(this.getCPUSerial());
            result.setMainBoardSerial(this.getMainBoardSerial());
src/main/java/com/zy/system/entity/license/CustomKeyStoreParam.java
@@ -2,7 +2,9 @@
import de.schlichtherle.license.AbstractKeyStoreParam;
import java.io.*;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
 * 自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中
@@ -47,7 +49,7 @@
     */
    @Override
    public InputStream getStream() throws IOException {
        final InputStream in = new FileInputStream(new File(storePath));
        final InputStream in = this.getClass().getClassLoader().getResourceAsStream(storePath);
        if (null == in) {
            throw new FileNotFoundException(storePath);
        }
src/main/java/com/zy/system/entity/license/CustomLicenseManager.java
@@ -129,12 +129,12 @@
        if(expectedCheckModel != null && serverCheckModel != null){
            //校验IP地址
            if(!checkIpAddress(expectedCheckModel.getIpAddress(),serverCheckModel.getIpAddress())){
                //throw new LicenseContentException("当前服务器的IP没在授权范围内");
                throw new LicenseContentException("当前服务器的IP没在授权范围内");
            }
            //校验Mac地址
            if(!checkIpAddress(expectedCheckModel.getMacAddress(),serverCheckModel.getMacAddress())){
                //throw new LicenseContentException("当前服务器的Mac地址没在授权范围内");
                throw new LicenseContentException("当前服务器的Mac地址没在授权范围内");
            }
            //校验主板序列号
@@ -194,7 +194,7 @@
        if (osName.startsWith("windows")) {
            abstractServerInfos = new WindowsServerInfos();
        } else if (osName.startsWith("linux")) {
//            abstractServerInfos = new LinuxServerInfos();
            abstractServerInfos = new LinuxServerInfos();
        }else{//其他服务器类型
            abstractServerInfos = new WindowsServerInfos();
        }
src/main/java/com/zy/system/entity/license/LicenseCheckListener.java
@@ -1,9 +1,13 @@
package com.zy.system.entity.license;
import com.core.common.Cools;
import com.zy.system.entity.LicenseInfos;
import com.zy.system.service.LicenseInfosService;
import com.zy.system.timer.LicenseTimer;
import de.schlichtherle.license.LicenseContent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
@@ -11,6 +15,7 @@
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.Date;
/**
 * 在项目启动时安装证书
@@ -48,6 +53,10 @@
     */
    @Value("${license.publicKeysStorePath}")
    private String publicKeysStorePath;
    @Autowired
    private LicenseTimer licenseTimer;
    @Autowired
    private LicenseInfosService licenseInfosService;
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
@@ -64,31 +73,51 @@
            logger.info("++++++++ 开始加载许可证 ++++++++");
            try {
                String publicKeysStoreFileName = this.getClass().getClassLoader().getResource(publicKeysStorePath).getPath();
                File publicKeysStoreFile = new File(publicKeysStoreFileName);
                licenseTimer.getRemoteLicense();
            } catch (Exception e) {
            }
                String licensePathFileName = this.getClass().getClassLoader().getResource(licensePath).getPath();
                File licensePathFile = new File(licensePathFileName);
            try {
                LicenseVerifyParam param = new LicenseVerifyParam();
                param.setSubject(subject);
                param.setPublicAlias(publicAlias);
                param.setStorePass(storePass);
                param.setLicensePath(licensePathFile.getPath());
                param.setPublicKeysStorePath(publicKeysStoreFile.getPath());
                param.setLicensePath(licensePath);
                param.setPublicKeysStorePath(publicKeysStorePath);
                LicenseVerify licenseVerify = new LicenseVerify();
                LicenseInfos latestLicense = licenseInfosService.getLatestLicense();
                if (latestLicense == null) {
                    logger.info("许可证不存在");
                    return false;
                }
                //安装证书
                LicenseContent install = licenseVerify.install(param);
                LicenseContent install = licenseVerify.install(param, latestLicense.getLicense());
                logger.info("++++++++ 许可证加载结束 ++++++++");
                licenseTimer.setSystemSupport(install!=null);
                if (install != null) {
                    Date start = new Date();
                    Date end = install.getNotAfter();
                    Long starTime = start.getTime();
                    Long endTime = end.getTime();
                    Long num = endTime - starTime;//时间戳相差的毫秒数
                    int day = (int) (num / 24 / 60 / 60 / 1000);
                    licenseTimer.setLicenseDays(day);
                }
                return install != null;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        licenseTimer.setSystemSupport(false);
        return false;
    }
}
src/main/java/com/zy/system/entity/license/LicenseVerify.java
@@ -5,9 +5,13 @@
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.prefs.Preferences;
/**
@@ -19,19 +23,21 @@
    /**
     * 安装License证书
     */
    public synchronized LicenseContent install(LicenseVerifyParam param){
    public synchronized LicenseContent install(LicenseVerifyParam param, String license) {
        LicenseContent result = null;
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //1. 安装证书
        try{
            LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param));
        try {
            LicenseParam licenseParam = initLicenseParam(param);
            LicenseManager licenseManager = LicenseManagerHolder.getInstance(licenseParam);
            licenseManager.uninstall();
            result = licenseManager.install(new File(param.getLicensePath()));
            logger.info(MessageFormat.format("许可证加载成功,许可证有效期:{0} - {1}",format.format(result.getNotBefore()),format.format(result.getNotAfter())));
        }catch (Exception e){
            logger.error("许可证加载失败!",e);
            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;
@@ -44,11 +50,6 @@
        try {
            LicenseManager licenseManager = LicenseManagerHolder.getInstance(null);
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            if (!updateSystemTime()) {
                //时间更新失败,系统时间被更改
                return false;
            }
            LicenseContent licenseContent = licenseManager.verify();
            logger.info(MessageFormat.format("许可证校验通过,许可证有效期:{0} - {1}",format.format(licenseContent.getNotBefore()),format.format(licenseContent.getNotAfter())));
@@ -64,11 +65,6 @@
     */
    public LicenseContent getVerifyInfo(){
        LicenseManager licenseManager = LicenseManagerHolder.getInstance(null);
        if (!updateSystemTime()) {
            //时间更新失败,系统时间被更改
            return null;
        }
        //校验证书
        try {
@@ -103,31 +99,28 @@
    }
    /**
     * 更新时间到注册表中
     * 将Base64字符串转换为临时文件
     * @param base64String Base64编码的字符串
     * @param filePrefix 文件名前缀(例如 "license_")
     * @param fileSuffix 文件后缀(例如 ".lic")
     * @return 生成的临时File对象(自动在JVM退出时删除)
     * @throws IOException
     */
    private boolean updateSystemTime() {
        // 获取用户根节点
        Preferences userRoot = Preferences.userRoot();
        // 获取指定路径下的节点
        Preferences node = userRoot.node("/zhongyang");
        String key = "time";
        // 读取注册表
        String value = node.get(key, null);
        if (value != null) {
            long originTime = Long.parseLong(value);
            long now = System.currentTimeMillis();
            long diff = now - originTime;//现在时间 - 源时间 = 时间差
            if (diff > 0) {
                //时间差大于0才允许更新注册表时间
                node.put(key, String.valueOf(System.currentTimeMillis()));
                return true;
            }
        }else {
            // 写入注册表
            node.put(key, String.valueOf(System.currentTimeMillis()));
            return true;
        }
        return false;
    public File base64ToTempFile(String base64String, String filePrefix, String fileSuffix)
            throws IOException {
        // 解码Base64
        byte[] decodedBytes = Base64.getDecoder().decode(base64String);
        // 创建临时文件
        Path tempPath = Files.createTempFile(filePrefix, fileSuffix);
        // 写入内容
        Files.write(tempPath, decodedBytes);
        // 设置JVM退出时自动删除
        tempPath.toFile().deleteOnExit();
        return tempPath.toFile();
    }
    public File createTempFileFromBase64(String base64Data) throws IOException {
        return base64ToTempFile(base64Data, "temp_license_", ".bin");
    }
}
src/main/java/com/zy/system/entity/license/LinuxServerInfos.java
New file
@@ -0,0 +1,91 @@
package com.zy.system.entity.license;
import com.core.common.Cools;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.util.List;
import java.util.stream.Collectors;
/**
 * 用于获取客户Linux服务器的基本信息
 */
public class LinuxServerInfos extends AbstractServerInfos {
    @Override
    protected List<String> getIpAddress() throws Exception {
        List<String> result = null;
        //获取所有网络接口
        List<InetAddress> inetAddresses = getLocalAllInetAddress();
        if (inetAddresses != null && inetAddresses.size() > 0) {
            result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList());
        }
        return result;
    }
    @Override
    protected List<String> getMacAddress() throws Exception {
        List<String> result = null;
        //1. 获取所有网络接口
        List<InetAddress> inetAddresses = getLocalAllInetAddress();
        if (inetAddresses != null && inetAddresses.size() > 0) {
            //2. 获取所有网络接口的Mac地址
            result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());
        }
        return result;
    }
    @Override
    protected String getCPUSerial() throws Exception {
        //序列号
        String serialNumber = "";
        //使用dmidecode命令获取CPU序列号
        String[] shell = {"/bin/bash", "-c", "dmidecode -t processor | grep 'ID' | awk -F ':' '{print $2}' | head -n 1"};
        Process process = Runtime.getRuntime().exec(shell);
        process.getOutputStream().close();
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        if (null == reader.readLine()) {
            return serialNumber;
        }
        String line = reader.readLine().trim();
        if (!Cools.isEmpty(line)) {
            serialNumber = line;
        }
        reader.close();
        return serialNumber;
    }
    @Override
    protected String getMainBoardSerial() throws Exception {
        //序列号
        String serialNumber = "";
        //使用dmidecode命令获取主板序列号
        String[] shell = {"/bin/bash", "-c", "dmidecode | grep 'Serial Number' | awk -F ':' '{print $2}' | head -n 1"};
        Process process = Runtime.getRuntime().exec(shell);
        process.getOutputStream().close();
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        if (null == reader.readLine()) {
            return serialNumber;
        }
        String line = reader.readLine().trim();
        if (!Cools.isEmpty(line)) {
            serialNumber = line;
        }
        reader.close();
        return serialNumber;
    }
}
src/main/java/com/zy/system/mapper/LicenseInfosMapper.java
New file
@@ -0,0 +1,14 @@
package com.zy.system.mapper;
import com.zy.system.entity.LicenseInfos;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface LicenseInfosMapper extends BaseMapper<LicenseInfos> {
    LicenseInfos getLatestLicense();
}
src/main/java/com/zy/system/service/LicenseInfosService.java
New file
@@ -0,0 +1,10 @@
package com.zy.system.service;
import com.zy.system.entity.LicenseInfos;
import com.baomidou.mybatisplus.service.IService;
public interface LicenseInfosService extends IService<LicenseInfos> {
    LicenseInfos getLatestLicense();
}
src/main/java/com/zy/system/service/impl/LicenseInfosServiceImpl.java
New file
@@ -0,0 +1,16 @@
package com.zy.system.service.impl;
import com.zy.system.mapper.LicenseInfosMapper;
import com.zy.system.entity.LicenseInfos;
import com.zy.system.service.LicenseInfosService;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service("licenseInfosService")
public class LicenseInfosServiceImpl extends ServiceImpl<LicenseInfosMapper, LicenseInfos> implements LicenseInfosService {
    @Override
    public LicenseInfos getLatestLicense() {
        return this.baseMapper.getLatestLicense();
    }
}
src/main/java/com/zy/system/timer/LicenseTimer.java
New file
@@ -0,0 +1,163 @@
package com.zy.system.timer;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.core.common.Cools;
import com.zy.common.utils.HttpHandler;
import com.zy.system.entity.LicenseInfos;
import com.zy.system.entity.license.*;
import com.zy.system.service.LicenseInfosService;
import de.schlichtherle.license.LicenseContent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
@Component
public class LicenseTimer {
    private static boolean SYSTEM_SUPPORT = false;//系统激活状态,默认关闭
    private static int LICENSE_DAYS = 0;//许可证天数
    /**
     * 证书subject
     */
    @Value("${license.subject}")
    private String subject;
    /**
     * 公钥别称
     */
    @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 LicenseInfosService licenseInfosService;
    //每天晚上11点更新系统激活状态
    @Scheduled(cron = "0 0 23 * * ? ")
    public void timer() {
        try {
            getRemoteLicense();
        } catch (Exception e) {
        }
        try {
            verify();
        } catch (Exception e) {
        }
    }
    public void getRemoteLicense() {
        try {
            AbstractServerInfos abstractServerInfos = null;
            String osName = System.getProperty("os.name");
            //根据不同操作系统类型选择不同的数据获取方法
            if (osName.startsWith("windows")) {
                abstractServerInfos = new WindowsServerInfos();
            } else if (osName.startsWith("linux")) {
                abstractServerInfos = new LinuxServerInfos();
            }else{//其他服务器类型
                abstractServerInfos = new WindowsServerInfos();
            }
            LicenseCheck serverInfos = abstractServerInfos.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")) {
                LicenseInfos licenseInfos = new LicenseInfos();
                licenseInfos.setLicense(jsonObject.getString("data"));
                licenseInfos.setCreateTime(new Date());
                licenseInfos.setLicenseTime(jsonObject.getString("licenseTime"));
                licenseInfosService.insert(licenseInfos);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void verify() {
        LicenseInfos latestLicense = licenseInfosService.getLatestLicense();
        if (latestLicense == null) {
            setLicenseDays(0);
            setSystemSupport(false);
            return;
        }
        LicenseVerifyParam param = new LicenseVerifyParam();
        param.setSubject(subject);
        param.setPublicAlias(publicAlias);
        param.setStorePass(storePass);
        param.setLicensePath(licensePath);
        param.setPublicKeysStorePath(publicKeysStorePath);
        //验证许可证是否有效
        LicenseVerify licenseVerify = new LicenseVerify();
        //安装证书
        LicenseContent install = licenseVerify.install(param, latestLicense.getLicense());
        if (install != null) {
            Date start = new Date();
            Date end = install.getNotAfter();
            Long starTime = start.getTime();
            Long endTime = end.getTime();
            long num = endTime - starTime;//时间戳相差的毫秒数
            int day = (int) (num / 24 / 60 / 60 / 1000);
            setLicenseDays(day);
            setSystemSupport(true);
        }else {
            setLicenseDays(0);
            setSystemSupport(false);
        }
    }
    public boolean getSystemSupport() {
        return SYSTEM_SUPPORT;
    }
    public void setSystemSupport(boolean systemSupport) {
        SYSTEM_SUPPORT = systemSupport;
    }
    public int getLicenseDays() {
        return LICENSE_DAYS;
    }
    public void setLicenseDays(int licenseDays) {
        LICENSE_DAYS = licenseDays;
    }
}
src/main/resources/application-prod.yml
@@ -1,6 +1,6 @@
wcs-slave:
  doubleDeep: true #双深
  doubleLocs: 5,8 #双深库位排号 1,4
  doubleLocs: 1,4 #双深库位排号 1,4
  groupCount: 4 #一个堆垛机负责的货架排数
  crn[0]: #堆垛机1
    id: 1
@@ -28,134 +28,14 @@
      row: 1
      bay: 59
      lev: 11
      devpPlcId: ${wcs-slave.devp[1].id}
      devpPlcId: ${wcs-slave.devp[0].id}
    crnInStn[1]: #堆垛机入库站点--2F输送线 取货口
      staNo: 2003
      row: 2
      bay: 59
      lev: 11
      backSta: 102
      devpPlcId: ${wcs-slave.devp[1].id}
  crn[1]: #堆垛机2
    id: 2
    ip: 192.168.110.90
    slot: 0
    demo: false
    rack: 0
    offset: 2  #偏移量,当堆垛机站点列号=1时,偏移量=2
    port: 102
    crnOutStn[0]: #堆垛机出库站点--1F输送线 放货口
      staNo: 1006
      row: 3
      bay: 1
      lev: 1
      devpPlcId: ${wcs-slave.devp[0].id}
    crnInStn[0]: #堆垛机入库站点1--1F输送线 取货口
      staNo: 1008
      row: 4
      bay: 1
      lev: 1
      backSta: 106
      devpPlcId: ${wcs-slave.devp[0].id}
    crnOutStn[1]: #堆垛机出库站点--2F输送线 放货口
      staNo: 2013
      row: 4
      bay: 59
      lev: 11
      devpPlcId: ${wcs-slave.devp[1].id}
    crnInStn[1]: #堆垛机入库站点1--2F输送线 取货口
      staNo: 2012
      row: 3
      bay: 59
      lev: 11
      backSta: 106
      devpPlcId: ${wcs-slave.devp[1].id}
  crn[2]: #堆垛机3
    id: 3
    ip: 192.168.110.10
    slot: 0
    demo: false
    rack: 0
    offset: 2  #偏移量,当堆垛机站点列号=1时,偏移量=2
    port: 102
    crnOutStn[0]: #堆垛机出库站点--1F输送线 放货口
      staNo: 1056
      row: 7
      bay: 1
      lev: 1
      devpPlcId: ${wcs-slave.devp[2].id}
    crnInStn[0]: #堆垛机入库站点1--1F输送线 取货口
      staNo: 1058
      row: 6
      bay: 1
      lev: 1
      backSta: 106
      devpPlcId: ${wcs-slave.devp[2].id}
    crnOutStn[1]: #堆垛机出库站点--2F输送线 放货口
      staNo: 2057
      row: 7
      bay: 1
      lev: 13
      devpPlcId: ${wcs-slave.devp[3].id}
    crnInStn[1]: #堆垛机入库站点1--2F输送线 取货口
      staNo: 2057
      row: 7
      bay: 1
      lev: 13
      backSta: 106
      devpPlcId: ${wcs-slave.devp[3].id}
    crnOutStn[2]: #堆垛机出库站点--2F输送线 放货口
      staNo: 2058
      row: 6
      bay: 1
      lev: 13
      devpPlcId: ${wcs-slave.devp[3].id}
    crnInStn[2]: #堆垛机入库站点1--2F输送线 取货口
      staNo: 2058
      row: 6
      bay: 1
      lev: 13
      backSta: 106
      devpPlcId: ${wcs-slave.devp[3].id}
    crnOutStn[3]: #堆垛机出库站点--2F输送线 放货口
      staNo: 2051
      row: 7
      bay: 13
      lev: 13
      devpPlcId: ${wcs-slave.devp[3].id}
    crnInStn[3]: #堆垛机入库站点1--2F输送线 取货口
      staNo: 2051
      row: 7
      bay: 13
      lev: 13
      backSta: 106
      devpPlcId: ${wcs-slave.devp[3].id}
    crnOutStn[4]: #堆垛机出库站点--2F输送线 放货口
      staNo: 2052
      row: 7
      bay: 10
      lev: 13
      devpPlcId: ${wcs-slave.devp[3].id}
    crnInStn[4]: #堆垛机入库站点1--2F输送线 取货口
      staNo: 2052
      row: 7
      bay: 10
      lev: 13
      backSta: 106
      devpPlcId: ${wcs-slave.devp[3].id}
    crnOutStn[5]: #堆垛机出库站点--2F输送线 放货口
      staNo: 2054
      row: 7
      bay: 4
      lev: 13
      devpPlcId: ${wcs-slave.devp[3].id}
    crnInStn[5]: #堆垛机入库站点1--2F输送线 取货口
      staNo: 2056
      row: 7
      bay: 2
      lev: 13
      backSta: 106
      devpPlcId: ${wcs-slave.devp[3].id}
  devp[0]: #输送线--半成品1F
    id: 1
    ip: 192.168.110.50
@@ -168,192 +48,51 @@
      staNo: 1014
      backSta: 1015
      barcode: ${wcs-slave.barcode[0].id}
      led: ${wcs-slave.led[2].id}
      scale: ${wcs-slave.scale[0].id}
      led: ${wcs-slave.led[0].id}
#      scale: ${wcs-slave.scale[0].id}
    outSta[0]: #出库口1
      staNo: 1019
    outSta[1]: #出库口2
      staNo: 1010
    outSta[2]: #出库口2
      staNo: 1020
  devp[1]: #输送线--半成品2F
    id: 2
    ip: 192.168.110.70
    rack: 0
    port: 102
    slot: 0
    emptyInSta[0]: #空板入库口0
      staNo: 2007
    inSta[0]: #入库口1
      staNo: 2007
      backSta: 2006
      barcode: ${wcs-slave.barcode[1].id}
      led: ${wcs-slave.led[8].id}
      scale: ${wcs-slave.scale[1].id}
    outSta[0]: #出库口1
      staNo: 2001
    outSta[1]: #出库口2
      staNo: 2019
  devp[2]: #输送线--成品1F
    id: 3
    ip: 192.168.110.30
    rack: 0
    port: 102
    slot: 0
    inSta[0]: #入库口1
      staNo: 1052
      backSta: 1051
      barcode: ${wcs-slave.barcode[2].id}
      led: ${wcs-slave.led[0].id}
      scale: ${wcs-slave.scale[2].id}
    inSta[1]: #空板入库口0
        staNo: 1058
    outSta[0]: #出库口1
      staNo: 1053
  devp[3]: #输送线--成品2F
    id: 4
    ip: 192.168.110.40
    rack: 0
    port: 102
    slot: 0
    emptyInSta[0]: #空板入库口0
      staNo: 2056
      led: ${wcs-slave.led[3].id}
    inSta[0]: #入库口1
      staNo: 2056
      backSta: 2055
      barcode: ${wcs-slave.barcode[3].id}
      led: ${wcs-slave.led[4].id}
      scale: ${wcs-slave.scale[3].id}
    outSta[0]: #出库口1
      staNo: 1053
    outSta[1]: #出库口1
      staNo: 251
    outSta[2]: #出库口1
      staNo: 252
    outSta[3]: #出库口1
      staNo: 257
    outSta[4]: #出库口1
      staNo: 258
    # 拣料入库口1
    pickSta[0]:
      staNo: 2051
      led: ${wcs-slave.led[6].id}
    # 拣料入库口2
    pickSta[1]:
      staNo: 2052
    # 拣料入库口3
    pickSta[2]:
      staNo: 2057
      led: ${wcs-slave.led[3].id}
    # 拣料入库口4
    pickSta[3]:
      staNo: 2058
  barcode[0]: #条码扫描仪
    port: 51236
    ip: 172.17.91.39
    id: 1
  barcode[1]: #条码扫描仪
    port: 51236
    ip: 172.17.91.39
    id: 2
  barcode[2]: #条码扫描仪
    port: 51236
    ip: 172.17.91.39
    id: 3
  barcode[3]: #条码扫描仪
    port: 51236
    ip: 172.17.91.39
    id: 4
  # LED1 成品库1F
  # LED1
  led[0]:
    id: 1
    ip: 192.168.110.210
    port: 5005
    devpPlcId: ${wcs-slave.devp[2].id}
    devpPlcId: ${wcs-slave.devp[0].id}
    staArr: 1052
  # LED2 成品库1F
  # LED2
  led[1]:
    id: 2
    ip: 192.168.110.211
    port: 5005
    devpPlcId: ${wcs-slave.devp[2].id}
    staArr: 1051
  # LED3 半成品库1F
  led[2]:
    id: 3
    ip: 192.168.110.212
    port: 5005
    devpPlcId: ${wcs-slave.devp[0].id}
    staArr: 1014,1015
  # LED4 成品库2F
  led[3]:
    id: 4
    ip: 192.168.110.213
    port: 5005
    devpPlcId: ${wcs-slave.devp[3].id}
    staArr: 2057
  # LED5 成品库2F
  led[4]:
    id: 5
    ip: 192.168.110.214
    port: 5005
    devpPlcId: ${wcs-slave.devp[3].id}
    staArr: 2056
  # LED6 成品库2F
  led[5]:
    id: 6
    ip: 192.168.110.215
    port: 5005
    devpPlcId: ${wcs-slave.devp[3].id}
    staArr: 2054
  # LED7 成品库2F
  led[6]:
    id: 7
    ip: 192.168.110.216
    port: 5005
    devpPlcId: ${wcs-slave.devp[3].id}
    staArr: 2051
  # LED8 半成品库2F
  led[7]:
    id: 8
    ip: 192.168.110.217
    port: 5005
    devpPlcId: ${wcs-slave.devp[1].id}
    staArr: 2002
  # LED9 半成品库2F
  led[8]:
    id: 9
    ip: 192.168.110.218
    port: 5005
    devpPlcId: ${wcs-slave.devp[1].id}
    staArr: 2007
  # LED10 半成品库2F
  led[9]:
    id: 10
    ip: 192.168.110.219
    port: 5005
    devpPlcId: ${wcs-slave.devp[1].id}
    staArr: 2018
    staArr: 1051
  # 磅秤 半成品一楼 192.168.110.24
  scale[0]:
    id: 1
    ip: 192.168.110.24
    port: 5005
#  scale[0]:
#    id: 1
#    ip: 192.168.110.24
#    port: 5005
  # 磅秤 半成品2楼 192.168.110.23
  scale[1]:
    id: 2
    ip: 192.168.110.23
    port: 5005
#  scale[1]:
#    id: 2
#    ip: 192.168.110.23
#    port: 5005
  # 磅秤 成品库一楼192.168.110.21
  scale[2]:
    id: 3
    ip: 192.168.110.21
    port: 5005
#  scale[2]:
#    id: 3
#    ip: 192.168.110.21
#    port: 5005
  # 磅秤 成品库2楼 192.168.110.22
  scale[3]:
    id: 4
    ip: 192.168.110.22
    port: 5005
#  scale[3]:
#    id: 4
#    ip: 192.168.110.22
#    port: 5005
src/main/resources/application.yml
@@ -1,5 +1,5 @@
server:
  port: 8080
  port: 9090
  servlet:
    context-path: /@pom.build.finalName@
@@ -37,7 +37,7 @@
#License相关配置
license:
  subject: scwcs
  subject: yrwcs
  publicAlias: publicCert
  storePass: public_zhongyang_123456789
  licensePath: license.lic
src/main/resources/mapper/LicenseInfosMapper.xml
New file
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zy.system.mapper.LicenseInfosMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.zy.system.entity.LicenseInfos">
        <id column="id" property="id" />
        <result column="license" property="license" />
        <result column="license_time" property="licenseTime" />
        <result column="create_time" property="createTime" />
    </resultMap>
    <select id="getLatestLicense" resultMap="BaseResultMap">
        select top 1 * from sys_license_infos order by create_time desc
    </select>
</mapper>
src/main/webapp/static/wcs/js/common.js
@@ -1,4 +1,4 @@
var baseUrl = "/scwcs";
var baseUrl = "/wcs";
// 赋值
function setVal(el, val) {
src/main/webapp/static/wcs/js/console.map.js
@@ -1,5 +1,5 @@
mapInfo = {
    "mapName": "scwcs",
    "mapName": "wcs",
    "rackCount": 13,
    "crnCount": 4,
    "stbCount": 4,
src/main/webapp/static/wcs/js/console1.map.js
@@ -1,5 +1,5 @@
mapInfo = {
    "mapName": "scwcs",
    "mapName": "wcs",
    "rackCount": 13,
    "crnCount": 4,
    "stbCount": 4,
src/main/webapp/static/wcs/js/console2.map.js
@@ -1,5 +1,5 @@
mapInfo = {
    "mapName": "scwcs",
    "mapName": "wcs",
    "rackCount": 13,
    "crnCount": 4,
    "stbCount": 4,
src/main/webapp/static/wcs/js/console3.map.js
@@ -1,5 +1,5 @@
mapInfo = {
    "mapName": "scwcs",
    "mapName": "wcs",
    "rackCount": 13,
    "crnCount": 4,
    "stbCount": 4,
src/main/webapp/views/index.html
@@ -2,15 +2,14 @@
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>自动化立体仓库 - wcs</title>
  <title>中扬 - 自动化立体仓库 - AS / RS</title>
  <meta name="renderer" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
  <link rel="stylesheet" href="../static/wms/layui/css/layui.css" media="all">
  <link rel="stylesheet" href="../static/wms/css/admin.css?v=318" media="all">
  <link rel="stylesheet" href="../static/wms/css/loader.css" media="all">
  <link rel="stylesheet" href="../static/wcs/css/layx.min.css" type="text/css" />
  <script src="../static/wcs/js/tools/layx.min.js"></script>
  <link rel="icon" type="image/x-icon" href="../static/image/favicon.ico" />
  <link rel="stylesheet" href="../static/layui/css/layui.css" media="all">
  <link rel="stylesheet" href="../static/css/admin.css?v=318" media="all">
  <link rel="stylesheet" href="../static/css/loader.css" media="all">
  <style>
    .layui-logo img {
      width: 25px;
@@ -20,27 +19,6 @@
      font-weight: 400;
      /*margin-left: 5px;*/
    }
    /* 弹窗样式 */
    .popup {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(0,0,0,0.5);
      display: none;
      justify-content: center;
      align-items: center;
      z-index: 9999;
    }
    .popup-content {
      background-color: #fff;
      padding: 20px;
      border-radius: 5px;
      box-shadow: 0px 0px 20px rgba(0,0,0,0.3);
      text-align: center;
    }
  </style>
</head>
<body class="layui-layout-body">
@@ -48,10 +26,10 @@
  <!-- 头部 -->
  <div class="layui-header">
    <div class="layui-logo">
      <img class="loginLogo" src="../static/wms/image/logo.png" style="display: inline-block; width: 60%;height: auto">
      <!--          <span style="margin-top: 0; letter-spacing: 10px">wcs</span>-->
      <!--          <img src="../static/wms/image/logo.svg"/>-->
      <!--          <cite>wcs - 自动化立体仓库</cite>-->
      <img src="../static/image/zy_logo_dark_color.png" style="display: inline-block; width: 40%;height: auto">
      <!--          <span style="margin-top: 0; letter-spacing: 10px">中扬立库</span>-->
      <!--          <img src="../static/image/logo.svg"/>-->
      <!--          <cite>中扬 - Zoneyung</cite>-->
    </div>
    <ul class="layui-nav layui-layout-left">
@@ -63,11 +41,8 @@
      </li>
    </ul>
    <ul class="layui-nav layui-layout-right">
<!--      <li class="layui-nav-item" lay-unselect>-->
<!--        <a ew-event="note" title="便签"><i class="layui-icon layui-icon-note"></i></a>-->
<!--      </li>-->
      <li class="layui-nav-item" lay-unselect id="licenseShow" style="display: none;user-select: none;">
        <div style="color: red;">仓储系统许可有效期:<span id="licenseDays">29</span>天</div>
        <div style="color: red;">临时许可证有效期:<span id="licenseDays">29</span>天</div>
      </li>
      <li class="layui-nav-item layui-hide-xs" lay-unselect>
        <a ew-event="fullScreen" title="全屏"><i class="layui-icon layui-icon-screen-full"></i></a>
@@ -100,7 +75,7 @@
  <div class="layui-body"></div>
  <!-- 底部 -->
  <div class="layui-footer layui-text">
<!--    <span class="copyright-text">copyright © 2023 wcs all rights reserved.</span>-->
    copyright © 2022 <a href="https://www.superton.cn/" target="_blank">浙江中扬立库有限公司</a> all rights reserved.
    <span class="pull-right">Version 1.0.0</span>
  </div>
@@ -110,65 +85,13 @@
<div class="layuimini-loader">
  <div class="layuimini-loader-inner"></div>
</div>
<!-- 弹窗内容 -->
<div class="popup" id="popup">
  <div class="popup-content">
    <h2 style="font-size: 28px;margin-bottom: 10px;">许可证即将过期</h2>
    <div id="popup-text" style="font-size: 28px;color: red"></div>
    <button style="background-color: #007bff;color: #fff;border: none;padding: 10px 20px;border-radius: 5px;cursor: pointer;font-size: 16px;" onclick="hidePopup()">关闭</button>
  </div>
</div>
<script>
  // 显示弹窗
  function showPopup(res) {
    document.getElementById('popup').style.display = 'block';
    // 获取弹出窗口内容的容器元素
    var popupText = document.getElementById('popup-text');
    // 假设后台返回的字符串为 responseString
    if (res!==""){
      // 获取当前日期
      const currentDate = new Date();
      // 创建新日期对象并添加天数
      const newDate = new Date();
      newDate.setDate(currentDate.getDate() + res + 1);
      // 将字符串设置为弹窗内容的文本
      popupText.textContent = "许可证将于" + new Intl.DateTimeFormat('zh-CN').format(newDate) + "过期,剩余有效期:" + res + "天!";
    }else {
      document.getElementById('popup').style.display = 'none';
    }
  }
  // 隐藏弹窗
  function hidePopup() {
    document.getElementById('popup').style.display = 'none';
  }
</script>
<script type="text/javascript" src="../static/wms/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../static/wms/layui/layui.js"></script>
<script type="text/javascript" src="../static/wms/js/handlebars/handlebars-v4.5.3.js"></script>
<script type="text/javascript" src="../static/wms/js/common.js"></script>
<script type="text/javascript" src="../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../static/layui/layui.js"></script>
<script type="text/javascript" src="../static/js/handlebars/handlebars-v4.5.3.js"></script>
<script type="text/javascript" src="../static/js/common.js"></script>
<script>
  f()
  function f() {
    $.ajax({
      url: baseUrl + "/license/getLicenseDays",
      headers: {'token': localStorage.getItem('token')},
      method: 'POST',
      success: function (res) {
        if (res.code == 200) {
          let days = res.data
          if (days <= 15) {
            showPopup(res.data)
          } else {
            showPopup("");
          }
        }
      }
    });
  }
</script>
<script>
  console.log('%c wcs %c 1.0.0','background-color:rgb(53,73,94);color: #fff;border-radius:2px 0 0 2px;padding:2px 4px;','background-color:rgb(25,190,107);color: #fff;border-radius:0 2px 2px 0;padding:2px 4px;font: 9pt "Apercu Regular", Georgia, "Times New Roman", Times, serif;');
  console.log('%c 中扬立库平台 %c 1.0.0','background-color:rgb(53,73,94);color: #fff;border-radius:2px 0 0 2px;padding:2px 4px;','background-color:rgb(25,190,107);color: #fff;border-radius:0 2px 2px 0;padding:2px 4px;font: 9pt "Apercu Regular", Georgia, "Times New Roman", Times, serif;');
  $(function () {
    if ("" === localStorage.getItem('token')) {
      top.location.href = baseUrl + "/login";
@@ -176,7 +99,7 @@
  });
  layui.config({
    base: baseUrl + "/static/wms/layui/lay/modules/"
    base: baseUrl + "/static/layui/lay/modules/"
  }).extend({
    notice: 'notice/notice',
  }).use(['index', 'element', 'layer', 'admin', 'notice'], function () {
@@ -209,29 +132,11 @@
          var html = template(res);
          $("#menu-main").html(html);
          element.init();
          $("#a-30519").attr({"href":$("#a-30519").attr("lay-href"),"target":"_blank"})
          $("#a-30519").attr({"lay-href":""})
          $("#a-30520").attr({"href":$("#a-30520").attr("lay-href"),"target":"_blank"})
          $("#a-30520").attr({"lay-href":""})
          $("#a-30522").attr({"href":$("#a-30522").attr("lay-href"),"target":"_blank"})
          $("#a-30522").attr({"lay-href":""})
        } else if (res.code === 403) {
          top.location.href = baseUrl + "/login";
        } else {
          layer.msg(res.msg, {icon: 2});
        }
      }
    });
    $.ajax({
      url: baseUrl+"/loginInformation",
      data: {},
      method: 'GET',
      success: function (res) {
        var data = res.data
        $(".copyright-text").text(data.loginCopyrightText);
        $(".loginLogo").attr("src", data.loginLogo);
      }
    });
@@ -243,22 +148,9 @@
        if (res.code == 200) {
          let days = res.data
          if (days <= 30) {
            // 弹出一个简单的提示框
            layer.alert(`
  <div style="font-size: 100px; text-align: center; line-height: 1.8; color: red">
    许可证有效期为:${days}
  </div>
`, {
              area: ['1000px', '800px'],
              btn: '确定',
              btnAlign: 'c', // 按钮居中(默认是右对齐)
              yes: function(index) {
                layer.msg('请联系立库公司商务续约');
                layer.close(index);
              }
            });
            $("#licenseShow").show()
            $("#licenseDays").html(days)
            alert("临时许可有效期:" + days + "天")
          }
        }else {
          top.location.href = baseUrl + "/login";
@@ -268,7 +160,7 @@
    // 默认加载主页
    index.loadHome({
      menuPath: baseUrl+'/views/home/console.html',
      menuPath: baseUrl+'/views/home/navigation.html',
      menuName: '<i class="layui-icon layui-icon-home"></i>'
    });
@@ -294,7 +186,7 @@
    <a><i class="layui-icon {{this.menuIcon}}"></i>&emsp;<cite>{{this.menu}}</cite></a>
    <dl class="layui-nav-child">
      {{#each this.subMenu}}
      <dd><a lay-href="{{this.code}}?resourceId={{this.id}}" id="a-{{this.id}}">{{this.name}}</a></dd>
      <dd><a lay-href="{{this.code}}?resourceId={{this.id}}">{{this.name}}</a></dd>
      {{/each}}
    </dl>
  </li>