diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index ce1c62c..3a0665a 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -12,5 +12,6 @@
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 5815a4a..d4b7acc 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index f937d01..13e4c05 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,9 +1,10 @@
+
-
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 7dfacb7..8a469d0 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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 平台
+}
\ No newline at end of file
diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt
deleted file mode 100644
index eebb4e4..0000000
--- a/src/main/kotlin/Main.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.echo
-
-fun main() {
- println("Hello World!")
-}
\ No newline at end of file
diff --git a/src/main/kotlin/org/echo/questionnaire/Questionnaire.kt b/src/main/kotlin/org/echo/questionnaire/Questionnaire.kt
new file mode 100644
index 0000000..f19bb10
--- /dev/null
+++ b/src/main/kotlin/org/echo/questionnaire/Questionnaire.kt
@@ -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,
+ @MappedCollection(idColumn = "questionnaire_id", keyColumn = "questionnaire_key")
+ val questionGroup: List,
+ @MappedCollection(idColumn = "questionnaire_id")
+ val surveyBackground: SurveyBackground, // 问卷中的所有问题
+ @MappedCollection(idColumn = "questionnaire_id", keyColumn = "questionnaire_key")
+ val procedures: List,
+) {
+ @Transient
+ private val selectedOptions: MutableMap> = mutableMapOf()// Map of questionId to optionId
+
+ companion object {
+ fun create(
+ title: String,
+ description: String,
+ questionGroup: List,
+ questions: List,
+ surveyBackground: SurveyBackground,
+ procedures: List
+ ): 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? {
+ 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 {
+ 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 {
+ 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,
+ val questionType: QuestionType, // 表示问题类型:单选、多选、开放式等
+ val order: Int, // 表示问题顺序
+ val isRequired: Int = 1, // 是否必答,默认为必答
+) {
+
+ companion object {
+ fun create(
+ title: String,
+ content: String,
+ optionList: List,
+ 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 {
+ 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
+) {
+ @Transient
+ private val answeredQuestions: MutableMap = mutableMapOf()
+
+ companion object {
+ fun create(
+ parentId: Long?,
+ groupName: String,
+ surveyQuestionList: List
+ ): 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 = 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,
+)
+
+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,
+) {
+ companion object {
+ fun create(
+ title: String,
+ backgroundQuestion: List
+ ): 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
+}
+
+@Repository
+class QuestionnaireRepositoryImpl(private val jdbcQuestionnaireRepository: JdbcQuestionnaireRepository) :
+ QuestionnaireRepository {
+ override fun save(questionnaire: Questionnaire): Questionnaire {
+ return jdbcQuestionnaireRepository.save(questionnaire)
+ }
+
+ override fun findById(id: Long): Optional {
+ return jdbcQuestionnaireRepository.findById(id)
+ }
+}
+
+@Repository
+interface JdbcQuestionnaireRepository : CrudRepository
+
+@DslMarker
+annotation class QuestionnaireDsl
+
+@QuestionnaireDsl
+class QuestionnaireBuilder {
+ var title: String = ""
+ var description: String = ""
+ private val questions = mutableListOf()
+ private val questionGroups = mutableListOf()
+ private val procedures = mutableListOf()
+ 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()
+
+ 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()
+
+ 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()
+
+ fun option(block: OptionBuilder.() -> Unit) {
+ val optionBuilder = OptionBuilder()
+ optionBuilder.block()
+ options.add(optionBuilder.build())
+ }
+
+ fun build(): List = 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()
+
+ fun procedure(block: ProcedureBuilder.() -> Unit) {
+ val procedureBuilder = ProcedureBuilder()
+ procedureBuilder.block()
+ procedures.add(procedureBuilder.build())
+ }
+
+ fun build(): List = 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()
+
+ 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()
+
+ fun question(block: QuestionBuilder.() -> Unit) {
+ val questionBuilder = QuestionBuilder()
+ questionBuilder.block()
+ backgroundQuestions.add(questionBuilder.build())
+ }
+
+ fun build(): SurveyBackground {
+ return SurveyBackground.create(
+ title = title,
+ backgroundQuestion = backgroundQuestions
+ )
+ }
+}
diff --git a/src/main/kotlin/questionnaire/Questionnaire.kt b/src/main/kotlin/questionnaire/Questionnaire.kt
deleted file mode 100644
index 7435450..0000000
--- a/src/main/kotlin/questionnaire/Questionnaire.kt
+++ /dev/null
@@ -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,
- val questionGroup: List,
- val sample: SurveyBackgroundSample, // 问卷中的所有问题
- val procedures: List,
-) {
- private val selectedOptions: MutableMap> = 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? {
- 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 {
- 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 {
- 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,
- val questionType: QuestionType, // 表示问题类型:单选、多选、开放式等
- val order: Int, // 表示问题顺序
- val isRequired: Boolean = true, // 是否必答,默认为必答
-) {
- fun getOptionListData(): List {
- return optionList
- }
-
-
-}
-
-class QuestionGroup(
- val id: Long,
- val parentId: Long,
- val groupName: String,
- val surveyQuestionList: List,
- var answeredQuestions: MutableMap = 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 = 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,
-)
-
-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
-}
\ No newline at end of file
diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml
new file mode 100644
index 0000000..2c58463
--- /dev/null
+++ b/src/main/resources/application-test.yml
@@ -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
+
+
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..9ff45d5
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -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
+
diff --git a/src/test/kotlin/org/echo/QuestionnaireApplicationTests.kt b/src/test/kotlin/org/echo/QuestionnaireApplicationTests.kt
new file mode 100644
index 0000000..878d54f
--- /dev/null
+++ b/src/test/kotlin/org/echo/QuestionnaireApplicationTests.kt
@@ -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
+ }
+ // 更多选项...
+ }
+ }
+ }
+
+ }
+ }
+
+}
\ No newline at end of file