本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目是一项面向心理健康领域的移动应用类毕业设计,旨在将广泛使用的SCL-90症状自评量表数字化,开发一款可在Android或iOS平台运行的心理健康测试App。该应用通过标准化问卷采集用户心理状态数据,支持自动评分与结果分析,生成可视化报告,帮助用户进行自我心理评估。项目采用现代软件工程实践,包含前端界面、后端服务、数据库管理及API接口设计,使用Maven构建工具与Git版本控制,并配备完整文档与开源许可证,具备良好的可扩展性与实用性。
毕业设计-基于SCL90的心理健康测试app

1. SCL-90量表原理与心理健康评估机制

SCL-90的理论基础与心理测量学结构

SCL-90通过九个症状维度(如抑郁、焦虑、敌对等)构建多维心理评估体系,每个维度由若干条目组成,采用李克特五点计分法(0–4分)量化个体主观不适程度。其设计基于精神病理学因子分析研究,确保各因子间具有良好的区分效度。总量表得分反映整体心理负担,因子分用于识别特定症状群,阳性项目数则体现症状广度,三者结合实现动态、立体的心理状态刻画。

信度与效度保障机制解析

为确保测评稳定性,SCL-90在多数群体中表现出高内部一致性(Cronbach’s α > 0.9),并通过重测信度验证时间稳定性。结构效度经探索性因子分析(EFA)和验证性因子分析(CFA)确认九因子模型拟合良好(CFI > 0.9, RMSEA < 0.08),支持其理论构念有效性。这些心理测量学指标为数字化评估系统提供了科学锚点,确保技术实现不偏离临床标准。

2. 移动端心理健康App需求分析与系统设计

随着移动互联网技术的迅猛发展,心理健康服务正逐步从传统的线下咨询模式向数字化、自助化、轻量化方向演进。尤其在大学生与职场人群心理压力日益加剧的背景下,一款具备专业性、隐私保护机制和良好用户体验的心理健康评估 App 成为现实刚需。本章围绕 SCL-90 量表为核心的心理测评功能,系统性地展开移动端应用的需求建模与架构设计。通过深入剖析用户角色、使用场景及核心功能模块,结合现代软件工程中的前后端分离思想与跨平台开发策略,构建一个高可用、可扩展且符合法规要求的心理健康服务平台。

2.1 心理健康App的功能需求建模

功能需求建模是系统设计的第一步,其目标是从用户视角出发,识别关键行为路径,并将其转化为可实现的技术组件。对于心理健康类 App 而言,不仅要满足基本的功能完整性,还需兼顾情感交互、数据安全与心理干预伦理等特殊考量。以下将从用户角色划分入手,逐步拆解核心使用场景与功能模块。

2.1.1 用户角色划分与使用场景分析

在设计心理健康 App 时,必须明确目标用户群体及其典型行为模式。不同用户对心理测评的认知水平、使用动机和技术能力存在显著差异,因此需进行精细化的角色建模。

用户类型 特征描述 使用动机 技术素养
大学生 年龄集中在18–24岁,面临学业、人际关系、未来规划等多重压力 自我觉察、缓解焦虑、了解心理状态 中等偏上,熟悉智能手机操作
职场人群 工作节奏快,长期处于高压环境,可能存在职业倦怠 快速筛查情绪问题,辅助决策是否寻求专业帮助 高,偏好高效简洁工具
心理咨询初筛者 正考虑或已预约心理咨询,希望提前掌握自身状况 获取权威参考依据,提升咨询效率 不一,部分人对心理测评缺乏信任
核心使用场景定义

基于上述用户画像,提取三大高频使用场景:

  1. 自我评估场景
    用户在感到情绪低落、失眠或多思时,主动打开 App 完成一次 SCL-90 测评。该过程强调界面引导清晰、答题流畅、耗时控制在15分钟以内,避免因冗长导致中途放弃。

  2. 趋势追踪场景
    用户定期(如每月一次)重复测评,系统自动绘制“焦虑”、“抑郁”等维度的变化曲线。此场景下需要支持历史记录对比、异常波动提醒等功能,增强用户的持续参与感。

  3. 报告分享场景
    用户可将生成的心理测评报告导出为 PDF 或图片格式,用于与心理咨询师沟通,或仅作个人存档。在此过程中,必须提供匿名化选项,确保敏感信息不被泄露。

这些场景共同构成了 App 的主流程闭环: 进入 → 测评 → 计算 → 展示 → 存储/分享 。每一个环节都应围绕“降低心理防御”这一核心原则进行交互设计。

graph TD
    A[用户启动App] --> B{是否首次使用?}
    B -- 是 --> C[新手引导 + 知情同意书]
    B -- 否 --> D[主页菜单]
    D --> E[开始SCL-90测评]
    E --> F[逐题作答界面]
    F --> G[提交答案]
    G --> H[后台计算因子分]
    H --> I[生成可视化报告]
    I --> J[保存本地 & 提供分享选项]

图:心理健康App核心使用流程图(Mermaid格式)

该流程体现了以用户为中心的设计逻辑,其中“知情同意书”环节尤为重要——它不仅是法律合规的要求,更是建立用户信任的关键节点。系统应在用户首次进入时明确告知数据用途、存储方式及删除权限,赋予其充分的控制权。

此外,在情绪敏感场景中,任何强制注册、频繁弹窗或广告推送都会引发负面体验。因此,整个流程应尽可能保持无干扰、低压迫感,采用柔和色调、留白布局与正向语言鼓励完成测评。

2.1.2 功能模块拆解

为了支撑上述使用场景,需将整体功能划分为四个核心模块:测评模块、计算模块、报告模块与隐私模块。每个模块承担特定职责,彼此松耦合,便于后期维护与迭代。

功能模块结构表
模块名称 主要职责 关键特性 技术实现要点
测评模块 呈现SCL-90题目并收集用户响应 分页加载、防误触、进度提示 RecyclerView/Kotlin Flow管理状态
计算模块 解析原始数据并计算各因子得分 实时反馈、临界值预警 Kotlin函数库封装评分规则
报告模块 可视化展示结果并生成文字建议 图表渲染、个性化文案、PDF导出 MPAndroidChart + iText
隐私模块 保障用户数据安全与合规性 AES加密、HTTPS传输、生物识别解锁 SQLCipher + Android Keystore
测评模块:SCL-90题目呈现与交互设计

SCL-90 共包含90道题目,每题采用李克特五点计分法(0–4分),涵盖九个症状维度:躯体化、强迫症状、人际关系敏感、抑郁、焦虑、敌对、恐怖、偏执、精神病性。前端需实现动态题目录入,支持滑动翻页、单选按钮选择、实时保存当前进度。

data class Scl90Item(
    val id: Int,
    val content: String, // 题目文本
    var selectedScore: Int? = null // 用户选择的分数
)

// ViewModel中管理答题状态
class AssessmentViewModel : ViewModel() {
    private val _questions = MutableLiveData<List<Scl90Item>>()
    val questions: LiveData<List<Scl90Item>> = _questions

    fun loadQuestions() {
        val items = (1..90).map { id ->
            Scl90Item(id, getQuestionTextById(id))
        }
        _questions.value = items
    }

    fun updateAnswer(questionId: Int, score: Int) {
        val updated = _questions.value?.map { item ->
            if (item.id == questionId) item.copy(selectedScore = score) else item
        }
        _questions.value = updated
    }
}

代码逻辑逐行解析
- Scl90Item 数据类封装每道题的信息,包括ID、内容和用户选择的分数。
- AssessmentViewModel 使用 LiveData 实现 MVVM 模式,保证 UI 与数据分离。
- loadQuestions() 初始化所有题目,实际项目中可从资源文件或远程 API 加载。
- updateAnswer() 提供更新接口,响应用户点击事件,自动刷新 UI。

该设计支持横屏适配、中断恢复(如来电后返回继续答题),并通过 SharedPreferences 或数据库持久化中间状态,防止数据丢失。

计算模块:因子分自动统计与异常值预警

SCL-90 的评分规则并非简单求和,而是按预设的因子映射表对题目归类后计算均值。例如,“抑郁”因子包含16道题,其因子分为这16题得分的平均值。若某因子分 ≥ 2,则判定为“阳性”,提示可能存在临床意义的症状。

object Scl90Calculator {
    private val DEPRESSION_ITEMS = listOf(5, 14, 15, 20, 22, 27, 29, 30, 31, 32, 33, 36, 37, 38, 39, 87)
    fun calculateDepressionScore(answers: List<Scl90Item>): Double {
        val relevantAnswers = answers.filter { it.id in DEPRESSION_ITEMS && it.selectedScore != null }
        return if (relevantAnswers.isEmpty()) 0.0
        else relevantAnswers.map { it.selectedScore!! }.average()
    }

    fun isPositive(factorScore: Double): Boolean = factorScore >= 2.0
}

参数说明与扩展性分析
- answers : 所有题目的答题列表,需确保完整性。
- DEPRESSION_ITEMS : 静态常量,表示属于“抑郁”维度的题目编号。
- calculateDepressionScore : 返回双精度浮点数,保留两位小数用于显示。
- isPositive : 判断是否达到警戒线,可用于触发UI警示动画(如红色边框闪烁)。

此模块应独立打包为纯 Kotlin 库,便于 Android、iOS(通过 KMM)及后端复用,避免多端重复实现带来的误差风险。

报告模块:可视化结果展示与文字建议生成

测评完成后,系统需将抽象数字转化为直观图表与易懂语言。推荐使用柱状图对比各因子分,辅以雷达图展现整体心理轮廓。

// 示例:使用MPAndroidChart绘制因子分柱状图
BarChart barChart = findViewById(R.id.bar_chart);
List<BarEntry> entries = new ArrayList<>();
entries.add(new BarEntry(0f, (float) result.getAnxietyScore()));
entries.add(new BarEntry(1f, (float) result.getDepressionScore()));
// ... 添加其他维度

BarDataSet dataSet = new BarDataSet(entries, "SCL-90因子分");
dataSet.setColor(Color.RED);
BarData barData = new BarData(dataSet);
barChart.setData(barData);
barChart.getDescription().setText("心理症状维度得分");
barChart.invalidate(); // 刷新图表

执行逻辑说明
- BarEntry 表示每个维度的坐标点,X轴为索引,Y轴为得分。
- BarDataSet 封装数据集并设置颜色样式。
- BarData 是最终绑定到图表的数据结构。
- invalidate() 触发重绘,确保UI即时更新。

同时,系统可根据得分区间生成个性化建议,如:
- 若抑郁因子≥2:建议“近期情绪波动较大,可尝试正念练习,必要时联系专业心理咨询。”
- 若所有因子均<1:提示“当前心理状态良好,请继续保持规律作息。”

此类文案应由心理学专家审核,避免误导或过度医疗化表述。

隐私模块:数据本地加密与匿名化处理机制

心理数据属于高度敏感个人信息,必须遵循“最小化采集、本地优先存储、加密传输”原则。系统默认不上传任何数据至服务器,除非用户主动选择同步备份。

关键技术措施包括:
- 使用 AES-256 对本地数据库(Room)加密,密钥由系统 KeyStore 管理;
- 所有网络请求通过 HTTPS + TLS 1.3 协议加密;
- 支持指纹/面容识别解锁 App,防止他人窥探;
- 导出报告时提供“去除姓名、手机号”等匿名化选项。

<!-- AndroidManifest.xml 中声明硬件支持 -->
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.BIOMETRIC" />

结合 BiometricPrompt API,可在进入报告页面前验证身份,进一步提升安全性。

综上所述,四大功能模块协同工作,形成完整的心理测评闭环。下一节将进一步探讨系统的整体架构设计与技术选型策略。

3. 基于Java/Kotlin/Swift的跨平台App开发实现

随着移动健康(mHealth)应用的快速发展,心理健康类App正从单一功能工具向集成化、智能化服务系统演进。在这一背景下,如何高效构建一个具备专业心理评估能力、跨平台兼容性高且用户体验优良的心理测评应用,成为开发者面临的核心挑战。本章聚焦于技术落地层面,深入探讨使用 Kotlin (Android)、 Swift (iOS)以及 Kotlin Multiplatform Mobile(KMM) 构建 SCL-90 心理测评 App 的关键技术路径与工程实践。通过剖析各端核心模块的编码逻辑、状态管理机制与共享业务层设计,揭示现代移动端开发中“一次编写,多端运行”理念的实际可行性,并为后续持续集成与数据库闭环提供稳定的技术支撑。

跨平台开发并非简单的 UI 复制粘贴,而是涉及数据流控制、本地持久化策略、异步任务调度和平台特性适配等多个维度的复杂系统工程。尤其在心理健康领域,用户对界面响应速度、隐私保护强度及结果准确性要求极高,因此必须在架构设计之初就确立清晰的分层结构与职责边界。本章将以 SCL-90 测评流程为主线,分别展示 Android 与 iOS 端的关键实现细节,并重点介绍如何通过抽象公共逻辑提升代码复用率,降低维护成本。

3.1 Android端核心功能编码实践(Kotlin)

Android 平台作为全球占有率最高的移动操作系统,其生态成熟度和技术文档完备性为心理健康类 App 提供了理想的部署环境。在 Kotlin 成为官方首选语言后,函数式编程特性、空安全机制与协程支持显著提升了开发效率与代码健壮性。针对 SCL-90 问卷交互性强、状态频繁变更的特点,采用 Jetpack 组件库中的 ViewModel + LiveData + Repository 模式进行解耦设计,确保即使在配置变化(如屏幕旋转)时答题进度也不会丢失。

3.1.1 SCL-90问卷页面构建

SCL-90 共包含 90 道题目,每题采用李克特五级评分法(0–4 分),涵盖九个症状因子:躯体化、强迫症状、人际关系敏感、抑郁、焦虑、敌对、恐怖、偏执、精神病性。为了实现流畅的滚动体验并支持动态选项更新,使用 RecyclerView 进行列表渲染是最优选择。

布局结构设计与适配器实现
<!-- item_question.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/tv_question_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textColor="#333" />

    <RadioGroup
        android:id="@+id/radio_group"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center">

        <RadioButton android:id="@+id/rb_0" android:text="无" />
        <RadioButton android:id="@+id/rb_1" android:text="轻度" />
        <RadioButton android:id="@+id/rb_2" android:text="中度" />
        <RadioButton android:id="@+id/rb_3" android:text="偏重" />
        <RadioButton android:id="@+id/rb_4" android:text="严重" />
    </RadioGroup>
</LinearLayout>

该布局定义了每个问题项的基本 UI 结构:题干文本 + 五个等级的单选按钮组。通过 RadioGroup 实现互斥选择,避免用户误操作导致多个选项同时选中。

Kotlin 适配器代码实现
class Scl90Adapter(
    private val questions: List<String>,
    private val answers: MutableMap<Int, Int>
) : RecyclerView.Adapter<Scl90Adapter.QuestionViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuestionViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_question, parent, false)
        return QuestionViewHolder(view)
    }

    override fun onBindViewHolder(holder: QuestionViewHolder, position: Int) {
        holder.bind(questions[position], position, answers[position])
    }

    override fun getItemCount() = questions.size

    inner class QuestionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val tvQuestion = itemView.findViewById<TextView>(R.id.tv_question_text)
        private val radioGroup = itemView.findViewById<RadioGroup>(R.id.radio_group)

        fun bind(question: String, position: Int, selectedAnswer: Int?) {
            tvQuestion.text = "${position + 1}. $question"

            // 设置默认选中状态
            when (selectedAnswer) {
                0 -> radioGroup.check(R.id.rb_0)
                1 -> radioGroup.check(R.id.rb_1)
                2 -> radioGroup.check(R.id.rb_2)
                3 -> radioGroup.check(R.id.rb_3)
                4 -> radioGroup.check(R.id.rb_4)
                else -> radioGroup.clearCheck()
            }

            // 监听选择事件
            radioGroup.setOnCheckedChangeListener { _, checkedId ->
                val answer = when (checkedId) {
                    R.id.rb_0 -> 0
                    R.id.rb_1 -> 1
                    R.id.rb_2 -> 2
                    R.id.rb_3 -> 3
                    R.id.rb_4 -> 4
                    else -> null
                }
                if (answer != null) {
                    answers[position] = answer
                }
            }
        }
    }
}
逻辑分析与参数说明:
  • questions : 题目字符串列表,共90条,来源于资源文件或远程加载。
  • answers : 使用 MutableMap<Int, Int> 存储当前用户的作答状态,键为题号索引(0~89),值为0~4之间的分数。
  • onBindViewHolder : 在每次绑定视图时同步题目内容与当前选择状态,保证数据一致性。
  • setOnCheckedChangeListener : 实时捕获用户输入,自动更新内存中的答案映射表,无需额外提交按钮即可保存中间状态。

⚠️ 注意:此方案将答案暂存于内存中,若需防崩溃恢复,应结合 ViewModel 持久化至 SavedStateHandle 或本地数据库。

ViewModel 模式管理答题状态
class Scl90ViewModel : ViewModel() {
    private val _answers = mutableStateOf(mutableMapOf<Int, Int>())
    val answers: State<Map<Int, Int>> get() = _answers

    fun saveAnswer(questionIndex: Int, score: Int) {
        _answers.value[questionIndex] = score
    }

    fun getAllAnswers(): Map<Int, Int> = _answers.value.toMap()
}

借助 ViewModel ,即使 Activity 因配置更改重建,答题数据仍可完整保留,极大提升了用户体验。

3.1.2 测评结果计算引擎开发

完成所有题目作答后,需根据预设的因子结构对原始得分进行聚合运算,生成标准化的心理健康报告。SCL-90 的因子分计算公式如下:

\text{因子分} = \frac{\sum_{i \in \text{该因子题目}} \text{得分}_i}{\text{该因子包含题目数}}

不同因子对应的具体题目编号已有国际标准划分(见下表):

因子名称 包含题目编号(示例) 题目数量
躯体化 1, 4, 12, 27, 40, 49, 52, 53, 56, 58 12
强迫症状 3, 9, 10, 28, 38, 45, 46, 51, 55, 65 10
人际关系敏感 6, 21, 34, 36, 37, 41, 61, 69, 73 9
抑郁 5, 14, 15, 20, 22, 26, 29, 30, 31, 32, 54, 71, 79 13
焦虑 2, 17, 23, 33, 39, 57, 72, 78, 82 9
敌对 11, 24, 63, 67, 74, 80 6
恐怖 13, 25, 43, 47, 50, 70, 75, 87 8
偏执 8, 18, 44, 68, 76, 81, 83 7
精神病性 7, 16, 35, 62, 77, 84, 85, 88, 90 9
Kotlin 计算引擎封装
object Scl90Calculator {

    private val factorMapping = mapOf(
        "somatization" to listOf(1,4,12,27,40,49,52,53,56,58),
        "obsession" to listOf(3,9,10,28,38,45,46,51,55,65),
        "interpersonal_sensitivity" to listOf(6,21,34,36,37,41,61,69,73),
        "depression" to listOf(5,14,15,20,22,26,29,30,31,32,54,71,79),
        "anxiety" to listOf(2,17,23,33,39,57,72,78,82),
        "hostility" to listOf(11,24,63,67,74,80),
        "phobic_anxiety" to listOf(13,25,43,47,50,70,75,87),
        "paranoid_ideation" to listOf(8,18,44,68,76,81,83),
        "psychoticism" to listOf(7,16,35,62,77,84,85,88,90)
    )

    data class Result(
        val somatization: Double,
        val obsession: Double,
        val interpersonalSensitivity: Double,
        val depression: Double,
        val anxiety: Double,
        val hostility: Double,
        val phobicAnxiety: Double,
        val paranoidIdeation: Double,
        val psychoticism: Double,
        val totalScore: Int,
        val positiveItemCount: Int
    )

    fun calculate(answers: Map<Int, Int>): Result {
        var totalScore = 0
        var positiveCount = 0

        val scores = mutableMapOf<String, Double>()

        for ((factor, indices) in factorMapping) {
            val validIndices = indices.map { it - 1 } // 转换为0基索引
            val sum = validIndices.sumOf { index ->
                answers.getOrDefault(index, 0).also { if (it > 0) positiveCount++ }
            }
            totalScore += sum
            scores[factor] = String.format("%.2f", sum.toDouble() / indices.size).toDouble()
        }

        return Result(
            somatization = scores["somatization"]!!,
            obsession = scores["obsession"]!!,
            interpersonalSensitivity = scores["interpersonal_sensitivity"]!!,
            depression = scores["depression"]!!,
            anxiety = scores["anxiety"]!!,
            hostility = scores["hostility"]!!,
            phobicAnxiety = scores["phobic_anxiety"]!!,
            paranoidIdeation = scores["paranoid_ideation"]!!,
            psychoticism = scores["psychoticism"]!!,
            totalScore = totalScore,
            positiveItemCount = positiveCount
        )
    }
}
参数说明与逻辑解读:
  • factorMapping : 定义每个因子对应的原始题号集合(注意减1转为数组索引)。
  • calculate() 方法接收用户答案映射,逐项累加并计算均值。
  • positiveItemCount : 统计得分为1~4的题目数量,反映阳性症状广度。
  • 返回 Result 数据类,便于后续传递至 UI 层或上传服务器。
实时反馈机制:颜色警示动画

当某因子分超过临界值(通常为2.0)时,触发视觉预警。可在 TextView 上添加颜色渐变动画:

fun bindFactorScore(factorName: String, score: Double, textView: TextView) {
    textView.text = "$factorName: ${"%.2f".format(score)}"
    if (score >= 2.0) {
        ValueAnimator.ofArgb(Color.BLACK, Color.RED).apply {
            duration = 800
            addUpdateListener { animator ->
                textView.setTextColor(animator.animatedValue as Int)
            }
            repeatCount = 1
            start()
        }
    } else {
        textView.setTextColor(Color.BLACK)
    }
}

📊 此机制利用 Android 动画系统增强反馈感知力,在不打扰用户前提下突出潜在心理风险点。

3.2 iOS端适配与Swift实现策略

尽管 Android 占据市场主导地位,但 iOS 用户群体普遍具有更高的隐私意识与付费意愿,是心理健康类产品不可忽视的重要阵地。Swift 语言凭借其现代化语法、类型安全机制与强大的编译优化能力,已成为 Apple 生态开发的核心工具。本节将阐述如何使用 SwiftUI 构建与 Android 一致的用户体验,并通过 Core Data 实现本地测评记录存储。

3.2.1 使用SwiftUI构建一致性UI组件

SwiftUI 是 Apple 推出的声明式 UI 框架,允许以更简洁的方式描述界面结构与行为逻辑。以下是 SCL-90 题目录入页的核心实现:

import SwiftUI

struct Scl90QuestionView: View {
    let question: String
    let index: Int
    @Binding var answers: [Int: Int]

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("\(index + 1). \(question)")
                .font(.body)

            HStack(spacing: 16) {
                ForEach(0..<5) { value in
                    Button(action: {
                        answers[index] = value
                    }) {
                        Text(label(for: value))
                            .frame(minWidth: 60)
                    }
                    .buttonStyle(AnswerButtonStyle(selected: answers[index] == value))
                }
            }
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(8)
    }

    private func label(for value: Int) -> String {
        switch value {
        case 0: return "无"
        case 1: return "轻度"
        case 2: return "中度"
        case 3: return "偏重"
        case 4: return "严重"
        default: return ""
        }
    }
}

struct AnswerButtonStyle: ButtonStyle {
    let selected: Bool

    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .padding(8)
            .background(selected ? Color.blue : Color.white)
            .foregroundColor(selected ? .white : .primary)
            .border(Color.gray.opacity(0.3), width: 1)
            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)
    }
}
逻辑分析:
  • @Binding var answers : 实现父子组件间双向绑定,确保父级能接收到子组件的选择变更。
  • ForEach : 动态生成五个选项按钮,避免重复代码。
  • AnswerButtonStyle : 自定义按钮样式,通过颜色与缩放反馈用户点击状态,提升交互质感。
表格化布局与手势优化

对于长列表场景,使用 List 替代传统 UITableView 可获得更好的性能与滚动流畅性:

struct Scl90FormView: View {
    @State private var answers: [Int: Int] = [:]
    private let questions = loadQuestions() // 加载90道题目

    var body: some View {
        NavigationView {
            List(0..<questions.count, id: \.self) { index in
                Scl90QuestionView(
                    question: questions[index],
                    index: index,
                    answers: $answers
                )
            }
            .navigationTitle("SCL-90 测评")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("提交") {
                        let result = Scl90Calculator.calculate(answers: answers)
                        print("总分: \(result.totalScore)")
                    }
                }
            }
        }
    }
}

✅ 利用 SwiftUI 的惰性加载机制,仅渲染可视区域内的项目,显著减少内存占用。

3.2.2 Core Data本地存储心理测评记录

为保障用户数据离线可用性与隐私安全,采用 Apple 原生的 Core Data 框架进行本地持久化。

实体模型设计(Core Data Model)
erDiagram
    USER_TEST_RECORD ||--o{ RESULT_SNAPSHOT : has
    USER_TEST_RECORD {
        string uuid
        date createdAt
        boolean isSynced
    }
    RESULT_SNAPSHOT {
        double somatization
        double obsession
        double depression
        double anxiety
        int totalScore
        int positiveItemCount
        string rawAnswersJson
    }

上述 ER 图展示了两个核心实体的关系:每次测评生成一条 UserTestRecord ,关联一个 ResultSnapshot 存储详细得分。

Swift 实体类定义(由 Xcode 自动生成)
@objc(UserTestRecord)
public class UserTestRecord: NSManagedObject {}

@objc(ResultSnapshot)
public class ResultSnapshot: NSManagedObject {}

// 扩展属性便于访问
extension UserTestRecord {
    @nonobjc public class func fetchRequest() -> NSFetchRequest<UserTestRecord> {
        return NSFetchRequest<UserTestRecord>(entityName: "UserTestRecord")
    }

    @NSManaged public var uuid: String?
    @NSManaged public var createdAt: Date?
    @NSManaged public var isSynced: Bool
    @NSManaged public var result: ResultSnapshot?
}

extension ResultSnapshot {
    @NSManaged public var somatization: Double
    @NSManaged public var obsession: Double
    @NSManaged public var depression: Double
    @NSManaged public var anxiety: Double
    @NSManaged public var totalScore: Int16
    @NSManaged public var positiveItemCount: Int16
    @NSManaged public var rawAnswersJson: String?
}
保存测评记录示例
func saveTestResult(result: Scl90Result, answers: [Int: Int]) {
    let context = persistentContainer.viewContext

    let record = UserTestRecord(context: context)
    record.uuid = UUID().uuidString
    record.createdAt = Date()
    record.isSynced = false

    let snapshot = ResultSnapshot(context: context)
    snapshot.somatization = result.somatization
    snapshot.obsession = result.obsession
    snapshot.depression = result.depression
    snapshot.anxiety = result.anxiety
    snapshot.totalScore = Int16(result.totalScore)
    snapshot.positiveItemCount = Int16(result.positiveItemCount)
    snapshot.rawAnswersJson = try? JSONEncoder().encode(answers).base64EncodedString()

    record.result = snapshot

    do {
        try context.save()
        print("测评记录已保存")
    } catch {
        print("保存失败: $error)")
    }
}
参数说明:
  • persistentContainer : 由 Core Data Stack 提供的容器实例,管理上下文与存储。
  • JSONEncoder().encode(...).base64EncodedString() : 将原始答案字典编码为 Base64 字符串,便于存储非结构化数据。
  • 异常处理确保即使部分写入失败也不会导致应用崩溃。

3.3 共享逻辑层抽取与代码复用

为避免 Android 与 iOS 端重复实现相同的评分算法、数据校验规则等业务逻辑,引入 Kotlin Multiplatform Mobile(KMM) 是最佳解决方案。KMM 允许开发者用 Kotlin 编写可在双端调用的共享代码,无需桥接 JavaScript 或依赖第三方运行时。

3.3.1 业务逻辑抽象为独立Module(Gradle Subproject)

创建一个新的 Gradle 模块 shared-core ,其 build.gradle.kts 配置如下:

plugins {
    kotlin("multiplatform")
    id("com.android.library")
    kotlin("plugin.serialization")
}

kotlin {
    android()
    iosX64()
    iosArm64()

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
            }
        }
        val androidMain by getting
        val iosMain by getting
    }
}

在此模块中定义通用的数据模型与计算逻辑:

// shared-core/src/commonMain/kotlin/Scl90Model.kt
data class Scl90Result(
    val somatization: Double,
    val obsession: Double,
    val interpersonalSensitivity: Double,
    val depression: Double,
    val anxiety: Double,
    val hostility: Double,
    val phobicAnxiety: Double,
    val paranoidIdeation: Double,
    val psychoticism: Double,
    val totalScore: Int,
    val positiveItemCount: Int
)

expect fun platformName(): String
// shared-core/src/commonMain/kotlin/Scl90Calculator.kt
class Scl90Calculator {
    fun calculate(answers: Map<Int, Int>): Scl90Result {
        // 同前文 Kotlin 版本实现
    }
}
Android 端调用方式
val calculator = Scl90Calculator()
val result = calculator.calculate(viewModel.answers.value)
iOS 端调用(Swift)

需先生成 .framework 并导入 Xcode 工程:

let calculator = Scl90Calculator()
let answersDict = Dictionary(uniqueKeysWithValues: answers.map { ($0.key, $0.value) })
let result = calculator.calculate(answers: answersDict)
print("Depression Score: $result.depression)")

🔗 KMM 极大减少了双端维护成本,尤其适用于算法密集型、规则明确的心理测评场景。

3.3.2 多平台统一接口定义

为进一步提升可测试性与扩展性,采用依赖倒置原则定义统一接口:

interface TestRepository {
    suspend fun saveResult(result: Scl90Result, answers: Map<Int, Int>)
    suspend fun getHistory(): List<Scl90Result>
}

Android 实现 Room 数据库操作,iOS 实现 Core Data 封装,共享层仅依赖接口而不关心具体实现,形成真正的平台无关架构。

流程图:KMM 跨平台调用链
flowchart TD
    A[Shared Core Module] -->|Kotlin| B(Android App)
    A -->|Kotlin/Native| C(iOS App)
    B --> D[Room Database]
    C --> E[Core Data]
    A --> F[Scl90Calculator]
    F --> G[Factor Scoring Algorithm]
    H[UI Layer] --> A

该架构实现了 逻辑隔离、数据自治、表现分离 ,为未来接入 Web 或桌面客户端预留了扩展空间。

4. 项目工程化管理与持续集成体系建设

现代软件开发已从单一编码行为演进为系统化的工程实践,尤其在涉及心理健康等敏感领域的应用中,项目的可维护性、可追溯性与自动化能力成为保障产品质量的核心支柱。随着团队规模扩大、功能模块增多以及多平台并行开发的现实需求,传统“手动构建—本地测试—人工部署”的模式早已无法满足敏捷迭代的要求。因此,建立一套完整的工程化管理体系和持续集成(CI)机制,不仅是提升开发效率的技术手段,更是确保心理测评数据一致性、服务稳定性与代码质量可控性的关键防线。

本章将围绕SCL-90心理健康App的实际项目结构,深入剖析如何通过Maven进行依赖治理、利用批处理与Shell脚本实现跨平台一键构建、借助Git规范版本控制流程,并最终通过GitHub Actions搭建全自动CI流水线。整个过程不仅关注技术实现细节,更强调工程思维的落地——即如何将开发、测试、部署各环节标准化、可视化、自动化,从而支撑一个具备生产级可靠性的移动医疗类应用的长期演进。

4.1 Maven项目构建与pom.xml配置管理

Maven作为Java生态中最主流的项目管理和构建工具,其核心价值在于统一管理依赖、标准化构建生命周期、支持多模块聚合构建。在本项目的后端Spring Boot服务中,采用Maven不仅能有效解决第三方库版本冲突问题,还能通过清晰的 pom.xml 文件实现构建逻辑的声明式表达,极大提升了项目的可读性和可移植性。

4.1.1 后端服务依赖声明与版本锁定

在实际开发过程中,若不加约束地引入各类开源组件,极易导致“依赖地狱”——不同模块引用同一库的不同版本,造成运行时异常或安全漏洞。为此,在 pom.xml 中合理组织依赖关系至关重要。

以下是一个典型的心理健康评估系统后端服务的 pom.xml 片段:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mh.app</groupId>
    <artifactId>scl90-backend</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <!-- 定义统一版本号 -->
    <properties>
        <spring-boot.version>3.1.5</spring-boot.version>
        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
        <jwt.version>0.11.5</jwt.version>
        <java.version>17</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Web模块 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- MyBatis Plus ORM框架 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- JWT身份认证 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>${jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>${jwt.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>${jwt.version}</version>
            <scope>runtime</scope>
        </dependency>

        <!-- 单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
代码逻辑逐行分析
  • <properties> 标签内定义了关键依赖的版本变量,避免硬编码版本号,便于集中升级。
  • 使用 <dependencyManagement> 导入 Spring Boot 官方 BOM(Bill of Materials),确保所有 Starter 组件版本兼容。
  • mybatis-plus-boot-starter 提供了对数据库操作的增强支持,简化DAO层编写。
  • JWT相关三个依赖分别对应API接口、实现类和Jackson序列化支持,构成完整的JWT令牌解析能力。
  • spring-boot-maven-plugin 插件允许打包成可执行JAR文件,内置Tomcat容器。
参数说明表
参数 作用
groupId 项目组织唯一标识,通常为反向域名
artifactId 项目名称,用于生成最终JAR名
version 版本号,SNAPSHOT表示开发中版本
packaging 打包格式,jar表示可运行的独立服务

此外,为了防止未来因依赖传递引发冲突,建议结合 mvn dependency:tree 命令定期审查依赖树,并使用 <exclusions> 排除不必要的间接依赖。

4.1.2 构建生命周期定制

Maven默认提供了 clean → compile → test → package → install → deploy 的标准生命周期。但在实际项目中,往往需要在特定阶段插入自定义行为,例如:在打包前校验SQL脚本语法正确性,防止错误脚本上线。

可通过配置 maven-antrun-plugin 实现此目标:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>3.1.0</version>
    <executions>
        <execution>
            <phase>process-resources</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <target>
                    <echo message="Running SQL syntax check before packaging..." />
                    <exec executable="sh" failonerror="true">
                        <arg value="-c"/>
                        <arg value="grep -E 'DROP TABLE|CREATE TABLE' src/main/resources/sql/init.sql | sqlfluff lint --dialect=mysql"/>
                    </exec>
                </target>
            </configuration>
        </execution>
    </executions>
</plugin>

该插件在 process-resources 阶段执行 shell 命令调用 sqlfluff 工具对初始化SQL文件进行静态检查,若发现语法错误则中断构建流程,强制开发者修复后再提交。

4.2 自动化构建脚本(ry.bat与ry.sh)应用

尽管IDE提供了图形化构建方式,但在团队协作和CI环境中,命令行脚本是实现一致化构建的关键。为此,项目根目录下提供两个自动化脚本:Windows下的 ry.bat 和Linux/macOS下的 ry.sh ,用于一键完成清理、编译、启动全过程。

4.2.1 Windows批处理脚本实现一键部署

以下是 ry.bat 脚本内容:

@echo off
title SCL-90 Backend Auto Deploy
color 0a

echo [INFO] 正在清理旧构建...
call mvn clean

if %errorlevel% neq 0 (
    echo [ERROR] 清理失败,请检查Maven环境!
    pause
    exit /b 1
)

echo [INFO] 开始编译项目...
call mvn compile

if %errorlevel% neq 0 (
    echo [ERROR] 编译失败,请查看上述日志!
    pause
    exit /b 1
)

echo [INFO] 构建可执行JAR...
call mvn package -DskipTests

if %errorlevel% neq 0 (
    echo [ERROR] 打包失败!
    pause
    exit /b 1
)

echo [INFO] 启动Spring Boot服务...
start "SCL90 Server" cmd /c "java -jar target/scl90-backend-1.0.0-SNAPSHOT.jar"

echo.
echo 服务已启动,访问 http://localhost:8080
timeout /t 3 >nul
exit /b 0
逻辑分析
  • 使用 call 调用Maven命令,保证出错时能捕获返回码。
  • 每个阶段后判断 %errorlevel% 是否为0,非零即终止流程并提示用户。
  • 最终使用 start 命令开启新窗口运行JAR,避免阻塞当前终端。
  • timeout /t 3 实现延迟退出,方便查看输出信息。
mermaid 流程图展示构建流程
graph TD
    A[开始] --> B{操作系统类型}
    B -->|Windows| C[执行 ry.bat]
    C --> D[清理 target 目录]
    D --> E[编译源码]
    E --> F[跳过测试打包]
    F --> G[启动 JAR 服务]
    G --> H[服务运行中]
    B -->|Linux/macOS| I[执行 ry.sh]
    I --> J[chmod +x 赋权]
    J --> K[执行构建链]
    K --> L[后台启动服务]

4.2.2 Linux Shell脚本支持CI环境运行

对应的 ry.sh 内容如下:

#!/bin/bash

set -e  # 出错立即停止

echo "[INFO] 开始自动化构建..."

# 清理
echo "[STEP 1] 清理旧文件"
mvn clean

# 编译
echo "[STEP 2] 编译源码"
mvn compile

# 打包(跳过测试以加快CI速度)
echo "[STEP 3] 打包为JAR"
mvn package -DskipTests=true

# 设置日志路径
LOG_FILE="./logs/app.log"
mkdir -p ./logs

# 启动服务并重定向日志
echo "[STEP 4] 启动后端服务,日志写入 $LOG_FILE"
nohup java -jar target/scl90-backend-1.0.0-SNAPSHOT.jar > "$LOG_FILE" 2>&1 &

# 输出PID便于监控
echo $! > ./logs/app.pid
echo "[SUCCESS] 服务已在后台启动,PID=$(cat ./logs/app.pid),日志位于 $LOG_FILE"
参数与逻辑说明
  • set -e 确保任意命令失败时脚本立即退出,防止后续误操作。
  • nohup 使进程脱离终端运行,适合服务器长期驻留。
  • > "$LOG_FILE" 2>&1 & 将标准输出和错误合并写入日志文件,并以后台模式运行。
  • 记录PID至文件,便于后续通过 kill $(cat logs/app.pid) 安全关闭服务。
权限设置指令
chmod +x ry.sh

确保脚本具有可执行权限,否则在CI环境中会报“Permission denied”。

4.3 Git版本控制与.gitignore规范配置

版本控制系统是团队协作的基础设施。合理的 .gitignore 配置不仅能减少仓库体积,更能防止敏感信息泄露。

4.3.1 分支策略设计(Git Flow)

采用经典的 Git Flow 模型进行分支管理:

分支类型 用途 生命周期
main 生产环境代码 长期存在,只接受合并
develop 集成开发主线 长期存在
feature/* 新功能开发 功能完成后删除
release/* 发布候选版本 发布后删除
hotfix/* 紧急线上修复 修复合并后删除
工作流示意图
graph LR
    main --> hotfix --> main
    develop --> release --> main & develop
    feature --> develop

每次新功能开发应基于 develop 创建 feature/login-ui 类似分支;发布前从 develop 切出 release/v1.2 进行测试;线上紧急bug则直接从 main 派生 hotfix/db-pool-leak

4.3.2 敏感文件过滤规则制定

.gitignore 文件应包含以下条目:

# IDE配置
.idea/
*.iml
.vscode/
.DS_Store

# 编译产物
/target/
/out/
/build/

# 日志与本地配置
/logs/
/config-local.properties
/application-secret.yml

# 密钥文件
*.pem
*.keystore
.env

# 依赖缓存
node_modules/
.gradle/
.m2/repository
表格:常见忽略项分类说明
类别 示例 忽略原因
IDE元数据 .idea/ , .vscode/ 用户个性化设置,不应共享
编译输出 /target , /build 可由源码重建,无需纳入版本库
本地配置 config-local.properties 包含数据库密码等敏感信息
日志文件 /logs/*.log 内容动态生成,占用空间大
第三方依赖缓存 .m2/repository 全球统一坐标,本地可下载

特别注意:一旦误提交了密钥文件,即使后续加入 .gitignore ,历史记录中仍存在风险。应使用 git filter-repo 彻底清除。

4.4 GitHub Actions与持续集成(.github目录)

持续集成的目标是“每一次提交都是一次可发布的候选”,通过自动化测试与构建验证代码质量。

4.4.1 CI流水线定义(yaml配置)

.github/workflows/ci.yml 配置如下:

name: CI Pipeline

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Cache Maven packages
        uses: actions/cache@v3
        with:
          path: ~/.m2
          key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
          restore-keys: ${{ runner.os }}-m2

      - name: Build with Maven
        run: ./ry.sh

      - name: Run Unit Tests
        run: mvn test

      - name: Build APK (Android)
        if: github.event_name == 'push'
        run: cd android && ./gradlew assembleRelease

      - name: Upload APK artifact
        uses: actions/upload-artifact@v3
        with:
          path: android/app/build/outputs/apk/release/*.apk
执行流程说明
  1. 触发条件:推送至 main 或发起PR至 main
  2. 检出代码后安装Java 17环境
  3. 缓存Maven依赖加速构建
  4. 执行 ry.sh 构建后端服务
  5. 运行单元测试确保基础逻辑正确
  6. 若为主干推送,则构建Android APK并上传为制品

4.4.2 集成SonarQube进行静态代码质量扫描

为进一步提升代码质量,可在CI中集成 SonarQube:

      - name: SonarQube Scan
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
        run: >
          mvn -B verify
          org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
          -Dsonar.qualitygate.wait=true
          -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml

需提前配置:
- 在项目中生成 JaCoCo 测试覆盖率报告
- 设置 GitHub Secrets 存储 SonarToken
- 质量门禁(Quality Gate)设定:覆盖率 ≥70%,无严重Bug,圈复杂度 ≤10

当PR未通过质量门限时,自动阻止合并,推动开发者优化代码结构。

5. 数据库设计与心理测评全流程闭环实现

5.1 数据库表结构设计与SQL脚本初始化

在心理健康App中,数据的持久化存储是保障用户测评记录可追溯、可分析的关键。系统采用关系型数据库(如MySQL或SQLite)进行核心数据建模,围绕SCL-90测评流程构建三大主实体:用户信息、测评记录与结果详情。

核心实体关系建模(ER图)

通过以下ER模型描述主要数据对象及其关联:

erDiagram
    USER_INFO ||--o{ TEST_RECORD : "1:N"
    TEST_RECORD ||--o{ RESULT_DETAIL : "1:N"

    USER_INFO {
        bigint id PK
        varchar(64) username
        varchar(255) encrypted_password
        datetime create_time
        datetime update_time
    }
    TEST_RECORD {
        bigint id PK
        bigint user_id FK
        int total_score
        int positive_item_count
        float anxiety_factor
        float depression_factor
        datetime test_time
        index(user_id, test_time)
    }

    RESULT_DETAIL {
        bigint id PK
        bigint record_id FK
        json raw_answers
        json factor_breakdown
        text personalized_advice
        datetime generate_time
    }

上述模型体现了以下设计原则:
- 用户表(user_info) :存储注册用户的基本身份信息,密码经BCrypt加密处理。
- 测评记录表(test_record) :保存每次测评的汇总指标,便于快速展示趋势图表。
- 结果详情表(result_detail) :存放原始答题数据与详细分析结果,支持报告回溯。

初始化脚本编写(init.sql)

以下是用于初始化数据库结构的SQL脚本,包含表创建、索引优化及示例数据插入:

-- 创建用户表
CREATE TABLE user_info (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(64) UNIQUE NOT NULL,
    encrypted_password VARCHAR(255) NOT NULL,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 创建测评记录表
CREATE TABLE test_record (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    total_score INT DEFAULT 0,
    positive_item_count INT DEFAULT 0,
    anxiety_factor FLOAT(4,2),
    depression_factor FLOAT(4,2),
    obsessive_compulsive_factor FLOAT(4,2),
    test_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES user_info(id) ON DELETE CASCADE,
    INDEX idx_user_time (user_id, test_time DESC)
);

-- 创建结果详情表
CREATE TABLE result_detail (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    record_id BIGINT NOT NULL,
    raw_answers JSON,
    factor_breakdown JSON,
    personalized_advice TEXT,
    generate_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (record_id) REFERENCES test_record(id) ON DELETE CASCADE
);

-- 插入测试用户数据(不少于10行)
INSERT INTO user_info (username, encrypted_password) VALUES
('user001', '$2a$10$EIX./LWzK.rUO7NjB8eR.eDkXtGvJdZqHbFyYfQn3uPw1TmJgV2a'),
('user002', '$2a$10$EIX./LWzK.rUO7NjB8eR.eDkXtGvJdZqHbFyYfQn3uPw1TmJgV2b'),
('user003', '$2a$10$EIX./LWzK.rUO7NjB8eR.eDkXtGvJdZqHbFyYfQn3uPw1TmJgV2c'),
('user004', '$2a$10$EIX./LWzK.rUO7NjB8eR.eDkXtGvJdZqHbFyYfQn3uPw1TmJgV2d'),
('user005', '$2a$10$EIX./LWzK.rUO7NjB8eR.eDkXtGvJdZqHbFyYfQn3uPw1TmJgV2e'),
('user006', '$2a$10$EIX./LWzK.rUO7NjB8eR.eDkXtGvJdZqHbFyYfQn3uPw1TmJgV2f'),
('user007', '$2a$10$EIX./LWzK.rUO7NjB8eR.eDkXtGvJdZqHbFyYfQn3uPw1TmJgV2g'),
('user008', '$2a$10$EIX./LWzK.rUO7NjB8eR.eDkXtGvJdZqHbFyYfQn3uPw1TmJgV2h'),
('user009', '$2a$10$EIX./LWzK.rUO7NjB8eR.eDkXtGvJdZqHbFyYfQn3uPw1TmJgV2i'),
('user010', '$2a$10$EIX./LWzK.rUO7NjB8eR.eDkXtGvJdZqHbFyYfQn3uPw1TmJgV2j');

该脚本确保了系统部署初期具备基础运行环境,并为后续接口测试提供初始数据集。特别是对 test_time 字段建立联合索引,显著提升按时间范围查询历史记录的性能表现。

5.2 后端服务与API接口设计实现

Spring Boot实现REST API

基于Spring Boot框架搭建后端服务,定义标准化RESTful接口以支撑移动端的数据交互需求。

提交测评数据接口
@PostMapping("/api/v1/scl90/submit")
public ResponseEntity<AssessmentReport> submitScl90(
    @RequestBody Scl90Submission submission,
    @RequestHeader("Authorization") String token) {

    // JWT验证
    String userId = jwtService.parseUserId(token);
    // 计算各维度因子分
    AssessmentResult result = assessmentEngine.calculate(submission.getAnswers());
    // 持久化记录
    TestRecord record = testRecordService.saveRecord(userId, result);
    // 生成完整报告
    AssessmentReport report = reportGenerator.generateComprehensiveReport(result);
    return ResponseEntity.ok(report);
}

参数说明:
- submission : 包含90个题项评分的JSON数组
- token : Bearer类型的JWT令牌
- 返回值:包含总分、因子分、建议文本等字段的标准JSON响应

获取历史趋势数据接口
@GetMapping("/api/v1/report/history")
public ResponseEntity<List<TestTrendDto>> getHistory(
    @RequestParam(defaultValue = "30") int days,
    @RequestHeader("Authorization") String token) {

    String userId = jwtService.parseUserId(token);
    List<TestTrendDto> trends = historyService.getTrendData(userId, days);
    return ResponseEntity.ok(trends);
}

此接口返回近N天内的测评得分变化曲线所需数据点,供前端绘制折线图使用。

接口安全性加固

采用多层防护机制增强API安全性:
1. 所有敏感接口需携带有效JWT令牌;
2. 使用Spring Security配置全局拦截器;
3. 引入RateLimitingFilter限制单IP每分钟最多5次提交请求;
4. 敏感操作日志记录至独立审计表。

5.3 心理测评结果计算逻辑与报告生成模块

多维度分数聚合算法实现

SCL-90的九个因子项需根据预设映射规则进行加权平均计算。例如焦虑因子涉及第2、9、14、16等共10个题目。

fun calculateAnxietyFactor(answers: IntArray): Float {
    val anxietyItems = listOf(1, 8, 13, 15, 22, 27, 28, 39, 45, 58) // 零基索引
    val sum = anxietyItems.sumOf { answers[it] }
    return BigDecimal(sum / 10.0).setScale(2, RoundingMode.HALF_UP).toFloat()
}

所有因子分统一保留两位小数,避免浮点误差影响临床判断。

PDF报告自动生成与导出

利用iText库动态生成专业格式的心理测评报告:

Document doc = new Document();
PdfWriter.getInstance(doc, new FileOutputStream("report.pdf"));
doc.open();

// 添加标题
Font titleFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 18);
Paragraph title = new Paragraph("SCL-90心理健康评估报告", titleFont);
doc.add(title);

// 插入雷达图(Base64编码图像)
Image chart = Image.getInstance(Base64.decode(pdfChartBase64));
chart.scaleToFit(500, 300);
doc.add(chart);

// 写入个性化建议
String advice = "您的焦虑水平略高,建议每周进行3次正念冥想...";
doc.add(new Paragraph(advice));

doc.close();

报告内容涵盖雷达图、文字解读、干预建议三部分,支持一键分享至邮箱或社交平台。

5.4 用户隐私保护与心理数据安全机制

数据脱敏处理策略

在应用日志中禁止输出原始答题内容:

logging:
  level:
    com.app.controller.Scl90Controller: WARN
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} - %level - %logger{36} - %msg%n"

同时,在AOP切面中对出入参做自动过滤:

@Around("@annotation(Sensitive)")
public Object maskSensitive(ProceedingJoinPoint pjp) throws Throwable {
    Object[] args = pjp.getArgs();
    // 清除raw_answers字段日志输出
    return pjp.proceed();
}

本地数据库加密(Room + SQLCipher)

Android端采用SQLCipher对Room数据库进行全量加密:

val passphrase: ByteArray = SQLiteDatabase.getBytes(userBiometricKey.toCharArray())
val factory = SupportFactory(passphrase)

val db = Room.databaseBuilder(context, AppDatabase::class.java, "mental_health.db")
            .openHelperFactory(factory)
            .build()

密钥由系统级生物识别认证后动态生成,确保即使设备丢失也无法轻易提取敏感心理数据。

5.5 毕业设计项目交付与文档体系完善

多语言项目说明文档撰写(README.md与README.en.md)

# 心理健康测评App(SCL-90)

## 功能特性
- 支持完整的SCL-90量表在线测评
- 自动计算9大症状因子分
- 历史趋势可视化分析
- PDF报告导出与分享

## 技术栈
- Android: Kotlin + Jetpack Compose
- Backend: Spring Boot + MySQL
- 安全: JWT + AES-256 + SQLCipher

配套英文版README确保国际学术交流无障碍。

开源许可证选择与LICENSE文件配置

项目采用MIT许可证,明确声明:

“本软件可用于学术研究、教学演示及非商业用途,保留所有权利。”

此举既促进知识传播,又规避潜在版权纠纷。

从需求到部署的完整闭环总结

最终交付物包括:
1. Figma原型设计图(dark_mode_ui_v3.fig)
2. Postman接口测试集合(SCL90_API_Test.json)
3. 服务器部署手册(Deployment_Guide.pdf)
4. 答辩用PPT模板(Final_Presentation_Template.pptx)

形成覆盖“需求—设计—开发—测试—部署”的全生命周期材料链,满足毕业设计综合评价要求。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目是一项面向心理健康领域的移动应用类毕业设计,旨在将广泛使用的SCL-90症状自评量表数字化,开发一款可在Android或iOS平台运行的心理健康测试App。该应用通过标准化问卷采集用户心理状态数据,支持自动评分与结果分析,生成可视化报告,帮助用户进行自我心理评估。项目采用现代软件工程实践,包含前端界面、后端服务、数据库管理及API接口设计,使用Maven构建工具与Git版本控制,并配备完整文档与开源许可证,具备良好的可扩展性与实用性。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐