| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016 |
- 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.GoodsItem;
- 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.DeviceInventory;
- import com.haha.entity.InventoryLog;
- import com.haha.entity.LayerTemplate;
- import com.haha.entity.Product;
- import com.haha.mapper.LayerTemplateMapper;
- import com.haha.sdk.HahaClient;
- import com.haha.sdk.api.GoodsApi;
- import com.haha.service.DeviceInventoryService;
- import com.haha.service.InventoryLogService;
- import com.haha.service.LayerTemplateService;
- import com.haha.service.ProductService;
- import lombok.RequiredArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import java.math.BigDecimal;
- import java.time.LocalDateTime;
- import java.time.format.DateTimeFormatter;
- import java.util.ArrayList;
- 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();
- @Autowired
- private DeviceInventoryService deviceInventoryService;
- @Autowired
- private ProductService productService;
- /**
- * 分页查询层模版列表
- * 支持按设备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: 从层模版数据初始化库存(仅对尚不存在的商品创建 stock=0 记录)
- try {
- initInventoryFromTemplate(deviceId, apiData);
- } catch (Exception e) {
- log.error("层模版同步后初始化库存失败: deviceId={}", deviceId, e);
- }
- // 步骤7: 更新同步状态为"已同步"并记录时间
- 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);
- }
- }
- /**
- * 获取设备商品配置(转换为前端格式)
- */
- @Override
- public Map<String, Object> getProductConfig(String deviceId) {
- log.info("获取设备商品配置: deviceId={}", deviceId);
- LayerTemplate template = getByDeviceId(deviceId);
- List<Map<String, Object>> floors = new ArrayList<>();
- if (template != null && template.getLeftFloors() != null && !template.getLeftFloors().isEmpty()) {
- try {
- List<FloorConfig> leftFloors = objectMapper.readValue(
- template.getLeftFloors(), new TypeReference<List<FloorConfig>>() {});
- int totalFloors = template.getShelfNum() != null ? template.getShelfNum() : leftFloors.size();
- for (FloorConfig config : leftFloors) {
- int floorNum = config.getFloor() != null ? config.getFloor() : 1;
- Map<String, Object> floor = new HashMap<>();
- floor.put("floorNumber", floorNum);
- floor.put("floorLabel", buildFloorLabel(floorNum, totalFloors));
- floor.put("floorName", "第" + floorNum + "层");
- List<Map<String, Object>> slots = new ArrayList<>();
- List<GoodsItem> goods = config.getGoods();
- if (goods != null) {
- for (int i = 0; i < goods.size(); i++) {
- GoodsItem item = goods.get(i);
- Map<String, Object> slot = new HashMap<>();
- slot.put("slotId", floorNum + "-" + (i + 1));
- if (item != null && item.getKey() != null && !item.getKey().isEmpty()) {
- slot.put("productId", item.getKey());
- slot.put("productName", item.getLabel());
- slot.put("productImage", normalizeImageUrl(item.getPic()));
- slot.put("stock", item.getStock() != null ? item.getStock() : 0);
- if (item.getC_price() != null && !item.getC_price().isEmpty()) {
- slot.put("price", new BigDecimal(item.getC_price()).doubleValue());
- }
- }
- slots.add(slot);
- }
- }
- floor.put("slots", slots);
- floors.add(floor);
- }
- } catch (Exception e) {
- log.error("解析层数据失败, deviceId={}", deviceId, e);
- }
- }
- Map<String, Object> result = new HashMap<>();
- result.put("deviceId", deviceId);
- result.put("floors", floors);
- return result;
- }
- @Override
- public Map<String, Object> getDeviceProducts(String deviceId) {
- log.info("获取设备商品陈列信息: deviceId={}", deviceId);
- LayerTemplate template = getByDeviceId(deviceId);
- if (template == null) {
- return null;
- }
- Map<String, Object> result = new HashMap<>();
- result.put("templateName", template.getTemplateName());
- result.put("deviceId", template.getDeviceId());
- result.put("shelfNum", template.getShelfNum());
- result.put("deviceType", template.getDeviceType());
- // 解析左门层数据
- if (template.getLeftFloors() != null && !template.getLeftFloors().isEmpty()) {
- try {
- List<FloorConfig> leftFloors = objectMapper.readValue(
- template.getLeftFloors(), new TypeReference<List<FloorConfig>>() {});
- result.put("leftFloors", leftFloors);
- } catch (Exception e) {
- log.warn("解析左门层数据失败, deviceId={}", deviceId, e);
- result.put("leftFloors", List.of());
- }
- } else {
- result.put("leftFloors", List.of());
- }
- // 解析右门层数据
- if (template.getRightFloors() != null && !template.getRightFloors().isEmpty()) {
- try {
- List<FloorConfig> rightFloors = objectMapper.readValue(
- template.getRightFloors(), new TypeReference<List<FloorConfig>>() {});
- result.put("rightFloors", rightFloors);
- } catch (Exception e) {
- log.warn("解析右门层数据失败, deviceId={}", deviceId, e);
- result.put("rightFloors", List.of());
- }
- } else {
- result.put("rightFloors", List.of());
- }
- return result;
- }
- /**
- * 保存设备商品配置
- */
- @Override
- public boolean saveProductConfig(String deviceId, List<Map<String, Object>> floors) {
- log.info("保存设备商品配置: deviceId={}, floors.size={}", deviceId, floors != null ? floors.size() : 0);
- LayerTemplate template = getByDeviceId(deviceId);
- if (template == null) {
- throw new BusinessException(404, "设备未配置层模板,无法保存");
- }
- if (floors == null || floors.isEmpty()) {
- throw new BusinessException(400, "楼层数据不能为空");
- }
- List<FloorConfig> leftFloors = new ArrayList<>();
- for (Map<String, Object> floor : floors) {
- FloorConfig config = new FloorConfig();
- Object floorNumber = floor.get("floorNumber");
- config.setFloor(floorNumber instanceof Number ? ((Number) floorNumber).intValue() : 1);
- @SuppressWarnings("unchecked")
- List<Map<String, Object>> slots = (List<Map<String, Object>>) floor.get("slots");
- List<GoodsItem> goods = new ArrayList<>();
- if (slots != null) {
- for (Map<String, Object> slot : slots) {
- GoodsItem item = new GoodsItem();
- String productId = (String) slot.get("productId");
- if (productId != null && !productId.isEmpty()) {
- item.setKey(productId);
- item.setLabel((String) slot.get("productName"));
- item.setPic((String) slot.get("productImage"));
- Object stock = slot.get("stock");
- item.setStock(stock instanceof Number ? ((Number) stock).intValue() : 0);
- Object price = slot.get("price");
- if (price instanceof Number) {
- item.setC_price(price.toString());
- }
- }
- goods.add(item);
- }
- }
- config.setGoods(goods);
- leftFloors.add(config);
- }
- LayerTemplateUpdateDTO dto = new LayerTemplateUpdateDTO();
- dto.setLeftFloors(leftFloors);
- update(template.getId(), dto);
- // 推送层模板到哈哈平台
- pushLayerTemplateToHaha(deviceId, leftFloors);
- return true;
- }
- /**
- * 推送层模板配置到哈哈平台
- */
- private void pushLayerTemplateToHaha(String deviceId, List<FloorConfig> leftFloors) {
- try {
- Map<String, Object> products = new HashMap<>();
- products.put("left", leftFloors);
- products.put("right", List.of());
- String productsJson = objectMapper.writeValueAsString(products);
- hahaClient.getGoodsApi().updateLayerTemplate(deviceId, productsJson);
- updateSyncStatus(deviceId, SyncConstants.STATUS_SYNCED, LocalDateTime.now());
- log.info("层模板已同步到哈哈平台 - deviceId: {}", deviceId);
- } catch (Exception e) {
- log.error("推送层模板到哈哈平台失败 - deviceId: {}, error: {}", deviceId, e.getMessage(), e);
- }
- }
- /**
- * 构建楼层标签(顶层/底层标识)
- */
- private String buildFloorLabel(int floorNum, int totalFloors) {
- String label = "第" + floorNum + "层";
- if (floorNum == 1) {
- label += "\n(顶层)";
- } else if (floorNum == totalFloors) {
- label += "\n(底层)";
- }
- return label;
- }
- /**
- * 标准化图片URL
- * 如果图片链接已经是完整的URL(包含http://或https://),则保持不变
- * 否则添加配置的图片域名前缀
- */
- private static final String IMAGE_DOMAIN_PREFIX = "https://img.hahabianli.com/";
- private String normalizeImageUrl(String picUrl) {
- if (picUrl == null || picUrl.isEmpty()) {
- return picUrl;
- }
- if (picUrl.startsWith("http://") || picUrl.startsWith("https://")) {
- return picUrl;
- }
- return IMAGE_DOMAIN_PREFIX + picUrl;
- }
- /**
- * 从层模版API数据中提取商品编码,为不存在的商品创建库存记录(stock=0)
- * 仅在库存记录不存在时创建,已存在的记录不受影响
- */
- private void initInventoryFromTemplate(String deviceId, Map<String, Object> apiData) {
- // 提取所有商品编码
- java.util.Set<String> productCodes = new java.util.HashSet<>();
- if (apiData.containsKey("products")) {
- @SuppressWarnings("unchecked")
- Map<String, Object> products = (Map<String, Object>) apiData.get("products");
- extractCodesFromFloors(products.get("left"), productCodes);
- extractCodesFromFloors(products.get("right"), productCodes);
- }
- if (productCodes.isEmpty()) {
- log.info("层模版中无商品配置,跳过库存初始化: deviceId={}", deviceId);
- return;
- }
- log.info("从层模版提取到 {} 个商品编码,开始初始化库存: deviceId={}", productCodes.size(), deviceId);
- int created = 0;
- int skipped = 0;
- for (String code : productCodes) {
- try {
- Product product = productService.getProductByCode(code);
- if (product == null) {
- log.debug("商品编码在本地库中不存在,跳过: code={}, deviceId={}", code, deviceId);
- skipped++;
- continue;
- }
- DeviceInventory existing = deviceInventoryService.getByDeviceAndProduct(deviceId, product.getId());
- if (existing != null) {
- log.debug("库存记录已存在,跳过: deviceId={}, productCode={}", deviceId, code);
- skipped++;
- continue;
- }
- DeviceInventory inventory = new DeviceInventory();
- inventory.setDeviceId(deviceId);
- inventory.setProductId(product.getId());
- inventory.setProductCode(code);
- inventory.setProductName(product.getName());
- inventory.setStock(0);
- inventory.setWarningThreshold(5);
- inventory.setCreateTime(LocalDateTime.now());
- inventory.setUpdateTime(LocalDateTime.now());
- deviceInventoryService.save(inventory);
- created++;
- log.info("初始化库存记录: deviceId={}, productCode={}, productName={}, stock=0",
- deviceId, code, product.getName());
- } catch (Exception e) {
- log.error("初始化单条库存记录失败: deviceId={}, productCode={}", deviceId, code, e);
- }
- }
- log.info("库存初始化完成: deviceId={}, 创建={}, 跳过={}", deviceId, created, skipped);
- }
- /**
- * 从层数据(left/right floors)中提取商品编码
- */
- private void extractCodesFromFloors(Object floorsObj, java.util.Set<String> codeSet) {
- if (floorsObj == null) return;
- try {
- List<FloorConfig> floors = objectMapper.convertValue(floorsObj,
- new TypeReference<List<FloorConfig>>() {});
- if (floors != null) {
- for (FloorConfig floor : floors) {
- if (floor.getGoods() != null) {
- for (GoodsItem goods : floor.getGoods()) {
- if (goods.getKey() != null && !goods.getKey().isEmpty()) {
- codeSet.add(goods.getKey());
- }
- }
- }
- }
- }
- } catch (Exception e) {
- log.warn("解析层商品数据失败: {}", e.getMessage());
- }
- }
- }
|