新增产品QC
This commit is contained in:
303
docs/backend-api.md
Normal file
303
docs/backend-api.md
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
# Corewing QC 后端接口文档
|
||||||
|
|
||||||
|
> 基础路径: `/api/qc`
|
||||||
|
|
||||||
|
## 通用响应结构
|
||||||
|
|
||||||
|
所有接口统一返回 `Result<T>`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": T,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|---------|---------|---------------------------|
|
||||||
|
| code | Integer | 状态码 |
|
||||||
|
| message | String | 消息内容 |
|
||||||
|
| data | T | 业务数据 |
|
||||||
|
| success | Boolean | 是否成功 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 验证无线板
|
||||||
|
|
||||||
|
验证 BLE MAC 地址对应的无线板是否已在系统中注册。
|
||||||
|
|
||||||
|
- **URL**: `POST /api/qc/wireless/validate-wireless`
|
||||||
|
- **Content-Type**: `application/json`
|
||||||
|
|
||||||
|
### 请求体
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mac": "AA:BB:CC:DD:EE:FF"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|--------|------|----------------------------------|
|
||||||
|
| mac | String | 是 | BLE MAC 地址,即设备 SN |
|
||||||
|
|
||||||
|
### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "验证通过",
|
||||||
|
"data": true,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `data = true` — 设备已注册,允许进行 QC 测试
|
||||||
|
- `data = false` / `success = false` — 设备未注册或验证失败,APP 端将**作废本次测试**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 上传测试记录
|
||||||
|
|
||||||
|
上传完整的 QC 测试结果(含所有步骤数据和照片 URL)。
|
||||||
|
|
||||||
|
- **URL**: `POST /api/qc/test/upload`
|
||||||
|
- **Content-Type**: `application/json`
|
||||||
|
|
||||||
|
### 请求体
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"testNo": "WL-20260227-001",
|
||||||
|
"productType": "WIRELESS_BOARD",
|
||||||
|
"sn": "AA:BB:CC:DD:EE:FF",
|
||||||
|
"operatorId": "OP001",
|
||||||
|
"operatorName": "张三",
|
||||||
|
"phoneModel": "Pixel 7",
|
||||||
|
"appVersion": "1.0.0",
|
||||||
|
"status": "PASS",
|
||||||
|
"createTime": "1740000000000",
|
||||||
|
"updatedAt": "1740001000000",
|
||||||
|
"uploadTime": "1740002000000",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"stepIndex": 1,
|
||||||
|
"stepName": "BLE连接与特征订阅",
|
||||||
|
"result": "PASS",
|
||||||
|
"dataJson": "{\"deviceName\":\"CoreWing-001\",\"deviceMac\":\"AA:BB:CC:DD:EE:FF\",\"validated\":true}",
|
||||||
|
"duration": 5200,
|
||||||
|
"completedAt": "1740000500000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"photoUrls": [
|
||||||
|
"https://qc.corewing.com/photos/xxx.jpg"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|--------------|------------------|------|------------------------------------------------|
|
||||||
|
| testNo | String | 是 | 测试单号,格式: `WL-yyyyMMdd-NNN` 或 `FC-yyyyMMdd-NNN` |
|
||||||
|
| productType | String | 是 | `WIRELESS_BOARD` / `FLIGHT_CONTROLLER` |
|
||||||
|
| sn | String | 是 | 产品序列号(无线板为 BLE MAC 地址) |
|
||||||
|
| operatorId | String | 是 | 测试员 ID |
|
||||||
|
| operatorName | String | 是 | 测试员姓名 |
|
||||||
|
| phoneModel | String | 否 | 测试手机型号 |
|
||||||
|
| appVersion | String | 否 | APP 版本号 |
|
||||||
|
| status | String | 是 | `PASS` / `FAIL` |
|
||||||
|
| createTime | String | 是 | 创建时间(毫秒时间戳字符串) |
|
||||||
|
| updatedAt | String | 否 | 最后更新时间(毫秒时间戳字符串) |
|
||||||
|
| uploadTime | String | 否 | 上传时间(毫秒时间戳字符串) |
|
||||||
|
| steps | StepResultData[] | 是 | 步骤结果数组 |
|
||||||
|
| photoUrls | String[] | 否 | 已上传照片的远程 URL 列表 |
|
||||||
|
|
||||||
|
**StepResultData:**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|-------------|--------|------|--------------------------------------|
|
||||||
|
| stepIndex | int | 是 | 步骤序号(从 1 开始) |
|
||||||
|
| stepName | String | 是 | 步骤名称 |
|
||||||
|
| result | String | 否 | `PASS` / `FAIL` / null(未完成) |
|
||||||
|
| dataJson | String | 否 | 步骤详细数据 JSON 字符串 |
|
||||||
|
| duration | long | 否 | 步骤耗时(毫秒) |
|
||||||
|
| completedAt | String | 否 | 完成时间(毫秒时间戳字符串) |
|
||||||
|
|
||||||
|
### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "上传成功",
|
||||||
|
"data": "https://qc.corewing.com/report/WL-20260227-001",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `data` — 测试报告 URL(可为 null)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 上传照片
|
||||||
|
|
||||||
|
上传测试步骤关联的照片。
|
||||||
|
|
||||||
|
- **URL**: `POST /api/qc/test/upload-photo`
|
||||||
|
- **Content-Type**: `multipart/form-data`
|
||||||
|
|
||||||
|
### 请求参数
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|---------------|------|------------------|
|
||||||
|
| testNo | String (text) | 是 | 测试单号 |
|
||||||
|
| photo | File | 是 | 照片文件 (JPEG等)|
|
||||||
|
|
||||||
|
### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "上传成功",
|
||||||
|
"data": {
|
||||||
|
"url": "https://qc.corewing.com/photos/xxx.jpg"
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 验证 SN
|
||||||
|
|
||||||
|
验证序列号是否有效(飞控板扫码流程使用)。
|
||||||
|
|
||||||
|
- **URL**: `POST /api/qc/test/validate-sn`
|
||||||
|
- **Content-Type**: `application/json`
|
||||||
|
|
||||||
|
### 请求体
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sn": "FC-SN-20260001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "SN 有效",
|
||||||
|
"data": true,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 获取测试配置
|
||||||
|
|
||||||
|
获取指定产品类型的测试参数/阈值。
|
||||||
|
|
||||||
|
- **URL**: `GET /api/qc/config/test-params?productType=WIRELESS_BOARD`
|
||||||
|
|
||||||
|
### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"success": true,
|
||||||
|
"data": { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> `data` 结构由后端自定义,APP 当前未使用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 查询 SN 历史记录
|
||||||
|
|
||||||
|
- **URL**: `GET /api/qc/test/history?sn=AA:BB:CC:DD:EE:FF`
|
||||||
|
|
||||||
|
### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"success": true,
|
||||||
|
"data": { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 获取测试报告
|
||||||
|
|
||||||
|
- **URL**: `GET /api/qc/test/report?testNo=WL-20260227-001`
|
||||||
|
|
||||||
|
### 响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"success": true,
|
||||||
|
"data": "https://qc.corewing.com/report/WL-20260227-001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 枚举值参考
|
||||||
|
|
||||||
|
### productType(产品类型)
|
||||||
|
|
||||||
|
| 值 | 说明 |
|
||||||
|
|---------------------|--------|
|
||||||
|
| WIRELESS_BOARD | 无线板 |
|
||||||
|
| FLIGHT_CONTROLLER | 飞控板 |
|
||||||
|
|
||||||
|
### status(测试状态)
|
||||||
|
|
||||||
|
| 值 | 说明 |
|
||||||
|
|-------------|----------|
|
||||||
|
| IN_PROGRESS | 进行中 |
|
||||||
|
| PASS | 通过 |
|
||||||
|
| FAIL | 失败 |
|
||||||
|
| UPLOADED | 已上传 |
|
||||||
|
|
||||||
|
### result(步骤结果)
|
||||||
|
|
||||||
|
| 值 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| PASS | 通过 |
|
||||||
|
| FAIL | 失败 |
|
||||||
|
|
||||||
|
### 无线板测试步骤(7 步)
|
||||||
|
|
||||||
|
| stepIndex | stepName |
|
||||||
|
|-----------|------------------------|
|
||||||
|
| 1 | BLE连接与特征订阅 |
|
||||||
|
| 2 | 安装无线板/外观检查 |
|
||||||
|
| 3 | 供电测试(4.5V) |
|
||||||
|
| 4 | Type-C USB测试 |
|
||||||
|
| 5 | BLE信号质量测试 |
|
||||||
|
| 6 | MAVLink心跳通信测试 |
|
||||||
|
| 7 | 激活/入库 |
|
||||||
|
|
||||||
|
### 飞控板测试步骤(12 步)
|
||||||
|
|
||||||
|
| stepIndex | stepName |
|
||||||
|
|-----------|-----------------|
|
||||||
|
| 1 | USB串口连接 |
|
||||||
|
| 2 | 安装SD卡与装夹 |
|
||||||
|
| 3 | 心跳检测 |
|
||||||
|
| 4 | IMU测试 |
|
||||||
|
| 5 | 气压计测试 |
|
||||||
|
| 6 | GPS模块检测 |
|
||||||
|
| 7 | RC输入测试 |
|
||||||
|
| 8 | ADC接口测试 |
|
||||||
|
| 9 | PWM输出测试 |
|
||||||
|
| 10 | 图传与OSD检查 |
|
||||||
|
| 11 | 参数重置 |
|
||||||
|
| 12 | 数据上传/结束 |
|
||||||
81
docs/schema.sql
Normal file
81
docs/schema.sql
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- Corewing QC 后端数据库建表 SQL(MySQL)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `flight_controller` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
`sn` VARCHAR(64) NOT NULL COMMENT '序列号',
|
||||||
|
`model` VARCHAR(64) DEFAULT NULL COMMENT '型号',
|
||||||
|
`batch_no` VARCHAR(64) DEFAULT NULL COMMENT '批次号',
|
||||||
|
`status` VARCHAR(32) NOT NULL DEFAULT 'REGISTERED' COMMENT '状态: REGISTERED / QC_PASS / QC_FAIL / ACTIVATED',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_sn` (`sn`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='飞控板设备注册表';
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `qc_test_record` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
`test_no` VARCHAR(32) NOT NULL COMMENT '测试单号,格式: WL-yyyyMMdd-NNN / FC-yyyyMMdd-NNN',
|
||||||
|
`product_type` VARCHAR(32) NOT NULL COMMENT '产品类型: WIRELESS_BOARD / FLIGHT_CONTROLLER',
|
||||||
|
`sn` VARCHAR(64) NOT NULL COMMENT '产品序列号(无线板为 BLE MAC)',
|
||||||
|
`operator_id` VARCHAR(32) NOT NULL COMMENT '测试员 ID',
|
||||||
|
`operator_name` VARCHAR(64) NOT NULL COMMENT '测试员姓名',
|
||||||
|
`phone_model` VARCHAR(64) DEFAULT NULL COMMENT '测试手机型号',
|
||||||
|
`app_version` VARCHAR(16) DEFAULT NULL COMMENT 'APP 版本',
|
||||||
|
`status` VARCHAR(16) NOT NULL COMMENT '测试结果: PASS / FAIL',
|
||||||
|
`create_time` DATETIME NOT NULL COMMENT 'APP 端创建时间',
|
||||||
|
`updated_at` DATETIME DEFAULT NULL COMMENT 'APP 端最后更新时间',
|
||||||
|
`upload_time` DATETIME DEFAULT NULL COMMENT 'APP 端上传时间',
|
||||||
|
`report_url` VARCHAR(512) DEFAULT NULL COMMENT '测试报告 URL',
|
||||||
|
`server_create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '服务端入库时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_test_no` (`test_no`),
|
||||||
|
KEY `idx_sn` (`sn`),
|
||||||
|
KEY `idx_product_type` (`product_type`),
|
||||||
|
KEY `idx_status` (`status`),
|
||||||
|
KEY `idx_create_time` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='QC 测试记录主表';
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `qc_step_result` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
`test_record_id` BIGINT NOT NULL COMMENT '关联 qc_test_record.id',
|
||||||
|
`step_index` INT NOT NULL COMMENT '步骤序号(从 1 开始)',
|
||||||
|
`step_name` VARCHAR(64) NOT NULL COMMENT '步骤名称',
|
||||||
|
`result` VARCHAR(8) DEFAULT NULL COMMENT '步骤结果: PASS / FAIL / NULL',
|
||||||
|
`data_json` TEXT DEFAULT NULL COMMENT '步骤详细数据 JSON',
|
||||||
|
`duration` BIGINT DEFAULT 0 COMMENT '步骤耗时(毫秒)',
|
||||||
|
`completed_at` DATETIME DEFAULT NULL COMMENT '步骤完成时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_test_record_id` (`test_record_id`),
|
||||||
|
UNIQUE KEY `uk_record_step` (`test_record_id`, `step_index`),
|
||||||
|
CONSTRAINT `fk_step_record` FOREIGN KEY (`test_record_id`)
|
||||||
|
REFERENCES `qc_test_record` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='QC 步骤结果表';
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `qc_photo` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
`test_record_id` BIGINT NOT NULL COMMENT '关联 qc_test_record.id',
|
||||||
|
`test_no` VARCHAR(32) NOT NULL COMMENT '测试单号',
|
||||||
|
`url` VARCHAR(512) NOT NULL COMMENT '照片远程 URL',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_test_no` (`test_no`),
|
||||||
|
CONSTRAINT `fk_photo_record` FOREIGN KEY (`test_record_id`)
|
||||||
|
REFERENCES `qc_test_record` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='QC 测试照片表';
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 示例数据(可选)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- 注册一批无线板
|
||||||
|
-- INSERT INTO `wireless_board` (`mac`) VALUES
|
||||||
|
-- ('AA:BB:CC:DD:EE:01'),
|
||||||
|
-- ('AA:BB:CC:DD:EE:02'),
|
||||||
|
-- ('AA:BB:CC:DD:EE:03');
|
||||||
@@ -30,6 +30,8 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
|||||||
.excludePathPatterns("/user/login", "/user/register", "/user/sendCode", "/user/forgetPassword", "/user/codeLogin")
|
.excludePathPatterns("/user/login", "/user/register", "/user/sendCode", "/user/forgetPassword", "/user/codeLogin")
|
||||||
// 排除后台管理登录接口
|
// 排除后台管理登录接口
|
||||||
.excludePathPatterns("/sys/user/login")
|
.excludePathPatterns("/sys/user/login")
|
||||||
|
//排除QC接口
|
||||||
|
.excludePathPatterns("/api/qc/**")
|
||||||
// 排除反馈接口(支持匿名提交)
|
// 排除反馈接口(支持匿名提交)
|
||||||
.excludePathPatterns("/feedback", "/feedback/**")
|
.excludePathPatterns("/feedback", "/feedback/**")
|
||||||
// 排除教程接口(支持匿名查询)
|
// 排除教程接口(支持匿名查询)
|
||||||
|
|||||||
19
src/main/java/com/corewing/app/dto/qc/StepResultData.java
Normal file
19
src/main/java/com/corewing/app/dto/qc/StepResultData.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package com.corewing.app.dto.qc;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class StepResultData {
|
||||||
|
|
||||||
|
private Integer stepIndex;
|
||||||
|
|
||||||
|
private String stepName;
|
||||||
|
|
||||||
|
private String result;
|
||||||
|
|
||||||
|
private String dataJson;
|
||||||
|
|
||||||
|
private Long duration;
|
||||||
|
|
||||||
|
private String completedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.corewing.app.dto.qc;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class UploadTestRecordRequest {
|
||||||
|
|
||||||
|
private String testNo;
|
||||||
|
|
||||||
|
private String productType;
|
||||||
|
|
||||||
|
private String sn;
|
||||||
|
|
||||||
|
private String operatorId;
|
||||||
|
|
||||||
|
private String operatorName;
|
||||||
|
|
||||||
|
private String phoneModel;
|
||||||
|
|
||||||
|
private String appVersion;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private String createTime;
|
||||||
|
|
||||||
|
private String updatedAt;
|
||||||
|
|
||||||
|
private String uploadTime;
|
||||||
|
|
||||||
|
private List<StepResultData> steps;
|
||||||
|
|
||||||
|
private List<String> photoUrls;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.corewing.app.dto.qc;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ValidateSnRequest {
|
||||||
|
|
||||||
|
private String sn;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.corewing.app.dto.qc;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ValidateWirelessBoardRequest {
|
||||||
|
private String mac;
|
||||||
|
}
|
||||||
28
src/main/java/com/corewing/app/entity/FlightController.java
Normal file
28
src/main/java/com/corewing/app/entity/FlightController.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package com.corewing.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("flight_controller")
|
||||||
|
public class FlightController {
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String sn;
|
||||||
|
|
||||||
|
private String model;
|
||||||
|
|
||||||
|
private String batchNo;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
24
src/main/java/com/corewing/app/entity/QcPhoto.java
Normal file
24
src/main/java/com/corewing/app/entity/QcPhoto.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package com.corewing.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("qc_photo")
|
||||||
|
public class QcPhoto {
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long testRecordId;
|
||||||
|
|
||||||
|
private String testNo;
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
30
src/main/java/com/corewing/app/entity/QcStepResult.java
Normal file
30
src/main/java/com/corewing/app/entity/QcStepResult.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package com.corewing.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("qc_step_result")
|
||||||
|
public class QcStepResult {
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long testRecordId;
|
||||||
|
|
||||||
|
private Integer stepIndex;
|
||||||
|
|
||||||
|
private String stepName;
|
||||||
|
|
||||||
|
private String result;
|
||||||
|
|
||||||
|
private String dataJson;
|
||||||
|
|
||||||
|
private Long duration;
|
||||||
|
|
||||||
|
private LocalDateTime completedAt;
|
||||||
|
}
|
||||||
42
src/main/java/com/corewing/app/entity/QcTestRecord.java
Normal file
42
src/main/java/com/corewing/app/entity/QcTestRecord.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package com.corewing.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("qc_test_record")
|
||||||
|
public class QcTestRecord {
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String testNo;
|
||||||
|
|
||||||
|
private String productType;
|
||||||
|
|
||||||
|
private String sn;
|
||||||
|
|
||||||
|
private String operatorId;
|
||||||
|
|
||||||
|
private String operatorName;
|
||||||
|
|
||||||
|
private String phoneModel;
|
||||||
|
|
||||||
|
private String appVersion;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
private LocalDateTime uploadTime;
|
||||||
|
|
||||||
|
private String reportUrl;
|
||||||
|
|
||||||
|
private LocalDateTime serverCreateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.corewing.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.corewing.app.entity.FlightController;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface FlightControllerMapper extends BaseMapper<FlightController> {
|
||||||
|
}
|
||||||
9
src/main/java/com/corewing/app/mapper/QcPhotoMapper.java
Normal file
9
src/main/java/com/corewing/app/mapper/QcPhotoMapper.java
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package com.corewing.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.corewing.app.entity.QcPhoto;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface QcPhotoMapper extends BaseMapper<QcPhoto> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.corewing.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.corewing.app.entity.QcStepResult;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface QcStepResultMapper extends BaseMapper<QcStepResult> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.corewing.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.corewing.app.entity.QcTestRecord;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface QcTestRecordMapper extends BaseMapper<QcTestRecord> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.corewing.app.modules.qc;
|
||||||
|
|
||||||
|
import com.corewing.app.common.Result;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Api(tags = "QC配置")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/qc/config")
|
||||||
|
public class QcConfigController {
|
||||||
|
|
||||||
|
@ApiOperation("获取测试配置")
|
||||||
|
@GetMapping("/test-params")
|
||||||
|
public Result<Map<String, Object>> getTestParams(@RequestParam("productType") String productType) {
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
// 后续可根据 productType 返回不同的测试参数/阈值
|
||||||
|
params.put("productType", productType);
|
||||||
|
return Result.success(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.corewing.app.modules.qc;
|
||||||
|
|
||||||
|
import com.corewing.app.common.Result;
|
||||||
|
import com.corewing.app.dto.qc.UploadTestRecordRequest;
|
||||||
|
import com.corewing.app.dto.qc.ValidateSnRequest;
|
||||||
|
import com.corewing.app.entity.QcTestRecord;
|
||||||
|
import com.corewing.app.service.QcTestService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Api(tags = "QC测试")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/qc/test")
|
||||||
|
public class QcTestController {
|
||||||
|
|
||||||
|
private final QcTestService qcTestService;
|
||||||
|
|
||||||
|
public QcTestController(QcTestService qcTestService) {
|
||||||
|
this.qcTestService = qcTestService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("上传测试记录")
|
||||||
|
@PostMapping("/upload")
|
||||||
|
public Result<String> uploadTestRecord(@RequestBody UploadTestRecordRequest request) {
|
||||||
|
return qcTestService.uploadTestRecord(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("上传照片")
|
||||||
|
@PostMapping("/upload-photo")
|
||||||
|
public Result<Map<String, String>> uploadPhoto(@RequestParam("testNo") String testNo,
|
||||||
|
@RequestParam("photo") MultipartFile photo) {
|
||||||
|
return qcTestService.uploadPhoto(testNo, photo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("验证SN")
|
||||||
|
@PostMapping("/validate-sn")
|
||||||
|
public Result<Boolean> validateSn(@RequestBody ValidateSnRequest request) {
|
||||||
|
return qcTestService.validateSn(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("查询SN历史记录")
|
||||||
|
@GetMapping("/history")
|
||||||
|
public Result<List<QcTestRecord>> getHistory(@RequestParam("sn") String sn) {
|
||||||
|
return qcTestService.getHistory(sn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("获取测试报告")
|
||||||
|
@GetMapping("/report")
|
||||||
|
public Result<String> getReport(@RequestParam("testNo") String testNo) {
|
||||||
|
return qcTestService.getReport(testNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.corewing.app.modules.qc;
|
||||||
|
|
||||||
|
import com.corewing.app.common.Result;
|
||||||
|
import com.corewing.app.dto.qc.ValidateWirelessBoardRequest;
|
||||||
|
import com.corewing.app.service.BizDeviceService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Api(tags = "无线版QC")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/qc/wireless")
|
||||||
|
public class WirelessBoardController {
|
||||||
|
private final BizDeviceService bizDeviceService;
|
||||||
|
|
||||||
|
public WirelessBoardController(BizDeviceService bizDeviceService) {
|
||||||
|
this.bizDeviceService = bizDeviceService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ApiOperation("验证已经激活的无线板")
|
||||||
|
@PostMapping("/validate-wireless")
|
||||||
|
public Result<Boolean> validateWirelessBoard(@RequestBody ValidateWirelessBoardRequest validateWirelessBoardRequest) {
|
||||||
|
return bizDeviceService.validateWirelessBoard(validateWirelessBoardRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.corewing.app.service;
|
package com.corewing.app.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.corewing.app.common.Result;
|
||||||
|
import com.corewing.app.dto.qc.ValidateWirelessBoardRequest;
|
||||||
import com.corewing.app.entity.BizDevice;
|
import com.corewing.app.entity.BizDevice;
|
||||||
|
|
||||||
public interface BizDeviceService extends IService<BizDevice> {
|
public interface BizDeviceService extends IService<BizDevice> {
|
||||||
|
Result<Boolean> validateWirelessBoard(ValidateWirelessBoardRequest validateWirelessBoardRequest);
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/main/java/com/corewing/app/service/QcTestService.java
Normal file
24
src/main/java/com/corewing/app/service/QcTestService.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package com.corewing.app.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.corewing.app.common.Result;
|
||||||
|
import com.corewing.app.dto.qc.UploadTestRecordRequest;
|
||||||
|
import com.corewing.app.dto.qc.ValidateSnRequest;
|
||||||
|
import com.corewing.app.entity.QcTestRecord;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface QcTestService extends IService<QcTestRecord> {
|
||||||
|
|
||||||
|
Result<String> uploadTestRecord(UploadTestRecordRequest request);
|
||||||
|
|
||||||
|
Result<Map<String, String>> uploadPhoto(String testNo, MultipartFile photo);
|
||||||
|
|
||||||
|
Result<Boolean> validateSn(ValidateSnRequest request);
|
||||||
|
|
||||||
|
Result<List<QcTestRecord>> getHistory(String sn);
|
||||||
|
|
||||||
|
Result<String> getReport(String testNo);
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package com.corewing.app.service.impl;
|
|||||||
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.corewing.app.dto.DeviceActivationRequest;
|
import com.corewing.app.dto.DeviceActivationRequest;
|
||||||
import com.corewing.app.entity.BizDevice;
|
import com.corewing.app.entity.BizDevice;
|
||||||
@@ -67,9 +66,7 @@ public class BizDeviceActivationServiceImpl extends ServiceImpl<BizDeviceActivat
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@Override
|
@Override
|
||||||
public Boolean factoryActivation(DeviceActivationRequest deviceActivationRequest) {
|
public Boolean factoryActivation(DeviceActivationRequest deviceActivationRequest) {
|
||||||
LambdaQueryWrapper<BizDeviceActivation> wrapper = new LambdaQueryWrapper<>();
|
BizDeviceActivation checkDeviceActivation = getBizDeviceActivationByMac(deviceActivationRequest.getMac());
|
||||||
wrapper.eq(BizDeviceActivation::getDeviceMac, deviceActivationRequest.getMac());
|
|
||||||
BizDeviceActivation checkDeviceActivation = getOne(wrapper);
|
|
||||||
if (checkDeviceActivation != null) {
|
if (checkDeviceActivation != null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -85,4 +82,16 @@ public class BizDeviceActivationServiceImpl extends ServiceImpl<BizDeviceActivat
|
|||||||
device.setCategoryId(deviceCategory.getId());
|
device.setCategoryId(deviceCategory.getId());
|
||||||
return deviceService.save(device);
|
return deviceService.save(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据mac地址获取激活设备
|
||||||
|
*
|
||||||
|
* @param mac mac地址
|
||||||
|
* @return 激活设备
|
||||||
|
*/
|
||||||
|
private BizDeviceActivation getBizDeviceActivationByMac(String mac) {
|
||||||
|
LambdaQueryWrapper<BizDeviceActivation> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(BizDeviceActivation::getDeviceMac, mac);
|
||||||
|
return getOne(wrapper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.corewing.app.service.impl;
|
package com.corewing.app.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.corewing.app.common.Result;
|
||||||
|
import com.corewing.app.dto.qc.ValidateWirelessBoardRequest;
|
||||||
import com.corewing.app.entity.BizDevice;
|
import com.corewing.app.entity.BizDevice;
|
||||||
import com.corewing.app.mapper.BizDeviceMapper;
|
import com.corewing.app.mapper.BizDeviceMapper;
|
||||||
import com.corewing.app.service.BizDeviceService;
|
import com.corewing.app.service.BizDeviceService;
|
||||||
@@ -8,4 +11,16 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class BizDeviceServiceImpl extends ServiceImpl<BizDeviceMapper, BizDevice> implements BizDeviceService {
|
public class BizDeviceServiceImpl extends ServiceImpl<BizDeviceMapper, BizDevice> implements BizDeviceService {
|
||||||
|
@Override
|
||||||
|
public Result<Boolean> validateWirelessBoard(ValidateWirelessBoardRequest validateWirelessBoardRequest) {
|
||||||
|
|
||||||
|
LambdaQueryWrapper<BizDevice> bizDeviceLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
bizDeviceLambdaQueryWrapper.eq(BizDevice::getDeviceMac, validateWirelessBoardRequest.getMac());
|
||||||
|
long deviceCount = count(bizDeviceLambdaQueryWrapper);
|
||||||
|
if (deviceCount > 0) {
|
||||||
|
return Result.success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.error("设备不存在或未激活");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package com.corewing.app.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.corewing.app.common.Result;
|
||||||
|
import com.corewing.app.dto.qc.StepResultData;
|
||||||
|
import com.corewing.app.dto.qc.UploadTestRecordRequest;
|
||||||
|
import com.corewing.app.dto.qc.ValidateSnRequest;
|
||||||
|
import com.corewing.app.entity.FlightController;
|
||||||
|
import com.corewing.app.entity.QcPhoto;
|
||||||
|
import com.corewing.app.entity.QcStepResult;
|
||||||
|
import com.corewing.app.entity.QcTestRecord;
|
||||||
|
import com.corewing.app.mapper.FlightControllerMapper;
|
||||||
|
import com.corewing.app.mapper.QcPhotoMapper;
|
||||||
|
import com.corewing.app.mapper.QcStepResultMapper;
|
||||||
|
import com.corewing.app.mapper.QcTestRecordMapper;
|
||||||
|
import com.corewing.app.service.QcTestService;
|
||||||
|
import com.corewing.app.util.OSSUploadUtil;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class QcTestServiceImpl extends ServiceImpl<QcTestRecordMapper, QcTestRecord> implements QcTestService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private QcStepResultMapper qcStepResultMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private QcPhotoMapper qcPhotoMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FlightControllerMapper flightControllerMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<String> uploadTestRecord(UploadTestRecordRequest request) {
|
||||||
|
if (request.getTestNo() == null || request.getTestNo().isEmpty()) {
|
||||||
|
return Result.error("测试单号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找是否已存在相同 testNo 的记录
|
||||||
|
LambdaQueryWrapper<QcTestRecord> testNoQuery = new LambdaQueryWrapper<>();
|
||||||
|
testNoQuery.eq(QcTestRecord::getTestNo, request.getTestNo());
|
||||||
|
QcTestRecord existByTestNo = baseMapper.selectOne(testNoQuery);
|
||||||
|
|
||||||
|
QcTestRecord record;
|
||||||
|
if (existByTestNo != null) {
|
||||||
|
// 相同 testNo 已存在 → 更新(APP 重传场景)
|
||||||
|
record = existByTestNo;
|
||||||
|
fillRecord(record, request);
|
||||||
|
baseMapper.updateById(record);
|
||||||
|
// 清除旧的步骤和照片,后面重新插入
|
||||||
|
deleteStepsAndPhotos(record.getId());
|
||||||
|
} else {
|
||||||
|
// 查找同一 SN 是否已有记录(返修重测场景)
|
||||||
|
LambdaQueryWrapper<QcTestRecord> snQuery = new LambdaQueryWrapper<>();
|
||||||
|
snQuery.eq(QcTestRecord::getSn, request.getSn())
|
||||||
|
.eq(QcTestRecord::getProductType, request.getProductType())
|
||||||
|
.orderByDesc(QcTestRecord::getCreateTime)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
QcTestRecord existBySn = baseMapper.selectOne(snQuery);
|
||||||
|
|
||||||
|
if (existBySn != null) {
|
||||||
|
// 同一 SN 已有记录 → 更新最近一条(返修后重新 QC)
|
||||||
|
record = existBySn;
|
||||||
|
fillRecord(record, request);
|
||||||
|
baseMapper.updateById(record);
|
||||||
|
deleteStepsAndPhotos(record.getId());
|
||||||
|
} else {
|
||||||
|
// 全新记录
|
||||||
|
record = new QcTestRecord();
|
||||||
|
fillRecord(record, request);
|
||||||
|
baseMapper.insert(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存步骤结果
|
||||||
|
if (!CollectionUtils.isEmpty(request.getSteps())) {
|
||||||
|
for (StepResultData stepData : request.getSteps()) {
|
||||||
|
QcStepResult step = new QcStepResult();
|
||||||
|
BeanUtils.copyProperties(stepData, step, "completedAt");
|
||||||
|
step.setTestRecordId(record.getId());
|
||||||
|
step.setCompletedAt(parseTimestamp(stepData.getCompletedAt()));
|
||||||
|
qcStepResultMapper.insert(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存照片 URL
|
||||||
|
if (!CollectionUtils.isEmpty(request.getPhotoUrls())) {
|
||||||
|
for (String photoUrl : request.getPhotoUrls()) {
|
||||||
|
QcPhoto photo = new QcPhoto();
|
||||||
|
photo.setTestRecordId(record.getId());
|
||||||
|
photo.setTestNo(request.getTestNo());
|
||||||
|
photo.setUrl(photoUrl);
|
||||||
|
qcPhotoMapper.insert(photo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回报告 URL(可为 null)
|
||||||
|
return Result.success("上传成功", record.getReportUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillRecord(QcTestRecord record, UploadTestRecordRequest request) {
|
||||||
|
// 拷贝同名同类型字段(testNo, productType, sn, operatorId, operatorName, phoneModel, appVersion, status)
|
||||||
|
BeanUtils.copyProperties(request, record, "createTime", "updatedAt", "uploadTime");
|
||||||
|
// 时间戳字符串 → LocalDateTime 需要手动转换
|
||||||
|
record.setCreateTime(parseTimestamp(request.getCreateTime()));
|
||||||
|
record.setUpdatedAt(parseTimestamp(request.getUpdatedAt()));
|
||||||
|
record.setUploadTime(parseTimestamp(request.getUploadTime()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteStepsAndPhotos(Long recordId) {
|
||||||
|
LambdaQueryWrapper<QcStepResult> stepDelete = new LambdaQueryWrapper<>();
|
||||||
|
stepDelete.eq(QcStepResult::getTestRecordId, recordId);
|
||||||
|
qcStepResultMapper.delete(stepDelete);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<QcPhoto> photoDelete = new LambdaQueryWrapper<>();
|
||||||
|
photoDelete.eq(QcPhoto::getTestRecordId, recordId);
|
||||||
|
qcPhotoMapper.delete(photoDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Map<String, String>> uploadPhoto(String testNo, MultipartFile photo) {
|
||||||
|
if (photo == null || photo.isEmpty()) {
|
||||||
|
return Result.error("照片文件不能为空");
|
||||||
|
}
|
||||||
|
if (testNo == null || testNo.isEmpty()) {
|
||||||
|
return Result.error("测试单号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找对应的测试记录
|
||||||
|
LambdaQueryWrapper<QcTestRecord> query = new LambdaQueryWrapper<>();
|
||||||
|
query.eq(QcTestRecord::getTestNo, testNo);
|
||||||
|
QcTestRecord record = baseMapper.selectOne(query);
|
||||||
|
if (record == null) {
|
||||||
|
return Result.error("测试记录不存在: " + testNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 生成 OSS 存储路径
|
||||||
|
String originalFilename = photo.getOriginalFilename();
|
||||||
|
String suffix = "";
|
||||||
|
if (originalFilename != null && originalFilename.contains(".")) {
|
||||||
|
suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||||
|
}
|
||||||
|
String objectName = "qc/photos/" + testNo + "/" + UUID.randomUUID().toString().replace("-", "") + suffix;
|
||||||
|
|
||||||
|
// 上传到 OSS
|
||||||
|
String url = OSSUploadUtil.uploadFile(photo.getInputStream(), objectName);
|
||||||
|
|
||||||
|
// 保存照片记录
|
||||||
|
QcPhoto qcPhoto = new QcPhoto();
|
||||||
|
qcPhoto.setTestRecordId(record.getId());
|
||||||
|
qcPhoto.setTestNo(testNo);
|
||||||
|
qcPhoto.setUrl(url);
|
||||||
|
qcPhotoMapper.insert(qcPhoto);
|
||||||
|
|
||||||
|
Map<String, String> result = new HashMap<>();
|
||||||
|
result.put("url", url);
|
||||||
|
return Result.success("上传成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("照片上传失败", e);
|
||||||
|
return Result.error("照片上传失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Boolean> validateSn(ValidateSnRequest request) {
|
||||||
|
if (request.getSn() == null || request.getSn().isEmpty()) {
|
||||||
|
return Result.error("SN 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaQueryWrapper<FlightController> query = new LambdaQueryWrapper<>();
|
||||||
|
query.eq(FlightController::getSn, request.getSn());
|
||||||
|
Long count = flightControllerMapper.selectCount(query);
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
return Result.success("SN 有效", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.error("SN 无效或未注册");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<List<QcTestRecord>> getHistory(String sn) {
|
||||||
|
if (sn == null || sn.isEmpty()) {
|
||||||
|
return Result.error("SN 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaQueryWrapper<QcTestRecord> query = new LambdaQueryWrapper<>();
|
||||||
|
query.eq(QcTestRecord::getSn, sn)
|
||||||
|
.orderByDesc(QcTestRecord::getCreateTime);
|
||||||
|
List<QcTestRecord> records = baseMapper.selectList(query);
|
||||||
|
|
||||||
|
return Result.success(records);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<String> getReport(String testNo) {
|
||||||
|
if (testNo == null || testNo.isEmpty()) {
|
||||||
|
return Result.error("测试单号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaQueryWrapper<QcTestRecord> query = new LambdaQueryWrapper<>();
|
||||||
|
query.eq(QcTestRecord::getTestNo, testNo);
|
||||||
|
QcTestRecord record = baseMapper.selectOne(query);
|
||||||
|
|
||||||
|
if (record == null) {
|
||||||
|
return Result.error("测试记录不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(record.getReportUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDateTime parseTimestamp(String timestamp) {
|
||||||
|
if (timestamp == null || timestamp.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
long millis = Long.parseLong(timestamp);
|
||||||
|
if (millis <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/main/resources/db/qc.sql
Normal file
81
src/main/resources/db/qc.sql
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- Corewing QC 后端数据库建表 SQL(MySQL)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `flight_controller` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
`sn` VARCHAR(64) NOT NULL COMMENT '序列号',
|
||||||
|
`model` VARCHAR(64) DEFAULT NULL COMMENT '型号',
|
||||||
|
`batch_no` VARCHAR(64) DEFAULT NULL COMMENT '批次号',
|
||||||
|
`status` VARCHAR(32) NOT NULL DEFAULT 'REGISTERED' COMMENT '状态: REGISTERED / QC_PASS / QC_FAIL / ACTIVATED',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_sn` (`sn`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='飞控板设备注册表';
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `qc_test_record` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
`test_no` VARCHAR(32) NOT NULL COMMENT '测试单号,格式: WL-yyyyMMdd-NNN / FC-yyyyMMdd-NNN',
|
||||||
|
`product_type` VARCHAR(32) NOT NULL COMMENT '产品类型: WIRELESS_BOARD / FLIGHT_CONTROLLER',
|
||||||
|
`sn` VARCHAR(64) NOT NULL COMMENT '产品序列号(无线板为 BLE MAC)',
|
||||||
|
`operator_id` VARCHAR(32) NOT NULL COMMENT '测试员 ID',
|
||||||
|
`operator_name` VARCHAR(64) NOT NULL COMMENT '测试员姓名',
|
||||||
|
`phone_model` VARCHAR(64) DEFAULT NULL COMMENT '测试手机型号',
|
||||||
|
`app_version` VARCHAR(16) DEFAULT NULL COMMENT 'APP 版本',
|
||||||
|
`status` VARCHAR(16) NOT NULL COMMENT '测试结果: PASS / FAIL',
|
||||||
|
`create_time` DATETIME NOT NULL COMMENT 'APP 端创建时间',
|
||||||
|
`updated_at` DATETIME DEFAULT NULL COMMENT 'APP 端最后更新时间',
|
||||||
|
`upload_time` DATETIME DEFAULT NULL COMMENT 'APP 端上传时间',
|
||||||
|
`report_url` VARCHAR(512) DEFAULT NULL COMMENT '测试报告 URL',
|
||||||
|
`server_create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '服务端入库时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_test_no` (`test_no`),
|
||||||
|
KEY `idx_sn` (`sn`),
|
||||||
|
KEY `idx_product_type` (`product_type`),
|
||||||
|
KEY `idx_status` (`status`),
|
||||||
|
KEY `idx_create_time` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='QC 测试记录主表';
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `qc_step_result` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
`test_record_id` BIGINT NOT NULL COMMENT '关联 qc_test_record.id',
|
||||||
|
`step_index` INT NOT NULL COMMENT '步骤序号(从 1 开始)',
|
||||||
|
`step_name` VARCHAR(64) NOT NULL COMMENT '步骤名称',
|
||||||
|
`result` VARCHAR(8) DEFAULT NULL COMMENT '步骤结果: PASS / FAIL / NULL',
|
||||||
|
`data_json` TEXT DEFAULT NULL COMMENT '步骤详细数据 JSON',
|
||||||
|
`duration` BIGINT DEFAULT 0 COMMENT '步骤耗时(毫秒)',
|
||||||
|
`completed_at` DATETIME DEFAULT NULL COMMENT '步骤完成时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_test_record_id` (`test_record_id`),
|
||||||
|
UNIQUE KEY `uk_record_step` (`test_record_id`, `step_index`),
|
||||||
|
CONSTRAINT `fk_step_record` FOREIGN KEY (`test_record_id`)
|
||||||
|
REFERENCES `qc_test_record` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='QC 步骤结果表';
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `qc_photo` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||||
|
`test_record_id` BIGINT NOT NULL COMMENT '关联 qc_test_record.id',
|
||||||
|
`test_no` VARCHAR(32) NOT NULL COMMENT '测试单号',
|
||||||
|
`url` VARCHAR(512) NOT NULL COMMENT '照片远程 URL',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_test_no` (`test_no`),
|
||||||
|
CONSTRAINT `fk_photo_record` FOREIGN KEY (`test_record_id`)
|
||||||
|
REFERENCES `qc_test_record` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='QC 测试照片表';
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 示例数据(可选)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- 注册一批无线板
|
||||||
|
-- INSERT INTO `wireless_board` (`mac`) VALUES
|
||||||
|
-- ('AA:BB:CC:DD:EE:01'),
|
||||||
|
-- ('AA:BB:CC:DD:EE:02'),
|
||||||
|
-- ('AA:BB:CC:DD:EE:03');
|
||||||
Reference in New Issue
Block a user