feat(product): 商品列表查询支持分页功能
- 添加分页参数pageNum和pageSize到ProductQueryRequestDTO - 将查询接口返回类型从List改为PageResult分页结果 - 实现MyBatis-Plus分页查询功能 - 添加链接码URL解析功能,支持完整URL或纯链接码输入 - 限制每页最大数量防止性能问题 - 添加分页相关的日志记录和调试信息 - 创建PageResult响应DTO类,包含分页元数据信息
This commit is contained in:
@@ -3,6 +3,7 @@ package com.mtkj.mtpay.controller;
|
||||
import com.mtkj.mtpay.common.Result;
|
||||
import com.mtkj.mtpay.dto.request.CreateProductRequestDTO;
|
||||
import com.mtkj.mtpay.dto.request.ProductQueryRequestDTO;
|
||||
import com.mtkj.mtpay.dto.response.PageResult;
|
||||
import com.mtkj.mtpay.dto.response.ProductResponseDTO;
|
||||
import com.mtkj.mtpay.service.ProductService;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -79,14 +80,14 @@ public class ProductController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询商品列表(支持多条件查询)
|
||||
* 查询商品列表(支持多条件查询和分页)
|
||||
* 支持:名称查询、链接查询、商品状态查询、发售地区查询
|
||||
*/
|
||||
@PostMapping("/query")
|
||||
public Result<List<ProductResponseDTO>> queryProducts(@RequestBody ProductQueryRequestDTO query) {
|
||||
public Result<PageResult<ProductResponseDTO>> queryProducts(@RequestBody ProductQueryRequestDTO query) {
|
||||
log.info("查询商品列表,查询条件:{}", query);
|
||||
List<ProductResponseDTO> products = productService.queryProducts(query);
|
||||
return Result.success(products);
|
||||
PageResult<ProductResponseDTO> pageResult = productService.queryProducts(query);
|
||||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,5 +27,15 @@ public class ProductQueryRequestDTO {
|
||||
* 发售地区(通过SKU的currency查询,如:MYR, PHP, THB, VND, SGD, CNY, USD等)
|
||||
*/
|
||||
private String salesRegion;
|
||||
|
||||
/**
|
||||
* 当前页码(从1开始,默认1)
|
||||
*/
|
||||
private Integer pageNum = 1;
|
||||
|
||||
/**
|
||||
* 每页大小(默认10)
|
||||
*/
|
||||
private Integer pageSize = 10;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.mtkj.mtpay.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页结果响应DTO
|
||||
*/
|
||||
@Data
|
||||
public class PageResult<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 当前页码(从1开始)
|
||||
*/
|
||||
private Long current;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
private Long size;
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 总页数
|
||||
*/
|
||||
private Long pages;
|
||||
|
||||
/**
|
||||
* 数据列表
|
||||
*/
|
||||
private List<T> records;
|
||||
|
||||
/**
|
||||
* 是否有上一页
|
||||
*/
|
||||
private Boolean hasPrevious;
|
||||
|
||||
/**
|
||||
* 是否有下一页
|
||||
*/
|
||||
private Boolean hasNext;
|
||||
|
||||
/**
|
||||
* 是否为第一页
|
||||
*/
|
||||
private Boolean isFirst;
|
||||
|
||||
/**
|
||||
* 是否为最后一页
|
||||
*/
|
||||
private Boolean isLast;
|
||||
|
||||
public PageResult() {
|
||||
}
|
||||
|
||||
public PageResult(Long current, Long size, Long total, List<T> records) {
|
||||
this.current = current;
|
||||
this.size = size;
|
||||
this.total = total;
|
||||
this.records = records;
|
||||
|
||||
// 计算总页数
|
||||
this.pages = (total + size - 1) / size;
|
||||
|
||||
// 计算是否有上一页和下一页
|
||||
this.hasPrevious = current > 1;
|
||||
this.hasNext = current < pages;
|
||||
|
||||
// 计算是否为第一页和最后一页
|
||||
this.isFirst = current == 1;
|
||||
this.isLast = current >= pages || pages == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.mtkj.mtpay.service;
|
||||
|
||||
import com.mtkj.mtpay.dto.request.CreateProductRequestDTO;
|
||||
import com.mtkj.mtpay.dto.request.ProductQueryRequestDTO;
|
||||
import com.mtkj.mtpay.dto.response.PageResult;
|
||||
import com.mtkj.mtpay.dto.response.ProductResponseDTO;
|
||||
|
||||
import java.util.List;
|
||||
@@ -53,10 +54,10 @@ public interface ProductService {
|
||||
void offShelfProduct(Long id);
|
||||
|
||||
/**
|
||||
* 查询商品列表(支持多条件查询)
|
||||
* @param query 查询条件
|
||||
* @return 商品列表
|
||||
* 查询商品列表(支持多条件查询和分页)
|
||||
* @param query 查询条件(包含分页参数)
|
||||
* @return 分页结果
|
||||
*/
|
||||
List<ProductResponseDTO> queryProducts(ProductQueryRequestDTO query);
|
||||
PageResult<ProductResponseDTO> queryProducts(ProductQueryRequestDTO query);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.mtkj.mtpay.common.ResultCode;
|
||||
import com.mtkj.mtpay.common.enums.ProductStatus;
|
||||
import com.mtkj.mtpay.common.enums.SkuStatus;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.mtkj.mtpay.dto.request.CreateProductRequestDTO;
|
||||
import com.mtkj.mtpay.dto.request.ProductQueryRequestDTO;
|
||||
import com.mtkj.mtpay.dto.response.PageResult;
|
||||
import com.mtkj.mtpay.dto.response.ProductResponseDTO;
|
||||
import com.mtkj.mtpay.entity.MtProduct;
|
||||
import com.mtkj.mtpay.entity.MtProductSku;
|
||||
@@ -658,9 +661,20 @@ public class ProductServiceImpl implements ProductService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProductResponseDTO> queryProducts(ProductQueryRequestDTO query) {
|
||||
public PageResult<ProductResponseDTO> queryProducts(ProductQueryRequestDTO query) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("查询商品列表,查询条件: {}", query);
|
||||
|
||||
// 处理分页参数
|
||||
int pageNum = query.getPageNum() != null && query.getPageNum() > 0 ? query.getPageNum() : 1;
|
||||
int pageSize = query.getPageSize() != null && query.getPageSize() > 0 ? query.getPageSize() : 10;
|
||||
|
||||
// 限制每页最大数量,防止性能问题
|
||||
if (pageSize > 100) {
|
||||
pageSize = 100;
|
||||
log.warn("每页大小超过限制,已调整为100,原始值: {}", query.getPageSize());
|
||||
}
|
||||
|
||||
log.info("查询商品列表,查询条件: {}, 页码: {}, 每页大小: {}", query, pageNum, pageSize);
|
||||
|
||||
// 1. 构建商品查询条件
|
||||
LambdaQueryWrapper<MtProduct> queryWrapper = new LambdaQueryWrapper<>();
|
||||
@@ -679,23 +693,29 @@ public class ProductServiceImpl implements ProductService {
|
||||
}
|
||||
|
||||
// 如果指定了链接码,先通过链接码获取商品ID
|
||||
// 支持完整URL或链接码两种输入方式
|
||||
List<Long> productIdsByLink = null;
|
||||
if (query.getLinkCode() != null && !query.getLinkCode().trim().isEmpty()) {
|
||||
try {
|
||||
Long productId = productLinkService.getProductIdByLinkCode(query.getLinkCode().trim());
|
||||
String linkCodeInput = query.getLinkCode().trim();
|
||||
String linkCode = extractLinkCodeFromUrl(linkCodeInput);
|
||||
|
||||
log.debug("提取链接码,输入: {}, 提取结果: {}", linkCodeInput, linkCode);
|
||||
|
||||
Long productId = productLinkService.getProductIdByLinkCode(linkCode);
|
||||
if (productId != null) {
|
||||
productIdsByLink = new ArrayList<>();
|
||||
productIdsByLink.add(productId);
|
||||
log.debug("通过链接码查询到商品ID: {}", productId);
|
||||
} else {
|
||||
log.debug("链接码无效,未找到商品: {}", query.getLinkCode());
|
||||
// 如果链接码无效,返回空列表
|
||||
return new ArrayList<>();
|
||||
log.debug("链接码无效,未找到商品: {}", linkCode);
|
||||
// 如果链接码无效,返回空分页结果
|
||||
return new PageResult<>((long) pageNum, (long) pageSize, 0L, new ArrayList<>());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("通过链接码查询商品失败,链接码: {}", query.getLinkCode(), e);
|
||||
// 链接码无效,返回空列表
|
||||
return new ArrayList<>();
|
||||
// 链接码无效,返回空分页结果
|
||||
return new PageResult<>((long) pageNum, (long) pageSize, 0L, new ArrayList<>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,8 +734,8 @@ public class ProductServiceImpl implements ProductService {
|
||||
log.debug("通过发售地区查询到商品ID数量: {}, 货币: {}", productIdsByRegion.size(), currency);
|
||||
} else {
|
||||
log.debug("发售地区无匹配商品,货币: {}", currency);
|
||||
// 如果发售地区无匹配,返回空列表
|
||||
return new ArrayList<>();
|
||||
// 如果发售地区无匹配,返回空分页结果
|
||||
return new PageResult<>((long) pageNum, (long) pageSize, 0L, new ArrayList<>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,8 +744,8 @@ public class ProductServiceImpl implements ProductService {
|
||||
// 两个条件都指定,取交集
|
||||
productIdsByLink.retainAll(productIdsByRegion);
|
||||
if (productIdsByLink.isEmpty()) {
|
||||
log.debug("链接码和发售地区条件无交集,返回空列表");
|
||||
return new ArrayList<>();
|
||||
log.debug("链接码和发售地区条件无交集,返回空分页结果");
|
||||
return new PageResult<>((long) pageNum, (long) pageSize, 0L, new ArrayList<>());
|
||||
}
|
||||
queryWrapper.in(MtProduct::getId, productIdsByLink);
|
||||
} else if (productIdsByLink != null) {
|
||||
@@ -737,13 +757,17 @@ public class ProductServiceImpl implements ProductService {
|
||||
}
|
||||
|
||||
queryWrapper.orderByDesc(MtProduct::getCreateTime);
|
||||
List<MtProduct> products = productMapper.selectList(queryWrapper);
|
||||
|
||||
log.debug("查询到商品数量: {}", products.size());
|
||||
// 使用MyBatis-Plus分页查询
|
||||
Page<MtProduct> page = new Page<>(pageNum, pageSize);
|
||||
IPage<MtProduct> productPage = productMapper.selectPage(page, queryWrapper);
|
||||
|
||||
List<MtProduct> products = productPage.getRecords();
|
||||
log.debug("查询到商品数量: {}/{}, 总记录数: {}", products.size(), pageSize, productPage.getTotal());
|
||||
|
||||
if (products.isEmpty()) {
|
||||
log.info("商品列表为空,耗时: {}ms", System.currentTimeMillis() - startTime);
|
||||
return new ArrayList<>();
|
||||
return new PageResult<>((long) pageNum, (long) pageSize, productPage.getTotal(), new ArrayList<>());
|
||||
}
|
||||
|
||||
// 2. 批量查询所有商品的SKU - 1次查询(优化N+1问题)
|
||||
@@ -830,10 +854,73 @@ public class ProductServiceImpl implements ProductService {
|
||||
}
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
log.info("查询商品列表完成,查询条件: {}, 结果数量: {}, 耗时: {}ms",
|
||||
query, result.size(), endTime - startTime);
|
||||
log.info("查询商品列表完成,查询条件: {}, 结果数量: {}/{}, 总记录数: {}, 耗时: {}ms",
|
||||
query, result.size(), pageSize, productPage.getTotal(), endTime - startTime);
|
||||
|
||||
return result;
|
||||
// 构建分页结果
|
||||
return new PageResult<>(
|
||||
productPage.getCurrent(),
|
||||
productPage.getSize(),
|
||||
productPage.getTotal(),
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从完整URL中提取链接码
|
||||
* 支持两种输入:
|
||||
* 1. 完整URL:http://localhost:3000/product/2563c74bd94c4a4b95abccf697a6c756
|
||||
* 2. 链接码:2563c74bd94c4a4b95abccf697a6c756
|
||||
*
|
||||
* @param input 用户输入的URL或链接码
|
||||
* @return 提取的链接码
|
||||
*/
|
||||
private String extractLinkCodeFromUrl(String input) {
|
||||
if (input == null || input.trim().isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
String trimmed = input.trim();
|
||||
|
||||
// 如果包含 "/product/",说明是完整URL,提取链接码
|
||||
int productIndex = trimmed.indexOf("/product/");
|
||||
if (productIndex >= 0) {
|
||||
String linkCode = trimmed.substring(productIndex + "/product/".length());
|
||||
// 移除可能的查询参数和锚点
|
||||
int queryIndex = linkCode.indexOf("?");
|
||||
if (queryIndex >= 0) {
|
||||
linkCode = linkCode.substring(0, queryIndex);
|
||||
}
|
||||
int hashIndex = linkCode.indexOf("#");
|
||||
if (hashIndex >= 0) {
|
||||
linkCode = linkCode.substring(0, hashIndex);
|
||||
}
|
||||
// 移除尾部斜杠
|
||||
linkCode = linkCode.replaceAll("/$", "");
|
||||
return linkCode.trim();
|
||||
}
|
||||
|
||||
// 如果不包含 "/product/",可能是直接输入的链接码,直接返回
|
||||
// 但也可能是其他格式的URL,尝试提取最后一段作为链接码
|
||||
if (trimmed.contains("/")) {
|
||||
String[] parts = trimmed.split("/");
|
||||
if (parts.length > 0) {
|
||||
String lastPart = parts[parts.length - 1];
|
||||
// 移除查询参数和锚点
|
||||
int queryIndex = lastPart.indexOf("?");
|
||||
if (queryIndex >= 0) {
|
||||
lastPart = lastPart.substring(0, queryIndex);
|
||||
}
|
||||
int hashIndex = lastPart.indexOf("#");
|
||||
if (hashIndex >= 0) {
|
||||
lastPart = lastPart.substring(0, hashIndex);
|
||||
}
|
||||
return lastPart.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// 直接返回(可能是纯链接码)
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user