# 后勤满意度调查API文档 ## 文档信息 - **版本**:1.0.0 - **最后更新**:2025-07-01 - **作者**:Cursor AI - **分类**:后勤管理模块 ## 概述 本文档详细说明后勤满意度调查系统的API接口设计,包括问卷管理、题目管理、选项管理、答题记录等功能。系统采用前后端分离架构,接口采用RESTful API风格设计,所有接口均需通过Token进行身份验证。 ## 通用响应格式 ### 成功响应 ```json { "code": 200, "msg": "操作成功", "data": {} // 响应数据,具体结构取决于不同接口 } ``` ### 分页数据响应 ```json { "total": 100, // 总记录数 "rows": [], // 当前页记录列表 "code": 200, // 状态码 "msg": "查询成功" // 提示信息 } ``` ### 错误响应 ```json { "code": 500, // 错误码 "msg": "操作失败", // 错误消息 "data": null // 通常为空 } ``` ## 认证与授权 所有接口均需要在请求头中携带Token进行身份验证: ``` Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` ## 接口清单 | 接口路径 | 方法 | 描述 | 权限标识 | |---------|-----|------|---------| | `/hq/satSurvey/list` | GET | 查询问卷列表 | hq:satSurvey:list | | `/hq/satSurvey/{id}` | GET | 获取问卷详情 | hq:satSurvey:query | | `/hq/satSurvey` | POST | 新增问卷 | hq:satSurvey:add | | `/hq/satSurvey` | PUT | 修改问卷 | hq:satSurvey:edit | | `/hq/satSurvey/{ids}` | DELETE | 删除问卷 | hq:satSurvey:remove | | `/hq/satSurvey/publish/{id}` | PUT | 发布问卷 | hq:satSurvey:publish | | `/hq/satQuestion/list` | GET | 查询题目列表 | hq:satQuestion:list | | `/hq/satQuestion/{id}` | GET | 获取题目详情 | hq:satQuestion:query | | `/hq/satQuestion` | POST | 新增题目 | hq:satQuestion:add | | `/hq/satQuestion` | PUT | 修改题目 | hq:satQuestion:edit | | `/hq/satQuestion/{ids}` | DELETE | 删除题目 | hq:satQuestion:remove | | **`/hq/satQuestion/{questionId}/options`** | GET | 获取题目的选项列表 | hq:satQuestion:query | | **`/hq/satOption/list`** | GET | 查询题目选项列表 | hq:satOption:list | | **`/hq/satOption/{id}`** | GET | 获取题目选项详情 | hq:satOption:query | | **`/hq/satOption`** | POST | 新增题目选项 | hq:satOption:add | | **`/hq/satOption`** | PUT | 修改题目选项 | hq:satOption:edit | | **`/hq/satOption/{ids}`** | DELETE | 删除题目选项 | hq:satOption:remove | | `/hq/satRecord/list` | GET | 查询答题记录列表 | hq:satRecord:list | | `/hq/satRecord/{id}` | GET | 获取答题记录详情 | hq:satRecord:query | | `/hq/satSurvey/{id}/statistics` | GET | 获取问卷统计分析 | hq:satSurvey:result | | `/hq/satSurvey/{id}/records` | GET | 获取问卷答题记录 | hq:satSurvey:result | | `/hq/satSurvey/{id}/exportReport` | POST | 导出调查报告 | hq:satSurvey:export | ## API详情 ### 题目选项管理API #### 1. 查询题目选项列表 **请求方式**:GET **请求路径**:`/hq/satOption/list` **请求参数**: | 参数名 | 类型 | 必填 | 描述 | |-------|-----|------|------| | questionId | Long | 否 | 题目ID | | pageNum | Integer | 否 | 页码,默认1 | | pageSize | Integer | 否 | 每页条数,默认10 | **请求示例**: ``` GET /hq/satOption/list?questionId=1937751489358733313&pageNum=1&pageSize=10 ``` **响应示例**: ```json { "total": 5, "rows": [ { "id": 1937810382736855041, "questionId": 1937751489358733313, "optionContent": "非常满意", "optionScore": 5, "optionOrder": 1, "isDefault": "N", "tenantId": "000000", "createBy": 1, "createTime": "2025-06-25 17:50:18", "updateBy": 1, "updateTime": "2025-06-25 17:50:18", "deleted": "0" }, { "id": 1937810382736855042, "questionId": 1937751489358733313, "optionContent": "满意", "optionScore": 4, "optionOrder": 2, "isDefault": "N", "tenantId": "000000", "createBy": 1, "createTime": "2025-06-25 17:50:18", "updateBy": 1, "updateTime": "2025-06-25 17:50:18", "deleted": "0" } // ... 更多选项数据 ], "code": 200, "msg": "查询成功" } ``` #### 2. 获取题目的选项列表 **请求方式**:GET **请求路径**:`/hq/satQuestion/{questionId}/options` **请求参数**: | 参数名 | 类型 | 必填 | 描述 | |-------|-----|------|------| | questionId | Long | 是 | 题目ID | **请求示例**: ``` GET /hq/satQuestion/1937751489358733313/options ``` **响应示例**: ```json { "code": 200, "msg": "操作成功", "data": [ { "id": 1937810382736855041, "questionId": 1937751489358733313, "optionContent": "非常满意", "optionScore": 5, "optionOrder": 1, "isDefault": "N", "tenantId": "000000", "createBy": 1, "createTime": "2025-06-25 17:50:18", "updateBy": 1, "updateTime": "2025-06-25 17:50:18", "deleted": "0" }, { "id": 1937810382736855042, "questionId": 1937751489358733313, "optionContent": "满意", "optionScore": 4, "optionOrder": 2, "isDefault": "N", "tenantId": "000000", "createBy": 1, "createTime": "2025-06-25 17:50:18", "updateBy": 1, "updateTime": "2025-06-25 17:50:18", "deleted": "0" }, // ... 更多选项数据 ] } ``` **前端使用说明**: - 此接口返回指定题目的全部选项,数据结构为数组,不分页 - **实现变更(2025-07-17):** - 接口现在根据questionId参数过滤选项,只返回特定题目的选项数据 - 返回的选项数据已按照optionOrder字段升序排序,无需前端再次排序 - 适用于问题选择界面或答题场景,可一次性获取题目所有选项 - 对于单选题和多选题应显示选项,对于问答题则不需要显示 #### 3. 获取题目选项详情 **请求方式**:GET **请求路径**:`/hq/satOption/{id}` **请求参数**: | 参数名 | 类型 | 必填 | 描述 | |-------|-----|------|------| | id | Long | 是 | 选项ID | **请求示例**: ``` GET /hq/satOption/1937810382736855041 ``` **响应示例**: ```json { "code": 200, "msg": "操作成功", "data": { "id": 1937810382736855041, "questionId": 1937751489358733313, "optionContent": "非常满意", "optionScore": 5, "optionOrder": 1, "isDefault": "N", "tenantId": "000000", "createBy": 1, "createTime": "2025-06-25 17:50:18", "updateBy": 1, "updateTime": "2025-06-25 17:50:18", "deleted": "0" } } ``` #### 4. 新增题目选项 **请求方式**:POST **请求路径**:`/hq/satOption` **请求参数**: | 参数名 | 类型 | 必填 | 描述 | |-------|-----|------|------| | questionId | Long | 是 | 题目ID | | optionContent | String | 是 | 选项内容 | | optionScore | Integer | 否 | 选项分值,默认0 | | optionOrder | Integer | 否 | 选项排序,默认0 | | isDefault | String | 否 | 是否默认选项(Y是 N否),默认N | **请求示例**: ```json { "questionId": 1937751489358733313, "optionContent": "一般般", "optionScore": 3, "optionOrder": 3, "isDefault": "N" } ``` **响应示例**: ```json { "code": 200, "msg": "操作成功", "data": null } ``` #### 5. 修改题目选项 **请求方式**:PUT **请求路径**:`/hq/satOption` **请求参数**: | 参数名 | 类型 | 必填 | 描述 | |-------|-----|------|------| | id | Long | 是 | 选项ID | | questionId | Long | 是 | 题目ID | | optionContent | String | 是 | 选项内容 | | optionScore | Integer | 否 | 选项分值 | | optionOrder | Integer | 否 | 选项排序 | | isDefault | String | 否 | 是否默认选项(Y是 N否) | **请求示例**: ```json { "id": 1937810382736855041, "questionId": 1937751489358733313, "optionContent": "非常满意", "optionScore": 5, "optionOrder": 1, "isDefault": "Y" } ``` **响应示例**: ```json { "code": 200, "msg": "操作成功", "data": null } ``` #### 6. 删除题目选项 **请求方式**:DELETE **请求路径**:`/hq/satOption/{ids}` **请求参数**: | 参数名 | 类型 | 必填 | 描述 | |-------|-----|------|------| | ids | String | 是 | 选项ID,多个用逗号分隔 | **请求示例**: ``` DELETE /hq/satOption/1937810382736855041,1937810382736855042 ``` **响应示例**: ```json { "code": 200, "msg": "操作成功", "data": null } ``` ## 最佳实践 ### 题目选项批量管理 虽然当前API不直接支持选项批量操作,但前端可以通过组合使用现有API实现选项的批量管理,步骤如下: 1. **加载选项**:使用 `GET /hq/satOption/list?questionId={questionId}` 获取题目当前所有选项 2. **处理更新**: - 对于有ID的选项,调用 `PUT /hq/satOption` 更新内容 - 对于新增的选项,调用 `POST /hq/satOption` 创建 - 对比前后选项ID差异,对不存在的选项调用 `DELETE /hq/satOption/{ids}` 删除 这种实现方式允许在一次操作中完成多个选项的增删改,但需注意以下几点: - 操作非事务性:多个API调用之间没有事务保证,如遇网络问题可能导致数据部分更新 - 性能考虑:选项数量大时,多次API调用可能影响性能 - 字段映射:注意前后端字段命名差异(如 `optionContent` → `optionText`) **示例代码**: ```javascript // 1. 获取当前题目的所有已有选项 const res = await listSatOption({ questionId: questionId }); const existingOptions = res.rows || []; // 2. 处理选项数据 const promises = []; // 新增或修改选项 for (const option of options) { if (!option.optionContent) continue; // 跳过空选项 const params = { id: option.id, questionId: questionId, optionText: option.optionContent, optionValue: option.optionScore || 0, sortOrder: option.optionOrder || 1, status: '0', isDefault: option.isDefault || 'N' }; if (option.id) { // 已有选项,执行更新 promises.push(updateSatOption(params)); } else { // 新选项,执行新增 promises.push(addSatOption(params)); } } // 删除不再存在的选项 const currentOptionIds = options.filter(opt => opt.id).map(opt => opt.id); const optionsToDelete = existingOptions .filter(opt => !currentOptionIds.includes(opt.id)) .map(opt => opt.id); if (optionsToDelete.length > 0) { promises.push(delSatOption(optionsToDelete)); } // 3. 等待所有操作完成 await Promise.all(promises); ``` **建议改进**:后续可考虑在后端添加批量操作API,如 `POST /hq/satOption/batch` 以提供事务保证和性能优化。 ## 数据结构说明 ### 题目选项表(hq_sat_option) | 字段名 | 类型 | 必填 | 描述 | |-------|-----|------|------| | id | bigint(20) | 是 | 选项ID,主键(雪花ID) | | question_id | bigint(20) | 是 | 题目ID,关联题目表 | | option_content | varchar(255) | 是 | 选项内容 | | option_score | int(11) | 否 | 选项分值,用于计算满意度评分 | | option_order | int(11) | 否 | 选项排序,控制前端显示顺序 | | is_default | char(1) | 否 | 是否默认选项(Y是 N否) | | tenant_id | varchar(20) | 是 | 租户编号 | | create_by | bigint(20) | 否 | 创建者 | | create_time | datetime | 否 | 创建时间 | | update_by | bigint(20) | 否 | 更新者 | | update_time | datetime | 否 | 更新时间 | | deleted | char(1) | 否 | 删除标志(0代表存在 1代表删除) | ## 错误码说明 | 错误码 | 描述 | 解决方案 | |-------|-----|---------| | 200 | 操作成功 | 请求正常处理完毕 | | 401 | 认证失败 | 检查用户Token是否正确 | | 403 | 权限不足 | 检查用户是否有操作权限 | | 404 | 资源不存在 | 检查请求的资源是否存在 | | 500 | 系统内部错误 | 联系系统管理员 | ## 变更记录 | 版本号 | 更新日期 | 更新内容 | 更新人 | |-------|---------|---------|-------| | 1.0.0 | 2025-07-01 | 初始版本:创建满意度调查API文档 | Cursor | | 1.0.0 | 2025-07-01 | 更新题目选项表(hq_sat_option)结构,完善选项管理API | Cursor | | 1.0.1 | 2025-07-17 | 优化题目选项获取接口,增强前端题目选项显示功能 | Cursor | | 1.0.2 | 2025-07-17 | 修复选项排序值保存后实时更新功能,优化用户体验 | wuzilong | | 1.0.3 | 2025-07-17 | 添加选项保存防重复提交机制,解决幂等性控制冲突 | wuzilong | | 1.0.4 | 2025-07-18 | 优化问答题文本回答展示功能,提高Vue渲染稳定性 | wuzilong | | 1.0.5 | 2025-07-18 | 增强问卷答题记录接口功能,返回答题详情数据 | wuzilong | ## 附录 ### 选项分值计算说明 选项分值(option_score)用于计算满意度评分,在统计分析时使用,具体规则如下: 1. 单选题:直接使用所选选项的分值作为该题得分 2. 多选题:将所选所有选项的分值相加作为该题得分 3. 问答题:不计分,分值为0 满意度问卷总分计算方式: - 总分(total_score) = 所有题目的得分之和 - 平均分(avg_score) = 总分 ÷ 有分值的题目数量(不包括问答题) ### 问答题文本回答数据结构 **统计分析接口中问答题文本回答数据结构**: 问卷统计分析接口(`/hq/satSurvey/{id}/statistics`)返回的问答题文本回答数据结构如下: ```json "textAnswers": [ { "id": 393, "answer_text": "用户回答内容示例", "text_length": 12, "answer_time": "2025-07-18 10:40:35" } ] ``` **字段说明**: | 字段名 | 类型 | 描述 | |-------|-----|------| | id | Integer | 回答ID,唯一标识 | | answer_text | String | 回答文本内容 | | text_length | Integer | 文本长度(字符数) | | answer_time | String | 回答时间,格式:yyyy-MM-dd HH:mm:ss | **前端使用注意事项**: - 在Vue循环渲染textAnswers数组时,应使用answer.id作为key值,而非索引,确保Vue的虚拟DOM正确更新 - 示例:`
` - 2025-07-18修复:解决了文本回答因key不唯一导致的渲染问题,详见`docs/变更管理/code-adjust-record-2025-07-18-wuzilong.md` #### 2025-07-18 接口返回顺序与问答题答案优化 - `/hq/satSurvey/record/{recordId}` 接口返回参数调整: 1. `answers` 数组顺序严格按照问卷题目顺序返回。 2. `answers.options` 选项数组顺序严格按照题目原始选项顺序返回。 3. 问答题(`questionType=3`)的 `answerValue` 字段取自 `remark` 字段,真实还原用户填写内容。 > 以上调整保证了前端展示与问卷设计一致,提升了数据准确性。 ### 获取问卷的所有答题记录列表 **请求方式**:GET **请求路径**:`/hq/satSurvey/{surveyId}/records` **请求参数**: | 参数名 | 类型 | 必填 | 描述 | |-------|-----|------|------| | surveyId | Long | 是 | 问卷ID,路径参数 | | status | String | 否 | 答题状态(1-已提交,0-未提交) | | userName | String | 否 | 用户姓名,模糊查询 | | dateRange | String[] | 否 | 日期范围 | | pageNum | Integer | 否 | 页码,默认1 | | pageSize | Integer | 否 | 每页条数,默认10 | **请求示例**: ``` GET /hq/satSurvey/1945836064483778562/records?pageNum=1&pageSize=10&surveyId=1945836064483778562 ``` **响应示例**: ```json { "code": 200, "msg": "操作成功", "data": { "total": 5, "rows": [ { "user_id": 2008, "userName": "智控学院郑十", "deptName": "智能控制学院", "id": "1946035501017587714", "survey_id": "1945836064483778562", "submit_time": "2025-07-18 10:33:14", "total_score": "24.00", "create_time": "2025-07-18 10:33:14", "status": "1", "duration": 313, "startTime": "2025-07-18T10:33:14", "endTime": "2025-07-18T10:33:14", "answers": [ { "id": 1945836064483778563, "questionId": 1945836064483778563, "questionTitle": "您对后勤服务总体满意度如何?", "questionType": "1", "score": 4.0, "answerValue": "B", "options": [ { "optionId": 1945836064483778569, "optionValue": "B", "optionText": "满意", "isSelected": true } ] }, { "id": 1945836064483778564, "questionId": 1945836064483778564, "questionTitle": "您认为校园环境有哪些需要改善的地方?", "questionType": "2", "score": 11.0, "answerValue": "A,C,D", "options": [ { "optionId": 1945836064483778570, "optionValue": "A", "optionText": "绿化维护", "isSelected": true }, { "optionId": 1945836064483778572, "optionValue": "C", "optionText": "卫生状况", "isSelected": true }, { "optionId": 1945836064483778573, "optionValue": "D", "optionText": "基础设施", "isSelected": true } ] }, { "id": 1945836064483778565, "questionId": 1945836064483778565, "questionTitle": "您对校园后勤服务的建议", "questionType": "3", "score": 0, "answerValue": "希望增加自习室的开放时间,目前晚上关门太早" } ] }, { "user_id": 4, "userName": "仅本人 密码666666", "deptName": "行政单位", "id": null, "survey_id": null, "submit_time": null, "total_score": 0, "create_time": null, "status": "0", "duration": 0 } ] } } ``` **特殊说明**: 1. **功能强化(2025-07-18)**:接口现在返回已提交记录的答题详情(`answers`字段),无需额外请求 2. 每条记录(已提交状态)包含完整的答题详情数据,包括: - 题目信息:`questionId`、`questionTitle`、`questionType` - 答案内容:`score`、`answerValue` - 选项数据(单选/多选题):`options`数组,包含所有选中的选项 3. 问题类型(`questionType`): - `1`:单选题 - `2`:多选题 - `3`:问答题 4. 未提交的记录(`status="0"`)不包含答题详情 **最佳实践**: - 前端可直接使用返回的`answers`数据渲染答题详情,无需额外请求 - 单选题和多选题显示选项,问答题显示文本内容 ### 相关文件 - 数据库表结构:`docs/设计文档/database/hzdyjs_xg.sql` - 变更记录:`docs/变更管理/code-adjust-record-2025-07-01-cursor.md` - 文本回答展示修复:`docs/变更管理/code-adjust-record-2025-07-18-wuzilong.md` - 答题详情功能优化:`docs/变更管理/code-adjust-record-2025-07-18-wuzilong.md` ## FAQ 常见问题解答 ### 1. 问卷答题记录查询报错 "Unknown column 'u.deleted' in 'where clause'" **问题描述**: 获取问卷答题记录列表时报错:`java.sql.SQLSyntaxErrorException: Unknown column 'u.deleted' in 'where clause'` **原因分析**: 在SQL查询中使用了 `u.deleted = 0` 条件,但实际上 `sys_user` 表中逻辑删除字段名是 `del_flag` 而非 `deleted`。这是系统表与业务表之间的字段命名差异导致的。 **解决方案**: 修改SQL查询,将 `WHERE u.deleted = 0` 改为 `WHERE u.del_flag = '0'`(注意类型也从数字改为了字符串)。 **相关代码**: ```java // 修改前 sql.append("WHERE u.deleted = 0 "); // 修改后 sql.append("WHERE u.del_flag = '0' "); ``` **注意事项**: - 系统表(如sys_user, sys_role等)使用 `del_flag` 作为逻辑删除字段,类型为char(1) - 业务表(如hq_sat_survey等)使用 `deleted` 作为逻辑删除字段,类型为tinyint(1) - 在跨表关联查询时,务必注意这一差异,避免类似错误