清理并优化项目

This commit is contained in:
xiao12feng@outlook.com 2025-12-18 09:00:51 +08:00
parent e7c1563f15
commit 2c277d0344
43 changed files with 55 additions and 9834 deletions

View File

@ -1,80 +0,0 @@
# 项目修改历史记录
## 2025-01-XX - 学员登录功能改造
### 修改内容
#### 1. 登录页面改造
- **文件**: `ruoyi-ui/src/views/login.vue`
- **修改说明**:
- 将登录页面改为学员登录页面
- 只需输入学员编号即可登录,无需密码和验证码
- 添加醒目的"管理员登录"链接,链接到管理员登录页面
- 登录成功后跳转到学员测试题列表页面
#### 2. 创建管理员登录页面
- **文件**: `ruoyi-ui/src/views/admin-login.vue`
- **修改说明**:
- 创建独立的管理员登录页面
- 保留原有的账号、密码、验证码登录方式
- 添加"返回学员登录"链接
#### 3. 学员登录API
- **文件**: `ruoyi-ui/src/api/login.js`
- **修改说明**:
- 新增 `studentLogin` 方法,用于学员登录
- 接口地址: `/student/login`
- 参数: `studentNo` (学员编号)
- 添加判空处理,确保学员编号不为空
#### 4. Store用户模块扩展
- **文件**: `ruoyi-ui/src/store/modules/user.js`
- **修改说明**:
- 新增 `StudentLogin` action处理学员登录逻辑
- 学员登录成功后设置token和学员角色
- 添加判空处理,确保数据安全
#### 5. 学员测试题列表页面
- **文件**: `ruoyi-ui/src/views/student/tests.vue`
- **修改说明**:
- 创建学员端心理测试题列表页面
- 显示所有开放的测试题(量表和问卷)
- 支持搜索功能
- 卡片式展示,点击可开始测试
- 支持量表和问卷两种类型的测试
- 显示学员编号和退出登录功能
#### 6. 路由配置更新
- **文件**: `ruoyi-ui/src/router/index.js`
- **修改说明**:
- 添加管理员登录路由: `/admin-login`
- 添加学员测试题列表路由: `/student/tests`
- 学员测试页面不使用Layout组件作为独立页面
#### 7. 权限控制更新
- **文件**: `ruoyi-ui/src/permission.js`
- **修改说明**:
- 将 `/admin-login` 添加到白名单
- 处理学员登录后的路由跳转逻辑
- 学员角色只能访问学员相关页面
- 已登录用户访问登录页面时根据角色跳转
### 功能特点
1. **学员登录简化**: 只需输入学员编号即可登录,无需密码,提升用户体验
2. **管理员登录分离**: 管理员和学员使用不同的登录入口,界面更清晰
3. **测试题展示**: 学员登录后可以看到所有开放的心理测试题
4. **权限控制**: 学员和管理员使用不同的角色权限,确保数据安全
5. **响应式设计**: 测试题列表页面采用卡片式布局,支持响应式显示
### 技术要点
- 使用Vue.js + Element UI实现
- 采用Vuex进行状态管理
- 路由守卫控制页面访问权限
- API接口统一管理
- 代码添加详细注释,提高可维护性
### 开发者
wanxiubin

View File

@ -1,152 +0,0 @@
# TTS 功能清理完成报告
## ✅ 清理完成
**清理时间**2025-01-27
**清理范围**:所有 TTS 相关代码、配置和文档
---
## 🗑️ 已删除的文件
### Java 代码文件
1. ✅ `ry-xinli-common/src/main/java/com/ddnai/common/utils/tts/PaddleTtsUtil.java`
2. ✅ `ry-xinli-admin/src/main/java/com/ddnai/web/controller/common/TtsController.java`
3. ✅ `ry-xinli-common/src/main/java/com/ddnai/common/utils/tts/` 目录
### Python 服务文件
1. ✅ `tts-service/` 整个目录(包含所有文件)
- `app.py`
- `requirements.txt`
- `README.md`
- `start.sh`
- `start.bat`
- `test_service.py`
### 配置文件
1. ✅ `ry-xinli-admin/src/main/resources/application.yml` - 已移除 TTS 配置节
### 依赖配置
1. ✅ `ry-xinli-admin/pom.xml` - 已移除 `paddle-tts-java` 依赖
### 测试脚本
1. ✅ `test-tts-integration.sh`
2. ✅ `test-tts-integration.bat`
3. ✅ `download-tts-sdk.ps1`
### 文档文件
1. ✅ `TTS功能快速开始.md`
2. ✅ `TTS功能实施完整总结.md`
3. ✅ `TTS功能实施完成总结.md`
4. ✅ `TTS功能完整部署指南.md`
5. ✅ `TTS服务部署架构说明.md`
6. ✅ `TTS服务快速部署参考.md`
7. ✅ `TTS功能部署检查清单.md`
8. ✅ `TTS功能快速开始指南.md`
9. ✅ `TTS功能实施说明.md`
10. ✅ `TTS方案可行性分析报告.md`
11. ✅ `TTS依赖验证结果报告-更新.md`
12. ✅ `TTS依赖验证结果报告.md`
13. ✅ `paddle-tts-java依赖验证最终结果.md`
14. ✅ `paddle-tts-java下载链接和验证方法.md`
15. ✅ `paddle-tts-java下载和验证指南.md`
16. ✅ `Python-TTS服务实施方案.md`
17. ✅ `README-TTS功能.md`
18. ✅ `PaddleSpeech分析报告.md`
---
## ✅ 已恢复的文件
### 配置文件
1. ✅ `ry-xinli-admin/src/main/resources/application.yml`
- 已移除 `tts:` 配置节
- 恢复到原始状态
### 依赖配置
1. ✅ `ry-xinli-admin/pom.xml`
- 已移除 `paddle-tts-java` 依赖
- 恢复到原始状态
---
## 🔍 验证结果
### 代码检查
- ✅ 无 TTS 相关 Java 代码残留
- ✅ 无 TTS 相关配置文件残留
- ✅ 无 TTS 相关依赖残留
### 目录检查
- ✅ `tts-service/` 目录已删除
- ✅ `ry-xinli-common/.../utils/tts/` 目录已删除
---
## 📋 清理清单
- [x] 删除 Java TTS 工具类
- [x] 删除 TTS Controller
- [x] 删除 Python TTS 服务目录
- [x] 删除配置文件中的 TTS 配置
- [x] 删除 pom.xml 中的 TTS 依赖
- [x] 删除所有 TTS 相关文档
- [x] 删除测试脚本
- [x] 验证无残留代码
---
## ✨ 清理完成
所有 TTS 相关代码、配置和文档已完全清除。
项目已恢复到未集成 TTS 功能之前的状态。
---
## 🔍 最终验证
### 代码文件
- ✅ `TtsController.java` - 已删除(未找到)
- ✅ `PaddleTtsUtil.java` - 已删除(未找到)
### 配置文件
- ✅ `application.yml` - TTS 配置已移除
- ✅ `pom.xml` - TTS 依赖已移除
### 目录
- ✅ `tts-service/` - 已删除
- ⚠️ `ry-xinli-common/.../utils/tts/` - 空目录(如果存在可手动删除)
### 文档
- ✅ 所有 TTS 相关文档已删除18个文档文件
---
## 📝 注意事项
如果 `ry-xinli-common/src/main/java/com/ddnai/common/utils/tts/` 空目录仍然存在,可以手动删除:
```bash
# Windows
rmdir /s ry-xinli-common\src\main\java\com\ddnai\common\utils\tts
# Linux/Mac
rm -rf ry-xinli-common/src/main/java/com/ddnai/common/utils/tts
```
---
**清理完成时间**2025-01-27
**清理状态**:✅ 完成

67
ry.bat
View File

@ -1,67 +0,0 @@
@echo off
rem jarƽ<72><C6BD>Ŀ¼
set AppName=ry-xinli-admin.jar
rem JVM<56><4D><EFBFBD><EFBFBD>
set JVM_OPTS="-Dname=%AppName% -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC"
ECHO.
ECHO. [1] <20><><EFBFBD><EFBFBD>%AppName%
ECHO. [2] <20>ر<EFBFBD>%AppName%
ECHO. [3] <20><><EFBFBD><EFBFBD>%AppName%
ECHO. [4] <20><><EFBFBD><EFBFBD>״̬ %AppName%
ECHO. [5] <20><> <20><>
ECHO.
ECHO.<2E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD>:
set /p ID=
IF "%id%"=="1" GOTO start
IF "%id%"=="2" GOTO stop
IF "%id%"=="3" GOTO restart
IF "%id%"=="4" GOTO status
IF "%id%"=="5" EXIT
PAUSE
:start
for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
set pid=%%a
set image_name=%%b
)
if defined pid (
echo %%is running
PAUSE
)
start javaw %JVM_OPTS% -jar %AppName%
echo starting<6E><67><EFBFBD><EFBFBD>
echo Start %AppName% success...
goto:eof
rem <20><><EFBFBD><EFBFBD>stopͨ<70><CDA8>jps<70><73><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>pid<69><64><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
:stop
for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
set pid=%%a
set image_name=%%b
)
if not defined pid (echo process %AppName% does not exists) else (
echo prepare to kill %image_name%
echo start kill %pid% ...
rem <20><><EFBFBD>ݽ<EFBFBD><DDBD><EFBFBD>ID<49><44>kill<6C><6C><EFBFBD><EFBFBD>
taskkill /f /pid %pid%
)
goto:eof
:restart
call :stop
call :start
goto:eof
:status
for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do (
set pid=%%a
set image_name=%%b
)
if not defined pid (echo process %AppName% is dead ) else (
echo %image_name% is running
)
goto:eof

View File

@ -129,7 +129,7 @@
</template>
<script>
import { getUserAssessmentSummary, getStudentOptions } from '@/api/psychology/assessment'
import { getUserAssessmentSummary, getStudentOptions, listAssessment } from '@/api/psychology/assessment'
import { getProfileByUserId, listProfile } from '@/api/psychology/profile'
import { getReport, listReport } from '@/api/psychology/report'
import { parseTime } from '@/utils/ruoyi'
@ -329,32 +329,63 @@ export default {
}
this.loading = true
try {
//
// 使
const assessmentResponse = await listAssessment({
userId: this.selectedUserId,
status: '1', //
pageNum: 1,
pageSize: 1000
})
const assessments = assessmentResponse.rows || []
// reportId
const scaleReportsResponse = await listReport({
userId: this.selectedUserId,
sourceType: 'assessment',
isGenerated: '1',
pageNum: 1,
pageSize: 1000
})
const reportMap = new Map()
if (scaleReportsResponse.rows) {
scaleReportsResponse.rows.forEach(report => {
if (report.assessmentId) {
reportMap.set(report.assessmentId, report)
}
})
}
//
const scaleReports = []
for (const assessment of assessments) {
//
if (assessment.hasReport) {
const report = reportMap.get(assessment.assessmentId)
scaleReports.push({
key: `${assessment.scaleId}-${assessment.assessmentId}`,
scaleId: assessment.scaleId,
scaleName: assessment.scaleName,
assessmentId: assessment.assessmentId,
reportId: report ? report.reportId : null,
reportTitle: report ? report.reportTitle : `${assessment.scaleName}测评报告`,
submitTime: assessment.submitTime || assessment.startTime,
totalScore: assessment.totalScore,
summary: report ? report.summary : '',
status: assessment.status,
sourceType: 'assessment'
})
}
}
//
const summaryResponse = await getUserAssessmentSummary(this.selectedUserId)
this.userSummary = summaryResponse.data || null
//
const rawScales = (this.userSummary && Array.isArray(this.userSummary.scales)) ? this.userSummary.scales : []
const scaleReports = rawScales.reduce((result, scale) => {
const attempts = Array.isArray(scale.attempts) ? scale.attempts : []
const reportRows = attempts
.filter((attempt) => attempt && attempt.reportId)
.map((attempt) => ({
key: `${scale.scaleId}-${attempt.assessmentId}`,
scaleId: scale.scaleId,
scaleName: scale.scaleName,
assessmentId: attempt.assessmentId,
reportId: attempt.reportId,
reportTitle: attempt.reportTitle || `${scale.scaleName}测评报告`,
submitTime: attempt.submitTime || attempt.startTime,
totalScore: attempt.totalScore,
summary: attempt.reportSummary || '',
status: attempt.status,
sourceType: 'assessment'
}))
return result.concat(reportRows)
}, [])
//
const questionnaireReports = await this.loadQuestionnaireReports(this.selectedUserId)
//
const combinedReports = [...scaleReports, ...questionnaireReports].sort((a, b) => {
const timeA = a.submitTime ? new Date(a.submitTime).getTime() : 0
const timeB = b.submitTime ? new Date(b.submitTime).getTime() : 0

View File

@ -1,139 +0,0 @@
# 主观题评分功能使用说明
## 功能说明
主观题评分功能允许管理员对问卷中的主观题(简答题、问答题、作文题)进行手动评分。
## 添加菜单方法
### 方法一执行SQL脚本推荐
1. 打开数据库管理工具如Navicat、MySQL Workbench等
2. 连接到数据库
3. 执行 `sql/添加主观题评分菜单.sql` 文件中的SQL语句
4. 刷新浏览器页面,菜单应该会出现在"心理测评管理"下
### 方法二:通过菜单管理页面手动添加
1. 登录系统,进入 **系统管理** -> **菜单管理**
2. 找到 **心理测评管理**菜单ID2009
3. 点击 **新增** 按钮
4. 填写以下信息:
- **菜单名称**:主观题评分
- **父菜单**:心理测评管理
- **显示顺序**13
- **路由地址**questionnaire/scoring
- **组件路径**psychology/questionnaire/scoring
- **路由名称**QuestionnaireScoring
- **菜单类型**:菜单
- **菜单图标**edit
- **权限标识**psychology:questionnaire:score
- **是否显示**:显示
- **状态**:正常
5. 点击 **确定** 保存
6. 添加按钮权限(可选):
- 在菜单管理中找到刚创建的"主观题评分"菜单
- 点击 **新增** 添加按钮权限:
- **菜单名称**:评分查询
- **权限标识**psychology:questionnaire:score:query
- **菜单类型**:按钮
- 再添加一个:
- **菜单名称**:评分操作
- **权限标识**psychology:questionnaire:score
- **菜单类型**:按钮
7. 分配菜单权限:
- 进入 **系统管理** -> **角色管理**
- 找到 **管理员** 角色,点击 **修改**
- 在 **菜单权限** 中勾选 **主观题评分** 及其按钮权限
- 点击 **确定** 保存
## 使用方法
### 1. 访问主观题评分页面
- 方法一:在左侧菜单中找到 **心理测评管理** -> **主观题评分**
- 方法二直接访问URL`/psychology/questionnaire/scoring`
### 2. 查看待评分题目
页面会自动显示所有待评分的主观题,包括:
- 问卷名称
- 答题人
- 题目序号和内容
- 题目类型(简答题/问答题/作文题)
- 题目分值
- 答案内容
- 提交时间
### 3. 单个评分
1. 在列表中点击 **评分** 按钮
2. 在弹出的对话框中:
- 查看题目内容和答案内容
- 输入得分(不能超过题目分值)
- 可选:输入评语
3. 点击 **确定** 保存评分
### 4. 批量评分
1. 在列表中勾选多个待评分的题目
2. 点击 **批量评分** 按钮
3. 在批量评分对话框中为每个题目输入得分和评语
4. 点击 **确定** 批量保存
### 5. 搜索和筛选
- 可以通过 **问卷名称** 搜索
- 可以通过 **答题人** 搜索
## 功能特点
1. **自动识别主观题**系统自动识别简答题text、问答题textarea、作文题essay三种类型
2. **分数验证**:评分不能超过题目分值,系统会自动限制
3. **自动更新总分**:评分后自动重新计算答题记录的总分
4. **自动更新排名**:评分后自动重新计算该问卷的排名
5. **批量评分**:支持同时为多个题目评分,提高效率
6. **评语功能**:可以为每个答案添加评语(可选)
## 注意事项
1. 只有已提交的问卷中的主观题才会出现在待评分列表中
2. 已经评分的题目不会出现在待评分列表中
3. 评分后,用户查看报告时会看到更新后的成绩
4. 如果评分失败,请检查:
- 是否有评分权限(`psychology:questionnaire:score`
- 题目是否为主观题且未评分
- 得分是否超过题目分值
## 权限配置
需要的权限标识:
- `psychology:questionnaire:score` - 评分操作权限
- `psychology:questionnaire:score:query` - 查询权限(可选)
## 常见问题
### Q: 菜单中没有显示"主观题评分"
A: 需要执行SQL脚本或通过菜单管理页面手动添加菜单并确保已分配菜单权限给当前角色。
### Q: 点击菜单后页面空白?
A: 检查:
1. 路由配置是否正确(`ruoyi-ui/src/router/index.js`
2. 页面文件是否存在(`ruoyi-ui/src/views/psychology/questionnaire/scoring.vue`
3. 浏览器控制台是否有错误信息
### Q: 没有待评分的数据?
A: 检查:
1. 是否有已提交的问卷答题记录
2. 问卷中是否包含主观题text、textarea、essay类型
3. 主观题是否已经被评分过(已评分的不会显示)
### Q: 评分后总分没有更新?
A: 系统会自动更新,如果未更新,请:
1. 刷新页面查看
2. 检查后端日志是否有错误
3. 确认评分是否成功(查看操作日志)

View File

@ -1,142 +0,0 @@
# 二维码功能修复说明(最终版)
## 修复时间
2025年1月
## 修复内容
### 一、删除快速生成功能
**修改内容**
1. 删除前端"快速生成"按钮和相关方法
2. 删除后端快速生成接口(`/quick/register` 和 `/quick/login`
3. 删除前端API调用方法
**说明**:用户可以通过"新增"按钮手动创建注册和登录二维码。
### 二、修复本地环境二维码URL生成问题
**问题**:扫描二维码后显示的地址是 `localhost:30081`,这会导致手机无法访问。
**原因分析**
- 本地环境前端运行在80端口后端运行在30081端口
- 二维码URL应该使用前端地址`localhost`80端口而不是后端地址`localhost:30081`
- 之前的逻辑在某些情况下仍然会生成包含30081端口的URL
**修复方案**
1. 优化 `buildServerAddress()` 方法,确保在本地环境下正确识别前端地址
2. 如果检测到后端端口30081自动改为前端端口80不显示端口号
3. 优先从 `Referer` 头获取前端地址,如果失败则使用请求的服务器地址并转换端口
**修复后的URL格式**
- 本地环境:`http://localhost/psychology/qrcode/scan/xxx`不包含端口号默认80端口
- 生产环境:`http://1.15.149.240/psychology/qrcode/scan/xxx`不包含端口号默认80端口
### 三、二维码功能实现方式说明
**当前实现方式**
1. **手动创建二维码**:通过"新增"按钮创建二维码,选择类型(测评、查看报告、注册、登录)
2. **二维码生成**系统自动生成二维码编码和Base64图片
3. **扫码跳转**:扫描二维码后自动跳转到对应页面
**本地环境使用建议**
1. **使用本机IP地址**:如果需要在手机上扫码,建议:
- 获取本机IP地址192.168.1.100
- 在浏览器中通过 `http://192.168.1.100` 访问前端页面
- 这样生成的二维码URL会是 `http://192.168.1.100/psychology/qrcode/scan/xxx`
- 手机和电脑在同一局域网内,可以正常扫码访问
2. **使用localhost**:如果只在电脑上测试:
- 使用 `http://localhost` 访问前端页面
- 生成的二维码URL会是 `http://localhost/psychology/qrcode/scan/xxx`
- 只能在电脑上访问,手机无法访问
**新的实现方式建议**(可选):
如果需要更好的本地开发体验,可以考虑:
1. **配置前端地址**:在 `application.yml` 中添加前端地址配置
2. **使用环境变量**:通过环境变量区分开发环境和生产环境
3. **使用配置文件**:在配置文件中明确指定前端和后端地址
## 测试步骤
### 1. 重启后端服务
**重要**:修改代码后必须重启后端服务才能生效。
### 2. 测试二维码生成
1. 登录管理系统
2. 进入"二维码管理"页面
3. 点击"新增"按钮
4. 选择二维码类型(如:注册、登录、测评、查看报告)
5. 填写相关信息
6. 保存后应成功生成二维码
### 3. 测试二维码扫码功能
1. 使用手机扫描生成的二维码
2. 应能正常打开扫码页面
3. 根据二维码类型自动跳转到对应页面:
- 注册二维码 → 跳转到注册页面
- 登录二维码 → 跳转到登录页面
- 测评二维码 → 跳转到测评开始页面
- 报告二维码 → 跳转到报告详情页面
### 4. 检查二维码URL
生成的二维码URL应该
- **不包含30081端口**:应该是 `http://localhost/...``http://192.168.1.100/...`
- **使用前端地址**指向前端页面80端口而不是后端API30081端口
- **手机可以访问**如果使用本机IP地址手机在同一局域网内可以正常访问
## 关键代码位置
### 后端
- **Controller**`ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQrcodeController.java`
- `buildServerAddress()` - 构建服务器地址(已修复,确保使用前端地址)
- `enrichQrcodeWithBase64()` - 为二维码添加Base64图片
- `scan()` - 扫码接口
- `buildRedirectUrl()` - 构建跳转地址
- **Service**`ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyQrcodeServiceImpl.java`
- `generateQrcode()` - 生成二维码Base64
- `buildQrcodeContent()` - 构建二维码内容URL
### 前端
- **页面**
- `xinli-ui/src/views/psychology/qrcode/index.vue` - 二维码管理页面(已删除快速生成功能)
- `xinli-ui/src/views/psychology/qrcode/scan.vue` - 扫码页面
- **API**`xinli-ui/src/api/psychology/qrcode.js`(已删除快速生成相关方法)
## 注意事项
1. **必须重启后端服务**:修改代码后必须重启后端服务才能生效
2. **本地环境测试**
- 如果需要在手机上扫码使用本机IP地址访问前端页面
- 如果只在电脑上测试使用localhost即可
3. **端口配置**前端运行在80端口后端运行在30081端口
4. **二维码URL**生成的二维码URL会自动使用前端地址80端口不包含30081端口
## 如果仍有问题
1. **检查二维码URL**
- 查看生成的二维码URL是否包含30081端口
- 如果包含,说明 `buildServerAddress()` 方法没有正确工作
- 检查后端日志查看实际生成的URL
2. **检查Referer头**
- 在浏览器开发者工具的Network面板中查看请求的Referer头
- 确保Referer头包含正确的前端地址
3. **本地环境测试**
- 如果使用localhost只能在电脑上访问
- 如果需要在手机上扫码使用本机IP地址192.168.1.100
4. **检查网络**
- 确保手机和电脑在同一局域网内
- 确保防火墙没有阻止访问
## 修复完成
所有问题已修复完成:
- ✅ 删除快速生成功能
- ✅ 修复本地环境二维码URL生成确保使用前端地址不包含30081端口
- ✅ 完善扫码跳转功能

View File

@ -1,136 +0,0 @@
# 二维码功能修复说明
## 修复时间
2025年1月
## 修复内容
### 一、修复快速生成接口404问题
**问题**快速生成注册和登录二维码时前端返回404错误。
**原因分析**
1. Spring Boot路径映射可能存在冲突
2. 路径变量 `/{qrcodeId}` 可能优先匹配 `/generate/register``/generate/login`
**修复方案**
1. 将 `/generate/register``/generate/login` 方法移到其他 `/generate/*` 方法附近,保持一致性
2. 在 `@PostMapping` 注解中添加 `produces = "application/json;charset=UTF-8"`,明确指定响应类型
3. 确保 `/{qrcodeId}` 路径变量方法放在所有具体路径之后
**修复后的接口路径**
- `POST /psychology/qrcode/generate/register` - 快速生成注册二维码
- `POST /psychology/qrcode/generate/login` - 快速生成登录二维码
### 二、修复二维码URL生成问题
**问题**生成的二维码URL使用后端地址和端口`http://1.15.149.240:30081`但前端页面运行在80端口导致手机扫码无法访问。
**修复方案**
1. 优化 `buildServerAddress()` 方法,优先从 `Referer` 头提取前端地址
2. 如果检测到后端端口30081自动改为前端端口80不显示端口号
3. 生成的二维码URL现在指向前端页面而不是后端API
**修复后的URL格式**
- 本地开发:`http://localhost/psychology/qrcode/scan/xxx`
- 生产环境:`http://1.15.149.240/psychology/qrcode/scan/xxx`
### 三、完善扫码跳转逻辑
**功能说明**
1. **扫码接口**`GET /psychology/qrcode/scan/{qrcodeCode}`(公开接口,使用 `@Anonymous` 注解)
2. **跳转地址映射**
- `test`(测评)→ `/psychology/assessment/start?scaleId={id}``/psychology/questionnaire/start?questionnaireId={id}`
- `view_report`(查看报告)→ `/psychology/report/detail?reportId={id}``/psychology/report/detail?assessmentId={id}`
- `register`(注册)→ `/register`
- `login`(登录)→ `/login`
3. **前端扫码页面**`scan.vue`
- 自动解析URL和查询参数
- 使用Vue Router进行内部跳转
- 支持完整URL的外部跳转
- 提供错误提示和重试机制
## 测试步骤
### 1. 重启后端服务
**重要**:修改代码后必须重启后端服务才能生效。
### 2. 测试快速生成功能
1. 登录管理系统
2. 进入"二维码管理"页面
3. 点击"快速生成" → "注册二维码"
4. 应成功生成二维码并显示预览
5. 点击"快速生成" → "登录二维码"
6. 应成功生成二维码并显示预览
### 3. 测试二维码扫码功能
1. 使用手机扫描生成的二维码
2. 应能正常打开扫码页面(`/psychology/qrcode/scan/{qrcodeCode}`
3. 根据二维码类型自动跳转到对应页面:
- 注册二维码 → 跳转到注册页面
- 登录二维码 → 跳转到登录页面
- 测评二维码 → 跳转到测评开始页面
- 报告二维码 → 跳转到报告详情页面
### 4. 检查二维码URL
生成的二维码URL应该
- 使用前端地址80端口而不是后端地址30081端口
- 格式为:`http://{host}/psychology/qrcode/scan/{qrcodeCode}`
- 手机可以正常访问
## 关键代码位置
### 后端
- **Controller**`ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQrcodeController.java`
- `generateRegisterQrcode()` - 快速生成注册二维码
- `generateLoginQrcode()` - 快速生成登录二维码
- `scan()` - 扫码接口
- `buildServerAddress()` - 构建服务器地址
- `buildRedirectUrl()` - 构建跳转地址
- **Service**`ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyQrcodeServiceImpl.java`
- `generateQrcode()` - 生成二维码Base64
- `buildQrcodeContent()` - 构建二维码内容URL
### 前端
- **API**`xinli-ui/src/api/psychology/qrcode.js`
- `generateRegisterQrcode()` - 调用快速生成注册二维码接口
- `generateLoginQrcode()` - 调用快速生成登录二维码接口
- `scanQrcode()` - 调用扫码接口
- **页面**
- `xinli-ui/src/views/psychology/qrcode/index.vue` - 二维码管理页面
- `xinli-ui/src/views/psychology/qrcode/scan.vue` - 扫码页面
- **路由**`xinli-ui/src/router/index.js`
- `/psychology/qrcode/scan/:qrcodeCode` - 扫码页面路由
- `/register` - 注册页面路由
- `/login` - 登录页面路由
## 注意事项
1. **必须重启后端服务**:修改代码后必须重启后端服务才能生效
2. **权限检查**:快速生成接口需要 `psychology:qrcode:add` 权限
3. **扫码接口**:扫码接口使用 `@Anonymous` 注解,不需要登录即可访问
4. **URL生成**二维码URL会根据请求的 `Referer` 头自动识别前端地址
5. **端口配置**前端运行在80端口后端运行在30081端口
## 如果仍有问题
1. **检查后端日志**:查看是否有路径映射错误或权限错误
2. **检查浏览器控制台**查看实际请求的URL和错误信息
3. **检查网络请求**在浏览器开发者工具的Network面板中查看请求详情
4. **确认服务状态**确保后端服务已正确启动并运行在30081端口
5. **确认权限**:确保当前用户有 `psychology:qrcode:add` 权限
## 修复完成
所有二维码相关功能已修复完成,包括:
- ✅ 快速生成注册二维码
- ✅ 快速生成登录二维码
- ✅ 二维码URL生成指向前端页面
- ✅ 扫码跳转功能
- ✅ 测评二维码扫码
- ✅ 报告二维码扫码

View File

@ -1,186 +0,0 @@
# 二维码功能完善说明
## 完成时间
2025年1月
## 完善内容
### 一、后端功能增强
#### 1. 新增快速生成接口
- ✅ **生成注册二维码接口**: `POST /psychology/qrcode/generate/register`
- ✅ **生成登录二维码接口**: `POST /psychology/qrcode/generate/login`
#### 2. 优化扫码接口
- ✅ **增强跳转URL构建**: 添加 `buildFullRedirectUrl()` 方法自动构建完整的跳转URL包含协议、域名、端口
- ✅ **返回完整URL**: 扫码接口现在返回 `redirectUrl`(相对路径)和 `fullRedirectUrl`完整URL
#### 3. 跳转地址映射
已完善所有二维码类型的跳转地址:
| 二维码类型 | 目标类型 | 跳转地址 |
|----------|---------|---------|
| test测评 | scale量表 | `/psychology/assessment/start?scaleId={id}` |
| test测评 | questionnaire问卷 | `/psychology/questionnaire/start?questionnaireId={id}` |
| test测评 | 其他 | `/psychology/assessment/start` |
| view_report查看报告 | report报告 | `/psychology/report/detail?reportId={id}` |
| view_report查看报告 | assessment测评 | `/psychology/report/detail?assessmentId={id}` |
| view_report查看报告 | 其他 | `/psychology/report` |
| register注册 | - | `/register` |
| login登录 | - | `/login` |
---
### 二、前端功能增强
#### 1. 扫码页面优化 (`scan.vue`)
- ✅ **智能跳转**: 支持相对路径和完整URL的自动识别和跳转
- ✅ **用户提示**: 根据二维码类型显示不同的跳转提示信息
- ✅ **重试机制**: 如果自动跳转失败,提供手动重试链接
- ✅ **错误处理**: 完善的错误提示和异常处理
#### 2. 二维码管理页面增强 (`index.vue`)
- ✅ **快速生成按钮**: 添加"快速生成"下拉按钮
- 注册二维码:一键生成注册二维码
- 登录二维码:一键生成登录二维码
- ✅ **自动预览**: 生成二维码后自动显示预览对话框
- ✅ **用户体验优化**: 生成成功后自动刷新列表并显示二维码
#### 3. API接口完善 (`qrcode.js`)
- ✅ 添加 `generateRegisterQrcode()` - 生成注册二维码
- ✅ 添加 `generateLoginQrcode()` - 生成登录二维码
---
### 三、功能使用说明
#### 1. 生成二维码
**方式一:快速生成(推荐)**
1. 进入"心理测评" -> "二维码管理"
2. 点击"快速生成"下拉按钮
3. 选择"注册二维码"或"登录二维码"
4. 确认后自动生成并显示二维码
**方式二:手动创建**
1. 点击"新增"按钮
2. 选择二维码类型(测评/查看报告/注册/登录)
3. 根据类型选择目标(如量表、报告等)
4. 保存后生成二维码
#### 2. 扫码使用流程
**扫码测试流程**
1. 管理员生成量表测评二维码
2. 用户使用手机扫描二维码
3. 自动跳转到测评开始页面
4. 用户填写信息并开始测评
**扫码查看报告流程**
1. 管理员生成报告查看二维码
2. 用户使用手机扫描二维码
3. 自动跳转到报告详情页面
4. 用户查看测评报告
**扫码注册流程**
1. 管理员生成注册二维码
2. 新用户使用手机扫描二维码
3. 自动跳转到注册页面
4. 用户完成注册
**扫码登录流程**
1. 管理员生成登录二维码
2. 用户使用手机扫描二维码
3. 自动跳转到登录页面
4. 用户完成登录
---
### 四、技术实现细节
#### 1. 二维码URL构建
- **二维码内容**: `http://{serverAddress}:{port}{contextPath}/psychology/qrcode/scan/{qrcodeCode}`
- **扫码后处理**: 后端验证二维码有效性,返回跳转地址
- **前端跳转**: 根据返回的URL自动跳转到目标页面
#### 2. 扫码统计
- 每次扫码自动增加扫码次数
- 可在二维码管理页面查看扫码统计
#### 3. 二维码有效期
- 支持设置过期时间
- 过期后扫码会提示"二维码已过期"
---
### 五、文件清单
#### 后端文件
- `ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQrcodeController.java`
- 新增 `generateRegisterQrcode()` 方法
- 新增 `generateLoginQrcode()` 方法
- 新增 `buildFullRedirectUrl()` 方法
- 优化 `scan()` 方法返回完整URL
#### 前端文件
- `xinli-ui/src/views/psychology/qrcode/scan.vue`
- 优化跳转逻辑
- 添加重试机制
- 优化用户提示
- `xinli-ui/src/views/psychology/qrcode/index.vue`
- 添加快速生成按钮
- 添加生成注册/登录二维码方法
- `xinli-ui/src/api/psychology/qrcode.js`
- 添加 `generateRegisterQrcode()` API
- 添加 `generateLoginQrcode()` API
---
### 六、测试建议
#### 1. 功能测试
- [ ] 测试生成注册二维码并扫码跳转
- [ ] 测试生成登录二维码并扫码跳转
- [ ] 测试生成测评二维码并扫码跳转
- [ ] 测试生成报告查看二维码并扫码跳转
- [ ] 测试二维码过期功能
- [ ] 测试扫码次数统计
#### 2. 兼容性测试
- [ ] 测试不同手机浏览器的扫码功能
- [ ] 测试微信扫码
- [ ] 测试支付宝扫码
- [ ] 测试其他扫码APP
#### 3. 性能测试
- [ ] 测试大量并发扫码
- [ ] 测试二维码生成速度
---
### 七、注意事项
1. **服务器地址配置**: 二维码URL使用配置的服务器地址如果部署到不同环境需要修改 `application.yml` 中的 `server.address` 配置
2. **HTTPS支持**: 当前默认使用HTTP如果需要HTTPS需要修改 `PsyQrcodeServiceImpl.java` 中的协议设置
3. **移动端适配**: 确保所有跳转页面在移动端显示正常
4. **二维码有效期**: 建议为不同类型的二维码设置合理的有效期
---
### 八、后续优化建议
1. **短链接功能**: 如果二维码URL过长可以考虑集成短链接服务
2. **二维码美化**: 可以添加Logo、颜色等美化功能
3. **批量生成**: 支持批量生成二维码
4. **二维码统计报表**: 添加详细的扫码统计分析
5. **动态二维码**: 支持动态更新二维码内容
---
**完成状态**: ✅ 所有功能已完成并测试通过

View File

@ -1,229 +0,0 @@
# AI心理健康测评系统 - 使用指南总览
## 📚 文档导航
本系统为不同身份的用户提供了详细的使用指南,请根据您的身份选择对应的指南文档。
---
## 👥 用户身份说明
### 1. 系统管理员
**适用对象**
- 系统管理员
- 拥有完整系统管理权限的用户
**主要职责**
- 管理量表、用户、权限
- 配置预警规则
- 查看和管理测评报告
- 管理二维码
- 系统设置和维护
**对应指南**:👉 [系统管理员使用指南](./使用指南-系统管理员.md)
---
### 2. 普通用户/测评者
**适用对象**
- 已获得量表权限的注册用户
- 需要进行心理测评的用户
- 查看测评报告的用户
**主要功能**
- 进行心理量表测评
- 查看测评报告
- 查看历史记录
- 使用二维码扫描
- 管理个人档案
**对应指南**:👉 [普通用户使用指南](./使用指南-普通用户.md)
---
### 3. 注册用户
**适用对象**
- 新注册的用户
- 等待权限分配的用户
**主要流程**
- 注册账号
- 首次登录
- 等待权限分配
- 开始使用系统
**对应指南**:👉 [注册用户使用指南](./使用指南-注册用户.md)
---
## 📖 快速开始
### 我是系统管理员
1. 使用默认账号登录(用户名:`admin`,密码:`admin123`
2. 阅读 [系统管理员使用指南](./使用指南-系统管理员.md)
3. 开始配置系统和管理用户
### 我是新用户
1. 在登录页面点击"注册"
2. 填写注册信息完成注册
3. 阅读 [注册用户使用指南](./使用指南-注册用户.md)
4. 等待管理员分配权限
5. 获得权限后,阅读 [普通用户使用指南](./使用指南-普通用户.md)
6. 开始进行测评
### 我是已有权限的用户
1. 使用账号登录系统
2. 阅读 [普通用户使用指南](./使用指南-普通用户.md)
3. 开始进行测评
---
## 🎯 功能模块说明
### 量表管理
- **管理员**:创建、导入、配置量表
- **普通用户**:选择量表进行测评
### 测评管理
- **管理员**:查看和管理所有测评记录
- **普通用户**:查看自己的测评记录
### 报告管理
- **管理员**:查看、编辑、导出所有报告
- **普通用户**:查看自己的测评报告
### 权限管理
- **管理员**:为用户分配量表权限
- **普通用户**:查看自己的权限范围
### 问卷管理
- **管理员**:创建自定义问卷、主观题评分
- **普通用户**:参与问卷答题
### 预警管理
- **管理员**:配置预警规则、查看预警信息
- **普通用户**:不涉及
### 二维码管理
- **管理员**:生成和管理二维码
- **普通用户**:扫描二维码进行测评或查看报告
---
## 📋 文档结构
```
使用指南总览.md ← 您当前所在的位置
├── 使用指南-系统管理员.md ← 管理员完整使用指南
├── 使用指南-普通用户.md ← 普通用户完整使用指南
└── 使用指南-注册用户.md ← 注册用户指南
```
---
## 🔍 常见问题快速查找
### 关于注册
- 如何注册账号? → [注册用户使用指南](./使用指南-注册用户.md#注册流程)
- 注册后无法登录? → [注册用户使用指南](./使用指南-注册用户.md#常见问题)
### 关于测评
- 如何开始测评? → [普通用户使用指南](./使用指南-普通用户.md#开始测评)
- 如何答题? → [普通用户使用指南](./使用指南-普通用户.md#答题操作)
- 可以暂停测评吗? → [普通用户使用指南](./使用指南-普通用户.md#暂停和继续)
### 关于报告
- 如何查看报告? → [普通用户使用指南](./使用指南-普通用户.md#查看报告)
- 报告什么时候生成? → [普通用户使用指南](./使用指南-普通用户.md#常见问题)
### 关于权限
- 看不到量表怎么办? → [普通用户使用指南](./使用指南-普通用户.md#常见问题)
- 如何分配权限? → [系统管理员使用指南](./使用指南-系统管理员.md#权限管理)
### 关于量表
- 如何导入新量表? → [系统管理员使用指南](./使用指南-系统管理员.md#量表管理)
- 如何配置量表? → [系统管理员使用指南](./使用指南-系统管理员.md#配置量表)
---
## 📞 获取帮助
### 技术支持
如遇到问题,可以:
1. **查看文档**:阅读对应的使用指南
2. **查看常见问题**:每个指南都包含常见问题部分
3. **联系管理员**:联系系统管理员获取帮助
4. **查看系统日志**:管理员可以查看系统日志排查问题
### 文档更新
- 文档会随着系统更新而更新
- 建议定期查看最新版本
- 如有疑问,请联系技术支持
---
## ✅ 使用检查清单
### 新用户检查清单
- [ ] 已完成账号注册
- [ ] 已成功登录系统
- [ ] 已阅读注册用户使用指南
- [ ] 已联系管理员申请权限
- [ ] 已获得量表权限
- [ ] 已阅读普通用户使用指南
- [ ] 已准备好进行首次测评
### 管理员检查清单
- [ ] 已修改默认密码
- [ ] 已阅读系统管理员使用指南
- [ ] 已配置系统基础设置
- [ ] 已导入或创建量表
- [ ] 已为用户分配权限
- [ ] 已配置预警规则(如需要)
- [ ] 已测试系统功能
---
## 🎓 学习路径建议
### 对于新用户
1. **第一步**:注册账号 → [注册用户使用指南](./使用指南-注册用户.md)
2. **第二步**:等待权限分配
3. **第三步**:学习使用系统 → [普通用户使用指南](./使用指南-普通用户.md)
4. **第四步**:进行首次测评
5. **第五步**:查看测评报告
### 对于管理员
1. **第一步**:登录系统并修改密码
2. **第二步**:阅读管理员指南 → [系统管理员使用指南](./使用指南-系统管理员.md)
3. **第三步**:配置系统基础设置
4. **第四步**:导入或创建量表
5. **第五步**:为用户分配权限
6. **第六步**:测试系统功能
7. **第七步**:开始日常管理
---
## 📝 文档说明
- **最后更新**2025-01-XX
- **文档版本**v1.0
- **适用系统版本**AI心理健康测评系统 v1.0
---
**祝您使用愉快!** 🎉

View File

@ -1,375 +0,0 @@
# AI心理健康测评系统 - 普通用户使用指南
## 📋 目录
1. [系统概述](#系统概述)
2. [注册账号](#注册账号)
3. [登录系统](#登录系统)
4. [开始测评](#开始测评)
5. [答题操作](#答题操作)
6. [查看报告](#查看报告)
7. [查看历史记录](#查看历史记录)
8. [使用二维码](#使用二维码)
9. [用户档案](#用户档案)
10. [常见问题](#常见问题)
---
## 系统概述
AI心理健康测评系统是一个专业的心理测评平台您可以通过该系统进行各种心理量表测评查看测评报告了解自己的心理健康状况。
### 主要功能
- **心理测评**:进行各种心理量表测评
- **查看报告**:查看详细的测评报告和结果解释
- **历史记录**:查看历史测评记录
- **二维码扫描**:通过扫码快速开始测评
- **用户档案**:管理个人档案信息
---
## 注册账号
### 注册步骤
1. 打开浏览器,访问系统地址(如:`http://localhost:80`
2. 在登录页面,点击 **"注册"** 链接
3. 填写注册信息:
- **用户名**:用于登录的用户名(必填,唯一)
- **密码**登录密码必填建议8位以上
- **确认密码**:再次输入密码(必填)
- **手机号**:手机号码(可选)
- **邮箱**:邮箱地址(可选)
4. 输入验证码(如显示)
5. 点击 **"注册"** 按钮
6. 注册成功后,系统会提示注册成功
### 注册注意事项
- 用户名不能重复,如果提示用户名已存在,请更换用户名
- 密码建议包含字母和数字,提高安全性
- 注册后需要等待管理员分配量表权限才能进行测评
- 如果忘记密码,请联系管理员重置
---
## 登录系统
### 登录步骤
1. 打开浏览器,访问系统地址
2. 在登录页面输入:
- **用户名**:您的用户名
- **密码**:您的密码
- **验证码**:输入显示的验证码(如显示)
3. 点击 **"登录"** 按钮
4. 登录成功后进入系统首页
### 登录问题
- **忘记密码**:请联系管理员重置密码
- **账号被锁定**:联系管理员解锁账号
- **验证码看不清**:点击验证码图片刷新
---
## 开始测评
### 方式一:通过菜单开始
1. 登录系统后,点击左侧菜单 **"心理测评管理"** → **"测评管理"**
2. 点击 **"开始测评"** 按钮
3. 在量表选择页面,选择要进行的量表
4. 填写被测评人信息:
- **姓名**:被测评人姓名
- **性别**:男/女
- **年龄**:年龄
- **其他信息**:根据需要填写
5. 点击 **"开始测评"** 按钮
6. 进入答题页面
### 方式二:通过二维码扫描
1. 使用手机扫描管理员提供的测评二维码
2. 如果未登录,系统会提示先登录
3. 登录后自动跳转到测评开始页面
4. 填写被测评人信息
5. 开始答题
### 量表选择说明
- **权限限制**:您只能看到管理员为您分配权限的量表
- **量表信息**:每个量表显示名称、类型、题目数量、预计时间
- **无权限提示**:如果没有可用的量表,系统会提示联系管理员
---
## 答题操作
### 答题界面
答题页面包含以下元素:
- **量表信息**:显示量表名称和进度
- **题目区域**:显示当前题目和选项
- **进度条**:显示答题进度
- **导航按钮**:上一题、下一题、提交
### 答题步骤
1. **阅读题目**:仔细阅读题目内容
2. **选择答案**
- **单选题**:选择一个选项
- **多选题**:可以选择多个选项
3. **保存答案**:选择答案后,系统自动保存
4. **继续答题**
- 点击 **"下一题"** 继续
- 或点击 **"上一题"** 返回修改
5. **完成答题**
- 答完所有题目后,点击 **"提交"** 按钮
- 确认提交后,系统生成测评报告
### 答题技巧
- **认真阅读**:仔细阅读每个题目,理解题意
- **如实回答**:根据自己的真实情况回答,不要猜测
- **不要跳过**:尽量回答所有题目,确保结果准确
- **可以修改**:在提交前可以随时返回修改答案
- **注意时间**:某些量表有时间限制,注意答题时间
### 暂停和继续
如果无法一次性完成测评:
1. **暂停测评**
- 点击 **"暂停"** 按钮
- 系统保存当前进度
2. **继续测评**
- 下次登录后,进入 **"测评管理"**
- 找到状态为"进行中"的测评
- 点击 **"继续测评"** 按钮
- 系统自动跳转到上次的答题位置
---
## 查看报告
### 查看方式
#### 方式一:通过菜单查看
1. 登录系统后,点击 **"心理测评管理"** → **"测评报告"**
2. 在报告列表中,找到要查看的报告
3. 点击 **"查看"** 按钮
4. 查看报告详细内容
#### 方式二:通过测评记录查看
1. 进入 **"测评管理"**
2. 找到已完成的测评记录
3. 点击 **"查看报告"** 按钮
4. 查看报告
#### 方式三:通过二维码扫描
1. 扫描管理员提供的报告查看二维码
2. 如果未登录,系统会提示先登录
3. 登录后自动跳转到报告详情页面
### 报告内容
测评报告通常包含以下内容:
- **基本信息**
- 被测评人信息
- 测评时间
- 量表名称
- **测评结果**
- 总分
- 各因子得分(如有)
- 分数解释
- **结果分析**
- 结果等级(如:正常、轻度、中度、重度)
- 详细解释
- 因子分析(如有)
- **建议指导**
- 针对性的建议
- 改善方法
- 注意事项
### 报告说明
- **报告生成**:提交测评后,系统自动生成报告
- **报告准确性**:报告结果基于您的答题情况,请如实回答
- **仅供参考**:报告结果仅供参考,不能替代专业诊断
- **隐私保护**:您的测评数据受到隐私保护
---
## 查看历史记录
### 查看测评记录
1. 进入 **"心理测评管理"** → **"测评管理"**
2. 查看所有测评记录:
- **进行中**:尚未完成的测评
- **已完成**:已完成的测评
- **已暂停**:暂停的测评
3. 可以按条件搜索:
- 量表名称
- 被测评人
- 测评状态
- 创建时间
### 查看报告记录
1. 进入 **"心理测评管理"** → **"测评报告"**
2. 查看所有报告记录
3. 可以按条件搜索:
- 报告标题
- 量表名称
- 创建时间
### 对比分析
- 可以查看同一被测评人的多次测评记录
- 对比不同时间的测评结果
- 了解心理变化趋势
---
## 使用二维码
### 扫描测评二维码
1. 使用手机扫描管理员提供的测评二维码
2. 如果未登录,系统会提示先登录
3. 登录后自动跳转到测评开始页面
4. 填写被测评人信息
5. 开始答题
### 扫描报告二维码
1. 使用手机扫描管理员提供的报告查看二维码
2. 如果未登录,系统会提示先登录
3. 登录后自动跳转到报告详情页面
4. 查看报告内容
### 二维码使用说明
- **扫码工具**:可以使用微信、支付宝等应用的扫一扫功能
- **网络要求**:需要网络连接才能访问
- **权限要求**:需要登录并有相应权限
- **有效期**:二维码可能有过期时间,过期后无法使用
---
## 用户档案
### 查看个人档案
1. 进入 **"心理测评管理"** → **"用户档案"**
2. 查看个人档案信息
3. 可以查看:
- 基本信息
- 测评记录
- 档案历史
### 编辑个人档案
1. 在档案页面,点击 **"编辑"** 按钮
2. 修改档案信息(如允许)
3. 点击 **"确定"** 保存
### 档案说明
- **档案内容**:由管理员配置,可能包含自定义字段
- **隐私保护**:档案信息受到隐私保护
- **权限限制**:某些字段可能无法自行修改
---
## 常见问题
### Q1: 注册后无法看到量表?
**A**: 注册后需要等待管理员为您分配量表权限。请联系管理员分配权限。
### Q2: 忘记密码怎么办?
**A**: 请联系管理员重置密码。管理员可以在用户管理中重置您的密码。
### Q3: 答题过程中可以暂停吗?
**A**: 可以。点击"暂停"按钮,系统会保存当前进度。下次登录后可以继续完成测评。
### Q4: 可以修改已提交的答案吗?
**A**: 不可以。提交后无法修改答案。请在提交前仔细检查。
### Q5: 报告什么时候生成?
**A**: 提交测评后,系统会自动生成报告。通常几秒钟内完成。
### Q6: 可以查看历史测评记录吗?
**A**: 可以。在"测评管理"中可以查看所有历史测评记录。
### Q7: 报告结果准确吗?
**A**: 报告结果基于您的答题情况。请如实回答题目,确保结果准确。但报告结果仅供参考,不能替代专业诊断。
### Q8: 可以导出报告吗?
**A**: 普通用户无法导出报告。如需导出,请联系管理员。
### Q9: 二维码扫描后无法访问?
**A**: 检查以下几点:
- 是否已登录
- 是否有相应权限
- 二维码是否过期
- 网络连接是否正常
### Q10: 如何联系管理员?
**A**: 请联系系统管理员或技术支持人员。
---
## 使用提示
### 测评前
- ✅ 确保网络连接正常
- ✅ 选择一个安静的环境
- ✅ 预留足够的答题时间
- ✅ 准备好被测评人的基本信息
### 测评中
- ✅ 认真阅读每个题目
- ✅ 根据自己的真实情况回答
- ✅ 不要猜测或随意选择
- ✅ 注意答题时间
### 测评后
- ✅ 查看生成的报告
- ✅ 理解报告内容
- ✅ 如有疑问,咨询专业人员
- ✅ 保存报告信息(如需要)
---
## 隐私说明
- 您的测评数据受到严格保护
- 只有管理员和您本人可以查看您的测评记录
- 系统不会向第三方泄露您的个人信息
- 请妥善保管您的账号密码
---
**最后更新**2025-01-XX

View File

@ -1,264 +0,0 @@
# AI心理健康测评系统 - 注册用户使用指南
## 📋 目录
1. [注册流程](#注册流程)
2. [首次登录](#首次登录)
3. [等待权限分配](#等待权限分配)
4. [开始使用](#开始使用)
5. [常见问题](#常见问题)
---
## 注册流程
### 访问注册页面
1. 打开浏览器,访问系统地址(如:`http://localhost:80`
2. 在登录页面,点击 **"注册"** 链接或按钮
3. 进入注册页面
### 填写注册信息
在注册页面填写以下信息:
#### 必填信息
- **用户名**
- 用于登录的用户名
- 必须唯一,不能与其他用户重复
- 建议使用字母、数字组合
- 长度建议3-20个字符
- **密码**
- 登录密码
- 建议8位以上
- 建议包含字母和数字
- 不要使用过于简单的密码123456
- **确认密码**
- 再次输入密码
- 必须与密码一致
#### 可选信息
- **手机号**:手机号码(用于找回密码等)
- **邮箱**:邮箱地址(用于接收通知等)
### 完成注册
1. 输入验证码(如显示)
2. 阅读并同意用户协议(如有)
3. 点击 **"注册"** 按钮
4. 系统验证信息:
- 如果用户名已存在,会提示"用户名已存在"
- 如果密码不一致,会提示"两次密码不一致"
- 如果验证码错误,会提示"验证码错误"
5. 注册成功后,系统会显示"注册成功"提示
---
## 首次登录
### 登录步骤
1. 注册成功后,返回登录页面
2. 使用注册的用户名和密码登录
3. 首次登录后,您可能会看到:
- 欢迎页面
- 系统提示(如:等待管理员分配权限)
- 空白的量表列表(如果还没有权限)
### 登录后状态
注册用户首次登录后,通常处于以下状态:
- ✅ **账号已激活**:可以正常登录系统
- ⏳ **等待权限**:需要等待管理员分配量表权限
- 📋 **功能受限**:可能无法看到量表或进行测评
---
## 等待权限分配
### 为什么需要等待?
注册用户需要管理员分配量表访问权限后才能进行测评。这是为了:
- **权限控制**:确保用户只能访问被授权的量表
- **数据安全**:保护测评数据的安全
- **管理规范**:便于管理员统一管理
### 如何知道权限已分配?
权限分配后,您可以通过以下方式确认:
1. **刷新页面**:登录后刷新浏览器页面
2. **查看量表列表**:进入"测评管理",如果能看到量表列表,说明已有权限
3. **联系管理员**:如果长时间没有权限,可以联系管理员确认
### 权限说明
管理员可以为您分配:
- **量表权限**:指定您可以访问哪些量表
- **时间范围**:权限的有效期(开始时间和结束时间)
- **权限类型**:按用户、角色或部门分配
---
## 开始使用
### 获得权限后
当管理员为您分配权限后,您可以:
1. **查看可用量表**
- 进入"心理测评管理" → "测评管理"
- 点击"开始测评"
- 查看可用的量表列表
2. **开始测评**
- 选择要进行的量表
- 填写被测评人信息
- 开始答题
3. **查看报告**
- 完成测评后查看报告
- 查看历史测评记录
### 使用流程
完整的测评流程:
```
注册账号
登录系统
等待权限分配
选择量表
填写被测评人信息
开始答题
提交测评
查看报告
```
---
## 常见问题
### Q1: 注册时提示"用户名已存在"
**A**: 该用户名已被其他用户使用。请更换一个不同的用户名。
**建议**
- 使用字母+数字组合
- 添加下划线或连字符
- 使用邮箱前缀作为用户名
### Q2: 注册时提示"密码不一致"
**A**: "密码"和"确认密码"输入不一致。请重新输入,确保两次输入的密码完全相同。
### Q3: 注册后无法登录?
**A**: 检查以下几点:
- 用户名和密码是否正确
- 是否输入了正确的验证码
- 账号是否被管理员停用
- 联系管理员确认账号状态
### Q4: 登录后看不到量表?
**A**: 这是正常情况。注册用户需要等待管理员分配量表权限。请:
- 等待管理员分配权限
- 或联系管理员申请权限
### Q5: 如何联系管理员?
**A**: 请联系系统管理员或技术支持人员,告知您的用户名和需求。
### Q6: 忘记密码怎么办?
**A**: 请联系管理员重置密码。管理员可以在用户管理中重置您的密码。
### Q7: 可以修改注册信息吗?
**A**: 部分信息可以修改:
- 登录后进入"个人中心"(如有)
- 或联系管理员修改
### Q8: 注册后多久可以获得权限?
**A**: 这取决于管理员的工作安排。通常:
- 管理员会定期审核新注册用户
- 或根据申请及时分配权限
- 建议主动联系管理员申请
### Q9: 可以注册多个账号吗?
**A**: 可以,但需要:
- 使用不同的用户名
- 每个账号独立管理
- 遵守系统使用规范
### Q10: 注册信息会被泄露吗?
**A**: 不会。系统严格保护用户隐私:
- 注册信息受到加密保护
- 不会向第三方泄露
- 只有管理员可以查看(用于权限管理)
---
## 注册注意事项
### 用户名选择
- ✅ 使用易记的用户名
- ✅ 避免使用特殊字符
- ✅ 不要使用真实姓名(保护隐私)
- ❌ 不要使用过于简单的用户名abc、123
### 密码设置
- ✅ 使用8位以上的密码
- ✅ 包含字母和数字
- ✅ 定期更换密码
- ❌ 不要使用过于简单的密码
- ❌ 不要使用个人信息作为密码
### 信息填写
- ✅ 如实填写信息(如手机号、邮箱)
- ✅ 信息用于账号管理和找回密码
- ⚠️ 注意保护个人隐私
---
## 下一步
注册成功后,建议您:
1. **保存账号信息**:记录用户名和密码,妥善保管
2. **联系管理员**:主动联系管理员申请量表权限
3. **了解系统**:阅读《普通用户使用指南》,了解系统功能
4. **准备测评**:获得权限后,准备好进行测评
---
## 相关文档
- [普通用户使用指南](./使用指南-普通用户.md) - 详细的使用说明
- [系统管理员使用指南](./使用指南-系统管理员.md) - 管理员功能说明
---
**最后更新**2025-01-XX

View File

@ -1,650 +0,0 @@
# AI心理健康测评系统 - 系统管理员使用指南
## 📋 目录
1. [系统概述](#系统概述)
2. [登录系统](#登录系统)
3. [量表管理](#量表管理)
4. [用户管理](#用户管理)
5. [权限管理](#权限管理)
6. [测评管理](#测评管理)
7. [报告管理](#报告管理)
8. [问卷管理](#问卷管理)
9. [预警管理](#预警管理)
10. [二维码管理](#二维码管理)
11. [用户档案管理](#用户档案管理)
12. [心理网站管理](#心理网站管理)
13. [系统设置](#系统设置)
14. [数据导出](#数据导出)
15. [常见问题](#常见问题)
---
## 系统概述
AI心理健康测评系统是一个基于Web的心理测评管理平台支持量表测评、自定义问卷、用户档案管理、预警管理等功能。
### 主要功能模块
- **量表管理**:创建、导入、配置心理量表
- **用户管理**:管理用户账号、分配权限
- **测评管理**:查看和管理测评记录
- **报告管理**:查看、编辑、导出测评报告
- **问卷管理**:创建自定义问卷、主观题评分
- **预警管理**:配置预警规则、查看预警信息
- **二维码管理**:生成和管理测评二维码
- **用户档案**:管理用户档案信息
- **心理网站**:管理心理教育网站内容
---
## 登录系统
### 访问系统
1. 打开浏览器,访问系统地址(如:`http://localhost:80`
2. 进入登录页面
### 默认管理员账号
- **用户名**`admin`
- **密码**`admin123`
> ⚠️ **安全提示**:首次登录后请立即修改密码!
### 登录步骤
1. 输入用户名和密码
2. 输入验证码(如显示)
3. 点击"登录"按钮
4. 登录成功后进入系统首页
---
## 量表管理
### 功能说明
量表管理是系统的核心功能,管理员可以创建、导入、配置和管理心理量表。
### 创建新量表
#### 方式一:手动创建
1. 进入 **心理测评管理** → **量表管理**
2. 点击 **"新增"** 按钮
3. 填写量表基本信息:
- **量表编码**:唯一标识(如:`SCL_90`
- **量表名称**:量表完整名称
- **量表类型**:选择类型(情绪、人格、行为等)
- **量表简介**:简短介绍
- **量表描述**:详细描述
- **题目数量**:预计题目数
- **预计完成时间**:分钟数
- **适用人群**:如"一般人群"、"青少年"等
- **作者**:量表作者
- **来源**:量表来源
- **状态**:选择"正常"(启用)
4. 点击 **"确定"** 保存
#### 方式二JSON格式导入
1. 进入 **量表管理** 页面
2. 点击 **"导入"** 按钮
3. 选择JSON格式的量表文件
4. 系统自动解析并导入量表数据
5. 检查导入结果,确认无误
> 📖 **详细说明**:参考《新量表导入完整操作指南.md》
### 配置量表
创建量表后,需要完成以下配置:
#### 1. 添加题目
1. 在量表列表中,点击 **"题目管理"** 按钮
2. 点击 **"新增"** 添加题目
3. 填写题目信息:
- **题目序号**:如 1、2、3...
- **题目内容**:题目的完整文字
- **题目类型**:单选题/多选题
- **是否必答**:是/否
4. 保存题目
#### 2. 配置选项
1. 在题目列表中,点击 **"选项管理"** 按钮
2. 为每个题目添加选项:
- **选项编码**:如 A、B、C、D
- **选项内容**:选项文字
- **选项分数**:该选项对应的分数
3. 保存选项
#### 3. 添加因子(如需要)
1. 在量表列表中,点击 **"因子管理"** 按钮
2. 点击 **"新增"** 添加因子
3. 填写因子信息:
- **因子编码**:如 F1、F2
- **因子名称**:如"躯体化因子"
- **因子描述**:详细说明
4. 保存因子
#### 4. 配置计分规则(如需要)
1. 在因子列表中,点击 **"计分规则"** 按钮
2. 为每个因子配置计分规则:
- 选择题目
- 选择选项(可选)
- 设置权重
- 选择计算方式(求和/平均/最大/最小)
3. 保存规则
#### 5. 配置结果解释
1. 进入 **心理测评管理** → **解释配置**
2. 点击 **"新增"** 添加解释
3. 填写解释信息:
- **量表**:选择量表
- **因子**:选择因子(可选)
- **分数下限/上限**:分数范围
- **等级**:如"低"、"中"、"高"
- **解释标题**:解释标题
- **解释内容**:详细解释
- **建议指导**:建议和指导
4. 保存解释
### 管理量表
- **编辑量表**:点击 **"修改"** 按钮
- **删除量表**:点击 **"删除"** 按钮(需谨慎)
- **查看详情**:点击量表名称
- **导出量表**:勾选量表后点击 **"导出"** 按钮
---
## 用户管理
### 功能说明
用户管理用于管理系统用户账号,包括创建用户、分配角色、设置权限等。
### 查看用户列表
1. 进入 **系统管理** → **用户管理**
2. 查看用户列表,可以按条件搜索:
- 用户名
- 手机号
- 状态
- 创建时间
### 创建新用户
1. 在用户管理页面,点击 **"新增"** 按钮
2. 填写用户信息:
- **用户名**:登录用户名
- **昵称**:显示名称
- **邮箱**:邮箱地址
- **手机号**:手机号码
- **性别**:男/女
- **密码**:初始密码
- **部门**:所属部门
- **角色**:选择角色
- **状态**:正常/停用
3. 点击 **"确定"** 保存
### 管理用户
- **编辑用户**:点击 **"修改"** 按钮
- **删除用户**:点击 **"删除"** 按钮
- **重置密码**:点击 **"重置密码"** 按钮
- **分配角色**:点击 **"更多"** → **"分配角色"**
- **分配量表权限**:点击 **"更多"** → **"分配量表权限"**
### 批量操作
1. 勾选多个用户
2. 点击 **"批量删除"** 或 **"批量导出"**
---
## 权限管理
### 功能说明
权限管理用于控制用户对量表的访问权限,支持按用户、角色、部门分配权限。
### 量表权限管理
1. 进入 **心理测评管理** → **量表权限管理**
2. 查看权限列表,可以按条件筛选:
- 量表名称
- 用户名称
- 部门
- 状态
### 分配权限
#### 方式一:通过权限管理页面
1. 在权限管理页面,点击 **"新增"** 按钮
2. 填写权限信息:
- **量表**:选择量表
- **用户**:选择用户(可选)
- **角色**:选择角色(可选)
- **部门**:选择部门(可选)
- **开始时间**:权限生效时间
- **结束时间**:权限失效时间
- **状态**:有效/无效
3. 点击 **"确定"** 保存
#### 方式二:通过用户管理页面
1. 进入 **系统管理** → **用户管理**
2. 找到要分配权限的用户
3. 点击 **"更多"** → **"分配量表权限"**
4. 在权限分配页面,选择量表并设置时间范围
5. 点击 **"确定"** 保存
### 权限规则
- **管理员**userId = 1自动拥有所有量表的访问权限
- **用户直接权限**:优先级最高
- **角色权限**:次优先级
- **部门权限**:再次优先级
- **全局权限**:最低优先级(所有用户)
---
## 测评管理
### 功能说明
测评管理用于查看和管理所有测评记录,包括进行中的测评和已完成的测评。
### 查看测评列表
1. 进入 **心理测评管理** → **测评管理**
2. 查看测评列表,可以按条件搜索:
- 量表名称
- 被测评人
- 测评状态
- 创建时间
### 测评状态
- **进行中**:测评尚未完成
- **已完成**:测评已完成并生成报告
- **已暂停**:测评被暂停(支持继续测评)
### 管理测评
- **查看详情**:点击测评记录
- **查看报告**:点击 **"查看报告"** 按钮
- **删除测评**:点击 **"删除"** 按钮
- **继续测评**:对于暂停的测评,可以继续完成
### 管理员快速填充(测试功能)
> ⚠️ **注意**:此功能仅用于测试,不建议用于真实测评数据。
1. 进入测评页面
2. 点击 **"快速填充"** 下拉按钮
3. 选择填充策略:
- 填充第一个选项并提交
- 填充中间选项并提交
- 填充最后一个选项并提交
- 随机填充并提交
4. 确认后系统自动填充并提交测评
---
## 报告管理
### 功能说明
报告管理用于查看、编辑、导出测评报告。
### 查看报告列表
1. 进入 **心理测评管理** → **测评报告**
2. 查看报告列表,可以按条件搜索:
- 报告标题
- 被测评人
- 量表名称
- 生成状态
- 创建时间
### 查看报告详情
1. 在报告列表中,点击 **"查看"** 按钮
2. 查看报告详细内容:
- 基本信息
- 测评结果
- 因子分析
- 结果解释
- 建议指导
### 编辑报告
1. 在报告列表中,点击 **"编辑"** 按钮
2. 修改报告内容:
- 解释标题
- 解释内容
- 建议指导
3. 点击 **"确定"** 保存
### 导出报告
1. 勾选需要导出的报告(可多选)
2. 点击 **"导出"** 按钮
3. 系统生成Excel文件并下载
---
## 问卷管理
### 功能说明
问卷管理用于创建自定义问卷,支持多种题型,适合在线考试、练习等场景。
### 创建问卷
1. 进入 **心理测评管理** → **问卷管理**
2. 点击 **"新增"** 按钮
3. 填写问卷信息:
- **问卷名称**:问卷标题
- **问卷描述**:问卷说明
- **问卷类型**:选择类型
- **状态**:正常/停用
4. 点击 **"确定"** 保存
### 添加题目
1. 在问卷列表中,点击 **"题目管理"** 按钮
2. 点击 **"新增"** 添加题目
3. 选择题目类型:
- **单选题**:只有一个正确答案
- **多选题**:可以有多个正确答案
- **判断题**:对/错
- **填空题**:文本输入
- **简答题**:短文本回答
- **问答题**:长文本回答
- **作文题**:长文本回答
4. 填写题目信息:
- **题目内容**:题目文字
- **题目分值**:该题分数
- **是否必答**:是/否
- **正确答案**:客观题的正确答案
5. 保存题目
### 主观题评分
1. 进入 **心理测评管理** → **主观题评分**
2. 查看待评分题目列表
3. 点击 **"评分"** 按钮
4. 输入得分和评语(可选)
5. 点击 **"确定"** 保存
### 批量评分
1. 勾选多个待评分题目
2. 点击 **"批量评分"** 按钮
3. 为每个题目输入得分和评语
4. 点击 **"确定"** 批量保存
---
## 预警管理
### 功能说明
预警管理用于配置预警规则,当测评结果异常时自动向管理员发出警告。
### 配置预警规则
1. 进入 **心理测评管理** → **预警规则配置**
2. 点击 **"新增"** 按钮
3. 填写预警规则信息:
- **量表**:选择量表
- **因子**:选择因子(可选)
- **规则名称**:如"重度抑郁预警"
- **预警等级**:低/中/高/紧急
- **分数下限/上限**:触发预警的分数范围
- **百分位下限/上限**:触发预警的百分位范围
- **自动解除**:是否自动解除预警
- **解除条件**:自动解除的条件
- **状态**:正常/停用
4. 点击 **"确定"** 保存
### 查看预警信息
1. 进入 **心理测评管理** → **预警管理**
2. 查看预警列表,可以按条件筛选:
- 预警等级
- 量表名称
- 被测评人
- 预警状态
- 创建时间
### 处理预警
- **查看详情**:点击预警记录查看详细信息
- **解除预警**:手动解除预警
- **导出预警**:导出预警数据
---
## 二维码管理
### 功能说明
二维码管理用于生成和管理测评二维码,方便用户通过扫码快速访问测评和查看报告。
### 生成量表测评二维码
1. 进入 **心理测评管理** → **量表管理**
2. 找到目标量表
3. 点击 **"二维码"** 按钮
4. 系统生成二维码并显示
5. 可以下载二维码图片或直接打印
### 生成报告查看二维码
1. 进入 **心理测评管理** → **测评报告**
2. 找到目标报告
3. 点击 **"二维码"** 按钮
4. 系统生成二维码
### 管理二维码
1. 进入 **心理测评管理** → **二维码管理**
2. 查看所有二维码:
- 二维码类型
- 目标信息
- 扫码次数
- 状态
- 过期时间
3. 可以重新生成或删除二维码
---
## 用户档案管理
### 功能说明
用户档案管理用于管理用户的档案信息,支持自定义档案项目。
### 查看用户档案
1. 进入 **心理测评管理** → **用户档案**
2. 查看用户档案列表
3. 可以按条件搜索用户
### 编辑用户档案
1. 在档案列表中,点击 **"编辑"** 按钮
2. 修改档案信息:
- 基本信息
- 自定义字段
3. 点击 **"确定"** 保存
### 自定义档案项目
1. 进入 **系统管理****参数设置**(如支持)
2. 配置自定义档案字段
3. 保存配置
---
## 心理网站管理
### 功能说明
心理网站管理用于管理心理教育网站的内容,包括文章、分类、评论等。
### 管理文章
1. 进入 **心理测评管理****心理网站** → **内容管理**
2. 查看文章列表
3. 可以新增、编辑、删除文章
### 管理分类
1. 进入 **心理测评管理****心理网站** → **分类管理**
2. 管理文章分类
### 管理评论
1. 进入 **心理测评管理****心理网站** → **评论管理**
2. 查看和管理用户评论
---
## 系统设置
### 角色管理
1. 进入 **系统管理** → **角色管理**
2. 查看和管理系统角色
3. 为角色分配菜单权限和按钮权限
### 菜单管理
1. 进入 **系统管理** → **菜单管理**
2. 查看和管理系统菜单
3. 可以新增、编辑、删除菜单
### 参数设置
1. 进入 **系统管理** → **参数设置**
2. 配置系统参数
### 字典管理
1. 进入 **系统管理** → **字典管理**
2. 管理系统字典数据
---
## 数据导出
### 量表导出
1. 进入 **心理测评管理** → **量表管理**
2. 勾选需要导出的量表(可多选)
3. 点击 **"导出"** 按钮
4. 系统生成JSON格式文件并下载
### 报告导出
1. 进入 **心理测评管理** → **测评报告**
2. 勾选需要导出的报告(可多选)
3. 点击 **"导出"** 按钮
4. 系统生成Excel格式文件并下载
### 数据备份
1. 进入 **系统管理****数据备份**(如支持)
2. 执行数据备份操作
---
## 常见问题
### Q1: 如何导入新量表?
**A**: 有两种方式:
1. **手动创建**:在量表管理中逐个添加题目和选项
2. **JSON导入**准备符合格式的JSON文件使用导入功能
详细说明请参考《新量表导入完整操作指南.md》
### Q2: 用户看不到量表怎么办?
**A**: 检查以下几点:
1. 量表状态是否为"正常"
2. 用户是否有量表访问权限
3. 权限是否在有效期内
4. 刷新浏览器页面
### Q3: 如何配置预警规则?
**A**:
1. 进入"预警规则配置"
2. 选择量表和因子
3. 设置分数范围或百分位范围
4. 选择预警等级
5. 保存规则
### Q4: 二维码无法访问怎么办?
**A**: 检查以下几点:
1. 二维码是否过期
2. 二维码状态是否为"有效"
3. 网络连接是否正常
4. 系统URL配置是否正确
### Q5: 如何批量分配用户权限?
**A**:
1. 进入"量表权限管理"
2. 点击"新增"
3. 选择量表
4. 选择角色或部门(不选择用户表示批量分配)
5. 设置时间范围
6. 保存
### Q6: 报告生成失败怎么办?
**A**: 检查以下几点:
1. 量表是否配置了结果解释
2. 因子计分规则是否正确
3. 测评是否已完成
4. 查看系统日志错误信息
### Q7: 如何修改报告内容?
**A**:
1. 进入"测评报告"
2. 找到目标报告
3. 点击"编辑"按钮
4. 修改解释内容或建议指导
5. 保存
---
## 技术支持
如遇到问题,请:
1. 查看系统日志
2. 检查浏览器控制台错误
3. 参考相关文档
4. 联系技术支持
---
**最后更新**2025-01-XX

View File

@ -1,182 +0,0 @@
# 用户导入504超时错误修复说明
## 问题描述
在导入用户档案数据时,出现 **504 Gateway Time-out** 错误。这是因为:
1. 数据量较大导入处理时间超过了网关Nginx的超时限制
2. 同步处理导致HTTP请求长时间等待响应
## 修复方案
采用**异步导入**方式将耗时的导入操作放到后台线程执行HTTP请求立即返回通过进度轮询查看导入状态。
## 修改内容
### 1. Controller层修改 ✅
**文件**: `ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyUserProfileController.java`
**修改内容**:
- 修改 `importData` 方法,调用异步导入方法 `importProfileAsync`
- 立即返回响应,告知前端导入任务已启动
- 前端通过 `/importProgress` 接口轮询获取进度
```java
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
{
ExcelUtil<PsyUserProfile> util = new ExcelUtil<PsyUserProfile>(PsyUserProfile.class);
List<PsyUserProfile> profileList = util.importExcel(file.getInputStream());
String operName = getUsername();
// 异步执行导入任务,立即返回
profileService.importProfileAsync(profileList, updateSupport, operName);
// 立即返回响应,告知前端导入任务已启动
return success("导入任务已启动,共 " + profileList.size() + " 条数据。请稍候查看导入进度...");
}
```
### 2. Service接口层修改 ✅
**文件**: `ry-xinli-system/src/main/java/com/ddnai/system/service/psychology/IPsyUserProfileService.java`
**修改内容**:
- 添加异步导入方法接口 `importProfileAsync`
```java
/**
* 异步导入用户档案数据(避免超时)
*/
public void importProfileAsync(List<PsyUserProfile> profileList, Boolean isUpdateSupport, String operName);
```
### 3. Service实现层修改 ✅
**文件**: `ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyUserProfileServiceImpl.java`
**修改内容**:
- 添加 `@Async` 注解导入
- 实现异步导入方法,使用 `@Async` 注解标记
```java
import org.springframework.scheduling.annotation.Async;
@Override
@Async
public void importProfileAsync(List<PsyUserProfile> profileList, Boolean isUpdateSupport, String operName)
{
try
{
log.info("异步导入任务开始,操作人:{},数据量:{}", operName, profileList.size());
// 调用同步导入方法执行实际导入逻辑
importProfile(profileList, isUpdateSupport, operName);
log.info("异步导入任务完成,操作人:{}", operName);
}
catch (Exception e)
{
log.error("异步导入任务失败,操作人:{},错误:{}", operName, e.getMessage(), e);
}
}
```
### 4. 启动类修改 ✅
**文件**: `ry-xinli-admin/src/main/java/com/ddnai/XinliApplication.java`
**修改内容**:
- 添加 `@EnableAsync` 注解启用异步支持
- 配置异步任务线程池
```java
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@EnableAsync
public class XinliApplication
{
@Bean(name = "taskExecutor")
public Executor taskExecutor()
{
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 核心线程数
executor.setMaxPoolSize(10); // 最大线程数
executor.setQueueCapacity(100); // 队列容量
executor.setThreadNamePrefix("async-import-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}
```
### 5. 配置文件修改 ✅
**文件**: `ry-xinli-admin/src/main/resources/application.yml`
**修改内容**:
- 增加MVC异步请求超时配置作为保险
```yaml
spring:
mvc:
async:
# 异步请求超时时间毫秒设置为10分钟
request-timeout: 600000
```
## 工作原理
1. **文件上传**: 用户上传Excel文件
2. **解析数据**: Controller解析Excel文件为对象列表
3. **启动异步任务**: 调用 `importProfileAsync` 方法Spring会在独立线程中执行
4. **立即返回**: HTTP请求立即返回成功响应
5. **进度管理**: 后台线程执行导入,通过 `ImportProgressManager` 记录进度
6. **前端轮询**: 前端通过 `/importProgress` 接口定期查询导入进度
7. **完成通知**: 导入完成后,前端获取到最终结果并显示
## 优势
**避免超时**: HTTP请求立即返回不会因导入时间长而超时
**用户体验好**: 前端实时显示导入进度
**不阻塞服务器**: 导入任务在独立线程池中执行
**支持大批量**: 可以处理大量数据而不影响其他请求
**向后兼容**: 保留了同步导入方法,不影响其他功能
## 注意事项
⚠️ **重启应用**: 修改完成后需要重启Spring Boot应用才能生效
⚠️ **数据库连接**: 异步线程需要数据库连接,确保连接池配置足够
⚠️ **事务管理**: 异步方法中的事务与主线程独立,需注意事务边界
⚠️ **线程池监控**: 生产环境建议监控线程池状态,避免资源耗尽
## 如何测试
1. 重启Spring Boot应用
2. 准备一个包含较多数据的Excel文件建议500+条)
3. 在用户档案管理页面点击"导入"
4. 上传文件后,应该立即收到"导入任务已启动"的提示
5. 观察进度条实时更新
6. 等待导入完成,查看结果统计
## 进一步优化建议(可选)
如果仍然遇到问题,可以考虑:
1. **增加Nginx超时配置** (如果使用了Nginx反向代理):
```nginx
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
```
2. **优化批量插入性能**:
- 已实现分批处理每批500条
- 可以根据实际情况调整批次大小
3. **数据库优化**:
- 确保相关字段有索引
- 检查批量插入SQL性能
---
**修复完成日期**: 2025-12-01
**修复人员**: Cascade AI
**影响范围**: 用户档案导入功能
**风险评估**: 低(仅改变执行方式,核心逻辑未变)

View File

@ -1,431 +0,0 @@
# 🎯 内网环境ANR问题彻底解决方案
## 问题诊断
### 原始问题
- App在内网环境下朗读按钮显示**灰色**disabled
- 点击无反应,无法使用朗读功能
- 一段时间后出现**ANR**Application Not Responding
- 应用被系统强制关闭
### 根本原因
1. **前端检测逻辑不完整**:只检查 `window.AndroidTTS` 是否存在,未调用 `isAvailable()` 方法
2. **旧实现依赖外网**使用百度在线TTS API (`https://fanyi.baidu.com/gettts`)
3. **内网无法访问外网**:网络请求超时导致主线程阻塞
4. **导致ANR**:长时间等待响应,系统判定应用无响应
---
## ✅ 解决方案
### 核心改进
1. **使用Android原生TextToSpeech引擎**:完全离线,不需要网络
2. **修改前端检测逻辑**:正确调用 `isAvailable()` 方法检查TTS状态
3. **添加延迟重试机制**处理TTS异步初始化的时序问题
4. **自动降级策略**如果原生TTS不可用自动切换到浏览器TTS
---
## 📝 已修改的文件
### Android代码
#### 1. `TtsHelper.java` - 使用原生TTS引擎
**文件位置**`xinli-App/app/src/main/java/com/xinli/app/TtsHelper.java`
**核心改动**
```java
// 使用Android原生TextToSpeech引擎
private TextToSpeech tts;
private void initTts() {
tts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
// 设置中文语言
int result = tts.setLanguage(Locale.CHINESE);
isReady = true;
Log.i(TAG, "✅ Android原生TTS初始化成功离线");
}
}
});
}
@JavascriptInterface
public void speak(String text) {
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, utteranceId);
}
```
**特性**
- ✅ 完全离线工作
- ✅ 使用系统内置TTS引擎
- ✅ 支持中文朗读
- ✅ 无网络超时风险
- ✅ 不会导致ANR
### 前端代码
#### 2. `assessment/taking.vue` - 测评答题页面
**文件位置**`xinli-ui/src/views/psychology/assessment/taking.vue`
**核心改动**
```javascript
initTts() {
// 检测 Android App 环境
if (window.AndroidTTS && typeof window.AndroidTTS.isAvailable === 'function') {
// 调用 isAvailable() 方法检查 TTS 是否真的可用
if (window.AndroidTTS.isAvailable()) {
this.isTtsSupported = true;
this.useAndroidTts = true;
console.log('✅ 使用Android原生TTS');
return;
} else {
// TTS 可能还在初始化中,延迟重试
console.log('⏳ Android TTS尚未就绪延迟检查...');
setTimeout(() => {
if (window.AndroidTTS && window.AndroidTTS.isAvailable()) {
this.isTtsSupported = true;
this.useAndroidTts = true;
console.log('✅ Android TTS 已就绪(延迟检测)');
} else {
this.fallbackToBrowserTts();
}
}, 500);
// 先启用按钮,避免用户等待
this.isTtsSupported = true;
return;
}
}
// 浏览器环境
this.fallbackToBrowserTts();
}
```
**特性**
- ✅ 调用 `isAvailable()` 验证TTS可用性
- ✅ 500ms延迟重试处理异步初始化
- ✅ 立即启用按钮,提升用户体验
- ✅ 自动降级到浏览器TTS
#### 3. `questionnaire/taking.vue` - 问卷答题页面
**文件位置**`xinli-ui/src/views/psychology/questionnaire/taking.vue`
**改动**:与测评页面保持一致的检测逻辑
---
## 🚀 部署步骤
### 第1步重新构建前端
```bash
cd c:\Users\Administrator\Desktop\Project\xinli\xinli-ui
npm run build:prod
```
**耗时**约2-5分钟
### 第2步部署前端到服务器
`xinli-ui/dist` 目录的所有文件复制到服务器的Web根目录。
### 第3步打包Android APK
```bash
cd c:\Users\Administrator\Desktop\Project\xinli\xinli-App
.\打包正式版APK.bat
```
**耗时**约3-5分钟
### 第4步查找生成的APK
```bash
.\查找APK.bat
```
APK文件位置
```
xinli-App\app\build\outputs\apk\release\app-release.apk
```
### 第5步部署到设备
#### 重要:必须先卸载旧版本!
**方法1通过手机设置卸载**
1. 设置 → 应用管理 → 找到"心理测评"App
2. 点击"卸载"
**方法2使用ADB命令**
```bash
adb uninstall com.xinli.app
```
#### 安装新版APK
**方法1直接安装**
1. 将APK文件复制到手机
2. 点击文件安装
**方法2使用ADB安装**
```bash
adb install app\build\outputs\apk\release\app-release.apk
```
---
## ✅ 验证测试
### 测试清单
#### 基础功能测试
- [ ] App能正常启动无崩溃
- [ ] 能正常登录
- [ ] 进入答题页面正常
#### TTS功能测试
- [ ] 朗读按钮**不是灰色**(可点击)
- [ ] 点击"朗读全部"能听到声音
- [ ] 点击"朗读题干"能听到声音
- [ ] 点击选项旁的朗读按钮能听到声音
- [ ] 可以正常停止朗读
- [ ] 切换题目后朗读功能正常
#### ANR问题测试
- [ ] **没有出现"应用无响应"对话框**
- [ ] 长时间使用不会卡死
- [ ] 快速点击朗读按钮不会崩溃
### 查看日志(可选)
使用ADB查看TTS日志
```bash
adb logcat | findstr TtsHelper
```
**预期日志**
```
TtsHelper: ✅ Android原生TTS初始化成功离线
TtsHelper: isAvailable: true
TtsHelper: ✅ 开始朗读: ...
```
**前端控制台日志**
```
✅ 使用Android原生TTS
```
```
⏳ Android TTS尚未就绪延迟检查...
✅ Android TTS 已就绪(延迟检测)
```
---
## 🔍 故障排除
### 问题1按钮仍然是灰色
**原因**未卸载旧版App代码未更新
**解决**
1. 完全卸载旧版App
2. 清除应用数据
3. 重新安装新APK
4. 重启App
### 问题2没有声音
**检查项**
1. **手机媒体音量**确保不是0注意不是铃声音量
2. **TTS引擎**检查系统是否安装了中文TTS引擎
- 设置 → 辅助功能 → 文字转语音
- 确认有可用的TTS引擎
3. **查看日志**:运行 `adb logcat | findstr TTS` 查看错误信息
**改善音质**
- 在手机设置中安装高质量TTS引擎
- 推荐Google文字转语音引擎
### 问题3仍然出现ANR
**检查**
1. 确认安装的是新版APK查看APK生成时间
2. 确认前端代码已更新检查dist目录修改时间
3. 查看日志确认使用的是原生TTS
```bash
adb logcat | findstr "Android原生TTS"
```
### 问题4部分设备不支持中文TTS
**解决**
1. 引导用户安装中文TTS引擎
2. 或在App中集成离线TTS SDK如讯飞、百度
---
## 📊 修复对比
| 项目 | 修复前 | 修复后 |
|------|--------|--------|
| **朗读按钮状态** | ❌ 灰色禁用 | ✅ 正常可用 |
| **网络依赖** | ❌ 需要外网访问百度API | ✅ 完全离线 |
| **ANR问题** | ❌ 内网环境会ANR | ✅ 不会ANR |
| **TTS引擎** | ❌ 在线API | ✅ Android原生引擎 |
| **音质** | 一般百度API | 良好(取决于设备) |
| **检测逻辑** | ❌ 只检查对象存在 | ✅ 调用isAvailable()验证 |
| **降级策略** | ❌ 无 | ✅ 自动降级到浏览器TTS |
| **初始化处理** | ❌ 无延迟重试 | ✅ 500ms延迟重试 |
---
## 🎉 技术优势
### 1. 完全离线
- 不依赖任何外部API
- 内网环境正常工作
- 无网络超时风险
### 2. 高可用性
- 使用系统内置TTS引擎
- 双重保障原生TTS + 浏览器TTS
- 异步初始化不阻塞UI
### 3. 用户体验
- 朗读按钮立即可用
- 无需等待网络响应
- 不会出现ANR
### 4. 维护性
- 代码简洁清晰
- 统一的检测逻辑
- 完善的日志输出
---
## 📚 技术细节
### Android原生TTS工作流程
```
1. App启动
2. 初始化TextToSpeech引擎异步
3. onInit回调 → 设置中文语言
4. 设置isReady = true
5. 前端调用isAvailable() → 返回true
6. 前端启用朗读按钮
7. 用户点击朗读 → 调用speak()
8. TTS引擎合成语音并播放
```
### 前端检测流程
```
1. 页面加载 → initTts()
2. 检查window.AndroidTTS是否存在
3. 调用AndroidTTS.isAvailable()
4. 如果返回false → 延迟500ms重试
5. 如果仍然false → 降级到浏览器TTS
6. 设置isTtsSupported = true
7. 朗读按钮可用
```
### 为什么需要延迟重试?
Android的TextToSpeech初始化是**异步**的:
- `new TextToSpeech()` 立即返回
- 实际初始化在后台进行
- `onInit()` 回调可能需要100-500ms
- 前端页面加载可能比TTS初始化更快
**解决方案**
- 第一次检查失败 → 延迟500ms重试
- 同时先启用按钮,提升用户体验
- 用户点击时如果还未就绪 → 内部再延迟重试
---
## ⚠️ 注意事项
### 1. 必须卸载旧版
新APK的签名可能与旧版不同直接安装可能失败或代码不更新。
### 2. 前端也需要更新
只更新APK不够前端检测逻辑也必须更新否则按钮仍然是灰色。
### 3. 设备TTS引擎差异
不同品牌手机的TTS引擎音质有差异
- **华为/小米/OPPO**通常有高质量中文TTS
- **三星**:音质良好
- **低端设备**:可能音质较差或不支持中文
### 4. 浏览器版本不受影响
- Web版PC浏览器继续使用浏览器的Web Speech API
- 修改仅影响Android App
---
## 🔄 未来优化方向
### 短期(可选)
1. **集成高质量离线TTS SDK**
- 讯飞语音SDK
- 百度语音SDK
- 提供更好的音质和更多音色选择
### 长期(可选)
2. **支持语速/音调调节**
- 在App中添加TTS设置界面
- 让用户自定义语音参数
3. **多语言支持**
- 检测题目语言
- 自动切换TTS语言
---
## 📞 技术支持
如果遇到问题:
1. **查看日志**
```bash
adb logcat | findstr "TtsHelper\|AndroidTTS"
```
2. **检查TTS引擎**
- 手机设置 → 辅助功能 → 文字转语音
- 确认有可用的中文TTS引擎
3. **验证安装**
```bash
# 查看已安装App版本
adb shell pm list packages -f | findstr xinli
# 查看APK信息
adb shell dumpsys package com.xinli.app | findstr version
```
---
**修复完成日期**2025-01-27
**测试状态**:✅ 待部署验证
**适用环境**:内网/外网均可使用

View File

@ -1,253 +0,0 @@
-- ========================================
-- 删除今天导入的用户数据 SQL脚本
-- 创建时间: 2025-12-01
-- 警告: 执行前请务必备份数据库!
-- ========================================
-- ========================================
-- 第一步: 查询今天导入的用户(先确认数量)
-- ========================================
-- 1. 查看今天创建的用户档案数量
SELECT
COUNT(*) AS '今天创建的用户档案数',
MIN(create_time) AS '最早创建时间',
MAX(create_time) AS '最晚创建时间'
FROM psy_user_profile
WHERE DATE(create_time) = CURDATE();
-- 2. 查看今天创建的用户档案详情前10条检查是否是要删除的数据
SELECT
profile_id,
user_id,
info_number,
user_name,
prison_area,
create_time,
create_by
FROM psy_user_profile
WHERE DATE(create_time) = CURDATE()
ORDER BY create_time DESC
LIMIT 10;
-- 3. 查看今天创建的系统用户数量
SELECT
COUNT(*) AS '今天创建的系统用户数',
MIN(create_time) AS '最早创建时间',
MAX(create_time) AS '最晚创建时间'
FROM sys_user
WHERE DATE(create_time) = CURDATE()
AND user_id > 1; -- 排除管理员账户
-- ========================================
-- 第二步: 数据备份(强烈推荐!)
-- ========================================
-- 创建备份表
CREATE TABLE IF NOT EXISTS psy_user_profile_backup_20251201 AS
SELECT * FROM psy_user_profile
WHERE DATE(create_time) = CURDATE();
CREATE TABLE IF NOT EXISTS sys_user_backup_20251201 AS
SELECT * FROM sys_user
WHERE DATE(create_time) = CURDATE()
AND user_id > 1;
-- 验证备份
SELECT COUNT(*) AS '备份的用户档案数' FROM psy_user_profile_backup_20251201;
SELECT COUNT(*) AS '备份的系统用户数' FROM sys_user_backup_20251201;
-- ========================================
-- 第三步: 删除关联数据(外键关联)
-- ========================================
-- 3.1 删除今天创建用户的测评记录
-- 警告:这会删除这些用户的所有测评数据!
DELETE FROM psy_assessment_answer
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
WHERE DATE(p.create_time) = CURDATE()
);
DELETE FROM psy_factor_score
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
WHERE DATE(p.create_time) = CURDATE()
);
DELETE FROM psy_assessment_warning
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
WHERE DATE(p.create_time) = CURDATE()
);
DELETE FROM psy_assessment_report
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
WHERE DATE(p.create_time) = CURDATE()
);
DELETE FROM psy_assessment
WHERE target_user_id IN (
SELECT user_id FROM psy_user_profile
WHERE DATE(create_time) = CURDATE()
);
-- 3.2 删除量表权限
DELETE FROM psy_scale_permission
WHERE user_id IN (
SELECT user_id FROM psy_user_profile
WHERE DATE(create_time) = CURDATE()
);
-- 3.3 删除问卷答题记录
DELETE FROM psy_questionnaire_answer_detail
WHERE answer_id IN (
SELECT qa.answer_id
FROM psy_questionnaire_answer qa
INNER JOIN psy_user_profile p ON qa.user_id = p.user_id
WHERE DATE(p.create_time) = CURDATE()
);
DELETE FROM psy_questionnaire_answer
WHERE user_id IN (
SELECT user_id FROM psy_user_profile
WHERE DATE(create_time) = CURDATE()
);
-- 3.4 删除用户角色关联
DELETE FROM sys_user_role
WHERE user_id IN (
SELECT user_id FROM psy_user_profile
WHERE DATE(create_time) = CURDATE()
);
-- 3.5 删除用户岗位关联
DELETE FROM sys_user_post
WHERE user_id IN (
SELECT user_id FROM psy_user_profile
WHERE DATE(create_time) = CURDATE()
);
-- ========================================
-- 第四步: 删除用户档案和系统用户
-- ========================================
-- 4.1 删除用户档案
DELETE FROM psy_user_profile
WHERE DATE(create_time) = CURDATE();
-- 4.2 删除系统用户对应的sys_user表
DELETE FROM sys_user
WHERE user_id IN (
SELECT user_id FROM psy_user_profile_backup_20251201
)
AND user_id > 1; -- 保护管理员账户
-- ========================================
-- 第五步: 验证删除结果
-- ========================================
-- 验证今天创建的用户是否已删除
SELECT COUNT(*) AS '剩余今天创建的用户档案数'
FROM psy_user_profile
WHERE DATE(create_time) = CURDATE();
SELECT COUNT(*) AS '剩余今天创建的系统用户数'
FROM sys_user
WHERE DATE(create_time) = CURDATE()
AND user_id > 1;
-- 查看备份表中的数据(确认备份成功)
SELECT COUNT(*) AS '备份的记录数' FROM psy_user_profile_backup_20251201;
SELECT * FROM psy_user_profile_backup_20251201 LIMIT 5;
-- ========================================
-- 恢复数据脚本(如果需要回滚)
-- ========================================
/*
-- 恢复用户档案
INSERT INTO psy_user_profile
SELECT * FROM psy_user_profile_backup_20251201;
-- 恢复系统用户
INSERT INTO sys_user
SELECT * FROM sys_user_backup_20251201;
-- 注意:恢复后需要手动恢复关联数据,建议在删除前做完整数据库备份!
*/
-- ========================================
-- 清理备份表(确认数据无误后可执行)
-- ========================================
/*
DROP TABLE IF EXISTS psy_user_profile_backup_20251201;
DROP TABLE IF EXISTS sys_user_backup_20251201;
*/
-- ========================================
-- 执行说明
-- ========================================
/*
1. "第一步"
2. "第二步"
3.
4. "第三步"
5. "第四步"
6. "第五步"
7. 使
1.
2.
3.
4.
5. 7
6.
*/
-- ========================================
-- 更安全的分步执行方案
-- ========================================
/*
-- 方案A只删除特定时间段的用户更精确
-- 例如删除今天上午10点到11点导入的用户
SELECT COUNT(*) FROM psy_user_profile
WHERE create_time >= '2025-12-01 10:00:00'
AND create_time < '2025-12-01 11:00:00';
-- 然后修改上面的WHERE条件为
-- WHERE create_time >= '2025-12-01 10:00:00' AND create_time < '2025-12-01 11:00:00'
*/
/*
-- 方案B只删除特定创建人导入的用户更精确
SELECT COUNT(*) FROM psy_user_profile
WHERE DATE(create_time) = CURDATE()
AND create_by = 'admin'; -- 替换为实际的创建人用户名
-- 然后在WHERE条件中增加
-- AND create_by = 'admin'
*/
/*
-- 方案C根据信息编号范围删除如果有规律
SELECT COUNT(*) FROM psy_user_profile
WHERE info_number LIKE 'XXX%'; -- 替换为实际的编号前缀
-- 然后修改WHERE条件
*/

View File

@ -1,262 +0,0 @@
-- ========================================
-- 删除姓名为纯数字的用户数据 SQL脚本
-- 创建时间: 2025-12-01
-- 警告: 执行前请务必备份数据库!
-- ========================================
-- ========================================
-- 第一步: 查询姓名为纯数字的用户(先确认数量)
-- ========================================
-- 1. 查看姓名为纯数字的用户档案数量
SELECT
COUNT(*) AS '姓名为纯数字的用户数',
MIN(p.create_time) AS '最早创建时间',
MAX(p.create_time) AS '最晚创建时间'
FROM psy_user_profile p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$';
-- 2. 查看姓名为纯数字的用户档案详情(检查是否是要删除的数据)
SELECT
profile_id,
user_id,
info_number,
user_name,
prison_area,
create_time,
create_by
FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]+$'
ORDER BY create_time DESC
LIMIT 20;
-- 3. 查看这些用户在sys_user表中的对应记录
SELECT
u.user_id,
u.user_name,
u.nick_name,
u.create_time
FROM sys_user u
INNER JOIN psy_user_profile p ON u.user_id = p.user_id
WHERE p.user_name REGEXP '^[0-9]+$'
AND u.user_id > 1; -- 排除管理员账户
-- ========================================
-- 第二步: 数据备份(强烈推荐!)
-- ========================================
-- 创建备份表
CREATE TABLE IF NOT EXISTS psy_user_profile_backup_numeric_names AS
SELECT * FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]+$';
CREATE TABLE IF NOT EXISTS sys_user_backup_numeric_names AS
SELECT u.* FROM sys_user u
INNER JOIN psy_user_profile p ON u.user_id = p.user_id
WHERE p.user_name REGEXP '^[0-9]+$'
AND u.user_id > 1;
-- 验证备份
SELECT COUNT(*) AS '备份的用户档案数' FROM psy_user_profile_backup_numeric_names;
SELECT COUNT(*) AS '备份的系统用户数' FROM sys_user_backup_numeric_names;
-- 查看备份的具体数据
SELECT profile_id, user_id, user_name, info_number FROM psy_user_profile_backup_numeric_names LIMIT 10;
-- ========================================
-- 第三步: 删除关联数据(外键关联)
-- ========================================
-- 3.1 删除测评答题详情
DELETE FROM psy_assessment_answer
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
WHERE p.user_name REGEXP '^[0-9]+$'
);
-- 3.2 删除因子得分
DELETE FROM psy_factor_score
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
WHERE p.user_name REGEXP '^[0-9]+$'
);
-- 3.3 删除测评预警
DELETE FROM psy_assessment_warning
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
WHERE p.user_name REGEXP '^[0-9]+$'
);
-- 3.4 删除测评报告
DELETE FROM psy_assessment_report
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
WHERE p.user_name REGEXP '^[0-9]+$'
);
-- 3.5 删除测评记录
DELETE FROM psy_assessment
WHERE target_user_id IN (
SELECT user_id FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]+$'
);
-- 3.6 删除量表权限
DELETE FROM psy_scale_permission
WHERE user_id IN (
SELECT user_id FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]+$'
);
-- 3.7 删除问卷答题详情
DELETE FROM psy_questionnaire_answer_detail
WHERE answer_id IN (
SELECT qa.answer_id
FROM psy_questionnaire_answer qa
INNER JOIN psy_user_profile p ON qa.user_id = p.user_id
WHERE p.user_name REGEXP '^[0-9]+$'
);
-- 3.8 删除问卷答题记录
DELETE FROM psy_questionnaire_answer
WHERE user_id IN (
SELECT user_id FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]+$'
);
-- 3.9 删除用户角色关联
DELETE FROM sys_user_role
WHERE user_id IN (
SELECT user_id FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]+$'
);
-- 3.10 删除用户岗位关联
DELETE FROM sys_user_post
WHERE user_id IN (
SELECT user_id FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]+$'
);
-- ========================================
-- 第四步: 删除用户档案和系统用户
-- ========================================
-- 4.1 删除用户档案
DELETE FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]+$';
-- 4.2 删除系统用户使用备份表中的user_id
DELETE FROM sys_user
WHERE user_id IN (
SELECT user_id FROM sys_user_backup_numeric_names
)
AND user_id > 1; -- 保护管理员账户
-- ========================================
-- 第五步: 验证删除结果
-- ========================================
-- 验证姓名为纯数字的用户是否已删除
SELECT COUNT(*) AS '剩余姓名为数字的用户档案数'
FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]+$';
SELECT COUNT(*) AS '剩余姓名为数字的系统用户数'
FROM sys_user u
INNER JOIN psy_user_profile p ON u.user_id = p.user_id
WHERE p.user_name REGEXP '^[0-9]+$'
AND u.user_id > 1;
-- 查看备份表中的数据(确认备份成功)
SELECT COUNT(*) AS '备份的档案记录数' FROM psy_user_profile_backup_numeric_names;
SELECT COUNT(*) AS '备份的系统用户记录数' FROM sys_user_backup_numeric_names;
-- 查看备份的示例数据
SELECT * FROM psy_user_profile_backup_numeric_names LIMIT 5;
SELECT * FROM sys_user_backup_numeric_names LIMIT 5;
-- ========================================
-- 恢复数据脚本(如果需要回滚)
-- ========================================
/*
-- 恢复用户档案
INSERT INTO psy_user_profile
SELECT * FROM psy_user_profile_backup_numeric_names;
-- 恢复系统用户
INSERT INTO sys_user
SELECT * FROM sys_user_backup_numeric_names;
-- 注意:恢复后需要手动恢复关联数据,建议在删除前做完整数据库备份!
*/
-- ========================================
-- 清理备份表(确认数据无误后可执行)
-- ========================================
/*
DROP TABLE IF EXISTS psy_user_profile_backup_numeric_names;
DROP TABLE IF EXISTS sys_user_backup_numeric_names;
*/
-- ========================================
-- 执行说明
-- ========================================
/*
1. "第一步"
2. "第二步"
3.
4. "第三步"
5. "第四步"
6. "第五步"
7. 使
1.
2.
3.
4.
5. 7
6. '^[0-9]+$' 0-9
7.
- '^[0-9]+$'
- '123''456'
- '张三''123abc''李4'
*/
-- ========================================
-- 更精确的匹配方案(可选)
-- ========================================
/*
-- 方案A只删除纯数字且长度在特定范围的姓名
-- 例如只删除3-10位纯数字的姓名
SELECT COUNT(*) FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]{3,10}$';
-- 方案B排除某些特定的数字姓名如果有合法的数字姓名
SELECT COUNT(*) FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]+$'
AND user_name NOT IN ('001', '002'); -- 排除这些合法的数字姓名
-- 方案C只删除特定区域的数字姓名用户
SELECT COUNT(*) FROM psy_user_profile
WHERE user_name REGEXP '^[0-9]+$'
AND prison_area = '某监区'; -- 替换为实际的监区名称
*/

View File

@ -1,287 +0,0 @@
-- ========================================
-- 删除姓名为纯数字的用户数据 SQL脚本修正版
-- 创建时间: 2025-12-01
-- 警告: 执行前请务必备份数据库!
-- 说明: user_name字段在sys_user表中不在psy_user_profile表中
-- ========================================
-- ========================================
-- 第一步: 查询姓名为纯数字的用户(先确认数量)
-- ========================================
-- 1. 查看姓名为纯数字的用户档案数量
SELECT
COUNT(*) AS '姓名为纯数字的用户数',
MIN(p.create_time) AS '最早创建时间',
MAX(p.create_time) AS '最晚创建时间'
FROM psy_user_profile p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$';
-- 2. 查看姓名为纯数字的用户档案详情(检查是否是要删除的数据)
SELECT
p.profile_id,
p.user_id,
p.info_number,
u.user_name,
p.prison_area,
p.create_time,
p.create_by
FROM psy_user_profile p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
ORDER BY p.create_time DESC
LIMIT 20;
-- 3. 查看这些用户在sys_user表中的对应记录
SELECT
u.user_id,
u.user_name,
u.nick_name,
u.create_time,
p.info_number
FROM sys_user u
INNER JOIN psy_user_profile p ON u.user_id = p.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
AND u.user_id > 1; -- 排除管理员账户
-- ========================================
-- 第二步: 数据备份(强烈推荐!)
-- ========================================
-- 创建备份表
CREATE TABLE IF NOT EXISTS psy_user_profile_backup_numeric_names AS
SELECT p.* FROM psy_user_profile p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$';
CREATE TABLE IF NOT EXISTS sys_user_backup_numeric_names AS
SELECT u.* FROM sys_user u
INNER JOIN psy_user_profile p ON u.user_id = p.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
AND u.user_id > 1;
-- 验证备份
SELECT COUNT(*) AS '备份的用户档案数' FROM psy_user_profile_backup_numeric_names;
SELECT COUNT(*) AS '备份的系统用户数' FROM sys_user_backup_numeric_names;
-- 查看备份的具体数据
SELECT profile_id, user_id, info_number FROM psy_user_profile_backup_numeric_names LIMIT 10;
SELECT user_id, user_name, nick_name FROM sys_user_backup_numeric_names LIMIT 10;
-- ========================================
-- 第三步: 删除关联数据(外键关联)
-- ========================================
-- 3.1 删除测评答题详情
DELETE FROM psy_assessment_answer
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
);
-- 3.2 删除因子得分
DELETE FROM psy_factor_score
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
);
-- 3.3 删除测评预警
DELETE FROM psy_assessment_warning
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
);
-- 3.4 删除测评报告
DELETE FROM psy_assessment_report
WHERE assessment_id IN (
SELECT a.assessment_id
FROM psy_assessment a
INNER JOIN psy_user_profile p ON a.target_user_id = p.user_id
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
);
-- 3.5 删除测评记录
DELETE FROM psy_assessment
WHERE target_user_id IN (
SELECT p.user_id FROM psy_user_profile p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
);
-- 3.6 删除量表权限
DELETE FROM psy_scale_permission
WHERE user_id IN (
SELECT p.user_id FROM psy_user_profile p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
);
-- 3.7 删除问卷答题详情
DELETE FROM psy_questionnaire_answer_detail
WHERE answer_id IN (
SELECT qa.answer_id
FROM psy_questionnaire_answer qa
INNER JOIN psy_user_profile p ON qa.user_id = p.user_id
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
);
-- 3.8 删除问卷答题记录
DELETE FROM psy_questionnaire_answer
WHERE user_id IN (
SELECT p.user_id FROM psy_user_profile p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
);
-- 3.9 删除用户角色关联
DELETE FROM sys_user_role
WHERE user_id IN (
SELECT p.user_id FROM psy_user_profile p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
);
-- 3.10 删除用户岗位关联
DELETE FROM sys_user_post
WHERE user_id IN (
SELECT p.user_id FROM psy_user_profile p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
);
-- ========================================
-- 第四步: 删除用户档案和系统用户
-- ========================================
-- 4.1 删除用户档案
DELETE FROM psy_user_profile
WHERE user_id IN (
SELECT user_id FROM sys_user_backup_numeric_names
);
-- 4.2 删除系统用户使用备份表中的user_id
DELETE FROM sys_user
WHERE user_id IN (
SELECT user_id FROM sys_user_backup_numeric_names
)
AND user_id > 1; -- 保护管理员账户
-- ========================================
-- 第五步: 验证删除结果
-- ========================================
-- 验证姓名为纯数字的用户是否已删除
SELECT COUNT(*) AS '剩余姓名为数字的用户档案数'
FROM psy_user_profile p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE u.user_name REGEXP '^[0-9]+$';
SELECT COUNT(*) AS '剩余姓名为数字的系统用户数'
FROM sys_user
WHERE user_name REGEXP '^[0-9]+$'
AND user_id > 1;
-- 查看备份表中的数据(确认备份成功)
SELECT COUNT(*) AS '备份的档案记录数' FROM psy_user_profile_backup_numeric_names;
SELECT COUNT(*) AS '备份的系统用户记录数' FROM sys_user_backup_numeric_names;
-- 查看备份的示例数据
SELECT * FROM psy_user_profile_backup_numeric_names LIMIT 5;
SELECT * FROM sys_user_backup_numeric_names LIMIT 5;
-- ========================================
-- 恢复数据脚本(如果需要回滚)
-- ========================================
/*
-- 恢复用户档案
INSERT INTO psy_user_profile
SELECT * FROM psy_user_profile_backup_numeric_names;
-- 恢复系统用户
INSERT INTO sys_user
SELECT * FROM sys_user_backup_numeric_names;
-- 注意:恢复后需要手动恢复关联数据,建议在删除前做完整数据库备份!
*/
-- ========================================
-- 清理备份表(确认数据无误后可执行)
-- ========================================
/*
DROP TABLE IF EXISTS psy_user_profile_backup_numeric_names;
DROP TABLE IF EXISTS sys_user_backup_numeric_names;
*/
-- ========================================
-- 执行说明
-- ========================================
/*
1. "第一步"
2. "第二步"
3.
4. "第三步"
5. "第四步"
6. "第五步"
7. 使
- user_name sys_user psy_user_profile
- INNER JOIN
- psy_user_profile.user_id = sys_user.user_id
1.
2.
3.
4.
5. 7
6. '^[0-9]+$' 0-9
7.
- '^[0-9]+$'
- '123''456'
- '张三''123abc''李4'
*/
-- ========================================
-- 更精确的匹配方案(可选)
-- ========================================
/*
-- 方案A只删除纯数字且长度在特定范围的姓名
-- 例如只删除3-10位纯数字的姓名
SELECT COUNT(*) FROM sys_user u
INNER JOIN psy_user_profile p ON u.user_id = p.user_id
WHERE u.user_name REGEXP '^[0-9]{3,10}$';
-- 方案B排除某些特定的数字姓名如果有合法的数字姓名
SELECT COUNT(*) FROM sys_user u
INNER JOIN psy_user_profile p ON u.user_id = p.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
AND u.user_name NOT IN ('001', '002'); -- 排除这些合法的数字姓名
-- 方案C只删除特定区域的数字姓名用户
SELECT COUNT(*) FROM sys_user u
INNER JOIN psy_user_profile p ON u.user_id = p.user_id
WHERE u.user_name REGEXP '^[0-9]+$'
AND p.prison_area = '某监区'; -- 替换为实际的监区名称
*/

View File

@ -1,191 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 心理测评系统 - 完整打包流程
echo ========================================
echo.
echo 此脚本将完成以下操作:
echo 1. 重新构建前端(包含最新的 TTS 修复)
echo 2. 清理并重新打包 Android APK
echo.
echo 预计耗时5-10 分钟
echo.
pause
echo.
echo ========================================
echo [阶段 1/2] 重新构建前端
echo ========================================
echo.
cd /d "%~dp0xinli-ui"
echo 当前目录:%CD%
echo.
echo [1.1] 检查 Node.js 环境...
call node -v >nul 2>&1
if %ERRORLEVEL% NEQ 0 (
echo ✗ Node.js 未安装或未配置到 PATH
echo 请先安装 Node.js
cd /d "%~dp0"
pause
exit /b 1
)
echo ✓ Node.js 已安装
call node -v
echo.
echo [1.2] 检查 package.json...
if not exist "package.json" (
echo ✗ 未找到 package.json
echo 请确认在正确的目录
cd /d "%~dp0"
pause
exit /b 1
)
echo ✓ package.json 存在
echo.
echo [1.3] 清理旧的构建文件...
if exist "dist" (
echo 删除 dist 目录...
rd /s /q "dist"
)
echo ✓ 清理完成
echo.
echo [1.4] 构建生产版本...
echo 开始构建,请耐心等待...
echo.
call npm run build:prod
if %ERRORLEVEL% NEQ 0 (
echo.
echo ✗ 前端构建失败
cd /d "%~dp0"
pause
exit /b 1
)
echo.
echo ✓ 前端构建成功
echo.
cd /d "%~dp0"
echo.
echo ========================================
echo [阶段 2/2] 打包 Android APK
echo ========================================
echo.
cd /d "%~dp0xinli-App"
echo 当前目录:%CD%
echo.
echo [2.1] 清理旧的构建文件...
if exist "app\build" (
echo 删除 app\build 目录...
rd /s /q "app\build"
)
if exist "build" (
echo 删除根目录 build 文件夹...
rd /s /q "build"
)
if exist ".gradle" (
echo 删除 .gradle 缓存...
rd /s /q ".gradle"
)
echo ✓ 清理完成
echo.
echo [2.2] 清理 Gradle 缓存...
call gradlew.bat clean
if %ERRORLEVEL% NEQ 0 (
echo ✗ Gradle clean 失败
cd /d "%~dp0"
pause
exit /b 1
)
echo ✓ Gradle clean 完成
echo.
echo [2.3] 检查 Java 环境...
if not defined JAVA_HOME (
echo ✗ JAVA_HOME 未设置
echo 请设置 JAVA_HOME 环境变量
cd /d "%~dp0"
pause
exit /b 1
)
echo JAVA_HOME: %JAVA_HOME%
"%JAVA_HOME%\bin\java.exe" -version
echo ✓ Java 环境正常
echo.
echo [2.4] 构建 Release APK...
echo 开始构建,请耐心等待...
echo.
call gradlew.bat assembleRelease --stacktrace
if %ERRORLEVEL% NEQ 0 (
echo.
echo ✗ APK 构建失败
cd /d "%~dp0"
pause
exit /b 1
)
echo.
echo ✓ APK 构建成功
echo.
cd /d "%~dp0"
echo.
echo ========================================
echo ✓✓✓ 完整打包流程完成!✓✓✓
echo ========================================
echo.
set APK_PATH=%~dp0xinli-App\app\build\outputs\apk\release\app-release.apk
if exist "%APK_PATH%" (
echo APK 文件位置:
echo %APK_PATH%
echo.
echo 文件大小:
dir "%APK_PATH%" | findstr "app-release.apk"
echo.
echo ========================================
echo 📱 安装说明
echo ========================================
echo.
echo 1. ⚠️ 先卸载手机上的旧版本 App重要
echo - 设置 → 应用管理 → 心理测评 → 卸载
echo.
echo 2. 📲 安装新的 APK
echo - 将 APK 传输到手机
echo - 点击安装
echo.
echo 3. ✅ 测试功能
echo - 登录系统
echo - 进入量表/问卷答题页面
echo - 测试朗读功能(应该不再灰色)
echo - 测试权限过滤(普通用户只能看到授权的问卷)
echo.
echo ========================================
echo 🎉 打包完成!
echo ========================================
) else (
echo ✗ 未找到 APK 文件
echo.
echo 搜索 APK 文件...
dir /s /b "%~dp0xinli-App\*.apk" 2>nul
)
echo.
pause

View File

@ -1,552 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>局域网文字转语音工具</title>
<!-- 引入Element UI样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 30px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
color: #333;
font-size: 28px;
margin-bottom: 10px;
}
.header p {
color: #666;
font-size: 14px;
}
.input-section {
margin-bottom: 30px;
}
.input-section label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
}
.input-section textarea {
width: 100%;
min-height: 120px;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
resize: vertical;
transition: border-color 0.3s;
}
.input-section textarea:focus {
outline: none;
border-color: #667eea;
}
.preset-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
.preset-btn {
padding: 8px 16px;
background: #f0f0f0;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
font-size: 13px;
}
.preset-btn:hover {
background: #667eea;
color: white;
border-color: #667eea;
}
.control-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.control-item {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
}
.control-item label {
display: block;
margin-bottom: 10px;
color: #333;
font-weight: 500;
}
.control-item .slider-container {
display: flex;
align-items: center;
gap: 15px;
}
.slider {
flex: 1;
height: 6px;
background: #e0e0e0;
border-radius: 3px;
position: relative;
cursor: pointer;
}
.slider-track {
height: 100%;
background: #667eea;
border-radius: 3px;
transition: width 0.2s;
}
.slider-thumb {
width: 18px;
height: 18px;
background: white;
border: 2px solid #667eea;
border-radius: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
cursor: grab;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.slider-thumb:active {
cursor: grabbing;
}
.value-display {
min-width: 60px;
text-align: right;
color: #666;
font-size: 14px;
}
.button-group {
display: flex;
gap: 15px;
justify-content: center;
margin-top: 30px;
}
.btn {
padding: 12px 30px;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
font-weight: 500;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn-danger {
background: #f56565;
color: white;
}
.btn-danger:hover:not(:disabled) {
background: #e53e3e;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(245, 101, 101, 0.4);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.status {
text-align: center;
margin-top: 20px;
padding: 15px;
border-radius: 6px;
font-size: 14px;
}
.status.info {
background: #e3f2fd;
color: #1976d2;
}
.status.warning {
background: #fff3e0;
color: #f57c00;
}
.status.success {
background: #e8f5e9;
color: #388e3c;
}
.status.error {
background: #ffebee;
color: #d32f2f;
}
@media (max-width: 768px) {
.container {
padding: 20px;
}
.control-section {
grid-template-columns: 1fr;
}
.button-group {
flex-direction: column;
}
.btn {
width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎤 局域网文字转语音工具</h1>
<p>基于浏览器 Web Speech API无需后端服务纯前端实现</p>
</div>
<div class="input-section">
<label for="textInput">📝 输入要朗读的文字:</label>
<textarea id="textInput" placeholder="请输入要转换为语音的文字内容..."></textarea>
<div class="preset-buttons">
<button class="preset-btn" onclick="setPresetText('欢迎使用AI心理健康测评系统请仔细阅读题目并选择您的答案。')">
欢迎语
</button>
<button class="preset-btn" onclick="setPresetText('请根据您的实际情况,选择最符合的选项。')">
答题提示
</button>
<button class="preset-btn" onclick="setPresetText('感谢您的参与,测评已完成,请查看您的测评报告。')">
完成提示
</button>
<button class="preset-btn" onclick="setPresetText('请注意,本次测评结果仅供参考,如有疑问请咨询专业心理医生。')">
免责声明
</button>
</div>
</div>
<div class="control-section">
<div class="control-item">
<label>🔊 音量:<span id="volumeValue">100%</span></label>
<div class="slider-container">
<div class="slider" id="volumeSlider">
<div class="slider-track" id="volumeTrack"></div>
<div class="slider-thumb" id="volumeThumb"></div>
</div>
</div>
</div>
<div class="control-item">
<label>⚡ 语速:<span id="rateValue">1.0x</span></label>
<div class="slider-container">
<div class="slider" id="rateSlider">
<div class="slider-track" id="rateTrack"></div>
<div class="slider-thumb" id="rateThumb"></div>
</div>
</div>
</div>
<div class="control-item">
<label>🎵 音调:<span id="pitchValue">1.0</span></label>
<div class="slider-container">
<div class="slider" id="pitchSlider">
<div class="slider-track" id="pitchTrack"></div>
<div class="slider-thumb" id="pitchThumb"></div>
</div>
</div>
</div>
</div>
<div class="button-group">
<button class="btn btn-primary" id="speakBtn" onclick="speakText()">
▶️ 开始朗读
</button>
<button class="btn btn-danger" id="stopBtn" onclick="stopSpeaking()" disabled>
⏹️ 停止朗读
</button>
<button class="btn btn-primary" onclick="clearText()">
🗑️ 清空文本
</button>
</div>
<div class="status info" id="status">
💡 提示:请在 Chrome、Edge 或 Safari 浏览器中使用以获得最佳体验
</div>
</div>
<script>
// 全局变量
let synth = null;
let utterance = null;
let isSpeaking = false;
// 参数值
let volume = 1.0; // 0-1
let rate = 1.0; // 0.5-2
let pitch = 1.0; // 0.5-2
// 初始化
window.onload = function() {
initTts();
initSliders();
};
/**
* 初始化 TTS
*/
function initTts() {
if ('speechSynthesis' in window) {
synth = window.speechSynthesis;
updateStatus('success', '✅ 语音合成功能已就绪');
} else {
updateStatus('error', '❌ 您的浏览器不支持语音合成功能,请使用 Chrome、Edge 或 Safari 浏览器');
document.getElementById('speakBtn').disabled = true;
}
}
/**
* 初始化滑块
*/
function initSliders() {
initSlider('volume', 0, 1, 1.0, updateVolume);
initSlider('rate', 0.5, 2, 1.0, updateRate);
initSlider('pitch', 0.5, 2, 1.0, updatePitch);
}
/**
* 初始化单个滑块
*/
function initSlider(name, min, max, value, callback) {
const slider = document.getElementById(name + 'Slider');
const thumb = document.getElementById(name + 'Thumb');
const track = document.getElementById(name + 'Track');
const valueDisplay = document.getElementById(name + 'Value');
let isDragging = false;
function updateSlider(clientX) {
const rect = slider.getBoundingClientRect();
let percent = (clientX - rect.left) / rect.width;
percent = Math.max(0, Math.min(1, percent));
const newValue = min + percent * (max - min);
const displayValue = name === 'volume'
? Math.round(newValue * 100) + '%'
: name === 'rate'
? newValue.toFixed(1) + 'x'
: newValue.toFixed(1);
valueDisplay.textContent = displayValue;
thumb.style.left = percent * 100 + '%';
track.style.width = percent * 100 + '%';
callback(newValue);
}
slider.addEventListener('mousedown', (e) => {
isDragging = true;
updateSlider(e.clientX);
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
updateSlider(e.clientX);
}
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
// 初始化位置
const percent = (value - min) / (max - min);
thumb.style.left = percent * 100 + '%';
track.style.width = percent * 100 + '%';
}
/**
* 更新音量
*/
function updateVolume(value) {
volume = value;
if (utterance) {
utterance.volume = value;
}
}
/**
* 更新语速
*/
function updateRate(value) {
rate = value;
if (utterance) {
utterance.rate = value;
}
}
/**
* 更新音调
*/
function updatePitch(value) {
pitch = value;
if (utterance) {
utterance.pitch = value;
}
}
/**
* 朗读文本
*/
function speakText() {
const text = document.getElementById('textInput').value.trim();
if (!text) {
updateStatus('warning', '⚠️ 请输入要朗读的文字');
return;
}
if (!synth) {
updateStatus('error', '❌ 语音合成功能不可用');
return;
}
// 停止之前的朗读
stopSpeaking();
// 创建语音合成对象
utterance = new SpeechSynthesisUtterance(text);
// 设置参数
utterance.lang = 'zh-CN';
utterance.volume = volume;
utterance.rate = rate;
utterance.pitch = pitch;
// 选择中文语音
const voices = synth.getVoices();
const chineseVoice = voices.find(voice =>
voice.lang.includes('zh') || voice.lang.includes('CN')
);
if (chineseVoice) {
utterance.voice = chineseVoice;
}
// 监听事件
utterance.onstart = () => {
isSpeaking = true;
document.getElementById('speakBtn').disabled = true;
document.getElementById('stopBtn').disabled = false;
updateStatus('success', '🔊 正在朗读...');
};
utterance.onend = () => {
isSpeaking = false;
document.getElementById('speakBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
updateStatus('info', '✅ 朗读完成');
};
utterance.onerror = (event) => {
isSpeaking = false;
document.getElementById('speakBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
updateStatus('error', '❌ 朗读失败: ' + event.error);
};
// 开始朗读
synth.speak(utterance);
}
/**
* 停止朗读
*/
function stopSpeaking() {
if (synth && synth.speaking) {
synth.cancel();
isSpeaking = false;
document.getElementById('speakBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
updateStatus('info', '⏹️ 已停止朗读');
}
}
/**
* 清空文本
*/
function clearText() {
document.getElementById('textInput').value = '';
stopSpeaking();
}
/**
* 设置预设文本
*/
function setPresetText(text) {
document.getElementById('textInput').value = text;
}
/**
* 更新状态提示
*/
function updateStatus(type, message) {
const statusEl = document.getElementById('status');
statusEl.className = 'status ' + type;
statusEl.textContent = message;
}
</script>
</body>
</html>

View File

@ -1,398 +0,0 @@
# 心理测评问题修复说明
## 修复日期
2025年12月1日
## 问题清单
### 问题1用户档案与量表权限管理用户数量不一致 ✅
**问题描述**:
- 用户档案显示 3014 个用户
- 量表权限管理显示 3016 个用户
- 数据不一致导致分配权限时出现困惑
**根本原因**:
两个页面使用了不同的API接口
- **用户档案页面**:使用 `listStudentProfile` 接口(`/psychology/profile/student/list`
- 只查询拥有**学员角色**的用户 → 3014个
- **量表权限管理**:使用 `listProfile` 接口(`/psychology/profile/list`
- 查询**所有用户档案** → 3016个包括非学员用户
**修复方案**:
修改量表权限管理的用户查询逻辑,统一使用 `listStudentProfile` 接口,只查询学员用户,与用户档案页面保持一致。
**修改文件**:
- `xinli-ui/src/views/psychology/permission/index.vue`
**修改内容**:
1. **导入接口更改** (第300行)
```javascript
// 修改前
import { listProfile } from "@/api/psychology/profile";
// 修改后
import { listStudentProfile } from "@/api/psychology/profile";
```
2. **加载监区选项** (第556行)
```javascript
// 修改前
listProfile({ pageNum: 1, pageSize: 10000, status: undefined }).then(response => {
// 修改后
listStudentProfile({ pageNum: 1, pageSize: 10000 }).then(response => {
```
3. **获取用户选择列表** (第599行)
```javascript
// 修改前
return listProfile(query).then(response => {
// 修改后
return listStudentProfile(query).then(response => {
```
4. **批量获取所有用户** (第675行)
```javascript
// 修改前
const response = await listProfile({
// 修改后
const response = await listStudentProfile({
```
5. **保留查询参数配置** (第346-353行)
```javascript
userQueryParams: {
pageNum: 1,
pageSize: 10,
infoNumber: undefined,
userName: undefined,
prisonArea: undefined,
status: undefined // 不限制状态
}
```
**修复效果**:
- ✅ 量表权限管理和用户档案显示相同数量的用户都是3014
- ✅ 只显示学员角色的用户,数据更准确
- ✅ 数据统计保持一致性
---
### 问题2测评状态显示和管理 ✅
**问题描述**:
1. 用户退出测评后,状态显示为"进行中"而不是"已暂停"
2. 希望区分"进行中"(正在答题)、"已暂停"(退出或暂停)、"已完成"(提交完成)
3. 暂停和退出都要保留用户填写的内容
**状态定义**:
```
status = '0' - 进行中(正在答题,未退出)
status = '1' - 已完成(已提交)
status = '2' - 已作废(删除或作废)
status = '3' - 已暂停(用户暂停或退出)
```
**现有逻辑验证**:
#### ✅ 暂停功能
**前端** (`xinli-ui/src/views/psychology/assessment/taking.vue` 第494-503行):
```javascript
handlePause() {
this.$modal.confirm('确定要暂停测评吗?您可以稍后继续完成。').then(() => {
pauseAssessment(this.assessmentId).then(() => {
this.$modal.msgSuccess("测评已暂停");
// 跳转回列表页
this.$router.push(isStudent ? '/student/tests' : '/psychology/assessment');
});
});
}
```
**后端** (`PsyAssessmentMapper.xml` 第142-152行):
```xml
<update id="pauseAssessment">
update psy_assessment
<set>
pause_time = sysdate(),
pause_count = pause_count + 1,
status = '3', <!-- 设置为已暂停 -->
update_time = sysdate()
</set>
where assessment_id = #{assessmentId}
</update>
```
#### ✅ 退出功能
**前端** (`xinli-ui/src/views/psychology/assessment/taking.vue` 第506-528行):
```javascript
handleExit() {
this.$modal.confirm('确定要退出测评吗?已答题目将会保存。').then(() => {
this.loading = true;
// 先等待一小段时间,确保最后的答案保存请求发出
setTimeout(() => {
// 退出前先暂停测评,保存进度
pauseAssessment(this.assessmentId).then(() => { // ✅ 调用暂停接口
this.loading = false;
this.$modal.msgSuccess("测评进度已保存");
// 跳转回列表页
this.$router.push(isStudent ? '/student/tests' : '/psychology/assessment');
});
}, 500); // 等待500ms确保答案保存请求已发送
});
}
```
**说明**: 退出功能也调用 `pauseAssessment`,会将状态设为 `'3'`(已暂停)
#### ✅ 提交功能
**前端** (`xinli-ui/src/views/psychology/assessment/taking.vue` 第531-560行):
```javascript
handleSubmit() {
if (!this.isComplete) {
const remaining = this.itemList.length - this.answeredCount;
this.$modal.msgError(`还有 ${remaining} 道题未作答,请完成后再提交`);
return;
}
this.$modal.confirm('确定要提交测评吗?提交后将不能修改。').then(() => {
this.loading = true;
submitAssessment(this.assessmentId).then(response => {
this.loading = false;
this.$modal.msgSuccess("测评已提交,报告正在生成中...");
// 跳转到报告页面
});
});
}
```
**后端** (`PsyAssessmentController.java` 第436-445行):
```java
// 更新测评状态为已完成
assessment.setStatus("1"); // ✅ 设置为已完成
assessment.setSubmitTime(new Date());
if (assessment.getStartTime() != null) {
long completeSeconds = (System.currentTimeMillis() - assessment.getStartTime().getTime()) / 1000;
assessment.setCompleteTime((int) completeSeconds);
}
assessmentService.updateAssessment(assessment);
```
#### ✅ 继续答题功能
**前端** (`xinli-ui/src/views/psychology/assessment/index.vue` 第103-106行):
```vue
<el-button
@click="handleContinue(scope.row)"
v-if="scope.row.status === '0' || scope.row.status === '3'"
>继续答题</el-button>
```
**说明**: 只有"进行中"(`status='0'`)或"已暂停"(`status='3'`)状态才显示"继续答题"按钮
#### ✅ 恢复测评
**后端** (`PsyAssessmentMapper.xml` 第154-163行):
```xml
<update id="resumeAssessment">
update psy_assessment
<set>
resume_time = sysdate(),
status = '0', <!-- 恢复为进行中 -->
update_time = sysdate()
</set>
where assessment_id = #{assessmentId}
</update>
```
**说明**: 点击"继续答题"时会调用 `resumeAssessment`,将状态从 `'3'` 改为 `'0'`
#### ✅ 答案自动保存
**前端** (`xinli-ui/src/views/psychology/assessment/taking.vue`):
```javascript
// 每次选择答案时自动保存
handleAnswerChange() {
// ... 保存逻辑
this.saveCurrentAnswer();
}
```
**说明**:
- 用户每选择一个答案都会自动保存到数据库
- 退出或暂停时不会丢失已答题目
- 继续答题时可以从上次位置继续
### 测评状态流转图
```
开始测评 ──> status='0'(进行中)
├──> 点击"暂停" ──> status='3'(已暂停)──> 点击"继续答题" ──> status='0'
├──> 点击"退出" ──> status='3'(已暂停)──> 点击"继续答题" ──> status='0'
└──> 点击"提交" ──> status='1'(已完成)
```
### 测评管理列表显示
**文件**: `xinli-ui/src/views/psychology/assessment/index.vue`
**状态显示** (第81-87行):
```vue
<el-table-column label="状态" align="center" prop="status" width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.status === '0'" type="info">进行中</el-tag>
<el-tag v-else-if="scope.row.status === '1'" type="success">已完成</el-tag>
<el-tag v-else-if="scope.row.status === '2'" type="danger">已作废</el-tag>
<el-tag v-else-if="scope.row.status === '3'" type="warning">已暂停</el-tag>
</template>
</el-table-column>
```
**状态筛选** (第22-28行):
```vue
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="状态" clearable>
<el-option label="进行中" value="0" />
<el-option label="已完成" value="1" />
<el-option label="已作废" value="2" />
<el-option label="已暂停" value="3" />
</el-select>
</el-form-item>
```
## 修复验证
### 验证步骤
#### 问题1验证
1. 登录系统
2. 进入"心理 → 用户档案",查看总数 → 应显示 **3014**(学员用户)
3. 进入"心理 → 量表权限管理",点击"分配用户"
4. 不添加任何筛选条件,查看用户列表总数 → 应显示 **3014**(与用户档案一致)
5. ✅ 数量一致,都显示学员角色用户
**说明**: 修改后两个页面都只显示拥有学员角色的用户3014个不再显示其他角色的用户如管理员等2个用户
#### 问题2验证
**测试场景1暂停测评**
1. 开始一个测评
2. 答几道题
3. 点击"暂停"按钮
4. 返回测评管理列表
5. ✅ 该测评显示为"已暂停"状态(黄色标签)
6. 点击"继续答题"
7. ✅ 之前答的题目答案都保留
**测试场景2退出测评**
1. 开始一个测评
2. 答几道题
3. 点击"退出"按钮
4. 返回测评管理列表
5. ✅ 该测评显示为"已暂停"状态(黄色标签)
6. 点击"继续答题"
7. ✅ 之前答的题目答案都保留
**测试场景3完成测评**
1. 开始一个测评
2. 答完所有题目
3. 点击"提交测评"
4. 返回测评管理列表
5. ✅ 该测评显示为"已完成"状态(绿色标签)
6. ✅ 不再显示"继续答题"按钮
7. ✅ 显示"查看答题"和"查看报告"按钮
## 技术说明
### 数据库表结构
```sql
-- psy_assessment 测评表
CREATE TABLE psy_assessment (
assessment_id BIGINT PRIMARY KEY,
scale_id BIGINT,
assessee_name VARCHAR(100),
status CHAR(1), -- 0:进行中 1:已完成 2:已作废 3:已暂停
start_time DATETIME,
pause_time DATETIME,
resume_time DATETIME,
submit_time DATETIME,
pause_count INT DEFAULT 0,
complete_time INT,
total_score DECIMAL(10,2),
-- ... 其他字段
);
```
### API接口
- `POST /psychology/assessment/pause/{assessmentId}` - 暂停测评
- `POST /psychology/assessment/resume/{assessmentId}` - 恢复测评
- `POST /psychology/assessment/submit/{assessmentId}` - 提交测评
- `POST /psychology/assessment/answer/save` - 保存答案
## 总结
### 问题1用户数量不一致 ✅
**修复方式**: 统一使用学员档案接口(`listStudentProfile`),只查询学员角色用户
**影响范围**: 量表权限管理的用户选择对话框
**预期效果**:
- 用户档案和量表权限管理显示相同数量的用户都是3014
- 只显示学员用户,不包括其他角色的用户
- 数据来源统一,避免混淆
### 问题2测评状态管理 ✅
**验证结果**: 现有逻辑已正确实现
**核心功能**:
1. ✅ 退出测评会设置 status='3'(已暂停)
2. ✅ 暂停测评会设置 status='3'(已暂停)
3. ✅ 提交测评会设置 status='1'(已完成)
4. ✅ 继续答题会恢复 status='0'(进行中)
5. ✅ 所有答案都会自动保存,不会丢失
6. ✅ 测评列表正确显示各种状态
**如果用户仍然遇到"退出显示进行中"的问题,请检查**:
1. 浏览器缓存是否清除
2. 后端服务是否重启
3. 数据库中 psy_assessment 表的 status 字段值
4. 网络请求是否成功F12查看Network
5. 是否有其他地方修改了状态
## 部署说明
1. **前端部署**:
- 修改文件: `xinli-ui/src/views/psychology/permission/index.vue`
- 重新编译: `npm run build`
- 部署到服务器
2. **无需后端修改**:
- 后端逻辑已经正确实现
- 无需重新部署
3. **验证步骤**:
- 清除浏览器缓存
- 刷新页面
- 按照验证步骤测试
## 附录:常见问题
**Q: 为什么要查询所有状态的用户?**
A: 因为即使用户被停用或删除,其历史测评记录和权限仍然存在,需要能够管理和查看。
**Q: 状态'0'和'3'有什么区别?**
A:
- status='0'(进行中): 用户正在答题页面答题
- status='3'(已暂停): 用户点击了暂停或退出按钮,离开了答题页面
**Q: 如何区分正在答题和已退出?**
A:
- 在测评列表中status='0'和'3'都显示"继续答题"按钮
- 区别在于status='0'可能是打开了答题页面但没有答题status='3'是明确点击了暂停/退出
**Q: 答案什么时候保存?**
A: 每次选择答案后都会立即自动保存到数据库,不需要手动保存。

View File

@ -1,55 +0,0 @@
# 用户数据生成工具
## 功能说明
这个Python脚本可以生成3000条符合导入模版格式的测试数据。
## 生成的数据字段
- **信息编号(必填)**: 10000-12999
- **姓名(必填)**: 随机生成的中文姓名
- **监区(必填)**: 第一监区、第二监区、第三监区、A区、B区、C区、D区
- **性别**: 男/女
- **出生日期**: 1950-2005年之间
- **民族**: 汉、汉族、回族、满族、蒙古族、维吾尔族、苗族、壮族
- **文化程度**: 小学、初中、高中、中专、大专、本科、研究生
- **罪名**: 18种常见罪名抢劫罪、盗窃罪、诈骗罪等
- **刑期**: 随机生成X年/X月/无期)
- **刑期起日**: 2015-2024年之间的随机日期
- **刑期止日**: 根据刑期起日和刑期长度计算
- **入监时间**: 与刑期起日相同
- **状态**: 在押
## 安装依赖
```powershell
pip install -r requirements_data_generation.txt
```
或者单独安装:
```powershell
pip install pandas openpyxl
```
## 运行脚本
```powershell
python generate_test_data.py
```
## 输出文件
脚本运行后会在当前目录生成:
- `用户导入测试数据_3000条.xlsx` - 包含3000条测试数据的Excel文件
## 自定义生成数量
如需修改生成数量,请编辑脚本中的 `NUM_RECORDS` 变量:
```python
NUM_RECORDS = 3000 # 修改为你需要的数量
```
## 数据示例
| 信息编号 | 姓名 | 监区 | 性别 | 出生日期 | 民族 | 文化程度 | 罪名 | 刑期 | 刑期起日 | 刑期止日 | 入监时间 | 状态 |
|---------|------|------|------|---------|------|---------|------|------|---------|---------|---------|------|
| 10000 | 张伟强 | 第一监区 | 男 | 1985-03-15 | 汉 | 高中 | 抢劫罪 | 5年 | 2020-01-10 | 2025-01-10 | 2020-01-10 | 在押 |
| 10001 | 李芳娜 | A区 | 女 | 1992-08-22 | 汉族 | 本科 | 诈骗罪 | 3年 | 2022-05-20 | 2025-05-20 | 2022-05-20 | 在押 |

View File

@ -1,283 +0,0 @@
# 🔧 最终权限修复说明
## 📋 明确的需求
**用户需求**:无论任何时候,用户只能显示被分配权限的量表或问卷。
## ✅ 已完成的修复
### 1. 统一的权限逻辑
**简化后的逻辑**
```java
// 用户只能看到分配给自己的量表和问卷
if (scaleId != null && allowedScaleIds.contains(scaleId))
{
filtered.add(scale); // 有权限,添加
}
// 没有权限的,直接跳过
```
**特殊情况**
- **管理员**userId=1 或有管理权限):看到所有内容
- **普通用户**:只看到分配给自己的内容
### 2. 修改的文件
1. **PsyScaleController.java**
- `filterScaleListByPermission()` - 简化为单一判断
- `resolveAllowedScaleIdsForCurrentUser()` - 添加日志输出
2. **PsyQuestionnaireController.java**
- `filterQuestionnaireListByPermission()` - 简化为单一判断
- `resolveAllowedScaleIdsForCurrentUser()` - 添加日志输出
### 3. 添加的调试日志
```java
// 会输出以下日志:
logger.info("用户 userId={} 的权限列表: {}", currentUserId, scaleIds);
logger.info("权限过滤结果: 总数={}, 过滤后={}, 用户权限数={}", ...);
logger.debug("权限过滤: 允许访问 scaleId={}, scaleName={}", ...);
logger.debug("权限过滤: 拒绝访问 scaleId={}, scaleName={}", ...);
```
## 🔍 排查"不能正常显示"的问题
### 可能的原因
#### 原因 1: 权限数据不正确
**问题**:数据库中的权限记录可能有问题
**检查方法**
```sql
-- 查看用户的权限列表
SELECT * FROM psy_scale_permission
WHERE user_id = 你的用户ID AND status = '0';
```
**注意事项**
- 量表使用**正数ID**1, 2, 3
- 问卷使用**负数ID**(如:-1, -2, -3
#### 原因 2: 用户没有被分配权限
**问题**:用户在权限表中没有记录
**解决方法**:为用户分配权限
```sql
-- 为用户ID=2分配量表ID=1的权限
INSERT INTO psy_scale_permission (user_id, scale_id, status, create_by, create_time)
VALUES (2, 1, '0', 'admin', NOW());
-- 为用户ID=2分配问卷ID=1的权限注意使用负数
INSERT INTO psy_scale_permission (user_id, scale_id, status, create_by, create_time)
VALUES (2, -1, '0', 'admin', NOW());
```
#### 原因 3: ID映射错误
**问题**前端显示的问卷在权限表中没有对应的负数ID
**检查方法**
```sql
-- 检查问卷ID和权限表中的ID是否匹配
SELECT
q.questionnaire_id,
q.questionnaire_name,
-q.questionnaire_id as 权限表应该用的ID,
p.scale_id as 权限表实际的ID
FROM psy_questionnaire q
LEFT JOIN psy_scale_permission p ON -q.questionnaire_id = p.scale_id
WHERE p.user_id = 你的用户ID;
```
#### 原因 4: 用户被识别为管理员
**问题**用户ID=1 或有 `psychology:scale:list` 权限,导致看到所有内容
**检查方法**
```sql
-- 检查用户ID
SELECT user_id FROM sys_user WHERE user_name = '你的用户名';
-- 检查用户权限
SELECT r.role_name, m.perms
FROM sys_user u
INNER JOIN sys_user_role ur ON u.user_id = ur.user_id
INNER JOIN sys_role r ON ur.role_id = r.role_id
LEFT JOIN sys_role_menu rm ON r.role_id = rm.role_id
LEFT JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE u.user_name = '你的用户名';
```
## 📝 使用提供的工具
### 1. SQL检查脚本
运行 `检查权限数据.sql` 中的查询:
```sql
-- 1. 查看用户权限数量
SELECT u.user_id, u.user_name, COUNT(*) FROM ...
-- 2. 查看用户的详细权限
SELECT * FROM psy_scale_permission WHERE user_id = X ...
-- 3. 查看量表权限分配情况
SELECT s.scale_id, s.scale_name, COUNT(*) FROM ...
-- 10. 快速诊断
WITH user_permissions AS (...) ...
```
### 2. 后端日志
重新编译部署后,查看日志文件:
```bash
# 查找权限相关日志
tail -f logs/xinli.log | grep "权限"
# 应该看到类似输出:
# 用户 userId=2 的权限列表: [1, 3, -1, -2]
# 权限过滤结果: 总数=10, 过滤后=4, 用户权限数=4
```
## 🚀 部署步骤
### 1. 重新编译后端
```bash
cd c:\Users\Administrator\Desktop\Project\xinli
mvn clean package -DskipTests
```
### 2. 备份当前jar
```bash
# 备份当前运行的jar文件
copy ry-xinli-admin\target\ry-xinli-admin.jar ry-xinli-admin.jar.backup
```
### 3. 停止服务
```bash
# 根据你的部署方式停止服务
# systemctl stop xinli
# 或手动kill进程
```
### 4. 替换jar文件
```bash
# 将新编译的jar复制到部署目录
```
### 5. 启动服务
```bash
# 启动新服务
# systemctl start xinli
```
### 6. 查看日志
```bash
# 检查启动日志
tail -f logs/xinli.log
```
## 🧪 测试验证
### 测试步骤
1. **清除浏览器缓存**
- 按 Ctrl+Shift+Delete
- 清除所有缓存和Cookie
2. **使用普通用户登录**
- 不要使用admin或管理员账号
3. **查看问卷/量表列表**
- 记录看到的数量
4. **对比数据库**
- 运行SQL检查脚本
- 对比数量是否一致
### 预期结果
假设:
- 数据库中有 10 个量表、8 个问卷
- 用户被分配了 3 个量表、2 个问卷的权限
**应该看到**
- 量表列表3 个
- 问卷列表2 个
- 合计列表包含问卷5 个
## ❓ 常见问题
### Q1: 管理员看不到所有内容?
**A**: 检查用户ID是否为1或是否有管理权限
```sql
SELECT user_id FROM sys_user WHERE user_name = 'admin';
```
### Q2: 普通用户看到所有内容?
**A**: 可能被误判为管理员,检查权限配置
### Q3: 用户看不到任何内容?
**A**: 检查权限表中是否有该用户的记录
```sql
SELECT COUNT(*) FROM psy_scale_permission
WHERE user_id = X AND status = '0';
```
### Q4: 数量不一致?
**A**:
1. 检查前端是否缓存
2. 检查后端日志中的过滤数量
3. 对比数据库中的实际权限数
## 📊 数据一致性检查清单
- [ ] 权限表中的 scale_id 正确(量表用正数,问卷用负数)
- [ ] 用户有对应的权限记录
- [ ] 权限记录的 status = '0'(启用状态)
- [ ] 量表/问卷确实存在(没有孤立的权限记录)
- [ ] 用户不是管理员(除非确实需要看所有内容)
## 🎯 快速诊断命令
```sql
-- 一键诊断用户权限(替换 USER_ID
WITH user_info AS (
SELECT
2 as uid, -- 替换为实际用户ID
(SELECT COUNT(*) FROM psy_scale_permission WHERE user_id = 2 AND status = '0') as perm_count
),
expected AS (
SELECT
COUNT(DISTINCT s.scale_id) as scale_count,
COUNT(DISTINCT q.questionnaire_id) as quest_count
FROM user_info ui
CROSS JOIN psy_scale s
INNER JOIN psy_scale_permission p1 ON s.scale_id = p1.scale_id AND p1.user_id = ui.uid
CROSS JOIN psy_questionnaire q
INNER JOIN psy_scale_permission p2 ON -q.questionnaire_id = p2.scale_id AND p2.user_id = ui.uid
)
SELECT
ui.uid as 用户ID,
ui.perm_count as 总权限数,
e.scale_count as 应看到量表数,
e.quest_count as 应看到问卷数,
(e.scale_count + e.quest_count) as 应看到总数
FROM user_info ui, expected e;
```
---
**修复完成时间**2024年12月2日
**版本**v3.0 - 最终简化版
**下一步**:重新编译部署 + 使用SQL脚本检查数据

View File

@ -1,243 +0,0 @@
# 🔧 Android App 朗读功能修复说明
## 📋 问题描述
在 Android App 中,用户登录后进行量表测评时,朗读功能按钮呈灰色禁用状态,无法使用。
## 🔍 问题根源
### 核心问题TTS 异步初始化导致的时序问题
1. **Android 端**`TtsHelper.java` 中的 TTS 引擎初始化是**异步**的
- TTS 引擎通过 `OnInitListener` 回调初始化
- `isReady` 标志只有在回调成功后才设置为 `true`
- 初始化需要一定时间(通常 100-500ms
2. **前端端**:页面在 `created()` 生命周期钩子中立即检查 TTS 可用性
- 调用 `window.AndroidTTS.isAvailable()`
- 此时 TTS 可能还未初始化完成
- 返回 `false`,导致 `isTtsSupported = false`
3. **结果**:所有朗读按钮被禁用
- 按钮属性:`:disabled="!isTtsSupported"`
- 用户看到的是灰色不可点击的按钮
## ✅ 修复方案
### 1. Android 端修复 (TtsHelper.java)
#### 修改点 1: isAvailable() 方法
**修改前**
```java
@JavascriptInterface
public boolean isAvailable() {
return isReady; // 等待异步初始化完成
}
```
**修改后**
```java
@JavascriptInterface
public boolean isAvailable() {
// 只要TTS对象存在就返回true不等待异步初始化完成
// 这样前端可以立即启用朗读按钮
return tts != null;
}
```
#### 修改点 2: speak() 方法增加延迟重试机制
**新增功能**
- 如果 TTS 未就绪,自动延迟 500ms 后重试
- 避免因初始化未完成而静默失败
- 提供更好的用户体验
### 2. 前端端修复
#### 2.1 测评答题页面 (assessment/taking.vue)
**新增延迟检查逻辑**
```javascript
initTts() {
if (window.AndroidTTS && typeof window.AndroidTTS.isAvailable === 'function') {
if (window.AndroidTTS.isAvailable()) {
this.isTtsSupported = true;
console.log('✅ 使用Android原生TTS');
return;
} else {
// TTS可能还在初始化中延迟重试
setTimeout(() => {
if (window.AndroidTTS && window.AndroidTTS.isAvailable()) {
this.isTtsSupported = true;
console.log('✅ Android TTS 已就绪(延迟检测)');
} else {
this.fallbackToBrowserTts();
}
}, 500);
// 先启用按钮,避免用户等待
this.isTtsSupported = true;
return;
}
}
this.fallbackToBrowserTts();
}
```
#### 2.2 问卷答题页面 (questionnaire/taking.vue)
**新增 Android TTS 支持**
- 之前只支持浏览器 TTS
- 现在添加了与测评页面相同的 Android TTS 支持
- 确保所有答题页面功能一致
## 📝 修改文件清单
### Android 代码
- ✅ `xinli-App/app/src/main/java/com/xinli/app/TtsHelper.java`
- 修改 `isAvailable()` 方法
- 改进 `speak()` 方法,添加延迟重试
### 前端代码
- ✅ `xinli-ui/src/views/psychology/assessment/taking.vue`
- 改进 `initTts()` 方法
- 新增 `fallbackToBrowserTts()` 方法
- ✅ `xinli-ui/src/views/psychology/questionnaire/taking.vue`
- 添加 Android TTS 支持
- 统一 `initTts()` 逻辑
- 更新 `speakText()``stopSpeaking()` 方法
## 🚀 重新打包和部署步骤
### 步骤 1: 重新构建前端
```bash
cd c:\Users\Administrator\Desktop\Project\xinli\xinli-ui
npm run build:prod
```
### 步骤 2: 将构建好的前端文件复制到服务器
`xinli-ui/dist` 目录的文件部署到服务器的 Web 目录。
### 步骤 3: 打包 Android App
```bash
cd c:\Users\Administrator\Desktop\Project\xinli\xinli-App
.\打包正式版APK.bat
```
或者在 Android Studio 中:
1. 打开项目
2. 点击 `Build``Build Bundle(s) / APK(s)``Build APK(s)`
3. 等待构建完成
### 步骤 4: 查找生成的 APK
运行:
```bash
.\查找APK.bat
```
APK 通常位于:
- `xinli-App/app/build/outputs/apk/release/app-release.apk`
### 步骤 5: 安装测试
1. 卸载旧版本 App重要
2. 安装新的 APK
3. 打开 App登录账号
4. 进入量表测评或问卷答题页面
5. 测试朗读功能
## ✅ 测试检查清单
### 测评答题页面
- [ ] 朗读按钮不再是灰色
- [ ] 点击"朗读全部"能听到声音
- [ ] 点击"朗读题干"能听到声音
- [ ] 点击选项旁的朗读按钮能听到声音
- [ ] 声音清晰,中文标准
- [ ] 可以正常停止朗读
- [ ] 切换题目后朗读功能正常
### 问卷答题页面
- [ ] 朗读按钮不再是灰色
- [ ] "朗读全部"功能正常
- [ ] "朗读题干"功能正常
- [ ] 选项朗读功能正常
- [ ] 各种题型(单选、多选、填空等)的朗读功能都正常
### 控制台日志检查
打开 Chrome DevTools如果是调试版查看控制台
- ✅ 应该看到:`✅ 使用Android原生TTS`
- ✅ 或者:`✅ Android TTS 已就绪(延迟检测)`
- ❌ 不应该看到:朗读按钮被禁用的警告
## 🎯 技术改进说明
### 优化点 1: 消除时序依赖
- **之前**:前端必须等待 Android TTS 完全初始化
- **现在**立即启用按钮TTS 在后台完成初始化
### 优化点 2: 双重保障机制
- **第一层**Android 端延迟重试500ms
- **第二层**前端端延迟检查500ms
- 确保 TTS 功能高可用性
### 优化点 3: 降级策略
- Android TTS 不可用时自动降级到浏览器 TTS
- 确保功能在所有环境都能工作
### 优化点 4: 统一体验
- 测评和问卷两个页面使用相同的 TTS 逻辑
- 代码一致性提高,便于维护
## ❓ 常见问题
### Q1: 重新安装后还是没声音?
**A**: 检查以下几点:
1. 确认已**完全卸载旧版本** App
2. 检查手机**媒体音量**(不是铃声音量)
3. 确保不是静音模式
4. 重启 App
5. 查看控制台日志,确认使用了 Android TTS
### Q2: 声音很奇怪/机器人声音?
**A**:
- 某些手机的默认 TTS 引擎音质较差
- 可以在手机设置中更换 TTS 引擎:
- 系统设置 → 辅助功能 → 文字转语音
- 推荐安装Google 文字转语音引擎
### Q3: 浏览器中还能用吗?
**A**:
- ✅ 完全兼容!
- 浏览器中自动使用浏览器 TTS
- PC 端功能不受影响
### Q4: 如何确认使用的是哪种 TTS
**A**:
打开 Chrome DevTools查看控制台日志
- `✅ 使用Android原生TTS` - Android 原生
- `✅ 使用浏览器TTS` - 浏览器 TTS
## 📊 修复效果
| 项目 | 修复前 | 修复后 |
|------|--------|--------|
| 朗读按钮状态 | ❌ 灰色禁用 | ✅ 正常可用 |
| TTS 初始化时间 | 等待完成(阻塞) | 异步加载(不阻塞) |
| 用户体验 | 功能不可用 | 立即可用 |
| 降级策略 | ❌ 无 | ✅ 自动降级到浏览器TTS |
| 问卷答题支持 | ❌ 仅浏览器TTS | ✅ Android原生TTS |
| 代码一致性 | 测评和问卷不同 | 统一实现 |
## 🎉 总结
此次修复完全解决了朗读功能在 Android App 中被禁用的问题:
1. ✅ **根本问题已修复**TTS 异步初始化不再阻塞功能使用
2. ✅ **用户体验改善**:朗读按钮立即可用,无需等待
3. ✅ **功能完整性**:测评和问卷页面都支持朗读
4. ✅ **健壮性提升**:双重保障+自动降级,确保高可用性
5. ✅ **代码质量**:统一实现,易于维护
---
**修复日期**: 2024年12月2日
**修复版本**: v1.1

View File

@ -1,261 +0,0 @@
# 🔧 权限逻辑修复说明
## 📋 问题描述
**用户反馈**
- ❌ 分配了权限的问卷/量表**看不到**
- ✅ 没有分配权限的问卷/量表**却能看到**
- 权限逻辑完全相反!
## 🔍 问题根源
### 原来的错误逻辑
```java
// 错误逻辑(已修复)
if ("questionnaire".equalsIgnoreCase(sourceType))
{
boolean restricted = restrictedScaleIds != null && restrictedScaleIds.contains(scaleId);
if (!restricted) // 如果未配置权限,就显示 ❌
{
filtered.add(scale);
continue;
}
}
if (scaleId != null && allowedScaleIds.contains(scaleId)) // 如果有权限,才显示
{
filtered.add(scale);
}
```
**问题**
1. 未配置权限的问卷/量表 → 对所有人开放(错误!)
2. 已配置权限的问卷/量表 → 只有授权用户能看到
3. 结果:用户看到的都是"没分配权限"的内容
### 正确的逻辑
```java
// 正确逻辑(已修复)
if (scaleId != null && allowedScaleIds.contains(scaleId))
{
filtered.add(scale);
}
```
**修复后**
- ✅ 用户只能看到**分配给自己**的问卷/量表
- ❌ 未分配的问卷/量表**完全看不到**
## ✅ 已修复内容
### 修改文件
1. **PsyQuestionnaireController.java**
- 修改 `filterQuestionnaireListByPermission()` 方法
- 移除错误的"公开未配置权限问卷"逻辑
- 改为:只显示用户有权限的问卷
2. **PsyScaleController.java**
- 修改 `filterScaleListByPermission()` 方法
- 统一量表和问卷的权限逻辑
- 移除 `resolveRestrictedScaleIds()` 方法(不再需要)
### 核心修改
#### 修改前
```java
// 如果该问卷未配置权限,对所有人开放 ❌
boolean restricted = restrictedScaleIds != null && restrictedScaleIds.contains(scaleId);
if (!restricted)
{
filtered.add(questionnaire);
continue;
}
// 如果配置了权限,检查用户是否有权限
if (allowedScaleIds.contains(scaleId))
{
filtered.add(questionnaire);
}
```
#### 修改后
```java
// 只显示用户有权限的问卷 ✅
if (allowedScaleIds.contains(scaleId))
{
filtered.add(questionnaire);
}
```
## 🎯 修复后的行为
### 场景 1: 管理员userId = 1 或有管理权限)
- ✅ 看到**所有**问卷和量表
- 理由:`allowedScaleIds = null`,不进行权限过滤
### 场景 2: 普通用户student 角色)
**假设**
- 系统中有 5 个问卷A、B、C、D、E
- 用户被分配了问卷 A、B 的权限
**修复前**
- ❌ 能看到C、D、E未分配权限的
- ❌ 看不到A、B已分配权限的
**修复后**
- ✅ 能看到A、B已分配权限的
- ✅ 看不到C、D、E未分配权限的
## 📊 权限分配说明
### 如何为用户分配问卷权限
#### 方法 1: 通过用户管理界面
1. 进入 **系统管理** → **用户管理**
2. 找到目标用户,点击 **编辑**
3. 在"量表权限"选项卡中勾选该用户可访问的问卷
4. 保存
#### 方法 2: 通过 SQL 直接插入
```sql
-- 为用户分配问卷权限
-- 注意问卷ID使用负数-questionnaireId
INSERT INTO psy_scale_permission (user_id, scale_id, status, create_by, create_time)
VALUES
(用户ID, -问卷ID, '0', 'admin', NOW());
-- 示例为用户ID=2 分配问卷ID=1 的权限
INSERT INTO psy_scale_permission (user_id, scale_id, status, create_by, create_time)
VALUES (2, -1, '0', 'admin', NOW());
-- 为用户ID=2 分配量表ID=3 的权限
INSERT INTO psy_scale_permission (user_id, scale_id, status, create_by, create_time)
VALUES (2, 3, '0', 'admin', NOW());
```
### 问卷ID的特殊标识
**重要**:在权限表中,问卷使用**负数ID**
- **量表**:使用正数 ID (1, 2, 3, ...)
- **问卷**:使用负数 ID (-1, -2, -3, ...)
**原因**:量表和问卷使用同一张权限表,用负数区分
**示例**
```
问卷ID = 5 → 权限表中存储为 scale_id = -5
量表ID = 5 → 权限表中存储为 scale_id = 5
```
## 🧪 测试验证
### 测试步骤
#### 准备数据
```sql
-- 1. 创建测试用户假设ID=100
INSERT INTO sys_user (user_name, nick_name, password, status)
VALUES ('test_user', '测试用户', '加密后的密码', '0');
-- 2. 分配学员角色
-- (根据你的系统具体操作)
-- 3. 分配权限只分配问卷ID=1和问卷ID=2的权限
INSERT INTO psy_scale_permission (user_id, scale_id, status, create_by, create_time)
VALUES
(100, -1, '0', 'admin', NOW()),
(100, -2, '0', 'admin', NOW());
```
#### 测试登录
1. 使用 test_user 登录系统
2. 进入"心理测评"或"学员测试"页面
3. **预期结果**
- ✅ 只能看到问卷1和问卷2
- ❌ 看不到其他问卷3、4、5...
#### 测试管理员
1. 使用管理员账号登录
2. 进入"心理测评"页面
3. **预期结果**
- ✅ 能看到所有问卷和量表
## 🚀 部署步骤
### 1. 重新编译后端
```bash
cd c:\Users\Administrator\Desktop\Project\xinli
mvn clean package -DskipTests
```
### 2. 部署新的 jar 文件
```bash
# 停止旧服务
# 根据你的部署方式systemd、supervisor、手动等
# 部署新 jar
# 位置ry-xinli-admin/target/ry-xinli-admin.jar
# 启动新服务
```
### 3. 验证修复
1. 清除浏览器缓存
2. 使用普通用户登录
3. 查看问卷列表
4. 确认只能看到已分配权限的问卷
## 📝 注意事项
### 1. 前端无需修改
- ✅ 前端代码不需要改动
- ✅ 权限过滤在后端完成
- ✅ 前端调用 API 保持不变
### 2. 数据库无需修改
- ✅ 使用现有的 `psy_scale_permission`
- ✅ 不需要执行 SQL 脚本
- ✅ 现有权限数据继续有效
### 3. 向后兼容性
- ⚠️ **行为变化**:修复后,未分配权限的用户看不到任何问卷
- ⚠️ 需要为现有用户重新分配权限
- ⚠️ 建议先在测试环境验证
### 4. 批量分配权限
如果需要为多个用户批量分配权限:
```sql
-- 为所有学员角色的用户分配问卷1的权限
INSERT INTO psy_scale_permission (user_id, scale_id, status, create_by, create_time)
SELECT u.user_id, -1, '0', 'admin', NOW()
FROM sys_user u
INNER JOIN sys_user_role ur ON u.user_id = ur.user_id
INNER JOIN sys_role r ON ur.role_id = r.role_id
WHERE r.role_key = 'student'
AND NOT EXISTS (
SELECT 1 FROM psy_scale_permission p
WHERE p.user_id = u.user_id AND p.scale_id = -1
);
```
## ✅ 修复总结
| 项目 | 修复前 | 修复后 |
|------|--------|--------|
| **权限逻辑** | ❌ 相反 | ✅ 正确 |
| **用户看到的** | 未分配的内容 | 已分配的内容 |
| **管理员** | ✅ 看所有 | ✅ 看所有 |
| **普通用户** | ❌ 看错误内容 | ✅ 只看授权内容 |
| **代码复杂度** | 复杂(双重判断) | 简化(单一判断) |
---
**修复时间**2024年12月2日
**修复人员**Cascade AI
**影响范围**:问卷和量表列表的权限控制
**部署状态**:⏳ 待重新编译部署后端

View File

@ -1,187 +0,0 @@
# 检查二维码URL的方法
## 方法一:在二维码管理页面查看(推荐)
### 步骤:
1. 登录管理系统
2. 进入"二维码管理"页面
3. 找到要检查的二维码记录
4. 点击"查看二维码"按钮
5. 在二维码预览对话框中,可以看到二维码图片
6. **查看浏览器控制台**
- 按 `F12` 打开开发者工具
- 切换到 `Console`(控制台)标签
- 在控制台中输入以下代码查看二维码URL
```javascript
// 查看当前页面的二维码数据
console.log('二维码列表数据:', document.querySelectorAll('.el-table__body tr'));
```
### 或者直接查看网络请求:
1. 打开浏览器开发者工具F12
2. 切换到 `Network`(网络)标签
3. 刷新二维码管理页面
4. 找到获取二维码列表的请求(通常是 `/psychology/qrcode/list`
5. 查看响应数据,找到 `qrcodeUrl` 字段
6. 或者找到获取二维码图片的请求(通常是 `/psychology/qrcode/image/{qrcodeId}`
7. 查看响应数据中的URL
## 方法二:解码二维码查看内容
### 使用在线工具:
1. 访问在线二维码解码工具,例如:
- https://www.qrcode-monkey.com/qr-code-decoder/
- https://zxing.org/w/decode.jspx
2. 上传二维码图片或输入二维码图片URL
3. 查看解码后的文本内容这就是二维码的URL
### 使用手机扫码:
1. 使用手机扫描二维码
2. 查看手机浏览器地址栏中的URL
3. 检查URL是否正确
- ✅ 正确格式:`http://192.168.1.183/psychology/qrcode/scan/xxx`
- ❌ 错误格式:`http://192.168.1.183:30081//psychology/qrcode/scan/xxx`
## 方法三:检查后端日志
### 步骤:
1. 查看后端控制台日志
2. 找到二维码生成相关的日志
3. 查找包含 `buildServerAddress``buildQrcodeContent` 的日志
4. 查看实际生成的URL
### 添加调试日志(可选):
如果需要更详细的日志,可以在代码中添加:
```java
// 在 PsyQrcodeController.java 的 enrichQrcodeWithBase64() 方法中
String serverAddress = buildServerAddress();
log.info("构建的服务器地址: {}", serverAddress);
// 在 PsyQrcodeServiceImpl.java 的 buildQrcodeContent() 方法中
String content = buildQrcodeContent(qrcode, serverAddress);
log.info("生成的二维码URL: {}", content);
```
## 方法四直接测试URL访问
### 步骤:
1. 从二维码管理页面获取二维码编码(`qrcodeCode`
2. 手动构建URL`http://192.168.1.183/psychology/qrcode/scan/{qrcodeCode}`
3. 在浏览器中直接访问该URL
4. 检查是否能正常访问:
- ✅ 能访问URL正确
- ❌ 不能访问检查URL格式和网络连接
## 方法五:使用浏览器开发者工具检查
### 步骤:
1. 打开二维码管理页面
2. 按 `F12` 打开开发者工具
3. 切换到 `Network`(网络)标签
4. 点击"查看二维码"按钮
5. 在Network标签中找到获取二维码图片的请求
6. 查看请求URL和响应数据
7. 检查响应中的 `qrcodeUrl` 字段
### 或者使用控制台:
在浏览器控制台中输入:
```javascript
// 获取二维码图片的Base64数据
const qrcodeImage = document.querySelector('img[src^="data:image"]');
if (qrcodeImage) {
console.log('二维码图片URL:', qrcodeImage.src);
// Base64数据很长可以截取前100个字符查看
console.log('二维码URL前缀:', qrcodeImage.src.substring(0, 100));
}
```
## 检查URL正确性的标准
### ✅ 正确的URL格式
- `http://192.168.1.183/psychology/qrcode/scan/xxx`
- `http://localhost/psychology/qrcode/scan/xxx`
- 不包含 `:30081` 端口号
- 不包含双斜杠 `//`
- 路径格式正确:`/psychology/qrcode/scan/{qrcodeCode}`
### ❌ 错误的URL格式
- `http://192.168.1.183:30081/psychology/qrcode/scan/xxx`包含30081端口
- `http://192.168.1.183//psychology/qrcode/scan/xxx`(包含双斜杠)
- `http://192.168.1.183:30081//psychology/qrcode/scan/xxx`(包含端口和双斜杠)
## 快速检查脚本
在浏览器控制台中运行以下脚本可以快速检查所有二维码的URL
```javascript
// 检查二维码列表中的URL
fetch('/dev-api/psychology/qrcode/list', {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
})
.then(res => res.json())
.then(data => {
if (data.code === 200 && data.rows) {
console.log('二维码列表:');
data.rows.forEach((qrcode, index) => {
console.log(`${index + 1}. 编码: ${qrcode.qrcodeCode}`);
console.log(` 类型: ${qrcode.qrcodeType}`);
// 如果有qrcodeUrl检查URL格式
if (qrcode.qrcodeUrl) {
const urlMatch = qrcode.qrcodeUrl.match(/http[s]?:\/\/[^"']+/);
if (urlMatch) {
const url = urlMatch[0];
console.log(` URL: ${url}`);
// 检查是否有问题
if (url.includes(':30081')) {
console.warn(' ⚠️ 警告URL包含30081端口');
}
if (url.includes('//')) {
console.warn(' ⚠️ 警告URL包含双斜杠');
}
}
}
});
}
});
```
## 常见问题排查
### 问题1URL包含30081端口
**原因**`buildServerAddress()` 方法没有正确识别后端端口
**解决**:检查后端日志,确认 `serverPort` 是否为30081
### 问题2URL包含双斜杠
**原因**:路径拼接时产生了双斜杠
**解决**:检查 `contextPath` 是否为 `/`,如果是则应该被忽略
### 问题3URL无法访问
**原因**
- URL格式错误
- 网络连接问题
- 前端服务未运行
**解决**
- 检查URL格式
- 确认前端服务运行在80端口
- 确认手机和电脑在同一局域网内
## 推荐检查流程
1. **生成二维码后立即检查**
- 在二维码管理页面查看二维码
- 使用浏览器开发者工具查看网络请求
- 检查URL格式是否正确
2. **测试扫码功能**
- 使用手机扫描二维码
- 检查是否能正常访问
- 查看手机浏览器地址栏中的URL
3. **如果发现问题**
- 检查后端日志
- 重新生成二维码
- 确认修复后再次测试

View File

@ -1,88 +0,0 @@
-- ============================================
-- 检查和清理"所有用户"权限记录
-- ============================================
-- 说明:在权限系统中,如果 psy_scale_permission 表中存在
-- user_id、role_id、dept_id 都为空的记录,则表示"所有用户"都有权限
-- 这可能导致用户看到未明确授权的量表/问卷
-- ============================================
-- 1. 查看所有"所有用户"权限记录
SELECT
p.permission_id,
p.scale_id,
CASE
WHEN p.scale_id < 0 THEN (SELECT questionnaire_name FROM psy_questionnaire WHERE questionnaire_id = -p.scale_id)
ELSE (SELECT scale_name FROM psy_scale WHERE scale_id = p.scale_id)
END AS scale_name,
CASE
WHEN p.scale_id < 0 THEN '问卷'
ELSE '量表'
END AS type,
p.start_time,
p.end_time,
p.status,
p.create_time,
p.remark
FROM psy_scale_permission p
WHERE p.user_id IS NULL
AND p.role_id IS NULL
AND p.dept_id IS NULL;
-- 2. 统计"所有用户"权限的数量
SELECT
COUNT(*) AS all_users_permission_count,
SUM(CASE WHEN status = '0' THEN 1 ELSE 0 END) AS active_count,
SUM(CASE WHEN status = '1' THEN 1 ELSE 0 END) AS inactive_count
FROM psy_scale_permission
WHERE user_id IS NULL
AND role_id IS NULL
AND dept_id IS NULL;
-- 3. 查看哪些量表/问卷被设置为"所有用户"可见
SELECT
CASE
WHEN p.scale_id < 0 THEN '问卷'
ELSE '量表'
END AS type,
CASE
WHEN p.scale_id < 0 THEN (SELECT questionnaire_name FROM psy_questionnaire WHERE questionnaire_id = -p.scale_id)
ELSE (SELECT scale_name FROM psy_scale WHERE scale_id = p.scale_id)
END AS name,
p.scale_id,
p.status,
p.start_time,
p.end_time
FROM psy_scale_permission p
WHERE p.user_id IS NULL
AND p.role_id IS NULL
AND p.dept_id IS NULL
AND p.status = '0' -- 只看有效的
ORDER BY type, name;
-- ============================================
-- 清理操作(谨慎执行!)
-- ============================================
-- 4. 【可选】禁用所有"所有用户"权限(将状态改为无效)
-- UPDATE psy_scale_permission
-- SET status = '1',
-- update_time = NOW(),
-- remark = CONCAT(IFNULL(remark, ''), ' [已禁用-原为所有用户权限]')
-- WHERE user_id IS NULL
-- AND role_id IS NULL
-- AND dept_id IS NULL
-- AND status = '0';
-- 5. 【可选】删除所有"所有用户"权限记录
-- DELETE FROM psy_scale_permission
-- WHERE user_id IS NULL
-- AND role_id IS NULL
-- AND dept_id IS NULL;
-- 6. 【可选】删除指定量表/问卷的"所有用户"权限
-- 将 YOUR_SCALE_ID 替换为实际的量表ID
-- DELETE FROM psy_scale_permission
-- WHERE scale_id = YOUR_SCALE_ID
-- AND user_id IS NULL
-- AND role_id IS NULL
-- AND dept_id IS NULL;

View File

@ -1,180 +0,0 @@
-- ========================================
-- 权限数据检查 SQL 脚本
-- ========================================
-- 1. 查看所有用户及其权限数量
SELECT
u.user_id,
u.user_name,
u.nick_name,
COUNT(DISTINCT p.scale_id) as
FROM sys_user u
LEFT JOIN psy_scale_permission p ON u.user_id = p.user_id AND p.status = '0'
GROUP BY u.user_id, u.user_name, u.nick_name
ORDER BY u.user_id;
-- 2. 查看某个用户的权限详情(替换 {USER_ID} 为实际用户ID
-- 示例查看用户ID=2的权限
SELECT
p.permission_id,
p.user_id,
p.scale_id,
CASE
WHEN p.scale_id > 0 THEN '量表'
WHEN p.scale_id < 0 THEN '问卷'
ELSE '未知'
END as ,
CASE
WHEN p.scale_id > 0 THEN s.scale_name
WHEN p.scale_id < 0 THEN q.questionnaire_name
ELSE NULL
END as ,
p.status,
p.create_time
FROM psy_scale_permission p
LEFT JOIN psy_scale s ON p.scale_id = s.scale_id AND p.scale_id > 0
LEFT JOIN psy_questionnaire q ON p.scale_id = -q.questionnaire_id AND p.scale_id < 0
WHERE p.user_id = 2 -- 替换为实际用户ID
AND p.status = '0'
ORDER BY p.scale_id;
-- 3. 查看所有量表及其权限分配情况
SELECT
s.scale_id,
s.scale_name,
s.status as ,
COUNT(DISTINCT p.user_id) as
FROM psy_scale s
LEFT JOIN psy_scale_permission p ON s.scale_id = p.scale_id AND p.status = '0'
GROUP BY s.scale_id, s.scale_name, s.status
ORDER BY s.scale_id;
-- 4. 查看所有问卷及其权限分配情况
SELECT
q.questionnaire_id,
q.questionnaire_name,
q.status as ,
-q.questionnaire_id as scale_id,
COUNT(DISTINCT p.user_id) as
FROM psy_questionnaire q
LEFT JOIN psy_scale_permission p ON -q.questionnaire_id = p.scale_id AND p.status = '0'
GROUP BY q.questionnaire_id, q.questionnaire_name, q.status
ORDER BY q.questionnaire_id;
-- 5. 检查权限数据一致性(查找孤立的权限记录)
-- 这些权限指向的量表/问卷不存在
SELECT
p.permission_id,
p.user_id,
p.scale_id,
p.create_time,
'权限记录存在但量表/问卷不存在' as
FROM psy_scale_permission p
WHERE p.status = '0'
AND p.scale_id > 0
AND NOT EXISTS (SELECT 1 FROM psy_scale s WHERE s.scale_id = p.scale_id)
UNION ALL
SELECT
p.permission_id,
p.user_id,
p.scale_id,
p.create_time,
'权限记录存在但问卷不存在' as
FROM psy_scale_permission p
WHERE p.status = '0'
AND p.scale_id < 0
AND NOT EXISTS (SELECT 1 FROM psy_questionnaire q WHERE -q.questionnaire_id = p.scale_id);
-- 6. 为指定用户分配所有量表权限(示例)
-- 注意:替换 {USER_ID} 为实际用户ID
/*
INSERT INTO psy_scale_permission (user_id, scale_id, status, create_by, create_time)
SELECT
2 as user_id, -- 替换为实际用户ID
scale_id,
'0',
'admin',
NOW()
FROM psy_scale
WHERE NOT EXISTS (
SELECT 1 FROM psy_scale_permission p
WHERE p.user_id = 2
AND p.scale_id = psy_scale.scale_id
);
*/
-- 7. 为指定用户分配所有问卷权限(示例)
-- 注意使用负数ID
/*
INSERT INTO psy_scale_permission (user_id, scale_id, status, create_by, create_time)
SELECT
2 as user_id, -- 替换为实际用户ID
-questionnaire_id as scale_id,
'0',
'admin',
NOW()
FROM psy_questionnaire
WHERE NOT EXISTS (
SELECT 1 FROM psy_scale_permission p
WHERE p.user_id = 2
AND p.scale_id = -psy_questionnaire.questionnaire_id
);
*/
-- 8. 删除某个用户的所有权限(示例)
/*
DELETE FROM psy_scale_permission
WHERE user_id = 2; -- 替换为实际用户ID
*/
-- 9. 检查特定量表/问卷的权限分配
-- 量表ID=1的权限分配情况
SELECT
u.user_id,
u.user_name,
u.nick_name,
p.status,
p.create_time
FROM psy_scale_permission p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE p.scale_id = 1 -- 量表ID
ORDER BY p.create_time DESC;
-- 问卷ID=1的权限分配情况注意使用负数
SELECT
u.user_id,
u.user_name,
u.nick_name,
p.status,
p.create_time
FROM psy_scale_permission p
INNER JOIN sys_user u ON p.user_id = u.user_id
WHERE p.scale_id = -1 -- 问卷ID=1 使用 -1
ORDER BY p.create_time DESC;
-- 10. 快速诊断:查看用户应该能看到什么
-- 替换 {USER_ID} 为实际用户ID
WITH user_permissions AS (
SELECT scale_id
FROM psy_scale_permission
WHERE user_id = 2 -- 替换为实际用户ID
AND status = '0'
)
SELECT
'量表' as ,
s.scale_id,
s.scale_name as ,
s.status as ,
CASE WHEN up.scale_id IS NOT NULL THEN '有权限' ELSE '无权限' END as
FROM psy_scale s
LEFT JOIN user_permissions up ON s.scale_id = up.scale_id
UNION ALL
SELECT
'问卷' as ,
-q.questionnaire_id as scale_id,
q.questionnaire_name as ,
q.status as ,
CASE WHEN up.scale_id IS NOT NULL THEN '有权限' ELSE '无权限' END as
FROM psy_questionnaire q
LEFT JOIN user_permissions up ON -q.questionnaire_id = up.scale_id
ORDER BY , scale_id;

View File

@ -1,42 +0,0 @@
-- 检查重复的信息编号 SQL 脚本
-- 用于排查用户导入时"信息编号已存在"的问题
-- 1. 检查这些信息编号在数据库中是否真的存在
SELECT
info_number AS ,
user_name AS ,
create_time AS ,
create_by AS
FROM psy_user_profile
WHERE info_number IN ('34', '99', '114', '120', '121', '122', '123', '124')
ORDER BY info_number;
-- 2. 检查数据库中所有重复的信息编号
SELECT
info_number AS ,
COUNT(*) AS ,
GROUP_CONCAT(user_name) AS ,
GROUP_CONCAT(profile_id) AS ID列表
FROM psy_user_profile
WHERE info_number IS NOT NULL AND info_number != ''
GROUP BY info_number
HAVING COUNT(*) > 1
ORDER BY COUNT(*) DESC;
-- 3. 查看最近导入的用户最近100条
SELECT
info_number AS ,
user_name AS ,
create_time AS ,
create_by AS
FROM psy_user_profile
ORDER BY create_time DESC
LIMIT 100;
-- 4. 统计信息编号的使用情况
SELECT
COUNT(DISTINCT info_number) AS ,
COUNT(*) AS ,
(COUNT(*) - COUNT(DISTINCT info_number)) AS
FROM psy_user_profile
WHERE info_number IS NOT NULL AND info_number != '';

View File

@ -1,43 +0,0 @@
-- ============================================
-- 清理已删除用户的档案数据
-- 用途:解决"信息编号已存在"但列表中看不到的问题
-- 日期2024-12-02
-- ============================================
-- 第一步:查看要删除的数据(确认)
SELECT
p.profile_id AS '档案ID',
p.info_number AS '信息编号',
p.user_id AS '关联用户ID',
u.user_name AS '用户账号',
u.del_flag AS '删除标记',
CASE
WHEN u.user_id IS NULL THEN '用户不存在'
WHEN u.del_flag = '2' THEN '用户已删除'
ELSE '正常'
END AS '状态',
p.create_time AS '创建时间'
FROM psy_user_profile p
LEFT JOIN sys_user u ON p.user_id = u.user_id
WHERE u.del_flag = '2' OR u.user_id IS NULL
ORDER BY CAST(p.info_number AS UNSIGNED);
-- 第二步:备份要删除的数据(可选但推荐)
CREATE TABLE IF NOT EXISTS psy_user_profile_deleted_backup_20241202 AS
SELECT p.*
FROM psy_user_profile p
LEFT JOIN sys_user u ON p.user_id = u.user_id
WHERE u.del_flag = '2' OR u.user_id IS NULL;
-- 第三步:删除已删除用户的档案
DELETE p FROM psy_user_profile p
LEFT JOIN sys_user u ON p.user_id = u.user_id
WHERE u.del_flag = '2' OR u.user_id IS NULL;
-- 查看删除结果
SELECT CONCAT('已删除 ', ROW_COUNT(), ' 条档案数据') AS '执行结果';
-- ============================================
-- 如果只想删除特定信息编号的档案如114
-- ============================================
-- DELETE FROM psy_user_profile WHERE info_number = '114';

View File

@ -1,179 +0,0 @@
# 用户导入性能优化说明
## 问题分析
### 原有性能瓶颈
1. **逐条处理**使用for循环逐条处理每个用户
2. **频繁数据库查询**每条记录都要查询用户是否存在N+1问题
3. **单条插入/更新**每条记录单独执行INSERT/UPDATE操作
4. **重复查询配置**:每次导入都查询默认密码配置
### 性能影响
- 导入3000条数据需要执行 **3000+ 次数据库查询**
- 导入3000条数据需要执行 **3000+ 次INSERT操作**
- 预计导入时间:**15-30分钟**(取决于网络和数据库性能)
## 优化方案
### 1. 批量查询已存在用户
**优化前**
```java
// 每条记录查询一次
SysUser u = userMapper.selectUserByUserName(user.getUserName());
```
**优化后**
```java
// 一次性查询所有用户名
List<SysUser> existingUsers = userMapper.selectUsersByUserNames(userNameList);
Map<String, SysUser> existingUserMap = existingUsers.stream()
.collect(Collectors.toMap(SysUser::getUserName, u -> u));
```
### 2. 批量插入新用户
**优化前**
```java
// 逐条插入
for (SysUser user : userList) {
userMapper.insertUser(user);
}
```
**优化后**
```java
// 批量插入每批500条
int batchSize = 500;
for (int i = 0; i < usersToInsert.size(); i += batchSize) {
List<SysUser> batch = usersToInsert.subList(i, end);
userMapper.batchInsertUser(batch);
}
```
### 3. 数据预处理
- 一次性获取默认密码配置
- 数据验证前置,减少无效数据的数据库操作
- 使用Map缓存已存在用户避免重复查询
### 4. 失败降级机制
批量插入失败时,自动降级为逐条插入,确保数据完整性
## 技术实现
### 新增Mapper方法
**SysUserMapper.java**
```java
/**
* 批量新增用户信息
*/
public int batchInsertUser(@Param("list") List<SysUser> userList);
/**
* 批量查询用户名是否存在
*/
public List<SysUser> selectUsersByUserNames(@Param("userNames") List<String> userNames);
```
**SysUserMapper.xml**
```xml
<!-- 批量插入用户 -->
<insert id="batchInsertUser" parameterType="java.util.List">
insert into sys_user(...) values
<foreach collection="list" item="item" separator=",">
(#{item.deptId}, #{item.userName}, ...)
</foreach>
</insert>
<!-- 批量查询用户名 -->
<select id="selectUsersByUserNames" resultMap="SysUserResult">
<include refid="selectUserVo"/>
where u.user_name in
<foreach collection="userNames" item="userName" open="(" separator="," close=")">
#{userName}
</foreach>
</select>
```
## 性能提升
### 数据库操作次数对比
| 操作 | 优化前 | 优化后 | 减少比例 |
|------|--------|--------|----------|
| 查询用户是否存在 | 3000次 | 1次 | **99.97%** |
| 插入用户记录 | 3000次 | 6次500条/批) | **99.8%** |
| 查询默认密码 | 3000次 | 1次 | **99.97%** |
### 预计性能提升
- **导入3000条数据**
- 优化前15-30分钟
- 优化后:**10-30秒**
- **性能提升30-180倍**
- **导入10000条数据**
- 优化前50-100分钟
- 优化后:**30-60秒**
- **性能提升50-200倍**
## 优化特性
### 1. 事务安全
- 批量操作使用数据库事务
- 失败时自动回滚,保证数据一致性
### 2. 错误处理
- 批量插入失败时降级为逐条插入
- 详细记录每条失败数据的错误信息
- 部分失败不影响其他数据导入
### 3. 内存优化
- 分批处理每批500条
- 避免大量数据一次性加载到内存
- 适合超大数据量导入
### 4. 向后兼容
- 保留原有API接口
- 不需要修改前端代码
- 完全透明的性能升级
## 使用说明
### 导入步骤
1. 准备Excel文件支持.xls/.xlsx格式
2. 点击"导入"按钮
3. 选择文件并上传
4. 系统自动批量处理
### 注意事项
1. **批量大小**默认每批500条可根据服务器性能调整
2. **超时设置**:大量数据导入建议增加请求超时时间
3. **数据格式**确保Excel格式符合模板要求
4. **唯一性检查**:用户名必须唯一
## 监控建议
### 日志监控
```java
log.info("开始导入用户,总数:{}", userList.size());
log.info("批量插入用户成功,本批数量:{}", batch.size());
log.warn("批量插入失败,降级为逐条插入", e);
```
### 性能指标
- 导入总耗时
- 数据库操作次数
- 成功/失败条数
- 平均处理速度(条/秒)
## 未来优化方向
1. **异步导入**:大数据量导入改为异步任务
2. **进度展示**:实时显示导入进度
3. **并行处理**:使用多线程并行处理数据验证
4. **Redis缓存**:缓存用户存在性检查结果
5. **文件预检查**:上传前验证文件格式和数据有效性
## 总结
通过本次优化,用户导入速度提升了 **30-200倍**,从分钟级降低到秒级,显著改善了用户体验。优化采用批量处理、预查询、分批插入等技术手段,在保证数据完整性的同时大幅提升了性能。

View File

@ -1,217 +0,0 @@
# 用户导入登录账号被错误修改问题修复说明
## 🐛 问题描述
导入用户时,出现了奇怪的登录账号,例如:
```sql
-- 日志显示
updateUserName - Parameters: 这是导入的(String), 6571(Long)
-- 正常应该是
updateUserName - Parameters: 13466(String), 13466(String), 6671(Long)
```
**问题现象:**
- Excel中某些行的**姓名列**填写了异常数据(如"这是导入的"
- 更新用户档案时,系统把姓名同步到了**登录账号**userName
- **用户无法登录**,因为登录账号被改成了姓名
## 🔍 问题根源
### 系统设计
**登录账号userName** = 信息编号(如"3466"
**昵称nickName** = 姓名(如"张三"
### 代码逻辑
**1. 创建新用户时(正确):**
```java
// autoCreateUserByProfile
user.setUserName(信息编号); // 登录账号 = 信息编号 ✅
user.setNickName(Excel姓名); // 昵称 = Excel姓名 ✅
```
**2. 更新用户时(错误):**
```java
// updateProfile → syncUserName
userService.updateUserName(userId, userName, userName);
// 同时更新了登录账号和昵称为Excel姓名 ❌
```
### 问题流程
```
导入Excel → profile.userName = "这是导入的"来自Excel姓名列
updateProfile() → syncUserName(userId, "这是导入的")
updateUserName(userId, "这是导入的", "这是导入的")
sys_user表
user_name (登录账号) = "这是导入的" ← 错误!应该保持为信息编号
nick_name (昵称) = "这是导入的" ← 正确
```
## ✅ 修复方案
### 修改内容
**文件:** `PsyUserProfileServiceImpl.java` 第448-461行
**修改前:**
```java
private void syncUserName(Long userId, String userName)
{
if (userId == null || StringUtils.isEmpty(userName))
{
return;
}
userService.updateUserName(userId, userName, userName);
}
```
**修改后:**
```java
private void syncUserName(Long userId, String userName)
{
if (userId == null || StringUtils.isEmpty(userName))
{
return;
}
// 只更新昵称nickName不修改登录账号userName保持为信息编号
// 获取当前用户,保持原有的登录账号不变
SysUser user = userService.selectUserById(userId);
if (user != null)
{
userService.updateUserName(userId, user.getUserName(), userName);
}
}
```
### 修复效果
**修复前:**
```
更新用户 → user_name和nick_name都变成Excel姓名 ❌
```
**修复后:**
```
更新用户 → user_name保持为信息编号nick_name变成Excel姓名 ✅
```
## 🔧 影响范围
### 已影响的用户
执行SQL查询受影响的用户
```sql
-- 查找登录账号不是纯数字的用户(异常)
SELECT
user_id,
user_name AS 登录账号,
nick_name AS 昵称,
create_time AS 创建时间
FROM sys_user
WHERE user_name REGEXP '[^0-9]' -- 包含非数字字符
AND del_flag = '0';
-- 查找对应的档案信息
SELECT
u.user_id,
u.user_name AS 当前登录账号,
u.nick_name AS 昵称,
p.info_number AS 正确的信息编号
FROM sys_user u
LEFT JOIN psy_user_profile p ON u.user_id = p.user_id
WHERE u.user_name REGEXP '[^0-9]'
AND u.del_flag = '0';
```
### 修复受影响的用户
**手动修复SQL**
```sql
-- 将登录账号恢复为信息编号
UPDATE sys_user u
INNER JOIN psy_user_profile p ON u.user_id = p.user_id
SET u.user_name = p.info_number
WHERE u.user_name REGEXP '[^0-9]' -- 包含非数字字符
AND u.del_flag = '0'
AND p.info_number IS NOT NULL
AND p.info_number REGEXP '^[0-9]+$'; -- 信息编号是纯数字
-- 验证修复结果
SELECT
u.user_id,
u.user_name AS 登录账号,
u.nick_name AS 昵称,
p.info_number AS 信息编号
FROM sys_user u
LEFT JOIN psy_user_profile p ON u.user_id = p.user_id
WHERE u.user_id IN (
SELECT user_id FROM sys_user
WHERE user_name REGEXP '[^0-9]'
);
```
## 📦 部署步骤
### 1. 备份数据库
```sql
-- 备份sys_user表
CREATE TABLE sys_user_backup_20251202 AS SELECT * FROM sys_user;
```
### 2. 修复已影响的用户(可选)
运行上面的修复SQL将异常的登录账号恢复为信息编号。
### 3. 重新编译后端
```bash
cd c:\Users\Administrator\Desktop\Project\xinli
mvn clean package -DskipTests
```
### 4. 重启后端服务
重启后,新的更新操作将不再修改登录账号。
## 📝 预防措施
### 1. Excel数据验证
导入前检查Excel数据
- **姓名列**:只能包含汉字和数字
- **信息编号列**:只能包含数字
- 使用Excel条件格式标记异常数据
### 2. 用户登录规则
**记住:**
- **登录账号** = 信息编号(纯数字)
- **昵称** = 姓名(显示用)
- 更新姓名**不会影响**登录账号
### 3. 导入最佳实践
- ✅ 导入前检查数据规范性
- ✅ 小批量测试导入
- ✅ 导入后验证用户是否能正常登录
- ✅ 备份数据后再大批量导入
## 🎯 总结
| 项目 | 修复前 | 修复后 |
|------|-------|-------|
| **创建用户** | userName=信息编号 ✅ | userName=信息编号 ✅ |
| **更新用户** | userName=姓名 ❌ | userName=信息编号(保持不变)✅ |
| **用户能否登录** | ❌ 无法登录 | ✅ 正常登录 |
**核心修复:**
更新用户档案时只同步姓名到昵称nickName保持登录账号userName为信息编号不变。
**影响评估:**
- 已导入的异常用户需要手动修复
- 修复后不再出现此问题
- 不影响正常的用户创建和更新
**风险:** 低
**优先级:** 高(影响用户登录)

View File

@ -1,107 +0,0 @@
# 用户导入"信息编号已存在"问题说明
## 📋 问题描述
导入 3800 条用户数据时,有 11 条失败,提示"信息编号 XX 已存在"
- 信息编号34, 99, 114, 120, 121, 122, 123, 124 等
## 🔍 问题原因
经过代码分析,"信息编号已存在"有**两种可能**
### 1. Excel 文件内部重复 ⭐ **最可能**
- Excel 文件中有多条记录使用了相同的信息编号
- 第一条记录导入成功
- 第二条相同信息编号的记录尝试导入时,发现数据库中已存在(刚刚导入的那条),导致失败
**示例:**
```
Excel 中:
行100: 信息编号=34, 姓名=张三
行500: 信息编号=34, 姓名=李四 ← 这条会失败,提示"34 已存在"
```
### 2. 数据库中已存在
- 这些信息编号在之前的导入中已经创建过
- 本次导入时没有勾选"是否更新已存在数据",导致失败
## ✅ 已修复内容
### 1. 添加 Excel 内部重复检查
现在导入时会先检查 Excel 文件内部是否有重复的信息编号,提前拦截并给出明确提示。
**修改位置:** `PsyUserProfileServiceImpl.java` 第 529-536 行
```java
// 检查Excel文件内部是否有重复的信息编号
if (infoNumberSet.contains(profile.getInfoNumber())) {
failureMsg.append("、信息编号 ").append(profile.getInfoNumber())
.append(" 在导入文件中重复出现");
continue;
}
```
### 2. 改进错误提示
现在错误提示更清晰,能区分两种情况:
- **Excel 内部重复**`信息编号 XX 在导入文件中重复出现`
- **数据库中已存在**`信息编号 XX 在数据库中已存在(若需更新请勾选"是否更新已存在数据"`
## 🔧 排查方法
### 方法 1运行 SQL 脚本检查
执行 `检查重复信息编号.sql` 脚本,查看:
1. 这些信息编号在数据库中是否真的存在
2. 数据库中是否有其他重复的信息编号
3. 最近导入的记录情况
### 方法 2检查 Excel 文件
在 Excel 中使用以下步骤检查重复:
1. 选中信息编号列
2. 点击"条件格式" → "突出显示单元格规则" → "重复值"
3. Excel 会自动标记重复的信息编号
### 方法 3使用 Excel 公式查找重复
在新列输入公式:
```excel
=COUNTIF($A$2:$A$3801, A2)
```
如果结果 > 1说明该信息编号重复出现。
## 📝 建议
### 给用户的建议
1. **导入前清理 Excel**
- 检查并删除重复的信息编号
- 或修改重复的信息编号,确保唯一性
2. **如果需要更新已存在数据**
- 导入时勾选"是否更新已存在数据"选项
- 系统会自动更新而不是提示失败
3. **使用 SQL 脚本检查**
- 导入前运行 `检查重复信息编号.sql`
- 确认哪些信息编号已在数据库中存在
### 下次导入优化建议
1. ✅ 已添加 Excel 内部重复检查
2. ✅ 已改进错误提示信息
3. 🔄 建议:添加导入预检查功能,在实际导入前先检查并报告所有问题
4. 🔄 建议:提供"忽略重复"选项,跳过重复记录继续导入其他数据
## 🚀 部署说明
修改已完成,需要重新编译后端:
```bash
cd c:\Users\Administrator\Desktop\Project\xinli
mvn clean package -DskipTests
```
重启后端服务后,新的检查逻辑即可生效。
## 📊 问题总结
| 问题类型 | 新错误提示 | 解决方法 |
|---------|-----------|---------|
| Excel 内部重复 | `信息编号 XX 在导入文件中重复出现` | 清理 Excel 中的重复记录 |
| 数据库已存在 | `信息编号 XX 在数据库中已存在(若需更新请勾选"是否更新已存在数据"` | 勾选更新选项,或修改信息编号 |

View File

@ -1,154 +0,0 @@
# 用户档案导入问题修复说明
## 问题描述
用户在导入用户档案数据时,明明某些用户已被删除(在系统中看不到),但系统仍提示"用户已存在",导致无法正常导入。
## 问题原因
`PsyUserProfileMapper.xml` 中,多个档案查询方法缺少对已删除用户的过滤条件:
1. **`selectProfilesByInfoNumbers`** - 批量查询档案(导入时使用)
2. **`selectProfileByInfoNumber`** - 根据信息编号查询(唯一性验证时使用)
3. **`selectProfileById`** - 根据档案ID查询
4. **`selectProfileByUserId`** - 根据用户ID查询
这些查询没有添加 `u.del_flag = '0'` 过滤条件,导致:
- 当用户被删除(`del_flag = '2'`)后,在列表中看不到(列表查询有过滤)
- 但导入时仍能查到这些已删除用户的档案(导入查询无过滤)
- 系统误判为"用户已存在",拒绝导入
## 修复内容
### 1. 修复 `selectProfilesByInfoNumbers`(批量查询)
**位置**: `PsyUserProfileMapper.xml` 第279-288行
```xml
<!-- 修复前 -->
<select id="selectProfilesByInfoNumbers" resultMap="PsyUserProfileResult">
<include refid="selectProfileVo"/>
from psy_user_profile p
left join sys_user u on p.user_id = u.user_id
where p.info_number in
<foreach item="infoNumber" collection="list" open="(" separator="," close=")">
#{infoNumber}
</foreach>
</select>
<!-- 修复后 -->
<select id="selectProfilesByInfoNumbers" resultMap="PsyUserProfileResult">
<include refid="selectProfileVo"/>
from psy_user_profile p
left join sys_user u on p.user_id = u.user_id
where u.del_flag = '0'
and p.info_number in
<foreach item="infoNumber" collection="list" open="(" separator="," close=")">
#{infoNumber}
</foreach>
</select>
```
### 2. 修复 `selectProfileByInfoNumber`(单个查询)
**位置**: `PsyUserProfileMapper.xml` 第62-68行
```xml
<!-- 修复前 -->
<select id="selectProfileByInfoNumber" parameterType="String" resultMap="PsyUserProfileResult">
<include refid="selectProfileVo"/>
from psy_user_profile p
left join sys_user u on p.user_id = u.user_id
where p.info_number = #{infoNumber}
</select>
<!-- 修复后 -->
<select id="selectProfileByInfoNumber" parameterType="String" resultMap="PsyUserProfileResult">
<include refid="selectProfileVo"/>
from psy_user_profile p
left join sys_user u on p.user_id = u.user_id
where p.info_number = #{infoNumber}
and u.del_flag = '0'
</select>
```
### 3. 修复 `selectProfileById`
**位置**: `PsyUserProfileMapper.xml` 第46-52行
```xml
<!-- 修复后 -->
<select id="selectProfileById" parameterType="Long" resultMap="PsyUserProfileResult">
<include refid="selectProfileVo"/>
from psy_user_profile p
left join sys_user u on p.user_id = u.user_id
where p.profile_id = #{profileId}
and u.del_flag = '0'
</select>
```
### 4. 修复 `selectProfileByUserId`
**位置**: `PsyUserProfileMapper.xml` 第54-60行
```xml
<!-- 修复后 -->
<select id="selectProfileByUserId" parameterType="Long" resultMap="PsyUserProfileResult">
<include refid="selectProfileVo"/>
from psy_user_profile p
left join sys_user u on p.user_id = u.user_id
where p.user_id = #{userId}
and u.del_flag = '0'
</select>
```
## 修复效果
修复后,系统在以下场景中的行为将更加合理:
1. **导入档案时**: 不会再将已删除用户的档案识别为"已存在",可以正常导入
2. **信息编号唯一性验证**: 不会因已删除用户的旧信息编号而拒绝新用户使用
3. **查询档案**: 所有档案查询统一过滤已删除用户,保持数据一致性
## 测试建议
### 测试场景1导入已删除用户的档案
1. 删除一个有档案的用户(设置 `del_flag = '2'`
2. 准备Excel文件包含该用户的信息编号
3. 导入档案数据
4. **预期结果**: 成功导入,系统自动创建新用户
### 测试场景2信息编号唯一性
1. 删除用户A信息编号001
2. 创建新档案使用信息编号001
3. **预期结果**: 成功创建,不提示"信息编号已存在"
### 测试场景3正常导入未删除用户
1. 准备包含现有用户信息编号的Excel
2. 不勾选"更新已存在数据"
3. 导入档案
4. **预期结果**: 提示"信息编号已存在"(行为不变)
## 影响范围
**修改文件**:
- `ry-xinli-system/src/main/resources/mapper/system/psychology/PsyUserProfileMapper.xml`
**影响模块**:
- 用户档案导入功能
- 用户档案查询功能
- 信息编号唯一性验证
**兼容性**: 完全向后兼容,只是增加了已删除用户的过滤条件
## 部署说明
1. 更新代码后重新编译项目
2. 重启应用服务器
3. 无需数据库变更
4. 建议清理旧的已删除用户档案数据(可选)
---
**修复日期**: 2024年12月2日
**修复人**: Cascade AI Assistant

View File

@ -1,441 +0,0 @@
# 系统优化说明文档
## 优化日期
2025年12月1日
## 优化内容概览
### 优化1大模型API - 从远程改为本地 ✅
### 优化2用户导入性能 - 批量处理优化 ✅
---
## 优化1大模型API配置修改
### 问题背景
系统使用远程Kimi API进行AI分析存在以下问题
- 依赖外部网络,可能不稳定
- 需要API Key有安全风险
- 调用次数可能受限
- 成本较高
### 修改方案
将远程API改为本地大模型如Ollama支持离线使用。
### 修改文件
#### 1. `xinli-ui/src/views/psychology/report/comprehensive.vue`
```javascript
// 修改前
API_URL: 'https://api.moonshot.cn/v1/chat/completions',
API_KEY: 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m',
MODEL: 'moonshot-v1-32k'
// 修改后
API_URL: 'http://localhost:11434/v1/chat/completions',
API_KEY: '', // 本地模型不需要API Key
MODEL: 'qwen2.5:7b' // 根据实际使用的本地模型修改
```
#### 2. `xinli-ui/src/views/psychology/report/index.vue`
```javascript
// 修改前
const API_URL = 'https://api.moonshot.cn/v1/chat/completions';
const API_KEY = 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m';
const MODEL = 'moonshot-v1-32k';
// 修改后
const API_URL = 'http://localhost:11434/v1/chat/completions';
const API_KEY = ''; // 本地模型不需要API Key
const MODEL = 'qwen2.5:7b'; // 根据实际使用的本地模型修改
```
#### 3. `xinli-ui/src/views/psychology/report/detail.vue`
```javascript
// 修改前
const API_URL = 'https://api.moonshot.cn/v1/chat/completions';
const API_KEY = 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m';
const MODEL = 'moonshot-v1-32k';
// 修改后
const API_URL = 'http://localhost:11434/v1/chat/completions';
const API_KEY = ''; // 本地模型不需要API Key
const MODEL = 'qwen2.5:7b'; // 根据实际使用的本地模型修改
```
### 本地模型部署
#### 使用Ollama推荐
1. **安装Ollama**
```bash
# Windows
# 下载并安装https://ollama.com/download
# Linux
curl -fsSL https://ollama.com/install.sh | sh
# MacOS
brew install ollama
```
2. **下载模型**
```bash
# 下载Qwen2.5模型(推荐,支持中文)
ollama pull qwen2.5:7b
# 或其他中文模型
ollama pull qwen:14b
ollama pull chatglm3:6b
```
3. **启动服务**
```bash
ollama serve
```
服务会在 `http://localhost:11434` 启动
4. **测试**
```bash
curl http://localhost:11434/api/generate -d '{
"model": "qwen2.5:7b",
"prompt": "你好"
}'
```
### 修改后的优势
- ✅ **离线可用**:不依赖外部网络
- ✅ **无需API Key**:本地部署,无需管理密钥
- ✅ **无调用限制**:可以无限次调用
- ✅ **数据安全**:数据不会发送到外部
- ✅ **成本降低**无API调用费用
- ✅ **可定制**:可以选择不同的本地模型
### 注意事项
1. **模型选择**:根据服务器配置选择合适的模型
- 7B模型需要至少8GB内存
- 14B模型需要至少16GB内存
2. **端口配置**:确保`11434`端口未被占用
3. **性能**:本地模型响应速度取决于硬件性能
4. **模型更换**:如果使用其他模型,需要同步修改代码中的`MODEL`参数
---
## 优化2用户导入性能优化
### 性能问题分析
#### 优化前的问题
导入3000条用户数据需要10几分钟原因
1. **逐条查询**3000次数据库查询
```java
for (PsyUserProfile profile : profileList) {
// 每条都要查询一次数据库
PsyUserProfile existProfile = profileMapper.selectProfileByInfoNumber(profile.getInfoNumber());
}
```
2. **逐条插入/更新**3000次数据库写操作
```java
for (PsyUserProfile profile : profileList) {
if (existProfile == null) {
this.insertProfile(profile); // 单条插入
} else {
this.updateProfile(profile); // 单条更新
}
}
```
**总计**: 6000+ 次数据库操作!
#### 性能瓶颈
- **N+1问题**:每条数据都触发一次查询
- **网络开销**:每次操作都有网络往返
- **事务开销**:每次操作都可能有事务提交
- **索引维护**:每次插入都要更新索引
### 优化方案
#### 核心思路
将**逐条处理**改为**批量处理**
#### 优化步骤
**步骤1批量查询1次查询替代3000次**
```java
// 收集所有infoNumber
List<String> infoNumbers = profileList.stream()
.map(PsyUserProfile::getInfoNumber)
.collect(Collectors.toList());
// 一次性查询所有已存在的记录
List<PsyUserProfile> existingProfiles =
profileMapper.selectProfilesByInfoNumbers(infoNumbers);
```
**步骤2数据分类**
```java
List<PsyUserProfile> toInsertList = new ArrayList<>(); // 待插入
List<PsyUserProfile> toUpdateList = new ArrayList<>(); // 待更新
for (PsyUserProfile profile : validProfiles) {
if (existingMap.containsKey(profile.getInfoNumber())) {
toUpdateList.add(profile);
} else {
toInsertList.add(profile);
}
}
```
**步骤3批量插入每批500条**
```java
int batchSize = 500;
for (int i = 0; i < toInsertList.size(); i += batchSize) {
List<PsyUserProfile> batch = toInsertList.subList(i, i + batchSize);
profileMapper.batchInsertProfiles(batch); // 批量插入500条
}
```
**步骤4批量更新每批500条**
```java
for (int i = 0; i < toUpdateList.size(); i += batchSize) {
List<PsyUserProfile> batch = toUpdateList.subList(i, i + batchSize);
profileMapper.batchUpdateProfiles(batch); // 批量更新500条
}
```
### 修改的文件
#### 1. Mapper接口 - `PsyUserProfileMapper.java`
**新增方法**:
```java
/**
* 批量查询档案(根据信息编号列表)
*/
public List<PsyUserProfile> selectProfilesByInfoNumbers(List<String> infoNumbers);
/**
* 批量插入档案
*/
public int batchInsertProfiles(List<PsyUserProfile> profileList);
/**
* 批量更新档案
*/
public int batchUpdateProfiles(List<PsyUserProfile> profileList);
```
#### 2. Mapper XML - `PsyUserProfileMapper.xml`
**批量查询SQL**:
```xml
<select id="selectProfilesByInfoNumbers" resultMap="PsyUserProfileResult">
SELECT * FROM psy_user_profile
WHERE info_number IN
<foreach item="infoNumber" collection="list" open="(" separator="," close=")">
#{infoNumber}
</foreach>
</select>
```
**批量插入SQL**:
```xml
<insert id="batchInsertProfiles">
INSERT INTO psy_user_profile(...) VALUES
<foreach item="item" collection="list" separator=",">
(#{item.userId}, #{item.infoNumber}, ...)
</foreach>
</insert>
```
**批量更新SQL**:
```xml
<update id="batchUpdateProfiles">
<foreach item="item" collection="list" separator=";">
UPDATE psy_user_profile SET ... WHERE profile_id = #{item.profileId}
</foreach>
</update>
```
#### 3. Service实现 - `PsyUserProfileServiceImpl.java`
**优化前**:
```java
for (PsyUserProfile profile : profileList) {
// 3000次查询
PsyUserProfile existProfile = profileMapper.selectProfileByInfoNumber(...);
if (existProfile == null) {
this.insertProfile(profile); // 3000次插入
} else {
this.updateProfile(profile); // 或3000次更新
}
}
```
**优化后**:
```java
// 1. 批量查询1次
List<PsyUserProfile> existingProfiles =
profileMapper.selectProfilesByInfoNumbers(infoNumbers);
// 2. 分类处理
List<PsyUserProfile> toInsertList = ...;
List<PsyUserProfile> toUpdateList = ...;
// 3. 批量插入6次每批500条
profileMapper.batchInsertProfiles(toInsertList);
// 4. 批量更新6次每批500条
profileMapper.batchUpdateProfiles(toUpdateList);
```
### 性能提升对比
| 操作 | 优化前 | 优化后 | 提升 |
|------|--------|--------|------|
| 数据库查询 | 3000次 | 1次 | **3000倍** |
| 数据库插入 | 3000次 | 6次500条/批) | **500倍** |
| 总操作数 | 6000次 | 7次 | **857倍** |
| 导入时间 | 10-15分钟 | **10-30秒** | **20-50倍** |
### 预期效果
- ✅ **3000条数据**从10-15分钟 → **10-30秒**
- ✅ **10000条数据**:预计 **30秒-1分钟**
- ✅ **数据库压力降低**减少99%的数据库操作
- ✅ **用户体验提升**:导入响应更快
### 容错机制
1. **批量失败回退**:如果批量插入失败,自动回退到逐条插入
```java
try {
profileMapper.batchInsertProfiles(batch);
} catch (Exception e) {
// 批量失败,尝试逐条插入
for (PsyUserProfile profile : batch) {
this.insertProfile(profile);
}
}
```
2. **进度跟踪**:保留导入进度显示功能
3. **错误记录**:详细记录每条失败的数据
### 数据库配置建议
为了支持批量更新需要在数据库连接URL中添加
```properties
# application.yml 或 application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/xinli?allowMultiQueries=true
```
`allowMultiQueries=true` 允许在一个语句中执行多条SQL用于批量更新
### 测试验证
#### 测试步骤
1. 准备3000条测试数据的Excel文件
2. 进入"用户档案" → 点击"导入"
3. 选择Excel文件上传
4. 观察导入进度和时间
#### 预期结果
- ✅ 导入时间10-30秒
- ✅ 进度显示:实时更新
- ✅ 成功率100%(数据正确情况下)
- ✅ 数据完整:所有字段正确导入
## 部署说明
### 前端部署
```bash
cd xinli-ui
npm run build
# 将dist目录部署到nginx
```
### 后端部署
```bash
cd ry-xinli
mvn clean package
# 重启Spring Boot应用
java -jar ry-xinli-admin/target/ry-xinli.jar
```
### 数据库配置
确保数据库连接URL包含 `allowMultiQueries=true`
```yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/xinli?allowMultiQueries=true&useUnicode=true&characterEncoding=utf-8&useSSL=false
```
## 总结
### 优化1大模型API
- ✅ 移除远程API依赖
- ✅ 改用本地Ollama
- ✅ 降低成本,提升安全性
### 优化2用户导入
- ✅ 批量查询3000次 → 1次
- ✅ 批量插入3000次 → 6次
- ✅ 导入时间10-15分钟 → 10-30秒
- ✅ 性能提升:**20-50倍**
### 技术要点
1. 批量处理替代逐条处理
2. 减少数据库往返次数
3. 使用MyBatis foreach批量操作
4. 分批处理避免内存溢出
5. 失败回退机制保证可靠性
### 注意事项
1. 本地大模型需要单独部署Ollama
2. 数据库需要开启`allowMultiQueries`
3. 大批量数据建议分批导入每批不超过1万条
4. 导入前建议备份数据库
## 附录
### 常见问题
**Q1: 本地大模型响应慢怎么办?**
A:
- 使用更小的模型如qwen2.5:3b
- 升级服务器硬件(增加内存/CPU
- 优化提示词,减少输出长度
**Q2: 批量导入失败怎么办?**
A:
- 查看错误日志
- 检查数据格式是否正确
- 确认数据库配置正确
- 系统会自动回退到逐条导入
**Q3: 能导入更多数据吗?**
A: 可以系统支持10万+数据导入,只是时间会更长。建议:
- 1万条以内一次性导入
- 1万-10万分批导入
- 10万以上后台定时任务导入
**Q4: 如何验证优化效果?**
A:
- 准备相同的测试数据
- 对比优化前后的导入时间
- 检查数据库慢查询日志
- 监控服务器资源使用情况

View File

@ -1,337 +0,0 @@
# 纯前端 TTS 功能使用说明
## 📋 方案概述
基于浏览器 **Web Speech API** 实现的纯前端文字转语音功能,**无需后端服务**,适合局域网环境使用。
### ✅ 优势
1. **零后端依赖**:完全在浏览器中运行,不需要服务器支持
2. **即开即用**:直接打开 HTML 文件即可使用
3. **跨平台**:支持 Windows、Linux、Mac
4. **响应式设计**:适配桌面和移动设备
5. **功能完整**:支持音量、语速、音调调节
---
## 🎯 使用方式
### 方式一:独立工具(推荐用于测试)
直接打开 `局域网TTS工具.html` 文件:
1. 双击打开 `局域网TTS工具.html`
2. 在文本框中输入要朗读的文字
3. 调整音量、语速、音调参数
4. 点击"开始朗读"按钮
**特点**
- 无需任何配置
- 适合快速测试和演示
- 可以放在局域网服务器上供多人使用
### 方式二Vue 组件集成(用于项目)
在 Vue 项目中使用 `TtsPlayer` 组件:
```vue
<template>
<div>
<!-- 题目内容 -->
<div class="question-content">{{ questionText }}</div>
<!-- TTS 播放器 -->
<TtsPlayer
:text="questionText"
lang="zh-CN"
@start="onTtsStart"
@end="onTtsEnd"
@stop="onTtsStop"
/>
</div>
</template>
<script>
import TtsPlayer from '@/components/Psychology/TtsPlayer.vue'
export default {
components: {
TtsPlayer
},
data() {
return {
questionText: '请根据您的实际情况,选择最符合的选项。'
}
},
methods: {
onTtsStart() {
console.log('开始朗读')
},
onTtsEnd() {
console.log('朗读完成')
},
onTtsStop() {
console.log('停止朗读')
}
}
}
</script>
```
---
## 📦 文件说明
### 1. `局域网TTS工具.html`
**位置**:项目根目录
**用途**:独立的 TTS 工具,可直接在浏览器中打开使用
**功能**
- ✅ 文本输入和朗读
- ✅ 音量、语速、音调调节
- ✅ 预设文本快速填充
- ✅ 响应式设计,支持移动端
### 2. `xinli-ui/src/components/Psychology/TtsPlayer.vue`
**位置**Vue 项目组件目录
**用途**:可复用的 TTS 组件,集成到 Vue 项目中
**Props**
- `text` (String): 要朗读的文本
- `lang` (String): 语言设置,默认 `zh-CN`
**Events**
- `@start`: 开始朗读时触发
- `@end`: 朗读完成时触发
- `@stop`: 停止朗读时触发
- `@error`: 朗读出错时触发
---
## 🔧 在问卷答题页面中集成
### 步骤 1导入组件
`xinli-ui/src/views/psychology/questionnaire/taking.vue` 中:
```vue
<script>
import TtsPlayer from '@/components/Psychology/TtsPlayer.vue'
export default {
components: {
TtsPlayer
},
// ...
}
</script>
```
### 步骤 2添加 TTS 播放器
在题目显示区域添加 TTS 组件:
```vue
<template>
<el-card shadow="never" class="question-card" v-if="currentItem">
<div class="question-number">第 {{ currentIndex + 1 }} 题</div>
<div class="question-content">{{ currentItem.itemContent }}</div>
<!-- 添加 TTS 播放器 -->
<div style="margin: 15px 0;">
<TtsPlayer :text="currentItem.itemContent" />
</div>
<!-- 其他内容... -->
</el-card>
</template>
```
### 步骤 3可选 - 自动朗读
如果需要自动朗读题目,可以在切换题目时触发:
```vue
<script>
export default {
watch: {
currentItem(newItem) {
if (newItem && this.autoRead) {
// 延迟一下,确保组件已渲染
this.$nextTick(() => {
this.$refs.ttsPlayer?.speakText()
})
}
}
}
}
</script>
```
---
## 🌐 浏览器兼容性
### ✅ 完全支持
- **Chrome** 33+(推荐)
- **Edge** 14+
- **Safari** 7+
- **Opera** 20+
### ⚠️ 部分支持
- **Firefox**:需要手动启用 `media.webspeech.synth.enabled`
### ❌ 不支持
- **IE** 11 及以下版本
---
## 🎨 自定义配置
### 修改默认参数
`TtsPlayer.vue` 组件中:
```javascript
data() {
return {
volume: 1.0, // 默认音量0-1
rate: 1.0, // 默认语速0.5-2
pitch: 1.0, // 默认音调0.5-2
}
}
```
### 选择语音
```javascript
// 获取可用语音列表
const voices = this.synth.getVoices()
console.log(voices)
// 选择特定语音
const chineseVoice = voices.find(voice =>
voice.name.includes('Chinese') ||
voice.lang === 'zh-CN'
)
if (chineseVoice) {
this.utterance.voice = chineseVoice
}
```
---
## 📝 使用示例
### 示例 1基本使用
```vue
<TtsPlayer text="欢迎使用AI心理健康测评系统" />
```
### 示例 2监听事件
```vue
<TtsPlayer
:text="questionText"
@start="handleTtsStart"
@end="handleTtsEnd"
/>
```
### 示例 3动态文本
```vue
<TtsPlayer :text="currentQuestion.content" />
```
---
## ⚠️ 注意事项
1. **浏览器限制**
- 某些浏览器需要用户交互后才能播放语音
- 建议在用户点击按钮后触发朗读
2. **语音质量**
- 不同浏览器的语音质量可能不同
- Chrome 的中文语音质量通常较好
3. **网络环境**
- 完全离线可用,不需要网络连接
- 适合局域网环境
4. **性能考虑**
- 长文本建议分段朗读
- 避免同时播放多个语音
---
## 🐛 常见问题
### Q: 为什么没有声音?
**A**: 检查以下几点:
1. 浏览器是否支持 Web Speech API
2. 系统音量是否开启
3. 浏览器是否允许自动播放音频
4. 是否在用户交互后触发(某些浏览器要求)
### Q: 语音质量不好?
**A**:
1. 使用 Chrome 浏览器(语音质量最好)
2. 调整语速和音调参数
3. 检查系统语音设置
### Q: 如何切换语音?
**A**:
```javascript
const voices = speechSynthesis.getVoices()
const voice = voices.find(v => v.name.includes('Microsoft'))
utterance.voice = voice
```
---
## 📚 技术文档
### Web Speech API 参考
- [MDN - SpeechSynthesis](https://developer.mozilla.org/zh-CN/docs/Web/API/SpeechSynthesis)
- [MDN - SpeechSynthesisUtterance](https://developer.mozilla.org/zh-CN/docs/Web/API/SpeechSynthesisUtterance)
### API 参数说明
| 参数 | 类型 | 范围 | 说明 |
|------|------|------|------|
| `lang` | String | - | 语言代码,如 `zh-CN` |
| `volume` | Number | 0-1 | 音量大小 |
| `rate` | Number | 0.5-2 | 语速倍数 |
| `pitch` | Number | 0.5-2 | 音调高低 |
| `voice` | SpeechSynthesisVoice | - | 语音对象 |
---
## ✨ 功能特性
- ✅ 纯前端实现,无需后端
- ✅ 支持中文语音合成
- ✅ 可调节音量、语速、音调
- ✅ 响应式设计,支持移动端
- ✅ 预设文本快速填充
- ✅ 事件监听支持
- ✅ 错误处理机制
---
**创建时间**2025-01-27
**版本**1.0.0
**状态**:✅ 可用

View File

@ -1,258 +0,0 @@
# 语音读题功能优化说明
## 问题修复
### 1. 编译错误修复 ✅
**文件**: `ry-xinli-system/src/main/java/com/ddnai/system/service/impl/SysUserServiceImpl.java`
**问题**: 缺少 `Map` 类的导入
**修复**:
```java
import java.util.Map;
```
## 功能优化
### 2. 语音播放参数优化 ✅
**问题描述**:
- 声音很小
- 朗读速度过慢
**优化方案**:
#### 音量提升
```javascript
// 优化前
this.currentUtterance.volume = 1.0; // 已经是最大值
// 优化后 - 确保使用最大音量
this.currentUtterance.volume = 1.0; // 最大音量
```
#### 语速优化
```javascript
// 优化前
this.currentUtterance.rate = 0.9; // 太慢
// 优化后
this.currentUtterance.rate = 1.2; // 正常语速,稍快
```
### 3. 新增"朗读全部"功能 ✅
**功能描述**:
点击后一次性朗读题目和所有选项
**实现文件**:
- `xinli-ui/src/views/psychology/assessment/taking.vue` (量表测评)
- `xinli-ui/src/views/psychology/questionnaire/taking.vue` (问卷答题)
**UI设计**:
```
题目旁边两个按钮:
┌────────────┬────────────┐
│ 🎤 朗读全部 │ 🔊 朗读题干 │
└────────────┴────────────┘
```
**按钮说明**:
- **朗读全部** (绿色图标): 朗读题目 + 所有选项
- **朗读题干** (蓝色图标): 只朗读题目内容
**代码实现**:
```javascript
/** 朗读当前题目和所有选项 */
speakCurrentQuestion() {
if (!this.isTtsSupported || !this.currentItem) {
this.$message.warning('浏览器不支持语音播放功能');
return;
}
// 如果正在播放,则停止
if (this.isSpeaking) {
this.stopSpeaking();
return;
}
// 构建完整文本:题目 + 所有选项
let fullText = this.currentItem.itemContent.trim();
if (this.currentOptions && this.currentOptions.length > 0) {
fullText += '。选项:';
this.currentOptions.forEach((option, index) => {
const optionCode = option.optionCode || String.fromCharCode(65 + index);
fullText += `${optionCode}、${option.optionContent}。`;
});
}
// 调用朗读
this.speakText(fullText);
}
```
### 4. 选项独立朗读按钮 ✅
**功能**: 每个选项旁边都有独立的朗读按钮
**特点**:
- 点击选项旁边的按钮,只朗读该选项内容
- 支持单选、多选、判断题等所有题型
- 播放时按钮显示暂停图标
### 5. UI交互优化 ✅
#### 播放状态指示
```css
/* 播放时的脉冲动画 */
.tts-btn-all.speaking, .tts-btn.speaking {
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
```
#### 悬停效果
```css
.tts-btn-all:hover:not(:disabled),
.tts-btn:hover:not(:disabled) {
background-color: #f5f7fa;
transform: scale(1.05);
}
```
#### 禁用状态
```css
.tts-btn:disabled, .tts-btn-all:disabled {
color: #c0c4cc;
cursor: not-allowed;
opacity: 0.5;
}
```
## 修改文件清单
### 后端
1. `ry-xinli-system/src/main/java/com/ddnai/system/service/impl/SysUserServiceImpl.java`
- 添加 `Map` 导入
### 前端 - 量表测评
2. `xinli-ui/src/views/psychology/assessment/taking.vue`
- 添加"朗读全部"按钮
- 优化语速参数0.9 → 1.2
- 新增 `speakCurrentQuestion()` 方法
- 添加播放状态标识 `isSpeaking`
- 优化样式布局
### 前端 - 问卷答题
3. `xinli-ui/src/views/psychology/questionnaire/taking.vue`
- 添加"朗读全部"按钮
- 优化语速参数1.0 → 1.2
- 新增 `speakCurrentQuestion()` 方法
- 添加播放状态标识 `isSpeaking`
- 优化样式布局
## 功能特性
### ✅ 音量优化
- 使用最大音量 (1.0)
- 确保声音清晰可听
### ✅ 语速优化
- 从 0.9/1.0 提升到 1.2
- 更自然流畅的阅读速度
- 避免拖沓感
### ✅ 智能朗读
- **朗读全部**: 题目 + 所有选项(完整体验)
- **朗读题干**: 只朗读题目(快速浏览)
- **选项朗读**: 独立朗读每个选项(精准控制)
### ✅ 交互友好
- 播放时显示暂停图标
- 点击暂停按钮停止播放
- 悬停时按钮放大动画
- 播放时脉冲动画提示
### ✅ 浏览器兼容
- 自动检测浏览器支持
- 优先选择中文语音引擎
- 不支持时禁用按钮
## 使用说明
### 量表测评/问卷答题
#### 方式1: 朗读全部
1. 点击题目旁的 **"🎤 朗读全部"** 按钮
2. 系统依次朗读:题目内容 → 选项A → 选项B → ...
3. 再次点击可停止播放
#### 方式2: 朗读题干
1. 点击题目旁的 **"🔊 朗读题干"** 按钮
2. 系统只朗读题目内容
3. 再次点击可停止播放
#### 方式3: 朗读单个选项
1. 点击选项旁边的 **🔊** 图标
2. 系统只朗读该选项内容
3. 适合需要反复听某个选项的场景
## 技术说明
### 语音引擎
使用浏览器内置的 **Web Speech API**
### 支持的浏览器
- Chrome 33+
- Edge 14+
- Safari 7+
- Firefox 49+
### 语音参数
```javascript
{
lang: 'zh-CN', // 中文
volume: 1.0, // 最大音量
rate: 1.2, // 语速稍快
pitch: 1.0 // 正常音调
}
```
## 性能优化
### 停止机制
- 切换题目时自动停止当前播放
- 点击播放按钮时先停止再播放
- 组件销毁时自动停止
### 错误处理
- 浏览器不支持时禁用功能
- 播放失败时静默处理
- 已开始播放后忽略非致命错误
## 未来优化方向
1. **语速调节**: 允许用户自定义语速0.5-2.0
2. **音量调节**: 添加音量滑块
3. **语音选择**: 支持多种语音引擎切换
4. **快捷键**: 支持键盘快捷键控制(如空格键播放/暂停)
5. **进度显示**: 显示当前朗读进度
6. **自动播放**: 支持切换题目时自动朗读
7. **离线语音**: 集成离线TTS引擎
## 总结
本次优化主要解决了:
1. ✅ **编译错误** - 添加缺失的导入
2. ✅ **音量问题** - 确保使用最大音量
3. ✅ **语速问题** - 优化语速从慢速提升到正常偏快
4. ✅ **功能增强** - 新增"朗读全部"功能
5. ✅ **交互优化** - 更好的视觉反馈和用户体验
语音读题功能现在更加实用和流畅,用户可以根据需要选择不同的朗读方式!

View File

@ -1,222 +0,0 @@
# 🚀 Kimi API版本部署说明
## ✅ 已完成的修改
### 1. 大模型切换
**从:** Ollama本地模型 (deepseek-r1:32b)
**到:** Kimi远程API (moonshot-v1-32k)
**配置信息:**
```javascript
API_URL: 'https://api.moonshot.cn/v1/chat/completions'
API_KEY: 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m'
MODEL: 'moonshot-v1-32k'
```
### 2. Android App地址修改
**从:** `http://192.168.0.106:8090` (局域网)
**到:** `http://1.15.149.240:20001` (云服务器)
---
## 📦 部署步骤
### 第一步:部署前端
#### 方式1复制到服务器推荐
```bash
# 本地dist目录路径
c:\Users\Administrator\Desktop\Project\xinli\xinli-ui\dist
# 服务器部署路径(根据实际情况修改)
服务器: 1.15.149.240
路径: /www/wwwroot/xinli_web/web/
```
**操作:**
1. 将 `xinli-ui\dist` 文件夹的**所有内容**复制
2. 上传到服务器的前端部署目录
3. 覆盖原有文件
#### 方式2使用FTP/SFTP工具
```
工具FileZilla / WinSCP
服务器1.15.149.240:22
用户:根据实际情况
路径:/www/wwwroot/xinli_web/web/
```
#### 方式3使用命令行如果有SSH
```bash
# 在本地执行需要安装scp
scp -r dist/* user@1.15.149.240:/www/wwwroot/xinli_web/web/
```
### 第二步安装Android App
**APK位置**
```
c:\Users\Administrator\Desktop\Project\xinli\xinli-App\app\build\outputs\apk\debug\app-debug.apk
```
**安装到手机:**
1. 将APK复制到手机
2. 安装或覆盖安装旧版本
3. 打开App会自动访问 `http://1.15.149.240:20001`
---
## 🧪 测试步骤
### 1. 测试前端访问
在浏览器访问:
```
http://1.15.149.240:20001
```
确保:
- ✅ 页面正常显示
- ✅ 功能正常使用
### 2. 测试AI分析功能
进入报告详情页:
1. 点击"AI分析"按钮
2. 等待AI生成分析结果
3. 查看是否正常显示
**预期:**
- ✅ 调用Kimi API成功
- ✅ 生成专业的报告分析
- ✅ 格式美观,内容完整
### 3. 测试App功能
1. 在手机上打开App
2. 检查是否能正常访问云服务器
3. 测试答题功能
4. **测试读题功能** 🔊
- 进入答题页面
- 点击读题按钮
- 应该听到Android原生TTS朗读
---
## 🔍 常见问题
### Q1: AI分析失败
**可能原因:**
- Kimi API密钥无效
- 网络连接问题
- API配额用完
**解决方法:**
1. 检查控制台错误信息
2. 确认API密钥正确
3. 检查Kimi账户余额
### Q2: App无法访问
**可能原因:**
- 手机无法连接外网
- 服务器防火墙阻止
- 端口未开放
**解决方法:**
1. 确保手机有网络
2. 在手机浏览器访问测试
3. 检查服务器防火墙配置
### Q3: 前端更新没生效?
**解决方法:**
1. 清除浏览器缓存Ctrl + F5
2. 确认服务器文件已更新
3. 检查Nginx/Apache配置
---
## 📊 API使用说明
### Kimi API调用格式
```javascript
POST https://api.moonshot.cn/v1/chat/completions
Headers:
Authorization: Bearer sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m
Content-Type: application/json
Body:
{
"model": "moonshot-v1-32k",
"messages": [
{"role": "system", "content": "系统提示词"},
{"role": "user", "content": "用户问题"}
],
"temperature": 0.2,
"max_tokens": 1000
}
Response:
{
"choices": [
{
"message": {
"content": "AI生成的内容"
}
}
]
}
```
### 模型选择
- `moonshot-v1-8k` - 8K上下文速度快
- `moonshot-v1-32k` - 32K上下文推荐 ✅
- `moonshot-v1-128k` - 128K上下文处理长文本
---
## 🎯 回滚方案
如果需要切换回Ollama本地模型
**1. 修改API配置**
```javascript
// detail.vue, index.vue, comprehensive.vue
const API_URL = 'http://192.168.0.106:11434/api/chat';
const API_KEY = '';
const MODEL = 'deepseek-r1:32b';
// 响应解析
data?.message?.content // Ollama格式
```
**2. 修改App地址**
```java
// MainActivity.java
private static final String FIXED_URL = "http://192.168.0.106:8090";
```
**3. 重新构建和部署**
---
## 📝 版本信息
- **大模型:** Kimi API (moonshot-v1-32k)
- **App地址** http://1.15.149.240:20001
- **TTS** Android原生TTS
- **构建时间:** 2025-12-02
---
## ✅ 部署检查清单
- [ ] 前端dist文件夹已上传到服务器
- [ ] 服务器Nginx/Apache已重启
- [ ] 浏览器访问 http://1.15.149.240:20001 正常
- [ ] AI分析功能测试成功
- [ ] APK已安装到测试手机
- [ ] App能正常访问云服务器
- [ ] 读题功能测试成功
- [ ] 所有功能正常使用
---
**部署完成后,即可在手机和浏览器正常使用新版本!** 🎉

View File

@ -1,329 +0,0 @@
# 🔧 重新打包指南
## ❓ 为什么之前的 APK 不能正常使用?
可能的原因:
1. **前端资源未更新** - TTS 功能修复后需要重新构建前端
2. **构建缓存问题** - 旧的缓存导致新代码未生效
3. **WebView 资源问题** - App 中的 Web 资源未同步
4. **签名问题** - 签名不一致导致安装失败
## ✅ 解决方案:完整重新打包
我已经为你创建了两个打包脚本:
### 方案 1完整打包流程推荐
**脚本位置**`完整打包流程.bat`
**包含内容**:前端构建 + Android 打包
```bash
# 在项目根目录执行
.\完整打包流程.bat
```
**优点**
- ✅ 自动构建最新前端代码(包含 TTS 修复)
- ✅ 清理所有缓存
- ✅ 完整的错误检查
- ✅ 一键完成所有步骤
**执行步骤**
1. 打开 PowerShell 或 CMD
2. 进入项目目录:`cd c:\Users\Administrator\Desktop\Project\xinli`
3. 执行脚本:`.\完整打包流程.bat`
4. 等待完成(约 5-10 分钟)
---
### 方案 2只打包 Android App
**脚本位置**`xinli-App\完整重新打包.bat`
**适用场景**:前端已经构建好,只需要重新打包 App
```bash
# 在 Android 项目目录执行
cd xinli-App
.\完整重新打包.bat
```
---
## 📋 打包流程详解
### 阶段 1: 前端构建(如果使用完整打包)
```bash
cd xinli-ui
npm run build:prod
```
**作用**
- 编译 Vue.js 前端代码
- 应用 TTS 朗读功能的修复
- 生成优化后的静态文件到 `dist` 目录
**预期输出**
```
Building for production...
✓ built in XXs
```
---
### 阶段 2: Android 打包
#### 步骤 1: 清理缓存
```bash
cd xinli-App
# 删除旧的构建文件
rd /s /q app\build
rd /s /q build
rd /s /q .gradle
# Gradle clean
gradlew.bat clean
```
#### 步骤 2: 检查环境
```bash
# 检查 JAVA_HOME
echo %JAVA_HOME%
# 检查 Java 版本(需要 JDK 8
java -version
```
**要求**
- ✅ JAVA_HOME 已设置
- ✅ Java 版本为 1.8.x
- ✅ Android SDK 已安装
#### 步骤 3: 构建 APK
```bash
gradlew.bat assembleRelease
```
**预期输出**
```
BUILD SUCCESSFUL in XXs
```
**APK 位置**
```
xinli-App\app\build\outputs\apk\release\app-release.apk
```
---
## 🚨 常见问题排查
### 问题 1: "JAVA_HOME 未设置"
**解决方法**
```bash
# 设置 JAVA_HOME替换为你的 JDK 路径)
set JAVA_HOME=D:\2_part\JAVA\JDK
# 或在系统环境变量中永久设置
```
### 问题 2: "Node.js 未安装"
**解决方法**
1. 下载 Node.jshttps://nodejs.org/
2. 安装后重启命令行
3. 验证:`node -v`
### 问题 3: "npm run build:prod 失败"
**解决方法**
```bash
cd xinli-ui
# 重新安装依赖
npm install
# 再次构建
npm run build:prod
```
### 问题 4: "Gradle 构建失败"
**常见原因**
1. **网络问题** - 无法下载依赖
- 解决:检查网络,重试
2. **SDK 版本问题** - Build Tools 未安装
- 解决:打开 Android Studio → SDK Manager → 安装 Build Tools 30.0.3
3. **内存不足**
- 解决:修改 `gradle.properties` 中的内存设置
### 问题 5: APK 安装失败
**原因**:旧版本 App 未卸载
**解决方法**
```bash
# 方法 1: 手动卸载
设置 → 应用管理 → 心理测评 → 卸载
# 方法 2: 使用 adb如果手机已连接电脑
adb uninstall com.xinli.app
adb install app-release.apk
```
---
## 📱 安装和测试步骤
### 1. 卸载旧版本(重要!)
```
⚠️ 必须先卸载旧版本,否则可能安装失败或功能异常
```
**操作步骤**
- 手机 → 设置 → 应用管理
- 找到"心理测评"或"xinli"
- 点击卸载
### 2. 安装新 APK
**方法 1: 通过 USB 传输**
1. 将 APK 复制到手机
2. 在手机上点击 APK 文件
3. 允许安装未知来源应用
4. 点击安装
**方法 2: 通过 adb 安装**
```bash
# 手机连接电脑,开启 USB 调试
adb devices
adb install -r app-release.apk
```
### 3. 测试功能
#### 测试 1: TTS 朗读功能
1. 登录系统
2. 进入量表/问卷答题页面
3. **预期**
- ✅ 朗读按钮不再是灰色
- ✅ 点击"朗读全部"能听到声音
- ✅ 点击"朗读题干"能听到声音
- ✅ 点击选项朗读按钮能听到声音
#### 测试 2: 权限过滤(如果已修复后端)
1. 使用普通用户登录
2. 查看问卷列表
3. **预期**
- ✅ 只能看到公开问卷和已授权的问卷
- ❌ 看不到未授权的问卷
#### 测试 3: 基本功能
1. 登录功能
2. 答题功能
3. 提交测评
4. 查看报告
---
## 🎯 快速打包步骤(简化版)
### 如果你只想快速打包 Android App
```bash
# 1. 进入 Android 项目目录
cd c:\Users\Administrator\Desktop\Project\xinli\xinli-App
# 2. 清理
rd /s /q app\build
rd /s /q build
# 3. 打包
gradlew.bat clean assembleRelease
# 4. 查找 APK
dir /s app-release.apk
```
APK 位置:`app\build\outputs\apk\release\app-release.apk`
---
## 📊 打包时间预估
| 步骤 | 预计时间 |
|------|---------|
| 前端构建 | 2-3 分钟 |
| Gradle clean | 10-30 秒 |
| APK 构建 | 3-5 分钟 |
| **总计** | **5-10 分钟** |
---
## ✅ 打包成功标志
看到以下信息表示打包成功:
```
========================================
✓✓✓ 完整打包流程完成!✓✓✓
========================================
APK 文件位置:
c:\Users\Administrator\Desktop\Project\xinli\xinli-App\app\build\outputs\apk\release\app-release.apk
文件大小:
XXXX MB
```
---
## 🔍 验证 APK 是否包含最新代码
### 检查方法 1: 查看构建时间
```bash
# 查看 APK 文件的修改时间
dir app\build\outputs\apk\release\app-release.apk
```
应该是刚刚构建的时间(几分钟前)
### 检查方法 2: 安装后测试
1. 安装 APK
2. 打开 App
3. 打开浏览器控制台(如果是调试版)
4. 进入答题页面
5. 查看控制台日志:
- 应该看到:`✅ 使用Android原生TTS`
- 或:`✅ Android TTS 已就绪(延迟检测)`
---
## 📝 注意事项
### 1. 前端资源同步
- ⚠️ 如果修改了前端代码,**必须**重新构建前端
- ⚠️ App 中的 WebView 加载的是服务器上的网页
- ⚠️ 打包 APK 后,还需要将前端 `dist` 目录部署到服务器
### 2. 签名说明
- 当前使用 debug 签名(测试用)
- 正式发布需要生成正式签名
- 签名不一致会导致安装失败
### 3. 版本号
- 当前版本:`versionCode 1`, `versionName "1.0"`
- 如需更新版本号,修改 `app/build.gradle`
---
## 🎉 完成!
打包完成后:
1. ✅ 找到 APK 文件
2. ✅ 卸载旧版本
3. ✅ 安装新 APK
4. ✅ 测试功能
5. ✅ 享受修复后的应用!

View File

@ -1,34 +0,0 @@
@echo off
echo ========================================
echo 重新编译项目(应用档案查询修复)
echo ========================================
cd /d "%~dp0"
echo.
echo 正在清理并编译项目...
call mvn clean package -Dmaven.test.skip=true
if %ERRORLEVEL% EQU 0 (
echo.
echo ========================================
echo 编译成功!
echo ========================================
echo.
echo 编译后的jar文件位置
echo %~dp0ry-xinli-admin\target\ry-xinli-admin.jar
echo.
echo 请执行以下步骤:
echo 1. 停止当前运行的服务器
echo 2. 替换jar文件
echo 3. 重新启动服务器
echo 4. 然后再尝试导入数据
echo.
) else (
echo.
echo ========================================
echo 编译失败!请检查错误信息
echo ========================================
)
pause

View File

@ -1,195 +0,0 @@
# ✅ 问卷权限漏洞修复完成报告
## 📋 问题总结
**原问题**:用户登录系统后能看到所有问卷,即使没有分配任何权限。
**根本原因**:问卷列表接口 `/psychology/questionnaire/list` 缺少权限控制,直接返回所有数据。
**严重程度**:⚠️ **高危** - 权限控制完全失效
## ✅ 已修复内容
### 修改文件
- ✅ `ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQuestionnaireController.java`
### 修复内容
#### 1. 添加权限注解
```java
@PreAuthorize("@ss.hasPermi('psychology:questionnaire:list') or @ss.hasAnyRoles('student')")
@GetMapping("/list")
public TableDataInfo list(PsyQuestionnaire questionnaire)
```
#### 2. 添加权限过滤逻辑
- 获取当前用户可访问的问卷ID列表
- 获取所有已配置权限的问卷ID列表
- 根据权限过滤返回数据
#### 3. 三个核心方法
**方法1**: `filterQuestionnaireListByPermission()`
- 过滤问卷列表
- 未配置权限的问卷对所有人开放(向后兼容)
- 已配置权限的问卷只对有权限的用户开放
**方法2**: `resolveAllowedScaleIdsForCurrentUser()`
- 获取当前用户有权限访问的问卷ID
- 管理员返回 null不过滤
- 普通用户返回其授权的ID列表
**方法3**: `resolveRestrictedScaleIds()`
- 获取所有已配置权限的问卷ID
- 用于判断哪些问卷需要权限才能访问
## 🎯 修复后的行为
### 场景 1: 管理员userId = 1
- ✅ 看到所有问卷,不受权限限制
- 理由:管理员拥有最高权限
### 场景 2: 有 `psychology:questionnaire:list` 权限的用户
- ✅ 看到所有问卷,不受权限限制
- 理由:拥有管理权限
### 场景 3: 普通用户student 角色)
**未配置权限的问卷**
- ✅ 所有人都能看到
- 理由:保持向后兼容,公开问卷
**已配置权限的问卷**
- ✅ 已分配权限的用户能看到
- ❌ 未分配权限的用户**看不到**
- 理由:权限控制生效
## 📊 权限逻辑对比表
| 问卷状态 | 管理员 | 普通用户(有权限) | 普通用户(无权限) |
|---------|-------|----------------|----------------|
| **未配置任何权限** | ✅ 可见 | ✅ 可见 | ✅ 可见 |
| **已配置权限(已授权)** | ✅ 可见 | ✅ 可见 | ❌ **不可见** |
| **已配置权限(未授权)** | ✅ 可见 | ❌ **不可见** | ❌ **不可见** |
## 🔧 如何分配问卷权限
### 方法 1: 通过用户管理界面
1. 进入 **系统管理** → **用户管理**
2. 找到目标用户,点击 **编辑**
3. 在"量表权限"选项卡中勾选该用户可访问的问卷
4. 保存
### 方法 2: 通过权限管理菜单
1. 进入 **心理测评** → **权限管理**
2. 添加新权限
3. 选择用户和问卷
4. 保存
### 注意事项
#### 问卷ID的特殊标识
- 问卷在权限表中使用**负数ID**`-questionnaireId`
- 例如问卷ID=5 → 权限表中存储为 -5
- 这样可以与量表ID区分量表使用正数ID
#### 默认行为
- 新建的问卷如果不配置权限,默认对所有人开放
- 一旦为某个问卷配置了权限,就只有有权限的用户才能看到
## 🧪 测试建议
### 测试步骤 1: 验证管理员权限
1. 使用管理员账号admin登录
2. 进入问卷管理页面
3. **预期**:能看到所有问卷
### 测试步骤 2: 验证普通用户(无权限)
1. 创建一个测试用户 test_user1
2. 不给该用户分配任何问卷权限
3. 用 test_user1 登录系统
4. 进入学员测试页面
5. **预期**
- ✅ 能看到未配置权限的问卷(公开问卷)
- ❌ **看不到**已配置权限的问卷
### 测试步骤 3: 验证普通用户(有权限)
1. 创建一个测试用户 test_user2
2. 为 test_user2 分配问卷A、B的权限假设问卷A、B、C都配置了权限
3. 用 test_user2 登录系统
4. **预期**
- ✅ 能看到问卷A、B
- ❌ **看不到**问卷C
- ✅ 能看到未配置权限的公开问卷
### 测试步骤 4: 验证权限动态更新
1. 用 test_user1 登录,记录能看到的问卷列表
2. 管理员为 test_user1 添加问卷D的权限
3. test_user1 刷新页面
4. **预期**test_user1 现在能看到问卷D
## 🚨 重要提醒
### 1. 向后兼容性
- ✅ 修复不会影响现有功能
- ✅ 未配置权限的问卷仍然对所有人开放
- ✅ 只有配置了权限的问卷才会进行权限过滤
### 2. 需要重新编译部署
```bash
# 1. 进入后端项目目录
cd c:\Users\Administrator\Desktop\Project\xinli
# 2. 重新打包
mvn clean package -DskipTests
# 3. 找到生成的jar文件
# 位置: ry-xinli-admin/target/ry-xinli-admin.jar
# 4. 停止旧服务部署新jar
# 根据你的部署方式执行相应操作
```
### 3. 数据库无需修改
- ✅ 不需要执行任何SQL脚本
- ✅ 使用现有的权限表结构
- ✅ 兼容现有数据
### 4. 前端无需修改
- ✅ 前端代码无需改动
- ✅ API调用方式不变
- ✅ 权限过滤在后端自动完成
## 📝 附加说明
### 与量表权限的关系
- 问卷和量表使用**同一套权限机制**
- 在权限表 `psy_scale_permission` 中:
- 量表使用**正数ID** (scaleId = 1, 2, 3...)
- 问卷使用**负数ID** (scaleId = -1, -2, -3...)
### 统一入口推荐
建议前端统一使用量表接口:
```javascript
// 推荐:同时获取量表和问卷
listScale({ includeQuestionnaire: true })
// 也可以:仅获取问卷
listQuestionnaire()
```
两个接口的权限控制逻辑已经统一,效果相同。
## ✅ 修复完成确认
- ✅ 代码已修改
- ✅ 权限控制已生效
- ✅ 向后兼容性已保证
- ✅ 文档已更新
- ⏳ **待部署** - 需要重新编译打包部署
---
**修复时间**2024年12月2日
**修复人员**Cascade AI
**修复级别**:🔥 紧急修复(权限漏洞)
**部署状态**:⏳ 等待部署

View File

@ -1,132 +0,0 @@
# 问卷权限问题修复说明
## 问题描述
用户反馈:问卷明明没有设置权限,但用户却能够查看。
## 问题根源
在权限系统中存在一个**"所有用户"权限机制**
### 权限查询逻辑(`PsyScalePermissionMapper.xml`
```sql
-- 或者所有用户权限user_id, role_id, dept_id 都为空)
or (p.user_id is null and p.role_id is null and p.dept_id is null)
```
这意味着:**如果在 `psy_scale_permission` 表中存在一条记录,其 `user_id`、`role_id`、`dept_id` 都为空,那么所有用户都能看到这个量表/问卷!**
### 问题产生原因
1. 当在权限管理页面新增权限时,如果**不选择任何用户**(留空),系统会创建一条 `user_id = null` 的记录
2. 这条记录会被解释为"所有用户都有权限"
3. 在权限列表中,这种记录会显示为"所有用户"标签,但之前不够明显
## 修复内容
### 1. 前端权限管理页面优化
修改文件:`xinli-ui/src/views/psychology/permission/index.vue`
**改进内容:**
1. **列表显示优化**
- 列名从"用户名称"改为"授权范围"
- "所有用户"权限使用醒目的橙色警告标签显示
- 显示"所有用户都可访问"的提示文字
2. **新增筛选功能**
- 添加"授权范围"筛选下拉框
- 可以快速筛选"所有用户"或"指定用户"权限
3. **添加明确的"所有用户"开关**
- 新增权限时有"授权范围"选项,可以选择"所有用户"或"指定用户"
- 启用"所有用户"时会显示警告提示
4. **强制用户选择**
- 如果不启用"所有用户"开关,必须选择至少一个用户
- 防止意外创建"所有用户"权限
5. **二次确认**
- 选择"所有用户"时会弹出确认框,提醒用户这将使所有人都能看到该量表/问卷
### 2. 数据库检查脚本
创建文件:`检查和清理所有用户权限.sql`
提供SQL脚本用于
- 查看所有"所有用户"权限记录
- 统计"所有用户"权限数量
- 禁用或删除"所有用户"权限
## 如何检查现有数据
1. 运行以下SQL查看当前存在的"所有用户"权限:
```sql
SELECT
p.permission_id,
p.scale_id,
CASE
WHEN p.scale_id < 0 THEN (SELECT questionnaire_name FROM psy_questionnaire WHERE questionnaire_id = -p.scale_id)
ELSE (SELECT scale_name FROM psy_scale WHERE scale_id = p.scale_id)
END AS scale_name,
CASE
WHEN p.scale_id < 0 THEN '问卷'
ELSE '量表'
END AS type,
p.status
FROM psy_scale_permission p
WHERE p.user_id IS NULL
AND p.role_id IS NULL
AND p.dept_id IS NULL
AND p.status = '0';
```
2. 如果发现不需要的"所有用户"权限,可以:
- 在权限管理页面找到该记录(显示"所有用户"标签),点击删除
- 或直接在数据库中删除/禁用
## 如何清理不需要的权限
### 方法1通过管理界面
1. 进入 **心理测评管理** → **量表权限管理**
2. 找到显示"所有用户"标签的权限记录
3. 点击"删除"按钮
### 方法2通过SQL
```sql
-- 禁用所有"所有用户"权限
UPDATE psy_scale_permission
SET status = '1', update_time = NOW()
WHERE user_id IS NULL
AND role_id IS NULL
AND dept_id IS NULL
AND status = '0';
-- 或者直接删除
DELETE FROM psy_scale_permission
WHERE user_id IS NULL
AND role_id IS NULL
AND dept_id IS NULL;
```
## 权限优先级说明
系统权限检查顺序:
1. **用户直接权限**: `user_id = 当前用户ID`
2. **角色权限**: `role_id = 用户的角色ID`
3. **部门权限**: `dept_id = 用户的部门ID`
4. **全局权限**: `user_id`, `role_id`, `dept_id` 都为空(所有用户)
只要满足任意一个条件,用户就能看到该量表/问卷。
## 注意事项
1. 修改后,新增权限时必须明确选择"所有用户"或指定具体用户
2. 建议检查现有数据,清理不需要的"所有用户"权限
3. 管理员角色admin不受权限限制始终可以看到所有量表/问卷

View File

@ -1,357 +0,0 @@
# 🔐 问卷权限问题分析报告
## 📋 问题描述
**用户反馈**:登录系统后能看到所有问卷,明明没有给用户分配问卷权限。
**影响范围**:所有普通用户都能看到系统中的所有问卷,存在严重的权限控制漏洞。
## 🔍 问题根源
### 1. 问卷列表接口缺少权限控制
**文件**`ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQuestionnaireController.java`
```java
/**
* 获取问卷列表(答题用户可访问)
*/
@GetMapping("/list")
public TableDataInfo list(PsyQuestionnaire questionnaire)
{
startPage();
List<PsyQuestionnaire> list = questionnaireService.selectQuestionnaireList(questionnaire);
return getDataTable(list);
}
```
**问题点**
- ❌ **没有 `@PreAuthorize` 权限注解**
- ❌ **没有权限过滤逻辑**
- ❌ **直接返回所有问卷数据**
- ❌ **任何登录用户都可以访问**
### 2. 对比:量表接口有完整的权限控制
**文件**`ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyScaleController.java`
```java
/**
* 获取量表列表(包含问卷)
* 允许管理员和学员访问
*/
@PreAuthorize("@ss.hasPermi('psychology:scale:list') or @ss.hasAnyRoles('student')")
@GetMapping("/list")
public TableDataInfo list(PsyScale scale, @RequestParam(required = false, defaultValue = "true") Boolean includeQuestionnaire)
{
Set<Long> allowedScaleIds = resolveAllowedScaleIdsForCurrentUser();
Set<Long> restrictedScaleIds = resolveRestrictedScaleIds();
boolean needPermissionFilter = allowedScaleIds != null;
// ... 权限过滤逻辑 ...
}
```
**优点**
- ✅ 有 `@PreAuthorize` 权限注解
- ✅ 有完整的权限过滤逻辑
- ✅ 根据用户权限返回数据
- ✅ 支持管理员和普通用户的不同权限
### 3. 权限过滤逻辑说明
`PsyScaleController` 中,问卷的处理逻辑是:
```java
if ("questionnaire".equalsIgnoreCase(sourceType))
{
boolean restricted = restrictedScaleIds != null && restrictedScaleIds.contains(scaleId);
if (!restricted)
{
filtered.add(scale); // 只要不在限制列表中,就添加
continue;
}
}
```
**这段代码的含义**
- **问卷采用黑名单机制**:默认所有人可见,除非该问卷配置了权限
- **量表采用白名单机制**:只有分配了权限的用户才能看到
**问题**
- 即使量表接口有权限过滤,但前端可以直接调用问卷接口 `/psychology/questionnaire/list`,完全绕过权限控制
## 📊 权限控制对比
| 项目 | 问卷接口 | 量表接口 |
|------|---------|---------|
| **接口路径** | `/psychology/questionnaire/list` | `/psychology/scale/list` |
| **权限注解** | ❌ 无 | ✅ `@PreAuthorize` |
| **权限过滤** | ❌ 无 | ✅ 完整逻辑 |
| **访问控制** | ❌ 所有人可见 | ✅ 根据权限过滤 |
| **安全性** | ❌ 严重漏洞 | ✅ 安全 |
## 🔧 修复方案
### 方案一:为问卷接口添加权限控制(推荐)
修改 `PsyQuestionnaireController.java`,添加完整的权限控制逻辑。
**优点**
- ✅ 问卷和量表使用统一的权限机制
- ✅ 安全性高,符合最小权限原则
- ✅ 可以精确控制每个用户看到的问卷
**实现步骤**
1. 在 `PsyQuestionnaireController` 中注入 `IPsyScalePermissionService`
2. 添加 `@PreAuthorize` 权限注解
3. 实现权限过滤逻辑(参考 `PsyScaleController`
4. 修改 `/list` 接口,根据用户权限返回问卷列表
### 方案二:问卷永久对所有人开放(不推荐)
如果业务上问卷确实需要对所有人开放(不需要权限控制),需要:
1. 明确文档说明:问卷对所有登录用户开放
2. 确保量表接口的权限过滤逻辑不影响问卷显示
3. 前端统一使用量表接口(`/psychology/scale/list?includeQuestionnaire=true`
**问题**
- ⚠️ 无法精确控制用户可见的问卷
- ⚠️ 可能暴露敏感内容
## ✅ 推荐修复代码
### 修改 `PsyQuestionnaireController.java`
```java
package com.ddnai.web.controller.psychology;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import com.ddnai.common.annotation.Log;
import com.ddnai.common.core.controller.BaseController;
import com.ddnai.common.core.domain.AjaxResult;
import com.ddnai.common.core.page.TableDataInfo;
import com.ddnai.common.enums.BusinessType;
import com.ddnai.common.utils.SecurityUtils;
import com.ddnai.system.domain.psychology.PsyQuestionnaire;
import com.ddnai.system.service.psychology.IPsyQuestionnaireService;
import com.ddnai.system.service.psychology.IPsyScalePermissionService;
@RestController
@RequestMapping("/psychology/questionnaire")
public class PsyQuestionnaireController extends BaseController
{
@Autowired
private IPsyQuestionnaireService questionnaireService;
@Autowired
private IPsyScalePermissionService scalePermissionService;
/**
* 获取问卷列表(带权限控制)
*/
@PreAuthorize("@ss.hasPermi('psychology:questionnaire:list') or @ss.hasAnyRoles('student')")
@GetMapping("/list")
public TableDataInfo list(PsyQuestionnaire questionnaire)
{
// 获取当前用户权限
Set<Long> allowedScaleIds = resolveAllowedScaleIdsForCurrentUser();
Set<Long> restrictedScaleIds = resolveRestrictedScaleIds();
// 查询所有问卷
startPage();
List<PsyQuestionnaire> list = questionnaireService.selectQuestionnaireList(questionnaire);
// 权限过滤
list = filterQuestionnaireListByPermission(list, allowedScaleIds, restrictedScaleIds);
return getDataTable(list);
}
/**
* 根据用户权限过滤问卷列表
*/
private List<PsyQuestionnaire> filterQuestionnaireListByPermission(
List<PsyQuestionnaire> list,
Set<Long> allowedScaleIds,
Set<Long> restrictedScaleIds)
{
// 管理员或无需权限过滤
if (allowedScaleIds == null || list == null)
{
return list;
}
List<PsyQuestionnaire> filtered = new ArrayList<>();
for (PsyQuestionnaire questionnaire : list)
{
if (questionnaire == null)
{
continue;
}
// 使用负数ID标识问卷与量表接口保持一致
Long scaleId = -questionnaire.getQuestionnaireId();
// 如果该问卷未配置权限,对所有人开放
boolean restricted = restrictedScaleIds != null && restrictedScaleIds.contains(scaleId);
if (!restricted)
{
filtered.add(questionnaire);
continue;
}
// 如果配置了权限,检查用户是否有权限
if (allowedScaleIds.contains(scaleId))
{
filtered.add(questionnaire);
}
}
return filtered;
}
/**
* 解析当前用户可访问的量表/问卷ID集合
* @return null 表示无需权限过滤管理员非null 表示必须过滤
*/
private Set<Long> resolveAllowedScaleIdsForCurrentUser()
{
try
{
Long currentUserId = SecurityUtils.getUserId();
// 管理员拥有所有权限
if (currentUserId == null || currentUserId.equals(1L))
{
return null;
}
// 检查是否有管理权限
boolean hasManagePerm = false;
try
{
hasManagePerm = SecurityUtils.hasPermi("psychology:questionnaire:list");
}
catch (Exception ignore)
{
// 忽略权限判断异常
}
if (hasManagePerm)
{
return null;
}
// 获取用户有权限访问的量表/问卷ID
List<Long> scaleIds = scalePermissionService.selectScaleIdsByUserId(currentUserId);
return new HashSet<>(scaleIds != null ? scaleIds : new ArrayList<>());
}
catch (Exception e)
{
logger.warn("获取用户问卷权限失败: {}", e.getMessage());
return null;
}
}
/**
* 获取所有已配置权限的量表/问卷ID
*/
private Set<Long> resolveRestrictedScaleIds()
{
try
{
List<Long> ids = scalePermissionService.selectAllScaleIdsWithPermission();
if (ids == null || ids.isEmpty())
{
return java.util.Collections.emptySet();
}
return new HashSet<>(ids);
}
catch (Exception e)
{
logger.warn("解析问卷权限限制列表失败: {}", e.getMessage());
return java.util.Collections.emptySet();
}
}
// ... 其他方法保持不变 ...
}
```
## 🧪 测试验证
### 测试场景 1普通用户未分配权限
**预期**
- ✅ 只能看到未配置权限的问卷(公开问卷)
- ❌ 不能看到已配置权限但未分配给该用户的问卷
### 测试场景 2普通用户已分配权限
**预期**
- ✅ 能看到未配置权限的问卷
- ✅ 能看到已分配权限的问卷
- ❌ 不能看到未分配权限的其他问卷
### 测试场景 3管理员
**预期**
- ✅ 能看到所有问卷(不受权限限制)
### 测试步骤
1. **准备数据**
- 创建3个问卷A未配置权限、B已配置权限、C已配置权限
- 创建普通用户 user1只分配问卷B的权限
2. **测试 user1 登录**
```bash
# 预期结果只能看到问卷A和B不能看到C
GET /psychology/questionnaire/list
```
3. **测试管理员登录**
```bash
# 预期结果能看到所有问卷A、B、C
GET /psychology/questionnaire/list
```
## 📝 修改文件清单
- ✅ `ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQuestionnaireController.java`
## ⚠️ 注意事项
1. **权限分配兼容性**
- 问卷使用负数ID-questionnaireId存储在权限表中
- 与量表接口保持一致
2. **默认行为**
- 未配置权限的问卷对所有人开放(向后兼容)
- 已配置权限的问卷只对有权限的用户开放
3. **前端调用**
- 建议统一使用量表接口:`/psychology/scale/list?includeQuestionnaire=true`
- 或使用修复后的问卷接口:`/psychology/questionnaire/list`
## 🎯 修复后的效果
| 用户类型 | 修复前 | 修复后 |
|---------|-------|-------|
| **管理员** | 看到所有问卷 | 看到所有问卷 ✅ |
| **普通用户(无权限)** | 看到所有问卷 ❌ | 只看到公开问卷 ✅ |
| **普通用户(有权限)** | 看到所有问卷 ❌ | 看到公开+授权问卷 ✅ |
---
**问题发现时间**2024年12月2日
**严重程度**:⚠️ 高危(权限控制漏洞)
**修复优先级**:🔥 紧急

View File

@ -1,490 +0,0 @@
# 项目结构分析报告
## 📋 项目概述
**项目名称**AI心理健康测评系统 (ry-xinli)
**项目类型**:心理健康测评管理系统
**技术架构**:前后端分离 + 移动端
**开发框架**:基于 RuoYi-Vue 框架改造
**版本**1.0.0
**开发者**wanxiubin
---
## 🏗️ 整体架构
### 架构分层
```
┌─────────────────────────────────────────────────────────────┐
│ 用户层 │
│ 桌面浏览器 │ 移动浏览器 │ 微信小程序 │ 第三方系统API │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 前端应用层 │
│ Vue 2.6 + Element UI + Vuex + Router │
└─────────────────────────────────────────────────────────────┘
↕ (HTTP/RESTful API)
┌─────────────────────────────────────────────────────────────┐
│ 应用服务层 │
│ Spring Boot 2.5 + Spring Security │
│ Controller层 → Service层 → Mapper层 (MyBatis) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 数据存储层 │
│ ┌──────────────┬──────────────┬──────────────┐ │
│ │ MySQL │ Redis │ 文件存储 │ │
│ │ (数据持久) │ (缓存/会话) │ (报告/音频) │ │
│ └──────────────┴──────────────┴──────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
---
## 📦 后端模块结构
### Maven 多模块架构
```
ry-xinli (根模块)
├── ry-xinli-admin # 启动模块Web入口
├── ry-xinli-system # 系统模块(业务逻辑)
├── ry-xinli-framework # 框架模块(安全配置)
├── ry-xinli-common # 公共模块(工具类)
├── ry-xinli-generator # 代码生成器
└── ry-xinli-quartz # 定时任务
```
### 模块详细说明
#### 1. ry-xinli-admin启动模块
**路径**`ry-xinli-admin/`
**职责**Web 应用入口,包含 Controller 层和配置
**结构**
```
ry-xinli-admin/
├── src/main/java/com/ddnai/
│ ├── XinliApplication.java # 启动类
│ └── web/controller/ # 控制器层
│ ├── psychology/ # 心理测评控制器20个
│ │ ├── PsyScaleController.java
│ │ ├── PsyAssessmentController.java
│ │ ├── PsyAssessmentReportController.java
│ │ ├── PsyFactorController.java
│ │ ├── PsyQrcodeController.java
│ │ ├── PsyQuestionnaireController.java
│ │ ├── PsyUserProfileController.java
│ │ ├── PsyWarningController.java
│ │ └── ...
│ ├── system/ # 系统管理控制器15个
│ │ ├── SysUserController.java
│ │ ├── SysRoleController.java
│ │ ├── SysMenuController.java
│ │ └── ...
│ ├── monitor/ # 监控控制器5个
│ ├── common/ # 通用控制器
│ └── api/ # 开放API
└── src/main/resources/
├── application.yml # 应用配置
└── mapper/ # MyBatis映射文件
```
**关键配置**
- 服务端口30081
- 文件上传路径:`D:\wwwroot\xinli_web\web\profile\uploadPath`
- Redis 缓存配置
- 数据库连接配置
#### 2. ry-xinli-system系统模块
**路径**`ry-xinli-system/`
**职责**:核心业务逻辑,包含 Domain、Mapper、Service
**结构**
```
ry-xinli-system/
├── src/main/java/com/ddnai/system/
│ ├── domain/ # 实体类
│ │ ├── psychology/ # 心理测评实体20+个)
│ │ │ ├── PsyScale.java # 量表
│ │ │ ├── PsyScaleItem.java # 量表题目
│ │ │ ├── PsyScaleOption.java # 题目选项
│ │ │ ├── PsyAssessment.java # 测评记录
│ │ │ ├── PsyAssessmentAnswer.java # 测评答案
│ │ │ ├── PsyAssessmentReport.java # 测评报告
│ │ │ ├── PsyFactor.java # 因子
│ │ │ ├── PsyFactorRule.java # 因子规则
│ │ │ ├── PsyQrcode.java # 二维码
│ │ │ ├── PsyQuestionnaire.java # 自定义问卷
│ │ │ ├── PsyUserProfile.java # 用户档案
│ │ │ ├── PsyWarning.java # 危机预警
│ │ │ └── ...
│ │ └── vo/ # 视图对象
│ ├── mapper/ # 数据访问层
│ │ ├── psychology/ # 心理测评Mapper20+个)
│ │ └── Sys*.java # 系统Mapper
│ └── service/ # 业务逻辑层
│ ├── psychology/ # 心理测评Service接口25个
│ └── impl/ # Service实现类
│ ├── psychology/ # 心理测评Service实现24个
│ └── Sys*.java # 系统Service实现
└── src/main/resources/mapper/ # MyBatis XML映射文件
├── psychology/ # 心理测评Mapper XML39个
└── system/ # 系统Mapper XML
```
#### 3. ry-xinli-framework框架模块
**路径**`ry-xinli-framework/`
**职责**:框架配置、安全、通用组件
**主要功能**
- Spring Security 安全配置
- 系统配置管理
- 通用 Web 组件
- 拦截器、过滤器
#### 4. ry-xinli-common公共模块
**路径**`ry-xinli-common/`
**职责**:公共工具类、常量、核心类
**结构**
```
ry-xinli-common/
├── src/main/java/com/ddnai/common/
│ ├── core/ # 核心类
│ ├── utils/ # 工具类
│ │ ├── file/ # 文件工具
│ │ ├── http/ # HTTP工具
│ │ ├── ip/ # IP工具
│ │ ├── poi/ # Excel工具
│ │ ├── sign/ # 签名工具
│ │ └── ...
│ ├── constants/ # 常量
│ └── config/ # 配置类
```
#### 5. ry-xinli-generator代码生成器
**路径**`ry-xinli-generator/`
**职责**:代码自动生成工具
#### 6. ry-xinli-quartz定时任务
**路径**`ry-xinli-quartz/`
**职责**:定时任务调度
---
## 🎨 前端模块结构
### Vue 前端项目
**路径**`xinli-ui/`
**技术栈**
- Vue 2.6.12
- Element UI 2.15.14
- Vuex 3.6.0
- Vue Router 3.4.9
- Axios 0.28.1
- ECharts 5.4.0
**结构**
```
xinli-ui/
├── src/
│ ├── api/ # API接口定义40个文件
│ │ ├── psychology/ # 心理测评API19个
│ │ │ ├── scale.js # 量表API
│ │ │ ├── assessment.js # 测评API
│ │ │ ├── report.js # 报告API
│ │ │ ├── questionnaire.js # 问卷API
│ │ │ └── ...
│ │ ├── system/ # 系统API10个
│ │ └── monitor/ # 监控API7个
│ ├── views/ # 页面视图76个Vue文件
│ │ ├── psychology/ # 心理测评页面
│ │ │ ├── scale/ # 量表管理
│ │ │ ├── assessment/ # 测评执行
│ │ │ ├── report/ # 测评报告
│ │ │ ├── questionnaire/ # 自定义问卷
│ │ │ ├── qrcode/ # 二维码管理
│ │ │ ├── userProfile/ # 用户档案
│ │ │ ├── warning/ # 危机预警
│ │ │ └── website/ # 心理网站
│ │ └── system/ # 系统管理页面
│ ├── components/ # 组件32个
│ │ ├── Psychology/ # 心理测评组件
│ │ └── RuoYi/ # 若依通用组件
│ ├── store/ # Vuex状态管理8个文件
│ ├── router/ # 路由配置
│ ├── utils/ # 工具函数24个文件
│ ├── assets/ # 静态资源
│ │ ├── icons/ # 图标89个SVG
│ │ ├── images/ # 图片
│ │ └── styles/ # 样式文件
│ └── main.js # 入口文件
├── public/ # 公共资源
└── package.json # 依赖配置
```
---
## 📱 移动端项目
### Android 应用
**路径**`xinli-App/`
**技术栈**Android + Gradle
**结构**
```
xinli-App/
├── app/
│ ├── src/main/
│ │ ├── java/ # Java代码
│ │ ├── res/ # 资源文件
│ │ └── AndroidManifest.xml # 清单文件
│ └── build.gradle # 构建配置
├── build.gradle # 项目构建配置
├── gradle/ # Gradle配置
└── README.md # 说明文档
```
---
## 🗄️ 数据库设计
### 核心表结构
#### 心理测评核心表20+个)
1. **量表相关**
- `psy_scale` - 量表基本信息
- `psy_scale_item` - 量表题目
- `psy_scale_option` - 题目选项
- `psy_scale_permission` - 量表权限
2. **测评相关**
- `psy_assessment` - 测评记录
- `psy_assessment_answer` - 测评答案
- `psy_assessment_report` - 测评报告
3. **因子与计分**
- `psy_factor` - 因子定义
- `psy_factor_rule` - 因子计分规则
- `psy_factor_score` - 因子得分
- `psy_result_interpretation` - 结果解释
4. **扩展功能**
- `psy_questionnaire` - 自定义问卷
- `psy_questionnaire_item` - 问卷题目
- `psy_questionnaire_answer` - 问卷答案
- `psy_user_profile` - 用户档案
- `psy_qrcode` - 二维码管理
- `psy_warning` - 危机预警
- `psy_website_content` - 心理网站内容
#### 系统管理表
- `sys_user` - 用户表
- `sys_role` - 角色表
- `sys_menu` - 菜单表
- `sys_dept` - 部门表
- `sys_config` - 系统配置
- `sys_dict_*` - 字典表
---
## 🔧 技术栈详情
### 后端技术栈
| 技术 | 版本 | 用途 |
|------|------|------|
| Spring Boot | 2.5.15 | 应用框架 |
| Spring Security | 5.7.14 | 安全框架 |
| MyBatis | - | ORM框架 |
| MySQL | - | 数据库 |
| Redis | - | 缓存/会话 |
| Druid | 1.2.23 | 数据库连接池 |
| JWT | 0.9.1 | 令牌认证 |
| Swagger | 3.0.0 | API文档 |
| Apache POI | 4.1.2 | Excel处理 |
| PDFBox | 2.0.29 | PDF处理 |
| ZXing | 3.5.1 | 二维码生成 |
### 前端技术栈
| 技术 | 版本 | 用途 |
|------|------|------|
| Vue | 2.6.12 | 前端框架 |
| Element UI | 2.15.14 | UI组件库 |
| Vuex | 3.6.0 | 状态管理 |
| Vue Router | 3.4.9 | 路由管理 |
| Axios | 0.28.1 | HTTP客户端 |
| ECharts | 5.4.0 | 图表库 |
| Quill | 2.0.2 | 富文本编辑器 |
---
## 📊 核心功能模块
### 1. 量表管理模块 ✅
- 量表CRUD操作
- 量表导入导出Excel
- 量表状态管理
- 量表权限控制
### 2. 题目管理模块 ✅
- 题目编辑(富文本)
- 选项配置
- 批量导入题目
- 题目排序拖拽
### 3. 因子与计分模块 ✅
- 因子配置
- 因子公式设置
- 权重配置
- 反向计分设置
### 4. 测评执行模块 ✅
- 开始测评
- 暂停/恢复测评
- 提交测评
- 防重复提交
- 断点续测
### 5. 报告生成模块 ✅
- 自动生成报告
- 因子分计算
- 结果解释
- 报告导出PDF/Excel
### 6. 用户档案管理 ✅
- 档案字段自定义
- 不同年龄段模板
- 档案信息CRUD
- 档案导入导出
### 7. 自定义问卷模块 ✅
- 问卷创建/编辑
- 多种题目类型支持9种
- 自动计分
- 成绩排名统计
### 8. 二维码功能 ✅
- 生成测评二维码
- 扫码测试
- 扫码查看报告
- 扫码注册/登录
### 9. 危机预警模块
- 预警规则设置
- 预警记录管理
- 预警解除记录
### 10. 心理网站模块
- 文章发布/编辑
- 栏目管理
- 留言评论
- 个性化配置
---
## 📁 项目目录结构总览
```
xinli/
├── bin/ # 构建脚本
│ ├── clean.bat
│ ├── package.bat
│ └── run.bat
├── doc/ # 文档
├── pom.xml # Maven根配置
├── ry-xinli-admin/ # 启动模块
├── ry-xinli-system/ # 系统模块
├── ry-xinli-framework/ # 框架模块
├── ry-xinli-common/ # 公共模块
├── ry-xinli-generator/ # 代码生成器
├── ry-xinli-quartz/ # 定时任务
├── xinli-ui/ # Vue前端
├── xinli-App/ # Android移动端
├── sql/ # SQL脚本
├── z_Project change/ # 项目变更文档
│ ├── 进度汇总/ # 开发进度文档
│ └── 量表示例/ # 量表示例数据
└── 项目介绍/ # 项目介绍文档
```
---
## 🔐 安全配置
- Spring Security 认证授权
- JWT Token 机制
- 密码加密存储
- XSS 攻击防护
- 防盗链配置
- 操作日志记录
---
## 📈 项目规模统计
### 代码统计(估算)
| 模块 | Java文件 | Vue文件 | 总行数(估算) |
|------|---------|---------|---------------|
| 后端 | 400+ | - | 50,000+ |
| 前端 | - | 76 | 30,000+ |
| 移动端 | 1 | - | 1,000+ |
| **总计** | **400+** | **76** | **80,000+** |
### 功能模块统计
- **Controller**: 40+ 个
- **Service**: 50+ 个
- **Mapper**: 60+ 个
- **实体类**: 30+ 个
- **前端页面**: 76 个
- **API接口**: 100+ 个
---
## 🎯 项目特点
1. **模块化设计**前后端分离Maven多模块架构
2. **功能完整**:覆盖心理测评全流程
3. **扩展性强**:支持自定义问卷、用户档案扩展
4. **安全可靠**:完善的权限控制和数据安全
5. **多端支持**Web端 + 移动端
6. **易于维护**:代码规范,文档完善
---
## 📝 开发状态
### 已完成功能 ✅
- 第一阶段:数据库设计与初始化
- 第二阶段:核心功能模块(量表、测评、报告)
- 第三阶段D3-1用户档案管理
- 第三阶段D3-2自定义问卷基础功能
- 第三阶段D3-3二维码管理功能
### 进行中功能 🟡
- 第三阶段剩余模块(心理网站、数据统计等)
---
## 🔗 相关文档
- 技术架构设计:`z_Project change/进度汇总/3-技术架构设计.md`
- 开发计划:`z_Project change/进度汇总/1-开发计划.md`
- 使用指南:`使用指南-*.md`
- 项目改造说明:`项目介绍/项目改造说明.md`
---
**报告生成时间**2025-01-27
**项目版本**1.0.0
**分析状态**:✅ 完成