Browse Source

对接拼团数据1

master
wangfukang 1 month ago
parent
commit
ea2d8c2990
  1. 7
      hiver-admin/src/main/resources/application.yml
  2. 14
      hiver-admin/test-output/test-report.html
  3. 4
      hiver-core/src/main/java/cc/hiver/core/common/utils/SecurityUtil.java
  4. 118
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/CommentController.java
  5. 66
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallAdPositionController.java
  6. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopController.java
  7. 6
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java
  8. 17
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallAdPositionMapper.java
  9. 17
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallAdProductMapper.java
  10. 56
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallAdPosition.java
  11. 39
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallAdProduct.java
  12. 32
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/query/MallAdPositionQuery.java
  13. 41
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallAdPositionService.java
  14. 191
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallAdPositionServiceImpl.java
  15. 231
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/AdPositionCacheUtil.java
  16. 7
      hiver-modules/hiver-mall/src/main/resources/mapper/CommentMapper.xml
  17. 62
      hiver-modules/hiver-mall/src/main/resources/mapper/MallAdPositionMapper.xml
  18. 44
      hiver-modules/hiver-mall/src/main/resources/mapper/MallAdProductMapper.xml

7
hiver-admin/src/main/resources/application.yml

@ -77,6 +77,13 @@ spring:
port: 6379
# 超时时间 Duration类型 3秒
timeout: 3S
# 增加 Jedis 连接池配置
jedis:
pool:
max-active: 20 # 最大连接数,建议从默认的 8 调大到 20 或更高
max-wait: 2000ms # 获取连接的最大等待时间,避免无限期阻塞
max-idle: 10 # 最大空闲连接数
min-idle: 1 # 最小空闲连接数
# Elasticsearch
data:
elasticsearch:

14
hiver-admin/test-output/test-report.html

@ -35,7 +35,7 @@
<a href="#"><span class="badge badge-primary">Hiver</span></a>
</li>
<li class="m-r-10">
<a href="#"><span class="badge badge-primary">四月 26, 2026 11:40:47</span></a>
<a href="#"><span class="badge badge-primary">四月 27, 2026 15:09:48</span></a>
</li>
</ul>
</div>
@ -84,7 +84,7 @@
<div class="test-detail">
<span class="meta text-white badge badge-sm"></span>
<p class="name">passTest</p>
<p class="text-sm"><span>11:40:47 上</span> / <span>0.015 secs</span></p>
<p class="text-sm"><span>15:09:49 下</span> / <span>0.015 secs</span></p>
</div>
<div class="test-contents d-none">
<div class="detail-head">
@ -92,8 +92,8 @@
<div class="info">
<div class='float-right'><span class='badge badge-default'>#test-id=1</span></div>
<h5 class="test-status text-pass">passTest</h5>
<span class='badge badge-success'>04.26.2026 11:40:47</span>
<span class='badge badge-danger'>04.26.2026 11:40:47</span>
<span class='badge badge-success'>04.27.2026 15:09:49</span>
<span class='badge badge-danger'>04.27.2026 15:09:49</span>
<span class='badge badge-default'>0.015 secs</span>
</div>
<div class="m-t-10 m-l-5"></div>
@ -104,7 +104,7 @@
<tbody>
<tr class="event-row">
<td><span class="badge log pass-bg">Pass</span></td>
<td>11:40:47</td>
<td>15:09:49</td>
<td>
Test passed
</td>
@ -128,13 +128,13 @@
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Started</p>
<h3>四月 26, 2026 11:40:47</h3>
<h3>四月 27, 2026 15:09:48</h3>
</div></div>
</div>
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Ended</p>
<h3>四月 26, 2026 11:40:47</h3>
<h3>四月 27, 2026 15:09:49</h3>
</div></div>
</div>
<div class="col-md-3">

4
hiver-core/src/main/java/cc/hiver/core/common/utils/SecurityUtil.java

@ -303,7 +303,7 @@ public class SecurityUtil {
token = IdUtil.simpleUUID();
tokenUser = new TokenUser(user, tokenProperties.getStorePerms(), saved);
// 单设备登录 之前的token失效
if (tokenProperties.getSdl()) {
/*if (tokenProperties.getSdl()) {
final String oldToken = redisTemplateHelper.get(SecurityConstant.USER_TOKEN + user.getUsername() + "type:" + user.getType());
if (CharSequenceUtil.isNotBlank(oldToken)) {
redisTemplateHelper.delete(SecurityConstant.TOKEN_PRE + oldToken);
@ -318,7 +318,7 @@ public class SecurityUtil {
redisTemplateHelper.delete(regionKey);
}
}
}*/
// 是否记住账号/保存登录
if (saved) {
redisTemplateHelper.set(SecurityConstant.USER_TOKEN + user.getUsername() + "type:" + user.getType(), token, tokenProperties.getSaveLoginTime(), TimeUnit.DAYS);

118
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/CommentController.java

@ -86,66 +86,68 @@ public class CommentController {
}else{
e.setLevel(ShopConstant.SHOP_STATUS_NORMAL);
}
if(e.getShopId().toUpperCase().startsWith("W")){
//更新配送员评分
Worker worker = workerServiceImpl.findByWorkerId(e.getShopId());
Integer oldCount = worker.getOrderCount() == null ? 0 : worker.getOrderCount();
BigDecimal oldScore = worker.getScore();
BigDecimal score = e.getScore();
// 1. 更新单量
int newCount = oldCount + 1;
// 2. 计算新评分:(旧总分 + 本次评分) / 新单量
BigDecimal newScore = oldScore.multiply(new BigDecimal(oldCount))
.add(score)
.divide(new BigDecimal(newCount), 2, RoundingMode.HALF_UP); // 保留2位小数
DecimalFormat df = new DecimalFormat("00.00");
String formattedResult = df.format(newScore);
worker.setScore(new BigDecimal(formattedResult));
// 3. 保存更新
workerServiceImpl.update(worker);
//更新缓存
WorkerRedisVo workerRedisVo = workerRedisCacheUtil.get(worker.getRegion(), worker.getWorkerId());
if(workerRedisVo != null){
workerRedisVo.setWorker(worker);
workerRedisCacheUtil.update(worker.getRegion(), workerRedisVo);
}
}else{
//更新店铺评分
Shop shop = shopService.findById(e.getShopId());
Integer oldSaleCount = shop.getSaleCount() == null ? 0 : shop.getSaleCount();
BigDecimal oldScore = shop.getShopScore();
BigDecimal score = e.getScore();
int newCount = oldSaleCount + 1;
// 2. 计算新评分:(旧总分 + 本次评分) / 新单量
BigDecimal newScore = oldScore.multiply(new BigDecimal(oldSaleCount))
.add(score)
.divide(new BigDecimal(newCount), 2, RoundingMode.HALF_UP); // 保留2位小数
DecimalFormat df = new DecimalFormat("00.00");
String formattedResult = df.format(newScore);
shop.setShopScore(new BigDecimal(formattedResult));
shopService.update(shop);
String shopCacheKey = "SHOP_CACHE:" + shop.getRegionId();
String shopJson = (String) redisTemplateHelper.hGet(shopCacheKey, shop.getId());
if (org.apache.commons.lang3.StringUtils.isNotBlank(shopJson)) {
ShopCacheDTO cacheDTO = JSONUtil.toBean(shopJson, ShopCacheDTO.class);
if (cacheDTO.getShop() != null) {
cacheDTO.getShop().setShopScore(new BigDecimal(formattedResult));
redisTemplateHelper.hPut(shopCacheKey, shop.getId(), JSONUtil.toJsonStr(cacheDTO));
if(e.getLevel() == ShopConstant.SHOP_STATUS_LOCK){
if(e.getShopId().toUpperCase().startsWith("W")){
//更新配送员评分
Worker worker = workerServiceImpl.findByWorkerId(e.getShopId());
Integer oldCount = worker.getOrderCount() == null ? 0 : worker.getOrderCount();
BigDecimal oldScore = worker.getScore();
BigDecimal score = e.getScore();
// 1. 更新单量
int newCount = oldCount + 1;
// 2. 计算新评分:(旧总分 + 本次评分) / 新单量
BigDecimal newScore = oldScore.multiply(new BigDecimal(oldCount))
.add(score)
.divide(new BigDecimal(newCount), 2, RoundingMode.HALF_UP); // 保留2位小数
DecimalFormat df = new DecimalFormat("00.00");
String formattedResult = df.format(newScore);
worker.setScore(new BigDecimal(formattedResult));
// 3. 保存更新
workerServiceImpl.update(worker);
//更新缓存
WorkerRedisVo workerRedisVo = workerRedisCacheUtil.get(worker.getRegion(), worker.getWorkerId());
if(workerRedisVo != null){
workerRedisVo.setWorker(worker);
workerRedisCacheUtil.update(worker.getRegion(), workerRedisVo);
}
}else{
//更新店铺评分
Shop shop = shopService.findById(e.getShopId());
Integer oldSaleCount = shop.getSaleCount() == null ? 0 : shop.getSaleCount();
BigDecimal oldScore = shop.getShopScore();
BigDecimal score = e.getScore();
int newCount = oldSaleCount + 1;
// 2. 计算新评分:(旧总分 + 本次评分) / 新单量
BigDecimal newScore = oldScore.multiply(new BigDecimal(oldSaleCount))
.add(score)
.divide(new BigDecimal(newCount), 2, RoundingMode.HALF_UP); // 保留2位小数
DecimalFormat df = new DecimalFormat("00.00");
String formattedResult = df.format(newScore);
shop.setShopScore(new BigDecimal(formattedResult));
shopService.update(shop);
String shopCacheKey = "SHOP_CACHE:" + shop.getRegionId();
String shopJson = (String) redisTemplateHelper.hGet(shopCacheKey, shop.getId());
if (org.apache.commons.lang3.StringUtils.isNotBlank(shopJson)) {
ShopCacheDTO cacheDTO = JSONUtil.toBean(shopJson, ShopCacheDTO.class);
if (cacheDTO.getShop() != null) {
cacheDTO.getShop().setShopScore(new BigDecimal(formattedResult));
redisTemplateHelper.hPut(shopCacheKey, shop.getId(), JSONUtil.toJsonStr(cacheDTO));
}
}
}
}
//放进缓存 小于4分
if(e.getScore().compareTo(new BigDecimal(4)) < 0){
commentCacheUtil.put(e.getShopId(),e);
//放进缓存 小于4分
if(e.getScore().compareTo(new BigDecimal(4)) < 0){
commentCacheUtil.put(e.getShopId(),e);
}
}
});
if(comment != null && StringUtils.isNotBlank(comment.get(0).getParentId())){

66
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallAdPositionController.java

@ -0,0 +1,66 @@
package cc.hiver.mall.controller;
import cc.hiver.core.common.utils.ResultUtil;
import cc.hiver.core.common.vo.Result;
import cc.hiver.mall.entity.MallAdPosition;
import cc.hiver.mall.pojo.query.MallAdPositionQuery;
import cc.hiver.mall.service.mybatis.MallAdPositionService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/hiver/mall/adPosition")
@Api(tags = "广告位接口")
public class MallAdPositionController {
@Autowired
private MallAdPositionService mallAdPositionService;
@PostMapping("/add")
@ApiOperation(value = "新增广告位")
public Result<Object> add(@RequestBody MallAdPosition adPosition) {
mallAdPositionService.addAdPosition(adPosition);
return ResultUtil.success("新增成功");
}
@PostMapping("/update")
@ApiOperation(value = "修改广告位")
public Result<Object> update(@RequestBody MallAdPosition adPosition) {
mallAdPositionService.updateAdPosition(adPosition);
return ResultUtil.success("修改成功");
}
@PostMapping("/delete/{id}")
@ApiOperation(value = "删除广告位")
public Result<Object> delete(@PathVariable String id) {
mallAdPositionService.deleteAdPosition(id);
return ResultUtil.success("删除成功");
}
@GetMapping("/detail/{id}")
@ApiOperation(value = "获取广告位详情")
public Result<MallAdPosition> detail(@PathVariable String id) {
MallAdPosition detail = mallAdPositionService.getAdPositionDetail(id);
return new ResultUtil<MallAdPosition>().setData(detail);
}
@RequestMapping(value = "/list", method = RequestMethod.POST)
@ApiOperation(value = "分页查询广告位列表")
public Result<IPage<MallAdPosition>> list(@RequestBody MallAdPositionQuery query) {
return new ResultUtil<IPage<MallAdPosition>>().setData(mallAdPositionService.selectPageVO(query));
}
@GetMapping("/region")
@ApiOperation(value = "根据区域ID获取广告位列表(缓存优先)")
public Result<List<MallAdPosition>> getByRegionId(@RequestParam(value = "regionId", required = true) String regionId ,
@RequestParam(value = "paths", required = true) List<String> paths) {
return new ResultUtil<List<MallAdPosition>>().setData(mallAdPositionService.getAdsByRegionId(regionId,paths));
}
}

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopController.java

@ -251,7 +251,7 @@ public class ShopController {
}
}
//已禁用
if(s.getStatus() < 1 && CharSequenceUtil.isBlank(shop.getShopIcon())){
if(s.getStatus() != null && s.getStatus() < 1 && CharSequenceUtil.isBlank(shop.getShopIcon())){
continue;
}
if (CharSequenceUtil.isNotBlank(shop.getShopType())) {

6
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java

@ -81,9 +81,9 @@ public class WorkerController {
@RequestMapping(value = "/getByCondition", method = RequestMethod.POST)
@ApiOperation(value = "多条件分页获取订单列表")
public Result<Page<Worker>> getByCondition(@RequestBody WorkerQueryVO worker) {
Page<Worker> page = (Page<Worker>) workerService.findByCondition(worker);
return new ResultUtil<Page<Worker>>().setData(page);
public Result<org.springframework.data.domain.Page<Worker>> getByCondition(@RequestBody WorkerQueryVO worker) {
org.springframework.data.domain.Page<Worker> page = workerService.findByCondition(worker);
return new ResultUtil<org.springframework.data.domain.Page<Worker>>().setData(page);
}

17
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallAdPositionMapper.java

@ -0,0 +1,17 @@
package cc.hiver.mall.dao.mapper;
import cc.hiver.mall.entity.MallAdPosition;
import cc.hiver.mall.pojo.query.MallAdPositionQuery;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MallAdPositionMapper extends BaseMapper<MallAdPosition> {
IPage<MallAdPosition> selectPageVO(IPage<?> page, @Param("q") MallAdPositionQuery q);
List<MallAdPosition> selectByRegionId(@Param("regionId") String regionId);
}

17
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallAdProductMapper.java

@ -0,0 +1,17 @@
package cc.hiver.mall.dao.mapper;
import cc.hiver.mall.entity.MallAdProduct;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MallAdProductMapper extends BaseMapper<MallAdProduct> {
List<MallAdProduct> selectByAdId(@Param("adId") String adId);
List<MallAdProduct> selectByAdIds(@Param("adIds") List<String> adIds);
int deleteByAdId(@Param("adId") String adId);
}

56
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallAdPosition.java

@ -0,0 +1,56 @@
package cc.hiver.mall.entity;
import cc.hiver.core.base.HiverBaseEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "广告位表")
@TableName(value = "t_mall_ad_position", autoResultMap = true)
public class MallAdPosition extends HiverBaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "放置位置:如 home_banner, home_popup, category_top 等")
private String position;
@ApiModelProperty(value = "商家名称")
private String merchantName;
@ApiModelProperty(value = "商家id")
private String merchantId;
@ApiModelProperty(value = "商家主图")
private String merchantImage;
@ApiModelProperty(value = "广告图片")
private String adImage;
@ApiModelProperty(value = "广告标题")
private String title;
@ApiModelProperty(value = "跳转链接")
private String linkUrl;
@ApiModelProperty(value = "跳转链接参数(JSON格式)")
private String linkParams;
@ApiModelProperty(value = "区域id")
private String regionId;
@ApiModelProperty(value = "排序值(越小越靠前)")
private Integer sortOrder;
@ApiModelProperty(value = "状态:0-下架,1-上架")
private Integer status;
@ApiModelProperty(value = "广告关联商品列表")
@TableField(exist = false)
private List<MallAdProduct> productList;
}

39
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallAdProduct.java

@ -0,0 +1,39 @@
package cc.hiver.mall.entity;
import cc.hiver.core.base.HiverBaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "广告位商品表")
@TableName(value = "t_mall_ad_product", autoResultMap = true)
public class MallAdProduct extends HiverBaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "广告位id")
private String adId;
@ApiModelProperty(value = "商品图片")
private String productImage;
@ApiModelProperty(value = "商品名称")
private String productName;
@ApiModelProperty(value = "商品原价")
private BigDecimal originalPrice;
@ApiModelProperty(value = "商品拼团价格")
private BigDecimal groupPrice;
@ApiModelProperty(value = "几人团")
private Integer groupMembers;
@ApiModelProperty(value = "排序值")
private Integer sortOrder;
}

32
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/query/MallAdPositionQuery.java

@ -0,0 +1,32 @@
package cc.hiver.mall.pojo.query;
import cc.hiver.core.base.HiverBasePageQuery;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 广告位分页查询对象
*/
@ApiModel("广告位分页查询对象")
@Data
public class MallAdPositionQuery extends HiverBasePageQuery {
@ApiModelProperty(value = "放置位置")
private String position;
@ApiModelProperty(value = "商家名称")
private String merchantName;
@ApiModelProperty(value = "商家id")
private String merchantId;
@ApiModelProperty(value = "广告标题")
private String title;
@ApiModelProperty(value = "区域id")
private String regionId;
@ApiModelProperty(value = "状态:0-下架,1-上架")
private Integer status;
}

41
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/MallAdPositionService.java

@ -0,0 +1,41 @@
package cc.hiver.mall.service.mybatis;
import cc.hiver.mall.entity.MallAdPosition;
import cc.hiver.mall.pojo.query.MallAdPositionQuery;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface MallAdPositionService extends IService<MallAdPosition> {
/**
* 新增广告位含商品列表
*/
void addAdPosition(MallAdPosition adPosition);
/**
* 修改广告位含商品列表
*/
void updateAdPosition(MallAdPosition adPosition);
/**
* 删除广告位级联删除商品
*/
void deleteAdPosition(String id);
/**
* 获取广告位详情含商品列表
*/
MallAdPosition getAdPositionDetail(String id);
/**
* 分页查询广告位
*/
IPage<MallAdPosition> selectPageVO(MallAdPositionQuery query);
/**
* 根据 regionId 获取该区域所有广告位优先缓存
*/
List<MallAdPosition> getAdsByRegionId(String regionId,List<String> paths);
}

191
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallAdPositionServiceImpl.java

@ -0,0 +1,191 @@
package cc.hiver.mall.serviceimpl.mybatis;
import cc.hiver.mall.dao.mapper.MallAdPositionMapper;
import cc.hiver.mall.dao.mapper.MallAdProductMapper;
import cc.hiver.mall.entity.MallAdPosition;
import cc.hiver.mall.entity.MallAdProduct;
import cc.hiver.mall.pojo.query.MallAdPositionQuery;
import cc.hiver.mall.service.mybatis.MallAdPositionService;
import cc.hiver.mall.utils.AdPositionCacheUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Service
public class MallAdPositionServiceImpl extends ServiceImpl<MallAdPositionMapper, MallAdPosition>
implements MallAdPositionService {
@Autowired
private MallAdPositionMapper mallAdPositionMapper;
@Autowired
private MallAdProductMapper mallAdProductMapper;
@Autowired
private AdPositionCacheUtil adPositionCacheUtil;
@Override
@Transactional(rollbackFor = Exception.class)
public void addAdPosition(MallAdPosition adPosition) {
// 1. 保存广告位主表
this.save(adPosition);
// 2. 保存关联商品
if (adPosition.getProductList() != null && !adPosition.getProductList().isEmpty()) {
for (MallAdProduct product : adPosition.getProductList()) {
product.setAdId(adPosition.getId());
mallAdProductMapper.insert(product);
}
}
// 3. 更新缓存:将新广告位加入对应 regionId 的缓存
if (StringUtils.isNotBlank(adPosition.getRegionId())) {
// 重新组装完整对象(包含productList)
adPosition.setProductList(mallAdProductMapper.selectByAdId(adPosition.getId()));
adPositionCacheUtil.put(adPosition.getRegionId(), adPosition);
}
log.info("新增广告位成功: id={}, regionId={}", adPosition.getId(), adPosition.getRegionId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateAdPosition(MallAdPosition adPosition) {
// 获取旧数据,用于处理 regionId 变更时清除旧缓存
MallAdPosition oldAd = this.getById(adPosition.getId());
// 1. 更新广告位主表
this.updateById(adPosition);
// 2. 先删除旧的商品关联,再重新插入
mallAdProductMapper.deleteByAdId(adPosition.getId());
if (adPosition.getProductList() != null && !adPosition.getProductList().isEmpty()) {
for (MallAdProduct product : adPosition.getProductList()) {
product.setAdId(adPosition.getId());
mallAdProductMapper.insert(product);
}
}
// 3. 更新缓存
if (oldAd != null && StringUtils.isNotBlank(oldAd.getRegionId())) {
// 如果 regionId 发生变化,先删除旧 regionId 下的缓存
if (!oldAd.getRegionId().equals(adPosition.getRegionId())) {
adPositionCacheUtil.remove(oldAd.getRegionId(), adPosition.getId());
}
}
if (StringUtils.isNotBlank(adPosition.getRegionId())) {
adPosition.setProductList(mallAdProductMapper.selectByAdId(adPosition.getId()));
adPositionCacheUtil.update(adPosition.getRegionId(), adPosition);
}
log.info("修改广告位成功: id={}, regionId={}", adPosition.getId(), adPosition.getRegionId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteAdPosition(String id) {
MallAdPosition adPosition = this.getById(id);
if (adPosition == null) {
return;
}
// 1. 删除关联商品
mallAdProductMapper.deleteByAdId(id);
// 2. 删除广告位主表
this.removeById(id);
// 3. 删除缓存
if (StringUtils.isNotBlank(adPosition.getRegionId())) {
adPositionCacheUtil.remove(adPosition.getRegionId(), id);
}
log.info("删除广告位成功: id={}, regionId={}", id, adPosition.getRegionId());
}
@Override
public MallAdPosition getAdPositionDetail(String id) {
MallAdPosition adPosition = this.getById(id);
if (adPosition == null) {
return null;
}
// 查询关联商品
List<MallAdProduct> productList = mallAdProductMapper.selectByAdId(id);
adPosition.setProductList(productList);
return adPosition;
}
@Override
public IPage<MallAdPosition> selectPageVO(MallAdPositionQuery query) {
IPage<MallAdPosition> page = new Page<>(query.getPageNum(), query.getPageSize());
IPage<MallAdPosition> result = mallAdPositionMapper.selectPageVO(page, query);
// 为每条广告位填充商品列表
if (result.getRecords() != null && !result.getRecords().isEmpty()) {
List<String> adIds = result.getRecords().stream()
.map(MallAdPosition::getId)
.collect(Collectors.toList());
List<MallAdProduct> allProducts = mallAdProductMapper.selectByAdIds(adIds);
Map<String, List<MallAdProduct>> productMap = allProducts.stream()
.collect(Collectors.groupingBy(MallAdProduct::getAdId));
for (MallAdPosition ad : result.getRecords()) {
ad.setProductList(productMap.getOrDefault(ad.getId(), Collections.emptyList()));
}
}
return result;
}
@Override
public List<MallAdPosition> getAdsByRegionId(String regionId,List<String> paths) {
if (StringUtils.isBlank(regionId)) {
return Collections.emptyList();
}
// 1. 优先查询缓存
List<MallAdPosition> cached = adPositionCacheUtil.getAll(regionId);
List<MallAdPosition> cachedReturn = new ArrayList<>();
if (cached != null && !cached.isEmpty()) {
for(MallAdPosition ad : cached){
if(paths.contains(ad.getPosition())){
cachedReturn.add(ad);
}
}
return cachedReturn;
}
// 2. 缓存不存在,查询数据库
List<MallAdPosition> dbList = mallAdPositionMapper.selectByRegionId(regionId);
if (dbList != null && !dbList.isEmpty()) {
// 填充商品列表
List<String> adIds = dbList.stream()
.map(MallAdPosition::getId)
.collect(Collectors.toList());
List<MallAdProduct> allProducts = mallAdProductMapper.selectByAdIds(adIds);
Map<String, List<MallAdProduct>> productMap = allProducts.stream()
.collect(Collectors.groupingBy(MallAdProduct::getAdId));
for (MallAdPosition ad : dbList) {
ad.setProductList(productMap.getOrDefault(ad.getId(), Collections.emptyList()));
if(paths.contains(ad.getPosition())){
cachedReturn.add(ad);
}
}
// 3. 写入缓存
adPositionCacheUtil.putAll(regionId, dbList);
log.info("从数据库加载广告位并写入缓存: regionId={}, count={}", regionId, dbList.size());
}
return cachedReturn;
}
}

231
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/AdPositionCacheUtil.java

@ -0,0 +1,231 @@
package cc.hiver.mall.utils;
import cc.hiver.core.common.redis.RedisTemplateHelper;
import cc.hiver.mall.entity.MallAdPosition;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 广告位缓存工具类
* <p>
* 底层使用 Redis Hash 结构 regionId 维度管理广告位缓存
* <pre>
* Key = AD_POSITION:{regionId}
* Field = adPositionId
* Value = MallAdPosition JSON 序列化 productList
* </pre>
*
* @author system
*/
@Slf4j
@Component
public class AdPositionCacheUtil {
/** Redis Key 前缀 */
private static final String KEY_PREFIX = "AD_POSITION:";
@Autowired
private RedisTemplateHelper redisTemplateHelper;
// ================================================================
// Key 构建
// ================================================================
private String buildKey(String regionId) {
return KEY_PREFIX + regionId;
}
// ================================================================
// 存放(put)
// ================================================================
/**
* 存放单个广告位到缓存
*
* @param regionId 区域ID
* @param adPosition 广告位必须包含有效的 id
*/
public void put(String regionId, MallAdPosition adPosition) {
if (StringUtils.isBlank(regionId) || adPosition == null || StringUtils.isBlank(adPosition.getId())) {
log.info("AdPositionCacheUtil.put 参数无效, regionId={}, adPosition={}", regionId, adPosition);
return;
}
try {
String key = buildKey(regionId);
String json = JSONUtil.toJsonStr(adPosition);
redisTemplateHelper.hPut(key, adPosition.getId(), json);
log.info("缓存广告位: regionId={}, adId={}", regionId, adPosition.getId());
} catch (Exception e) {
log.info("缓存广告位失败: regionId={}, adId={}", regionId, adPosition.getId(), e);
}
}
/**
* 批量存放广告位到缓存通常用于缓存预热 / 首次加载
*
* @param regionId 区域ID
* @param ads 广告位列表
*/
public void putAll(String regionId, List<MallAdPosition> ads) {
if (StringUtils.isBlank(regionId) || ads == null || ads.isEmpty()) {
return;
}
try {
String key = buildKey(regionId);
Map<String, String> map = new java.util.LinkedHashMap<>(ads.size());
for (MallAdPosition ad : ads) {
if (ad != null && StringUtils.isNotBlank(ad.getId())) {
map.put(ad.getId(), JSONUtil.toJsonStr(ad));
}
}
if (!map.isEmpty()) {
redisTemplateHelper.hPutAll(key, map);
log.info("批量缓存广告位: regionId={}, count={}", regionId, map.size());
}
} catch (Exception e) {
log.info("批量缓存广告位失败: regionId={}", regionId, e);
}
}
// ================================================================
// 删除(remove)
// ================================================================
/**
* 根据 regionId 和广告位ID 从缓存中删除指定广告位
*
* @param regionId 区域ID
* @param adId 广告位ID
*/
public void remove(String regionId, String adId) {
if (StringUtils.isBlank(regionId) || StringUtils.isBlank(adId)) {
log.info("AdPositionCacheUtil.remove 参数无效, regionId={}, adId={}", regionId, adId);
return;
}
try {
String key = buildKey(regionId);
redisTemplateHelper.hDelete(key, adId);
log.info("删除广告位缓存: regionId={}, adId={}", regionId, adId);
} catch (Exception e) {
log.info("删除广告位缓存失败: regionId={}, adId={}", regionId, adId, e);
}
}
/**
* 清除指定区域的所有广告位缓存
*
* @param regionId 区域ID
*/
public void removeAll(String regionId) {
if (StringUtils.isBlank(regionId)) {
return;
}
try {
redisTemplateHelper.delete(buildKey(regionId));
log.info("清除全部广告位缓存: regionId={}", regionId);
} catch (Exception e) {
log.info("清除全部广告位缓存失败: regionId={}", regionId, e);
}
}
// ================================================================
// 更新(update)
// ================================================================
/**
* 更新缓存中的广告位信息覆盖写入
*
* @param regionId 区域ID
* @param adPosition 更新后的广告位
*/
public void update(String regionId, MallAdPosition adPosition) {
if (StringUtils.isBlank(regionId) || adPosition == null || StringUtils.isBlank(adPosition.getId())) {
log.info("AdPositionCacheUtil.update 参数无效, regionId={}, adPosition={}", regionId, adPosition);
return;
}
try {
put(regionId, adPosition);
log.info("更新广告位缓存: regionId={}, adId={}", regionId, adPosition.getId());
} catch (Exception e) {
log.info("更新广告位缓存失败: regionId={}, adId={}", regionId, adPosition.getId(), e);
}
}
// ================================================================
// 查询(get)
// ================================================================
/**
* 获取指定区域的全部广告位缓存
*
* @param regionId 区域ID
* @return 缓存的广告位列表缓存不存在时返回 null调用方可据此判断是否需要回源查库
*/
public List<MallAdPosition> getAll(String regionId) {
if (StringUtils.isBlank(regionId)) {
return null;
}
try {
String key = buildKey(regionId);
Map<Object, Object> entries = redisTemplateHelper.hGetAll(key);
if (entries == null || entries.isEmpty()) {
return null;
}
List<MallAdPosition> result = new ArrayList<>(entries.size());
for (Object value : entries.values()) {
if (value != null) {
result.add(JSONUtil.toBean(value.toString(), MallAdPosition.class));
}
}
return result;
} catch (Exception e) {
log.info("获取广告位缓存失败: regionId={}", regionId, e);
return null;
}
}
/**
* 获取缓存中的单个广告位
*
* @param regionId 区域ID
* @param adId 广告位ID
* @return 缓存的广告位不存在时返回 null
*/
public MallAdPosition get(String regionId, String adId) {
if (StringUtils.isBlank(regionId) || StringUtils.isBlank(adId)) {
return null;
}
try {
String key = buildKey(regionId);
Object value = redisTemplateHelper.hGet(key, adId);
if (value == null) {
return null;
}
return JSONUtil.toBean(value.toString(), MallAdPosition.class);
} catch (Exception e) {
log.info("获取单个广告位缓存失败: regionId={}, adId={}", regionId, adId, e);
return null;
}
}
/**
* 判断该区域的广告位缓存是否已存在
*
* @param regionId 区域ID
* @return true 表示缓存存在
*/
public boolean exists(String regionId) {
if (StringUtils.isBlank(regionId)) {
return false;
}
Boolean hasKey = redisTemplateHelper.hasKey(buildKey(regionId));
return hasKey != null && hasKey;
}
}

7
hiver-modules/hiver-mall/src/main/resources/mapper/CommentMapper.xml

@ -38,10 +38,13 @@
<if test="comment.picture != 0" >
and picture != ''
</if>
<if test="comment.hasAnswer != null" >
<if test="comment.hasAnswer != null and comment.hasAnswer != 0" >
and has_answer = #{comment.hasAnswer,jdbcType=Integer}
</if>
<if test="comment.score != 0" >
<if test="comment.hasAnswer != null and comment.hasAnswer == 0" >
and has_answer is null
</if>
<if test="comment.score != null and comment.score != 0" >
and score <![CDATA[ < ]]> 4
</if>
order by create_time desc

62
hiver-modules/hiver-mall/src/main/resources/mapper/MallAdPositionMapper.xml

@ -0,0 +1,62 @@
<?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="cc.hiver.mall.dao.mapper.MallAdPositionMapper">
<resultMap id="adPositionMap" type="cc.hiver.mall.entity.MallAdPosition">
<id column="id" property="id"/>
<result column="position" property="position"/>
<result column="merchant_name" property="merchantName"/>
<result column="merchant_id" property="merchantId"/>
<result column="merchant_image" property="merchantImage"/>
<result column="ad_image" property="adImage"/>
<result column="title" property="title"/>
<result column="link_url" property="linkUrl"/>
<result column="link_params" property="linkParams"/>
<result column="region_id" property="regionId"/>
<result column="sort_order" property="sortOrder"/>
<result column="status" property="status"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<sql id="selectColumns">
id, position, merchant_name, merchant_id, merchant_image,
ad_image, title, link_url, link_params, region_id,
sort_order, status, create_time, update_time
</sql>
<select id="selectPageVO" resultMap="adPositionMap">
SELECT <include refid="selectColumns"/>
FROM t_mall_ad_position
<where>
del_flag = 0
<if test="q.position != null and q.position != ''">
AND position = #{q.position}
</if>
<if test="q.merchantName != null and q.merchantName != ''">
AND merchant_name LIKE CONCAT('%', #{q.merchantName}, '%')
</if>
<if test="q.merchantId != null and q.merchantId != ''">
AND merchant_id = #{q.merchantId}
</if>
<if test="q.title != null and q.title != ''">
AND title LIKE CONCAT('%', #{q.title}, '%')
</if>
<if test="q.regionId != null and q.regionId != ''">
AND region_id = #{q.regionId}
</if>
<if test="q.status != null">
AND status = #{q.status}
</if>
</where>
ORDER BY sort_order ASC, id DESC
</select>
<select id="selectByRegionId" resultMap="adPositionMap">
SELECT <include refid="selectColumns"/>
FROM t_mall_ad_position
WHERE del_flag = 0 AND status = 1 AND region_id = #{regionId}
ORDER BY sort_order ASC, id DESC
</select>
</mapper>

44
hiver-modules/hiver-mall/src/main/resources/mapper/MallAdProductMapper.xml

@ -0,0 +1,44 @@
<?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="cc.hiver.mall.dao.mapper.MallAdProductMapper">
<resultMap id="adProductMap" type="cc.hiver.mall.entity.MallAdProduct">
<id column="id" property="id"/>
<result column="ad_id" property="adId"/>
<result column="product_image" property="productImage"/>
<result column="product_name" property="productName"/>
<result column="original_price" property="originalPrice"/>
<result column="group_price" property="groupPrice"/>
<result column="group_members" property="groupMembers"/>
<result column="sort_order" property="sortOrder"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<sql id="selectColumns">
id, ad_id, product_image, product_name, original_price,
group_price, group_members, sort_order, create_time, update_time
</sql>
<select id="selectByAdId" resultMap="adProductMap">
SELECT <include refid="selectColumns"/>
FROM t_mall_ad_product
WHERE del_flag = 0 AND ad_id = #{adId}
ORDER BY sort_order ASC, id ASC
</select>
<select id="selectByAdIds" resultMap="adProductMap">
SELECT <include refid="selectColumns"/>
FROM t_mall_ad_product
WHERE del_flag = 0 AND ad_id IN
<foreach collection="adIds" item="adId" open="(" separator="," close=")">
#{adId}
</foreach>
ORDER BY sort_order ASC, id ASC
</select>
<delete id="deleteByAdId">
DELETE FROM t_mall_ad_product WHERE ad_id = #{adId}
</delete>
</mapper>
Loading…
Cancel
Save