|
|
@ -79,7 +79,6 @@ 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"; |
|
|
@ -459,31 +458,25 @@ public class ShopController { |
|
|
@ApiOperation("多条件分页获取公司列表") |
|
|
@ApiOperation("多条件分页获取公司列表") |
|
|
public Result<Page<Shop>> getByCondition(Shop shop, |
|
|
public Result<Page<Shop>> getByCondition(Shop shop, |
|
|
PageVo pageVo) { |
|
|
PageVo pageVo) { |
|
|
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); |
|
|
boolean useRegionCache = CharSequenceUtil.isNotBlank(regionId) |
|
|
boolean useRegionCache = !adminQuery |
|
|
|
|
|
&& CharSequenceUtil.isNotBlank(regionId) |
|
|
&& !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); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -495,14 +488,10 @@ 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) { |
|
|
String shopCacheJson = v.toString(); |
|
|
ShopCacheDTO dto = parseShopCacheBase(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; |
|
|
@ -563,9 +552,6 @@ public class ShopController { |
|
|
} |
|
|
} |
|
|
allShops.add(s); |
|
|
allShops.add(s); |
|
|
} |
|
|
} |
|
|
timer.mark("parseShops"); |
|
|
|
|
|
timer.note("hvalsShops", values.size()); |
|
|
|
|
|
timer.note("hvalsBytes", hvalsBytes); |
|
|
|
|
|
|
|
|
|
|
|
if (adminQuery) { |
|
|
if (adminQuery) { |
|
|
applyAdminShopListSort(allShops, pageVo); |
|
|
applyAdminShopListSort(allShops, pageVo); |
|
|
@ -574,7 +560,6 @@ public class ShopController { |
|
|
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; |
|
|
@ -594,17 +579,12 @@ 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 (isSearchMatchedProduct(p, shop.getKeyWord())) { |
|
|
if (isSearchMatchedProduct(p, shop.getKeyWord())) { |
|
|
@ -625,40 +605,27 @@ 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()); |
|
|
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; |
|
|
@ -668,16 +635,11 @@ 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()); |
|
|
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); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -929,45 +891,6 @@ public class ShopController { |
|
|
return ShopBusinessHourUtil.getBusinessTimeOrder(takeaway, currentTime); |
|
|
return ShopBusinessHourUtil.getBusinessTimeOrder(takeaway, currentTime); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private void logShopListQuery(String source, ShopListStageTimer timer, long totalElements, int pageSize, String regionId) { |
|
|
|
|
|
long cost = timer.totalMs(); |
|
|
|
|
|
if (cost > SHOP_LIST_SLOW_THRESHOLD_MS) { |
|
|
|
|
|
log.warn("app/shop/getByCondition slow source={}, cost={}ms, regionId={}, total={}, pageSize={}, stages=[{}]", |
|
|
|
|
|
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(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void cacheDefaultGroupBuyPage(String cacheKey, List<Shop> content, long totalElements) { |
|
|
private void cacheDefaultGroupBuyPage(String cacheKey, List<Shop> content, long totalElements) { |
|
|
if (CharSequenceUtil.isBlank(cacheKey) || content == null) { |
|
|
if (CharSequenceUtil.isBlank(cacheKey) || content == null) { |
|
|
return; |
|
|
return; |
|
|
|