From 0eba587459b6b43df7695457571260352277eb53 Mon Sep 17 00:00:00 2001 From: wangziqi Date: Sat, 1 Aug 2026 12:17:00 +0800 Subject: [PATCH] docs(education): update migration status and add EDU-017~033 tickets --- .gitmodules | 3 + docs/education/migration/00-current-state.md | 4 +- .../migration/01-capability-matrix.md | 28 +- docs/education/migration/02-api-mapping.md | 36 ++- .../migration/03-database-object-mapping.md | 16 +- .../migration/04-module-reuse-map.md | 10 +- docs/education/migration/08-slice-roadmap.md | 11 +- .../migration/12-admin-authoring-ui.md | 282 ++++++++++++++++++ .../issues/EDU-012-classes-relationships.md | 10 +- .../issues/EDU-014-extended-learning.md | 8 +- .../issues/EDU-017-tenant-appearance.md | 38 +++ .../EDU-018-native-payment-social-admin.md | 33 ++ .../EDU-019-learning-activation-codes.md | 34 +++ .../issues/EDU-020-native-pay-runtime.md | 36 +++ .../EDU-021-legacy-pay-account-import.md | 51 ++++ .../issues/EDU-022-native-pay-transactions.md | 45 +++ .../issues/EDU-023-legacy-pay-transactions.md | 41 +++ .../EDU-024-native-pay-transfer-wallet.md | 41 +++ .../issues/EDU-025-native-mall-product.md | 58 ++++ .../issues/EDU-026-native-mall-coupon.md | 51 ++++ .../issues/EDU-027-native-mall-trade-order.md | 53 ++++ .../EDU-028-native-checkout-promotions.md | 36 +++ .../issues/EDU-029-native-trade-delivery.md | 34 +++ .../issues/EDU-030-native-trade-after-sale.md | 36 +++ .../issues/EDU-031-native-trade-brokerage.md | 43 +++ .../EDU-032-native-promotion-seckill.md | 39 +++ .../EDU-033-native-promotion-combination.md | 39 +++ docs/education/migration/issues/README.md | 22 +- .../EducationAssetAdmissionServiceImpl.java | 2 +- .../importjob/ImportObjectScanGateway.java | 4 +- .../InfraFileImportObjectScanGateway.java | 27 -- .../QuestionImportJobServiceImpl.java | 3 +- .../InfraFileImportObjectScanGatewayTest.java | 39 --- .../QuestionImportJobServiceImplTest.java | 27 +- .../yudao/module/infra/api/file/FileApi.java | 7 +- .../module/infra/api/file/FileApiImpl.java | 5 - .../module/infra/api/file/FileDescriptor.java | 9 +- .../module/infra/api/file/FileScanStatus.java | 1 - .../infra/dal/mysql/file/FileMapper.java | 6 - .../file/config/FileScanProperties.java | 29 -- .../config/YudaoFileAutoConfiguration.java | 8 - .../file/core/scan/ClamAvFileScanner.java | 50 ---- .../infra/service/file/FileService.java | 4 - .../infra/service/file/FileServiceImpl.java | 30 -- .../infra/api/file/FileApiImplTest.java | 13 +- .../file/core/scan/ClamAvFileScannerTest.java | 69 ----- .../service/file/FileServiceImplTest.java | 43 --- .../src/main/resources/application.yaml | 8 - yudao-ui/yudao-ui-admin-vben | 1 + yudao-ui/yudao-ui-admin-vben/README.md | 4 - 50 files changed, 1120 insertions(+), 407 deletions(-) create mode 100644 .gitmodules create mode 100644 docs/education/migration/12-admin-authoring-ui.md create mode 100644 docs/education/migration/issues/EDU-017-tenant-appearance.md create mode 100644 docs/education/migration/issues/EDU-018-native-payment-social-admin.md create mode 100644 docs/education/migration/issues/EDU-019-learning-activation-codes.md create mode 100644 docs/education/migration/issues/EDU-020-native-pay-runtime.md create mode 100644 docs/education/migration/issues/EDU-021-legacy-pay-account-import.md create mode 100644 docs/education/migration/issues/EDU-022-native-pay-transactions.md create mode 100644 docs/education/migration/issues/EDU-023-legacy-pay-transactions.md create mode 100644 docs/education/migration/issues/EDU-024-native-pay-transfer-wallet.md create mode 100644 docs/education/migration/issues/EDU-025-native-mall-product.md create mode 100644 docs/education/migration/issues/EDU-026-native-mall-coupon.md create mode 100644 docs/education/migration/issues/EDU-027-native-mall-trade-order.md create mode 100644 docs/education/migration/issues/EDU-028-native-checkout-promotions.md create mode 100644 docs/education/migration/issues/EDU-029-native-trade-delivery.md create mode 100644 docs/education/migration/issues/EDU-030-native-trade-after-sale.md create mode 100644 docs/education/migration/issues/EDU-031-native-trade-brokerage.md create mode 100644 docs/education/migration/issues/EDU-032-native-promotion-seckill.md create mode 100644 docs/education/migration/issues/EDU-033-native-promotion-combination.md delete mode 100644 yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/InfraFileImportObjectScanGateway.java delete mode 100644 yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/importjob/InfraFileImportObjectScanGatewayTest.java delete mode 100644 yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileScanProperties.java delete mode 100644 yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/scan/ClamAvFileScanner.java delete mode 100644 yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/scan/ClamAvFileScannerTest.java create mode 160000 yudao-ui/yudao-ui-admin-vben delete mode 100644 yudao-ui/yudao-ui-admin-vben/README.md diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..8d5a7531 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "yudao-ui/yudao-ui-admin-vben"] + path = yudao-ui/yudao-ui-admin-vben + url = https://git.gongxue100.com/wangziqi/yudao-ui-admin-vben.git diff --git a/docs/education/migration/00-current-state.md b/docs/education/migration/00-current-state.md index 60365e1d..65bbb716 100644 --- a/docs/education/migration/00-current-state.md +++ b/docs/education/migration/00-current-state.md @@ -1,10 +1,10 @@ # Current State -> Phase 0 static assessment originally generated on 2026-07-29. Updated on 2026-07-31 after bounded implementation through V4180, full Education tests against disposable PostgreSQL, server compilation, and the student harness. +> Phase 0 static assessment originally generated on 2026-07-29. Current delivery status updated on 2026-08-01 through V4450/EDU-033; the original investigation remains below as provenance. ## Current implementation summary -The target branch now contains the complete student core loop, tenant/identity enforcement, module-owned PostgreSQL Flyway migrations V4010–V4200, bounded tenant content authoring, import job and asset contracts, class/invitation relationships, education entitlement bindings, a representative extended-learning wave, and operational dependency/health contracts. The full Education suite passed against disposable PostgreSQL (596 tests), the server reactor compiled successfully, and the fixture-based student harness passed six browser flows. These tests executed the module-owned Flyway chain in isolated disposable test schemas; they are not evidence that a shared Pilot or production database was migrated. Production deployment, real Pilot data, production scanner/parser adapters, automatic Mall/Pay/CRM fulfillment events, and selected deferred learning families remain outside the verified implementation. +The target branch now contains the student core loop, tenant/identity enforcement, bounded content/operations/appearance/activation-code capabilities, and module-owned PostgreSQL Flyway migrations V4010–V4450. Native RuoYi Pay, Product, Coupon, normal Trade order, Discount/Reward checkout, delivery, after-sale, brokerage, Seckill, and Combination administration are activated with tenant-composite persistence and their existing APIs, RBAC, services, and Vben pages; Education does not own shadow financial, catalog, order, delivery, refund, commission, or promotion ledgers. The full Flyway suite passes 52 PostgreSQL scenarios through V4450; EDU-031 proves tenant-isolated two-level commission/statistics, EDU-032 proves same-number cross-tenant seckill state plus atomic last-stock competition, and EDU-033 proves tenant-isolated group records plus atomic last-place competition and Trade Order/head consistency. The required non-clean Maven compile chain and complete Vben typecheck pass without interrupting the running server. This is isolated disposable-schema evidence, not proof that a shared Pilot or production database was migrated. Explicit legacy Product/coupon/order/refund mapping, source referral CRM and settlement proof/export import, Bargain/Point and other special-order Promotion families, production deployment/data, automatic purchase fulfillment/refund revocation, and selected deferred learning/platform families remain open. This document retains the original Phase 0 findings below as provenance. Current ticket status is authoritative in [`issues/README.md`](issues/README.md), and rollout evidence is tracked by [`../pilot-acceptance-runbook.md`](../pilot-acceptance-runbook.md). diff --git a/docs/education/migration/01-capability-matrix.md b/docs/education/migration/01-capability-matrix.md index d17e65f0..15e9d933 100644 --- a/docs/education/migration/01-capability-matrix.md +++ b/docs/education/migration/01-capability-matrix.md @@ -2,6 +2,30 @@ > Phase 0 static assessment generated on 2026-07-29. No build, test, application startup, PostgreSQL connection, or Flyway migration was executed during this assessment. +> Execution update 2026-08-01: the `Auth, student profile, and extended learning` row's original `pending migration` cell is superseded by EDU-014 and is now **partially migrated**. V4160–V4200 deliver Member/System-backed auth context, vocabulary, reminders, fixed learning awards, feedback submission, bounded summaries and leaderboard; V4250–V4280 add idempotent Member point business keys, tenant-admin feedback handling/audit, resolved-feedback rewards, configurable badge definitions, automatic practice/vocabulary/feedback rules, lifetime-once grants, System notifications, learning-risk supervision and follow-up tasks, RBAC/data scope, and Vben UI. Generic check-in/point tasks/exchange, check-in/mock-exam/activity badge triggers, automatic scheduled supervision execution, broader trends/exports, and identity-history import remain open and must not be treated as complete. + +> Execution update 2026-08-01: the `Tenant education operations, appearance, integrations, secrets, and codes` row's original `pending migration` cell is now **partially migrated**. V4140 delivered classes, Member relationships, and invitations; V4230 delivered the first class UI; V4270 adds native learning-risk preview, supervision rules, idempotent follow-up tasks, System AdminUser/RBAC/department-self data scopes, and Vben UI. V4290 adds System-fallback branding, public/admin settings, three platform theme templates, optimistic draft/publish lifecycle, a tenant-context public projection, closed secret/rendering validation, four independent permissions, PostgreSQL evidence, and the fourteenth custom Education Vben page. V4300 exposes Pay application/channel and tenant-scoped System social-client administration below Education using their original controllers, permissions, and existing Vben pages; V4320 makes Pay App/Channel operational with fail-closed tenant-scoped PostgreSQL tables and parent validation. V4330/EDU-021 adds a Pay-owned redacted audit plus explicit single-account import for safe `tenant_collect` WeChat/Alipay manifests. V4340/EDU-022 activates tenant-scoped native Pay order/extension/refund/notification tables, callbacks/retry processing, and the three existing Pay administration pages without an Education financial ledger. V4350/EDU-023 adds terminal-only reconciled legacy order/payment/refund aggregate import into those Pay-owned ledgers, a redacted event-digest audit, and an import/history modal on the native order page. V4360/EDU-024 activates empty tenant-scoped native Transfer/Wallet ledgers, tenant-qualified locks, safe amount-changing paths, granular permissions, and the three existing Pay Transfer/Wallet Vben pages; it does not infer historical balances. V4310 adds tenant-owned learning activation-code batches/codes and the fifteenth custom Education Vben page while composing Mall-owned SPU binding, Member principal, and the idempotent Education entitlement pipeline. Plaintext activation codes are returned once; only SHA-256 digest and mask persist. Domains remain System-owned and coupons remain Mall Promotion-owned. Production bulk financial export/runbooks and reviewed opening balances, automatic purchase fulfillment/refund revocation, tenant SMS/PNVS, encrypted secret rotation, non-equivalent payment modes/providers, legacy activation-code import, coupons, scheduled production rule execution, and the rest of tenant operations remain open. + +> Execution update 2026-08-01: V4370/EDU-025 enables the Mall reactor and Product server module, changes all nine Product records to `TenantBaseDO`, creates tenant-composite PostgreSQL catalog tables and constraints, and reuses the five native Product Vben pages with their exact controller permissions. + +> EDU-025 supersedes older matrix cells that list Mall Product activation as open. V4370 activates the tenant-scoped native brand/category/property/SPU/SKU/comment/favorite/history catalog and five existing Product administration pages. It deliberately does not infer SPUs, SKUs, prices, stock, brands, or properties from the legacy display-only `products` projection. + +> Execution update 2026-08-01: V4380/EDU-026 enables the Promotion server module, changes native `CouponTemplateDO` and `CouponDO` to `TenantBaseDO`, creates tenant-composite coupon template/instance persistence, and reuses the two native Promotion coupon Vben pages with exact controller permissions. It does not reinterpret legacy code campaigns/redemptions as pre-issued member coupons. EDU-027 subsequently activates the normal Trade order core; Statistics, other Promotion/Trade table families, and explicit legacy Product/code-coupon import remain separate. + +> Execution update 2026-08-01: V4390/EDU-027 enables the native Trade server module, tenantizes order/cart/config records, supplies tenant-composite order-core persistence, and reuses the native order/config Vben pages and permissions. Legacy orders remain unimported because verified Member, SPU/SKU, price-allocation, and lifecycle mappings are absent. Trade after-sale/delivery/brokerage tables, special-order Promotion families, Statistics, and explicit legacy order import remain separate. + +> Execution update 2026-08-01: V4400/EDU-028 tenantizes native Discount Activity/Product and Reward Activity records, creates the three PostgreSQL tables queried by every normal Trade price calculation, and reuses the native discount/reward APIs, exact permissions, and two existing Vben pages. Empty real API lookups now succeed against PostgreSQL; delivery, configured payment runtime, special-order Promotion families, and end-to-end checkout evidence remain separate. + +> Execution update 2026-08-01: V4410/EDU-029 tenantizes all five native Trade delivery records, creates express-company/template/charge/free/pickup persistence, connects Product templates and pickup orders through tenant-qualified references, and reuses the three existing Vben pages with exact permissions. A real Spring/MyBatis calculation resolves a persisted tenant template and adds the expected freight. Configured Pay runtime, after-sale/brokerage, special orders, and target-environment checkout evidence remain separate. + +> Execution update 2026-08-01: V4420/EDU-030 tenantizes native Trade after-sale and log records, creates their PostgreSQL persistence and tenant-qualified Order/Order Item/Product/Pay Refund/Delivery references, and reuses the native app/admin state machine plus the corrected Vben list/detail page with five exact permissions. A real Spring/MyBatis test proves tenant-isolated create/page/detail/log behavior. Source aggregate UUID refunds remain unimported until verified legacy Member/Product/Order Item mappings exist; Trade brokerage, special orders, fulfillment/revocation orchestration, and target-environment refund evidence remain separate. + +> Execution update 2026-08-01: V4430/EDU-031 tenantizes native Trade brokerage user/record/withdrawal records, creates tenant-composite PostgreSQL relationships and Pay Transfer/Order references, and reuses native Member-backed two-level commissions, freeze/unfreeze, withdrawal APIs/jobs, eight exact permissions, and three corrected Vben pages. A real Spring/MyBatis test proves same-ID cross-tenant teams, balances, summaries, and annotated ranking SQL remain isolated. Source referral CRM, UUID settlement aggregates, and proof/export events remain unimported pending explicit Member/lead/Order/evidence mapping. + +> Execution update 2026-08-01: V4440/EDU-032 tenantizes native Promotion seckill configuration/activity/product records, creates tenant-composite PostgreSQL Product/Trade references and consistency triggers, and reuses native time-slot/activity/atomic-stock services, nine exact permissions, and two corrected Vben pages. A real Spring/MyBatis test proves same-ID cross-tenant records, isolated reads, atomic last-stock competition, restoration, close propagation, and used-slot protection. The source has no seckill capability, so the native tables intentionally start empty; later Combination activation and remaining families are tracked separately. + +> Execution update 2026-08-01: V4450/EDU-033 tenantizes native Promotion Combination activity/product/record objects, creates tenant-composite Product/Trade/record/head references and capacity/snapshot triggers, and reuses native activity/group/job services, six exact permissions, and corrected Vben pages. A real Spring/MyBatis test proves same-ID cross-tenant activities/products, isolated page/record/summary reads, snapshot propagation, real head IDs, atomic last-place competition, order/head consistency, and record-backed deletion protection. The source `combination` token is an education question type rather than group buying, so native tables intentionally start empty; Bargain, Point, and other special-order families remain separate. + Statuses are restricted to the Goal vocabulary. Evidence marked as verified is static repository evidence. | Legacy capability | Legacy code location | Legacy database objects | Business value | Target module | Existing capability to reuse | Education gap | Other-module change | Priority | Risk | Verification | Status | Evidence | Open decision | @@ -10,8 +34,8 @@ Statuses are restricted to the Goal vocabulary. Evidence marked as verified is s | Student core learning loop | /Users/tiku1/code/tiku-backend/apps/api/src/nest/learning.module.ts:23-113
/Users/tiku1/code/tiku-backend/apps/api/src/features/learning/use-cases.ts
/Users/tiku1/code/tiku-backend/apps/api/src/features/learning/access.ts | Education catalog and practice/report/idempotency/wrong-question/favorite tables.
Verified: native catalog V4020 is a dirty module resource; practice/report/idempotency DDL is currently in untracked sql/postgresql/education files rather than proven active Flyway history. | Provides the student loop from published catalog browsing through practice creation, answer saving, restore, submission, report, wrong questions, and favorites. | Education | Education provider/adapter boundary, Member/System identity, database uniqueness and transactions, framework locks/idempotency only as supplements—not replacements—for atomic database claims. | Verified: commit ce02f8a contains committed access/core controllers, safe projections, and focused tests, while native provider/catalog and additional core-loop work are dirty; these statuses must be separated. Verified: native and Scalar providers disagree on malformed/absent options; QuestionCatalogService and SessionResponseAssembler can emit apparently valid empty options. Verified: submit idempotency performs check-then-insert rather than atomic initial reservation. Inference: core-loop completion and concurrency guarantees are not established. | Education owns education-domain state and orchestration; Member/System context is reused. Paid/private access remains blocked on an entitlement decision. | P0 | High: corrupt assessment content, mode-dependent behavior, duplicate state transitions, answer leakage, or unauthorized access. | Provider-neutral tests across Scalar and Java, browsing/collection/practice-create/restore safe projections, malformed/unavailable/unpublished fail-closed cases, cross-tenant cases, and PostgreSQL concurrent same-key/different-key submit tests. | partially migrated | Verified ce02f8a, target repository, for committed EducationAccessService, core controllers/services, projections, and HTTP/service tests.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/config/EducationProperties.java:34-36 defaults to SCALAR_READ.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/integration/scalar/config/ScalarAutoConfiguration.java:30-35 selects Scalar for SCALAR_READ/missing mode.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/catalog/provider/JavaCatalogProvider.java:397-417, ScalarCatalogProvider.java:736-751, QuestionCatalogServiceImpl.java:182-202, SessionResponseAssembler.java:69-89.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/practice/PracticeSessionServiceImpl.java:294-307,397-409.
Verified /Users/tiku1/code/ruoyi-vue-pro/sql/postgresql/education/009-education-idempotency-unified.sql:81, but no execution evidence. | Select the pilot-authoritative provider or require a provider-neutral contract; define valid option structure by question type and absent-option semantics; define atomic submit claim/crash recovery; decide entitlement contract before paid/private practice. | | Education catalog and question content | /Users/tiku1/code/tiku-backend/apps/api/src/nest/catalog.module.ts:74-138
/Users/tiku1/code/tiku-backend/apps/nest/tenant-content.module.ts | V4020 native catalog tables for regions, schools, majors, subjects, categories, banks, questions, versions, content, collections, blueprints, and bindings.
Legacy catalog/question/content/asset tables, constraints, functions, triggers, grants, and RLS are reference objects requiring semantic mapping. | Supplies reusable published catalog, question, classification, and content reads for student and future admin workflows. | Education | Education Provider boundary, explicit CatalogScopeQuery, framework tenant context, Infra File public API for future assets. | Verified: current native reads intentionally run inside TenantUtils.executeIgnore and apply explicit scope predicates; this is a controlled manual-isolation boundary, not proof of a current leak. Verified: V4020 uses ordinary single-column foreign keys, so tenant-owned/public graph consistency is not enforced. Inference: every mapper needs audit and content admission needs composite constraints or equivalent enforcement. | Education owns domain reads; Infra File may later provide asset transport. No provider expansion should occur before provider authority and graph-integrity rules are decided. | P0 | High if manual scope is bypassed or invalid cross-tenant/public relationships are admitted. | Inventory every mapper, provider contract tests, invalid graph insert tests, malformed/unpublished tests, PostgreSQL Flyway syntax/resource-packaging checks, and runtime migration evidence only when executed. | partially migrated | Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/catalog/provider/JavaCatalogProvider.java:82-89.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/dal/mysql/catalog/CatalogScopeQuery.java:12-20.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-education/src/main/resources/db/migration/education/V4020__create_native_catalog.sql:38-123,133-152,160-265,324-386.
Verified /Users/tiku1/code/tiku-backend/supabase/migrations/202606210008_content_navigation_practice.sql:3-178. | Choose Scalar-only, native PostgreSQL, or explicit coexistence; define tenant_id=0 PUBLIC graph semantics and composite-key strategy; decide whether source RLS/functions/triggers are contractual. | | Auth, student profile, and extended learning | /Users/tiku1/code/tiku-backend/apps/api/src/nest/auth.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/profile.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/learning.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/features/profile/ | Legacy auth/session/verification/OAuth/phone-binding objects.
Legacy profile/check-in/points/tasks/exchange/notifications/badges/feedback/exam-countdown objects.
Legacy leaderboard/history/report/stats/trend/vocabulary progress/review/favorites/stats objects. | Covers broader student learning and profile experiences beyond the core loop, preserving discoverable legacy behavior and its disposition. | Member + System + Education, with Infra composition | Member/System auth and profile primitives, System/Infra notifications, Member points/levels where semantics match, Education-specific projections and authorization. | Verified source inventory shows these are distinct required Phase 0 domains, not merely generic context or secondary engagement. Target ownership and compatibility are not established. Inference: the definition-of-done is unsupported until each endpoint/state family is classified. | Member/System own authentication and generic membership; Education owns education-specific profile/progress projections. System/Infra may own notifications, while product owners must decide points, badges, feedback, exams, and vocabulary ownership. | P1 | High for auth compatibility and medium for omitted student progress/profile behavior. | Endpoint/API mapping, principal and tenant tests, profile redaction, progress/report compatibility, vocabulary state transitions, and explicit retired/product-decision checks. | pending migration | Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/auth.module.ts:31-56.
Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/profile.module.ts:38-87.
Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/learning.module.ts:23-60,70-113.
Verified /Users/tiku1/code/ruoyi-vue-pro/docs/education/migration/GOAL.md:126-141,393-405. | For every Auth/Profile/extended Learning family, assign Member/System/Education/Infra ownership, compatibility requirement, data disposition, and phase; decide vocabulary, analytics, feedback, exam dates, notifications, points, and badges. | -| Tenant education operations, appearance, integrations, secrets, and codes | /Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-classes.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-appearance.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-integrations.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-secrets.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-codes.module.ts | Legacy classes, student relationships, supervision, roles/configuration, branding/settings/themes, domains, payment accounts, auth-provider configuration, tenant secrets, activation codes, coupons/redemptions, integrations, and marketing objects. | Enables tenant administrators to operate education organizations while preserving separate security and ownership boundaries. | Education + System + Member + Mall/Pay + Infra | System Tenant/RBAC/DataPermission/AdminUserApi, Member relationships, Mall/Pay/Member APIs, Infra secret/file/message/audit facilities. | Verified: classes/supervision were only part of the source tenant-admin surface. Appearance/theme lifecycle, domains/payment/auth integrations, secret rotation, and codes/coupons are separate migration/security surfaces with no verified target equivalent. Inference: collapsing them into one row would hide authorization and secret-handling decisions. | System RBAC/DataPermission and tenant configuration are reused; Mall/Pay/Member own commercial primitives; Infra owns secrets/messaging/files where applicable; Education owns only domain relationships and configuration extensions. | P1 | High: admin scope, secret leakage, payment configuration, and code redemption errors. | Permission matrix, row-scope negatives, secret redaction/rotation, integration authorization, code/coupon idempotency, audit, and cross-tenant tests. | pending migration | Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-appearance.module.ts:17-41.
Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-integrations.module.ts:18-42.
Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-secrets.module.ts:13-21.
Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-codes.module.ts:12-31.
Verified /Users/tiku1/code/tiku-backend/supabase/migrations/202606290002_tenant_classes.sql.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/annotation/DataPermission.java:12-32. | Define class/student/teacher scope semantics and separately decide appearance, domain, payment/auth integration, secret, activation-code, coupon, public-bank grant, and marketing ownership or retirement. | +| Tenant education operations, appearance, integrations, secrets, and codes | /Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-classes.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-appearance.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-integrations.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-secrets.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/tenant-admin-codes.module.ts | Legacy classes, student relationships, supervision, roles/configuration, branding/settings/themes, domains, payment accounts, auth-provider configuration, tenant secrets, activation codes, coupons/redemptions, integrations, and marketing objects. | Enables tenant administrators to operate education organizations while preserving separate security and ownership boundaries. | Education + System + Member + Mall/Pay + Infra | System Tenant/RBAC/DataPermission/AdminUserApi, tenant-scoped System Social Client, native Pay App/Channel/Order/Refund/Notify/Transfer/Wallet, Member relationships, Mall Product, Promotion Coupon/Seckill/Combination, Trade Order/Cart/Config/Delivery/After Sale/Brokerage, Education entitlement events, and Infra secret/file/message/audit facilities. | V4290 owns only presentation extensions. V4300–V4330 reuse native Pay/System configuration and bounded Pay account import. V4340–V4360 activate Pay transactions and Transfer/Wallet; V4370 activates Product; V4380 activates coupon templates/instances; V4390 activates the native Trade order/cart/config core; V4400 activates always-invoked Discount/Reward rules; V4410 activates delivery; V4420 activates native after-sale; V4430 activates native brokerage; V4440 activates native Seckill; V4450 activates native Combination activity/product/group records, capacity protection, Trade bridge, and corrected Vben pages. V4310 owns only activation-code issuance/redemption and composes product binding/entitlement. Production bulk tooling and reviewed opening balances, legacy product/code-coupon/order/refund and referral/settlement-proof import, Bargain/Point and other Promotion families, fulfillment/refund revocation, System-global SMS versus tenant PNVS, encrypted secrets, public-bank grants, and other marketing remain distinct gaps. | System RBAC/DataPermission and tenant configuration are reused; Pay owns payment configuration and ledgers, System owns social providers, Mall owns SPUs/coupons/promotions/orders/delivery/after-sale/brokerage, Member owns principals, Education owns activation-code state and learning-entitlement orchestration, and Infra owns secrets/messaging/files where applicable. | P1 | High: admin scope, secret leakage, payment/order/refund/commission/promotion lifecycle, and code redemption errors. | Native permission/menu shape, Pay/System/Mall tenant-scope tests, provider-data import tests, native Pay/Product/Coupon/Trade/Promotion regressions, secret redaction/rotation, activation/code-coupon idempotency, stock/capacity concurrency, audit, and cross-tenant tests. | partially migrated | Verified V4290 appearance through V4450 Promotion Combination contracts, including native Pay/Product/Coupon/Trade/Promotion UI and tenant-aware records.
Verified provider mapping, reconciliation, replay/conflict, audit redaction, composite tenant references, wallet locks/amount safety, delivery calculation, after-sale service/log isolation, brokerage relationship/commission/statistics isolation, seckill stock concurrency, combination group capacity/order consistency, and fail-closed global-table adoption.
Verified digest-only activation-code persistence, tenant isolation, atomic redemption, dependency rejection, and concurrent unique winner.
Verified System Social Client remains tenant-aware and legacy integration/secrets/codes controllers remain inventoried. | Keep domains in System; use Pay for payment and System Social Client for supported OAuth providers. Activation codes are Education learning-access credentials; Product, coupons, promotions, orders, delivery, after-sale, brokerage, and special-order promotion remain Mall-owned. Continue other special-order Promotion families, production financial/order/refund reconciliation, explicit legacy commerce/referral/settlement-proof import, automatic fulfillment/refund revocation, non-equivalent provider replacement, tenant PNVS, encrypted secrets, public-bank grants, and other marketing separately. | | Platform administration and governance | /Users/tiku1/code/tiku-backend/apps/api/src/nest/platform-admin-overview.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/platform-admin-permissions.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/platform-admin-*.module.ts | Legacy platform staff, tenant lifecycle/billing profiles, public question-bank grant/sync, SaaS plan/invoice/usage/overage/dunning, audit/export/alert/notification-channel, and permission objects. | Provides platform staff and governance over tenants, staff lifecycle, public-bank grants, SaaS plans, billing, usage, dunning, audits, alerts, and permissions. | System + Pay + Mall + Infra + CRM with Education extensions | System RBAC/DataPermission/AdminUserApi, authorized tenant-ignore mechanisms, Pay/Mall/Infra/CRM public APIs, audit/logging. | Verified source surface is broader than one aggregated platform-admin row. Target seams exist, but object-level ownership, data scopes, and cross-tenant operation policy remain incomplete. | System, Pay, Mall, Infra, CRM, and Education-specific extension permissions; Education must not duplicate platform ledgers or generic administration. | P1 | High access-control and financial-governance risk. | Permission matrix, platform-admin integration, cross-tenant negative, audit-redaction, billing/usage reconciliation, and alert/export tests. | pending migration | Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/platform-admin-overview.module.ts.
Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/platform-admin-permissions.module.ts.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/permission/PermissionApi.java:12-20.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityFrameworkService.java:7-57. | Define separate Student App, Tenant Admin, Platform Admin, public, and internal policies; map each platform surface to System/Pay/Mall/Infra/CRM/Education or explicit retirement. | -| Commercialization and growth | /Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-orders.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-payments.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-reconciliation.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/referral-growth.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/referral-crm.module.ts | Legacy product/order/payment/event/entitlement/coupon/refund/reconciliation/commission/referral/points/dunning objects.
Target Mall/Pay/Member tables remain authoritative; Education may add minimal binding records. | Supports paid products, fulfillment, entitlements, refunds, reconciliation, commissions, referrals, and CRM conversion without recreating platform ledgers. | Mall + Pay + Member + CRM with Education binding | Mall/Pay DTO APIs, Member identity/entitlement/points, CRM services, Infra Job/MQ/audit. | Verified target Pay/Mall APIs expose core seams, but scoped entitlement issuance/revocation, activation codes, reconciliation, commissions, dunning, and referral semantics are not proven. One prior evidence path was malformed; corrected source location is /Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-reconciliation.module.ts. | Mall Trade/Product, Pay, Member entitlement/points, CRM, Infra jobs/events/audit; Education owns product-to-education bindings and fulfillment orchestration only. | P2 | High financial and authorization risk. | Callback/idempotency/amount/refund, entitlement lifecycle, reconciliation, and education fulfillment contract tests. | product decision required | Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/order/PayOrderApi.java:13-38.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApi.java:12-30.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/api/order/TradeOrderApi.java:12-38.
Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-orders.module.ts:15-70 and /Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-reconciliation.module.ts.
Verified /Users/tiku1/code/ruoyi-vue-pro/docs/education/migration/GOAL.md:36-40,226-228. | Choose entitlement/activation-code/coupon model and confirm issuance, revocation, callback, refund, reconciliation, commission, and referral contracts before paid practice. | +| Commercialization and growth | /Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-orders.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-payments.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-reconciliation.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/referral-growth.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/referral-crm.module.ts | Legacy product/order/payment/event/entitlement/coupon/refund/reconciliation/commission/referral/points/dunning objects.
Target Mall/Pay/Member tables remain authoritative; Education adds only binding/entitlement records. | Supports paid products, fulfillment, entitlements, refunds, reconciliation, commissions, referrals, and CRM conversion without recreating platform ledgers. | Mall + Pay + Member + CRM with Education binding | Native Pay, Mall Product/Promotion Coupon/Seckill/Combination/Trade Order/Brokerage APIs, controllers, jobs, permissions and Vben pages; Member identity/points, CRM, Infra Job/MQ/audit. | Bounded Education binding/entitlement and activation redemption are proven. V4340–V4360 activate/import Pay; V4370 activates Product; V4380 activates Coupon; V4390–V4420 activate normal Trade order/checkout/delivery/after-sale; V4430 activates native two-level brokerage; V4440 activates empty native Seckill; V4450 activates empty native Combination activity/product/group state because the source has no group-buying equivalent. The source combination-question token remains Education content. Explicit legacy Product/code-coupon/order/refund/referral/settlement-proof import, automatic fulfillment/refund revocation, Bargain/Point and other Promotion families, dunning, and source CRM conversion remain open. | Mall Product/Promotion/Trade, Pay, Member, CRM, and Infra remain owners; Education owns product-to-learning binding, activation credentials, and access orchestration only. | P2 | High financial and authorization risk. | Delivered native Pay/Product/Coupon/Trade/Seckill/Combination tenant and PostgreSQL tests; still require reviewed legacy mapping, production reconciliation/runbooks, callback/idempotency, entitlement lifecycle, source settlement/proof/CRM mapping, and fulfillment tests. | partially migrated | Verified V4130 binding/entitlement through V4450 native Combination, native module tenant contracts, exact permissions/routes, composite references, unsafe-state checks, real service/statistics/stock/capacity isolation, and fail-closed legacy/deferred table adoption.
Verified native Pay, Product, Promotion Coupon/Seckill/Combination, Trade Order/After Sale/Brokerage APIs/pages are the public seams. | Keep activation codes separate from coupons; complete Bargain/Point and other Promotion families, explicit legacy commerce/referral/settlement-proof mapping, production reconciliation/runbooks and reviewed balances, automatic issuance/fulfillment/refund revocation, dunning, and CRM conversion contracts before broader paid-practice fulfillment. | | Background processing, assets, and operational platform | /Users/tiku1/code/tiku-backend/apps/worker/src/worker-jobs.ts
/Users/tiku1/code/tiku-backend/apps/worker/src/jobs/imports.ts
/Users/tiku1/code/tiku-backend/apps/worker/src/jobs/exports.ts
/Users/tiku1/code/tiku-backend/apps/asset-scanner/src/ | Legacy worker queues, leases, retries/dead letters, imports/exports, reconciliation, notification/audit, usage, and security scan state.
Target owns business state in domain modules and uses platform execution primitives; do not copy queue tables wholesale. | Preserves operational reliability for imports, exports, payments, CRM, scanning, notifications, retries, and audit while removing dependence on NestJS workers. | Infra platform plus owning domain modules | Infra Job, Redis MQ, File, locks, idempotency, logging, tracing, Excel utilities, tenant propagation. | Verified target primitives exist, but durable claim/lease/heartbeat/retry and malware-scanner equivalence are not proven. Education import/export business state is absent or not verified. | Infra Job/MQ/File/logging/observability plus owning Education/Pay/Mall/CRM handlers; scanner deployment or adapter ownership must be decided. | P1 | High operational and security risk. | Concurrent claim/lease/recovery, retries/dead letters, scan fail-closed, file access, tenant propagation, audit, and deployment smoke tests. | partially migrated | Verified /Users/tiku1/code/tiku-backend/apps/worker/src/worker-jobs.ts:24-220 and /Users/tiku1/code/tiku-backend/apps/worker/src/jobs/imports.ts:87-260.
Verified /Users/tiku1/code/tiku-backend/apps/asset-scanner/src/scanner.service.ts:23-50.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/RedisMQTemplate.java.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/core/handler/JobHandler.java. | Confirm Infra claim/lease semantics and scanner ownership, file privacy/retention, legacy asset migration/re-scan, and duplicate-safe at-least-once processing. | | Secondary learning, media, AI, and engagement | /Users/tiku1/code/tiku-backend/apps/api/src/nest/scoreline.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/video.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/nest/ai.module.ts
/Users/tiku1/code/tiku-backend/apps/api/src/features/profile/ | Legacy scoreline, vocabulary, handbook, video entitlement/progress, recommendation, notification, badge, exam-date, and analytics objects. | Delivers selected recommendation, scoreline, vocabulary, handbook, video, AI, notification, badge, exam-date, and engagement experiences after ownership and priority are explicit. | Education plus AI/Infra/Member/System | AI services, Infra File/notifications, Member points/levels, Education authorization/projections. | Verified legacy capabilities exist, but target equivalence and priority are not established. These cannot remain an undifferentiated P3 bucket if Phase 0 must give every capability a disposition. | AI, Infra File/messaging, Member growth primitives, System notifications, and Education extensions. | P3 | Medium-to-high due to entitlement, media access, sensitive reporting, and unclear scope. | Per-capability contract, authorization, entitlement, export/redaction, and migration compatibility tests. | product decision required | Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/scoreline.module.ts:207-226.
Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/video.module.ts:431-464.
Verified /Users/tiku1/code/tiku-backend/apps/api/src/nest/ai.module.ts:120-139.
Verified /Users/tiku1/code/tiku-backend/supabase/migrations/202606210009_content_import_vocabulary_handbook.sql.
Verified /Users/tiku1/code/ruoyi-vue-pro/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java. | For each capability, assign Education, existing platform ownership, explicit retirement, or later product scope; decide entitlement and safe export/redaction requirements. | diff --git a/docs/education/migration/02-api-mapping.md b/docs/education/migration/02-api-mapping.md index b604abfc..d9ac3bcd 100644 --- a/docs/education/migration/02-api-mapping.md +++ b/docs/education/migration/02-api-mapping.md @@ -2,6 +2,8 @@ > Phase 0 static assessment generated on 2026-07-29. No build, test, application startup, PostgreSQL connection, or Flyway migration was executed during this assessment. +> Runtime mapping update 2026-08-01: EDU-027 keeps native Trade order/config contracts authoritative, EDU-028 keeps native Discount/Reward APIs authoritative, EDU-029 activates delivery, EDU-030 activates after-sale/Pay Refund, EDU-031 activates Member-backed brokerage, EDU-032 activates native Promotion Seckill, and EDU-033 activates native Promotion Combination activity/group/Trade Order contracts. Education adds no shadow commerce, refund, commission, or special-order API. Configured target Pay runtime, Bargain/Point and other special orders, legacy commerce/referral/settlement-proof mapping, fulfillment/revocation, and deployed checkout/refund/commission/promotion evidence remain open. + This Phase 0 artifact maps API families rather than all 342 operations. Endpoint-level method/path/request/response mapping remains required before implementing each family. ## Tenant resolution, identity, and student context @@ -60,10 +62,28 @@ This Phase 0 artifact maps API families rather than all 342 operations. Endpoint - **Legacy authorization semantics to preserve:** tenant boundary, principal type, visibility, idempotency, state transitions, and redaction as applicable. - **Target:** Education + System + Member + Mall/Pay + Infra - **Reuse:** System Tenant/RBAC/DataPermission/AdminUserApi, Member relationships, Mall/Pay/Member APIs, Infra secret/file/message/audit facilities. -- **Migration conclusion:** pending migration -- **Contract gap:** Verified: classes/supervision were only part of the source tenant-admin surface. Appearance/theme lifecycle, domains/payment/auth integrations, secret rotation, and codes/coupons are separate migration/security surfaces with no verified target equivalent. Inference: collapsing them into one row would hide authorization and secret-handling decisions. -- **Required verification:** Permission matrix, row-scope negatives, secret redaction/rotation, integration authorization, code/coupon idempotency, audit, and cross-tenant tests. -- **Open decision:** Define class/student/teacher scope semantics and separately decide appearance, domain, payment/auth integration, secret, activation-code, coupon, public-bank grant, and marketing ownership or retirement. +- **Migration conclusion:** partially migrated — class/supervision, appearance, native Pay/System integration and ledgers, learning activation codes, native Mall Product/Coupon, and V4390–V4450 normal Trade order/checkout/delivery/after-sale/brokerage/seckill/combination activation delivered +- **Delivered activation-code contracts:** Admin `GET /education/activation-code/batch/page`, `POST /batch`, `PUT /batch/{id}`, `POST /batch/{id}/generate`, `GET /code/page`, and `PUT /code/{id}/disable`; Member-only app `POST /education/activation-code/check` and `POST /education/activation-code/redeem`. Query/manage/generate permissions are independent. Generation returns plaintext once, while persistence and later reads expose only digest/mask. Redemption locks the code row and composes `EducationEntitlementService` with `sourceSystem=ACTIVATION_CODE`. +- **Delivered legacy Pay import contracts:** Pay-owned `POST /pay/legacy-account-import/import` requires App+Channel create permissions and maps one explicitly reviewed `tenant_collect` WeChat/Alipay manifest through native Pay services. `GET /pay/legacy-account-import/page` requires both query permissions and returns the tenant-filtered redacted audit. Same source-account/checksum replays; checksum conflicts, non-equivalent modes/providers, channel-family mismatch, ambiguous rotating keys, and unsafe Alipay endpoints fail closed. The existing Pay App Vben page owns the import modal. +- **Delivered native Pay transaction contracts:** Existing `/pay/order`, `/pay/refund`, and `/pay/notify` query/detail/export/callback contracts and the `pay/order/index`, `pay/refund/index`, and `pay/notify/index` Vben pages are reused. V4340 supplies tenant-scoped PostgreSQL order/extension/refund/notification tables and composite tenant foreign keys; callback processing retains channel-derived `TenantUtils` context and notification retries retain `@TenantJob` execution. +- **Delivered legacy transaction bridge:** `POST /pay/legacy-transaction-import/import` accepts one terminal, reconciled order/payment/refund manifest under `pay:legacy-transaction:import`; `GET /pay/legacy-transaction-import/page` exposes redacted tenant audit under `pay:legacy-transaction:query`. It requires an EDU-021 account mapping, exact cent/status/provider reconciliation, source UUID/checksum idempotency, and explicit optional native Member ID. It writes native Pay ledgers without SDK calls, callbacks, notifications, raw payloads, or error originals. The native order page owns the import/history modal. +- **Delivered native Transfer/Wallet contracts:** Existing `/pay/transfer`, `/pay/wallet`, `/pay/wallet-transaction`, `/pay/wallet-recharge`, and `/pay/wallet-recharge-package` controllers remain authoritative. V4360 activates empty tenant-scoped native ledgers and existing `pay/transfer/index`, `pay/wallet/balance/index`, and `pay/wallet/rechargePackage/index` pages. Five data objects use `TenantBaseDO`, Transfer sync retains `@TenantJob`, wallet locks include tenant ID, administrator reductions use conditional subtraction, and recharge refund has a dedicated permission. No legacy wallet balance is inferred. +- **Delivered native Product contracts:** Existing `/product/brand`, `/product/category`, `/product/property`, `/product/property/value`, `/product/spu`, `/product/comment`, `/product/favorite`, and `/product/browse-history` controllers remain authoritative. V4370 activates nine tenant-scoped Product tables and the existing SPU, Category, Brand, Property, and Comment Vben pages. Nine Product data objects use `TenantBaseDO`; PostgreSQL composite tenant references enforce the catalog graph. The legacy display-only `products` projection is not automatically imported. +- **Delivered native Coupon contracts:** Existing `/promotion/coupon-template`, `/promotion/coupon`, and app coupon controllers remain authoritative. V4380 activates tenant-scoped template/issued-instance persistence, Product SPU/category scope validation, Member lookup/issuance, registration issuance, expiry processing, and the existing template/record Vben pages. Both coupon records use `TenantBaseDO`; the template reference is tenant-qualified. Legacy code campaigns/redemptions are not automatically imported. +- **Delivered native Trade contracts:** Existing `/trade/order`, `/trade/config`, `/app-api/trade/order`, and `/app-api/trade/cart` controllers remain authoritative. V4390 activates tenant-scoped Order/Item/Log/Cart/Config persistence, native `TradeOrderApiImpl`, exact order/config permissions, and the existing Vben pages. Legacy aggregate orders are not automatically imported. +- **Delivered native checkout Promotion contracts:** Existing `/promotion/discount-activity`, `/promotion/reward-activity`, `DiscountActivityApi`, and `RewardActivityApi` remain authoritative. V4400 activates tenant-scoped Discount Activity/Product and Reward Activity persistence, exact action permissions, and the two existing Vben pages. Real empty API lookups are proven on PostgreSQL; no legacy campaigns are inferred. +- **Delivered native delivery contracts:** Existing `/trade/delivery/express`, `/trade/delivery/express-template`, `/trade/delivery/pick-up-store`, app delivery reads, and `TradeDeliveryPriceCalculator` remain authoritative. V4410 activates tenant-scoped company/template/rule/store persistence, Product/Order references, exact permissions, three existing Vben pages, and a real PostgreSQL express-fee calculation; no source delivery data is inferred. +- **Delivered native after-sale contracts:** Existing Member application/cancel/delivery reads, `/trade/after-sale/page`, `/get-detail`, `/agree`, `/disagree`, `/receive`, `/refuse`, `/refund`, Pay refund callback handling, and operation logs remain authoritative. V4420 supplies tenant-scoped persistence/references and exact permissions. Vben now sends `auditReason`, requires `refuseMemo`, shows the application `createTime`, and permission-guards every action. +- **Legacy refund mapping:** `commerce_refund_requests` and `commerce_refund_events` are aggregate UUID records without verified native Member, Order Item, Product/SKU, return-logistics, or Pay Refund identities. V4420 deliberately imports none; mapping follows explicit legacy Product/Member/Order Item reconciliation. +- **Delivered native brokerage contracts:** Existing app/admin relationship, eligibility, team/rank, commission-record, freeze/unfreeze/cancel, withdrawal/audit, Pay Transfer callback, and scheduled job contracts remain authoritative. V4430 supplies tenant-owned persistence, references, exact eight permissions, and corrected user/record/withdrawal pages. Immediate settlements now participate in time-range statistics. +- **Legacy referral/settlement mapping:** Source referral codes/leads/team edges/tracks/QR/CRM assignment and UUID settlement/item/proof/export rows lack verified native Member, Order, relationship, Pay Transfer, and evidence identities. V4430 deliberately imports none; they remain explicit mapping/import work rather than being treated as native-equivalent. +- **Delivered native Seckill contracts:** Existing `/promotion/seckill-config`, `/promotion/seckill-activity`, supporting app reads, Product lookups, atomic stock updates, and Trade Order seckill fields remain authoritative. V4440 supplies empty tenant-owned time/activity/product persistence, Product/Trade references, consistency triggers, exact nine permissions, and corrected activity/config pages. Duplicate SKU, price/stock overrun, invalid time/limit inputs, unsafe restoration, and deletion of an in-use slot fail closed. +- **Source Seckill disposition:** Repository-wide source inventory found no seckill capability. V4440 deliberately starts empty; ordinary products, coupons, and aggregate orders are not reinterpreted as activities. +- **Delivered native Combination contracts:** Existing `/promotion/combination-activity`, `/promotion/combination-record`, supporting app reads/jobs, Product/Member lookups, and Trade Order combination fields remain authoritative. V4450 supplies empty tenant-owned activity/product/record persistence, capacity and reference triggers, exact six permissions, and corrected activity/record pages. Duplicate/mismatched SKUs, price/time/limit errors, cross-activity heads, over-capacity joins, inconsistent orders, and deletion with records fail closed. +- **Source Combination disposition:** The source `combination` token is an education combination-question type, not group buying. V4450 deliberately starts empty; no content question, ordinary product, or aggregate order is reinterpreted as a promotion group. +- **Contract gap:** Native Pay, Product, Coupon, normal Trade order, Promotion discount/reward/seckill/combination, Trade delivery, Trade after-sale, and native Trade brokerage administration are operational. Production bulk export/runbooks, reviewed UUID-to-Member/opening-balance artifacts, explicit legacy commerce/referral/settlement-proof mapping, Bargain/Point and other special-order activation, provider settlement equivalence, XPay/Xunhu replacement, and generic credential encryption remain open. V4310 does not claim legacy activation-code data import. Domains remain System Tenant websites. Tenant PNVS, private encrypted secrets, fulfillment, refund-to-entitlement revocation, and other marketing surfaces remain separate. +- **Required verification:** Permission matrix, row-scope negatives, secret redaction/rotation, integration authorization, legacy import idempotency/audit, and cross-tenant tests. Pay/Coupon/Trade/Seckill/Combination tests are delivered through V4450, including composite after-sale/brokerage/special-order references, state/amount/stock/capacity validation, exact menus, real tenant-isolated service/statistics/concurrency reads and writes, and fail-closed adoption. +- **Open decision:** Compose payments through Pay, products/coupons/promotions/orders/refunds/commissions through Mall Product/Promotion/Trade, and auth providers through System/Member. Continue Bargain/Point and other Promotion families, explicit legacy commerce/referral/settlement-proof imports, production financial/order/refund runbooks, reviewed balances, non-equivalent provider replacement, private secret rotation, automatic fulfillment, and refund revocation separately. Activation codes remain Education-owned learning credentials composed with Mall SPU binding and the entitlement pipeline. ## Platform administration and governance @@ -82,10 +102,10 @@ This Phase 0 artifact maps API families rather than all 342 operations. Endpoint - **Legacy authorization semantics to preserve:** tenant boundary, principal type, visibility, idempotency, state transitions, and redaction as applicable. - **Target:** Mall + Pay + Member + CRM with Education binding - **Reuse:** Mall/Pay DTO APIs, Member identity/entitlement/points, CRM services, Infra Job/MQ/audit. -- **Migration conclusion:** product decision required -- **Contract gap:** Verified target Pay/Mall APIs expose core seams, but scoped entitlement issuance/revocation, activation codes, reconciliation, commissions, dunning, and referral semantics are not proven. One prior evidence path was malformed; corrected source location is /Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-reconciliation.module.ts. -- **Required verification:** Callback/idempotency/amount/refund, entitlement lifecycle, reconciliation, and education fulfillment contract tests. -- **Open decision:** Choose entitlement/activation-code/coupon model and confirm issuance, revocation, callback, refund, reconciliation, commission, and referral contracts before paid practice. +- **Migration conclusion:** partially migrated — bounded Education binding/entitlement and activation redemption, native Pay ledgers, terminal legacy aggregate import, tenant-scoped native Mall Product persistence/UI, and native Promotion Coupon template/instance persistence/UI are proven; commerce orchestration remains open +- **Contract gap:** V4340 provides tenant-scoped native transaction ledgers, callbacks, retry tasks, and existing admin UI; V4350 adds bounded terminal import; V4360 activates native Transfer/Wallet ledgers; V4370 activates native Product; V4380 activates native coupon templates/instances without translating legacy code campaigns. These slices do not connect successful purchases to Education entitlements. Explicit legacy product/code-coupon import, production bulk migration, reviewed opening balances, automatic Pay/Mall fulfillment, refund-driven entitlement revocation, legacy activation-code import, Trade/other Promotion families, settlement reconciliation, commissions, dunning, and referral semantics remain unproven. One prior evidence path was malformed; corrected source location is /Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-reconciliation.module.ts. +- **Required verification:** Native order/refund/notify service, tenant-database, and terminal amount/status/provider mapping contracts are delivered. Production export/reconciliation evidence, callback/idempotency integration, entitlement lifecycle, settlement reconciliation, and education fulfillment contract tests remain required. +- **Open decision:** Keep V4310 activation codes separate from Mall Promotion coupons; confirm automatic issuance, callback, refund, reconciliation, coupon, commission, and referral contracts before broader commerce migration. ## Background processing, assets, and operational platform diff --git a/docs/education/migration/03-database-object-mapping.md b/docs/education/migration/03-database-object-mapping.md index 784196a5..c90041d8 100644 --- a/docs/education/migration/03-database-object-mapping.md +++ b/docs/education/migration/03-database-object-mapping.md @@ -2,6 +2,8 @@ > Phase 0 static assessment generated on 2026-07-29. No build, test, application startup, PostgreSQL connection, or Flyway migration was executed during this assessment. +> Runtime mapping update 2026-08-01: V4390/EDU-027 owns the native Trade order core, V4400/EDU-028 owns normal-checkout Discount/Reward persistence, V4410/EDU-029 owns delivery, V4420/EDU-030 owns after-sale/log persistence, and V4430/EDU-031 owns brokerage persistence. V4440/EDU-032 adds tenant-composite Seckill tables; V4450/EDU-033 adds tenant-composite `promotion_combination_activity`, `promotion_combination_product`, and `promotion_combination_record` plus tenant-qualified Product, Trade Order, order-record, and head references. The source has neither seckill nor group-buying state, so these tables start empty; its combination-question token stays Education content. Legacy aggregate orders/refunds/campaigns and UUID referral/settlement/proof objects remain unmapped; Bargain/Point and other Promotion tables require dedicated tenant-safe migrations. + ## Global disposition rules - Ordinary education tables and constraints become immutable module-owned PostgreSQL Flyway migrations. @@ -81,10 +83,10 @@ ### Disposition - **Target owner:** Education + System + Member + Mall/Pay + Infra -- **Current status:** pending migration -- **Required cross-module treatment:** System RBAC/DataPermission and tenant configuration are reused; Mall/Pay/Member own commercial primitives; Infra owns secrets/messaging/files where applicable; Education owns only domain relationships and configuration extensions. -- **Risk:** High: admin scope, secret leakage, payment configuration, and code redemption errors. -- **Decision still required:** Define class/student/teacher scope semantics and separately decide appearance, domain, payment/auth integration, secret, activation-code, coupon, public-bank grant, and marketing ownership or retirement. +- **Current status:** partially migrated — V4290 owns appearance/settings/theme extensions; V4300 reuses native Pay/System administration; V4310 adds activation-code tables; V4320–V4360 create/adopt native Pay configuration, transaction, Transfer, and Wallet tables plus bounded import audits; V4370 creates native Product; V4380 creates Coupon; V4390–V4430 create tenant-scoped normal Trade order, checkout Promotion, delivery, after-sale/log, and brokerage tables; V4440 creates Seckill tables and V4450 creates Combination activity/product/record tables plus their Trade bridges +- **Required cross-module treatment:** System RBAC/DataPermission and tenant configuration are reused. Native Pay tables remain Pay-owned and tenant-aware. Native Product tables remain Mall-owned, all nine records use `TenantBaseDO`, and composite tenant references protect the catalog graph. Native Promotion Coupon, Seckill, and Combination tables remain Mall-owned and tenant-qualified; Product validates SPU/SKU scope, Member supplies principals, and Trade owns order lifecycle. V4330 stores digests/mapping notes but no credentials. Tenant-scoped `system_social_client` remains System-owned. Global `system_sms_channel` is not a safe substitute for tenant PNVS. Education owns resource binding and entitlement state; activation-code rows store digest/mask, never plaintext. +- **Risk:** High: admin scope, secret leakage, payment configuration, promotion stock, and code redemption errors. +- **Decision still required:** Domains stay in System. EDU-021–EDU-033 resolve bounded Pay/Product/Coupon/normal-Trade/delivery/after-sale/native-brokerage/Seckill/Combination activation. Decide Bargain/Point and other Promotion families, explicit legacy product/code-coupon/order/refund/referral/settlement-proof import, production export/runbooks, Member mapping and reviewed opening balances, non-equivalent providers, fulfillment/refund revocation, legacy activation-code import, tenant PNVS, encrypted generic private secrets, public-bank grants, and other marketing as separate contracts. ## Platform administration and governance @@ -110,10 +112,10 @@ ### Disposition - **Target owner:** Mall + Pay + Member + CRM with Education binding -- **Current status:** product decision required -- **Required cross-module treatment:** Mall Trade/Product, Pay, Member entitlement/points, CRM, Infra jobs/events/audit; Education owns product-to-education bindings and fulfillment orchestration only. +- **Current status:** partially migrated — product binding, entitlement events, activation-code redemption, native Pay ledgers/import, Product/Coupon, and V4390–V4450 normal Trade order/checkout/delivery/after-sale/brokerage/Seckill/Combination persistence/UI are implemented; explicit legacy product/code-coupon/order/refund/referral/settlement-proof import, Bargain/Point and other Promotion families, production bulk migration, and broader commerce orchestration remain open +- **Required cross-module treatment:** Mall Trade/Product/Promotion, Pay transaction/callback APIs, Member entitlement/points, CRM, Infra jobs/events/audit; Education owns product-to-education bindings and fulfillment orchestration only. Legacy financial rows may enter Pay only through EDU-023's reviewed aggregate contract; incompatible legacy code campaigns may not be silently copied into native coupon instances. - **Risk:** High financial and authorization risk. -- **Decision still required:** Choose entitlement/activation-code/coupon model and confirm issuance, revocation, callback, refund, reconciliation, commission, and referral contracts before paid practice. +- **Decision still required:** Keep activation codes as Education learning credentials and coupons/seckill as Mall Promotion objects; native Trade owns new two-level commissions and special-order order fields, while other Promotion families, source CRM referral and settlement-proof import, automatic issuance, revocation callbacks, refunds, and reconciliation require separate contracts before broader paid-practice fulfillment. ## Background processing, assets, and operational platform diff --git a/docs/education/migration/04-module-reuse-map.md b/docs/education/migration/04-module-reuse-map.md index c7e4a842..e1f8c710 100644 --- a/docs/education/migration/04-module-reuse-map.md +++ b/docs/education/migration/04-module-reuse-map.md @@ -2,6 +2,8 @@ > Phase 0 static assessment generated on 2026-07-29. No build, test, application startup, PostgreSQL connection, or Flyway migration was executed during this assessment. +> Runtime reuse update 2026-08-01: EDU-027 activates Mall Trade order/cart/config, EDU-028 reuses native Promotion Discount/Reward, EDU-029 reuses delivery, EDU-030 reuses after-sale/Pay Refund, and EDU-031 reuses native Trade brokerage. EDU-032 reuses native Promotion Seckill; EDU-033 reuses native Combination app/admin controllers, services, mappers, jobs, Product/Member lookups, group lifecycle, Trade Order bridge, RBAC, and corrected Vben pages while supplying tenant-safe PostgreSQL persistence. Education owns no duplicate commerce, promotion, delivery, refund, commission, stock, or group aggregate. + Education must call public APIs, framework extension points, or events. It must not depend on another module's internal ServiceImpl, Mapper, or DO. ## Tenant resolution, identity, and student context @@ -36,8 +38,8 @@ Education must call public APIs, framework extension points, or events. It must - **Target owner:** Education + System + Member + Mall/Pay + Infra - **Public/framework capability to reuse:** System Tenant/RBAC/DataPermission/AdminUserApi, Member relationships, Mall/Pay/Member APIs, Infra secret/file/message/audit facilities. -- **Education-owned gap:** Verified: classes/supervision were only part of the source tenant-admin surface. Appearance/theme lifecycle, domains/payment/auth integrations, secret rotation, and codes/coupons are separate migration/security surfaces with no verified target equivalent. Inference: collapsing them into one row would hide authorization and secret-handling decisions. -- **Allowed external-module change:** System RBAC/DataPermission and tenant configuration are reused; Mall/Pay/Member own commercial primitives; Infra owns secrets/messaging/files where applicable; Education owns only domain relationships and configuration extensions. +- **Education-owned gap:** V4290 owns presentation extensions. V4300–V4380 expose and activate native Pay/System/Product/Promotion administration without Education shadow ledgers. V4390–V4430 activate native Trade order core, Discount/Reward checkout dependencies, delivery, after-sale, and brokerage; V4440 activates native Seckill and V4450 activates native Combination activity, SKU pricing, group records, capacity protection, and Trade bridge with their real APIs/services and existing pages. Education adds no financial, product, coupon, cart, order, promotion, delivery, refund, commission, stock, or group ledger. V4310 adds only activation-code state and composes Mall-owned SPU binding, Member authentication, and the existing entitlement event. Explicit legacy product/code-coupon/order/refund/referral/settlement-proof import, Bargain/Point and other Promotion families, production bulk export/runbooks and reviewed wallet opening balances, non-equivalent provider/mode replacement, automatic fulfillment/refund revocation, and legacy activation-code import remain unhandled; global System SMS Channel cannot satisfy per-tenant PNVS. Infra/private generic secrets remain separate gaps. +- **Allowed external-module change:** System RBAC/DataPermission and tenant configuration are reused; Mall owns products/coupons/promotions/orders, Pay owns payment, Member owns principals, and Infra owns secrets/messaging/files. Education owns learning relationships/configuration, activation-code credentials, and access orchestration without duplicating those platform ledgers. ## Platform administration and governance @@ -50,8 +52,8 @@ Education must call public APIs, framework extension points, or events. It must - **Target owner:** Mall + Pay + Member + CRM with Education binding - **Public/framework capability to reuse:** Mall/Pay DTO APIs, Member identity/entitlement/points, CRM services, Infra Job/MQ/audit. -- **Education-owned gap:** Verified target Pay/Mall APIs expose core seams, but scoped entitlement issuance/revocation, activation codes, reconciliation, commissions, dunning, and referral semantics are not proven. One prior evidence path was malformed; corrected source location is /Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-reconciliation.module.ts. -- **Allowed external-module change:** Mall Trade/Product, Pay, Member entitlement/points, CRM, Infra jobs/events/audit; Education owns product-to-education bindings and fulfillment orchestration only. +- **Education-owned gap:** Bounded resource binding, entitlement issuance/revocation, activation-code redemption, native Pay ledgers/import, native Product/Coupon, and V4390–V4450 Trade order/checkout/delivery/after-sale/brokerage/Seckill/Combination persistence/admin are proven. Legacy display-only Product, incompatible code campaigns, aggregate orders/refunds, and UUID referral/settlement/proof objects are not imported; the source has no seckill or group-buying state to import, and its combination-question token remains Education content. Bargain/Point and other Promotion families, production bulk migration and reviewed balances remain open, and successful native Pay/Trade events are not yet composed into automatic Education fulfillment or refund revocation. Native two-level commission, Seckill stock isolation, and Combination capacity isolation are proven, but source CRM referral, proof/export, dunning, and settlement equivalence remain unproven. One prior evidence path was malformed; corrected source location is /Users/tiku1/code/tiku-backend/apps/api/src/nest/commerce-reconciliation.module.ts. +- **Allowed external-module change:** Mall Trade/Product/Promotion, Pay, Member entitlement/points, CRM, Infra jobs/events/audit; Education owns product-to-education bindings and fulfillment orchestration only. ## Background processing, assets, and operational platform diff --git a/docs/education/migration/08-slice-roadmap.md b/docs/education/migration/08-slice-roadmap.md index 3bebd42a..80791266 100644 --- a/docs/education/migration/08-slice-roadmap.md +++ b/docs/education/migration/08-slice-roadmap.md @@ -114,16 +114,17 @@ Tickets are vertical behaviors, not technical layers. Work blockers first and us ## EDU-P4-S8 — Tenant configuration, integrations, and access operations +- **Progress:** V4290/EDU-017 completed bounded appearance, public settings, and theme lifecycle. V4300–V4360 reuse and activate native Pay/System integration, transaction, import, Transfer, and Wallet capabilities. V4310/EDU-019 delivers secure learning activation codes and entitlement composition. V4370/EDU-025 activates Product; V4380/EDU-026 activates coupons; V4390/EDU-027 activates normal Trade order; V4400/EDU-028 activates Discount/Reward; V4410/EDU-029 activates delivery; V4420/EDU-030 activates after-sale/Pay Refund; V4430/EDU-031 activates native brokerage; V4440/EDU-032 activates tenant-aware Seckill; V4450/EDU-033 activates tenant-aware Combination activities, SKU pricing, group records, atomic capacity, Trade Order/head bridges, exact permissions, and corrected Vben pages. Domains remain with their RuoYi owners; source referral CRM/settlement-proof import, Bargain/Point and other Promotion families, explicit legacy Product/code-coupon/order/refund import, production bulk export/runbooks and reviewed balances, automatic fulfillment/refund revocation, non-equivalent provider/mode replacement, tenant PNVS, legacy activation-code import, generic private secrets, and public-bank access remain separate. - **Outcome:** Selected tenant appearance, integrations, secrets, activation codes, coupons, and public-bank access capabilities have explicit owners and safe contracts. - **Risk:** High because secret, payment configuration, redemption, and public-bank synchronization boundaries differ. - **Blockers:** - Appearance/domain/integration/secrets/codes ownership decisions. - System tenant configuration and secret APIs. -- Mall/Pay/Member entitlement and code contracts. +- Mall/Pay/Member entitlement and coupon contracts; EDU-019 resolves bounded new activation codes, EDU-021–EDU-024 resolve bounded Pay activation/import/ledgers, EDU-025 resolves Product, EDU-026 resolves coupons, EDU-027 resolves normal Trade order, EDU-028 resolves Discount/Reward, EDU-029 resolves delivery, EDU-030 resolves after-sale, EDU-031 resolves native brokerage, EDU-032 resolves native Seckill, and EDU-033 resolves native Combination activation. Explicit legacy Product/code-coupon/order/refund/referral/settlement-proof import, Bargain/Point and other Promotion families, legacy activation-code import, production financial bulk migration and reviewed opening balances, fulfillment, and refund revocation remain open. - **Verification:** - Secret redaction/rotation and authorization tests. -- Domain/auth-provider/payment-account configuration tests. -- Code/coupon redemption idempotency and audit tests. +- Domain/auth-provider/payment-account configuration tests; EDU-021 covers bounded account mapping/replay/redaction, EDU-022 covers transaction tenant inheritance, composite database references, notification uniqueness, native service regressions, and menu shape, EDU-023 covers terminal reconciliation, redacted audit, idempotency/conflict, and native order-page UI, EDU-024 covers Transfer/Wallet tenant inheritance, tenant-qualified locks, amount safety, composite references, and native UI/menu shape, and EDU-025 covers nine Product tenant records, catalog graph references, category parent isolation, amount/score safety, global-table refusal, and native UI/menu shape. +- Native coupon tenant/reference/counter/discount/use-state and menu-shape tests are delivered by V4380. V4390 adds Trade tenant inheritance, composite Order/Cart/Product references, state/amount checks, global/deferred-table refusal, sequences, and exact order/config UI/menu shape. Legacy code and order import/reconciliation remain open. V4310 already covers activation-code digest, tenant, replay, conflict, disabled dependency, entitlement-event, and concurrent-winner behavior. - Public-bank grant/sync and cross-tenant tests. ## EDU-P5-S9 — Education commercialization binding @@ -131,8 +132,8 @@ Tickets are vertical behaviors, not technical layers. Work blockers first and us - **Outcome:** Education products bind to commerce purchases and Member entitlements without duplicated financial ledgers. - **Risk:** High financial and authorization risk. - **Blockers:** -- Product binding model. -- Mall/Pay public APIs. +- Product binding model; EDU-025 supplies the tenant-scoped native Product owner but binding authoring/import semantics remain open. +- Mall/Pay public APIs; Product, native coupons, normal Trade order/delivery/after-sale, native brokerage, Seckill, and Combination are active, while Bargain/Point and other Promotion families, source referral/settlement-proof mapping, and purchase/refund-to-entitlement composition remain open. - Member entitlement decision and callback/refund semantics. - **Verification:** - Order/payment/refund callback contracts. diff --git a/docs/education/migration/12-admin-authoring-ui.md b/docs/education/migration/12-admin-authoring-ui.md new file mode 100644 index 00000000..ea4b273b --- /dev/null +++ b/docs/education/migration/12-admin-authoring-ui.md @@ -0,0 +1,282 @@ +# Education Admin UI + +> Implemented on 2026-07-31 as the first two Vben management UI slices. This record covers build-time and disposable PostgreSQL evidence only; it is not Pilot or production runtime evidence. + +## Delivered pages + +The Vben admin submodule now contains the four routes seeded by Education Flyway migration V4220: + +| Menu component | Capability | +|---|---| +| `education/content-node/index` | Page, create, revise, activate, and archive tenant content nodes | +| `education/question/index` | Page, create/revise drafts, place on a content node, publish, and archive questions | +| `education/collection/index` | Page, create/revise drafts, replace ordered question membership, activate, and archive collections | +| `education/practice-blueprint/index` | Page, create/revise NODE or COLLECTION blueprints, activate, and archive blueprints | + +Flyway migration V4230 adds four further routes over existing backend contracts: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `education/import-job/index` | Infra File-backed upload and malware scan, parser preview, explicit execution, and job lookup | +| `education/classroom/index` | Class creation, Member user membership display, and expiring idempotent invitations | +| `education/commercialization/index` | Question-collection binding to Mall SPU and idempotent Member entitlement events suitable for Pay/Mall callbacks | +| `education/operations/index` | Read-only dependency, Worker, Scanner, and dead-letter health with server-sanitized diagnostics | + +Flyway migration V4240 adds the remaining bounded admin contracts: + +| Menu component | Capability | +|---|---| +| `education/category/index` | Tenant category page/detail reads plus create, revise, activate, and archive lifecycle | +| `education/content-export/index` | Export field-policy evaluation with separate answer-inclusion permission; explicitly does not claim artifact generation | + +Member migration V4250 and Education migration V4260 add the first member-learning operations slice: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `education/learning-operations/index` | Tenant learning summary, student feedback handling/audit, resolved-feedback point rewards, and learning award/badge projection. Member remains authoritative for users, levels, balances, and point records; System remains authoritative for admin identity and notifications. | + +V4250 adds a fail-closed unique business key for Education-owned entries in the Member point ledger. `MemberPointApi.addPointOnce` treats only a verified matching ledger row as an idempotent replay, so a retry cannot double-credit a member and unrelated unique-key failures are not hidden. V4260 adds tenant-owned feedback events, optimistic feedback versions, reward delivery state, separate query/feedback/reward RBAC permissions, and menu rows. + +Education migration V4270 adds the tenant student-supervision slice: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `education/supervision/index` | Risk-student preview over native practice reports, wrong questions, active sessions, and vocabulary progress; configurable supervision rules; idempotent follow-up generation; and optimistic follow-up handling. Member remains authoritative for student accounts. System AdminUser, RBAC, department data scopes, and simple-list selectors remain authoritative for operators. | + +V4270 adds `dept_id` and `owner_user_id` authorization projections to Education classes and registers classes, supervision rules, and follow-ups with RuoYi `DeptDataPermissionRule`. It does not copy System users, departments, roles, or Member profiles. Stable `(tenant_id, batch_key, student_user_id)` uniqueness plus PostgreSQL `ON CONFLICT DO NOTHING` prevents both sequential and concurrent retries from duplicating work without aborting the surrounding transaction. The risk query keeps `education_class` in the main select so RuoYi's department/self interceptor can scope candidate students; a real `LoginUser` and `DeptDataPermissionRespDTO` PostgreSQL test verifies the negative department case. + +Education migration V4280 adds configurable tenant badges and the thirteenth Education admin page: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `education/badge/index` | Badge definition filtering, creation, optimistic editing, manual Member grant, and grant-history audit. Member remains authoritative for student identity; System remains authoritative for administrator identity, RBAC, and the `education_badge_granted` notify template/message. | + +V4280 extends the existing `education_learning_award` ledger instead of creating a second user-badge table. `(tenant_id, user_id, badge_definition_id)` makes a badge a lifetime-once grant and PostgreSQL `ON CONFLICT DO NOTHING` makes manual and automatic replay safe. Automatic evaluation is attached only to real migrated events: practice submission, vocabulary review, and feedback resolution/reward. Check-in, mock-exam, and activity-reward triggers remain unavailable until those source capabilities are migrated; the rule editor does not advertise invented event sources. + +Education migration V4290 adds tenant appearance/settings/theme lifecycle and the fourteenth Education admin page: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `education/tenant-appearance/index` | Branding, public/admin JSON settings, three platform theme templates, draft preview, and explicit publication. System Tenant remains authoritative for tenant name and websites; System RBAC, tenant validation, AdminUser projection, and operation/access logging are reused. | + +V4290 adds one tenant-owned optimistic configuration row and one global platform-template table. `classic`, `focus`, and `high-contrast` use the exact legacy theme token/assets payloads. The anonymous public appearance endpoint stays under the normal validated `tenant-id` context and excludes admin flags, drafts, and operator data. It is deliberately separate from `/education/tenant/resolve`, whose minimal two-field locator response remains unchanged. Public JSON recursively rejects sensitive key names except `secretRef`, while renderable theme fields, CSS variables, icons, URLs, modes, densities, colors, and radii are validated by a closed policy at both preview and publication boundaries. + +Education migration V4300 adds two Education-menu entry points while reusing existing native UI and backend contracts: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `pay/app/index` | Tenant payment applications and channels through Pay's existing controllers, V4320 tenant-scoped App/Channel persistence, channel configuration forms, eight granular app/channel permissions, and V4330's explicit legacy-account import modal. | +| `system/social/client/index.vue` | Tenant-scoped third-party login clients through System's existing controller, `TenantBaseDO`, Vben page, and four granular permissions. | + +V4300 creates no Education page, endpoint, payment account, auth-provider, or credential table. Its unique route names allow the native components to coexist with their original menu locations. System SMS Channel remains a platform-global `@TenantIgnore` object and does not provide legacy tenant-level PNVS equivalence, so V4300 deliberately does not expose it as a migrated tenant auth provider. + +V4330/EDU-021 extends the same native Pay page rather than adding a sixteenth Education page. Operators paste one reviewed manifest containing source IDs/checksum, explicit native channel, new business callbacks, and provider configuration. The modal warns that only `tenant_collect` WeChat/Alipay is supported and that native Pay retains channel credentials using its existing storage. The action is visible only when both App-create and Channel-create permissions are present, matching the backend AND check. Its template is instructional and contains placeholders that must be replaced. The tenant-filtered audit API stores mappings and digests but no second raw configuration or credential copy; import request-body logging is also disabled across access, non-production, and unexpected-error logs. Account-import audit history does not yet have a dedicated table view. + +Education migration V4340 adds three more Education-menu entry points while continuing to reuse native Pay UI and controllers: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `pay/order/index` | Tenant-scoped native Pay order/detail queries and export using `pay:order:query` / `pay:order:export`. | +| `pay/refund/index` | Tenant-scoped native Pay refund queries and export using `pay:refund:query` / `pay:refund:export`. | +| `pay/notify/index` | Tenant-scoped callback task/detail/log inspection using `pay:notify:query`. | + +V4340/EDU-022 adds no custom Education transaction page. It activates Pay-owned PostgreSQL order, extension, refund, notification-task, and notification-log persistence, makes every corresponding data object tenant-aware, and preserves channel-derived callback context plus the native tenant job. The pages begin with empty ledgers: no legacy order/payment/refund row is imported by inference. + +V4350/EDU-023 extends the native `pay/order/index` page with **迁移旧支付交易**. The modal accepts one reviewed terminal aggregate JSON manifest, warns that live states and inconsistent totals fail closed, locks submission while importing, and switches to a compact recent-audit table after success. Import and audit-query permissions are independent; an audit-only operator can open the history tab without receiving write access. The importer does not call channel SDKs, callbacks, or notification jobs, and the UI never renders raw payloads or error originals. The template remains instructional: source UUID/checksum, optional explicit native Member ID, and event digests must come from a controlled export/reconciliation process. + +Education migration V4360/EDU-024 adds three more Education-menu entry points while continuing to reuse native Pay UI and controllers: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `pay/transfer/index` | Tenant-scoped native transfer query/detail/export using `pay:transfer:query` and `pay:transfer:export`; the existing sync job remains tenant-aware. | +| `pay/wallet/balance/index` | Tenant-scoped native member wallet and transaction inspection using `pay:wallet:query`; the existing Member page retains the guarded `pay:wallet:update-balance` action. | +| `pay/wallet/rechargePackage/index` | Native recharge-package create/update/delete administration with independent CRUD permissions; recharge refund uses `pay:wallet-recharge:refund`. | + +V4360 creates empty Transfer/Wallet ledgers and never derives balances from legacy payments. Tenant-qualified Redis locks, conditional administrator subtraction, positive-amount validation, composite tenant foreign keys, and non-negative database constraints protect the existing UI operations without adding an Education financial page or API. + +Education migration V4370/EDU-025 adds a nested `商品中心` and five Education-menu entry points while reusing native Mall Product UI and controllers: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `mall/product/spu/index` | Native SPU/SKU create, update, status, delete, query, and export using the original Product services and five granular action permissions. | +| `mall/product/category/index` | Tenant-scoped two-level category tree administration with query/create/update/delete permissions. | +| `mall/product/brand/index` | Tenant-scoped brand query/create/update/delete administration. | +| `mall/product/property/index` | Native property and property-value query/create/update/delete administration. | +| `mall/product/comment/index` | Native comment query, visibility changes, and merchant replies using query/update permissions. | + +V4370 activates nine Product-owned PostgreSQL tables and makes all corresponding data objects tenant-aware. It creates an empty catalog: the legacy `products` endpoint exposes display labels, links, and media but no authoritative SKU, integer price, stock, brand, property, or delivery facts, so no automatic product import is performed. The existing SPU form was normalized by oxfmt; no new UI component or runtime dependency was introduced. + +Education migration V4380/EDU-026 adds a nested `优惠券中心` and reuses two native Mall Promotion pages: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `mall/promotion/coupon/template/index` | Native coupon-template query/create/update/status/delete with Product SPU/category scope validation and four granular template permissions. | +| `mall/promotion/coupon/index` | Native issued-coupon/member records, administrator send, query, and safe recovery using three granular coupon permissions. | + +V4380 activates tenant-scoped `promotion_coupon_template` and `promotion_coupon`, including composite tenant references, counter/validity/discount/use-state checks, registration issuance and expiry-job compatibility. It starts empty because legacy code campaigns/redemptions are not equivalent to pre-issued native member coupons. No Education coupon API or custom UI page was added. + +Education migration V4390/EDU-027 adds a nested `交易中心` and reuses two native Mall Trade pages: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `mall/trade/order/index` | Tenant-scoped native order page/summary/detail, remark, price/address update, delivery and pick-up verification using `trade:order:query`, `trade:order:update`, and `trade:order:pick-up`. | +| `mall/trade/config/index` | One active tenant-owned Trade configuration using `trade:config:query` and `trade:config:save`. | + +V4390 activates tenant-scoped `trade_config`, `trade_cart`, `trade_order`, `trade_order_item`, and `trade_order_log`, replaces Promotion's temporary absent-Trade adapter with the native `TradeOrderApiImpl`, and starts the order ledger empty. Existing legacy payment totals do not prove normalized member/SPU/SKU line items or order lifecycle, so no automatic order import or Education order API/UI was added. Delivery master data is activated by EDU-029; after-sale, brokerage, and special-order tables remain later slices. + +Education migration V4400/EDU-028 adds a nested `营销活动` group and reuses two native Mall Promotion pages: + +| Menu component | Capability | +|---|---| +| `mall/promotion/discountActivity/index` | Native limited-time SKU discount query/create/update/close/delete with the original five action permissions. | +| `mall/promotion/rewardActivity/index` | Native full-reduction/gift rule query/create/update/close/delete with the original five action permissions. | + +V4400 activates the three Promotion-owned tables consulted by normal Trade price calculation, tenantizes their native records, and preserves the existing PostgreSQL-compatible `findInSet` mapper path. No new frontend component or Education promotion API is introduced. + +Education migration V4410/EDU-029 adds `配送管理` below the native Trade group and reuses three native pages: + +| Menu component | Capability | +|---|---| +| `mall/trade/delivery/express/index` | Tenant-scoped express-company query/create/update/delete/export with the original five permissions. | +| `mall/trade/delivery/expressTemplate/index` | Express template and area-based charge/free rule query/create/update/delete with four permissions. | +| `mall/trade/delivery/pickUpStore/index` | Pickup-store query/create/update/delete and verifier binding through the existing native controller. | + +V4410 activates all five Trade-owned delivery tables, connects Product SPUs to templates and pickup orders to stores through tenant-qualified references, and drives the native express calculator over real PostgreSQL persistence. The source has no physical-delivery master data, so no companies, stores, rules, or Product assignments are fabricated and no Education delivery API/UI is introduced. + +Education migration V4420/EDU-030 adds `售后退款` below the native Trade group and reuses the native list/detail page: + +| Menu component | Capability | +|---|---| +| `mall/trade/afterSale/index` | Tenant-scoped after-sale query/detail, agree/disagree, return receipt/refusal, Pay Refund handling, and operation logs using the five exact `trade:after-sale:*` permissions. | + +V4420 activates Trade-owned `trade_after_sale` and `trade_after_sale_log`, connects Order, Order Item, Product, Pay Refund, Delivery, logs, and the order-item back-reference through tenant-qualified constraints, and retains the native app/admin services. The Vben page now sends `auditReason`, collects required `refuseMemo` in a validated locked modal, displays `createTime` as the application time, and applies exact permission guards to all actions. Source aggregate UUID refunds are not imported because they do not prove native Member, line-item, Product/SKU, return-logistics, or Pay Refund identities; no Education refund API/UI is introduced. + +Education migration V4310 adds secure learning activation codes and the fifteenth custom Education admin page: + +| Menu component | Capability and reused RuoYi module | +|---|---| +| `education/activation-code/index` | Batch filtering/creation/optimistic editing, one-time plaintext generation, masked status queries, and confirmed disable. Mall-owned SPUs are referenced through the existing Education resource-product binding; Member supplies the redeeming principal; the existing Education entitlement event pipeline grants access. | + +The page separates query, management, and generation permissions. A generated plaintext set exists only in the controlled modal state: operators receive a prominent one-time warning, copy/download actions, and a second confirmation before discarding an unsaved set. Closing the modal clears plaintext; later tables return only masks. Batch product, duration, and prefix become immutable after the first code is generated. The app check/redeem endpoints reject administrator principals and use only the authenticated Member ID. + +The operations page also loads `/education/capability` and shows feature flags, capabilities, migration themes, evidence levels, blockers, owning modules, and remaining legacy dependencies. + +Custom Education HTTP contracts are isolated under `apps/web-antd/src/api/education/`; the Pay-owned legacy bridge remains under `apps/web-antd/src/api/pay/legacy-account-import/`. Page actions use the exact System RBAC permissions declared by the corresponding controllers and migration menu rows. Mutations use server-returned content, placement, or authoring versions rather than client-invented values. + +## UI contract + +- Uses the existing Vben, Ant Design Vue, `requestClient`, VXE Grid, and `TableAction` interfaces. +- Preserves the existing application typography and theme; no new runtime design dependency or external font was added. +- Shows explicit lifecycle labels and confirmations for publish/activate/archive actions. +- Locks modal submissions during requests and reports validation or request failures through the existing message layer. +- Rejects invalid question-option JSON and option-level correctness flags before submission. +- Normalizes optional metadata/access-rule JSON objects and validates ordered membership IDs. +- Enforces blueprint target selection and `minimum <= suggested <= maximum` before submission. +- Keeps import execution separate from upload and preview, surfaces scan/parser state, and never writes an uploaded file directly into the question catalog. +- Uses Member user IDs for classroom and entitlement subjects instead of introducing a second education account table. +- Uses server-returned product-binding versions for deactivation and source-system event IDs for entitlement idempotency. +- Keeps operational health read-only and displays only the bounded, sanitized detail returned by the backend. +- Displays the module's honest migration/capability manifest alongside health instead of hiding deferred dependencies. +- Separates `education:content-export` from the stronger `education:content-export:answers` permission and labels artifact generation as not yet implemented. +- Keeps member lookup and point enrichment behind `MemberUserApi`/`MemberPointApi`; no Education account, level, balance, or generic point-ledger table was added. +- Requires a feedback to be `RESOLVED` before a bounded 1–100 point reward can be scheduled, records retry state in Education, and uses the feedback ID as the stable Member ledger business key. +- Uses optimistic feedback versions, independent feedback/reward permissions, and immutable tenant-owned status events for administrator handling. +- Applies RuoYi department/self data permission to classes, supervision rules, candidate class scope, and follow-up tasks, while keeping action permissions independent for query, rule authoring, generation, and handling. +- Computes supervision evidence from Education's existing learning tables, enriches students through `MemberUserApi`, validates assignees through `AdminUserApi`, and never creates a duplicate student or administrator directory. +- Uses optimistic follow-up versions and a tenant/batch/member database key so stale handling and duplicate generation both fail closed. +- Separates badge query, definition-write, and manual-grant permissions; definition updates use optimistic versions and disabled badges cannot be granted. +- Validates manual recipients through `MemberUserApi`, enriches grant history through Member/System public APIs, and sends badge messages through `NotifyMessageSendApi` without rolling back a durable grant when notification delivery fails. +- Keeps System Tenant name and websites authoritative; Education stores only presentation extensions and never adds branding/theme fields to the public locator contract. +- Separates appearance query, branding, settings, and theme permissions; every mutation uses a server-returned optimistic version. +- Keeps platform templates global while tenant drafts/published state uses RuoYi tenant injection; public projection contains neither admin feature flags nor drafts. +- Applies recursive public-secret rejection and a closed renderable-theme policy on the server, with matching JSON-object and obvious-secret preflight checks in Vben. +- Uses responsive Ant Design grids, visible labels, loading states, confirmation before publication, and keyboard-operable template choices without adding a new UI dependency or font. +- Separates activation-code query, management, and generation permissions; all updates/generation/disable actions submit server-returned optimistic versions. +- Never lists activation-code plaintext after generation. The one-time modal offers explicit copy/download, warns before unsaved dismissal, clears plaintext after close, and confirms permanent disable actions. +- Uses Mall SPU IDs, Member principals, and the existing Education resource binding/entitlement event pipeline instead of adding shadow product, account, coupon, or access-ledger models. +- Reuses native Pay order/refund/notify pages and permissions over composite-tenant PostgreSQL tables; Education does not own a duplicate financial ledger, callback controller, retry worker, or export implementation. +- Reuses the native order page for EDU-023 terminal aggregate import and recent redacted audit; Pay owns the importer, permissions, native ledgers, and audit tables. +- Reuses native Pay Transfer, Wallet Balance, and Recharge Package pages for EDU-024; V4360 supplies tenant-scoped empty ledgers and granular permissions, while Member administration retains the existing balance-adjustment form. +- Reuses native Mall Product SPU/SKU, Category, Brand, Property, and Comment pages for EDU-025; V4370 supplies tenant-scoped catalog persistence, composite graph references, and the original granular Product permissions without adding an Education product API. +- Reuses native Mall Promotion Coupon Template and Issued Coupon pages for EDU-026; V4380 supplies tenant-scoped template/instance persistence, Product/Member composition, exact coupon permissions, and fail-closed adoption without adding an Education coupon API. +- Reuses native Mall Trade Order and Config pages for EDU-027; V4390 supplies tenant-scoped order/cart/config persistence, Pay/Product/Coupon composition, exact Trade permissions, and fail-closed deferred-table adoption without adding an Education order API. +- Reuses native Mall Promotion Discount Activity and Reward Activity pages for EDU-028; V4400 supplies tenant-scoped persistence, Product references, validated rule JSON, exact permissions, and real PostgreSQL API lookup evidence without adding an Education promotion API. +- Reuses native Trade Express, Express Template, and Pickup Store pages for EDU-029; V4410 supplies tenant-scoped persistence, Product/Order references, exact permissions, and real PostgreSQL freight-calculation evidence without adding an Education delivery API. +- Reuses the native Trade After Sale list/detail page for EDU-030; V4420 supplies tenant-scoped state/log persistence, Order/Product/Pay/Delivery references, exact permissions, and real PostgreSQL service isolation. Required audit/refusal fields, application time, loading locks, responsive forms, and action guards match the backend contract without adding an Education refund API. + +## Backend contract correction + +The question revise endpoint previously delegated to the default `TenantQuestionLifecycleService.reviseDraft` implementation and could throw `UnsupportedOperationException`. It now performs tenant-scoped DRAFT/content-version CAS, increments `content_version`, and appends an immutable `education_question_version` snapshot. V4220 protects the same invariant in PostgreSQL. V4220 also backfills the education root and early question permission rows when a `system_menu` table is introduced after V4080/V4090, while still failing closed on conflicting IDs. + +## Verification + +Successful commands from the Vben submodule root: + +```text +node --max-old-space-size=8192 node_modules/vue-tsc/bin/vue-tsc.js --noEmit --skipLibCheck -p apps/web-antd/tsconfig.json +./node_modules/.bin/oxfmt --check apps/web-antd/src/api/education apps/web-antd/src/views/education +./node_modules/.bin/oxlint apps/web-antd/src/api/education apps/web-antd/src/views/education +node --max-old-space-size=8192 ../../node_modules/vite/bin/vite.js build --mode production # from apps/web-antd +``` + +All four commands passed. Production builds emit independent chunks for all fifteen Education pages, including `learning-operations-*.js`, `supervision-*.js`, `badge-*.js`, `tenant-appearance-*.js`, and `activation-code-*.js`, and only report the existing non-blocking Lightning CSS warnings for unrelated `:deep` selectors. + +Backend evidence: + +```text +mvn -pl yudao-server -am -DskipTests compile +mvn -pl yudao-module-education -am -DskipTests test-compile +mvn -pl yudao-module-education -am -Dtest=EducationFlywayMigrationIntegrationTest -Dsurefire.failIfNoSpecifiedTests=false test +JAVA_HOME=/Users/tiku1/.sdkman/candidates/java/21.0.12-amzn \ + mvn -pl yudao-module-education -am \ + -Dtest=TenantQuestionLifecycleServiceImplTest,InfraFileImportObjectScanGatewayTest,StandardQuestionImportParserTest,QuestionImportJobServiceImplTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +JAVA_HOME=/Users/tiku1/.sdkman/candidates/java/21.0.12-amzn \ + mvn -pl yudao-module-education -am \ + -Dtest=MemberPointApiImplTest,LearningOperationsAdminServiceImplTest,LearningOperationsAdminControllerContractTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +JAVA_HOME=/Users/tiku1/.sdkman/candidates/java/21.0.12-amzn \ + mvn -pl yudao-module-education -am \ + -Dtest=StudentSupervisionAdminServiceImplTest,StudentSupervisionAdminControllerContractTest,StudentSupervisionPostgreSqlIntegrationTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +JAVA_HOME=/Users/tiku1/.sdkman/candidates/java/21.0.12-amzn \ + mvn -pl yudao-module-education -am \ + -Dtest=BadgeAdminControllerContractTest,BadgeAdminServiceImplTest,BadgeGrantServiceImplTest,BadgePostgreSqlIntegrationTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +JAVA_HOME=/Users/tiku1/.sdkman/candidates/java/21.0.12-amzn \ + mvn -pl yudao-module-education -am \ + -Dtest=TenantAppearanceAdminControllerContractTest,TenantAppearancePolicyTest,TenantAppearanceServiceImplTest,TenantAppearancePostgreSqlIntegrationTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +mvn -pl yudao-module-education -am \ + -Dtest=ActivationCodeServiceImplTest,ActivationCodeAdminControllerContractTest,ActivationCodeAppControllerHttpTest,ActivationCodePostgreSqlIntegrationTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +mvn -pl yudao-module-pay -am \ + -Dtest=PayLegacyAccountImportServiceImplTest,PayLegacyAccountImportControllerContractTest,PayChannelServiceTest,PayAppTenantContractTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +mvn -pl yudao-module-pay -am \ + -Dtest=PayOrderServiceTest,PayRefundServiceTest,PayNotifyServiceTest,PayTransactionTenantContractTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +mvn -pl yudao-module-pay -am \ + -Dtest=PayTransferServiceTest,PayTransferWalletTenantContractTest,PayWalletLockRedisDAOTest,PayWalletControllerTest,PayWalletServiceImplTest,PayWalletRechargeServiceImplTest,WalletPayClientTest \ + -Dsurefire.failIfNoSpecifiedTests=false test +``` + +Compilation and test compilation passed. All 46 Flyway integration tests, all 35 selected question/import tests, all 13 selected category/export tests, all 8 selected Member point / learning-operations tests, all 8 selected supervision service/controller/PostgreSQL tests, all 7 selected badge controller/service/PostgreSQL tests, all 14 selected appearance controller/policy/service/PostgreSQL tests, and all 11 selected activation-code controller/service/PostgreSQL tests passed. The prior combined regression run executed 101 tests with no failures; V4310 then added the 11 focused activation-code checks. The EDU-021 Pay selection passes 29 tests: 17 Pay channel checks, two tenant/permission contracts, nine V4330 importer checks, and one request-log/dual-permission controller contract. The EDU-022 native transaction selection passes 86 tests: 46 order, 28 refund, 11 notify, and one tenant-inheritance contract. EDU-023 adds nine focused terminal importer/controller checks. EDU-024 adds 12 focused Transfer/Wallet checks. EDU-025 adds the Product tenant contract and the 44th Flyway scenario. EDU-026 adds the Promotion coupon tenant contract and the 45th Flyway scenario. EDU-027 adds the Trade tenant contract and the 46th Flyway scenario. V4390 evidence covers five tenant-aware Trade records, cross-tenant ID reuse and Cart/Order Item reference rejection, order/config state safety, exact order/config permissions/routes, five explicit sequences, and fail-closed global/deferred Trade adoption. V4380 evidence covers tenant-owned coupon templates/instances, cross-tenant identifier reuse and template-reference rejection, issue/use counter and discount/use-state safety, exact coupon permissions/routes, explicit identity sequences, and fail-closed global Coupon adoption. V4370 evidence covers nine tenant-scoped Product tables, cross-tenant identifier reuse and reference rejection, category-parent isolation, non-negative prices/stock/sales/commission/browse counts, rating bounds, active uniqueness, exact Product permissions/routes, and fail-closed global Product adoption. V4360 evidence covers cross-tenant composite references, per-tenant identifier reuse, same-tenant wallet uniqueness, negative-balance rejection, fail-closed global-wallet adoption, exact permission mappings, tenant-qualified Redis locking, safe administrator subtraction, recharge-refund wallet identity, and correct wallet-transfer lookup. V4350 evidence covers cross-tenant references, per-target-tenant source reuse, event count/digest consistency, sensitive-column absence, fail-closed global audit tables, and exact permission mappings. V4340 evidence covers composite tenant references, same merchant identifier across tenants, notification-task uniqueness and soft-delete recreation, fail-closed global transaction tables, and exact native menu/permission mappings. V4330 evidence covers provider/config mapping, safe disable mapping, replay/checksum conflict, mode/provider/channel rejection, unsafe endpoint/key rejection, audit/log redaction, composite tenant targets, and fail-closed global-audit adoption. V4310 evidence covers independent permissions, Member-only app principals, digest/mask-only persistence, tenant isolation, atomic entitlement/event creation, same-member replay, different-member conflict, disabled batch/binding rejection, and a concurrent unique winner. The current JDK emits Mockito's forward-looking dynamic-agent warning but does not fail the tests. + +The normal `pnpm --filter @vben/web-antd run typecheck` entry point completed successfully with the configured workspace toolchain. + +EDU-028 advances the PostgreSQL total to 47 passing Flyway scenarios and adds one real Spring/MyBatis Promotion API lookup test plus the Promotion activity tenant contract. The reused Discount/Reward page set passes Vben typecheck, scoped oxlint, and scoped oxfmt checks; V4400 proves exact routes/permissions, three explicit sequences, tenant-qualified Product references, PostgreSQL scope matching, rule validation, and fail-closed global-table adoption. + +EDU-029 advances the PostgreSQL total to 48 passing Flyway scenarios and adds the five-record delivery tenant contract plus a real Spring/MyBatis template-service and `TradeDeliveryPriceCalculator` test. The reused delivery page set passes Vben typecheck and scoped lint/format checks; V4410 proves exact routes/permissions, five explicit sequences, tenant-qualified Product/Order references, area/location/amount/state constraints, persisted freight calculation, and fail-closed global-table adoption. + +EDU-030 advances the PostgreSQL total to 49 passing Flyway scenarios and adds the two-record after-sale tenant contract plus a real Spring/MyBatis service/log integration test with the production tenant SQL interceptor. The corrected after-sale page passes Vben typecheck, scoped oxlint, and scoped oxfmt checks; V4420 proves exact route/permissions, two explicit sequences, tenant-qualified Order/Order Item/Product/Pay Refund/Delivery/log references, state/JSON/audit/return/refund constraints, isolated create/page/detail/log reads, and fail-closed global-table adoption. + +## Remaining scope + +- Run browser/API integration against a target deployment with V4220 applied and roles explicitly granted. +- Add management pages and backend contracts for each later capability selected from the deferred legacy families. +- Add real check-in, mock-exam, and activity-reward domains before exposing those legacy badge triggers; V4280 intentionally supports only migrated event sources. +- Keep domains in System Tenant websites. V4300–V4330 expose operational Pay configuration and bounded account import; V4340–V4360 activate native Pay transaction/Transfer/Wallet ledgers and pages; V4370 activates Product; V4380 activates coupons; V4390–V4420 activate the normal Trade order, Promotion, delivery, and after-sale dependencies/pages. Explicit legacy Product/code-coupon/order/refund import, Trade brokerage and special-order Promotion families, production export/Member-map/opening-balance/runbook evidence, non-equivalent payment modes/providers, tenant PNVS, generic private secret storage/rotation, automatic Mall/Pay fulfillment, and refund-to-entitlement revocation remain dedicated decisions. V4310 resolves new activation-code ownership but not legacy-code import. +- Add the scheduled worker adapter for `DAILY`/`WEEKLY` supervision rules; V4270 persists the schedule and supports safe manual/interactive execution, but does not claim an automatic production worker deployment. +- Replace raw relationship IDs with searchable selectors when stable simple-list contracts are exposed by Education. +- Commit the UI changes in the UI repository and then advance the parent gitlink as part of the normal integration workflow. diff --git a/docs/education/migration/issues/EDU-012-classes-relationships.md b/docs/education/migration/issues/EDU-012-classes-relationships.md index a622409b..33c8097c 100644 --- a/docs/education/migration/issues/EDU-012-classes-relationships.md +++ b/docs/education/migration/issues/EDU-012-classes-relationships.md @@ -3,7 +3,7 @@ - **Status:** implemented - **Type:** bounded vertical capability - **Phase:** 4 -- **Decision:** Education owns tenant-local classes and student/teacher relationships to existing Member identities. System RBAC remains the action-authorization boundary; class roles never become System roles. No platform tenant-ignore endpoint or supervision model is introduced in this slice. +- **Decision:** Education owns tenant-local classes, student/teacher relationships, learning-risk rules, and follow-up tasks. System RBAC, AdminUser, departments, and data-permission policy remain authoritative; Member owns student accounts. Class roles never become System roles. ## Tenant-admin outcome @@ -25,7 +25,10 @@ Tenant administrators manage classes, student education relationships, invitatio - Management permissions are independent: `education:class:create`, `education:class:query`, `education:class-member:query`, and `education:class-invitation:create`. Permissions are provisioned separately by System RBAC and are not represented in class relationships. - All aggregate tenant IDs come from `TenantContextHolder`; tenant-qualified mapper predicates and PostgreSQL triggers reject cross-tenant relationships. - Invitation creation is idempotent per tenant, actor, and key with request-hash conflict detection. Acceptance locks the invitation, checks invitee and expiry, and writes relationship plus audit in one transaction. -- This bounded slice intentionally excludes account creation, password handling, platform tenant-ignore operations, education profile duplication, and supervision relationships. +- This capability intentionally excludes account creation, password handling, platform tenant-ignore operations, and education profile duplication. +- V4270 adds supervision without changing account ownership: risk evidence is aggregated from existing Education practice, wrong-question, session, and vocabulary tables; Member and System data are API projections only. +- Classes, rules, and follow-ups carry only `dept_id`/`owner_user_id` authorization projections and are registered with RuoYi `DeptDataPermissionRule` for department/self row scope. +- Risk preview, rule authoring, idempotent task generation, and optimistic task handling have independent System permissions. `(tenant_id, batch_key, student_user_id)` is the retry-safe generation key; atomic PostgreSQL conflict-ignore and a subsequent scoped read return the same committed task to concurrent callers without recovering from a failed transaction. ## Role and permission semantics @@ -42,6 +45,9 @@ Tenant administrators manage classes, student education relationships, invitatio - [x] Invitation acceptance is idempotent and auditable. - [x] Platform-admin tenant-ignore operations are explicit and permission guarded (none are exposed by this bounded capability). - [x] Database changes use `flyway-postgresql`. +- [x] Risk preview and follow-up reads respect tenant plus System department/self data scope, including an unfiltered-class CTE aggregation path verified against a real `LoginUser`/`DeptDataPermissionRespDTO` PostgreSQL context. +- [x] Supervision assignees are validated by `AdminUserApi`, and students are enriched by `MemberUserApi`. +- [x] Follow-up generation and handling are duplicate-safe and stale-write-safe. ## Risk and rollback diff --git a/docs/education/migration/issues/EDU-014-extended-learning.md b/docs/education/migration/issues/EDU-014-extended-learning.md index b21af72e..47de7dae 100644 --- a/docs/education/migration/issues/EDU-014-extended-learning.md +++ b/docs/education/migration/issues/EDU-014-extended-learning.md @@ -16,7 +16,7 @@ | Video entitlement and progress | Entitlement owner unresolved; media delivery outside Education | **Explicitly retired from this wave.** No video endpoint, token, progress write, or metadata-based access bypass is added. Legacy video/progress data is retained until an entitlement-led child slice defines import and deletion policy. | P3 blocked | Capability manifest must expose no video interface | | Recommendation and AI generation | AI public services plus Education authorization | **Explicitly deferred.** No student profile or learning history is sent to AI, and no AI recommendation endpoint is exposed. Legacy recommendation data remains retained but non-authoritative. | P3 blocked | Capability manifest must expose no AI interface | | Notifications and reminders | Education schedule + System `NotifyMessageSendApi` | **Migrated for exam reminders.** Education owns claim/retry state; System owns message rendering/storage. A stable `education_exam_reminder` template is conditionally seeded. | P1 | Due claim, retry, tenant context, notify-port tests | -| Points, badges, check-ins, feedback, exam countdowns | Education orchestration + Member `MemberPointApi`; feedback/reminders Education | **Partially migrated.** Fixed server-side learning awards, one badge family, feedback, and exam reminder/countdown data are delivered. Generic check-in/task exchange remains Member-owned/deferred. | P1 bounded | Duplicate award, fixed-value, port failure, feedback ownership | +| Points, badges, check-ins, feedback, exam countdowns | Education orchestration + Member `MemberPointApi`; feedback/reminders/badge rules Education; System Notify | **Partially migrated.** Fixed server-side learning awards, configurable tenant badge definitions, automatic practice/vocabulary/feedback rules, lifetime-once manual grants, tenant-admin handling/audit, bounded resolved-feedback rewards, and exam reminder/countdown data are delivered. Generic check-in/task exchange and badge triggers that depend on not-yet-migrated check-in/mock-exam/activity domains remain deferred. | P1 bounded | Duplicate award/grant, invalid rule, optimistic conflict, notify failure, feedback ownership | | Learning analytics, leaderboard, trends, reports | Education projections over immutable reports/vocabulary/awards | **Partially migrated.** Own summary and bounded tenant leaderboard are delivered. Leaderboard is anonymized and contains no user IDs or report details. Trend/export surfaces are deferred. | P1 bounded | Tenant isolation, deterministic ranking, redaction | ## Executable APIs @@ -29,6 +29,9 @@ Student identity and tenant are always derived from the authenticated context; n - Learning award orchestration: `/app-api/education/learning/awards` - Own learning summary: `/app-api/education/learning/summary` - Anonymous tenant leaderboard: `/app-api/education/learning/leaderboard` +- Tenant-admin learning operations: `/admin-api/education/learning-operations/*` +- Student badge projection: `/app-api/education/engagement/badges` +- Tenant-admin badge definitions and grants: `/admin-api/education/badge/*` - Scheduled dispatch bean: `examReminderSendJob` using System Notify public API ## Security and reversibility @@ -37,6 +40,9 @@ Student identity and tenant are always derived from the authenticated context; n - Client requests cannot choose point values, badge codes, notify templates, delivery users, or leaderboard tenant. - Exam reminder delivery uses a token-fenced, expiring database claim. An interrupted `SENDING` row is reclaimed after lease expiry, attempts are bounded, and exhausted claims become observable `FAILED` rows through V4200. - Award rows and reminder rows are durable retry authorities; cross-module tables are never written directly by Education application code. +- Education point calls use stable Member ledger business keys backed by V4250 uniqueness. Feedback rewards require `RESOLVED` state, a separate reward permission, and a bounded server-validated value. +- Badge definitions and automatic rules are tenant-owned. Badge grants reuse the Education award ledger, validate manual targets through Member, project administrators through System, and use a partial unique key for lifetime-once delivery. A failed System notification is recorded as `FAILED` without undoing the grant. +- Automatic badge rules can subscribe only to implemented Education events (`PRACTICE_SUBMIT`, `VOCABULARY_REVIEW`, `FEEDBACK_RESOLVED`); absent legacy domains are not represented as fake triggers. - Leaderboard output uses deterministic tenant-local aliases and aggregate score only. No phone, profile, member ID, answer, explanation, feedback content, or report detail is exported. - Rollback disables/removes executable application paths while retaining V4160 data. Destructive rollback is not provided; later correction uses a higher forward migration. - Video and AI remain fail-closed: this wave exposes no executable interface and establishes no entitlement through access metadata. diff --git a/docs/education/migration/issues/EDU-017-tenant-appearance.md b/docs/education/migration/issues/EDU-017-tenant-appearance.md new file mode 100644 index 00000000..7877ff13 --- /dev/null +++ b/docs/education/migration/issues/EDU-017-tenant-appearance.md @@ -0,0 +1,38 @@ +# EDU-017 — Tenant appearance, public settings, and theme lifecycle + +- **Status:** done — bounded appearance/theme slice implemented and verified +- **Type:** tenant administration / public presentation configuration +- **Phase:** 4 / tenant operations +- **Blockers:** EDU-003, System Tenant authority, Vben admin foundation + +## Decision + +System Tenant remains authoritative for tenant identity, display name, lifecycle, and bound websites. Education owns only presentation-specific configuration: branding extensions, student/admin feature flags, public runtime configuration, and draft/published theme state. The public tenant locator response remains the minimal EDU-003 contract; appearance is exposed separately under the normal request tenant context. + +Platform theme templates are global trusted configuration and are explicitly excluded from MyBatis tenant injection. Tenant appearance rows extend `TenantBaseDO`, retain one live row per tenant, and use optimistic versions for every update. RuoYi System RBAC, tenant validation, admin projection, API access/operation logging, and the normal `tenant-id` security filter remain authoritative. + +## Delivered contract + +- Admin read, branding write, settings write, theme preview, and theme publish endpoints with independent permissions. +- `GET /education/tenant-appearance/public` is anonymous but not tenant-ignored; it requires the normal validated tenant header/context and returns no admin flags or draft data. +- System tenant name is the lazy-row default and fallback; no duplicate Education tenant identity or domain authority is created. +- Three legacy-compatible templates are seeded: `classic`, `focus`, and `high-contrast`. +- Theme preview is a shallow template/override merge. Publication revalidates the persisted draft and clears it atomically. +- Recursively rejects secret/password/token/private-key/API-key-like public keys except `secretRef`; theme tokens, CSS variables, icons, assets, colors, radii, modes, and density are allowlisted and unsafe renderable strings fail closed. +- Vben page `education/tenant-appearance/index` manages branding, JSON settings, templates, draft preview, and explicit publication with client-side preflight checks. + +## Verification + +- Policy unit tests cover recursive secret rejection, closed theme/asset key sets, colors/radii/CSS, URLs, and shallow merge. +- Service unit tests cover System-name fallback, optimistic conflict, sanitized draft persistence, and public projection. +- Method-security contract tests prove query, branding, settings, and theme permissions are independent. +- Real PostgreSQL service tests prove lazy defaults, shared templates, settings, draft/publish, public projection, stale-version rejection, and cross-tenant isolation through the production MyBatis interceptor. +- Flyway tests prove the V4290 schema, exact template seeds, menu shape, unique tenant row, and draft/publish database state. + +## Explicitly open + +- Domains stay in System Tenant `websites`; no Education domain CRUD authority is planned. +- Payment-account configuration must compose Pay rather than copy the legacy table. +- Authentication-provider configuration must reuse System/Member authentication seams. +- Tenant secret storage/rotation needs a dedicated encrypted private-storage decision and must never be added to the public appearance table. +- Activation codes and coupons need a Mall Promotion/Member entitlement ownership and idempotent redemption decision. diff --git a/docs/education/migration/issues/EDU-018-native-payment-social-admin.md b/docs/education/migration/issues/EDU-018-native-payment-social-admin.md new file mode 100644 index 00000000..ffa7727d --- /dev/null +++ b/docs/education/migration/issues/EDU-018-native-payment-social-admin.md @@ -0,0 +1,33 @@ +# EDU-018 — Reuse native payment and social-provider administration + +- **Status:** done — bounded native-administration reuse implemented and verified +- **Type:** tenant administration / module reuse +- **Phase:** 4 / tenant operations +- **Blockers:** EDU-017, Pay module, System social-client module, Vben admin foundation + +## Decision + +Legacy `tenant_payment_accounts` and supported OAuth-provider administration must not become Education-owned shadow tables. Payment applications/channels remain authoritative in Pay; tenant third-party login clients remain authoritative in System. V4300 places their existing Vben pages under the Education menu and grants only their original granular permissions. + +The duplicated menu locations use unique route names, so roles may receive Education-scoped navigation without changing the original Pay/System routes. All requests still reach the native controllers and services. No credential is copied into Education and no compatibility facade invents a second status model. + +## Delivered contract + +- Education menu entry for native `pay/app/index` with Pay App and Pay Channel query/create/update/delete permissions. +- Education menu entry for native `system/social/client/index.vue` with Social Client query/create/update/delete permissions. +- Fail-closed Flyway menu-shape validation and collision detection. +- Existing Pay/System Vben forms, controllers, tenant interception, and validation are reused. EDU-020/V4320 subsequently activates the Pay runtime and supplies the missing tenant-scoped App/Channel PostgreSQL contract. + +## Verification + +- Real PostgreSQL Flyway test verifies both route components, unique route names, all twelve native permissions, and V4300 history. +- A conflicting pre-existing menu ID causes V4300 to fail rather than silently binding the wrong permission. +- The existing Vben production build already compiles both reused native pages; V4300 adds no frontend source or dependency. + +## Explicitly open + +- EDU-021 now maps bounded `tenant_collect` WeChat/Alipay accounts into native Pay with a redacted audit. Platform/service-provider modes, XPay/Xunhu replacement, and production bulk export/runbook work remain open. +- `system_sms_channel` is global and `@TenantIgnore`; it is not legacy tenant-level auth-provider equivalence. +- Aliyun PNVS has no proven native target provider and remains a separate auth/SMS slice. +- Generic tenant secret storage/rotation remains separate. Native Pay/System credentials stay owned by those modules. +- Activation codes and coupons remain a Mall Promotion/Member entitlement and idempotent-redemption slice. diff --git a/docs/education/migration/issues/EDU-019-learning-activation-codes.md b/docs/education/migration/issues/EDU-019-learning-activation-codes.md new file mode 100644 index 00000000..a1d3ddd2 --- /dev/null +++ b/docs/education/migration/issues/EDU-019-learning-activation-codes.md @@ -0,0 +1,34 @@ +# EDU-019 — Secure learning activation codes + +## Status + +Done for the bounded V4310 contract. Legacy activation-code import and coupons remain separate. + +## Decision + +Activation codes are Education learning-access credentials, not Mall Promotion coupons. They reference a Mall-owned SPU through the existing tenant-scoped `education_resource_product_binding`, authenticate redemption with the existing Member principal, and grant access through the existing idempotent `EducationEntitlementService` event pipeline. No product, member, coupon, or second entitlement ledger is introduced. + +## Delivered contract + +- Tenant-owned activation-code batches and codes with database constraints, tenant-composite foreign keys, optimistic versions, and separate query/manage/generate permissions. +- Admin batch page/create/update/generate and masked code page/disable endpoints. +- Member-only app check/redeem endpoints; administrator and anonymous principals fail closed. +- Cryptographically random codes normalized for redemption. Plaintext is returned only by the successful generation response; persistence stores SHA-256 digest and a mask. +- `SELECT ... FOR UPDATE` serializes redemption. A successful grant writes the existing entitlement event/aggregate, marks the code redeemed, and increments the batch count in one transaction. +- Same-member retry returns the existing entitlement idempotently; another member receives an already-used conflict. +- Batch SPU, duration, and prefix become immutable after generation. Disabled batches, codes, or resource bindings cannot be redeemed. `durationDays=0` intentionally means no expiry. +- Vben page `education/activation-code/index` provides batch and masked-code tables, create/edit/generate flows, copy/download, unsaved-plaintext dismissal warning, and disable confirmation. Closing the generation modal clears plaintext. + +## Verification + +- PostgreSQL Flyway verifies both tables, constraints, cross-tenant batch references, four menu rows, and migration history through V4310. +- Controller contracts verify independent admin permissions and Member-only app access. +- Unit tests verify digest/mask persistence, entitlement composition, replay/conflict, target validation, and generated-batch immutability. +- Real PostgreSQL service tests verify digest-only persistence, tenant isolation, entitlement/event creation, same/different-member behavior, disabled dependencies, and one winner under concurrent redemption. +- Vben formatting, lint, Vue typecheck, and production build cover the fifteenth custom Education page. + +## Explicit non-goals + +- Importing or preserving plaintext from legacy activation-code rows. +- Coupon templates, claims, discounts, stacking, or redemption; those remain Mall Promotion-owned. +- Payment/order/refund fulfillment, legacy payment mode/provider mapping, tenant PNVS, or generic encrypted secret rotation. diff --git a/docs/education/migration/issues/EDU-020-native-pay-runtime.md b/docs/education/migration/issues/EDU-020-native-pay-runtime.md new file mode 100644 index 00000000..3e8ab25a --- /dev/null +++ b/docs/education/migration/issues/EDU-020-native-pay-runtime.md @@ -0,0 +1,36 @@ +# EDU-020 — Activate tenant-scoped native Pay administration + +- **Status:** done — bounded Pay App/Channel runtime activation implemented and verified +- **Type:** platform reuse / tenant security / database takeover +- **Phase:** 4 / tenant operations +- **Blockers:** EDU-018, native Pay module, PostgreSQL Flyway + +## Problem + +V4300 deliberately reused the native Pay App/Channel controllers, permissions, and Vben page, but the repository reactor and `yudao-server` still excluded `yudao-module-pay`, and the active PostgreSQL baseline had no `pay_app` or `pay_channel` tables. The menu was therefore only navigational evidence, not an operational payment-configuration backend. + +Stock/global Pay tables are also unsafe to adopt silently in a multi-tenant education deployment. A channel supplied with an arbitrary `appId` must not bind to an application outside the current tenant. + +## Delivered contract + +- `yudao-module-pay` is included in the reactor and server runtime. +- V4320 creates Pay-owned `pay_app` and `pay_channel` tables with tenant columns, logical-delete audit fields, active-row uniqueness, status checks, and tenant-first indexes. +- Existing Pay tables without `tenant_id` fail migration with an explicit mapping error; V4320 never assigns legacy credentials to tenant `0` or another guessed tenant. +- `PayAppDO` explicitly extends `TenantBaseDO`, so framework MyBatis tenant interception is a declared contract rather than an implicit table convention. +- Channel create/update verifies that the referenced application is visible to the current tenant before persisting the channel. +- `/pay/app/list` now uses the actual `pay:app:query` permission already granted by V4300 instead of the obsolete `pay:merchant:query` permission. +- The existing `pay/app/index` Vben page remains authoritative; no Education payment form or credential table is duplicated. + +## Verification + +- 17 `PayChannelServiceTest` checks pass, including missing/cross-tenant-parent rejection seams. +- Two Pay tenant/permission contract checks pass. +- All 45 current PostgreSQL Flyway tests pass through V4380, including same `app_key` across tenants, duplicate rejection within a tenant, channel uniqueness, V4320 history, and fail-closed adoption of a global `pay_app` table. +- `mvn -pl yudao-server -am -DskipTests compile` includes and compiles the native Pay module. + +## Explicitly open + +- EDU-021 now provides an explicit, audited single-account import for `tenant_collect` WeChat/Alipay manifests. Platform/service-provider modes, unsupported providers, and production bulk export/runbook work remain open. +- EDU-022 now activates tenant-scoped native Pay order, refund, and notification persistence/UI; EDU-023 adds bounded terminal legacy transaction import; EDU-024 activates native Transfer/Wallet persistence and UI without inventing opening balances. +- Payment credentials remain Pay-owned. Generic tenant secret encryption/rotation and PNVS remain separate slices. +- EDU-025 delivers native Mall Product activation. Explicit legacy product import, Promotion/Trade, coupon redemption, purchase fulfillment, refund-to-entitlement revocation, and reconciliation remain separate commercialization slices. diff --git a/docs/education/migration/issues/EDU-021-legacy-pay-account-import.md b/docs/education/migration/issues/EDU-021-legacy-pay-account-import.md new file mode 100644 index 00000000..2927cb72 --- /dev/null +++ b/docs/education/migration/issues/EDU-021-legacy-pay-account-import.md @@ -0,0 +1,51 @@ +# EDU-021 — Import legacy tenant payment accounts into native Pay + +- **Status:** done — bounded single-account import, audit, and native Pay UI entry implemented and verified +- **Type:** legacy data bridge / payment security / tenant isolation +- **Phase:** 4 / tenant operations +- **Blockers:** EDU-020, native Pay App/Channel runtime, PostgreSQL Flyway + +## Problem + +The legacy `tenant_payment_accounts` and `app_private.tenant_secrets` records cannot be copied directly into native Pay. Provider aliases, collection modes, channel variants, callback ownership, credential shapes, and status values are not one-to-one. Guessing any of them can route money or callbacks to the wrong party. + +The migration also needs durable evidence without creating an Education payment shadow model or persisting a second plaintext credential copy. + +## Delivered contract + +- The bridge is Pay-owned and creates native `pay_app` and `pay_channel` rows through `PayAppService` and `PayChannelService`; Education owns neither a payment account nor a credential table. +- `POST /pay/legacy-account-import/import` imports exactly one explicitly reviewed manifest. It requires both `pay:app:create` and `pay:channel:create`. +- `GET /pay/legacy-account-import/page` exposes tenant-filtered audit history and requires both Pay App and Channel query permissions. +- The existing `pay/app/index` Vben page adds a **迁移旧支付账号** action and JSON manifest modal. The button uses explicit AND permission visibility, matching the controller. +- Import request-body logging is disabled for database access logs, non-production request logs, and unexpected-error logs so the manifest does not become a plaintext logging side channel. +- Only `tenant_collect` is accepted. `platform_collect` and `service_provider` fail closed because their settlement and merchant ownership semantics are not equivalent. +- Historical WeChat and Alipay aliases normalize to `wechat_pay` or `alipay`. Non-equivalent providers such as XPay/Xunhu fail closed. +- Operators must explicitly select a native channel such as `wx_lite`, `wx_pub`, or an Alipay variant. Provider family and channel family must match; the importer never guesses a WeChat client type. +- WeChat V3 and Alipay public-key configurations map into native Pay configuration objects. Multiple rotating WeChat platform keys require an explicit choice. Alipay accepts only the native production or sandbox official gateway. +- Old provider callbacks are not reused. The manifest must provide new business order/refund callbacks and may provide a transfer callback. +- `active` maps to enabled. `disabled` and `pending` map to disabled with an audit note. +- Within the current target tenant, `sourceAccountId` is the idempotency key. A replay with the same source SHA-256 returns the existing mapping; a different checksum is rejected rather than overwriting it. + +## Audit and database contract + +V4330 creates tenant-scoped `pay_legacy_account_import` with source identifiers/checksum, normalized provider/config digest, target App/Channel IDs, mapping notes, operator, and timestamp. It deliberately has no `config_public`, `secret_json`, raw config, or secret-value column. + +Composite foreign keys `(tenant_id,target_app_id)` and `(tenant_id,target_channel_id)` prevent an audit row from pointing across tenants. A target tenant may import the same legacy UUID independently, while duplicate active source IDs inside one tenant are rejected. An existing global audit table without `tenant_id` causes migration failure and requires explicit disposition. + +Credentials still enter native `pay_channel.config` using Pay's existing configuration storage. EDU-021 prevents an extra audit copy; it does not introduce generic encryption or key rotation for native Pay credentials. + +## Verification + +- Nine focused importer tests pass for WeChat/Alipay mapping, aliases, disabled-state mapping, replay, checksum conflict, unsupported modes/providers, channel mismatch, unsafe endpoints/rotating keys, and audit redaction. A controller contract verifies dual write permission and request-body logging suppression. +- The combined Pay selection passes 29 tests, including the prior tenant App/Channel contracts. +- All 45 PostgreSQL Flyway integration tests pass through V4380. V4330 coverage verifies tenant-independent legacy UUID reuse, no raw credential columns, cross-tenant composite-FK rejection, mode constraints, migration history, and fail-closed global-table adoption. +- The Vben `@vben/web-antd` typecheck passes with the import API, modal, and explicit dual-permission button. + +## Explicitly open + +- Audit history currently has a backend/Vben API contract but no dedicated history table in the account-import modal. EDU-023 provides its own recent transaction-import history table on the native order page. +- A controlled export job from the legacy database and operator runbook are still required before production bulk migration. The UI template contains placeholders and must never be submitted unchanged. +- Concurrent first imports of the same source account rely on the database unique constraint and transaction rollback; a friendly concurrent-replay response is not claimed. +- Platform/service-provider settlement, XPay/Xunhu replacement, generic credential encryption/rotation, and tenant PNVS require separate decisions. +- EDU-022 activates tenant-scoped native Pay order, refund, and notification ledgers; EDU-023 adds bounded terminal legacy transaction import; EDU-024 activates empty native Transfer/Wallet ledgers. Production bulk tooling and reviewed opening-balance migration remain open. +- EDU-025 delivers native Mall Product activation. Explicit legacy product import, Promotion/Trade, coupon import/redemption, purchase fulfillment, refunds-to-entitlement revocation, and reconciliation remain separate commercialization work. diff --git a/docs/education/migration/issues/EDU-022-native-pay-transactions.md b/docs/education/migration/issues/EDU-022-native-pay-transactions.md new file mode 100644 index 00000000..a0a0eb9e --- /dev/null +++ b/docs/education/migration/issues/EDU-022-native-pay-transactions.md @@ -0,0 +1,45 @@ +# EDU-022 — Activate tenant-scoped native Pay transactions + +- **Status:** done — bounded order/refund/notification takeover and native administration UI verified +- **Type:** platform reuse / financial isolation / database takeover +- **Phase:** 4 / commercialization foundation +- **Blockers:** EDU-020, EDU-021, native Pay runtime, PostgreSQL Flyway + +## Problem + +EDU-020 made native Pay application/channel configuration operational, but the active PostgreSQL baseline still had no order, order-extension, refund, notification-task, or notification-log tables. The existing Pay controllers and Vben pages therefore could not administer real transaction state. + +The stock transaction data objects were also inconsistent: notification tasks were tenant-aware, while orders, order extensions, refunds, and notification logs inherited only `BaseDO`. Silently creating global financial tables or assigning existing rows to a guessed tenant would make callbacks, exports, and background retries cross tenant boundaries. + +## Delivered contract + +- V4340 creates Pay-owned `pay_order`, `pay_order_extension`, `pay_refund`, `pay_notify_task`, and `pay_notify_log` tables for PostgreSQL. Education does not create a parallel order, refund, or webhook ledger. +- `PayOrderDO`, `PayOrderExtensionDO`, `PayRefundDO`, `PayNotifyTaskDO`, and `PayNotifyLogDO` all inherit `TenantBaseDO`, so normal MyBatis tenant interception scopes native admin queries and mutations. +- Composite tenant foreign keys bind orders to Pay applications/channels, extensions to orders/channels, refunds to their application/channel/order, and logs to their notification task. Cross-tenant references fail in PostgreSQL even if application code is bypassed. +- Active merchant order/refund identifiers are unique inside a tenant application but may be reused by another tenant. Native Pay numbers and extension numbers are tenant-scoped. +- One active notification task is allowed for each `(tenant_id,type,data_id)`. This closes duplicate terminal-callback races; a deliberately soft-deleted task may be recreated. +- Existing transaction tables without `tenant_id` fail V4340. No global financial row is assigned to tenant `0` or inferred from an application ID. +- The native order/refund callback entry points continue to resolve the channel first and execute the business update inside `TenantUtils.execute(channel.tenantId, ...)`. The existing notification retry job continues to use `@TenantJob`. +- Existing Pay controllers remain authoritative: `/pay/order` and `/pay/refund` provide tenant-filtered query/export contracts, while `/pay/notify` provides tenant-filtered task/detail reads and the provider callback entry points. +- V4340 exposes the existing `pay/order/index`, `pay/refund/index`, and `pay/notify/index` Vben pages below Education with the original `pay:order:*`, `pay:refund:*`, and `pay:notify:query` permissions. No custom Education transaction page was added. + +## Database safety + +The circular order/extension relationship is created in two steps. The final `fk_pay_order_extension` installation is guarded through `pg_constraint`: an equivalent named composite tenant foreign key is accepted, a conflicting named constraint fails closed, and a missing constraint is installed. V4340 also verifies the complete required column shape after table creation. + +The migration intentionally creates empty native transaction ledgers. It does **not** import legacy `orders`, `payments`, `payment_events`, or `commerce_refund_requests`; importing those records requires an explicit, reconciled mapping with amount/status/identifier/callback ownership rules. + +## Verification + +- All 45 PostgreSQL Flyway integration tests pass through V4380. V4340 coverage proves same merchant order ID across tenants, composite-FK cross-tenant rejection, active notification-task uniqueness and soft-delete recreation, native menu/permission shape, successful migration history, and fail-closed adoption of a global `pay_order` table. +- The combined native Pay transaction regression passes 86 tests: 46 order, 28 refund, 11 notification, and one tenant-inheritance contract. +- The previously disabled `PayNotifyServiceTest` is active; its asynchronous scheduling assertions and retry-count fixtures now match the production contract. +- `mvn -pl yudao-server -am -DskipTests compile` and the Vben `@vben/web-antd` typecheck are the closing reactor/UI gates for this slice. + +## Explicitly open + +- EDU-023 now supplies a bounded, terminal-only reconciled import. Production export tooling, reviewed Member-ID mapping, dry-run/runbook evidence, and operator sign-off remain required; direct table copying is still forbidden. +- EDU-024 now activates tenant-aware native Pay Transfer and Wallet persistence plus the existing administration pages. Historical opening balances remain deliberately unpopulated pending a reviewed source artifact. +- EDU-025 delivers native Mall Product activation and EDU-026 delivers native Promotion Coupon activation. Explicit legacy product/code-coupon import, Trade/other Promotion activation, and automatic purchase-to-entitlement fulfillment are not delivered. +- Refund completion does not yet revoke or shorten Education entitlements; commerce reconciliation must define partial-refund and replay semantics first. +- Coupons, commissions, referrals, dunning, settlement/reconciliation, generic Pay credential encryption/rotation, tenant PNVS, production deployment, and browser/API integration evidence remain separate work. diff --git a/docs/education/migration/issues/EDU-023-legacy-pay-transactions.md b/docs/education/migration/issues/EDU-023-legacy-pay-transactions.md new file mode 100644 index 00000000..a2aab0a6 --- /dev/null +++ b/docs/education/migration/issues/EDU-023-legacy-pay-transactions.md @@ -0,0 +1,41 @@ +# EDU-023 — Import reconciled terminal legacy Pay transactions + +- **Status:** done — bounded terminal aggregate import, redacted audit, and native Pay order-page UI verified +- **Type:** legacy data bridge / financial reconciliation / privacy +- **Phase:** 4 / commercialization foundation +- **Blockers:** EDU-021, EDU-022, PostgreSQL Flyway + +## Problem + +The legacy `orders`, `payments`, `payment_events`, and `commerce_refund_requests` rows cannot be copied into native Pay independently. Their statuses, identifiers, and totals form one aggregate; importing a live or inconsistent aggregate could make native jobs, callbacks, or operators charge or refund it again. Raw provider payloads and error bodies also contain data that does not belong in a second audit store. + +## Delivered contract + +- `POST /pay/legacy-transaction-import/import` imports one explicitly reviewed aggregate and requires `pay:legacy-transaction:import`. `GET /pay/legacy-transaction-import/page` exposes tenant-filtered audit history under `pay:legacy-transaction:query`. +- Only terminal order, payment, and refund states are accepted. Amounts are already denominated in cents and must reconcile exactly: one verifiable successful payment at most, payment amount equals order price, successful refund sum equals `refundedPrice`, and order/refund status agrees with that sum. +- Every aggregate must reference an EDU-021 `pay_legacy_account_import` from the same source tenant. All payment and refund provider families must match that reviewed mapping. +- Imported data is written into Pay-owned `pay_order`, `pay_order_extension`, and `pay_refund`; Education does not gain a financial shadow ledger. +- Import does not call a provider SDK, enqueue notification tasks, invoke business callbacks, or copy provider credentials. `raw_payload`, event payloads, channel notification bodies, and error originals are excluded. Payment-event evidence is reduced to a count and SHA-256 digest. +- A target tenant uses source order UUID as its idempotency key. The same checksum replays the audit result without rewriting native ledgers; a different checksum is rejected. Native merchant order, payment extension, and refund-number collisions fail with a reconciliation error before writes. +- The legacy model has no reliable client IP or channel fee. Native rows therefore use `0.0.0.0` and zero fee, and the audit records that limitation. +- `targetUserId` is an explicit optional native Member ID. The importer does not infer a UUID-to-Member mapping and Pay does not depend directly on Member, avoiding a module cycle. Export tooling or the operator owns that reviewed mapping. +- Request-body access logging is disabled. The existing native `pay/order/index` page exposes a permission-aware JSON manifest modal and the most recent 50 audit rows; no raw provider payload is rendered. + +## Database contract + +V4350 adds tenant-scoped `pay_legacy_transaction_import`, `pay_legacy_transaction_payment_import`, and `pay_legacy_transaction_refund_import`. Composite tenant foreign keys bind audit rows to the reviewed account mapping and native App, Channel, Order, Extension, and Refund targets. Existing global tables fail closed. The same legacy source UUID may be imported independently by distinct target tenants. + +The schema enforces terminal status sets, non-negative counts/totals, event-count/digest consistency, lowercase SHA-256 shapes, tenant-scoped source uniqueness, and exact expected table shape. None of the three tables contains a raw payload, credential, notification body, or error-original column. + +## Verification + +- Nine focused service/controller tests pass, covering reconciled full-refund import, redaction, replay, checksum conflict, provider mismatch, refund/status mismatch, event digest mismatch, refund-without-payment rejection, native-number collision, permission, and request-log suppression. +- All 45 PostgreSQL Flyway integration tests pass through V4380. V4350 coverage proves composite cross-tenant rejection, per-target-tenant source reuse, event digest consistency, sensitive-column absence, exact menu/permission shape, migration history, and fail-closed global-table adoption. +- Vben lint, formatting, and `@vben/web-antd` typecheck pass for the API, import/history modal, and native order-page integration. + +## Explicitly open + +- A controlled legacy export tool, Member-ID mapping artifact, operator runbook, dry-run report, backup, and production reconciliation sign-off are required before bulk import. The UI template contains placeholders and is not an unattended bulk migrator. +- Concurrent first imports and native-number races still rely on database uniqueness and transaction rollback; a friendlier conflict replay is not claimed. +- Failed/cancelled historical attempts are preserved as closed native extensions/refunds only inside a reconciled terminal aggregate. No live state is resumed. +- EDU-024 activates native Pay Transfer/Wallet with empty tenant-owned ledgers, and EDU-025 activates an empty native Product catalog. Reviewed opening balances, explicit legacy product import, Promotion/Trade, automatic purchase fulfillment, refund-to-entitlement revocation, settlement reconciliation, coupons, commissions, referrals, dunning, generic credential encryption/rotation, tenant PNVS, and production browser/API evidence remain separate slices. diff --git a/docs/education/migration/issues/EDU-024-native-pay-transfer-wallet.md b/docs/education/migration/issues/EDU-024-native-pay-transfer-wallet.md new file mode 100644 index 00000000..fdbdec0c --- /dev/null +++ b/docs/education/migration/issues/EDU-024-native-pay-transfer-wallet.md @@ -0,0 +1,41 @@ +# EDU-024 — Activate native Pay Transfer and Wallet + +- **Status:** done — tenant-owned native ledgers, security hardening, existing Pay APIs/UI, and PostgreSQL evidence delivered +- **Type:** native module takeover / financial ledger / tenant isolation +- **Phase:** 4 / commercialization foundation +- **Blockers:** EDU-020, EDU-022, PostgreSQL Flyway + +## Problem + +RuoYi already provides Transfer and Wallet services, callbacks, jobs, permissions, and Vben pages, but their persistence was not activated by the Education PostgreSQL migration line and the corresponding data objects were not tenant-aware. Reimplementing those capabilities inside Education would create a second financial ledger. Inferring a wallet balance from the legacy backend would be unsafe because the legacy schema has no equivalent authoritative balance aggregate. + +## Delivered contract + +- Existing native Pay contracts remain authoritative: `/pay/transfer`, `/pay/wallet`, `/pay/wallet-transaction`, `/pay/wallet-recharge`, and `/pay/wallet-recharge-package`. No Education transfer, balance, recharge, or transaction controller was added. +- `PayTransferDO`, `PayWalletDO`, `PayWalletTransactionDO`, `PayWalletRechargeDO`, and `PayWalletRechargePackageDO` now extend `TenantBaseDO`. Transfer synchronization keeps `@TenantJob`, and framework tenant injection scopes all normal mapper access. +- Wallet locks now use `pay_wallet:lock:{tenantId}:{walletOrUserId}` so the same native identifier in two tenants cannot serialize against or interfere with the other tenant's operation. +- Amount-changing service methods reject null, zero, and negative amounts. Administrator balance reductions use the conditional subtract path and cannot create a negative balance or incorrectly increase the member's lifetime expense total. `Integer.MIN_VALUE` cannot be negated through the admin request contract. +- Wallet recharge refund completion uses the persisted recharge `walletId`; it does not dereference a separately loaded wallet. The refund action has its own `pay:wallet-recharge:refund` permission. +- Wallet-provider transfer status lookup uses the native transfer number, matching the business key used when the wallet transaction was created. +- Request DTOs validate positive user/package IDs, valid wallet business types, positive add amounts, non-zero admin adjustments, package name/amount/status shape, and non-negative bonus amounts. +- The existing Vben `pay/transfer/index`, `pay/wallet/balance/index`, and `pay/wallet/rechargePackage/index` pages and APIs are reused. Member administration continues to provide the permission-aware balance-adjustment action. + +## Database contract + +V4360 creates empty tenant-scoped `pay_transfer`, `pay_wallet`, `pay_wallet_transaction`, `pay_wallet_recharge`, and `pay_wallet_recharge_package` ledgers. It never imports or invents a historical wallet balance. + +Composite tenant foreign keys bind transfers to native Pay App/Channel and wallet rows to their Wallet, Package, Order, and Refund owners. Balances, frozen amounts, and cumulative totals cannot be negative. A tenant can have only one active wallet per `(userId,userType)`, while the same identifiers remain valid in another tenant. Non-administrator transaction business keys are tenant-idempotent. Existing global tables fail closed instead of being silently adopted. + +V4360 also seeds the existing Vben routes and granular Transfer query/export, Wallet query/update, Recharge Package CRUD, and Recharge refund permissions. + +## Verification + +- Twelve focused Pay tests pass across transfer service behavior, tenant-aware DO/job/permission contracts, tenant-qualified Redis locking, safe positive/negative admin adjustments, non-positive amount rejection, recharge-refund wallet identity, and wallet-provider transfer lookup. +- All 45 PostgreSQL Flyway integration tests pass through V4380. V4360 coverage proves same identifiers across tenants, same-tenant wallet uniqueness, cross-tenant Wallet Transaction and Transfer foreign-key rejection, database rejection of negative balances, exact menu/permission shape, migration history, and fail-closed global-wallet adoption. +- The existing Vben Transfer/Wallet/Recharge Package APIs and pages pass formatting, lint, and `@vben/web-antd` typecheck as part of the combined UI verification. + +## Explicitly open + +- Production opening balances require a separately reviewed, reconciled source artifact and operator runbook. This slice deliberately creates empty wallet ledgers because the legacy system has no equivalent balance authority. +- Transfer initiation, wallet recharge, channel callbacks, and refunds still require target-environment credentials, App/Channel setup, explicit role grants, and browser/API integration evidence. +- EDU-025 now activates native Mall Product persistence and existing administration without importing legacy display rows. Promotion/Trade, explicit legacy product import, automatic purchase fulfillment, refund-driven entitlement revocation, settlement reconciliation, commissions, referrals, dunning, generic credential encryption/rotation, tenant PNVS, legacy activation-code import, coupons, and production bulk financial migration remain separate slices. diff --git a/docs/education/migration/issues/EDU-025-native-mall-product.md b/docs/education/migration/issues/EDU-025-native-mall-product.md new file mode 100644 index 00000000..861ebba9 --- /dev/null +++ b/docs/education/migration/issues/EDU-025-native-mall-product.md @@ -0,0 +1,58 @@ +# EDU-025 — Activate native Mall Product + +- **Status:** done — tenant-owned native Product persistence, existing Product APIs/UI, RBAC, and PostgreSQL evidence delivered +- **Type:** native module takeover / product catalog / tenant isolation +- **Phase:** 5 / commercialization foundation +- **Blockers:** EDU-013, PostgreSQL Flyway + +## Problem + +RuoYi Vue Pro already provides Product controllers, services, mappers, permissions, and Vben pages for brands, categories, properties, SPUs, SKUs, comments, favorites, and browse history. The Mall reactor and Product server dependency were disabled, however, and the Product data objects were not tenant-aware. Rebuilding those capabilities inside Education would create a second product catalog. + +The legacy `public.products` object is only a tenant/region presentation projection with `title`, a display `price_label`, links, tags, cover/iframe/detail images, ordering, and lifecycle status. It has no authoritative SKU, integer price, stock, brand, property, delivery, commission, or sales model. An automatic conversion would invent commercial facts. + +## Delivered contract + +- The root build now includes `yudao-module-mall`, while `yudao-server` activates only `yudao-module-product`. Promotion, Trade, and Statistics runtime dependencies remain off until their own tenant-ledger slices are verified. +- Existing native `/product/brand`, `/product/category`, `/product/property`, `/product/property/value`, `/product/spu`, `/product/comment`, `/product/favorite`, and `/product/browse-history` controllers remain authoritative. No Education product controller or duplicate catalog service was added. +- `ProductBrandDO`, `ProductCategoryDO`, `ProductPropertyDO`, `ProductPropertyValueDO`, `ProductSpuDO`, `ProductSkuDO`, `ProductCommentDO`, `ProductFavoriteDO`, and `ProductBrowseHistoryDO` now extend `TenantBaseDO`, so normal MyBatis access participates in framework tenant injection. +- Existing Vben pages are reused for SPU/SKU authoring, category trees, brands, property/value management, and comment moderation. V4370 seeds their original permissions below an Education `商品中心` route with unique component names. +- The Product Vben scope passes typecheck, oxlint, and oxfmt. The formatter normalized one pre-existing multiline expression in the SPU form without changing behavior. + +## Database contract + +V4370 creates tenant-scoped PostgreSQL tables: + +- `product_brand` +- `product_category` +- `product_property` +- `product_property_value` +- `product_spu` +- `product_sku` +- `product_comment` +- `product_favorite` +- `product_browse_history` + +Every table uses `(tenant_id,id)` as its primary ownership key, allowing an explicit native identifier to be reused in another tenant while keeping every reference tenant-qualified. Composite foreign keys enforce Property Value → Property, SPU → Category/Brand, SKU → SPU, Comment → SPU/SKU, and Favorite/Browse History → SPU. Category root `parent_id=0` remains a sentinel; a trigger requires non-root parents to exist in the same tenant and limits the native category model to two levels. + +Database checks reject negative prices, stock, sales, integral, commission, weight, volume, and browse counts, and restrict comment scores to 1–5. Partial unique indexes protect active brand/property/value names, one active favorite per member/SPU, one active browse-history row per member/SPU, and one active comment per member/order item. Existing global Product tables fail closed instead of being silently assigned to a tenant. + +V4370 seeds menu IDs 6920–6944 for the Product root, five native pages, and the exact SPU, Category, Brand, Property, and Comment controller permissions. + +## Legacy data decision + +V4370 creates an empty native catalog and does not read or transform legacy `public.products`. A later import, if required, must provide an explicit reviewed mapping for tenant UUIDs, region semantics, integer prices, SPU/SKU structure, stock authority, brand/category/property ownership, delivery mode, media admission, and Education resource bindings. A display price label or URL is not sufficient evidence for any of those fields. + +## Verification + +- The focused Product tenant contract passes and proves all nine native Product records inherit `TenantBaseDO`. +- The Product reactor test run succeeds; the repository's 33 pre-existing Product service tests remain disabled by their existing test configuration, while the new tenant contract executes successfully. +- All 45 PostgreSQL Flyway integration tests pass through V4380. V4370 coverage proves cross-tenant ID reuse, same-tenant uniqueness, composite foreign-key rejection, category parent isolation/two-level enforcement, amount/stock and score checks, exact menu shape, migration history, and fail-closed global-table adoption. +- `mvn -pl yudao-server -am -DskipTests compile` succeeds with Product enabled. +- `@vben/web-antd` typecheck and scoped Product oxlint/oxfmt checks pass. + +## Explicitly open + +- Native Product pages require target-environment role grants, browser/API smoke evidence, media/file configuration, and deliberate catalog population. +- Promotion coupons are delivered by EDU-026; Promotion discounts and other activity families, Trade cart/order/after-sale/delivery, Statistics, automatic purchase fulfillment, refund-driven entitlement revocation, and Education product-binding workflows remain separate slices. +- Legacy `products` import remains blocked on an explicit semantic mapping and reconciled source artifact; no SKU, stock, price, or category is inferred. diff --git a/docs/education/migration/issues/EDU-026-native-mall-coupon.md b/docs/education/migration/issues/EDU-026-native-mall-coupon.md new file mode 100644 index 00000000..84ff466f --- /dev/null +++ b/docs/education/migration/issues/EDU-026-native-mall-coupon.md @@ -0,0 +1,51 @@ +# EDU-026 — Activate native Mall Promotion coupons + +- **Status:** done — tenant-owned native coupon templates/instances, existing APIs/UI, RBAC, and PostgreSQL evidence delivered +- **Type:** native module takeover / coupon lifecycle / tenant isolation +- **Phase:** 5 / commercialization foundation +- **Blockers:** EDU-025, PostgreSQL Flyway + +## Problem + +RuoYi Vue Pro already provides coupon-template and issued-coupon controllers, services, Product-scope validation, Member lookup, registration issuance, expiry processing, permissions, and Vben pages. The Promotion server dependency was disabled and its two coupon records inherited only `BaseDO`, so reimplementing coupons inside Education would create a second marketing ledger without fixing tenant ownership. + +Legacy `public.coupons` are code-based campaign rules with plan/region restrictions, first-order rules, usage counters, and `coupon_redemptions`. Native Promotion coupons are templates that issue member-owned coupon instances before order use. They are not losslessly interchangeable. + +## Delivered contract + +- `yudao-server` now activates `yudao-module-promotion` in addition to Product; Trade and Statistics remain disabled until their own ledger slices are verified. +- Existing native `/promotion/coupon-template`, `/promotion/coupon`, `/app-api/promotion/coupon-template`, and `/app-api/promotion/coupon` contracts remain authoritative. Education adds no coupon controller or duplicate service. +- `CouponTemplateDO` and `CouponDO` extend `TenantBaseDO`, so native mapper/service access participates in the framework tenant interceptor. +- Native template creation reuses Product SPU/category validation. Native coupon administration reuses Member lookup, direct/admin/registration issuance, expiry processing, use/return, and soft-delete recovery rules. +- Promotion's unrelated bargain/combination beans require `TradeOrderApi`. EDU-027 now activates the native Trade implementation and removes the temporary fail-closed adapter that was used while Trade was absent. +- V4380 mounts the existing Vben template and issued-coupon pages below an Education `优惠券中心` using the original seven controller permissions and unique component names. + +## Database contract + +V4380 creates tenant-scoped PostgreSQL tables: + +- `promotion_coupon_template` +- `promotion_coupon` + +Both use `(tenant_id,id)` ownership keys and explicitly named identity sequences. Coupon → Template is a tenant-qualified composite foreign key, so an identifier valid in another tenant cannot be referenced. Checks enforce native status/take/scope/validity/discount enums, fixed-date or relative-term validity, non-negative thresholds and discounts, issue/use counter consistency, positive members/orders, and complete used-coupon state. Indexes support template discovery, member/status lookup, template issuance lookup, and expiry jobs. Existing global coupon tables fail closed instead of being silently assigned to a tenant. + +V4380 seeds menu IDs 6950–6959 for the coupon root, template page, issued-coupon page, and exact query/create/update/delete/send permissions. + +## Legacy data decision + +V4380 starts the native coupon ledger empty. It does not reinterpret a legacy code campaign as a pre-issued member coupon, invent template/instance IDs, discard plan/region/first-order semantics, or attach historical redemptions to unverified native orders and members. A later compatibility/import slice must explicitly decide whether to preserve code redemption as a separate adapter or transform reviewed campaigns and redemption history. + +## Verification + +- The Promotion coupon tenant contract proves both native coupon records inherit `TenantBaseDO`; EDU-027 separately verifies the native Trade dependency that replaced the temporary fallback. +- The focused V4380 Flyway scenario passes and proves same IDs can exist across tenants, cross-tenant template references fail, invalid counters/discounts/used state fail, explicit sequences exist, exact menus/permissions are installed, and global-table adoption fails closed. +- All 45 PostgreSQL Flyway integration tests pass. +- `mvn -pl yudao-server -am -DskipTests compile` succeeds with Product and Promotion enabled. +- `@vben/web-antd` typecheck and scoped Coupon oxlint/oxfmt checks pass. + +## Explicitly open + +- Legacy code-campaign and redemption compatibility/import remains a separate mapping slice. +- EDU-027 activates the tenant-scoped normal-order core. Coupon-to-special-order and refund composition still depend on deferred Promotion and Trade after-sale slices. +- Discount/reward/seckill/combination/bargain/point/Diy/KeFu Promotion families are not activated at the database/menu level by this slice. +- Target-environment role grants, browser/API smoke evidence, and deliberate template population remain operational work. diff --git a/docs/education/migration/issues/EDU-027-native-mall-trade-order.md b/docs/education/migration/issues/EDU-027-native-mall-trade-order.md new file mode 100644 index 00000000..aecebbae --- /dev/null +++ b/docs/education/migration/issues/EDU-027-native-mall-trade-order.md @@ -0,0 +1,53 @@ +# EDU-027 — Activate native Mall Trade order core + +- **Status:** done — tenant-owned order core, native APIs/UI, RBAC, and PostgreSQL evidence delivered +- **Type:** native module takeover / order lifecycle / tenant isolation +- **Phase:** 5 / commercialization foundation +- **Blockers:** EDU-020, EDU-024, EDU-025, EDU-026, PostgreSQL Flyway + +## Problem + +RuoYi Vue Pro already provides the normalized Trade order aggregate, cart and price orchestration, Pay/Product/Promotion/Member composition, administrator order/config controllers, jobs, permissions, and Vben pages. The Server dependency was disabled and its core data objects inherited only `BaseDO`. Rebuilding orders inside Education would create a second commerce ledger and duplicate native payment, member, catalog, and coupon boundaries. + +Legacy orders cannot be copied safely from terminal payment aggregates or display-only product rows. Native Trade requires verified Member identities, SPU/SKU line items, price allocation, delivery mode, payment linkage, status history, and promotion state that those projections do not contain. + +## Delivered contract + +- `yudao-server` activates `yudao-module-trade`; native `TradeOrderApiImpl` is now authoritative and the temporary Promotion fallback from EDU-026 is removed. +- Existing `/trade/order`, `/trade/config`, `/app-api/trade/order`, and `/app-api/trade/cart` contracts remain owned by Trade. Education adds no shadow order or cart API. This slice proves the administrator order/config interface and core persistence; it does not yet claim successful app checkout. +- `TradeOrderDO`, `TradeOrderItemDO`, `TradeOrderLogDO`, `TradeConfigDO`, and `CartDO` extend `TenantBaseDO`, so their native mapper/service access participates in framework tenant injection. +- V4390 mounts the existing order and trade-config Vben pages below an Education `交易中心` with the exact native query/update/pick-up/config permissions. +- Deferred after-sale, delivery-master-data, and brokerage tables are not silently activated. V4390 rejects any pre-existing version of those tables until a dedicated tenant-safe slice owns their schema and UI. + +## Database contract + +V4390 creates tenant-scoped PostgreSQL tables: + +- `trade_config` +- `trade_cart` +- `trade_order` +- `trade_order_item` +- `trade_order_log` + +All use `(tenant_id,id)` ownership keys and explicitly named identity sequences. Order Item → Order/Cart/Product and Order Log → Order references are tenant-qualified. Cart → Product, Order → Pay Order, and Order → Promotion Coupon references are also tenant-qualified. Checks enforce native order/type/terminal/delivery/refund/cancel/log enums, amount/count bounds, payment/cancel/delivery shapes, after-sale reference shape, and one active config per tenant. Indexes match administrator paging, member history, cart selection, auto-cancel/receive jobs, order details, and log history. + +V4390 seeds menu IDs 6960–6967 for the Trade root, order page, config page, and exact five controller permissions. + +## Legacy data decision + +The native Trade ledger starts empty. V4390 does not fabricate line items from Pay totals, guess tenant/member/SPU/SKU links, translate terminal statuses into a richer order lifecycle, or attach historical coupons and refunds without reviewed source identities. A later import slice requires an explicit reconciliation contract and quarantine path. + +## Verification + +- The focused Trade tenant contract proves all five activated records inherit `TenantBaseDO`. +- The focused V4390 Flyway scenario proves same IDs can exist across tenants, cross-tenant Cart/Order Item references fail, invalid cancel/config states fail, five explicit sequences exist, exact menus/permissions are installed, core global-table adoption fails closed, and deferred Trade tables fail closed. +- The full PostgreSQL Flyway suite passes with 46 scenarios through V4390. +- `mvn -pl yudao-server -am -DskipTests compile` succeeds with Product, Promotion, and Trade enabled. +- The existing Vben order/config pages pass the web app typecheck and scoped lint/format checks. + +## Explicitly open + +- Native after-sale, delivery express/template/pick-up-store, and brokerage persistence/UI require later tenant-safe slices. +- Normal checkout still invokes native Promotion discount and reward lookups whose tables are deferred, and special orders require additional Promotion families. V4390 therefore does not claim end-to-end purchase creation, seckill, bargain, combination, point, discount, or reward orders. +- Legacy order import, automatic education-entitlement fulfillment, refund-driven revocation, and production reconciliation/runbooks remain separate work. +- Target-environment role grants, scheduled-job deployment, and browser/API smoke evidence remain operational work. diff --git a/docs/education/migration/issues/EDU-028-native-checkout-promotions.md b/docs/education/migration/issues/EDU-028-native-checkout-promotions.md new file mode 100644 index 00000000..07e22798 --- /dev/null +++ b/docs/education/migration/issues/EDU-028-native-checkout-promotions.md @@ -0,0 +1,36 @@ +# EDU-028 — Activate native checkout Promotion activities + +- **Status:** done — tenant-owned discount/reward persistence, native APIs/UI, RBAC, and PostgreSQL evidence delivered +- **Type:** native module takeover / checkout dependency / tenant isolation +- **Phase:** 5 / commercialization foundation +- **Blockers:** EDU-025, EDU-026, EDU-027, PostgreSQL Flyway + +## Problem + +Every normal RuoYi Trade price calculation invokes the native limited-time discount and reward calculators. Those calculators call `DiscountActivityApi` and `RewardActivityApi` even when no activity is configured, so V4390's order core still failed with missing Promotion tables before it could return an empty promotion result. Replacing these APIs inside Education would duplicate Promotion ownership and bypass the native administration UI. + +## Delivered contract + +- `DiscountActivityDO`, `DiscountProductDO`, and `RewardActivityDO` extend `TenantBaseDO`; their existing services, mappers, controllers, and public APIs remain authoritative. +- V4400 creates tenant-scoped `promotion_discount_activity`, `promotion_discount_product`, and `promotion_reward_activity` tables with explicit PostgreSQL sequences and `(tenant_id,id)` ownership keys. +- Discount Product → Activity/SPU/SKU references are tenant-qualified. Active SKU uniqueness, status/time, discount type/value, reward condition/scope, and JSON reward-rule checks fail closed in PostgreSQL. +- The native cross-database `MyBatisUtils.findInSetWithParamIndex` already renders PostgreSQL `POSITION(...)` against the comma-separated `LongListTypeHandler` value; no mapper fork or Education shadow interface is introduced. +- Menu IDs 6970–6982 mount the existing `mall/promotion/discountActivity/index` and `mall/promotion/rewardActivity/index` Vben pages with the exact ten native controller permissions. + +## Legacy data decision + +V4400 starts both activity families empty. Legacy coupon/code campaigns are not equivalent to time-boxed SKU discounts or structured full-reduction/gift rules, and no activity, product scope, time range, or reward rule is inferred. + +## Verification + +- The Promotion tenant contract proves all three activated records participate in framework tenant isolation. +- The focused V4400 scenario proves cross-tenant identifier reuse, composite reference rejection, active-SKU uniqueness, PostgreSQL scope membership semantics, reward-rule/scope rejection, three explicit sequences, exact routes/permissions, and fail-closed global-table adoption. +- A Spring/MyBatis integration test invokes the real `DiscountActivityApiImpl` and `RewardActivityApiImpl` against migrated PostgreSQL tables and proves empty normal-checkout lookups return empty lists rather than missing-table errors. +- The full PostgreSQL Flyway suite passes 47 scenarios through V4400. + +## Explicitly open + +- This slice removes the normal price pipeline's discount/reward missing-table blocker; it does not claim end-to-end order creation. Delivery express/template/pick-up persistence and configured Pay App/Channel runtime data remain prerequisites for applicable checkout modes. +- Seckill, bargain, combination, point, and other special-order Promotion tables remain separate tenant-safe activations. +- Legacy Product/campaign/order import, purchase-driven Education entitlement fulfillment, refund-driven revocation, and production API/browser smoke evidence remain separate work. + diff --git a/docs/education/migration/issues/EDU-029-native-trade-delivery.md b/docs/education/migration/issues/EDU-029-native-trade-delivery.md new file mode 100644 index 00000000..f39ae700 --- /dev/null +++ b/docs/education/migration/issues/EDU-029-native-trade-delivery.md @@ -0,0 +1,34 @@ +# EDU-029 — Activate native Trade delivery + +- **Status:** done — tenant-owned delivery persistence, native checkout calculation, RBAC, UI, and PostgreSQL evidence delivered +- **Type:** native module takeover / checkout dependency / tenant isolation +- **Phase:** 5 / commercialization foundation +- **Blockers:** EDU-025, EDU-027, EDU-028, PostgreSQL Flyway + +## Problem + +Normal native Trade checkout supports express delivery and store pickup. Express pricing reads Product SPU delivery-template IDs, Member addresses, per-tenant Trade configuration, template charge/free rules, and delivery areas. Pickup validates an enabled store. The source backend has no equivalent physical-shipping aggregate, so implementing an Education-owned delivery API would duplicate RuoYi Trade and leave its existing administration UI disconnected. + +## Delivered contract + +- `DeliveryExpressDO`, `DeliveryExpressTemplateDO`, `DeliveryExpressTemplateChargeDO`, `DeliveryExpressTemplateFreeDO`, and `DeliveryPickUpStoreDO` extend `TenantBaseDO`; the native services, mappers, controllers, calculators, and Vben pages remain authoritative. +- V4410 creates all five tables with explicit sequences, `(tenant_id,id)` ownership, tenant-qualified template-child references, and a charge-mode-qualified reference that prevents child/template mode drift. +- Comma-separated area/user IDs are validated in the database using the formats consumed by `IntegerListTypeHandler` and `LongListTypeHandler`. Amounts, counts, charge modes, status, address, business hours, and coordinates fail closed. +- Product SPU → delivery template and Trade Order → pickup store references are tenant-qualified. Pickup orders require a store; relevant lookup indexes are installed. +- Menu IDs 6990–7006 mount the existing Express, Express Template, and Pickup Store pages with the exact thirteen permissions exposed by the native controllers. + +## Legacy data decision + +V4410 starts delivery configuration empty. The source backend contains no authoritative express company, freight rule, delivery area, store, coordinates, hours, or verifier mapping. No product is silently assigned a template and no store is fabricated. + +## Verification + +- The delivery tenant contract proves all five activated records participate in framework tenant isolation. +- The focused V4410 Flyway scenario proves cross-tenant identifier reuse, Product/template and Order/store ownership, charge-mode consistency, area/location checks, pickup-order shape, five explicit sequences, exact routes/permissions, and fail-closed global-table adoption. +- A Spring/MyBatis PostgreSQL integration test uses the production tenant SQL interceptor, creates same-named native templates in two tenants, proves isolated mapper results, reads real charge/free rules, and drives `TradeDeliveryPriceCalculator` through the persisted express template to a 700-cent delivery fee. +- The full Flyway suite passes 48 scenarios through V4410; server and Vben verification are recorded in the migration UI report. + +## Explicitly open + +- Delivery persistence and calculation are active, but configured Pay App/Channel data and a target-environment order/payment smoke remain prerequisites for an end-to-end paid checkout claim. +- Trade after-sale and brokerage, special-order Promotion families, explicit legacy Product/coupon/order import, purchase-driven Education entitlement fulfillment, and refund-driven revocation remain separate slices. diff --git a/docs/education/migration/issues/EDU-030-native-trade-after-sale.md b/docs/education/migration/issues/EDU-030-native-trade-after-sale.md new file mode 100644 index 00000000..222c06d8 --- /dev/null +++ b/docs/education/migration/issues/EDU-030-native-trade-after-sale.md @@ -0,0 +1,36 @@ +# EDU-030 — Activate native Trade after-sale + +- **Status:** done — tenant-owned after-sale persistence, native state machine, Pay Refund bridge, RBAC, corrected Vben UI, and PostgreSQL evidence delivered +- **Type:** native module takeover / refund lifecycle / tenant isolation +- **Phase:** 5 / commercialization foundation +- **Blockers:** EDU-022, EDU-025, EDU-027, EDU-029, PostgreSQL Flyway + +## Problem + +The source backend stores order-level UUID rows in `public.commerce_refund_requests` and append-only `public.commerce_refund_events`, with statuses `requested`, `approved`, `processing`, `succeeded`, `failed`, `rejected`, and `cancelled`. RuoYi already owns a richer line-item after-sale state machine: Member application, administrator agree/disagree, return logistics, receipt/refusal, Pay Refund creation/callback, order-item state updates, immutable operation logs, exact RBAC, and an existing Vben list/detail page. Reimplementing that lifecycle in Education would create a second refund ledger and bypass native Trade/Pay coordination. + +## Delivered contract + +- `AfterSaleDO` and `AfterSaleLogDO` extend `TenantBaseDO`; existing app/admin controllers, `AfterSaleServiceImpl`, `AfterSaleLogServiceImpl`, order updates, and `PayRefundApi` remain authoritative. +- V4420 creates `trade_after_sale` and `trade_after_sale_log` with explicit sequences and `(tenant_id,id)` ownership. Order, order item, SPU, SKU, Pay Refund, delivery express, log, and order-item back-reference constraints are tenant-qualified. +- Database checks enforce native status/type/way values, non-negative amounts, required audit/return/refund facts, JSON arrays, valid log actor/operation/status values, unique tenant numbers, and at most one active after-sale per order item. +- Menu IDs 7010–7015 reuse `mall/trade/afterSale/index` with exactly `trade:after-sale:query`, `agree`, `disagree`, `receive`, and `refund` permissions. +- The Vben page sends the backend's `auditReason` field, requires `refuseMemo` in a locked/validated refusal modal, displays the actual application `createTime`, and guards every action with its exact permission. Backend request validation now rejects blank refusal notes and the detail response exposes `createTime`. + +## Legacy data decision + +V4420 starts the native after-sale ledger empty. A source request identifies an aggregate UUID order but does not identify a verified native Member, `trade_order_item`, SPU/SKU allocation, return quantity, delivery company, or Pay Refund row. Its status history also does not prove the native line-item and return-logistics transitions. Automatic import would invent ownership and lifecycle facts, so explicit legacy refund import is deferred until legacy Product, Order, Order Item, and Member mappings are reviewed. + +## Verification + +- The after-sale tenant contract proves both native records participate in framework tenant isolation. +- The focused V4420 Flyway scenario proves cross-tenant identifier reuse, tenant-qualified ownership/back-references, two explicit sequences, audit/log rejection, exact route/permissions, and fail-closed adoption of a pre-existing global table. +- A Spring/MyBatis PostgreSQL integration test imports the real services and production tenant SQL interceptor, creates same-ID Product/Order/Order Item fixtures in tenants 10 and 20, and proves isolated create, page, detail, and log reads plus order-item update calls. +- The full PostgreSQL Flyway suite passes 49 scenarios through V4420. The server reactor compiles, and the corrected Vben page passes typecheck plus scoped oxlint/oxfmt checks. + +## Explicitly open + +- Trade brokerage and special-order Promotion families remain separate native activation slices. +- Legacy Product/coupon/order/refund import requires explicit UUID-to-native mappings and reconciliation artifacts; V4420 does not reinterpret source refunds. +- Configured target Pay App/Channel data and a deployed order/payment/refund smoke are still required for end-to-end production evidence. +- Purchase-driven Education entitlement fulfillment and refund-driven entitlement revocation remain separate orchestration work. diff --git a/docs/education/migration/issues/EDU-031-native-trade-brokerage.md b/docs/education/migration/issues/EDU-031-native-trade-brokerage.md new file mode 100644 index 00000000..680417b7 --- /dev/null +++ b/docs/education/migration/issues/EDU-031-native-trade-brokerage.md @@ -0,0 +1,43 @@ +# EDU-031 — Activate native Trade brokerage + +- **Status:** done — native tenant-owned relationships, commission records, withdrawal/Pay Transfer bridge, exact RBAC, corrected Vben UI, and PostgreSQL evidence delivered +- **Type:** native module takeover / two-level commission / tenant isolation +- **Phase:** 5 / commercialization foundation +- **Blockers:** EDU-024, EDU-027, PostgreSQL Flyway + +## Problem + +The source backend has referral codes, leads, team edges, tracks, QR codes, CRM assignment, tenant commission settings, settlement aggregates/items, and proof/export events. RuoYi already owns a different but substantial Trade brokerage lifecycle: Member-backed promoter relationships, first/second-level order commissions, freeze/unfreeze, cancellation, withdrawals, Pay Transfer composition, app/admin APIs, scheduled settlement work, exact permissions, and three Vben administration pages. Rebuilding those overlapping behaviors in Education would create a second commission ledger and bypass native Member/Trade/Pay coordination. + +Primary source evidence remains in: + +- `/Users/tiku1/code/tiku-backend/supabase/migrations/202606210006_growth_referral_crm.sql` +- `/Users/tiku1/code/tiku-backend/supabase/migrations/202606290009_commission_settlements.sql` +- `/Users/tiku1/code/tiku-backend/supabase/migrations/202606290027_commission_settlement_proofs.sql` + +## Delivered contract + +- `BrokerageUserDO`, `BrokerageRecordDO`, and `BrokerageWithdrawDO` extend `TenantBaseDO`; native app/admin controllers, services, mappers, jobs, Member lookups, Trade order integration, and Pay Transfer API remain authoritative. +- V4430 creates `trade_brokerage_user`, `trade_brokerage_record`, and `trade_brokerage_withdraw` with `(tenant_id,id)` ownership, explicit record/withdraw sequences, tenant-qualified relationship/source/withdrawal/Pay Transfer/Trade Order references, commission idempotency, amount/state/account/audit/transfer checks, and a 0–99 withdrawal-fee range. +- Menu IDs 7020–7031 reuse the existing user, record, and withdrawal pages with exactly eight controller permissions: user query/create/update-bind/clear-bind/update-enable, record query, and withdrawal query/audit. +- The Vben user page uses those real permissions for both drill-downs, disables the eligibility switch without update permission, exposes responsive forms/tables, and its API types now match the backend responses. Withdrawal rejection trims and requires a non-blank reason on both sides. +- Immediate-settlement commission records now persist a settlement time, so the native time-range summary and ranking SQL includes zero-freeze commissions. + +## Legacy data decision + +V4430 activates the native ledger empty. Source referral/settlement rows use UUID identities and carry CRM lead assignment, referral-code/track attribution, settlement-batch state, and proof/export semantics that have no verified one-to-one mapping to native Member IDs, `trade_order` rows, two-level promoter relations, Pay Transfer rows, or immutable evidence artifacts. Importing them now would invent identity and settlement facts. Explicit referral CRM and commission-settlement import stays deferred until Member, lead, Order, payment, and proof mappings are reviewed; those source-only semantics are not declared retired. + +## Verification + +- The focused V4430 scenario proves table/sequence/check/index shape, same-number cross-tenant identities, tenant-qualified references, exact routes/permissions, and fail-closed adoption of a pre-existing global table. +- The tenant contract proves all three native records participate in framework tenant isolation and withdrawal inputs reject missing type, non-positive price, and blank audit reason. +- A Spring/MyBatis PostgreSQL integration test imports the real user/record services and production tenant SQL interceptor. Tenants 10 and 20 create the same Member IDs and business ID with different commission percentages; relationship/page reads, balances, MyBatis-Join summaries, and native annotated summary/ranking SQL remain isolated. +- The corrected Vben pages pass workspace typecheck and scoped oxlint/oxfmt checks. +- The full PostgreSQL Flyway suite passes 50 scenarios through V4430, and the server clean reactor compiles all 28 modules. + +## Explicitly open + +- Source referral code/lead/team-edge/track/QR/CRM-assignment semantics and settlement proof/export aggregates require dedicated mapping/import slices. +- Special-order Promotion families and explicit legacy Product/coupon/order/refund import remain separate. +- Purchase-driven Education entitlement fulfillment and refund-driven entitlement revocation remain separate orchestration work. +- Configured Pay runtime, scheduled unfreeze/transfer operation, and deployed order-to-commission-to-withdrawal smoke evidence remain required for production acceptance. diff --git a/docs/education/migration/issues/EDU-032-native-promotion-seckill.md b/docs/education/migration/issues/EDU-032-native-promotion-seckill.md new file mode 100644 index 00000000..b63b14d0 --- /dev/null +++ b/docs/education/migration/issues/EDU-032-native-promotion-seckill.md @@ -0,0 +1,39 @@ +# EDU-032 — Activate native Promotion seckill + +- **Status:** done — native tenant-owned seckill configuration, activities, SKU stock, Trade Order bridge, exact RBAC, corrected Vben UI, and PostgreSQL evidence delivered +- **Type:** native module takeover / special-order promotion / tenant isolation +- **Phase:** 5 / commercialization foundation +- **Blockers:** EDU-025, EDU-027, PostgreSQL Flyway + +## Problem + +The source backend has no seckill table, endpoint, job, or administration surface. RuoYi already owns a complete Promotion seckill lifecycle: time configurations, SPU/SKU activity authoring, atomic stock changes, Trade Order integration, app/admin APIs, exact permissions, and two Vben administration pages. Rebuilding those behaviors in Education would create a second promotion inventory and bypass native Product, Promotion, Trade, Member, and tenant/RBAC coordination. + +Repository-wide source inventory under `/Users/tiku1/code/tiku-backend` found no seckill or 秒杀 capability. That absence is a data decision: the target capability starts empty and no legacy records are invented. + +## Delivered contract + +- `SeckillConfigDO`, `SeckillActivityDO`, and `SeckillProductDO` extend `TenantBaseDO`; the native controllers, services, mappers, Product APIs, Trade Order fields, and Vben pages remain authoritative. +- V4440 creates `promotion_seckill_config`, `promotion_seckill_activity`, and `promotion_seckill_product` with `(tenant_id,id)` ownership, explicit PostgreSQL sequences, tenant-qualified Product SPU/SKU and activity references, JSON/time/price/stock/limit/state constraints, and fail-closed adoption of pre-existing global tables. +- Tenant-aware triggers validate every configured time slot, keep product activity snapshots aligned, and reject deletion of a slot still used by an activity. `trade_order` gains a tenant-qualified seckill-activity reference and an order-type/activity consistency constraint. +- Menu IDs 7040–7051 reuse the existing activity and time-config pages with exactly nine controller permissions: five activity permissions and four configuration permissions. +- Service validation rejects duplicate SKUs, seckill prices above native SKU prices, stock above native SKU stock, invalid limit relationships, and unsafe stock restoration. Closing an activity transactionally disables its product snapshots; a used time configuration cannot be deleted. +- The Vben pages use the real backend field shapes and permissions, validate time/limit/product relationships, handle empty products and prices safely, use the correct `0=enabled` switch direction, and provide responsive forms. + +## Legacy data decision + +V4440 activates all three native tables empty. There is no source seckill state to import, reconcile, or retire. This ticket does not reinterpret ordinary products, coupons, referral campaigns, or aggregate orders as seckill activities. + +## Verification + +- All 51 PostgreSQL Flyway scenarios pass through V4440, including the focused table/sequence/constraint/trigger/menu shape and fail-closed adoption cases. +- A Spring/MyBatis PostgreSQL integration test imports the real native seckill services and production tenant SQL interceptor. Tenants 10 and 20 create the same numeric config/activity/product IDs; page and detail reads remain isolated; two concurrent attempts for the final unit produce exactly one success; restoration, close-state propagation, and used-slot deletion protection are verified. +- Promotion contract tests prove the three records participate in framework tenant isolation and activity/config/product requests reject invalid time, limit, SKU, price, stock, status, and slider-image inputs. +- The scoped Vben seckill files pass oxlint and oxfmt checks, and the complete `@vben/web-antd` typecheck passes. +- The non-clean Maven reactor install through Education succeeds across 26 required modules without stopping the running server. + +## Explicitly open + +- Combination, Bargain, Point, and any other special-order Promotion families require separate tenant-safe activation slices. +- Explicit legacy Product/coupon/order/refund/referral/settlement-proof import remains separate; no source seckill import is needed. +- Purchase-driven Education entitlement fulfillment, refund-driven entitlement revocation, configured Pay runtime, and deployed end-to-end seckill checkout evidence remain required for production acceptance. diff --git a/docs/education/migration/issues/EDU-033-native-promotion-combination.md b/docs/education/migration/issues/EDU-033-native-promotion-combination.md new file mode 100644 index 00000000..f83e6602 --- /dev/null +++ b/docs/education/migration/issues/EDU-033-native-promotion-combination.md @@ -0,0 +1,39 @@ +# EDU-033 — Activate native Promotion combination + +- **Status:** done — native tenant-owned combination activities, SKU pricing, group records, Trade Order bridge, exact RBAC, corrected Vben UI, and PostgreSQL concurrency evidence delivered +- **Type:** native module takeover / special-order promotion / tenant isolation +- **Phase:** 5 / commercialization foundation +- **Blockers:** EDU-025, EDU-027, PostgreSQL Flyway + +## Problem + +RuoYi already owns the complete group-buying lifecycle: SPU/SKU activity authoring, head/member records, group capacity and expiry, Trade Order integration, app/admin APIs, Member and Product lookups, permissions, and Vben administration. Rebuilding it in Education would create a second promotion/order aggregate and bypass the native Product, Promotion, Trade, Member, tenant, and RBAC boundaries. + +The source backend has no group-buying capability. Its only `combination` value appears in `docs/pb_schema.json` and `apps/api/src/features/tenant-content/imports.ts`, where it means an education combination-question type. It must not be interpreted as a promotion activity, product, order, or group record. + +## Delivered contract + +- `CombinationActivityDO`, `CombinationProductDO`, and `CombinationRecordDO` extend `TenantBaseDO`; native controllers, services, mappers, Product/Member/Trade APIs, jobs, app endpoints, and Vben pages remain authoritative. +- V4450 creates `promotion_combination_activity`, `promotion_combination_product`, and `promotion_combination_record` with `(tenant_id,id)` ownership, PostgreSQL sequences, tenant-qualified Product SPU/SKU/activity/order/head references, snapshot/time/price/limit/state constraints, and fail-closed adoption of pre-existing global tables. +- Product triggers require activity/SPU/SKU/status/time consistency and prevent a combination price above the native SKU price. Record and Trade triggers lock the head row, enforce capacity and activity/order/user/head consistency, allow the order-before-record bridge state, and reject deletion of an activity with group records. +- Menu IDs 7060–7067 reuse the existing activity and record pages with exactly six controller permissions: five activity permissions and one record query permission. +- Request and service validation reject blank/oversized names, invalid time and limit relationships, groups smaller than two, duplicate or mismatched SKUs, non-positive identifiers/prices/counts, prices above the native SKU price, and cross-activity parent groups. Closing an activity transactionally disables product snapshots; activity updates retain and refresh snapshot status/time. +- Head creation returns the persisted head-record ID rather than the `0` sentinel. Joining locks the head before validation/insertion, preventing concurrent over-capacity membership. +- The Vben activity and record pages use the backend VO shapes and combination-record dictionary, validate positive/time/limit/product inputs, handle missing prices/products, pass the correct head ID to the member dialog, expose exact permission guards, and use viewport-bounded dialogs and keyboard-operable showcase controls. + +## Legacy data decision + +V4450 activates the native tables empty. No legacy rows are imported or invented. Education combination questions remain Education content and are not mapped to Mall group buying; ordinary products and aggregate orders are likewise not reinterpreted. + +## Verification + +- All 52 PostgreSQL Flyway scenarios pass through V4450, including focused table/sequence/constraint/trigger/menu shape, order bridge, cross-tenant same-ID, and fail-closed adoption cases. +- A Spring/MyBatis PostgreSQL integration test imports the real native activity and record services plus the production tenant SQL interceptor. Tenants 10 and 20 create the same numeric activity/product IDs; page, record, and summary reads remain isolated; activity edits and closure propagate product snapshots; a head returns its real record ID; two concurrent members competing for the final place produce exactly one success; cross-activity joins and deletion with records fail closed; Trade Order/record/head references remain consistent. +- Promotion contract tests prove all three records participate in framework tenant isolation, request DTOs carry the required validation annotations and relationship checks, and the head response conversion never returns the sentinel. +- Scoped Vben combination files pass oxlint and oxfmt checks, and the complete `@vben/web-antd` typecheck passes. + +## Explicitly open + +- Bargain, Point, and any other special-order Promotion families require separate tenant-safe activation slices. +- Explicit legacy Product/coupon/order/refund/referral/settlement-proof import remains separate; no source combination import is needed. +- Purchase-driven Education entitlement fulfillment, refund-driven entitlement revocation, configured Pay runtime, virtual-group expiry job operations, and deployed end-to-end combination checkout evidence remain required for production acceptance. diff --git a/docs/education/migration/issues/README.md b/docs/education/migration/issues/README.md index 51c9629a..09a03782 100644 --- a/docs/education/migration/issues/README.md +++ b/docs/education/migration/issues/README.md @@ -48,6 +48,25 @@ Commerce ownership decisions All owner/contract decisions └── EDU-014 Extended learning waves bounded representative wave delivered; blocked families explicitly deferred └── EDU-015 Operational independence contracts implemented; release/deployment evidence remains + +EDU-003 + tenant-admin inventory + └── EDU-017 Tenant appearance/theme bounded appearance/settings/theme lifecycle delivered; integrations/secrets/codes deferred + └── EDU-018 Native payment/social admin Pay/System controllers, RBAC, and Vben pages reused + ├── EDU-019 Learning activation codes bounded generation/redemption, entitlement composition, and Vben page delivered + └── EDU-020 Native Pay activation Pay module plus tenant App/Channel schema and parent isolation delivered + └── EDU-021 Legacy Pay import explicit tenant-account mapping into native Pay with redacted audit delivered + └── EDU-022 Native Pay transactions tenant order/refund/notify ledgers and native UI delivered + └── EDU-023 Legacy Pay transactions reconciled terminal import and redacted audit delivered + └── EDU-024 Native Pay Transfer/Wallet tenant ledgers and native UI delivered + └── EDU-025 Native Mall Product tenant catalog and native UI delivered + └── EDU-026 Native Mall Promotion coupon templates/instances and native UI delivered + └── EDU-027 Native Mall Trade order core and native UI delivered + └── EDU-028 Native checkout discount/reward activities and native UI delivered + └── EDU-029 Native Trade delivery and native UI delivered + └── EDU-030 Native Trade after-sale and corrected native UI delivered + └── EDU-031 Native Trade brokerage and corrected native UI delivered + └── EDU-032 Native Promotion Seckill and corrected native UI delivered + └── EDU-033 Native Promotion Combination and corrected native UI delivered ``` ## Recommended execution order @@ -55,7 +74,8 @@ All owner/contract decisions 1. Complete production release evidence for **EDU-015** using the Pilot runbook and target-only `JAVA_READ` deployment. 2. Add deferred EDU-011 scanner/parser/export adapters only when their owning platform contracts and deployment are available. 3. Add automatic EDU-013 fulfillment only after Mall/Pay/CRM expose the recorded public events; keep manual trusted fulfillment and access fail-closed meanwhile. -4. Select the next EDU-014 family only after its entitlement, privacy, and owner decisions are recorded. +4. EDU-025 activates native Product, EDU-026 activates coupons, EDU-027 activates the normal Trade order core, EDU-028 activates discount/reward, EDU-029 activates delivery, EDU-030 activates after-sale/Pay Refund, EDU-031 activates native brokerage, EDU-032 activates native Seckill, and EDU-033 activates native Combination activities, group records, atomic capacity, exact RBAC, and corrected administration pages. Continue with Bargain/Point and other Promotion families, source referral CRM/settlement-proof import, explicit legacy product/coupon/order/refund import, automatic Mall/Pay fulfillment, refund-driven entitlement revocation, production export/runbook evidence, legacy activation-code import, tenant PNVS, and encrypted generic secret storage as separate slices. +5. Select the next EDU-014 family only after its entitlement, privacy, and owner decisions are recorded. ## Phase 0 completion caveat diff --git a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/asset/EducationAssetAdmissionServiceImpl.java b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/asset/EducationAssetAdmissionServiceImpl.java index 8f835c8f..aa229439 100644 --- a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/asset/EducationAssetAdmissionServiceImpl.java +++ b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/asset/EducationAssetAdmissionServiceImpl.java @@ -63,7 +63,7 @@ public class EducationAssetAdmissionServiceImpl implements EducationAssetAdmissi asset.setOriginalName(name); asset.setContentType(contentType); asset.setSize((long) bytes.length); - asset.setSha256(descriptor.checksumSha256() != null ? descriptor.checksumSha256() : DigestUtil.sha256Hex(bytes)); + asset.setSha256(DigestUtil.sha256Hex(bytes)); asset.setFileReference(descriptor.reference()); asset.setScanStatus(scanStatus.name()); assetMapper.insert(asset); diff --git a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/ImportObjectScanGateway.java b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/ImportObjectScanGateway.java index 501e9887..95ab6c1c 100644 --- a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/ImportObjectScanGateway.java +++ b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/ImportObjectScanGateway.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.education.service.importjob; public interface ImportObjectScanGateway { - enum ScanResult { CLEAN, INFECTED, ERROR, UNAVAILABLE } - ScanResult scan(String reference, String name, String mimeType, long size, String checksumSha256); + enum ScanResult { CLEAN, INFECTED, ERROR } + ScanResult scan(String objectKey); } diff --git a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/InfraFileImportObjectScanGateway.java b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/InfraFileImportObjectScanGateway.java deleted file mode 100644 index 46eb4e66..00000000 --- a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/InfraFileImportObjectScanGateway.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.education.service.importjob; - -import cn.iocoder.yudao.module.infra.api.file.FileApi; -import cn.iocoder.yudao.module.infra.api.file.FileDescriptor; -import cn.iocoder.yudao.module.infra.api.file.FileScanStatus; -import org.springframework.stereotype.Service; - -@Service -public class InfraFileImportObjectScanGateway implements ImportObjectScanGateway { - - private final FileApi fileApi; - - public InfraFileImportObjectScanGateway(FileApi fileApi) { - this.fileApi = fileApi; - } - - @Override - public ScanResult scan(String reference, String name, String mimeType, long size, String checksumSha256) { - FileScanStatus status; - try { - status = fileApi.scan(new FileDescriptor(reference, name, mimeType, size, checksumSha256)); - } catch (RuntimeException ex) { - return ScanResult.ERROR; - } - return status == null ? ScanResult.UNAVAILABLE : ScanResult.valueOf(status.name()); - } -} diff --git a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/QuestionImportJobServiceImpl.java b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/QuestionImportJobServiceImpl.java index 1107f8dc..8a217122 100644 --- a/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/QuestionImportJobServiceImpl.java +++ b/yudao-module-education/src/main/java/cn/iocoder/yudao/module/education/service/importjob/QuestionImportJobServiceImpl.java @@ -133,8 +133,7 @@ public class QuestionImportJobServiceImpl implements QuestionImportJobService { finishFailed(job, token, "SCAN_UNAVAILABLE"); throw exception(QUESTION_IMPORT_SCAN_NOT_CLEAN); } - ImportObjectScanGateway.ScanResult scanResult = gateway.scan(asset.getObjectKey(), asset.getFileName(), - asset.getMimeType(), asset.getFileSizeBytes(), asset.getChecksumSha256()); + ImportObjectScanGateway.ScanResult scanResult = gateway.scan(asset.getObjectKey()); if (scanResult != ImportObjectScanGateway.ScanResult.CLEAN) { finishFailed(job, token, "SCAN_" + scanResult.name()); throw exception(QUESTION_IMPORT_SCAN_NOT_CLEAN); diff --git a/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/importjob/InfraFileImportObjectScanGatewayTest.java b/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/importjob/InfraFileImportObjectScanGatewayTest.java deleted file mode 100644 index 65d44f0b..00000000 --- a/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/importjob/InfraFileImportObjectScanGatewayTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.education.service.importjob; - -import cn.iocoder.yudao.module.infra.api.file.FileApi; -import cn.iocoder.yudao.module.infra.api.file.FileDescriptor; -import cn.iocoder.yudao.module.infra.api.file.FileScanStatus; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; - -class InfraFileImportObjectScanGatewayTest { - - @Test - void passesCompleteDescriptorAndMapsEveryStatus() { - FileApi fileApi = mock(FileApi.class); - InfraFileImportObjectScanGateway gateway = new InfraFileImportObjectScanGateway(fileApi); - FileDescriptor descriptor = new FileDescriptor("ref", "questions.csv", "text/csv", 3, - "039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81"); - for (FileScanStatus status : FileScanStatus.values()) { - when(fileApi.scan(descriptor)).thenReturn(status); - assertEquals(ImportObjectScanGateway.ScanResult.valueOf(status.name()), - gateway.scan(descriptor.reference(), descriptor.name(), descriptor.contentType(), - descriptor.size(), descriptor.checksumSha256())); - } - verify(fileApi, times(FileScanStatus.values().length)).scan(descriptor); - } - - @Test - void nullAndExceptionsFailClosed() { - FileApi fileApi = mock(FileApi.class); - InfraFileImportObjectScanGateway gateway = new InfraFileImportObjectScanGateway(fileApi); - when(fileApi.scan(any())).thenReturn(null).thenThrow(new IllegalStateException("down")); - - assertEquals(ImportObjectScanGateway.ScanResult.UNAVAILABLE, - gateway.scan("ref", "x", null, 1, null)); - assertEquals(ImportObjectScanGateway.ScanResult.ERROR, - gateway.scan("ref", "x", null, 1, null)); - } -} diff --git a/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/importjob/QuestionImportJobServiceImplTest.java b/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/importjob/QuestionImportJobServiceImplTest.java index 412411ea..a59d4576 100644 --- a/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/importjob/QuestionImportJobServiceImplTest.java +++ b/yudao-module-education/src/test/java/cn/iocoder/yudao/module/education/service/importjob/QuestionImportJobServiceImplTest.java @@ -79,8 +79,7 @@ class QuestionImportJobServiceImplTest { when(jobMapper.claimById(eq(101L), eq("PREVIEW_PENDING"), anyString(), anyString(), anyLong())) .thenReturn(claimed); when(assetMapper.selectTenantAsset(10L, 88L)).thenReturn(asset(88L)); - when(scanGateway.scan("object/10/questions.csv", "questions.csv", "text/csv", 128L, "checksum")) - .thenReturn(ImportObjectScanGateway.ScanResult.CLEAN); + when(scanGateway.scan("object/10/questions.csv")).thenReturn(ImportObjectScanGateway.ScanResult.CLEAN); when(jobMapper.finishClaim(eq(10L), eq(101L), eq("PREVIEW_PENDING"), eq("PREVIEW_READY"), anyString(), eq("CLEAN"), eq("UNAVAILABLE"), contains("METADATA_ONLY"), isNull(), isNull(), isNull(), isNull(), isNull(), isNull())).thenReturn(1); @@ -107,28 +106,6 @@ class QuestionImportJobServiceImplTest { verifyNoInteractions(parser, lifecycleService); } - @Test - void nonCleanStatusesHaveStableFailureCodesAndNeverParse() { - for (ImportObjectScanGateway.ScanResult result : List.of( - ImportObjectScanGateway.ScanResult.INFECTED, - ImportObjectScanGateway.ScanResult.ERROR, - ImportObjectScanGateway.ScanResult.UNAVAILABLE)) { - reset(jobMapper, assetMapper, scanGateway, parser, lifecycleService); - when(jobMapper.claimById(eq(101L), eq("PREVIEW_PENDING"), anyString(), anyString(), anyLong())) - .thenReturn(job(101L, "PREVIEW_PENDING")); - when(assetMapper.selectTenantAsset(10L, 88L)).thenReturn(asset(88L)); - when(scanGateway.scan("object/10/questions.csv", "questions.csv", "text/csv", 128L, "checksum")) - .thenReturn(result); - - assertThrows(ServiceException.class, () -> service.preview(101L)); - - verify(jobMapper).finishClaim(eq(10L), eq(101L), eq("PREVIEW_PENDING"), eq("FAILED"), - anyString(), isNull(), isNull(), isNull(), isNull(), isNull(), isNull(), isNull(), - eq("SCAN_" + result.name()), eq("SCAN_" + result.name())); - verifyNoInteractions(parser, lifecycleService); - } - } - @Test void executePropagatesClaimedTenantAndCreatesDrafts() { TenantContextHolder.clear(); @@ -154,7 +131,7 @@ class QuestionImportJobServiceImplTest { private ContentImportAssetDO asset(Long id) { ContentImportAssetDO asset = new ContentImportAssetDO(); asset.setId(id); asset.setTenantId(10L); asset.setObjectKey("object/10/questions.csv"); asset.setFileName("questions.csv"); - asset.setMimeType("text/csv"); asset.setFileSizeBytes(128L); asset.setChecksumSha256("checksum"); return asset; + asset.setMimeType("text/csv"); asset.setFileSizeBytes(128L); return asset; } private ContentImportJobDO job(Long id, String status) { diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java index 7f094b28..0a6f6445 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.infra.api.file; -import cn.hutool.crypto.digest.DigestUtil; import jakarta.validation.constraints.NotEmpty; /** @@ -47,10 +46,8 @@ public interface FileApi { * 保存有界文件内容,返回不泄露存储实现的公开描述。 */ default FileDescriptor createFile(FileContent content) { - byte[] bytes = content.bytes(); - String reference = createFile(bytes, content.name(), content.directory(), content.contentType()); - return new FileDescriptor(reference, content.name(), content.contentType(), bytes.length, - DigestUtil.sha256Hex(bytes)); + String reference = createFile(content.bytes(), content.name(), content.directory(), content.contentType()); + return new FileDescriptor(reference, content.name(), content.contentType(), content.bytes().length); } /** diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java index d49a30ba..f5d8da82 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java @@ -26,11 +26,6 @@ public class FileApiImpl implements FileApi { return fileService.createFile(content, name, directory, type); } - @Override - public FileScanStatus scan(FileDescriptor file) { - return fileService.scan(file); - } - @Override public String presignGetUrl(String url, Integer expirationSeconds) { return fileService.presignGetUrl(url, expirationSeconds); diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileDescriptor.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileDescriptor.java index 686c2365..0eb575df 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileDescriptor.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileDescriptor.java @@ -1,11 +1,7 @@ package cn.iocoder.yudao.module.infra.api.file; /** Public, storage-agnostic description of an accepted file. */ -public record FileDescriptor(String reference, String name, String contentType, long size, String checksumSha256) { - - public FileDescriptor(String reference, String name, String contentType, long size) { - this(reference, name, contentType, size, null); - } +public record FileDescriptor(String reference, String name, String contentType, long size) { public FileDescriptor { if (reference == null || reference.isBlank()) { @@ -14,8 +10,5 @@ public record FileDescriptor(String reference, String name, String contentType, if (size < 0 || size > FileContent.MAX_CONTENT_BYTES) { throw new IllegalArgumentException("file size is outside public contract bounds"); } - if (checksumSha256 != null && !checksumSha256.matches("(?i)[0-9a-f]{64}")) { - throw new IllegalArgumentException("file SHA-256 checksum is invalid"); - } } } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileScanStatus.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileScanStatus.java index 9e0838b3..5538130f 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileScanStatus.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileScanStatus.java @@ -4,6 +4,5 @@ package cn.iocoder.yudao.module.infra.api.file; public enum FileScanStatus { CLEAN, INFECTED, - ERROR, UNAVAILABLE } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java index 4bbc9521..b64df52e 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java @@ -23,12 +23,6 @@ public interface FileMapper extends BaseMapperX { .orderByDesc(FileDO::getId)); } - default FileDO selectLatestByUrl(String url) { - return selectLastOne(new LambdaQueryWrapperX() - .eq(FileDO::getUrl, url) - .orderByAsc(FileDO::getId)); - } - default FileDO selectLatestByConfigIdAndPath(Long configId, String path) { return selectLastOne(new LambdaQueryWrapperX() .eq(FileDO::getConfigId, configId) diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileScanProperties.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileScanProperties.java deleted file mode 100644 index 3b7b99b4..00000000 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileScanProperties.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.infra.framework.file.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.time.Duration; - -@ConfigurationProperties(prefix = "yudao.infra.file-scan") -public class FileScanProperties { - - private boolean enabled; - private String host = "127.0.0.1"; - private int port = 3310; - private Duration connectTimeout = Duration.ofSeconds(3); - private Duration readTimeout = Duration.ofSeconds(30); - private int chunkSize = 8192; - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public String getHost() { return host; } - public void setHost(String host) { this.host = host; } - public int getPort() { return port; } - public void setPort(int port) { this.port = port; } - public Duration getConnectTimeout() { return connectTimeout; } - public void setConnectTimeout(Duration connectTimeout) { this.connectTimeout = connectTimeout; } - public Duration getReadTimeout() { return readTimeout; } - public void setReadTimeout(Duration readTimeout) { this.readTimeout = readTimeout; } - public int getChunkSize() { return chunkSize; } - public void setChunkSize(int chunkSize) { this.chunkSize = chunkSize; } -} diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/YudaoFileAutoConfiguration.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/YudaoFileAutoConfiguration.java index 0d34e8d7..8e3f506f 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/YudaoFileAutoConfiguration.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/YudaoFileAutoConfiguration.java @@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.infra.framework.file.config; import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientFactory; import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientFactoryImpl; -import cn.iocoder.yudao.module.infra.framework.file.core.scan.ClamAvFileScanner; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -13,7 +11,6 @@ import org.springframework.context.annotation.Configuration; * @author 恭学教育 */ @Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(FileScanProperties.class) public class YudaoFileAutoConfiguration { @Bean @@ -21,9 +18,4 @@ public class YudaoFileAutoConfiguration { return new FileClientFactoryImpl(); } - @Bean - public ClamAvFileScanner clamAvFileScanner(FileScanProperties properties) { - return new ClamAvFileScanner(properties); - } - } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/scan/ClamAvFileScanner.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/scan/ClamAvFileScanner.java deleted file mode 100644 index 571a96de..00000000 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/scan/ClamAvFileScanner.java +++ /dev/null @@ -1,50 +0,0 @@ -package cn.iocoder.yudao.module.infra.framework.file.core.scan; - -import cn.iocoder.yudao.module.infra.api.file.FileScanStatus; -import cn.iocoder.yudao.module.infra.framework.file.config.FileScanProperties; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.InputStream; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.nio.charset.StandardCharsets; - -public class ClamAvFileScanner { - - private final FileScanProperties properties; - - public ClamAvFileScanner(FileScanProperties properties) { - this.properties = properties; - } - - public FileScanStatus scan(byte[] content) { - if (!properties.isEnabled()) return FileScanStatus.UNAVAILABLE; - try (Socket socket = new Socket()) { - socket.connect(new InetSocketAddress(properties.getHost(), properties.getPort()), - Math.toIntExact(properties.getConnectTimeout().toMillis())); - socket.setSoTimeout(Math.toIntExact(properties.getReadTimeout().toMillis())); - DataOutputStream output = new DataOutputStream(socket.getOutputStream()); - output.write("zINSTREAM\0".getBytes(StandardCharsets.US_ASCII)); - for (int offset = 0; offset < content.length; offset += properties.getChunkSize()) { - int length = Math.min(properties.getChunkSize(), content.length - offset); - output.writeInt(length); - output.write(content, offset, length); - } - output.writeInt(0); - output.flush(); - String response = readResponse(socket.getInputStream()); - if (response.endsWith(" OK")) return FileScanStatus.CLEAN; - if (response.endsWith(" FOUND")) return FileScanStatus.INFECTED; - return FileScanStatus.ERROR; - } catch (Exception ex) { - return FileScanStatus.UNAVAILABLE; - } - } - - private String readResponse(InputStream input) throws Exception { - ByteArrayOutputStream response = new ByteArrayOutputStream(); - for (int value; (value = input.read()) != -1 && value != 0 && value != '\n';) response.write(value); - return response.toString(StandardCharsets.US_ASCII); - } -} diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java index 53cfc8ea..23603d35 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java @@ -1,8 +1,6 @@ package cn.iocoder.yudao.module.infra.service.file; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.infra.api.file.FileDescriptor; -import cn.iocoder.yudao.module.infra.api.file.FileScanStatus; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; @@ -38,8 +36,6 @@ public interface FileService { String createFile(@NotEmpty(message = "文件内容不能为空") byte[] content, String name, String directory, String type); - FileScanStatus scan(FileDescriptor file); - /** * 生成文件预签名地址信息,用于上传 * diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java index d2585ee5..3ff0777a 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java @@ -9,15 +9,12 @@ import cn.hutool.crypto.digest.DigestUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.infra.api.file.FileDescriptor; -import cn.iocoder.yudao.module.infra.api.file.FileScanStatus; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient; -import cn.iocoder.yudao.module.infra.framework.file.core.scan.ClamAvFileScanner; import cn.iocoder.yudao.module.infra.framework.file.core.utils.FilePathUtils; import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils; import com.google.common.annotations.VisibleForTesting; @@ -25,7 +22,6 @@ import jakarta.annotation.Resource; import lombok.SneakyThrows; import org.springframework.stereotype.Service; -import java.security.MessageDigest; import java.util.List; import static cn.hutool.core.date.DatePattern.PURE_DATE_PATTERN; @@ -67,9 +63,6 @@ public class FileServiceImpl implements FileService { @Resource private FileMapper fileMapper; - @Resource - private ClamAvFileScanner fileScanner; - @Override public PageResult getFilePage(FilePageReqVO pageReqVO) { return fileMapper.selectPage(pageReqVO); @@ -111,29 +104,6 @@ public class FileServiceImpl implements FileService { return url; } - @Override - public FileScanStatus scan(FileDescriptor descriptor) { - FileDO file = fileMapper.selectLatestByUrl(descriptor.reference()); - if (file == null || !descriptor.reference().equals(file.getUrl())) return FileScanStatus.ERROR; - FileClient client = fileConfigService.getFileClient(file.getConfigId()); - if (client == null) return FileScanStatus.UNAVAILABLE; - byte[] content; - try { - content = client.getContent(file.getPath()); - } catch (Exception ex) { - return FileScanStatus.UNAVAILABLE; - } - if (content.length != descriptor.size() || file.getSize() == null || content.length != file.getSize()) { - return FileScanStatus.ERROR; - } - if (descriptor.checksumSha256() != null && !MessageDigest.isEqual( - DigestUtil.sha256Hex(content).getBytes(java.nio.charset.StandardCharsets.US_ASCII), - descriptor.checksumSha256().toLowerCase().getBytes(java.nio.charset.StandardCharsets.US_ASCII))) { - return FileScanStatus.ERROR; - } - return fileScanner.scan(content); - } - @VisibleForTesting String generateUploadPath(String name, String directory) { // 1.1 处理 name 和 directory 的合法性 diff --git a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/api/file/FileApiImplTest.java b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/api/file/FileApiImplTest.java index 1d7425b8..753054de 100644 --- a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/api/file/FileApiImplTest.java +++ b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/api/file/FileApiImplTest.java @@ -25,20 +25,15 @@ class FileApiImplTest { assertEquals("questions.csv", descriptor.name()); assertEquals("text/csv", descriptor.contentType()); assertEquals(3, descriptor.size()); - assertEquals("039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81", - descriptor.checksumSha256()); verify(fileService).createFile(new byte[]{1, 2, 3}, "questions.csv", "education/imports", "text/csv"); } @Test - void scannerDelegatesToFileService() { - FileService fileService = mock(FileService.class); - FileDescriptor descriptor = new FileDescriptor("opaque", "questions.csv", "text/csv", 3); - when(fileService.scan(descriptor)).thenReturn(FileScanStatus.CLEAN); - FileApiImpl api = new FileApiImpl(fileService); + void scannerUnavailableNeverReportsClean() { + FileApiImpl api = new FileApiImpl(mock(FileService.class)); - assertEquals(FileScanStatus.CLEAN, api.scan(descriptor)); - verify(fileService).scan(descriptor); + assertEquals(FileScanStatus.UNAVAILABLE, + api.scan(new FileDescriptor("opaque", "questions.csv", "text/csv", 3))); } @Test diff --git a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/scan/ClamAvFileScannerTest.java b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/scan/ClamAvFileScannerTest.java deleted file mode 100644 index 3b0d41fd..00000000 --- a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/scan/ClamAvFileScannerTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package cn.iocoder.yudao.module.infra.framework.file.core.scan; - -import cn.iocoder.yudao.module.infra.api.file.FileScanStatus; -import cn.iocoder.yudao.module.infra.framework.file.config.FileScanProperties; -import org.junit.jupiter.api.Test; - -import java.io.DataInputStream; -import java.net.ServerSocket; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.CompletableFuture; - -import static org.junit.jupiter.api.Assertions.*; - -class ClamAvFileScannerTest { - - @Test - void sendsClamdInstreamProtocol() throws Exception { - try (ServerSocket server = new ServerSocket(0)) { - CompletableFuture received = CompletableFuture.supplyAsync(() -> { - try (var socket = server.accept()) { - DataInputStream input = new DataInputStream(socket.getInputStream()); - assertArrayEquals("zINSTREAM\0".getBytes(StandardCharsets.US_ASCII), input.readNBytes(10)); - int length = input.readInt(); - byte[] content = input.readNBytes(length); - assertEquals(0, input.readInt()); - socket.getOutputStream().write("stream: OK\0".getBytes(StandardCharsets.US_ASCII)); - return content; - } catch (Exception ex) { - throw new RuntimeException(ex); - } - }); - FileScanProperties properties = properties(server.getLocalPort()); - - assertEquals(FileScanStatus.CLEAN, new ClamAvFileScanner(properties).scan(new byte[]{1, 2, 3})); - assertArrayEquals(new byte[]{1, 2, 3}, received.get()); - } - } - - @Test - void mapsFoundErrorAndDisabledFailClosed() throws Exception { - assertEquals(FileScanStatus.UNAVAILABLE, new ClamAvFileScanner(new FileScanProperties()).scan(new byte[]{1})); - assertEquals(FileScanStatus.INFECTED, scanResponse("stream: Eicar-Signature FOUND\0")); - assertEquals(FileScanStatus.ERROR, scanResponse("stream: size limit exceeded ERROR\0")); - } - - private FileScanStatus scanResponse(String response) throws Exception { - try (ServerSocket server = new ServerSocket(0)) { - CompletableFuture.runAsync(() -> { - try (var socket = server.accept()) { - DataInputStream input = new DataInputStream(socket.getInputStream()); - input.readNBytes(10); - for (int length; (length = input.readInt()) != 0;) input.readNBytes(length); - socket.getOutputStream().write(response.getBytes(StandardCharsets.US_ASCII)); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - }); - return new ClamAvFileScanner(properties(server.getLocalPort())).scan(new byte[]{1}); - } - } - - private FileScanProperties properties(int port) { - FileScanProperties properties = new FileScanProperties(); - properties.setEnabled(true); - properties.setHost("127.0.0.1"); - properties.setPort(port); - return properties; - } -} diff --git a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java index 5c169a77..4b14aec0 100644 --- a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java +++ b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java @@ -5,14 +5,11 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.framework.test.core.util.AssertUtils; -import cn.iocoder.yudao.module.infra.api.file.FileDescriptor; -import cn.iocoder.yudao.module.infra.api.file.FileScanStatus; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient; -import cn.iocoder.yudao.module.infra.framework.file.core.scan.ClamAvFileScanner; import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,9 +40,6 @@ public class FileServiceImplTest extends BaseDbUnitTest { @MockitoBean private FileConfigService fileConfigService; - @MockitoBean - private ClamAvFileScanner fileScanner; - @BeforeEach public void setUp() { FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true; @@ -152,43 +146,6 @@ public class FileServiceImplTest extends BaseDbUnitTest { assertEquals(content.length, file.getSize()); } - @Test - public void scanUsesOnlyRegisteredFileClientAndValidatesIntegrity() throws Exception { - byte[] content = {1, 2, 3}; - FileDO file = new FileDO().setConfigId(10L).setPath("safe/file.csv") - .setUrl("https://storage.example/file.csv").setSize(3L); - fileMapper.insert(file); - FileClient client = mock(FileClient.class); - when(fileConfigService.getFileClient(10L)).thenReturn(client); - when(client.getContent("safe/file.csv")).thenReturn(content); - when(fileScanner.scan(content)).thenReturn(FileScanStatus.CLEAN); - - FileScanStatus result = fileService.scan(new FileDescriptor(file.getUrl(), "file.csv", "text/csv", 3, - "039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81")); - - assertEquals(FileScanStatus.CLEAN, result); - verify(client).getContent("safe/file.csv"); - verify(fileScanner).scan(content); - } - - @Test - public void scanRejectsArbitraryUrlAndIntegrityMismatch() throws Exception { - assertEquals(FileScanStatus.ERROR, fileService.scan( - new FileDescriptor("http://127.0.0.1:8080/admin", "x", null, 1))); - verifyNoInteractions(fileConfigService, fileScanner); - - FileDO file = new FileDO().setConfigId(10L).setPath("safe/file.csv") - .setUrl("https://storage.example/file.csv").setSize(3L); - fileMapper.insert(file); - FileClient client = mock(FileClient.class); - when(fileConfigService.getFileClient(10L)).thenReturn(client); - when(client.getContent("safe/file.csv")).thenReturn(new byte[]{1, 2}); - - assertEquals(FileScanStatus.ERROR, fileService.scan( - new FileDescriptor(file.getUrl(), "file.csv", "text/csv", 3))); - verifyNoInteractions(fileScanner); - } - @Test public void testDeleteFile_success() throws Exception { // mock 数据 diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 7fd24eb6..364badbc 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -260,14 +260,6 @@ yudao: --- #################### 项目相关配置 #################### yudao: - infra: - file-scan: - enabled: ${FILE_SCAN_ENABLED:false} - host: ${FILE_SCAN_HOST:127.0.0.1} - port: ${FILE_SCAN_PORT:3310} - connect-timeout: ${FILE_SCAN_CONNECT_TIMEOUT:3s} - read-timeout: ${FILE_SCAN_READ_TIMEOUT:30s} - chunk-size: ${FILE_SCAN_CHUNK_SIZE:8192} info: version: 1.0.0 base-package: cn.iocoder.yudao diff --git a/yudao-ui/yudao-ui-admin-vben b/yudao-ui/yudao-ui-admin-vben new file mode 160000 index 00000000..4c3c2fcb --- /dev/null +++ b/yudao-ui/yudao-ui-admin-vben @@ -0,0 +1 @@ +Subproject commit 4c3c2fcb7df713d18028783a40dfa0251a70a55a diff --git a/yudao-ui/yudao-ui-admin-vben/README.md b/yudao-ui/yudao-ui-admin-vben/README.md deleted file mode 100644 index c6a866cc..00000000 --- a/yudao-ui/yudao-ui-admin-vben/README.md +++ /dev/null @@ -1,4 +0,0 @@ -基于 Vue3 + vben(ant-design-vue) 实现的管理后台。仓库地址: - -* Gitee: -* GitHub: