package com.ruoyi.app.mendian; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.app.mendian.dto.FoodSearchOutput; import com.ruoyi.app.order.OrderInvoiceService; import com.ruoyi.app.utils.ImageCompressUtils; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.system.domain.*; import com.ruoyi.system.dto.menu.StoreMenuCopyExecuteRequest; import com.ruoyi.system.dto.menu.StoreMenuCopyPreviewRequest; import com.ruoyi.system.mapper.PosStoreMapper; import com.ruoyi.system.service.*; import com.ruoyi.system.utils.Auth; import com.ruoyi.system.utils.AuthContext; import com.ruoyi.system.utils.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import jakarta.servlet.http.HttpServletResponse; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; /** * foodController * * @author ruoyi * @date 2023-05-21 */ @RestController @RequestMapping("/chanting/food") public class PosFoodController extends BaseController { @Autowired //商品 private IPosFoodService posFoodService; @Autowired //门店 private IPosStoreService posStoreService; @Autowired //商品分类 private IPosFenleiService posFenleiService; @Autowired //用户 private IInfoUserService infoUserService; @Autowired //店铺Mapper private PosStoreMapper posStoreMapper; @Autowired private IPosReviewService posReviewService; @Autowired private IOperatingHoursService operatingHoursService; @Autowired private IPosOrderService posOrderService; @Autowired private PosStoreEnrichService posStoreEnrichService; @Autowired //规格组 private IFoodSpecsService foodSpecsService; @Autowired //规格值 private IFoodSpecsValueService foodSpecsValueService; @Autowired //商品-规格关联 private IFoodSpecRelationService foodSpecRelationService; @Autowired //订单电子发票(门店是否能开票) private OrderInvoiceService orderInvoiceService; @Autowired private MerchantStoreAccessService merchantStoreAccessService; @Autowired private StoreMenuCopyService storeMenuCopyService; //删除商品 @Anonymous @Auth(session = true) @GetMapping("/delefood") public AjaxResult delefood(@RequestHeader String token, @RequestParam String id) { merchantStoreAccessService.requireFoodAccess( AuthContext.requireUserId(), Long.valueOf(id)); return toAjax(posFoodService.deletePosFoodById(Long.valueOf(id))); } //推荐商品 @Anonymous @Auth(session = true) @PutMapping("/changerecommend") public AjaxResult changerecommend(@RequestHeader String token, @RequestBody PosFood posFood) { PosFood existing = merchantStoreAccessService.requireFoodAccess( AuthContext.requireUserId(), posFood.getId()); posFood.setMdid(existing.getMdid()); return toAjax(posFoodService.saveOrUpdate(posFood)); } /** * 添加或修改商品 */ @Anonymous @Auth(session = true) @PostMapping("/setposfood") public AjaxResult setposfood(@RequestHeader String token, @RequestBody PosFood posFood) { Long userId = AuthContext.requireUserId(); if (posFood.getId() == null) { merchantStoreAccessService.requireStoreAccess(userId, posFood.getMdid()); } else { PosFood existing = merchantStoreAccessService.requireFoodAccess(userId, posFood.getId()); posFood.setMdid(existing.getMdid()); } Boolean org = posFoodService.saveOrUpdate(posFood); if (!org) { return error(); } handleFoodSpec(posFood); return success(); } @Anonymous @Auth(session = true) @PostMapping("/menu-copy/preview") public AjaxResult menuCopyPreview(@RequestHeader String token, @RequestBody StoreMenuCopyPreviewRequest request) { return success(storeMenuCopyService.preview(AuthContext.requireUserId(), request)); } @Anonymous @Auth(session = true) @PostMapping("/menu-copy/execute") public AjaxResult menuCopyExecute(@RequestHeader String token, @RequestBody StoreMenuCopyExecuteRequest request) { return success(storeMenuCopyService.execute(AuthContext.requireUserId(), request)); } /** * (h5页面使用)通过分类查询商品列表 */ @Anonymous @GetMapping("/h5Getidlist") public AjaxResult h5Getidlist(@RequestParam Integer id, @RequestParam(defaultValue = "") String language, @RequestParam(required = false) String stackingUp) { JwtUtil jwtUtil = new JwtUtil(); // String ids = jwtUtil.getusid(token); // InfoUser user = infoUserService.getById(ids); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("fl_id", id); // if(!user.getUserType().equals("1")){ // queryWrapper.eq("to_examine","1"); // } if (stackingUp != null && !"".equals(stackingUp)) { queryWrapper.eq("stacking_up", stackingUp); } String lang = "3"; if (!"".equals(language)) { lang = language; } queryWrapper.eq("language", lang); List list = posFoodService.list(queryWrapper); List __foodIds = list.stream().map(PosFood::getId).collect(Collectors.toList()); Map> __specMap = loadFoodSpecsMap(__foodIds); JSONArray all = new JSONArray(); for (int i = 0; i < list.size(); i++) { JSONObject org = new JSONObject(); org.put("id", list.get(i).getId()); org.put("fenlei", posFenleiService.getById(list.get(i).getFlId())); org.put("store", posStoreService.getById(list.get(i).getMdid())); org.put("name", list.get(i).getName()); org.put("image", list.get(i).getImage()); org.put("price", list.get(i).getPrice()); org.put("introduce", list.get(i).getIntroduce()); org.put("recommend", list.get(i).getRecommend()); List __fs = __specMap.getOrDefault(list.get(i).getId(), new ArrayList<>()); org.put("foodSpecs", __fs); org.put("foodSku", __fs.isEmpty() ? JSONArray.parseArray(list.get(i).getFoodSku()) : buildFoodSkuArray(__fs)); org.put("stackingUp", list.get(i).getStackingUp()); org.put("toExamine", list.get(i).getToExamine()); all.add(org); } return success(all); } /** * 通过分类查询商品列表 */ @Anonymous @Auth(session = true) @GetMapping("/getidlist") public AjaxResult getidlist(@RequestHeader String token, @RequestParam Integer id, @RequestParam(defaultValue = "") String language, @RequestParam(required = false) String stackingUp, @RequestParam(required = false) String name) { Long userId = AuthContext.requireUserId(); InfoUser user = infoUserService.getById(userId); PosFenlei category = posFenleiService.getById(id); if (category != null && ("1".equals(user.getUserType()) || "5".equals(user.getUserType()))) { merchantStoreAccessService.requireStoreAccess(userId, category.getMendid()); } QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("fl_id", id); //普通用户显示审核通过的 if("0".equals(user.getUserType())){ queryWrapper.eq("to_examine", "1"); } queryWrapper.ne("stacking_up", "1"); if (stackingUp != null && !"".equals(stackingUp)) { queryWrapper.eq("stacking_up", stackingUp); } String lang = "3"; if (!"".equals(language)) { lang = language; } queryWrapper.eq("language", lang); if (name != null && !"".equals(name)) { queryWrapper.apply(" BINARY name LIKE CONCAT('%', {0}, '%')", name); } List list = posFoodService.list(queryWrapper); List __foodIds = list.stream().map(PosFood::getId).collect(Collectors.toList()); Map> __specMap = loadFoodSpecsMap(__foodIds); JSONArray all = new JSONArray(); for (int i = 0; i < list.size(); i++) { JSONObject org = new JSONObject(); org.put("id", list.get(i).getId()); org.put("fenlei", posFenleiService.getById(list.get(i).getFlId())); PosStore store = posStoreService.getById(list.get(i).getMdid()); fillStallLocation(store); org.put("store", store); org.put("canInvoice", orderInvoiceService.canInvoice(list.get(i).getMdid())); org.put("name", list.get(i).getName()); org.put("image", list.get(i).getImage()); org.put("price", list.get(i).getPrice()); org.put("introduce", list.get(i).getIntroduce()); org.put("recommend", list.get(i).getRecommend()); List __fs = __specMap.getOrDefault(list.get(i).getId(), new ArrayList<>()); org.put("foodSpecs", __fs); org.put("foodSku", __fs.isEmpty() ? JSONArray.parseArray(list.get(i).getFoodSku()) : buildFoodSkuArray(__fs)); org.put("stackingUp", list.get(i).getStackingUp()); org.put("toExamine", list.get(i).getToExamine()); all.add(org); } return success(all); } /** * 查询商品详情 */ @Anonymous @GetMapping("/getfood") public AjaxResult getfood(@RequestParam Integer id) { JSONObject org = new JSONObject(); PosFood posFood = posFoodService.getById(id); List __foodIds = new ArrayList<>(); __foodIds.add(posFood.getId()); Map> __specMap = loadFoodSpecsMap(__foodIds); List __fs = __specMap.getOrDefault(posFood.getId(), new ArrayList<>()); org.put("id", posFood.getId()); org.put("fenlei", posFenleiService.getById(posFood.getFlId())); PosStore store = posStoreService.getById(posFood.getMdid()); fillStallLocation(store); org.put("store", store); org.put("canInvoice", orderInvoiceService.canInvoice(posFood.getMdid())); org.put("name", posFood.getName()); org.put("image", posFood.getImage()); org.put("price", posFood.getPrice()); org.put("introduce", posFood.getIntroduce()); org.put("recommend", posFood.getRecommend()); org.put("stackingUp", posFood.getStackingUp()); org.put("foodSpecs", __fs); org.put("foodSku", __fs.isEmpty() ? JSONArray.parseArray(posFood.getFoodSku()) : buildFoodSkuArray(__fs)); return success(org); } /** * 搜索 */ @Anonymous @GetMapping("/searchfor") public AjaxResult searchfor(@RequestParam String keyword, @RequestParam(defaultValue = "") String language) { if (keyword.equals("")) { return error(MessageUtils.message("no.tip.lock.keywork")); } JSONArray all = new JSONArray(); JSONObject object = new JSONObject(); QueryWrapper query = new QueryWrapper<>(); String lang = "3"; if (!"".equals(language)) { lang = language; } query.eq("language", lang); query.eq("to_examine", "1"); // query.like("name",keyword); query.apply("BINARY name LIKE CONCAT('%', {0}, '%')", keyword); List folist = posFoodService.list(query); QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("off_shelf", "0") // .and(i->i.like("pos_name", keyword).or().like("brief_introduction",keyword)); .and(i -> i.apply("BINARY pos_name LIKE CONCAT('%', {0}, '%')", keyword).or().apply("BINARY brief_introduction LIKE CONCAT('%', {0}, '%')", keyword)); List stlist = posStoreService.list(wrapper); for (int i = 0; i < folist.size(); i++) { PosStore store = posStoreService.getById(folist.get(i).getMdid()); if (store.getOffShelf().equals("0")) { JSONObject org = new JSONObject(); org.put("id", folist.get(i).getId()); org.put("fenlei", posFenleiService.getById(folist.get(i).getFlId())); org.put("store", posStoreService.getById(folist.get(i).getMdid())); org.put("name", folist.get(i).getName()); org.put("image", folist.get(i).getImage()); org.put("price", folist.get(i).getPrice()); org.put("introduce", folist.get(i).getIntroduce()); org.put("recommend", folist.get(i).getRecommend()); org.put("stackingUp", folist.get(i).getStackingUp()); org.put("foodSku", JSONArray.parseArray(folist.get(i).getFoodSku())); all.add(org); } } object.put("food", all); object.put("store", stlist); return success(object); } /** * 客户端查询美食 * * @param keyword * @param language * @return */ @Anonymous @GetMapping("/foodSearch") public AjaxResult foodSearch(@RequestParam String keyword, @RequestParam Integer pageNum, @RequestParam Integer pageSize, @RequestParam(defaultValue = "") String language) { if (keyword.isEmpty()) { return error(MessageUtils.message("no.tip.lock.keywork")); } // 设置默认分页参数 if (pageNum == null || pageNum < 1) { pageNum = 1; } if (pageSize == null || pageSize < 1) { pageSize = 10; } String lang = "3"; if (!"".equals(language)) { lang = language; } // 创建分页对象 Page page = new Page<>(pageNum, pageSize); // 1. 先通过联表查询获取分页的店铺列表(包含总数) IPage storePage = posStoreMapper.selectStoresByFoodKeyword(page, keyword, lang); List stores = storePage.getRecords(); List result = new ArrayList<>(); Map> reviewMap = Collections.emptyMap(); Map orderCountMap = Collections.emptyMap(); List hourslist=new ArrayList<>(); // 2. 一次性查询所有相关商品(避免循环查询数据库) if (!stores.isEmpty()) { // 提取所有店铺ID List storeIds = stores.stream() .map(store -> store.getId().longValue()) .collect(Collectors.toList()); // 一次性查询所有店铺下匹配关键词的商品 QueryWrapper foodQuery = new QueryWrapper<>(); foodQuery.in("mdid", storeIds) .eq("language", lang) .eq("to_examine", "1") .apply(" CONCAT(name, ' ') LIKE CONCAT('%', {0}, '%') COLLATE utf8mb4_unicode_ci", keyword); List allFoods = posFoodService.list(foodQuery); { List __foodIds = allFoods.stream().map(PosFood::getId).collect(Collectors.toList()); Map> __specMap = loadFoodSpecsMap(__foodIds); for (PosFood __f : allFoods) { __f.setFoodSpecs(__specMap.getOrDefault(__f.getId(), new ArrayList<>())); } } // 按店铺ID分组商品 Map> foodsByStore = allFoods.stream() .collect(Collectors.groupingBy(PosFood::getMdid)); posStoreEnrichService.enrichStoreList(stores); // 3. 为每个店铺设置对应的商品 for (PosStore store : stores) { FoodSearchOutput output = new FoodSearchOutput(); output.setPosStore(store); // 从分组结果中获取该店铺的商品 List storeFoods = foodsByStore.getOrDefault(store.getId().longValue(), new ArrayList<>()); output.setPosFoodList(storeFoods); result.add(output); } } // 3. 返回分页结果 Map response = new HashMap<>(); response.put("list", result); response.put("total", storePage.getTotal()); return success(response); } /** * 客户端查询门店 * * @param keyword * @param language * @return */ @Anonymous @GetMapping("/storeSearch") public AjaxResult storeSearch(@RequestParam String keyword, @RequestParam Integer pageNum, @RequestParam Integer pageSize, @RequestParam(defaultValue = "") String language) { if (keyword.isEmpty()) { return error(MessageUtils.message("no.tip.lock.keywork")); } QueryWrapper query = new QueryWrapper<>(); // 设置默认分页参数 if (pageNum == null || pageNum < 1) { pageNum = 1; } if (pageSize == null || pageSize < 1) { pageSize = 10; } Page page = new Page<>(pageNum, pageSize); QueryWrapper storeWrapper = new QueryWrapper<>(); String finalKeyword = keyword; storeWrapper.eq("off_shelf", "0") // .and(i->i.like("pos_name", keyword).or().like("brief_introduction",keyword)); .and(i -> i.apply(" CONCAT(pos_name,' ') LIKE CONCAT('%', {0}, '%') COLLATE utf8mb4_unicode_ci", finalKeyword).or().apply(" CONCAT(brief_introduction,' ') LIKE CONCAT('%', {0}, '%') COLLATE utf8mb4_unicode_ci", finalKeyword)); IPage data = posStoreService.page(page, storeWrapper); Map response = new HashMap<>(); List stores=data.getRecords(); posStoreEnrichService.enrichStoreList(stores); response.put("list", stores); response.put("total", data.getTotal()); return success(response); } @Anonymous @Auth(session = true) @GetMapping("/getFoodPageList") public AjaxResult getFoodPageList(@RequestHeader String token, @RequestParam Integer page, @RequestParam Integer size, @RequestParam Long flId, @RequestParam Long mdId, @RequestParam(defaultValue = "") String language, @RequestParam(defaultValue = "") String name) { Long id = AuthContext.requireUserId(); merchantStoreAccessService.requireStoreAccess(id, mdId); IPage stlist = new Page<>(page, size); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(PosFood::getFlId, flId); queryWrapper.eq(PosFood::getMdid, mdId); // language 不为 null 且不为空字符串时才加条件 if (language != null && !"".equals(language)) { queryWrapper.eq(PosFood::getLanguage, language); } // name 不为 null 且不为空字符串时模糊查询 if (name != null && !"".equals(name)) { queryWrapper.like(PosFood::getName, name); } IPage list = posFoodService.page(stlist, queryWrapper); return success(list); } /** * C端扫码:获取摊位商品列表(摊位码扫码入口) */ @Anonymous @GetMapping("/stallFoodList") public AjaxResult stallFoodList(@RequestParam Long storeId, @RequestParam(required = false) Long flId, @RequestParam Integer page, @RequestParam Integer size, @RequestParam(required = false) Long tableId) { IPage foodPage = new Page<>(page, size); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("mdid", storeId); queryWrapper.eq("to_examine", "1"); queryWrapper.eq("stacking_up", "1"); if (flId != null) { queryWrapper.eq("fl_id", flId); } IPage result = posFoodService.page(foodPage, queryWrapper); List __foodIds = result.getRecords().stream().map(PosFood::getId).collect(Collectors.toList()); Map> __specMap = loadFoodSpecsMap(__foodIds); for (PosFood __f : result.getRecords()) { __f.setFoodSpecs(__specMap.getOrDefault(__f.getId(), new ArrayList<>())); } return success(result); } /** * 查询商品列表 */ @Anonymous @GetMapping("/getfoodlist") public AjaxResult getfoodlist(@RequestParam Integer page, @RequestParam Integer size, @RequestParam(defaultValue = "") String language, @RequestParam(defaultValue = "") String keyword) { IPage food = new Page<>(page, size); QueryWrapper queryWrapper = new QueryWrapper<>(); String lang = "3"; if (!"".equals(language)) { lang = language; } queryWrapper.eq("language", lang); queryWrapper.eq("to_examine", "1"); if (!"".equals(keyword)) { queryWrapper.like("name", keyword); } IPage list = posFoodService.page(food, queryWrapper); List foodlist = list.getRecords(); JSONArray all = new JSONArray(); for (int i = 0; i < foodlist.size(); i++) { PosStore store = posStoreService.getById(foodlist.get(i).getMdid()); if (store.getOffShelf().equals("0")) { JSONObject org = new JSONObject(); org.put("id", foodlist.get(i).getId()); org.put("fenlei", posFenleiService.getById(foodlist.get(i).getFlId())); org.put("store", posStoreService.getById(foodlist.get(i).getMdid())); org.put("name", foodlist.get(i).getName()); org.put("image", foodlist.get(i).getImage()); org.put("price", foodlist.get(i).getPrice()); org.put("introduce", foodlist.get(i).getIntroduce()); org.put("recommend", foodlist.get(i).getRecommend()); org.put("stackingUp", foodlist.get(i).getStackingUp()); org.put("foodSku", JSONArray.parseArray(foodlist.get(i).getFoodSku())); all.add(org); } } return success(all); } /** * 查询food列表 */ @PreAuthorize("@ss.hasPermi('chanting:food:list')") @GetMapping("/list") public TableDataInfo list(PosFood posFood) { startPage(); List list = posFoodService.selectPosFoodList(posFood); return getDataTable(list); } /** * 导出food列表 */ @PreAuthorize("@ss.hasPermi('chanting:food:export')") @Log(title = "food", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(HttpServletResponse response, PosFood posFood) { List list = posFoodService.selectPosFoodList(posFood); ExcelUtil util = new ExcelUtil(PosFood.class); util.exportExcel(response, list, MessageUtils.message("no.export.excel.food")); } /** * 获取food详细信息 */ @Anonymous @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable("id") Long id) { return success(posFoodService.selectPosFoodById(id)); } /** * 新增food */ @PreAuthorize("@ss.hasPermi('chanting:food:add')") @Log(title = "food", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@RequestBody PosFood posFood) { int rows = posFoodService.insertPosFood(posFood); if (rows > 0) { handleFoodSpec(posFood); } return toAjax(rows); } /** * 修改food */ @PreAuthorize("@ss.hasPermi('chanting:food:edit')") @Log(title = "food", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@RequestBody PosFood posFood) { int rows = posFoodService.updatePosFood(posFood); if (rows > 0) { handleFoodSpec(posFood); } return toAjax(rows); } /** * 删除food */ @PreAuthorize("@ss.hasPermi('chanting:food:remove')") @Log(title = "food", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public AjaxResult remove(@PathVariable Long[] ids) { return toAjax(posFoodService.deletePosFoodByIds(ids)); } /** * 压缩图片店铺的商品图片 */ @Anonymous @GetMapping("/compressFoodImage") public AjaxResult compressFoodImage(@RequestParam Long mdId, @RequestParam String idsStr) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(PosFood::getMdid, mdId); List ids= new ArrayList<>(); if(!StringUtils.isEmpty(idsStr)){ ids = Arrays.stream(idsStr.split(",")) .map(Long::valueOf) .collect(Collectors.toList()); } queryWrapper.in(!ids.isEmpty(), PosFood::getId, ids); List list = posFoodService.list(queryWrapper); AtomicInteger count = new AtomicInteger(); list.forEach(food -> { String compressImage = ImageCompressUtils.compressImageByWebPath(food.getImage()); if (!compressImage.equals(food.getImage())) { food.setImage(compressImage); PosFood posFood = new PosFood(); posFood.setId(food.getId()); posFood.setImage(compressImage); posFoodService.saveOrUpdate(posFood); count.getAndIncrement(); System.out.println("food成功压缩的图片数量:"+count.get()); logger.info("food成功压缩的图片数量:"+count.get()); } }); return success("成功压缩的 :"+count.get()); } /** * 压缩所有的商品图片 */ @Anonymous @GetMapping("/compressFoodImageAll") public AjaxResult compressFoodImage() { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); List list = posFoodService.list(queryWrapper); AtomicInteger count = new AtomicInteger(); list.forEach(food -> { String compressImage = ImageCompressUtils.compressImageByWebPath(food.getImage()); if (!compressImage.equals(food.getImage())) { food.setImage(compressImage); PosFood posFood = new PosFood(); posFood.setId(food.getId()); posFood.setImage(compressImage); posFoodService.saveOrUpdate(posFood); count.getAndIncrement(); System.out.println("food成功压缩的图片数量:"+count.get()); logger.info("food成功压缩的图片数量:"+count.get()); } }); return success("成功压缩的图片数量:"+count.get()); } /** * 当门店是摊位时,用夜市的经纬度覆盖摊位的经纬度 */ /** * 保存商品规格:sku JSON 写 food_sku(落库) -> 删除旧关联 -> 按 foodSpecs 建立新关联 */ private void handleFoodSpec(PosFood posFood) { if (posFood == null || posFood.getId() == null) { return; } JSONArray sku = posFood.getSku(); if (sku != null) { PosFood upd = new PosFood(); upd.setId(posFood.getId()); upd.setFoodSku(sku.toString()); posFoodService.updateById(upd); posFood.setFoodSku(sku.toString()); } foodSpecRelationService.remove(new LambdaQueryWrapper() .eq(FoodSpecRelation::getFoodId, posFood.getId())); List foodSpecs = posFood.getFoodSpecs(); if (foodSpecs != null && !foodSpecs.isEmpty()) { List rels = foodSpecs.stream() .filter(sp -> sp.getId() != null && sp.getId() > 0) .map(sp -> { FoodSpecRelation r = new FoodSpecRelation(); r.setFoodId(posFood.getId()); r.setSpecsId(sp.getId()); return r; }).collect(Collectors.toList()); if (!rels.isEmpty()) { foodSpecRelationService.saveBatch(rels); } } } /** * 批量查询商品规格(避免 N+1):关联 -> 启用规格组 -> 启用规格值 -> 内存分组 * @return foodId -> 规格组列表(含启用规格值) */ private Map> loadFoodSpecsMap(List foodIds) { Map> result = new HashMap<>(); if (foodIds == null || foodIds.isEmpty()) { return result; } List rels = foodSpecRelationService.list(new LambdaQueryWrapper() .in(FoodSpecRelation::getFoodId, foodIds)); if (rels.isEmpty()) { return result; } Map> foodToSpecIds = rels.stream() .collect(Collectors.groupingBy(FoodSpecRelation::getFoodId, Collectors.mapping(FoodSpecRelation::getSpecsId, Collectors.toList()))); List specsIds = rels.stream().map(FoodSpecRelation::getSpecsId).distinct().collect(Collectors.toList()); List specsList = foodSpecsService.list(new LambdaQueryWrapper() .in(FoodSpecs::getId, specsIds) .eq(FoodSpecs::getIsOpen, true) .eq(FoodSpecs::getIsDelete, false) .orderByAsc(FoodSpecs::getSort)); if (specsList.isEmpty()) { return result; } List enabledSpecIds = specsList.stream().map(FoodSpecs::getId).collect(Collectors.toList()); List values = foodSpecsValueService.list(new LambdaQueryWrapper() .in(FoodSpecsValue::getParentId, enabledSpecIds) .eq(FoodSpecsValue::getIsOpen, true) .orderByAsc(FoodSpecsValue::getId)); Map> valueMap = values.stream() .collect(Collectors.groupingBy(FoodSpecsValue::getParentId)); Map specsMap = specsList.stream().collect(Collectors.toMap(FoodSpecs::getId, sp -> sp)); for (Map.Entry> entry : foodToSpecIds.entrySet()) { List fsList = new ArrayList<>(); for (Long sid : entry.getValue()) { FoodSpecs src = specsMap.get(sid); if (src != null) { FoodSpecs copy = new FoodSpecs(); copy.setId(src.getId()); copy.setTitle(src.getTitle()); copy.setType(src.getType()); copy.setState(src.getState()); copy.setLanguage(src.getLanguage()); copy.setRemark(src.getRemark()); copy.setMdId(src.getMdId()); copy.setSort(src.getSort()); copy.setIsOpen(src.getIsOpen()); copy.setIsDelete(src.getIsDelete()); copy.setObjects(valueMap.getOrDefault(src.getId(), new ArrayList<>())); fsList.add(copy); } } result.put(entry.getKey(), fsList); } return result; } /** * 由结构化规格构建兼容的 food_sku JSON 数组(明细字段名 foodSpecsItems) */ private JSONArray buildFoodSkuArray(List fsList) { JSONArray skuArr = new JSONArray(); if (fsList == null || fsList.isEmpty()) { return skuArr; } for (FoodSpecs s : fsList) { skuArr.add(JSONObject.toJSON(s)); } return skuArr; } private void fillStallLocation(PosStore store) { if (store != null && store.getIsStall() != null && store.getIsStall() == 1 && store.getNightMarketId() != null) { InfoUser nightMarket = infoUserService.getById(store.getNightMarketId()); if (nightMarket != null) { store.setLongitude(nightMarket.getLongitude()); store.setLatitude(nightMarket.getLatitude()); } } } }