You've already forked FrameTour-BE
feat(integration): 添加问卷服务集成模块
- 新增问卷服务配置和客户端接口 - 实现问卷创建、查询、提交答案和统计分析等功能 - 添加问卷集成示例,演示各项功能的使用- 设计并实现问卷服务的 fallback 缓存管理策略
This commit is contained in:
@@ -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
|
Reference in New Issue
Block a user