Browse Source

对接拼团数据1

master
wangfukang 3 weeks ago
parent
commit
93a8cac771
  1. 3
      .vscode/settings.json
  2. 16
      hiver-admin/test-output/test-report.html
  3. 1
      hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/AuthController.java
  4. 2
      hiver-modules/hiver-base/src/main/java/cc/hiver/base/vo/RegisterShopVo.java
  5. 216
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSeckillGroupController.java
  6. 66
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/SeckillGroupController.java
  7. 14
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopController.java
  8. 9
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WechatPayController.java
  9. 9
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/SeckillGroupCategoryMapper.java
  10. 9
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/SeckillGroupProductMapper.java
  11. 2
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallOrder.java
  12. 32
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/SeckillGroupCategory.java
  13. 95
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/SeckillGroupProduct.java
  14. 8
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/Shop.java
  15. 3
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/dto/CreateOrderDTO.java
  16. 55
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/dto/SeckillGroupProductDTO.java
  17. 32
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/query/SeckillGroupProductQuery.java
  18. 89
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/vo/SeckillGroupProductVO.java
  19. 17
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/SeckillGroupCategoryService.java
  20. 29
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/SeckillGroupProductService.java
  21. 10
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopServiceImpl.java
  22. 1
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java
  23. 97
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/SeckillGroupCategoryServiceImpl.java
  24. 509
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/SeckillGroupProductServiceImpl.java
  25. 17
      hiver-modules/hiver-mall/src/main/resources/db/alter_snapshot_fields.sql
  26. 54
      hiver-modules/hiver-mall/src/main/resources/db/seckill_group.sql
  27. 11
      hiver-modules/hiver-mall/src/main/resources/mapper/MallDeliveryOrderMapper.xml
  28. 7
      hiver-modules/hiver-mall/src/main/resources/mapper/MallOrderMapper.xml
  29. 18
      hiver-modules/hiver-mall/src/main/resources/mapper/SeckillGroupCategoryMapper.xml
  30. 36
      hiver-modules/hiver-mall/src/main/resources/mapper/SeckillGroupProductMapper.xml
  31. 24
      hiver-modules/hiver-mall/src/main/resources/mapper/ShopMapper.xml
  32. 73
      hiver-modules/hiver-mall/src/main/resources/sql/init_coupon.sql

3
.vscode/settings.json

@ -0,0 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic"
}

16
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">五月 13, 2026 18:10:53</span></a>
<a href="#"><span class="badge badge-primary">五月 16, 2026 17:37:44</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>18:10:53 下午</span> / <span>0.017 secs</span></p>
<p class="text-sm"><span>17:37:45 下午</span> / <span>0.018 secs</span></p>
</div>
<div class="test-contents d-none">
<div class="detail-head">
@ -92,9 +92,9 @@
<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'>05.13.2026 18:10:53</span>
<span class='badge badge-danger'>05.13.2026 18:10:53</span>
<span class='badge badge-default'>0.017 secs</span>
<span class='badge badge-success'>05.16.2026 17:37:45</span>
<span class='badge badge-danger'>05.16.2026 17:37:45</span>
<span class='badge badge-default'>0.018 secs</span>
</div>
<div class="m-t-10 m-l-5"></div>
</div>
@ -104,7 +104,7 @@
<tbody>
<tr class="event-row">
<td><span class="badge log pass-bg">Pass</span></td>
<td>18:10:53</td>
<td>17:37:45</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>五月 13, 2026 18:10:53</h3>
<h3>五月 16, 2026 17:37:44</h3>
</div></div>
</div>
<div class="col-md-3">
<div class="card"><div class="card-body">
<p class="m-b-0">Ended</p>
<h3>五月 13, 2026 18:10:53</h3>
<h3>五月 16, 2026 17:37:45</h3>
</div></div>
</div>
<div class="col-md-3">

1
hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/AuthController.java

@ -430,6 +430,7 @@ public class AuthController {
shop.setShopLecard(registerShopVo.getShopLecard());
shop.setAliAccount(registerShopVo.getAliAccount());
shop.setAliName(registerShopVo.getAliName());
shop.setMerchantType(registerShopVo.getMerchantType());
shop.setRegion(registerShopVo.getRegion());
shop.setRegionId(registerShopVo.getRegionId());
shop.setIsStudent(registerShopVo.getIsStudent());

2
hiver-modules/hiver-base/src/main/java/cc/hiver/base/vo/RegisterShopVo.java

@ -17,6 +17,8 @@ public class RegisterShopVo {
private Integer isStudent;
private Integer merchantType;
private String unionid;
private String password;

216
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminSeckillGroupController.java

@ -0,0 +1,216 @@
package cc.hiver.mall.controller;
import cc.hiver.core.common.utils.ResultUtil;
import cc.hiver.core.common.vo.Result;
import cc.hiver.mall.entity.SeckillGroupCategory;
import cc.hiver.mall.entity.SeckillGroupProduct;
import cc.hiver.mall.pojo.dto.SeckillGroupProductDTO;
import cc.hiver.mall.pojo.query.SeckillGroupProductQuery;
import cc.hiver.mall.pojo.vo.SeckillGroupProductVO;
import cc.hiver.mall.service.mybatis.SeckillGroupCategoryService;
import cc.hiver.mall.service.mybatis.SeckillGroupProductService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@Api(tags = "后台管理系统秒杀团接口")
@RequestMapping("/hiver/mall/admin/seckillGroup")
@Transactional
public class AdminSeckillGroupController {
@Autowired
private SeckillGroupCategoryService seckillGroupCategoryService;
@Autowired
private SeckillGroupProductService seckillGroupProductService;
@RequestMapping(value = "/category/save", method = RequestMethod.POST)
@ApiOperation(value = "新增秒杀团分类")
public Result saveCategory(@RequestBody SeckillGroupCategory category) {
if (category == null || StringUtils.isBlank(category.getCategoryName())) {
return ResultUtil.error("分类名称不能为空");
}
if (StringUtils.isBlank(category.getRegionId())) {
return ResultUtil.error("区域ID不能为空");
}
category.setStatus(category.getStatus() == null ? 1 : category.getStatus());
category.setSort(category.getSort() == null ? 0 : category.getSort());
boolean result = seckillGroupCategoryService.save(category);
if (result) {
seckillGroupCategoryService.refreshCategoryCache(category.getRegionId());
return ResultUtil.success("添加成功");
}
return ResultUtil.error("添加失败");
}
@RequestMapping(value = "/category/edit", method = RequestMethod.POST)
@ApiOperation(value = "修改秒杀团分类")
public Result editCategory(@RequestBody SeckillGroupCategory category) {
if (category == null || StringUtils.isBlank(category.getId())) {
return ResultUtil.error("分类ID不能为空");
}
SeckillGroupCategory old = seckillGroupCategoryService.getById(category.getId());
if (old == null) {
return ResultUtil.error("分类不存在");
}
boolean result = seckillGroupCategoryService.updateById(category);
if (result) {
seckillGroupCategoryService.refreshCategoryCache(old.getRegionId());
if (StringUtils.isNotBlank(category.getRegionId()) && !category.getRegionId().equals(old.getRegionId())) {
seckillGroupCategoryService.refreshCategoryCache(category.getRegionId());
}
return ResultUtil.success("修改成功");
}
return ResultUtil.error("修改失败");
}
@RequestMapping(value = "/category/delete", method = RequestMethod.POST)
@ApiOperation(value = "删除秒杀团分类")
public Result deleteCategory(String id) {
if (StringUtils.isBlank(id)) {
return ResultUtil.error("分类ID不能为空");
}
SeckillGroupCategory category = seckillGroupCategoryService.getById(id);
if (category == null) {
return ResultUtil.error("分类不存在");
}
category.setDelFlag(1);
boolean result = seckillGroupCategoryService.updateById(category);
if (result) {
seckillGroupCategoryService.removeCategoryCache(category.getRegionId(), id);
seckillGroupCategoryService.refreshCategoryCache(category.getRegionId());
seckillGroupProductService.removeProductCache(id);
return ResultUtil.success("删除成功");
}
return ResultUtil.error("删除失败");
}
@RequestMapping(value = "/category/list", method = RequestMethod.GET)
@ApiOperation(value = "查询秒杀团分类列表")
public Result<List<SeckillGroupCategory>> listCategory(@RequestParam(value = "regionId") String regionId) {
LambdaQueryWrapper<SeckillGroupCategory> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SeckillGroupCategory::getDelFlag, 0);
if (StringUtils.isNotBlank(regionId)) {
wrapper.eq(SeckillGroupCategory::getRegionId, regionId);
}
wrapper.orderByDesc(SeckillGroupCategory::getSort)
.orderByAsc(SeckillGroupCategory::getCreateTime);
return new ResultUtil<List<SeckillGroupCategory>>().setData(seckillGroupCategoryService.list(wrapper));
}
@RequestMapping(value = "/category/sort", method = RequestMethod.POST)
@ApiOperation(value = "批量调整秒杀团分类排序")
public Result sortCategory(@RequestBody List<SeckillGroupCategory> categories) {
if (categories == null || categories.isEmpty()) {
return ResultUtil.error("分类列表不能为空");
}
boolean result = seckillGroupCategoryService.updateBatchById(categories);
if (result) {
for (SeckillGroupCategory category : categories) {
if (StringUtils.isNotBlank(category.getRegionId())) {
seckillGroupCategoryService.refreshCategoryCache(category.getRegionId());
} else if (StringUtils.isNotBlank(category.getId())) {
SeckillGroupCategory old = seckillGroupCategoryService.getById(category.getId());
if (old != null) {
seckillGroupCategoryService.refreshCategoryCache(old.getRegionId());
}
}
}
return ResultUtil.success("排序成功");
}
return ResultUtil.error("排序失败");
}
@RequestMapping(value = "/product/save", method = RequestMethod.POST)
@ApiOperation(value = "新增秒杀团商品")
public Result<SeckillGroupProduct> saveProduct(@RequestBody SeckillGroupProductDTO dto) {
try {
return new ResultUtil<SeckillGroupProduct>().setData(seckillGroupProductService.saveFromProduct(dto));
} catch (IllegalArgumentException e) {
return ResultUtil.error(e.getMessage());
}
}
@RequestMapping(value = "/product/edit", method = RequestMethod.POST)
@ApiOperation(value = "修改秒杀团商品")
public Result<SeckillGroupProduct> editProduct(@RequestBody SeckillGroupProductDTO dto) {
try {
return new ResultUtil<SeckillGroupProduct>().setData(seckillGroupProductService.editSeckillProduct(dto));
} catch (IllegalArgumentException e) {
return ResultUtil.error(e.getMessage());
}
}
@RequestMapping(value = "/product/page", method = RequestMethod.POST)
@ApiOperation(value = "分页查询秒杀团商品")
public Result<IPage<SeckillGroupProductVO>> pageProduct(@RequestBody SeckillGroupProductQuery query) {
if (query == null) {
query = new SeckillGroupProductQuery();
}
return new ResultUtil<IPage<SeckillGroupProductVO>>().setData(seckillGroupProductService.pageProducts(query));
}
@RequestMapping(value = "/product/up", method = RequestMethod.POST)
@ApiOperation(value = "上架秒杀团商品")
public Result upProduct(String id) {
return updateProductStatus(id, 1, "上架");
}
@RequestMapping(value = "/product/down", method = RequestMethod.POST)
@ApiOperation(value = "下架秒杀团商品")
public Result downProduct(String id) {
return updateProductStatus(id, 0, "下架");
}
@RequestMapping(value = "/product/delete", method = RequestMethod.POST)
@ApiOperation(value = "删除秒杀团商品")
public Result deleteProduct(String id) {
if (StringUtils.isBlank(id)) {
return ResultUtil.error("商品ID不能为空");
}
SeckillGroupProduct product = seckillGroupProductService.getById(id);
if (product == null) {
return ResultUtil.error("商品不存在");
}
product.setDelFlag(1);
boolean result = seckillGroupProductService.updateById(product);
if (result) {
seckillGroupProductService.refreshProductCache(product.getCategoryId());
seckillGroupProductService.removeProductDetailCache(id);
return ResultUtil.success("删除成功");
}
return ResultUtil.error("删除失败");
}
private Result updateProductStatus(String id, Integer status, String actionName) {
if (StringUtils.isBlank(id)) {
return ResultUtil.error("商品ID不能为空");
}
SeckillGroupProduct product = seckillGroupProductService.getById(id);
if (product == null) {
return ResultUtil.error("商品不存在");
}
product.setStatus(status);
boolean result = seckillGroupProductService.updateById(product);
if (result) {
if (status == 1) {
seckillGroupProductService.refreshProductCacheByProductId(id);
} else {
seckillGroupProductService.refreshProductCache(product.getCategoryId());
seckillGroupProductService.removeProductDetailCache(id);
}
return ResultUtil.success(actionName + "成功");
}
return ResultUtil.error(actionName + "失败");
}
}

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

@ -0,0 +1,66 @@
package cc.hiver.mall.controller;
import cc.hiver.core.common.annotation.RateLimiter;
import cc.hiver.core.common.utils.ResultUtil;
import cc.hiver.core.common.vo.Result;
import cc.hiver.mall.entity.SeckillGroupCategory;
import cc.hiver.mall.pojo.query.SeckillGroupProductQuery;
import cc.hiver.mall.pojo.vo.SeckillGroupProductVO;
import cc.hiver.mall.service.mybatis.SeckillGroupCategoryService;
import cc.hiver.mall.service.mybatis.SeckillGroupProductService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@Api(tags = "秒杀团接口")
@RequestMapping("/hiver/app/seckillGroup")
public class SeckillGroupController {
@Autowired
private SeckillGroupCategoryService seckillGroupCategoryService;
@Autowired
private SeckillGroupProductService seckillGroupProductService;
@RequestMapping(value = "/category/list", method = RequestMethod.GET)
@ApiOperation(value = "查询秒杀团横排分类")
public Result<List<SeckillGroupCategory>> listCategory(String regionId) {
if (StringUtils.isBlank(regionId)) {
return ResultUtil.error("区域ID不能为空");
}
return new ResultUtil<List<SeckillGroupCategory>>().setData(seckillGroupCategoryService.listFromCache(regionId));
}
@RequestMapping(value = "/product/page", method = RequestMethod.POST)
@ApiOperation(value = "分页查询秒杀团商品")
@RateLimiter(name = "seckillGroupProductPage", ipLimit = true)
public Result<IPage<SeckillGroupProductVO>> pageProduct(@RequestBody SeckillGroupProductQuery query) {
if (query == null) {
query = new SeckillGroupProductQuery();
}
if (StringUtils.isBlank(query.getRegionId())) {
return ResultUtil.error("区域ID不能为空");
}
query.setOnlyActive(true);
return new ResultUtil<IPage<SeckillGroupProductVO>>().setData(seckillGroupProductService.appPageProducts(query));
}
@RequestMapping(value = "/product/detail/{id}", method = RequestMethod.GET)
@ApiOperation(value = "查询秒杀团商品详情")
public Result<SeckillGroupProductVO> detail(@PathVariable("id") String id) {
if (StringUtils.isBlank(id)) {
return ResultUtil.error("商品ID不能为空");
}
SeckillGroupProductVO detail = seckillGroupProductService.getDetail(id);
if (detail == null) {
return ResultUtil.error("商品不存在");
}
return new ResultUtil<SeckillGroupProductVO>().setData(detail);
}
}

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

@ -246,7 +246,17 @@ public class ShopController {
}
if (shop.getIsStudent() != null) {
if (!(shop.getIsStudent() == s.getIsStudent())) {
if (!shop.getIsStudent().equals(s.getIsStudent())) {
continue;
}
}
if (shop.getMerchantType() != null) {
Integer merchantType = s.getMerchantType();
if (shop.getMerchantType().equals(1)) {
if (merchantType != null && !shop.getMerchantType().equals(merchantType)) {
continue;
}
} else if (!shop.getMerchantType().equals(merchantType)) {
continue;
}
}
@ -427,7 +437,7 @@ public class ShopController {
final Shop shop = shopService.get(id);
shop.setStatus(ShopConstant.SHOP_STATUS_LOCK);
shopService.update(shop);
shopService.removeShopCache(shop.getId(), shop.getRegionId());
shopService.refreshShopCache(shop.getId(), shop.getRegionId());
return ResultUtil.success("操作成功");
}

9
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WechatPayController.java

@ -99,11 +99,16 @@ public class WechatPayController {
String description = request.get("description");
String outTradeNo = request.get("outTradeNo");
String userRequireMake = request.get("userRequireMake");
if(userRequireMake != null && userRequireMake.equals("1")){
String isPack = request.get("isPack");
if(isPack != null){
MallOrder order = mallOrderService.getById(outTradeNo);
order.setUserRequireMake(1);
order.setIsPack(Integer.valueOf(isPack));
if(userRequireMake != null && userRequireMake.equals("1")){
order.setUserRequireMake(1);
}
mallOrderService.updateById(order);
}
if(description.equals("增加配送佣金")){
outTradeNo = "ORDERDE_" + outTradeNo;
}else{

9
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/SeckillGroupCategoryMapper.java

@ -0,0 +1,9 @@
package cc.hiver.mall.dao.mapper;
import cc.hiver.mall.entity.SeckillGroupCategory;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface SeckillGroupCategoryMapper extends BaseMapper<SeckillGroupCategory> {
}

9
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/SeckillGroupProductMapper.java

@ -0,0 +1,9 @@
package cc.hiver.mall.dao.mapper;
import cc.hiver.mall.entity.SeckillGroupProduct;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface SeckillGroupProductMapper extends BaseMapper<SeckillGroupProduct> {
}

2
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallOrder.java

@ -84,6 +84,8 @@ public class MallOrder implements Serializable {
private String regionId;
@ApiModelProperty("是否免单")
private Integer isFreeOrder;
@ApiModelProperty("0 堂食 1 打包")
private Integer isPack;
@ApiModelProperty("免单金额")
private BigDecimal freeAmount;

32
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/SeckillGroupCategory.java

@ -0,0 +1,32 @@
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;
@Data
@ApiModel(value = "秒杀团横排分类")
@TableName(value = "t_seckill_group_category", autoResultMap = true)
public class SeckillGroupCategory extends HiverBaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "分类名称")
private String categoryName;
@ApiModelProperty(value = "分类图标")
private String categoryIcon;
@ApiModelProperty(value = "区域ID")
private String regionId;
@ApiModelProperty(value = "排序值,越大越靠前")
private Integer sort;
@ApiModelProperty(value = "状态:0禁用 1启用")
private Integer status;
@ApiModelProperty(value = "备注")
private String remark;
}

95
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/SeckillGroupProduct.java

@ -0,0 +1,95 @@
package cc.hiver.mall.entity;
import cc.hiver.core.base.HiverBaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
@Data
@ApiModel(value = "秒杀团商品")
@TableName(value = "t_seckill_group_product", autoResultMap = true)
public class SeckillGroupProduct extends HiverBaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "秒杀分类ID")
private String categoryId;
@ApiModelProperty(value = "原商品ID")
private String productId;
@ApiModelProperty(value = "商品名称快照")
private String productName;
@ApiModelProperty(value = "商品图片快照")
private String productPicture;
@ApiModelProperty(value = "单位快照")
private String unit;
@ApiModelProperty(value = "商品属性列表/规格快照")
private String attributeList;
@ApiModelProperty(value = "商品简介快照")
private String productIntro;
@ApiModelProperty(value = "原价/划线价快照")
private BigDecimal originalPrice;
@ApiModelProperty(value = "秒杀价格")
private BigDecimal seckillPrice;
@ApiModelProperty(value = "秒杀总库存")
private Integer totalStock;
@ApiModelProperty(value = "已售库存")
private Integer soldStock;
@ApiModelProperty(value = "锁定库存")
private Integer lockStock;
@ApiModelProperty(value = "每人限购数量,空表示不限")
private Integer limitNum;
@ApiModelProperty(value = "商家ID")
private String shopId;
@ApiModelProperty(value = "商家名称快照")
private String shopName;
@ApiModelProperty(value = "商家电话快照")
private String shopPhone;
@ApiModelProperty(value = "商家地址快照")
private String shopAddress;
@ApiModelProperty(value = "取货区域ID,便于下单传参")
private String getAreaId;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "秒杀开始时间")
private Date startTime;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "秒杀结束时间")
private Date endTime;
@ApiModelProperty(value = "人工排序值,越大越靠前")
private Integer orderFiled;
@ApiModelProperty(value = "稳定随机排序值")
private Long sortHash;
@ApiModelProperty(value = "状态:0下架 1上架")
private Integer status;
@ApiModelProperty(value = "备注")
private String remark;
}

8
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/Shop.java

@ -175,6 +175,9 @@ public class Shop extends HiverBaseEntity {
@ApiModelProperty(value = "是否为学生商家 0 不是 1是")
private Integer isStudent;
@ApiModelProperty(value = "1外卖商家 / 2团购商家")
private Integer merchantType;
@Transient
@TableField(exist = false)
@ApiModelProperty(value = "店铺抽佣等设置")
@ -184,4 +187,9 @@ public class Shop extends HiverBaseEntity {
@TableField(exist = false)
@ApiModelProperty(value = "模糊搜索条件")
private String keyWord;
@Transient
@TableField(exist = false)
@ApiModelProperty(value = "是否后台查询")
private Integer isAdmin;
}

3
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/dto/CreateOrderDTO.java

@ -42,6 +42,9 @@ public class CreateOrderDTO {
@ApiModelProperty(value = "送货区域ID(用户收货地址关联区域id)")
private String putAreaId;
@ApiModelProperty("0 堂食 1 打包")
private Integer isPack;
@ApiModelProperty(value = "打包费(默认0)")
private BigDecimal packageFee;

55
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/dto/SeckillGroupProductDTO.java

@ -0,0 +1,55 @@
package cc.hiver.mall.pojo.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
@Data
@ApiModel("秒杀团商品保存对象")
public class SeckillGroupProductDTO {
@ApiModelProperty(value = "秒杀团商品ID,编辑时必传")
private String id;
@ApiModelProperty(value = "秒杀分类ID", required = true)
private String categoryId;
@ApiModelProperty(value = "原商品ID", required = true)
private String productId;
@ApiModelProperty(value = "秒杀价格", required = true)
private BigDecimal seckillPrice;
@ApiModelProperty(value = "秒杀库存", required = true)
private Integer totalStock;
@ApiModelProperty(value = "每人限购数量")
private Integer limitNum;
@ApiModelProperty(value = "取货区域ID,不传则使用商家所属区域ID")
private String getAreaId;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "秒杀开始时间")
private Date startTime;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "秒杀结束时间")
private Date endTime;
@ApiModelProperty(value = "人工排序值,越大越靠前")
private Integer orderFiled;
@ApiModelProperty(value = "状态:0下架 1上架")
private Integer status;
@ApiModelProperty(value = "备注")
private String remark;
}

32
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/query/SeckillGroupProductQuery.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;
@Data
@ApiModel("秒杀团商品分页查询对象")
public class SeckillGroupProductQuery extends HiverBasePageQuery {
@ApiModelProperty(value = "秒杀分类ID")
private String categoryId;
@ApiModelProperty(value = "区域ID")
private String regionId;
@ApiModelProperty(value = "商家ID")
private String shopId;
@ApiModelProperty(value = "关键字")
private String keywords;
@ApiModelProperty(value = "商品名称")
private String productName;
@ApiModelProperty(value = "状态:0下架 1上架")
private Integer status;
@ApiModelProperty(value = "是否仅查询当前有效活动商品")
private Boolean onlyActive;
}

89
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/vo/SeckillGroupProductVO.java

@ -0,0 +1,89 @@
package cc.hiver.mall.pojo.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class SeckillGroupProductVO {
@ApiModelProperty(value = "秒杀团商品ID")
private String id;
@ApiModelProperty(value = "秒杀分类ID")
private String categoryId;
@ApiModelProperty(value = "原商品ID")
private String productId;
@ApiModelProperty(value = "商品名称")
private String productName;
@ApiModelProperty(value = "商品图片")
private String productPicture;
@ApiModelProperty(value = "单位")
private String unit;
@ApiModelProperty(value = "商品属性列表/规格")
private String attributeList;
@ApiModelProperty(value = "商品简介")
private String productIntro;
@ApiModelProperty(value = "原价/划线价")
private BigDecimal originalPrice;
@ApiModelProperty(value = "秒杀价格")
private BigDecimal seckillPrice;
@ApiModelProperty(value = "秒杀总库存")
private Integer totalStock;
@ApiModelProperty(value = "已售库存")
private Integer soldStock;
@ApiModelProperty(value = "锁定库存")
private Integer lockStock;
@ApiModelProperty(value = "可售库存")
private Integer availableStock;
@ApiModelProperty(value = "每人限购数量")
private Integer limitNum;
@ApiModelProperty(value = "商家ID")
private String shopId;
@ApiModelProperty(value = "商家名称")
private String shopName;
@ApiModelProperty(value = "商家电话")
private String shopPhone;
@ApiModelProperty(value = "商家地址")
private String shopAddress;
@ApiModelProperty(value = "取货区域ID")
private String getAreaId;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "秒杀开始时间")
private Date startTime;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "秒杀结束时间")
private Date endTime;
@ApiModelProperty(value = "人工排序值")
private Integer orderFiled;
@ApiModelProperty(value = "状态:0下架 1上架")
private Integer status;
}

17
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/SeckillGroupCategoryService.java

@ -0,0 +1,17 @@
package cc.hiver.mall.service.mybatis;
import cc.hiver.mall.entity.SeckillGroupCategory;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface SeckillGroupCategoryService extends IService<SeckillGroupCategory> {
List<SeckillGroupCategory> listForApp(String regionId);
List<SeckillGroupCategory> listFromCache(String regionId);
void refreshCategoryCache(String regionId);
void removeCategoryCache(String regionId, String categoryId);
}

29
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/SeckillGroupProductService.java

@ -0,0 +1,29 @@
package cc.hiver.mall.service.mybatis;
import cc.hiver.mall.entity.SeckillGroupProduct;
import cc.hiver.mall.pojo.dto.SeckillGroupProductDTO;
import cc.hiver.mall.pojo.query.SeckillGroupProductQuery;
import cc.hiver.mall.pojo.vo.SeckillGroupProductVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
public interface SeckillGroupProductService extends IService<SeckillGroupProduct> {
SeckillGroupProduct saveFromProduct(SeckillGroupProductDTO dto);
SeckillGroupProduct editSeckillProduct(SeckillGroupProductDTO dto);
IPage<SeckillGroupProductVO> pageProducts(SeckillGroupProductQuery query);
IPage<SeckillGroupProductVO> appPageProducts(SeckillGroupProductQuery query);
SeckillGroupProductVO getDetail(String id);
void refreshProductCache(String categoryId);
void refreshProductCacheByProductId(String id);
void removeProductCache(String categoryId);
void removeProductDetailCache(String id);
}

10
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopServiceImpl.java

@ -116,7 +116,8 @@ public class ShopServiceImpl implements ShopService {
final Path<String> ShopNameField = root.get("shopName");
final Path<String> regionField = root.get("regionId");
final Path<String> shopAreaField = root.get("shopArea");
final Path<String> isStudentField = root.get("isStudent");
final Path<Integer> isStudentField = root.get("isStudent");
final Path<Integer> merchantTypeField = root.get("merchantType");
final Path<String> statusField = root.get("status");
@ -160,6 +161,13 @@ public class ShopServiceImpl implements ShopService {
if (shop.getIsStudent() != null) {
list.add(cb.equal(isStudentField, shop.getIsStudent()));
}
if (shop.getMerchantType() != null) {
if (shop.getMerchantType().equals(1)) {
list.add(cb.or(cb.equal(merchantTypeField, shop.getMerchantType()), cb.isNull(merchantTypeField)));
} else {
list.add(cb.equal(merchantTypeField, shop.getMerchantType()));
}
}
final Predicate[] arr = new Predicate[list.size()];

1
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java

@ -1480,6 +1480,7 @@ public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder
order.setUserId(dto.getUserId());
order.setShopId(dto.getShopId());
order.setOrderType(orderType);
order.setIsPack(dto.getIsPack() == null ? 1 : dto.getIsPack());
order.setReceiverPhone(user.getMobile());
order.setDeliveryType(dto.getDeliveryType());
order.setAddressId(dto.getAddressId());

97
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/SeckillGroupCategoryServiceImpl.java

@ -0,0 +1,97 @@
package cc.hiver.mall.serviceimpl.mybatis;
import cc.hiver.core.common.redis.RedisTemplateHelper;
import cc.hiver.mall.dao.mapper.SeckillGroupCategoryMapper;
import cc.hiver.mall.entity.SeckillGroupCategory;
import cc.hiver.mall.service.mybatis.SeckillGroupCategoryService;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class SeckillGroupCategoryServiceImpl extends ServiceImpl<SeckillGroupCategoryMapper, SeckillGroupCategory> implements SeckillGroupCategoryService {
private static final String CATEGORY_CACHE_KEY = "SECKILL_GROUP_CATEGORY_CACHE";
@Autowired
private RedisTemplateHelper redisTemplateHelper;
@Override
public List<SeckillGroupCategory> listForApp(String regionId) {
LambdaQueryWrapper<SeckillGroupCategory> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SeckillGroupCategory::getDelFlag, 0)
.eq(SeckillGroupCategory::getStatus, 1)
.eq(SeckillGroupCategory::getRegionId, regionId)
.orderByDesc(SeckillGroupCategory::getSort)
.orderByAsc(SeckillGroupCategory::getCreateTime);
return list(wrapper);
}
@Override
public List<SeckillGroupCategory> listFromCache(String regionId) {
if (CharSequenceUtil.isBlank(regionId)) {
return new ArrayList<>();
}
List<Object> values = redisTemplateHelper.hValues(categoryCacheKey(regionId));
if (values != null && !values.isEmpty()) {
List<SeckillGroupCategory> categories = new ArrayList<>();
for (Object value : values) {
categories.add(JSONUtil.toBean(value.toString(), SeckillGroupCategory.class));
}
sortCategories(categories);
return categories;
}
List<SeckillGroupCategory> categories = listForApp(regionId);
refreshCategoryCache(regionId, categories);
return categories;
}
@Override
public void refreshCategoryCache(String regionId) {
if (CharSequenceUtil.isBlank(regionId)) {
return;
}
refreshCategoryCache(regionId, listForApp(regionId));
}
@Override
public void removeCategoryCache(String regionId, String categoryId) {
if (CharSequenceUtil.isBlank(regionId) || CharSequenceUtil.isBlank(categoryId)) {
return;
}
redisTemplateHelper.hDelete(categoryCacheKey(regionId), categoryId);
}
private void refreshCategoryCache(String regionId, List<SeckillGroupCategory> categories) {
String key = categoryCacheKey(regionId);
redisTemplateHelper.delete(key);
if (categories == null || categories.isEmpty()) {
return;
}
for (SeckillGroupCategory category : categories) {
redisTemplateHelper.hPut(key, category.getId(), JSONUtil.toJsonStr(category));
}
}
private String categoryCacheKey(String regionId) {
return CATEGORY_CACHE_KEY + ":" + regionId;
}
private void sortCategories(List<SeckillGroupCategory> categories) {
categories.sort((o1, o2) -> {
Integer sort1 = o1.getSort() == null ? 0 : o1.getSort();
Integer sort2 = o2.getSort() == null ? 0 : o2.getSort();
int sortCompare = sort2.compareTo(sort1);
if (sortCompare != 0) {
return sortCompare;
}
return o1.getId().compareTo(o2.getId());
});
}
}

509
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/SeckillGroupProductServiceImpl.java

@ -0,0 +1,509 @@
package cc.hiver.mall.serviceimpl.mybatis;
import cc.hiver.core.common.redis.RedisTemplateHelper;
import cc.hiver.mall.dao.mapper.SeckillGroupProductMapper;
import cc.hiver.mall.entity.Product;
import cc.hiver.mall.entity.SeckillGroupCategory;
import cc.hiver.mall.entity.SeckillGroupProduct;
import cc.hiver.mall.entity.Shop;
import cc.hiver.mall.pojo.dto.SeckillGroupProductDTO;
import cc.hiver.mall.pojo.query.SeckillGroupProductQuery;
import cc.hiver.mall.pojo.vo.SeckillGroupProductVO;
import cc.hiver.mall.service.ShopService;
import cc.hiver.mall.service.mybatis.ProductService;
import cc.hiver.mall.service.mybatis.SeckillGroupCategoryService;
import cc.hiver.mall.service.mybatis.SeckillGroupProductService;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.zip.CRC32;
@Service
public class SeckillGroupProductServiceImpl extends ServiceImpl<SeckillGroupProductMapper, SeckillGroupProduct> implements SeckillGroupProductService {
private static final String PRODUCT_LIST_CACHE_PREFIX = "SECKILL_GROUP_PRODUCTS:";
private static final String PRODUCT_REGION_LIST_CACHE_PREFIX = "SECKILL_GROUP_PRODUCTS_REGION:";
private static final String PRODUCT_DETAIL_CACHE_PREFIX = "SECKILL_GROUP_PRODUCT:";
private static final DateTimeFormatter SEED_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHH");
@Autowired
private ProductService productService;
@Autowired
private ShopService shopService;
@Autowired
private SeckillGroupCategoryService seckillGroupCategoryService;
@Autowired
private RedisTemplateHelper redisTemplateHelper;
@Transactional(rollbackFor = Exception.class)
@Override
public SeckillGroupProduct saveFromProduct(SeckillGroupProductDTO dto) {
validateSaveDto(dto, false);
Product product = productService.getById(dto.getProductId());
if (product == null) {
throw new IllegalArgumentException("原商品不存在");
}
Shop shop = shopService.get(product.getShopId());
SeckillGroupProduct seckillProduct = new SeckillGroupProduct();
fillSnapshot(seckillProduct, dto, product, shop);
seckillProduct.setSoldStock(0);
seckillProduct.setLockStock(0);
seckillProduct.setStatus(dto.getStatus() == null ? 0 : dto.getStatus());
seckillProduct.setSortHash(stableHash(seckillProduct.getId()));
save(seckillProduct);
refreshProductCache(seckillProduct.getCategoryId());
refreshProductDetailCache(seckillProduct);
return seckillProduct;
}
@Transactional(rollbackFor = Exception.class)
@Override
public SeckillGroupProduct editSeckillProduct(SeckillGroupProductDTO dto) {
validateSaveDto(dto, true);
SeckillGroupProduct old = getById(dto.getId());
if (old == null) {
throw new IllegalArgumentException("秒杀团商品不存在");
}
String productId = CharSequenceUtil.isBlank(dto.getProductId()) ? old.getProductId() : dto.getProductId();
Product product = productService.getById(productId);
if (product == null) {
throw new IllegalArgumentException("原商品不存在");
}
Shop shop = shopService.get(product.getShopId());
SeckillGroupProduct updated = new SeckillGroupProduct();
updated.setId(old.getId());
updated.setSoldStock(old.getSoldStock() == null ? 0 : old.getSoldStock());
updated.setLockStock(old.getLockStock() == null ? 0 : old.getLockStock());
fillSnapshot(updated, dto, product, shop);
updated.setStatus(dto.getStatus() == null ? old.getStatus() : dto.getStatus());
updated.setSortHash(stableHash(updated.getId()));
int usedStock = safeStock(updated.getSoldStock()) + safeStock(updated.getLockStock());
if (updated.getTotalStock() != null && updated.getTotalStock() < usedStock) {
throw new IllegalArgumentException("秒杀库存不能小于已售和锁定库存");
}
updateById(updated);
refreshProductCache(old.getCategoryId());
if (!old.getCategoryId().equals(updated.getCategoryId())) {
refreshProductCache(updated.getCategoryId());
}
refreshProductDetailCache(updated);
return updated;
}
@Override
public IPage<SeckillGroupProductVO> pageProducts(SeckillGroupProductQuery query) {
Page<SeckillGroupProduct> page = new Page<>(query.getPageNum(), query.getPageSize());
IPage<SeckillGroupProduct> result = page(page, buildQueryWrapper(query, false));
return convertPage(result);
}
@Override
public IPage<SeckillGroupProductVO> appPageProducts(SeckillGroupProductQuery query) {
int pageNum = query.getPageNum() > 0 ? query.getPageNum() : 1;
int pageSize = query.getPageSize() > 0 ? query.getPageSize() : 10;
List<SeckillGroupProductVO> allProducts = filterProducts(getRegionAppListFromCache(query.getRegionId()), query);
applyQuerySort(allProducts, query);
int fromIndex = (pageNum - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, allProducts.size());
List<SeckillGroupProductVO> records = fromIndex >= allProducts.size()
? new ArrayList<>()
: allProducts.subList(fromIndex, toIndex);
Page<SeckillGroupProductVO> page = new Page<>(pageNum, pageSize, allProducts.size());
page.setRecords(records);
return page;
}
@Override
public SeckillGroupProductVO getDetail(String id) {
if (CharSequenceUtil.isBlank(id)) {
return null;
}
String cached = redisTemplateHelper.get(PRODUCT_DETAIL_CACHE_PREFIX + id);
if (CharSequenceUtil.isNotBlank(cached)) {
return JSONUtil.toBean(cached, SeckillGroupProductVO.class);
}
SeckillGroupProduct product = getById(id);
if (product == null || product.getDelFlag() != 0) {
return null;
}
SeckillGroupProductVO vo = toVo(product);
redisTemplateHelper.set(PRODUCT_DETAIL_CACHE_PREFIX + id, JSONUtil.toJsonStr(vo));
return vo;
}
@Override
public void refreshProductCache(String categoryId) {
if (CharSequenceUtil.isBlank(categoryId)) {
return;
}
List<SeckillGroupProductVO> products = toVoList(list(buildActiveCategoryWrapper(categoryId)));
sortForApp(products);
redisTemplateHelper.set(productListCacheKey(categoryId), JSONUtil.toJsonStr(products));
removeRegionProductCacheByCategory(categoryId);
for (SeckillGroupProductVO product : products) {
redisTemplateHelper.set(PRODUCT_DETAIL_CACHE_PREFIX + product.getId(), JSONUtil.toJsonStr(product));
}
}
@Override
public void refreshProductCacheByProductId(String id) {
SeckillGroupProduct product = getById(id);
if (product != null) {
refreshProductCache(product.getCategoryId());
refreshProductDetailCache(product);
}
}
@Override
public void removeProductCache(String categoryId) {
if (CharSequenceUtil.isBlank(categoryId)) {
return;
}
redisTemplateHelper.deleteByPattern(PRODUCT_LIST_CACHE_PREFIX + categoryId + ":*");
removeRegionProductCacheByCategory(categoryId);
}
@Override
public void removeProductDetailCache(String id) {
if (CharSequenceUtil.isNotBlank(id)) {
redisTemplateHelper.delete(PRODUCT_DETAIL_CACHE_PREFIX + id);
}
}
private List<SeckillGroupProductVO> getAppListFromCache(String categoryId) {
String cacheKey = productListCacheKey(categoryId);
String cached = redisTemplateHelper.get(cacheKey);
if (CharSequenceUtil.isNotBlank(cached)) {
return JSONUtil.toList(cached, SeckillGroupProductVO.class);
}
List<SeckillGroupProductVO> products = toVoList(list(buildActiveCategoryWrapper(categoryId)));
sortForApp(products);
redisTemplateHelper.set(cacheKey, JSONUtil.toJsonStr(products));
return products;
}
private List<SeckillGroupProductVO> getRegionAppListFromCache(String regionId) {
String cacheKey = regionProductListCacheKey(regionId);
String cached = redisTemplateHelper.get(cacheKey);
if (CharSequenceUtil.isNotBlank(cached)) {
return JSONUtil.toList(cached, SeckillGroupProductVO.class);
}
SeckillGroupProductQuery query = new SeckillGroupProductQuery();
query.setRegionId(regionId);
List<SeckillGroupProductVO> products = toVoList(list(buildQueryWrapper(query, true)));
sortForApp(products);
redisTemplateHelper.set(cacheKey, JSONUtil.toJsonStr(products));
return products;
}
private List<SeckillGroupProductVO> filterProducts(List<SeckillGroupProductVO> products, SeckillGroupProductQuery query) {
List<SeckillGroupProductVO> filtered = new ArrayList<>();
if (products == null || products.isEmpty()) {
return filtered;
}
String keywords = CharSequenceUtil.isNotBlank(query.getKeywords()) ? query.getKeywords() : query.getProductName();
for (SeckillGroupProductVO product : products) {
if (CharSequenceUtil.isNotBlank(query.getCategoryId()) && !query.getCategoryId().equals(product.getCategoryId())) {
continue;
}
if (CharSequenceUtil.isNotBlank(query.getShopId()) && !query.getShopId().equals(product.getShopId())) {
continue;
}
if (query.getStatus() != null && !query.getStatus().equals(product.getStatus())) {
continue;
}
if (CharSequenceUtil.isNotBlank(keywords)) {
String productName = product.getProductName() == null ? "" : product.getProductName();
String shopName = product.getShopName() == null ? "" : product.getShopName();
if (!productName.contains(keywords) && !shopName.contains(keywords)) {
continue;
}
}
filtered.add(product);
}
return filtered;
}
private LambdaQueryWrapper<SeckillGroupProduct> buildQueryWrapper(SeckillGroupProductQuery query, boolean onlyActive) {
LambdaQueryWrapper<SeckillGroupProduct> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SeckillGroupProduct::getDelFlag, 0);
if (CharSequenceUtil.isNotBlank(query.getCategoryId())) {
wrapper.eq(SeckillGroupProduct::getCategoryId, query.getCategoryId());
}
appendRegionCondition(wrapper, query.getRegionId());
if (CharSequenceUtil.isNotBlank(query.getShopId())) {
wrapper.eq(SeckillGroupProduct::getShopId, query.getShopId());
}
if (query.getStatus() != null) {
wrapper.eq(SeckillGroupProduct::getStatus, query.getStatus());
}
String keywords = CharSequenceUtil.isNotBlank(query.getKeywords()) ? query.getKeywords() : query.getProductName();
if (CharSequenceUtil.isNotBlank(keywords)) {
wrapper.and(w -> w.like(SeckillGroupProduct::getProductName, keywords)
.or()
.like(SeckillGroupProduct::getShopName, keywords));
}
if (onlyActive || Boolean.TRUE.equals(query.getOnlyActive())) {
appendActiveCondition(wrapper);
}
if (isSeckillPriceSort(query)) {
boolean asc = "asc".equalsIgnoreCase(query.getOrder()) || "asc".equalsIgnoreCase(query.getSort());
wrapper.orderBy(true, asc, SeckillGroupProduct::getSeckillPrice);
} else {
wrapper.orderByDesc(SeckillGroupProduct::getCreateTime);
}
return wrapper;
}
private LambdaQueryWrapper<SeckillGroupProduct> buildActiveCategoryWrapper(String categoryId) {
LambdaQueryWrapper<SeckillGroupProduct> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SeckillGroupProduct::getDelFlag, 0)
.eq(SeckillGroupProduct::getStatus, 1)
.eq(SeckillGroupProduct::getCategoryId, categoryId);
appendActiveCondition(wrapper);
return wrapper;
}
private void appendActiveCondition(LambdaQueryWrapper<SeckillGroupProduct> wrapper) {
Date now = new Date();
wrapper.eq(SeckillGroupProduct::getStatus, 1)
.and(w -> w.isNull(SeckillGroupProduct::getStartTime).or().le(SeckillGroupProduct::getStartTime, now))
.and(w -> w.isNull(SeckillGroupProduct::getEndTime).or().ge(SeckillGroupProduct::getEndTime, now));
}
private void appendRegionCondition(LambdaQueryWrapper<SeckillGroupProduct> wrapper, String regionId) {
if (CharSequenceUtil.isBlank(regionId)) {
return;
}
List<SeckillGroupCategory> categories = seckillGroupCategoryService.listForApp(regionId);
if (categories == null || categories.isEmpty()) {
wrapper.eq(SeckillGroupProduct::getCategoryId, "__none__");
return;
}
List<String> categoryIds = new ArrayList<>();
for (SeckillGroupCategory category : categories) {
categoryIds.add(category.getId());
}
wrapper.in(SeckillGroupProduct::getCategoryId, categoryIds);
}
private boolean categoryBelongsToRegion(String categoryId, String regionId) {
if (CharSequenceUtil.isBlank(regionId)) {
return true;
}
SeckillGroupCategory category = seckillGroupCategoryService.getById(categoryId);
return category != null
&& Integer.valueOf(0).equals(category.getDelFlag())
&& Integer.valueOf(1).equals(category.getStatus())
&& regionId.equals(category.getRegionId());
}
private IPage<SeckillGroupProductVO> convertPage(IPage<SeckillGroupProduct> result) {
Page<SeckillGroupProductVO> page = new Page<>(result.getCurrent(), result.getSize(), result.getTotal());
page.setRecords(toVoList(result.getRecords()));
return page;
}
private List<SeckillGroupProductVO> toVoList(List<SeckillGroupProduct> products) {
List<SeckillGroupProductVO> voList = new ArrayList<>();
if (products == null || products.isEmpty()) {
return voList;
}
for (SeckillGroupProduct product : products) {
voList.add(toVo(product));
}
return voList;
}
private SeckillGroupProductVO toVo(SeckillGroupProduct product) {
SeckillGroupProductVO vo = new SeckillGroupProductVO();
vo.setId(product.getId());
vo.setCategoryId(product.getCategoryId());
vo.setProductId(product.getProductId());
vo.setProductName(product.getProductName());
vo.setProductPicture(product.getProductPicture());
vo.setUnit(product.getUnit());
vo.setAttributeList(product.getAttributeList());
vo.setProductIntro(product.getProductIntro());
vo.setOriginalPrice(product.getOriginalPrice());
vo.setSeckillPrice(product.getSeckillPrice());
vo.setTotalStock(product.getTotalStock());
vo.setSoldStock(product.getSoldStock());
vo.setLockStock(product.getLockStock());
vo.setAvailableStock(Math.max(0, safeStock(product.getTotalStock()) - safeStock(product.getSoldStock()) - safeStock(product.getLockStock())));
vo.setLimitNum(product.getLimitNum());
vo.setShopId(product.getShopId());
vo.setShopName(product.getShopName());
vo.setShopPhone(product.getShopPhone());
vo.setShopAddress(product.getShopAddress());
vo.setGetAreaId(product.getGetAreaId());
vo.setStartTime(product.getStartTime());
vo.setEndTime(product.getEndTime());
vo.setOrderFiled(product.getOrderFiled());
vo.setStatus(product.getStatus());
return vo;
}
private void sortForApp(List<SeckillGroupProductVO> products) {
products.sort((o1, o2) -> {
int hashCompare = Long.compare(stableHash(o1.getId()), stableHash(o2.getId()));
if (hashCompare != 0) {
return hashCompare;
}
Integer order1 = o1.getOrderFiled() == null ? 0 : o1.getOrderFiled();
Integer order2 = o2.getOrderFiled() == null ? 0 : o2.getOrderFiled();
int orderCompare = order2.compareTo(order1);
if (orderCompare != 0) {
return orderCompare;
}
return o1.getId().compareTo(o2.getId());
});
}
private void applyQuerySort(List<SeckillGroupProductVO> products, SeckillGroupProductQuery query) {
if (!isSeckillPriceSort(query)) {
return;
}
boolean asc = "asc".equalsIgnoreCase(query.getOrder()) || "asc".equalsIgnoreCase(query.getSort());
products.sort((o1, o2) -> {
java.math.BigDecimal p1 = o1.getSeckillPrice() == null ? java.math.BigDecimal.ZERO : o1.getSeckillPrice();
java.math.BigDecimal p2 = o2.getSeckillPrice() == null ? java.math.BigDecimal.ZERO : o2.getSeckillPrice();
int compare = p1.compareTo(p2);
return asc ? compare : -compare;
});
}
private boolean isSeckillPriceSort(SeckillGroupProductQuery query) {
return "seckillPrice".equalsIgnoreCase(query.getSort())
|| "seckill_price".equalsIgnoreCase(query.getSort())
|| "seckillPrice".equalsIgnoreCase(query.getOrder())
|| "seckill_price".equalsIgnoreCase(query.getOrder());
}
private void fillSnapshot(SeckillGroupProduct target, SeckillGroupProductDTO dto, Product product, Shop shop) {
target.setCategoryId(dto.getCategoryId());
target.setProductId(product.getId());
target.setProductName(product.getProductName());
target.setProductPicture(product.getProductPicture());
target.setUnit(product.getUnit());
target.setAttributeList(product.getAttributeList());
target.setProductIntro(product.getProductIntro());
target.setOriginalPrice(resolveOriginalPrice(product));
target.setSeckillPrice(dto.getSeckillPrice());
target.setTotalStock(dto.getTotalStock());
target.setLimitNum(dto.getLimitNum());
target.setStartTime(dto.getStartTime());
target.setEndTime(dto.getEndTime());
target.setOrderFiled(dto.getOrderFiled() == null ? 0 : dto.getOrderFiled());
target.setRemark(dto.getRemark());
target.setShopId(product.getShopId());
if (shop != null) {
target.setShopName(shop.getShopName());
target.setShopPhone(shop.getContactPhone());
target.setShopAddress(shop.getShopAddress());
target.setGetAreaId(CharSequenceUtil.isBlank(dto.getGetAreaId()) ? shop.getRegionId() : dto.getGetAreaId());
} else {
target.setGetAreaId(dto.getGetAreaId());
}
}
private void validateSaveDto(SeckillGroupProductDTO dto, boolean edit) {
if (dto == null) {
throw new IllegalArgumentException("参数不能为空");
}
if (edit && CharSequenceUtil.isBlank(dto.getId())) {
throw new IllegalArgumentException("秒杀团商品ID不能为空");
}
if (CharSequenceUtil.isBlank(dto.getCategoryId())) {
throw new IllegalArgumentException("秒杀分类不能为空");
}
if (!edit && CharSequenceUtil.isBlank(dto.getProductId())) {
throw new IllegalArgumentException("原商品不能为空");
}
if (dto.getSeckillPrice() == null || dto.getSeckillPrice().signum() <= 0) {
throw new IllegalArgumentException("秒杀价格必须大于0");
}
if (dto.getTotalStock() == null || dto.getTotalStock() < 0) {
throw new IllegalArgumentException("秒杀库存不能小于0");
}
}
private void refreshProductDetailCache(SeckillGroupProduct product) {
redisTemplateHelper.set(PRODUCT_DETAIL_CACHE_PREFIX + product.getId(), JSONUtil.toJsonStr(toVo(product)));
}
private java.math.BigDecimal resolveOriginalPrice(Product product) {
if (product == null || CharSequenceUtil.isBlank(product.getAttributeListPrice())) {
return product == null ? null : product.getPrice();
}
try {
JSONArray specs = JSONUtil.parseArray(product.getAttributeListPrice());
if (!specs.isEmpty()) {
JSONObject firstSpec = specs.getJSONObject(0);
String specPrice = firstSpec.getStr("specPrice");
if (CharSequenceUtil.isNotBlank(specPrice)) {
return new java.math.BigDecimal(specPrice);
}
}
} catch (Exception ignored) {
}
return product.getPrice();
}
private String productListCacheKey(String categoryId) {
return PRODUCT_LIST_CACHE_PREFIX + categoryId + ":" + currentHalfHourSeed();
}
private String regionProductListCacheKey(String regionId) {
return PRODUCT_REGION_LIST_CACHE_PREFIX + regionId + ":" + currentHalfHourSeed();
}
private void removeRegionProductCacheByCategory(String categoryId) {
SeckillGroupCategory category = seckillGroupCategoryService.getById(categoryId);
if (category != null && CharSequenceUtil.isNotBlank(category.getRegionId())) {
redisTemplateHelper.deleteByPattern(PRODUCT_REGION_LIST_CACHE_PREFIX + category.getRegionId() + ":*");
}
}
private int safeStock(Integer value) {
return value == null ? 0 : value;
}
private long stableHash(String id) {
CRC32 crc32 = new CRC32();
String seed = currentHalfHourSeed();
crc32.update((id + ":" + seed).getBytes(StandardCharsets.UTF_8));
return crc32.getValue();
}
private String currentHalfHourSeed() {
LocalDateTime now = LocalDateTime.now();
String minuteBucket = now.getMinute() < 30 ? "00" : "30";
return now.format(SEED_FORMATTER) + minuteBucket;
}
}

17
hiver-modules/hiver-mall/src/main/resources/db/alter_snapshot_fields.sql

@ -0,0 +1,17 @@
-- 为 mall_order 表添加快照字段(用户/商家联系信息)
ALTER TABLE `mall_order`
ADD COLUMN `receiver_name` VARCHAR(64) NULL COMMENT '收货人姓名(下单快照)' AFTER `remark`,
ADD COLUMN `receiver_phone` VARCHAR(20) NULL COMMENT '收货人电话(下单快照)' AFTER `receiver_name`,
ADD COLUMN `receiver_address` VARCHAR(255) NULL COMMENT '收货地址(下单快照,areaName+floor+roomNum)' AFTER `receiver_phone`,
ADD COLUMN `shop_name` VARCHAR(128) NULL COMMENT '商家名称(下单快照)' AFTER `receiver_address`,
ADD COLUMN `shop_phone` VARCHAR(20) NULL COMMENT '商家联系电话(下单快照)' AFTER `shop_name`,
ADD COLUMN `shop_address` VARCHAR(255) NULL COMMENT '商家地址(下单快照)' AFTER `shop_phone`;
-- 为 mall_delivery_order 表添加快照字段(配送员看到的收货人及商家信息)
ALTER TABLE `mall_delivery_order`
ADD COLUMN `receiver_name` VARCHAR(64) NULL COMMENT '收货人姓名(快照)' AFTER `status`,
ADD COLUMN `receiver_phone` VARCHAR(20) NULL COMMENT '收货人电话(快照)' AFTER `receiver_name`,
ADD COLUMN `receiver_address` VARCHAR(255) NULL COMMENT '收货地址(快照,areaName+floor+roomNum)' AFTER `receiver_phone`,
ADD COLUMN `shop_name` VARCHAR(128) NULL COMMENT '商家名称(快照)' AFTER `receiver_address`,
ADD COLUMN `shop_phone` VARCHAR(20) NULL COMMENT '商家联系电话(快照)' AFTER `shop_name`,
ADD COLUMN `shop_address` VARCHAR(255) NULL COMMENT '商家地址(快照)' AFTER `shop_phone`;

54
hiver-modules/hiver-mall/src/main/resources/db/seckill_group.sql

@ -0,0 +1,54 @@
CREATE TABLE `t_seckill_group_category` (
`id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID',
`create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` int DEFAULT 0 COMMENT '删除标志 默认0',
`category_name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
`category_icon` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '分类图标',
`region_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '区域ID',
`sort` int DEFAULT 0 COMMENT '排序值,越大越靠前',
`status` tinyint DEFAULT 1 COMMENT '状态:0禁用 1启用',
`remark` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
KEY `idx_region_status_sort` (`region_id`, `status`, `del_flag`, `sort`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='秒杀团横排分类表';
CREATE TABLE `t_seckill_group_product` (
`id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '主键ID',
`create_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` int DEFAULT 0 COMMENT '删除标志 默认0',
`category_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '秒杀分类ID',
`product_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原商品ID',
`product_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品名称快照',
`product_picture` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商品图片快照',
`unit` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '单位快照',
`attribute_list` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商品属性列表/规格快照',
`product_intro` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商品简介快照',
`original_price` decimal(10,2) DEFAULT NULL COMMENT '原价/划线价快照',
`seckill_price` decimal(10,2) NOT NULL COMMENT '秒杀价格',
`total_stock` int NOT NULL DEFAULT 0 COMMENT '秒杀总库存',
`sold_stock` int NOT NULL DEFAULT 0 COMMENT '已售库存',
`lock_stock` int NOT NULL DEFAULT 0 COMMENT '锁定库存',
`limit_num` int DEFAULT NULL COMMENT '每人限购数量,空表示不限',
`shop_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商家ID',
`shop_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商家名称快照',
`shop_phone` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商家电话快照',
`shop_address` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商家地址快照',
`get_area_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '取货区域ID,便于下单传参',
`start_time` datetime DEFAULT NULL COMMENT '秒杀开始时间',
`end_time` datetime DEFAULT NULL COMMENT '秒杀结束时间',
`order_filed` int DEFAULT 0 COMMENT '人工排序值,越大越靠前',
`sort_hash` bigint unsigned DEFAULT 0 COMMENT '稳定随机排序值',
`status` tinyint DEFAULT 0 COMMENT '状态:0下架 1上架',
`remark` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
KEY `idx_category_status_time` (`category_id`, `status`, `del_flag`, `start_time`, `end_time`),
KEY `idx_sort_hash` (`category_id`, `status`, `del_flag`, `sort_hash`, `order_filed`),
KEY `idx_product_id` (`product_id`),
KEY `idx_shop_id` (`shop_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='秒杀团商品表';

11
hiver-modules/hiver-mall/src/main/resources/mapper/MallDeliveryOrderMapper.xml

@ -223,17 +223,6 @@
AND finish_time >= CURDATE()
</select>
<select id="countOrdersWorkerIndex" resultType="cc.hiver.core.entity.Worker">
SELECT
COUNT(id) AS orderCount,
MAX(worker_name) AS workerName,
MAX(worker_id) AS workerId-- 使用 MAX 或 MIN 包裹非分组字段
FROM mall_delivery_order
WHERE `status` = 3 and region_id = #{regionId}
AND finish_time >= CURDATE()
GROUP BY worker_id ORDER BY orderCount DESC limit 20;
</select>
<select id="selectByOrderIds" resultMap="deliveryMap">
-- 第一部分:直接查 order_id
SELECT * FROM mall_delivery_order WHERE order_id IN

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

@ -33,6 +33,7 @@
<result column="user_require_make" property="userRequireMake"/>
<result column="is_free_order" property="isFreeOrder"/>
<result column="free_amount" property="freeAmount"/>
<result column="is_pack" property="isPack"/>
</resultMap>
<!-- 公共查询条件:店铺ID -->
@ -106,7 +107,8 @@
o.package_fee, o.address_id, o.remark, o.create_time, o.pay_time,
o.receiver_name, o.receiver_phone, o.receiver_address,
o.shop_name, o.shop_phone, o.shop_address,o.number_code,o.region_id,
o.settlement_status,o.shop_make_time,o.other_order,o.user_require_make,o.is_free_order,o.free_amount
o.settlement_status,o.shop_make_time,o.other_order,o.user_require_make
,o.is_free_order,o.free_amount,o.is_pack
FROM mall_order o
<where>
<if test="q.userId != null and q.userId != ''">
@ -206,7 +208,8 @@
o.package_fee, o.address_id, o.remark, o.create_time, o.pay_time,
o.receiver_name, o.receiver_phone, o.receiver_address,
o.shop_name, o.shop_phone, o.shop_address,o.number_code,o.region_id,
o.settlement_status,o.shop_make_time,o.other_order,o.user_require_make,o.is_free_order,o.free_amount
o.settlement_status,o.shop_make_time,o.other_order,o.user_require_make
,o.is_free_order,o.free_amount,o.is_pack
FROM mall_order o LEFT JOIN mall_order_group og ON o.id = og.head_order_id
<where>
<if test="groupId != null and groupId != ''">

18
hiver-modules/hiver-mall/src/main/resources/mapper/SeckillGroupCategoryMapper.xml

@ -0,0 +1,18 @@
<?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.SeckillGroupCategoryMapper">
<resultMap id="BaseResultMap" type="cc.hiver.mall.entity.SeckillGroupCategory">
<id column="id" jdbcType="VARCHAR" property="id"/>
<result column="create_by" jdbcType="VARCHAR" property="createBy"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_by" jdbcType="VARCHAR" property="updateBy"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
<result column="del_flag" jdbcType="INTEGER" property="delFlag"/>
<result column="category_name" jdbcType="VARCHAR" property="categoryName"/>
<result column="category_icon" jdbcType="VARCHAR" property="categoryIcon"/>
<result column="region_id" jdbcType="VARCHAR" property="regionId"/>
<result column="sort" jdbcType="INTEGER" property="sort"/>
<result column="status" jdbcType="INTEGER" property="status"/>
<result column="remark" jdbcType="VARCHAR" property="remark"/>
</resultMap>
</mapper>

36
hiver-modules/hiver-mall/src/main/resources/mapper/SeckillGroupProductMapper.xml

@ -0,0 +1,36 @@
<?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.SeckillGroupProductMapper">
<resultMap id="BaseResultMap" type="cc.hiver.mall.entity.SeckillGroupProduct">
<id column="id" jdbcType="VARCHAR" property="id"/>
<result column="create_by" jdbcType="VARCHAR" property="createBy"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_by" jdbcType="VARCHAR" property="updateBy"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
<result column="del_flag" jdbcType="INTEGER" property="delFlag"/>
<result column="category_id" jdbcType="VARCHAR" property="categoryId"/>
<result column="product_id" jdbcType="VARCHAR" property="productId"/>
<result column="product_name" jdbcType="VARCHAR" property="productName"/>
<result column="product_picture" jdbcType="VARCHAR" property="productPicture"/>
<result column="unit" jdbcType="VARCHAR" property="unit"/>
<result column="attribute_list" jdbcType="VARCHAR" property="attributeList"/>
<result column="product_intro" jdbcType="VARCHAR" property="productIntro"/>
<result column="original_price" jdbcType="DECIMAL" property="originalPrice"/>
<result column="seckill_price" jdbcType="DECIMAL" property="seckillPrice"/>
<result column="total_stock" jdbcType="INTEGER" property="totalStock"/>
<result column="sold_stock" jdbcType="INTEGER" property="soldStock"/>
<result column="lock_stock" jdbcType="INTEGER" property="lockStock"/>
<result column="limit_num" jdbcType="INTEGER" property="limitNum"/>
<result column="shop_id" jdbcType="VARCHAR" property="shopId"/>
<result column="shop_name" jdbcType="VARCHAR" property="shopName"/>
<result column="shop_phone" jdbcType="VARCHAR" property="shopPhone"/>
<result column="shop_address" jdbcType="VARCHAR" property="shopAddress"/>
<result column="get_area_id" jdbcType="VARCHAR" property="getAreaId"/>
<result column="start_time" jdbcType="TIMESTAMP" property="startTime"/>
<result column="end_time" jdbcType="TIMESTAMP" property="endTime"/>
<result column="order_filed" jdbcType="INTEGER" property="orderFiled"/>
<result column="sort_hash" jdbcType="BIGINT" property="sortHash"/>
<result column="status" jdbcType="INTEGER" property="status"/>
<result column="remark" jdbcType="VARCHAR" property="remark"/>
</resultMap>
</mapper>

24
hiver-modules/hiver-mall/src/main/resources/mapper/ShopMapper.xml

@ -43,6 +43,7 @@
<result column="sale_count" property="saleCount" jdbcType="INTEGER" />
<result column="is_student" property="isStudent" jdbcType="INTEGER" />
<result column="client_id" property="clientId" jdbcType="VARCHAR" />
<result column="merchant_type" property="merchantType" jdbcType="INTEGER" />
</resultMap>
<resultMap id="ResultMapWithBLOBs" type="cc.hiver.mall.entity.Shop" extends="BaseResultMap" >
<result column="remark" property="remark" jdbcType="LONGVARCHAR" />
@ -51,7 +52,8 @@
id, create_by, create_time, del_flag, update_by, update_time, shop_name, shop_owner_id, shop_manger_id, region,
region_id, shop_area, shop_area_title, shop_type, shop_type_title, shop_address, year_fee, charge_time, start_time,
end_time, status, remark, business_district_level, contact_phone, shop_icon, defaulted, ali_account, ali_name,
balance, attr_id, printing_method,shop_images,shop_lename,shop_lecard,shop_score,isbrandflag,subtitle,shoprank,sale_count,is_student,client_id
balance, attr_id, printing_method,shop_images,shop_lename,shop_lecard,
shop_score,isbrandflag,subtitle,shoprank,sale_count,is_student,client_id,merchant_type
</sql>
<sql id="Blob_Column_List" >
remark
@ -72,11 +74,12 @@ balance, attr_id, printing_method,shop_images,shop_lename,shop_lecard,shop_score
insert into t_shop (id, create_by, create_time, del_flag, update_by, update_time, shop_name, shop_owner_id, shop_manger_id, region,
region_id, shop_area, shop_area_title, shop_type, shop_type_title, shop_address, year_fee, charge_time, start_time,
end_time, status, remark, business_district_level, contact_phone, shop_icon, defaulted, ali_account, ali_name,
balance, attr_id, printing_method, shop_images, shop_lename, shop_lecard, shop_score, isbrandflag, subtitle,shoprank, sale_count,is_student,client_id)
balance, attr_id, printing_method, shop_images, shop_lename, shop_lecard, shop_score,
isbrandflag, subtitle,shoprank, sale_count,is_student,client_id,merchant_type)
values (#{id,jdbcType=VARCHAR}, #{createBy,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{delFlag,jdbcType=INTEGER}, #{updateBy,jdbcType=VARCHAR}, #{updateTime,jdbcType=TIMESTAMP}, #{shopName,jdbcType=VARCHAR}, #{shopOwnerId,jdbcType=VARCHAR}, #{shopMangerId,jdbcType=VARCHAR}, #{region,jdbcType=VARCHAR},
#{regionId,jdbcType=VARCHAR}, #{shopArea,jdbcType=VARCHAR}, #{shopAreaTitle,jdbcType=VARCHAR}, #{shopType,jdbcType=VARCHAR}, #{shopTypeTitle,jdbcType=VARCHAR}, #{shopAddress,jdbcType=VARCHAR}, #{yearFee,jdbcType=VARCHAR}, #{chargeTime,jdbcType=VARCHAR}, #{startTime,jdbcType=VARCHAR},
#{endTime,jdbcType=VARCHAR}, #{status,jdbcType=INTEGER}, #{remark,jdbcType=LONGVARCHAR}, #{businessDistrictLevel,jdbcType=VARCHAR}, #{contactPhone,jdbcType=VARCHAR}, #{shopIcon,jdbcType=VARCHAR}, #{defaulted,jdbcType=INTEGER}, #{aliAccount,jdbcType=VARCHAR}, #{aliName,jdbcType=VARCHAR},
#{balance,jdbcType=VARCHAR}, #{attrId,jdbcType=VARCHAR}, #{printingMethod,jdbcType=VARCHAR}, #{shopImages,jdbcType=VARCHAR}, #{shopLename,jdbcType=VARCHAR}, #{shopLecard,jdbcType=VARCHAR}, #{shopScore,jdbcType=DECIMAL}, #{isbrandflag,jdbcType=INTEGER}, #{subtitle,jdbcType=VARCHAR}, #{shoprank,jdbcType=INTEGER}, #{saleCount,jdbcType=INTEGER}, #{isStudent,jdbcType=INTEGER},#{clientId,jdbcType=VARCHAR})
#{balance,jdbcType=VARCHAR}, #{attrId,jdbcType=VARCHAR}, #{printingMethod,jdbcType=VARCHAR}, #{shopImages,jdbcType=VARCHAR}, #{shopLename,jdbcType=VARCHAR}, #{shopLecard,jdbcType=VARCHAR}, #{shopScore,jdbcType=DECIMAL}, #{isbrandflag,jdbcType=INTEGER}, #{subtitle,jdbcType=VARCHAR}, #{shoprank,jdbcType=INTEGER}, #{saleCount,jdbcType=INTEGER}, #{isStudent,jdbcType=INTEGER},#{clientId,jdbcType=VARCHAR}, #{merchantType,jdbcType=INTEGER})
</insert>
<insert id="insertSelective" parameterType="cc.hiver.mall.entity.Shop" >
insert into t_shop
@ -180,6 +183,9 @@ balance, attr_id, printing_method,shop_images,shop_lename,shop_lecard,shop_score
<if test="clientId != null" >
client_id
</if>
<if test="merchantType != null" >
merchant_type
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
@ -281,6 +287,9 @@ balance, attr_id, printing_method,shop_images,shop_lename,shop_lecard,shop_score
<if test="clientId != null" >
#{clientId,jdbcType=VARCHAR},
</if>
<if test="merchantType != null" >
#{merchantType,jdbcType=INTEGER},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="cc.hiver.mall.entity.Shop" >
@ -382,6 +391,9 @@ balance, attr_id, printing_method,shop_images,shop_lename,shop_lecard,shop_score
<if test="clientId != null" >
client_id = #{clientId,jdbcType=VARCHAR},
</if>
<if test="merchantType != null" >
merchant_type = #{merchantType,jdbcType=INTEGER},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -418,7 +430,8 @@ balance, attr_id, printing_method,shop_images,shop_lename,shop_lecard,shop_score
shoprank = #{shoprank,jdbcType=INTEGER},
sale_count = #{saleCount,jdbcType=INTEGER},
is_student = #{isStudent,jdbcType=INTEGER},
client_id = #{clientId,jdbcType=VARCHAR}
client_id = #{clientId,jdbcType=VARCHAR},
merchant_type = #{merchantType,jdbcType=INTEGER}
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="cc.hiver.mall.entity.Shop" >
@ -462,7 +475,8 @@ balance, attr_id, printing_method,shop_images,shop_lename,shop_lecard,shop_score
shoprank = #{shoprank,jdbcType=INTEGER},
sale_count = #{saleCount,jdbcType=INTEGER},
is_student = #{isStudent,jdbcType=INTEGER},
client_id = #{clientId,jdbcType=VARCHAR}
client_id = #{clientId,jdbcType=VARCHAR},
merchant_type = #{merchantType,jdbcType=INTEGER}
where id = #{id,jdbcType=VARCHAR}
</update>
<select id="findByUserId" resultType="cc.hiver.mall.entity.Shop">

73
hiver-modules/hiver-mall/src/main/resources/sql/init_coupon.sql

@ -0,0 +1,73 @@
-- 1. 优惠券模板表 / 配置表
CREATE TABLE `t_mall_coupon` (
`id` varchar(32) NOT NULL COMMENT '唯一标识',
`name` varchar(100) NOT NULL COMMENT '优惠券名称,如:5元无门槛红包、满20减5券',
-- 发放方与适用范围规则
`issuer_type` tinyint(2) NOT NULL DEFAULT '1' COMMENT '发放方:1-平台,2-商家',
`issuer_id` varchar(32) NOT NULL DEFAULT '0' COMMENT '发放方ID,平台为0,商家则为具体商户ID',
`apply_scene` tinyint(2) NOT NULL DEFAULT '0' COMMENT '适用场景:0-通用,1-外卖/买饭,2-快递/跑腿,3-二手物品交易',
-- 面额与门槛规则
`type` tinyint(2) NOT NULL DEFAULT '1' COMMENT '优惠券类型:1-满减券,2-无门槛直减券',
`min_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '使用门槛金额,0或没填表示无门槛',
`discount_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '抵扣面额(直减金额)',
-- 库存与限领规则
`total_count` int(11) NOT NULL DEFAULT '0' COMMENT '发行总数,-1表示不限制数量',
`remain_count` int(11) NOT NULL DEFAULT '0' COMMENT '剩余库存可领取数量',
`limit_per_user` int(11) NOT NULL DEFAULT '1' COMMENT '每人最多限领张数',
-- 有效期规则
`valid_type` tinyint(2) NOT NULL DEFAULT '1' COMMENT '有效期计算类型:1-绝对时间段有效,2-领取后相对天数有效',
`valid_start_time` datetime DEFAULT NULL COMMENT '有效期开始时间(适用绝对时间段)',
`valid_end_time` datetime DEFAULT NULL COMMENT '有效期结束时间(适用绝对时间段)',
`valid_days` int(11) DEFAULT '0' COMMENT '自领取之日起有效天数(适用相对天数)',
-- 状态控制
`status` tinyint(2) NOT NULL DEFAULT '1' COMMENT '状态:0-已下架/停发,1-发放中',
-- 基础字段
`create_by` varchar(32) DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(32) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` int(1) DEFAULT '0' COMMENT '删除标志 默认0',
PRIMARY KEY (`id`),
KEY `idx_issuer` (`issuer_type`, `issuer_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='优惠券模板配置表';
-- 2. 用户领取的优惠券资产表
CREATE TABLE `t_mall_user_coupon` (
`id` varchar(32) NOT NULL COMMENT '唯一标识',
`user_id` varchar(32) NOT NULL COMMENT '领取用户ID',
`coupon_id` varchar(32) NOT NULL COMMENT '关联的优惠券模板ID',
-- 冗余核心匹配字段(空间换时间,避免下单查询时疯狂Join)
`issuer_type` tinyint(2) NOT NULL COMMENT '冗余:发放方(1平台/2商家)',
`issuer_id` varchar(32) NOT NULL DEFAULT '0' COMMENT '冗余:发放方ID',
`apply_scene` tinyint(2) NOT NULL COMMENT '冗余:适用场景',
`min_amount` decimal(10,2) NOT NULL COMMENT '冗余:使用门槛',
`discount_amount` decimal(10,2) NOT NULL COMMENT '冗余:抵扣金额',
-- 用户实体的生命周期
`status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态:0-未使用,1-已挂起(下单锁定中),2-已使用,3-已过期',
`receive_time` datetime DEFAULT NULL COMMENT '领取时间',
`valid_start_time` datetime DEFAULT NULL COMMENT '实际可使用生效时间',
`valid_end_time` datetime DEFAULT NULL COMMENT '实际可使用失效时间',
-- 核销与使用记录
`use_time` datetime DEFAULT NULL COMMENT '真实核销时间',
`order_id` varchar(32) DEFAULT NULL COMMENT '核销时使用的订单ID',
-- 基础字段
`create_by` varchar(32) DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(32) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` int(1) DEFAULT '0' COMMENT '删除标志 默认0',
PRIMARY KEY (`id`),
KEY `idx_user_valid` (`user_id`, `status`, `valid_end_time`) USING BTREE,
KEY `idx_order_id` (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户领取的优惠券明细表';
Loading…
Cancel
Save