Browse Source

对接拼团数据1

master
wangfukang 3 days ago
parent
commit
6bac4905db
  1. 22
      hiver-admin/src/main/resources/application.yml
  2. 16
      hiver-admin/test-output/test-report.html
  3. 8
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallOrderController.java
  4. 9
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ProductCategoryController.java
  5. 207
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopController.java
  6. 42
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopServiceImpl.java
  7. 20
      hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/DaQuReceiptRenderUtil.java

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

@ -29,6 +29,12 @@ ie:
server: server:
port: 8888 port: 8888
# 响应 gzip 压缩 店铺列表这类大 JSON 字段名高度重复 压缩率约 6-8 倍 小程序端会自动解压
compression:
enabled: true
mime-types: application/json,application/xml,text/html,text/xml,text/plain,text/css,text/javascript,application/javascript
# 小于该阈值的响应不压缩 避免小响应白付 CPU
min-response-size: 2KB
servlet: servlet:
context-path: / context-path: /
tomcat: tomcat:
@ -50,7 +56,7 @@ spring:
# 数据源 # 数据源
datasource: datasource:
# url: jdbc:mysql://154.8.162.157:3306/hiver_shop?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&allowMultiQueries=true # url: jdbc:mysql://154.8.162.157:3306/hiver_shop?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&allowMultiQueries=true
url: jdbc:mysql://8.140.253.224:3306/school?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&allowMultiQueries=true&autoReconnect=true&failOverReadOnly=false&maxReconnects=10 url: jdbc:mysql://127.0.0.1:3306/school?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&allowMultiQueries=true&autoReconnect=true&failOverReadOnly=false&maxReconnects=10
username: root username: root
# Jasypt加密 可到common-utils中找到JasyptUtil加解密工具类生成加密结果 格式为ENC(加密结果) 以下解密结果为123456 # Jasypt加密 可到common-utils中找到JasyptUtil加解密工具类生成加密结果 格式为ENC(加密结果) 以下解密结果为123456
password: ENC(87YloapnWG6mLejOa27eLN0afFhCrbAGxoHXXrSUrGJ0trM/BndC2kx/gfoJsXIi) password: ENC(87YloapnWG6mLejOa27eLN0afFhCrbAGxoHXXrSUrGJ0trM/BndC2kx/gfoJsXIi)
@ -58,8 +64,16 @@ spring:
driver-class-name: com.mysql.jdbc.Driver driver-class-name: com.mysql.jdbc.Driver
# Druid StatViewServlet配置 # Druid StatViewServlet配置
druid: druid:
test-on-borrow: true initial-size: 5
min-idle: 5
max-active: 30
max-wait: 3000
validation-query: SELECT 1 validation-query: SELECT 1
test-on-borrow: false
test-while-idle: true
test-on-return: false
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
stat-view-servlet: stat-view-servlet:
# 默认true 内置监控页面首页/druid/index.html # 默认true 内置监控页面首页/druid/index.html
enabled: true enabled: true
@ -94,7 +108,7 @@ spring:
# Redis 若设有密码自行添加配置password # Redis 若设有密码自行添加配置password
redis: redis:
# host: 154.8.162.157 # host: 154.8.162.157
host: 8.140.253.224 host: 127.0.0.1
password: reddoor168 password: reddoor168
# 数据库索引 默认0 # 数据库索引 默认0
database: 1 database: 1
@ -120,7 +134,7 @@ spring:
uris: http://localhost:9200 uris: http://localhost:9200
# RabbitMQ配置 # RabbitMQ配置
rabbitmq: rabbitmq:
host: 8.140.253.224 host: 127.0.0.1
port: 5672 port: 5672
username: root username: root
password: ziyi123QQ password: ziyi123QQ

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

@ -35,7 +35,7 @@
<a href="#"><span class="badge badge-primary">Hiver</span></a> <a href="#"><span class="badge badge-primary">Hiver</span></a>
</li> </li>
<li class="m-r-10"> <li class="m-r-10">
<a href="#"><span class="badge badge-primary">九月 20, 2026 17:27:21</span></a> <a href="#"><span class="badge badge-primary">九月 21, 2026 15:37:23</span></a>
</li> </li>
</ul> </ul>
</div> </div>
@ -84,7 +84,7 @@
<div class="test-detail"> <div class="test-detail">
<span class="meta text-white badge badge-sm"></span> <span class="meta text-white badge badge-sm"></span>
<p class="name">passTest</p> <p class="name">passTest</p>
<p class="text-sm"><span>17:27:22 下午</span> / <span>0.025 secs</span></p> <p class="text-sm"><span>15:37:24 下午</span> / <span>0.022 secs</span></p>
</div> </div>
<div class="test-contents d-none"> <div class="test-contents d-none">
<div class="detail-head"> <div class="detail-head">
@ -92,9 +92,9 @@
<div class="info"> <div class="info">
<div class='float-right'><span class='badge badge-default'>#test-id=1</span></div> <div class='float-right'><span class='badge badge-default'>#test-id=1</span></div>
<h5 class="test-status text-pass">passTest</h5> <h5 class="test-status text-pass">passTest</h5>
<span class='badge badge-success'>09.20.2026 17:27:22</span> <span class='badge badge-success'>09.21.2026 15:37:24</span>
<span class='badge badge-danger'>09.20.2026 17:27:22</span> <span class='badge badge-danger'>09.21.2026 15:37:24</span>
<span class='badge badge-default'>0.025 secs</span> <span class='badge badge-default'>0.022 secs</span>
</div> </div>
<div class="m-t-10 m-l-5"></div> <div class="m-t-10 m-l-5"></div>
</div> </div>
@ -104,7 +104,7 @@
<tbody> <tbody>
<tr class="event-row"> <tr class="event-row">
<td><span class="badge log pass-bg">Pass</span></td> <td><span class="badge log pass-bg">Pass</span></td>
<td>17:27:22</td> <td>15:37:24</td>
<td> <td>
Test passed Test passed
</td> </td>
@ -128,13 +128,13 @@
<div class="col-md-3"> <div class="col-md-3">
<div class="card"><div class="card-body"> <div class="card"><div class="card-body">
<p class="m-b-0">Started</p> <p class="m-b-0">Started</p>
<h3>九月 20, 2026 17:27:21</h3> <h3>九月 21, 2026 15:37:23</h3>
</div></div> </div></div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<div class="card"><div class="card-body"> <div class="card"><div class="card-body">
<p class="m-b-0">Ended</p> <p class="m-b-0">Ended</p>
<h3>九月 20, 2026 17:27:22</h3> <h3>九月 21, 2026 15:37:24</h3>
</div></div> </div></div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">

8
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/MallOrderController.java

@ -165,7 +165,8 @@ public class MallOrderController {
@PostMapping("/cloudPrint/{id}") @PostMapping("/cloudPrint/{id}")
@ApiOperation("手动云打印订单小票") @ApiOperation("手动云打印订单小票")
public Result<Object> cloudPrint(@ApiParam("订单ID") @PathVariable String id) { public Result<Object> cloudPrint(@ApiParam("订单ID") @PathVariable String id,
@RequestParam(value = "scanVerify", required = false) Boolean scanVerify) {
MallOrderVO vo = mallOrderService.getOrderDetail(id); MallOrderVO vo = mallOrderService.getOrderDetail(id);
if (vo == null) { if (vo == null) {
return ResultUtil.error("订单不存在"); return ResultUtil.error("订单不存在");
@ -173,7 +174,10 @@ public class MallOrderController {
if (!daQuCloudPrintService.isCloudPrintShop(vo.getShopId())) { if (!daQuCloudPrintService.isCloudPrintShop(vo.getShopId())) {
return ResultUtil.error("当前商家未配置云打印"); return ResultUtil.error("当前商家未配置云打印");
} }
orderAsyncProducer.sendCloudPrint(id, "MANUAL-" + System.currentTimeMillis()); String printReason = Boolean.TRUE.equals(scanVerify)
? "SCAN_VERIFY-" + System.currentTimeMillis()
: "MANUAL-" + System.currentTimeMillis();
orderAsyncProducer.sendCloudPrint(id, printReason);
return ResultUtil.success("云打印任务已发送"); return ResultUtil.success("云打印任务已发送");
} }

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

@ -5,8 +5,10 @@ import cc.hiver.core.common.utils.ResultUtil;
import cc.hiver.core.common.utils.SecurityUtil; import cc.hiver.core.common.utils.SecurityUtil;
import cc.hiver.core.common.vo.Result; import cc.hiver.core.common.vo.Result;
import cc.hiver.mall.entity.ProductCategory; import cc.hiver.mall.entity.ProductCategory;
import cc.hiver.mall.entity.Shop;
import cc.hiver.mall.pojo.vo.ProductCategoryVo; import cc.hiver.mall.pojo.vo.ProductCategoryVo;
import cc.hiver.mall.pojo.vo.ProductCategoryVo2; import cc.hiver.mall.pojo.vo.ProductCategoryVo2;
import cc.hiver.mall.service.ShopService;
import cc.hiver.mall.service.mybatis.ProductCategoryService; import cc.hiver.mall.service.mybatis.ProductCategoryService;
import cc.hiver.mall.service.mybatis.ProductService; import cc.hiver.mall.service.mybatis.ProductService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@ -37,6 +39,9 @@ public class ProductCategoryController {
@Autowired @Autowired
private ProductService productService; private ProductService productService;
@Autowired
private ShopService shopService;
@Autowired @Autowired
private SecurityUtil securityUtil; private SecurityUtil securityUtil;
@ -84,6 +89,10 @@ public class ProductCategoryController {
// 将该分类下的商品id删除掉 // 将该分类下的商品id删除掉
productService.deleteProductByCategoryId(productCategory.getId()); productService.deleteProductByCategoryId(productCategory.getId());
productCategoryService.removeProductCategoryCache(old.getShopId(), old.getId()); productCategoryService.removeProductCategoryCache(old.getShopId(), old.getId());
Shop shop = shopService.get(old.getShopId());
if (shop != null) {
shopService.refreshShopCache(shop.getId(), shop.getRegionId());
}
return ResultUtil.success("删除成功"); return ResultUtil.success("删除成功");
} }

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

@ -79,6 +79,7 @@ import java.util.stream.Collectors;
public class ShopController { public class ShopController {
private static final String GROUP_BUY_DEFAULT_PAGE_CACHE_PREFIX = "GROUP_BUY_DEFAULT_SHOP_PAGE:"; private static final String GROUP_BUY_DEFAULT_PAGE_CACHE_PREFIX = "GROUP_BUY_DEFAULT_SHOP_PAGE:";
private static final long GROUP_BUY_DEFAULT_PAGE_CACHE_SECONDS = 30L; private static final long GROUP_BUY_DEFAULT_PAGE_CACHE_SECONDS = 30L;
private static final long SHOP_LIST_SLOW_THRESHOLD_MS = 10L;
private static final DateTimeFormatter BUSINESS_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); private static final DateTimeFormatter BUSINESS_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
private static final String PRINTING_METHOD_BLUETOOTH = "bluetooth"; private static final String PRINTING_METHOD_BLUETOOTH = "bluetooth";
private static final String PRINTING_METHOD_CLOUD = "cloud"; private static final String PRINTING_METHOD_CLOUD = "cloud";
@ -458,7 +459,7 @@ public class ShopController {
@ApiOperation("多条件分页获取公司列表") @ApiOperation("多条件分页获取公司列表")
public Result<Page<Shop>> getByCondition(Shop shop, public Result<Page<Shop>> getByCondition(Shop shop,
PageVo pageVo) { PageVo pageVo) {
long queryStartTime = System.currentTimeMillis(); ShopListStageTimer timer = new ShopListStageTimer();
String regionId = shop.getRegionId(); String regionId = shop.getRegionId();
boolean adminQuery = CharSequenceUtil.isNotBlank(shop.getShopIcon()); boolean adminQuery = CharSequenceUtil.isNotBlank(shop.getShopIcon());
boolean hasKeyword = hasSearchKeyword(shop); boolean hasKeyword = hasSearchKeyword(shop);
@ -466,17 +467,23 @@ public class ShopController {
&& !hasKeyword && !hasKeyword
&& CharSequenceUtil.isBlank(shop.getShopName()); && CharSequenceUtil.isBlank(shop.getShopName());
String defaultPageCacheKey = buildDefaultGroupBuyPageCacheKey(shop, pageVo, adminQuery); String defaultPageCacheKey = buildDefaultGroupBuyPageCacheKey(shop, pageVo, adminQuery);
timer.note("pageCacheKey", defaultPageCacheKey == null ? "null" : defaultPageCacheKey);
if (CharSequenceUtil.isNotBlank(defaultPageCacheKey)) { if (CharSequenceUtil.isNotBlank(defaultPageCacheKey)) {
String cacheJson = redisTemplateHelper.get(defaultPageCacheKey); String cacheJson = redisTemplateHelper.get(defaultPageCacheKey);
timer.mark("pageCacheGet");
timer.note("pageCacheBytes", cacheJson == null ? 0 : cacheJson.length());
if (CharSequenceUtil.isNotBlank(cacheJson)) { if (CharSequenceUtil.isNotBlank(cacheJson)) {
try { try {
ShopListPageCache cache = JSONUtil.toBean(cacheJson, ShopListPageCache.class); ShopListPageCache cache = JSONUtil.toBean(cacheJson, ShopListPageCache.class);
timer.mark("pageCacheParse");
if (cache != null && cache.getContent() != null) { if (cache != null && cache.getContent() != null) {
if (containsUserHiddenShop(cache.getContent())) { if (containsUserHiddenShop(cache.getContent())) {
redisTemplateHelper.delete(defaultPageCacheKey); redisTemplateHelper.delete(defaultPageCacheKey);
} else { } else {
fillBaseDeliveryFee(cache.getContent(), regionId); fillBaseDeliveryFee(cache.getContent(), regionId);
timer.mark("baseFee");
Page<Shop> pageResult = new PageImpl<>(cache.getContent(), PageUtil.initPage(pageVo), cache.getTotalElements()); Page<Shop> pageResult = new PageImpl<>(cache.getContent(), PageUtil.initPage(pageVo), cache.getTotalElements());
logShopListQuery("pageCache", timer, cache.getTotalElements(), cache.getContent().size(), regionId);
return new ResultUtil<Page<Shop>>().setData(pageResult); return new ResultUtil<Page<Shop>>().setData(pageResult);
} }
} }
@ -488,10 +495,14 @@ public class ShopController {
} }
if (useRegionCache) { if (useRegionCache) {
List<Object> values = redisTemplateHelper.hValues("SHOP_CACHE:" + regionId); List<Object> values = redisTemplateHelper.hValues("SHOP_CACHE:" + regionId);
timer.mark("hvals");
if (values != null && !values.isEmpty()) { if (values != null && !values.isEmpty()) {
List<Shop> allShops = new ArrayList<>(); List<Shop> allShops = new ArrayList<>();
long hvalsBytes = 0L;
for (Object v : values) { for (Object v : values) {
ShopCacheDTO dto = parseShopCacheBase(v.toString()); String shopCacheJson = v.toString();
hvalsBytes += shopCacheJson.length();
ShopCacheDTO dto = parseShopCacheBase(shopCacheJson);
Shop s = dto.getShop(); Shop s = dto.getShop();
if (s == null) { if (s == null) {
continue; continue;
@ -552,50 +563,18 @@ public class ShopController {
} }
allShops.add(s); allShops.add(s);
} }
timer.mark("parseShops");
timer.note("hvalsShops", values.size());
timer.note("hvalsBytes", hvalsBytes);
String sort = pageVo.getSort();
String order = pageVo.getOrder();
if (CharSequenceUtil.isNotBlank(sort)) {
allShops.sort((s1, s2) -> {
int compareResult = 0;
if ("saleCount".equals(sort)) {
Integer count1 = s1.getSaleCount();
Integer count2 = s2.getSaleCount();
Integer c1 = count1 == null ? 0 : count1;
Integer c2 = count2 == null ? 0 : count2;
compareResult = c1.compareTo(c2);
} else if ("shopScore".equals(sort)) {
BigDecimal score1 = s1.getShopScore() == null ? BigDecimal.ZERO : s1.getShopScore();
BigDecimal score2 = s2.getShopScore() == null ? BigDecimal.ZERO : s2.getShopScore();
compareResult = score1.compareTo(score2);
} else if ("shoprank".equals(sort)) {
Integer shoprank1 = s1.getShoprank() == null ? 0 : s1.getShoprank();
Integer shoprank2 = s2.getShoprank() == null ? 0 : s2.getShoprank();
compareResult = shoprank1.compareTo(shoprank2);
}
if ("desc".equalsIgnoreCase(order)) {
compareResult = -compareResult;
}
return compareResult;
});
}
// 稳定排序:用户端优先展示营业中的商家,后台仍将禁用商家置底。
if (adminQuery) { if (adminQuery) {
allShops.sort((s1, s2) -> { applyAdminShopListSort(allShops, pageVo);
boolean disabled1 = ShopConstant.SHOP_STATUS_LOCK.equals(s1.getStatus());
boolean disabled2 = ShopConstant.SHOP_STATUS_LOCK.equals(s2.getStatus());
if (disabled1 != disabled2) {
return disabled1 ? 1 : -1;
}
Integer status1 = s1.getStatus() == null ? ShopConstant.SHOP_STATUS_LOCK : s1.getStatus();
Integer status2 = s2.getStatus() == null ? ShopConstant.SHOP_STATUS_LOCK : s2.getStatus();
return status1.compareTo(status2);
});
} else { } else {
applyUserShopListSort(allShops, pageVo);
String currentBusinessTime = LocalTime.now().format(BUSINESS_TIME_FORMATTER); String currentBusinessTime = LocalTime.now().format(BUSINESS_TIME_FORMATTER);
allShops.sort((s1, s2) -> compareUserShopPriority(s1, s2, currentBusinessTime)); allShops.sort((s1, s2) -> compareUserShopPriority(s1, s2, currentBusinessTime));
} }
timer.mark("sort");
int pageNumber = pageVo.getPageNumber() > 0 ? pageVo.getPageNumber() : 1; int pageNumber = pageVo.getPageNumber() > 0 ? pageVo.getPageNumber() : 1;
int pageSize = pageVo.getPageSize() > 0 ? pageVo.getPageSize() : 10; int pageSize = pageVo.getPageSize() > 0 ? pageVo.getPageSize() : 10;
@ -615,21 +594,27 @@ public class ShopController {
productKeys.add("SHOP_PRODUCTS:" + s.getId()); productKeys.add("SHOP_PRODUCTS:" + s.getId());
} }
List<String> productsJsonList = redisTemplateHelper.multiGet(productKeys); List<String> productsJsonList = redisTemplateHelper.multiGet(productKeys);
timer.mark("mgetProducts");
long productBytes = 0L;
int productCount = 0;
if (productsJsonList != null && productsJsonList.size() == pageList.size()) { if (productsJsonList != null && productsJsonList.size() == pageList.size()) {
for (int i = 0; i < pageList.size(); i++) { for (int i = 0; i < pageList.size(); i++) {
String productsJson = productsJsonList.get(i); String productsJson = productsJsonList.get(i);
if (CharSequenceUtil.isNotBlank(productsJson)) { if (CharSequenceUtil.isNotBlank(productsJson)) {
productBytes += productsJson.length();
List<ProductPageVO> productsReturn = new ArrayList<>(); List<ProductPageVO> productsReturn = new ArrayList<>();
List<ProductPageVO> products = JSONUtil.toList(productsJson, ProductPageVO.class); List<ProductPageVO> products = JSONUtil.toList(productsJson, ProductPageVO.class);
productCount += products == null ? 0 : products.size();
if (hasKeyword) { if (hasKeyword) {
for (ProductPageVO p : products) { for (ProductPageVO p : products) {
if (p.getDelFlag() == 1 && p.getProductName() != null && p.getProductName().contains(shop.getKeyWord())) { if (isSearchMatchedProduct(p, shop.getKeyWord())) {
p.setIsPush(1);
productsReturn.add(p); productsReturn.add(p);
} }
} }
}else{ }else{
for (ProductPageVO p : products) { for (ProductPageVO p : products) {
if (p.getDelFlag() == 1 && p.getIsPush() == 1) { if (Integer.valueOf(1).equals(p.getDelFlag()) && Integer.valueOf(1).equals(p.getIsPush())) {
productsReturn.add(p); productsReturn.add(p);
} }
} }
@ -640,28 +625,40 @@ public class ShopController {
} }
} }
} }
timer.mark("parseProducts");
timer.note("productBytes", productBytes);
timer.note("productCount", productCount);
} }
fillGroupDeliveryTime(pageList, regionId); fillGroupDeliveryTime(pageList, regionId);
timer.mark("deliveryTime");
fillBaseDeliveryFee(pageList, regionId); fillBaseDeliveryFee(pageList, regionId);
timer.mark("baseFee");
Page<Shop> pageResult = new PageImpl<>(pageList, PageUtil.initPage(pageVo), allShops.size()); Page<Shop> pageResult = new PageImpl<>(pageList, PageUtil.initPage(pageVo), allShops.size());
cacheDefaultGroupBuyPage(defaultPageCacheKey, pageList, allShops.size()); cacheDefaultGroupBuyPage(defaultPageCacheKey, pageList, allShops.size());
logSlowShopListQuery("redis", queryStartTime, allShops.size(), pageList.size(), regionId); timer.mark("pageCacheWrite");
logShopListQuery("redis", timer, allShops.size(), pageList.size(), regionId);
return new ResultUtil<Page<Shop>>().setData(pageResult); return new ResultUtil<Page<Shop>>().setData(pageResult);
} }
} }
Page<Shop> page; Page<Shop> page;
if (adminQuery) { if (adminQuery) {
page = shopService.findByCondition(shop, PageUtil.initPage(pageVo)); page = shopService.findByCondition(shop, PageUtil.initPage(pageVo));
timer.mark("dbQuery");
fillShopTakeawayAndProducts(page.getContent(), shop, false); fillShopTakeawayAndProducts(page.getContent(), shop, false);
timer.mark("dbFill");
} else { } else {
// 用户端:全量查询 → 挂 takeaway → 按多时段营业状态排序 → 再分页(与缓存路径一致) // 用户端:全量查询 → 挂 takeaway → 按多时段营业状态排序 → 再分页(与缓存路径一致)
Page<Shop> allPage = shopService.findByCondition(shop, Pageable.unpaged()); Page<Shop> allPage = shopService.findByCondition(shop, Pageable.unpaged());
List<Shop> allShops = new ArrayList<>(allPage.getContent()); List<Shop> allShops = new ArrayList<>(allPage.getContent());
timer.mark("dbQuery");
timer.note("dbShops", allShops.size());
fillShopTakeawayOnly(allShops); fillShopTakeawayOnly(allShops);
timer.mark("dbTakeaway");
applyUserShopListSort(allShops, pageVo); applyUserShopListSort(allShops, pageVo);
String currentBusinessTime = LocalTime.now().format(BUSINESS_TIME_FORMATTER); String currentBusinessTime = LocalTime.now().format(BUSINESS_TIME_FORMATTER);
allShops.sort((s1, s2) -> compareUserShopPriority(s1, s2, currentBusinessTime)); allShops.sort((s1, s2) -> compareUserShopPriority(s1, s2, currentBusinessTime));
timer.mark("sort");
int pageNumber = pageVo.getPageNumber() > 0 ? pageVo.getPageNumber() : 1; int pageNumber = pageVo.getPageNumber() > 0 ? pageVo.getPageNumber() : 1;
int pageSize = pageVo.getPageSize() > 0 ? pageVo.getPageSize() : 10; int pageSize = pageVo.getPageSize() > 0 ? pageVo.getPageSize() : 10;
@ -671,15 +668,85 @@ public class ShopController {
? new ArrayList<>() ? new ArrayList<>()
: new ArrayList<>(allShops.subList(fromIndex, toIndex)); : new ArrayList<>(allShops.subList(fromIndex, toIndex));
fillShopProducts(pageList, shop); fillShopProducts(pageList, shop);
timer.mark("dbProducts");
fillGroupDeliveryTime(pageList, shop.getRegionId()); fillGroupDeliveryTime(pageList, shop.getRegionId());
timer.mark("deliveryTime");
fillBaseDeliveryFee(pageList, shop.getRegionId()); fillBaseDeliveryFee(pageList, shop.getRegionId());
timer.mark("baseFee");
page = new PageImpl<>(pageList, PageUtil.initPage(pageVo), allShops.size()); page = new PageImpl<>(pageList, PageUtil.initPage(pageVo), allShops.size());
} }
cacheDefaultGroupBuyPage(defaultPageCacheKey, page.getContent(), page.getTotalElements()); cacheDefaultGroupBuyPage(defaultPageCacheKey, page.getContent(), page.getTotalElements());
logSlowShopListQuery("db", queryStartTime, page.getTotalElements(), page.getContent().size(), shop.getRegionId()); timer.mark("pageCacheWrite");
logShopListQuery("db", timer, page.getTotalElements(), page.getContent().size(), shop.getRegionId());
return new ResultUtil<Page<Shop>>().setData(page); return new ResultUtil<Page<Shop>>().setData(page);
} }
private void applyAdminShopListSort(List<Shop> allShops, PageVo pageVo) {
if (allShops == null || allShops.isEmpty()) {
return;
}
String shoprankOrder = getShoprankOrder(pageVo);
String sort = pageVo == null ? null : pageVo.getSort();
String order = pageVo == null ? null : pageVo.getOrder();
allShops.sort((s1, s2) -> {
int compareResult = Integer.compare(getAdminShopStatusOrder(s1), getAdminShopStatusOrder(s2));
if (compareResult != 0) {
return compareResult;
}
compareResult = compareShoprank(s1, s2, shoprankOrder);
if (compareResult != 0 || CharSequenceUtil.isBlank(sort)
|| "status".equals(sort) || "shoprank".equals(sort)) {
return compareResult;
}
compareResult = compareShopBySortField(s1, s2, sort);
if ("desc".equalsIgnoreCase(order)) {
compareResult = -compareResult;
}
return compareResult;
});
}
private int getAdminShopStatusOrder(Shop shop) {
Integer status = shop == null ? null : shop.getStatus();
if (ShopConstant.SHOP_STATUS_NORMAL.equals(status)) {
return 0;
}
return ShopConstant.SHOP_STATUS_LOCK.equals(status) ? 2 : 1;
}
private String getShoprankOrder(PageVo pageVo) {
if (pageVo == null) {
return "desc";
}
if ("shoprank".equals(pageVo.getSortOrder())) {
return CharSequenceUtil.isBlank(pageVo.getOrderOrder()) ? "desc" : pageVo.getOrderOrder();
}
if ("shoprank".equals(pageVo.getSort())) {
return CharSequenceUtil.isBlank(pageVo.getOrder()) ? "desc" : pageVo.getOrder();
}
return "desc";
}
private int compareShoprank(Shop s1, Shop s2, String order) {
Integer shoprank1 = s1.getShoprank() == null ? 0 : s1.getShoprank();
Integer shoprank2 = s2.getShoprank() == null ? 0 : s2.getShoprank();
int compareResult = shoprank1.compareTo(shoprank2);
return "asc".equalsIgnoreCase(order) ? compareResult : -compareResult;
}
private int compareShopBySortField(Shop s1, Shop s2, String sort) {
if ("saleCount".equals(sort)) {
Integer c1 = s1.getSaleCount() == null ? 0 : s1.getSaleCount();
Integer c2 = s2.getSaleCount() == null ? 0 : s2.getSaleCount();
return c1.compareTo(c2);
} else if ("shopScore".equals(sort)) {
BigDecimal score1 = s1.getShopScore() == null ? BigDecimal.ZERO : s1.getShopScore();
BigDecimal score2 = s2.getShopScore() == null ? BigDecimal.ZERO : s2.getShopScore();
return score1.compareTo(score2);
}
return 0;
}
private void applyUserShopListSort(List<Shop> allShops, PageVo pageVo) { private void applyUserShopListSort(List<Shop> allShops, PageVo pageVo) {
if (allShops == null || allShops.isEmpty() || pageVo == null) { if (allShops == null || allShops.isEmpty() || pageVo == null) {
return; return;
@ -747,6 +814,9 @@ public class ShopController {
Map<String, List<ProductPageVO>> productsByShopId = new HashMap<>(); Map<String, List<ProductPageVO>> productsByShopId = new HashMap<>();
if (productList != null && productList.getRecords() != null) { if (productList != null && productList.getRecords() != null) {
productList.getRecords().forEach(productPageVO -> { productList.getRecords().forEach(productPageVO -> {
if (CharSequenceUtil.isNotBlank(query.getKeyWord())) {
productPageVO.setIsPush(1);
}
productsByShopId.computeIfAbsent(productPageVO.getShopId(), key -> new ArrayList<>()).add(productPageVO); productsByShopId.computeIfAbsent(productPageVO.getShopId(), key -> new ArrayList<>()).add(productPageVO);
}); });
} }
@ -755,6 +825,14 @@ public class ShopController {
} }
} }
private boolean isSearchMatchedProduct(ProductPageVO product, String keyword) {
return product != null
&& Integer.valueOf(1).equals(product.getDelFlag())
&& CharSequenceUtil.isNotBlank(product.getProductName())
&& CharSequenceUtil.isNotBlank(keyword)
&& product.getProductName().contains(keyword);
}
private void fillShopTakeawayAndProducts(List<Shop> shops, Shop query) { private void fillShopTakeawayAndProducts(List<Shop> shops, Shop query) {
fillShopTakeawayAndProducts(shops, query, true); fillShopTakeawayAndProducts(shops, query, true);
} }
@ -851,11 +929,42 @@ public class ShopController {
return ShopBusinessHourUtil.getBusinessTimeOrder(takeaway, currentTime); return ShopBusinessHourUtil.getBusinessTimeOrder(takeaway, currentTime);
} }
private void logSlowShopListQuery(String source, long startTime, long totalElements, int pageSize, String regionId) { private void logShopListQuery(String source, ShopListStageTimer timer, long totalElements, int pageSize, String regionId) {
long cost = System.currentTimeMillis() - startTime; long cost = timer.totalMs();
if (cost > 300) { if (cost > SHOP_LIST_SLOW_THRESHOLD_MS) {
log.warn("app/shop/getByCondition slow source={}, cost={}ms, regionId={}, total={}, pageSize={}", log.warn("app/shop/getByCondition slow source={}, cost={}ms, regionId={}, total={}, pageSize={}, stages=[{}]",
source, cost, regionId, totalElements, pageSize); source, cost, regionId, totalElements, pageSize, timer.detail());
} else if (log.isDebugEnabled()) {
log.debug("app/shop/getByCondition source={}, cost={}ms, regionId={}, total={}, pageSize={}, stages=[{}]",
source, cost, regionId, totalElements, pageSize, timer.detail());
}
}
/**
* 分段计时mark 记录距上一个 mark 的耗时note 记录传输字节数等辅助指标
* 用于定位 getByCondition 的耗时分布Redis 取数 / JSON 解析 / 排序 / DB
*/
private static final class ShopListStageTimer {
private final long startNano = System.nanoTime();
private final StringBuilder detail = new StringBuilder();
private long lastNano = startNano;
void mark(String stage) {
long now = System.nanoTime();
detail.append(stage).append('=').append((now - lastNano) / 1_000_000L).append("ms ");
lastNano = now;
}
void note(String name, Object value) {
detail.append(name).append('=').append(value).append(' ');
}
long totalMs() {
return (System.nanoTime() - startNano) / 1_000_000L;
}
String detail() {
return detail.toString().trim();
} }
} }

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

@ -187,13 +187,24 @@ public class ShopServiceImpl implements ShopService {
cq.where(list.toArray(arr)); cq.where(list.toArray(arr));
if (adminQuery) { if (adminQuery) {
List<Order> orderList = new ArrayList<>(); List<Order> orderList = new ArrayList<>();
Expression<Integer> disabledOrder = cb.<Integer>selectCase() Expression<Integer> statusOrder = cb.<Integer>selectCase()
.when(cb.equal(statusField, ShopConstant.SHOP_STATUS_LOCK), 1) .when(cb.equal(statusField, ShopConstant.SHOP_STATUS_NORMAL), 0)
.otherwise(0); .when(cb.equal(statusField, ShopConstant.SHOP_STATUS_LOCK), 2)
orderList.add(cb.asc(disabledOrder)); .otherwise(1);
orderList.add(cb.asc(statusField)); orderList.add(cb.asc(statusOrder));
Sort.Order shoprankOrder = null;
for (Sort.Order order : pageSort) { for (Sort.Order order : pageSort) {
if ("status".equals(order.getProperty())) { if ("shoprank".equals(order.getProperty())) {
shoprankOrder = order;
break;
}
}
Path<Object> shoprankField = root.get("shoprank");
orderList.add(shoprankOrder != null && shoprankOrder.isAscending()
? cb.asc(shoprankField)
: cb.desc(shoprankField));
for (Sort.Order order : pageSort) {
if ("status".equals(order.getProperty()) || "shoprank".equals(order.getProperty())) {
continue; continue;
} }
Path<Object> sortField = root.get(order.getProperty()); Path<Object> sortField = root.get(order.getProperty());
@ -384,10 +395,25 @@ public class ShopServiceImpl implements ShopService {
// 第二层缓存:单独存储商品列表 // 第二层缓存:单独存储商品列表
String productsRedisKey = "SHOP_PRODUCTS:" + shopId; String productsRedisKey = "SHOP_PRODUCTS:" + shopId;
String pushProductsRedisKey = "SHOP_PUSH_PRODUCTS:" + shopId;
if (productList != null && productList.getRecords() != null && !productList.getRecords().isEmpty()) { if (productList != null && productList.getRecords() != null && !productList.getRecords().isEmpty()) {
redisTemplateHelper.set(productsRedisKey, JSONUtil.toJsonStr(productList.getRecords())); List<ProductPageVO> products = productList.getRecords();
redisTemplateHelper.set(productsRedisKey, JSONUtil.toJsonStr(products));
List<ProductPageVO> pushProducts = new ArrayList<>();
for (ProductPageVO product : products) {
if (Integer.valueOf(1).equals(product.getDelFlag()) && Integer.valueOf(1).equals(product.getIsPush())) {
pushProducts.add(product);
}
}
if (!pushProducts.isEmpty()) {
redisTemplateHelper.set(pushProductsRedisKey, JSONUtil.toJsonStr(pushProducts));
} else {
redisTemplateHelper.delete(pushProductsRedisKey);
}
} else { } else {
redisTemplateHelper.delete(productsRedisKey); redisTemplateHelper.delete(productsRedisKey);
redisTemplateHelper.delete(pushProductsRedisKey);
} }
} }
@ -403,6 +429,8 @@ public class ShopServiceImpl implements ShopService {
// 同步删除第二层商品缓存 // 同步删除第二层商品缓存
String productsRedisKey = "SHOP_PRODUCTS:" + shopId; String productsRedisKey = "SHOP_PRODUCTS:" + shopId;
redisTemplateHelper.delete(productsRedisKey); redisTemplateHelper.delete(productsRedisKey);
String pushProductsRedisKey = "SHOP_PUSH_PRODUCTS:" + shopId;
redisTemplateHelper.delete(pushProductsRedisKey);
} }
private void removeGroupBuyDefaultPageCache(String regionId) { private void removeGroupBuyDefaultPageCache(String regionId) {

20
hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/DaQuReceiptRenderUtil.java

@ -12,6 +12,7 @@ import java.util.List;
public final class DaQuReceiptRenderUtil { public final class DaQuReceiptRenderUtil {
private static final String LINE = "--------------------------------"; private static final String LINE = "--------------------------------";
private static final String TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; private static final String TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
private static final String APPOINTMENT_TIME_PATTERN = "yyyy-MM-dd HH:mm";
private DaQuReceiptRenderUtil() { private DaQuReceiptRenderUtil() {
} }
@ -22,12 +23,14 @@ public final class DaQuReceiptRenderUtil {
List<MallRefundRecord> refunds = order.getMallRefundRecord(); List<MallRefundRecord> refunds = order.getMallRefundRecord();
boolean refundPrint = isRefundPrint(printReason); boolean refundPrint = isRefundPrint(printReason);
boolean forcedOrderPrint = isOrderEventPrint(printReason); boolean forcedOrderPrint = isOrderEventPrint(printReason);
boolean scanVerifyPrint = isScanVerifyPrint(printReason);
boolean hasRefund = refundPrint && refunds != null && !refunds.isEmpty(); boolean hasRefund = refundPrint && refunds != null && !refunds.isEmpty();
centerLarge(sb, orderNo(order)); centerLarge(sb, orderNo(order));
centerLarge(sb, orderTypeText(order, delivery, refundPrint, forcedOrderPrint)); centerLarge(sb, orderTypeText(order, delivery, refundPrint, forcedOrderPrint, scanVerifyPrint));
if (delivery != null && Integer.valueOf(1).equals(delivery.getAppointmentDelivery())) { if (delivery != null && Integer.valueOf(1).equals(delivery.getAppointmentDelivery())) {
largeLeft(sb, "要求送达时间:" + formatTime(delivery.getMustFinishTime())); left(sb, "要求送达时间:");
largeLeft(sb, formatAppointmentTime(delivery.getMustFinishTime()));
} }
if (isFaceToFaceDelivery(order)) { if (isFaceToFaceDelivery(order)) {
largeLeft(sb, "本单编号:" + defaultString(order.getNumberCode())); largeLeft(sb, "本单编号:" + defaultString(order.getNumberCode()));
@ -143,7 +146,7 @@ public final class DaQuReceiptRenderUtil {
return defaultString(order.getNumberCode()); return defaultString(order.getNumberCode());
} }
private static String orderTypeText(MallOrderVO order, MallDeliveryOrder delivery, boolean refundPrint, boolean forcedOrderPrint) { private static String orderTypeText(MallOrderVO order, MallDeliveryOrder delivery, boolean refundPrint, boolean forcedOrderPrint, boolean scanVerifyPrint) {
Integer status = order.getStatus(); Integer status = order.getStatus();
if (delivery != null && Integer.valueOf(1).equals(delivery.getAppointmentDelivery()) if (delivery != null && Integer.valueOf(1).equals(delivery.getAppointmentDelivery())
&& !refundPrint) { && !refundPrint) {
@ -167,6 +170,9 @@ public final class DaQuReceiptRenderUtil {
&& !Integer.valueOf(1).equals(order.getUserRequireMake())) { && !Integer.valueOf(1).equals(order.getUserRequireMake())) {
return "待消费"; return "待消费";
} }
if (scanVerifyPrint) {
return "到店核销";
}
if (Integer.valueOf(5).equals(status)) { if (Integer.valueOf(5).equals(status)) {
return "已完成"; return "已完成";
} }
@ -196,6 +202,10 @@ public final class DaQuReceiptRenderUtil {
return StringUtils.containsIgnoreCase(printReason, "REFUND"); return StringUtils.containsIgnoreCase(printReason, "REFUND");
} }
private static boolean isScanVerifyPrint(String printReason) {
return StringUtils.containsIgnoreCase(printReason, "SCAN_VERIFY");
}
private static boolean isFaceToFaceDelivery(MallOrderVO order) { private static boolean isFaceToFaceDelivery(MallOrderVO order) {
return Integer.valueOf(1).equals(order.getDeliveryType()) return Integer.valueOf(1).equals(order.getDeliveryType())
&& Integer.valueOf(3).equals(order.getOrderType()); && Integer.valueOf(3).equals(order.getOrderType());
@ -293,6 +303,10 @@ public final class DaQuReceiptRenderUtil {
return date == null ? "" : new SimpleDateFormat(TIME_PATTERN).format(date); return date == null ? "" : new SimpleDateFormat(TIME_PATTERN).format(date);
} }
private static String formatAppointmentTime(Date date) {
return date == null ? "" : new SimpleDateFormat(APPOINTMENT_TIME_PATTERN).format(date);
}
private static String delNode(String text) { private static String delNode(String text) {
return defaultString(text).replace("{", "").replace("}", "").replace("\"", ""); return defaultString(text).replace("{", "").replace("}", "").replace("\"", "");
} }

Loading…
Cancel
Save