feat: 将统计数据导出从Excel改为CSV格式
This commit is contained in:
@@ -32,7 +32,7 @@ public class StatisticsController {
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public ResponseEntity<byte[]> exportActivityData(
|
||||
@PathVariable Long activityId,
|
||||
@RequestParam(defaultValue = "excel") String format) {
|
||||
@RequestParam(defaultValue = "csv") String format) {
|
||||
return statisticsService.exportActivityData(activityId, format);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.campus.activity.mapper.CheckInMapper;
|
||||
import com.campus.activity.mapper.RegistrationMapper;
|
||||
import com.campus.activity.mapper.ReviewMapper;
|
||||
import com.campus.activity.service.StatisticsService;
|
||||
import com.campus.activity.util.ExcelUtil;
|
||||
import com.campus.activity.util.CsvUtil;
|
||||
import com.campus.activity.vo.ActivityStatisticsVO;
|
||||
import com.campus.activity.vo.OverviewStatisticsVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -34,7 +34,7 @@ public class StatisticsServiceImpl implements StatisticsService {
|
||||
private final CheckInMapper checkInMapper;
|
||||
private final ReviewMapper reviewMapper;
|
||||
private final ActivityMapper activityMapper;
|
||||
private final ExcelUtil excelUtil;
|
||||
private final CsvUtil csvUtil;
|
||||
|
||||
@Override
|
||||
public ActivityStatisticsVO getActivityStatistics(Long activityId) {
|
||||
@@ -98,21 +98,15 @@ public class StatisticsServiceImpl implements StatisticsService {
|
||||
throw new BusinessException(ResultCode.ACTIVITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
List<Registration> registrations = registrationMapper.selectList(
|
||||
new LambdaQueryWrapper<Registration>()
|
||||
.eq(Registration::getActivityId, activityId)
|
||||
.eq(Registration::getStatus, 1)
|
||||
);
|
||||
byte[] csvBytes = csvUtil.exportActivityData(activity);
|
||||
|
||||
byte[] excelBytes = excelUtil.exportActivityData(activity, registrations);
|
||||
|
||||
String filename = "activity_" + activityId + "_data.xlsx";
|
||||
String filename = "activity_" + activityId + "_statistics.csv";
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.contentLength(excelBytes.length)
|
||||
.body(excelBytes);
|
||||
.contentType(MediaType.parseMediaType("text/csv; charset=UTF-8"))
|
||||
.contentLength(csvBytes.length)
|
||||
.body(csvBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
121
server/src/main/java/com/campus/activity/util/CsvUtil.java
Normal file
121
server/src/main/java/com/campus/activity/util/CsvUtil.java
Normal file
@@ -0,0 +1,121 @@
|
||||
package com.campus.activity.util;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.campus.activity.entity.Activity;
|
||||
import com.campus.activity.entity.CheckIn;
|
||||
import com.campus.activity.entity.Registration;
|
||||
import com.campus.activity.entity.Review;
|
||||
import com.campus.activity.mapper.CheckInMapper;
|
||||
import com.campus.activity.mapper.RegistrationMapper;
|
||||
import com.campus.activity.mapper.ReviewMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CsvUtil {
|
||||
|
||||
private final RegistrationMapper registrationMapper;
|
||||
private final CheckInMapper checkInMapper;
|
||||
private final ReviewMapper reviewMapper;
|
||||
|
||||
public byte[] exportActivityData(Activity activity) {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
// 写入BOM头,让Excel正确识别UTF-8编码
|
||||
outputStream.write(0xEF);
|
||||
outputStream.write(0xBB);
|
||||
outputStream.write(0xBF);
|
||||
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// 写入表头
|
||||
StringBuilder header = new StringBuilder();
|
||||
header.append("活动ID,活动名称,开始时间,结束时间,活动地点,报名人数上限,报名人数,签到人数,签到率,评价人数,平均评分,1星,2星,3星,4星,5星");
|
||||
outputStream.write(header.toString().getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.write("\n".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 统计数据
|
||||
long registeredCount = registrationMapper.selectCount(
|
||||
new LambdaQueryWrapper<Registration>()
|
||||
.eq(Registration::getActivityId, activity.getId())
|
||||
.in(Registration::getStatus, 1, 2)
|
||||
);
|
||||
|
||||
List<CheckIn> checkIns = checkInMapper.selectList(
|
||||
new LambdaQueryWrapper<CheckIn>()
|
||||
.eq(CheckIn::getActivityId, activity.getId())
|
||||
);
|
||||
long checkedInCount = checkIns.size();
|
||||
|
||||
double checkInRate = registeredCount > 0 ? (double) checkedInCount / registeredCount : 0.0;
|
||||
|
||||
List<Review> reviews = reviewMapper.selectList(
|
||||
new LambdaQueryWrapper<Review>()
|
||||
.eq(Review::getActivityId, activity.getId())
|
||||
);
|
||||
long reviewCount = reviews.size();
|
||||
|
||||
double averageRating = reviews.isEmpty() ? 0.0 :
|
||||
reviews.stream().mapToInt(Review::getRating).average().orElse(0.0);
|
||||
|
||||
// 评分分布
|
||||
Map<Integer, Long> ratingDistribution = new java.util.HashMap<>();
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
final int rating = i;
|
||||
long count = reviews.stream().filter(r -> r.getRating() == rating).count();
|
||||
ratingDistribution.put(rating, count);
|
||||
}
|
||||
|
||||
// 写入数据行
|
||||
StringBuilder dataLine = new StringBuilder();
|
||||
dataLine.append(escapeCsv(String.valueOf(activity.getId()))).append(",");
|
||||
dataLine.append(escapeCsv(activity.getTitle())).append(",");
|
||||
dataLine.append(escapeCsv(activity.getStartTime() != null ? activity.getStartTime().format(formatter) : "")).append(",");
|
||||
dataLine.append(escapeCsv(activity.getEndTime() != null ? activity.getEndTime().format(formatter) : "")).append(",");
|
||||
dataLine.append(escapeCsv(activity.getLocation())).append(",");
|
||||
dataLine.append(escapeCsv(String.valueOf(activity.getMaxParticipants()))).append(",");
|
||||
dataLine.append(escapeCsv(String.valueOf(registeredCount))).append(",");
|
||||
dataLine.append(escapeCsv(String.valueOf(checkedInCount))).append(",");
|
||||
dataLine.append(escapeCsv(String.format("%.2f%%", checkInRate * 100))).append(",");
|
||||
dataLine.append(escapeCsv(String.valueOf(reviewCount))).append(",");
|
||||
dataLine.append(escapeCsv(String.format("%.1f", averageRating))).append(",");
|
||||
dataLine.append(escapeCsv(String.valueOf(ratingDistribution.getOrDefault(1, 0L)))).append(",");
|
||||
dataLine.append(escapeCsv(String.valueOf(ratingDistribution.getOrDefault(2, 0L)))).append(",");
|
||||
dataLine.append(escapeCsv(String.valueOf(ratingDistribution.getOrDefault(3, 0L)))).append(",");
|
||||
dataLine.append(escapeCsv(String.valueOf(ratingDistribution.getOrDefault(4, 0L)))).append(",");
|
||||
dataLine.append(escapeCsv(String.valueOf(ratingDistribution.getOrDefault(5, 0L))));
|
||||
|
||||
outputStream.write(dataLine.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
return outputStream.toByteArray();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("导出CSV失败: " + e.getMessage(), e);
|
||||
} finally {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String escapeCsv(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
// 如果包含逗号、引号或换行符,需要用引号包裹并转义
|
||||
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
|
||||
return "\"" + value.replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package com.campus.activity.util;
|
||||
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
|
||||
import com.alibaba.excel.write.metadata.style.WriteFont;
|
||||
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
|
||||
import com.campus.activity.entity.Activity;
|
||||
import com.campus.activity.entity.Registration;
|
||||
import com.campus.activity.entity.User;
|
||||
import com.campus.activity.mapper.UserMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ExcelUtil {
|
||||
|
||||
private final UserMapper userMapper;
|
||||
|
||||
public byte[] exportActivityData(Activity activity, List<Registration> registrations) {
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
|
||||
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
|
||||
WriteFont headWriteFont = new WriteFont();
|
||||
headWriteFont.setFontHeightInPoints((short) 11);
|
||||
headWriteFont.setBold(true);
|
||||
headWriteCellStyle.setWriteFont(headWriteFont);
|
||||
headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
|
||||
|
||||
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
|
||||
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
|
||||
|
||||
HorizontalCellStyleStrategy horizontalCellStyleStrategy =
|
||||
new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
|
||||
|
||||
List<RegistrationExportData> dataList = new ArrayList<>();
|
||||
|
||||
for (Registration registration : registrations) {
|
||||
User user = userMapper.selectById(registration.getUserId());
|
||||
RegistrationExportData data = new RegistrationExportData();
|
||||
data.setStudentId(user != null ? user.getStudentId() : "");
|
||||
data.setName(user != null ? user.getName() : "");
|
||||
data.setUsername(user != null ? user.getUsername() : "");
|
||||
data.setEmail(user != null ? user.getEmail() : "");
|
||||
data.setPhone(user != null ? user.getPhone() : "");
|
||||
data.setTicketCode(registration.getTicketCode());
|
||||
data.setRegistrationTime(registration.getCreatedAt());
|
||||
data.setStatus(registration.getStatus() == 1 ? "已报名" :
|
||||
registration.getStatus() == 2 ? "已签到" : "已取消");
|
||||
dataList.add(data);
|
||||
}
|
||||
|
||||
EasyExcel.write(outputStream)
|
||||
.head(RegistrationExportData.class)
|
||||
.registerWriteHandler(horizontalCellStyleStrategy)
|
||||
.sheet("报名数据")
|
||||
.doWrite(dataList);
|
||||
|
||||
return outputStream.toByteArray();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("导出Excel失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@lombok.Data
|
||||
public static class RegistrationExportData {
|
||||
private String studentId;
|
||||
private String name;
|
||||
private String username;
|
||||
private String email;
|
||||
private String phone;
|
||||
private String ticketCode;
|
||||
private java.time.LocalDateTime registrationTime;
|
||||
private String status;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user