暂时取消反调试功能

main
zhangliang 4 months ago
parent 0109b2e9a7
commit d465e5ff5a

@ -47,6 +47,20 @@
<version>1.78.1</version>
</dependency>
<!-- Apache POI for Excel export -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

@ -12,25 +12,31 @@ public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 允许的前端地址
config.addAllowedOrigin("http://localhost:5173");
// 允许携带凭证Cookie
// ========== 核心修复Spring Boot 2.4+ 推荐用 allowedOriginPatterns 替代 allowedOrigin ==========
// 方式1允许指定前端域名精准控制
config.addAllowedOriginPattern("http://localhost:5173");
config.addAllowedOriginPattern("http://127.0.0.1:5500");
// 方式2允许所有域名测试环境可用生产环境建议指定具体域名
// config.addAllowedOriginPattern("*");
// 允许携带凭证Cookie/Token
config.setAllowCredentials(true);
// 允许所有请求头
// 允许所有请求头包含自定义头如Token
config.addAllowedHeader("*");
// 允许所有HTTP方法
// 允许所有HTTP方法GET/POST/OPTIONS等
config.addAllowedMethod("*");
// 预检请求的有效期(秒)
// 预检请求缓存时长减少OPTIONS请求次数
config.setMaxAge(3600L);
// 配置生效的接口路径(/** 表示所有接口)
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
}

@ -0,0 +1,302 @@
package com.xky.controller;
import com.xky.pojo.entity.Score;
import com.xky.pojo.entity.User;
import com.xky.pojo.resultful.Result;
import com.xky.service.ScoreService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;
/**
*
*/
@Slf4j
@RestController
@RequestMapping("/file")
@CrossOrigin
public class FileController {
@Autowired
private ScoreService scoreService;
// 从配置文件读取文件存储路径,如果没有配置则使用默认值
@Value("${file.upload.path:D:/uploads}")
private String uploadPath;
/**
*
*/
@PostMapping("/upload")
public Result uploadFile(@RequestParam("file") MultipartFile file) {
try {
// 检查文件是否为空
if (file.isEmpty()) {
return Result.error("文件不能为空");
}
// 创建上传目录
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 获取原始文件名
String originalFilename = file.getOriginalFilename();
// 生成唯一文件名UUID + 原始扩展名)
String extension = "";
if (originalFilename != null && originalFilename.contains(".")) {
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
String storeName = UUID.randomUUID().toString() + extension;
// 保存文件
File destFile = new File(uploadPath + File.separator + storeName);
file.transferTo(destFile);
return Result.success("文件上传成功", storeName);
} catch (Exception e) {
e.printStackTrace();
return Result.error("文件上传失败:" + e.getMessage());
}
}
/**
* Excel
*/
@GetMapping("/exportScores")
public void exportScores(HttpSession session, HttpServletResponse response) {
try {
// 获取当前登录用户
Object currentUser = session.getAttribute("currentUser");
if (currentUser == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("请先登录");
return;
}
User user = (User) currentUser;
log.info("导出成绩 - 当前用户:{}", user.getName());
// 获取学生成绩列表
List<Score> scores = scoreService.listStudentScore(user.getName());
if (scores == null || scores.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().write("暂无成绩记录");
return;
}
// 创建Excel工作簿
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("我的成绩");
// 创建标题行样式
CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
headerStyle.setBorderBottom(BorderStyle.THIN);
headerStyle.setBorderTop(BorderStyle.THIN);
headerStyle.setBorderLeft(BorderStyle.THIN);
headerStyle.setBorderRight(BorderStyle.THIN);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerFont.setFontHeightInPoints((short) 12);
headerStyle.setFont(headerFont);
// 创建数据行样式
CellStyle dataStyle = workbook.createCellStyle();
dataStyle.setBorderBottom(BorderStyle.THIN);
dataStyle.setBorderTop(BorderStyle.THIN);
dataStyle.setBorderLeft(BorderStyle.THIN);
dataStyle.setBorderRight(BorderStyle.THIN);
dataStyle.setAlignment(HorizontalAlignment.CENTER);
// 创建标题行
Row headerRow = sheet.createRow(0);
String[] headers = {"课程名称", "学生姓名", "成绩"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 填充数据
int rowNum = 1;
for (Score score : scores) {
Row row = sheet.createRow(rowNum++);
Cell cell0 = row.createCell(0);
cell0.setCellValue(score.getCourseName());
cell0.setCellStyle(dataStyle);
Cell cell1 = row.createCell(1);
cell1.setCellValue(score.getUsername());
cell1.setCellStyle(dataStyle);
Cell cell2 = row.createCell(2);
cell2.setCellValue(score.getScore());
cell2.setCellStyle(dataStyle);
}
// 自动调整列宽
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
// 额外增加一些宽度以确保内容完全显示
sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 2000);
}
// 设置响应头
String fileName = user.getName() + "_成绩单.xlsx";
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
// 写入响应流
try (OutputStream os = response.getOutputStream()) {
workbook.write(os);
os.flush();
}
workbook.close();
log.info("成绩导出成功 - 用户:{},记录数:{}", user.getName(), scores.size());
} catch (Exception e) {
log.error("导出成绩失败", e);
try {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("导出失败:" + e.getMessage());
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
/**
*
*/
@GetMapping("/download")
public void downloadFile(
@RequestParam("storeName") String storeName,
HttpServletRequest request,
HttpServletResponse response
) {
try {
// 1. 拼接文件完整路径
File file = new File(uploadPath + File.separator + storeName);
// 2. 文件校验
if (!file.exists() || !file.isFile()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().write("错误:文件不存在或已被删除");
return;
}
// 3. 解决中文文件名乱码
String fileName = storeName;
String userAgent = request.getHeader("User-Agent");
String encodedFileName;
if (userAgent != null && (userAgent.contains("MSIE") || userAgent.contains("Trident"))) {
encodedFileName = URLEncoder.encode(fileName, "GBK");
} else {
encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
}
// 4. 设置响应头
String contentType = Files.probeContentType(Paths.get(file.getAbsolutePath()));
response.setContentType(contentType == null ? "application/octet-stream" : contentType);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + encodedFileName + "\"");
response.setContentLength((int) file.length());
// 5. 流传输
byte[] buffer = new byte[8192];
try (FileInputStream fis = new FileInputStream(file);
OutputStream os = response.getOutputStream()) {
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.flush();
}
} catch (Exception e) {
try {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("下载失败:" + e.getMessage());
} catch (Exception ex) {
ex.printStackTrace();
}
e.printStackTrace();
}
}
/**
*
*/
@GetMapping("/list")
public Result listFiles() {
try {
File dir = new File(uploadPath);
if (!dir.exists()) {
dir.mkdirs();
return Result.success(new String[0]);
}
File[] files = dir.listFiles(File::isFile);
if (files == null || files.length == 0) {
return Result.success(new String[0]);
}
String[] fileNames = new String[files.length];
for (int i = 0; i < files.length; i++) {
fileNames[i] = files[i].getName();
}
return Result.success(fileNames);
} catch (Exception e) {
return Result.error("获取文件列表失败:" + e.getMessage());
}
}
/**
*
*/
@DeleteMapping("/delete")
public Result deleteFile(@RequestParam("storeName") String storeName) {
try {
File file = new File(uploadPath + File.separator + storeName);
if (!file.exists()) {
return Result.error("文件不存在");
}
if (file.delete()) {
return Result.success("文件删除成功", null);
} else {
return Result.error("文件删除失败");
}
} catch (Exception e) {
return Result.error("文件删除失败:" + e.getMessage());
}
}
}

@ -47,3 +47,11 @@ server.servlet.session.cookie.name=JSESSIONID
server.servlet.session.cookie.same-site=lax
# Cookie的HttpOnly属性
server.servlet.session.cookie.http-only=true
# 文件上传配置
file.upload.path=D:/
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.enabled=true

@ -5,29 +5,9 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>学生选课系统</title>
<script>
// 页面加载时立即执行的防调试代码(仅基础保护,避免卡顿)
(function() {
// 禁用右键(但允许视频元素)
document.addEventListener('contextmenu', function(e) {
if (e.target.tagName !== 'VIDEO') {
e.preventDefault()
return false
}
})
// 禁用选择文本(但允许输入框)
document.addEventListener('selectstart', function(e) {
if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
e.preventDefault()
return false
}
})
})()
</script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
</html>

@ -8,207 +8,6 @@ import axios from 'axios'
import { ElMessageBox } from 'element-plus'
import router from './router'
document.addEventListener('contextmenu', (e) => {
if (e.target.tagName !== 'VIDEO' && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
e.preventDefault()
return false
}
})
document.addEventListener('keydown', (e) => {
if (e.key === 'F12') {
e.preventDefault()
return false
}
if (e.ctrlKey && e.shiftKey && e.key === 'I') {
e.preventDefault()
return false
}
if (e.ctrlKey && e.shiftKey && e.key === 'J') {
e.preventDefault()
return false
}
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
e.preventDefault()
return false
}
if (e.ctrlKey && e.key === 'u') {
e.preventDefault()
return false
}
if (e.ctrlKey && e.key === 's') {
e.preventDefault()
return false
}
})
let devtoolsWarningShown = false
let debuggerActive = false
const showDevToolsWarning = () => {
if (!devtoolsWarningShown) {
devtoolsWarningShown = true
debuggerActive = true
const overlay = document.createElement('div')
overlay.id = 'devtools-warning-overlay'
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.95);
z-index: 999999;
display: flex;
align-items: center;
justify-content: center;
font-family: Arial, sans-serif;
`
const message = document.createElement('div')
message.style.cssText = `
background: white;
padding: 40px;
border-radius: 12px;
text-align: center;
max-width: 500px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
`
message.innerHTML = `
<div style="font-size: 60px; margin-bottom: 20px;"></div>
<h2 style="color: #e74c3c; margin: 0 0 15px 0; font-size: 24px;">检测到开发者工具</h2>
<p style="color: #555; margin: 0 0 10px 0; font-size: 16px; line-height: 1.6;">
为保护系统安全禁止使用开发者工具访问本系统
</p>
<p style="color: #888; margin: 0; font-size: 14px;">
请关闭开发者工具后刷新页面继续使用
</p>
<div style="margin-top: 25px; padding-top: 25px; border-top: 1px solid #eee;">
<p style="color: #999; margin: 0; font-size: 12px;">
如有疑问请联系系统管理员
</p>
</div>
`
overlay.appendChild(message)
document.body.appendChild(overlay)
setTimeout(() => {
const infiniteDebug = () => {
debugger
infiniteDebug()
}
infiniteDebug()
}, 1000)
}
}
const detectDevToolsBySize = () => {
const threshold = 160
const widthThreshold = window.outerWidth - window.innerWidth > threshold
const heightThreshold = window.outerHeight - window.innerHeight > threshold
if (widthThreshold || heightThreshold) {
showDevToolsWarning()
}
}
const detectDevToolsByTiming = () => {
const start = performance.now()
debugger
const end = performance.now()
if (end - start > 100) {
showDevToolsWarning()
}
}
const detectFirebug = () => {
if (window.console && (window.console.firebug || window.console.exception)) {
showDevToolsWarning()
}
}
const protectWarningOverlay = () => {
if (debuggerActive) {
const overlay = document.getElementById('devtools-warning-overlay')
if (!overlay && devtoolsWarningShown) {
showDevToolsWarning()
}
}
}
const detectToString = () => {
const element = new Image()
let isOpen = false
Object.defineProperty(element, 'id', {
get: function() {
isOpen = true
showDevToolsWarning()
}
})
console.log('%c', element)
}
// 延迟启动检测,让页面先完全加载
setTimeout(() => {
// 主检测窗口尺寸每1秒
setInterval(detectDevToolsBySize, 1000)
// 辅助检测1时间差检测每3秒
setInterval(detectDevToolsByTiming, 3000)
// 辅助检测2Firebug检测每2秒
setInterval(detectFirebug, 2000)
// 辅助检测3保护遮罩每500毫秒
setInterval(protectWarningOverlay, 500)
// 辅助检测4toString检测每5秒
setInterval(() => {
try {
detectToString()
} catch (e) {
// 捕获异常,继续运行
}
}, 5000)
}, 2000)
// 监听窗口大小变化(实时检测)
window.addEventListener('resize', () => {
setTimeout(detectDevToolsBySize, 100)
})
if (import.meta.env.MODE === 'production') {
const noop = () => {}
const methods = ['log', 'debug', 'info', 'warn', 'error', 'table', 'trace', 'dir', 'dirxml', 'group', 'groupCollapsed', 'groupEnd', 'clear', 'count', 'countReset', 'assert', 'profile', 'profileEnd', 'time', 'timeLog', 'timeEnd', 'timeStamp']
methods.forEach(method => {
window.console[method] = noop
})
}
try {
Object.freeze(Object.prototype)
Object.freeze(Array.prototype)
Object.freeze(Function.prototype)
} catch (e) {
}
// 设置axios全局配置
axios.defaults.withCredentials = true

@ -291,12 +291,6 @@ onMounted(() => {
align-items: center;
}
.header-left {
display: flex;
align-items: center;
gap: 30px;
}
.header-left h2 {
margin: 0;
color: #2c3e50;
@ -304,16 +298,6 @@ onMounted(() => {
font-weight: 600;
}
.header-right {
flex-shrink: 0;
}
.toolbar {
margin-bottom: 20px;
display: flex;
justify-content: flex-end;
}
.pagination {
margin-top: 20px;
display: flex;

@ -13,6 +13,7 @@
clearable
/>
<el-button type="primary" @click="handleSearch"></el-button>
<el-button type="primary" @click="handleExport"></el-button>
</div>
<div class="table-wrapper">
@ -93,6 +94,49 @@ const handleSearch = () => {
}
}
const handleExport = async () => {
if (scores.value.length === 0) {
ElMessage.warning('暂无成绩可导出')
return
}
try {
const response = await axios.get(`${API_BASE_URL}/file/exportScores`, {
responseType: 'blob',
withCredentials: true
})
//
const blob = new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 使
const contentDisposition = response.headers['content-disposition']
let fileName = '我的成绩单.xlsx'
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename\*?=['"]?(?:UTF-\d['"]*)?([^;\r\n"']*)['"]?;?/)
if (fileNameMatch && fileNameMatch[1]) {
fileName = decodeURIComponent(fileNameMatch[1])
}
}
link.download = fileName
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('成绩导出成功')
} catch (error) {
console.error('导出成绩失败:', error)
ElMessage.error('导出成绩失败')
}
}
onMounted(() => {
loadScores()
})
@ -129,6 +173,7 @@ onMounted(() => {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-right: 150px;
}
.table-wrapper {

Loading…
Cancel
Save