diff --git a/client-api/src/main/java/org/jiayunet/controller/JobController.java b/client-api/src/main/java/org/jiayunet/controller/JobController.java index 9f1d08b..fb7b3fc 100644 --- a/client-api/src/main/java/org/jiayunet/controller/JobController.java +++ b/client-api/src/main/java/org/jiayunet/controller/JobController.java @@ -16,9 +16,13 @@ import java.util.List; import org.jiayunet.pojo.vo.JobFavoriteCountVo; import org.jiayunet.service.JobService; import org.jiayunet.tool.UserSecurityTool; +import org.springframework.beans.BeanUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; +import java.util.Collections; + /** * 岗位接口 * @@ -50,6 +54,51 @@ public class JobController { return jobService.listJobs(param, userId); } + /** + * 岗位列表查询(按匹配度排序) + *
入参与 /job/list 一致。匹配度是内存计算的,无法在 SQL 层排序,因此先取候选池全量算分排序 + * 并整体缓存,再在本方法内对缓存结果切片分页
+ *排序仅在候选池范围内有效,候选池之外的岗位不参与排序
+ *未登录用户没有简历和求职意向,匹配度几乎全部相同,排序无意义,直接退回 /job/list 的最新排序
+ */ + @PostMapping("/list/match") + public PageResult筛选条件与 /job/list 一致,仅返回符合条件的岗位总数,接口允许不登录访问
diff --git a/client-api/src/main/java/org/jiayunet/service/JobService.java b/client-api/src/main/java/org/jiayunet/service/JobService.java index 955d7b1..015f280 100644 --- a/client-api/src/main/java/org/jiayunet/service/JobService.java +++ b/client-api/src/main/java/org/jiayunet/service/JobService.java @@ -87,6 +87,9 @@ public class JobService { @Autowired private AiChatAbility aiChatAbility; + /** 匹配度排序的候选池大小,匹配度无法在 SQL 层排序,只能在此范围内做全量算分后排序 */ + public static final int CANDIDATE_POOL_SIZE = 5000; + /** * 岗位列表查询 *方法逻辑流程:
@@ -166,6 +169,37 @@ public class JobService { return new PageResult<>(page.getCurrent(), page.getSize(), page.getTotal(), dtoList); } + /** + * 岗位匹配度排序查询(候选池全量,不分页) + *匹配度是内存计算出来的,无法在 SQL 层 ORDER BY,因此一次性取候选池全量算分后排序
+ *结果整体缓存,翻页由调用方对返回列表切片,避免每页重复计算
+ * + *方法逻辑流程:
+ *1. 复用 listJobs 查询候选池(含匹配度计算)
+ *2. 按匹配度倒序排序,同分按岗位ID倒序保证顺序稳定
+ * + *调用约定:缓存 key 由 param 与 userId 共同决定,且在方法执行前就已生成, + * 因此调用方必须先把 pageNum 置为 1、pageSize 置为 CANDIDATE_POOL_SIZE 再传入。 + * 否则每一页都会生成独立缓存条目,缓存命中率恒为 0(方法内部无法补救)
+ * + * @param param 查询条件,pageNum 须为 1、pageSize 须为 CANDIDATE_POOL_SIZE + * @param userId 用户ID,匹配度因人而异,参与缓存 key + * @return 按匹配度倒序排列的候选池全量列表 + */ + @Cacheable(cacheNames = CacheConfig.JOB_MATCH_SORT) + public List过滤条件与 listJobs 完全一致(SQL 层共用 jobPageWhere 片段),不分页、不查收藏/投递状态、不算匹配度
diff --git a/common/src/main/java/org/jiayunet/config/CacheConfig.java b/common/src/main/java/org/jiayunet/config/CacheConfig.java index bf2e847..0dadf1e 100644 --- a/common/src/main/java/org/jiayunet/config/CacheConfig.java +++ b/common/src/main/java/org/jiayunet/config/CacheConfig.java @@ -30,9 +30,15 @@ public class CacheConfig { /** 缓存名:热门城市 */ public static final String HOT_CITY = "hotCity"; + /** 缓存名:岗位匹配度排序结果(单条目为5000个JobDto,占用大,容量单独控制) */ + public static final String JOB_MATCH_SORT = "jobMatchSort"; + /** 单个缓存最大条目数 */ private static final long MAX_SIZE = 100; + /** 岗位匹配度排序缓存最大条目数(单条目约5~10MB,不能按 MAX_SIZE 放开) */ + private static final long JOB_MATCH_SORT_MAX_SIZE = 20; + /** * 缓存管理器 *三个热门榜单各注册一份独立的 Caffeine 实例,过期时间互相独立(单位:分钟)
@@ -48,22 +54,26 @@ public class CacheConfig { .maximumSize(MAX_SIZE)); // 各缓存独立注册,独立过期时间 - cacheManager.registerCustomCache(HOT_INDUSTRY, buildCache(60)); - cacheManager.registerCustomCache(HOT_JOB_CATEGORY, buildCache(60)); - cacheManager.registerCustomCache(HOT_CITY, buildCache(60)); + cacheManager.registerCustomCache(HOT_INDUSTRY, buildCache(60, MAX_SIZE)); + cacheManager.registerCustomCache(HOT_JOB_CATEGORY, buildCache(60, MAX_SIZE)); + cacheManager.registerCustomCache(HOT_CITY, buildCache(60, MAX_SIZE)); + + // 匹配度排序结果:单条目占用大,容量收紧;内含收藏/投递状态快照,时长不宜过长 + cacheManager.registerCustomCache(JOB_MATCH_SORT, buildCache(5, JOB_MATCH_SORT_MAX_SIZE)); return cacheManager; } /** - * 构建一个指定过期时长的 Caffeine 缓存实例 + * 构建一个 Caffeine 缓存实例 * * @param expireMinutes 写入后过期时长(分钟) + * @param maximumSize 最大条目数 */ - private Cache