Browse Source

feat: add onboarding repositories and review aggregation

backup/master-local-20261012
pwklppg 1 month ago
parent
commit
ef4a9b875b
  1. 63
      mydreamjob-server/src/main/java/com/mydreamjob/onboarding/CompanyKnowledge.java
  2. 12
      mydreamjob-server/src/main/java/com/mydreamjob/onboarding/CompanyKnowledgeRepository.java
  3. 95
      mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistItem.java
  4. 26
      mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistItemRepository.java
  5. 64
      mydreamjob-server/src/main/java/com/mydreamjob/onboarding/StudentOnboardingProgress.java
  6. 19
      mydreamjob-server/src/main/java/com/mydreamjob/onboarding/StudentOnboardingProgressRepository.java
  7. 19
      mydreamjob-server/src/main/java/com/mydreamjob/review/JobReviewDimensionScoreRepository.java
  8. 35
      mydreamjob-server/src/main/java/com/mydreamjob/review/JobReviewRepository.java
  9. 399
      mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingRepositoryIntegrationTest.java

63
mydreamjob-server/src/main/java/com/mydreamjob/onboarding/CompanyKnowledge.java

@ -0,0 +1,63 @@
package com.mydreamjob.onboarding;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.persistence.Version;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(
name = "company_knowledge",
uniqueConstraints = @UniqueConstraint(
name = "uk_company_knowledge_company",
columnNames = "company_id"))
@EntityListeners(AuditingEntityListener.class)
public class CompanyKnowledge {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "company_id", nullable = false)
private Long companyId;
@Column(length = 2000)
private String culture;
@Column(name = "work_policies", length = 4000)
private String workPolicies;
@Column(name = "onboarding_guide", length = 4000)
private String onboardingGuide;
@Version
@Column(nullable = false)
@Builder.Default
private Long version = 0L;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
}

12
mydreamjob-server/src/main/java/com/mydreamjob/onboarding/CompanyKnowledgeRepository.java

@ -0,0 +1,12 @@
package com.mydreamjob.onboarding;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface CompanyKnowledgeRepository extends JpaRepository<CompanyKnowledge, Long> {
Optional<CompanyKnowledge> findByCompanyId(Long companyId);
}

95
mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistItem.java

@ -0,0 +1,95 @@
package com.mydreamjob.onboarding;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.persistence.Version;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(
name = "onboarding_checklist_items",
uniqueConstraints = {
@UniqueConstraint(
name = "uk_onboarding_checklist_scope_item",
columnNames = {"scope_type", "scope_id", "item_key"}),
@UniqueConstraint(
name = "uk_onboarding_checklist_company_idempotency",
columnNames = {"company_id", "idempotency_key"})
})
@EntityListeners(AuditingEntityListener.class)
public class OnboardingChecklistItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "scope_type", nullable = false, length = 20)
private String scopeType;
@Column(name = "scope_id", nullable = false)
private Long scopeId;
@Column(name = "company_id")
private Long companyId;
@Column(name = "item_key", nullable = false, length = 80)
private String itemKey;
@Column(nullable = false, length = 120)
private String title;
@Column(length = 1000)
private String description;
@Column(nullable = false, length = 30)
private String category;
@Column(name = "sort_order", nullable = false)
@Builder.Default
private Integer sortOrder = 1000;
@Column(nullable = false)
@Builder.Default
private Boolean required = false;
@Column(nullable = false)
@Builder.Default
private Boolean active = true;
@Column(name = "idempotency_key", length = 64)
private String idempotencyKey;
@Column(name = "request_hash", columnDefinition = "CHAR(64)")
private String requestHash;
@Version
@Column(nullable = false)
@Builder.Default
private Long version = 0L;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
}

26
mydreamjob-server/src/main/java/com/mydreamjob/onboarding/OnboardingChecklistItemRepository.java

@ -0,0 +1,26 @@
package com.mydreamjob.onboarding;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface OnboardingChecklistItemRepository extends JpaRepository<OnboardingChecklistItem, Long> {
@Query("""
SELECT item
FROM OnboardingChecklistItem item
WHERE item.active = true
AND (
item.scopeType = 'STANDARD'
OR (item.scopeType = 'COMPANY'
AND item.scopeId = :companyId
AND item.companyId = :companyId)
)
ORDER BY item.sortOrder ASC, item.id ASC
""")
List<OnboardingChecklistItem> findEffectiveChecklistItems(@Param("companyId") Long companyId);
}

64
mydreamjob-server/src/main/java/com/mydreamjob/onboarding/StudentOnboardingProgress.java

@ -0,0 +1,64 @@
package com.mydreamjob.onboarding;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.persistence.Version;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(
name = "student_onboarding_progress",
uniqueConstraints = @UniqueConstraint(
name = "uk_student_onboarding_progress_student_item",
columnNames = {"student_user_id", "item_id"}))
@EntityListeners(AuditingEntityListener.class)
public class StudentOnboardingProgress {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "student_user_id", nullable = false)
private Long studentUserId;
@Column(name = "item_id", nullable = false)
private Long itemId;
@Column(nullable = false)
@Builder.Default
private Boolean completed = false;
@Column(name = "completed_at")
private LocalDateTime completedAt;
@Version
@Column(nullable = false)
@Builder.Default
private Long version = 0L;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
}

19
mydreamjob-server/src/main/java/com/mydreamjob/onboarding/StudentOnboardingProgressRepository.java

@ -0,0 +1,19 @@
package com.mydreamjob.onboarding;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@Repository
public interface StudentOnboardingProgressRepository
extends JpaRepository<StudentOnboardingProgress, Long> {
List<StudentOnboardingProgress> findByStudentUserIdAndItemIdIn(
Long studentUserId, Collection<Long> itemIds);
Optional<StudentOnboardingProgress> findByStudentUserIdAndItemId(
Long studentUserId, Long itemId);
}

19
mydreamjob-server/src/main/java/com/mydreamjob/review/JobReviewDimensionScoreRepository.java

@ -42,4 +42,23 @@ public interface JobReviewDimensionScoreRepository extends JpaRepository<JobRevi
GROUP BY d.dimensionCode
""")
List<Object[]> aggregatePublishedByJobId(@Param("jobId") Long jobId);
/**
* 按公司聚合公开评价八维评分:一次数据库聚合,不加载 JobReview 实体,
* 不先查询 reviewId 列表。返回 [dimensionCode, AVG(score), COUNT(id)]。
*/
@Query("""
SELECT d.dimensionCode, AVG(d.score), COUNT(d.id)
FROM JobReviewDimensionScore d
WHERE d.reviewId IN (
SELECT r.id
FROM JobReview r
WHERE r.jobId IN (SELECT j.id FROM Job j WHERE j.companyId = :companyId)
AND r.status = 'PUBLISHED'
AND r.moderationStatus IN ('AUTO_APPROVED', 'APPROVED')
)
GROUP BY d.dimensionCode
ORDER BY d.dimensionCode
""")
List<Object[]> aggregatePublishedDimensionsByCompanyId(@Param("companyId") Long companyId);
}

35
mydreamjob-server/src/main/java/com/mydreamjob/review/JobReviewRepository.java

@ -31,6 +31,41 @@ public interface JobReviewRepository extends JpaRepository<JobReview, Long> {
List<JobReview> findByJobIdAndStatusAndModerationStatusIn(
Long jobId, String status, Collection<String> moderationStatuses);
/** 企业公开评价汇总:一次查询统计全部岗位的 PUBLISHED + 审核通过评价。 */
@Query("""
SELECT COUNT(r), COALESCE(AVG(r.rating), 0)
FROM JobReview r
WHERE r.jobId IN (SELECT j.id FROM Job j WHERE j.companyId = :companyId)
AND r.status = 'PUBLISHED'
AND r.moderationStatus IN ('AUTO_APPROVED', 'APPROVED')
""")
List<Object[]> countAndAveragePublishedRatingByCompanyId(@Param("companyId") Long companyId);
/** 企业公开评价评分分布,按评分升序返回 [rating, count]。 */
@Query("""
SELECT r.rating, COUNT(r)
FROM JobReview r
WHERE r.jobId IN (SELECT j.id FROM Job j WHERE j.companyId = :companyId)
AND r.status = 'PUBLISHED'
AND r.moderationStatus IN ('AUTO_APPROVED', 'APPROVED')
GROUP BY r.rating
ORDER BY r.rating
""")
List<Object[]> findPublishedRatingDistributionByCompanyId(@Param("companyId") Long companyId);
/** 企业公开评价的原始 tags 列,避免逐条读取评价。 */
@Query("""
SELECT r.tags
FROM JobReview r
WHERE r.jobId IN (SELECT j.id FROM Job j WHERE j.companyId = :companyId)
AND r.status = 'PUBLISHED'
AND r.moderationStatus IN ('AUTO_APPROVED', 'APPROVED')
AND r.tags IS NOT NULL
ORDER BY r.id ASC
""")
List<String> findPublishedTagsByCompanyId(@Param("companyId") Long companyId);
// ── 防刷规则查询 ──────────────────────────────────────────
long countByStudentUserIdAndCreatedAtAfter(Long studentUserId, LocalDateTime after);

399
mydreamjob-server/src/test/java/com/mydreamjob/onboarding/OnboardingRepositoryIntegrationTest.java

@ -0,0 +1,399 @@
package com.mydreamjob.onboarding;
import com.mydreamjob.auth.User;
import com.mydreamjob.auth.UserRepository;
import com.mydreamjob.company.Company;
import com.mydreamjob.company.CompanyRepository;
import com.mydreamjob.job.Job;
import com.mydreamjob.job.JobRepository;
import com.mydreamjob.review.JobReview;
import com.mydreamjob.review.JobReviewDimensionScore;
import com.mydreamjob.review.JobReviewDimensionScoreRepository;
import com.mydreamjob.review.JobReviewRepository;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.boot.model.relational.Sequence;
import org.hibernate.mapping.Table;
import org.junit.jupiter.api.BeforeEach;
import org.hibernate.tool.schema.spi.SchemaFilter;
import org.hibernate.tool.schema.spi.SchemaFilterProvider;
import org.junit.jupiter.api.Test;
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.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.time.LocalDateTime;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:integrationdb_onboarding_repository;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE",
"spring.datasource.driver-class-name=org.h2.Driver",
"spring.datasource.username=sa",
"spring.datasource.password=",
"spring.jpa.hibernate.ddl-auto=validate",
"spring.jpa.properties.hibernate.hbm2ddl.schema_filter_provider=com.mydreamjob.onboarding.OnboardingRepositoryIntegrationTest$OnboardingSchemaFilterProvider"
})
@ActiveProfiles("integration-test")
@Transactional
class OnboardingRepositoryIntegrationTest {
private static final AtomicLong PHONE_SEQ = new AtomicLong(900);
@Autowired private OnboardingChecklistItemRepository checklistItemRepository;
@Autowired private StudentOnboardingProgressRepository progressRepository;
@Autowired private CompanyKnowledgeRepository knowledgeRepository;
@Autowired private JobReviewRepository reviewRepository;
@Autowired private JobReviewDimensionScoreRepository dimensionScoreRepository;
@Autowired private UserRepository userRepository;
@Autowired private CompanyRepository companyRepository;
@Autowired private JobRepository jobRepository;
@Autowired private JdbcTemplate jdbcTemplate;
@MockBean private RedisTemplate<String, Object> redisTemplate;
@BeforeEach
void clearData() {
progressRepository.deleteAll();
knowledgeRepository.deleteAll();
dimensionScoreRepository.deleteAll();
reviewRepository.deleteAll();
jobRepository.deleteAll();
checklistItemRepository.deleteAll();
companyRepository.deleteAll();
userRepository.deleteAll();
}
@Test
void effectiveChecklistReturnsActiveStandardAndCompanyItemsInStableOrder() {
Long companyAId = createCompany("清单企业A");
Long companyBId = createCompany("清单企业B");
OnboardingChecklistItem standardActive = saveChecklistItem(
"STANDARD", 0L, null, "standard-visible", "通用可见项", 100, true, null, null);
saveChecklistItem("STANDARD", 0L, null, "standard-inactive", "通用停用项", 1, false, null, null);
OnboardingChecklistItem companyFirst = saveChecklistItem(
"COMPANY", companyAId, companyAId, "company-a-first", "企业A首项", 50, true,
"company-a-first-key", "a".repeat(64));
OnboardingChecklistItem companySecond = saveChecklistItem(
"COMPANY", companyAId, companyAId, "company-a-second", "企业A次项", 50, true,
"company-a-second-key", "b".repeat(64));
saveChecklistItem("COMPANY", companyAId, companyAId, "company-a-inactive", "企业A停用项",
40, false, null, null);
saveChecklistItem("COMPANY", companyBId, companyBId, "company-b-visible", "企业B可见项",
10, true, "company-b-key", "c".repeat(64));
List<OnboardingChecklistItem> result = checklistItemRepository.findEffectiveChecklistItems(companyAId);
assertThat(result)
.extracting(OnboardingChecklistItem::getId)
.containsExactly(companyFirst.getId(), companySecond.getId(), standardActive.getId());
assertThat(result)
.extracting(OnboardingChecklistItem::getItemKey)
.containsExactly("company-a-first", "company-a-second", "standard-visible");
}
@Test
void requestHashIsMappedAsChar64() {
Long companyId = createCompany("哈希企业");
String hash = "0123456789abcdef".repeat(4);
OnboardingChecklistItem saved = saveChecklistItem(
"COMPANY", companyId, companyId, "company-hash", "哈希映射项", 200, true,
"company-hash-key", hash);
checklistItemRepository.flush();
assertThat(checklistItemRepository.findById(saved.getId()))
.get()
.extracting(OnboardingChecklistItem::getRequestHash)
.isEqualTo(hash);
String typeName = jdbcTemplate.queryForObject(
"SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS " +
"WHERE UPPER(TABLE_NAME) = 'ONBOARDING_CHECKLIST_ITEMS' " +
"AND UPPER(COLUMN_NAME) = 'REQUEST_HASH'",
String.class);
Integer columnSize = jdbcTemplate.queryForObject(
"SELECT CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS " +
"WHERE UPPER(TABLE_NAME) = 'ONBOARDING_CHECKLIST_ITEMS' " +
"AND UPPER(COLUMN_NAME) = 'REQUEST_HASH'",
Integer.class);
assertThat(typeName).isNotNull().startsWith("CHAR").doesNotContain("VARCHAR");
assertThat(columnSize).isEqualTo(64);
}
@Test
void progressBatchAndUniqueLookupsAreScopedToStudentAndItem() {
Long studentOne = createUser(User.Role.STUDENT, "进度学生A");
Long studentTwo = createUser(User.Role.STUDENT, "进度学生B");
OnboardingChecklistItem firstItem = saveChecklistItem(
"STANDARD", 0L, null, "progress-item-1", "进度项1", 10, true, null, null);
OnboardingChecklistItem secondItem = saveChecklistItem(
"STANDARD", 0L, null, "progress-item-2", "进度项2", 20, true, null, null);
OnboardingChecklistItem thirdItem = saveChecklistItem(
"STANDARD", 0L, null, "progress-item-3", "进度项3", 30, true, null, null);
LocalDateTime completedAt = LocalDateTime.of(2026, 9, 16, 8, 0);
progressRepository.save(StudentOnboardingProgress.builder()
.studentUserId(studentOne).itemId(firstItem.getId()).completed(true)
.completedAt(completedAt).build());
progressRepository.save(StudentOnboardingProgress.builder()
.studentUserId(studentOne).itemId(secondItem.getId()).completed(false)
.completedAt(null).build());
progressRepository.save(StudentOnboardingProgress.builder()
.studentUserId(studentTwo).itemId(firstItem.getId()).completed(true)
.completedAt(LocalDateTime.of(2026, 9, 16, 9, 0)).build());
progressRepository.flush();
List<StudentOnboardingProgress> batch = progressRepository.findByStudentUserIdAndItemIdIn(
studentOne, List.of(firstItem.getId(), secondItem.getId(), thirdItem.getId()));
assertThat(batch)
.extracting(StudentOnboardingProgress::getItemId)
.containsExactlyInAnyOrder(firstItem.getId(), secondItem.getId());
assertThat(batch)
.filteredOn(progress -> progress.getItemId().equals(firstItem.getId()))
.singleElement()
.extracting(StudentOnboardingProgress::getCompletedAt)
.isEqualTo(completedAt);
Optional<StudentOnboardingProgress> unique = progressRepository.findByStudentUserIdAndItemId(
studentOne, firstItem.getId());
assertThat(unique).isPresent();
assertThat(unique.orElseThrow().getCompleted()).isTrue();
assertThat(progressRepository.findByStudentUserIdAndItemId(studentOne, thirdItem.getId())).isEmpty();
assertThat(progressRepository.findByStudentUserIdAndItemId(studentTwo, secondItem.getId())).isEmpty();
}
@Test
void companyKnowledgeLookupIsScopedToCompany() {
Long companyId = createCompany("知识企业");
Long otherCompanyId = createCompany("其他知识企业");
knowledgeRepository.save(CompanyKnowledge.builder()
.companyId(companyId)
.culture("重视透明沟通")
.workPolicies("弹性工作制")
.onboardingGuide("前台报到")
.build());
knowledgeRepository.flush();
Optional<CompanyKnowledge> found = knowledgeRepository.findByCompanyId(companyId);
assertThat(found).isPresent();
assertThat(found.orElseThrow().getCulture()).isEqualTo("重视透明沟通");
assertThat(found.orElseThrow().getWorkPolicies()).isEqualTo("弹性工作制");
assertThat(found.orElseThrow().getOnboardingGuide()).isEqualTo("前台报到");
assertThat(knowledgeRepository.findByCompanyId(otherCompanyId)).isEmpty();
}
@Test
void companyReviewAggregatesIncludeOnlyPublishedApprovedReviews() {
Long companyId = createCompany("评价企业A");
Long otherCompanyId = createCompany("评价企业B");
Long studentOne = createUser(User.Role.STUDENT, "评价学生A");
Long studentTwo = createUser(User.Role.STUDENT, "评价学生B");
Long studentThree = createUser(User.Role.STUDENT, "评价学生C");
Long hiddenStudent = createUser(User.Role.STUDENT, "评价学生D");
Long otherStudent = createUser(User.Role.STUDENT, "评价学生E");
Long firstJobId = createJob(companyId, "评价岗位A1");
Long secondJobId = createJob(companyId, "评价岗位A2");
Long otherJobId = createJob(otherCompanyId, "评价岗位B");
JobReview five = saveReview(firstJobId, studentOne, 5, "[\"成长空间\",\"团队氛围\"]",
"PUBLISHED", JobReview.MODERATION_APPROVED);
JobReview four = saveReview(secondJobId, studentTwo, 4, "[\"成长空间\"]",
"PUBLISHED", JobReview.MODERATION_AUTO_APPROVED);
JobReview three = saveReview(firstJobId, studentThree, 3, "[\"团队氛围\"]",
"PUBLISHED", JobReview.MODERATION_APPROVED);
JobReview hidden = saveReview(firstJobId, hiddenStudent, 1, "[\"薪资透明\"]",
"PUBLISHED", JobReview.MODERATION_PENDING);
saveReview(otherJobId, otherStudent, 2, "[\"其他企业标签\"]",
"PUBLISHED", JobReview.MODERATION_APPROVED);
dimensionScoreRepository.saveAll(List.of(
dimensionScore(five.getId(), "TEAM_ATMOSPHERE", 5),
dimensionScore(five.getId(), "GROWTH", 5),
dimensionScore(four.getId(), "TEAM_ATMOSPHERE", 4),
dimensionScore(four.getId(), "GROWTH", 4),
dimensionScore(three.getId(), "TEAM_ATMOSPHERE", 3),
dimensionScore(hidden.getId(), "TEAM_ATMOSPHERE", 1)
));
dimensionScoreRepository.flush();
List<Object[]> summary = reviewRepository.countAndAveragePublishedRatingByCompanyId(companyId);
assertThat(summary).hasSize(1);
assertThat(((Number) summary.get(0)[0]).longValue()).isEqualTo(3);
assertThat(((Number) summary.get(0)[1]).doubleValue()).isEqualTo(4.0);
List<Object[]> distribution = reviewRepository.findPublishedRatingDistributionByCompanyId(companyId);
assertThat(distribution).hasSize(3);
assertThat(((Number) distribution.get(0)[0]).intValue()).isEqualTo(3);
assertThat(((Number) distribution.get(0)[1]).longValue()).isEqualTo(1);
assertThat(((Number) distribution.get(1)[0]).intValue()).isEqualTo(4);
assertThat(((Number) distribution.get(1)[1]).longValue()).isEqualTo(1);
assertThat(((Number) distribution.get(2)[0]).intValue()).isEqualTo(5);
assertThat(((Number) distribution.get(2)[1]).longValue()).isEqualTo(1);
List<String> tags = reviewRepository.findPublishedTagsByCompanyId(companyId);
assertThat(tags).hasSize(3);
assertThat(tags).allSatisfy(tag ->
assertThat(tag).doesNotContain("薪资透明", "其他企业标签"));
assertThat(tags).anySatisfy(tag ->
assertThat(tag).contains("成长空间").contains("团队氛围"));
assertThat(tags).anySatisfy(tag ->
assertThat(tag).contains("成长空间").doesNotContain("团队氛围"));
assertThat(tags).anySatisfy(tag ->
assertThat(tag).contains("团队氛围").doesNotContain("成长空间"));
List<Object[]> dimensions = dimensionScoreRepository.aggregatePublishedDimensionsByCompanyId(companyId);
assertThat(dimensions).hasSize(2);
assertThat(dimensions.get(0)[0]).isEqualTo("GROWTH");
assertThat(((Number) dimensions.get(0)[1]).doubleValue()).isEqualTo(4.5);
assertThat(((Number) dimensions.get(0)[2]).longValue()).isEqualTo(2);
assertThat(dimensions.get(1)[0]).isEqualTo("TEAM_ATMOSPHERE");
assertThat(((Number) dimensions.get(1)[1]).doubleValue()).isEqualTo(4.0);
assertThat(((Number) dimensions.get(1)[2]).longValue()).isEqualTo(3);
}
private Long createCompany(String name) {
Long ownerId = createUser(User.Role.COMPANY, name + "负责人");
return companyRepository.save(Company.builder()
.ownerUserId(ownerId)
.name(name)
.verified(true)
.build()).getId();
}
private Long createUser(User.Role role, String nickname) {
User user = userRepository.save(User.builder()
.phone("175" + String.format("%08d", PHONE_SEQ.incrementAndGet()))
.nickname(nickname)
.role(role)
.roles(Set.of(role))
.active(true)
.verified(true)
.build());
return user.getId();
}
private Long createJob(Long companyId, String title) {
return jobRepository.save(Job.builder()
.companyId(companyId)
.title(title)
.description("岗位描述")
.city("上海")
.employmentType("FULL_TIME")
.status("PUBLISHED")
.build()).getId();
}
private OnboardingChecklistItem saveChecklistItem(
String scopeType,
Long scopeId,
Long companyId,
String itemKey,
String title,
int sortOrder,
boolean active,
String idempotencyKey,
String requestHash) {
return checklistItemRepository.save(OnboardingChecklistItem.builder()
.scopeType(scopeType)
.scopeId(scopeId)
.companyId(companyId)
.itemKey(itemKey)
.title(title)
.description(title + "说明")
.category("OTHER")
.sortOrder(sortOrder)
.required(false)
.active(active)
.idempotencyKey(idempotencyKey)
.requestHash(requestHash)
.build());
}
private JobReview saveReview(
Long jobId,
Long studentUserId,
int rating,
String tags,
String status,
String moderationStatus) {
return reviewRepository.save(JobReview.builder()
.jobId(jobId)
.studentUserId(studentUserId)
.rating(rating)
.content("评价内容")
.tags(tags)
.status(status)
.moderationStatus(moderationStatus)
.build());
}
private JobReviewDimensionScore dimensionScore(Long reviewId, String dimensionCode, int score) {
return JobReviewDimensionScore.builder()
.reviewId(reviewId)
.dimensionCode(dimensionCode)
.score(score)
.build();
}
public static class OnboardingSchemaFilterProvider implements SchemaFilterProvider {
private static final Set<String> VALIDATED_TABLES = Set.of(
"onboarding_checklist_items",
"student_onboarding_progress",
"company_knowledge"
);
@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