diff --git a/backend/src/main/java/com/openisle/OpenIsleApplication.java b/backend/src/main/java/com/openisle/OpenIsleApplication.java index ba2421898..e0c86581f 100644 --- a/backend/src/main/java/com/openisle/OpenIsleApplication.java +++ b/backend/src/main/java/com/openisle/OpenIsleApplication.java @@ -2,8 +2,10 @@ package com.openisle; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class OpenIsleApplication { public static void main(String[] args) { SpringApplication.run(OpenIsleApplication.class, args); diff --git a/backend/src/main/java/com/openisle/dto/ContributorMedalDto.java b/backend/src/main/java/com/openisle/dto/ContributorMedalDto.java new file mode 100644 index 000000000..da130ccca --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/ContributorMedalDto.java @@ -0,0 +1,12 @@ +package com.openisle.dto; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ContributorMedalDto extends MedalDto { + private long currentContributionLines; + private long targetContributionLines; +} + diff --git a/backend/src/main/java/com/openisle/model/ContributorConfig.java b/backend/src/main/java/com/openisle/model/ContributorConfig.java new file mode 100644 index 000000000..de94b0aa5 --- /dev/null +++ b/backend/src/main/java/com/openisle/model/ContributorConfig.java @@ -0,0 +1,27 @@ +package com.openisle.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@Table(name = "contributor_configs") +public class ContributorConfig { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String userIname; + + @Column(nullable = false, unique = true) + private String githubId; + + @Column(nullable = false) + private long contributionLines = 0; +} + diff --git a/backend/src/main/java/com/openisle/model/MedalType.java b/backend/src/main/java/com/openisle/model/MedalType.java index 58ab4e22e..d6c31bb7d 100644 --- a/backend/src/main/java/com/openisle/model/MedalType.java +++ b/backend/src/main/java/com/openisle/model/MedalType.java @@ -3,5 +3,6 @@ package com.openisle.model; public enum MedalType { COMMENT, POST, + CONTRIBUTOR, SEED } diff --git a/backend/src/main/java/com/openisle/repository/ContributorConfigRepository.java b/backend/src/main/java/com/openisle/repository/ContributorConfigRepository.java new file mode 100644 index 000000000..852f142a0 --- /dev/null +++ b/backend/src/main/java/com/openisle/repository/ContributorConfigRepository.java @@ -0,0 +1,11 @@ +package com.openisle.repository; + +import com.openisle.model.ContributorConfig; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface ContributorConfigRepository extends JpaRepository { + Optional findByUserIname(String userIname); +} + diff --git a/backend/src/main/java/com/openisle/service/ContributorService.java b/backend/src/main/java/com/openisle/service/ContributorService.java new file mode 100644 index 000000000..778648d36 --- /dev/null +++ b/backend/src/main/java/com/openisle/service/ContributorService.java @@ -0,0 +1,73 @@ +package com.openisle.service; + +import com.openisle.model.ContributorConfig; +import com.openisle.repository.ContributorConfigRepository; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class ContributorService { + private static final String OWNER = "OpenIsle"; + private static final String REPO = "OpenIsle"; + + private final ContributorConfigRepository repository; + private final RestTemplate restTemplate = new RestTemplate(); + + @PostConstruct + @Scheduled(cron = "0 0 * * * *") + public void updateContributions() { + for (ContributorConfig config : repository.findAll()) { + long lines = fetchContributionLines(config.getGithubId()); + config.setContributionLines(lines); + repository.save(config); + } + } + + private long fetchContributionLines(String githubId) { + try { + String url = String.format("https://api.github.com/repos/%s/%s/stats/contributors", OWNER, REPO); + ResponseEntity response = restTemplate.getForEntity(url, List.class); + List> body = response.getBody(); + if (body == null) { + return 0; + } + for (Map item : body) { + Map author = (Map) item.get("author"); + if (author != null && githubId.equals(author.get("login"))) { + List> weeks = (List>) item.get("weeks"); + long total = 0; + if (weeks != null) { + for (Map week : weeks) { + Number a = (Number) week.get("a"); + Number d = (Number) week.get("d"); + if (a != null) { + total += a.longValue(); + } + if (d != null) { + total += d.longValue(); + } + } + } + return total; + } + } + } catch (Exception ignored) { + } + return 0; + } + + public long getContributionLines(String userIname) { + return repository.findByUserIname(userIname) + .map(ContributorConfig::getContributionLines) + .orElse(0L); + } +} + diff --git a/backend/src/main/java/com/openisle/service/MedalService.java b/backend/src/main/java/com/openisle/service/MedalService.java index 05fd14754..e2cbb2569 100644 --- a/backend/src/main/java/com/openisle/service/MedalService.java +++ b/backend/src/main/java/com/openisle/service/MedalService.java @@ -1,6 +1,7 @@ package com.openisle.service; import com.openisle.dto.CommentMedalDto; +import com.openisle.dto.ContributorMedalDto; import com.openisle.dto.MedalDto; import com.openisle.dto.PostMedalDto; import com.openisle.dto.SeedUserMedalDto; @@ -22,10 +23,12 @@ public class MedalService { private static final long COMMENT_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 long CONTRIBUTION_TARGET = 1; private final CommentRepository commentRepository; private final PostRepository postRepository; private final UserRepository userRepository; + private final ContributorService contributorService; public List getMedals(Long userId) { List medals = new ArrayList<>(); @@ -69,6 +72,23 @@ public class MedalService { postMedal.setSelected(selected == MedalType.POST); medals.add(postMedal); + ContributorMedalDto contributorMedal = new ContributorMedalDto(); + contributorMedal.setIcon("https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/icons/achi_contributor.png"); + contributorMedal.setTitle("贡献者"); + contributorMedal.setDescription("对仓库贡献超过1行代码"); + contributorMedal.setType(MedalType.CONTRIBUTOR); + contributorMedal.setTargetContributionLines(CONTRIBUTION_TARGET); + if (user != null) { + long lines = contributorService.getContributionLines(user.getUsername()); + contributorMedal.setCurrentContributionLines(lines); + contributorMedal.setCompleted(lines >= CONTRIBUTION_TARGET); + } else { + contributorMedal.setCurrentContributionLines(0); + contributorMedal.setCompleted(false); + } + contributorMedal.setSelected(selected == MedalType.CONTRIBUTOR); + medals.add(contributorMedal); + SeedUserMedalDto seedUserMedal = new SeedUserMedalDto(); seedUserMedal.setIcon("https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/icons/achi_seed.png"); seedUserMedal.setTitle("种子用户"); @@ -104,6 +124,8 @@ public class MedalService { user.setDisplayMedal(MedalType.COMMENT); } else if (postRepository.countByAuthor_Id(user.getId()) >= POST_TARGET) { user.setDisplayMedal(MedalType.POST); + } else if (contributorService.getContributionLines(user.getUsername()) >= CONTRIBUTION_TARGET) { + user.setDisplayMedal(MedalType.CONTRIBUTOR); } else if (user.getCreatedAt().isBefore(SEED_USER_DEADLINE)) { user.setDisplayMedal(MedalType.SEED); }