feat: add pioneer medal dto

This commit is contained in:
Tim
2025-08-11 20:15:49 +08:00
parent 6342b8f3a6
commit 2ebccb40f5
7 changed files with 41 additions and 2 deletions

View File

@@ -0,0 +1,10 @@
package com.openisle.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class PioneerMedalDto extends MedalDto {
private long rank;
}

View File

@@ -4,5 +4,6 @@ public enum MedalType {
COMMENT, COMMENT,
POST, POST,
CONTRIBUTOR, CONTRIBUTOR,
SEED SEED,
PIONEER
} }

View File

@@ -2,6 +2,7 @@ package com.openisle.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import com.openisle.model.User; import com.openisle.model.User;
import java.time.LocalDateTime;
import java.util.Optional; import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
@@ -10,4 +11,5 @@ public interface UserRepository extends JpaRepository<User, Long> {
java.util.List<User> findByUsernameContainingIgnoreCase(String keyword); java.util.List<User> findByUsernameContainingIgnoreCase(String keyword);
java.util.List<User> findByRole(com.openisle.model.Role role); java.util.List<User> findByRole(com.openisle.model.Role role);
long countByExperienceGreaterThanEqual(int experience); long countByExperienceGreaterThanEqual(int experience);
long countByCreatedAtBefore(LocalDateTime createdAt);
} }

View File

@@ -5,6 +5,7 @@ import com.openisle.dto.ContributorMedalDto;
import com.openisle.dto.MedalDto; import com.openisle.dto.MedalDto;
import com.openisle.dto.PostMedalDto; import com.openisle.dto.PostMedalDto;
import com.openisle.dto.SeedUserMedalDto; import com.openisle.dto.SeedUserMedalDto;
import com.openisle.dto.PioneerMedalDto;
import com.openisle.model.MedalType; import com.openisle.model.MedalType;
import com.openisle.model.User; import com.openisle.model.User;
import com.openisle.repository.CommentRepository; import com.openisle.repository.CommentRepository;
@@ -24,6 +25,7 @@ public class MedalService {
private static final long POST_TARGET = 100; private static final long POST_TARGET = 100;
private static final LocalDateTime SEED_USER_DEADLINE = LocalDateTime.of(2025, 9, 16, 0, 0); private static final LocalDateTime SEED_USER_DEADLINE = LocalDateTime.of(2025, 9, 16, 0, 0);
private static final long CONTRIBUTION_TARGET = 1; private static final long CONTRIBUTION_TARGET = 1;
private static final long PIONEER_LIMIT = 1000;
private final CommentRepository commentRepository; private final CommentRepository commentRepository;
private final PostRepository postRepository; private final PostRepository postRepository;
@@ -102,6 +104,21 @@ public class MedalService {
} }
seedUserMedal.setSelected(selected == MedalType.SEED); seedUserMedal.setSelected(selected == MedalType.SEED);
medals.add(seedUserMedal); medals.add(seedUserMedal);
PioneerMedalDto pioneerMedal = new PioneerMedalDto();
pioneerMedal.setIcon("https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/icons/achi_pioneer.png");
pioneerMedal.setTitle("开山鼻祖");
pioneerMedal.setDescription("前1000位加入的用户");
pioneerMedal.setType(MedalType.PIONEER);
if (user != null) {
long rank = userRepository.countByCreatedAtBefore(user.getCreatedAt()) + 1;
pioneerMedal.setRank(rank);
pioneerMedal.setCompleted(rank <= PIONEER_LIMIT);
} else {
pioneerMedal.setCompleted(false);
}
pioneerMedal.setSelected(selected == MedalType.PIONEER);
medals.add(pioneerMedal);
if (user != null && selected == null) { if (user != null && selected == null) {
for (MedalDto medal : medals) { for (MedalDto medal : medals) {
if (medal.isCompleted()) { if (medal.isCompleted()) {
@@ -126,6 +143,8 @@ public class MedalService {
user.setDisplayMedal(MedalType.POST); user.setDisplayMedal(MedalType.POST);
} else if (contributorService.getContributionLines(user.getUsername()) >= CONTRIBUTION_TARGET) { } else if (contributorService.getContributionLines(user.getUsername()) >= CONTRIBUTION_TARGET) {
user.setDisplayMedal(MedalType.CONTRIBUTOR); user.setDisplayMedal(MedalType.CONTRIBUTOR);
} else if (userRepository.countByCreatedAtBefore(user.getCreatedAt()) < PIONEER_LIMIT) {
user.setDisplayMedal(MedalType.PIONEER);
} else if (user.getCreatedAt().isBefore(SEED_USER_DEADLINE)) { } else if (user.getCreatedAt().isBefore(SEED_USER_DEADLINE)) {
user.setDisplayMedal(MedalType.SEED); user.setDisplayMedal(MedalType.SEED);
} }

View File

@@ -27,7 +27,7 @@ class MedalServiceTest {
List<MedalDto> medals = service.getMedals(null); List<MedalDto> medals = service.getMedals(null);
medals.forEach(m -> assertFalse(m.isCompleted())); medals.forEach(m -> assertFalse(m.isCompleted()));
assertEquals(4, medals.size()); assertEquals(5, medals.size());
} }
@Test @Test
@@ -40,6 +40,7 @@ class MedalServiceTest {
when(commentRepo.countByAuthor_Id(1L)).thenReturn(120L); when(commentRepo.countByAuthor_Id(1L)).thenReturn(120L);
when(postRepo.countByAuthor_Id(1L)).thenReturn(80L); when(postRepo.countByAuthor_Id(1L)).thenReturn(80L);
when(contributorService.getContributionLines(anyString())).thenReturn(0L); when(contributorService.getContributionLines(anyString())).thenReturn(0L);
when(userRepo.countByCreatedAtBefore(any())).thenReturn(50L);
User user = new User(); User user = new User();
user.setId(1L); user.setId(1L);
user.setCreatedAt(LocalDateTime.of(2025, 9, 15, 0, 0)); user.setCreatedAt(LocalDateTime.of(2025, 9, 15, 0, 0));
@@ -56,6 +57,8 @@ class MedalServiceTest {
assertFalse(medals.stream().filter(m -> m.getType() == MedalType.POST).findFirst().orElseThrow().isSelected()); assertFalse(medals.stream().filter(m -> m.getType() == MedalType.POST).findFirst().orElseThrow().isSelected());
assertTrue(medals.stream().filter(m -> m.getType() == MedalType.SEED).findFirst().orElseThrow().isCompleted()); assertTrue(medals.stream().filter(m -> m.getType() == MedalType.SEED).findFirst().orElseThrow().isCompleted());
assertFalse(medals.stream().filter(m -> m.getType() == MedalType.SEED).findFirst().orElseThrow().isSelected()); assertFalse(medals.stream().filter(m -> m.getType() == MedalType.SEED).findFirst().orElseThrow().isSelected());
assertTrue(medals.stream().filter(m -> m.getType() == MedalType.PIONEER).findFirst().orElseThrow().isCompleted());
assertFalse(medals.stream().filter(m -> m.getType() == MedalType.PIONEER).findFirst().orElseThrow().isSelected());
verify(userRepo).save(user); verify(userRepo).save(user);
} }
@@ -69,6 +72,7 @@ class MedalServiceTest {
when(commentRepo.countByAuthor_Id(1L)).thenReturn(120L); when(commentRepo.countByAuthor_Id(1L)).thenReturn(120L);
when(postRepo.countByAuthor_Id(1L)).thenReturn(0L); when(postRepo.countByAuthor_Id(1L)).thenReturn(0L);
when(contributorService.getContributionLines(anyString())).thenReturn(0L); when(contributorService.getContributionLines(anyString())).thenReturn(0L);
when(userRepo.countByCreatedAtBefore(any())).thenReturn(0L);
User user = new User(); User user = new User();
user.setId(1L); user.setId(1L);
user.setCreatedAt(LocalDateTime.of(2025, 9, 15, 0, 0)); user.setCreatedAt(LocalDateTime.of(2025, 9, 15, 0, 0));
@@ -90,6 +94,7 @@ class MedalServiceTest {
when(commentRepo.countByAuthor_Id(1L)).thenReturn(10L); when(commentRepo.countByAuthor_Id(1L)).thenReturn(10L);
when(postRepo.countByAuthor_Id(1L)).thenReturn(0L); when(postRepo.countByAuthor_Id(1L)).thenReturn(0L);
when(contributorService.getContributionLines(anyString())).thenReturn(0L); when(contributorService.getContributionLines(anyString())).thenReturn(0L);
when(userRepo.countByCreatedAtBefore(any())).thenReturn(0L);
User user = new User(); User user = new User();
user.setId(1L); user.setId(1L);
user.setCreatedAt(LocalDateTime.of(2025, 9, 15, 0, 0)); user.setCreatedAt(LocalDateTime.of(2025, 9, 15, 0, 0));

View File

@@ -29,6 +29,7 @@
<template v-else-if="medal.type === 'CONTRIBUTOR'"> <template v-else-if="medal.type === 'CONTRIBUTOR'">
{{ medal.currentContributionLines }}/{{ medal.targetContributionLines }} {{ medal.currentContributionLines }}/{{ medal.targetContributionLines }}
</template> </template>
<template v-else-if="medal.type === 'PIONEER'"> {{ medal.rank }} </template>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,6 +3,7 @@ export const medalTitles = {
POST: '发帖达人', POST: '发帖达人',
SEED: '种子用户', SEED: '种子用户',
CONTRIBUTOR: '贡献者', CONTRIBUTOR: '贡献者',
PIONEER: '开山鼻祖',
} }
export function getMedalTitle(type) { export function getMedalTitle(type) {