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