Files
hzdyjs/docs/开发文档/API/后勤满意度调查API.md
T
2025-11-13 23:30:05 +08:00

20 KiB
Raw Blame History

后勤满意度调查API文档

文档信息

  • 版本:1.0.0
  • 最后更新:2025-07-01
  • 作者:Cursor AI
  • 分类:后勤管理模块

概述

本文档详细说明后勤满意度调查系统的API接口设计,包括问卷管理、题目管理、选项管理、答题记录等功能。系统采用前后端分离架构,接口采用RESTful API风格设计,所有接口均需通过Token进行身份验证。

通用响应格式

成功响应

{
  "code": 200,
  "msg": "操作成功",
  "data": {}  // 响应数据,具体结构取决于不同接口
}

分页数据响应

{
  "total": 100,       // 总记录数
  "rows": [],         // 当前页记录列表
  "code": 200,        // 状态码
  "msg": "查询成功"    // 提示信息
}

错误响应

{
  "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

📌 排序策略更新(2025-11-13):题目列表在按指定字段排序时,会额外追加 createTime DESC 作为最后一位兜底排序,确保返回结果顺序稳定。 | /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

响应示例:

{
  "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

响应示例:

{
  "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

响应示例:

{
  "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

请求示例:

{
  "questionId": 1937751489358733313,
  "optionContent": "一般般",
  "optionScore": 3,
  "optionOrder": 3,
  "isDefault": "N"
}

响应示例:

{
  "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否)

请求示例:

{
  "id": 1937810382736855041,
  "questionId": 1937751489358733313,
  "optionContent": "非常满意",
  "optionScore": 5,
  "optionOrder": 1,
  "isDefault": "Y"
}

响应示例:

{
  "code": 200,
  "msg": "操作成功",
  "data": null
}

6. 删除题目选项

请求方式:DELETE

请求路径:/hq/satOption/{ids}

请求参数:

参数名 类型 必填 描述
ids String 是 选项ID,多个用逗号分隔

请求示例:

DELETE /hq/satOption/1937810382736855041,1937810382736855042

响应示例:

{
  "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)

示例代码:

// 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
1.0.6 2025-11-13 调整题目列表排序策略,追加创建时间倒序兜底,避免排序不稳定 Cursor

附录

选项分值计算说明

选项分值(option_score)用于计算满意度评分,在统计分析时使用,具体规则如下:

  1. 单选题:直接使用所选选项的分值作为该题得分
  2. 多选题:将所选所有选项的分值相加作为该题得分
  3. 问答题:不计分,分值为0

满意度问卷总分计算方式:

  • 总分(total_score) = 所有题目的得分之和
  • 平均分(avg_score) = 总分 ÷ 有分值的题目数量(不包括问答题)

问答题文本回答数据结构

统计分析接口中问答题文本回答数据结构:

问卷统计分析接口(/hq/satSurvey/{id}/statistics)返回的问答题文本回答数据结构如下:

"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正确更新
  • 示例:<div v-for="(answer, idx) in question.textAnswers.slice(0, 5)" :key="answer.id || idx" class="answer-item">
  • 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

响应示例:

{
  "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'(注意类型也从数字改为了字符串)。

相关代码:

// 修改前
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)
  • 在跨表关联查询时,务必注意这一差异,避免类似错误