This commit is contained in:
zhouhefeng
2026-06-15 14:20:03 +08:00
parent 873213de53
commit 601e1ac039
9 changed files with 5487 additions and 0 deletions
@@ -0,0 +1,21 @@
package com.budwk.app.zhgh.dayofficework.qsv.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.annotations.ApiOperation;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@IocBean
@At("/platform/qsv")
@Ok("json:full")
@ApiOperation("问卷调查新建")
public class QsvNewController {
@At("/new")
@Ok("beetl:/platform/zhgh/dayofficework/qsv/activity/new.html")
@SaCheckPermission("qsv.activity")
public void newPage() {
}
}
@@ -0,0 +1,562 @@
package com.budwk.app.zhgh.dayofficework.qsv.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvOption;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord;
import com.budwk.app.zhgh.dayofficework.qsv.param.QsvAnswerParam;
import com.budwk.app.zhgh.dayofficework.qsv.dto.QsvCheckAnswerResult;
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvQuizService;
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvUserAnswerRecordService;
import io.swagger.annotations.ApiOperation;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@IocBean
@At("/platform/qsv/online")
@Ok("json:full")
@ApiOperation("在线答题")
public class QsvOnlineController {
@Inject
private Dao dao;
@Inject
private QsvUserAnswerRecordService qsvUserAnswerRecordService;
@Inject
private QsvQuizService qsvQuizService;
@At("")
@Ok("beetl:/platform/zhgh/dayofficework/qsv/online/index.html")
@SaCheckPermission("qsv.online")
public void index() {
}
@At
@SaCheckPermission("qsv.online")
@ApiOperation("在线答题任务分页")
public Result pageData(@Valid PageForm pageForm, Integer year, String title, Boolean isAnswered) {
List<Integer> groupIds = dao.query(ActivityUserScope.class, Cnd.where("userId", "=", SecurityUtil.getUserId()))
.stream().map(ActivityUserScope::getGroupId).collect(Collectors.toList());
if (groupIds.isEmpty()) {
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), 0, new ArrayList<>()));
}
Cnd cnd = Cnd.where("enabled", "=", true);
cnd.and("groupId", "in", groupIds);
cnd.andEX("YEAR(startTime)", "=", year);
cnd.and(Cnd.likeEX("title", title));
cnd.desc("startTime");
int totalCount = dao.count(QsvActivity.class, cnd);
if (totalCount == 0) {
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), 0, new ArrayList<>()));
}
List<QsvActivity> activities = dao.query(QsvActivity.class, cnd);
List<String> activityIds = activities.stream().map(QsvActivity::getId).collect(Collectors.toList());
Map<String, List<QsvUserAnswerRecord>> recordGroup = dao.query(QsvUserAnswerRecord.class, Cnd.where("userId", "=", SecurityUtil.getUserId())
.and("activityId", "in", activityIds))
.stream().collect(Collectors.groupingBy(QsvUserAnswerRecord::getActivityId));
List<NutMap> rows = activities.stream()
.map((activity) -> buildOnlineRow(activity, recordGroup.getOrDefault(activity.getId(), new ArrayList<>())))
.filter((row) -> {
if (Boolean.TRUE.equals(isAnswered)) {
return row.getBoolean("isAnswered");
}
return !row.getBoolean("isAnswered") || row.getBoolean("canAnswer");
})
.collect(Collectors.toList());
int filteredTotal = rows.size();
int fromIndex = Math.min((pageForm.getPageNumber() - 1) * pageForm.getPageSize(), filteredTotal);
int toIndex = Math.min(fromIndex + pageForm.getPageSize(), filteredTotal);
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), filteredTotal, rows.subList(fromIndex, toIndex)));
}
@At
@SaCheckPermission("qsv.online")
@Aop(TransAop.READ_COMMITTED)
@ApiOperation("在线答题题目")
public Result subjects(@Valid String activityId, Boolean viewOnly) {
QsvActivity activity = dao.fetch(QsvActivity.class, activityId);
if (activity == null) {
return Result.error("调查不存在");
}
QsvUserAnswerRecord answerRecord = findCurrentAnswerRecord(activityId, Boolean.TRUE.equals(viewOnly));
if (Boolean.TRUE.equals(viewOnly) && answerRecord == null) {
return Result.error("未找到已完成的答题记录");
}
boolean finishedView = Boolean.TRUE.equals(viewOnly) && answerRecord != null && Boolean.TRUE.equals(answerRecord.getIsFinish());
if (!finishedView) {
Result activityStatusResult = checkActivityStatus(activity);
if (activityStatusResult != null) {
return activityStatusResult;
}
}
Result scopeResult = checkUserScope(activity);
if (scopeResult != null) {
return scopeResult;
}
answerRecord = prepareAnswerRecord(activity, answerRecord);
List<QsvSubject> activitySubjects = queryActivitySubjectsForSync(activity);
syncAnswerRecordSubjects(answerRecord, activity, activitySubjects);
List<QsvSubject> subjects = querySubjectsByRecordOrder(answerRecord.getSubjectIds());
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
subjects.forEach((subject) -> {
if (subject.getUserSelectOptionIds() == null) {
subject.setUserSelectOptionIds(new ArrayList<>());
}
});
return Result.success(NutMap.NEW()
.addv("activity", activity)
.addv("subjects", subjects)
.addv("answerRecordId", answerRecord.getId()));
}
@At
@SaCheckPermission("qsv.online")
@ApiOperation("在线答题记录")
public Result answerRecord(@Valid String answerRecordId) {
QsvUserAnswerRecord record = dao.fetch(QsvUserAnswerRecord.class, answerRecordId);
return Result.success(record);
}
@At
@SaCheckPermission("qsv.online")
@ApiOperation("在线投票统计")
public Result voteStats(@Valid String activityId) {
QsvActivity activity = dao.fetch(QsvActivity.class, activityId);
if (activity == null || !"VOTE".equals(activity.getCategory())) {
return Result.error("投票不存在");
}
Result scopeResult = checkUserScope(activity);
if (scopeResult != null) {
return scopeResult;
}
return Result.success(buildVoteStats(activityId));
}
@At
@SaCheckPermission("qsv.online")
@Aop(TransAop.READ_COMMITTED)
@ApiOperation("提交在线答题")
public Result submitAnswer(@Valid @Param("answer") QsvAnswerParam qsvAnswerParam) {
QsvActivity activity = dao.fetch(QsvActivity.class, qsvAnswerParam.getActivityId());
Result activityStatusResult = checkActivityStatus(activity);
if (activityStatusResult != null) {
return activityStatusResult;
}
Result scopeResult = checkUserScope(activity);
if (scopeResult != null) {
return scopeResult;
}
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, qsvAnswerParam.getAnswerRecordId());
if (answerRecord == null || !SecurityUtil.getUserId().equals(answerRecord.getUserId())) {
return Result.error("答题记录不存在");
}
if (Boolean.TRUE.equals(answerRecord.getIsFinish())) {
return Result.error("您已完成该调查");
}
JSONObject extJson = answerRecord.getExtJson();
if (extJson == null) {
extJson = new JSONObject();
}
float totalScore = 0;
boolean isQuiz = "QUIZ".equals(activity.getCategory());
for (QsvAnswerParam.Subject subject : qsvAnswerParam.getSubjects()) {
JSONObject entries = extJson.get(subject.getId(), JSONObject.class);
if (ObjectUtil.isEmpty(entries)) {
entries = new JSONObject();
}
entries.set("optionIds", subject.getUserSelectOptionIds());
entries.set("text", subject.getUserFillContent());
entries.set("optionFillContents", subject.getOptionFillContents());
if (isQuiz) {
QsvCheckAnswerResult answerResult = qsvQuizService.calcScore(subject.getId(), subject.getUserSelectOptionIds());
entries.set("score", answerResult.getScore());
entries.set("isCorrect", answerResult.isCorrect());
totalScore += answerResult.getScore();
}
extJson.set(subject.getId(), entries);
}
answerRecord.setExtJson(extJson);
if (isQuiz) {
answerRecord.setTotalScore(totalScore);
answerRecord.setAnswerTime(qsvAnswerParam.getAnswerTime());
}
answerRecord.setAttemptDate(new Date());
answerRecord.setSubmitTime(new Date());
answerRecord.setIsFinish(true);
dao.update(answerRecord);
if (isQuiz) {
qsvUserAnswerRecordService.calcByScoreMode(qsvAnswerParam.getActivityId(), SecurityUtil.getUserId());
}
return Result.success("提交成功");
}
private Result checkActivityStatus(QsvActivity activity) {
if (activity == null) {
return Result.error("调查不存在");
}
if (Boolean.FALSE.equals(activity.getEnabled())) {
return Result.error("活动已关闭,暂不能参与");
}
Date now = new Date();
if (activity.getStartTime() != null && activity.getStartTime().after(now)) {
return Result.error("调查尚未开始,请在开始后再参与");
}
if (activity.getEndTime() != null && activity.getEndTime().before(now)) {
return Result.error("调查已结束");
}
return null;
}
private Result checkUserScope(QsvActivity activity) {
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
.and("userId", "=", SecurityUtil.getUserId()));
if (count != 1) {
return Result.error("您无需参加此次调查,感谢您的关注!");
}
return null;
}
private NutMap buildOnlineRow(QsvActivity activity, List<QsvUserAnswerRecord> records) {
boolean isAnswered = records.stream().anyMatch((record) -> Boolean.TRUE.equals(record.getIsFinish()));
boolean hasUnfinished = records.stream().anyMatch((record) -> !Boolean.TRUE.equals(record.getIsFinish()));
boolean canRepeat = "QUIZ".equals(activity.getCategory())
&& Boolean.TRUE.equals(activity.getRepeatable())
&& ("SCHEDULED".equals(activity.getMode()) || ("REGULAR".equals(activity.getMode()) && "ALL".equals(activity.getDisplayMode())));
boolean randomDisplayMode = "QUIZ".equals(activity.getCategory())
&& "REGULAR".equals(activity.getMode())
&& "RANDOM".equals(activity.getDisplayMode());
int maxAttempts = activity.getMaxAttempts() == null || activity.getMaxAttempts() < 1 ? 1 : activity.getMaxAttempts();
int totalRandom = activity.getTotalRandom() == null || activity.getTotalRandom() < 1 ? 1 : activity.getTotalRandom();
long attemptCount = records.size();
if ("SCHEDULED".equals(activity.getMode())) {
attemptCount = records.stream()
.filter((record) -> record.getAttemptDate() != null && DateUtil.isSameDay(record.getAttemptDate(), DateUtil.date()))
.count();
}
Date now = new Date();
boolean notStarted = activity.getStartTime() != null && activity.getStartTime().after(now);
boolean ended = activity.getEndTime() != null && activity.getEndTime().before(now);
boolean activityAvailable = Boolean.TRUE.equals(activity.getEnabled()) && !notStarted && !ended;
boolean canAnswerByAttempt = hasUnfinished || !isAnswered || (canRepeat && attemptCount < maxAttempts) || (randomDisplayMode && attemptCount < totalRandom);
boolean canAnswer = activityAvailable && canAnswerByAttempt;
boolean isVote = "VOTE".equals(activity.getCategory());
String actionText;
String actionType;
String statusText;
String statusType;
if (!activityAvailable && !isAnswered) {
if (!Boolean.TRUE.equals(activity.getEnabled())) {
actionText = "已关闭";
statusText = "已关闭";
} else if (notStarted) {
actionText = isVote ? "未开始投票" : "未开始答题";
statusText = "未开始";
} else {
actionText = isVote ? "投票已结束" : "答题已结束";
statusText = "已结束";
}
actionType = "unavailable";
statusType = "info";
} else if (hasUnfinished && canAnswer) {
actionText = isVote ? "继续投票" : "继续答题";
actionType = "answer";
statusText = isVote ? "投票中" : "答题中";
statusType = "warning";
} else if (!isAnswered && canAnswer) {
actionText = isVote ? "投票" : "答题";
actionType = "answer";
statusText = isVote ? "未投票" : "未答题";
statusType = "warning";
} else if (canAnswer) {
actionText = isVote ? "再次投票" : "再次答题";
actionType = "answer";
statusText = isVote ? "可再次投票" : "可再次答题";
statusType = "warning";
} else {
actionText = isVote ? "查看投票" : "查看答题";
actionType = "view";
statusText = isVote ? "已投票" : "已答题";
statusType = "success";
}
Optional<QsvUserAnswerRecord> latestFinishedRecord = records.stream()
.filter((record) -> Boolean.TRUE.equals(record.getIsFinish()))
.max((first, second) -> {
long firstTime = first.getSubmitTime() == null ? 0 : first.getSubmitTime().getTime();
long secondTime = second.getSubmitTime() == null ? 0 : second.getSubmitTime().getTime();
return Long.compare(firstTime, secondTime);
});
return NutMap.NEW()
.addv("id", activity.getId())
.addv("title", activity.getTitle())
.addv("category", activity.getCategory())
.addv("startTime", activity.getStartTime())
.addv("endTime", activity.getEndTime())
.addv("enabled", activity.getEnabled())
.addv("isAnswered", isAnswered)
.addv("hasUnfinished", hasUnfinished)
.addv("canAnswer", canAnswer)
.addv("notStarted", notStarted)
.addv("ended", ended)
.addv("canRepeat", canRepeat)
.addv("answeredCount", records.stream().filter((record) -> Boolean.TRUE.equals(record.getIsFinish())).count())
.addv("remainingAttempts", canRepeat ? Math.max(maxAttempts - attemptCount, 0) : (randomDisplayMode ? Math.max(totalRandom - attemptCount, 0) : 0))
.addv("actionText", actionText)
.addv("actionType", actionType)
.addv("statusText", statusText)
.addv("statusType", statusType)
.addv("totalScore", latestFinishedRecord.map(QsvUserAnswerRecord::getTotalScore).orElse(null));
}
private QsvUserAnswerRecord findCurrentAnswerRecord(String activityId, boolean viewOnly) {
Cnd cnd = Cnd.where("activityId", "=", activityId)
.and("userId", "=", SecurityUtil.getUserId());
if (viewOnly) {
cnd.and("isFinish", "=", true).desc("submitTime").desc("createdAt");
} else {
cnd.and(Cnd.exps("isFinish", "=", false).or("isFinish", "is", null)).desc("createdAt");
}
return dao.fetch(QsvUserAnswerRecord.class, cnd);
}
private QsvUserAnswerRecord prepareAnswerRecord(QsvActivity activity, QsvUserAnswerRecord answerRecord) {
if (answerRecord != null) {
return answerRecord;
}
if ("QUIZ".equals(activity.getCategory())
&& "REGULAR".equals(activity.getMode())
&& "RANDOM".equals(activity.getDisplayMode())) {
return createRandomAnswerRecord(activity);
}
List<QsvSubject> activitySubjects = queryActivitySubjectsForSync(activity);
return qsvUserAnswerRecordService.insertRecord(activity.getId(), activitySubjects.stream().map(QsvSubject::getId).collect(Collectors.toList()));
}
private QsvUserAnswerRecord createRandomAnswerRecord(QsvActivity activity) {
Integer randomCount = activity.getRandomCount();
Integer totalRandom = activity.getTotalRandom();
if (randomCount == null || randomCount < 1) {
return qsvUserAnswerRecordService.insertRecord(activity.getId(), new ArrayList<>());
}
if (totalRandom == null || totalRandom < 1) {
totalRandom = 1;
}
List<QsvUserAnswerRecord> answerRecords = dao.query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activity.getId())
.and("userId", "=", SecurityUtil.getUserId()));
if (answerRecords.size() >= totalRandom) {
return answerRecords.stream()
.max((first, second) -> Integer.compare(first.getRandomNumber() == null ? 0 : first.getRandomNumber(), second.getRandomNumber() == null ? 0 : second.getRandomNumber()))
.orElse(null);
}
List<String> usedSubjectIds = answerRecords.stream()
.filter((record) -> record.getSubjectIds() != null)
.flatMap((record) -> record.getSubjectIds().stream())
.collect(Collectors.toList());
Cnd subjectCnd = Cnd.where("activityId", "=", activity.getId());
if (!usedSubjectIds.isEmpty()) {
subjectCnd.and("id", "not in", usedSubjectIds);
}
List<QsvSubject> subjects = dao.query(QsvSubject.class, subjectCnd.asc("sortNum"));
if (subjects.size() < randomCount) {
throw new RuntimeException("题目数不够,无法生成题目");
}
Collections.shuffle(subjects);
List<String> randomSubjectIds = subjects.subList(0, randomCount).stream().map(QsvSubject::getId).collect(Collectors.toList());
return qsvUserAnswerRecordService.insertRecord(activity.getId(), randomSubjectIds);
}
private List<QsvSubject> queryActivitySubjectsForSync(QsvActivity activity) {
Cnd cnd = Cnd.where("activityId", "=", activity.getId());
if ("QUIZ".equals(activity.getCategory())
&& "SCHEDULED".equals(activity.getMode())) {
cnd.and("displayDate", "=", DateUtil.today());
}
return dao.query(QsvSubject.class, cnd.asc("sortNum"));
}
private void syncAnswerRecordSubjects(QsvUserAnswerRecord answerRecord, QsvActivity activity, List<QsvSubject> activitySubjects) {
if (answerRecord == null || Boolean.TRUE.equals(answerRecord.getIsFinish())) {
return;
}
List<String> currentSubjectIds = activitySubjects.stream().map(QsvSubject::getId).collect(Collectors.toList());
List<String> recordSubjectIds = answerRecord.getSubjectIds();
if (recordSubjectIds == null) {
recordSubjectIds = new ArrayList<>();
}
JSONObject extJson = answerRecord.getExtJson();
if (extJson == null) {
extJson = new JSONObject();
}
boolean randomDisplayMode = "QUIZ".equals(activity.getCategory())
&& "REGULAR".equals(activity.getMode())
&& "RANDOM".equals(activity.getDisplayMode());
LinkedHashSet<String> currentSubjectIdSet = new LinkedHashSet<>(currentSubjectIds);
List<String> normalizedSubjectIds = recordSubjectIds.stream()
.filter(currentSubjectIdSet::contains)
.collect(Collectors.toList());
if (randomDisplayMode) {
currentSubjectIds = normalizedSubjectIds;
} else {
for (String subjectId : currentSubjectIds) {
if (!normalizedSubjectIds.contains(subjectId)) {
normalizedSubjectIds.add(subjectId);
}
}
}
boolean changed = !recordSubjectIds.equals(normalizedSubjectIds);
boolean subjectOrderLocked = Boolean.TRUE.equals(extJson.get("_subjectOrderLocked"));
if ("QUIZ".equals(activity.getCategory())
&& Boolean.TRUE.equals(activity.getShuffleSubject())
&& !subjectOrderLocked
&& isAnswerRecordEmpty(extJson, currentSubjectIds)) {
Collections.shuffle(normalizedSubjectIds);
extJson.set("_subjectOrderLocked", true);
changed = true;
}
for (String subjectId : currentSubjectIds) {
if (ObjectUtil.isEmpty(extJson.get(subjectId, JSONObject.class))) {
extJson.set(subjectId, Dict.create()
.set("optionIds", new ArrayList<>())
.set("text", null)
.set("optionFillContents", new JSONObject()));
changed = true;
}
}
if (changed) {
answerRecord.setSubjectIds(normalizedSubjectIds);
answerRecord.setExtJson(extJson);
dao.update(answerRecord);
}
}
private boolean isAnswerRecordEmpty(JSONObject extJson, List<String> subjectIds) {
for (String subjectId : subjectIds) {
JSONObject answer = extJson.get(subjectId, JSONObject.class);
if (ObjectUtil.isEmpty(answer)) {
continue;
}
if (ObjectUtil.isNotEmpty(answer.get("optionIds"))
|| ObjectUtil.isNotEmpty(answer.get("text"))
|| ObjectUtil.isNotEmpty(answer.get("optionFillContents"))) {
return false;
}
}
return true;
}
private List<QsvSubject> querySubjectsByRecordOrder(List<String> subjectIds) {
if (subjectIds == null || subjectIds.isEmpty()) {
return new ArrayList<>();
}
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("id", "in", subjectIds));
Map<String, QsvSubject> subjectMap = subjects.stream()
.collect(Collectors.toMap(QsvSubject::getId, Function.identity(), (oldValue, newValue) -> oldValue));
return subjectIds.stream()
.map(subjectMap::get)
.filter(ObjectUtil::isNotEmpty)
.collect(Collectors.toList());
}
private NutMap buildVoteStats(String activityId) {
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
Map<String, NutMap> optionStats = new LinkedHashMap<>();
for (QsvSubject subject : subjects) {
if (subject.getOptions() == null) {
continue;
}
for (QsvOption option : subject.getOptions()) {
optionStats.put(option.getId(), NutMap.NEW()
.addv("optionId", option.getId())
.addv("subjectId", subject.getId())
.addv("text", option.getText())
.addv("imgUrl", option.getImgUrl())
.addv("votes", 0)
.addv("percent", 0D));
}
}
List<QsvUserAnswerRecord> records = dao.query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
.and("isFinish", "=", true));
for (QsvUserAnswerRecord record : records) {
JSONObject extJson = record.getExtJson();
if (extJson == null) {
continue;
}
for (QsvSubject subject : subjects) {
JSONObject entry = extJson.get(subject.getId(), JSONObject.class);
if (entry == null) {
continue;
}
List<String> optionIds = entry.getBeanList("optionIds", String.class);
if (optionIds == null) {
continue;
}
for (String optionId : optionIds) {
NutMap stat = optionStats.get(optionId);
if (stat != null) {
stat.put("votes", stat.getInt("votes", 0) + 1);
}
}
}
}
int totalParticipants = records.size();
List<NutMap> rankList = new ArrayList<>(optionStats.values());
for (NutMap stat : rankList) {
int votes = stat.getInt("votes", 0);
double percent = totalParticipants == 0 ? 0D : votes * 100D / totalParticipants;
stat.put("percent", percent);
}
rankList.sort((left, right) -> Integer.compare(right.getInt("votes", 0), left.getInt("votes", 0)));
return NutMap.NEW()
.addv("totalParticipants", totalParticipants)
.addv("optionCount", optionStats.size())
.addv("optionStats", optionStats)
.addv("rankList", rankList);
}
}
@@ -0,0 +1,301 @@
package com.budwk.app.zhgh.dayofficework.qsv.h5controller;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.result.Result;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvOption;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvUserAnswerRecord;
import com.budwk.app.zhgh.dayofficework.qsv.param.QsvAnswerParam;
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvUserAnswerRecordService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@IocBean
@At("/platform/h5/qsv/vote")
@Ok("json:full")
public class H5QsvVoteController {
@Inject
private Dao dao;
@Inject
private QsvUserAnswerRecordService qsvUserAnswerRecordService;
@At("")
@Ok("beetl:/platform/zhghh5/dayofficework/qsv/vote/index.html")
public void index() {
}
@At
@Aop(TransAop.READ_COMMITTED)
public Result subjects(String activityId) {
QsvActivity activity = dao.fetch(QsvActivity.class, activityId);
Result activityStatusResult = checkActivityReadable(activity);
if (activityStatusResult != null) {
return activityStatusResult;
}
Result scopeResult = checkScope(activity);
if (scopeResult != null) {
return scopeResult;
}
List<QsvSubject> activitySubjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
.and("userId", "=", SecurityUtil.getUserId()));
if (answerRecord == null) {
List<String> subjectIds = activitySubjects.stream()
.map(QsvSubject::getId)
.collect(Collectors.toList());
answerRecord = qsvUserAnswerRecordService.insertRecord(activityId, subjectIds);
}
syncAnswerRecordSubjects(answerRecord, activitySubjects);
List<QsvSubject> subjects = querySubjectsByRecordOrder(answerRecord.getSubjectIds());
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
for (QsvSubject subject : subjects) {
if (subject.getUserSelectOptionIds() == null) {
subject.setUserSelectOptionIds(new ArrayList<>());
}
}
NutMap result = NutMap.NEW()
.addv("subjects", subjects)
.addv("answerRecordId", answerRecord.getId())
.addv("answerRecord", answerRecord)
.addv("activity", activity)
.addv("voteStats", buildVoteStats(activityId));
return Result.success(result);
}
@At
public Result answerRecord(@Valid String answerRecordId) {
QsvUserAnswerRecord record = dao.fetch(QsvUserAnswerRecord.class, answerRecordId);
return Result.success(record);
}
@At
public Result rank(@Valid String activityId) {
return Result.success(buildVoteStats(activityId));
}
@At
@Aop(TransAop.READ_COMMITTED)
public Result submitAnswer(@Valid @Param("answer") QsvAnswerParam qsvAnswerParam) {
QsvActivity activity = dao.fetch(QsvActivity.class, qsvAnswerParam.getActivityId());
Result activityStatusResult = checkActivityWritable(activity);
if (activityStatusResult != null) {
return activityStatusResult;
}
QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, qsvAnswerParam.getAnswerRecordId());
if (answerRecord == null || !SecurityUtil.getUserId().equals(answerRecord.getUserId())) {
return Result.error("\u6295\u7968\u8bb0\u5f55\u4e0d\u5b58\u5728");
}
if (Boolean.TRUE.equals(answerRecord.getIsFinish())) {
return Result.error("\u60a8\u5df2\u5b8c\u6210\u6295\u7968");
}
JSONObject extJson = answerRecord.getExtJson();
if (extJson == null) {
extJson = new JSONObject();
}
for (QsvAnswerParam.Subject subject : qsvAnswerParam.getSubjects()) {
JSONObject entries = extJson.get(subject.getId(), JSONObject.class);
if (entries == null) {
entries = new JSONObject();
}
entries.set("optionIds", subject.getUserSelectOptionIds());
entries.set("text", subject.getUserFillContent());
entries.set("optionFillContents", subject.getOptionFillContents());
extJson.set(subject.getId(), entries);
}
answerRecord.setExtJson(extJson);
answerRecord.setAttemptDate(new Date());
answerRecord.setSubmitTime(new Date());
answerRecord.setIsFinish(true);
dao.update(answerRecord);
return Result.success("\u6295\u7968\u6210\u529f");
}
private Result checkActivityReadable(QsvActivity activity) {
if (activity == null) {
return Result.error("\u6295\u7968\u4e0d\u5b58\u5728");
}
if (Boolean.FALSE.equals(activity.getEnabled())) {
return Result.error("\u6d3b\u52a8\u5df2\u5173\u95ed\uff0c\u6682\u4e0d\u80fd\u53c2\u4e0e");
}
Date now = new Date();
if (activity.getStartTime() != null && activity.getStartTime().after(now)) {
return Result.error("\u6295\u7968\u5c1a\u672a\u5f00\u59cb\uff0c\u8bf7\u5728\u5f00\u59cb\u540e\u518d\u53c2\u4e0e");
}
return null;
}
private Result checkActivityWritable(QsvActivity activity) {
Result readableResult = checkActivityReadable(activity);
if (readableResult != null) {
return readableResult;
}
Date now = new Date();
if (activity.getEndTime() != null && activity.getEndTime().before(now)) {
return Result.error("\u6295\u7968\u5df2\u7ed3\u675f");
}
return null;
}
private Result checkScope(QsvActivity activity) {
if (activity.getGroupId() == null) {
return null;
}
int count = dao.count(ActivityUserScope.class, Cnd.where("groupId", "=", activity.getGroupId())
.and("userId", "=", SecurityUtil.getUserId()));
if (count != 1) {
return Result.error("\u60a8\u65e0\u9700\u53c2\u52a0\u6b64\u6b21\u6295\u7968\uff0c\u611f\u8c22\u60a8\u7684\u5173\u6ce8\uff01");
}
return null;
}
private void syncAnswerRecordSubjects(QsvUserAnswerRecord answerRecord, List<QsvSubject> activitySubjects) {
if (answerRecord == null || Boolean.TRUE.equals(answerRecord.getIsFinish())) {
return;
}
List<String> currentSubjectIds = activitySubjects.stream()
.map(QsvSubject::getId)
.collect(Collectors.toList());
List<String> recordSubjectIds = answerRecord.getSubjectIds();
if (recordSubjectIds == null) {
recordSubjectIds = new ArrayList<>();
}
JSONObject extJson = answerRecord.getExtJson();
if (extJson == null) {
extJson = new JSONObject();
}
LinkedHashSet<String> currentSubjectIdSet = new LinkedHashSet<>(currentSubjectIds);
List<String> normalizedSubjectIds = recordSubjectIds.stream()
.filter(currentSubjectIdSet::contains)
.collect(Collectors.toList());
for (String subjectId : currentSubjectIds) {
if (!normalizedSubjectIds.contains(subjectId)) {
normalizedSubjectIds.add(subjectId);
}
}
boolean changed = !recordSubjectIds.equals(normalizedSubjectIds);
for (String subjectId : currentSubjectIds) {
if (ObjectUtil.isEmpty(extJson.get(subjectId, JSONObject.class))) {
JSONObject entry = new JSONObject();
entry.set("optionIds", new ArrayList<>());
entry.set("text", null);
entry.set("optionFillContents", new JSONObject());
extJson.set(subjectId, entry);
changed = true;
}
}
if (changed) {
answerRecord.setSubjectIds(normalizedSubjectIds);
answerRecord.setExtJson(extJson);
dao.update(answerRecord);
}
}
private List<QsvSubject> querySubjectsByRecordOrder(List<String> subjectIds) {
if (subjectIds == null || subjectIds.isEmpty()) {
return new ArrayList<>();
}
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("id", "in", subjectIds));
Map<String, QsvSubject> subjectMap = subjects.stream()
.collect(Collectors.toMap(QsvSubject::getId, Function.identity(), (oldValue, newValue) -> oldValue));
return subjectIds.stream()
.map(subjectMap::get)
.filter(ObjectUtil::isNotEmpty)
.collect(Collectors.toList());
}
private NutMap buildVoteStats(String activityId) {
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
Map<String, NutMap> optionStats = new LinkedHashMap<>();
for (QsvSubject subject : subjects) {
if (subject.getOptions() == null) {
continue;
}
for (QsvOption option : subject.getOptions()) {
optionStats.put(option.getId(), NutMap.NEW()
.addv("optionId", option.getId())
.addv("subjectId", subject.getId())
.addv("text", option.getText())
.addv("imgUrl", option.getImgUrl())
.addv("votes", 0)
.addv("percent", 0D));
}
}
List<QsvUserAnswerRecord> records = dao.query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)
.and("isFinish", "=", true));
for (QsvUserAnswerRecord record : records) {
JSONObject extJson = record.getExtJson();
if (extJson == null) {
continue;
}
for (QsvSubject subject : subjects) {
JSONObject entry = extJson.get(subject.getId(), JSONObject.class);
if (entry == null) {
continue;
}
List<String> optionIds = entry.getBeanList("optionIds", String.class);
if (optionIds == null) {
continue;
}
for (String optionId : optionIds) {
NutMap stat = optionStats.get(optionId);
if (stat != null) {
stat.put("votes", stat.getInt("votes", 0) + 1);
}
}
}
}
int totalParticipants = records.size();
List<NutMap> rankList = new ArrayList<>(optionStats.values());
for (NutMap stat : rankList) {
int votes = stat.getInt("votes", 0);
double percent = totalParticipants == 0 ? 0D : votes * 100D / totalParticipants;
stat.put("percent", percent);
}
rankList.sort((left, right) -> Integer.compare(right.getInt("votes", 0), left.getInt("votes", 0)));
return NutMap.NEW()
.addv("totalParticipants", totalParticipants)
.addv("optionCount", optionStats.size())
.addv("optionStats", optionStats)
.addv("rankList", rankList);
}
}