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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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