基于实时参数的分页策略——商城APP应用场景分析
1.引言
在现代商城APP中,用户需要经常浏览大量的商品信息。由于商品的价格、库存、销量等数据是实时动态变化的,当用户应用筛选条件或排序规则时,传统的分页方式可能会导致以下问题:数据丢失、重复加载、性能瓶颈等。
2.传统分页方法的不足
传统分页通常基于静态的页面代码或数据范围,每次请求时,页面根据指定的页面代码或偏移量来加载固定数量的数据。这种方法存在以下几个问题,特别是在动态数据场景下表现严重突出:
2.1 数据丢失或重复
在商城APP中,商品信息(如库存、价格、销量)可能随时发生变化。假设用户在翻页时,数据发生了更新(如某些商品被下架或价格波动),则传统分页可能会出现数据丢失或重复的情况。例如,用户翻到下一页时,可能会丢失已更新的数据或已经看到被删除的商品。
2.2 不适应筛选和排序
当根据用户价格、销量、评价等动态筛选条件来浏览商品时,传统分页的固定页面码和数据偏移方式无法灵活适应这些变化。如果用户更改筛选条件或排序规则,可能导致数据错乱,甚至出现重复加载或丢失数据的情况。
3.实时动态参数的分页策略优点
简化前端代码:前端只需要通过一个动态参数(如 wp
)来传递所有分页和筛选条件,避免了多个参数的处理,代码更简洁。
提高用户体验:前端可以根据用户操作实时调整请求参数,快速加载当前页的数据,避免刷新整个页面,使用户操作更流畅。
按需加载数据:只加载当前需要的数据,减少不必要的请求和数据传输,提高响应速度,提升前端性能。
4.分页列表接口设计
{
"isEnd": [boolean],
"list": [
{
"id": 196,
"categoryName": "笔记本电脑",
"title": "笔记本电脑A",
"goodImage": "https://example.com/images/laptop1.jpg",
"sales": 200,
"price": 5999
},
{
"id": 197,
"categoryName": "笔记本电脑",
"title": "笔记本电脑B",
"goodImage": "https://example.com/images/laptop3.jpg",
"sales": 250,
"price": 6999
},
{
"id": 198,
"categoryName": "笔记本电脑",
"title": "笔记本电脑C",
"goodImage": "https://example.com/images/laptop5.jpg",
"sales": 300,
"price": 7999
},
{
"id": 199,
"categoryName": "笔记本电脑",
"title": "笔记本电脑D",
"goodImage": "https://example.com/images/laptop7.jpg",
"sales": 220,
"price": 8499
},
{
"id": 200,
"categoryName": "笔记本电脑",
"title": "笔记本电脑E",
"goodImage": "https://example.com/images/laptop9.jpg",
"sales": 180,
"price": 9299
}
],
"wp": "eyJuYW1lIjoi56yU6K6w5pysIiwicGFnZSI6NSwicGFnZVNpemUiOjEwfQ%3D%3D"
}
5.加密与编码
1. 加密
加密是指将数据转化为另一种不可读的格式,以确保数据在传输或存储过程中不会被未经授权的第三方获取。加密的目的是保护数据的机密性。
- 过程:加密使用密钥和加密算法将原始数据(明文)转化为不可读的密文。
- 解密:加密后的数据只有通过特定的密钥和算法才能恢复为原始数据(明文)。
- 常见加密算法:
- 对称加密:使用同一个密钥进行加密和解密(如AES、DES)。
- 非对称加密:使用一对密钥进行加密和解密,通常是公钥和私钥(如RSA)。
2. 编码(Encoding)
编码是将数据从一种格式转换为另一种格式,目的是确保数据能够被正确传输和读取。编码不是为了保护数据的机密性,而是为了保证数据在传输过程中不丢失、没有误解,能够在不同的系统和平台上进行正确处理。
- 过程:编码通过特定的规则将原始数据转化为另一种字符格式。
- 解码:编码后的数据可以通过对应的解码方式恢复成原始数据。
- 常见编码方式:
- Base64:将二进制数据转换为可打印的ASCII字符,用于在URL、HTTP请求等地方传输数据。
- URL编码:将特殊字符转换为%符号表示的格式,确保URL的正确性。
6.基于实时参数的分页策略代码实现
GoodsVo
类
@Data
@Accessors(chain = true)
public class GoodsVo {
private Boolean isEnd;
private String wP;
private List<GoodsItemVo> list;
}
GoodsItemVo
类
@Data
@Accessors(chain = true)
public class GoodsItemVo {
private BigInteger id;
private String categoryName;
private String title;
private String goodImage;
private Integer sales;
private Integer price;
}
动态参数 Wp类
@Data
@Accessors(chain = true)
public class Wp implements Serializable {
public Integer page;
public Integer pageSize;
public String name;
}
写一个工具类Utility将编码encodeWp封装 (编码器使用urlEncode,JSON序列化使用fastjson)
public class Utility {
public String encodeWp(int page, int pageSize,String keyword) {
Wp wp1 = new Wp();
wp1.setPage(page);
wp1.setPageSize(pageSize);
wp1.setName(keyword);
// JSON 序列化
String jsonString = JSON.toJSONString(wp1);
// Base64 编码
String base64Encoded = Base64.getEncoder().encodeToString(jsonString.getBytes(StandardCharsets.UTF_8));
String urlEncode = URLEncoder.encode(base64Encoded);
return urlEncode;
}
}
为什么要使用编码
简化 URL:避免传递多个查询参数,使 URL 更简洁易管理。
支持复杂数据结构:封装多个分页、筛选条件和排序信息,便于传递复杂的数据。
提高安全性与可读性:可以通过编码进行加密或压缩,保护数据隐私或减少数据传输量。
Controller层分页列表接口
@RequestMapping("/goods/list")
public GoodsVo getGoodsAll( @RequestParam(name = "keyword",required = false) String keyword,
@RequestParam(name = "wp",required = false) String wp) {
int page;
int pageSize;
String name;
if (wp != null) {
// Base64解码
String decodedWp = URLDecoder.decode(wp, StandardCharsets.UTF_8);
byte[] decodedBytes = Base64.getDecoder().decode(decodedWp);
String jsonString = new String(decodedBytes, StandardCharsets.UTF_8);
String decodeWp = URLDecoder.decode(jsonString, StandardCharsets.UTF_8);
System.out.println("JSON string: " + decodeWp);
// 解析 JSON 字符串为 Wp 对象
Wp wpJson = JSON.parseObject(decodeWp, Wp.class);
page = wpJson.getPage();
pageSize = wpJson.getPageSize();
name = wpJson.getName();
} else {
page = 1;
pageSize = 10;
name = keyword;
}
// 获取商品数据
List<Goods> goodsList = goodsService.getAllGoodsInfo(name,page,pageSize);
// 创建商品展示对象列表
List<GoodsItemVo> goodsVoList = new ArrayList<>();
// 遍历商品列表,将每个商品转换为 goodsItemVO
for (Goods goods : goodsList) {
GoodsItemVo goodsItemVo = new GoodsItemVo();
Category category = categoryService.getById(goods.getCategoryId());
String categoryName;
if (category == null) {
continue;
} else {
categoryName = category.getName();
}
String[] images = goods.getGoodsImages().split("\\$");
goodsItemVo.setId(goods.getId())
.setCategoryName(categoryName)
.setGoodImage(images[0])
.setTitle(goods.getTitle())
.setPrice(goods.getPrice())
.setSales(goods.getSales());
goodsVoList.add(goodsItemVo);
}
//创建对象设置商品列表最终返回
GoodsVo goodsVo = new GoodsVo();
Utility utility = new Utility();
goodsVo.setList(goodsVoList);
// 判断是否是最后一页(分页结束),如果当前页获取到的商品数量小于每页数量说明分页结束
Boolean isEnd = goodsList.size() < pageSize;
goodsVo.setIsEnd(isEnd);
String nextWp = utility.encodeWp(page+1,pageSize,name); // 自动生成下一页
goodsVo.setWP(nextWp);
return goodsVo;
}
解码逻辑
解码步骤如下
Encoded wp string
|
Base64 解码
|
JSON 字符串
|
JSON 解码
|
wp 对象
|
访问属性:wp.getPage, wp.getName, wp.getXXX
判断前端是否有wp传入,如果wp不为null,后端进行解码操作。解码器使用URLDecoder
,JSON使用fastjson
。如果不存在,代码会进入 else
分支,使用默认分页参数。
if (wp != null) {
// Base64解码
String decodedWp = URLDecoder.decode(wp, StandardCharsets.UTF_8);
byte[] decodedBytes = Base64.getDecoder().decode(decodedWp);
String jsonString = new String(decodedBytes, StandardCharsets.UTF_8);
String decodeWp = URLDecoder.decode(jsonString, StandardCharsets.UTF_8);
// 解析 JSON 字符串为 Wp 对象
Wp wpJson = JSON.parseObject(decodeWp, Wp.class);
page = wpJson.getPage();
pageSize = wpJson.getPageSize();
name = wpJson.getName();
} else {
page = 1;
pageSize = 10;
name = keyword;
}
-
第1步:
URLDecoder.decode(wp, StandardCharsets.UTF_8)
- 对
wp
参数进行 URL 解码。因为wp
在传输过程中可能被 URL 编码,这一步确保参数中的特殊字符(如%20
表示空格)恢复为原始字符。
- 对
-
第2步:
Base64.getDecoder().decode(decodedWp)
- 对 URL 解码后的字符串进行 Base64 解码。
wp
参数经过 Base64 编码传输,这一步将其解码为原始的 JSON 字符串。
- 对 URL 解码后的字符串进行 Base64 解码。
-
第3步:
new String(decodedBytes, StandardCharsets.UTF_8)
- 将解码后的字节数组转换为字符串,得到 JSON 格式的字符串。
-
第4步:再次 URL 解码
- JSON 字符串可能再次包含 URL 编码的字符(例如嵌套转义),这一步将它解码为最终的可读 JSON 字符串。
-
第5步: 提取分页和筛选参数
getPage()
:从Wp
对象中获取当前页码。getPageSize()
:从Wp
对象中获取每页显示的记录数。getName()
:从Wp
对象中获取筛选条件(如商品名称或关键字)。
编码逻辑
编码步骤如下:
wp 对象
|
设置属性:wp.setPage, wp.setName, wp.setXXX
|
JSON 编码
|
JSON 字符串
|
Base64 编码
|
Encoded wp string
|
返回给前端
//创建对象设置商品列表最终返回
GoodsVo goodsVo = new GoodsVo();
goodsVo.setList(goodsVoList);
// 判断是否是最后一页(分页结束),如果当前页获取到的商品数量小于每页数量说明分页结束
Boolean isEnd = goodsList.size() < pageSize;
goodsVo.setIsEnd(isEnd);
Utility utility = new Utility(); // 创建工具类对象
String nextWp = utility.encodeWp(page+1,pageSize,name); // 自动生成下一页
goodsVo.setWP(nextWp);
encodeWp
方法:
-
encodeWp
是一个工具类封装的方法,用于将分页参数(page+1
、pageSize
和name
)编码为一个字符串。 -
编码过程通常包含以下步骤:
- 将分页参数组合成一个对象(如 JSON 格式的字符串):
- 参数:
page+1
:表示下一页的页码(当前页码page
加 1)。pageSize
:表示每页显示的记录数。name
:表示筛选条件(如搜索关键字)。
- 参数:
- 将对象编码为 JSON 字符串:
- 示例:
{"page":2,"pageSize":10,"name":"笔记本电脑"}
- 示例:
- 对 JSON 字符串进行 Base64 编码:
- Base64 编码使字符串可以在 URL 中安全传递,同时避免特殊字符的干扰。
- 返回编码后的字符串:
- 示例:
eyJwYWdlIjoyLCJwYWdlU2l6ZSI6MTAsIm5hbWUiOiJrZXl3b3JkIn0=
- 示例:
作用:
- 根据当前页码(
page
)、每页记录数(pageSize
)和筛选条件(name
),生成下一页的分页参数字符串(nextWp
)。 - 这一字符串可以安全传递给前端,用于后续的分页请求。
- 将分页参数组合成一个对象(如 JSON 格式的字符串):
前端请求逻辑
前端第一次请求的时候是没有wp的,举例前端请求过程:
前端第一次请求:GET /goods/list?keyword=手机
后端接收处理逻辑
判断 wp
是否存在:
- 后端检查
wp
参数,由于这是第一次请求,wp
为空。
进入else
分支:
page = 1;
pageSize = 10;
name = "keyword"
设置默认分页参数:
page = 1
:当前页为第一页。pageSize = 10
:默认每页显示10条数据。name =手机
:使用手机
作为商品筛选条件。
后端查询商品数据
List<Goods> goodsList = goodsService.getAllGoodsInfo(name, page, pageSize);
后端返回内容:
- 当前页的商品数据。
- 是否为最后一页的标识。
- 下一页的分页参数
wp
{
"isEnd": false,
"list": [
{
"id": 151,
"categoryName": "手机",
"title": "Huawei Mate 50 Pro",
"goodImage": "https://example.com/images/mate50pro.jpg",
"sales": 600,
"price": 7999
},
{
"id": 152,
"categoryName": "手机",
"title": "Huawei P60 Pro",
"goodImage": "https://example.com/images/p60pro.jpg",
"sales": 550,
"price": 7299
},
{
"id": 153,
"categoryName": "手机",
"title": "Xiaomi 13 Pro",
"goodImage": "https://example.com/images/xiaomi13pro.jpg",
"sales": 800,
"price": 6499
}
],
"wp": "eyJuYW1lIjoi5omL5py6IiwicGFnZSI6MiwicGFnZVNpemUiOjEwfQ%3D%3D"
下一次请求:
- 前端请求:
/goods/list?wp=eyJuYW1lIjoi5omL5py6IiwicGFnZSI6MiwicGFnZVNpemUiOjEwfQ%3D%3D
- 后端解析
wp
,查询对应页数据,并生成再下一页的wp
。
循环往复:
- 前端通过
wp
参数逐页请求数据,后端始终返回下一页的wp
参数,直到isEnd
为true
。