Browse Source

游标分页字段加密

glx_phase2
glx 3 days ago
parent
commit
c0132e5eef
  1. 24
      src/main/java/com/youlai/boot/common/util/CursorEncryptUtil.java
  2. 4
      src/main/java/com/youlai/boot/mini/model/query/AdoptionDiaryWaterfallQuery.java
  3. 4
      src/main/java/com/youlai/boot/mini/model/query/UserPostWaterfallQuery.java
  4. 4
      src/main/java/com/youlai/boot/mini/model/query/WaterfallQuery.java
  5. 6
      src/main/java/com/youlai/boot/mini/model/vo/WaterfallResult.java
  6. 11
      src/main/java/com/youlai/boot/mini/service/impl/AdoptionDiaryServiceImpl.java
  7. 11
      src/main/java/com/youlai/boot/mini/service/impl/StrayAnimalServiceImpl.java
  8. 16
      src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java

24
src/main/java/com/youlai/boot/common/util/CursorEncryptUtil.java

@ -0,0 +1,24 @@
package com.youlai.boot.common.util;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
/**
* 瀑布流游标加解密使用 AES 对称加密隐藏内部自增主键 ID
*/
public class CursorEncryptUtil {
private static final byte[] AES_KEY = "CrsrK3y!2026_StrayAnimals_Proj".getBytes(java.nio.charset.StandardCharsets.UTF_8);
private static final AES AES = SecureUtil.aes(AES_KEY);
public static String encode(Long id) {
if (id == null) return null;
return AES.encryptBase64(String.valueOf(id));
}
public static Long decode(String cursor) {
if (cursor == null || cursor.isBlank()) return null;
return Long.parseLong(AES.decryptStr(cursor));
}
}

4
src/main/java/com/youlai/boot/mini/model/query/AdoptionDiaryWaterfallQuery.java

@ -7,7 +7,7 @@ import lombok.Data;
@Data
public class AdoptionDiaryWaterfallQuery extends BaseQuery {
@Schema(description = "游标(上一页最后一条的ID,首次请求不传)", example = "100")
private Long cursor;
@Schema(description = "游标(上一页返回的nextCursor,首次请求不传)", example = "xY3kA9zW")
private String cursor;
}

4
src/main/java/com/youlai/boot/mini/model/query/UserPostWaterfallQuery.java

@ -7,7 +7,7 @@ import lombok.Data;
@Data
public class UserPostWaterfallQuery extends BaseQuery {
@Schema(description = "游标(上一页最后一条的ID,首次请求不传)", example = "100")
private Long cursor;
@Schema(description = "游标(上一页返回的nextCursor,首次请求不传)", example = "xY3kA9zW")
private String cursor;
}

4
src/main/java/com/youlai/boot/mini/model/query/WaterfallQuery.java

@ -10,8 +10,8 @@ import lombok.Data;
@Schema(description = "瀑布流查询参数")
public class WaterfallQuery extends BaseQuery {
@Schema(description = "游标(上一页最后一条的ID,首次请求不传)", example = "100")
private Long cursor;
@Schema(description = "游标(上一页返回的nextCursor,首次请求不传)", example = "xY3kA9zW")
private String cursor;
@Schema(description = "动物类型筛选(cat-猫 dog-狗 other-其他)", example = "cat")
private String animalType;

6
src/main/java/com/youlai/boot/mini/model/vo/WaterfallResult.java

@ -12,13 +12,13 @@ public class WaterfallResult<T> {
@Schema(description = "数据列表")
private List<T> list;
@Schema(description = "下一页游标(传null表示没有更多了)", example = "100")
private Long nextCursor;
@Schema(description = "下一页游标(传null表示没有更多了)", example = "xY3kA9zW")
private String nextCursor;
@Schema(description = "是否最后一页", example = "false")
private Boolean isLastPage;
public static <T> WaterfallResult<T> of(List<T> list, Long nextCursor, boolean isLastPage) {
public static <T> WaterfallResult<T> of(List<T> list, String nextCursor, boolean isLastPage) {
WaterfallResult<T> result = new WaterfallResult<>();
result.setList(list);
result.setNextCursor(nextCursor);

11
src/main/java/com/youlai/boot/mini/service/impl/AdoptionDiaryServiceImpl.java

@ -19,6 +19,7 @@ import com.youlai.boot.admin.service.ContentAuditConfigService;
import com.youlai.boot.admin.service.ContentAuditService;
import com.youlai.boot.admin.service.ContentAuditTaskService;
import com.youlai.boot.common.exception.MsgException;
import com.youlai.boot.common.util.CursorEncryptUtil;
import com.youlai.boot.common.util.FileUtils;
import com.youlai.boot.common.util.JavaVCUtils;
import com.youlai.boot.common.util.RandomNumberUtils;
@ -823,7 +824,7 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl<MiniAdoptionDiaryMappe
int fetchSize = pageSize * 3;
// 1. 从DB取原始数据(仅按id降序,不含过滤)
List<AdoptionDiaryVO> rawList = baseMapper.getWaterfall(query.getCursor(), fetchSize, userId);
List<AdoptionDiaryVO> rawList = baseMapper.getWaterfall(CursorEncryptUtil.decode(query.getCursor()), fetchSize, userId);
if (CollUtil.isEmpty(rawList)) {
return WaterfallResult.of(Collections.emptyList(), null, true);
@ -842,13 +843,13 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl<MiniAdoptionDiaryMappe
List<AdoptionDiaryVO> resultList = afterVisibility.stream().limit(pageSize).toList();
// 5. 游标逻辑
Long nextCursor = null;
Long nextCursorId = null;
boolean isLastPage = true;
if (resultList.size() >= pageSize) {
nextCursor = resultList.get(pageSize - 1).getId();
nextCursorId = resultList.get(pageSize - 1).getId();
isLastPage = false;
} else if (hasMore) {
nextCursor = CollUtil.isNotEmpty(resultList)
nextCursorId = CollUtil.isNotEmpty(resultList)
? resultList.get(resultList.size() - 1).getId()
: rawList.get(rawList.size() - 1).getId();
isLastPage = false;
@ -863,7 +864,7 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl<MiniAdoptionDiaryMappe
});
}
return WaterfallResult.of(resultList, nextCursor, isLastPage);
return WaterfallResult.of(resultList, CursorEncryptUtil.encode(nextCursorId), isLastPage);
}
private List<AdoptionDiaryVO> applyBloomDedup(List<AdoptionDiaryVO> list, Long userId) {

11
src/main/java/com/youlai/boot/mini/service/impl/StrayAnimalServiceImpl.java

@ -23,6 +23,7 @@ import com.youlai.boot.common.constant.CommonConstants;
import com.youlai.boot.common.exception.MsgException;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.common.util.CoordinateTransformUtils;
import com.youlai.boot.common.util.CursorEncryptUtil;
import com.youlai.boot.common.util.FileUtils;
import com.youlai.boot.common.util.JavaVCUtils;
import com.youlai.boot.common.util.RandomNumberUtils;
@ -1029,7 +1030,7 @@ public class StrayAnimalServiceImpl extends ServiceImpl<MiniStrayAnimalMapper, M
// 1. 从DB取原始数据(仅按id降序,不含过滤)
List<StrayAnimalShortVO> rawList = miniStrayAnimalMapper.getWaterfall(
query.getCursor(), fetchSize, query.getAnimalType(), userId);
CursorEncryptUtil.decode(query.getCursor()), fetchSize, query.getAnimalType(), userId);
if (CollUtil.isEmpty(rawList)) {
return WaterfallResult.of(Collections.emptyList(), null, true);
@ -1047,13 +1048,13 @@ public class StrayAnimalServiceImpl extends ServiceImpl<MiniStrayAnimalMapper, M
List<StrayAnimalShortVO> resultList = afterVisibility.stream().limit(pageSize).toList();
// 5. 游标逻辑
Long nextCursor = null;
Long nextCursorId = null;
boolean isLastPage = true;
if (resultList.size() >= pageSize) {
nextCursor = resultList.get(pageSize - 1).getId();
nextCursorId = resultList.get(pageSize - 1).getId();
isLastPage = false;
} else if (hasMore) {
nextCursor = CollUtil.isNotEmpty(resultList)
nextCursorId = CollUtil.isNotEmpty(resultList)
? resultList.get(resultList.size() - 1).getId()
: rawList.get(rawList.size() - 1).getId();
isLastPage = false;
@ -1078,7 +1079,7 @@ public class StrayAnimalServiceImpl extends ServiceImpl<MiniStrayAnimalMapper, M
});
}
return WaterfallResult.of(resultList, nextCursor, isLastPage);
return WaterfallResult.of(resultList, CursorEncryptUtil.encode(nextCursorId), isLastPage);
}
private List<StrayAnimalShortVO> applyBloomDedup(List<StrayAnimalShortVO> list, Long userId) {

16
src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java

@ -19,6 +19,7 @@ import com.youlai.boot.admin.service.ContentAuditConfigService;
import com.youlai.boot.admin.service.ContentAuditService;
import com.youlai.boot.admin.service.ContentAuditTaskService;
import com.youlai.boot.common.exception.MsgException;
import com.youlai.boot.common.util.CursorEncryptUtil;
import com.youlai.boot.common.util.FileUtils;
import com.youlai.boot.common.util.JavaVCUtils;
import com.youlai.boot.common.util.RandomNumberUtils;
@ -791,7 +792,7 @@ public class UserPostServiceImpl extends ServiceImpl<MiniUserPostMapper, MiniUse
int fetchSize = pageSize * 3;
// 1. 从DB取原始数据(仅按id降序,不含可见性过滤)
List<UserPostVO> rawList = baseMapper.getWaterfall(query.getCursor(), fetchSize, userId);
List<UserPostVO> rawList = baseMapper.getWaterfall(CursorEncryptUtil.decode(query.getCursor()), fetchSize, userId);
if (CollUtil.isEmpty(rawList)) {
return WaterfallResult.of(Collections.emptyList(), null, true);
@ -811,17 +812,14 @@ public class UserPostServiceImpl extends ServiceImpl<MiniUserPostMapper, MiniUse
// 4. 截取 pageSize 条作为本页结果
List<UserPostVO> resultList = afterVisibility.stream().limit(pageSize).toList();
// 5. 游标逻辑:
// - 过滤后还有 ≥ pageSize 条 → 用最后一条的ID作为下页游标
// - 过滤后不够,但DB原始数据是满的 → 用resultList最后一条ID(给被过滤帖子二次曝光机会)
// - 否则最后一页
Long nextCursor = null;
// 5. 游标逻辑
Long nextCursorId = null;
boolean isLastPage = true;
if (resultList.size() >= pageSize) {
nextCursor = resultList.get(pageSize - 1).getId();
nextCursorId = resultList.get(pageSize - 1).getId();
isLastPage = false;
} else if (hasMore) {
nextCursor = CollUtil.isNotEmpty(resultList)
nextCursorId = CollUtil.isNotEmpty(resultList)
? resultList.get(resultList.size() - 1).getId()
: rawList.get(rawList.size() - 1).getId();
isLastPage = false;
@ -836,7 +834,7 @@ public class UserPostServiceImpl extends ServiceImpl<MiniUserPostMapper, MiniUse
});
}
return WaterfallResult.of(resultList, nextCursor, isLastPage);
return WaterfallResult.of(resultList, CursorEncryptUtil.encode(nextCursorId), isLastPage);
}
private String getDefaultCoverHost() {

Loading…
Cancel
Save