过期验证

This commit is contained in:
Yuxuecheng 2025-01-22 23:49:27 +08:00
parent f1fe30920f
commit 108066fab2
9 changed files with 224 additions and 46 deletions

View File

@ -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
{

View File

@ -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<String,String> redisTemplate;
@GetMapping("overdue")
private R<String> 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("激活成功");
}
}

View File

@ -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()

View File

@ -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";
}

View File

@ -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<String,String> 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;
}
/**
* 验证是否重复提交由子类实现具体的防重复提交的规则
*

View File

@ -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",

View File

@ -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()

View File

@ -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
})

View File

@ -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) => {