新增产品QC
This commit is contained in:
@@ -30,6 +30,8 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
||||
.excludePathPatterns("/user/login", "/user/register", "/user/sendCode", "/user/forgetPassword", "/user/codeLogin")
|
||||
// 排除后台管理登录接口
|
||||
.excludePathPatterns("/sys/user/login")
|
||||
//排除QC接口
|
||||
.excludePathPatterns("/api/qc/**")
|
||||
// 排除反馈接口(支持匿名提交)
|
||||
.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;
|
||||
|
||||
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;
|
||||
|
||||
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 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.corewing.app.dto.DeviceActivationRequest;
|
||||
import com.corewing.app.entity.BizDevice;
|
||||
@@ -36,9 +35,9 @@ public class BizDeviceActivationServiceImpl extends ServiceImpl<BizDeviceActivat
|
||||
LambdaQueryWrapper<BizDeviceActivation> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(BizDeviceActivation::getDeviceMac, deviceActivationRequest.getMac());
|
||||
BizDeviceActivation checkDeviceActivation = getOne(wrapper);
|
||||
if(checkDeviceActivation != null) {
|
||||
if (checkDeviceActivation != null) {
|
||||
// 该用户已经激活过该设备
|
||||
if(checkDeviceActivation.getUserId().equals(loginId)) {
|
||||
if (checkDeviceActivation.getUserId().equals(loginId)) {
|
||||
return true;
|
||||
}
|
||||
return updateById(checkDeviceActivation);
|
||||
@@ -48,7 +47,7 @@ public class BizDeviceActivationServiceImpl extends ServiceImpl<BizDeviceActivat
|
||||
LambdaQueryWrapper<BizDevice> checkDeviceWrapper = new LambdaQueryWrapper<>();
|
||||
checkDeviceWrapper.eq(BizDevice::getDeviceMac, deviceActivationRequest.getMac());
|
||||
List<BizDevice> list = deviceService.list(checkDeviceWrapper);
|
||||
if(list.isEmpty()) {
|
||||
if (list.isEmpty()) {
|
||||
throw new RuntimeException("该设备不是酷翼官方产品");
|
||||
}
|
||||
|
||||
@@ -67,10 +66,8 @@ public class BizDeviceActivationServiceImpl extends ServiceImpl<BizDeviceActivat
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public Boolean factoryActivation(DeviceActivationRequest deviceActivationRequest) {
|
||||
LambdaQueryWrapper<BizDeviceActivation> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(BizDeviceActivation::getDeviceMac, deviceActivationRequest.getMac());
|
||||
BizDeviceActivation checkDeviceActivation = getOne(wrapper);
|
||||
if(checkDeviceActivation != null) {
|
||||
BizDeviceActivation checkDeviceActivation = getBizDeviceActivationByMac(deviceActivationRequest.getMac());
|
||||
if (checkDeviceActivation != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -85,4 +82,16 @@ public class BizDeviceActivationServiceImpl extends ServiceImpl<BizDeviceActivat
|
||||
device.setCategoryId(deviceCategory.getId());
|
||||
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;
|
||||
|
||||
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.ValidateWirelessBoardRequest;
|
||||
import com.corewing.app.entity.BizDevice;
|
||||
import com.corewing.app.mapper.BizDeviceMapper;
|
||||
import com.corewing.app.service.BizDeviceService;
|
||||
@@ -8,4 +11,16 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
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