feat(integration): 添加问卷服务集成模块

- 新增问卷服务配置和客户端接口
- 实现问卷创建、查询、提交答案和统计分析等功能
- 添加问卷集成示例,演示各项功能的使用- 设计并实现问卷服务的 fallback 缓存管理策略
This commit is contained in:
2025-09-06 00:19:19 +08:00
parent d7c6ce9f40
commit a49450b795
18 changed files with 1490 additions and 1 deletions

View File

@@ -0,0 +1,125 @@
package com.ycwl.basic.integration.questionnaire.client;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.questionnaire.dto.answer.ResponseDetailResponse;
import com.ycwl.basic.integration.questionnaire.dto.answer.ResponseListResponse;
import com.ycwl.basic.integration.questionnaire.dto.answer.SubmitAnswerRequest;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.CreateQuestionnaireRequest;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.QuestionnaireListResponse;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.QuestionnaireResponse;
import com.ycwl.basic.integration.questionnaire.dto.statistics.QuestionnaireStatistics;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
@FeignClient(name = "zt-questionnaire", contextId = "questionnaire", path = "/api")
public interface QuestionnaireClient {
// ==================== 问卷管理接口 ====================
/**
* 创建问卷
*/
@PostMapping("/questionnaires")
CommonResponse<QuestionnaireResponse> createQuestionnaire(
@RequestBody CreateQuestionnaireRequest request,
@RequestHeader("X-User-ID") String userId
);
/**
* 获取问卷详情
*/
@GetMapping("/questionnaires/{id}")
CommonResponse<QuestionnaireResponse> getQuestionnaire(@PathVariable("id") Long id);
/**
* 获取问卷列表
*/
@GetMapping("/questionnaires")
CommonResponse<QuestionnaireListResponse> getQuestionnaireList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String name,
@RequestParam(required = false) Integer status,
@RequestParam(required = false) String createdBy
);
/**
* 更新问卷
*/
@PutMapping("/questionnaires/{id}")
CommonResponse<QuestionnaireResponse> updateQuestionnaire(
@PathVariable("id") Long id,
@RequestBody CreateQuestionnaireRequest request,
@RequestHeader("X-User-ID") String userId
);
/**
* 删除问卷
*/
@DeleteMapping("/questionnaires/{id}")
CommonResponse<Void> deleteQuestionnaire(
@PathVariable("id") Long id,
@RequestHeader("X-User-ID") String userId
);
/**
* 发布问卷
*/
@PostMapping("/questionnaires/{id}/publish")
CommonResponse<QuestionnaireResponse> publishQuestionnaire(
@PathVariable("id") Long id,
@RequestHeader("X-User-ID") String userId
);
/**
* 停止问卷
*/
@PostMapping("/questionnaires/{id}/stop")
CommonResponse<QuestionnaireResponse> stopQuestionnaire(
@PathVariable("id") Long id,
@RequestHeader("X-User-ID") String userId
);
// ==================== 答案提交接口 ====================
/**
* 提交问卷答案
*/
@PostMapping("/questionnaires/submit")
CommonResponse<ResponseDetailResponse> submitAnswer(@RequestBody SubmitAnswerRequest request);
// ==================== 统计分析接口 ====================
/**
* 获取问卷统计
*/
@GetMapping("/questionnaires/{id}/statistics")
CommonResponse<QuestionnaireStatistics> getStatistics(@PathVariable("id") Long id);
/**
* 获取回答记录列表
*/
@GetMapping("/responses")
CommonResponse<ResponseListResponse> getResponseList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) Long questionnaireId,
@RequestParam(required = false) String userId,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime
);
/**
* 获取回答详情
*/
@GetMapping("/responses/{id}")
CommonResponse<ResponseDetailResponse> getResponseDetail(@PathVariable("id") Long id);
// ==================== 健康检查接口 ====================
/**
* 服务健康检查
*/
@GetMapping("/health")
CommonResponse<String> health();
}

View File

@@ -0,0 +1,17 @@
package com.ycwl.basic.integration.questionnaire.config;
import com.ycwl.basic.integration.common.config.IntegrationProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.openfeign.EnableFeignClients;
@Configuration
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "integration.questionnaire", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableFeignClients(basePackages = "com.ycwl.basic.integration.questionnaire.client")
public class QuestionnaireIntegrationConfig {
private final IntegrationProperties integrationProperties;
}

View File

@@ -0,0 +1,23 @@
package com.ycwl.basic.integration.questionnaire.dto.answer;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AnswerRequest {
@NotNull(message = "问题ID不能为空")
@JsonProperty("questionId")
private Long questionId;
@NotBlank(message = "答案内容不能为空")
@JsonProperty("answer")
private String answer;
}

View File

@@ -0,0 +1,44 @@
package com.ycwl.basic.integration.questionnaire.dto.answer;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
public class ResponseDetailResponse {
@JsonProperty("id")
private Long id;
@JsonProperty("questionnaireId")
private Long questionnaireId;
@JsonProperty("userId")
private String userId;
@JsonProperty("ipAddress")
private String ipAddress;
@JsonProperty("submittedAt")
private String submittedAt;
@JsonProperty("answers")
private List<AnswerDetailResponse> answers;
}
@Data
class AnswerDetailResponse {
@JsonProperty("questionId")
private Long questionId;
@JsonProperty("questionTitle")
private String questionTitle;
@JsonProperty("questionType")
private Integer questionType;
@JsonProperty("answer")
private String answer;
}

View File

@@ -0,0 +1,12 @@
package com.ycwl.basic.integration.questionnaire.dto.answer;
import com.ycwl.basic.integration.common.response.PageResponse;
import lombok.Data;
@Data
public class ResponseListResponse extends PageResponse<ResponseDetailResponse> {
public ResponseListResponse() {
super();
}
}

View File

@@ -0,0 +1,25 @@
package com.ycwl.basic.integration.questionnaire.dto.answer;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.List;
@Data
public class SubmitAnswerRequest {
@NotNull(message = "问卷ID不能为空")
@JsonProperty("questionnaireId")
private Long questionnaireId;
@JsonProperty("userId")
private String userId; // 可选,用于非匿名问卷
@NotEmpty(message = "答案不能为空")
@Valid
@JsonProperty("answers")
private List<AnswerRequest> answers;
}

View File

@@ -0,0 +1,34 @@
package com.ycwl.basic.integration.questionnaire.dto.question;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateQuestionOptionRequest {
@NotBlank(message = "选项文本不能为空")
@Size(max = 500, message = "选项文本长度不能超过500字符")
@JsonProperty("text")
private String text;
@NotBlank(message = "选项值不能为空")
@Size(max = 100, message = "选项值长度不能超过100字符")
@JsonProperty("value")
private String value;
@JsonProperty("sort")
private Integer sort = 0;
public CreateQuestionOptionRequest(String text, String value, Integer sort) {
this.text = text;
this.value = value;
this.sort = sort;
}
}

View File

@@ -0,0 +1,37 @@
package com.ycwl.basic.integration.questionnaire.dto.question;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.util.List;
@Data
public class CreateQuestionRequest {
@NotBlank(message = "问题标题不能为空")
@Size(max = 500, message = "问题标题长度不能超过500字符")
@JsonProperty("title")
private String title;
@NotNull(message = "问题类型不能为空")
@Min(value = 1, message = "问题类型无效")
@Max(value = 5, message = "问题类型无效")
@JsonProperty("type")
private Integer type; // 1:单选 2:多选 3:填空 4:文本域 5:评分
@JsonProperty("isRequired")
private Boolean isRequired = false;
@JsonProperty("sort")
private Integer sort = 0;
@Valid
@JsonProperty("options")
private List<CreateQuestionOptionRequest> options;
}

View File

@@ -0,0 +1,26 @@
package com.ycwl.basic.integration.questionnaire.dto.question;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class QuestionOptionResponse {
@JsonProperty("id")
private Long id;
@JsonProperty("text")
private String text;
@JsonProperty("value")
private String value;
@JsonProperty("sort")
private Integer sort;
@JsonProperty("createdAt")
private String createdAt;
@JsonProperty("updatedAt")
private String updatedAt;
}

View File

@@ -0,0 +1,37 @@
package com.ycwl.basic.integration.questionnaire.dto.question;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
public class QuestionResponse {
@JsonProperty("id")
private Long id;
@JsonProperty("title")
private String title;
@JsonProperty("type")
private Integer type;
@JsonProperty("typeText")
private String typeText;
@JsonProperty("isRequired")
private Boolean isRequired;
@JsonProperty("sort")
private Integer sort;
@JsonProperty("createdAt")
private String createdAt;
@JsonProperty("updatedAt")
private String updatedAt;
@JsonProperty("options")
private List<QuestionOptionResponse> options;
}

View File

@@ -0,0 +1,38 @@
package com.ycwl.basic.integration.questionnaire.dto.questionnaire;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ycwl.basic.integration.questionnaire.dto.question.CreateQuestionRequest;
import lombok.Data;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.util.List;
@Data
public class CreateQuestionnaireRequest {
@NotBlank(message = "问卷名称不能为空")
@Size(max = 255, message = "问卷名称长度不能超过255字符")
@JsonProperty("name")
private String name;
@JsonProperty("description")
private String description;
@JsonProperty("startTime")
private String startTime; // 格式: "2024-01-01 00:00:00"
@JsonProperty("endTime")
private String endTime; // 格式: "2024-12-31 23:59:59"
@JsonProperty("isAnonymous")
private Boolean isAnonymous = true;
@JsonProperty("maxAnswers")
private Integer maxAnswers = 0;
@Valid
@JsonProperty("questions")
private List<CreateQuestionRequest> questions;
}

View File

@@ -0,0 +1,13 @@
package com.ycwl.basic.integration.questionnaire.dto.questionnaire;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ycwl.basic.integration.common.response.PageResponse;
import lombok.Data;
@Data
public class QuestionnaireListResponse extends PageResponse<QuestionnaireResponse> {
public QuestionnaireListResponse() {
super();
}
}

View File

@@ -0,0 +1,54 @@
package com.ycwl.basic.integration.questionnaire.dto.questionnaire;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ycwl.basic.integration.questionnaire.dto.question.QuestionResponse;
import com.ycwl.basic.integration.questionnaire.dto.statistics.QuestionnaireStatistics;
import lombok.Data;
import java.util.List;
@Data
public class QuestionnaireResponse {
@JsonProperty("id")
private Long id;
@JsonProperty("name")
private String name;
@JsonProperty("description")
private String description;
@JsonProperty("status")
private Integer status;
@JsonProperty("statusText")
private String statusText;
@JsonProperty("createdBy")
private String createdBy;
@JsonProperty("startTime")
private String startTime;
@JsonProperty("endTime")
private String endTime;
@JsonProperty("isAnonymous")
private Boolean isAnonymous;
@JsonProperty("maxAnswers")
private Integer maxAnswers;
@JsonProperty("createdAt")
private String createdAt;
@JsonProperty("updatedAt")
private String updatedAt;
@JsonProperty("questions")
private List<QuestionResponse> questions;
@JsonProperty("statistics")
private QuestionnaireStatistics statistics;
}

View File

@@ -0,0 +1,73 @@
package com.ycwl.basic.integration.questionnaire.dto.statistics;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class QuestionnaireStatistics {
@JsonProperty("totalResponses")
private Integer totalResponses;
@JsonProperty("completionRate")
private Double completionRate;
@JsonProperty("averageTime")
private Integer averageTime; // 平均答题时间(秒)
@JsonProperty("questionStats")
private List<QuestionStatistics> questionStats;
@JsonProperty("responsesByDate")
private Map<String, Integer> responsesByDate;
@JsonProperty("createdAt")
private String createdAt;
@JsonProperty("updatedAt")
private String updatedAt;
}
@Data
class QuestionStatistics {
@JsonProperty("questionId")
private Long questionId;
@JsonProperty("questionTitle")
private String questionTitle;
@JsonProperty("questionType")
private Integer questionType;
@JsonProperty("totalAnswers")
private Integer totalAnswers;
@JsonProperty("optionStats")
private List<OptionStatistics> optionStats;
@JsonProperty("textAnswers")
private List<String> textAnswers; // 用于填空题和文本域题
}
@Data
class OptionStatistics {
@JsonProperty("optionId")
private Long optionId;
@JsonProperty("optionText")
private String optionText;
@JsonProperty("optionValue")
private String optionValue;
@JsonProperty("count")
private Integer count;
@JsonProperty("percentage")
private Double percentage;
}

View File

@@ -0,0 +1,306 @@
package com.ycwl.basic.integration.questionnaire.example;
import com.ycwl.basic.integration.common.service.IntegrationFallbackService;
import com.ycwl.basic.integration.questionnaire.dto.answer.AnswerRequest;
import com.ycwl.basic.integration.questionnaire.dto.answer.ResponseDetailResponse;
import com.ycwl.basic.integration.questionnaire.dto.answer.SubmitAnswerRequest;
import com.ycwl.basic.integration.questionnaire.dto.question.CreateQuestionOptionRequest;
import com.ycwl.basic.integration.questionnaire.dto.question.CreateQuestionRequest;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.CreateQuestionnaireRequest;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.QuestionnaireListResponse;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.QuestionnaireResponse;
import com.ycwl.basic.integration.questionnaire.dto.statistics.QuestionnaireStatistics;
import com.ycwl.basic.integration.questionnaire.service.QuestionnaireIntegrationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "integration.questionnaire.example", name = "enabled", havingValue = "true")
public class QuestionnaireIntegrationExample {
private final QuestionnaireIntegrationService questionnaireService;
private final IntegrationFallbackService fallbackService;
@EventListener(ApplicationReadyEvent.class)
public void runExamples() {
try {
log.info("=== 开始问卷集成服务示例 ===");
// 示例1:创建问卷
createQuestionnaireExample();
// 示例2:查询问卷
queryQuestionnaireExample();
// 示例3:提交答案
submitAnswerExample();
// 示例4:统计查询
statisticsExample();
// 示例5:Fallback 缓存管理
fallbackCacheExample();
log.info("=== 问卷集成服务示例完成 ===");
} catch (Exception e) {
log.error("问卷集成服务示例执行失败", e);
}
}
/**
* 示例1:创建问卷
*/
private void createQuestionnaireExample() {
log.info("--- 示例1:创建客户满意度问卷 ---");
try {
CreateQuestionnaireRequest request = new CreateQuestionnaireRequest();
request.setName("客户满意度调查");
request.setDescription("用于了解客户对我们服务的满意度");
request.setIsAnonymous(true);
request.setMaxAnswers(1000);
// 添加单选题
CreateQuestionRequest question1 = new CreateQuestionRequest();
question1.setTitle("您对我们的服务满意吗?");
question1.setType(1); // 单选题
question1.setIsRequired(true);
question1.setSort(1);
List<CreateQuestionOptionRequest> options1 = new ArrayList<>();
options1.add(new CreateQuestionOptionRequest("非常满意", "5", 1));
options1.add(new CreateQuestionOptionRequest("满意", "4", 2));
options1.add(new CreateQuestionOptionRequest("一般", "3", 3));
options1.add(new CreateQuestionOptionRequest("不满意", "2", 4));
options1.add(new CreateQuestionOptionRequest("非常不满意", "1", 5));
question1.setOptions(options1);
// 添加多选题
CreateQuestionRequest question2 = new CreateQuestionRequest();
question2.setTitle("您感兴趣的服务有哪些?");
question2.setType(2); // 多选题
question2.setIsRequired(false);
question2.setSort(2);
List<CreateQuestionOptionRequest> options2 = new ArrayList<>();
options2.add(new CreateQuestionOptionRequest("技术支持", "tech_support", 1));
options2.add(new CreateQuestionOptionRequest("产品培训", "training", 2));
options2.add(new CreateQuestionOptionRequest("定制开发", "custom_dev", 3));
options2.add(new CreateQuestionOptionRequest("其他", "others", 4));
question2.setOptions(options2);
// 添加文本域题
CreateQuestionRequest question3 = new CreateQuestionRequest();
question3.setTitle("您还有什么建议吗?");
question3.setType(4); // 文本域题
question3.setIsRequired(false);
question3.setSort(3);
question3.setOptions(null); // 文本域题不需要选项
request.setQuestions(Arrays.asList(question1, question2, question3));
QuestionnaireResponse response = questionnaireService.createQuestionnaire(request, "admin");
log.info("✅ 问卷创建成功,ID: {}, 名称: {}", response.getId(), response.getName());
} catch (Exception e) {
log.error("❌ 创建问卷示例失败", e);
}
}
/**
* 示例2:查询问卷
*/
private void queryQuestionnaireExample() {
log.info("--- 示例2:查询问卷示例 ---");
try {
// 获取问卷列表(支持 fallback)
QuestionnaireListResponse listResponse = questionnaireService.getQuestionnaireList(1, 10, null, null, null);
log.info("✅ 问卷列表查询成功,总数: {}, 当前页数据: {}",
listResponse.getTotal(), listResponse.getList().size());
if (listResponse.getList() != null && !listResponse.getList().isEmpty()) {
Long questionnaireId = listResponse.getList().get(0).getId();
// 获取问卷详情(支持 fallback)
QuestionnaireResponse detailResponse = questionnaireService.getQuestionnaire(questionnaireId);
log.info("✅ 问卷详情查询成功,ID: {}, 名称: {}, 问题数: {}",
detailResponse.getId(), detailResponse.getName(),
detailResponse.getQuestions() != null ? detailResponse.getQuestions().size() : 0);
}
} catch (Exception e) {
log.error("❌ 查询问卷示例失败", e);
}
}
/**
* 示例3:提交答案
*/
private void submitAnswerExample() {
log.info("--- 示例3:提交问卷答案示例 ---");
try {
SubmitAnswerRequest request = new SubmitAnswerRequest();
request.setQuestionnaireId(1001L);
request.setUserId("user123");
List<AnswerRequest> answers = new ArrayList<>();
// 单选题答案
answers.add(new AnswerRequest(123L, "4")); // 满意
// 多选题答案
answers.add(new AnswerRequest(124L, "tech_support,training")); // 技术支持和产品培训
// 文本域题答案
answers.add(new AnswerRequest(125L, "服务很好,希望能增加更多实用功能"));
request.setAnswers(answers);
ResponseDetailResponse response = questionnaireService.submitAnswer(request);
log.info("✅ 问卷答案提交成功,回答ID: {}, 提交时间: {}",
response.getId(), response.getSubmittedAt());
} catch (Exception e) {
log.error("❌ 提交答案示例失败", e);
}
}
/**
* 示例4:统计查询
*/
private void statisticsExample() {
log.info("--- 示例4:问卷统计查询示例 ---");
try {
Long questionnaireId = 1001L;
// 获取问卷统计(支持 fallback)
QuestionnaireStatistics stats = questionnaireService.getStatistics(questionnaireId);
log.info("✅ 统计查询成功,总回答数: {}, 完成率: {}%, 平均用时: {}秒",
stats.getTotalResponses(),
stats.getCompletionRate() != null ? stats.getCompletionRate() * 100 : 0,
stats.getAverageTime());
// 获取回答记录列表(支持 fallback)
questionnaireService.getResponseList(1, 10, questionnaireId, null, null, null);
log.info("✅ 回答记录列表查询成功");
} catch (Exception e) {
log.error("❌ 统计查询示例失败", e);
}
}
/**
* 示例5:Fallback 缓存管理
*/
private void fallbackCacheExample() {
log.info("--- 示例5:Fallback 缓存管理示例 ---");
try {
String serviceName = "zt-questionnaire";
// 检查缓存状态
boolean hasQuestionnaireCache = fallbackService.hasFallbackCache(serviceName, "questionnaire:1001");
boolean hasListCache = fallbackService.hasFallbackCache(serviceName, "questionnaire:list:1:10:null:null:null");
log.info("✅ 缓存状态检查 - 问卷缓存: {}, 列表缓存: {}", hasQuestionnaireCache, hasListCache);
// 获取缓存统计
IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats(serviceName);
log.info("✅ 缓存统计 - 缓存项目数: {}, TTL: {} 天",
stats.getTotalCacheCount(), stats.getFallbackTtlDays());
// 清理特定缓存示例(仅演示,实际使用时谨慎操作)
// fallbackService.clearFallbackCache(serviceName, "questionnaire:1001");
// log.info("✅ 已清理问卷缓存");
} catch (Exception e) {
log.error("❌ Fallback 缓存管理示例失败", e);
}
}
/**
* 问卷管理工作流示例
*/
public void questionnaireWorkflowExample(String userId) {
log.info("--- 问卷管理工作流示例 ---");
try {
// 1. 创建问卷
CreateQuestionnaireRequest createRequest = createSampleQuestionnaire();
QuestionnaireResponse questionnaire = questionnaireService.createQuestionnaire(createRequest, userId);
log.info("✅ 步骤1 - 问卷创建成功: {}", questionnaire.getName());
Long questionnaireId = questionnaire.getId();
// 2. 发布问卷
QuestionnaireResponse published = questionnaireService.publishQuestionnaire(questionnaireId, userId);
log.info("✅ 步骤2 - 问卷发布成功,状态: {}", published.getStatusText());
// 3. 模拟用户提交答案
SubmitAnswerRequest answerRequest = createSampleAnswers(questionnaireId);
ResponseDetailResponse answerResponse = questionnaireService.submitAnswer(answerRequest);
log.info("✅ 步骤3 - 答案提交成功: {}", answerResponse.getId());
// 4. 查看统计数据
QuestionnaireStatistics statistics = questionnaireService.getStatistics(questionnaireId);
log.info("✅ 步骤4 - 统计查询成功,回答数: {}", statistics.getTotalResponses());
// 5. 停止问卷
QuestionnaireResponse stopped = questionnaireService.stopQuestionnaire(questionnaireId, userId);
log.info("✅ 步骤5 - 问卷停止成功,状态: {}", stopped.getStatusText());
} catch (Exception e) {
log.error("❌ 问卷管理工作流示例失败", e);
}
}
private CreateQuestionnaireRequest createSampleQuestionnaire() {
CreateQuestionnaireRequest request = new CreateQuestionnaireRequest();
request.setName("产品体验调查");
request.setDescription("收集用户对产品的使用体验反馈");
request.setIsAnonymous(false);
request.setMaxAnswers(500);
// 评分题
CreateQuestionRequest ratingQuestion = new CreateQuestionRequest();
ratingQuestion.setTitle("请对我们的产品进行评分(1-10分)");
ratingQuestion.setType(5); // 评分题
ratingQuestion.setIsRequired(true);
ratingQuestion.setSort(1);
ratingQuestion.setOptions(null); // 评分题不需要选项
// 填空题
CreateQuestionRequest textQuestion = new CreateQuestionRequest();
textQuestion.setTitle("请输入您的姓名");
textQuestion.setType(3); // 填空题
textQuestion.setIsRequired(true);
textQuestion.setSort(2);
textQuestion.setOptions(null); // 填空题不需要选项
request.setQuestions(Arrays.asList(ratingQuestion, textQuestion));
return request;
}
private SubmitAnswerRequest createSampleAnswers(Long questionnaireId) {
SubmitAnswerRequest request = new SubmitAnswerRequest();
request.setQuestionnaireId(questionnaireId);
request.setUserId("test_user");
List<AnswerRequest> answers = new ArrayList<>();
answers.add(new AnswerRequest(1L, "8")); // 评分题答案
answers.add(new AnswerRequest(2L, "张三")); // 填空题答案
request.setAnswers(answers);
return request;
}
}

View File

@@ -0,0 +1,166 @@
package com.ycwl.basic.integration.questionnaire.service;
import com.ycwl.basic.integration.common.exception.IntegrationException;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.common.service.IntegrationFallbackService;
import com.ycwl.basic.integration.questionnaire.client.QuestionnaireClient;
import com.ycwl.basic.integration.questionnaire.dto.answer.ResponseDetailResponse;
import com.ycwl.basic.integration.questionnaire.dto.answer.ResponseListResponse;
import com.ycwl.basic.integration.questionnaire.dto.answer.SubmitAnswerRequest;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.CreateQuestionnaireRequest;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.QuestionnaireListResponse;
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.QuestionnaireResponse;
import com.ycwl.basic.integration.questionnaire.dto.statistics.QuestionnaireStatistics;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class QuestionnaireIntegrationService {
private final QuestionnaireClient questionnaireClient;
private final IntegrationFallbackService fallbackService;
private static final String SERVICE_NAME = "zt-questionnaire";
// ==================== 问卷查询接口(支持 fallback) ====================
public QuestionnaireResponse getQuestionnaire(Long id) {
log.info("获取问卷详情, id: {}", id);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"questionnaire:" + id,
() -> {
CommonResponse<QuestionnaireResponse> response = questionnaireClient.getQuestionnaire(id);
return handleResponse(response, "获取问卷详情失败");
},
QuestionnaireResponse.class
);
}
public QuestionnaireListResponse getQuestionnaireList(Integer page, Integer pageSize,
String name, Integer status, String createdBy) {
log.info("获取问卷列表, page: {}, pageSize: {}, name: {}, status: {}, createdBy: {}",
page, pageSize, name, status, createdBy);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"questionnaire:list:" + page + ":" + pageSize + ":" + name + ":" + status + ":" + createdBy,
() -> {
CommonResponse<QuestionnaireListResponse> response =
questionnaireClient.getQuestionnaireList(page, pageSize, name, status, createdBy);
return handleResponse(response, "获取问卷列表失败");
},
QuestionnaireListResponse.class
);
}
public QuestionnaireStatistics getStatistics(Long id) {
log.info("获取问卷统计, id: {}", id);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"questionnaire:statistics:" + id,
() -> {
CommonResponse<QuestionnaireStatistics> response = questionnaireClient.getStatistics(id);
return handleResponse(response, "获取问卷统计失败");
},
QuestionnaireStatistics.class
);
}
public ResponseListResponse getResponseList(Integer page, Integer pageSize, Long questionnaireId,
String userId, String startTime, String endTime) {
log.info("获取回答记录列表, page: {}, pageSize: {}, questionnaireId: {}, userId: {}",
page, pageSize, questionnaireId, userId);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"responses:list:" + page + ":" + pageSize + ":" + questionnaireId + ":" + userId,
() -> {
CommonResponse<ResponseListResponse> response =
questionnaireClient.getResponseList(page, pageSize, questionnaireId, userId, startTime, endTime);
return handleResponse(response, "获取回答记录列表失败");
},
ResponseListResponse.class
);
}
public ResponseDetailResponse getResponseDetail(Long id) {
log.info("获取回答详情, id: {}", id);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"response:" + id,
() -> {
CommonResponse<ResponseDetailResponse> response = questionnaireClient.getResponseDetail(id);
return handleResponse(response, "获取回答详情失败");
},
ResponseDetailResponse.class
);
}
// ==================== 问卷管理接口(直接执行,不支持 fallback) ====================
public QuestionnaireResponse createQuestionnaire(CreateQuestionnaireRequest request, String userId) {
log.info("创建问卷, name: {}, userId: {}", request.getName(), userId);
CommonResponse<QuestionnaireResponse> response = questionnaireClient.createQuestionnaire(request, userId);
return handleResponse(response, "创建问卷失败");
}
public QuestionnaireResponse updateQuestionnaire(Long id, CreateQuestionnaireRequest request, String userId) {
log.info("更新问卷, id: {}, userId: {}", id, userId);
CommonResponse<QuestionnaireResponse> response = questionnaireClient.updateQuestionnaire(id, request, userId);
return handleResponse(response, "更新问卷失败");
}
public void deleteQuestionnaire(Long id, String userId) {
log.info("删除问卷, id: {}, userId: {}", id, userId);
CommonResponse<Void> response = questionnaireClient.deleteQuestionnaire(id, userId);
handleResponse(response, "删除问卷失败");
}
public QuestionnaireResponse publishQuestionnaire(Long id, String userId) {
log.info("发布问卷, id: {}, userId: {}", id, userId);
CommonResponse<QuestionnaireResponse> response = questionnaireClient.publishQuestionnaire(id, userId);
return handleResponse(response, "发布问卷失败");
}
public QuestionnaireResponse stopQuestionnaire(Long id, String userId) {
log.info("停止问卷, id: {}, userId: {}", id, userId);
CommonResponse<QuestionnaireResponse> response = questionnaireClient.stopQuestionnaire(id, userId);
return handleResponse(response, "停止问卷失败");
}
public ResponseDetailResponse submitAnswer(SubmitAnswerRequest request) {
log.info("提交问卷答案, questionnaireId: {}, userId: {}", request.getQuestionnaireId(), request.getUserId());
CommonResponse<ResponseDetailResponse> response = questionnaireClient.submitAnswer(request);
return handleResponse(response, "提交问卷答案失败");
}
// ==================== 健康检查接口 ====================
public String health() {
log.debug("问卷服务健康检查");
return fallbackService.executeWithFallback(
SERVICE_NAME,
"health",
() -> {
CommonResponse<String> response = questionnaireClient.health();
return handleResponse(response, "健康检查失败");
},
String.class
);
}
// ==================== 工具方法 ====================
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
if (response == null || !response.isSuccess()) {
String msg = response != null && response.getMessage() != null
? response.getMessage()
: errorMessage;
Integer code = response != null ? response.getCode() : 5000;
throw new IntegrationException(code, msg, SERVICE_NAME);
}
return response.getData();
}
}