|
@@ -1,6 +1,7 @@
|
|
|
package com.web.api.config;
|
|
|
|
|
|
import com.alibaba.fastjson.JSONException;
|
|
|
+import com.google.common.collect.ImmutableMap;
|
|
|
import com.web.api.dao.AnswerDao;
|
|
|
import com.web.api.exception.ErrorCode;
|
|
|
import com.web.api.exception.SgException;
|
|
@@ -8,20 +9,25 @@ import com.web.api.model.dto.AnswerDto;
|
|
|
import com.web.api.model.po.UserInfo;
|
|
|
import com.web.api.service.mail.MailServiceImpl;
|
|
|
import com.web.api.utils.ContextUtils;
|
|
|
-import com.web.api.utils.DateUtills;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
+import org.springframework.dao.DataAccessException;
|
|
|
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
|
|
-import org.springframework.util.ObjectUtils;
|
|
|
+import org.thymeleaf.context.Context;
|
|
|
+import org.thymeleaf.spring5.SpringTemplateEngine;
|
|
|
+
|
|
|
import javax.annotation.Resource;
|
|
|
import java.io.IOException;
|
|
|
import java.lang.reflect.Method;
|
|
|
import java.text.DecimalFormat;
|
|
|
+import java.time.Duration;
|
|
|
import java.time.LocalDateTime;
|
|
|
-import java.time.ZoneOffset;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.Set;
|
|
|
|
|
|
|
|
|
/**
|
|
@@ -42,6 +48,20 @@ public class AsyncExceptionConfig implements AsyncConfigurer {
|
|
|
@Autowired
|
|
|
private MailServiceImpl mailService;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private SpringTemplateEngine templateEngine;
|
|
|
+
|
|
|
+ private static final Set<Class<? extends Throwable>> BUSINESS_EXCEPTIONS = new HashSet<Class<? extends Throwable>>() {{
|
|
|
+ add(SgException.class);
|
|
|
+ add(IOException.class);
|
|
|
+ add(NullPointerException.class);
|
|
|
+ add(JSONException.class);
|
|
|
+ }};
|
|
|
+
|
|
|
+ private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分");
|
|
|
+ private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("######0.00");
|
|
|
+ private static final String SERVER_IP = "http://42.192.203.166";
|
|
|
+ private static final String[] DEFAULT_RECIPIENTS = {"251664727@qq.com", "632062365@qq.com"};
|
|
|
|
|
|
|
|
|
@Override
|
|
@@ -51,94 +71,322 @@ public class AsyncExceptionConfig implements AsyncConfigurer {
|
|
|
|
|
|
class SpringAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
|
|
|
@Override
|
|
|
- public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
|
|
|
- if (!(throwable instanceof SgException) &&
|
|
|
- !(throwable instanceof IOException) &&
|
|
|
- !(throwable instanceof NullPointerException) &&
|
|
|
- !(throwable instanceof JSONException)){
|
|
|
- log.error("------非业务类异常,此处不做处理--------:{}", throwable);
|
|
|
- return;
|
|
|
+ public void handleUncaughtException(Throwable throwable, Method method, Object... params) {
|
|
|
+ try {
|
|
|
+ if (!BUSINESS_EXCEPTIONS.contains(throwable.getClass())) {
|
|
|
+ log.error("------非业务类异常,此处不做处理--------: ", throwable);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ validateParams(params);
|
|
|
+
|
|
|
+ AnswerDto answerDto = (AnswerDto) params[2];
|
|
|
+ Double startScore = (Double) params[3];
|
|
|
+
|
|
|
+ //组装用户信息
|
|
|
+ UserInfo userInfo = processUserInfo(answerDto, startScore, throwable);
|
|
|
+ //更新数据库
|
|
|
+ int updateCount = handleDatabaseOperation(userInfo);
|
|
|
+ //发送邮件
|
|
|
+ if (updateCount > 0) {
|
|
|
+ sendNotificationEmail(answerDto, userInfo, startScore, throwable, updateCount);
|
|
|
+ }
|
|
|
+ cleanUpResources(answerDto, throwable);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("异常处理过程出错: ", e);
|
|
|
+ } finally {
|
|
|
+ log.error("异步任务异常详情: ", throwable);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void validateParams(Object[] params) {
|
|
|
+ log.info("业务参数:{}", params);
|
|
|
+ if (params.length < 4) {
|
|
|
+ throw new IllegalArgumentException("参数数量不足,需要4个参数");
|
|
|
+ }
|
|
|
+ if (!(params[2] instanceof AnswerDto)) {
|
|
|
+ throw new IllegalArgumentException("第三个参数应为AnswerDto类型");
|
|
|
}
|
|
|
- log.info("业务参数:{}",objects);
|
|
|
- //收件人
|
|
|
- String[] toUser = {"251664727@qq.com", "632062365@qq.com"};
|
|
|
- AnswerDto answerDto = (AnswerDto) objects[2];
|
|
|
- Double startScore = (Double) objects[3];
|
|
|
+ if (!(params[3] instanceof Double)) {
|
|
|
+ throw new IllegalArgumentException("第四个参数应为Double类型");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private UserInfo processUserInfo(AnswerDto answerDto, Double startScore, Throwable throwable) {
|
|
|
UserInfo userInfo = ContextUtils.getUserInfo(answerDto.getJobNo());
|
|
|
- DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分");
|
|
|
- userInfo.setEndTime(LocalDateTime.now().format(fmt));
|
|
|
+ userInfo.setEndTime(LocalDateTime.now().format(DATE_TIME_FORMATTER));
|
|
|
userInfo.setAnswerStatus("已结束");
|
|
|
- DecimalFormat df = new DecimalFormat("######0.00");
|
|
|
- Object addScores = ObjectUtils.isEmpty(userInfo.getFinalScore()) ? "本次答题异常终止,无法计算分数" : df.format(userInfo.getFinalScore() - startScore);
|
|
|
- if (answerDto.getBatchFlag()){
|
|
|
- ContextUtils.setBatchUserList(answerDto.getJobNo(),userInfo);
|
|
|
+ if (userInfo.getFinalScore() != null) {
|
|
|
+ double addScore = userInfo.getFinalScore() - startScore;
|
|
|
+ userInfo.setAddScores(DECIMAL_FORMAT.format(addScore));
|
|
|
+ } else {
|
|
|
+ userInfo.setAddScores("本次答题异常终止,无法计算分数");
|
|
|
}
|
|
|
- //关闭当前答题
|
|
|
- ContextUtils.setOnOff(answerDto.getJobNo(), false);
|
|
|
- String subject="";
|
|
|
if (throwable instanceof SgException ) {
|
|
|
- subject=throwable.getMessage();
|
|
|
-// if (((SgException) throwable).getCode() == ErrorCode.LOGOUT.getCode()) {
|
|
|
-// log.error("cookie已失效,移除本地用户:{}cookie", answerDto);
|
|
|
-// }
|
|
|
- if (((SgException) throwable).getCode() != ErrorCode.STOP_ANSWER.getCode()) {
|
|
|
- userInfo.setErrMsg(throwable.getMessage());
|
|
|
- }
|
|
|
+ userInfo.setErrMsg(throwable.getMessage());
|
|
|
}else {
|
|
|
userInfo.setErrMsg("本次答题异常终止");
|
|
|
- subject=userInfo.getUserName()+"【"+userInfo.getJobNo()+"】答题异常结束";
|
|
|
}
|
|
|
- int num = 0;
|
|
|
- if (!addScores.toString().equals("0.00")) {
|
|
|
- userInfo.setAddScores(addScores.toString());
|
|
|
- log.info("本次答题已结束,更新数据记录对应字段:{}", userInfo);
|
|
|
- num = answerDao.updatePassRecord(userInfo);
|
|
|
- } else {
|
|
|
- answerDao.deletePassRecord(userInfo.getId());
|
|
|
- log.error("本次答题增长积分小于等于0:{},不保存此数据", addScores);
|
|
|
+ handleBatchOperation(answerDto, userInfo);
|
|
|
+ return userInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /***
|
|
|
+ * 关闭答题
|
|
|
+ */
|
|
|
+ private void handleBatchOperation(AnswerDto answerDto, UserInfo userInfo) {
|
|
|
+ if (answerDto.getBatchFlag()) {
|
|
|
+ ContextUtils.setBatchUserList(answerDto.getJobNo(), userInfo);
|
|
|
}
|
|
|
- double startTime = LocalDateTime.parse(userInfo.getStartTime(),fmt).toEpochSecond(ZoneOffset.of("+8"));
|
|
|
- double endTime = LocalDateTime.parse(userInfo.getEndTime(),fmt).toEpochSecond(ZoneOffset.of("+8"));
|
|
|
- String elapsedTime =DateUtills.getElapsedTime(startTime,endTime);
|
|
|
- log.info("本次答题耗时:{}",elapsedTime);
|
|
|
- String loginFrom = answerDto.getBatchFlag()?"批量导入答题":"用户登录";
|
|
|
- String content = "<html>\n" +
|
|
|
- "<body>\n" +
|
|
|
- " <h3>用户:" + userInfo.getUserName() + " 已完成本次刷题,详情如下 :</h3>\n" +
|
|
|
- " <p>工号:" + userInfo.getJobNo() + "</p>" +
|
|
|
- " <p>登录方式:" +loginFrom + "</p>" +
|
|
|
- " <p>答题工种:<span style=\"color: blue;font-weight: bold\">" + answerDto.getJobTypeName() + "</span></p>" +
|
|
|
- " <p>初始分数:" + startScore + "</p>" +
|
|
|
- " <p>目标分数:" + userInfo.getTargetScore() + "</p>" +
|
|
|
- " <p>增长分数:<span style=\"color: blue;font-weight: bold\">" + addScores + "</span></p>" +
|
|
|
- " <p>最终分数:<span style=\"color: red;font-weight: bold\">" + userInfo.getFinalScore() + "</span></p>" +
|
|
|
- " <p>开始时间:" + userInfo.getStartTime() + "</p>" +
|
|
|
- " <p>结束时间:" + userInfo.getEndTime() + "</p>" +
|
|
|
- " <p>运行时间:<span style=\"color: blue;font-weight: bold\">" + elapsedTime + "</span></p>" +
|
|
|
- " <p>结束原因:<span style=\"color: red;font-weight: bold\">" + throwable.getMessage() + "</span></p>" +
|
|
|
- " <a href=\"http://49.232.153.218/answer\">更多详情请前往系统查看!</a>" +
|
|
|
- "</body>\n" +
|
|
|
- "</html>";
|
|
|
+ ContextUtils.setOnOff(answerDto.getJobNo(), false);
|
|
|
+ }
|
|
|
+
|
|
|
+ private int handleDatabaseOperation(UserInfo userInfo) {
|
|
|
try {
|
|
|
- if (num > 0) {
|
|
|
- if (ContextUtils.getBatchUserList().size()>10){
|
|
|
- toUser = new String[]{"251664727@qq.com"};
|
|
|
+ if (isValidAddScore(userInfo.getAddScores())) {
|
|
|
+ log.info("更新答题记录:{}", userInfo);
|
|
|
+ int updateCount = answerDao.updatePassRecord(userInfo);
|
|
|
+ if (updateCount == 0) {
|
|
|
+ log.error("数据库更新失败:{}", userInfo);
|
|
|
}
|
|
|
- mailService.sendSimpleMail(toUser, subject, content);
|
|
|
+ return updateCount;
|
|
|
} else {
|
|
|
- log.error("答题结算更新失败:{}", userInfo);
|
|
|
+ answerDao.deletePassRecord(userInfo.getId());
|
|
|
+ log.warn("无效分数,删除记录:{}", userInfo.getId());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ } catch (DataAccessException e) {
|
|
|
+ log.error("数据库操作异常:{}", e.getMessage());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isValidAddScore(String addScore) {
|
|
|
+ try {
|
|
|
+ return Double.parseDouble(addScore) > 0.0;
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void sendNotificationEmail(AnswerDto answerDto, UserInfo userInfo,
|
|
|
+ Double startScore, Throwable throwable,
|
|
|
+ int updateCount) {
|
|
|
+ try {
|
|
|
+ String[] recipients = determineRecipients(answerDto); //收件人邮箱
|
|
|
+ String subject = buildEmailSubject(throwable, userInfo); //邮件主题
|
|
|
+ String content = buildEmailContent(answerDto, userInfo, startScore, throwable); //邮件内容
|
|
|
+
|
|
|
+ if (updateCount > 0 && !userInfo.getAddScores().contains("异常终止")) {
|
|
|
+ mailService.sendSimpleMail(recipients, subject, content);
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
- log.error("邮件发送异常:{}", e);
|
|
|
+ log.error("邮件发送失败: ", e);
|
|
|
}
|
|
|
- //手动停止,不删除当前对象
|
|
|
- if (throwable instanceof SgException && ((SgException) throwable).getCode() == ErrorCode.STOP_ANSWER.getCode()) {
|
|
|
- return;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String[] determineRecipients(AnswerDto answerDto) {
|
|
|
+ if (answerDto.getBatchFlag() && ContextUtils.getBatchUserList().size() > 10) {
|
|
|
+ return new String[]{"251664727@qq.com"};
|
|
|
+ }
|
|
|
+ return DEFAULT_RECIPIENTS.clone();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildEmailSubject(Throwable throwable, UserInfo userInfo) {
|
|
|
+ return (throwable instanceof SgException) ?
|
|
|
+ throwable.getMessage() :
|
|
|
+ userInfo.getUserName() + "【" + userInfo.getJobNo() + "】答题异常结束";
|
|
|
+ }
|
|
|
+
|
|
|
+ public String buildEmailContent(AnswerDto answer, UserInfo user,
|
|
|
+ Double startScore, Throwable ex) {
|
|
|
+ Context ctx = new Context();
|
|
|
+
|
|
|
+ // 用户信息
|
|
|
+ ctx.setVariable("user", ImmutableMap.of(
|
|
|
+ "userName", user.getUserName(),
|
|
|
+ "jobNo", user.getJobNo(),
|
|
|
+ "addScores", user.getAddScores(),
|
|
|
+ "finalScore", user.getFinalScore()
|
|
|
+ ));
|
|
|
+ // 答题信息
|
|
|
+ ctx.setVariable("answer", ImmutableMap.of(
|
|
|
+ "batchFlag", answer.getBatchFlag(),
|
|
|
+ "jobTypeName", answer.getJobTypeName()
|
|
|
+ ));
|
|
|
+
|
|
|
+ // 分数卡片数据
|
|
|
+ ctx.setVariable("scores", Arrays.asList(
|
|
|
+ ImmutableMap.of("type", "初始分数", "value", startScore, "color", "#2196F3"),
|
|
|
+ ImmutableMap.of("type", "目标分数", "value", user.getTargetScore(), "color", "#FF9800")
|
|
|
+ ));
|
|
|
+
|
|
|
+ // 时间信息
|
|
|
+ ctx.setVariable("times", Arrays.asList(
|
|
|
+ ImmutableMap.of("label", "开始", "value", user.getStartTime()),
|
|
|
+ ImmutableMap.of("label", "结束", "value", user.getEndTime()),
|
|
|
+ ImmutableMap.of("label", "用时", "value", calculateElapsedTime(user))
|
|
|
+ ));
|
|
|
+
|
|
|
+ // 异常信息
|
|
|
+ if (ex != null) {
|
|
|
+ ctx.setVariable("exception", ImmutableMap.of(
|
|
|
+ "message", ex.getMessage()
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 服务器IP
|
|
|
+ ctx.setVariable("serverIp", SERVER_IP);
|
|
|
+
|
|
|
+ return templateEngine.process("answer-report", ctx);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算答题时常
|
|
|
+ */
|
|
|
+ private String calculateElapsedTime(UserInfo userInfo) {
|
|
|
+ try {
|
|
|
+ LocalDateTime start = LocalDateTime.parse(userInfo.getStartTime(), DATE_TIME_FORMATTER);
|
|
|
+ LocalDateTime end = LocalDateTime.parse(userInfo.getEndTime(), DATE_TIME_FORMATTER);
|
|
|
+ Duration duration = Duration.between(start, end);
|
|
|
+ return String.format("%d天%d时%d分", duration.toDays(), duration.toHours() % 24, duration.toMinutes() % 60);
|
|
|
+ } catch (Exception e) {
|
|
|
+ return "时间计算错误";
|
|
|
}
|
|
|
- //删除当前答题对象
|
|
|
- ContextUtils.removeUserCookies(answerDto.getJobNo());
|
|
|
- ContextUtils.removeUserInfo(answerDto.getJobNo());
|
|
|
- log.error("------Async无返回方法的异常处理方法--------:{}", throwable);
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 非手动结束情况下清楚用户缓存信息
|
|
|
+ */
|
|
|
+ private void cleanUpResources(AnswerDto answerDto, Throwable throwable) {
|
|
|
+ if (!isManualStop(throwable)) {
|
|
|
+ ContextUtils.removeUserCookies(answerDto.getJobNo());
|
|
|
+ ContextUtils.removeUserInfo(answerDto.getJobNo());
|
|
|
+ log.info("已清理用户资源:{}", answerDto.getJobNo());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isManualStop(Throwable throwable) {
|
|
|
+ return throwable instanceof SgException &&
|
|
|
+ ((SgException) throwable).getCode() == ErrorCode.STOP_ANSWER.getCode();
|
|
|
+ }
|
|
|
+
|
|
|
+ /* private String buildEmailContent(AnswerDto answerDto, UserInfo userInfo,
|
|
|
+ Double startScore, Throwable throwable) {
|
|
|
+ String elapsedTime = calculateElapsedTime(userInfo);
|
|
|
+
|
|
|
+ // 增强的响应式样式
|
|
|
+ String mediaQuery = "@media only screen and (max-width:480px) { "
|
|
|
+ + ".mobile-adjust { padding:12px 15px !important; } "
|
|
|
+ + ".section-title { font-size:18px !important; margin-bottom:8px !important; } "
|
|
|
+ + ".info-card { padding:12px !important; margin-bottom:16px !important; } "
|
|
|
+ + ".score-container { gap:10px !important; margin-bottom:16px !important; } "
|
|
|
+ + ".score-box { padding:12px !important; margin-bottom:12px !important; } "
|
|
|
+ + ".final-score { padding:16px !important; margin:12px 0 !important; } "
|
|
|
+ + ".time-record td { padding:4px 0 !important; } "
|
|
|
+ + ".system-alert { margin:16px 0 !important; padding:12px !important; } "
|
|
|
+ + ".action-button { padding:12px 24px !important; font-size:14px !important; } "
|
|
|
+ + "}";
|
|
|
+
|
|
|
+ return String.format("<html>"
|
|
|
+ + "<head>"
|
|
|
+ + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
|
|
|
+ + "<style>%s</style>"
|
|
|
+ + "</head>"
|
|
|
+ + "<body style=\"margin:0; padding:20px 0; background:#f5f5f5;\">"
|
|
|
+ + "<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"%s\">"
|
|
|
+ + "<tr><td class=\"mobile-adjust\" style=\"padding:20px; background:white;\">"
|
|
|
+
|
|
|
+ // 头部信息
|
|
|
+ + "<div style=\"border-bottom:2px solid #2196F3; padding-bottom:12px; margin-bottom:16px;\">"
|
|
|
+ + "<h2 class=\"section-title\" style=\"margin:0; font-size:24px;\">✅ 答题结束通知</h2>"
|
|
|
+ + "<p style=\"color:#7f8c8d; margin:6px 0; font-size:14px;\">"
|
|
|
+ + "用户:<strong>%s</strong> | 工号:%s"
|
|
|
+ + "</p>"
|
|
|
+ + "</div>"
|
|
|
+
|
|
|
+ // 答题概览
|
|
|
+ + "<div style=\"margin-bottom:20px;\">"
|
|
|
+ + "<div class=\"section-title\" style=\"color:#2c3e50; font-size:20px; margin-bottom:12px;\">📝 答题概览</div>"
|
|
|
+ + "<div class=\"info-card\" style=\"background:#f8f9fa; padding:15px; border-radius:8px;\">"
|
|
|
+ + "<p style=\"margin:4px 0; font-size:14px;\">"
|
|
|
+ + "<span style=\"color:#7f8c8d;\">模式:</span>%s"
|
|
|
+ + "</p>"
|
|
|
+ + "<p style=\"margin:4px 0; font-size:14px;\">"
|
|
|
+ + "<span style=\"color:#7f8c8d;\">工种:</span>"
|
|
|
+ + "<span style=\"color:#2196F3; font-weight:600;\">%s</span>"
|
|
|
+ + "</p>"
|
|
|
+ + "</div>"
|
|
|
+ + "</div>"
|
|
|
+
|
|
|
+ // 分数区块
|
|
|
+ + "<div class=\"score-container\" style=\"display:flex; gap:15px; margin-bottom:20px;\">"
|
|
|
+ + "<div class=\"score-box\" style=\"flex:1; background:#f3f5f7; padding:15px; border-radius:8px; box-sizing:border-box;\">"
|
|
|
+ + "<p style=\"margin:2px 0; color:#7f8c8d; font-size:14px;\">初始分数</p>"
|
|
|
+ + "<p style=\"font-size:24px; margin:6px 0; color:#2196F3;\">%.1f</p>"
|
|
|
+ + "</div>"
|
|
|
+ + "<div class=\"score-box\" style=\"flex:1; background:#f3f5f7; padding:15px; border-radius:8px; box-sizing:border-box;\">"
|
|
|
+ + "<p style=\"margin:2px 0; color:#7f8c8d; font-size:14px;\">目标分数</p>"
|
|
|
+ + "<p style=\"font-size:24px; margin:6px 0; color:#FF9800;\">%s</p>"
|
|
|
+ + "</div>"
|
|
|
+ + "</div>"
|
|
|
+
|
|
|
+ // 增长分数
|
|
|
+ + "<div class=\"score-box\" style=\"text-align:center; background:#4CAF50; padding:15px; border-radius:8px;\">"
|
|
|
+ + "<p style=\"margin:0; color:white; font-size:16px; font-weight:bold;\">"
|
|
|
+ + "+%s 分数增长"
|
|
|
+ + "</p>"
|
|
|
+ + "</div>"
|
|
|
+
|
|
|
+ // 最终分数
|
|
|
+ + "<div class=\"final-score\" style=\"text-align:center; background:white; padding:20px; margin:20px 0; border-radius:8px; box-shadow:0 2px 8px rgba(0,0,0,0.1);\">"
|
|
|
+ + "<p style=\"margin:4px 0; color:#7f8c8d; font-size:14px;\">最终得分</p>"
|
|
|
+ + "<p style=\"font-size:32px; margin:8px 0; color:#4CAF50; font-weight:800;\">%s</p>"
|
|
|
+ + "</div>"
|
|
|
+
|
|
|
+ // 时间记录
|
|
|
+ + "<div>"
|
|
|
+ + "<div class=\"section-title\" style=\"color:#2c3e50; font-size:20px; margin:12px 0;\">⏱ 时间记录</div>"
|
|
|
+ + "<table class=\"time-record\" style=\"width:100%%; font-size:14px;\">"
|
|
|
+ + "<tr><td style=\"padding:4px 0; color:#2196F3;\">▸</td><td style=\"padding:4px 0;\">开始:%s</td></tr>"
|
|
|
+ + "<tr><td style=\"padding:4px 0; color:#2196F3;\">▸</td><td style=\"padding:4px 0;\">结束:%s</td></tr>"
|
|
|
+ + "<tr><td style=\"padding:4px 0; color:#2196F3;\">▸</td><td style=\"padding:4px 0;\">用时:%s</td></tr>"
|
|
|
+ + "</table>"
|
|
|
+ + "</div>"
|
|
|
+
|
|
|
+ // 系统提示
|
|
|
+ + "<div class=\"system-alert\" style=\"margin:25px 0; padding:15px; background:#fff3e0; border-radius:8px;\">"
|
|
|
+ + "<p style=\"margin:4px 0; color:#EF6C00; font-size:14px;\">%s</p>"
|
|
|
+ + "</div>"
|
|
|
+
|
|
|
+ // 操作按钮
|
|
|
+ + "<div style=\"text-align:center; margin-top:30px;\">"
|
|
|
+ + "<a href=\"%s/answer\" class=\"action-button\" "
|
|
|
+ + "style=\"display:inline-block; background:#2196F3; color:white!important; "
|
|
|
+ + "padding:16px 40px; text-decoration:none; border-radius:30px; font-size:16px; "
|
|
|
+ + "letter-spacing:1px; min-width:200px;\">🚀 前往答题系统查看</a>"
|
|
|
+ + "</div>"
|
|
|
+
|
|
|
+ + "</td></tr>"
|
|
|
+ + "</table>"
|
|
|
+ + "</body></html>",
|
|
|
+ // 参数列表
|
|
|
+ mediaQuery,
|
|
|
+ "width:100% !important; max-width:600px; margin:0 auto; font-family:'Segoe UI',Tahoma,sans-serif; border-collapse:collapse;",
|
|
|
+ userInfo.getUserName(),
|
|
|
+ userInfo.getJobNo(),
|
|
|
+ answerDto.getBatchFlag() ? "批量模式" : "单用户模式",
|
|
|
+ answerDto.getJobTypeName(),
|
|
|
+ startScore,
|
|
|
+ userInfo.getTargetScore(),
|
|
|
+ userInfo.getAddScores(),
|
|
|
+ userInfo.getFinalScore(),
|
|
|
+ userInfo.getStartTime(),
|
|
|
+ userInfo.getEndTime(),
|
|
|
+ elapsedTime,
|
|
|
+ throwable != null ? "终止事件:" + throwable.getMessage() : "异常终止",
|
|
|
+ SERVER_IP
|
|
|
+ );
|
|
|
+ }*/
|
|
|
}
|
|
|
}
|