| | |
| | | package com.zy.asrs.service.impl; |
| | | |
| | | import com.zy.asrs.mapper.BasMapMapper; |
| | | import com.zy.asrs.entity.BasMap; |
| | | import com.zy.asrs.service.BasMapService; |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | |
| | | import java.util.List; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import com.core.common.Cools; |
| | | import com.core.exception.CoolException; |
| | | import com.zy.asrs.entity.BasMap; |
| | | import com.zy.asrs.entity.LocMast; |
| | | import com.zy.asrs.mapper.BasMapMapper; |
| | | import com.zy.asrs.service.BasMapService; |
| | | import com.zy.asrs.service.LocMastService; |
| | | import com.zy.asrs.utils.Utils; |
| | | import com.zy.common.utils.NavigateSolution; |
| | | import com.zy.common.utils.RedisUtil; |
| | | import com.zy.core.News; |
| | | import com.zy.core.enums.RedisKeyType; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Service("basMapService") |
| | | public class BasMapServiceImpl extends ServiceImpl<BasMapMapper, BasMap> implements BasMapService { |
| | | |
| | | @Autowired |
| | | private LocMastService locMastService; |
| | | @Autowired |
| | | private RedisUtil redisUtil; |
| | | |
| | | // 地图拓扑变化后,这些可达性和站点选择结果都可能过期,必须整体失效。 |
| | | private static final String[] MAP_DERIVED_CACHE_PREFIXES = { |
| | | RedisKeyType.STATION_REACHABLE_CACHE.key, |
| | | RedisKeyType.PRECOMPUTE_IN_TASK_ROW_CACHE.key, |
| | | RedisKeyType.IN_STATION_ROUTE_CACHE.key, |
| | | RedisKeyType.OUT_STATION_ROUTE_CACHE.key |
| | | }; |
| | | |
| | | @Override |
| | | public BasMap selectLatestMap(Integer lev) { |
| | |
| | | |
| | | @Override |
| | | public boolean deleteByLev(Integer lev) { |
| | | return this.baseMapper.deleteByLev(lev); |
| | | boolean deleted = this.baseMapper.deleteByLev(lev); |
| | | refreshMapRuntimeCaches(Collections.singletonList(lev)); |
| | | return deleted; |
| | | } |
| | | |
| | | @Override |
| | |
| | | return this.baseMapper.selectList(new QueryWrapper<>()).stream().map(BasMap::getLev).collect(Collectors.toList()); |
| | | } |
| | | |
| | | @Override |
| | | public IPage<BasMap> pageLight(Page<BasMap> page, QueryWrapper<BasMap> wrapper) { |
| | | QueryWrapper<BasMap> queryWrapper = wrapper == null ? new QueryWrapper<>() : wrapper; |
| | | queryWrapper.select("id", "create_time", "update_time", "lev", "base_row", "base_row_code", "base_bay", "base_bay_code"); |
| | | return this.baseMapper.selectPage(page, queryWrapper); |
| | | } |
| | | |
| | | @Override |
| | | public BasMap selectPayloadById(Integer id) { |
| | | return this.baseMapper.selectPayloadById(id); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional |
| | | public void saveMapPayloadInBatches(Integer lev, String data, String originData, Date updateTime) { |
| | | if (lev == null || lev <= 0) { |
| | | throw new CoolException("楼层不能为空"); |
| | | } |
| | | Date now = updateTime == null ? new Date() : updateTime; |
| | | BasMap basMap = this.getOne(new QueryWrapper<BasMap>().select("id", "data").eq("lev", lev)); |
| | | boolean existingMap = basMap != null; |
| | | if (basMap == null) { |
| | | basMap = insertLightMap(lev, now); |
| | | } |
| | | |
| | | if (existingMap) { |
| | | updateLastDataOnly(basMap.getId(), basMap.getData()); |
| | | } |
| | | updateDataOnly(basMap.getId(), data, now); |
| | | updateOriginDataOnly(basMap.getId(), originData); |
| | | } |
| | | |
| | | private BasMap insertLightMap(Integer lev, Date now) { |
| | | BasMap insertMap = new BasMap(); |
| | | insertMap.setLev(lev); |
| | | insertMap.setCreateTime(now); |
| | | insertMap.setUpdateTime(now); |
| | | if (!this.save(insertMap)) { |
| | | throw new CoolException("地图基础信息保存失败"); |
| | | } |
| | | return insertMap; |
| | | } |
| | | |
| | | private void updateLastDataOnly(Integer id, String lastData) { |
| | | UpdateWrapper<BasMap> updateWrapper = new UpdateWrapper<>(); |
| | | updateWrapper.eq("id", id) |
| | | .set("last_data", lastData); |
| | | if (!this.update(updateWrapper)) { |
| | | throw new CoolException("地图历史数据保存失败"); |
| | | } |
| | | } |
| | | |
| | | private void updateDataOnly(Integer id, String data, Date updateTime) { |
| | | UpdateWrapper<BasMap> updateWrapper = new UpdateWrapper<>(); |
| | | updateWrapper.eq("id", id) |
| | | .set("data", data) |
| | | .set("update_time", updateTime); |
| | | if (!this.update(updateWrapper)) { |
| | | throw new CoolException("地图运行数据保存失败"); |
| | | } |
| | | } |
| | | |
| | | private void updateOriginDataOnly(Integer id, String originData) { |
| | | UpdateWrapper<BasMap> updateWrapper = new UpdateWrapper<>(); |
| | | updateWrapper.eq("id", id) |
| | | .set("origin_data", originData); |
| | | if (!this.update(updateWrapper)) { |
| | | throw new CoolException("地图编辑数据保存失败"); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void refreshMapRuntimeCaches(List<Integer> levList) { |
| | | redisUtil.del(RedisKeyType.LOC_MAP_BASE.key); |
| | | redisUtil.del(RedisKeyType.LOC_MAST_MAP_LIST.key); |
| | | clearMapDerivedRedisCaches(); |
| | | if (levList == null || levList.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | LinkedHashSet<Integer> distinctLevSet = new LinkedHashSet<>(levList); |
| | | for (Integer lev : distinctLevSet) { |
| | | if (lev == null) { |
| | | continue; |
| | | } |
| | | NavigateSolution.clearMapCache(lev); |
| | | if (!hasMapLev(lev)) { |
| | | continue; |
| | | } |
| | | refreshNavigateMapCache(lev); |
| | | } |
| | | } |
| | | |
| | | private void clearMapDerivedRedisCaches() { |
| | | for (String keyPrefix : MAP_DERIVED_CACHE_PREFIXES) { |
| | | redisUtil.deleteByPrefix(keyPrefix); |
| | | } |
| | | } |
| | | |
| | | private boolean hasMapLev(Integer lev) { |
| | | if (lev == null) { |
| | | return false; |
| | | } |
| | | return this.count(new QueryWrapper<BasMap>().eq("lev", lev)) > 0; |
| | | } |
| | | |
| | | private void refreshNavigateMapCache(Integer lev) { |
| | | try { |
| | | NavigateSolution.refreshMapCache(lev); |
| | | } catch (Exception e) { |
| | | News.error("地图运行缓存刷新失败,lev={}", lev, e); |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional |
| | | public int syncLocMastByMap(Integer lev) { |
| | | if (lev == null || lev <= 0) { |
| | | throw new CoolException("请输入有效楼层"); |
| | | } |
| | | |
| | | List<Integer> locLevList = new ArrayList<>(locMastService.getLevList()); |
| | | if (Cools.isEmpty(locLevList) || !locLevList.contains(lev)) { |
| | | throw new CoolException("第" + lev + "层暂无库位数据,请先初始化库位"); |
| | | } |
| | | |
| | | List<BasMap> basMapList = this.list(new QueryWrapper<BasMap>().orderByAsc("lev")); |
| | | if (Cools.isEmpty(basMapList)) { |
| | | throw new CoolException("请先初始化地图"); |
| | | } |
| | | |
| | | Map<Integer, BasMap> basMapByLev = new LinkedHashMap<>(); |
| | | for (BasMap basMap : basMapList) { |
| | | if (basMap != null && basMap.getLev() != null) { |
| | | basMapByLev.put(basMap.getLev(), basMap); |
| | | } |
| | | } |
| | | BasMap fallbackMap = basMapByLev.size() == 1 ? basMapList.get(0) : null; |
| | | |
| | | BasMap basMap = basMapByLev.get(lev); |
| | | if (basMap == null) { |
| | | basMap = fallbackMap; |
| | | } |
| | | if (basMap == null) { |
| | | throw new CoolException("第" + lev + "层缺少地图,无法同步locType"); |
| | | } |
| | | |
| | | List<TargetLocMeta> targetList = buildTargetLocMeta(basMap, lev); |
| | | List<LocMast> currentList = new ArrayList<>(locMastService.selectLocByLev(lev)); |
| | | if (targetList.size() != currentList.size()) { |
| | | throw new CoolException("第" + lev + "层地图货架数(" + targetList.size() + ")与现有库位数(" + currentList.size() + ")不一致,无法同步locType"); |
| | | } |
| | | |
| | | int updatedCount = syncLevelLocType(lev, currentList, targetList); |
| | | refreshLocMastMapListCache(); |
| | | redisUtil.del(RedisKeyType.LOC_MAP_BASE.key); |
| | | return updatedCount; |
| | | } |
| | | |
| | | private int syncLevelLocType(Integer lev, List<LocMast> currentList, List<TargetLocMeta> targetList) { |
| | | Map<String, TargetLocMeta> targetByLocNo = new LinkedHashMap<>(); |
| | | for (TargetLocMeta target : targetList) { |
| | | if (targetByLocNo.put(target.locNo, target) != null) { |
| | | throw new CoolException("第" + lev + "层存在重复库位号:" + target.locNo); |
| | | } |
| | | } |
| | | |
| | | Date now = new Date(); |
| | | int updatedCount = 0; |
| | | for (LocMast current : currentList) { |
| | | TargetLocMeta target = targetByLocNo.get(current.getLocNo()); |
| | | if (target == null) { |
| | | throw new CoolException("第" + lev + "层地图中未找到库位号:" + current.getLocNo()); |
| | | } |
| | | if (java.util.Objects.equals(current.getLocType(), target.locType)) { |
| | | continue; |
| | | } |
| | | |
| | | UpdateWrapper<LocMast> updateWrapper = new UpdateWrapper<>(); |
| | | updateWrapper.eq("loc_no", current.getLocNo()) |
| | | .set("loc_type", target.locType) |
| | | .set("modi_time", now); |
| | | if (!locMastService.update(null, updateWrapper)) { |
| | | throw new CoolException("第" + lev + "层库位locType同步失败:" + current.getLocNo()); |
| | | } |
| | | updatedCount++; |
| | | } |
| | | return updatedCount; |
| | | } |
| | | |
| | | private void refreshLocMastMapListCache() { |
| | | List<LocMast> locMastList = locMastService.list(new QueryWrapper<LocMast>().eq("lev1", 1)); |
| | | redisUtil.set(RedisKeyType.LOC_MAST_MAP_LIST.key, JSON.toJSONString(locMastList), 60 * 60 * 24); |
| | | } |
| | | |
| | | private List<TargetLocMeta> buildTargetLocMeta(BasMap basMap, Integer targetLev) { |
| | | if (basMap == null || Cools.isEmpty(basMap.getData())) { |
| | | throw new CoolException("第" + targetLev + "层地图数据为空,无法同步locType"); |
| | | } |
| | | |
| | | List<List<JSONObject>> dataList; |
| | | try { |
| | | dataList = JSON.parseObject(basMap.getData(), List.class); |
| | | } catch (Exception e) { |
| | | throw new CoolException("第" + targetLev + "层地图数据格式错误,无法同步locType"); |
| | | } |
| | | if (Cools.isEmpty(dataList)) { |
| | | throw new CoolException("第" + targetLev + "层地图数据为空,无法同步locType"); |
| | | } |
| | | |
| | | List<TargetLocMeta> targetList = new ArrayList<>(); |
| | | int initRow = 1; |
| | | for (int mapX = 0; mapX < dataList.size(); mapX++) { |
| | | List<JSONObject> row = dataList.get(mapX); |
| | | if (row == null) { |
| | | continue; |
| | | } |
| | | |
| | | int initBay = -1; |
| | | for (int mapY = 0; mapY < row.size(); mapY++) { |
| | | JSONObject cell = row.get(mapY); |
| | | if (cell == null || !"shelf".equals(cell.getString("type"))) { |
| | | continue; |
| | | } |
| | | |
| | | if (initBay == -1) { |
| | | initBay = 2; |
| | | } |
| | | |
| | | String value = cell.getString("value"); |
| | | int userConfigRow = -1; |
| | | int userConfigBay = -1; |
| | | try { |
| | | String[] split = value.split("-"); |
| | | userConfigRow = Integer.parseInt(split[0]); |
| | | userConfigBay = Integer.parseInt(split[1]); |
| | | } catch (Exception ignored) { |
| | | } |
| | | if (userConfigBay != -1) { |
| | | initRow = userConfigRow; |
| | | initBay = userConfigBay; |
| | | } |
| | | |
| | | targetList.add(new TargetLocMeta( |
| | | Utils.getLocNo(initRow, initBay, targetLev), |
| | | Utils.getLocNo(mapX, mapY, targetLev) |
| | | )); |
| | | initBay++; |
| | | } |
| | | if (initBay != -1) { |
| | | initRow++; |
| | | } |
| | | } |
| | | return targetList; |
| | | } |
| | | |
| | | private static final class TargetLocMeta { |
| | | private final String locNo; |
| | | private final String locType; |
| | | |
| | | private TargetLocMeta(String locNo, String locType) { |
| | | this.locNo = locNo; |
| | | this.locType = locType; |
| | | } |
| | | } |
| | | } |