fix: 优化签到时间规则、报名状态判断和评价查询逻辑
This commit is contained in:
@@ -40,6 +40,9 @@ public class SecurityConfig {
|
||||
config.addAllowedMethod("*");
|
||||
config.addAllowedHeader("*");
|
||||
config.addExposedHeader("*");
|
||||
config.addExposedHeader("Content-Disposition");
|
||||
config.addExposedHeader("Content-Length");
|
||||
config.addExposedHeader("Content-Type");
|
||||
config.setMaxAge(3600L);
|
||||
return config;
|
||||
}))
|
||||
|
||||
@@ -4,13 +4,14 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.campus.activity.entity.Review;
|
||||
import com.campus.activity.vo.ReviewVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface ReviewMapper extends BaseMapper<Review> {
|
||||
|
||||
IPage<Review> selectActivityReviews(Page<Review> page, @Param("activityId") Long activityId);
|
||||
IPage<ReviewVO> selectActivityReviews(Page<Review> page, @Param("activityId") Long activityId);
|
||||
|
||||
IPage<Review> selectMyReviews(Page<Review> page, @Param("userId") Long userId);
|
||||
IPage<ReviewVO> selectMyReviews(Page<Review> page, @Param("userId") Long userId);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public class ActivityServiceImpl implements ActivityService {
|
||||
new LambdaQueryWrapper<Registration>()
|
||||
.eq(Registration::getUserId, currentUser.getId())
|
||||
.eq(Registration::getActivityId, id)
|
||||
.eq(Registration::getStatus, 1)
|
||||
.in(Registration::getStatus, 1, 2)
|
||||
);
|
||||
vo.setIsRegistered(registration != null);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -76,7 +76,8 @@ public class CheckInServiceImpl implements CheckInService {
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (now.isBefore(activity.getStartTime()) || now.isAfter(activity.getEndTime())) {
|
||||
LocalDateTime earlyCheckInTime = activity.getStartTime().minusHours(1);
|
||||
if (now.isBefore(earlyCheckInTime) || now.isAfter(activity.getEndTime())) {
|
||||
throw new BusinessException(ResultCode.CHECKIN_TIME_EXPIRED);
|
||||
}
|
||||
|
||||
@@ -121,7 +122,8 @@ public class CheckInServiceImpl implements CheckInService {
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (now.isBefore(activity.getStartTime()) || now.isAfter(activity.getEndTime())) {
|
||||
LocalDateTime earlyCheckInTime = activity.getStartTime().minusHours(1);
|
||||
if (now.isBefore(earlyCheckInTime) || now.isAfter(activity.getEndTime())) {
|
||||
throw new BusinessException(ResultCode.CHECKIN_TIME_EXPIRED);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,23 +73,13 @@ public class ReviewServiceImpl implements ReviewService {
|
||||
|
||||
@Override
|
||||
public IPage<ReviewVO> getActivityReviews(Page<Review> page, Long activityId) {
|
||||
return reviewMapper.selectActivityReviews(page, activityId)
|
||||
.convert(review -> {
|
||||
ReviewVO vo = new ReviewVO();
|
||||
BeanUtils.copyProperties(review, vo);
|
||||
return vo;
|
||||
});
|
||||
return reviewMapper.selectActivityReviews(page, activityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<ReviewVO> getMyReviews(Page<Review> page) {
|
||||
User currentUser = authService.getCurrentUser();
|
||||
return reviewMapper.selectMyReviews(page, currentUser.getId())
|
||||
.convert(review -> {
|
||||
ReviewVO vo = new ReviewVO();
|
||||
BeanUtils.copyProperties(review, vo);
|
||||
return vo;
|
||||
});
|
||||
return reviewMapper.selectMyReviews(page, currentUser.getId());
|
||||
}
|
||||
|
||||
private ReviewVO convertToVO(Review review, User user, Activity activity) {
|
||||
|
||||
@@ -8,72 +8,46 @@ import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
import com.itextpdf.io.font.PdfEncodings;
|
||||
import com.itextpdf.io.image.ImageDataFactory;
|
||||
import com.itextpdf.kernel.colors.ColorConstants;
|
||||
import com.itextpdf.kernel.font.PdfFont;
|
||||
import com.itextpdf.kernel.font.PdfFontFactory;
|
||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||
import com.itextpdf.kernel.pdf.canvas.draw.SolidLine;
|
||||
import com.itextpdf.layout.Document;
|
||||
import com.itextpdf.layout.element.Image;
|
||||
import com.itextpdf.layout.element.Paragraph;
|
||||
import com.itextpdf.layout.element.Table;
|
||||
import com.itextpdf.layout.element.*;
|
||||
import com.itextpdf.layout.properties.HorizontalAlignment;
|
||||
import com.itextpdf.layout.properties.TextAlignment;
|
||||
import com.itextpdf.layout.properties.UnitValue;
|
||||
import com.itextpdf.layout.properties.VerticalAlignment;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PdfUtil {
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm");
|
||||
|
||||
public static byte[] generateTicketPdf(User user, Activity activity, Registration registration) {
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
PdfWriter writer = new PdfWriter(outputStream);
|
||||
PdfDocument pdf = new PdfDocument(writer);
|
||||
Document document = new Document(pdf);
|
||||
|
||||
document.add(new Paragraph("活动电子票")
|
||||
.setFontSize(24)
|
||||
.setBold()
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setMarginBottom(20));
|
||||
PdfFont chineseFont = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
|
||||
PdfFont boldChineseFont = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
|
||||
|
||||
Table table = new Table(UnitValue.createPercentArray(new float[]{1, 2}))
|
||||
.setMarginBottom(20);
|
||||
|
||||
table.addCell(createCell("活动名称:"));
|
||||
table.addCell(createCell(activity.getTitle()));
|
||||
|
||||
table.addCell(createCell("学生姓名:"));
|
||||
table.addCell(createCell(user.getName()));
|
||||
|
||||
table.addCell(createCell("学号:"));
|
||||
table.addCell(createCell(user.getStudentId()));
|
||||
|
||||
table.addCell(createCell("活动时间:"));
|
||||
table.addCell(createCell(activity.getStartTime() + " ~ " + activity.getEndTime()));
|
||||
|
||||
table.addCell(createCell("活动地点:"));
|
||||
table.addCell(createCell(activity.getLocation()));
|
||||
|
||||
table.addCell(createCell("电子票号:"));
|
||||
table.addCell(createCell(registration.getTicketCode()));
|
||||
|
||||
document.add(table);
|
||||
|
||||
byte[] qrCodeBytes = generateQrCode(registration.getTicketCode());
|
||||
Image qrCodeImage = new Image(ImageDataFactory.create(qrCodeBytes))
|
||||
.setWidth(150)
|
||||
.setHeight(150)
|
||||
.setMarginTop(20)
|
||||
.setTextAlignment(TextAlignment.CENTER);
|
||||
|
||||
document.add(qrCodeImage);
|
||||
|
||||
document.add(new Paragraph("请妥善保管此电子票,活动当天凭此票签到")
|
||||
.setFontSize(10)
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setMarginTop(20));
|
||||
createHeader(document, chineseFont, boldChineseFont);
|
||||
createDivider(document);
|
||||
createActivityInfo(document, activity, chineseFont, boldChineseFont);
|
||||
createParticipantAndQrCode(document, user, registration, chineseFont, boldChineseFont);
|
||||
createFooter(document, chineseFont);
|
||||
|
||||
document.close();
|
||||
|
||||
@@ -83,10 +57,201 @@ public class PdfUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static Paragraph createCell(String text) {
|
||||
return new Paragraph(text)
|
||||
private static void createHeader(Document document, PdfFont font, PdfFont boldFont) {
|
||||
Paragraph title = new Paragraph("校园活动电子票")
|
||||
.setFont(boldFont)
|
||||
.setFontSize(20)
|
||||
.setBold()
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setMarginTop(15)
|
||||
.setMarginBottom(5);
|
||||
|
||||
Paragraph subtitle = new Paragraph("Campus Activity E-Ticket")
|
||||
.setFont(font)
|
||||
.setFontSize(9)
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setFontColor(ColorConstants.GRAY)
|
||||
.setMarginBottom(10);
|
||||
|
||||
document.add(title);
|
||||
document.add(subtitle);
|
||||
}
|
||||
|
||||
private static void createDivider(Document document) {
|
||||
SolidLine line = new SolidLine(1f);
|
||||
line.setColor(ColorConstants.LIGHT_GRAY);
|
||||
LineSeparator separator = new LineSeparator(line)
|
||||
.setMarginBottom(15)
|
||||
.setWidth(UnitValue.createPercentValue(100));
|
||||
document.add(separator);
|
||||
}
|
||||
|
||||
private static void createActivityInfo(Document document, Activity activity, PdfFont font, PdfFont boldFont) {
|
||||
Paragraph sectionTitle = new Paragraph("活动信息")
|
||||
.setFont(boldFont)
|
||||
.setFontSize(12)
|
||||
.setPadding(5);
|
||||
.setBold()
|
||||
.setMarginBottom(8);
|
||||
|
||||
document.add(sectionTitle);
|
||||
|
||||
Table infoTable = new Table(UnitValue.createPercentArray(new float[]{25, 75}))
|
||||
.setMarginBottom(15)
|
||||
.setWidth(UnitValue.createPercentValue(90))
|
||||
.setHorizontalAlignment(HorizontalAlignment.CENTER);
|
||||
|
||||
addTableRow(infoTable, "活动名称", activity.getTitle(), font, boldFont, true);
|
||||
addTableRow(infoTable, "活动时间", formatDateTime(activity.getStartTime()) + " ~ " + formatDateTime(activity.getEndTime()), font, boldFont, false);
|
||||
addTableRow(infoTable, "活动地点", activity.getLocation(), font, boldFont, false);
|
||||
|
||||
document.add(infoTable);
|
||||
}
|
||||
|
||||
private static void createParticipantAndQrCode(Document document, User user, Registration registration, PdfFont font, PdfFont boldFont) {
|
||||
Table mainTable = new Table(UnitValue.createPercentArray(new float[]{60, 40}))
|
||||
.setWidth(UnitValue.createPercentValue(90))
|
||||
.setHorizontalAlignment(HorizontalAlignment.CENTER)
|
||||
.setMarginBottom(15);
|
||||
|
||||
Cell leftCell = new Cell()
|
||||
.setBorder(com.itextpdf.layout.borders.Border.NO_BORDER)
|
||||
.setPadding(0);
|
||||
|
||||
Cell rightCell = new Cell()
|
||||
.setBorder(com.itextpdf.layout.borders.Border.NO_BORDER)
|
||||
.setPadding(0)
|
||||
.setVerticalAlignment(VerticalAlignment.MIDDLE);
|
||||
|
||||
Paragraph participantTitle = new Paragraph("参与者信息")
|
||||
.setFont(boldFont)
|
||||
.setFontSize(12)
|
||||
.setBold()
|
||||
.setMarginBottom(8);
|
||||
|
||||
Table participantTable = new Table(UnitValue.createPercentArray(new float[]{25, 75}))
|
||||
.setMarginBottom(0);
|
||||
|
||||
addTableRow(participantTable, "学生姓名", user.getName(), font, boldFont, true);
|
||||
addTableRow(participantTable, "学号", user.getStudentId(), font, boldFont, false);
|
||||
addTableRow(participantTable, "电子票号", registration.getTicketCode(), font, boldFont, false);
|
||||
addTableRow(participantTable, "报名时间", formatDateTime(registration.getCreatedAt()), font, boldFont, false);
|
||||
|
||||
leftCell.add(participantTitle);
|
||||
leftCell.add(participantTable);
|
||||
|
||||
try {
|
||||
byte[] qrCodeBytes = generateQrCode(registration.getTicketCode());
|
||||
Image qrCodeImage = new Image(ImageDataFactory.create(qrCodeBytes))
|
||||
.setWidth(120)
|
||||
.setHeight(120)
|
||||
.setHorizontalAlignment(HorizontalAlignment.CENTER)
|
||||
.setMarginBottom(5);
|
||||
|
||||
Paragraph qrTitle = new Paragraph("签到二维码")
|
||||
.setFont(font)
|
||||
.setFontSize(12)
|
||||
.setBold()
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setMarginBottom(8);
|
||||
|
||||
Paragraph ticketCode = new Paragraph("票号:" + registration.getTicketCode())
|
||||
.setFont(font)
|
||||
.setFontSize(9)
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setFontColor(ColorConstants.GRAY)
|
||||
.setMarginBottom(0);
|
||||
|
||||
rightCell.add(qrTitle);
|
||||
rightCell.add(qrCodeImage);
|
||||
rightCell.add(ticketCode);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("生成二维码失败", e);
|
||||
}
|
||||
|
||||
mainTable.addCell(leftCell);
|
||||
mainTable.addCell(rightCell);
|
||||
|
||||
document.add(mainTable);
|
||||
}
|
||||
|
||||
private static void createFooter(Document document, PdfFont font) {
|
||||
SolidLine line = new SolidLine(1f);
|
||||
line.setColor(ColorConstants.LIGHT_GRAY);
|
||||
LineSeparator separator = new LineSeparator(line)
|
||||
.setMarginTop(15)
|
||||
.setMarginBottom(10)
|
||||
.setWidth(UnitValue.createPercentValue(100));
|
||||
document.add(separator);
|
||||
|
||||
Paragraph notice = new Paragraph("温馨提示:请妥善保管此电子票,活动当天凭此票签到入场。请提前15分钟到达活动现场,出示电子票二维码签到。")
|
||||
.setFont(font)
|
||||
.setFontSize(9)
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setMarginBottom(10);
|
||||
|
||||
document.add(notice);
|
||||
|
||||
Paragraph footer = new Paragraph("本电子票由校园活动系统自动生成 | 生成时间:" + LocalDateTime.now().format(DATE_TIME_FORMATTER))
|
||||
.setFont(font)
|
||||
.setFontSize(7)
|
||||
.setTextAlignment(TextAlignment.CENTER)
|
||||
.setFontColor(ColorConstants.GRAY)
|
||||
.setMarginBottom(15);
|
||||
|
||||
document.add(footer);
|
||||
}
|
||||
|
||||
private static void addTableRow(Table table, String label, String value, PdfFont font, PdfFont boldFont, boolean isFirstRow) {
|
||||
Cell labelCell = new Cell()
|
||||
.add(new Paragraph(label).setFont(boldFont).setFontSize(10))
|
||||
.setPadding(4)
|
||||
.setBorder(com.itextpdf.layout.borders.Border.NO_BORDER);
|
||||
|
||||
Cell valueCell = new Cell()
|
||||
.add(new Paragraph(value != null ? value : "").setFont(font).setFontSize(10))
|
||||
.setPadding(4)
|
||||
.setBorder(com.itextpdf.layout.borders.Border.NO_BORDER);
|
||||
|
||||
if (isFirstRow) {
|
||||
labelCell.setBackgroundColor(new com.itextpdf.kernel.colors.DeviceRgb(240, 240, 240));
|
||||
valueCell.setBackgroundColor(new com.itextpdf.kernel.colors.DeviceRgb(240, 240, 240));
|
||||
}
|
||||
|
||||
table.addCell(labelCell);
|
||||
table.addCell(valueCell);
|
||||
}
|
||||
|
||||
private static String formatDateTime(LocalDateTime dateTime) {
|
||||
if (dateTime == null) {
|
||||
return "待定";
|
||||
}
|
||||
return dateTime.format(DATE_TIME_FORMATTER);
|
||||
}
|
||||
|
||||
private static String formatDescription(String description) {
|
||||
if (description == null || description.isEmpty()) {
|
||||
return "暂无描述";
|
||||
}
|
||||
if (description.length() > 100) {
|
||||
return description.substring(0, 100) + "...";
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
private static String formatRegistrationStatus(Integer status) {
|
||||
if (status == null) {
|
||||
return "未知";
|
||||
}
|
||||
switch (status) {
|
||||
case 0:
|
||||
return "已取消";
|
||||
case 1:
|
||||
return "已报名";
|
||||
case 2:
|
||||
return "已签到";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] generateQrCode(String content) throws IOException {
|
||||
@@ -94,6 +259,7 @@ public class PdfUtil {
|
||||
QRCodeWriter qrCodeWriter = new QRCodeWriter();
|
||||
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
|
||||
hints.put(EncodeHintType.MARGIN, 2);
|
||||
|
||||
BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, 300, 300, hints);
|
||||
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.campus.activity.mapper.ReviewMapper">
|
||||
|
||||
<select id="selectActivityReviews" resultType="com.campus.activity.entity.Review">
|
||||
SELECT r.*,
|
||||
u.username,
|
||||
<select id="selectActivityReviews" resultType="com.campus.activity.vo.ReviewVO">
|
||||
SELECT r.id,
|
||||
r.user_id as userId,
|
||||
r.activity_id as activityId,
|
||||
r.rating,
|
||||
r.content,
|
||||
r.created_at as createdAt,
|
||||
u.name as userName,
|
||||
u.avatar as userAvatar
|
||||
FROM review r
|
||||
@@ -13,8 +17,13 @@
|
||||
ORDER BY r.created_at DESC
|
||||
</select>
|
||||
|
||||
<select id="selectMyReviews" resultType="com.campus.activity.entity.Review">
|
||||
SELECT r.*,
|
||||
<select id="selectMyReviews" resultType="com.campus.activity.vo.ReviewVO">
|
||||
SELECT r.id,
|
||||
r.user_id as userId,
|
||||
r.activity_id as activityId,
|
||||
r.rating,
|
||||
r.content,
|
||||
r.created_at as createdAt,
|
||||
a.title as activityTitle
|
||||
FROM review r
|
||||
LEFT JOIN activity a ON r.activity_id = a.id
|
||||
|
||||
Reference in New Issue
Block a user