当前位置: 首页 > news >正文

3分钟搞定Goods查询页:Map传参+StringUtils分割符实战(附避坑指南)

3分钟搞定商品查询页:Map传参与字符串分割的高效实践

商品查询功能作为电商系统的核心模块,其性能与用户体验直接影响转化率。本文将聚焦查询页开发中的两个关键技术点:Map传参优化StringUtils分割技巧,通过可落地的代码示例,帮助开发者快速构建高效的商品查询接口。

1. 为什么需要优化参数传递方式?

传统开发中,我们常使用POJO对象或基本类型参数传递商品查询条件。但在实际电商场景中,查询条件往往具有以下特征:

  • 动态多变:不同分类商品的筛选条件差异大(如服装需要颜色/尺码,家电需要功率/能效)
  • 组合复杂:用户可能同时选择多个品牌、价格区间、商品属性
  • 后期扩展:随着业务发展会持续新增筛选维度
// 传统方式的问题示例 public PageResult<Goods> searchGoods( Long categoryId, String brandIds, Double minPrice, Double maxPrice, String color, String size) { // 参数膨胀且难以扩展 }

Map传参的优势对比

方式扩展性可维护性接口稳定性代码简洁度
传统POJO
Map键值对

提示:Map传参特别适合前端筛选条件动态生成的场景,无需频繁修改接口定义

2. Map传参的四种实战模式

2.1 基础键值对传递

// 控制器层接收 @GetMapping("/search") public Result search(@RequestParam Map<String, Object> params) { return goodsService.search(params); } // 服务层处理示例 public PageResult<Goods> search(Map<String, Object> params) { QueryWrapper<Goods> wrapper = new QueryWrapper<>(); // 品牌条件处理 if (params.containsKey("brandId")) { String brandIds = (String) params.get("brandId"); wrapper.in("brand_id", Arrays.asList(brandIds.split(","))); } // 价格区间处理 if (params.containsKey("minPrice") && params.containsKey("maxPrice")) { wrapper.between("price", params.get("minPrice"), params.get("maxPrice")); } }

2.2 多层嵌套参数处理

面对复杂规格参数时,可采用JSON字符串+Map解析的方案:

// 前端传递格式 { "specs": { "颜色": ["红色","蓝色"], "内存": ["8GB","12GB"] } } // 后端处理逻辑 String specsJson = (String) params.get("specs"); Map<String, List<String>> specsMap = JSON.parseObject( specsJson, new TypeReference<Map<String, List<String>>>(){} ); specsMap.forEach((key, values) -> { wrapper.and(qw -> { for (String value : values) { qw.or().like("specs", "\"" + key + "\":\"" + value + "\""); } }); });

2.3 类型安全转换技巧

为避免类型转换异常,推荐使用Apache Commons Lang3的转换工具:

import org.apache.commons.lang3.math.NumberUtils; // 安全获取数值型参数 Double minPrice = NumberUtils.toDouble( params.getOrDefault("minPrice", "0").toString() ); // 日期类型转换 Date startDate = DateUtils.parseDate( params.get("startDate").toString(), "yyyy-MM-dd", "yyyy/MM/dd" );

2.4 与MyBatis Plus的优雅结合

// 动态SQL生成示例 public PageResult<Goods> search(Map<String, Object> params) { LambdaQueryWrapper<Goods> wrapper = Wrappers.lambdaQuery(); // 自动驼峰转换 params.forEach((key, value) -> { if (value != null && !value.toString().isEmpty()) { String column = StringUtils.uncapitalize( StringUtils.join( StringUtils.splitByCharacterTypeCamelCase(key), "_" ).toLowerCase() ); wrapper.eq(column, value); } }); return new PageResult<>(goodsMapper.selectPage(params, wrapper)); }

3. StringUtils分割操作的五大陷阱与解决方案

3.1 空值处理误区

错误示范

String[] images = sku.getImages().split(",");

正确做法

// 使用Apache Commons Lang3 String[] images = StringUtils.split(sku.getImages(), ","); // 或者带空值检查的版本 String[] images = StringUtils.defaultIfEmpty(sku.getImages(), "") .split(",");

3.2 保留空字符串的特殊场景

当需要保留分割后的空值时(如CSV文件处理):

// 使用split的保留空值模式 String[] parts = StringUtils.splitPreserveAllTokens(str, "|"); // 与普通split对比 String input = "a||c|d"; String[] splitResult = input.split("\\|"); // ["a", "c", "d"] String[] preserveResult = StringUtils.splitPreserveAllTokens(input, "|"); // ["a", "", "c", "d"]

3.3 多分隔符混合处理

处理复杂字符串时(如商品描述中的特殊标记):

String input = "颜色:红色;尺寸:XL|材质:纯棉"; String[] tokens = StringUtils.split(input, ":;|"); // 结果:["颜色", "红色", "尺寸", "XL", "材质", "纯棉"]

3.4 性能优化方案

高频分割场景下的优化技巧:

// 预编译分隔符模式(JDK7+) private static final Pattern SPLIT_PATTERN = Pattern.compile("[,;]"); public List<String> parseTags(String tagStr) { return SPLIT_PATTERN.splitAsStream(tagStr) .filter(StringUtils::isNotBlank) .collect(Collectors.toList()); }

3.5 与JSON转换的配合使用

处理商品规格参数时的典型应用:

// 将规格参数Map转换为搜索条件字符串 Map<String, Object> specs = getGoodsSpecs(); String searchSpecs = specs.entrySet().stream() .map(entry -> entry.getKey() + ":" + entry.getValue()) .collect(Collectors.joining(";")); // 反向解析 Map<String, String> specMap = Arrays.stream(StringUtils.split(searchSpecs, ";")) .map(item -> StringUtils.split(item, ":", 2)) .collect(Collectors.toMap( arr -> arr[0], arr -> arr.length > 1 ? arr[1] : "" ));

4. 完整商品查询接口实现示例

4.1 控制器层设计

@RestController @RequestMapping("/goods") @RequiredArgsConstructor public class GoodsController { private final GoodsService goodsService; @GetMapping("/search") public Result searchGoods( @RequestParam Map<String, Object> searchParams, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { // 添加分页参数 searchParams.put("page", page); searchParams.put("size", size); return Result.success(goodsService.searchGoods(searchParams)); } }

4.2 服务层核心逻辑

@Service @RequiredArgsConstructor public class GoodsServiceImpl implements GoodsService { private final GoodsMapper goodsMapper; @Override public PageResult<Goods> searchGoods(Map<String, Object> params) { // 构建查询条件 QueryWrapper<Goods> wrapper = buildQueryWrapper(params); // 分页参数处理 Integer page = NumberUtils.toInt(params.get("page").toString(), 1); Integer size = NumberUtils.toInt(params.get("size").toString(), 10); Page<Goods> pageInfo = new Page<>(page, size); // 执行查询 IPage<Goods> result = goodsMapper.selectPage(pageInfo, wrapper); return new PageResult<>( result.getTotal(), result.getRecords() ); } private QueryWrapper<Goods> buildQueryWrapper(Map<String, Object> params) { QueryWrapper<Goods> wrapper = new QueryWrapper<>(); // 分类筛选 if (params.containsKey("categoryId")) { wrapper.eq("category_id", params.get("categoryId")); } // 品牌多选 if (params.containsKey("brandIds")) { List<Long> brandIds = Arrays.stream( StringUtils.split(params.get("brandIds").toString(), ",")) .map(NumberUtils::toLong) .collect(Collectors.toList()); wrapper.in("brand_id", brandIds); } // 价格区间 if (params.containsKey("minPrice") && params.containsKey("maxPrice")) { wrapper.between("price", NumberUtils.toDouble(params.get("minPrice").toString()), NumberUtils.toDouble(params.get("maxPrice").toString()) ); } // 关键词搜索 if (params.containsKey("keyword")) { wrapper.and(qw -> qw .like("title", params.get("keyword")) .or() .like("sub_title", params.get("keyword")) .or() .like("keywords", params.get("keyword")) ); } // 规格参数过滤 if (params.containsKey("specs")) { handleSpecsCondition(wrapper, params.get("specs").toString()); } return wrapper; } private void handleSpecsCondition(QueryWrapper<Goods> wrapper, String specsJson) { try { Map<String, List<String>> specsMap = JSON.parseObject( specsJson, new TypeReference<Map<String, List<String>>>(){} ); specsMap.forEach((specName, specValues) -> { wrapper.and(qw -> { for (String value : specValues) { qw.or().like("specs", String.format("\"%s\":\"%s\"", specName, value)); } }); }); } catch (Exception e) { throw new BusinessException("规格参数解析失败"); } } }

4.3 前端参数构造示例

// 构造查询参数对象 const buildSearchParams = () => { const params = new URLSearchParams(); // 基本参数 params.append('categoryId', selectedCategory); params.append('keyword', searchKeyword); // 多选品牌 if (selectedBrands.length > 0) { params.append('brandIds', selectedBrands.join(',')); } // 价格区间 if (priceRange) { params.append('minPrice', priceRange[0]); params.append('maxPrice', priceRange[1]); } // 规格参数 if (Object.keys(selectedSpecs).length > 0) { params.append('specs', JSON.stringify(selectedSpecs)); } return params; }; // 发起请求 const searchGoods = async () => { const response = await fetch(`/goods/search?${buildSearchParams()}`); return await response.json(); };

5. 性能优化与异常处理

5.1 缓存策略设计

@Cacheable(value = "goods", key = "#params.hashCode()") public PageResult<Goods> searchGoods(Map<String, Object> params) { // 查询逻辑 }

缓存键设计建议

  1. 排除分页参数:

    String cacheKey = params.entrySet().stream() .filter(entry -> !"page".equals(entry.getKey()) && !"size".equals(entry.getKey())) .sorted(Map.Entry.comparingByKey()) .map(entry -> entry.getKey() + "=" + entry.getValue()) .collect(Collectors.joining("&"));
  2. 对JSON参数做MD5摘要:

    if (params.containsKey("specs")) { String specs = params.get("specs").toString(); params.put("specsHash", DigestUtils.md5Hex(specs)); }

5.2 参数校验最佳实践

// 使用Spring Validation进行参数校验 public Result searchGoods( @Valid @RequestParam Map<String, Object> params, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return Result.fail(bindingResult.getAllErrors()); } // 业务逻辑 } // 自定义校验注解 @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = GoodsSearchValidator.class) public @interface ValidGoodsSearch { String message() default "Invalid search parameters"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } // 校验器实现 public class GoodsSearchValidator implements ConstraintValidator<ValidGoodsSearch, Map<String, Object>> { @Override public boolean isValid(Map<String, Object> params, ConstraintValidatorContext context) { // 验证价格区间 if (params.containsKey("minPrice") && params.containsKey("maxPrice")) { double min = Double.parseDouble(params.get("minPrice").toString()); double max = Double.parseDouble(params.get("maxPrice").toString()); if (min > max) { context.buildConstraintViolationWithTemplate("价格区间无效") .addConstraintViolation(); return false; } } return true; } }

5.3 日志记录与监控

@Aspect @Component @Slf4j public class GoodsSearchAspect { @Around("execution(* com..GoodsService.searchGoods(..))") public Object logSearch(ProceedingJoinPoint joinPoint) throws Throwable { Map<String, Object> params = (Map<String, Object>) joinPoint.getArgs()[0]; long start = System.currentTimeMillis(); try { Object result = joinPoint.proceed(); long duration = System.currentTimeMillis() - start; log.info("商品搜索成功 - 参数: {}, 耗时: {}ms", StringUtils.substring(JSON.toJSONString(params), 0, 200), duration); // 监控指标上报 Metrics.counter("goods.search.count").increment(); Metrics.timer("goods.search.time").record(duration, TimeUnit.MILLISECONDS); return result; } catch (Exception e) { log.error("商品搜索失败 - 参数: {}", params, e); throw e; } } }
http://www.jsqmd.com/news/609165/

相关文章:

  • 网易云音乐体验升级:BetterNCM插件管理器全攻略
  • MyCLI:一个增强型MySQL命令行客户端
  • 去屑洗发水哪个效果好? - 中媒介
  • 终极启动盘制作工具:Deepin Boot Maker 完整使用指南
  • 高防服务器被攻击后 IP 被封?黑洞解封与清洗策略设置
  • 如何掌握递归与迭代:编程思维深度训练指南
  • Pretext:值得关注的文本排版引擎啡
  • 西门子S7-200 SMART高速计数器实战:从模式配置到脉冲精准捕获
  • 主席树实战:C++实现区间第K小查询(附动态图解与完整代码)
  • 安卓逆向浅浅范围
  • 高防服务器无法远程连接?端口、防火墙与安全组排查
  • 头发干枯毛躁用什么洗发水? - 中媒介
  • 掌握Vue 3日历组件实战:从业务场景到深度定制的全流程指南
  • 当cl软件节点标红无法上网的时候-可能是因为电脑的时间没有同步过来,可以通过右下角右键-调整日期和时间-点击立即同步即可同步北京时间-方法二,使用SyncTime-Aliyun.bat软件同步时间。-
  • 聚酰亚胺薄膜价格怎么样? - 中媒介
  • Git团队协作终极指南:10个提升项目可维护性的关键实践
  • [python]logging模块
  • 5大核心优势!Open Canvas对比OpenAI Canvas:开源AI协作工具如何重塑你的工作流
  • 1篇1章3节:AIGC的发展历程,迈向生成创造世界的关键突破
  • Omron NJ/NX程序:自动化控制与智能人机交互的集成
  • 婚纱照无隐形消费推荐? - 中媒介
  • 锌合金门厂家哪家强? - 中媒介
  • ncmdump终极指南:5分钟解锁网易云加密音乐,实现全设备自由播放
  • JointJS测试策略完整指南:单元测试与端到端测试的最佳实践
  • 深信服防火墙AF8.0实战配置指南:从零搭建安全防护体系
  • 表皮覆合设备供应商推荐? - 中媒介
  • 1篇1章4节:生成对抗网络GAN和图像生成领域的StyleGAN
  • 运维视角的测试:可观测性驱动的质量保障
  • Python execjs执行中文JS文件报GBK解码错?一个继承Popen的修复方案
  • RT-Thread SPI设备驱动实战:手把手教你挂载SPI20设备并驱动RW007 WiFi模块