Skip to content
cccskills
FreePublished on GitHub

crud-repository

PowerX CRUD Repository 规则(BaseRepository、tenant 约束、分页)。

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md9.3 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

PowerX CRUD Repository

步骤

  1. 打开 本文件内嵌规则。
  2. 按规则执行实现/校对。
  3. 完成后按核对清单验收。

核对点

  • 与 PowerX 当前代码结构、路径与命名一致。
  • 仅在传输层/契约层做职责内改动,不跨层越界。

规则(内嵌)

repository.yaml

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)
      }
    }

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

PowerX API 命名、前缀配置、版本迁移与插件网关路径规范(/admin、/internal 边界)。

The English summary is being prepared. Showing the original description.

ArtisanCloud/PowerX382Updated Oct 20, 2026

PowerX 底座 Capability 治理与发布准入规则。用于审计 REST/OpenAPI/gRPC/Gin 生成的能力候选、正式 platform_capabilities 目录、Capability Registry 登记、agent_usable/permission_code/risk_level 元数据、ignore 清单和缺失能力;当用户要求“重新识别能力”“能力是否能发布”“补齐能力登记”“检查 capability-gen/audit/check”“底座接口暴露给插件/agent”时使用。

The English summary is being prepared. Showing the original description.

ArtisanCloud/PowerX382Updated Oct 20, 2026

PowerX REST 契约规则(资源命名、分页、错误、版本化)。

The English summary is being prepared. Showing the original description.

ArtisanCloud/PowerX382Updated Oct 20, 2026

PowerX CRUD 依赖注入规则(Deps 单入口、构造注入、跨传输复用)。

The English summary is being prepared. Showing the original description.

ArtisanCloud/PowerX382Updated Oct 20, 2026

PowerX CRUD DTO 规则(输入输出分离、分页、校验)。

The English summary is being prepared. Showing the original description.

ArtisanCloud/PowerX382Updated Oct 20, 2026

PowerX CRUD gRPC 开发规范(proto、server、拦截器、错误映射)。

The English summary is being prepared. Showing the original description.

ArtisanCloud/PowerX382Updated Oct 20, 2026

See all skills by ArtisanCloud

Report a problem with this skill