Browse Source

bug修复

master
luogw 2 months ago
parent
commit
24f2443864
  1. 31
      src/main/java/com/project/classicpaper/application/impl/ClassicPaperSetApplicationServiceImpl.java
  2. 17
      src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java
  3. 8
      src/main/java/com/project/interaction/config/WebClientConfig.java
  4. 9
      src/main/java/com/project/interaction/domain/service/impl/PostToAiScoringDomainServiceImpl.java
  5. 22
      src/main/java/com/project/statistics/domain/service/impl/ExamTaskStatisticsDomainServiceImpl.java
  6. 2
      src/main/resources/application-dev.yml
  7. 1
      src/main/resources/application-prod.yml
  8. 1
      src/main/resources/application-test.yml

31
src/main/java/com/project/classicpaper/application/impl/ClassicPaperSetApplicationServiceImpl.java

@ -265,18 +265,27 @@ public class ClassicPaperSetApplicationServiceImpl implements ClassicPaperSetApp
if (StrUtil.isBlank(paperSet.getScoreRatio())) { if (StrUtil.isBlank(paperSet.getScoreRatio())) {
return; return;
} }
Map<String, Integer> ratioMap; ObjectMapper mapper = new ObjectMapper();
try { String scoreRatio = paperSet.getScoreRatio();
String scoreRatio = paperSet.getScoreRatio();
// DB 中存的是带转义符的 JSON 字符串,需先还原 // JacksonTypeHandler 会在 String 字段上额外包裹一层 JSON 字符串,
scoreRatio = scoreRatio.replace("\\\"", "\""); // 导致实际值有多层转义。循环剥离直到能解析为 Map
// 可能有外层引号,去掉 Map<String, Integer> ratioMap = null;
if (scoreRatio.startsWith("\"") && scoreRatio.endsWith("\"")) { for (int i = 0; i < 5; i++) {
scoreRatio = scoreRatio.substring(1, scoreRatio.length() - 1); if (StrUtil.isBlank(scoreRatio)) break;
try {
ratioMap = mapper.readValue(scoreRatio, Map.class);
break;
} catch (Exception e) {
if (scoreRatio.startsWith("\"") && scoreRatio.endsWith("\"")) {
scoreRatio = scoreRatio.substring(1, scoreRatio.length() - 1);
}
scoreRatio = scoreRatio.replace("\\\"", "\"").replace("\\\\", "\\");
} }
ratioMap = new ObjectMapper().readValue(scoreRatio, Map.class); }
} catch (Exception e) {
log.warn(">>> [经典套题] scoreRatio 解析失败, setId={}", paperSet.getId(), e); if (ratioMap == null) {
log.warn(">>> [经典套题] scoreRatio 解析失败, setId={}", paperSet.getId());
return; return;
} }

17
src/main/java/com/project/exam/domain/job/ShortAnswerScoringJob.java

@ -26,6 +26,8 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -210,19 +212,22 @@ public class ShortAnswerScoringJob {
&& response.getData() != null) { && response.getData() != null) {
// 计算各得分点得分和命中点 // 计算各得分点得分和命中点
double pointsValue = totalPoints > 0 ? shortAnswerScore / totalPoints : 0.0;
double aiScore = 0.0; double aiScore = 0.0;
List<Integer> hitPoints = new ArrayList<>(); List<Integer> hitPoints = new ArrayList<>();
StringBuilder comment = new StringBuilder(); StringBuilder comment = new StringBuilder();
boolean allFullScore = true;
double pointsValue = totalPoints > 0 ? shortAnswerScore / totalPoints : 0.0;
for (int i = 0; i < response.getData().size(); i++) { for (int i = 0; i < response.getData().size(); i++) {
AiScoringResponseDTO.PointScore ps = response.getData().get(i); AiScoringResponseDTO.PointScore ps = response.getData().get(i);
int algoScore = ps.getScore() != null ? ps.getScore() : 0; double algoScore = ps.getScore() != null ? ps.getScore() : 0D;
double pointScore = pointsValue * algoScore; aiScore += pointsValue * algoScore;
aiScore += pointScore;
if (algoScore > 0) { if (algoScore > 0) {
hitPoints.add(i); hitPoints.add(i);
} }
if (algoScore < 1.0) {
allFullScore = false;
}
if (ps.getReason() != null && !ps.getReason().isEmpty()) { if (ps.getReason() != null && !ps.getReason().isEmpty()) {
if (!comment.isEmpty()) { if (!comment.isEmpty()) {
comment.append("; "); comment.append("; ");
@ -231,6 +236,10 @@ public class ShortAnswerScoringJob {
} }
} }
// 全部答对直接给满分,否则按比例计算并四舍五入保留两位小数
aiScore = allFullScore ? shortAnswerScore
: BigDecimal.valueOf(aiScore).setScale(2, RoundingMode.HALF_UP).doubleValue();
snapshot.setAiScore(aiScore); snapshot.setAiScore(aiScore);
snapshot.setAiComment(comment.toString()); snapshot.setAiComment(comment.toString());
snapshot.setHitPoints(hitPoints); snapshot.setHitPoints(hitPoints);

8
src/main/java/com/project/interaction/config/WebClientConfig.java

@ -22,13 +22,13 @@ public class WebClientConfig {
public WebClient algorithmWebClient() { public WebClient algorithmWebClient() {
// 配置HTTP客户端,设置长超时以支持流式响应 // 配置HTTP客户端,设置长超时以支持流式响应
HttpClient httpClient = HttpClient.create() HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(60)) .responseTimeout(Duration.ofSeconds(180))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 180000)
.doOnConnected(conn -> conn .doOnConnected(conn -> conn
// 读超时 // 读超时
.addHandlerLast(new ReadTimeoutHandler(60, TimeUnit.SECONDS)) .addHandlerLast(new ReadTimeoutHandler(180, TimeUnit.SECONDS))
// 写超时 // 写超时
.addHandlerLast(new WriteTimeoutHandler(30, TimeUnit.SECONDS)) .addHandlerLast(new WriteTimeoutHandler(180, TimeUnit.SECONDS))
); );
return WebClient.builder() return WebClient.builder()

9
src/main/java/com/project/interaction/domain/service/impl/PostToAiScoringDomainServiceImpl.java

@ -25,12 +25,9 @@ public class PostToAiScoringDomainServiceImpl implements PostToAiScoringDomainSe
private WebClient algorithmWebClient; private WebClient algorithmWebClient;
/** 算法服务阅卷路径 */ /** 算法服务阅卷路径 */
@Value("${algo.scoringUrl:/v1/score/short_answer}") @Value("${algo.scoringUrl:http://172.16.204.50:8010/v1/score/short_answer}")
private String scoringUrl; private String scoringUrl;
@Value("${algo.apiUrl:http://172.16.25.174:8000}")
private String apiUrl;
private final ObjectMapper objectMapper = new ObjectMapper(); private final ObjectMapper objectMapper = new ObjectMapper();
@Override @Override
@ -39,11 +36,11 @@ public class PostToAiScoringDomainServiceImpl implements PostToAiScoringDomainSe
log.info(">>> [AI阅卷] 正在请求AI阅卷, question={}", request.getQuestion()); log.info(">>> [AI阅卷] 正在请求AI阅卷, question={}", request.getQuestion());
String responseBody = algorithmWebClient.post() String responseBody = algorithmWebClient.post()
.uri(apiUrl+scoringUrl) .uri(scoringUrl)
.bodyValue(request) .bodyValue(request)
.retrieve() .retrieve()
.bodyToMono(String.class) .bodyToMono(String.class)
.timeout(Duration.ofSeconds(60)) .timeout(Duration.ofSeconds(180))
.block(); .block();
log.info(">>> [AI阅卷] 算法服务返回, question={}, response={}", log.info(">>> [AI阅卷] 算法服务返回, question={}, response={}",

22
src/main/java/com/project/statistics/domain/service/impl/ExamTaskStatisticsDomainServiceImpl.java

@ -75,21 +75,31 @@ public class ExamTaskStatisticsDomainServiceImpl implements ExamTaskStatisticsDo
.divide(BigDecimal.valueOf(totalUserCount), 2, RoundingMode.HALF_UP); .divide(BigDecimal.valueOf(totalUserCount), 2, RoundingMode.HALF_UP);
dto.setParticipationRate(formatValue(participationRate.doubleValue())); dto.setParticipationRate(formatValue(participationRate.doubleValue()));
// 6. 计算平均得分 = 累加考试得分 / 已提交记录数 // 6. 每人只取最新一次考试记录,用于计算平均分和通过率
double totalScore = submittedRecordList.stream() List<ExamRecordEntity> latestRecordPerUser = submittedRecordList.stream()
.collect(Collectors.toMap(
ExamRecordEntity::getTaskUserId,
record -> record,
(r1, r2) -> r1.getSubmitTime().compareTo(r2.getSubmitTime()) >= 0 ? r1 : r2
))
.values().stream()
.toList();
// 7. 计算平均得分 = 每人最新一次得分的累加 / 人数
double totalScore = latestRecordPerUser.stream()
.mapToDouble(record -> record.getScore() != null ? record.getScore() : 0.0) .mapToDouble(record -> record.getScore() != null ? record.getScore() : 0.0)
.sum(); .sum();
BigDecimal averageScore = BigDecimal.valueOf(totalScore) BigDecimal averageScore = BigDecimal.valueOf(totalScore)
.divide(BigDecimal.valueOf(submittedRecordList.size()), 2, RoundingMode.HALF_UP); .divide(BigDecimal.valueOf(latestRecordPerUser.size()), 2, RoundingMode.HALF_UP);
dto.setAverageScore(formatValue(averageScore.doubleValue())); dto.setAverageScore(formatValue(averageScore.doubleValue()));
// 7. 计算通过率 = 通过的记录数 / 已提交记录数 * 100 // 8. 计算通过率 = 最新一次通过的记录数 / 人数 * 100
long passCount = submittedRecordList.stream() long passCount = latestRecordPerUser.stream()
.filter(record -> Boolean.TRUE.equals(record.getPass())) .filter(record -> Boolean.TRUE.equals(record.getPass()))
.count(); .count();
BigDecimal passRate = BigDecimal.valueOf(passCount) BigDecimal passRate = BigDecimal.valueOf(passCount)
.multiply(BigDecimal.valueOf(100)) .multiply(BigDecimal.valueOf(100))
.divide(BigDecimal.valueOf(submittedRecordList.size()), 2, RoundingMode.HALF_UP); .divide(BigDecimal.valueOf(latestRecordPerUser.size()), 2, RoundingMode.HALF_UP);
dto.setPassRate(formatValue(passRate.doubleValue())); dto.setPassRate(formatValue(passRate.doubleValue()));
return Result.success(dto); return Result.success(dto);

2
src/main/resources/application-dev.yml

@ -54,6 +54,7 @@ minio:
accessKey: ${MINIO_ASSESSKEY:DTKYZDZM1i31XOvd24SP} accessKey: ${MINIO_ASSESSKEY:DTKYZDZM1i31XOvd24SP}
secretKey: ${MINIO_SECRETKEY:PnfLPcJbvaUboZIwYZAADPB0pDtPZgbi0QiLSs3C} secretKey: ${MINIO_SECRETKEY:PnfLPcJbvaUboZIwYZAADPB0pDtPZgbi0QiLSs3C}
bucket: ${MINIO_BUCKET:ai-evaluator} bucket: ${MINIO_BUCKET:ai-evaluator}
tempAccessFileUrl: http://172.16.204.50/minio-api
mybatis-plus: mybatis-plus:
configuration: configuration:
map-underscore-to-camel-case: true map-underscore-to-camel-case: true
@ -85,6 +86,7 @@ algo:
apiUrl: http://172.16.204.50:8000 apiUrl: http://172.16.204.50:8000
extractUrl: /v1/key_points/extract extractUrl: /v1/key_points/extract
callbackUrl: http://172.16.204.50/evaluator-api callbackUrl: http://172.16.204.50/evaluator-api
scoringUrl: http://172.16.204.50:8000/v1/score/short_answer
jwt: jwt:
secret: "my-very-fixed-and-secure-secret-key-1234567890" secret: "my-very-fixed-and-secure-secret-key-1234567890"

1
src/main/resources/application-prod.yml

@ -84,6 +84,7 @@ algo:
apiUrl: http://127.0.0.1:8000 apiUrl: http://127.0.0.1:8000
extractUrl: /v1/key_points/extract extractUrl: /v1/key_points/extract
callbackUrl: http://8.129.84.155/evaluator-api callbackUrl: http://8.129.84.155/evaluator-api
scoringUrl: http://127.0.0.1:8010/v1/score/short_answer
jwt: jwt:
secret: "my-very-fixed-and-secure-secret-key-1234567890" secret: "my-very-fixed-and-secure-secret-key-1234567890"

1
src/main/resources/application-test.yml

@ -81,6 +81,7 @@ algo:
apiUrl: http://127.0.0.1:8000/ apiUrl: http://127.0.0.1:8000/
extractUrl: /v1/key_points/extract extractUrl: /v1/key_points/extract
callbackUrl: http://47.119.114.204/evaluator-api callbackUrl: http://47.119.114.204/evaluator-api
scoringUrl: http://47.119.114.204:8010/v1/score/short_answer
jwt: jwt:
secret: "my-very-fixed-and-secure-secret-key-1234567890" secret: "my-very-fixed-and-secure-secret-key-1234567890"

Loading…
Cancel
Save