调查问卷系统设计

 核心表设计

1、问卷表

create table t_questionnaire_form
(
    id          bigint auto_increment comment 'id'
        primary key,
    uuid        varchar(36)                        not null comment 'uuid',
    biz_type    smallint                           null comment '对应业务类型',
    title       varchar(255)                       null comment '标题',
    remark      varchar(1024)                      null comment '说明',
    form_status tinyint  default 1                 null comment '状态 1:未发布 2:已发布 3:暂停发布',
    update_time datetime default CURRENT_TIMESTAMP null comment '修改时间',
    create_user varchar(36)                        null comment '创建人',
    create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间',
    update_user varchar(36)                        null comment '修改人',
    constraint questionnaire_form_uuid_uindex
        unique (uuid)
)
    comment '问卷表';

 2、题目表

create table t_questionnaire_question
(
    id             bigint auto_increment comment 'id'
        primary key,
    uuid           varchar(36)                        not null comment 'uuid',
    form_uuid      varchar(36)                        not null comment '对应问卷表单uuid',
    type           tinyint                            not null comment '题目类型 1:单选 2:多选 3:填空',
    question_title varchar(255)                       null comment '题目标题',
    sort           int      default 0                 not null comment '排序',
    update_time    datetime default CURRENT_TIMESTAMP not null comment '修改时间',
    create_time    datetime default CURRENT_TIMESTAMP null comment '创建时间',
    create_user    varchar(36)                        null comment '创建人',
    update_user    varchar(36)                        null comment '修改人',
    constraint questionnaire_question_uuid_uindex
        unique (uuid)
)
    comment '问卷题目表';

3、选项表

create table t_questionnaire_option
(
    id            bigint auto_increment comment 'id'
        primary key,
    uuid          varchar(36)                        null comment 'uuid',
    form_uuid     varchar(36)                        not null comment '问卷uuid',
    question_uuid varchar(36)                        null comment '题目uuid',
    option_name   varchar(255)                       null comment '选项名称',
    sort          int      default 0                 not null comment '排序',
    create_time   datetime default CURRENT_TIMESTAMP not null comment '创建时间',
    update_time   datetime default CURRENT_TIMESTAMP null comment '修改时间',
    create_user   varchar(36)                        null comment '创建人',
    update_user   varchar(36)                        null comment '修改人'
)
    comment '问卷选项表';

4、用户答题表

create table t_questionnaire_user_option_relation
(
    id            bigint auto_increment comment 'id'
        primary key,
    uuid          varchar(36)                        null comment 'uuid',
    biz_type      smallint                           null comment '业务类型',
    answer_user   varchar(36)                        not null comment '回答用户',
    form_uuid     varchar(36)                        not null comment '问卷uuid',
    question_uuid varchar(36)                        not null comment '问题uuid',
    option_uuid   varchar(36)                        null comment '选项uuid',
    answer        varchar(1024)                      not null comment '回答(多选用,拼接)',
    create_time   datetime default CURRENT_TIMESTAMP null comment '创建时间',
    constraint questionnaire_user_option_relation_uuid_uindex
        unique (uuid)
)
    comment '用户与选项关系表';

5、用户答题记录表

create table t_questionnaire_user_form_relation
(
    id          bigint auto_increment comment 'id'
        primary key,
    uuid        varchar(36)                         not null comment 'uuid',
    biz_type    smallint                            null comment '业务类型',
    form_uuid   varchar(36)                         null comment '问卷uuid',
    answer_user varchar(36)                         not null comment '回答用户',
    use_times   int                                 null comment '用时 单位秒',
    end_time    datetime                            null comment '答题开始时间',
    start_time  datetime                            null comment '答题结束时间',
    create_time timestamp default CURRENT_TIMESTAMP null comment '创建时间',
    constraint questionnaire_user_form_relation_uuid_uindex
        unique (uuid)
)
    comment '用户与问卷关系表';

数据流向说明

题目的3张表就是简单的一对多对多 多的一方记录一的一方的uuid 

页面接口设计

运营侧 :

新增/修改/停启用调查问卷 (一下操作3个表)

统计相关答题人次 提交时间区间

问卷选择题的选项占比分布 问卷简答题的 高频词Top10

 用户侧:

详情 答题

Service接口设计

暂无

选项分布统计核心代码

统计

public OptionStatisticsVO calculateOptionStats(Long questionId) {
    // 1. 获取问题基本信息
    Question question = questionRepository.findById(questionId)
        .orElseThrow(() -> new ResourceNotFoundException("问题不存在"));
    
    if (!question.getType().isChoiceType()) {
        throw new BusinessException("非选择题类型");
    }

    // 2. 获取所有选项
    List<Option> options = optionRepository.findByQuestionId(questionId);
    
    // 3. 获取原始答案数据
    List<AnswerDetail> answers = answerDetailRepository.findByQuestionId(questionId);
    
    // 4. 统计计算
    Map<Long, AtomicInteger> optionCountMap = new HashMap<>();
    Set<Long> respondentSet = new HashSet<>();
    // 数据初始化
    options.forEach(option -> optionCountMap.put(option.getId(), new AtomicInteger(0)));
    
    for (AnswerDetail answer : answers) {
        if (StringUtils.isBlank(answer.getOptionIds())) continue;
        //处理多选情况
        Set<Long> selectedOptions = Arrays.stream(answer.getOptionIds().split(","))
            .map(Long::parseLong)
            .collect(Collectors.toSet());
        
        // 统计选项
        selectedOptions.forEach(optionId -> {
            if (optionCountMap.containsKey(optionId)) {
                //数量自增
                optionCountMap.get(optionId).incrementAndGet();
            }
        });
        
        // 统计总人数
        respondentSet.add(answer.getAnswerSheetId());
    }
    
    // 5. 计算百分比
    int totalRespondents = respondentSet.size();
    List<OptionStatDTO> stats = new ArrayList<>();
    
    for (Option option : options) {
        int count = optionCountMap.get(option.getId()).get();
        double percentage = totalRespondents > 0 ? 
            (count * 100.0) / totalRespondents : 0.0;
        
        stats.add(new OptionStatDTO(
            option.getId(),
            option.getContent(),
            count,
            Math.round(percentage * 100.0) / 100.0
        ));
    }
    
    // 6. 按选择次数排序
    stats.sort((a, b) -> b.getCount() - a.getCount());
    
    return new OptionStatisticsVO(
        question.getId(),
        question.getContent(),
        totalRespondents,
        stats
    );
}

选项DTO

@Data
@AllArgsConstructor
public class OptionStatDTO {
    @ApiModelProperty(value = "选项id")
    private Long optionId;
    @ApiModelProperty(value = "选项内容")
    private String optionContent;
    @ApiModelProperty(value = "被选择次数")
    private Integer count;
    @ApiModelProperty(value = "百分比(0-100)")
    private Double percentage;
}

统计结果VO

@Data
@AllArgsConstructor
public class OptionStatisticsVO {
    @ApiModelProperty(value = "问题id")
    private Long questionId;
    @ApiModelProperty(value = "问题内容")
    private String questionContent;
    @ApiModelProperty(value = "总回答数")
    private Integer totalRespondents;
    @ApiModelProperty(value = "选项DTO集合")
    private List<OptionStatDTO> optionsStats;
}

注意事项&拓展

每走一次统计 上边算一遍 不如放缓存里面 后面直接读缓存 

搞个定时任务 扫表写缓存  key 问卷uuid value对应 statisticsVOList

public void preCalculateStatistics() {
    //获取所有选择题目
    List<Question> questions = questionRepository.findAllChoiceQuestions();
    //根据问卷id分组
        Map<String, List<Question>> questionMap =
                questions.stream().collect(Collectors.groupingBy(Question::getFormId));
        //遍历questionMap
        questionMap.forEach((formId, questionList) -> {
            List<OptionStatisticsVO> statisticsVOList = new ArrayList<>();
            questionList.forEach(question -> {
                OptionStatisticsVO stats = calculateOptionStats(question.getId());
                statisticsVOList.add(stats);
            });
            RedisUtil.addIntoList("question:stats:" + formId, statisticsVOList);
        });
}

如果问题多 可以考虑多线程并发执行 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值