本文档详细说明后勤满意度调查系统的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 |
请求方式: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": "查询成功"
}
请求方式: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"
},
// ... 更多选项数据
]
}
前端使用说明:
请求方式: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"
}
}
请求方式: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
}
请求方式: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
}
请求方式:DELETE
请求路径:/hq/satOption/{ids}
请求参数:
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| ids | String | 是 | 选项ID,多个用逗号分隔 |
请求示例:
DELETE /hq/satOption/1937810382736855041,1937810382736855042
响应示例:
{
"code": 200,
"msg": "操作成功",
"data": null
}
虽然当前API不直接支持选项批量操作,但前端可以通过组合使用现有API实现选项的批量管理,步骤如下:
GET /hq/satOption/list?questionId={questionId} 获取题目当前所有选项PUT /hq/satOption 更新内容POST /hq/satOption 创建DELETE /hq/satOption/{ids} 删除这种实现方式允许在一次操作中完成多个选项的增删改,但需注意以下几点:
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 以提供事务保证和性能优化。
| 字段名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| 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)用于计算满意度评分,在统计分析时使用,具体规则如下:
满意度问卷总分计算方式:
统计分析接口中问答题文本回答数据结构:
问卷统计分析接口(/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 |
前端使用注意事项:
<div v-for="(answer, idx) in question.textAnswers.slice(0, 5)" :key="answer.id || idx" class="answer-item">docs/变更管理/code-adjust-record-2025-07-18-wuzilong.md/hq/satSurvey/record/{recordId} 接口返回参数调整:
answers 数组顺序严格按照问卷题目顺序返回。answers.options 选项数组顺序严格按照题目原始选项顺序返回。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
}
]
}
}
特殊说明:
answers字段),无需额外请求questionId、questionTitle、questionTypescore、answerValueoptions数组,包含所有选中的选项questionType):
1:单选题2:多选题3:问答题status="0")不包含答题详情最佳实践:
answers数据渲染答题详情,无需额外请求docs/设计文档/database/hzdyjs_xg.sqldocs/变更管理/code-adjust-record-2025-07-01-cursor.mddocs/变更管理/code-adjust-record-2025-07-18-wuzilong.mddocs/变更管理/code-adjust-record-2025-07-18-wuzilong.md问题描述:
获取问卷答题记录列表时报错: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' ");
注意事项:
del_flag 作为逻辑删除字段,类型为char(1)deleted 作为逻辑删除字段,类型为tinyint(1)