From 4edf83de94f849ac782705cb7c6b4415b11cd16e Mon Sep 17 00:00:00 2001 From: wangziqi Date: Thu, 30 Jul 2026 12:06:55 +0800 Subject: [PATCH] feat(education): complete Flyway migration and atomic submit --- .claude/skills/design/api-designer.yaml | 709 +++++++++ .claude/skills/design/crud-designer.yaml | 518 +++++++ .claude/skills/design/db-designer.yaml | 654 ++++++++ .claude/skills/design/entity-designer.yaml | 836 ++++++++++ .claude/skills/flyway-postgresql/SKILL.md | 115 ++ .claude/skills/index.yaml | 152 ++ .claude/skills/modules/ai/skill-ai.yaml | 706 +++++++++ .claude/skills/modules/bpm/skill-bpm.yaml | 719 +++++++++ .claude/skills/modules/crm/skill-crm.yaml | 861 +++++++++++ .claude/skills/modules/erp/skill-erp.yaml | 968 ++++++++++++ .claude/skills/modules/im/skill-im.yaml | 748 +++++++++ .claude/skills/modules/infra/skill-infra.yaml | 662 ++++++++ .claude/skills/modules/iot/skill-iot.yaml | 722 +++++++++ .claude/skills/modules/mall/skill-mall.yaml | 654 ++++++++ .../skills/modules/member/skill-member.yaml | 609 ++++++++ .claude/skills/modules/mes/skill-mes.yaml | 964 ++++++++++++ .claude/skills/modules/mp/skill-mp.yaml | 638 ++++++++ .claude/skills/modules/pay/skill-pay.yaml | 654 ++++++++ .../skills/modules/report/skill-report.yaml | 345 +++++ .../skills/modules/system/skill-system.yaml | 840 +++++++++++ .claude/skills/modules/wms/skill-wms.yaml | 832 ++++++++++ .claude/skills/patterns/factory-pattern.yaml | 91 ++ .claude/skills/patterns/index.yaml | 58 + .claude/skills/patterns/strategy-pattern.yaml | 136 ++ .../patterns/template-method-pattern.yaml | 119 ++ .claude/skills/templates/extraction-prompt.md | 139 ++ .claude/skills/templates/skill-template.yaml | 161 ++ .claude/skills/usage/entity-implementation.md | 1237 +++++++++++++++ .claude/skills/usage/extend-module.md | 1083 +++++++++++++ .claude/skills/usage/index.md | 363 +++++ .claude/skills/usage/new-module.md | 1212 +++++++++++++++ .claude/skills/usage/pattern-usage.md | 1341 +++++++++++++++++ .claude/skills/usage/quick-start.md | 594 ++++++++ .claude/skills/usage/refactor-module.md | 1307 ++++++++++++++++ .serena/.gitignore | 2 + .serena/memories/memory_maintenance.md | 33 + .serena/project.yml | 167 ++ CLAUDE.md | 84 ++ CONTEXT-MAP.md | 11 + docs/education/migration/00-current-state.md | 38 + .../migration/01-capability-matrix.md | 17 + docs/education/migration/02-api-mapping.md | 110 ++ .../migration/03-database-object-mapping.md | 145 ++ .../migration/04-module-reuse-map.md | 68 + .../migration/05-commit-review-11e9cc6.md | 34 + .../migration/06-commit-review-0f846fd.md | 37 + docs/education/migration/07-decisions.md | 67 + docs/education/migration/08-slice-roadmap.md | 165 ++ docs/education/migration/09-first-slice.md | 51 + .../migration/10-documentation-corrections.md | 15 + .../11-question-content-safety-contract.md | 174 +++ docs/education/migration/GOAL.md | 432 ++++++ .../issues/EDU-000-phase-0-inventory.md | 37 + .../issues/EDU-001-safe-question-content.md | 55 + .../EDU-002-practice-regression-baseline.md | 104 ++ .../EDU-003-tenant-resolution-decision.md | 160 ++ .../EDU-004-tenant-identity-security.md | 139 ++ .../EDU-005-flyway-takeover-decision.md | 160 ++ .../issues/EDU-006-practice-schema-flyway.md | 83 + .../issues/EDU-007-create-restore-practice.md | 57 + .../issues/EDU-008-idempotent-answer-save.md | 53 + .../issues/EDU-009-atomic-submit-report.md | 69 + .../issues/EDU-010-content-publication.md | 33 + .../issues/EDU-011-import-export-assets.md | 32 + .../issues/EDU-012-classes-relationships.md | 33 + .../issues/EDU-013-commercialization.md | 33 + .../issues/EDU-014-extended-learning.md | 37 + .../EDU-015-operational-independence.md | 42 + .../EDU-016-postgresql-persistence-tests.md | 64 + docs/education/migration/issues/README.md | 59 + docs/education/pilot-acceptance-runbook.md | 12 +- docs/education/scalar-contract-from-source.md | 225 +++ script/docker/Docker-HOWTO.md | 2 +- script/docker/docker-compose.yml | 28 +- script/docker/docker.env | 13 +- .../008-education-catalog-rollback.sql | 21 + sql/mysql/education/008-education-catalog.sql | 349 +++++ sql/mysql/member-init.sql | 174 +++ .../education/000-education-rollback.sql | 8 + .../education/000-education-schema.sql | 7 + .../education/000-education-seed.sql | 15 + .../001-education-tenant-rollback.sql | 1 + .../education/001-education-tenant-seed.sql | 3 + ...02-education-practice-session-rollback.sql | 32 + .../002-education-practice-session.sql | 105 ++ ...-education-answer-idempotency-rollback.sql | 35 + .../003-education-answer-idempotency.sql | 113 ++ .../004-education-submit-report-rollback.sql | 50 + .../education/004-education-submit-report.sql | 220 +++ .../005-education-wrong-question-rollback.sql | 25 + .../005-education-wrong-question.sql | 156 ++ .../007-education-favorite-rollback.sql | 25 + .../education/007-education-favorite.sql | 95 ++ .../008-education-catalog-rollback.sql | 17 + .../education/008-education-catalog.sql | 530 +++++++ ...education-idempotency-unified-rollback.sql | 13 + .../009-education-idempotency-unified.sql | 103 ++ .../biz/system/tenant/TenantCommonApi.java | 12 +- .../security/TenantSecurityWebFilterTest.java | 95 ++ .../yudao-spring-boot-starter-mybatis/pom.xml | 5 - .../desensitize/core/DesensitizeTest.java | 2 +- yudao-module-education/CONTEXT.md | 61 + yudao-module-education/README.md | 42 +- yudao-module-education/pom.xml | 15 + .../education/config/EducationProperties.java | 31 +- .../app/EducationContextController.java | 9 +- .../app/catalog/CatalogController.java | 47 +- .../app/catalog/vo/CatalogMajorRespVO.java | 21 + .../app/catalog/vo/CatalogSchoolRespVO.java | 21 + .../practice/PracticeSessionController.java | 25 +- .../app/tenant/EducationTenantController.java | 347 +++-- .../app/tenant/vo/EducationTenantRespVO.java | 15 +- .../dal/dataobject/AnswerIdempotencyDO.java | 55 - .../dal/dataobject/EducationFavoriteDO.java | 8 +- .../dal/dataobject/IdempotencyDO.java | 74 + .../dal/dataobject/PracticeQuestionDO.java | 6 +- .../dal/dataobject/PracticeReportDO.java | 6 +- .../dataobject/PracticeReportDetailDO.java | 6 +- .../dal/dataobject/PracticeSessionDO.java | 6 +- .../dal/dataobject/SubmitIdempotencyDO.java | 52 - .../dal/dataobject/WrongQuestionDO.java | 6 +- .../WrongQuestionIdempotencyDO.java | 6 +- .../dataobject/catalog/CatalogScopeDO.java | 21 + .../dal/dataobject/catalog/CategoryDO.java | 26 + .../dataobject/catalog/ContentEntryDO.java | 34 + .../dal/dataobject/catalog/ContentNodeDO.java | 34 + .../dal/dataobject/catalog/MajorDO.java | 29 + .../catalog/PracticeBlueprintDO.java | 35 + .../catalog/QuestionCollectionDO.java | 33 + .../catalog/QuestionCollectionQuestionDO.java | 33 + .../dal/dataobject/catalog/QuestionDO.java | 79 + .../dal/dataobject/catalog/RegionDO.java | 57 + .../dal/dataobject/catalog/SchoolDO.java | 29 + .../dal/dataobject/catalog/SubjectDO.java | 29 + .../dal/mysql/AnswerIdempotencyMapper.java | 43 - .../dal/mysql/EducationFavoriteMapper.java | 20 +- .../dal/mysql/IdempotencyStoreMapper.java | 121 ++ .../dal/mysql/PracticeReportMapper.java | 10 +- .../dal/mysql/PracticeSessionMapper.java | 30 +- .../dal/mysql/SubmitIdempotencyMapper.java | 43 - .../mysql/WrongQuestionIdempotencyMapper.java | 11 +- .../dal/mysql/WrongQuestionMapper.java | 35 +- .../dal/mysql/catalog/CatalogScopeQuery.java | 22 + .../dal/mysql/catalog/CategoryMapper.java | 19 + .../dal/mysql/catalog/ContentEntryMapper.java | 20 + .../dal/mysql/catalog/ContentNodeMapper.java | 29 + .../dal/mysql/catalog/MajorMapper.java | 20 + .../catalog/PracticeBlueprintMapper.java | 21 + .../catalog/QuestionCollectionMapper.java | 22 + .../QuestionCollectionQuestionMapper.java | 19 + .../dal/mysql/catalog/QuestionMapper.java | 80 + .../dal/mysql/catalog/RegionMapper.java | 16 + .../dal/mysql/catalog/SchoolMapper.java | 19 + .../dal/mysql/catalog/SubjectMapper.java | 23 + .../education/enums/CatalogProviderMode.java | 2 +- .../education/enums/ErrorCodeConstants.java | 10 +- .../config/ScalarAutoConfiguration.java | 13 +- .../service/catalog/CatalogProvider.java | 6 + .../service/catalog/CatalogService.java | 4 + .../service/catalog/CatalogServiceImpl.java | 49 +- .../catalog/ScalarCatalogProvider.java | 11 + .../UnsupportedModeCatalogProvider.java | 10 + .../catalog/provider/JavaCatalogProvider.java | 441 ++++++ .../practice/PracticeSessionServiceImpl.java | 496 +++--- .../service/practice/ScoringService.java | 49 + .../service/practice/ScoringServiceImpl.java | 205 +++ .../practice/SessionResponseAssembler.java | 74 + .../service/practice/dto/ScoreResult.java | 50 + .../question/QuestionCatalogServiceImpl.java | 50 +- .../question/QuestionContentSafety.java | 163 ++ .../wrong/WrongQuestionServiceImpl.java | 62 +- .../V4010__initialize_education_flyway.sql | 7 + .../V4020__create_native_catalog.sql | 401 +++++ ...4030__create_and_adopt_practice_schema.sql | 398 +++++ .../V4040__add_submit_claim_lease.sql | 51 + .../config/EducationPropertiesTest.java | 11 + .../EducationContextControllerHttpTest.java | 91 ++ .../catalog/CatalogControllerHttpTest.java | 36 +- .../app/catalog/CatalogControllerTest.java | 43 +- .../PracticeAnswerControllerHttpTest.java | 23 + .../PracticeSessionControllerHttpTest.java | 26 + ...acticeSessionControllerSubmitHttpTest.java | 5 + .../EducationTenantControllerHttpTest.java | 334 ++++ .../tenant/EducationTenantControllerTest.java | 87 -- ...EducationTenantResolveIntegrationTest.java | 197 --- .../dal/mysql/PracticeSessionMapperTest.java | 11 +- .../config/ScalarAutoConfigurationTest.java | 50 +- .../catalog/CatalogServiceImplTest.java | 8 +- .../favorite/FavoriteServiceImplTest.java | 6 +- .../PracticeAnswerServiceImplTest.java | 163 +- .../PracticeSessionServiceImplTest.java | 128 +- ...ssionServicePostgreSqlIntegrationTest.java | 152 ++ ...acticeSubmitProjectionIntegrationTest.java | 9 +- .../PracticeSubmitServiceImplTest.java | 149 +- .../QuestionCatalogServiceImplTest.java | 38 +- .../question/QuestionContentSafetyTest.java | 123 ++ .../wrong/WrongQuestionServiceImplTest.java | 37 +- ...ucationFlywayMigrationIntegrationTest.java | 337 +++++ .../test/PostgreSqlDbIntegrationTest.java | 116 ++ ...application-education-postgresql-test.yaml | 28 + .../src/test/resources/sql/clean.sql | 1 + .../src/test/resources/sql/create_tables.sql | 25 + .../test/resources/sql/postgresql/clean.sql | 12 + .../statistics/ImStatisticsManagerMapper.java | 42 +- .../infra/dal/mysql/job/JobLogMapper.java | 3 +- .../dal/mysql/logger/ApiAccessLogMapper.java | 3 +- .../dal/mysql/logger/ApiErrorLogMapper.java | 3 +- .../codegen/CodegenServiceImplTest.java | 2 +- .../service/config/ConfigServiceImplTest.java | 2 +- .../mapper/member/MemberStatisticsMapper.xml | 2 +- .../mapper/pay/PayWalletStatisticsMapper.xml | 2 +- .../product/ProductStatisticsMapper.xml | 10 +- .../trade/TradeOrderStatisticsMapper.xml | 6 +- .../mapper/trade/TradeStatisticsMapper.xml | 4 +- .../system/dal/mysql/dict/DictTypeMapper.java | 2 +- .../mysql/oauth2/OAuth2AccessTokenMapper.java | 3 +- .../oauth2/OAuth2RefreshTokenMapper.java | 3 +- .../system/api/tenant/TenantApiImplTest.java | 86 ++ .../service/dict/DictDataServiceImplTest.java | 2 +- .../service/dict/DictTypeServiceImplTest.java | 2 +- .../notify/NotifyTemplateServiceImplTest.java | 2 +- .../oauth2/OAuth2ApproveServiceImplTest.java | 10 +- .../oauth2/OAuth2TokenServiceImplTest.java | 3 +- .../permission/MenuServiceImplTest.java | 2 +- .../social/SocialClientServiceImplTest.java | 2 +- .../social/SocialUserServiceImplTest.java | 2 +- .../service/tenant/TenantServiceImplTest.java | 2 +- .../user/AdminUserServiceImplTest.java | 2 +- yudao-server/pom.xml | 10 + .../src/main/resources/application-dev.yaml | 23 +- .../src/main/resources/application-local.yaml | 37 +- 231 files changed, 32753 insertions(+), 1376 deletions(-) create mode 100644 .claude/skills/design/api-designer.yaml create mode 100644 .claude/skills/design/crud-designer.yaml create mode 100644 .claude/skills/design/db-designer.yaml create mode 100644 .claude/skills/design/entity-designer.yaml create mode 100644 .claude/skills/flyway-postgresql/SKILL.md create mode 100644 .claude/skills/index.yaml create mode 100644 .claude/skills/modules/ai/skill-ai.yaml create mode 100644 .claude/skills/modules/bpm/skill-bpm.yaml create mode 100644 .claude/skills/modules/crm/skill-crm.yaml create mode 100644 .claude/skills/modules/erp/skill-erp.yaml create mode 100644 .claude/skills/modules/im/skill-im.yaml create mode 100644 .claude/skills/modules/infra/skill-infra.yaml create mode 100644 .claude/skills/modules/iot/skill-iot.yaml create mode 100644 .claude/skills/modules/mall/skill-mall.yaml create mode 100644 .claude/skills/modules/member/skill-member.yaml create mode 100644 .claude/skills/modules/mes/skill-mes.yaml create mode 100644 .claude/skills/modules/mp/skill-mp.yaml create mode 100644 .claude/skills/modules/pay/skill-pay.yaml create mode 100644 .claude/skills/modules/report/skill-report.yaml create mode 100644 .claude/skills/modules/system/skill-system.yaml create mode 100644 .claude/skills/modules/wms/skill-wms.yaml create mode 100644 .claude/skills/patterns/factory-pattern.yaml create mode 100644 .claude/skills/patterns/index.yaml create mode 100644 .claude/skills/patterns/strategy-pattern.yaml create mode 100644 .claude/skills/patterns/template-method-pattern.yaml create mode 100644 .claude/skills/templates/extraction-prompt.md create mode 100644 .claude/skills/templates/skill-template.yaml create mode 100644 .claude/skills/usage/entity-implementation.md create mode 100644 .claude/skills/usage/extend-module.md create mode 100644 .claude/skills/usage/index.md create mode 100644 .claude/skills/usage/new-module.md create mode 100644 .claude/skills/usage/pattern-usage.md create mode 100644 .claude/skills/usage/quick-start.md create mode 100644 .claude/skills/usage/refactor-module.md create mode 100644 .serena/.gitignore create mode 100644 .serena/memories/memory_maintenance.md create mode 100644 .serena/project.yml create mode 100644 CLAUDE.md create mode 100644 CONTEXT-MAP.md create mode 100644 docs/education/migration/00-current-state.md create mode 100644 docs/education/migration/01-capability-matrix.md create mode 100644 docs/education/migration/02-api-mapping.md create mode 100644 docs/education/migration/03-database-object-mapping.md create mode 100644 docs/education/migration/04-module-reuse-map.md create mode 100644 docs/education/migration/05-commit-review-11e9cc6.md create mode 100644 docs/education/migration/06-commit-review-0f846fd.md create mode 100644 docs/education/migration/07-decisions.md create mode 100644 docs/education/migration/08-slice-roadmap.md create mode 100644 docs/education/migration/09-first-slice.md create mode 100644 docs/education/migration/10-documentation-corrections.md create mode 100644 docs/education/migration/11-question-content-safety-contract.md create mode 100644 docs/education/migration/GOAL.md create mode 100644 docs/education/migration/issues/EDU-000-phase-0-inventory.md create mode 100644 docs/education/migration/issues/EDU-001-safe-question-content.md create mode 100644 docs/education/migration/issues/EDU-002-practice-regression-baseline.md create mode 100644 docs/education/migration/issues/EDU-003-tenant-resolution-decision.md create mode 100644 docs/education/migration/issues/EDU-004-tenant-identity-security.md create mode 100644 docs/education/migration/issues/EDU-005-flyway-takeover-decision.md create mode 100644 docs/education/migration/issues/EDU-006-practice-schema-flyway.md create mode 100644 docs/education/migration/issues/EDU-007-create-restore-practice.md create mode 100644 docs/education/migration/issues/EDU-008-idempotent-answer-save.md create mode 100644 docs/education/migration/issues/EDU-009-atomic-submit-report.md create mode 100644 docs/education/migration/issues/EDU-010-content-publication.md create mode 100644 docs/education/migration/issues/EDU-011-import-export-assets.md create mode 100644 docs/education/migration/issues/EDU-012-classes-relationships.md create mode 100644 docs/education/migration/issues/EDU-013-commercialization.md create mode 100644 docs/education/migration/issues/EDU-014-extended-learning.md create mode 100644 docs/education/migration/issues/EDU-015-operational-independence.md create mode 100644 docs/education/migration/issues/EDU-016-postgresql-persistence-tests.md create mode 100644 docs/education/migration/issues/README.md create mode 100644 docs/education/scalar-contract-from-source.md create mode 100644 sql/mysql/education/008-education-catalog-rollback.sql create mode 100644 sql/mysql/education/008-education-catalog.sql create mode 100644 sql/mysql/member-init.sql create mode 100644 sql/postgresql/education/000-education-rollback.sql create mode 100644 sql/postgresql/education/000-education-schema.sql create mode 100644 sql/postgresql/education/000-education-seed.sql create mode 100644 sql/postgresql/education/001-education-tenant-rollback.sql create mode 100644 sql/postgresql/education/001-education-tenant-seed.sql create mode 100644 sql/postgresql/education/002-education-practice-session-rollback.sql create mode 100644 sql/postgresql/education/002-education-practice-session.sql create mode 100644 sql/postgresql/education/003-education-answer-idempotency-rollback.sql create mode 100644 sql/postgresql/education/003-education-answer-idempotency.sql create mode 100644 sql/postgresql/education/004-education-submit-report-rollback.sql create mode 100644 sql/postgresql/education/004-education-submit-report.sql create mode 100644 sql/postgresql/education/005-education-wrong-question-rollback.sql create mode 100644 sql/postgresql/education/005-education-wrong-question.sql create mode 100644 sql/postgresql/education/007-education-favorite-rollback.sql create mode 100644 sql/postgresql/education/007-education-favorite.sql create mode 100644 sql/postgresql/education/008-education-catalog-rollback.sql create mode 100644 sql/postgresql/education/008-education-catalog.sql create mode 100644 sql/postgresql/education/009-education-idempotency-unified-rollback.sql create mode 100644 sql/postgresql/education/009-education-idempotency-unified.sql create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-tenant/src/test/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilterTest.java create mode 100644 yudao-module-education/CONTEXT.md create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/controller/app/catalog/vo/CatalogMajorRespVO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/controller/app/catalog/vo/CatalogSchoolRespVO.java delete mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/AnswerIdempotencyDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/IdempotencyDO.java delete mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/SubmitIdempotencyDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/CatalogScopeDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/CategoryDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/ContentEntryDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/ContentNodeDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/MajorDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/PracticeBlueprintDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/QuestionCollectionDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/QuestionCollectionQuestionDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/QuestionDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/RegionDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/SchoolDO.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/dataobject/catalog/SubjectDO.java delete mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/AnswerIdempotencyMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/IdempotencyStoreMapper.java delete mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/SubmitIdempotencyMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/CatalogScopeQuery.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/CategoryMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/ContentEntryMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/ContentNodeMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/MajorMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/PracticeBlueprintMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/QuestionCollectionMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/QuestionCollectionQuestionMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/QuestionMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/RegionMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/SchoolMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/SubjectMapper.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/catalog/provider/JavaCatalogProvider.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/practice/ScoringService.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/practice/ScoringServiceImpl.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/practice/SessionResponseAssembler.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/practice/dto/ScoreResult.java create mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/question/QuestionContentSafety.java create mode 100644 yudao-module-education/src/main/resources/db/migration/education/V4010__initialize_education_flyway.sql create mode 100644 yudao-module-education/src/main/resources/db/migration/education/V4020__create_native_catalog.sql create mode 100644 yudao-module-education/src/main/resources/db/migration/education/V4030__create_and_adopt_practice_schema.sql create mode 100644 yudao-module-education/src/main/resources/db/migration/education/V4040__add_submit_claim_lease.sql create mode 100644 yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/controller/app/EducationContextControllerHttpTest.java create mode 100644 yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/controller/app/tenant/EducationTenantControllerHttpTest.java delete mode 100644 yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/controller/app/tenant/EducationTenantControllerTest.java delete mode 100644 yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/controller/app/tenant/EducationTenantResolveIntegrationTest.java create mode 100644 yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/practice/PracticeSessionServicePostgreSqlIntegrationTest.java create mode 100644 yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/question/QuestionContentSafetyTest.java create mode 100644 yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/test/EducationFlywayMigrationIntegrationTest.java create mode 100644 yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/test/PostgreSqlDbIntegrationTest.java create mode 100644 yudao-module-education/src/test/resources/application-education-postgresql-test.yaml create mode 100644 yudao-module-education/src/test/resources/sql/postgresql/clean.sql create mode 100644 yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/api/tenant/TenantApiImplTest.java diff --git a/.claude/skills/design/api-designer.yaml b/.claude/skills/design/api-designer.yaml new file mode 100644 index 00000000..e4637f6a --- /dev/null +++ b/.claude/skills/design/api-designer.yaml @@ -0,0 +1,709 @@ +# API 设计规范知识库 + +convention: + id: "api-designer" + name: "API 设计规范" + category: "设计规范" + description: "定义项目 REST API 的设计规范,包括接口命名、请求响应格式、权限控制、文档注解等,确保 API 设计的统一性和规范性" + +# ============================================ +# 第一部分:设计原则 +# ============================================ +philosophy: + design_principles: + - principle: "操作式 URL 设计" + description: "使用动词表达操作意图,而非 RESTful 资源式命名" + example: "/system/user/create 而非 POST /users" + - principle: "统一响应格式" + description: "所有接口使用 CommonResult 包装,确保响应格式一致" + - principle: "权限分级控制" + description: "通过 @PermitAll 和 @PreAuthorize 实现公开接口与权限接口的分离" + - principle: "文档即代码" + description: "使用 OpenAPI 3.0 注解,接口文档与代码同步维护" + +# ============================================ +# 第二部分:接口规范 +# ============================================ +interface_conventions: + # URL 命名规范 + url_naming: + format: "/{模块}/{功能}/{操作}" + rules: + - "模块名与功能名使用小写字母" + - "操作名使用动词或动词短语" + - "多词使用连字符分隔(如 update-status)" + examples: + - path: "/system/user/create" + description: "创建用户" + - path: "/system/user/page" + description: "用户分页列表" + - path: "/pay/order/get" + description: "获取订单详情" + - path: "/system/user/update-status" + description: "更新用户状态" + + # HTTP 方法映射 + http_methods: + - method: "POST" + operations: ["create", "import", "send", "login", "logout"] + description: "创建类操作" + idempotent: false + - method: "PUT" + operations: ["update", "update-status", "update-password"] + description: "更新类操作" + idempotent: true + - method: "DELETE" + operations: ["delete", "delete-list"] + description: "删除类操作" + idempotent: true + - method: "GET" + operations: ["get", "page", "list", "export-excel", "get-import-template"] + description: "查询类操作" + idempotent: true + safe: true + + # 标准端点定义 + standard_endpoints: + - endpoint: "/create" + method: "POST" + purpose: "创建单个实体" + request: "@RequestBody @Valid XxxSaveReqVO" + response: "CommonResult" + permission: "{模块}:{功能}:create" + + - endpoint: "/update" + method: "PUT" + purpose: "更新单个实体" + request: "@RequestBody @Valid XxxSaveReqVO" + response: "CommonResult" + permission: "{模块}:{功能}:update" + + - endpoint: "/delete" + method: "DELETE" + purpose: "删除单个实体" + request: "@RequestParam(\"id\") Long id" + response: "CommonResult" + permission: "{模块}:{功能}:delete" + parameter_annotation: "@Parameter(name = \"id\", description = \"编号\", required = true)" + + - endpoint: "/delete-list" + method: "DELETE" + purpose: "批量删除实体" + request: "@RequestParam(\"ids\") List ids" + response: "CommonResult" + permission: "{模块}:{功能}:delete" + parameter_annotation: "@Parameter(name = \"ids\", description = \"编号列表\", required = true)" + + - endpoint: "/get" + method: "GET" + purpose: "获取单个实体详情" + request: "@RequestParam(\"id\") Long id" + response: "CommonResult" + permission: "{模块}:{功能}:query" + parameter_annotation: "@Parameter(name = \"id\", description = \"编号\", required = true)" + + - endpoint: "/page" + method: "GET" + purpose: "分页查询列表" + request: "@Valid XxxPageReqVO" + response: "CommonResult>" + permission: "{模块}:{功能}:query" + + - endpoint: "/list" + method: "GET" + purpose: "列表查询(不分页)" + request: "@RequestParam(\"ids\") List ids 或无参数" + response: "CommonResult>" + permission: "{模块}:{功能}:query" + + - endpoint: "/export-excel" + method: "GET" + purpose: "导出 Excel" + request: "@Valid XxxPageReqVO" + response: "void(直接写入 HttpServletResponse)" + permission: "{模块}:{功能}:export" + special_annotation: "@ApiAccessLog(operateType = EXPORT)" + + - endpoint: "/get-import-template" + method: "GET" + purpose: "获取导入模板" + request: "无参数" + response: "void(直接写入 HttpServletResponse)" + permission: "通常无需权限或 {模块}:{功能}:import" + + - endpoint: "/import" + method: "POST" + purpose: "导入 Excel" + request: "@RequestParam(\"file\") MultipartFile file, @RequestParam(\"updateSupport\") Boolean updateSupport" + response: "CommonResult" + permission: "{模块}:{功能}:import" + parameter_annotation: "@Parameters({...})" + +# ============================================ +# 第三部分:注解规范 +# ============================================ +annotation_standards: + # Controller 类注解 + class_annotations: + - annotation: "@Tag" + format: "@Tag(name = \"管理后台 - {模块名}\")" + purpose: "OpenAPI 3.0 文档分组" + import: "io.swagger.v3.oas.annotations.tags.Tag" + - annotation: "@RestController" + purpose: "REST 控制器" + import: "org.springframework.web.bind.annotation.RestController" + - annotation: "@RequestMapping" + format: "@RequestMapping(\"/{模块}/{功能}\")" + purpose: "路由前缀" + import: "org.springframework.web.bind.annotation.RequestMapping" + - annotation: "@Validated" + purpose: "参数校验支持" + import: "org.springframework.validation.annotation.Validated" + + # 方法注解 + method_annotations: + - annotation: "@Operation" + format: "@Operation(summary = \"{操作描述}\")" + purpose: "接口文档说明" + import: "io.swagger.v3.oas.annotations.Operation" + examples: + - "@Operation(summary = \"创建用户\")" + - "@Operation(summary = \"获得用户分页列表\")" + - "@Operation(summary = \"导出用户 Excel\")" + + - annotation: "@PermitAll" + purpose: "公开接口(无需登录)" + import: "cn.iocoder.yudao.framework.security.core.annotations.PermitAll" + when_to_use: "登录、登出、注册、验证码等无需认证的接口" + + - annotation: "@PreAuthorize" + format: "@PreAuthorize(\"@ss.hasPermission('{模块}:{功能}:{操作}')\")" + purpose: "权限控制" + import: "org.springframework.security.access.prepost.PreAuthorize" + examples: + - "@PreAuthorize(\"@ss.hasPermission('system:user:create')\")" + - "@PreAuthorize(\"@ss.hasPermission('pay:order:query')\")" + + - annotation: "@Parameter" + format: "@Parameter(name = \"{参数名}\", description = \"{描述}\", required = {是否必填}, example = \"{示例}\")" + purpose: "单参数文档" + import: "io.swagger.v3.oas.annotations.Parameter" + when_to_use: "单个 @RequestParam 参数时使用" + + - annotation: "@Parameters" + format: "@Parameters({ @Parameter(name = \"xxx\", ...), @Parameter(name = \"yyy\", ...) })" + purpose: "多参数文档" + import: "io.swagger.v3.oas.annotations.Parameters" + when_to_use: "多个 @RequestParam 参数时使用" + + - annotation: "@ApiAccessLog" + format: "@ApiAccessLog(operateType = EXPORT)" + purpose: "操作日志记录" + import: "cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog" + when_to_use: "导出、导入等需要记录日志的操作" + constants_import: "static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT" + + # 参数注解 + parameter_annotations: + - annotation: "@RequestBody" + purpose: "接收 JSON 请求体" + import: "org.springframework.web.bind.annotation.RequestBody" + usage: "@RequestBody @Valid XxxReqVO reqVO" + - annotation: "@RequestParam" + purpose: "接收 URL 查询参数" + import: "org.springframework.web.bind.annotation.RequestParam" + usage: "@RequestParam(\"id\") Long id" + - annotation: "@Valid" + purpose: "参数校验" + import: "javax.validation.Valid" + usage: "@Valid @RequestBody XxxReqVO reqVO" + +# ============================================ +# 第四部分:请求响应规范 +# ============================================ +request_response: + # 统一响应结构 + common_result: + class: "cn.iocoder.yudao.framework.common.pojo.CommonResult" + structure: + code: "Integer(0 表示成功)" + msg: "String(错误提示信息)" + data: "T(返回数据)" + usage: + success: "return success(data)" + success_boolean: "return success(true)" + success_id: "return success(id)" + import: "static cn.iocoder.yudao.framework.common.pojo.CommonResult.success" + + # 分页响应结构 + page_result: + class: "cn.iocoder.yudao.framework.common.pojo.PageResult" + structure: + total: "Long(总记录数)" + list: "List(当前页数据列表)" + usage: "CommonResult> getXxxPage(...)" + empty: "new PageResult<>(pageResult.getTotal())" + + # 分页请求参数 + page_param: + class: "cn.iocoder.yudao.framework.common.pojo.PageParam" + fields: + pageNo: "Integer(页码,从 1 开始,默认 1,最小值 1)" + pageSize: "Integer(每页条数,默认 10,范围 1-200)" + constants: + PAGE_NO: "1" + PAGE_SIZE: "10" + PAGE_SIZE_NONE: "-1(不分页,用于导出等场景)" + usage: "分页查询 VO 继承 PageParam" + + # 请求 VO 类型 + request_vo_types: + - type: "SaveReqVO" + purpose: "新增/修改请求" + naming: "XxxSaveReqVO" + features: + - "修改时包含 id 字段" + - "包含 @NotBlank/@NotNull 校验注解" + - "使用 @Schema 注解描述字段" + - type: "PageReqVO" + purpose: "分页查询请求" + naming: "XxxPageReqVO" + features: + - "继承 PageParam" + - "包含查询条件字段" + - "时间字段使用 @DateTimeFormat" + - type: "ImportReqVO" + purpose: "导入请求" + naming: "XxxImportReqVO 或使用 @RequestParam 接收 file" + features: + - "包含 file 和 updateSupport 参数" + - type: "RespVO" + purpose: "响应对象" + naming: "XxxRespVO" + features: + - "包含 @Schema 注解描述字段" + - "包含 createTime 等只读字段" + + # VO 注解规范 + vo_annotations: + - annotation: "@Schema" + purpose: "OpenAPI 3.0 字段描述" + format: "@Schema(description = \"{字段描述}\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"{示例}\")" + import: "io.swagger.v3.oas.annotations.media.Schema" + - annotation: "@ExcelProperty" + purpose: "Excel 导出字段" + format: "@ExcelProperty(\"{列名}\")" + import: "com.alibaba.excel.annotation.ExcelProperty" + - annotation: "@DictFormat" + purpose: "字典转换" + format: "@DictFormat(\"{字典类型}\")" + +# ============================================ +# 第五部分:权限标识规范 +# ============================================ +permission_naming: + format: "{模块}:{功能}:{操作}" + rules: + - "模块名与功能名使用小写字母" + - "操作名使用标准动词" + standard_operations: + - operation: "query" + description: "查询权限(包括 get、page、list)" + - operation: "create" + description: "新增权限" + - operation: "update" + description: "修改权限" + - operation: "delete" + description: "删除权限" + - operation: "export" + description: "导出权限" + - operation: "import" + description: "导入权限" + examples: + - "system:user:query" + - "system:user:create" + - "system:user:update" + - "system:user:delete" + - "system:user:export" + - "system:user:import" + - "pay:order:query" + - "pay:order:create" + + # 权限 SQL 配置 + permission_sql: + menu_format: | + INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, status) + VALUES ('{功能名称}管理', '', 2, 0, {父菜单ID}, '{功能}', 'ep:document', '{模块}/{功能}/index', 0); + + button_format: | + INSERT INTO system_menu (name, permission, type, sort, parent_id, status) VALUES + ('{功能名称}查询', '{模块}:{功能}:query', 3, 1, @menuId, 0), + ('{功能名称}新增', '{模块}:{功能}:create', 3, 2, @menuId, 0), + ('{功能名称}修改', '{模块}:{功能}:update', 3, 3, @menuId, 0), + ('{功能名称}删除', '{模块}:{功能}:delete', 3, 4, @menuId, 0), + ('{功能名称}导出', '{模块}:{功能}:export', 3, 5, @menuId, 0); + +# ============================================ +# 第六部分:错误码体系 +# ============================================ +error_codes: + format: "1_模块编号_功能编号_错误序号" + success_code: 0 + + # 全局错误码 + global_errors: + - code: 0 + message: "成功" + - code: 400 + message: "请求参数不正确" + - code: 401 + message: "账号未登录" + - code: 403 + message: "没有该操作权限" + - code: 404 + message: "请求未找到" + - code: 405 + message: "请求方法不正确" + - code: 423 + message: "请求失败,请稍后重试" + - code: 429 + message: "请求过于频繁" + - code: 500 + message: "系统异常" + - code: 501 + message: "功能未实现/未开启" + - code: 502 + message: "错误的配置项" + - code: 900 + message: "重复请求" + - code: 901 + message: "演示模式,禁止写操作" + - code: 999 + message: "未知错误" + + # 模块编号速查 + module_codes: + - module: "infra" + code_prefix: "1_001" + description: "基础设施模块" + - module: "system" + code_prefix: "1_002" + description: "系统管理模块" + - module: "member" + code_prefix: "1_003" + description: "会员模块" + - module: "pay" + code_prefix: "1_007" + description: "支付模块" + - module: "product" + code_prefix: "1_008" + description: "商品模块" + - module: "trade" + code_prefix: "1_011" + description: "交易模块" + - module: "promotion" + code_prefix: "1_013" + description: "促销模块" + + # 错误码定义模板 + error_code_template: | + // ========== {实体名称} 相关错误码 1_XXX_XXX_XXX ========== + ErrorCode XXX_NOT_EXISTS = new ErrorCode(1_002_001_000, "{实体名称}不存在"); + ErrorCode XXX_CODE_DUPLICATE = new ErrorCode(1_002_001_001, "已存在该编码的{实体名称}"); + ErrorCode XXX_NAME_DUPLICATE = new ErrorCode(1_002_001_002, "已存在该名称的{实体名称}"); + ErrorCode XXX_CAN_NOT_DELETE = new ErrorCode(1_002_001_003, "{实体名称}不能删除,原因:{}"); + ErrorCode XXX_STATUS_ERROR = new ErrorCode(1_002_001_004, "{实体名称}状态不正确"); + +# ============================================ +# 第七部分:Controller 模板 +# ============================================ +controller_template: + # 基础 CRUD 模板 + basic_crud: | + package cn.iocoder.yudao.module.{模块}.controller.admin.{功能}; + + import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; + import cn.iocoder.yudao.framework.common.pojo.CommonResult; + import cn.iocoder.yudao.framework.common.pojo.PageParam; + import cn.iocoder.yudao.framework.common.pojo.PageResult; + import cn.iocoder.yudao.framework.common.util.object.BeanUtils; + import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; + import cn.iocoder.yudao.module.{模块}.controller.admin.{功能}.vo.*; + import cn.iocoder.yudao.module.{模块}.dal.dataobject.{功能}.XxxDO; + import cn.iocoder.yudao.module.{模块}.service.{功能}.XxxService; + import io.swagger.v3.oas.annotations.Operation; + import io.swagger.v3.oas.annotations.Parameter; + import io.swagger.v3.oas.annotations.tags.Tag; + import org.springframework.security.access.prepost.PreAuthorize; + import org.springframework.validation.annotation.Validated; + import org.springframework.web.bind.annotation.*; + + import javax.annotation.Resource; + import javax.servlet.http.HttpServletResponse; + import javax.validation.Valid; + import java.io.IOException; + import java.util.List; + + import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; + import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + + @Tag(name = "管理后台 - {实体名称}") + @RestController + @RequestMapping("/{模块}/{功能}") + @Validated + public class XxxController { + + @Resource + private XxxService xxxService; + + @PostMapping("/create") + @Operation(summary = "创建{实体名称}") + @PreAuthorize("@ss.hasPermission('{模块}:{功能}:create')") + public CommonResult createXxx(@Valid @RequestBody XxxSaveReqVO createReqVO) { + return success(xxxService.createXxx(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新{实体名称}") + @PreAuthorize("@ss.hasPermission('{模块}:{功能}:update')") + public CommonResult updateXxx(@Valid @RequestBody XxxSaveReqVO updateReqVO) { + xxxService.updateXxx(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除{实体名称}") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('{模块}:{功能}:delete')") + public CommonResult deleteXxx(@RequestParam("id") Long id) { + xxxService.deleteXxx(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得{实体名称}") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('{模块}:{功能}:query')") + public CommonResult getXxx(@RequestParam("id") Long id) { + XxxDO xxx = xxxService.getXxx(id); + return success(BeanUtils.toBean(xxx, XxxRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得{实体名称}分页") + @PreAuthorize("@ss.hasPermission('{模块}:{功能}:query')") + public CommonResult> getXxxPage(@Valid XxxPageReqVO pageReqVO) { + PageResult pageResult = xxxService.getXxxPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, XxxRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出{实体名称} Excel") + @PreAuthorize("@ss.hasPermission('{模块}:{功能}:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportXxxExcel(@Valid XxxPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = xxxService.getXxxPage(pageReqVO).getList(); + List excelList = BeanUtils.toBean(list, XxxExcelVO.class); + ExcelUtils.write(response, "{实体名称}.xls", "数据", XxxExcelVO.class, excelList); + } + } + + # 公开接口模板 + public_endpoint: | + @PostMapping("/login") + @PermitAll + @Operation(summary = "使用账号密码登录") + public CommonResult login(@RequestBody @Valid AuthLoginReqVO reqVO) { + return success(authService.login(reqVO)); + } + + # 批量操作模板 + batch_operations: | + @DeleteMapping("/delete-list") + @Parameter(name = "ids", description = "编号列表", required = true) + @Operation(summary = "批量删除{实体名称}") + @PreAuthorize("@ss.hasPermission('{模块}:{功能}:delete')") + public CommonResult deleteXxxList(@RequestParam("ids") List ids) { + xxxService.deleteXxxList(ids); + return success(true); + } + + # 导入导出模板 + import_export: | + @GetMapping("/get-import-template") + @Operation(summary = "获得导入{实体名称}模板") + public void importTemplate(HttpServletResponse response) throws IOException { + // 手动创建导出 demo + List list = Arrays.asList( + XxxImportExcelVO.builder().name("示例1").code("TEST1").build(), + XxxImportExcelVO.builder().name("示例2").code("TEST2").build() + ); + ExcelUtils.write(response, "{实体名称}导入模板.xls", "数据", XxxImportExcelVO.class, list); + } + + @PostMapping("/import") + @Operation(summary = "导入{实体名称}") + @Parameters({ + @Parameter(name = "file", description = "Excel 文件", required = true), + @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true") + }) + @PreAuthorize("@ss.hasPermission('{模块}:{功能}:import')") + public CommonResult importExcel(@RequestParam("file") MultipartFile file, + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { + List list = ExcelUtils.read(file, XxxImportExcelVO.class); + return success(xxxService.importXxxList(list, updateSupport)); + } + +# ============================================ +# 第八部分:设计流程 +# ============================================ +design_workflow: + steps: + - step: 1 + name: "实体分析" + action: "分析数据库设计或 PRD,确定实体字段和业务含义" + input: ["db_design_file", "prd_file", "模块 skill 文档"] + output: "实体字段清单" + checklist: + - "确认实体所属模块" + - "阅读对应模块的 skill 文档" + - "确定需要哪些标准端点" + + - step: 2 + name: "端点设计" + action: "根据业务需求确定需要的 API 端点" + considerations: + - "是否需要完整 CRUD(create/update/delete/get/page)" + - "是否需要批量操作(delete-list)" + - "是否需要导入导出(export-excel/import)" + - "是否需要状态切换(update-status)" + - "是否有公开接口需求(使用 @PermitAll)" + + - step: 3 + name: "请求响应设计" + action: "设计各端点的请求参数和响应格式" + templates: + - "使用 SaveReqVO 处理新增/修改" + - "使用 PageReqVO 处理分页查询(继承 PageParam)" + - "使用 RespVO 处理响应" + - "使用 CommonResult 包装响应" + + - step: 4 + name: "权限设计" + action: "确定各端点的权限标识" + rules: + - "公开接口使用 @PermitAll" + - "权限接口使用 @PreAuthorize" + - "权限标识格式:模块:功能:操作" + - "查询类接口统一使用 query 权限" + + - step: 5 + name: "错误码设计" + action: "定义业务错误码" + rules: + - "格式:1_模块编号_功能编号_错误序号" + - "常见错误:不存在、重复、状态错误、操作限制" + + - step: 6 + name: "文档生成" + action: "生成 Controller 代码和 API 文档" + output: ["Controller.java", "VO classes", "错误码定义", "权限 SQL"] + +# ============================================ +# 第九部分:检查清单 +# ============================================ +checklist: + before_design: + - "确认实体所属模块" + - "阅读对应模块的 skill 文档" + - "了解现有类似接口的设计模式" + - "准备表结构设计" + + during_design: + - "URL 命名符合规范(/{模块}/{功能}/{操作})" + - "HTTP 方法与操作类型匹配" + - "请求 VO 类型选择正确" + - "响应格式使用 CommonResult" + - "权限标识命名正确" + - "注解使用完整" + + after_design: + - "所有接口添加 @Operation 注解" + - "参数添加 @Parameter 或 @Parameters 注解" + - "权限接口添加 @PreAuthorize 注解" + - "公开接口添加 @PermitAll 注解" + - "导出接口添加 @ApiAccessLog 注解" + - "错误码定义完整" + +# ============================================ +# 第十部分:与 entity-implementation.md 协同 +# ============================================ +collaboration: + relationship: + - doc: "api-designer.yaml" + role: "API 设计阶段规范指导" + focus: "设计决策、端点规划、权限标识" + - doc: "entity-implementation.md" + role: "完整实现流程指导" + focus: "实现细节、模板代码、各层规范" + usage_flow: + - "1. 使用 api-designer.yaml 进行 API 设计决策" + - "2. 参考 entity-implementation.md 进行完整实现" + - "3. 两者 Controller 模板保持一致" + +# ============================================ +# 快速参考 +# ============================================ +quick_reference: + http_methods: + POST: "创建类操作(create, import, send)" + PUT: "更新类操作(update, update-status)" + DELETE: "删除类操作(delete, delete-list)" + GET: "查询类操作(get, page, list, export-excel)" + + standard_endpoints: + /create: "创建 → CommonResult" + /update: "更新 → CommonResult" + /delete: "删除 → CommonResult" + /delete-list: "批量删除 → CommonResult" + /get: "详情 → CommonResult" + /page: "分页 → CommonResult>" + /list: "列表 → CommonResult>" + /export-excel: "导出 → void" + /import: "导入 → CommonResult" + + permission_operations: + query: "查询(get/page/list)" + create: "新增" + update: "修改" + delete: "删除" + export: "导出" + import: "导入" + + response_types: + CommonResult: "创建类响应" + CommonResult: "操作类响应" + CommonResult: "详情类响应" + CommonResult>: "分页类响应" + CommonResult>: "列表类响应" + + vo_types: + XxxSaveReqVO: "新增/修改请求" + XxxPageReqVO: "分页查询请求(继承 PageParam)" + XxxRespVO: "响应对象" + XxxExcelVO: "Excel 导出对象" + XxxImportExcelVO: "Excel 导入对象" + XxxImportRespVO: "导入结果响应" + + imports: + controller_common: + - "io.swagger.v3.oas.annotations.tags.Tag" + - "io.swagger.v3.oas.annotations.Operation" + - "io.swagger.v3.oas.annotations.Parameter" + - "io.swagger.v3.oas.annotations.Parameters" + - "org.springframework.security.access.prepost.PreAuthorize" + - "cn.iocoder.yudao.framework.common.pojo.CommonResult" + - "cn.iocoder.yudao.framework.common.pojo.PageResult" + - "static cn.iocoder.yudao.framework.common.pojo.CommonResult.success" \ No newline at end of file diff --git a/.claude/skills/design/crud-designer.yaml b/.claude/skills/design/crud-designer.yaml new file mode 100644 index 00000000..454cf9b8 --- /dev/null +++ b/.claude/skills/design/crud-designer.yaml @@ -0,0 +1,518 @@ +# CRUD 代码生成 Skill +# 整合性入口指南 - 引用已有的模板文档 + +skill: + id: "crud-generator" + name: "CRUD 代码生成" + version: "1.0.0" + category: "design" + description: "根据数据库表结构自动生成符合yudao项目规范的完整CRUD功能代码" + created_at: "2026-03-31" + updated_at: "2026-03-31" + +# ============================================ +# 第一部分:触发条件 +# ============================================ +trigger: + commands: + - "/crud-gen" + - "/generate-crud" + keywords: + - "生成CRUD" + - "创建实体代码" + - "生成增删改查" + - "实体类实现" + events: + - name: "table_created" + condition: "数据库表创建完成" + +# ============================================ +# 第二部分:输入参数 +# ============================================ +input: + parameters: + - name: "module" + type: "string" + required: true + description: "模块编码(system/infra/pay/mes等)" + examples: ["system", "infra", "pay", "mes"] + + - name: "feature" + type: "string" + required: true + description: "功能编码(user/order/process等)" + examples: ["user", "order", "process"] + + - name: "entity_name" + type: "string" + required: true + description: "实体名称(中文)" + examples: ["用户", "订单", "工序"] + + - name: "entity_class" + type: "string" + required: true + description: "实体类名(英文,首字母大写)" + examples: ["User", "Order", "Process"] + + - name: "table_name" + type: "string" + required: true + description: "数据库表名" + examples: ["system_user", "pay_order", "mes_process"] + + - name: "is_tenant" + type: "boolean" + required: false + default: true + description: "是否多租户实体(true使用TenantBaseDO,false使用BaseDO)" + + - name: "fields" + type: "array" + required: true + description: "业务字段列表" + item_schema: + name: "string" + type: "string" + required: "boolean" + description: "string" + + - name: "enable_export" + type: "boolean" + required: false + default: false + description: "是否启用Excel导出功能" + + - name: "enable_import" + type: "boolean" + required: false + default: false + description: "是否启用Excel导入功能" + + - name: "enable_batch" + type: "boolean" + required: false + default: true + description: "是否启用批量删除功能" + +# ============================================ +# 第三部分:执行流程(5阶段) +# ============================================ +workflow: + description: "CRUD代码生成按以下5个阶段顺序执行" + + phases: + # 阶段1:结构分析 + - name: "结构分析" + description: "分析表结构,确定实体属性和生成范围" + duration: "5min" + steps: + - step: "解析表名和字段定义" + action: "读取表结构信息,提取字段列表" + - step: "确定DO基类" + action: "根据is_tenant参数选择BaseDO或TenantBaseDO" + rules: + - "is_tenant=true → TenantBaseDO(含tenantId)" + - "is_tenant=false → BaseDO" + - step: "生成文件清单" + action: "根据参数确定需要创建的文件列表" + decision_table: + - condition: "默认" + files: ["DO", "Mapper", "Service", "ServiceImpl", "Controller", "SaveReqVO", "PageReqVO", "RespVO"] + - condition: "enable_export=true" + add_files: ["ExcelVO", "export-excel接口"] + - condition: "enable_import=true" + add_files: ["ImportExcelVO", "ImportRespVO", "import接口"] + - condition: "enable_batch=true" + add_files: ["delete-list接口"] + - step: "分配错误码编号" + action: "根据模块编号分配错误码段" + reference: "api-designer.yaml 第六部分 module_codes" + + # 阶段2:VO生成 + - name: "VO生成" + description: "生成请求和响应VO类" + duration: "5min" + reference: "entity-implementation.md 第3.6节" + steps: + - step: "生成 SaveReqVO" + description: "新增/修改共用请求对象" + template_path: "entity-implementation.md 第621-657行" + rules: + - "包含id字段(修改时必填)" + - "包含所有可创建/更新字段" + - "排除:creator, createTime, updater, updateTime, deleted, tenantId" + - "添加校验注解(@NotBlank/@NotNull/@Size)" + - step: "生成 PageReqVO" + description: "分页查询请求对象" + template_path: "entity-implementation.md 第661-695行" + rules: + - "继承 PageParam" + - "包含常用查询条件字段" + - "时间字段使用 @DateTimeFormat" + - step: "生成 RespVO" + description: "响应对象" + template_path: "entity-implementation.md 第699-733行" + rules: + - "包含所有展示字段" + - "包含 createTime 等只读字段" + - step: "生成 ExcelVO(可选)" + condition: "enable_export=true 或 enable_import=true" + template_path: "entity-implementation.md 第737-773行" + + # 阶段3:DAL生成 + - name: "DAL生成" + description: "生成数据访问层代码" + duration: "3min" + reference: "entity-implementation.md 第3.2-3.3节" + steps: + - step: "生成 DO实体类" + template_path: "entity-implementation.md 第128-180行" + rules: + - "@TableName 指定表名" + - "@TableId 标注主键" + - "继承正确基类" + - "枚举字段添加引用注释" + - step: "生成 Mapper接口" + template_path: "entity-implementation.md 第234-281行" + rules: + - "继承 BaseMapperX" + - "定义 selectPage 分页查询方法" + - "使用 LambdaQueryWrapperX 构建条件" + - "使用 xxxIfPresent() 方法链" + + # 阶段4:Service生成 + - name: "Service生成" + description: "生成业务逻辑层代码" + duration: "5min" + reference: "entity-implementation.md 第3.4节" + steps: + - step: "生成 Service接口" + template_path: "entity-implementation.md 第313-377行" + methods: + - "createXxx(SaveReqVO) → Long" + - "updateXxx(SaveReqVO) → void" + - "deleteXxx(Long id) → void" + - "getXxx(Long id) → XxxDO" + - "getXxxPage(PageReqVO) → PageResult" + - step: "生成 ServiceImpl实现" + template_path: "entity-implementation.md 第382-485行" + rules: + - "@Service + @Validated 注解" + - "新增方法校验唯一性" + - "更新/删除方法校验存在性" + - "使用 exception(ERROR_CODE) 抛异常" + - "使用 BeanUtils.toBean() 转换对象" + + # 阶段5:Controller生成 + - name: "Controller生成" + description: "生成HTTP接口层代码" + duration: "7min" + reference: "api-designer.yaml 第七部分" + steps: + - step: "生成 Controller类" + template_path: "api-designer.yaml 第420-511行" + annotations: + - "@Tag(name = '管理后台 - {实体名称}')" + - "@RestController" + - "@RequestMapping('/{module}/{feature}')" + - "@Validated" + - step: "生成标准端点" + endpoints: + - endpoint: "/create" + method: "POST" + template_path: "api-designer.yaml 第67-72行" + - endpoint: "/update" + method: "PUT" + template_path: "api-designer.yaml 第74-79行" + - endpoint: "/delete" + method: "DELETE" + template_path: "api-designer.yaml 第81-87行" + - endpoint: "/get" + method: "GET" + template_path: "api-designer.yaml 第97-103行" + - endpoint: "/page" + method: "GET" + template_path: "api-designer.yaml 第105-110行" + - step: "生成扩展端点(可选)" + conditionals: + - condition: "enable_batch=true" + endpoint: "/delete-list" + template_path: "api-designer.yaml 第523-531行" + - condition: "enable_export=true" + endpoint: "/export-excel" + template_path: "api-designer.yaml 第119-126行" + - condition: "enable_import=true" + endpoints: ["/get-import-template", "/import"] + template_path: "api-designer.yaml 第534-557行" + - step: "生成权限SQL" + template_path: "api-designer.yaml 第333-344行" + +# ============================================ +# 第四部分:输出产物 +# ============================================ +output: + base_path: "yudao-module-{module}-biz/src/main/java/cn/iocoder/yudao/module/{module}" + + artifacts: + # DAL层 + - name: "DO实体类" + path: "dal/dataobject/{feature}/{Entity}DO.java" + description: "数据库实体映射类" + - name: "Mapper接口" + path: "dal/mysql/{feature}/{Entity}Mapper.java" + description: "数据访问接口" + + # Service层 + - name: "Service接口" + path: "service/{feature}/{Entity}Service.java" + description: "业务服务接口" + - name: "Service实现" + path: "service/{feature}/{Entity}ServiceImpl.java" + description: "业务服务实现类" + + # Controller层 + - name: "Controller" + path: "controller/admin/{feature}/{Entity}Controller.java" + description: "HTTP API控制器" + + # VO类 + - name: "SaveReqVO" + path: "controller/admin/{feature}/vo/{Entity}SaveReqVO.java" + description: "新增/修改请求对象" + - name: "PageReqVO" + path: "controller/admin/{feature}/vo/{Entity}PageReqVO.java" + description: "分页查询请求对象" + - name: "RespVO" + path: "controller/admin/{feature}/vo/{Entity}RespVO.java" + description: "响应对象" + + # 可选产物 + - name: "ExcelVO" + path: "controller/admin/{feature}/vo/{Entity}ExcelVO.java" + condition: "enable_export=true" + description: "Excel导出对象" + - name: "ImportExcelVO" + path: "controller/admin/{feature}/vo/{Entity}ImportExcelVO.java" + condition: "enable_import=true" + description: "Excel导入对象" + - name: "ImportRespVO" + path: "controller/admin/{feature}/vo/{Entity}ImportRespVO.java" + condition: "enable_import=true" + description: "导入结果响应" + + # 配置文件 + - name: "错误码定义" + path: "enums/ErrorCodeConstants.java" + action: "追加" + description: "业务错误码定义" + - name: "权限配置SQL" + path: "sql/menu/{module}_{feature}_menu.sql" + description: "菜单和按钮权限SQL" + +# ============================================ +# 第五部分:命名规范速查 +# ============================================ +naming_standards: + # 文件命名 + files: + do: "{Entity}DO" + mapper: "{Entity}Mapper" + service: "{Entity}Service" + service_impl: "{Entity}ServiceImpl" + controller: "{Entity}Controller" + vo_save: "{Entity}SaveReqVO" + vo_page: "{Entity}PageReqVO" + vo_resp: "{Entity}RespVO" + vo_excel: "{Entity}ExcelVO" + vo_import: "{Entity}ImportExcelVO" + + # 权限标识 + permission: + format: "{module}:{feature}:{operation}" + operations: + - "query" + - "create" + - "update" + - "delete" + - "export" + - "import" + example: "system:user:create" + + # 错误码 + error_code: + format: "1_{module_code}_{feature_code}_{seq}" + module_codes: + infra: "001" + system: "002" + member: "003" + pay: "007" + product: "008" + trade: "011" + promotion: "013" + example: "1_002_001_000" + + # 数据库表名 + table: + format: "{module}_{feature}" + example: "system_user" + +# ============================================ +# 第六部分:关联文档引用 +# ============================================ +references: + primary: + - path: "skills/design/api-designer.yaml" + description: "API设计规范、Controller模板、权限标识、错误码体系" + sections: + - "第二部分:接口规范(标准端点定义)" + - "第五部分:权限标识规范" + - "第六部分:错误码体系" + - "第七部分:Controller模板" + + - path: "skills/usage/entity-implementation.md" + description: "实体类实现完整流程、各层代码模板" + sections: + - "3.2节:DO实体类模板" + - "3.3节:Mapper层模板" + - "3.4节:Service层模板" + - "3.5节:Controller层模板" + - "3.6节:VO类模板" + - "3.7节:错误码定义" + - "3.8节:权限配置SQL" + + - path: "skills/design/db-designer.yaml" + relationship: "前置依赖" + description: "数据库表结构设计规范" + + secondary: + - path: "skills/modules/{module}/skill-{module}.yaml" + relationship: "模块参考" + description: "具体模块的技术规范和扩展指南" + example: "skills/modules/system/skill-system.yaml" + +# ============================================ +# 第七部分:质量检查清单 +# ============================================ +checklist: + before_generate: + - item: "确认模块编码正确" + check: "module参数在module_codes中有对应编号" + - item: "确认表名符合命名规范" + check: "表名格式为 {module}_{feature}" + - item: "阅读对应模块的skill文档" + reference: "skills/modules/{module}/skill-{module}.yaml" + - item: "确认字段类型映射正确" + check: "数据库类型与Java类型对应" + + during_generate: + - item: "DO类继承正确的基类" + check: "TenantBaseDO(多租户)或 BaseDO(单租户)" + - item: "Mapper使用BaseMapperX和LambdaQueryWrapperX" + check: "继承BaseMapperX,条件构建使用xxxIfPresent()" + - item: "Service包含业务校验方法" + check: "新增校验唯一性,更新/删除校验存在性" + - item: "Controller注解完整" + check: "@Tag/@RestController/@RequestMapping/@Validated" + - item: "接口方法注解完整" + check: "@Operation/@PreAuthorize/@Parameter" + + after_generate: + - item: "错误码编号符合规范" + format: "1_{module_code}_{feature_code}_{seq}" + - item: "权限标识格式正确" + format: "{module}:{feature}:{operation}" + - item: "所有文件包路径正确" + base: "cn.iocoder.yudao.module.{module}" + - item: "VO类添加@Schema注解" + check: "所有字段有description" + - item: "导入语句无缺失" + check: "所有注解有对应import" + +# ============================================ +# 第八部分:与原skill的差异调整 +# ============================================ +migration_notes: + # 命名规范调整 + naming_adjustments: + - original: "CreateDTO" + target: "XxxSaveReqVO" + reason: "yudao系统新增/修改共用一个VO" + - original: "UpdateDTO" + target: "XxxSaveReqVO" + reason: "与CreateDTO合并,通过id字段区分" + - original: "QueryDTO" + target: "XxxPageReqVO" + reason: "yudao系统分页查询VO继承PageParam" + - original: "VO" + target: "XxxRespVO" + reason: "响应对象命名规范" + - original: "ListVO" + target: "XxxRespVO" + reason: "分页列表使用同一响应VO" + + # 技术栈调整 + tech_adjustments: + - original: "BaseMapper" + target: "BaseMapperX" + reason: "yudao扩展的Mapper基类" + - original: "LambdaQueryWrapper" + target: "LambdaQueryWrapperX" + reason: "支持xxxIfPresent()简化条件构建" + - original: "Result" + target: "CommonResult" + reason: "yudao统一响应包装类" + - original: "Page" + target: "PageResult" + reason: "yudao分页结果类" + - original: "@Api (Swagger 2)" + target: "@Tag (OpenAPI 3)" + reason: "Swagger版本升级" + - original: "@ApiOperation" + target: "@Operation" + reason: "OpenAPI 3.0注解" + + # 目录结构调整 + path_adjustments: + - original: "controller/{Entity}Controller.java" + target: "controller/admin/{feature}/{Entity}Controller.java" + - original: "dto/{Entity}DTO.java" + target: "controller/admin/{feature}/vo/{Entity}VO.java" + - original: "mapper/{Entity}Mapper.java" + target: "dal/mysql/{feature}/{Entity}Mapper.java" + - original: "entity/{Entity}.java" + target: "dal/dataobject/{feature}/{Entity}DO.java" + +# ============================================ +# 快速参考 +# ============================================ +quick_reference: + standard_endpoints: + POST_create: "创建 → CommonResult" + PUT_update: "更新 → CommonResult" + DELETE_delete: "删除 → CommonResult" + GET_get: "详情 → CommonResult" + GET_page: "分页 → CommonResult>" + DELETE_delete-list: "批量删除 → CommonResult" + GET_export-excel: "导出 → void" + POST_import: "导入 → CommonResult" + + permission_operations: + query: "查询(get/page/list)" + create: "新增" + update: "修改" + delete: "删除" + export: "导出" + import: "导入" + + base_classes: + BaseDO: "单租户场景 - creator/createTime/updater/updateTime/deleted" + TenantBaseDO: "多租户场景 - 额外含tenantId" + + mapper_methods: + selectPage: "分页查询" + selectOne: "查询单条" + selectList: "查询列表" + insert: "插入" + updateById: "更新" + deleteById: "删除" \ No newline at end of file diff --git a/.claude/skills/design/db-designer.yaml b/.claude/skills/design/db-designer.yaml new file mode 100644 index 00000000..95e185b4 --- /dev/null +++ b/.claude/skills/design/db-designer.yaml @@ -0,0 +1,654 @@ +# 数据库设计 Skill +# 根据业务需求生成符合项目规范的 PostgreSQL 表结构 +# 本项目默认且唯一首选数据库方言为 PostgreSQL;除非用户明确指定,否则禁止生成 MySQL 语法。 + +skill: + id: "db-designer" + name: "数据库设计 Skill" + version: "1.0.0" + category: "design" + description: "根据业务需求自动生成符合项目规范的 PostgreSQL 数据库设计,包括表结构、索引、ER图等" + created_at: "2026-03-31" + updated_at: "2026-03-31" + +# ============================================ +# 触发条件 +# ============================================ +trigger: + commands: + - "/db-design" + - "/db-designer" + + keywords: + - "设计数据库" + - "生成表结构" + - "创建数据模型" + - "建表" + - "DDL" + + events: + - name: "prd_approved" + condition: "PRD文档审核通过" + - name: "architecture_defined" + condition: "系统架构设计完成" + +# ============================================ +# 输入参数 +# ============================================ +input: + parameters: + - name: "module_code" + type: "string" + required: true + description: "模块编码(如 mes、erp、crm、bpm 等)" + examples: ["mes", "erp", "crm", "bpm", "pay", "fz"] + + - name: "table_name" + type: "string" + required: true + description: "表名(不含前缀)" + examples: ["work_order", "product", "customer", "letter"] + + - name: "business_fields" + type: "array" + required: true + description: "业务字段列表" + + - name: "indexes" + type: "array" + required: false + default: [] + description: "额外索引定义" + + - name: "create_date" + type: "string" + required: false + default: "today" + description: "创建日期(用于文件命名)" + +# ============================================ +# 设计理念 +# ============================================ +philosophy: + business_position: "数据库设计是业务建模的核心环节,将业务需求转化为可存储的数据结构" + + design_principles: + - "多租户隔离:所有业务表包含 tenant_id 字段,索引必须包含 tenant_id" + - "审计追踪:标准化审计字段,支持数据变更追踪" + - "逻辑删除:使用 deleted 字段实现软删除,避免数据物理删除" + - "索引优化:根据查询场景设计索引,遵循最左匹配原则" + - "命名规范:统一的表名、字段名、索引名命名规则" + +# ============================================ +# 目录结构规范 +# ============================================ +directory_structure: + base_path: "sql/postgresql" + layout: | + sql/postgresql/ + ├── {module}/ # 按模块组织 PostgreSQL 迁移脚本 + ├── table/ # 通用表结构定义 + │ ├── create/ # 建表脚本 + │ └── update/ # 表结构更新脚本 + ├── index/ # 索引定义 + ├── view/ # 视图定义 + ├── data/ # 初始数据 + └── init/ # 模块初始化脚本 + + directories: + - name: "table/create" + purpose: "存放建表 SQL 脚本" + note: "每个表一个文件,按日期命名" + - name: "table/update" + purpose: "存放表结构更新 SQL 脚本" + note: "ALTER TABLE、ADD COLUMN 等增量变更" + - name: "sequence" + purpose: "需要显式序列时存放序列脚本" + note: "默认优先使用 GENERATED BY DEFAULT AS IDENTITY;仅兼容既有 @KeySequence 约定时显式建序列" + - name: "index" + purpose: "存放索引创建脚本" + note: "可按表名组织子目录" + - name: "view" + purpose: "存放视图创建脚本" + note: "复杂查询可封装为视图" + - name: "data" + purpose: "存放初始数据 SQL 脚本" + note: "INSERT 语句,初始化字典、菜单等" + - name: "init" + purpose: "存放模块初始化脚本" + note: "按模块组织,如 bpm.sql, quartz.sql" + +# ============================================ +# 文件命名规范 +# ============================================ +file_naming: + # 建表文件命名 + create_table: + format: "YYYY_MM_DD_{表名}_create.sql" + examples: + - "2025_08_09_fz_letter_create.sql" + - "2026_03_25_mes_work_order_create.sql" + rules: + - "日期前缀使用实际创建日期" + - "表名使用小写字母和下划线" + - "统一使用 _create.sql 后缀" + + # 更新文件命名 + update_table: + format: "YYYY_MM_DD_{表名}_update.sql" + examples: + - "2025_08_15_fz_metaletter_update.sql" + - "2026_01_07_fz_attachment_rel_update.sql" + rules: + - "日期前缀使用实际更新日期" + - "同一表多次更新使用不同日期" + - "统一使用 _update.sql 后缀" + special_cases: + - pattern: "YYYY_MM_DD_{表名}_index_update.sql" + purpose: "专门用于索引更新" + + # 序列文件命名 + sequence: + format: "YYYY_MM_DD_{表名}_seq_create.sql" + examples: + - "2025_07_24_fz_metaletter_seq_create.sql" + rules: + - "与建表日期保持一致" + - "序列名通常为 表名_seq" + + # 初始数据文件命名 + initial_data: + format: "模块名.sql" + examples: + - "fazhi.sql" + - "bpm.sql" + - "quartz.sql" + - "ruoyi-vue-pro.sql" + rules: + - "按模块或功能命名" + - "包含该模块的初始数据" + +# ============================================ +# 表命名规范 +# ============================================ +table_naming: + # 表名前缀规范 + prefixes: + - prefix: "sys_" + usage: "系统核心表" + examples: ["sys_user", "sys_role", "sys_menu", "sys_dict_data"] + - prefix: "mes_" + usage: "制造执行系统表" + examples: ["mes_work_order", "mes_product", "mes_workshop"] + - prefix: "erp_" + usage: "企业资源计划表" + examples: ["erp_purchase", "erp_sale", "erp_inventory"] + - prefix: "crm_" + usage: "客户关系管理表" + examples: ["crm_customer", "crm_contract", "crm_clue"] + - prefix: "bpm_" + usage: "工作流表" + examples: ["bpm_process_definition", "bpm_process_instance"] + - prefix: "pay_" + usage: "支付模块表" + examples: ["pay_order", "pay_refund", "pay_channel"] + - prefix: "member_" + usage: "会员模块表" + examples: ["member_user", "member_level", "member_address"] + - prefix: "infra_" + usage: "基础设施表" + examples: ["infra_file", "infra_config"] + - prefix: "fz_" + usage: "法制业务表" + examples: ["fz_letter", "fz_replay", "fz_handle_unit"] + + # 表名规则 + rules: + - "使用小写字母,单词间用下划线分隔" + - "使用名词,表示实体或关系" + - "避免使用 PostgreSQL 保留字" + - "长度不超过 64 个字符" + - "关联表命名:主表_关联表_rel,如 fz_letter_label_rel" + + # 字段命名规则 + column_rules: + - "使用小写字母和下划线" + - "布尔类型使用 is_ 前缀,如 is_deleted, is_enabled" + - "时间类型使用 _time 或 _date 后缀" + - "外键使用 关联表_id 格式,如 user_id, dept_id" + - "主键统一命名为 id" + +# ============================================ +# 必需字段规范 +# ============================================ +required_columns: + # 字段定义 + columns: + - name: "id" + type: "BIGINT" + constraint: "GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY" + comment: "主键ID" + position: 1 + + - name: "tenant_id" + type: "BIGINT" + default: "0" + constraint: "NOT NULL" + comment: "租户编号" + position: 2 + note: "所有业务表必须包含,位于 id 之后" + + - name: "creator" + type: "VARCHAR(64)" + default: "''" + comment: "创建者" + + - name: "create_time" + type: "TIMESTAMP" + default: "CURRENT_TIMESTAMP" + constraint: "NOT NULL" + comment: "创建时间" + + - name: "updater" + type: "VARCHAR(64)" + default: "''" + comment: "更新者" + + - name: "update_time" + type: "TIMESTAMP" + default: "CURRENT_TIMESTAMP" + constraint: "NOT NULL" + comment: "更新时间" + note: "PostgreSQL 不支持 ON UPDATE CURRENT_TIMESTAMP,由应用层或触发器维护" + + - name: "deleted" + type: "BOOLEAN" + default: "false" + constraint: "NOT NULL" + comment: "是否删除" + + # 可选备注字段 + remark: + name: "remark" + type: "VARCHAR(500)" + default: "NULL" + comment: "备注" + position: "before_audit" + +# ============================================ +# 索引设计规范 +# ============================================ +index_standards: + # 索引命名规则 + naming: + primary_key: "PRIMARY KEY (id)" + unique_key: "uk_{字段名} 或 uk_{字段1_字段2}" + normal_index: "idx_{字段名} 或 idx_{字段1_字段2}" + + # 索引设计原则 + principles: + - "所有索引必须包含 tenant_id 作为第一列" + - "唯一索引必须包含 deleted 字段(软删除场景)" + - "外键字段必须建立索引" + - "经常用于查询条件的字段建立索引" + - "唯一约束字段建立唯一索引" + - "组合索引注意字段顺序(最左匹配)" + - "避免过多索引,影响写入性能" + + # 索引模板 + templates: + unique_with_tenant: + pattern: "CREATE UNIQUE INDEX uk_tenant_{col} ON {table} (tenant_id, {col}) WHERE deleted = false" + example: "CREATE UNIQUE INDEX uk_tenant_order ON mes_work_order (tenant_id, order_no) WHERE deleted = false" + + normal_with_tenant: + pattern: "CREATE INDEX idx_tenant_{col} ON {table} (tenant_id, {col})" + example: "CREATE INDEX idx_tenant_status ON mes_work_order (tenant_id, status)" + + foreign_key: + pattern: "CREATE INDEX idx_tenant_{fk} ON {table} (tenant_id, {fk})" + example: "CREATE INDEX idx_tenant_line ON mes_work_order (tenant_id, line_id)" + +# ============================================ +# 建表模板 +# ============================================ +create_table_template: + structure: | + -- ============================================ + -- 文件名:{filename} + -- 描述:{table_comment} + -- 作者:{author} + -- 日期:{date} + -- ============================================ + + CREATE TABLE {table_name} ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + tenant_id BIGINT NOT NULL DEFAULT 0, + -- 业务字段 + {business_columns} + -- 通用字段 + remark VARCHAR(500) DEFAULT NULL, + creator VARCHAR(64) DEFAULT '', + create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) DEFAULT '', + update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted BOOLEAN NOT NULL DEFAULT false + ); + + COMMENT ON TABLE {table_name} IS '{table_comment}'; + {column_comments} + {indexes} + + # 字段类型推荐 + column_types: + string_short: "VARCHAR(64)" + string_medium: "VARCHAR(255)" + string_long: "VARCHAR(500)" + text: "TEXT" + integer: "INT" + bigint: "BIGINT" + decimal_amount: "DECIMAL(18,2)" + decimal_quantity: "DECIMAL(10,4)" + boolean: "BOOLEAN" + datetime: "TIMESTAMP" + date: "DATE" + status: "SMALLINT" + +# ============================================ +# 更新脚本规范 +# ============================================ +update_standards: + # 更新文件命名 + naming: "YYYY_MM_DD_{表名}_update.sql" + + # 字段操作模板 + operations: + add_column: | + -- 新增字段并添加字段注释 + ALTER TABLE {table} ADD COLUMN {column} {type}; + COMMENT ON COLUMN {table}.{column} IS '{comment}'; + -- 示例 + ALTER TABLE fz_letter ADD COLUMN priority SMALLINT DEFAULT 0; + COMMENT ON COLUMN fz_letter.priority IS '优先级'; + + modify_column: | + -- 修改字段类型 + ALTER TABLE {table} ALTER COLUMN {column} TYPE {new_type} USING {column}::{new_type}; + COMMENT ON COLUMN {table}.{column} IS '{comment}'; + -- 示例 + ALTER TABLE fz_letter ALTER COLUMN content TYPE TEXT USING content::TEXT; + COMMENT ON COLUMN fz_letter.content IS '信件内容'; + + drop_column: | + -- 删除字段(确保无业务依赖) + ALTER TABLE {table} DROP COLUMN {column}; + + add_index: | + -- 添加普通索引 + CREATE INDEX idx_tenant_{col} ON {table} (tenant_id, {col}); + -- 添加软删除唯一索引 + CREATE UNIQUE INDEX uk_tenant_{col} ON {table} (tenant_id, {col}) WHERE deleted = false; + + # 文件头部模板 + header: | + -- ============================================ + -- 更新说明:{更新目的} + -- 更新日期:{YYYY-MM-DD} + -- 更新人:{姓名} + -- ============================================ + + # 注释规范 + comment_standards: + - "文件头部说明更新目的" + - "每个变更语句添加注释" + - "危险操作(删除字段、删除数据)添加警告注释" + +# ============================================ +# 数据初始化规范 +# ============================================ +data_standards: + # INSERT 语句格式 + insert_format: | + -- 插入菜单数据 + INSERT INTO `sys_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`) + VALUES + (1001, '信件管理', '', 2, 1, 0, 'letter', 'ep:document', 'fazhi/letter/index', 0), + (1002, '信件查询', 'fz:letter:query', 3, 1, 1001, '', '', '', 0); + + # 字典数据 + dict_data: | + -- 字典类型 + INSERT INTO `sys_dict_type` (`name`, `type`, `status`, `remark`) + VALUES ('信件状态', 'fz_letter_status', 0, '信件流转状态'); + + -- 字典数据 + INSERT INTO `sys_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`) + VALUES + (1, '待处理', '0', 'fz_letter_status', 0), + (2, '处理中', '1', 'fz_letter_status', 0), + (3, '已完成', '2', 'fz_letter_status', 0); + +# ============================================ +# 编写规范 +# ============================================ +coding_standards: + # SQL 格式 + formatting: + - "关键字使用大写:SELECT, FROM, WHERE, JOIN, ORDER BY" + - "表名、字段名使用小写;仅在名称冲突或保留字场景使用双引号" + - "每个字段占一行,逗号放在行尾" + - "复杂查询使用缩进和换行提高可读性" + + # 注释规范 + comments: + file_header: | + -- ============================================ + -- 文件名:{文件名} + -- 描述:{功能描述} + -- 作者:{作者} + -- 日期:{YYYY-MM-DD} + -- ============================================ + table_comment: "CREATE TABLE 后必须使用 COMMENT ON TABLE 添加表注释" + column_comment: "每个字段必须使用 COMMENT ON COLUMN 添加字段注释" + index_comment: "复杂索引添加注释说明用途" + + # 事务处理 + transaction: + - "多条关联语句使用事务包裹" + - "DDL 语句(部分数据库)自动提交,注意顺序" + - "大数据量操作分批执行" + +# ============================================ +# 版本管理 +# ============================================ +version_control: + principles: + - "建表后表结构变更使用 update 脚本,不修改原文件" + - "每个变更独立文件,便于追踪和回滚" + - "日期前缀确保文件顺序和变更时间线" + - "版本升级时按日期顺序执行所有脚本" + + change_log: + format: | + -- 变更记录: + -- 2025-08-09:创建表 + -- 2025-11-11:新增 priority 字段 + -- 2026-01-07:新增 category 字段,添加索引 + +# ============================================ +# 执行流程 +# ============================================ +workflow: + phases: + - name: "需求分析" + description: "解析业务需求,提取实体和关系" + steps: + - "识别核心业务实体" + - "分析实体间关系(一对多、多对多)" + - "确定实体属性列表" + - "确定表名前缀(模块编码)" + + - name: "实体设计" + description: "设计实体属性和字段" + steps: + - "设计业务属性字段" + - "添加系统必需字段(审计字段)" + - "定义主键策略(BIGINT GENERATED BY DEFAULT AS IDENTITY;兼容既有代码时配合 @KeySequence)" + - "设置外键关系" + + - name: "多租户设计" + description: "添加租户隔离字段和索引" + steps: + - "添加 tenant_id 字段(第二位置)" + - "确保所有索引包含 tenant_id" + - "唯一索引包含 deleted 字段" + + - name: "索引设计" + description: "根据查询场景设计索引" + steps: + - "分析查询场景(列表、详情、关联、搜索)" + - "设计主键索引" + - "设计唯一索引(业务唯一键)" + - "设计普通索引(查询优化)" + - "设计外键索引" + + - name: "文件生成" + description: "生成符合规范的 SQL 文件" + steps: + - "确定文件命名(日期前缀)" + - "生成文件头部注释" + - "生成 CREATE TABLE 语句" + - "生成索引定义" + - "添加表注释" + +# ============================================ +# 质量检查清单 +# ============================================ +quality_check: + before_create: + - "确认表名符合命名规范" + - "确认包含所有必需字段" + - "确认字段类型和长度合理" + - "确认添加了必要的索引" + - "确认添加了表注释和字段注释" + - "确认文件命名符合规范" + + after_create: + - "测试建表脚本可正常执行" + - "确认所有索引包含 tenant_id" + - "确认唯一索引包含 deleted" + - "确认外键字段有索引" + - "检查索引是否生效" + - "更新相关文档" + + before_update: + - "确认变更的必要性" + - "评估对现有数据的影响" + - "准备回滚方案" + - "选择低峰期执行" + +# ============================================ +# 快速参考 +# ============================================ +quick_reference: + table_prefixes: + sys_: "系统表" + mes_: "制造执行系统表" + erp_: "企业资源计划表" + crm_: "客户关系管理表" + bpm_: "工作流表" + pay_: "支付表" + member_: "会员表" + infra_: "基础设施表" + fz_: "法制业务表" + + file_suffixes: + _create.sql: "建表脚本" + _update.sql: "更新脚本" + _seq_create.sql: "序列脚本" + + common_columns: + id: "主键ID(BIGINT)" + tenant_id: "租户编号(BIGINT)" + creator: "创建者(VARCHAR(64))" + create_time: "创建时间(TIMESTAMP)" + updater: "更新者(VARCHAR(64))" + update_time: "更新时间(TIMESTAMP)" + deleted: "逻辑删除(BOOLEAN)" + remark: "备注(VARCHAR(500))" + +# ============================================ +# 示例 +# ============================================ +examples: + work_order: + input: + module_code: "mes" + table_name: "mes_work_order" + table_comment: "生产工单表" + business_fields: + - name: "order_no" + type: "VARCHAR(64)" + required: true + comment: "工单编号" + unique: true + - name: "product_id" + type: "BIGINT" + required: true + comment: "产品ID" + - name: "status" + type: "TINYINT" + required: true + default: "0" + comment: "状态:0-待下发,1-已下发,2-生产中,3-已完成,4-已关闭" + + output_file: "2026_03_25_mes_work_order_create.sql" + + output_sql: | + -- PostgreSQL 示例(字段注释通过 COMMENT ON COLUMN 补充) + CREATE TABLE mes_work_order ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + tenant_id BIGINT NOT NULL DEFAULT 0, + order_no VARCHAR(64) NOT NULL, + product_id BIGINT NOT NULL, + status SMALLINT NOT NULL DEFAULT 0, + plan_start_time TIMESTAMP NOT NULL, + plan_end_time TIMESTAMP NOT NULL, + remark VARCHAR(500) DEFAULT NULL, + creator VARCHAR(64) DEFAULT '', + create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) DEFAULT '', + update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted BOOLEAN NOT NULL DEFAULT false + ); + + COMMENT ON TABLE mes_work_order IS '生产工单表'; + CREATE UNIQUE INDEX uk_tenant_order + ON mes_work_order (tenant_id, order_no) WHERE deleted = false; + CREATE INDEX idx_tenant_status ON mes_work_order (tenant_id, status); + CREATE INDEX idx_tenant_time ON mes_work_order (tenant_id, plan_start_time); + +# ============================================ +# 错误处理 +# ============================================ +error_handling: + errors: + - code: "DB001" + message: "表名不符合命名规范" + solution: "检查表名前缀和格式" + + - code: "DB002" + message: "缺少必需字段" + solution: "确保包含 id, tenant_id, 审计字段" + + - code: "DB003" + message: "索引缺少 tenant_id" + solution: "所有索引必须包含 tenant_id 作为第一列" + + - code: "DB004" + message: "文件命名不符合规范" + solution: "使用 YYYY_MM_DD_{表名}_create.sql 格式" + + - code: "DB005" + message: "唯一索引缺少 deleted" + solution: "唯一索引必须包含 deleted 字段" \ No newline at end of file diff --git a/.claude/skills/design/entity-designer.yaml b/.claude/skills/design/entity-designer.yaml new file mode 100644 index 00000000..5f2b0020 --- /dev/null +++ b/.claude/skills/design/entity-designer.yaml @@ -0,0 +1,836 @@ +# 实体类设计 Skill +# 根据 PostgreSQL 表结构生成符合 yudao-vue-pro 规范的实体类(DO) +# 本项目默认数据库方言为 PostgreSQL,类型映射不得回退为 MySQL 默认。 + +skill: + id: "design-entity" + name: "实体类设计 Skill" + version: "1.0.0" + category: "design" + description: "根据 PostgreSQL 表结构自动生成符合 yudao 项目规范的实体类(DO),支持多租户、审计字段、JSONB 字段等特性" + created_at: "2026-03-31" + updated_at: "2026-03-31" + +# ============================================ +# 触发条件 +# ============================================ +trigger: + # 命令触发 + commands: + - "/entity-design" + - "/design-entity" + - "/entity-generator" + + # 自然语言触发 + keywords: + - "生成实体类" + - "创建DO" + - "生成DO实体" + - "实体类设计" + - "数据对象" + + # 事件触发 + events: + - name: "db_design_completed" + condition: "数据库表结构设计完成" + - name: "sql_created" + condition: "建表 SQL 文件创建完成" + +# ============================================ +# 输入参数 +# ============================================ +input: + parameters: + - name: "module_code" + type: "string" + required: true + description: "模块编码(如 mes、erp、crm、system)" + examples: ["mes", "erp", "crm", "system", "pay"] + + - name: "table_name" + type: "string" + required: true + description: "表名(含前缀,如 mes_work_order)" + examples: ["mes_work_order", "erp_product", "system_user"] + + - name: "table_comment" + type: "string" + required: true + description: "表注释/描述" + + - name: "business_fields" + type: "array" + required: true + description: "业务字段列表(不含通用字段)" + schema: + - name: "字段名" + type: "数据类型" + required: "是否必填" + comment: "字段注释" + enum_ref: "枚举引用(可选)" + json_field: "是否JSON字段(可选)" + java_type: "JSON字段的Java类型(可选,如 Set)" + + - name: "tenant_type" + type: "string" + required: false + default: "multi" + description: "租户类型:single-单租户,multi-多租户,ignore-忽略租户" + examples: ["single", "multi", "ignore"] + + - name: "entity_name" + type: "string" + required: false + default: "auto" + description: "实体类名称(默认根据表名自动生成)" + + - name: "feature" + type: "string" + required: false + default: "auto" + description: "功能子目录名(默认根据实体名推断)" + +# ============================================ +# 设计理念 +# ============================================ +philosophy: + business_position: "实体类(DO)是数据库表与业务逻辑之间的桥梁,将关系型数据映射为对象" + + design_principles: + - "继承体系:多租户继承 TenantBaseDO,单租户继承 BaseDO" + - "审计字段:由基类提供,业务实体不重复定义" + - "字段注释:使用 JavaDoc 格式,不使用 Swagger 注解" + - "JSON 字段:使用 JacksonTypeHandler 处理复杂类型" + - "命名规范:实体类以 DO 结尾,与表名保持映射关系" + - "枚举引用:在注释中说明枚举类,字段使用 Integer 类型" + + inheritance_model: + base_hierarchy: + BaseDO: + package: "cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO" + fields: ["createTime", "updateTime", "creator", "updater", "deleted"] + use_case: "单租户业务表" + note: "deleted 字段为 Boolean 类型" + TenantBaseDO: + package: "cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO" + parent: "BaseDO" + extra_fields: ["tenantId"] + use_case: "多租户业务表(绝大多数场景)" + special_annotation: + TenantIgnore: + package: "cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore" + use_case: "租户表、套餐表等需要忽略多租户过滤的实体" + +# ============================================ +# 命名转换规则 +# ============================================ +naming: + # 表名 -> 实体类名转换 + table_to_class: + rules: + - "去除表前缀(如 mes_、erp_、system_、crm_、bpm_、pay_)" + - "snake_case 转 PascalCase" + - "添加 DO 后缀" + - "特殊表名特殊处理" + examples: + - table: "mes_work_order" + class: "WorkOrderDO" + - table: "erp_product" + class: "ProductDO" + - table: "system_users" + class: "AdminUserDO" + - table: "system_role" + class: "RoleDO" + - table: "crm_customer" + class: "CustomerDO" + + # 字段名 -> 属性名转换 + column_to_field: + rules: + - "snake_case 转 camelCase" + - "保持与数据库字段映射" + examples: + - column: "work_order_no" + field: "workOrderNo" + - column: "create_time" + field: "createTime" + - column: "tenant_id" + field: "tenantId" + + # 特殊表名映射 + special_mappings: + system_users: "AdminUserDO" + system_menu: "MenuDO" + system_tenant: "TenantDO" + system_dict_data: "DictDataDO" + system_dict_type: "DictTypeDO" + + # 表前缀映射(模块编码) + prefix_mapping: + sys: "system" + mes: "mes" + erp: "erp" + crm: "crm" + bpm: "bpm" + pay: "pay" + member: "member" + infra: "infra" + +# ============================================ +# 类型映射规则 +# ============================================ +type_mapping: + postgresql_to_java: + - postgresql: "BIGINT" + java: "Long" + note: "主键、外键、数量类字段" + + - postgresql: "INT / INTEGER" + java: "Integer" + note: "状态、排序、计数类字段" + + - postgresql: "TINYINT" + java: "Integer" + note: "枚举字段、开关字段" + + - postgresql: "SMALLINT" + java: "Integer" + note: "小范围数值" + + - postgresql: "DECIMAL(p,s)" + java: "BigDecimal" + note: "金额、精确数值" + import: "java.math.BigDecimal" + + - postgresql: "FLOAT / DOUBLE" + java: "Double" + note: "浮点数" + + - postgresql: "VARCHAR / CHAR" + java: "String" + note: "字符串" + + - postgresql: "TEXT / LONGTEXT" + java: "String" + note: "大文本" + + - postgresql: "TIMESTAMP / TIMESTAMPTZ" + java: "LocalDateTime" + note: "日期时间" + import: "java.time.LocalDateTime" + + - postgresql: "DATE" + java: "LocalDate" + note: "日期" + import: "java.time.LocalDate" + + - postgresql: "TIME" + java: "LocalTime" + note: "时间" + import: "java.time.LocalTime" + + - postgresql: "BOOLEAN" + java: "Boolean" + note: "布尔值" + + - postgresql: "BLOB / LONGBLOB" + java: "byte[]" + note: "二进制数据" + + - postgresql: "JSON" + java: "复杂类型" + handler: "JacksonTypeHandler" + note: "JSON 数据,需特殊处理" + + # JSON 字段类型映射 + json_type_mapping: + - pattern: "ID数组" + java_type: "Set" + import: "java.util.Set" + - pattern: "字符串数组" + java_type: "List" + import: "java.util.List" + - pattern: "键值对对象" + java_type: "Map" + import: "java.util.Map" + +# ============================================ +# 注解规范 +# ============================================ +annotations: + # 类级别注解 + class_annotations: + required: + - annotation: "@TableName" + format: "@TableName(value = \"{table_name}\", autoResultMap = true)" + import: "com.baomidou.mybatisplus.annotation.TableName" + note: "autoResultMap = true 用于支持 TypeHandler" + + - annotation: "@KeySequence" + format: "@KeySequence(\"{table_name}_seq\")" + import: "com.baomidou.mybatisplus.annotation.KeySequence" + note: "PostgreSQL 主键约定;与项目既有 @KeySequence 规范保持一致" + + - annotation: "@Data" + import: "lombok.Data" + note: "Lombok getter/setter" + + - annotation: "@EqualsAndHashCode" + format: "@EqualsAndHashCode(callSuper = true)" + import: "lombok.EqualsAndHashCode" + note: "必须 callSuper = true 以包含基类字段" + + optional: + - annotation: "@Builder" + import: "lombok.Builder" + note: "构建器模式" + + - annotation: "@NoArgsConstructor" + import: "lombok.NoArgsConstructor" + note: "无参构造(与 Builder 配合)" + + - annotation: "@AllArgsConstructor" + import: "lombok.AllArgsConstructor" + note: "全参构造(与 Builder 配合)" + + - annotation: "@TenantIgnore" + import: "cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore" + note: "忽略多租户过滤(仅用于租户表等特殊场景)" + + # 字段级别注解 + field_annotations: + primary_key: + - annotation: "@TableId" + import: "com.baomidou.mybatisplus.annotation.TableId" + note: "主键字段,默认使用雪花算法" + + json_field: + - annotation: "@TableField" + format: "@TableField(typeHandler = JacksonTypeHandler.class)" + import: "com.baomidou.mybatisplus.annotation.TableField" + handler_import: "com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" + note: "JSON 类型字段,需要 @TableName 包含 autoResultMap = true" + +# ============================================ +# 代码模板 +# ============================================ +templates: + # 多租户实体模板(最常用) + tenant_entity: + description: "多租户业务表实体类" + base_class: "TenantBaseDO" + code: | + package cn.iocoder.yudao.module.{module}.dal.dataobject.{feature}; + + import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; + import com.baomidou.mybatisplus.annotation.KeySequence; + import com.baomidou.mybatisplus.annotation.TableId; + import com.baomidou.mybatisplus.annotation.TableName; + import lombok.*; + {additional_imports} + + /** + * {table_comment} DO + * + * @author {author} + */ + @TableName(value = "{table_name}", autoResultMap = true) + @KeySequence("{table_name}_seq") + @Data + @EqualsAndHashCode(callSuper = true) + @Builder + @NoArgsConstructor + @AllArgsConstructor + public class {EntityName}DO extends TenantBaseDO { + + /** + * 主键ID + */ + @TableId + private Long id; + + {business_fields} + + } + + # 单租户实体模板 + single_entity: + description: "单租户业务表实体类" + base_class: "BaseDO" + code: | + package cn.iocoder.yudao.module.{module}.dal.dataobject.{feature}; + + import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; + import com.baomidou.mybatisplus.annotation.KeySequence; + import com.baomidou.mybatisplus.annotation.TableId; + import com.baomidou.mybatisplus.annotation.TableName; + import lombok.*; + {additional_imports} + + /** + * {table_comment} DO + * + * @author {author} + */ + @TableName(value = "{table_name}", autoResultMap = true) + @KeySequence("{table_name}_seq") + @Data + @EqualsAndHashCode(callSuper = true) + @Builder + @NoArgsConstructor + @AllArgsConstructor + public class {EntityName}DO extends BaseDO { + + /** + * 主键ID + */ + @TableId + private Long id; + + {business_fields} + + } + + # 忽略多租户实体模板 + tenant_ignore_entity: + description: "需要忽略多租户过滤的实体类(如租户表、套餐表)" + base_class: "BaseDO" + extra_annotation: "@TenantIgnore" + code: | + package cn.iocoder.yudao.module.{module}.dal.dataobject.{feature}; + + import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; + import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; + import com.baomidou.mybatisplus.annotation.KeySequence; + import com.baomidou.mybatisplus.annotation.TableId; + import com.baomidou.mybatisplus.annotation.TableName; + import lombok.*; + {additional_imports} + + /** + * {table_comment} DO + * + * @author {author} + */ + @TenantIgnore + @TableName(value = "{table_name}", autoResultMap = true) + @KeySequence("{table_name}_seq") + @Data + @EqualsAndHashCode(callSuper = true) + @Builder + @NoArgsConstructor + @AllArgsConstructor + public class {EntityName}DO extends BaseDO { + + /** + * 主键ID + */ + @TableId + private Long id; + + {business_fields} + + } + + # 业务字段模板 + business_field: + normal: | + /** + * {字段注释} + {enum_reference} + */ + private {JavaType} {fieldName}; + + json_field: | + /** + * {字段注释} + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private {JavaType} {fieldName}; + +# ============================================ +# 输出规范 +# ============================================ +output: + # 文件输出位置 + file_location: + path_pattern: "yudao-module-{module}/src/main/java/cn/iocoder/yudao/module/{module}/dal/dataobject/{feature}/{EntityName}DO.java" + example: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/workorder/WorkOrderDO.java" + + # 导入语句规范 + imports: + base_imports: + - "com.baomidou.mybatisplus.annotation.TableName" + - "com.baomidou.mybatisplus.annotation.TableId" + - "com.baomidou.mybatisplus.annotation.KeySequence" + - "lombok.Data" + - "lombok.EqualsAndHashCode" + + tenant_imports: + - "cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO" + + single_imports: + - "cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO" + + json_handler_import: + - "com.baomidou.mybatisplus.annotation.TableField" + - "com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" + + builder_imports: + - "lombok.Builder" + - "lombok.NoArgsConstructor" + - "lombok.AllArgsConstructor" + + # 产物清单 + artifacts: + - name: "实体类文件" + type: "code" + path: "dal/dataobject/{feature}/{EntityName}DO.java" + +# ============================================ +# 执行流程 +# ============================================ +workflow: + phases: + - name: "表结构解析" + description: "解析数据库表结构,提取元数据" + steps: + - "识别表名、表前缀、表注释" + - "提取业务字段列表(排除通用字段:id, tenant_id, creator, create_time, updater, update_time, deleted)" + - "识别主键、索引信息" + - "识别 JSON 字段、枚举字段" + + - name: "命名转换" + description: "将数据库命名转换为 Java 命名" + steps: + - "表名 -> 实体类名(去除前缀 + PascalCase + DO 后缀)" + - "字段名 -> 属性名(camelCase)" + - "处理特殊表名映射" + + - name: "类型映射" + description: "将 MySQL 类型映射为 Java 类型" + steps: + - "标准类型映射(BIGINT -> Long 等)" + - "JSON 字段识别和处理" + - "枚举字段处理(Integer + 注释引用)" + + - name: "继承选择" + description: "确定实体类继承的基类" + steps: + - "判断租户类型(multi/single/ignore)" + - "选择对应基类(TenantBaseDO/BaseDO)" + - "判断是否需要 @TenantIgnore" + + - name: "代码生成" + description: "生成实体类代码" + steps: + - "生成类注释和注解" + - "生成主键字段" + - "生成业务字段(含 JavaDoc 注释)" + - "生成导入语句(按规范分组排序)" + - "格式化代码" + + - name: "质量检查" + description: "验证生成的实体类" + steps: + - "检查注解完整性" + - "检查字段注释格式" + - "检查继承正确性" + - "检查命名规范" + +# ============================================ +# 质量检查清单 +# ============================================ +quality_check: + before_generate: + - "确认表名符合命名规范" + - "确认业务字段列表完整" + - "确认租户类型选择正确" + - "确认字段类型映射正确" + + after_generate: + - "实体类名称以 DO 结尾" + - "@TableName 注解包含 autoResultMap = true" + - "@EqualsAndHashCode 包含 callSuper = true" + - "继承正确的基类" + - "主键字段使用 @TableId 注解" + - "所有字段使用 JavaDoc 注释格式" + - "JSON 字段使用 JacksonTypeHandler" + - "枚举字段注释包含枚举引用" + - "不包含 Swagger @Schema 注解" + - "不重复定义审计字段(已在基类中)" + +# ============================================ +# 示例 +# ============================================ +examples: + # 多租户实体示例 + work_order: + input: + module_code: "mes" + table_name: "mes_work_order" + table_comment: "生产工单" + tenant_type: "multi" + business_fields: + - name: "order_no" + type: "VARCHAR(64)" + comment: "工单编号" + unique: true + - name: "product_id" + type: "BIGINT" + comment: "产品ID" + - name: "status" + type: "TINYINT" + comment: "状态" + enum_ref: "WorkOrderStatusEnum" + - name: "plan_qty" + type: "INT" + comment: "计划数量" + + output_class_name: "WorkOrderDO" + output_file: "yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/workorder/WorkOrderDO.java" + + output_code: | + package cn.iocoder.yudao.module.mes.dal.dataobject.workorder; + + import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; + import cn.iocoder.yudao.module.mes.enums.WorkOrderStatusEnum; + import com.baomidou.mybatisplus.annotation.KeySequence; + import com.baomidou.mybatisplus.annotation.TableId; + import com.baomidou.mybatisplus.annotation.TableName; + import lombok.*; + + /** + * 生产工单 DO + * + * @author yudao + */ + @TableName(value = "mes_work_order", autoResultMap = true) + @KeySequence("mes_work_order_seq") + @Data + @EqualsAndHashCode(callSuper = true) + @Builder + @NoArgsConstructor + @AllArgsConstructor + public class WorkOrderDO extends TenantBaseDO { + + /** + * 主键ID + */ + @TableId + private Long id; + + /** + * 工单编号 + */ + private String orderNo; + + /** + * 产品ID + */ + private Long productId; + + /** + * 状态 + * + * 枚举 {@link WorkOrderStatusEnum} + */ + private Integer status; + + /** + * 计划数量 + */ + private Integer planQty; + + } + + # JSON 字段实体示例 + role_entity: + input: + module_code: "system" + table_name: "system_role" + table_comment: "角色" + tenant_type: "multi" + business_fields: + - name: "name" + type: "VARCHAR(100)" + comment: "角色名称" + - name: "code" + type: "VARCHAR(50)" + comment: "角色标识" + - name: "data_scope_dept_ids" + type: "JSON" + comment: "数据范围部门ID数组" + json_field: true + java_type: "Set" + + output_code: | + package cn.iocoder.yudao.module.system.dal.dataobject.permission; + + import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; + import com.baomidou.mybatisplus.annotation.KeySequence; + import com.baomidou.mybatisplus.annotation.TableField; + import com.baomidou.mybatisplus.annotation.TableId; + import com.baomidou.mybatisplus.annotation.TableName; + import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; + import lombok.*; + + import java.util.Set; + + /** + * 角色 DO + * + * @author yudao + */ + @TableName(value = "system_role", autoResultMap = true) + @KeySequence("system_role_seq") + @Data + @EqualsAndHashCode(callSuper = true) + @Builder + @NoArgsConstructor + @AllArgsConstructor + public class RoleDO extends TenantBaseDO { + + /** + * 主键ID + */ + @TableId + private Long id; + + /** + * 角色名称 + */ + private String name; + + /** + * 角色标识 + */ + private String code; + + /** + * 数据范围(指定部门数组) + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Set dataScopeDeptIds; + + } + + # 忽略租户实体示例 + tenant_entity: + input: + module_code: "system" + table_name: "system_tenant" + table_comment: "租户" + tenant_type: "ignore" + business_fields: + - name: "name" + type: "VARCHAR(100)" + comment: "租户名称" + - name: "contact_name" + type: "VARCHAR(50)" + comment: "联系人" + - name: "status" + type: "TINYINT" + comment: "状态" + enum_ref: "CommonStatusEnum" + + output_code: | + package cn.iocoder.yudao.module.system.dal.dataobject.tenant; + + import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; + import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; + import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; + import com.baomidou.mybatisplus.annotation.KeySequence; + import com.baomidou.mybatisplus.annotation.TableId; + import com.baomidou.mybatisplus.annotation.TableName; + import lombok.*; + + /** + * 租户 DO + * + * @author yudao + */ + @TenantIgnore + @TableName(value = "system_tenant", autoResultMap = true) + @KeySequence("system_tenant_seq") + @Data + @EqualsAndHashCode(callSuper = true) + @Builder + @NoArgsConstructor + @AllArgsConstructor + public class TenantDO extends BaseDO { + + /** + * 主键ID + */ + @TableId + private Long id; + + /** + * 租户名称 + */ + private String name; + + /** + * 联系人 + */ + private String contactName; + + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + + } + +# ============================================ +# 与其他 Skill 的关系 +# ============================================ +related_skills: + upstream: + - skill: "db-designer" + relationship: "依赖" + description: "依赖数据库表结构设计,从 SQL 解析表信息" + path: "skills/design/db-designer.yaml" + + downstream: + - skill: "api-designer" + relationship: "输出" + description: "实体类作为 API 设计的输入" + path: "skills/design/api-designer.yaml" + + collaboration: + - path: "skills/usage/entity-implementation.md" + relationship: "协同" + description: "entity-designer 专注实体类设计规范,entity-implementation 提供完整实现流程(含 Mapper、Service 等)" + +# ============================================ +# 错误处理 +# ============================================ +error_handling: + errors: + - code: "ENT001" + message: "表名不符合命名规范" + solution: "检查表名前缀和格式,确保包含模块前缀(如 mes_、erp_)" + + - code: "ENT002" + message: "缺少必需的表注释" + solution: "提供 table_comment 参数" + + - code: "ENT003" + message: "字段类型无法映射" + solution: "使用默认 String 类型,手动调整" + + - code: "ENT004" + message: "JSON 字段缺少 java_type" + solution: "为 JSON 字段指定 java_type,如 Set" + + - code: "ENT005" + message: "租户类型选择错误" + solution: "multi 使用 TenantBaseDO,single 使用 BaseDO,ignore 使用 @TenantIgnore" + + - code: "ENT006" + message: "枚举字段缺少 enum_ref" + solution: "为枚举字段提供 enum_ref 参数,在注释中引用枚举类" \ No newline at end of file diff --git a/.claude/skills/flyway-postgresql/SKILL.md b/.claude/skills/flyway-postgresql/SKILL.md new file mode 100644 index 00000000..ce6a8dd6 --- /dev/null +++ b/.claude/skills/flyway-postgresql/SKILL.md @@ -0,0 +1,115 @@ +--- +name: flyway-postgresql +description: Flyway PostgreSQL migrations for this project. Use when adding or changing database schema, indexes, constraints, required seed data, migration baselines, or Flyway configuration. +--- + +# Flyway PostgreSQL migrations + +Use a **forward-only** migration process. Treat `flyway_schema_history` as immutable release history. + +## 1. Inspect the migration state + +Before editing: + +1. Read `CLAUDE.md` PostgreSQL and Flyway rules. +2. Inspect `yudao-server/src/main/resources/application-{local,dev}.yaml` and `yudao-server/pom.xml` when configuration is involved. +3. List every `db/migration` directory and versioned migration across active modules. +4. Inspect the target table DO, Mapper, service use, and relevant PostgreSQL DDL. +5. Check the working tree so existing uncommitted work is preserved. + +**Complete when:** the active Flyway locations, baseline, highest migration version, affected database objects, and pending user changes are known. + +## 2. Choose the migration branch + +### New schema change + +Create a new versioned SQL migration under: + +```text +/src/main/resources/db/migration// +``` + +Use the next unused project-wide version after `V4010`. Leave gaps of 10 for normal changes when practical: + +```text +V4020__add_student_progress.sql +V4030__add_practice_report_index.sql +``` + +### Fix an executed migration + +Create a higher version that repairs or reverses the prior change. Preserve the executed file byte-for-byte. + +### Existing database adoption + +The current baseline is `4009`; `V4010__initialize_education_flyway.sql` is the first managed migration. Keep `baseline-on-migrate` only while existing environments are being adopted. After every existing environment has a baseline record, change it to `false` in a separate reviewed change. + +### Configuration change + +Flyway must target the dynamic datasource `master`, never `slave`. Keep these safeguards enabled: + +```yaml +validate-on-migrate: true +clean-disabled: true +out-of-order: false +``` + +Allow `FLYWAY_URL`, `FLYWAY_USER`, and `FLYWAY_PASSWORD` to override master credentials. + +**Complete when:** exactly one branch is selected and its version/configuration does not conflict with the current repository state. + +## 3. Write PostgreSQL-native SQL + +Follow these project conventions: + +- Identity primary key: `BIGINT GENERATED BY DEFAULT AS IDENTITY`. +- Time: `TIMESTAMP`; use `CURRENT_TIMESTAMP` for defaults. +- Boolean: `BOOLEAN NOT NULL DEFAULT false` where appropriate. +- Idempotency/upsert: `ON CONFLICT ... DO NOTHING` or `DO UPDATE SET ... EXCLUDED.column`. +- Null fallback: `COALESCE`. +- Date formatting: `TO_CHAR`; date parts: `EXTRACT`. +- Bounded delete: delete by IDs selected in an ordered, limited subquery or CTE. +- Add comments for business tables and non-obvious columns. +- Add indexes from observed query and conflict targets, not speculation. +- Required seed data must be deterministic and idempotent. +- Put `CREATE INDEX CONCURRENTLY` in its own non-transactional migration; otherwise prefer transactional PostgreSQL DDL. + +Keep verification queries in comments when useful. Put rollback notes in the change description or a separate operational document; production recovery is another forward migration. + +**Complete when:** every affected object, data backfill, constraint, index, and application assumption is represented in PostgreSQL-native SQL. + +## 4. Align application code + +Update all affected DOs, Mappers, services, tests, and fixtures. Search Java annotations and MyBatis XML for stale column names and incompatible SQL. For a new identity/sequence-backed DO, follow the surrounding project’s `@TableId` and `@KeySequence` pattern. + +**Complete when:** every code reference agrees with the post-migration schema and no active runtime SQL depends on the previous shape. + +## 5. Verify + +Run, in order: + +```bash +git diff --check +mvn -pl yudao-server -am -DskipTests clean compile +``` + +Confirm each migration is present under the owning module’s `target/classes/db/migration/...` after compilation. If an authorized disposable PostgreSQL database is available, run Flyway against it and inspect: + +```sql +SELECT installed_rank, version, description, script, checksum, success +FROM flyway_schema_history +ORDER BY installed_rank; +``` + +Run focused tests for the affected module. Report any skipped database execution separately from compilation success. + +**Complete when:** formatting and compilation pass, migration packaging is confirmed, focused tests pass or their exact blocker is reported, and any real-database migration status is stated truthfully. + +## Release rules + +- Version numbers are project-wide across every Flyway location. +- One committed migration version has one immutable meaning. +- Production migrations move forward; recovery is a higher version. +- `clean` remains disabled. +- Demo/test seed data lives outside production migrations. +- Do not copy the legacy `sql/postgresql/ruoyi-vue-pro.sql` dump into a versioned runtime migration; it contains destructive bootstrap statements and embedded transactions. Use it only to initialize a disposable empty database or to establish the pre-Flyway baseline. diff --git a/.claude/skills/index.yaml b/.claude/skills/index.yaml new file mode 100644 index 00000000..990fc291 --- /dev/null +++ b/.claude/skills/index.yaml @@ -0,0 +1,152 @@ +# Skill 索引文件 +# 记录所有模块的Skill文档位置和状态 + +skills: + # 核心模块 + system: + name: "系统管理模块" + path: "skills/modules/system/skill-system.yaml" + status: "completed" + priority: 1 + lines: 839 + + infra: + name: "基础设施模块" + path: "skills/modules/infra/skill-infra.yaml" + status: "completed" + priority: 1 + lines: 700+ + + pay: + name: "支付模块" + path: "skills/modules/pay/skill-pay.yaml" + status: "completed" + priority: 1 + lines: 654 + + # 业务模块 + member: + name: "会员模块" + path: "skills/modules/member/skill-member.yaml" + status: "completed" + priority: 2 + lines: 608 + + mall: + name: "商城模块" + path: "skills/modules/mall/skill-mall.yaml" + status: "completed" + priority: 2 + lines: 650+ + + crm: + name: "CRM模块" + path: "skills/modules/crm/skill-crm.yaml" + status: "completed" + priority: 2 + lines: 850+ + + erp: + name: "ERP模块" + path: "skills/modules/erp/skill-erp.yaml" + status: "completed" + priority: 3 + lines: 900+ + + # 技术/平台模块 + bpm: + name: "工作流模块" + path: "skills/modules/bpm/skill-bpm.yaml" + status: "completed" + priority: 2 + lines: 650+ + + ai: + name: "AI模块" + path: "skills/modules/ai/skill-ai.yaml" + status: "completed" + priority: 3 + lines: 670+ + + iot: + name: "物联网模块" + path: "skills/modules/iot/skill-iot.yaml" + status: "completed" + priority: 3 + lines: 721 + + mp: + name: "微信公众号模块" + path: "skills/modules/mp/skill-mp.yaml" + status: "completed" + priority: 3 + lines: 660+ + + report: + name: "报表模块" + path: "skills/modules/report/skill-report.yaml" + status: "completed" + priority: 3 + lines: 320+ + +# 设计规范 +design: + db: + name: "数据库设计" + path: "skills/design/db-designer.yaml" + status: "completed" + description: "数据库表结构设计规范、SQL文件规范、编写规范" + lines: 450 + entity: + name: "实体类设计" + path: "skills/design/entity-designer.yaml" + status: "completed" + description: "yudao 实体类(DO)设计规范,包括继承体系、注解规范、命名转换" + lines: 520 + api: + name: "API 设计" + path: "skills/design/api-designer.yaml" + status: "completed" + description: "REST API 设计规范、注解、请求响应格式、权限标识" + crud: + name: "CRUD 代码生成" + path: "skills/design/crud-designer.yaml" + status: "completed" + description: "根据数据库表结构自动生成完整CRUD功能代码" + lines: 517 + +# 使用样例自动引用说明 +# 使用样例文档(usage/目录)通过 YAML front matter 声明需要引用的规范文件 +# AI 读取使用样例时会自动加载相关规范,确保生成的代码符合项目标准 +usage_auto_reference: + description: "使用样例通过 YAML front matter 自动引用设计规范" + mechanism: | + 每个使用样例文档头部包含 references 配置,声明该场景需要引用的规范文件: + - design: 设计规范(db-designer, entity-designer, api-designer, crud-designer) + - module_guide: 模块引用提示(用户指定模块后加载对应 skill) + - patterns: 设计模式引用(factory, strategy, template-method) + - templates: 模板文件引用 + example: | + --- + references: + design: + - skills/design/db-designer.yaml + - skills/design/entity-designer.yaml + module_guide: + prompt: "请指定目标模块" + mapping: + mes: skills/modules/mes/skill-mes.yaml + --- + +# 提取进度 +progress: + total: 12 + completed: 12 + in_progress: 0 + pending: 0 + +# 最后更新时间 +metadata: + created_at: "2026-03-18" + updated_at: "2026-03-31" + completed_at: "2026-03-18" \ No newline at end of file diff --git a/.claude/skills/modules/ai/skill-ai.yaml b/.claude/skills/modules/ai/skill-ai.yaml new file mode 100644 index 00000000..9cdf7fe8 --- /dev/null +++ b/.claude/skills/modules/ai/skill-ai.yaml @@ -0,0 +1,706 @@ +# Skill 文件 - AI 模块 +# 用于提取模块知识的标准格式 + +skill: + id: "skill-ai" + name: "AI Skill" + version: "1.0.0" + module_path: "yudao-module-ai" + created_at: "2026-03-18" + updated_at: "2026-03-18" + +# ============================================ +# 第一阶段:设计理念 +# ============================================ +philosophy: + # 业务定位:模块解决什么业务问题?在整个系统中的定位? + business_position: | + AI 模块是系统的智能化核心,提供大模型集成能力,支持多种 AI 能力: + 1. 聊天对话:支持多模型切换、流式响应、知识库增强、联网搜索 + 2. 图像生成:支持 OpenAI DALL-E、Stable Diffusion、Midjourney、硅基流动等 + 3. 音乐生成:支持 Suno AI 音乐创作 + 4. 知识库:RAG 检索增强生成,支持文档分片、向量存储、语义检索 + 5. 写作助手:AI 辅助写作 + 6. 思维导图:AI 生成思维导图 + 7. 工作流:可视化 AI 工作流编排 + + # 设计原则:遵循了哪些设计原则(SOLID、DDD)?有哪些架构决策? + design_principles: + - "策略模式:通过 AiModelFactory 统一创建不同平台的 AI 模型实例" + - "工厂模式:AiModelFactoryImpl 负责创建 ChatModel、ImageModel、EmbeddingModel 等" + - "单例缓存:使用 Hutool Singleton 缓存 AI 客户端实例,避免重复创建" + - "Spring AI 集成:基于 Spring AI 框架,实现多模型统一抽象" + - "开放-封闭原则:新增 AI 平台只需扩展 AiPlatformEnum 和工厂方法" + - "接口隔离:Service 层接口按领域划分,职责单一" + + # 领域模型:核心领域对象有哪些?领域对象之间的关系?聚合根是什么? + domain_model: + aggregates: + - name: "聊天会话" + type: "聚合根" + entities: ["AiChatMessageDO"] + description: "用户发起的对话,包含多条消息" + - name: "知识库" + type: "聚合根" + entities: ["AiKnowledgeDocumentDO", "AiKnowledgeSegmentDO"] + description: "知识库包含多个文档,文档包含多个分段" + - name: "AI 模型配置" + type: "聚合根" + entities: ["AiApiKeyDO"] + description: "模型配置关联 API 密钥" + + value_objects: + - name: "AiPlatformEnum" + description: "AI 平台枚举,定义支持的 AI 服务商" + - name: "AiModelTypeEnum" + description: "模型类型枚举:对话、图像、语音、视频、向量、重排序" + + services: + - name: "AiChatMessageService" + description: "聊天消息服务,处理对话生成" + - name: "AiImageService" + description: "图像生成服务" + - name: "AiKnowledgeService" + description: "知识库管理服务" + - name: "AiModelService" + description: "模型配置管理服务" + - name: "AiMusicService" + description: "音乐生成服务" + +# ============================================ +# 第二阶段:架构设计 +# ============================================ +architecture: + # 分层架构 + layers: + - name: "controller" + purpose: "HTTP 接口层" + components: + - "AiChatMessageController: 聊天消息接口" + - "AiChatConversationController: 对话管理接口" + - "AiImageController: 图像生成接口" + - "AiKnowledgeController: 知识库管理接口" + - "AiModelController: 模型配置接口" + - "AiMusicController: 音乐生成接口" + - "AiWriteController: 写作助手接口" + - "AiMindMapController: 思维导图接口" + - "AiWorkflowController: 工作流接口" + + - name: "service" + purpose: "业务逻辑层" + components: + - "chat/: 聊天相关服务" + - "image/: 图像生成服务" + - "knowledge/: 知识库服务" + - "model/: 模型配置服务" + - "music/: 音乐生成服务" + - "write/: 写作服务" + - "mindmap/: 思维导图服务" + - "workflow/: 工作流服务" + + - name: "dal" + purpose: "数据访问层" + components: + - "dataobject/: DO 实体类" + - "mysql/: MyBatis Mapper 接口" + + - name: "framework/ai" + purpose: "AI 框架集成层" + components: + - "core/model/: 模型工厂和多平台适配" + - "core/webserch/: 联网搜索客户端" + - "config/: 自动配置类" + + # 设计模式应用 + design_patterns: + - pattern: "工厂模式" + location: "framework/ai/core/model/AiModelFactory.java" + purpose: "统一创建各平台 AI 模型实例,屏蔽创建细节" + - pattern: "策略模式" + location: "framework/ai/core/model/AiModelFactoryImpl.java" + purpose: "根据平台类型选择不同的模型创建策略" + - pattern: "单例模式" + location: "framework/ai/core/model/AiModelFactoryImpl.java" + purpose: "使用 Hutool Singleton 缓存 AI 客户端,避免重复创建连接" + - pattern: "模板方法" + location: "service/chat/AiChatMessageServiceImpl.java" + purpose: "聊天消息处理的统一流程" + + # 模块间通信 + communication: + apis: [] + # 暂无对外暴露的 RPC API + consumers: + - module: "system" + api: "AdminUserApi" + purpose: "获取用户信息" + mq: [] + # 暂未使用消息队列 + +# ============================================ +# 第三阶段:数据表设计 +# ============================================ +data_model: + # 实体继承体系 + entity_hierarchy: + base: "BaseDO" + description: "所有实体继承 BaseDO,包含 id、creator、createTime、updater、updateTime、deleted 字段" + + # 核心数据表 + tables: + # ========== 模型配置 ========== + - name: "ai_api_key" + comment: "AI API 密钥表" + entity: "AiApiKeyDO" + extends: "BaseDO" + columns: + - { name: "id", type: "Long", comment: "编号" } + - { name: "name", type: "String", comment: "名称" } + - { name: "api_key", type: "String", comment: "密钥" } + - { name: "platform", type: "String", comment: "平台,枚举 AiPlatformEnum" } + - { name: "url", type: "String", comment: "API 地址" } + - { name: "status", type: "Integer", comment: "状态" } + + - name: "ai_model" + comment: "AI 模型配置表" + entity: "AiModelDO" + extends: "BaseDO" + columns: + - { name: "id", type: "Long", comment: "编号" } + - { name: "key_id", type: "Long", comment: "API 密钥编号" } + - { name: "name", type: "String", comment: "模型名称" } + - { name: "model", type: "String", comment: "模型标识" } + - { name: "platform", type: "String", comment: "平台" } + - { name: "type", type: "Integer", comment: "类型:1对话 2图片 3语音 4视频 5向量 6重排序" } + - { name: "sort", type: "Integer", comment: "排序值" } + - { name: "status", type: "Integer", comment: "状态" } + - { name: "temperature", type: "Double", comment: "温度参数" } + - { name: "max_tokens", type: "Integer", comment: "最大 Token 数" } + - { name: "max_contexts", type: "Integer", comment: "最大上下文数" } + + - name: "ai_chat_role" + comment: "AI 聊天角色表" + entity: "AiChatRoleDO" + extends: "BaseDO" + columns: + - { name: "id", type: "Long", comment: "编号" } + - { name: "name", type: "String", comment: "角色名称" } + - { name: "avatar", type: "String", comment: "角色头像" } + - { name: "category", type: "String", comment: "角色分类" } + - { name: "description", type: "String", comment: "角色描述" } + - { name: "system_message", type: "String", comment: "角色设定" } + - { name: "user_id", type: "Long", comment: "用户编号" } + - { name: "model_id", type: "Long", comment: "模型编号" } + - { name: "knowledge_ids", type: "List", comment: "知识库编号列表" } + - { name: "tool_ids", type: "List", comment: "工具编号列表" } + - { name: "mcp_client_names", type: "List", comment: "MCP Client 名字列表" } + - { name: "public_status", type: "Boolean", comment: "是否公开" } + - { name: "sort", type: "Integer", comment: "排序值" } + - { name: "status", type: "Integer", comment: "状态" } + + - name: "ai_tool" + comment: "AI 工具表" + entity: "AiToolDO" + extends: "BaseDO" + columns: + - { name: "id", type: "Long", comment: "编号" } + - { name: "name", type: "String", comment: "工具名称(Bean 名字)" } + - { name: "description", type: "String", comment: "工具描述" } + - { name: "status", type: "Integer", comment: "状态" } + + # ========== 聊天对话 ========== + - name: "ai_chat_conversation" + comment: "AI 聊天对话表" + entity: "AiChatConversationDO" + extends: "BaseDO" + columns: + - { name: "id", type: "Long", comment: "编号" } + - { name: "user_id", type: "Long", comment: "用户编号" } + - { name: "title", type: "String", comment: "对话标题" } + - { name: "pinned", type: "Boolean", comment: "是否置顶" } + - { name: "pinned_time", type: "LocalDateTime", comment: "置顶时间" } + - { name: "role_id", type: "Long", comment: "角色编号" } + - { name: "model_id", type: "Long", comment: "模型编号" } + - { name: "model", type: "String", comment: "模型标识" } + - { name: "system_message", type: "String", comment: "角色设定" } + - { name: "temperature", type: "Double", comment: "温度参数" } + - { name: "max_tokens", type: "Integer", comment: "最大 Token 数" } + - { name: "max_contexts", type: "Integer", comment: "最大上下文数" } + + - name: "ai_chat_message" + comment: "AI 聊天消息表" + entity: "AiChatMessageDO" + extends: "BaseDO" + columns: + - { name: "id", type: "Long", comment: "编号" } + - { name: "conversation_id", type: "Long", comment: "对话编号" } + - { name: "reply_id", type: "Long", comment: "回复消息编号" } + - { name: "type", type: "String", comment: "消息类型(USER/ASSISTANT/SYSTEM)" } + - { name: "user_id", type: "Long", comment: "用户编号" } + - { name: "role_id", type: "Long", comment: "角色编号" } + - { name: "model", type: "String", comment: "模型标识" } + - { name: "model_id", type: "Long", comment: "模型编号" } + - { name: "content", type: "String", comment: "聊天内容" } + - { name: "reasoning_content", type: "String", comment: "推理内容" } + - { name: "use_context", type: "Boolean", comment: "是否携带上下文" } + - { name: "segment_ids", type: "List", comment: "知识库段落编号数组" } + - { name: "web_search_pages", type: "List", comment: "联网搜索网页内容" } + - { name: "attachment_urls", type: "List", comment: "附件 URL 数组" } + + # ========== 图像生成 ========== + - name: "ai_image" + comment: "AI 绘画表" + entity: "AiImageDO" + extends: "BaseDO" + columns: + - { name: "id", type: "Long", comment: "编号" } + - { name: "user_id", type: "Long", comment: "用户编号" } + - { name: "prompt", type: "String", comment: "提示词" } + - { name: "platform", type: "String", comment: "平台" } + - { name: "model_id", type: "Long", comment: "模型编号" } + - { name: "model", type: "String", comment: "模型标识" } + - { name: "width", type: "Integer", comment: "图片宽度" } + - { name: "height", type: "Integer", comment: "图片高度" } + - { name: "status", type: "Integer", comment: "生成状态" } + - { name: "finish_time", type: "LocalDateTime", comment: "完成时间" } + - { name: "error_message", type: "String", comment: "错误信息" } + - { name: "pic_url", type: "String", comment: "图片地址" } + - { name: "public_status", type: "Boolean", comment: "是否公开" } + - { name: "options", type: "Map", comment: "绘制参数" } + - { name: "buttons", type: "List