diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/LicenseController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/LicenseController.java new file mode 100644 index 0000000..b45fd73 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/LicenseController.java @@ -0,0 +1,56 @@ +package com.ruoyi.web.controller.system; + +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.system.service.ILicenseService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; + +@RestController +@RequestMapping("/license") +public class LicenseController { + + @Autowired + private ILicenseService licenseService; + + @PostMapping("/activate") + public AjaxResult activateLicense(@RequestParam String licenseKey, + @RequestParam String expireDate) { + Date expire = DateUtils.parseDate(expireDate); + boolean success = licenseService.activateLicense(licenseKey, expire); + return success ? AjaxResult.success("授权激活成功") + : AjaxResult.error("授权密钥无效"); + } + + @PostMapping("/getLicenseKey") + public AjaxResult getLicenseKey(String expire) { + Date expireDate = DateUtils.parseDate(expire); + String licenseKey = licenseService.generateLicenseKey(expireDate); + return AjaxResult.success(licenseKey); + } + + @GetMapping("/check") + public AjaxResult checkLicense() { + boolean isValid = licenseService.checkLicense(); + Date expireDate = licenseService.getExpireDate(); + return AjaxResult.success("检查成功", new LicenseStatus(isValid, expireDate)); + } + + static class LicenseStatus { + private boolean valid; + private Date expireDate; + private boolean expired; + + public LicenseStatus(boolean valid, Date expireDate) { + this.valid = valid; + this.expireDate = expireDate; + this.expired = expireDate != null && expireDate.before(new Date()); + } + + // getters and setters + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/LicenseException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/LicenseException.java new file mode 100644 index 0000000..465b568 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/LicenseException.java @@ -0,0 +1,28 @@ +package com.ruoyi.common.exception; + +/** + * 系统授权异常类 + */ +public class LicenseException extends RuntimeException { + private static final long serialVersionUID = 1L; + + // 错误码字段(可扩展) + private Integer code; + + + public LicenseException(String message) { + super(message); + } + + public LicenseException(String message, Throwable cause) { + super(message, cause); + } + + public LicenseException(Integer code, String message) { + super(message); + this.code = code; + } + public Integer getCode() { + return code; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java index 0f48b11..350a02e 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java @@ -1,6 +1,8 @@ package com.ruoyi.framework.config; import java.util.concurrent.TimeUnit; + +import com.ruoyi.framework.interceptor.LicenseInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -25,6 +27,8 @@ public class ResourcesConfig implements WebMvcConfigurer { @Autowired private RepeatSubmitInterceptor repeatSubmitInterceptor; + @Autowired + private LicenseInterceptor licenseInterceptor; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) @@ -46,6 +50,9 @@ public class ResourcesConfig implements WebMvcConfigurer public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); + registry.addInterceptor(licenseInterceptor) + .addPathPatterns("/**") + .excludePathPatterns("/login", "/license/**"); } /** diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index 511842b..088e730 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -111,7 +111,8 @@ public class SecurityConfig .authorizeHttpRequests((requests) -> { permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll()); // 对于登录login 注册register 验证码captchaImage 允许匿名访问 - requests.antMatchers("/login", "/register", "/captchaImage").permitAll() + // requests.antMatchers("/login", "/register", "/captchaImage").permitAll() + requests.antMatchers("/login", "/register", "/captchaImage", "/license/**").permitAll() // 静态资源,可匿名访问 .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/LicenseInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/LicenseInterceptor.java new file mode 100644 index 0000000..6ce7d5b --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/LicenseInterceptor.java @@ -0,0 +1,54 @@ +package com.ruoyi.framework.interceptor; + +import com.ruoyi.common.exception.LicenseException; +import com.ruoyi.system.service.ILicenseService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Date; + +@Component +public class LicenseInterceptor implements HandlerInterceptor { + + @Autowired + private ILicenseService licenseService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // 白名单路径直接放行 + if (isExcludePath(request.getRequestURI())) { + return true; + } + + // 验证授权状态 + if (!licenseService.checkLicense()) { + throw new LicenseException("系统授权已过期,请更新授权!"); + } + + // 检查剩余有效期(提前30天提醒) + Date expireDate = licenseService.getExpireDate(); + if (expireDate != null && isNearExpire(expireDate)) { + Long licw = getRemainingDays(expireDate); + response.setHeader("License-Warning", licw.toString());//系统到期天数存到header中,用于前端获取 + // response.setHeader("Access-Control-Expose-Headers", "License-Warning"); + } + return true; + } + + private boolean isExcludePath(String uri) { + return uri.startsWith("/license") || + uri.startsWith("/login") || + uri.startsWith("/static"); + } + + private boolean isNearExpire(Date expireDate) { + long diff = expireDate.getTime() - System.currentTimeMillis(); + return diff > 0 && diff < 30L * 24 * 60 * 60 * 1000; + } + + private long getRemainingDays(Date expireDate) { + return (expireDate.getTime() - System.currentTimeMillis()) / (1000 * 60 * 60 * 24); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLicense.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLicense.java new file mode 100644 index 0000000..a486261 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLicense.java @@ -0,0 +1,42 @@ +package com.ruoyi.system.domain; + +import java.util.Date; +public class SysLicense { + private Long id; + private String licenseKey; + private Date expireDate; + private Date createTime; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLicenseKey() { + return licenseKey; + } + + public void setLicenseKey(String licenseKey) { + this.licenseKey = licenseKey; + } + + public Date getExpireDate() { + return expireDate; + } + + public void setExpireDate(Date expireDate) { + this.expireDate = expireDate; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + // getter/setter省略 +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLicenseMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLicenseMapper.java new file mode 100644 index 0000000..df133e9 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLicenseMapper.java @@ -0,0 +1,31 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.system.domain.SysLicense; +import java.util.Date; + +public interface SysLicenseMapper { + /** + * 查询最新授权信息 + */ + SysLicense selectLicense(); + + /** + * 插入授权记录 + * @param license 授权对象 + * @return 影响行数 + */ + int insertLicense(SysLicense license); + + /** + * 删除所有授权记录 + * @return 影响行数 + */ + int deleteLicense(); + + /** + * 检查授权有效性 + * @param currentDate 当前日期 + * @return 有效记录数 + */ + int checkValidLicense(Date currentDate); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ILicenseService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ILicenseService.java new file mode 100644 index 0000000..86b50bf --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ILicenseService.java @@ -0,0 +1,11 @@ +package com.ruoyi.system.service; + +import java.util.Date; + +public interface ILicenseService { + boolean activateLicense(String licenseKey, Date expireDate); + boolean checkLicense(); + Date getExpireDate(); + + String generateLicenseKey(Date expireDate); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LicenseServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LicenseServiceImpl.java new file mode 100644 index 0000000..24aa936 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LicenseServiceImpl.java @@ -0,0 +1,62 @@ +package com.ruoyi.system.service.impl; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.sign.Md5Utils; +import com.ruoyi.system.domain.SysLicense; +import com.ruoyi.system.mapper.SysLicenseMapper; +import com.ruoyi.system.service.ILicenseService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; + +@Service +public class LicenseServiceImpl implements ILicenseService { + + @Resource + private SysLicenseMapper licenseMapper; + + private static final String SECRET_KEY = "TangShanKaoKeSecretKey076"; + + @Override + public boolean activateLicense(String licenseKey, Date expireDate) { + if (StringUtils.isEmpty(licenseKey) || expireDate == null) { + return false; + } + + String expectedKey = generateLicenseKey(expireDate); + if (!expectedKey.equals(licenseKey)) { + return false; + } + + SysLicense license = new SysLicense(); + license.setLicenseKey(licenseKey); + license.setExpireDate(expireDate); + license.setCreateTime(new Date()); + + licenseMapper.deleteLicense(); + return licenseMapper.insertLicense(license) > 0; + } + + @Override + public boolean checkLicense() { + SysLicense license = licenseMapper.selectLicense(); + if (license == null||(!generateLicenseKey( license.getExpireDate()).equals(license.getLicenseKey()))) { + return false; + } + + return !license.getExpireDate().before(new Date()); + } + + @Override + public Date getExpireDate() { + SysLicense license = licenseMapper.selectLicense(); + return license != null ? license.getExpireDate() : null; + } + + public String generateLicenseKey(Date expireDate) { + String raw = SECRET_KEY + DateUtils.parseDateToStr("yyyyMMdd", expireDate); + return Md5Utils.hash(raw); + } +} diff --git a/ruoyi-system/src/main/resources/mapper/system/SysLicenseMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysLicenseMapper.xml new file mode 100644 index 0000000..dc96dfc --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/system/SysLicenseMapper.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + select id, license_key, expire_date, create_time from sys_license + + + + + + insert into sys_license ( + license_key, + expire_date, + create_time + ) values ( + #{licenseKey}, + #{expireDate}, + NOW() + ) + + + + DELETE FROM sys_license + + + +