diff --git a/pom.xml b/pom.xml
index a0e7089..da8a503 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,6 +40,7 @@
1.6.3
1.6.2
2.9.11
+ 1.33
3.8.1
${env.NACOS_SERVER_NAME}
${env.NACOS_SERVER_PORT}
@@ -295,6 +296,13 @@
${wfc.version}
+
+
+ org.wfc
+ wfc-common-license
+ ${wfc.version}
+
+
org.mapstruct
@@ -343,6 +351,13 @@
IJPay-All
${ijapy.version}
+
+
+
+ de.schlichtherle.truelicense
+ truelicense-core
+ ${turelicense.version}
+
diff --git a/wfc-auth/pom.xml b/wfc-auth/pom.xml
index 66e3269..fed23b8 100644
--- a/wfc-auth/pom.xml
+++ b/wfc-auth/pom.xml
@@ -62,6 +62,11 @@
org.wfc
wfc-api-user
+
+
+ org.wfc
+ wfc-common-license
+
diff --git a/wfc-common/pom.xml b/wfc-common/pom.xml
index ca9c893..909c3c9 100644
--- a/wfc-common/pom.xml
+++ b/wfc-common/pom.xml
@@ -20,6 +20,7 @@
wfc-common-datasource
wfc-common-mail
wfc-common-mybatis
+ wfc-common-license
wfc-common
diff --git a/wfc-common/wfc-common-license/pom.xml b/wfc-common/wfc-common-license/pom.xml
new file mode 100644
index 0000000..b0df6a3
--- /dev/null
+++ b/wfc-common/wfc-common-license/pom.xml
@@ -0,0 +1,32 @@
+
+ 4.0.0
+
+ org.wfc
+ wfc-common
+ 1.0.13
+
+
+ wfc-common-license
+
+ wfc-common-license服务
+
+
+
+
+ org.wfc
+ wfc-common-core
+
+
+
+ org.springframework
+ spring-webmvc
+
+
+
+ de.schlichtherle.truelicense
+ truelicense-core
+
+
+
+
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/CustomKeyStoreParam.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/CustomKeyStoreParam.java
new file mode 100644
index 0000000..cd6dbd6
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/CustomKeyStoreParam.java
@@ -0,0 +1,64 @@
+package org.wfc.common.license;
+
+import de.schlichtherle.license.AbstractKeyStoreParam;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中
+ *
+ */
+public class CustomKeyStoreParam extends AbstractKeyStoreParam {
+
+ /**
+ * 公钥/私钥在磁盘上的存储路径
+ */
+ private String storePath;
+ private String alias;
+ private String storePwd;
+ private String keyPwd;
+
+ public CustomKeyStoreParam(Class clazz, String resource,String alias,String storePwd,String keyPwd) {
+ super(clazz, resource);
+ this.storePath = resource;
+ this.alias = alias;
+ this.storePwd = storePwd;
+ this.keyPwd = keyPwd;
+ }
+
+
+ @Override
+ public String getAlias() {
+ return alias;
+ }
+
+ @Override
+ public String getStorePwd() {
+ return storePwd;
+ }
+
+ @Override
+ public String getKeyPwd() {
+ return keyPwd;
+ }
+
+ /**
+ * 复写de.schlichtherle.license.AbstractKeyStoreParam的getStream()方法
+ * 用于将公私钥存储文件存放到其他磁盘位置而不是项目中
+ * @param
+ * @return java.io.InputStream
+ */
+ @Override
+ public InputStream getStream() throws IOException {
+ final InputStream in = new FileInputStream(new File(storePath));
+ if (null == in){
+ throw new FileNotFoundException(storePath);
+ }
+
+ return in;
+ }
+}
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/CustomLicenseManager.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/CustomLicenseManager.java
new file mode 100644
index 0000000..ff86a0f
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/CustomLicenseManager.java
@@ -0,0 +1,265 @@
+package org.wfc.common.license;
+
+import de.schlichtherle.license.LicenseContent;
+import de.schlichtherle.license.LicenseContentException;
+import de.schlichtherle.license.LicenseManager;
+import de.schlichtherle.license.LicenseNotary;
+import de.schlichtherle.license.LicenseParam;
+import de.schlichtherle.license.NoLicenseInstalledException;
+import de.schlichtherle.xml.GenericCertificate;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.wfc.common.license.domain.LicenseCheckModel;
+import org.wfc.common.license.serverinfo.AbstractServerInfos;
+import org.wfc.common.license.serverinfo.LinuxServerInfos;
+import org.wfc.common.license.serverinfo.WindowsServerInfos;
+
+import java.beans.XMLDecoder;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 自定义LicenseManager,用于增加额外的服务器硬件信息校验
+ */
+@Slf4j
+public class CustomLicenseManager extends LicenseManager{
+
+ //XML编码
+ private static final String XML_CHARSET = "UTF-8";
+ //默认BUFSIZE
+ private static final int DEFAULT_BUFSIZE = 8 * 1024;
+
+ public CustomLicenseManager() {
+
+ }
+
+ public CustomLicenseManager(LicenseParam param) {
+ super(param);
+ }
+
+ /**
+ * 复写create方法
+ * @return byte[]
+ */
+ @Override
+ protected synchronized byte[] create(
+ LicenseContent content,
+ LicenseNotary notary)
+ throws Exception {
+ initialize(content);
+ this.validateCreate(content);
+ final GenericCertificate certificate = notary.sign(content);
+ return getPrivacyGuard().cert2key(certificate);
+ }
+
+ /**
+ * 复写install方法,其中validate方法调用本类中的validate方法,校验IP地址、Mac地址等其他信息
+ * @param
+ * @return de.schlichtherle.license.LicenseContent
+ */
+ @Override
+ protected synchronized LicenseContent install(
+ final byte[] key,
+ final LicenseNotary notary)
+ throws Exception {
+ final GenericCertificate certificate = getPrivacyGuard().key2cert(key);
+
+ notary.verify(certificate);
+ final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded());
+ this.validate(content);
+ setLicenseKey(key);
+ setCertificate(certificate);
+
+ return content;
+ }
+
+ /**
+ * 复写verify方法,调用本类中的validate方法,校验IP地址、Mac地址等其他信息
+ * @param
+ * @return de.schlichtherle.license.LicenseContent
+ */
+ @Override
+ protected synchronized LicenseContent verify(final LicenseNotary notary)
+ throws Exception {
+ GenericCertificate certificate = getCertificate();
+
+ // Load license key from preferences,
+ final byte[] key = getLicenseKey();
+ if (null == key){
+ throw new NoLicenseInstalledException(getLicenseParam().getSubject());
+ }
+
+ certificate = getPrivacyGuard().key2cert(key);
+ notary.verify(certificate);
+ final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded());
+ this.validate(content);
+ setCertificate(certificate);
+
+ return content;
+ }
+
+ /**
+ * 校验生成证书的参数信息
+ * @param content 证书正文
+ */
+ protected synchronized void validateCreate(final LicenseContent content)
+ throws LicenseContentException {
+ final LicenseParam param = getLicenseParam();
+
+ final Date now = new Date();
+ final Date notBefore = content.getNotBefore();
+ final Date notAfter = content.getNotAfter();
+ if (null != notAfter && now.after(notAfter)){
+ throw new LicenseContentException("证书失效时间不能早于当前时间");
+ }
+ if (null != notBefore && null != notAfter && notAfter.before(notBefore)){
+ throw new LicenseContentException("证书生效时间不能晚于证书失效时间");
+ }
+ final String consumerType = content.getConsumerType();
+ if (null == consumerType){
+ throw new LicenseContentException("用户类型不能为空");
+ }
+ }
+
+
+ /**
+ * 复写validate方法,增加IP地址、Mac地址等其他信息校验
+ * @param content LicenseContent
+ */
+ @Override
+ protected synchronized void validate(final LicenseContent content)
+ throws LicenseContentException {
+ //1. 首先调用父类的validate方法
+ super.validate(content);
+
+ //2. 然后校验自定义的License参数
+ //License中可被允许的参数信息
+ LicenseCheckModel expectedCheckModel = (LicenseCheckModel) content.getExtra();
+ if(expectedCheckModel != null){
+ //当前服务器真实的参数信息
+ LicenseCheckModel serverCheckModel = getServerInfos();
+
+ if(serverCheckModel != null){
+ //校验IP地址
+ if(!checkIpAddress(expectedCheckModel.getIpAddress(),serverCheckModel.getIpAddress())){
+ throw new LicenseContentException("当前服务器的IP没在授权范围内");
+ }
+
+ //校验Mac地址
+ if(!checkIpAddress(expectedCheckModel.getMacAddress(),serverCheckModel.getMacAddress())){
+ throw new LicenseContentException("当前服务器的Mac地址没在授权范围内");
+ }
+
+ //校验主板序列号
+ if(!checkSerial(expectedCheckModel.getMainBoardSerial(),serverCheckModel.getMainBoardSerial())){
+ throw new LicenseContentException("当前服务器的主板序列号没在授权范围内");
+ }
+
+ //校验CPU序列号
+ if(!checkSerial(expectedCheckModel.getCpuSerial(),serverCheckModel.getCpuSerial())){
+ throw new LicenseContentException("当前服务器的CPU序列号没在授权范围内");
+ }
+ }else{
+ throw new LicenseContentException("不能获取服务器硬件信息");
+ }
+ }
+ }
+
+
+ /**
+ * 重写XMLDecoder解析XML
+ * @param encoded XML类型字符串
+ * @return java.lang.Object
+ */
+ private Object load(String encoded){
+ BufferedInputStream inputStream = null;
+ XMLDecoder decoder = null;
+ try {
+ inputStream = new BufferedInputStream(new ByteArrayInputStream(encoded.getBytes(XML_CHARSET)));
+
+ decoder = new XMLDecoder(new BufferedInputStream(inputStream, DEFAULT_BUFSIZE),null,null);
+
+ return decoder.readObject();
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if(decoder != null){
+ decoder.close();
+ }
+ if(inputStream != null){
+ inputStream.close();
+ }
+ } catch (Exception e) {
+ log.error("XMLDecoder解析XML失败",e);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * 获取当前服务器需要额外校验的License参数
+ * @return demo.LicenseCheckModel
+ */
+ private LicenseCheckModel getServerInfos(){
+ //操作系统类型
+ String osName = System.getProperty("os.name").toLowerCase();
+ AbstractServerInfos abstractServerInfos = null;
+
+ //根据不同操作系统类型选择不同的数据获取方法
+ if (osName.startsWith("windows")) {
+ abstractServerInfos = new WindowsServerInfos();
+ } else if (osName.startsWith("linux")) {
+ abstractServerInfos = new LinuxServerInfos();
+ }else{//其他服务器类型
+ abstractServerInfos = new LinuxServerInfos();
+ }
+
+ return abstractServerInfos.getServerInfos();
+ }
+
+ /**
+ * 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内
+ * 如果存在IP在可被允许的IP/Mac地址范围内,则返回true
+ * @return boolean
+ */
+ private boolean checkIpAddress(List expectedList,List serverList){
+ if(expectedList != null && expectedList.size() > 0){
+ if(serverList != null && serverList.size() > 0){
+ for(String expected : expectedList){
+ if(serverList.contains(expected.trim())){
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }else {
+ return true;
+ }
+ }
+
+ /**
+ * 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内
+ * @param
+ * @return boolean
+ */
+ private boolean checkSerial(String expectedSerial,String serverSerial){
+ if(StringUtils.isNotBlank(expectedSerial)){
+ if(StringUtils.isNotBlank(serverSerial)){
+ if(expectedSerial.equals(serverSerial)){
+ return true;
+ }
+ }
+
+ return false;
+ }else{
+ return true;
+ }
+ }
+
+}
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/LicenseManagerHolder.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/LicenseManagerHolder.java
new file mode 100644
index 0000000..d180912
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/LicenseManagerHolder.java
@@ -0,0 +1,25 @@
+package org.wfc.common.license;
+
+import de.schlichtherle.license.LicenseManager;
+import de.schlichtherle.license.LicenseParam;
+
+/**
+ * de.schlichtherle.license.LicenseManager的单例
+ */
+public class LicenseManagerHolder {
+
+ private static volatile LicenseManager LICENSE_MANAGER;
+
+ public static LicenseManager getInstance(LicenseParam param){
+ if(LICENSE_MANAGER == null){
+ synchronized (LicenseManagerHolder.class){
+ if(LICENSE_MANAGER == null){
+ LICENSE_MANAGER = new CustomLicenseManager(param);
+ }
+ }
+ }
+
+ return LICENSE_MANAGER;
+ }
+
+}
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/LicenseVerify.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/LicenseVerify.java
new file mode 100644
index 0000000..dd48d62
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/LicenseVerify.java
@@ -0,0 +1,88 @@
+package org.wfc.common.license;
+
+import de.schlichtherle.license.CipherParam;
+import de.schlichtherle.license.DefaultCipherParam;
+import de.schlichtherle.license.DefaultLicenseParam;
+import de.schlichtherle.license.KeyStoreParam;
+import de.schlichtherle.license.LicenseContent;
+import de.schlichtherle.license.LicenseManager;
+import de.schlichtherle.license.LicenseParam;
+import lombok.extern.slf4j.Slf4j;
+import org.wfc.common.license.domain.LicenseVerifyParam;
+
+import java.io.File;
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.prefs.Preferences;
+
+/**
+ * License校验类
+ */
+@Slf4j
+public class LicenseVerify {
+ /**
+ * 安装License证书
+ */
+ public synchronized LicenseContent install(LicenseVerifyParam param){
+ LicenseContent result = null;
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ //1. 安装证书
+ try{
+ LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param));
+ licenseManager.uninstall();
+
+ result = licenseManager.install(new File(param.getLicensePath()));
+ log.info(MessageFormat.format("证书安装成功,证书有效期:{0} - {1}",format.format(result.getNotBefore()),format.format(result.getNotAfter())));
+ }catch (Exception e){
+ log.error("证书安装失败!",e);
+ }
+
+ return result;
+ }
+
+ /**
+ * 校验License证书
+ * @return boolean
+ */
+ public boolean verify(){
+ LicenseManager licenseManager = LicenseManagerHolder.getInstance(null);
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ //2. 校验证书
+ try {
+ LicenseContent licenseContent = licenseManager.verify();
+// System.out.println(licenseContent.getSubject());
+
+ log.info(MessageFormat.format("证书校验通过,证书有效期:{0} - {1}",format.format(licenseContent.getNotBefore()),format.format(licenseContent.getNotAfter())));
+ return true;
+ }catch (Exception e){
+ log.error("证书校验失败!",e);
+ return false;
+ }
+ }
+
+ /**
+ * 初始化证书生成参数
+ * @param param License校验类需要的参数
+ * @return de.schlichtherle.license.LicenseParam
+ */
+ private LicenseParam initLicenseParam(LicenseVerifyParam param){
+ Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class);
+
+ CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
+
+ KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class
+ ,param.getPublicKeysStorePath()
+ ,param.getPublicAlias()
+ ,param.getStorePass()
+ ,null);
+
+ return new DefaultLicenseParam(param.getSubject()
+ ,preferences
+ ,publicStoreParam
+ ,cipherParam);
+ }
+
+}
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/domain/LicenseCheckModel.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/domain/LicenseCheckModel.java
new file mode 100644
index 0000000..e12dc6d
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/domain/LicenseCheckModel.java
@@ -0,0 +1,73 @@
+package org.wfc.common.license.domain;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 自定义需要校验的License参数
+ */
+public class LicenseCheckModel implements Serializable{
+
+ private static final long serialVersionUID = 8600137500316662317L;
+ /**
+ * 可被允许的IP地址
+ */
+ private List ipAddress;
+
+ /**
+ * 可被允许的MAC地址
+ */
+ private List macAddress;
+
+ /**
+ * 可被允许的CPU序列号
+ */
+ private String cpuSerial;
+
+ /**
+ * 可被允许的主板序列号
+ */
+ private String mainBoardSerial;
+
+ public List getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(List ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public List getMacAddress() {
+ return macAddress;
+ }
+
+ public void setMacAddress(List macAddress) {
+ this.macAddress = macAddress;
+ }
+
+ public String getCpuSerial() {
+ return cpuSerial;
+ }
+
+ public void setCpuSerial(String cpuSerial) {
+ this.cpuSerial = cpuSerial;
+ }
+
+ public String getMainBoardSerial() {
+ return mainBoardSerial;
+ }
+
+ public void setMainBoardSerial(String mainBoardSerial) {
+ this.mainBoardSerial = mainBoardSerial;
+ }
+
+ @Override
+ public String toString() {
+ return "LicenseCheckModel{" +
+ "ipAddress=" + ipAddress +
+ ", macAddress=" + macAddress +
+ ", cpuSerial='" + cpuSerial + '\'' +
+ ", mainBoardSerial='" + mainBoardSerial + '\'' +
+ '}';
+ }
+}
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/domain/LicenseVerifyParam.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/domain/LicenseVerifyParam.java
new file mode 100644
index 0000000..d37ec5f
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/domain/LicenseVerifyParam.java
@@ -0,0 +1,95 @@
+package org.wfc.common.license.domain;
+
+/**
+ * License校验类需要的参数
+ */
+public class LicenseVerifyParam {
+
+ /**
+ * 证书subject
+ */
+ private String subject;
+
+ /**
+ * 公钥别称
+ */
+ private String publicAlias;
+
+ /**
+ * 访问公钥库的密码
+ */
+ private String storePass;
+
+ /**
+ * 证书生成路径
+ */
+ private String licensePath;
+
+ /**
+ * 密钥库存储路径
+ */
+ private String publicKeysStorePath;
+
+ public LicenseVerifyParam() {
+
+ }
+
+ public LicenseVerifyParam(String subject, String publicAlias, String storePass, String licensePath, String publicKeysStorePath) {
+ this.subject = subject;
+ this.publicAlias = publicAlias;
+ this.storePass = storePass;
+ this.licensePath = licensePath;
+ this.publicKeysStorePath = publicKeysStorePath;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ public String getPublicAlias() {
+ return publicAlias;
+ }
+
+ public void setPublicAlias(String publicAlias) {
+ this.publicAlias = publicAlias;
+ }
+
+ public String getStorePass() {
+ return storePass;
+ }
+
+ public void setStorePass(String storePass) {
+ this.storePass = storePass;
+ }
+
+ public String getLicensePath() {
+ return licensePath;
+ }
+
+ public void setLicensePath(String licensePath) {
+ this.licensePath = licensePath;
+ }
+
+ public String getPublicKeysStorePath() {
+ return publicKeysStorePath;
+ }
+
+ public void setPublicKeysStorePath(String publicKeysStorePath) {
+ this.publicKeysStorePath = publicKeysStorePath;
+ }
+
+ @Override
+ public String toString() {
+ return "LicenseVerifyParam{" +
+ "subject='" + subject + '\'' +
+ ", publicAlias='" + publicAlias + '\'' +
+ ", storePass='" + storePass + '\'' +
+ ", licensePath='" + licensePath + '\'' +
+ ", publicKeysStorePath='" + publicKeysStorePath + '\'' +
+ '}';
+ }
+}
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/interceptor/LicenseCheckInterceptor.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/interceptor/LicenseCheckInterceptor.java
new file mode 100644
index 0000000..c0e8b14
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/interceptor/LicenseCheckInterceptor.java
@@ -0,0 +1,62 @@
+package org.wfc.common.license.interceptor;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.wfc.common.license.LicenseVerify;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 使用拦截器拦截请求,验证证书的可用性
+ *
+ */
+@Slf4j
+@Component
+public class LicenseCheckInterceptor implements HandlerInterceptor {
+
+ /**
+ * 进入controller层之前拦截请求
+ * @param request
+ * @param response
+ * @param handler
+ * @return
+ * @throws Exception
+ */
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ LicenseVerify licenseVerify = new LicenseVerify();
+
+ //校验证书是否有效
+ boolean verifyResult = licenseVerify.verify();
+
+ if(verifyResult){
+ log.info("验证成功,证书可用");
+ return true;
+ }else{
+ log.info("验证失败,证书无效");
+ response.setCharacterEncoding("utf-8");
+ Map result = new HashMap<>(1);
+ result.put("result","您的证书无效,请核查服务器是否取得授权或重新申请证书!");
+
+ outputError(response, "Your License is invalid");
+
+ return false;
+ }
+ }
+
+ // 输出错误信息
+ private void outputError(HttpServletResponse response, String errorMsg) throws IOException {
+ response.setContentType("application/json;charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ out.write("{\"msg\": \"" + errorMsg + "\", \"code\": 500}");
+ out.flush();
+ out.close();
+ }
+
+}
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/interceptor/LicenseInterceptorConfig.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/interceptor/LicenseInterceptorConfig.java
new file mode 100644
index 0000000..08b545e
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/interceptor/LicenseInterceptorConfig.java
@@ -0,0 +1,28 @@
+package org.wfc.common.license.interceptor;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.annotation.Resource;
+
+/**
+ * @Description: 拦截器配置
+ */
+@Configuration
+public class LicenseInterceptorConfig implements WebMvcConfigurer {
+
+ @Resource
+ private LicenseCheckInterceptor licenseCheckInterceptor;
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+
+ //添加要拦截的url
+ registry.addInterceptor(licenseCheckInterceptor)
+ // 拦截的路径
+ .addPathPatterns("/**");
+ // 放行的路径
+// .excludePathPatterns("/admin/login");
+ }
+}
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/runner/LicenseCheckRunner.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/runner/LicenseCheckRunner.java
new file mode 100644
index 0000000..b1fdaf5
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/runner/LicenseCheckRunner.java
@@ -0,0 +1,163 @@
+package org.wfc.common.license.runner;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+import org.wfc.common.license.LicenseVerify;
+import org.wfc.common.license.domain.LicenseVerifyParam;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+@Data
+public class LicenseCheckRunner implements ApplicationRunner {
+
+ @Autowired
+ private LicenseProperties licenseProperties;
+
+ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+ private ScheduledFuture> scheduledFuture;
+
+
+ // 启动定时任务
+ public void startTimer() {
+ scheduledFuture = scheduler.scheduleAtFixedRate(this::timer, 0, 60, TimeUnit.SECONDS);
+ }
+ // 停止定时任务
+ public void stopTimer() {
+ if (scheduledFuture != null) {
+ scheduledFuture.cancel(false);
+ }
+ }
+ /**
+ * 文件唯一身份标识 == 相当于人类的指纹一样
+ */
+ private static String md5 = "";
+ private static boolean isLoad = true;
+
+ public static void LicenseVerifyInstall(LicenseProperties licenseVerifyProperties) {
+ new LicenseCheckRunner(licenseVerifyProperties);
+ }
+
+ public LicenseCheckRunner(LicenseProperties licenseProperties) {
+ // startTimer();
+ this.licenseProperties = licenseProperties;
+ if (StrUtil.isNotEmpty(licenseProperties.getLicensePath())) {
+ install();
+ try {
+ String readMd5 = getMd5(licenseProperties.getLicensePath());
+ isLoad = true;
+ if (LicenseCheckRunner.md5 == null || "".equals(LicenseCheckRunner.md5)) {
+ LicenseCheckRunner.md5 = readMd5;
+ }
+ } catch (Exception e) {
+
+ }
+ }
+ }
+
+
+ /**
+ * 5秒检测一次,不能太快也不能太慢
+ */
+ protected void timer() {
+ if (!isLoad) {
+ return;
+ }
+ String readMd5 = null;
+ try {
+ readMd5 = getMd5(licenseProperties.getLicensePath());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ // 不相等,说明lic变化了
+ if (!readMd5.equals(LicenseCheckRunner.md5)) {
+ log.info("定时任务:检测到证书有变化");
+ install();
+ LicenseCheckRunner.md5 = readMd5;
+ }
+
+ }
+
+ private void install() {
+ log.info("++++++++ 开始安装证书 ++++++++");
+ LicenseVerifyParam param = new LicenseVerifyParam();
+ param.setSubject(licenseProperties.getSubject());
+ param.setPublicAlias(licenseProperties.getPublicAlias());
+ param.setStorePass(licenseProperties.getStorePass());
+ // 相对路径resources资源目录
+ String resourcePath = getClass().getClassLoader().getResource("").getPath();
+ // 证书地址
+// param.setLicensePath(resourcePath + "license.lic");
+ param.setLicensePath(licenseProperties.getLicensePath());
+ // 公钥地址
+// param.setPublicKeysStorePath(resourcePath + "publicCerts.keystore");
+ param.setPublicKeysStorePath(licenseProperties.getPublicKeysStorePath());
+ // 安装证书
+ LicenseVerify licenseVerify = new LicenseVerify();
+ licenseVerify.install(param);
+ log.info("++++++++ 证书安装结束 ++++++++");
+ }
+
+ /**
+ * 获取文件的md5
+ */
+ public String getMd5(String filePath) throws Exception {
+ File file;
+ String md5 = "";
+ try {
+ file = new File(filePath);
+ if (file.exists()) {
+ FileInputStream is = new FileInputStream(file);
+ byte[] data = new byte[is.available()];
+ is.read(data);
+ md5 = calculateMD5(data);
+ is.close();
+ }
+ } catch (FileNotFoundException e) {
+ log.error("找不到证书文件 {}", e.getMessage());
+ }
+ return md5;
+ }
+
+ public static String calculateMD5(byte[] data) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] hashBytes = md.digest(data);
+
+ StringBuilder hexString = new StringBuilder();
+ for (byte b : hashBytes) {
+ String hex = Integer.toHexString(0xff & b);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+
+ return hexString.toString();
+ } catch (NoSuchAlgorithmException e) {
+ log.error("证书文件计算MD5失败 {}", e.getMessage());
+ return null;
+ }
+ }
+
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ install();
+ startTimer();
+ }
+}
\ No newline at end of file
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/runner/LicenseProperties.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/runner/LicenseProperties.java
new file mode 100644
index 0000000..7066460
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/runner/LicenseProperties.java
@@ -0,0 +1,39 @@
+package org.wfc.common.license.runner;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author: cyc
+ * @since: 2025-04-11
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "license")
+public class LicenseProperties {
+ /**
+ * 证书subject
+ */
+ private String subject;
+
+ /**
+ * 公钥别称
+ */
+ private String publicAlias;
+
+ /**
+ * 访问公钥库的密码
+ */
+ private String storePass;
+
+ /**
+ * 证书生成路径
+ */
+ private String licensePath;
+
+ /**
+ * 密钥库存储路径
+ */
+ private String publicKeysStorePath;
+}
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/serverinfo/AbstractServerInfos.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/serverinfo/AbstractServerInfos.java
new file mode 100644
index 0000000..ba6cfeb
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/serverinfo/AbstractServerInfos.java
@@ -0,0 +1,120 @@
+package org.wfc.common.license.serverinfo;
+
+import lombok.extern.slf4j.Slf4j;
+import org.wfc.common.license.domain.LicenseCheckModel;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * 用于获取客户服务器的基本信息,如:IP、Mac地址、CPU序列号、主板序列号等
+ */
+@Slf4j
+public abstract class AbstractServerInfos {
+
+ /**
+ * 组装需要额外校验的License参数
+ * @return demo.LicenseCheckModel
+ */
+ public LicenseCheckModel getServerInfos(){
+ LicenseCheckModel result = new LicenseCheckModel();
+
+ try {
+ result.setIpAddress(this.getIpAddress());
+ result.setMacAddress(this.getMacAddress());
+ result.setCpuSerial(this.getCPUSerial());
+ result.setMainBoardSerial(this.getMainBoardSerial());
+ }catch (Exception e){
+ log.error("获取服务器硬件信息失败",e);
+ }
+
+ return result;
+ }
+
+ /**
+ * 获取IP地址
+ * @return java.util.List
+ */
+ protected abstract List getIpAddress() throws Exception;
+
+ /**
+ * 获取Mac地址
+ * @return java.util.List
+ */
+ protected abstract List getMacAddress() throws Exception;
+
+ /**
+ * 获取CPU序列号
+ * @return java.lang.String
+ */
+ protected abstract String getCPUSerial() throws Exception;
+
+ /**
+ * 获取主板序列号
+ * @return java.lang.String
+ */
+ protected abstract String getMainBoardSerial() throws Exception;
+
+ /**
+ * 获取当前服务器所有符合条件的InetAddress
+ * @return java.util.List
+ */
+ protected List getLocalAllInetAddress() throws Exception {
+ List result = new ArrayList<>(4);
+
+ // 遍历所有的网络接口
+ for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) {
+ NetworkInterface iface = (NetworkInterface) networkInterfaces.nextElement();
+ // 在所有的接口下再遍历IP
+ for (Enumeration inetAddresses = iface.getInetAddresses(); inetAddresses.hasMoreElements(); ) {
+ InetAddress inetAddr = (InetAddress) inetAddresses.nextElement();
+
+ //排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址
+ if(!inetAddr.isLoopbackAddress() /*&& !inetAddr.isSiteLocalAddress()*/
+ && !inetAddr.isLinkLocalAddress() && !inetAddr.isMulticastAddress()){
+ result.add(inetAddr);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * 获取某个网络接口的Mac地址
+ * @param
+ * @return void
+ */
+ protected String getMacByInetAddress(InetAddress inetAddr){
+ try {
+ byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress();
+ StringBuffer stringBuffer = new StringBuffer();
+
+ for(int i=0;i getIpAddress() throws Exception {
+ List result = null;
+
+ //获取所有网络接口
+ List 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 getMacAddress() throws Exception {
+ List result = null;
+
+ //1. 获取所有网络接口
+ List 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()));
+
+ String line = reader.readLine().trim();
+ if(StringUtils.isNotBlank(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()));
+
+ String line = reader.readLine().trim();
+ if(StringUtils.isNotBlank(line)){
+ serialNumber = line;
+ }
+
+ reader.close();
+ return serialNumber;
+ }
+}
diff --git a/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/serverinfo/WindowsServerInfos.java b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/serverinfo/WindowsServerInfos.java
new file mode 100644
index 0000000..9761cab
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/java/org/wfc/common/license/serverinfo/WindowsServerInfos.java
@@ -0,0 +1,85 @@
+package org.wfc.common.license.serverinfo;
+
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Scanner;
+import java.util.stream.Collectors;
+
+/**
+ * 用于获取客户Windows服务器的基本信息
+ */
+public class WindowsServerInfos extends AbstractServerInfos{
+
+ @Override
+ protected List getIpAddress() throws Exception {
+ List result = null;
+
+ //获取所有网络接口
+ List 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 getMacAddress() throws Exception {
+ List result = null;
+
+ //1. 获取所有网络接口
+ List 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 = "";
+
+ //使用WMIC获取CPU序列号
+ Process process = Runtime.getRuntime().exec("wmic cpu get processorid");
+ process.getOutputStream().close();
+ Scanner scanner = new Scanner(process.getInputStream());
+
+ if(scanner != null && scanner.hasNext()){
+ scanner.next();
+ }
+
+ if(scanner.hasNext()){
+ serialNumber = scanner.next().trim();
+ }
+
+ scanner.close();
+ return serialNumber;
+ }
+
+ @Override
+ protected String getMainBoardSerial() throws Exception {
+ //序列号
+ String serialNumber = "";
+
+ //使用WMIC获取主板序列号
+ Process process = Runtime.getRuntime().exec("wmic baseboard get serialnumber");
+ process.getOutputStream().close();
+ Scanner scanner = new Scanner(process.getInputStream());
+
+ if(scanner != null && scanner.hasNext()){
+ scanner.next();
+ }
+
+ if(scanner.hasNext()){
+ serialNumber = scanner.next().trim();
+ }
+
+ scanner.close();
+ return serialNumber;
+ }
+}
diff --git a/wfc-common/wfc-common-license/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/wfc-common/wfc-common-license/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..3ac4935
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,4 @@
+org.wfc.common.license.runner.LicenseProperties
+org.wfc.common.license.runner.LicenseCheckRunner
+org.wfc.common.license.interceptor.LicenseCheckInterceptor
+org.wfc.common.license.interceptor.LicenseInterceptorConfig
\ No newline at end of file
diff --git a/wfc-common/wfc-common-license/src/main/resources/application.yml b/wfc-common/wfc-common-license/src/main/resources/application.yml
new file mode 100644
index 0000000..6fc2d6d
--- /dev/null
+++ b/wfc-common/wfc-common-license/src/main/resources/application.yml
@@ -0,0 +1,6 @@
+license:
+ subject: license_demo
+ publicAlias: publicCert
+ storePass: public_password1234
+ licensePath: D:/workspace/gitee/spring-boot2-license/license/license.lic
+ publicKeysStorePath: D:/workspace/gitee/spring-boot2-license/license/publicCerts.keystore
\ No newline at end of file
diff --git a/wfc-modules/wfc-system/pom.xml b/wfc-modules/wfc-system/pom.xml
index b6dd658..e3519ba 100644
--- a/wfc-modules/wfc-system/pom.xml
+++ b/wfc-modules/wfc-system/pom.xml
@@ -89,6 +89,11 @@
org.mapstruct
mapstruct
+
+
+ org.wfc
+ wfc-common-license
+
diff --git a/wfc-modules/wfc-user/pom.xml b/wfc-modules/wfc-user/pom.xml
index bc5e3b4..f1bfc17 100644
--- a/wfc-modules/wfc-user/pom.xml
+++ b/wfc-modules/wfc-user/pom.xml
@@ -103,6 +103,11 @@
org.wfc
wfc-common-mail
+
+
+ org.wfc
+ wfc-common-license
+