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
|
@@ -31,6 +31,11 @@ public class IntegrationProperties {
|
||||
*/
|
||||
private RenderWorkerConfig render = new RenderWorkerConfig();
|
||||
|
||||
/**
|
||||
* 问卷服务配置
|
||||
*/
|
||||
private QuestionnaireConfig questionnaire = new QuestionnaireConfig();
|
||||
|
||||
@Data
|
||||
public static class ScenicConfig {
|
||||
/**
|
||||
@@ -104,6 +109,7 @@ public class IntegrationProperties {
|
||||
private ServiceFallbackConfig scenic = new ServiceFallbackConfig();
|
||||
private ServiceFallbackConfig device = new ServiceFallbackConfig();
|
||||
private ServiceFallbackConfig render = new ServiceFallbackConfig();
|
||||
private ServiceFallbackConfig questionnaire = new ServiceFallbackConfig();
|
||||
}
|
||||
|
||||
@Data
|
||||
@@ -131,6 +137,31 @@ public class IntegrationProperties {
|
||||
private int maxRetries = 3;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class QuestionnaireConfig {
|
||||
/**
|
||||
* 是否启用问卷服务集成
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 服务名称
|
||||
*/
|
||||
private String serviceName = "zt-questionnaire";
|
||||
|
||||
/**
|
||||
* 超时配置(毫秒)
|
||||
*/
|
||||
private int connectTimeout = 5000;
|
||||
private int readTimeout = 10000;
|
||||
|
||||
/**
|
||||
* 重试配置
|
||||
*/
|
||||
private boolean retryEnabled = false;
|
||||
private int maxRetries = 3;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ServiceFallbackConfig {
|
||||
/**
|
||||
|
@@ -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();
|
||||
}
|
@@ -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;
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user