feat: validate consumer behavior questionnaire
parent
1e409dfa69
commit
fc5b3e7b96
@ -0,0 +1,29 @@
|
|||||||
|
package com.sztzjy.linkCommerce.entity.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ConsumerBehaviorStepThreeValidationRequest {
|
||||||
|
private List<Question> questions;
|
||||||
|
private String importedDocumentUrl;
|
||||||
|
|
||||||
|
public List<Question> getQuestions() { return questions; }
|
||||||
|
public void setQuestions(List<Question> questions) { this.questions = questions; }
|
||||||
|
public String getImportedDocumentUrl() { return importedDocumentUrl; }
|
||||||
|
public void setImportedDocumentUrl(String importedDocumentUrl) { this.importedDocumentUrl = importedDocumentUrl; }
|
||||||
|
|
||||||
|
public static class Question {
|
||||||
|
private String section;
|
||||||
|
private String type;
|
||||||
|
private String title;
|
||||||
|
private String options;
|
||||||
|
|
||||||
|
public String getSection() { return section; }
|
||||||
|
public void setSection(String section) { this.section = section; }
|
||||||
|
public String getType() { return type; }
|
||||||
|
public void setType(String type) { this.type = type; }
|
||||||
|
public String getTitle() { return title; }
|
||||||
|
public void setTitle(String title) { this.title = title; }
|
||||||
|
public String getOptions() { return options; }
|
||||||
|
public void setOptions(String options) { this.options = options; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.sztzjy.linkCommerce.entity.dto;
|
||||||
|
|
||||||
|
public class ConsumerBehaviorStepThreeValidationResult {
|
||||||
|
private boolean valid;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public boolean isValid() { return valid; }
|
||||||
|
public void setValid(boolean valid) { this.valid = valid; }
|
||||||
|
public String getMessage() { return message; }
|
||||||
|
public void setMessage(String message) { this.message = message; }
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.sztzjy.linkCommerce.entity.dto;
|
||||||
|
|
||||||
|
public class ConsumerBehaviorWordPreviewResult {
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
public String getContent() { return content; }
|
||||||
|
public void setContent(String content) { this.content = content; }
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package com.sztzjy.linkCommerce.service;
|
||||||
|
|
||||||
|
import com.sztzjy.linkCommerce.config.security.JwtUser;
|
||||||
|
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepThreeValidationRequest;
|
||||||
|
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepThreeValidationResult;
|
||||||
|
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorWordPreviewResult;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
public interface ConsumerBehaviorStepThreeService {
|
||||||
|
ConsumerBehaviorStepThreeValidationResult validate(ConsumerBehaviorStepThreeValidationRequest request, JwtUser user);
|
||||||
|
ConsumerBehaviorWordPreviewResult previewWord(MultipartFile file, JwtUser user);
|
||||||
|
}
|
||||||
@ -0,0 +1,120 @@
|
|||||||
|
package com.sztzjy.linkCommerce.service.impl;
|
||||||
|
|
||||||
|
import com.sztzjy.linkCommerce.config.exception.handler.ServiceException;
|
||||||
|
import com.sztzjy.linkCommerce.config.security.JwtUser;
|
||||||
|
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepThreeValidationRequest;
|
||||||
|
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorStepThreeValidationResult;
|
||||||
|
import com.sztzjy.linkCommerce.entity.dto.ConsumerBehaviorWordPreviewResult;
|
||||||
|
import com.sztzjy.linkCommerce.service.ConsumerBehaviorStepThreeService;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
||||||
|
import org.apache.poi.xwpf.usermodel.XWPFTable;
|
||||||
|
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
|
||||||
|
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ConsumerBehaviorStepThreeServiceImpl implements ConsumerBehaviorStepThreeService {
|
||||||
|
private static final Set<String> REQUIRED_SECTIONS = new LinkedHashSet<>(Arrays.asList("基本信息", "购买行为", "需求偏好", "开放建议"));
|
||||||
|
private static final Set<String> QUESTION_TYPES = new LinkedHashSet<>(Arrays.asList("SINGLE", "MULTIPLE", "LIKERT", "TEXT"));
|
||||||
|
private static final long MAX_WORD_FILE_SIZE = 10L * 1024 * 1024;
|
||||||
|
private static final int MAX_PREVIEW_LENGTH = 30000;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConsumerBehaviorStepThreeValidationResult validate(ConsumerBehaviorStepThreeValidationRequest request, JwtUser user) {
|
||||||
|
requireStudent(user);
|
||||||
|
if (request != null && StringUtils.isNotBlank(request.getImportedDocumentUrl())) {
|
||||||
|
return validResult();
|
||||||
|
}
|
||||||
|
List<ConsumerBehaviorStepThreeValidationRequest.Question> questions = request == null ? null : request.getQuestions();
|
||||||
|
if (questions == null || questions.isEmpty()) {
|
||||||
|
throw badRequest("请完整设计问卷,或导入 Word 问卷");
|
||||||
|
}
|
||||||
|
Set<String> completedSections = new LinkedHashSet<>();
|
||||||
|
boolean hasLikert = false;
|
||||||
|
for (ConsumerBehaviorStepThreeValidationRequest.Question question : questions) {
|
||||||
|
if (question == null || StringUtils.isBlank(question.getTitle())
|
||||||
|
|| !REQUIRED_SECTIONS.contains(StringUtils.trimToEmpty(question.getSection()))
|
||||||
|
|| !QUESTION_TYPES.contains(StringUtils.trimToEmpty(question.getType()))) {
|
||||||
|
throw badRequest("请完整填写每道问卷题目的分区、题型和内容");
|
||||||
|
}
|
||||||
|
if (("SINGLE".equals(question.getType()) || "MULTIPLE".equals(question.getType())) && StringUtils.isBlank(question.getOptions())) {
|
||||||
|
throw badRequest("单选题和多选题请填写选项");
|
||||||
|
}
|
||||||
|
completedSections.add(question.getSection());
|
||||||
|
hasLikert = hasLikert || ("需求偏好".equals(question.getSection()) && "LIKERT".equals(question.getType()));
|
||||||
|
}
|
||||||
|
if (!completedSections.containsAll(REQUIRED_SECTIONS) || !hasLikert) {
|
||||||
|
throw badRequest("问卷需包含基本信息、购买行为、需求偏好、开放建议四部分,并在需求偏好中包含 Likert 五级量表");
|
||||||
|
}
|
||||||
|
return validResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConsumerBehaviorWordPreviewResult previewWord(MultipartFile file, JwtUser user) {
|
||||||
|
requireStudent(user);
|
||||||
|
validateWordFile(file);
|
||||||
|
try (XWPFDocument document = new XWPFDocument(file.getInputStream())) {
|
||||||
|
StringBuilder content = new StringBuilder();
|
||||||
|
document.getParagraphs().forEach(paragraph -> appendLine(content, paragraph.getText()));
|
||||||
|
for (XWPFTable table : document.getTables()) {
|
||||||
|
for (XWPFTableRow row : table.getRows()) {
|
||||||
|
StringBuilder rowContent = new StringBuilder();
|
||||||
|
for (XWPFTableCell cell : row.getTableCells()) {
|
||||||
|
if (rowContent.length() > 0) rowContent.append(" | ");
|
||||||
|
rowContent.append(StringUtils.trimToEmpty(cell.getText()).replaceAll("\\s+", " "));
|
||||||
|
}
|
||||||
|
appendLine(content, rowContent.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (content.length() > MAX_PREVIEW_LENGTH) content.setLength(MAX_PREVIEW_LENGTH);
|
||||||
|
ConsumerBehaviorWordPreviewResult result = new ConsumerBehaviorWordPreviewResult();
|
||||||
|
result.setContent(content.toString().trim());
|
||||||
|
return result;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw badRequest("Word 文档解析失败,请确认文件未损坏");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConsumerBehaviorStepThreeValidationResult validResult() {
|
||||||
|
ConsumerBehaviorStepThreeValidationResult result = new ConsumerBehaviorStepThreeValidationResult();
|
||||||
|
result.setValid(true);
|
||||||
|
result.setMessage("校验通过");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateWordFile(MultipartFile file) {
|
||||||
|
String fileName = file == null ? "" : StringUtils.defaultString(file.getOriginalFilename());
|
||||||
|
if (file == null || file.isEmpty()) throw badRequest("请上传 Word 问卷");
|
||||||
|
if (!StringUtils.endsWithIgnoreCase(fileName, ".docx")) throw badRequest("仅支持 .docx 格式的 Word 问卷预览");
|
||||||
|
if (file.getSize() > MAX_WORD_FILE_SIZE) throw badRequest("Word 问卷大小不能超过 10MB");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendLine(StringBuilder content, String value) {
|
||||||
|
String line = StringUtils.trimToEmpty(value);
|
||||||
|
if (line.isEmpty() || content.length() >= MAX_PREVIEW_LENGTH) return;
|
||||||
|
if (content.length() > 0) content.append('\n');
|
||||||
|
content.append(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireStudent(JwtUser user) {
|
||||||
|
if (user == null || StringUtils.isBlank(user.getUserId())) {
|
||||||
|
throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再提交");
|
||||||
|
}
|
||||||
|
if (user.getRoleId() != 4) {
|
||||||
|
throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可提交实训答案");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceException badRequest(String message) {
|
||||||
|
return new ServiceException(HttpStatus.BAD_REQUEST, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue