|
|
@@ -0,0 +1,697 @@
|
|
|
+package com.haha.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
+import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.haha.common.dto.FloorConfig;
|
|
|
+import com.haha.common.dto.LayerTemplateCreateDTO;
|
|
|
+import com.haha.common.dto.LayerTemplateUpdateDTO;
|
|
|
+import com.haha.common.constant.CommonConstants;
|
|
|
+import com.haha.common.constant.SyncConstants;
|
|
|
+import com.haha.common.exception.BusinessException;
|
|
|
+import com.haha.common.utils.EntityLabelUtils;
|
|
|
+import com.haha.entity.LayerTemplate;
|
|
|
+import com.haha.mapper.LayerTemplateMapper;
|
|
|
+import com.haha.sdk.HahaClient;
|
|
|
+import com.haha.sdk.api.GoodsApi;
|
|
|
+import com.haha.service.LayerTemplateService;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 设备层模版管理服务实现类
|
|
|
+ * 提供设备层模版的完整业务逻辑实现,包括CRUD和同步功能
|
|
|
+ *
|
|
|
+ * @author haha-service
|
|
|
+ * @version 1.0.0
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class LayerTemplateServiceImpl extends ServiceImpl<LayerTemplateMapper, LayerTemplate> implements LayerTemplateService {
|
|
|
+
|
|
|
+ private final HahaClient hahaClient;
|
|
|
+ private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分页查询层模版列表
|
|
|
+ * 支持按设备ID、同步状态、模板名称进行筛选
|
|
|
+ *
|
|
|
+ * @param page 页码
|
|
|
+ * @param pageSize 每页条数
|
|
|
+ * @param deviceId 设备ID(可选)
|
|
|
+ * @param syncStatus 同步状态(可选)
|
|
|
+ * @param templateName 模板名称(可选)
|
|
|
+ * @return 分页结果(已填充展示标签)
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<LayerTemplate> getPage(int page, int pageSize, String deviceId, Integer syncStatus, String templateName) {
|
|
|
+ log.info("分页查询层模版: page={}, pageSize={}, deviceId={}, syncStatus={}, templateName={}",
|
|
|
+ page, pageSize, deviceId, syncStatus, templateName);
|
|
|
+
|
|
|
+ LambdaQueryWrapper<LayerTemplate> wrapper = new LambdaQueryWrapper<>();
|
|
|
+
|
|
|
+ // 构建查询条件
|
|
|
+ wrapper.like(deviceId != null && !deviceId.isEmpty(), LayerTemplate::getDeviceId, deviceId)
|
|
|
+ .eq(syncStatus != null, LayerTemplate::getSyncStatus, syncStatus)
|
|
|
+ .like(templateName != null && !templateName.isEmpty(), LayerTemplate::getTemplateName, templateName)
|
|
|
+ .eq(LayerTemplate::getIsDeleted, CommonConstants.NOT_DELETED)
|
|
|
+ .orderByDesc(LayerTemplate::getCreateTime);
|
|
|
+
|
|
|
+ // 执行分页查询
|
|
|
+ IPage<LayerTemplate> pageResult = this.page(new Page<>(page, pageSize), wrapper);
|
|
|
+
|
|
|
+ log.info("层模版查询结果: total={}, records={}", pageResult.getTotal(), pageResult.getRecords().size());
|
|
|
+
|
|
|
+ // 填充展示字段(同步状态标签、设备类型等)
|
|
|
+ pageResult.getRecords().forEach(this::fillLabels);
|
|
|
+
|
|
|
+ return pageResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取层模版详情
|
|
|
+ * 包含完整的展示字段填充
|
|
|
+ *
|
|
|
+ * @param id 层模版ID
|
|
|
+ * @return 层模版详情
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public LayerTemplate getDetailById(Long id) {
|
|
|
+ log.info("获取层模版详情: id={}", id);
|
|
|
+
|
|
|
+ LayerTemplate layerTemplate = this.getById(id);
|
|
|
+ if (layerTemplate == null) {
|
|
|
+ log.warn("层模版不存在: id={}", id);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充展示字段
|
|
|
+ fillLabels(layerTemplate);
|
|
|
+
|
|
|
+ return layerTemplate;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据设备ID获取层模版
|
|
|
+ * 一个设备通常对应一个层模版配置
|
|
|
+ *
|
|
|
+ * @param deviceId 设备ID(SN号)
|
|
|
+ * @return 层模版信息
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public LayerTemplate getByDeviceId(String deviceId) {
|
|
|
+ log.info("根据设备ID获取层模版: deviceId={}", deviceId);
|
|
|
+
|
|
|
+ LambdaQueryWrapper<LayerTemplate> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(LayerTemplate::getDeviceId, deviceId)
|
|
|
+ .eq(LayerTemplate::getIsDeleted, CommonConstants.NOT_DELETED)
|
|
|
+ .orderByDesc(LayerTemplate::getCreateTime)
|
|
|
+ .last("LIMIT 1");
|
|
|
+
|
|
|
+ LayerTemplate layerTemplate = this.getOne(wrapper);
|
|
|
+ if (layerTemplate != null) {
|
|
|
+ fillLabels(layerTemplate);
|
|
|
+ }
|
|
|
+
|
|
|
+ return layerTemplate;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建层模版
|
|
|
+ * 将前端提交的层模版数据保存到本地数据库
|
|
|
+ *
|
|
|
+ * @param dto 创建请求DTO
|
|
|
+ * @return 创建后的层模版
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public LayerTemplate create(LayerTemplateCreateDTO dto) {
|
|
|
+ log.info("创建层模版: deviceId={}, templateName={}", dto.getDeviceId(), dto.getTemplateName());
|
|
|
+
|
|
|
+ // 检查是否已存在该设备的层模版
|
|
|
+ LayerTemplate existing = this.getByDeviceId(dto.getDeviceId());
|
|
|
+ if (existing != null) {
|
|
|
+ throw new BusinessException(400, "该设备已存在层模版配置");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转换DTO为实体对象
|
|
|
+ LayerTemplate layerTemplate = convertFromCreateDto(dto);
|
|
|
+
|
|
|
+ // 设置基础字段
|
|
|
+ layerTemplate.setIsDeleted(CommonConstants.NOT_DELETED);
|
|
|
+ layerTemplate.setSyncStatus(SyncConstants.STATUS_NOT_SYNCED);
|
|
|
+ layerTemplate.setCreateTime(LocalDateTime.now());
|
|
|
+ layerTemplate.setUpdateTime(LocalDateTime.now());
|
|
|
+
|
|
|
+ // 保存到数据库
|
|
|
+ this.save(layerTemplate);
|
|
|
+
|
|
|
+ log.info("层模版创建成功: id={}, deviceId={}", layerTemplate.getId(), layerTemplate.getDeviceId());
|
|
|
+
|
|
|
+ fillLabels(layerTemplate);
|
|
|
+ return layerTemplate;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新层模版
|
|
|
+ * 支持更新模板名称和层数据配置
|
|
|
+ *
|
|
|
+ * @param id 层模版ID
|
|
|
+ * @param dto 更新请求DTO
|
|
|
+ * @return 更新后的层模版
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public LayerTemplate update(Long id, LayerTemplateUpdateDTO dto) {
|
|
|
+ log.info("更新层模版: id={}", id);
|
|
|
+
|
|
|
+ LayerTemplate existing = this.getById(id);
|
|
|
+ if (existing == null) {
|
|
|
+ throw new BusinessException(404, "层模版不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新可编辑字段
|
|
|
+ if (dto.getTemplateName() != null) {
|
|
|
+ existing.setTemplateName(dto.getTemplateName());
|
|
|
+ }
|
|
|
+ if (dto.getLeftFloors() != null && !dto.getLeftFloors().isEmpty()) {
|
|
|
+ try {
|
|
|
+ existing.setLeftFloors(objectMapper.writeValueAsString(dto.getLeftFloors()));
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.error("左门层数据JSON序列化失败", e);
|
|
|
+ throw new BusinessException(500, "数据格式错误");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (dto.getRightFloors() != null && !dto.getRightFloors().isEmpty()) {
|
|
|
+ try {
|
|
|
+ existing.setRightFloors(objectMapper.writeValueAsString(dto.getRightFloors()));
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.error("右门层数据JSON序列化失败", e);
|
|
|
+ throw new BusinessException(500, "数据格式错误");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新时间戳和同步状态标记
|
|
|
+ existing.setUpdateTime(LocalDateTime.now());
|
|
|
+ existing.setSyncStatus(SyncConstants.STATUS_NOT_SYNCED); // 标记为需要重新同步
|
|
|
+
|
|
|
+ this.updateById(existing);
|
|
|
+
|
|
|
+ log.info("层模版更新成功: id={}", id);
|
|
|
+
|
|
|
+ fillLabels(existing);
|
|
|
+ return existing;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 软删除层模版
|
|
|
+ * 仅标记删除状态,不物理删除数据
|
|
|
+ *
|
|
|
+ * @param id 层模版ID
|
|
|
+ * @return 是否成功
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean softDelete(Long id) {
|
|
|
+ log.info("软删除层模版: id={}", id);
|
|
|
+
|
|
|
+ LayerTemplate layerTemplate = this.getById(id);
|
|
|
+ if (layerTemplate == null) {
|
|
|
+ throw new BusinessException(404, "层模版不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ LambdaUpdateWrapper<LayerTemplate> updateWrapper = new LambdaUpdateWrapper<>();
|
|
|
+ updateWrapper.eq(LayerTemplate::getId, id)
|
|
|
+ .set(LayerTemplate::getIsDeleted, CommonConstants.DELETED)
|
|
|
+ .set(LayerTemplate::getUpdateTime, LocalDateTime.now());
|
|
|
+
|
|
|
+ boolean result = this.update(updateWrapper);
|
|
|
+
|
|
|
+ log.info("层模版软删除结果: id={}, result={}", id, result);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从哈哈平台同步单个设备层模版
|
|
|
+ * 这是核心同步逻辑:
|
|
|
+ * 1. 更新状态为"同步中"
|
|
|
+ * 2. 调用GoodsApi获取平台数据
|
|
|
+ * 3. 解析并转换数据
|
|
|
+ * 4. 更新或创建本地记录
|
|
|
+ * 5. 更新同步状态和时间
|
|
|
+ * 6. 异常处理确保状态正确更新
|
|
|
+ *
|
|
|
+ * @param deviceId 设备ID(SN号)
|
|
|
+ * @return 是否成功
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean syncFromHaha(String deviceId) {
|
|
|
+ log.info("开始同步层模版: deviceId={}", deviceId);
|
|
|
+
|
|
|
+ // 步骤1: 查询或创建本地记录,并更新状态为"同步中"
|
|
|
+ LayerTemplate localTemplate = this.getByDeviceId(deviceId);
|
|
|
+ boolean isNewRecord = false;
|
|
|
+
|
|
|
+ if (localTemplate == null) {
|
|
|
+ // 不存在则创建新记录
|
|
|
+ localTemplate = new LayerTemplate();
|
|
|
+ localTemplate.setDeviceId(deviceId);
|
|
|
+ localTemplate.setIsDeleted(CommonConstants.NOT_DELETED);
|
|
|
+ localTemplate.setCreateTime(LocalDateTime.now());
|
|
|
+ isNewRecord = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新状态为"同步中"
|
|
|
+ updateSyncStatus(deviceId, SyncConstants.STATUS_SYNCING, null);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 步骤2: 调用哈哈平台API获取层模版数据
|
|
|
+ GoodsApi goodsApi = hahaClient.getGoodsApi();
|
|
|
+ Map<String, Object> apiData = goodsApi.getLayerTemplate(deviceId);
|
|
|
+
|
|
|
+ log.info("成功获取平台层模版数据: deviceId={}, data={}", deviceId, apiData);
|
|
|
+
|
|
|
+ // 步骤3-4: 解析返回数据并转换为实体对象
|
|
|
+ LayerTemplate updatedTemplate = convertFromApiData(deviceId, apiData);
|
|
|
+
|
|
|
+ // 步骤5: 更新或创建本地记录
|
|
|
+ if (isNewRecord) {
|
|
|
+ // 新记录,设置所有字段
|
|
|
+ localTemplate.setTemplateId(updatedTemplate.getTemplateId());
|
|
|
+ localTemplate.setTemplateName(updatedTemplate.getTemplateName());
|
|
|
+ localTemplate.setDeviceType(updatedTemplate.getDeviceType());
|
|
|
+ localTemplate.setShelfNum(updatedTemplate.getShelfNum());
|
|
|
+ localTemplate.setLeftFloors(updatedTemplate.getLeftFloors());
|
|
|
+ localTemplate.setRightFloors(updatedTemplate.getRightFloors());
|
|
|
+ localTemplate.setGoodsRule(updatedTemplate.getGoodsRule());
|
|
|
+ localTemplate.setSyncTime(LocalDateTime.now());
|
|
|
+ localTemplate.setUpdateTime(LocalDateTime.now());
|
|
|
+
|
|
|
+ this.save(localTemplate);
|
|
|
+ log.info("新建层模版记录: id={}, deviceId={}", localTemplate.getId(), deviceId);
|
|
|
+ } else {
|
|
|
+ // 已存在记录,更新数据
|
|
|
+ LambdaUpdateWrapper<LayerTemplate> updateWrapper = new LambdaUpdateWrapper<>();
|
|
|
+ updateWrapper.eq(LayerTemplate::getDeviceId, deviceId)
|
|
|
+ .set(LayerTemplate::getTemplateId, updatedTemplate.getTemplateId())
|
|
|
+ .set(LayerTemplate::getTemplateName, updatedTemplate.getTemplateName())
|
|
|
+ .set(LayerTemplate::getDeviceType, updatedTemplate.getDeviceType())
|
|
|
+ .set(LayerTemplate::getShelfNum, updatedTemplate.getShelfNum())
|
|
|
+ .set(LayerTemplate::getLeftFloors, updatedTemplate.getLeftFloors())
|
|
|
+ .set(LayerTemplate::getRightFloors, updatedTemplate.getRightFloors())
|
|
|
+ .set(LayerTemplate::getGoodsRule, updatedTemplate.getGoodsRule())
|
|
|
+ .set(LayerTemplate::getUpdateTime, LocalDateTime.now());
|
|
|
+
|
|
|
+ this.update(updateWrapper);
|
|
|
+ log.info("更新层模版记录: deviceId={}", deviceId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 步骤6: 更新同步状态为"已同步"并记录时间
|
|
|
+ updateSyncStatus(deviceId, SyncConstants.STATUS_SYNCED, LocalDateTime.now());
|
|
|
+
|
|
|
+ log.info("层模版同步成功: deviceId={}", deviceId);
|
|
|
+ return true;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("层模版同步失败: deviceId={}, error={}", deviceId, e.getMessage(), e);
|
|
|
+
|
|
|
+ // 步骤7: 异常处理 - 更新状态为"同步失败"
|
|
|
+ updateSyncStatus(deviceId, SyncConstants.STATUS_SYNC_FAILED, null);
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量同步设备层模版
|
|
|
+ * 遍历设备列表逐个执行同步操作,并统计结果
|
|
|
+ *
|
|
|
+ * @param deviceIds 设备ID列表
|
|
|
+ * @return 同步结果统计(包含成功数、失败数、总耗时等)
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> batchSync(List<String> deviceIds) {
|
|
|
+ log.info("开始批量同步层模版: totalDevices={}", deviceIds.size());
|
|
|
+
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ int successCount = 0;
|
|
|
+ int failCount = 0;
|
|
|
+ List<String> failedDeviceIds = new java.util.ArrayList<>();
|
|
|
+
|
|
|
+ for (String deviceId : deviceIds) {
|
|
|
+ boolean success = this.syncFromHaha(deviceId);
|
|
|
+ if (success) {
|
|
|
+ successCount++;
|
|
|
+ } else {
|
|
|
+ failCount++;
|
|
|
+ failedDeviceIds.add(deviceId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ long endTime = System.currentTimeMillis();
|
|
|
+ long costTime = endTime - startTime;
|
|
|
+
|
|
|
+ // 构建统计结果
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("totalDevices", deviceIds.size());
|
|
|
+ result.put("successCount", successCount);
|
|
|
+ result.put("failCount", failCount);
|
|
|
+ result.put("failedDeviceIds", failedDeviceIds);
|
|
|
+ result.put("costTime", costTime);
|
|
|
+ result.put("costTimeDisplay", formatCostTime(costTime));
|
|
|
+
|
|
|
+ log.info("批量同步完成: total={}, success={}, fail={}, cost={}ms",
|
|
|
+ deviceIds.size(), successCount, failCount, costTime);
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 私有工具方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将API返回的Map数据转换为实体对象
|
|
|
+ * 解析哈哈平台返回的层模版数据结构
|
|
|
+ *
|
|
|
+ * @param deviceId 设备ID
|
|
|
+ * @param apiData API返回的数据Map
|
|
|
+ * @return 转换后的实体对象
|
|
|
+ */
|
|
|
+ private LayerTemplate convertFromApiData(String deviceId, Map<String, Object> apiData) {
|
|
|
+ LayerTemplate template = new LayerTemplate();
|
|
|
+ template.setDeviceId(deviceId);
|
|
|
+
|
|
|
+ // 提取基本字段
|
|
|
+ if (apiData.containsKey("template_id")) {
|
|
|
+ template.setTemplateId(String.valueOf(apiData.get("template_id")));
|
|
|
+ }
|
|
|
+ if (apiData.containsKey("name")) {
|
|
|
+ template.setTemplateName(String.valueOf(apiData.get("name")));
|
|
|
+ }
|
|
|
+ if (apiData.containsKey("type")) {
|
|
|
+ Object typeObj = apiData.get("type");
|
|
|
+ if (typeObj instanceof Number) {
|
|
|
+ template.setDeviceType(((Number) typeObj).intValue());
|
|
|
+ } else if (typeObj != null) {
|
|
|
+ try {
|
|
|
+ template.setDeviceType(Integer.parseInt(typeObj.toString()));
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.warn("解析deviceType失败: {}", typeObj);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (apiData.containsKey("shelf_num")) {
|
|
|
+ Object shelfNumObj = apiData.get("shelf_num");
|
|
|
+ if (shelfNumObj instanceof Number) {
|
|
|
+ template.setShelfNum(((Number) shelfNumObj).intValue());
|
|
|
+ } else if (shelfNumObj != null) {
|
|
|
+ try {
|
|
|
+ template.setShelfNum(Integer.parseInt(shelfNumObj.toString()));
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.warn("解析shelfNum失败: {}", shelfNumObj);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提取products中的left/right数据(关键!)
|
|
|
+ if (apiData.containsKey("products")) {
|
|
|
+ Object productsObj = apiData.get("products");
|
|
|
+ if (productsObj instanceof Map) {
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ Map<String, Object> products = (Map<String, Object>) productsObj;
|
|
|
+
|
|
|
+ // 左门层数据 -> JSON字符串存储
|
|
|
+ if (products.containsKey("left")) {
|
|
|
+ Object leftObj = products.get("left");
|
|
|
+ try {
|
|
|
+ template.setLeftFloors(objectMapper.writeValueAsString(leftObj));
|
|
|
+ log.debug("左门层数据转换成功: deviceId={}", deviceId);
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.error("左门层数据JSON序列化失败: deviceId={}", deviceId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 右门层数据 -> JSON字符串存储
|
|
|
+ if (products.containsKey("right")) {
|
|
|
+ Object rightObj = products.get("right");
|
|
|
+ try {
|
|
|
+ template.setRightFloors(objectMapper.writeValueAsString(rightObj));
|
|
|
+ log.debug("右门层数据转换成功: deviceId={}", deviceId);
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.error("右门层数据JSON序列化失败: deviceId={}", deviceId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 商品规则(如果存在)
|
|
|
+ if (apiData.containsKey("goods_rule")) {
|
|
|
+ Object goodsRuleObj = apiData.get("goods_rule");
|
|
|
+ if (goodsRuleObj != null) {
|
|
|
+ try {
|
|
|
+ template.setGoodsRule(objectMapper.writeValueAsString(goodsRuleObj));
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.warn("商品规则JSON序列化失败: deviceId={}", deviceId, e);
|
|
|
+ template.setGoodsRule(goodsRuleObj.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return template;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将前端DTO转换为提交给平台的JSON格式
|
|
|
+ * 用于将本地修改后的层模版数据推送到哈哈平台
|
|
|
+ *
|
|
|
+ * @param dto 前端提交的创建DTO
|
|
|
+ * @return 平台所需的JSON格式字符串
|
|
|
+ */
|
|
|
+ private String convertToPlatformJson(LayerTemplateCreateDTO dto) {
|
|
|
+ try {
|
|
|
+ Map<String, Object> platformData = new HashMap<>();
|
|
|
+
|
|
|
+ // 构建products结构
|
|
|
+ Map<String, Object> products = new HashMap<>();
|
|
|
+ products.put("left", dto.getLeftFloors());
|
|
|
+ products.put("right", dto.getRightFloors() != null ? dto.getRightFloors() : List.of());
|
|
|
+
|
|
|
+ platformData.put("products", products);
|
|
|
+ platformData.put("name", dto.getTemplateName());
|
|
|
+
|
|
|
+ return objectMapper.writeValueAsString(platformData);
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.error("转换为平台JSON格式失败", e);
|
|
|
+ throw new BusinessException(500, "数据格式转换失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将创建DTO转换为实体对象
|
|
|
+ *
|
|
|
+ * @param dto 创建请求DTO
|
|
|
+ * @return 实体对象
|
|
|
+ */
|
|
|
+ private LayerTemplate convertFromCreateDto(LayerTemplateCreateDTO dto) {
|
|
|
+ LayerTemplate template = new LayerTemplate();
|
|
|
+ template.setDeviceId(dto.getDeviceId());
|
|
|
+ template.setTemplateName(dto.getTemplateName());
|
|
|
+
|
|
|
+ // 序列化左门层数据
|
|
|
+ if (dto.getLeftFloors() != null && !dto.getLeftFloors().isEmpty()) {
|
|
|
+ try {
|
|
|
+ template.setLeftFloors(objectMapper.writeValueAsString(dto.getLeftFloors()));
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.error("左门层数据序列化失败", e);
|
|
|
+ throw new BusinessException(500, "左门层数据格式错误");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 序列化右门层数据
|
|
|
+ if (dto.getRightFloors() != null && !dto.getRightFloors().isEmpty()) {
|
|
|
+ try {
|
|
|
+ template.setRightFloors(objectMapper.writeValueAsString(dto.getRightFloors()));
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.error("右门层数据序列化失败", e);
|
|
|
+ throw new BusinessException(500, "右门层数据格式错误");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return template;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 填充展示字段
|
|
|
+ * 参考ProductServiceImpl.fillProductLabels模式
|
|
|
+ * 为前端展示提供友好的标签和格式化数据
|
|
|
+ *
|
|
|
+ * @param template 层模版实体
|
|
|
+ */
|
|
|
+ private void fillLabels(LayerTemplate template) {
|
|
|
+ // 1. 同步状态标签和颜色
|
|
|
+ if (template.getSyncStatus() != null) {
|
|
|
+ var statusLabel = EntityLabelUtils.getStatusLabel("sync", template.getSyncStatus());
|
|
|
+ template.setSyncStatusLabel(statusLabel.getLabel());
|
|
|
+ template.setSyncStatusColor(statusLabel.getColor());
|
|
|
+ template.setStatusText(statusLabel.getLabel()); // 兼容性字段
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 设备类型名称
|
|
|
+ if (template.getDeviceType() != null) {
|
|
|
+ template.setDeviceTypeName(getDeviceTypeName(template.getDeviceType()));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 左门层数据展示(美化JSON显示)
|
|
|
+ if (template.getLeftFloors() != null && !template.getLeftFloors().isEmpty()) {
|
|
|
+ template.setLeftFloorsDisplay(formatJsonForDisplay(template.getLeftFloors()));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 右门层数据展示
|
|
|
+ if (template.getRightFloors() != null && !template.getRightFloors().isEmpty()) {
|
|
|
+ template.setRightFloorsDisplay(formatJsonForDisplay(template.getRightFloors()));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 计算总层数(左门+右门)
|
|
|
+ Integer totalFloors = calculateTotalFloors(template);
|
|
|
+ template.setTotalFloors(totalFloors);
|
|
|
+
|
|
|
+ // 6. 最后同步时间格式化
|
|
|
+ if (template.getSyncTime() != null) {
|
|
|
+ template.setLastSyncTime(formatDateTime(template.getSyncTime()));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 商品规则展示
|
|
|
+ if (template.getGoodsRule() != null && !template.getGoodsRule().isEmpty()) {
|
|
|
+ template.setGoodsRuleDisplay(formatJsonForDisplay(template.getGoodsRule()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新同步状态
|
|
|
+ * 统一的状态更新方法,确保原子性操作
|
|
|
+ *
|
|
|
+ * @param deviceId 设备ID
|
|
|
+ * @param status 新的同步状态
|
|
|
+ * @param syncTime 同步时间(可为null)
|
|
|
+ */
|
|
|
+ private void updateSyncStatus(String deviceId, Integer status, LocalDateTime syncTime) {
|
|
|
+ LambdaUpdateWrapper<LayerTemplate> updateWrapper = new LambdaUpdateWrapper<>();
|
|
|
+ updateWrapper.eq(LayerTemplate::getDeviceId, deviceId)
|
|
|
+ .set(LayerTemplate::getSyncStatus, status)
|
|
|
+ .set(LayerTemplate::getUpdateTime, LocalDateTime.now());
|
|
|
+
|
|
|
+ if (syncTime != null) {
|
|
|
+ updateWrapper.set(LayerTemplate::getSyncTime, syncTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.update(updateWrapper);
|
|
|
+
|
|
|
+ log.debug("同步状态更新: deviceId={}, status={}", deviceId,
|
|
|
+ SyncConstants.getStatusDesc(status));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取设备类型名称
|
|
|
+ *
|
|
|
+ * @param deviceType 设备类型代码
|
|
|
+ * @return 设备类型名称
|
|
|
+ */
|
|
|
+ private String getDeviceTypeName(Integer deviceType) {
|
|
|
+ if (deviceType == null) {
|
|
|
+ return "未知";
|
|
|
+ }
|
|
|
+ return switch (deviceType) {
|
|
|
+ case 1 -> "静态柜";
|
|
|
+ case 2 -> "动态柜";
|
|
|
+ case 3 -> "全部柜";
|
|
|
+ default -> "未知类型(" + deviceType + ")";
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算总层数
|
|
|
+ * 从左右门的JSON数据中解析出实际层数
|
|
|
+ *
|
|
|
+ * @param template 层模版
|
|
|
+ * @return 总层数
|
|
|
+ */
|
|
|
+ private Integer calculateTotalFloors(LayerTemplate template) {
|
|
|
+ int leftFloors = parseFloorCount(template.getLeftFloors());
|
|
|
+ int rightFloors = parseFloorCount(template.getRightFloors());
|
|
|
+ return leftFloors + rightFloors;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从JSON字符串中解析层数量
|
|
|
+ *
|
|
|
+ * @param floorsJson JSON格式的层数据
|
|
|
+ * @return 层数量
|
|
|
+ */
|
|
|
+ private int parseFloorCount(String floorsJson) {
|
|
|
+ if (floorsJson == null || floorsJson.isEmpty()) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ List<FloorConfig> floors = objectMapper.readValue(floorsJson,
|
|
|
+ new TypeReference<List<FloorConfig>>() {});
|
|
|
+ return floors.size();
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.debug("解析层数失败,返回0", e);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化JSON用于展示(美化输出)
|
|
|
+ *
|
|
|
+ * @param jsonStr JSON字符串
|
|
|
+ * @return 美化后的JSON字符串
|
|
|
+ */
|
|
|
+ private String formatJsonForDisplay(String jsonStr) {
|
|
|
+ try {
|
|
|
+ Object json = objectMapper.readValue(jsonStr, Object.class);
|
|
|
+ return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ log.debug("JSON格式化失败,返回原始值", e);
|
|
|
+ return jsonStr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化日期时间
|
|
|
+ *
|
|
|
+ * @param dateTime 日期时间
|
|
|
+ * @return 格式化后的字符串
|
|
|
+ */
|
|
|
+ private String formatDateTime(LocalDateTime dateTime) {
|
|
|
+ if (dateTime == null) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化耗时显示
|
|
|
+ *
|
|
|
+ * @param costTimeMs 耗时(毫秒)
|
|
|
+ * @return 可读的时间字符串
|
|
|
+ */
|
|
|
+ private String formatCostTime(long costTimeMs) {
|
|
|
+ if (costTimeMs < 1000) {
|
|
|
+ return costTimeMs + "ms";
|
|
|
+ } else if (costTimeMs < 60000) {
|
|
|
+ return String.format("%.2fs", costTimeMs / 1000.0);
|
|
|
+ } else {
|
|
|
+ return String.format("%.2fmin", costTimeMs / 60000.0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|