소스 정보
- 저장소
- ArtisanCloud/PowerX
- 최근 소스 활동
- 2026년 3월 25일 07:16
- 감지된 SKILL.md 언어
- 중국어
- 스타
- 364
- 포크
- 67
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/ArtisanCloud/PowerX --skill crud-repository명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
SOC 직업 분류 기준
| name | crud-repository |
| description | PowerX CRUD Repository 规则(BaseRepository、tenant 约束、分页)。 |
本文件内嵌规则。kind: ruleset
name: crud_repository
version: 1.0.0
owner: powerx
status: stable
meta:
intent: >
统一 Repository(持久化)层的接口与实现规范:只负责数据访问与映射,
不承载业务逻辑;严格多租户隔离、软删、分页筛选与一致的错误/返回语义。
CoreX 模块仓储统一采用 BaseRepository 泛型封装,便于事务注入与测试。
references:
- constitution.md
- crud_model.yaml
- crud_migration.yaml
- crud_dto.yaml
- crud_di.yaml
scope:
codebase:
repo_globs:
- "internal/repository/**/**_repo.go"
- "pkg/corex/db/persistence/repository/**/**_repo.go"
model_globs:
- "pkg/corex/db/persistence/model/**/**.go"
principles:
- 目录规范:CoreX 仓库实现放置于 `pkg/corex/db/persistence/repository/<domain>/`,目录名使用 snake_case,对应的模型位于同层级 `model/`。
- 职责单一:Repository 只做数据访问(SQL/GORM),禁止业务分支与权限判断。
- 多租户强约束:所有查询均带 TenantID 条件;默认忽略软删(GORM 默认生效)。
- 事务注入:方法接受 *gorm.DB(可为 tx),由 Service 层控制 tx 生命周期。
- 上下文贯穿:方法首参为 context.Context,用于 tracing/超时/审计钩子。
- 错误语义:不存在返回 gorm.ErrRecordNotFound(由 Service 翻译成 404);唯一冲突返回 ErrConflict(由 Service 翻译 409)。
- 可测试性:提供接口与实现;实现命名 `XxxRepoImpl`,禁止全局单例。
- 性能:列表查询必须显式选择字段/建立索引,禁止 N+1(注意 Preload 选择性)。
checks:
# 目录/文件命名
- id: repo.file.naming
level: error
when: { glob: "internal/repository/**/**_repo.go" }
assert:
- must_package_suffix: "repo"
# BaseRepository 形态
- id: repo.base_repository.pattern
level: error
when: { glob: "internal/repository/**/**_repo.go" }
assert:
- must_contain_regex: "type [A-Za-z0-9]+Repository struct {"
- must_contain_regex: "\\*repository.BaseRepository"
- must_contain_regex: "func New[A-Za-z0-9]+Repository\\(db \\*gorm\\.DB\\)"
- must_not_import: ["github.com/gin-gonic/gin"] # 不允许直接耦合 HTTP
# 方法签名(ctx + db)
- id: repo.method.signature
level: error
when: { glob: "internal/repository/**/**_repo.go" }
assert:
- must_contain_regex: "func \\(r \\*.*Repository\\) .*\\(ctx context\\.Context, db \\*gorm\\.DB"
- must_import: ["context","gorm.io/gorm"]
# 多租户条件
- id: repo.must.tenant.scope
level: error
when: { glob: "internal/repository/**/**_repo.go" }
assert:
- contains_any:
- "Where(\"tenant_id = ?\""
- "Scopes(TenantScope("
- "WithTenant("
message: "所有 CRUD 查询必须带 TenantID(直写 Where 或统一 Scopes/TenantScope)"
# 列表与分页(limit/offset 或 cursor)
- id: repo.list.pagination
level: error
when: { glob: "internal/repository/**/**_repo.go" }
assert:
- contains_any: ["Limit(","Offset(","Scopes(Paginate("]
# 冲突与不存在的错误
- id: repo.error.mapping
level: warn
when: { glob: "internal/repository/**/**_repo.go" }
assert:
- contains_any: ["gorm.ErrRecordNotFound","ErrDuplicate","unique"]
message: "建议把 gorm.ErrRecordNotFound 原样返回,由 Service 统一翻译为 404;唯一冲突包装为 ErrConflict"
# 软删(默认开启),如实现硬删需标注
- id: repo.soft_delete
level: warn
when: { glob: "internal/repository/**/**_repo.go" }
assert:
- should_contain_any: ["Delete(&m)","Unscoped().Delete("]
# 性能/索引使用(建议)
- id: repo.index.hint
level: warn
when: { glob: "internal/repository/**/**_repo.go" }
assert:
- should_contain_any: ["Select(","Omit("]
acceptance:
checklist:
- "[ ] 每个仓库文件包含接口 + 实现 + New 构造函数"
- "[ ] 方法签名统一为 (ctx context.Context, db *gorm.DB, ...)"
- "[ ] 所有查询带 TenantID 条件(Where/Scopes/TenantScope)"
- "[ ] 列表查询有分页(Limit/Offset 或 Cursor)"
- "[ ] 不做业务鉴权/分支;错误语义由 Service 统一翻译"
- "[ ] 使用软删;如需硬删必须在 Service 授权并清晰标注"
- "[ ] 可被 DI 装配(由 Deps 提供 *gorm.DB 注入)"
templates:
repo_interface_impl_go: |
// pkg/corex/db/persistence/repository/{{domain}}/{{resource}}_repo.go
package {{domain}}repo
import (
"context"
"gorm.io/gorm"
m "{{module_path}}/pkg/corex/db/persistence/model/{{domain}}"
)
// {{Entity}}Repo 数据访问接口
type {{Entity}}Repo interface {
Create(ctx context.Context, db *gorm.DB, tenantID uint64, in *m.{{Entity}}) error
GetByID(ctx context.Context, db *gorm.DB, tenantID uint64, id string) (*m.{{Entity}}, error)
List(ctx context.Context, db *gorm.DB, tenantID uint64, pg Page, filters map[string]string) ([]*m.{{Entity}}, int64, error)
Update(ctx context.Context, db *gorm.DB, tenantID uint64, in *m.{{Entity}}) error
Delete(ctx context.Context, db *gorm.DB, tenantID uint64, id string, hard bool) error
// Tx 透传辅助(可选)
WithTx(ctx context.Context, db *gorm.DB, fn func(tx *gorm.DB) error) error
}
// 实现
type {{Entity}}RepoImpl struct {
db *gorm.DB
}
func New{{Entity}}Repo(db *gorm.DB) {{Entity}}Repo {
return &{{Entity}}RepoImpl{db: db}
}
// 分页结构(按项目实际放置到共用包)
type Page struct {
Page int
PageSize int
SortBy string
SortOrder string
}
func (r *{{Entity}}RepoImpl) Create(ctx context.Context, db *gorm.DB, tenantID uint64, in *m.{{Entity}}) error {
in.TenantID = tenantID
return db.WithContext(ctx).Create(in).Error
}
func (r *{{Entity}}RepoImpl) GetByID(ctx context.Context, db *gorm.DB, tenantID uint64, id string) (*m.{{Entity}}, error) {
var out m.{{Entity}}
err := db.WithContext(ctx).
Where("tenant_id = ? AND id = ?", tenantID, id).
First(&out).Error
if err != nil {
return nil, err // 由 Service 翻译为 404
}
return &out, nil
}
// 可抽成 Scopes:TenantScope / Filters / Paginate
func (r *{{Entity}}RepoImpl) List(ctx context.Context, db *gorm.DB, tenantID uint64, pg Page, filters map[string]string) ([]*m.{{Entity}}, int64, error) {
q := db.WithContext(ctx).Model(&m.{{Entity}}{}).Where("tenant_id = ?", tenantID)
// 简化:按 filters 动态拼装(生产中建议固定白名单)
if v, ok := filters["status"]; ok { q = q.Where("status = ?", v) }
if v, ok := filters["q"]; ok { q = q.Where("name ILIKE ?", "%"+v+"%") }
// 计数
var total int64
if err := q.Count(&total).Error; err != nil { return nil, 0, err }
// 排序/分页
orderBy := "created_at desc"
if pg.SortBy != "" {
if pg.SortOrder == "" { pg.SortOrder = "desc" }
orderBy = pg.SortBy + " " + pg.SortOrder
}
if pg.Page <= 0 { pg.Page = 1 }
if pg.PageSize <= 0 { pg.PageSize = 20 }
var items []*m.{{Entity}}
err := q.Order(orderBy).
Limit(pg.PageSize).
Offset((pg.Page-1)*pg.PageSize).
Find(&items).Error
if err != nil { return nil, 0, err }
return items, total, nil
}
func (r *{{Entity}}RepoImpl) Update(ctx context.Context, db *gorm.DB, tenantID uint64, in *m.{{Entity}}) error {
return db.WithContext(ctx).
Model(&m.{{Entity}}{}).
Where("tenant_id = ? AND id = ?", tenantID, in.ID).
Updates(map[string]any{
"name": in.Name,
"meta": in.Meta,
"status": in.Status,
}).Error
}
func (r *{{Entity}}RepoImpl) Delete(ctx context.Context, db *gorm.DB, tenantID uint64, id string, hard bool) error {
q := db.WithContext(ctx).Where("tenant_id = ? AND id = ?", tenantID, id)
if hard {
return q.Unscoped().Delete(&m.{{Entity}}{}).Error
}
return q.Delete(&m.{{Entity}}{}).Error
}
func (r *{{Entity}}RepoImpl) WithTx(ctx context.Context, db *gorm.DB, fn func(tx *gorm.DB) error) error {
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { return fn(tx) })
}
scopes_go: |
// internal/repository/scopes/scopes.go (可选:集中放置)
package scopes
import "gorm.io/gorm"
func TenantScope(tenantID uint64) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("tenant_id = ?", tenantID)
}
}
func Paginate(page, pageSize int) func(*gorm.DB) *gorm.DB {
if page <= 0 { page = 1 }
if pageSize <= 0 { pageSize = 20 }
return func(db *gorm.DB) *gorm.DB {
return db.Limit(pageSize).Offset((page-1)*pageSize)
}
}