Browse Source

feat: add personal skill match service

backup/master-local-20261012
pwklppg 1 month ago
parent
commit
6eb30e7fd0
  1. 152
      mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillMatchService.java
  2. 28
      mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillNameNormalizer.java
  3. 16
      mydreamjob-server/src/main/java/com/mydreamjob/skill/dto/SkillMatchResponse.java
  4. 482
      mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillMatchServiceTest.java

152
mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillMatchService.java

@ -0,0 +1,152 @@
package com.mydreamjob.skill;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mydreamjob.profile.StudentProfile;
import com.mydreamjob.profile.StudentProfileRepository;
import com.mydreamjob.skill.domain.SkillAlias;
import com.mydreamjob.skill.dto.SkillCloudItem;
import com.mydreamjob.skill.dto.SkillCloudResponse;
import com.mydreamjob.skill.dto.SkillMatchResponse;
import com.mydreamjob.skill.repository.SkillAliasRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Slf4j
@Service
@Transactional(readOnly = true)
public class SkillMatchService {
private static final int TOP_LIMIT = 10;
private final SkillCloudQueryService skillCloudQueryService;
private final StudentProfileRepository studentProfileRepository;
private final SkillAliasRepository skillAliasRepository;
private final SkillNameNormalizer skillNameNormalizer;
private final ObjectMapper objectMapper;
public SkillMatchService(SkillCloudQueryService skillCloudQueryService,
StudentProfileRepository studentProfileRepository,
SkillAliasRepository skillAliasRepository,
SkillNameNormalizer skillNameNormalizer,
ObjectMapper objectMapper) {
this.skillCloudQueryService = skillCloudQueryService;
this.studentProfileRepository = studentProfileRepository;
this.skillAliasRepository = skillAliasRepository;
this.skillNameNormalizer = skillNameNormalizer;
this.objectMapper = objectMapper;
}
public SkillMatchResponse match(Long studentUserId,
Long industryId,
Long occupationId,
Long categoryId) {
SkillCloudResponse cloud = skillCloudQueryService.query(industryId, occupationId, categoryId);
int actualTopSize = Math.min(TOP_LIMIT, cloud.items().size());
List<SkillCloudItem> topSkills = cloud.items().subList(0, actualTopSize);
Set<String> studentSkills = loadStudentSkills(studentUserId);
Map<Long, List<SkillAlias>> aliasesBySkillId = loadAliases(topSkills);
List<SkillCloudItem> matchedSkills = new ArrayList<>();
List<SkillCloudItem> missingSkills = new ArrayList<>();
for (SkillCloudItem skill : topSkills) {
if (matches(skill, studentSkills, aliasesBySkillId.getOrDefault(skill.skillId(), List.of()))) {
matchedSkills.add(skill);
} else {
missingSkills.add(skill);
}
}
int matchScore = actualTopSize == 0
? 0
: (int) Math.round(matchedSkills.size() * 100.0 / actualTopSize);
return new SkillMatchResponse(
matchScore,
matchedSkills.size(),
actualTopSize,
matchedSkills,
missingSkills
);
}
private Set<String> loadStudentSkills(Long studentUserId) {
if (studentUserId == null) {
return Set.of();
}
StudentProfile profile = studentProfileRepository.findByUserId(studentUserId).orElse(null);
if (profile == null || profile.getSkillsJson() == null || profile.getSkillsJson().isBlank()) {
return Set.of();
}
try {
List<String> parsed = objectMapper.readValue(
profile.getSkillsJson(),
new TypeReference<List<String>>() {}
);
if (parsed == null || parsed.isEmpty()) {
return Set.of();
}
Set<String> normalizedSkills = new HashSet<>();
for (String skill : parsed) {
if (skill == null) {
continue;
}
String normalized = skillNameNormalizer.normalize(skill);
if (!normalized.isBlank()) {
normalizedSkills.add(normalized);
}
}
return Set.copyOf(normalizedSkills);
} catch (Exception exception) {
log.warn("Failed to parse student skills JSON: profileId={}, errorType={}",
profile.getId(), exception.getClass().getSimpleName());
return Set.of();
}
}
private Map<Long, List<SkillAlias>> loadAliases(List<SkillCloudItem> topSkills) {
Collection<Long> skillIds = topSkills.stream()
.map(SkillCloudItem::skillId)
.filter(java.util.Objects::nonNull)
.toList();
if (skillIds.isEmpty()) {
return Map.of();
}
Map<Long, List<SkillAlias>> aliasesBySkillId = new HashMap<>();
for (SkillAlias alias : skillAliasRepository.findAllBySkillIdInOrderByIdAsc(skillIds)) {
aliasesBySkillId.computeIfAbsent(alias.getSkillId(), ignored -> new ArrayList<>()).add(alias);
}
return aliasesBySkillId;
}
private boolean matches(SkillCloudItem skill,
Set<String> studentSkills,
List<SkillAlias> aliases) {
if (containsNormalized(studentSkills, skill.nameZh())
|| containsNormalized(studentSkills, skill.nameEn())
|| containsNormalized(studentSkills, skill.skillCode())) {
return true;
}
return aliases.stream().anyMatch(alias -> containsNormalized(studentSkills, alias.getAlias()));
}
private boolean containsNormalized(Set<String> studentSkills, String candidate) {
if (candidate == null) {
return false;
}
String normalized = skillNameNormalizer.normalize(candidate);
return !normalized.isBlank() && studentSkills.contains(normalized);
}
}

28
mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillNameNormalizer.java

@ -0,0 +1,28 @@
package com.mydreamjob.skill;
import org.springframework.stereotype.Component;
import java.text.Normalizer;
import java.util.Locale;
import java.util.regex.Pattern;
@Component
public class SkillNameNormalizer {
private static final Pattern UNICODE_HYPHENS = Pattern.compile("\\p{Pd}");
private static final Pattern WHITESPACE = Pattern.compile("\\s+", Pattern.UNICODE_CHARACTER_CLASS);
public String normalize(String value) {
if (value == null) {
return "";
}
String normalized = Normalizer.normalize(value, Normalizer.Form.NFKC)
.trim()
.toLowerCase(Locale.ROOT);
normalized = UNICODE_HYPHENS.matcher(normalized).replaceAll(" ");
normalized = normalized.replace(".", "");
normalized = WHITESPACE.matcher(normalized).replaceAll(" ");
return normalized.trim();
}
}

16
mydreamjob-server/src/main/java/com/mydreamjob/skill/dto/SkillMatchResponse.java

@ -0,0 +1,16 @@
package com.mydreamjob.skill.dto;
import java.util.List;
public record SkillMatchResponse(
int matchScore,
int matchedCount,
int totalCount,
List<SkillCloudItem> matchedSkills,
List<SkillCloudItem> missingSkills
) {
public SkillMatchResponse {
matchedSkills = List.copyOf(matchedSkills);
missingSkills = List.copyOf(missingSkills);
}
}

482
mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillMatchServiceTest.java

@ -0,0 +1,482 @@
package com.mydreamjob.skill;
import com.mydreamjob.profile.StudentProfile;
import com.mydreamjob.profile.StudentProfileRepository;
import com.mydreamjob.skill.domain.SkillAlias;
import com.mydreamjob.skill.dto.SkillCloudItem;
import com.mydreamjob.skill.dto.SkillCloudNode;
import com.mydreamjob.skill.dto.SkillCloudResponse;
import com.mydreamjob.skill.dto.SkillMatchResponse;
import com.mydreamjob.skill.repository.SkillAliasRepository;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.boot.model.relational.Sequence;
import org.hibernate.mapping.Table;
import org.hibernate.tool.schema.spi.SchemaFilter;
import org.hibernate.tool.schema.spi.SchemaFilterProvider;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.Mockito.doReturn;
@SpringBootTest
@ExtendWith(OutputCaptureExtension.class)
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:integrationdb_skill_match;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE",
"spring.jpa.hibernate.ddl-auto=validate",
"spring.jpa.properties.hibernate.hbm2ddl.schema_filter_provider=com.mydreamjob.skill.SkillMatchServiceTest$SkillMatchSchemaFilterProvider"
})
@ActiveProfiles("integration-test")
@Transactional
class SkillMatchServiceTest {
private static final Long USER_ID = 7001L;
@Autowired
private SkillMatchService service;
@SpyBean
private SkillCloudQueryService queryService;
@SpyBean
private StudentProfileRepository profileRepository;
@SpyBean
private SkillAliasRepository aliasRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
@MockBean
private RedisTemplate<String, Object> redisTemplate;
@Test
void normalizerAppliesUnicodeRulesAndPreservesProgrammingSymbols() {
SkillNameNormalizer normalizer = new SkillNameNormalizer();
assertThat(normalizer.normalize(" Spring‐Boot. ")).isEqualTo("spring boot");
assertThat(normalizer.normalize(" JAVA\n\tSCRIPT ")).isEqualTo("java script");
assertThat(normalizer.normalize("Node.js")).isEqualTo("nodejs");
assertThat(normalizer.normalize("C++")).isEqualTo("c++");
assertThat(normalizer.normalize("C#")).isEqualTo("c#");
}
@Test
void usesOnlyTopTenWhenMoreThanTenSkillsExist() {
List<SkillCloudItem> items = IntStream.rangeClosed(1, 11)
.mapToObj(index -> item((long) index, "Skill " + index, "Skill " + index, "SKILL_" + index))
.toList();
stubCloud(cloud(items));
saveProfile(USER_ID, json(items.stream().map(SkillCloudItem::nameZh).toList()));
SkillMatchResponse result = service.match(USER_ID, 1L, 1L, null);
assertThat(result.totalCount()).isEqualTo(10);
assertThat(result.matchedCount()).isEqualTo(10);
assertThat(result.matchScore()).isEqualTo(100);
assertThat(result.matchedSkills())
.extracting(SkillCloudItem::skillId)
.containsExactlyElementsOf(IntStream.rangeClosed(1, 10).mapToLong(Long::valueOf).boxed().toList());
assertThat(result.missingSkills()).isEmpty();
}
@Test
void usesTenAsDenominatorWhenExactlyTenSkillsExist() {
List<SkillCloudItem> items = IntStream.rangeClosed(1, 10)
.mapToObj(index -> item((long) index, "Skill " + index, "Skill " + index, "SKILL_" + index))
.toList();
stubCloud(cloud(items));
saveProfile(USER_ID, json(items.stream().limit(5).map(SkillCloudItem::nameZh).toList()));
SkillMatchResponse result = service.match(USER_ID, 1L, 1L, null);
assertThat(result.totalCount()).isEqualTo(10);
assertThat(result.matchedCount()).isEqualTo(5);
assertThat(result.matchScore()).isEqualTo(50);
assertThat(result.missingSkills()).hasSize(5);
}
@Test
void usesActualSizeAsDenominatorAndRoundsOneThirdToThirtyThree() {
List<SkillCloudItem> items = List.of(
item(1L, "Java", "Java", "JAVA"),
item(2L, "Python", "Python", "PYTHON"),
item(3L, "SQL", "SQL", "SQL")
);
stubCloud(cloud(items));
saveProfile(USER_ID, json(List.of("Java")));
SkillMatchResponse result = service.match(USER_ID, 1L, 1L, null);
assertThat(result.totalCount()).isEqualTo(3);
assertThat(result.matchedCount()).isEqualTo(1);
assertThat(result.matchScore()).isEqualTo(33);
assertThat(result.matchedSkills()).extracting(SkillCloudItem::skillId).containsExactly(1L);
assertThat(result.missingSkills()).extracting(SkillCloudItem::skillId).containsExactly(2L, 3L);
}
@Test
void returnsZeroAndEmptyListsWhenCloudIsEmpty() {
stubCloud(cloud(List.of()));
saveProfile(USER_ID, json(List.of("Java")));
SkillMatchResponse result = service.match(USER_ID, 1L, 1L, null);
assertThat(result.matchScore()).isZero();
assertThat(result.matchedCount()).isZero();
assertThat(result.totalCount()).isZero();
assertThat(result.matchedSkills()).isEmpty();
assertThat(result.missingSkills()).isEmpty();
}
@Test
void treatsMissingProfileAsNoSkills() {
stubCloud(cloud(List.of(item(1L, "Java", "Java", "JAVA"))));
SkillMatchResponse result = service.match(9999L, 1L, 1L, null);
assertThat(result.matchScore()).isZero();
assertThat(result.matchedSkills()).isEmpty();
assertThat(result.missingSkills()).extracting(SkillCloudItem::skillId).containsExactly(1L);
}
@Test
void treatsBlankAndEmptyArrayAsNoSkills() {
stubCloud(cloud(List.of(item(1L, "Java", "Java", "JAVA"), item(2L, "SQL", "SQL", "SQL"))));
doReturn(Optional.of(StudentProfile.builder()
.id(100L)
.userId(USER_ID)
.skillsJson(" ")
.build()))
.when(profileRepository).findByUserId(USER_ID);
SkillMatchResponse blank = service.match(USER_ID, 1L, 1L, null);
assertThat(blank.matchScore()).isZero();
assertThat(blank.missingSkills()).extracting(SkillCloudItem::skillId).containsExactly(1L, 2L);
doReturn(Optional.of(StudentProfile.builder()
.id(101L)
.userId(USER_ID)
.skillsJson("[]")
.build()))
.when(profileRepository).findByUserId(USER_ID);
SkillMatchResponse empty = service.match(USER_ID, 1L, 1L, null);
assertThat(empty.matchScore()).isZero();
assertThat(empty.matchedSkills()).isEmpty();
}
@Test
void treatsMalformedJsonAsNoSkillsWithoutLeakingRawJson(CapturedOutput output) {
String malformedJson = "{\"skills\":[\"Java\"]";
stubCloud(cloud(List.of(item(1L, "Java", "Java", "JAVA"))));
doReturn(Optional.of(StudentProfile.builder()
.id(102L)
.userId(USER_ID)
.skillsJson(malformedJson)
.build()))
.when(profileRepository).findByUserId(USER_ID);
SkillMatchResponse result = service.match(USER_ID, 1L, 1L, null);
assertThat(result.matchScore()).isZero();
assertThat(result.matchedSkills()).isEmpty();
assertThat(output.getAll()).contains("Failed to parse student skills JSON");
assertThat(output.getAll()).doesNotContain(malformedJson);
}
@Test
void matchesNameZhNameEnSkillCodeAndAliasAndDeduplicatesStudentSkills() {
List<SkillCloudItem> items = List.of(
item(1L, "数据库", "Database Systems", "DATA_STORE_CLASS"),
item(2L, "中文技能", "database technology", "UNRELATED_CODE"),
item(3L, "代码技能", "Code English", "DATABASE_ENGINE")
);
stubCloud(cloud(items));
doReturn(List.of(alias(1L, "JVM Language", "jvm language")))
.when(aliasRepository).findAllBySkillIdInOrderByIdAsc(anyCollection());
saveProfile(USER_ID, json(List.of(
"jvm-language",
"JVM LANGUAGE",
"数据库",
"DATABASE TECHNOLOGY",
"database_engine"
)));
SkillMatchResponse result = service.match(USER_ID, 1L, 1L, null);
assertThat(result.matchScore()).isEqualTo(100);
assertThat(result.matchedCount()).isEqualTo(3);
assertThat(result.matchedSkills()).extracting(SkillCloudItem::skillId).containsExactly(1L, 2L, 3L);
assertThat(result.missingSkills()).isEmpty();
}
@Test
void matchesOnlyNameZhForV36JavaWhenOtherIdentityFieldsDoNotMatch() {
jdbcTemplate.update("UPDATE skills SET code = ? WHERE id = ?", "JAVA_NAME_ZH_ISOLATED", 1L);
saveProfile(USER_ID, json(List.of("Java")));
SkillMatchResponse result = service.match(USER_ID, 1L, 2L, 201L);
assertThat(result.totalCount()).isEqualTo(2);
assertThat(result.matchScore()).isEqualTo(50);
assertThat(result.matchedSkills())
.extracting(SkillCloudItem::skillId)
.containsExactly(1L);
assertThat(result.missingSkills())
.extracting(SkillCloudItem::skillId)
.containsExactly(2L);
}
@Test
void matchesOnlyInsertedAliasForV36JavaWhenAliasIsTheOnlyIdentityMatch() {
jdbcTemplate.update("""
INSERT INTO skill_aliases (id, skill_id, alias, normalized_alias)
VALUES (?, ?, ?, ?)
""", 10001L, 1L, "ONLY.ALIAS-JVM-XYZ", "only alias jvm xyz");
saveProfile(USER_ID, json(List.of("ONLY.ALIAS-JVM-XYZ")));
SkillMatchResponse result = service.match(USER_ID, 1L, 2L, 201L);
assertThat(result.totalCount()).isEqualTo(2);
assertThat(result.matchScore()).isEqualTo(50);
assertThat(result.matchedSkills())
.extracting(SkillCloudItem::skillId)
.containsExactly(1L);
assertThat(result.missingSkills())
.extracting(SkillCloudItem::skillId)
.containsExactly(2L);
}
@Test
void doesNotApplyAliasOwnedByAnotherTopSkillToV36Java() {
jdbcTemplate.update("""
INSERT INTO skill_aliases (id, skill_id, alias, normalized_alias)
VALUES (?, ?, ?, ?)
""", 10002L, 2L, "ONLY.ALIAS-OWNER-SKILL-2", "only alias owner skill 2");
saveProfile(USER_ID, json(List.of("ONLY.ALIAS-OWNER-SKILL-2")));
SkillMatchResponse result = service.match(USER_ID, 1L, 2L, 201L);
assertThat(result.totalCount()).isEqualTo(2);
assertThat(result.matchScore()).isEqualTo(50);
assertThat(result.matchedSkills())
.extracting(SkillCloudItem::skillId)
.containsExactly(2L);
assertThat(result.missingSkills())
.extracting(SkillCloudItem::skillId)
.containsExactly(1L);
}
@Test
void keepsMatchedAndMissingListsOrderedAndTheirUnionEqualToTopTen() {
List<SkillCloudItem> items = List.of(
item(1L, "Java", "Java", "JAVA"),
item(2L, "Python", "Python", "PYTHON"),
item(3L, "SQL", "SQL", "SQL")
);
stubCloud(cloud(items));
saveProfile(USER_ID, json(List.of("Python")));
SkillMatchResponse result = service.match(USER_ID, 1L, 1L, null);
List<Long> union = Stream.concat(result.matchedSkills().stream(), result.missingSkills().stream())
.map(SkillCloudItem::skillId)
.toList();
assertThat(result.matchScore()).isEqualTo(33);
assertThat(result.matchedSkills()).extracting(SkillCloudItem::skillId).containsExactly(2L);
assertThat(result.missingSkills()).extracting(SkillCloudItem::skillId).containsExactly(1L, 3L);
assertThat(union).containsExactlyInAnyOrder(1L, 2L, 3L);
}
@Test
void returnsZeroWhenNoTopSkillMatches() {
List<SkillCloudItem> items = List.of(
item(1L, "Java", "Java", "JAVA"),
item(2L, "SQL", "SQL", "SQL")
);
stubCloud(cloud(items));
saveProfile(USER_ID, json(List.of("Marketing")));
SkillMatchResponse result = service.match(USER_ID, 1L, 1L, null);
assertThat(result.matchScore()).isZero();
assertThat(result.matchedSkills()).isEmpty();
assertThat(result.missingSkills()).extracting(SkillCloudItem::skillId).containsExactly(1L, 2L);
}
@Test
void returnsOneHundredAndNoMissingSkillsWhenAllTopSkillsMatch() {
List<SkillCloudItem> items = List.of(
item(1L, "Java", "Java", "JAVA"),
item(2L, "SQL", "SQL", "SQL")
);
stubCloud(cloud(items));
saveProfile(USER_ID, json(List.of("Java", "SQL")));
SkillMatchResponse result = service.match(USER_ID, 1L, 1L, null);
assertThat(result.matchScore()).isEqualTo(100);
assertThat(result.matchedSkills()).extracting(SkillCloudItem::skillId).containsExactly(1L, 2L);
assertThat(result.missingSkills()).isEmpty();
}
@Test
void excludesCrossOccupationCategoryMismatchFromMatchSet() {
jdbcTemplate.update("""
INSERT INTO occupation_skills (
id, occupation_id, skill_id, category_id, importance,
importance_source_code, importance_element_id, importance_is_proxy,
relationship_type, source_version_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
10000L, 1L, 1L, 201L, new BigDecimal("5.00"),
"15-1254.00", "2.E.7.h", true, "ESSENTIAL", 2L);
saveProfile(USER_ID, json(List.of("Java")));
SkillCloudResponse cloud = queryService.query(1L, 1L, null);
SkillMatchResponse result = service.match(USER_ID, 1L, 1L, null);
List<Long> expectedTop = cloud.items().stream().limit(10).map(SkillCloudItem::skillId).toList();
List<Long> actualUnion = Stream.concat(result.matchedSkills().stream(), result.missingSkills().stream())
.map(SkillCloudItem::skillId)
.toList();
assertThat(result.matchedCount()).isZero();
assertThat(actualUnion).containsExactlyElementsOf(expectedTop);
assertThat(actualUnion).doesNotContain(1L);
}
private void stubCloud(SkillCloudResponse response) {
doReturn(response).when(queryService).query(any(), any(), any());
}
private StudentProfile saveProfile(Long userId, String skillsJson) {
jdbcTemplate.update("""
INSERT INTO users (id, phone, role, is_active, is_verified, created_at, updated_at)
VALUES (?, ?, 'STUDENT', TRUE, FALSE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
""", userId, String.valueOf(userId));
return profileRepository.saveAndFlush(StudentProfile.builder()
.userId(userId)
.skillsJson(skillsJson)
.build());
}
private SkillCloudResponse cloud(List<SkillCloudItem> items) {
return new SkillCloudResponse(
new SkillCloudNode(1L, "INTERNET", "互联网"),
new SkillCloudNode(1L, "FRONTEND_DEVELOPER", "前端开发工程师"),
null,
items.size(),
items
);
}
private SkillCloudItem item(Long id, String nameZh, String nameEn, String code) {
return new SkillCloudItem(
id.intValue(),
id,
code,
nameZh,
nameEn,
"O*NET",
"test-source",
1L,
"测试分类",
BigDecimal.TEN,
null,
null,
false
);
}
private SkillAlias alias(Long skillId, String alias, String normalizedAlias) {
return SkillAlias.builder()
.id(1L)
.skillId(skillId)
.alias(alias)
.normalizedAlias(normalizedAlias)
.build();
}
private String json(List<String> values) {
return values.stream()
.map(value -> "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\"")
.collect(java.util.stream.Collectors.joining(",", "[", "]"));
}
public static class SkillMatchSchemaFilterProvider implements SchemaFilterProvider {
private static final Set<String> VALIDATED_TABLES = Set.of(
"skill_source_versions",
"skill_industries",
"skill_occupations",
"skill_categories",
"skills",
"skill_aliases",
"occupation_skills"
);
@Override
public SchemaFilter getCreateFilter() {
return SchemaFilter.ALL;
}
@Override
public SchemaFilter getDropFilter() {
return SchemaFilter.ALL;
}
@Override
public SchemaFilter getTruncatorFilter() {
return SchemaFilter.ALL;
}
@Override
public SchemaFilter getMigrateFilter() {
return SchemaFilter.ALL;
}
@Override
public SchemaFilter getValidateFilter() {
return new SchemaFilter() {
@Override
public boolean includeNamespace(Namespace namespace) {
return true;
}
@Override
public boolean includeTable(Table table) {
return VALIDATED_TABLES.contains(table.getName().toLowerCase(Locale.ROOT));
}
@Override
public boolean includeSequence(Sequence sequence) {
return false;
}
};
}
}
}
Loading…
Cancel
Save