| 
					
				 | 
			
			
				@@ -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 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        }*/ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 |