需求
管理后台创建多条招聘需求,分别配置招聘条件,对简历进行筛选
分析
一条招聘需求就是由各种招聘条件组成的,我们把每个招聘条件抽象成一个实体字段,再封装一个对应的校验逻
辑,应用于简历筛选。本需求同样适用于各种banner、广告等的选择性展示。
先在脑海了想象这个场景:你发布了一条招聘需求,然后你要对别人投的简历进行筛选。enough,Talk is cheap,show me the code。
1. 简化了的招聘条件实体类,省略getter/setter。
public class RecruitmentCondition {
/**
* 年龄要求,包括边界
*/
@Validate(ResumeValidator.AGE)
private Range age;
/**
* 性别要求
*/
@Validate(ResumeValidator.SEX)
private Integer sex;
/**
* 技能要求
*/
@Validate(ResumeValidator.SKILL)
private Set skills;
/**
* 婚姻要求
*/
@Validate(ResumeValidator.MARITAL_STATUS)
private Boolean maritalStatus;
}
2. 简化了的简历实体类,省略getter/setter
public class Resume {
/**
* 年龄
*/
private Integer age;
/**
* 性别
*/
private Integer sex;
/**
* 技能
*/
private String skill;
/**
* 婚姻
*/
private Boolean maritalStatus;
}
3. 有了上面两个实体后,考虑校验逻辑。实现方式有很多,简单一点直接写个校验方法,把校验逻辑全部扔进去,但是这样不利于扩(zhuang)展(bi),我们把每个字段的校验逻辑独立开来,通过注解申明各个字段应该应用哪个封装好的校验逻辑。先声明一个校验器接口:
public interface Validator {
boolean valid(RecruitmentCondition condition, Resume resume);
}
继承接口实现校验逻辑:
public enum ResumeValidator implements Validator {
/**
* 年龄校验逻辑
*/
AGE {
@Override
public boolean valid(RecruitmentCondition condition, Resume resume) {
Range ageCondition = condition.getAge();
if (ageCondition != null) {
Integer userAge = resume.getAge();
return ageCondition.contains(userAge);
}
return true;
}
},
/**
* 性别校验逻辑
*/
SEX{
@Override
public boolean valid(RecruitmentCondition condition, Resume resume) {
Integer sexCondition = condition.getSex();
if (sexCondition != null) {
return Objects.equals(sexCondition, resume.getSex());
}
return true;
}
},
/**
* 技能校验逻辑
*/
SKILL {
@Override
public boolean valid(RecruitmentCondition condition, Resume resume) {
Set skillCondition = condition.getSkills();
if (CollectionUtils.isNotEmpty(skillCondition)) {
String userSkill = resume.getSkill();
return userSkill != null && skillCondition.contains(userSkill);
}
return true;
}
},
/**
* 婚姻状态校验逻辑
*/
MARITAL_STATUS {
@Override
public boolean valid(RecruitmentCondition condition, Resume resume) {
Boolean marriageCondition = condition.getMaritalStatus();
if (marriageCondition != null) {
return Objects.equals(marriageCondition, resume.getMaritalStatus());
}
return true;
}
},
}
我们把每个字段的校验逻辑分开,以枚举的方式实现,如果需要增加新的条件,只要再加个枚举就是了,现有的代码无需变动,嗯,蛮灵活的。
现在考虑如何将条件字段与校验逻辑串联起来,没错,注解,相信你已经在RecruitmentCondition实体类中看到我标注的注解了,注解的value属性指定了该字段应用的校验逻辑。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Validate {
ResumeValidator value();
}
4. 将字段和校验逻辑关联起来后,我们只需要把对应的逻辑跑起来就行了,封装一个Predicate:
public class ResumeFilter implements Predicate {
private static final Set validators = Arrays.stream(RecruitmentCondition.class.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(Validate.class))
.map(field -> field.getAnnotation(Validate.class).value()).collect(Collectors.toSet());
private RecruitmentCondition recruitmentCondition;
public ResumeFilter(RecruitmentCondition recruitmentCondition) {
this.recruitmentCondition = recruitmentCondition;
}
@Override
public boolean test(Resume resume) {
return validators.stream().allMatch(validator -> validator.valid(recruitmentCondition, resume));
}
}
5. 最后,demo搞起来
public static void main(String[] args) {
// 后台组招聘20-35岁的java或python工程师
RecruitmentCondition backendCondition = new RecruitmentCondition();
backendCondition.setAge(Range.between(20, 35));
backendCondition.setSkills(Sets.newHashSet("java", "python"));
// 测试组招聘20-28岁未婚的妹子测试工程师
RecruitmentCondition testerCondition = new RecruitmentCondition();
testerCondition.setAge(Range.between(20, 28));
testerCondition.setSex(1);
testerCondition.setSkills(Sets.newHashSet("测试"));
testerCondition.setMaritalStatus(false);
// 收集100份简历
HashSet resumes = Sets.newHashSetWithExpectedSize(100);
Resume resume;
Set candidateSkills = Sets.newHashSet("java", "C", "C++", "python", "go", "产品经理", "测试", "UI");
for (int i = 0; i < 100; i++) {
resume = new Resume();
resume.setAge(RandomUtils.nextInt(18, 50));
resume.setSex(RandomUtils.nextInt(0, 2));
resume.setSkill(RandomDataUtils.randomOne(candidateSkills).orElse("java"));
resume.setMaritalStatus(RandomUtils.nextBoolean());
resumes.add(resume);
}
// 筛选出符合要求的简历
Set backendResumes = resumes.stream().filter(new ResumeFilter(backendCondition)).collect(Collectors.toSet());
Set testerResumes = resumes.stream().filter(new ResumeFilter(testerCondition)).collect(Collectors.toSet());
}
搞定,还有一些我们线上的各种banner啊,广告啊,如果需要对不同的用户做展示控制,都可以用这套,你学废了没有?!