master:系统推广营销调优;

pull/1121/head
liujiang 2025-08-26 21:05:03 +08:00
parent 163b7eca0b
commit b07bf2cfd7
11 changed files with 314 additions and 5 deletions

View File

@ -50,8 +50,8 @@ public class StoreCustomerProductDiscountController extends XktBaseController {
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')||@ss.hasSupplierSubRole()")
@ApiOperation(value = "客户销售管理 批量删除", httpMethod = "PUT", response = R.class)
@Log(title = "客户销售管理 批量删除", businessType = BusinessType.UPDATE)
@ApiOperation(value = "客户销售管理 批量删除", httpMethod = "DELETE", response = R.class)
@Log(title = "客户销售管理 批量删除", businessType = BusinessType.DELETE)
@DeleteMapping("/batch")
public R<Integer> batchDiscountDelete(@Validated @RequestBody StoreCusProdBatchDiscountDeleteVO batchDiscDeleteVO) {
return R.ok(storeCusProdDiscService.batchDiscountDelete(BeanUtil.toBean(batchDiscDeleteVO, StoreCusProdBatchDiscountDeleteDTO.class)));

View File

@ -12,6 +12,7 @@ import com.ruoyi.web.controller.xkt.vo.advertRound.app.index.APPIndexMidBrandVO;
import com.ruoyi.web.controller.xkt.vo.advertRound.app.index.APPIndexTopBannerVO;
import com.ruoyi.web.controller.xkt.vo.advertRound.app.own.APPOwnGuessLikeVO;
import com.ruoyi.web.controller.xkt.vo.advertRound.pc.PCDownloadVO;
import com.ruoyi.web.controller.xkt.vo.advertRound.pc.PCSearchResultAdvertVO;
import com.ruoyi.web.controller.xkt.vo.advertRound.pc.PCUserCenterVO;
import com.ruoyi.web.controller.xkt.vo.advertRound.pc.index.*;
import com.ruoyi.web.controller.xkt.vo.advertRound.pc.newProd.*;
@ -76,12 +77,24 @@ public class WebsiteController extends XktBaseController {
return R.ok(websitePCService.pcNewProdRecommendPage(BeanUtil.toBean(searchVO, IndexSearchDTO.class)));
}
@ApiOperation(value = "PC 搜索结果", httpMethod = "POST", response = R.class)
@ApiOperation(value = "PC 搜索", httpMethod = "POST", response = R.class)
@PostMapping("/pc/search")
public R<Page<PCSearchDTO>> psSearchPage(@Validated @RequestBody IndexSearchVO searchVO) throws IOException {
return R.ok(websitePCService.psSearchPage(BeanUtil.toBean(searchVO, IndexSearchDTO.class)));
}
@ApiOperation(value = "PC 首页 顶部通栏", httpMethod = "GET", response = R.class)
@GetMapping("/pc/index/top")
public R<List<PCIndexTopBannerVO>> getPcIndexTop() {
return R.ok(BeanUtil.copyToList(websitePCService.getPcIndexTop(), PCIndexTopBannerVO.class));
}
@ApiOperation(value = "PC 搜索结果广告", httpMethod = "GET", response = R.class)
@GetMapping("/pc/search/advert")
public R<List<PCSearchResultAdvertVO>> getPcSearchAdvertList() {
return R.ok(BeanUtil.copyToList(websitePCService.getPcSearchAdvertList(), PCSearchResultAdvertVO.class));
}
@ApiOperation(value = "PC 首页 顶部横向轮播图", httpMethod = "GET", response = R.class)
@GetMapping("/pc/index/top/left")
public R<List<PCIndexTopLeftBannerVO>> getPcIndexTopLeftBanner() {

View File

@ -0,0 +1,39 @@
package com.ruoyi.web.controller.xkt.vo.advertRound.pc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
* @author liujiang
* @version v1.0
* @date 2025/3/27 15:12
*/
@ApiModel
@Data
public class PCSearchResultAdvertVO {
@ApiModelProperty(value = "会员等级")
private Integer memberLevel;
@ApiModelProperty(value = "档口ID")
private Long storeId;
@ApiModelProperty(value = "档口名称")
private String storeName;
@ApiModelProperty(value = "2 商品")
private Integer displayType;
@ApiModelProperty(value = "单价")
private BigDecimal price;
@ApiModelProperty(value = "档口商品ID")
private Long storeProdId;
@ApiModelProperty(value = "货号")
private String prodArtNum;
@ApiModelProperty(value = "主图")
private String mainPicUrl;
@ApiModelProperty(value = "排序")
private Integer orderNum;
@ApiModelProperty(value = "是否关注")
private Boolean focus;
}

View File

@ -0,0 +1,25 @@
package com.ruoyi.web.controller.xkt.vo.advertRound.pc.index;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author liujiang
* @version v1.0
* @date 2025/3/27 15:12
*/
@ApiModel("PC 首页 顶部通栏")
@Data
public class PCIndexTopBannerVO {
@ApiModelProperty(value = "1推广图")
private Integer displayType;
@ApiModelProperty(value = "排序")
private Integer orderNum;
@ApiModelProperty(value = "档口ID")
private Long storeId;
@ApiModelProperty(value = "推广图路径")
private String fileUrl;
}

View File

@ -33,7 +33,6 @@ public class StoreUpdateVO {
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "联系电话格式不正确,请输入有效的中国大陆手机号")
private String contactPhone;
@ApiModelProperty(value = "备选联系电话")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "备选联系电话格式不正确,请输入有效的中国大陆手机号")
private String contactBackPhone;
@ApiModelProperty(value = "微信账号")
private String wechatAccount;

View File

@ -94,6 +94,14 @@ public class CacheConstants {
*/
public static final String PC_ADVERT = "pc_advert:";
/**
* PC
*/
public static final String PC_ADVERT_INDEX_TOP = "pc_index_top";
/**
* PC 广
*/
public static final String PC_SEARCH_RESULT_ADVERT = "pc_search_result_advert";
/**
* PC
*/

View File

@ -0,0 +1,43 @@
package com.ruoyi.xkt.dto.advertRound.pc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
/**
* @author liujiang
* @version v1.0
* @date 2025/3/27 15:12
*/
@ApiModel
@Data
@Accessors(chain = true)
public class PCSearchResultAdvertDTO {
@ApiModelProperty(value = "会员等级")
private Integer memberLevel;
@ApiModelProperty(value = "档口ID")
private Long storeId;
@ApiModelProperty(value = "档口名称")
private String storeName;
@ApiModelProperty(value = "2 商品")
private Integer displayType;
@ApiModelProperty(value = "单价")
private BigDecimal price;
@ApiModelProperty(value = "档口出的推广价格")
private BigDecimal payPrice;
@ApiModelProperty(value = "档口商品ID")
private Long storeProdId;
@ApiModelProperty(value = "货号")
private String prodArtNum;
@ApiModelProperty(value = "主图")
private String mainPicUrl;
@ApiModelProperty(value = "排序")
private Integer orderNum;
@ApiModelProperty(value = "是否关注")
private Boolean focus;
}

View File

@ -0,0 +1,31 @@
package com.ruoyi.xkt.dto.advertRound.pc.index;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
/**
* @author liujiang
* @version v1.0
* @date 2025/3/27 15:12
*/
@ApiModel("PC 首页 顶部通栏")
@Data
@Accessors(chain = true)
public class PCIndexTopBannerDTO {
@ApiModelProperty(value = "1推广图")
private Integer displayType;
@ApiModelProperty(value = "排序")
private Integer orderNum;
@ApiModelProperty(value = "档口ID")
private Long storeId;
@ApiModelProperty(value = "推广图路径")
private String fileUrl;
@ApiModelProperty(value = "档口出的推广价格")
private BigDecimal payPrice;
}

View File

@ -3,6 +3,7 @@ package com.ruoyi.xkt.service;
import com.ruoyi.common.core.page.Page;
import com.ruoyi.xkt.dto.advertRound.pc.PCDownloadDTO;
import com.ruoyi.xkt.dto.advertRound.pc.PCSearchDTO;
import com.ruoyi.xkt.dto.advertRound.pc.PCSearchResultAdvertDTO;
import com.ruoyi.xkt.dto.advertRound.pc.PCUserCenterDTO;
import com.ruoyi.xkt.dto.advertRound.pc.index.*;
import com.ruoyi.xkt.dto.advertRound.pc.newProd.*;
@ -15,7 +16,6 @@ import com.ruoyi.xkt.dto.website.IndexSearchDTO;
import com.ruoyi.xkt.dto.website.StoreSearchDTO;
import java.io.IOException;
import java.text.ParseException;
import java.util.List;
/**
@ -198,4 +198,17 @@ public interface IWebsitePCService {
*/
Page<PCStoreRecommendDTO> pcStoreRecommendPage(StoreSearchDTO searchDTO);
/**
* PC
*
* @return PCIndexTopBannerDTO
*/
List<PCIndexTopBannerDTO> getPcIndexTop();
/**
* PC 广
*
* @return List<PCSearchResultAdvertDTO>
*/
List<PCSearchResultAdvertDTO> getPcSearchAdvertList();
}

View File

@ -45,6 +45,8 @@ import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
@ -254,6 +256,14 @@ public class StoreServiceImpl implements IStoreService {
@Override
@Transactional
public int updateStore(StoreUpdateDTO storeUpdateDTO) {
if (StringUtils.isNotBlank(storeUpdateDTO.getContactBackPhone())) {
final String PHONE_REGEX = "^1[3-9]\\d{9}$";
final Pattern pattern = Pattern.compile(PHONE_REGEX);
Matcher matcher = pattern.matcher(storeUpdateDTO.getContactBackPhone().trim());
if (!matcher.matches()) {
throw new ServiceException("备选手机号格式错误,请重新输入!", HttpStatus.ERROR);
}
}
Store store = Optional.ofNullable(this.storeMapper.selectOne(new LambdaQueryWrapper<Store>()
.eq(Store::getId, storeUpdateDTO.getStoreId()).eq(Store::getDelFlag, Constants.UNDELETED)))
.orElseThrow(() -> new ServiceException("档口不存在!", HttpStatus.ERROR));

View File

@ -20,6 +20,7 @@ import com.ruoyi.framework.es.EsClientWrapper;
import com.ruoyi.xkt.domain.*;
import com.ruoyi.xkt.dto.advertRound.pc.PCDownloadDTO;
import com.ruoyi.xkt.dto.advertRound.pc.PCSearchDTO;
import com.ruoyi.xkt.dto.advertRound.pc.PCSearchResultAdvertDTO;
import com.ruoyi.xkt.dto.advertRound.pc.PCUserCenterDTO;
import com.ruoyi.xkt.dto.advertRound.pc.index.*;
import com.ruoyi.xkt.dto.advertRound.pc.newProd.*;
@ -85,6 +86,7 @@ public class WebsitePCServiceImpl implements IWebsitePCService {
final StoreMapper storeMapper;
final StoreProductStatisticsMapper prodStatsMapper;
final IPictureService pictureService;
final UserSubscriptionsMapper userSubsMapper;
/**
* PC
@ -369,6 +371,104 @@ public class WebsitePCServiceImpl implements IWebsitePCService {
return new Page<>(searchDTO.getPageNum(), searchDTO.getPageSize(), pages, redisList.size(), realDataList);
}
/***
* PC
* @return
*/
@Override
@Transactional(readOnly = true)
public List<PCIndexTopBannerDTO> getPcIndexTop() {
// 从redis 中获取 PC 首页 顶部通栏
List<PCIndexTopBannerDTO> topBannerList = redisCache.getCacheObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_INDEX_TOP);
if (ObjectUtils.isNotEmpty(topBannerList)) {
return topBannerList;
}
// 正在播放的首页顶部通栏
List<AdvertRound> indexTopList = this.advertRoundMapper.selectList(new LambdaQueryWrapper<AdvertRound>()
.isNotNull(AdvertRound::getStoreId).eq(AdvertRound::getDelFlag, Constants.UNDELETED)
.eq(AdvertRound::getTypeId, AdType.PC_HOME_TOPMOST_BANNER.getValue())
.eq(AdvertRound::getLaunchStatus, AdLaunchStatus.LAUNCHING.getValue()));
if (CollectionUtils.isEmpty(indexTopList)) {
return Collections.emptyList();
}
Map<Long, SysFile> fileMap = fileMapper.selectList(new LambdaQueryWrapper<SysFile>().eq(SysFile::getDelFlag, Constants.UNDELETED)
.in(SysFile::getId, indexTopList.stream().map(AdvertRound::getPicId).filter(ObjectUtils::isNotEmpty).collect(Collectors.toList())))
.stream().collect(Collectors.toMap(SysFile::getId, Function.identity()));
// 顶部通栏数据
List<PCIndexTopBannerDTO> topList = indexTopList.stream().map(x -> new PCIndexTopBannerDTO().setDisplayType(x.getDisplayType())
.setStoreId(x.getStoreId()).setPayPrice(ObjectUtils.defaultIfNull(x.getPayPrice(), BigDecimal.ZERO))
.setFileUrl(ObjectUtils.isNotEmpty(x.getPicId()) ? fileMap.get(x.getPicId()).getFileUrl() : null))
.sorted(Comparator.comparing(PCIndexTopBannerDTO::getPayPrice).reversed())
.collect(Collectors.toList());
// 按照价格由高到低进行排序价格高的排1其次按照价格排 2 3 4 5
for (int i = 0; i < topList.size(); i++) {
topList.get(i).setOrderNum(i + 1);
}
// 存放到redis中过期时间为1天
redisCache.setCacheObject(CacheConstants.PC_ADVERT + CacheConstants.PC_ADVERT_INDEX_TOP, topList, 1, TimeUnit.DAYS);
return topList;
}
@Override
@Transactional(readOnly = true)
public List<PCSearchResultAdvertDTO> getPcSearchAdvertList() {
// 从redis 中获取 PC 搜索结果广告列表
List<PCSearchResultAdvertDTO> searchResultAdvertList = redisCache.getCacheObject(CacheConstants.PC_ADVERT + CacheConstants.PC_SEARCH_RESULT_ADVERT);
if (ObjectUtils.isNotEmpty(searchResultAdvertList)) {
return searchResultAdvertList;
}
List<AdvertRound> oneMonthList = this.getOneMonthAdvertList(Collections.singletonList(AdType.PC_SEARCH_RESULT.getValue()));
if (CollectionUtils.isEmpty(oneMonthList)) {
return new ArrayList<>();
}
// 获取档口
final List<Long> storeIdList = oneMonthList.stream().filter(x -> ObjectUtils.isNotEmpty(x.getStoreId())).map(AdvertRound::getStoreId).collect(Collectors.toList());
List<Store> storeList = storeMapper.selectList(new LambdaQueryWrapper<Store>().eq(Store::getDelFlag, Constants.UNDELETED).in(Store::getId, storeIdList));
Map<Long, Store> storeMap = storeList.stream().collect(Collectors.toMap(Store::getId, Function.identity()));
List<AdvertRound> launchingList = oneMonthList.stream().filter(x -> Objects.equals(x.getLaunchStatus(), AdLaunchStatus.LAUNCHING.getValue()))
.filter(x -> Objects.equals(x.getBiddingStatus(), AdBiddingStatus.BIDDING_SUCCESS.getValue())).collect(Collectors.toList());
List<AdvertRound> expiredList = oneMonthList.stream().filter(x -> Objects.equals(x.getLaunchStatus(), AdLaunchStatus.EXPIRED.getValue())).collect(Collectors.toList());
final List<Long> storeProdIdList = oneMonthList.stream()
.filter(x -> StringUtils.isNotBlank(x.getProdIdStr())).map(x -> Long.parseLong(x.getProdIdStr())).distinct().collect(Collectors.toList());
// 档口商品的价格及商品主图map
Map<Long, StoreProdPriceAndMainPicDTO> prodPriceAndMainPicMap = CollectionUtils.isEmpty(storeProdIdList) ? new ConcurrentHashMap<>()
: this.storeProdMapper.selectPriceAndMainPicList(storeProdIdList).stream().collect(Collectors
.toMap(StoreProdPriceAndMainPicDTO::getStoreProdId, Function.identity()));
List<PCSearchResultAdvertDTO> pcSearchResAdvertList;
if (CollectionUtils.isEmpty(launchingList)) {
pcSearchResAdvertList = this.fillPcSearchResultAdvertList(expiredList, prodPriceAndMainPicMap, storeMap, new ArrayList<>(), 15);
pcSearchResAdvertList.sort(Comparator.comparing(PCSearchResultAdvertDTO::getPayPrice).reversed());
// 按照价格由高到低进行排序价格高的排1其次按照价格排 2 3 4 5
for (int i = 0; i < pcSearchResAdvertList.size(); i++) {
pcSearchResAdvertList.get(i).setOrderNum(i + 1);
}
} else {
pcSearchResAdvertList = launchingList.stream().filter(x -> StringUtils.isNotBlank(x.getProdIdStr())).map(x -> {
final Long storeProdId = Long.parseLong(x.getProdIdStr());
return new PCSearchResultAdvertDTO().setDisplayType(AdDisplayType.PRODUCT.getValue()).setStoreId(x.getStoreId())
.setStoreName(ObjectUtils.isNotEmpty(storeMap.get(x.getStoreId())) ? storeMap.get(x.getStoreId()).getStoreName() : "")
.setOrderNum(this.positionToNumber(x.getPosition())).setStoreProdId(storeProdId).setFocus(Boolean.FALSE)
.setPrice(ObjectUtils.isNotEmpty(prodPriceAndMainPicMap.get(storeProdId)) ? prodPriceAndMainPicMap.get(storeProdId).getMinPrice() : null)
.setProdArtNum(ObjectUtils.isNotEmpty(prodPriceAndMainPicMap.get(storeProdId)) ? prodPriceAndMainPicMap.get(storeProdId).getProdArtNum() : "")
.setMainPicUrl(ObjectUtils.isNotEmpty(prodPriceAndMainPicMap.get(storeProdId)) ? prodPriceAndMainPicMap.get(storeProdId).getMainPicUrl() : "");
}).collect(Collectors.toList());
}
// 获取当前登录用户
Long userId = SecurityUtils.getUserIdSafe();
if (ObjectUtils.isNotEmpty(userId)) {
// 获取当前用户关注档口
List<UserSubscriptions> subsList = this.userSubsMapper.selectList(new LambdaQueryWrapper<UserSubscriptions>()
.eq(UserSubscriptions::getDelFlag, Constants.UNDELETED).eq(UserSubscriptions::getUserId, userId));
if (CollectionUtils.isNotEmpty(subsList)) {
final List<Long> focusStoreIdList = subsList.stream().map(UserSubscriptions::getStoreId).collect(Collectors.toList());
pcSearchResAdvertList.forEach(x -> x.setFocus(focusStoreIdList.contains(x.getStoreId()) ? Boolean.TRUE : Boolean.FALSE));
}
}
// 放到redis中过期时间为1天
redisCache.setCacheObject(CacheConstants.PC_ADVERT + CacheConstants.PC_SEARCH_RESULT_ADVERT, pcSearchResAdvertList, 1, TimeUnit.DAYS);
return pcSearchResAdvertList;
}
/**
* PC
*
@ -1660,6 +1760,34 @@ public class WebsitePCServiceImpl implements IWebsitePCService {
return tempList.stream().limit(limitCount).collect(Collectors.toList());
}
/**
*
* @param advertRoundList 广
* @param prodPriceAndMainPicMap map
* @param existProdIdList ID
* @param limitCount
* @return
*/
private List<PCSearchResultAdvertDTO> fillPcSearchResultAdvertList(List<AdvertRound> advertRoundList, Map<Long, StoreProdPriceAndMainPicDTO> prodPriceAndMainPicMap,
Map<Long, Store> storeMap, List<Long> existProdIdList, int limitCount) {
List<PCSearchResultAdvertDTO> tempList = new ArrayList<>();
advertRoundList.stream().collect(Collectors.groupingBy(AdvertRound::getStoreId))
.forEach((storeId, list) -> {
AdvertRound advertRound = list.stream().filter(x -> StringUtils.isNotBlank(x.getProdIdStr()))
.filter(x -> CollectionUtils.isEmpty(existProdIdList) || !existProdIdList.contains(Long.parseLong(x.getProdIdStr()))).findAny().orElse(null);
if (ObjectUtils.isNotEmpty(advertRound)) {
final Long storeProdId = Long.parseLong(advertRound.getProdIdStr());
tempList.add(new PCSearchResultAdvertDTO().setDisplayType(AdDisplayType.PRODUCT.getValue()).setStoreProdId(storeProdId).setStoreId(storeId)
.setStoreName(ObjectUtils.isNotEmpty(storeMap.get(storeId)) ? storeMap.get(storeId).getStoreName() : "").setFocus(Boolean.FALSE)
.setPayPrice(ObjectUtils.isNotEmpty(advertRound.getPayPrice()) ? advertRound.getPayPrice() : BigDecimal.ZERO)
.setPrice(ObjectUtils.isNotEmpty(prodPriceAndMainPicMap.get(storeProdId)) ? prodPriceAndMainPicMap.get(storeProdId).getMinPrice() : null)
.setProdArtNum(ObjectUtils.isNotEmpty(prodPriceAndMainPicMap.get(storeProdId)) ? prodPriceAndMainPicMap.get(storeProdId).getProdArtNum() : "")
.setMainPicUrl(ObjectUtils.isNotEmpty(prodPriceAndMainPicMap.get(storeProdId)) ? prodPriceAndMainPicMap.get(storeProdId).getMainPicUrl() : ""));
}
});
return tempList.stream().limit(limitCount).collect(Collectors.toList());
}
private static FieldValue newFieldValue(String value) {
return FieldValue.of(value);
}