代码加入保存数据库功能, 创建DLS语句来创建调查问卷

This commit is contained in:
lulz1 2024-09-24 16:49:31 +08:00
parent 2347120cd3
commit cebfde04b7
10 changed files with 908 additions and 331 deletions

View File

@ -12,5 +12,6 @@
</option>
</GradleProjectSettings>
</option>
<option name="offlineMode" value="true" />
</component>
</project>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.10" />
<option name="version" value="2.0.20" />
</component>
</project>

View File

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jdk-17.0.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@ -1,5 +1,9 @@
plugins {
kotlin("jvm") version "2.0.10"
kotlin("jvm") version "2.0.20"
kotlin("plugin.spring") version "2.0.20"
kotlin("plugin.serialization") version "2.0.20"
id("org.springframework.boot") version "3.3.3"
id("io.spring.dependency-management") version "1.1.6"
}
group = "org.echo"
@ -26,3 +30,23 @@ repositories {
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.apache.httpcomponents:httpclient:4.5.14")
implementation("com.google.code.gson:gson:2.10.1")
runtimeOnly("com.mysql:mysql-connector-j")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.test {
useJUnitPlatform() // 确保使用 JUnit 平台
}

View File

@ -1,5 +0,0 @@
package org.echo
fun main() {
println("Hello World!")
}

View File

@ -0,0 +1,737 @@
package org.echo.org.echo.questionnaire
import org.springframework.data.annotation.Id
import org.springframework.data.annotation.Transient
import org.springframework.data.relational.core.mapping.MappedCollection
import org.springframework.data.relational.core.mapping.Table
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository
import java.util.Optional
import kotlin.math.abs
import kotlin.random.Random
@Table("questionnaire")
class Questionnaire(
@Id val id: Long?,
val title: String, // 问卷的标题
val description: String, // 问卷的描述或说明
@MappedCollection(idColumn = "questionnaire_id", keyColumn = "questionnaire_key")
val questions: List<SurveyQuestion>,
@MappedCollection(idColumn = "questionnaire_id", keyColumn = "questionnaire_key")
val questionGroup: List<QuestionGroup>,
@MappedCollection(idColumn = "questionnaire_id")
val surveyBackground: SurveyBackground, // 问卷中的所有问题
@MappedCollection(idColumn = "questionnaire_id", keyColumn = "questionnaire_key")
val procedures: List<SurveyProcedure>,
) {
@Transient
private val selectedOptions: MutableMap<Long, MutableSet<Long>> = mutableMapOf()// Map of questionId to optionId
companion object {
fun create(
title: String,
description: String,
questionGroup: List<QuestionGroup>,
questions: List<SurveyQuestion>,
surveyBackground: SurveyBackground,
procedures: List<SurveyProcedure>
): Questionnaire {
return Questionnaire(null, title, description, questions, questionGroup, surveyBackground, procedures)
}
fun questionnaire(block: QuestionnaireBuilder.() -> Unit): Questionnaire {
val builder = QuestionnaireBuilder()
builder.block()
return builder.build()
}
}
fun selectOption(questionId: Long, optionId: Long) {
val options = selectedOptions.getOrPut(questionId) { mutableSetOf() }
options.add(optionId)
onOptionSelected(questionId, optionId)
}
fun isOptionSelected(questionId: Long, optionId: Long): Boolean {
return selectedOptions[questionId]?.contains(optionId) == true
}
fun getSelectedOption(questionId: Long): Set<QuestionOption>? {
val optionIds = selectedOptions[questionId]
return optionIds?.let {
questions.map { it.question }
.find { it.id == questionId }
?.optionList
?.filter { it.id in optionIds }
?.toSet()
}
}
fun onOptionSelected(questionId: Long, optionId: Long) {
// Identify target questions influenced by the source question
val influencedQuestionIds = getInfluencedQuestionIdsInGroup(questionId)
for (targetQuestionId in influencedQuestionIds) {
addDependenciesAfterAnswer(this, questionId, optionId, targetQuestionId)
}
}
fun getInfluencedQuestionIdsInGroup(questionId: Long): Set<Long> {
return this.questionGroup.filter { it.checkIfInGroup(questionId) }
.flatMap { it.surveyQuestionList.map { it.question.id!! }.toSet() }
.toSet()
}
fun addDependenciesAfterAnswer(
questionnaire: Questionnaire,
sourceQuestionId: Long,
sourceOptionId: Long,
targetQuestionId: Long,
) {
val influencedOptionSet = questionnaire.getSelectedOption(sourceQuestionId)
val targetQuestion = questionnaire.questions.map { it.question }.find { it.id == targetQuestionId }
if (influencedOptionSet == null || targetQuestion == null) return
for (influencedOption in influencedOptionSet) {
targetQuestion.optionList.forEach { targetOption ->
// Remove existing dependencies from the same source question
val dependency = OptionDependency(
sourceQuestionId = sourceQuestionId,
sourceOptionId = sourceOptionId,
influencedOptionId = influencedOption.id!!,
targetOptionId = targetQuestion.id!!,
condition = { q ->
q.isOptionSelected(sourceQuestionId, sourceOptionId)
},
effect = { _ ->
// 使用枚举类的 calculateEffectBasedOnOrder 方法
val effectValue = ComputeMethod.BaseComputeMethod
.calculateEffectBasedOnOrder(influencedOption, targetOption)
// 根据计算结果进行相应处理
println("Effect calculated: $effectValue")
effectValue // 返回 effect 值,这个可以用于后续的逻辑
}
)
targetOption.addDependency(dependency)
}
}
}
fun calculateEffectBasedOnOrder(
influencedOption: QuestionOption,
targetOption: QuestionOption, //是他自己
): Double {
val orderDifference = abs(influencedOption.markOrder - targetOption.markOrder)
return when (orderDifference) {
0 -> 1.0
1 -> 0.7
2 -> 0.4
else -> 0.1
}
}
fun autoFillAnswers() {
for (group in questionGroup) {
group.surveyQuestionList
.map { it.question }
.forEach { question -> autoSelectAnswer(question) }
}
}
fun autoSelectAnswer(question: Question) {
val weightedOptions = question.optionList
.map { it.calculateAndMapToWeight(this) }
// 按照权重随机选择
val selectedOption = weightedRandomSelection(weightedOptions)
selectOption(question.id!!, selectedOption.optionId)
// 如果是多选题,随机选择两个选项(前提是至少有两个选项可选)
if (question.questionType == QuestionType.MULTIPLE_CHOICE &&
weightedOptions.size > 1
) {
val secondOption =
weightedRandomSelection(weightedOptions.filter { it.optionId != selectedOption.optionId })
selectOption(question.id, secondOption.optionId)
}
}
fun weightedRandomSelection(weightedOptions: List<QuestionOptionWeight>): QuestionOptionWeight {
val totalWeight = weightedOptions.sumOf { it.weight }
val randomValue = Random.nextDouble() * totalWeight
var cumulativeWeight = 0.0
for (option in weightedOptions) {
cumulativeWeight += option.weight
if (randomValue <= cumulativeWeight) {
return option
}
}
return weightedOptions.last()
}
}
@Table("question")
open class Question(
@Id val id: Long?,
val wrapperQuestionId: Long?,
val title: String,
val content: String,
@MappedCollection(idColumn = "question_id", keyColumn = "question_key")
val optionList: List<QuestionOption>,
val questionType: QuestionType, // 表示问题类型:单选、多选、开放式等
val order: Int, // 表示问题顺序
val isRequired: Int = 1, // 是否必答,默认为必答
) {
companion object {
fun create(
title: String,
content: String,
optionList: List<QuestionOption>,
questionType: QuestionType,
order: Int,
isRequired: Int = 1
): Question {
return Question(
id = null, // `id` 由数据库生成
wrapperQuestionId = null,
title = title,
content = content,
optionList = optionList,
questionType = questionType,
order = order,
isRequired = isRequired
)
}
}
fun getOptionListData(): List<QuestionOption> {
return optionList
}
}
@Table("question_group")
class QuestionGroup(
@Id val id: Long?,
val questionnaireId: Long?,
val parentId: Long?,
val groupName: String,
@MappedCollection(idColumn = "question_group_id", keyColumn = "question_group_key")
val surveyQuestionList: List<SurveyQuestion>
) {
@Transient
private val answeredQuestions: MutableMap<SurveyQuestion, QuestionOption> = mutableMapOf()
companion object {
fun create(
parentId: Long?,
groupName: String,
surveyQuestionList: List<SurveyQuestion>
): QuestionGroup {
return QuestionGroup(
id = null, // `id` 由数据库生成
questionnaireId = null, //外键由数据库生成
parentId = parentId,
groupName = groupName,
surveyQuestionList = surveyQuestionList
)
}
}
fun checkIfInGroup(questionId: Long): Boolean {
return surveyQuestionList.map { it.question }.any { it.id == questionId }
}
}
@Table("question_option")
class QuestionOption(
@Id val id: Long?,
val questionId: Long?, //外键
val label: String,
val content: String,
val mark: String,
val markOrder: Int,
val score: Int? = null, // 可选的分数字段
) {
@Transient
private val dependencies: MutableList<OptionDependency> = mutableListOf()
companion object {
fun create(
label: String,
content: String,
mark: String,
markOrder: Int,
score: Int? = null
): QuestionOption {
return QuestionOption(
id = null, // `id` 由数据库生成
questionId = null, //外键
label = label,
content = content,
mark = mark,
markOrder = markOrder,
score = score
)
}
}
// 计算依赖关系对权重的影响
fun calculateWeightWithDependencies(questionnaire: Questionnaire): Double {
var baseWeight = 1.0
dependencies.forEach { dependency ->
if (dependency.condition(questionnaire)) {
baseWeight += dependency.effect(questionnaire)
}
}
return baseWeight.coerceAtLeast(0.0)
}
fun calculateAndMapToWeight(questionnaire: Questionnaire): QuestionOptionWeight {
return QuestionOptionWeight(id!!, calculateWeightWithDependencies(questionnaire))
}
fun addDependency(dependency: OptionDependency) {
//添加选项依赖,添加前会删除所有之前 问题的产生的依赖项,然后再进行添加
val sourceQuestionId = dependency.sourceQuestionId
this.dependencies.removeAll { it.sourceQuestionId == sourceQuestionId }
this.dependencies.add(dependency)
}
}
class QuestionOptionWeight(
val optionId: Long,
val weight: Double,
)
class OptionDependency(
val sourceQuestionId: Long,
val sourceOptionId: Long,
val influencedOptionId: Long,
val targetOptionId: Long,
val condition: (Questionnaire) -> Boolean,
val effect: (Questionnaire) -> Double,
)
enum class QuestionType {
SINGLE_CHOICE, // 单选
MULTIPLE_CHOICE, // 多选
OPEN_ENDED // 开放式问题
}
@Table("survey_question")
class SurveyQuestion(
@Id val id: Long?,
val questionnaireId: Long?,
val title: String,
val content: String,
@MappedCollection(idColumn = "wrapper_question_id", keyColumn = "wrapper_question_key")
val question: Question,
) {
companion object {
fun create(
title: String,
content: String,
question: Question
): SurveyQuestion {
return SurveyQuestion(
id = null, // `id` 由数据库生成
questionnaireId = null, //外键值由数据库生成
title = title,
content = content,
question = question
)
}
}
fun getQuestionData(): Question {
return this.question
}
}
class SurveyBackgroundSample(
val surveyRespondent: SurveyRespondent,
val surveyBackgroundList: List<SurveyBackground>,
)
class SurveyRespondent(
val name: String,
val age: Int,
)
@Table("survey_background")
class SurveyBackground(
@Id val id: Long?,
val questionnaireId: Long?,
val title: String,
@MappedCollection(idColumn = "wrapper_question_id", keyColumn = "wrapper_question_key")
val backgroundQuestion: List<Question>,
) {
companion object {
fun create(
title: String,
backgroundQuestion: List<Question>
): SurveyBackground {
return SurveyBackground(
id = null, // `id` 由数据库生成
questionnaireId = null,
title = title,
backgroundQuestion = backgroundQuestion
)
}
}
}
@Table("survey_procedure")
class SurveyProcedure(
@Id val id: Long?,
val questionnaireId: Long?,
val sourceQuestionId: Long,
val sourceOptionId: Long,
val influencedQuestionId: Long,
val influencedOptionId: Long,
var computeMethodName: String?,
@Transient val computeMethod: ComputeMethod = ComputeMethod.BaseComputeMethod
) {
companion object {
fun create(
sourceQuestionId: Long,
sourceOptionId: Long,
influencedQuestionId: Long,
influencedOptionId: Long,
computeMethod: ComputeMethod = ComputeMethod.BaseComputeMethod
): SurveyProcedure {
val surveyProcedure = SurveyProcedure(
id = null, // `id` 由数据库生成
questionnaireId = null,
sourceQuestionId = sourceQuestionId,
sourceOptionId = sourceOptionId,
influencedQuestionId = influencedQuestionId,
influencedOptionId = influencedOptionId,
computeMethodName = null,
computeMethod = computeMethod
)
surveyProcedure.computeMethodName = computeMethod.name
return surveyProcedure
}
}
fun ifProcess(sourceOptionalId: Long, questionnaire: Questionnaire) {
if (sourceOptionalId == sourceOptionId) generateOptionDependency(questionnaire)
}
fun generateOptionDependency(questionnaire: Questionnaire) {
val influencedQuestionOptions = questionnaire.questions
.filter { it.question.id == influencedQuestionId }
.map { it.question }
.flatMap { it.optionList }
val influencedOption = influencedQuestionOptions
.filter { option -> option.id == influencedOptionId }
.last()
influencedQuestionOptions.forEach {
//遍历所有的选项,向其中加入选项依赖
val optionDependency = OptionDependency(
sourceOptionId, sourceOptionId,
influencedOption.id!!,
it.id!!,
condition = { q ->
q.isOptionSelected(sourceQuestionId, sourceOptionId)
},
effect = { _ ->
// Define your effect based on source and target options
computeMethod.calculateEffectBasedOnOrder(influencedOption, it)
}
)
it.addDependency(optionDependency)
}
}
}
enum class ComputeMethod(
val computeMethodName: String,
) {
BaseComputeMethod("Default") {
override fun calculateEffectBasedOnOrder(
influencedOption: QuestionOption,
targetOption: QuestionOption,
): Double {
val orderDifference = abs(influencedOption.markOrder - targetOption.markOrder)
return when (orderDifference) {
0 -> 1.0
1 -> 0.7
2 -> 0.4
else -> 0.1
}
}
};
// 定义抽象方法,子类必须实现
abstract fun calculateEffectBasedOnOrder(
sourceOption: QuestionOption,
targetOption: QuestionOption,
): Double
}
interface QuestionnaireRepository {
fun save(questionnaire: Questionnaire): Questionnaire
fun findById(id: Long): Optional<Questionnaire>
}
@Repository
class QuestionnaireRepositoryImpl(private val jdbcQuestionnaireRepository: JdbcQuestionnaireRepository) :
QuestionnaireRepository {
override fun save(questionnaire: Questionnaire): Questionnaire {
return jdbcQuestionnaireRepository.save(questionnaire)
}
override fun findById(id: Long): Optional<Questionnaire> {
return jdbcQuestionnaireRepository.findById(id)
}
}
@Repository
interface JdbcQuestionnaireRepository : CrudRepository<Questionnaire, Long>
@DslMarker
annotation class QuestionnaireDsl
@QuestionnaireDsl
class QuestionnaireBuilder {
var title: String = ""
var description: String = ""
private val questions = mutableListOf<SurveyQuestion>()
private val questionGroups = mutableListOf<QuestionGroup>()
private val procedures = mutableListOf<SurveyProcedure>()
lateinit var surveyBackground: SurveyBackground
fun questionGroup(block: QuestionGroupBuilder.() -> Unit) {
val groupBuilder = QuestionGroupBuilder()
groupBuilder.block()
val group = groupBuilder.build()
questionGroups.add(group)
questions.addAll(group.surveyQuestionList)
}
fun procedures(block: ProceduresBuilder.() -> Unit) {
val proceduresBuilder = ProceduresBuilder()
proceduresBuilder.block()
procedures.addAll(proceduresBuilder.build())
}
fun background(block: BackgroundBuilder.() -> Unit) {
val backgroundBuilder = BackgroundBuilder()
backgroundBuilder.block()
surveyBackground = backgroundBuilder.build()
}
fun build(): Questionnaire {
return Questionnaire.create(
title = title,
description = description,
questionGroup = questionGroups,
questions = questions,
surveyBackground = surveyBackground,
procedures = procedures
)
}
}
@QuestionnaireDsl
class QuestionGroupBuilder {
var parentId: Long? = null
var groupName: String = ""
private val surveyQuestions = mutableListOf<SurveyQuestion>()
fun surveyQuestion(block: SurveyQuestionBuilder.() -> Unit) {
val questionBuilder = SurveyQuestionBuilder()
questionBuilder.block()
surveyQuestions.add(questionBuilder.build())
}
fun build(): QuestionGroup {
return QuestionGroup.create(
parentId = parentId,
groupName = groupName,
surveyQuestionList = surveyQuestions
)
}
}
@QuestionnaireDsl
class SurveyQuestionBuilder {
var title: String = ""
var content: String = ""
lateinit var question: Question
fun question(block: QuestionBuilder.() -> Unit) {
val questionBuilder = QuestionBuilder()
questionBuilder.block()
question = questionBuilder.build()
}
fun build(): SurveyQuestion {
return SurveyQuestion.create(
title = title,
content = content,
question = question
)
}
}
@QuestionnaireDsl
class QuestionBuilder {
var title: String = ""
var content: String = ""
var questionType: QuestionType = QuestionType.SINGLE_CHOICE
var order: Int = 0
var isRequired: Int = 1
private val options = mutableListOf<QuestionOption>()
fun options(block: OptionsBuilder.() -> Unit) {
val optionsBuilder = OptionsBuilder()
optionsBuilder.block()
options.addAll(optionsBuilder.build())
}
fun build(): Question {
return Question.create(
title = title,
content = content,
optionList = options,
questionType = questionType,
order = order,
isRequired = isRequired
)
}
}
@QuestionnaireDsl
class OptionsBuilder {
private val options = mutableListOf<QuestionOption>()
fun option(block: OptionBuilder.() -> Unit) {
val optionBuilder = OptionBuilder()
optionBuilder.block()
options.add(optionBuilder.build())
}
fun build(): List<QuestionOption> = options
}
@QuestionnaireDsl
class OptionBuilder {
var label: String = ""
var content: String = ""
var mark: String = ""
var markOrder: Int = 0
var score: Int? = null
fun build(): QuestionOption {
return QuestionOption.create(
label = label,
content = content,
mark = mark,
markOrder = markOrder,
score = score
)
}
}
@QuestionnaireDsl
class ProceduresBuilder {
private val procedures = mutableListOf<SurveyProcedure>()
fun procedure(block: ProcedureBuilder.() -> Unit) {
val procedureBuilder = ProcedureBuilder()
procedureBuilder.block()
procedures.add(procedureBuilder.build())
}
fun build(): List<SurveyProcedure> = procedures
}
@QuestionnaireDsl
class ProcedureBuilder {
var sourceQuestionId: Long = 0L
var sourceOptionId: Long = 0L
var influencedQuestionId: Long = 0L
var influencedOptionId: Long = 0L
var computeMethod: ComputeMethod = ComputeMethod.BaseComputeMethod
fun build(): SurveyProcedure {
return SurveyProcedure.create(
sourceQuestionId = sourceQuestionId,
sourceOptionId = sourceOptionId,
influencedQuestionId = influencedQuestionId,
influencedOptionId = influencedOptionId,
computeMethod = computeMethod
)
}
}
@QuestionnaireDsl
class SampleBuilder {
lateinit var surveyRespondent: SurveyRespondent
private val backgroundList = mutableListOf<SurveyBackground>()
fun respondent(block: RespondentBuilder.() -> Unit) {
val respondentBuilder = RespondentBuilder()
respondentBuilder.block()
surveyRespondent = respondentBuilder.build()
}
fun background(block: BackgroundBuilder.() -> Unit) {
val backgroundBuilder = BackgroundBuilder()
backgroundBuilder.block()
backgroundList.add(backgroundBuilder.build())
}
fun build(): SurveyBackgroundSample {
return SurveyBackgroundSample(
surveyRespondent = surveyRespondent,
surveyBackgroundList = backgroundList
)
}
}
@QuestionnaireDsl
class RespondentBuilder {
var name: String = ""
var age: Int = 0
fun build(): SurveyRespondent {
return SurveyRespondent(name = name, age = age)
}
}
@QuestionnaireDsl
class BackgroundBuilder {
var title: String = ""
private val backgroundQuestions = mutableListOf<Question>()
fun question(block: QuestionBuilder.() -> Unit) {
val questionBuilder = QuestionBuilder()
questionBuilder.block()
backgroundQuestions.add(questionBuilder.build())
}
fun build(): SurveyBackground {
return SurveyBackground.create(
title = title,
backgroundQuestion = backgroundQuestions
)
}
}

View File

@ -1,323 +0,0 @@
package org.echo.questionnaire
import kotlin.math.abs
import kotlin.random.Random
class Questionnaire(
val id: Long,
val title: String, // 问卷的标题
val description: String, // 问卷的描述或说明
val questions: List<SurveyQuestion>,
val questionGroup: List<QuestionGroup>,
val sample: SurveyBackgroundSample, // 问卷中的所有问题
val procedures: List<SurveyProcedure>,
) {
private val selectedOptions: MutableMap<Long, MutableSet<Long>> = mutableMapOf()// Map of questionId to optionId
fun selectOption(questionId: Long, optionId: Long) {
val options = selectedOptions.getOrPut(questionId) { mutableSetOf() }
options.add(optionId)
onOptionSelected(questionId, optionId)
}
fun isOptionSelected(questionId: Long, optionId: Long): Boolean {
return selectedOptions[questionId]?.contains(optionId) == true
}
fun getSelectedOption(questionId: Long): Set<QuestionOption>? {
val optionIds = selectedOptions[questionId]
return optionIds?.let {
questions.map { it.question }
.find { it.id == questionId }
?.optionList
?.filter { it.id in optionIds }
?.toSet()
}
}
fun onOptionSelected(questionId: Long, optionId: Long) {
// Identify target questions influenced by the source question
val influencedQuestionIds = getInfluencedQuestionIdsInGroup(questionId)
for (targetQuestionId in influencedQuestionIds) {
addDependenciesAfterAnswer(this, questionId, optionId, targetQuestionId)
}
// Recalculate weights for all options that might be affected
// this.questions.map { it.question }.forEach { question ->
// question.optionList.forEach { option ->
// val weight = option.calculateWeightWithDependencies(this)
// // Use the weight as needed, e.g., adjust display order, filter out options, etc.
// }
// }
}
fun getInfluencedQuestionIdsInGroup(questionId: Long): Set<Long> {
return this.questionGroup.filter { it.checkIfInGroup(questionId) }
.flatMap { it.surveyQuestionList.map { it.question.id }.toSet() }
.toSet()
}
fun addDependenciesAfterAnswer(
questionnaire: Questionnaire,
sourceQuestionId: Long,
sourceOptionId: Long,
targetQuestionId: Long,
) {
val influencedOptionSet = questionnaire.getSelectedOption(sourceQuestionId)
val targetQuestion = questionnaire.questions.map { it.question }.find { it.id == targetQuestionId }
if (influencedOptionSet == null || targetQuestion == null) return
for (influencedOption in influencedOptionSet) {
targetQuestion.optionList.forEach { targetOption ->
// Remove existing dependencies from the same source question
val dependency = OptionDependency(
sourceQuestionId = sourceQuestionId,
sourceOptionId = sourceOptionId,
influencedOptionId = influencedOption.id,
targetOptionId = targetQuestion.id,
condition = { q ->
q.isOptionSelected(sourceQuestionId, sourceOptionId)
},
effect = { _ ->
// 使用枚举类的 calculateEffectBasedOnOrder 方法
val effectValue = ComputeMethod.BaseComputeMethod
.calculateEffectBasedOnOrder(influencedOption, targetOption)
// 根据计算结果进行相应处理
println("Effect calculated: $effectValue")
effectValue // 返回 effect 值,这个可以用于后续的逻辑
}
)
targetOption.addDependency(dependency)
}
}
}
fun calculateEffectBasedOnOrder(
influencedOption: QuestionOption,
targetOption: QuestionOption, //是他自己
): Double {
val orderDifference = abs(influencedOption.markOrder - targetOption.markOrder)
return when (orderDifference) {
0 -> 1.0
1 -> 0.7
2 -> 0.4
else -> 0.1
}
}
fun autoFillAnswers() {
for (group in questionGroup) {
group.surveyQuestionList
.map { it.question }
.forEach { question -> autoSelectAnswer(question) }
}
}
fun autoSelectAnswer(question: Question) {
val weightedOptions = question.optionList
.map { it.calculateAndMapToWeight(this) }
// 按照权重随机选择
val selectedOption = weightedRandomSelection(weightedOptions)
selectOption(question.id, selectedOption.optionId)
// 如果是多选题,随机选择两个选项(前提是至少有两个选项可选)
if (question.questionType == QuestionType.MULTIPLE_CHOICE &&
weightedOptions.size > 1
) {
val secondOption =
weightedRandomSelection(weightedOptions.filter { it.optionId != selectedOption.optionId })
selectOption(question.id, secondOption.optionId)
}
}
fun weightedRandomSelection(weightedOptions: List<QuestionOptionWeight>): QuestionOptionWeight {
val totalWeight = weightedOptions.sumOf { it.weight }
val randomValue = Random.nextDouble() * totalWeight
var cumulativeWeight = 0.0
for (option in weightedOptions) {
cumulativeWeight += option.weight
if (randomValue <= cumulativeWeight) {
return option
}
}
return weightedOptions.last()
}
}
open class Question(
val id: Long,
val title: String,
val content: String,
val optionList: List<QuestionOption>,
val questionType: QuestionType, // 表示问题类型:单选、多选、开放式等
val order: Int, // 表示问题顺序
val isRequired: Boolean = true, // 是否必答,默认为必答
) {
fun getOptionListData(): List<QuestionOption> {
return optionList
}
}
class QuestionGroup(
val id: Long,
val parentId: Long,
val groupName: String,
val surveyQuestionList: List<SurveyQuestion>,
var answeredQuestions: MutableMap<SurveyQuestion, QuestionOption> = mutableMapOf(), // 已回答的问题及其分数
) {
fun checkIfInGroup(questionId: Long): Boolean {
return surveyQuestionList.map { it.question }.any { it.id == questionId }
}
}
class QuestionOption(
val id: Long,
val label: String,
val content: String,
val mark: String,
val markOrder: Int,
val score: Int? = null, // 可选的分数字段
var dependencies: MutableList<OptionDependency> = mutableListOf(), // 与其他选项的依赖关系
) {
// 计算依赖关系对权重的影响
fun calculateWeightWithDependencies(questionnaire: Questionnaire): Double {
var baseWeight = 1.0
dependencies.forEach { dependency ->
if (dependency.condition(questionnaire)) {
baseWeight += dependency.effect(questionnaire)
}
}
return baseWeight.coerceAtLeast(0.0)
}
fun calculateAndMapToWeight(questionnaire: Questionnaire): QuestionOptionWeight {
return QuestionOptionWeight(id, calculateWeightWithDependencies(questionnaire))
}
fun addDependency(dependency: OptionDependency) {
//添加选项依赖,添加前会删除所有之前 问题的产生的依赖项,然后再进行添加
val sourceQuestionId = dependency.sourceQuestionId
this.dependencies.removeAll { it.sourceQuestionId == sourceQuestionId }
this.dependencies.add(dependency)
}
}
class QuestionOptionWeight(
val optionId: Long,
val weight: Double,
)
class OptionDependency(
val sourceQuestionId: Long,
val sourceOptionId: Long,
val influencedOptionId: Long,
val targetOptionId: Long,
val condition: (Questionnaire) -> Boolean,
val effect: (Questionnaire) -> Double,
)
enum class QuestionType {
SINGLE_CHOICE, // 单选
MULTIPLE_CHOICE, // 多选
OPEN_ENDED // 开放式问题
}
class SurveyQuestion(
val id: Long,
val title: String,
val content: String,
val questionId: Long,
val question: Question,
) {
fun getQuestionData(): Question {
return this.question
}
}
class SurveyBackgroundSample(
val surveyRespondent: SurveyRespondent,
val surveyBackgroundList: List<SurveyBackground>,
)
class SurveyRespondent(
val name: String,
val age: Int,
)
class SurveyBackground(
val title: String,
val backgroundQuestion: Question,
)
class SurveyProcedure(
val id: Long,
val sourceQuestionId: Long,
val sourceOptionId: Long,
val influencedQuestionId: Long,
val influencedOptionId: Long,
val computeMethod: ComputeMethod = ComputeMethod.BaseComputeMethod,
) {
fun ifProcess(sourceOptionalId: Long, questionnaire: Questionnaire) {
if (sourceOptionalId == sourceOptionId) generateOptionDependency(questionnaire)
}
fun generateOptionDependency(questionnaire: Questionnaire) {
val influencedQuestionOptions = questionnaire.questions
.filter { it.questionId == influencedQuestionId }
.map { it.question }
.flatMap { it.optionList }
val influencedOption = influencedQuestionOptions
.filter { option -> option.id == influencedOptionId }
.last()
influencedQuestionOptions.forEach {
//遍历所有的选项,向其中加入选项依赖
val optionDependency = OptionDependency(
sourceOptionId, sourceOptionId,
influencedOption.id,
it.id,
condition = { q ->
q.isOptionSelected(sourceQuestionId, sourceOptionId)
},
effect = { _ ->
// Define your effect based on source and target options
computeMethod.calculateEffectBasedOnOrder(influencedOption, it)
}
)
it.addDependency(optionDependency)
}
}
}
enum class ComputeMethod(
val computeMethodName: String,
) {
BaseComputeMethod("Default") {
override fun calculateEffectBasedOnOrder(
influencedOption: QuestionOption,
targetOption: QuestionOption,
): Double {
val orderDifference = abs(influencedOption.markOrder - targetOption.markOrder)
return when (orderDifference) {
0 -> 1.0
1 -> 0.7
2 -> 0.4
else -> 0.1
}
}
};
// 定义抽象方法,子类必须实现
abstract fun calculateEffectBasedOnOrder(
sourceOption: QuestionOption,
targetOption: QuestionOption,
): Double
}

View File

@ -0,0 +1,24 @@
server:
port: 8083
echo:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
host: 47.97.21.20
port: 3306
database: questionnaire
username: root
password: kerowsqw34
redis:
host: 10.10.103.131
port: 6379
password: Worktask@Redis2023
database: 8
logging:
level:
org.springframework.jdbc.core.JdbcTemplate: info
org.springframework.jdbc.core.StatementCreatorUtils: info
root: info
org.springframework.cache: info

View File

@ -0,0 +1,26 @@
spring:
profiles:
active: test
datasource:
driver-class-name: ${echo.datasource.driver-class-name}
url: jdbc:mysql://${echo.datasource.host}:${echo.datasource.port}/${echo.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: ${echo.datasource.username}
password: ${echo.datasource.password}
hikari:
minimum-idle: 20
maximum-pool-size: 60 # 连接池最大连接数
idle-timeout: 300000 # 空闲连接超时时间(毫秒)
max-lifetime: 1800000 # 连接的最长生命周期(毫秒)
connection-timeout: 60000 # 连接超时时间(毫秒)
pool-name: 'SpringBootHikariCP' # 连接池名字
leak-detection-threshold: 30000 # 连接泄露检测阈值(毫秒)
data:
redis:
host: ${echo.redis.host}
port: ${echo.redis.port}
password: ${echo.redis.password}
database: ${echo.redis.database}
aop:
auto: true
proxy-target-class: true

View File

@ -0,0 +1,92 @@
package org.echo
import org.echo.org.echo.questionnaire.ComputeMethod
import org.echo.org.echo.questionnaire.QuestionType
import org.echo.org.echo.questionnaire.Questionnaire
import org.echo.org.echo.questionnaire.Questionnaire.Companion.questionnaire
import org.echo.org.echo.questionnaire.QuestionnaireRepository
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest
class QuestionnaireApplicationTests {
@Autowired
private lateinit var questionnaireRepository: QuestionnaireRepository
@Test
fun contextLoads() {
var exampleQuestionnaire = exampleQuestionnaire()
questionnaireRepository.save(exampleQuestionnaire)
}
@Test
fun findQuestionnaire(){
var findById = questionnaireRepository.findById(16L)
println(findById)
}
}
fun exampleQuestionnaire(): Questionnaire {
return questionnaire {
title = "客户满意度调查"
description = "一份用于测量客户满意度的调查问卷。"
background {
title = "背景信息"
question {
title = "您的年龄是多少?"
content = "请选择您的年龄范围。"
questionType = QuestionType.SINGLE_CHOICE
options {
option {
label = "18-25"
content = "18-25 岁"
mark = "A"
markOrder = 1
}
option {
label = "26-35"
content = "26-35 岁"
mark = "B"
markOrder = 2
}
// 更多选项...
}
}
}
questionGroup {
groupName = "服务满意度"
surveyQuestion {
title = "您对我们的服务满意吗?"
content = "请选择一个选项。"
question {
title = "您对我们的服务满意吗?"
content = "请选择一个选项。"
questionType = QuestionType.SINGLE_CHOICE
options {
option {
label = "非常满意"
content = "非常满意"
mark = "A"
markOrder = 1
}
option {
label = "满意"
content = "满意"
mark = "B"
markOrder = 2
}
// 更多选项...
}
}
}
}
}
}