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

@@ -24,6 +24,7 @@ Currently implemented:
- **Scenic Integration** (`com.ycwl.basic.integration.scenic`): ZT-Scenic microservice integration
- **Device Integration** (`com.ycwl.basic.integration.device`): ZT-Device microservice integration
- **Render Worker Integration** (`com.ycwl.basic.integration.render`): ZT-Render-Worker microservice integration
- **Questionnaire Integration** (`com.ycwl.basic.integration.questionnaire`): ZT-Questionnaire microservice integration
### Integration Pattern
@@ -1243,4 +1244,431 @@ logging:
- **Proactive cleanup**: Monitor cache size and clean up periodically
- **Service-specific management**: Separate cache management per service
- **Debugging support**: Use cache statistics for troubleshooting
- **Configuration validation**: Ensure fallback configuration matches service requirements
- **Configuration validation**: Ensure fallback configuration matches service requirements
## Questionnaire Integration (ZT-Questionnaire Microservice)
### Key Components
#### Feign Client
- **QuestionnaireClient**: Comprehensive questionnaire operations (CRUD, answer submission, statistics)
#### Service
- **QuestionnaireIntegrationService**: High-level questionnaire operations (with automatic fallback for queries)
#### Configuration
```yaml
integration:
questionnaire:
enabled: true
serviceName: zt-questionnaire
connectTimeout: 5000
readTimeout: 10000
retryEnabled: false
maxRetries: 3
fallback:
questionnaire:
enabled: true
ttlDays: 7
```
### Usage Examples
#### Basic Questionnaire Operations (with Automatic Fallback)
```java
@Autowired
private QuestionnaireIntegrationService questionnaireService;
// Get questionnaire details (automatically falls back to cache on failure)
QuestionnaireResponse questionnaire = questionnaireService.getQuestionnaire(questionnaireId);
// Get questionnaire list with filters (automatically falls back to cache on failure)
QuestionnaireListResponse list = questionnaireService.getQuestionnaireList(1, 10, "客户调查", 2, null);
// Get questionnaire statistics (automatically falls back to cache on failure)
QuestionnaireStatistics stats = questionnaireService.getStatistics(questionnaireId);
// Get response records (automatically falls back to cache on failure)
ResponseListResponse responses = questionnaireService.getResponseList(1, 10, questionnaireId, null, null, null);
// Get response details (automatically falls back to cache on failure)
ResponseDetailResponse responseDetail = questionnaireService.getResponseDetail(responseId);
```
#### Questionnaire Management Operations (Direct Operations)
```java
// Create questionnaire (direct operation, fails immediately on error)
CreateQuestionnaireRequest request = new CreateQuestionnaireRequest();
request.setName("客户满意度调查");
request.setDescription("收集客户对服务的满意度反馈");
request.setIsAnonymous(true);
request.setMaxAnswers(1000);
// Add single-choice question
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);
// Add multiple-choice question
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);
// Add text area question
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 created = questionnaireService.createQuestionnaire(request, "admin");
// Update questionnaire (direct operation, fails immediately on error)
CreateQuestionnaireRequest updateRequest = new CreateQuestionnaireRequest();
updateRequest.setName("更新后的客户满意度调查");
QuestionnaireResponse updated = questionnaireService.updateQuestionnaire(questionnaireId, updateRequest, "admin");
// Publish questionnaire (direct operation, fails immediately on error)
QuestionnaireResponse published = questionnaireService.publishQuestionnaire(questionnaireId, "admin");
// Stop questionnaire (direct operation, fails immediately on error)
QuestionnaireResponse stopped = questionnaireService.stopQuestionnaire(questionnaireId, "admin");
// Delete questionnaire (direct operation, fails immediately on error)
questionnaireService.deleteQuestionnaire(questionnaireId, "admin");
```
#### Answer Submission
```java
// Submit questionnaire answers (direct operation, no fallback)
SubmitAnswerRequest answerRequest = new SubmitAnswerRequest();
answerRequest.setQuestionnaireId(questionnaireId);
answerRequest.setUserId("user123");
List<AnswerRequest> answers = new ArrayList<>();
// Single-choice answer
answers.add(new AnswerRequest(123L, "4")); // 满意
// Multiple-choice answer
answers.add(new AnswerRequest(124L, "tech_support,training")); // 技术支持和产品培训
// Text area answer
answers.add(new AnswerRequest(125L, "服务很好,希望能增加更多实用功能"));
answerRequest.setAnswers(answers);
ResponseDetailResponse response = questionnaireService.submitAnswer(answerRequest);
log.info("答案提交成功,回答ID: {}", response.getId());
```
#### Question Types and Answer Formats
##### 1. Single Choice (Type 1)
```java
// Creating single-choice question
CreateQuestionRequest singleChoice = new CreateQuestionRequest();
singleChoice.setTitle("您的性别是?");
singleChoice.setType(1);
singleChoice.setIsRequired(true);
singleChoice.setSort(1);
List<CreateQuestionOptionRequest> options = new ArrayList<>();
options.add(new CreateQuestionOptionRequest("男", "male", 1));
options.add(new CreateQuestionOptionRequest("女", "female", 2));
options.add(new CreateQuestionOptionRequest("不愿透露", "prefer_not_to_say", 3));
singleChoice.setOptions(options);
// Submitting single-choice answer
AnswerRequest singleChoiceAnswer = new AnswerRequest(123L, "male");
```
##### 2. Multiple Choice (Type 2)
```java
// Creating multiple-choice question
CreateQuestionRequest multipleChoice = new CreateQuestionRequest();
multipleChoice.setTitle("您感兴趣的编程语言有哪些?");
multipleChoice.setType(2);
multipleChoice.setIsRequired(false);
multipleChoice.setSort(2);
List<CreateQuestionOptionRequest> options = new ArrayList<>();
options.add(new CreateQuestionOptionRequest("Java", "java", 1));
options.add(new CreateQuestionOptionRequest("Python", "python", 2));
options.add(new CreateQuestionOptionRequest("Go", "go", 3));
options.add(new CreateQuestionOptionRequest("JavaScript", "javascript", 4));
multipleChoice.setOptions(options);
// Submitting multiple-choice answer (comma-separated values)
AnswerRequest multipleChoiceAnswer = new AnswerRequest(124L, "java,python,go");
```
##### 3. Fill in Blank (Type 3)
```java
// Creating fill-in-blank question
CreateQuestionRequest fillInBlank = new CreateQuestionRequest();
fillInBlank.setTitle("请输入您的姓名");
fillInBlank.setType(3);
fillInBlank.setIsRequired(true);
fillInBlank.setSort(3);
fillInBlank.setOptions(null); // No options needed
// Submitting fill-in-blank answer
AnswerRequest fillAnswer = new AnswerRequest(125L, "张三");
```
##### 4. Text Area (Type 4)
```java
// Creating text area question
CreateQuestionRequest textArea = new CreateQuestionRequest();
textArea.setTitle("请详细描述您对我们产品的建议");
textArea.setType(4);
textArea.setIsRequired(false);
textArea.setSort(4);
textArea.setOptions(null); // No options needed
// Submitting text area answer
AnswerRequest textAnswer = new AnswerRequest(126L, "建议增加更多功能,提升用户体验...");
```
##### 5. Rating (Type 5)
```java
// Creating rating question
CreateQuestionRequest rating = new CreateQuestionRequest();
rating.setTitle("请对我们的服务进行评分(1-10分)");
rating.setType(5);
rating.setIsRequired(true);
rating.setSort(5);
rating.setOptions(null); // No options needed, range controlled by frontend
// Submitting rating answer
AnswerRequest ratingAnswer = new AnswerRequest(127L, "8");
```
#### Complete Questionnaire Workflow
```java
// 1. Create questionnaire
CreateQuestionnaireRequest createRequest = buildSampleQuestionnaire();
QuestionnaireResponse questionnaire = questionnaireService.createQuestionnaire(createRequest, "admin");
// 2. Publish questionnaire
QuestionnaireResponse published = questionnaireService.publishQuestionnaire(questionnaire.getId(), "admin");
// 3. Users submit answers
SubmitAnswerRequest answerRequest = buildSampleAnswers(questionnaire.getId());
ResponseDetailResponse answerResponse = questionnaireService.submitAnswer(answerRequest);
// 4. View statistics
QuestionnaireStatistics statistics = questionnaireService.getStatistics(questionnaire.getId());
log.info("Statistics - Total responses: {}, Completion rate: {}%",
statistics.getTotalResponses(), statistics.getCompletionRate() * 100);
// 5. Stop questionnaire when done
QuestionnaireResponse stopped = questionnaireService.stopQuestionnaire(questionnaire.getId(), "admin");
```
#### Fallback Cache Management for Questionnaires
```java
@Autowired
private IntegrationFallbackService fallbackService;
// Check fallback cache status
boolean hasQuestionnaireCache = fallbackService.hasFallbackCache("zt-questionnaire", "questionnaire:1001");
boolean hasListCache = fallbackService.hasFallbackCache("zt-questionnaire", "questionnaire:list:1:10:null:null:null");
boolean hasStatsCache = fallbackService.hasFallbackCache("zt-questionnaire", "questionnaire:statistics:1001");
// Get cache statistics
IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-questionnaire");
log.info("Questionnaire fallback cache: {} items, TTL: {} days",
stats.getTotalCacheCount(), stats.getFallbackTtlDays());
// Clear specific cache
fallbackService.clearFallbackCache("zt-questionnaire", "questionnaire:1001");
// Clear all questionnaire caches
fallbackService.clearAllFallbackCache("zt-questionnaire");
```
### Question Types and Validation Rules
| Question Type | Type Value | Description | Options Required | Answer Format |
|---------------|------------|-------------|------------------|---------------|
| Single Choice | 1 | User can select one answer | Yes (2+ options) | Single option value |
| Multiple Choice | 2 | User can select multiple answers | Yes (2+ options) | Comma-separated option values |
| Fill in Blank | 3 | User inputs short text | No | Text content (1-200 chars) |
| Text Area | 4 | User inputs long text | No | Text content (1-2000 chars) |
| Rating | 5 | User provides numerical rating | No | Number as string (e.g., "1", "10") |
### Answer Validation Rules
| Question Type | Validation Rules | Example |
|---------------|------------------|---------|
| Single Choice | Must be existing option value | "male", "female" |
| Multiple Choice | Comma-separated existing option values | "java,python", "option1,option2,option3" |
| Fill in Blank | Non-empty string, 1-200 characters | "张三", "北京市" |
| Text Area | String, 1-2000 characters | "这是一段较长的文本内容..." |
| Rating | Numeric string, typically 1-10 range | "1", "5", "10" |
### Questionnaire Status
- **1**: Draft - Questionnaire is being edited
- **2**: Published - Questionnaire is live and accepting responses
- **3**: Stopped - Questionnaire is no longer accepting responses
- **4**: Deleted - Questionnaire has been deleted
### Common Use Cases
#### Customer Satisfaction Survey
```java
// Create customer satisfaction questionnaire with rating and feedback
CreateQuestionnaireRequest customerSurvey = new CreateQuestionnaireRequest();
customerSurvey.setName("客户满意度调查");
customerSurvey.setDescription("收集客户对服务的满意度反馈");
customerSurvey.setIsAnonymous(true);
// Add rating question
CreateQuestionRequest ratingQ = new CreateQuestionRequest();
ratingQ.setTitle("整体满意度评分(1-10分)");
ratingQ.setType(5);
ratingQ.setIsRequired(true);
// Add feedback question
CreateQuestionRequest feedbackQ = new CreateQuestionRequest();
feedbackQ.setTitle("请提供具体的改进建议");
feedbackQ.setType(4);
feedbackQ.setIsRequired(false);
customerSurvey.setQuestions(Arrays.asList(ratingQ, feedbackQ));
```
#### Product Feature Feedback
```java
// Create product feature questionnaire with multiple choice and priorities
CreateQuestionnaireRequest featureSurvey = new CreateQuestionnaireRequest();
featureSurvey.setName("产品功能需求调研");
featureSurvey.setIsAnonymous(false);
// Priority features question
CreateQuestionRequest featuresQ = new CreateQuestionRequest();
featuresQ.setTitle("您最希望我们优先开发哪些功能?");
featuresQ.setType(2); // Multiple choice
featuresQ.setIsRequired(true);
List<CreateQuestionOptionRequest> featureOptions = new ArrayList<>();
featureOptions.add(new CreateQuestionOptionRequest("移动端适配", "mobile_support", 1));
featureOptions.add(new CreateQuestionOptionRequest("数据导出", "data_export", 2));
featureOptions.add(new CreateQuestionOptionRequest("API集成", "api_integration", 3));
featureOptions.add(new CreateQuestionOptionRequest("高级分析", "advanced_analytics", 4));
featuresQ.setOptions(featureOptions);
featureSurvey.setQuestions(Arrays.asList(featuresQ));
```
### Error Handling and HTTP Status Codes
#### HTTP Status Codes
- **200 OK**: Operation successful
- **201 Created**: Questionnaire/response created successfully
- **400 Bad Request**: Invalid request parameters or validation errors
- **404 Not Found**: Questionnaire or response not found
- **500 Internal Server Error**: Server error occurred
#### Common Error Scenarios
```java
try {
QuestionnaireResponse questionnaire = questionnaireService.getQuestionnaire(invalidId);
} catch (IntegrationException e) {
switch (e.getCode()) {
case 404:
log.warn("问卷不存在: {}", invalidId);
break;
case 400:
log.warn("请求参数错误: {}", e.getMessage());
break;
case 500:
log.error("服务器内部错误: {}", e.getMessage());
break;
default:
log.error("未知错误: {}", e.getMessage());
}
}
```
### Testing Questionnaire Integration
```bash
# Run questionnaire integration tests
mvn test -Dtest=QuestionnaireIntegrationServiceTest
# Run all integration tests
mvn test -Dtest="com.ycwl.basic.integration.*Test"
# Enable example runner in application-dev.yml
integration:
questionnaire:
example:
enabled: true
```
### Configuration Properties
```yaml
integration:
questionnaire:
enabled: true # Enable questionnaire integration
serviceName: zt-questionnaire # Service name for Nacos discovery
connectTimeout: 5000 # Connection timeout in ms
readTimeout: 10000 # Read timeout in ms
retryEnabled: false # Enable retry mechanism
maxRetries: 3 # Maximum retry attempts
fallback:
questionnaire:
enabled: true # Enable fallback for questionnaire service
ttlDays: 7 # Cache TTL in days
cachePrefix: "questionnaire:fallback:" # Optional custom prefix
```
### Best Practices for Questionnaire Integration
#### Query vs Mutation Operations
- **Query operations (GET)**: Use fallback - questionnaire details, lists, statistics, responses
- **Mutation operations (POST/PUT/DELETE)**: No fallback - create, update, delete, publish, stop, submit
#### Cache Key Design
- `questionnaire:{id}` - Individual questionnaire cache
- `questionnaire:list:{page}:{size}:{name}:{status}:{createdBy}` - List cache
- `questionnaire:statistics:{id}` - Statistics cache
- `response:{id}` - Individual response cache
- `responses:list:{page}:{size}:{questionnaireId}:{userId}` - Response list cache
#### Answer Submission Best Practices
- Validate question types before submission
- Handle validation errors gracefully
- Provide clear error messages for users
- Log submission attempts for audit purposes
#### Performance Considerations
- Use appropriate page sizes for questionnaire lists
- Cache frequently accessed questionnaires
- Monitor response submission patterns
- Implement rate limiting for public questionnaires