From 86441541577ecff182db91c3f9944f61a069a047 Mon Sep 17 00:00:00 2001 From: vsxd Date: Thu, 12 Mar 2026 02:00:34 +0800 Subject: [PATCH] feat(domain): add Skill domain entities, repositories, and JPA implementations --- .../2026-03-12-phase2-namespace-skill-core.md | 116 +++++++++-- .../iflytek/skillhub/domain/skill/Skill.java | 195 ++++++++++++++++++ .../skillhub/domain/skill/SkillFile.java | 84 ++++++++ .../domain/skill/SkillFileRepository.java | 10 + .../domain/skill/SkillRepository.java | 13 ++ .../skillhub/domain/skill/SkillStatus.java | 7 + .../skillhub/domain/skill/SkillTag.java | 86 ++++++++ .../domain/skill/SkillTagRepository.java | 11 + .../skillhub/domain/skill/SkillVersion.java | 140 +++++++++++++ .../domain/skill/SkillVersionRepository.java | 11 + .../domain/skill/SkillVersionStatus.java | 8 + .../domain/skill/SkillVisibility.java | 7 + .../infra/jpa/SkillFileJpaRepository.java | 14 ++ .../infra/jpa/SkillJpaRepository.java | 33 +++ .../infra/jpa/SkillTagJpaRepository.java | 15 ++ .../infra/jpa/SkillVersionJpaRepository.java | 25 +++ 16 files changed, 763 insertions(+), 12 deletions(-) create mode 100644 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/Skill.java create mode 100644 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillFile.java create mode 100644 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillFileRepository.java create mode 100644 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillRepository.java create mode 100644 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillStatus.java create mode 100644 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillTag.java create mode 100644 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillTagRepository.java create mode 100644 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersion.java create mode 100644 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersionRepository.java create mode 100644 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersionStatus.java create mode 100644 server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVisibility.java create mode 100644 server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillFileJpaRepository.java create mode 100644 server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillJpaRepository.java create mode 100644 server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillTagJpaRepository.java create mode 100644 server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillVersionJpaRepository.java diff --git a/docs/superpowers/plans/2026-03-12-phase2-namespace-skill-core.md b/docs/superpowers/plans/2026-03-12-phase2-namespace-skill-core.md index f6e0ed7e..3457d580 100644 --- a/docs/superpowers/plans/2026-03-12-phase2-namespace-skill-core.md +++ b/docs/superpowers/plans/2026-03-12-phase2-namespace-skill-core.md @@ -5978,18 +5978,110 @@ Phase 2 Chunk 1 backend complete: ### Chunk 1 验收标准 -1. ✅ `V2__phase2_skill_tables.sql` 迁移成功 -2. ✅ Phase 1 实体补齐(Namespace.type/avatarUrl, NamespaceMember.updatedAt) -3. ✅ 对象存储 LocalFile + S3 双实现 -4. ✅ 命名空间 CRUD + 成员管理 API -5. ✅ CLI/Web 发布接口:zip → 校验 → 存储 → PUBLISHED -6. ✅ 技能详情、版本列表、文件清单、文件内容 API -7. ✅ 下载 API:latest / 指定版本 / 按标签 -8. ✅ 标签 CRUD API,latest 标签不可操作 -9. ✅ 搜索 API:关键词 + 可见性过滤 + 排序 + 分页 -10. ✅ 异步事件:发布后索引更新,下载后计数递增 -11. ✅ 限流:超限 429,Redis 不可用 fail-open -12. ✅ 所有后端测试通过 +运行以下命令验证 Chunk 1 完成: + +```bash +# 1. 启动依赖服务 +make dev + +# 2. 运行所有后端测试 +cd server && ./mvnw test +# Expected: BUILD SUCCESS, all tests pass + +# 3. 启动后端应用 +cd server && ./mvnw spring-boot:run -Dspring-boot.run.profiles=local & +sleep 15 + +# 4. 验证 Phase 2 数据库迁移 +docker compose exec postgres psql -U skillhub -d skillhub -c "\dt skill*" +# Expected: 列出 skill, skill_version, skill_file, skill_tag, skill_search_document 五张表 + +# 5. 验证命名空间 CRUD(创建命名空间) +curl -s -X POST http://localhost:8080/api/v1/namespaces \ + -H "Content-Type: application/json" \ + -d '{"slug":"test-ns","displayName":"Test NS","description":"test"}' | jq . +# Expected: {"code":0,"data":{"slug":"test-ns","displayName":"Test NS",...}} + +# 6. 验证命名空间查询 +curl -s http://localhost:8080/api/v1/namespaces/test-ns | jq . +# Expected: {"code":0,"data":{"slug":"test-ns",...}} + +# 7. 验证 CLI 发布接口(创建测试 zip) +mkdir -p /tmp/test-skill && cat > /tmp/test-skill/SKILL.md << 'SKILLEOF' +--- +name: hello-world +description: A test skill +version: 1.0.0 +--- +# Hello World +Test skill body. +SKILLEOF +cd /tmp/test-skill && zip -r /tmp/test-skill.zip . && cd - + +curl -s -X POST http://localhost:8080/api/v1/cli/publish \ + -F "file=@/tmp/test-skill.zip" \ + -F "namespace=test-ns" \ + -F "visibility=PUBLIC" | jq . +# Expected: {"code":0,"data":{"slug":"hello-world","version":"1.0.0","status":"PUBLISHED",...}} + +# 8. 验证技能详情查询 +curl -s http://localhost:8080/api/v1/skills/test-ns/hello-world | jq . +# Expected: {"code":0,"data":{"slug":"hello-world","displayName":"hello-world",...}} + +# 9. 验证版本列表 +curl -s http://localhost:8080/api/v1/skills/test-ns/hello-world/versions | jq . +# Expected: {"code":0,"data":{"content":[{"version":"1.0.0","status":"PUBLISHED",...}],...}} + +# 10. 验证文件清单 +curl -s "http://localhost:8080/api/v1/skills/test-ns/hello-world/versions/1.0.0/files" | jq . +# Expected: {"code":0,"data":[{"filePath":"SKILL.md",...}]} + +# 11. 验证下载最新版本 +curl -s -o /tmp/download-test.zip -w "%{http_code}" \ + http://localhost:8080/api/v1/skills/test-ns/hello-world/download +# Expected: 200, /tmp/download-test.zip 为有效 zip 文件 + +# 12. 验证标签管理(创建标签) +curl -s -X PUT http://localhost:8080/api/v1/skills/test-ns/hello-world/tags/stable \ + -H "Content-Type: application/json" \ + -d '{"targetVersion":"1.0.0"}' | jq . +# Expected: {"code":0,"data":{"tagName":"stable","version":"1.0.0",...}} + +# 13. 验证标签列表 +curl -s http://localhost:8080/api/v1/skills/test-ns/hello-world/tags | jq . +# Expected: 包含 "stable" 和虚拟 "latest" 标签 + +# 14. 验证按标签下载 +curl -s -o /tmp/download-tag.zip -w "%{http_code}" \ + http://localhost:8080/api/v1/skills/test-ns/hello-world/tags/stable/download +# Expected: 200 + +# 15. 验证搜索 API +curl -s "http://localhost:8080/api/v1/skills?q=hello&sort=relevance&page=0&size=20" | jq . +# Expected: {"code":0,"data":{"items":[{"slug":"hello-world",...}],"total":1,...}} + +# 16. 验证搜索空关键词(列表模式) +curl -s "http://localhost:8080/api/v1/skills?sort=newest&page=0&size=20" | jq . +# Expected: {"code":0,"data":{"items":[...],"total":...}} + +# 17. 验证限流(连续请求触发 429) +for i in $(seq 1 25); do + code=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8080/api/v1/skills?q=test") + echo "Request $i: $code" +done +# Expected: 前 20 次返回 200,之后返回 429(匿名 search 限额 20 次/60s) + +# 18. 验证限流 header +curl -s -D - "http://localhost:8080/api/v1/skills?q=test" 2>&1 | grep -i "x-ratelimit\|retry-after" +# Expected: X-RateLimit-Remaining header 存在 + +# 19. 停止应用并清理 +kill %1 2>/dev/null +rm -rf /tmp/test-skill /tmp/test-skill.zip /tmp/download-test.zip /tmp/download-tag.zip +make dev-down +``` + +Chunk 1 产出:Phase 2 全部后端功能 — 数据库迁移 + 对象存储 + 命名空间管理 + 技能发布/查询/下载 + 标签管理 + PostgreSQL 全文搜索 + 异步事件 + Redis 滑动窗口限流。 --- diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/Skill.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/Skill.java new file mode 100644 index 00000000..6a290561 --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/Skill.java @@ -0,0 +1,195 @@ +package com.iflytek.skillhub.domain.skill; + +import jakarta.persistence.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Entity +@Table(name = "skill") +public class Skill { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "namespace_id", nullable = false) + private Long namespaceId; + + @Column(nullable = false, length = 100) + private String slug; + + @Column(name = "display_name", length = 200) + private String displayName; + + @Column(columnDefinition = "TEXT") + private String summary; + + @Column(name = "owner_id", nullable = false) + private Long ownerId; + + @Column(name = "source_skill_id") + private Long sourceSkillId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private SkillVisibility visibility; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private SkillStatus status; + + @Column(name = "latest_version_id") + private Long latestVersionId; + + @Column(name = "download_count", nullable = false) + private Long downloadCount = 0L; + + @Column(name = "star_count", nullable = false) + private Integer starCount = 0; + + @Column(name = "rating_avg", precision = 3, scale = 2, nullable = false) + private BigDecimal ratingAvg = BigDecimal.ZERO; + + @Column(name = "rating_count", nullable = false) + private Integer ratingCount = 0; + + @Column(name = "created_by") + private Long createdBy; + + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_by") + private Long updatedBy; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + protected Skill() { + } + + public Skill(Long namespaceId, String slug, Long ownerId, SkillVisibility visibility) { + this.namespaceId = namespaceId; + this.slug = slug; + this.ownerId = ownerId; + this.visibility = visibility; + this.status = SkillStatus.ACTIVE; + } + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } + + // Getters + public Long getId() { + return id; + } + + public Long getNamespaceId() { + return namespaceId; + } + + public String getSlug() { + return slug; + } + + public String getDisplayName() { + return displayName; + } + + public String getSummary() { + return summary; + } + + public Long getOwnerId() { + return ownerId; + } + + public Long getSourceSkillId() { + return sourceSkillId; + } + + public SkillVisibility getVisibility() { + return visibility; + } + + public SkillStatus getStatus() { + return status; + } + + public Long getLatestVersionId() { + return latestVersionId; + } + + public Long getDownloadCount() { + return downloadCount; + } + + public Integer getStarCount() { + return starCount; + } + + public BigDecimal getRatingAvg() { + return ratingAvg; + } + + public Integer getRatingCount() { + return ratingCount; + } + + public Long getCreatedBy() { + return createdBy; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public Long getUpdatedBy() { + return updatedBy; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + // Setters + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public void setSourceSkillId(Long sourceSkillId) { + this.sourceSkillId = sourceSkillId; + } + + public void setVisibility(SkillVisibility visibility) { + this.visibility = visibility; + } + + public void setStatus(SkillStatus status) { + this.status = status; + } + + public void setLatestVersionId(Long latestVersionId) { + this.latestVersionId = latestVersionId; + } + + public void setCreatedBy(Long createdBy) { + this.createdBy = createdBy; + } + + public void setUpdatedBy(Long updatedBy) { + this.updatedBy = updatedBy; + } +} diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillFile.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillFile.java new file mode 100644 index 00000000..3caa640e --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillFile.java @@ -0,0 +1,84 @@ +package com.iflytek.skillhub.domain.skill; + +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "skill_file") +public class SkillFile { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "version_id", nullable = false) + private Long versionId; + + @Column(name = "file_path", nullable = false, length = 500) + private String filePath; + + @Column(name = "file_size", nullable = false) + private Long fileSize; + + @Column(name = "content_type", length = 100) + private String contentType; + + @Column(length = 64) + private String sha256; + + @Column(name = "storage_key", nullable = false, length = 500) + private String storageKey; + + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + protected SkillFile() { + } + + public SkillFile(Long versionId, String filePath, Long fileSize, String contentType, String sha256, String storageKey) { + this.versionId = versionId; + this.filePath = filePath; + this.fileSize = fileSize; + this.contentType = contentType; + this.sha256 = sha256; + this.storageKey = storageKey; + } + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + } + + // Getters + public Long getId() { + return id; + } + + public Long getVersionId() { + return versionId; + } + + public String getFilePath() { + return filePath; + } + + public Long getFileSize() { + return fileSize; + } + + public String getContentType() { + return contentType; + } + + public String getSha256() { + return sha256; + } + + public String getStorageKey() { + return storageKey; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } +} diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillFileRepository.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillFileRepository.java new file mode 100644 index 00000000..0a93e8e5 --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillFileRepository.java @@ -0,0 +1,10 @@ +package com.iflytek.skillhub.domain.skill; + +import java.util.List; + +public interface SkillFileRepository { + List findByVersionId(Long versionId); + SkillFile save(SkillFile file); + void saveAll(List files); + void deleteByVersionId(Long versionId); +} diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillRepository.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillRepository.java new file mode 100644 index 00000000..46902848 --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillRepository.java @@ -0,0 +1,13 @@ +package com.iflytek.skillhub.domain.skill; + +import java.util.List; +import java.util.Optional; + +public interface SkillRepository { + Optional findById(Long id); + Optional findByNamespaceIdAndSlug(Long namespaceId, String slug); + List findByNamespaceIdAndStatus(Long namespaceId, SkillStatus status); + Skill save(Skill skill); + List findByOwnerId(Long ownerId); + void incrementDownloadCount(Long skillId); +} diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillStatus.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillStatus.java new file mode 100644 index 00000000..e4059f10 --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillStatus.java @@ -0,0 +1,7 @@ +package com.iflytek.skillhub.domain.skill; + +public enum SkillStatus { + ACTIVE, + HIDDEN, + ARCHIVED +} diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillTag.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillTag.java new file mode 100644 index 00000000..92f6c432 --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillTag.java @@ -0,0 +1,86 @@ +package com.iflytek.skillhub.domain.skill; + +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "skill_tag") +public class SkillTag { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "skill_id", nullable = false) + private Long skillId; + + @Column(name = "tag_name", nullable = false, length = 50) + private String tagName; + + @Column(name = "version_id") + private Long versionId; + + @Column(name = "created_by", nullable = false) + private Long createdBy; + + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + protected SkillTag() { + } + + public SkillTag(Long skillId, String tagName, Long versionId, Long createdBy) { + this.skillId = skillId; + this.tagName = tagName; + this.versionId = versionId; + this.createdBy = createdBy; + } + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } + + // Getters + public Long getId() { + return id; + } + + public Long getSkillId() { + return skillId; + } + + public String getTagName() { + return tagName; + } + + public Long getVersionId() { + return versionId; + } + + public Long getCreatedBy() { + return createdBy; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + // Setter + public void setVersionId(Long versionId) { + this.versionId = versionId; + } +} diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillTagRepository.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillTagRepository.java new file mode 100644 index 00000000..f8cd1c1c --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillTagRepository.java @@ -0,0 +1,11 @@ +package com.iflytek.skillhub.domain.skill; + +import java.util.List; +import java.util.Optional; + +public interface SkillTagRepository { + Optional findBySkillIdAndTagName(Long skillId, String tagName); + List findBySkillId(Long skillId); + SkillTag save(SkillTag tag); + void delete(SkillTag tag); +} diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersion.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersion.java new file mode 100644 index 00000000..2a2a8136 --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersion.java @@ -0,0 +1,140 @@ +package com.iflytek.skillhub.domain.skill; + +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "skill_version") +public class SkillVersion { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "skill_id", nullable = false) + private Long skillId; + + @Column(nullable = false, length = 50) + private String version; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private SkillVersionStatus status; + + @Column(columnDefinition = "TEXT") + private String changelog; + + @Column(name = "parsed_metadata_json", columnDefinition = "TEXT") + private String parsedMetadataJson; + + @Column(name = "manifest_json", columnDefinition = "TEXT") + private String manifestJson; + + @Column(name = "file_count", nullable = false) + private Integer fileCount = 0; + + @Column(name = "total_size", nullable = false) + private Long totalSize = 0L; + + @Column(name = "published_at") + private LocalDateTime publishedAt; + + @Column(name = "created_by", nullable = false) + private Long createdBy; + + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + protected SkillVersion() { + } + + public SkillVersion(Long skillId, String version, Long createdBy) { + this.skillId = skillId; + this.version = version; + this.createdBy = createdBy; + this.status = SkillVersionStatus.DRAFT; + } + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + } + + // Getters + public Long getId() { + return id; + } + + public Long getSkillId() { + return skillId; + } + + public String getVersion() { + return version; + } + + public SkillVersionStatus getStatus() { + return status; + } + + public String getChangelog() { + return changelog; + } + + public String getParsedMetadataJson() { + return parsedMetadataJson; + } + + public String getManifestJson() { + return manifestJson; + } + + public Integer getFileCount() { + return fileCount; + } + + public Long getTotalSize() { + return totalSize; + } + + public LocalDateTime getPublishedAt() { + return publishedAt; + } + + public Long getCreatedBy() { + return createdBy; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + // Setters + public void setStatus(SkillVersionStatus status) { + this.status = status; + } + + public void setChangelog(String changelog) { + this.changelog = changelog; + } + + public void setParsedMetadataJson(String parsedMetadataJson) { + this.parsedMetadataJson = parsedMetadataJson; + } + + public void setManifestJson(String manifestJson) { + this.manifestJson = manifestJson; + } + + public void setFileCount(Integer fileCount) { + this.fileCount = fileCount; + } + + public void setTotalSize(Long totalSize) { + this.totalSize = totalSize; + } + + public void setPublishedAt(LocalDateTime publishedAt) { + this.publishedAt = publishedAt; + } +} diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersionRepository.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersionRepository.java new file mode 100644 index 00000000..427c1624 --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersionRepository.java @@ -0,0 +1,11 @@ +package com.iflytek.skillhub.domain.skill; + +import java.util.List; +import java.util.Optional; + +public interface SkillVersionRepository { + Optional findById(Long id); + Optional findBySkillIdAndVersion(Long skillId, String version); + List findBySkillIdAndStatus(Long skillId, SkillVersionStatus status); + SkillVersion save(SkillVersion version); +} diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersionStatus.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersionStatus.java new file mode 100644 index 00000000..57022259 --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVersionStatus.java @@ -0,0 +1,8 @@ +package com.iflytek.skillhub.domain.skill; + +public enum SkillVersionStatus { + DRAFT, + PENDING_REVIEW, + PUBLISHED, + REJECTED +} diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVisibility.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVisibility.java new file mode 100644 index 00000000..36004721 --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillVisibility.java @@ -0,0 +1,7 @@ +package com.iflytek.skillhub.domain.skill; + +public enum SkillVisibility { + PUBLIC, + NAMESPACE_ONLY, + PRIVATE +} diff --git a/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillFileJpaRepository.java b/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillFileJpaRepository.java new file mode 100644 index 00000000..db239fb4 --- /dev/null +++ b/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillFileJpaRepository.java @@ -0,0 +1,14 @@ +package com.iflytek.skillhub.infra.jpa; + +import com.iflytek.skillhub.domain.skill.SkillFile; +import com.iflytek.skillhub.domain.skill.SkillFileRepository; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface SkillFileJpaRepository extends JpaRepository, SkillFileRepository { + List findByVersionId(Long versionId); + void deleteByVersionId(Long versionId); +} diff --git a/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillJpaRepository.java b/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillJpaRepository.java new file mode 100644 index 00000000..8b093b82 --- /dev/null +++ b/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillJpaRepository.java @@ -0,0 +1,33 @@ +package com.iflytek.skillhub.infra.jpa; + +import com.iflytek.skillhub.domain.skill.Skill; +import com.iflytek.skillhub.domain.skill.SkillRepository; +import com.iflytek.skillhub.domain.skill.SkillStatus; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface SkillJpaRepository extends JpaRepository, SkillRepository { + Optional findByNamespaceIdAndSlug(Long namespaceId, String slug); + + @Override + default List findByNamespaceIdAndStatus(Long namespaceId, SkillStatus status) { + return findByNamespaceIdAndStatusOrderByCreatedAtDesc(namespaceId, status); + } + + List findByNamespaceIdAndStatusOrderByCreatedAtDesc(Long namespaceId, SkillStatus status); + Page findByNamespaceIdAndStatus(Long namespaceId, SkillStatus status, Pageable pageable); + List findByOwnerId(Long ownerId); + + @Modifying + @Query("UPDATE Skill s SET s.downloadCount = s.downloadCount + 1 WHERE s.id = :skillId") + void incrementDownloadCount(@Param("skillId") Long skillId); +} diff --git a/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillTagJpaRepository.java b/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillTagJpaRepository.java new file mode 100644 index 00000000..9a8aef3a --- /dev/null +++ b/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillTagJpaRepository.java @@ -0,0 +1,15 @@ +package com.iflytek.skillhub.infra.jpa; + +import com.iflytek.skillhub.domain.skill.SkillTag; +import com.iflytek.skillhub.domain.skill.SkillTagRepository; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface SkillTagJpaRepository extends JpaRepository, SkillTagRepository { + Optional findBySkillIdAndTagName(Long skillId, String tagName); + List findBySkillId(Long skillId); +} diff --git a/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillVersionJpaRepository.java b/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillVersionJpaRepository.java new file mode 100644 index 00000000..5af5f958 --- /dev/null +++ b/server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillVersionJpaRepository.java @@ -0,0 +1,25 @@ +package com.iflytek.skillhub.infra.jpa; + +import com.iflytek.skillhub.domain.skill.SkillVersion; +import com.iflytek.skillhub.domain.skill.SkillVersionRepository; +import com.iflytek.skillhub.domain.skill.SkillVersionStatus; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface SkillVersionJpaRepository extends JpaRepository, SkillVersionRepository { + Optional findBySkillIdAndVersion(Long skillId, String version); + + @Override + default List findBySkillIdAndStatus(Long skillId, SkillVersionStatus status) { + return findBySkillIdAndStatusOrderByCreatedAtDesc(skillId, status); + } + + List findBySkillIdAndStatusOrderByCreatedAtDesc(Long skillId, SkillVersionStatus status); + Page findBySkillIdAndStatus(Long skillId, SkillVersionStatus status, Pageable pageable); +}