Files
hzdyjs/docs/开发文档/API/API.md.backup-20250701-163220
T
zlwu 249b1dfeb4 feat: 完善项目开发规范和测试文档体系
📝 主要改进:
• Cursor配置模块化:拆分为前端、后端、移动端、文档、API测试5个专门规范文件
• API文档重新组织:按业务模块拆分到docs/开发文档/API/目录下
• 新增维修分类功能完整测试方案和自动化测试脚本
• 后端热启动脚本完善,支持Linux/Windows双平台
• 强化文档同步更新规范,确保代码与文档一致性

🔧 技术改进:
• 优化Cursor AI工作流程和代码生成质量
• 完善API测试规范(AES+RSA双重加密登录方法)
• 提升开发效率和代码质量标准化程度

�� 测试覆盖:
• 维修分类管理功能端到端测试验证通过
• 包含页面加载、CRUD操作、搜索功能等核心测试用例

更新时间:2025-07-01
2025-07-01 17:53:05 +08:00

2452 lines
72 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API 接口文档
## 文档说明
本文档记录项目所有的 API 接口信息,包括请求方式、参数说明、响应格式等。所有接口遵循 RESTful 设计规范。
### 基础信息
- **基础路径**: `/api`
- **响应格式**: JSON
- **字符编码**: UTF-8
- **权限认证**: Sa-Token + JWT
- **版本**: v1.0
### 🔐 请求头规范(强制性)
**所有需要认证的接口必须在请求头中包含 Token,格式如下:**
```json
{
"Content-Type": "application/json;charset=utf-8",
"Authorization": "Bearer {access_token}",
"clientid": "e5cd7e4891bf95d1d19206ce24a7b32e",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"referer": "http://localhost/login?redirect=%252Findex"
}
```
**请求头字段说明**:
- **Authorization**: `Bearer ` + Token 值(⚠️ 注意 Bearer 后面有一个空格)
- **clientid**: 客户端 ID,用于权限验证
- **Content-Type**: 请求内容类型,必须为 application/json;charset=utf-8
- **Accept-Language**: 语言设置,用于国际化
- **referer**: 来源页面,用于安全验证
**Token 格式强制要求**:
- ✅ 正确:`"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."`
- ❌ 错误:`"Authorization": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."`(缺少 Bearer 前缀)
- ❌ 错误:`"Authorization": "Bearer eyJ0eXAi..."`(Bearer 后面没有空格)
### 🔴 统一响应格式(强制性)
**⚠️ 所有 API 接口必须严格遵循以下统一响应格式!**
#### 📊 基础响应格式
**分页列表响应格式**:
```json
{
"total": 7,
"rows": [],
"code": 200,
"msg": "查询成功"
}
```
#### 🎯 详细响应格式说明
**1. 分页列表响应**
```json
{
"total": 100,
"rows": [
{
"id": 1,
"name": "示例数据1",
"createTime": "2025-06-30 12:00:00"
},
{
"id": 2,
"name": "示例数据2",
"createTime": "2025-06-30 12:01:00"
}
],
"code": 200,
"msg": "查询成功"
}
```
**2. 非分页列表响应**
```json
{
"code": 200,
"msg": "查询成功",
"data": [
{
"id": 1,
"name": "示例数据1"
},
{
"id": 2,
"name": "示例数据2"
}
]
}
```
**3. 单条数据响应**
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"name": "示例数据",
"createTime": "2025-06-30 12:00:00"
}
}
```
**4. 操作成功响应(无数据)**
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
**5. 错误响应**
```json
{
"code": 400,
"msg": "参数错误:用户名不能为空",
"data": null
}
```
#### 📋 状态码规范
**成功状态码**:
- `200` - 操作成功
- `201` - 创建成功
**客户端错误(4xx)**:
- `400` - 请求参数错误
- `401` - 未认证/认证失败
- `403` - 无权限访问
- `404` - 资源不存在
- `409` - 数据冲突
**服务端错误(5xx)**:
- `500` - 服务器内部错误
- `502` - 网关错误
- `503` - 服务不可用
#### 🚨 开发强制要求
1. **请求头必须包含 Bearer Token 格式的 Authorization**
2. **分页接口必须直接返回 TableDataInfo 格式(total、rows、code、msg)**
3. **非分页接口必须使用 R 类型包装返回结果**
4. **单条数据接口必须使用 R 类型包装返回结果**
5. **操作类接口必须使用 R 类型包装返回结果**
6. **必须包含 clientid、Content-Type 等必要请求头**
#### 💥 违规检查
如发现接口不符合统一格式,必须立即修改!
---
## 0. 认证与登录 API
### 0.1 用户认证
#### 用户登录
**接口地址**: `POST /auth/login`
**接口描述**: 用户登录认证接口,支持密码登录。请求数据使用 AES+RSA 混合加密传输,确保安全性。
**权限要求**: 无需认证(公开接口)
**安全机制**:
- **数据加密**: 请求参数使用 AES 对称加密
- **密钥保护**: AES 密钥使用 RSA 非对称加密
- **自动解密**: 后端自动解密请求数据和响应数据
**请求头配置**:
```json
{
"Content-Type": "application/json;charset=utf-8",
"clientid": "e5cd7e4891bf95d1d19206ce24a7b32e",
"encrypt-key": "[RSA加密的AES密钥]",
"referer": "http://localhost/login?redirect=%252Findex",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"isToken": "false",
"isEncrypt": "true",
"repeatSubmit": "false"
}
```
**关键请求头说明**:
| 请求头名 | 必填 | 说明 | 示例值 |
| ----------- | ---- | --------------------------------- | -------------------------------------------- |
| clientid | 是 | 客户端 ID,用于身份验证 | "e5cd7e4891bf95d1d19206ce24a7b32e" |
| encrypt-key | 是 | RSA 加密的 AES 密钥,用于数据解密 | "Q8Td0si9R3EYj3pFe50rzn/8wxo69Y9uR6LE..." |
| referer | 是 | 来源页面地址,用于请求来源验证 | "http://localhost/login?redirect=%252Findex" |
**加密流程说明**:
1. 前端生成随机 AES 密钥(32 位字符串)
2. 使用 AES-ECB-PKCS7 模式加密请求数据
3. 使用 RSA 公钥加密 AES 密钥
4. 将加密后的 AES 密钥放入请求头 `encrypt-key`
5. 后端使用 RSA 私钥解密 AES 密钥,再解密请求数据
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
| --------- | ------ | ---- | ------------------------------------ | ---------------------------------- |
| tenantId | string | 是 | 租户 ID,多租户系统标识 | "000000" |
| username | string | 是 | 用户名,长度 2-30 字符 | "admin" |
| password | string | 是 | 用户密码,长度 5-30 字符 | "admin123" |
| clientId | string | 是 | 客户端 ID,用于权限控制 | "e5cd7e4891bf95d1d19206ce24a7b32e" |
| grantType | string | 是 | 授权类型,目前支持 password | "password" |
| code | string | 否 | 验证码(当系统开启验证码时必填) | "1234" |
| uuid | string | 否 | 验证码唯一标识(当需要验证码时必填) | "uuid-string" |
**请求示例**:
```json
{
"tenantId": "000000",
"username": "admin",
"password": "admin123",
"clientId": "e5cd7e4891bf95d1d19206ce24a7b32e",
"grantType": "password"
}
```
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJzeXNfdXNlcjoxIiwicm5TdHIiOiJ5MlhvbHRnd1Y2YjUzRnRMVzF4SFR1anRSUERjeGdmYyIsImNsaWVudGlkIjoiZTVjZDdlNDg5MWJmOTVkMWQxOTIwNmNlMjRhN2IzMmUiLCJ0ZW5hbnRJZCI6IjAwMDAwMCIsInVzZXJJZCI6MSwidXNlck5hbWUiOiJhZG1pbiIsImRlcHRJZCI6MTAzLCJkZXB0TmFtZSI6IuaZuuiDveaOp-WItuWtpumZoiIsImRlcHRDYXRlZ29yeSI6IiIsIm5pY2tOYW1lIjoi55av54uC55qE54uu5a2QTGkifQ.WI6iY3AgwPz7ceI8WDsVy-GaTXZnU-JKUYUTjhQdoKM",
"refresh_token": null,
"expire_in": 604800,
"refresh_expire_in": null,
"client_id": "e5cd7e4891bf95d1d19206ce24a7b32e",
"scope": null,
"openid": null
}
}
```
**实际 Token 解析信息**:
| 字段名 | 值 | 说明 |
| --------- | ----------------- | --------- |
| userId | 1 | 用户 ID |
| userName | admin | 用户名 |
| nickName | 疯狂狮子 Li | 用户昵称 |
| deptId | 103 | 部门 ID |
| deptName | 智胜技术开发团队 | 部门名称 |
| tenantId | 000000 | 租户 ID |
| clientId | e5cd7e4891bf95... | 客户端 ID |
| loginType | login | 登录类型 |
**Token 有效期**: 7 天 (604800 秒)
**响应参数说明**:
| 参数名 | 类型 | 说明 |
| ----------------- | ------ | ------------------------------ |
| access_token | string | 访问令牌,用于 API 调用认证 |
| refresh_token | string | 刷新令牌,用于获取新的访问令牌 |
| expire_in | long | 访问令牌过期时间(秒) |
| refresh_expire_in | long | 刷新令牌过期时间(秒) |
| client_id | string | 客户端 ID |
| scope | string | 权限范围 |
| openid | string | 第三方登录标识(可为空) |
**多种登录方式支持**:
系统采用策略模式,支持多种登录方式:
1. **密码登录** (`grantType: "password"`)
- 使用用户名和密码登录
- 支持验证码验证(可配置开关)
- BCrypt 密码加密存储
2. **邮箱登录** (`grantType: "email"`)
- 使用邮箱和邮箱验证码登录
- 需要先发送邮箱验证码
3. **短信登录** (`grantType: "sms"`)
- 使用手机号和短信验证码登录
- 需要先发送短信验证码
4. **小程序登录** (`grantType: "xcx"`)
- 微信小程序授权登录
- 需要小程序码和应用 ID
**错误码说明**:
| 错误码 | 错误信息 | 说明 | 解决方案 |
| ------ | ---------------------------- | ----------------------- | ---------------------- |
| 400 | 参数校验失败 | 请求参数格式错误 | 检查参数格式和必填字段 |
| 400 | 客户端 id 异常 | clientId 不存在或被禁用 | 检查 clientId 配置 |
| 400 | 授权类型不正确 | grantType 不支持 | 使用支持的授权类型 |
| 400 | 当前系统没有开启注册功能 | 租户未开启注册 | 联系管理员开启注册功能 |
| 401 | 用户不存在 | 用户名不存在 | 检查用户名是否正确 |
| 401 | 用户已被停用 | 用户账号被禁用 | 联系管理员激活账号 |
| 401 | 密码错误 | 用户密码不正确 | 检查密码是否正确 |
| 401 | 验证码已过期 | 验证码超时 | 重新获取验证码 |
| 401 | 验证码错误 | 验证码输入错误 | 检查验证码是否正确 |
| 403 | 登录失败次数过多,账户已锁定 | 密码错误次数超限 | 等待解锁或联系管理员 |
| 500 | 租户不存在 | tenantId 无效 | 检查租户配置 |
**安全特性**:
1. **密码安全**:
- BCrypt 哈希加密存储
- 登录失败次数限制
- 账户自动锁定机制
2. **传输安全**:
- AES+RSA 混合加密
- 防重放攻击保护
- HTTPS 协议传输
3. **会话安全**:
- JWT Token 认证
- Token 自动过期机制
- 刷新 Token 机制
4. **多租户安全**:
- 租户隔离验证
- 客户端权限控制
- 不同设备类型支持
**实际使用示例**:
```javascript
// 完整的登录实现示例(AES+RSA加密)
const axios = require('axios');
const CryptoJS = require('crypto-js');
const NodeRSA = require('node-rsa');
async function loginWithEncryption(username = 'admin', password = 'admin123') {
// 配置信息
const CONFIG = {
url: 'http://localhost/dev-api/auth/login',
clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
tenantId: '000000',
referer: 'http://localhost/login?redirect=%252Findex',
rsaKey: 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
};
// 生成32位随机AES密钥
function generateAESKey() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < 32; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
// AES加密
function aesEncrypt(data, key) {
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
return CryptoJS.AES.encrypt(data, keyUtf8, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
}).toString();
}
// RSA加密AES密钥
function rsaEncrypt(aesKey) {
const pemKey = `-----BEGIN PUBLIC KEY-----\n${CONFIG.rsaKey}\n-----END PUBLIC KEY-----`;
const rsaKey = new NodeRSA(pemKey);
rsaKey.setOptions({ encryptionScheme: 'pkcs1' });
const aesKeyBase64 = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(aesKey));
return rsaKey.encrypt(aesKeyBase64, 'base64');
}
// 生成AES密钥并加密登录数据
const aesKey = generateAESKey();
const loginData = {
tenantId: CONFIG.tenantId,
username: username,
password: password,
clientId: CONFIG.clientId,
grantType: 'password',
};
const encryptedData = aesEncrypt(JSON.stringify(loginData), aesKey);
const encryptedKey = rsaEncrypt(aesKey);
// 设置请求头
const headers = {
'Content-Type': 'application/json;charset=utf-8',
clientid: CONFIG.clientId,
'encrypt-key': encryptedKey,
referer: CONFIG.referer,
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
isToken: 'false',
isEncrypt: 'true',
repeatSubmit: 'false',
};
// 发送登录请求
const response = await axios.post(CONFIG.url, encryptedData, {
headers: headers,
timeout: 10000,
});
if (response.data.code === 200 && response.data.data?.access_token) {
return response.data.data.access_token;
} else {
throw new Error(response.data.msg || 'Login failed');
}
}
// 使用示例
loginWithEncryption()
.then((token) => {
console.log('Login successful, token:', token);
})
.catch((error) => {
console.error('Login failed:', error.message);
});
```
**快速测试脚本**:
项目中提供了完整的测试脚本,位于 `test/` 目录:
- `test/custom-login-script.js` - 完整功能版
- `test/quick-token-getter.js` - 快速获取版
- `test/登录脚本使用说明.md` - 详细使用文档
#### 退出登录
**接口地址**: `POST /auth/logout`
**接口描述**: 用户退出登录,清除服务端会话信息
**权限要求**: 需要登录
**请求参数**: 无需请求体
**请求头**:
```json
{
"Authorization": "Bearer {access_token}"
}
```
**响应示例**:
```json
{
"code": 200,
"msg": "退出成功",
"data": null
}
```
**功能说明**:
- 清除服务端 Token 会话
- 关闭 SSE 连接(如果开启)
- 清理用户登录状态
#### 获取验证码
**接口地址**: `GET /auth/code`
**接口描述**: 获取图形验证码,用于登录时的安全验证
**权限要求**: 无需认证
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"uuid": "d7c2c4c4-4e9d-4a5f-9c1a-2b3c4d5e6f7g",
"img": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABH..."
}
}
```
**响应参数说明**:
| 参数名 | 类型 | 说明 |
| ------ | ------ | ----------------------- |
| uuid | string | 验证码唯一标识 |
| img | string | Base64 编码的验证码图片 |
**使用流程**:
1. 前端调用此接口获取验证码图片和 UUID
2. 用户输入验证码
3. 登录时将验证码和 UUID 一起提交
4. 服务端验证验证码的正确性和有效性
### 0.2 用户信息
#### 获取当前用户信息
**接口地址**: `GET /system/user/getInfo`
**接口描述**: 获取当前登录用户的详细信息,包括角色权限等
**权限要求**: 需要登录
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"user": {
"userId": 1,
"userName": "admin",
"nickName": "疯狂狮子Li",
"email": "admin@example.com",
"phonenumber": "13800138000",
"sex": "1",
"avatar": "/profile/avatar/default.jpg",
"status": "0",
"loginIp": "127.0.0.1",
"loginDate": "2025-01-26 10:30:00",
"tenantId": "000000",
"deptId": 103,
"deptName": "智胜技术开发团队"
},
"roles": ["admin", "common"],
"permissions": ["*:*:*"]
}
}
```
**超级管理员权限说明**:
admin 用户具有系统最高权限 `*:*:*`,可以访问所有功能模块和执行所有操作。
### 0.3 租户管理
#### 获取租户列表
**接口地址**: `GET /auth/tenant/list`
**接口描述**: 获取系统可用的租户列表,用于登录时选择租户
**权限要求**: 无需认证
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"tenantEnabled": true,
"voList": [
{
"tenantId": "000000",
"companyName": "杭州第一技师学院",
"domain": "hzdyjs.edu.cn",
"status": "0"
}
]
}
}
```
**响应参数说明**:
| 参数名 | 类型 | 说明 |
| ------------- | ------- | ------------------ |
| tenantEnabled | boolean | 是否启用多租户功能 |
| voList | array | 租户列表 |
**voList 中每项参数**:
| 参数名 | 类型 | 说明 |
| ----------- | ------ | ------------------- |
| tenantId | string | 租户 ID |
| companyName | string | 租户名称 |
| domain | string | 租户域名 |
| status | string | 状态(0 正常 1 停用) |
---
## 1. 物业报修管理 API
### 1.1 维修分类管理
#### 查询维修分类列表
**接口地址**: `GET /hq/pm/repairCategory/list`
**接口描述**: 分页查询维修分类列表,支持权限过滤
**权限要求**: `pm:category:list`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------------ | ------ | ---- | --------------------- |
| pageNum | int | 否 | 页码,默认 1 |
| pageSize | int | 否 | 页大小,默认 10 |
| categoryName | string | 否 | 分类名称 |
| categoryCode | string | 否 | 分类编码 |
| parentId | long | 否 | 父级分类 ID |
| skillLevel | string | 否 | 技能等级要求 |
| status | string | 否 | 状态(0-停用,1-正常) |
**响应示例**:
```json
{
"code": 200,
"msg": "查询成功",
"data": {
"total": 6,
"rows": [
{
"id": 1,
"categoryName": "电路维修",
"categoryCode": "ELECTRIC",
"categoryDescription": "电路、照明、开关、插座等电气设备维修",
"parentId": 0,
"ancestorIds": "0",
"levelDepth": 1,
"childrenCount": 0,
"requiredSkills": "电工技能、安全操作",
"skillLevel": "2",
"estimatedDuration": 120,
"standardCost": 200.0,
"iconUrl": null,
"colorCode": "#FF6B35",
"sortOrder": 1,
"status": "1",
"usageCount": 0,
"lastUsedTime": null,
"remark": null,
"createTime": "2025-01-23 14:30:00"
}
]
}
}
```
#### 获取维修分类树形结构
**接口地址**: `GET /hq/pm/repairCategory/tree`
**接口描述**: 获取维修分类的树形结构数据
**权限要求**: `pm:category:tree`
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 1,
"categoryName": "电路维修",
"categoryCode": "ELECTRIC",
"categoryDescription": "电路、照明、开关、插座等电气设备维修",
"parentId": 0,
"children": []
}
]
}
```
#### 获取维修分类下拉选项
**接口地址**: `GET /hq/pm/repairCategory/options`
**接口描述**: 获取维修分类的下拉选项数据,用于表单选择器
**权限要求**: `pm:category:list`
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": [
{
"value": 1,
"label": "电路维修"
},
{
"value": 2,
"label": "水管维修"
},
{
"value": 3,
"label": "门窗维修"
}
]
}
```
**数据格式说明**:
- **value**: 分类 ID,用于表单提交
- **label**: 分类名称,用于显示
- **扁平化处理**: 将树形结构扁平化,便于下拉选择
**使用场景**:
- 报修工单创建时选择维修类型
- 表单编辑时的分类选择
- 搜索条件中的分类筛选
#### 获取维修分类详情
**接口地址**: `GET /hq/pm/repairCategory/{id}`
**接口描述**: 根据 ID 获取维修分类详细信息
**权限要求**: `pm:category:query`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------ | ---- | ---- | ------- |
| id | long | 是 | 分类 ID |
#### 新增维修分类
**接口地址**: `POST /hq/pm/repairCategory`
**接口描述**: 创建新的维修分类
**权限要求**: `pm:category:add`
**请求参数**:
```json
{
"categoryName": "测试维修类型",
"categoryCode": "TEST_REPAIR",
"categoryDescription": "这是一个测试的维修类型",
"parentId": 0,
"skillLevel": "1",
"estimatedDuration": 60,
"standardCost": 100.0,
"colorCode": "#1890FF",
"sortOrder": 99,
"status": "1",
"remark": "测试数据"
}
```
### 1.2 报修工单管理
#### 查询报修工单列表
**接口地址**: `GET /hq/pm/repairOrder/list`
**接口描述**: 分页查询报修工单列表,支持多种条件筛选,根据用户角色进行数据权限过滤
**权限要求**: `pm:repairOrder:list`
**数据权限说明**:
- **超级管理员**: 可查看所有报修工单
- **物业维修管理员** (role_key: property_maintenance_admin): 可查看所有报修工单
- **维修人员** (role_key: maintenance_worker): 只能查看自己创建的工单和分配给自己的工单
- **普通用户**: 只能查看自己创建的工单
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
| --------------- | ------ | ---- | --------------- |
| pageNum | int | 否 | 页码,默认 1 |
| pageSize | int | 否 | 页大小,默认 10 |
| orderNo | string | 否 | 工单编号 |
| title | string | 否 | 报修标题 |
| repairLocation | string | 否 | 报修地点 |
| categoryId | long | 否 | 维修类型 ID |
| priorityLevel | int | 否 | 紧急程度 |
| reporterName | string | 否 | 报修人姓名 |
| reporterPhone | string | 否 | 报修人电话 |
| orderStatus | string | 否 | 工单状态 |
| assigneeId | long | 否 | 分派维修员 ID |
| assigneeName | string | 否 | 分派维修员姓名 |
| createTimeRange | array | 否 | 创建时间范围 |
| finishTimeRange | array | 否 | 完成时间范围 |
**响应示例**:
```json
{
"code": 200,
"msg": "查询成功",
"data": {
"total": 1,
"rows": [
{
"id": "1939510596285673473",
"tenantId": "000000",
"orderNo": "RO202506300002",
"title": "详细测试工单 - 会议室投影仪故障",
"description": "会议室投影仪无法正常启动,显示器黑屏,影响会议正常进行。设备型号:EPSON EB-U05,购买时间:2023年6月。",
"repairLocation": "行政楼三楼305会议室",
"categoryId": 1,
"categoryName": "电路维修",
"priorityLevel": 2,
"expectedFinishTime": "2025-07-02 02:25:34",
"reporterId": 1,
"reporterName": "测试用户",
"reporterPhone": "13900139001",
"reporterDept": "信息技术部",
"orderStatus": "1",
"assignTime": null,
"assigneeId": null,
"assigneeName": null,
"assignRemark": null,
"startTime": null,
"finishTime": null,
"totalDuration": null,
"repairResult": null,
"usedMaterials": null,
"followSuggestions": null,
"evaluationScore": null,
"evaluationTime": null,
"attachmentCount": 0,
"progressCount": 1,
"cancelReason": null,
"remark": "设备在保修期内,请联系厂家技术支持",
"createTime": "2025-06-30 02:25:34",
"updateTime": "2025-06-30 02:25:34"
}
]
}
}
```
#### 生成工单编号
**接口地址**: `GET /hq/pm/repairOrder/generateOrderNo`
**接口描述**: 自动生成报修工单编号
**权限要求**: 无需特殊权限
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "RO202506300002",
"data": null
}
```
**说明**: 工单编号格式为 `RO + 年月日 + 序号`,如 `RO202506300002`
#### 获取报修统计数据
**接口地址**: `GET /hq/pm/repairOrder/statistics`
**接口描述**: 获取报修工单的统计数据,用于仪表板展示
**权限要求**: `pm:repairOrder:statistics`
**访问权限说明**:
- **超级管理员**: 可以访问统计数据
- **物业维修管理员** (role_key: property_maintenance_admin): 可以访问统计数据
- **维修人员** (role_key: maintenance_worker): **无权限访问**
- **普通用户**: **无权限访问**
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"totalOrders": 156,
"pendingAssign": 23,
"inProgress": 45,
"completed": 88,
"cancelled": 0,
"avgProcessTime": 2.5,
"avgEvaluationScore": 4.3,
"monthlyNewOrders": 67,
"monthlyCompletedOrders": 52,
"statusDistribution": [
{
"status": "待分派",
"count": 23,
"percentage": 14.7
},
{
"status": "进行中",
"count": 45,
"percentage": 28.8
},
{
"status": "已完成",
"count": 88,
"percentage": 56.4
}
],
"categoryDistribution": [
{
"categoryName": "电路维修",
"count": 45,
"percentage": 28.8
},
{
"categoryName": "水管维修",
"count": 38,
"percentage": 24.4
}
],
"priorityDistribution": [
{
"priorityLevel": 1,
"count": 12,
"percentage": 7.7
},
{
"priorityLevel": 2,
"count": 34,
"percentage": 21.8
}
]
}
}
```
**统计字段说明**:
| 字段名 | 类型 | 说明 |
| ---------------------- | ----- | ---------------------- |
| totalOrders | int | 总工单数量 |
| pendingAssign | int | 待分派工单数 |
| inProgress | int | 进行中工单数 |
| completed | int | 已完成工单数 |
| cancelled | int | 已取消工单数 |
| avgProcessTime | float | 平均处理时长(小时) |
| avgEvaluationScore | float | 平均评价得分(1-5 分) |
| monthlyNewOrders | int | 本月新增工单数 |
| monthlyCompletedOrders | int | 本月完成工单数 |
| statusDistribution | array | 各状态分布统计 |
| categoryDistribution | array | 各类型分布统计 |
| priorityDistribution | array | 紧急程度分布统计 |
**使用场景**:
- 报修管理仪表板数据展示
- 运营分析和报表生成
- 工单处理效率监控
- 维修类型热度分析
**注意事项**:
- 当前版本返回模拟数据,用于前端开发测试
- 生产环境需要实现真实的统计计算逻辑
- 统计数据会根据用户权限进行过滤
#### 获取报修工单详情
**接口地址**: `GET /hq/pm/repairOrder/{id}`
**接口描述**: 根据 ID 获取报修工单详细信息,用于前端详情弹窗展示完整工单信息
**权限要求**: `pm:repairOrder:query`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------ | ---- | ---- | ------- |
| id | long | 是 | 工单 ID |
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1001,
"orderNo": "RO20250126001",
"title": "宿舍A304水龙头漏水",
"description": "水龙头一直滴水,影响正常使用,需要及时维修",
"repairLocation": "学生宿舍A栋304室",
"categoryId": 102,
"categoryName": "水管维修",
"priorityLevel": 2,
"expectedFinishTime": "2025-01-27 18:00:00",
"reporterId": 10001,
"reporterName": "张小明",
"reporterPhone": "13800138001",
"reporterDept": "计算机学院",
"orderStatus": "1",
"assignTime": null,
"assigneeId": null,
"assigneeName": null,
"assignRemark": null,
"startTime": null,
"finishTime": null,
"totalDuration": null,
"repairResult": null,
"usedMaterials": null,
"followSuggestions": null,
"evaluationScore": null,
"evaluationTime": null,
"attachmentCount": 0,
"progressCount": 1,
"cancelReason": null,
"remark": null,
"createTime": "2025-01-26 10:30:00",
"updateTime": "2025-01-26 10:30:00"
}
}
```
**前端使用说明**:
- 该接口用于工单详情弹窗展示,返回 RepairOrderVO 完整对象
- 前端根据字段是否有值决定显示哪些信息区域:
- 基本信息:工单编号、状态、紧急程度、创建时间、标题、问题描述
- 报修信息:报修人、联系电话、部门、期望完成时间、报修地点
- 维修信息:维修员、时间节点、处理时长、进度记录、分派备注
- 维修结果:维修结果、使用材料、后续建议
- 评价信息:综合评分、评价时间
- 其他信息:附件数量、维修类型、取消原因、备注信息
- 支持响应式布局,在移动端和桌面端都有良好展示
#### 新增报修工单
**接口地址**: `POST /hq/pm/repairOrder`
**接口描述**: 创建新的报修工单
**权限要求**: `pm:repairOrder:add`
**请求参数**:
```json
{
"orderNo": "RO202506300002",
"title": "详细测试工单 - 会议室投影仪故障",
"description": "会议室投影仪无法正常启动,显示器黑屏,影响会议正常进行。设备型号:EPSON EB-U05,购买时间:2023年6月。",
"repairLocation": "行政楼三楼305会议室",
"categoryId": 1,
"priorityLevel": 2,
"expectedFinishTime": "2025-07-02 02:25:34",
"reporterName": "测试用户",
"reporterPhone": "13900139001",
"reporterDept": "信息技术部",
"remark": "设备在保修期内,请联系厂家技术支持"
}
```
**字段说明**:
| 字段名 | 类型 | 必填 | 说明 |
| ------------------ | ------ | ---- | -------------------------------------------------------- |
| orderNo | string | 是 | 工单编号 |
| title | string | 是 | 报修标题 |
| description | string | 是 | 问题描述 |
| repairLocation | string | 是 | 报修地点 |
| categoryId | long | 是 | 维修类型 ID |
| priorityLevel | int | 是 | 紧急程度:1-非常紧急,2-紧急,3-一般,4-不紧急,5-可延后 |
| expectedFinishTime | string | 否 | 期望完成时间 |
| reporterName | string | 是 | 报修人姓名 |
| reporterPhone | string | 是 | 报修人电话 |
| reporterDept | string | 否 | 报修人部门 |
| remark | string | 否 | 备注信息 |
### 1.3 工单操作管理
#### 分派工单
**接口地址**: `PUT /hq/pm/repairOrder/{orderId}/assign`
**接口描述**: 将工单分派给维修人员
**权限要求**: `pm:repairOrder:assign`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------- | ---- | ---- | ------- |
| orderId | long | 是 | 工单 ID |
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------------ | ------ | ---- | ------------ |
| assigneeId | long | 是 | 维修员 ID |
| assignRemark | string | 否 | 分派备注说明 |
#### 开始维修
**接口地址**: `PUT /hq/pm/repairOrder/{orderId}/start`
**接口描述**: 维修人员开始处理工单
**权限要求**: `pm:repairOrder:start`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------- | ---- | ---- | ------- |
| orderId | long | 是 | 工单 ID |
#### 完成维修
**接口地址**: `PUT /hq/pm/repairOrder/{orderId}/finish`
**接口描述**: 标记工单为已完成
**权限要求**: `pm:repairOrder:finish`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------- | ---- | ---- | ------- |
| orderId | long | 是 | 工单 ID |
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------------- | ------ | ---- | ------------ |
| repairResult | string | 否 | 维修结果描述 |
| usedMaterials | string | 否 | 使用材料清单 |
#### 取消工单
**接口地址**: `PUT /hq/pm/repairOrder/{orderId}/cancel`
**接口描述**: 取消工单
**权限要求**: `pm:repairOrder:cancel`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------- | ---- | ---- | ------- |
| orderId | long | 是 | 工单 ID |
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------------ | ------ | ---- | -------- |
| cancelReason | string | 否 | 取消原因 |
#### 获取工单进度列表
**接口地址**: `GET /hq/pm/repairOrder/{orderId}/progress`
**接口描述**: 获取指定工单的进度记录列表,按时间顺序展示工单处理进度
**权限要求**: `pm:repairOrder:progress`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| orderId | Long | Y | 工单 ID | 1 |
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 1,
"orderId": 1,
"progressType": "1",
"progressStatus": "工单已分派",
"progressDetail": "工单已分派给维修人员zwc007",
"operatorId": 1,
"operatorName": "管理员",
"createTime": "2025-06-30 09:00:00"
},
{
"id": 2,
"orderId": 1,
"progressType": "2",
"progressStatus": "接收确认",
"progressDetail": "维修人员已接收工单,准备前往现场",
"operatorId": 7,
"operatorName": "zwc007",
"createTime": "2025-06-30 09:30:00"
}
]
}
```
**错误码**:
| 错误码 | 说明 | 解决方案 |
|--------|------|----------|
| 403 | 无权限 | 确认用户有查看进度权限 |
| 404 | 工单不存在 | 检查工单 ID 是否正确 |
#### 更新维修进度
**接口地址**: `POST /hq/pm/repairOrder/progress`
**接口描述**: 维修人员更新工单维修进度,保存真实进度记录到数据库,首次更新时自动将工单状态从"已分派"改为"进行中"
**权限要求**: `pm:repairOrder:updateProgress`
**业务逻辑**:
- 保存进度记录到`pm_repair_progress`表
- 如果是首次更新进度(工单状态为"已分派"),自动将状态改为"进行中"
- 自动设置`start_time`开始处理时间
- 更新工单的`progress_count`统计字段
- 完整的权限验证和数据权限控制
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| orderId | Long | Y | 工单 ID | 1 |
| progressType | String | Y | 进度类型:1-工单分派,2-接收确认,3-到达现场,4-开始维修,5-进度更新,6-完成维修,7-用户评价,8-工单归档,9-其他 | "5" |
| progressStatus | String | Y | 进度状态描述 | "现场检查中" |
| progressDetail | String | Y | 进度详细说明 | "已到达现场,正在检查设备状态" |
| locationInfo | String | N | 位置信息 | "教学楼 A 座 201" |
| estimatedDuration | Integer | N | 预计用时(分钟) | 120 |
| nextStep | String | N | 下一步计划 | "安装新部件" |
| issuesEncountered | String | N | 遇到的问题 | "部分线路老化需要更换" |
| materialsNeeded | String | N | 所需材料 | "电线、插座" |
| remark | String | N | 备注信息 | "进度顺利" |
**状态自动流转**:
- 当工单状态为"已分派"(2)时,首次更新进度将自动触发状态流转为"进行中"(3)
- 同时设置工单的`start_time`为当前时间
- 后续进度更新不再触发状态变化
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
**错误码**:
| 错误码 | 说明 | 解决方案 |
|--------|------|----------|
| 400001 | 工单不存在 | 检查工单 ID |
| 400002 | 无权限操作该工单 | 检查用户权限 |
| 400003 | 只能为已分派或进行中状态的工单更新进度 | 工单状态必须为"已分派"(2)或"进行中"(3) |
| 500001 | 进度记录保存失败 | 联系系统管理员 |
#### 完成工单(详细版)
**接口地址**: `PUT /hq/pm/repairOrder/{orderId}/complete`
**接口描述**: 完成工单并填写维修结果,将工单状态从"进行中"改为"已完成",同时记录完成时间和处理时长
**权限要求**: `pm:repairOrder:complete`
**业务逻辑**:
- 验证工单状态必须为"进行中"(3)
- 自动计算处理时长(基于开始时间和完成时间)
- 更新工单状态为"已完成"(4)
- 自动添加完成进度记录
- 完成的权限验证和数据权限控制
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| orderId | Long | Y | 工单 ID(路径参数) | 1 |
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| repairResult | String | Y | 维修结果描述 | "电路故障已修复,更换了老化线路和插座" |
| usedMaterials | String | N | 使用材料清单 | "电线 2 米、插座 1 个、绝缘胶带 1 卷" |
| followSuggestions | String | N | 后续建议 | "建议半年后进行一次电路安全检查" |
**状态流转**:
- 只有"进行中"(3)状态的工单才能完成
- 完成后状态自动变为"已完成"(4)
- 自动设置`finish_time`完成时间
- 自动计算`total_duration`处理时长
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": null
}
```
**错误码**:
| 错误码 | 说明 | 解决方案 |
|--------|------|----------|
| 400001 | 工单不存在 | 检查工单 ID |
| 400002 | 无权限操作该工单 | 检查用户权限 |
| 400003 | 只能完成进行中状态的工单 | 工单状态必须为"进行中"(3) |
| 500001 | 工单完成状态更新失败 | 联系系统管理员 |
#### 创建服务评价
**接口地址**: `POST /hq/pm/repairOrder/evaluation`
**接口描述**: 对已完成的工单进行服务评价,支持多维度评分
**权限要求**: `pm:repairOrder:evaluate`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| orderId | Long | Y | 工单 ID | 1 |
| evaluatedWorkerId | Long | N | 被评价的维修员 ID | 7 |
| overallScore | Integer | Y | 综合评分(1-5) | 5 |
| qualityScore | Integer | N | 质量评分(1-5) | 5 |
| attitudeScore | Integer | N | 态度评分(1-5) | 4 |
| responseSpeedScore | Integer | N | 响应速度评分(1-5) | 5 |
| communicationScore | Integer | N | 沟通评分(1-5) | 4 |
| evaluationContent | String | Y | 评价内容 | "维修及时,服务态度好" |
| satisfactionLevel | String | N | 满意度等级 | "1" |
| isAnonymous | Integer | N | 是否匿名(0 否 1 是) | 0 |
| isPublic | Integer | N | 是否公开(0 否 1 是) | 1 |
| positiveTags | String | N | 正面标签 | "专业,及时" |
| negativeTags | String | N | 负面标签 | "" |
| improvementSuggestions | String | N | 改进建议 | "无" |
| remark | String | N | 备注 | "" |
**响应示例**:
```json
{
"code": 200,
"msg": "服务评价提交成功",
"data": null
}
```
**错误码**:
| 错误码 | 说明 | 解决方案 |
|--------|------|----------|
| 400 | 参数错误 | 检查评分范围和必填参数 |
| 403 | 无权限 | 确认用户有评价权限 |
| 409 | 重复评价 | 该工单已有评价记录 |
#### 获取服务评价
**接口地址**: `GET /hq/pm/repairOrder/{orderId}/evaluation`
**接口描述**: 获取指定工单的服务评价信息
**权限要求**: `pm:repairOrder:query`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| orderId | Long | Y | 工单 ID | 1 |
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"orderId": 1,
"overallScore": 5,
"qualityScore": 5,
"attitudeScore": 4,
"responseSpeedScore": 5,
"communicationScore": 4,
"evaluationContent": "维修及时,服务态度好,质量可靠。",
"satisfactionLevel": "1",
"createTime": "2025-06-30 16:00:00"
}
}
```
**错误码**:
| 错误码 | 说明 | 解决方案 |
|--------|------|----------|
| 403 | 无权限 | 确认用户有查询权限 |
| 404 | 评价不存在 | 该工单暂无评价记录 |
#### 获取维修人员选项
**接口地址**: `GET /hq/pm/repairOrder/maintenance-workers`
**接口描述**: 获取拥有"维修人员"角色的用户选项列表,用于分派工单时选择维修员
**权限要求**: `pm:repairOrder:assign`
**请求参数**: 无
**响应示例**:
```json
{
"code": 200,
"msg": "操作成功",
"data": [
{
"value": 7,
"label": "zwc007 - 智胜技术开发团队",
"userName": "zwc007",
"deptName": "智胜技术开发团队"
}
]
}
```
**错误码**:
| 错误码 | 说明 | 解决方案 |
|--------|------|----------|
| 403 | 无权限 | 确认用户有分派权限 |
### 1.4 工单状态说明
| 状态值 | 状态名称 | 说明 |
| ------ | -------- | -------------------- |
| 1 | 待分派 | 工单已创建,等待分派 |
| 2 | 已分派 | 已分派给维修人员 |
| 3 | 进行中 | 维修人员正在处理 |
| 4 | 已完成 | 维修工作已完成 |
| 5 | 已评价 | 用户已对服务进行评价 |
| 6 | 已归档 | 工单已归档 |
| 9 | 已取消 | 工单已被取消 |
### 1.5 紧急程度说明
| 级别值 | 级别名称 | 说明 |
| ------ | -------- | ------------------------ |
| 1 | 非常紧急 | 影响正常运营,需立即处理 |
| 2 | 紧急 | 影响使用,需尽快处理 |
| 3 | 一般 | 正常维修需求 |
| 4 | 不紧急 | 可延后处理 |
| 5 | 可延后 | 条件允许时处理 |
---
## 2. 问卷管理 API
### 1.1 问卷基础管理
#### 查询问卷列表
**接口地址**: `GET /hq/satSurvey/list`
**接口描述**: 分页查询问卷列表,支持权限过滤
**权限要求**: `hq:satSurvey:list`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ----------- | ------ | ---- | --------------------- |
| pageNum | int | 否 | 页码,默认 1 |
| pageSize | int | 否 | 页大小,默认 10 |
| surveyCode | string | 否 | 问卷编号 |
| surveyTitle | string | 否 | 问卷标题 |
| targetGroup | string | 否 | 目标群体 |
| status | string | 否 | 问卷状态 |
| isPublished | int | 否 | 是否发布(1 是 0 否) |
**响应示例**:
```json
{
"code": 200,
"msg": "查询成功",
"data": {
"total": 100,
"rows": [
{
"id": 1,
"surveyCode": "SURVEY001",
"surveyTitle": "满意度调查",
"surveyDesc": "学生满意度调查",
"targetGroup": "1",
"startTime": "2025-01-01 00:00:00",
"endTime": "2025-01-31 23:59:59",
"isAnonymous": 1,
"isPublished": 1,
"status": "1"
}
]
}
}
```
#### 获取问卷详情
**接口地址**: `GET /hq/satSurvey/{id}`
**接口描述**: 根据 ID 获取问卷详细信息
**权限要求**: `hq:satSurvey:query`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------ | ---- | ---- | ------- |
| id | long | 是 | 问卷 ID |
**响应示例**:
```json
{
"code": 200,
"msg": "查询成功",
"data": {
"id": 1,
"surveyCode": "SURVEY001",
"surveyTitle": "满意度调查",
"surveyDesc": "学生满意度调查",
"surveyInstruction": "请认真填写",
"targetGroup": "1",
"startTime": "2025-01-01 00:00:00",
"endTime": "2025-01-31 23:59:59",
"maxSubmitTimes": 1,
"isAnonymous": 1,
"isPublished": 1,
"status": "1",
"remark": ""
}
}
```
#### 新增问卷
**接口地址**: `POST /hq/satSurvey`
**接口描述**: 创建新的问卷
**权限要求**: `hq:satSurvey:add`
**请求参数**:
```json
{
"surveyCode": "SURVEY001",
"surveyTitle": "满意度调查",
"surveyDesc": "学生满意度调查",
"surveyInstruction": "请认真填写",
"targetGroup": "1",
"startTime": "2025-01-01 00:00:00",
"endTime": "2025-01-31 23:59:59",
"maxSubmitTimes": 1,
"isAnonymous": 1,
"remark": ""
}
```
#### 修改问卷
**接口地址**: `PUT /hq/satSurvey`
**接口描述**: 修改问卷信息
**权限要求**: `hq:satSurvey:edit`
#### 删除问卷
**接口地址**: `DELETE /hq/satSurvey/{ids}`
**接口描述**: 批量删除问卷
**权限要求**: `hq:satSurvey:remove`
#### 发布问卷
**接口地址**: `PUT /hq/satSurvey/{id}/publish`
**接口描述**: 发布问卷,将状态改为进行中
**权限要求**: `hq:satSurvey:publish`
#### 撤销发布问卷
**接口地址**: `PUT /hq/satSurvey/{id}/unpublish`
**接口描述**: 撤销发布问卷,将状态改为草稿
**权限要求**: `hq:satSurvey:unpublish`
### 1.2 问卷题目管理
#### 获取问卷题目列表
**接口地址**: `GET /hq/satSurvey/{surveyId}/questions`
**接口描述**: 获取问卷关联的题目列表,包含题目详细信息和选项
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
| -------- | ---- | ---- | ------- |
| surveyId | long | 是 | 问卷 ID |
**响应示例**:
```json
{
"code": 200,
"msg": "查询成功",
"data": [
{
"id": 1,
"surveyId": 1,
"questionId": 1,
"sortOrder": 1,
"weight": 1,
"isRequired": 1,
"questionTitle": "您对服务质量的评价?",
"questionType": "1",
"questionDesc": "请选择一个选项",
"options": [
{
"id": 1,
"questionId": 1,
"optionValue": "A",
"optionText": "非常满意",
"optionScore": 5,
"sortOrder": 1
}
]
}
]
}
```
#### 更新问卷题目关联
**接口地址**: `PUT /hq/satSurvey/{surveyId}/questions`
**接口描述**: 批量更新问卷题目关联关系
**权限要求**: `hq:satSurvey:question`
---
## 2. 答题 API(新增)
### 2.1 答题提交
#### 提交问卷答案
**接口地址**: `POST /hq/satSurvey/answer/submit`
**接口描述**: 提交问卷答案,正式提交后不可修改
**权限要求**: 需要登录
**请求参数**:
```json
{
"surveyId": 1,
"startTime": "2025-01-26T14:00:00.000Z",
"endTime": "2025-01-26T14:15:00.000Z",
"isAnonymous": 1,
"answers": [
{
"questionId": 1,
"questionType": "1",
"answerValue": "A",
"options": [
{
"optionId": 1,
"optionValue": "A",
"optionText": "非常满意",
"isSelected": true
}
]
},
{
"questionId": 2,
"questionType": "2",
"answerValue": "A,C,D",
"options": [
{
"optionId": 2,
"optionValue": "A",
"optionText": "选项A",
"isSelected": true
},
{
"optionId": 4,
"optionValue": "C",
"optionText": "选项C",
"isSelected": true
}
]
},
{
"questionId": 3,
"questionType": "3",
"answerValue": "这是我的意见和建议内容",
"options": []
}
],
"remark": ""
}
```
**参数说明**:
| 参数名 | 类型 | 必填 | 说明 |
| ----------- | ------ | ---- | ----------------------------- |
| surveyId | long | 是 | 问卷 ID |
| startTime | string | 是 | 答题开始时间(ISO 8601 格式) |
| endTime | string | 是 | 答题结束时间(ISO 8601 格式) |
| isAnonymous | int | 是 | 是否匿名(1 是 0 否) |
| answers | array | 是 | 答案列表 |
| remark | string | 否 | 备注 |
**answers 中每项参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ------------ | ------ | ---- | ------------------------------------------------ |
| questionId | long | 是 | 题目 ID |
| questionType | string | 是 | 题目类型(1 单选题 2 多选题 3 问答题) |
| answerValue | string | 是 | 答案值(单选存选项值,多选逗号分隔,问答存文本) |
| options | array | 否 | 选项详情(单选题和多选题需要) |
**响应示例**:
```json
{
"code": 200,
"msg": "答题提交成功",
"data": {
"id": 1001,
"surveyId": 1,
"userId": 1,
"userType": "student",
"submitTime": "2025-01-26T14:15:00.000Z",
"totalScore": 0,
"avgScore": 0,
"remark": ""
}
}
```
**多次提交功能说明**(v1.0.3 新增,v1.0.4 优化):
系统支持问卷多次提交功能,提交次数限制由问卷的 `max_submit_times` 字段控制:
1. **提交次数规则**:
- `max_submit_times = 0`: 不限制提交次数,用户可以无限次提交
- `max_submit_times = 1`: 最多提交 1 次(默认行为,向后兼容)
- `max_submit_times = N`: 最多提交 N 次
2. **数据存储策略**(v1.0.4 重要更新):
- **软删除历史数据**: 用户提交新答案时,系统会自动将之前的答题记录标记为已删除(deleted=1)
- **保持数据完整性**: 历史数据完整保留用于审计,但业务查询只返回最新有效数据
- **关联删除**: 通过 record_id 关联,确保 hq_sat_record、hq_sat_detail、hq_sat_answer 三表数据状态同步
3. **数据状态管理**:
- `deleted = 0`: 有效数据,正常业务查询可见
- `deleted = 1`: 历史数据,仅审计查询可见
- 用户每次只能看到最新的有效答题记录
4. **处理流程**:
```
用户提交新答案 → 标记历史数据为删除 → 保存新答题数据 → 返回最新记录
```
5. **错误处理**:
```json
{
"code": 500,
"msg": "您已达到该问卷的最大提交次数限制(3次)",
"data": null
}
```
6. **使用场景**:
- **单次提交**: 正式考试、重要调研等
- **多次提交**: 练习测试、阶段性评估等
- **无限提交**: 学习练习、反馈收集等
7. **业务优势**:
- ✅ **用户体验**: 查看答题记录始终是最新版本
- ✅ **数据审计**: 历史记录完整保留,支持数据回溯
- ✅ **查询性能**: 业务查询只涉及有效数据,提高效率
- ✅ **数据一致性**: 避免多版本数据混乱问题
**数据保存说明**:
此接口在保存答题记录时,会同时保存详细的答案信息到两个表:
1. **hq_sat_detail 表**:保存答题概要信息
- 记录答题记录 ID、题目 ID、答案分数等基础信息
- remark 字段存储答案内容的摘要信息
2. **hq_sat_answer 表**:保存具体的答案详情(v1.0.1 新增,v1.0.2 修复)
- **单选题**:为选中的选项创建一条记录,包含选项 ID、选项标识(A/B/C/D)、选项文本、真实分值
- **多选题**:为每个选中的选项创建一条记录,使用选项标识区分不同选项
- **问答题**:创建一条记录,选项相关字段为 NULL,答案值存储用户输入的文本内容
**hq_sat_answer 表数据示例**(v1.0.2 修复后):
```sql
-- 单选题答案记录(用户选择"非常满意"选项)
INSERT INTO hq_sat_answer VALUES (
1, '000000', 1001, NOW(), 1, NOW(), 0, -- 基础字段
123, 1, '1', 1, 'A', '非常满意', 'A', 5, NULL -- 答案字段
);
-- 多选题答案记录(用户选中A、C两个选项)
INSERT INTO hq_sat_answer VALUES
(2, '000000', 1001, NOW(), 1, NOW(), 0, 123, 2, '2', 1, 'A', '非常满意', 'A,C', 5, NULL),
(3, '000000', 1001, NOW(), 1, NOW(), 0, 123, 2, '2', 3, 'C', '一般', 'A,C', 1, NULL);
-- 问答题答案记录
INSERT INTO hq_sat_answer VALUES (
4, '000000', 1001, NOW(), 1, NOW(), 0, -- 基础字段
123, 3, '3', NULL, NULL, NULL, '这是我的意见和建议', 0, NULL -- 答案字段
);
```
**字段说明**(修复后):
- `option_value`: 存储选项标识(A、B、C、D),便于统计分析
- `answer_score`: 存储真实分值(从 hq_sat_option 表查询获得)
- `answer_value`: 单选题存选项标识,多选题存逗号分隔的多个标识,问答题存文本
#### 保存问卷答案草稿
**接口地址**: `POST /hq/satSurvey/answer/draft`
**接口描述**: 保存问卷答案草稿,可多次保存和修改
**权限要求**: 需要登录
**请求参数**: 同提交问卷答案
**响应示例**: 同提交问卷答案
### 2.2 答题状态查询
#### 检查用户是否已答过问卷
**接口地址**: `GET /hq/satSurvey/{surveyId}/answered`
**接口描述**: 检查当前用户是否已经答过指定问卷(v1.0.3 更新:支持多次提交检查)
**权限要求**: 需要登录
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
| -------- | ---- | ---- | ------- |
| surveyId | long | 是 | 问卷 ID |
**响应示例**:
```json
{
"code": 200,
"msg": "查询成功",
"data": true
}
```
**响应说明**(v1.0.3 更新):
- `data` 为 `true` 表示用户已答过该问卷(至少提交过 1 次)
- `data` 为 `false` 表示用户未答过该问卷
**多次提交支持**:
此接口仅返回用户是否有答题记录,不考虑提交次数限制。要检查是否还能继续提交,请参考以下逻辑:
- 如果 `max_submit_times = 0`:始终可以提交
- 如果 `max_submit_times > 0`:需要检查用户提交次数是否小于此限制
- 具体的提交限制检查由"提交问卷答案"接口在提交时进行
#### 获取用户问卷答题记录
**接口地址**: `GET /hq/satSurvey/{surveyId}/answer`
**接口描述**: 获取当前用户对指定问卷的答题记录(v1.0.5 重大更新:优化数据源,支持前端答案回显)
**权限要求**: 需要登录
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
| -------- | ---- | ---- | ------- |
| surveyId | long | 是 | 问卷 ID |
**响应示例**:
```json
{
"code": 200,
"msg": "查询成功",
"data": {
"id": 1001,
"surveyId": 1,
"userId": 1,
"userType": "student",
"submitTime": "2025-01-26T14:15:00.000Z",
"totalScore": 0,
"avgScore": 0,
"remark": "ANSWERS_DATA:[{\"questionId\":1,\"questionType\":\"1\",\"answerValue\":\"A\"},{\"questionId\":2,\"questionType\":\"2\",\"answerValue\":\"A,C\"}]"
}
}
```
**返回数据说明**(v1.0.5 重大更新):
- 此接口只返回用户最新的有效答题记录(deleted=0)
- 如果用户进行了多次提交,历史记录已被软删除,查询不到
- 返回的是用户最后一次提交的完整答题信息
- 历史记录完整保留在数据库中,仅用于审计目的
**v1.0.5 新增功能 - 答案回显数据**:
为支持前端答题界面的历史答案回显功能,此接口现在会在 `remark` 字段中包含格式化的答案数据:
1. **数据格式**: `ANSWERS_DATA:[答案JSON数组]`
2. **数据来源**: 直接从 `hq_sat_answer` 表查询,比 v1.0.4 之前的 `remark` 解析更准确
3. **答案结构**:
```json
[
{
"questionId": 1,
"questionType": "1",
"answerValue": "A"
},
{
"questionId": 2,
"questionType": "2",
"answerValue": "A,C"
},
{
"questionId": 3,
"questionType": "3",
"answerValue": "用户输入的文本内容"
}
]
```
**前端答案回显逻辑**:
- **单选题** (`questionType = "1"`): `answerValue` 直接设置到单选控件(如 "A")
- **多选题** (`questionType = "2"`): `answerValue` 按逗号分割成数组(如 ["A", "C"])
- **问答题** (`questionType = "3"`): `answerValue` 直接设置到文本框
**使用场景**:
- ✅ **首次答题**: 返回 404,前端正常显示空表单
- ✅ **重新答题**: 返回历史答案,前端自动回显到表单
- ✅ **多次修改**: 基于上次答案继续编辑,提升用户体验
---
## 3. 题目管理 API
### 3.1 题目基础管理
#### 查询题目列表
**接口地址**: `GET /hq/satQuestion/list`
**权限要求**: `hq:satQuestion:list`
#### 获取题目选项
**接口地址**: `GET /hq/satQuestion/{questionId}/options`
**接口描述**: 获取指定题目的所有选项
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
| ---------- | ---- | ---- | ------- |
| questionId | long | 是 | 题目 ID |
**响应示例**:
```json
{
"code": 200,
"msg": "查询成功",
"data": [
{
"id": 1,
"questionId": 1,
"optionValue": "A",
"optionText": "非常满意",
"optionScore": 5,
"sortOrder": 1
},
{
"id": 2,
"questionId": 1,
"optionValue": "B",
"optionText": "满意",
"optionScore": 4,
"sortOrder": 2
}
]
}
```
---
## 4. 答题记录管理 API
### 4.1 答题记录查询
#### 查询答题记录列表
**接口地址**: `GET /hq/satRecord/list`
**权限要求**: `hq:satRecord:list`
#### 获取答题记录详情
**接口地址**: `GET /hq/satRecord/{id}`
**权限要求**: `hq:satRecord:query`
---
## 5. 错误码说明
### 业务错误码
| 错误码 | 说明 | 解决方案 |
| ------ | ------------------ | ------------------------ |
| 40001 | 问卷不存在 | 检查问卷 ID 是否正确 |
| 40002 | 问卷未在进行中状态 | 等待问卷开始或联系管理员 |
| 40003 | 您已经答过这份问卷 | 不能重复答题 |
| 40004 | 答题记录保存失败 | 重新提交或联系技术支持 |
| 40005 | 题目类型不支持 | 检查题目类型是否正确 |
---
## 6. 数据字典
### 6.1 问卷状态
| 值 | 说明 | 备注 |
| --- | ------ | -------------- |
| 0 | 草稿 | 未发布状态 |
| 1 | 进行中 | 已发布,可答题 |
| 2 | 已结束 | 超过结束时间 |
### 6.2 目标群体
| 值 | 说明 | 备注 |
| --- | ------ | ---------- |
| 1 | 学生 | 在校学生 |
| 2 | 教职工 | 教师和职工 |
| 3 | 全体 | 所有人员 |
### 6.4 用户类型
| 值 | 说明 | 备注 |
| --- | ---------- | --------------------------------------------------------- |
| 1 | 学生 | student、学生、stu 等角色映射为此值 |
| 2 | 教师 | teacher、老师、教师、教职工、tea 等角色映射为此值 |
| 3 | 超级管理员 | admin、管理员、超级管理员、administrator 等角色映射为此值 |
### 6.3 题目类型
| 值 | 说明 | 备注 |
| --- | ------ | ---------------- |
| 1 | 单选题 | 只能选择一个选项 |
| 2 | 多选题 | 可以选择多个选项 |
| 3 | 问答题 | 文本输入 |
---
## 7. 版本更新记录
### v1.0.5 (2025-01-26)
- 🚀 **重大更新**:实现答题界面历史答案回显功能,提升多次答题用户体验
- 🔧 **数据源优化**:`GET /hq/satSurvey/{surveyId}/answer` 接口改用 hq_sat_answer 表直接查询
- 📊 新增 `getAnswersFromSatAnswerTable()` 方法,提供更准确的答案数据聚合
- ✨ 前端新增 `loadPreviousAnswers()` 方法,支持三种题型的智能答案回显
- 🎯 智能用户体验:只在成功加载历史答案时显示提示,异常时优雅降级
- 🛡️ 错误处理优化:404 错误不显示提示,历史答案加载失败不阻断答题流程
### v1.0.4 (2025-01-26)
- 🔄 **重要优化**:多次提交时实现历史数据软删除,确保用户只有一份有效答题记录
- 🗃️ 保留历史数据用于审计,但业务查询只返回最新有效数据
- ⚡ 优化查询性能,提交次数统计和答题记录查询只涉及有效数据
- 🛡️ 增强数据一致性,通过 record_id 关联确保三表数据状态同步
### v1.0.3 (2025-01-26)
- 🚀 **新功能**:实现问卷多次提交功能,支持根据 max_submit_times 设置控制提交次数
- 📊 优化提交次数检查逻辑,支持 0=无限制、N=最大 N 次的灵活配置
- ✅ 保留完整的答题历史记录,每次提交创建新的答题记录
- 🎯 增强用户体验,提供详细的提交次数限制提示信息
### v1.0.2 (2025-01-26)
- 🔧 **重要修复**:修复单选题和多选题答案未记录到 hq_sat_answer 表的问题
- 🎯 修复前端选项绑定使用分值导致的数据类型不匹配问题
- ✅ 优化答案保存逻辑,正确存储选项标识(A、B、C、D)和分值
- 📊 增强答案数据查询,支持选项分值自动计算
- 🐛 修复多选题答案保存逻辑错误
### v1.0.1 (2025-01-26)
- 🔧 修复答题提交时 user_type 字段长度问题
- ✅ 新增用户角色到数字编码的转换逻辑
- 📚 新增用户类型数据字典说明
- 🆕 新增答案详情记录功能,同时保存到 hq_sat_answer 表
- 📝 完善提交问卷答案接口的数据保存说明
### v1.0.0 (2025-01-26)
- ✅ 新增答题提交 API
- ✅ 新增答题状态查询 API
- ✅ 完善问卷管理 API
- ✅ 新增题目选项查询 API
- ✅ 建立完整的 API 文档体系
---
## 8. 开发说明
### 8.1 接口测试
推荐使用以下工具进行接口测试:
- **Postman**: 图形化接口测试工具
- **Apifox**: 国产 API 管理工具
- **curl**: 命令行工具
### 8.2 认证方式
所有需要权限的接口都需要在请求头中携带认证信息:
```http
Authorization: Bearer {token}
```
### 8.3 时间格式
所有时间字段统一使用 ISO 8601 格式:
```
2025-01-26T14:15:00.000Z
```
### 8.4 分页参数
所有分页接口统一使用以下参数:
- `pageNum`: 页码,从 1 开始
- `pageSize`: 页大小,默认 10
---
## 联系方式
如有 API 相关问题,请联系开发团队或查看项目仓库的 Issues。
---
### 分派工单
**接口地址**: `PUT /hq/pm/repairOrder/{orderId}/assign`
**接口描述**: 分派工单给维修员
**权限要求**: `pm:repairOrder:assign`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| orderId | Long | Y | 工单 ID | 1 |
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| assigneeId | Long | Y | 维修员 ID | 2 |
| assignRemark | String | N | 分派备注 | "紧急处理" |
**响应示例**:
```json
{
"code": 200,
"msg": "分派成功",
"data": null
}
```
**错误码**:
| 错误码 | 说明 | 解决方案 |
|--------|------|----------|
| 400001 | 工单不存在 | 检查工单 ID |
| 400002 | 维修员不存在 | 检查维修员 ID |
| 400003 | 工单状态不允许分派 | 检查工单状态 |
---
### 获取工单进度列表
**接口地址**: `GET /hq/pm/repairOrder/{orderId}/progress`
**接口描述**: 获取指定工单的进度记录列表
**权限要求**: `pm:repairOrder:progress`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| orderId | Long | Y | 工单 ID | 1 |
**响应示例**:
```json
{
"code": 200,
"msg": "查询成功",
"data": [
{
"id": 1,
"orderId": 1,
"progressType": "5",
"progressStatus": "正在维修",
"progressDetail": "已到达现场,开始检查设备",
"operatorId": 2,
"operatorName": "张维修",
"operatorRole": "worker",
"locationInfo": "教学楼A座301",
"estimatedDuration": 60,
"operationTime": "2025-06-30 14:30:00",
"nextStep": "更换零件",
"issuesEncountered": "发现零件老化",
"materialsNeeded": "电路板x1",
"remark": "需要采购新零件"
}
]
}
```
---
### 更新维修进度
**接口地址**: `POST /hq/pm/repairOrder/progress`
**接口描述**: 添加工单维修进度记录
**权限要求**: `pm:repairOrder:updateProgress`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| orderId | Long | Y | 工单 ID | 1 |
| progressType | String | Y | 进度类型:1-工单分派,2-接收确认,3-到达现场,4-开始维修,5-进度更新,6-完成维修,7-用户评价,8-工单归档,9-其他 | "5" |
| progressStatus | String | Y | 进度状态描述 | "正在维修" |
| progressDetail | String | Y | 进度详细描述 | "已到达现场,开始检查设备" |
| locationInfo | String | N | 操作位置信息 | "教学楼 A 座 301" |
| estimatedDuration | Integer | N | 预计耗时(分钟) | 60 |
| nextStep | String | N | 下一步计划 | "更换零件" |
| issuesEncountered | String | N | 遇到的问题 | "发现零件老化" |
| materialsNeeded | String | N | 需要的材料 | "电路板 x1" |
| remark | String | N | 备注信息 | "需要采购新零件" |
**响应示例**:
```json
{
"code": 200,
"msg": "进度更新成功",
"data": null
}
```
---
### 完成工单
**接口地址**: `PUT /hq/pm/repairOrder/{orderId}/finish`
**接口描述**: 完成工单维修,填写维修结果
**权限要求**: `pm:repairOrder:complete`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| orderId | Long | Y | 工单 ID | 1 |
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| repairResult | String | Y | 维修结果描述 | "已更换故障电路板,设备恢复正常" |
| usedMaterials | String | N | 使用材料清单 | "电路板 x1, 螺丝 x4" |
| followSuggestions | String | N | 后续建议 | "建议定期检查电路板状态" |
**响应示例**:
```json
{
"code": 200,
"msg": "工单完成成功",
"data": null
}
```
---
### 创建服务评价
**接口地址**: `POST /hq/pm/repairOrder/evaluation`
**接口描述**: 创建工单服务评价
**权限要求**: `pm:repairOrder:evaluate`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| orderId | Long | Y | 工单 ID | 1 |
| evaluatedWorkerId | Long | Y | 被评价维修员 ID | 2 |
| overallScore | Number | Y | 综合评价得分(1.0-5.0) | 4.5 |
| qualityScore | Number | Y | 维修质量得分(1.0-5.0) | 4.8 |
| attitudeScore | Number | Y | 服务态度得分(1.0-5.0) | 4.5 |
| responseSpeedScore | Number | Y | 响应速度得分(1.0-5.0) | 4.2 |
| communicationScore | Number | Y | 沟通效果得分(1.0-5.0) | 4.6 |
| evaluationContent | String | Y | 文字评价内容 | "维修师傅技术过硬,态度很好" |
| satisfactionLevel | String | Y | 满意度等级:1-非常满意,2-满意,3-一般,4-不满意,5-非常不满意 | "1" |
| isAnonymous | Integer | N | 是否匿名评价:0-否,1-是 | 0 |
| isPublic | Integer | N | 是否公开显示:0-否,1-是 | 1 |
| positiveTags | String | N | 正面标签 | "技术专业,态度友好" |
| negativeTags | String | N | 负面标签 | "" |
| improvementSuggestions | String | N | 改进建议 | "希望能提前通知到达时间" |
| remark | String | N | 备注信息 | "" |
**响应示例**:
```json
{
"code": 200,
"msg": "评价提交成功",
"data": null
}
```
---
### 获取工单评价
**接口地址**: `GET /hq/pm/repairOrder/{orderId}/evaluation`
**接口描述**: 获取指定工单的服务评价信息
**权限要求**: `pm:repairOrder:query`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| orderId | Long | Y | 工单 ID | 1 |
**响应示例**:
```json
{
"code": 200,
"msg": "查询成功",
"data": {
"id": 1,
"orderId": 1,
"evaluatorId": 3,
"evaluatorName": "张老师",
"evaluatedWorkerId": 2,
"evaluatedWorkerName": "张维修",
"overallScore": 4.5,
"qualityScore": 4.8,
"attitudeScore": 4.5,
"responseSpeedScore": 4.2,
"communicationScore": 4.6,
"evaluationContent": "维修师傅技术过硬,态度很好",
"satisfactionLevel": "1",
"isAnonymous": 0,
"isPublic": 1,
"positiveTags": "技术专业,态度友好",
"improvementSuggestions": "希望能提前通知到达时间",
"evaluationTime": "2025-06-30 16:30:00"
}
}
```
---