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

【商城实战(60)】解锁搜索排序与相关性优化密码(java版)

【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配,乃至运营推广策略,102 章内容层层递进。无论是想深入钻研技术细节,还是探寻商城运营之道,本专栏都能提供从 0 到 1 的系统讲解,助力你打造独具竞争力的电商平台,开启电商实战之旅。

目录

  • 一、设计搜索结果排序算法
    • 1.1 综合因素考量
    • 1.2 前端实现(uniapp 与 Element plus)
    • 1.3 后端实现(SpringBoot 与 Mybatis-plus )
  • 二、优化搜索相关性
    • 2.1 BM25 算法原理
    • 2.2 前后端集成实现
  • 三、基于用户搜索行为动态调整参数
    • 3.1 行为数据收集与存储
    • 3.2 参数动态调整策略
    • 3.3 实时更新效果展示


一、设计搜索结果排序算法

1.1 综合因素考量

在电商商城中,搜索结果的排序直接影响用户体验和购买转化率。综合考虑商品销量、价格、评分等因素,能够为用户提供更符合其需求的商品排序。高销量商品通常代表着受欢迎程度,价格因素能满足不同预算用户的需求,评分则反映了商品的质量和用户满意度。通过合理设计排序算法,将这些因素有机结合,可以让用户更快速地找到心仪的商品。例如,对于追求性价比的用户,价格和评分的权重可以适当提高;对于追求热门商品的用户,销量的权重可以加大。

1.2 前端实现(uniapp 与 Element plus)

在 uniapp(移动前端)中,通过调用 API 接收后端返回的排序后商品数据。在页面的script部分,定义数据接收函数,如:

export default {
  data() {
    return {
      sortedGoodsList: []
    };
  },
  methods: {
    receiveSortedGoods(data) {
      this.sortedGoodsList = data;
    }
  }
};

在template部分,使用v-for指令遍历sortedGoodsList,展示商品信息,如:

<template>
  <view>
    <view v-for="(item, index) in sortedGoodsList" :key="index">
      <text>{{ item.goodsName }}</text>
      <text>价格: {{ item.price }}</text>
      <text>评分: {{ item.rating }}</text>
    </view>
  </view>
</template>

同时,提供排序方式切换的交互界面,如通过picker组件让用户选择按销量、价格或评分排序。

在 Element plus(PC 前端)中,同样在script部分定义数据接收和排序切换方法,如:

import { ref } from 'vue';
export default {
  setup() {
    const sortedGoodsList = ref([]);
    const sortType = ref('sales');
    const receiveSortedGoods = (data) => {
      sortedGoodsList.value = data;
    };
    const changeSortType = (type) => {
      sortType.value = type;
      // 发送请求,重新获取对应排序的数据
    };
    return {
      sortedGoodsList,
      sortType,
      receiveSortedGoods,
      changeSortType
    };
  }
};

在template部分,使用el-table展示商品列表,并添加排序切换按钮,如:

<template>
  <el-table :data="sortedGoodsList">
    <el-table-column prop="goodsName" label="商品名称"></el-table-column>
    <el-table-column prop="price" label="价格"></el-table-column>
    <el-table-column prop="rating" label="评分"></el-table-column>
  </el-table>
  <el-button-group>
    <el-button @click="changeSortType('sales')">按销量排序</el-button>
    <el-button @click="changeSortType('price')">按价格排序</el-button>
    <el-button @click="changeSortType('rating')">按评分排序</el-button>
  </el-button-group>
</template>

1.3 后端实现(SpringBoot 与 Mybatis-plus )

SpringBoot 作为后端框架,接收前端发送的搜索请求和排序参数。在 Controller 层,定义请求处理方法,如:

@RestController
@RequestMapping("/search")
public class SearchController {
    @Autowired
    private GoodsService goodsService;

    @GetMapping("/sorted")
    public List<Goods> getSortedGoods(@RequestParam String keyword, @RequestParam String sortType) {
        return goodsService.getSortedGoods(keyword, sortType);
    }
}

在 Service 层,GoodsService实现具体的业务逻辑,调用 Mybatis-plus 从数据库获取数据并进行排序。例如,当按销量排序时:

@Service
public class GoodsServiceImpl implements GoodsService {
    @Autowired
    private GoodsMapper goodsMapper;

    @Override
    public List<Goods> getSortedGoods(String keyword, String sortType) {
        QueryWrapper<Goods> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("goods_name", keyword);
        if ("sales".equals(sortType)) {
            queryWrapper.orderByDesc("sales_volume");
        } else if ("price".equals(sortType)) {
            queryWrapper.orderByAsc("price");
        } else if ("rating".equals(sortType)) {
            queryWrapper.orderByDesc("rating");
        }
        return goodsMapper.selectList(queryWrapper);
    }
}

Mybatis-plus 通过GoodsMapper接口与数据库交互,执行 SQL 查询语句,获取符合条件并排序后的商品数据。

二、优化搜索相关性

2.1 BM25 算法原理

BM25(Best Matching 25)是一种用于估算文档与查询相关性的排名函数,在信息检索领域应用广泛。其核心原理是综合考虑词频(TF)、逆文档频率(IDF)以及文档长度等因素来计算相关性得分。

对于一个查询 Q 和文档 D,BM25 算法计算相关性得分的公式如下:在这里插入图片描述

其中:

  • n是查询 Q 中的词项数。
  • q_i是查询 Q 中的第i个词项。
  • IDF(q_i)是逆文档频率,计算方式通常是(\text{IDF}(q_i)=\log\frac{N - n(q_i)+0.5}{n(q_i)+0.5}),N是文档总数,n(q_i)是包含词项q_i的文档数 。这一计算方式使得稀有词的 IDF 值较高,在相关性计算中具有更大的权重。
  • f(q_i,D)是词项q_i在文档 D 中的出现频率。
  • k_1和b是可调节的参数。k_1主要用于调节词频对得分的影响,防止词频过高对得分产生过大影响,通常取值在 1.2 - 2.0 之间,默认值为 1.2;b是文档长度归一化参数,用于控制文档长度对得分的影响,取值范围一般在 0 - 1 之间,默认值为 0.75 。
  • len(D)是文档 D 的长度,avg_len是所有文档的平均长度。通过文档长度归一化,避免长文档在搜索结果中总是占据优势。

例如,当用户搜索 “苹果手机” 时,BM25 算法会分别计算 “苹果” 和 “手机” 这两个词项在各个商品文档中的相关性得分,然后累加得到每个商品文档与查询的总相关性得分,根据得分对商品进行排序,得分越高表示相关性越强。

2.2 前后端集成实现

在 SpringBoot 后端集成 BM25 算法,首先需要对商品数据进行预处理,构建文档集合。将商品的名称、描述等文本信息作为文档内容,分词后形成词项集合。例如,使用 HanLP 等中文分词工具对商品数据进行分词处理:

import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import java.util.List;

public class GoodsPreprocessor {
    public static List<Term> tokenize(String text) {
        return HanLP.segment(text);
    }
}

然后,根据 BM25 算法公式实现计算相关性得分的逻辑。可以封装一个BM25Calculator类:

import java.util.List;

public class BM25Calculator {
    private final double k1 = 1.2;
    private final double b = 0.75;
    private final int N;
    private final double[] idf;
    private final double avgdl;

    public BM25Calculator(List<List<String>> corpus) {
        N = corpus.size();
        // 计算IDF等相关参数
        // ...
    }

    public double calculate(List<String> query, List<String> document) {
        double score = 0;
        // 根据BM25公式计算得分
        // ...
        return score;
    }
}

在搜索接口中,接收前端传来的查询关键词,调用BM25Calculator计算每个商品与查询的相关性得分,然后根据得分对商品进行排序,并返回排序后的结果给前端。

在 uniapp 前端,通过fetch或axios等工具发送搜索请求,接收后端返回的基于 BM25 算法排序后的商品数据,并展示在页面上。例如:

import axios from 'axios';

export default {
  data() {
    return {
      relevantGoodsList: []
    };
  },
  methods: {
    async searchGoods(keyword) {
      try {
        const response = await axios.get('/search/bm25', {
          params: { keyword }
        });
        this.relevantGoodsList = response.data;
      } catch (error) {
        console.error('搜索请求失败', error);
      }
    }
  }
};

在 Element plus 前端,同样通过 HTTP 请求获取数据,并在template中展示商品列表:

<template>
  <el-table :data="relevantGoodsList">
    <el-table-column prop="goodsName" label="商品名称"></el-table-column>
    <el-table-column prop="description" label="商品描述"></el-table-column>
  </el-table>
</template>

import { ref } from 'vue';
import axios from 'axios';

export default {
  setup() {
    const relevantGoodsList = ref([]);
    const searchGoods = async (keyword) => {
      try {
        const response = await axios.get('/search/bm25', {
          params: { keyword }
        });
        relevantGoodsList.value = response.data;
      } catch (error) {
        console.error('搜索请求失败', error);
      }
    };
    return {
      relevantGoodsList,
      searchGoods
    };
  }
};

通过以上前后端集成,实现了基于 BM25 算法的搜索相关性优化,为用户提供更精准的搜索结果。

三、基于用户搜索行为动态调整参数

3.1 行为数据收集与存储

在 SpringBoot 后端,通过切面编程(AOP)或过滤器等技术收集用户搜索行为数据。例如,使用 AOP 定义一个切面类SearchBehaviorAspect:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

@Aspect
@Component
public class SearchBehaviorAspect {
    @Autowired
    private SearchBehaviorMapper searchBehaviorMapper;

    @Around("execution(* com.example.demo.controller.SearchController.search(..))")
    public Object collectSearchBehavior(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String keyword = request.getParameter("keyword");
        String userId = request.getHeader("userId");// 假设通过header传递userId
        SearchBehavior searchBehavior = new SearchBehavior();
        searchBehavior.setUserId(userId);
        searchBehavior.setSearchKeyword(keyword);
        searchBehavior.setSearchTime(new Date());
        // 记录其他行为数据,如搜索结果点击情况等
        searchBehaviorMapper.insert(searchBehavior);
        return joinPoint.proceed();
    }
}

SearchBehaviorMapper使用 Mybatis-plus 将数据插入数据库:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.SearchBehavior;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SearchBehaviorMapper extends BaseMapper<SearchBehavior> {
}

数据库表结构设计如下:

CREATE TABLE search_behavior (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id VARCHAR(50),
    search_keyword VARCHAR(255),
    search_time TIMESTAMP,
    -- 其他字段
);

3.2 参数动态调整策略

在后端服务中,定时任务或事件驱动机制触发对用户搜索行为数据的分析。例如,使用 Spring 的@Scheduled注解定时执行数据分析任务:

import com.example.demo.entity.SearchBehavior;
import com.example.demo.mapper.SearchBehaviorMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Component
public class SearchBehaviorAnalyzer {
    @Autowired
    private SearchBehaviorMapper searchBehaviorMapper;

    @Scheduled(cron = "0 0 2 * *?")// 每天凌晨2点执行
    public void analyzeSearchBehavior() {
        List<SearchBehavior> searchBehaviors = searchBehaviorMapper.selectList(null);
        Map<String, Long> keywordCountMap = searchBehaviors.stream()
               .collect(Collectors.groupingBy(SearchBehavior::getSearchKeyword, Collectors.counting()));
        // 根据关键词搜索次数调整排序与相关性参数
        for (Map.Entry<String, Long> entry : keywordCountMap.entrySet()) {
            String keyword = entry.getKey();
            long count = entry.getValue();
            if (count > 1000) {
                // 例如,提高该关键词搜索结果中销量因素的权重
            }
        }
        // 分析其他行为数据,如搜索结果点击情况,调整参数
    }
}

通过分析用户搜索行为数据,如热门搜索关键词、搜索结果点击次数、搜索结果购买转化率等,动态调整排序与相关性参数。对于经常被搜索且购买转化率高的关键词,提高相关商品在搜索结果中的排序权重;对于用户频繁点击但未购买的商品,降低其相关性得分,以优化搜索结果排序。

3.3 实时更新效果展示

在 uniapp 前端,通过 WebSocket 或轮询机制获取参数调整后的搜索结果更新。例如,使用uni-socket.io实现 WebSocket 连接:

import io from 'uni-socket.io';

export default {
  data() {
    return {
      socket: null
    };
  },
  mounted() {
    this.socket = io('http://localhost:3000');// 假设后端WebSocket服务地址
    this.socket.on('searchResultUpdate', (data) => {
      this.sortedGoodsList = data;
    });
  },
  beforeDestroy() {
    this.socket.disconnect();
  }
};

在 Element plus 前端,同样使用 WebSocket 实现实时更新:

<template>
  <el-table :data="sortedGoodsList">
    <el-table-column prop="goodsName" label="商品名称"></el-table-column>
    <el-table-column prop="price" label="价格"></el-table-column>
    <el-table-column prop="rating" label="评分"></el-table-column>
  </el-table>
</template>

import { ref } from 'vue';
import io from'socket.io-client';

export default {
  setup() {
    const sortedGoodsList = ref([]);
    const socket = io('http://localhost:3000');
    socket.on('searchResultUpdate', (data) => {
      sortedGoodsList.value = data;
    });
    return {
      sortedGoodsList
    };
  }
};

当后端参数调整后,通过 WebSocket 推送更新后的搜索结果数据给前端,前端实时展示优化后的搜索结果,让用户能够及时体验到更精准、符合其行为习惯的搜索服务。


http://www.kler.cn/a/611443.html

相关文章:

  • uniapp再次封装uni-nav-bar导航栏组件
  • AWE 2025 |AI科技引领智能生活,传感器赋能智慧时代
  • Rust从入门到精通之进阶篇:16.智能指针
  • UML 图六种箭头含义详解:泛化、实现、依赖、关联、聚合、组合
  • LeetCode热题100JS(79/100)第十五天|347|295|121|55|45
  • 初级:反射机制面试题全攻略
  • Vue Router动态改变路由参数的两种方法
  • Rust从入门到精通之进阶篇:18.测试与文档
  • 淘宝评论API接口详解与JSON数据示例
  • Unity Shader编程】之复杂光照
  • Java技术生态前沿:Java 21革新与性能优化全解析
  • leetcode 46 全排列 | 回溯
  • 重学vue3(三):vue3基本语法及使用
  • 【测试开发】OKR 小程序端黑盒测试报告
  • leetcode.189.轮转数组
  • ZBlog泛目录程序插件实现零编程基础实现自动化内容生成
  • 一、MySQL8的my.ini文件
  • 【Python】pillow库学习笔记4-利用ImageDraw和ImageFont在图像上添加文字
  • sqlite3数据库(文件)损坏恢复方法
  • 【论文分析】无人机轨迹规划,Fast-Planner:实时避障+全局最优的路径引导优化算法