create-feature-module
GitHub用于从零开始生成完整后端功能模块及前端页面的脚手架工具,涵盖实体、服务、控制器、测试及迁移脚本的自动化创建。
Trigger Scenarios
Install
npx skills add OpenAEV-Platform/openaev --skill create-feature-module -g -y
SKILL.md
Frontmatter
{
"name": "create-feature-module",
"description": "Scaffolds a complete feature end-to-end: JPA entity, repository, service, DTOs, mapper, controller, migration, tests (fixture + composer + integration test), and frontend actions\/page. Use when asked to create a new feature or module."
}
Create Feature Module
Prerequisites
- Entity name (singular, e.g.
PlatformGroup) - Table name (plural snake_case, e.g.
platform_groups) - Tenancy scope: tenant-scoped, platform-level, or dual-scope
- Fields with types and constraints
Checkpoint: Scope Confirmation
Before writing any code, present the following to the user and wait for confirmation:
- Entity name and table name
- Tenancy scope: tenant-scoped, platform-level, or dual-scope
- Fields: name, type, constraints, nullable
- Files to create: list every file (entity, repository, service, DTOs, mapper, controller, migration, test fixtures, integration test, frontend actions/page)
- Instruction files read: confirm you have read the relevant
.github/instructions/files for all layers involved
Do not proceed until the user confirms the scope is correct.
Procedure
Step 1 — Create the JPA Entity
Location: openaev-model/src/main/java/io/openaev/database/model/
Follow Group.java (tenant-scoped) or Tenant.java (platform-level):
@ControlledUuidGenerationfor ID@Queryableon filterable fields@Transient @JsonIgnore ResourceTypefield- Collections initialized as mutable (
new ArrayList<>()) - Follow conventions from
database.instructions.md
If dual-scope (Settings, User, Role, Group pattern):
- Implement
DualScopeBaseinterface - Use
ModelBaseListeneronly (noTenantBaseListener) - Do NOT add
@Filter("tenantFilter") tenant_idmust be nullable:@JoinColumn(name = "tenant_id", nullable = true)@JsonIgnoreon the tenant relation
Step 2 — Create the Repository
Location: openaev-model/src/main/java/io/openaev/database/repository/
public interface {Entity}Repository extends JpaRepository<{Entity}, String>,
JpaSpecificationExecutor<{Entity}> {}
Step 3 — Add ResourceType + Capabilities
- Add value in
ResourceType.java - Add
ACCESS_,MANAGE_,DELETE_inCapability.javawith parent hierarchy
Checkpoint: Entity Layer Review
After completing Steps 1–3, present the following to the user and wait for confirmation:
- Entity class: field names, column names, annotations, tenant scope
- ResourceType and Capability additions: exact enum values and hierarchy
- Repository: confirm interface signature
Do not proceed to the service/API/frontend layers until the user confirms the entity layer is correct.
Step 4 — Create the Service
Location: openaev-api/src/main/java/io/openaev/service/
@Service @RequiredArgsConstructor @Transactional(rollbackFor = Exception.class)- CRUD + search with pagination
- JavaDoc on all public methods
If dual-scope — create TWO services:
Platform{Entity}Service— all queries usefindByTenantIsNull()variants, never receivestenantIdTenant{Entity}Service— all queries usefindByTenantId(tenantId)variants, receivestenantIdas argument- See
multi-tenancy.instructions.md→ Dual-Scope Entities for full pattern
Step 5 — Create DTOs + Mapper
Location: openaev-api/src/main/java/io/openaev/api/{feature}/
{Entity}Inputand{Entity}Outputas Javarecord{Entity}Mapperwith staticfromInput()+toOutput()
Step 6 — Create the Controller
Location: openaev-api/src/main/java/io/openaev/api/{feature}/
@AccessControl+@LogExecutionTime+@Operationon every endpoint- CRUD + search endpoints
- All new tenant-scoped APIs use
TENANT_PREFIX:@RequestMapping(TENANT_PREFIX + "/{entities}")→ resolves to/api/tenants/{tenantId}/{entities}
If dual-scope — create TWO controllers:
Platform{Entity}Apiat/api/platform-{entities}— usesPlatform{Entity}Service, platform-admin@AccessControlTenant{Entity}ApiatTENANT_PREFIX + "/{entities}"— tenant ID extracted from URL path, passed toTenant{Entity}Service
Step 7 — Create the Migration
Location: openaev-api/src/main/java/io/openaev/migration/
- Find next version number in existing migrations
CREATE TABLE, FK constraints, indexes
If dual-scope:
tenant_id VARCHAR(255)— nullable, FK totenants(tenant_id) ON DELETE CASCADE- Partial unique indexes:
CREATE UNIQUE INDEX uk_{table}_name_platform ON {table} ({field}) WHERE tenant_id IS NULL; CREATE UNIQUE INDEX uk_{table}_name_tenant ON {table} ({field}, tenant_id) WHERE tenant_id IS NOT NULL;
Step 8 — Create Test Fixtures + Composer
Location: openaev-api/src/test/java/io/openaev/utils/fixtures/
- Fixture:
createDefault{Entity}()with random names - Composer: extends
ComposerBase, innerComposerclass
If dual-scope:
- Fixture must support both:
createDefaultPlatform{Entity}()(tenant = null) andcreateDefaultTenant{Entity}(String tenantId)
Step 9 — Create Integration Test
Location: openaev-api/src/test/java/io/openaev/api/{feature}/
@Nested @DisplayNamegroups,@WithMockUser,assertThatJson
If dual-scope — add isolation tests:
given_platformEntity_should_notAppearInTenantListgiven_tenantEntity_should_notAppearInPlatformListgiven_tenantA_should_notSeeTenantBEntities- Test both
Platform{Entity}ApiandTenant{Entity}Apiindependently
Step 10 — Create Frontend Actions + Page
Follow templates and conventions from frontend.instructions.md.
Location: openaev-front/src/actions/{feature}/ and src/admin/components/
{feature}-action.ts— API calls (CRUD + search){feature}-helper.d.ts— TypeScript types (or use auto-generatedapi-types.d.ts){feature}-schema.ts— Zod validation schema- List page with
Queryable+DataTable - Create/Edit form with React Hook Form + Zod
- Permission guards with CASL (
ability.can(ACTIONS.MANAGE, SUBJECTS.X))
Step 11 — Verify
mvn spotless:apply
mvn test
cd openaev-front && yarn lint && yarn check-ts && yarn test
Version History
- 3.260818.1 Current 2026-08-20 12:00


