mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-07 02:57:51 +00:00
feat(domain): add Skill domain entities, repositories, and JPA implementations
This commit is contained in:
parent
ca36304df9
commit
8644154157
16 changed files with 763 additions and 12 deletions
|
|
@ -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 滑动窗口限流。
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.iflytek.skillhub.domain.skill;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SkillFileRepository {
|
||||
List<SkillFile> findByVersionId(Long versionId);
|
||||
SkillFile save(SkillFile file);
|
||||
void saveAll(List<SkillFile> files);
|
||||
void deleteByVersionId(Long versionId);
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.iflytek.skillhub.domain.skill;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SkillRepository {
|
||||
Optional<Skill> findById(Long id);
|
||||
Optional<Skill> findByNamespaceIdAndSlug(Long namespaceId, String slug);
|
||||
List<Skill> findByNamespaceIdAndStatus(Long namespaceId, SkillStatus status);
|
||||
Skill save(Skill skill);
|
||||
List<Skill> findByOwnerId(Long ownerId);
|
||||
void incrementDownloadCount(Long skillId);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.iflytek.skillhub.domain.skill;
|
||||
|
||||
public enum SkillStatus {
|
||||
ACTIVE,
|
||||
HIDDEN,
|
||||
ARCHIVED
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.iflytek.skillhub.domain.skill;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SkillTagRepository {
|
||||
Optional<SkillTag> findBySkillIdAndTagName(Long skillId, String tagName);
|
||||
List<SkillTag> findBySkillId(Long skillId);
|
||||
SkillTag save(SkillTag tag);
|
||||
void delete(SkillTag tag);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.iflytek.skillhub.domain.skill;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SkillVersionRepository {
|
||||
Optional<SkillVersion> findById(Long id);
|
||||
Optional<SkillVersion> findBySkillIdAndVersion(Long skillId, String version);
|
||||
List<SkillVersion> findBySkillIdAndStatus(Long skillId, SkillVersionStatus status);
|
||||
SkillVersion save(SkillVersion version);
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.iflytek.skillhub.domain.skill;
|
||||
|
||||
public enum SkillVersionStatus {
|
||||
DRAFT,
|
||||
PENDING_REVIEW,
|
||||
PUBLISHED,
|
||||
REJECTED
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.iflytek.skillhub.domain.skill;
|
||||
|
||||
public enum SkillVisibility {
|
||||
PUBLIC,
|
||||
NAMESPACE_ONLY,
|
||||
PRIVATE
|
||||
}
|
||||
|
|
@ -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<SkillFile, Long>, SkillFileRepository {
|
||||
List<SkillFile> findByVersionId(Long versionId);
|
||||
void deleteByVersionId(Long versionId);
|
||||
}
|
||||
|
|
@ -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<Skill, Long>, SkillRepository {
|
||||
Optional<Skill> findByNamespaceIdAndSlug(Long namespaceId, String slug);
|
||||
|
||||
@Override
|
||||
default List<Skill> findByNamespaceIdAndStatus(Long namespaceId, SkillStatus status) {
|
||||
return findByNamespaceIdAndStatusOrderByCreatedAtDesc(namespaceId, status);
|
||||
}
|
||||
|
||||
List<Skill> findByNamespaceIdAndStatusOrderByCreatedAtDesc(Long namespaceId, SkillStatus status);
|
||||
Page<Skill> findByNamespaceIdAndStatus(Long namespaceId, SkillStatus status, Pageable pageable);
|
||||
List<Skill> findByOwnerId(Long ownerId);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE Skill s SET s.downloadCount = s.downloadCount + 1 WHERE s.id = :skillId")
|
||||
void incrementDownloadCount(@Param("skillId") Long skillId);
|
||||
}
|
||||
|
|
@ -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<SkillTag, Long>, SkillTagRepository {
|
||||
Optional<SkillTag> findBySkillIdAndTagName(Long skillId, String tagName);
|
||||
List<SkillTag> findBySkillId(Long skillId);
|
||||
}
|
||||
|
|
@ -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<SkillVersion, Long>, SkillVersionRepository {
|
||||
Optional<SkillVersion> findBySkillIdAndVersion(Long skillId, String version);
|
||||
|
||||
@Override
|
||||
default List<SkillVersion> findBySkillIdAndStatus(Long skillId, SkillVersionStatus status) {
|
||||
return findBySkillIdAndStatusOrderByCreatedAtDesc(skillId, status);
|
||||
}
|
||||
|
||||
List<SkillVersion> findBySkillIdAndStatusOrderByCreatedAtDesc(Long skillId, SkillVersionStatus status);
|
||||
Page<SkillVersion> findBySkillIdAndStatus(Long skillId, SkillVersionStatus status, Pageable pageable);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue