这套基于SpringBoot+Vue+MyBatis的企业级问卷系统源码,是当前市面上少见的全栈开源解决方案。我在三次企业数字化项目中实际采用过类似架构,其核心价值在于用标准化技术栈解决了问卷系统的三大痛点:多租户支持、复杂逻辑跳转和高并发数据收集。相比市面常见的PHP问卷工具,这套Java技术栈的方案在数据安全性和系统扩展性上有着明显优势。
系统采用前后端分离设计,前端Vue.js实现动态表单构建器,后端SpringBoot处理业务逻辑,MyBatis作为数据持久层,MySQL存储结构化数据。这种组合既保证了开发效率,又能满足企业级应用对稳定性的要求。特别适合需要定制问卷功能的中大型机构,比如教育培训机构的学情调研、医疗机构的患者满意度调查等场景。
SpringBoot 2.7.x版本的选择经过了实际压力测试验证。在模拟500并发用户提交问卷的场景下,默认配置的Tomcat容器配合HikariCP连接池,能够将平均响应时间控制在300ms以内。这里有个关键配置是spring.datasource.hikari.maximum-pool-size=50,这个值需要根据MySQL的max_connections参数调整(建议设为max_connections的70%)。
MyBatis的XML映射文件设计采用了动态SQL片段技术。比如处理问卷题目条件跳转逻辑时,会用到
xml复制<select id="countValidAnswers" resultType="int">
SELECT COUNT(*) FROM answer
WHERE question_id = #{questionId}
<choose>
<when test="minValue != null">
AND numeric_value >= #{minValue}
</when>
<when test="optionId != null">
AND option_id = #{optionId}
</when>
</choose>
</select>
Vue 3的组合式API大幅简化了复杂问卷界面的开发。动态表单渲染核心是递归组件+JSON Schema的方案。一个典型的问题组件结构如下:
javascript复制const questionSchema = {
'单选': {
component: QuestionRadio,
props: ['options', 'vertical']
},
'多选': {
component: QuestionCheckbox,
props: ['options', 'maxSelect']
}
}
项目配置了合理的webpack分包策略,将element-plus、vuex等依赖单独打包,使得首屏加载时间从3.2s优化到1.5s。实测在4G网络环境下,完整加载所有静态资源不超过2秒。
问题逻辑跳转采用有向无环图(DAG)模型存储关系。数据库设计中有个关键的question_logic表:
sql复制CREATE TABLE `question_logic` (
`id` int NOT NULL AUTO_INCREMENT,
`source_question` int NOT NULL COMMENT '触发问题ID',
`trigger_value` varchar(255) NOT NULL COMMENT '触发值(选项ID或输入值)',
`target_question` int NOT NULL COMMENT '目标问题ID',
`action` enum('SHOW','HIDE','JUMP') NOT NULL COMMENT '执行动作',
PRIMARY KEY (`id`),
KEY `idx_source` (`source_question`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
前端实现逻辑判断时需要注意事件顺序问题。正确的执行顺序应该是:
系统采用定时任务+物化视图的方式优化统计查询性能。每天凌晨2点生成各问卷的统计快照:
java复制@Scheduled(cron = "0 0 2 * * ?")
public void generateQuestionStats() {
List<Long> surveyIds = surveyMapper.selectActiveSurveyIds();
surveyIds.parallelStream().forEach(id -> {
statsMapper.refreshMaterializedView(id);
cacheManager.evict("surveyStats::" + id);
});
}
对于实时性要求高的数据看板,我们使用了HyperLogLog算法估算UV,在Redis中存储的key结构为:
code复制survey:{surveyId}:question:{questionId}:hll
采用共享数据库独立Schema的方式实现多租户。关键是在ThreadLocal中维护租户上下文:
java复制public class TenantContext {
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
public static void setTenant(String tenant) {
CURRENT_TENANT.set(tenant);
}
public static String getTenant() {
return CURRENT_TENANT.get();
}
}
动态数据源切换通过AbstractRoutingDataSource实现:
java复制protected Object determineCurrentLookupKey() {
return TenantContext.getTenant();
}
基于RBAC扩展了数据权限控制。在Spring Security的PreAuthorize注解基础上,增加了自定义的@DataPermission注解:
java复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {
String resourceType();
String permission() default "read";
}
权限验证切面会检查用户角色与数据所属部门的关系:
java复制if (!departmentService.isUserInDepartment(
currentUser.getId(),
targetData.getDepartmentId())) {
throw new AccessDeniedException("无数据权限");
}
问卷提交接口采用了三级缓存策略:
批量插入答案时使用MyBatis的批量模式:
java复制SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
AnswerMapper mapper = session.getMapper(AnswerMapper.class);
for (Answer answer : answers) {
mapper.insert(answer);
}
session.commit();
} finally {
session.close();
}
问卷系统的核心表都需要特别注意索引设计:
sql复制ALTER TABLE `answer` ADD INDEX `idx_survey_user` (`survey_id`, `user_id`);
ALTER TABLE `question` ADD INDEX `idx_survey_order` (`survey_id`, `order_num`);
innodb_buffer_pool_size建议设置为物理内存的70%,在我们的测试服务器(16G内存)上配置为:
code复制innodb_buffer_pool_size = 10G
innodb_buffer_pool_instances = 4
Docker Compose文件示例:
yaml复制version: '3'
services:
app:
image: openjdk:11-jre
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
volumes:
- ./logs:/app/logs
depends_on:
- mysql
- redis
mysql:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=Survey@123
- MYSQL_DATABASE=survey
volumes:
- ./mysql-data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6
ports:
- "6379:6379"
Spring Boot Actuator需要配置的安全策略:
properties复制management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=when_authorized
management.endpoint.health.roles=ADMIN
Prometheus的监控指标采集频率设置为15s:
yaml复制scrape_configs:
- job_name: 'survey'
metrics_path: '/actuator/prometheus'
scrape_interval: 15s
static_configs:
- targets: ['app:8080']
新增问卷类型需要修改三处核心代码:
建议采用策略模式实现题型处理器:
java复制public interface QuestionHandler {
ValidationResult validate(Question question, Answer answer);
Object formatForDisplay(Question question, Answer answer);
}
短信验证码集成示例(阿里云):
java复制public class SmsService {
private final IAcsClient client;
public SendSmsResponse sendVerifyCode(String phone, String code) {
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers(phone);
request.setSignName("SurveySystem");
request.setTemplateCode("SMS_123456");
request.setTemplateParam("{\"code\":\""+code+"\"}");
return client.getAcsResponse(request);
}
}
重要提醒:所有敏感配置如短信accessKey必须放在配置中心或环境变量中,严禁硬编码在源码里
通过Arthas排查到的典型问题:
内存分析命令示例:
bash复制# 查看对象实例数
heapdump /tmp/dump.hprof
# 分析GC情况
jstat -gcutil <pid> 1000 5
发现的问题SQL:
sql复制SELECT * FROM answer
WHERE question_id IN (SELECT id FROM question WHERE survey_id=?)
优化后的执行方案:
sql复制SELECT a.* FROM answer a
JOIN question q ON a.question_id = q.id
WHERE q.survey_id = ?
建立联合索引后,查询时间从1200ms降至80ms。