From 108066fab2b77084ff992ace7f0e8266ff497dad Mon Sep 17 00:00:00 2001 From: Yuxuecheng Date: Wed, 22 Jan 2025 23:49:27 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=87=E6=9C=9F=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/ruoyi/RuoYiApplication.java | 2 + .../controller/overdue/OverdueController.java | 64 +++++++++++ .../framework/config/SecurityConfig.java | 2 +- .../ruoyi/framework/constants/Constants.java | 10 ++ .../interceptor/RepeatSubmitInterceptor.java | 106 +++++++++++++++--- ruoyi-ui/package.json | 5 +- ruoyi-ui/src/permission.js | 2 +- ruoyi-ui/src/router/index.js | 23 ++-- ruoyi-ui/src/utils/request.js | 56 +++++---- 9 files changed, 224 insertions(+), 46 deletions(-) create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/overdue/OverdueController.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/constants/Constants.java diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java index 32eb6f1..3b2c891 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java @@ -3,12 +3,14 @@ package com.ruoyi; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.scheduling.annotation.EnableScheduling; /** * 启动程序 * * @author ruoyi */ +@EnableScheduling @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) public class RuoYiApplication { diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/overdue/OverdueController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/overdue/OverdueController.java new file mode 100644 index 0000000..e2c9b48 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/overdue/OverdueController.java @@ -0,0 +1,64 @@ +package com.ruoyi.web.controller.overdue; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.framework.AES.AESExample; +import com.ruoyi.framework.constants.Constants; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.io.FileWriter; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @author ruoyi + */ +@RestController +@RequestMapping +public class OverdueController { + + @Resource + private AESExample aesExample; + + @Resource + private RedisTemplate redisTemplate; + + @GetMapping("overdue") + private R overdue(@RequestParam String activationCode) { + try { + String overdue = aesExample.decrypt(activationCode); + // 确保文件内容长度足够 + if (overdue != null && overdue.length() >= 19) { + // 截取从第5个字符开始的14个字符 + String timeString = overdue.substring(4, 18); + // 获取当前时间并转换为相同格式 + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + String currentTime = sdf.format(new Date()); + // 比较时间 + if (currentTime.compareTo(timeString) > 0) { + // 当前时间大于激活码中的时间,返回激活码已经过期 + return R.fail("激活码已过期"); + }else { + //当前时间小于激活码中的时间时 + FileWriter writer = new FileWriter(Constants.KEY_FILE_PATH); + // 写入文件内容 + writer.write(activationCode); + writer.close(); + redisTemplate.opsForValue().set("overdue", overdue); + } + } else { + return R.fail("激活码不正确"); + } + } catch (Exception e) { + return R.fail("激活码不正确"); + } + return R.ok("激活成功"); + } +} 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 d0c0684..deda9b9 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,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter // 过滤请求 .authorizeRequests() // 对于登录login 注册register 验证码captchaImage 允许匿名访问 - .antMatchers("/login","/wxLogin", "/register","/registerSmsCode","/registerPhone", "/captchaImage").permitAll() + .antMatchers("/login","/wxLogin", "/register","/registerSmsCode","/registerPhone", "/captchaImage","/overdue").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/constants/Constants.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/constants/Constants.java new file mode 100644 index 0000000..fe4bbc7 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/constants/Constants.java @@ -0,0 +1,10 @@ +package com.ruoyi.framework.constants; + +public interface Constants { + // 加密算法 + String ALGORITHM = "AES"; + // 常量密钥 + String SECRET_KEY = "jinanshandongdianliangxinxijishu"; + //密钥存储文件地址 + String KEY_FILE_PATH = "E:/overdue.txt"; +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java index 4ddacae..e7c9770 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java @@ -1,8 +1,17 @@ package com.ruoyi.framework.interceptor; +import java.io.*; import java.lang.reflect.Method; +import java.text.SimpleDateFormat; +import java.util.Date; +import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.framework.AES.AESExample; +import com.ruoyi.framework.constants.Constants; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; @@ -17,33 +26,104 @@ import com.ruoyi.common.utils.ServletUtils; * @author ruoyi */ @Component -public abstract class RepeatSubmitInterceptor implements HandlerInterceptor -{ +public abstract class RepeatSubmitInterceptor implements HandlerInterceptor { + + @Resource + private AESExample aesExample; + @Resource + private RedisTemplate redisTemplate; + @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception - { - if (handler instanceof HandlerMethod) - { + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + // 获取请求路径 + String requestURI = request.getRequestURI(); + + // 如果请求路径不是 /overdue,则执行过期检查逻辑 + if (!"overdue".equals(requestURI.substring( requestURI.lastIndexOf('/') + 1))) { + String overdue = redisTemplate.opsForValue().get("overdue"); + if (overdue == null) { + // 读取文件内容 + overdue = readFileContent(); + } + // 确保文件内容长度足够 + if (overdue != null && overdue.length() >= 19) { + // 截取从第5个字符开始的14个字符 + String timeString = overdue.substring(4, 18); + // 获取当前时间并转换为相同格式 + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + String currentTime = sdf.format(new Date()); + // 比较时间 + if (currentTime.compareTo(timeString) > 0) { + // 当前时间大于文件中的时间,返回901错误码 + AjaxResult ajaxResult = AjaxResult.error(901, "系统已经过期,请联系管理员"); + ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); + return false; + } + } else { + // 当前时间大于文件中的时间,返回901错误码 + AjaxResult ajaxResult = AjaxResult.error(901, "系统已经过期,请联系管理员"); + ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); + return false; + } + } + + if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); - if (annotation != null) - { - if (this.isRepeatSubmit(request, annotation)) - { + if (annotation != null) { + if (this.isRepeatSubmit(request, annotation)) { AjaxResult ajaxResult = AjaxResult.error(annotation.message()); ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); return false; } } return true; - } - else - { + } else { return true; } } + + /** + * 读取文件内容并返回解密后的字符串 + * + * @return 文件内容 + */ + private String readFileContent() { + String filePath = Constants.KEY_FILE_PATH; + File file = new File(filePath); + + // 判断文件是否存在 + if (!file.exists()) { + try { + file.createNewFile(); + } catch (IOException e) { + System.err.println("创建文件失败"); + e.printStackTrace(); + } + } + + StringBuilder content = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) { + String line; + while ((line = reader.readLine()) != null) { + content.append(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + String decrypt = null; + try { + decrypt = aesExample.decrypt(content.toString()); + } catch (Exception e) { + throw new RuntimeException("验证程序错误,请检查"); + } + redisTemplate.opsForValue().set("overdue", decrypt); + return decrypt; + } + /** * 验证是否重复提交由子类实现具体的防重复提交的规则 * diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json index b5817bb..b475ee5 100644 --- a/ruoyi-ui/package.json +++ b/ruoyi-ui/package.json @@ -40,16 +40,19 @@ "@riophae/vue-treeselect": "0.4.0", "axios": "0.24.0", "clipboard": "2.0.8", - "core-js": "3.25.3", + "core-js": "^3.40.0", + "cross-spawn": "^7.0.6", "dayjs": "^1.11.9", "echarts": "5.4.0", "element-ui": "2.15.12", + "execa": "^9.5.2", "file-saver": "2.0.5", "fuse.js": "6.4.3", "highlight.js": "9.18.5", "js-beautify": "1.13.0", "js-cookie": "3.0.1", "jsencrypt": "3.0.0-rc.1", + "nice-try": "^3.0.1", "nprogress": "0.2.0", "qrcodejs2": "0.0.2", "quill": "1.3.7", diff --git a/ruoyi-ui/src/permission.js b/ruoyi-ui/src/permission.js index e1ef627..8e49f9f 100644 --- a/ruoyi-ui/src/permission.js +++ b/ruoyi-ui/src/permission.js @@ -8,7 +8,7 @@ import { isRelogin } from '@/utils/request' NProgress.configure({ showSpinner: false }) -const whiteList = ['/login', '/register','/faCade','/cerebrum'] +const whiteList = ['/login', '/register','/faCade','/cerebrum','/overdue'] router.beforeEach((to, from, next) => { NProgress.start() diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js index c280f18..e37d2e5 100644 --- a/ruoyi-ui/src/router/index.js +++ b/ruoyi-ui/src/router/index.js @@ -83,7 +83,7 @@ export const constantRoutes = [ path: 'index', component: () => import('@/views/index'), name: 'Index', - meta: { title: '首页', icon: 'dashboard', affix: true } + meta: {title: '首页', icon: 'dashboard', affix: true} } ] }, @@ -97,7 +97,7 @@ export const constantRoutes = [ path: 'profile', component: () => import('@/views/system/user/profile/index'), name: 'Profile', - meta: { title: '个人中心', icon: 'user' } + meta: {title: '个人中心', icon: 'user'} } ] }, @@ -111,9 +111,14 @@ export const constantRoutes = [ path: 'cerebrum', component: () => import('@/views/tabList/cerebrum.vue'), name: 'cerebrum', - meta: { title: 'cerebrum', icon: 'user' } + meta: {title: 'cerebrum', icon: 'user'} } ] + }, + { + path: '/overdue', + component: () => import('@/views/overdue'), + hidden: true } ] @@ -129,7 +134,7 @@ export const dynamicRoutes = [ path: 'role/:userId(\\d+)', component: () => import('@/views/system/user/authRole'), name: 'AuthRole', - meta: { title: '分配角色', activeMenu: '/system/user' } + meta: {title: '分配角色', activeMenu: '/system/user'} } ] }, @@ -143,7 +148,7 @@ export const dynamicRoutes = [ path: 'user/:roleId(\\d+)', component: () => import('@/views/system/role/authUser'), name: 'AuthUser', - meta: { title: '分配用户', activeMenu: '/system/role' } + meta: {title: '分配用户', activeMenu: '/system/role'} } ] }, @@ -157,7 +162,7 @@ export const dynamicRoutes = [ path: 'index/:dictId(\\d+)', component: () => import('@/views/system/dict/data'), name: 'Data', - meta: { title: '字典数据', activeMenu: '/system/dict' } + meta: {title: '字典数据', activeMenu: '/system/dict'} } ] }, @@ -171,7 +176,7 @@ export const dynamicRoutes = [ path: 'index/:jobId(\\d+)', component: () => import('@/views/monitor/job/log'), name: 'JobLog', - meta: { title: '调度日志', activeMenu: '/monitor/job' } + meta: {title: '调度日志', activeMenu: '/monitor/job'} } ] }, @@ -185,7 +190,7 @@ export const dynamicRoutes = [ path: 'index/:tableId(\\d+)', component: () => import('@/views/tool/gen/editTable'), name: 'GenEdit', - meta: { title: '修改生成配置', activeMenu: '/tool/gen' } + meta: {title: '修改生成配置', activeMenu: '/tool/gen'} } ] } @@ -199,6 +204,6 @@ Router.prototype.push = function push(location) { export default new Router({ mode: 'history', // 去掉url中的# - scrollBehavior: () => ({ y: 0 }), + scrollBehavior: () => ({y: 0}), routes: constantRoutes }) diff --git a/ruoyi-ui/src/utils/request.js b/ruoyi-ui/src/utils/request.js index 4a7bf13..1272a64 100644 --- a/ruoyi-ui/src/utils/request.js +++ b/ruoyi-ui/src/utils/request.js @@ -1,15 +1,15 @@ import axios from 'axios' -import { Notification, MessageBox, Message, Loading } from 'element-ui' +import {Notification, MessageBox, Message, Loading} from 'element-ui' import store from '@/store' -import { getToken } from '@/utils/auth' +import {getToken} from '@/utils/auth' import errorCode from '@/utils/errorCode' -import { tansParams, blobValidate } from "@/utils/ruoyi"; +import {tansParams, blobValidate} from "@/utils/ruoyi"; import cache from '@/plugins/cache' -import { saveAs } from 'file-saver' +import {saveAs} from 'file-saver' let downloadLoadingInstance; // 是否显示重新登录 -export let isRelogin = { show: false }; +export let isRelogin = {show: false}; axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' // 创建axios实例 @@ -61,8 +61,8 @@ service.interceptors.request.use(config => { } return config }, error => { - console.log(error) - Promise.reject(error) + console.log(error) + Promise.reject(error) }) // 响应拦截器 @@ -72,30 +72,38 @@ service.interceptors.response.use(res => { // 获取错误信息 const msg = errorCode[code] || res.data.msg || errorCode['default'] // 二进制数据则直接返回 - if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { return res.data } if (code === 401) { if (!isRelogin.show) { isRelogin.show = true; - MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { + MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { + confirmButtonText: '重新登录', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { isRelogin.show = false; store.dispatch('LogOut').then(() => { location.href = '/index'; }) - }).catch(() => { - isRelogin.show = false; - }); - } + }).catch(() => { + isRelogin.show = false; + }); + } return Promise.reject('无效的会话,或者会话已过期,请重新登录。') } else if (code === 500) { - Message({ message: msg, type: 'error' }) + Message({message: msg, type: 'error'}) return Promise.reject(new Error(msg)) } else if (code === 601) { - Message({ message: msg, type: 'warning' }) + Message({message: msg, type: 'warning'}) return Promise.reject('error') + } else if (code === 901) { + // 跳转到 overdue 页面 + location.href = '/overdue'; + return Promise.reject('使用期限已过,请联系管理员。'); } else if (code !== 200) { - Notification.error({ title: msg }) + Notification.error({title: msg}) return Promise.reject('error') } else { return res.data @@ -103,7 +111,7 @@ service.interceptors.response.use(res => { }, error => { console.log('err' + error) - let { message } = error; + let {message} = error; if (message == "Network Error") { message = "后端接口连接异常"; } else if (message.includes("timeout")) { @@ -111,17 +119,23 @@ service.interceptors.response.use(res => { } else if (message.includes("Request failed with status code")) { message = "系统接口" + message.substr(message.length - 3) + "异常"; } - Message({ message: message, type: 'error', duration: 5 * 1000 }) + Message({message: message, type: 'error', duration: 5 * 1000}) return Promise.reject(error) } ) // 通用下载方法 export function download(url, params, filename, config) { - downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) + downloadLoadingInstance = Loading.service({ + text: "正在下载数据,请稍候", + spinner: "el-icon-loading", + background: "rgba(0, 0, 0, 0.7)", + }) return service.post(url, params, { - transformRequest: [(params) => { return tansParams(params) }], - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + transformRequest: [(params) => { + return tansParams(params) + }], + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, responseType: 'blob', ...config }).then(async (data) => {