|
|
@@ -0,0 +1,295 @@
|
|
|
+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;
|
|
|
+import com.fs.hisStore.mapper.FsStoreUserEndCategoryScrmMapper;
|
|
|
+import com.fs.hisStore.service.IFsStoreUserEndCategoryScrmService;
|
|
|
+import com.fs.hisStore.vo.FsStoreProductTagNameVO;
|
|
|
+import com.fs.hisStore.vo.FsStoreUserEndCategoryProductVO;
|
|
|
+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;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.ThreadLocalRandom;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 商品用户分端类 Service 实现
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCategoryScrmService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsStoreUserEndCategoryScrmMapper mapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsStoreProductUserEndCategoryMapper productUserEndCategoryMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsStoreProductScrmMapper fsStoreProductScrmMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsStoreProductTagRelationScrmMapper productTagRelationMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedisCache redisCache;
|
|
|
+
|
|
|
+ /** Redis key 用于存储固定的好评率随机值 */
|
|
|
+ private static final String POSITIVE_RATING_REDIS_KEY = "product:positiveRating:fixed:";
|
|
|
+ private static final double MIN_RATING = 95.0;
|
|
|
+ private static final double MAX_RATING = 99.9;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<FsStoreUserEndCategoryProductVO> listProductsByCategoryId(Long categoryId, String keyword) {
|
|
|
+ if (categoryId == null) return new ArrayList<>();
|
|
|
+ List<Long> productIds = productUserEndCategoryMapper.selectDistinctProductIdsByCategoryId(categoryId, keyword);
|
|
|
+ if (productIds == null || productIds.isEmpty()) return new ArrayList<>();
|
|
|
+ long total = (productIds instanceof Page) ? ((Page<?>) productIds).getTotal() : productIds.size();
|
|
|
+ int pageNum = (productIds instanceof Page) ? ((Page<?>) productIds).getPageNum() : 1;
|
|
|
+ int pageSize = (productIds instanceof Page) ? ((Page<?>) productIds).getPageSize() : 10;
|
|
|
+ List<FsStoreProductScrm> products = fsStoreProductScrmMapper.getStoreProductInProductIds(productIds);
|
|
|
+ Map<Long, FsStoreProductScrm> productMap = products.stream().collect(Collectors.toMap(FsStoreProductScrm::getProductId, p -> p, (a, b) -> a));
|
|
|
+ List<FsStoreProductTagNameVO> tagNames = productTagRelationMapper.selectProductTagNamesByProductIds(productIds);
|
|
|
+ // 按商品ID分组,每个商品的标签列表按sort排序
|
|
|
+ Map<Long, List<FsStoreProductTagNameVO>> tagVoMap = new LinkedHashMap<>();
|
|
|
+ for (FsStoreProductTagNameVO tn : tagNames) {
|
|
|
+ tagVoMap.computeIfAbsent(tn.getProductId(), k -> new ArrayList<>()).add(tn);
|
|
|
+ }
|
|
|
+ // 转换为标签名称列表,已按sort排序(SQL已排序)
|
|
|
+ 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);
|
|
|
+ if (p == null) continue;
|
|
|
+ FsStoreUserEndCategoryProductVO vo = new FsStoreUserEndCategoryProductVO();
|
|
|
+ vo.setProductId(p.getProductId());
|
|
|
+ vo.setProductName(p.getProductName());
|
|
|
+ vo.setImage(p.getImage());
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ // 包装为 Page 以携带正确的 total,供 getDataTable 使用
|
|
|
+ Page<FsStoreUserEndCategoryProductVO> pageResult = new Page<>(pageNum, pageSize);
|
|
|
+ pageResult.setTotal(total);
|
|
|
+ pageResult.addAll(result);
|
|
|
+ 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<>();
|
|
|
+ out.put("list", new ArrayList<FsStoreUserEndCategoryProductVO>());
|
|
|
+ out.put("total", 0L);
|
|
|
+ if (pageNum == null || pageSize == null || pageSize <= 0) return out;
|
|
|
+ PageHelper.startPage(pageNum, pageSize);
|
|
|
+ List<Long> productIds;
|
|
|
+ if (position == null) {
|
|
|
+ position = 1;
|
|
|
+ }
|
|
|
+ if (position == 1 || position == 2) {
|
|
|
+ productIds = productUserEndCategoryMapper.selectDistinctProductIdsByPosition(id,storeId, position, keyword);
|
|
|
+ } else if (id != null && id != 0L) {
|
|
|
+ productIds = productUserEndCategoryMapper.selectDistinctProductIdsByCategoryId(id, keyword);
|
|
|
+ } else {
|
|
|
+ productIds = productUserEndCategoryMapper.selectDistinctProductIds(keyword);
|
|
|
+ }
|
|
|
+ long total = productIds instanceof Page ? ((Page<?>) productIds).getTotal() : (productIds != null ? productIds.size() : 0);
|
|
|
+ if (productIds == null || productIds.isEmpty()) {
|
|
|
+ out.put("total", total);
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+ List<FsStoreProductScrm> products = fsStoreProductScrmMapper.getStoreProductInProductIdsForApp(productIds);
|
|
|
+ Map<Long, FsStoreProductScrm> productMap = products.stream().collect(Collectors.toMap(FsStoreProductScrm::getProductId, p -> p, (a, b) -> a));
|
|
|
+ List<FsStoreProductTagNameVO> tagNames = productTagRelationMapper.selectProductTagNamesByProductIds(productIds);
|
|
|
+ // 按商品ID分组,每个商品的标签列表按sort排序
|
|
|
+ Map<Long, List<FsStoreProductTagNameVO>> tagVoMap = new LinkedHashMap<>();
|
|
|
+ for (FsStoreProductTagNameVO tn : tagNames) {
|
|
|
+ tagVoMap.computeIfAbsent(tn.getProductId(), k -> new ArrayList<>()).add(tn);
|
|
|
+ }
|
|
|
+ // 转换为标签名称列表,已按sort排序(SQL已排序)
|
|
|
+ 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 = relationSortMapForApp(id, storeId, position, productIds);
|
|
|
+ List<FsStoreUserEndCategoryProductVO> result = new ArrayList<>();
|
|
|
+ for (Long pid : productIds) {
|
|
|
+ FsStoreProductScrm p = productMap.get(pid);
|
|
|
+ if (p == null) continue;
|
|
|
+ FsStoreUserEndCategoryProductVO vo = new FsStoreUserEndCategoryProductVO();
|
|
|
+ vo.setProductId(p.getProductId());
|
|
|
+ vo.setProductName(p.getProductName());
|
|
|
+ vo.setImage(p.getImage());
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ out.put("list", result);
|
|
|
+ out.put("total", total);
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public FsStoreUserEndCategoryScrm selectById(Long id) {
|
|
|
+ return mapper.selectById(id);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<FsStoreUserEndCategoryScrm> selectList(FsStoreUserEndCategoryScrm query) {
|
|
|
+ return mapper.selectList(query);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<FsStoreUserEndCategoryScrm> selectListPage(FsStoreUserEndCategoryScrm query) {
|
|
|
+ return mapper.selectList(query);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<FsStoreUserEndCategoryScrm> selectListForProduct(Long storeId) {
|
|
|
+ return mapper.selectListForProduct(storeId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<FsStoreUserEndCategoryScrm> selectTop8ByPosition(Long storeId, Integer position) {
|
|
|
+ return mapper.selectTop8ByPosition(storeId, position);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int insert(FsStoreUserEndCategoryScrm entity) {
|
|
|
+ entity.setCreateTime(DateUtils.getNowDate());
|
|
|
+ entity.setUpdateTime(DateUtils.getNowDate());
|
|
|
+ return mapper.insert(entity);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int update(FsStoreUserEndCategoryScrm entity) {
|
|
|
+ entity.setUpdateTime(DateUtils.getNowDate());
|
|
|
+ return mapper.update(entity);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int deleteById(Long id) {
|
|
|
+ productUserEndCategoryMapper.deleteByCategoryIds(new Long[]{id});
|
|
|
+ return mapper.deleteById(id);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int deleteByIds(Long[] ids) {
|
|
|
+ if (ids == null || ids.length == 0) return 0;
|
|
|
+ productUserEndCategoryMapper.deleteByCategoryIds(ids);
|
|
|
+ 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(永久保存)
|
|
|
+ * @return 固定的好评率值
|
|
|
+ */
|
|
|
+ private BigDecimal getFixedPositiveRating(Long productId) {
|
|
|
+ // 先从 Redis 获取
|
|
|
+ BigDecimal cachedRating = redisCache.getCacheObject(POSITIVE_RATING_REDIS_KEY + productId);
|
|
|
+ if (cachedRating != null) {
|
|
|
+ return cachedRating;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Redis 不存在,生成随机值
|
|
|
+ double rating = ThreadLocalRandom.current().nextDouble(MIN_RATING, MAX_RATING);
|
|
|
+ BigDecimal fixedRating = new BigDecimal(rating).setScale(1, RoundingMode.HALF_UP);
|
|
|
+
|
|
|
+ // 保存到 Redis(永久保存,不设置过期时间)
|
|
|
+ redisCache.setCacheObject(POSITIVE_RATING_REDIS_KEY + productId, fixedRating);
|
|
|
+
|
|
|
+ return fixedRating;
|
|
|
+ }
|
|
|
+}
|