博客十:小程序端主页商品推送功能实现

1 | 业务背景与技术选型

随着移动电商流量红利见顶,「千人千面」已成为提升 GMV 与用户黏性的核心抓手。本项目旨在用最小改动让传统规则系统接入 LLM 精排能力:

graph LR
A[用户进入首页] --> B(候选集生成<br/>MySQL + Redis)
B --> C{候选集<br/>≈100~300 item}
C --> D[调用推荐微服务<br/>/recommendProducts]
D --LLM prompt + profile--> E((GPT-4o))
E --返回 JSON(topK)--> F(ProdService.pageByProdsId)
F --payload--> G[小程序渲染]

引入大模型原因:

  1. 多模态特征(图 / 文 / 结构化属性)难以一次性编码到简单 LR/BPR 中;

  2. 我们已有 商品文生向量图像特征用户交互序列,直接塞给 LLM 可利用其多模态推理能力做轻量排序;

  3. Prompt 易迭代、不改线上二进制。

技术栈概览

层级

核心框架

说明

前端小程序

uni-app 3.x + Vue 3 + Pinia

一次编码,多端运行;Composition API

后端网关

Spring Cloud Gateway

统一鉴权 / 灰度 / 限流

业务服务

Spring Boot 3 + MyBatis-Plus

轻量 ORM,分页插件

缓存

Redis 7

高频列表、本地缓存 Caffeine 二级

推荐微服务

Python 3.11 + FastAPI

Faiss 向量检索 + OpenAI SDK

消息队列

RocketMQ

异步写曝光 / 点击 / 购买日志


2 | 数据模型与表结构

关键表只列需要字段,为方便理解已去除审计字段:

CREATE TABLE `prod` (
  `prod_id` BIGINT PRIMARY KEY,
  `prod_name` VARCHAR(255),
  `put_away_time` DATETIME,
  `price` DECIMAL(10,2),
  `ori_price` DECIMAL(10,2),
  `sold_num` BIGINT,
  `daily_sold` BIGINT,
  `brief` VARCHAR(255),
  `imgs` TEXT,
  `delivery_mode` JSON,
  `tag_list` JSON
);

CREATE TABLE `sku` (
  `sku_id` BIGINT PRIMARY KEY,
  `prod_id` BIGINT,
  `status` TINYINT,
  `attrs` JSON,
  `stock` INT
);

CREATE TABLE `prod_tag_reference` (
  `id` BIGINT PRIMARY KEY,
  `prod_id` BIGINT,
  `tag_id` BIGINT
);

CREATE TABLE `user_profile` (
  `user_id` VARCHAR(64) PRIMARY KEY,
  `gender` CHAR(1),
  `age` INT,
  `favorite_cats` JSON,
  `embedding` BLOB
);


3 | 候选集生成策略

3.1 新品推荐 (New Arrivals)

  • 业务规则: put_away_time >= NOW() - INTERVAL 7 DAY,按 put_away_time DESC

  • 接口: /prod/lastedProdPage

  • 实现: ProductMapper.pageByPutAwayTime 使用 <![CDATA[ put_away_time >= DATE_SUB(...) ]]>

3.2 限时特惠 (Time-Limited Deal)

  • 业务规则: ori_price - price ≥ 5% 且 deal_start < NOW() < deal_end

  • 优势: 天然带营销氛围,可做限购逻辑。

  • 实现: 单独 promotion 表,物化视图写回 Redis ZSET。

3.3 每日疯抢 (Daily Hot)

  • 业务规则: daily_sold DESC top N

  • 接口: /prod/moreBuyProdList

  • 实现: 00:05 定时任务 refreshDailySold() 重置并写入前一天数据。

3.4 商城热卖 (Overall Hot Sale)

  • 业务规则: sold_num DESC 排序

  • 接口: /prod/tagProdList 中 style === "1"

3.5 猜你喜欢 (You May Like)

  • 使用 Faiss 对商品向量做 ANN 召回 M=200 条;

  • 无画像 fallback 到 Hot + New 策略。

注: 五类候选生成平均延迟 < 30 ms。


4 | LLM 精排 (Re-Ranking with GPT-4o)

4.1 整体流程

  1. 请求 /prod/* 接口时注入 X-UserId

  2. 调用 RecommendService.recommendProducts

  3. Python 拼 Prompt(见下)

  4. GPT-4o 排序返回 topK

  5. pageByProdsId 保序拼装

4.2 Prompt 设计

{
  "system": "You are an e-commerce ranking model. Return a JSON array of productIds in best ordering.",
  "user": "User profile: {\"age\":25,\"gender\":\"F\",\"favorite_cats\":[3,5,7]}\nCandidate products:\n1. {\"prodId\":101,\"categoryId\":3,\"price\":129.9,\"image\":\"...\",\"title\":\"...\"}\n2. {...} ...\nOutput format: [101, 205, 999]"
}

  • 候选数 ≤ 50

  • 素材多模态,image 仅 URL,caption 推理

  • GPT temperature=0.2

4.3 Embedding & Feature Schema

字段

类型

说明

title_tok

文本

分词或 SentencePiece 编码

img_clip

512-d

CLIP 图像向量

price_norm

float

归一化后价位

cat_onehot

n

一级分类 one-hot


5 | 后端源码逐行解析

5.1 

ProdController

亮点:

  • @Tag + @Operation → 自动文档

  • SecurityUtils.getUser().getUserId() 安全透传

  • FIELD() 保序排序

@GetMapping("/prodListByTagId")
public ServerResponseEntity<IPage<ProductDto>> prodListByTagId(
        @RequestParam("tagId") Long tagId, PageParam<ProductDto> page) {
    String userId = SecurityUtils.getUser().getUserId();
    String reviewerId = userService.getReviewerIdByUserId(userId);
    List<Long> recommendedProdsId =
            recommendService.recommendProducts(reviewerId, tagId, 6);
    IPage<ProductDto> productPage =
            prodService.pageByProdsId(page, recommendedProdsId);
    return ServerResponseEntity.success(productPage);
}

5.2 

ProductServiceImpl.pageByProdsId

@Override
public IPage<ProductDto> pageByProdsId(Page<ProductDto> page, List<Long> prodsId) {
    if (CollectionUtil.isEmpty(prodsId)) {
        return Page.of(page.getCurrent(), page.getSize(), 0);
    }
    return baseMapper.pageByProdsId(page, prodsId);
}

对应 XML:

<select id="pageByProdsId" resultType="com.yami.shop.bean.app.dto.ProductDto">
  SELECT  <include refid="BaseColumnList"/>
  FROM prod
  WHERE prod_id IN
  <foreach collection="prodsId" item="id" open="(" separator="," close=")">
      #{id}
  </foreach>
  ORDER BY FIELD(prod_id,
  <foreach collection="prodsId" item="id" separator=",">
      #{id}
  </foreach>)
</select>

5.3 推荐微服务片段

def recommend_products(user_id: str, tag_id: int, size: int) -> list[int]:
    candidates = recall_candidates(tag_id, size * 3)
    profile = db["user_profile"].find_one({"user_id": user_id}) or {}
    prompt = build_prompt(profile, candidates)
    resp = openai.chat.completions.create(model="gpt-4o", **prompt).json()
    product_ids = json.loads(resp["choices"][0]["message"]["content"])
    return product_ids[:size]


6 | 前端小程序实现细节

6.1 页面结构

  • <swiper> Banner

  • cat-item 宫格分类

  • up-to-date / hot-sale / more-prod 三类卡片

6.2 异步加载

onLoad(() => { getAllData() })

const getAllData = () => {
  Promise.all([
    getIndexImgs(),
    getNoticeList(),
    getTag()
  ]).finally(() => uni.stopPullDownRefresh())
}

6.3 骨架屏占位

<skeleton v-if="loading" rows="6" theme="article" />
<view v-else>...</view>

6.4 性能优化

  • virtual-list 长列表

  • 图片统一 mode="aspectFill"

  • uni.addInterceptor('request') JWT 注入


7 | 缓存与容灾

7.1 Redis 键设计

Key 模板

类型

TTL

说明

prod:new

ZSET

10 min

上架时间

prod:deal:{date}

ZSET

5 min

折扣百分比

prod:daily:{date}

ZSET

5 min

当日销量

prod:hot

ZSET

1 h

累计销量

rec:llm:{userId}:{tagId}

LIST

30 s

LLM 短缓存

7.2 降级策略

  • 超时 fallback

  • 返回 200 + X-Rec-Mode header

  • Prometheus & 钉钉告警


8 | 监控 & 性能数据

指标

P50

P90

P99

备注

候选生成

12 ms

23 ms

45 ms

MySQL + Redis

LLM 精排

610 ms

890 ms

1.4 s

GPT-4o, 50 candidates

总接口 /prod/*

680 ms

960 ms

1.6 s

含网络

小程序首屏白屏

820 ms

1.3 s

2.0 s

Android 10, 4G


9 | 常见坑 & Debug Tips

  1. FIELD 排序失效:缺失第二个 foreach

  2. uni-app key 冲突:重复触发警告

  3. LLM JSON 不合法:正则过滤字符

  4. Redis ZSET Tie 分数覆盖:加微秒区分


10 | 总结与展望

本文自下而上拆解了「规则召回 + LLM 精排」全链路,并基于 mall4uni 实现了小程序首页推荐。

未来方向包括:

  • 向量检索替换为 Milvus + Delta Lake;

  • RAG 将评价摘要纳入 Prompt;

  • 多臂赌博机 + LLM 协同探索;

  • Serverless GPT 方案 降本提效。

🎉 至此,首页推荐的所有实现细节已倾囊相授。如果你觉得本文对你有帮助,欢迎一键三连 👍 💬 ⭐!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值