You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
93 lines
3.7 KiB
93 lines
3.7 KiB
package com.mydreamjob.job;
|
|
|
|
import jakarta.persistence.criteria.Predicate;
|
|
import org.springframework.data.jpa.domain.Specification;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
|
|
/**
|
|
* 岗位搜索动态查询 Specification。
|
|
* 全部使用 CriteriaBuilder 参数化查询,防止 SQL 注入。
|
|
*/
|
|
public class JobSearchSpecification {
|
|
|
|
private static final String STATUS = "status";
|
|
private static final String TITLE = "title";
|
|
private static final String DESCRIPTION = "description";
|
|
private static final String REQUIREMENTS = "requirements";
|
|
private static final String CITY = "city";
|
|
private static final String EMPLOYMENT_TYPE = "employmentType";
|
|
private static final String SALARY_MIN = "salaryMin";
|
|
private static final String SALARY_MAX = "salaryMax";
|
|
private static final String COMPANY_ID = "companyId";
|
|
|
|
private JobSearchSpecification() {
|
|
// utility class
|
|
}
|
|
|
|
/**
|
|
* 构建动态查询条件。
|
|
*
|
|
* @param keyword 关键词(搜索标题/描述/要求)
|
|
* @param city 城市精确匹配
|
|
* @param employmentType 用工类型精确匹配
|
|
* @param salaryMin 最低薪资筛选
|
|
* @param salaryMax 最高薪资筛选
|
|
* @param companyId 公司ID筛选
|
|
* @return Specification
|
|
*/
|
|
public static Specification<Job> searchJobs(
|
|
String keyword, String city, String employmentType,
|
|
Integer salaryMin, Integer salaryMax, Long companyId) {
|
|
|
|
return (root, query, cb) -> {
|
|
List<Predicate> predicates = new ArrayList<>();
|
|
|
|
// 固定:仅 PUBLISHED
|
|
predicates.add(cb.equal(root.get(STATUS), "PUBLISHED"));
|
|
|
|
// 关键词:搜索 title / description / requirements
|
|
if (keyword != null && !keyword.isBlank()) {
|
|
String pattern = "%" + keyword.trim() + "%";
|
|
Predicate keywordPred = cb.or(
|
|
cb.like(cb.lower(root.get(TITLE)), pattern.toLowerCase(Locale.ROOT)),
|
|
cb.like(cb.lower(root.get(DESCRIPTION)), pattern.toLowerCase(Locale.ROOT)),
|
|
cb.like(cb.lower(root.get(REQUIREMENTS)), pattern.toLowerCase(Locale.ROOT))
|
|
);
|
|
predicates.add(keywordPred);
|
|
}
|
|
|
|
// 城市:trim 后忽略大小写精确匹配。
|
|
// 显式 lower 可避免 MySQL 的大小写不敏感排序规则与 H2 默认比较规则产生差异。
|
|
if (city != null && !city.isBlank()) {
|
|
predicates.add(cb.equal(cb.lower(root.get(CITY)), city.trim().toLowerCase(Locale.ROOT)));
|
|
}
|
|
|
|
// 用工类型:trim 后忽略大小写精确匹配,与线上 MySQL 既有比较结果保持一致。
|
|
if (employmentType != null && !employmentType.isBlank()) {
|
|
predicates.add(cb.equal(
|
|
cb.lower(root.get(EMPLOYMENT_TYPE)),
|
|
employmentType.trim().toLowerCase(Locale.ROOT)));
|
|
}
|
|
|
|
// 薪资范围:岗位 salaryMax >= 用户 salaryMin
|
|
if (salaryMin != null) {
|
|
predicates.add(cb.greaterThanOrEqualTo(root.get(SALARY_MAX), salaryMin));
|
|
}
|
|
|
|
// 薪资范围:岗位 salaryMin <= 用户 salaryMax
|
|
if (salaryMax != null) {
|
|
predicates.add(cb.lessThanOrEqualTo(root.get(SALARY_MIN), salaryMax));
|
|
}
|
|
|
|
// 公司筛选
|
|
if (companyId != null) {
|
|
predicates.add(cb.equal(root.get(COMPANY_ID), companyId));
|
|
}
|
|
|
|
return cb.and(predicates.toArray(new Predicate[0]));
|
|
};
|
|
}
|
|
}
|
|
|