Selaa lähdekoodia

商品排序功能

yuhongqi 5 päivää sitten
vanhempi
commit
199cc7a323

+ 10 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreUserEndCategoryScrmController.java

@@ -7,6 +7,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.hisStore.domain.FsStoreUserEndCategoryScrm;
+import com.fs.hisStore.dto.FsStoreProductSortItemDTO;
 import com.fs.hisStore.service.IFsStoreUserEndCategoryScrmService;
 import com.fs.hisStore.vo.FsStoreUserEndCategoryProductVO;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -55,6 +56,15 @@ public class FsStoreUserEndCategoryScrmController extends BaseController {
         return getDataTable(list);
     }
 
+    /** 保存关联商品在当前页的排序(写入 fs_store_product_user_end_category.sort) */
+    @PreAuthorize("@ss.hasPermi('store:userEndCategory:edit')")
+    @Log(title = "用户分端类关联商品排序", businessType = BusinessType.UPDATE)
+    @PutMapping("/products/sort")
+    public AjaxResult saveCategoryProductsSort(@RequestParam Long id,
+                                               @RequestBody List<FsStoreProductSortItemDTO> items) {
+        return toAjax(userEndCategoryService.saveProductsSort(id, items));
+    }
+
     @PreAuthorize("@ss.hasPermi('store:userEndCategory:query')")
     @GetMapping("/{id}")
     public AjaxResult getInfo(@PathVariable Long id) {

+ 20 - 0
fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java

@@ -379,6 +379,11 @@ public class FsStoreOrderController extends BaseController
                         vo.setCateName("");
                         vo.setBankTransactionId("");
                     }
+                    vo.setCost(java.math.BigDecimal.ZERO);
+                    vo.setFPrice(java.math.BigDecimal.ZERO);
+                    vo.setBarCode("");
+                    vo.setCateName("");
+                    vo.setBankTransactionId("");
                 }
             }
             ExcelUtil<com.fs.hisStore.vo.FsStoreOrderItemExportZMVO> util = new ExcelUtil<>(com.fs.hisStore.vo.FsStoreOrderItemExportZMVO.class);
@@ -409,6 +414,11 @@ public class FsStoreOrderController extends BaseController
                     vo.setCateName("");
                     vo.setBankTransactionId("");
                 }
+                vo.setCost(java.math.BigDecimal.ZERO);
+                vo.setFPrice(java.math.BigDecimal.ZERO);
+                vo.setBarCode("");
+                vo.setCateName("");
+                vo.setBankTransactionId("");
             }
         }
         ExcelUtil<FsStoreOrderItemExportVO> util = new ExcelUtil<>(FsStoreOrderItemExportVO.class);
@@ -484,6 +494,11 @@ public class FsStoreOrderController extends BaseController
                             vo.setCateName("");
                             vo.setBankTransactionId("");
                         }
+                        vo.setCost(java.math.BigDecimal.ZERO);
+                        vo.setFPrice(java.math.BigDecimal.ZERO);
+                        vo.setBarCode("");
+                        vo.setCateName("");
+                        vo.setBankTransactionId("");
                     }
                 }
                 ExcelUtil<com.fs.hisStore.vo.FsStoreOrderItemExportZMVO> util = new ExcelUtil<>(com.fs.hisStore.vo.FsStoreOrderItemExportZMVO.class);
@@ -509,6 +524,11 @@ public class FsStoreOrderController extends BaseController
                     vo.setCateName("");
                     vo.setBankTransactionId("");
                 }
+                vo.setCost(java.math.BigDecimal.ZERO);
+                vo.setFPrice(java.math.BigDecimal.ZERO);
+                vo.setBarCode("");
+                vo.setCateName("");
+                vo.setBankTransactionId("");
             }
         }
         ExcelUtil<FsStoreOrderItemExportVO> util = new ExcelUtil<>(FsStoreOrderItemExportVO.class);

+ 114 - 26
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -33,10 +33,8 @@ import com.fs.his.vo.AppWatchLogReportVO;
 import com.fs.his.vo.WatchLogReportVO;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.hisStore.dto.FsStoreCartDTO;
-import com.fs.hisStore.mapper.FsStoreOrderItemScrmMapper;
 import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
 import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
-import com.fs.hisStore.vo.FsStoreOrderItemVO;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.IFsUserCoursePeriodDaysService;
 import com.fs.course.service.IFsUserCoursePeriodService;
@@ -185,9 +183,6 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private FsStoreOrderScrmMapper fsStoreOrderScrmMapper;
 
-    @Autowired
-    private FsStoreOrderItemScrmMapper fsStoreOrderItemScrmMapper;
-
     @Autowired
     private FsStoreProductScrmMapper fsStoreProductScrmMapper;
 
@@ -1889,29 +1884,10 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             Long redCount = fsCourseRedPacketLogMapper.countDistinctUsersByVideoAndPeriod(videoId, periodId,companyId,companyUserId);
             vo.setRedPacketUserCount(redCount != null ? redCount : 0L);
 
-            // 单品销量统计:从订单明细汇总
+            // 单品销量统计:使用订单主表 itemJson 明细,按 payPrice 分摊(与 GMV 一致,避免逐单查库)
             Map<Long, CourseProductSalesVO> productSalesMap = new HashMap<>();
             for (FsStoreOrderScrm order : paidOrders) {
-                // todo 数据量大的时候需要优化查询 外面批量查询 里面数据过滤
-                List<FsStoreOrderItemVO> items = fsStoreOrderItemScrmMapper.selectFsStoreOrderItemListByOrderId(order.getId());
-                if (items == null || items.isEmpty()) continue;
-                long totalNum = order.getTotalNum() != null && order.getTotalNum() > 0 ? order.getTotalNum() : 1;
-//                BigDecimal orderPayPrice = order.getPayPrice() != null ? order.getPayPrice() : BigDecimal.ZERO;
-
-                for (FsStoreOrderItemVO item : items) {
-                    FsStoreCartDTO cartDTO = JSONUtil.toBean(item.getJsonInfo(), FsStoreCartDTO.class);
-                    if (item.getProductId() == null) continue;
-                    long itemNum = item.getNum() != null ? item.getNum() : 0;
-                    BigDecimal itemAmount = totalNum > 0 ? cartDTO.getPrice().multiply(BigDecimal.valueOf(itemNum)) : BigDecimal.ZERO;
-                    CourseProductSalesVO productSales = productSalesMap.computeIfAbsent(item.getProductId(), k -> {
-                        CourseProductSalesVO pvo = new CourseProductSalesVO();
-                        pvo.setProductName(cartDTO.getProductName());
-                        return pvo;
-                    });
-
-                    productSales.setSalesCount(productSales.getSalesCount() + itemNum);
-                    productSales.setSalesAmount(productSales.getSalesAmount().add(itemAmount));
-                }
+                accumulateProductSalesFromPaidOrder(productSalesMap, order);
             }
             List<CourseProductSalesVO> productList = new ArrayList<>(productSalesMap.values());
             productList.sort((a, b) -> b.getSalesAmount().compareTo(a.getSalesAmount()));
@@ -2370,6 +2346,118 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             vo.setVideoTitle("");
         }
     }
+
+    /**
+     * 将单笔已支付订单的 payPrice 按明细「单价×数量」占比分摊到各商品;无有效标价时按行数均分。
+     * 明细来自订单主表 {@link FsStoreOrderScrm#getItemJson()}(与行表结构一致:productId、num、jsonInfo 等)。
+     * 每笔订单分摊之和严格等于该笔 payPrice,故多笔订单汇总后各商品 salesAmount 之和等于 GMV。
+     */
+    private void accumulateProductSalesFromPaidOrder(Map<Long, CourseProductSalesVO> productSalesMap, FsStoreOrderScrm order) {
+        String itemJson = order.getItemJson();
+        if (itemJson == null || itemJson.trim().isEmpty()) {
+            return;
+        }
+        JSONArray items;
+        try {
+            items = JSON.parseArray(itemJson);
+        } catch (Exception e) {
+            return;
+        }
+        if (items == null || items.isEmpty()) {
+            return;
+        }
+        BigDecimal orderPay = order.getPayPrice() != null ? order.getPayPrice() : BigDecimal.ZERO;
+        List<Long> pids = new ArrayList<>();
+        List<Long> nums = new ArrayList<>();
+        List<BigDecimal> raws = new ArrayList<>();
+        List<String> names = new ArrayList<>();
+        for (int j = 0; j < items.size(); j++) {
+            JSONObject o = items.getJSONObject(j);
+            if (o == null) {
+                continue;
+            }
+            Long productId = o.getLong("productId");
+            if (productId == null) {
+                continue;
+            }
+            long itemNum = 0L;
+            if (o.get("num") != null) {
+                try {
+                    itemNum = o.getLongValue("num");
+                } catch (Exception e) {
+                    itemNum = 0L;
+                }
+            }
+            String jsonInfo = o.getString("jsonInfo");
+            FsStoreCartDTO cartDTO = null;
+            try {
+                if (jsonInfo != null && !jsonInfo.isEmpty()) {
+                    cartDTO = JSONUtil.toBean(jsonInfo, FsStoreCartDTO.class);
+                }
+            } catch (Exception ignored) {
+            }
+            BigDecimal unit = (cartDTO != null && cartDTO.getPrice() != null) ? cartDTO.getPrice() : BigDecimal.ZERO;
+            BigDecimal raw = unit.multiply(BigDecimal.valueOf(itemNum));
+            pids.add(productId);
+            nums.add(itemNum);
+            raws.add(raw);
+            names.add(cartDTO != null ? cartDTO.getProductName() : null);
+        }
+        int n = pids.size();
+        if (n == 0) {
+            return;
+        }
+        BigDecimal rawSum = BigDecimal.ZERO;
+        for (BigDecimal r : raws) {
+            rawSum = rawSum.add(r);
+        }
+        int lastPos = -1;
+        for (int i = n - 1; i >= 0; i--) {
+            if (raws.get(i).compareTo(BigDecimal.ZERO) > 0) {
+                lastPos = i;
+                break;
+            }
+        }
+        BigDecimal[] allocs = new BigDecimal[n];
+        Arrays.fill(allocs, BigDecimal.ZERO);
+        if (rawSum.compareTo(BigDecimal.ZERO) > 0 && lastPos >= 0) {
+            BigDecimal used = BigDecimal.ZERO;
+            for (int i = 0; i < n; i++) {
+                BigDecimal raw = raws.get(i);
+                if (raw.compareTo(BigDecimal.ZERO) <= 0) {
+                    allocs[i] = BigDecimal.ZERO;
+                } else if (i == lastPos) {
+                    allocs[i] = orderPay.subtract(used);
+                } else {
+                    allocs[i] = orderPay.multiply(raw).divide(rawSum, 8, RoundingMode.HALF_UP);
+                    used = used.add(allocs[i]);
+                }
+            }
+        } else {
+            BigDecimal used = BigDecimal.ZERO;
+            BigDecimal share = orderPay.divide(BigDecimal.valueOf(n), 8, RoundingMode.HALF_UP);
+            for (int i = 0; i < n; i++) {
+                if (i == n - 1) {
+                    allocs[i] = orderPay.subtract(used);
+                } else {
+                    allocs[i] = share;
+                    used = used.add(share);
+                }
+            }
+        }
+        for (int i = 0; i < n; i++) {
+            Long pid = pids.get(i);
+            final int idx = i;
+            CourseProductSalesVO productSales = productSalesMap.computeIfAbsent(pid, k -> {
+                CourseProductSalesVO pvo = new CourseProductSalesVO();
+                pvo.setProductName(names.get(idx));
+                return pvo;
+            });
+            productSales.setSalesCount(productSales.getSalesCount() + nums.get(i));
+            productSales.setSalesAmount(productSales.getSalesAmount().add(allocs[i]));
+        }
+    }
+
     /**
      * 从 Map 中安全获取 Long 值,兼容 MyBatis 返回的驼峰/小写键名
      */

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductUserEndCategory.java

@@ -17,4 +17,7 @@ public class FsStoreProductUserEndCategory implements Serializable {
 
     /** 用户分端类ID */
     private Long userEndCategoryId;
+
+    /** 在该用户分端类下的排序(仅关联表,非商品表 sort) */
+    private Long sort;
 }

+ 18 - 0
fs-service/src/main/java/com/fs/hisStore/dto/FsStoreProductSortItemDTO.java

@@ -0,0 +1,18 @@
+package com.fs.hisStore.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 批量更新商品排序项(用户端分类关联商品列表用)
+ */
+@Data
+public class FsStoreProductSortItemDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long productId;
+    /** 排序值,0~9999 */
+    private Long sort;
+}

+ 17 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductUserEndCategoryMapper.java

@@ -27,4 +27,21 @@ public interface FsStoreProductUserEndCategoryMapper {
 
     /** 按区域位置查询商品ID:1=金刚区 2=瀑布区,配合 keyword 筛选,支持 storeId */
     List<Long> selectDistinctProductIdsByPosition(@Param("id") Long id,@Param("storeId") Long storeId, @Param("position") Integer position, @Param("keyword") String keyword);
+
+    /** 更新指定分类下某商品的关联排序 */
+    int updateRelSortByCategoryAndProduct(@Param("userEndCategoryId") Long userEndCategoryId,
+                                          @Param("productId") Long productId,
+                                          @Param("sort") long sort);
+
+    /** 指定分类下,批量查询商品与关联 sort(管理端/App 指定分类列表用) */
+    List<FsStoreProductUserEndCategory> selectSortByCategoryAndProductIds(@Param("userEndCategoryId") Long userEndCategoryId,
+                                                                          @Param("productIds") List<Long> productIds);
+
+    /** 按金刚区/瀑布区聚合:同一商品多条关联时取最大 sort(App 未指定分类时列表展示用) */
+    List<FsStoreProductUserEndCategory> selectAggSortByPositionAndProductIds(@Param("storeId") Long storeId,
+                                                                              @Param("position") Integer position,
+                                                                              @Param("productIds") List<Long> productIds);
+
+    /** 全部分类关联聚合:同一商品多条关联时取最大 sort(App「全部」列表展示用) */
+    List<FsStoreProductUserEndCategory> selectAggSortGlobalAndProductIds(@Param("productIds") List<Long> productIds);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreUserEndCategoryScrmService.java

@@ -1,6 +1,7 @@
 package com.fs.hisStore.service;
 
 import com.fs.hisStore.domain.FsStoreUserEndCategoryScrm;
+import com.fs.hisStore.dto.FsStoreProductSortItemDTO;
 import com.fs.hisStore.vo.FsStoreUserEndCategoryProductVO;
 
 import java.util.List;
@@ -26,6 +27,9 @@ public interface IFsStoreUserEndCategoryScrmService {
     /** 按用户端分类ID分页查询关联商品(去重商品ID分页,再查商品简表+标签并组装) */
     List<FsStoreUserEndCategoryProductVO> listProductsByCategoryId(Long categoryId, String keyword);
 
+    /** 批量更新关联排序(写入 fs_store_product_user_end_category.sort,需指定用户分端类 id) */
+    int saveProductsSort(Long userEndCategoryId, List<FsStoreProductSortItemDTO> items);
+
     /** 首页商品列表:id 为空查全部(分页商品ID后查简表+标签),id 不为空按用户端分类查;position=1金刚区/2瀑布区 时按区域查全部;返回 list+total */
     java.util.Map<String, Object> listProductsForApp(Long id, String keyword, Integer pageNum, Integer pageSize, Long storeId, Integer position);
 

+ 65 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreUserEndCategoryScrmServiceImpl.java

@@ -3,7 +3,9 @@ package com.fs.hisStore.service.impl;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.hisStore.domain.FsStoreProductScrm;
+import com.fs.hisStore.domain.FsStoreProductUserEndCategory;
 import com.fs.hisStore.domain.FsStoreUserEndCategoryScrm;
+import com.fs.hisStore.dto.FsStoreProductSortItemDTO;
 import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
 import com.fs.hisStore.mapper.FsStoreProductTagRelationScrmMapper;
 import com.fs.hisStore.mapper.FsStoreProductUserEndCategoryMapper;
@@ -15,6 +17,7 @@ import com.github.pagehelper.Page;
 import com.github.pagehelper.PageHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
@@ -69,13 +72,15 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
             tagVoMap.computeIfAbsent(tn.getProductId(), k -> new ArrayList<>()).add(tn);
         }
         // 转换为标签名称列表,已按sort排序(SQL已排序)
-        Map<Long, List<String>> tagMap = new LinkedHashMap<>();
+               Map<Long, List<String>> tagMap = new LinkedHashMap<>();
         for (Map.Entry<Long, List<FsStoreProductTagNameVO>> entry : tagVoMap.entrySet()) {
             List<String> tagNameList = entry.getValue().stream()
                     .map(FsStoreProductTagNameVO::getTagName)
                     .collect(Collectors.toList());
             tagMap.put(entry.getKey(), tagNameList);
         }
+        Map<Long, Long> relSortMap = toRelationSortMap(
+                productUserEndCategoryMapper.selectSortByCategoryAndProductIds(categoryId, productIds));
         List<FsStoreUserEndCategoryProductVO> result = new ArrayList<>();
         for (Long pid : productIds) {
             FsStoreProductScrm p = productMap.get(pid);
@@ -87,6 +92,7 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
             vo.setPrice(p.getPrice());
             vo.setOtPrice(p.getOtPrice());
             vo.setSales(p.getSales());
+            vo.setSort(relSortMap.getOrDefault(pid, 0L));
             vo.setTagList(tagMap.getOrDefault(pid, new ArrayList<>()));
             result.add(vo);
         }
@@ -97,6 +103,29 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
         return pageResult;
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int saveProductsSort(Long userEndCategoryId, List<FsStoreProductSortItemDTO> items) {
+        if (userEndCategoryId == null || items == null || items.isEmpty()) {
+            return 0;
+        }
+        int n = 0;
+        for (FsStoreProductSortItemDTO item : items) {
+            if (item == null || item.getProductId() == null) {
+                continue;
+            }
+            long s = item.getSort() == null ? 0L : item.getSort();
+            if (s < 0) {
+                s = 0;
+            }
+            if (s > 9999) {
+                s = 9999;
+            }
+            n += productUserEndCategoryMapper.updateRelSortByCategoryAndProduct(userEndCategoryId, item.getProductId(), s);
+        }
+        return n;
+    }
+
     @Override
     public Map<String, Object> listProductsForApp(Long id, String keyword, Integer pageNum, Integer pageSize, Long storeId, Integer position) {
         Map<String, Object> out = new HashMap<>();
@@ -136,6 +165,7 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
                     .collect(Collectors.toList());
             tagMap.put(entry.getKey(), tagNameList);
         }
+        Map<Long, Long> relSortMap = relationSortMapForApp(id, storeId, position, productIds);
         List<FsStoreUserEndCategoryProductVO> result = new ArrayList<>();
         for (Long pid : productIds) {
             FsStoreProductScrm p = productMap.get(pid);
@@ -147,6 +177,7 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
             vo.setPrice(p.getPrice());
             vo.setOtPrice(p.getOtPrice());
             vo.setSales(p.getSales());
+            vo.setSort(relSortMap.getOrDefault(pid, 0L));
             vo.setTagList(tagMap.getOrDefault(pid, new ArrayList<>()));
             vo.setPositiveRating(getFixedPositiveRating(p.getProductId()));
             result.add(vo);
@@ -207,6 +238,39 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
         return mapper.deleteByIds(ids);
     }
 
+    private Map<Long, Long> relationSortMapForApp(Long categoryId, Long storeId, Integer position, List<Long> productIds) {
+        if (productIds == null || productIds.isEmpty()) {
+            return new HashMap<>();
+        }
+        if (position != null && (position == 1 || position == 2)) {
+            if (categoryId != null && categoryId != 0L) {
+                return toRelationSortMap(
+                        productUserEndCategoryMapper.selectSortByCategoryAndProductIds(categoryId, productIds));
+            }
+            return toRelationSortMap(
+                    productUserEndCategoryMapper.selectAggSortByPositionAndProductIds(storeId, position, productIds));
+        }
+        if (categoryId != null && categoryId != 0L) {
+            return toRelationSortMap(
+                    productUserEndCategoryMapper.selectSortByCategoryAndProductIds(categoryId, productIds));
+        }
+        return toRelationSortMap(productUserEndCategoryMapper.selectAggSortGlobalAndProductIds(productIds));
+    }
+
+    private static Map<Long, Long> toRelationSortMap(List<FsStoreProductUserEndCategory> rows) {
+        Map<Long, Long> m = new HashMap<>();
+        if (rows == null) {
+            return m;
+        }
+        for (FsStoreProductUserEndCategory row : rows) {
+            if (row.getProductId() == null) {
+                continue;
+            }
+            m.put(row.getProductId(), row.getSort() != null ? row.getSort() : 0L);
+        }
+        return m;
+    }
+
     /**
      * 获取固定的好评率随机值
      * 先从 Redis 获取,如果不存在则生成随机值并保存到 Redis(永久保存)

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreUserEndCategoryProductVO.java

@@ -26,6 +26,8 @@ public class FsStoreUserEndCategoryProductVO implements Serializable {
     private BigDecimal positiveRating;
     /** 销量 */
     private Long sales;
+    /** 在用户分端类下的展示排序(fs_store_product_user_end_category.sort;App 聚合场景可能为多条关联中的最大 sort) */
+    private Long sort;
     /** 产品标签名称列表 */
     private List<String> tagList;
     

+ 4 - 4
fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml

@@ -522,7 +522,7 @@
         <if test='appId != null and appId != "" '>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
-        and p.is_new=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc limit #{count}
+        and p.is_new=1 and p.is_display=1 order by CASE WHEN p.sort IS NULL OR p.sort = 0 THEN 1 ELSE 0 END ASC, CASE WHEN p.sort IS NULL OR p.sort = 0 THEN 0 ELSE p.sort END DESC, p.create_time DESC, p.product_id DESC limit #{count}
     </select>
     <select id="selectFsStoreProductNewQueryPage" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
@@ -537,7 +537,7 @@
         <if test='keyword != null and keyword != ""'>
             and p.product_name like CONCAT('%', #{keyword}, '%')
         </if>
-        and p.is_new=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc
+        and p.is_new=1 and p.is_display=1 order by CASE WHEN p.sort IS NULL OR p.sort = 0 THEN 1 ELSE 0 END ASC, CASE WHEN p.sort IS NULL OR p.sort = 0 THEN 0 ELSE p.sort END DESC, p.create_time DESC, p.product_id DESC
     </select>
     <select id="selectFsStoreProductHotQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
@@ -549,7 +549,7 @@
         <if test='appId != null and appId != "" '>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
-        and  p.is_hot=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc limit #{count}
+        and  p.is_hot=1 and p.is_display=1 order by CASE WHEN p.sort IS NULL OR p.sort = 0 THEN 1 ELSE 0 END ASC, CASE WHEN p.sort IS NULL OR p.sort = 0 THEN 0 ELSE p.sort END DESC, p.create_time DESC, p.product_id DESC limit #{count}
     </select>
     <select id="selectFsStoreProductHotQueryPage" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
@@ -564,7 +564,7 @@
         <if test='keyword != null and keyword != ""'>
             and p.product_name like CONCAT('%', #{keyword}, '%')
         </if>
-        and  p.is_hot=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc
+        and  p.is_hot=1 and p.is_display=1 order by CASE WHEN p.sort IS NULL OR p.sort = 0 THEN 1 ELSE 0 END ASC, CASE WHEN p.sort IS NULL OR p.sort = 0 THEN 0 ELSE p.sort END DESC, p.create_time DESC, p.product_id DESC
     </select>
     <select id="selectFsStoreProductGoodListQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p

+ 118 - 55
fs-service/src/main/resources/mapper/hisStore/FsStoreProductUserEndCategoryMapper.xml

@@ -1,55 +1,118 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.fs.hisStore.mapper.FsStoreProductUserEndCategoryMapper">
-
-    <insert id="insertBatch">
-        insert into fs_store_product_user_end_category (product_id, user_end_category_id) values
-        <foreach collection="categoryIds" item="cid" separator=",">(#{productId}, #{cid})</foreach>
-    </insert>
-
-    <delete id="deleteByProductId">
-        delete from fs_store_product_user_end_category where product_id = #{productId}
-    </delete>
-
-    <select id="selectCategoryIdsByProductId" resultType="java.lang.Long">
-        select user_end_category_id from fs_store_product_user_end_category where product_id = #{productId}
-    </select>
-
-    <delete id="deleteByCategoryIds">
-        delete from fs_store_product_user_end_category where user_end_category_id in
-        <foreach collection="categoryIds" item="id" open="(" separator="," close=")">#{id}</foreach>
-    </delete>
-
-    <select id="selectDistinctProductIdsByCategoryId" resultType="java.lang.Long">
-        select distinct a.product_id from fs_store_product_user_end_category a left join fs_store_product_scrm c on a.product_id = c.product_id
-        where a.user_end_category_id = #{categoryId} and c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1
-        <if test="keyword != null and keyword != ''">
-            and c.product_name like CONCAT('%', #{keyword}, '%')
-        </if>
-        order by c.sort asc, c.create_time desc, a.product_id
-    </select>
-
-    <select id="selectDistinctProductIds" resultType="java.lang.Long">
-        select distinct a.product_id from fs_store_product_user_end_category  a left join fs_store_product_scrm c on a.product_id = c.product_id
-        where c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1
-        <if test="keyword != null and keyword != ''">
-            and c.product_name like CONCAT('%', #{keyword}, '%')
-        </if>
-        order by c.sort asc, c.create_time desc, a.product_id
-    </select>
-
-    <!-- 按区域位置(1金刚区 2瀑布区)查询商品ID,支持 keyword、storeId -->
-    <select id="selectDistinctProductIdsByPosition" resultType="java.lang.Long">
-        select distinct a.product_id from fs_store_product_user_end_category a
-        left join fs_store_product_scrm c on a.product_id = c.product_id
-        left join fs_store_user_end_category_scrm uec on a.user_end_category_id = uec.id
-        where uec.status = 1 and uec.position = #{position}
-        and c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1
-        <if test="id != null and id != 0">and a.user_end_category_id = #{id}</if>
-        <if test="storeId != null">and uec.store_id = #{storeId}</if>
-        <if test="keyword != null and keyword != ''">
-            and c.product_name like CONCAT('%', #{keyword}, '%')
-        </if>
-        order by c.sort asc, c.create_time desc, a.product_id
-    </select>
-</mapper>
+<?xml version="1.0" encoding="UTF-8" ?>

+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

+<mapper namespace="com.fs.hisStore.mapper.FsStoreProductUserEndCategoryMapper">

+

+    <insert id="insertBatch">

+        insert into fs_store_product_user_end_category (product_id, user_end_category_id, sort) values

+        <foreach collection="categoryIds" item="cid" separator=",">(#{productId}, #{cid}, 0)</foreach>

+    </insert>

+

+    <delete id="deleteByProductId">

+        delete from fs_store_product_user_end_category where product_id = #{productId}

+    </delete>

+

+    <select id="selectCategoryIdsByProductId" resultType="java.lang.Long">

+        select user_end_category_id from fs_store_product_user_end_category where product_id = #{productId}

+    </select>

+

+    <delete id="deleteByCategoryIds">

+        delete from fs_store_product_user_end_category where user_end_category_id in

+        <foreach collection="categoryIds" item="id" open="(" separator="," close=")">#{id}</foreach>

+    </delete>

+

+    <!-- 排序使用关联表 a.sort;GROUP BY 避免同一商品多分类时 DISTINCT+ORDER BY 不稳定 -->

+    <select id="selectDistinctProductIdsByCategoryId" resultType="java.lang.Long">

+        select t.product_id from (

+        select a.product_id,

+               min(case when a.sort is null or a.sort = 0 then 1 else 0 end) as o1,

+               max(case when a.sort is null or a.sort = 0 then 0 else a.sort end) as o2,

+               max(c.create_time) as ct

+        from fs_store_product_user_end_category a

+        left join fs_store_product_scrm c on a.product_id = c.product_id

+        where a.user_end_category_id = #{categoryId} and c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1

+        <if test="keyword != null and keyword != ''">

+            and c.product_name like CONCAT('%', #{keyword}, '%')

+        </if>

+        group by a.product_id

+        ) t

+        order by t.o1 asc, t.o2 desc, t.ct desc, t.product_id desc

+    </select>

+

+    <select id="selectDistinctProductIds" resultType="java.lang.Long">

+        select t.product_id from (

+        select a.product_id,

+               min(case when a.sort is null or a.sort = 0 then 1 else 0 end) as o1,

+               max(case when a.sort is null or a.sort = 0 then 0 else a.sort end) as o2,

+               max(c.create_time) as ct

+        from fs_store_product_user_end_category  a

+        left join fs_store_product_scrm c on a.product_id = c.product_id

+        where c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1

+        <if test="keyword != null and keyword != ''">

+            and c.product_name like CONCAT('%', #{keyword}, '%')

+        </if>

+        group by a.product_id

+        ) t

+        order by t.o1 asc, t.o2 desc, t.ct desc, t.product_id desc

+    </select>

+

+    <!-- 按区域位置(1金刚区 2瀑布区)查询商品ID,支持 keyword、storeId;排序为关联表 a.sort -->

+    <select id="selectDistinctProductIdsByPosition" resultType="java.lang.Long">

+        select t.product_id from (

+        select a.product_id,

+               min(case when a.sort is null or a.sort = 0 then 1 else 0 end) as o1,

+               max(case when a.sort is null or a.sort = 0 then 0 else a.sort end) as o2,

+               max(c.create_time) as ct

+        from fs_store_product_user_end_category a

+        left join fs_store_product_scrm c on a.product_id = c.product_id

+        left join fs_store_user_end_category_scrm uec on a.user_end_category_id = uec.id

+        where uec.status = 1 and uec.position = #{position}

+        and c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1

+        <if test="id != null and id != 0">and a.user_end_category_id = #{id}</if>

+        <if test="storeId != null">and uec.store_id = #{storeId}</if>

+        <if test="keyword != null and keyword != ''">

+            and c.product_name like CONCAT('%', #{keyword}, '%')

+        </if>

+        group by a.product_id

+        ) t

+        order by t.o1 asc, t.o2 desc, t.ct desc, t.product_id desc

+    </select>

+

+    <update id="updateRelSortByCategoryAndProduct">

+        update fs_store_product_user_end_category

+        set sort = #{sort}

+        where user_end_category_id = #{userEndCategoryId} and product_id = #{productId}

+    </update>

+

+    <select id="selectSortByCategoryAndProductIds" resultType="com.fs.hisStore.domain.FsStoreProductUserEndCategory">

+        select product_id, user_end_category_id, sort

+        from fs_store_product_user_end_category

+        where user_end_category_id = #{userEndCategoryId}

+        and product_id in

+        <foreach collection="productIds" item="pid" open="(" separator="," close=")">#{pid}</foreach>

+    </select>

+

+    <select id="selectAggSortByPositionAndProductIds" resultType="com.fs.hisStore.domain.FsStoreProductUserEndCategory">

+        select a.product_id,

+               max(case when a.sort is null or a.sort = 0 then 0 else a.sort end) as sort

+        from fs_store_product_user_end_category a

+        left join fs_store_product_scrm c on a.product_id = c.product_id

+        left join fs_store_user_end_category_scrm uec on a.user_end_category_id = uec.id

+        where uec.status = 1 and uec.position = #{position}

+        and c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1

+        <if test="storeId != null">and uec.store_id = #{storeId}</if>

+        and a.product_id in

+        <foreach collection="productIds" item="pid" open="(" separator="," close=")">#{pid}</foreach>

+        group by a.product_id

+    </select>

+

+    <select id="selectAggSortGlobalAndProductIds" resultType="com.fs.hisStore.domain.FsStoreProductUserEndCategory">

+        select a.product_id,

+               max(case when a.sort is null or a.sort = 0 then 0 else a.sort end) as sort

+        from fs_store_product_user_end_category a

+        left join fs_store_product_scrm c on a.product_id = c.product_id

+        where c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1

+        and a.product_id in

+        <foreach collection="productIds" item="pid" open="(" separator="," close=")">#{pid}</foreach>

+        group by a.product_id

+    </select>

+</mapper>